Purpose of this article
Another colleague was not keen on looking into our database, treating is as “a backend stuff”. Being a frontend developer with 13+ years of experience, he was not able to check if his REST POSTs and GETs requests are working properly, saving or returning some sample data from the storage. He had to rely on our help, even after we had created a special - but still simple - queries and shared it with him. That was, on the other hand, pretty much annoying.
I think that every Java developer should have at least basic knowledge of SQL and related areas. Then, life of a developer (and life of his co-workers) is easier. Not only it helps in everyday work, but also it gives another technical skill, making a programmer better prepared for an interview. It does not take too much: the SQL entry treshold is very low. And even basic commands are useful. More difficult areas are often handled by database specialist (lucky if you have one in your team). If not, having SQL fondamentals in your tech stack is a good starting point to resolve more complex problems - and often it saves you and your team.
This article is for Java developers (but not only Java) who want to quickly start with simple SQL commands and queries, as well as for SQL false beginners - in order to make a quick repetition of shamefully forgotten items.
I will start with SQL fundamentals, then I’ll explain less known (but still simple) topics. And finally, we will make more theoretical summary of terms and questions that may appear during development or at technical interview.
Sample relational database
For database connection and management, you can use tools like Workbench for MySQL or pgAdmin for Postgres. DBeaver is also nice for various flavours. But why shall we use third-party software when IntelliJ IDEA offers its great, in-built feature? All in all, we are paying for it (or a company that hires you is paying for the licence). So let’s take advantage of that and have all in one place! All the more so, because there exists JetBrains documentation for Database.
Pluralitas non est ponenda sine neccesitate. (William Ockham - Ockham’s Razor)
Sample database for training purpose can be found in the internet. Microsoft offers some SQL samples for its SQL flavour. The same is true for MySQL. I think Postgres and MariaDB samples can be found at GitHub. Cloud database storage and connection may be limited, especially for Microsoft and Azure. If someone asks: the cloud costs money - although sometimes you can get something for free - see No Free Lunch theorem
Local database can be created by installing e.g. MySQL Server on the hard drive and then filling it with sample data. But I would go or a database as Docker image.
~/IdeaProjects/blog$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES c81d61fa3746 mysql:5.7 "docker-entrypoint.s…" 2 years ago Up 2 hours 33060/tcp, 0.0.0.0:3307->3306/tcp, :::3307->3306/tcp persistency_db_1 29baec3383ba mariadb "docker-entrypoint.s…" 2 years ago Up 2 hours 0.0.0.0:3308->3306/tcp, :::3308->3306/tcp persistency_mariadb_1
Playing with Docker is quite different field of interest, so I will skip the details. There is a lot of on-line manuals. My private rule-of-thumb is Docker plus IntelliJ database tool: it saves time and space.
Create database and tables
Suppose we do not have a database, or we want to have a brand new, with empty tables. Initial script could look like this:
DROP DATABASE IF EXISTS customers; CREATE DATABASE IF NOT EXISTS customers; USE customers; DROP TABLE IF EXISTS branch_customers, branch_top_customer, field, turnover, customer, branch; CREATE TABLE customer ( customer_id INT NOT NULL, birth_date DATE NOT NULL, first_name VARCHAR(14) NOT NULL, last_name VARCHAR(16) NOT NULL, gender ENUM ('M','F') NOT NULL, registration_date DATE NOT NULL, PRIMARY KEY (customer_id) ); CREATE TABLE branch ( branch_id CHAR(4) NOT NULL, branch_name VARCHAR(40) NOT NULL, PRIMARY KEY (branch_id), UNIQUE KEY (branch_name) ); CREATE TABLE branch_top_customer ( customer_id INT NOT NULL, branch_id CHAR(4) NOT NULL, from_date DATE NOT NULL, to_date DATE NOT NULL, FOREIGN KEY (customer_id) REFERENCES customer (customer_id) ON DELETE CASCADE, FOREIGN KEY (branch_id) REFERENCES branch (branch_id) ON DELETE CASCADE, PRIMARY KEY (customer_id,branch_id) ); CREATE TABLE branch_customers ( customer_id INT NOT NULL, branch_id CHAR(4) NOT NULL, from_date DATE NOT NULL, to_date DATE NOT NULL, FOREIGN KEY (customer_id) REFERENCES customer (customer_id) ON DELETE CASCADE, FOREIGN KEY (branch_id) REFERENCES branch (branch_id) ON DELETE CASCADE, PRIMARY KEY (customer_id,branch_id) ); CREATE TABLE field ( customer_id INT NOT NULL, field_name VARCHAR(50) NOT NULL, from_date DATE NOT NULL, to_date DATE, FOREIGN KEY (customer_id) REFERENCES customer (customer_id) ON DELETE CASCADE, PRIMARY KEY (customer_id,field_name, from_date) ) ; CREATE TABLE turnover ( customer_id INT NOT NULL, turnover INT NOT NULL, from_date DATE NOT NULL, to_date DATE NOT NULL, FOREIGN KEY (customer_id) REFERENCES customer (customer_id) ON DELETE CASCADE, PRIMARY KEY (customer_id, from_date) ) ;
Now we have a structure, but it is empty. As sample databases often comes filled with data, or at least with data upload scripts, I will skip this part. It is good to remember that writing loading script contains a lot of repetitive items, and it can be automated.
In our use case, the structure we’ve just added is a sample database of B2B customers of a company, e.g. e-commerce wholesale website.
Now, something more advanced. Suppose we’d like to find out how database internals look like?
How to get database structure (tables and columns)? Information schema will help us.
We can get some metadata - for example, about tables - for each
TABLE_SCHEMA, meaning for both
customers (our database):
SELECT * FROM INFORMATION_SCHEMA.TABLES
To limit result to the proper database:
SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'customers'
And even more info about user privilegies, constraints, created views (explained later).
Trick: how to check MySQL version?
prints MySQL version as a result, e.g.
Create and alter custom table
Let’s add another table to our database of business customers, optionally with constraints.
-- create table in the db currently in use, set columns and their type CREATE TABLE company ( company_id INT NOT NULL AUTO_INCREMENT, name VARCHAR (255) NOT NULL, established_date DATE, hq_country VARCHAR (4), PRIMARY KEY (company_id), -- constraint on column -- CHECK (established_date >= '2000-01-01') -- constraint on multiple columns -- CONSTRAINT CHK_Company CHECK (established_date >= '2000-01-01' AND hq_country NOT LIKE 'Z%') );
Alter table by inserting column:
-- add column with ALTER TABLE ALTER TABLE company ADD customer_id INT;
Finally, insert some small amount of records:
-- insert rows INSERT INTO company (name, established_date, hq_country) VALUES ('Sunrise Ltd', '1987-06-26', 'JPN'), ('SEOUL_88', '1988-09-17', 'KOR');
Using the metadata, we can check if everything went well.
Let’s take this simple table with small amount of data:
company. What columns are inside?
Backticks are required in some flavours.
SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = 'customers' AND `TABLE_NAME` = 'company';
company_id name established_date hq_country customer_id
Now we can play with basic SQL commands.
Basic SELECT command
Starting with legendary SELECT - it works as universal tool for everything - also for debugging:
SELECT 'Hello world!'
not only returns result row as it was
'Hello world!' record in
'Hello world!' column but also prints it to the console:
customers> SELECT 'Hello world!' [2023-04-04 20:52:22] 1 row retrieved starting from 1 in 28 ms (execution: 7 ms, fetching: 21 ms)
So we may know that query console accepts and processes queries.
SELECT can do the math!
SELECT can work as math operator: it evaluates expressions, for example boolean (logic) value:
-- evaluate boolean value of query (0 as false, 1 as true) SELECT 1 = 1 AS boolean_value; -- 1, true SELECT 0 = 1 AS boolean_value; -- 0, false and so on. SELECT 1 < 0 AS boolean_value;
It also serves for doing some Boolean algebra.
false are boolean values keywords used in expressions. SQL returns
0 as numeric equivalents of true and false when evaluating expressions.
And all that heavily reminds me of studying logic at the university, intensive three hours course every Friday morning. Old school.
SELECT true AND true AS result; -- returns true (1) SELECT false AND true AS result; -- returns false (0)
Thus, we can simulate XOR gate (FYI: XOR gate returns true when only one side is true, no more, no less):
SELECT false XOR false, -- 0 false XOR true, -- 1 true XOR false, -- 1 true XOR true; -- 0
We can use
0 instead of
SELECT 0 XOR 0, -- 0 0 XOR 1, -- 1 1 XOR 0, -- 1 1 XOR 1; -- 0
Setting constants, using ternary operator and aliases, the XOR gate could be nicely formatted:
SET @false_xor := 'gate returns false'; SET @true_xor := 'gate returns true'; SELECT IF(0 XOR 0, @true_xor, @false_xor) AS '0 XOR 0', IF(0 XOR 1, @true_xor, @false_xor) AS '0 XOR 1', IF(1 XOR 0, @true_xor, @false_xor) AS '1 XOR 0', IF(1 XOR 1, @true_xor, @false_xor) AS '1 XOR 1'
SQL ternary operator explained:
-- TERNARY OPERATOR SELECT IF(0 = 0, 'TRUE', 'FALSE') AS ternary_operator; -- returns second arg SELECT IF(1 = 0, 'TRUE', 'FALSE') AS ternary_operator; -- returns third arg
SQL boolean operators explained:
-- AND (&& in some dialects) SELECT 1 > 0 AND 1 = 1; SELECT 1 < 0 && 1 = 1; -- OR (|| in some dialects) SELECT 1 < 0 OR 1 = 1; SELECT 1 > 0 || 1 = 1; -- <> (!=) SELECT 1 <> 0; SELECT 1 != 0; -- NOT (sometimes: !) SELECT NOT 1 != 0; -- XOR SELECT 1 = 0 XOR 1 = 1
And finally -
SELECT can do the math:
-- arithmetic operators SELECT 1 + 1 * 1 / 3; SELECT 3 % 5
Basic SELECT use cases
SELECT is used for less trivial purposes, like selecting (querying) data from tables in various configurations:
-- select all columns from given table SELECT * FROM company; -- select (view) particular columns only SELECT name, country FROM company; -- select with where clause SELECT * FROM company WHERE hq_country = 'JPN';
LIKE keyword allows more elastic searching strategy:
-- PLACEHOLDER -- returns S plus 7 other chars replaced by underscore SELECT * FROM company WHERE name LIKE 'S_______';
-- WILDCARD -- returns sth before - L - sth after SELECT * FROM company WHERE name LIKE '%L%';
SELECT is used without
FROM, nothing is selected (retrieved). In fact, we simulate query result with fake data and aliases:
-- SELECT WITHOUT FROM SELECT 123; SELECT 9999 AS id, 'mock company' AS company_name;
Null checks - well known among Java developers:
-- NULL CHECK SELECT IFNULL('GIVEN ARGUMENT (NOT NULL)', 'Exception: given argument is NULL') AS null_check; -- returns first arg, because it's not null (String) SELECT IFNULL(null, 'Exception: given argument is NULL') AS null_check; -- returns second arg, because first arg is null
Example of use:
SELECT *, IFNULL(customer_id, 'OMG, customer id is null !!!') AS null_check FROM company
Result of null check is placed in additional column. In case of null, the null value is replaced by exception message:
COALESCE function returns first non-null value available from a list:
SELECT COALESCE(NULL, NULL, 'it is a first not null arg!') AS coalesce; -- returns first not null arg available (so it prints the string in coalesce column)
Let’s complete this part of article with
CASE clause. Programmers should be familiar with it:
-- CASE SELECT company.name, hq_country, CASE WHEN hq_country LIKE 'JPN' THEN 'OK' WHEN hq_country LIKE 'KOR' THEN 'OK' ELSE 'Non-Asian' END AS country_check FROM company; -- CASE - another version SELECT company.name, hq_country, CASE hq_country WHEN 'JPN' THEN 'OK' WHEN 'KOR' THEN 'OK' ELSE 'Non-Asian' END AS country_check FROM company;