0% found this document useful (0 votes)
14 views

SQL Notes para Profesionales

The document provides various SQL techniques and functions, including window functions like PERCENT_RANK and CUME_DIST, as well as methods for identifying out-of-sequence records using the LAG function. It also covers the use of Common Table Expressions (CTEs) for generating series of numbers, enumerating subtrees, and creating temporary queries. Additionally, it discusses views and materialized views, highlighting their creation and use in SQL databases.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
14 views

SQL Notes para Profesionales

The document provides various SQL techniques and functions, including window functions like PERCENT_RANK and CUME_DIST, as well as methods for identifying out-of-sequence records using the LAG function. It also covers the use of Common Table Expressions (CTEs) for generating series of numbers, enumerating subtrees, and creating temporary queries. Additionally, it discusses views and materialized views, highlighting their creation and use in SQL databases.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 27

Assitant to the Cheif Financial

262 48 0 1
Officer
239 Benefits Specialist 45 0 1
252 Buyer 50 0 0.111111111111111
251 Buyer 49 0.125 0.333333333333333
256 Buyer 49 0.125 0.333333333333333
253 Buyer 48 0.375 0.555555555555555
254 Buyer 48 0.375 0.555555555555555

The PERCENT_RANKfunction ranks the entries within each group. For each entry, it returns the percentage of entries
in the same group that have lower values.

The CUME_DISTfunction is similar, except that it returns the percentage of values less than or equal to the current
value.

GoalKicker.com – SQL Notes for Professionals 126


Chapter 45: Window Functions
Section 45.1: Setting up a flag if other rows have a comm
property
Let's say I have this data:

Table items

id name tag
1 example unique_tag
2 foo simple
42 bar simple
3 baz hello
51 quux world

I'd like to get all those lines and know if a tag is used by other lines

SELECT id, name, tag, COUNT(*) OVER (PARTITION BY tag) > 1 AS flag FROM items

The result will be:

id name tag flag


1 example unique_tag false
2 foo simple true
42 bar simple true
3 baz hello false
51 quux world false

In case your database doesn't have OVER and PARTITION you can use this to produce the same result:

SELECT id, name, tag, (SELECT COUNT(tag) FROM items B WHERE tag = A.tag) > 1 AS flag FROM items A

Section 45.2: Finding "out-of-sequence" records using th


LAG() function
Given these sample data:

ID STATUS STATUS_TIME STATUS_BY


1 ONE 2016-09-28-19.47.52.501398 USER_1
3 ONE 2016-09-28-19.47.52.501511 USER_2
1 THREE 2016-09-28-19.47.52.501517 USER_3
3 TWO 2016-09-28-19.47.52.501521 USER_2
3 THREE 2016-09-28-19.47.52.501524 USER_4

Items identified byID values must move from


STATUS 'ONE' to 'TWO' to 'THREE' in sequence, without skipping
statuses. The problem is to find users ( ) values who violate the rule and move from 'ONE' immediately to
STATUS_BY
'THREE'.

The LAG() analytical function helps to solve the problem by returning for each row the value in the preceding row:

GoalKicker.com – SQL Notes for Professionals 127


SELECT * FROM (
SELECT
t.*,
LAG(status) OVER (PARTITION BY id ORDER BY status_time) AS prev_status
FROM test t
) t1 WHERE status = 'THREE' AND prev_status != 'TWO'

In case your database doesn't have LAG() you can use this to produce the same result:

SELECT A.id, A.status, B.status as prev_status, A.status_time, B.status_time as prev_status_time


FROM Data A, Data B
WHERE A.id = B.id
AND B.status_time = (SELECT MAX(status_time) FROM Data where status_time < A.status_time and id =
A.id)
AND A.status = 'THREE' AND NOT B.status = 'TWO'

Section 45.3: Getting a running total


Given this data:

date amount
2016-03-12 200
2016-03-11 -50
2016-03-14 100
2016-03-15 100
2016-03-10 -250
SELECT date, amount, SUM(amount) OVER (ORDER BY date ASC) AS running
FROM operations
ORDER BY date ASC

will give you

date amount running


2016-03-10 -250 -250
2016-03-11 -50 -300
2016-03-12 200 -100
2016-03-14 100 0
2016-03-15 100 -100

Section 45.4: Adding the total rows selected to every ro


SELECT your_columns, COUNT(*) OVER() as Ttl_Rows FROM your_data_set
id name Ttl_Rows
1 example 5
2 foo 5
3 bar 5
4 baz 5
5 quux 5

Instead of using two queries to get a count then the line, you can use an aggregate as a window function and use
the full result set as the window.

GoalKicker.com – SQL Notes for Professionals 128


This can be used as a base for further calculation without the complexity of extra self joins.

Section 45.5: Getting the N most recent rows over multi


grouping
Given this data

User_ID Completion_Date
1 2016-07-20
1 2016-07-21
2 2016-07-20
2 2016-07-21
2 2016-07-22
;with CTE as
(SELECT *,
ROW_NUMBER() OVER (PARTITION BY User_ID
ORDER BY Completion_Date DESC) Row_Num
FROM Data)
SELECT * FORM CTE WHERE Row_Num <= n

Using n=1, you'll get the one most recent row user_id
per :

User_ID Completion_Date Row_Num


1 2016-07-21 1
2 2016-07-22 1

GoalKicker.com – SQL Notes for Professionals 129


Chapter 46: Common Table Expressions
Section 46.1: generating values
Most databases do not have a native way of generating a series of numbers for ad-hoc use; however, common
table expressions can be used with recursion to emulate that type of function.

The following example generates a common table expression called


Numberswith a columni which has a row for
numbers 1-5:

--Give a table name `Numbers" and a column `i` to hold the numbers
WITH Numbers(i) AS (
--Starting number/index
SELECT 1
--Top-level UNION ALL operator required for recursion
UNION ALL
--Iteration expression:
SELECT i + 1
--Table expression we first declared used as source for recursion
FROM Numbers
--Clause to define the end of the recursion
WHERE i < 5
)
--Use the generated table expression like a regular table
SELECT i FROM Numbers;
i
1
2
3
4
5

This method can be used with any number interval, as well as other types of data.

Section 46.2: recursively enumerating a subtree


WITH RECURSIVE ManagedByJames(Level, ID, FName, LName) AS (
-- start with this row
SELECT 1, ID, FName, LName
FROM Employees
WHERE ID = 1

UNION ALL

-- get employees that have any of the previously selected rows as manager
SELECT ManagedByJames.Level + 1,
Employees.ID,
Employees.FName,
Employees.LName
FROM Employees
JOIN ManagedByJames
ON Employees.ManagerID = ManagedByJames.ID

ORDER BY 1 DESC -- depth-first search


)
SELECT * FROM ManagedByJames;

GoalKicker.com – SQL Notes for Professionals 130


Level ID FName LName
1 1 James Smith
2 2 John Johnson
3 4 Johnathon Smith
2 3 Michael Williams

Section 46.3: Temporary query


These behave in the same manner as nested subqueries but with a different syntax.

WITH ReadyCars AS (
SELECT *
FROM Cars
WHERE Status = 'READY'
)
SELECT ID, Model, TotalCost
FROM ReadyCars
ORDER BY TotalCost;
ID Model TotalCost
1 Ford F-150 200
2 Ford F-150 230

Equivalent subquery syntax

SELECT ID, Model, TotalCost


FROM (
SELECT *
FROM Cars
WHERE Status = 'READY'
) AS ReadyCars
ORDER BY TotalCost

Section 46.4: recursively going up in a tree


WITH RECURSIVE ManagersOfJonathon AS (
-- start with this row
SELECT *
FROM Employees
WHERE ID = 4

UNION ALL

-- get manager(s) of all previously selected rows


SELECT Employees.*
FROM Employees
JOIN ManagersOfJonathon
ON Employees.ID = ManagersOfJonathon.ManagerID
)
SELECT * FROM ManagersOfJonathon;
Id FName LName PhoneNumber ManagerId DepartmentId
4 Johnathon Smith 1212121212 2 1
2 John Johnson 2468101214 1 1
1 James Smith 1234567890 NULL 1

GoalKicker.com – SQL Notes for Professionals 131


Section 46.5: Recursively generate dates, extended to in
team rostering as example
DECLARE @DateFrom DATETIME = '2016-06-01 06:00'
DECLARE @DateTo DATETIME = '2016-07-01 06:00'
DECLARE @IntervalDays INT = 7

-- Transition Sequence = Rest & Relax into Day Shift into Night Shift
-- RR (Rest & Relax) = 1
-- DS (Day Shift) = 2
-- NS (Night Shift) = 3

;WITH roster AS
(
SELECT @DateFrom AS RosterStart, 1 AS TeamA, 2 AS TeamB, 3 AS TeamC
UNION ALL
SELECT DATEADD(d, @IntervalDays, RosterStart),
CASE TeamA WHEN 1 THEN 2 WHEN 2 THEN 3 WHEN 3 THEN 1 END AS TeamA,
CASE TeamB WHEN 1 THEN 2 WHEN 2 THEN 3 WHEN 3 THEN 1 END AS TeamB,
CASE TeamC WHEN 1 THEN 2 WHEN 2 THEN 3 WHEN 3 THEN 1 END AS TeamC
FROM roster WHERE RosterStart < DATEADD(d, -@IntervalDays, @DateTo)
)

SELECT RosterStart,
ISNULL(LEAD(RosterStart) OVER (ORDER BY RosterStart), RosterStart + @IntervalDays) AS
RosterEnd,
CASE TeamA WHEN 1 THEN 'RR' WHEN 2 THEN 'DS' WHEN 3 THEN 'NS' END AS TeamA,
CASE TeamB WHEN 1 THEN 'RR' WHEN 2 THEN 'DS' WHEN 3 THEN 'NS' END AS TeamB,
CASE TeamC WHEN 1 THEN 'RR' WHEN 2 THEN 'DS' WHEN 3 THEN 'NS' END AS TeamC
FROM roster

Result

I.e. For Week 1 TeamA is on R&R, TeamB is on Day Shift and TeamC is on Night Shift.

Section 46.6: Oracle CONNECT BY functionality with recu


CTEs
Oracle's CONNECT BY functionality provides many useful and nontrivial features that are not built-in when using
SQL standard recursive CTEs. This example replicates these features (with a few additions for sake of
completeness), using SQL Server syntax. It is most useful for Oracle developers finding many features missing in
their hierarchical queries on other databases, but it also serves to showcase what can be done with a hierarchical
query in general.

WITH tbl AS (
SELECT id, name, parent_id
FROM mytable)
, tbl_hierarchy AS (
/* Anchor */

GoalKicker.com – SQL Notes for Professionals 132


SELECT 1 AS "LEVEL"
--, 1 AS CONNECT_BY_ISROOT
--, 0 AS CONNECT_BY_ISBRANCH
, CASE WHEN t.id IN (SELECT parent_id FROM tbl) THEN 0 ELSE 1 END AS CONNECT_BY_ISLEAF
, 0 AS CONNECT_BY_ISCYCLE
, '/' + CAST(t.id AS VARCHAR(MAX)) + '/' AS SYS_CONNECT_BY_PATH_id
, '/' + CAST(t.name AS VARCHAR(MAX)) + '/' AS SYS_CONNECT_BY_PATH_name
, t.id AS root_id
, t.*
FROM tbl t
WHERE t.parent_id IS NULL -- START WITH parent_id IS NULL
UNION ALL
/* Recursive */
SELECT th."LEVEL" + 1 AS "LEVEL"
--, 0 AS CONNECT_BY_ISROOT
--, CASE WHEN t.id IN (SELECT parent_id FROM tbl) THEN 1 ELSE 0 END AS
CONNECT_BY_ISBRANCH
, CASE WHEN t.id IN (SELECT parent_id FROM tbl) THEN 0 ELSE 1 END AS CONNECT_BY_ISLEAF
, CASE WHEN th.SYS_CONNECT_BY_PATH_id LIKE '%/' + CAST(t.id AS VARCHAR(MAX)) + '/%'
THEN 1 ELSE 0 END AS CONNECT_BY_ISCYCLE
, th.SYS_CONNECT_BY_PATH_id + CAST(t.id AS VARCHAR(MAX)) + '/' AS
SYS_CONNECT_BY_PATH_id
, th.SYS_CONNECT_BY_PATH_name + CAST(t.name AS VARCHAR(MAX)) + '/' AS
SYS_CONNECT_BY_PATH_name
, th.root_id
, t.*
FROM tbl t
JOIN tbl_hierarchy th ON (th.id = t.parent_id) -- CONNECT BY PRIOR id = parent_id
WHERE th.CONNECT_BY_ISCYCLE = 0) -- NOCYCLE
SELECT th.*
--, REPLICATE(' ', (th."LEVEL" - 1) * 3) + th.name AS tbl_hierarchy
FROM tbl_hierarchy th
JOIN tbl CONNECT_BY_ROOT ON (CONNECT_BY_ROOT.id = th.root_id)
ORDER BY th.SYS_CONNECT_BY_PATH_name; -- ORDER SIBLINGS BY name

CONNECT BY features demonstrated above, with explanations:

Clauses
CONNECT BY: Specifies the relationship that defines the hierarchy.
START WITH: Specifies the root nodes.
ORDER SIBLINGS BY: Orders results properly.
Parameters
NOCYCLE: Stops processing a branch when a loop is detected. Valid hierarchies are Directed Acyclic
Graphs, and circular references violate this construct.
Operators
PRIOR: Obtains data from the node's parent.
CONNECT_BY_ROOT: Obtains data from the node's root.
Pseudocolumns
LEVEL: Indicates the node's distance from its root.
CONNECT_BY_ISLEAF: Indicates a node without children.
CONNECT_BY_ISCYCLE: Indicates a node with a circular reference.
Functions
SYS_CONNECT_BY_PATH: Returns a flattened/concatenated representation of the path to the node
from its root.

GoalKicker.com – SQL Notes for Professionals 133


Chapter 47: Views
Section 47.1: Simple views
A view can filter some rows from the base table or project only some columns from it:

CREATE VIEW new_employees_details AS


SELECT E.id, Fname, Salary, Hire_date
FROM Employees E
WHERE hire_date > date '2015-01-01';

If you select form the view:

select * from new_employees_details


Id FName Salary Hire_date
4 Johnathon 500 24-07-2016

Section 47.2: Complex views


A view can be a really complex query(aggregations, joins, subqueries, etc). Just be sure you add column names fo
everything you select:

Create VIEW dept_income AS


SELECT d.Name as DepartmentName, sum(e.salary) as TotalSalary
FROM Employees e
JOIN Departments d on e.DepartmentId = d.id
GROUP BY d.Name;

Now you can select from it as from any table:

SELECT *
FROM dept_income;
DepartmentName TotalSalary
HR 1900
Sales 600

GoalKicker.com – SQL Notes for Professionals 134


Chapter 48: Materialized Views
A materialized view is a view whose results are physically stored and must be periodically refreshed in order to
remain current. They are therefore useful for storing the results of complex, long-running queries when realtime
results are not required. Materialized views can be created in Oracle and PostgreSQL. Other database systems off
similar functionality, such as SQL Server's indexed views or DB2's materialized query tables.

Section 48.1: PostgreSQL example


CREATE TABLE mytable (number INT);
INSERT INTO mytable VALUES (1);

CREATE MATERIALIZED VIEW myview AS SELECT * FROM mytable;

SELECT * FROM myview;

number
--------
1
(1 row)

INSERT INTO mytable VALUES(2);

SELECT * FROM myview;

number
--------
1
(1 row)

REFRESH MATERIALIZED VIEW myview;

SELECT * FROM myview;

number
--------
1
2
(2 rows)

GoalKicker.com – SQL Notes for Professionals 135


Chapter 49: Comments
Section 49.1: Single-line comments
Single line comments are preceded --
by, and go until the end of the line:

SELECT *
FROM Employees -- this is a comment
WHERE FName = 'John'

Section 49.2: Multi-line comments


Multi-line code comments are wrapped /*
in... */ :

/* This query
returns all employees */
SELECT *
FROM Employees

It is also possible to insert such a comment into the middle of a line:

SELECT /* all columns: */ *


FROM Employees

GoalKicker.com – SQL Notes for Professionals 136


Chapter 50: Foreign Keys
Section 50.1: Foreign Keys explained
Foreign Keys constraints ensure data integrity, by enforcing that values in one table must match values in anothe
table.

An example of where a foreign key is required is: In a university, a course must belong to a department. Code for
the this scenario is:

CREATE TABLE Department (


Dept_Code CHAR (5) PRIMARY KEY,
Dept_Name VARCHAR (20) UNIQUE
);

Insert values with the following statement:

INSERT INTO Department VALUES ('CS205', 'Computer Science');

The following table will contain the information of the subjects offered by the Computer science branch:

CREATE TABLE Programming_Courses (


Dept_Code CHAR(5),
Prg_Code CHAR(9) PRIMARY KEY,
Prg_Name VARCHAR (50) UNIQUE,
FOREIGN KEY (Dept_Code) References Department(Dept_Code)
);

(The data type of the Foreign Key must match the datatype of the referenced key.)

The Foreign Key constraint on the column


Dept_Code allows values only if they already exist in the referenced table,
Department. This means that if you try to insert the following values:

INSERT INTO Programming_Courses Values ('CS300', 'FDB-DB001', 'Database Systems');

the database will raise a Foreign Key violation error, because


CS300 does not exist in the
Department table. But
when you try a key value that exists:

INSERT INTO Programming_Courses VALUES ('CS205', 'FDB-DB001', 'Database Systems');


INSERT INTO Programming_Courses VALUES ('CS205', 'DB2-DB002', 'Database Systems II');

then the database allows these values.

A few tips for using Foreign Keys

A Foreign Key must reference a UNIQUE (or PRIMARY) key in the parent table.
Entering a NULL value in a Foreign Key column does not raise an error.
Foreign Key constraints can reference tables within the same database.
Foreign Key constraints can refer to another column in the same table (self-reference).

Section 50.2: Creating a table with a foreign key


In this example we have an existing table,
SuperHeros .

GoalKicker.com – SQL Notes for Professionals 137


This table contains a primary key
ID .

We will add a new table in order to store the powers of each super hero:

CREATE TABLE HeroPowers


(
ID int NOT NULL PRIMARY KEY,
Name nvarchar(MAX) NOT NULL,
HeroId int REFERENCES SuperHeros(ID)
)

The columnHeroId is a foreign key to the table


SuperHeros .

GoalKicker.com – SQL Notes for Professionals 138


Chapter 51: Sequence
Section 51.1: Create Sequence
CREATE SEQUENCE orders_seq
START WITH 1000
INCREMENT BY 1;

Creates a sequence with a starting value of 1000 which is incremented by 1.

Section 51.2: Using Sequences


a reference to seq_name.NEXTVAL is used to get the next value in a sequence. A single statement can only gener
a single sequence value. If there are multiple references to NEXTVAL in a statement, they use will use the same
generated number.

NEXTVAL can be used for INSERTS

INSERT INTO Orders (Order_UID, Customer)


VALUES (orders_seq.NEXTVAL, 1032);

It can be used for UPDATES

UPDATE Orders
SET Order_UID = orders_seq.NEXTVAL
WHERE Customer = 581;

It can also be used for SELECTS

SELECT Order_seq.NEXTVAL FROM dual;

GoalKicker.com – SQL Notes for Professionals 139


Chapter 52: Subqueries
Section 52.1: Subquery in FROM clause
A subquery in aFROMclause acts similarly to a temporary table that is generated during the execution of a query
and lost afterwards.

SELECT Managers.Id, Employees.Salary


FROM (
SELECT Id
FROM Employees
WHERE ManagerId IS NULL
) AS Managers
JOIN Employees ON Managers.Id = Employees.Id

Section 52.2: Subquery in SELECT clause


SELECT
Id,
FName,
LName,
(SELECT COUNT(*) FROM Cars WHERE Cars.CustomerId = Customers.Id) AS NumberOfCars
FROM Customers

Section 52.3: Subquery in WHERE clause


Use a subquery to filter the result set. For example this will return all employees with a salary equal to the highes
paid employee.

SELECT *
FROM Employees
WHERE Salary = (SELECT MAX(Salary) FROM Employees)

Section 52.4: Correlated Subqueries


Correlated (also known as Synchronized or Coordinated) Subqueries are nested queries that make references to
the current row of their outer query:

SELECT EmployeeId
FROM Employee AS eOuter
WHERE Salary > (
SELECT AVG(Salary)
FROM Employee eInner
WHERE eInner.DepartmentId = eOuter.DepartmentId
)

SubquerySELECT AVG(Salary) ... is correlated because it refersEmployee


to row eOuter from its outer query.

Section 52.5: Filter query results using query on dieren


table
This query selects all employees not on the Supervisors table.

SELECT *

GoalKicker.com – SQL Notes for Professionals 140


FROM Employees
WHERE EmployeeID not in (SELECT EmployeeID
FROM Supervisors)

The same results can be achieved using a LEFT JOIN.

SELECT *
FROM Employees AS e
LEFT JOIN Supervisors AS s ON s.EmployeeID=e.EmployeeID
WHERE s.EmployeeID is NULL

Section 52.6: Subqueries in FROM clause


You can use subqueries to define a temporary table and use it in the FROM clause of an "outer" query.

SELECT * FROM (SELECT city, temp_hi - temp_lo AS temp_var FROM weather) AS w


WHERE temp_var > 20;

The above finds cities from the weather table whose daily temperature variation is greater than 20. The result is:

city temp_var
ST LOUIS 21
LOS ANGELES 31
LOS ANGELES 23
LOS ANGELES 31
LOS ANGELES 27
LOS ANGELES 28
LOS ANGELES 28
LOS ANGELES 32

Section 52.7: Subqueries in WHERE clause


The following example finds cities (from the cities example) whose population is below the average temperature
(obtained via a sub-qquery):

SELECT name, pop2000 FROM cities


WHERE pop2000 < (SELECT avg(pop2000) FROM cities);

Here: the subquery (SELECT avg(pop2000) FROM cities) is used to specify conditions in the WHERE clause. The re
is:

name pop2000
San Francisco 776733
ST LOUIS 348189
Kansas City 146866

GoalKicker.com – SQL Notes for Professionals 141


Chapter 53: Execution blocks
Section 53.1: Using BEGIN ... END
BEGIN
UPDATE Employees SET PhoneNumber = '5551234567' WHERE Id = 1;
UPDATE Employees SET Salary = 650 WHERE Id = 3;
END

GoalKicker.com – SQL Notes for Professionals 142


Chapter 54: Stored Procedures
Section 54.1: Create and call a stored procedure
Stored procedures can be created through a database management GUI (SQL Server example), or through a SQL
statement as follows:

-- Define a name and parameters


CREATE PROCEDURE Northwind.getEmployee
@LastName nvarchar(50),
@FirstName nvarchar(50)
AS

-- Define the query to be run


SELECT FirstName, LastName, Department
FROM Northwind.vEmployeeDepartment
WHERE FirstName = @FirstName AND LastName = @LastName
AND EndDate IS NULL;

Calling the procedure:

EXECUTE Northwind.getEmployee N'Ackerman', N'Pilar';

-- Or
EXEC Northwind.getEmployee @LastName = N'Ackerman', @FirstName = N'Pilar';
GO

-- Or
EXECUTE Northwind.getEmployee @FirstName = N'Pilar', @LastName = N'Ackerman';
GO

GoalKicker.com – SQL Notes for Professionals 143


Chapter 55: Triggers
Section 55.1: CREATE TRIGGER
This example creates a trigger that inserts a record to a second table (MyAudit) after a record is inserted into the
table the trigger is defined on (MyTable). Here the "inserted" table is a special table used by Microsoft SQL Server
store affected rows during INSERT and UPDATE statements; there is also a special "deleted" table that performs t
same function for DELETE statements.

CREATE TRIGGER MyTrigger


ON MyTable
AFTER INSERT

AS

BEGIN
-- insert audit record to MyAudit table
INSERT INTO MyAudit(MyTableId, User)
(SELECT MyTableId, CURRENT_USER FROM inserted)
END

Section 55.2: Use Trigger to manage a "Recycle Bin" for


deleted items
CREATE TRIGGER BooksDeleteTrigger
ON MyBooksDB.Books
AFTER DELETE
AS
INSERT INTO BooksRecycleBin
SELECT *
FROM deleted;
GO

GoalKicker.com – SQL Notes for Professionals 144


Chapter 56: Transactions
Section 56.1: Simple Transaction
BEGIN TRANSACTION
INSERT INTO DeletedEmployees(EmployeeID, DateDeleted, User)
(SELECT 123, GetDate(), CURRENT_USER);
DELETE FROM Employees WHERE EmployeeID = 123;
COMMIT TRANSACTION

Section 56.2: Rollback Transaction


When something fails in your transaction code and you want to undo it, you can rollback your transaction:

BEGIN TRY
BEGIN TRANSACTION
INSERT INTO Users(ID, Name, Age)
VALUES(1, 'Bob', 24)

DELETE FROM Users WHERE Name = 'Todd'


COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
END CATCH

GoalKicker.com – SQL Notes for Professionals 145


Chapter 57: Table Design
Section 57.1: Properties of a well designed table
A true relational database must go beyond throwing data into a few tables and writing some SQL statements to p
that data out.
At best a badly designed table structure will slow the execution of queries and could make it impossible for the
database to function as intended.

A database table should not be considered as just another table; it has to follow a set of rules to be considered tru
relational. Academically it is referred to as a 'relation' to make the distinction.

The five rules of a relational table are:

1. Each value is atomic; the value in each field in each row must be a single value.
2. Each field contains values that are of the same data type.
3. Each field heading has a unique name.
4. Each row in the table must have at least one value that makes it unique amongst the other records in the
table.
5. The order of the rows and columns has no significance.

A table conforming to the five rules:

Id Name DOB Manager


1 Fred 11/02/1971 3
2 Fred 11/02/1971 3
3 Sue 08/07/1975 2

Rule 1: Each value is atomic.


Id , Name
, DOBand Manager only contain a single value.
Rule 2:Id contains only integers,Namecontains text (we could add that it's text of four characters or DOB
less),
contains dates of a valid type and
Manager contains integers (we could add that corresponds to a Primary Key
field in a managers table).
Rule 3:Id , Name, DOBand Manager are unique heading names within the table.
Rule 4: The inclusion of the
Id field ensures that each record is distinct from any other record within the
table.

A badly designed table:

Id Name DOB Name


1 Fred 11/02/1971 3
1 Fred 11/02/1971 3
3 Sue Friday the 18th July 1975 2, 1

Rule 1: The second name field contains two values - 2 and 1.


Rule 2: The DOB field contains dates and text.
Rule 3: There's two fields called 'name'.
Rule 4: The first and second record are exactly the same.
Rule 5: This rule isn't broken.

GoalKicker.com – SQL Notes for Professionals 146


Chapter 58: Synonyms
Section 58.1: Create Synonym
CREATE SYNONYM EmployeeData
FOR MyDatabase.dbo.Employees

GoalKicker.com – SQL Notes for Professionals 147


Chapter 59: Information Schema
Section 59.1: Basic Information Schema Search
One of the most useful queries for end users of large RDBMS's is a search of an information schema.

Such a query allows users to rapidly find database tables containing columns of interest, such as when attemptin
to relate data from 2 tables indirectly through a third table, without existing knowledge of which tables may conta
keys or other useful columns in common with the target tables.

Using T-SQL for this example, a database's information schema may be searched as follows:

SELECT *
FROM INFORMATION_SCHEMA.COLUMNS
WHERE COLUMN_NAME LIKE '%Institution%'

The result contains a list of matching columns, their tables' names, and other useful information.

GoalKicker.com – SQL Notes for Professionals 148


Chapter 60: Order of Execution
Section 60.1: Logical Order of Query Processing in SQL
/*(8)*/ SELECT /*9*/ DISTINCT /*11*/ TOP
/*(1)*/ FROM
/*(3)*/ JOIN
/*(2)*/ ON
/*(4)*/ WHERE
/*(5)*/ GROUP BY
/*(6)*/ WITH {CUBE | ROLLUP}
/*(7)*/ HAVING
/*(10)*/ ORDER BY
/*(11)*/ LIMIT

The order in which a query is processed and description of each section.

VT stands for 'Virtual Table' and shows how various data is produced as the query is processed

1. FROM: A Cartesian product (cross join) is performed between the first two tables in the FROM clause, and as
a result, virtual table VT1 is generated.

2. ON: The ON filter is applied to VT1. Only rows for which the is TRUE are inserted to VT2.

3. OUTER (join): If an OUTER JOIN is specified (as opposed to a CROSS JOIN or an INNER JOIN), rows from the
preserved table or tables for which a match was not found are added to the rows from VT2 as outer rows,
generating VT3. If more than two tables appear in the FROM clause, steps 1 through 3 are applied repeated
between the result of the last join and the next table in the FROM clause until all tables are processed.

4. WHERE: The WHERE filter is applied to VT3. Only rows for which the is TRUE are inserted to VT4.

5. GROUP BY: The rows from VT4 are arranged in groups based on the column list specified in the GROUP BY
clause. VT5 is generated.

6. CUBE | ROLLUP: Supergroups (groups of groups) are added to the rows from VT5, generating VT6.

7. HAVING: The HAVING filter is applied to VT6. Only groups for which the is TRUE are inserted to VT7.

8. SELECT: The SELECT list is processed, generating VT8.

9. DISTINCT: Duplicate rows are removed from VT8. VT9 is generated.

10. ORDER BY: The rows from VT9 are sorted according to the column list specified in the ORDER BY clause. A
cursor is generated (VC10).

11. TOP: The specified number or percentage of rows is selected from the beginning of VC10. Table VT11 is
generated and returned to the caller. LIMIT has the same functionality as TOP in some SQL dialects such as
Postgres and Netezza.

GoalKicker.com – SQL Notes for Professionals 149


Chapter 61: Clean Code in SQL
How to write good, readable SQL queries, and example of good practices.

Section 61.1: Formatting and Spelling of Keywords and


Table/Column Names

Two common ways of formatting table/column names CamelCase


are and snake_case :

SELECT FirstName, LastName


FROM Employees
WHERE Salary > 500;

SELECT first_name, last_name


FROM employees
WHERE salary > 500;

Names should describe what is stored in their object. This implies that column names usually should be singular.
Whether table names should use singular or plural is a heavily discussed question, but in practice, it is more
common to use plural table names.

Adding prefixes or suffixes like


tbl or col reduces readability, so avoid them. However, they are sometimes used to
avoid conflicts with SQL keywords, and often used with triggers and indexes (whose names are usually not
mentioned in queries).

Keywords

SQL keywords are not case sensitive. However, it is common practice to write them in upper case.

Section 61.2: Indenting


There is no widely accepted standard. What everyone agrees on is that squeezing everything into a single line is
bad:

SELECT d.Name, COUNT(*) AS Employees FROM Departments AS d JOIN Employees AS e ON d.ID =


e.DepartmentID WHERE d.Name != 'HR' HAVING COUNT(*) > 10 ORDER BY COUNT(*) DESC;

At the minimum, put every clause into a new line, and split lines if they would become too long otherwise:

SELECT d.Name,
COUNT(*) AS Employees
FROM Departments AS d
JOIN Employees AS e ON d.ID = e.DepartmentID
WHERE d.Name != 'HR'
HAVING COUNT(*) > 10
ORDER BY COUNT(*) DESC;

Sometimes, everything after the SQL keyword introducing a clause is indented to the same column:

SELECT d.Name,
COUNT(*) AS Employees
FROM Departments AS d
JOIN Employees AS e ON d.ID = e.DepartmentID
WHERE d.Name != 'HR'
HAVING COUNT(*) > 10

GoalKicker.com – SQL Notes for Professionals 150


ORDER BY COUNT(*) DESC;

(This can also be done while aligning the SQL keywords right.)

Another common style is to put important keywords on their own lines:

SELECT
d.Name,
COUNT(*) AS Employees
FROM
Departments AS d
JOIN
Employees AS e
ON d.ID = e.DepartmentID
WHERE
d.Name != 'HR'
HAVING
COUNT(*) > 10
ORDER BY
COUNT(*) DESC;

Vertically aligning multiple similar expressions improves readability:

SELECT Model,
EmployeeID
FROM Cars
WHERE CustomerID = 42
AND Status = 'READY';

Using multiple lines makes it harder to embed SQL commands into other programming languages. However, man
languages have a mechanism for multi-line strings, e.g.,
@"..." in C#,"""...""" in Python, orR"(...)" in C++.

Section 61.3: SELECT *


SELECT * returns all columns in the same order as they are defined in the table.

When usingSELECT * , the data returned by a query can change whenever the table definition changes. This
increases the risk that different versions of your application or your database are incompatible with each other.

Furthermore, reading more columns than necessary can increase the amount of disk and network I/O.

So you should always explicitly specify the column(s) you actually want to retrieve:

--SELECT * don't
SELECT ID, FName, LName, PhoneNumber -- do
FROM Emplopees;

(When doing interactive queries, these considerations do not apply.)

However,SELECT * does not hurt in the subquery of an EXISTS operator, because EXISTS ignores the actual data
anyway (it checks only if at least one row has been found). For the same reason, it is not meaningful to list any
specific column(s) for EXISTS, SELECT
so * actually makes more sense:

-- list departments where nobody was hired recently


SELECT ID,
Name
FROM Departments

GoalKicker.com – SQL Notes for Professionals 151


WHERE NOT EXISTS (SELECT *
FROM Employees
WHERE DepartmentID = Departments.ID
AND HireDate >= '2015-01-01');

Section 61.4: Joins


Explicit joins should always be used; implicit joins have several problems:

The join condition is somewhere in the WHERE clause, mixed up with any other filter conditions. This makes
it harder to see which tables are joined, and how.

Due to the above, there is a higher risk of mistakes, and it is more likely that they are found later.

In standard SQL, explicit joins are the only way to use outer joins:

SELECT d.Name,
e.Fname || e.LName AS EmpName
FROM Departments AS d
LEFT JOIN Employees AS e ON d.ID = e.DepartmentID;

Explicit joins allow using the USING clause:

SELECT RecipeID,
Recipes.Name,
COUNT(*) AS NumberOfIngredients
FROM Recipes
LEFT JOIN Ingredients USING (RecipeID);

(This requires that both tables use the same column name.
USING automatically removes the duplicate column from the result, e.g., the join in this query returns a
singleRecipeID column.)

GoalKicker.com – SQL Notes for Professionals 152

You might also like