KKKelly's Blog

Snippets, cheatsheets, & musings on web development

All posts

SQL Notes

By Kelly King
11 min read

Notes from SQL Essential Training.

Composing Queries

Comments

-- Single line comment starts with a dash

/*
  Multiline comment, often with:

  CREATED BY: Kelly King
  CREATE DATE: 06/24/2025
  DESCRIPTION: What a comment looks like
*/

Composition

SELECT FirstName, LastName, Email
FROM Customer

Custom names (aliases)

Square brackets, quotes, or directly use the new word if there are no spaces!

SELECT
	FirstName AS [Customer first name],
	LastName AS "Customer last name",
	Email	AS EMAIL
FROM Customer

Alias the table itself:

SELECT
	t.Composer,
	t.Name AS "Track Name"
FROM Track as t
ORDER BY t.Name
LIMIT 10

Sorting results

Ascending is default!

SELECT
	FirstName AS [Customer first name],
	LastName AS "Customer last name",
	Email	AS EMAIL
FROM Customer
ORDER BY
	FirstName ASC,
	LastName DESC

Limiting results

SELECT
	FirstName AS [Customer first name],
	LastName AS "Customer last name",
	Email	AS EMAIL
FROM Customer
ORDER BY
	FirstName ASC,
	LastName DESC
LIMIT 10

Filtering

| Arithmetic | Symbol | Comparison | Symbol | Logical | |------------|--------|----------------------|--------|---------| | Add | + | Equal to | = | AND | | Subtract | - | Not equal to | <> | OR | | Multiply | * | Greater than | > | IN | | Divide | / | Less than | < | LIKE | | Modulo | % | Less than or equal to| <= | |

Numeric data

-- Description: Number of customers purchased two songs at 99cents each

SELECT *
FROM Invoice
WHERE Total = 1.98

BETWEEN and IN

-- Description: Number of invoices that are between 1.98 and 5

SELECT *
FROM Invoice
WHERE Total BETWEEN 1.98 AND 5.00
-- Description: Number of invoices that are exactly 1.98 or 3.96

SELECT *
FROM Invoice
WHERE Total IN (1.98, 3.96)

Text data

-- Description: Number of invoices billed to Brussels

SELECT *
FROM Invoice
WHERE BillingCity= 'Brussels'
-- Description: Number of invoices billed to Brussels, Orlando, or Paris

SELECT *
FROM Invoice
WHERE BillingCity in ('Brussels', 'Orlando', 'Paris')

Non-exact match

LIKE operator searches for partial or incorrect values, used with % which is a wild card character

-- Description: Number of invoices billed to cities that start with a B

SELECT *
FROM Invoice
WHERE BillingCity LIKE 'B%'
-- Description: Number of invoices billed to cities that contain a B

SELECT *
FROM Invoice
WHERE BillingCity LIKE '%B%'

Dates

Options

  • Match the format in the table
  • Apply a Date function to the column
-- Description: How many invoices billed on May 22, 2010

SELECT *
FROM Invoice
WHERE InvoiceDate = '2010-05-22 00:00:00'
SELECT *
FROM Invoice
WHERE DATE(InvoiceDate) = '2010-05-22'

Multiple conditions

-- Description: Get all inovices that were billed after 5/22/2010 and total less than $3

SELECT *
FROM Invoice
WHERE DATE(InvoiceDate) > '2010-05-22' AND total < 3.00
ORDER BY InvoiceDate

Logical OR

-- Description: Get all invoices whose billing city starts with a P or a D

SELECT *
FROM Invoice
WHERE BillingCity LIKE 'P%' OR BillingCity LIKE 'D%'

Brackets and order

The order matters, so we apply PEMDAS

  • Parens around the part we want executed first
-- Description: All invoices greater than 1.99 from any cities starting with P or D

SELECT *
FROM Invoice
WHERE total > 1.98 AND (BillingCity LIKE 'P%' OR BillingCity LIKE 'D%' )

IF THEN case

/*
Description: Customers spending between $7 and 15
Sales categories
- Basline: Between .99 and 1.99
- Low purchase: Between 2 and 6.99
- Target: Between 7 and 15
- Top: Above 15
*/

SELECT
	InvoiceDate,
	BillingAddress,
	BillingCity,
	total,
	CASE
	WHEN total < 2 THEN 'Baseline purchase'
	WHEN total BETWEEN 2.00 AND 6.99 THEN 'Low purchase'
	WHEN total BETWEEN 7.00 AND 15.00 THEN 'Target purchase'
	ELSE 'Top performer'
	END as PurchaseType
FROM Invoice
ORDER BY BillingCity
-- Description: Which cities do top sales come from

SELECT
	InvoiceDate,
	BillingAddress,
	BillingCity,
	total,
	CASE
	WHEN total < 2 THEN 'Baseline purchase'
	WHEN total BETWEEN 2.00 AND 6.99 THEN 'Low purchase'
	WHEN total BETWEEN 7.00 AND 15.00 THEN 'Target purchase'
	ELSE 'Top performer'
	END as PurchaseType
FROM Invoice
WHERE PurchaseType =  'Top performer'
ORDER BY BillingCity
-- Description: Categorize each track based on its price

SELECT
	Name as "Track name",
	Composer,
	UnitPrice as Price,
	CASE
		WHEN UnitPrice <= 0.99 THEN "Budget"
		WHEN UnitPrice > 0.99 AND UnitPrice < 1.49 THEN "Regular"
		WHEN UnitPrice > 1.49 THEN "Premium"
		ELSE "Exclusive"
	END as PriceCategory
FROM Track
ORDER BY UnitPrice DESC

Multiple tables

Joins

-- Description: Join customer and invoice table

SELECT  *
 FROM Invoice
 INNER JOIN
	Customer
ON Invoice.CustomerId = Customer.CustomerId
ORDER BY Invoice.CustomerId
-- Description: Join customer and invoice table

SELECT  *
 FROM Invoice as i
 INNER JOIN
	Customer
ON Invoice.CustomerId = Customer.CustomerId
ORDER BY Invoice.CustomerId

Types of joins

Discrepencies between tables are handled by different join types.

For example, imagine a customer deletes their account. Their ID migh still appear on invoices, despite being removed from our customer database.

  • Inner join: The center / overlapping parts of a venn diagram.
  • Left outer join: The left side of a venn diagram, including any overlap from the right. We get all fields from left table, and any matching info from the right table.
  • Right outer join: Returns the entire right table, plus any relevant data from the left. Note that SQL lite doesn't support this operation. The best practice is to reverse the order of the tables, and do a left outer join instead.

Joining many tables

-- Description: Get the customer and sales rep names for the highest ten individual sales

 SELECT
	e.FirstName,
	e.LastName,
	e.EmployeeId,
	c.FirstName,
	c.LastName,
	c.SupportRepId,
	i.CustomerId,
	i.total
FROM
	Invoice as i
INNER JOIN
	Customer as c
ON
	i.CustomerId = c.CustomerId
INNER JOIN
	Employee as e
ON c.SupportRepId = e.EmployeeId
ORDER BY
	i.total DESC
LIMIT 10
-- Description: Generate a report that lists each customer along with their assigned support representative

 SELECT
	c.FirstName as "Customer name",
	c.LastName as "Customer surname",
	e.FirstName as "Employee name",
	e.LastName as "Employee surname"
 FROM Customer as c
	INNER JOIN Employee as e
	ON c.SupportRepId = e.EmployeeId
ORDER BY e.LastName, c.LastName

SQL functions

| Aggregate | | String | | Date | | -------- | -------- | -------- |-------- |-------- | | Min() | | Upper() | | Now | | Max() | | Substr() | | Datetime() | | Aug() | | Instr() | | Date() | | Count() | | Ltrim() | | Date() | | Sum() | | Replace() | | Strftime() | | | | Trim() | | Time() |

Connecting strings

We can use the || double pipe operator to concatenate strings, including string literals.

-- Description: Return a mailing address field that combines the customer name and address for American customers

SELECT
	FirstName || " " || LastName || " " || Address  || ", " || City  || "  " ||  State  || ",  " ||  PostalCode as [Mailing address]
FROM Customer
WHERE
	Country = "USA"

Separating text

-- Description: same as above, but only get 5 digit postal code

SELECT
	FirstName || " " || LastName || " " || Address  || ", " || City  || "  " ||  State  || ",  " ||  Substr(PostalCode,  1, 5) as [Mailing address]
FROM Customer
WHERE
	Country = "USA"

UPPER and LOWER

-- Description: Get customer name, uppercasing surname and lowercasing first name

 SELECT
	UPPER(LastName),
	LOWER(FirstName)
FROM Customer

Date functions

-- Description: Get ages and birthdays of employees

 SELECT
	LastName,
	FirstName,
	strftime("%Y-%m-%d", BirthDate),
	strftime("%Y-%m-%d", 'now') - strftime("%Y-%m-%d", BirthDate) as Age
FROM Employee

Aggregate functions

-- Description: Get total, min, max and average sales

 SELECT
	SUM(total) as [Total sales],
	RND(AVG(total)) as [Average sale],
	MAX(total) as [Max sale],
	MIN(total) as [Min sale],
	Count(*) as [Num sales]
FROM Invoice

Nesting functions

-- Description: as above, but round the average

 SELECT
	SUM(total) as [Total sales],
	ROUND(AVG(total), 2) as [Average sale],
	MAX(total) as [Max sale],
	MIN(total) as [Min sale],
	Count(*) as [Num sales]
FROM Invoice

Grouping

Basic Grouping

Apply the Group By to the non-aggregated field.

-- Description: Average amount customers spend by billing city

SELECT
	BillingCity,
	ROUND(avg(total))
FROM Invoice
GROUP BY BillingCity
ORDER BY BillingCity

Grouping with the WHERE clause

WHERE always comes after FROM and before GROUP BY

-- Description: Average amount customers spend by billing city in cities that start with an "L"

SELECT
	BillingCity,
	ROUND(avg(total))
FROM Invoice
WHERE BillingCity LIKE 'L%'
GROUP BY BillingCity
ORDER BY BillingCity

Grouping with the HAVING clause

HAVING always comes after the GROUP BY. Having acts as a further filter.

-- Description: same as above, but only for averages over $5

SELECT
	BillingCity,
	ROUND(avg(total))
FROM Invoice
GROUP BY BillingCity
HAVING avg(total) > 5.00
ORDER BY BillingCity

Grouping WHERE and HAVING

WHERE comes before HAVING!

-- Description: as above, but for cities starting with a "B"

SELECT
	BillingCity,
	ROUND(avg(total))
FROM Invoice
WHERE BillingCity LIKE 'B%'
GROUP BY BillingCity
HAVING avg(total) > 5.00
ORDER BY BillingCity

Grouping by many fields

-- Description: Average invoice totals by billing country and city

SELECT
	BillingCountry,
	BillingCity,
	ROUND(avg(total))
FROM Invoice
GROUP BY BillingCountry, BillingCity
HAVING avg(total) > 5.00
ORDER BY BillingCountry

Nesting queries

-- Description: Return all invoices where total is less than the average

SELECT
	InvoiceDate,
	BillingAddress,
	BillingCity,
	total
FROM Invoice
WHERE
	total < (SELECT  Avg(total) FROM Invoice)
ORDER BY
	total DESC

Aggregated subqueries

-- Description: Get cities average sale with global average

SELECT
	BillingCity,
	Round(Avg(total),2) as CityAverage,
	(SELECT Round(avg(total),2) FROM Invoice) as GlobalAverage
FROM Invoice
GROUP BY BillingCity
ORDER BY BillingCity

Non-aggregate subqueries

-- Description: Get all invoices that occur after a particular invoice

SELECT
	InvoiceDate,
	BillingAddress,
	BillingCity
FROM INVOICE
WHERE InvoiceDate >
(SELECT
	InvoiceDate
	FROM Invoice
	WHERE InvoiceId = 251)

IN clause subquery

Allows you to modify a query and narrow it down further

-- Description: Get details from Invoices id 251, 252 and 254

 SELECT
	InvoiceDate,
	BillingAddress,
	BillingCity
FROM Invoice
WHERE
	InvoiceDate IN (
	SELECT
		InvoiceDate
	FROM INVOICE
	WHERE InvoiceId in (251, 252, 254)
)

Distinct subquery

-- Description: List all tracks by composer and name that do not occur in the Invoice table -- ie find unpopular songs.

 SELECT
	TrackId,
	Composer,
	Name,
  GenreId
 FROM Track
 INNER JOIN
 WHERE
	TrackId NOT IN
	(SELECT DISTINCT
		TrackId
	FROM InvoiceLine
	ORDER BY
		TrackId
	)
-- Description: Get data for best-selling tracks, accessing the genre name from the Genre table

 SELECT
	t.TrackId,
	t.Composer,
	t.Name,
	g.Name
 FROM Track as t
INNER JOIN Genre as g
	ON t.GenreId = g.GenreId
 WHERE
	t.TrackId NOT IN
	(SELECT DISTINCT
		li.TrackId
	FROM InvoiceLine as li
	ORDER BY
		li.TrackId
	)

Stored queries

Creating a view

A view is a query that can be stored and executed repeatedly or accessed by other queries. You can find it in the DB structure and browse data tabs.

-- Description: Create a view for the average total on our invoice table

CREATE VIEW V_AvgTotal AS
  SELECT
	round(avg(total), 2) AS [Average total]
FROM
	Invoice

Editing a view

Views in SQLite are not modified with syntax - they are deleted and then recreated. We must drop first, or delete via right xlick.

-- Description: Update the average view to not round. Note that the bulk of the query will be generated if you right-click to delete the view.

DROP VIEW IF EXISTS "main"."V_AvgTotal";
CREATE VIEW V_AvgTotal AS
 SELECT
	avg(total)  AS [Average total]
FROM
	Invoice

Joining views

-- Description: Create a view of invoices with track data

CREATE VIEW V_Tracks_InvoiceLine AS
SELECT
	il.InvoiceId,
	il.UnitPrice,
	il.Quantity,
	t.Name,
	t.Composer,
	t.Milliseconds
FROM
	InvoiceLine il
INNER JOIN
	Track t
ON
	il.TrackId = t.TrackId

Deleting views

-- Description: Remove the average total view

DROP VIEW
	V_AvgTotal

Adding, modifying and deleting data

Data Manipulation Language (DML): SQL Statements used to alter data stored in the tables of a database.

INSERT
UPDATE
DELETE

Inserting data

Description: Add a new artist to the catalog.

INSERT INTO
	Artist (Name)
VALUES ('Bob Marley')

Updating data

Use with WHERE clause, otherwise it will update every row in the table!

Description: Update the artist name

UPDATE
	Artist
SET Name = 'Damien Marley'
WHERE ArtistId = 276

Deleting data

Use with WHERE clause, otherwise it will delete every row in the table!

Description: Remove the artist we just added

DELETE FROM Artist
WHERE ArtistId = 276