Start - how to set up SQL database?

Before you start learning SQL queries on your computer you need to download and install database management tools and connect to a database. You will need:

Download Northwind Database - a sample database provided by Microsoft in early 2000. The Northwind database contains the sales data for a fictitious company called “Northwind Traders,” which imports and exports specialty foods from around the world.

Next, download and install Microsoft® SQL Server® - a relational database management system. SQL Server 2019 Developer Edition includes the same features as SQL Server Enterprise Edition, but is limited by the license to be only used as a development and test system, and not as production server.

https://www.microsoft.com/en-us/sql-server/sql-server-downloads

Download and install a tool for managing your SQL infrastructure - SQL Server Management Studio (SSMS).

https://docs.microsoft.com/en-us/sql/ssms/download-sql-server-management-studio-ssms?view=sql-server-ver15

In the web you will find multiple tutorials if you face any difficulties during the installation process so I won't go deeper into that. Once the tools are installed start SSMS and type server name to connect. In my case it is: localhost\SQLEXPRESS.

To connect to Northwind database press right mouse button on Databases and choose Restore Database.

Next, Choose Device as a Source. Press Add and find your database. Next, click Ok. Once done Northwind database should be visible under Databases folder. Click New Query to start typing your queries.

Due to the limited space just 10 first rows of a table will be shown! Total number of rows will be mentioned under a table.


SELECT

Selects everything (all rows and columns) from Employees table.

SELECT

   *

FROM

   Employees;

EmployeeID LastName FirstName Title TitleOfCourtesy BirthDate HireDate Address City Region PostalCode Country HomePhone Extension Photo Notes ReportsTo PhotoPath
1 Davolio Nancy Sales Representative Ms. 1948-12-08 00:00:00.000 1992-05-01 00:00:00.000 507 - 20th Ave. E. Apt. 2A Seattle WA 98122 USA (206) 555-9857 5467 0x151… Education … 2 http://accweb/employ …
2 Fuller Andrew Vice President, Sales Dr. 1952-02-19 00:00:00.000 1992-08-14 00:00:00.000 908 W. Capital Way Tacoma WA 98401 USA (206) 555-9482 3457 0x151… Andrew … NULL http://accweb/employ …
3 Leverling Janet Sales Representative Ms. 1963-08-30 00:00:00.000 1992-04-01 00:00:00.000 722 Moss Bay Blvd. Kirkland WA 98033 USA (206) 555-3412 3355 0x151… Janet has … 2 http://accweb/employ …
4 Peacock Margaret Sales Representative Mrs. 1937-09-19 00:00:00.000 1993-05-03 00:00:00.000 4110 Old Redmond Rd. Redmond WA 98052 USA (206) 555-8122 5176 0x151… Margaret … 2 http://accweb/employ …
5 Buchanan Steven Sales Manager Mr. 1955-03-04 00:00:00.000 1993-10-17 00:00:00.000 14 Garrett Hill London NULL SW1 8JR UK (71) 555-4848 3453 0x151… Steven … 2 http://accweb/employ …
6 Suyama Michael Sales Representative Mr. 1963-07-02 00:00:00.000 1993-10-17 00:00:00.000 Coventry House Miner Rd. London NULL EC2 7JR UK (71) 555-7773 428 0x151… Michael … 5 http://accweb/employ …
7 King Robert Sales Representative Mr. 1960-05-29 00:00:00.000 1994-01-02 00:00:00.000 Edgeham Hollow Winchester Way London NULL RG1 9SP UK (71) 555-5598 465 0x151… Robert … 5 http://accweb/employ …
8 Callahan Laura Inside Sales Coordinator Ms. 1958-01-09 00:00:00.000 1994-03-05 00:00:00.000 4726 - 11th Ave. N.E. Seattle WA 98105 USA (206) 555-1189 2344 0x151… Laura … 2 http://accweb/employ …
9 Dodsworth Anne Sales Representative Ms. 1966-01-27 00:00:00.000 1994-11-15 00:00:00.000 7 Houndstooth Rd. London NULL WG2 7LT UK (71) 555-4444 452 0x151… Anne has … 5 http://accweb/employ …
9 rows in total

Limits selection to particular columns from a table.

SELECT

   EmployeeID,

   LastName,

   FirstName

FROM

   Employees;

EmployeeID LastName FirstName
1 Davolio Nancy
2 Fuller Andrew
3 Leverling Janet
4 Peacock Margaret
5 Buchanan Steven
6 Suyama Michael
7 King Robert
8 Callahan Laura
9 Dodsworth Anne
9 rows in total

No such column like '1'. The number appears as a result of the query. There are nine rows which corresponds to number of rows in Employees table.

SELECT

   1

FROM

   Employees;

(No column name)
1
1
1
1
1
1
1
1
1
9 rows in total

Column or table names containing spaces should be in brackets.

SELECT

   UnitPrice

FROM

   [Order Details];

UnitPrice
14.00
9.80
34.80
18.60
42.40
7.70
42.40
16.80
16.80
15.60
2155 rows in total

Aliases

Gives alias to a column.

SELECT

   LastName,

   FirstName,

   FirstName + ' ' + LastName As FullName

FROM

   Employees;

LastName FirstName FullName
Davolio Nancy Nancy Davolio
Fuller Andrew Andrew Fuller
Leverling Janet Janet Leverling
Peacock Margaret Margaret Peacock
Buchanan Steven Steven Buchanan
Suyama Michael Michael Suyama
King Robert Robert King
Callahan Laura Laura Callahan
Dodsworth Anne Anne Dodsworth
9 rows in total

Gives alias to a table.

SELECT

   UnitPrice

FROM

   [Order Details] As OrderDetails;

UnitPrice
14.00
9.80
34.80
18.60
42.40
7.70
42.40
16.80
16.80
15.60
2155 rows in total

LEFT

Returns 25 characters counting from the left.

SELECT

   LEFT(PhotoPath, 25)

FROM

   Employees;

(No column name)
http//accweb/emmployees/
http//accweb/emmployees/
http//accweb/emmployees/
http//accweb/emmployees/
http//accweb/emmployees/
http//accweb/emmployees/
http//accweb/emmployees/
http//accweb/emmployees/
http//accweb/emmployees/
9 rows in total

Adding DISTINCT to above example will reduce number of results to distinct (different) records.

SELECT

   DISTINCT(LEFT(PhotoPath, 25))

FROM

   Employees;

(No column name)
http//accweb/emmployees/
1 row in total

RIGHT

Returns 25 characters counting from the right.

SELECT

   RIGHT(HomePhone, 8)

FROM

   Employees;

(No column name)
555-9857
555-9482
555-3412
555-8122
555-4848
555-7773
555-5598
555-1189
555-4444
9 rows in total

SUBSTRING

Extracts a substring from a string (start at position 6, extract 9 characters).

SELECT

   SUBSTRING(HomePhone, 6, 9)

FROM

   Employees;

(No column name)
 555-9857
 555-9482
 555-3412
 555-8122
555-4848
555-7773
555-5598
 555-1189
555-4444
9 rows in total

LTRIM removes unwanted space(s) from the left, which appeared in the above example.

SELECT

   LTRIM(SUBSTRING(HomePhone, 6, 9))

FROM

   Employees;

(No column name)
555-9857
555-9482
555-3412
555-8122
555-4848
555-7773
555-5598
555-1189
555-4444
9 rows in total

LTRIM

Removes space(s) from the left.

SELECT

   LTRIM(' SQL')

FROM

   Employees;

(No column name)
SQL
SQL
SQL
SQL
SQL
SQL
SQL
SQL
SQL
9 rows in total

RTRIM

Removes space(s) from the right.

SELECT

   RTRIM('SQL ')

FROM

   Employees;

(No column name)
SQL
SQL
SQL
SQL
SQL
SQL
SQL
SQL
SQL
9 rows in total

CONCAT

Joins character strings.

SELECT

   LastName,

   FirstName,

   CONCAT(FirstName,' ',LastName)

FROM

   Employees;

LastName FirstName (No column name)
Davolio Nancy Nancy Davolio
Fuller Andrew Andrew Fuller
Leverling Janet Janet Leverling
Peacock Margaret Margaret Peacock
Buchanan Steven Steven Buchanan
Suyama Michael Michael Suyama
King Robert Robert King
Callahan Laura Laura Callahan
Dodsworth Anne Anne Dodsworth
9 rows in total

Another way of joining character strings.

SELECT

   LastName,

   FirstName,

   FirstName + ' ' + LastName

FROM

   Employees;

LastName FirstName (No column name)
Davolio Nancy Nancy Davolio
Fuller Andrew Andrew Fuller
Leverling Janet Janet Leverling
Peacock Margaret Margaret Peacock
Buchanan Steven Steven Buchanan
Suyama Michael Michael Suyama
King Robert Robert King
Callahan Laura Laura Callahan
Dodsworth Anne Anne Dodsworth
9 rows in total

Operators +, -, *, /

Calculations using arithemetic operators.

SELECT

   ProductID,

   UnitPrice, Quantity,

   Discount,

   (UnitPrice * (1 - Discount)) * Quantity AS 'Total Sale'

FROM

   [Order Details];

ProductID UnitPrice Quantity Discount Total Sale
11 14.00 12 0 168
42 9.80 10 0 98
72 34.80 5 0 174
14 18.60 9 0 167.4
51 42.40 40 0 1696
41 7.70 10 0 77
51 42.40 35 0.15 1261.4
65 16.80 15 0.15 214.2
22 16.80 6 0.05 95.75999
57 15.60 15 0.05 222.3
2155 rows in total

UPPER

Returns a character expression with lowercase character data converted to uppercase.

SELECT

   UPPER(LastName)

FROM

   Employees;

(No column name)
BUCHANAN
CALLAHAN
DAVOLIO
DODSWORTH
FULLER
KING
LEVERLING
PEACOCK
SUYAMA
9 rows in total

LOWER

Returns a character expression with uppercase character data converted to lowercase.

SELECT

   LOWER(LastName)

FROM

   Employees;

(No column name)
buchanan
callahan
davolio
dodsworth
fuller
king
leverling
peacock
suyama
9 rows in total

GETDATE

Returns current date and time.

SELECT

   GETDATE();

(No column name)
2018-01-17 21:51:48.227
1 row in total

CURRENT_TIMESTAMP

Returns current date and time.

SELECT

   CURRENT_TIMESTAMP;

(No column name)
2018-01-17 21:51:48.227
1 row in total

DATEPART

Returns year, month, week, day, etc. from the date. In the example refers to a month.

SELECT

   DATEPART(Month, OrderDate)

FROM

   Orders;

(No column name)
7
7
7
7
7
7
7
7
7
7
830 rows in total

Returns year from a date.

SELECT

   FirstName,

   LastName,

   DATEPART(year, BirthDate) As [Year of birth]

FROM

   Employees

ORDER BY

   [Year of birth] ASC;

FirstName LastName Year of birth
Margaret Peacock 1937
Nancy Davolio 1948
Andrew Fuller 1952
Steven Buchanan 1955
Laura Callahan 1958
Robert King 1960
Michael Suyama 1963
Janet Leverling 1963
Anne Dodsworth 1966
9 rows in total

In the example query returns age of an employee using GETDATE and DATEPART.

SELECT

   FirstName,

   LastName,

   DATEPART(year, GETDATE()) - DATEPART(year, BirthDate) As Age

FROM

   Employees

ORDER BY

   Age DESC;

FirstName LastName Age
Margaret Peacock 81
Nancy Davolio 70
Andrew Fuller 66
Steven Buchanan 63
Laura Callahan 60
Robert King 58
Michael Suyama 55
Janet Leverling 55
Anne Dodsworth 52
9 rows in total

DATEDIFF

Find the difference in years, months, weeks, days, etc. between two dates.

SELECT

   DATEDIFF(YEAR, HireDate, GETDATE())

FROM

   Employees;

(No column name)
26
26
26
25
25
25
24
24
24
9 rows in total

In the example query returns tenure of an employee using GETDATE and DATEDIFF for employee with tenure over 24 years.

SELECT

   FirstName,

   LastName,

   DATEDIFF(YEAR, HireDate, GETDATE()) As Tenure

FROM

   Employees

WHERE

   DATEDIFF(YEAR, HireDate, GETDATE()) > 24;

FirstName LastName Tenure
Nancy Davolio 26
Andrew Fuller 26
Janet Leverling 26
Margaret Peacock 25
Steven Buchanan 25
Michael Suyama 25
6 rows in total

CASE, WHEN, THEN, ELSE, END

Evaluates a list of conditions. Next, returns one of possible result expressions.

SELECT

   OrderID,

   Quantity,

   CASE

     WHEN

       Quantity < 5 THEN 'Small quantity'

     WHEN

       Quantity BETWEEN 5 AND 20 THEN 'Medium quantity'

     ELSE

       'Big quantity'

   END AS 'Quantity range'

FROM

   [Order Details];

OrderID Quantity Quantity range
10248 12 Medium quantity
10248 10 Medium quantity
10248 5 Medium quantity
10249 9 Medium quantity
10249 40 Big quantity
10250 10 Medium quantity
10250 35 Big quantity
10250 15 Medium quantity
10251 6 Medium quantity
10251 15 Medium quantity
2155 rows in total

ORDER BY

Sorts data in either ascending or descending order. In the example sorted in ascending order by last name.

! No need to type ASC. By default, data is be sorted in ascending order.

SELECT

   LastName,

   FirstName

FROM

   Employees

ORDER BY

   LastName ASC;

LastName FirstName
Buchanan Steven
Callahan Laura
Davolio Nancy
Dodsworth Anne
Fuller Andrew
King Robert
Leverling Janet
Peacock Margaret
Suyama Michael
9 rows in total

In the example data is sorted in descending order by employee's birth of date.

SELECT

   LastName,

   FirstName,

   BirthDate

FROM

   Employees

ORDER BY

   BirthDate DESC;

LastName FirstName BirthDate
Dodsworth Anne 1966-01-27 00:00:00.000
Leverling Janet 1963-08-30 00:00:00.000
Suyama Michael 1963-07-02 00:00:00.000
King Robert 1960-05-29 00:00:00.000
Callahan Laura 1958-01-09 00:00:00.000
Buchanan Steven 1955-03-04 00:00:00.000
Fuller Andrew 1952-02-19 00:00:00.000
Davolio Nancy 1948-12-08 00:00:00.000
Peacock Margaret 1937-09-19 00:00:00.000
9 rows in total

Data sorted by several columns.

SELECT

   LastName,

   FirstName,

   City

FROM

   Employees

ORDER BY

   City,

   LastName;

LastName FirstName City
Leverling Janet Kirkland
Buchanan Steven London
Dodsworth Anne London
King Robert London
Suyama Michael London
Peacock Margaret Redmond
Callahan Laura Seattle
Davolio Nancy Seattle
Fuller Andrew Tacoma
9 rows in total

Again, data sorted by several columns.

SELECT

   LastName,

   FirstName,

   City

FROM

   Employees

ORDER BY

   City ASC,

   LastName DESC;

LastName FirstName City
Leverling Janet Kirkland
Suyama Michael London
King Robert London
Dodsworth Anne London
Buchanan Steven London
Peacock Margaret Redmond
Davolio Nancy Seattle
Callahan Laura Seattle
Fuller Andrew Tacoma
9 rows in total

WHERE

The WHERE clause filters for rows that meet certain criteria.

In the example city equals 'London'.

SELECT

   CustomerID,

   CompanyName,

   ContactName,

City

FROM

   Customers

WHERE

   City = 'London';

CustomerID CompanyName ContactName City
AROUT Around the Horn Thomas Hardy London
BSBEV B's Beverages Victoria Ashworth London
CONSH Consolidated Holdings Elizabeth Brown London
EASTC Eastern Connection Ann Devon London
NORTS North/South Simon Crowther London
SEVES Seven Seas Imports Hari Kumar London
6 rows in total

In the example quantity is greater than 110.

SELECT

   *

FROM

   [Order Details]

WHERE

   Quantity >110;

OrderID ProductID UnitPrice Quantity Discount
10398 55 19.20 120 0.1
10451 55 19.20 120 0.1
10515 27 43.90 120 0
10595 61 28.50 120 0.25
10678 41 9.65 120 0
10711 53 32.80 120 0
10764 39 18.00 130 0.1
10776 51 53.00 120 0.05
10894 75 7.75 120 0.05
11072 64 33.25 130 0
10 rows in total

In the example quantity is greater or equal to 120.

SELECT

   *

FROM

   [Order Details]

WHERE

   Quantity >=120;

OrderID ProductID UnitPrice Quantity Discount
10398 55 19.20 120 0.1
10451 55 19.20 120 0.1
10515 27 43.90 120 0
10595 61 28.50 120 0.25
10678 41 9.65 120 0
10711 53 32.80 120 0
10764 39 18.00 130 0.1
10776 51 53.00 120 0.05
10894 75 7.75 120 0.05
11072 64 33.25 130 0
10 rows in total

In the example quantity is smaller or equal to 10.

SELECT

   *

FROM

   [Order Details]

WHERE

   Quantity <=10;

OrderID ProductID UnitPrice Quantity Discount
10248 42 9.80 10 0
10248 72 34.80 5 0
10249 14 18.60 9 0
10250 41 7.70 10 0
10251 22 16.80 6 0.05
10257 39 14.40 6 0
10258 32 25.60 6 0.2
10259 21 8.00 10 0
10259 37 20.80 1 0
10262 56 30.40 2 0
608 rows in total

In the example Title is different than 'Sales Representative'.

SELECT

   EmployeeID,

   LastName,

   FirstName,

   Title

FROM

   Employees

WHERE

   Title <> 'Sales Representative';

EmployeeID LastName FirstName Title
2 Fuller Andrew Vice President, Sales
5 Buchanan Steven Sales Manager
8 Callahan Laura Inside Sales Coordinator
3 rows in total

BETWEEN

The BETWEEN operator selects values within a given range.

In the example Unit Price value is between 10 and 15.

! BETWEEN is inclusive.

SELECT

   *

FROM

   [Order Details]

WHERE

   UnitPrice BETWEEN 10 AND 15;

OrderID ProductID UnitPrice Quantity Discount
10248 11 14.00 12 0
10253 31 10.00 20 0
10253 39 14.40 42 0
10255 16 13.90 35 0
10256 77 10.40 12 0
10257 39 14.40 6 0
10257 77 10.40 15 0
10260 70 12.00 21 0.25
10261 35 14.40 20 0
10263 16 13.90 60 0.25
412 rows in total

SELECT TOP

Limits selection to a specified number of records.

In the example top 10 rows are returned.

SELECT TOP 10

   UnitPrice

FROM

   [Order Details];

UnitPrice
14.00
9.80
34.80
18.60
42.40
7.70
42.40
16.80
16.80
15.60
10 rows in total

In the example top 5% of rows is returned.

SELECT TOP 5 PERCENT

   UnitPrice

FROM

   [Order Details];

UnitPrice
14.00
9.80
34.80
18.60
42.40
7.70
42.40
16.80
16.80
15.60
108 rows in total

In the example top 10 Unit Price rows are returned and sorted by Unit Price.

SELECT TOP 10

   UnitPrice,

   Quantity

FROM

   [Order Details]

ORDER BY

   UnitPrice ASC;

UnitPrice Quantity
2.00 25
2.00 60
2.00 24
2.00 20
2.00 8
2.00 60
2.00 49
2.00 50
2.00 20
2.00 20
10 rows in total

AND, OR, NOT

Comparisons with NULL and the three-valued logic (3VL). SQL query can result in True, False or Unknown (NULL). The following table presents possible combinations:

p q p OR q p AND q p = q
True True True True True
True False True False False
True Unknown True Unknown Unknown
False True True False False
False False False False True
False Unknown Unknown False Unknown
Unknown True True Unknown Unknown
Unknown False Unknown False Unknown
Unknown Unknown Unknown Unknown Unknown
p NOT p
True False
False True
Unknown Unknown

In the example companies starting with the letter "L" and located in France are returned.

For more information about the "Like" operator scroll down.

SELECT

   *

FROM

   Customers

WHERE

   CompanyName LIKE 'L%'

   AND Country = 'France';

CustomerID CompanyName ContactName ContactTitle Address City Region PostalCode Country Phone Fax
LACOR La corne d'abondance Daniel Tonini Sales Representative 67, avenue de l'Europe Versailles NULL 78000 France 30.59.84.10 30.59.85.11
31000 LAMAI La maison d'Asie Annette Roulet Sales Manager 1 rue Alsace-Lorraine Toulouse NULL France 61.77.61.10 61.77.61.11
2 rows in total

In the example companies starting with the letter "L" or located in Poland are returned.

SELECT

   *

FROM

   Customers

WHERE

   CompanyName LIKE 'L%'

   OR Country = 'Poland';

CustomerID CompanyName ContactName ContactTitle Address City Region PostalCode Country Phone Fax
LACOR La corne d'abondance Daniel Tonini Sales Representative 67, avenue de l'Europe Versailles NULL 78000 France 30.59.84.10 30.59.85.11
LAMAI La maison d'Asie Annette Roulet Sales Manager 1 rue Alsace-Lorraine Toulouse NULL 31000 France 61.77.61.10 61.77.61.11
LAUGB Laughing Bacchus Wine Cellars Yoshi Tannamuri Marketing Assistant 1900 Oak St. Vancouver BC V3F 2K1 Canada (604) 555-3392 (604) 555-7293
LAZYK Lazy K Kountry Store John Steel Marketing Manager 12 Orchestra Terrace Walla Walla WA 99362 USA (509) 555-7969 (509) 555-6221
LEHMS Lehmanns Marktstand Renate Messner Sales Representative Magazinweg 7 Frankfurt a.M. NULL 60528 Germany 069-0245984 069-0245874
LETSS Let's Stop N Shop Jaime Yorres Owner 87 Polk St. Suite 5 San Francisco CA 94117 USA (415) 555-5938 NULL
LILAS LILA-Supermercado Carlos González Accounting Manager Carrera 52 con Ave. Bolívar #65-98 Llano Largo Barquisimeto Lara 3508 Venezuela (9) 331-6954 (9) 331-7256
LINOD LINO-Delicateses Felipe Izquierdo Owner Ave. 5 de Mayo Porlamar I. de Margarita Nueva Esparta 4980 Venezuela (8) 34-56-12 (8) 34-93-93
LONEP Lonesome Pine Restaurant Fran Wilson Sales Manager 89 Chiaroscuro Rd. Portland OR 97219 USA (503) 555-9573 (503) 555-9646
WOLZA Wolski Zajazd Zbyszek Piestrzeniewicz Owner ul. Filtrowa 68 Warszawa NULL 01-012 Poland (26) 642-7012 (26) 642-7012
10 rows in total

In the example companies starting with the letter "L" and located in France or Germany are returned.

SELECT

   *

FROM

   Customers

WHERE

   CompanyName LIKE 'L%'

   AND (Country = 'France' OR Country = 'Germany');

CustomerID CompanyName ContactName ContactTitle Address City Region PostalCode Country Phone Fax
LACOR La corne d'abondance Daniel Tonini Sales Representative 67, avenue de l'Europe Versailles NULL 78000 France 30.59.84.10 30.59.85.11
LAMAI La maison d'Asie Annette Roulet Sales Manager 1 rue Alsace-Lorraine Toulouse NULL 31000 France 61.77.61.10 61.77.61.11
LEHMS Lehmanns Marktstand Renate Messner Sales Representative Magazinweg 7 Frankfurt a.M. NULL 60528 Germany 069-0245984 069-0245874
3 rows in total

In the example companies starting with the letter "L" and not located in France are returned.

SELECT

   *

FROM

   Customers

WHERE

   CompanyName LIKE 'L%'

   AND NOT Country = 'France';

CustomerID CompanyName ContactName ContactTitle Address City Region PostalCode Country Phone Fax
LAUGB Laughing Bacchus Wine Cellars Yoshi Tannamuri Marketing Assistant 1900 Oak St. Vancouver BC V3F 2K1 Canada (604) 555-3392 (604) 555-7293
LAZYK Lazy K Kountry Store John Steel Marketing Manager 12 Orchestra Terrace Walla Walla WA 99362 USA (509) 555-7969 (509) 555-6221
LEHMS Lehmanns Marktstand Renate Messner Sales Representative Magazinweg 7 Frankfurt a.M. NULL 60528 Germany 069-0245984 069-0245874
LETSS Let's Stop N Shop Jaime Yorres Owner 87 Polk St. Suite 5 San Francisco CA 94117 USA (415) 555-5938 NULL
LILAS LILA-Supermercado Carlos González Accounting Manager Carrera 52 con Ave. Bolívar #65-98 Llano Largo Barquisimeto Lara 3508 Venezuela (9) 331-6954 (9) 331-7256
LINOD LINO-Delicateses Felipe Izquierdo Owner Ave. 5 de Mayo Porlamar I. de Margarita Nueva Esparta 4980 Venezuela (8) 34-56-12 (8) 34-93-93
LONEP Lonesome Pine Restaurant Fran Wilson Sales Manager 89 Chiaroscuro Rd. Portland OR 97219 USA (503) 555-9573 (503) 555-9646
7 rows in total

IN

IN operator in the WHERE clause is useful to filter data by a list of values. It is similar to multiple OR conditions.

In the example companies operating in Argentina or Mexico are returned.

SELECT

   CustomerID,

   CompanyName,

   Country

FROM

   Customers

WHERE

   Country IN ('Argentina', 'Mexico');

CustomerID CompanyName Country
ANATR Ana Trujillo Emparedados y helados Mexico
ANTON Antonio Moreno Taquería Mexico
CACTU Cactus Comidas para llevar Argentina
CENTC Centro comercial Moctezuma Mexico
OCEAN Océano Atlántico Ltda. Argentina
PERIC Pericles Comidas clásicas Mexico
RANCH Rancho grande Argentina
TORTU Tortuga Restaurante Mexico
3 rows in total

In the example companies operating in countries other than Argentina or Mexico are returned.

SELECT

   CustomerID,

   CompanyName,

   Country

FROM

   Customers

WHERE

   Country NOT IN ('Argentina', 'Mexico');

CustomerID CompanyName Country
ALFKI Alfreds Futterkiste Germany
AROUT Around the Horn UK
BERGS Berglunds snabbköp Sweden
BLAUS Blauer See Delikatessen Germany
BLONP Blondesddsl père et fils France
BOLID Bólido Comidas preparadas Spain
BONAP Bon app' France
BOTTM Bottom-Dollar Markets Canada
BSBEV B's Beverages UK
CHOPS Chop-suey Chinese Switzerland
83 rows in total

The following SQL statement selects all customers that are from the same countries as the suppliers.

SELECT

   CustomerID,

   CompanyName,

   ContactName,

   City

FROM

   Customers

WHERE

   City IN (SELECT City FROM Suppliers);

CustomerID CompanyName ContactName City
ALFKI Alfreds Futterkiste Maria Anders Berlin
AROUT Around the Horn Thomas Hardy London
BSBEV B's Beverages Victoria Ashworth London
COMMI Comércio Mineiro Pedro Afonso Sao Paulo
CONSH Consolidated Holdings Elizabeth Brown London
EASTC Eastern Connection Ann Devon London
FAMIA Familia Arquibaldo Aria Cruz Sao Paulo
MEREP Mère Paillarde Jean Fresnière Montréal
NORTS North/South Simon Crowther London
PARIS Paris spécialités Marie Bertrand Paris
14 rows in total

The following SQL statement selects all Suppliers that are from the same countries as the customers.

SELECT

   SupplierID,

   CompanyName,

   ContactName,

   City

FROM

   Suppliers

WHERE

   City IN (SELECT City FROM Customers);

SupplierID CompanyName ContactName City
1 Exotic Liquids Charlotte Cooper London
10 Refrescos Americanas LTDA Carlos Diaz Sao Paulo
11 Heli Süßwaren GmbH & Co. KG Petra Winkler Berlin
18 Aux joyeux ecclésiastiques Guylène Nodier Paris
25 Ma Maison Jean-Guy Lauzon Montréal
5 rows in total

LIKE

LIKE operator determines if a character string matches a pattern. Wildcard characters are % (percent) and _ (underscore).

A % matches any string with zero or more characters. While an _ matches any single character.

In the example companies starting with the letter "S" are returned.

! It doesn't matter whether the letter is in upper or lower case.

SELECT

   *

FROM

   Customers

WHERE

   Country Like 's%';

CustomerID CompanyName ContactName ContactTitle Address City Region PostalCode Country Phone Fax
BERGS Berglunds snabbköp Christina Berglund Order Administrator Berguvsvägen 8 Luleå NULL S-958 22 Sweden 0921-12 34 65 0921-12 34 67
BOLID Bólido Comidas preparadas Martín Sommer Owner C/ Araquil, 67 Madrid NULL 28023 Spain (91) 555 22 82 (91) 555 91 99
CHOPS Chop-suey Chinese Yang Wang Owner Hauptstr. 29 Bern NULL 3012 Switzerland 0452-076545 NULL
FISSA FISSA Fabrica Inter. Salchichas S.A. Diego Roel Accounting Manager C/ Moralzarzal, 86 Madrid NULL 28034 Spain (91) 555 94 44 (91) 555 55 93
FOLKO Folk och fä HB Maria Larsson Owner Åkergatan 24 Bräcke NULL S-844 67 Sweden 0695-34 67 21 NULL
GALED Galería del gastrónomo Eduardo Saavedra Marketing Manager Rambla de Cataluña, 23 Barcelona NULL 08022 Spain (93) 203 4560 (93) 203 4561
GODOS Godos Cocina Típica José Pedro Freyre Sales Manager C/ Romero, 33 Sevilla NULL 41101 Spain (95) 555 82 82 NULL
RICSU Richter Supermarkt Michael Holz Sales Manager Grenzacherweg 237 Genève NULL 1203 Switzerland 0897-034214 NULL
ROMEY Romero y tomillo Alejandra Camino Accounting Manager Gran Vía, 1 Madrid NULL 28001 Spain (91) 745 6200 (91) 745 6210
9 rows in total

Companies which names end with "ssen".

SELECT

   CompanyName

FROM

   Customers

WHERE

   CompanyName Like '%ssen';

CompanyName
Blauer See Delikatessen
Drachenblut Delikatessen
Königlich Essen
Old World Delicatessen
4 rows in total

Customers which name consists of 8 signs (8 x underscore). Including space(s) and signs such as apostrophe.

SELECT

   CompanyName

FROM

   Customers

WHERE

   CompanyName Like '________';

CompanyName
Bon app'
1 row in total

Customers where second letter in the name is "a".

SELECT

   CompanyName

FROM

   Customers

WHERE

   CompanyName Like '_a%';

CompanyName
Cactus Comidas para llevar
Eastern Connection
Familia Arquibaldo
Galería del gastrónomo
Hanari Carnes
La corne d'abondance
La maison d'Asie
Laughing Bacchus Wine Cellars
Lazy K Kountry Store
Magazzini Alimentari Riuniti
18 rows in total

Customers where first letter in the name is "c" and the last one is "r".

SELECT

   CompanyName

FROM

   Customers

WHERE

   CompanyName Like 'c%r';

CompanyName
Cactus Comidas para llevar
1 row in total

Null Values

A field that contains a NULL value has no value. NULL value is different from a zero value or a field that contains spaces. A field with a NULL value has been left blank when a record has been created.

It is not possible to compare NULL values against operators, such as =, <, or <>. Instead, WHERE IS NULL or WHERE IS NOT NULL are used as shown in examples below.

SELECT

   FirstName,

   LastName,

   Title,

   Region

FROM

   Employees

WHERE

   Region IS NULL;

FirstName LastName Title Region
Steven Buchanan Sales Manager NULL
Michael Suyama Sales Representative NULL
Robert King Sales Representative NULL
Anne Dodsworth Sales Representative NULL
4 rows in total

No results are shown since WHERE IS NULL clause has to be used to return NULL records.

SELECT

   FirstName,

   LastName,

   Title,

   Region

FROM

   Employees

WHERE

   Region = 'NULL';

FirstName LastName Title Region
0 rows in total

Similarly, no results are shown since WHERE IS NULL clause has to be used to return NULL records.

SELECT

   FirstName,

   LastName,

   Title,

   Region

FROM

   Employees

WHERE

   Region = '';

FirstName LastName Title Region
0 rows in total

Error, no records returned.

SELECT

   FirstName,

   LastName,

   Title,

   Region

FROM

   Employees

WHERE

   Region = 0;

Error returned Msg 245, Level 16, State 1, Line 1

Conversion failed when converting the nvarchar value 'WA' to data type int.

rows in total

Returns records where region is not NULL.

SELECT

   FirstName,

   LastName,

   Title,

   Region

FROM

   Employees

WHERE

   Region IS NOT NULL;

FirstName LastName Title Region
Nancy Davolio Sales Representative WA
Andrew Fuller Vice President, Sales WA
Janet Leverling Sales Representative WA
Margaret Peacock Sales Representative WA
Laura Callahan Inside Sales Coordinator WA
5 rows in total

In the example region's NULL values are replaced with "Unknown".

SELECT

   FirstName,

   LastName,

   Title,

   ISNULL(Region, 'Unknown')

FROM

   Employees

WHERE

   Region IS NULL;

FirstName LastName Title (No column name)
Steven Buchanan Sales Manager Unknown
Michael Suyama Sales Representative Unknown
Robert King Sales Representative Unknown
Anne Dodsworth Sales Representative Unknown
4 rows in total

SELECT DISTINCT

DISTINCT statement returns only distinct (unique) values.

In the example distinct countries are returned.

SELECT

   DISTINCT Country

FROM

   Customers;

Country
Argentina
Austria
Belgium
Brazil
Canada
Denmark
Finland
France
Germany
Ireland
21 rows in total

In the example distinct customer IDs are returned from the Orders table.

SELECT

   DISTINCT CustomerID

FROM

   Orders;

CustomerID
ALFKI
ANATR
ANTON
AROUT
BERGS
BLAUS
BLONP
BOLID
BONAP
BOTTM
89 rows in total

COUNT, AVG, SUM

COUNT function returns a count of the number of data values. SUM function returns the sum of the data values. AVG function returns the average of the data values.

The example shows number of customer IDs from Customers table.

SELECT

   COUNT(CustomerID)

FROM

   Customers;

(No column name)
91
1 row in total

The example shows number of countries from Customers table.

! The query doesn't remove duplicates. Countries are counted multiple times.

SELECT

   COUNT(Country)

FROM

   Customers;

(No column name)
91
1 row in total

The example shows number of countries from Customers table.

! Duplicates are removed thanks to DISTINCT statement. Query counts unique countries.

SELECT

   COUNT(DISTINCT Country)

FROM

   Customers;

(No column name)
21
1 row in total

Average value of discount granted on condition that any discount was granted.

! There is no limit in terms of decimal places.

SELECT

   AVG(Discount)

FROM

   [Order Details]

WHERE

   Discount > 0;

(No column name)
0,144439142966434
1 row in total

Average value of discount granted on condition that any discount was granted.

Limited to two decimal places.

SELECT

   ROUND(AVG(Discount),2)

FROM

   [Order Details]

WHERE

   Discount > 0;

(No column name)
0,14
1 row in total

Ordered quantity in total.

SELECT

   SUM(Quantity)

FROM

   [Order Details];

(No column name)
51317
1 row in total

MIN, MAX

Min and Max functions show the smallest value and the largest value respectively.

In the example the product with the highest price.

SELECT

   MAX(UnitPrice)

FROM

   Products;

(No column name)
263.50
1 row in total

In the example the product with the lowest price.

SELECT

   MIN(UnitPrice)

FROM

   Products;

(No column name)
2.50
1 row in total

GROUP BY

GROUP BY is used to collect data across multiple records and group the results by one or more columns. The clause typically also involves aggregates, such as COUNT, MAX, SUM, AVG, etc.

In the example grouping by country.

SELECT

   Country

FROM

   Customers

GROUP BY

   Country;

Country
Argentina
Austria
Belgium
Brazil
Canada
Denmark
Finland
France
Germany
Ireland
21 rows in total

In the example grouping by title and aggregating (counting) by employee ID.

SELECT

   COUNT(EmployeeID),

   Title

FROM

   Employees

GROUP BY

   Title

ORDER BY

   COUNT(EmployeeID) DESC;

(No column name) Title
6 Sales Representative
1 Vice President, Sales
1 Inside Sales Coordinator
1 Sales Manager
4 rows in total

HAVING

HAVING clause filters records that work on summarized GROUP BY results.

In the example Having clause is used as a filter of grouped results.

SELECT

   Country AS 'Customer''s country',

   COUNT(CustomerID) AS 'Number of customers'

FROM

   Customers

GROUP BY

   Country

HAVING

   COUNT(CustomerID) > 5

ORDER BY

   COUNT(CustomerID) DESC;

Customer's country Number of customers
USA 13
France 11
Germany 11
Brazil 9
UK 7
5 rows in total

SQL Joins

A SQL JOIN combines records from two or more tables. Possible joins in the picture below.

SQL joins

INNER JOIN

Returns rows when the joining key between both tables matches.

SELECT

   EmployeeID,

   Territories.TerritoryDescription

FROM

   EmployeeTerritories

INNER JOIN Territories

   ON EmployeeTerritories.TerritoryID = Territories.TerritoryID;

EmployeeID TerritoryDescription
1 Wilton
1 Neward
2 Westboro
2 Bedford
2 Georgetow
2 Boston
2 Cambridge
2 Braintree
2 Louisville
3 Atlanta
49 rows in total

LEFT JOIN

The LEFT JOIN keyword returns all records from the left table (table A), and the matched records from the right table (table B). If there is no match, the result is NULL from the right side.

! If we use RIGHT JOIN and switch the order of tables in query the same results will be obtained which will be shown in the next example.

! Similar results were obtained in above INNER JOIN query which means there are records in table A (EmployeeTerritories) which don't match table B (Territories).

SELECT

   EmployeeID,

   Territories.TerritoryDescription

FROM

   EmployeeTerritories

LEFT JOIN Territories

   ON EmployeeTerritories.TerritoryID = Territories.TerritoryID;

ORDER BY

   EmployeeID,

   Territories.TerritoryDescription;

EmployeeID TerritoryDescription
1 Wilton
1 Neward
2 Westboro
2 Bedford
2 Georgetow
2 Boston
2 Cambridge
2 Braintree
2 Louisville
3 Atlanta
49 rows in total

Using RIGHT JOIN to obtain the same result as above. Order of tables in query was altered.

SELECT

   EmployeeID,

   Territories.TerritoryDescription

FROM

   Territories

RIGHT JOIN EmployeeTerritories

   ON Territories.TerritoryID = EmployeeTerritories.TerritoryID;

ORDER BY

   EmployeeID,

   Territories.TerritoryDescription;

EmployeeID TerritoryDescription
1 Wilton
1 Neward
2 Westboro
2 Bedford
2 Georgetow
2 Boston
2 Cambridge
2 Braintree
2 Louisville
3 Atlanta
49 rows in total

Three-table left join example.

SELECT

   EmployeeTerritories.EmployeeID,

   Employees.FirstName + ' ' + Employees.LastName AS FullName,

   Territories.TerritoryDescription

FROM

   Employees

LEFT JOIN EmployeeTerritories

   ON Employees.EmployeeID = EmployeeTerritories.EmployeeID

LEFT JOIN Territories

   ON EmployeeTerritories.TerritoryID = Territories.TerritoryID;

EmployeeID FullName TerritoryDescription
1 Nancy Davolio Wilton
1 Nancy Davolio Neward
2 Andrew Fuller Westboro
2 Andrew Fuller Bedford
2 Andrew Fuller Georgetow
2 Andrew Fuller Boston
2 Andrew Fuller Cambridge
2 Andrew Fuller Braintree
2 Andrew Fuller Louisville
3 Janet Leverling Atlanta
49 rows in total

RIGHT JOIN

The RIGHT JOIN keyword returns all records from the right table (table B), and the matched records from the left table (table A). If there is no match, the result is NULL from the left side.

! If we use LEFT JOIN and switch the order of tables in query the same results will be obtained which will be shown in the next example.

! There are 4 more records than in case of similar LEFT JOIN example. This is because in 4 cases there are no matches which results in NULL values.

SELECT

   EmployeeID,

   Territories.TerritoryDescription

FROM

   EmployeeTerritories

RIGHT JOIN Territories

   ON EmployeeTerritories.TerritoryID = Territories.TerritoryID

ORDER BY

   EmployeeID,

   Territories.TerritoryDescription;

EmployeeID TerritoryDescription
NULL Austin
NULL Bentonville
NULL Columbia
NULL Dallas
1 Neward
1 Wilton
2 Bedford
2 Boston
2 Braintree
2 Cambridge
53 rows in total

Using RIGHT JOIN to obtain the same result as above. Order of tables in query was altered.

SELECT

   EmployeeID,

   Territories.TerritoryDescription

FROM

   Territories

LEFT JOIN EmployeeTerritories

   ON Territories.TerritoryID = EmployeeTerritories.TerritoryID

ORDER BY

   EmployeeID,

   Territories.TerritoryDescription;

EmployeeID TerritoryDescription
NULL Austin
NULL Bentonville
NULL Columbia
NULL Dallas
1 Neward
1 Wilton
2 Bedford
2 Boston
2 Braintree
2 Cambridge
53 rows in total

Three-table right join example.

SELECT

   EmployeeTerritories.EmployeeID,

   Employees.FirstName + ' ' + Employees.LastName,

   Territories.TerritoryDescription

FROM

   Employees

RIGHT JOIN EmployeeTerritories

   ON Employees.EmployeeID = EmployeeTerritories.EmployeeID

RIGHT JOIN Territories

   ON EmployeeTerritories.TerritoryID = Territories.TerritoryID;

EmployeeID (No column name) TerritoryDescription
2 Andrew Fuller Westboro
2 Andrew Fuller Bedford
2 Andrew Fuller Georgetow
2 Andrew Fuller Boston
2 Andrew Fuller Cambridge
2 Andrew Fuller Braintree
5 Steven Buchanan Providence
9 Anne Dodsworth Hollis
9 Anne Dodsworth Portsmouth
1 Nancy Davolio Wilton
53 rows in total

SELF JOIN

A self join is used to join a table to itself. It can be useful to compare rows within the same table.

In the example we will query employees and their managers. Null means that the eployee is on the top of the structure and has no boss.

SELECT

   e.FirstName + ' ' + e.LastName As Employee,

   m.FirstName + ' ' + m.LastName As Manager

FROM

   Employees e

LEFT JOIN Employees m

   ON m.EmployeeID = e.ReportsTo

ORDER BY

   Manager;

Employee Manager
Andrew Fuller NULL
Janet Leverling Andrew Fuller
Margaret Peacock Andrew Fuller
Steven Buchanan Andrew Fuller
Laura Callahan Andrew Fuller
Nancy Davolio Andrew Fuller
Anne Dodsworth Steven Buchanan
Michael Suyama Steven Buchanan
Robert King Steven Buchanan
9 rows in total

In the example we are looking for customers from the same city, Buenos Aires.

SELECT

   c1.City,

   c1.CompanyName As Customer_1,

   c2.CompanyName As Customer_2

FROM

   Customers c1

INNER JOIN Customers c2

   ON c1.CustomerID > c2.CustomerID

   AND c1.City = c2.City

WHERE

   c1.City = 'Buenos Aires';

City Customer_1 Customer_2
Buenos Aires Océano Atlántico Ltda. Cactus Comidas para llevar
Buenos Aires Rancho grande Cactus Comidas para llevar
Buenos Aires Rancho grande Océano Atlántico Ltda.
3 rows in total

FULL JOIN

Returns all records from both Table A and Table B regardless there are matches or not.

SELECT

   EmployeeID,

   Territories.TerritoryDescription

FROM

   EmployeeTerritories

FULL JOIN Territories

   ON EmployeeTerritories.TerritoryID = Territories.TerritoryID

ORDER BY

   EmployeeID,

   Territories.TerritoryDescription;

EmployeeID (No column name) TerritoryDescription
2 Andrew Fuller Westboro
2 Andrew Fuller Bedford
2 Andrew Fuller Georgetow
2 Andrew Fuller Boston
2 Andrew Fuller Cambridge
2 Andrew Fuller Braintree
5 Steven Buchanan Providence
9 Anne Dodsworth Hollis
9 Anne Dodsworth Portsmouth
1 Nancy Davolio Wilton
53 rows in total

Example shows combination of full and left table joins.

SELECT

   EmployeeTerritories.EmployeeID,

   Employees.FirstName + ' ' + Employees.LastName,

   Territories.TerritoryDescription

FROM

   Employees

FULL JOIN EmployeeTerritories

   ON Employees.EmployeeID = EmployeeTerritories.EmployeeID

LEFT JOIN Territories

   ON EmployeeTerritories.TerritoryID = Territories.TerritoryID;

EmployeeID (No column name) TerritoryDescription
1 Nancy Davolio Wilton
1 Nancy Davolio Neward
2 Andrew Fuller Westboro
2 Andrew Fuller Bedford
2 Andrew Fuller Georgetow
2 Andrew Fuller Boston
2 Andrew Fuller Cambridge
2 Andrew Fuller Braintree
2 Andrew Fuller Louisville
3 Janet Leverling Atlanta
53 rows in total

UNION

The UNION operator combines the results of two or more SELECT statements. In order to use UNION columns in tables have to match. Duplicated pairs are removed.

SELECT

   City,

   Country

FROM

   Suppliers

UNION

   SELECT

     City,

     Country

   FROM

     Employees;

City Country
Ann Arbor USA
Annecy France
Bend USA
Berlin Germany
Boston USA
Cuxhaven Germany
Frankfurt Germany
Göteborg Sweden
Kirkland USA
Lappeenranta Finland
33 rows in total

The UNION ALL operator combines the results of two or more SELECT statements, however duplicated pairs are not removed. London, UK appears multiple times

SELECT

   City,

   Country

FROM

   Suppliers

UNION ALL

   SELECT

     City,

     Country

   FROM

     Employees;

City Country
London UK
New Orleans USA
Ann Arbor USA
Tokyo Japan
Oviedo Spain
Osaka Japan
Melbourne Australia
Manchester UK
Göteborg Sweden
38 rows in total

INSERT INTO

Inserts a new record into a table.

INSERT INTO Employees (

   LastName,

   FirstName,

   Title)

VALUES

   ('Janus',

   'Piotr',

   'Sales Representative');

LastName FirstName Title
Davolio Nancy Sales Representative
Fuller Andrew Vice President, Sales
Leverling Janet Sales Representative
Peacock Margaret Sales Representative
Buchanan Steven Sales Manager
Suyama Michael Sales Representative
King Robert Sales Representative
Callahan Laura Inside Sales Coordinator
Dodsworth Anne Sales Representative
Janus Piotr Sales Representative
(1 row affected)

UPDATE

Updates a record in a table.

UPDATE

   Employees

SET

   FirstName = 'Peter',

   LastName = 'Janusz'

WHERE

   EmployeeID = 10;

LastName FirstName Title
Davolio Nancy Sales Representative
Fuller Andrew Vice President, Sales
Leverling Janet Sales Representative
Peacock Margaret Sales Representative
Buchanan Steven Sales Manager
Suyama Michael Sales Representative
King Robert Sales Representative
Callahan Laura Inside Sales Coordinator
Dodsworth Anne Sales Representative
Janusz Piotr Sales Representative
(1 row affected)

DELETE

Deletes a record from a table.

DELETE FROM

   Employees

WHERE

   EmployeeID = 10;

LastName FirstName Title
Davolio Nancy Sales Representative
Fuller Andrew Vice President, Sales
Leverling Janet Sales Representative
Peacock Margaret Sales Representative
Buchanan Steven Sales Manager
Suyama Michael Sales Representative
King Robert Sales Representative
Callahan Laura Inside Sales Coordinator
Dodsworth Anne Sales Representative
(1 row affected)

VIEW

A view is a virtual table that is based on the result-set of an SQL statement. It is stored in the database catalog that allows you to refer to it later.

The example shows creation of a view that includes top 10 products by the ordered quantity.

CREATE VIEW Qty_per_Comp_Prod_vw

AS

   SELECT TOP 10

     CompanyName,

     ProductName,

     SUM(Quantity) As TotalQuantity

   FROM

     Suppliers s

   LEFT JOIN Products p

     ON s.SupplierID = p.SupplierID

   LEFT JOIN [Order Details] od

     ON p.ProductID = od.ProductID

   GROUP BY

     CompanyName,

     ProductName

   ORDER BY

     TotalQuantity DESC;

It is possible to query against a view like against a regular table.

SELECT

   *

FROM

   Qty_per_Comp_Prod_vw

WHERE

   TotalQuantity > 1200;

CompanyName ProductName TotalQuantity
Gai pâturage Camembert Pierrot 1577
Gai pâturage Raclette Courdavault 1496
Formaggi Fortini s.r.l. Gorgonzola Telino 1397
Pasta Buttini s.r.l. Gnocchi di nonna Alice 1263
4 rows in total

SUBQUERIES SELECT, FROM, WHERE

Subqueries are nested queries that provide data to the enclosing query. A subquery is often called inner query while the statement containing the subquery - outer query. Let's go through a number of examples of subqueries after SELECT, FROM, WHERE statements.

Example of a subquery after SELECT that returns average freight values for all the orders.

SELECT

   *,

   ROUND(

     (SELECT

       AVG(Freight)

     FROM

       Orders)

   ,2) AS [Avg Freight]

FROM

   Orders;

OrderID CustomerID EmployeeID OrderDate RequiredDate ShippedDate ShipVia Freight ShipName ShipAddress ShipCity ShipRegion ShipPostalCode ShipCountry Avg Freight
10248 VINET 5 1996-07-04 00:00:00.000 1996-08-01 00:00:00.000 1996-07-16 00:00:00.000 3 32.38 Vins et alcools Chevalier 59 rue de l'Abbaye Reims NULL 51100 France 78.24
10249 TOMSP 6 1996-07-05 00:00:00.000 1996-08-16 00:00:00.000 1996-07-10 00:00:00.000 1 11.61 Toms Spezialitäten Luisenstr. 48 Münster NULL 44087 Germany 78.24
10250 HANAR 4 1996-07-08 00:00:00.000 1996-08-05 00:00:00.000 1996-07-12 00:00:00.000 2 65.83 Hanari Carnes Rua do Paço, 67 Rio de Janeiro RJ 05454-876 Brazil 78.24
10251 VICTE 3 1996-07-08 00:00:00.000 1996-08-05 00:00:00.000 1996-07-15 00:00:00.000 1 41.34 Victuailles en stock 2, rue du Commerce Lyon NULL 69004 France 78.24
10252 SUPRD 4 1996-07-09 00:00:00.000 1996-08-06 00:00:00.000 1996-07-11 00:00:00.000 2 51.3 Suprêmes délices Boulevard Tirou, 255 Charleroi NULL B-6000 Belgium 78.24
10253 HANAR 3 1996-07-10 00:00:00.000 1996-07-24 00:00:00.000 1996-07-16 00:00:00.000 2 58.17 Hanari Carnes Rua do Paço, 67 Rio de Janeiro RJ 05454-876 Brazil 78.24
10254 CHOPS 5 1996-07-11 00:00:00.000 1996-08-08 00:00:00.000 1996-07-23 00:00:00.000 2 22.98 Chop-suey Chinese Hauptstr. 31 Bern NULL 3012 Switzerland 78.24
10255 RICSU 9 1996-07-12 00:00:00.000 1996-08-09 00:00:00.000 1996-07-15 00:00:00.000 3 148.33 Richter Supermarkt Starenweg 5 Genève NULL 1204 Switzerland 78.24
10256 WELLI 3 1996-07-15 00:00:00.000 1996-08-12 00:00:00.000 1996-07-17 00:00:00.000 2 13.97 Wellington Importadora Rua do Mercado, 12 Resende SP 08737-363 Brazil 78.24
10257 HILAA 4 1996-07-16 00:00:00.000 1996-08-13 00:00:00.000 1996-07-22 00:00:00.000 3 81.91 HILARION-Abastos Carrera 22 con Ave. Carlos Soublette #8-35 San Cristóbal Táchira 5022 Venezuela 78.24
830 rows in total

Example of a subquery after SELECT that returns average freight values per employee.

SELECT

   *,

   ROUND(

     (SELECT

       AVG(Freight)

     FROM

       Orders o2

     WHERE

       o1.EmployeeID = o2.EmployeeID)

  ,2) AS [Avg Freight]

FROM

   Orders o1;

OrderID CustomerID EmployeeID OrderDate RequiredDate ShippedDate ShipVia Freight ShipName ShipAddress ShipCity ShipRegion ShipPostalCode ShipCountry Avg Freight
10248 VINET 5 1996-07-04 00:00:00.000 1996-08-01 00:00:00.000 1996-07-16 00:00:00.000 3 32.38 Vins et alcools Chevalier 59 rue de l'Abbaye Reims NULL 51100 France 93.30
10249 TOMSP 6 1996-07-05 00:00:00.000 1996-08-16 00:00:00.000 1996-07-10 00:00:00.000 1 11.61 Toms Spezialitäten Luisenstr. 48 Münster NULL 44087 Germany 56.42
10250 HANAR 4 1996-07-08 00:00:00.000 1996-08-05 00:00:00.000 1996-07-12 00:00:00.000 2 65.83 Hanari Carnes Rua do Paço, 67 Rio de Janeiro RJ 05454-876 Brazil 72.73
10251 VICTE 3 1996-07-08 00:00:00.000 1996-08-05 00:00:00.000 1996-07-15 00:00:00.000 1 41.34 Victuailles en stock 2, rue du Commerce Lyon NULL 69004 France 85.71
10252 SUPRD 4 1996-07-09 00:00:00.000 1996-08-06 00:00:00.000 1996-07-11 00:00:00.000 2 51.3 Suprêmes délices Boulevard Tirou, 255 Charleroi NULL B-6000 Belgium 72.73
10253 HANAR 3 1996-07-10 00:00:00.000 1996-07-24 00:00:00.000 1996-07-16 00:00:00.000 2 58.17 Hanari Carnes Rua do Paço, 67 Rio de Janeiro RJ 05454-876 Brazil 85.71
10254 CHOPS 5 1996-07-11 00:00:00.000 1996-08-08 00:00:00.000 1996-07-23 00:00:00.000 2 22.98 Chop-suey Chinese Hauptstr. 31 Bern NULL 3012 Switzerland 93.30
10255 RICSU 9 1996-07-12 00:00:00.000 1996-08-09 00:00:00.000 1996-07-15 00:00:00.000 3 148.33 Richter Supermarkt Starenweg 5 Genève NULL 1204 Switzerland 77.35
10256 WELLI 3 1996-07-15 00:00:00.000 1996-08-12 00:00:00.000 1996-07-17 00:00:00.000 2 13.97 Wellington Importadora Rua do Mercado, 12 Resende SP 08737-363 Brazil 85.71
10257 HILAA 4 1996-07-16 00:00:00.000 1996-08-13 00:00:00.000 1996-07-22 00:00:00.000 3 81.91 HILARION-Abastos Carrera 22 con Ave. Carlos Soublette #8-35 San Cristóbal Táchira 5022 Venezuela 72.73
830 rows in total

Example of a subquery after FROM that returns average of a freight summed up by employee.

SELECT

   ROUND(AVG(Freight_per_Employee),2) As [Avg Freight per Employee]

FROM(

   SELECT

     SUM(Freight) As Freight_per_Employee

   FROM

     Orders

GROUP BY

     EmployeeID) o;

Avg Freight per Employee
7215.85
1 row in total

Example of a subquery after FROM that returns a client from Poland.

SELECT

   *

FROM

   Orders

WHERE CustomerID =

   (SELECT

     CustomerID

   FROM

     Customers

   WHERE

     Country = 'Poland');

OrderID CustomerID EmployeeID OrderDate RequiredDate ShippedDate ShipVia Freight ShipName ShipAddress ShipCity ShipRegion ShipPostalCode ShipCountry
10374 WOLZA 1 1996-12-05 00:00:00.000 1997-01-02 00:00:00.000 1996-12-09 00:00:00.000 3 3.94 Wolski Zajazd ul. Filtrowa 68 Warszawa NULL 01-012 Poland
10611 WOLZA 6 1997-07-25 00:00:00.000 1997-08-22 00:00:00.000 1997-08-01 00:00:00.000 2 80.65 Wolski Zajazd ul. Filtrowa 68 Warszawa NULL 01-012 Poland
10792 WOLZA 1 1997-12-23 00:00:00.000 1998-01-20 00:00:00.000 1997-12-31 00:00:00.000 3 23.79 Wolski Zajazd ul. Filtrowa 68 Warszawa NULL 01-012 Poland
10870 WOLZA 5 1998-02-04 00:00:00.000 1998-03-04 00:00:00.000 1998-02-13 00:00:00.000 3 12.04 Wolski Zajazd ul. Filtrowa 68 Warszawa NULL 01-012 Poland
10906 WOLZA 4 1998-02-25 00:00:00.000 1998-03-11 00:00:00.000 1998-03-03 00:00:00.000 3 26.29 Wolski Zajazd ul. Filtrowa 68 Warszawa NULL 01-012 Poland
10998 WOLZA 8 1998-04-03 00:00:00.000 1998-04-17 00:00:00.000 1998-04-17 00:00:00.000 2 20.31 Wolski Zajazd ul. Filtrowa 68 Warszawa NULL 01-012 Poland
11044 WOLZA 4 1998-04-23 00:00:00.000 1998-05-21 00:00:00.000 1998-05-01 00:00:00.000 1 8.72 Wolski Zajazd ul. Filtrowa 68 Warszawa NULL 01-012 Poland
7 rows in total

Example of a subquery after FROM that returns clients from Germany.

SELECT

   *

FROM

   Orders

WHERE CustomerID IN

   (SELECT

     CustomerID

   FROM

     Customers

   WHERE

     Country = 'Germany');

OrderID CustomerID EmployeeID OrderDate RequiredDate ShippedDate ShipVia Freight ShipName ShipAddress ShipCity ShipRegion ShipPostalCode ShipCountry
10249 TOMSP 6 1996-07-05 00:00:00.000 1996-08-16 00:00:00.000 1996-07-10 00:00:00.000 1 11.61 Toms Spezialitäten Luisenstr. 48 Münster NULL 44087 Germany
10260 OTTIK 4 1996-07-19 00:00:00.000 1996-08-16 00:00:00.000 1996-07-29 00:00:00.000 1 55.09 Ottilies Käseladen Mehrheimerstr. 369 Köln NULL 50739 Germany
10267 FRANK 4 1996-07-29 00:00:00.000 1996-08-26 00:00:00.000 1996-08-06 00:00:00.000 1 208.58 Frankenversand Berliner Platz 43 München NULL 80805 Germany
10273 QUICK 3 1996-08-05 00:00:00.000 1996-09-02 00:00:00.000 1996-08-12 00:00:00.000 3 76.07 QUICK-Stop Taucherstraße 10 Cunewalde NULL 1307 Germany
10277 MORGK 2 1996-08-09 00:00:00.000 1996-09-06 00:00:00.000 1996-08-13 00:00:00.000 3 125.77 Morgenstern Gesundkost Heerstr. 22 Leipzig NULL 4179 Germany
10279 LEHMS 8 1996-08-13 00:00:00.000 1996-09-10 00:00:00.000 1996-08-16 00:00:00.000 2 25.83 Lehmanns Marktstand Magazinweg 7 Frankfurt a.M. NULL 60528 Germany
10284 LEHMS 4 1996-08-19 00:00:00.000 1996-09-16 00:00:00.000 1996-08-27 00:00:00.000 1 76.56 Lehmanns Marktstand Magazinweg 7 Frankfurt a.M. NULL 60528 Germany
10285 QUICK 1 1996-08-20 00:00:00.000 1996-09-17 00:00:00.000 1996-08-26 00:00:00.000 2 76.83 QUICK-Stop Taucherstraße 10 Cunewalde NULL 1307 Germany
10286 QUICK 8 1996-08-21 00:00:00.000 1996-09-18 00:00:00.000 1996-08-30 00:00:00.000 3 229.24 QUICK-Stop Taucherstraße 10 Cunewalde NULL 1307 Germany
10301 WANDK 8 1996-09-09 00:00:00.000 1996-10-07 00:00:00.000 1996-09-17 00:00:00.000 2 45.08 Die Wandernde Kuh Adenauerallee 900 Stuttgart NULL 70563 Germany
122 rows in total

Example of a subquery after FROM that returns a second from the top highest freight's value.

SELECT

   *

FROM

   Orders

WHERE

   Freight =

     (SELECT

       MAX(Freight)

     FROM

       Orders

     WHERE

       Freight <

         (SELECT

           MAX(Freight)

         FROM

           Orders)

     );

OrderID CustomerID EmployeeID OrderDate RequiredDate ShippedDate ShipVia Freight ShipName ShipAddress ShipCity ShipRegion ShipPostalCode ShipCountry
10372 QUEEN 5 1996-12-04 00:00:00.000 1997-01-01 00:00:00.000 1996-12-09 00:00:00.000 2 890.78 Queen Cozinha Alameda dos Canàrios, 891 Sao Paulo SP 05487-020 Brazil
1 row in total

SUBQUERIES EXISTS, NOT EXISTS

The EXISTS operator allows you to check the existence of any record in a subquery. It returns true or false depending on the result of the operation.

Example of a subquery after EXISTS that returns true if there are customers who placed orders in both July and August 1996.

SELECT

   *

FROM

   Orders o1

WHERE

   EXISTS(

     SELECT

       *

     FROM

       Orders o2

     WHERE

       o2.CustomerID = o1.CustomerID

       AND YEAR(OrderDate) = 1996 and MONTH(OrderDate) = 7)

       AND YEAR(OrderDate) = 1996 and MONTH(OrderDate) = 8;

OrderID CustomerID EmployeeID OrderDate RequiredDate ShippedDate ShipVia Freight ShipName ShipAddress ShipCity ShipRegion ShipPostalCode ShipCountry
10270 WARTH 1 1996-08-01 00:00:00.000 1996-08-29 00:00:00.000 1996-08-02 00:00:00.000 1 136.54 Wartian Herkku Torikatu 38 Oulu NULL 90110 Finland
10272 RATTC 6 1996-08-02 00:00:00.000 1996-08-30 00:00:00.000 1996-08-06 00:00:00.000 2 98.03 Rattlesnake Canyon Grocery 2817 Milton Dr. Albuquerque NM 87110 USA
10274 VINET 6 1996-08-06 00:00:00.000 1996-09-03 00:00:00.000 1996-08-16 00:00:00.000 1 6.01 Vins et alcools Chevalier 59 rue de l'Abbaye Reims NULL 51100 France
10291 QUEDE 6 1996-08-27 00:00:00.000 1996-09-24 00:00:00.000 1996-09-04 00:00:00.000 2 6.4 Que Delícia Rua da Panificadora, 12 Rio de Janeiro RJ 02389-673 Brazil
10294 RATTC 4 1996-08-30 00:00:00.000 1996-09-27 00:00:00.000 1996-09-05 00:00:00.000 2 147.26 Rattlesnake Canyon Grocery 2817 Milton Dr. Albuquerque NM 87110 USA
5 rows in total

Example of a subquery after NOT EXISTS that returns true if there are customers who placed orders in August 1996 but not in July 1996.

SELECT

   *

FROM

   Orders o1

WHERE

   NOT EXISTS(

     SELECT

       *

     FROM

       Orders o2

     WHERE

       o2.CustomerID = o1.CustomerID

       AND YEAR(OrderDate) = 1996 and MONTH(OrderDate) = 7)

       AND YEAR(OrderDate) = 1996 and MONTH(OrderDate) = 8;

OrderID CustomerID EmployeeID OrderDate RequiredDate ShippedDate ShipVia Freight ShipName ShipAddress ShipCity ShipRegion ShipPostalCode ShipCountry
10271 SPLIR 6 1996-08-01 00:00:00.000 1996-08-29 00:00:00.000 1996-08-30 00:00:00.000 2 4.54 Split Rail Beer & Ale P.O. Box 555 Lander WY 82520 USA
10273 QUICK 3 1996-08-05 00:00:00.000 1996-09-02 00:00:00.000 1996-08-12 00:00:00.000 3 76.07 QUICK-Stop Taucherstraße 10 Cunewalde NULL 1307 Germany
10275 MAGAA 1 1996-08-07 00:00:00.000 1996-09-04 00:00:00.000 1996-08-09 00:00:00.000 1 26.93 Magazzini Alimentari Riuniti Via Ludovico il Moro 22 Bergamo NULL 24100 Italy
10276 TORTU 8 1996-08-08 00:00:00.000 1996-08-22 00:00:00.000 1996-08-14 00:00:00.000 3 13.84 Tortuga Restaurante Avda. Azteca 123 México D.F. NULL 5033 Mexico
10277 MORGK 2 1996-08-09 00:00:00.000 1996-09-06 00:00:00.000 1996-08-13 00:00:00.000 3 125.77 Morgenstern Gesundkost Heerstr. 22 Leipzig NULL 4179 Germany
10278 BERGS 8 1996-08-12 00:00:00.000 1996-09-09 00:00:00.000 1996-08-16 00:00:00.000 2 92.69 Berglunds snabbköp Berguvsvägen 8 Luleå NULL S-958 22 Sweden
10279 LEHMS 8 1996-08-13 00:00:00.000 1996-09-10 00:00:00.000 1996-08-16 00:00:00.000 2 25.83 Lehmanns Marktstand Magazinweg 7 Frankfurt a.M. NULL 60528 Germany
10280 BERGS 2 1996-08-14 00:00:00.000 1996-09-11 00:00:00.000 1996-09-12 00:00:00.000 1 8.98 Berglunds snabbköp Berguvsvägen 8 Luleå NULL S-958 22 Sweden
10281 ROMEY 4 1996-08-14 00:00:00.000 1996-08-28 00:00:00.000 1996-08-21 00:00:00.000 1 2.94 Romero y tomillo Gran Vía, 1 Madrid NULL 28001 Spain
10282 ROMEY 4 1996-08-15 00:00:00.000 1996-09-12 00:00:00.000 1996-08-21 00:00:00.000 1 12.69 Romero y tomillo Gran Vía, 1 Madrid NULL 28001 Spain
20 rows in total

SUBQUERIES ANY, ALL

If any of the subquery values meet the condition the ANY operator returns true. For the All operator ro rerutn true - all of the subquery values must meet the condition. Both operators are used with a WHERE or HAVING clause.

Example of a subquery after ANY that returns all the customers where freight is above 750.

SELECT

   *

FROM

   Customers

WHERE

   CustomerID =

  ANY(

     SELECT

       CustomerID

     FROM

       Orders

     WHERE

       Freight > 750);

CustomerID CompanyName ContactName ContactTitle Address City Region PostalCode Country Phone Fax
ERNSH Ernst Handel Roland Mendel Sales Manager Kirchgasse 6 Graz NULL 8010 Austria 7675-3425 7675-3426
QUEEN Queen Cozinha Lúcia Carvalho Marketing Assistant Alameda dos Canàrios, 891 Sao Paulo SP 05487-020 Brazil (11) 555-1189 NULL
QUICK QUICK-Stop Horst Kloss Accounting Manager Taucherstraße 10 Cunewalde NULL 1307 Germany 0372-035188 NULL
SAVEA Save-a-lot Markets Jose Pavarotti Sales Representative 187 Suffolk Ln. Boise ID 83720 USA (208) 555-8097 NULL
4 rows in total

Example of a subquery after ALL that returns the five recent orders which are done by one shipping company United Package.

SELECT

   *

FROM

   Shippers

WHERE

   ShipperID =

   ALL(

     SELECT Top 5

       ShipVia

     FROM

       Orders

     ORDER BY

       OrderID DESC);

ShipperID CompanyName Phone
2 United Package (503) 555-3199
1 row in total

INDEX

Following the Microsoft definitions:

"Clustered indexes sort and store the data rows in the table or view based on their key values. These are the columns included in the index definition. There can be only one clustered index per table, because the data rows themselves can be stored in only one order."

"The only time the data rows in a table are stored in sorted order is when the table contains a clustered index. When a table has a clustered index, the table is called a clustered table. If a table has no clustered index, its data rows are stored in an unordered structure called a heap."

"Nonclustered indexes have a structure separate from the data rows. A nonclustered index contains the nonclustered index key values and each key value entry has a pointer to the data row that contains the key value."

"The pointer from an index row in a nonclustered index to a data row is called a row locator. The structure of the row locator depends on whether the data pages are stored in a heap or a clustered table. For a heap, a row locator is a pointer to the row. For a clustered table, the row locator is the clustered index key."

To see the benefits from applying a nonclustered index we need a big dataset. Let's download a movie rating dataset that contains over 20 million rows. Link to the dataset on Kaggle.

Next, let's create a database and a table, and let's upload the data using BULK INSERT.

CREATE DATABASE Rating_DB;


CREATE TABLE Rating (

   User_ID INT,

   Movie_ID INT,

   Rating DEC(2,1),

   Rating_Datetime DATE

)

In my case the data upload took around 10 mins.

BULK INSERT Rating

FROM 'C:\path\rating.csv'

WITH (

   FIRSTROW = 2,

   FIELDTERMINATOR = ',',

   ROWTERMINATOR = '\n'

)

The results.

SELECT

   *

FROM

   Rating;

User_ID Movie_ID Rating Rating_Datetime
1 2 3.5 2005-04-02
1 29 3.5 2005-04-02
1 32 3.5 2005-04-02
1 47 3.5 2005-04-02
1 50 3.5 2005-04-02
1 112 3.5 2004-09-10
1 151 4.0 2004-09-10
1 223 4.0 2005-04-02
1 253 4.0 2005-04-02
1 260 4.0 2005-04-02
20000263 rows in total

At this point the table has no primary key. Let's measure the execution time of the following query.

SET STATISTICS TIME ON;

SELECT

   User_ID,

   Movie_ID,

   Rating_Datetime

FROM

   Rating

WHERE

   User_ID = 138493

   AND Movie_ID = 44555

   AND Rating_Datetime = '2009-10-17';

SET STATISTICS TIME OFF;


SQL Server Execution Times:

CPU time = 10328 ms, elapsed time = 2990 ms.

Let's add a column that will hold the primary key for the table. In my case the process took around 30 mins.

ALTER TABLE Rating

ADD ID INT IDENTITY(1,1) PRIMARY KEY;

The table after adding the column.

SELECT

   *

FROM

   Rating;

User_ID Movie_ID Rating Rating_Datetime ID
1 2 3.5 2005-04-02 1
1 29 3.5 2005-04-02 2
1 32 3.5 2005-04-02 3
1 47 3.5 2005-04-02 4
1 50 3.5 2005-04-02 5
1 112 3.5 2004-09-10 6
1 151 4.0 2004-09-10 7
1 223 4.0 2005-04-02 8
1 253 4.0 2005-04-02 9
1 260 4.0 2005-04-02 10
20000263 rows in total

Let's measure the execution time of the earlier query.

SQL Server Execution Times:

CPU time = 6423 ms, elapsed time = 1794 ms.

Finally, let's create a nonclustered index.

CREATE INDEX ix_used_movie_date

ON Rating(

   User_ID,

   Movie_ID,

   Rating_Datetime);

Let's measure the execution time of the earlier query.

SQL Server Execution Times:

CPU time = 0 ms, elapsed time = 0 ms.

The differences may not seem significant but imagine the time savings if there are hundreds of millions of rows. Just rememeber that the indexes are memory consuming so use them for queries that occur frequently.


FUNCTION

In SQL Server you can create your own function. They are programs that accept one or more parameter and return a single value.

Example of a creation of a function that returns value of net sales.

CREATE FUNCTION Net_Sales_udf(

   @Quantity SMALLINT,

   @Unitprice DEC(6,2),

   @Discount DEC(3,2)

)

RETURNS DEC(10,2)

AS

BEGIN

  RETURN @Quantity * @Unitprice * (1 - @Discount);

END;

Let's use the function in a query to get some results. In this case value of net sales by a product id.

SELECT

   ProductID,

   SUM(dbo.Net_Sales_udf(Quantity, Unitprice, Discount)) net_sales

FROM

   [Order Details]

GROUP BY

   ProductID

ORDER BY

   net_sales DESC;

ProductID net_sales
38 141396.74
29 80368.69
59 71155.70
62 47234.99
60 46825.48
56 42593.06
51 41819.65
17 32698.38
18 29171.88
28 25696.64
77 rows in total

STORED PROCDEDURE

Stored Procedure in SQL Server can be defined as a batch of statements grouped as a logical unit and stored in the database. The stored procedure accepts the parameters and executes the T-SQL statements in the procedure. Returns results if there are any.

Example of a creation of a stored procedure that returns orders per provided client id.

CREATE PROCEDURE sp_order_client(

   @customerid = AS NCHAR(5)

)

AS

BEGIN

   SELECT

     *

   FROM

     Orders

   WHERE

     CustomerID = @customerid

END

Let's run the procedure for the VINET customer.

EXEC sp_order_client 'VINET';

or

EXECUTE sp_order_client 'VINET';

OrderID CustomerID EmployeeID OrderDate RequiredDate ShippedDate ShipVia Freight ShipName ShipAddress ShipCity ShipRegion ShipPostalCode ShipCountry
10248 VINET 5 1996-07-04 00:00:00.000 1996-08-01 00:00:00.000 1996-07-16 00:00:00.000 3 32.38 Vins et alcools Chevalier 59 rue de l'Abbaye Reims NULL 51100 France
10274 VINET 6 1996-08-06 00:00:00.000 1996-09-03 00:00:00.000 1996-08-16 00:00:00.000 1 6.01 Vins et alcools Chevalier 59 rue de l'Abbaye Reims NULL 51100 France
10295 VINET 2 1996-09-02 00:00:00.000 1996-09-30 00:00:00.000 1996-09-10 00:00:00.000 2 1.15 Vins et alcools Chevalier 59 rue de l'Abbaye Reims NULL 51100 France
10737 VINET 2 1997-11-11 00:00:00.000 1997-12-09 00:00:00.000 1997-11-18 00:00:00.000 2 7.79 Vins et alcools Chevalier 59 rue de l'Abbaye Reims NULL 51100 France
10739 VINET 3 1997-11-12 00:00:00.000 1997-12-10 00:00:00.000 1997-11-17 00:00:00.000 3 11.08 Vins et alcools Chevalier 59 rue de l'Abbaye Reims NULL 51100 France
5 rows in total

Something a bit more advanced. Example of a craetion of a stored procedure that accepts three parameters (sales' year, month and a product name), refers to earlier created function Net_Sales_udf, and returns one output parameter (net sales value).

CREATE PROCEDURE sp_product_sale(

   @sales_year AS SMALLINT,

   @sales_month AS SMALLINT,

   @product_name As nvarchar(40),

   @net_sales As DEC(10,2) OUTPUT

)

AS

BEGIN

   SELECT

     @net_sales = SUM(dbo.Net_Sales_udf(od.Quantity, od.Unitprice, od.Discount))

  FROM

     [Order Details] od

  LEFT JOIN Orders o

     ON od.OrderID = o.OrderID

  LEFT JOIN Products p

     ON od.ProductID = p.ProductID

  WHERE

     p.ProductName = @product_name

     AND YEAR(o.OrderDate) = @sales_year

     AND MONTH(o.OrderDate) = @sales_month

  GROUP BY

     p.ProductName,

     YEAR(o.OrderDate),

     MONTH(o.OrderDate)

END;

Let's run the procedure by giving all the required parameters.

DECLARE

   @OutputParameter DEC(10,2)

EXEC sp_product_sale

   @sales_year = 1996,

   @sales_month = 8,

   @product_name = 'Chai',

   @net_sales = @OutputParameter OUTPUT

SELECT

   @OutputParameter;

(No column name)
777.60
1 row in total

IF ELSE

The If Else statement allows you to control the flow of program.

Example of a query where we check if product sales increased in comparison to the same month last year. Let's use the stored procedure sp_product_sale and the If Else statement.

DECLARE

   @Year DEC(10,2)

EXEC sp_product_sale

   @sales_year = 1997,

   @sales_month = 8,

   @product_name = 'Chai',

   @net_sales = @Year OUTPUT;

DECLARE

   @PreviousYear DEC(10,2)

EXEC sp_product_sale

   @sales_year = 1996,

   @sales_month = 8,

   @product_name = 'Chai',

   @net_sales = @PreviousYear OUTPUT;

IF @Year > @PreviousYear * 1.05

   BEGIN

     Print 'Target achieved'

   END

ELSE

   BEGIN

     Print 'Target not achieved'

   END

Target not achieved

CREATE TABLE

For most shipping carriers, payment options include credit cards, debit cards, electronic funds transfer, letters of credit or cash (accepted in limited circumstances).

Let's create a Payment Method table that contains two columns: PaymentID, PaymentName. The id will increase incrtementally by 1 starting from 1.

CREATE TABLE [Payment Method] (

   PaymentID int IDENTITY(1,1) PRIMARY KEY,

   PaymentName nvarchar(30)

);

Next, let's insert the values.

INSERT INTO [Payment Method] (PaymentName)

VALUES

   ('credit card'),

   ('debit card'),

   ('electronic funds transfer'),

   ('letters of credit'),

   ('cash');

Finally, let's create a column named PaymentID in the Orders table. Let's define it as a foreign key of the PaymentID column from the Payment Method table.

ALTER TABLE

   Orders

ADD PaymentID int FOREIGN KEY REFERENCES [Payment Method](PaymentID);

The exercise continues with the While description in the next chapter.


WHILE

The If Else statement is loop that allows you to control the flow of program.

In the example query loops through all the order ids in the Orders table and assigns random payment id value (from 1 to 5) to each of them.

DECLARE

   @MinID INT,

   @MaxID INT;

SET

   @MinID = (SELECT MIN(OrderID) FROM Orders);

SET

   @MaxID = (SELECT MAX(OrderID) FROM Orders);

WHILE @MinID <= @MaxID

   BEGIN

     UPDATE

       Orders

     SET

       PaymentID = FLOOR(RAND()*(5-1+1)+1)

     WHERE

       OrderID = @MinID

     SET

       @MinID = @MinID + 1;

   END


DROP KEY, COLUMN, TABLE

Drop should be used to remove objects. We are going to remove the foreign key, the column and the table created in the CREATE TABLE and WHILE chgapters. Remember you have to remove constraints before you can are allowed a table.

Check the foreign key's exact name under Keys in order to remove it. If you wish to you can rename it. Drop the foreign key constraint in the PaymentID column in the Orders table:


ALTER TABLE

   Orders

DROP CONSTRAINT

   FK__Orders__PaymentI__0F624AF8;

Drop the PaymentID column in the Orders table.

ALTER TABLE

   Orders

DROP COLUMN

   PaymentID;

Drop the Payment Method table.

DROP TABLE

   [Payment Method]


TRIGGER

DML triggers are executed automatically when a data manipulation language (DML) event takes place. DML events include INSERT, UPDATE, or DELETE statements.

DML trigger statements use two temporary, memory-resident tables: the deleted table and the inserted tables. The tables can be referenced to in order to set conditions for DML trigger actions.

Triggers can be used to store audit information, e.g. what record whas added or deleted from a database.

In the example we will log in a separate table any action of inserting od deleting a record. To start with, let's create a table to store change logs in the Customers table.

CREATE TABLE CustomersAuditLog(

   ChangeID INT IDENTITY PRIMARY KEY,

   ActionTaken CHAR(6) NOT NULL,

   Updated DATETIME NOT NULL,

   CustomerID NCHAR(5),

   CompanyName NVARCHAR(40) NOT NULL,

   ContactName NVARCHAR(30),

   ContactTitle NVARCHAR(30),

   [Address] NVARCHAR(60),

   City NVARCHAR(15),

   Region NVARCHAR(15),

   PostalCode NVARCHAR(10),

   Country NVARCHAR(15),

   Phone NVARCHAR(24),

   Fax NVARCHAR(24),

   CHECK(ActionTaken = 'Insert' or ActionTaken = 'Delete' or ActionTaken = 'Reject')

);

The AFTER trigger fires after SQL Server completes the execution of the action successfully that fired it.

CREATE TRIGGER trg_customer_audit_log

   ON Customers

AFTER

   INSERT, DELETE

AS

BEGIN

SET

   NOCOUNT ON;

INSERT INTO CustomersAuditLog(

   ActionTaken,

   Updated,

   CustomerID,

   CompanyName,

   ContactName,

   ContactTitle,

   Address,

   City,

   Region,

   PostalCode,

   Country,

   Phone,

   Fax

)

   SELECT

     'Insert',

     GETDATE(),

     CustomerID,

     CompanyName,

     ContactName,

     ContactTitle,

     Address,

     City,

     Region,

     PostalCode,

     Country,

     Phone,

     Fax

   FROM

     inserted

UNION ALL

   SELECT

     'Delete',

     GETDATE(),

     CustomerID,

     CompanyName,

     ContactName,

     ContactTitle,

     Address,

     City,

     Region,

     PostalCode,

     Country,

     Phone,

     Fax

   FROM

     deleted;

   END

Let's insert a new customer to the Customers table.

INSERT INTO Customers(

   CustomerID,

   CompanyName,

   ContactName,

   ContactTitle,

   Address,

   City,

   Region,

   PostalCode,

   Country,

   Phone,

   Fax

)

VALUES (

   'WAWAC',

   'Piotrex Import Eksport',

   'Piotr Janus',

   'Owner',

   'ul. Spokojna 44',

   'Warszawa',

   NULL,

   '01-105',

   'Poland',

   '(48) 505 112 434',

   NULL

);

Action of inserting data runs the trigger. The record was automatically added by the trigger to the CustomersAuditLog table.

SELECT

   *

FROM

   CustomersAuditLog;

ChangeID ActionTaken Updated CustomerID CompanyName ContactName ContactTitle Address City Region PostalCode Country Phone Fax
1 Insert 2020-09-04 17:45:53.773 WAWAC Piotrex Import Eksport Piotr Janus Owner ul. Spokojna 44 Warszawa NULL 01-105 Poland (48) 505 112 434 NULL
1 row in total

Let's remove the new customer from the Customers table. The change will be recorded in the CustomersAuditLog table.

DELETE FROM

   Customers

WHERE

   CustomerID = 'WAWAC';


SELECT

   *

FROM

   CustomersAuditLog;

ChangeID ActionTaken Updated CustomerID CompanyName ContactName ContactTitle Address City Region PostalCode Country Phone Fax
1 Insert 2020-09-04 17:45:53.773 WAWAC Piotrex Import Eksport Piotr Janus Owner ul. Spokojna 44 Warszawa NULL 01-105 Poland (48) 505 112 434 NULL
2 Delete 2020-09-04 17:53:22.112 WAWAC Piotrex Import Eksport Piotr Janus Owner ul. Spokojna 44 Warszawa NULL 01-105 Poland (48) 505 112 434 NULL
2 rows in total

The INSTEAD OF trigger fires before SQL Server starts the execution of the action that fired it.

Let's create a trigger that will block the possibility to delete a client originating from Poland. In case of such attempt there should be a message that such action is not allowed and there should be a row added in the CustomersAuditLog table containing Reject message in the ActionTaken column. If client is from other country than Poland, it can be deleted, but this should be reflected in the CustomersAuditLog table.

The trigger creation.

CREATE TRIGGER trg_cannot_delete_customer_audit_log

   ON Customers

INSTEAD OF

   DELETE

AS

BEGIN

SET

   NOCOUNT ON;

DECLARE

   @Country NVARCHAR(15)

SELECT

   @Country = deleted.Country

FROM

   deleted

IF @Country = 'Poland'

   BEGIN

     RAISERROR('Customers from Poland cannot be deleted',16 ,1)

   INSERT INTO CustomersAuditLog(

     ActionTaken,

     Updated,

     CustomerID,

     CompanyName,

     ContactName,

     ContactTitle,

     Address,

     City,

     Region,

     PostalCode,

     Country,

     hone,

     Fax

   )

     SELECT

       'Reject',

       GETDATE(),

       CustomerID,

       CompanyName,

       ContactName,

       ContactTitle,

       Address,

       City,

       Region,

       PostalCode,

       Country,

       Phone,

       Fax

     FROM

       deleted d;

   END

ELSE

   BEGIN

   INSERT INTO CustomersAuditLog(

     ActionTaken,

     Updated,

     CustomerID,

     CompanyName,

     ContactName,

     ContactTitle,

     Address,

     City,

     Region,

     PostalCode,

     Country,

     Phone,

     Fax

   )

     SELECT

       'Delete',

       GETDATE(),

       CustomerID,

       CompanyName,

       ContactName,

       ContactTitle,

       Address,

       City,

       Region,

       PostalCode,

       Country,

       Phone,

       Fax

     FROM

       deleted;

   END

END

Let's again insert a new customer to the Customers table. The same as previosuly.

INSERT INTO Customers(

   CustomerID,

   CompanyName,

   ContactName,

   ContactTitle,

   Address,

   City,

   Region,

   PostalCode,

   Country,

   Phone,

   Fax

)

VALUES (

   'WAWAC',

   'Piotrex Import Eksport',

   'Piotr Janus',

   'Owner',

   'ul. Spokojna 44',

   'Warszawa',

   NULL,

   '01-105',

   'Poland',

   '(48) 505 112 434',

   NULL

);

The action of inserting data run again the trg_customer_audit_log trigger. The record was automatically added by the trigger to the CustomersAuditLog table.

SELECT

   *

FROM

   CustomersAuditLog;

ChangeID ActionTaken Updated CustomerID CompanyName ContactName ContactTitle Address City Region PostalCode Country Phone Fax
1 Insert 2020-09-04 17:45:53.773 WAWAC Piotrex Import Eksport Piotr Janus Owner ul. Spokojna 44 Warszawa NULL 01-105 Poland (48) 505 112 434 NULL
2 Delete 2020-09-04 17:53:22.112 WAWAC Piotrex Import Eksport Piotr Janus Owner ul. Spokojna 44 Warszawa NULL 01-105 Poland (48) 505 112 434 NULL
3 Insert 2020-09-07 13:35:01.992 WAWAC Piotrex Import Eksport Piotr Janus Owner ul. Spokojna 44 Warszawa NULL 01-105 Poland (48) 505 112 434 NULL
3 rows in total

Next, let's try to delete the recently inserted client.

DELETE FROM

   Customers

WHERE

   CustomerID = 'WAWAC';

In the Messages tab the error will be comunicated.

Customers from Poland cannot be deleted

Let's check the CustomersAuditLog table. Instead of Delete we received Reject in the ActionTaken column.

SELECT

   *

FROM

   CustomersAuditLog;

ChangeID ActionTaken Updated CustomerID CompanyName ContactName ContactTitle Address City Region PostalCode Country Phone Fax
1 Insert 2020-09-04 17:45:53.773 WAWAC Piotrex Import Eksport Piotr Janus Owner ul. Spokojna 44 Warszawa NULL 01-105 Poland (48) 505 112 434 NULL
2 Delete 2020-09-04 17:53:22.112 WAWAC Piotrex Import Eksport Piotr Janus Owner ul. Spokojna 44 Warszawa NULL 01-105 Poland (48) 505 112 434 NULL
3 Insert 2020-09-07 13:35:01.992 WAWAC Piotrex Import Eksport Piotr Janus Owner ul. Spokojna 44 Warszawa NULL 01-105 Poland (48) 505 112 434 NULL
4 Reject 2020-09-07 13:41:35.800 WAWAC Piotrex Import Eksport Piotr Janus Owner ul. Spokojna 44 Warszawa NULL 01-105 Poland (48) 505 112 434 NULL
4 rows in total

Notice that the trg_cannot_delete_customer_audit_log trigger had priority over the trg_customer_audit_log trigger. This is because if there are AFTER triggers on the table, they will fire after constraint processing. If the constraints are violated, the INSTEAD OF trigger actions are rolled back and the AFTER trigger is not executed.

Eventually, let's double check if the 'WAWAC' CustomerID was not removed from the customers table.

SELECT

   *

FROM

   Customers

WHERE

   CustomerID = 'WAWAC';

CustomerID CompanyName ContactName ContactTitle Address City Region PostalCode Country Phone Fax
WAWAC Piotrex Import Eksport Piotr Janus Owner ul. Spokojna 44 Warszawa NULL 01-105 Poland (48) 505 112 434 NULL
1 row in total

If you would like to remove a client from Poland you have to either disable or drop the trigger.

DISABLE TRIGGER trg_cannot_delete_customer_audit_log

ON Customers;


DROP TRIGGER trg_cannot_delete_customer_audit_log