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

SQL

The document provides an overview of SQL indexing, including the creation of unique indexes, rebuilding indexes, and the differences between GROUP BY and DISTINCT. It also covers string functions, such as concatenation, length, trimming, and substring operations, along with examples. Additionally, it discusses conditional aggregation and list concatenation in SQL queries.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views

SQL

The document provides an overview of SQL indexing, including the creation of unique indexes, rebuilding indexes, and the differences between GROUP BY and DISTINCT. It also covers string functions, such as concatenation, length, trimming, and substring operations, along with examples. Additionally, it discusses conditional aggregation and list concatenation in SQL queries.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 56

fail. (Tables with no clustered indexes are also called heaps.

CREATE UNIQUE INDEX uq_customers_email ON Customers(Email);

This will create an unique index for the column Email in the table Customers. This index, along with speeding up
queries like a normal index, will also force every email address in that column to be unique. If a row is inserted or
updated with a non-unique Email value, the insertion or update will, by default, fail.

CREATE UNIQUE INDEX ix_eid_desc ON Customers(EmployeeID);

This creates an index on Customers which also creates a table constraint that the EmployeeID must be unique.
(This will fail if the column is not currently unique - in this case, if there are employees who share an ID.)

CREATE INDEX ix_eid_desc ON Customers(EmployeeID Desc);

This creates an index that is sorted in descending order. By default, indexes (in MSSQL server, at least) are
ascending, but that can be changed.

Section 37.6: Rebuild index


Over the course of time B-Tree indexes may become fragmented because of updating/deleting/inserting data. In
SQLServer terminology we can have internal (index page which is half empty ) and external (logical page order
doesn't correspond physical order). Rebuilding index is very similar to dropping and re-creating it.

We can re-build an index with

ALTER INDEX index_name REBUILD;

By default rebuilding index is offline operation which locks the table and prevents DML against it , but many RDBM
allow online rebuilding. Also, some DB vendors offer alternatives to index rebuilding such as
REORGANIZE
(SQLServer) orCOALESCE/ SHRINK SPACE(Oracle).

Section 37.7: Inserting with a Unique Index


UPDATE Customers SET Email = "[email protected]" WHERE id = 1;

This will fail if an unique index is set on the Email column of Customers. However, alternate behavior can be defin
for this case:

UPDATE Customers SET Email = "[email protected]" WHERE id = 1 ON DUPLICATE KEY;

GoalKicker.com – SQL Notes for Professionals 104


Chapter 38: Row number
Section 38.1: Delete All But Last Record (1 to Many Tabl
WITH cte AS (
SELECT ProjectID,
ROW_NUMBER() OVER (PARTITION BY ProjectID ORDER BY InsertDate DESC) AS rn
FROM ProjectNotes
)
DELETE FROM cte WHERE rn > 1;

Section 38.2: Row numbers without partitions


Include a row number according to the order specified.

SELECT
ROW_NUMBER() OVER(ORDER BY Fname ASC) AS RowNumber,
Fname,
LName
FROM Employees

Section 38.3: Row numbers with partitions


Uses a partition criteria to group the row numbering according to it.

SELECT
ROW_NUMBER() OVER(PARTITION BY DepartmentId ORDER BY DepartmentId ASC) AS RowNumber,
DepartmentId, Fname, LName
FROM Employees

GoalKicker.com – SQL Notes for Professionals 105


Chapter 39: SQL Group By vs Distinct
Section 39.1: Dierence between GROUP BY and DISTINC
GROUP BYis used in combination with aggregation functions. Consider the following table:

orderId userId storeName orderValue orderDate


1 43 Store A 25 20-03-2016
2 57 Store B 50 22-03-2016
3 43 Store A 30 25-03-2016
4 82 Store C 10 26-03-2016
5 21 Store A 45 29-03-2016

The query below usesGROUP BYto perform aggregated calculations.

SELECT
storeName,
COUNT(*) AS total_nr_orders,
COUNT(DISTINCT userId) AS nr_unique_customers,
AVG(orderValue) AS average_order_value,
MIN(orderDate) AS first_order,
MAX(orderDate) AS lastOrder
FROM
orders
GROUP BY
storeName;

and will return the following information

storeName total_nr_orders nr_unique_customers average_order_value first_order lastOrder


Store A 3 2 33.3 20-03-2016 29-03-2016
Store B 1 1 50 22-03-2016 22-03-2016
Store C 1 1 10 26-03-2016 26-03-2016

While DISTINCT is used to list a unique combination of distinct values for the specified columns.

SELECT DISTINCT
storeName,
userId
FROM
orders;
storeName userId
Store A 43
Store B 57
Store C 82
Store A 21

GoalKicker.com – SQL Notes for Professionals 106


Chapter 40: Finding Duplicates on a
Column Subset with Detail
Section 40.1: Students with same name and date of birt
WITH CTE (StudentId, Fname, LName, DOB, RowCnt)
as (
SELECT StudentId, FirstName, LastName, DateOfBirth as DOB, SUM(1) OVER (Partition By FirstName,
LastName, DateOfBirth) as RowCnt
FROM tblStudent
)
SELECT * from CTE where RowCnt > 1
ORDER BY DOB, LName

This example uses a Common Table Expression and a Window Function to show all duplicate rows (on a subset of
columns) side by side.

GoalKicker.com – SQL Notes for Professionals 107


Chapter 41: String Functions
String functions perform operations on string values and return either numeric or string values.

Using string functions, you can, for example, combine data, extract a substring, compare strings, or convert a stri
to all uppercase or lowercase characters.

Section 41.1: Concatenate


In (standard ANSI/ISO) SQL, the operator for string concatenation
|| .is
This syntax is supported by all major
databases except SQL Server:

SELECT 'Hello' || 'World' || '!'; --returns HelloWorld!

Many databases support CONCAT


a function to join strings:

SELECT CONCAT('Hello', 'World'); --returns 'HelloWorld'

Some databases support using


CONCATto join more than two strings (Oracle does not):

SELECT CONCAT('Hello', 'World', '!'); --returns 'HelloWorld!'

In some databases, non-string types must be cast or converted:

SELECT CONCAT('Foo', CAST(42 AS VARCHAR(5)), 'Bar'); --returns 'Foo42Bar'

Some databases (e.g., Oracle) perform implicit lossless conversions. For example, a on a CLOBand NCLOB
CONCAT
yields aNCLOB. A CONCATon a number and avarchar2 results in avarchar2 , etc.:

SELECT CONCAT(CONCAT('Foo', 42), 'Bar') FROM dual; --returns Foo42Bar

Some databases can use the non-standard


+ operator (but in most,
+ works only for numbers):

SELECT 'Foo' + CAST(42 AS VARCHAR(5)) + 'Bar';

On SQL Server < 2012, where


CONCATis not supported,+ is the only way to join strings.

Section 41.2: Length


SQL Server

The LEN doesn't count the trailing space.

SELECT LEN('Hello') -- returns 5

SELECT LEN('Hello '); -- returns 5

The DATALENGTH counts the trailing space.

SELECT DATALENGTH('Hello') -- returns 5

SELECT DATALENGTH('Hello '); -- returns 6

GoalKicker.com – SQL Notes for Professionals 108


It should be noted though, that DATALENGTH returns the length of the underlying byte representation of the strin
which depends, i.a., on the charset used to store the string.

DECLARE @str varchar(100) = 'Hello ' --varchar is usually an ASCII string, occupying 1 byte per
char
SELECT DATALENGTH(@str) -- returns 6

DECLARE @nstr nvarchar(100) = 'Hello ' --nvarchar is a unicode string, occupying 2 bytes per char
SELECT DATALENGTH(@nstr) -- returns 12

Oracle

Syntax: Length ( char )

Examples:

SELECT Length('Bible') FROM dual; --Returns 5


SELECT Length('righteousness') FROM dual; --Returns 13
SELECT Length(NULL) FROM dual; --Returns NULL

See Also: LengthB, LengthC, Length2, Length4

Section 41.3: Trim empty spaces


Trim is used to remove write-space at the beginning or end of selection

In MSSQL there is no single


TRIM()

SELECT LTRIM(' Hello ') --returns 'Hello '


SELECT RTRIM(' Hello ') --returns ' Hello'
SELECT LTRIM(RTRIM(' Hello ')) --returns 'Hello'

MySql and Oracle

SELECT TRIM(' Hello ') --returns 'Hello'

Section 41.4: Upper & lower case


SELECT UPPER('HelloWorld') --returns 'HELLOWORLD'
SELECT LOWER('HelloWorld') --returns 'helloworld'

Section 41.5: Split


Splits a string expression using a character separator. Note STRING_SPLIT()
that is a table-valued function.

SELECT value FROM STRING_SPLIT('Lorem ipsum dolor sit amet.', ' ');

Result:

value
-----
Lorem
ipsum
dolor
sit

GoalKicker.com – SQL Notes for Professionals 109


amet.

Section 41.6: Replace


Syntax:

REPLACE( String to search


, String to search for and replace
, String to place into the original string
)

Example:

SELECT REPLACE( 'Peter Steve Tom', 'Steve', 'Billy' ) --Return Values: Peter Billy Tom

Section 41.7: REGEXP


MySQL Version ≥ 3.19

Checks if a string matches a regular expression (defined by another string).

SELECT 'bedded' REGEXP '[a-f]' -- returns True

SELECT 'beam' REGEXP '[a-f]' -- returns False

Section 41.8: Substring


Syntax is:SUBSTRING ( string_expression, start, length ) . Note that SQL strings are 1-indexed.

SELECT SUBSTRING('Hello', 1, 2) --returns 'He'


SELECT SUBSTRING('Hello', 3, 3) --returns 'llo'

This is often used in conjunction with the


LEN() function to get the last
n characters of a string of unknown length.

DECLARE @str1 VARCHAR(10) = 'Hello', @str2 VARCHAR(10) = 'FooBarBaz';


SELECT SUBSTRING(@str1, LEN(@str1) - 2, 3) --returns 'llo'
SELECT SUBSTRING(@str2, LEN(@str2) - 2, 3) --returns 'Baz'

Section 41.9: Stu


Stuff a string into another, replacing 0 or more characters at a certain position.

Note: start position is 1-indexed (you start indexing at 1, not 0).

Syntax:

STUFF ( character_expression , start , length , replaceWith_expression )

Example:

SELECT STUFF('FooBarBaz', 4, 3, 'Hello') --returns 'FooHelloBaz'

Section 41.10: LEFT - RIGHT


Syntax is:
LEFT ( string-expression , integer )

GoalKicker.com – SQL Notes for Professionals 110


RIGHT ( string-expression , integer )

SELECT LEFT('Hello',2) --return He


SELECT RIGHT('Hello',2) --return lo

Oracle SQL doesn't have LEFT and RIGHT functions. They can be emulated with SUBSTR and LENGTH.
SUBSTR ( string-expression, 1, integer )
SUBSTR ( string-expression, length(string-expression)-integer+1, integer)

SELECT SUBSTR('Hello',1,2) --return He


SELECT SUBSTR('Hello',LENGTH('Hello')-2+1,2) --return lo

Section 41.11: REVERSE


Syntax is: REVERSE ( string-expression )

SELECT REVERSE('Hello') --returns olleH

Section 41.12: REPLICATE


The REPLICATE function concatenates a string with itself a specified number of times.

Syntax is: REPLICATE ( string-expression , integer )

SELECT REPLICATE ('Hello',4) --returns 'HelloHelloHelloHello'

Section 41.13: Replace function in sql Select and Update


The Replace function in SQL is used to update the content of a string. The function call is REPLACE( ) for MySQL,
Oracle, and SQL Server.

The syntax of the Replace function is:

REPLACE (str, find, repl)

The following example replaces occurrencesSouth


of with Southern in Employees table:

FirstName Address
James South New York
John South Boston
Michael South San Diego

Select Statement :

If we apply the following Replace function:

SELECT
FirstName,
REPLACE (Address, 'South', 'Southern') Address
FROM Employees
ORDER BY FirstName

Result:

GoalKicker.com – SQL Notes for Professionals 111


FirstName Address
James Southern New York
John Southern Boston
Michael Southern San Diego

Update Statement :

We can use a replace function to make permanent changes in our table through following approach.

Update Employees
Set city = (Address, 'South', 'Southern');

A more common approach is to use this in conjunction with a WHERE clause like this:

Update Employees
Set Address = (Address, 'South', 'Southern')
Where Address LIKE 'South%';

Section 41.14: INSTR


Return the index of the first occurrence of a substring (zero if not found)

Syntax: INSTR ( string, substring )

SELECT INSTR('FooBarBar', 'Bar') -- return 4


SELECT INSTR('FooBarBar', 'Xar') -- return 0

Section 41.15: PARSENAME


DATABASE : SQL Server

PARSENAME function returns the specific part of given string(object name). object name may contains string like
object name,owner name, database name and server name.

More details MSDN:PARSENAME

Syntax

PARSENAME('NameOfStringToParse',PartIndex)

Example

To get object name use part index


1

SELECT PARSENAME('ServerName.DatabaseName.SchemaName.ObjectName',1) // returns `ObjectName`


SELECT PARSENAME('[1012-1111].SchoolDatabase.school.Student',1) // returns `Student`

To get schema name use part index


2

SELECT PARSENAME('ServerName.DatabaseName.SchemaName.ObjectName',2) // returns `SchemaName`


SELECT PARSENAME('[1012-1111].SchoolDatabase.school.Student',2) // returns `school`

To get database name use part index


3

GoalKicker.com – SQL Notes for Professionals 112


SELECT PARSENAME('ServerName.DatabaseName.SchemaName.ObjectName',3) // returns `DatabaseName`
SELECT PARSENAME('[1012-1111].SchoolDatabase.school.Student',3) // returns `SchoolDatabase`

To get server name use part index


4

SELECT PARSENAME('ServerName.DatabaseName.SchemaName.ObjectName',4) // returns `ServerName`


SELECT PARSENAME('[1012-1111].SchoolDatabase.school.Student',4) // returns `[1012-1111]`

PARSENAME will returns null is specified part is not present in given object name string

GoalKicker.com – SQL Notes for Professionals 113


Chapter 42: Functions (Aggregate)
Section 42.1: Conditional aggregation
Payments Table

Customer Payment_type Amount


Peter Credit 100
Peter Credit 300
John Credit 1000
John Debit 500
select customer,
sum(case when payment_type = 'credit' then amount else 0 end) as credit,
sum(case when payment_type = 'debit' then amount else 0 end) as debit
from payments
group by customer

Result:

Customer Credit Debit


Peter 400 0
John 1000 500
select customer,
sum(case when payment_type = 'credit' then 1 else 0 end) as credit_transaction_count,
sum(case when payment_type = 'debit' then 1 else 0 end) as debit_transaction_count
from payments
group by customer

Result:

Customer credit_transaction_count debit_transaction_count


Peter 2 0
John 1 1

Section 42.2: List Concatenation


Partial credit to this SO answer.

List Concatenation aggregates a column or expression by combining the values into a single string for each group
string to delimit each value (either blank or a comma when omitted) and the order of the values in the result can
specified. While it is not part of the SQL standard, every major relational database vendor supports it in their own
way.

MySQL
SELECT ColumnA
, GROUP_CONCAT(ColumnB ORDER BY ColumnB SEPARATOR ',') AS ColumnBs
FROM TableName
GROUP BY ColumnA
ORDER BY ColumnA;

Oracle & DB2


SELECT ColumnA
, LISTAGG(ColumnB, ',') WITHIN GROUP (ORDER BY ColumnB) AS ColumnBs
FROM TableName

GoalKicker.com – SQL Notes for Professionals 114


GROUP BY ColumnA
ORDER BY ColumnA;

PostgreSQL
SELECT ColumnA
, STRING_AGG(ColumnB, ',' ORDER BY ColumnB) AS ColumnBs
FROM TableName
GROUP BY ColumnA
ORDER BY ColumnA;

SQL Server
SQL Server 2016 and earlier

(CTE included to encourage the DRY principle)

WITH CTE_TableName AS (
SELECT ColumnA, ColumnB
FROM TableName)
SELECT t0.ColumnA
, STUFF((
SELECT ',' + t1.ColumnB
FROM CTE_TableName t1
WHERE t1.ColumnA = t0.ColumnA
ORDER BY t1.ColumnB
FOR XML PATH('')), 1, 1, '') AS ColumnBs
FROM CTE_TableName t0
GROUP BY t0.ColumnA
ORDER BY ColumnA;

SQL Server 2017 and SQL Azure


SELECT ColumnA
, STRING_AGG(ColumnB, ',') WITHIN GROUP (ORDER BY ColumnB) AS ColumnBs
FROM TableName
GROUP BY ColumnA
ORDER BY ColumnA;

SQLite

without ordering:

SELECT ColumnA
, GROUP_CONCAT(ColumnB, ',') AS ColumnBs
FROM TableName
GROUP BY ColumnA
ORDER BY ColumnA;

ordering requires a subquery or CTE:

WITH CTE_TableName AS (
SELECT ColumnA, ColumnB
FROM TableName
ORDER BY ColumnA, ColumnB)
SELECT ColumnA
, GROUP_CONCAT(ColumnB, ',') AS ColumnBs
FROM CTE_TableName
GROUP BY ColumnA
ORDER BY ColumnA;

GoalKicker.com – SQL Notes for Professionals 115


Section 42.3: SUM
Sumfunction sum the value of all the rows in the group. If the group by clause is omitted then sums all the rows.

select sum(salary) TotalSalary


from employees;
TotalSalary
2500
select DepartmentId, sum(salary) TotalSalary
from employees
group by DepartmentId;
DepartmentId TotalSalary
1 2000
2 500

Section 42.4: AVG()


The aggregate function AVG() returns the average of a given expression, usually numeric values in a column.
Assume we have a table containing the yearly calculation of population in cities across the world. The records for
New York City look similar to the ones below:

EXAMPLE TABLE
city_name population year
New York City 8,550,405 2015
New York City ... ...
New York City 8,000,906 2005

To select the average population of the New York City, USA from a table containing city names, population
measurements, and measurement years for last ten years:

QUERY
select city_name, AVG(population) avg_population
from city_population
where city_name = 'NEW YORK CITY';

Notice how measurement year is absent from the query since population is being averaged over time.

RESULTS
city_name avg_population
New York City 8,250,754

Note: The AVG() function will convert values to numeric types. This is especially important to keep in mind
when working with dates.

Section 42.5: Count


You can count the number of rows:

SELECT count(*) TotalRows


FROM employees;
TotalRows

GoalKicker.com – SQL Notes for Professionals 116


4

Or count the employees per department:

SELECT DepartmentId, count(*) NumEmployees


FROM employees
GROUP BY DepartmentId;
DepartmentId NumEmployees
1 3
2 1

You can count over a column/expression with the effect that will not count thevalues:
NULL

SELECT count(ManagerId) mgr


FROM EMPLOYEES;
mgr
3

(There is one null value managerID column)

You can also use DISTINCT inside of another function such as COUNT to only find the DISTINCT members of the
set to perform the operation on.

For example:

SELECT COUNT(ContinentCode) AllCount


, COUNT(DISTINCT ContinentCode) SingleCount
FROM Countries;

Will return different values. The SingleCount will only Count individual Continents once, while the AllCount will
include duplicates.

ContinentCode
OC
EU
AS
NA
NA
AF
AF

AllCount: 7 SingleCount: 5

Section 42.6: Min


Find the smallest value of column:

select min(age) from employee;

Above example will return smallest value for column


age of employee table.

Syntax:

GoalKicker.com – SQL Notes for Professionals 117


SELECT MIN(column_name) FROM table_name;

Section 42.7: Max


Find the maximum value of column:

select max(age) from employee;

Above example will return largest value for column


age of employee table.

Syntax:

SELECT MAX(column_name) FROM table_name;

GoalKicker.com – SQL Notes for Professionals 118


Chapter 43: Functions (Scalar/Single Ro
SQL provides several built-in scalar functions. Each scalar function takes one value as input and returns one value
as output for each row in a result set.

You use scalar functions wherever an expression is allowed within a T-SQL statement.

Section 43.1: Date And Time


In SQL, you use date and time data types to store calendar information. These data types include the time, date,
smalldatetime, datetime, datetime2, and datetimeoffset. Each data type has a specific format.

Data type Format


time hh:mm:ss[.nnnnnnn]
date YYYY-MM-DD
smalldatetime YYYY-MM-DD hh:mm:ss
datetime YYYY-MM-DD hh:mm:ss[.nnn]
datetime2 YYYY-MM-DD hh:mm:ss[.nnnnnnn]
datetimeoffset YYYY-MM-DD hh:mm:ss[.nnnnnnn] [+/-]hh:mm

The DATENAMEfunction returns the name or value of a specific part of the date.

SELECT DATENAME (weekday,'2017-01-14') as Datename


Datename
Saturday

You use theGETDATEfunction to determine the current date and time of the computer running the current SQL
instance. This function doesn't include the time zone difference.

SELECT GETDATE() as Systemdate


Systemdate
2017-01-14 11:11:47.7230728

The DATEDIFF function returns the difference between two dates.

In the syntax, datepart is the parameter that specifies which part of the date you want to use to calculate
difference. The datepart can be year, month, week, day, hour, minute, second, or millisecond. You then specify th
start date in the startdate parameter and the end date in the enddate parameter for which you want to find the
difference.

SELECT SalesOrderID, DATEDIFF(day, OrderDate, ShipDate)


AS 'Processing time'
FROM Sales.SalesOrderHeader
SalesOrderID Processing time
43659 7
43660 7
43661 7
43662 7

The DATEADDfunction enables you to add an interval to part of a specific date.

GoalKicker.com – SQL Notes for Professionals 119


SELECT DATEADD (day, 20, '2017-01-14') AS Added20MoreDays
Added20MoreDays
2017-02-03 00:00:00.000

Section 43.2: Character modifications


Character modifying functions include converting characters to upper or lower case characters, converting
numbers to formatted numbers, performing character manipulation, etc.

The lower(char) function converts the given character parameter to be lower-cased characters.

SELECT customer_id, lower(customer_last_name) FROM customer;

would return the customer's last name changed from "SMITH" to "smith".

Section 43.3: Configuration and Conversion Function


An example of a configuration function in SQL is the function. This function provides the name of the
@@SERVERNAME
local server that's running SQL.

SELECT @@SERVERNAME AS 'Server'


Server
SQL064

In SQL, most data conversions occur implicitly, without any user intervention.

To perform any conversions that can't be completed implicitly, you can use theor CONVERTfunctions.
CAST

The CAST function syntax is simpler than the


CONVERTfunction syntax, but is limited in what it can do.

In here, we use both the


CAST and CONVERTfunctions to convert the datetime data type to varchar
the data type.

The CAST function always uses the default style setting. For example, it will represent dates and times using the
format YYYY-MM-DD.

The CONVERTfunction uses the date and time style you specify. In this case, 3 specifies the date format dd/mm/yy.

USE AdventureWorks2012
GO
SELECT FirstName + ' ' + LastName + ' was hired on ' +
CAST(HireDate AS varchar(20)) AS 'Cast',
FirstName + ' ' + LastName + ' was hired on ' +
CONVERT(varchar, HireDate, 3) AS 'Convert'
FROM Person.Person AS p
JOIN HumanResources.Employee AS e
ON p.BusinessEntityID = e.BusinessEntityID
GO
Cast Convert
David Hamiltion was hired on 2003-02-04 David Hamiltion was hired on 04/02/03

Another example of a conversion function is the


PARSE function. This function converts a string to a specified data
type.

In the syntax for the function, you specify the string that must be converted,
AS the
keyword, and then the required

GoalKicker.com – SQL Notes for Professionals 120


data type. Optionally, you can also specify the culture in which the string value should be formatted. If you don't
specify this, the language for the session is used.

If the string value can't be converted to a numeric, date, or time format, it will result in an error. You'll then need
use CAST or CONVERTfor the conversion.

SELECT PARSE('Monday, 13 August 2012' AS datetime2 USING 'en-US') AS 'Date in English'


Date in English
2012-08-13 00:00:00.0000000

Section 43.4: Logical and Mathmetical Function


SQL has two logical functions – andIIF .
CHOOSE

The CHOOSEfunction returns an item from a list of values, based on its position in the list. This position is specified
by the index.

In the syntax, the index parameter specifies the item and is a whole number, or integer. The val_1 … val_n
parameter identifies the list of values.

SELECT CHOOSE(2, 'Human Resources', 'Sales', 'Admin', 'Marketing' ) AS Result;


Result
Sales

In this example, you use the


CHOOSEfunction to return the second entry in a list of departments.

The IIF function returns one of two values, based on a particular condition. If the condition is true, it will return
true value. Otherwise it will return a false value.

In the syntax, the boolean_expression parameter specifies the Boolean expression. The true_value parameter
specifies the value that should be returned if the boolean_expression evaluates to true and the false_value
parameter specifies the value that should be returned if the boolean_expression evaluates to false.

SELECT BusinessEntityID, SalesYTD,


IIF(SalesYTD > 200000, 'Bonus', 'No Bonus') AS 'Bonus?'
FROM Sales.SalesPerson
GO
BusinessEntityID SalesYTD Bonus?
274 559697.5639 Bonus
275 3763178.1787 Bonus
285 172524.4512 No Bonus

In this example, you use the IIF function to return one of two values. If a sales person's year-to-date sales are abo
200,000, this person will be eligible for a bonus. Values below 200,000 mean that employees don't qualify for
bonuses.

SQL includes several mathematical functions that you can use to perform calculations on input value
return numeric results.

One example is theSIGN function, which returns a value indicating the sign of an expression. The value of -1
indicates a negative expression, the value of +1 indicates a positive expression, and 0 indicates zero.

GoalKicker.com – SQL Notes for Professionals 121


SELECT SIGN(-20) AS 'Sign'
Sign
-1

In the example, the input is a negative number, so the Results pane lists the result -1.

Another mathematical function is the


POWERfunction. This function provides the value of an expression raised to a
specified power.

In the syntax, the float_expression parameter specifies the expression, and the y parameter specifies the power t
which you want to raise the expression.

SELECT POWER(50, 3) AS Result


Result
125000

GoalKicker.com – SQL Notes for Professionals 122


Chapter 44: Functions (Analytic)
You use analytic functions to determine values based on groups of values. For example, you can use this type of
function to determine running totals, percentages, or the top result within a group.

Section 44.1: LAG and LEAD


The LAG function provides data on rows before the current row in the same result set. For example, in a
SELECT
statement, you can compare values in the current row with values in a previous row.

You use a scalar expression to specify the values that should be compared. The offset parameter is the number o
rows before the current row that will be used in the comparison. If you don't specify the number of rows, the
default value of one row is used.

The default parameter specifies the value that should be returned when the expression at offsetNULL
hasvalue.
a If
you don't specify a value, a valueNULL
of is returned.

The LEAD function provides data on rows after the current row in the row set. For example, in a statement,
SELECT
you can compare values in the current row with values in the following row.

You specify the values that should be compared using a scalar expression. The offset parameter is the number of
rows after the current row that will be used in the comparison.

You specify the value that should be returned when the expression at offset NULL
has avalue using the default
parameter. If you don't specify these parameters, the default of one row is used and a value
NULLof
is returned.

SELECT BusinessEntityID, SalesYTD,


LEAD(SalesYTD, 1, 0) OVER(ORDER BY BusinessEntityID) AS "Lead value",
LAG(SalesYTD, 1, 0) OVER(ORDER BY BusinessEntityID) AS "Lag value"
FROM SalesPerson;

This example uses the LEAD and LAG functions to compare the sales values for each employee to date with those
the employees listed above and below, with records ordered based on the BusinessEntityID column.

BusinessEntityID SalesYTD Lead value Lag value


274 559697.5639 3763178.1787 0.0000
275 3763178.1787 4251368.5497
559697.5639
276 4251368.5497 3189418.3662 3763178.1787
277 3189418.3662 1453719.4653 4251368.5497
278 1453719.4653 2315185.6110 3189418.3662
279 2315185.6110 1352577.1325 1453719.4653

Section 44.2: PERCENTILE_DISC and PERCENTILE_CONT


The PERCENTILE_DISC function lists the value of the first entry where the cumulative distribution is higher than the
percentile that you provide using the
numeric_literal parameter.

The values are grouped by rowset or partition, as specified byWITHIN


the GROUPclause.

The PERCENTILE_CONTfunction is similar to the


PERCENTILE_DISC function, but returns the average of the sum of
the first matching entry and the next entry.

GoalKicker.com – SQL Notes for Professionals 123


SELECT BusinessEntityID, JobTitle, SickLeaveHours,
CUME_DIST() OVER(PARTITION BY JobTitle ORDER BY SickLeaveHours ASC)
AS "Cumulative Distribution",
PERCENTILE_DISC(0.5) WITHIN GROUP(ORDER BY SickLeaveHours)
OVER(PARTITION BY JobTitle) AS "Percentile Discreet"
FROM Employee;

To find the exact value from the row that matches or exceeds the 0.5 percentile, you pass the percentile as the
numeric literal in the
PERCENTILE_DISC function. The Percentile Discreet column in a result set lists the value of the
row at which the cumulative distribution is higher than the specified percentile.

BusinessEntityID JobTitle SickLeaveHours Cumulative Distribution Percentile Discreet


272 Application Specialist 55 0.25 56
268 Application Specialist 56 0.75 56
269 Application Specialist 56 0.75 56
267 Application Specialist 57 1 56

To base the calculation on a set of values, you usePERCENTILE_CONT


the function. The "Percentile Continuous"
column in the results lists the average value of the sum of the result value and the next highest matching value.

SELECT BusinessEntityID, JobTitle, SickLeaveHours,


CUME_DIST() OVER(PARTITION BY JobTitle ORDER BY SickLeaveHours ASC)
AS "Cumulative Distribution",
PERCENTILE_DISC(0.5) WITHIN GROUP(ORDER BY SickLeaveHours)
OVER(PARTITION BY JobTitle) AS "Percentile Discreet",
PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY SickLeaveHours)
OVER(PARTITION BY JobTitle) AS "Percentile Continuous"
FROM Employee;
Cumulative Percentile Percentile
BusinessEntityID JobTitle SickLeaveHours
Distribution Discreet Continuous
272 Application Specialist 55 0.25 56 56
268 Application Specialist 56 0.75 56 56
269 Application Specialist 56 0.75 56 56
267 Application Specialist 57 1 56 56

Section 44.3: FIRST_VALUE


You use theFIRST_VALUE function to determine the first value in an ordered result set, which you identify using a
scalar expression.

SELECT StateProvinceID, Name, TaxRate,


FIRST_VALUE(StateProvinceID)
OVER(ORDER BY TaxRate ASC) AS FirstValue
FROM SalesTaxRate;

In this example, the


FIRST_VALUE function is used to return the
ID of the state or province with the lowest tax rate.
The OVERclause is used to order the tax rates to obtain the lowest rate.

StateProvinceID Name TaxRate FirstValue


74 Utah State Sales Tax 5.00 74
36 Minnesota State Sales Tax 6.75 74
30 Massachusetts State Sales Tax 7.00 74

GoalKicker.com – SQL Notes for Professionals 124


1 Canadian GST 7.00 74
57 Canadian GST 7.00 74
63 Canadian GST 7.00 74

Section 44.4: LAST_VALUE


The LAST_VALUE function provides the last value in an ordered result set, which you specify using a scalar
expression.

SELECT TerritoryID, StartDate, BusinessentityID,


LAST_VALUE(BusinessentityID)
OVER(ORDER BY TerritoryID) AS LastValue
FROM SalesTerritoryHistory;

This example uses the


LAST_VALUE function to return the last value for each rowset in the ordered values.

TerritoryID StartDate BusinessentityID LastValue


1 2005-07-01 00.00.00.000 280 283
1 2006-11-01 00.00.00.000 284 283
1 2005-07-01 00.00.00.000 283 283
2 2007-01-01 00.00.00.000 277 275
2 2005-07-01 00.00.00.000 275 275
3 2007-01-01 00.00.00.000 275 277

Section 44.5: PERCENT_RANK and CUME_DIST


The PERCENT_RANKfunction calculates the ranking of a row relative to the row set. The percentage is based on the
number of rows in the group that have a lower value than the current row.

The first value in the result set always has a percent rank of zero. The value for the highest-ranked – or last – valu
in the set is always one.

The CUME_DISTfunction calculates the relative position of a specified value in a group of values, by determining th
percentage of values less than or equal to that value. This is called the cumulative distribution.

SELECT BusinessEntityID, JobTitle, SickLeaveHours,


PERCENT_RANK() OVER(PARTITION BY JobTitle ORDER BY SickLeaveHours DESC)
AS "Percent Rank",
CUME_DIST() OVER(PARTITION BY JobTitle ORDER BY SickLeaveHours DESC)
AS "Cumulative Distribution"
FROM Employee;

In this example, you use an


ORDERclause to partition – or group – the rows retrieved by SELECT
the statement based
on employees' job titles, with the results in each group sorted based on the numbers of sick leave hours that
employees have used.

BusinessEntityID JobTitle SickLeaveHours Percent Rank Cumulative Distribution


267 Application Specialist 57 0 0.25
268 Application Specialist 56 0.333333333333333 0.75
269 Application Specialist 56 0.333333333333333 0.75
272 Application Specialist 55 1 1

GoalKicker.com – SQL Notes for Professionals 125


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


Chapter 62: SQL Injection
SQL injection is an attempt to access a website's database tables by injecting SQL into a form field. If a web serve
does not protect against SQL injection attacks, a hacker can trick the database into running the additional SQL co
By executing their own SQL code, hackers can upgrade their account access, view someone else's private
information, or make any other modifications to the database.

Section 62.1: SQL injection sample


Assuming the call to your web application's login handler looks like this:

https://somepage.com/ajax/login.ashx?username=admin&password=123

Now in login.ashx, you read these values:

strUserName = getHttpsRequestParameterString("username");
strPassword = getHttpsRequestParameterString("password");

and query your database to determine whether a user with that password exists.

So you construct an SQL query string:

txtSQL = "SELECT * FROM Users WHERE username = '" + strUserName + "' AND password = '"+ strPassword
+"'";

This will work if the username and password do not contain a quote.

However, if one of the parameters does contain a quote, the SQL that gets sent to the database will look like this:

-- strUserName = "d'Alambert";
txtSQL = "SELECT * FROM Users WHERE username = 'd'Alambert' AND password = '123'";

This will result in a syntax error, because the quote afterdthe


in d'Alambert ends the SQL string.

You could correct this by escaping quotes in username and password, e.g.:

strUserName = strUserName.Replace("'", "''");


strPassword = strPassword.Replace("'", "''");

However, it's more appropriate to use parameters:

cmd.CommandText = "SELECT * FROM Users WHERE username = @username AND password = @password";

cmd.Parameters.Add("@username", strUserName);
cmd.Parameters.Add("@password", strPassword);

If you do not use parameters, and forget to replace quote in even one of the values, then a malicious user (aka
hacker) can use this to execute SQL commands on your database.

For example, if an attacker is evil, he/she will set the password to

lol'; DROP DATABASE master; --

and then the SQL will look like this:

GoalKicker.com – SQL Notes for Professionals 153


"SELECT * FROM Users WHERE username = 'somebody' AND password = 'lol'; DROP DATABASE master; --'";

Unfortunately for you, this is valid SQL, and the DB will execute this!

This type of exploit is called an SQL injection.

There are many other things a malicious user could do, such as stealing every user's email address, steal everyon
password, steal credit card numbers, steal any amount of data in your database, etc.

This is why you always need to escape your strings.


And the fact that you'll invariably forget to do so sooner or later is exactly why you should use parameters. Becau
if you use parameters, then your programming language framework will do any necessary escaping for you.

Section 62.2: simple injection sample


If the SQL statement is constructed like this:

SQL = "SELECT * FROM Users WHERE username = '" + user + "' AND password ='" + pw + "'";
db.execute(SQL);

Then a hacker could retrieve your data by giving a passwordpw'


like
or '1'='1 ; the resulting SQL statement will
be:

SELECT * FROM Users WHERE username = 'somebody' AND password ='pw' or '1'='1'

This one will pass the password check for all rows in Users
the table because'1'='1' is always true.

To prevent this, use SQL parameters:

SQL = "SELECT * FROM Users WHERE username = ? AND password = ?";


db.execute(SQL, [user, pw]);

GoalKicker.com – SQL Notes for Professionals 154


Credits
Thank you greatly to all the people from Stack Overflow Documentation who helped provide this content,
more changes can be sent to [email protected] for new content to be published or updated

Özgür Öztürk Chapters 8 and 17


3N1GM4 Chapter 7
a1ex07 Chapter 37
Abe Miessler Chapter 7
Abhilash R Vankayala Chapters 5, 6, 11, 27, 30 and 32
aholmes Chapter 6
Aidan Chapters 21 and 25
alex9311 Chapter 21
Almir Vuk Chapters 21 and 37
Alok Singh Chapter 6
Ameya Deshpande Chapter 26
‫ ریما‬Amir Pourmand
Chapter 56
‫دنمروپ‬
Amnon Chapter 6
Andrea Chapter 24
Andrea Montanari Chapter 36
Andreas Chapter 2
Andy G Chapter 18
apomene Chapter 6
Ares Chapter 21
Arkh Chapter 45
Arpit Solanki Chapter 6
Arthur D Chapter 41
Arulkumar Chapters 13 and 41
ashja99 Chapters 11 and 42
Athafoud Chapter 24
Ayaz Shah Chapter 11
A_Arnold Chapter 18
Bart Schuijt Chapter 11
Batsu Chapter 41
bhs Chapter 45
bignose Chapter 5
blackbishop Chapter 25
Blag Chapter 17
Bostjan Chapters 5, 7 and 13
Branko Dimitrijevic Chapter 18
Brent Oliver Chapter 6
brichins Chapter 54
carlosb Chapters 37 and 39
Chris Chapter 6
Christian Chapter 5
Christian Sagmüller Chapter 6
Christos Chapter 6
CL. Chapters 1, 2, 6, 8, 10, 14, 18, 19, 21, 31, 36, 37, 41, 42, 46, 49, 61 and 62
Cristian Abelleira Chapter 30
DaImTo Chapter 30
Daniel Chapter 46

GoalKicker.com – SQL Notes for Professionals 155


Daniel Langemann Chapters 18 and 24
dariru Chapter 6
Dariusz Chapters 10 and 19
Darrel Lee Chapter 40
Darren Bartrup Chapters 18 and 57
Daryl Chapters 6, 55, 56 and 58
dasblinkenlight Chapter 52
David Manheim Chapter 37
David Pine Chapter 6
David Spillett Chapter 6
day_dreamer Chapter 6
dd4711 Chapter 46
dmfay Chapter 48
Durgpal Singh Chapter 6
Dylan Vander Berg Chapters 21 and 29
Emil Rowland Chapter 20
Eric VB Chapter 6
Florin Ghita Chapters 5, 18, 25, 42 and 47
FlyingPiMonster Chapters 5, 6, 19, 36 and 37
forsvarir Chapters 5 and 18
Franck Dernoncourt Chapters 6, 18 and 41
Frank Chapter 7
fuzzy_logic Chapter 46
Gallus Chapter 60
geeksal Chapter 1
Gidil Chapter 45
Golden Gate Chapter 41
guiguiblitz Chapter 9
H. Pauwelyn Chapter 21
Hack Chapter 59
Harish Gyanani Chapter 11
Harjot Chapter 50
hatchet Chapter 41
hellyale Chapter 11
HK1 Chapter 18
HLGEM Chapter 18
HoangHieu Chapter 6
Horaciux Chapter 37
Hynek Bernard Chapter 30
Ian Kenney Chapter 42
iliketocode Chapter 6
Imran Ali Khan Chapters 6, 41 and 42
Inca Chapter 6
IncrediApp Chapter 55
Jared Hooper Chapter 6
Jason W Chapter 24
JavaHopper Chapter 5
Jaydip Jadhav Chapter 41
Jaydles Chapters 6, 7, 8 and 10
Jenism Chapter 37
Jerry Jeremiah Chapter 45
Jim Chapter 24
Joe Taras Chapter 24

GoalKicker.com – SQL Notes for Professionals 156


Joel Chapters 29 and 31
John Odom Chapters 6, 18, 22, 32 and 56
John Slegers Chapters 6 and 18
John Smith Chapter 51
JohnLBevan Chapter 1
Jojodmo Chapter 21
Jon Chan Chapter 13
Jon Ericson Chapters 1 and 13
JonH Chapter 6
juergen d Chapters 12, 13 and 42
Karthikeyan Chapter 28
Kewin Björk Nielsen Chapters 41 and 43
KIRAN KUMAR MATAM Chapter 21
KjetilNordin Chapter 36
Knickerless Chapter 62
Lankymart Chapter 6
LCIII Chapter 15
Leigh Riffel Chapter 41
Lexi Chapter 25
Lohitha Palagiri Chapter 11
Mark Iannucci Chapters 6 and 18
Mark Perera Chapters 6 and 11
Mark Stewart Chapter 43
Matas Vaitkevicius Chapters 6, 13, 14, 19, 21 and 41
Mateusz Piotrowski Chapter 41
Matt Chapters 5, 6 and 10
Matt S Chapter 6
Mattew Whitt Chapter 6
mauris Chapter 37
Mihai Chapters 6 and 24
mithra chintha Chapters 8 and 25
MotKohn Chapter 10
Mureinik Chapters 10, 18 and 45
mustaccio Chapters 6 and 45
Mzzzzzz Chapter 5
Nathan Chapter 42
nazark Chapter 8
Neria Nachum Chapter 41
Nunie123 Chapter 52
Oded Chapter 6
Ojen Chapters 6 and 11
omini data Chapters 42 and 44
onedaywhen Chapter 6
Ozair Kafray Chapter 25
Parado Chapters 8 and 37
Paul Bambury Chapter 30
Paulo Freitas Chapter 37
Peter K Chapters 42 and 46
Phrancis Chapters 1, 3, 4, 8, 11, 13, 18, 19, 29, 38, 41, 46, 49, 52 and 53
Prateek Chapters 1, 6 and 21
Preuk Chapter 6
Racil Hilan Chapter 6
raholling Chapter 18

GoalKicker.com – SQL Notes for Professionals 157


rajarshig Chapter 26
RamenChef Chapter 41
Reboot Chapter 42
Redithion Chapter 11
Ricardo Pontual Chapter 22
Robert Columbia Chapters 6 and 41
Ryan Chapter 37
Ryan Rockey Chapter 60
Saroj Sasmal Chapters 4 and 6
Shiva Chapter 5
Sibeesh Venu Chapter 46
Simon Foster Chapter 25
Simone Chapter 7
Simulant Chapter 16
SommerEngineering Chapter 6
SQLFox Chapter 27
sqluser Chapter 6
Stanislovas Kalašnikovas Chapter 10
Stefan Steiger Chapters 11, 18, 33 and 62
Steven Chapter 35
Stivan Chapter 61
Stu Chapter 31
Timothy Chapter 6
tinlyx Chapter 52
Tot Zam Chapters 5, 13, 18, 19, 26 and 42
Uberzen1 Chapter 23
Umesh Chapter 29
user1221533 Chapter 38
user1336087 Chapter 6
user2314737 Chapter 34
user5389107 Chapter 5
Vikrant Chapter 11
vmaroli Chapters 11, 19 and 41
walid Chapters 4 and 12
WesleyJohnson Chapter 5
William Ledbetter Chapter 42
wintersolider Chapter 6
Wolfgang Chapter 8
xenodevil Chapters 18 and 29
xQbert Chapter 6
Yehuda Shapira Chapter 50
ypercube Chapters 1 and 4
Yury Fedorov Chapter 6
Zaga Chapter 12
Zahiro Mor Chapters 6 and 7
zedfoxus Chapter 6
Zoyd Chapter 27
zplizzi Chapter 26
ɐlǝx Chapters 10 and 41
Алексей Неудачин Chapter 42
Рахул Маквана Chapter 18

GoalKicker.com – SQL Notes for Professionals 158


You may also like

You might also like