SQL Fundamentals Notes
SQL Fundamentals Notes
SQL Fundamentals Notes
3.3: Expressions
1
3.4: Using round() Function
SELECT PRODUCT_ID,
DESCRIPTION,
PRICE,
round(PRICE * 1.07, 2) AS TAXED_PRICE
FROM PRODUCT;
3.6: Comments
/*
This is a
multiline comment
*/
2
Section IV- WHERE
This will get the years between 2005 and 2010, but exclude 2005 and 2010
SELECT * FROM station_data
WHERE year > 2005 AND year < 2010
4.6: Using OR
3
4.7: Using IN
The modulus will perform division but return the remainder. So a remainder of
0 means the two numbers divide evenly.
SELECT * FROM station_data
WHERE MONTH % 3 = 0;
4
4.13B: Using Regular Expressions
If you are familiar with regular expressions, you can use those to identify and
qualify text patterns.
SELECT * FROM STATION_DATA
WHERE report_code REGEXP '^A.*$'
A NULL is an absent value. It is not zero, empty text ‘’, or any value. It is blank.
To check for a null value:
SELECT * FROM station_data
WHERE snow_depth IS NULL;
5
4.20: Handling NULL in conditions
Nulls will not qualify with any condition that doesn’t explicitly handle it.
SELECT * FROM station_data
WHERE precipitation <= 0.5;
If you want to include nulls, do this:
SELECT * FROM station_data
WHERE precipitation IS NULL OR precipitation <= 0.5;
You can also use a coalesce() function to turn a null value into a default value,
if it indeed is null.
This will treat all null values as a 0.
SELECT * FROM station_data
WHERE coalesce(precipitation, 0) <= 0.5;
6
5.3 Getting a count by year
7
5.9 Average temperature by month since year 2000
SELECT year,
SUM(snow_depth) as total_snow,
SUM(precipitation) as total_precipitation,
MAX(precipitation) as max_precipitation
FROM station_data
WHERE year >= 2005
GROUP BY year
EXERCISES
Flip to slides
8
WHERE total_precipitation > 30
GROUP BY year
You can however, use HAVING.
SELECT year,
SUM(precipitation) as total_precipitation
FROM station_data
GROUP BY year
HAVING total_precipitation > 30
Note that some platforms like Oracle do not support aliasing in GROUP BY
and HAVING.
Therefore you have to rewrite the entire expression each time
SELECT year,
SUM(precipitation) as total_precipitation
FROM station_data
GROUP BY year
HAVING SUM(precipitation) > 30
You can use a CASE statement to turn a column value into another value based
on conditions. For instance, we can turn different wind_speed ranges into HIGH,
MODERATE, and LOW categories.
SELECT report_code, year, month, day, wind_speed,
CASE
WHEN wind_speed >= 40 THEN 'HIGH'
WHEN wind_speed >= 30 AND wind_speed < 40 THEN 'MODERATE'
ELSE 'LOW' END
AS wind_severity
9
FROM station_data
We can actually omit AND wind_speed < 40 from the previous example because
each WHEN/THEN is evaluated from top-to-bottom. The first one it finds to be
true is the one it will go with, and stop evaluating subsequent conditions.
SELECT report_code, year, month, day, wind_speed,
CASE
WHEN wind_speed >= 40 THEN 'HIGH'
WHEN wind_speed >= 30 THEN 'MODERATE'
ELSE 'LOW' END
as wind_severity
FROM station_data
We can use GROUP BY in conjunction with a CASE statement to slice data in more
ways, such as getting the record count by wind_severity and year.
SELECT year,
CASE
WHEN wind_speed >= 40 THEN 'HIGH'
WHEN wind_speed >= 30 THEN 'MODERATE'
ELSE 'LOW' END
as wind_severity,
COUNT(*) as record_count
FROM station_data
GROUP BY 1,2
10
SELECT year, month,
SUM(precipitation) as tornado_precipitation
FROM station_data
WHERE tornado = 1
GROUP BY year, month
Non-Tornado Precipitation
SELECT year, month,
SUM(precipitation) as non_tornado_precipitation
FROM station_data
WHERE tornado = 0
GROUP BY year, month
But you can use a single query using a CASE statement that sets a value to 0 if
the condition is not met. That way it will not impact the sum.
SELECT year, month,
SUM(CASE WHEN tornado = 1 THEN precipitation ELSE 0 END) as tornado_precipitation,
Since NULL is ignored in SUM, MIN, MAX, and other aggregate functions, you
can use it in a CASE statement to conditionally control whether or not a value
should be included in that aggregation.
For instance, we can split up max precipitation when a tornado was present vs
not present.
SELECT year,
MAX(CASE WHEN tornado = 0 THEN precipitation ELSE NULL END) as max_non_tornado_precipitation
MAX(CASE WHEN tornado = 1 THEN precipitation ELSE NULL END) as max_tornado_precipitation
FROM station_data
GROUP BY year
Switch to slides for exercise
Exercise 6.1
11
SELECT
report_code,
year,
CASE
WHEN month BETWEEN 1 and 3 THEN 'Q1'
WHEN month BETWEEN 4 and 6 THEN 'Q2'
WHEN month BETWEEN 7 and 9 THEN 'Q3'
WHEN month BETWEEN 10 and 12 THEN 'Q4'
END as quarter,
temperature
FROM STATION_DATA
Exercise 6.2
Get the average temperature by quarter and month, where a “quarter” is “Q1”,
“Q2”, “Q3”, or “Q4” reflecting months 1-3, 4-6, 7-9, and 10-12 respectively.
ANSWER
SELECT
year,
CASE
WHEN month BETWEEN 1 and 3 THEN 'Q1'
WHEN month BETWEEN 4 and 6 THEN 'Q2'
WHEN month BETWEEN 7 and 9 THEN 'Q3'
WHEN month BETWEEN 10 and 12 THEN 'Q4'
END as quarter,
AVG(temperature) as avg_temp
FROM STATION_DATA
GROUP BY 1,2
12
View customer address information with each order by joining tables CUSTOMER
and CUSTOMER_ORDER.
SELECT ORDER_ID,
CUSTOMER.CUSTOMER_ID,
ORDER_DATE,
SHIP_DATE,
NAME,
STREET_ADDRESS,
CITY,
STATE,
ZIP,
PRODUCT_ID,
ORDER_QTY
You may come across a style of joining where commas are used to select the
needed tables, and a WHERE defines the join condition as shown below:
SELECT ORDER_ID,
CUSTOMER.CUSTOMER_ID,
ORDER_DATE,
SHIP_DATE,
NAME,
STREET_ADDRESS,
CITY,
STATE,
ZIP,
PRODUCT_ID,
ORDER_QTY
13
between both), and then filter it based on the WHERE. It does not work with
LEFT JOIN either, which we will look at shortly.
Using the INNER JOIN with an ON condition avoids the cartesian product and is
more efficient. Therefore, always use that approach.
To include all customers, regardless of whether they have orders, you can use a
left outer join via LEFT JOIN (refer to slides).
If any customers do not have any orders, they will get one record where the
CUSTOMER_ORDER fields will be null.
SELECT CUSTOMER.CUSTOMER_ID,
NAME,
STREET_ADDRESS,
CITY,
STATE,
ZIP,
ORDER_DATE,
SHIP_DATE,
ORDER_ID,
PRODUCT_ID,
ORDER_QTY
With a left outer join, you can filter for NULL values on the CUSTOMER_ORDER
table to find customers that have no orders.
SELECT CUSTOMER.CUSTOMER_ID,
NAME AS CUSTOMER_NAME
14
7.4 Joining Multiple Tables
You can use expressions combining any fields on any of the joined tables. For
instance, we can now get the total revenue for each customer.
SELECT ORDER_ID,
CUSTOMER.CUSTOMER_ID,
NAME AS CUSTOMER_NAME,
STREET_ADDRESS,
CITY,
STATE,
ZIP,
ORDER_DATE,
PRODUCT.PRODUCT_ID,
DESCRIPTION,
ORDER_QTY,
ORDER_QTY * PRICE as REVENUE
15
7.6 Using GROUP BY with JOINs
You can use GROUP BY with a join. For instance, you can find the total revenue
for each customer by leveraging all three joined tables, and aggregating the
REVENUE expression we created earlier.
SELECT
CUSTOMER.CUSTOMER_ID,
NAME AS CUSTOMER_NAME,
sum(ORDER_QTY * PRICE) as TOTAL_REVENUE
GROUP BY 1,2
To see all customers even if they had no orders, use a LEFT JOIN
SELECT
CUSTOMER.CUSTOMER_ID,
NAME AS CUSTOMER_NAME,
sum(ORDER_QTY * PRICE) as TOTAL_REVENUE
GROUP BY 1,2
You can also use a coalesce() function to turn null sums into zeros.
SELECT
CUSTOMER.CUSTOMER_ID,
NAME AS CUSTOMER_NAME,
coalesce(sum(ORDER_QTY * PRICE), 0) as TOTAL_REVENUE
GROUP BY 1,2
16
Section VIII - Database Design
Refer to slides for database design concepts
To view source code for SQL Injection Demo, here is the GitHub page:
https://github.com/thomasnield/sql-injection-demo
To read about normalized forms (which we do not cover in favor of a more
intuitive approach), you can read this article:
http://www.dummies.com/programming/sql/sql-first-second-and-third-
normal-forms/
Create the other tables using the SQLiteStudio New table wizard, or just execut-
ing the following SQL code.
17
CREATE TABLE ROOM (
ROOM_ID INTEGER PRIMARY KEY AUTOINCREMENT,
FLOOR_NUMBER INTEGER NOT NULL,
SEAT_CAPACITY INTEGER NOT NULL
);
Creating Views
It is not uncommon to save SELECT queries that are used frequently into a
database. These are known as Views and act very similarly to tables. You can
essentially save a SELECT query and work with it just like a table.
For instance, say we wanted to save this SQL query that includes ROOM and
COMPANY info with each PRESENTATION record.
SELECT COMPANY.NAME as BOOKED_COMPANY,
ROOM.ROOM_ID as ROOM_NUMBER,
ROOM.FLOOR_NUMBER as FLOOR,
ROOM.SEAT_CAPACITY as SEATS,
18
START_TIME, END_TIME
FROM PRESENTATION
FROM PRESENTATION
19
9.1 Using INSERT
To create a new record in a table, use the INSERT command and supply the
values for the needed columns.
Put yourself into the ATTENDEE table.
INSERT INTO ATTENDEE (FIRST_NAME, LAST_NAME)
VALUES ('Thomas','Nield')
Notice above that we declare the table we are writing to, which is ATTENDEE. Then
we declare the columns we are supplying values for (FIRST_NAME, LAST_NAME),
followed by the values for this new record ('Thomas','Nield').
Notice we did not have to supply a value for ATTENDEE_ID as we have set it in
the previous section to generate its own value. PHONE, EMAIL, and VIP fields
have default values or are nullable, and therefore optional.
You can insert multiple rows in an INSERT. This will add three people to the
ATTENDEE table.
INSERT INTO ATTENDEE (FIRST_NAME, LAST_NAME, PHONE, EMAIL, VIP)
VALUES ('Jon', 'Skeeter', 4802185842,'[email protected]', 1),
('Sam','Scala', 2156783401,'[email protected]', 0),
('Brittany','Fisher', 5932857296,'[email protected]', 0)
Let’s test our design and make sure our primary/foreign keys are working.
Try to INSERT a COMPANY with a PRIMARY_CONTACT_ATTENDEE_ID that does not
exist in the ATTENDEE table.
INSERT INTO COMPANY (NAME, DESCRIPTION, PRIMARY_CONTACT_ATTENDEE_ID)
VALUES ('RexApp Solutions','A mobile app delivery service', 5)
Currently, there is no ATTENDEE with an ATTENDEE_ID of 5, this should error out
which is good. It means we kept bad data out.
If you use an ATTENDEE_ID value that does exist and supply it as a
PRIMARY_CONTACT_ATTENDEE_ID, we should be good to go.
INSERT INTO COMPANY (NAME, DESCRIPTION, PRIMARY_CONTACT_ATTENDEE_ID)
VALUES ('RexApp Solutions', 'A mobile app delivery service', 3)
20
9.3 DELETE records
The DELETE command is dangerously simple. To delete records from both the
COMPANY and ATTENDEE tables, execute the following SQL commands.
DELETE FROM COMPANY;
DELETE FROM ATTENDEE;
Note that the COMPANY table has a foreign key relationship with the ATTENDEE
table. Therefore we will have to delete records from COMPANY first before it
allows us to delete data from ATTENDEE. Otherwise we will get a “FOREIGN
KEY constraint failed effort” due to the COMPANY record we just added which is
tied to the ATTENDEE with the ATTENDEE_ID of 3.
You can also use a WHERE to only delete records that meet a conditional. To
delete all ATTENDEE records with no PHONE or EMAIL, you can run this command.
DELETE FROM ATTENDEE
WHERE PHONE IS NULL AND EMAIL IS NULL
A good practice is to use a SELECT * in place of the DELETE first. That way you
can get a preview of what records will be deleted with that WHERE condition.
SELECT * FROM ATTENDEE
WHERE PHONE IS NULL AND EMAIL IS NULL
UPDATE records
Say we wanted to change the phone number for the ATTENDEE with the
ATTENDEE_ID value of 3, which is Sam Scala. We can do this with an UPDATE
statement.
UPDATE ATTENDEE SET PHONE = 4802735872
WHERE ATTENDEE_ID = 3
Using a WHERE is important, otherwise it will update all records with the specified
SET assignment. This can be handy if you wanted to say, make all EMAIL values
uppercase.
UPDATE ATTENDEE SET EMAIL = UPPER(EMAIL)
If you want to delete a table, it also is dangerously simple. Be very careful and
sure before you delete any table, because it will remove it permanently.
DROP TABLE MY_UNWANTED_TABLE
21
9.5 Transactions
END TRANSACTION;
But if we ever encountered a failure with our write operations, we can call
ROLLBACK instead of END TRANSACTION to go back to the database state when
BEGIN TRANSACTION was called.
Below, we have a failed operation due to a broken INSERT.
BEGIN TRANSACTION;
22
Section X - Moving Forward
SQL Resources
23