Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
100% found this document useful (1 vote)
222 views

Peter Lalovsky Learn Microsoft SQL Server Intuitively. Transact SQL The Solid Basics

Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (1 vote)
222 views

Peter Lalovsky Learn Microsoft SQL Server Intuitively. Transact SQL The Solid Basics

Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 289

t

Learn
Microsoft® SQL Server®
Intuitively

Transact-SQL:
The Solid Basics

Peter Lalovsky
Learn Microsoft® SQL Server® Intuitively
Transact-SQL: The Solid Basics

Copyright
©2016 zPL Concept

Notice of Rights
All rights reserved. No part of this publication may be reproduced, distributed, or transmitted in any form or by any
means, including photocopying, recording, or other electronic or mechanical methods, without the prior written
permission of the publisher.

Trademarks
All brand names, product names, and technologies presented in this book are trademarks or registered trademarks
of their respective owners.

Notice of Liability
The information in this book is distributed on an “As Is” basis, without warranty. While every precaution has been
taken in the preparation of the book, neither the author nor the publisher shall have any liability to any person or
entity with respect to any loss or damage caused or alleged to be caused directly or indirectly by the instructions
contained in this book or by the computer software and hardware products described in it.

Author Cover Design


Peter Lalovsky Svetlana Safonova

Technical Editor Series Concept


Marleen Banton Peter Lalovsky

Book Layout Proofreader


George Yordanov Marleen Banton

Paperback ISBN
ISBN-13: 978-0995245105

Website
http://learnintuitively.lalovsky.com/sqlserver

Audience
Beginner/Intermediate
Table of Contents

The Basics
Intro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .4
What is a Database (DB) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .5
Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .9
Working with DB (DB Professional Roles) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
DB Types (Transactional (OLTP) ETL Analytical (OLAP)) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
How the DBE is Structured . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Relational DBs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Naming Conventions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
SQL and T-SQL (Transact-SQL) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
T-SQL Elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
DDL and DML Statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
CRUD (Create, Read, Update, Delete or INSERT, SELECT, UPDATE, DELETE) . . . . . . . . . . . . . . . . . . . . . . 55
Data Types and Conversions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
Collations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
NULL and 3VL (Three-valued Logic) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
SQL Server® Management Studio (SSMS) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
Constraints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78

DDL Statements
CREATE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
ALTER . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
DROP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
Script Objects in SSMS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110

DML Stataments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111


Modify Statements
INSERT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
SELECT... INTO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
UPDATE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
DELETE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
Query Statements
SELECT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
FROM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
JOIN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
Aliases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
WHERE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
ORDER BY . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
TOP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
OFFSET... FETCH . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
Table of Contents

GROUP BY . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
HAVING . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
GROUPING SETS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
ROLLUP and CUBE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
Execution Logic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
Subqueries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
UNION, EXCEPT and INTERSECT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
PIVOT and UNPIVOT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176

The Code in Action


Conditional Execution
IF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
IIF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
CASE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
Sessions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
Loops WHILE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
Common Table Expressions (CTE) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220
Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
Stored Procedures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240

Extended Cheat Sheet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259

Conventions Used in This Book

The following typographical conventions are used in this book:

Good practice
Comment, clarification and explanation

Bad practice Result of the execution of the code

! Pay attention
1 ∞
One-to-Many relation

Linking tables
Hint
Sequence
Reference in the book
Intro

Why learn “intuitively”?

Looking at colors and forms and making connections between them creates associations in the brain.
The brain links the colors and forms by using its natural attribute – the intuition.

The following example will give you an exact explanation of the above statement.

Blue
Red
Green

This book Next you can I love add more and starts from more SQL Server® - scratch and and more knowledge one
of the to the solid gives you most powerful background, databases in a solid base built by the world. in SQL Serv-
er®. this book.

You know what to do and you did it. Your intuition helped you with this simple exercise.

Metadata is data about the data. It explains the data.

We can do this:
This book Next you can I love add more and starts from more SQL Server® - scratch and and more knowledge one
of the to the solid gives you most powerful background, databases in a solid base built by the world. in SQL Serv-
er®. this book.

A collection of information, stored on paper or in electronic format.


A database, created by Microsoft® Corporation and widely used worldwide.

or this: Metadata

The database (DB) contains tables that store data. One of the usage of the DB is to serve webpages.

Here we declare an abbreviation Here we use the abbreviation

This book will help your intuition to teach you in Transact-SQL programing.

You can find the supporting source code, videos and more on http://learnintuitively.lalovsky.com/sqlserver.

4
What is a Database (DB)

Database (DB)

The Database (DB) is an electronic warehouse where we store data. The data in the DB is a representation of infor-
mation that exists in the real world and it is strictly organized. Our emails, social networks, bank accounts, medical
or school records are stored in DBs. We can store literally any information in a DB.

The following examples will help you draw the big picture.
Let’s move our contacts from the old notebook (paper) to a file (electronic).
First we create a text file like this:

Name: Anna Laurier


Email: anna.laurier@customer2.net
Phone: +1 555 2222

Name: John Smith


Email: john.smith@customer1.com
Phone: +1 555 1111

After we add a few contacts, we notice that repeating Name, Email and Phone for every single contact is time and
space consuming. We convert the format of the text file to this:

Name Email Phone


Anna Laurier anna.laurier@customer2.net +1 555 2222
John Smith john.smith@customer1.com +1 555 1111

Our next step is moving the data from the text file to a spreadsheet like Microsoft® Office Excel® or LibreOffice®
Calc - the simplest database.
The spreadsheet allows us to handle the information much easier than the text file - insert every slice of data in its
own cell, quick search, add filters on the columns, order the data etc.

Name Email Phone


Anna Laurier anna.laurier@customer2.net +1 555 2222
John Smith john.smith@customer1.com +1 555 1111

After we start using the spreadsheet (actually this is our first DB), we add multiple emails and phones for a single
contact:

Name Email Phone


Anna Laurier anna.laurier@customer2.net, al@customer2.net +1 555 2222, +1 555 2223
John Smith john.smith@customer1.com, js@customer1.com +1 555 1111, +1 555 1112, +1 555 1113

5
What is a Database (DB)

Oups! We discovered our first data issue: we used a comma (,) to split the data, but the comma itself is data. We fix
this issue by splitting the data in separate cells:

Name Email 1 Email 2 Phone 1 Phone 2 Phone 3


Anna Laurier anna.laurier@customer2.net al@customer2.net +1 555 2222 +1 555 2223
John Smith john.smith@customer1.com js@customer1.com +1 555 1111 +1 555 1112 +1 555 1113

Later, we search for a contact and we are confused: Column Phone 1 is a mobile or business phone? The same ques-
tion stands for column Email. We add additional columns to define the type on the phones and emails:

Email 1 Email 2 Phone Phone Phone Phone Phone Phone 3


Name Email 1 Email 2
Type Type 1 1 Type 2 2 Type 3 Type
Anna anna.laurier@ Personal al@custom- Business +1 555 Home +1 555 Mobile
Laurier customer2.net er2.net 2222 2223
John john.smith@ Business js@custom- Business +1 555 Mobile +1 555 Mobile +1 555 Business
Smith customer1.com er1.com 1111 1112 1113

After one year of using the spreadsheet, we notice that a person has 50 phones and we added 100 columns (Phone
and Phone Type pairs), but... what if we split the attributes (Email and Phone) into separate tables and one phone
for one contact is one row?
The next step is to convert our contacts DB from one table that stores all the data to multiple related tables:

Emails
ContactName Email Type
Anna Laurier anna.laurier@customer2.net Personal
Anna Laurier al@customer2.net Business
John Smith john.smith@customer1.com Business
John Smith js@customer1.com Business

One contact, many Emails Contacts


ContactName
Anna Laurier
John Smith
One contact, many Phones
Phones
ContactName Phone Type
Anna Laurier +1 555 2222 Home
Anna Laurier +1 555 2223 Mobile
John Smith +1 555 1111 Mobile
John Smith +1 555 1112 Mobile
John Smith +1 555 1113 Business

6
What is a Database (DB)

We just built our first One-to-Many relation – One Contact may have multiple phones and emails.

In few months we decide to add description to the phone and email types. We find that it will be easier to manage
separate tables for email and phone types and link them to the tables that store email and phone. We also decide
to add pictures for our contacts. As we decide to show only one picture per contact, we add column Picture in table
Contacts.

The other significant change is the replacement of the long column headers ContactName, EmailType and Phone-
Type with short numbers, used to link the tables.
EmailTypes
EmailTypeID EmailTypeName EmailTypeDescription
1 Personal Send jokes, pictures and any other
personal data.
2 Business Related to business conversations.
Send contacts and invoices.

Emails
ContactID Email EmailTypeID
1 anna.laurier@customer2.net 1
1 al@customer2.net 2
2 john.smith@customer1.com 2
2 js@customer1.com 2

Contacts
ContactID ContactName Picture

Link on ContactID 1 Anna Laurier 1-1.png


2 John Smith 2-3.png

Phones
ContactID Phone PhoneTypeID
1 +1 555 2222 1
1 +1 555 2223 2
2 +1 555 1111 2
2 +1 555 1112 2
2 +1 555 1113 3

PhoneTypes
PhoneTypeID PhoneTypeName PhoneTypeDescription
1 Home Stationary at home. Used by all family members.
2 Mobile Available all the time.
3 Business In business hours only. Located in the office.

7
What is a Database (DB)

The last step in our example is to transform the data in the DB to “human readable” reports like this

Anna Laurier
Emails
anna.laurier@customer2.net (Personal)
al@customer2.net (Business)
Phones
+1 555 2222 (Home)
+1 555 2223 (Mobile)
John Smith
Emails
john.smith@customer1.com (Business)
js@customer1.com (Business)
Phones
+1 555 1111 (Mobile)
+1 555 1112 (Mobile)
+1 555 1113 (Business)

or parameterized reports that will give us the option to search the contacts by City, Company, Job Title and any oth-
er way that we can imagine, based on the available data.

Already confused? It’s OK. The best thing about the database is that it is a never ending learning adventure! Let’s
stop complicating the example for now. We learned that:
• The DB stores data in electronic format
• The data in the DB is strictly organized
• The data in the DB relates to each other

Software and Vendors

We use software to manipulate the data in the DBs. This software is an interface (the point where two systems
cross) between the DBs and the DB professionals. It is known as RDBMS (Relational Database Management Sys-
tem). Some of the most popular RDBMS are:

Vendor Name Software Name


Microsoft® Corporation SQL Server®
Oracle Corporation Oracle
IBM DB2
SAP Sybase
MySQL AB MySQL

8
Installation

Download and Install SQL Server® 2016 Express

This book is for Microsoft® SQL Server® – one of the most powerful RDBMS. SQL Server® is released in different
editions that serve different needs - Enterprise, Standard, Web, Developer and Express.
The Express edition is free. It doesn’t support all the functionalities of the other editions, but it includes all that
we need in this book. The another version - Developer edition - is also free. It includes all the functionalities of the
Enterprise edition, but serves only development and testing. It can not be used on a production server.

The Express edition has different components. We need to download and install the DB engine (DBE) and the main
tool that we use to manage the data and the objects in SQL Server® – SSMS (SQL Server® Management Studio).

Before starting the installation, we need to verify that our computer meets the minimum requirements.

Hardware:
• Processor: x64 only, 1.4 GHz
• Memory: 512 MB
• Storage: 6 GB free disk space, NTFS file format
• Internet connection

Software:
• Operating system - Windows 8 or Windows Server 2012

From the Microsoft®’s website


(https://www.microsoft.com/en-ca/
server-cloud/products/sql-serv-
er-editions/sql-server-express.
aspx) select Try SQL Server®
2016 Express for free. Select
your preferred language and click
Download. Save the installer SQL-
Server2016-SSEI-Expr.exe on your
hard drive. To start the installation,
Double click on this file.

Click Download Media.

9
Installation

Click:
• SELECT LANGUAGE: English
• WHICH PACKAGE WOULD
YOU LIKE TO DOWNLOAD?:
Express Advanced
• SELECT DOWNLOAD LOCA-
TION: Location of your choice
• Next

In my installation I use C:\Temp as


my working directory. This is not
mandatory and you can create your
own working strategy.

Download the SQL Server® 2016


Express Database Engine (DBE).

10
Installation

Click Open Folder.

Click Yes and Close.

In the folder of our choice (C:\Temp)


the installer SQLEXPRADV_x64_
ENU.exe is downloaded.

I move the file to another folder, so


C:\Temp is empty for the next steps
and double click it.

11
Installation

Unzip the installation files in a direc-


tory of your choice.

To download the DBE, click on New SQL


Server® stand-alone installation or add
features to an existing installation.

After we finish the installation of the


DBE, we’ll come back to this window to
download SSMS Install SQL Server®
Management Tools.

Read the License Terms and check I


accept the license terms to continue
the installation.

12
Installation

Click Use Microsoft® Update to check


for updates (recommended).

If your computer needs any additional


update(s), they are listed here.

13
Installation

The next window verifies the neces-


sary updates, downloads and installs
them.

On the next screen all the checks have


to be green. If any of them is not, we
need to make the required fix(es) (for
example: edit the firewall settings).

14
Installation

Next we choose the features that we


want to install. If we install features
that we don’t need now, but will need
in the future, when we need them, they
will be there and ready to use. A good
example is R Services or Reporting
Services.

One computer (server) can run mul-


tiple SQL Server® instances. In this
step we define a name for our instance
- SQL2016EXPRESS.

15
Installation

On Server Configuration step, we


define how to start up the SQL Server®
services and which account to use for
each service.

In the next step we configure the


authentication mode - windows (the
account, with which we are logged in
Windows) or mixed (windows accounts
and users, created by us).

16
Installation

This step takes care of the configura-


tion of SQL Server® Reporting Services
(SSRS). 

In this step we accept to download and


install Microsoft® R Open.
Click Accept and Next.

17
Installation

The installation starts.

Scroll down and make sure that all the


checks are green.

18
Installation

Delete all the files in the working direc-


tory (C:\Temp) and click on Install SQL
Server® Management Tools to down-
load SSMS.
In the Microsoft®’s web page click on
Download SQL Server® Management
Studio and save the installer SSMS-
Setup-ENU.exe in a location of your
preference (in my installation C:\Temp).
Double click on SSMS-Setup-ENU.exe.

Click Install.

19
Installation

The installation is in progress.

Finally click Close.

20
Installation

After the installation is complete, browse Microsoft® SQL Server® Manage-


ment Studio in the Search Windows. This is our interface to the DB. Here we
compose and run our code in the programming language that communicates
with the DBE - SQL.

Click Connect to open SSMS.

SQL Server® Management Studio (SSMS) on page 75

21
Working with DB
(DB Professional Roles)

DB Professional Role

The DB needs to be:


• Created:
• Install the Database Engine (DBE) and the necessary components (SSMS)
• Create data models and DB objects for storing and manipulating the data
• Queried:
• Select, add, modify and delete data
• Create, edit and delete DB objects
• Maintained:
• Keep the DB up and running 24/7
• Create a redundancy copy of the data (backup)
• Optimize the performance of the DB and the load of the DB server

These tasks are done by DB professionals and are divided in 3 main roles:
• Database Administrator (DBA):
• Installs and configures the RDBMS
• Schedules backups and restores DBs and automations
• Replicates data between multiple DBs
• Monitors and optimizes the load of the DB server
• Manages the DB security (groups and users)
• Database Architect:
• Designs the conceptual, logical and physical data models
• Creates DB objects, entities and relations
• Database Developer – Creates the Transact-SQL code which:
• Connects the DB with the interface that manipulates the data:
Interface DB
Bank cash machine (ATM) Your bank account
Your browser Your email
• Manipulates data internally in the DB, between different DBs and the exchange of data with another
data sources (API to another DBs, flat files on FTP, etc.).
• Extracts data from different sources, transforms data and loads it into destination tables.
• Serves internal automated processes such as to send emails from the email queue every 2 minutes and
move data to an archive table.

These roles cross each other.


• The DBA can suggest an optimization of code to the developer
• The developer can create a DB architecture
• The architect can build a model that allows the developer to
create the most efficient code

22
DB Types

Operational Systems - OLTP (On-line Transaction Processing)

Data in the DB is multifarious. It is generated by multiple IT systems:


• Customer Relationship Management (CRM)
• Enterprise Resource Planning (ERP)
• Warehouse Management System (WMS)
• Standalone or web application like ATM or web application that manages the move of goods inside the ware-
house
• Custom built web applications
• Files exchanged with business partners
• Any other source of data that you can imagine

These systems are grouped in the OLTP (On-line Transaction Processing). They execute a lot of on-line transactions
to generate data.

The following example shows the main elements in the process of data generation in OLTP.

Customer 1: Online sale


1. DB Interface: Web browser
2. Transactions:
a. Read the customer data from CRM
b. Create order in ERP
c. Manipulate the ordered item(s) in WMS
d. Send an email to the customer

Customer 2: Sale in our store


1. DB Interface: POS Terminal (cash register)
2. Transactions:
a. Create order in ERP
b. Manipulate the ordered item(s) in WMS
c. Select the data, related to the order to print a bill

Order operator: internal order processing


1. DB interface: Standalone application
2. Transactions:
a. Process orders in ERP
b. Update quantities and locations on item(s) in WMS
c. Insert data in ERP to bill the customer

23
DB Types

OLTP

ETL (Extract, Transform, Load)

To make business decisions and analyses based on data created in OLTP, you need to cleanse, calculate and aggre-
gate the data... in one word transform it.
• How much did our sales people sell last month?
• Which 5 marketers signed the most new contracts last year?
These are questions that lead to business decisions.

The process that Extract, Transform and Load data from the OLTP systems to the destination system is called ETL
– Extract, Transform, Load. This process is necessary because the OLTP system generates a lot of data that is not
useful for the analyses (corrected lines in orders, internal moves of items, etc.), unclean data (data for non existing
items, garbage from bad transactions, etc.) or data from different sources, that needs to be joined for the analyses.

24
DB Types

Analytical Systems - OLAP (On-line Analytical Processing)

The ETL loads data into the Data Warehouse or OLAP (On-line Analytical Processing) system. The data in the OLAP
is organized into the Data Warehouses and Data Marts. It can be sliced and dice in any possible way. We query the
OLAP system to build reports that help the business to make decisions.

OLAP
The dataflow between the DB systems:

OLTP ETL OLAP

25
How the DBE is Structured

Microsoft® SQL Server® is a RDBMS that includes several components:


• Database Engine (DBE) - The DB
• SSRS (SQL Server® Reporting Services) - Creates, runs and automates reports
• SSIS (SQL Server® Integration Services) - Connects the DB to the world by import, export, transformation
and load of data and automations
• SSAS (SQL Server® Analysis Services) - Creates OLAP systems

and tools:
• SSMS (SQL Server® Management Studio) - Visual tool for writing and executing SQL code and managing DB
objects
• Profiler - Tracing and monitoring
• Tuning Advisor - Helps us to improve the performance of the DB, by suggesting sets of indexes

In this book we’ll need only the DBE and SSMS.

The DBE is built by different components which serves different needs:


• Database - Collection of structured and correlated objects that store
data and SQL code
• Schema - Container that isolates sets of DB objects
• Table - Stores data in rows and columns
• View - Shows sliced and diced data from the tables
• Function - Reusable code that is doing a specific task
• Stored Procedure - Reusable multi-step and complex code
DB objects (DBO)

Database
Schema
Table
View
Function
Stored Procedure
Hierarchy

26
How the DBE is Structured

The DB objects are System and User-Defined:


System – created and used by the DB for internal purposes, a.k.a. Object Catalog. For example we can query sys-
tem objects to list all table names in a DB
User-Defined – created and used by the DB professionals. These are the objects, where we store our data and code

Schemas
Namespace to store separately logical sets of objects related to the data for Sales, People, Human Resources, etc.
The Schemas facilitate the management of the DB security. The default schema is “dbo” (DB object).

Tables
The tables are composed of:
• Rows (records) – Items
• Columns – Attributes (quality or characteristic belonging to the data) of the items
• Cells – Joins an attribute to an item
• Values in cells – The value of the data stored in a cell

Columns
Column headers Attribute 1 Attribute 2 Attribute 3 Attribute 4
Item 1
Value for Physical element
Rows Item 2 and Item 2 Logical element
Attribute 3
Value
Item 3

Attribute 1 Attribute 2 Attribute 3 Attribute 4

Value
Attribute Item

Vechile NumberOfSeats
Airplane 312
Car 5
Item

27
How the DBE is Structured

Item Item – Attribute – Value is a


Attribute hierarchical structure. Every value
Value belongs to a attribute and every
attribute belongs to an item.
Example
Car Item Airplane Item
Color Attribute Color Attribute
Black Value White Value
Blue Value Pink Value
Red Value Number of seats Attribute
Number of seats Attribute 4 Value
4 Value 223 Value
5 Value Type Attribute
Type Attribute Cargo Value
Sedan Value Passenger Value
Convertible Value

This little table will help us put the pieces together:


The table name is Customers. The names of the DB objects are called identifiers.
In our SQL code, we use the names to identify which objects we manipulate.
Customers
FirstName LastName Email The headers identify the names
Anabel Larson anabel.larson@customer5.info of the attributes (the columns).
Anna Laurier anna.laurier@customer2.net
Beverly NULL NULL
John Smith john.smith@customer1.com
John Smith john.smith@customer3.org In the rows we store the items
Melanie Larson melanie.larson@customer4.biz
(the customers). One row in the
Xavier Jameson xavier.garcía@customer6.com
table represents one customer.
Zak Smith NULL

In the cells, we store the values for the


The attributes that we store for each custom- attributes belonging to the items.
er, are FirstName, LastName and Email.

The table contains:


• 8 rows for 8 items (customers)
• 3 columns for the attributes FirstName, LastName and Email.

The value for the attribute Email and Item in row 4 is john.smith@customer1.com.
The value for the attribute LastName, which belongs to item Melanie, is Larson.

28
How the DBE is Structured

The View (VW):


• Is a virtual recordset that selects data from tables or another views
• Is defined by SQL code and doesn’t store the data that it produces
• Directly selects and transforms data from underlying tables and views. Transformation or direct select
• Restricts sensitive data to specified roles and users (security)

Views on page 220

We can create a view to select all the columns from the HumanResources table, excluding the Salary column and
make it visible to everyone.
Another view is the column Salary which is visible only to the HR director.

The function (FN):


• Is SQL code that performs specific task
• Is stored in the DBE, centralized and reusable
• Is called with or without input parameters
• Is scalar-valued when it returns a single value or table-valued when it returns a recordset

When the business logic needs to change, we edit the logic in the function only. Objects dependent on the function
don’t need to be edited.

We can think of the function as a workshop in a factory.


Every workshop is doing a specific task.
In a guitar factory, there are multiple workshops to cut the bodies and necks; paint; and assemble the guitars and
more.
If we call the function PaintBody with a parameter called Color with value Blue, the body of the guitar is painted
in blue.
Otherwise it will be painted with the default color defined in the workshop (call the FN without specifying value for
the parameter).

An example of not parameterized scalar FN is: Select the last day of the current month. The result that the FN
returns is based on today’s date.

The parameterized scalar FN udf_UnitConvertor accepts the parameters Type, FromMetric, ToMetric and Value
and returns the value in the desired metric.

Input Output
Type FromMetric ToMetric Value Returned Value
Length Inch Centimeter 1 2.54
Volume Liter US Gallon 10 2.64172

29
How the DBE is Structured

The non-parameterized table-valued function udf_CustomersEligibleForDiscount returns a list of the custom-


ers, who have sales of $1,000 or more in the last fiscal month.

udf_CustomersEligibleForDiscount
CustomerID Sales
Functions on page 227
5174 2356.15
7652 1005.14

The Stored Procedure (SP):


• Is complex SQL code that executes multiple consecutive steps
• Is stored in the DBE
• Can be executed input parameters
• Returns a result (output parameter or recordset)
• Is the most powerful SQL code execution object

We can now compare a SP to a warehouse, factory and related business processes together:
1. We store the wood (warehouse) for the bodies of the guitars
2. We create guitars (production)
3. We store and ship (distribution) the guitars
All these steps in the process are dependent on each other and are executed in order.

An example of a SP that executes an ETL:


• If it does not exist, create (temporary) tables for the ETL process
• Select the sales data from the last month into the tables
• Aggregate the data and add additional columns to the tables
• Load the data by weeks into destination tables
• Delete/truncate the (temp) tables at the end of the ETL process
• Manipulate the log created during the ETL process

Stored Procedures on page 240

30
Relational DBs

RDBMS

Data in the DB refers to each other. We can’t create a sales order for a customer that doesn’t exist. Sales and Cus-
tomers data are related. This is why databases like SQL Server® are called RDBMS (Relational database manage-
ment system).

Structure (Dim) and Data (Fact) Tables

The tables store different attributes of data. Based on the stored data, the tables are logically defined as:
• Structure (Dim or Dimension in OLAP) tables – stores the structure of data like customers, items and man-
ufacturers
• Data (Fact in OLAP) tables – stores the values (Metrics, Measures), related to the structure tables such as
quantity and price of purchased item, made by manufacturer and total value of sales for a customer, etc

The structure and data tables are organized into logical schema. We can define two main schemas:
• Star - Represents a data table in the middle and the structure tables on the rays of the star
• Snowflake - an extension of the Star schema. Represents a data table in the middle and structure table in
the rays and related structure tables on the sub rays

Star Schema

Built by a data table, linked to one or multiple structure table(s).

31
Relational DBs

PK

PK PK

FK
FK
FK
FK
FK

PK PK

The Customers table stores the structure of the customers. One row is one customer. The column CustomerID
identifies the row and can store only unique numeric values.
This column is called Primary Key (PK). In table Customers we store all the attributes of the customer like names,
addresses, phone numbers, emails, etc. The same logic applies to all the structure tables.

The data table combines the structure tables with the values (metrics) that the table represents. In the Sales
table, these values are DateOfSale, Quantity and ItemPrice.

The column SaleID is the PK. Columns CustomerID, ItemID and ManufacturerID are linked to the structure tables
(Customers, Items and Manufacturers), they are not unique (they may store duplicates) because one customer can
order the same item by the same manufacturer multiple times. These columns are called Foreign Key (FK).

32
Relational DBs

There can only be one PK column per table and its type is:
• Natural (real data) - created by attribute(s) in the table
We can use the column Email (unique values) as natural PK
• Surrogate (not meaningful data) - consecutive number (columns CustomerID, ManufactiurerID, etc)

The tables that make up the Star schema may look like this:

Manucaturers (structure)
Customers (structure) Manufac- Manufactur- Manufacture-
Custo- Customer- Customer- CustomerAddress turerID erName rAddress
merID FirstName LastName 1 Toys For Boys 164 Blue Str.
1 John Smith 48 Mountain Str. Ltd.
2 Adrian Jameson 9423 Highway Road 2 Little Princess 1534 av. Lake
Fun Inc. Shore
3 Michael Olson 8 av. Riverside
3 Toys and 74 Green For-
1
Nature Co. est Blvd.

a.k.a. reference value or look-up value 1


Items (structure)
a.k.a. key value ItemID Item- ItemDescription
Code

1 P123 Plastic Airplane Blue


2 F12S Rag Doll 33 cm.
3 1234 Wooden Horse (Age:
3-6 years)
1

Sales (data) ∞ ∞
SaleID CustomerID ManufacturerID ItemID StoreID PromotionID DateOfSale Quantity ItemPrice
1 1 3 2 1 NULL 1967-04-15 4 9.3582
2 3 3 2 1 3 1964-12-13 12 1.5237
3 3 2 1 2 NULL 1965-03-17 5 22.3452
4 1 1 3 3 NULL 1966-01-27 75 0.8374
5 2 2 1 2 1 1966-04-05 6 45.5384


1
1
Stores (structure)
Promotions (structure)
StoreID StoreName StoreAddress
Promo- Promotion- Promotion- Promotion-
1 Magic Toys 15 1st Avenue tionID DateStart DateEnd Percent
2 Happy Kids 174 Rockland Str. 1 1966-03-08 1966-04-08 0.21
3 My Childhood 90 Blueway Blvd. #303 2 1963-07-12 1963-09-12 0.07
3 1964-12-10 1964-12-17 0.35

33
Relational DBs

Snowflake Schema

An extension of the Star Schema. The structure table refers to other structure tables, called outriggers.

Countries (structure, outrigger) Types (structure, outrigger)


CountryID CountryName CountryAl- Contry- TypeID TypeName TypeDescription
pha2Code Alpha3C- 1 Web Customers that buy
ode
online.
1 United States of US USA
2 Industry Manufacturers that oper-
America
ate in the same domain.
2 Canada CA CAN
3 Government Government related sales.
3 Germany DE DEU
4 Education Education specific sales.

1 1

Customers (structure)
∞ ∞
Custo- Custom- Custom- Custom- Custom-
merID erFirst- erLast- erCoun- erTypeID
Name Name tryID
1 John Smith 1 1
2 Adrian Jameson 2 2
3 Michael Olson 3 3

Data Table
Col 1 Col 2 Col 3
... ... ...
... ... ...

Items (structure)
ItemID Item- ItemDe- ItemCol- ItemSi-
Code scription orID zeID

1 D-123-RS Dress 1 1
2 S-456-GM Shirt 2 2
Structure Table 3 H-789-BL Hat 3 3
Col 1 Col 2 Col 3 ∞
... ... ... ∞
... ... ... 1

Colors (structure, outrigger)


ColorID ColorName
Outrigger Table Outrigger Table 1 Red
Col 1 Col 2 Col 3 Col 1 Col 2 Col 3 2 Green
... ... ... ... ... ... 3 Blue
1
... ... ... ... ... ...
Sizes (structure, outrigger)
SizeID SizeShort- SizeLongName
Name
1 S Small
2 M Medium
3 L Large
4 XL Extra Large
5 XXL Double Extra Large

34
Relational DBs

Entity

Entity is a logical group of tables that store related data on one subject - Time, Items, Manufacturers, Vendors and
Customers. The entities give us the option to extend the flexibility of the data. We can store multiple variants of
one item in separate table.

If we manufacture or sell clothes, one item is the dress and the multiple variants are size, color, fabric and other.

We can store multiple phones for one customer in CustomersPhones table. The uniqueness (one row represents
one customer) of table Customers is kept, and the requirement to store multiple phones for one customer is met.

Entity Tables in the entity


Time Years, Quarters, Months, Weeks, Days
Product Items, Variants, Colors, Sizes, UnitOfMeasures
Producers Manufacturers, Industries, Geography
Suppliers Vendors, Categories, Classes, Geography
Clients Customers, Addresses, Phones, Geography
Geography Countries, Cities, PostalCodees

The entities above are used in all subject areas (Purchases , Sales , Marketing Campaigns) of the business.

35
Relational DBs

Sales
Time
Days Quarters
Day- Day- DaysIn- WeeksIn-
Day- Quar- QuarterID
Day Of- Of- Quarter Quarter
OfMonth terID
Week Year
1969-01 90 14
1969-03-30 1 30 89 1969-01
1969-02 91 14
1969-03-31 2 31 90 1969-01
1969-03 92 14
1969-04-01 3 1 91 1969-02
1 Entity
1 ∞

Product
Items Colors
Item- Col- Color- Color-
ItemID ItemDescription
Code orID ID Name
1 P123 Plastic Airplane Blue 1 1 Red
2 F125 Rag Doll 33 cm. 2 2 Green
3 1234 Wooden Horse (Age: 1 3 Blue
3-6 years)
1 Entity
1 ∞

Suppliers
Vendors Classes
Ven- Ven- Vendor- Clas-
VndorName ClassName
dorID dorCode ClassID sID
1 VC12 Toys 3 1 Critical
Wholesales
2 Digital
2 VA56 Toys and 1
3 Tactical
Games
1
3 CU05 Infinite Toys 3 Entity
1 ∞

Sales ∞ ∞ ∞
Ven- DateOf- Quan- Item-
SaleID ItemID
dorID Sale tity Price
1 1 2 1969-03-31 536 3.14
2 3 3 1969-03-30 78 22.75
3 2 1 1969-04-01 13 673.24
Entity

36
Relational DBs

The presented structures (star, snowflake and entity) are usually built by a DB architect, so as mentioned, the DB
professional’s roles can overlap and a DBA or developer can also be involved.

Relations

The tables are linked by the following relations:


• One-to-One - one row corresponds to one row in another table. Extends the number of the attributes, by
spliting them in two tables

Customers CustomersMarketingDetails
Custo- First- Last- Custo- Market- Market-
Email
merID Name Name merID ingEmails ingCalls
1 John Smith js@customer1.net 1 1 1
2 Anna Laurier lauriera@customer2.org 2 NULL 1
3 Kimberly Nelson kim_nel@customer3.com 3 0 NULL
1 1

• One-to-Many - one row corresponds to many rows in another table. One customer may have multiple
phone numbers
Customers CustomersPhones
Custo- First- Last- Custo-
Email Phone Type
merID Name Name merID
1 John Smith js@customer1.net 1 +1 555 2222 Home
2 Anna Laurier lauriera@customer2.org 1 +1 555 2223 Mobile
3 Kimberly Nelson kim_nel@customer3.com 1 +1 555 2224 Business

1 2 +1 555 1111 Home


2 +1 555 1112 Mobile

• Many-to-Many - many rows corresponds to many rows in another table. This relation is built by two One-to-
Many relations. One customer can rent multiple cars and one car can be rented to multiple customers
Customers Rents Cars
Custo- First- Last- Custo- CarID Make
Email RentID CarID
merID Name Name merID
1 Toyota
1 John Smith js@customer1.net 1 3 2
2 Ford
2 Anna Laurier lauriera@customer2.org 2 2 2
3 Mazda
3 Kimberly Nelson kim_nel@customer3.com 3 3 1
1
1 4 1 1
5 2 3

∞ ∞

37
Relational DBs

Primary Key – Foreign Key (PK - FK) Relations

To manage these relations, we create a PRIMARY KEY (PK) and a FOREIGN KEY (FK) constraints on the columns
that define the relations. We are flagged to insert a FK when a corresponding PK doesn’t exist.

PRIMARY KEY is created on:


• One column with unique values that identify the rows in the table. One of the most commonly used PKs is
an auto incremental identity column (surrogate PK)
• Multiple columns - composite (or compound) PK. The combination of the columns in the key is unique

FOREIGN KEY is a column in the data table that is linked to the PK in the structure table. The values in the FK
column can be duplicated.

Customers Items
Custo- First- Last- Item-
Email ItemID ItemDescription
merID Name Name Code
1 John Smith john.smith@customer1.com 1 P123 Plastic Airplane Blue
2 Anna Laurier anna.laurier@customer2.net 2 F12S Rag Doll 33 cm.
3 Melanie Larson melanie.larson@customer4.biz 3 1234 Wooden Horse (Age:
3-6 years)
1
1
PRIMARY KEY
PRIMARY KEY
Sales ∞ ∞
SaleID CustomerID ItemID DateOfSale Quantity Price
1 2 3 1967-12-14 73 0.2563
2 1 3 1966-05-17 12 5.2341
3 1 2 1968-10-27 52 62.3251

PRIMARY KEY FOREIGN KEY


FOREIGN KEY

38
Relational DBs

Logical and physical PK – FK relations:


• Logical relations – constraints are not created on the columns. Managed by a logic in the SQL code or out-
side the DB (another layer of the software architecture – web or standalone application)
• Physical – PK and FK are real constraints in the DB and they are respected when manipulating the data

When a physical PK-FK relation is created, we can’t insert FK with no corresponding PK. That means that we
need a customer to exists in the Customers table (PK) to sell to this customer (FK in Sales table).

Relation Keys
One-to-One PK to PK
One-to-Many PK to FK
Many-to-Many PK to FK and FK to PK (junction table)

Add PK to all the tables as:


• Surrogate - Integer identity column or
• Natural - One column or composite PK

39
Naming Conventions

The DB objects, the identifiers and the variables need to have names. The goal is to build a easy to understand DB
structure and programming code. To construct the names, we follow rules called Naming Conventions. The rules
are defined by the DB professionals and are not mandatory. We can group the naming conventions as:

Descriptive Names

• Table names explain the data that the table stores:


Customers SalesHeader SalesDetails Items
• What is the task performed by the function:
usf_CalculateRebateforCustomer
udf_IsFirstDayofFiscalMonth
utf_SalesForCustomerIDByPeriod
• The name of the Stored Procedure explains what the procedure executes:
usp__ProjectID_1234__ETL uspDailyFullBackup

Special characters, start with numbers and keywords

When the name:


• Contains a special character (every character that is not a letter or number) [This is My First Table!]
• Starts with a number [123ThisisMyFirstTable]
• Is T-SQL keyword SELECT, FROM, TABLE, CREATE, AND etc
we make it valid by surrounding it with square brackets.

Examples: [12 Months Sales $] [Customers-Stats] [Split String]

Case

The different options of combining the upper and lower case define:
• PascalCase – every word starts with upper case MyFirstTable
• camelCase – first word starts with lower case and every next word starts with upper case myFirstTable
• All UPPER, all lower case MYFIRSTTABLE, myfirsttable

Delimited

We can use a character to delimit parts of the name:


• An underscore (_) can replace the spaces:
Before After
• [My First Table] My_First_Table
• Two underscores (__) can be a splitter between the prefix, the name and the suffix:
Before After
• uspSalesData_Archive usp__Sales_Data__Archive

40
Naming Conventions

Prefix

We can add a prefix to include the object type in the name:


tbl_MyFirstTable table vw_MyFirstView view
udf_MyFirstFunction function usp_MyFirstStoredProcedure stored procedure
The prefix gives us the flexibility to give the same name to different types of objects and facilitates the DB profes-
sionals by giving them a hint of what type of object they are working with.

We can add custom clarification in the prefix to identify the action that the object performs or to which project the
object is assigned:
• usp_Monthly_ETL__ProjectID_1234 is the name of a stored procedure, assigned to ProjectID 1234, that
runs an ETL once per month.
• usp_pid1234__Monthly_ETL and ups_pid1234__ReportByClient
are assigned to the same projectID 1234.

We can use some of the suggestions below or define our custom prefixes.

Prefix Object Type Comment


t, t_, Table t, tbl - table
tbl, tbl_
v, v_, View v, vw - view
vw, vw_
fn, fn_, Function fn - function
ufn, ufn_, ufn - user function
udf, udf_, udf - user-defined function
usf, usf_ usf - user-defined scalar function
utf, utf_ utf - user-defined table-valued funcion
sp Stored Procedure sp - stored procedure
usp_ usp - user-defined stored procedure
sp_ sp_ is a system prefix and using it as user-defined prefix is not a good practice

Suffix

• The suffix can be used to give additional description or clarity:


• The variation of the data in table or view:
tbl__Sales_Current tbl__Sales_LastYear tbl__Sales_Archive
• The action, performed by a function or SP
usp_SalesCurrentMonth_ETL usp__Sales_Current_Month__Report

Prefix, Suffix and delimiter

By adding both prefix and suffix, we benefit from all the advantages that they offer.

41
Naming Conventions

When we delimit the name with underscores, we can delimit the name from the prefix and the suffix with two de-
limiters (__) to distinct the delimiter between:
• Prefix and suffix
• Name and the delimiter inside the name

By adding:
• Delimiter in the name underscore (_)
• Delimiter after prefix and before suffix two underscores (__)
we split the elements clearly and make the name much more intuitive Prefix__This_Is_Object_Name__Suffixfx

Order of the Words

DateStart or StartDate FromDate or DateFrom InvoiceDate or DateInvoiced


DateEnd or EndDate ToDate or DateTo DateModified or MofifyDate

To facilitate the read and use of the (object, column, variable) name, we start with the common word of a group:
Group Name
Date DateInvoiced, DateOrdered, @DateStart, @DateEnd
Name NameFirst, NameLast, NameDisplayed
Status StatusOrder, StatusRejection, StatusActive etc

By combining the rules, we may end with names like:

Rule Variant Name


Special Characters Space [My First Table]
Exclamation mark [MyFirstTable!]
Space and quote sign [John’s Column in His First Table]
Space and dash [Sales – 1967]
Start with number [123 Store Inc. Sales]
Keyword [Table]
Case camelCase myFirstTable
PascalCase MyFirstTable
All lower myfirsttable
ALL UPPER MYFIRSTTABLE
Delimited Underscore and camelCase my_first_table
Underscore and PascalCase My_First_Table
Underscore all lower my_first_table
Underscore all upper MY_FIRST_TABLE
Prefix Not delimited and camelCase tblMyFirstTable
Delimited and camelCase tbl_My_Table
Double delimiter and PascalCase Tbl__My_First_Table

42
Naming Conventions

Suffix camelCase tblMyFirstTableArchive


camelCase and delimiter tblMyFirstTable_Archive
PascalCase and double delimiter Tbl__My_First_Table__Archive
Prefix and Suffix Single delimiter tbl_MyFirstTable_Archive
Double and single delimiter usp_ETL__Project_1234__Daily

SDSO, DSO and SO

When pointing to an object, we can use:


• Relative path - only ObjectName or
• Full path - start from a higher level and point to the object:
ServerName DatabaseName SchemaName ObjectName

As the object name is an identifier, two objects of the same type (table, view, function, stored procedure) and with
the same name can’t exist on one level. They can exist in different schemas.
Two schemas with the same name can’t exist in one DB, but can exist in different DB.s
Two DBs with the same name can’t exist on one DBE Instance, but can exist in different instances.

To point to the exact DB object, we use the full path to the object.

Level 1 Level 2 Level 3 Level 4


SDSO ServerName.DatabaseName.SchemaName.ObjectName
DSO DatabaseName.SchemaName.ObjectName
SO SchemaName.ObjectName

SDSO
SDSO Syntax FROM Clause Example
Level
Object [ObjectName] FROM [MyTable]
FROM [vw_MyView]
FROM [udf_MyFunction]
Schema [SchemaName].[ObjectName] FROM [dbo].[MyTable]
Database [DatabaseName].[SchemaName].[ObjectName] FROM [MyDatabase].[mySchema].[vw_MyView]
Server [ServerName].[DatabaseName].[SchemaName].[ObjectName] FROM [SQLServer1].[Client173].[dbo].[usp_ETL_
Daily]

Using the DatabaseName.SchemaName.ObjectName structure is making our code more robust and able to be
executed in different databases and schemas on the same server.

43
Naming Conventions

Server 1

Database 1 Database 2

Schema 1 Schema 1

Table 1 Table 1

Table 2 Table 2

Stored Procedure 1
Schema 2

Table 1
Schema 2

Table 1
copy

Table 2

Table 2 Stored Procedure 1 Copy

When we use the DSO structure, we can copy [Database 1].[Schema 1].[Stored Procedure 1] that queries
[Database 1].[Schema 2].[Table 2] to [Database 2].[Schema 2].[Stored Procedure 1 Copy] and the
copied stored procedure will continue to query the same object - [Database 1].[Schema 2].[Table 2].

44
SQL and T-SQL (Transact-SQL)

SQL is an abbreviation of Structured Query Language. This is the programming language that communicates with
the DB.

SQL is Structured because it is built by batches that hold statements which hold clauses in an organized structure.

Code, executed on one run


One step of the code
Command that builds the step T-SQL keyword
Batch Statement Clause SELECT
(Select query)
Clause FROM
Clause WHERE
Statement Clause UPDATE
(Modify query)
Clause SET
Clause WHERE

SQL runs Queries to retrieve data from the DB (Select queries). The select queries can be understood as ques-
tions. We ask the DB for specific data and it gives us back an answer (the data, organized in recordset). We may
also ask (query) the DB to add, modify or delete existing data (Modify queries).

One set of queries creates, Another set of queries creates,


reads, updates and deletes data. edits and deletes DB objects.
Manipulates T-SQL Manipulates T-SQL
Query type Query type
what clause what clause
Existing data SELECT Select queries New object CREATE Create queries
UPDATE Modify queries Existing object ALTER Modify queries
DELETE TRUNCATE
New data INSERT DROP

SQL is Language, because it is a programming language.

SQL is standardized by the American National Standards Institute (ANSI) and the International Organization for
Standardization (ISO). The standards are revised continuously to cover new technologies and improve the function-
ality of the databases. The revisions have names like SQL-92, SQL:2003 or SQL:2008.

As different RDBMSs are created by different companies, the SQL has specific extensions that serve the specific
software. The extended SQL respects the standards defined by ANSI and ISO (ANSI SQL). The extended SQL in Mic-
rosoft® SQL Server® is called T-SQL or Transact-SQL.

45
SQL and T-SQL (Transact-SQL)

Some of the most popular database software and the extended SQL:

Vendor DB Software Extended SQL


Microsoft® Corporation SQL Server® T-SQL (Transact SQL)
Oracle Corporation Oracle PL/SQL (Procedural Language/SQL)
IBM DB2 SQL PL (SQL Procedural Language)
SAP Sybase Watcom-SQL
MySQL AB MySQL SQL/PSM (SQL/Persistent Stored Module)

This book is dedicated to Microsoft® SQL Server® and T-SQL.

46
T-SQL Elements

Code Elements

The structure of T-SQL code is hierarchical and contains different elements:


1. Batch
2. Statement (a.k.a. Query, Command)
3. Clause
Batch
SELECT
FirstName
, LastName
FROM Customers
WHERE FirstName = 'Beverly';

UPDATE Customers Statement


SET Email = 'b@BeverlyWebsite.com'
WHERE Clause
FirstName = 'Beverly'
AND LastName IS NULL;
GO

Batch

SQL code, grouping one or multiple statements (commands) and executing on one run.
In SSMS we use the keyword GO to separate the batches, customizable in Tools Options Query Execution
SQL Server® General:

47
T-SQL Elements

GO is not a T-SQL command. It is not relevant outside SSMS.

The GO batch separator sends the batch to the DBE for execution.
GO 50 will send the batch 50 times to the DBE (will be executed 50 times).

Statement

SQL Code that executes a command to:


• Create, alter or delete DB object (Data Definition Language (DDL))
• Manipulate data (Data Manipulation Language (DML)

The statements in a batch are executed consecutively and synchronously - the execution of a second statement will
start after the execution of the first statement executed has finished.

Execution Step Statement 1 Statement 2 Statement 3


Time Start End Start End Start End
10:00:02 AM 10:02:14 AM 10:02:14 AM 10:05:56 AM 10:05:56 AM 10:06:06 AM

Every statement starts with a keyword (CREATE, ALTER, DROP, SELECT, INSERT, UPDATE, DELETE, etc.) and terminates
with a semicolon (;).

Clause

Component that constructs the statement, structured in exact order. There can be one or multiple in a statement
and define the type of the statement (DDL or DML).

Keyword

Words, existing in the English language, used in T-SQL to construct the clauses and statements. They are defined
by ANSI and ISO and modified in T-SQL to extend the functionality of the SQL Server®. Keywords are reserved in
T-SQL, so it is not good practice to use them as identifiers. When we use a keyword as an identifier, we surround it
in square brackets ([]).

Keywords are not case sensitive. CREATE TABLE and create table will be executed the same way, but we
write the keywords in upper case to make them stand out to distinguish them from the identifiers (names of
DB objects, column names in table etc.) and the values.

They are self explanatory and:


• USE selects the DB to be used
• CREATE creates a new object
• ALTER alters the structure of an object
• DROP drops existing object

48
T-SQL Elements

• SELECT selects data from an object


• FROM points to the object that we query from, etc

The keywords constitute the T-SQL grammar.

Identifier

SELECT
FirstName Identifier (column name)
, LastName
FROM Customers Identifier (table name)
WHERE FirstName = 'Beverly';

UPDATE Customers
SET Email = 'b@BeverlyWebsite.com'
WHERE
FirstName = 'Beverly'
AND LastName IS NULL;
GO

The names of:


• DB
• DB objects
• Table or view columns
are their identifiers in SQL.

They are:
• Required (created by the DB architect) or
• Optional (automatically generated by SQL Server®)

Identifiers are collected in the Object Catalog and can be queried to perform a task such as to bulk update some
value in all string columns in multiple tables.

The case sensitivity of the identifiers is defined by the SQL Server® Collation.

Collation on page 66

49
T-SQL Elements

Data Type

When we create or alter DB object, we specify the data type that table column, variable, function parameter or
stored procedure parameter can store. One table column can store strings, whilst another can store numbers, dates
and times or booleans.
ALTER TABLE Customers
CREATE TABLE Customers ADD Phone VARCHAR(16);
( GO
FirstName NVARCHAR(32)
, LastName NVARCHAR(64) Data Type
, Email VARCHAR(128)
);
DECLARE @MyVariable INT;
GO
GO
CREATE and ALTER DDL statements specify the data type that table column can store or the data type of SP or FN
parameters. When we create (DECLARE) a variable, we also specify the data type that the variable can
store.

SELECT ('MyString' + 3) AS Expression; The data type of the elements has to be the same in order the
GO expression to be evaluated correctly.
String Integer
Conversion failed when converting the varchar value
'MyString' to data type int.

Expression Data Types and Conversions on page 53

Combined values and operators that evaluates to a single value.

SELECT ('John' + ' ' + 'Smith'); Evaluates to John Smith


SELECT (TotalSales * 2); If TotalSales = 1234, evaluates to 2468 (1234 * 2)
The data type of the expression is the same as
Value Expresion the data types of its components.
Operator
SELECT
CustomerID Arithmetic
Operator , ItemID
, (TotalSales * 1.3) AS ExpandedSales
Keywords or character that perform FROM Sales
comparison, arithmetic, logical or negate WHERE
Comparison
operation. Logical
ItemID = 2
AND DateOfSale IN ('1967-04-15', '1967-05-17')
AND NOT IsCorrection = 0;
GO Negate

50
T-SQL Elements

Value

Value is the amount of the data.


• In expressions:
• (1.3 + 17)
• ('John' + ' ' + 'Smith')
• In operators: FirstName = 1.3
• In identifier or variable, transformed to value during the execution:
• In expression that contains identifier: (SalesTotal * 1.3)
• In expression that contains identifier and variable: (SalesTotal * @Multiplier)

SELECT The value, stored


CustomerID in the variable
, ItemID
, (TotalSales * 1.3) AS ExpandedSales
The value, stored
FROM Sales in the column
WHERE Value

ItemID = 2
AND DateOfSale IN ('1967-04-15', '1967-05-17')
AND NOT IsCorrection = 0;
GO

Alias

Name, defined in the code that replaces SELECT


the name of identifier. Customizes and CONCAT(C.FirstName, ' ', C.LastName) AS CustomerName
facilitates the usage of the identifiers. , I.ItemCode
, (S.TotalSales * 1.3) AS [Expanded Sales]
FROM
Sales AS S
JOIN Customers AS C Assign Alias (AS)
ON S.CustomerID = C.CustomerID
JOIN Items AS I
Aliases on page 138
ON S.ItemID = I.ItemID
WHERE
I.ItemID = 2 Bind Alias (.)
AND S.DateOfSale IN ('1967-04-15', '1967-05-17')
AND NOT S.IsCorrection = 0;
GO

51
T-SQL Elements

Square Bracket

We surround the identifier that holds a special characters (not letter or number) in square brackets. Else they are
not valid in T-SQL and we can’t execute the batch.
SELECT [First!Name] AS [First Name]
FROM [Approved?Customers];
GO Special Character

Literal value

Literal is exact constant value.


SELECT
Literal decimal value
CustomerID
, ItemID
In T-SQL the letter N before the left single quote
, (TotalSales * 1.3) AS ExpandedSales
of a string, defines the string as Unicode.
FROM Sales Literal string value
WHERE
'This is My String' Character string
ItemID = 2
N'これは私の文字列であります'
AND DateOfSale IN ('1967-04-15', '1967-05-17')
AND NOT IsCorrection = 0;
GO Literal integer value Unicode character string

Variable

The variable is place in the memory of the computer where we store value that can vary.
Variable
DECLARE Create variable and
@ItemID NVARCHAR(64) = 2 assign a value to it
, @DateStart DATE = '1967-04-15'
, @DateEnd DATE = '1967-05-17';

SELECT
CustomerID The value of @DateEnd is '1967-05-17'
, ItemID
, (TotalSales * 1.3) AS ExpandedSales
FROM Sales
Use the variable
WHERE
ItemID = @ItemID
AND DateOfSale IN (@DateStart, @DateEnd)
AND NOT IsCorrection = 0;

52
T-SQL Elements

SET @ItemID = 3; Change the value of the variable

Now the value of @ItemID


SELECT @ItemID AS Result;
is 3
GO

Delimiter

The delimiter: To delimit We use Glyph


• Shows where one portion of the code ends Identifiers Comma ,
• List identifiers Statements Semicolon ;
Batches Keyword GO

SELECT
FirstName
, LastName
, Email
FROM Customers
WHERE FirstName = 'Beverly';
Identifier delimiter

UPDATE Customers
SET Email = 'b@BeverlyWebsite.com'
WHERE
FirstName = 'Beverly'
AND LastName IS NULL; Statement delimiter
GO Batch delimiter

Syntax

Every programming language is following rules, called syntax. The above rules define the SQL syntax.

53
DDL and DML Statements

SQL statements work with:


• DB objects
• Data

The statements that create, alter or delete DB objects are grouped as Data Definition Language (DDL). The state-
ments that query or manipulate the data are called Data Manipulation language (DML).

DDL

We can recognize the DDL Statements by their first clause - CREATE, ALTER, TRUNCATE, DROP.

Clause Apply On Description


CREATE Database, schema, table, view, function, stored procedure Create new DB or DB object
ALTER Database, schema, table, view, function, stored procedure Edit existing DB or DB object
TRUNCATE Table Delete all the data in a table and resets the identity
column
DROP Database, schema, table, view, function, stored procedure Delete existing DB or DB object

DML

The DML statements manipulate the data in the DB and their first clause are INSERT, SELECT, UPDATE, DELETE

Clause Apply On Description


INSERT Table, view Insert new row
SELECT Table, view, table-valued function Select existing data
UPDATE Table, view Update existing data
DELETE Table, view Delete existing data

54
CRUD

CRUD is an abbreviation of the four basic actions that we perform in the DB - Create, Read, Update and Delete.
We apply CRUD actions on DDL and DML as follows:

DDL DML
Abbreviation Clause Action Clause Action
Create CREATE, TRUNCATE Create object INSERT Create row in a table

Read SELECT Select data from a table

Update ALTER Change the definition of an object UPDATE Update data in a table

Delete DROP, TRUNCATE Delete object DELETE Delete row from a table

The DDL TRUNCATE statement is logically a DML statement, because it deletes all the data in a table, but not the ta-
ble itself. It is in the DDL group, because it changes the object (resets the identity column). It is a variation of DROP
and CREATE statements together.

55
Data Types and Conversions

The values of:


• Columns in the DB tables
• Variables
• Parameters in functions or stored procedures
• Elements of the expressions
can be a specified data type.

Table columns

Customers
CustomerID FirstName LastName Email YTDSales DateRegistered TimeRegistered
1 John Smith jsmith@js1.org 523.43 1967-03-25 17:36:55.2030000
2 Anna Larson larsona@al2.com 8542.12 1968-12-14 03:21:28.7470000
3 Michelle Boyer mb@customer3.net 85.59 1965-07-18 13:47:00.8130000

Column Data type Can store only


CustomerID INT Whole numbers (integers)
FirstName NVARCHAR(32) Strings of maximum 32, characters
LastName NVARCHAR(64) Strings of maximum 64 characters
Email VARCHAR(128) Strings of maximum 128 characters
YTDSales MONEY Numbers (money)
DateRegistered DATE Dates (year, month, day)
TimeRegistered TIME Time (hour, minute, second, millisecond)

Variable

DECLARE @MyIntVariable INT;


SET @MyIntVariable = 123; @MyIntVariable can store only whole numbers
SET @MyIntVariable = 'ABC'; We can’t assign the string value 'ABC'

Parameter in Function or Stored Procedure Conversion failed when converting the


varchar value 'ABC' to data type int.
CREATE FUNCTION udf_MyFunction
(
Accepts only whole numbers
@MyFunctionParameter1 INT
Accepts only date and time
, @MyFunctionParameter2 DATETIME2
)...

Expression

SELECT 'My String ' + CAST(3 AS CHAR(1)) AS MyExpression;


MyExpression
My String 3
String Integer, converted to string

56
Data Types and Conversions

The data types in SQL Server® are grouped as follows:


Numerics BINARY
Exact VARBINARY
Integer - TINYINT, SMALLINT, INT, BIGINT IMAGE - Obsolete. Replaced with BINARY(MAX)
Fraction - NUMERIC, DECIMAL (DEC) Date and time
Monetary, currency - MONEY, SMALLMONEY SMALLDATETIME, DATETIME, DATETIME2
Boolean - BIT DATE
Approximate TIME
Floating point - FLOAT, REAL DATETIMEOFFSET

Strings Other
Character Non-Unicode UNIQUEIDENTIFIER
CHAR TIMESTAMP - Obsolete. Replaced with ROWVERSION.
VARCHAR XML
TEXT - Obsolete. Replaced with VARCHAR(MAX) SQL_VARIANT
Character Unicode TABLE
NCHAR HIYERERARCHYID
NVARCHAR CURSOR
NTEXT - Obsolete. Replaced with NVARCHAR(- Spacial
MAX) GEOGRAPHY
GEOMETRY
Binary

Numerics - Exact

Integer (whole number)


Storage Size
Name Range (From) Range (To)
(bytes)
TINYINT 1 0 255
SMALLINT 2 -215 -32,768 215 - 1 32,767
INT 4 -231 -2,147,483,648 231 - 1 2,147,483,647
63
BIGINT 8 -2 -9,223,372,036,854,775,808 263 -1 9,223,372,036,854,775,807

Fraction
Precision:
Name Storage Size Range (From) Range (To)
(bytes)
DECIMAL(P, S) 1 to 9: 5 -1038 + 1 1038 - 1
DEC 10 to 19: 9
NUMERIC(P, S) 20 to 28: 13
29 to 38: 17

57
Data Types and Conversions

P - Precision: Total digits (including the decimal point) - 1 to 38; Default is 18


S - Scale: Digits after the decimal point - 0 to 37; Default is 0
The Position of the digits and the decimal point
Thousands

Thousands

Thousands

Thousands

Thousands

Thousands
Hundreds

Hundreds
Hundred

Hundred
Millions

Millions

Millions
Ones
Tens

Tens

Tens

Tens

Tens
1,000,000

100,000

10,000

1,000

100

10

10

100

1,000

10,000

100,000

1,000,000

10,000,000
7 6 5 4 3 2 1 . 1 2 3 4 5 6 7
Decimal

Position
Whole Number Decimal Fraction
Point

Monetary, Currency
Storage Size
Name Range (From) Range (To)
(bytes)
MONEY 8 -922,337,203,685,477.5808 922,337,203,685,477.5807
SMALLMONEY 4 -214,748.3648 214,748.3647

Boolean
BIT columns per table:
Name Range (From) Range (To)
Storage Size (bytes)
BIT 1 to 8: 1 0 1
9 to 16: 2 False True
17 to 24: 3 No Yes
25 to 32: 4
...

Numerics - Approximate

Floating Point Number


Storage
Precision Range 1 Range 1 Range 2 Range 2
Name N (value) Size Zero
(digits) (From) (To) (From) (To)
(bytes)
FLOAT(N) 1 to 24 4 7 -3.40e38 -1.18e-38 0 1.18e-38 3.40e38
N is 1 to 24: 24
25 to 53 8 15 -1.79e308 -2.23e-308 0 2.23e-308 1.79e308
N is 25 to 53: 53
Default: 53
REAL * 4 7 -3.40e38 -1.18e-38 0 1.18e-38 3.40e38

* REAL is an equivalent of FLOAT(24)

58
Data Types and Conversions

Strings

Character Non-Unicode

Number of Number of
Name Storage Size Characters Characters
(From) (To)
CHAR(N) 1 byte per N 1 8,000
VARCHAR(N) N is 1 to 8000: 1 byte per N + 2 bytes 1 8,000
N is MAX: up to 231 - 1 bytes (2,147,483,647 bytes or 2 GB)

Character Unicode

Number of Number of
Name Storage Size Characters Characters
(From) (To)
NCHAR(N) 2 bytes per N 1 4,000
NVARCHAR(N) N is 1 to 4000: 2 byte per N + 2 bytes 1 4,000
N is MAX: up to 231 - 1 bytes (2,147,483,647 bytes or 2 GB)

Binary

Number of Number of
Name Storage Size (bytes) Characters Characters
(From) (To)
BINARY(N) 1 byte per N 1 8,000
VARBINARY(N) N is 1 to 8000: 1 byte per N + 2 bytes 1 8,000
N is MAX: up to 231 - 1 bytes (2,147,483,647 bytes or 2 GB)

N - number of characters

59
Data Types and Conversions

Date and Time

Date and time in Gregorian calendar

N: Storage
Preci-
Name Rande (From) Rande (To) Size Format Note
sion
(bytes)
DATE 1 0001-01-01 9999-12-31 YYYY-MM-DD
day
TIME(N) 100 00:00:00.0000000 23:59:59.9999999 0 to 2: 3 HH:MI:SS.fffffff
ns 3 to 4: 4
5 to 7: 5
Not specified: 5
DATETIME 3.33 1753-01-01 9999-12-31 8 YYYY-MM-DD The last decimal frac-
ms 00:00:00.000 23:59:59.997 HH:MI:SS.fff tion digit is rounded
to 0, 3, 7
DATETIME2(N) 100 0001-01-01 9999-12-31 0 to 2: 6 YYYY-MM-DD
ns 00:00:00.0000000 23:59:59.9999999 3 to 4: 7 HH:MI:SS.fffffff
5 to 7: 8
Not specified: 8
SMALLDATETIME 1 1900-01-01 2079-06-06 4 YYYY-MM-DD SS is always 00
min 00:00:00 23:59:59:00 HH:MI:SS 29 seconds - rounded
down
30 seconds - ronded
up
DATETIMEOFFSET 100 0001-01-01 9999-12-31 0 to 2: 8 YYYY-MM-DD Time zone offset
ns 00:00:00.0000000 23:59:59.9999999 3 to 4: 9 HH:MI:SS.fffffff Range: -14:00 -
5 to 7: 10 (- or +) HH:MI +14:00
Not specified: 10

N - number of digits to present the fraction of the second (0-7)

YYYY - 4-digits year


MM - 2-digits month
DD - 2-digits day
HH - 2-digits hour
MI - 2-digits minute
SS - 2-digits second
f - decimal fraction for seconds

60
Data Types and Conversions

Precision examples
TIME TIME(0) TIME(1) TIME(2) TIME(3) TIME(4) TIME(5)
14:10:36.7718695 14:10:37 14:10:36.8 14:10:36.77 14:10:36.772 14:10:36.7719 14:10:36.77187

TIME(6) TIME(7)
14:10:36.771870 14:10:36.7718695

DATETIME2 DATETIME2(0) DATETIME2(1) DATETIME2(2) DATETIME2(3) DATETIME2(4) DATETIME2(5)


1967-05-12 1967-05-12 1967-05-12 1967-05-12 1967-05-12 1967-05-12 1967-05-12
16:19:53.4739587 16:19:53 16:19:53.5 16:19:53.47 16:19:53.474 16:19:53.4740 16:19:53.47396

DATETIME2(6) DATETIME2(7)
1967-05-12 1967-05-12
16:19:53.473959 16:19:53.4739587

DATETIMEOFFSET DATETIMEOFFSET(0) DATETIMEOFFSET(1) DATETIMEOFFSET(2) DATETIMEOFFSET(3) DATETIMEOFFSET(4)


1967-05-12 1967-05-12 1967-05-12 1967-05-12 1967-05-12 1967-05-12
16:35:10.8617596 16:35:11 +02:00 16:35:10.9 16:35:10.86 16:35:10.862 16:35:10.8618
+02:00 +02:00 +02:00 +02:00 +02:00

DATETIMEOFFSET(5) DATETIMEOFFSET(6) DATETIMEOFFSET(7)


1967-05-12 1967-05-12 1967-05-12
16:35:10.86176 16:35:10.861760 16:35:10.8617596
+02:00 +02:00 +02:00

Conversion - Implicit and explicit

When we build expression or compare values, we need to have the same data type for the elements of the expres-
sion and the compared values.

Implicit conversion is the conversion that the SQL Server® is doing automatically in the background without no-
tifying that it is being made. It converts compatible (String and convertible to String, Numeric and convertible to
Numeric and so on) data types.

Explicit conversion is what we do with built-in functions:


• CAST() or TRY_CAST()
• CONVERT() or TRY_CONVERT()
• PARSE() or TRY_PARSE()
• STR()

61
Data Types and Conversions

SELECT CAST('03.4.1965a' AS DATE)


SELECT TRY_CAST('03.4.1965a' AS DATE)

SELECT CONVERT(DATE, '03.4.1965a') Error message on fail


SELECT TRY_CONVERT(DATE, '03.4.1965a')

(No column name)


SELECT PARSE('123a' AS INT)
NULL on fail > 123<
SELECT TRY_PARSE('123a' AS INT)
(No column name)
SELECT '>' + STR(123) + '<' 123.5
SELECT STR(123.45, 6, 1);
SELECT STR(123.45, 2, 2); (No column name)

SELECT STR (FLOOR(123.45), 8, 3); **

(No column name)


123.000

Expression
Implicit conversion
SELECT '3' + 3 AS Expression; Expression
GO Number 6
String

Explicit conversion
SELECT 'My String ' + CONVERT(CHAR(1), 3) AS Expression; Expression
GO My String 3
String Numeric, converted to String

Comparison
Implicit conversion
SELECT *
Date
FROM Sales
WHERE DateOfSale = '1968-09-15';
GO String

Explicit conversion
SELECT * Numeric, converted to String
FROM Sales
WHERE (MonthLiteral + ' ' + CAST(YearFiscal AS VARCHAR(9))) = 'September 1968';
GO
String
String

62
Data Types and Conversions

The VALUES clause creates a VR. This VR respects the Data Type Precedence (on page 64)

Clarification:
INSERT DatabaseName.SchemaName.TableName
(
ColumnString
, ColumnNumeric
)
VALUES
(1, 2) Numeric, Numeric
, ('3', 4) String, Numeric
, ('D', '5'); String, String
GO

In the first row 1 is Numeric. The data type of the first column will be defined as INT.
In the second row, the string '3' is implicitly converted to INT.
As the data type of the first column is defined as INT, the string 'D' can’t be implicitly converted to INT.
The same is valid for any VR.

INSERT DatabaseName.SchemaName.TableName
(
ColumnString
, ColumnNumeric
)
SELECT 1, 2 Numeric, Numeric
UNION ALL SELECT '3', 4 String, Numeric
UNION ALL SELECT 'D', '5'; String, String
GO

It is good practice to always use ISO 8601 format for dates:

Date ISO 8601


YYYY-MM-DD

YYYY – Year (4 digits)


MM – Month (4 digits)
DD - Day (4 digits)

Date
SELECT CAST('08/31/1965 14:11:56.3506429' AS DATE) AS [Date];
1965-08-31
GO

63
Data Types and Conversions

Time ISO 8601


HH:mm:ss.fffffff

HH – hours (24-hour clock)


m - minutes (2 digits)
s – seconds (2 digits)
f – fraction of the second (3 to 7)

Time
SELECT CAST('08/31/1965 14:11:56.3506429' AS TIME) AS [Time];
14:11:56.3506429
GO

Date and Time SQL Server®


YYYY-MM-DD HH:mm:ss.fffffff
Time
SELECT CAST('08/31/1965 14:11:56.350' AS DATETIME) AS [Time];
1965-08-31 14:11:56.350
GO

SELECT CAST('08/31/1965 14:11:56.3506429' AS DATETIME2) AS [Time];


GO
Time
1965-08-31 14:11:56.3506429

Data Type Precedence

When the DB converts implicitly, it converts data with a lower precedence data type to one with higher precedence.

The hierarchy is:

User-defined Data Types Numeric


Other Approximate Numeric
SQL_VARIANT FLOAT
XML REAL

Date and Time Exact Numeric


DATETIMEOFFSET NUMERIC
DATETIME2 DECIMAL
DATETIME MONEY
SMALLDATETIME SMALLMONEY
DATE BIGINT
TIME INT

64
Data Types and Conversions

SMALLINT NVARCHAR
TINYINT NCHAR
BIT
Character
Other VARCHAR
TIMESTAMP CHAR
UNIQUEIDENTIFIER
Binary
String VARBINARY
Unicode BINARY

Numeric and String - can be implicitly converted to Numeric


When we concatenate INT and NVARCHAR (can be implicitly converted to INT), the result is INT

SELECT 12 + '345' AS Result; 357 (12 + 345)


SELECT 12 + 'CDE' AS Result; Conversion failed when converting the
CHAR value 'CDE' to data type INT
Numeric and Date and Time (Implicitly converted to Date and Time):

The built-in function GETDATE() returns now (now is 1968-08-13 15:10:29.017)


SELECT 12 + GETDATE();
The result is 1968-08-25 15:10:29.017 (12 days added)

65
Collations

Collation

The way the:


• Server
• DB
• Column (table or view)
stores and represents string data type is specific for language (Japanese or Russian) or alphabet (Latin, Cyrillic or
Indic). Collation is a set (code page) of characters, used in a language or alphabet.

SQL Server® collation can be applied to:


• Server
• DB
• Table or view column
levels.

We define the collation in DDL (create or alter object) and in DML statements (select, join or filter the data).

Server Level
Check all the collations on the server name
SELECT *
Albanian_BIN Albanian, binary sort
FROM sys.fn_helpcollations();
Albanian_BIN2 Albanian, binary code
GO point comparison sort
... ...
SELECT SERVERPROPERTY('Collation') AS ServerCollation;
GO
Check the current collation on the server
ServerCollation
DB level SQL_Latin1_General_CP1_CI_AS

Create DB with specified collation


CREATE DATABASE Collations
COLLATE Korean_Wansung_CS_AI_WS;
GO

SELECT name, collation_name


name collation_name
FROM Collations.sys.databases
Collations Korean_Wansung_CS_AI_WS
WHERE name = N'Collations';
GO
Check the collation of a specified database
USE Collations;
SELECT DATABASEPROPERTYEX('Collations', 'Collation') AS Collation;
GO

66
Collations

Column level

USE Collations;
GO

CREATE TABLE CollationsTable Specified collation for column FirstName


(
FirstName NVARCHAR(32) COLLATE Japanese_CS_AI_WS
, Email VARCHAR(128)
); Column Email uses the default collation of
the DB (Korean_Wansung_CS_AI_WS)
GO

SELECT Check column collation

[name] AS ColumnName
, [collation_name] AS ColumnCollation ColumnName ColumnCollation
FROM Collations.sys.columns FirstName Japanese_CS_AI_WS
WHERE Object name Email Korean_Wansung_CS_AI_WS
OBJECT_NAME([object_id]) = 'CollationsTable'
--AND [name] = N'Email'; Uncomment to filter exact column
GO

Overwrite the collation

The keyword COLLATE, added to column identifier, switches the collation of the column:

In the SELECT clause

SELECT ColumnName COLLATE Latin1_General_CI_AI


FROM MyTable;
GO

In the FROM clause

SELECT
T1.Column1
, T2.Column2 T2.Column1 is not Latin1_General_CI_AI
FROM and we need to equalize the collations
Table1 AS T1 in order to join the objects
JOIN Table2 AS T2
ON T1.Column1 = T2.Column1 COLLATE Latin1_General_CI_AI
GO
T1.Column1 is Latin1_General_CI_AI

67
Collations

In the WHERE clause

SELECT
T1.Column1
, T1.Column2
FROM
Table1 AS T1
JOIN Table2 AS T2 Equalize the collations to apply to filter condition
ON T1.Column1 = T2.Column1
WHERE T1.Column1 = T2.Column1 COLLATE Latin1_General_CI_AI;
GO

! When we UNION recordsets, the collation of the matching columns have to be the same

UNION on page 172

The flags in the collation name explain the properties of the collation:

Case Sensitivity
• CI - Case Insensitive Code Page CP(X) (X is between1 and 4)
• CS - Case Sensitive • CP1 - Code page 1252 (Latin1 (ANSI))
• CP1251 - Code Page 1251 (Cyrillic)
Accent Sensitivity • CP949 - Code page 949 (Korean)
• AI - Accent Insensitive
• AS - Accent Sensitive Binary
• BIN - older BIN collations
Kana (Japanese kana characters) • BIN2 - newer BIN2 collations
• KI - Kana Insensitive
• KS - Kana Sensitive Unicode
• CS - Supplementary Characters
Width
• WI - Width Insensitive
• WS - Width Sensitive

Collation with name SQL_Latin1_General_CP1_CI_AS is:


• CP1 - Code Page 1252
• CI - Case Insensitive
• AS - Accent sensitive

68
NULL and 3VL (Three-valued Logic)

Based on the standard ISO 9075-1: 2011, NULL is a “Special value that is used to indicate the absence of any data
value”.
When we add new nullable column to an existing table, it stores NULL, before we insert values.
When we create (DECLARE) a new variable, its value is NULL, i.e. a value is missing.
NULL is a marker that shows an absence of data.
NULL is not an empty string, space, multiple spaces or 0 (zero), all of which are actually values. It is an absence of
value - unknown or nothing.

The 3VL (Three-value Logic) explains that the NULL marker or placeholder can’t result in True or False.
The result of comparison with NULL is unknown.
This is why we compare NULL in a table column or variable with the special keyword IS (IS NULL, IS NOT NULL) and
manipulate NULL separately from the other values.
The question should we use or not use NULL in the DB architecture and development is not explicit. Both options
have their pros and cons.

The following example explains 3VL

Customers If we need to select the rows, where the last name


FirstName LastName doesn’t start with Smi, the returned recordset will be:
John Smith
FirstName LastName
Anna NULL
Melanie Johnson
Deborah Smilesh
Melanie Johnson

SELECT *
FROM dbo.Customers
WHERE LastName NOT LIKE 'Smi%';
GO

The result above is correct, because when we compare column LastName to Smi*, the DBE asks the question “Col-
umn LastName on this row begins with Smi?” and returns the rows, corresponding to answer Yes (True):

FirstName LastName LastName begins with 'Smi'? Question


John Smith False
Anna NULL Unknown
Answer
Deborah Smilesh False
Melanie Johnson True

To select the rows that we need, we need to select the rows where “LastName doesn’t start with Smi or LastName
is unknown?”:

69
NULL and 3VL (Three-valued Logic)

SELECT *
FROM dbo.Customers
WHERE
FirstName LastName
(
Anna NULL
LastName NOT LIKE 'Smi%'
Melanie Johnson
OR LastName IS NULL
);
GO

We use built-in functions to replace NULL with a real value and vice versa:
• ISNULL(ValueToCheck, SpecifiedValue) - When ValueToCheck IS NULL SpecifiedValue is returned
• NULLIF(ValueToCheck, SpecifiedValue) - When ValueToCheck is equal to SpecifiedValue, NULL is
returned

The built-in function COALESCE returns the first not NULL value from the specified values:
• COALESCE(ValueToCheck1, ValueToCheck2, ValueToCheck3, 'N/A') - returns the first not NULL
value and N/A if all the precedent values are NULL. If all the specified values are NULL, NULL is returned.

NULL can’t be used to join objects


! (table, view, table-valued function).
JOIN on page 128

Special attention has to be paid to NULL, because knowing


its logic helps us to create more robust and stable code.

70
Operators

Operators

Characters or reserved words acting on operands.

Left Right Operator


Operator Left Operand Type Right Operand Type
Operand Operand Category
19 + 8 Arithmetic Value Value
Age > 18 Comparison Table cell Value
@Age = 18 Assignment Variable Value
(19 > 8) AND (Age > 18) Logical Expression (Value, Value) Expression (Table cell, Value)
@FirstName + 'Smith' Concatenation Variable String

The operators are:

Arithmetic

Does mathematical operation. Both operands have to be a numeric data type.


Operator Name Example Description Result
+ Addition 19 + 8 Adds 8 to 19 27
- Subtraction 19 - 8 Subtracts 8 from 19 11
* Multiplication 19 * 8 Multiplies 19 by 8 152
/ Division 19 / 8 Divides 19 by 8 2.375
% Modulo 19 % 8 Divides 19 by 8 and returns the remainder 3

8 - operand
Comparison (Relational) 19 - operand

Compares the values of the operands and returns true, false or unknown (boolean operator). The boolean opera-
tors test the truth or falsity of the condition.
When the values of the operands can’t be implicitly converted, we convert them with the built-in functions CAST(),
TRY_CAST(), CONVERT(), TRY_CONVERT(), PARSE(), TRY_PARSE(), STR().
Operator True If Compares if the left operand Result
= 19 = 8 equals False
Data Types and Con-
<> 19 <> 8 doesn’t equal True
!= 19 != 8 versions on page 56
> 19 > 8 is greater than True
< 19 < 8 is less than False
>= 19 >= 8 is greater than or equal to True
! reverses the meaning of the operator.
!< 19 !< 8 isn’t less than Not ISO standard.
<= 19 <= 8 is less than or equal to False
!> 19 !> 8 isn’t greater than
the right operand

71
Operators

Assignment

Sets a value to the table cell or variable.

Current New
Operator Name Description Example
Value Value
= Assignment Overwrites the current value. UPDATE @Table1 123456 8
SET Column1 = 8;
SELECT @Variable1 = 8;
+= Addition Adds 8 to the current value and assigns the result UPDATE @Table1 19 27
SET Column1 += 8;
SELECT @Variable1 += 8;
-= Subtraction Substracts 8 from the current value and assigns UPDATE @Table1 19 11
the result SET Column1 -= 8;
SELECT @Variable1 -= 8;
*= Multiplication Multiplies the current value by 8 and assigns the UPDATE @Table1 19 152
result SET Column1 *= 8;
SELECT @Variable1 *= 8;
/= Division Divides the current value by 8 and assigns the UPDATE @Table1 19 2.375
result SET Column1 /= 8;
SELECT @Variable1 /= 8;
%= Modulo Divides the current value by 8 and assigns the UPDATE @Table1 19 3
remainder SET Column1 %= 8;
SELECT @Variable1 %= 8;

Logical

Tests the truth of a condition, applied on the operands (boolean operator).

NOT
Operator Example Result (reverses the meaning
of the operator)
AND (Age >= 18) AND (DateRegistered = '1968-05-18') True if both operands AND NOT
(expressions) return True
OR (Age >= 18) OR (DateRegistered = '1968-05-18') True if one of the operands OR NOT
(expressions) return True
EXISTS EXISTS (Subuery) True if the subquery returns at NOT EXISTS
least one row
BETWEEN Age BETWEEN 18 AND 32 True if Age is in the range NOT BETWEEN
from 18 to 32 (including)
IN Age IN (18, 22) True if Age matches any value NOT IN
in the list 18, 22
IS NULL Age IS NULL True if Age is NULL IS NOT NULL
LIKE FirstName LIKE 'Smi%' True if FirstName starts with NOT LIKE
'Smi'

72
Operators

Operator
Left Operand Right Operand AND OR
(8 = 8) (12 = 12) True True True AND True = True True OR True = True
(8 = 8) (12 = 13) False True True AND False = False True OR False = True
(8 = 9) (12 = 13) False False False AND False = False False OR False = False

Concatenation

Joins String data type operands.

Operator Name Example Description Result


+ Concatenation 'John' + ' ' + 'Smith' Joins John to space and to Smith John Smith

Operator Precedence

The order in which the operators are executed:


1. Multiplication, Division, Modulo
2. Addition, Subtraction, Concatenation
3. Comparison
4. NOT
5. AND
6. BETWEEN, IN, LIKE, OR
7. Assignment

To force the precedence, we use round brackets (()).

The execution order of the operations are:

Direction of
Order Operation T-SQL Result Explanation
the execution
1 Parentheses Left to right SELECT 7 + 6 * 5; 37 7 + 30
SELECT (7 + 6) * 5; 65 13 * 5
23
2 Exponents Right to left SELECT POWER(POWER(2, 3), 2); 64 2 = 28
(power and SELECT 7 + 6 * POWER(2, 3); 55 7 + 6 * 8 = 7 + 48
root)
SELECT - 4 + POWER(2, 3); 4 -4+8=8-4
3 Multiplication Left to right SELECT 7. / 6. * 5.; 5.833330 1.166666 * 5
and division SELECT 5. * 6. / 7.; 4.285714 30 / 7
4 Addition and Left to right SELECT 7 + 6 - 5; 8 13 - 5
subtraction SELECT 5 - 6 + 7; 6 -1+7=7-1

73
Operators

Examples:

Arithmetical Operators
DB defined precedence: Parentheses to force the precedence:
2 + 4 * 6 = 26 (2 + 4) * 6 = 36
4 * 6 = 24 + 2 = 26 2 + 4 = 6 * 6 = 36
Not 2 + 4 = 6 * 6 = 36

Logical Operators
DB defined precedence:
SELECT *
FROM dbo.Customers
WHERE
Age >= 18
OR IsEligible = 1
AND DateRegistered = '1969-12-17';
GO

Parentheses to force the precedence:


SELECT *
FROM dbo.Customers
WHERE
(
Age >= 18
OR IsEligible = 1
)
AND DateRegistered = '1969-12-17';
GO

74
SQL Server® Management Studio
(SSMS)

Until now we have defined the theory and the basics. The fun starts here :-) From now on the main tool to learn
T-SQL is SSMS (SQL Server® Management Studio).

Log in SSMS

To start SQL Server® Management Studio, click the Start menu and Type SQL Server® 2016 Management Studio
in the search box, then click on SQL Server® 2016 Management Studio in the top section.

Server type: Database Engine


Server name: Computer name of the server, where the SQL Server® is installed\Instance name
Authentication:
• Windows Authentication - uses windows credentials, i.e. the user, logged in Windows (used in the book)
• SQL Server® Authentication - uses DB logins, defined in the SQL Server®
• Active Directory Password Authentication and
• Active Directory Integrated Authentication - use the Active Directory Authentication Library (ADAL)
authentication

When login in with Windows Authentication, the text fields Login, Password and the checkbox Remember pass-
word are not active.

When we login with SQL Server® Authentication, we specify:


• User name - login name, existing in SQL Server®
• Password - password, assigned to the login name
• Remember password (checked) - SQL Server® remembers the password for this login name and we don’t
need to type it in future logins

75
SQL Server Management Studio
(SSMS)

We can use the built-in function SUSER_SNAME() to verify our login name:

SELECT SUSER_SNAME() AS LoginName;


GO

Each tab in SSMS is one session: Tab

Session ID

Returned Rows

Execution Time

SQL Server® Database against which


Version the batches in this
window (session) will
Server Name be executed
Login

On the left-hand side is the Object Explorer (DBs, Tables, Views, Functions, Stored Procedures,etc.).

The right part of the window is the area where we write our T-SQL code.

The bottom-right area is where we examine the result of the batch and the error messages (if any).

76
SQL Server Management Studio
(SSMS)

In SSMS the functions are in the database’s Programmability section:

Tables, views and stored procedures are in the database’s Programmability section:

By selecting a portion of the code and click Execute (F5), we execute only the selected portion of the code:

By selecting a keyword and pressing Shift + F1, we search Books Online for this keyword.

77
Constraints

As the name suggests, constraints constrain data. These are rules that belong to the table columns that limit the
values that we can stored in the columns.

The constraints are:

PRIMARY KEY (PK)

• A column with a PK constraint can store only unique values and can’t store NULL
• PK uniquely identifies the rows in the table
• Only one PK can exist in a table

Customers
CustomerID FirstName LastName Email
1 Anabel Larson anabel.larson@customer5.info
2 Anna Laurier anna.laurier@customer2.net
3 Beverly NULL NULL
4 John Smith john.smith@customer1.com
5 John Smith john.smith@customer3.org
PK
NOT NULL Insert NULL fail
UNIQUE Insert 3 fail (duplicate)

Composite (compound) PK
• The PK constraint is defined in multiple columns
• Neither of the columns can store NULL
• The combination of the column values define the unique value for the PK
Customers
FirstName LastName Email
Anabel Larson anabel.larson@customer5.info
Anna Laurier anna.laurier@customer2.net
John Smith john.smith@customer1.com
Melanie Larson melanie.larson@customer4.biz
Zak Smith NULL
PK
NOT NULL NOT NULL
UNIQUE
Insert NULL fail Insert NULL fail
Insert 'John' Insert 'Henderson' OK
Insert 'John' Insert 'Smith' fail (duplicate)

It is good practice to define a PK constraint in all the tables in the DB


to enforce the referential (PK - FK) integrity of the database.

78
Constraints

To view the PK in the Object Catalog:


USE LearnSQLServerIntuitively;
GO

SELECT
OBJECT_NAME([parent_object_id]) AS TableName
, [name] AS PKName
FROM sys.key_constraints
WHERE [type] = 'PK';
GO

FOREIGN KEY (FK)

• Constraints the column to store only values that exist in the referencing PK column in another table
• Ensures the referential integrity of the data

Customers
CustomerID FirstName LastName Email
1 Anabel Larson anabel.larson@customer5.info
2 Anna Laurier anna.laurier@customer2.net
3 Beverly NULL NULL
4 John Smith john.smith@customer1.com
5 John Smith john.smith@customer3.org
PK

Reference
Sales
SaleID CustomerID ItemID Quantity Prce DateOrdered
1 3 3 3 1.24 1967-10-17
2 3 5 5 2.43 1968-05-08
3 4 2 2 12.44 1965-09-04
4 3 3 7 5.88 1968-12-17
5 5 1 1 28.19 1968-03-04
PK FK
Only values,
existing in Insert 6 - fail (doesn’t exist)
the PK refer- Insert 3 - OK (exist)
ence column Insert NULL - OK

FK Referential Actions

79
Constraints

When we update or delete a value in a PK column, the FK can be automatically actioned with the following rules:
• Delete - ON DELETE
• Update - ON UPDATE

The referential actions, applied on the rules are:


• NO ACTION - Used when no action is specified (default)
• CASCADE - FK is automatically updated with the new PK value or deleted
• SET NULL - the FK is set to NULL
• SET DEFAULT - the FK is set to the DEFAULT value of the column

Rules
Referential Action Update PK Delete PK
NO ACTION Not Allowed Not Allowed
CASCADE Updates FK with PK Deletes the FK
SET NULL Updates FK to NULL Updates FK to NULL
SET DEFAULT Updates FK with the DEFAULT value Updates FK with the DEFAULT value

CASCADE (UPDATE)
Customers
CustomerID FirstName LastName Email
1 Anabel Larson anabel.larson@customer5.info
2 Anna Laurier anna.laurier@customer2.net
3 Beverly NULL NULL Update CustomerID to 6
4 John Smith john.smith@customer1.com
5 John Smith john.smith@customer3.org
PK

Reference
Sales
SaleID CustomerID ItemID Quantity Prce DateOrdered
1 3 1004 3 1.24 1967-10-17
2 1 5273 5 2.43 1968-05-08
Updates FK (CustomerID) to 6
3 4 5432 2 12.44 1965-09-04
4 3 8541 7 5.88 1968-12-17
5 5 9514 1 28.19 1968-03-04
PK FK

80
Constraints

Customers CASCADE (DELETE)


CustomerID FirstName LastName Email
1 Anabel Larson anabel.larson@customer5.info
2 Anna Laurier anna.laurier@customer2.net
3 Beverly NULL NULL DELETE CustomerID = 3
4 John Smith john.smith@customer1.com
5 John Smith john.smith@customer3.org
PK

Reference
Sales
SaleID CustomerID ItemID Quantity Prce DateOrdered
1 3 1004 3 1.24 1967-10-17
2 1 5273 5 2.43 1968-05-08 Deletes row where FK (Custo-
3 4 5432 2 12.44 1965-09-04 merID) = 3
4 3 8541 7 5.88 1968-12-17
5 5 9514 1 28.19 1968-03-04
PK FK

Customers SET NULL (UPDATE, DELETE)


CustomerID FirstName LastName Email
1 Anabel Larson anabel.larson@customer5.info
2 Anna Laurier anna.laurier@customer2.net
Update CustomerID
3 Beverly NULL NULL
to 6 or delete
4 John Smith john.smith@customer1.com
5 John Smith john.smith@customer3.org
PK

Reference
Sales
SaleID CustomerID ItemID Quantity Prce DateOrdered
1 3 1004 3 1.24 1967-10-17
2 1 5273 5 2.43 1968-05-08 Updates FK (CustomerID)
3 4 5432 2 12.44 1965-09-04 to NULL
4 3 8541 7 5.88 1968-12-17
5 5 9514 1 28.19 1968-03-04
PK FK

81
Constraints

Customers SET DEFAULT (UPDATE, DELETE)


CustomerID FirstName LastName Email
1 Anabel Larson anabel.larson@customer5.info
2 Anna Laurier anna.laurier@customer2.net
Update CustomerID to
3 Beverly NULL NULL
6 or delete
4 John Smith john.smith@customer1.com
5 John Smith john.smith@customer3.org
PK

Reference
Sales
SaleID CustomerID ItemID Quantity Prce DateOrdered
1 3 1004 3 1.24 1967-10-17
2 1 5273 5 2.43 1968-05-08 Updates FK (CustomerID) to the
3 4 5432 2 12.44 1965-09-04 DEFAULT value of the column
4 3 8541 7 5.88 1968-12-17
5 5 9514 1 28.19 1968-03-04
PK FK

To view the FK in the Object Catalog:


SELECT
OBJECT_NAME([parent_object_id]) AS TableName
, [name] AS FKName
FROM sys.foreign_keys;
GO

To view the Referential Actions in the Object Catalog:


SELECT *
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS;
GO

DEFAULT (DF)

When a row is inserted and no value is specified for a column with a DF value, the default value is inserted. DF is a
replacement for NULL.

82
Constraints

Customers
Custome- First- Last-
Email DateRegistered
rID Name Name
1 Anabel Larson anabel.larson@customer5.info 1967-12-04
2 Anna Laurier anna.laurier@customer2.net 1966-05-17
3 Beverly NULL NULL 1964-08-11
4 John Smith john.smith@customer1.com 1966-11-07
5 John Smith john.smith@customer3.org 1965-08-11

DF
DateRegistered = DF Definition
today
Insert new line without
Adam Hasen ah@customer17.com
specifying DateRegistered
6 Adam Hasen ah@customer17.com 1969-12-08
New line with default Da-
Today is 1969-12-08
teRegistered (today)

To list the DF in the Object Catalog:


SELECT
OBJECT_NAME([parent_object_id]) AS TableName
, [name] AS DFName
, [definition] AS DFDefinition
FROM sys.default_constraints
GO

CHECK (CK)

Verifies if the value in a CK column is valid against the CK definition.

Customers
Custome- First- Last-
Email
rID Name Name
1 Anabel Larson anabel.larson@customer5.info
2 Anna Laurier anna.laurier@customer2.net
3 Beverly NULL NULL
4 John Smith john.smith@customer1.com
5 John Smith john.smith@customer3.org
CK
Column Email contains @ CK Definition
sign
6 Adam Hasen ah@customer17.com Insert valid Email - OK
7 Adam Hasen ah#customer17.com Insert not valid Email - fail
4 John Smith john.smith&customer1.com Update CustomerID = 4 with not
valid Email - fail

83
Constraints

To view the CK in the Object Catalog:


SELECT
OBJECT_NAME([parent_object_id]) AS TableName
, [name] AS CKName
, [definition] AS CKDefinition
FROM sys.check_constraints
GO

UNIQUE (UQ)

Constraints the values in a column to be unique.


Customers
Custome- First- Last-
Email
rID Name Name
1 Anabel Larson anabel.larson@customer5.info
2 Anna Laurier anna.laurier@customer2.net
3 Beverly NULL NULL
4 John Smith john.smith@customer1.com
5 John Smith john.smith@customer3.org

UQ
6 Adam Hasen ah@customer17.com Insert not existing value in Email - OK
7 Adam Hasen ah@customer17.com Insert duplicate in Email - fail
4 John Smith ah@customer17.com Update CustomerID = 4 with duplicate
Email - fail

To view the UQ in the Object Catalog:


SELECT
OBJECT_NAME(C.[object_id]) AS TableName
, C.[name] AS UQColumn
, I.[name] AS UQName
FROM
sys.indexes AS I
JOIN sys.index_columns AS IC
ON I.[object_id] = IC.[object_id]
AND I.[index_id] = IC.[index_id]
JOIN sys.columns AS C
ON IC.[object_id] = C.[object_id]
AND IC.[column_id] = C.[column_id]
WHERE I.[is_unique_constraint] = 1;
GO

84
Constraints

NOT NULL (NN)

Constrains a column not to store NULL. We can’t insert or update a row, without specifying a value for the NN col-
umn.

Customers
Custome- First- Last-
Email
rID Name Name
1 Anabel Larson anabel.larson@customer5.info
2 Anna Laurier anna.laurier@customer2.net
3 Beverly NULL bev@customer7.pl
4 John Smith john.smith@customer1.com
5 John Smith john.smith@customer3.org

NOT NULL
6 Adam Hasen ah@customer17.com Insert Email - OK
7 Adam Hasen Insert row and skip Email - fail
4 John Smith NULL Update CustomerID = 4 with Email = NULL - fail

Data Type

The data type that a column in a table can store is also a constraint. We can’t insert String into a column with a
Numeric data type.

Constraints are created, altered and deleted with DDL statements (on page 86)

85
DDL Statements
CREATE

Naming Conventions on page 40


CREATE Statement

Creates new DB objects. It is good practice to use the DSO naming convention to point to the database and the
schema, where we need to create the object.
The keyword CREATE is the first clause in a DDL statement, followed by the type of the object (DATABASE, SCHEMA,
TABLE, VIEW, etc.) and the name of the object (identifier).

CREATE DATABASE

Creates a new database by using the default settings for DB location and DB size. We can see where the default DB
location is:
In SSMS Object Explorer (F8) (right click on the server name) Properties Database Settings
Database default locations

86
DDL Statements
CREATE

To create a DB, we execute the following statement:


CREATE DATABASE LearnSQLServerIntuitively;
GO
SQL Server® creates two files, used by the DBE to store and manipulate the data:
1. LearnSQLServerIntuitively.mdf – stores the DB objects and the data
2. LearnSQLServerIntuitively_log.ldf – used for transactional purposes on data manipulation

To query the Object Catalog for information about the DB:

SELECT *
FROM sys.databases
WHERE [name] = 'LearnSQLServerIntuitively'; Comment this line to select all the DBs
GO

87
DDL Statements
CREATE

The DB files:

SELECT *
FROM sys.database_files;
GO

To point to a specific database, we use the keyword USE in the beginning of the batch:

USE LearnSQLServerIntuitively;
GO

After we change the DB with USE, all the following statements in the batch are be executed against DB LearnSQLS-
erverIntuitively.

CREATE DATABASE limitations:


• Storage size: 524,272 terabytes
• Number of databases: 32,767

CREATE SCHEMA

After the DB exists, we create the next level DB object - the schema. To create a schema:

CREATE SCHEMA Sales;


GO

To view the schemas in the DB, we query the Object Catalog:

SELECT *
FROM sys.schemas;
GO

We use the default schema dbo in the book.

CREATE TABLE

After the keyword CREATE and the identifier for the name of the table, we list (in brackets) the column names and
their data types:

CREATE TABLE [DatabaseName].[SchemaName].[Customers]


( Between the braces, we list the column defi-
FirstName NVARCHAR(32) Identifier
nition (the properties of the column): name
, LastName NVARCHAR(64) (identifier), data type, identity specification,
collation, nullability and constraint

88
DDL Statements
CREATE

, [Email Address] VARCHAR(128) Data type


);
Square brackets for identifiers that contain special characters or reserved words
GO

We can create a table and the constraints belonging to the table in one DDL CREATE statement:

PRIMARY KEY Constraint (PK)

To create the PK and let the DBE generate the name for the PK:

CREATE TABLE dbo.Customers


(
CustomerID INT IDENTITY(1, 1) PRIMARY KEY PK with automatically generated name
, FirstName NVARCHAR(32)
, LastName NVARCHAR(64)
, [Email Address] VARCHAR(128)
);
GO

To specify a name for the PK right after column name and data type:

CREATE TABLE dbo.Customers


(
CustomerID INT IDENTITY(1, 1)
CONSTRAINT PK__Customers PK with specified name
PRIMARY KEY
, FirstName NVARCHAR(32)
, LastName NVARCHAR(64)
, [Email Address] VARCHAR(128)
);
GO

or at the end of the column list:

CREATE TABLE dbo.Customers


(
CustomerID INT IDENTITY(1, 1)
, FirstName NVARCHAR(32)
, LastName NVARCHAR(64)
, [Email Address] VARCHAR(128)
, CONSTRAINT PK__Customers PK with specified name on column CustomerID
PRIMARY KEY (CustomerID)

89
DDL Statements
CREATE

);
GO

Composite PK with specified name at the end of the column list:

CREATE TABLE dbo.Customers


(
FirstName NVARCHAR(32)
, LastName NVARCHAR(64)
, [Email Address] VARCHAR(128)
Composite PK on columns First-
, CONSTRAINT PK__Customers__FirstName_LastName
Name, LastName with specified name
PRIMARY KEY (FirstName, LastName)
);
GO

FOREIGN KEY Constraint (FK) Both referenced columns (PK - FK)


! have to be the same data type.
The reference table must exist with a PK.

We can add a FK to column and let the DBE generate the FK name:

CREATE TABLE dbo.Sales


( FK with automatically generated name
SaleID INT to table Customers, column CustomerID
, CustomerID INT
FOREIGN KEY REFERENCES dbo.Customers (CustomerID)
, ItemID INT
, Quantity INT
, Price MONEY
, DateOrdered DATETIME
, CONSTRAINT PK__Sales PRIMARY KEY (SaleID) PK with specified name on column SaleID
);
GO

We can specify the FK name:

CREATE TABLE dbo.Sales


(
SaleID INT
FK with specified name to table
, CustomerID INT
Customers, column CustomerID
CONSTRAINT FK__Sales__CustomerID
FOREIGN KEY REFERENCES dbo.Customers (CustomerID)

90
DDL Statements
CREATE

, ItemID INT
, Quantity INT
, Price MONEY
, DateOrdered DATETIME
, CONSTRAINT PK__Sales PK with specified name on column SaleID
PRIMARY KEY (SaleID)
);
GO

or

CREATE TABLE dbo.Sales


(
SaleID INT
, CustomerID INT
, ItemID INT
, Quantity INT
, Price MONEY
, DateOrdered DATETIME
, CONSTRAINT PK__Sales PK on column SaleID
PRIMARY KEY (SaleID)
, CONSTRAINT FK__Sales__CustomerID FK on column CustomerID to table
FOREIGN KEY (CustomerID) Customers, column CustomerID
REFERENCES dbo.Customers (CustomerID)
);
GO

To add the referential actions to the FK, we list them after the FK definition:

CREATE TABLE dbo.Sales


(
SaleID INT
, CustomerID INT
, ItemID INT
, Quantity INT
, Price MONEY
, DateOrdered DATETIME
, CONSTRAINT PK__Sales PK on column SaleID
PRIMARY KEY (SaleID)
FK on column CustomerID to table
, CONSTRAINT FK__Sales__CustomerID
Customers, column CustomerID
FOREIGN KEY (CustomerID)

91
DDL Statements
CREATE

REFERENCES dbo.Customers (CustomerID)


ON DELETE CASCADE Delete rule
ON UPDATE CASCADE Update rule
);
GO Referential Actions

DEFAULT Constraint (DF)

To create a DEFAULT constraint, we add the keyword DEFAULT and the constraint definition after the column
name and data type:

CREATE TABLE dbo.Customers


(
FirstName NVARCHAR(32)
, LastName NVARCHAR(64)
, [Email Address] VARCHAR(128)
, [Date Registered] DATETIME
DEFAULT (GETDATE()) DF with automatically generated name
);
GO

To specify a name for the constraint, we add the keyword CONSTRAINT, followed by the constraint name and
definition:

CREATE TABLE dbo.Customers


(
FirstName NVARCHAR(32)
, LastName NVARCHAR(64)
, [Email Address] VARCHAR(128)
, [Date Registered] DATETIME
CONSTRAINT DF__Customers_DateRegistered__GetDate DF with specified name
DEFAULT (GETDATE())
);
GO

CHECK Constraint (CK)

To create the CK with an automatically generated name, we add the keyword CHECK next to the column name and
data type, followed by the constraint definition:

CREATE TABLE dbo.Customers


(

92
DDL Statements
CREATE

FirstName NVARCHAR(32)
, LastName NVARCHAR(64)
, [Email Address] VARCHAR(128)
CHECK (CHARINDEX('@', [Email Address]) > 0) CK with automatically generated name
, [Date Registered] DATETIME
);
GO

To specify name, we use the keyword CONSTRAINT after the column name and data type:

CREATE TABLE dbo.Customers


(
FirstName NVARCHAR(32)
, LastName NVARCHAR(64)
, [Email Address] VARCHAR(128)
CONSTRAINT CK__Email__AtSign CK with specified name
CHECK (CHARINDEX('@', [Email Address]) > 0)
, [Date Registered] DATETIME
);
GO

UNIQUE Constraint (UQ)

To create UNIQUE constraint with an automatically generated name, we add the keyword UNIQUE after the column
name and data type:

CREATE TABLE dbo.Customers


(
FirstName NVARCHAR(32)
, LastName NVARCHAR(64)
, [Email Address] VARCHAR(128)
UNIQUE UQ with automatically generated name
);
GO

To specify the constraint name, we use the keyword CONSTRAINT after the column name and data type:

CREATE TABLE dbo.Customers


(
FirstName NVARCHAR(32)
, LastName NVARCHAR(64)

93
DDL Statements
CREATE

, [Email Address] VARCHAR(128)


CONSTRAINT UQ__Customers__Email UQ with specified name
UNIQUE
);
GO

or we add the keyword CONSTRAINT after the column list

CREATE TABLE dbo.Customers


(
FirstName NVARCHAR(32)
, LastName NVARCHAR(64)
, [Email Address] VARCHAR(128)
, CONSTRAINT UQ__Customers__Email UQ with specified name
UNIQUE ([Email Address])
);
GO

NOT NULL Constraint (NN)

To create NN constraint for a column, add NOT NULL after the column name and data type:

CREATE TABLE dbo.Customers


(
FirstName NVARCHAR(32)
, LastName NVARCHAR(64)
, [Email Address] VARCHAR(128) NOT NULL NOT NULL Constraint
);
GO

CREATE TABLE limitations:


• Maximum number of columns: 1,024
Tables on page 194
• Storage size per row: 8,060 bytes

CREATE VIEW

The identifier after CREATE VIEW doesn’t allow us to specify a DatabaseName. We use SO ([Schema].[Object]) nam-
ing convention as the identifier.
After the keyword AS, we write the SELECT statement that creates the recordset, returned by the view.

To create a view:

94
DDL Statements
CREATE

USE LearnSQLServerIntuitively;
GO

CREATE VIEW dbo.vw_Sales


AS
SELECT View definition
C.FirstName
, C.LastName
, S.DateOfSale
, S.Quantity
, S.Price
, (S.Quantity * S.Price) AS LineTotal
FROM
dbo.Customers AS C
JOIN dbo.Sales AS S
ON C.CustomerID = S.CustomerID; Views on page 220
GO

CREATE VIEW limitations:


• ORDER BY clause is not allowed in the view definition
• DSO ([Database].[Schema].[Object]) is not allowed, we use SO ([Schema].[Object]) identifier structure
• CREATE VIEW has to be the first statement in the batch

CREATE PROC (CREATE PROCEDURE)

CREATE PROC creates a new Stored Procedure. After the SP name, we specify the parameter(s) list and the SP
definition:

CREATE PROC dbo.usp_EligibleCustomers SO (not DSO) naming convention


(
@Paramater1 INT Stored Procedure Parameters List
, @Parameter2 DECIMAL(10, 2)
, @Parameter3 MONEY
)
AS
BEGIN
... Statement 1... Stored Procedure Definition
CREATE PROC limitations:
... Statement 2...
• Number of parameters: 2,100
...
... Statement n...;
END Stored Procedures on page 240
GO

95
DDL Statements
CREATE

CREATE FUNCTION

After the CREATE FUNCTION, we specify the function name, the parameter(s) list and the function definition.

Scalar-valued:

CREATE FUNCTION dbo.udf_CustomerLastSaleValue SO (not DSO) naming convention


(
@CustomerID INT Function Parameter(s) List
, @Quantity INT = NULL NULL is the default value
)
The scalar-valued function returns a single value of a speci-
RETURNS MONEY
fied data type. Same as the data type of the return variable
AS
BEGIN
The first statement de-
DECLARE @LastSaleValue MONEY;
clares the return variable
that the function returns
SELECT @LastSaleValue = S.LineTotal
FROM
(
SELECT
One or multiple statements
(Quantity * Price) AS LineTotal
to assign value to the
, ROW_NUMBER() OVER (ORDER BY DateOfSale DESC) AS [Rank]
return variable
FROM dbo.Sales
WHERE
CustomerID = @CustomerID
AND Quantity >= ISNULL(@Quantity, Quantity)
The function definition is
) AS S
between BEGIN and END
WHERE S.[Rank] = 1;

The last statement returns


RETURN @LastSaleValue;
the return variable
END
GO

Table-valued (Inline):

CREATE FUNCTION dbo.udf_SalesOverValueInline (@SaleValue MONEY)


RETURNS TABLE
AS Parameter(s) List
RETURN
(

96
DDL Statements
CREATE

WITH CTE_SumLineTotal (CustomerID) AS


(
SELECT CustomerID
FROM dbo.Sales
GROUP BY CustomerID
HAVING SUM(Quantity * Price) >= @SaleValue
)
SELECT
C.FirstName
, C.LastName Between the brackets (the function
definition) we add a single (SELECT)
, S.SaleDate
statement that returns a recordset
, S.Quantity
, S.Price
, (S.Quantity * S.Price) AS LineTotal
FROM
dbo.Customers AS C
JOIN dbo.Sales AS S
ON C.CustomerID = S.CustomerID
JOIN CTE_SumLineTotal AS X
ON C.CustomerID = X.CustomerID
);
GO

Table-valued (Multi-Statement):

CREATE FUNCTION dbo.udf_SalesOverValueMultiStatement (@SaleValue MONEY)


RETURNS @SalesOverValue TABLE Table variable (recordset), returned by the function
(
FirstName NVARCHAR(32)
, LastName NVARCHAR(64)
, SaleDate DATETIME
, Quantity INT
, Price MONEY
, LineTotal MONEY Column definition of the table variable
)
AS
BEGIN
DECLARE @FilterCustomerID TABLE (CustomerID INT); Statement No. 1

INSERT @FilterCustomerID (CustomerID) Statement No. 2


SELECT CustomerID

97
DDL Statements
CREATE

FROM dbo.Sales
GROUP BY CustomerID
HAVING SUM(Quantity * Price) >= @SaleValue; Statement No. 3

INSERT @SalesOverValue (FirstName, LastName, SaleDate, Quantity, Price, LineTotal)
SELECT
C.FirstName Populate the return
, C.LastName table variable
, S.DateOfSale
, S.Quantity
, S.Price
, (S.Quantity * S.Price) AS LineTotal
FROM
dbo.Customers AS C
JOIN dbo.Sales AS S
ON C.CustomerID = S.CustomerID
WHERE C.CustomerID IN (SELECT CustomerID FROM @FilterCustomerID);

RETURN; Statement No. 4
END
GO Function definition (multiple statements to populate the return table)

The code (View, Function, Stored Procedure) deployed in production is repeatable - it is executed multiple
times.

To avoid the error Cannot drop the {ObjectType} '{ObjectName}', because it does not exist or you do not have
permission., we add a cleanup statement (delete if exists) before the CRAETE statement.

Functions on page 227

CREATE FUNCTION limitations:


• Number of parameters: 2,100

Before creating an object, we delete an existing object with the same name (if any).

DROP on page 104

98
DDL Statements
ALTER

ALTER Statement

Alters an existing DB object.

ALTER DATABASE

Modify the DB name or the DB files (.mdf, .ldf):

ALTER DATABASE LearnSQLServerIntuitively


MODIFY Name = LearnSQLServerIntuitively_1;
GO

ALTER SCHEMA

Transfer DB objects from one schema to another.

ALTER SCHEMA DestinationSchemaName Destination schema


TRANSFER SourceSchemaName.SourceObjectName; Source schema and DB object to transfer
GO

ALTER TABLE

ALTER TABLE modifies a table design. The keyword that follows is:
• ADD - adds one or multiple new columns to an existing table Columns
• ALTER COLUMN - changes the column definition of an existing column
• DROP COLUMN - deletes one or multiple existing columns

• ADD CONSTRAINT - adds new constraint Constraints


• DROP - deletes existing constraint

ADD

ALTER TABLE dbo.Customers


ADD
FirstName NVARCHAR(32)
, LastName NVARCHAR(64)
, Email VARCHAR(128);
Customers Customers
GO
Custo- Custo- First- Last-
Email
merID merID Name Name
1 1 NULL NULL NULL
2 2 NULL NULL NULL
3 3 NULL NULL NULL

99
DDL Statements
ALTER

ALTER COLUMN

Sales Customers
Price Price
5.3542 ALTER TABLE dbo.Sales 5.354200
52.6234 ALTER COLUMN Price DECIMAL(18, 6) NOT NULL; 52.623400
203.2416 GO 203.241600

MONEY DECIMAL(18, 6)
NULL NOT NULL

DROP COLUMN

ALTER TABLE dbo.Customers


DROP COLUMN
LastName
, Email;
GO
Customers
Customers
Custo- First- Last- Custo- First-
Email
merID Name Name merID Name
1 Anabel Larson anabel.larson@ustomer5.info 1 Anabel
2 Anna Laurier anna.laurier@customer2.net 2 Anna
3 Beverly NULL NULL 3 Beverly

ADD CONSTRAINT

Adds constraint on a column for values that will be inserted in the future. Doesn’t affect current values that don’t
violate the constraint. If the column already contains values, these values are then validated against the constraint
to be created and in case of violation, the constraint can’t be created.

100
DDL Statements
ALTER

Customers
First- Last- DateReg-
Email
Name Name istered
ALTER TABLE dbo.Customers
Anabel Larson anabel.larson@ 1967-12-04
customer5.info
ADD CONSTRAINT DF__DateRegistered__GetDate
Anna Laurier anna.laurier@ 1966-05-17
DEFAULT (GETDATE())
customer2.net FOR DateRegistered;
Beverly NULL NULL NULL GO

Insert new row, by not Customers


specifying value for First- Last- DateReg-
Email
DateRegistered Name Name istered
Not affected by DF
Anabel Larson anabel.larson@customer5.info 1967-12-04
Anna Laurier anna.laurier@customer2.net 1966-05-17
Beverly NULL NULL NULL
John Smith john.smith@customer1.com 1969-11-07

The date when the row is in-


DROP (DROP CONSTRAINT) serted, generated by the DF

Deletes an existing constraint.


Customers ALTER TABLE dbo.Customers
First- Last- DateReg- DROP DF__DateRegistered__GetDate;
Email
Name Name istered
GO
Anabel Larson anabel.larson@ 1967-12-04
customer5.info The keyword CONSTRAINT
Anna Laurier anna.laurier@ 1966-05-17 or can be omitted
customer2.net
Beverly NULL NULL NULL ALTER TABLE dbo.Customers
John Smith john.smith@ 1969-11-07 DROP CONSTRAINT DF__DateRegistered__GetDate;
customer1.com
GO

Customers
Insert new row, by not First- Last-
Email
DateReg-
Name Name istered
specifying value for
Anabel Larson anabel.larson@customer5.info 1967-12-04
DateRegistered
Anna Laurier anna.laurier@customer2.net 1966-05-17
No DF
Beverly NULL NULL NULL
John Smith john.smith@customer1.com 1969-11-07
Barnie Jason bj@customer6.br NULL

101
DDL Statements
ALTER

ALTER VIEW

Changes the definition (the code) of an existing view. As the ALTER VIEW statement overwrites the definition of
the view, we need first to create a backup of the view.

ALTER VIEW [SchemaName].[ViewName]


AS
...SELECT statement that will overwrite the existing definition of the view...;
GO

When we change the design of an underlying object, we need to refresh the view:
EXEC sp_refreshview '[SchemaName].[ViewName]';

ALTER PROC (ALTER PROCEDURE)

Edits the definition (and the parameters) of an existing Stored Procedure. First we back up the Stored Procedure.

ALTER PROC [SchemaName].[StoredProcedureName]


(
@Paramater1 INT Edit the parameters list
, @Parameter2 DECIMAL(10, 2)
, @Parameter3 MONEY
)
AS
BEGIN
... Statement 1...
... Statement 2...
... Edit the definition
... Statement n...;
END
GO

ALTER FUNCTION

Edits the definition (and the parameters) of an existing Function. We back up the function before altering it.

Scalar-valued

ALTER FUNCTION [SchemaName].[FunctionName]


(
@Parameter1 NVARCHAR(16) Edit the parameters list
, @Parameter2 SMALLINT = NULL
) NULL is the default value

102
DDL Statements
ALTER

RETURNS DATETIME
AS
BEGIN
DECLARE @ReturnVariable DATETIME; Edit the definition

SELECT @ReturnVariable = ...Statement that populates @ReturnVariable...;

RETURN @ReturnVariable;
END

Table-valued Inline

ALTER FUNCTION [SchemaName].[FunctionName] (@Parameter1 MONEY)


RETURNS TABLE
AS
RETURN Edit the parameters list
(
SELECT Statement... Edit the definition
);
GO

Table-valued Multi-Statement

ALTER FUNCTION [SchemaName].[FunctionName]


(
@Parameter1 INT Edit the parameters list
, @Parameter2 MONEY = NULL
)
NULL is the default value
RETURNS @ReturnTable TABLE
(
Column1 NVARCHAR(32) Edit the returned table
, Column2 NVARCHAR(64)
)
AS
BEGIN
...Statement 1... Edit the definition
...Statement 2...

INSERT @ReturnTable ...Statement 3...

RETURN (0);
END
GO

103
DDL Statements
DROP

DROP Statement

Deletes an existing DB object.

DROP DATABASE

Deletes an existing database.

USE [master];
GO

DROP DATABASE LearnSQLServerIntuitively;


GO

If we execute the statement above and the DB doesn’t exist, an error message “Cannot drop the database
'LearnSQLServerIntuitively', because it does not exist or you do not have permission.“ is returned and the
execution of the batch stops. To avoid this, we wrap the execution of the DROP with the IF condition:

USE [master];
GO

DROP DATABASE IF EXISTS LearnSQLServerIntuitively; From SQL Server® 2016


GO

USE [master];
GO

Validate if DB exists with the


IF (DB_ID('LearnSQLServerIntuitively') IS NOT NULL)
built-in function DB_ID()
BEGIN
ALTER DATABASE LearnSQLServerIntuitively
SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE LearnSQLServerIntuitively;
END Before SQL Server® 2016
GO

or

IF EXISTS Validate if DB exists with IF EXISTS in the Object Catalog


(
SELECT [name]
FROM [master].sys.databases

104
DDL Statements
DROP

WHERE [name] = 'LearnSQLServerIntuitively'


)
BEGIN
ALTER DATABASE LearnSQLServerIntuitively SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE LearnSQLServerIntuitively;
END
GO
Before deleting a DB, we delete the backup history for the DB:
EXEC msdb.dbo.sp_delete_database_backuphistory
@database_name = N'LearnSQLServerIntuitively';
DROP SCHEMA
GO
Deletes an existing schema.

USE LearnSQLServerIntuitively;
GO

DROP SCHEMA Sales;


GO

Execute DROP SCHEMA with the IF condition to prevent the code termination in case the schema doesn’t exist:

DROP SCHEMA IF EXISTS Sales; From SQL Server® 2016


GO

USE LearnSQLServerIntuitively;
GO

IF (SCHEMA_ID('Sales') IS NOT NULL) Validate if schema exists with the


built-in function SCHEMA_ID()
BEGIN DROP SCHEMA Sales; END
GO
Before SQL Server® 2016
or

IF EXISTS Validate if schema exists with IF EXISTS in the Object Catalog


(
SELECT [name]
FROM sys.schemas
WHERE [name] = 'Sales'
)
BEGIN DROP SCHEMA Sales; END
GO

105
DDL Statements
DROP

We can’t delete schema that contains DB object(s). The error message “Cannot drop schema 'Sales' be-
! cause it is being referenced by object 'SalesDetails'.” is returned.

DROP TABLE

Deletes an existing table.

DROP TABLE IF EXISTS dbo.Customers; From SQL Server® 2016


GO

IF (OBJECT_ID('dbo.Customers', 'U') IS NOT NULL)


BEGIN DROP TABLE dbo.Customers; END
GO
Validate if the table exists with the built-in function OBJECT_ID()
or

Before SQL Server® 2016


IF EXISTS
(
SELECT [name] Validate if the table exists with IF EXISTS in the Object Catalog
FROM sys.tables
WHERE [name] = 'Customers'
)
BEGIN DROP TABLE dbo.Customers; END
GO

DROP CONSTRAINT

Deletes an existing constraint.

ALTER TABLE dbo.Sales


DROP CONSTRAINT IF EXISTS FK__Sales__CustomerID; From SQL Server® 2016
GO

IF EXISTS Validate if table exists with IF EXISTS in the Object Catalog


(
SELECT 1
FROM sys.foreign_keys
WHERE
OBJECT_NAME([parent_object_id]) = 'Sales'
AND [name] = 'FK__Sales__CustomerID'
)
BEGIN
ALTER TABLE dbo.Sales

106
DDL Statements
DROP

DROP CONSTRAINT FK__Sales__CustomerID;


END
Constraints on page 78
GO

DROP VIEW

Deletes an existing view.

USE LearnSQLServerIntuitively;
GO

DROP VIEW IF EXISTS dbo.vw_Sales; From SQL Server® 2016


GO
Validate if the view exists with the built-in function OBJECT_ID()
IF (OBJECT_ID('dbo.vw_Sales', 'V') IS NOT NULL)
BEGIN DROP VIEW dbo.vw_Sales; END
GO
Before SQL Server® 2016

or

IF EXISTS Validate if the view exists with IF EXISTS in the Object Catalog
(
SELECT [name]
FROM sys.views
WHERE
SCHEMA_NAME([schema_id]) = 'dbo'
AND [name] = 'vw_Sales'
)
BEGIN DROP VIEW dbo.vw_Sales; END
GO

DROP PROC (DROP PROCEDURE)

Deletes an existing stored procedure.

USE LearnSQLServerIntuitively;
GO

DROP PROC IF EXISTS dbo.usp_Customers_CRUD; From SQL Server® 2016


GO

107
DDL Statements
DROP

IF (OBJECT_ID('dbo.usp_Customers_CRUD', 'P') IS NOT NULL)


BEGIN DROP PROC dbo.usp_Customers_CRUD; END
GO
Validate if the stored procedure exists with the built-in function OBJECT_ID()

or Before SQL Server® 2016

IF EXISTS Validate if the stored procedure exists with IF EXISTS in the Object Catalog
(
SELECT [name]
FROM sys.procedures
WHERE
SCHEMA_NAME([schema_id]) = 'dbo'
AND [name] = 'usp_Customers_CRUD'
)
BEGIN DROP PROC dbo.usp_Customers_CRUD; END
GO

DROP FUNCTION

Deletes an existing function.

USE LearnSQLServerIntuitively;
GO

DROP FUNCTION IF EXISTS dbo.udf_DateNoTime; From SQL Server® 2016


GO

IF (OBJECT_ID('dbo.udf_DateNoTime', 'FN') IS NOT NULL)


BEGIN DROP FUNCTION dbo.udf_DateNoTime; END
GO Validate if the function exists with the built-in function OBJECT_ID()

or Before SQL Server® 2016

IF EXISTS Validate if the function exists with IF EXISTS in the Object Catalog
(
SELECT [name]
FROM sys.objects
WHERE IF = SQL_INLINE_TABLE_VALUED_FUNCTION
[type] IN ('IF', 'FN', 'TF') FN = SQL_SCALAR_FUNCTION
AND SCHEMA_NAME([schema_id]) = 'dbo' TF = SQL_TABLE_VALUED_FUNCTION
AND [name] = 'udf_DateNoTime'

108
DDL Statements
DROP

)
BEGIN DROP FUNCTION dbo.udf_DateNoTime; END
GO

Object Type

As you noticed, the second parameter in the built-in function OBJECT_ID() is object type. We specify it to limit the
function to search only for specific object type.

To view the object types in the Object Catalog:

SELECT DISTINCT
[type] AS ObjectType
, [type_desc] AS ObjectTypeDescription
FROM sys.all_objects
ORDER BY type_desc;
GO

109
DDL Statements
Script Objects in SSMS

Log in SQL Server® Management Studio (SSMS).


SQL Server® Management Studio (SSMS) on page 75

We execute DDL (Data Definition Language) statements to:


• CREATE
• ALTER
• DROP
DB objects.

The DB objects, covered by this book are database, schema, table, view, stored procedure and function.

Scripting DB objects in SSMS

Create a backup of every DB object that you modify or delete.

View

In SSMS: Expand the Object Explorer to the View (right click) Script View as CREATE To (ALTER To)
File... (New Query Editor Window)

Stored Procedure

In SSMS: Expand the Object Explorer to the Stored Procedure (Database Programability Stored Proce-
dures) (right click) Script Stored Procedure as CREATE To (ALTER To) File... (New Query Editor
Window)

In the same way we script the other objects.

110
DML Stataments

DML (Data Manipulation Language) Statements

As the DDL statements are the ones that manage the DB objects, the DML statements are the ones that manipu-
late the data in the DB. They are represented by CRUD as follows:
• INSERT for C (Create)
• SELECT for R (Read)
• UPDATE for U (Update)
• DELETE for D (Delete)

The source of the data is/are the table(s), view(s), table-valued function(s).
T-SQL extracts data from these objects into virtual recordsets (virtual tables, created in the background, used to
store data the T-SQL needs to manipulate) or tables (temporary or real) and manipulates it.
After all the manipulations are done, the resulting recordset is returned to the requester (SSMS or the application
that executed the T-SQL code).

To create a report that shows the sales persons, eligible for commission (Threshold >= 10) for October 1968, or-
dered by name, we extract the data from multiple sources:

Sales (table) SalesPersons (view) CommissionsTreshholds


SalesPer- Sales- SalesPer- First- Last- (table-valued function)
Period
sonID Value sonID Name Name SalesPer- Threshold-
1 1968-10 463.27 1 John Smith sonID Value

3 1967-01 196.52 2 Ashley Larson 1 11.35

3 1968-10 16.23 3 Brandon Fuston 2 1.52

4 1969-02 113.2 4 Jude Audrey 3 56.12

3 1968-10 5324.87 5 Michael Stolknom 4 5.2

6 Peter Bernier 5 5.34


6 452.34
7 753.2
Data sources
8 18
9 0.563

into virtual recordset (VR):

SalesPer- Sales- First- Last- Threshold-


Period
sonID Value Name Name Value
1 1968-10 463.27 John Smith 11.350
3 1967-01 196.52 Brandon Fuston 56.120
3 1968-10 16.23 Brandon Fuston 56.120
3 1968-10 5324.87 Brandon Fuston 56.120
4 1969-02 113.20 Jude Audrey 5.200 Virtual recordset (result of JOINed sources)

111
DML Stataments

and manipulate the virtual recordset to build the resulting recordset, used for the report:

Sales- First- Last- Threshold- Commission Program


Value Name Name Value Sales for October 1968
463.27 John Smith 11.350 SalesPerson Sales (USD)
Transform the dataset
16.23 Brandon Fuston 56.120 Brandon Fuston 5341.10
to report
5324.87 Brandon Fuston 56.120 John Smith 463.27

The recordsets are created and used in background. After all the manipulations are done and the data is returned
to the requester, the virtual recordsets are deleted.

The example above summarizes the material that we’ll learn in DML Statements.

112
DML Statements (Modify)
INSERT

INSERT

Inserts a new row or recordset (multiple rows) in an existing table.

The syntax is:

INSERT INTO Identifier Insert values in all the columns


...Statement that creates the recordset to be inserted...;
GO

or

INSERT INTO dbo.Customers


(
FirstName
, LastName Insert values in the listed columns
, Email
,...
)
...Statement that creates the recordset to be inserted...;
GO

The keyword INTO can be omitted:

INSERT dbo.Customers
(
FirstName
, LastName
, Email
, ...
)
...Statement that creates the recordset to be inserted...;
GO

Customers
FirstName LastName Email
Anabel Larson anabel.larson@customer5.info
Anna Laurier anna.laurier@customer2.net
Beverly NULL NULL
Existing table into which we insert rows
John Smith john.smith@customer1.com
John Smith john.smith@customer3.org

113
DML Statements (Modify)
INSERT

INSERT... SELECT

This example Inserts one row. The column list is not specified so we provide values for all the columns in the table
in the same order as in the table structure:

INSERT dbo.Customers
SELECT
'Melanie' Insert in the first column
, 'Larson' Insert in the second column
, 'melanie.larson@customer4.biz'; Insert in the third column
GO

The next example Insert multiple rows and specifies the column list. The UNION clause unions two recordsets into
one:

INSERT dbo.Customers (LastName, Email, FirstName) Row 1


SELECT 'Larson', 'melanie.larson@customer4.biz', 'Melanie' Row 2
UNION ALL SELECT 'Dernier', 'gdernier@customer412.be', 'George';
GO

The order of the columns in the column list must match


the order of the columns in the inserted recordset

INSERT... SELECT... FROM

We can SELECT rows from one data source and INSERT them into existing table:

INSERT dbo.Customers
(
LastName The number of the inserted rows has to
, FirstName match the number of the rows in the re-
, Email cordset, produced by the SELECT statement
)
SELECT
LastName
SELECT... FROM creates recordset from
, FirstName
rows in existing object (table, view or
, Email table-valued function)
FROM [Another Database].[Another Schema].Customers
GO

INSERT... VALUES

114
DML Statements (Modify)
INSERT

The VALUES clause creates a recordset with one row:

INSERT dbo.Customers
VALUES ('Jonathan', 'Grenier', 'jg@jgcompanyandassocies.ca');
GO

To create recordset with multiple rows, we delimit them with comma (,):

INSERT dbo.Customers (Email, LastName, FirstName)


VALUES
('Michaels.Zoe@customer18.se', 'Michaels', 'Zoe')
, ('t_robertson@customer19.at', 'Robertson', 'Thomas')
, ('af@customer20.fr', 'Frances', 'Albert');
GO

INSERT... EXEC

Inserts a recordset, created by Stored Procedure.

INSERT dbo.Customers (FirstName, LastName, Email)


EXEC dbo.usp_InsertCustomer;
GO

Stored Procedures on page 240

The columns, not specified in the column list of the INSERT clause will be populated with NULL or their default
values (if default constraint is specified for the columns).
The constraints are checked on INSERT and if there is a violation in a constraint, the row is not inserted.

We specify the names of the columns that we insert into. If we don’t, a change of the design of the object
may break the code. For example if we insert into a table with 4 columns and we didn’t specify the column
list, adding a 5-th column to the table will cause the INSERT statement to return an error 'Column name or
number of supplied values does not match table definition'.

INSERT limitations
• Number of columns: 4,096

115
DML Statements (Modify)
SELECT... INTO

SELECT... INTO

SELECT... INTO combines DDL and DML clauses into one statement. It SELECTs data from an existing object (table,
view or table-valued function) and inserts the resulting recordset into a new table, created during the execution of
the SELECT... INTO statement.

The syntax is:


SELECT Column1, Column2, Column3...
INTO [DatabaseName].[SchemaName].[DestinationTableName]
FROM [DatabaseName].[SchemaName].[SourceTableName];
GO

If the new table, specified in the INTO clause, already exists, the execution of the statement will return an
error “There is already an object named 'DestinationTableName' in the database.” and will be terminated.
To avoid this, we run a cleanup statement first (If DestinationTableName exists, delete it).

DROP TABLE IF EXISTS dbo.Customers_1; Clean up


GO

SELECT FirstName, LastName, Email


INTO dbo.Customers_1
Table Customers_1 is created during
FROM dbo.Customers the execution of the statement
GO

The columns in the destination table inherits the data types of the columns in the recordset.

Customers
First- Last-
Email
Name Name
John Smith john.smith@customer3.org
Melanie Larson melanie.larson@customer4.biz SELECT
Xavier Jameson xavier.j@customer6.com CONCAT(FirstName, ' ', LastName) AS [Name]
, Email
NVARCHAR(32) NVARCHAR(64) INTO dbo.Customers_1
VARCHAR(128)
NVARCHAR(97)
FROM dbo.Customers;
Customers_1 32 + space + 64 = 97
GO
Name Email
John Smith john.smith@customer3.org
Melanie Larson melanie.larson@customer4.biz
Xavier Jameson xavier.garcía@customer6.com

116
DML Statements (Modify)
SELECT... INTO

In the example below, the data type of the TotalValue column in the source table is TINYINT (Value: 0 to 255). The
query sums the column TotalValue and the summed value for CustomerID = 1 (280) extends the range of TINYINT
(255). The data type of TotalValue in the destination table is converted to INT.

Sales
CustomerID ItemID TotalValue SELECT
1 3 130 CustomerID
2 6 37 , ItemID
1 3 150 , SUM(TotalValue) AS Sum_TotalValue
INTO dbo.Sales_1
INT INT TINYINT FROM dbo.Sales
Sales GROUP BY
CustomerID
CustomerID ItemID Sum_TotalValue
, ItemID;
1 3 280
GO
2 6 37

INT INT INT

If a column in the SELECT clause has no name (if we omit the alias AS Sum_TotalValue in the example
! above), the DBE will return an error “An object or column name is missing or empty. For SELECT INTO state-
ments, verify each column has a name. For other statements, look for empty alias names. Aliases defined as
“” or [] are not allowed. Change the alias to a valid name.”.

117
DML Statements (Modify)
UPDATE

Updates the value(s) of one or multiple cells in a table.

The syntax is:

UPDATE DatabaseName.SchemaName.UpdatedObjectName
SET UpdatedColumnName = NewValue; Table, view or table-valued function
GO

Customers
CustomerID FirstName LastName Email
1 Anabel Larson anabel.larson@customer5.info
2 Anna Laurier anna.laurier@customer2.net
3 Beverly NULL NULL
4 John Smith john.smith@customer1.com
5 John Smith john.smith@customer3.org
6 Melanie Larson larson_mel@customer4.biz
7 Xavier Jameson xj@customer6.com
8 Zak Smith NULL

UPDATE... SET

Updates all the rows of the updated column.


Updates all the rows in column Email
UPDATE dbo.Customers
SET Email = 'customer84186@customer84186.pl';
GO

Customers
CustomerID FirstName LastName Email
1 Anabel Larson customer84186@customer84186.pl
2 Anna Laurier customer84186@customer84186.pl
3 Beverly NULL customer84186@customer84186.pl
4 John Smith customer84186@customer84186.pl
5 John Smith customer84186@customer84186.pl
6 Melanie Larson customer84186@customer84186.pl
7 Xavier Jameson customer84186@customer84186.pl
8 Zak Smith customer84186@customer84186.pl

UPDATE... SET... WHERE

Affects only the rows that correspond to the filter condition(s) in the WHERE clause.

118
DML Statements (Modify)
UPDATE

UPDATE dbo.Customers
SET Email = 'customer84186@customer84186.pl'
WHERE
FirstName = 'Anabel'
AND LastName = 'Larson';
GO
LastName = 'Larson'?
Customers FirstName = 'Anabel'?
Custo- First- Last-
Email
merID Name Name Email
1 Anabel Larson anabel.larson@customer5.info Yes AND Yes customer84186@customer84186.pl
2 Anna Laurier anna.laurier@customer2.net No AND No
3 Beverly NULL NULL No AND No
4 John Smith john.smith@customer1.com No AND No New value
5 John Smith john.smith@customer3.org No AND No
6 Melanie Larson larson_mel@customer4.biz No AND Yes
7 Xavier Jameson xj@customer6.com No AND No
8 Zak Smith NULL No AND No

Logical Operator

UPDATE... SET... FROM... WHERE

Updates cell values, based on a resultset, created from multiple DB objects.

The following tables are used to demonstrate an UPDATE... SET... FROM... WHERE example:

Customer
First- Last- IsEligibleFor- IsLowSales-
CustomerID Email DateLastUpdated
Name Name Discount Value
1 Anabel Larson anabel.larson@customer5.info NULL NULL NULL
2 Anna Laurier anna.laurier@customer2.net NULL NULL NULL
3 Beverly NULL NULL NULL NULL NULL
4 John Smith john.smith@customer1.com NULL NULL NULL
5 John Smith john.smith@customer3.org NULL NULL NULL
6 Melanie Larson larson_mel@customer4.biz NULL NULL NULL
7 Xavier Jameson xj@customer6.com NULL NULL NULL
8 Zak Smith NULL NULL NULL NULL

PK

119
DML Statements (Modify)
UPDATE

Sales
SaleID CustomerID ItemID DateOfSale Quantity Price LineTotal
1 7 345 1967-05-13 512 5.26 2693.12
2 1 58 1965-01-12 23 5.22 120.06
3 2 173 1965-12-14 1 5.231 5.231
4 5 1542 1968-08-15 55 66.231 3642.705 ItemID = 58
5 3 58 1967-11-28 276 5.24 1446.24
DateOfSale in the
6 4 234 1964-04-07 32 126.24 4039.68 last 30 days (in-
7 2 6562 1968-10-01 27 4638.02 125226.54 cluding today)
8 5 44 1968-03-28 127 1.24 157.48
9 2 58 1967-12-03 65 5.24 340.6
10 5 727 1965-08-17 52 5.21 270.92

FK Today is December 18, 1967

This example UPDATEs the value of IsEligibleForDiscount for the customers that bought ItemID = 58 in the last
30 days (including today):

UPDATE C Alias
SET
IsEligibleForDiscount = 1
, DateLastUpdated = GETUTCDATE()
FROM
dbo.Customers AS C
JOIN dbo.Sales AS S
ON C.CustomerID = S.CustomerID
WHERE
S.ItemID = 58
AND S.DateOfSale BETWEEN DATEADD(DD, -30, CAST(GETDATE() AS DATE)) AND CAST(GETDATE() AS
DATE);
GO

120
DML Statements (Modify)
UPDATE

Customers table after the execution on December 18, 1967:

First- Last- IsEligibleFor- IsLowSales-


CustomerID Email DateLastUpdated
Name Name Discount Value
1 Anabel Larson anabel.larson@customer5.info NULL NULL NULL
2 Anna Laurier anna.laurier@customer2.net 1 NULL 1967-12-18
3 Beverly NULL NULL 1 NULL 1967-12-18
4 John Smith john.smith@customer1.com NULL NULL NULL
5 John Smith john.smith@customer3.org NULL NULL NULL
6 Melanie Larson larson_mel@customer4.biz NULL NULL NULL
7 Xavier Jameson xj@customer6.com NULL NULL NULL
8 Zak Smith NULL NULL NULL NULL

UPDATE... SET... FROM (Subquery)... WHERE

! We can’t use an aggregate built-in function in the SET clause.

The example below UPDATEs columns IsLowSalesValue and DateLastUpdated (in table Customers) with the values
from the returned by the subquery (SQ) recordset (ILSV and DEFDU), on the rows where the value of CustomerID
column equals the value CustID in the subquery (SQ):

UPDATE dbo.Customers
SET
IsLowSalesValue = SQ.ILSV
, DateLastUpdated = SQ.DEFDU
FROM
The subquery (between the brack-
(
ets) returns this recordset
SELECT
C.CustomerID AS CustID
CustID ILSV DEFDU
, 1 AS ILSV
1 1 1967-12-18
, GETUTCDATE() AS DEFDU
FROM
dbo.Customers AS C
JOIN dbo.Sales AS S
ON C.CustomerID = S.CustomerID
WHERE S.ItemID = 58
GROUP BY C.CustomerID
HAVING SUM(S.LineTotal) < 200
) AS SQ
WHERE CustomerID = SQ.CustID;
GO

121
DML Statements (Modify)
UPDATE

Customers table after the execution (CustomerID = 1 is UPDATEd):

First- Last- IsEligibleFor- IsLowSales-


CustomerID Email DateLastUpdated
Name Name Discount Value
1 Anabel Larson anabel.larson@customer5.info NULL 1 1967-12-18
2 Anna Laurier anna.laurier@customer2.net 1 NULL 1967-12-18
3 Beverly NULL NULL 1 NULL 1967-12-18
4 John Smith john.smith@customer1.com NULL NULL NULL
5 John Smith john.smith@customer3.org NULL NULL NULL
6 Melanie Larson larson_mel@customer4.biz NULL NULL NULL
7 Xavier Jameson xj@customer6.com NULL NULL NULL
8 Zak Smith NULL NULL NULL NULL

SELECT on page 125


FROM on page 127
WHERE on page 142
GROUP BY on page 151
HAVING on page 154

122
DML Statements (Modify)
DELETE

DELETE FROM... (DELETE)

Deletes one or multiple rows from a table.

DELETE FROM TableName;


GO The keyword FROM is optional and can be omitted

! DELETE statement without WHERE clause deletes all the rows in the table.

DELETE [DatabaseName].[SchemaName].[TableName]
Deletes the rows that correspond
WHERE ColumnName Operator Value;
to the filter condition(s)
GO

We need to pay special attention to the DELETE statements and:


• Backup the data before executing the DELETE statement
• Test the DELETE statement(s) on objects located on the development server and deploy on the pro-
duction server, after being sure that we deleted exactly the data that we need

DELETE FROM... FROM...

When we join tables and delete rows from one of the joined tables, we specify from which table we delete rows.

We can identify the rows that we need to delete by writing a query that JOINs multiple DB objects. In the example
below, we JOIN tables Customers and Sales to pick the right rows for deleting from table Sales and delete them
in table Customers.
Customer FROM can be omitted
Custo- First- Last-
Email DELETE FROM C Alias
merID Name Name
1 Anabel Larson anabel.larson@cus- FROM
tomer5.info dbo.Customers AS C
2 Anna Laurier anna.laurier@custom- JOIN dbo.Sales AS S
er2.net
ON C.CustomerID = S.CustomerID
3 Beverly NULL NULL
WHERE S.DateOfSale <= '1967-01-01';
GO
PK
FK The rows of CustomerID 1 and 2 are deleted from table Customers
Sales
Custo- Quan-
SaleID ItemID DateOfSale Price LineTotal
merID tity
Translation: Create a recordset by
1 2 345 1967-05-13 512 5.26 2693.12
joining both tables and delete from
2 1 58 1965-01-12 23 5.22 120.06
Customers the customers which sales
3 2 173 1965-12-14 1 5.231 5.231
are older than a specified date.

DateOfSale older than January 01, 1967

123
DML Statements (Modify)
DELETE

Before we execute the DELETE or UPDATE statements, we execute the same statement with the SELECT
clause, instead of the DELETE or UPDATE clause to verify the rows to be deleted/updated.

Execute first:

SELECT *
FROM dbo.Customers
WHERE FirstName = 'John';
GO

After you confirm that the statement affects the exact rows, execute:

DELETE dbo.Customers
WHERE FirstName = 'John';
GO

UPDATE dbo.Customers
SET Email = 'j@customer2.net'
WHERE FirstName = 'John';
GO

124
DML Statements (Query)
SELECT

SELECT... ...or create a recordset

Creates a recordset and returns it to the requester (SSMS, external application that queries the DB etc.).
The SELECT clause can be constructed from a single SELECT clause or multiple clauses - SELECT... FROM... WHERE...

Manipulates
T-SQL Data Type Returned Value or Recordset
What
Value SELECT 'John'; String (in single quotes) John
SELECT 123; Numeric 123
Expression SELECT ('John' + ' ' + 'Smith'); String and String John Smith
SELECT (123 + 456); Numeric and Numeric 579
SELECT ('John' + ' ' + CAST(123 AS String and Numeric, explicitly John 123
VARCHAR)); converted to String
SELECT (Price * 1.3) Column value and Numeric 3.9
FROM DBObjectName (Price = 3)
SELECT SellPrice - Expences Column value and column 7.18
FROM DBObjectName value (SellPrice = 9.86
Expences = 2.68)
Built-in function SELECT GETDATE(); Output: Date and Time 1965-06-05 13:33:23.390
(now)
SELECT LEN('John'); Input: String 4
Output: Numeric
User-defined SELECT dbo.udf_ Function output data type 1968-04-13 00:00:00.000
function GetFirstDayOfCurrentFiscalMonth();
Value, expres- SELECT Both columns: String FirstName LastName
sion, user-de- 'John’' AS FirstName
John Smith
fined function , 'Smith' AS LastName;
and retutns SELECT First column: String FirstName NumericValue
recordset 'John' AS FirstName Second column: Numeric
(rows and col- , (100 + 23) AS NumericValue;
John 123
umns)
SELECT First column: String First- Numer-
UDFResult
'John' AS FirstName Second column: Numeric Name icValue
, 123 AS NumericValue Third column: Function output John 123 1968-04-13
, dbo.udf_ data type 00:00:00.000
GetFirstDayOfCurrentFiscalMonth()
AS UDFResult;
Variable * SELECT @FirstName AS The data type of the variable John
VariableValue; (@FirstName = John)

*
Variables on page 199

125
DML Statements (Query)
SELECT

SELECT DISTINCT...

The keyword DISTINCT, added to the SELECT statement, changes the logic to select only distinct (unique) rows.

The rows in table Customers are unique (the combination of all the columns are unique).

Customers
First- Last-
Email
Name Name
Anabel Larson anabel.larson@customer5.info
Anna Laurier anna.laurier@customer2.net
Beverly NULL NULL
John Smith john.smith@customer1.com
John Smith john.smith@customer3.org

When we select columns FirstName and LastName, the SELECT statement generates a recordset, that is not unique
(John Smith is a duplicate).

SELECT First- Last-


FirstName Name Name
, LastName Anabel Larson
FROM dbo.Customers; Anna Laurier
GO Beverly NULL
John Smith
John Smith

To generate a recordset with unique rows across the FirstName and LastName rows, we add the keyword DISTINCT
to the SELECT clause:

SELECT DISTINCT First- Last-


FirstName Name Name
, LastName Anabel Larson
FROM dbo.Customers; Anna Laurier
GO Beverly NULL
John Smith

SELECT limitations
• Number of columns in the SELECT statement: 4,096
126
DML Statements (Query)
FROM

SELECT... FROM ...or point to the source of the data

The FROM clause points to the DB object (table - real or temporary, view, table-values function) or subquery which is
a data source for the SELECT clause.

The syntax is: Column list, delimited with comma (,).


SELECT [Column1], [Column2], [Column3]... Square brackets if the column header
FROM [DatabaseName].[SchemaName].[ObjectName]; contains special character(s).
GO
Table, view or table-valued function.

When we select all the columns, we use SELECT * (star). If we need specific columns, we SELECT a list of columns,
delimited with comma (,).

Customer
CustomerID FirstName LastName Email
1 Anabel Larson anabel.larson@customer5.info
SELECT FirstName, LastName
2 Anna Laurier anna.laurier@customer2.net
FROM Customers;
3 Beverly NULL NULL
GO

* means all the columns


SELECT *
FROM Customers;
GO
Table Customers is the source of the data, manipulated by the statement

Subquery

The subquery is a nested SELECT statement (inner) into another SELECT statement (outer).
When we have a subquery in the FROM clause the inner SELECT statement creates a recorsdet and the outer SELECT
statement selects from this recordset.

Subqueries on page 165

1. List the columns in the SELECT clause instead of using * (star), because:
1.1. After a change of the design of the data source the query may break.
1.2. * Selects columns that we don’t need and creates an overload of the server.
2. Use the DSO ([DatabaseName].[SchemaName].[ObjectName]) naming convention as identifier in the
FROM clause. This way we can move the SQL code to another database in the same instance on the SQL
Server® without editing it in order to point to the exact object.

127
DML Statements (Query)
JOIN

SELECT... FROM... JOIN ...or create a recordset from multiple data sources

The data in the RDBMS are related and it is in multiple tables split by subject area. Joining related tables is one of
the most important and powerful tasks in SQL.
When we select data from multiple data sources (DS) - table, view, table-valued function or subquery - we JOIN
them.
The joined objects create a virtual recordset (VR) in the background that holds all data from the data sources and
we query this VR.
We JOIN the objects ON one or multiple columns.

The different types of JOIN, create different virtual recordsets:

JOIN type creates a VR and based on the JOIN conditions


that contains in the FROM clause
INNER JOIN the matching rows in both DS Yes
LEFT OUTER JOIN all the rows from the left DS the matching rows from
the right DS Yes
RIGHT OUTER JOIN all the rows from the right DS the matching rows from
the left DS Yes
FULL OUTER JOIN all the rows from both DS Yes
CROSS JOIN all the combinations of the No ON clause
rows in both DS

DS - data source
The syntax is: Aliases on page 138

SELECT
L.Col2 Alias in the SELECT clause (bind alias)
, R.Col3
Keyword (type of joining), JOIN,
, ...
right data source and alias
FROM
LeftDataSource AS L Left data source and alias in the FROM clause (assign alias)
(INNER, LEFT, RIGHT, FULL) JOIN RightDataSource AS R
ON L.Column1 = R.Column1 After the ON clause, we list the
AND JOIN condition(s) (link column(s))
( When we use both AND and OR logical op-
L.Column2 = R.Column2 erators, we separate them with brackets
Comparison operators
OR L.Column3 >= R.Column3
)
AND R.Column4 > 123 Filter the VR with value or expression
AND ...;
GO Multiple JOIN conditions joined with logical operators (AND, OR)

128
DML Statements (Query)
JOIN

Let’s join the two sources below. For the simplicity of the example, the test data is:
• Col1 is used to join the tables and values that exist in both tables such as Col1 (3,4 and 5)
• The values in Col2 and Col3 are: first character is the column index and the second character is the value in
Col1
Left data source Right data source
Col1 Col2 Col3 Col1 Col2 Col3
1 21 31 3 23 33
2 22 32 4 24 34
3 23 33 5 25 35
4 24 34 6 26 36
5 25 35 7 27 37

Joining the sources on column Col1, creates the following VR:

JOINed data sources


Left data source Right data source
Col1 Col2 Col3 Col1 Col2 Col3
1 21 31
2 22 32
3 23 33 3 23 33
4 24 34 4 24 34
5 25 35 5 25 35
6 26 36
7 27 37

ON L.Col1 = R.Col1
The statement that manipulates the joined tables is actually manipulating the recordset, built with the FROM... JOIN
clause.

JOIN (INNER JOIN)

INNER JOIN is inner, because it creates a VR that contains only the rows that meet the join criteria(s).
SELECT
L.*
, R.*
FROM
dbo.LeftDataSource AS L
JOIN dbo.RightDataSource AS R
ON L.Col1 = R.Col1;
GO The keyword INNER is optional and can be omitted

129
DML Statements (Query)
JOIN

Left data source Right data source


Col1 Col2 Col3 Col1 Col2 Col3
1 21 31
2 22 32
3 23 33 3 23 33
Resulting VR 4 24 34 4 24 34 Not manipulated
5 25 35 5 25 35
6 26 36
7 27 37

ON L.Col1 = R.Col1

LEFT JOIN (LEFT OUTER JOIN)

OUTER JOIN is outer, because it creates VR, that contains more rows than the rows that meet the JOIN condition(s)
in the ON clause.
LEFT JOIN creates VR with all the rows from the left data source and only the rows from the right data source that
meet the JOIN condition(s).
The VR fills the rows in the right table that don’t have a match on the JOIN condition(s) with NULL.

SELECT
L.*
, R.*
FROM
dbo.LeftDataSource AS L
LEFT JOIN dbo.RightDataSource AS R
ON L.Col1 = R.Col1;
GO
The keyword OUTER is op- Left data source Right data source
tional and can be omitted Col1 Col2 Col3 Col1 Col2 Col3
1
2
21
22
31 NULL
32 NULL
NULL
NULL
NULL
NULL } Out of the JOIN condition(s)

}
Resulting VR 3 23 33 3 23 33
4 24 34 4 24 34 Meet the JOIN condition(s)
5 25 35 5 25 35
6 26 36
Not
7 27 37
manipulated

ON L.Col1 = R.Col1

130
DML Statements (Query)
JOIN

When we use the LEFT or RIGHT JOIN and filter in the WHERE clause, the JOIN type is converted from LEFT
! (RIGHT) to INNER.
To avoid this and filter only the LEFT (RIGHT) data source, we filter in the ON clause.

SELECT
T1.A
, T1.B
, T2.C
FROM
Table1 AS T1
LEFT JOIN Table2 AS T2
ON T1. Column1 = T2.Column1
AND T2.Column 2 = @Parameter1 Filter here
WHERE T2.Column 2 = @Parameter1; If we filter on the WHERE clause, the JOIN
GO is converted from LEFT (RIGHT) to INNER

RIGHT JOIN (RIGHT OUTER JOIN)

The same logic as LEFT JOIN, but reversed - the VR contains all the rows from the right data source and the
matching rows from the left data source.

SELECT
L.*
, R.*
FROM
dbo.LeftDataSource AS L
RIGHT JOIN dbo.RightDataSource AS R
ON L.Col1 = R.Col1;
GO
Left data source Right data source
Col1 Col2 Col3 Col1 Col2 Col3
1 21 31
Not manipulated
2 22 32
3 23 33 3 23 33
4 24 34 4 24 34
5 25 35 5 25 35 Resulting VR
NULL NULL NULL 6 26 36
NULL NULL NULL 7 27 37

ON L.Col1 = R.Col1

131
DML Statements (Query)
JOIN

FULL JOIN (FULL OUTER JOIN)

Creates a VR with all the rows from both data sources. The unmatched values in the left and the right source are
represented with NULL. It is a combined LEFT JOIN and RIGHT JOIN.

SELECT
L.*
, R.*
FROM
dbo.LeftDataSource AS L
FULL JOIN dbo.RightDataSource AS R
ON L.Col1 = R.Col1;
GO
Left data source Right data source
Col1 Col2 Col3 Col1 Col2 Col3
1 21 31 NULL NULL NULL
2 22 32 NULL NULL NULL
3 23 33 3 23 33
Resulting VR 4 24 34 4 24 34
5 25 35 5 25 35
NULL NULL NULL 6 26 36
NULL NULL NULL 7 27 37

ON L.Col1 = R.Col1

Let’s combine (LEFT, RIGHT) JOIN and WHERE clauses to select:


• Rows from the left data source, that don’t have a match in the right data source (LEFT JOIN WHERE
RightRecordset IS NULL)

WHERE on page 142

SELECT
L.*
, R.*
FROM
dbo.LeftDataSource AS L
LEFT JOIN dbo.RightDataSource AS R
ON L.Col1 = R.Col1
WHERE R.Col1 IS NULL;
GO

132
DML Statements (Query)
JOIN

Left data source Right data source


Col1 Col2 Col3 Col1 Col2 Col3
1 21 31 NULL NULL NULL
Resulting VR
2 22 32 NULL NULL NULL
3 23 33 3 23 33
4 24 34 4 24 34
5 25 35 5 25 35 Not manipulated
6 26 36
7 27 37

ON L.Col1 = R.Col1

• Rows from the right data source, that don’t have a match in the left data source (RIGHT JOIN WHERE
LeftRecordset IS NULL)

SELECT
L.*
, R.*
FROM
dbo.LeftDataSource AS L
RIGHT JOIN dbo.RightDataSource AS R
ON L.Col1 = R.Col1
WHERE L.Col1 IS NULL;
GO

Left data source Right data source


Col1 Col2 Col3 Col1 Col2 Col3
1 21 31
2 22 32
Not manipulated 3 23 33 3 23 33
4 24 34 4 24 34
5 25 35 5 25 35
NULL NULL NULL 6 26 36
Resulting VR
NULL NULL NULL 7 27 37

ON L.Col1 = R.Col1

• Rows from both data sources, that don’t have a match (FULL JOIN WHERE LeftRecordset OR RightRecordset
IS NULL). FULL JOIN except INNER JOIN.

133
DML Statements (Query)
JOIN

SELECT
L.*
, R.*
FROM
dbo.LeftDataSource AS L
FULL JOIN dbo.RightDataSource AS R
ON L.Col1 = R.Col1
WHERE
L.Col1 IS NULL
OR R.Col1 IS NULL;
GO

Left data source Right data source


Col1 Col2 Col3 Col1 Col2 Col3
1 21 31 NULL NULL NULL
2 22 32 NULL NULL NULL
3 23 33 3 23 33
Resulting VR 4 24 34 4 24 34
5 25 35 5 25 35
NULL NULL NULL 6 26 36
NULL NULL NULL 7 27 37

ON L.Col1 = R.Col1

CROSS JOIN

Creates a VR, containing all the combinations of the rows in both data sources, (a.k.a. Cartesian Product).

SELECT
L.*
, R.*
FROM
dbo.LeftDataSource AS L
CROSS JOIN dbo.RightDataSource AS R;
GO

No ON clausev

134
DML Statements (Query)
JOIN

Col1 Col2 Col3 Col1 Col2 Col3


1 21 31 3 23 33
2 22 32 3 23 33
All the rows from the Row No. 1 from the
3 23 33 3 23 33
left data source right data source
4 24 34 3 23 33
5 25 35 3 23 33
1 21 31 4 24 34
2 22 32 4 24 34
Row No. 2 from the
3 23 33 4 24 34
right data source
4 24 34 4 24 34
5 25 35 4 24 34
10 more rows here
The number of the combinations 1 21 31 7 27 37
of the rows is 25 (5 rows in the 2 22 32 7 27 37
Row No. 5 (last) from
left data source, 5 rows in the 3 23 33 7 27 37
the right data source
right data source). 4 24 34 7 27 37
5 25 35 7 27 37

JOIN and NULL in the ON clause (NULL in link column(s))

When we join on a column, that stores NULL, the rows with NULL can’t be joined like NULL = NULL (L.Col1 =
R.Col1).

Let’s insert NULL to the data sources: INSERT on page 113

INSERT dbo.LeftDataSource
(
Col1
, Col2 The same INSERT statement to
, Col3 the right data source
)
SELECT
NULL
Left data source  Right data source
, '2NULL'
Col1 Col2 Col3 Col1 Col2 Col3
, '3NULL';
1 21 31 3 23 33
GO
2 22 32 4 24 34
3 23 33 5 25 35
4 24 34 6 26 36
5 25 35 7 27 37
NULL 2NULL 3NULL NULL 2NULL 3NULL

135
DML Statements (Query)
JOIN

SELECT
L.*
, R.*
FROM
dbo.LeftDataSource AS L
JOIN dbo.RightDataSource AS R
ON L.Col1 = R.Col1;
GO We can’t link NULL to NULL

Left data source Right data source


Col1 Col2 Col3 Col1 Col2 Col3
1 21 31
2 22 32
3 23 33 3 23 33
Resulting VR 4 24 34 4 24 34
Not manipulated
5 25 35 5 25 35
NULL 2NULL 3NULL 6 26 36
7 27 37
NULL 2NULL 3NULL

ON L.Col1 = R.Col1

To return the linked NULL to NULL rows, we use the built-in function ISNULL() to replace NULL (unknown) with
known value and use this value to join.

SELECT
L.*
, R.*
FROM
dbo.LeftDataSource AS L
JOIN dbo.RightDataSource AS R
ON ISNULL(L.Col1, '') = ISNULL(R.Col1, '');
Link empty string to empty string
GO

136
DML Statements (Query)
JOIN

Left data source Right data source


Col1 Col2 Col3 Col1 Col2 Col3
1 21 31
2 22 32
3 23 33 3 23 33
Resulting VR 4 24 34 4 24 34
Not manipulated
5 25 35 5 25 35
NULL 2NULL 3NULL NULL 2NULL 3NULL
6 26 36
7 27 37

ON L.Col1 = R.Col1

Replacing NULL with a real value can create incorrect results, because NULL (unknown) is not an actual value
and we are joining unknown to unknown.

137
Aliases

The alias gives a pseudo name to a DB object, column or expression and is used to:
• Point to the exact data source and the exact column when JOINing data sources
• Makes the code neat and easy to understand

Customers Sales

Quantity
ItemID
SaleID

Custo-
merID
Custo-

DateOf- DateModi-
merID

First- Last- DateModi- Price


Email Sale fied
Name Name fied

1 Anabel Larson anabel.larson@ 1966-12-11 1 3 2043 1967-01-24 4 1.27 1967-01-24


ustomer5.info 2 4 5164 1967-05-15 2 1.56 1967-05-15
2 Anna Laurier anna.laurier@ 1964-05-13 3 3 5293 1966-12-11 77 55.12 1967-01-14
customer2.net
4 5 6223 1969-01-17 3 4.89 1969-01-17
3 Beverly NULL NULL 1964-08-15
5 7 1352 1965-10-14 89 0.84 1965-10-16
4 John Smith john.smith@cus- 1963-04-25
6 2 1953 1966-03-05 53 2.84 1966-03-05
tomer1.com
7 3 8613 1963-04-13 22 23.41 1963-04-13
5 John Smith john.smith@cus- 1968-05-13
tomer3.org 8 2 5523 1967-09-18 55 127.19 1967-09-18
6 Melanie Larson melanie.larson@ 1963-12-31 9 1 8534 1962-10-13 1 45.88 1963-04-15
customer4.biz 10 2 9375 1965-10-06 6 0.43 1966-04-13
7 Xavier Jameson xavier.garcía@ 1968-04-28
customer6.com
8 Zak Smith NULL 1963-12-11

CustomerID and DateModified exist in both tables. When we JOIN the data sources, we need to create a separate
identification of the columns with the same name:
• CustomerID from Customers • DateModified from Customers
• CustomerID from Sales • DateModified from Sales

SELECT
FirstName
Exists only in Customers
, LastName
, DateModified Exists in more than one data source
FROM
dbo.Customers
JOIN dbo.Sales
ON dbo.Customers.CustomerID = dbo.Sales.CustomerID
WHERE CustomerID = 2; Exists in more than one data source
GO

Error messages are returned:


Ambiguous column name 'DateModified'.
Ambiguous column name 'CustomerID'.

138
Aliases

To point to the exact columns in the exact objects, we add the identifiers before the column name as described in
Naming Conventions - DSO:

Naming Conventions on page 40

SELECT
FirstName
, LastName
, dbo.Customers.DateModified
Bind to the exact object and column
, dbo.Sales.DateModified
, (Quantity * Price) AS LineTotal Assign Alias for the column name
FROM
dbo.Customers
JOIN dbo.Sales
ON dbo.Customers.CustomerID = dbo.Sales.CustomerID [SchemaName].[ObjectName].[ColumnName]
WHERE (Quantity * Price) >= 500
ORDER BY LineTotal DESC;
GO

First- Last- DateModi- DateModi-


LineTotal
Alias, assigned in the SELECT clause Name Name fied fied
Anna Laurier 1964-05-13 1967-09-18 6995.45
Beverly NULL 1964-08-15 1967-01-14 4244.24
Beverly NULL 1964-08-15 1963-04-13 515.02

The DSO identification is too long to be added in all the clauses where needed which makes the code hard to read.
The next step is to replace the DSO identifiers with Aliases:

SELECT
No Alias. The statement will work until we add an-
FirstName
other column FirstName, selected from another source
, C.LastName
, C.DateModified AS Cust_DateModified 1. Bind to alias, assigned in the FROM clause (C, S)
, S.DateModified AS Sale_DateModified 2. Assign the alias in the SELECT clause (column name)
, (S.Quantity * S.Price) AS LineTotal
FROM
dbo.Customers AS C
Assign alias
JOIN dbo.Sales AS S
ON C.CustomerID = S.CustomerID
Can’t use aliases, assigned in the
WHERE (S.Quantity * S.Price) >= 500
SELECT clause in the WHERE clause
ORDER BY LineTotal DESC;
GO

139
Aliases

With the keyword AS we assign an alias. The keyword AS is optional and can be omitted.
With . (dot) we bind to an already assigned alias.

Don’t skip the keyword AS. It is very useful for a quick search of aliases in the development
or debugging processes.

Search for Find


AS Assign
. (dot) Bind

When we JOIN data sources, we always add aliases to always point to the exact column in the exact source.
If
(
We don’t use aliases
and
Modify a data source, used by the statement (add column with the same name as a col-
umn in another data source, used by the statement)
)
{ the execution stops with the error message “Ambiguous column name”. };

Assign TableName and ColumnName(s) in one alias

We can assign column names of an alias object by adding the list next to the table alias in round braces:

SELECT FN LN
C.FN Anabel Larson
, C.LN Anna Laurier
FROM
(
C
SELECT TOP 2
FirstName LastName
FirstName Anabel Larson
, LastName Anna Laurier
FROM dbo.Customers
) AS C (FN, LN);
GO
Assign table alias and column aliases in braces

Alias in the SELECT clause

In the SELECT clause we can use assign (creates a pseudo name for the column) and bind (binds to the data source
in the FROM clause) aliases.

140
Aliases

If we skip the alias, the column header is (No column name): (No column name)
SELECT TOP 3 CONCAT(FirstName, ' ', LastName) Anabel Larson
FROM dbo.Customers Anna Laurier
GO Beverly

Assign alias (column name)

SELECT TOP 3 CONCAT(FirstName, ' ', LastName) AS Name


SELECT TOP 3 CONCAT(C.FirstName, ' ', C.LastName) Bind Alias (to ON
SELECT TOP 3 CONCAT(FirstName, ' ', LastName) AS [Customer Name] in the FROM clause)

Alias, including special


character is wrapped in
square brackets ([])

Create aliases without special characters, to facilitate the usage of the column names in the requesting
application.

Alias with equal sign (=)

We can assign an alias with the equal sign: • Can be used only in the SELECT clause
! • The syntax that assigns value to variable is similar:
Alias Column Identifier

DECLARE @FN VARCHAR(128);


SELECT FN = FirstName
SELECT @FN = FirstName
FROM dbo.Customers;
FROM dbo.Customers;
GO
GO

will assign a value to the variable @FN.

Variables on page 199

Alias in the FROM clause

The assign alias in the FROM clause creates a pseudo name for the DB object.
Bind alias in ON clause, pointing to the assign alias in the FROM clause

Alias in the WHERE, GROUP BY, HAVING and ORDER BY clauses

Only bind aliases.

141
DML Statements (Query)
WHERE

SELECT... (FROM...) WHERE ...or filter the recordset

After a virtual recordeset (VR) is built with the SELECT and FROM (if FROM is used) clauses, we can filter the rows with
the WHERE clause.

How it works?
1. The SELECT (and FROM) clause(s) build the source virtual recordset (SVR)
2. The filter condition(s) in the WHERE clause is/are verified on every row of the SVR
3. If all the filtered conditions return True, the row is selected in the resulting recordert (RR)

Source virtual record-


set (source of the
The source of the data (table Customers) Resulting recordert
data to be filtered
with the WHERE clause)
Customers
First- Last- First- Last- First- Last-
Email
Name Name Name Name Name Name
Anabel Larson anabel.larson@customer5.info Anabel Larson Anabel Larson
Anna Laurier anna.laurier@customer2.net Anna Laurier Anna Laurier
Beverly NULL NULL Beverly NULL
John Smith john.smith@customer1.com John Smith
John Smith john.smith@customer3.org John Smith

SELECT
FirstName
, LastName
FROM dbo.Customers
WHERE LastName LIKE 'La%';
GO

The basic syntax is:

SELECT...
FROM...
WHERE {Left Operand} {Operator} {Right Operand};
GO

In the statement:

SELECT 'ABC' AS ColumnName1


WHERE 17 = 45;
GO

142
DML Statements (Query)
WHERE

• The SELECT clause builds the SVR (one row, one column and value 'ABC')
• The WHERE clause is validated once (one row in the SVR) and the result of the validation is False (17 doesn’t
equal 45)
• The RR is blank (nothing is selected)

The statement:

SELECT 'ABC' AS ColumnName1


WHERE 17 = 17;
GO

returns a RR with one row, one column and value 'ABC', because the filter condition in the where clause is
validated to True (17 equals 17).

The full syntax is:


SELECT
T1.ColumnName2
, T2.ColumnName3
, ... Left Operand
FROM Operator
[DatabaseName].[SchemaName].[Table1Name] AS T1 Right Operand
JOIN [DatabaseName].[SchemaName].[Table2Name] AS T2
ON T1.ColumnName1 = T2.ColumnName1
WHERE
{NOT logical operator} [Bind alias].[Filtered column name] {comparison or logical operator} {compared
value, expression or VR} Filter condition 1
{AND, OR logical operator} {NOT logical operator} [Bind Alias].[Filtered column name] {comparison or
logical operator} {compared value, expression or VR} Filter condition 2
AND ...; Filter condition 3
GO

The filtering logic is:


• The condition(s) is/are validated on every row of the SVR
• The validation returns Boolean value - True, False or Unknown (NULL)
• The rows from the SVR validated as True are selected

NULL and (Three-valued Logic) on page 69

NOT (logical operator)


Reverses the verification result of the filter condition.

143
DML Statements (Query)
WHERE

[Bind alias].[Filtered column name] Left operand of the comparison


Column in the SVR on which the filter condition is applied.
Operator (comparison or logical) Operator
Determines how to compare the values in the filter condition.

Operators on page 71

Compared value, expression or recordset (VR) Right operand of the comparison


Value, expression or VR that is compared with the filtered column.
Operator that joins filter conditions (logical operator)
• AND - the row in the SVR is selected if the left AND the right filter condition returns True
• OR - the row in the SVR is selected if one of the left OR the right filter condition returns True

Example with one filter condition

SELECT *
FROM dbo.Customers
WHERE FirstName = 'John';
GO
Operator
Left operand Right operand

Filter condition
Customers
First- Last- Row is re-
Email FirstName = 'John'?
Name Name turned?
Anabel Larson anabel.larson@customer5.info False No
Anna Laurier anna.laurier@customer2.net False No
Beverly NULL NULL False No
John Smith john.smith@customer1.com True Yes
John Smith john.smith@customer3.org True Yes
Melanie Larson melanie.larson@customer4.biz False No
Xavier Jameson xavier.garcía@customer6.com False No
Zak Smith NULL False No

Example with more than one filter conditions (AND, OR logical operators)

The logical operator AND:


• Additionally narrows down the selected result, because all conditions joined with AND have to be valid (re-
turn True)
• Marks the rows to be returned where the left AND the right conditions evaluate to True

144
DML Statements (Query)
WHERE

SELECT *
FROM dbo.Customers
WHERE
FirstName = 'Anabel' Left filter condition
AND LastName = 'Larson'; Right filter condition
GO
Logical operator AND which joins the filter conditions

Customers
First- Last- FirstName = Logical Join LastName = Row is re-
Email
Name Name 'Anabel'? Operator 'Larson'? turned?
Anabel Larson anabel.larson@customer5.info True AND True Yes
Anna Laurier anna.laurier@customer2.net False AND False No
Beverly NULL NULL False AND False No
John Smith john.smith@customer1.com False AND False No
John Smith john.smith@customer3.org False AND False No
Melanie Larson melanie.larson@customer4.biz False AND True No
Xavier Jameson xavier.garcía@customer6.com False AND False No
Zak Smith NULL False AND False No

Filter
condition

The operator OR:


• returns the lines where either left OR right condition evaluates to True

SELECT *
FROM dbo.Customers
WHERE
FirstName = 'Anabel' Left filter condition
OR LastName = 'Larson'; Right filter condition
GO
Logical operator AND that joins the filter conditions
Customers
First- Last- FirstName = Logical Join LastName = Row is re-
Email
Name Name 'Anabel'? Operator 'Larson'? turned?
Anabel Larson anabel.larson@customer5.info True OR True Yes
Anna Laurier anna.laurier@customer2.net False OR False No
Beverly NULL NULL False OR False No
John Smith john.smith@customer1.com False OR False No
John Smith john.smith@customer3.org False OR False No
Melanie Larson melanie.larson@customer4.biz False OR True Yes
Xavier Jameson xavier.garcía@customer6.com False OR False No
Zak Smith NULL False OR False No

145
DML Statements (Query)
WHERE

More than one filter conditions, joined with both logical operator AND and OR:

SELECT *
FROM dbo.Customers Separate (in brackets) the OR opera-
WHERE tor(s) from the AND operator(s).
(
FirstName = 'Anabel' Left filter condition
OR LastName = 'Larson' Right filter condition
) Left filter condition (between the brackets)
AND Email LIKE '%.biz'; Right filter condition
GO

Email LIKE
'%.biz'?
Customers Logical Join AND
Operator Evaluation
First- Last-
Email
Name Name
Anabel Larson anabel.larson@customer5.info True OR True True AND False False No
Anna Laurier anna.laurier@customer2.net False OR False False AND False False No
Beverly NULL NULL False OR False False AND False False No
John Smith john.smith@customer1.com False OR False False AND False False No
John Smith john.smith@customer3.org False OR False False AND False False No
Melanie Larson melanie.larson@customer4.biz False OR True True AND True True Yes
Xavier Jameson xavier.garcía@customer6.com False OR False False AND False False No
Zak Smith NULL False OR False False AND False False No

FirstName = LastName = Row is


'Anabel'? 'Larson'? returned?
Logical Join OR
Operator Evaluation

The WHERE clause is used to filter not only SELECT statements; it is also used in other DML statements (UPDATE, DE-
LETE) that manipulate SVR.

UPDATE on page 118 DELETE on page 123

146
DML Statements (Query)
ORDER BY

The ORDER BY clause orders the resulting recordset (RR).

We can order on:


• Identifier - the name of the column in a table, view or table-valued function
• Alias - a substitute of the column name in the SELECT clause
• Column Index - the position of a column in the RR
Customers
Aliases on page 138
First- Last- DateOf-
Email
Name Name Birth
Anabel Larson anabel.larson@customer5.info 1953-03-15
Anna Laurier anna.laurier@customer2.net 1948-07-17
Beverly NULL NULL 1948-07-17
John Smith john.smith@customer1.com 1955-08-16
John Smith john.smith@customer3.org 1950-05-18
Melanie Larson melanie.larson@customer4.biz 1951-12-18
Xavier Jameson xavier.garcía@customer6.com 1941-08-23
Zak Smith NULL 1936-11-08 First- Last- DateOf-
Name Name Birth
Anabel Larson 1953-03-15
SELECT
Assign alias Melanie Larson 1951.12-18
FirstName AS FN
Column index is 3 (the John Smith 1955-08-16
, LastName
third column in the RR) John Smith 1950-05-18
, DateOfBirth
FROM dbo.Customers
WHERE DateOfBirth >= '1950-01-01'
ORDER BY
Column name
LastName
, FN ASC Bind alias
, 3 DESC; The column with index ASC - Ascending. Default, can be omitted
GO 3 is DateOfBirth DESC - Descending

The RR is ordered by:


1. LastName - ascending (L in Larson is before S in Smith in the alphabet)
2. FirstName - ascending (A in Anabel is before M in Melanie in the alphabet)
3. DateOfBirth - descending (for both John Smith year 1955 is after 1950 in the calendar)

We can ORDER BY column(s), not included in the RR (not in the SELECT clause).

ORDER BY is expensive operation. It is recommended to


! be used only when we need ordered resulting recordset.

147
DML Statements (Query)
TOP

SELECT TOP... ORDER BY...

When we extend the SELECT clause with the TOP keyword, we select a recordset with Number or Percent rows.

In order to return the exact result, TOP always has to be used together with ORDER BY clause.

The syntax is:


SELECT TOP Number PERCENT ColumnName(s) delimited with comma (,) or *
FROM ObjectName - table, view, table-valued function
ORDER BY ColumnName (ASC, DESC); ASC is default and can be omitted
GO

Customers
First- Last- DateOf-
Email
Name Name Birth
Anabel Larson anabel.larson@customer5.info 1967-05-11
Anna Laurier anna.laurier@customer2.net 1967-05-11
Beverly NULL NULL 1965-11-09
John Smith john.smith@customer1.com 1963-07-19
John Smith john.smith@customer3.org 1965-11-09
Melanie Larson melanie.larson@customer4.biz 1965-09-08
Xavier Jameson xavier.garcía@customer6.com 1965-11-09
Zak Smith NULL 1963-12-11

SELECT TOP (3) First- Last- DateOf-


FirstName Name Name Birth
Number of rows
, LastName Anabel Larson 1967-05-11
, DateOfBirth Anna Laurier 1967-05-11
FROM dbo.Customers Beverly NULL 1965-11-09
ORDER BY
DateOfBirth DESC
, LastName
, FirstName;
GO

SELECT TOP... WITH TIES

WITH TIES adds ties to the resulting recordset.

148
DML Statements (Query)
TOP

We select TOP (3), but DateOfBirth =


1965-11-09 exists also for John Smith and First- Last- DateOf-
Xavier Jameson (5 rows selected - ties) Name Name Birth
Anabel Larson 1967-05-11
Anna Laurier 1967-05-11
SELECT TOP (3) WITH TIES * Beverly NULL 1965-11-09
FROM dbo.Customers John Smith 1965-11-09
ORDER BY DateOfBirth DESC; Xavier Jameson 1965-11-09
GO

SELECT TOP... PERCENT

Select specified percent of all the rows in the source recordset.

SELECT TOP (35) PERCENT *


First- Last- DateOf-
FROM dbo.Customers Name Name Birth
ORDER BY
Anabel Larson 1967-05-11
DateOfBirth DESC
Anna Laurier 1967-05-11
, LastName
Beverly NULL 1965-11-09
, FirstName;
GO

149
DML Statements (Query)
OFFSET... FETCH

SELECT TOP... ORDER BY... OFFSET... FETCH

Starting from any row but the first, we can select rows from ordered recordsets. To do this, we use the OFFSET
clause, added to the ORDER BY clause.

Custo- First- Last- Email Customers


merID Name Name
1 Anabel Larson anabel.larson@customer5.info
2 Anna Laurier anna.laurier@customer2.net
3 Beverly NULL NULL
4 John Smith john.smith@customer1.com CustomerID from 1 to 3 are skipped
5 John Smith john.smith@customer3.org
6 Melanie Larson melanie.larson@customer4.biz
7 Xavier Jameson xavier.garcía@customer6.com First- Last- Email
Custo-
merID

Name Name
8 Zak Smith NULL

4 John Smith john.smith@customer1.com


SELECT * 5 John Smith john.smith@customer3.org
FROM dbo.Customers 6 Melanie Larson melanie.larson@customer4.biz
ORDER BY 7 Xavier Jameson xavier.garcía@customer6.com
FirstName 8 Zak Smith NULL
, LastName
OFFSET 3 ROWS;
GO
OFFSET 1 ROW, OFFSET X ROWS

By adding the FETCH clause to OFFSET, we can pick a specified number of rows, starting from the row specified in
OFFSET:

SELECT *
FROM dbo.Customers
ORDER BY
FirstName 1 ROW ONLY, X ROWS ONLY
, LastName
OFFSET 3 ROWS
Custo- First- Last- Email
FETCH NEXT 3 ROWS ONLY; merID Name Name
GO 5 John Smith john.smith@customer3.org

FETCH FIRST, FETCH NEXT 4 John Smith john.smith@customer1.com


6 Melanie Larson melanie.larson@customer4.biz

OFFSET and FETCH are very useful when CustomerID from 1 to 3 are skipped
we need to paginate the result. and the next 3 rows are selected

150
DML Statements (Query)
GROUP BY

By adding a GROUP BY clause, we create groups from columns and calculate values for other columns.
• Grouped columns (a.k.a. Dimensions) – the columns that define the scope for aggregation (the groups)
• Aggregated columns (a.k.a. Facts, Measures or Properties) – summarized data for the groups

The syntax is: SELECT


Col1
Grouped column
, Col2
Source of the data , Aggregation Function(Col3)
Aggregated column
, Aggregation Function(Col4)
FROM [SchemaName].[ObjectName]
GROUP BY
Col1
Grouped column
, Col2;
dbo.GroupBy1 GO

Grouped columns Not selected columns Aggregated columns


Col-
Col1 Col2 Col3 Col4 Col5 Col6 Col7 Col8 Col9 Col10
umn 1
Group 1 1 2 3 NULL 4 3 3 6 8
1 2 3 A B 4 4 1 6 2
1 2 3 D C NULL 5 8 7 2
Group 2 2 1 3 15 23 4 1 12 23 2
2 1 3 6 C 8 7 4 78.5 3
2 1 3 8 7 8 4 -56 5 1
2 1 3 FA S 5 NULL 6 5 4
Group 3 3 2 1 NULL SD 2 6 BCD BCD ABE
3 2 1 16 R 8 23 ABD ABD ABC
3 2 1 D 2 17 5 ABC ABC ABC
3 2 1 RH 42 45 9 AAB AAB ABD

First we build a virtual Next we modify the query to create groups on Col1, Col2 and Col3 and aggregate
recordset: the data in columns Col6 to Col10:
SELECT SELECT

}
Col1, Col2, Col3 Col1 AS [Column 1]
Grouped columns (Source is struc-
, Col6, Col7, Col8 , Col2
ture tables or Dimensions in OLAP)
, Col9, Col10 , Col3

}
FROM dbo.GroupBy1 , SUM(Col6) AS [Sum Col6]
Aggregated columns
GO , AVG(Col7) AS Avg_Col7
(Source is data tables
, MIN(Col8) AS Min_Col8
or Facts in OLAP.
, MAX(Col9) AS Max_Col9 A.k.a. Measures in
, COUNT(Col10) AS Count_Col10 the reporting slang)
, COUNT(DISTINCT Col10) AS CountDistinct_Col10
FROM dbo.GroupBy1

151
DML Statements (Query)
GROUP BY

GROUP BY

}
Col1
Grouped columns. We can’t
, Col2
use the alias Column 1 here Resulting recordset
, Col3;
GO

dbo.GroupBy1
SUM() AVG() MIN() MAX() COUNT() COUNT(DISTINCT)
Column 1 Col2 Col3 Sum Col6 Avg_Col7 Min_Col8 Max_Col9 Count_Col10 CountDistinct_Col10
Group 1 1 2 3 8.000 4.000000 1 7 3 2
Group 2 2 1 3 25.000 4.000000 -56 78.5 4 4
Group 3 3 2 1 72.000 10.750000 AAB BCD 4 3
Grouped columns Aggregated columns

Numbers ordered first Characters ordered last

The grouped columns in the resultset are unique (no duplicates for Col 1, Col2 and Col3).
The aggregate functions summarize the values for the aggregated columns.

• NULL is excluded from the aggregations


! In Col7, Group 2, the values are 1, 7, 4 and NULL
AVG() returns 4 ((1 + 7 + 4) / 3) and not 3 ((1 + 7 + 4 + NULL) / 4)

• GROUP BY can’t use the bound aliases, assigned in the SELECT clause, because GROUP BY is before SE-
LECT (where the aliases are assigned) in the execution order

Execution Logic on page 163

Aggregate Function Returns


SUM(ColumnName) Sums the values in the column
MIN(ColumnName) Selects the minimum value in the column
MAX(ColumnName) Selects the maximum value in the column
AVG(ColumnName) Selects the average of the values in the column
Aggregate Function, cov-
COUNT(ColumnName) Counts the values in the column
ered by this book
COUNT(DISTINCT ColumnName) Count unique values in the column

GROUP BY is like SELECT DISTINCT

When we SELECT columns and GROUP BY the same columns, without aggregating other columns, the resultset is the
same as if we SELECT DISTINCT these columns.

152
DML Statements (Query)
GROUP BY

SELECT SELECT DISTINCT


Col1 Col1
No aggregated columns
, Col2 , Col2
, Col3 and , Col3
, Col6 , Col6
FROM dbo.GroupBy1 FROM dbo.GroupBy1;
GROUP BY GO
Col1
, Col2
The same columns as
, Col3 in the SELECT clause
, Col6
GO create the same resultset:

Col1 Col2 Col3 Col6


1 2 3 NULL
1 2 3 4
2 1 3 4
2 1 3 5
2 1 3 8
3 2 1 2
3 2 1 8
3 2 1 17
3 2 1 45

153
DML Statements (Query)
HAVING

The HAVING clause filters already aggregated data. It can’t be used without the GROUP BY clause.

The data flow is:


1. Create a recordset - SELECT... FROM... JOIN
2. Filter the recordset - WHERE
3. Aggregate the recordset - GROUP BY
4. Filter the aggregated recordset - HAVING

The step by step example below selects the customers with total sales more or equal to 150$ for the period from
1965-10-06 to 1967-05-15.

The structure table Items is not shown


Customers Sales
Custo- First- Last- DateModi-
Email
CustomerID

merID Name Name fied

Quantity
ItemID
SaleID

1 Anabel Larson anabel.larson@ 1966-12-11 DateOf- DateModi-


customer5.info Price
Sale fied
2 Anna Laurier anna.laurier@ 1964-05-13
customer2.net
3 Beverly NULL NULL 1964-08-15
1 3 2043 1967-01-24 4 1.27 1967-01-24
4 John Smith john.smith@cus- 1963-04-25
2 4 5164 1967-05-15 2 1.56 1967-05-15
tomer1.com
3 3 5293 1966-12-11 77 55.12 1967-01-14
5 John Smith john.smith@cus- 1968-05-13
tomer3.org 4 5 6223 1969-01-17 3 4.89 1969-01-17
6 Melanie Larson melanie.larson@ 1963-12-31 5 7 1352 1965-10-14 89 0.84 1965-10-16
customer4.biz 6 2 1953 1966-03-05 53 2.84 1966-03-05
7 Xavier Jameson xavier.garcía@ 1968-04-28 7 3 8613 1963-04-13 22 23.41 1963-04-13
customer6.com
8 2 5523 1967-09-18 55 127.19 1967-09-18
8 Zak Smith NULL 1963-12-11
9 1 8534 1962-10-13 1 45.88 1963-04-15
1
10 2 9375 1965-10-06 6 0.43 1966-04-13

154
DML Statements (Query)
HAVING

ItemID
DateOf- First- Last- LineTo-
1. Create a recordset (JOIN tables Customers and Sales)
Sale Name Name tal
SELECT
1967-01-24 Beverly NULL 2043 5.08
S.DateOfSale
1967-05-15 John Smith 5164 3.12
, C.FirstName
1966-12-11 Beverly NULL 5293 4244.24
, C.LastName
1969-01-17 John Smith 6223 14.67
, S.ItemID
1965-10-14 Xavier Jameson 1352 74.76
, (S.Quantity * S.Price) AS LineTotal
1966-03-05 Anna Laurier 1953 150.52
FROM
1963-04-13 Beverly NULL 8613 515.02
dbo.Customers AS C
1967-09-18 Anna Laurier 5523 6995.45
JOIN dbo.Sales AS S
1962-10-13 Anabel Larson 8534 45.88
ON C.CustomerID = S.CustomerID;
1965-10-06 Anna Laurier 9375 2.58
GO

2. Filter the recordset (sales WHERE the period is BETWEEN 1965-10-06 to 1967-05-15)

SELECT
S.DateOfSale DateOf- First- Last- ItemID LineTo-
Sale Name Name tal
, C.FirstName
, C.LastName 1966-03-05 Anna Laurier 1953 150.52
, S.ItemID 1965-10-06 Anna Laurier 9375 2.58
, (S.Quantity * S.Price) AS LineTotal 1967-01-24 Beverly NULL 2043 5.08
FROM 1966-12-11 Beverly NULL 5293 4244.24
dbo.Customers AS C 1967-05-15 John Smith 5164 3.12
JOIN dbo.Sales AS S 1965-10-14 Xavier Jameson 1352 74.76
ON C.CustomerID = S.CustomerID
WHERE S.DateOfSale BETWEEN '1965-10-06' AND '1967-05-15';
GO

155
DML Statements (Query)
HAVING

3. Aggregate the recordset (SUM() the sales for each customer)

SELECT First- Last- Sum_


C.FirstName Name Name LineTotal
, C.LastName Anna Laurier 153.10 150.52 + 2.58
, SUM(S.Quantity * S.Price) AS Sum_LineTotal Beverly NULL 4249.32 4244.24 + 5.08
FROM John Smith 3.12
dbo.Customers AS C Xavier Jameson 74.76
JOIN dbo.Sales AS S
ON C.CustomerID = S.CustomerID
WHERE S.DateOfSale BETWEEN '1965-10-06' AND '1967-05-15'
GROUP BY
C.FirstName
, C.LastName;
GO

4. Filter the aggregated recordset (customers HAVING total sales more or equal to 150$)

SELECT First- Last- Sum_


C.FirstName Name Name LineTotal
, C.LastName Anna Laurier 153.10
>= 150
, SUM(S.Quantity * S.Price) AS Sum_LineTotal Beverly NULL 4249.32
FROM
dbo.Customers AS C
JOIN dbo.Sales AS S
ON C.CustomerID = S.CustomerID
WHERE S.DateOfSale BETWEEN '1965-10-06' AND '1967-05-15'
GROUP BY
C.FirstName
, C.LastName
HAVING SUM(S.Quantity * S.Price) >= 150;
GO

! HAVING can’t bind the aliases, assigned in the SELECT statement

156
DML Statements (Query)
GROUPING SETS

Combines multiple GROUP BY statements into one.

Sales The statement below returns aggregated data for groups:


Customer Store Item SaleValue • Total • Customer
Customer 1 Store 1 Item 3 303.6996 • Store • Item
Customer 1 Store 5 Item 1 368.5147
SELECT
Customer 2 Store 2 Item 1 366.3898
Customer
Customer 2 Store 2 Item 4 109.3525
, Store
Customer 2 Store 5 Item 6 949.0834
, Item
Customer 3 Store 1 Item 6 615.9823
, SUM(SaleValue) AS Sum_SaleValue
Customer 4 Store 1 Item 3 911.5682
FROM dbo.Sales
Customer 4 Store 4 Item 7 93.2123
GROUP BY GROUPING SETS
Customer 4 Store 5 Item 1 95.5645
(
Customer 5 Store 2 Item 3 550.4955
Customer
Customer 5 Store 4 Item 4 914.9231
, Store
Customer 5 Store 4 Item 4 679.8012
, Item
Customer 6 Store 5 Item 3 758.6422 Total
, ()
Customer 6 Store 5 Item 3 951.0204
)
Customer 7 Store 2 Item 2 923.3026
ORDER BY
Customer 8 Store 4 Item 5 813.9188
Customer
, Store
, Item
GO

Customer Store Item Sum_SaleValue S = 9405.4711

}
NULL NULL NULL 9405.4711 } Total
NULL NULL Item 1 830.469
NULL NULL Item 2 923.3026
NULL NULL Item 3 3475.4259
NULL NULL Item 4 1704.0768 Item Customer 3 NULL NULL 615.9823

NULL NULL Item 5 813.9188 Customer 4 NULL NULL 1100.345

NULL NULL Item 6 1565.0657 Customer 5 NULL NULL 2145.2198

NULL NULL Item 7 93.2123 Customer 6 NULL NULL 1709.6626

}
NULL Store 1 NULL 1831.2501 Customer 7 NULL NULL 923.3026

NULL Store 2 NULL 1949.5404 Customer 8 NULL NULL 813.9188


Store
NULL Store 4 NULL 2501.8554
NULL Store 5 NULL 3122.8252
Customer 1 NULL NULL 672.2143
Customer 2 NULL NULL 1424.8257

157
DML Statements (Query)
GROUPING SETS

The statement below returns aggregated data for groups:


• Total
• Store and Item
• Customer and Item
SELECT
Customer Customer Store Item Sum_SaleValue
, Store NULL NULL NULL 9405.4711
, Item NULL Store 1 Item 3 1215.2678
, SUM(SaleValue) AS Sum_SaleValue NULL Store 1 Item 6 615.9823
FROM dbo.Sales NULL Store 2 Item 1 366.3898
GROUP BY GROUPING SETS NULL Store 2 Item 2 923.3026
( Customer and Item NULL Store 2 Item 3 550.4955
(Customer, Item) NULL Store 2 Item 4 109.3525
, (Store, Item) NULL Store 4 Item 4 1594.7243
, () Store and Item NULL Store 4 Item 5 813.9188
) NULL Store 4 Item 7 93.2123
ORDER BY Total NULL Store 5 Item 1 464.0792
Customer NULL Store 5 Item 3 1709.6626
, Store NULL Store 5 Item 6 949.0834 S =
, Item Customer 1 NULL Item 1 368.5147 9405.4711
GO Customer 1 NULL Item 3 303.6996
Customer 2 NULL Item 1 366.3898
Customer 2 NULL Item 4 109.3525
Customer 2 NULL Item 6 949.0834
Customer 3 NULL Item 6 615.9823
The same result will be created by multiple
Customer 4 NULL Item 1 95.5645
UNIONed GROUP BY statements:
Customer 4 NULL Item 3 911.5682
Customer 4 NULL Item 7 93.2123
Customer 5 NULL Item 3 550.4955
SELECT A.* Total Customer 5 NULL Item 4 1594.7243
FROM
Customer 6 NULL Item 3 1709.6626
(
Customer 7 NULL Item 2 923.3026
SELECT
Customer 8 NULL Item 5 813.9188
NULL AS Customer
, NULL AS Store
, NULL AS Item
, SUM(SaleValue) AS Sum_SaleValue
FROM dbo.Sales

UNION ALL

158
DML Statements (Query)
GROUPING SETS

SELECT
Customer Customer and Item
, NULL AS Store
, Item
, SUM(SaleValue) AS Sum_SaleValue
FROM dbo.Sales
GROUP BY
Customer
, Item

UNION ALL

SELECT Store and Item


NULL AS Customer
, Store
, Item
, SUM(SaleValue) AS Sum_SaleValue With GROUPING SETS we can present different slice
FROM dbo.Sales and dices of the data in one statement.
GROUP BY
Store
, Item
) AS A
ORDER BY
A.Customer
, A.Store
, A.Item
GO

159
DML Statements (Query)
ROLLUP and CUBE

When we aggregate data, we add subtotals and a grand total to the resultset with ROLLUP and CUBE.

Sales SaleID CustomerName ItemDescription DateOfSale Quantity Price


1 Xavier Jameson Plastic Plain Blue 1967-01-24 4 1.27
2 John Smith Plastic Plain Blue 1967-05-15 2 1.56
3 Xavier Jameson Rag Doll 33 cm. 1966-12-11 77 55.12
4 Anabel Larson Plastic Plain Blue 1969-01-17 3 4.89
5 Anabel Larson Wooden Horse 1965-10-14 89 0.84
6 John Smith Rag Doll 33 cm. 1966-03-05 53 2.84
7 Xavier Jameson Wooden Horse 1963-04-13 22 23.41
8 John Smith Rag Doll 33 cm. 1967-09-18 55 127.19
9 Anabel Larson Wooden Horse 1962-10-13 1 45.88
10 John Smith Plastic Plain Blue 1965-10-06 6 0.43

Add ROLLUP to the GROUP BY clause to calculate subtotals and the grand total for the grouped columns:

SELECT DateOf- SalesVal-


CustomerName ItemDescription
CustomerName Sale ue
, ItemDescription NULL NULL NULL 12051.32
, DateOfSale Anabel Larson NULL NULL 135.31
, SUM(Quantity * Price) AS SalesValue Anabel Larson Plastic Plain Blue NULL 14.67
FROM dbo.Sales Anabel Larson Plastic Plain Blue 1969-01-17 14.67
GROUP BY ROLLUP Anabel Larson Wooden Horse NULL 120.64
( Anabel Larson Wooden Horse 1962-10-13 45.88
CustomerName Anabel Larson Wooden Horse 1965-10-14 74.76
, ItemDescription John Smith NULL NULL 7151.67
, DateOfSale John Smith Plastic Plain Blue NULL 5.70
) John Smith Plastic Plain Blue 1965-10-06 2.58
ORDER BY John Smith Plastic Plain Blue 1967-05-15 3.12
CustomerName John Smith Rag Doll 33 cm. NULL 7145.97
, ItemDescription John Smith Rag Doll 33 cm. 1966-03-05 150.52
, DateOfSale; John Smith Rag Doll 33 cm. 1967-09-18 6995.45
GO Xavier Jameson NULL NULL 4764.34
Xavier Jameson Plastic Plain Blue NULL 5.08
Level 1 (GROUP BY CustomerName, Xavier Jameson Plastic Plain Blue 1967-01-24 5.08
ItemDescription, SaleDate) Xavier Jameson Rag Doll 33 cm. NULL 4244.24
Xavier Jameson Rag Doll 33 cm. 1966-12-11 4244.24
Level 2 (GROUP BY Customer-
Subtotal Xavier Jameson Wooden Horse NULL 515.02
Name, ItemDescription)
Xavier Jameson Wooden Horse 1963-04-13 515.02
Level 3 (GROUP BY CustomerName)

Level 4 (No GROUP BY) Grand Total

160
DML Statements (Query)
ROLLUP and CUBE

All the rows with NULL in a column, belonging to a group (not an aggregated column) are added by ROLLUP operator.

We can use the GROUPING() function to transform NULLs to human readable values:
SELECT
CASE
WHEN GROUPING(CustomerName) = 1 CASE on page 186
THEN 'Total: Customer'
ELSE CustomerName
SalesVal-
END AS Customer Customer Item Date
ue
, CASE Total: Customer Total: Item Total: Date 12051.32
WHEN GROUPING(ItemDescription) = 1 Anabel Larson Total: Item Total: Date 135.31
THEN 'Total: Item' Anabel Larson Plastic Plain Blue Total: Date 14.67
ELSE ItemDescription
Anabel Larson Plastic Plain Blue 1969-01-17 14.67
END AS Item
Anabel Larson Wooden Horse Total: Date 120.64
, CASE
Anabel Larson Wooden Horse 1962-10-13 45.88
WHEN GROUPING(DateOfSale) = 1
Anabel Larson Wooden Horse 1965-10-14 74.76
THEN 'Total: Date of Sale'
John Smith Total: Item Total: Date 7151.67
ELSE CONVERT(VARCHAR(17), DateOfSale)
John Smith Plastic Plain Blue Total: Date 5.70
END AS DateOfSale
John Smith Plastic Plain Blue 1965-10-06 2.58
, SUM(Quantity * Price) AS SalesValue
John Smith Plastic Plain Blue 1967-05-15 3.12
FROM dbo.Sales
John Smith Rag Doll 33 cm. Total: Date 7145.97
GROUP BY ROLLUP
John Smith Rag Doll 33 cm. 1966-03-05 150.52
(
John Smith Rag Doll 33 cm. 1967-09-18 6995.45
CustomerName
Xavier Jameson Total: Item Total: Date 4764.34
, ItemDescription
Xavier Jameson Plastic Plain Blue Total: Date 5.08
, DateOfSale
Xavier Jameson Plastic Plain Blue 1967-01-24 5.08
)
Xavier Jameson Rag Doll 33 cm. Total: Date 4244.24
ORDER BY
Xavier Jameson Rag Doll 33 cm. 1966-12-11 4244.24
CustomerName
Xavier Jameson Wooden Horse Total: Date 515.02
, ItemDescription
Xavier Jameson Wooden Horse 1963-04-13 515.02
, DateOfSale;
GO

CUBE

When we add CUBE to the GROUP BY clause, we calculate subtotals and grand totals for all the combinations in the
grouped columns:
Sales SaleID CustomerName ItemDescription SaleDate Quantity Price
1 Xavier Jameson Plastic Plain Blue 1967-01-24 4 1.27
2 Anabel Larson Wooden Horse 1962-10-13 1 45.88
3 Anabel Larson Wooden Horse 1964-11-07 1 41.32

161
DML Statements (Query)
ROLLUP and CUBE

SELECT Sales-
Customer Item Date
CASE Value
WHEN GROUPING(CustomerName) = 1 Total: Customer Total: Item Total: Date 92.28
THEN 'Total: Customer' Total: Customer Total: Item 1962-10-13 45.88
ELSE CustomerName Total: Customer Total: Item 1964-11-07 41.32
END AS Customer Total: Customer Total: Item 1967-01-24 5.08
, CASE Total: Customer Plastic Plain Blue Total: Date 5.08
WHEN GROUPING(ItemDescription) = 1 Total: Customer Plastic Plain Blue 1967-01-24 5.08
THEN 'Total: Item' Total: Customer Wooden Horse Total: Date 87.20
ELSE ItemDescription Total: Customer Wooden Horse 1962-10-13 45.88
END AS Item Total: Customer Wooden Horse 1964-11-07 41.32
, CASE Anabel Larson Total: Item Total: Date 87.20
WHEN GROUPING(DateOfSale) = 1 Anabel Larson Total: Item 1962-10-13 45.88
THEN 'Total: Date of Sale' Anabel Larson Total: Item 1964-11-07 41.32
ELSE CONVERT(VARCHAR(17), DateOfSale) Anabel Larson Wooden Horse Total: Date 87.20
END AS [Date] Anabel Larson Wooden Horse 1962-10-13 45.88
, SUM(Quantity * Price) AS SalesValue Anabel Larson Wooden Horse 1964-11-07 41.32
FROM dbo.Sales Xavier Jameson Total: Item Total: Date 5.08
GROUP BY CUBE Xavier Jameson Total: Item 1967-01-24 5.08
( Xavier Jameson Plastic Plain Blue Total: Date 5.08
CustomerName Xavier Jameson Plastic Plain Blue 1967-01-24 5.08
, ItemDescription
, DateOfSale
)
ORDER BY
CustomerName
In this example CUBE expanded
, ItemDescription 3 rows to 19 rows:
, DateOfSale; • 3 rows details
GO • 15 rows subtotals
• 1 row grand total
GROUP BY:
No GROUP BY
Date
Item
Item, Date
Customer
Customer, Date
Customer, Item
Customer, Item, Date

162
DML Statements (Query)
Execution Logic

The clauses in the DML query statement are ordered as follows:


1. SELECT
2. FROM… JOIN
3. WHERE
4. GROUP BY
5. HAVING
6. ORDER BY

Behind the scene SQL Server® is executing the DML query statement in a different order:
Step 1: FROM… JOIN - build the virtual recordset (VR) from one or multiple data sources (DS)
Step 2: WHERE - filter the VR
Step 3: GROUP BY... HAVING - group, aggregate and filter the grouped VR
Step 4: SELECT – build the resulting VR and create aliases
Step 5: ORDER BY – order the resulting VR and use the aliases, created in the SELECT statement

Step 1: FROM Sales AS S JOIN Customers AS C ON... Step 2: WHERE C.CustomerName != 'Beverly'

Virtual recordset Virtual recordset


Customers Sales Customers Sales
Custo- Customer- Custo- LineTo- Custo- Customer- Custo- LineTo-
merID Name merID tal merID Name merID tal
5 Melanie Larson 5 21.35 Resultset 5 Melanie Larson 5 21.35
from FROM
3 Beverly 3 5.29 1 Anabel Larson 1 53.29
1 Anabel Larson 1 53.29 2 Anna Laurier 2 96.31
2 Anna Laurier 2 96.31 Datasource 4 John Smith 4 7.65
3 Beverly 3 18.16 for WHERE 5 Melanie Larson 5 3.14
4 John Smith 4 7.65
5 Melanie Larson 5 3.14 CustomerName 'Beverly' is excluded

Resultset
from WHERE

163
DML Statements (Query)
Execution Logic

Datasource
for GROUP BY

Step 3: GROUP BY C.CustomerID...


Step 4: HAVING SUM(S.LineTotal) >= 20
Virtual recordset
Virtual recordset
Customers Sales
Custo- Customer- Custo- LineTo- Resultset Customers Sales
merID Name merID tal from Custo- Customer- Custo- LineTo-
1 Anabel Larson 1 53.29 GROUP BY merID Name merID tal
2 Anna Laurier 2 96.31 1 Anabel Larson 1 53.29
4 John Smith 4 7.65 2 Anna Laurier 2 96.31
Datasource
5 Melanie Larson 5 24.49 for HAVING 5 Melanie Larson 5 24.49

LineTotal for CustomerID = 5 is summed CustomerID = 4 is excluded

Resultset
Datasource from HAVING
for SELECT

Step 5: SELECT CustomerName, LineTotal AS LT Step 6: ORDER BY LT DESC

Resulting recordset Resulting recordset


Customers Sales Resultset from SELECT Customers Sales
Customer- Customer-
LT LT
Name Name
Datasource for ORDER BY
Anabel Larson 53.29 Anna Laurier 96.31
Anna Laurier 96.31 Anabel Larson 53.29
Melanie Larson 24.49 Melanie Larson 24.49

164
DML Statements (Query)
Subqueries

Subquery Outer
Inner
Subquery is a query (statement), nested in another query (statement).
During the execution, the subquery creates a virtual recordset (VR) a.k.a. derived (or virtual) table.
This recordset is the data source for the outer query (statement).

Scope of the subquery


The VR, created by the subquery is alive during the execution of the outer statement.

The subquery is executed on every row of the virtual recordset, created by


! the FROM clause (the outer query) and this can lead to bad performance.

We can use subquery in SELECT, FROM, WHERE and HAVING clauses.

Data sources for the examples below:

Customers Sales
SaleID Custo- ItemID DateOf- Quan- Price
Custo-
merID

First- Last- merID Sale tity


Email
Name Name 1 2 1953 1966-03-05 53 2.84
2 2 5523 1967-09-18 55 127.19
1 Anabel Larson anabel.larson@customer5.info
3 2 9375 1965-10-06 6 0.43
2 Anna Laurier anna.laurier@customer2.net
4 3 2043 1967-01-24 4 1.27
3 Beverly NULL NULL
5 3 5293 1966-12-11 77 55.12
4 John Smith john.smith@customer1.com
6 3 8613 1963-04-13 22 23.41
5 John Smith john.smith@customer3.org
7 4 5164 1967-05-15 2 1.56
6 Melanie Larson melanie.larson@customer4.biz
8 6 8534 1962-10-13 1 45.88
7 Xavier Jameson xavier.garcía@customer6.com
9 7 1352 1965-10-14 89 0.84
8 Zak Smith NULL
10 7 6223 1969-01-17 3 4.89

Subquery in the SELECT clause

The subquery in the SELECT clause has to return one value (one row and one column). If it returns more than one
value, an error message “Subquery returned more than 1 value. This is not permitted when the subquery follows =,
!=, <, <= , >, >= or when the subquery is used as an expression.” is returned:

SELECT This subquery returns more than 1


( value (two rows and one column)
SELECT CONCAT(FirstName, ' ', LastName) AS CustomerName
FROM dbo.Customers
WHERE Email IS NULL CustomerName
) AS CustomerName Beverly
, (S.Quantity * S.Price) AS LineTotal Zak Smith

165
DML Statements (Query)
Subqueries

FROM dbo.Sales AS S
GO

The statement below uses a subquery in the SELECT clause to show the average sales for all the customers next
to the individual sales:

• SELECT • Outer statement


C.FirstName
, C.LastName
, SUM(S.Quantity * S.Price) AS Sum_Sales
, ( • Inner statement (subquery) Returns 1 value
(one row and one column)
SELECT
• AVG(Quantity * Price)
FROM
• dbo.Sales
) AS Avg_SalesForAllCustomers
• FROM (No column name)

dbo.Customers AS C 1205.132

JOIN dbo.Sales AS S
ON C.CustomerID = S.CustomerID
• GROUP BY FirstName LastName
Sum_ Avg_SalesForAll-
Sales Customers
C.FirstName
Anna Laurier 7148.55 1205.132
, C.LastName
Beverly NULL 4764.34 1205.132
• ORDER BY
John Smith 3.12 1205.132
C.FirstName
Melanie Larson 45.88 1205.132
, C.LastName;
Xavier Jameson 89.43 1205.132
GO

Subquery in the FROM clause

The subquery in the FROM clause allows us to extract the portion of the data that we need (from multiple data
sources, aggregated, etc) and JOIN it to the other data sources in the FROM clause.
The statement below returns the customers without Email where their sales (if any) are on and after January
01, 1965:
SELECT
C.FirstName Bind alias
, C.LastName
This subquery creates a recordset
, SUM(S.Quantity * S.Price) AS Sum_Sales
with the customers without Email
FROM
(
CustomerID FirstName LastName
SELECT
3 Beverly NULL
CustomerID 8 Zak Smith

166
DML Statements (Query)
Subqueries

, FirstName
, LastName
FROM dbo.Customers Data source for the subquery
WHERE Email IS NULL
) AS C Assign alias
LEFT JOIN dbo.Sales AS S
ON C.CustomerID = S.CustomerID
Filter in the ON clause (not in the WHERE clause) to
AND S.DateOfSale >= '1965-01-01'
avoid the conversion of the JOIN from LEFT to INNER
GROUP BY
C.FirstName
, C.LastName;
GO
FirstName LastName Sum_Sales
Beverly NULL 4249.32
The subquery is LEFT JOINed to the Sales
Zak Smith NULL
table to SELECT the customers with no sales.

When we LEFT JOIN, we filter the right data source in the ON clause.
! If we apply the filter in the WHERE clause, the JOIN is transformed to
LEFT JOIN on page 130

an INNER JOIN.

Subquery in the WHERE clause

To select the 3 customers with highest sales:

SELECT FirstName LastName


FirstName Anna Laurier
, LastName Beverly NULL
FROM dbo.Customers Xavier Jameson
WHERE
CustomerID IN
(
SELECT TOP 3 CustomerID The subquery creates a list
FROM dbo.Sales of CustomerID to be filtered
CustomerID
GROUP BY CustomerID
2
ORDER BY SUM(Quantity * Price) DESC
3
);
7
GO

Subquery in the HAVING clause

167
DML Statements (Query)
Subqueries

SELECT Select the customers where total sales is greater


C.FirstName than the average sales:
, C.LastName
, SUM(Quantity * Price) AS Sum_Sales
FROM
FirstName LastName Sum_Sales
dbo.Customers AS C
Beverly NULL 4764.34
JOIN dbo.Sales AS S
Anna Laurier 7148.55
ON C.CustomerID = S.CustomerID
GROUP BY
C.FirstName
, C.LastName
The subquery SELECTs the aver-
HAVING
age sales for all the customers
SUM(Quantity * Price) >
(
SELECT AVG(Quantity * Price)
FROM dbo.Sales
); (No column name)
GO 1205.132

Correlated subquery

The correlated subquery is linked to the outer query. It correlates with objects in the FROM clause.
We can use correlated subquery in the SELECT, WHERE and HAVING clauses.

Let’s add one more table that stores the results of the marketing calls to the data sources, for the example:

Marketing- Custo- CallDura-


CallDateTime Notes
CallID merID tionInMin
1 1 1966-12-18 07:42:15.503 2 On vacation. Call on Jan 08
2 1 1967-01-08 17:27:18.140 18 Not interested
3 5 1966-12-10 22:31:00.983 4 Needs red sweater. Will be in warehouse on Dec 23
4 5 1966-12-23 01:15:08.790 7 Will take a look in our store on Queen street
5 5 1967-04-28 13:02:17.363 1 Spent the Christmas budget. Already bought red sweater
6 3 1967-01-24 10:12:48.983 23 Will take a look at the golden rings
7 3 1967-04-14 14:23:08.247 23 Positive feedback for ItemID 8613
8 4 1967-05-12 04:57:16.210 3 Has a dog and needs a new leash
9 7 1969-01-17 09:12:43.260 7 Very happy with the old purchase. Will visit our store on St.
Eduard Str.
10 7 1969-01-19 10:08:13.320 7 Very happy with the old purchase. Will visit our store on St.
Eduard Str.

168
DML Statements (Query)
Subqueries

Correlated subquery in the SELECT clause

We can query the MarketingCalls table using subqueries in the SELECT clause to SELECT the count and the duration
of the marketing calls for each customer:

SELECT
C.FirstName
, C.LastName
, (
SELECT AVG(Quantity * Price) Not correlated subquery
FROM dbo.Sales
) AS Avg_SalesAllCustomers
, SUM(S.Quantity * S.Price) AS Sum_Sales
, (
SELECT COUNT(MarketingCallID)
FROM dbo.MarketingCalls AS MC
WHERE MC.CustomerID = C.CustomerID
) AS Count_MarketingCalls Correlation (link to an ob-
, ( ject in the FROM clause)
SELECT SUM(CallDurationInMin)
FROM dbo.MarketingCalls AS MC
WHERE MC.CustomerID = C.CustomerID
) AS Sum_CallDurationInMin
FROM
dbo.Customers AS C
JOIN dbo.Sales AS S
ON C.CustomerID = S.CustomerID
GROUP BY
C.FirstName
, C.LastName
, C.CustomerID
ORDER BY
C.FirstName
, C.LastName; First- Last- Avg_SalesAll- Sum_ Count_Mar- Sum_CallDu-
Name Name Customers Sales ketingCalls rationInMin
GO
Anna Laurier 1205.132 7148.55 0 NULL
Beverly NULL 1205.132 4764.34 2 46
John Smith 1205.132 3.12 1 3
Melanie Larson 1205.132 45.88 0 NULL
Xavier Jameson 1205.132 89.43 2 14

169
DML Statements (Query)
Subqueries

Correlated subquery in the WHERE clause

To SELECT customers with total sales greater than 2000: Subquery with WHERE EXISTS

SELECT
On every row of the execution of the outer state-
C.FirstName
ment, CustomerID is linked and if the filter con-
, C.LastName
dition is met (total sales equal to or is greater
FROM dbo.Customers AS C than 2000), the customer is selected (it EXISTS)
WHERE
EXISTS
(
SELECT 1
FROM dbo.Sales AS S
WHERE C.CustomerID = S.CustomerID
GROUP BY CustomerID
HAVING SUM(S.Quantity * S.Price) >= 2000 FirstName LastName
); Anna Laurier
GO Beverly NULL

EXISTS returns True if the subquery contains one or multiple rows.


We actually don’t need to SELECT any value from the subquery in the WHERE EXISTS filtering condition. We just
need to know if the subquery contains a row or not.

SELECT
C.FirstName
, C.LastName
FROM dbo.Customers AS C
WHERE
CustomerID IN Subquery with WHERE ColumnName IN
(
SELECT CustomerID
FROM dbo.Sales AS S
WHERE C.CustomerID = S.CustomerID
GROUP BY CustomerID
HAVING SUM(S.Quantity * S.Price) >= 2000 Filter
);
GO
FirstName LastName
Anna Laurier
Beverly NULL

170
DML Statements (Query)
Subqueries

Correlated subquery in the HAVING clause

To SELECT the total sales for the customers that have single sale equal to or greater than 150:

SELECT
C.FirstName
, C.LastName
, SUM(S1.Quantity * S1.Price) AS Sum_Sales
FROM
dbo.Customers AS C
JOIN dbo.Sales AS S1
ON C.CustomerID = S1.CustomerID
GROUP BY
C.CustomerID
, S1.CustomerID
, C.FirstName
, C.LastName
HAVING
S1.CustomerID IN
(
SELECT CustomerID
FROM dbo.Sales AS S2
WHERE
C.CustomerID = S2.CustomerID
AND (S2.Quantity * S2.Price) >= 150
FirstName LastName Sum_Sales
)
Anna Laurier 7148.55
GO
Beverly NULL 4764.34

171
DML Statements (Query)
UNION, EXCEPT and INTERSECT

UNION

UNION creates a recordset by appending the result from one statement to the result of another statement.

Both statements have to:


• Have the same number of columns
• Have the same collation and the same or implicitly convertible data type of the matching columns. If the
data type can’t be implicitly converted we convert it manually (explicit conversion)

Statement 1 Customers
This is selected

Custom-
erCode
First- Last-
Statement 2 Email
Name Name
This is selected
Anabel Larson anabel.larson@customer5.info 34
SELECT Column1, Column2, Column3 Anna Laurier anna.laurier@customer2.net 863
FROM TableName1 Beverly NULL NULL 23
Statement 1 John Smith john.smith@customer1.com 943
UNION John Smith john.smith@customer3.org 72
Melanie Larson melanie.larson@customer4.biz 7565
SELECT Column1, Column2, Column3 Xavier Jameson xavier.garcía@customer6.com 4
FROM TableName2; Zak Smith NULL 425
GO Statement 2
Employees
Let’s say we have external and internal (employees)

Employ-
eeCode
customers. We may need to UNION both tables and First- Last-
Email
output the result to a report. Name Name

SELECT
Anabel Larson anabel.larson@customer5.info E1-465
FirstName
Anna Laurier anna.laurier@customer2.net 67 C22
, LastName
John Smith john.smith@customer1.com Fa 3 58B
FROM dbo.Customers

UNION FirstName LastName


Anabel Larson
SELECT Anna Laurier
FirstName Beverly NULL
, LastName John Smith
FROM dbo.Employees; Melanie Larson
GO Xavier Jameson
Zak Smith
UNION acts like SELECT DISTINCT - no duplicates in the resulting recordset.

172
DML Statements (Query)
UNION, EXCEPT and INTERSECT

UNION ALL First- Last-


Email Code
Name Name
UNION ALL returns duplicates: Can’t be implicitly Anabel Larson anabel.larson@ 34
converted to Charac- customer5.info
ter and we convert
SELECT Anna Laurier anna.laurier@ 863
it manually. customer2.net
FirstName
Beverly NULL NULL 23
, LastName
John Smith john.smith@cus- 943
, Email tomer1.com
, CONVERT(VARCHAR(10), CustomerCode) AS Code
John Smith john.smith@cus- 72
FROM dbo.Customers tomer3.org
Melanie Larson melanie.larson@ 7565
UNION ALL customer4.biz
Xavier Jameson xavier.garcía@ 4
customer6.com
SELECT
Zak Smith NULL 425
FirstName
Anabel Larson anabel.larson@ E1-465
, LastName customer5.info
, Email Anna Laurier anna.laurier@ 67 C22
, EmployeeCode AS Code customer2.net
FROM dbo.Employees; John Smith john.smith@cus- Fa 3 58B
tomer1.com
GO
An alias is not needed in the bottom state-
The performance of UNION is slower, compared to
! UNION ALL, because it cleans duplicates (additional
ment. We add it to be able to execute the
bottom statement separately at the time of
operation). the development and verify the column names.

UNION and ORDER BY

SELECT
Add identification column to mark
'Top Table' AS [Type]
the source of the data and identify
, FirstName it in future statement(s)
, LastName
FROM dbo.Customers
WHERE
LEFT(FirstName, 1) BETWEEN 'J' AND 'M' Type FirstName LastName
OR FirstName IN ('Anna', 'Anabel') Top Table Anabel Larson
Bottom Table Anabel Larson
UNION ALL Bottom Table Anna Laurier
Top Table Anna Laurier
SELECT Top Table John Smith
'Bottom Table' AS [Type] Top Table John Smith
, FirstName Bottom Table John Smith
, LastName Top Table Melanie Larson

173
DML Statements (Query)
UNION, EXCEPT and INTERSECT

FROM dbo.Employees
ORDER BY is at the end of the bottom statement and
ORDER BY
orders the UNIONed resulting recordset (not the last
FirstName statement).
, LastName;
GO

EXCEPT

We can create one statement that selects data and another statement to exclude the matching data from the first
statement. The combining keyword in this case is EXCEPT. Nothing from the bottom statement is selected. It just
determines which rows from the top statement are excluded.

• Match and excluded


This is selected Customers
Statement 1
This is not selected
Statement 2
This is not selected } Matching
rows •

FirstName
Anabel
Anna
LastName
Larson
Laurier
Email
anabel.larson@customer5.info
anna.laurier@customer2.net
This is not selected
• Beverly NULL NULL
John Smith john.smith@customer1.com

SELECT John Smith john.smith@customer3.org

FirstName Melanie Larson melanie.larson@customer4.biz


, LastName Xavier Jameson xavier.garcía@customer6.com
, Email Zak Smith NULL
FROM dbo.Customers
CustomersWithExceptionalities
EXCEPT
FirstName LastName Email
Anabel Larson anabel.larson@customer5.info
SELECT
Anna Laurier anna.laurier@customer2.net
FirstName
John Smith john.smith@customer1.com
, LastName
, Email
FROM dbo.CustomersWithExceptionalities; FirstName LastName Email
GO Beverly NULL NULL
John Smith john.smith@customer3.org
Melanie Larson melanie.larson@customer4.biz
Xavier Jameson xavier.garcía@customer6.com
EXCEPT excludes the data that matches in the Zak Smith NULL
! virtual recordsets.

If we don’t select column Email, the rows for John Smith are not unique anymore and are excluded:

174
DML Statements (Query)
UNION, EXCEPT and INTERSECT

SELECT
FirstName
, LastName
FROM dbo.Customers
FirstName LastName
EXCEPT Beverly NULL
Melanie Larson
SELECT Xavier Jameson
FirstName Zak Smith
, LastName
FROM dbo.CustomersWithExceptionalities;
GO

INTERSECT

To select the rows that exist in the both statements, we use INTERSECT.

This is not selected

}
Statement 1 Statement 2 Matching Customers
rows FirstName LastName Email
This is selected This is selected
Anabel Larson anabel.larson@customer5.info
This is not selected
Anna Laurier anna.laurier@customer2.net
Beverly NULL NULL
SELECT John Smith john.smith@customer1.com
FirstName John Smith john.smith@customer3.org
, Email Melanie Larson melanie.larson@customer4.biz
FROM dbo.Customers Xavier Jameson xavier.garcía@customer6.com
Zak Smith NULL
INTERSECT
CustomersWithExceptionalities
SELECT FirstName LastName Email
FirstName Anabel Larson anabel.larson@customer5.info
, Email Anna Laurier anna.laurier@customer2.net
FROM dbo.CustomersWithExceptionalities; John Smith john.smith@customer1.com
GO

FirstName Email
Rows, matching in Anabel anabel.larson@customer5.info
both recordsets
Anna anna.laurier@customer2.net
John john.smith@customer1.com

175
DML Statements (Query)
PIVOT and UNPIVOT

PIVOT

Pivot is the central point of rotation. When we pivot data, we rotate, reorganize and aggregate it.
When we pivot a table, we create a matrix from the table. The matrix allows us to show a value, belonging to two
groups of attributes.
The pivot table is also known as Tablix or Cross-Tab.
The pivoting is generally used for presentation of data, related to reporting and analysis purposes.

Custo- Sales- Sales


SaleID ItemID Period This groups (CustomerID, ItemID, Period)
merID Value
are aggregated with SUM(SalesValue)
3 1 1 1966-Q1 8.99
16 1 1 1966-Q1 53.24
10 1 1 1966-Q2 81.09
12 1 1 1966-Q3 81.82 Martix
15 2 1 1966-Q1 39.37 Columns
2 2 1 1966-Q2 86.92 Rows Values
14 2 2 1966-Q1 93.12
4 2 2 1966-Q3 40.55 Columns CustomerID and ItemID are presented on
8 2 3 1966-Q2 18.37 the rows.
11 3 1 1966-Q1 12.36
7 3 1 1966-Q3 31.22 Column Period is the pivot - transforms the data
1 3 1 1966-Q3 21.44 from rows to columns.
6 3 2 1966-Q2 13.33
9 3 2 1966-Q3 86.42 SalesValue column is the values, aggregated in
5 3 2 1966-Q3 68.46 CustomerID, ItemID (rows), Period (columns)
13 3 3 1966-Q2 29.39 groups.
17 3 3 1966-Q2 84.72

Attributes Pivot column Values 

SELECT To pivot a table, we add the PIVOT


P.CustomerID, P.ItemID relational operator in the FROM clause.
, P.[1966-Q1], P.[1966-Q2], P.[1966-Q3]
FROM Column CustomerID is not selected, but as we
dbo.Sales AS S pivot the table directly, it is in the virtual
PIVOT recordset that is pivoted
(
SUM(SalesValue)
FOR Period IN ([1966-Q1], [1966-Q2], [1966-Q3])
) AS P
ORDER BY CustomerID, ItemID;
GO

176
DML Statements (Query)
PIVOT and UNPIVOT

Custo- The result is not exactly as we expect. It is not aggre-


ItemID 1966-Q1 1966-Q2 1966-Q3
merID gated. What this means is that the recordset pivoted
1 1 8.99 NULL NULL (table Sales) includes a column that is not needed in the
1 1 NULL 81.09 NULL pivoting (column SaleID). To get the result we need, we
1 1 NULL NULL 81.82 have to select only the data that we need to PIVOT in the
1 1 53.24 NULL NULL subquery:
2 1 NULL 86.92 NULL
2 1 39.37 NULL NULL
2 2 NULL NULL 40.55 Column PIVOT as
2 2 93.12 NULL NULL CustomerID, ItemID Rows
2 3 NULL 18.37 NULL Period Columns
3 1 NULL NULL 21.44 SalesValue Values
3 1 NULL NULL 31.22
3 1 12.36 NULL NULL
3 2 NULL NULL 68.46
3 2 NULL 13.33 NULL
3 2 NULL NULL 86.42
3 3 NULL 29.39 NULL
3 3 NULL 84.72 NULL

SELECT
P.CustomerID, P.ItemID Rows
, P.[1966-Q1], P.[1966-Q2], P.[1966-Q3] Columns
FROM In a subquery select only the columns
( for rows (CustomerID, ItemID), columns
SELECT DISTINCT CustomerID, ItemID, Period, SalesValue (Period) and values (SalesValue)
FROM dbo.Sales
) AS S
Pivoting the above data source (the sub-
PIVOT
query, aliased with S)
(
SUM(SalesValue) Values (Aggregated)
FOR Period IN ([1966-Q1], [1966-Q2], [1966-Q3])
Only these values are transformed into
) AS P
columns (PIVOTed)
ORDER BY CustomerID, ItemID;
GO

177
DML Statements (Query)
PIVOT and UNPIVOT

Custo-
* Aggregated
ItemID 1966-Q1 1966-Q2 1966-Q3
merID
1 1 62.23 * 81.09 81.82
2 1 39.37 86.92 NULL
2 2 93.12 NULL 40.55
2 3 NULL 18.37 NULL
3 1 12.36 NULL 52.66 *
3 2 NULL 13.33 154.88 *
3 3 NULL 114.11 NULL

After we PIVOT the table, we can easily compare the sales in the each period side by side.

UNPIVOT

UNPIVOT is the opposite of PIVOT. It transforms the data from columns to rows. In the example below,
we use the table that we just PIVOTed.

SELECT
Subquery to select only the
CustomerID
data that we need to UNPIVOT
, ItemID
, Period
, SaleValue The UNPIVOTed result is:
FROM
( Custo- Sales-
ItemID Period
merID Value
SELECT
1 1 1966-Q1 62.23 *
CustomerID, ItemID
1 1 1966-Q2 81.09
, [1966-Q1], [1966-Q2], [1966-Q3]
1 1 1966-Q3 81.82
FROM dbo.Sales
2 1 1966-Q1 39.37
) AS P
2 1 1966-Q2 86.92
UNPIVOT
2 2 1966-Q1 93.12
(
2 2 1966-Q3 40.55
SaleValue
2 3 1966-Q2 18.37
FOR Period IN ([1966-Q1], [1966-Q2], [1966-Q3])
3 1 1966-Q1 12.36
) AS U
3 1 1966-Q3 52.66 *
ORDER BY
3 2 1966-Q2 13.33
CustomerID
Combine multiple columns 3 2 1966-Q3 154.88 *
, ItemID
into a new column (Period) 3 3 1966-Q2 114.11
, Period;
GO
* Can’t undo the aggregation

! UNPIVOT is not able to fully reverse the data as it was before the PIVOTing. We can’t undo the aggregation.

178
Conditional Execution
IF

IF... ELSE are keywords of the Control-of-Flow Language which manipulates the execution of the code. T-SQL is
executed from top to bottom. Based on a specified condition, we can skip the execution of a block of code.

Execution steps:
1. The IF condition is verified
2. If the verification returns True, the block of code between BEGIN and END keywords that follows is
executed
3. ELSE keyword adds another block of code that is executed when the IF condition returns False.
The ELSE statement is optional

The syntax is:


IF (Condition) Condition that is verified and returns
BEGIN boolean expression (True or False)
... Statement 1 ...; Block of code, executed if the verification of the
END condition returns True. One or multiple statements.
ELSE
BEGIN
... Statement 1 ...;
Block of code, executed if the verification of the condition
... Statement 2 ...;
returns False or NULL (unknown). One or multiple statements.
END
GO
If the block contains one statement, BEGIN and END can be omitted.

Always use BEGIN and END, regardless if the block contains one or more than one statements.

IF (7 > 17) The condition is not met (False) and the block of T-SQL that follows is not executed
BEGIN
PRINT 'This BEGIN... END block is executed when condition is True';
PRINT '7 is greater that 17';
END
GO

IF... ELSE

The block of code, defined by ELSE is executed if the verification of the condition returns False.
IF (DATENAME(DW, GETDATE()) = 'Saturday') GETDATE returns the date and time now
BEGIN PRINT 'Today is Saturday'; END DATENAME returns the name of the day in the week
ELSE
Formating the code
If one statement then BEGIN and END on the same line

179
Conditional Execution
IF

BEGIN The statement is executed not


PRINT 'Block of code when the condition is FALSE'; on Saturday and returns this:
PRINT 'Today is not Saturday';
END Block of code when the condition is FALSE
GO Two statements Today is not Saturday

Nested IF

We can verify one condition in case when another condition is met by nesting one IF condition into another.

IF (DATENAME(DW, GETDATE()) = 'Tuesday')


BEGIN
IF ((SELECT DATEPART(HOUR, GETDATE())) = 15) DATEPART with first pa-
BEGIN PRINT 'Today is Tuesday and the hour is 15'; END rameter = HOUR extracts
ELSE the hour from the time
BEGIN PRINT 'Today is Tuesday and the hour is not 15'; END
Executed on Tuesday at 11h
END 27 min and results to:
ELSE
BEGIN PRINT 'Today is not Tuesday'; END
GO Today is Tuesday and the hour is not 15

In this example if the condition is verified to False, ELSE, including nested IF is executed:
IF (7 > 17)
BEGIN PRINT '(7 > 17) is True'; END
ELSE
BEGIN
IF (8 > 17)
BEGIN PRINT '(8 > 17) is True'; END
ELSE
BEGIN PRINT '(8 > 17) is False'; END
END
GO

Independent IFs

IF (7 > 17) IF (7 < 17)


BEGIN PRINT 'First IF --> True'; END BEGIN PRINT 'Second IF --> True'; END
ELSE ELSE
BEGIN PRINT 'First IF --> False'; END BEGIN PRINT 'Second IF --> False'; END
GO
First IF --> False
As they are not nested, both conditions are executed consecutively and independently. Second IF --> True

180
Conditional Execution
IF

IF EXISTS

The statement in the condition creates a recordset. If a row exists in the recordset, the verification of the condition
returns True.

Customers
FirstName LastName Email
Anabel Larson anabel.larson@customer5.info
Anna Laurier anna.laurier@customer2.net
Beverly NULL NULL

If a customer with a specified Email exists, a message is printed:

IF EXISTS The reason to select 1 is because We don’t need any actual val-
( ue. We just need to know if a row exists in the recordset
SELECT 1
FROM dbo.Customers
Result
WHERE Email = 'anna.laurier@customer3.net'
anna.laurier@customer3.net
) does not EXISTS
BEGIN SELECT 'anna.laurier@customer3.net EXISTS' AS Result; END
ELSE
BEGIN SELECT 'anna.laurier@customer3.net does not EXISTS' AS Result; END
GO

RETURN

Terminates the execution of the code (the rest of the code after RETURN is not executed).

IF (7 > 17)
BEGIN SELECT 'True' AS Result; END
ELSE
BEGIN
SELECT 'False' AS Result;
RETURN; The execution of the code is terminated here
END

SELECT 'This is not executed, because RETURN is reached'; The code after RETURN is not executed
GO

Result
False

181
Conditional Execution
IIF

Let’s use the following tables for the IIF example:

Sales ItemsStandardDiscounted Items


Custo- IsItemDis- Quan- Discount- ItemID Price
SaleID ItemID ItemID
merID counted tity edItemID
11 5.23
1 1 11 1 52 11 111
22 15.88
2 2 22 1 2 22 222
111 4.99
3 3 11 1 62
222 14.26
4 1 22 0 552
5 1 11 0 1

1. Table Sales contains the sales by customer and item. Column IsItemDiscounted determines how we
manipulate the data and how we JOIN the tables
2. Table ItemsStandarsDiscounted is intermediate and links the standard item (the ones that we usually
sell) to the discounted item (the same item with reduced price for one or another reason)
3. Table Items contains the attributes for the items. For the purposes of this example we need only Price

In the following scenarios we JOIN:


• Sales to ItemsStandarsDiscounted when we need the special routing
• Sales to Items for the cases when we manipulates the discounted items differently

We can’t implement the IF condition in DML statement’s clause (SELECT, FROM, WHERE, GROUP BY, ORDER BY) direct-
ly:

SELECT
S.CustomerID
, S.ItemID
, S.IsItemDiscounted
IF condition
, S.Quantity
, I.Price
, IF (S.IsItemDiscounted = 0)
BEGIN (S.Quantity * I.Price) END Incorrect syntax near the keyword 'IF'.
ELSE Incorrect syntax near 'S'.
BEGIN (S.Quantity * (I.Price * 0.85)) END Incorrect syntax near 'S'.
AS SalesValue
FROM
dbo.Sales AS S
JOIN dbo.Items AS I To accomplish this task, we need to use the
ON S.ItemID = I.ItemID; built-in function IIF
GO The syntax is:

182
Conditional Execution
IIF

IIF(Condition, WhenTrue, WhenFalse);


• Condition - validation that returns True or False
• WhenTrue - value or expression, that the function returns when the condition returns True
• WhenFalse - value or expression, that the function returns when the condition returns False

IIF in the SELECT clause

The IIF condition verifies if the item is discounted and calculates the standard or the discounted (85%) sales value:
SELECT
S.CustomerID
, S.ItemID
, S.IsItemDiscounted
, S.Quantity
, I.Price
, IIF((S.IsItemDiscounted = 0), (S.Quantity * I.Price), (S.Quantity * (I.Price * 0.85))) AS
SalesValue
FROM
dbo.Sales AS S
JOIN dbo.Items AS I
ON S.ItemID = I.ItemID;
GO

Custo- IsItemDis- Quan-


ItemID Price SalesValue
merID counted tity
1 11 1 52 5.23 231.166000
2 22 1 2 15.88 26.996000 (Quantity * (Price * 0.85))
3 11 1 62 5.23 275.621000
1 22 0 552 15.88 8765.760000
(Quantity * Price)
1 11 0 1 5.23 5.230000

SalesValue is calculated differently, based on the flag in IsItemDiscounted column.

IIF in the FROM clause

The conditional executions in the FROM... JOIN (ON) clause gives us the chance to create a zig-zag JOIN.
This means that, based on condition, we link to one or another columns.

SELECT
S.CustomerID
, S.ItemID
, S.IsItemDiscounted

183
Conditional Execution
IIF

, S.Quantity
, ISD.ItemID
, ISD.DiscountedItemID
, I.ItemID
, I.Price
, (S.Quantity * I.Price) AS SavesValue
FROM
dbo.Sales AS S
JOIN dbo.ItemsStandardDiscounted AS ISD
ON S.ItemID = ISD.ItemID Zig-zag JOINing

JOIN dbo.Items AS I
ON IIF((S.IsItemDiscounted = 0), ISD.ItemID, ISD.DiscountedItemID) = I.ItemID;
GO

Sales ItemsStandardDiscounted Items IIF()


Custome- IsItemDis- Quan- Discounte- Saves-
ItemID ItemID ItemID Price
rID counted tity dItemID Value
1 11 1 52 11 111 111 4.99 259.48
2 22 1 2 22 222 222 14.26 28.52
3 11 1 62 11 111 111 4.99 309.38
1 22 0 552 22 222 22 15.88 8765.76
1 11 0 1 11 111 11 5.23 5.23

In the first row ItemID = 11 is linked to ItemID = 11. Hense IsItemDiscounted is 1, DiscountedItemID = 111 is
linked to ItemID = 111 and the price of ItemID = 111 (4.99) is used in the calculation (52 * 4.99 = 259.48)

In the last row ItemID = 11 is linked to ItemID = 11. Hense IsItemDiscounted is 0, ItemID = 11 is linked to ItemID
= 11 and the price of ItemID = 11 (5.23) is used in the calculation (1 * 5.23 = 5.23)

IIF in the WHERE clause

We can filter on the calculated column SalesValue and select the rows where SalesValue is greater than 200:

SELECT AS SalesValue
S.CustomerID FROM
, S.ItemID dbo.Sales AS S
, S.IsItemDiscounted JOIN dbo.Items AS I
, S.Quantity ON S.ItemID = I.ItemID
, I.Price WHERE IIF((S.IsItemDiscounted = 0), (S.Quantity *
, IIF((S.IsItemDiscounted = 0), (S.Quantity I.Price), (S.Quantity * (I.Price * 0.85))) > 200;
* I.Price), (S.Quantity * (I.Price * 0.85))) GO

184
Conditional Execution
IIF

CustomerID ItemID IsItemDiscounted Quantity Price SalesValue


(52 * 5.23 * 0.85) = 231.166

1 11 1 52 5.23 231.166000
3 11 1 62 5.23 275.621000
1 22 0 552 15.88 8765.760000 (552 * 15.88) = 8765.76

IIF in the GROUP BY clause

We can create a column, based on a condition and use it in a group.


COUNT the customers for item:

SELECT
IIF((S.IsItemDiscounted = 0), ISD.ItemID, ISD.DiscountedItemID) AS ItemID
, COUNT(DISTINCT CustomerID) AS Count_Customers
FROM
dbo.Sales AS S
JOIN dbo.ItemsStandardDiscounted AS ISD
ON S.ItemID = ISD.ItemID
The same as in the
GROUP BY IIF((S.IsItemDiscounted = 0), ISD.ItemID, ISD.DiscountedItemID);
SELECT clause
GO

IIF in the HAVING clause ItemID Count_Customers


11 1
Filter the aggregated recordset where SalesValue is greater than 250:
22 1
111 2
SELECT
S.CustomerID 222 1

, S.ItemID
, SUM(IIF((S.IsItemDiscounted = 0), (S.Quantity *
I.Price), (S.Quantity * (I.Price * 0.85)))) AS SalesValue
FROM
dbo.Sales AS S
JOIN dbo.Items AS I
ON S.ItemID = I.ItemID
GROUP BY
S.CustomerID
, S.ItemID CustomerID ItemID SalesValue
HAVING SUM(IIF((S.IsItemDiscounted = 0), (S.Quantity * 1 22 8765.760000
I.Price), (S.Quantity * (I.Price * 0.85)))) > 250; 3 11 275.621000
GO

185
Conditional Execution
CASE

The CASE expression is an extension of the logic of the IF condition. CASE combines multiple conditions into one.

Structure
• CASE and END distinct the CASE expression from the rest of the code
• WHEN defines the conditions to be verified
• THEN defines the code that is executed if the WHEN condition evaluates to True
• ELSE defines the code that is executed if none of the WHEN conditions evaluates to True

Execution
1. The WHEN conditions are verified in the order that they appear
2. When the condition verification returns False, the execution jumps to the next condition
3. When the condition’s verification is True, the code defined with the keyword THEN is executed and the
execution of the CASE terminates
4. If none of the conditions validates to True, the code defined with the keyword ELSE is executed

Sales HasDis- Quan-


SaleID CustomerName ItemDescription SaleDate Price
count tity
1 Anabel Larson Plastic Plain Blue 0 1969-01-17 3 4.89
2 Anabel Larson Wooden Horse 1 1965-10-14 89 43.12
3 John Smith Rag Doll 33 cm. 0 1966-03-05 53 7.52
4 John Smith Plastic Plain Blue 0 1965-10-06 6 3.43
5 Xavier Jameson Plastic Plain Blue 1 1968-01-24 4 1.27
6 Xavier Jameson Rag Doll 33 cm. 1 1966-12-11 77 55.12
7 Xavier Jameson Wooden Horse 1 1968-04-13 22 23.41

We can write the CASE expression in two variations: simple and complex.

We can calculate the discount, based on the boolean value in HasDiscount column with simple CASE expression:

SELECT Left operand


CustomerName The operator is equals to (=)
Right operand ColumnName, which’s values are validated
, ItemDescription
, HasDiscount
If the value in HasDiscount
, SUM(Quantity * Price) AS SalesValue equals to 1 (True) then multi-
, CASE HasDiscount ply SalesValue by 0.85
WHEN 1 THEN (SUM(Quantity * Price) * 0.85)
ELSE SUM(Quantity * Price) If all the conditions above evaluate to False
END AS Case_Discount
FROM dbo.Sales
GROUP BY
CustomerName

186
Conditional Execution
CASE

, ItemDescription Customer- ItemDescrip- HasDis- Sales-


Case_Discount
, HasDiscount Name tion count Value
ORDER BY Anabel Larson Plastic Plain Blue 0 14.67 14.670000 ELSE
CustomerName Anabel Larson Wooden Horse 1 3837.68 3262.028000 WHEN
, ItemDescription; John Smith Plastic Plain Blue 0 20.58 20.580000
GO John Smith Rag Doll 33 cm. 0 398.56 398.560000
Xavier Jameson Plastic Plain Blue 1 5.08 4.318000
Xavier Jameson Rag Doll 33 cm. 1 4244.24 3607.604000
Xavier Jameson Wooden Horse 1 515.02 437.767000

When we use the complex variation of the CASE expression, we can involve one or more condition like:
WHEN ('A' = 'A' AND (13 > 7 OR ColumnName = 'Y')) THEN...

To create groups by verifying the year in SaleDate:

SELECT
CustomerName
Both operands and the operator are after WHEN
, ItemDescription
, YEAR(SaleDate) AS Year_SaleDate
, CASE
WHEN YEAR(SaleDate) = @CurrentYear THEN 'Current Year' @CurrentYear = 1969
WHEN YEAR(SaleDate) = (@CurrentYear - 1) THEN 'Last Year'
ELSE 'Older than last year'
END AS Case_Year_SaleDate
, SUM(Quantity * Price) AS SalesValue
FROM dbo.Sales
GROUP BY
CustomerName
, ItemDescription
, YEAR(SaleDate)
, CASE
WHEN YEAR(SaleDate) = @CurrentYear THEN 'Current Year'
WHEN YEAR(SaleDate) = (@CurrentYear - 1) THEN 'Last Year'
ELSE 'Older than last year'
END
ORDER BY The same as in the SELECT clause
CustomerName
, ItemDescription;
GO

187
Conditional Execution
CASE

Customer- ItemDescrip- Year_ Case_Year_Sale- Sales-


Name tion SaleDate Date Value
Anabel Larson Plastic Plain Blue 1969 Cusrrent Year 14.67
Anabel Larson Wooden Horse 1965 Older than last year 3837.68
John Smith Plastic Plain Blue 1965 Older than last year 20.58
John Smith Rag Doll 33 cm. 1966 Older than last year 398.56
Xavier Jameson Plastic Plain Blue 1968 Last Year 5.08
Xavier Jameson Rag Doll 33 cm. 1966 Older than last year 4244.24
Xavier Jameson Wooden Horse 1968 Last Year 515.02

CASE in the FROM clause

We can use CASE in the FROM clause when we need to equalize values in the ON clause.
A simple example is joining two data sources where values in Table1 are 0 and 1, but the corresponding values in
Table2 are 'Y' and 'N':

...
FROM
Table1 AS T1
JOIN Table1 AS T2
ON CASE
WHEN T1.HasDiscount = 1 THEN 'Y'
ELSE 'N'
END = T2.HasDiscount
...

CASE in the WHERE clause

Let’s add one more table that contains the thresholds, related to the marketing strategies:

ItemDis- Current- Last- OlderThan- ItemDiscounts


ItemID
countID Year Year LastYear
1 1 10.00 20.00 30.00
2 2 300.00 400.00 500.00
3 3 50.00 60.00 70.00

The items are on the rows and three groups are in the columns. We generated these groups in the last example.
Let’s move the last example into a subquery and join the new table with the subquery:

SELECT
S.CustomerName
, S.ItemDescription

188
Conditional Execution
CASE

, S.Year_SaleDate
, S.Case_Year_SaleDate
, S.SalesValue
, ID.CurrentYear
, ID.LastYear Subquery (from the previous exam-
, ID.OlderThanLastYear ple) to create the sales data
FROM
(

CustomerName ItemID ItemDescription Year_SaleDate Case_Year_SaleDate SalesValue

Anabel Larson 1 Plastic Plain Blue 1969 Current Year 14.67

Anabel Larson 3 Wooden Horse 1965 Older than last year 3837.68

John Smith 1 Plastic Plain Blue 1965 Older than last year 20.58

John Smith 2 Rag Doll 33 cm. 1966 Older than last year 398.56

Xavier Jameson 1 Plastic Plain Blue 1968 Last Year 5.08

Xavier Jameson 2 Rag Doll 33 cm. 1966 Older than last year 4244.24

Xavier Jameson 3 Wooden Horse 1968 Last Year 515.02

) AS S
JOIN dbo.ItemDiscounts AS ID
ON S.ItemID = ID.ItemID
WHERE
S.SalesValue >=
CASE
WHEN S.Case_Year_SaleDate = 'Current Year' THEN ID.CurrentYear
WHEN S.Case_Year_SaleDate = 'Last Year' THEN ID.LastYear
ELSE ID.OlderThanLastYear
END
ORDER BY Zig-zag filtering
S.CustomerName
, S.ItemDescription;
GO

Customer- ItemDescrip- Year_ Case_Year_Sale- Sales- Current- Last- OlderThan-


Name tion SaleDate Date Value Year Year LastYear
Anabel Larson Plastic Plain Blue 1969 Current Year 14.67 10.00 20.00 30.00
Anabel Larson Wooden Horse 1965 Older than last year 3837.68 50.00 60.00 70.00
Xavier Jameson Rag Doll 33 cm. 1966 Older than last year 4244.24 300.00 400.00 500.00
Xavier Jameson Wooden Horse 1968 Last Year 515.02 50.00 60.00 70.00

We can use the same zig-zag logic in the FROM (ON) clause to JOIN on different columns, based on a condition.

189
Conditional Execution
CASE

CASE in the GROUP BY clause

When we use CASE in the GROUP BY clause, it has to be the same as the corresponding CASE in the SELECT clause.
This is not mandatory. We may have a logic that SELECTs and GROUPs BY differently.

CASE in the HAVING clause

In the below example, we used CASE to add a calculated column (Case_Discount) to explain the simple CASE ex-
pression.
We can filter the aggregated virtual recordset on this column by adding CASE expression in the HAVING clause:

SELECT
CustomerName
, ItemDescription
, HasDiscount
, SUM(Quantity * Price) AS SalesValue
, CASE HasDiscount
WHEN 1 THEN (SUM(Quantity * Price) * 0.85)
ELSE SUM(Quantity * Price)
END AS Case_Discount
FROM dbo.Sales
GROUP BY
CustomerName
, ItemDescription
, HasDiscount
HAVING
CASE HasDiscount
WHEN 1 THEN (SUM(Quantity * Price) * 0.85)
ELSE SUM(Quantity * Price)
END <= 500
ORDER BY
CustomerName CustomerName ItemDescription HasDiscount SalesValue Case_Discount
, ItemDescription; Anabel Larson Plastic Plain Blue 0 14.67 14.670000
GO John Smith Plastic Plain Blue 0 20.58 20.580000
John Smith Rag Doll 33 cm. 0 398.56 398.560000
Xavier Jameson Plastic Plain Blue 1 5.08 4.318000
Xavier Jameson Wooden Horse 1 515.02 437.767000

CASE in the ORDER BY clause

We can order the resultset by moving the rows with discount at the top:

190
Conditional Execution
CASE

SELECT
CustomerName
, ItemDescription
, HasDiscount
, SUM(Quantity * Price) AS SalesValue
, CASE HasDiscount
WHEN 1 THEN (SUM(Quantity * Price) *
0.85)
ELSE SUM(Quantity * Price)
END AS Case_Discount
FROM dbo.Sales
GROUP BY
CustomerName
, ItemDescription
, HasDiscount
ORDER BY
CASE HasDiscount
WHEN 1 THEN 0 The sales that have discount are marked with 0
ELSE 1
END
All the other rows are marked with 1 and are
, CustomerName
ordered after the discounted sales
, ItemDescription;
GO

Customer- ItemDescrip-
HasDiscount SalesValue Case_Discount
Name tion

}
Anabel Larson Wooden Horse 1 3837.68 3262.028000
Xavier Jameson Plastic Plain Blue 1 5.08 4.318000
HasDiscount = 1
Xavier Jameson Rag Doll 33 cm. 1 4244.24 3607.604000
Xavier Jameson Wooden Horse 1 515.02 437.767000

}
Anabel Larson Plastic Plain Blue 0 14.67 14.670000
John Smith Plastic Plain Blue 0 20.58 20.580000 HasDiscount = 0
John Smith Rag Doll 33 cm. 0 398.56 398.560000

191
Sessions

In a real life scenario we may have a parametrized stored procedure (SP) that creates the data for a report.
The steps in the SP are:
1. Extract the data for the chosen fiscal period (parameter)
2. Insert the data into a new table
3. Manipulate the new table
4. Extract the final data for the report from the new table
5. Delete the new table

If multiple users execute the same SP simultaneously, there is a risk that they manipulate each others data.

The simultaneous execution steps may be:


1. User 1 runs a report for 1968-Q2, the SP truncates and loads the data for 1968-Q2 into Table1
2. User 2 runs a report for 1963-Q1, the SP truncates and loads the data for 1963-Q1 into Table1 (the same
table that serves User 1)
3. User 2 overwrites the data of User 1 in Table1
4. User 1 reads data, not related to the fiscal period he picked

Time 14:23:43 14:23:45 14:23:48 14:23:50


Insert data for 1968- Report, containing
User 1
Q2 into Table1 data for 1963-Q1
Insert data for 1963- Report, containing
User 2
Q1 into Table1 data for 1963-Q1

To isolate the data for each user, SQL Server® associates a session for each user’s execution.

User Connection Session T-SQL Isolated Data


User 1 Session 1 Isolated Data 1
User 2 Connection 1 Session 2 Stored Procedure 1 Isolated Data 2
User 3 Session 3 Isolated Data 3

User 4 Connection 2 Session 4 Stored Procedure 1 Isolated Data 4

Server SessionID Stored Prodecure Isolated data in:


Database or • Temp tables
User Authentication T-SQL from external • Variables
application

The dataflow is:


• The User is using specified Connection and sends a command - T-SQL code
• The DBE associates a unique Session for each command

192
Sessions

• All the commands that run the same Stored Procedure, create their own Isolated Data (real or temp table,
variable, etc.)
• Unique resultset is returned to each User

In the table above:


• User - the application that is connecting to the database and executes T-SQL
• Connection - the credentials that the User sends to the DBE:
• Server name
• DB name
• User name
• Password
• Session – the unique session assigned to each command (Combination of user and T-SQL code)
• Stored Procedure – the T-SQL that may cause an overlapping of the data between the users
• Isolated Data – the unique data for the unique session

The built-in function @@SPID returns the SessionID of the current session:

SELECT @@SPID As SessionID; SessionID


GO 53

Session in SSMS

We can test different statements in different sessions by executing them in different tabs in SSMS:

The SessionID, used The other tabs use


by this tab is 56 another SessionID

193
Tables

The table is a DB object where the data is stored.

In SQL Server® the tables are:


• Permanent
• Temporary (temp)
• Local
• Global
• Variable

Permanent Table

The permanent table is stored on the hard drive(s) of the server and exists permanently until it is dropped with the
DDL DROP TABLE statement.

Query the Object Catalog and list the permanent tables in a database:

USE LearnSQLServerIntuitively; SchemaName TableName DateCreated


GO dbo Table1 1965-07-15 17:35:02.160
dbo Table2 1966-11-01 02:37:12.747
SELECT etl Table1 1967-08-18 08:12:45.560
SCHEMA_NAME([schema_id]) AS SchemaName etl Table2 1967-04-05 12:54:37.783
, name AS TableName
, create_date AS DateCreated
SELECT DISTINCT *
FROM sys.tables
FROM sys.objects
ORDER BY
WHERE [type] = 'U'
SchemaName
GO
, TableName;
GO

Temporary (temp) Table

The temp table exists temporarily.


It is created by T-SQL and isolated to:
• The batch (the session) that executes the T-SQL
• All the sessions

The temporary existence and the isolation are the reasons why we use temp tables.

The different types of temp tables are:


• Local
• Global
• Variable 

194
Tables

Local temp table Global temp table Table Variable


The identifier (table name) # ## @
starts with #TempTable1 ##TempTable1 @TempTable1
Exists until the session that creates the the session that creates the the batch that DECLAREs the
table is open table is open table variable is executed
Stores the data in the system database tempdb the system database tempdb the memory of the server
Accessible by Only the session that cre- All the sessions Only the batch that creates
ates the table the table variable
Manipulated as a permanent DDL and DML statements DDL and DML statements DML statements
table with

Create and use temp tables

Before we create local or global temp tables, we clean up:

DROP TABLE IF EXISTS #LocalTempTable;


GO

Create temp table with CREATE TABLE or SELECT... INTO statement:

CREATE TABLE #LocalTempTable1


(
CustomerID INT IDENTITY(1, 1) PRIMARY KEY
, FirstName NVARCHAR(32)
, LastName NVARCHAR(64)
, Email VARCHAR(128)
);
GO

After the local or global temp table is created, we can manipulate it with the DDL and DML statements that we
learned so far.

Once we no longer need the temp table, we run the clean up code to release the disk space that was used by the
temp table.

As the table variable is created in the memory and is alive at the time of the execution of the batch, it is not nec-
essary to handle the clean up manually.

195
Tables

! We can’t use the SELECT... INTO statement to load data into table variable.

DECLARE @TableVariable1 TABLE Create the table variable


(
CustomerID INT IDENTITY(1, 1) PRIMARY KEY
, FirstName NVARCHAR(32)
, LastName NVARCHAR(64)
);

INSERT @TableVariable1 (FirstName, LastName) Load data


SELECT 'Anna', 'Laurier'
UNION ALL SELECT 'Anabel', 'Larson';

SELECT * Select the data from the table variable


FROM @TableVariable1;
GO CustomerID FirstName LastName

After the execution of the batch, 1 Anna Laurier


the variable doesn’t exist 2 Anabel Larson

! We can’t manipulate the table variable with DDL statements.

Query the Object Catalog and list the tables in tempdb:

SELECT Schema-
TableName DateCreated
SCHEMA_NAME([schema_id]) AS SchemaName Name
, name AS TableName dbo ##GlobalTempTable1 1966-04-18
11:13:41.870
, create_date AS DateCreated
dbo #LocalTempTable1__...___000000000002 1966-04-18
FROM tempdb.sys.tables
12:05:05.097
ORDER BY
SchemaName
, TableName;
GO

196
Tables

Isolation of the temp table

The local temp table is local, because it is accessible only by the session that creates it.

Local temp table


The local temp table, created in the first tab in SSMS:

DROP TABLE IF EXISTS #LocalTempTable;


Step 1: Execute in
CREATE TABLE #LocalTempTable (TableType VARCHAR(6)); the first tab in SSMS

INSERT #LocalTempTable (TableType)


SELECT 'Local';

SELECT *
TableType
FROM #LocalTempTable;
Local
GO

is not accessible in the second tab in SSMS (another session):

SELECT * Step 2: Execute in the second tab in SSMS

FROM #LocalTempTable; Invalid object name '#LocalTempTable'.


GO

Global temp table


The global temp table, created in the first tab in SSMS:

DROP TABLE IF EXISTS ##GlobalTempTable; Step 1: Execute in


the first tab in SSMS
CREATE TABLE ##GlobalTempTable (TableType VARCHAR(6));

INSERT ##GlobalTempTable (TableType)


SELECT 'Global';

SELECT *
FROM ##GlobalTempTable; TableType
GO Global

is accessible by all the sessions:

197
Tables

Step 2: Execute in any tab


other than the first in SSMS
SELECT *
TableType
FROM ##GlobalTempTable;
Global
GO

until the session that creates the table is open:


Step 3: Close the first tab (close the session)

SELECT * Step 2: Execute in any tab other than the first in SSMS
FROM ##GlobalTempTable;
GO Invalid object name '##GlobalTempTable'.

Table variable

DECLARE @TableVariable1...
INSERT @TableVariable1...
GO GO ends the batch and the table variable doesn’t exist anymore

SELECT *
FROM @TableVariable1; The table variable doesn’t exist anymore
GO
Must declare the table variable “@TableVariable1”.

198
Variables

The variable:
• Stored in the memory of the server is:
• Single value of specific data type
• Multiple values of multiple data types (table variable)
• Exists at the time of the execution of the batch where it is created
• Is accessible only inside the batch where it is created
• Has unique name, starting with the at symbol (@)

Variable life cycle:


1. Create - declare the variable and reserve space in the memory of the server
2. Assign value - attach a value(s) to the variable
3. Use - read the value of the variable from the computers memory and implement it in the code
4. Clean up - after the execution of the batch, the variable doesn’t exist anymore

Before
Computer memory

Operating System The OS is using a portion of the memory

Other Other applications are using a portion of the memory

Create
Computer memory

Operating System Variable The variable is using a portion of the memory


Name: @Age
Other Data type: INT DECLARE @Age INT;
Value: NULL

Assign
Computer memory

Operating System Variable


Name: @Age SET @Age = 21;
Other Data type: INT or
Value: 21 SELECT @Age = 21;

199
Variables

Use
Computer memory

Operating System Variable


SELECT *
Name: @Age
FROM Customers
Other Data type: INT
WHERE Age >= @Age;
Value: 21
GO

After End of the batch

Computer memory

Operating System
The variable is not in the memory anymore
Other

Create variable (DECLARE)

DECLARE @VariableNumeric INT; DECLARE one variable.

DECLARE DECLARE more than one variable,


@VariableNumeric INT delimited with a comma (,).
, @VariableString VARCHAR(32);

! After the variable is created its value is NULL.

Variable name

Starts with the at symbol (@).


@FirstName, @firstName, @_firstName, @Par_DateInvoiced are valid variable names.

The following characters can not be used in the variable name:

Character Variable Name ‘ @Variable’Name - @Variable-Name


(space) @Variable Name ( @Variable(Name . @Variable.Name
! @Variable!Name ) @Variable)Name / @Variable/Name
“ @Variable”Name * @Variable*Name : @Variable:Name
% @Variable%Name + @Variable+Name ; @Variable;Name
& @Variable&Name , @Variable,Name < @Variable<Name

200
Variables

= @Variable=Name ‹ @Variable‹Name « @Variable«Name


> @Variable>Name ‘ @Variable‘Name ¬ @Variable¬Name
? @Variable?Name ’ @Variable’Name ® @Variable®Name
[ @Variable[Name “ @Variable“Name ¯ @Variable¯Name
\ @Variable\Name ” @Variable”Name ° @Variable°Name
] @Variable]Name • @Variable•Name ± @Variable±Name
^ @Variable^Name – @Variable–Name ² @Variable²Name
` @Variable`Name — @Variable—Name ³ @Variable³Name
{ @Variable{Name ˜ @Variable˜Name ´ @Variable´Name
| @Variable|Name ™ @Variable™Name ¶ @Variable¶Name
} @Variable}Name › @Variable›Name · @Variable·Name
~ @Variable~Name ¡ @Variable¡Name ¸ @Variable¸Name
€ @Variable€Name ¢ @Variable¢Name ¹ @Variable¹Name
‚ @Variable‚Name £ @Variable£Name » @Variable»Name
„ @Variable„Name ¤ @Variable¤Name ¼ @Variable¼Name
… @Variable…Name ¥ @Variable¥Name ½ @Variable½Name
† @Variable†Name ¦ @Variable¦Name ¾ @Variable¾Name
‡ @Variable‡Name § @Variable§Name ¿ @Variable¿Name
ˆ @VariableˆName ¨ @Variable¨Name × @Variable×Name
‰ @Variable‰Name © @Variable©Name ÷ @Variable÷Name

! We are not allowed to wrap the variable name in square brackets ([]) in order to use the characters above.

We can extend the naming conventions rules with:


• Include the data type as a prefix in the name
DECLARE @strVariable NVARCHAR(32)
DECLARE @intVariable INT;

Variable Data type

The data type that a variable can hold which is any SQL Server® data type, except TEXT, NTEXT and IMAGE (these
data types are obsolete and not suggested to be used).

! TEXT, NTEXT and IMAGE data types are obsolete and suggested not to be used.

Assign value to variable (SET, SELECT)

Data type conversion


• When the variable and the assigned value are of the same data type or can be implicitly converted, we
assign with an assignment operator:

201
Variables

• = • *=
• += • /=
• -= • %=
• Else, we manually convert the assigned value to the data type of the variable

Assign value

DECLARE @VariableNumeric INT;

SET @VariableNumeric = 123; No conversion


SET @VariableNumeric = '123'; Implicit conversion
SET @VariableNumeric = CAST(GETDATE() AS INT); Manual conversion
SET @VariableNumeric = (120 + 3); Expression. No conversion

SELECT @VariableNumeric = 123;

The SELECT statement can assign values to more than one variable:

SELECT
@VariableNumeric = 456
, @VariableString = 'DEF';

Assign value from recordset


Variables As the variable accepts a single value, when we use the result of
ColumnNumeric ColumnString a recordset, it must contain a single value (1 row and 1 column).
1 A
2 B
DECLARE @VariableNumeric INT;
SELECT @VariableNumeric = More than one column
3 C
4 D
(
5 E
SELECT
6 F
1 AS ColumnNumeric
Only one expression can be
7 G
, 'A' AS ColumnString specified in the select list
); when the subquery is not
GO introduced with EXISTS.

SET @VariableNumeric = Assign value 7 to @VariableNumeric


(
SELECT MAX(ColumnNumeric)
FROM dbo.Variables
);
GO One row, one column

202
Variables

When we assign a new value, it overwrites the old one:

SELECT @VariableNumeric = 123; The value is 123


SELECT @VariableNumeric = 456; The new value 456 overwrites the old value 123
SELECT @VariableNumeric += 1; The new value is 456 + 1 = 457

When we assign a value from the recordset, we’ll assign it as many times as the number of the rows in the
recordset.
The last assigned value is the last queried value of the recordset.

Assign:
• Constant value 123 to @VariableNumeric
• The value of ColumnString from Variables to @VariableString:

SELECT
@VariableNumeric = 123 Constant value
, @VariableString = ColumnString Value, obtained from a recordset (Data-
FROM dbo.Variables; baseName.SchemaName.TableName)
GO

Value 123 will be assigned 7 times to @VariableNumeric, because there are 7 rows in table Variables.
On every row, returned by the SELECT statement, the value of the column ColumnString is assigned to @
VariableString.
The last value of column ColumnString 'G' is assigned.

To avoid any additional load of the server, we need to make sure that the recordset returns one row:

SELECT
@VariableNumeric = MIN(ColumnNumeric)
, @VariableString = MAX(ColumnString)
FROM dbo.Variables;
GO

Now the recordset contains 1 (the minimum value in column ColumnNumeric) and 'G' (the maximum value in
column ColumnString).

Assigning a value from the multi-row recordset with the SET statement returns an error:

DECLARE @VariableNumeric INT;


SET @VariableNumeric =
(

203
Variables

SELECT ColumnNumeric Subquery returned more than 1 value. This


FROM dbo.Variables is not permitted when the subquery follows
=, !=, <, <= , >, >= or when the subquery
);
is used as an expression.
GO

We can create a variable and assign a value to it in one statement:

DECLARE @VariableNumeric INT = 123;

Use the variable

DECLARE Create and assign a value


@VariableNumeric INT = 11 11 + 22 Result
, @VariableString VARCHAR(2) = '22'; 33

SELECT (@VariableNumeric + @VariableString) AS Result; Use


GO
After the batch is executed, the
variable doesn’t exist anymore
or Create variable(s)

DECLARE
@CommissionPercentage DECIMAL(5, 3) Remove the time from the DATE-
, @Today DATE = CONVERT(CHAR(10), GETDATE(), 120); TIME data type, returned by the
built-in function GETDATE()
SELECT @CommissionPercentage = CommisionPercentage Assign a value to the variable(s)
FROM dbo.CommissionPercentage
WHERE DateFiscal = @Today;

SELECT Use the variables


CONCAT(SP.FirstName, SP.LastName) AS CustomerName
, FORMAT(@Today, 'MMM dd, yyy') AS CommissionDate
, SUM(S.Quantity * S.Price) AS LineTotal
, @CommissionPercentage AS CommissionPercentage
Variable in the
, (SUM(S.Quantity * S.Price) * @CommissionPercentage) AS Commission
SELECT clause
FROM
dbo.Sales AS S
JOIN dbo.SalesPersons AS SP
ON S.SalesPersonID = SP.SalesPersonID
WHERE S.SaleDate = @Today
GROUP BY Variable in the
SP.FirstName WHERE clause
, SP.LastName; GO

204
Variables

Table variable

The table variable:


• Stores multiple values in rows and columns
• Is queried with SELECT DML statements like any other table

In the example below, we (INNER) JOIN table variable to filter the recordset.

TableName CustomerID IsCommisionable FiscalYear FiscalMonth SalesValue


1 1 1967 9 927.288
1 1 1967 10 476.5055
1 1 1967 11 965.9223
1 1 1967 12 92.0612
2 1 1967 5 820.0465
2 1 1968 1 906.2333
2 0 1968 1 518.9526
2 1 1968 2 408.4823
2 1 1968 2 295.8332
3 1 1967 4 946.367
3 1 1967 11 296.7948
3 1 1967 11 762.0847
3 0 1968 1 486.4593

DECLARE @IsCommisionable BIT = 1; Create variable

DECLARE @TableVariable TABLE


( Create Table Variable
FiscalYear SMALLINT
, FiscalMonth TINYINT
);

INSERT @TableVariable
( Assign values (INSERT)
FiscalYear
, FiscalMonth FiscalYear FiscalMonth

) 1967 11

SELECT 1967, 11 1967 12

UNION ALL SELECT 1967, 12 1968 1

205
Variables

UNION ALL SELECT 1968, 1;


SELECT
CustomerID
, CAST(S.FiscalYear AS CHAR(4))
+ '-' + RIGHT('0' + CAST(S.FiscalMonth AS VARCHAR(2)), 2) AS Period
, SUM(S.SalesValue) AS Sum_SalesValue
FROM
dbo.Sales AS S Add leading zero
JOIN @TableVariable AS TV
ON S.FiscalYear = TV.FiscalYear Use the Table Variable in the FROM clause
AND S.FiscalMonth = TV.FiscalMonth
AND S.IsCommisionable = @IsCommisionable
GROUP BY Variable in the ON clause
CustomerID
, CAST(S.FiscalYear AS CHAR(4))
+ '-' + RIGHT('0' + CAST(S.FiscalMonth AS VARCHAR(2)), 2)
ORDER BY
CustomerID
, Period; CustomerID Period Sum_SalesValue
GO 1 1967-11 965.9223
1 1967-12 92.0612

Scope of the variable 2 1968-01 906.2333


3 1967-11 1058.8795
The variable exists in the batch, in which it is created.

DECLARE @VariableNumeric INT = 1;


SELECT @VariableNumeric;
End of the batch
GO
@VariableNumeric is not alive anymore

SELECT @VariableNumeric; Next batch


GO
Must declare the scalar variable “@VariableNumeric”.

@VariableNumeric is created in the first batch and can’t be used in the next batch.

There are system functions, with names starting with 2 at symbols (@@). They are not variables.
! SELECT
@@VERSION AS [Database Version]
, @@SPID AS [Session ID]
, @@SERVERNAME AS [Server Name]
, @@LANGUAGE AS [Language];
GO

206
Loops
WHILE

WHILE Loops

The WHILE statement is a loop i.e. a block of statement(s) in which the execution is repeated as long as the verifica-
tion of condition returns True.

The syntax is:

1. WHILE (Condition) The execution steps are:


2. BEGIN 1. The Condition is verified (line 1)
3. ...Statement1...; 2. If the Condition is met (True), the execution continues inside the BEGIN... END
4. ...Statement2...; block (lines 3, 4), else the BEGIN... END block is skipped (continue to line 7)
5. END 3. The statement(s) inside the BEGIN... END block (lines 3, 4)) is/are executed
6. 4. The Condition is verified again (line 1)
7. Statement3...; 5. Repeat (loop) the statement(s) inside the BEGIN... END block (lines 3, 4) until the
Condition (line 1) is met (True) and exit the BEGIN... END block (continue to line 7 )
6. If the Condition (line 1) is never met (False) after the execution is inside the BE-
GIN... END block (lines 3, 4)), the loop repeats infinitely (Infinite Loop)

Example: The condition is not met on the first verification

DECLARE @LoopedValue INT = 1;

The condition is not met (the value of @LoopedValue


WHILE (@LoopedValue >= 5)
is 1) and the BEGIN... END block is skipped
BEGIN
PRINT @LoopedValue; Example: Count to 5
SELECT @LoopedValue += 1;
END To loop the numbers from 1 to 5, we need one variable that stores and
GO prints the current value and another variable that counts the loops:

DECLARE Variables
@Counter TINYINT = 1 Counts the loops
, @LoopedValue INT = 1; Stores and prints in the looped value

WHILE (@Counter <= 5) Condition


BEGIN
PRINT @LoopedValue; Print the current value of @LoopedValue
SELECT
@LoopedValue += 1 Increase the value of @LoopedValue by 1
, @Counter += 1; Increase the value of @Counter by 1
END
GO

207
Loops
WHILE

When the condition is verified for the first time, the value of both variables is 1 and the verification of the condition
returns True (1 is less or equal to 5).
Inside the BEGIN... END block the value of @LoopedValue (1) is printed and the values of both variables are increased
by 1. Now the value of the both variables is 2.
The condition is verified again and returns True (2 is less or equal to 5)
The value of @LoopedValue (2) is printed and the values of both variables are increased by 1. Now the value of the
both variables is 3.

The condition is verified again and returns False (6 is not less or equal to 5)
The execution of the WHILE loop is terminated.

Example: Print the capital characters of the alphabet

To print the alphabet, we use the built-in function CHAR() that converts an integer value to its corresponding ASCII
code.

The same variable @LoopedValue is used for data manipulation and counting purposes:

DECLARE @LoopedValue INT = 65; SELECT CHAR(65); Capital A

WHILE (@LoopedValue <= 90) SELECT CHAR(90); Capital Z


BEGIN
PRINT CHAR(@LoopedValue);

SET @LoopedValue += 1;
END ...
GO

Example: The condition can not be met after the execution is inside the BEGIN... END block (Infinite Loop)

DECLARE @Counter INT = 1 Starting from 1 and decreasing @Counter by 1 (1, 0, -1, -2...)), the condition is
always True. (the value of @Counter is always less than 50)
WHILE (@Counter <= 50)
BEGIN The condition will never be met and the WHILE loop will be repeated until the
PRINT @Counter; execution time-out is reached.
To change the time-out in SSMS only for the current tab, right-click in the Query
SELECT @Counter -= 1; Window Query Options… Execution General Execution time-
END out:
GO
! As this statement is an infinite loop, cancel it as soon as possible.

208
Loops
WHILE

When we change the query time-out and execute again with the same WHILE statement, after 1 second an error
message is returned:
Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not
responding.

BREAK... CONTINUE or CONTINUE... BREAK

CONTINUE and BREAK control the execution inside the BEGIN... END block. They are used together with another con-
dition.

To stop the looping when a certain condition is met, we can use the IF statement inside the BEGIN... END block:

DROP TABLE IF EXISTS #WHILEResult1;


GO

CREATE TABLE #WHILEResult1 (Result VARCHAR(16));

DECLARE
@MaxID INT Select the maximum value of
column ID (4) into variable WHILE1
, @ID INT = 1;
ID Column1 Column2
1 100 A
SELECT @MaxID = MAX(ID)
2 101 B
FROM dbo.WHILE1;
3 102 C
4 103 D

209
Loops
WHILE

WHILE (@ID <= @MaxID)


BEGIN
If @ID is less or equal to 2, insert a row in #WHILEResult1
IF (@ID <= 2)
and CONTINUE the execution (Verify the WHILE condition again)
BEGIN
INSERT #WHILEResult1 (Result)
SELECT CONCAT(CAST(Column1 AS VARCHAR), ' - ', Column2)
FROM dbo.WHILE1
WHERE ID = @ID;

SELECT @ID += 1; Transfer the data for the row where the ID
matches the current @ID into the temp table

CONTINUE;
END
ELSE If @ID is not less or equal to 2, BREAK the execution
BEGIN BREAK; END
END
#WHILEResult1
SELECT * Result
SELECT the data 100 - A
FROM #WHILEResult1;
from the temp table
GO 101 - B

DROP TABLE IF EXISTS #WHILEResult1; Clean up the temp table


GO

210
Common Table Expressions
(CTE)

Common Table Expression (CTE) is a virtual recordset (VR), used in the subsequent DML statement.

The syntax is:

WITH CTE_Name (Col1, Col2) AS


(
SELECT CTE definition
Column1
, Column2
FROM DatabaseName.SchemaName.TableName
)
SELECT Subsequent DML statement
Col1
, Col2
FROM CTE_Name;
GO

The life cycle of CTE is:


1. The WITH clause creates the VR that is used in the next DML statement
2. The next DML statement is using the VR
3. After the execution of the DML statement, the CTE doesn’t exist anymore

1. CTE

DML statement that


2.
uses the CTE CTE

3. Another statement CTE doesn’t exist anymore

Data sources for the examples below:

Customers
CustomerID FirstName LastName Email DateTimeRegistered IsActive
1 Anabel Larson anabel.larson@customer5.info 1968-07-02 03:22:20.133 1
2 Anna Laurier anna.laurier@customer2.net 1969-12-14 19:24:49.527 NULL
3 Beverly NULL NULL 1969-06-02 22:09:48.943 NULL
4 John Smith john.smith@customer1.com 1968-04-12 06:16:25.337 1
5 John Smith john.smith@customer3.org 1963-04-11 01:55:38.420 NULL
6 Melanie Larson melanie.larson@customer4.biz 1965-11-18 16:34:48.753 1
7 Xavier Jameson xavier.garcía@customer6.com 1961-12-11 01:49:52.020 1
8 Zak Smith NULL 1967-12-03 06:15:50.117 NULL

211
Common Table Expressions
(CTE)

Sales
SaleID CustomerID ItemID DateTimeOfSale Quantity Price
1 3 2043 1969-09-05 12:47:05.467 4 1.27
2 4 5164 1969-12-18 02:15:36.470 2 1.56
3 3 5293 1969-11-10 20:18:49.610 77 55.12
4 5 6223 1964-01-10 21:13:18.150 3 4.89
5 7 1352 1961-12-12 02:28:34.363 89 0.84
6 2 1953 1969-12-22 07:39:41.243 53 2.84
7 3 8613 1969-12-24 18:34:13.777 22 23.41
8 2 5523 1969-12-27 16:43:01.750 55 127.19
9 1 8534 1969-12-25 08:33:58.513 1 45.88
10 2 9375 1969-12-15 09:39:11.403 6 0.43

CTE in the SELECT clause

As the CTE is a recordset, when we use it in the SELECT clause, we put it in a subquery, that has to return a single
value (one row and one column):
CTE name and aliases (CID, IA), created explicitly and
WITH CTE_SELECT (CID, IA) AS in the same order as the columns in the CTE definition
(
SELECT CTE definition
CTE_SELECT
CustomerID
CID IA
, 'Yes'
1 Yes
FROM dbo.Customers
4 Yes
WHERE IsActive = 1
6 Yes
)
7 Yes
SELECT
S.CustomerID
, C.FirstName
, C.LastName
, (SELECT IA FROM CTE_SELECT WHERE CID = S.CustomerID) AS IsActive
, MAX(S.DateTimeOfSale) AS Max_DateOfSale
, SUM(S.Quantity * S.Price) AS SalesValue
FROM
dbo.Sales AS S
Subquery to add the status
JOIN dbo.Customers AS C of the Customers
ON S.CustomerID = C.CustomerID
GROUP BY
S.CustomerID
, C.FirstName
, C.LastName;
GO

212
Common Table Expressions
(CTE)

CustomerID FirstName LastName IsActive Max_DateOfSale SalesValue


1 Anabel Larson Yes 1969-12-25 08:33:58.513 45.88
2 Anna Laurier NULL 1969-12-27 16:43:01.750 7148.55
3 Beverly NULL NULL 1969-12-24 18:34:13.777 4764.34
4 John Smith Yes 1969-12-18 02:15:36.470 3.12
5 John Smith NULL 1964-01-10 21:13:18.150 14.67
7 Xavier Jameson Yes 1961-12-12 02:28:34.363 74.76

CTE in the FROM clause

We can use a CTE in the FROM clause the same way we use any other recordset (data source).

The CTE creates a VR with data for the last 3 registered customers. The next statement is using the CTE to SELECT
the sales for these customers:

CTE name. Aliases are not specified here and the column
WITH CTE_FROM AS names as they are defined in the CTE definition are used
(
SELECT TOP 3 CustomerID, FirstName, LastName, DateTimeRegistered AS DTR
FROM dbo.Customers
ORDER BY DateTimeRegistered DESC
)
Custo-
merID

First- Last-
SELECT DTR
Name Name
C.CustomerID
, C.FirstName 2 Anna Laurier 1969-12-14 19:24:49.527
, C.LastName 3 Beverly NULL 1969-06-02 22:09:48.943
, C.DTR AS DateTimeRegistered 1 Anabel Larson 1968-07-02 03:22:20.133
, MAX(S.DateTimeOfSale) AS Max_DateTimeOfSale
, SUM(S.Quantity * S.Price) AS SalesValue
FROM
dbo.Sales AS S
JOIN CTE_FROM AS C
ON S.CustomerID = C.CustomerID
GROUP BY
C.CustomerID
, C.FirstName
, C.LastName
, C.DTR
ORDER BY If more than one customer bought in the
MAX(S.DateTimeOfSale) DESC same day, sort by SalesValue descending
, SUM(S.Quantity * S.Price) DESC;
GO

213
Common Table Expressions
(CTE)

CustomerID FirstName LastName DateTimeRegistered Max_DateTimeOfSale SalesValue


2 Anna Laurier 1969-12-14 19:24:49.527 1969-12-27 16:43:01.750 7148.55
1 Anabel Larson 1968-07-02 03:22:20.133 1969-12-25 08:33:58.513 45.88
3 Beverly NULL 1969-06-02 22:09:48.943 1969-12-24 18:34:13.777 4764.34

CTE in the WHERE clause


CTE_WHERE
WITH CTE_WHERE AS
( CustomerID

SELECT TOP 3 CustomerID 2

FROM dbo.Customers 3

ORDER BY DateTimeRegistered DESC 1

)
SELECT
C.CustomerID
, C.FirstName
, C.LastName
, MAX(S.DateTimeOfSale) AS Max_DateTimeOfSale
, SUM(S.Quantity * S.Price) AS SalesValue
FROM
dbo.Sales AS S
JOIN dbo.Customers AS C
ON S.CustomerID = C.CustomerID
WHERE
C.CustomerID IN
(
SELECT CustomerID
FROM CTE_WHERE
)
GROUP BY
C.CustomerID
, C.FirstName
, C.LastName
ORDER BY
MAX(S.DateTimeOfSale) DESC
, SUM(S.Quantity * S.Price) DESC;
Custo-
merID

First- Sales-
GO LastName Max_DateTimeOfSale
Name Value

2 Anna Laurier 1969-12-27 16:43:01.750 7148.55


1 Anabel Larson 1969-12-25 08:33:58.513 45.88
3 Beverly NULL 1969-12-24 18:34:13.777 4764.34

214
Common Table Expressions
(CTE)

Multiple CTEs in one statement

We can split multiple CTEs with comma (,) and use them in the subsequent DML statement.

WITH CTE_CountItemID (CustomerID, Count_ItemID) AS Fake Alias is not used, because


( a column list is provided
SELECT
CustomerID AS FakeAlias Count the sold items by CTE_CountItemID
, COUNT(DISTINCT ItemID) customer. No alias. The
Custo- Count_
FROM dbo.Sales column names are defined merID ItemID
in the column list
GROUP BY CustomerID 1 1
) 2 3
, CTE_Sum_SalesValue AS 3 3
( Alias list is not provided next to the 4 1
CTE name and the column names, defined
SELECT 5 1
in the CTE definition are used
CustomerID 7 1
, SUM(Quantity * Price) AS Sum_SalesVallue
FROM dbo.Sales
CTE_Sum_SalesValue
GROUP BY CustomerID
Custo- Sum_Sales-
HAVING SUM(Quantity * Price) > 20
merID Vallue
) Filter customers with
1 45.88
SELECT sales greater than 20
2 7148.55
CI.CustomerID
3 4764.34
, CONCAT(C.FirstName, ' ', C.LastName) AS CustomerName
7 74.76
, CI.Count_ItemID
, SSV.Sum_SalesVallue
, CASE
WHEN CI.Count_ItemID != 0 THEN (SSV.Sum_SalesVallue / CI.Count_ItemID)
END AS Avg_SalesValuePerItemID
FROM
dbo.Customers AS C
JOIN CTE_CountItemID AS CI
ON C.CustomerID = CI.CustomerID
JOIN CTE_Sum_SalesValue AS SSV
ON CI.CustomerID = SSV.CustomerID;
GO
Custo- Customer- Count_ Sum_Sales- Avg_SalesValue-
merID Name ItemID Vallue PerItemID
1 Anabel Larson 1 45.88 45.88
2 Anna Laurier 3 7148.55 2382.85
3 Beverly 3 4764.34 1588.1133
7 Xavier Jameson 1 74.76 74.76

215
Common Table Expressions
(CTE)

Recursive CTE

Recursive CTE is one that calls itself. In the example below we build a parent-child relation with recursive CTE.

GroupsParentChild
ParentID ChildID ParentToChild ChildToParent
NULL 3 Parent None - Child 3 Child 3 - Parent None
NULL 7 Parent None - Child 7 Child 7 - Parent None
1 8 Parent 1 - Child 8 Child 8 - Parent 1
2 4 Parent 2 - Child 4 Child 4 - Parent 2
3 1 Parent 3 - Child 1 Child 1 - Parent 3
3 2 Parent 3 - Child 2 Child 2 - Parent 3
6 9 Parent 6 - Child 9 Child 9 - Parent 6
7 5 Parent 7 - Child 5 Child 5 - Parent 7
7 6 Parent 7 - Child 6 Child 6 - Parent 7
8 10 Parent 8 - Child 10 Child 10 - Parent 8

Every child belongs to a parent. The child which parent is NULL is the top level. We can show the dependencies in 2
directions:
• Parent to child
• Child to parent

Parent to child (only ChildID = 3 is shown):

Level 0 (Anchor) Level 1 (Recursive) Level 2 (Recursive) Level 3 (Recursive)


Parent None - Child 3 Parent 3 - Child 1 Parent 1 - Child 8 Parent 8 - Child 10
Parent None - Child 3 Parent 3 - Child 2 Parent 2 - Child 4

As we don’t know how many levels we have, we need to build the dependency list of the parent-child hierarchy
dynamically:

WITH CTE_Recursive AS Anchor statement


(
SELECT
SourceStatement

'A' AS SourceStatement
ParentToChild

, 0 AS [Level]
ParentID

ChildID
Level

, ParentID
, ChildID
, ParentToChild
FROM dbo.GroupsParentChild
A 0 NULL 3 Parent None - Child 3
WHERE ParentID IS NULL
A 0 NULL 7 Parent None - Child 7
NULL is the top level

216
Common Table Expressions
(CTE)

UNION ALL UNION ALL combines the anchor and the recursive statement(s)

SELECT Recursive statement


'R' AS SourceStatement
, (C.[Level] + 1) AS [Level]
, GPC.ParentID
, GPC.ChildID
, GPC.ParentToChild
FROM dbo.GroupsParentChild AS GPC
JOIN CTE_Recursive AS C This CTE
ON GPC.ParentID = C.ChildID
)
SELECT * JOIN parent to child
FROM CTE_Recursive
ORDER BY
[Level]
, ParentID;
GO

Recursions (only ChildID = 3 is shown):


Step 1: Execute the anchor statement The result of the execution of the above
SourceStatement Level ParentID ChildID ParentToChild statement is:
A 0 NULL 3 Parent None - Child 3
tatement

ParentID
SourceS-

ChildID
Level

Step 2: Recursion 1 (anchor statement UNION ALL Level 1) ParentToChild


SourceStatement Level ParentID ChildID ParentToChild
R 1 3 1 Parent 3 - Child 1
A 0 NULL 3 Parent None - Child 3
R 1 3 2 Parent 3 - Child 2
A 0 NULL 7 Parent None - Child 7
R 1 3 1 Parent 3 - Child 1
Step 3: Recursion 2 (CTE_Recursive UNION ALL Level 2) R 1 3 2 Parent 3 - Child 2
SourceStatement Level ParentID ChildID ParentToChild
R 1 7 5 Parent 7 - Child 5
R 2 1 8 Parent 1 - Child 8
R 1 7 6 Parent 7 - Child 6
R 2 2 4 Parent 2 - Child 4
R 2 1 8 Parent 1 - Child 8
R 2 2 4 Parent 2 - Child 4
Step 4: Recursion 3 (CTE_Recursive UNION ALL Level 3) R 2 6 9 Parent 6 - Child 9
SourceStatement Level ParentID ChildID ParentToChild R 3 8 10 Parent 8 - Child 10
R 3 8 10 Parent 8 - Child 10

Values 10 and 4 don’t exist in column ParentID and the recursions are A - Anchor
terminated. The deepest level (3) is reached and the execution is finished. R - Recursive

217
Common Table Expressions
(CTE)

Child to parent
We can build the parent-child relation in the opposite direction – from child to parent:
DECLARE @ChildID INT = 10;

WITH CTE_Recursive AS
(
SELECT
'A' AS SourceStatement
, 0 AS [Level]
, ChildToParent
, ChildID
, ParentID
FROM dbo.GroupsParentChild
WHERE ChildID = @ChildID Start from the child

UNION ALL

SELECT
'R' AS SourceStatement
, (C.[Level] + 1) AS [Level]
, GPC.ChildToParent
, GPC.ChildID
, GPC.ParentID
FROM dbo.GroupsParentChild AS GPC
JOIN CTE_Recursive AS C
ON C.ParentID = GPC.ChildID Reverse the link logic
)
SELECT *
FROM CTE_Recursive;
GO

The result is:

SourceStatement Level ChildToParent ChildID ParentID


A 0 Child 10 - Parent 8 10 8
R 1 Child 8 - Parent 1 8 1
R 2 Child 1 - Parent 3 1 3
R 3 Child 3 - Parent None 3 NULL

The recursion No. 3 reaches the top level (ParentID = NULL) and the execution terminates.

Changing the value of the variable @ChildID to 9, selects the relation for ChildID = 9 (9 6 7)

218
Common Table Expressions
(CTE)

SourceStatement Level ChildToParent ChildID ParentID


A 0 Child 9 - Parent 6 9 6
R 1 Child 6 - Parent 7 6 7
R 2 Child 7 - Parent None 7 NULL

Limit the recursions


To avoid an infinite loop in recursive CTEs, we limit the number of the recursions by adding the OPTION clause
(MAXRECURSION hint) at the end of the CTE.

X is between 1 and 32,767.


OPTION (MAXRECURSION X)
The default value of X (if OPTION (MAXRECURSION X) is not used) is 100.

As the deepest level in the last example is 2, this statement is executed successfully:

WITH CTE_Recursive...
SELECT *
FROM CTE_Recursive
OPTION (MAXRECURSION 2);
GO

If we change the value of MAXRECURSION to 1, an error message “The statement terminated. The maximum
recursion 1 has been exhausted before statement completion.” is returned.

CTE Scope
The CTE can be used only by the DML statement that is after the WITH clause. If the statement that follows is using
the CTE, an error message “Invalid object name 'CTE_Name'.“ is returned.

! The statement preceding the CTE (the WITH clause) must end with semicolon (;)

219
Views

The view is T-SQL code. It is not a DB objects that stores data


The T-SQL is a single SELECT (SELECT, FROM, WHERE, GROUP BY) statement
The statement SELECTs data from one or more data source(s) (underlying table(s)) and creates
virtual recordset
The recordset is manipulated like a table

The view is used to


• Simplify the usage of the data (hides from the user complex calculations, JOINed data sources, etc.)
• Transform the data to the business needs
• Manage the security by permitting the users to access only the views that show data that the users are
authorized to use

The view can:


• SELECT specified columns

Customers
Custo- First- Last-
Email
merID Name Name
1 Anabel Larson anabel.larson@customer5.info
vw_Customers
2 Anna Laurier anna.laurier@customer2.net
3 Beverly NULL NULL
4 John Smith john.smith@customer1.com
5 John Smith john.smith@customer3.org

Before creating a view,


we delete the view with
the same name

DROP VIEW IF EXISTS dbo.vw_Customers


GO
CREATE VIEW (DDL statement)

CREATE VIEW dbo.vw_Customers


AS
SELECT View definition
FirstName AS FN vw_Customers
, LastName AS LN FN LN
FROM dbo.Customers; Anabel Larson
GO ORDER BY Anna Laurier

Use the View (DML statement) FN Beverly NULL


SELECT * , LN; John Smith
FROM dbo.vw_Customers GO John Smith

220
Views

We SELECT the data that the View provides like any other data source (table, table variable, table-valued function
etc.)

• SELECT specified rows

Customers
Custo- First- Last-
Email
merID Name Name
1 Anabel Larson anabel.larson@customer5.info
2 Anna Laurier anna.laurier@customer2.net vw_Customers
3 Beverly NULL NULL
4 John Smith john.smith@customer1.com
5 John Smith john.smith@customer3.org

SELECT * vw_Customers

FROM dbo.Customers Custo- First- Last-


Email
merID Name Name
WHERE CustomerID <= 3;
1 Anabel Larson anabel.larson@customer5.info
2 Anna Laurier anna.laurier@customer2.net
View definition 3 Beverly NULL NULL

• SELECT specified rows and columns

Customers
Custo- First- Last-
Email
merID Name Name
1 Anabel Larson anabel.larson@customer5.info
2 Anna Laurier anna.laurier@customer2.net vw_Customers
3 Beverly NULL NULL
4 John Smith john.smith@customer1.com
5 John Smith john.smith@customer3.org

View definition vw_Customers


SELECT First- Last-
FirstName Name Name
, LastName Anabel Larson
FROM dbo.Customers Anna Laurier
WHERE CustomerID <= 3; Beverly NULL

221
Views

• JOIN DB objects

Customers Sales
Custo- First- Last-

Quantity
Email

ItemID
SaleID

Custo-
merID
merID Name Name

Price
DateOfSale
1 Anabel Larson anabel.larson@customer5.info
2 Anna Laurier anna.laurier@customer2.net
1 3 1004 1967-10-17 21:03:40.813 3 1.24
3 Beverly NULL NULL
2 1 5273 1968-05-08 04:07:40.647 5 2.43
4 John Smith john.smith@customer1.com
3 4 5432 1965-09-04 22:51:01.710 2 12.44
5 John Smith john.smith@customer3.org
4 3 8541 1968-12-17 07:20:54.940 7 5.88
1 5 5 9514 1968-03-04 10:06:34.767 1 28.19

vw_Sales

SELECT View definition


C.CustomerID AS CustomerID
, C.FirstName
, C.LastName
, S.DateOfSale
, S.Quantity
, S.Price
, (S.Quantity * S.Price) AS LineTotal
FROM
dbo.Customers AS C vw_Sales
JOIN dbo.Sales AS S
Quantity

LineTo-
Custo-
merID

Name

Name
First-

Last-

Price

ON C.CustomerID = S.CustomerID
tal

DateOfSale
WHERE
YEAR(S.DateOfSale) = 1968 5 John Smith 1968-03-04 1 28.19 28.19
AND MONTH(S.DateOfSale) = 03; 10:06:34.767

• GROUP BY and aggregate the data

SELECT View definition


C.CustomerID
, CONCAT(C.FirstName, ' ', C.LastName) AS CustomerName
, SUM(S.Quantity * S.Price) AS LineTotal

222
Views

FROM vw_Sales

dbo.Customers AS C CustomerID CustomerName LineTotal

JOIN dbo.Sales AS S 1 Anabel Larson 12.15

ON C.CustomerID = S.CustomerID 3 Beverly 44.88

GROUP BY 4 John Smith 24.88

C.CustomerID 5 John Smith 28.19

,CONCAT(C.FirstName, ' ', C.LastName);

• Calculate the data

The column Discount in table Customers presents the discount in percent and it is used in the calculation of the
LineTotal.

Customers Sales

Quantity
Custo-
merID

ItemID
SaleID

Custo-
First- Last-
merID

Price
Email Discount DateOfSale
Name Name

1 Anabel Larson anabel.larson@customer5.info 0.111


1 3 1004 1967-10-17 21:03:40.813 3 1.24
2 Anna Laurier anna.laurier@customer2.net 0.000
2 1 5273 1968-05-08 04:07:40.647 5 2.43
3 Beverly NULL NULL 0.217
3 4 5432 1965-09-04 22:51:01.710 2 12.44
4 John Smith john.smith@customer1.com 0.068
4 3 8541 1968-12-17 07:20:54.940 7 5.88
5 John Smith john.smith@customer3.org 0.000
5 5 9514 1968-03-04 10:06:34.767 1 28.19
1

vw_SalesWithDiscounts

SELECT View definition


C.CustomerID AS CustomerID
, CONCAT(C.FirstName, ' ', C.LastName) AS CustomerName
, S.DateOfSale
, S.Quantity
, S.Price
, C.Discount
, (S.Quantity * S.Price) AS LineTotal
, (S.Quantity * S.Price * (1 - C.Discount)) AS LineTotalDiscount
FROM
dbo.Customers AS C
JOIN dbo.Sales AS S
ON C.CustomerID = S.CustomerID;

223
Views

vw_SalesWithDiscounts
Custome- Customer- LineTotalDis-
DateOfSale Quantity Price Discount LineTotal
rID Name count
3 Beverly 1967-10-17 21:03:40.813 3 1.24 0.217 3.72 2.9127600
1 Anabel Larson 1968-05-08 04:07:40.647 5 2.43 0.111 12.15 10.8013500
4 John Smith 1965-09-04 22:51:01.710 2 12.44 0.068 24.88 23.1881600
3 Beverly 1968-12-17 07:20:54.940 7 5.88 0.217 41.16 32.2282800
5 John Smith 1968-03-04 10:06:34.767 1 28.19 0.000 28.19 28.1900000

• Be accessible by specified user and not by other (data security)

vw_Employees limited permissions

Employees
EmployeeID FirstName LastName DepartmentID Salary User1 User2
1 Adam Short 2 5000
2 Jeremy Scott 1 15000
3 Anna Branton 3 2000

vw_EmployeesExtended full permissions

User3

An error message “The server principal “User4” is not able to access the database “LearnSQLServerIntui-
tively” under the current security context.” is returned to the users that are not permitted to manipulate the
View.
Advantages (Pros):
• Facilitate the correlations between the business logic and the data
• Facilitate the DB development by “masking” complex code
• Apply security rules
• Saves space that should be used if we store the result of a view in a table
• We can edit the code of the view without the need to edit the code of the dependent DB object(s)

Disadvantages (Cons):
• When we use the view as a table, we add the complexity of the view to the code. This can lead to bad
performance

224
Views

CREATE VIEW

The DDL statement CREATE VIEW is wrapping the definition of the View (the SELECT statement that builds the
virtual recordset that the view SELECTs):

CREATE VIEW [dbo].[vw_Sales]


AS
SELECT
The definition of the View
C.CustomerID AS CustomerID
, C.FirstName
, C.LastName
, S.DateOfSale
, S.Quantity
, S.Price
, (S.Quantity * S.Price) AS LineTotal
FROM
dbo.Customers AS C
JOIN dbo.Sales AS S
ON C.CustomerID = S.CustomerID;
GO

We can export the definition


of an existing View in SSMS:

225
Views

ALTER VIEW

To edit the code behind the view, we need to:


• Script the view in a New Query Editor Window:

• Edit the code


• Execute the code (press F5)

To browse the Views in the Object Catalog:

SELECT *
FROM sys.views;
GO

SELECT *
FROM sys.objects
WHERE [type] = 'V';
GO

226
Functions

The function:
• Is a DB object, stored in the DBE
• Is encapsulated and reusable code that extends the functionality of T-SQL
• Is created by us (User-defined functions) or installed together with SQL Server®
(Built-in and System functions)
• Requires or does not require input parameter(s) (parametrized function) with or without default values
• Outputs single value (scalar-valued function) or recordset (table-valued function)
• Returns the same (deterministic function) or different (non deterministic function) value(s) on every
execution

Input parameters Function Output

Item Color udf_PaintShop(Item, Color) Returned value(s)

The paint shop may act like a function.


We can input the item (car, guitar) and define the printing color (blue, red).
On the output is the painted item.

We can define a calculation that is often used, encapsulate it in a function and call it when needed.

udf__CalculateRebate
Input Output
(Function, data manipulation)
RebatePercent = 0.8 (5324.73 * 0.8) 4,259.784
SalesValue = 5324.73

Input Parameters
When we create a function, we define if:
• It requires input parameter(s) or not
• A function that removes the time from DATETIME data type (1963-04-17 18:23:45.517 to 1963-04-17

227
Functions

00:00:00.000), doesn’t need input parameter


• A function that divides two numbers and avoids divide by zero needs two input parameters – one for
the dividend and one for the divisor
• The parameters have DEFAULT values (optional parameters)
• When we call the function, can omit the values for the parameters that have DEFAULT. In this case
the default value is used
• The parameters without default value can’t be omitted

Function data manipulation


The code of the function manipulates the data. Similar to the view and the stored procedure, the function’s code is
called (function’s) definition.
The definition of the function is not visible for the code that calls the function and the complexity of the function’s
code is hidden.

Output
The function returns a result, which type define the function as:
• Scalar-valued – returns single value
• Inline Table-valued – returns multiple values (table), created with a single statement
• Multi-statement Table-valued – returns multiple values (table), created with multiple statements

Deterministic and Non Deterministic Functions

The result of a function may be the same or not the same on every execution.

Deterministic Non Deterministic


Time asked How much is 2 + 3? What is the time now? Give me a random number
1 5 1966-04-27 10:53:47.657 0.254343874234445
2 5 1967-07-03 11:08:07.370 0.982723166477615
3 5 1968-11-05 09:23:15.060 0.818569863640294

Deterministic
The result of How much is 2 + 3? is always the same (5):

SELECT (2 + 3) AS Calculation;
GO

Execution 1 Execution 2 Execution 3

Calculation Calculation Calculation


5 5 5

228
Functions

The function that calculates 2 + 3 is deterministic.

The built-in function that concatenates customer’s FirstName and LastName is also deterministic:

SELECT CONCAT('John', ' ', 'Smith') AS FullName;


GO

Execution 1 Execution 2 Execution 3

FullName FullName FullName


John Smith John Smith John Smith

Returns the same result on every execution, unless the data in the source DB object is changed.

Non Deterministic
The results of
• What is the time now?
• Give me a random number
are different on every execution of the functions:

SELECT
GETDATE() AS DateTimeNow
, RAND() AS Random;
GO

Execution 1 Execution 2 Execution 3

DateTimeNow Random DateTimeNow Random


1966-04-27 10:53:47.657 0.254343874234445 1968-11-05 09:23:15.060 0.818569863640294

DateTimeNow Random
1967-07-03 11:08:07.370 0.982723166477615

GETDATE() and RAND() are non deterministic.


The types of the
To list the types of the user-defined functions covered in this book in the Object Catalog: functions are:

type type_desc
SELECT DISTINCT [type], type_desc
FN SQL_SCALAR_FUNCTION
FROM sys.objects
IF SQL_INLINE_TABLE_VALUED_FUNCTION
WHERE [type] IN ('FN', 'TF', 'IF');
TF SQL_TABLE_VALUED_FUNCTION
GO

229
Functions

Before we create an object with a DDL statement, we clean up

DROP FUNCTION IF EXISTS SchemaName.udf_FunctionName;


GO
SchemaName.ObjectName naming convention

Comparison of the function types, based on the output:

Multi-statement Ta-
Scalar-valued Inline Table-valued
ble-valued
Input parameters Yes, No Yes, No Yes, No
DEFAULT values Yes, No Yes, No Yes, No
Output Single value of a specified data Recordset (multiple col- Recordset (multiple col-
type umns, multiple data types) umns, multiple data types)
Function’s definition
Is wrapped in BEGIN... END Yes No Yes
block?
Temp Tables in the function’s definition
Local and Global (#Local- No No No
TempTable, ##GlobalTempTable)
Table Variable (@TableVari- Yes No Yes
able)
Statements in the function’s definition
Is the definition single state- No Yes No
ment?
SELECT Yes Yes Yes
INSERT, UPDATE, DELETE Only manipulate table variable, No Only to manipulate the ta-
created in the definition ble variable that is INSERTed
Execution
The function can be called in statement
SELECT Yes Yes (in subquery) Yes (in subquery)
FROM Yes (in the ON clause) Yes Yes
WHERE Yes Yes (in subquery) Yes (in subquery)
GROUP BY Yes No No
ORDER BY Yes Yes Yes
Is executed on every row? Yes No No
Steps RETURN (Value) RETURN (T-SQL to generate 1. Create @TableVariable
or the returned recordset); 2. Execute multiple
1. DECLARE @ReturnVariable; statements to populate the
2. SET @ReturnVariable = ...; @TableVariable
3. RETURN @ReturnVariable; 3. RETURN

230
Functions

Scalar-valued Functions

In mathematics the division by zero is undefined. The code:


SELECT (15 / 0) AS Divide;
GO
returns error message “Divide by zero error encountered.”.

We can create a function udf_Divide() and use it everywhere we need to divide. It accepts two input parameters
(@Dividend and @Divisor) and returns a single value - which is the result of the division. When the divisor is NULL
or 0, the returned value is NULL.
Function parameters
CREATE FUNCTION dbo.udf_Divide(@Dividend DECIMAL(38, 6), @Divisor DECIMAL(38, 6))
RETURNS DECIMAL(38, 6) Data type of the value that the function returns Parameter name and data type
AS AS is optional and can be omitted
BEGIN
RETURN (CASE WHEN ISNULL(@Divisor, 0) != 0 THEN (@Dividend / @Divisor) END); Function Definition
END
GO

Name type type_desc


The functions in the Object Catalog:
udf_Divide FN SQL_SCALAR_FUNCTION

To use a scalar-valued function, we add it in the SELECT, ON (in FROM), WHERE, GROUP BY or ORDER BY clause.

Test divide by zero:

Division
SELECT dbo.udf_Divide(15, 0) AS Division;
NULL
GO

Test divide:

Division
SELECT dbo.udf_Divide(15, 0.35) AS Division;
42.857142
GO

Data sources for the examples below:

Customers Custo- First- Last-


Email DateTimeRegistered
merID Name Name
1 Anabel Larson anabel.larson@customer5.info 1966-08-24 05:58:58.983
2 Anna Laurier anna.laurier@customer2.net 1967-09-09 16:43:46.510
3 Beverly NULL NULL 1967-10-15 16:57:11.237
4 John Smith john.smith@customer1.com 1965-07-12 17:20:31.277
5 John Smith john.smith@customer3.org 1964-09-02 17:27:07.667

231
Functions

Sales SaleID CustomerID ItemID Quantity Price DateTimeOfSale


1 3 1004 3 1.24 1967-10-17 04:37:37.487
2 5 5273 5 2.43 1968-05-08 20:44:01.887
3 4 5432 2 12.44 1965-09-04 22:13:09.130
4 3 8541 7 5.88 1968-12-17 08:46:12.740
5 5 9514 1 28.19 1968-03-04 10:28:11.877

We can create a user-defined scalar-valued function that returns the last sale value for a specified customer:

CREATE FUNCTION dbo.udf_LastSaleValueForCustomerID(@CustomerID INT)


RETURNS MONEY
AS The same data type
BEGIN
Creates a variable to store the value that the function
DECLARE @LastSaleValue MONEY;
returns (the same data type like the returned variable)

SET @LastSaleValue = Assign value to the variable


(
Can’t use the SELECT statement
SELECT TOP 1 (Quantity * Price)
directly to return a value
FROM dbo.Sales
WHERE CustomerID = @CustomerID
ORDER BY DateTimeOfSale DESC
)
The last statement is RETURN to
RETURN @LastSaleValue; return the value from the variable
END to the caller of the function
GO

Test the function for CustomerID = 3:


LastSaleValue
SELECT dbo.udf_LastSaleValueForCustomerID(3);
41.16
GO

The code below uses udf_LastSaleValueForCustomerID() and udf_Divide()functions. The result of udf_
LastSaleValueForCustomerID() is passed as parameter in the udf_Divide() function:

SELECT
C.CustomerID
, C.FirstName
, C.LastName
, AVG(Quantity * Price) AS Avg_SalesValue
, dbo.udf_LastSaleValueForCustomerID(C.CustomerID) AS Last_SaleValue

232
Functions

, dbo.udf_Divide
(
AVG(Quantity * Price)
, dbo.udf_LastSaleValueForCustomerID(C.CustomerID) @Divident (Avg_SalesValue)
)
AS Percentage
FROM
@Divisor (Last_SaleValue) - Nested functions
dbo.Customers AS C
JOIN dbo.Sales AS S
ON C.CustomerID = S.CustomerID (Avg_SalesValue / Last_SaleValue) = Percentage
GROUP BY
C.CustomerID
, C.FirstName Custo- First- Last- Avg_Sales- Last_Sa- Percent-
merID Name Name Value leValue age
, C.LastName
3 Beverly NULL 22.44 41.16 0.545189
ORDER BY CustomerID;
4 John Smith 24.88 24.88 1.000000
GO
5 John Smith 20.17 12.15 1.660082

To define the DEFAULT value of a parameter, we add it to the parameter name and data type.

We can use NULL as DEFAULT and handle the default value (NULL) in the function’s definition:

CREATE FUNCTION dbo.udf_DateNoTime(@DateTime DATE = NULL) The default value for @DateTime
RETURNS DATE parameter is NULL
AS
BEGIN
DECLARE @Date DATE;

IF (@DateTime IS NULL) Handle the default parameter -


BEGIN SELECT @DateTime = GETDATE(); END replace NULL with now

SELECT @Date = CONVERT(CHAR(10), @DateTime, 120);

RETURN @Date;
END
GO

233
Functions

Call the function with a specified parameter value:

SELECT dbo.udf_DateNoTime ('1968-10-08 13:45:18.277') AS DateNoTime;


GO
DateNoTime
Calling the function with the DEFAULT value (NULL) returns the date of now: 1968-10-08

SELECT dbo.udf_DateNoTime (DEFAULT) AS DateNoTime;


GO
DateNoTime
1966-04-28
Use the default value (today is 1966-04-28).

If the keyword DEFAULT is omitted, the DEFAULT value is not passed and an error message “An insufficient
! number of arguments were supplied for the procedure or function dbo.udf_DateNoTime.“ is returned.

Pass a column value as input parameter:

SELECT
SaleID
, CustomerID
, ItemID
, DateTimeOfSale
, dbo.udf_DateNoTime(DateTimeOfSale) AS DateOfSale
FROM dbo.Sales
ORDER BY CustomerID; Date and time Date only
GO
SaleID CustomerID ItemID DateTimeOfSale DateOfSale
1 3 1004 1967-10-17 04:37:37.487 1967-10-17
4 3 8541 1968-12-17 08:46:12.740 1968-12-17
3 4 5432 1965-09-04 22:13:09.130 1965-09-04
5 5 9514 1968-03-04 10:28:11.877 1968-03-04
2 5 5273 1968-05-08 20:44:01.887 1968-05-08

The DEFAULT value can not be additionally manipulated in the function’s definition:

CREATE FUNCTION dbo.udf_Calculation(@Value1 INT, @Value2 INT = 15)


RETURNS INT
AS
BEGIN RETURN (@Value1 + @Value2); END Default = 15
GO

234
Functions

Call with specified values: 7 + 12 = 19

SELECT dbo.udf_Calculation (7, 12) AS Calculation; Calculation


GO 19

Call with DEFAULT value: 7 + 15 = 22

SELECT dbo.udf_Calculation (7, DEFAULT) AS Calculation;


GO Calculation
22
Call and pass DEFAULT to parameter that doesn’t have specified DEFAULT value:

SELECT dbo.udf_Calculation (DEFAULT, 12) AS Calculation;


GO

Calculation
Default value for the first parameter
NULL

An example of a scalar-valued function that has no parameter and returns today’s date (no time):

CREATE FUNCTION dbo.udf_DateNow() No parameter


RETURNS DATE
AS
BEGIN
DECLARE @DateNow DATE;

Assigning DATETIME data type to


SELECT @DateNow = GETDATE();
DATE data type cuts the time

RETURN @DateNow;
END
GO

Call the function without parameter (brackets only):

SELECT dbo.udf_DateNow() AS DateToday; Today is 1966-04-28


GO
DateToday
1966-04-28

Table-valued Functions

The table-valued functions return a recordset.

235
Functions

Inline
The inline table-valued function is constructed by one statement.

This function returns the total sales for the customers:

CREATE FUNCTION dbo.udf_SumSalesValue()


RETURNS TABLE
AS
RETURN
(
SELECT Single statement
CustomerID
, SUM(Quantity * Price) AS Sum_SaleValue
FROM dbo.Sales
GROUP BY CustomerID
)
GO

Call the function:


CustomerID Sum_SalesValue
3 44.88
SELECT *
4 24.88
FROM dbo.udf_SumSalesValue();
5 40.34
GO

Collect the totals of SaleValue for the customers and calculate the percentage of the current sale of the total
sales:

SELECT
C.CustomerID
Scalar-valued function
, C.FirstName
, C.LastName
, dbo.udf_DateNoTime(S.DateTimeOfSale) AS DateOfSale
, (S.Quantity * S.Price) AS SaleValue
, SSV.Sum_SaleValue
, dbo.udf_Divide
( Scalar-valued function

(S.Quantity * S.Price)
, SSV.Sum_SaleValue
)
AS Percentage
FROM

236
Functions

dbo.Customers AS C
JOIN dbo.Sales AS S
Table-valued function
ON C.CustomerID = S.CustomerID
JOIN dbo.udf_SumSalesValue() AS SSV
ON C.CustomerID = SSV.CustomerID
ORDER BY CustomerID; Implement the table-valued function
GO by JOINing it in the FROM clause:

Sum_
Custo- First- Last- SaleV- Percent-
DateOfSale SaleV-
merID Name Name alue age
alue
3 Beverly NULL 1967-10-17 3.72 44.88 0.082887 3.72 / 44.88 = 0.082887
3 Beverly NULL 1968-12-17 41.16 44.88 0.917112
4 John Smith 1965-09-04 24.88 24.88 1.000000
5 John Smith 1968-05-08 12.15 40.34 0.301189
5 John Smith 1968-03-04 28.19 40.34 0.698810

Multi-Statement
Creates a recordset by executing multiple statements. Function udf_EligibleCustomers() returns CustomerID
and Sum_SalesValue for customers that have a total SaleValue greater then the parametrized threshold value.

CREATE FUNCTION dbo.udf_EligibleCustomers (@ThresholdValue MONEY)


RETURNS
@Avg_SalesValuesLast3Customers TABLE
(
CustomerID INT Specifies the structure of the table
, Sum_SalesValue MONEY variable that the function returns
)
AS
BEGIN
DECLARE @EligibleCustomers TABLE (CustomerID INT); Statement 1 (Table variable in
INSERT @EligibleCustomers (CustomerID) the function’s definition)
SELECT CustomerID
FROM dbo.Sales Statement 2 (Populate the table variable)
GROUP BY CustomerID
HAVING SUM(Quantity * Price) > @ThresholdValue;
Statement 3 (Populates
INSERT @Avg_SalesValuesLast3Customers (CustomerID, Sum_SalesValue) the table variable
SELECT CustomerID, SUM(Quantity * Price) AS Sum_SalesValue that the function
FROM dbo.Sales returns. Filter with
the values from the
WHERE
other table variable
CustomerID IN @EligibleCustomers)

237
Functions

(
SELECT CustomerID
FROM @EligibleCustomers
)
GROUP BY CustomerID;

RETURN
END
GO
CustomerID Sum_SalesValue
Call the function with @ThresholdValue = 30: 3 44.88
5 40.34
SELECT *
FROM dbo.udf_EligibleCustomers(30);
GO

Call the function with @ThresholdValue = 41: CustomerID Sum_SalesValue


3 44.88
SELECT *
FROM dbo.udf_EligibleCustomers(41);
GO

Implement the function udf_EligibleCustomers in the FROM clause and calculate the DiscountedValue only for
the eligible customers (the ones who have total SalesValue above the threshold):

SELECT
C.CustomerID
, C.FirstName
, C.LastName
, dbo.udf_DateNoTime (S.DateTimeOfSale) AS DateOfSale
, (S.Quantity * S.Price) AS SalesValue
, EC.Sum_SalesValue The discounted
value is 90%
, CASE
WHEN EC.Sum_SalesValue IS NOT NULL THEN ((S.Quantity * S.Price) * 0.9)
END AS DiscountedValue
FROM
Only the eligible customers
dbo.Customers AS C
JOIN dbo.Sales AS S
ON C.CustomerID = S.CustomerID
LEFT JOIN dbo.udf_EligibleCustomers(41) AS EC
ON C.CustomerID = EC.CustomerID
ORDER BY CustomerID; Table-valued function. Pa-
GO rameter to filter the data.

238
Functions

Sum_
Custo- First- Last- Sales- Discount-
DateOfSale Sales-
merID Name Name Value edValue
Value
3 Beverly NULL 1967-10-17 3.72 44.88 3.34800
3 Beverly NULL 1968-12-17 41.16 44.88 37.04400
4 John Smith 1965-09-04 24.88 NULL NULL
5 John Smith 1968-03-04 28.19 NULL NULL
5 John Smith 1968-05-08 12.15 NULL NULL

Functions Nesting

Nest one function inside another. The inner function is executed first and its result is used as a parameter for the
outer function.
Result
SELECT RIGHT(LEFT('This is My String', 14), 6) AS Result;
My Str
GO

Position 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
String T h i s i s M y S t r i n g
LEFT(String, 14) Left 14 characters
RIGHT(String, 6) Right 6 characters

The result of LEFT() ('This is My Str') is used as parameter for RIGHT().

Limitations:
• Nesting maximum 32 levels
• User-fefined functions: Maximum 2100 input parameter
• No special characters in the name

239
Stored Procedures

The Stored Procedure (SP):


• Is the most powerful programmable object in SQL Server®
• Encapsulates reusable code, stored in the DBE
• Uses the full capacity of T-SQL. All the statements we learned so far can be used in the
SP definition, except:
• USE
• CREATE SCHEMA
• CREATE, ALTER VIEW
• CREATE, ALTER FUNCTION
• CREATE, ALTER PROC
• Can call another SP
• Can be parametrized:
• With input parameters (DEFAULTed or not)
• With output parameters
• Returns status value (success or error message)

The SPs in the Object catalog:

SELECT DISTINCT name, [type], type_desc SELECT DISTINCT *


FROM sys.objects FROM sys.procedures
WHERE [type] IN ('P', 'X'); GO
GO

P - SQL Stored Procedure


X - Extended Stored Procedure

Naming Conventions for SPst

We can add the following rules, that are specific for SPs to the Naming Convention rules:
• SP (and every other DB object) name is a maximum of 128 characters
• Avoid sp_ prefix. It is used by system SPs
• Add the group where the SP belongs as prefix:
• main_ - general use
• rep_ - reporting
• etl_ - ETL
• task_ - called by SQL Job Agent, Windows scheduled job etc
• Add the action (what the SP is doing) as suffix:
• _Select - SELECTs data
• _Insert - INSERTs data
• _Update - UPDATEs data
• _Delete - UPDATEs data

240
Stored Procedures

Tables, used as data source in the examples:

Customers
Custo- First- Last-
Email DateTimeRegistered
merID Name Name
1 Anabel Larson anabel.larson@customer5.info 1966-08-24 05:58:58.983
2 Anna Laurier anna.laurier@customer2.net 1967-09-09 16:43:46.510
3 Beverly NULL NULL 1967-10-15 16:57:11.237
4 John Smith john.smith@customer1.com 1966-07-12 17:20:31.277
5 John Smith john.smith@customer3.org 1964-09-02 17:27:07.667

Items
UnitOf-
ItemID ItemCode ItemDescription Price
Measure
1 ID52-P1 Interactive Doll - Pink Pcs 12.42
2 EG5-WW58 Word Whammer Kg 49.38
3 BT-D521 Bath Toy Duck Box 0.58
4 BT-S512 Bath Toy Forms (set) Pcs 5.68
5 B-P12_G Bike for Girls (Purple) Pcs 124.86

Sales
Custo- Quan-
SaleID ItemID Price DateTimeOfSale
merID tity
1 3 1 3 1.24 1967-10-17 04:37:37.487
2 5 3 5 2.43 1968-05-08 20:44:01.887
3 4 2 2 12.44 1965-09-04 22:13:09.130
4 3 4 7 5.88 1968-12-17 08:46:12.740
5 5 5 1 28.19 1968-03-04 10:28:11.877
6 2 2 12 12.28 1966-08-05 15:23:47.233
7 1 3 5 2.28 1969-11-14 07:18:52.667

CREATE PROC (PROCEDURE).

Creates a new SP.

! Has to be the first statement in the batch.

Before creating a new SP, delete the SP with the same name (if it exists):

DROP PROC IF EXISTS SchemaName.udf_StoredProcedureName;


GO
SchemaName.ObjectName naming convention

241
Stored Procedures

The syntax is:

CREATE PROC SchemaName.StoredProcedureName SchemaName.ObjectName naming convention


(
@Parameter1 INT Parameter(s) list
, @Parameter2 DECIMAL(18, 6) OUT OUT or OUTPUT(output parameter)
)
AS
BEGIN
SET NOCOUNT ON; NOCOUNT Turns off the messages that informs
how many rows are manipulated in a DML state-
SP definition
ments and avoid unnecessary network traffic
... Statement 2 ...;
... Statement 3 ...;
... Statement 4 ...;
END
GO

CREATE PROC is followed by the parameter(s) list, if the SP is parametrized. In the BEGIN… END block is the SP
definition – the code that the SP executes. The RETURN keyword inside the SP definition terminates the execution
of the SP.

To create a SP that has no input parameters and selects all customers, ordered from the last to the first registered:

CREATE PROC dbo.usp_Customers_Select


AS
BEGIN
SET NOCOUNT ON;

SELECT
FirstName
, LastName
, Email
, DateTimeRegistered
FROM dbo.Customers
DSO naming convention is allowed in
ORDER BY DateTimeRegistered DESC;
the stored procedure’s definition
END
GO

After the SP is created, we can EXECute it:


EXEC dbo.usp_Customers_Select;
GO

242
Stored Procedures

First- Last-
Email DateTimeRegistered
Name Name
Beverly NULL NULL 1967-10-15 16:57:11.237
Anna Laurier anna.laurier@customer2.net 1967-09-09 16:43:46.510
Anabel Larson anabel.larson@customer5.info 1966-08-24 05:58:58.983
John Smith john.smith@customer1.com 1966-07-12 17:20:31.277
John Smith john.smith@customer3.org 1964-09-02 17:27:07.667

Parametrized SP

The parameters are variables that give us the option to pass input values into the SP definition.
The parameters add flexibility to the logic of the SP.
To add parameters to a SP, we add the parameter(s) list after the CREATE PROC DDL statement and we define their
names and data types.

To create a SP that apply the CRUD DML statements Prefix type of the DB object
on the Customers table, we execute the CREATE PROC SPName - Name of the manipulated object
DDL statement: Suffix Action

1. CREATE PROC dbo.usp_Customers_CRUD No DEFAULT. Value has to be provided


on execution (mandatory parameter)
2. (
3. @Action CHAR(1) -- C = INSERT, R = SELECT, U = UPDATE, D = DELETE
4. , @CustomerID INT = NULL
5. , @FirstName NVARCHAR(32) = NULL Parameters list (line 3 to 7)
6. , @LastName NVARCHAR(64) = NULL
7. , @Email VARCHAR(128) = NULL NULL is the DEFAULT value. The SP can be executed
8. ) without providing a value for these parameters
9. AS
10. BEGIN -- usp_Customers_CRUD The data type matches the destination data type (col-
umns FirstName, LastName and Email in table Customers)
11. SET NOCOUNT ON;
12.
Add comments to facilitate
13. -- Error for mandatory parameter and exit
the future editing of the SP
14. IF (@Action IN ('R', 'U', 'D') AND @CustomerID IS NULL)
15. BEGIN
16. RAISERROR('Parameter @CustomerID is NULL', 11, 1); Handle the parameters and their
default values in the beginning of
17. RETURN;
the SP definition (lines 14 to 18)
18. END
19.
20. IF (@Action = 'C')
RETURN terminates the execution
21. BEGIN

243
Stored Procedures

22. INSERT dbo.Customers


23. (
24. FirstName
25. , LastName
26. , Email
27. , DateTimeRegistered If the value of the parameter @Action is:
28. ) • 'C' (Create), an INSERT
• 'R' (Read), an SELECT
29. SELECT
• 'U' (Update), an UPDATE
30. @FirstName
• 'D' (Delete), an DELETE
31. , @LastName DML statement is executed
32. , @Email
33. , GETDATE();
34. END
35.
36. IF (@Action = 'R')
37. BEGIN -- @Action = 'R'
38. SELECT
Mark with comments BEGIN and the
39. FirstName
matching END (line 37 and 45)
40. , LastName
41. , Email
42. , DateTimeRegistered
43. FROM dbo.Customers
44. WHERE CustomerID = ISNULL(@CustomerID, CustomerID); NULL = All Customers
45. END -- @Action = 'R'
46.
47. IF (@Action = 'U')
48. BEGIN
49. UPDATE dbo.Customers
50. SET
51. FirstName = @FirstName
52. , LastName = @LastName
53. , Email = @Email
54. WHERE CustomerID = @CustomerID;
55. END
56.
57. IF (@Action = 'D')
58. BEGIN CustomerID is the FK in table
59. DELETE dbo.Sales Sales. The FKs have to be de-
60. WHERE CustomerID = @CustomerID; leted before deleting the PK

244
Stored Procedures

61. After the FK rows are deleted


62. DELETE dbo.Customers from all the dependent ta-
63. WHERE CustomerID = @CustomerID; bles, the PK can be deleted
64. END
65. END -- usp_Customers_CRUD
66. GO Add comments to the parameter(s) list and SP definition to
facilitate the future editing and understanding of the SP.

Looks scary, but it is not!


• The parameter(s) list defines the parameters (variables)
• To assign a DEFAULT value to a parameter, we add the equal to sign and the DEFAULT value
• The parameters that have a DEFAULT value can be omitted when executing the SP
• The parameters that don’t have a DEFAULT value are mandatory and can‘t be omitted
when EXECuting the SP
• The DEFAULT value can be NULL or any value, compatible with the data type of the variable (the parameter)

First we check the parameters. In usp_Customers_CRUD the only check is if a value for the parameter @CustomerID
is passed for Read, Update or Delete actions (line 14). If the result it True (no value):
• RAISERROR() - returns custom error message
• RETURN - terminates the execution of the SP

In this simple SP we have just a few consecutive statements and we execute only three of them:
line 11
and lines 16, 17 (if @CustomerID is omitted for Read, Update, Delete actions)
and line 22 (on Create action)
or line 38 (on Read action)
or line 49 (on Update action)
or line 59, 62 (on Delete action)
With the IF condition we decide which portion of the code is to be executed. The @Action parameter determines
the CRUD (Create, Read, Update or Delete a row in a table) cases:
• To Create a new customer, we need values for the parameters @FirstName, @LastName and @Email.
@CustomerID is not needed, it is created automatically (Column CustomerID in Customers table is
IDENTITY(1, 1) i.e. auto incremental). In column DateOfRegistration we insert now
• The Read action SELECTs the row where CustomerID = @CustomerID from table Customers.
When @CustomerID is NULL, the right operand in the filtering condition is replaced with CustomerID
(CustomerID = CustomerID) and all the customers are selected.
• The Update action updates the attributes (the columns FirstName, LastName, Email) for @CustomerID
• The Delete action executes two statements:
• The first one deletes the rows for @CustomerID from the table Sales. This is needed, because we can’t
delete a Primary Key if a Foreign Key exists.
• The second statement deletes @CustomerID from table Customers.

245
Stored Procedures

EXEC PROC (EXECUTE PROCEDURE)

To execute a SP, we use EXEC PROC (EXECUTE PROCEDURE)

If we EXECute a SP without specifying value(s) for the parameters which don’t have DEFAULT values:

EXEC dbo.usp_Customers_CRUD;
GO
the error message “Procedure or function 'usp_Customers_CRUD' expects parameter '@Action', which was not
supplied.” is returned.

We can EXECute parametrized SP in two ways:

1. Pass the parameter(s) value(s) in the exact order as they are defined in the parameter(s) list:

EXEC dbo.usp_Customers_CRUD
'R' The value for Parameter 1 is in Position 1
, 5 The value for Parameter 2 is in Position 1
, DEFAULT
, DEFAULT
, DEFAULT; The value for Parameter 5 is in Position 1
GO

FirstName LastName Email DateTimeRegistered


John Smith john.smith@customer3.org 1964-09-02 17:27:07.667

If we use the DEFAULT value for the last parameter, we can skip the DEFAULT keyword:

EXEC dbo.usp_Customers_CRUD
'R' Parameter 1 in Position 1
, 5; Parameter 2 in Position 2
GO

We can execute a SP without the keyword EXEC, but this is not a good practice, because:
• The code is not easy to understand
• We can’t search the batch for a SP by the keyword EXEC

dbo.usp_Customers_CRUD
'R'
, 5;
GO

246
Stored Procedures

For any other than the last parameter, we can’t skip the keyword DEFAULT, because the next parameter takes the
previous position:

EXEC dbo.usp_Customers_CRUD
'C' @Action
If we skip this DEFAULT, the next parameter is used in position 2
, DEFAULT @CustomerID
(the value 'Antoine' is passed for parameter @CustomerID
, 'Antoine' @FirstName
, 'Kern'; @LastName
The last parameter @Email can be omitted
GO

SELECT *
FROM dbo.Customers;
GO

CustomerID FirstName LastName Email DateTimeRegistered


1 Anabel Larson anabel.larson@customer5.info 1966-08-24 05:58:58.983
2 Anna Laurier anna.laurier@customer2.net 1967-09-09 16:43:46.510
3 Beverly NULL NULL 1967-10-15 16:57:11.237
4 John Smith john.smith@customer1.com 1966-07-12 17:20:31.277
5 John Smith john.smith@customer3.org 1964-09-02 17:27:07.667
6 Antoine Kern NULL 1969-05-13 21:19:01.747

2. Pass the parameter(s) value and the parameter(s) name:

When we EXECute a SP with parameter(s) name and value(s), we do not have to list them in order:

EXEC dbo.usp_Customers_CRUD
@CustomerID = 5
, @Action = 'R';
GO

FirstName LastName Email DateTimeRegistered


John Smith john.smith@customer3.org 1964-09-02 17:27:07.667

To test the custom error, we EXECute the SP without passing a value for @CustomerID:

Execute this in a new SSMS window, starting from line 1. This helps the error message to return the correct
! line number.

247
Stored Procedures

1. EXEC dbo.usp_Customers_CRUD @Action = 'R';


2. GO

Msg 50000, Level 11, State 1, Procedure usp_Customers_CRUD, Line 17


Parameter @CustomerID is NULL

The error message points us to line 17. To debug the SP, we need to examine line 17.

We do this by EXECuting the system SP sp_helptext. The parameter that it accepts is ObjectName. The
returned recordset is the object (Function, Stored Procedure) definition:

EXEC sys.sp_helptext 'dbo.usp_Customers_CRUD';


GO

Text
16 ...
17 RAISERROR('Parameter @CustomerID is NULL', 11, 1);
18 ...

To UPDATE a row, we provide values for the parameters that edit the values for the columns - FirstName, LastName
and Email:

EXEC dbo.usp_Customers_CRUD
@Action = 'U'
, @FirstName = 'Jonathan'
, @LastName = 'Gourmand'
, @Email = 'jonathang@customer52341.org'
, @CustomerID = 3;
GO

Verify table Customers:

SELECT *
FROM dbo.Customers;
GO

248
Stored Procedures

Customers
CustomerID FirstName LastName Email DateTimeRegistered
1 Anabel Larson anabel.larson@customer5.info 1966-08-24 05:58:58.983
2 Anna Laurier anna.laurier@customer2.net 1967-09-09 16:43:46.510
3 Jonathan Gourmand jonathang@customer52341.org 1967-10-15 16:57:11.237
4 John Smith john.smith@customer1.com 1966-07-12 17:20:31.277
5 John Smith john.smith@customer3.org 1964-09-02 17:27:07.667
6 Antoine Kern NULL 2016-05-16 21:32:43.563

The way this SP is designed, if we omit a value for one of the parameters @FirstName, @LastName or
! @Email, its DEFAULT value (NULL) is used.

To DELETE all the rows for CustomerID = 3 in table Sales and the row in table Customers, we EXEC:

EXEC dbo.usp_Customers_CRUD
@Action = 'D'
, @CustomerID = 3;
GO

Verify table Sales:

SELECT *
FROM dbo.Sales;
GO

Sales
SaleID CustomerID ItemID Quantity Price DateTimeOfSale
2 5 3 5 2.43 1968-05-08 20:44:01.887
3 4 2 2 12.44 1965-09-04 22:13:09.130
5 5 5 1 28.19 1968-03-04 10:28:11.877
6 2 2 12 12.28 1966-08-05 15:23:47.233
7 1 3 5 2.28 1969-11-14 07:18:52.667

Verify table Customers:

SELECT *
FROM dbo.Customers;
GO CustomerID = 3 is deleted from
tables Sales and Customers.

249
Stored Procedures

Customers
CustomerID FirstName LastName Email DateTimeRegistered
1 Anabel Larson anabel.larson@customer5.info 1966-08-24 05:58:58.983
2 Anna Laurier anna.laurier@customer2.net 1967-09-09 16:43:46.510
4 John Smith john.smith@customer1.com 1966-07-12 17:20:31.277
5 John Smith john.smith@customer3.org 1964-09-02 17:27:07.667
6 Antoine Kern NULL 2016-05-16 11:08:46.963

Output Parameter(s)

The OUTPUT parameter(s) return value(s) to the caller (SSMS, external application, etc.) of the SP.

To CREATE PROC with OUTPUT parameter(s), we:


• Add parameter(s) in the parameter(s) list with the OUT (or OUTPUT) keyword
• Assign a value to the variable in the SP definition

To EXECute a SP with an OUTPUT parameter(s):


• Create @Variable that stores the value of the OUTPUT parameter
• EXECute the SP and assign the value of the OUTPUT parameter to the @Variable
• Manipulate the @Variable (the value of the OUTPUT parameter)

The SP usp_Sales returns a summarized or detailed recordset for specified customer, item and sale period.
Create usp_Sales:

1. CREATE PROC rep.usp_Sales Schema rep serves the reporting


2. (
3. @CustomerID INT = NULL -- Mandatory. Single value. Validated in the SP Definition
4. , @ItemID INT = NULL -- NULL = All_Values The comments in the parameter(s)
5. , @Grain CHAR(1) = NULL -- S = Summary; D = Details list and the object definition fa-
6. , @DateStart DATE = NULL -- NULL is converted to now cilitate the future understanding
and editing of the object
7. , @DateEnd DATE = NULL -- NULL is converted to tomorrow
8. , @ReturnParameter_OUT NVARCHAR(64) = NULL OUT -- number of the manipulated rows
9. )
10. AS
11. BEGIN
12. SET NOCOUNT ON;
13.
14. --** Handle Parameters - Start **--
15. IF (@CustomerID IS NULL)
16. -- Custom error
17. BEGIN

250
Stored Procedures

18. RAISERROR('Parameter @CustomerID is NULL', 11, 1);


19. RETURN;
20. END
21.
22. -- DEFAULT - Start
23. IF (@Grain IS NULL)
24. BEGIN SET @Grain = 'D'; END
25.
26. IF (@DateStart IS NULL)
27. BEGIN
28. SELECT @DateStart = MIN(DateTimeOfSale)
29. FROM dbo.Sales
30. WHERE CustomerID = @CustomerID;
31. END
32.
33. IF (@DateEnd IS NULL)
34. BEGIN
35. SELECT @DateEnd = MAX(DateTimeOfSale)
36. FROM dbo.Sales
37. WHERE CustomerID = @CustomerID;
38. END
39.
40. SET @DateEnd = DATEADD(DD, 1, @DateEnd);
41. -- DEFAULT - End
42. --** Handle Parameters - End **--
43.
44. --** Get the report - Start **--
45. -- Details
46. IF (@Grain = 'D')
47. BEGIN
48. SELECT
49. C.FirstName
50. , C.LastName
51. , C.Email
52. , C.DateTimeRegistered
53. , I.ItemDescription
54. , S.Quantity
55. , S.Price
56. , S.DateTimeOfSale
57. FROM
58. dbo.Sales AS S

251
Stored Procedures

59. JOIN dbo.Customers AS C


60. ON C.CustomerID = S.CustomerID
61. JOIN dbo.Items AS I
62. ON S.ItemID = I.ItemID
63. WHERE
64. C.CustomerID = @CustomerID
65. AND S.ItemID = ISNULL(@ItemID,S. ItemID) -- NULL = All Items
66. AND S.DateTimeOfSale >= @DateStart
67. AND S.DateTimeOfSale < @DateEnd
68. ORDER BY
69. C.FirstName
70. , C.LastName
71. , I.ItemCode
72. , S.DateTimeOfSale;
73.
74. SELECT @ReturnParameter_OUT =
'Grain: '
+ @Grain
+ '; Rows: '
+ CAST(@@ROWCOUNT AS VARCHAR);
75. END
76.
77. -- Summary
78. IF (@Grain = 'S')
79. BEGIN
80. SELECT
81. C.FirstName + ' ' + C.LastName AS CustomerName
82. , SUM(S.Quantity * S.Price) As SalesValue
83. FROM
84. dbo.Sales AS S
85. JOIN dbo.Customers AS C
86. ON C.CustomerID = S.CustomerID
87. JOIN dbo.Items AS I
88. ON S.ItemID = I.ItemID
89. WHERE
90. C.CustomerID = @CustomerID
91. AND S.ItemID = ISNULL(@ItemID, S.ItemID) -- NULL = All Items
92. AND S.DateTimeOfSale >= @DateStart
93. AND S.DateTimeOfSale < @DateEnd
94. GROUP BY
95. C.FirstName

252
Stored Procedures

96. , C.LastName
97. ORDER BY
98. C.FirstName
99. , C.LastName;
100.
101. SELECT @ReturnParameter_OUT =
'Grain: '
+ @Grain
+ '; Rows: '
+ CAST(@@ROWCOUNT AS VARCHAR);
102. END
103. --** Get the report - End **--
104. END
105. GO

On line 8 we add the parameter @ReturnParameter_OUT with a DEFAULT value of NULL (to be able to EXECute the
SP without the OUTPUT functionality) and OUT keyword that specifies the parameter as OUTPUT.
On lines 74 and 101 we assign a value to the OUTPUT parameter - the number of rows in the recordset, returned by
the SP.

The built-in function @@ROWCOUNT returns the number of the rows, manipulated by the last statement.

Additionaly the SP verifies the parameters:


• If @CustomerID doesn’t have a value, the execution is terminated (lines 15 to 20)
• If @Grain is not specified, 'D' (Details) is set (lines 23 and 24)
• If @DateStart and @DateEnd are not specified, the first (MIN) and last (MAX) sale dates for @CustomerID
are picked (lines 26 to 38). Additional logic can be added in the case when @CustomerID has no sales.
On line 40 we add one day to @DateEnd to avoid the usage of a function that removed the time from
DATETIME data type in the WHERE WHERE clause
• Based on the value of the @Grain parameter, we execute different statements that create the recordsets
for Details (lines 46 to 72) and Summary (lines 77 to 98)

We can EXECute the SP without using the OUTPUT functionality:

EXEC rep.usp_Sales
Details (@Action parameter is not speci-
@CustomerID = 5;
fied and the DETAILS value is used)
GO

253
Stored Procedures

First- Last- DateTimeRegis- Quan-


Email ItemDescription Price DateTimeOfSale
Name Name tered tity
John Smith john.smith@customer3.org 1964-09-02 Bike for Girls (Pur- 1 28.19 1968-03-04
17:27:07.667 ple) 10:28:11.877
John Smith john.smith@customer3.org 1964-09-02 Bath Toy Duck 5 2.43 1968-05-08
17:27:07.667 20:44:01.887

EXECute the summary for @CustomerID = 4:

EXEC rep.usp_Sales
@Grain = 'S' CustomerName SalesValue
, @CustomerID = 4; John Smith 24.88
GO
To handle the value, returned by the OUTPUT parameter(s), we need to follow this 3-steps logic:

Step 1: Create variable (@Out_ReturnParameter) that stores the value of the OUTPUT parameter:

DECLARE
@Out_ReturnParameter NVARCHAR(64) Variable that stores the value of the OUTPUT parameter
, @DateTimeExecuted DATETIME = GETDATE(); Variable that stores the value of now.
Inserted into the log table in Step 3
Step 2: EXECute the SP and populate the variable @Out_ReturnParameter:

EXEC rep.usp_Sales
@CustomerID = 5
, @ReturnParameter_OUT = @Out_ReturnParameter OUT; Assign a value to @Out_ReturnParameter

First- Last- DateTimeRegis- Quan-


Email ItemDescription Price DateTimeOfSale
Name Name tered tity
John Smith john.smith@customer3.org 1964-09-02 Bike for Girls (Pur- 1 28.19 1968-03-04
17:27:07.667 ple) 10:28:11.877
John Smith john.smith@customer3.org 1964-09-02 Bath Toy Duck 5 2.43 1968-05-08
17:27:07.667 20:44:01.887

Step 3: Manipulate the value of the OUTPUT parameter:

INSERT dbo.StoredProceduresLogs Add row in the Log


(
DateTimeExecuted
, Details

254
Stored Procedures

)
SELECT
GETDATE()
, @Out_ReturnParameter;
GO

Edit Step 2 - the value of @CustomerID to 4, @Grain to 'S' and EXECute the batch again:

EXEC rep.usp_Sales
@Grain = 'S'
CustomerName SalesValue
, @CustomerID = 4
John Smith 24.88
, @ReturnParameter_OUT = @Out_ReturnParameter OUT;

To verify the hahdling of the OUTPUT parameter in Step 3, we query the Log table:

SELECT *
FROM dbo.StoredProceduresLogs;
GO

LogID DateTimeExecuted Details


1 1968-05-17 11:15:59.973 Grain: D; Rows: 2
2 1968-06-05 23:48:05.633 Grain: S; Rows: 1
SP that EXECs another SP

SP usp_SalesAndLog EXECutes another SP (usp_Sales) and inserts the value of the OUTPUT parameter
(@ReturnParameter_OUT) in the log table. It collects and bypasses the parameters, needed to EXECute the inner SP.

Create usp_SalesAndLog that reproduces the 3-steps logic, explained above. Step 2 bypasses the parameters from
the outer SP to the inner SP:

CREATE PROC rep.usp_SalesAndLog


(
@CustomerID INT = NULL
, @ItemID INT = NULL
Bypass the parameters from
, @Grain CHAR(1) = NULL
the outer SP to the inner SP
, @DateStart DATE = NULL
, @DateEnd DATE = NULL
)
AS
BEGIN

255
Stored Procedures

SET NOCOUNT ON;



DECLARE Step 1
@Out_ReturnParameter NVARCHAR(64)
, @DateTimeExecuted DATETIME = GETDATE();

EXEC rep.usp_Sales Step 2


Bypass the parameters from
@CustomerID = @CustomerID the outer SP to the inner SP
, @ItemID = @ItemID
, @Grain = @Grain
, @DateStart = @DateStart
, @DateEnd = @DateEnd
, @ReturnParameter_OUT = @Out_ReturnParameter OUT;

INSERT dbo.StoredProceduresLogs Step 3


(
DateTimeExecuted
, Details
)
SELECT
GETDATE()
, @Out_ReturnParameter;
END
GO

EXECute the outer SP:

EXEC rep.usp_SalesAndLog
@CustomerID = 4 CustomerName SalesValue
, @Grain = 'S' John Smith 24.88
GO

Verify the handling of the OUTPUT parameter:

SELECT *
FROM dbo.StoredProceduresLogs;
GO
LogID DateTimeExecuted Details
1 1968-05-17 11:15:59.973 Grain: D; Rows: 2
2 1968-06-05 23:48:05.633 Grain: S; Rows: 1
3 1969-05-17 11:25:34.630 Grain: S; Rows: 1

256
Stored Procedures

INSERT... EXEC

We can insert a recordset, created by a SP into a table, by using the INSERT… EXEC statement.
Collect the results of the EXECutions of usp_SalesAndLog into table SPExecResults in schema rep:

INSERT rep.SPExecResults (CustomerName, SalesValue)


EXEC rep.usp_SalesAndLog
@CustomerID = 4
, @Grain = 'S'
GO

EXECute again for @CustomerID = 2 and verify the content of table SPExecResults:

SELECT *
FROM rep.SPExecResults;
GO
CustomerName SalesValue DateTimeExecuted
John Smith 24.88 1967-05-17 11:43:45.060
Anna Laurier 147.36 1969-06-09 20:05:45.757

ALTER PROC

To edit a SP:
• Export the current SP definition: (In SSMS Object Explorer) Databases (DatabaseName)
Programmability Stored Procedures (right click) (StoredProcedureName)
Script Stored Procedure as ALTER To File ...
• Edit the:
• Parameters list (if we need to add an additional parameter or edit/delete an existing one)
• The SP definition
• Execute the ALTER PROC statement (F5) to save the changes in the DBE

Before the ALTER or DROP SP, we save a backup, exported as explained above (...CREATE To...)

Add parameter @CustomerID to usp_Customers_Select:

ALTER PROC dbo.usp_Customers_Select (@CustomerID INT) Add parameter


AS
BEGIN
SET NOCOUNT ON;

257
Stored Procedures

SELECT
FirstName
, LastName
, Email
, DateTimeRegistered
FROM dbo.Customers
WHERE CustomerID = @CustomerID; Use the parameter
END
GO

DROP PROC

We delete the Stored Procedure with the DROP PROC DDL statement.

Before the DROP PROC, check the dependencies:


• DB object(s) that depend on the SP
• DB objects in which the SP depends

EXEC sp_depends 'rep.usp_Sales';


GO

Delete the SP:


USE LearnSQLServerIntuitively;
GO

DROP PROC IF EXISTS dbo.usp_Customers_Select;


GO

SP Limitations
• Number of parameters per SP: 2,100

258
Extended Cheat Sheet

Naming Conventions

camelCase, PascalCase, [Special Characters], alllowercase, ALLUPPERCASE Case


NoDelimiter, Delimiter_Underscore, [Delimiter Space] Delimiter

Prefix
t, t_, tbl, tbl_ - Table
v, v_, vw, vw_ - View
fn, fn_, ufn, ufn_, udf, udf_, usf, utf - Function
sp, usp_ (Do not use sp_ - built in SP) - Stored Procedure

__Current, __Archive, _AddRow, _Report, _ETL Sufix


DateInvoiced, InvoiceDate, @DateStart, @StartDate Words Order

Hierarchy
[ServerName].[DatabaseName].[SchemaName].[ObjectName] SDSO
[DatabaseName].[SchemaName].[ObjectName] DSO
[SchemaName].[ObjectName] SO
[ObjectName] O

SSMS

Script Object
• In Object Explorer expand Databases
• Expand DatabaseName
• Expand Tables, Views or Programmability (Stored Procedures or Functions (Scalar-valued, Table-valued))
• Right click on the ObjectName
• Script (Table, View, Function, Stored Procedure) as
• Pick the type of the exported script (DDL or DML)
• New Query Window, File, Clipboard

Shortcuts
F5 Execute F3 Find next
Alt + Break Cancel running execution Shift + F1 (mark keyword) Search in Books Online
Ctrl + F5 Parse Ctrl + Tab Move to the next tab
Ctrl + R Toggle Results and Messages Ctrl + Shift + Tab (↑, ↓) Jump to tab
F4 Properties Ctrl + N New query tab
Ctrl + M Include actual execution plan Ctro + O Open file
Ctrl + L Estimated execution plan Ctrl + F4 Close the current tab
Alt + F1 (mark ObjectName) EXEC sp_help Alt + F4 Exit SSMS
Ctrl + F (Ctrl + H) Find and Replace Ctrl + S Save

259
Extended Cheat Sheet

Ctrl + Shift + S Save all Shift + Delete Delete entire row


Ctrl + X Cut Ctrl + Shift + U Uppercase
Ctrl + C Copy Ctrl + Shift + L Lowercase
Ctrl + V Paste Ctrl +Shift + D Results to grid
Ctrl + Z Undo Ctrl + T Results to text
Ctrl + Y Redo Ctrl + Shift + T Results to file

Comments

-- Single line comment


...T-SQL... -- Comment for this line

/*
Multiple lines comment - row 1
Multiple lines comment - row 2
*/

Collations

Server Level
SELECT *
FROM sys.fn_helpcollations();

SELECT SERVERPROPERTY('Collation') AS [Server Collation];

DB level
CREATE DATABASE DatabaseName
COLLATE SQL_Latin1_General_CP1_CI_AS;

SELECT name, collation_name


FROM sys.databases;

SELECT DATABASEPROPERTYEX('DatabaseName', 'Collation') AS [Database Collation];

Column level
SELECT [collation_name] AS ColumnCollation
FROM sys.columns
WHERE
OBJECT_NAME([object_id]) = 'TableName'
AND [name] = 'ColumnName';

260
Extended Cheat Sheet

Overwrite collation
SELECT ColumnName COLLATE SQL_Latin1_General_CP1_CI_AS
FROM TableName;

SELECT
T1.ColumnName2
, T2.ColumnName2
FROM
DatabaseName1.SchemaName1.TableName1 AS T1
JOIN DatabaseName2.SchemaName2.TableName2 AS T2
ON T1.ColumnName1 COLLATE SQL_Latin1_General_CP1_CI_AS = T2.ColumnName1;

DML Query: Object Catalog

Objects All system and user objects


SELECT * SELECT *
FROM DatabaseName.sys.objects; FROM DatabaseName.sys.all_objects; Object Types

Tables IF SQL inline table-valued function


IT Internal table TF SQL table-valued-function
S System base table
U Table (user-defined) Stored Procedures
P SQL Stored Procedure
Constraints PC Assembly (CLR) stored-procedure
C CHECK constraint RF Replication-filter-procedure
D DEFAULT (constraint or stand-alone) X Extended stored procedure
F FOREIGN KEY constraint
PK PRIMARY KEY constraint Triggers
UQ UNIQUE constraint TA Assembly (CLR) DML trigger
TR SQL DML trigger
Views
V View Other
PG Plan guide
Functions R Rule (old-style, stand-alone)
AF Aggregate function (CLR) SN Synonym
FN SQL scalar function SO Sequence object
FS Assembly (CLR) scalar-function SQ Service queue
FT Assembly (CLR) table-valued function TT Table type

Object Definition (View, Function, Stored Procedure)


EXEC sp_help 'SchemaName.ObjectName'; T, V, F, SP
SELECT OBJECT_DEFINITION(OBJECT_ID('SchemaName.ObjectName')); V, F, SP
EXEC sp_helptext 'SchemaName.ObjectName'; V, F, SP

261
Extended Cheat Sheet

Server, Database Properties

SELECT SERVERPROPERTY('PropertyName');

BuildClrVersion, Collation, CollationID, ComparisonStyle, ComputerNamePhysicalNetBIOS, Edition, EditionID, Engine-


Edition, FilestreamConfiguredLevel, FilestreamEffectiveLevel, FilestreamShareName, HadrManagerStatus, InstanceDe-
faultDataPath, InstanceDefaultLogPath, InstanceName, IsAdvancedAnalyticsInstalled, IsClustered, IsFullTextInstalled,
IsHadrEnabled, IsIntegratedSecurityOnly, IsLocalDB, IsPolybaseInstalled, IsSingleUser, IsXTPSupported, LCID, Licen-
seType, MachineName, NumLicenses, ProcessID, ProductBuild, ProductBuildType, ProductLevel, ProductMajorVersion, Pro-
ductMinorVersion, ProductUpdateLevel, ProductUpdateReference, ProductVersion, ResourceLastUpdateDateTime, ResourceV-
ersion, ServerName, SqlCharSet, SqlCharSetName, SqlSortOrder, SqlSortOrderName

SELECT DATABASEPROPERTY('DatabaseName', 'PropertyName');

IsAnsiNullDefault, IsAnsiNullsEnabled, IsAnsiWarningsEnabled, IsAutoClose, IsAutoCreateStatistics, IsAutoShrink,


IsAutoUpdateStatistics, IsBulkCopy, IsCloseCursorsOnCommitEnabled, IsDboOnly, IsDetached, IsEmergencyMode, IsFull-
textEnabled, IsInLoad, IsInRecovery, IsInStandBy, IsLocalCursorsDefault, IsNotRecovered, IsNullConcat, IsOffline,
IsParameterizationForced, IsQuotedIdentifiersEnabled, IsReadOnly, IsRecursiveTriggersEnabled, IsShutDown, IsSingleUs-
er, IsSuspect, IsTruncLog, Version

SELECT DATABASEPROPERTYEX('DatabaseName', 'PropertyName');

Collation, ComparisonStyle, Edition, IsAnsiNullDefault, IsAnsiNullsEnabled, IsAnsiPaddingEnabled, IsAnsiWarningsEn-


abled, IsArithmeticAbortEnabled, IsAutoClose, IsAutoCreateStatistics, IsAutoCreateStatisticsIncremental, IsAu-
toShrink, IsAutoUpdateStatistics, IsCloseCursorsOnCommitEnabled, IsFulltextEnabled, IsInStandBy, IsLocalCursors-
Default, IsMemoryOptimizedElevateToSnapshotEnabled, IsMergePublished, IsNullConcat, IsNumericRoundAbortEnabled,
IsParameterizationForced, IsPublished, IsQuotedIdentifiersEnabled, IsRecursiveTriggersEnabled, IsSubscribed, IsSyn-
cWithBackup, IsTornPageDetectionEnabled, IsXTPSupported, LCID, MaxSizeInBytes, Recovery, ServiceObjective, ServiceO-
bjectiveId, SQLSortOrder, Status, Updateability, UserAccess, Version

Databases

DML Query: Object Catalog


SELECT *
FROM sys.databases; Databases

SELECT
DB_NAME(database_id) AS [Database Name]
, name AS [File Name]
, physical_name AS [File Path]
, (size * 8) / 1024 AS [Size (MB)]
FROM sys.master_files;

Database files
SELECT *
FROM DatabaseName.sys.database_files;

262
Extended Cheat Sheet

DDL: DROP DATABASE


USE [master];
IF (DB_ID('DatabaseName') IS NOT NULL)
BEGIN
ALTER DATABASE DatabaseName SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE DatabaseName; Before SQL Server® 2016
END

DROP DATABASE IF EXISTS DatabaseName;

EXEC msdb.dbo.sp_delete_database_backuphistory @database_name = N'DatabaseName';

Delete the backup history


DDL: CREATE DATABASE
CREATE DATABASE DatabaseName;

DDL: ALTER DATABASE


ALTER DATABASE DatabaseName
MODIFY Name = DatabaseNameNew;

Schemas

DML Query: Object Catalog


SELECT *
FROM DatabaseName.sys.schemas;

DDL: DROP SCHEMA


IF (SCHEMA_ID('SchemaName') IS NOT NULL)
BEGIN DROP SCHEMA SchemaName; END Before SQL Server® 2016

DROP SCHEMA IF EXISTS SchemaName;

DDL: CREATE SCHEMA


CREATE SCHEMA SchemaName;

DDL: ALTER SCHEMA


ALTER SCHEMA DestinationSchemaName
TRANSFER SourceSchemaName.ObjectToTransferName;

Tables

263
Extended Cheat Sheet

DML Query: Object Catalog


Tables
SELECT *
FROM DatabaseName.sys.tables;
Change DatabaseName to tempdb for temp tables
SELECT *
FROM DatabaseName.sys.objects
WHERE [type] IN ('IT', 'S', 'U');

Tables, Columns, Data Types


SELECT
T.*
, '<-- Table | Column -->' AS [<-- Table | Column -->]
, C.*
, '<-- Column | Type -->' AS [<-- Column | Type -->]
, Ty.*
FROM
DatabaseName.sys.tables AS T
JOIN DatabaseName.sys.columns AS C
ON T.[object_id] = C.[object_id]
JOIN DatabaseName.sys.types AS Ty
ON C.user_type_id = Ty.user_type_id;

DDL: DROP TABLE (Real table)


IF (OBJECT_ID('DatabaseName.SchemaName.TableName', 'U') IS NOT NULL)
BEGIN DROP TABLE DatabaseName.SchemaName.TableName; END Before SQL Server® 2016

DROP TABLE IF EXISTS DatabaseName.SchemaName.TableName;

DDL: CREATE TABLE (Real table)


CREATE TABLE DatabaseName.SchemaName.TableName
(
ColumnName1 INT NOT NULL NOT NULL constraint, created
after the column definition
, ColumnName2 DATETIME
, CONSTRAINT PK__Customers PRIMARY KEY (ColumnName1) Create constraint(s)
); after the column list

DDL: DROP TABLE (Temp table)


IF (OBJECT_ID('tempdb.dbo.#TempTableName', 'U') IS NOT NULL)
BEGIN DROP TABLE #TempTableName; END Before SQL Server® 2016

DROP TABLE IF EXISTS #TempTableName; # - Local; ## - Global

264
Extended Cheat Sheet

DDL: CREATE TABLE (Temp table)


CREATE TABLE #TempTableName
(
ColumnName1 INT
, ColumnName2 DATETIME
);

DDL: Create CONSTRAINT(s) in CREATE TABLE (after the column list)


PRIMARY KEY (PK)
, CONSTRAINT PK__TableName -- Consraint name
PRIMARY KEY (ColumnName)
, CONSTRAINT PK__TableName_ColumnName1_ColumnName2
PRIMARY KEY (ColumnName1, ColumnName2) -- Composite PK

FOREIGN KEY (PK)


, CONSTRAINT FK__PKTableName_FKColumName
FOREIGN KEY (FKColumnName) REFERENCES PKTableName (PKColumName)
ON DELETE CASCADE
NO ACTION (default), CASCADE, SET NULL, SET DEFAULT
ON UPDATE CASCADE

DDL: Create CONSTRAINT(s) in CREATE TABLE (after the column definition)


DEFAULT (DF)
CONSTRAINT DF__TableName_ColumnName__DFDescription
DEFAULT (GETDATE())

CHECK (CK)
CONSTRAINT CK__TableName_ColumnName__CKDescription
CHECK (ColumnName > 0)

UNIQUE (UQ)
CONSTRAINT UQ__TableName_ColumnName
UNIQUE

NOT NULL (NN)


NOT NULL

DDL: ALTER TABLE


Columns
ADD – add columns(s)
ALTER TABLE DatabaseName.SchemaName.TableName
ADD

265
Extended Cheat Sheet

ColumnName1 INT
, ColumnName2 MONEY;

ALTER COLUMN – change the column definition


ALTER TABLE DatabaseName.SchemaName.TableName
ALTER COLUMN ColumnName MONEY NOT NULL; New data type, NN constraint

DROP COLUMN – delete column(s)


ALTER TABLE DatabaseName.SchemaName.TableName
DROP COLUMN Before SQL Server® 2016
ColumnName1
, ColumnName2;

ALTER TABLE DatabaseName.SchemaName.TableName


DROP COLUMN IF EXISTS ColumnName;

Constraints
ADD CONSTRAINT – add new constraint
ALTER TABLE DatabaseName.SchemaName.TableName
ADD CONSTRAINT DF__TableName_ColumnName__DFDescription
DEFAULT (GETDATE())
FOR ColumnName;

DROP CONSTRAINT – delete existing constraint


ALTER TABLE DatabaseName.SchemaName.TableName Before SQL Server® 2016
DROP ConstraintName;

ALTER TABLE DatabaseName.SchemaName.TableName


DROP CONSTRAINT IF EXISTS ConstraintName;

DDL, DML Modify: TRUNCATE TABLE


TRUNCATE TABLE DatabaseName.SchemaName.TableName;

DML Modify: INSERT


INSERT... SELECT
INSERT DatabaseName1.SchemaName1.TableName1
(
ColumnName1
, ColumnName2
)

266
Extended Cheat Sheet

SELECT
ColumnName1
, ColumnName2
FROM DatabaseName2.SchemaName2.TableName2;

INSERT... UNION
INSERT DatabaseName.SchemaName.TableName
(
ColumnName1
, ColumnName2
)
SELECT 1, 'Value1'
UNION ALL SELECT 2, 'Value2';

INSERT... VALUES
INSERT DatabaseName.SchemaName.TableName
(
ColumnName1
, ColumnName2
)
VALUES
(1, 'Value1')
, (2, 'Value2');

DML Modify: UPDATE


UPDATE DatabaseName.SchemaName.TableName
SET ColumnName1 = NewValue
WHERE ColumnName1 = FilterValue

UPDATE DatabaseName1.SchemaName1.TableName1
SET
ColumnName3 = A.Sum_Col3
, ColumnName4 = A.Avg_Col4
FROM
(
SELECT
ColumnName1 AS Col1
, ColumnName2 AS Col2
, SUM(ColumnName3) AS Sum_Col3
, AVG(ColumnName4) AS Avg_Col4
FROM DatabaseName2.SchemaName2.TableName2

267
Extended Cheat Sheet

GROUP BY
ColumnName1
, ColumnName2
) AS A
WHERE
ColumnName1 = A.Col1
AND ColumnName2 = A.Col2;

DML Modify: DELETE


DELETE DatabaseName.SchemaName.TableName
WHERE ColumnName = FilterValue;

DELETE T1
FROM
DatabaseName1.SchemaName1.TableName1 AS T1
JOIN DatabaseName2.SchemaName2.TableName2 AS T2
ON T1.ColumnName1 = T2.ColumnName1;
WHERE T2.ColumnName2 = FilterValue;

Views

DML Query: Object Catalog


SELECT *
FROM DatabaseName.sys.views;

SELECT *
FROM DatabaseName.sys.objects
WHERE [type] IN ('V');

DDL: DROP VIEW


IF (OBJECT_ID('SchemaName.vw_ViewName', 'V') IS NOT NULL)
BEGIN DROP VIEW SchemaName.vw_ViewName; END Before SQL Server® 2016

DROP VIEW IF EXISTS SchemaName.vw_ViewName;

DDL: CREATE VIEW


CREATE VIEW SchemaName.vw_ViewName SO naming convention
AS
SELECT
ColumnName1

268
Extended Cheat Sheet

, ColumnName2
FROM DatabaseName.SchemaName.TableName;

! No ORDER BY in Views

DDL: ALTER VIEW


1. Script the view in SSMS
2. Edit the definition
3. Change the keyword CREATE to ALTER
4. Execute the edited code (F5)

DML Query: SELECT


SELECT *
FROM DatabaseName.SchemaName.ViewName;

DML Query: Tables, Views, Table-valued functions

SELECT
SELECT 123;
SELECT (123 + 456);
SELECT '123 ' + 456;
SELECT '123 ' + CAST(456 AS VARCHAR);

FROM
SELECT
ColumnName1
, ColumnName2
FROM DatabaseName.SchemaName.TableName;

JOIN
SELECT
T1.ColumnName2
, T2.ColumnName2
FROM
DatabaseName1.SchemaName1.TableName1 AS T1
JOIN DatabaseName2.SchemaName2.TableName2 AS T2 -- JOIN, LEFT JOIN, RIGHT JOIN, FULL JOIN
ON T1.ColumnName1 = T2.ColumnName1;

WHERE

269
Extended Cheat Sheet

SELECT 'A'
WHERE @VariableName = FilterValue;

SELECT
T1.ColumnName2
, T2.ColumnName2
FROM
DatabaseName1.SchemaName1.TableName1 AS T1
JOIN DatabaseName2.SchemaName2.TableName2 AS T2
ON T1.ColumnName1 = T2.ColumnName1;
WHERE
T1.ColumnName3 = FilterValue1
AND T2.ColumnName4 <= FilterValue2
AND
Separate OR from AND in brackets
(
T1.ColumnName5 > (@ParameterValue1 + @ParameterValue2)
OR T1.ColumnName6 BETWEEN FilterValue4 AND FilterValue5
);

GROUP BY
SELECT
ColumnName1
, ColumnName2
, SUM(ColumnName3) AS Sum_ColumnName3
, MIN(ColumnName4) AS Min_ColumnName4
, MAX(ColumnName5) AS Max_ColumnName5
, AVG(ColumnName6) AS Avg_ColumnName6
, COUNT(ColumnName7) AS Count_ColumnName7
, COUNT(DISTINCT ColumnName8) AS CountDistinct_ColumnName8
FROM DatabaseName.SchemaName.TableName
GROUP BY
ColumnName1
, ColumnName2;

GROUPING SETS
...
GROUP BY GROUPING SETS
(
(ColumnName1, ColumnName2) Group 1
, (ColumnName3, ColumnName4, ColumnName5) Group 2
, () Grand Total

270
Extended Cheat Sheet

ROLLUP
...
GROUP BY ROLLUP
(
ColumnName1
, ColumnName2
)

CUBE
...
GROUP BY CUBE
(
ColumnName1
, ColumnName2
)

HAVING
SELECT
ColumnName1
, ColumnName2
, MIN(ColumnName3) AS Min_ColumnName3
FROM DatabaseName.SchemaName.TableName
GROUP BY
ColumnName1
, ColumnName2
HAVING SUM(ColumnName4) >= FilterValue;

ORDER BY
SELECT
ColumnName1
, ColumnName2 AS Col2
, ColumnName3
FROM DatabaseName.SchemaName.TableName;
ORDER BY
ColumnName1 Column name
, Col2 DESC Alias. Descending
, 3; Column index

TOP

271
Extended Cheat Sheet

SELECT TOP (10) * Rows


FROM DatabaseName.SchemaName.TableName;
ORDER BY ColumnName;

SELECT TOP (10) WITH TIES Rows


ColumnName1
, ColumnName2
FROM DatabaseName.SchemaName.TableName;
ORDER BY ColumnName3;

SELECT TOP 10 PERCENT *


FROM DatabaseName.SchemaName.TableName;
ORDER BY ColumnName DESC;

Statement Execution Order


Order of the clauses in statement:
1. SELECT
2. FROM… JOIN
3. WHERE
4. GROUP BY
5. HAVING
6. ORDER BY

Order of execution:
Step 1: FROM… JOIN - build the virtual recordset (VR) from one or multiple data sources (DS)
Step 2: WHERE - filter the VR
Step 3: GROUP BY... HAVING - group, aggregate and filter the grouped VR
Step 4: SELECT – builds the resulting VR and create aliases
Step 5: ORDER BY – order the resulting VR and use the aliases created in the SELECT statement

Subqueries

Non-Correlated Subquery
SELECT
ColumnName1
, (
SELECT MIN(ColumnName2) Single value
FROM DatabaseName1.SchemaName1.TableName1
) AS ColumnName2 In the SELECT clause
FROM DatabaseName2.SchemaName2.TableName2;

272
Extended Cheat Sheet

SELECT
T1.ColumnName2
, T2.Sum_ColumnName2
FROM
DatabaseName1.SchemaName1.TableName1 AS T1
JOIN
(
SELECT DISTINCT
ColumnName1
, SUM(T2.ColumnName2) AS Sum_ColumnName2
FROM DatabaseName2.SchemaName2.TableName2
) AS T2 In the FROM clause
ON T1.ColumnName1 = T2.ColumnName1;

SELECT
ColumnName1
, ColumnName2
FROM DatabaseName1.SchemaName1.TableName1
WHERE
ColumnName3 IN In the WHERE clause (IN)
(
SELECT TOP 10 ColumnName1
FROM DatabaseName2.SchemaName2.TableName2
ORDER BY ColumnName2 DESC
);

SELECT
ColumnName1
, ColumnName2
FROM DatabaseName1.SchemaName1.TableName1
WHERE
EXISTS In the WHERE clause (EXISTS)
(
SELECT 1 Not needed to select data
FROM DatabaseName2.SchemaName2.TableName2
WHERE ColumnName1 = @VariableValue
);

Correlated Subquery
SELECT
T2.ColumnName2

273
Extended Cheat Sheet

, (
SELECT MIN(T1.ColumnName2)
FROM DatabaseName1.SchemaName1.TableName1 AS T1
WHERE T1.Column1 = T2.Column1
) AS Min_ColumnName2 In the SELECT clause
FROM DatabaseName2.SchemaName2.TableName2 AS T2;

SELECT
T1.ColumnName2
, T1.ColumnName3
FROM DatabaseName1.SchemaName1.TableName1 AS T1
WHERE
T1.ColumnName3 IN In the WHERE clause (IN)
(
SELECT T2.ColumnName1
FROM DatabaseName2.SchemaName2.TableName2 AS T2
WHERE T2.ColumnName1 = T1.ColumnName1 Correlation
GROUP BY T2.ColumnName1
HAVING SUM(T2.ColumnName2) >= FilterValue
);

SELECT
T1.ColumnName2
, T1.ColumnName3
FROM DatabaseName1.SchemaName1.TableName1 AS T1
WHERE
EXISTS In the WHERE clause (EXISTS)
(
SELECT 1
FROM DatabaseName1.SchemaName1.TableName1 AS T2
WHERE T2.ColumnName1 = T1.ColumnName1 Correlation
GROUP BY T2.ColumnName1
HAVING SUM(T2.ColumnName2) >= FilterValue
);

Functions

Object Catalog
SELECT *
FROM DatabaseName.sys.objects
WHERE [type] IN ('AF', 'FN', 'FS', 'FT', 'IF', 'TF');

274
Extended Cheat Sheet

Scalar-valued
DDL: DROP FUNCTION
IF (OBJECT_ID('SchemaName.udf_FunctionName', 'FN') IS NOT NULL)
BEGIN DROP FUNCTION SchemaName.udf_FunctionName; END Before SQL Server® 2016

DROP FUNCTION IF EXISTS SchemaName.udf_FunctionName;

DDL: CREATE FUNCTION


CREATE FUNCTION SchemaName.udf_FunctionName SO naming convention
(
@ParameterName1 INT
, @ParameterName2 MONEY = NULL DEFAULT
)
RETURNS INT
AS
BEGIN
IF (@ParameterName2 IS NULL)
BEGIN
{Statements to handle the DEFAULT value}
...Statement 1;...
...Statement 2;...
END

DECLARE @ReturnVariableName INT;


SELECT @ReturnVariableName = {Statement(s) to populate @ReturnVariableName};
RETURN @ReturnVariableName;
END

DML Query: Call Scalar-valued function


SELECT DatabaseName.SchemaName.udf_FunctionName(ParameterName1Value, DEFAULT);

SELECT * In the SELECT clause, DEFAULT parameter


FROM
DatabaseName1.SchemaName1.TableName1 AS T1
JOIN DatabaseName2.SchemaName2.TableName2 AS T2
ON DatabaseName3.SchemaName3.udf_FunctionName(T1.ColumnName1) = T2.ColumnName1;

SELECT * In the FROM clause


FROM DatabaseName.SchemaName.TableName
WHERE ColumnName = DatabaseName.SchemaName.udf_FunctionName(@ParameterName1Value, DEFAULT);
In the WHERE clause

275
Extended Cheat Sheet

Table-valued (Inline)
DDL: DROP FUNCTION
IF (OBJECT_ID('SchemaName.udf_FunctionName', 'IF') IS NOT NULL)
BEGIN DROP FUNCTION SchemaName.udf_FunctionName; END Before SQL Server® 2016

DROP FUNCTION IF EXISTS SchemaName.udf_FunctionName;

DDL: CREATE FUNCTION


CREATE FUNCTION SchemaName.udf_FunctionName SO naming convention
(
ParameterName1 INT
, @ParameterName2 MONEY = NULL DEFAULT
)
RETURNS TABLE
AS
RETURN
(
...Statement 1;... Single statement that returns a recordset
);

Table-valued (Multi-statement)
DDL: DROP FUNCTION
IF (OBJECT_ID('SchemaName.udf_FunctionName', 'TF') IS NOT NULL)
BEGIN DROP FUNCTION SchemaName.udf_FunctionName; END Before SQL Server® 2016

DROP FUNCTION IF EXISTS SchemaName.udf_FunctionName;

DDL: CREATE FUNCTION


CREATE FUNCTION SchemaName.udf_FunctionName
(
@ParameterName1 INT
, @ParameterName2 MONEY = NULL DEFAULT
)
RETURNS @ReturnTableName TABLE
(
ColumnName1 INT
, ColumnName2 MONEY
)
AS
BEGIN
...Statement 1.... Multiple statements to populate @ReturnTableName

276
Extended Cheat Sheet

...Statement 2;...

INSERT @ReturnTableName Statement 3


SELECT
ColumnName1
, ColumnName2
FROM DatabaseName.SchemaName.ObjectName;

RETURN;
END

DML Query: Call Table-valued (Inline, Multi-statement) function


SELECT *
FROM DatabaseName.SchemaName.udf_FunctionName; In the FROM clause

DDL: ALTER FUNCTION


1. Script the function in SSMS
2. Edit the parameters and/or the definition
3. Change the keyword CREATE to ALTER
4. Execute the edited code (F5)

Stored Procedures

Object Catalog
SELECT *
FROM DatabaseName.sys.procedures;

SELECT *
FROM DatabaseName.sys.objects
WHERE type IN ('P', 'PC', 'RF', 'X');

DDL: DROP PROC


IF (OBJECT_ID('SchemaName.usp_StoredProcedureName', 'P') IS NOT NULL)
BEGIN DROP PROC SchemaName.usp_StoredProcedureName; END Before SQL Server® 2016

DROP PROCEDURE IF EXISTS SchemaName.usp_StoredProcedureName;

DDL: CREATE PROC


CREATE PROC SchemaName.usp_StoredProcedureName SO naming convention
(
@ParameterName1 INT
, @ParameterName2 DATE = NULL DEFAULT

277
Extended Cheat Sheet

)
AS
BEGIN
IF (@ParameterName2 IS NULL)
BEGIN
...Statement(s) to sets DEFAULT value to @ParameterName2...;
END

...Statement 2...;
...Statement 3...;
END

DDL: ALTER PROC


1. Script the stored procedure in SSMS
2. Edit the parameters and/or the definition
3. Change the keyword CREATE to ALTER
4. Execute the edited code (F5)

DML Query: EXEC PROC


EXEC PROC DatabaseName.SchemaName.usp_StoredProcedureName
@ParameterName1 = 123
@ParameterName2 has default value and can be omitted
, @ParameterName3 OUT = 'ABC';

OUTPUT patameter
DECLARE @ParameterName1_Out INT; Step 1: Create a variable to store the
value returned by the OUT parameter
EXEC PROC DatabaseName.SchemaName.usp_StoredProcedureName Step 2: EXECute and
@ParameterName1 = 123 populate the variable
, @ParameterName2
, @ParameterName3 = @ParameterName1_Out OUT;

INSERT DatabaseName.SchemaName.TableName Step 3: Manipulate the value


SELECT @ParameterName1_Out; returned by the OUT parameter

Conditional Execution

IF
IF (5 > 17) Condition (False)
BEGIN PRINT 'The condition is True'; END
ELSE
BEGIN PRINT 'The condition is False'; END

278
Extended Cheat Sheet

Nested IF
IF (5 < 17) Condition 1 (True)
BEGIN
Condition 2 (False)
IF (6 = 7)
BEGIN PRINT 'Condition 1 is True, Condition 2 is True'; END
ELSE
BEGIN PRINT 'Condition 1 is True, Condition 2 is False'; END
END
ELSE
BEGIN PRINT 'Condition 1 is False'; END

IIF
SELECT IIF(ColumnName1 > 5, 'Greater than 5', 'Not greater than five') AS IIF_Result
FROM DatabaseName.SchemaName.TableName;

CASE
SELECT
ColumnName1
, ColumnName2
, CASE ColumnName2
WHEN 5 THEN 'Five'
WHEN 6 THEN 'Six'
ELSE 'Not 5 and 6'
END AS Case_Result
FROM DatabaseName.SchemaName.TableName;

SELECT
ColumnName1
, ColumnName2
, CASE
WHEN (ColumnName2 BETWEEN 5 AND 6 AND ColumnName1 = 52) THEN 'BETWEEN 5 AND 6 (52)'
WHEN ColumnName2 BETWEEN 5 AND 6 THEN 'BETWEEN 5 AND 6'
ELSE 'Ignored'
END AS Case_Result
FROM DatabaseName.SchemaName.TableName;

Common Table Expressions (CTE)

WITH CTE_Name (Col1, Col2) AS


(
SELECT

279
Extended Cheat Sheet

ColumnName1
, ColumnName2
FROM DatabaseName1.SchemaName1.TableName1
)
SELECT
C.Col1
, C.Col2
, T1.ColumnName2
FROM
CTE AS C
JOIN DatabaseName2.SchemaName2.TableName2 AS T1
ON C.Col1 = T1.ColumnName1;

Non-recursive, multiple CTEs


WITH
CTE_Name1 (Col1, Col2, Col3) AS
(
SELECT
ColumnName1
, ColumnName2
, ColumnName3
FROM DatabaseName1.SchemaName1.TableName1
)
, CTE_Name2 AS
(
SELECT
ColumnName1
, ColumnName2
FROM DatabaseName2.SchemaName2.TableName2
)
SELECT INSERT, SELECT, UPDATE, DELETE
C1.Col2 AS ColumnAlias1
, C2.Col2 AS ColumnAlias2
FROM
CTE_Name1 AS C1
JOIN CTE_Name2 AS C2
ON C1.Col1 = C2.ColumnName1
WHERE C1.Col3 >= FilterValue;

280
Extended Cheat Sheet

Recursive CTE (Parent to child)


WITH CTE_Recursive AS
(
SELECT Anchor statement
0 AS [Level]
, ParentColumnName
, ChildColumnName
FROM DatabaseName.SchemaName.TableName
WHERE ParentColumnName IS NULL NULL is the top level

UNION ALL UNION ALL combines the anchor and the recursive statement(s)

SELECT Recursive statement


(C.[Level] + 1) AS [Level]
, P.ParentColumnName
, P.ChildColumnName
FROM DatabaseName.SchemaName.TableName AS P
JOIN CTE_Recursive AS C This CTE
ON P.ParentColumnName = C.ChildColumnName JOIN parent to child
)
SELECT *
FROM CTE_Recursive
ORDER BY
[Level]
, ParentColumnName
OPTION (MAXRECURSION 3); Limit the recursions

Recursive CTE (Child to parent)


WITH CTE_Recursive AS
(
SELECT
0 AS [Level]
, ChildColumnName
, ParentColumnName
FROM DatabaseName.SchemaName.TableName
WHERE ChildColumnName = FilterValue

UNION ALL

SELECT
(C.[Level] + 1) AS [Level]

281
Extended Cheat Sheet

, P.ChildColumnName
, P.ParentColumnName
FROM DatabaseName.SchemaName.TableName AS P
JOIN CTE_Recursive AS C
ON C.ParentColumnName = P.ChildColumnName
)
SELECT *
FROM CTE_Recursive;

! The statement before WITH has to end with semicolon (;)

Variables

DECLARE @VariableName1 INT = 123; Create and assign a value to the variable
SELECT *
FROM DatabaseName.SchemaName.TableName
WHERE ColumnName = @VariableName1; Use the variable

DECLARE @VariableName2 INT Create variable


SELECT @VariableName2 = MAX(ColumnName) Assign a value to the variable
FROM DatabaseName.SchemaName.TableName;
SELECT @VariableName2; Use the variable

Table variable
DECLARE @TableVariable TABLE Create table variable
(
ColumnName1 INT
, ColumnName2 DATETIME2
);

INSERT @TableVariable (ColumnName1, ColumnName2) Assign a value to the table variable


SELECT ColumnName1, ColumnName2
FROM DatabaseName1.SchemaName1.TableName1
WHERE ColumnName3 >= FilterValue;

SELECT *
FROM Use the table variable
DatabaseName2.SchemaName2.TableName2 AS T1
JOIN @TableVariable AS TV
ON T1.ColumnName1 = TV.ColumnName1
AND T1.ColumnName2 = TV.ColumnName2;

282
Extended Cheat Sheet

Loops (WHILE)

Count to 5 BREAK, CONTINUE


DECLARE @Counter INT = 1 DECLARE @Counter INT = 1
WHILE (@Counter <= 5) WHILE (@Counter <= 10)
BEGIN BEGIN
PRINT @Counter; PRINT @Counter;
SET @Counter += 1; SET @Counter += 1;
END
IF (@Counter <= 6)
BEGIN CONTINUE; END Go to the statement after BEGIN
ELSE
BEGIN BREAK; END Go to the statement after END
END

PRINT 'Finished!';
Data Types

Numerics Binary
Exact BINARY
TINYINT, SMALLINT, INT, BIGINT VARBINARY
NUMERIC, DECIMAL (DEC) IMAGE - Obsolete. Replaced with BINARY(MAX)
MONEY, SMALLMONEY Date and time
BIT SMALLDATETIME, DATETIME, DATETIME2
Approximate DATE
FLOAT, REAL TIME
DATETIMEOFFSET
Strings
Character Non-Unicode Other
CHAR UNIQUEIDENTIFIER
VARCHAR TIMESTAMP - Obsolete. Replaced with ROWVERSION.
TEXT - Obsolete. Replaced with VARCHAR(MAX) XML
Character Unicode SQL_VARIANT
NCHAR TABLE
NVARCHAR HIYERERARCHYID
NTEXT - Obsolete. Replaced with NVARCHAR(- CURSOR
MAX) Spacial
GEOGRAPHY
GEOMETRY

Conversions

283
Extended Cheat Sheet

Convert one data type to another


SELECT
ColumnName
, CAST(ColumnName AS VARCHAR) numeric to character
FROM DatabaseName.SchemaName.TableName;

SELECT
ColumnName
, CONVERT(DATETIME ColumnName)
FROM DatabaseName.SchemaName.TableName;

SELECT PARSE('10/02/1968' AS DATETIME USING 'en-US') AS PARSE_enUS;


SELECT PARSE('10/02/1968' AS DATETIME USING 'ru-RU') AS PARSE_ruRU;

TRY_ converts from one data type to another and return NULL instead of an error message
SELECT TRY_CAST('ABC' AS INT);
SELECT TRY_CONVERT(INT, 'ABC');
SELECT TRY_PARSE('10/48/1968' AS DATETIME USING 'en-US') AS [TRY_PARSE];

SELECT STR(123.789);

Operators

Arithmetic Operators
+ Addition
- Subtraction
* Multiplication
/ Division
% Modulo

Compound Operators
+= Add the value of the right operand to the left operand
-= Subtracts the value of the right operand from the left operand
*= Multiplies the operands and sets the result to the left operand
/= Divides the operands and sets the result to the left operand
%= Sets the result of the modulo to the left operand

Assignment Operator
Equal sign (=)

Comparison Operators
= Equals to

284
Extended Cheat Sheet

<> or != Not equal to *


> Greater than
< Less than
>= Greater than or equal to
<= Less than or equal to
!< Not less than *
!> Not greater than *

* ! not ISO standard

Logical Operators
AND True if both operands return True
OR True if one of the operands return True
IN True if the operand is in the list (the other operand)
LIKE True if the operand matches a pattern (the other operand)
BETWEEN True if the operand is in the BETWEEN range
EXISTS True if the query returns one or more rows
NOT Reverse the meaning of the operator

Set Operators
UNION - combines two recordsets into one
SELECT ColumnName1, ColumnName2, ColumnName3
FROM DatabaseName1.SchemaName1.TableName1
UNION ALL -- or UNION to return duplicate rows
SELECT ColumnName1, ColumnName2, ColumnName3
FROM DatabaseName2.SchemaName2.TableName2

EXCEPT - remove the matching rows from the top recordset
SELECT ColumnName1, ColumnName2, ColumnName3
FROM DatabaseName1.SchemaName1.TableName1
EXCEPT
SELECT ColumnName1, ColumnName2, ColumnName3
FROM DatabaseName2.SchemaName2.TableName2

INTERSECT - return only the matching rows in the both recordsets
SELECT ColumnName1, ColumnName2, ColumnName3
FROM DatabaseName1.SchemaName1.TableName1
INTERSECT
SELECT ColumnName1, ColumnName2, ColumnName3
FROM DatabaseName2.SchemaName2.TableName2

285
Extended Cheat Sheet

String Concatenation Operator


+ Concatenate the left and the right operands
+= Appends value to the left operand

Wildcards in LIKE operator


% Any character(s)
[ ] Range of matching character(s)
[^] Range of not matching character(s)
_ Any matching single character

NULL and 3VL (Three-valued Logic)

SELECT ISNULL(ColumnName, ValueToReplaceNULL)


FROM DatabaseName.SchemaName.TableName;

SELECT NULLIF(ColumnName, ValueToBeNULL)


FROM DatabaseName.SchemaName.TableName;

SELECT COALESCE(ColumnName1, ColumnName2, ColumnName3)


FROM DatabaseName.SchemaName.TableName;

3VL from
...
WHERE ColumnName1 NOT LIKE '%StringToSearch%';

3VL to
...
WHERE
(
ColumnName1 NOT LIKE '%StringToSearch%'
OR ColumnName1 IS NULL
);

Pivoting

PIVOT Columns
SELECT Rows Values
P.ColumnNameRows1 AS Row1, P.ColumnNameRows2 AS Row2
, P.[ValueInColumns1] AS Col1, P.[ValueInColumns2] AS Col2
FROM

286
Extended Cheat Sheet

(
SELECT
ColumnNameRows1, ColumnNameRows2
, ColumnNameColumns
, ColumnNameValues
FROM DatabaseName.SchemaName.TableName
) AS S Subquery for Rows, Columns and Values only
PIVOT
(
SUM(ColumnNameValues)
FOR ColumnNameColumns IN ([ValueInColumns1], [ValueInColumns2])
) AS P
ORDER BY P.ColumnNameRows1, P.ColumnNameRows2;

UNPIVOT
SELECT
U.ColumnNameRows1, U.ColumnNameRows2
, U.ColumnNameColumns
, U.ColumnNameValues
FROM
(
SELECT
ColumnNameRows1, ColumnNameRows2
, ValueInColumns1, ValueInColumns2
FROM DatabaseName.SchemaName.TableName
) AS P Subquery to select only data to be UNPIVOTed
UNPIVOT
(
ColumnNameValues
FOR ColumnNameColumns IN ([ValueInColumns1], [ValueInColumns2])
) AS U
ORDER BY
U.ColumnNameRows1, U.ColumnNameRows2
, U.ColumnNameColumns;

287
t

Learn Microsoft® SQL Server® Intuitively


Transact-SQL: The Solid Basics
Learn Microsoft® SQL Server® Intuitively is a complete, hands-on and practical guide
to everything you will ever need to know about the basics of Microsoft® SQL Server® rela-
tional database management systems.

From the basics, ideal for beginners with their own PC, to more complex ideas for big
business, Learn Microsoft® SQL Server® Intuitively teaches you one step at a time, in an
easy-to-follow and simple format, written in a language that you will understand.

The wide ranging benefits and features of this book include:


• A hands on approach
• Fast and easy
• Practical problem solving
• Saves you time and money
• Self-educating
• Intuitive and visual
• Tutorials and in-depth explanations
• And much more…

Learn Microsoft® SQL Server® Intuitively is the only book you will ever need to help
you navigate these complex processes. Due to its unique visual approach you will find it
easier than ever to completely understand the database programming and how it can work
better for you.

Get your copy of Learn Microsoft® SQL Server® Intuitively now and make the most out
of your home PC or business.

Peter Lalovsky lives and works in Montreal, Canada. He started his career in the IT field 17 years
ago as a prepress designer and an IT specialist.

In the last decade, guided by his passion towards Microsoft® SQL Server®, he worked on mul-
tiple projects as a T-SQL, .NET and BI developer (SSRS, SSIS, SSAS). Nowadays he works as a
database consultant at his company zPL Concept.

His interest in all spheres of IT has led Peter to become a Microsoft® Certified Professional and
prompted him to write the book – Learn Microsoft® SQL Server® Intuitively – which is a hands on and practical
manual, in which he hopes to help others with his in-depth knowledge of the subject.

He saves and shares his practical tips and advice ISBN 9780995245105

on his personal webpage: Peter.Lalovsky.com.

Source code online: LearnIntuitively.Lalovsky.com/SQLServer


Audience: Beginner/Intermediate 9 780995 245105

You might also like