Sie sind auf Seite 1von 28

08/09/2016 QueryingSQLServer2012:PartIICodeProject

Querying SQL Server 2012: Part II


Sander Rossel,24 Jan 2014 CPOL Rate this:

4.9042votes

The second of two; everything you need to create the most amazing queries!

1. Table of Contents
Part I

1. Table of Contents
2. Introduction
3. Installing the sample database
4. Defining a query
5. Our first query; the SELECT statement

5.1. Aliasing

6. Eliminating duplicates; DISTINCT


7. Filtering data; the WHERE clause

7.1. The NOT keyword


7.2. Combining predicates
7.3. Filtering strings
7.4. Filtering dates
7.5. The IN and BETWEEN keywords

8. Sorting data; ORDER BY


9. Further limiting results; TOP and OFFSETFETCH

9.1. TOP
9.2. OFFSETFETCH

10. Aggregating data; GROUP BY and HAVING


11. Selecting from multiple tables; using JOINS

11.1. CROSS JOIN


11.2. INNER JOIN
11.3. OUTER JOIN
11.4. Self JOIN
11.5. Multiple JOINS

12. Multiple groups; GROUPING SETS

12.1. GROUPING SETS


12.2. CUBE
12.3. ROLLUP
12.4. GROUPING
12.5. GROUPING_ID

13. Windowing functions; the OVER clause

13.1. Aggregate functions


13.2. Framing
13.3. Ranking functions
13.4. Offset functions

14. That's not it yet...

Part II

1. Table of Contents
2. Welcome back!
3. Queries in your queries; Subqueries

3.1.Writing the same queries differently


3.2.More filtering options; IN, ANY, SOME, ALL and EXISTS

http://www.codeproject.com/Articles/692269/QueryingSQLServerPartII 1/28
08/09/2016 QueryingSQLServer2012:PartIICodeProject
3.2.1.IN
3.2.2.ANY and SOME
3.2.3.ALL
3.2.4.EXISTS

4. Querying from subqueries; Derived tables


5. Common Table Expressions a.k.a. CTEs
6. Set operators; UNION, INTERSECT and EXCEPT

6.1.Combining sets; UNION and UNION ALL


6.2.Recursion with CTEs and UNION ALL
6.3.INTERSECT
6.4.EXCEPT

7. Pushing over tables; PIVOT and UNPIVOT

7.1.Pivoting
7.2.Unpivoting

8. More uses for Table Expressions; APPLY

8.1.CROSS APPLY
8.2.OUTER APPLY

9. Other aspects of querying

9.1.Converting types; CAST and CONVERT, PARSE and FORMAT

9.1.1CAST and CONVERT


9.1.2PARSE
9.1.3FORMAT

9.2.VARCHAR functions
9.3.DATETIME functions
9.4.CASE and IIF

9.4.1CASE
9.4.2IIF

9.5.COALESCE, ISNULL and NULLIF

10. Conclusions

2. Welcome back!
As you can read in the title of this article this is actually the second and last part of an article dedicated to querying a Microsoft SQL Server database. I
strongly recommend that you read the first article,Querying SQL Server 2012 Part I, if you have not done so already.
The first article focused on building up your query. It started of simple with theSELECTstatement and built up difficulty by adding filtering, grouping and
windowing functions to your queries.
This second part of the article focuses on combining multipleSELECTstatements and still returning a single result set. In addition we will see how to use
functions and manipulate data so we can combine values from our database and apply functions to values.

3. Queries in your queries; Subqueries


So as I said this article will focus on using multipleSELECTstatements within a query. The easiest and most common way to do this is by using
asubquery. A subquery is a query within a query that returns a result that is expected at the place where the subquery is placed. Some of the windowing
functions we used in part I of this article return results that could have been returned by using a subquery. Say we want to query the orders table and for
each order we want to show the most expensive order and least expensive order as well. To get this result we could use the following query, as we have
seen in part I.

HideCopy Code
SELECT
SalesOrderID,
SalesOrderNumber,
CustomerID,
SubTotal,
MIN(SubTotal)OVER()ASLeastExpensive,
MAX(SubTotal)OVER()ASMostExpensive
FROMSales.SalesOrderHeader
ORDERBYSubTotal

We could get this same result using subqueries. The subquery returns a result for each row. So let me show you the above query rewritten with
subqueries.

http://www.codeproject.com/Articles/692269/QueryingSQLServerPartII 2/28
08/09/2016 QueryingSQLServer2012:PartIICodeProject
HideCopy Code
SELECT
SalesOrderID,
SalesOrderNumber,
CustomerID,
SubTotal,
(SELECTMIN(SubTotal)
FROMSales.SalesOrderHeader)ASLeastExpensive,
(SELECTMAX(SubTotal)
FROMSales.SalesOrderHeader)ASMostExpensive
FROMSales.SalesOrderHeader
ORDERBYSubTotal

That is a lot more code to get the same result you might think. Besides, this method is more error prone than using a windowing function. What if we
would forget our aggregate function? The subquery would returnallsubtotals from the order table, but we cannot put more than one value in, well, a
single value! See what happens for yourself. The error message is pretty clear I think.

So if subquerying requires more code and is more error prone to boot then why would we use a subquery? Well, subqueries can be a lot more complex
than I have just shown you. You can query anything from anywhere as long as it returns a result set that is expected at the place where you use it. In
aSELECTstatement that would be a single value. You could query from thePersontable and still include the most and least expensive orders.

HideCopy Code
SELECT
BusinessEntityID,
Title,
FirstName,
MiddleName,
LastName,
(SELECTMIN(SubTotal)
FROMSales.SalesOrderHeader)ASLeastExpensive,
(SELECTMAX(SubTotal)
FROMSales.SalesOrderHeader)ASMostExpensive
FROMPerson.Person
ORDERBYFirstName,MiddleName,LastName

Now that is already pretty neat, but wait, there is more. The subqueries I have just shown you areselfcontained subqueries. That means the subquery can
be executed without the outer query and still return a result. These can be useful, but a lot of the times you might want to select something based on a
value of the outer query. In this case we can usecorrelated subqueries. That simply means that we use a value from our outer query in our subquery. Let us
use the query above, but now show the least and most expensive orders for that particular person. Remember that a person does not make an order. A
customer makes an order and a customer is linked to a person. We are going to need a join for this... Or another subquery!

HideCopy Code
SELECT
BusinessEntityID,
Title,
FirstName,
MiddleName,
LastName,
(SELECTMIN(SubTotal)
FROMSales.SalesOrderHeaderASs
WHEREs.CustomerID=(SELECT
c.CustomerID
FROMSales.CustomerASc
WHEREc.PersonID=p.BusinessEntityID)
)ASLeastExpensive,
(SELECTMAX(SubTotal)
FROMSales.SalesOrderHeaderASs
WHEREs.CustomerID=(SELECT
c.CustomerID
FROMSales.CustomerASc
WHEREc.PersonID=p.BusinessEntityID)
)ASMostExpensive

http://www.codeproject.com/Articles/692269/QueryingSQLServerPartII 3/28
08/09/2016 QueryingSQLServer2012:PartIICodeProject
FROMPerson.PersonASp
ORDERBYFirstName,MiddleName,LastName

Allright, now that is looking complex! It really is not. Both subqueries are the same except for theMINandMAXfunctions, so focus on one subquery.
Notice that I have used column aliases becauseCustomerIDis a column in bothSalesOrderHeaderandCustomer. So let us isolate the subquery and
take a closer look at it. Note that you cannot run it, because it references a column from the outer query.

HideCopy Code
(SELECTMAX(SubTotal)
FROMSales.SalesOrderHeaderASs
WHEREs.CustomerID=(SELECT
c.CustomerID
FROMSales.CustomerASc
WHEREc.PersonID=p.BusinessEntityID)
)ASMostExpensive

So that is it. Notice that you can actually use a subquery in aWHEREclause? Now that is pretty cool! Remember that windowing functions cannot be used
in any clause exceptORDERBY. So in theWHEREclause we select theCustomerIDfrom theCustomertable wherePersonIDis equal to
theBusinessEntityIDfrom the outer query. So far so good, right? TheCustomerIDreturned by the query is used to select the most or least
expensive order from that customer from theSalesOrderHeadertable. Notice that when the subquery does not return a value the row is not discarded,
but aNULLis shown instead.

3.1 Writing the same queries differently

Here is a nice challenge, if you are up for it. Rewrite the last query using no subselects. There are actually multiple ways to tackle this. I usedJOINSinstead
of subqueries. There are a lot of other methods as well, but they are not covered in this article or not covered yet. Here are two possible solutions, one
using aGROUPBYclause and the same solutions, but using windowing andDISTINCTinstead ofGROUPBY.

HideShrink Copy Code


SELECT
BusinessEntityID,
Title,
FirstName,
MiddleName,
LastName,
MIN(s.SubTotal)ASLeastExpensive,
MAX(s.SubTotal)ASMostExpensive
FROMPerson.PersonASp
LEFTJOINSales.CustomerAScONc.PersonID=p.BusinessEntityID
LEFTJOINSales.SalesOrderHeaderASsONs.CustomerID=c.CustomerID
GROUPBY
BusinessEntityID,
Title,
FirstName,
MiddleName,
LastName
ORDERBYFirstName,MiddleName,LastName

SELECTDISTINCT
BusinessEntityID,
Title,
FirstName,
MiddleName,
LastName,
MIN(s.SubTotal)OVER(PARTITIONBYs.CustomerID)ASLeastExpensive,
MAX(s.SubTotal)OVER(PARTITIONBYs.CustomerID)ASMostExpensive
FROMPerson.PersonASp
LEFTJOINSales.CustomerAScONc.PersonID=p.BusinessEntityID
LEFTJOINSales.SalesOrderHeaderASsONs.CustomerID=c.CustomerID
ORDERBYFirstName,MiddleName,LastName

So if all three queries return the same result which one should you use!? query optimization is outside the scope of this article, but I do want to mention
this. You can see IO statistics by running the following statement in your query window.

HideCopy Code
SETSTATISTICSIOON

Next you can turn on the option 'Include actual query plan' under the 'Query' menu option.

http://www.codeproject.com/Articles/692269/QueryingSQLServerPartII 4/28
08/09/2016 QueryingSQLServer2012:PartIICodeProject

Now run your query again. In the messages window you will see detailed IO information. As a rule of thumb goes the lesser reads the better. You will now
also find a third window, the 'Execution plan', which shows exactly what steps SQL Server had to perform to get to your result. Read it from right to left.
Less steps is not always better, some steps require more work from SQL Server than others.

So which query did actually perform best? The one with theGROUPBY. And which was worst by far? The one including the subqueries.
That does not automatically mean subqueries are slow or evil. It simply means there are more queries to get to the same result and what is best in one
scenario is not always the best in another. In addition, sometimes subqueries can be used where other functions cannot be used.

3.2 More filtering options; IN, ANY, SOME, ALL and EXISTS

3.2.1 IN

I have already shown a subquery in aWHEREclause. You can use it to check if values are greater than, less than, like another value etc. The following query
shows a somewhat lame example of using a subquery and theLIKEoperator. It selects all people whos first name begins with a 'Z'.

HideCopy Code
SELECT*
FROMPerson.Person
WHEREFirstNameLIKE(SELECT'Z%')

Now remember theINfunction? It can actually take a range of values as input. We can easily combine that with a subquery. We just have to make sure
our subquery returns a single column and whatever number of rows. So far we have only seen subqueries that return a single value. Let us say we want to
find all people who are also a customer. If aPersonis aCustomerwe know that there is aCustomerwith thePersonIDof thatPerson. So the next
query will easily solve this.

HideCopy Code
SELECT*
FROMPerson.PersonASp
WHEREp.BusinessEntityIDIN(SELECTPersonID
FROMSales.Customer)

Which is equivalent to:

HideCopy Code
SELECT*
FROMPerson.PersonASp
WHEREp.BusinessEntityID=xORp.BusinessEntityID=yORp.BusinessEntityID=z...etc.

And if we want to know which people are not a customer we use the keywordNOT. In this case we also have to check forNULL.

HideCopy Code
SELECT*
FROMPerson.PersonASp
WHERENOTp.BusinessEntityIDIN(SELECTPersonID
FROMSales.Customer
WHEREPersonIDISNOTNULL)

http://www.codeproject.com/Articles/692269/QueryingSQLServerPartII 5/28
08/09/2016 QueryingSQLServer2012:PartIICodeProject
Remember thatNULLcan also be explained asUNKNOWN, so if a singleNULLis returned from your subquery SQL Server returns no rows because it does
not know if a value is or is not contained in the result. After all, it might be thatNULLvalue. The previous query is equivalent to the following.

HideCopy Code
SELECT*
FROMPerson.Person
WHEREBusinessEntityID<>xANDBusinessEntityID<>yANDBusinessEntityID<>z...etc.

3.2.2 ANY and SOME

TheANYoperator works much like theINoperator, except in that you can use the>,<,>=,<=,=and<>operators to compare values.ANYreturns true if at
least one value returned by the subquery makes the predicate true. So the following query returns all persons except that withBusinessEntityID1,
because1>1returnsFALSE.

HideCopy Code
SELECT*
FROMPerson.Person
WHEREBusinessEntityID>ANY(SELECT1)

In queries such as theseANYmay not be that useful, even when you specifiy a more meaningful subquery. In some casesANYcan be useful though. For
example if you want to know if all orders from a specific date have at least a certain status. The following query illustrates this.

HideCopy Code
DECLARE@OrderDateASDATETIME='20050517'
DECLARE@StatusASTINYINT=4
IF@Status>ANY(SELECTStatus
FROMPurchasing.PurchaseOrderHeader
WHEREOrderDate=@OrderDate)
PRINT'Notallordershavethespecifiedstatus!'
ELSE
PRINT'Allordershavethespecifiedstatus.'

If@Statusis bigger than any result from the subquery then the result isTRUEbecause there are some/any orders that are not at least status 4. The query
prints"Notallordershavethespecifiedstatus!".

Instead ofANYyou can useSOME, which has the same meaning.

HideCopy Code
DECLARE@OrderDateASDATETIME='20050517'
DECLARE@StatusASTINYINT=4
IF@Status>SOME(SELECTStatus
FROMPurchasing.PurchaseOrderHeader
WHEREOrderDate=@OrderDate)
PRINT'Notallordershavethespecifiedstatus!'
ELSE
PRINT'Allordershavethespecifiedstatus.'

3.2.3 ALL

UnlikeANY,ALLlooks at all results returned by a subquery and only returnsTRUEif the comparison with all results makes the predicate true. The previous
query could have been rewritten as follows.

HideCopy Code
DECLARE@OrderDateASDATETIME='20050517'
DECLARE@StatusASTINYINT=4
IF@Status<ALL(SELECTStatus
FROMPurchasing.PurchaseOrderHeader
WHEREOrderDate=@OrderDate)
PRINT'Allordershavethespecifiedstatus.'
ELSE
PRINT'Notallordershavethespecifiedstatus!'

3.2.4 EXISTS

EXISTScan be used likeANYandALL, but returns true only if at least one record was returned by the subquery. It is pretty useful and you will probably
use this more often. Let us say we want all customers that have placed at least one order.

HideCopy Code
SELECT*
FROMSales.CustomerASc
WHEREEXISTS(SELECT*
FROMSales.SalesOrderHeaderASs
WHEREs.CustomerID=c.CustomerID)

Now we might be more interested in customers that have not placed any orders.

http://www.codeproject.com/Articles/692269/QueryingSQLServerPartII 6/28
08/09/2016 QueryingSQLServer2012:PartIICodeProject
HideCopy Code
SELECT*
FROMSales.CustomerASc
WHERENOTEXISTS(SELECT*
FROMSales.SalesOrderHeaderASs
WHEREs.CustomerID=c.CustomerID)

Notice that theEXISTSfunctions only returnsTRUEorFALSEand not any columns. For that reason it does not matter what you put in
yourSELECTstatement. In fact, this is the only place where you can useSELECT*without worrying about it!

4. Querying from subqueries; Derived tables


In the previous chapter we have seen subqueries. We have seen them in ourSELECTstatement, in ourWHEREstatement and passed as parameters to
functions. You can put subqueries almost anywhere you want, includingHAVINGandORDERBYclauses. This also includes theFROMclause.

When we use subqueries in ourFROMclause the result is called aderived table. A derived table is a named table expression and, like a subquery, is only
visible to its outer query. It differs from subqueries in that they return a complete table result. This can actually solve some problems we had earlier!
Remember that we could not use windowing functions anywhere except in theORDERBYclause? What if we selected the values first and then filtered
them in an outer query? This is perfectly valid!

HideCopy Code
SELECT*
FROM(SELECT
SalesOrderID,
SalesOrderNumber,
CustomerID,
AVG(SubTotal)OVER(PARTITIONBYCustomerID)ASAvgSubTotal
FROMSales.SalesOrderHeader)ASd
WHEREAvgSubTotal>100
ORDERBYAvgSubTotal,CustomerID,SalesOrderNumber

There are a few things you will have to keep in mind. The result of a subquery needs to berelational.That means every column it returns must have a
name.AVG(SubTotal)... would not have a name, so we MUST alias it. We must also alias the derived table itself.

Also, a relation is not ordered, so we cannot specify anORDERBYin the derived table. There is an exception to this last rule. Whenever you specify
aTOPorOFFSETFETCHclause in your derived table you can useORDERBY. In this case the query will not return an ordered result, but it returns the top
x rows that should be in the result IF the result was ordered. So theORDERBYis used as a filter rather than ordering. The next query illustrates this.

HideCopy Code
SELECT*
FROM(SELECTTOP100PERCENT
SalesOrderID,
SalesOrderNumber,
CustomerID,
Freight
FROMSales.SalesOrderHeader
ORDERBYFreight)ASd

And here is the result.

Notice that I am making a select on the entire table, because it is kind of hard to NOT have SQL Server return sorted data. In every other case it would
have to sort the data first before it can check which rows should and should not be returned. And when the data is sorted SQL Server does not unsort
them before returning the result. In this case a sort is not necessary because the entire table needs to be returned anyway. The bottom line here is that
theORDERBYdid not actually order our result set.

The next query illustrates that the result MAY be ordered though.

HideCopy Code

http://www.codeproject.com/Articles/692269/QueryingSQLServerPartII 7/28
08/09/2016 QueryingSQLServer2012:PartIICodeProject

SELECT*
FROM(SELECTTOP10000
SalesOrderID,
SalesOrderNumber,
CustomerID
FROMSales.SalesOrderHeader
ORDERBYCustomerID)ASd

And here are the results.

It may look like these results are ordered, but remember that SQL Server cannot guarantee ordering. In this case SQL Server needed to sort the rows
onCustomerIDbefore returning the rows. Just remember thatORDERBYin any relational result is used for filtering, not ordering.

One thing you can not do using derived tables is joining with the derived table. You might want to do something like I did in my example on selfJOINs in
part I of the article. This is not valid however.

HideCopy Code
SELECT*
FROM(SELECT
SalesOrderID,
SalesOrderNumber,
CustomerID,
AVG(SubTotal)OVER(PARTITIONBYCustomerID)ASAvgSubTotal
FROMSales.SalesOrderHeader)ASd
JOINdASd2ONd2.CusomerID=d.CustomerID+1
WHEREAvgSubTotal>100
ORDERBYAvgSubTotal,CustomerID,SalesOrderNumber

This is NOT valid syntax and the only way you will be able to join on your derived table is by duplicating your derived table in yourJOINclause!

HideCopy Code
SELECT*
FROM(SELECT
SalesOrderID,
SalesOrderNumber,
CustomerID,
AVG(SubTotal)OVER(PARTITIONBYCustomerID)ASAvgSubTotal
FROMSales.SalesOrderHeader)ASd
JOIN(SELECT
SalesOrderID,
SalesOrderNumber,
CustomerID,
AVG(SubTotal)OVER(PARTITIONBYCustomerID)ASAvgSubTotal
FROMSales.SalesOrderHeader)ASd2ONd2.CustomerID=d.CustomerID+1
WHEREd.AvgSubTotal>100
ORDERBYd.AvgSubTotal,d.CustomerID,d.SalesOrderNumber

Yikes! This also exposes another problem with derived tables and subqueries in general. They can make your query big, complex and difficult to read.

5. Common Table Expressions a.k.a. CTEs


http://www.codeproject.com/Articles/692269/QueryingSQLServerPartII 8/28
08/09/2016 QueryingSQLServer2012:PartIICodeProject
Like derived tables aCommon Table Expression, also commonly abbreviated asCTE, is a named table expression that is only visible to the query that
defines it. TheCTEmakes up for some of the shortcomings of the derived table though. For starters, aCTEis defined at the beginning of your query, or
actually at the top. This makes it more readable than a derived table. You first name and define yourCTEand then work with it in a following query. The
first example I showed using a derived table can be rewritten using aCTE.

HideCopy Code
WITHCTE
AS
(
SELECT
SalesOrderID,
SalesOrderNumber,
CustomerID,
AVG(SubTotal)OVER(PARTITIONBYCustomerID)ASAvgSubTotal
FROMSales.SalesOrderHeader
)
SELECT*
FROMCTE
WHEREAvgSubTotal>100
ORDERBYAvgSubTotal,CustomerID,SalesOrderNumber

The syntax as clear and concise. But that is not all aCTEcan do. You can use multipleCTEs and join them together in your finalSELECTstatement. The
following is actually a case I have had a few times in production. We have a header and a detail table. The detail has some sort of total a total price or
weight and the total of all lines is stored in the header. Somethimes things do not go the way they should and your header total does not actually reflect
the total of all of your details.

HideCopy Code
WITHSOH
AS
(
SELECT
s.SalesOrderID,
s.SalesOrderNumber,
s.CustomerID,
p.FirstName,
p.LastName,
s.SubTotal
FROMSales.SalesOrderHeaderASs
JOINSales.CustomerAScONc.CustomerID=s.CustomerID
JOINPerson.PersonASpONp.BusinessEntityID=c.PersonID
),
SODAS
(
SELECT
SalesOrderID,
SUM(LineTotal)ASTotalSum
FROMSales.SalesOrderDetail
GROUPBYSalesOrderID
)
SELECT*
FROMSOH
JOINSODONSOD.SalesOrderID=SOH.SalesOrderID
WHERESOH.SubTotal<>SOD.TotalSum

Notice a few things. The firstCTEfocuses on getting the data we need and performing necessary joins. The secondCTEfocuses on getting the sum for all
salesorder details. The final query joins the twoCTEs and filters only those where the total of the order is not equal to that of the details. We could have
written this withoutCTEs. The following query shows how.

HideCopy Code
SELECT
s.SalesOrderID,
s.SalesOrderNumber,
s.CustomerID,
p.FirstName,
p.LastName,
s.SubTotal,
sd.SalesOrderID,
SUM(sd.LineTotal)ASTotalSum
FROMSales.SalesOrderHeaderASs
JOINSales.CustomerAScONc.CustomerID=s.CustomerID
JOINPerson.PersonASpONp.BusinessEntityID=c.PersonID
JOINSales.SalesOrderDetailASsdONsd.SalesOrderID=s.SalesOrderID
GROUPBY
s.SalesOrderID,
s.SalesOrderNumber,
s.CustomerID,
p.FirstName,
p.LastName,
s.SubTotal,
sd.SalesOrderID
HAVINGSUM(sd.LineTotal)<>s.SubTotal

http://www.codeproject.com/Articles/692269/QueryingSQLServerPartII 9/28
08/09/2016 QueryingSQLServer2012:PartIICodeProject
So which one should you use? Actually it really does not matter. Both queries perform exactly the same in terms of reads. The only difference is that the
query plan for the last query performs an extra step for theHAVINGclause.

You could go crazy withCTEs and even perform your last join in a seperateCTE. This illustrates a nice use ofCTEs. You can refer to aCTEwithin
anotherCTE. The following query compiles to the exact same query as the one with 'only' twoCTEs.

HideShrink Copy Code


WITHSOH
AS
(
SELECT
s.SalesOrderID,
s.SalesOrderNumber,
s.CustomerID,
p.FirstName,
p.LastName,
s.SubTotal
FROMSales.SalesOrderHeaderASs
JOINSales.CustomerAScONc.CustomerID=s.CustomerID
JOINPerson.PersonASpONp.BusinessEntityID=c.PersonID
),
SODAS
(
SELECT
SalesOrderID,
SUM(LineTotal)ASTotalSum
FROMSales.SalesOrderDetail
GROUPBYSalesOrderID
),
TOTALAS
(
SELECT
SOH.SalesOrderID,
SOH.SalesOrderNumber,
SOH.CustomerID,
SOH.FirstName,
SOH.LastName,
SOH.SubTotal,
SOD.TotalSum
FROMSOH
JOINSODONSOD.SalesOrderID=SOH.SalesOrderID
WHERESOH.SubTotal<>SOD.TotalSum
)
SELECT*
FROMTOTAL

Unlike a derived table aCTEcan be used inJOINs and can also make selfJOINs. I still have no use case for selfJOINs in AdventureWorks2012, so I will
use the same example I used in part I, but this time using aCTE.

HideCopy Code
WITHCTEAS
(
SELECT
BusinessEntityID,
Title,
FirstName,
LastName
FROMPerson.Person
)
SELECT
CTE1.BusinessEntityIDASCurrentID,
CTE1.TitleASCurrentTitle,
CTE1.FirstNameASCurrentFirstName,
CTE1.LastNameASCurrentLastName,
CTE2.BusinessEntityIDASNextID,
CTE2.TitleASNextTitle,
CTE2.FirstNameASNextFirstName,
CTE2.LastNameASNextLastName
FROMCTEASCTE1
LEFTJOINCTEASCTE2ONCTE2.BusinessEntityID=CTE1.BusinessEntityID+1
ORDERBYCurrentID,CurrentFirstName,CurrentLastName

CTEs actually have another use within SQL, which is recursion. I will get to that in the next section.

6. Set operators; UNION, INTERSECT and EXCEPT


6.1 Combining sets; UNION and UNION ALL

There are a few operators that can be used to combine multiple result sets into a single set. TheUNIONoperator is one of those.UNIONtakes two result
sets and glues them together. There are two types ofUNIONs, theUNIONand theUNIONALL. The difference between the two is thatUNIONeliminates

http://www.codeproject.com/Articles/692269/QueryingSQLServerPartII 10/28
08/09/2016 QueryingSQLServer2012:PartIICodeProject
rows that are in both sets duplicates that is whileUNIONALLkeeps these rows. The following example makes clear how to useUNIONandUNION
ALLand what the difference is.

HideCopy Code
SELECTTOP1
BusinessEntityID,
Title,
FirstName,
MiddleName,
LastName
FROMPerson.Person

UNION

SELECTTOP2
BusinessEntityID,
Title,
FirstName,
MiddleName,
LastName
FROMPerson.Person
ORDERBYBusinessEntityID

And the result.

Notice that we actually selected a total of three records, yet the result shows only two. That is because the first record was eliminated by
theUNIONbecause all attributes had the same value they were duplicates. Notice thatNULLs are considered as equal.

If we wanted to keep that third row we could useUNIONALL.

HideCopy Code
SELECTTOP1
BusinessEntityID,
Title,
FirstName,
MiddleName,
LastName
FROMPerson.Person

UNIONALL

SELECTTOP2
BusinessEntityID,
Title,
FirstName,
MiddleName,
LastName
FROMPerson.Person
ORDERBYBusinessEntityID

This time the duplicate row was not discarded from the result.

AUNIONoperator can work on any two sets that have the same number of columns with the same type at each column index.

HideCopy Code
SELECT
'Salesorder'ASOrderType,
SalesOrderIDASOrderID,
SalesOrderNumber,
CustomerIDASCustomerOrVendorID,
SubTotal,
NULLASRevisionNumber
FROMSales.SalesOrderHeader
WHERESubTotal<2

UNION

SELECT
'Purchaseorder',
PurchaseOrderID,
NULL,
VendorID,
SubTotal,
http://www.codeproject.com/Articles/692269/QueryingSQLServerPartII 11/28
08/09/2016 QueryingSQLServer2012:PartIICodeProject
RevisionNumber
FROMPurchasing.PurchaseOrderHeader
WHERERevisionNumber>5
ORDERBYOrderType,OrderID

As you can see we can select from any two tables, but the number of columns must be the same for both queries, as well as the type of the returned value
text, numeric, date, etc.

Other than that there are a few things to notice. The column names of the first query are used. For columns that are not applicable in the current select we
can useNULLs or any other value of the correct type as placeholders. Each query has its ownWHEREclause and any other clauses exceptORDER
BY.ORDERBYis placed at the end to order the entire result set.

You can use more than oneUNIONto combine even more sets. The following query adds an additional row for each order types total subtotal.

HideShrink Copy Code


SELECT
'Salesorder'ASOrderType,
SalesOrderIDASOrderID,
SalesOrderNumber,
CustomerIDASCustomerOrVendorID,
SubTotal,
NULLASRevisionNumber
FROMSales.SalesOrderHeader
WHERESubTotal<2

UNION

SELECT
'Purchaseorder',
PurchaseOrderID,
NULL,
VendorID,
SubTotal,
RevisionNumber
FROMPurchasing.PurchaseOrderHeader
WHERERevisionNumber>5

UNION

SELECT
'Salesordertotal',
NULL,
NULL,
NULL,
SUM(SubTotal),
NULL
FROMSales.SalesOrderHeader
WHERESubTotal<2

UNION

SELECT
'Purchaseordertotal',
NULL,
NULL,
NULL,
SUM(SubTotal),
NULL
FROMPurchasing.PurchaseOrderHeader
WHERERevisionNumber>5

ORDERBYOrderType,OrderID

6.2 Recursion with CTEs and UNION ALL

I already mentioned thatCTEs can be used for recursive functions. Remember the selfJOINexample from part I? An Employee might have
aManagerIDwhich refers to anotherEmployee. Of course any manager can have his own manager all the way up to the top manager. There is no way
for us to tell how many managers are up the hierarchy. For this we can use recursion. Just give us the manager of the manager of the manager... up to the
point where a manager has no manager. This can be achieved usingUNIONALL. Such a recursiveCTEconsists of two or more queries, one being the
anchor member and the other as therecursive member. The anchor member is invoked once and returns a relational result. The recursive member is called
until it returns an empty result set and has a reference to theCTE.

Unfortunately I have no use case for recursion in AdventureWorks2012 so I am just going to use recursion to select anyPersonfrom thePersontable
with aBusinessEntityIDthat is one lower than the previous until there is no ID that is one lower anymore

HideCopy Code
WITHRECAS
(
SELECT
BusinessEntityID,
FirstName,
LastName

http://www.codeproject.com/Articles/692269/QueryingSQLServerPartII 12/28
08/09/2016 QueryingSQLServer2012:PartIICodeProject
FROMPerson.Person
WHEREBusinessEntityID=9

UNIONALL

SELECT
p.BusinessEntityID,
p.FirstName,
p.LastName
FROMREC
JOINPerson.PersonASpONp.BusinessEntityID=REC.BusinessEntityID1
)
SELECT*
FROMREC

And here is the result for running this query forBusinessEntityID9.

And to prove this really works, here is the result of the same query, but withBusinessEntityID1704. It stops at 1699 because appearently there is
noPersonwith aBusinessEntityIDof 1698.

A small word of caution: the maximum recursion in SQL Server is 100. So the following query will run without problems.

HideCopy Code
WITHRECAS(
SELECT100ASSomeCounter

UNIONALL

SELECTSomeCounter1
FROMREC
WHERESomeCounter1>=0
)
SELECT*
FROMREC

Adding one will result in an overflow though!

Using thequery hintMAXRECURSIONcan help overcome this limitation. The following query will run fine again up to 200 recursion depth.

HideCopy Code

http://www.codeproject.com/Articles/692269/QueryingSQLServerPartII 13/28
08/09/2016 QueryingSQLServer2012:PartIICodeProject

WITHRECAS(
SELECT101ASSomeCounter

UNIONALL

SELECTSomeCounter1
FROMREC
WHERESomeCounter1>=0
)
SELECT*
FROMREC
OPTION(MAXRECURSION200)

6.3 INTERSECT

INTERSECTis another set operator and the syntax and rules are the same as that of theUNIONoperator. The difference between the two is the results
that are returned.UNIONreturns all rows and discards duplicate rows.INTERSECTreturns only duplicate rows once. Let us take the first example I used
forUNION, but replace theUNIONwith anINTERSECT.

HideCopy Code
SELECTTOP1
BusinessEntityID,
Title,
FirstName,
MiddleName,
LastName
FROMPerson.Person

INTERSECT

SELECTTOP2
BusinessEntityID,
Title,
FirstName,
MiddleName,
LastName
FROMPerson.Person
ORDERBYBusinessEntityID

Since only theTOP1record is returned by both queries this is also the result thatINTERSECTreturns.

Like withUNIONs inINTERSECTNULLs are considered as equal.

6.4 EXCEPT

EXCEPTis the third set operator and the syntax and rules are also the same as for that of theUNIONoperator.EXCEPTreturns only the records from the
first query that are not returned by the second query. In other words,EXCEPTreturns rows that are unique to the first query. We should notice here that
withUNIONandINTERSECTit does not matter which query comes first and which query comes second, the result remains the same. WithEXCEPTthe
order of queries does matter. I will show this by using the same example I used forUNIONandINTERSECT, but useEXCEPTinstead. The following query
returns no rows.

HideCopy Code
SELECTTOP1
BusinessEntityID,
Title,
FirstName,
MiddleName,
LastName
FROMPerson.Person

EXCEPT

SELECTTOP2
BusinessEntityID,
Title,
FirstName,
MiddleName,
LastName
FROMPerson.Person
ORDERBYBusinessEntityID

No rows were returned because the first query returned no rows that were not returned by the second query. Now let us switch theTOPs. The second row
is now only returned by the first query.

HideCopy Code

http://www.codeproject.com/Articles/692269/QueryingSQLServerPartII 14/28
08/09/2016 QueryingSQLServer2012:PartIICodeProject

SELECTTOP2
BusinessEntityID,
Title,
FirstName,
MiddleName,
LastName
FROMPerson.Person

EXCEPT

SELECTTOP1
BusinessEntityID,
Title,
FirstName,
MiddleName,
LastName
FROMPerson.Person
ORDERBYBusinessEntityID

In this query a result is returned.

You can useUNION,INTERSECTandEXCEPTin the same query. In this caseINTERSECTtakes precedence


overUNIONandEXCEPT.UNIONandEXCEPTare considered equal and are performed in the order in which they appear.

7. Pushing over tables; PIVOT and UNPIVOT


7.1 Pivoting

Pivoting and unpivoting are specialized cases of grouping and aggregating data. Pivoting is the process of creating columns from rows while unpivoting is
the process of creating rows from columns. We start with pivoting data. For example, we want to know the total subtotal that customers ordered grouped
by sales person. However, for some reason we want to make columns out of the sales persons. So we'd get the
columnsCustomerID,SalesPerson1,SalesPerson2, etc. and the rows showing the ID for a customer and then the total subtotal ordered
forSalesPerson1,SalesPerson2, etc. This is exactly what thePIVOToperator is for. To understand this better let us look at an example.

HideCopy Code
WITHPivotDataAS
(
SELECT
s.CustomerID,
s.SalesPersonIDASSpreadingCol,
s.SubTotalASAggregationCol
FROMSales.SalesOrderHeaderASs
)
SELECT
CustomerID,
[274]ASStephenJiang,
[275]ASMichaelBlythe,
[276]ASLindaMitchell
FROMPivotData
PIVOT(SUM(AggregationCol)FORSpreadingColIN([274],[275],[276]))ASP
WHERE[274]ISNOTNULLOR[275]ISNOTNULLOR[276]ISNOTNULL

And here is a part of the result notice I'm not showing from the first line.

So that is looking pretty difficult. Let us break that up in simpler parts. First of all I am using aCTEto identify the columns I want to use in myPIVOT. I
then make a rather weirdSELECTstatement where I select for, what seem to be, random numbers. Actually these are values from the so calledspreading
columndefined in thePIVOTclause. So 274, 275 and 276 are actuallySalesPersonID's that I want to see as columns rather than values in rows. In

http://www.codeproject.com/Articles/692269/QueryingSQLServerPartII 15/28
08/09/2016 QueryingSQLServer2012:PartIICodeProject
thePIVOTclause I indicate the aggregation operator I want to use, in this caseSUM, and the column I want to aggregate. Then I specify thespreading
column, or the column which values should be columns instead of values in rows. Last, but not least, yourPIVOTmust have an alias assigned, even when
you do not use it. TheWHEREclause is optional. That is quite something so take a while to study the syntax and let it sink in.

You might have noticed I do not do anything withCustomerIDin thePIVOTclause. This is because withPIVOTthe grouping elements areidentified by
elimination. Since I am not usingCustomerIDin either an aggregate function or as speading element it automatically becomes the grouping element.
This is also the reason it is best to use aCTE. If you had just selected from theSales.SalesOrderHeadertable directly
theSalesOrderHeaderIDwould become part of the grouping and you would have gotten one row per order instead of perCustomerID!

Now there are a few limitations to using thePIVOToperator. One of those is that you cannot use expressions to define your aggregation or spreading
colum values. Another limitation is thatCOUNT(*)is not allowed as aggregation function used byPIVOT. Instead you need to useCOUNT(ColumnName).
You can work around this limitation by selecting a constant value in yourCTEand usingCOUNTon that column. Furthermore you can use only one
aggregate function.
The last limitation, and in my opinion the limitation that makes you not want to usePIVOTto often, is that the spreading values must be a list of static
values. In our example these values areSalesPersonID's. So what does this mean? It means you are actually hard coding values in your query! I only
showed threeSalesPersonsbecause listing them all would make the query a lot bigger. Just presume I queried for our top three sales people. What if
next month someone else sells better? We would have to alter our query! Or what if aSalesPersonleft the company? Back to rewriting your query...
Actually I had to find the names of theSalesPersons manually and use them as column aliases to make sense of the columns although I could have
joined on the names and use those as spreading values. You can come around this limitation by usingDynamic SQL, but that is outside the scope of this
article. And of course this is no problem at all when you are writing queries for values that are really sort of static, like VAT percentages or status ID's.

7.2 Unpivoting

Unpivoting, in a sense, is the opposite, or inverse, of pivoting. Instead of making columns out of row values we can make row values out of column values.
In fact the starting point for anUNPIVOTis, usually, pivoted data. So let us take thePIVOTexample andUNPIVOTit again. For this example I will wrap the
result of thePIVOTexample in aCTEand use this to unpivot the data. this is not very useful in a real world example after all, why would youPIVOTfirst
andUNPIVOTdirectly after?, but this becomes more useful when the pivoted data is stored in a table or the result of aVIEWorSTOREDPROCEDURE.

HideCopy Code
WITHDataToPivotAS
(
SELECT
s.CustomerID,
s.SalesPersonIDASSpreadingCol,
s.SubTotalASAggregationCol
FROMSales.SalesOrderHeaderASs
),
DataToUnpivotAS
(
SELECT
CustomerID,
[274]ASStephenJiang,
[275]ASMichaelBlythe,
[276]ASLindaMitchell
FROMDataToPivot
PIVOT(SUM(AggregationCol)FORSpreadingColIN([274],[275],[276]))ASP
WHERE[274]ISNOTNULLOR[275]ISNOTNULLOR[276]ISNOTNULL
)
SELECT
CustomerID,
SalesPerson,
SubTotal
FROMDataToUnpivot
UNPIVOT(SubTotalFORSalesPersonIN(StephenJiang,MichaelBlythe,LindaMitchell))ASU
ORDERBYCustomerID

I wrapped the result of thePIVOTexample in theDataToUnpivotCTE. Notice that in this example you should get three times as many rows as in the
result of thePIVOT. After all each row from thePIVOTresult is now applied to theStephenJiangvalue, theMichaelBlythevalue and
theLindaMitchellvalue. This is not the case however, sinceUNPIVOTremoves rows where theSubTotalwould beNULLfor a givenSalesPerson.
The following example of the same customer for the pivoted and the unpivoted result might clear things up.

In the unpivoted resultLindaMitchellwas removed because in the pivoted result her value was missing.

Let us take another look at an example where we unpivot a table that is not actually pivoted. TheSales.SpecialOffertable has special offers with
aDescription, aMinQtyand aMaxQty. Instead of showing all three in one row we want a seperate row for bothMinQtyandMaxQty.

HideCopy Code

http://www.codeproject.com/Articles/692269/QueryingSQLServerPartII 16/28
08/09/2016 QueryingSQLServer2012:PartIICodeProject

SELECT
SpecialOfferID,
Description,
QtyType,
Qty
FROMSales.SpecialOffer
UNPIVOT(QtyFORQtyTypeIN(MinQty,MaxQty))ASU

As you can seeQtyTypegets the value of eitherMinQtyorMaxQtyprevious column names and theQtynow shows the value that was previously either
in theMinQtycolumn or in theMaxQtycolumn, dependent onQtyType.

Again it is not possible to use expressions for the value of the new values column or the new column name values column.

8. More uses for Table Expressions; APPLY


TheAPPLYoperator can be used to 'join' a table expression to your query. I use the term 'join' because anAPPLYactually looks like aJOINin that you
can merge multiple tables into the same result set. There are two types ofAPPLYoperators, being theCROSSAPPLYand theOUTERAPPLY. The
interesting part about theAPPLYoperator is that it can have a table expression that references values from the outer query. One thing you should
remember is thatCROSSAPPLYactually looks most like theINNERJOINwhile theOUTERAPPLYworks like anOUTERJOIN. Other than that
anAPPLYoperator may perform much better than aJOINwhen theJOINconditions are rather complex.

8.1 CROSS APPLY

TheCROSSAPPLYoperator works like anINNERJOINin that it can match rows from two tables and leaves out rows that were not matched by the other
table in the result. So let us look at an example. We want to select allPersonsthat have aSalesOrderand show some order information for the most
expensive order thatPersonhas made.

HideCopy Code
SELECT
p.BusinessEntityID,
p.FirstName,
p.LastName,
a.*
FROMPerson.PersonASp
CROSSAPPLY(SELECTTOP1
s.SalesOrderID,
s.CustomerID,
s.SubTotal
FROMSales.SalesOrderHeaderASs
JOINSales.CustomerAScONc.CustomerID=s.CustomerID
WHEREc.PersonID=p.BusinessEntityID
ORDERBYs.SubTotalDESC)ASa
ORDERBYp.BusinessEntityID

So theCROSSAPPLYoperator takes a table expression as input parameter and simply joins the result with each row of the outer query. Notice that we
can do aTOP1, filter by using aWHEREclause to match theCustomerIDwith theBusinessEntityIDandORDERBYDESCto get the most expensive
order for that particular customer. Something that would not have been possible by simply using aJOIN! Notice thatPersonsthat have not placed an
order are not returned. Like withPIVOTandUNPIVOTwe need to alias the result of theAPPLYoperator.

Because we can reference values from the outer query in ourAPPLYoperator it is also possible to usefunctionswith theAPPLYoperator.
TheAdventureWorks2012database actually has oneuserdefined table valued functioncalledufnGetContactInformation, which takes aPersonIDas
input and returns information about aPersonnames and if they are suppliers, customers etc.. So using theAPPLYoperator we can show this
information in our resultset by passing theBusinessEntityIDof the outer query to the input of the function. Since calling this function 19972 times
once for eachPerson is actually quite time consuming we are only selecting aTOP1000.

HideCopy Code
SELECTTOP1000
p.BusinessEntityID,
a.*
FROMPerson.PersonASp
CROSSAPPLYufnGetContactInformation(p.BusinessEntityID)ASa
ORDERBYp.BusinessEntityID

Notice that we must specify the function without parenthesis or aSELECT...FROM.

And of course we can use multipleAPPLYoperators in a single query.

HideCopy Code
SELECTTOP1000
p.BusinessEntityID,
a.*,
s.*
FROMPerson.PersonASp
CROSSAPPLYufnGetContactInformation(p.BusinessEntityID)ASa
CROSSAPPLY(SELECTTOP1

http://www.codeproject.com/Articles/692269/QueryingSQLServerPartII 17/28
08/09/2016 QueryingSQLServer2012:PartIICodeProject
s.SalesOrderID,
s.CustomerID,
s.SubTotal
FROMSales.SalesOrderHeaderASs
JOINSales.CustomerAScONc.CustomerID=s.CustomerID
WHEREc.PersonID=p.BusinessEntityID
ORDERBYs.SubTotalDESC)ASs
ORDERBYp.BusinessEntityID

You do not have to match rows from your outer query with yourAPPLYresult. The followingAPPLYsimply returns the most expensive order and as a
result shows this with eachPerson, regardless of whether the order was placed by thisPerson.

HideCopy Code
SELECT
p.BusinessEntityID,
p.FirstName,
p.LastName,
a.*
FROMPerson.PersonASp
OUTERAPPLY(SELECTTOP1
s.SalesOrderID,
s.CustomerID,
s.SubTotal
FROMSales.SalesOrderHeaderASs
ORDERBYs.SubTotalDESC)ASa
ORDERBYp.BusinessEntityID

And the rows do not have to be a one on one match either. The following query gets the top three most expensive orders regardless of customer and as a
result eachPersonis duplicated three times in the result once for each order, regardless of whether the order was placed by thisPerson.

HideCopy Code
SELECT
p.BusinessEntityID,
p.FirstName,
p.LastName,
a.*
FROMPerson.PersonASp
OUTERAPPLY(SELECTTOP3
s.SalesOrderID,
s.CustomerID,
s.SubTotal
FROMSales.SalesOrderHeaderASs
ORDERBYs.SubTotalDESC)ASa
ORDERBYp.BusinessEntityID

8.2 OUTER APPLY

TheOUTERAPPLYworks in much the same way as theCROSSAPPLYwith the exception that it also returns rows if no corresponding row was returned
by theAPPLYoperator. We can see this by using the first example of the previous section, but by changing theCROSSAPPLYinto anOUTERAPPLY. By
running this query you can see thatPersons that have not placed an order are now also returned in the result set.

HideCopy Code
SELECT
p.BusinessEntityID,
p.FirstName,
p.LastName,
a.*
FROMPerson.PersonASp
OUTERAPPLY(SELECTTOP3
s.SalesOrderID,
s.CustomerID,
s.SubTotal
FROMSales.SalesOrderHeaderASs
JOINSales.CustomerAScONc.CustomerID=s.CustomerID
WHEREc.PersonID=p.BusinessEntityID
ORDERBYs.SubTotalDESC)ASa
ORDERBYp.BusinessEntityID

Other than that all rules that apply to theCROSSAPPLYoperator also apply toOUTERAPPLY.

9. Other aspects of querying


So far we have only queried data and returned the values from the database as they actually are. There is much more that we can do with these values
though. Suppose, for example, an order has aSubTotaland aTotalDuecolumn. What we want to know is how much percent tax the customer paid on
an order. HoweverFreightis also added toTotalDue, so we must subtract that first. Usually we would calculate this by taking the difference between
theSubTotalandTotalDue, divide that byTotalDueand multiply that by 100.

We can do this kind of math in SQL Server. The following query shows how this can be done.

http://www.codeproject.com/Articles/692269/QueryingSQLServerPartII 18/28
08/09/2016 QueryingSQLServer2012:PartIICodeProject
HideCopy Code
SELECT
SalesOrderID,
CustomerID,
SubTotal,
TaxAmt,
Freight,
TotalDue,
(((TotalDueFreight)SubTotal)/(TotalDueFreight))*100ASTaxPercentage
FROMSales.SalesOrderHeader

And of course we can useSubTotalandTaxAmtandFreightto calculateTotalDueourselves. You should remember is that adding a numeric value
toNULLalways results inNULL.

HideCopy Code
SELECT
SalesOrderID,
CustomerID,
SubTotal,
TaxAmt,
Freight,
TotalDue,
SubTotal+TaxAmt+FreightASTotalDueCalc
FROMSales.SalesOrderHeader

How about if we want to give every customer a ten percent discount on their orders probably a bad idea, but let's do this.

HideCopy Code
SELECT
SalesOrderID,
CustomerID,
SubTotal,
SubTotal(SubTotal*0.10)ASSubTotalAfterDiscount
FROMSales.SalesOrderHeader

We can also use functions such asFLOOR,CEILINGandROUNDto further manipulate our data.

HideCopy Code
SELECT
SalesOrderID,
CustomerID,
SubTotal,
FLOOR(SubTotal)ASSubTotalRoundedDown,
CEILING(SubTotal)ASSubTotalRoundedUp,
ROUND(SubTotal,2)ASRoundedToTwoDecimals
FROMSales.SalesOrderHeader

There are much more functions that can be used to manipulate data in various ways. I cannot possibly discuss them all here. The following sections will
give an overview of some important and most used functions for manipulating data in SQL Server.

9.1 Converting types; CAST and CONVERT, PARSE and FORMAT

9.1.1 CAST and CONVERT

Casting and converting valuesare the same thing, being changing a datatype to another datatype. For example, we can change a numeric into a string, a
string into a numeric given that the text actually represents a numeric value, a date to a string, string to date etc.

SQL Server actually has four functions for casting and converting values.CAST, CONVERT,TRY_CASTandTRY_CONVERT.

Let us look at theCASTfunction first. First of all, you can cast anything toVARCHAR(x), since each and every value can be shown as plain text. Here is a
simple example of casting some values to different types.

HideCopy Code
SELECT
CAST('123'ASINT)ASVarcharToInt,
CAST('20131231'ASDATETIME2)ASVarcharToDateTime2,
CAST(1.2ASINT)ASFloatToInt,
CAST(1234ASVARCHAR(4))ASIntToVarchar

We can cast theSubTotalto aVARCHAR(20)and order it not as numerics, but as alphanumerics which means 10 comes before 2 etc..

HideCopy Code
SELECT
SalesOrderID,
SubTotal,
CAST(SubTotalASVARCHAR(20))ASSubTotalAsAlphaNum
FROMSales.SalesOrderHeader
ORDERBYSubTotalAsAlphaNum

http://www.codeproject.com/Articles/692269/QueryingSQLServerPartII 19/28
08/09/2016 QueryingSQLServer2012:PartIICodeProject
Unfortunately SQL Server formats the results for us and the alphanumeric outcome has only two digits after the comma. WithCONVERTwe have more
control over the formatting of values.

TheCONVERTfunction takes three parameters. The first parameter is the type you want to convert to, the second is the value you want to convert and the
optional third is a style parameter. In this case we want our money to have style number 2.

HideCopy Code
SELECT
SalesOrderID,
SubTotal,
CONVERT(VARCHAR(20),SubTotal,2)ASSubTotalAsAlphaNum
FROMSales.SalesOrderHeader
ORDERBYSubTotalAsAlphaNum

How did I know the format? Look them up on theCAST and CONVERT page on TechNet.

WithCONVERTwe can cast aDATETIMEvalue to aVARCHARand use the style parameter to display a local date format.

HideCopy Code
SELECT
SalesOrderID,
OrderDate,
CONVERT(VARCHAR,OrderDate,13)ASOrderDateAsEur,
CONVERT(VARCHAR,OrderDate,101)ASOrderDateAsUS
FROMSales.SalesOrderHeader

And of course you can convertVARCHARStoDATETIMES. To do this you should always use the text format yyyyMMdd as shown in the following query.
Every other format is not guaranteed to be culture invariant and running a query in Europe with result july 6th might have the result of june 7th in
America!

HideCopy Code
SELECTCONVERT(DATETIME,'20131231')

Some casts may actually not be what you expect. For example, you can cast aDATETIMEvalue to anINT. The value that is returned is actually the
difference in days between the specified value and the minimum date for theSMALLDATETIMEtype which is januari first 1900. The following query shows
this.
The following functions are discussed later in this article, but for now focus on the results.

HideCopy Code
SELECTCONVERT(INT,CONVERT(DATETIME,'17530101'))ASMinDateAsInt,
CONVERT(INT,GETDATE())ASTodayAsInt,
DATEADD(d,CONVERT(INT,
CONVERT(DATETIME,'17530101')),'19000101')
ASMinSmallDateTimePlusMinDateAsInt,
DATEADD(d,CONVERT(INT,GETDATE()),'19000101')
ASMinSmallDateTimePlusTodayAsInt

And here is the result your results will look different since I am using the current datetime and the time at which I write this is different than the time at
which you are reading.

So what happens when we cast a value that cannot be cast to the specified type? In that case we make an invalid cast and an exception is thrown.

If you do not want an exception to be thrown at an invalid cast you can use theTRY_CASTandTRY_CONVERTfunctions. They work exactly the same
asCASTandCONVERT, except that theTRY_variants do not throw an error when a cast is invalid, but returnsNULLinstead.

HideCopy Code
SELECTTRY_CAST('Hello'ASINT),
TRY_CONVERT(INT,'Hello')

SQL Server might give you a warning thatTRY_CASTis not recognized as a builtin function name. This appears to be a bug and you can ignore it, the
query will run fine.

9.1.2 PARSE

http://www.codeproject.com/Articles/692269/QueryingSQLServerPartII 20/28
08/09/2016 QueryingSQLServer2012:PartIICodeProject
Parsingis a special kind of cast which always casts aVARCHARvalue into another datatype. In SQL Server we can use thePARSEorTRY_PARSEfunction
which takes as parameters aVARCHARvalue, a datetype and an optional culture code to specify in which culture format the value is formatted. We can for
example parse aVARCHARvalue that represents a date formatted to Dutch standards into aDATETIMEvalue.

HideCopy Code
SELECTPARSE('12312013'ASDATETIME2USING'enUS')ASUSDate,
PARSE('31122013'ASDATETIME2USING'nlNL')ASDutchDate

We can also usePARSEfor numeric or money types. The following example shows how two differently formatted money styles produce the same output
withPARSE. Notice that Americans use a point as decimal seperator while Dutch use a comma.

HideCopy Code
SELECTPARSE('$123.45'ASMONEYUSING'enUS')ASUSMoney,
PARSE('123,45'ASMONEYUSING'nlNL')ASDutchMoney

If we ommit the currency symbol and the culture info we actually get very different results!

HideCopy Code
SELECTPARSE('123.45'ASMONEY)ASUSMoney,
PARSE('123,45'ASMONEY)ASDutchMoney

Of course a parse can also fail. As withCASTandCONVERTwe get an error.

And again you can useTRY_PARSEto returnNULLif aPARSEfails.

HideCopy Code
SELECTTRY_PARSE('Hello'ASMONEYUSING'nlNL')

It is recommended to usePARSEonly to parse date and numeric values represented as text to their corresponding datatypes. For more general casting
useCASTandCONVERT.

9.1.3 FORMAT

TheFORMATfunction does not really provide a means to convert between datatypes. Instead it provides a way to output data in a given format.

For example, we can format dates to only show the date without time or we can format numerics to show leading 0's and always x decimal digits.

HideCopy Code
SELECT
SalesOrderID,
FORMAT(SalesOrderID,'SO0')ASSalesOrderNumber,
CustomerID,
FORMAT(CustomerID,'0.00')ASCustomerIDAsDecimal,
OrderDate,
FORMAT(OrderDate,'ddMMyy')ASFormattedOrderDate
FROMSales.SalesOrderHeader

And you can specify cultures to format to that specified culture.

HideCopy Code
SELECT
SalesOrderID,
OrderDate,
FORMAT(OrderDate,'d','enUS')ASUSShortDate,
FORMAT(OrderDate,'d','nlNL')ASDutchShortDate,
FORMAT(OrderDate,'D','enUS')ASUSLongDate,
FORMAT(OrderDate,'D','nlNL')ASDutchLongDate
FROMSales.SalesOrderHeader
ORDERBYCustomerID

And here are some results.

http://www.codeproject.com/Articles/692269/QueryingSQLServerPartII 21/28
08/09/2016 QueryingSQLServer2012:PartIICodeProject

And of course we can format numeric values as well.

HideCopy Code
SELECT
SalesOrderID,
SubTotal,
FORMAT(SubTotal,'C','nlNL')ASDutchCurrency
FROMSales.SalesOrderHeader

You might be wondering if there is aTRY_FORMAT. There is not. Ask yourself, why should a format fail? It probably doesn't. It might format to unexpected
values, but other than that invalid values can be caught when SQL Server parses the query. Here is an example of what you think might go wrong, but
actually just formats to a weird value.

HideCopy Code
SELECT
SalesOrderID,
SubTotal,
FORMAT(SubTotal,'Hello','nlNL')ASDutchCurrency
FROMSales.SalesOrderHeader

For format values I once again redirect you to theFORMAT page on TechNet.

9.2 VARCHAR functions

You will be working with the(VAR)CHARtype a lot. In the previous section we have seenFORMATwhich can be used to get a specific, culture dependent,
output for certain values. But that is not all you can do with text data.

First of all, there are many times that you want to concatenate text. ThePersontable, for example, has aFirstNameand aLastNamecolumn. Those
combined, seperated by a space, can become aFullNamefield.

HideCopy Code
SELECT
BusinessEntityID,
FirstName+''+LastNameASFullName
FROMPerson.Person
ORDERBYFullName

When concatenating a string toNULLthis results inNULLunless a session option calledCONCAT_NULL_YIELDS_NULL_INPUTis turned off. This is outside
the scope of this article.

A word of caution on concatenation. Make sure you are either concatenating text to text or numerics to numerics. The following concatenation results in
an error.

HideCopy Code
SELECT
BusinessEntityID,
BusinessEntityID+FirstNameASIDName
FROMPerson.Person
ORDERBYIDName

And here is the error.

SQL Server tries to convertFirstNameto anINT, sinceBusinessEntityIDis anINT. Switching the values will not help either. What will help is aCAST.

HideCopy Code
SELECT
BusinessEntityID,
CAST(BusinessEntityIDASVARCHAR(6))+FirstNameASIDName
FROMPerson.Person
ORDERBYIDName

Or you could use theCONCATfunction, which concatenates all values passed to the function as strings. When usingCONCATNULLs are ignored.
http://www.codeproject.com/Articles/692269/QueryingSQLServerPartII 22/28
08/09/2016 QueryingSQLServer2012:PartIICodeProject
HideCopy Code
SELECT
BusinessEntityID,
CONCAT(BusinessEntityID,FirstName)ASIDName,
CONCAT(NULL,LastName)ASLastName
FROMPerson.Person
ORDERBYIDName

Other times you only want to return a portion of a string. For example the first or last letter. This can be achieved with theLEFTandRIGHTfunctions.

HideCopy Code
SELECT
BusinessEntityID,
LEFT(FirstName,1)+'.'+LastNameASAbbrvName,
RIGHT(FirstName,3)ASLastThreeLetters
FROMPerson.Person
ORDERBYAbbrvName

You can also useSUBSTRINGto get a portion of a string by 1based index. The following query outputs the same data as the last query. Notice I also use
theLENfunction to determine the length of a given input.

HideCopy Code
SELECT
BusinessEntityID,
SUBSTRING(FirstName,1,1)+'.'+LastNameASAbbrvName,
SUBSTRING(FirstName,LEN(FirstName)2,3)ASLastThreeLetters
FROMPerson.Person
ORDERBYAbbrvName

To get the index of a specific character in a string you can use theCHARINDEXfunction which returns the position of the first occurrence of the specified
character. Using this we can, for example, format a numeric value and return the portion before the decimal seperator and the portion after the decimal
seperator seperately.

HideCopy Code
SELECT
SubTotal,
SUBSTRING(
FORMAT(SubTotal,'G','enUS'),
0,
CHARINDEX('.',FORMAT(SubTotal,'G','enUS')))ASDigitsBeforeDecimal,
SUBSTRING(
FORMAT(SubTotal,'G','enUS'),
CHARINDEX('.',FORMAT(SubTotal,'G','enUS'))+1,
4)ASDigitsAfterDecimal
FROMSales.SalesOrderHeader

Sometimes you want to format strings in a way that is not supported by theFORMATfunction. You can use the functionsUPPERandLOWERto make a
string all uppercase or all lowercase. The functionsLTRIMandRTRIMremove leading and trailing spaces from a string especially useful when dealing with
legacy applications!.

HideCopy Code
SELECT
UPPER(FirstName)ASUpperName,
LOWER(FirstName)ASLowerName,
LTRIM('abc')ASAbcWTrailing,
RTRIM('abc')ASAbcWLeading,
LTRIM(RTRIM('abc'))ASAbc
FROMPerson.Person

There are a few more useful functions that you can use to alter strings. WithREPLACEyou can replace a character or a substring of a string with another
character or string. WithSTUFFyou can replace a part of a string based on index. WithREVERSEyou can, of course, reverse a string. In the following
example we revert theSalesOrderNumber, we replace the'SO'in theSalesOrderNumberwith'SALE', and we replace the first two characters of
thePurchaseOrderNumberwith'PURC'.

HideCopy Code
SELECT
SalesOrderNumber,
REVERSE(SalesOrderNumber)ASReversedOrderNumber,
REPLACE(SalesOrderNumber,'SO','SALE')ASNewOrderFormat,
PurchaseOrderNumber,
STUFF(PurchaseOrderNumber,1,2,'PURC')ASNewPurchaseFormat
FROMSales.SalesOrderHeader

There are more functions you can use to format, alter or get information about string values. You can find them on TechNet.

9.3 DATETIME functions

http://www.codeproject.com/Articles/692269/QueryingSQLServerPartII 23/28
08/09/2016 QueryingSQLServer2012:PartIICodeProject
Working with dates and time in SQL Server or any language has never been easy. There is no date without time, no time without date, comparisons fail if
two values differ by a millisecond, every culture has its own formats, we have timezones, daylight savings time, and not even every culture has the same
calendar! Luckily SQL Server provides us with lots of functions and datatypes to work with dates and time.

I can recommend reading the following page on TechNet:Date and Time Data Types and Functions.

First of all, how do we get the current time? SQL Server has a couple of functions you can use.GETDATEandCURRENT_TIMESTAMPto get the current date
and time on the computer that the SQL Server instance is running on as datatypeDATETIME,GETUTCDATEgets the current date and time in Coordinated
Universal Time as datatypeDATETIME,SYSDATETIMEwhich also returns the current date and time, but as
datatypeDATETIME2(7),SYSUTCDATETIMEwhich returns the current date and time as Coordinated Universal Time as datatypeDATETIME2(7)and
theSYSDATETIMEOFFSETwhich returns the current date and time including the timezone offset as datatypeDATETIMEOFFSET(7). The following query
shows these functions.

HideCopy Code
SELECT
GETDATE()AS[GetDate],
CURRENT_TIMESTAMPASCurrentTimestamp,
GETUTCDATE()AS[GetUtcDate],
SYSDATETIME()AS[SysDateTime],
SYSUTCDATETIME()AS[SysUtcDateTime],
SYSDATETIMEOFFSET()AS[SysDateTimeOffset]

To get a date without the time part simply convert aDATETIMEvalue toDATE. Similary, if you want the time without the date you can cast toTIME.

HideCopy Code
SELECT
SYSDATETIME()ASDateAndTime,
CAST(SYSDATETIME()ASDATE)AS[Date],
CAST(SYSDATETIME()ASTIME)AS[Time]

You may also be interested in only a part of the date, for example the day, month or year. You can use theDATEPARTfunction for this, or the 'shortcut'
functionsYEAR,MONTHandDAY. Notice that you can actually extract a lot more usingDATEPART. In addition there is aDATENAMEfunction which works
the same asDATEPART, except it returns the part of the date as string.DATENAMEis especially useful for returning the name of the month. Be aware that
the name of the month is translated in the language of your session.

HideCopy Code
SELECT
DATEPART(DAY,SYSDATETIME())ASDayFromDatePart,
DATEPART(WEEK,SYSDATETIME())ASWeekFromDatePart,
DATEPART(MONTH,SYSDATETIME())ASMonthFromDatePart,
DATEPART(YEAR,SYSDATETIME())ASYearFromDatePart,
DATEPART(SECOND,SYSDATETIME())ASSecondFromDatePart,
DATEPART(NANOSECOND,SYSDATETIME())ASNanoSecondFromDatePart,
DAY(SYSDATETIME())ASDayFromFunc,
MONTH(SYSDATETIME())ASMonthFromFunc,
YEAR(SYSDATETIME())ASYearFromFunc,
DATENAME(DAY,SYSDATETIME())ASDayFromDateName,
DATENAME(MONTH,SYSDATETIME())ASMonthFromDateName,
DATENAME(YEAR,SYSDATETIME())ASYearFromDateName

Sometimes you want to add specific intervals to dates. For example, when an order is placed today the latest delivery date is seven days ahead. Or when
an item is not in stock it may take up to a month. To add or subtract dates you can use theDATEADDfunction. In the following example I remove the time
part by casting to date.

HideCopy Code
SELECT
DATEADD(DAY,1,CAST(SYSDATETIME()ASDATE))ASPreviousDay,
DATEADD(DAY,1,CAST(SYSDATETIME()ASDATE))ASNextDay,
DATEADD(WEEK,1,CAST(SYSDATETIME()ASDATE))ASNextWeek,
DATEADD(MONTH,1,CAST(SYSDATETIME()ASDATE))ASNextMonth,
DATEADD(YEAR,1,CAST(SYSDATETIME()ASDATE))ASNextYear

You can also get the difference between two dates. For example, we want to know the difference in days between the order date and the delivery date of
an order in theSalesOrderHeadertable. This can be accomplished by using theDATEDIFFfunction.

HideCopy Code
SELECT
OrderDate,
ShipDate,
DATEDIFF(DAY,OrderDate,ShipDate)ASDiffBetweenOrderAndShipDate
FROMSales.SalesOrderHeader
ORDERBYDiffBetweenOrderAndShipDateDESC

Be aware that theDATEDIFFfunction only looks at the part you want to know the difference of. So when you want the difference in years the function
only looks at the year part of the dates. So the result of the next query is 1 for day, month and year, even though the difference between the dates is really
just one day.

http://www.codeproject.com/Articles/692269/QueryingSQLServerPartII 24/28
08/09/2016 QueryingSQLServer2012:PartIICodeProject
HideCopy Code
SELECT
DATEDIFF(DAY,'20131231','20140101')ASDiffInDays,
DATEDIFF(MONTH,'20131231','20140101')ASDiffInMonths,
DATEDIFF(YEAR,'20131231','20140101')ASDiffInYears

As mentioned we live in a world divided in timezones. With theSWITCHOFFSETfunction you can display a date in any given offset, no matter what
timezone you are currently in. For example I live in the Netherlands, which is UTC/GMT+1, now I want the Hawaiin time, which is UTC/GMT10 and the
time in Sydney which is UTC/GMT+10, or +11 when it's daylight savings time. Unfortunately there is no easy way to correct for daylight savings time, so
you may figure that out by yourself and Google. The following query shows the local time, the time in Sydney not corrected for daylight savings and
the time in Hawaii.

HideCopy Code
SELECT
SYSDATETIMEOFFSET()ASLocalTime,
SWITCHOFFSET(SYSDATETIMEOFFSET(),'+10:00')ASSydneyTime,
SWITCHOFFSET(SYSDATETIMEOFFSET(),'10:00')ASHawaiianTime

So far we have only constructed dates with strings in a specific format or by calling a function that returns the current date. There are also a couple of
functions that can construct date values from various date parts. These functions
areDATEFROMPARTS,DATETIME2FROMPARTS,DATETIMEFROMPARTS,DATETIMEOFFSETFROMPARTS,SMALLDATETIMEFROMPARTSandTIMEFROMPARTS.
The function names describe what they do pretty well, so I will not expand on that further. Here are some examples of the usages of various function.

HideCopy Code
SELECT
DATEFROMPARTS(2013,12,31)AS[DateFromParts],
DATETIME2FROMPARTS(2013,12,31,14,30,0,0,0)AS[DateTime2FromParts],
DATETIMEOFFSETFROMPARTS(2013,12,31,14,30,0,0,1,0,0)AS[DateTimeOffsetFromParts],
TIMEFROMPARTS(14,30,0,0,0)AS[TimeFromParts]

SQL Server has more useful functions that you can use when working with dates and time. One such functions isEOMONTH, which returns a date
representing the last day of the month of the datetime that was passed as a parameter. Another isISDATEwhich checks if a string can be converted to a
valid date. You can find those and others on TechNet.

I recently came across a rather nice CP article explaining all there is to know about dates, times and functions in all versions of SQL Server. Recommended
reading:Date and Time Date Types and Functions SQL Server 2000, 2005, 2008, 2008 R2, 2012

9.4 CASE and IIF

9.4.1 CASE

Sometimes you want to return a value based on another value. For example, when a bit is 1 or true you want to return 'Yes' and else 'No'. Or you want to
include a value to the result only when that value is not empty. WithCASEsuch scenario's become possible. WithCASEyou can either test a column for a
value and return another value based on that value or you can include more advanced criteria for testing which value to show.

Let us look at the firstCASEvariant, thesimpleform. We know that aPersonfrom thePersontable can have the title'Mr.'or'Mrs.','Ms.'or'Ms'.
Instead of these values we want to return'Mister'for'Mr.'and'Miss'for all the'Ms.'variants. If the title is something else, like'Sr.'then we
want to show that.

HideCopy Code
SELECT
BusinessEntityID,
CASETitle
WHEN'Mr.'THEN'Mister'
WHEN'Mrs.'THEN'Miss'
WHEN'Ms.'THEN'Miss'
WHEN'Ms'THEN'Miss'
ELSETitle
ENDASSalutation,
FirstName,
LastName
FROMPerson.Person

So the simpleCASEstatement has an input expression, in this caseTitle, which is compared to multiple values defined in theWHENclauses. If a match is
found the value in theTHENclause is returned. If no match is found the value in theELSEclause is returned. When the value was not matched in
anyWHENclause and noELSEclause is specified aNULLis returned.

Another variant on theCASEexpression is thesearchedform. With the searched form of theCASEexpression we have more flexibility in when clauses. We
can now use predicates to test for a certain criterium. The firstWHENclause that returns true determines what value is returned. The following query shows
how you can use the searchedCASEexpression and also offers an alternative usingCONCAT.

HideCopy Code
SELECT
BusinessEntityID,
CASE

http://www.codeproject.com/Articles/692269/QueryingSQLServerPartII 25/28
08/09/2016 QueryingSQLServer2012:PartIICodeProject
WHENTitleISNULLANDMiddleNameISNULL
THENFirstName+''+LastName
WHENTitleISNULLANDMiddleNameISNOTNULL
THENFirstName+''+MiddleName+''+LastName
WHENTitleISNOTNULLANDMiddleNameISNULL
THENTitle+''+FirstName+''+LastName
ELSETitle+''+FirstName+''+MiddleName+''+LastName
ENDASFullNameAndTitle,
CONCAT(Title+'',FirstName,'',MiddleName+'',LastName)ASFullNameAndTitleConcat
FROMPerson.Person
ORDERBYFullNameAndTitle

Here is another example which we cannot write using other functions.

HideCopy Code
SELECT
SalesOrderID,
CustomerID,
SubTotal,
CASE
WHENSubTotal<100
THEN'Verycheaporder'
WHENSubTotal<1000
THEN'Cheaporder'
WHENSubTotal<5000
THEN'Moderateorder'
WHENSubTotal<10000
THEN'Expensiveorder'
ELSE'Veryexpensiveorder'
ENDASOrderType
FROMSales.SalesOrderHeader

Notice that in the secondWHENclause we do not have to check if the order is more expensive than 100. If the firstWHENclause returns true then the value
in the correspondingTHENclause is returned and the subsequentWHENclauses are not evaluated.

9.4.2 IIF

Sometimes all you want to know is if a certain attribute has a value or if it isNULLand return a value based on that predicate. Using aCASEexpression can
make your query rather wordy and it would be nice if we had a shortcut. Well, we have. WithIIFyou can test a predicate and specify a value if it evaluates
to true and a value if it evaluates to false. The following example shows howIIFis used and can replace aCASEexpression.

HideCopy Code
SELECT
BusinessEntityID,
CASE
WHENTitleISNULLTHEN'Notitle'
ELSETitle
ENDASTitleCase,
IIF(TitleISNULL,'Notitle',Title)ASTitleIIF,
FirstName,
LastName
FROMPerson.Person

And of course other types of predicates can be used as well.

HideCopy Code
SELECT
SalesOrderID,
CustomerID,
SubTotal,
IIF(SubTotal>5000,'Expensiveorder','Notsoexpensiveorder')
ASOrderType
FROMSales.SalesOrderHeader

And even the following.

HideCopy Code
SELECT
BusinessEntityID,
FirstName,
LastName,
IIF(EXISTS(SELECT*
FROMSales.SalesOrderHeaderASs
JOINSales.CustomerAScONc.CustomerID=s.CustomerID
WHEREc.PersonID=BusinessEntityID),
'Hasorders','Doesnothaveorders')
FROMPerson.Person

9.5 COALESCE, ISNULL and NULLIF

http://www.codeproject.com/Articles/692269/QueryingSQLServerPartII 26/28
08/09/2016 QueryingSQLServer2012:PartIICodeProject
WithCOALESCEwe can specify a range of values and the first value that is notNULLis returned. It can actually make ourIIFthat checks for aNULLfrom
the previous section even shorter.

HideCopy Code
SELECT
BusinessEntityID,
COALESCE(Title,'Notitle'),
FirstName,
LastName
FROMPerson.Person

More values can be specified.

HideCopy Code
SELECT
ProductID,
Name,
ProductNumber,
COALESCE(Style,Class,ProductLine)ASStyle
FROMProduction.Product

Of course now we do not know if the result represents a style, class or productline, but you can fix that by adding aCASEexpression.

COALESCEreturnsNULLif all values that were passed to it areNULLs.

ISNULLdoes the same asCOALESCE, but with some differences. The first difference is thatISNULLcan only have two values. So if the first value isNULLit
will return the second value which may also beNULL.

HideCopy Code
SELECT
BusinessEntityID,
ISNULL(Title,'Notitle'),
FirstName,
LastName
FROMPerson.Person

And you can nestISNULLto get the same effect asCOALESCE.

HideCopy Code
SELECT
ProductID,
Name,
ProductNumber,
ISNULL(Style,ISNULL(Class,ProductLine))ASStyle
FROMProduction.Product

So why would you choose one over the other? Well,COALESCEis an ANSI SQL standard function, so it is more portable thanISNULL. The more important
difference, however, is the return type of the two functions. The type thatCOALESCEreturns is determined by the returned element, while forISNULLthe
type is determined by the first element. In the following query the returned value ofISNULLis truncated to fit the type of the first
element.COALESCEkeeps the value intact.

HideCopy Code
DECLARE@firstASVARCHAR(4)=NULL
DECLARE@secondASVARCHAR(5)='Hello'
SELECT
COALESCE(@first,@second)AS[Coalesce],
ISNULL(@first,@second)AS[IsNull]

And the result.

Another difference between the two is that the underlying type ofCOALESCEis alwaysNULLABLE, even when aNULLcan never be
returned.ISNULLrecognizes scenario's whereNULLis never returned and gives the underlying value definition theNOTNULLABLEattribute. This
difference can be important when you are creatingVIEWSorSTOREDPROCEDURES.

Though it is not within the scope of this article I want to show the difference just to make it clear.
I have created a view using the following definition.

HideCopy Code
CREATEVIEWdbo.CoalesceVsIsNull
AS
SELECT
BusinessEntityID,
COALESCE(Title,'Notitle')ASTitleIsCoalesce,
ISNULL(Title,'Notitle')ASTitleIsNull,
FirstName,

http://www.codeproject.com/Articles/692269/QueryingSQLServerPartII 27/28
08/09/2016 QueryingSQLServer2012:PartIICodeProject
LastName
FROMPerson.Person

And here is the views column definition.

As you can seeTitleIsCoalescecan containNULLs even though this is impossible.TitleIsNullwill never haveNULLs. If you have created
theVIEWyou can now delete it using the following command.

HideCopy Code
DROPVIEWdbo.CoalesceVsIsNull

Another thing you should be aware of when working withCOALESCE,ISNULLorCASEis that every returned value should have the same data type or
conversion errors may occur. For example, the following query raises an error because the@secondparameter is going to be converted to anINT.

HideCopy Code
DECLARE@firstASINT=NULL
DECLARE@secondASVARCHAR(5)='Hello'
SELECT
ISNULL(@first,@second)

A last function I want to mention isNULLIF. This function takes two parameters and returns the first value if the values are different orNULLif the values
are equal.

HideCopy Code
SELECT
NULLIF(1,1)ASEqual,
NULLIF(1,2)ASNotEqual

http://www.codeproject.com/Articles/692269/QueryingSQLServerPartII 28/28