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 '________';
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;
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;
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;
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;
Ordered quantity in total.
SELECT
SUM(Quantity)
FROM
[Order Details];
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;
In the example the product with the lowest price.
SELECT
MIN(UnitPrice)
FROM
Products;
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.
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;
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