Pro T SQL Programmer S Guide
User Manual:
Open the PDF directly: View PDF .
Page Count: 818 [warning: Documents this large are best viewed by clicking the View PDF Link!]
- Title
- Copyright
- Contents at a Glance
- Contents
- About the Authors
- About the Technical Reviewer
- Acknowledgments
- Introduction
- Chapter 1: Foundations of T-SQL
- Chapter 2: Tools of the Trade
- Chapter 3: Procedural Code
- Chapter 4: User-Defined Functions
- Chapter 5: Stored Procedures
- Chapter 6: In-Memory Programming
- Chapter 7: Triggers
- Chapter 8: Encryption
- Chapter 9: Common Table Expressions and Windowing Functions
- Chapter 10: Data Types and Advanced Data Types
- Chapter 11: Full-Text Search
- FTS Architecture
- Creating Full-Text Catalogs and Indexes
- Creating Full-Text Catalogs
- Creating Full-Text Indexes
- Full-Text Querying
- The FREETEXT Predicate
- FTS Performance Optimization
- The CONTAINS Predicate
- The FREETEXTTABLE and CONTAINSTABLE Functions
- Thesauruses and Stoplists
- Stored Procedures and Dynamic Management Views and Functions
- Statistical Semantics
- Summary
- FTS Architecture
- Chapter 12: XML
- Legacy XML
- OPENXML
- OPENXML Result Formats
- FOR XML Clause
- FOR XML RAW
- FOR XML AUTO
- FOR XML EXPLICIT
- FOR XML PATH
- The xml Data Type
- Untyped xml
- Typed xml
- The xml Data Type Methods
- The query Method
- The value Method
- The exist Method
- The nodes Method
- The modify Method
- XML Indexes
- XSL Transformations
- SQL CLR Security Settings
- Summary
- Chapter 13: XQuery and XPath
- XPath and FOR XML PATH
- XPath Attributes
- Columns without Names and Wildcards
- Element Grouping
- The data Function
- Node Tests and Functions
- XPath and NULL
- The WITH XMLNAMESPACES Clause
- Node Tests
- XQuery and the xml Data Type
- Expressions and Sequences
- The query Method
- Location Paths
- Node Tests
- Namespaces
- Axis Specifiers
- Dynamic XML Construction
- XQuery Comments
- Data Types
- Predicates
- Value Comparison Operators
- General Comparison Operators
- Xquery Date Format
- Node Comparisons
- Conditional Expressions (if...then...else)
- Arithmetic Expressions
- Integer Division in XQuery
- XQuery Functions
- Constructors and Casting
- FLWOR Expressions
- The for and return Keywords
- The where Keyword
- The order by Keywords
- The let Keyword
- UTF-16 Support
- Summary
- XPath and FOR XML PATH
- Chapter 14: Catalog Views and Dynamic aent Views
- Chapter 15: .NET Client Programming
- ADO.NET
- The .NET SQL Client
- Connected Data Access
- Disconnected Datasets
- Parameterized Queries
- Nonquery, Scalar, and XML Querying
- SqIBulkCopy
- Multiple Active Result Sets
- LINQ to SQL
- Using the Designer
- Querying with LINQ to SQL
- Basic LINQ to SQL Querying
- Deferred Query Execution
- From LINQ to Entity Framework
- Querying Entities
- Summary
- Chapter 16: CLR Integration Programming
- Chapter 17: Data Services
- Chapter 18: Error Handling and Dynamic SQL
- Chapter 19: Performance Tuning
- Appendix A: Exercise Answers
- Appendix B: XQuery Data Types
- Appendix C: Glossary
- ACID
- adjacency list model
- ADO.NET Data Services
- anchor query
- application programming interface (API)
- assembly
- asymmetric encryption
- atomic, list, and union data types
- axis
- Bulk Copy Program (BCP)
- catalog view
- certificate
- check constraint
- closed-world assumption (CWA)
- clustered index
- comment
- computed constructor
- content expression
- context item expression
- context node
- database encryption key
- database master key
- data domain
- data page
- datum
- empty sequence
- entity data model (EDM)
- Extended Events (XEvents)
- extensible key management (EKM)
- extent
- Extract, Transform, Load (ETL)
- facet
- filter expression
- FLWOR expression
- foreign key constraint
- full-text catalog
- full-text index
- full-text search (FTS)
- Functions and Operators (F&O)
- general comparison
- Geography Markup Language (GML)
- grouping set
- hash
- heap
- heterogeneous sequence
- homogenous sequence
- indirect recursion
- inflectional forms
- initialization vector (IV)
- Language Integrated Query (LINQ)
- location path
- logon trigger
- materialized path model
- Multiple Active Result Sets (MARS)
- nested sets model
- node
- node comparison
- node test
- nonclustered index
- object-relational mapping (O/RM)
- open-world assumption (OWA)
- optional occurrence indicator
- parameterization
- path expression
- predicate
- predicate truth value
- primary expression
- query plan
- recompilation
- recursion
- row constructor
- scalar function
- searched CASE expression
- sequence
- server certificate
- service master key (SMK)
- shredding
- simple CASE expression
- SOAP
- spatial data
- spatial index
- SQL Server Data Tools
- SQL injection
- step
- table type
- three-valued logic (3VL)
- transparent data encryption (TDE)
- untyped XML
- user-defined aggregate (UDA)
- user-defined type (UDT)
- value comparison
- well-formed XML
- well-known text (WKT)
- windowing functions
- World Wide Web Consortium (W3C)
- XML
- XML schema
- XPath
- XQuery
- XQuery/XPath Data Model (XDM)
- XSL
- XSLT
- Appendix D: SQLCMD Quick Reference
- Index
ProT-SQLProgrammer’sGuide
Copyright©2015byMiguelCebollero,JayNatarajan,andMichaelColes
Thisworkissubjecttocopyright.AllrightsarereservedbythePublisher,whetherthewholeorpartofthe
materialisconcerned,specificallytherightsoftranslation,reprinting,reuseofillustrations,recitation,
broadcasting,reproductiononmicrofilmsorinanyotherphysicalway,andtransmissionorinformationstorage
andretrieval,electronicadaptation,computersoftware,orbysimilarordissimilarmethodologynowknownor
hereafterdeveloped.Exemptedfromthislegalreservationarebriefexcerptsinconnectionwithreviewsor
scholarlyanalysisormaterialsuppliedspecificallyforthepurposeofbeingenteredandexecutedonacomputer
system,forexclusiveusebythepurchaserofthework.Duplicationofthispublicationorpartsthereofispermitted
onlyundertheprovisionsoftheCopyrightLawofthePublisher’slocation,initscurrentversion,andpermission
forusemustalwaysbeobtainedfromSpringer.PermissionsforusemaybeobtainedthroughRightsLinkatthe
CopyrightClearanceCenter.ViolationsareliabletoprosecutionundertherespectiveCopyrightLaw.
ISBN-13(pbk):978-1-4842-0146-6
ISBN-13(electronic):978-1-4842-0145-9
Trademarkednames,logos,andimagesmayappearinthisbook.Ratherthanuseatrademarksymbolwithevery
occurrenceofatrademarkedname,logo,orimageweusethenames,logos,andimagesonlyinaneditorial
fashionandtothebenefitofthetrademarkowner,withnointentionofinfringementofthetrademark.
Theuseinthispublicationoftradenames,trademarks,servicemarks,andsimilarterms,eveniftheyarenot
identifiedassuch,isnottobetakenasanexpressionofopinionastowhetherornottheyaresubjecttoproprietary
rights.
Whiletheadviceandinformationinthisbookarebelievedtobetrueandaccurateatthedateofpublication,
neithertheauthorsnortheeditorsnorthepublishercanacceptanylegalresponsibilityforanyerrorsoromissions
thatmaybemade.Thepublishermakesnowarranty,expressorimplied,withrespecttothematerialcontained
herein.
ManagingDirector:WelmoedSpahr
LeadEditor:JonathanGennick
TechnicalReviewer:EdgarLanting
EditorialBoard:SteveAnglin,GaryCornell,LouiseCorrigan,JimDeWolf,JonathanGennick,Robert
Hutchinson,MichelleLowman,JamesMarkham,MatthewMoodie,JeffOlson,JeffreyPepper,
DouglasPundick,BenRenow-Clarke,GwenanSpearing,MattWade,SteveWeiss
CoordinatingEditor:JillBalzano
CopyEditor:TiffanyTaylor
Compositor:SPiGlobal
Indexer:SPiGlobal
Artist:SPiGlobal
CoverDesigner:AnnaIshchenko
DistributedtothebooktradeworldwidebySpringerScience+BusinessMediaNewYork,233SpringStreet,6th
Floor,NewYork,NY10013.Phone1-800-SPRINGER,fax(201)348-4505,e-mailorders-ny@springer-
sbm.com,orvisitwww.springeronline.com.
Forinformationontranslations,pleasee-mailrights@apress.com,orvisitwww.apress.com.
ApressandfriendsofEDbooksmaybepurchasedinbulkforacademic,corporate,orpromotionaluse.eBook
versionsandlicensesarealsoavailableformosttitles.Formoreinformation,referenceourSpecialBulkSales–
eBookLicensingwebpageatwww.apress.com/bulk-sales.
Anysourcecodeorothersupplementarymaterialsreferencedbytheauthorinthistextisavailabletoreadersat
www.apress.com/9781430245964.Fordetailedinformationabouthowtolocateyourbook’ssourcecode,
gotowww.apress.com/source-code/.
www.allitebooks.com
ContentsataGlance
AbouttheAuthors
AbouttheTechnicalReviewer
Acknowledgments
Introduction
Chapter1:FoundationsofT-SQL
Chapter2:ToolsoftheTrade
Chapter3:ProceduralCode
Chapter4:User-DefinedFunctions
Chapter5:StoredProcedures
Chapter6:In-MemoryProgramming
Chapter7:Triggers
Chapter8:Encryption
Chapter9:CommonTableExpressionsandWindowingFunctions
Chapter10:DataTypesandAdvancedDataTypes
Chapter11:Full-TextSearch
Chapter12:XML
Chapter13:XQueryandXPath
Chapter14:CatalogViewsandDynamicaentViews
Chapter15:.NETClientProgramming
Chapter16:CLRIntegrationProgramming
Chapter17:DataServices
Chapter18:ErrorHandlingandDynamicSQL
Chapter19:PerformanceTuning
AppendixA:ExerciseAnswers
AppendixB:XQueryDataTypes
AppendixC:Glossary
www.allitebooks.com
Contents
AbouttheAuthors
AbouttheTechnicalReviewer
Acknowledgments
Introduction
Chapter1:FoundationsofT-SQL
AShortHistoryofT-SQL
Imperativevs.DeclarativeLanguages
SQLBasics
Statements
Databases
TransactionLogs
Schemas
Tables
Views
Indexes
StoredProcedures
User-DefinedFunctions
SQLCLRAssemblies
ElementsofStyle
Whitespace
NamingConventions
OneEntry,OneExit
DefensiveCoding
TheSELECT*Statement
VariableInitialization
Summary
Chapter2:ToolsoftheTrade
SQLServerManagementStudio
IntelliSense
CodeSnippets
KeyboardShortcutSchemes
T-SQLDebugging
SSMSEditingOptions
www.allitebooks.com
Context-SensitiveHelp
GraphicalQueryExecutionPlans
Project-ManagementFeatures
TheObjectExplorer
TheSQLCMDUtility
SQLServerDataTools
SQLProfiler
ExtendedEvents
SQLServerIntegrationServices
TheBulkCopyProgram
SQLServer2014BooksOnline
TheAdventureWorksSampleDatabase
Summary
Chapter3:ProceduralCode
Three-ValuedLogic
Control-of-FlowStatements
TheBEGINandENDKeywords
TheIF…ELSEStatement
TheWHILE,BREAK,andCONTINUEStatements
TheGOTOStatement
TheWAITFORStatement
TheRETURNStatement
TheCASEExpression
TheSimpleCASEExpression
TheSearchedCASEExpression
CASEandPivotTables
TheIIFStatement
CHOOSE
COALESCEandNULLIF
Cursors
Summary
Chapter4:User-DefinedFunctions
ScalarFunctions
www.allitebooks.com
RecursioninScalarUser-DefinedFunctions
ProceduralCodeinUser-DefinedFunctions
MultistatementTable-ValuedFunctions
InlineTable-ValuedFunctions
RestrictionsonUser-DefinedFunctions
NondeterministicFunctions
StateoftheDatabase
Summary
Chapter5:StoredProcedures
IntroducingStoredProcedures
MetadataDiscovery
NativelyCompiledStoredProcedures
ManagingStoredProcedures
StoredProceduresBestPractices
StoredProcedureExample
RecursioninStoredProcedures
Table-ValuedParameters
TemporaryStoredProcedures
RecompilationandCaching
StoredProcedureStatistics
ParameterSniffing
Recompilation
Summary
Chapter6:In-MemoryProgramming
TheDriversforIn-MemoryTechnology
HardwareTrends
GettingStartedwithIn-MemoryObjects
Step1:AddaNewMemory-OptimizedDataFILEGROUP
Step2:AddaNewMemory-OptimizedContainer
Step3:CreateYourNewMemory-OptimizedTable
LimitationsonMemory-OptimizedTables
www.allitebooks.com
In-MemoryOLTPTableIndexes
HashIndexes
RangeIndexes
NativelyCompiledStoredProcedures
Chapter7:Triggers
DMLTriggers
MultipleTriggers
WhentoUseDMLTriggers
InsertedandDeletedVirtualTables
AuditingwithDMLTriggers
UsingChangeDataCaptureInstead
SharingDatawithTriggers
NestedandRecursiveTriggers
TheUPDATE()andCOLUMNS_UPDATED()Functions
TriggersonViews
DDLTriggers
DDLEventTypesandEventGroups
LogonTriggers
Summary
Chapter8:Encryption
TheEncryptionHierarchy
ServiceMasterKeys
DatabaseMasterKeys
Certificates
LimitationsofAsymmetricEncryption
AsymmetricKeys
AsymmetricKey“Backups”
SymmetricKeys
TemporarySymmetricKeys
SaltandAuthenticators
EncryptionWithoutKeys
www.allitebooks.com
HashingData
ExtensibleKeyManagement
TransparentDataEncryption
Summary
Chapter9:CommonTableExpressionsandWindowingFunctions
CommonTableExpressions
MultipleCommonTableExpressions
CTEReadabilityBenefits
RecursiveCommonTableExpressions
WindowingFunctions
ROW_NUMBERFunction
QueryPagingwithOFFSET/FETCH
TheRANKandDENSE_RANKFunctions
TheNTILEFunction
AggregateFunctions,AnalyticFunctions,andtheOVERClause
AnalyticFunctionExamples
CUME_DISTandPERCENT_RANK
PERCENTILE_CONTandPERCENTILE_DISC
LAGandLEAD
FIRST_VALUEandLAST_VALUE
Summary
Chapter10:DataTypesandAdvancedDataTypes
BasicDataTypes
Characters
TheMaxDataTypes
Numerics
DateandTimeDataTypes
UTCandMilitaryTime
DateandTimeFunctions
TimeZonesandOffsets
TheUniqueidentifierDataType
TheHierarchyidDataType
RepresentingHierarchicalData
HierarchyidExample
www.allitebooks.com
HierarchyidMethods
SpatialDataTypes
HemisphereandOrientation
MichiganandtheGreatlakes
FILESTREAMSupport
EnablingFILESTREAMSupport
CreatingFILESTREAMFilegroups
FILESTREAM-EnablingTables
AccessingFILESTREAMData
FileTableSupport
FiletableFunctions
TriggersonFiletables
Summary
Chapter11:Full-TextSearch
FTSArchitecture
CreatingFull-TextCatalogsandIndexes
CreatingFull-TextCatalogs
CreatingFull-TextIndexes
Full-TextQuerying
TheFREETEXTPredicate
FTSPerformanceOptimization
TheCONTAINSPredicate
TheFREETEXTTABLEandCONTAINSTABLEFunctions
ThesaurusesandStoplists
StoredProceduresandDynamicManagementViewsandFunctions
StatisticalSemantics
Summary
Chapter12:XML
LegacyXML
OPENXML
OPENXMLResultFormats
FORXMLClause
FORXMLRAW
FORXMLAUTO
FORXMLEXPLICIT
FORXMLPATH
ThexmlDataType
Untypedxml
Typedxml
ThexmlDataTypeMethods
ThequeryMethod
ThevalueMethod
TheexistMethod
ThenodesMethod
ThemodifyMethod
XMLIndexes
XSLTransformations
SQLCLRSecuritySettings
Summary
Chapter13:XQueryandXPath
XPathandFORXMLPATH
XPathAttributes
ColumnswithoutNamesandWildcards
ElementGrouping
ThedataFunction
NodeTestsandFunctions
XPathandNULL
TheWITHXMLNAMESPACESClause
NodeTests
XQueryandthexmlDataType
ExpressionsandSequences
ThequeryMethod
LocationPaths
NodeTests
Namespaces
AxisSpecifiers
DynamicXMLConstruction
XQueryComments
DataTypes
Predicates
ValueComparisonOperators
GeneralComparisonOperators
XqueryDateFormat
NodeComparisons
ConditionalExpressions(if.then.else)
ArithmeticExpressions
IntegerDivisioninXQuery
XQueryFunctions
ConstructorsandCasting
FLWORExpressions
TheforandreturnKeywords
ThewhereKeyword
TheorderbyKeywords
TheletKeyword
UTF-16Support
Summary
Chapter14:CatalogViewsandDynamicaentViews
CatalogViews
TableandColumnMetadata
QueryingPermissions
DynamicManagementViewsandFunctions
IndexMetadata
SessionInformation
ConnectionInformation
CurrentlyExecutingSQL
Memory-OptimizedSystemViews
MostExpensiveQueries
TempdbSpace
ServerResources
UnusedIndexes
WaitStats
INFORMATION_SCHEMAViews
Summary
Chapter15:.NETClientProgramming
ADO.NET
The.NETSQLClient
ConnectedDataAccess
DisconnectedDatasets
ParameterizedQueries
Nonquery,Scalar,andXMLQuerying
SqIBulkCopy
MultipleActiveResultSets
LINQtoSQL
UsingtheDesigner
QueryingwithLINQtoSQL
BasicLINQtoSQLQuerying
DeferredQueryExecution
FromLINQtoEntityFramework
QueryingEntities
Summary
Chapter16:CLRIntegrationProgramming
TheOldWay
TheCLRIntegrationWay
CLRIntegrationAssemblies
User-DefinedFunctions
StoredProcedures
User-DefinedAggregates
CreatingaSimpleUDA
CreatinganAdvancedUDA
CLRIntegrationUser-DefinedTypes
Triggers
Summary
Chapter17:DataServices
SQLServer2014ExpressLocalDB
AsynchronousProgrammingwithADO.NET4.5
ODBCforLinux
JDBC
Service-OrientedArchitectureandWCFDataServices
CreatingaWCFDataService
DefiningtheDataSource
CreatingtheDataService
CreatingaWCFDataServiceConsumer
Summary
Chapter18:ErrorHandlingandDynamicSQL
ErrorHandling
LegacyErrorHandling
TheRAISERRORStatement
Try.CatchExceptionHandling
TRY_PARSE,TRY_CONVERT,andTRY_CAST
ThrowStatement
DebuggingTools
PRINTStatementDebugging
TraceFlags
SSMSIntegratedDebugger
VisualStudioT-SQLDebugger
DynamicSQL
TheEXECUTEStatement
SQLInjectionandDynamicSQL
TroubleshootingDynamicSQL
Thesp_executesqlStoredProcedure
DynamicSQLandScope
Client-SideParameterization
Summary
Chapter19:PerformanceTuning
SQLServerStorage
FilesandFilegroups
SpaceAllocation
Partitions
DataCompression
SparseColumns
Indexes
Heaps
ClusteredIndexes
NonclusteredIndexes
FilteredIndexes
OptimizingQueries
ReadingQueryPlans
Methodology
Waits
ExtendedEvents
Summary
AppendixA:ExerciseAnswers
Chapter1
Chapter2
Chapter3
Chapter4
Chapter5
Chapter6
Chapter7
Chapter8
Chapter9
Chapter10
Chapter11
Chapter12
Chapter13
Chapter14
Chapter15
Chapter16
Chapter17
Chapter18
Chapter19
AppendixB:XQueryDataTypes
AppendixC:Glossary
ACID
adjacencylistmodel
ADO.NETDataServices
anchorquery
applicationprogramminginterface(API)
assembly
asymmetricencryption
atomic,list,anduniondatatypes
axis
BulkCopyProgram(BCP)
catalogview
certificate
checkconstraint
closed-worldassumption(CWA)
clusteredindex
comment
computedconstructor
contentexpression
contextitemexpression
contextnode
databaseencryptionkey
databasemasterkey
datadomain
datapage
datum
emptysequence
entitydatamodel(EDM)
ExtendedEvents(XEvents)
extensiblekeymanagement(EKM)
extent
Extract,Transform,Load(ETL)
facet
filterexpression
FLWORexpression
foreignkeyconstraint
full-textcatalog
full-textindex
full-textsearch(FTS)
FunctionsandOperators(F&O)
generalcomparison
GeographyMarkupLanguage(GML)
groupingset
hash
heap
heterogeneoussequence
homogenoussequence
indirectrecursion
inflectionalforms
initializationvector(IV)
LanguageIntegratedQuery(LINQ)
locationpath
logontrigger
materializedpathmodel
MultipleActiveResultSets(MARS)
nestedsetsmodel
node
nodecomparison
nodetest
nonclusteredindex
object-relationalmapping(O/RM)
open-worldassumption(OWA)
optionaloccurrenceindicator
parameterization
pathexpression
predicate
predicatetruthvalue
primaryexpression
queryplan
recompilation
recursion
rowconstructor
scalarfunction
searchedCASEexpression
sequence
servercertificate
servicemasterkey(SMK)
shredding
simpleCASEexpression
SOAP
spatialdata
spatialindex
SQLServerDataTools
SQLinjection
step
tabletype
three-valuedlogic(3VL)
transparentdataencryption(TDE)
untypedXML
user-definedaggregate(UDA)
user-definedtype(UDT)
valuecomparison
well-formedXML
well-knowntext(WKT)
windowingfunctions
WorldWideWebConsortium(W3C)
XML
XMLschema
XPath
XQuery
XQuery/XPathDataModel(XDM)
XSL
XSLT
AppendixD:SQLCMDQuickReference
Command-LineOptions
ScriptingVariables
www.allitebooks.com
AbouttheAuthors
MiguelCebolleroisfathertotwobeautifulchildren(AvaandAlex),husbandtowife
Sandy,andadatabaseprofessionalwithmorethan16yearsofexperienceintheSQL
Serverandotherdatabaseplatforms.Hehasheldpositionsinmanagement,database
administration,development,architecting,andBIdevelopmentforFortune500
corporationsinthesoftware,reverselogistics,telecommunications,insurance,legal,
professionalsportsandbankingindustries.
Miguelisaregularspeakeratlocalusergroups,regionalSQLSaturdayevents,andthe
nationalProfessionalAssociationforSQLServerUsers(PASS)Summitconferenceon
variousdatabasetopics.Hehasbeenanavidvolunteer,chapterleader,andcontributorto
PASSsince2000andacontributortotheinsuranceindustrystandardsorganization
ACORD.Heisalife-longlearnerwithaBSfromtheUniversityofTampaandanMSc
fromtheUniversityofNorthCarolinaGreensboro.Biking,thebeach,reading,andtime
withfamilyfulfillhislifeoutsideofwork.
JayNatarajanhasmorethan15yearsofexperienceintheSQLServerspace.Herskills
lieinboththedesignandimplementationarenas;shehasarchitectedanddeployed
complexsolutionsforenterprisecustomers.ShejoinedMicrosoftConsultingServicesin
2008.Sheholdsabachelor’sdegreeinmechanicalengineeringfromtheUniversityof
Madras.JaycurrentlylivesinAtlantawithherhusband,Chad,andtheirson,Satya.
MichaelColeshasmorethanadecade’sworthofexperiencedesigningandadministering
SQLServerdatabases.HeisaprolificwriterofarticlesonallaspectsofSQLServer,
particularlyontheexpertuseofT-SQL,andheholdsMCDBAandMCPcertifications.He
graduatedmagnacumlaudewithabachelor’sdegreeininformationtechnologyfrom
AmericanIntercontinentalUniversityinGeorgia.AmemberoftheUnitedStatesArmy
Reserve,hewasactivatedfortwoyearsfollowing9/11.
AbouttheTechnicalReviewer
EdgarLantingisacertifiedOracleandMicrosoftSQLServerdatabaseengineerandis
currentlyworkingatthefile-transfercompanyWeTransfer(www.wetransfer.com),
whereheisresponsibleforallthingsrelatedtodatabases.HehasworkedinITforover19
years;hestartedasasystemadministratorandmadethechangetoDBAmorethan15
yearsago.He’scurrentlylivinginasmallDutchvillagewithhiswifeandtwodogs.
Acknowledgments
Iwouldliketothankmywifeforhersupportduringtheweekendsandnightswhenshe
tookcareofthebedtimeroutinewiththekidssothatIcouldfinishthisbook.Myfamily
hasbeenthebiggestdriverofwhyIwanttobecomebettereverydayinmycareer.I
wouldliketothankLouisDavidsonforintroducingmetotheApressteamandhis
publisher,JonathanGennick.Iamgratefulforthisopportunityasafirst-timewriter.Many
thankstotheSQLprofessionalsinmycommunitywhogavemewordsofencouragement
onthisjourney.
—MiguelE.Cebollero
Introduction
Inthemid-1990s,whenMicrosoftpartedwayswithSybaseintheirconjointdevelopment
ofSQLServer,itwasanentirelydifferentproduct.WhenSQLServer6.5wasreleasedin
1996,itwasstartingtogaincredibilityasanenterprise-classdatabaseserver.Itstillhad
roughmanagementtools,onlycorefunctionalities,andsomelimitationsthatareforgotten
today,likefixed-sizedevicesandtheinabilitytodroptablecolumns.Itfunctionedasa
rudimentarydatabaseserver:storingandretrievingdataforclientapplications.Therewas
alreadyplentyforanyonenewtotherelationaldatabaseworldtolearn.Newcomershadto
understandmanyconcepts,suchasforeignkeys,storedprocedures,triggers,andthe
dedicatedlanguage,T-SQL(whichcouldbeabafflingexperience—writingSELECT
queriessometimesinvolvesalotofhead-scratching).Evenwhendevelopersmasteredall
that,theystillhadtokeepupwiththeadditionsMicrosoftmadetothedatabaseengine
witheachnewversion.Someofthechangeswerenotforthefaintofheart,like.NET
databasemodules,supportforXMLandtheXQuerylanguage,andafullimplementation
ofsymmetricandasymmetricencryption.Theseadditionsaretodaycorecomponentsof
SQLServer.
Becausearelationaldatabasemanagementserver(RDBMS)likeSQLServerisoneof
themostimportantelementsoftheITenvironment,youneedtomakethebestofit,which
impliesagoodunderstandingofitsmoreadvancedfeatures.Wehavedesignedthisbook
withthegoalofhelpingT-SQLdevelopersgettheabsolutemostoutofthedevelopment
featuresandfunctionalityinSQLServer2014.Wecoverallofwhat’sneededtomasterT-
SQLdevelopment,fromusingmanagementanddevelopmenttoolstoperformancetuning.
WehopeyouenjoythebookandthatithelpsyoutobecomeaproSQLServer2014
developer.
WhomThisBookIsFor
ThisbookisintendedforSQLServerdeveloperswhoneedtoportcodefromprior
versionsofSQLServer,andthosewhowanttogetthemostoutofdatabasedevelopment
onthe2014release.YoushouldhaveaworkingknowledgeofSQL,preferablyT-SQLon
SQLServer2005orlater,becausemostoftheexamplesinthisbookarewritteninT-SQL.
ThebookcoverssomeofthebasicsofT-SQL,includingintroductoryconceptslikedata
domainandthree-valuedlogic,butthisisn’tabeginner’sbook.Wedon’tdiscussdatabase
design,databasearchitecture,normalization,andthemostbasicSQLconstructsinany
detail.Apressoffersabeginner’sguidetoT-SQL2012thatcoversmorebasicSQL
constructs.
WefocushereonadvancedSQLServer2014functionalities,andsoweassumeyou
haveabasicunderstandingofSQLstatementslikeINSERTandSELECT.Aworking
knowledgeofC#andthe.NETFrameworkisalsouseful(butnotrequired),becausetwo
chaptersarededicatedto.NETclientprogrammingand.NETdatabaseintegration.
SomeexamplesinthebookarewritteninC#.WhenC#samplecodeisprovided,it’s
explainedindetail,soanin-depthknowledgeofthe.NETFrameworkclasslibraryisn’t
required.
HowThisBookIsStructured
Thisbookwaswrittentoaddresstheneedsoffourtypesofreaders:
SQLdeveloperswhoarecomingfromotherplatformstoSQLServer
2014
SQLdeveloperswhoaremovingfrompriorversionsofSQLServerto
SQLServer2014
SQLdeveloperswhohaveaworkingknowledgeofbasicT-SQL
programmingandwanttolearnaboutadvancedfeatures
Databaseadministratorsandnon-developerswhoneedaworking
knowledgeofT-SQLfunctionalitytoeffectivelysupportSQLServer
2014instances
Foralltypesofreaders,thisbookisdesignedtoactasatutorialthatdescribesand
demonstratesT-SQLfeatureswithworkingexamples,andasareferenceforquickly
locatingdetailsaboutspecificfeatures.Thefollowingsectionsprovideachapter-by-
chapteroverview.
Chapter1
Chapter1startsthisbookbyputtingSQLServer2014’simplementationofT-SQLin
context,includingashorthistory,adiscussionofthebasics,andanoverviewofT-SQL
codingbestpractices.
Chapter2
Chapter2givesanoverviewofthetoolsthatarepackagedwithSQLServerandavailable
toSQLServerdevelopers.ToolsdiscussedincludeSQLServerManagementStudio
(SSMS),SQLCMD,SQLServerDataTools(SSDT),andSQLProfiler,amongothers.
Chapter3
Chapter3introducesT-SQLproceduralcode,includingcontrol-of-flowstatementslike
IF…THENandWHILE.ThischapteralsodiscussesCASEexpressionsandCASE-derived
functions,andprovidesanin-depthdiscussionofSQLthree-valuedlogic.
Chapter4
Chapter4discussesthevarioustypesofT-SQLuser-definedfunctionsavailableto
encapsulateT-SQLlogicontheserver.WetalkaboutallformsofT-SQL–baseduser-
definedfunctions,includingscalaruser-definedfunctions,inlinetable-valuedfunctions,
andmultistatementtable-valuedfunctions.
Chapter5
Chapter5coversstoredprocedures,whichallowyoutocreateserver-sideT-SQL
subroutines.InadditiontodescribinghowtocreateandexecutestoredproceduresonSQL
Server,wealsoaddressanissuethatisthornyforsome:whyyoumightwanttousestored
procedures.
Chapter6
Chapter6coversthelatestfeaturesavailableinSQLServer2014:In-MemoryOLTP
tables.TheIn-Memoryfeaturesprovidethecapabilitytodramaticallyincreasethe
databaseperformanceofanOLTPordata-warehouseinstance.Withthenewfeaturesalso
comesomelimitations.
Chapter7
Chapter7introducesallthreetypesofSQLServertriggers:classicDMLtriggers,which
fireinresponsetoDMLstatements;DDLtriggers,whichfireinresponsetoserverand
databaseDDLevents;andlogontriggers,whichfireinresponsetoserverLOGONevents.
Chapter8
Chapter8discussesSQLServerencryption,includingthecolumn-levelencryption
functionalityintroducedinSQLServer2005andthenewertransparentdatabase
encryption(TDE)andextensiblekeymanagement(EKM)functionality,bothintroduced
inSQLServer2008.
Chapter9
Chapter9divesintothedetailsofcommontableexpressions(CTEs)andwindowing
functionsinSQLServer2014,whichfeaturesomeimprovementstotheOVERclauseto
achieverow-levelrunningandslidingaggregations.
Chapter10
Chapter10discussesT-SQLdatatypes:firstsomeimportantthingstoknowaboutbasic
datatypes,suchashowtohandledateandtimeinyourcode,andthenadvanceddata
typesandfeatures,suchasthehierarchyidcomplextypeandFILESTREAMand
filetablefunctionality.
Chapter11
Chapter11coversthefull-textsearch(FTS)featureandadvancementsmadesinceSQL
Server2008,includinggreaterintegrationwiththeSQLServerqueryengineandgreater
transparencybywayofFTS-specificdata-managementviewsandfunctions.
Chapter12
Chapter12providesanin-depthdiscussionofSQLServer2014XMLfunctionality,which
carriesforwardandimproveonthenewfeaturesintroducedinSQLServer2005.We
coverseveralXML-relatedtopicsinthischapter,includingthexmldatatypeanditsbuilt-
inmethods,theFORXMLclause,andXMLindexes.
Chapter13
Chapter13discussesXQueryandXPathsupportinSQLServer2014,including
improvementsontheXQuerysupportintroducedinSQLServer2005,suchassupportfor
thexmldatatypeinXMLDMLinsertstatementsandtheletclauseinFLWOR
expressions.
Chapter14
Chapter14introducesSQLServercatalogviews,whicharethepreferredtoolsfor
retrievingdatabaseanddatabaseobjectmetadata.Thischapteralsodiscussesdynamic-
managementviewsandfunctions,whichprovideaccesstoserveranddatabasestate
information.
Chapter15
Chapter15coversSQLCLRIntegrationfunctionalityinSQLServer2014.Inthischapter,
wediscussandprovideexamplesofSQLCLRstoredprocedures,user-definedfunctions,
user-definedtypes,anduser-definedaggregates.
Chapter16
Chapter16focusesonclient-sidesupportforSQLServer,includingADO.NET-based
connectivityandthenewestMicrosoftobject-relationalmapping(ORM)technology,
EntityFramework4.
Chapter17
Chapter17discussesSQLServerconnectivityusingmiddle-tiertechnologies.Because
nativeHTTPendpointshavebeendeprecatedsinceSQLServer2008,wediscussthemas
itemsthatmayneedtobesupportedinexistingdatabasesbutshouldn’tbeusedfornew
development.Wefocusinsteadonpossiblereplacementtechnologies,suchasADO.NET
dataservicesandIIS/.NETwebservices.
Chapter18
Chapter18discussesimprovementstoserver-sideerrorhandlingmadepossiblewiththe
TRY…CATCHblock.Wealsodiscussvariousmethodsfordebuggingcode,includingusing
theVisualStudioT-SQLdebugger.Thischapterwrapsupwithadiscussionofdynamic
SQLandSQLinjection,includingthecausesofSQLinjectionandmethodsyoucanuseto
protectyourcodeagainstthistypeofattack.
Chapter19
Chapter19providesanoverviewofperformance-tuningSQLServercode.Thischapter
discussesSQLServerstorage,indexingmechanisms,andqueryplans.Weendthechapter
withadiscussionofaprovenmethodologyfortroubleshootingT-SQLperformanceissues.
AppendixA
AppendixAprovidestheanswerstotheexercisequestionsincludedattheendofeach
chapter.
AppendixB
AppendixBisdesignedasaquickreferencetotheXQueryDataModel(XDM)type
system.
AppendixC
AppendixCprovidesaquickreferenceglossarytoseveralterms,manyofwhichmaybe
newtothoseusingSQLServerforthefirsttime.
AppendixD
AppendixDisaquickreferencetotheSQLCMDcommand-linetool,whichallowsyouto
executeadhocT-SQLstatementsandbatchesinteractively,orrunscriptfiles.
Conventions
Tohelpmakereadingthisbookamoreenjoyableexperience,andtohelpyougetasmuch
outofitaspossible,we’veusedthefollowingstandardizedformattingconventions
throughout.
C#codeisshownincodefont.NotethatC#codeiscasesensitive.Here’sanexample:
while(i<10)
T-SQLsourcecodeisalsoshownincodefont,withkeywordscapitalized.Notethat
we’velowercasedthedatatypesintheT-SQLcodetohelpimprovereadability.Here’san
example:
DECLARE@xxml;
XMLcodeisshownincodefontwithattributeandelementcontentinboldfor
readability.
Somecodesamplesandresultshavebeenreformattedinthebookforeasierreading.
XMLignoreswhitespace,sothesignificantcontentoftheXMLhasnotbeenaltered.
Here’sanexample:
<bookpublisher="Apress">ProSQLServer2014XML</book>:
NoteNotes,tips,andwarningsaredisplayedlikethis,inaspecialfontwithsolidbars
placedoverandunderthecontent.
SIDEBARS
Sidebarsincludeadditionalinformationrelevanttothecurrentdiscussionandother
interestingfacts.Sidebarsareshownonagraybackground.
Prerequisites
ThisbookrequiresaninstallationofSQLServer2014toruntheT-SQLsamplecode
provided.Notethatthecodeinthisbookhasbeenspecificallydesignedtotakeadvantage
ofSQLServer2014features,andsomeofthecodesampleswon’trunonpriorversionsof
SQLServer.Thecodesamplespresentedinthebookaredesignedtoberunagainstthe
AdventureWorks2014andSQLServer2014In-MemoryOLTPsampledatabases,
availablefromtheCodePlexwebsiteat
www.codeplex.com/MSFTDBProdSamples.Thedatabasenameusedinthe
samplesisnotAdventureWorks2014,butAdventureWorksor2014In-Memory,forthe
sakeofsimplicity.
Ifyou’reinterestedincompilinganddeployingthe.NETcodesamples(theclientcode
andSQLCLRexamples)presentedinthebook,wehighlyrecommendaninstallationof
VisualStudio2010oralaterversion.Althoughyoucancompileanddeploy.NETcode
fromthecommandline,we’veprovidedinstructionsfordoingsothroughtheVisual
StudioIntegratedDevelopmentEnvironment(IDE).WefindthattheIDEprovidesamuch
moreenjoyableexperience.
Someexamples,suchastheADO.NETDataServicesexamplesinChapter16,require
aninstallationofInternetInformationServer(IIS)aswell.Othercodesamplespresented
inthebookmayhavespecificrequirements,suchastheEntityFramework4samples,
whichrequirethe.NETFramework3.5.We’veaddednotestocodesamplesthathave
additionalrequirementslikethese.
www.allitebooks.com
ApressWebSite
Visitthisbook’sapress.comwebpageatwww.apress.com/9781484201466forthe
completesamplecodedownloadforthisbook.It’scompressedinazipfileandstructured
sothateachsubdirectorycontainsallthesamplecodeforitscorrespondingchapter.
WeandtheApressteamhavemadeeveryefforttoensurethatthisbookisfreefrom
errorsanddefects.Unfortunately,theoccasionalerrordoesslippastus,despiteourbest
efforts.Intheeventthatyoufindanerrorinthebook,pleaseletusknow!Youcansubmit
errorstoApressbyvisitingwww.apress.com/9781484201466andfillingoutthe
formontheErratatab.
CHAPTER1
FoundationsofT-SQL
SQLServer2014isthelatestreleaseofMicrosoft’senterprise-classdatabasemanagement
system(DBMS).Asthenameimplies,aDBMSisatooldesignedtomanage,secure,and
provideaccesstodatastoredinstructuredcollectionsindatabases.Transact-SQL(T-SQL)
isthelanguagethatSQLServerspeaks.T-SQLprovidesqueryanddata-manipulation
functionality,datadefinitionandmanagementcapabilities,andsecurityadministration
toolstoSQLServerdevelopersandadministrators.TocommunicateeffectivelywithSQL
Server,youmusthaveasolidunderstandingofthelanguage.Inthischapter,youbegin
exploringT-SQLonSQLServer2014.
AShortHistoryofT-SQL
ThehistoryofStructuredQueryLanguage(SQL),anditsdirectdescendantTransact-SQL
(T-SQL),beginswithaman.Specifically,itallbeganin1970whenDr.E.F.Codd
publishedhisinfluentialpaper“ARelationalModelofDataforLargeSharedDataBanks”
intheCommunicationsoftheAssociationforComputingMachinery(ACM).Inhis
seminalpaper,Dr.Coddintroducedthedefinitivestandardforrelationaldatabases.IBM
wentontocreatethefirstrelationaldatabasemanagementsystem,knownasSystemR.It
subsequentlyintroducedtheStructuredEnglishQueryLanguage(SEQUEL,asitwas
knownatthetime)tointeractwiththisearlydatabasetostore,modify,andretrievedata.
ThenameofthisearlyquerylanguagewaslaterchangedfromSEQUELtothenow-
commonSQLduetoatrademarkissue.
Fast-forwardto1986,whentheAmericanNationalStandardsInstitute(ANSI)
officiallyapprovedthefirstSQLstandard,commonlyknownastheANSISQL-86
standard.TheoriginalversionsofMicrosoftSQLServersharedacommoncodebasewith
theSybaseSQLServerproduct.ThischangedwiththereleaseofSQLServer7.0,when
Microsoftpartiallyrewrotethecodebase.Microsofthassinceintroducedseveral
iterationsofSQLServer,includingSQLServer2000,SQLServer2005,SQLServer
2008,SQL2008R2,SQL2012,andnowSQLServer2014.ThisbookfocusesonSQL
Server2014,whichfurtherextendsthecapabilitiesofT-SQLbeyondwhatwaspossiblein
previousreleases.
Imperativevs.DeclarativeLanguages
SQLisdifferentfrommanycommonprogramminglanguagessuchasC#andVisualBasic
becauseit’sadeclarativelanguage.Incontrast,languagessuchasC++,VisualBasic,C#,
andevenassemblerlanguageareimperativelanguages.Theimperativelanguagemodel
requirestheusertodeterminewhattheendresultshouldbeandtellthecomputerstepby
stephowtoachievethatresult.It’sanalogoustoaskingacabdrivertodriveyoutothe
airportandthengivingthedriverturn-by-turndirectionstogetthere.Declarative
languages,ontheotherhand,allowyoutoframeyourinstructionstothecomputerin
termsoftheendresult.Inthismodel,youallowthecomputertodeterminethebestroute
toachieveyourobjective,analogoustotellingthecabdrivertotakeyoutotheairportand
trustingthemtoknowthebestroute.Thedeclarativemodelmakesalotofsensewhenyou
considerthatSQLServerisprivytoalotof“insideinformation.”Justlikethecabdriver
whoknowstheshortcuts,trafficconditions,andotherfactorsthataffectyourtrip,SQL
Serverinherentlyknowsseveralmethodstooptimizeyourqueriesanddata-manipulation
operations.
ConsiderListing1-1,whichisasimpleC#codesnippetthatreadsinaflatfileof
namesanddisplaysthemonthescreen.
Listing1-1.C#SnippettoReadaFlatFile
StreamReadersr=newStreamReader("c:\\Person_Person.txt");
stringFirstName=null;
while((FirstName=sr.ReadLine())!=null){
Console.WriteLine(s);}sr.Dispose();
Theexampleperformsthefollowingfunctionsinanorderlyfashion:
1. Thecodeexplicitlyopensthestorageforinput(inthisexample,a
flatfileisusedasa“database”).
2. Itreadsineachrecord(onerecordperline),explicitlycheckingfor
theendofthefile.
3. Asitreadsthedata,thecodereturnseachrecordfordisplayusing
Console.Writeline().
4. Finally,itclosesanddisposesoftheconnectiontothedatafile.
Considerwhathappenswhenyouwanttoaddanametoordeleteanamefromthe
flat-file“database.”Inthosecases,youmustextendthepreviousexampleandaddcustom
routinestoexplicitlyreorganizeallthedatainthefilesothatitmaintainsproperordering.
Ifyouwantthenamestobelistedandretrievedinalphabetical(oranyother)order,you
mustwriteyourownsortroutinesaswell.Anytypeofadditionalprocessingonthedata
requiresthatyouimplementseparateproceduralroutines.
TheSQLequivalentoftheC#codeinListing1-1mightlooksomethinglikeListing1-
2.
Listing1-2.SQLQuerytoRetrieveNamesfromaTable
SELECTFirstNameFROMPerson.Person;
TipUnlessotherwisespecified,youcanrunalltheT-SQLsamplesinthisbookinthe
AdventureWorks2014orSQL2014In-MemorysampledatabaseusingSQLServer
ManagementStudioorSQLCMD.
Tosortyourdata,youcansimplyaddanORDERBYclausetotheSELECTqueryin
Listing1-2.Withproperlydesignedandindexedtables,SQLServercanautomatically
reorganizeandindexyourdataforefficientretrievalafteryouinsert,update,ordelete
rows.
T-SQLincludesextensionsthatallowyoutouseproceduralsyntax.Infact,youcould
rewritethepreviousexampleasacursortocloselymimictheC#samplecode.These
extensionsshouldbeusedwithcare,however,becausetryingtoforcetheimperative
modelonT-SQLeffectivelyoverridesSQLServer’sbuilt-inoptimizations.Moreoften
thannot,thishurtsperformanceandmakessimpleprojectsalotmorecomplexthanthey
needtobe.
OneofthegreatassetsofSQLServeristhatyoucaninvokeitspower,initsnative
language,fromnearlyanyotherprogramminglanguage.Forexample,in.NETyoucan
connecttoSQLServerandissueSQLqueriesandT-SQLstatementstoitviathe
System.Data.SqlClientnamespace,whichisdiscussedfurtherinChapter16.This
givesyoutheopportunitytocombineSQL’sdeclarativesyntaxwiththestrictcontrolofan
imperativelanguage.
SQLBasics
BeforeyoulearnaboutdevelopmentsinT-SQL,oronanySQL-basedplatformforthat
matter,let’smakesurewe’respeakingthesamelanguage.Fortunately,SQLcanbe
describedaccuratelyusingwell-definedandtime-testedconceptsandterminology.Let’s
beginthediscussionofthecomponentsofSQLbylookingatstatements.
Statements
Tobeginwith,inSQLyouusestatementstocommunicateyourrequirementstothe
DBMS.Astatementiscomposedofseveralparts,asshowninFigure1-1.
Figure1-1.ComponentsofaSQLstatement
Asyoucanseeinthefigure,SQLstatementsarecomposedofoneormoreclauses,
someofwhichmaybeoptionaldependingonthestatement.IntheSELECTstatement
shown,therearethreeclauses:theSELECTclause,whichdefinesthecolumnstobe
returnedbythequery;theFROMclause,whichindicatesthesourcetableforthequery;
andtheWHEREclause,whichisusedtolimittheresults.Eachclauserepresentsa
primitiveoperationintherelationalalgebra.Forinstance,intheexample,theSELECT
clauserepresentsarelationalprojectionoperation,theFROMclauseindicatestherelation,
andtheWHEREclauseperformsarestrictionoperation.
NoteTherelationalmodelofdatabasesisthemodelformulatedbyDr.E.F.Codd.In
therelationalmodel,whatareknowninSQLastablesarereferredtoasrelations;hence
thename.Relationalcalculusandrelationalalgebradefinethebasisofquerylanguages
fortherelationalmodelinmathematicalterms.
ORDEROFEXECUTION
UnderstandingthelogicalorderinwhichSQLclausesareappliedwithinastatement
orqueryisimportantwhensettingyourexpectationsaboutresults.Althoughvendors
arefreetophysicallyperformwhateveroperations,inanyorder,thattheychooseto
fulfillaqueryrequest,theresultsmustbethesameasiftheoperationswereapplied
inastandards-definedorder.
TheWHEREclauseintheexamplecontainsapredicate,whichisalogicalexpression
thatevaluatestooneofSQL’sthreepossiblelogicalresults:true,false,orunknown.In
thiscase,theWHEREclauseandthepredicatelimittheresultstoonlyrowsinwhich
ContactIdequals1.
TheSELECTclauseincludesanexpressionthatiscalculatedduringstatement
execution.Intheexample,theexpressionEmailPromotion*10isused.This
expressioniscalculatedforeveryrowoftheresultset.
SQLTHREE-VALUEDLOGIC
SQLinstitutesalogicsystemthatmayseemforeigntodeveloperscomingfromother
languageslikeC++orVisualBasic(ormostotherprogramminglanguages,forthat
matter).Mostmoderncomputerlanguagesusesimpletwo-valuedlogic:aBoolean
resultiseithertrueorfalse.SQLsupportstheconceptofNULL,whichisa
placeholderforamissingorunknownvalue.Thisresultsinamorecomplexthree-
valuedlogic(3VL).
Let’slookataquickexampletodemonstrate.IfIaskedyou,“Isxlessthan10?”your
firstresponsemightbealongthelinesof,“Howmuchisx?”IfIrefusedtotellyou
whatvaluexstoodfor,youwouldhavenoideawhetherxwaslessthan,equalto,or
greaterthan10;sotheanswertothequestionisneithertruenorfalse—it’sthethird
truthvalue,unknown.NowreplacexwithNULL,andyouhavetheessenceofSQL
3VL.NULLinSQLisjustlikeavariableinanequationwhenyoudon’tknowthe
variable’svalue.
Nomatterwhattypeofcomparisonyouperformwithamissingvalue,orwhichother
valuesyoucomparethemissingvalueto,theresultisalwaysunknown.The
discussionofSQL3VLcontinuesinChapter3.
ThecoreofSQLisdefinedbystatementsthatperformfivemajorfunctions:querying
datastoredintables,manipulatingdatastoredintables,managingthestructureoftables,
controllingaccesstotables,andmanagingtransactions.ThesesubsetsofSQLaredefined
following:
Querying:TheSELECTquerystatementiscomplex.Ithasmore
optionalclausesandvendor-specifictweaksthananyotherstatement.
SELECTisconcernedsimplywithretrievingdatastoredinthe
database.
DataManipulationLanguage(DML):DMLisconsidereda
sublanguageofSQL.It’sconcernedwithmanipulatingdatastoredin
thedatabase.DMLconsistsoffourcommonlyusedstatements:
INSERT,UPDATE,DELETE,andMERGE.DMLalsoencompasses
cursor-relatedstatements.Thesestatementsallowyoutomanipulate
thecontentsoftablesandpersistthechangestothedatabase.
DataDefinitionLanguage(DDL):DDLisanothersublanguageof
SQL.TheprimarypurposeofDDListocreate,modify,andremove
tablesandotherobjectsfromthedatabase.DDLconsistsofvariations
oftheCREATE,ALTER,andDROPstatements.
DataControlLanguage(DCL):DCLisyetanotherSQLsublanguage.
DCL’sgoalistoallowyoutorestrictaccesstotablesanddatabase
objects.It’scomposedofvariousGRANTandREVOKEstatementsthat
allowordenyusersaccesstodatabaseobjects.
TransactionalControlLanguage(TCL):TCListheSQLsublanguage
thatisconcernedwithinitiatingandcommittingorrollingback
transactions.Atransactionisbasicallyanatomicunitofwork
performedbytheserver.TCLcomprisestheBEGINTRANSACTION,
COMMIT,andROLLBACKstatements.
Databases
ASQLServerinstance—anindividualinstallationofSQLServerwithitsownports,
logins,anddatabases—canmanagemultiplesystemdatabasesanduserdatabases.SQL
Serverhasfivesystemdatabases,asfollows:
resource:Theresourcedatabaseisaread-onlysystemdatabase
thatcontainsallsystemobjects.Youdon’tseetheresource
databaseintheSQLServerManagementStudio(SSMS)Object
Explorerwindow,butthesystemobjectspersistedintheresource
databaselogicallyappearineverydatabaseontheserver.
master:Themasterdatabaseisaserver-widerepositoryfor
configurationandstatusinformation.Itmaintainsinstance-wide
metadataaboutSQLServeraswellasinformationaboutalldatabases
installedonthecurrentinstance.It’swisetoavoidmodifyingoreven
accessingthemasterdatabasedirectlyinmostcases.Anentire
servercanbebroughttoitskneesifthemasterdatabaseis
corrupted.Ifyouneedtoaccesstheserverconfigurationandstatus
information,usecatalogviewsinstead.
model:Themodeldatabaseisusedasthetemplatefromwhich
newlycreateddatabasesareessentiallycloned.Normally,youwon’t
wanttochangethisdatabaseinproductionsettingsunlessyouhavea
veryspecificpurposeinmindandareextremelyknowledgeableabout
thepotentialimplicationsofchangingthemodeldatabase.
msdb:Themsdbdatabasestoressystemsettingsandconfiguration
informationforvarioussupportservices,suchasSQLAgentand
DatabaseMail.Normally,youusethesuppliedstoredproceduresand
viewstomodifyandaccessthisdata,ratherthanmodifyingitdirectly.
tempdb:ThetempdbdatabaseisthemainworkingareaforSQL
Server.WhenSQLServerneedstostoreintermediateresultsof
queries,forinstance,they’rewrittentotempdb.Also,whenyou
createtemporarytables,they’reactuallycreatedintempdb.The
tempdbdatabaseisreconstructedfromscratcheverytimeyourestart
SQLServer.
Microsoftrecommendsthatyouusethesystem-providedstoredproceduresand
catalogviewstomodifysystemobjectsandsystemmetadata,andletSQLServermanage
thesystemdatabases.Youshouldavoidmodifyingthecontentsandstructureofthesystem
databasesdirectlythroughadhocT-SQL.Onlymodifythesystemobjectsandmetadata
byexecutingthesystemstoredproceduresandfunctions.
Userdatabasesarecreatedbydatabaseadministrators(DBAs)anddevelopersonthe
server.Thesetypesofdatabasesaresocalledbecausetheycontainuserdata.The
AdventureWorks2014sampledatabaseisoneexampleofauserdatabase.
TransactionLogs
EverySQLServerdatabasehasitsownassociatedtransactionlog.Thetransactionlog
providesrecoverabilityintheeventoffailureandensurestheatomicityoftransactions.
Thetransactionlogaccumulatesallchangestothedatabasesothatdatabaseintegritycan
bemaintainedintheeventofanerrororotherproblem.Becauseofthisarrangement,all
SQLServerdatabasesconsistofatleasttwofiles:adatabasefilewithan.mdfextension
andatransactionlogwithan.ldfextension.
THEADVENTUREWORKS2014CIDTEST
SQLfolks,andITprofessionalsingeneral,lovetheiracronyms.Acommonacronym
intheSQLworldisACID,whichstandsfor“atomicity,consistency,isolation,
durability.”Thesefourwordsformasetofpropertiesthatdatabasesystemsshould
implementtoguaranteereliabilityofdatastorage,processing,andmanipulation:
Atomicity :Alldatachangesshouldbetransactionalinnature.Thatis,
datachangesshouldfollowanall-or-nothingpattern.Theclassic
exampleisadouble-entrybookkeepingsysteminwhicheverydebit
hasanassociatedcredit.Recordingadebit-and-creditdoubleentryin
thedatabaseisconsideredonetransaction,orasingleunitofwork.
Youcan’trecordadebitwithoutrecordingitsassociatedcredit,and
viceversa.Atomicityensuresthateithertheentiretransactionis
performedornoneofitis.
Consistency:Onlydatathatisconsistentwiththerulessetupinthe
databaseisstored.Datatypesandconstraintscanhelpenforce
consistencyinthedatabase.Forinstance,youcan’tinsertthename
Meghaninanintegercolumn.Consistencyalsoapplieswhen
dealingwithdataupdates.Iftwousersupdatethesamerowofatable
atthesametime,aninconsistencycouldoccurifoneupdateisonly
partiallycompletewhenthesecondupdatebegins.Theconceptof
isolation,describedinthefollowingbulletpoint,isdesignedtodeal
withthissituation.
Isolation:Multiplesimultaneousupdatestothesamedatashouldnot
interferewithoneanother.SQLServerincludesseverallocking
mechanismsandisolationlevelstoensurethattwouserscan’tmodify
theexactsamedataattheexactsametime,whichcouldputthedatain
aninconsistentstate.Isolationalsopreventsyoufromevenreading
uncommitteddatabydefault.
Durability:Datathatpassesalltheprevioustestsiscommittedtothe
database.Theconceptofdurabilityensuresthatcommitteddataisn’t
lost.Thetransactionloganddatabackupandrecoveryfeatureshelpto
ensuredurability.
ThetransactionlogisoneofthemaintoolsSQLServerusestoenforcetheACID
conceptwhenstoringandmanipulatingdata.
Schemas
SQLServer2014supportsdatabaseschemas,whicharelogicalgroupingsbytheownerof
databaseobjects.TheAdventureWorks2014sampledatabase,forinstance,contains
severalschemas,suchasHumanResources,Person,andProduction.These
schemasareusedtogrouptables,storedprocedures,views,anduser-definedfunctions
(UDFs)formanagementandsecuritypurposes.
TipWhenyoucreatenewdatabaseobjects,liketables,anddon’tspecifyaschema,
they’reautomaticallycreatedinthedefaultschema.Thedefaultschemaisnormallydbo,
butDBAsmayassigndifferentdefaultschemastodifferentusers.Becauseofthis,it’s
alwaysbesttospecifytheschemanameexplicitlywhencreatingdatabaseobjects.
Tables
SQLServersupportsseveraltypesofobjectsthatcanbecreatedinadatabase.SQLstores
andmanagesdatainitsprimarydatastructures:tables.Atableconsistsofrowsand
columns,withdatastoredattheintersectionsoftheserowsandcolumns.Asanexample,
theAdventureWorksHumanResources.DepartmenttableisshowninFigure1-2.In
SQLServer2014,younowhavetheoptionofcreatingatableIn-Memory.Thisfeature
allowsallthetabledatatobestoredinmemoryandcanbeaccessedwithextremelylow
latency.
Figure1-2.HumanResources.Departmenttable
Inthetable,eachrowisassociatedwithcolumnsandeachcolumnhascertain
restrictionsplacedonitscontent.Theserestrictionsformthedatadomain.Thedata
domaindefinesallthevaluesacolumncancontain.Atthelowestlevel,thedatadomainis
basedonthedatatypeofthecolumn.Forinstance,asmallintcolumncancontainany
integervaluesbetween-32,768and+32,767.
Thedatadomainofacolumncanbefurtherconstrainedthroughtheuseofcheck
constraints,triggers,andforeignkeyconstraints.Checkconstraintsprovideameansof
automaticallycheckingthatthevalueofacolumniswithinacertainrangeorequaltoa
certainvaluewheneverarowisinsertedorupdated.Triggerscanprovidefunctionality
similartothatofcheckconstraints.Foreignkeyconstraintsallowyoutodeclarea
relationshipbetweenthecolumnsofonetableandthecolumnsofanothertable.Youcan
useforeignkeyconstraintstorestrictthedatadomainofacolumntoincludeonlythose
valuesthatappearinadesignatedcolumnofanothertable.
RESTRICTINGTHEDATADOMAIN:ACOMPARISON
Thissectionhasgivenabriefoverviewofthreemethodsofconstrainingthedata
domainforacolumn.Eachmethodrestrictsthevaluesthatcanbecontainedinthe
column.Here’saquickcomparisonofthethreemethods:
ForeignkeyconstraintsallowSQLServertoperformanautomatic
checkagainstanothertabletoensurethatthevaluesinagivencolumn
existinthereferencedtable.Ifthevalueyou’retryingtoupdateor
insertinatabledoesn’texistinthereferencedtable,anerrorisraised
andanychangesarerolledback.Theforeignkeyconstraintprovidesa
flexiblemeansofalteringthedatadomain,becauseaddingvaluesto
orremovingthemfromthereferencedtableautomaticallychangesthe
datadomainforthereferencingtable.Also,foreignkeyconstraints
offeranadditionalfeatureknownascascadingdeclarativereferential
integrity(DRI),whichautomaticallyupdatesordeletesrowsfroma
referencingtableifanassociatedrowisremovedfromthereferenced
table.
Checkconstraintsprovideasimple,efficient,andeffectivetoolfor
ensuringthatthevaluesbeinginsertedorupdatedinacolumn(s)are
withinagivenrangeoramemberofagivensetofvalues.Check
constraints,however,aren’tasflexibleasforeignkeyconstraintsand
triggersbecausethedatadomainisnormallydefinedusinghard-coded
constantvaluesorlogicalexpressions.
Triggersarestoredproceduresattachedtoinsert,update,ordelete
eventsonatableorview.TriggerscanbesetonDMLorDDLevents.
BothDMLandDDLtriggersprovideaflexiblesolutionfor
constrainingdata,buttheymayrequiremoremaintenancethanthe
otheroptionsbecausethey’reessentiallyaspecializedformofstored
procedure.Unlessthey’reextremelywelldesigned,triggershavethe
potentialtobemuchlessefficientthanothermethodsofconstraining
data.Generallytriggersareavoidedinmoderndatabasesinfavorof
moreefficientmethodsofconstrainingdata.Theexceptiontothisis
whenyou’retryingtoenforceaforeignkeyconstraintacross
databases,becauseSQLServerdoesn’tsupportcross-databaseforeign
keyconstraints.
Whichmethodyouusetoconstrainthedatadomainofyourcolumn(s)needstobe
determinedbyyourproject-specificrequirementsonacase-by-casebasis.
Views
Aviewislikeavirtualtable—thedataitexposesisn’tstoredintheviewobjectitself.
ViewsarecomposedofSQLqueriesthatreferencetablesandotherviews,butthey’re
referencedjustliketablesinqueries.ViewsservetwomajorpurposesinSQLServer:they
canbeusedtohidethecomplexityofqueries,andtheycanbeusedasasecuritydeviceto
limittherowsandcolumnsofatablethatausercanquery.Viewsareexpanded,meaning
theirlogicisincorporatedintotheexecutionplanforquerieswhenyouusethemin
queriesandDMLstatements.SQLServermaynotbeabletouseindexesonthebase
www.allitebooks.com
tableswhentheviewisexpanded,resultinginless-than-optimalperformancewhen
queryingviewsinsomesituations.
Toovercomethequeryperformanceissueswithviews,SQLServeralsohastheability
tocreateaspecialtypeofviewknownasanindexedview.Anindexedviewisaviewthat
SQLServerpersiststothedatabaselikeatable.Whenyoucreateanindexedview,SQL
Serverallocatesstorageforitandallowsyoutoqueryitlikeanyothertable.Thereare,
however,restrictionsoninsertinginto,updating,anddeletingfromanindexedview.For
instance,youcan’tperformdatamodificationsonanindexedviewifmorethanoneofthe
view’sbasetableswillbeaffected.Youalsocan’tperformdatamodificationsonan
indexedviewiftheviewcontainsaggregatefunctionsoraDISTINCTclause.
Youcanalsocreateindexesonanindexedviewtoimprovequeryperformance.The
downsidetoanindexedviewisincreasedoverheadwhenyoumodifydataintheview’s
basetables,becausetheviewmustbeupdatedaswell.
Indexes
IndexesareSQLServer’smechanismsforoptimizingaccesstodata.SQLServer2014
supportsseveraltypesofindexes,includingthefollowing:
Clusteredindex:Aclusteredindexislimitedtoonepertable.This
typeofindexdefinestheorderingoftherowsinthetable.Aclustered
indexisphysicallyimplementedusingab-treestructurewiththedata
storedintheleaflevelsofthetree.Clusteredindexesorderthedatain
atableinmuchthesamewaythataphonebookisorderedbylast
name.Atablewithaclusteredindexisreferredtoasaclusteredtable,
whereasatablewithnoclusteredindexisreferredtoasaheap.
Nonclusteredindex:Anonclusteredindexisalsoab-treeindex
managedbySQLServer.Inanonclusteredindex,indexrowsare
includedintheleaflevelsoftheb-tree.Becauseofthis,nonclustered
indexeshavenoeffectontheorderingofrowsinatable.Theindex
rowsintheleaflevelsofanonclusteredindexconsistofthe
following:
Anonclusteredkeyvalue
Arowlocator,whichistheclusteredindexkeyonatablewith
aclusteredindex,oraSQL-generatedrowIDforaheap
Nonkeycolumns,whichareaddedviatheINCLUDEclauseof
theCREATEINDEXstatement
Columnstoreindex:Acolumnstoreindexisaspecialindexusedfor
verylargetables(>100millionrows)andismostlyapplicabletolarge
data-warehouseimplementations.Acolumnstoreindexcreatesan
indexonthecolumnasopposedtotherowandallowsforefficient
andextremelyfastretrievaloflargedatasets.PriortoSQLServer
2014,tableswithcolumnstoreindexeswererequiredtoberead-only.
InSQLServer2014,columnstoreindexesarenowupdateable.This
featureisdiscussedfurtherinChapter6.
XMLindex:SQLServersupportsspecialindexesdesignedtohelp
efficientlyqueryXMLdata.SeeChapter11formoreinformation.
Spatialindex:Aspatialindexisaninterestingnewindexingstructure
tosupportefficientqueryingofthenewgeometryandgeographydata
types.SeeChapter2formoreinformation.
Full-textindex:Afull-textindex(FTI)isaspecialindexdesignedto
efficientlyperformfull-textsearchesofdataanddocuments.
Memory-optimizedindex:SQLServer2014introducedIn-Memory
tablesthatbringwiththemnewindextypes.Thesetypesofindexes
onlyexistinmemoryandmustbecreatedwiththeinitialtable
creation.TheseindextypesarecoveredatlengthinChapter6:
Nonclusteredhashindex:Thistypeofindexismostefficient
inscenarioswherethequerywillreturnvaluesforaspecific
valuecriteria.Forexample,SELECT*FROM<Table>
WHERE<Column>=@<ColumnValue>.
Memory-optimizednonclusteredindex:Thistypeofindex
supportsthesamefunctionsasahashindex,inadditiontoseek
operationsandsortordering.
Youcanalsoincludenonkeycolumnsinyournonclusteredindexeswiththe
INCLUDEclauseoftheCREATEINDEXstatement.Theincludedcolumnsgiveyouthe
abilitytoworkaroundSQLServer’sindexsizelimitations.
StoredProcedures
SQLServersupportstheinstallationofserver-sideT-SQLcodemodulesviastored
procedures(SPs).It’sverycommontouseSPsasasortofintermediatelayerorcustom
server-sideapplicationprogramminginterface(API)thatsitsbetweenuserapplications
andtablesinthedatabase.Storedproceduresthatarespecificallydesignedtoperform
queriesandDMLstatementsagainstthetablesinadatabasearecommonlyreferredtoas
CRUD(create,read,update,delete)procedures.
User-DefinedFunctions
User-definedfunctions(UDFs)canperformqueriesandcalculations,andreturneither
scalarvaluesortabularresultsets.UDFshavecertainrestrictionsplacedonthem.For
instance,theycan’tusecertainnondeterministicsystemfunctions,norcantheyperform
DMLorDDLstatements,sotheycan’tmakemodificationstothedatabasestructureor
content.Theycan’tperformdynamicSQLqueriesorchangethestateofthedatabase
(causesideeffects).
SQLCLRAssemblies
SQLServer2014supportsaccesstoMicrosoft.NETfunctionalityviatheSQLCommon
LanguageRuntime(SQLCLR).Toaccessthisfunctionality,youmustregistercompiled
.NETSQLCLRassemblieswiththeserver.Theassemblyexposesitsfunctionality
throughclassmethods,whichcanbeaccessedviaSQLCLRfunctions,procedures,
triggers,user-definedtypes,anduser-definedaggregates.SQLCLRassembliesreplacethe
deprecatedSQLServerextendedstoredprocedure(XP)functionalityavailableinprior
releases.
TipAvoidusingextendedstoredprocedures(XPs)onSQLServer2014.Thesame
functionalityprovidedbyXPscanbeprovidedbySQLCLRcode.TheSQLCLRmodel
ismorerobustandsecurethantheXPmodel.AlsokeepinmindthattheXPlibraryis
deprecated,andXPfunctionalitymaybecompletelyremovedinafutureversionofSQL
Server.
ElementsofStyle
Nowthatyou’vehadabroadoverviewofthebasicsofSQLServer,let’slookatsome
recommendeddevelopmenttipstohelpwithcodemaintenance.Selectingaparticular
styleandusingitconsistentlyhelpsimmenselywithbothdebuggingandfuture
maintenance.Thefollowingsectionscontainsomegeneralrecommendationstomakeyour
T-SQLcodeeasytoread,debug,andmaintain.
Whitespace
SQLServerignoresextrawhitespacebetweenkeywordsandidentifiersinSQLqueries
andstatements.Asinglestatementorquerymayincludeextraspacesandtabcharacters
andcanevenextendacrossseverallines.Youcanusethisknowledgetogreatadvantage.
ConsiderListing1-3,whichisadaptedfromtheHumanResources.vEmployeeview
intheAdventureWorks2014database.
Listing1-3.TheHumanResources.vEmployeeViewfromthe
AdventureWorks2014Database
SELECTe.BusinessEntityID,p.Title,p.FirstName,
p.MiddleName,p.LastName,p.Suffix,e.JobTitle,
pp.PhoneNumber,pnt.NameASPhoneNumberType,
ea.EmailAddress,
p.EmailPromotion,a.AddressLine1,a.AddressLine2,a.City,
sp.NameASStateProvinceName,a.PostalCode,cr.NameAS
CountryRegionName,p.AdditionalContactInfo
FROMHumanResources.EmployeeASeINNERJOINPerson.Person
ASpONp.BusinessEntityID=e.BusinessEntityIDINNERJOIN
Person.BusinessEntityAddressASbeaONbea.BusinessEntityID
=e.BusinessEntityIDINNERJOINPerson.AddressASaON
a.AddressID=bea.AddressIDINNERJOINPerson.StateProvince
ASspONsp.StateProvinceID=a.StateProvinceIDINNERJOIN
Person.CountryRegionAScrONcr.CountryRegionCode
=sp.CountryRegionCodeLEFTOUTERJOINPerson.PersonPhoneAS
ppONpp.BusinessEntityID=p.BusinessEntityIDLEFTOUTER
JOINPerson.PhoneNumberTypeASpntONpp.PhoneNumberTypeID
=pnt.PhoneNumberTypeIDLEFTOUTERJOINPerson.EmailAddress
ASeaONp.BusinessEntityID=ea.BusinessEntityID
Thisquerywillrunandreturnthecorrectresult,butit’sveryhardtoread.Youcanuse
whitespaceandtablealiasestogenerateaversionthatismucheasierontheeyes,as
demonstratedinListing1-4.
Listing1-4.TheHumanResources.vEmployeeViewReformattedforReadability
SELECT
e.BusinessEntityID,
p.Title,
p.FirstName,
p.MiddleName,
p.LastName,
p.Suffix,
e.JobTitle,
pp.PhoneNumber,
pnt.NameASPhoneNumberType,
ea.EmailAddress,
p.EmailPromotion,
a.AddressLine1,
a.AddressLine2,
a.City,
sp.NameASStateProvinceName,
a.PostalCode,
cr.NameASCountryRegionName,
p.AdditionalContactInfo
FROMHumanResources.EmployeeASeINNERJOINPerson.Person
ASp
ONp.BusinessEntityID=e.BusinessEntityID
INNERJOINPerson.BusinessEntityAddressASbea
ONbea.BusinessEntityID=e.BusinessEntityID
INNERJOINPerson.AddressASa
ONa.AddressID=bea.AddressID
INNERJOINPerson.StateProvinceASsp
ONsp.StateProvinceID=a.StateProvinceID
INNERJOINPerson.CountryRegionAScr
ONcr.CountryRegionCode=sp.CountryRegionCode
LEFTOUTERJOINPerson.PersonPhoneASpp
ONpp.BusinessEntityID=p.BusinessEntityID
LEFTOUTERJOINPerson.PhoneNumberTypeASpnt
ONpp.PhoneNumberTypeID=pnt.PhoneNumberTypeID
LEFTOUTERJOINPerson.EmailAddressASea
ONp.BusinessEntityID=ea.BusinessEntityID;
NoticethattheONkeywordsareindented,associatingthemvisuallywiththeINNER
JOINoperatorsdirectlybeforetheminthelisting.Thecolumnnamesonthelinesdirectly
aftertheSELECTkeywordarealsoindented,associatingthemvisuallywithSELECT.
Thisparticularstyleisusefulinhelpingvisuallybreakupaqueryintosections.The
personalstyleyoudecideonmaydifferfromthisone,butonceyou’vedecidedona
standardindentationstyle,besuretoapplyitconsistentlythroughoutyourcode.
Codethatiseasytoreadiseasiertodebugandmaintain.ThecodeinListing1-4uses
tablealiases,plentyofwhitespace,andthesemicolon(;)terminatortomarktheendof
SELECTstatements,tomakethecodemorereadable.(It’sagoodideatogetintothehabit
ofusingtheterminatingsemicoloninyourSQLqueries—it’srequiredinsomeinstances.)
TipSemicolonsarerequiredterminatorsforsomestatementsinSQLServer2014.
Insteadoftryingtorememberallthespecialcaseswheretheyareoraren’trequired,it’sa
goodideatousethesemicolonstatementterminatorthroughoutyourT-SQLcode.You’ll
noticetheuseofsemicolonterminatorsinalltheexamplesinthisbook.
NamingConventions
SQLServerallowsyoutonameyourdatabaseobjects(tables,views,procedures,andso
on)usingjustaboutanycombinationofupto128characters(116charactersforlocal
temporarytablenames),aslongasyouenclosetheminsinglequotes('')orbrackets([
]).Justbecauseyoucan,however,doesn’tnecessarilymeanyoushould.Manyofthe
allowedcharactersarehardtodifferentiatefromothersimilar-lookingcharacters,and
somemaynotportwelltootherplatforms.Thefollowingsuggestionswillhelpyouavoid
potentialproblems:
Usealphabeticcharacters(A–Z,a–z,andUnicodeStandard3.2
letters)forthefirstcharacterofyouridentifiers.Theobvious
exceptionsareSQLServervariablenamesthatstartwiththeat(@)
sign,temporarytablesandproceduresthatstartwiththenumbersign
(#),andglobaltemporarytablesandproceduresthatbeginwitha
doublenumbersign(##).
Manybuilt-inT-SQLfunctionsandsystemvariableshavenamesthat
beginwithadoubleatsign(@@),suchas@@ERR0Rand
@@IDENTITY.Toavoidconfusionandpossibleconflicts,don’tusea
leadingdoubleatsigntonameyouridentifiers.
Restricttheremainingcharactersinyouridentifierstoalphabetic
characters(A–Z,a–z,andUnicodeStandard3.2letters),numeric
digits(0–9),andtheunderscorecharacter(_).Thedollarsign($)
character,althoughallowed,isn’tadvisable.
Avoidembeddedspaces,punctuationmarks(otherthantheunderscore
character),andotherspecialcharactersinyouridentifiers.
AvoidusingSQLServer2014reservedkeywordsasidentifiers.You
canfindthelisthere:http://msdn.microsoft.com/en-
us/library/ms189822.aspx.
Limitthelengthofyouridentifiers.Thirty-twocharactersorlessisa
reasonablelimitwhilenotbeingoverlyrestrictive.Muchmorethan
thatbecomescumbersometotypeandcanhurtyourcodereadability.
Finally,tomakeyourcodemorereadable,selectacapitalizationstyleforyour
identifiersandcode,anduseitconsistently.MypreferenceistofullycapitalizeT-SQL
keywordsandusemixed-caseandunderscorecharacterstovisuallybreakupidentifiers
intoeasilyreadablewords.Usingallcapitalcharactersorinconsistentlyapplyingmixed
casetocodeandidentifierscanmakeyourcodeillegibleandhardtomaintain.Consider
theexamplequeryinListing1-5.
Listing1-5.All-CapitalSELECTQuery
SELECTP.BUSINESSENTITYID,P.FIRSTNAME,P.LASTNAME,
S.SALESYTD
FROMPERSON.PERSONPINNERJOINSALES.SALESPERSONSP
ONP.BUSINESSENTITYID=SP.BUSINESSENTITYID;
Theall-capitalversionisdifficulttoread.It’shardtotelltheSQLkeywordsfromthe
columnandtablenamesataglance.Compoundwordsforcolumnandtablenamesaren’t
easilyidentified.Basically,youreyeshavetoworkalothardertoreadthisquerythan
theyshould,whichmakesotherwisesimplemaintenancetasksmoredifficult.
Reformattingthecodeandidentifiersmakesthisquerymucheasierontheeyes,asListing
1-6demonstrates.
Listing1-6.Reformatted,Easy-on-the-EyesQuery
SELECT
p.BusinessEntityID,
p.FirstName,
p.LastName,
sp.SalesYTD
FROMPerson.PersonpINNERJOINSales.SalesPersonsp
ONp.BusinessEntityID=sp.BusinessEntityID;
Theuseofallcapitalsforthekeywordsinthesecondversionmakesthemstandout
fromthemixed-casetableandcolumnnames.Likewise,themixed-casecolumnandtable
namesmakethecompoundwordnameseasytorecognize.Theneteffectisthatthecode
iseasiertoread,whichmakesiteasiertodebugandmaintain.Consistentuseofgood
formattinghabitshelpskeeptrivialchangestrivialandmakescomplexchangeseasier.
OneEntry,OneExit
WhenwritingSPsandUDFs,it’sgoodprogrammingpracticetousethe“oneentry,one
exit”rule.SPsandUDFsshouldhaveasingleentrypointandasingleexitpoint(RETURN
statement).
TheSPinListing1-7isasimpleprocedurewithoneentrypointandseveralexit
points.ItretrievestheContactTypelDnumberfromtheAdventureWorks2014
Person.ContactTypetablefortheContactTypenamepassedintoit.Ifno
ContactTypeexistswiththenamepassedin,anewoneiscreated,andthenewly
createdContactTypelDispassedback.
Listing1-7.StoredProcedureExamplewithOneEntryandMultipleExits
CREATEPROCEDUREdbo.GetOrAdd_ContactType
(
@NameNVARCHAR(50),
@ContactTypeIDINTOUTPUT
)
AS
DECLARE@Err_CodeASINT;
SELECT@Err_Code=0;
SELECT@ContactTypeID=ContactTypeID
FROMPerson.ContactType
WHERE[Name]=@Name;
IF@ContactTypeIDISNOTNULL
RETURN;--Exit1:iftheContactTypeexists
INSERT
INTOPerson.ContactType([Name],ModifiedDate)
SELECT@Name,CURRENT_TIMESTAMP;
SELECT@Err_Code='error';
IF@Err_Code<>0
RETURN@Err_Code;--Exit2:ifthereisanerroron
INSERT
SELECT@ContactTypeID=SCOPE_IDENTITY();
RETURN@Err_Code;—Exit3:aftersuccessfulINSERT
GO
Thiscodehasoneentrypointbutthreepossibleexitpoints.Figure1-3showsasimple
flowchartforthepathsthiscodecantake.
Figure1-3.Flowchartforanexamplewithoneentryandmultipleexits
Asyoucanimagine,maintainingcodesuchasthatinListing1-7becomesmore
difficultbecausetheflowofthecodehassomanypossibleexitpoints,eachofwhichmust
beaccountedforwhenyoumakemodificationstotheSP.Listing1-8updatesListing1-7
togiveitasingleentrypointandasingleexitpoint,makingthelogiceasiertofollow.
Listing1-8.StoredProcedurewithOneEntryandOneExit
CREATEPROCEDUREdbo.GetOrAdd_ContactType
(
@NameNVARCHAR(50),
@ContactTypeIDINTOUTPUT
)
AS
DECLARE@Err_CodeASINT;
SELECT@Err_Code=0;
SELECT@ContactTypeID=ContactTypeID
FROMPerson.ContactType
WHERE[Name]=@Name;
IF@ContactTypeIDISNULL
BEGIN
INSERT
INTOPerson.ContactType([Name],ModifiedDate)
SELECT@Name,CURRENT_TIMESTAMP;
SELECT@Err_Code=@@error;
IF@Err_Code=0--Ifthere'sanerror,skip
next
SELECT@ContactTypeID=SCOPE_IDENTITY();
END
RETURN@Err_Code;—Singleexitpoint
GO
Figure1-4showsthemodifiedflowchartforthisnewversionoftheSP.
Figure1-4.Flowchartforanexamplewithoneentryandoneexit
Theoneentryandoneexitmodelmakesthelogiceasiertofollow,whichinturn
makesthecodeeasiertomanage.Thisrulealsoappliestoloopingstructures,whichyou
implementviatheWHILEstatementinT-SQL.AvoidusingtheWHILEloop’s
CONTINUEandBREAKstatementsandtheGOTOstatement;thesestatementsleadtoold-
fashioned,difficult-to-maintainspaghetticode.
DefensiveCoding
Defensivecodinginvolvesanticipatingproblemsbeforetheyoccurandmitigatingthem
throughgoodcodingpractices.Thefirstandforemostlessonofdefensivecodingisto
alwayscheckuserinput.Onceyouopenyoursystemtousers,expectthemtodo
everythingintheirpowertotrytobreakyoursystem.Forinstance,ifyouaskusersto
enteranumberbetween1and10,expectthatthey’llignoreyourdirectionsandkeyin;
DROPTABLEdbo.syscomments;—atthefirstavailableopportunity.Defensive
codingpracticesdictatethatyoushouldcheckandscrubexternalinputs.Don’tblindly
trustanythingthatcomesfromanexternalsource.
Anotheraspectofdefensivecodingisacleardelineationbetweenexceptionsandrun-
of-the-millissues.Thekeyisthatexceptionsare,well,exceptionalinnature.Ideally,
exceptionsshouldbecausedbyerrorsthatyoucan’taccountfororcouldn’treasonably
anticipate,likealostnetworkconnectionorphysicalcorruptionofyourapplicationor
datastorage.Errorsthatcanbereasonablyexpected,likedata-entryerrors,shouldbe
capturedbeforethey’reraisedtothelevelofexceptions.Keepinmindthatexceptionsare
oftenresource-intensive,expensiveoperations.Ifyoucanavoidanexceptionby
anticipatingaparticularproblem,yourapplicationwillbenefitinbothperformanceand
control.SQLServer2012introducedavaluablenewerror-handlingfeaturecalledTHROW.
TheTRY/CATCH/THROWstatementsarediscussedinmoredetailinChapter18.
TheSELECT*Statement
ConsidertheSELECT*styleofquerying.InaSELECTclause,theasterisk(*)isa
shorthandwayofspecifyingthatallcolumnsinatableshouldbereturned.Although
SELECT*isahandytoolforadhocqueryingoftablesduringdevelopmentand
debugging,younormallyshouldn’tuseitinaproductionsystem.Onereasontoavoidthis
methodofqueryingistominimizetheamountofdataretrievedwitheachcall.SELECT
*retrievesallcolumns,regardlessofwhetherthey’reneededbythehigher-level
applications.Forqueriesthatreturnalargenumberofrows,evenoneortwoextraneous
columnscanwastealotofresources.
Iftheunderlyingtableorviewisaltered,columnsmaybeaddedtoorremovedfrom
thereturnedresultset.Thiscancauseerrorsthatarehardtolocateandfix.Byspecifying
thecolumnnames,yourfront-endapplicationcanbeassuredthatonlytherequired
columnsarereturnedbyaqueryandthaterrorscausedbymissingcolumnswillbeeasier
tolocate.
Aswithmostthings,therearealwaysexceptions—forexample,ifyou’reusingthe
FORXMLAUTOclausetogenerateXMLbasedonthestructureandcontentofyour
relationaldata.Inthiscase,SELECT*canbequiteuseful,becauseyou’rerelyingon
FORXMLtoautomaticallygeneratethenodenamesbasedonthetableandcolumnnames
inthesourcetables.
TipSELECT*shouldbeavoided,butifyoudoneedtouseit,alwaystrytolimitthe
datasetbeingreturned.OnewayofdoingsoistomakefulluseoftheT-SQLTOP
commandandrestrictthenumberofrecordsreturned.Inpractice,though,youshould
neverwriteSELECT*inyourcode—evenforsmalltables.Smalltablestodaycouldbe
www.allitebooks.com
largetablestomorrow.
VariableInitialization
WhenyoucreateSPs,UDFs,oranyscriptthatusesT-SQLuservariables,youshould
initializethosevariablesbeforethefirstuse.Unlikesomeotherprogramminglanguages
thatguaranteethatnewlydeclaredvariableswillbeinitializedto0oranemptystring
(dependingontheirdatatypes),T-SQLguaranteesonlythatnewlydeclaredvariableswill
beinitializedtoNULL.ConsiderthecodesnippetshowninListing1-9.
Listing1-9.SampleCodeUsinganUninitializedVariable
DECLARE@iINT;SELECT@i=@i+5;SELECT@i;
TheresultisNULL,whichisashockifyouwereexpecting5.ExpectingSQLServer
toinitializenumericvariablesto0(like@iinthepreviousexample)oranemptystring
willresultinbugsthatcanbeextremelydifficulttolocateinyourT-SQLcode.Toavoid
theseproblems,alwaysexplicitlyinitializeyourvariablesafterdeclaration,as
demonstratedinListing1-10.
Listing1-10.SampleCodeUsinganInitializedVariable
DECLARE@iINT=0;—Changedthisstatementtoinitialize@i
to0
SELECT@i=@i+5;
SELECT@i;
Summary
ThischapterhasservedasanintroductiontoT-SQL,includingabriefhistoryofSQLand
adiscussionofthedeclarativeprogrammingstyle.Thechapterstartedwithadiscussionof
ISOSQLstandardcompatibilityinSQLServer2014andthedifferencesbetween
imperativeanddeclarativelanguages,ofwhichSQListhelatter.Youalsosawmanyof
thebasiccomponentsofSQL,includingdatabases,tables,views,SPs,andothercommon
databaseobjects.Finally,IprovidedmypersonalrecommendationsforwritingSQLcode
thatiseasytodebugandmaintain.Isubscribetothe“eatyourowndogfood”theory,and
throughoutthisbookIfaithfullyfollowthebestpracticerecommendationsthatI’veasked
youtoconsider.
Thenextchapterprovidesanoverviewofthenewandimprovedtoolsavailableoutof
theboxfordevelopers.Specifically,Chapter2discussestheSQLCMDtext-basedSQL
client(originallyareplacementforosql),SSMS,SQLServer2014BooksOnline
(BOL),andsomeoftheotheravailabletoolsthatmakewriting,editing,testing,and
debuggingeasierandfasterthanever.
EXERCISES
1. Describethedifferencebetweenanimperativelanguageanda
declarativelanguage.
2. WhatdoestheacronymACIDstandfor?
3. SQLServer2014supportssevendifferenttypesofindexes.Twoof
theseindexesarenewlyintroducedinSQL2014.Whatarethey?
4. NametwooftherestrictionsonanytypeofSQLServerUDF.
5. [True/False]InSQLServer,newlydeclaredvariablesarealways
assignedthedefaultvalue0fornumericdatatypesandanempty
stringforcharacterdatatypes.
CHAPTER2
ToolsoftheTrade
SQLServer2014comeswithawideselectionoftoolsandutilitiestomakedevelopment
easierandmoreproductivefordevelopers.Thischapterintroducessomeofthemost
importanttoolsforSQLServerdevelopers,includingSQLServerManagementStudio
(SSMS)andtheSQLCMDutility,SQLServerDataTooladd-instoMicrosoftVisual
Studio,SQLProfiler,DatabaseTuningAdvisor,ExtendedEvents,andSQLServer2014
BooksOnline(BOL).You’realsointroducedtosupportingtoolslikeSQLServer
IntegrationServices(SSIS),theBulkCopyProgram(BCP),andtheAdventureWorks
2014sampledatabase,whichyouuseinexamplesthroughoutthebook.
SQLServerManagementStudio
BackintheheydayofSQLServer2000,itwascommonfordeveloperstofireupthe
EnterpriseManager(EM)andQueryEditorGUIdatabasetoolsinrapidsuccessionevery
timetheysatdowntowritecode.Historically,developerandDBArolesintheDBMS
havebeenhighlyseparated,andwithgoodreason.DBAshavehistoricallybrought
hardwareandsoftwareadministrationandtuningskills,databasedesignoptimization
experience,andhealthydosesofskepticismandsecuritytothetable.Ontheotherhand,
developershavefocusedoncodingskills,problemsolving,systemoptimization,and
debugging.Thisseparationofpowersworksverywellinproductionsystems,butin
developmentenvironmentsdevelopersareoftenresponsiblefortheirowndatabasedesign
andmanagement.Sometimesdevelopersareputinchargeoftheirowndevelopment
serverlocalsecurity.
SQLServer2000EMwasoriginallydesignedasaDBAtool,providingaccesstothe
graphicaluserinterface(GUI)administrationinterface,includingsecurityadministration,
databaseobjectcreationandmanagement,andservermanagementfunctionality.Query
Editorwasdesignedasadevelopertool,theprimaryGUItoolforcreating,testing,and
tuningqueries.
SQLServer2014continuesthetraditionbegunwithSQLServer2005bycombining
thefunctionalityofboththeseGUItoolsintoasingleGUIinterfaceknownasSQLServer
ManagementStudio(SSMS).Thismakesperfectsenseinsupportingreal-worldSQL
Serverdevelopment,wheretherolesofDBAanddeveloperareoftenintermingledin
developmentenvironments.
ManySQLServerdevelopersprefertheGUIadministrationanddevelopmenttoolsto
thetext-basedquerytoolSQLCMDtobuildtheirdatabases,andonthisfrontSSMS
doesn’tdisappoint.SSMSoffersseveralfeaturesthatmakedevelopmentand
administrationeasier,includingthefollowing:
Integrated,functionalObjectExplorer,whichprovidestheabilityto
easilyviewalltheobjectsintheserverandmanagetheminatree
structure.Theaddedfilterfunctionalityhelpsusersnarrowdownthe
objectstheywanttoworkwith.
Colorcodingofscripts,makingeditinganddebuggingeasier.
Enhancedkeyboardshortcutsthatmakesearchingfasterandeasier.
Additionally,userscanmappredefinedkeyboardshortcutstostored
proceduresthatareusedmostoften.
Twokeyboardshortcutschemes:keyboardshortcutsfromSQLServer
2008R2andMicrosoftVisualStudio2010compatibility.
UsabilityenhancementssuchastheabilitytozoomtextintheQuery
EditorbyholdingtheCtrlkeyandscrollingtozoominandout.Users
candraganddroptabs,andthereistruemultimonitorsupport.
Breakpointvalidation,whichpreventsusersfromsettingbreakpoints
atinvalidlocations.
T-SQLcodesnippets,whicharetemplatesthatcanbeusedasstarting
pointstobuildT-SQLstatementinscriptsandbatches.
T-SQLDebuggerWatchandQuickWatchwindows,whichsupport
watchingT-SQLexpressions.
Graphicalqueryexecutionplans.Thesearethebreadandbutterofthe
query-optimizationprocess.Theygreatlysimplifytheprocessof
optimizingcomplexqueries,quicklyexposingpotentialbottlenecksin
yourcode.
Project-managementandcode-versioncontrolintegration,including
integrationwithTeamFoundationServer(TFS)andVisual
SourceSafeversioncontrolsystems.
SQLCMDmode,whichallowsyoutoexecuteSQLscriptsusing
SQLCMD.YoucantakeadvantageofSQLCMD’sadditionalscript
capabilities,likescriptingvariablesandsupportfortheAlwaysON
feature.
SSMSalsoincludesdatabaseandservermanagementfeatures,butthisdiscussionis
limitedtosomeofthemostimportantdeveloper-specificfeatures.
IntelliSense
IntelliSenseisafeaturethatwasintroducedinSQLServer2008.Whencoding,youoften
needtolookuplanguageelementssuchasfunctions,tablenames,andcolumnnamesto
completeyourcode.ThisfeatureallowstheSQLEditortoautomaticallypromptforthe
completionofthesyntaxyouinput,basedonpartialwords.ToenableIntelliSense,goto
Tools Options TextEditor Transact-SQL IntelliSense.Figure2-1demonstrates
howtheIntelliSensefeaturesuggestslanguageelementsbasedonthefirstletterentered.
Figure2-1.UsingIntelliSensetocompleteaSelectstatement
CodeSnippets
Codesnippetsaren’tanewconcepttotheprogrammingworld.VisualStudiodevelopers
areveryfamiliarwiththisfeature;andbecauseSSMSisbuiltontheVisualStudio2010
shell,SQLinheritsthisfunctionalityaswell.Duringthedevelopmentcycle,developers
oftenuseasetofT-SQLstatementsmultipletimesthroughoutthecodebeingworkedon.
It’smuchmoreefficienttoaccessablockofcodethatcontainscommoncodeelements
suchascreatestoredprocedureandcreatefunction,tohelpyoubuild
ontopofthecodeblock.Codesnippetsarebuildingblocksofcodethatyoucanuseasa
startingpointwhenbuildingT-SQLscripts.Thisfeaturecanhelpyoubemore
productivitywhileincreasingreusabilityandstandardizationbyenablingthedevelopment
teamtouseexistingtemplatesortocreateandcustomizeanewtemplate.
CodesnippetshelpprovideabetterT-SQLcode-editingexperience.Inaddition,a
snippetisanXMLtemplatethatcanbeusedtoguaranteeconsistencyacrossthe
developmentteam.Thesesnippetsfallintothreecategories:
ExpansionsnippetslistthecommonoutlineofT-SQLcommandssuch
as
Select,Insert,andCreateTable.
Surroundsnippetsincludeconstructssuchaswhile,ifelse,and
beginendstatements.
Customsnippetsallowcustomtemplatesthatcanbeinvokedviathe
snippetmenu.Youcancreateacustomsnippetandaddittotheserver
byimportingthesnippetusingtheCodeSnippetManager.Onceyou
addacustomsnippet,theCustomSnippetscategoryappearsinthe
CodeSnippetManager.
Toaccessthecodesnippets,selecttheCodeSnippetsManagerfromtheToolsmenu.
Figure2-2showstheCodeSnippetManagerinterface,whichyoucanusetoadd,remove,
orimportcodesnippets.
Figure2-2.CodeSnippetManager
ToinsertacodesnippetintheT-SQLEditor,right-clickandselectInsertSnippetor
pressCtrlK+X.
Figure2-3demonstrateshowtoinvoketheInsertSnippetandSurroundWith
commands.
Figure2-3.Right-clickintheT-SQLEditortoinvokethecommandtoinsertsnippets
OncetheInsertSnippetcommandisinvoked,youhavetheoptiontochooseatemplate
basedontheSQLobjecttype,suchasIndex,Table,Function,Login,Role,Schema,
StoredProcedure,Trigger,CustomSnippets,andsoon.Figure2-4showshowtoinserta
snippet.
Figure2-4.Insertingasnippet
WhenthesnippetisinsertedintotheT-SQLEditor,fieldsthatneedtobecustomized
arehighlighted,andyoucanusetheTabkeytonavigatethroughthem.Ifyoumouseover
ahighlightedtoken,atooltipprovidesadditionalinformationaboutit.Figure2-5shows
theCREATETABLEsnippetinvokedintheT-SQLEditoralongwiththetooltipthatlists
thefield’sdescription.
Figure2-5.AddingaCREATETABLEsnippet,withthetooltipdisplayed
KeyboardShortcutSchemes
IfyouaskanSQLuserandaVisualStudiouser,“Whatistheshortcutkeytoexecute
queries?”you’reboundtoreceivetwodifferentanswers:Ctrl+EforSQLusersand
Ctrl+Shift+EforVisualStudiousers.BecauseapplicationdevelopersareprimarilyVisual
Studiousers,it’sprudenttohaveanoptionthatletsuserspickthekeyboardshortcut
schemethat’sfamiliarbasedonthetooltheyhavebeenusing.Anotheradvantageof
definingandstandardizingthekeyboardshortcutschemeattheteamlevelisthatdoingso
helpsteammembersavoidexecutingwrongactionsintheteamenvironment.
SQLServer2014offerstwokeyboardshortcutschemes:thedefault,theSQLServer
2014shortcutscheme(thedefault)andtheVisualStudio2010shortcutscheme.The
SSMSinterfacehasn’tbeenupdatedinSQL2014.Functionalityandcolorschemes
operatethesameasinSQLServer2012.Tochangethekeyboardshortcutsettings,choose
Tools Options Environment Keyboard.Figure2-6showstheoptiontochangethe
keyboardmappingscheme.
Figure2-6.Keyboardshortcutmappingscheme
T-SQLDebugging
SQLServer2012introducedenhancementstoT-SQLdebuggingbyprovidingtheability
tosetconditionalbreakpoints,meaningabreakpointisinvokedonlyifacertain
expressionisevaluated.T-SQLdebuggingalsoextendssupportforexpressionevaluation
inWatchandQuickWatchwindows.Youcanalsospecifyahitcount,meaningyoucan
specifyhowmanytimesabreakpointcanbehitbeforeit’sinvoked.Breakpointscanalso
beexportedfromonesessiontotheother.TheWatchandQuickWatchwindowssupport
T-SQLexpressionsaswell.Figure2-7showstheDebuggingscreenwiththeOutputand
Localswindows.
Figure2-7.T-SQLdebuggingwiththeLocalsandOutputwindows
Abreakpointcannowbeplacedonindividualstatementsinabatch,andbreakpoints
arecontext-sensitive.Whenabreakpointisset,SQLvalidatesthebreakpoint’slocation
andimmediatelyprovidesfeedbackifthebreakpointissetataninvalidlocation.For
example,ifyousetabreakpointonacomment,yougetfeedbackthatit’saninvalid
breakpoint;andifyoutrytosetabreakpointforoneofthelinesinamultilinestatement,
thebreakpointsisaddedtoallthelines.
ADataTipisanotherdebuggingenhancementthatwasaddedinSQLServer2012to
helpyoutrackvariablesandexpressionsinthescopeofexecutionwhiledebuggingby
providingabilityto“pin”aDataTiptokeepitvisible(evenwhenthedebugsessionis
restarted).Whenthedebuggerisinbreakmode,ifyoumouseoveraT-SQLexpression
thatisbeingevaluated,youcanseethecurrentvalueofthatexpression.Figure2-8shows
abreakpointandDataTip.
Figure2-8.AbreakpointsandaDataTip
NoteTheuserloginmustbepartofthesysadminroleontheSQLServerinstancein
ordertouseT-SQLdebuggingcapabilitieswhenusingSSMS.WithSQLServerData
Tools(SSDT),developersnowhavetheoptionofdebuggingwithoutbeingpartofthe
sysadminrole,usingtheirlocaldbinstanceoftheschema.
SSMSEditingOptions
SSMSincorporatesandimprovesonmanyofthedeveloperfeaturesfoundinQuery
Editor.YoucanchangetheeditingoptionsdiscussedinthissectionviatheToolsä
Options.
SSMSincludesfullycustomizablescriptcolorcoding.Thedefaultfonthasbeen
changedtothemonotypefontConsolas,andthebackgroundcolorisnowbluetomatch
VisualStudio2012.Youcancustomizetheforegroundandbackgroundcolors,fontface,
size,andstyleforelementsofT-SQL,XML,XSLT,andMDXscripts.Likewise,youcan
customizejustaboutanyfeedbackthatSSMSgenerates,tosuityourpersonaltaste.
Youcansetothereditingoptions,suchaswordwrap,line-numberdisplay,indentation,
andtabsfordifferentfiletypesbasedontheirassociatedfileextensions.SSMSletsyou
configureyourownkeyboardshortcutstoexecutecommonT-SQLstatementsorSPs.
Bydefault,SSMSdisplaysqueriesusingatabbedwindowenvironment.Ifyouprefer
theclassicmultiple-documentinterface(MDI)windowstyle,youcanswitchthe
www.allitebooks.com
environmentlayoutaccordingly.Youcanalsochangethequeryresultoutputstylefrom
thedefaultgridoutputtotextorfileoutput.
Context-SensitiveHelp
StartingwithSQLServer2012,theproductdocumentationishostedonline
(MSDN/TechNet)toensurethatthecontentiskeptuptodate.Ifyouwanttoaccessthe
productdocumentationfromyourlocalcomputer,youhavetodownloadthehelpcatalogs
andsetuptheHelpViewer.Toconfigurethedocumentation,gototheHelpmenuand
selectManageHelpSettings.DoingsolaunchestheHelpLibraryManager.Scrolldownto
theSQLServer2014section,andclickAddNextforthedocumentationyouwantto
download.Ifthedocumentationisalreadyavailableinyoursystem,theHelpLibrary
Managerupdatesthecatalog’sindexwiththeSQLServerdocumentation.
Toaccesscontext-sensitivehelp,highlighttheT-SQLorotherstatementyouwanthelp
withandpressF1.YoucanaddhelppagestoyourHelpFavoritesorgodirectlyto
MSDN.IfpressingF1doesn’twork,remembertodownloadthedocumentationlocally
andchoosetouselocalhelp.Figure2-9showstheresultofcallingcontext-sensitivehelp
fortheCREATETABLEstatement.
Figure2-9.UsingSSMScontext-sensitivehelptofindtheCREATETABLEstatement
SSMShasseveraloptionsthatallowyoutocontrolhelpfunctionalityand
presentation.Youcan,forexample,usetheSSMSIntegratedHelpViewer,shownin
Figure2-9,oryoucanusetheExternalOnlineHelpViewer.TheSettingswindowinthe
HelpViewerallowsyoutosetapreferencetouseonlineorofflinehelp;it’sshownin
Figure2-10.
Figure2-10.UsingtheHelpViewerSettingswindowtopersonalizeSSMShelp
HelpSearchroundsoutthisdiscussionofthehelpfunctionalityinSSMS.TheHelp
SearchfunctionautomaticallysearchesseveralonlineprovidersofSQLServer–related
informationforanswerstoyourquestions.Searchesaren’trestrictedtoSQLServer
keywordsorstatements;youcansearchforanything,andtheHelpSearchfunctionscours
registeredwebsitesandcommunitiesforrelevantanswers.Figure2-11showstheresultof
usingHelpSearchtofindXQuerycontentandarticles.
Figure2-11.UsingHelpSearchtofindhelponXQuery
GraphicalQueryExecutionPlans
SSMSoffersgraphicalqueryexecutionplanssimilartotheplansavailableinQuery
Editor.Agraphicalqueryexecutionplanisanexcellenttoolforaidingandoptimizing
queryperformance.SSMSallowsyoutoviewtwotypesofgraphicalqueryexecution
plans:estimatedandactual.AnestimatedqueryexecutionplanisSQLServer’scost-based
performanceestimateofaquery.Theactualexecutionplanisvirtuallyidenticaltothe
estimatedexecutionplan,exceptthatitshowsadditionalinformationsuchasactualrow
counts,numberofrebinds,andnumberofrewindswhenthequeryisrun.Sometimesthe
actualexecutionplandiffersfromtheestimatedexecutionplan;thismaybedueto
changesinindexesorstatistics,parallelism,or,insomecases,aqueryusingtemporary
tablesorDDLstatements.TheseoptionsareavailableviatheQuerymenu.Figure2-12
showsanestimatedqueryexecutionplaninSSMS.
Figure2-12.Estimatedqueryexecutionplanforasimplequery
Inaddition,youcanright-clicktheExecutionPlanwindowandchoosetosavethe
XMLversionofthegraphicalqueryplantoafile.SSMScanopentheseXMLqueryplan
files(withtheextension.sqlplan)andautomaticallyshowyouthegraphicalversion.
Inaddition,thePropertieswindowoftheSQLServer2014queryplancontainsdetails
regardingtheMemoryGrantInfo,
OptimizerHardwareDependentProperties,andwarningsaboutdatathatcan
affectplans.Figure2-13showsasamplePropertieswindowforaqueryplan.Youalso
haveanoptiontoviewtheexecutionplaninXMLformatbyright-clickingtheExecution
PlanwindowandchoosingShowExecutionPlanXML.
Figure2-13.SamplePropertieswindowforasimplequery
Alongwiththeexecutionplan,youcanreviewquerystatisticsandnetworkstatistics
intheClientStatisticstab.Thisisextremelyusefulforremotelytroubleshooting
performanceproblemswithslow-runningqueries.
Project-ManagementFeatures
SQLServer2014SSMSsupportsproject-managementfeaturesthatwillbefamiliarto
VisualStudiodevelopersusingsolution-baseddevelopment.Thesetypesofsolutions,
referredtoasSQLServerManagementStudiodatabaseprojects,areadeprecatedfeature
inSQLServer2014.Thereisnomigrationpathforthesetypesofsolutions/projects,and
theywon’tbesupportedinfuturereleasesofSQLServer.Thereplacementforthistypeof
functionalityisSQLServerDataTools(SSDT)usingVisualStudiodatabaseprojects.The
twoproductshavecompletelydifferentprojecttypesthatcan’tbemanagedoropenedin
theotherproduct.
ThissectionexplainshowtouseSSMSprojectstypes,buttherecommendationisthat
youstartdevelopinganynewprojectsinSSDT.ThereisasectiondiscussingSSDTatthe
endofthischapter.
SSMSletsyoucreatesolutionsthatconsistofprojects,whichcontainT-SQLscripts,
XMLfiles,connectioninformation,andotherfiles.Bydefault,projectsandsolutionsare
savedinyourMyDocuments\SQLServerManagementStudio\Projects
directory.Solutionfileshavetheextension.ssmssln,andprojectfilesaresavedinan
XMLformatwiththe.smssprojextension.SSMSincorporatesaSolutionExplorer
windowsimilartoVisualStudio’sSolutionExplorer,asshowninFigure2-14.Youcan
accesstheSolutionExplorerthroughtheViewmenu.
Figure2-14.ViewingasolutionintheSSMSSolutionExplorer
SSMScantakeadvantageofsource-controlintegrationwithTFStohelpyoumanage
versioninganddeployment.TouseSSMS’ssource-controlintegration,youhavetosetthe
appropriatesource-controloptionintheOptionsmenu.TheOptionswindowisshownin
Figure2-15.
Figure2-15.Viewingthesource-controloptions
NoteTouseSSMSwithTFS,youneedtodownloadandinstalltheappropriate
MicrosoftSourceCodeControlInterface(MSSCCI)providerfromMicrosoft.Goto
www.microsoft.com/,searchfor“MSSCCI”,anddownloadtheVisualStudioTeam
System2010,2012,or2013versionoftheMSSCCIprovider,dependingonwhichversion
you’realreadyusing.
Afteryoucreateasolutionandaddprojects,connections,andSQLscripts,youcan
addyoursolutiontoTFSbyright-clickingthesolutionintheSolutionExplorerand
selectingAddSolutiontoSourceControl.
Tocheckoutitemsfromsourcecontrol,openalocalcopyandchooseCheckOutfor
Edit.YoucanfindoptionsforcheckingoutitemsfromsourcecontrolontheFile
SourceControlmenu.AftercheckingoutasolutionfromTFS,SSMSshowsyouthe
pendingcheck-ins,lettingyouaddcommentstoorcheckinindividualfilesorprojects.
TheObjectExplorer
TheSSMSObjectExplorerletsyouviewandmanagedatabaseandserverobjects.Inthe
ObjectExplorer,youcanviewtables,storedprocedures(SPs),user-definedfunctions
(UDFs),HTTPendpoints,users,logins,andjustabouteveryotherdatabase-specificor
server-scopedobject.Figure2-16showstheObjectExplorerintheleftpaneandthe
ObjectExplorerDetailstabontheright.
Figure2-16.ViewingtheObjectExplorerandtheObjectExplorerDetailstab
MostobjectsintheObjectExplorerandtheObjectExplorerDetailstabhaveobject-
specificpop-upcontextmenus.Right-clickinganygivenobjectbringsupthemenu.
Figure2-17showsanexamplepop-upcontextmenufordatabasetables.
Figure2-17.ObjectExplorerdatabasetablepop-upcontextmenusss
ObjectExplorerinSQLServer2014allowsdeveloperstofilterspecifictypesof
objectsfromallthedatabaseobjects.Tofilterobjects,typetextwithoptionalwildcard
charactersintheObjectExplorerDetailswindow,andpressEnter.Optionally,youcan
filterobjectsusingtheFiltericonontheObjectExplorerDetailstoolbar.Figure2-18
showsanexampleoffilteringobjectsnamed“Person”.
Figure2-18.ObjectExplorerwithdatabaseobjectsfilteredon“Person”
TheSQLCMDUtility
TheSQLCMDutilitywasoriginallyintroducedinSQLServer2005asanupdated
replacementfortheSQL2000osqlcommand-lineutility.YoucanuseSQLCMDto
executebatchesofT-SQLstatementsfromscriptfiles,individualqueriesorbatchesof
queriesininteractivemode,orindividualqueriesfromthecommandline.Thisutilityuses
SQLServerNativeClienttoexecutetheT-SQLstatements.
NoteAppendixDprovidesaquickreferencetoSQLCMDcommand-lineoptions,
scriptingvariables,andcommands.Thedescriptionsintheappendixarebasedon
extensivetestingofSQLCMDanddifferinsomeareasfromthedescriptionsgivenin
BOL.
SQLCMDsupportsawidevarietyofcommand-lineswitches,makingitaflexible
utilityforone-offbatchorscheduledscriptexecution.Thefollowingcommand
demonstratestheuseofsomecommonlyusedcommand-lineoptionstoconnecttoanSQL
ServerinstancenamedSQL2014andexecuteaT-SQLscriptinthe
AdventureWorks2014database:
sqlcmd-SSQL2014-E-dAdventureWorks2014-i
"d:\scripts\ListPerson.sql"
Theoptionsinclude-Stospecifytheserver\instancename,-EtoindicateWindows
authentication,-dtosetthedatabasename,and-itospecifythenameofascriptfileto
execute.Thecommand-lineswitchesareallcasesensitive,so-visadifferentoption
from-V,forinstance.
SQLCMDallowsyoutousescriptingvariablesthatletyouuseasinglescriptin
multiplescenarios.Scriptingvariablesprovideamechanismforcustomizingthebehavior
ofT-SQLscriptswithoutmodifyingthescripts’content.Youcanreferencescripting
variablesthatwerepreviouslysetwiththe-vcommand-lineswitch,withtheSQLCMD
:setvarcommand(discussedinthenextsection),orviaWindowsenvironment
variables.YoucanalsouseanyofthepredefinedSQLCMDscriptingvariablesfrom
withinyourscript.Theformattoaccessanyofthesetypesofscriptingvariablesfrom
withinyourscriptisthesame:$(variable_name).SQLCMDreplacesyourscripting
variableswiththeirrespectivevaluesduringscriptexecution.Listing2-1showssome
examplesofscriptingvariablesinaction.
Listing2-1.UsingScriptingVariablesinanSQLCMDScript
--Windowsenvironmentvariable
SELECT'$(PATH)';
--SQLCMDscriptingvariable
SELECT'$(SQLCMDSERVER)';
--Command-linescriptingvariable-vCOLVAR="Name"switch
SELECT$(COLVAR)
FROMSys.Tables;
Becausescriptingvariablesarereplacedinascriptwholesale,someorganizationsmay
considertheiruseasecurityriskduetothepossibilityofSQLinjection-styleattacks.For
thisreason,youmaychoosetoturnoffthisfeaturebyusingthe-xcommand-lineoption,
whichdisablesvariablesubstitution.
AnexampleofanSQLCMDscriptingvariableisthepredefinedSOLCMDINI,which
specifiestheSQLCMDstartupscript.ThestartupscriptisruneverytimeSQLCMDis
run.It’susefulforsettingscriptingvariableswiththe:setvarcommand,settinginitial
T-SQLoptionssuchasQUOTED_IDENTIFIERandANSI_PADDING,andperforming
anynecessarydatabasetasksbeforeotherscriptsarerun.
InadditiontoT-SQLstatements,SQLCMDrecognizesseveralcommandsspecificto
theapplication.SQLCMDcommandsallowyoutoperformtaskslikelistingserversand
scriptingvariables,connectingtoaserver,andsettingscriptingvariables,amongothers.
ExceptforthebatchterminatorGO,allSQLCMDcommandsbeginwithacolon(:).
SQLCMDcanalsoberuninteractively.Tostartaninteractivemodesession,run
SQLCMDwithanyofthepreviousoptionsthatdon’texitimmediatelyoncompletion.
NoteSQLCMDoptionssuchas-0,-i,-Z,and-?exitimmediatelyoncompletion.
Youcan’tstartaninteractiveSQLCMDsessionifyouspecifyanyofthesecommand-line
options.
DuringaninteractiveSQLCMDsession,youcanrunT-SQLqueriesandcommands
fromtheSQLCMDprompt.TheinteractivescreenlookssimilartoFigure2-19.
www.allitebooks.com
Figure2-19.SamplequeryrunfromtheSQLCMDinteractiveprompt
TheSQLCMDpromptindicatesthecurrentlinenumberofthebatch(1>,2>,andso
on).YoucanenterT-SQLstatementsorSQLCMDcommandsattheprompt.T-SQL
statementsarestoredinthestatementcacheasthey’reentered;SQLCMDcommandsare
executedimmediately.OnceyouhaveenteredacompletebatchofT-SQLstatements,use
theGObatchterminatortoprocessallthestatementsinthecache.
SQLCMDhassupportforthenewAlwaysOnfeature.Youcanusetheswitch–Kto
specifythelistenername.
TherehasbeenabehaviorchangeforSQLCMDforXMLaswell.InSQL2008,text
datathatcontainedasinglequotewasalwaysreplacedwithanapostrophe.Thisbehavior
changehasbeenaddressedinSQLServer2012.Additionally,legacydatetimevalueswith
nofractionalsecondsdonotreturnthreedecimaldigits;however,otherdatetimedatatypes
aren’taffected.
SQLServerDataTools
SQLServer2014shipswithanewdevelopertoolsetnamedSQLServerDataToolsthat
servesasareplacementforBusinessIntelligenceDevelopmentStudio(BIDS).Inthe
highlycompetitivebusinessworld,thetopthreechallengestoday’sdevelopersfaceare
collaboration,targetingdifferentdatabaseplatformswiththesamecodebase,andcode
stability.SSDTisdesignedtohelpwiththesechallenges.Itprovidesatoolthatenables
youtoaddvalidationsatdesigntimeandnotatruntime.Acommonpitfallfordevelopers
isthaterrorsarediscoveredatruntimewhicharen’tapparentanddon’tsurfaceatdesign
time,andSSDTservestoeliminatethisissue.
Youcancode,build,debug,package,anddeploycodewithoutleavingthetool.After
importingorcreatinganewdatabaseproject,youcanaltertheprojectpropertiestotarget
aspecificdatabaseversion.Theunderlyingcompilerusesthedatabaseversionrules
engineandcompilestheprojectbasedonthedatabaseeditionfeatures.Forexample,if
you’redevelopingcodeforSQLAzure,thetoolknowsthatyoucan’tusesequence
objects.Thistypeofbuilt-inintelligenceinthetooliskeytofastereffectivedevelopment
soyoudon’tdiscoverissuesatruntime,whichwouldrequirerearchitectingthe
application.
Thistypeoffeatureisalsohelpfulwhenyou’reupgradingfromanolderversionof
SQLtoanewerversion.Thecompilertellsyouiftheoldercodewillgenerateerrorsinthe
newerversionofSQL.
SSDTcanbeusedforconnecteddevelopmentanddisconnecteddevelopmentincase
ofateamproject.Figure2-20showstheNewProjectwindow,whichisbasedonthe
familiarSSMSObjectExplorer.
Figure2-20.SSDTNewProjectwindow
Youcancreateobjectsandbufferobjectediting,andT-SQLIntelliSenseisalsoused.
Onceyoufinalizedevelopment,youcanchoosetheplatformtodeployto,andtheproject
isdeployedwithasingleclick.
SQLProfiler
SQLProfileristheprimarytoolforanalyzingSQLServerperformance.Ifyouhavea
performanceproblembutaren’tsurewherethebottlenecklies,SQLProfilercanhelpyou
rapidlynarrowdownthesuspects.Itworksbycapturingeventsthatoccurontheserver
andloggingthemtoatracefileortable.Theclassesofeventsthatcanbecapturedare
exhaustive,coveringawiderangeofserver-sideeventsincludingT-SQLandSP
preparationandexecution,securityevents,transactionactivity,locks,anddatabase
resizing.
Whenyoucreateanewtrace,SQLProfilerallowsyoutoselectalltheeventsyouwish
toaudit.Normally,younarrowthislistasmuchaspossibleforbothperformanceand
manageabilityreasons.Figure2-21isasampletracethatcapturesT-SQL–specificevents
ontheserver.
Figure2-21.PreparingtocaptureT-SQLeventsinSQLProfiler
Onceatraceisconfiguredandrunning,itcapturesallthespecifiedeventsonthe
server.AsampletracerunusingT-SQLeventsisshowninFigure2-22.
Figure2-22.RunningatraceofT-SQLevents
Asyoucanseeintheexample,evenasimpletracethatcapturesarelativelysmall
numberofeventscaneasilybecomeoverwhelming,particularlyifrunagainstanSQL
Serverinstancewithseveralsimultaneoususerconnections.SQLProfileroffersthe
ColumnFilteroption,whichletsyoueliminateresultsfromatrace.Usingfilters,youcan
narrowtheresultstoincludeonlyactionsperformedbyspecificapplicationsorusers,or
activitiesrelevantonlytoaparticulardatabase.Figure2-23showstheEditFilterwindow
whereyouselecttracefilters.
Figure2-23.EditingfiltersinSQLProfiler
SQLProfileroffersseveraladditionaloptions,includingtracereplayandtheabilityto
savetraceresultstoeitherafileoradatabasetable.SQLProfilerisvitalto
troubleshootingSQLServerperformanceandsecurityissues.
SQLServer2014listsSQLProfilerfortracecaptureandtracereplayasdeprecated;
theywon’tbesupportedinfutureversionsofSQLServer.However,foranalysisservices
workloads,bothtracecaptureandtracereplaywillbesupported.Thereplacementfeature
forthedeprecatedfunctionalityisExtendedEvents.
ExtendedEvents
Thesedaysit’scommontohavemanycomplexsystemswithhundredsofcoresthat
supportapplicationswithascale-outmodelwithasetofSQLServers.TheSQLServers
thatsupportthecomplexapplicationsusevariousfeaturessuchascompressiontoreduce
storagecosts,highavailability,anddisaster-recoveryfeatures.Forsuchacomplexsystem,
performancemonitoringisvital:ExtendedEventsisdesignedtohandlethesecomplex
situationsanddiagnoseissuesinthesesystemswithoutaddingaperformancepenalty.
TheExtendedEvents(XEvents)diagnostictoolswasintroducedinSQL2008,andit
receivedamakeoverinSQLServer2012withanewGUIinterfacetoaideaseofuse.It’s
alightweight,asynchronouseventingsystemthatcanretrieveinformationbasedonevents
triggeredintheSQLengine.YoucanuseXEventstotrackbothhigh-levelissuessuchas
queryexecutionorblockingintheserver,andlow-levelissuesthatareveryclosetothe
SQLServercode,suchashowlongittookforthespinlockstobackoff.XEventscanbe
usedtocollectadditionaldataaboutanyeventandperformpredefinedactionssuchas
takingamemorydumpwheneventshappen;forexample,youmaybeworkingwithan
applicationwhosedeveloperrequeststhatyoutakeamemorydumpwhenaspecificquery
executes.
ResultsfromXEventscanbewrittentovarioustargets,includingtheWindowstrace
file.IfyouhaveanapplicationthatisgatheringdiagnosticinformationfromIIS,andyou
wanttocorrelatethedatafromSQLServer,writingtotheWindowstracefilewillmake
debuggingmucheasier.TheeventdatathathasbeenwrittentotheWindowstracefilecan
beviewedusingatoolsuchasXperfortracerpt.Aswithanydiagnostictool,thedatathat
iscollectedcanbesavedtomultiplelocationsincludingthefilesystem,tables,and
windowsloggingsimultaneously.Figure2-24showstheExtendedEventsuserinterface.
Figure2-24.ExtendedEventsnewsession
XEventshasbeenimplementedbytheSQLEngine,mergereplication,analysis
services,andreportingservicesinSQLServer2014.Insomeofthecomponents,suchas
analysisservices,it’stargetedinformationandnotacompleteimplementation.
TheXEventsUIisintegratedwithManagementStudio:thetreehasaseparatenode
calledExtendedEvents.Youcancreateanewsessionbyright-clickingtheExtended
Eventsnodeandselectingthesession.XEventssessionscanbebasedonpredefined
templates,oryoucancreateasessionbychoosingspecificevents.
XEventsoffersarichdiagnosticframeworkthatishighlyscalableandoffersthe
capabilitytocollectlittleorlargeamountsofdatainordertotroubleshootagiven
performanceissue.AnotherreasontostartusingXEventsisthatSQLProfilerhasbeen
markedfordeprecation.ExtendedEventsisdiscussedindetailinChapter19.
SQLServerIntegrationServices
SSISwasintroducedinSQLServer2005asthereplacementforSQLServer7.0and2000
DataTransformationServices(DTS).SSISprovidesanenterprise-classExtractTransform
Load(ETL)toolthatallowsyoutodesignsimpleorcomplexpackagestoextractdata
frommultiplesourcesandintegratethemintoyourSQLServerdatabases.Italsoprovides
richBIintegrationandextensibility.Inadditiontodatatransformations,SSISprovides
SQLServer–specifictasksthatallowyoutoperformdatabase-administrationand-
managementfunctionslikeupdatingstatisticsandrebuildingindexes.
SSISdividestheETLprocessintothreemajorparts:controlflow,dataflow,andevent
handlers.ThecontrolflowprovidesstructuretoSSISpackagesandcontrolsexecutionvia
tasks,containers,andprecedenceconstraints.Thedataflowimportsdatafromvarious
sources,transformsit,andstoresitinspecifieddestinations.Thedataflow,fromthe
perspectiveofthecontrolflow,isjustanothertask.However,thedataflowisimportant
enoughtorequireitsowndetaileddesignsurfaceinapackage.Eventhandlersallowyou
toperformactionsinresponsetopredefinedeventsduringtheETLprocess.Figure2-25
showsasimpleSSISdataflowthatimportsdatafromatableintoaflatfile.
Figure2-25.Dataflowtoimportdatafromatabletoflatfile
SSISisafarmoreadvancedETLtoolthanDTS,anditprovidessignificant
improvementsinfeatures,functionality,andrawpowerovertheoldDTStools.
TheBulkCopyProgram
Althoughitisn’tasflashyorfeature-richasSSIS,BCPissmallandfast,anditcan
performsimpleimportswithnohassle.BCPishandyforgeneratingformatfilesforBCP
andotherbulk-importtools,forone-offimportswhereafull-blownSSISpackagewould
beoverkill,forexportingdatafromdatabasetablestofiles,andforbackward
compatibilitywhenyoudon’thavetheresourcestodevotetoimmediatelyupgradingold
BCP-basedETLprocesses.
Figure2-26showsasimplecommand-linecalltoBCPtocreateaBCPformatfileand
alistingoftheformatfile.TheformatfilesgeneratedbyBCPcanbeusedbyBCP,SSIS,
andtheT-SQLBULKINSERTstatement.
Figure2-26.GeneratingaformatfilewithBCP
SQLServer2014BooksOnline
BooksOnline(BOL)istheprimaryreferenceforSQLServerprogrammingand
administration.SQLServer2014introducestheHelpViewerpiecefromtheVS2010shell
anddoesn’tincludeBOLalongwiththedefaultsetup.DuringtheSQLinstallation,you
havetheoptiontochoosethedocumentationfeature,whichinturninstallstheHelp
Viewer.
YoualsohavetheoptiontoinstalltheBOLfromanonlineresource.Youcanaccessa
locallyinstalledcopyofBOL,oryoucanaccessitovertheWebatMicrosoft’swebsite.
Thehelpdocumentationcanbefoundat
www.microsoft.com/download/en/details.aspx?id=347.Figure2-27
showsasearchofalocalcopyofBOL.
Figure2-27.SearchinglocalBOLforinformationabouttheSELECTstatement
YoucangetupdatesforBOLatwww.microsoft.com/sql/default.mspx.
TheonlineversionofSQLServer2012BOLisavailableat
http://msdn.microsoft.com/en-us/library/ms130214.aspx.Alsokeep
inmindthatyoucansearchonlineandlocalversionsofBOL,aswellasseveralother
SQLresources,viatheHelpSearchfunctiondiscussedpreviouslyinthischapter.
TipMicrosoftnowoffersanadditionaloptionforobtainingthemostup-to-date
versionofBOL.YoucandownloadthelatestBOLupdatesfromtheMicrosoftUpdate
site,athttp://update.microsoft.com/microsoftupdate.Microsofthas
announcedplanstorefreshBOLwithupdatedcontentmoreoftenandtointegrateSQL
ServerdeveloperandDBAfeedbackintoBOLmorequickly.
TheAdventureWorksSampleDatabase
SQLServer2014hastwomainsampledatabases:theAdventureWorks2014OLTP
andSQLServer2014RTMIn-MemoryOLTPdatabases.Thisbookreferstothe
AdventureWorks2014OLTPdatabaseformostexamples.Microsoftnowreleases
SQLServersampledatabasesthroughitsCodePlexwebsite.Youcandownloadthe
AdventureWorksdatabasesandassociatedsamplecodefrom
www.codeplex.com/MSFTDBProdSamples.
NoteIt’shighlyrecommendedthatyoudownloadtheSQLServer
AdventureWorks2014OLTPdatabasesothatyoucanrunthesamplecodeinthis
bookasyougothrougheachchapter.
Summary
SQLServer2014includesthetoolsyou’vecometoexpectwithanySQLServerrelease.
Thischapterhasprovidedanoverviewofseveraltoolsthatwillbeimportanttoyouasan
SQLServer2014developer.Thetoolsdiscussedincludethefollowing:
SSMS,theprimaryGUIforSQLServerdevelopmentand
administration
SQLCMD,SSMS’stext-basedcounterpart
SSDT,anintegratedtoolfordevelopers
SQLProfiler,whichsuppliesevent-captureandserver-sidetracing
capabilitiesforanalyzingSQLServerperformanceandauditing
security
ExtendedEvents,alightweight,asynchronous,event-based
troubleshootingtool
SSIS,theprimaryETLtoolforSQLServer2014
BCP,acommandline–basedbulkimporttool
BOL,thefirstplacetolookwhenyou’retryingtolocateinformation
aboutallthingsSQLServer
AdventureWorks,thefreelyavailableMicrosoft-suppliedsample
database
Thesetopicscouldeasilyfillabookbythemselves(andmany,infact,have).The
followingchaptersreviewtheSQLServer2014featuresindetail.
EXERCISES
1. SSDTisanSQLdevelopmenttool.WhattoolsdidSSDTreplace?
2. [Chooseallthatapply]SQLServer2014SSMSprovideswhichof
thefollowingfeatures?
a. Abilitytoaddcodesnippetsandcustomizethem
b. AnintegratedObjectExplorerforviewingandmanaging
theserver,databases,anddatabaseobjects
c. IntelliSense,whichsuggeststable,object,andfunction
namesasyoutypeSQLstatements
d. CustomizablekeyboardmappingschemeforVisualStudio
users
3. SSISisconsideredwhattypeoftool?
4. [True/False]SQLCMDcanusecommand-lineoptions,environment
variables,andSQLCMD:setvarcommandstosetscripting
variables.
5. [Chooseone]BCPcanbeusedtoperformwhichofthefollowing
tasks?
a. GeneratingformatfilesforusewithSSIS
b. Importingdataintotableswithoutformatfiles
c. Exportingdatafromatabletoafile
d. Alloftheabove
6. WhatisonefeaturethatExtendedEventsoffersthatSQLProfiler
doesn’t?
7. WhatarethetargetplatformsthatcanbedeployedusingSSDT?
CHAPTER3
ProceduralCode
T-SQLhasalwaysincludedsupportforproceduralprogrammingCASEexpressionsinthe
formofcontrol-of-flowstatementsandcursors.Onethingthatthrowsdevelopersfrom
otherlanguagesofftheirguardwhenmigratingtoSQListhepeculiarthree-valuedlogic
(3VL)weenjoy.Chapter1introducedyoutoSQL3VL,andthischapterexpandsfurther
onthistopic.SQL3VLisdifferentfrommostotherprogramminglanguages’simpletwo-
valuedBooleanlogic.ThischapteralsodiscussesT-SQLcontrol-of-flowconstructs,
whichallowyoutochangethenormallysequentialorderofstatementexecution.Control-
of-flowstatementsletyoubranchyourcodelogicwithstatementslikeIF…ELSE…,
performloopswithstatementslikeWHILE,andperformunconditionaljumpswiththe
GOTOstatement.You’realsointroducedtoCASEexpressionsandCASE-derivedfunctions
thatreturnvaluesbasedongivencomparisoncriteriainanexpression.Finally,wefinish
thechapterbyexplainingatopiccloselytiedtoproceduralcode:SQLcursors.
NoteTechnicallytheT-SQLTRY…CATCHandthenewerTRY_PARSEand
TRY_CONVERTarecontrol-of-flowconstructs.Butthesearespecificallyusedforerror
handlingandarediscussedinChapter18,whichdescribeserrorhandlinganddynamic
SQL.
Three-ValuedLogic
SQLServer2014,likeallANSI-compatibleSQLDBMSproducts,implementsapeculiar
formoflogicknownas3VL.3VLisnecessarybecauseSQLintroducestheconceptof
NULLtoserveasaplaceholderforvaluesthataren’tknownatthetimethey’restoredin
thedatabase.TheconceptofNULLintroducesanunknownlogicalresultintoSQL’s
ternarylogicsystem.Let’sbeginlookingatSQL3VLwithasimplesetofpropositions:
Considertheproposition“1islessthan3.”Theresultislogicallytrue
becausethevalueofthenumber1islessthanthevalueofthenumber
3.
Theproposition“5isequalto6”islogicallyfalsebecausethevalueof
thenumber5isn’tequaltothevalueofthenumber6.
Theproposition“Xisgreaterthan10”presentsabitofaproblem.The
variableXisanalgebraicplaceholderforanactualvalue.
Unfortunately,wehaven’ttoldyouwhatvalueXstandsforatthis
time.Becauseyoudon’tknowwhatthevalueofXis,youcan’tsay
thestatementistrueorfalse;insteadyoucansaytheresultis
unknown.SQLNULLrepresentsanunknownvalueinthedatabasein
muchthesamewaythatthevariableXrepresentsanunknownvalue
inthisproposition,andcomparisonswithNULLproducethesame
unknownlogicalresultinSQL.
BecauseNULLrepresentsunknownvaluesinthedatabase,comparinganythingwith
NULL(evenotherNULLs)producesanunknownlogicalresult.Figure3-1isaquick
referenceforSQLServer3VL,wherepandqrepresent3VLresultvalues.
Figure3-1.SQL3VLquickreferencechart
Asmentionedpreviously,theunknownlogicvaluesshowninthechartaretheresultof
comparisonswithNULL.Thefollowingpredicates,forexample,allevaluatetoan
unknownresult:
@x=NULL
FirstName<>NULL
PhoneNumber>NULL
IfyouusedoneoftheseasthepredicateinaWHEREclauseofaSELECTstatement,
thestatementwouldreturnnorows—SELECTwithaWHEREclausereturnsonlyrows
wheretheWHEREclausepredicateevaluatestotrue;itdiscardsrowsforwhichtheWHERE
clauseisfalseorunknown.Similarly,theINSERT,UPDATE,andDELETEstatements
withaWHEREclauseonlyaffectrowsforwhichtheWHEREclauseevaluatestotrue.
SQLServerprovidesaproprietarymechanism,theSETANSI_NULLSOFFoption,
toallowdirectequalitycomparisonswithNULLusingthe=and<>operators.Theonly
ISO-compliantwaytotestforNULLiswiththeISNULLandISNOTNULL
comparisonpredicates.WehighlyrecommendthatyoustickwiththeISO-compliantIS
NULLandISNOTNULLpredicatesforafewreasons:
ManySQLServerfeatureslikecomputedcolumns,indexedviews,
andXMLindexesrequireSETANSI_NULLSONatcreationtime.
MixingandmatchingSETANSI_NULLSsettingsinyourdatabase
canconfuseotherdeveloperswhohavetomaintainyourcode.Using
ISO-compliantNULL-handlingconsistentlyeliminatesconfusion.
SETANSI_NULLSOFFallowsdirectequalitycomparisonswith
NULL,returningtrueifyoucompareacolumnorvariabletoNULL.It
doesn’treturntrueifyoucompareNULLscontainedintwocolumns,
though,whichcanbeconfusing.
Totopitalloff,MicrosofthasdeprecatedtheSETANSI_NULLS
OFFsetting.ItwillberemovedinafutureversionofSQLServer,so
it’sagoodideatostartfuture-proofingyourcodenow.
IT’SACLOSEDWORLD,AFTERALL
Theclosed-worldassumption(CWA)isanassumptioninlogicthattheworldis
“blackandwhite,”“trueorfalse,”or“onesandzeros.”Whenappliedtodatabases,
theCWAbasicallystatesthatalldatastoredinthedatabaseistrue;everythingelseis
false.TheCWApresumesthatonlyknowledgeoftheworldthatiscompletecanbe
storedinadatabase.
NULLintroducesanopen-worldassumption(OWA)tothemix.Itallowsyoutostore
informationinthedatabasethatmayormaynotbetrue.ThismeansanSQLdatabase
canstoreincompleteknowledgeoftheworld—adirectviolationoftheCWA.Many
relationalmanagement(RM)theoristsseethisasaninconsistencyintheSQLDBMS
model.ThisargumentfillsmanyanRMtextbookandacademicblog,includingweb
siteslikeHughDarwen’sandC.J.Date’sTheThirdManifesto
(www.thethirdmanifesto.com),sowewon’tgodeeplyintothedetailshere.
JustrealizethatmanyRMexpertsdislikeSQLNULL.AsanSQLpractitionerinthe
realworld,however,youmaydiscoverthatNULLisoftenthebestoptionavailableto
accomplishmanytasks.
Control-of-FlowStatements
T-SQLimplementsprocedurallanguagecontrol-of-flowstatements,includingsuch
constructsasBEGIN…END,IF…ELSE,WHILE,andGOTO.T-SQL’scontrol-of-flow
statementsprovideaframeworkfordevelopingrichserver-sideproceduralcode.
ProceduralcodeinT-SQLdoescomewithsomecaveats,though,whichwediscussinthis
section.
TheBEGINandENDKeywords
T-SQLusesthekeywordsBEGINandENDtogroupmultiplestatementstogetherina
statementblock.TheBEGINandENDkeywordsdon’talterexecutionorderofthe
statementstheycontain,nordotheydefineanatomictransaction,limitscope,orperform
anyfunctionotherthandefiningasimplegroupingofT-SQLstatements.
Unlikeotherlanguages,suchasC++andC#,whichusebraces({})togroup
statementsinlogicalblocks,T-SQL’sBEGINandENDkeywordsdon’tdefineorlimit
scope.ThefollowingsampleC#code,forinstance,won’tevencompile:
{
intj=10;}Console.WriteLine(j);
C#programmerswillautomaticallyrecognizethatthevariablejinthepreviouscode
isdefinedinsidebraces,limitingitsscopeandmakingitaccessibleonlyinsidethebraces.
T-SQL’sroughlyequivalentcode,however,doesn’tlimitscopeinthismanner:
BEGIN
DECLARE@jint=10;
END
PRINT@j;
ThepreviousT-SQLcodeexecuteswithnoproblem,aslongastheDECLARE
statementisencounteredbeforethevariableisreferencedinthePRINTstatement.The
scopeofvariablesinT-SQLisdefinedintermsofcommandbatchesanddatabaseobject
definitions(suchasSPs,UDFs,andtriggers).Declaringtwoormorevariableswiththe
samenameinonebatchorSPresultsinerrors.
CautionT-SQL’sBEGINandENDkeywordscreateastatementblockbutdon’tdefine
ascope.VariablesdeclaredinaBEGIN…ENDblockaren’tlimitedinscopejusttothat
block,butarescopedtothewholebatch,SP,orUDFinwhichthey’redefined.
BEGIN…ENDisusefulforcreatingstatementblockswhereyouwanttoexecute
multiplestatementsbasedontheresultsofothercontrol-of-flowstatementslikeIF…
ELSEandWHILE.BEGIN…ENDcanalsohaveanotheraddedbenefitifyou’reusing
SSMS2014oragoodthird-partySQLeditorlikeApexSQLEdit(www.apexsql.com).
BEGIN…ENDcanalerttheGUIthatasectionofcodeiscollapsible.Figure3-2shows
morethanoneregionofcodethatiscollapsible.Thiscanspeedupdevelopmentandease
debugging,especiallyifyou’rewritingcomplexT-SQLscripts.
Figure3-2.BEGIN…ENDstatementblocksmarkedcollapsibleinSSMS
TipAlthoughit’snotrequired,weliketowrapthebodyofCREATEPROCEDURE
statementswithBEGIN…END.Thisclearlydelineatesthebodyofthestoredprocedure.
Thisispurelyacodingstylepreferenceandhasnoaffectonthestoredprocedure
performanceorfunction.
TheIF…ELSEStatement
Likemanyprocedurallanguages,T-SQLimplementsconditionalexecutionofcodeusing
thesimplestofproceduralstatements:theIF…ELSEconstruct.TheIFstatementis
followedbyalogicalpredicate.Ifthepredicateevaluatestotrue,thesingleSQLstatement
orstatementblockwrappedinBEGIN…ENDisexecuted.Ifthepredicateevaluatesto
eitherfalseorunknown,SQLServerfallsthroughtotheELSEstatementandexecutesthe
singlestatementorstatementblockfollowingELSE.
TipApredicateinSQLisanexpressionthatevaluatestooneofthelogicalresultstrue,
false,orunknown.PredicatesareusedinIF…ELSEstatements,WHEREclauses,and
anywherethatalogicalresultisneeded.
TheexampleinListing3-1performsuptothreecomparisonstodeterminewhethera
variableisequaltoaspecifiedvalue.ThesecondELSEstatementexecutesifandonlyif
thetestsforbothtrueandfalseconditionsfail.
Listing3-1.SimpleIF…ELSEExample
DECLARE@iint=NULL;
IF@i=10
PRINT'TRUE.';
ELSEIFNOT(@i=10)
PRINT'FALSE.';
ELSE
PRINT'UNKNOWN.';
Becausethevariable@iisNULLintheexample,SQLServerreportsthattheresultis
unknown.Ifyouassignthevalue10tothevariable@i,SQLServerwillreportthatthe
resultistrue;allothervalueswillreportfalse.
TocreateastatementblockcontainingmultipleT-SQLstatementsaftereithertheIF
statementortheELSEstatement,simplywrapyourstatementswiththeT-SQLBEGIN
andENDkeywordsdiscussedintheprevioussection.TheexampleinListing3-2isan
IF…ELSEstatementwithstatementblocks.TheexampleusesIF…ELSEtocheckthe
valueofthevariable@direction.If@directionisASCENDING,amessageis
printed,andthetoptennames,inorderoflastname,areselectedfromthe
Person.Contacttable.If@directionisDESCENDING,adifferentmessageis
printed,andthebottomtennamesareselectedfromthePerson.Contacttable.Any
othervalueresultsinamessagethat@directionwasnotrecognized.Theresultsof
Listing3-2areshowninFigure3-3.
Listing3-2.IF…ELSEwithStatementBlocks
DECLARE@directionNVARCHAR(20)=N'DESCENDING';
IF@direction=N'ASCENDING'
BEGIN
PRINT'Startatthetop!';
SELECTTOP(10)
LastName,
FirstName,
MiddleName
FROMPerson.Person
ORDERBYLastNameASC;
END
ELSEIF@direction=N'DESCENDING'
BEGIN
PRINT'Startatthebottom!';
SELECTTOP(10)
LastName,
FirstName,
MiddleName
FROMPerson.Person
ORDERBYLastNameDESC;
ENDs
ELSE
PRINT'@directionwasnotrecognized!';
Figure3-3.ThelasttencontactnamesintheAdventureWorksdatabase
TheWHILE,BREAK,andCONTINUEStatements
Loopingisastandardfeatureofprocedurallanguages,andT-SQLprovideslooping
supportthroughtheWHILEstatementanditsassociatedBREAKandCONTINUE
statements.TheWHILEloopisimmediatelyfollowedbyapredicate;WHILEexecutesa
givenSQLstatementorstatementblockboundedbytheBEGINandENDkeywordsas
longastheassociatedpredicateevaluatestotrue.Ifthepredicateevaluatestofalseor
unknown,thecodeintheWHILEloopdoesn’texecuteandcontrolpassestothenext
statementaftertheWHILEloop.TheWHILEloopinListing3-3isaverysimpleexample
thatcountsfrom1to10.TheresultisshowninFigure3-4.
Listing3-3.WHILEStatementExample
DECLARE@iint=1;
WHILE@i<=10
BEGIN
PRINT@i;
SET@i=@i+1;
END
Figure3-4.Countingfrom1to10withWHILE
TipBesuretoupdateyourcounterorotherflagintheWHILEloop.TheWHILE
statementwillkeeploopinguntilitspredicateevaluatestofalseorunknown.Asimple
codingmistakecancreateanastyinfiniteloop.
T-SQLalsoincludestwoadditionalkeywordsthatcanbeusedwiththeWHILE
statement:BREAKandCONTINUE.TheCONTINUEkeywordforcestheWHILEloopto
immediatelyjumptothestartofthecodeblock,asinthemodifiedexampleinListing3-4.
Listing3-4.WHILE…CONTINUEExample
DECLARE@iint=1;
WHILE@i<=10
BEGIN
PRINT@i;
SET@i=@i+1;
CONTINUE;—ForcetheWHILElooptorestart
PRINT'TheCONTINUEkeywordensuresthatthiswillneverbe
printed.';
END
TheBREAKkeyword,ontheotherhand,forcestheWHILElooptoterminate
immediately.InListing3-5,BREAKforcestheWHILElooptoexitduringthefirst
iterationsothatthenumbers2through10areneverprinted.
Listing3-5.WHILE…BREAKExample
DECLARE@iint=1;
WHILE@i<=10
BEGIN
PRINT@i;
SET@i=@i+1;
BREAK;—ForcetheWHILElooptoterminate
PRINT'TheBREAKkeywordensuresthatthiswillneverbe
printed.';
END
TipBREAKandCONTINUEshouldbeavoidedinmostcases.It’snotuncommonto
seeaWHILEl=lstatementwithaBREAKinthebodyoftheloop.Thiscanalwaysbe
rewritten,usuallyveryeasily,toremovetheBREAKstatement.Mostofthetime,the
BREAKandCONTINUEkeywordsintroduceadditionalcomplexitytoyourlogicand
causemoreproblemsthantheysolve.
TheGOTOStatement
DespiteEdsgerW.Dijkstra’sbesteffortsatwarningdevelopers(seeDijkstra’s1968letter,
“GoToStatementConsideredHarmful”),1T-SQLstillhasaGOTOstatement.TheGOTO
statementtransferscontrolofyourprogramtoaspecifiedlabelunconditionally.Labelsare
definedbyplacingthelabelidentifieronalinefollowedbyacolon(:),asshownin
Listing3-6.Thissimpleexampleexecutesitsstep1andusesGOTOtodivestraightinto
step3,skippingstep2.TheresultsareshowninFigure3-5.
Listing3-6.SimpleGOTOExample
PRINT'Step1Begin.';
GOTOStep3_Label;
PRINT'Step2willnotbeprinted.';
Step3_Label:
PRINT'Step3End.';
Figure3-5.TheGOTOstatementtransferscontrolunconditionally
TheGOTOstatementisbestavoided,becauseitcanquicklydegenerateyourprograms
intounstructuredspaghetticode.Whenyouhavetowriteproceduralcode,you’remuch
betteroffusingstructuredprogrammingconstructslikeIF…ELSEandWHILEstatements.
TheWAITFORStatement
TheWAITFORstatementsuspendsexecutionofatransaction,SP,orT-SQLcommand
batchuntilaspecifiedtimeisreached,atimeintervalhaselapsed,oramessageis
receivedfromServiceBroker.
NoteServiceBrokerisanSQLServermessagingsystem.Wedon’tdetailService
Brokerinthisbook,butyoucanfindoutmoreaboutitinProSQLServer2008Service
Broker,byKlausAschenbrenner(Apress,2008).
TheWAITFORstatementhasaDELAYoptionthattellsSQLServertosuspendcode
executionuntiloneofthefollowingcriteriaismetoraspecifiedtimeintervalhaselapsed.
Thetimeintervalisspecifiedasavalidtimestringintheformathh:mm:ss.Thetime
intervalcan’tcontainadateportion;itmustonlyincludethetime,anditcanbeupto24
hours.Listing3-7isanexampleoftheWAITFORstatementwiththeDELAYoption,
whichblocksexecutionofthebatchfor3seconds.
WAITFORCAVEATS
TherearesomecaveatsassociatedwiththeWAITFORstatement.Insomesituations,
WAITFORcancauselongerdelaysthantheintervalyouspecify.SQLServeralso
assignseachWAITFORstatementitsownthread,andifSQLServerbegins
experiencingthreadstarvation,itcanrandomlystopWAITFORthreadstofreeup
threadresources.Ifyouneedtodelayexecutionforanexactamountoftime,youcan
guaranteemoreconsistentresultsbysuspendingexecutionthroughanexternal
applicationlikeSQLServerIntegrationServices(SSIS).
InadditiontoitsDELAYandTIMEoptions,youcanuseWAITFORwiththe
RECEIVEandGETCONVERSATIONGROUPoptionswithServiceBroker–
enabledapplications.WhenyouuseWAITFORwithRECEIVE,thestatementwaits
forreceiptofoneormoremessagesfromaspecifiedqueue.
WhenyouuseWAITFORwiththeGETCONVERSATIONGROUPoption,itwaits
foraconversationgroupidentifierofamessage.GETCONVERSATIONGROUP
allowsyoutoretrieveinformationaboutamessageandlocktheconversationgroup
fortheconversationcontainingthemessage,allbeforeretrievingthemessageitself.
Listing3-7.WAITFORExample
PRINT'Step1complete.';
GO
DECLARE@time_to_passnvarchar(8);
SELECT@time_to_pass=N'00:00:03';
WAITFORDELAY@time_to_pass;
PRINT'Step2completedthreesecondslater.';
YoucanalsousetheTIMEoptionwiththeWAITFORstatement.IfyouusetheTIME
option,SQLServerwaitsuntiltheappointedtimebeforeallowingexecutiontocontinue.
Datetimevariablesareallowed,butthedateportionisignoredwhentheTIMEoptionis
used.
TheRETURNStatement
TheRETURNstatementexitsunconditionallyfromanSPorcommandbatch.Whenyou
useRETURN,youcanoptionallyspecifyanintegerexpressionasareturnvalue.The
RETURNstatementreturnsagivenintegerexpressiontothecallingroutineorbatch.If
youdon’tspecifyanintegerexpressiontoreturn,avalueof0isreturnedbydefault.
RETURNisn’tnormallyusedtoreturncalculatedresults,exceptforUDFs,whichoffer
moreRETURNoptions(asdetailedinChapter4).ForSPsandcommandbatches,the
RETURNstatementisusedalmostexclusivelytoreturnasuccessindicator,afailure
indicator,oranerrorcode.
WHATNUMBER,SUCCESS?
AllsystemSPsreturn0toindicatesuccess,oranonzerovaluetoindicatefailure
(unlessotherwisedocumentedinBOL).It’sconsideredbadformtousetheRETURN
statementtoreturnanythingotherthananintegerstatuscodefromascriptorSP.
UDFs,ontheotherhand,havetheirownrules.UDFshaveaflexiblevariationofthe
RETURNstatement,whichexitsthebodyoftheUDF.Infact,aUDFrequiresthe
RETURNstatementbeusedtoreturnscalarortabularresultstothecaller.Yousee
UDFsagainindetailinChapter4.
NoteThereareacoupleofmethodsinT-SQLtoredirectlogicflowbasedonerrors.
TheseincludetheTRY…CATCHstatementandtheTHROWstatement.Bothstatementsare
discussedindetailinChapter18.
TheCASEExpression
TheT-SQLCASEfunctionisSQLServer’simplementationoftheISOSQLCASE
expression.WhereasthepreviouslydiscussedT-SQLcontrol-of-flowstatementsallowfor
conditionalexecutionofSQLstatementsorstatementblocks,theCASEexpressionallows
forset-basedconditionalprocessinginasinglequery.CASEprovidestwosyntaxes,
simpleandsearched,whicharediscussedinthissection.
TheSimpleCASEExpression
ThesimpleCASEexpressionreturnsaresultexpressionbasedonthevalueofagiven
inputexpression.ThesimpleCASEexpressioncomparestheinputexpressiontoaseries
ofexpressionsfollowingWHENkeywords.Onceamatchisencountered,CASEreturnsa
correspondingresultexpressionfollowingthekeywordTHEN.Ifnomatchisfound,the
expressionfollowingthekeywordELSEisreturned.NULLisreturnedifnoELSE
keywordissupplied.
ConsidertheexampleinListing3-8,whichusesasimpleCASEexpressiontocount
alltheAdventureWorkscustomersontheWestCoast(arbitrarilydefinedasthestatesof
California,Washington,andOregon).Thequeryalsousesacommontableexpression
(CTE,discussedmorethoroughlyinChapter9).TheresultsareshowninFigure3-6.
Listing3-8.CountingWestCoastCustomerswithaSimpleCASEExpression
WITHEmployeesByRegion(Region)
AS
(
SELECT
CASEsp.StateProvinceCode
WHEN'CA'THEN'WestCoast'
WHEN'WA'THEN'WestCoast'
WHEN'OR'THEN'WestCoast'
ELSE'Elsewhere'
END
FROMHumanResources.Employeee
INNERJOINPerson.Personp
ONe.BusinessEntityID=p.BusinessEntityID
INNERJOINPerson.BusinessEntityAddressbea
ONbea.BusinessEntityID=e.BusinessEntityID
INNERJOINPerson.Addressa
ONa.AddressID=bea.AddressID
INNERJOINPerson.StateProvincesp
ONsp.StateProvinceID=a.StateProvinceID
WHEREsp.CountryRegionCode='US'
)
SELECTCOUNT(Region)ASNumOfEmployees,Region
FROMEmployeesByRegion
GROUPBYRegion;
Figure3-6.ResultsoftheWestCoastCustomerCount
TheCASEexpressioninthesubquerycomparestheStateProvinceCodevalueto
eachofthestatecodesfollowingtheWHENkeywords,returningthenameWestCoast
whenStateProvinceCodeisequaltoCA,WA,orOR.Foranyother
StateProvinceCodeintheUnitedStates,itreturnsavalueofElsewhere:
SELECTCASEsp.StateProvinceCode
WHEN'CA'THEN'WestCoast'
WHEN'WA'THEN'WestCoast'
WHEN'OR'THEN'WestCoast'
ELSE'Elsewhere'
END
Theremainderoftheexamplecountsthenumberofrowsreturnedbythequery,
groupedbyRegion.
ASIMPLECASEOFNULL
ThesimpleCASEexpressionperformsbasicequalitycomparisonsbetweentheinput
expressionandtheexpressionsfollowingtheWHENkeywords.Thismeansyoucan’t
usethesimpleCASEexpressiontocheckforNULLs.Recallfromthe“Three-Valued
Logic”sectionofthischapterthataNULL,whencomparedtoanything,returns
unknown.ThesimpleCASEexpressiononlyreturnstheexpressionfollowingthe
THENkeywordwhenthecomparisonreturnstrue.Thismeansifyouevertrytouse
NULLinaWHENexpression,thecorrespondingTHENexpressionwon’tbereturned.
IfyouneedtocheckforNULLinaCASEexpression,useasearchedCASE
expressionwiththeISNULLorISNOTNULLcomparisonoperators.
TheSearchedCASEExpression
ThesearchedCASEexpressionprovidesamechanismforperformingmorecomplex
comparisons.ThesearchedCASEevaluatesaseriesofpredicatesfollowingWHEN
keywordsuntilitencountersonethatevaluatestotrue.Atthatpoint,itreturnsthe
correspondingresultexpressionfollowingtheTHENkeyword.Ifnoneofthepredicates
evaluatestotrue,theresultfollowingtheELSEkeywordisreturned.Ifnoneofthe
predicatesevaluatestotrueandELSEisn’tsupplied,thesearchedCASEexpression
returnsNULL.
PredicatesinthesearchedCASEexpressioncantakeadvantageofanyvalidSQL
comparisonoperators(suchas<,>,=,LIKE,andIN).ThesimpleCASEexpressionfrom
Listing3-8canbeeasilyexpandedtocovermultiplegeographicregionsusingthe
searchedCASEexpressionandtheINlogicaloperator,asshowninListing3-9.This
exampleusesasearchedCASEexpressiontogroupstatesintoWestCoast,Pacific,and
NewEnglandregions.TheresultsareshowninFigure3-7.
Listing3-9.CountingEmployeesbyRegionwithaSearchedCASEExpression
WITHEmployeesByRegion(Region)
AS
(
SELECT
CASEWHENsp.StateProvinceCodeIN('CA','WA','OR')
THEN'WestCoast'
WHENsp.StateProvinceCodeIN('HI','AK')THEN
'Pacific'
WHENsp.StateProvinceCodeIN('CT','MA','ME','NH',
'RI','VT')
THEN'NewEngland'
ELSE'Elsewhere'
END
FROMHumanResources.Employeee
INNERJOINPerson.Personp
ONe.BusinessEntityID=p.BusinessEntityID
INNERJOINPerson.BusinessEntityAddressbea
ONbea.BusinessEntityID=e.BusinessEntityID
INNERJOINPerson.Addressa
ONa.AddressID=bea.AddressID
INNERJOINPerson.StateProvincesp
ONsp.StateProvinceID=a.StateProvinceID
WHEREsp.CountryRegionCode='US'
)
SELECTCOUNT(Region)ASNumOfCustomers,Region
FROMEmployeesByRegion
GROUPBYRegion;
Figure3-7.Resultsoftheregionalcustomercount
ThesearchedCASEexpressionintheexampleusestheINoperatortoreturnthe
geographicareathatStateProvinceCodeisin:California,Washington,andOregon
allreturnWestCoast;andConnecticut,Massachusetts,Maine,NewHampshire,Rhode
Island,andVermontallreturnNewEngland.IftheStateProvinceCodedoesn’tfitin
oneoftheseregions,thesearchedCASEexpressionreturnsElsewhere:
SELECT
CASEWHENsp.StateProvinceCodeIN('CA','WA','OR')THEN
'WestCoast'
WHENsp.StateProvinceCodeIN('HI','AK')THEN
'Pacific'
WHENsp.StateProvinceCodeIN('CT','MA','ME','NH',
'RI','VT')
THEN'NewEngland'
ELSE'Elsewhere'
END
ThebalanceofthesamplecodeinListing3-9countstherowsreturned,groupedby
Region.TheCASEexpression,eithersimpleorsearched,canbeusedinSELECT,
UPDATE,INSERT,MERGE,andDELETEstatements.
ACASEBYANYOTHERNAME
Manyprogrammingandquerylanguagesofferexpressionsthatareanalogoustothe
SQLCASEexpression.C++andC#,forinstance,offerthe?:operator,which
fulfillsthesamefunctionasasearchedCASEexpression.XQueryhasitsownflavor
ofif…then…elseexpressionthatisalsoequivalenttotheSQLsearchedCASE.
C#andVisualBasicsupplytheswitchandSelectstatements,respectively,
whicharesemi-analogoustoSQL’ssimpleCASEexpression.Themaindifference,of
course,isthatSQL’sCASEexpressionreturnsascalarvalue,whereastheC#and
VisualBasicstatementsactuallycontrolprogramflow,allowingyoutoexecute
statementsbasedonanexpression’svalue.Thesimilaritiesanddifferencesbetween
SQLexpressionsandstatementsandsimilarconstructsinotherlanguagesprovidea
greatstartingpointforlearningthenitty-grittydetailsofT-SQL.
CASEandPivotTables
Manytimes,businessreportingrequirementsdictatethataresultshouldbereturnedin
pivottableformat.Pivottableformatsimplymeansthelabelsforcolumnsand/orrowsare
generatedfromthedatacontainedinrows.MicrosoftAccessandExcelusershavelong
hadtheabilitytogeneratepivottablesontheirdata,andSQLServer2014supportsthe
PIVOTandUNPIVOToperatorsintroducedinSQLServer2005.BackinthedaysofSQL
Server2000andbefore,however,CASEexpressionsweretheonlymethodofgenerating
pivottable–typequeries.AndeventhoughSQLServer2014providesthePIVOTand
UNPIVOToperators,trulydynamicpivottablesstillrequireusingCASEexpressionsand
dynamicSQL.ThestaticpivottablequeryshowninListing3-10returnsapivottable–
formattedresultwiththetotalnumberofordersforeachAdventureWorkssalesregionin
theUnitedStates.TheresultsareshowninFigure3-8.
Listing3-10.CASE-StylePivotTable
SELECT
t.CountryRegionCode,
SUM
(
CASEWHENt.Name='Northwest'THEN1
ELSE0
END
)ASNorthwest,
SUM
(
CASEWHENt.Name='Northeast'THEN1
ELSE0
END
)ASNortheast,
SUM
(
CASEWHENt.Name='Southwest'THEN1
ELSE0
END
)ASSouthwest,
SUM
(
CASEWHENt.Name='Southeast'THEN1
ELSE0
END
)ASSoutheast,
SUM
(
CASEWHENt.Name='Central'THEN1
ELSE0
END
)ASCentral
FROMSales.SalesOrderHeadersoh
INNERJOINSales.SalesTerritoryt
ONsoh.TerritoryID=t.TerritoryID
WHEREt.CountryRegionCode='US'
GROUPBYt.CountryRegionCode;
Figure3-8.Numberofsalesbyregioninpivottableformat
ThistypeofstaticpivottablecanalsobeusedwiththeSQLServer2014PIVOT
operator.ThesamplecodeinListing3-11usesthePIVOToperatortogeneratethesame
resultastheCASEexpressionsinListing3-10.
Listing3-11.PIVOTOperatorPivotTable
SELECT
CountryRegionCode,
Northwest,
Northeast,
Southwest,
Southeast,
Central
FROM
(
SELECT
t.CountryRegionCode,
t.Name
FROMSales.SalesOrderHeadersoh
INNERJOINSales.SalesTerritoryt
ONsoh.TerritoryID=t.TerritoryID
WHEREt.CountryRegionCode='US'
)p
PIVOT
(
COUNT(Name)
FORName
IN
(
Northwest,
Northeast,
Southwest,
Southeast,
Central
)
)ASpvt;
Onoccasion,youmayneedtorunapivottable–stylereportwhereyoudon’tknowthe
columnnamesinadvance.Thisisadynamicpivottablescriptthatusesatemporarytable
anddynamicSQLtogenerateapivottable,withoutspecifyingthecolumnnamesin
advance.Listing3-12demonstratesonemethodofgeneratingdynamicpivottablesinT-
SQL.TheresultsareshowninFigure3-9.
Listing3-12.DynamicPivotTableQuery
--Declarevariables
DECLARE@sqlnvarchar(4000);
DECLARE@temp_pivottable
(
TerritoryIDintNOTNULLPRIMARYKEY,
CountryRegionnvarchar(20)NOTNULL,
CountryRegionCodenvarchar(3)NOTNULL
);
--Getcolumnnamesfromsourcetablerows
INSERTINTO@temp_pivot
(
TerritoryID,
CountryRegion,
CountryRegionCode
)
SELECT
TerritoryID,
Name,
CountryRegionCode
FROMSales.SalesTerritory
GROUPBY
TerritoryID,
Name,
CountryRegionCode;
--GeneratedynamicSQLquery
SET@sql=N'SELECT'+
SUBSTRING(
(
SELECTN',SUM(CASEWHENt.TerritoryID='+
CAST(TerritoryIDASNVARCHAR(3))+
N'THEN1ELSE0END)AS'+QUOTENAME(CountryRegion)
AS"*"
FROM@temp_pivot
FORXMLPATH('')
),2,4000)+
N'FROMSales.SalesOrderHeadersoh'+
N'INNERJOINSales.SalesTerritoryt'+
N'ONsoh.TerritoryID=t.TerritoryID;';
--PrintandexecutedynamicSQL
PRINT@sql;
EXEC(@sql);
Figure3-9.Dynamicpivottableresult
ThescriptinListing3-12firstdeclaresannvarcharvariablethatholdsthe
dynamicallygeneratedSQLscriptandatablevariablethatholdsallthecolumnnames,
whichareretrievedfromtherowvaluesinthesourcetable:
--Declarevariables
DECLARE@sqlnvarchar(4000);
DECLARE@temp_pivottable
(
TerritoryIDintNOTNULLPRIMARYKEY,
CountryRegionnvarchar(20)NOTNULL,
CountryRegionCodenvarchar(3)NOTNULL
);
Next,thescriptgrabsalistofdistinctterritory-specificvaluesfromthetableand
storestheminthe@temp_pivottablevariable.Thesevaluesfromthetablebecome
columnnamesinthepivottableresult:
--Getcolumnnamesfromsourcetablerows
INSERTINTO@temp_pivot
(
TerritoryID,
CountryRegion,
CountryRegionCode
)
SELECT
TerritoryID,
Name,
CountryRegionCode
FROMSales.SalesTerritory
GROUPBY
TerritoryID,
Name,
CountryRegionCode;
ThescriptthenusesFORXMLPATHtoefficientlygeneratethedynamicSQL
SELECTquerythatcontainsCASEexpressionsandcolumnnamesgenerateddynamically
basedonthevaluesinthe@temppivottablevariable.ThisSELECTquerycreatesthe
dynamicpivottableresult:
--GeneratedynamicSQLquery
SET@sql=N'SELECT'+
SUBSTRING(
(
SELECTN',SUM(CASEWHENt.TerritoryID='+
CAST(TerritoryIDASNVARCHAR(3))+
N'THEN1ELSE0END)AS'+QUOTENAME(CountryRegion)
AS"*"
FROM@temp_pivot
FORXMLPATH('')
),2,4000)+
N'FROMSales.SalesOrderHeadersoh'+
N'INNERJOINSales.SalesTerritoryt'+
N'ONsoh.TerritoryID=t.TerritoryID;';
Finally,thedynamicpivottablequeryisprintedoutandexecutedwiththeT-SQL
PRINTandEXECstatements:
--PrintandexecutedynamicSQL
PRINT@sql;
EXEC(@sql);
Listing3-13showsthedynamicSQLpivottablequerygeneratedbythecodein
Listing3-12.
Listing3-13.AutogeneratedDynamicSQLPivotTableQuery
SELECTSUM
(
CASEWHENt.TerritoryID=1THEN1
ELSE0
END
)AS[Northwest],
SUM
(
CASEWHENt.TerritoryID=2THEN1
ELSE0
END
)AS[Northeast],
SUM
(
CASEWHENt.TerritoryID=3THEN1
ELSE0
END
)AS[Central],
SUM
(
CASEWHENt.TerritoryID=4THEN1
ELSE0
END
)AS[Southwest],
SUM
(
CASEWHENt.TerritoryID=5THEN1
ELSE0
END
)AS[Southeast],
SUM
(
CASEWHENt.TerritoryID=6THEN1
ELSE0
END
)AS[Canada],
SUM
(
CASEWHENt.TerritoryID=7THEN1
ELSE0
END
)AS[France],
SUM
(
CASEWHENt.TerritoryID=8THEN1
ELSE0
END
)AS[Germany],
SUM
(
CASEWHENt.TerritoryID=9THEN1
ELSE0
END
)AS[Australia],
SUM
(
CASEWHENt.TerritoryID=10THEN1
ELSE0
END
)AS[UnitedKingdom]
FROMSales.SalesOrderHeadersoh
INNERJOINSales.SalesTerritoryt
ONsoh.TerritoryID=t.TerritoryID;
CautionAnytimeyouusedynamicSQL,makesureyoutakeprecautionsagainstSQL
injection—thatis,maliciousSQLcodebeinginsertedintoyourSQLstatements.This
exampleusestheQUOTENAMEfunctiontoquotethecolumnnamesbeingdynamically
generated,tohelpavoidSQLinjectionproblems.Chapter18coversdynamicSQLand
SQLinjectioningreaterdetail.
TheIIFStatement
SQLServer2012simplifiedthestandardCASEstatementbyintroducingtheconceptofan
IIFstatement.YougetthesameresultsasyouwouldusingtheCASEstatementbutwith
muchlesscode.ThosefamiliarwithMicrosoft.NETwillbegladtoseethatthesame
functionalityisnowpartofT-SQL.
Thesyntaxissimple.ThecommandtakesaBooleanexpression,avalueforwhenthe
expressionequatestotrue,andavalueforwhentheexpressionequatestofalse.Listing3-
14showtwoexamples:oneusesvariables,andtheotherusestablecolumns.Theoutput
forbothstatementsisshowninFigure3-10.
Listing3-14.ExamplesUsingtheIIFstatement
--Example1.IIFStatementUsingVariables
DECLARE@valueAint=85
DECLARE@valueBint=45
SELECTIIF(@valueA<@valueB,'True','False')ASResult
--Example2.IIFStatementUsingTableColumn
SELECTIIF(Namein('Alberta','BritishColumbia'),
'Canada',Name)
FROM[Person].[StateProvince]
Figure3-10.PartialoutputofIIFstatements
CHOOSE
AnotherlogicalfunctionintroducedinSQLServer2012istheCHOOSEfunction.The
CHOOSEfunctionallowsyoutoselectamemberofanarraybasedonanintegerindex
value.Simplyput,CHOOSEletsyouselectamemberfromalist.Thememberyouselect
canbebasedoneitherastaticindexvalueoracomputedvalue.Thesyntaxforthe
CHOOSEfunctionisasfollows:
CHOOSE(index,val_1,val_2[,val_n])
Iftheindexvalueisn’taninteger(let’ssayit’sadecimal),thenSQLconvertsittoan
integer.Iftheindexvalueisoutofrangefortheindex,thenthefunctionreturnsNULL.
Listing3-15showsasimpleexample,andFigure3-11showstheoutput.Theexample
usestheintegervalueofPhoneNumberTypeIDtodeterminethetypeofphone.Inthis
case,thephonetypeisdefinedinthetable,soaCHOOSEfunctionwouldn’tbenecessary;
butinothercases,thevaluemaynotbedefined.
Listing3-15.ExampleUsingtheCHOOSEStatement
SELECTp.FirstName,
pp.PhoneNumber,
CHOOSE(pp.PhoneNumberTypeID,'Cell','Home','Work')
'PhoneType'
FROMPerson.Personp
JOINPerson.PersonPhonepp
ONp.BusinessEntityID=pp.BusinessEntityID
Figure3-11.PartialoutputoftheCHOOSEstatement
COALESCEandNULLIF
TheCOALESCEfunctiontakesalistofexpressionsasargumentsandreturnsthefirstnon-
NULLvaluefromthelist.COALESCEisdefinedbyISOasshorthandforthefollowing
equivalentsearchedCASEexpression:
CASE
WHEN(expression1ISNOTNULL)THENexpression1WHEN
(expression2ISNOTNULL)THENexpression2
[..."]END
ThefollowingCOALESCEfunctionreturnsthevalueofMiddleNamewhen
MiddleNameisnotNULL,andthestringNoMiddleNamewhenMiddleNameis
NULL:
COALESCE(MiddleName,'NoMiddleName')
TheNULLIFfunctionacceptsexactlytwoarguments.NULLIFreturnsNULLifthe
twoexpressionsareequal,anditreturnsthevalueofthefirstexpressionifthetwo
expressionsaren’tequal.NULLIFisdefinedbytheISOstandardasequivalenttothe
followingsearchedCASEexpression:
CASEWHENexpression1=expression2THENNULL
ELSEexpression1
END
NULLIFisoftenusedinconjunctionwithCOALESCE.ConsiderListing3-16,which
combinesCOALESCEwithNULLIFtoreturnthestring“ThisisNULLorA”ifthe
variable@sissettothecharactervalueAorNULL.
Listing3-16.UsingCOALESCEwithNULLIF
DECLARE@svarchar(10);
SELECT@s='A';
SELECTCOALESCE(NULLIF(@s,'A'),'ThisisNULLorA');
T-SQLhaslonghadalternatefunctionalitysimilartoCOALESCE.Specifically,the
ISNULLfunctionacceptstwoparametersandreturnsNULLifthey’reequal.
COALESCEORISNULL?
TheT-SQLfunctionsCOALESCEandISNULLperformsimilarfunctions,butwhich
oneshouldyouuse?COALESCEismoreflexiblethanISNULLandiscompliant
withtheISOstandardtoboot.Thismeansit’salsothemoreportableoptionamong
ISO-compliantsystems.COALESCEalsoimplicitlyconvertstheresulttothedata
typewiththehighestprecedencefromthelistofexpressions.ISNULLimplicitly
convertstheresulttothedatatypeofthefirstexpression.Finally,COALESCEisabit
lessconfusingthanISNULL,especiallyconsideringthatthere’salreadya
comparisonoperatorcalledISNULL.Ingeneral,werecommendusingthe
COALESCEfunctioninsteadofISNULL.
Cursors
ThewordcursorcomesfromtheLatinwordforrunner,andthatisexactlywhataT-SQL
cursordoes:it“runs”througharesultset,returningonerowatatime.ManyT-SQL
programmingexpertsrailagainsttheuseofcursorsforavarietyofreasons—thechief
amongtheseincludethefollowing:
Cursorsusealotofoverhead,oftenmuchmorethananequivalentset-
basedapproach.
CursorsoverrideSQLServer’sbuilt-inqueryoptimizations,often
makingthemmuchslowerthananequivalentset-basedsolution.
Becausecursorsareproceduralinnature,they’reoftentheslowestwaytomanipulate
datainT-SQL.Ratherthanspendthebalanceofthechapterrantingagainstcursoruse,
however,we’dliketointroduceT-SQLcursorfunctionalityandplaydevil’sadvocateto
pointoutsomeareaswherecursorsprovideanadequatesolution.
Thefirstsuchareawherewecanrecommendtheuseofcursorsisinscriptsor
proceduresthatperformadministrativetasks.Inadministrativetasks,thefollowingitems
oftenholdtrue:
Unlikenormaldataqueriesanddatamanipulationsthatareperformed
dozens,hundreds,orpotentiallythousandsoftimesperday,
administrativetasksareoftenperformedonaone-offbasisorona
regularschedulelikeonceperday.
AdministrativetasksoftenrequirecallinganSPorexecutinga
proceduralcodeblockonceforeachrowwhenthetasksarebasedon
atableofentries.
Administrativetasksgenerallydon’tneedtoqueryormanipulate
massiveamountsofdatatoperformtheirjobs.
Theorderofthestepsinwhichadministrativetasksareperformedand
theorderofthedatabaseobjectstheytouchareoftenimportant.
ThesampleSPinListing3-17isanexampleofanadministrativetaskperformedwith
aT-SQLcursor.Thesampleusesacursortoloopthroughallindexesonallusertablesin
thecurrentdatabase.ItthencreatesdynamicSQLstatementstorebuildeveryindexwhose
fragmentationlevelisaboveauser-specifiedthreshold.TheresultsareshowninFigure3-
12.Beawarethatyourresultsmayreturndifferentvaluesforeachrow.
Listing3-17.SampleAdministrativeTaskPerformedwithaCursor
CREATEPROCEDUREdbo.RebuildIndexes
(@ShowOrRebuiIdnvarchar(10)=N'show'
,@MaxFragdecimal(20,2)=20.0
)
AS
SETNOCOUNTON;
BEGIN
—Declarevariables
DECLARE
@Schemanvarchar(128),@Tablenvarchar(128)
,@Indexnvarchar(128),@Sqlnvarchar(4000)
,@DatabaseIdint,@SchemaIdint
,@TableIdint,@lndexIdint;
—Createtheindexlisttable
DECLARE@IndexListTABLE
(DatabaseNamenvarchar(128)NOTNULL
,DatabaseIdintNOTNULL
,SchemaNamenvarchar(128)NOTNULL
,SchemaIdintNOTNULL
,TableNamenvarchar(128)NOTNULL
,TableIdintNOTNULL
,IndexNamenvarchar(128)
,IndexIdintNOTNULL
,Fragmentationdecimal(20,2)
,PRIMARYKEY(DatabaseId,SchemaId,TableId,IndexId)
);
—Populateindexlisttable
INSERTINTO@IndexList
(DatabaseName,DatabaseId
,SchemaName,SchemaId
,TableName,TableId
,IndexName,IndexId
,Fragmentation
)
SELECTdb_name(),db_id()
,s.Name,s.schema_id
,t.Name,t.object_id
,i.Name,i.index_id
,MAX(ip.avg_fragmentation_in_percent)
FROMsys.tablest
INNERJOINsys.schemassON
t.schema_id=s.schema_id
INNERJOINsys.indexesiON
t.object_id=i.object_id
INNERJOINsys.dm_db_index_physical_stats(db_id(),NULL,
NULL,NULL,NULL)ipON
ip.object_id=t.object_idANDip.index_id=i.index_id
WHEREip.database_id=db_id()
GROUPBY
s.Name
,s.schema_id
,t.Name
,t.object_id
,i.Name
,i.index_id;
—IfuserspecifiedrebuiId,useacursortoloopthrough
allindexes
—rebuiIdthem
IF@ShowOrRebuiId=N'rebuiId'
BEGIN
—DeclareacursortocreatethedynamicSQLstatements
DECLAREIndex_CursorCURSORFAST_FORWARD
FORSELECTSchemaName,TableName,IndexName
FROM@IndexList
WHEREFragmentation>@MaxFrag
ORDERBYFragmentationDESC,TableNameASC,IndexName
ASC;
—Openthecursorforreading
OPENIndex_Cursor;
—Loopthroughallthetablesinthedatabase
FETCHNEXTFROMIndex_Cursor
INTO@Schema,@Table,@Index;
WHILE@@FETCH_STATUS=0
BEGIN—CreateALTERINDEXstatementtorebuiIdindex
SET@Sql=N'ALTERINDEX'+
QUOTENAME(RTRIM(@Index))+N'ON'
+QUOTENAME(RTRIM(@Table))+N'.'+
QUOTENAME(RTRIM(@Table))+N'REBUILDWITH(ONLINE
=OFF);';
PRINT@Sql;
—ExecutedynamicSQL
EXEC(@Sql);
—Getthenextindex
FETCHNEXTFROMIndex_Cursor
INTO@Schema,@Table,@Index;
END
—Closeanddeallocatethecursor.
CLOSEIndex_Cursor;
DEALLOCATEIndex_Cursor;
END
—Showresults,includingoIdfragmentationandnew
fragmentation
—afterindexrebuiId
SELECTil.DatabaseName
,il.SchemaName
,il.TableName
,il.IndexName
,il.FragmentationASFragmentationStart
,MAX(CAST(ip.avg_fragmentation_in_percentAS
DECIMAL(20,2))
)ASFragmentationEnd
FROM@IndexListil
INNERJOINsys.dm_db_index_physical_stats(@DatabaseId,
NULL,NULL,NULL,NULL)ipON
DatabaseId=ip.database_idAND
TableId=ip.object_idAND
IndexId=ip.index_id
GROUPBY
il.DatabaseName
,il.SchemaName
,il.TableName
,il.IndexName
,il.Fragmentation
ORDERBY
FragmentationDESC
,TableNameASC
,IndexNameASC;
RETURN;
END
GO
—Executeindexrebuildstoredprocedure
EXECdbo.RebuildIndexesN'rebuild',30;
Figure3-12.Theresultsofacursor-basedindexrebuildintheAdventureWorksdatabase
Thedbo.RebuildIndexesprocedureshowninListing3-17populatesatable
variablewiththeinformationnecessarytoidentifyallindexesonalltablesinthecurrent
database.Italsousesthesys.dm_db_indexphysical_statscatalogfunctionto
retrieveinitialindexfragmentationinformation:
--Populateindexlisttable
INSERTINTO@IndexList
(
DatabaseName,
DatabaseId,
SchemaName,
SchemaId,
TableName,
TableId,
IndexName,
IndexId,
Fragmentation
)
SELECT
db_name(),
db_id(),
s.Name,
s.schema_id,
t.Name,
t.object_id,
i.Name,
i.index_id,
MAX(ip.avg_fragmentation_in_percent)
FROMsys.tablest
INNERJOINsys.schemass
ONt.schema_id=s.schema_id
INNERJOINsys.indexesi
ONt.object_id=i.object_id
INNERJOINsys.dm_db_index_physical_stats(db_id(),NULL,
NULL,NULL,NULL)ip
ONip.object_id=t.object_id
ANDip.index_id=i.index_id
WHEREip.database_id=db_id()
GROUPBY
s.Name,
s.schema_id,
t.Name,
t.object_id,
i.Name,
i.index_id;
Ifyouspecifyarebuildactionwhenyoucalltheprocedure,itcreatesacursortoloop
throughtherowsofthe@IndexListtable,butonlyforindexeswithafragmentation
percentagehigherthanthelevelyouspecifiedwhencallingtheprocedure:
--DeclareacursortocreatethedynamicSOLstatements
DECLAREIndex_CursorCURSORFAST_FORWARD
FOR
SELECT
SchemaName,
TableName,
IndexNameFROM@IndexList
WHEREFragmentation>@MaxFrag
ORDERBY
FragmentationDESC,
TableNameASC,
IndexNameASC;
Theprocedurethenloopsthroughalltheindexesinthe@IndexListtable,creating
anALTERINDEXstatementtorebuildeachindex.EachALTERINDEXstatementis
createdasdynamicSQLtobeprintedandexecutedusingtheSQLPRINTandEXEC
statements:
--Openthecursorforreading
OPENIndex_Cursor;
--Loopthroughallthetablesinthedatabase
FETCHNEXTFROMIndex_Cursor
INTO@Schema,@Table,@Index;
WHILE@@FETCH_STATUS=0
BEGIN
--CreateALTERINDEXstatementtorebuildindex
SET@Sql=N'ALTERINDEX'+
QUOTENAME(RTRIM(@Index))+N'ON'+QUOTENAME(l@Schema)
+N'.'+
QUOTENAME(RTRIM(@Table))+N'REBUILDWITH(ONLINE=OFF);
';
PRINT@Sql;
--ExecutedynamicSQL
EXEC(@Sql);
--Getthenextindex
FETCHNEXTFROMIndex_Cursor
INTO@Schema,@Table,@lndex;
END
--Closeanddeallocatethecursor.
CLOSEIndex_Cursor;
DEALLOCATEIndex_Cursor;
ThedynamicSQLstatementsgeneratedbytheprocedurelooksimilartothe
following:
ALTERINDEX[IX_PurchaseOrderHeader_EmployeeID]
ON[Purchasing].[PurchaseOrderHeader]REBUILDWITH(ONLINE
=OFF);
Thebalanceofthecodesimplydisplaystheresults,includingthenewfragmentation
percentageaftertheindexesarerebuilt.
NODBCC?
NoticeintheexamplecodeinListing3-17thatwespecificallyavoidedusing
databaseconsolecommands(DBCCs)likeDBCCDBREINDEXandDBCC
SHOWCONTIGtomanageindexfragmentationandrebuildtheindexesinthe
database.Thereisaverygoodreasonforthis:theseDBCCstatements,andmany
others,aredeprecated.MicrosoftisplanningtodoawaywithmanycommonDBCC
statementsinfavorofcatalogviewsandenhancedT-SQLstatementsyntax.The
DBCCDBREINDEXstatement,forinstance,isbeingreplacedbytheALTER
INDEXREBUILDsyntax,andDBCCSHOWCONTIGisbeingreplacedbythe
sys.dm_db_index_physical_statscatalogfunction.Keepthisinmind
whenportingcodefromlegacysystemsandcreatingnewcode.
Anothersituationwhereweadvisedeveloperstousecursorsiswhenthesolution
requiredisaone-offtask,aset-basedsolutionwouldbeverycomplex,andtimeis
short.Examplesincludecreatingcomplexrunningsum-typecalculationsand
performingcomplexdata-scrubbingroutinesonaverylimitedtimeframe.Wedon’t
usingacursorasapermanentproductionapplicationsolutionwithoutexploringall
availableset-basedoptions.Rememberthatwheneveryouuseacursor,youoverride
SQLServer’sautomaticoptimizations—andtheSQLServerqueryenginehasmuch
betterandmorecurrentinformationtooptimizeoperationsthanyouhaveaccesstoat
anygivenpointintime.Alsokeepinmindthattasksyouconsiderextremely
complextodaywillbecomemucheasierasSQL’sset-basedprocessingbecomes
secondnaturetoyou.
CURSORS,CURSORSEVERYWHERE
AlthoughcursorscommonlygetalotofbadpressfromSQLgurus,thereisnothing
inherentlyevilaboutthem.They’rejustanothertoolinthetoolkitandshouldbe
viewedassuch.Whatiswrongisthewaysinwhichdevelopersabusethem.
Generallyspeaking,asmuchas90%ofthetime,cursorsabsolutelyarenotthebest
toolforthejobwhenyou’rewritingT-SQLcode.Unfortunately,manySQLnewbies
findset-basedlogicdifficulttograspatfirst.Cursorsprovideacomfortzonefor
proceduraldevelopersbecausetheylendthemselvestoproceduraldesignpatterns.
Oneoftheworstdesignpatternsyoucanadoptisthe“cursors,cursorseverywhere”
designpattern.Believeitornot,therearedeveloperswhohavebeenwritingSQL
codeforyearsandhaveneverbotheredlearningaboutSQL’sset-basedprocessing.
ThesedeveloperstendtoapproacheverySQLproblemasifitwereaC#orVisual
Basicproblem,andtheircodetendstoreflectitwith“cursors,cursorseverywhere.”
Replacingcursor-basedcodewithWHILEloopsdoesn’tsolvetheproblem.
SimulatingthebehaviorofcursorswithWHILEloopsdoesn’tfixthedesignflaw
inherentinthecursor-basedsolution:row-by-rowprocessingofdata.WHILEloops
may,undersomecircumstances,performcomparablytocursors;andinsome
situationsevenacursorwilloutperformaWHILEloop.
Anotherhorribledesignpatternresultsfromwhatareactuallybestpracticesinother
procedurallanguages.Codereuseisn’tSQL’sstrongpoint.Manyprogrammers
comingfromobject-orientedlanguagesthatpromoteheavycodereusetendtowrite
layersandlayersofSPsthatcalloneanother.TheseSPsoftenhavecursors,and
cursorswithincursors,tofeedeachlayerofprocedures.Althoughitdoespromote
codereuse,thisdesignpatterncausessevereperformancedegradation.Acommonly
usedtermforthistypeofdesignpattern,popularizedbySQLprofessionalJeff
Moden,is“row-by-agonizing-row”(RBAR)processing.Thisdesignpatternishigh
onourtop-tenlistofwaystoabuseSQLServerandwillcauseyoufarmore
problemsthaniteversolves.SQLServer2014offersafeature,thetable-valued
parameter,thatmayhelpincreasemanageabilityandperformanceofthelayeredSP
designmethodology.Chapter5discussestable-valuedparameters.
SQLServersupportssyntaxforbothISOstandardcursorsandT-SQLextendedsyntax
cursors.TheISOstandardsupportsthefollowingcursoroptions:
TheINSENSITIVEoptionmakesatemporarycopyofthecursor
resultsetandusesthatcopytofulfillcursorrequests.Thismeans
changestotheunderlyingtablesaren’treflectedwhenyourequest
rowsfromthecursor.
TheSCROLLoptionallowsyoutouseallcursorfetchoptionsto
positionthecursoronanyrowinthecursorresultset.Thecursorfetch
optionsincludeFIRST,LAST,NEXT,PRIOR,ABSOLUTE,and
RELATIVE.IftheSCROLLoptionisn’tspecified,onlytheNEXT
cursorfetchoptionisallowed.
TheREADONLYoptioninthecursorFORclausepreventsupdatesto
theunderlyingdatathroughthecursor.Inanon-readonlycursor,you
canupdatetheunderlyingdatawiththeWHERECURRENTOF
clauseintheUPDATEandDELETEstatements.
TheUPDATEOFoptionallowsyoutospecifyalistofupdatable
columnsinthecursor’sresultset.YoucanspecifyUPDATEwithout
theOFkeywordanditsassociatedcolumnlisttoallowupdatestoall
columns.
TheT-SQLextendedsyntaxprovidesmanymoreoptionsthantheISOsyntax.In
additiontosupportingread-onlycursors,theUPDATEOFoption,theSCROLLoption,
andinsensitivecursors(usingtheSTATICkeyword),T-SQLextendedsyntaxcursors
supportthefollowingoptions:
Cursorsthatarelocaltothecurrentbatch,procedure,ortriggerin
whichthey’recreatedviatheLOCALkeyword.Cursorsthatareglobal
totheconnectioninwhichthey’recreatedcanbedefinedusingthe
GLOBALkeyword.
TheFORWARDONLYoption,whichistheoppositeoftheSCROLL
option,allowingyoutoonlyfetchrowsfromthecursorusingthe
NEXToption.
TheKEYSEToption,whichspecifiesthatthenumberandorderof
rowsisfixedatthetimethecursoriscreated.Tryingtofetchrowsthat
aresubsequentlydeleteddoesn’tsucceed,anda@@FETCH_STATUS
valueof-2isreturned.
TheDYNAMICoption,whichspecifiesacursorthatreflectsalldata
changesmadetotherowsinitsunderlyingresultset.Thistypeof
cursorisoneoftheslowest,becauseeverychangetotheunderlying
datamustbereflectedwheneveryouscrolltoanewrowoftheresult
set.
TheFAST_FORWARDoption,whichspecifiesaperformance-
optimizedcombinationforward-only/read-onlycursor.
TheSCROLLLOCKSoption,whichlocksunderlyingdatarowsas
they’rereadtoensurethatdatamodificationswillsucceed.The
SCROLLLOCKSoptionismutuallyexclusivewiththe
FAST_FORWARDandSTATICoptions.
TheOPTIMISTICoption,whichusestimestampstodetermineifa
rowhaschangedsincethecursorwasloaded.Ifarowhaschanged,
theOPTIMISTICoptiondoesn’tallowthecurrentcursortoupdate
thesamerow.TheOPTIMISTICoptionisincompatiblewiththe
FAST_FORWARDoption.
TheTYPEWARNINGoption,whichsendsawarningifacursorwillbe
automaticallyconvertedfromtherequestedtypetoanothertype.This
canhappen,forinstance,ifSQLServerneedstoconvertaforward-
onlycursortoastaticcursor.
NoteIfyoudon’tspecifyacursorasLOCALorGLOBAL,cursorsthatarecreated
defaulttothesettingdefinedbythedefaulttolocalcursordatabasesetting.
CURSORCOMPARISONS
Cursorscomeinseveralflavors,andyoucouldspendalotoftimejusttryingto
figureoutwhichoneyouneedtoperformagiventask.Mostofthetime,youneed
forward-only/read-onlycursors.Thesecursorsareefficientbecausetheymovein
onlyonedirectionanddon’tneedtoperformupdatesontheunderlyingdata.
Maximizingcursorefficiencybychoosingtherighttypeofcursorforthejobisa
quick-winstrategythatyoushouldkeepinmindwhenyouhavetoresorttoacursor.
Summary
ThischapterintroducedSQL3VL,whichconsistsofthreelogicalresultvalues:true,false,
andunknown.ThisisakeyconcepttounderstandingSQLdevelopmentingeneral,butit
canbeaforeignideatodeveloperscomingfrombackgroundsinotherprogramming
languages.Ifyou’renotyetfamiliarwiththe3VLchart,wehighlyrecommendrevisiting
Figure3-1.ThischartsummarizesthelogicthatgovernsSQL3VL.
ThischapteralsointroducedT-SQL’scontrol-of-flowstatementofferings,whichallow
youtobranchconditionallyandunconditionally,loop,handleexceptions,andforcedelays
inyourcode.WealsocoveredthetwoflavorsofCASEexpressionandsomeofthemore
advancedusesofCASE,includingdynamicpivottablequeriesandCASE-basedfunctions
likeCOALESCEandNULLIF.
Finally,wediscussedtheredheadedstepchildofSQLdevelopment,thecursor.
Althoughcursorscommonlygetabadrep,there’snothinginherentlybadaboutthem;the
problemiswithhowpeopleusethem.Thediscussionofcursorsfocusedonsome
commonscenarioswheretheymightbeconsideredthebesttoolforthejob,including
administrativeandcomplexone-offtasks.Finally,wepresentedtheoptionsavailablefor
ISO-compliantcursorsandT-SQLextendedsyntaxcursors,bothofwhicharesupported
bySQLServer2014.
ThenextchapterbeginstodiscussT-SQLprogrammabilityfeatures,startingwithan
in-depthlookatT-SQLUDFsinalltheirvariousforms.
EXERCISES
1. [True/False]SQL3VLsupportsthelogicalresultvaluestrue,false,
andunknown.
2. [Chooseone]SQLNULLrepresentswhichofthefollowing?
a. Anunknownormissingvalue
b. Thenumber0
c. Anempty(zero-length)string
d. Alloftheabove
3. [True/False]TheBEGINandENDkeywordsdelimitastatement
blockandlimitthescopeofvariablesdeclaredinthatstatement
block,likecurlybraces({})inC#.
4. [Fillintheblank]The____keywordforcesaWHILEloopto
terminateimmediately.
5. [True/False]TheTRY…CATCHblockcancatcheverypossibleSQL
Servererror.
6. [Fillintheblanks]SQLCASEexpressionscomeintwoforms,___
and___.
7. [Chooseallthatapply]T-SQLsupportswhichofthefollowing
cursoroptions?
a. Read-onlycursors
b. Forward-onlycursors
c. Backward-onlycursors
d. Write-onlycursors
8. ModifythecodeinListing3-10togenerateapivottableresultset
thatreturnsthetotaldollaramount(TotalDue)ofordersby
region,insteadofthecountofordersbyregion.
____________________
1ACaseagainsttheGOTOStatementby:EdsgerWDijkstra;TechnologyUniversityEindhoven,TheNetherlands
http://www.cs.utexas.edu/users/EWD/transcriptions/EWD02xx/EWD215.html
CHAPTER4
User-DefinedFunctions
EachnewversionofSQLServerfeaturesimprovementstoT-SQLthatmakedevelopment
easier.SQLServer2000introduced(amongotherthings)theconceptofuser-defined
functions(UDFs).Likefunctionsinotherprogramminglanguages,T-SQLUDFsprovide
aconvenientwayfordeveloperstodefineroutinesthatacceptparameters,performactions
basedonthoseparameters,andreturndatatothecaller.T-SQLfunctionscomeinthree
flavors:inlinetable-valuedfunctions(TVFs),multistatementTVFs,andscalarfunctions.
SQLServer2014alsosupportstheabilitytocreateCLRintegrationUDFs,whichare
discussedinChapter15.
ScalarFunctions
Basically,ascalarUDFisafunctionthatacceptszeroormoreparametersandreturnsa
singlescalarvalueastheresult.You’reprobablyalreadyfamiliarwithscalarfunctionsin
mathematics,andwithT-SQL’sbuilt-inscalarfunctions(suchasABSandSUBSTRING).
TheCREATEFUNCTIONstatementallowsyoutocreatecustomscalarfunctionsthat
behavelikethebuilt-inscalarfunctions.
TodemonstratescalarUDFs,let’satripbackintimetohighschoolgeometryclass.In
accordancewiththerulespasseddownfromEuclid,thisUDFacceptsacircle’sradiusand
returnstheareaofthecircleusingtheformulaarea=π×r2.Listing4-1demonstratesthis
simplescalarUDF.
Listing4-1.SimpleScalarUDF
CREATEFUNCTIONdbo.CalculateCircleArea(@Radiusfloat=1.0)
RETURNSfloat
WITHRETURNSNULLONNULLINPUT
AS
BEGIN
RETURNPI()*POWER(@Radius,2);
END;
ThefirstlineoftheCREATEFUNCTIONstatementdefinestheschemaandnameof
thefunctionusingastandardSQLServertwo-partname
(dbo.CalculateCircleArea)andasinglerequiredparameter,theradiusofthe
circle(@Radius).The@RadiusparameterisdefinedasaT-SQLfloattype.The
parameterisassignedadefaultvalueof1.0bythe=1.0aftertheparameterdeclaration:
CREATEFUNCTIONdbo.CalculateCircleArea(@Radiusfloat=1.0)
ThenextlinecontainstheRETURNSkeyword,whichspecifiesthedatatypeofthe
resultthatwillbereturnedbytheUDF.Inthisinstance,theRETURNSkeywordindicates
thattheUDFwillreturnafloatresult:
RETURNSfloat
ThethirdlinecontainsadditionaloptionsfollowingtheWITHkeyword.Theexample
usestheRETURNSNULLONNULLINPUTfunctionoptionforaperformance
improvement.TheRETURNSNULLONNULLINPUToptionisaperformance-
enhancingoptionthatautomaticallyreturnsNULLifanyoftheparameterspassedinare
NULL.TheperformanceenhancementoccursbecauseSQLServerwon’texecutethebody
ofthefunctionifaNULLispassedinandthisoptionisspecified:
WITHRETURNSNULLONNULLINPUT
TheASkeywordindicatesthestartofthefunctionbodywhichmustbeenclosedinthe
T-SQLBEGINandENDkeywords.ThesamplefunctioninListing4-1isverysimple,
consistingofasingleRETURNstatementthatimmediatelyreturnsthevalueofthecircle
areacalculation.TheRETURNstatementmustbethelaststatementbeforetheEND
keywordineveryscalarUDF:
RETURNPI()*POWER(@radius,2);
YoucantestthissimpleUDFwithafewSELECTstatementslikethefollowing.The
resultsareshowninFigure4-1:
SELECTdbo.CalculateCircleArea(10);
SELECTdbo.CalculateCircleArea(NULL);
SELECTdbo.CalculateCircleArea(2.5);
Figure4-1.Theresultsofthesamplecircleareacalculations
UDFPARAMETERS
UDFparametersoperatesimilarlyto,butslightlydifferentlyfrom,storedprocedure
(SP)parameters.It’simportanttobeawareofthedifferences.Forinstance,ifyou
createaUDFthatacceptsnoparameters,youstillneedtoincludeemptyparentheses
afterthefunctionname—bothwhencreatingandwheninvokingthefunction.Some
built-infunctions,likethePI()functionusedinListing4-1,whichrepresentsthe
valueoftheconstantπ(3.14159265358979),don’ttakeparameters.Noticethatwhen
thefunctioniscalledintheUDF,it’sstillcalledwithemptyparentheses.
WhenSPsareassigneddefaultvalues,youcansimplyleavetheparameteroffyour
parameterlistcompletelywhencallingtheprocedure.Thisisn’tanoptionwith
UDFs.TouseaUDFdefaultvalue,youmustusetheDEFAULTkeywordwhen
callingtheUDF.Tousethedefaultvalueforthe@radiusparameteroftheexample
dbo.CalculateCircleAreaUDF,youcalltheUDFlikethis:
SELECTdbo.CalculateCircleArea(DEFAULT);
Finally,SPshavenoequivalenttotheRETURNSNULLONNULLINPUToption.
Youcansimulatethisfunctionalitytosomeextentbycheckingyourparametersfor
NULLimmediatelyonenteringtheSP,though.SPsarediscussedingreaterdetailin
Chapter5.
UDFsprovideseveralcreation-timeoptionsthatallowyoutoimproveperformance
andsecurity,includingthefollowing:
TheENCRYPTIONoptioncanbeusedtostoreyourUDFinthe
databaseinobfuscatedformat.Notethatthisisn’ttrueencryption,but
ratheraneasilycircumventedobfuscationofyourcode.Seethe“UDF
‘Encryption’”sidebarformoreinformation.
TheSCHEMABINDINGoptionindicatesthatyourUDFwillbebound
todatabaseobjectsreferencedinthebodyofthefunction.With
SCHEMABINDINGturnedon,attemptstochangeordropreferenced
tablesandotherdatabaseobjectsresultinanerror.Thishelpsto
preventinadvertentchangestotablesandotherdatabaseobjectsthat
canbreakyourUDF.Additionally,theSQLServerDatabaseEngine
teamhaspublishedinformationindicatingthatSCHEMABINDINGcan
improvetheperformanceofUDFs,eveniftheydon’treferenceother
databaseobjects
(http://blogs.msdn.com/b/sqlprogrammability/archive/2006/05/12/596424.aspx
TheCALLEDONNULLINPUToptionistheoppositeofRETURNS
NULLONNULLINPUT.WhenCALLEDONNULLINPUTis
specified,SQLServerexecutesthebodyofthefunctionevenifoneor
moreparametersareNULL.CALLEDONNULLINPUTisa
defaultoptionforallscalar-valuedfunctions.
TheEXECUTEASoptionmanagescallersecurityonUDFs.Youcan
specifythattheUDFbeexecutedasanyofthefollowing:
CALLERindicatesthattheUDFshouldrununderthesecurity
contextoftheusercallingthefunction.Thisisthedefault.
SELFindicatesthattheUDFshouldrununderthesecurity
contextoftheuserwhocreated(oraltered)thefunction.
OWNERindicatesthattheUDFshouldrununderthesecurity
contextoftheowneroftheUDF(ortheowneroftheschema
containingtheUDF).
Finally,youcanspecifythattheUDFshouldrununderthe
securitycontextofaspecificuserbyspecifyingausername.
UDF“ENCRYPTION”
UsingtheENCRYPTIONoptiononUDFsperformsasimpleobfuscationofyour
code.Itactuallydoeslittlemorethan“keephonestpeoplehonest,”andinrealityit
tendstobemoretroublethanit’sworth.ManydevelopersandDBAshavespent
precioustimescouringtheInternetfortoolstodecrypttheirdatabaseobjectsbecause
theywereconvincedthescriptsintheirsourcecontroldatabasewereoutofsyncwith
theproductiondatabase.Keepinmindthatthosesamedecryptiontoolsareavailable
toanyonewithanInternetconnectionandabrowser.Ifyouwritecommercial
databasescriptsorperformdatabaseconsultingservices,yourbest(andreallyonly)
protectionagainstcuriousDBAsanddevelopersreverse-engineeringandmodifying
yourcodeisawell-writtencontract.Keepthisinmindwhendecidingwhetherto
“encrypt”yourdatabaseobjects.
RecursioninScalarUser-DefinedFunctions
Nowthatyou’velearnedthebasics,let’shangoutinmathclassforafewmoreminutesto
talkaboutrecursion.Likemostproceduralprogramminglanguagesthatallowfunction
definitions,T-SQLallowsrecursioninUDFs.There’shardlyabetterwaytodemonstrate
recursionthanthemostbasicrecursivealgorithmaround:thefactorialfunction.
Forthosewhoputfactorialsoutoftheirmindsimmediatelyaftergraduation,here’sa
briefrundownofwhattheyare.Afactorialistheproductofallnatural(orcounting)
numberslessthanorequalton,wheren>0.Factorialsarerepresentedinmathematics
withthebangnotation:n!.Asanexample,5!=1×2×3×4×5=120.Thesimplescalar
dbo.CalculateFactorialUDFinListing4-2calculatesafactorialrecursivelyfor
anintegerparameterpassedintoit.
Listing4-2.RecursiveScalarUDF
CREATEFUNCTIONdbo.CalculateFactorial(@nint=1)
RETURNSdecimal(38,0)
WITHRETURNSNULLONNULLINPUT
AS
BEGIN
RETURN
(CASE
WHEN@n<=0THENNULL
WHEN@n>1THENCAST(@nASfloat)
*dbo.CalculateFactorial(@n-1)
WHEN@n=1THEN1
END);
END;
ThefirstfewlinesaresimilartoListing4-1.Thefunctionacceptsasingleint
parameterandreturnsascalardecimalvalue.TheRETURNSNULLONNULL
INPUToptionreturnsNULLimmediatelyifNULLispassedin:
CREATEFUNCTIONdbo.CalculateFactorial(@nint=1)
RETURNSdecimal(38,0)
WITHRETURNSNULLONNULLINPUT
Youreturnadecimalresultinthisexamplebecauseofthelimitationsoftheint
andbiginttypes.Specifically,theinttypeoverflowsat13!andbigintbombsout
at21!.InordertoputtheUDFthroughitspaces,youhavetoallowittoreturnresultsup
to32!,asdiscussedlaterinthissection.AsinListing4-1,thebodyofthisUDFisasingle
RETURNstatement,thistimewithasearchedCASEexpression:
RETURN(CASE
WHEN@n<=0THENNULL
WHEN@n>1THENCAST(@nASfloat)*dbo.CalculateFactorial
(@n-1)
WHEN@n=1THEN1END);
TheCASEexpressionchecksthevalueoftheUDFparameter,@n.If@nis0or
negative,dbo.CalculateFactorialreturnsNULLbecausetheresultisundefined.
If@nisgreaterthan1,dbo.CalculateFactorialreturns@n*
dbo.CalculateFactorial(@n-1),therecursivepartoftheUDF.Thisensures
thattheUDFwillcontinuecallingitselfrecursively,multiplyingthecurrentvalueof@n
by(@n-1)!.
Finally,when@nreaches1,theUDFreturns1.Thisisthepartof
dbo.CalculateFactorialthatstopstherecursion.Withoutthecheckfor@n=1,
youcouldtheoreticallyendupinaninfiniterecursiveloop.Inpractice,however,SQL
Serversavesyoufromyourselfbylimitingyoutoamaximumof32levelsofrecursion.
Demonstratingthe32-levellimitonrecursioniswhyitwasimportantfortheUDFto
returnresultsupto32!.Followingaresomeexamplesofdbo.CalculateFactorial
callswithvariousparameters,andtheirresults:
SELECTdbo.CalculateFactorial(NULL);—ReturnsNULL
SELECTdbo.CalculateFactorial(-1);—ReturnsNULL
SELECTdbo.CalculateFactorial(0);--ReturnsNULL
SELECTdbo.CalculateFactorial(5);--Returns120
SELECTdbo.CalculateFactorial(32);—Returns
263130836933693520000000000000000000
Asyoucansee,thedbo.CalculateFactorialfunctioneasilyhandlesthe32
levelsofrecursionrequiredtocalculate32!.Ifyoutrytogobeyondthatlimit,yougetan
errormessage.Executingthefollowingcode,whichattempts33levelsofrecursion,
doesn’twork:
SELECTdbo.CalculateFactorial(33);
ThiscausesSQLServertogrumbleloudlywithanerrormessagesimilartothe
following:
Msg217,Level16,State1,Line1
Maximumstoredprocedure,function,trigger,orviewnesting
levelexceeded(limit32).
MORETHANONEWAYTOSKINACAT
The32-levelrecursionlimitisahardlimit;thatis,youcan’tprogrammatically
changeitthroughserverordatabasesettings.Thisreallyisn’tasbadalimitationas
youmightthink.VeryrarelydoyouactuallyneedtorecursivelycallaUDFmore
than32times,anddoingsocouldresultinasevereperformancepenalty.There’s
generallymorethanonewaytogetthejobdone.Youcanworkaroundthe32-level
recursionlimitationinthedbo.CalculateFactorialfunctionbyrewritingit
withaWHILElooporusingarecursivecommontableexpression(CTE),asshown
here:
CREATEFUNCTIONdbo.CalculateFactorial(@nint=1)
RETURNSfloat
WITHRETURNSNULLONNULLINPUT
AS
BEGIN
DECLARE@resultfloat;
SET@result=NULL;
IF@n>0
BEGIN
SET@result=1.0;
WITHNumbers(num)
AS(
SELECT1
UNIONALL
SELECTnum+1
FROMNumbers
WHEREnum<@n
)
SELECT@result=@result*num
FROMNumbers;
END;
RETURN@result;
END;
Thisrewriteofthedbo.CalculateFactorialfunctionavertstherecursive
functioncalllimitbyeliminatingtherecursivefunctioncalls.Instead,itpushesthe
recursionbackintothebodyofthefunctionthroughtheuseofarecursivecommon
tableexpression(CTE).Bydefault,SQLServerallowsupto100levelsofrecursion
inaCTE(youcanoverridethiswiththeMAXRECURSIONoption),greatly
expandingyourfactorialcalculationpower.Withthisfunction,youcaneasilyfind
outthat33!is8.68331761881189E+36,oreventhat100!is
9.33262154439441E+157.Theimportantideatotakeawayfromthisdiscussionis
thatalthoughrecursivefunctioncallshavehardlimitsonthem,youcanoftenwork
aroundthoselimitationsusingotherT-SQLfunctionality.
Alsokeepinmindthatalthoughyouusedfactorialcalculationasasimpleexampleof
recursion,thismethodisconsiderednaive,andthereareseveralmore-efficient
methodsofcalculatingfactorials.
ProceduralCodeinUser-DefinedFunctions
Sofar,you’veseensimplefunctionsthatdemonstratethebasicpointsofscalarUDFs.But
inalllikelihood,unlessyou’reimplementingbusinesslogicforaswimmingpool
installationcompany,youaren’tlikelytoneedtospendmuchtimecalculatingtheareaof
acircleinT-SQL.
Acommonproblemthatyouhaveamuchgreaterchanceofrunningintoisname-
basedsearching.T-SQLofferstoolsforexactmatching,partialmatching,andevenlimited
patternmatchingviatheLIKEpredicate.T-SQLevenoffersbuilt-inphoneticmatching
(sound-alikematching)throughthebuilt-inSOUNDEXfunction.
Heavy-dutyapproximatematchingusuallyrequiresamoreadvancedtool,likeabetter
phoneticmatchingalgorithm.Let’suseoneofthesealgorithms,theNewYorkState
IdentificationandIntelligenceSystem(NYSIIS)algorithm,todemonstrateprocedural
codeinUDFs.
THESOUNDEXALGORITHM
TheNYSIISalgorithmisanimprovementontheSoundexphoneticencoding
algorithm,itselfnearly90yearsold.TheNYSIISalgorithmconvertsgroupsofone,
two,orthreealphabeticcharacters(knownasn-grams)innamestoaphonetic
(“soundslike”)approximation.Thismakesiteasiertosearchfornamesthathave
similarpronunciationsbutdifferentspellings,suchasSmytheandSmith.As
mentionedinthissection,SQLServerprovidesabuilt-inSOUNDEXfunction,but
Soundexprovidesverypooraccuracyandusuallyresultsinmanyfalsehits.NYSIIS
andothermodernalgorithmsprovidemuchbetterresultsthanSoundex.
TodemonstrateproceduralcodeinUDFs,youcanimplementaUDFthatphonetically
encodesnamesusingNYSIISencodingrules.TherulesforNYSIISphoneticencodingare
relativelysimple,withthemajorityoftherulesrequiringsimplen-gramsubstitutions.The
followingisacompletelistofNYSIISencodingrules:
1. Removeallnon-alphabeticcharactersfromthename.
2. Thefirstcharactersofthenameareencodedaccordingtothen-
gramsubstitutionsshownintheStartofNametableinFigure4-2.
InFigure4-2,then-gramsshowntotheleftofthearrowsare
replacedwiththen-gramstotherightofthearrowsduringthe
encodingprocess.
Figure4-2.NYSIISphoneticencodingrules/charactersubstitutions
3. Thelastcharactersofthenameareencodedaccordingtothen-gram
substitutionsshownintheEndofNametableinFigure4-2.
4. Thefirstcharacteroftheencodedvalueissettothefirstcharacter
ofthename.
5. Afterthefirstandlastn-gramsareencoded,allremaining
charactersinthenameareencodedaccordingtothen-gram
substitutionsshownintheMiddleofNametableinFigure4-2.
6. Allside-by-sideduplicatecharactersintheencodednameare
reducedtoasinglecharacter.ThismeansthatAAisreducedtoA
andSSisreducedtoS.
7. IfthelastcharacteroftheencodednameisS,it’sremoved.
8. IfthelastcharactersoftheencodednameareAY,they’rereplaced
withY.
9. IfthelastcharacteroftheencodednameisA,it’sremoved.
10. Theresultistruncatedtoamaximumlengthofsixcharacters.
YoucouldusesomefairlylargeCASEexpressionstoimplementtheserules,butlet’s
gowithamoreflexibleoption:usingareplacementtable.Thistablewillcontainthe
majorityofthereplacementrulesinthreecolumns,asdescribedhere:
Location:ThiscolumntellstheUDFwhethertheruleshouldbe
appliedtothestart,end,ormiddleofthename.
NGram:Thiscolumnisthen-gram,orsequenceofcharacters,that
willbeencoded.Thesen-gramscorrespondtotheleftsideofthe
arrowsinFigure4-2.
Replacement:Thiscolumnrepresentsthereplacementvaluefor
thecorrespondingn-gramonthesamerow.Thesecharactersequences
correspondtotherightsideofthearrowsinFigure4-2.
Listing4-3isaCREATETABLEstatementthatbuildstheNYSIISphoneticencoding
replacementrulestable.
Listing4-3.CreatingtheNYSIISReplacementRulesTable
--CreatetheNYSIISreplacementrulestable
CREATETABLEdbo.NYSIIS_Replacements
(Locationnvarchar(10)NOTNULL,
NGramnvarchar(10)NOTNULL,
Replacementnvarchar(10)NOTNULL,
PRIMARYKEY(Location,NGram));
Listing4-4isasingleINSERTstatementthatusesrowconstructorstopopulateallthe
NYSIISreplacementrules,asshowninFigure4-2.
Listing4-4.INSERTStatementtoPopulatetheNYSIISReplacementRulesTable
INSERTINTONYSIIS_Replacements(Location,NGram,
Replacement)
VALUES(N'End',N'DT',N'DD'),
(N'End',N'EE',N'YY'),
(N'End',N'lE',N'YY'),
(N'End',N'ND',N'DD'),
(N'End',N'NT',N'DD'),
(N'End',N'RD',N'DD'),
(N'End',N'RT',N'DD'),
(N'Mid',N'A',N'A'),
(N'Mid',N'E',N'A'),
(N'Mid',N'T',N'A'),
(N'Mid',N'K',N'C'),
(N'Mid',N'M',N'N'),
(N'Mid',N'O',N'A'),
(N'Mid',N'Q',N'G'),
(N'Mid',N'U',N'A'),
(N'Mid',N'Z',N'S'),
(N'Mid',N'AW',N'AA'),
(N'Mid',N'EV',N'AF'),
(N'Mid',N'EW',N'AA'),
(N'Mid',N'lW',N'AA'),
(N'Mid',N'KN',N'NN'),
(N'Mid',N'OW',N'AA'),
(N'Mid',N'PH',N'FF'),
(N'Mid',N'UW',N'AA'),
(N'Mid',N'SCH',N'SSS'),
(N'Start',N'K',N'C'),
(N'Start',N'KN',N'NN'),
(N'Start',N'PF',N'FF'),
(N'Start',N'PH',N'FF'),
(N'Start',N'MAC',N'MCC'),
(N'Start',N'SCH',N'SSS');
GO
Listing4-5istheUDFthatencodesastringusingNYSIIS.ThisUDFdemonstrates
thecomplexityofthecontrol-of-flowlogicthatcanbeimplementedinascalarUDF.
Listing4-5.FunctiontoEncodeStringsUsingNYSIIS
CREATEFUNCTIONdbo.EncodeNYSIIS
(
@Stringnvarchar(100)
)
RETURNSnvarchar(6)
WITHRETURNSNULLONNULLINPUT
AS
BEGIN
DECLARE@Resultnvarchar(100);
SET@Result=UPPER(@String);
--Step1:RemoveAllNonalphabeticCharacters
WITHNumbers(Num)
AS
(
SELECT1
UNIONALL
SELECTNum+1
FROMNumbers
WHERENum<LEN(@Result)
)
SELECT@Result=STUFF
(
@Result,
Num,
1,
CASEWHENSUBSTRING(@Result,Num,1)>=N'A'
ANDSUBSTRING(@Result,Num,1)<=N'Z'
THENSUBSTRING(@Result,Num,1)
ELSEN'.'
END)
FROMNumbers;
SET@Result=REPLACE(@Result,N'.',N'');
--Step2:ReplacetheStartN-gram
SELECTTOP(1)@Result=STUFF
(
@Result,
1,
LEN(NGram),
Replacement
)
FROMdbo.NYSIIS_Replacements
WHERELocation=N'Start'
ANDSUBSTRING(@Result,1,LEN(NGram))=NGram
ORDERBYLEN(NGram)DESC;
--Step3:ReplacetheEndN-gram
SELECTTOP(1)@Result=STUFF
(
@Result,
LEN(@Result)-LEN(NGram)+1,
LEN(NGram),
Replacement
)
FROMdbo.NYSIIS_Replacements
WHERELocation=N'End'
ANDSUBSTRING(@Result,LEN(@Result)-LEN(NGram)+1,
LEN(NGram))=NGram
ORDERBYLEN(NGram)DESC;
--Step4:SavetheFirstLetteroftheName
DECLARE@FirstLetternchar(1);
SET@FirstLetter=SUBSTRING(@Result,1,1);
--Step5:ReplaceAllMiddleN-grams
DECLARE@Replacementnvarchar(10);
DECLARE@iint;
SET@i=1;
WHILE@i<=LEN(@Result)
BEGIN
SET@Replacement=NULL;
--Grabthemiddle-of-namereplacementn-gram
SELECTTOP(1)@Replacement=Replacement
FROMdbo.NYSIIS_Replacements
WHERELocation=N'Mid'
ANDSUBSTRING(@Result,@i,LEN(NGram))=NGram
ORDERBYLEN(NGram)DESC;
SET@Replacement=COALESCE(@Replacement,SUBSTRING(@Result,
@i,1));
--Ifwefoundareplacement,applyit
SET@Result=STUFF(@Result,@i,LEN(@Replacement),
@Replacement)
--Moveontothenextn-gram
SET@i=@i+COALESCE(LEN(@Replacement),1);
END;
--Replacethefirstcharacterwiththefirstletterwe
savedatthestart
SET@Result=STUFF(@Result,1,1,@FirstLetter);
--Hereweapplyourspecialrulesforthe'H'character.
Specialhandlingfor'W'
--charactersistakencareofinthereplacementrules
table
WITHNumbers(Num)
AS
(
SELECT2--Don'tbotherwiththefirstcharacter
UNIONALL
SELECTNum+1
FROMNumbers
WHERENum<LEN(@Result)
)
SELECT@Result=STUFF
(
@Result,
Num,
1,
CASESUBSTRING(@Result,Num,1)
WHENN'H'THEN
CASEWHENSUBSTRING(@Result,Num+1,1)
NOTIN(N'A',N'E',N'I',N'O',N'U')
ORSUBSTRING(@Result,Num-1,1)
NOTIN(N'A',N'E',N'I',N'O',N'U')
THENSUBSTRING(@Result,Num-1,1)
ELSEN'H'
END
ELSESUBSTRING(@Result,Num,1)
END
)
FROMNumbers;
--Step6:ReduceAllSide-by-sideDuplicateCharacters
--Firstreplacethefirstletterofanysequenceoftwo
side-by-side
--duplicateletterswithaperiod
WITHNumbers(Num)
AS
(
SELECT1
UNIONALL
SELECTNum+1
FROMNumbers
WHERENum<LEN(@Result)
)
SELECT@Result=STUFF
(
@Result,
Num,
1,
CASESUBSTRING(@Result,Num,1)
WHENSUBSTRING(@Result,Num+1,1)THENN'.'
ELSESUBSTRING(@Result,Num,1)
END
)
FROMNumbers;
--Nextreplaceallperiods'.'withanemptystring''
SET@Result=REPLACE(@Result,N'.',N'');
--Step7:RemoveTrailing'S'Characters
WHILERIGHT(@Result,1)=N'S'ANDLEN(@Result)>1
SET@Result=STUFF(@Result,LEN(@Result),1,N'');
--Step8:RemoveTrailing'A'Characters
WHILERIGHT(@Result,1)=N'A'ANDLEN(@Result)>1
SET@Result=STUFF(@Result,LEN(@Result),1,N'');
--Step9:ReplaceTrailing'AY'Characterswith'Y'
IFRIGHT(@Result,2)='AY'
SET@Result=STUFF(@Result,LEN(@Result)-1,1,N'');
--Step10:TruncateResultto6Characters
RETURNCOALESCE(SUBSTRING(@Result,1,6),'');
END;
GO
TheNYSIISReplacementstablerulesreflectmostoftheNYSIISrulesdescribed
byRobertL.Taftinhisfamouspaper“NameSearchTechniques.”1Thestartandendn-
gramsarereplaced,andthentheremainingn-gramrulesareappliedinaWHILEloop.The
specialrulesfortheletterHareapplied,side-by-sideduplicatesareremoved,special
handlingofcertaintrailingcharactersisperformed,andthefirstsixcharactersoftheresult
arereturned.
NUMBERSTABLES
ThisexampleusesrecursiveCTEstodynamicallygeneratevirtualnumberstablesin
acoupleofplaces.Anumberstableissimplyatableofnumberscountinguptoa
specifiedmaximum.ThefollowingrecursiveCTEgeneratesasmallnumberstable
(thenumbers1through100):
WITHNumbers(Num)
AS
(
SELECT1
UNIONALL
SELECTNum+1
FROMNumbers
WHERENum<100
)
SELECTNumFROMNumbers;
Listing4-5usedthenumberofcharactersinthenametolimittherecursionofthe
CTEs.ThisspeedsuptheUDFoverall.Youcangetevenmoreperformancegainsby
creatingapermanentnumberstableinyourdatabasewithaclusteredindex/primarykey
onit,insteadofusingCTEs.Anumberstableisalwayshandytohavearound,doesn’tcost
youverymuchtobuildormaintain,doesn’ttakeupmuchstoragespace,andisextremely
usefulforconvertingloopsandcursorstoset-basedcode.Anumberstableisbyfaroneof
thehandiestandsimplesttoolsyoucanaddtoyourT-SQLtoolkit.
Asanexample,youcanusethequeryinListing4-6tophoneticallyencodethelast
namesofallcontactsintheAdventureWorksdatabaseusingNYSIIS.Partialresultsare
showninFigure4-3.
Listing4-6.UsingNYSIIStoPhoneticallyEncodeAllAdventureWorksContacts
SELECTLastName,
dbo.EncodeNYSIIS(LastName)ASNYSIIS
FROMPerson.Person
GROUPBYLastName;
Figure4-3.PartialresultsofNYSIISencodingAdventureWorkscontacts
Usingthedbo.EncodeNYSIISUDFisrelativelysimple.Listing4-7isasimple
exampleofusingthenewUDFintheWHEREclausetoretrieveallAdventureWorks
contactswhoselastnameisphoneticallysimilartothenameLiu.Theresultsareshownin
Figure4-4.
Listing4-7.RetrievingAllContactPhoneticMatchesforLiu
SELECT
BusinessEntityID,
LastName,
FirstName,
MiddleName,
dbo.EncodeNYSIIS(LastName)ASNYSIIS
FROMPerson.Person
WHEREdbo.EncodeNYSIIS(LastName)=dbo.EncodeNYSIIS(N'
Liu');
Figure4-4.PartiallistingofAdventureWorkscontactswithnamesphoneticallysimilartoLiu
TheexampleinListing4-7isthenaivemethodofusingaUDF.Thequeryengine
mustapplytheUDFtoeverysinglerowofthesourcetable.Inthiscase,the
dbo.EncodeNYSIISfunctionisappliedtothenearly20,000lastnamesinthe
Person.Contacttable,resultinginaninefficientqueryplanandexcessiveI/O.A
moreefficientmethodistoperformtheNYSIISencodingsaheadoftime—topre-encode
thenames.Thepre-encodingmethodisdemonstratedinListing4-8.
Listing4-8.Pre-encodingAdventureWorksContactNameswithNYSIIS
CREATETABLEPerson.ContactNYSIIS
(
BusinessEntityIDintNOTNULL,
NYSIISnvarchar(6)NOTNULL,
PRIMARYKEY(NYSIIS,BusinessEntityID)
);
GO
INSERTINTOPerson.ContactNYSIIS
(
BusinessEntityID,
NYSIIS
)
SELECT
BusinessEntityID,
dbo.EncodeNYSIIS(LastName)
FROMPerson.Person;
GO
Onceyouhavepre-encodedthedata,queriesaremuchmoreefficient.Thequery
showninListing4-9usesthetablecreatedinListing4-8toreturnthesameresultsas
Listing4-7—justmuchmoreefficiently,becausethisversiondoesn’tneedtoencodeevery
rowofdataforcomparisonintheWHEREclauseatquerytime.
Listing4-9.EfficientNYSIISQueryUsingPre-encodedData
SELECT
cn.BusinessEntityID,
c.LastName,
c.FirstName,
c.MiddleName,
cn.NYSIIS
FROMPerson.ContactNYSIIScn
INNERJOINPerson.Personc
ONcn.BusinessEntityID=c.BusinessEntityID
WHEREcn.NYSIIS=dbo.EncodeNYSIIS(N'Liu');
Tokeeptheefficiencyofthedbo.EncodeNYSIISUDF-basedsearchesoptimized,
Ihighlyrecommendpre-encodingyoursearchdata.Thisisespeciallytrueinproduction
environmentswhereperformanceiscritical.NYSIIS(andphoneticmatchingingeneral)is
anextremelyusefultoolforapproximatename-basedsearchesinavarietyofapplications,
suchascustomerservice,businessreporting,andlawenforcement.
MultistatementTable-ValuedFunctions
MultistatementTVFsaresimilarinstyletoscalarUDFs,butinsteadofreturningasingle
scalarvalue,theyreturntheirresultasatabledatatype.Thedeclarationisverysimilar
tothatofascalarUDF,withafewimportantdifferences:
ThereturntypefollowingtheRETURNSkeywordisactuallyatable
variabledeclaration,withitsstructuredeclaredimmediatelyfollowing
thetablevariablename.
TheRETURNSNULLONNULLINPUTandCALLEDONNULL
INPUTfunctionoptionsaren’tvalidinamultistatementTVF
definition.
TheRETURNstatementinthebodyofthemultistatementTVFhasno
valuesorvariablesfollowingit.
InsidethebodyofthemultistatementTVF,youcanusetheSQLDataManipulation
Language(DML)statementsINSERT,UPDATE,MERGE,andDELETEtocreateand
manipulatethereturnresultsinthetablevariablethatwillbereturnedastheresult.
FortheexampleofamultistatementTVF,let’screateanotherbusinessapplication
function:aproductpulllistforAdventureWorks.ThisTVFmatchestheAdventureWorks
salesordersstoredintheSales.SalesOrderDetailtableagainsttheproduct
inventoryintheProduction.ProductInventorytable.Iteffectivelycreatesalist
forAdventureWorksemployees,tellingthemexactlywhichinventorybintogotowhen
theyneedtofillanorder.Somebusinessrulesneedtobedefinedbeforeyouwritethis
multistatementTVF:
Insomecases,thenumberofordereditemsmaybemorethanare
availableinonebin.Inthatcase,thepulllistwillinstructthe
employeetograbtheproductfrommultiplebins.
Anypartialfillsfromabinwillbereportedonthelist.
Anysubstitutionwork(forexample,substitutingadifferent-colored
itemofthesamemodel)willbehandledbyaseparatebusiness
processandwon’tbeallowedonthislist.
Nozerofills(ordereditemsforwhichthereisnomatchingproductin
inventory)willbereportedbackonthelist.
Forpurposesofthisexample,let’ssaytherearethreecustomers:Jill,Mike,andDave.
Eachofthesethreecustomersplacesanorderforexactlyfiveofitemnumber783,the
blackMountain-20042-inchmountainbike.Let’salsosaythatAdventureWorkshassixof
thisparticularinventoryiteminbin1,shelfA,location7,andanotherthreeofthis
particulariteminbin2,shelfB,location10.Yourbusinessruleswillcreateapulllistlike
thefollowing:
Jill’sorder:Pullfiveofitem783frombin1,shelfA,location7;mark
theorderasacompletefill.
Mike’sorder:Pulloneofitem783frombin1,shelfA,location7;
marktheorderasapartialfill.
Mike’sorder:Pullthreeofitem783frombin2,shelfB,location10;
marktheorderasapartialfill.
Inthisexample,thereareonly9oftheordereditemsininventory,but15totalitems
havebeenordered(3customersmultipliedby5itemseach).Becauseofthis,Dave’sorder
iszero-filled—noitemsarepulledfrominventorytofillhisorder.Figure4-5isdesigned
tohelpyouvisualizethesampleinventory/orderfillscenario.
Figure4-5.Fillingordersfrominventory
Becausetheinventoryisoutofitem783atthispoint(therewerenineitemsin
inventoryandallninewereusedtofillJill’sandMike’sorders),Dave’sorderisnoteven
listedonthepulllistreport.Thisfunctiondoesn’tconcernitselfwithproductsubstitutions
—forexample,completingMike’sandDave’sorderswithacomparableproductsuchas
itemIDnumber780(thesilverMountain-20042-inchmountainbike),iftherehappensto
besomeinstock.Thebusinessruleforsubstitutionsstatesthataseparateprocesshandles
thisaspectoforderfulfillment.
Manydevelopersmayseethisproblemasanopportunitytoflextheircursor-based
codingmuscles.Ifyoulookattheproblemfromaproceduralpointofview,itessentially
callsforperformingnestedloopsthroughAdventureWorks’customerordersandinventory
tomatchthemup.However,thiscodedoesn’trequireproceduralcode,andthetaskcanbe
completedinaset-basedfashionusinganumberstable,asdescribedintheprevious
section.Anumberstablewithnumbersfrom0to30,000isadequateforthistask;thecode
tocreateitisshowninListing4-10.
Listing4-10.CreatingaNumbersTable
USE[AdventureWorks2014]
GO
IFEXISTS(SELECT*FROMsys.objects
WHEREobject_id=OBJECT_ID(N'[dbo].[Numbers]')
ANDtypein(N'U'))
DROPTABLE[dbo].[Numbers];
--Createanumberstabletoallowtheproductpulllistto
be
--createdusingset-basedlogic
CREATETABLEdbo.Numbers(NumintNOTNULLPRIMARYKEY);
GO
--Fillthenumberstablewithnumbersfrom0to30,000
WITHNumCTE(Num)
AS
(
SELECT0
UNIONALL
SELECTNum+1
FROMNumCTE
WHERENum<30000
)
INSERTINTOdbo.Numbers(Num)SELECTNumFROMNumCTE
OPTION(MAXRECURSION0);
GO
So,withabetterunderstandingoforder-fulfillmentlogicandbusinessrules,Listing4-
11createsamultistatementTVFtoreturntheproductpulllistaccordingtotherules
provided.Asmentioned,thismultistatementTVFusesset-basedlogic(nocursorsor
loops)toretrievetheproductpulllist.
LOOKMA,NOCURSORS!
Manyprogrammingproblemsinbusinesspresentaproceduralloop-basedsolution
onfirstglance.ThisappliestoproblemsthatyoumustsolveinT-SQLaswell.Ifyou
lookatbusinessproblemswithaset-basedmindset,youoftenfindaset-based
solution.Intheproductpulllistexample,theloop-basedprocessofcomparingevery
rowofinventorytotheorder-detailrowsisimmediatelyapparent.
However,ifyouthinkoftheinventoryitemsandorder-detailitemsastwosets,then
theproblembecomesaset-basedproblem.Inthiscase,thesolutionisavariationof
theclassiccomputerscience/mathematicsbin-packingproblem.Inthebin-packing
problem,you’regivenasetofbins(inthiscase,orders)inwhichtoplaceafiniteset
ofitems(inventoryitemsinthisexample).Thenaturalboundsprovidedarethe
numberofeachitemininventoryandthenumberofeachitemoneachorder-detail
line.
Bysolvingthisasaset-basedprobleminT-SQL,youallowSQLServertooptimize
theperformanceofyourcodebasedonthemostcurrentinformationavailable.As
mentionedinChapter3,whenyouusecursorsandloops,youtakeawaySQL
Server’sperformance-optimizationoptions,andyouassumetheresponsibilityfor
performanceoptimization.Youcanuseset-basedlogicinsteadofcursorsandloops
tosolvethisparticularproblem.Inreality,solvingthisproblemwithaset-based
solutiontookonlyabout30minutesofmytime.Acursororloop-basedsolution
wouldhavetakenjustaslongorlonger,anditwouldn’thavebeennearlyasefficient.
Listing4-11.CreatingaProductPullList
CREATEFUNCTIONdbo.GetProductPullList()
RETURNS@resulttable
(
SalesOrderIDintNOTNULL,
ProductIDintNOTNULL,
LocationIDsmallintNOTNULL,
Shelfnvarchar(10)NOTNULL,
BintinyintNOTNULL,
QuantityInBinsmallintNOTNULL,
QuantityOnOrdersmallintNOTNULL,
QuantityToPullsmallintNOTNULL,
PartialFillFlagnchar(1)NOTNULL,
PRIMARYKEY(SalesOrderID,ProductID,LocationID,Shelf,
Bin)
)
AS
BEGIN
INSERTINTO@result
(
SalesOrderID,
ProductID,
LocationID,
Shelf,
Bin,
QuantityInBin,
QuantityOnOrder,
QuantityToPull,
PartialFillFlag
)
SELECT
Order_Details.SalesOrderID,
Order_Details.ProductID,
Inventory_Details.LocationID,
Inventory_Details.Shelf,
Inventory_Details.Bin,
Inventory_Details.Quantity,
Order_Details.OrderQty,
COUNT(*)ASPullQty,
CASEWHENCOUNT(*)<Order_Details.OrderQty
THENN'Y'
ELSEN'N'
ENDASPartialFillFlag
FROM
(
SELECTROW_NUMBER()OVER
(
PARTITIONBYp.ProductID
ORDERBYp.ProductID,
p.LocationID,
p.Shelf,
p.Bin
)ASNum,
p.ProductID,
p.LocationID,
p.Shelf,
p.Bin,
p.Quantity
FROMProduction.ProductInventoryp
INNERJOINdbo.Numbersn
ONn.NumBETWEEN1ANDQuantity
)Inventory_Details
INNERJOIN
(
SELECTROW_NUMBER()OVER
(
PARTITIONBYo.ProductID
ORDERBYo.ProductID,
o.SalesOrderID
)ASNum,
o.ProductID,
o.SalesOrderID,
o.OrderQty
FROMSales.SalesOrderDetailo
INNERJOINdbo.Numbersn
ONn.NumBETWEEN1ANDo.OrderQty
)Order_Details
ONInventory_Details.ProductID=Order_Details.ProductID
ANDInventory_Details.Num=Order_Details.Num
GROUPBY
Order_Details.SalesOrderID,
Order_Details.ProductID,
Inventory_Details.LocationID,
Inventory_Details.Shelf,
Inventory_Details.Bin,
Inventory_Details.Quantity,
Order_Details.OrderQty;
RETURN;
END;
GO
RetrievingtheproductpulllistinvolvesasimpleSELECTquerylikethefollowing.
PartialresultsareshowninFigure4-6:
SELECT
SalesOrderID,
ProductID,
LocationID,
Shelf,
Bin,
QuantityInBin,
QuantityOnOrder,
QuantityToPull,
PartialFillFlag
FROMdbo.GetProductPullList();
Figure4-6.AdventureWorksproductpulllist(partial)
OneinterestingaspectofthemultistatementTVFistheCREATEFUNCTION
keywordanditsRETURNSclause,whichdefinethenameoftheprocedure,parameters
passedin(ifany),andtheresultingsettablestructure:
CREATEFUNCTIONdbo.GetProductPullList()
RETURNS@resulttable
(
SalesOrderIlDintNOTNULL,
ProductIDintNOTNULL,
LocationIDsmallintNOTNULL,
Shelfnvarchar(10)NOTNULL,
BintinyintNOTNULL,
QuantityInBinsmallintNOTNULL,
QuantityOnOrdersmallintNOTNULL,
QuantityToPullsmallintNOTNULL,
PartialFillFlagnchar(1)NOTNULL,
PRIMARYKEY(SalesOrderID,ProductID,LocationID,Shelf,
Bin)
)
Noticethatyoudefineaprimarykeyonthetableresult.Thisalsoservesasthe
clusteredindexfortheresultset.Duetolimitationsintablevariables,youcan’texplicitly
specifyotherindexesontheresultset.
ThebodyofthefunctionbeginswiththeINSERTINTOandSELECTclausesthat
follow:
INSERTINTO@result
(
SalesOrderID,
ProductID,
LocationID,
Shelf,
Bin,
QuantitylnBin,
QuantityOnOrder,
QuantityToPull,
PartialFillFlag
)
SELECT
Order_Details.SalesOrderID,
Order_Details.ProductID,
Inventory_Details.LocationID,
Inventory_Details.Shelf,
Inventory_Details.Bin,
Inventory_Details.Quantity,
Order_Details.OrderQty,
COUNT(*)ASPullQty,
CASEWHENC0UNT(*)<Order_Details.OrderQty
THENN'Y'
ELSEN'N'
ENDASPartialFillFlag
Theseclausesestablishpopulationofthe@resulttablevariable.Themostimportant
pointtonoticehereisthatthereturnresultsofthismultistatementTVFarecreatedby
manipulatingthecontentsofthe@resulttablevariable.Whenthefunctionends,the
@resulttablevariableisreturnedtothecaller.Someotherimportantfactsaboutthis
portionofthemultistatementTVFarethattheCOUNT(*)ASPullQtyaggregate
functionreturnsthetotalnumberofeachitemtopullfromagivenbintofillaspecific
order-detailrow,andtheCASEexpressionreturnsYwhenanorder-detailitemispartially
filledfromasinglebinandNwhenanorder-detailitemiscompletelyfilledfromasingle
bin.
ThesourcefortheSELECTqueryiscomposedoftwosubqueriesjoinedtogether.The
firstsubquery,aliasedasInventoryDetails,isshownnext.Thissubqueryreturnsa
singlerowforeveryitemininventorywithinformationidentifyingthepreciselocation
wheretheinventoryitemcanbefound:
(
SELECTROW_NUMBER()OVER
(
PARTITIONBYp.ProductID
ORDERBYp.ProductID,
p.LocationID,
p.Shelf,
p.Bin
)ASNum,
p.ProductID,
p.LocationID,
p.Shelf,
p.Bin,
p.Quantity
FROMProduction.ProductInventoryp
INNERJOINdbo.Numbersn
ONn.NumBETWEEN1ANDQuantity
)Inventory_Details
ConsiderthepreviousexamplewiththecustomersJill,Mike,andDave.Ifthereare
nineblackMountain-20042-inchmountainbikesininventory,thisqueryreturnsnine
rows,oneforeachinstanceoftheitemininventory,andeachwithauniquerownumber
countingfrom1.
TheInventoryDetailssubqueryisinner-joinedtoasecondsubquery,identified
asOrder_Details:
(
SELECTROW_NUMBER()OVER
(
PARTITIONBYo.ProductID
ORDERBYo.ProductID,
o.SalesOrderID
)ASNum,
o.ProductID,
o.SalesOrderID,
o.OrderQty
FROMSales.SalesOrderDetailo
INNERJOINdbo.Numbersn
ONn.NumBETWEEN1ANDo.OrderQty
)Order_Details
Thissubquerybreaksupquantitiesofitemsinallorderdetailsintoindividualrows.
Again,intheexampleofJill,Mike,andDave,thisquerybreakseachoftheorderdetails
intofiverows,oneforeachitemofeachorderdetail.Therowsareassignedunique
numbersforeachproduct.Sointheexample,therowsforeachblackMountain-20042-
inchmountainbikethatthethreecustomersorderedarenumberedindividuallyfrom1to
15.
TherowsofbothsubqueriesarejoinedbasedontheirProductIDnumbersandthe
uniquerownumbersassignedtoeachrowofeachsubquery.Thiseffectivelyassignsone
itemfrominventorytofillexactlyoneitemineachorder.Figure4-7isavisualizationof
theprocessdescribedhere,wheretheinventoryitemsandorder-detailitemsaresplitinto
separaterowsandthetworowsetsarejoinedtogether.
Figure4-7.Splittingandjoiningindividualinventoryandorder-detailitems
TheSELECTstatementalsorequiresaGROUPBYtoaggregatethetotalnumberof
itemstobepulledfromeachbintofilleachorderdetail,asopposedtoreturningtheraw
inventory-to-orderdetailitemsonaone-to-onebasis:
GROUPBY
Order_Details.SalesOrderID,
Order_Details.ProductID,
Inventory_Details.LocationID,
Inventory_Details.Shelf,
Inventory_Details.Bin,
Inventory_Details.Quantity,
Order_Details.OrderQty;
Finally,theRETURNstatementreturnsthe@resulttablebacktothecallerasthe
multistatementTVFresult.NoticethattheRETURNstatementinamultistatementTVF
isn’tfollowedbyanexpressionorvariableasitisinascalarUDF:
RETURN;
ThetablereturnedbyaTVFcanbeusedjustlikeatableinaWHEREclauseoraJOIN
clauseofanSQLSELECTquery.Listing4-12isasamplequerythatjoinstheexample
TVFtotheProduction.Producttabletogettheproductnamesandcolorsforeach
productlistedinthepulllist.Figure4-8showstheoutputoftheproductpulllistjoinedto
theProduction.Producttable.
Listing4-12.RetrievingaProductPullListwithProductNames
SELECT
p.NameASProductName,
p.ProductNumber,
p.Color,
ppl.SalesOrderID,
ppl.ProductID,
ppl.LocationID,
ppl.Shelf,
ppl.Bin,
ppl.QuantityInBin,
ppl.QuantityOnOrder,
ppl.QuantityToPull,
ppl.PartialFillFlag
FROMProduction.Productp
INNERJOINdbo.GetProductPullList()ppl
ONp.ProductID=ppl.ProductID;
Figure4-8.JoiningtheproductpulllisttotheProduction.Producttable
InlineTable-ValuedFunctions
IfscalarUDFsandmultistatementTVFsaren’tenoughtogetyouexcitedaboutT-SQL’s
UDFcapabilities,herecomesathirdformofUDF:theinlineTVF.InlineTVFsare
similartomultistatementTVFsinthattheyreturnatabularrowsetresult.
However,whereasamultistatementTVFcancontainmultipleSQLstatementsand
control-of-flowstatementsinthefunctionbody,theinlinefunctionconsistsofonlya
singleSELECTquery.TheinlineTVFisliterally“inlined”bySQLServer(expandedby
thequeryoptimizeraspartoftheSELECTstatementthatcontainsit),muchlikeaview.In
fact,becauseofthisbehavior,inlineTVFsaresometimesreferredtoasparameterized
views.
TheinlineTVFdeclarationmustsimplystatethattheresultisatableviathe
RETURNSclause.ThebodyoftheinlineTVFconsistsofanSQLqueryafteraRETURN
statement.BecausetheinlineTVFreturnstheresultofasingleSELECTquery,youdon’t
needtobotherwithdeclaringatablevariableordefiningthereturn-tablestructure.The
structureoftheresultisimpliedbytheSELECTquerythatmakesupthebodyofthe
function.
ThesampleinlineTVFperformsafunctioncommonlyimplementedbydevelopersin
T-SQLusingcontrol-of-flowstatements.Manytimes,adeveloperdeterminesthata
functionorSPrequiresthatalargeorvariablenumberofparametersbepassedinto
accomplishaparticulargoal.Theidealsituationwouldbetopassanarrayasaparameter.
T-SQLdoesn’tprovideanarraydatatypeperse,butyoucansplitacomma-delimitedlist
ofstringsintoatabletosimulateanarray.Thisgivesyoutheflexibilityofanarraythat
youcanuseinSQLjoins.
TipSQLServer2012forwardallowstable-valuedparameters,whicharecoveredin
Chapter5inthediscussionofSPs.Becausetable-valuedparametershavespecial
requirements,theymaynotbeoptimalinallsituations.
AlthoughyoucoulddothisusingamultistatementTVFandcontrol-of-flowstatement
suchasaWHILEloop,yougetbetterperformanceifyouletSQLServerdotheheavy
liftingwithaset-basedsolution.Thesamplefunctionacceptsacomma-delimited
varchar(max)stringandreturnsatablewithtwocolumns,NumandElement,which
aredescribedhere:
TheNumcolumncontainsauniquenumberforeachelementofthe
array,countingfrom1tothenumberofelementsinthecomma-
delimitedstring.
TheElementcolumncontainsthesubstringsextractedfromthe
comma-delimitedlist.
Listing4-13isthefullcodelistingforthecomma-separatedstring-splittingfunction.
Thisfunctionacceptsasingleparameter,whichisacomma-delimitedstringlike
Ronnie,Bobbie,Ricky,Mike.Theoutputisatable-likerowsetwitheachcomma-
delimiteditemreturnedonitsownrow.Toavoidloopingandproceduralconstructs
(whicharen’tallowedinaninlineTVF),youusethesameNumberstablecreated
previouslyinListing4-10.
Listing4-13.Comma-SeparatedString-SplittingFunction
CREATEFUNCTIONdbo.GetCommaSplit(@Stringnvarchar(max))
RETURNStable
AS
RETURN
(
WITHSplitter(Num,String)
AS
(
SELECTNum,SUBSTRING(@String,
Num,
CASECHARINDEX(N',',@String,Num)
WHEN0THENLEN(@String)-Num+1
ELSECHARINDEX(N',',@String,Num)-Num
END
)ASString
FROMdbo.Numbers
WHERENum<=LEN(@String)
AND(SUBSTRING(@String,Num-1,1)=N','ORNum=0)
)
SELECT
ROW_NUMBER()OVER(ORDERBYNum)ASNum,
RTRIM(LTRIM(String))ASElement
FROMSplitter
WHEREString<>''
);
GO
TheinlineTVFnameandparametersaredefinedatthebeginningoftheCREATE
FUNCTIONstatement.TheRETURNStableclausespecifiesthatthefunctionreturnsa
table.Noticethatthestructureofthetableisn’tdefinedasitiswithamultistatement
TVF:
CREATEFUNCTIONdbo.GetCommaSplit(@Stringvarchar(max))
RETURNStable
ThebodyoftheinlineTVFconsistsofasingleRETURNstatementfollowedbya
SELECTquery.ThisexampleusesaCTEcalledSplittertoperformtheactual
splittingofthecomma-delimitedlist.ThequeryoftheCTEreturnseachsubstringfrom
thecomma-delimitedlist.CASEexpressionsarerequiredtohandletwospecialcases,as
follows:
Thefirstiteminthelist,becauseitisn’tprecededbyacomma
Thelastiteminthelist,becauseitisn’tfollowedbyacomma
WITHSplitter(Num,String)
AS
(
SELECTNum,SUBSTRING(@String,
Num,
CASECHARINDEX(N',',@String,Num)
WHEN0THENLEN(@String)-Num+1
ELSECHARINDEX(N',',@String,Num)-Num
END
)ASString
FROMdbo.Numbers
WHERENum<=LEN(@String)
AND(SUBSTRING(@String,Num-1,l)=N','ORNum=0)
)
Finally,thequeryselectseachROWNUMBERandElementfromtheCTEastheresult
toreturntothecaller.Extraspacecharactersarestrippedfromthebeginningandendof
eachstringreturned,andemptystringsareignored:
SELECT
ROW_NUMBER()OVER(ORDERBYNum)ASNum,
LTRIM(RTRIM(String))ASElement
FROMSplitter
WHEREString<>''
YoucanusethisinlineTVFtosplituptheJacksonfamily,asshowninListing4-14.
TheresultsareshowninFigure4-9.
Figure4-9.SplittinguptheJacksons
Listing4-14.SplittingUptheJacksons
SELECTNum,Element
FROMdbo.GetCommaSplit
('Michael,Tito,Jermaine,Marlon,Rebbie,Jackie,Janet,La
Toya,Randy');
YoucanusethistechniquetopulldescriptionsforaspecificsetofAdventureWorks
products.Ausagelikethisisgoodforfront-endwebpagedisplaysorbusinessreports
whereenduserscanselectmultipleitemsforwhichtheywantdatareturned.Listing4-15
retrievesproductinformationforacomma-delimitedlistofAdventureWorksproduct
numbers.TheresultsareshowninFigure4-10.
Listing4-15.UsingtheFnCommaSplitFunction
SELECTn.Num,
p.Name,
p.ProductNumber,
p.Color,
p.Size,
p.SizeUnitMeasureCode,
p.StandardCost,
p.ListPrice
FROMProduction.Productp
INNERJOINdbo.GetCommaSplit('FR-R38R-52,FR-M94S-52,FR-M94B-
44,BK-M68B-38')n
ONp.ProductNumber=n.Element;
Figure4-10.Usingacomma-delimitedlisttoretrieveproductinformation
RestrictionsonUser-DefinedFunctions
T-SQLimposessomerestrictionsonUDFs.Thissectiondiscussestheserestrictionsand
someofthereasoningbehindthem.
NondeterministicFunctions
T-SQLprohibitstheuseofnondeterministicfunctionsinUDFs.Adeterministicfunction
isonethatreturnsthesamevalueeverytimewhenpassedagivensetofparameters(orno
parameters).Anondeterministicfunctioncanreturndifferentresultswiththesamesetof
parameterspassedtoit.AnexampleofadeterministicfunctionisABS,themathematical
absolutevaluefunction.EverytimeandnomatterhowmanytimesyoucallABS(-10),
theresultisalways10.Thisisthebasicideabehinddeterminism.
Ontheflipside,therearefunctionsthatdon’treturnthesamevaluedespitethefact
thatyoupassinthesameparameters,ornoparameters.Built-infunctionssuchasRAND
(withoutaseedvalue)andNEWIDarenondeterministicbecausetheyreturnadifferent
resulteverytimethey’recalled.Onehackthatpeoplesometimesusetotrytocircumvent
thisrestrictioniscreatingaviewthatinvokesthenondeterministicfunctionandselecting
fromthatviewinsidetheirUDFs.Althoughthismayworktosomeextent,itisn’t
recommended:itcouldfailtoproducethedesiredresultsorcauseasignificant
performancehit,becauseSQLcan’tcacheoreffectivelyindextheresultsof
nondeterministicfunctions.Also,ifyoucreateacomputedcolumnthattriestoreference
yourUDF,thenondeterministicfunctionsyou’retryingtoaccessviayourviewcan
produceunpredictableresults.Ifyouneedtousenondeterministicfunctionsinyour
applicationlogic,SPsareprobablythebetteralternative.Chapter5discussesSPs.
NONDETERMINISTICFUNCTIONSINAUDF
InpreviousversionsofSQL,therewereseveralrestrictionsontheuseof
nondeterministicsystemfunctionsinUDFs.InSQLServer2012,theserestrictions
weresomewhatrelaxed.Youcanusethenondeterministicsystemfunctionslistedin
thefollowingtableinyourUDFs.Onethingthesesystemfunctionshaveincommon
isthattheydon’tcausesideeffectsorchangethedatabasestatewhenyouusethem:
@@CONNECTIONS @@PACK_RECEIVED @@TOTAL_WRITE
@@CPU_BUSY @@PACK_SENT CURRENT_TIMESTAMP
@@DBTS @@PACKET_ERRORS GET_TRANSMISSION_STATUS
@@IDLE @@TIMETICKS GETDATE
@@IO_BUSY @@TOTAL_ERRORS GETUTCDATE
@@MAX_CONNECTIONS @@TOTAL_READ
IfyouwanttobuildanindexonavieworcomputedcolumnthatusesaUDF,your
UDFhastobedeterministic.TherequirementstomakeaUDFdeterministicinclude
thefollowing:
TheUDFmustbedeclaredusingtheWITHSCHEMABINDING
option.WhenaUDFisschema-bound,nochangesareallowedtoany
tablesorobjectsonwhichit’sdependentwithoutdroppingtheUDF
first.
AnyfunctionsyourefertoinyourUDFmustalsobedeterministic.
Thismeansifyouuseanondeterministicsystemfunction—suchas
GETDATE—inyourUDF,it’smarkednondeterministic.
Youcan’tinvokeextendedstoredprocedures(XPs)inthefunction.
Thisshouldn’tbeaproblem,becauseXPsaredeprecatedandwillbe
removedfromfutureversionsofSQLServer.
IfyourUDFmeetsallthesecriteria,youcanchecktoseeifSQLServerhasmarked
itdeterministicviatheOBJECTPROPERTYfunction,withaquerylikethefollowing:
SELECTOBJECTPROPERTY(OBDECT_ID('dbo.GetCommaSplit'),
'IsDeterministic');
TheOBJECTPROPERTYfunctionreturns0ifyourUDFisnondeterministicand1if
it’sdeterministic.
StateoftheDatabase
OneoftherestrictionsonUDFsisthattheyaren’tallowedtochangethestateofthe
databaseorcauseothersideeffects.ThisprohibitiononsideeffectsinUDFsmeansyou
can’tevenexecutePRINTstatementsfromwithinaUDF.Italsomeansthatalthoughyou
canquerydatabasetablesandresources,youcan’texecuteINSERT,UPDATE,MERGE,or
DELETEstatementsagainstdatabasetables.Someotherrestrictionsincludethefollowing:
Youcan’tcreatetemporarytableswithinaUDF.Youcan,however,
createandmodifytablevariablesinthebodyofaUDF.
Youcan’texecuteCREATE,ALTER,orDROPondatabasetablesfrom
withinaUDF.
DynamicSQLisn’tallowedwithinaUDF,althoughXPsand
SQLCLRfunctionscanbecalled.
ATVFcanreturnonlyasingletable/resultset.Ifyouneedtoreturn
morethanonetable/resultset,youmaybebetterservedbyanSP.
MOREONSIDEEFFECTS
AlthoughXPsandSQLCLRfunctionscanbecalledfromaUDF,Microsoftwarns
againstdependingonresultsreturnedbyXPsandSQLCLRfunctionsthatcauseside
effects.IfyourXPorSQLCLRfunctionmodifiestables,altersthedatabaseschema,
accessesthefilesystem,changessystemsettings,orutilizesnon-deterministic
resourcesexternaltothedatabase,youmaygetunpredictableresultsfromyourUDF.
Ifyouneedtochangedatabasestateorrelyonsideeffectsinyourserver-sidecode,
considerusinganSQLCLRfunctionoraregularSPinsteadofaUDF.
TheprohibitiononUDFsideeffectsextendstotheSQLServerdisplayanderror
systems.Thismeansyoucan’tusetheT-SQLPRINTorRAISERRORstatementina
UDF.ThePRINTandRAISERRORstatementsareusefulindebuggingstored
proceduresandT-SQLcodebatchesbutareunavailableforuseinUDFs.One
workaroundthatIoftenuseistotemporarilymovethebodyoftheUDFcodetoan
SPwhiletesting.ThisgivesyoutheabilitytousePRINTandRAISERRORwhile
testinganddebuggingcodeindevelopmentenvironments.
VariablesandtablevariablescreatedinUDFshaveawell-definedscopeandcan’tbe
accessedoutsideoftheUDF.EvenifyouhavearecursiveUDF,youcan’taccessthe
variablesandtablevariablesthatwerepreviouslydeclaredandassignedvaluesbythe
callingfunction.IfyouneedvaluesthatweregeneratedbyaUDF,youmustpass
theminasparameterstoanotherUDFcallorreturnthemtothecallerintheUDF
result.
Summary
ThischapterdiscussedthethreetypesofT-SQLUDFsandprovidedworkingexamplesof
thedifferenttypes.ScalarUDFsareanalogoustomathematicalfunctionsthatacceptzero
ormoreparametersandreturnasinglescalarvalueforaresult.Youcanusethestandard
SQLstatements,aswellascontrol-of-flowstatements,inascalarUDF.Multistatement
TVFsallowcontrol-of-flowstatementsaswellbutreturnatable-styleresultsettothe
caller.YoucanusetheresultsetreturnedbyamultistatementTVFinWHEREandJOIN
clauses.Finally,inlineTVFsalsoreturntable-styleresultsetstothecaller;however,the
bodyconsistsofasingleSELECTquery,muchlikeanSQLview.Infact,inlineTVFsare
sometimesreferredtoasparameterizedviews.
ThetypeofUDFthatyouneedtoaccomplishagiventaskdependsontheproblem
you’retryingtosolve.Forinstance,ifyouneedtocalculateasinglescalarvalue,ascalar
UDFwilldothejob.Ontheotherhand,ifyouneedtoperformcomplexcalculationsor
manipulationsandreturnatable,amultistatementTVFmightbethecorrectchoice.
YoualsolearnedaboutrecursioninUDFs,includingthe32-levelrecursionlimit.
Although32levelsofrecursionisthehardlimit,forallpracticalpurposesyoushould
rarely—ifever—hitthislimit.Ifyoudoneedrecursionbeyond32levels,youcanreplace
recursivefunctioncallswithCTEsandotherT-SQLconstructs.
Finally,thechaptertalkedaboutdeterminismandsideeffectsinUDFs.Specifically,
yourUDFsshouldnotcausesideeffects,andspecificcriteriamustbemetinorderfor
SQLServertomarkyourUDFsasdeterministic.Determinismisanimportantaspectof
UDFsifyouplantousetheminindexedviewsorcomputedcolumns.
ThenextchapterlooksatSPs—anothertoolthatallowsproceduralT-SQLcodetobe
consolidatedintoserver-sideunits.
EXERCISES
1. [Fillintheblank]SQLServersupportsthreetypesofT-SQLUDFs:
_______,________,and_________.
2. [True/False]TheRETURNSNULLONNULLINPUToptionisa
performance-enhancingoptionavailableforusewithscalarUDFs.
3. [True/False]TheENCRYPTIONoptionprovidesasecureoption
thatpreventsanyonefromreverse-engineeringyoursourcecode.
4. [Chooseallthatapply]Youaren’tallowedtodowhichofthe
followinginamultistatementTVF?
a. ExecuteaPRINTstatement
b. CallRAISERRORtogenerateanexception
c. Declareatablevariable
d. Createatemporarytable
5. ThealgebraicformulaforconvertingFahrenheitmeasurementsto
theCelsiusscaleis:C=(F–32.0)×(5/9),whereFisthe
measurementindegreesFahrenheitandCisthemeasurementin
degreesCelsius.
WriteadeterministicscalarUDFthatconvertsameasurementindegreesFahrenheit
todegreesCelsius.TheUDFshouldacceptasinglefloatparameterandreturna
floatresult.YoucanusetheOBJECTPROPERTYfunctiontoensurethattheUDF
isdeterministic.
_______________
1RobertL.Taft,“NameSearchTechniques,”SpecialReport(Albany,NY:BureauofSystemsDevelopment,1970).
CHAPTER5
StoredProcedures
Storedprocedures(SPs)havebeenapartofT-SQLfromthebeginning.SPsprovidea
meansforcreatingserver-sidesubroutineswritteninT-SQL.SQLServer2014introduces
theabilitytonativelycompileanSPthataccessesmemory-optimizedtables.The
efficienciesgainedwithnativelycompiledSPsareanabsolutegame-changerinhowyou
considerarchitectinganOLTPdatabasesolution.
ThischapterbeginswithadiscussionofwhatSPsareandwhyyoumightwanttouse
them,anditcontinueswithadiscussionofSPcreationandusage,includingexamples.
NativelycompiledSPsareintroducedinthischapter,butthecompletepictureofhowthey
workwithin-memorytablesiscoveredinmoredetailinChapter6.
IntroducingStoredProcedures
SPsaresavedcollectionsofoneormoreT-SQLstatementsstoredontheserverascode
units.They’reanalogoustoproceduresorsubroutinesinprocedurallanguageslikeVisual
BasicandC#.Andjustlikeproceduresinprocedurallanguages,SPsgiveyoutheability
toeffectivelyextendthelanguageofSQLServerbylettingyouaddnamedcustom
subroutinestoyourdatabases.
AnSPdeclarationbeginswiththeCREATEPROCEDUREkeywordsfollowedbythe
nameoftheSP.MicrosoftrecommendsagainstnamingtheSPwiththeprefixsp_.This
prefixisusedbySQLServertonamesystemSPsandisn’trecommendedforuserSPsin
databasesotherthanthemasterdatabase.Thenamecanspecifyaschemanameand
procedurename,orjustaprocedurename.Ifyoudon’tspecifyaschemanamewhen
creatinganSP,SQLServercreatesitinthedefaultschemaforyourlogin.It’sabest
practicetoalwaysspecifytheschemanamesoyourSPsarealwayscreatedintheproper
schema,ratherthanleavingituptoSQLServer.SQLServerallowsyoutodropgroupsof
procedureswiththesamenamewithasingleDROPPROCEDUREstatement.
WarningYoucanalsodefineastoredprocedurewiththegroupnumberoptionduring
SPcreation.Thegroupnumberoptionisdeprecatedandwillberemovedfromfuture
versionsofSQLServer.Don’tusethisoptioninnewdevelopment,andstartplanningto
updatecodethatusesit.
SPs,liketheT-SQLuser-definedfunctions(UDFs)discussedinChapter4,canaccept
parametervaluesfromandreturnthemtothecaller.Theparametersarespecifiedina
comma-separatedlistfollowingtheprocedurenameintheCREATEPROCEDURE
statement.UnlikewithUDFs,whenyoucallanSP,youcanspecifytheparametersinany
order;andyoucanomitthemaltogetherifyouassignedadefaultvalueatcreationtime.
YoucanalsospecifyOUTPUTparameters,whichreturnvaluesfromtheprocedure.All
thismakesSPparametersfarmoreflexiblethanthoseofUDFs.
EachparameterisdeclaredasaspecifictypeandcanalsobedeclaredasOUTPUTor
withtheVARYINGkeyword(forcursorparametersonly).WhencallingSPs,youhave
twochoices:youcanspecifyparametersbypositionorbyname.Ifyouspecifyan
unnamedparameterlist,thevaluesareassignedbasedonposition.Ifyouspecifynamed
parametersintheformat@parameter=value,theycanbeinanyorder.Ifyour
parameterspecifiesadefaultvalueinitsdeclaration,youdon’thavetopassinavaluefor
thatparameter.UnlikeUDFs,SPsdon’trequiretheDEFAULTkeywordasaplaceholder
tospecifydefaultvalues.LeavingoutaparameterwhenyoucalltheSPappliesthedefault
valuetothatparameter.
UnlikeUDFs,whichcanreturnresultsonlyviatheRETURNstatement,SPscan
communicatewiththecallerinavarietyofways:
TheSP’sRETURNstatementcanreturnanintvaluetothecaller.
UnlikeUDFs,SPsdon’trequireaRETURNstatement.IftheRETURN
statementisleftoutoftheSP,0isreturnedbydefaultifnoerrors
wereraisedduringexecution.
SPsdon’thavethesamerestrictionsondatabasesideeffectsand
determinismasdoUDFs.SPscanread,write,delete,andupdate
permanenttables.Inthisway,thecallerandSPcancommunicate
informationtooneanotherthroughtheuseofpermanenttables.
WhenatemporarytableiscreatedinanSP,thattemporarytableis
availabletoanySPscalledbythatSP.Therearetwotypesof
temporarytables:localandglobal.Thescopeofalocaltemporary
tableisthecurrentsession,andthescopeofaglobaltemporarytable
isallsessions.Alocaltemporarytableisprefixedwith#,andaglobal
temporarytableisprefixedwith##.Asanexample,if
dbo.MyProc1createsalocaltemporarytablenamed#Tempand
thencallsdbo.MyProc2,dbo.MyProc2canaccess#Tempas
well.Ifdbo.MyProc2thencallsdbo.MyProc3,dbo.MyProc3
canalsoaccessthesame#Temptemporarytable.Globaltemporary
tablesareaccessiblebyallusersandallconnectionsafterthey’re
created.Thisprovidesausefulmethodofpassinganentiretableof
temporaryresultsfromoneSPtoanotherforfurtherprocessing.
Outputparametersprovidetheprimarymethodofretrievingscalar
resultsfromanSP.Parametersarespecifiedasoutputparameterswith
theOUTPUTkeyword.
Toreturntable-typeresultsfromanSP,theSPcanreturnoneormore
resultsets.Resultsetsarelikevirtualtablesthatcanbeaccessedby
thecaller.Unlikewithviews,updatestotheseresultsetsby
applicationsdon’tchangetheunderlyingtablesusedtogeneratethem.
Also,unliketable-valuedfunction(TVFs)andinlinefunctionsthat
returnasingletableonly,SPscanreturnmultipleresultsetswitha
singlecall.
SPRETURNSTATEMENTS
BecausetheSPRETURNstatementcan’treturntables,characterdata,decimal
numbers,andsoon,it’snormallyusedonlytoreturnanintstatusorerrorcode.
Thisisagoodconventiontofollow,becausemostdeveloperswhouseyourSPswill
expectit.Thenormalpractice,followedbymostofSQLServer’ssystemSPs,isto
returnavalueof0toindicatesuccessandanonzerovalueoranerrorcodeto
indicateanerrororafailure.
MetadataDiscovery
SQLServer2012introducedtwonewstoredproceduresandsupportingDynamic
ManagementViews(DMVs)toprovidenewcapabilitiesfordeterminingmetadata
associatedwithcodebatchesorSPs.ThissetofcapabilitiesreplacestheSETFMTONLY
option,whichisbeingdeprecated.
Oftenit’snecessarytodeterminetheformatofaresultsetwithoutactuallyexecuting
thequery.Therearealsoscenariosinwhichyouhavetoensurethatthecolumnand
parametermetadatafromqueryexecutioniscompatiblewithoridenticaltotheformatyou
specifiedbeforeexecutingthequery.Forexample,ifyouwanttogeneratedynamic
screensbasedonaSELECTstatement,youneedtomakesuretherearenometadataerrors
afterqueryexecution,sointurnyouneedtodeterminewhethertheparametermetadatais
compatiblebeforeandafterqueryexecution.Thisfunctionalityintroducesmetadata
discoverycapabilitiesforresultsetsandparametersusingtheSPs
sp_describe_first_result_setand
sp_describe_undeclared_parametersandtheDMVs
dm_exec_describe_first_result_setand
dm_exec_describe_first_result_set_for_object.
TheSPsp_describe_first_result_setanalyzesallpossiblefirstresultsets
andreturnsthemetadatainformationforthefirstresultsetthatisexecutedfromtheinput
T-SQLbatch.IftheSPreturnsmultipleresultsets,thisprocedureonlyreturnsthefirst
resultset.IfSQLServerisunabletodeterminethemetadataforthefirstquery,thenan
errorisraised.Thisproceduretakesthreeparameters:@tsqlpassestheT-SQLbatch,
@paramspassestheparametersfortheT-SQLbatch,and
@browse_information_modedetermineswhetheradditionalbrowseinformationfor
eachresultsetisreturned.
Alternatively,youcanusetheDMV
sys.dm_exec_describe_first_result_settoqueryagainst;thisDMV
returnsthesamedetailsastheSPsp_describe_first_result_set.Youcanuse
theDMVsys.dm_exec_describe_first_result_set_for_objectto
analyzeobjectssuchasSPsortriggersinthedatabaseandreturnthemetadataforthefirst
possibleresultsetandtheerrorsassociatedwiththem.Let’ssayyouwanttoanalyzeall
theobjectsinthedatabaseandusetheinformationfordocumentationpurposes.Insteadof
analyzingtheobjectsonebyone,youcanusetheDMV
sys.dm_exec_describe_first_result_set_for_objectwithaquery
similartofollowing:
SELECTp.name,p.schema_id,x.*FROMsys.procedurespCROSS
APPLY
sys.dm_exec_describe_first_result_set_for_object(p.object_id,0)
x
TheSPsp_describe_undeclared_parametersanalyzestheT-SQLbatch
andreturnsthesuggestionforthebestparameterdatatypebasedonleastnumberof
conversions.Thisfeatureisveryusefulwhenyouhavecomplicatedcalculationsor
expressionsandyou’retryingtofigureoutthebestdatatypefortheundeclaredparameter
value.
NativelyCompiledStoredProcedures
NativelycompiledstoredproceduresarenewinSQLServer2014andcanprovide
massiveperformancegains.TheseSPsaresimilartotraditionalT-SQLcompiledSPsin
thewayyoucallthemandhowtheyfunction.NativelycompiledSPsarecompiledinto
nativeCmachinecode,whichisstoredasaDLLinmachinecode.ThisallowstheCPUto
runthecodewithouttheneedtointerpretthecodeatruntime,providingforsomeextreme
performancegains.Bycontrast,traditionalT-SQLSPsareinterpretive;they’recompiled
andthenexecutedeverytimetheSPiscalled.NativelycompiledSPshaveseveral
limitationsandcanonlyaccessmemory-optimizedtables.(Memory-optimizedtablesare
discussedinChapter6.)AsofSQLServer2014RTM,creatinganativelycompiledSP
hasseverallimitationsandrequiresaveryspecificsyntax.
Listing5-1isasimpleexampleofatraditionalT-SQLinterpretedSPinthePerson
schemathatacceptsanAdventureWorksemployee’sIDandreturnstheemployee’sfull
nameande-mailaddressviaoutputparameters.Thefollowingsectioncontrastsanew
nativelycompiledSPusingthesamememory-optimizedtableobjectsinListing5-1.
NoteTheSPintheexample,Person.GetEmployee,acceptsabusinessentityID
numberasaninputparameterandreturnsthecorrespondingemployee’se-mailaddress
andfullnameasoutputparameters.IfthebusinessentityIDnumberpassedinisvalid,the
SPreturns0asareturnvalue;otherwise1isreturned.
Listing5-1.CreatingaTraditionalT-SQLSPThatRetrievesanEmployee’sNameandE-
mail
CREATEPROCEDUREPerson.GetEmployee
(
@BusinessEntityIDint=NULL
,@Email_Addressnvarchar(50)OUTPUT
,@Full_Namenvarchar(100)OUTPUT
)
AS
BEGIN
--Retrieveemailaddressandfullnamefrom
HumanResources.Employeetable
SELECT@Email_Address=ea.EmailAddress,
@Full_Name=p.FirstName+''
+COALESCE(p.MiddleName,'')+''+p.LastName
FROMHumanResources.Employeee
INNERJOINPerson.Personp
ONe.BusinessEntityID=p.BusinessEntityID
INNERJOINPerson.EmailAddressea
ONp.BusinessEntityID=ea.BusinessEntityID
WHEREe.BusinessEntityID=@BusinessEntityID;
--Returnacodeof1whennomatchisfound,0for
success
RETURN(
CASE
WHEN@Email_AddressISNULLTHEN1
ELSE0
END
);
END;
GO
Tocontrastthedifferences,seeListing5-2.Ibreakdownthedifferenceslinebyline
followingthislisting.
NoteThecodeinListing5-2willnotexecutecorrectlyonatestmachineuntilallthe
in-memorytableshavebeencreated.Chapter6discussesallthecodesamples,withan
explanationofhowtosetupthein-memorytables.
Listing5-2.NativelyCompiledSPPerson.GetEmployee_inmem
CREATEPROCEDUREPerson.GetEmployee_inmem
(
@BusinessEntityIDint=NULL
,@Email_Addressnvarchar(50)OUTPUT
,@Full_Namenvarchar(100)OUTPUT
)
/***NewInMemorySyntax***/
WITHNATIVE_COMPILATION,SCHEMABINDING,EXECUTEASOWNER
AS
/***NewInMemorySyntax***/
BEGINATOMICWITH
(TRANSACTIONISOLATIONLEVEL=SNAPSHOT,
LANGUAGE=N'us_english')
/***NewVariabletohandleReturnCodeLogic***/
DECLARE@ReturnCodebit=0;
--Retrieveemailaddressandfullnamefrom
HumanResources.Employeetable
SELECT@Email_Address=ea.EmailAddress,
@Full_Name=p.FirstName+''
+ISNULL(p.MiddleName,'')+''+p.LastName
/***NewCodetohandleReturnCodeLogic***/
,@ReturnCode=ISNULL(LEN(ea.EmailAddress,1))
FROMHumanResources.Employee_inmeme
INNERJOINPerson.Person_inmempON
e.BusinessEntityID=p.BusinessEntityID
INNERJOINPerson.EmailAddress_inmemeaON
p.BusinessEntityID=ea.BusinessEntityID
WHEREe.BusinessEntityID=@BusinessEntityID;
--Returnacodeof1whennomatchisfound,0for
success
RETURN(@ReturnCode)
END;
GO
ThereshouldseveralobviousdifferenceswhenyoulookattheSPsinListing5-1and
5-2.FollowingisanoutlineofthedifferencesandhowtocreateanativelycompiledSP:
1. ThetablesaccessedinListing5-2referencein-memorytablesonly.
Thenewtablesareidentifiedwiththe_inmemsuffix.It’san
absoluterequirementtoaccessmemory-optimizedtablesfroma
nativelycompiledSP.Chapter6goesoverhowtocreatein-memory
tables,inadditiontoseveralofthelimitationsandrequirementsfor
thesetypesoftables.
2. ThefirstdifferencefromatraditionalT-SQLSPisinline9:
a. TheWITHoptionisrequiredwiththeindicator
NATIVE_COMPILATIONtoshowthatit’sanatively
compiledSP.
b. SCHEMABINDINGmustbespecifiedsoit’sboundtothe
schemaoftheobjectsitreferences.Thetablesreferencedin
theSPcan’tbedroppedwithoutfirstdroppingtheSPitself.
c. TheEXECUTEASexecutioncontextmustbespecifiedas
EXECUTEASOWNER,EXECUTEASUSER,or
EXECUTEASSELF.ThedefaultbehaviorofaT-SQLSP
isEXECUTEASCALLER,whichisn’tsupportedina
nativelycompiledSP.
3. Thesecondlinewithadifferenceisline13.BEGINATOMICmust
bespecifiedsotheexecutionisguaranteedtobeatomic.Thereare
tworequiredoptionsfortheatomicblocks:
a. TRANSACTIONISOLATIONLEVELmustbespecified
b. LANGUAGEmustbespecified.
4. Line33iscompletelydifferentfromtheoriginalversionofthecode
(seeFigure5-1),foraveryimportantreason.Nativelycompiled
SPsdon’tsupporttheCASEstatement.Thislimitationforcedmeto
accommodateforthelogicinadifferentmanner.IntheSELECT
clause(line24inListing5-2),IcheckthecolumnforISNULLand
setthevariable@ReturnCodesothatthevalidvalueisreturned.
Figure5-1.DifferencesintheRETURNcodeblocksbetweentheoriginalT-SQLSPandthenativelycompiledSP
NativelycompiledSPshaveasignificantnumberoflimitations.They’resonumerous
thatit’sbesttoreferencetheMicrosoftMSDNforthelatestlimitationsandworkarounds
athttp://msdn.microsoft.com/en-us/library/dn246937.aspx.
Onethingtokeepinmind:thisisthefirstversionofthistypeoffunctionality.Each
timeaSPiscompiledintonativemachinecode,it’stranslatingalltheT-SQLintoC.The
limitationsarisefromthechallengesinvolvedindoingthisaccurately.Microsofthas
promisedtocontinueinvestinginadditionalcapabilitiesinthenextversionofthein-
memoryfeatures.Evenwiththeirlimitations,theenhancedperformancegainsofusing
thesefeaturesaretoocompellingtonotbeginusingthemnow.
ManagingStoredProcedures
T-SQLprovidestwostatementsthatallowyoutomodifyanddeleteSPs:ALTER
PROCEDUREandDROPPROCEDURE,respectively.ALTERPROCEDUREletsyou
modifythecodeforanSPwithoutfirstdroppingit.Thesyntaxisthesameasforthe
CREATEPROCEDUREstatement,exceptthatthekeywordsALTERPROCEDUREare
usedinplaceofCREATEPROCEDURE.ALTERPROCEDURE,likeCREATE
PROCEDURE,mustalwaysbethefirststatementinabatch.UsingtheCREATE,DROP,
andALTERPROCEDUREstatementsforcesSQLServertogenerateanewqueryplan.
TheadvantageofALTERoverCREATEorDROPisthatALTERpreservesthepermissions
fortheobject,whereasCREATEandDROPresetthepermissions.Ifyou’reusinga
nativelycompiledSP,theALTERPROCEDUREcodeisn’tallowed.Theonlywaytoalter
anativelycompiledSPistodroptheprocedureandre-createit.
Todeleteaprocedurefromyourdatabase,usetheDROPPROCEDUREstatement.
Listing5-3showshowtodroptheprocedurecreatedinListing5-1.
Listing5-3.DroppingthePerson.GetEmployeeSP
DROPPROCEDUREPerson.GetEmployee;
YoucanspecifymultipleSPsinasingleDROPPROCEDUREstatementbyputtingthe
SPnamesinacomma-separatedlist.Notethatyoucan’tspecifythedatabaseorserver
namewhendroppinganSP,andyoumustbeinthedatabasecontainingtheSPinorderto
dropit.Additionally,aswithotherdatabaseobjects,youcangrantordenyEXECUTE
permissionsonanSPthroughtheGRANTandDENYstatements.
StoredProceduresBestPractices
StoredproceduresenableyoutostorebatchesofTransact-SQLorManagedCommon
LanguageRuntime(CLR)codecentrallyontheserver.SPscanbeveryefficient;hereare
somebestpracticesthatcanaiddevelopmentandavoidcommonpitfallsthatcanhurt
performance:
UsetheSETNOCOUNTONstatementaftertheASkeyword,asthe
firststatementinthebodyoftheprocedure,whenyouhavemultiple
statementsinyourSP.ThisturnsofftheDONE_IN_PROCmessages
thatSQLServersendsbacktotheclientaftereachstatementintheSP
isexecuted.ThisalsoreducestheprocessingperformedbySQL
Serverandthesizeoftheresponsesentacrossthenetwork.
UseschemanameswhencreatingorreferencingtheSPandthe
databaseobjectsintheprocedure.ThishelpsSQLServerfindthe
objectsmorequicklyandthusreducescompilelock,whichresultsin
lessprocessingtime.
Don’tusetheSP_andsys**prefixestonameuser-createddatabase
objects.They’rereservedforMicrosoftandhavedifferentbehaviors.
AvoidusingscalarfunctionsinSELECTstatementsthatreturnmany
rowsofdata.Becausethescalarfunctionmustbeappliedtoevery
row,theresultingbehaviorislikerow-basedprocessinganddegrades
performance.
AvoidusingSELECT*,andselectonlythecolumnsyouneed.This
reducesprocessinginthedatabaseserveraswellasnetworktraffic.
UseparameterswhencallingSPstoincreaseperformance.Inyour
SPs,explicitlycreateparameterswithtype,size,andprecisionto
avoidtypeconversions.
UseexplicittransactionsbyusingBEGIN/ENDTRANSACTION,and
keeptransactionsasshortaspossible.Thelongerthetransaction,the
morechancesyouhaveforlockingorblocking,andinsomecases
deadlocking,aswell.Keeptransactionsshorttoreduceblockingand
locking.
UsetheT-SQLTRY…CATCHfeatureforerrorhandlinginprocedures.
TRY…CATCHcanencapsulateanentireblockofT-SQLstatements.If
you’reusingTRY…CATCHwithloops,placeitoutsidetheloopfor
betterperformance.Thisnotonlycreateslessperformanceoverhead,
butalsomakeserrorreportingmoreaccuratewithsignificantlyless
programming.
UseNULLorNOTNULLforeachcolumninatemporarytable.The
ANSI_DFLT_ONandANSI_DFLT_OFFoptionscontrolthewaythe
databaseengineassignstheNULLorNOTNULLattributetocolumns
whentheseattributesaren’tspecifiedinaCREATETABLEor
ALTERTABLEstatement.Ifaconnectionexecutesaprocedurewith
differentsettingsfortheseoptionsthantheconnectionthatcreatedthe
procedure,thecolumnsofthetablecreatedforthesecondconnection
canhavedifferentnullabilityandexhibitdifferentbehavior.IfNULL
orNOTNULLisexplicitlystatedforeachcolumn,thetemporary
tablesarecreatedbyusingthesamenullabilityforallconnectionsthat
executetheprocedure.
UsetheUNIONALLoperatorinsteadoftheUNIONorORoperator,
unlessthereisaspecificneedfordistinctvalues.UNIONfiltersand
removestheduplicaterecords,whereastheUNIONALLoperator
requireslessprocessingoverheadbecauseduplicatesaren’tfilteredout
oftheresultset.
WHYSTOREDPROCEDURES?
DebateshaveragedthroughtheyearsovertheutilityofSQLServerSPs.Traditional
SPsinSQLServer2014offerthesameexecutionplancachingandreuse,butthe
lusterofthisbenefithasfadedsomewhat.Queryoptimization,querycaching,and
reuseofqueryexecutionplansforparameterizedquerieshavebeeninastateof
constantimprovementsinceSQLServer2000.Queryoptimizationhasbeen
improvedevenmoreinSQLServer2014.SPsstilloffertheperformancebenefitof
nothavingtosendlargeandcomplexqueriesoverthenetwork,buttheprimary
benefitofqueryexecutionplancachingandreuseisn’tasenticingasitoncewas.
SowhyuseSPs?Apartfromtheperformancebenefit,whichisn’tasbigafactorin
thesedaysofhighlyefficientparameterizedqueries,SPsoffercodemodularization
andsecurity.Creatingcodemoduleshelpsreduceredundantcode,eliminating
potentialmaintenancenightmarescausedbyduplicatecodestoredinmultiple
locations.ByusingSPs,youcandenyuserstheabilitytoperformdirectqueries
againsttables,butstillallowthemtouseSPstoretrievetherelevantdatafromthose
tables.SPsalsooffertheadvantageofcentralizedadministrationofportionsofyour
databasecode.Finally,SPscanreturnmultipleresultsetswithasingleprocedure
call,suchasthesp_helpsystemSPdemonstratedhere(theresultsareshownin
Figure5-2):
EXECUTEdbo.sp_help;
Figure5-2.Resultsofthedbo.sp_helpSPcall
UsingSPs,youcaneffectivelybuildanapplicationprogramminginterface(API)for
yourdatabase.YoucanalsominimizeandalmostpreventSQLinjectionbyusingSPs
withinputparameterstofilterandvalidatealltheinputs.Creationandadherenceto
suchanAPIcanhelpensureconsistentaccessacrossapplicationsandmake
developmenteasierforfront-endandclient-sidedeveloperswhoneedtoaccessyour
database.Somethird-partyapplications,suchascertainETLprogramsanddatabase
drivers,alsorequireSPs.
UsingnativelycompiledSPswillchangethewaySPsarethoughtofinthe
architectureofanapplication.Becausethey’recompiledintomachinelanguage,
therewillbeinstancesthatplacingbusinesslogicdirectlyinthedatabaselayerwill
performbetterthanotherarchitectures.
WhataretheargumentsagainstSPs?Onemajorissuetendstobethattheytightly
coupleyourcodetotheDBMS.AcodebasethatistightlyintegratedwithSQL
Server2014willbemoredifficulttoporttoanotherRDBMS(suchasOracle,DB2,
orMySQL)inthefuture.Alooselycoupledapplication,ontheotherhand,ismuch
easiertoporttodifferentSQLDBMSs.
Portability,inturn,hasitsownproblems.Trulyportablecodecanresultindatabases
andapplicationsthatareslowandinefficient.Togettrueportabilityoutofany
RDBMSsystem,youhavetotakegreatcaretocodeeverythinginplainvanillaSQL,
meaningalotoftheplatform-specificperformance-enhancingfunctionalityoffered
bySQLServerisoff-limits.
I’mnotgoingtodivetoodeeplyintoadiscussionoftheplusesandminusesofSPs.
Intheend,thebalancebetweenportabilityandperformanceneedstobedetermined
byyourbusinessrequirementsandcorporateITpoliciesonaper-projectbasis.Just
keepthesecompetingfactorsinmindwhenmakingthatdecision.
StoredProcedureExample
AcommonapplicationofSPsistocreatealayerofabstractionforvariousdataquery,
aggregation,andmanipulationfunctionality.TheexampleSPinListing5-4performsthe
commonbusinessreportingtaskofcalculatingarunningtotal.Theresultsareshownin
Figure5-3.
Listing5-4.ProceduretoCalculateandRetrieveaRunningTotalforSales
CREATEPROCEDURESales.GetSalesRunningTotal(@Yearint)
AS
BEGIN
WITHRunningTotalCTE
AS
(
SELECTsoh.SalesOrderNumber,
soh.OrderDate,
soh.TotalDue,
(
SELECTSUM(soh1.TotalDue)
FROMSales.SalesOrderHeadersoh1
WHEREsoh1.SalesOrderNumber<=soh.SalesOrderNumber
)ASRunningTotal,
SUM(soh.TotalDue)OVER()ASGrandTotal
FROMSales.SalesOrderHeadersoh
WHEREDATEPART(year,soh.OrderDate)=@Year
GROUPBYsoh.SalesOrderNumber,
soh.OrderDate,
soh.TotalDue
)
SELECTrt.SalesOrderNumber,
rt.OrderDate,
rt.TotalDue,
rt.RunningTotal,
(rt.RunningTotal/rt.GrandTotal)*100AS
PercentTotal
FROMRunningTotalCTErt
ORDERBYrt.SalesOrderNumber;
RETURN0;
END;
GO
EXECSales.GetSalesRunningTotal@Year=2014;
GO
Figure5-3.Partialresultsoftherunningtotalcalculationfortheyear2014
TheSPinListing5-4acceptsasingleintparameterindicatingtheyearforwhichthe
calculationshouldbeperformed:
CREATEPROCEDURESales.GetSalesRunningTotal(@Yearint)
TheSPusesacommontableexpression(CTE)toreturntherelevantdatafortheyear
specified,includingcalculationsfortherunningtotalviaasimplescalarsubqueryandthe
grandtotalviaaSUMcalculationwithanOVERclause:
WITHRunningTotalCTE
AS
(
SELECTsoh.SalesOrderNumber,
soh.OrderDate,
soh.TotalDue,
(
SELECTSUM(soh1.TotalDue)
FROMSales.SalesOrderHeadersoh1
WHEREsoh1.SalesOrderNumber<=soh.SalesOrderNumber
)ASRunningTotal,
SUM(soh.TotalDue)OVER()ASGrandTotal
FROMSales.SalesOrderHeadersoh
WHEREDATEPART(year,soh.OrderDate)=@Year
GROUPBYsoh.SalesOrderNumber,
soh.OrderDate,
soh.TotalDue
)
TheresultsetisreturnedbytheCTE’souterSELECTquery,andtheSPfinisheswitha
RETURNstatementthatsendsareturncodeof0backtothecaller:
SELECTrt.SalesOrderNumber,
rt.OrderDate,
rt.TotalDue,
rt.RunningTotal,
(rt.RunningTotal/rt.GrandTotal)*100ASPercentTotalFROM
RunningTotalCTErtORDERBYrt.SalesOrderNumber;RETURN0;
RUNNINGSUMS
Therunningsum,orrunningtotal,isaverycommonlyusedbusinessreportingtool.
Arunningsumcalculatestotalsasofcertainpointsintime(usuallydollaramounts,
andoftencalculatedoverdays,months,quarters,oryears—butnotalways).In
Listing5-4,therunningsumiscalculatedperorder,foreachdayoverthecourseofa
givenyear.
Therunningsumgeneratedinthesamplegivesyouatotalsalesamountasofthe
dateandtimewheneachorderisplaced.Whenthefirstorderisplaced,therunning
sumisequaltotheamountofthatorder.Whenthesecondorderisplaced,the
runningsumisequaltotheamountofthefirstorderplustheamountofthesecond
order,andsoon.Anothercloselyrelatedandoftenusedcalculationistherunning
average,whichrepresentsacalculatedpoint-in-timeaverageasopposedtoapoint-
in-timesum.
Asaninterestingaside,theISOSQLstandardallowsyoutousetheOVERclause
withaggregatefunctionslikeSUMandAVG.TheISOSQLstandardallowsthe
ORDERBYclausetobeusedwiththeaggregatefunctionOVERclause,makingfor
extremelyefficientandcompactrunningsumcalculations.Unfortunately,SQL
Server2012doesn’tsupportthisparticularoption,soyoustillhavetoresortto
subqueriesandotherlessefficientmethodsofperformingthesecalculationsfornow.
Forthenextexample,assumethatAdventureWorksmanagementhasdecidedtoadda
database-drivenfeaturetoitswebsite.Thefeaturetheywantisa“recommendedproducts
list”thatwillappearwhencustomersaddproductstotheironlineshoppingcarts.Of
course,thefirststeptoimplementinganysolutionistoclearlydefinetherequirements.
Thedetailsoftherequirements-gatheringprocessarebeyondthescopeofthisbook,so
youworkundertheassumptionthattheAdventureWorksbusinessanalystshavedone
theirduediligenceandreportedbackthefollowingbusinessrulesforthisparticular
function:
Therecommendedproductslistshouldincludeadditionalitemson
ordersthatcontaintheproductselectedbythecustomer.Asan
example,iftheproductselectedbythecustomerisproductID773
(thesilverMountain-10044-inchbike),thenitemspreviouslybought
byothercustomersinconjunctionwiththisbike—likeproductID712
(theAWClogocap)—shouldberecommended.
Productsthatareinthesamecategoryastheproductthecustomer
selectedshouldnotberecommended.Asanexample,ifacustomer
hasaddedabicycletoanorder,otherbicyclesshouldnotbe
recommended.
Therecommendedproductlistshouldnevercontainmorethanten
items.
ThedefaultproductIDshouldbe776,theblackMountain-10042-
inchbike.
Therecommendedproductsshouldbelistedindescendingorderof
thetotalquantitythathasbeenordered.Inotherwords,thebest-
sellingitemswillbelistedintherecommendationslistfirst.
Listing5-5showstheSPthatimplementsallthesebusinessrulestoreturnalistof
recommendedproductsbasedonagivenproductID.
Listing5-5.RecommendedProductListSP
CREATEPROCEDUREProduction.GetProductRecommendations
(@ProductIDint=776)
AS
BEGIN
WITHRecommendedProducts
(
ProductID,
ProductSubCategoryID,
TotalQtyOrdered,
TotalDollarsOrdered
)
AS
(
SELECT
od2.ProductID,
p1.ProductSubCategoryID,
SUM(od2.OrderQty)ASTotalQtyOrdered,
SUM(od2.UnitPrice*od2.OrderQty)ASTotalDollarsOrdered
FROMSales.SalesOrderDetailod1
INNERJOINSales.SalesOrderDetailod2
ONod1.SalesOrderID=od2.SalesOrderID
INNERJOINProduction.Productp1
ONod2.ProductID=p1.ProductID
WHEREod1.ProductID=@ProductID
ANDod2.ProductID<>@ProductID
GROUPBY
od2.ProductID,
p1.ProductSubcategoryID
)
SELECTTOP(10)ROW_NUMBER()OVER
(
ORDERBYrp.TotalQtyOrderedDESC
)ASRank,
rp.TotalQtyOrdered,
rp.ProductID,
rp.TotalDollarsOrdered,
p.[Name]
FROMRecommendedProductsrp
INNERJOINProduction.Productp
ONrp.ProductID=p.ProductID
WHERErp.ProductSubcategoryID<>
(
SELECTProductSubcategoryID
FROMProduction.Product
WHEREProductID=@ProductID
)
ORDERBYTotalQtyOrderedDESC;
END;
GO
TheSPbeginswithadeclarationthatacceptsasingleparameter,@ProductID.The
default@ProductIDissetto776,pertheAdventureWorksmanagementteam’srules:
CREATEPROCEDUREProduction.GetProductRecommendations
(@ProductIDint=776)
Next,theCTEthatwillreturntheTotalQtyOrdered,ProductID,
TotalDollarsOrdered,andProductSubCategoryIDforeachproductis
defined:
WITHRecommendedProducts(
ProductID,
ProductSubCategorylD,
TotalQtyOrdered,
TotalDollarsOrdered)
InthebodyoftheCTE,theSales.SalesOrderDetailtableisjoinedtoitself
basedonSalesOrderlD.AjointotheProduction.Producttableisalsoincluded
togeteachproduct’sSubcategorylD.Thepointoftheself-joinistograbthetotal
quantityordered(OrderQty)andthetotaldollarsordered(UnitPrice*
OrderQty)foreachproduct.
Thequeryisdesignedtoincludeonlyordersthatcontaintheproductpassedinvia
@ProductIDintheWHEREclause,anditalsoeliminatesresultsfor@ProductIDitself
fromthefinalresults.AlltheresultsaregroupedbyProductIDand
ProductSubcategorylD:
(
SELECT
od2.ProductID,
p1.ProductSubCategoryID,
SUM(od2.OrderQty)ASTotalQtyOrdered,
SUM(od2.UnitPrice*od2.OrderQty)ASTotalDollarsOrdered
FROMSales.SalesOrderDetailod1
INNERJOINSales.SalesOrderDetailod2
ONod1.SalesOrderID=od2.SalesOrderID
INNERJOINProduction.Productp1
ONod2.ProductID=p1.ProductID
WHEREod1.ProductID=@ProductID
ANDod2.ProductID<>@ProductID
GROUPBY
od2.ProductID,
p1.ProductSubcategoryID
)
ThefinalpartoftheCTEexcludesproductsthatareinthesamecategoryastheitem
passedinby@ProductID.Itthenlimitstheresultstothetoptenandnumberstheresults
fromhighesttolowestbyTotalQtyOrdered.Italsojoinsonthe
Production.Producttabletogeteachproduct’sname:
SELECTTOP(lO)ROW_NUMBER()OVER(
ORDERBYrp.TotalOtyOrderedDESC)ASRank,
rp.TotalOtyOrdered,
rp.ProductID,
rp.TotalDollarsOrdered,
p.[Name]
FROMRecommendedProductsrpINNERJOINProduction.Productp
ONrp.ProductID=p.ProductIDWHERErp.ProductSubcategorylD
<>(
SELECTProductSubcategorylDFROMProduction.ProductWHERE
ProductID=@ProductID)ORDERBYTotalOtyOrderedDESC;
Figure5-4showstheresultsetofarecommendedproductlistforpeoplewhoboughta
silverMountain-10044-inchbike(ProductID=773),asshowninListing5-6.
Figure5-4.RecommendedproductlistforProductID773
Listing5-6.GettingaRecommendedProductList
EXECUTEProduction..GetProductRecommendations773;
ImplementingthisbusinesslogicinanSPprovidesalayerofabstractionthatmakesit
easiertousefromfront-endapplications.Front-endapplicationprogrammersdon’tneed
toworryaboutthedetailsofwhichtablesneedtobeaccessed,howtheyneedtobejoined,
andsoon.Allyourapplicationdevelopersneedtoknowtoutilizethislogicfromthefront
endisthattheyneedtopasstheSPaProductIDnumberparameter,anditwillreturn
therelevantinformationinawell-definedresultset.
Thesameprocedurepromotescodereuse,andifyouhavebusinesslogicimplemented
withcomplexcodeinanSP,thecodedoesn’thavetobewrittenmultipletimes;instead
youcansimplycalltheSPtoaccessthecode.Also,ifyouneedtochangethebusiness
logic,itcanbedoneonetime,inoneplace.ConsiderwhathappensiftheAdventureWorks
managementdecidestomakesuggestionsbasedontotaldollars’worthofaproduct
orderedinsteadofthetotalquantityordered.YoucanchangetheORDERBYclausefrom
this
ORDERBYTotalOtyOrderedDESC;
tothefollowing:
ORDERBYTotalDollarsOrderedDESC;
Thissimplechangeintheproceduredoesthetrick.Noadditionalchangestofront-end
codeorlogicarerequired,andnorecompilationandredeploymentofcodetowebserver
farmsisneeded,becausetheinterfacetotheSPremainsthesame.
RecursioninStoredProcedures
LikeUDFs,SPscancallthemselvesrecursively.ThereisanSQLServer–imposedlimitof
32levelsofrecursion.Todemonstraterecursion,let’ssolveaveryoldpuzzle.
TheTowersofHanoipuzzleconsistsofthreepegsandaspecifiednumberofdiscsof
varyingsizesthatslideontothepegs.Thepuzzlebeginswiththediscsstackedontopof
oneanother,fromsmallesttolargest,allononepeg.TheTowersofHanoipuzzle’s
startingpositionisshowninFigure5-5.
Figure5-5.TheTowersofHanoipuzzle’sstartingposition
Theobjectofthepuzzleistomoveallthediscsfromthefirsttowertothethirdtower.
Thetrickisthatyoucanonlymoveonediscatatime,andnolargerdiscmaybestacked
ontopofasmallerdiscatanytime.Youcantemporarilyplacediscsonthemiddletower
asnecessary,andyoucanstackanysmallerdiscontopofalargerdisconanytower.The
TowersofHanoipuzzleisoftenusedasanexerciseincomputersciencecoursesto
demonstraterecursioninprocedurallanguages.ThismakesitaperfectcandidateforaT-
SQLsolutiontodemonstrateSPrecursion.
TheT-SQLimplementationoftheTowersofHanoipuzzleusesfivediscsanddisplays
eachmoveasthecomputermakesit.ThecompletesolutionisshowninListing5-7.
Listing5-7.TheTowersofHanoiPuzzle
--Thisstoredproceduredisplaysallthediscsinthe
appropriate
--towers.
CREATEPROCEDUREdbo.ShowTowers
AS
BEGIN
--Eachdiscisdisplayedlikethis"===3==="wherethe
numberisthedisc
--andthewidthofthe===signsoneitherside
indicatesthewidthofthe
--disc.
--TheseCTEsaredesignedfordisplayingthediscsin
properorderoneach
--tower.
WITHFiveNumbers(Num)—RecursiveCTEgeneratestablewith
numbers1…5
AS
(
SELECT1
UNIONALL
SELECTNum+1
FROMFiveNumbers
WHERENum<5
),
GetTowerA(Disc)—ThediscsforTowerA
AS
(
SELECTCOALESCE(a.Disc,-1)ASDisc
FROMFiveNumbersf
LEFTJOIN#TowerAa
ONf.Num=a.Disc
),
GetTowerB(Disc)—ThediscsforTowerB
AS
(
SELECTCOALESCE(b.Disc,-1)ASDisc
FROMFiveNumbersf
LEFTJOIN#TowerBb
ONf.Num=b.Disc
),
GetTowerC(Disc)—ThediscsforTowerC
AS
(
SELECTCOALESCE(c.Disc,-1)ASDisc
FROMFiveNumbersf
LEFTJOIN#TowerCc
ONf.Num=c.Disc
)
--ThisSELECTquerygeneratesthetextrepresentationfor
allthreetowers
--andallfivediscs.FULLOUTERJOINisusedtorepresent
thetowersina
--side-by-sideformat.
SELECTCASEa.Disc
WHEN5THEN'=====5====='
WHEN4THEN'====4===='
WHEN3THEN'===3==='
WHEN2THEN'==2=='
WHEN1THEN'=1='
ELSE'|'
ENDASTower_A,
CASEb.Disc
WHEN5THEN'=====5====='
WHEN4THEN'====4===='
WHEN3THEN'===3==='
WHEN2THEN'==2=='
WHEN1THEN'=1='
ELSE'|'
ENDASTower_B,
CASEc.Disc
WHEN5THEN'=====5====='
WHEN4THEN'====4===='
WHEN3THEN'===3==='
WHEN2THEN'==2=='
WHEN1THEN'=1='
ELSE'|'
ENDASTower_C
FROM(
SELECTROW_NUMBER()OVER(ORDERBYDisc)ASNum,
COALESCE(Disc,-1)ASDisc
FROMGetTowerA
)a
FULLOUTERJOIN(
SELECTROW_NUMBER()OVER(ORDERBYDisc)ASNum,
COALESCE(Disc,-1)ASDisc
FROMGetTowerB
)b
ONa.Num=b.Num
FULLOUTERJOIN(
SELECTROW_NUMBER()OVER(ORDERBYDisc)ASNum,
COALESCE(Disc,-1)ASDisc
FROMGetTowerC
)c
ONb.Num=c.Num
ORDERBYa.Num;
END;
GO
--ThisSPmovesasinglediscfromthespecifiedsource
towertothe
--specifieddestinationtower.
CREATEPROCEDUREdbo.MoveOneDisc(@Sourcenchar(1),
@Destnchar(1))
AS
BEGIN
--@SmallestDiscisthesmallestdisconthesource
tower
DECLARE@SmallestDiscint=0;
--IF…ELSEconditionalstatementgetsthesmallestdisc
fromthe
--correctsourcetower
IF@Source=N'A'
BEGIN
--ThisgetsthesmallestdiscfromTowerA
SELECT@SmallestDisc=MIN(Disc)
FROM#TowerA;
--ThendeleteitfromTowerA
DELETEFROM#TowerA
WHEREDisc=@SmallestDisc;
END
ELSEIF@Source=N'B'
BEGIN
--ThisgetsthesmallestdiscfromTowerB
SELECT@SmallestDisc=MIN(Disc)
FROM#TowerB;
--ThendeleteitfromTowerB
DELETEFROM#TowerB
WHEREDisc=@SmallestDisc;
END
ELSEIF@Source=N'C'
BEGIN
--ThisgetsthesmallestdiscfromTowerC
SELECT@SmallestDisc=MIN(Disc)
FROM#TowerC;
--ThendeleteitfromTowerC
DELETEFROM#TowerC
WHEREDisc=@SmallestDisc;
END
--Showthediscmoveperformed
SELECTN'MovingDisc('+CAST(COALESCE(@SmallestDisc,0)AS
nchar(1))+
N')fromTower'+@Source+N'toTower'+@Dest+':'
ASDescription;
--Performthemove-INSERTthediscfromthesourcetower
intothe
--destinationtower
IF@Dest=N'A'
INSERTINTO#TowerA(Disc)VALUES(@SmallestDisc);
ELSEIF@Dest=N'B'
INSERTINTO#TowerB(Disc)VALUES(@SmallestDisc);
ELSEIF@Dest=N'C'
INSERTINTO#TowerC(Disc)VALUES(@SmallestDisc);
--Showthetowers
EXECUTEdbo.ShowTowers;
END;
GO
--ThisSPmovesmultiplediscsrecursively
CREATEPROCEDUREdbo.MoveDiscs(@DiscNumint,
@MoveNumintOUTPUT,
@Sourcenchar(1)=N'A',
@Destnchar(1)=N'C',
@Auxnchar(1)=N'B'
)
AS
BEGIN
--Ifthenumberofdiscstomoveis0,thesolutionhas
beenfound
IF@DiscNum=0
PRINTN'Done';
ELSE
BEGIN
-
-Ifthenumberofdiscstomoveis1,goaheadandmoveit
IF@DiscNum=1
BEGIN
--Increasethemovecounterby1
SELECT@MoveNum+=1;
--Andfinallymoveonediscfromsourceto
destination
EXECdbo.MoveOneDisc@Source,@Dest;
END
ELSE
BEGIN
-
-Determinenumberofdiscstomovefromsourcetoauxiliarytower
DECLARE@nint=@DiscNum-1;
--Move(@DiscNum-1)discsfromsourceto
auxiliarytower
EXECdbo.MoveDiscs@n,@MoveNumOUTPUT,
@Source,@Aux,@Dest;
--Move1discfromsourcetofinaldestinationtower
EXECdbo.MoveDiscs1,@MoveNumOUTPUT,@Source,
@Dest,@Aux;
--Move(@DiscNum-1)discsfromauxiliaryto
finaldestinationtower
EXECdbo.MoveDiscs@n,@MoveNumOUTPUT,@Aux,
@Dest,@Source;
END;
END;
END;
GO
--ThisSPcreatesthethreetowersandpopulatesTower
Awith5discs
CREATEPROCEDUREdbo.SolveTowers
AS
BEGIN
--SETNOCOUNTONtoeliminatesystemmessagesthatwill
clutterup
--theMessagedisplay
SETNOCOUNTON;
--Createthethreetowers:TowerA,TowerB,andTower
C
CREATETABLE#TowerA(DiscintPRIMARYKEYNOTNULL);
CREATETABLE#TowerB(DiscintPRIMARYKEYNOTNULL);
CREATETABLE#TowerC(DiscintPRIMARYKEYNOTNULL);
--PopulateTowerAwithallfivediscs
INSERTINTO#TowerA(Disc)
VALUES(1),(2),(3),(4),(5);
--Initializethemovenumberto0
DECLARE@MoveNumint=0;
--Showtheinitialstateofthetowers
EXECUTEdbo.ShowTowers;
--Solvethepuzzle.Noticeyoudon'tneedtospecify
theparameters
--withdefaults
EXECUTEdbo.MoveDiscs5,@MoveNumOUTPUT;
--Howmanymovesdidittake?
PRINTN'Solvedin'+CAST(@MoveNumASnvarchar(10))
+N'moves.';
--Dropthetemptablestocleanup-alwaysagood
idea.
DROPTABLE#TowerC;
DROPTABLE#TowerB;
DROPTABLE#TowerA;
--SETNOCOUNTOFFbeforeweexit
SETNOCOUNTOFF;
END;
GO
Tosolvethepuzzle,justrunthefollowingstatement:
--Solvethepuzzle
EXECUTEdbo.SolveTowers;
Figure5-6isascreenshotoftheprocessingasthediscsaremovedfromtowerto
tower.
Figure5-6.Discsaremovedfromtowertotower.
NoteTheresultsofListing5-7arebestviewedinResultstoTextmode.Youcanput
SSMSinResultstoTextmodebypressingCtrl+TwhileintheQueryEditorwindow.To
switchtoResultstoGridmode,pressCtrl+D.
Themainprocedureyoucalltosolvethepuzzleisdbo.SolveTowers.ThisSP
createsthreetemporarytablesnamed#TowerA,#TowerB,and#TowerC.Itthen
populates#TowerAwithfivediscsandinitializesthecurrentmovenumberto0:
--Createthethreetowers:TowerA,TowerB,andTowerC
CREATETABLE#TowerA(DiscintPRIMARYKEYNOTNULL);
CREATETABLE#TowerB(DiscintPRIMARYKEYNOTNULL);
CREATETABLE#TowerC(DiscintPRIMARYKEYNOTNULL);
--PopulateTowerAwithallfivediscs
INSERTINTO#TowerA(Disc)
VALUES(1),(2),(3),(4),(5);
--Initializethemovenumberto0
DECLARE@MoveNumINT=0;
BecausethisSPistheentrypointfortheentirepuzzle-solvingprogram,itdisplaysthe
startpositionofthetowersandcallsdbo.MoveDiscstogettheballrolling:
--Showtheinitialstateofthetowers
EXECUTEdbo.ShowTowers;
--Solvethepuzzle.Noticeyoudon'tneedtospecifythe
parameters
--withdefaults
EXECUTEdbo.MoveDiscs5,@MoveNumOUTPUT;
Whenthepuzzleisfinallysolved,controlreturnsbackfromdbo.MoveDiscsto
dbo.SolveTowers,whichdisplaysthenumberofstepsittooktocompletethepuzzle
andperformssomecleanupwork,likedroppingthetemporarytables:
--Howmanymovesdidittake?
PRINTN'Solvedin'+CAST(@MoveNumASnvarchar(10))+N'
moves.';
--Dropthetemptablestocleanup-alwaysagoodidea.
DROPTABLE#TowerC;
DROPTABLE#TowerB;
DROPTABLE#TowerA;
--SETNOCOUNTOFFbeforeweexit
SETNOCOUNTOFF;
TipWhenanSPthatcreatedlocaltemporarytablesisnolongerinscope,thelocal
temporarytablesareautomaticallydropped.Becausetemporarytablesarecreatedinthe
tempdbsystemdatabase,it’sagoodideatogetinthehabitofexplicitlydropping
temporarytables.Byexplicitlydroppingtemporarytables,youcanguaranteethatthey
existonlyaslongasthey’reneeded,whichcanhelpminimizecontentioninthetempdb
database.
Theprocedureresponsibleformovingdiscsfromtowertotowerrecursivelyis
dbo.MoveDiscs.Thisprocedureacceptsseveralparameters,includingthenumberof
discstomove(@DiscNum);thenumberofthecurrentmove(@MoveNum);andthenames
ofthesource,destination,andauxiliary/intermediatetowers.ThisprocedureusesT-SQL
proceduralIFstatementstodeterminewhichtypesofmovesarerequired—single-disc
moves,recursivemultiple-discmoves,ornomoremoves(whenthesolutionisfound).If
thesolutionhasbeenfound,themessageDoneisdisplayed,andcontrolissubsequently
passedbacktothecallingprocedure,dbo.SolveTowers:
--Ifthenumberofdiscstomoveis0,thesolutionhas
beenfound
IF@DiscNum=0
PRINTN'Done';
ELSE
RETURN0;
Ifthereisonlyonedisctomove,themovecounterisincrementedand
dbo.MoveOneDisciscalledtoperformthemove:
--Ifthenumberofdiscstomoveis1,goaheadandmoveit
IF@DiscNum=1
BEGIN
--Increasethemovecounterby1
SELECT@MoveNum+=1;
--Andfinallymoveonediscfromsourcetodestination
EXECdbo.MoveOneDisc@Source,@Dest;
END
Finally,ifthereismorethanonediscmoverequired,dbo.MoveDiscscallsitself
recursivelyuntilthereareeitheroneorzerodiscslefttomove:
ELSE
BEGIN
--Determinenumberofdiscstomovefromsourceto
auxiliarytower
DECLARE@nINT=@DiscNum-1;
--Move(@DiscNum-1)discsfromsourcetoauxiliarytower
EXECdbo.MoveDiscs@n,@MoveNumOUTPUT,@Source,@Aux,
@Dest;
--Move1discfromsourcetofinaldestinationtower
EXECdbo.MoveDiscs1,@MoveNumOUTPUT,@Source,@Dest,@Aux;
--Move(@DiscNum-1)discsfromauxiliarytofinal
destinationtower
EXECdbo.MoveDiscs@n,@MoveNumOUTPUT,@Aux,@Dest,
@Source;
END;
ThebasisoftheTowersofHanoipuzzleisthemovementofasinglediscatatime
fromtowertotower,sothemostbasicprocedure,dbo.MoveOneDisc,simplymovesa
discfromthespecifiedsourcetowertothespecifieddestinationtower.Givensourceand
destinationtowersasinputs,thisprocedurefirstdeterminesthesmallest(ortop)discon
thesourceandmovesittothedestinationtableusingsimpleSELECTqueries.The
smallestdiscisthendeletedfromthesourcetable:
--@SmallestDiscisthesmallestdisconthesourcetower
DECLARE@SmallestDiscint=0;
--IF…ELSEconditionalstatementgetsthesmallestdisc
fromthe
--correctsourcetower
IF@Source=N'A'
BEGIN
--ThisgetsthesmallestdiscfromTowerA
SELECT@SmallestDisc=MIN(Disc)
FROM#TowerA;
--ThendeleteitfromTowerA
DELETEFROM#TowerA
WHEREDisc=@SmallestDisc;
END
Oncethesmallestdiscofthesourcetableisdetermined,dbo.MoveOneDisc
displaysthemoveit’sabouttoperformandthenperformstheINSERTtoplacethediscin
thedestinationtower.Finally,itcallsthedbo.ShowTowersproceduretoshowthe
currentstateofthetowersanddiscs:
--Showthediscmoveperformed
SELECTN'MovingDisc
('+CAST(COALESCE(@SmallestDisc,0)ASnchar(1))
+N')
FROMTower'+@Source+N'toTower'
+@Dest+':'ASDescription;
--Performthemove-INSERTthediscfromthesourcetower
intothe
--destinationtower
IF@Dest=N'A'
INSERTINTO#TowerA(Disc)VALUES(@SmallestDisc);
ELSEIF@Dest=N'B'
INSERTINTO#TowerB(Disc)VALUES(@SmallestDisc);
ELSEIF@Dest=N'C
INSERTINTO#TowerC(Disc)VALUES(@SmallestDisc);
--Showthetowers
EXECUTEdbo.ShowTowers;
Thedbo.ShowTowersproceduredoesn’taffectprocessing;it’sincludedasa
conveniencetooutputareasonablerepresentationofthetowersanddiscstheycontainat
anygivenpointduringprocessing.
ThisimplementationofasolverfortheTowersofHanoipuzzledemonstratesseveral
aspectsofSPsintroducedinthischapter,includingthefollowing:
SPscancallthemselvesrecursively.Thisisdemonstratedwiththe
dbo.MoveDiscsprocedure,whichcallsitselfuntilthepuzzleis
solved.
WhendefaultvaluesareassignedtoparametersinanSPdeclaration,
youdon’thavetospecifyvaluesforthemwhenyoucallthe
procedure.Thisconceptisdemonstratedinthedbo.SolveTowers
procedure,whichcallsthedbo.MoveDiscsprocedure.
ThescopeoftemporarytablescreatedinanSPincludestheprocedure
inwhichthey’recreated,aswellasanySPsitcallsandanySPsthey
inturncall.Thisisdemonstratedindbo.SolveTowers,which
createsthreetemporarytablesandthencallsotherproceduresthat
accessthosesametemporarytables.Theprocedurescalledby
dbo.SolveTowersandthosecalledbythoseprocedures(andso
on)canalsoaccessthesesametemporarytables.
Thedbo.MoveDiscsSPdemonstratesoutputparameters.This
procedureusesanoutputparametertoupdatethecountofthetotal
numberofmovesperformedaftereachmove.
Table-ValuedParameters
BeginningwithSQLServer2008,youcanpasstable-valuedparameterstoSPsandUDFs.
PriortoSQLServer2008,theprimarymethodsofpassingmultiplerowsofdatatoanSP
includedthefollowing:
Convertingmultiplerowstoanintermediateformatlikecomma-
delimitedorXML.Ifyouusethismethod,youhavetoparseoutthe
parameterintoatemporarytable,tablevariable,orsubquerytoextract
therowsfromtheintermediateformat.Theseconversionstoandfrom
intermediateformatcanbecostly,especiallywhenlargeamountsof
dataareinvolved.
Placingrowsinapermanentortemporarytableandcallingthe
procedure.Thismethodeliminatesconversionstoandfromthe
intermediateformat,butitisn’twithoutproblemsofitsown.
Managingmultiplesetsofinputrowsfrommultiplesimultaneous
userscanintroducealotofoverheadandadditionalconversioncode
thatmustbemanaged.
PassinglotsandlotsofparameterstotheSP.SQLServerSPscan
acceptupto2,100parameters.Conceivably,youcouldpassseveral
rowsofdatausingthousandsofparametersandignorethose
parametersyoudon’tneed.Onebigdrawbacktothismethod,
however,isthatitresultsincomplexcodethatcanbeextremely
difficulttomanage.
Callingproceduresmultipletimeswithasinglerowofdataeachtime.
Thismethodisprobablythesimplest,resultingincodethatisvery
easytocreateandmanage.Thedownsidetothismethodisthat
queryingandmanipulatingpotentiallytensofthousandsofrowsof
dataormore,onerowatatime,canresultinabigperformance
penalty.
Atable-valuedparameterallowsyoutopassrowsofdatatoyourT-SQLstatementor
SPsandUDFsintabularformat.Tocreateatable-valuedparameter,youmustfirstcreate
atabletypethatdefinesyourtablestructure,asshowninListing5-8.
Listing5-8.CreatingaTableType
CREATETYPEHumanResources.LastNameTableType
ASTABLE(LastNamenvarchar(50)NOTNULLPRIMARYKEY);
GO
TheCREATETYPEstatementinListing5-8createsasimpletabletypethat
representsatablewithasinglecolumnnamedLastName,whichalsoservesasthe
primarykeyforthetable.Tousetable-valuedparameters,youmustdeclareyourSPwith
parametersofthetabletype.TheSPinListing5-9acceptsasingletable-valuedparameter
oftheHumanResources.LastNameTableTypetypefromListing5-8.Itthenuses
therowsinthetable-valuedparameterinaninnerjointorestricttherowsreturnedbythe
SP.
Listing5-9.SimpleProcedureAcceptingaTable-ValuedParameter
CREATEPROCEDUREHumanResources.GetEmployees
(@LastNameTableHumanResources.LastNameTableType
READONLY)
AS
BEGIN
SELECT
p.LastName,
p.FirstName,
p.MiddleName,
e.NationalIDNumber,
e.Gender,
e.HireDate
FROMHumanResources.Employeee
INNERJOINPerson.Personp
ONe.BusinessEntityID=p.BusinessEntityID
INNERJOIN@LastNameTablelnt
ONp.LastName=lnt.LastName
ORDERBY
p.LastName,
p.FirstName,
p.MiddleName;
END;
GO
TheCREATEPROCEDUREstatementinListing5-9declaresasingletable-valued
parameter,@LastNameTable,oftheHumanResources.LastNameTableType
createdinListing5-8:
CREATEPROCEDUREHumanResources.GetEmployees
(@LastNameTableHumanResources.LastNameTableTypeREADONLY)
Thetable-valuedparameterisdeclaredREADONLY,whichismandatory.Although
youcanqueryandjointotherowsinatable-valuedparameterjustlikeatablevariable,
youcan’tmanipulatetherowsintable-valuedparameterswithINSERT,UPDATE,
DELETE,orMERGEstatements.
TheHumanResources.GetEmployeesprocedureperformsasimplequeryto
retrievethenames,nationalIDnumber,gender,andhiredateforallemployeeswhoselast
namesmatchanyofthelastnamespassedintotheSPviathe@LastNameTabletable-
valuedparameter.AsyoucanseeinListing5-9,theSELECTqueryperformsaninnerjoin
againstthetable-valuedparametertorestricttherowsreturned:
SELECT
p.LastName,
p.FirstName,
p.MiddleName,
e.NationalIDNumber,
e.Gender,
e.HireDate
FROMHumanResources.Employeee
INNERJOINPerson.Personp
ONe.BusinessEntitylD=p.BusinessEntitylD
INNERJOIN@LastNameTablelnt
ONp.LastName=Int.LastName
ORDERBY
p.LastName,
p.FirstName,
p.MiddleName;
Tocallaprocedurewithatable-valuedparameter,likethe
HumanResources.GetEmployeesSPinListing5-9,youneedtodeclareavariable
ofthesametypeasthetable-valuedparameter.Thenyoupopulatethevariablewithrows
ofdataandpassthevariableasaparametertotheprocedure.Listing5-10demonstrates
howtocalltheHumanResources.GetEmployeesSPwithatable-valuedparameter.
TheresultsareshowninFigure5-7.
Listing5-10.CallingaProcedurewithaTable-valuedParameter
DECLARE@LastNameListHumanResources.LastNameTableType;
INSERTINTO@LastNameList
(LastName)
VALUES
(N'Walters'),
(N'Anderson'),
(N'Chen'),
(N'Rettig'),
(N'Lugo'),
(N'Zwilling'),
(N'Johnson');
EXECUTEHumanResources.GetEmployees@LastNameList;
Figure5-7.EmployeesreturnedbytheSPcallinListing5-10
Inadditiontobeingread-only,thefollowingadditionalrestrictionsapplytotable-
valuedparameters:
Aswithtablevariables,youcan’tuseatable-valuedparameterasthe
targetofanINSERTEXECorSELECTINTOassignment
statement.
Table-valuedparametersarescopedjustlikeotherparametersand
localvariablesdeclaredinaprocedureorfunction.Theyaren’tvisible
outsideoftheprocedureinwhichthey’redeclared.
SQLServerdoesn’tmaintaincolumn-levelstatisticsfortable-valued
parameters,whichcanaffectperformanceifyou’repassinglarge
numbersofrowsofdataviatable-valuedparameters.
Youcanalsopasstable-valuedparameterstoSPsfromADO.NETclients,asdiscussed
inChapter16.
TemporaryStoredProcedures
InadditiontonormalSPs,T-SQLprovideswhatareknownastemporarySPs.Temporary
SPsarecreatedjustlikeanyotherSPs;theonlydifferenceisthatthenamemustbegin
withanumbersign(#)foralocaltemporarySPandtwonumbersigns(##)foraglobal
temporarySP.AthirdpossibilityistocreateatemporarySPinthetempdbdatabase.The
scopeofanythingcreatedinthetempdbdatabaseisuntiltheinstanceisrestarted,
becausetempdbisre-createdeachtimeaninstanceisrestarted.Itisn’tpossibletocreate
atemporarynativelycompiledSP.TemporarySPsareonlyusedintraditionalT-SQL
interpretiveSPs.
WhereasanormalSPremainsinthedatabaseandschemaitwascreatedinuntilit’s
explicitlydroppedviatheDROPPROCEDUREstatement,temporarySPsaredropped
automatically.AlocaltemporarySPisvisibleonlytothecurrentsessionandisdropped
whenthecurrentsessionends.AglobaltemporarySPisvisibletoallconnectionsandis
automaticallydroppedwhenthelastsessionusingitends.
Normallyyouwon’tusetemporarySPs;they’reusuallyusedforspecializedsolutions,
likedatabasedrivers.OpenDatabaseConnectivity(ODBC)drivers,forinstance,use
temporarySPstoimplementSQLServerconnectivityfunctions.TemporarySPsareuseful
whenyouwanttheadvantagesofusingSPs,suchasexecutionplanreuseandimproved
errorhandling,withtheadvantagesofadhoccode.However,temporarySPsbringsome
othereffects,aswell.They’reoftennotdestroyeduntiltheconnectionisclosedor
explicitlydropped.Thismaycausetheprocedurestofilluptempdbovertimeandcause
queriestofail.CreatingtemporarySPsinatransactionmayalsocauseblockingproblems,
becausetheSPcreationcausesdata-pagelockinginseveralsystemtablesforthe
transactionduration.
RecompilationandCaching
SQLServerhasseveralfeaturesthatworkbehindthescenestooptimizeSPperformance.
ThefirsttimeyouexecuteanSP,SQLServercompilesitintoaqueryplan,whichitthen
caches.Thiscompilationprocessinvokesacertainamountofoverhead,whichcanbe
substantialforproceduresthatarecomplexorthatarerunveryoften.SQLServerusesa
complexcachingmechanismtostoreandreusequeryplansonsubsequentcallstothe
sameSP,inanefforttominimizetheimpactofSPcompilationoverhead.Thissection
talksaboutmanagingquery-planrecompilationandcachedquery-planreuse.
StoredProcedureStatistics
SQLServer2014providesDMVsanddynamicmanagementfunctions(DMFs)toexpose
SPquery-planusageandcachinginformationthatcanbeusefulforperformancetuning
andgeneraltroubleshooting.Listing5-11isaprocedurethatretrievesanddisplaysseveral
relevantSPstatisticsfromafewdifferentDMVsandDMFs.
Listing5-11.ProceduretoRetrieveSPStatisticswithDMVsandDMFs
CREATEPROCEDUREdbo.GetProcStats(@ordervarchar(100)='use')
AS
BEGIN
WITHGetQueryStats
(
plan_handle,
total_elapsed_time,
total_logical_reads,
total_logical_writes,
total_physical_reads
)
AS
(
SELECT
qs.plan_handle,
SUM(qs.total_elapsed_time)AStotal_elapsed_time,
SUM(qs.total_logical_reads)AStotal_logical_reads,
SUM(qs.total_logical_writes)AStotal_logical_writes,
SUM(qs.total_physical_reads)AStotal_physical_reads
FROMsys.dm_exec_query_statsqs
GROUPBYqs.plan_handle
)
SELECT
DB_NAME(st.dbid)ASdatabase_name,
OBJECT_SCHEMA_NAME(st.objectid,st.dbid)AS
schema_name,
OBJECT_NAME(st.objectid,st.dbid)ASproc_name,
SUM(cp.usecounts)ASuse_counts,
SUM(cp.size_in_bytes)ASsize_in_bytes,
SUM(qs.total_elapsed_time)AStotal_elapsed_time,
CAST
(
SUM(qs.total_elapsed_time)ASdecimal(38,4)
)/SUM(cp.usecounts)ASavg_elapsed_time_per_use,
SUM(qs.total_logical_reads)AStotal_logical_reads,
CAST
(
SUM(qs.total_logical_reads)ASdecimal(38,4)
)/SUM(cp.usecounts)ASavg_logical_reads_per_use,
SUM(qs.total_logical_writes)AStotal_logical_writes,
CAST
(
SUM(qs.total_logical_writes)ASdecimal(38,4)
)/SUM(cp.usecounts)ASavg_logical_writes_per_use,
SUM(qs.total_physical_reads)AStotal_physical_reads,
CAST
(
SUM(qs.total_physical_reads)ASdecimal(38,4)
)/SUM(cp.usecounts)ASavg_physical_reads_per_use,
st.text
FROMsys.dm_exec_cached_planscp
CROSSAPPLYsys.dm_exec_sql_text(cp.plan_handle)st
INNERJOINGetQueryStatsqs
ONcp.plan_handle=qs.plan_handle
INNERJOINsys.proceduresp
ONst.objectid=p.object_id
WHEREp.typeIN('P','PC')
GROUPBYst.dbid,st.objectid,st.text
ORDERBY
CASE@order
WHEN'name'THENOBJECT_NAME(st.objectid)
WHEN'size'THENSUM(cp.size_in_bytes)
WHEN'read'THENSUM(qs.total_logical_reads)
WHEN'write'THENSUM(qs.total_logical_writes)
ELSESUM(cp.usecounts)
ENDDESC;
END;
GO
Thisprocedureusesthesys.dm_exec_cached_plansand
sys.dm_exec_query_statsDMVsinconjunctionwiththe
sys.dmexecsqltextDMFtoretrieverelevantSPexecutioninformation.The
sys.procedurescatalogviewisusedtolimittheresultstoonlySPs(typeP).
AggregationisrequiredonmostofthestatisticsbecausetheDMVsandDMFscanreturn
multiplerows,eachrepresentingindividualstatementsinSPs.The
dbo.GetProcStatsprocedureacceptsasingleparameterthatdetermineshowthe
resultrowsaresorted.Settingthe@orderparametertosizesortstheresultsin
descendingorderbythesizeinbytescolumn,whereasreadsortsindescending
orderbythetotallogicalreadscolumn.Otherpossiblevaluesincludenameand
write—allothervaluessortbythedefaultusecountscolumnindescendingorder.
TipThisSPusesafewusefulsystemfunctions:DB_NAMEacceptstheIDofadatabase
andreturnsthedatabasename,OBDECT_SCHEMA_NAMEacceptstheIDofanobjectand
adatabaseIDandreturnsthenameoftheschemainwhichtheobjectresides,and
OBJECT_NAMEacceptstheobjectIDandreturnsthenameoftheobjectitself.Theseare
handyfunctions,andyoucanretrievethesameinformationviaSQLServer’scatalog
views.
Listing5-12demonstrateshowtocallthisSP.SampleresultsareshowninFigure5-8.
Listing5-12.RetrievingSPStatistics
EXECdbo.GetProcStats@order='use';
GO
Figure5-8.PartialresultsofcallingtheGetProcStatsprocedure
SQLServerDMVsandDMFscanbeusedthiswaytoanswerseveralquestionsabout
yourSPs,includingthefollowing:
WhichSPsareexecutedthemost?
WhichSPstakethelongesttoexecute?
WhichSPsperformthemostlogicalreadsandwrites?
Theanswerstothesetypesofquestionscanhelpyouquicklylocateperformance
bottlenecksandfocusyourperformance-tuningeffortswherethey’remostneeded.
Chapter20discussesperformancetuningindetail.
ParameterSniffing
SQLServerusesamethodknownasparametersniffingtofurtheroptimizeSPcalls.
DuringcompilationorrecompilationofanSP,SQLServercapturestheparametersused
andpassesthevaluesalongtotheoptimizer.Theoptimizerthengeneratesandcachesa
queryplanoptimizedforthoseparameters.Thiscanactuallycauseproblemsinsome
cases—forexample,whenyourSPcanreturnwildlyvaryingnumbersofrowsbasedon
theparameterspassedin.Listing5-13showsasimpleSPthatretrievesallproductsfrom
theProduction.ProducttablewithaNamelikethe@Prefixparameterpassed
intotheSP.
Listing5-13.SimpleProceduretoDemonstrateParameterSniffing
CREATEPROCEDUREProduction.GetProductsByName
@PrefixNVARCHAR(100)
AS
BEGIN
SELECT
p.Name,
p.ProductID
FROMProduction.Productp
WHEREp.NameLIKE@Prefix;
END;
GO
CallingthisSPwiththe@Prefixparametersetto%resultsinaqueryplanoptimized
toreturn504rowsofdatawithanonclusteredindexscan,asshowninFigure5-9.
Figure5-9.Queryplanoptimizedtoreturn504rows
IfyouruntheProduction.GetProductsByNameprocedureasecondtimewith
the@PrefixparametersettoM%,thequeryplanshowsthattheplanisstilloptimizedto
return504estimatedrows,althoughonly102rowsarereturnedbytheSP.Figure5-10
showsthequeryplanforthesecondprocedurecall.
Figure5-10.Queryplanoptimizedforthewrongnumberofrows
Incaseswhereyouexpectwidelyvaryingnumbersofrowstobereturnedbyyour
SPs,youcanoverrideparametersniffingonaper-procedurebasis.Overridingparameter
sniffingissimple—justdeclarealocalvariableinyourSP,assigntheparametervalueto
thevariable,andusethevariableinplaceoftheparameterinyourquery.Whenyou
overrideparametersniffing,SQLServerusesthesourcetabledata-distributionstatisticsto
estimatethenumberofrowstoreturn.Thetheoryisthattheestimatewillbebetterfora
widervarietyofpossibleparametervalues.Inthiscase,theestimatewillstillbe
considerablyofffortheextremecaseofthe504rowsreturnedinthisexample,butitwill
bemuchcloserandwillthereforegeneratebetterqueryplansforotherpossibleparameter
values.Listing5-14alterstheSPinListing5-13tooverrideparametersniffing.Figure5-
11showstheresultsofcallingtheupdatedSPwitha@PrefixparameterofM%
Figure5-11.ResultsoftheSPwithparametersniffingoverridden
.
Listing5-14.OverridingParameterSniffinginanSP
ALTERPROCEDUREProduction.GetProductsByName
@PrefixNVARCHAR(100)
AS
BEGIN
DECLARE@PrefixVarNVARCHAR(100)=@Prefix;
SELECT
p.Name,
p.ProductID
FROMProduction.Productp
WHEREp.NameLIKE@PrefixVar;
END;
GO
Withparametersniffingoverridden,thequeryplanfortheSPinListing5-14usesthe
sameestimatednumberofrows,inthiscase27.0914,nomatterwhatvalueyoupassinthe
@Prefixparameter.Thisresultsinaqueryplanthatusesanonclusteredindexseek—not
anindexscan—whichisamuchbetterqueryplanforthevastmajorityofpossible
parametervaluesforthisparticularSP.
Recompilation
Asdiscussedpreviouslyinthischapter,SQLServeroptimizesperformancebycaching
compiledqueryplanswhileitcan.TherecompilationofSPsisperformedonindividual
statementsinSPsratherthanentireSPstoavoidunnecessaryrecompilesandconsuming
CPUresources.
ThereareseveralreasonstheSPsarerecompiled:
Iftheobjectismodifiedbetweenexecutions,eachstatementintheSP
thatreferencesthisobjectisrecompiled.
Ifsufficientdatahaschangedinthetablethatisbeingreferencedby
theSPsincetheoriginalqueryplanwasgenerated,theSPrecompiles
theplan.
UseofatemporarytableintheSPmaycausetheSPtoberecompiled
everytimetheprocedureisexecuted.
IftheSPwascreatedwiththerecompileoption,thismaycausetheSP
toberecompiledeverytimetheprocedureisexecuted.
Cachingthequeryplaneliminatestheoverheadassociatedwithrecompilingyour
queryonsubsequentruns,butoccasionallythisfeaturecancauseperformancetosuffer.
WhenyouexpectyourSPtoreturnwidelyvaryingnumbersofrowsintheresultsetwith
eachcall,thecachedquery-executionplanisonlyoptimizedforthefirstcall.Itisn’t
optimizedforsubsequentexecutions.Incaseslikethis,youmaydecidetoforce
recompilationwitheachcall.ConsiderListing5-15,whichisanSPthatreturnsorder
headerinformationforagivensalesperson.
Listing5-15.SPtoRetrieveOrdersbySalesperson
CREATEPROCEDURESales.GetSalesBySalesPerson(@SalesPersonIdint)
AS
BEGIN
SELECT
soh.SalesOrderID,
soh.OrderDate,
soh.TotalDue
FROMSales.SalesOrderHeadersoh
WHEREsoh.SalesPersonID=@SalesPersonId;
END;
GO
TherehappenstobeanonclusteredindexontheSalesPersonIDcolumnofthe
Sales.SalesOrderHeadertable,whichyoumightexpecttobeconsideredbythe
optimizer.However,whenthisSPisexecutedwiththeEXECUTEstatementinListing5-
16,theoptimizerignoresthenonclusteredindexandinsteadperformsaclusteredindex
scan,asshowninFigure5-12.
Listing5-16.RetrievingSalesforSalesperson277
EXECUTESales.GetSalesBySalesPerson277;
Figure5-12.TheSPignoresthenonclusteredindex
TheSPignoresthenonclusteredindexontheSalesPersonIDcolumnbecause473
matchingrowsarereturnedbythequeryintheprocedure.SQLServerusesameasure
calledselectivity,theratioofqualifyingrowstothetotalnumberofrowsinthetable,asa
factorindeterminingwhichindex,ifany,touse.InListing5-16,theparametervalue277
representslowselectivity,meaningalargenumberofrowsarereturnedrelativetothe
numberofrowsinthetable.SQLServerfavorsindexesforhighlyselectivequeries,tothe
pointofcompletelyignoringindexeswhenthequeryhaslowselectivity.
IfyousubsequentlycalltheSPwiththe@SalesPersonIdparametersetto285,
whichrepresentsahighlyselectivevalue(only16rowsarereturned),query-plancaching
forcesthesameclusteredindexscan,eventhoughit’ssuboptimalforahighlyselective
query.Fortunately,SQLServerprovidesoptionsthatallowyoutoforcerecompilationat
theSPlevelorthestatementlevel.YoucanforcearecompilationinanSPcallbyadding
theWITHRECOMPILEoptiontotheEXECUTEstatement,asshowninListing5-17.
Listing5-17.ExecutinganSPwithRecompilation
EXECUTESales.GetSalesBySalesPerson285WITHRECOMPILE;
TheWITHRECOMPILEoptionoftheEXECUTEstatementforcesarecompilationof
theSPwhenyouexecuteit.Thisoptionisusefulifyourdatahassignificantlychanged
sincethelastSPrecompilationoriftheparametervalueyou’repassingtotheprocedure
representsanatypicalvalue.ThequeryplanforthisSPcallwiththehighlyselectivevalue
285isshowninFigure5-13.
Figure5-13.SPqueryplanoptimizedforahighlyselectiveparametervalue
Youcanalsousethesp_recompilesystemSPtoforceanSPtorecompilethenext
timeit’srun.
IfyouexpectthatthevaluessubmittedtoyourSPwillvaryalot,andthatthe“one
executionplanforallparameters”modelwillcausepoorperformance,youcanspecify
statement-levelrecompilationbyaddingOPTION(RECOMPILE)toyourstatements.
Thestatement-levelrecompilationalsoconsidersthevaluesoflocalvariablesduringthe
recompilationprocess.Listing5-18alterstheSPcreatedinListing5-16toaddstatement-
levelrecompilationtotheSELECTquery.
Listing5-18.AddingStatement-LevelRecompilationtotheSP
ALTERPROCEDURESales.GetSalesBySalesPerson(@SalesPersonIdint)
AS
BEGIN
SELECT
soh.SalesOrderID,
soh.OrderDate,
soh.TotalDue
FROMSales.SalesOrderHeadersoh
WHEREsoh.SalesPersonID=@SalesPersonId
OPTION(RECOMPILE);
END;
GO
Asanalternative,youcanspecifyprocedure-levelrecompilationbyaddingtheWITH
RECOMPILEoptiontoyourCREATEPROCEDUREstatement.Thisoptionisusefulif
youdon’twantSQLServertocachethequeryplanfortheSP.Withthisoptioninplace,
SQLServerrecompilestheentireSPeverytimeyourunit.Thiscanbeusefulfor
procedurescontainingseveralstatementsthatneedtoberecompiledoften.Keepinmind,
however,thatthisoptionislessefficientthanastatement-levelrecompilebecausethe
entireSPneedstoberecompiled.Becauseit’slessefficientthanstatement-level
recompilation,thisoptionshouldbeusedwithcare.
Toexpandonthe“StoredProcedureStatistics”sectionofthischapter,SQLServer
2014providesdetailsaboutthelasttimetheSPorthestatementswererecompiledwith
DMVs.Thiscanhelpyouidentifythemost-recompiledSPsandallowyoutofocuson
resolvingtherecompilationissues.Listing5-19isaprocedurethatreturnstheSPsthat
havebeenrecompiled.
Listing5-19.SPtoReturnaListofStoredProceduresThatHaveBeenRecompiled
CREATEPROCEDUREdbo.GetRecompiledProcs
AS
BEGIN
SELECT
sql_text.text,
stats.sql_handle,
stats.plan_generation_num,
stats.creation_time,
stats.execution_count,
sql_text.dbid,
sql_text.objectid
FROMsys.dm_exec_query_statsstats
Crossapplysys.dm_exec_sql_text(sql_handle)as
sql_text
WHEREstats.plan_generation_num>1
andsql_text.objectidisnotnull--Filteradhoc
queries
ORDERBYstats.plan_generation_numdesc
END;
GO
Thisprocedureusesthesys.dm_exec_query_statsDMVwiththe
sys.dm_exec_sql_textDMFtoretrieverelevantSPexecutioninformation.The
queryreturnsonlytheSPsthathavebeenrecompiledbyfiltering
plan_generation_num,andtheadhocqueriesarefilteredoutbyremoving
object_idswithnullvalues.
Listing5-20demonstrateshowtocallthisSP,andpartialresultsareshowninFigure
5-14.
Listing5-20.RetrievingSPStatistics
EXECdbo.GetRecompiledProcs;
GO
Figure5-14.PartialresultsfortheSPdbo.GetRecompiledProcs
Summary
SPsarepowerfultoolsforSQLServerdevelopment.Theyprovideaflexiblemethodof
extendingthepowerofSQLServerbyallowingyoutocreatecustomserver-side
subroutines.AlthoughsomeoftheperformanceadvantagesprovidedbySPsinolder
releasesofSQLServeraren’taspronouncedinSQLServer2014,theabilityto
modularizeserver-sidecode,administeryourT-SQLcodebaseinasinglelocation,
provideadditionalsecurity,andeasefront-endprogrammingdevelopmentstillmakeSPs
usefuldevelopmenttoolsinanyT-SQLdeveloper’stoolkit.Withthenewlyadded
functionalityofcompilingSPsintomachinecode,themannerinwhichyouhave
traditionallyarchitectedasolutionshouldcomeintoquestion.Pullingyourbusinesslogic
intoyourdatabasemaymakesenseinsomeusecases,butaswithanydecisionin
software,italwaysdepends.
ThischapterintroducedkeyaspectsofSPdevelopment:creatingnativelycompiled
SPs;management;passingscalarparameterstoSPs;andretrievingresultsets,output
parameters,andreturnvaluesfromSPs.Youalsosawsomeadvancedtopics,includingthe
useoftemporarytablestopasstabulardatabetweenSPs,writingrecursiveSPs,andSQL
Server2014’stable-valuedparameters.
Finally,thechapterendedwithadiscussionofSPoptimizations,includingSPcaching,
accessingSPcachestatisticsthroughDMVsandDMFs,parametersniffing,and
recompilationoptions,includingstatement-levelandprocedure-levelrecompilation.
Theexamplesprovidedinthischapteraredesignedtodemonstrateseveralaspectsof
SPfunctionalityinSQLServer2014.ThenextchaptergoesintothenewlyavailableIn-
MemoryOLTPfeaturesavailableinSQLServer2014.
EXERCISES
1. [True/False]TheSPRETURNstatementcanreturnascalarvalueof
anydatatype.
2. TherecursionlevelforSPsis32levels,asdemonstratedbythe
followingcodesample,whicherrorsoutafterreachingthe
maximumdepthofrecursion:
CREATEPROCEDUREdbo.FirstProc(@iint)
AS
BEGIN
PRINT@i;
SET@i+=1;
EXECdbo.FirstProc@i;END;GO
EXECdbo.FirstProc1;
Writeasecondprocedureandmodifythisonetoprovethatthe
recursionlimitappliestotwoSPsthatcalleachotherrecursively.
3. [Chooseone]Table-valuedparametersmustbedeclaredwithwhich
ofthefollowingmodifiers:
READWRITE
WRITEONLY
RECOMPILE
READONLY
4. Whencreatinganativelycompiledstoredprocedure,whichofthe
followingoptionsarerequired?[Chooseallthatapply]
a. SCHEMABINDING
b. WITHNATIVE_COMPILATION
c. EXECUTEAS
d. BEGINATOMIC
CHAPTER6
In-MemoryProgramming
SQLServer2014introducesnewIn-Memoryfeaturesthatareagame-changerinhowyou
considerthedataandphysicalarchitectureofdatabasesolutions.Themannerinwhich
dataisaccessed,theindexesusedforin-memorytables,andthemethodsusedfor
concurrencymakethisasignificantnewfeatureofthedatabasesoftwareinSQLServer
2014.In-MemoryOLTPisaperformanceenhancementthatallowsyoutostoredatain
memoryusingacompletelynewarchitecture.Inadditiontostoringdatainmemory,
databaseobjectsarecompiledintoanativeDLLinthedatabase.
ThisreleaseofSQLServerhasmadeinvestmentsinthreedifferentIn-Memory
technologies:In-MemoryOLTP,In-Memorydatawarehousing(DW),andtheSSDBuffer
PoolExtension.ThischaptercoverstheIn-MemoryOLTPprogrammingfeatures;In-
MemoryDWandtheBufferPoolExtensionaren’tapplicabletothesubjectmatterinthis
book.
In-MemorysolutionsprovideasignificantperformanceenhancementtargetedatOLTP
workloads.In-MemoryOLTPspecificallytargetsthehighconcurrency,processing,and
retrievalcontentiontypicalinOLTPtransactionalworkloads.Thesearethefirstversions
ofsuchfeaturesforSQLServer,andthereforetheyhavenumerouslimitations,whichare
discussedinthischapter.Regardlessofthelimitations,someusecasesseeasmuchasa
30xperformanceimprovement.SuchperformanceimprovementsmakeIn-MemoryOLTP
compellingforuseinyourenvironment.
In-MemoryOLTPisavailableinexistingSQLServer2014installations;no
specializedsoftwareisrequired.Additionally,theuseofcommodityhardwareisabenefit
ofSQLServer’simplementationofthisfeatureoverothervendorsthatmayrequire
expensivehardwareorspecializedversionsoftheirsoftware.
TheDriversforIn-MemoryTechnology
Hardwaretrends,largerdatasets,andthespeedatwhichOLTPdataneedstobecome
availableareallmajordriversforthedevelopmentofin-memorytechnology.This
technologyhasbeenintheworksforthepastseveralyears,asMicrosofthassoughtto
addressthesetechnologicaltrends.
HardwareTrends
CPU,memory,diskspeeds,andnetworkconnectionshavecontinuallyincreasedinspeed
andcapacitysincetheinventionofcomputers.However,we’reatthepointthattraditional
approachestomakingcomputersrunfasterarechangingduetotheeconomicsofthecost
ofmemoryversusthespeedofCPUprocessing.In1965,GordonEMoore“madethe
observationthat,overthehistoryofcomputinghardware,thenumberoftransistorsina
denseintegratedcircuitdoublesapproximatelyeverytwoyears.”1Sincethen,this
statementhasbeenknownasMoore’sLaw.Figure6-1showsagraphoftheincreasein
thenumberoftransistorsonasinglecircuit.
Figure6-1.Moore’sLawtransistorcounts
Manufacturersofmemory,pixelsonascreen,networkbandwidth,CPUarchitecture,
andsoonhaveallusedMoore’sLawasaguideforlong-termplanning.It’shardto
believe,buttoday,increasingtheamountofpowertoatransistor,forfasterCPUclock
speed,nolongermakeseconomicsense.Astheamountofpowerbeingsenttoatransistor
isincreased,thetransistorheatsuptothepointthatthephysicalcomponentsbegintomelt
andmalfunction.We’veessentiallyhitapracticallimitationontheclockspeedforan
individualchip,becauseitisn’tpossibletoeffectivelycontrolthetemperatureofaCPU.
ThebestwaytocontinuetoincreasethepowerofaCPUwiththesameclockspeedisvia
additionalcorespersocket.
InparalleltothelimitationsofCPUs,thecostofmemoryhascontinuedtodecline
significantlyovertime.It’scommonforserversandcommodityhardwaretocome
equippedwithmorememorythanmultimillion-dollarservershadavailable20yearsago.
Table6-1showsthehistoricalpriceof1gigabyteofmemory.
Table6-1.PriceofRAMovertime2
HistoricRAMPrices
Year AverageCostperGigabyte
1980 $6,635,520.00
1985 $901,120.00
1990 $108,544.00
1995 $31,641.60
2000 $1,149.95
2005 $189.44
2010 $12.50
2014 $9.34
Inordertomakeeffectiveuseofadditionalcoresandtheincreaseinmemoryavailable
withmodernhardware,softwarehastobewrittentotakeadvantageofthesehardware
trends.TheSQLServer2014In-Memoryfeaturesaretheresultofthesetrendsand
customerdemandforadditionalcapacityonOLTPdatabases.
GettingStartedwithIn-MemoryObjects
SQLServer2014In-MemoryfeaturesareofferedinEnterprise,Developer,andEvaluation
(64-bitonly)Editionsofthesoftware.Thesefeatureswerepreviouslyavailableonlyto
corporationsthathadaverylargebudgettospendonspecializedsoftwareandhardware.
GiventhewayMicrosofthasdeployedthesefeaturesinexistingeditions,youmaybeable
tousethemanexistinginstallationofyourOLTPdatabasesystem.
Thein-memoryobjectsrequireaFILESTREAMdatafile(container)tobecreated
usingamemory-optimizeddatafilegroup.Fromhereon,thischapterusestheterm
containerratherthandatafile;it’smoreappropriatebecauseadatafileiscreatedondisk
atthetimedataiswrittentothenewmemory-optimizedtables.Severalcheckpointfiles
arecreatedinthememory-optimizeddatafilegroupforthepurposesofkeepingtrackof
changestodataintheFILESTREAMcontainerfile.Thedataformemory-optimizedtables
isstoredinacombinationofthetransactionlogandcheckpointfilesuntilabackground
threadcalledanofflinecheckpointappendstheinformationtodataanddeltafiles.Inthe
eventofaservercrashoravailabilitygroupfailover,alldurabletabledataisrecovered
fromacombinationofthedata,delta,transactionlog,andcheckpointfiles.Allnondurable
tablesarere-created,becausetheschemaisdurable,butthedataislost.Thedifferences
betweendurableandnon-durabletables,advantages,disadvantages,andsomeusecases
areexplainedfurtherinthesection“Step3,”laterinthischapter.
Youcanalteranyexistingdatabaseornewdatabasetoaccommodatein-memorydata
files(containers)byaddingthenewdataandfilegroupstructures.Severalconsiderations
shouldbetakenintoaccountpriortodoingso.Thefollowingsectionscoverthesteps
listedinthecodeformatandSQLServerManagementStudiotocreatethesestructures.
Step1:AddaNewMemory-OptimizedData
FILEGROUP
Typically,beforeyoucanbegintousingFILESTREAMinSQLServer,youmustenable
FILESTREAMontheinstanceoftheSQLServerDatabaseEngine.Withmemory-
optimizedfilegroups,youdon’tneedtoenableFILESTREAMbecausethemappingtoit’s
handledbytheIn-MemoryOLTPengine.
Thememory-optimizeddatafilegroupshouldbecreatedonasolidstatedrive(SSD)
orfastserialattachedSCSI(SAS)drive.Memory-optimizedtableshavedifferentaccess
patternsthantraditionaldisk-basedtablesandrequirethefasterdisksubsystemstofully
realizethespeedbenefitofthisfilegroup.Listing6-1,addsanewmemory-optimized
filegrouptoourexistingAdventureWorks2014database.Thissyntaxcanbeusedagainst
anyexisting2014databaseontheproperSQLServereditionofthesoftware.
Listing6-1.AddingaNewFilegroup
IFNOTEXISTS
(SELECT*FROMAdventureWorks2014.sys.data_spacesWHERE
TYPE='FX')
ALTERDATABASEAdventureWorks2014
ADDFILEGROUP[AdventureWorks2014_mem]CONTAINS
MEMORY_OPTIMIZED_DATA
GO
Thisaddsanemptymemory-optimizeddatafilegrouptowhichyou’lladdcontainers
inthenextstep.ThekeywordsinthesyntaxareCONTAINS
MEMORY_OPTIMIZED_DATA,tocreateasamemory-optimizeddatafilegroup.Youcan
createmultiplecontainersbutonlyonememory-optimizeddatafilegroup.Adding
additionalmemory-optimizeddatafilegroupsresultsinthefollowingerror:
Msg10797,Level15,State2,Line2
OnlyoneMEMORY_OPTIMIZED_DATAfilegroupisallowedper
database.
InListing6-1,weaddedanewmemory-optimizedfilegroupusingT-SQLcode.Inthe
followingexample,wewilldothesameusingSQLServerManagementStudio.Following
arethestepstoaccomplishaddingthefilegroupviaManagementStudio(seeFigure6-2):
1. Right-clickthedatabasetowhichyouwanttoaddthenew
filegroup,andselectProperties.
2. SelecttheFilegroupsoption,andtypeinthenameofthememory-
optimizeddatafilegroupyouwishtoadd.
3. ClicktheAddFilegroupbutton.
Figure6-2.Addinganewmemory-optimizeddatafilegroup
NoteMemory-optimizeddatafilegroupscanonlyberemovedbydroppingthe
database.Therefore,youshouldcarefulconsiderthedecisiontomoveforwardwiththis
architecture.
Step2:AddaNewMemory-OptimizedContainer
Instep-2wewilladdanewmemory-optimizedcontainer.Listing6-2showsanexample
ofhowthisisaccomplishedusingT-SQLcode.Thiscodecanbeusedagainstany
databasethathasamemory-optimizedfilegroup.
Listing6-2.AddingaNewContainertotheDatabase
IFNOTEXISTS
(SELECT*FROMAdventureWorks2014.sys.data_spacesds
JOINAdventureWorks2014.sys.database_filesdfON
ds.data_space_id=df.data_space_id
WHEREds.type='FX')
ALTERDATABASEAdventureWorks2014
ADDFILE(name='AdventureWorks2014_mem',
filename='C:\SQLData\AdventureWorks2014_mem')
TOFILEGROUP[AdventureWorks2014_mem]
GO
InListing6-2,weaddedanewmemory-optimizedcontainertoourdatabaseusingT-
SQLcode.InthefollowingstepswewilldothesameusingManagementStudio.Inorder
toaccomplishthis,followthestepsoutlinedbelow(seeFigure6-3):
1. Right-clickthedatabasetowhichyouwanttoaddthenew
container,andselectProperties.
2. SelecttheFilesoption,andtypeinthenameofthefileyouwishto
add.
3. SelectFILESTREAMDatafromtheFileTypelist,andclickthe
Addbutton.
Figure6-3.Addinganewfilestreamcontainerfiletoamemory-optimizedfilegroup
ItisabestpracticetoadjusttheAutogrowth/Maxsizeofafiegroup;thisoptionisto
therightofthe“Filegroup”columninFigure6-4.Foramemory-optimizedfilegroup,you
willnotbeabletoadjustthisoptionwhencreatingthefielgroupthroughManagement
Studio.Thisfilegrouplivesinmemory;therefore,thepreviouspracticeofalteringthis
optionnolongerapplies.LeavetheAutogrowth/MaxsizeoptionsettoUnlimited.It’sa
limitationofthecurrentversionthatyoucan’tspecifyaMAXSIZEforthespecific
containeryou’recreating.
Younowhaveacontainerinthememory-optimizeddatafilegroupthatyoupreviously
addedtothedatabase.Durabletablessavetheirdatatodiskinthecontainersyoujust
defined;therefore,it’srecommendedthatyoucreatemultiplecontainersacrossmultiple
disks,ifthey’reavailabletoyou.SSDswon’tnecessarilyhelpperformance,becausedata
isaccessedinasequentialmannerandnotinarandom-accesspattern.Theonly
requirementisthatyouhaveperformantdiskssothedatacanbeaccessedefficientlyfrom
disk.MultipledisksallowSQLServertorecoverdatainparallelintheeventofasystem
crashoravailabilitygroupfailover.Yourin-memorytableswon’tbecomeavailableuntil
SQLServerhasrecoveredthedataintomemory.
NoteDataanddeltafilepairscan’tbemovedtoothercontainersinthememory-
optimizedfilegroup.
Step3:CreateYourNewMemory-OptimizedTable
Step1andStep2laidoutthefoundationnecessarytoaddmemory-optimizedobjects.
Listing6-3createsatablethatinmemory.Theresultisacompiledtablewithdatathat
residesinmemory.
Listing6-3.CreatingaNewMemory-OptimizedTable
USEAdventureWorks2014;
GO
CREATESCHEMA[MOD]AUTHORIZATION[dbo];
GO
CREATETABLE[MOD].[Address]
(
AddressIDINTNOTNULLIDENTITY(1,1)
,AddressLine1NVARCHAR(120)COLLATE
Latin1_General_100_BIN2NOTNULL
,AddressLine2NVARCHAR(120)NULL
,CityNVARCHAR(60)COLLATELatin1_General_100_BIN2NOT
NULL
,StateProvinceIDINTNOTNULL
,PostalCodeNVARCHAR(30)COLLATELatin1_General_100_BIN2
NOTNULL
,rowguidUNIQUEIDENTIFIERNOTNULL
INDEX[AK_MODAddress_rowguid]NONCLUSTERED
CONSTRAINT[DF_MODAddress_rowguid]DEFAULT
(NEWID())
,ModifiedDateDATETIMENOTNULL
INDEX[IX_MODAddress_ModifiedDate]NONCLUSTERED
CONSTRAINT[DF_MODAddress_ModifiedDate]
DEFAULT(GETDATE())
,INDEX[IX_MODAddress_AddressLine1_
City_StateProvinceID_PostalCode]
NONCLUSTERED
([AddressLine1]ASC,[StateProvinceID]ASC,[PostalCode]
ASC)
,INDEX[IX_MODAddress_City]
([City]DESC)
,INDEX[IX_MODAddress_StateProvinceID]
NONCLUSTERED
([StateProvinceID]ASC)
,CONSTRAINTPK_MODAddress_Address_ID
PRIMARYKEYNONCLUSTEREDHASH
([AddressID])WITH(BUCKET_COUNT=30000)
)WITH(MEMORY_OPTIMIZED=ON,DURABILITY=SCHEMA_AND_DATA);
GO
NoteYoudon’tneedtospecifyafilegroupwhenyoucreateanin-memorytable.
You’relimitedtoasinglememory-optimizedfilegroup;therefore,SQLServerknowsthe
filegrouptowhichtoaddthistable.
Thesampletableusedforthismemory-optimizedtableexampleissimilartothe
AdventureWorks2014.Person.Addresstable,withseveraldifferences:
ThehintattheendoftheCREATETABLEstatementisextremely
important:
WITH(MEMORY_OPTIMIZED=ON,
DURABILITY=SCHEMA_AND_DATA);
TheoptionMEMORY_OPTIMIZED=ONtellsSQLServerthatthisisa
memory-optimizedtable.
TheDURABILITY=SCHEMA_AND_DATAoptiondefineswhether
thistablewillbedurable(datarecoverable)ornon-durable(schema-
onlyrecovery)afteraserverrestart.Ifthedurabilityoptionisn’t
specified,itdefaultstoSCHEMA_AND_DATA.
PRIMARYKEYisNONCLUSTERED,becausedataisn’tphysically
sorted:
,CONSTRAINTPK_MODAddress_Address_ID
PRIMARYKEYNONCLUSTEREDHASH
([AddressID])WITH(BUCKET_COUNT=30000)
TheNONCLUSTEREDhintisrequiredonaPRIMARYKEY
constraint,becauseSQLServerattemptstocreateitasaCLUSTERED
indexbydefault.BecauseCLUSTEREDindexesaren’tallowed,not
specifyingtheindextyperesultsinanerror.Additionally,youcan’t
addasorthintonthecolumnbeingusedinthisindex,becauseHASH
indexescan’tbedefinedinaspecificsortorder.
AllcharacterstringdatathatisusedinanindexmustuseBIN2
collation:
COLLATELatin1_General_100_BIN2
NoticethattheMOD.Addresstablepurposelydoesn’tdeclareBIN2
collationfortheAddressLine2column,becauseitisn’tusedinan
index.Figure6-8showstheeffectthatBIN2collationhasondatain
differentcollationtypes.
IfyoucomparetheMOD.AddresstabletoPerson.Address,you
seethatthecolumnSpatialLocationismissing.In-memory
tablesdon’tsupportLOBobjects.TheSpatialLocationcolumn
inPerson.AddressisdefinedasaGEOGRAPHYdatatype,which
isn’tsupportedformemory-optimizedtables.Ifyouwereconverting
thisdatatypetobeusedinamemory-optimizedtable,youwould
potentiallyneedtomakecodingchangestoaccommodatethelackof
thedatatype.
TheindextypeHASHwiththehintWITH
(BUCKET_COUNT=30000)isnew.Thisisdiscussedfurtherinthe
“In-MemoryOLTPTableIndexes”sectionofthischapter.
Listing6-3addedamemory-optimizedtableusingT-SQL.Wewillnowaddadda
memory-optimizedtableusingManagementStudio.Right-clicktheTablesfolder,and
selectNew Memory-OptimizedTable(seeFigure6-4).Anewquerywindowopens
withtheIn-MemoryTableCreationtemplatescriptavailable.
Figure6-4.Creatinganewmemory-optimizedtable
YounowhaveaverybasicworkingdatabaseandtableandcanbeginusingtheIn-
Memoryfeatures.Youcanaccessthetableanddifferentindexpropertiesusingasystem
view(seeListing6-4andFigure6-5)orManagementStudio(seeFigure6-6).
Listing6-4.SelectingTablePropertiesfromaSystemView
SELECTt.nameas'TableName'
,t.object_id
,t.schema_id
,filestream_data_space_id
,is_memory_optimized
,durability
,durability_desc
FROMsys.tablest
WHEREtype='U'
ANDt.schema_id=SCHEMA_ID(N'MOD');
Figure6-5.SystemviewshowingMOD.Addresstableproperties
Figure6-6.ManagementStudioshowingMOD.Addresstableproperties
Nowthatyou’veconfiguredyourdatabaseandcreatedanewtable,let’slookatan
exampleofthedatainthistableandsomespecificissuesyoumayencounter.Firstyou
mustloadthedatainthenewlycreatedmemory-optimizedtable[MOD].[Address],as
showninListing6-5.
Listing6-5.InsertingDataintotheNewlyCreatedTable
SETIDENTITY_INSERT[MOD].[Address]ON;
INSERTINTO[MOD].[Address]
(AddressID,AddressLine1,AddressLine2
,City,StateProvinceID,PostalCode
--,SpatialLocation
,rowguid,ModifiedDate)
SELECTAddressID,AddressLine1,AddressLine2
,City,StateProvinceID,PostalCode
--,SpatialLocation
,rowguid,ModifiedDate
FROM[Person].[Address];
SETIDENTITY_INSERT[MOD].[Address]OFF;
UPDATESTATISTICS[MOD].[Address]WITHFULLSCAN,
NORECOMPUTE;
GO
NoteIn-memorytablesdon’tsupportstatisticsauto-updates.InListing6-5,you
manuallyupdatethestatisticsafterinsertingnewdata.
BecauseAddressLine1isbeingusedinanindexonthetable,youhavetodeclare
thecolumnwithaBIN2collation.Thelimitationwiththiscollationisthatalluppercase
AddressLine1valuesaresortedbeforelowercasestringvalues(Zsortsbeforea).In
addition,stringcomparisonsofBIN2columnsdon’tgivecorrectresults.Alowercase
valuedoesn’tequalanuppercasevaluewhenselectingdata(A!=a).Listing6-6givesan
examplequeryofthestring-comparisonscenario.
Listing6-6.SelectingDatafromtheAddressLine1Column
SELECTAddressID,AddressLine1,RowGuid
FROM[MOD].[Address]
WHEREAddressIDIN(804,831)
ANDAddressLine1LIKE'%plaza'
Thisquerycorrectlyresultsinonlyonerecord.However,youwouldexpecttwo
recordstobereturned,usingdisk-basedtables.Paycarefulattentioninthisareawhen
you’reconsideringmovingyourdisk-basedtablestomemory-optimizedtables.Figure6-7
displaystheresultofthequery.
Figure6-7.AddressLine1resultswithnocollation
Whenthecollationforthecolumnisalteredwithahint(Listing6-7),thequery
correctlyreturnstworecords(Figure6-8).
Listing6-7.SelectingDatafromtheAddressLine1ColumnwithCollation
SELECTAddressID,AddressLine1,RowGuid
FROM[MOD].[Address]
WHEREAddressIDIN(804,831)
ANDAddressLine1COLLATESQL_Latin1_General_CP1_CI_AS
LIKE'%plaza';
Figure6-8.AddressLine1resultswithcollation
Inordertoensureproperresultsandbehavior,youneedtospecifythecollationforall
stringtypecolumns,withBIN2collationforcomparisonandsortoperations.
LimitationsonMemory-OptimizedTables
Whenyoucreateatable,youneedtotakeseverallimitationsintoaccount.Followingare
someofthemorecommonrestrictionsthatyoumayencounter:
NoneoftheLOBdatatypescanbeusedtodeclareacolumn(XML,
CLR,spatialdatatypes,oranyoftheMAXdatatypes).
Alltherowlengthsinatablearelimitedto8,060bytes.Thislimitis
enforcedatthetimethetableisinitiallycreated.Disk-basedtables
allowyoutocreatetablesthatcouldpotentiallyexceed8,060bytes
perrow.
Allin-memorytablesmusthaveatleastoneindexdefined.Noheap
tablesareallowed.
NoDDL/DMLtriggersareallowed.
Noschemachangesareallowed(ALTERTABLE).Tochangethe
schemaofthetable,youwouldneedtodropandre-createthetable.
Partitioningorcompressingamemory-optimizedtableisn’tallowed.
WhenyouuseanIDENTITYcolumnproperty,itmustbeinitialized
tostartat1andincrementby1.
Ifyou’recreatingadurabletable,youmustdefineaprimarykey
constraint.
NoteForacomprehensiveandup-to-datelistoflimitations,visit
http://msdn.microsoft.com/en-us/library/dn246937.aspx.
In-MemoryOLTPTableIndexes
Indexesareusedtomoreefficientlyaccessdatastoredintables.Bothin-memorytables
anddisk-basedtablesbenefitfromindexes;however,In-MemoryOLTPtableindexes
havesomesignificantdifferencesfromtheirdisk-basedcounterparts.Twotypesof
indexesdifferfromthoseofdisk-basedtables:nonclusteredhashesandnonclusteredrange
indexes.Theseindexesarebothcontainedinmemoryandareoptimizedformemory-
optimizedtables.Thedifferencesbetweenin-memoryanddisk-basedtableindexesare
outlinedinTable6-2.
Table6-2.Comparisonofin-memoryanddisk-basedindexes
In-MemoryTable Disk-BasedTable
Musthaveatleastoneindex Noindexesrequired
ClusteredIndexnotallowed;Onlyhashorrangenon-
clusteredindexesallowed. ClusteredIndexusuallyrecommended
Indexesaddedonlyattablecreation Indexescanbeaddedtothetableaftertable
creation
Noautoupdatestatistics Autoupdatestatisticsallowed
In-memorytableindexesonlyexistinmemory Indexespersistondiskandthetransactionlog
Indexesarecreatedduringtablecreationordatabase
startup
Indexesarepersistedtodisk;therefore,theyare
notrebuiltandcanbereadfromdisk
Indexesarecovering,sincetheindexcontainsa
memorypointertotheactualrowofthedata Indexesarenotcoveringbydefault.
Thereisalimitationof8indexespertable 1ClusteredIndex+999NonClustered=1000
Indexesor249XMLIndexes
NoteDurablememory-optimizedtablesrequireaprimarykey.Bydefault,aprimary
keyattemptstocreateaclusteredindex,whichwillgenerateanerrorforamemory-
optimizedtable.YoumustspecificallyindicateNONCLUSTEREDastheindextype.
Theneedforatleastoneindexstemsfromthearchitectureofanin-memorytable.The
tableusesindexpointersastheonlymethodoflinkingrowsinmemoryintoatable.This
isalsowhyclusteredindexesaren’tneededonmemory-optimizedtables;thedataisn’t
specificallyorderedorarrangedinanymanner.
AnewfeatureofSQLServer2014isthatyoucancreateindexesinlinewiththe
tablecreatestatement.Earlier,noticethatListing6-3createsaninlinenonclustered
indexwithtablecreate:
,rowguidUNIQUEIDENTIFIERNOTNULL
INDEX[AK_MODAddress_rowguid]NONCLUSTERED
CONSTRAINT[DF_MODAddress_rowguid]DEFAULT
(NEWID())
InlineindexcreationisnewtoSQLServer2014butnotuniquetomemory-optimized
tables.It’salsovalidfordisk-basedtables.
Bothhashandrangeindexesareallowedonthesamecolumn.Thiscanbeagood
strategywhentheusecasesvaryforhowthedataisaccessed.
HashIndexes
Ahashindexisanefficientmechanismthatacceptsinputvaluesintoahashingfunction
andmapstoahashbucket.Thehashbucketisanarraythatcontainspointerstoefficiently
returnarowofdata.Thecollectionofpointersinthehashbucketisthehashindex.When
created,thisindexexistsentirelyinmemory.
Hashindexesarebestusedforsingle-itemlookups,aWHEREclausewithan=,or
equalityjoins.Theycan’tbeusedforrangelookupssuchasLIKEoperationsorbetween
queries.Theoptimizerwon’tgiveyouanerror,butitisn’tanefficientwayofaccessing
thedata.Whencreatingthehashindex,youmustdecideattable-creationtimehowmany
bucketstoassignfortheindex.It’srecommendedthatitshouldbecreatedat1.5to2times
largerthantheexistinguniquekeycountsinyourtable.Thisisanimportantassessment,
becausethebucketcountcan’tbeextendedbyre-creatingtheindexandthetable.The
performanceofthepointlookupsdoesn’tdegradeifyouhaveabucketcountthatislarger
thannecessary.However,performancewillsufferifthebucketcountistoosmall.Listing
6-3usedahashbucketcountof30,000,becausethenumberofuniquerowsinthetableis
slightlylessthan20,000.Here’sthecodethatdefinestheconstraintwiththebucketcount:
,CONSTRAINTPK_MODAddress_Address_IDPRIMARYKEY
NONCLUSTEREDHASH
([AddressID]ASC)WITH(BUCKET_COUNT=30000)
Ifyourusecaserequiresit,youcancreateacompositeindexonahashindex.There
aresomelimitationstobeawareofifyoudecidetouseacompositeindex.Thehashindex
willbeusedonlyifthepoint-lookupsearchisdoneonbothcolumnsintheindex.Ifboth
columnsaren’tusedinthesearch,theresultisanindexscanorascanofallthehash
buckets.Thisoccursbecausethehashfunctionconvertsthevaluesfrombothcolumnsinto
ahashvalues.Therefore,inacompositehashindex,thevalueofonecolumnnever
equatestothehashvalueoftwocolumns:
HASH(<Column1>)<>HASH(<Column1>,<Column2>)
Let’scomparetheaffectofahashindexonamemory-optimizedtableversusadisk-
basedtableclusteredindex.
WarningThisappliestothecodeinListing6-8andseveralotherexamples.Donot
attempttoruntheDBCCcommandsonaproductionsystem,becausetheycanseverely
affecttheperformanceofyourentireinstance.
Listing6-8includessomeDBCCcommandstoflushallcachepagesandmakesure
thecomparisonsstartinarepeatablestatewithnothinginmemory.It’shighly
recommendedthatthesetypesofcommandsberunonlyinanon-productionenvironment
thatwon’taffectanyoneelseontheinstance.
Listing6-8.PointLookuponaHashIndexvs.Disk-BasedClusteredIndex
CHECKPOINT
GO
DBCCDROPCLEANBUFFERS
GO
DBCCFREEPROCCACHE
GO
SETSTATISTICSIOON;
SELECT*FROMPerson.AddressWHEREAddressId=26007;
SELECT*FROMMOD.AddressWHEREAddressId=26007;
Thisfirstexamplesimplylooksatwhathappenswhenyoucompareperformance
whendoingasimplepointlookupforaspecificvalue.Boththedisk-basedtable
(Person.Address)andthememory-optimizedtable(MOD.Address)havea
clusteredandhashindexontheAddressIDcolumn.Theresultofrunningtheentire
batchisasshowninFigure6-9intheMessagestab.
Figure6-9.Hashindexvs.clusteredindexIOstatistics
Therearetwopieceofinformationworthnoting.Thefirstbatchtorunwasthedisk-
basedtable,whichresultedintwologicalreadsandtwophysicalreads.Thesecondbatch
wasthememory-optimizedtable,whichdidn’tregisteranylogicalorphysicalIOreads
becausethistable’sdataandindexesarecompletelyheldinmemory.
Figure6-10clearlyshowsthatthedisk-basedtabletook99%oftheentirebatch
executiontime;thememory-optimizedtabletook1%ofthetimerelativetotheentire
batch.Bothqueryplansareexactlythesame;however,thisillustratesthesignificant
differencethatamemory-optimizedtablecanmaketothesimplestofqueries.
Figure6-10.Hashindexvs.clusteredindexpointlookupexecutionplan
HoveringovertheIndexSeekoperatorintheexecutionplanshowsacoupleof
differences.ThefirstisthattheStoragecategorynowdifferentiatesbetweenthedisk-
basedtableasRowStoreandthememory-optimizedtableasMemoryOptimized.
Thereisalsoasignificantdifferencebetweentheestimatedrowsizeofthetwotables.
Nextlet’sexperimentwithrunningarangelookupagainstthedisk-basedtableandthe
memory-optimizedtable.Listing6-9doesasimplerangelookupagainsttheprimarykey
ofthetabletodemonstratesomeofthedifferenceinperformance(seeFigure6-11).
Listing6-9.RangeLookupUsingaHashIndex
SELECT*FROMPERSON.ADDRESSWHEREADDRESSIDBETWEEN100AND
26007;
SELECT*FROMMOD.ADDRESSWHEREADDRESSIDBETWEEN100AND
26007;
Figure6-11.Hashindexvs.clusteredindexrangelookupexecutionplan
Thisexampleclearlydisplaysthatamemory-optimizedtablehashindexisn’t
necessarilyquickerthanadisk-basedclusteredindexforallusecases.Thememory-
optimizedtablehadtoperformanindexscanandthenfiltertheresultsforthespecific
criteriayou’relookingtogetback.Thedisk-basedtableclusteredindexseekisstillmore
efficientforthisparticularusecase.Themoralofthestoryisthatitalwaysdepends.You
shouldalwaysrunthroughseveralusecasesanddeterminethebestmethodofaccessing
yourdata.
RangeIndexes
Arangeindexmightbestbedefinedasamemory-optimizednonclusteredindex.When
created,thisindexexistsentirelyinmemory.Thememory-optimizednonclusteredindex
workssimilarlytoadisk-basednonclusteredindex,butithassomesignificant
architecturaldifferences.Thearchitectureforrangeindexesisbasedonanewdata
structurecalledaBw-tree.3TheBw-treearchitectureisalatch-freearchitecturethatcan
takeadvantageofmodernprocessorcachesandmulticorechips.
Memory-optimizednonclusteredindexesarebestusedforrange-typequeriessuchas
(<,>,IN),(Allsalesordersbetweendates),andsoon.Theseindexesalsoworkwithpoint
lookupsbutaren’tasoptimizedforthosetypesoflookupsasahashindex.Memory-
optimizednonclusteredindexesshouldalsobeconsideredoverhashindexeswhenyou’re
migratingadisk-basedtablethathasaconsiderablenumberofduplicatevaluesina
column.Thesizeoftheindexgrowswiththesizeofthedata,similartoB-treedisk-based
tablestructures.
Whenyou’reusingmemory-optimizednonclusteredindexes,ahandfuloflimitations
anddifferencesfromdisk-basednonclusteredindexesareworthmentioning.Listing6-3
createdthenonclusteredindexontheCitycolumn.Belowisanexcerptfromthelisting,
thatdisplaysthecreationofthenonclusteredindex.
INDEX[IX_MODAddress_City]
([City]DESC)
AllthecolumnsthatarepartofanindexmustbedefinedasNOT
NULL.
Ifthecolumnisdefinedasastringdatatype,itmustbedefinedusing
aBIN2collation.
TheNONCLUSTEREDhintisoptionalunlessthecolumnisthe
primarykeyforthetable,becauseSQLServerwilltrytodefinea
primarykeyconstraintasclustered.
Thesort-orderhintonacolumninarangeindexisespecially
importantforamemory-optimizedtable.SQLServercan’tperforma
seekontheindexiftheorderinwhichtherecordsareaccessedis
differentfromtheorderinwhichtheindexwasoriginallydefined,
whichwouldresultinanindexscan.
Followingareacoupleofexamplesthatdemonstratethecomparisonofadisk-based
nonclusteredindexandamemory-optimizednonclusteredindex(rangeindex).Thetwo
queriesinListing6-10selectallcolumnsfromtheAddressdisk-basedtableandthe
memory-optimizedtableusingasingle-pointlookupofthedate.Theresultofthequeries
isdisplayedinFigure6-12.
Listing6-10.Single-PointLookupUsingaRangeIndex
CHECKPOINT
GO
DBCCDROPCLEANBUFFERS
GO
DBCCFREEPROCCACHE
GO
SETSTATISTICSIOON
SELECT*FROM[Person].[Address]WHEREModifiedDate='2013-
12-21';
SELECT*FROM[MOD].[Address]WHEREModifiedDate='2013-12-
21';
Figure6-12.Single-pointlookupusinganonclusteredindexcomparison
ThisexampledisplaysasignificantdifferencebetweenQuery1(disk-basedtable)and
Query2(memory-optimizedindex).Bothqueriesuseanindexseektogettotherowin
thetable,butthedisk-basedtablehastodoanadditionalkey-lookupoperationonthe
clusteredindex.Becausethequeryisaskingforallthecolumnsofdataintherow,the
disk-basednonclusteredindexmustobtainthepointertothedatathroughtheclustered
index.Thememory-optimizedindexdoesn’thavetheaddedcostofthekeylookup,
becausealltheindexesarecoveringand,therefore,theindexalreadyhasapointertothe
additionalcolumnsofdata.
Next,Listing6-11doesarangelookuponthedisk-basedtablenonclusteredindexand
arangelookuponthememory-optimizednonclusteredindex.Thedifferencebetweenthe
twoqueriesisdisplayedinFigure6-13.
Listing6-11.RangeLookupUsingaRangeIndex
CHECKPOINT
GO
DBCCDROPCLEANBUFFERS
GO
DBCCFREEPROCCACHE
GO
SETSTATISTICSIOON
SELECT*FROM[Person].[Address]WHEREModifiedDate
BETWEEN'2013-12-01'AND'2013-12-21';
SELECT*FROM[MOD].[Address]WHEREModifiedDate
BETWEEN'2013-12-01'AND'2013-12-21';
Figure6-13.RangeLookupComparison
Theresultsareasexpected.Thememory-optimizednonclusteredindexperforms
significantlybetterthanthedisk-basednonclusteredindexwhenperformingarangequery
usingarangeindex.
NativelyCompiledStoredProcedures
Nativelycompilestoredproceduresaresimilarinpurposetodisk-basedstoredprocedures,
withthemajordifferencethatanativelycompiledstoredprocedureiscompiledintoCand
thenintomachinelanguagestoredasaDLL.TheDLLallowsSQLServertoaccessthe
stored-procedurecodemorequickly,totakeadvantageofparallelprocessingand
significantimprovementsinexecution.Thereareseverallimitations,butifusedcorrectly,
nativelycompiledstoredprocedurescanyielda2xormoreincreaseinperformance.
Togetstarted,let’sexaminetheoutlineofanativelycompiledstoredprocedurein
Listing6-12indetail.
Listing6-12.NativelyCompiledStoredProcedureExample
1CREATEPROCEDUREselAddressModifiedDate
2(@BeginModifiedDateDATETIME
3,@EndmodifiedDateDATETIME)
4WITH
5NATIVE_COMPILATION
6,SCHEMABINDING
7,EXECUTEASOWNER
8AS
9BEGINATOMIC
10WITH
11(TRANSACTIONISOLATIONLEVEL=SNAPSHOT
12LANGUAGE=N'us_english')
13
14--T-SQLLogicHere
15SELECTAddressID,AddressLine1
16,AddressLine2,City
17,StateProvinceID,PostalCode
18,rowguid,ModifiedDate
19FROM[MOD].[Address]
20WHEREModifiedDate
21BETWEEN@BeginModifiedDateAND@EndmodifiedDate;
22
23END;
Therequirementstocreateanativelycompiledstoredprocedureareasfollows:
Line5,NATIVECOMPILATION:ThisoptiontellsSQLServerthat
theprocedureistobecompiledintoaDLL.Ifyouaddthisoption,
youmustalsospecifytheSCHEMABINDING,EXECUTEAS,and
BEGINATOMICoptions.
Line6,SCHEMABINDING:Thisoptionbindsthestoredprocedureto
theschemaoftheobjectsitreferences.Atthetimethestored
procedureiscompiled,theschemaandoftheobjectsitreferencesare
compiledintotheDLL.Whentheprocedureisexecuted,itdoesn’t
havetochecktoseewhetherthecolumnsoftheobjectsitreferences
havebeenaltered.Thisoffersthefastestandshortestmethodof
executingastoredprocedure.Ifanyoftheunderlyingobjectsit
referencesarealtered,you’refirstforcedtodropandrecompilethe
storedprocedurewithanychangestotheunderlyingobjectsit
references.
Line7,EXECUTEASOWNER:Thedefaultexecutioncontextfora
storedprocedureisEXECUTEASCALLER.Nativelycompiled
storedproceduresdon’tsupportthiscallercontextandmustbe
specifiedasoneoftheoptionsEXECUTEASOWNER,SELF,or
USER.ThisisrequiredsothatSQLServerdoesn’thavetocheck
executionrightsfortheusereverytimetheyattempttoexecutethe
storedprocedure.Theexecutionrightsarehardcodedandcompiled
intotheDLLtooptimizethespeedofexecution.
Line9,BEGINATOMIC:Nativelycompiledstoredprocedureshave
therequirementthatthebodymustconsistofexactlyoneatomic
block.TheatomicblockispartoftheANSISQLstandardthat
specifiesthateithertheentirestoredproceduresucceedsortheentire
storedprocedurelogicfailsandrollsbackasawhole.Atthetimethe
storedprocedureiscalled,ifanexistingtransactionisopen,thestored
procedurejoinsthetransactionandcommits.Ifnotransactionisopen,
thenthestoredprocedurecreatesitsowntransactionandcommits.
Lines11and12,TRANSACTIONISOLATION:Allthesession
settingsarefixedatthetimethestoredprocedureiscreated.Thisis
donetooptimizethestoredprocedure’sperformanceatexecution
time.
Thosearethemainoptionsinanativelycompiledstoredprocedurethatareuniqueto
itssyntax,versusadisk-basedstoredprocedure.Thereareasignificantnumberof
limitationswhencreatinganativelycompiledstoredprocedure.Someofthemore
commonlimitationsarelistednext:
Objectsmustbecalledusingtwo-partnames(schema.table).
Temporarytablesfromtempdbcan’tbeusedandshouldbereplaced
withtablevariablesornondurablememory-optimizedtables.
Anativelycompiledstoredprocedurecan’tbeaccessedfroma
distributedtransaction.
Thestoredprocedurecan’taccessdisk-basedtables,onlymemory-
optimizedtables.
Thestoredprocedurecan’tuseanyoftherankingfunctions.
DISTINCTinaqueryisn’tsupported.
EXISTSorINarenotsupportedfunctions.
Commontableexpressions(CTEs)arenotsupportedconstructs.
Subqueriesaren’tavailable.
NoteForacomprehensivelistoflimitations,visit
http://msdn.microsoft.com/en-us/library/dn246937.aspx.
Executionplansforqueriesintheprocedureareoptimizedwhentheprocedureis
compiled.Thishappensonlywhentheprocedureiscreatedandwhentheserverrestarts,
notwhenstatisticsareupdated.Therefore,thetablesneedtocontainarepresentativeset
ofdata,andstatisticsneedtobeup-to-datebeforetheproceduresarecreated.(Natively
compiledstoredproceduresarerecompiledifthedatabaseistakenofflineandbrought
backonline.)
EXERCISES
1. WhicheditionsofSQLServersupportthenewIn-Memory
features?
a. DeveloperEdition
b. EnterpriseEdition
c. BusinessIntelligenceEdition
d. Alloftheabove
2. Whendefiningastringtypecolumninanin-memorytable,you
mustalwaysuseaBIN2collation.
[True/False]
3. Youwanttodefinethebestindextypeforadatecolumninyour
table.Whichindextypemightbebestsuitedforthiscolumn,ifitis
beingusedforreportingpurposesusingarangeofvalues?
a. Hashindex
b. Clusteredindex
c. Rangeindex
d. AandB
4. Whencreatingamemory-optimizedtable,ifyoudonotspecifythe
durabilityoptionforthetable,itwilldefaultto
SCHEMA_AND_DATA.
[True/False]
5. Memory-optimizedtablesalwaysrequireaprimarykeyconstraint.
[True/False]
6. Nativelycompiledstoredproceduresallowforwhichofthe
followingexecutioncontexts?
a. EXECUTEASOWNER
b. EXECUTEASSELF
c. EXECUTEASUSER
d. AandB
e. A,B,andC
_____________________
1“Moore’sLaw,”http://en.wikipedia.org/wiki/Moore's_law.
2“AverageHistoricPriceofRAM,”StatisticBrain,www.statisticbrain.com/average-historic-price-
of-ram.
3JustinJ.Levandoski,DavidB.Lomet,andSudiptaSengupta,“TheBw-Tree:AB-treeforNewHardwarePlatforms,”
MicrosoftResearch,April8,2013,http://research.microsoft.com/apps/pubs/default.aspx?
id=178758.
CHAPTER7
Triggers
SQLServerprovidestriggersasameansofexecutingT-SQLcodeinresponsetodatabase
object,database,andserverevents.SQLServer2014implementsthreetypesoftriggers:
classicT-SQLDataManipulationLanguage(DML)triggers,whichfireinresponseto
INSERT,UPDATE,andDELETEeventsagainsttables;DataDefinitionLanguage(DDL)
triggers,whichfireinresponsetoCREATE,ALTER,andDROPstatements;andlogon
triggers,whichfireinresponsetoLOGONevents.DDLtriggerscanalsofireinresponseto
somesystemSPsthatperformDDL-likeoperations.
TriggersareaformofspecializedSP,closelytiedtoyourdataanddatabaseobjects.In
thepast,DMLtriggerswereusedtoenforcevariousaspectsofbusinesslogic,suchas
foreignkeyandotherconstraintsondata,andothermorecomplexbusinesslogic.
Cascadingdeclarativereferentialintegrity(DRI)androbustcheckconstraintsinT-SQL
havesupplantedDMLtriggersinmanyareas,butthey’restillusefulintheirownright.
Thischapterdiscusseshowtriggerswork,howtousethem,andwhenthey’remost
appropriate.YoualsolearnaboutDDLtriggersandexploretheiruse.
AsdiscussedinChapter6,oneofthelimitationsofin-memorytablesisthattriggers
aren’tavailable.Thischapterofthebookstillappliestoalldisk-basedtables.
DMLTriggers
DMLtriggersarecomposedofT-SQLcodethatisexecuted(fired)inresponsetoan
INSERT,anUPDATE,aDELETE,oraMERGEstatementonatableorview.DML
triggersarecreatedviatheCREATETRIGGERstatement,whichallowsyoutospecify
thefollowingdetailsaboutthetrigger:
Thenameofthetrigger,whichistheidentifieryoucanusetomanage
thetrigger.Youcanspecifyatwo-partnameforatrigger(schemaand
triggername),buttheschemamustbethesameastheschemaforthe
tableonwhichthetriggerexecutes.
Thetableorviewonwhichthetriggerexecutes.
Thetriggeringevents,whichcanbeanycombinationofINSERT,
UPDATE,andDELETE.Thetriggeringeventsindicatethetypeof
eventsthatthetriggerfiresinresponseto.
TheAFTER/FORandINSTEADOFindicators,whichdetermine
whetherthetriggerisfiredafterthetriggeringstatementcompletesor
thetriggeroverridesthefiringstatement.
AdditionaloptionsliketheENCRYPTIONandEXECUTEASclauses,
whichallowyoutoobfuscatethetriggersourcecodeandspecifythe
contextunderwhichthetriggerexecutes,respectively.
NoteDMLtriggershavesomerestrictionsontheircreationthatyoushouldkeepin
mind.DMLtriggerscan’tbedefinedontemporarytables,theycan’tbedeclaredontable
variables,andtheycan’tbedefinedonin-memorytables.Finally,onlyINSTEADOF
triggerscanbeusedonviews.
InadditiontotheCREATETRIGGERstatement,SQLServerprovidesanALTER
TRIGGERstatementtomodifythedefinitionofatrigger,aDROPTRIGGERstatementto
removeanexistingtriggerfromthedatabase,andDISABLETRIGGERandENABLE
TRIGGERstatementstodisableandenableatrigger,respectively.Listing7-1showshow
todisableandenableaspecifictriggernamed
HumanResources.EmployeeUpdateTriggeroralltriggersonanobject,namely,
theHumanResources.Employeetable.Italsocontainsanexampleofhowtoquery
thesys.triggerscatalogviewtoreturnallthedisabledtriggersinthecurrent
database.
Listing7-1.DisablingandEnablingTriggers
DISABLETRIGGERHumanResources.EmployeeUpdateTrigger
ONHumanResources.Employee;
SELECT
name,
OBJECT_SCHEMA_NAME(parent_id)+'.'
+OBJECT_NAME(parent_id)asParent
FROMsys.triggers
WHEREis_disabled=1;
ENABLETRIGGERHumanResources.EmployeeUpdateTrigger
ONHumanResources.Employee;
--disablingandenablingalltriggersontheobject
DISABLETRIGGERALLONHumanResources.Employee;
ENABLETRIGGERALLONHumanResources.Employee;
Disablingtriggerscangreatlyimproveperformancewhenyouapplyabatchof
modificationsonatable.Justmakesuretherulesenforcedbythetrigger(s)arechecked
anotherway:forinstance,manuallyafterthebatch.Alsodon’tforgettore-enablethe
triggerattheendoftheprocess.
MultipleTriggers
Youcancreatemultipletriggersonthesameobjects.Theywillfireinnospecificorder.If
youreallyneedto,youcanspecifythatatriggershouldbefiredfirstorlast,byusingthe
sp_settriggerordersystemstoredprocedure.Forexample:
EXECsp_settriggerorder@triggername='MyTrigger',@order
='first',@stmttype='UPDATE';
ThatsetstheMyTriggertriggertofirefirstonUPDATEactions.However,inour
opinion,thisshouldn’tbeused,becauseitaddsunnecessarycomplexityinthedatabase.If
youneedtomanageprecedencebetweentriggeractions,it’sbesttoconsolidatewhatyou
needtodointhesametrigger.
WhentoUseDMLTriggers
Waybackintheday,usingtriggerswasthebest(andinsomecasesonly)waytoperform
avarietyoftasks,suchasensuringcascadingDRI,validatingdatabeforestoringitin
tables,auditingchanges,andenforcingcomplexbusinesslogic.NewerreleasesofSQL
Serverhaveaddedfunctionalitythatmorecloselyintegratesmanyofthesefunctionsinto
thecoredatabaseengine.Forinstance,inmostcases,youcanuseSQLServer’sbuilt-in
cascadingDRItoensurereferentialintegrityandcheckconstraintsforsimplevalidations
duringinsertandupdateoperations.DMLtriggersarestillagoodchoicewhensimple
auditingtasksorvalidationswithcomplexbusinesslogicarerequired.
NoteDRIisn’tenforcedacrossdatabases.Thismeansyoucan’treferenceatableina
differentdatabaseinaDRI/foreign-keyconstraint.Becausetheycanreferenceobjects
suchastablesandviewsinotherdatabases,triggersarestillagoodoptionwhenthistype
ofreferential-integrityenforcementisnecessary.
Listing7-2showsaverysimpletriggercreatedonthe
HumanResources.EmployeetableoftheAdventureWorksdatabase.The
HumanResources.EmployeeUpdateTriggertriggerupdatesthe
ModifiedDatecolumnoftheHumanResources.Employeetablewiththecurrent
dateandtimewheneverarowisupdated.
Listing7-2.HumanResources.EmployeeUpdateTriggerCode
CREATETRIGGERHumanResources.EmployeeUpdateTrigger
ONHumanResources.Employee
AFTERUPDATE
NOTFORREPLICATION
AS
BEGIN
--stopifnorowwasaffected
IF@@ROWCOUNT=0RETURN
--Turnoff"rowsaffected"messages
SETNOCOUNTON;
--Makesureatleastonerowwasaffected
--UpdateModifiedDateforallaffectedrows
UPDATEHumanResources.Employee
SETModifiedDate=GETDATE()
WHEREEXISTS
(
SELECT1
FROMinsertedi
WHEREi.BusinessEntityID
=HumanResources.Employee.BusinessEntityID
);
END;
ThefirstpartoftheCREATETRIGGERstatementdefinesthenameofthetriggerand
specifiesthatitwillbecreatedontheHumanResources.Employeetable.The
definitionalsospecifiesthatthetriggerwillfireafterrowsareupdated,andtheNOTFOR
REPLICATIONkeywordspreventreplicationeventsfromfiringthetrigger:
CREATETRIGGERHumanResources.EmployeeUpdateTrigger
ONHumanResources.Employee
AFTERUPDATE
NOTFORREPLICATION
ThebodyofthetriggerstartsbycheckingthenumberofrowsaffectedbytheUPDATE
withthe@@ROWCOUNTsystemfunction.Thisisanoptimizationthatskipsthebodyofthe
triggerifnorowswereaffected.
Wheneveranytriggerisfired,it’simplicitlywrappedinthesametransactionasthe
DMLstatementthatfiredit.Thishasbigperformanceandconcurrencyimplications.It
meansthatwhateveryourtriggerdoes,itshoulddoasquicklyandefficientlyaspossible.
TheT-SQLstatementsinatriggerbodycanpotentiallycreatelocksinyourdatabase,a
situationthatyouwanttominimize.It’snotunheardofforinefficienttriggerstocause
blockingproblems.Youshouldalsominimizetheamountofworkdoneinsidethetrigger
andoptimizetheoperationsithastoperform.ItalsomeansaROLLBACK
TRANSACTIONstatementinthetriggerwillrollbackDMLstatementsexecutedinthe
trigger,aswellastheoriginalDMLstatementthatfiredthetrigger(andallexplicit
transactionsinwhichthestatementisrun).
Checking@@ROWCOUNTatthestartofthetriggerhelpsensurethatyourtriggersare
efficient.If@@ROWCOUNTis0,itmeansnorowswereaffectedbytheoriginalDML
statementthatfiredthetrigger.Thenthetriggerhasnoworktodo,andyoucanskipthe
rest:
--stopifnorowwasaffected
IF@@ROWCOUNT=0RETURN
CautionChecking@@ROWCOUNTmustbedoneattheveryfirstline.Anyprevious
actioninthetrigger,evenSETcommands,couldchangethe@@ROWCOUNTvalue.
Next,thetriggerturnsofftherowsaffectedmessagesviatheSETNOCOUNT
ONstatement:
--Turnoff"rowsaffected"messages
SETNOCOUNTON;
NoteUsingSETNOCOUNTONisn’tstrictlyrequiredintriggers,butitprevents
superfluousrowsaffectedmessagesfrombeinggeneratedbythetrigger.Someolder
databasedrivers—andevensomemorerecentones,suchascertainJavaDatabase
Connectivity(JDBC)drivers—cangetconfusedbytheseextramessages,soit’snotabad
ideatodisabletheminthebodyofyourtriggers.AnySETstatementcanbeusedinthe
bodyofatrigger.Thestatementremainsineffectwhilethetriggerexecutesandrevertsto
itsformersettingwhenthetriggercompletes.
TheIFstatementcontainsanUPDATEstatementthatsetstheModifiedDate
columntothecurrentdateandtimewhenrowsinthetableareupdated.Animportant
conceptoftriggerprogrammingistobesureyouaccountformultiplerowupdates.It’s
notsafetoassumethataDMLstatementwillupdateonlyasinglerowofyourtable,
becausetriggersinSQLServerareset-orientedandfireonlyonceforastatement.Thereis
nosuchthingasaper-rowtriggerinSQLServer.Inthistrigger,theUPDATEstatement
usestheEXISTSpredicateintheWHEREclausetoensurethatModifiedDateis
updatedforeveryrowthatwasaffected.Itaccomplishesthisbyusingtheinserted
virtualtable,describedinthe“insertedanddeletedVirtualTables”sectionbelow.
--UpdateModifiedDateforallaffectedrows
UPDATEHumanResources.Employee
SETModifiedDate=GETDATE()
WHEREEXISTS
(
SELECT1
FROMinsertedi
WHEREi.BusinessEntitylD
=HumanResources.Employee.BusinessEntitylD
);
InsertedandDeletedVirtualTables
ADMLtriggerneedstoknowwhichrowswereaffectedbytheDMLstatementthatfired
it.Theinsertedanddeletedvirtualtablesfulfillthisneed.Whenatriggerfires,
SQLServerpopulatestheinsertedanddeletedvirtualtablesandmakesthem
availablewithinthebodyofthetrigger.Thesetwovirtualtableshavethesamestructure
astheaffectedtableandcontainthedatafromallaffectedrows.
Theinsertedtablecontainsallrowsinsertedintothedestinationtablebyan
INSERTstatement.Thedeletedtablecontainsallrowsdeletedfromthedestination
tablebyaDELETEstatement.ForUPDATEstatements,therowsaretreatedasaDELETE
followedbyanINSERT,sothepre-UPDATE-affectedrowsarestoredinthedeleted
table,andthepost-UPDATE-affectedrowsarestoredintheinsertedtable.
Thevirtualtablesareread-onlyandcan’tbemodifieddirectly.TheexampleinListing
7-2usestheinsertedvirtualtabletodeterminewhichrowswereaffectedbythe
UPDATEstatementthatfiredthetrigger.ThetriggerupdatestheModifiedDatecolumn
foreveryrowintheHumanResources.Employeetablewithamatchingrowinthe
insertedtable.Youusetheinsertedanddeletedvirtualtablesinotherexample
codeinthissection.
TestingthetriggerisassimpleasusingSELECTandUPDATE.Theexamplein
Listing7-3changesthemaritalstatusofemployeeswithBusinessEntityIDnumbers
1and2toM(for“married”).
Listing7-3.TestingHumanResources.EmployeeUpdateTrigger
UPDATEHumanResources.Employee
SETMaritalStatus='M'
WHEREBusinessEntityIDIN(1,2);
SELECTBusinessEntityID,NationalIDNumber,MaritalStatus,
ModifiedDate
FROMHumanResources.Employee
WHEREBusinessEntityIDIN(1,2);
Theresults,showninFigure7-1,demonstratethattheUPDATEstatementfiredthe
triggerandproperlyupdatedModifiedDateforthetwospecifiedrows.
Figure7-1.Updatedmaritalstatusfortwoemployees
CautionIftheRECURSIVE_TRIGGERSdatabaseoptionisturnedoninthe
AdventureWorksdatabase,HumanResources.EmployeeUpdateTriggerwill
erroroutwithamessagethatthe“nestinglimithasbeenexceeded.”Thisiscausedbythe
triggerrecursivelyfiringitselfaftertheUPDATEstatementinthetriggerisexecuted.Use
ALTERDATABASEAdventureWorksSETRECURSIVE_TRIGGERSOFFto
turnoffrecursivetriggersandALTERDATABASEAdventureWorksSET
RECURSIVE_TRIGGERSONtoturntheoptionbackon.ThedefaultisOFF.Recursive
triggersarecoveredlaterinthischapter.
AuditingwithDMLTriggers
AnothercommonuseforDMLtriggersisauditingDMLactionsagainsttables.The
primarypurposeofDMLauditingistomaintainarecordofchangestothedatainyour
database.Thismayberequiredforanumberofreasons,includingregulatorycompliance
ortofulfillcontractualobligations.
UsingChangeDataCaptureInstead
SinceSQLServer2008,youcanusethefeatureknownasChangeDataCapture(CDC),
whichprovidesbuilt-inauditingfunctionality.TheCDCfunctionalityprovidesanother
optionforloggingDMLactionsagainsttables.AlthoughCDCfunctionalityisbeyondthe
scopeofthisbook,werecommendlookingintothisoptionbeforedecidingwhichmethod
tousewhenyouneedDMLloggingfunctionality;itmaybeamoreelegantandefficient
waytoauditdatachanges.Oneofthedrawbackswithtriggersistheperformanceimpact
theyhaveonDMLoperations,especiallybecausethey’repartoftheDMLtransaction.
CDCismuchfasterbecauseitactsasaseparateprocessthattracksthedatabase-
transactionlogformodificationsappliedtotheauditedtablesandwriteschangesto
internalchangetables,usingthesametechnologyastransactionreplication.Moreover,
CDCcanautomaticallyprunetheaudittablestokeeptheirsizemanageable.Notethat
CDCisavailableonlyinEnterpriseEdition.
ThefirststeptoimplementingDMLauditingistocreateatabletostoreyouraudit
information.Listing7-4createsjustsuchatable.
Listing7-4.DMLAuditLoggingTable
CREATETABLEdbo.DmlActionLog(
EntryNumintIDENTITY(1,1)PRIMARYKEYNOTNULL,
SchemaNamesysnameNOTNULL,
TableNamesysnameNOTNULL,
ActionTypenvarchar(10)NOTNULL,
ActionXmlxmlNOTNULL,
LoginNamesysnameNOTNULL,
ApplicationNamesysnameNOTNULL,
HostNamesysnameNOTNULL,
ActionDateTimedatetime2(0)NOTNULLDEFAULT
(SYSDATETIME())
);
GO
Thedbo.DmlActionLogtableinListing7-4storesinformationforeachDML
actionperformedagainstatable,includingthenameoftheschemaandtableagainst
whichtheDMLactionwasperformed,thetypeofDMLactionperformed,XML-
formattedsnapshotsofthebeforeandafterstatesoftherowsaffected,andadditional
informationtoidentifywhoperformedtheDMLactionandwhentheactionwas
performed.Oncetheauditloggingtableiscreated,it’stimetocreateatriggertologDML
actions.ThisisshowninListing7-5.
Listing7-5.DMLAuditLoggingTrigger
CREATETRIGGERHumanResources.DepartmentChangeAudit
ONHumanResources.Department
AFTERINSERT,UPDATE,DELETE
NOTFORREPLICATION
AS
BEGIN
--stopifnorowwasaffected
IF@@ROWCOUNT=0RETURN
--Turnoff"rowsaffected"messages
SETNOCOUNTON;
DECLARE@ActionTypenvarchar(10),@ActionXmlxml;
--Getcountofinsertedrows
DECLARE@inserted_countint=(
SELECTCOUNT(*)
FROMinserted
);
--Getcountofdeletedrows
DECLARE@deleted_countint=(
SELECTCOUNT(*)
FROMdeleted
);
--DeterminethetypeofDMLactionthatfiredthe
trigger
SET@ActionType=CASE
WHEN(@inserted_count>0)AND(@deleted_count=0)
THENN'insert'
WHEN(@inserted_count=0)AND(@deleted_count>0)
THENN'delete'
ELSEN'update'
END;
--UseFORXMLAUTOtoretrievebeforeandafter
snapshotsofthechanged
--datainXMLformat
SELECT@ActionXml=COALESCE
(
(
SELECT*
FROMdeleted
FORXMLAUTO
),N'<deleted/>'
)+COALESCE
(
(
SELECT*
FROMinserted
FORXMLAUTO
),N'<inserted/>'
);
--Insertarowfortheloggedactionintheaudit
loggingtable
INSERTINTOdbo.DmlActionLog
(
SchemaName,
TableName,
ActionType,
ActionXml,
LoginName,
ApplicationName,
HostName
)
SELECT
OBJECT_SCHEMA_NAME(@@PROCID,DB_ID()),
OBJECT_NAME(t.parent_id,DB_ID()),
@ActionType,
@ActionXml,
SUSER_SNAME(),
APP_NAME(),
HOST_NAME()
FROMsys.triggerst
WHEREt.object_id=@@PROCID;
END;
GO
ThetriggerinListing7-5iscreatedontheHumanResources.Departmenttable,
althoughit’swritteninsuchawaythatthebodyofthetriggercontainsnocodespecificto
thetableit’screatedon.Thismeansyoucaneasilymodifythetriggertoworkas-ison
mosttables.
TheHumanResources.DepartmentChangeAudittriggerdefinitionbegins
withtheCREATETRIGGERstatement,whichnamesthetriggerandcreatesitonthe
HumanResources.Departmenttable.Italsospecifiesthatthetriggershouldfire
afterINSERT,UPDATE,andDELETEstatementsareperformedagainstthetable.Finally,
theNOTFORREPLICATIONclausespecifiesthatreplicationeventswon’tcausethe
triggertofire:
CREATETRIGGERHumanResources.DepartmentChangeAudit
ONHumanResources.Department
AFTERINSERT,UPDATE,DELETE
NOTFORREPLICATION
ThetriggerbodybeginsbycheckingthenumberofrowsaffectedbytheDML
statementwiththe@@ROWCOUNTfunction.Thetriggerskipstheremainderofthe
statementsinthebodyifnorowswereaffected:
--stopifnorowwasaffected
IF@@ROWCOUNT=0RETURN
Themainbodyofthetriggerbeginswithaninitializationthatturnsoffextraneous
rowsaffectedmessages,declareslocalvariables,andgetsthecountofrowsinserted
anddeletedbytheDMLstatementfromtheinsertedanddeletedvirtualtables:
--Turnoff"rowsaffected"messages
SETNOCOUNTON;
DECLARE@ActionTypenvarchar(10),@ActionXmlxml;
--Getcountofinsertedrows
DECLARE@inserted_countint=(
SELECTCOUNT(*)
FROMinserted
);
--Getcountofdeletedrows
DECLARE@deleted_countint=(
SELECTCOUNT(*)
FROMdeleted
);
BecausethetriggerisloggingthetypeofDMLactionthatcausedittofire(an
INSERT,aDELETE,oranUPDATEaction),itmustdeterminethetypeprogrammatically.
Thiscanbedonebyapplyingthefollowingsimplerulestothecountsofrowsfromthe
insertedanddeletedvirtualtables:
1. Ifatleastonerowwasinsertedbutnorowsweredeleted,theDML
actionwasaninsert.
2. Ifatleastonerowwasdeletedbutnorowswereinserted,theDML
actionwasadelete.
3. Ifatleastonerowwasdeletedandatleastonerowwasinserted,the
DMLactionwasanupdate.
TheserulesareappliedintheformofaCASEexpression,asshowninthefollowing:
--DeterminethetypeofDMLactionthatfiredthetrigger
SET@ActionType=CASE
WHEN(@inserted_count>0)AND(@deleted_count=0)THEN
N'insert'
WHEN(@inserted_count=0)AND(@deleted_count>0)THEN
N'delete'
ELSEN'update'
END;
ThenextstepinthetriggerusestheSELECTstatement’sFORXMLAUTOclauseto
generateXML-formattedbeforeandaftersnapshotsoftheaffectedrows.FORXML
AUTOisusefulbecauseitautomaticallyusesthesourcetablenameastheXMLelement
name—inthiscase,insertedordeleted.TheFORXMLAUTOclause
automaticallyusesthenamesofthecolumnsinthetableasXMLattributesforeach
element.Becausetheinsertedanddeletedvirtualtableshavethesamecolumn
namesasthisaffectedtable,youdon’thavetohard-codecolumnnamesintothetrigger.In
theresultingXML,the<deleted>elementsrepresentthebeforesnapshotandthe
<inserted>elementsrepresenttheaftersnapshotoftheaffectedrows:
--UseFORXMLAUTOtoretrievebeforeandaftersnapshots
ofthechanged
--datainXMLformat
SELECT@ActionXml=COALESCE
(
(
SELECT*
FROMdeleted
FORXMLAUTO
),N'<deleted/>'
)+COALESCE
(
(
SELECT*
FROMinserted
FORXMLAUTO
),N'<inserted/>'
);
TipTheDMLauditloggingtriggerwascreatedtobeflexiblesoyoucanuseitwith
minimalchangesonmosttables.However,therearesomecircumstanceswhereitmay
requiretheuseofadditionaloptionsormoreextensivechangestoworkwithagiventable.
Asanexample,ifyourtablecontainsavarbinarycolumn,youhavetousetheFOR
XMLclause’sBINARYBASE64directive(FORXML,BINARYBASE64).
Thefinalstepinthetriggerinsertsarowrepresentingtheloggedactionintothe
dbo.DmlActionLogtable.SeveralSQLServermetadatafunctions—-like@@PROCID,
OBJECT_SCHEMA_NAME(),andOBJECT_NAME(),aswellasthesys.triggers
catalogview—areusedintheINSERTstatementtodynamicallyidentifythecurrent
triggerprocedureID,andtheschemaandtablenameinformation.Also,functionslike
SUSER_SNAME(),APP_NAME(),andHOST_NAME()allowyoutoretrieveuseful
auditinformationontheexecutioncontext.Again,thismeansalmostnothingneedstobe
hard-codedintothetrigger,makingiteasiertousethetriggeronmultipletableswith
minimalchanges:
--Insertarowfortheloggedactionintheauditlogging
table
INSERTINTOdbo.DmlActionLog
(
SchemaName,
TableName,
ActionType,
ActionXml,
LoginName,
ApplicationName,
HostName
)
SELECT
OBJECT_SCHEMA_NAME(@@PROCID,DB_ID()),
OBJECT_NAME(t.parent_id,DB_ID()),
@ActionType,
@ActionXml,
SUSER_SNAME(),
APP_NAME(),
HOST_NAME()
FROMsys.triggerst
WHEREt.object_id=@@PROCID;
TipSQLServerincludesseveralmetadatafunctions,catalogviews,anddynamic
managementviewsandfunctionsthatareusefulfordynamicallyretrievinginformation
aboutdatabases,databaseobjects,andthecurrentstateoftheserver.Moreoftheseuseful
T-SQLfunctionsandviewsaredescribedasthey’reencounteredinlaterchapters.
YoucaneasilyverifythetriggerwithafewsimpleDMLstatements.Listing7-6
changesthenameoftheAdventureWorksInformationServicesdepartmenttoInformation
Technology,andtheninsertsanddeletesaCustomerServicedepartment.Theresultsare
showninFigure7-2.
Listing7-6.TestingtheDMLAuditLoggingTrigger
UPDATEHumanResources.DepartmentSETName=N'Information
Technology'
WHEREDepartmentId=11;
INSERTINTOHumanResources.Department
(
Name,
GroupName
)
VALUES
(
N'CustomerService',
N'SalesandMarketing'
);
DELETE
FROMHumanResources.Department
WHEREName=N'CustomerService';
SELECT
EntryNum,
SchemaName,
TableName,
ActionType,
ActionXml,
LoginName,
ApplicationName,
HostName,
ActionDateTime
FROMdbo.DmlActionLog;
Figure7-2.Auditloggingresults
TheFORXMLAUTO-generatedActionXmlcolumndatadeservesacloserlook.As
mentionedearlierinthissection,theFORXMLAUTOclauseautomaticallygenerates
elementandattributenamesbasedonthesourcetableandsourcecolumnnames.The
UPDATEstatementinListing7-6generatestheActionXmlentryshowninFigure7-3.
NotethattheXMLhasbeenformattedforeasierreading,butthecontenthasn’tchanged.
Figure7-3.TheActionXmlentrygeneratedbytheUPDATEstatement
SharingDatawithTriggers
Acommonlyaskedquestionis,“Howdoyoupassparameterstotriggers?”Theshort
answeris,youcan’t.Becausethey’reautomaticallyfiredinresponsetoevents,SQL
Servertriggersprovidenomeanstopassparameters.Ifyouneedtopassadditionaldatato
atrigger,youdohaveacoupleofoptionsavailable,however.Thefirstoptionistocreatea
table,whichthetriggercanthenaccessviaSELECTqueries.Theadvantageofthis
methodisthattheamountofdataatriggercanaccessiseffectivelyunlimited.A
disadvantageistheadditionaloverheadrequiredtoquerythetablewithinthetrigger.
Anotheroption,ifyouhavesmallamountsofdatatosharewithyourtriggers,istouse
theCONTEXT_INFOfunction.Youcanassignupto128bytesofvarbinarydatatothe
CONTEXT_INFOforthecurrentsessionthroughtheSETCONTEXT_INFOstatement.
Thisstatementacceptsonlyavariableorconstantvalue—nootherexpressionsare
allowed.Afteryou’vesettheCONTEXT_INFOforyoursession,youcanaccessitinyour
triggerviatheCONTEXT_INFO()function.Thedisadvantageofthismethodisthesmall
amountofdatayoucanstoreinCONTEXT_INFO.Keepthesemethodsinmind,because
youmayonedayfindthatyouneedtopassinformationintoatriggerfromabatchorSP.
NestedandRecursiveTriggers
SQLServersupportstriggersfiringothertriggersthroughtheconceptofnestedtriggers.A
nestedtriggerissimplyatriggerthatisfiredbytheactionofanothertrigger,onthesame
oradifferenttable.Triggerscanbenestedupto32levelsdeep.Weadviseagainstnesting
triggersdeeply,however,becausetheadditionallevelsofnestingaffectperformance.If
youdohavetriggersnesteddeeply,youmaywanttoreconsideryourtriggerdesign.
Nestedtriggersareturnedonbydefault,butyoucanturnthemoffwiththe
sp_configurestatement,asshowninListing7-7.
Listing7-7.TurningOffNestedTriggers
EXECsp_configure'nestedtriggers',0;
RECONFIGURE;
GO
Setthenestedtriggersoptionto1toturnnestedtriggersbackon.Thisoption
affectsonlyAFTERtriggers.INSTEADOFtriggerscanbenestedandwillexecute
regardlessofthesetting.Triggerscanalsobecalledrecursively.Therearetwotypesof
triggerrecursion:
Directrecursion:Occurswhenatriggerperformsanactionthatcauses
ittorecursivelyfireitself.
Indirectrecursion:Occurswhenatriggerfiresanothertrigger(which
canfireanothertrigger,andsoon),whicheventuallyfiresthefirst
trigger.
Directandindirectrecursionoftriggersappliesonlytotriggersofthesametype.As
anexample,anINSTEADOFtriggerthatcausesanotherINSTEADOFtriggertofireis
directrecursion.Evenifadifferenttypeoftriggerisfiredbetweenthefirstandsecond
firingofthesametrigger,it’sstillconsidereddirectrecursion.Forexample,ifoneormore
AFTERtriggersarefiredbetweenthefirstandsecondfiringsofthesameINSTEADOF
trigger,it’sstillconsidereddirectrecursion.Indirectrecursionoccurswhenatriggerofthe
sametypeiscalledbetweenfiringsofthesametrigger.
YoucanusetheALTERDATABASEstatement’sSETRECURSIVE_TRIGGERS
optiontoturndirectrecursionofAFTERtriggersonandoff,asshowninListing7-8.
TurningoffdirectrecursionofINSTEADOFtriggersrequiresthatyoualsosetthe
nestedtriggersoptionto0,asshownpreviouslyinListing7-7.
Listing7-8.TurningOffRecursiveAFTERTriggers
ALTERDATABASEAdventureWorksSETRECURSIVE_TRIGGERSOFF;
ActionstakenwithanINSTEADOFtriggerdon’tcauseittofireagain.Instead,the
INSTEADOFtriggerperformsconstraintchecksandfiresanyAFTERtriggers.Asan
example,ifanINSTEADOFUPDATEtriggeronatableisfired,andduringthecourse
ofitsexecutionitperformsanUPDATEstatementagainstthetable,theUPDATEdoesn’t
firetheINSTEADOFtriggeragain.Instead,theUPDATEstatementinitiatesconstraint-
checkoperationsandfiresAFTERtriggersonthetable.
CautionNestedandrecursivetriggersshouldbeusedwithcare,becausenestingand
recursionthataretoodeepwillcauseyourtriggerstothrowexceptions.Youcanusethe
TRIGGER_NESTLEVEL()functiontodeterminethecurrentlevelofrecursionfrom
withinatrigger.
TheUPDATE()andCOLUMNS_UPDATED()
Functions
Triggerscantakeadvantageoftwosystemfunctions,UPDATE()and
COLUMNS_UPDATED(),totellyouwhichcolumnsareaffectedbytheINSERTor
UPDATEstatementthatfiresthetriggerinthefirstplace.UPDATE()takesthenameofa
columnasaparameterandreturnstrueifthecolumnisupdatedorinserted,andfalse
otherwise.COLUMNS_UPDATED()returnsabitpatternindicatingwhichcolumnsare
affectedbytheINSERTorUPDATEstatement.
InthecaseofanUPDATE,affectedmeansthecolumnispresentinthestatement,not
thatthevalueofthecolumneffectivelychanged.Thereisonlyonewaytoknowifthe
valueofacolumnreallychanged:bycomparingthecontentofthedeletedand
insertedvirtualtables.Youcanadaptthefollowingqueryexampletodothatwithyour
trigger:
SELECTi.ProductId,d.ColorasOldColor,i.ColorasNewColor
FROMdeletedasd
JOINinsertedasiONd.ProductId=i.ProductId
ANDCOALESCE(d.Color,'')<>COALESCE(i.Color,'');
Thisfragmentisdesignedtobepartofatriggerthatcouldbecreatedonthe
Production.Producttable.TheJOINconditionassociateslinesfromthedeleted
andinsertedtablesontheprimarykeycolumnandaddsanon-equijoincondition
(joiningondifferenceratherthanonequivalence)ontheColorcolumn,tokeeponly
rowswheretheColorvaluewaschanged.TheCOALESCE()functionallowsyouto
takeintoaccountthepossibilityofaNULLbeingpresentinthepreviousornewvalue.
GettingbacktotheUPDATE()andCOLUMNS_UPDATED()functions,theexample
triggerinListing7-9demonstratestheuseoftriggerstoenforcebusinessrules.Inthis
example,thetriggerusestheUPDATEfunctiontodeterminewhetherSizeor
SizeUnitMeasureCodehasbeenaffectedbyanINSERTorUPDATEstatement.If
eitherofthesecolumnsisaffectedbyanINSERTorUPDATEstatement,thetrigger
checkswhetherarecognizedSizeUnitMeasureCodewasused.Ifso,thetrigger
convertsSizetocentimeters.ThetriggerrecognizesseveralSizeUnitMeasureCode
values,includingcentimeters(CM),millimeters(MM),andinches(IN).
Listing7-9.TriggertoEnforceStandardSizes
CREATETRIGGERProduction.ProductEnforceStandardSizes
ONProduction.Product
AFTERINSERT,UPDATE
NOTFORREPLICATION
AS
BEGIN
--Makesureatleastonerowwasaffectedandeitherthe
Sizeor
--SizeUnitMeasureCodecolumnwaschanged
IF(@@ROWCOUNT>0)AND(UPDATE(SizeUnitMeasureCode)OR
UPDATE(Size))
BEGIN
--Eliminate"rowsaffected"messages
SETNOCOUNTON;
--OnlyacceptrecognizedunitsofmeasureorNULL
IFEXISTS
(
SELECT1
FROMinserted
WHERENOT
(SizeUnitMeasureCodeIN(N'M',N'DM',N'CM',
N'MM',N'IN')
ORSizeUnitMeasureCodeISNULL
)
)
BEGIN
--Iftheunitofmeasurewasn'trecognizedraise
anerrorandrollback
--thetransaction
RAISERROR('InvalidSizeUnitMeasureCode.',10,
127);
ROLLBACKTRANSACTION;
END
ELSE
BEGIN
--Iftheunitofmeasureisarecognizedunitof
measurethensetthe
--SizeUnitMeasureCodetocentimetersandperform
theSizeconversion
UPDATEProduction.Product
SETSizeUnitMeasureCode=CASE
WHENProduction.Product.SizeUnitMeasureCode
ISNULLTHENNULLELSEN'CM'END,
Size=CAST(
CAST(CAST(i.SizeASfloat)*
CASEi.SizeUnitMeasureCode
WHENN'M'THEN100.0
WHENN'DM'THEN10.0
WHENN'CM'THEN1.0
WHENN'MM'THEN0.10
WHENN'IN'THEN2.54
END
ASint
)ASnvarchar(5)
)
FROMinsertedi
WHEREProduction.Product.ProductID=i.ProductID
ANDi.SizeUnitMeasureCodeISNOTNULL;
END;
END;
END;
GO
Thefirstpartofthetriggerdefinitiongivesthetriggeritsname,
Production.ProductEnforceStandardSizes,andcreatesitonthe
Production.Producttable.It’sspecifiedasanAFTERINSERT,UPDATEtrigger
andisdeclaredasNOTFORREPLICATION:
CREATETRIGGERProduction.ProductEnforceStandardSizes
ONProduction.Product
AFTERINSERT,UPDATE
NOTFORREPLICATION
Thecodeinthebodyofthetriggerimmediatelychecks@@ROWCOUNTtomakesureat
leastonerowwasaffectedbytheDMLstatementthatfiredthetrigger,andusesthe
UPDATEfunctiontoensurethattheSizeandSizeUnitMeasureCodecolumnswere
affectedbytheDMLstatement:
IF(@@ROWCOUNT>0)
AND(UPDATE(SizeUnitMeasureCode)ORUPDATE(Size))BEGIN
•••
END;
Oncethetriggerhasverifiedthatatleastonerowwasaffectedandtheappropriate
columnsweremodified,thetriggersetsNOCOUNTONtopreventtherowsaffected
messagesfrombeinggeneratedbythetrigger.TheIFEXISTSstatementchecksto
makesurevalidunit-of-measurecodesareused.Ifnot,thetriggerraisesanerrorandrolls
backthetransaction:
--Eliminate"rowsaffected"messages
SETNOCOUNTON;
--OnlyacceptrecognizedunitsofmeasureorNULL
IFEXISTS
(
SELECT1
FROMinserted
WHERENOT
(SizeUnitMeasureCodeIN(N'M',N'DM',N'CM',N'MM',
N'IN')
ORSizeUnitMeasureCodeISNULL
)
)
BEGIN
--Iftheunitofmeasurewasn'trecognizedraisean
errorandrollback
--thetransaction
RAISERROR('InvalidSizeUnitMeasureCode.',10,127);
ROLLBACKTRANSACTION;
END
TipTheROLLBACKTRANSACTIONstatementinthetriggerrollsbackthe
transactionandpreventsfurthertriggersfrombeingfiredbythecurrenttrigger.Twoerror
messagesarereceivedbytheclient:theoneraisedbyRAISERROR(),andtheerror3609
or3616,warningthatthetransactionendedinthetrigger.
Iftheunit-of-measurevalidationispassed,SizeUnitMeasureCodeissetto
centimetersandSizeisconvertedtocentimetersforeachinsertedorupdatedrow:
BEGIN
--Iftheunitofmeasureisarecognizedunitofmeasure
thensetthe
--SizeUnitMeasureCodetocentimetersandperformthe
Sizeconversion
UPDATEProduction.Product
SETSizeUnitMeasureCode=CASE
WHENProduction.Product.SizeUnitMeasureCodeIS
NULLTHENNULLELSEN'CM'END,
Size=CAST(
CAST(CAST(i.SizeASfloat)*
CASEi.SizeUnitMeasureCode
WHENN'M'THEN100.0
WHENN'DM'THEN10.0
WHENN'CM'THEN1.0
WHENN'MM'THEN0.10
WHENN'IN'THEN2.54
END
ASint
)ASnvarchar(5)
)
FROMinsertedi
WHEREProduction.Product.ProductID=i.ProductID
ANDi.SizeUnitMeasureCodeISNOTNULL;
END;
Thistriggerenforcessimplebusinesslogicbyensuringthatstandard-sizecodesare
usedwhenupdatingtheProduction.ProducttableandconvertingtheSizevalues
tocentimeters.Totestthetrigger,youcanperformupdatesofexistingrowsinthe
Production.Producttable.Listing7-10updatesthesizesoftheproductswith
ProductID680and780to600millimetersand22.85inches,respectively.Theresults,
withtheSizevaluesautomaticallyconvertedtocentimeters,areshowninFigure7-4.
Listing7-10.TestingtheTriggerbyAddingaNewProduct
UPDATEProduction.Product
SETSize=N'600',
SizeUnitMeasureCode=N'MM'
WHEREProductId=680;
UPDATEProduction.Product
SETSize=N'22.85',
SizeUnitMeasureCode=N'IN'
WHEREProductId=706;
SELECTProductID,
Name,
ProductNumber,
Size,
SizeUnitMeasureCode
FROMProduction.Product
WHEREProductIDIN(680,706);
Figure7-4.ResultsoftheProduction.ProductEnforceStandardSizestriggertest
WhereastheUPDATE()functionacceptsacolumnnameandreturnstrueifthe
columnisaffected,theCOLUMNS_UPDATED()functionacceptsnoparametersand
returnsavarbinaryvaluewithasinglebitrepresentingeachcolumn.Youcanusethe
bitwiseANDoperator(&)andabitmasktotestwhichcolumnsareaffected.Thebitsare
setfromlefttoright,basedontheColumnIDnumberofthecolumnsfromthe
sys.columnscatalogviewortheCOLUMNPROPERTY()function.
CautionThepositionofCOLUMNS_UPDATED()isnotthesameasthe
ORDINAL_POSITIONvaluefoundintheINFORMATION_SCHEMA.COLUMNScatalog
view.Relyonthesys.columns.ColumnIDvalueinstead.
Tocreateabitmask,youmustuse20(1)torepresentthefirstcolumn,21(2)to
representthesecondcolumn,andsoon.BecauseCOLUMNS_UPDATED()returnsa
varbinaryresult,thecolumnindicatorbitscanbespreadoutoverseveralbytes.Totest
columnsbeyondthefirsteight,liketheSizeandSizeUnitMeasureCodecolumnsin
theexamplecode(columns11and12),youcanusetheSUBSTRINGfunctiontoreturn
thesecondbyteofCOLUMNS_UPDATED()andtesttheappropriatebitswithabitmask
of12(12=22+23).TheexampletriggerinListing7-9canbemodifiedtousethe
COLUMNS_UPDATED()function,asshownhere:
IF(@@ROWCOUNT>0)AND(SUBSTRING(COLUMNS_UPDATED(),2,1)
&12<>0x00)
TheCOLUMNS_UPDATED()functionwon’treturncorrectresultsiftheColumnID
valuesofthetablearechanged.Ifthetableisdroppedandre-createdwithcolumnsina
differentorder,youneedtochangethetriggersthatuseCOLUMNS_UPDATED()to
reflectthechanges.Theremaybespecializedinstancesinwhichyoucantakeadvantage
oftheCOLUMNS_UPDATED()functionality,butingeneralweadviseagainstusing
COLUMNS_UPDATED():instead,usetheUPDATE()functiontodeterminewhich
columnswereaffectedbytheDMLstatementthatfiredyourtrigger.
TriggersonViews
Althoughyoucan’tcreateAFTERtriggersonviews,SQLServerdoesallowyoutocreate
INSTEADOFtriggersonyourviews.Atriggercanbeusefulforupdatingviewsthatare
otherwisenon-updatable,suchasviewswithmultiplebasetablesorviewsthatcontain
aggregatefunctions.INSTEADOFtriggersonviewsalsogiveyoufine-grainedcontrol,
becauseyoucancontrolwhichcolumnsoftheviewareupdatablethroughthetrigger.The
AdventureWorksdatabasecomeswithaviewnamedSales.vSalesPerson,whichis
formedbyjoining11separatetablestogether.TheINSTEADOFtriggerinListing7-11
allowsyoutoupdatespecificcolumnsoftwoofthebasetablesusedintheviewby
executingUPDATEstatementsdirectlyagainsttheview.
Listing7-11.INSTEADOFTriggeronaView
CREATETRIGGERSales.vIndividualCustomerUpdate
ONSales.vIndividualCustomer
INSTEADOFUPDATE
NOTFORREPLICATION
AS
BEGIN
--Firstmakesureatleastonerowwasaffected
IF@@ROWCOUNT=0RETURN
--Turnoff"rowsaffected"messages
SETNOCOUNTON;
--Initializeaflagtoindicateupdatesuccess
DECLARE@UpdateSuccessfulbit=0;
--Checkforupdatablecolumnsinthefirsttable
IFUPDATE(FirstName)ORUPDATE(MiddleName)OR
UPDATE(LastName)
BEGIN
--Updatecolumnsinthebasetable
UPDATEPerson.Person
SETFirstName=i.FirstName,
MiddleName=i.MiddleName,
LastName=i.LastName
FROMinsertedi
WHEREi.BusinessEntityID
=Person.Person.BusinessEntityID;
--Setflagtoindicatesuccess
SET@UpdateSuccessful=1;
END;
--Ifupdatablecolumnsfromthesecondtablewere
specified,updatethose
--columnsinthebasetable
IFUPDATE(EmailAddress)
BEGIN
--Updatecolumnsinthebasetable
UPDATEPerson.EmailAddress
SETEmailAddress=i.EmailAddress
FROMinsertedi
WHEREi.BusinessEntityID
=Person.EmailAddress.BusinessEntityID;
--Setflagtoindicatesuccess
SET@UpdateSuccessful=1;
END;
--Iftheupdatewasnotsuccessful,raiseanerrorand
rollbackthe
--transaction
IF@UpdateSuccessful=0
RAISERROR('Mustspecifyupdatablecolumns.',10,127);
END;
GO
ThetriggerinListing7-11iscreatedasanINSTEADOFUPDATEtriggeronthe
Sales.vIndividualCustomerview,asshownhere:
CREATETRIGGERSales.vIndividualCustomerUpdate
ONSales.vIndividualCustomer
INSTEADOFUPDATE
NOTFORREPLICATION
Aswiththepreviousexamplesinthischapter,thistriggerbeginsbychecking
@@ROWCOUNTtoensurethatatleastonerowwasupdated:
--Firstmakesureatleastonerowwasaffected
IF@@ROWCOUNT=0RETURN;
OncethetriggerverifiesthatoneormorerowswereaffectedbytheDMLstatement
thatfiredthetrigger,itturnsofftherowsaffectedmessagesandinitializesaflagto
indicatesuccessorfailureoftheupdateoperation:
--Turnoff"rowsaffected"messages
SETNOCOUNTON;
--Initializeaflagtoindicateupdatesuccess
DECLARE@UpdateSuccessfulbit=0;
Thetriggerthencheckstoseewhetherthecolumnsdesignatedasupdatablewere
affectedbytheUPDATEstatement.IfthepropercolumnswereaffectedbytheUPDATE
statement,thetriggerperformsupdatesontheappropriatebasetablesfortheview.For
purposesofthisdemonstration,thecolumnsthatareupdatablebythetriggerarethe
FirstName,MiddleName,andLastNamecolumnsfromthePerson.Person
table,andtheEmailAddresscolumnfromthePerson.EmailAddresscolumn:
--Checkforupdatablecolumnsinthefirsttable
IFUPDATE(FirstName)ORUPDATE(MiddleName)OR
UPDATE(LastName)
BEGIN
--Updatecolumnsinthebasetable
UPDATEPerson.Person
SETFirstName=i.FirstName,
MiddleName=i.MiddleName,
LastName=i.LastName
FROMinsertedi
WHEREi.BusinessEntityID
=Person.Person.BusinessEntityID;
--Setflagtoindicatesuccess
SET@UpdateSuccessful=1;
END;
--Ifupdatablecolumnsfromthesecondtablewere
specified,updatethose
--columnsinthebasetable
IFUPDATE(EmailAddress)BEGIN
--Updatecolumnsinthebasetable
UPDATEPerson.EmailAddress
SETEmailAddress=i.EmailAddress
FROMinsertedi
WHEREi.BusinessEntityID
=Person.EmailAddress.BusinessEntityID;
--Setflagtoindicatesuccess
SET@UpdateSuccessful=1;
END;
Finally,ifnoupdatablecolumnswerespecifiedbytheUPDATEstatementthatfired
thetrigger,anerrorisraisedandthetransactionisrolledback:
--Iftheupdatewasnotsuccessful,raiseanerrorandroll
backthe
--transaction
IF@UpdateSuccessful=1
RAISERROR('Mustspecifyupdatablecolumns.',10,127);
Listing7-12demonstratesasimpleUPDATEagainstthe
Sales.vIndividualCustomerviewwiththeINSTEADOFtriggerfromListing7-
11createdonit.TheresultisshowninFigure7-5.
Listing7-12.UpdatingaViewUsinganINSTEADOFTrigger
UPDATESales.vIndividualCustomer
SETFirstName=N'Dave',
MiddleName=N'Robert',
EmailAddress=N'dave.robinett@adventure-works.com'
WHEREBusinessEntityID=1699;
SELECTBusinessEntityID,FirstName,MiddleName,LastName,
EmailAddress
FROMSales.vIndividualCustomer
WHEREBusinessEntityID=1699;
Figure7-5.ResultoftheINSTEADOFtriggerviewupdate
DDLTriggers
SinceSQLServer2005,T-SQLprogrammershavehadtheabilitytocreateDDLtriggers
thatfirewhenDDLeventsoccurinadatabaseorontheserver.Thissectiondiscusses
DDLtriggers,theeventsthatfirethem,andtheirpurpose.TheformatoftheCREATE
TRIGGERstatementforDDLtriggersisonlyslightlydifferentfromtheDMLtrigger
syntax,withthemajordifferencebeingthatyoumustspecifythescopeforthetrigger:
eitherALLSERVERorDATABASE.TheDATABASEscopecausestheDDLtriggerto
fireifaneventofaspecifiedeventtypeoreventgroupoccursinthedatabaseinwhichthe
triggerwascreated.ALLSERVERscopecausestheDDLtriggertofireifaneventofthe
specifiedeventtypeoreventgroupoccursanywhereonthecurrentserver.
DDLtriggerscanonlybespecifiedasFORorAFTER(there’snoINSTEADOF-type
DDLtrigger).TheeventtypesthatcanfireaDDLtriggerarelargelyoftheformCREATE,
ALTER,DROP,GRANT,DENY,orREVOKE.SomesystemSPsthatperformDDL
functionsalsofireDDLtriggers.TheALTERTRIGGER,DROPTRIGGER,DISABLE
TRIGGER,andENABLETRIGGERstatementsworkforDDLtriggersjustastheydofor
DMLtriggers.
DDLtriggersareusefulwhenyouwanttopreventchangestoyourdatabase,perform
actionsinresponsetoachangeinthedatabase,orauditchangestothedatabase.Which
DDLstatementscanfireaDDLtriggerdependsonthescopeofthetrigger.
DDLEventTypesandEventGroups
DDLtriggerscanfireinresponsetoawidevarietyofeventtypesandeventgroups,
scopedateitherthedatabaseorserverlevel.TheeventsthatfireDDLtriggersarelargely
DDLstatementslikeCREATEandDROP,andDataControlLanguage(DCL)statements
likeGRANTandDENY.EventgroupsformahierarchicalstructureofDDLeventsin
logicalgroupings,likeDDL_FUNCTION_EVENTSandDDL_PROCEDURE_EVENTS.
EventgroupsallowyoutofiretriggersinresponsetoawiderangeofDDLevents.
BOLhascompletelistingsofallavailableDDLtriggereventtypesandeventgroups,
sotheyaren’treproducedfullyhere.Justkeepinmindthatyoucanfiretriggersin
responsetomostT-SQLDDLandDCLstatements.Youcanalsoquerythe
sys.trigger_event_typescatalogviewtoretrieveavailableDDLevents.
WithDDLtriggers,youcanspecifyeitheraneventtypeoraneventgroup,thelatterof
whichcanencompassmultipleeventsorothereventgroups.Ifyouspecifyanevent
group,anyeventsincludedinthatgroup,orinthesubgroupsofthatgroup,willfirethe
DDLtrigger.
NoteCreatingaDDLtriggerwithALLSERVERscoperequiresCONTROLSERVER
permissionontheserver.CreatingaDDLtriggerwithDATABASEscoperequiresALTER
ANYDATABASEDDLTRIGGERpermissions.
OncetheDDLtriggerfires,youcanaccessmetadataabouttheeventthatfiredthe
triggerwiththeEVENTDATA()function.EVENTDATA()returnsinformationsuchasthe
time,connection,objectname,andtypeofeventthatfiredthetrigger.Theresultsare
returnedasaSQLServerxmldatatypeinstance.Listing7-13showsasampleofthetype
ofdatareturnedbytheEVENTDATAfunction.
Listing7-13.EVENTDATA()FunctionExampleData
<EVENT_INSTANCE>
<EventType>CREATE_TABLE</EventType>
<PostTime>2012-04-21T17:08:28.527</PostTime>
<SPID>115</SPID>
<ServerName>SQL2012</ServerName>
<LoginName>SQL2O12\Rudi</LoginName>
<UserName>dbo</UserName>
<DatabaseName>AdventureWorks</DatabaseName>
<SchemaName>dbo</SchemaName>
<ObjectName>MyTable</ObjectName>
<ObjectType>TABLE</ObjectType>
<TSQLCommand>
<SetOptions
ANSI_NULLS="ON"ANSI_NULL_DEFAULT="ON"ANSI_PADDING="ON"QUOTED_IDENTIFIER="ON"ENCRYPTED="FALSE"
/>
<CommandText>CREATETABLEdbo.MyTable(iint);
</CommandText>
</TSQLCommand>
</EVENT_INSTANCE>
Youcanusethexmldatatype’svalue()methodtoretrievespecificnodesfromthe
result.TheexampleDDLtriggerinListing7-14createsaDDLtriggerthatfiresin
responsetotheCREATETABLEstatementintheAdventureWorksdatabase.Itlogsthe
eventdatatoatablenameddbo.DdlActionLog.
Listing7-14.CREATETABLEDDLTriggerExample
--CreateatabletologDDLCREATETABLEactions
CREATETABLEdbo.DdlActionLog
(
EntryIdintNOTNULLIDENTITY(1,1)PRIMARYKEY,
EventTypenvarchar(200)NOTNULL,
PostTimedatetimeNOTNULL,
LoginNamesysnameNOTNULL,
UserNamesysnameNOTNULL,
ServerNamesysnameNOTNULL,
SchemaNamesysnameNOTNULL,
DatabaseNamesysnameNOTNULL,
ObjectNamesysnameNOTNULL,
ObjectTypesysnameNOTNULL,
CommandTextnvarchar(max)NOTNULL
);
GO
CREATETRIGGERAuditCreateTable
ONDATABASE
FORCREATE_TABLE
AS
BEGIN
--AssigntheXMLeventdatatoanxmlvariable
DECLARE@eventdataxml=EVENTDATA();
--ShredtheXMLeventdataandinsertarowinthelog
table
INSERTINTOdbo.DdlActionLog
(
EventType,
PostTime,
LoginName,
UserName,
ServerName,
SchemaName,
DatabaseName,
ObjectName,
ObjectType,
CommandText
)
SELECT
EventNode.value(N'EventType[1]',N'nvarchar(200)'),
EventNode.value(N'PostTime[1]',N'datetime'),
EventNode.value(N'LoginName[1]',N'sysname'),
EventNode.value(N'UserName[1]',N'sysname'),
EventNode.value(N'ServerName[1]',N'sysname'),
EventNode.value(N'SchemaName[1]',N'sysname'),
EventNode.value(N'DatabaseName[1]',N'sysname'),
EventNode.value(N'ObjectName[1]',N'sysname'),
EventNode.value(N'ObjectType[1]',N'sysname'),
EventNode.value(N'(TSQLCommand/CommandText)[1]',
'nvarchar(max)')
FROM@eventdata.nodes('/EVENT_INSTANCE')
EventTable(EventNode);
END;
GO
ThefirstpartoftheexampleinListing7-14createsasimpletabletostoretheevent-
specificdatageneratedbyeventsthatfiretheDDLtrigger:
--CreateatabletologDDLCREATETABLEactions
CREATETABLEdbo.DdlActionLog
(
EntryIdintNOTNULLIDENTITY(1,1)PRIMARYKEY,
EventTypenvarchar(200)NOTNULL,
PostTimedatetimeNOTNULL,
LoginNamesysnameNOTNULL,
UserNamesysnameNOTNULL,
ServerNamesysnameNOTNULL,
SchemaNamesysnameNOTNULL,
DatabaseNamesysnameNOTNULL,
ObjectNamesysnameNOTNULL,
ObjectTypesysnameNOTNULL,
CommandTextnvarchar(max)NOTNULL
);
GO
TheDDLtriggerdefinitionbeginswiththename,thescope(DATABASE),andthe
DDLactionthatfiresthetrigger.Inthisexample,theactionthatfiresthetriggeristhe
CREATETABLEevent.NoticethatunlikeDMLtriggers,DDLtriggersdon’tbelongto
schemasanddon’thaveschemasspecifiedintheirnames:
CREATETRIGGERAuditCreateTable
ONDATABASE
FORCREATE_TABLE
Thebodyofthetriggerbeginsbydeclaringanxmlvariable,@eventdata.This
variableholdstheresultsoftheEVENTDATA()functionforfurtherprocessinglaterin
thetrigger:
--AssigntheXMLeventdatatoanxmlvariable
DECLARE@eventdataxml=EVENTDATA();
Next,thetriggerusesthenodes()andvalue()methodsofthe@eventdata
xmlvariabletoshredtheeventdata,whichistheninsertedintothe
dbo.DdlActionLogtableinrelationalform:
--ShredtheXMLeventdataandinsertarowinthelog
table
INSERTINTOdbo.DdlActionLog
(
EventType,
PostTime,
LoginName,
UserName,
ServerName,
SchemaName,
DatabaseName,
ObjectName,
ObjectType,
CommandText
)
SELECT
EventNode.value(N'EventType[1]',N'nvarchar(200)'),
EventNode.value(N'PostTime[1]',N'datetime'),
EventNode.value(N'LoginName[1]',N'sysname'),
EventNode.value(N'UserName[1]',N'sysname'),
EventNode.value(N'ServerName[1]',N'sysname'),
EventNode.value(N'SchemaName[1]',N'sysname'),
EventNode.value(N'DatabaseName[1]',N'sysname'),
EventNode.value(N'ObjectName[1]',N'sysname'),
EventNode.value(N'ObjectType[1]',N'sysname'),
EventNode.value(N'(TSQLCommand/CommandText)[1]',
'nvarchar(max)')
FROM@eventdata.nodes('/EVENT_INSTANCE')
EventTable(EventNode);
Listing7-15demonstratestheDDLtriggerbyperformingaCREATETABLE
statement.PartialresultsareshowninFigure7-6.
Listing7-15.TestingtheDDLTriggerwithaCREATETABLEStatement
CREATETABLEdbo.MyTable(iint);
GO
SELECT
EntryId,
EventType,
UserName,
ObjectName,
CommandText
FROMDdlActionLog;
Figure7-6.DDLauditloggingresults
DroppingaDDLtriggerisassimpleasexecutingtheDROPTRIGGERstatement,as
showninListing7-16.NoticethattheONDATABASEclauseisrequiredinthisinstance.
ThereasonisthattheDDLtriggerexistsoutsidetheschemasofthedatabase,soyoumust
tellSQLServerwhetherthetriggerexistsatthedatabaseorserverscope.
Listing7-16.DroppingaDDLTrigger
DROPTRIGGERAuditCreateTable
ONDATABASE;
LogonTriggers
SQLServeroffersyetanothertypeoftrigger:thelogontrigger.Logontriggerswerefirst
madeavailableinSQLServer2005SP2.ThesetriggersfireinresponsetoanSQLServer
LOGONevent—afterauthenticationsucceeds,butbeforetheusersessionisestablished.
YoucanperformtasksrangingfromsimpleLOGONeventauditingtomoreadvancedtasks
likerestrictingthenumberofsimultaneoussessionsforaloginordenyinguserstheability
tocreatesessionsatcertaintimes.
Thecodeexampleforthissectionuseslogontriggerstodenyagivenusertheability
tologintoSQLServerduringaspecifiedtimeperiod(forexample,duringaresource-
intensivenightlybatchprocess).Listing7-17beginsbycreatingasampleloginandatable
thatholdsalogon-denialschedule.Thefirstentryinthistablewillbeusedtodenythe
examplelogintheabilitytologintoSQLServerbetweenthehoursof9:00and11:00
p.m.onSaturdaynights.
Listing7-17.CreatingaTestLoginandLogon-DenialSchedule
CREATELOGINPublicUserWITHPASSWORD='p@$$w0rd';
GO
USEMaster;
CREATETABLEdbo.DenyLogonSchedule(
UserIdsysnameNOTNULL,
DayOfWeektinyintNOTNULL,
TimeStarttimeNOTNULL,
TimeEndtimeNOTNULL,
PRIMARYKEY(UserId,DayOfWeek,TimeStart,TimeEnd)
);
GO
INSERTINTOdbo.DenyLogonSchedule(
UserId,
DayOfWeek,
TimeStart,
TimeEnd
)VALUES(
'PublicUser',
7,
'21:00:00',
'23:00:00'
);
ThelogontriggerthatusesthistabletodenylogonsonascheduleisshowninListing
7-18.
Listing7-18.ExampleLogonTrigger
USEMaster;
CREATETRIGGERDenyLogons
ONALLSERVER
WITHEXECUTEAS'sa'
FORLOGON
AS
BEGIN
IFEXISTS(SELECT1
FROMMaster.dbo.DenyLogonSchedule
WHEREUserId=ORIGINAL_LOGIN()
ANDDayOfWeek=DATEPART(WeekDay,GETDATE())
ANDCAST(GETDATE()ASTIME)BETWEENTimeStartAND
TimeEnd
)BEGIN
ROLLBACKTRANSACTION;
END;
END;
CautionIfyourlogontriggererrorsout,youcan’tlogonintoSQLServernormally.
YoucanstillconnectusingtheDedicatedAdministratorConnection(DAC),which
bypasseslogontriggers.Makesurethetabledbo.DenyLogonScheduleexistsand
thatyourlogontriggerworksproperlybeforeputtingitinproduction.
TheCREATETRIGGERstatementbeginsmuchliketheothertriggerexamples
you’veusedtothispoint,byspecifyingthenameandscope(ALLSERVER).TheWITH
EXECUTEclauseisusedtospecifythatthelogontriggershouldrununderthesasecurity
context,andtheFORLOGONclauseindicatesthatthisisactuallyalogontrigger:
CREATETRIGGERDenyLogons
ONALLSERVER
WITHEXECUTEAS'sa'
FORLOGON
Thetriggerbodyisfairlysimple.Itchecksfortheexistenceofanentryinthe
AdventureWorks.dbo.DenyLogonScheduletable,indicatingthatthecurrent
user(retrievedwiththeORIGINAL_LOGIN()function)isdeniedloginbasedonthe
currentdateandtime.Ifthereisanentryindicatingthattheloginshouldbedenied,then
theROLLBACKTRANSACTIONstatementisexecuted,denyingthelogin:
IFEXISTS(SELECT1
FROMAdventureWorks.dbo.DenyLogonSchedule
WHEREUserId=ORIGINAL_LOGIN()
ANDDayOfWeek=DATEPART(WeekDay,GETDATE())
ANDCAST(GETDATE()ASTIME)BETWEENTimeStartANDTimeEnd
)BEGIN
ROLLBACKTRANSACTION;
END;
Noticethatthethree-partnameofthetableisusedinthisstatement,becausetheuser
attemptingtologinmaybeconnectingtoadifferentdefaultdatabase.Attemptingtolog
intoSQLServerusingthePublicUseraccountonSaturdaynightbetweenthehours
indicatedresultsinanerrormessageliketheoneshowninFigure7-7.
Figure7-7.Alogontriggerdenyingalogin
TipLogontriggersareusefulforauditingandrestrictinglogins,butbecausetheyonly
fireafterasuccessfulauthentication,theycan’tbeusedtologunsuccessfulloginattempts.
ThelogontriggeralsomakeslogoninformationavailableinXMLformatinthetrigger
viatheEVENTDATA()function.Anexampleofthelogoninformationgeneratedbythe
LOGONeventisshowninListing7-19.
Listing7-19.ExampleEventDataGeneratedbyaLOGONEvent
<EVENT_INSTANCE>
<EventType>LOGON</EventType>
<PostTime>2012-04-21T23:18:33.357</PostTime>
<SPID>110</SPID>
<ServerName>SQL2012</ServerName>
<LoginName>PublicUser</LoginName>
<LoginType>SQLLogin</LoginType>
<SID>zgPcN6UCBE2j/HYTug0i4A==</SID>
<ClientHost><localmachine></ClientHost>
<IsPooled>0</IsPooled>
</EVENT_INSTANCE>
NoteLogontriggerstodenyaccesstologinsbasedondayofweek,timeofday,and
numberofsessionsperloginareavailableintheCommonCriteriacompliancepackage
forSQLServer.YoucandownloadthemontheSQLServerCommonCriteria
Certificationswebsite:http://msdn.microsoft.com/en-
us/library/bb326650.aspx.
Summary
Thischapterdiscussedtriggers,includingtraditionalDMLtriggers,DDLtriggers,and
logontriggers.Asyou’veseen,triggersareusefultoolsforavarietyofpurposes.
DMLtriggersaretheoriginalformoftrigger.MuchofthefunctionalitythatDML
triggerswereusedforinthepast,suchasenforcingreferentialintegrity,hasbeen
supplantedbynewerandmoreefficientT-SQLfunctionalityovertheyears,likecascading
DRI.DMLtriggersareusefulforauditingDMLstatementsandforenforcingcomplex
businessrulesandlogicinthedatabase.Theycanalsobeusedtoimplementupdatingfor
viewsthatnormallyaren’tupdatable.
Thischapterdiscussedtheinsertedanddeletedvirtualtables,whichhold
copiesoftherowsbeingaffectedbyaDMLstatement.YoualsosawtheUPDATE()and
COLUMNS_UPDATED()functionsinDMLtriggers,whichidentifythecolumnsaffected
bytheDMLstatementthatfiredatrigger.Finally,youlearnedaboutthedifferences
betweenAFTERandINSTEADOFtriggersalongwithnestedtriggersandtrigger
recursion.
DDLtriggerscanbeusedtoauditandrestrictdatabaseobjectandserverchanges.
DDLtriggerscanhelpprovideprotectionagainstaccidentalormaliciouschangesto,or
destructionof,databaseobjects.ThischapterdiscussedtheEVENTDATA()functionand
howyoucanuseittoauditDDLactionsinadatabaseorontheserver.
Logontriggerscanlikewisebeusedtoauditsuccessfulloginsandrestrictloginsfor
variousreasons.
ThenextchapterdiscussesthenativeencryptionfunctionalityavailableinSQLServer
2014.
EXERCISES
1. [True/False]TheEVENTDATA()functionreturnsinformation
aboutDDLeventswithinDDLtriggers.
2. [True/False]InaDMLtrigger,theinsertedanddeleted
virtualtablesarebothpopulatedwithrowsduringanUPDATE
event.
3. [True/False]DMLtriggersareavailableonin-memorytablesand
disk-basedtables.
4. [Chooseallthatapply]Whichofthefollowingtypesoftriggers
doesSQLServer2014support?
Logontriggers
TCLtriggers
DDLtriggers
Hierarchytriggers
DMLtriggers
5. [Fillintheblank]The___________statementpreventstriggers
fromgeneratingextraneousrowsaffectedmessages.
6. [Chooseone]TheCOLUMNS_UPDATED()functionreturnsdatain
whichofthefollowingformats?
Avarbinarystringwithbitssettorepresentaffected
columns
Acomma-delimitedvarcharstringwithacolumnID
numberforeachaffectedcolumn
AtableconsistingofcolumnIDnumbersforeachaffected
column
AtableconsistingofallrowsthatwereinsertedbytheDML
operation
7. [True/False]@@ROWCOUNT,whenusedatthebeginningofaDML
trigger,reflectsthenumberofrowsaffectedbytheDMLstatement
thatfiredthetrigger.
8. [True/False]YoucancreaterecursiveAFTERtriggersonviews.
CHAPTER8
Encryption
SQLServer2014supportsbuilt-incolumn-anddatabase-levelencryptionfunctionality
directlythroughT-SQL.Column-levelencryptionallowsyoutoencryptthedatainyour
databaseatthecolumnlevel.BackinthedaysofSQLServer2000(andbefore),youhad
toturntothird-partytoolsorwriteyourownextendedstoredprocedures(XPs)toencrypt
sensitivedata.Evenwiththesetoolsinplace,subparimplementationofvariousaspectsof
thesystem,suchasencryptionkeymanagement,couldleavemanysystemsina
vulnerablestate.
SQLServer2014’sencryptionmodeltakesadvantageoftheWindowsCryptoAPIto
secureyourdata.Withbuilt-inencryptionkeymanagementandfacilitiestohandle
encryption,decryption,andone-wayhashingthroughT-SQLstatements,SQLServer2014
providesusefultoolsforefficientandsecuredataencryption.SQLServer2014also
supportstwoencryptionoptions:transparentdataencryption(TDE)forsupporting
encryptionofanentiredatabase;andextensiblekeymanagement(EKM),whichallows
youtousethird-partyhardware-basedencryptionkeymanagementandencryption
acceleration.
ThischapterdiscussesSQLServer2014’sbuilt-incolumn-levelencryptionand
decryptionfunctionality,keymanagementcapabilities,one-wayhashingfunctions,and
TDEandEKMfunctionality.
TheEncryptionHierarchy
SQLServer2014offersalayeredapproachtoencryptionkeymanagementbyallowing
severallevelsofkey-encryptingkeysbetweenthetop-levelmasterkeyandthelowest-
leveldata-encryptingkeys.SQLServeralsoallowsforencryptionbycertificates,
symmetrickeys,andasymmetrickeys.TheSQLServer2014encryptionmodelis
hierarchical,asshowninFigure8-1.
Figure8-1.SQLServer2014encryptionhierarchy
AtthetopoftheSQLServer2014encryptionhierarchyistheWindowsData
ProtectionAPI(DPAPI),whichisusedtoprotectthegranddaddyofallSQLServer2014
encryptionkeys:theservicemasterkey(SMK).TheSMKisautomaticallygeneratedby
SQLServerthefirsttimeit’sneededtoencryptanotherkey.ThereisonlyoneSMKper
SQLServerinstance,anditdirectlyorindirectlysecuresallkeysintheSQLServer
encryptionkeyhierarchyontheserver.
AlthougheachSQLServerinstancehasonlyasingleSMK,eachdatabasecanhavea
databasemasterkey(DMK).TheDMKisencryptedbytheSMKandisusedtoencrypt
lower-levelkeysandcertificates.
AtthebottomoftheSQLServer2014keyhierarchyarethecertificates,symmetric
keys,andasymmetrickeysusedtoencryptdata.
SQLServer2014alsointroducestheconceptoftheservercertificate,whichisa
certificatecreatedinthemasterdatabaseforthepurposeofprotectingdatabase
encryptionkeys.Databaseencryptionkeysaresymmetricencryptionkeyscreatedto
encryptentiredatabasesviaTDE.
ServiceMasterKeys
Asmentionedintheprevioussection,theSMKisautomaticallygeneratedbySQLServer
thefirsttimeit’sneeded.BecausetheSMKisgeneratedautomaticallyandmanagedby
SQLServer,thereareonlyacoupleofadministrativetasksyouneedtoperformforthis
key:backingitupandrestoringitonaserverasnecessary.Youalsoneedaccesstothe
directorywherethebackupfileislocated.Forexample,inListing8-1,youwanttocreate
afoldernamedCH08onyourCdrive.Listing8-1demonstratestheBACKUPand
RESTORESERVICEMASTERKEYstatements.
Listing8-1.BACKUPandRESTORESMKExamples
--BackuptheSMKtoafile
BACKUPSERVICEMASTERKEYTOFILE='c:\CH08\S0L2012.SMK'
ENCRYPTIONBYPASSWORD='p@$$w0rd';
--RestoretheSMKfromafile
RESTORESERVICEMASTERKEYFROMFILE='c:\CH08\S0L2012.SMK'
DECRYPTIONBYPASSWORD='p@$$w0rd';
TheBACKUPSERVICEMASTERKEYstatementallowsyoutobackupyourSMK
toafile.TheSMKisencryptedinthefile,sotheENCRYPTIONBYPASSWORDclause
ofthisstatementismandatory.
TheRESTORESERVICEMASTERKEYstatementrestorestheSMKfroma
previouslycreatedbackupfile.TheDECRYPTIONBYPASSWORDclausemustspecify
thesamepasswordusedtoencryptthefilewhenyoucreatedthebackup.Backingupand
restoringanSMKrequiresCONTROLSERVERpermissions.Inthepreviousscenario,
SQLServerisintelligentenoughtoknowthatthebackupSMKandtheSMKinthe
restorearethesame,soitdoesn’tneedtogothroughanunnecessarydecryptionand
encryptionprocess.ThedataisencryptedagainonlyiftheSMKyou’retryingtorestoreis
differentfromtheSMKyoubackedup.
TheRESTORESERVICEMASTERKEYstatementcanincludetheoptional
keywordFORCEtoforcetheSMKtorestoreevenifthereisadatadecryptionfailure.If
youhavetousetheFORCEkeyword,youcanexpecttolosedata,sousethisoptionwith
careandonlyasalastresort.
TipAfterinstallingSQLServer2014,youshouldimmediatelybackupyourSMKand
storeacopyofitinasecureoffsitelocation.IfyourSMKbecomescorruptedoris
otherwisecompromised,youcouldloseaccesstoallofyourencrypteddataifyoudon’t
haveabackupoftheSMK.
InadditiontoBACKUPandRESTOREstatements,SQLServerprovidestheALTER
SERVICEMASTERKEYstatementtoallowyoutochangetheSMKforaninstanceof
SQLServer.WhenSQLServergeneratestheSMK,itusesthecredentialsoftheSQL
ServerserviceaccounttoencrypttheSMK.IfyouchangetheSQLServerserviceaccount,
youcanuseALTERSERVICEMASTERKEYtoupdateitusingthecurrentservice
accountcredentials.Alternatively,youcanadviseSQLServertosecuretheSMKusing
thelocalmachinekey,whichismanagedbytheoperatingsystem.Youcanalsouse
ALTERSERVICEMASTERKEYtoregeneratetheSMKcompletely.
AswiththeRESTORESERVICEMASTERKEYstatement,theALTERSERVICE
MASTERKEYstatementallowsuseoftheFORCEkeyword.Normally,ifthereisa
decryptionerrorduringtheprocessofalteringtheSMK,SQLServerstopstheprocess
withanerrormessage.WhenFORCEisused,theSMKisregeneratedevenattheriskof
dataloss.JustliketheRESTOREstatement,theFORCEoptionshouldbeusedwithcare,
andonlyasalastresort.
TipWhenyouregeneratetheSMK,allkeysthatareencryptedbyitmustbedecrypted
andre-encrypted.Thisoperationcanberesourceintensiveandshouldbescheduledduring
off-peaktimeperiods.
DatabaseMasterKeys
EachdatabasecanhaveasingleDMK,whichisusedtoencryptcertificateprivatekeys
andasymmetrickey-pairprivatekeysinthecurrentdatabase.TheDMKiscreatedwith
theCREATEMASTERKEYstatement,asshowninListing8-2.
Listing8-2.CreatingaMasterKey
USEAdventureWorks2014;
GO
CREATEMASTERKEY
ENCRYPTIONBYPASSWORD='p@$$w0rd';
TheCREATEMASTERKEYstatementcreatestheDMKandusestheAdvanced
EncryptionStandard(AES)toencryptitwiththesuppliedpassword.Ifthepasswordyou
supplydoesn’tmeetWindows’password-complexityrequirements,SQLServerwill
complainwithanerrormessagelikethefollowing:
Msg15118,Level16,State1,Line1
Passwordvalidationfailed.Thepassworddoesnotmeet
Windows
policyrequirementsbecauseitisnotcomplexenough.
NoteVersionsofSQLpriortoSQL2012usedTripleDataEncryptionStandard
(3DES)forencryptingSMKsandDMKs.SQLServer2012andlaterusethemore
advancedAESencryption.IfyouupgradeSQLServerfromapreviousversion,youneed
toalsoupgradeyourencryptionkeys.ThiscanbeaccomplishedbyusingeitherALTER
SERVICEMASTERKEYorALTERMASTERKEYandtheREGENERATEclause.
SQLServer2014automaticallyusestheSMKtoencryptacopyoftheDMK.When
thisfeatureisused,SQLServercandecryptyourDMKwhennecessarywithouttheneed
tofirstopenthemasterkey.Whenthisfeatureisn’tinuse,youmustissuetheOPEN
MASTERKEYstatementandsupplythesamepasswordinitiallyusedtoencrypttheDMK
wheneveryouneedtouseit.ThepotentialdownsidetoencryptingyourDMKwiththe
SMKisthatanymemberofthesysadminserverrolecandecrypttheDMK.Youcan
usetheALTERMASTERKEYstatementtochangethemethodSQLServerusesto
decrypttheDMK.Listing8-3showshowtoturnoffencryptionbySMKforaDMK.
Listing8-3.TurningOffDMKEncryptionbytheSMK
ALTERMASTERKEY
DROPENCRYPTIONBYSERVICEMASTERKEY;
WhentheDMKisregenerated,allthekeysitprotectsaredecryptedandre-encrypted
withthenewDMK.TheFORCEkeywordisusedtoforceSQLServertoregeneratethe
DMKeveniftherearedecryptionerrors.AswiththeSMK,theFORCEkeywordshould
beusedonlyasalastresort.YoucanexpecttolosedataifyouhavetouseFORCE.
YoucanalsobackupandrestoreaDMKwiththeBACKUPMASTERKEYand
RESTOREMASTERKEYstatements.TheBACKUPMASTERKEYstatementissimilar
inoperationtotheBACKUPSERVICEMASTERKEYstatement.Whenyoubackupthe
DMK,youmustspecifythepasswordthatSQLServerwillusetoencrypttheDMKinthe
outputfile.WhenyourestoretheDMK,youmustspecifythesamepasswordinthe
DECRYPTIONBYPASSWORDclausetodecrypttheDMKintheoutputfile.Inaddition,
youmustspecifyanencryptionpasswordthatSQLServerwillusetoencryptthe
passwordintheENCRYPTIONBYPASSWORDclause.Listing8-4demonstratesbacking
upandrestoringaDMK.
Listing8-4.BackingUpandRestoringaDMK
USEAdventureWorks2014;
GO
OPENMASTERKEYDECRYPTIONBYPASSWORD='p@$$w0rd';
BACKUPMASTERKEY
TOFILE='c:\CH08\AdventureWorks2014.DMK'
ENCRYPTIONBYPASSWORD='p@$$wOrd';
--RestoreDMKfrombackup
RESTOREMASTERKEY
FROMFILE='c:\CH08\AdventureWorks2014.DMK'
DECRYPTIONBYPASSWORD='p@$$wOrd'
ENCRYPTIONBYPASSWORD='3rt=d4uy';
CLOSEMASTERKEY;
TheFORCEkeywordisavailableforusewiththeRESTOREMASTERKEY
statement.Butaswithotherstatements,itshouldonlybeusedasalastresort,becauseit
couldresultinunrecoverableencrypteddata.
TheDROPMASTERKEYstatementcanbeusedtoremoveaDMKfromthe
database.DROPMASTERKEYdoesn’tremoveaDMKifit’scurrentlybeingusedto
encryptotherkeysinthedatabase.IfyouwanttodropaDMKthatisprotectingother
keysinthedatabase,theprotectedkeysmustfirstbealteredtoremovetheirencryptionby
theDMK.
TipAlwaysmakebackupsofyourDMKsimmediatelyoncreationandstorethemina
securelocation.
IfyouchoosetodisableautomatickeymanagementwiththeALTERMASTERKEY
statement,youneedtousetheOPENMASTERKEYandCLOSEMASTERKEY
statementseverytimeyouwishtoperformencryptionanddecryptioninadatabase.
OPENMASTERKEYrequiresyoutosupplythesamepasswordusedtoencryptthe
DMKintheDECRYPTIONBYPASSWORDclause.Thispasswordisusedtodecryptthe
DMK,arequiredstepwhenyou’reencryptinganddecryptingdata.Whenyou’refinished
usingtheDMK,issuetheCLOSEMASTERKEYstatement.IfyourDMKisencryptedby
theSMK,youdon’tneedtousetheOPENMASTERKEYandCLOSEMASTERKEY
statements;SQLServerhandlesthattaskforyouautomatically.
Certificates
Certificatesareasymmetricencryptionkeypairswithadditionalmetadata,suchassubject
andexpirationdate,intheX.509certificateformat.Asymmetricencryptionisamethodof
encryptingdatausingtwoseparatebutmathematicallyrelatedkeys.SQLServer2014uses
thestandardpublickey/privatekeyencryptionmethodology.Youcanthinkofacertificate
asawrapperforanasymmetricencryptionpublickey/privatekeypair.TheCREATE
CERTIFICATEstatementcanbeusedtoeitherinstallanexistingcertificateorcreatea
newcertificateonSQLServer.Listing8-5showshowtocreateanewcertificateonSQL
Server.
Listing8-5.CreatingaCertificateonSQLServer
CREATECERTIFICATETestCertificate
ENCRYPTIONBYPASSWORD='p@$$wOrd'
WITHSUBJECT='AdventureWorks2014TestCertificate',
EXPIRY_DATE='2026-10-31';
TheCREATECERTIFICATEstatementincludesseveraloptions.Theonly
mandatorythingsaretheSQLServeridentifierforthecertificateimmediatelyfollowing
theCREATECERTIFICATEstatement(inthiscaseTestCertificate)andthe
WITHSUBJECTclause,whichsetsthecertificatesubjectname.IftheENCRYPTION
BYPASSWORDclauseisn’tusedwhenyoucreateacertificate,thecertificate’sprivate
keyisencryptedbytheDMK.AdditionaloptionsavailabletotheCREATE
CERTIFICATEstatementincludeSTART_DATEandEXPIRY_DATE,whichsetthe
startandexpirationdatesforthecertificate;andtheACTIVEFORBEGINDIALOG
clause,whichmakesthecertificateavailableforusebyServiceBrokerdialogs.
TipIfSTART_DATEisn’tspecified,thecurrentdateisused.IfEXPIRY_DATEis
omitted,theexpirationdateissettooneyearafterthestartdate.
YoucanalsousetheCREATECERTIFICATEstatementtoloadanexisting
certificateinavarietyofways,includingthefollowing:
YoucanusetheFROMASSEMBLYclausetoloadanexisting
certificatefromasignedassemblyalreadyloadedinthedatabase.
YoucanusetheEXECUTABLEFILEclausetocreateacertificate
fromasignedDLLfile.
YoucanusetheFILEclausetocreateacertificatefromanexisting
DistinguishedEncodingRules(DER)X.509certificatefile.
YoucanalsousetheWITHPRIVATEKEYclausewiththeFILEor
EXECUTABLEFILEoptiontospecifyaseparatefilecontainingthe
certificate’sprivatekey.WhenyouspecifytheWITHPRIVATE
KEYclause,youcanspecifytheoptionalDECRYPTIONBY
PASSWORDandENCRYPTIONBYPASSWORDclausestospecify
thepasswordthatwillbeusedtodecrypttheprivatekeyifit’s
encryptedinthesourcefile,andtosecuretheprivatekeyonceit’s
loaded.
NoteSQLServergeneratesprivatekeysthatare1,024bitsinlength.Ifyouimporta
privatekeyfromanexternalsource,itmustbeamultipleof64bits,between384and
3,456bitsinlength.
Aftercreatingacertificate—aswithDMKsandSMKs—youshouldimmediately
makeabackupandstoreitinasecurelocation.Listing8-6demonstrateshowtomakea
backupofacertificate.
Listing8-6.BackingUpaCertificate
BACKUPCERTIFICATETestCertificate
TOFILE='c:\CH08\TestCertificate.CER'
WITHPRIVATEKEY
(
FILE='c:\CH08\TestCertificate.PVK',
ENCRYPTIONBYPASSWORD='7&rtOxp2',
DECRYPTIONBYPASSWORD='p@$$wOrd'
);
TheBACKUPCERTIFICATEstatementinListing8-6backsuptheTestCertificate
certificatetothec:\TestCertificate.CERfileandthecertificate’sprivatekeyto
thec:\TestCertificate.PVKfile.TheDECRYPTIONBYPASSWORDclause
specifiesthepasswordtousetodecryptthecertificate,andENCRYPTIONBY
PASSWORDgivesSQLServerthepasswordtousewhenencryptingtheprivatekeyinthe
file.ThereisnoRESTOREstatementforcertificates;instead,theCREATE
CERTIFICATEstatementhasalltheoptionsnecessarytorestoreacertificatefroma
backupfilebysimplycreatingfromanexistingcertificateusingtheFROMFILEclause.
T-SQLalsoprovidesanALTERCERTIFICATEstatementthatallowsyoutomake
changestoanexistingcertificate.
Youcanusecertificatestoencryptanddecryptdatadirectlywiththecertificate
encryptionanddecryptionfunctions,EncryptByCertandDecryptByCert.The
EncryptByCertfunctionencryptsagivencleartextmessagewithaspecified
certificate.ThefunctionacceptsanintcertificateIDandaplaintextvaluetoencrypt.
TheintcertificateIDcanberetrievedbypassingthecertificatenametotheCertID
function.Listing8-7demonstratesthisfunction.EncryptByCertreturnsa
varbinaryvalueuptoamaximumof432bytesinlength(thelengthoftheresult
dependsonthelengthofthekey).Thefollowingsection,“LimitationsofAsymmetric
Encryption,”describessomeofthelimitationsofasymmetricencryptiononSQLServer,
includingencryptionbycertificate.
LimitationsofAsymmetricEncryption
Asymmetricencryptionhascertainlimitationsthatshouldbenotedbeforeyouattemptto
encryptdatadirectlywithcertificatesorasymmetrickeys.TheEncryptByCert
functioncanacceptachar,varchar,binary,nchar,nvarchar,orvarbinary
constant,columnname,orvariableascleartexttoencrypt.Asymmetricencryption,
includingencryptionbycertificate,onSQLServerreturnsavarbinaryresult,butit
won’treturnaresultlongerthan432bytes.Asmentioned,themaximumlengthofthe
resultdependsonthelengthoftheencryptionkeyused.Asanexample,withthedefault
privatekeylengthof1,024bits,youcanencryptavarcharplaintextmessagewitha
maximumlengthof117charactersandannvarcharplaintextmessagewitha
maximumlengthof58characters.Theresultineithercaseisavarbinaryresultof128
bytes.
Microsoftrecommendsthatyouavoidusingasymmetricencryptiontoencryptdata
directlybecauseofthesizelimitations,andforperformancereasons.Symmetric
encryptionalgorithmsuseshorterkeysbutoperatemorequicklythanasymmetric
encryptionalgorithms.TheSQLServer2014encryptionkeyhierarchyprovidesthebest
ofbothworlds,withthelongkeylengthsofasymmetrickeysprotectingtheshorter,more
efficientsymmetrickeys.Tomaximizeperformance,Microsoftrecommendsusing
symmetricencryptiontoencryptdataandasymmetricencryptiontoencryptsymmetric
keys.
TheDecryptByCertfunctiondecryptstextpreviouslyencryptedby
EncryptByCert.TheDecryptByCertfunctionacceptsanintcertificateID,an
encryptedvarbinaryciphertextmessage,andanoptionalcertificatepasswordthat
mustmatchtheoneusedwhenthecertificatewascreated(ifonewasspecifiedatcreation
time).Ifnocertificatepasswordisspecified,theDMKisusedtodecryptit.Listing8-7
demonstratesencryptionanddecryptionbycertificateforshortplaintext.Theresultsare
showninFigure8-2.IfyougetanerrorduringtheCREATEMASTERKEYandCREATE
CERTIFICATEcommands,besuretorunthefinalDROPstatementspriortocreatingthe
objects.
Listing8-7.ExampleEncryptionandDecryptionbyCertificate
--CreateaDMK
CREATEMASTERKEY
ENCRYPTIONBYPASSWORD='P@55w0rd';
--Createacertificate
CREATECERTIFICATETestCertificate
WITHSUBJECT=N'AdventureWorksTestCertificate',
EXPIRY_DATE='2026-10-31';
--Createtheplaintextdatatoencrypt
DECLARE@plaintextnvarchar(58)=
N'Thisisateststringtoencrypt';
SELECT'Plaintext=',@plaintext;
--Encrypttheplaintextbycertificate
DECLARE@ciphertextvarbinary(128)=
EncryptByCert(Cert_ID('TestCertificate'),@plaintext);
SELECT'Ciphertext=',@ciphertext;
--Decrypttheciphertextbycertificate
DECLARE@decryptedtextnvarchar(58)=
DecryptByCert(Cert_ID('TestCertificate'),@ciphertext);
SELECT'Decryptedtext=',@decryptedtext;
--Dropthetestcertificate
DROPCERTIFICATETestCertificate;
--DroptheDMK
DROPMASTERKEY;
Figure8-2.Resultofencryptinganddecryptingbycertificate
Listing8-7firstcreatesaDMKandatestcertificateusingtheCREATEMASTER
KEYandCREATECERTIFICATEstatementspresentedpreviouslyinthischapter.It
thengeneratesannvarcharplaintextmessagetoencrypt:
--CreateaDMK
CREATEMASTERKEYENCRYPTIONBYPASSWORD='P@55wOrd';
--Createacertificate
CREATECERTIFICATETestCertificate
WITHSUBJECT=N'AdventureWorksTestCertificate',
EXPIRY_DATE='2026-10-31';
--Createtheplaintextdatatoencrypt
DECLARE@plaintextnvarchar(58)=
N'Thisisateststringtoencrypt';
SELECT'Plaintext=',@plaintext;
TheexampleusestheEncryptByCertfunctiontoencrypttheplaintextmessage.
TheCertIDfunctionisusedtoretrievetheintcertificateIDforTestCertificate:
--Encrypttheplaintextbycertificate
DECLARE@ciphertextvarbinary(128)=
EncryptByCert(Cert_ID('TestCertificate'),@plaintext);
SELECT'Ciphertext=',@ciphertext;
TheDecryptByCertfunctionisthenusedtodecrypttheciphertext.Again,the
CertIDfunctionisusedtoretrievetheTestCertificatecertificateID:
--Decrypttheciphertextbycertificate
DECLARE@decryptedtextnvarchar(58)=
DecryptByCert(Cert_ID('TestCertificate'),@ciphertext);
SELECT'Decryptedtext=',@decryptedtext;
Thebalanceofthecodeperformssomecleanup,droppingthecertificateandDMK:
--Dropthetestcertificate
DROPCERTIFICATETestCertificate;
--DroptheDMK
DROPMASTERKEY;
Youcanalsouseacertificatetogenerateasignatureforaplaintextmessage.
SignByCertacceptsacertificateID,aplaintextmessage,andanoptionalcertificate
password.Theresultisavarbinarystring,uptoalengthof432characters(again,the
lengthoftheresultisdeterminedbythelengthoftheencryptionkey).When
SignByCertisused,theslightestchangeintheplaintextmessage—evenasingle
character—willresultinacompletelydifferentsignaturebeinggeneratedforthemessage.
Thisallowsyoutoeasilydetectwhetheryourplaintexthasbeentamperedwith.Listing8-
8usestheSignByCertfunctiontocreateasignatureforaplaintextmessage.The
resultsareshowninFigure8-3.
Listing8-8.SigningaMessagewiththeSignByCertFunction
--CreateaDMK
CREATEMASTERKEYENCRYPTIONBYPASSWORD='P@55w0rd';
--Createacertificate
CREATECERTIFICATETestCertificate
WITHSUBJECT='AdventureWorksTestCertificate',
EXPIRY_DATE='2026-10-31';
--Createmessage
DECLARE@messagenvarchar(4000)=N'Fourscoreandseven
yearsagoourfathersbroughtforthonthiscontinentanew
nation,conceivedinLiberty,anddedicatedtothe
propositionthatallmenarecreatedequal.
Nowweareengagedinagreatcivilwar,testingwhether
thatnation,oranynation,soconceivedandsodedicated,
canlongendure.Wearemetonagreatbattle-fieldofthat
war.Wehavecometodedicateaportionofthatfield,as
afinalrestingplaceforthosewhoheregavetheirlives
thatthatnationmightlive.Itisaltogetherfittingand
properthatweshoulddothis.';
--Signthemessagebycertificate
SELECTSignByCert(Cert_ID(N'TestCertificate'),@message);
--Dropthecertificate
DROPCERTIFICATETestCertificate;
--DroptheDMKDROPMASTERKEY;
Figure8-3.SignaturegeneratedbySignByCert(partial)
AsymmetricKeys
Asymmetrickeysareactuallycomposedofakeypair:apublickey,whichispublicly
accessible,andaprivatekey,whichiskeptsecret.Themathematicalrelationshipbetween
thepublicandprivatekeysallowsforencryptionanddecryptionwithoutrevealingthe
privatekey.T-SQLincludesstatementsforcreatingandmanagingasymmetrickeys.
TheCREATEASYMMETRICKEYstatementallowsyoutogenerateanasymmetric
keypairorinstallanexistingkeypairontheserver,inmuchthesamemanneraswhen
creatingacertificate.Encryption-keylengthisoftenusedasanindicatorofrelative
encryptionstrength,andwhenyoucreateanasymmetrickeyonSQLServer,youcan
specifyanRSAkeylength,asshowninTable8-1.
Table8-1.AsymmetricKeyAlgorithmsandLimits
Listing8-9createsanasymmetrickeypaironSQLServer2014.
Listing8-9.CreatinganAsymmetricKeyPair
CREATEASYMMETRICKEYTempAsymmetricKeyWITHALGORITHM
=RSA_1024;
YoucanalteranexistingasymmetrickeywiththeALTERASYMMETRICKEY
statement.ALTERASYMMETRICKEYoffersthefollowingoptionsformanagingyour
asymmetrickeys:
YoucanusetheREMOVEPRIVATEKEYclausetoremovethe
privatekeyfromtheasymmetricpublickey/privatekeypair.
YoucanusetheWITHPRIVATEKEYclausetochangethemethod
usedtoprotecttheprivatekey.
YoucanchangetheasymmetrickeyprotectionmethodfromDMK
encryptiontopasswordencryptionwiththeENCRYPTIONBY
PASSWORDoption.
Youcanswitchfrompasswordprotectionforyourasymmetrickeyto
DMKprotectionwiththeDECRYPTIONBYPASSWORDclause.
YoucanspecifyboththeENCRYPTIONBYPASSWORDand
DECRYPTIONBYPASSWORDclausestogethertochangethe
passwordusedtoencrypttheprivatekey.
TheDROPASYMMETRICKEYstatementremovesanasymmetric
keyfromthedatabase.
TheEncryptByAsymKeyandDecryptByAsymKeyfunctionsallowyouto
encryptanddecryptdatawithanasymmetrickeyinthesamewayasEncryptByCert
andDecryptByCert.
TheEncryptByAsymKeyfunctionacceptsanintasymmetrickeyIDandplain
texttoencrypt.TheAsymKeyIDfunctioncanbeusedtoretrieveanasymmetrickeyID
byname.DecryptByAsymKeyacceptsanasymmetrickeyID,encryptedciphertextto
decrypt,andanoptionalpasswordtodecrypttheasymmetrickey.Ifthepasswordis
specified,itmustbethesamepasswordusedtoencrypttheasymmetrickeyatcreation
time.
TipThelimitationsforasymmetrickeyencryptionanddecryptiononSQLServerare
thesameasthoseforcertificateencryptionanddecryption.
Listing8-10demonstratestheuseofasymmetrickeyencryptionanddecryption
functions.Besuretodropanymasterkeyspriortorunningthecode.Theresultsare
showninFigure8-4.
Listing8-10.EncryptingandDecryptingwithAsymmetricKeys
--CreateDMK
CREATEMASTERKEY
ENCRYPTIONBYPASSWORD='P@55wOrd';
--Createasymmetrickey
CREATEASYMMETRICKEYTestAsymmetricKeyWITHALGORITHM
=RSA_512;
--Assignacreditcardnumbertoencrypt
DECLARE@CreditCardnvarchar(26)=N'9000123456789012';
SELECT@CreditCard;
--Encryptthecreditcardnumber
DECLARE@EncryptedCreditCardvarbinary(64)=
EncryptByAsymKey(AsymKey_ID(N'TestAsymmetricKey'),
@CreditCard);
SELECT@EncryptedCreditCard;
--Decrypttheencryptedcreditcardnumber
DECLARE@DecryptedCreditCardnvarchar(26)=
DecryptByAsymKey(AsymKey_ID(N'TestAsymmetricKey'),
@EncryptedCreditCard);
SELECT@DecryptedCreditCard;
--Dropasymmetrickey
DROPASYMMETRICKEYTestAsymmetricKey;
--DropDMK
DROPMASTERKEY;
Figure8-4.Asymmetrickeyencryptionresults
ThisexamplefirstcreatesaDMKandanRSAasymmetrickeywitha512-bitprivate
keylength.Thenitcreatesplaintextrepresentingasimplecreditcardnumber:
--CreateDMK
CREATEMASTERKEYENCRYPTIONBYPASSWORD='P@55wOrd';
--Createasymmetrickey
CREATEASYMMETRICKEYTestAsymmetricKeyWITHALGORITHM
=RSA_512;
--Assignacreditcardnumbertoencrypt
DECLARE@CreditCardnvarchar(26)=N'9000123456789012';
SELECT@CreditCard;
NoteYouhavetheoptiontocreateanasymmetrickeywithoutacorresponding
databasemasterkey.Ifyoudecidetodothis,youmusthaveapasswordassignedtothe
asymmetrickey;otherwise,apasswordisoptional.
TheexamplethenencryptsthecreditcardnumberwiththeEncryptByAsymKey
functionanddecryptsitwiththeDecryptByAsymKeyfunction.Bothfunctionsusethe
AsymKeylDfunctiontoretrievetheasymmetrickeyID:
--Encryptthecreditcardnumber
DECLARE@EncryptedCreditCardvarbinary(64)=
EncryptByAsymKey(AsymKey_ID(N'TestAsymmetricKey'),
@CreditCard);
SELECT@EncryptedCreditCard;
--Decrypttheencryptedcreditcardnumber
DECLARE@DecryptedCreditCardnvarchar(26)=
DecryptByAsymKey(AsymKey_ID(N'TestAsymmetricKey'),
@EncryptedCreditCard);
SELECT@DecryptedCreditCard;
Thecodefinishesupwithalittlehousekeeping,namelydroppingtheasymmetrickey
andtheDMKcreatedfortheexample:
--Dropasymmetrickey
DROPASYMMETRICKEYTestAsymmetricKey;
--DropDMK
DROPMASTERKEY;
Likecertificates,asymmetrickeysofferafunctiontogeneratedigitalsignaturesfor
plaintext.TheSignByAsymKeyfunctionacceptsastringupto8,000bytesinlength
andreturnsavarbinarysignatureforthestring.Thelengthofthesignatureis
dependentonthekeylength,aspreviouslyshowninTable8-1.Listing8-11isasimple
exampleoftheSignByAsymKeyfunctioninaction.TheresultsareshowninFigure8-5.
Listing8-11.SigningaMessagebyAsymmetricKey
--CreateDMK
CREATEMASTERKEY
ENCRYPTIONBYPASSWORD='P@55wOrd';
--Createasymmetrickey
CREATEASYMMETRICKEYTestAsymmetricKeyWITHALGORITHM
=RSA_512;
--Createmessage
DECLARE@messagenvarchar(4000)=N'Alas,poorYorick!';
SELECT@message;
--Signmessagebyasymmetrickey
SELECTSignByAsymKey(AsymKey_ID(N'TestAsymmetricKey'),
@message);
--Dropasymmetrickey
DROPASYMMETRICKEYTestAsymmetricKey;
--DropDMK
DROPMASTERKEY;
Figure8-5.Signingamessagewithanasymmetrickey
AsymmetricKey“Backups”
SQLServerprovidesnoBACKUPorRESTOREstatementforasymmetrickeys.For
physicalbackupsofyourasymmetrickeys,youshouldinstalltheasymmetrickeysfrom
anexternalsourcelikeanassembly,anexecutablefile,astrong-namefile,orahardware
securitymodule(HSM).Youcanmakebackupsofthesourcefilescontainingyour
asymmetrickeys.Asanalternative,youcanusecertificatesinsteadofasymmetrickeys.
Keeptheseoptionsinmindwhenyou’replanningtotakeadvantageofSQLServer2014
encryption.
SymmetricKeys
SymmetrickeysareatthebottomoftheSQLServerencryptionkeyhierarchy.Symmetric
encryptionalgorithmsusetriviallyrelatedkeystobothencryptanddecryptyourdata.
Triviallyrelatedsimplymeansthealgorithmcanuseeitherthesamekeyforboth
encryptionanddecryption,ortwokeysthataremathematicallyrelatedviaasimple
transformationtoderiveonekeyfromtheother.SymmetrickeysonSQLServer2014are
specificallydesignedtosupportSQLServer’ssymmetricencryptionfunctionality.The
algorithmsprovidedbySQLServer2014useasinglekeyforbothencryptionand
decryption.IntheSQLServer2014encryptionmodel,symmetrickeysareencryptedby
certificatesorasymmetrickeys,andtheycanbeusedinturntoencryptothersymmetric
keysorrawdata.TheCREATESYMMETRICKEYstatementallowsyoutogenerate
symmetrickeys,asshowninListing8-12.
Listing8-12.CreatingaSymmetricKey
CREATESYMMETRICKEYTestSymmetricKeyWITHALGORITHM
=AES_128ENCRYPTIONBYPASSWORD='p@55wOrd';
TheoptionsspecifiedintheCREATESYMMETRICKEYstatementinListing8-12
specifythatthesymmetrickeyiscreatedwiththenameTestSymmetricKey,it’s
protectedbythepasswordp@55wOrd,anditusesAESwitha127-bitkey(AES128)to
encryptdata.
Whencreatingasymmetrickey,youcanspecifyanyofseveralencryptionalgorithms,
includingthefollowing:
AES128,AES192,andAES256specifytheAESblock-encryption
algorithmwithasymmetrickeylengthof128,192,or256bitsanda
blocksizeof128bits.
DESspecifiestheDESblock-encryptionalgorithm,whichhasa
symmetrickeylengthof56bitsandablocksizeof64bits.
DESXspecifiestheDES-Xblock-encryptionalgorithm,whichwas
introducedasasuccessortotheDESalgorithm.DES-Xalsohasa
symmetrickeylengthof56bits(althoughbecausethealgorithm
includessecurityaugmentations,theeffectivekeylengthiscalculated
ataround118bits)andablocksizeof64bits.
RC2specifiestheRC2block-encryptionalgorithm,whichhasakey
sizeof128bitsandablocksizeof64bits.
RC4andRC4_128specifytheRC4stream-encryptionalgorithm,
whichhasakeylengthof40or128bits.RC4andRC4_128aren’t
recommended,becausetheydon’tgeneraterandominitialization
vectorstofurtherobfuscatetheciphertext.
TheCREATESYMMETRICKEYstatementprovidesadditionaloptionsthatallow
youtospecifyoptionsforsymmetrickeycreation,includingthefollowing:
YoucanspecifyaKEYSOURCEtodesignateapassphrasetobeused
askeymaterialfromwhichthesymmetrickeyisderived.Ifyoudon’t
specifyaKEYSOURCE,SQLServergeneratesthesymmetrickey
fromrandomkeymaterial.
TheENCRYPTIONBYclausespecifiesthemethodusedtoencrypt
thissymmetrickeyinthedatabase.Youcanspecifyencryptionbya
certificate,apassword,anasymmetrickey,anothersymmetrickey,or
HSM.
ThePROVIDER_KEY_NAMEandCREATI0N_DISP0SITI0N
clausesallowyoutouseyoursymmetrickeywithEKMsecurity.
TheIDENTITYVALUEclausespecifiesanidentityphrasethatisused
togenerateaGUIDto“tag”dataencryptedwiththekey.
CautionWhenasymmetrickeyisencryptedwithapasswordinsteadofthepublickey
ofthedatabasemasterkey,the3DESencryptionalgorithmisused.Becauseofthis,keys
thatarecreatedwithastrongencryptionalgorithm,suchasAES,arethemselvessecured
byaweakeralgorithm.
TemporarySymmetricKeys
Youcancreatetemporarysymmetrickeysbyprefixingthesymmetrickeynamewitha
numbersign(#).Atemporarysymmetrickeyexistsonlyduringthecurrentsessionandis
automaticallyremovedwhenthecurrentsessionends.Temporarysymmetrickeysaren’t
accessibletoanysessionsoutsideofthesessionthey’recreatedin.Whenreferencinga
temporarysymmetrickey,thenumbersign(#)prefixmustbeused.Youcanusethesame
WITHclauseoptionsdescribedinthissectiontospecifyhowthesymmetrickeyshouldbe
created.Tobehonest,wedon’tseemuchusefortemporarysymmetrickeysatthispoint,
althoughwedon’twanttodiscountthemtotally.Afterall,someonemayfindausefor
theminthefuture.
SQLServeralsoprovidestheALTERSYMMETRICKEYandDROPSYMMETRIC
KEYstatementsforsymmetrickeymanagement.TheALTERstatementallowsyoutoadd
orremoveencryptionmethodsonasymmetrickey.Asanexample,ifyoucreateda
symmetrickeyandencrypteditbypasswordbutlaterwishedtochangeittoencryptionby
certificate,youwouldissuetwoALTERSYMMETRICKEYstatements—thefirstALTER
statementwouldspecifytheADDENCRYPTIONBYCERTIFICATEclause,andthe
secondwouldspecifyDROPENCRYPTIONBYPASSWORD,asshowninListing8-13.
Again,youmayneedtodropthecertificateandkeypriortorunningthecode.
Listing8-13.ChangingtheSymmetricKeyEncryptionMethod
--Createcertificatetoprotectsymmetrickey
CREATECERTIFICATETestCertificate
WITHSUBJECT='AdventureWorksTestCertificate',
EXPIRY_DATE='2026-10-31';
CREATESYMMETRICKEYTestSymmetricKeyWITHALGORITHM
=AES_128ENCRYPTIONBY
PASSWORD='p@55wOrd';
OPENSYMMETRICKEYTestSymmetricKey
DECRYPTIONBYPASSWORD='p@55wOrd';
ALTERSYMMETRICKEYTestSymmetricKey
ADDENCRYPTIONBYCERTIFICATETestCertificate;
ALTERSYMMETRICKEYTestSymmetricKey
DROPENCRYPTIONBYPASSWORD='p@55wOrd';
CLOSESYMMETRICKEYTestSymmetricKey;
--Dropthesymmetrickey
DROPSYMMETRICKEYTestSymmetricKey;
--Dropthecertificate
DROPCERTIFICATETestCertificate;
NoteBeforeyoualterasymmetrickey,youmustfirstopenitwiththeOPEN
SYMMETRICKEYstatement.
TheDROPSYMMETRICKEYstatementallowsyoutoremoveasymmetrickeyfrom
thedatabase.
Onceyoucreateasymmetrickey,youcanencryptanddecryptdatawiththe
EncryptByKeyandDecryptByKeyfunctions.Listing8-14createsasymmetrickey
andencrypts100nameswithit.PartialresultsareshowninFigure8-6.
Listing8-14.EncryptingDatawithaSymmetricKey
--Createatemporarytabletoholdresults
CREATETABLE#TempNames
(
BusinessEntityIDintPRIMARYKEY,
FirstNamenvarchar(50),
MiddleNamenvarchar(50),
LastNamenvarchar(50),
EncFirstNamevarbinary(200),
EncMiddleNamevarbinary(200),
EncLastNamevarbinary(200)
);
--CreateDMK
CREATEMASTERKEY
ENCRYPTIONBYPASSWORD='Test_P@sswOrd';
--Createcertificatetoprotectsymmetrickey
CREATECERTIFICATETestCertificate
WITHSUBJECT='AdventureWorksTestCertificate',
EXPIRY_DATE='2026-10-31';
--Createsymmetrickeytoencryptdata
CREATESYMMETRICKEYTestSymmetricKey
WITHALGORITHM=AES_128
ENCRYPTIONBYCERTIFICATETestCertificate;
--Opensymmetrickey
OPENSYMMETRICKEYTestSymmetricKey
DECRYPTIONBYCERTIFICATETestCertificate;
--Populatetemptablewith100encryptednamesfromthe
Person.Persontable
INSERT
INTO#TempNames
(
BusinessEntityID,
EncFirstName,
EncMiddleName,
EncLastName
)
SELECTTOP(100)BusinessEntityID,
EncryptByKey(Key_GUID(N'TestSymmetricKey'),FirstName),
EncryptByKey(Key_GUID(N'TestSymmetricKey'),MiddleName),
EncryptByKey(Key_GUID(N'TestSymmetricKey'),LastName)
FROMPerson.Person
ORDERBYBusinessEntityID;
--Updatethetemptablewithdecryptednames
UPDATE#TempNames
SETFirstName=DecryptByKey(EncFirstName),
MiddleName=DecryptByKey(EncMiddleName),
LastName=DecryptByKey(EncLastName);
--Showtheresults
SELECTBusinessEntityID,
FirstName,
MiddleName,
LastName,
EncFirstName,
EncMiddleName,
EncLastName
FROM#TempNames;
--Closethesymmetrickey
CLOSESYMMETRICKEYTestSymmetricKey;
--Dropthesymmetrickey
DROPSYMMETRICKEYTestSymmetricKey;
--Dropthecertificate
DROPCERTIFICATETestCertificate;
--DroptheDMK
DROPMASTERKEY;
--Dropthetemptable
DROPTABLE#TempNames;
Figure8-6.Symmetrickeyencryptionresults(partial)
Listing8-14firstcreatesatemporarytabletoholdtheencryptionanddecryption
results:
--Createatemporarytabletoholdresults
CREATETABLE#TempNames
(
BusinessEntityIDintPRIMARYKEY,
FirstNamenvarchar(50),
MiddleNamenvarchar(50),
LastNamenvarchar(50),
EncFirstNamevarbinary(200),
EncMiddleNamevarbinary(200),
EncLastNamevarbinary(200)
);
ThenaDMKiscreatedtoprotectthecertificatethatwillbecreatednext.The
certificatethat’screatedisthenusedtoencryptthesymmetrickey:
--CreateDMK
CREATEMASTERKEY
ENCRYPTIONBYPASSWORD='Test_P@sswOrd';
--Createcertificatetoprotectsymmetrickey
CREATECERTIFICATETestCertificate
WITHSUBJECT='AdventureWorksTestCertificate',
EXPIRY_DATE='2026-10-31';
--Createsymmetrickeytoencryptdata
CREATESYMMETRICKEYTestSymmetricKey
WITHALGORITHM=AES_128
ENCRYPTIONBYCERTIFICATETestCertificate;
Inordertoencryptdatawiththesymmetrickey,theexamplemustfirstexecutethe
OPENSYMMETRICKEYstatementtoopenthesymmetrickey.TheDECRYPTIONBY
clausespecifiesthemethodtousetodecryptthesymmetrickeyforuse.Inthisexample,
thekeyisprotectedbycertificate,soDECRYPTIONBYCERTIFICATEisused.You
canspecifydecryptionbycertificate,asymmetrickey,symmetrickey,orpassword.Ifthe
DMKwasusedtoencryptthecertificateorasymmetrickey,leaveofftheWITH
PASSWORDclause:
--Opensymmetrickey
OPENSYMMETRICKEYTestSymmetricKey
DECRYPTIONBYCERTIFICATETestCertificate;
ThenextstepistousetheEncryptByKeyfunctiontoencryptthedata.Inthis
example,theFirstName,MiddleName,andLastNamefor100rowsfromthe
Person.PersontableareencryptedwithEncryptByKey.TheEncryptByKey
functionacceptsacleartextchar,varchar,binary,varbinary,nchar,ornvarchar
constant,column,orT-SQLvariablewithamaximumlengthof8,000bytes.Theresult
returnedistheencrypteddatainvarbinaryformatwithamaximumlengthof8,000
bytes.Inadditiontocleartext,EncryptByKeyacceptsaGUIDidentifyingthe
symmetrickeywithwhichyouwishtoencryptthecleartext.TheKeyGUIDfunction
returnsasymmetrickey’sGUIDbyname:
--Populatetemptablewith100encryptednamesfromthe
Person.Persontable
INSERT
INTO#TempNames
(
BusinessEntityID,
EncFirstName,
EncMiddleName,
EncLastName
)
SELECTTOP(100)BusinessEntityID,
EncryptByKey(Key_GUID(N'TestSymmetricKey'),FirstName),
EncryptByKey(Key_GUID(N'TestSymmetricKey'),MiddleName),
EncryptByKey(Key_GUID(N'TestSymmetricKey'),LastName)
FROMPerson.Person
ORDERBYBusinessEntityID;
TheexamplecodethenusestheDecryptByKeyfunctiontodecryptthepreviously
encryptedciphertextinthetemporarytable.SQLServerstorestheGUIDofthe
symmetrickeyusedtoencryptthedatawiththeencrypteddata,soyoudon’tneedto
supplythesymmetrickeyGUIDtoDecryptByKey.Intheexamplecode,the
varbinaryencryptedciphertextisallthat’spassedtotheEncryptByKeyfunction:
--Updatethetemptablewithdecryptednames
UPDATE#TempNames
SETFirstName=DecryptByKey(EncFirstName),
MiddleName=DecryptByKey(EncMiddleName),
LastName=DecryptByKey(EncLastName);
Finally,theresultsareshownandthesymmetrickeyisclosedwiththeCLOSE
SYMMETRICKEYstatement:
--Showtheresults
SELECTBusinessEntityID,
FirstName,
MiddleName,
LastName,
EncFirstName,
EncMiddleName,
EncLastName
FROM#TempNames;
--Closethesymmetrickey
CLOSESYMMETRICKEYTestSymmetricKey;
Thebalanceofthecodedropsthesymmetrickey,thecertificate,themasterkey,and
thetemporarytable:
--Dropthesymmetrickey
DROPSYMMETRICKEYTestSymmetricKey;
--Dropthecertificate
DROPCERTIFICATETestCertificate;
--DroptheDMK
DROPMASTERKEY;
--Dropthetemptable
DROPTABLE#TempNames;
NoteYoucancloseasinglesymmetrickeybynameorusetheCLOSEALL
SYMMETRICKEYSstatementtocloseallopensymmetrickeys.Openingandclosing
symmetrickeysaffectsonlythecurrentsessionontheserver.Allopensymmetrickeys
availabletothecurrentsessionareautomaticallyclosedwhenthecurrentsessionends.
SaltandAuthenticators
Theinitializationvector(IV),orsalt,isanimportantaspectofencryptionsecurity.TheIV
isablockofbitsthatfurtherobfuscatestheresultofanencryption.TheideaisthattheIV
helpspreventthesamedatafromgeneratingthesameciphertextifit’sencryptedmore
thanoncebythesamekeyandalgorithm.SQLServerdoesn’tallowyoutospecifyanIV
whenencryptingdatawithasymmetrickey,however.Instead,SQLServergeneratesa
randomIVautomaticallywhenyouencryptdatawithblockcipherslikeAESandDES.
TheobfuscationprovidedbytheIVhelpseliminatepatternsfromyourencrypteddata
patternsthatcryptanalystscanusetotheiradvantagewhenattemptingtohackyour
encrypteddata.ThedownsidetoSQLServer’srandomlygeneratedIVsisthattheymake
indexinganencryptedcolumnatrueexerciseinfutility.
InadditiontorandomIVgeneration,SQLServer’sEncryptByKeyand
DecryptByKeyfunctionsprovideanothertooltohelpeliminatepatternsinencrypted
data.Bothfunctionsprovidetwooptionsparameters:anadd_authenticatorflag
andanauthenticatorvalue.Iftheadd_authenticatorflagissetto1,SQLServer
derivesanauthenticatorfromtheauthenticatorvaluepassedin.Theauthenticatoristhen
usedtoobfuscateyourencrypteddatafurther,preventingpatternsthatcanreveal
informationtohackersthroughcorrelationanalysisattacks.Ifyousupplyanauthenticator
valueduringencryption,thesameauthenticatorvaluemustbesuppliedduringdecryption.
WhenSQLServerencryptsyourdatawithasymmetrickey,itautomaticallyadds
metadatatotheencryptedresult,aswellaspadding,makingtheencryptedresultlarger
(sometimessignificantlylarger)thantheunencryptedplaintext.Theformatforthe
encryptedresultwithmetadatafollowsthefollowingformat:
Thefirst16bytesoftheencryptedresultrepresenttheGUIDofthe
symmetrickeyusedtoencryptthedata.
Thenext4bytesrepresentaversionnumber,currentlyhard-codedas
0x01000000.
Thenext8bytesforDESencryption(16bytesforAESencryption)
representtherandomlygeneratedIV.
Ifanauthenticatorwasused,thenext8bytescontainheader
informationwithanadditional20-byteSHA1hashofthe
authenticator,makingtheheaderinformation28bytesinlength.
Thelastpartoftheencrypteddataistheactualpaddeddata.ForDES
algorithms,thelengthofthisencrypteddataisamultipleof8bytes.
ForAESalgorithms,thelengthisamultipleof16bytes.
InadditiontoDecryptByKey,SQLServer2014provides
DecryptByKeyAutoCertandDecryptByKeyAutoAsymKeyfunctions.Both
functionscombinethefunctionalityoftheOPENSYMMETRICKEYstatementwiththe
DecryptByKeyfunction,meaningyoudon’tneedtoissueanOPENSYMMETRIC
KEYtodecryptyourciphertext.TheDecryptByKeyAutoAsymKeyfunction
automaticallyopensanasymmetrickeyprotectingasymmetrickey,whereas
DecryptByKeyAutoCertautomaticallyopensacertificateprotectingasymmetric
key.Ifapasswordisusedtoencryptyourasymmetrickeyorcertificate,thatsame
passwordmustbepassedtothesefunctions.Iftheasymmetrickeyisencryptedwiththe
DMK,youpassNULLasthepassword.Youcanalsospecifyanauthenticatorwiththese
functionsifonewasusedduringencryption.Decryptionofdatainbulkusingthese
functionsmaycauseaprettysevereperformancepenaltyoverusingtheOPEN
SYMMETRICKEYstatementandtheDecryptByKeyfunction.
EncryptionWithoutKeys
SQLServer2014providesadditionalfunctionsforencryptionanddecryptionwithout
keysandforone-wayhashing,whichistheconceptofinputtingavalueintoafunctionto
getahashvaluebutnotbeingabletousethehashvaluetoreproducetheinput.These
functionsarenamedEncryptByPassPhrase,DecryptByPassPhrase,and
HashBytes,respectively.
TheEncryptByPassPhrasefunctionacceptsapassphraseandcleartextto
encrypt.ThepassphraseissimplyaplaintextphrasefromwhichSQLServercanderive
anencryptionkey.Theideabehindthepassphraseisthatusersaremorelikelyto
rememberasimplephrasethanacomplexencryptionkey.Thefunctionderivesa
temporaryencryptionkeyfromthepassphraseandusesittoencrypttheplaintext.You
canalsopassanoptionalauthenticatorvaluetoEncryptByPassPhraseifyouwish.
EncryptByPassPhrasealwaysusesthe3DESalgorithmtoencryptthecleartext
passedin.
DecryptByPassPhrasedecryptsciphertextthatwaspreviouslyencryptedwith
EncryptByPassPhrase.Todecryptusingthisfunction,youmustsupplythesame
passphraseandauthenticatoroptionsthatyouusedwhenencryptingthecleartext.
HashingData
TheHashBytesfunctionperformsaone-wayhashonthedatapassedtoitandreturns
thehashvaluegenerated.HashBytesacceptstwoparameters:ahashalgorithmname
andthedatatohash.Thereturnvalueisafixed-lengthvarbinaryhashvalue,whichis
analogoustoafingerprintforanygivendata.Table8-2liststheSQLServer-supported
hashalgorithms.
Table8-2.SQLServer-SupportedHashAlgorithms
Algorithm HashLength
MD2,MD4,MD5 128bits(16bytes)
SHA,SHA1 160bits(20bytes)
CautionForhighlysecureapplications,theMD2,MD4,andMD5seriesofhashes
shouldbeavoided.Cryptanalystshaveproducedmeaningfulhashcollisionswiththese
algorithmsoverthepastfewyearsthathaverevealedvulnerabilitiestohackerattacks.A
hashcollisionisastringofbytesthatproducesahashvaluethatisidenticaltoanother
stringofbytes.Ameaningfulhashcollisionisonethatcanbeproducedwithmeaningful
(orapparentlymeaningful)stringsofbytes.Generatingahashcollisionbymodifyingthe
contentofacertificatewouldbeanexampleofameaningful,anddangerous,hash
collision.
Listing8-15demonstratestheEncryptByPassPhrase,
DecryptByPassPhrase,andHashBytesfunctions.TheresultsareshowninFigure
8-7.
Listing8-15.EncryptionandDecryptionbyPassphraseandByteHashing
DECLARE@cleartextnvarchar(256);
DECLARE@encryptedvarbinary(512);
DECLARE@decryptednvarchar(256);
SELECT@cleartext=N'Tobe,ornottobe:thatisthe
question:'+
N'Whether''tisnoblerinthemindtosuffer'+
N'Theslingsandarrowsofoutrageousfortune,'+
N'Ortotakearmsagainstaseaoftroubles';
SELECT@encrypted=EncryptByPassPhrase(N'Shakespeare''s
Donkey',@cleartext);
SELECT@decrypted=CAST
(
DecryptByPassPhrase(N'Shakespeare''sDonkey',@encrypted)
ASnvarchar(128)
);
SELECT@cleartextASClearText;
SELECT@encryptedASEncrypted;
SELECT@decryptedASDecrypted;
SELECTHashBytes('SHA1',@ClearText)ASHashed;
Figure8-7.Resultsofencryptionbypassphraseandhashing
ExtensibleKeyManagement
SQLServer2014containsafeatureaddedinSQL2008knownasEKM,whichallows
youtoencryptyourSQLServerasymmetrickeys(andsymmetrickeys)withkeys
generatedandstoredonathird-partyHSM.TouseEKM,youmustfirstturnontheEKM
providerenabledoptionwithspconfigure,asshowninListing8-16.
NoteEKMisavailableonlyontheEnterprise,Developer,andEvaluationeditionsof
SQLServer2014,anditrequiresthird-partyHSMandsupportingsoftware.
Listing8-16.EnablingEKMProviders
sp_configure'showadvanced',1;
GO
RECONFIGURE;
GO
sp_configure'EKMproviderenabled',1;
GO
RECONFIGURE;
GO
Onceyou’veenabledEKMprovidersandhaveanHSMavailable,youmustregistera
cryptographicproviderwithSQLServer.Thecryptographicproviderreferencesavendor-
suppliedDLLfileinstalledontheserver.Listing8-17givesanexampleofregisteringa
cryptographicproviderwithSQLServer.
Listing8-17.RegisteringaCryptographicProvider
CREATECRYPTOGRAPHICPROVIDEREagle_EKM_Provider
FROMFILE='c:\ProgramFiles\Eagle_EKM\SQLEKM.DLL';
GO
AfteryourEKMproviderisregisteredwithSQLServer,creatinganasymmetrickey
thatisencryptedbyanexistingkeyontheHSMissimplyamatterofspecifyingtheEKM
provider,theCREATIONDISPOSITIONoption,andthenameofthekeyontheEKM
deviceviathePROVIDER_KEY_NAMEoption.Listing8-18givesanexample.
Listing8-18.CreatinganAsymmetricKeywithHSMProtection
CREATEASYMMETRICKEYAsymKeyEKMProtected
FROMPROVIDEREagle_EKM_Provider
WITHPROVIDER_KEY_NAME='EKM_Key_1',
CREATION_DISPOSITION=OPEN_EXISTING;
GO
EKMisdesignedtosupportenterprise-levelencryptionkeymanagementbyproviding
additionalencryptionkeysecurity.Itprovidesthisadditionalsecuritybyphysically
separatingtheencryptionkeysfromthedatatheyencrypt.Inadditiontoexternalstorage
ofencryptionkeys,HSMvendorscanalsoprovidehardware-basedbulkencryptionand
decryptionfunctionalityandexternalsupportforadditionalencryptionoptionsbeyond
whatissupportednativelybySQLServer2014.Someoftheadditionaloptionsprovided
byHSMvendorsincludekeyagingandkeyrotationfunctionality.
TransparentDataEncryption
Uptothispoint,we’vetalkedaboutthecolumn-levelencryptionfunctionalityavailablein
SQLServer2014.Thesefunctionsarespecificallydesignedtoencryptdatastoredinthe
columnsofyourdatabasetables.SQLServer2014providesamethodofencryption,TDE,
whichallowsyoutoencryptyourentiredatabaseatonce.
TDEautomaticallyencryptseverypageinyourdatabaseanddecryptspagesas
requiredwhenyouaccessthem.Thisfeatureallowsyoutosecureanentiredatabase
withoutworryingaboutallthoselittledetailsthatpopupwhenencryptingatthecolumn
level.TDEdoesn’trequireextrastoragespace,anditletsthequeryoptimizergeneratefar
moreefficientqueryplansthanitcanwhenyousearchonencryptedcolumns.Asan
addedbonus,TDEiseasytoimplementandallowsyoutosecurethedatainyour
databaseswithnochangestomiddle-tierorfront-endcode.
ThefirststeptoimplementTDEinyourdatabaseistocreateaservercertificate(see
Listing8-19).Aservercertificateisacertificatecreatedinthemasterdatabaseforthe
purposeofencryptingdatabaseswithTDE.
Listing8-19.CreatingaServerCertificate
CREATECERTIFICATEServerCert
WITHSUBJECT='ServerCertificateforTDE',
EXPIRY_DATE='2022-12-31';
GO
TipRemembertobackupyourservercertificateimmediatelyafteryoucreateit!
Onceyou’vecreatedaservercertificate,youcancreateadatabaseencryptionkeyin
thedatabasetobeencrypted(seeListing8-20).Thedatabaseencryptionkeyiscreated
withtheCREATEDATABASEENCRYPTIONKEYstatement.Usingthisstatement,you
cancreateakeyusingoneofthefourdifferentalgorithmslistedinTable8-3.
Listing8-20.CreatingasDatabaseEncryptionKeyandSecuringtheDatabase
USEAdventureWorks2014;
GO
CREATEDATABASEENCRYPTIONKEY
WITHALGORITHM=AES_128
ENCRYPTIONBYSERVERCERTIFICATEServerCert;
GO
ALTERDATABASEAdventureWorks2014
SETENCRYPTIONON;
GO
Table8-3.DatabaseEncryptionKeyAlgorithms
Algorithm Description
AES_128 AES,127-bitkey
AES_192 AES,192-bitkey
AES_256 AES,256-bitkey
TRIPLE_DES_3KEY Three-key3DES,~112-biteffectivekey
Theobviousquestionatthispointis,becauseTDEissosimpleandsecure,whynot
useitallthetime?Well,thesimplicityandsecurityofTDEcomeatacost.Whenyou
encryptadatabasewithTDE,SQLServeralsoencryptsthedatabaselogfileandthe
tempdbdatabase.Thisisdonetopreventleakeddatathatahackerwiththerighttools
mightbeabletoaccess.Becausetempdbisencrypted,theperformanceofeverydatabase
onthesameservertakesahit.Also,SQLServerincursadditionalCPUoverheadbecause
ithastodecryptnoncacheddatapagesthatareaccessedbyqueries.
Summary
BackinthedaysofSQLServer2000,databaseencryptionfunctionalitycouldbeachieved
onlythroughthird-partytoolsorbycreatingyourownencryptionanddecryption
functions.SQLServer2014continuesthetraditionofT-SQLcolumn-levelencryptionand
decryptionfunctionalityintroducedinSQLServer2005.ThetightintegrationofWindows
DPAPIencryptionfunctionalitywithnativeT-SQLstatementsandfunctionsmakes
databaseencryptioneasierandmoresecurethanever.
SQLServer2012introducednewfunctionality,includingTDEforquicklyandeasily
encryptingentiredatabasestransparently,andEKMforprovidingaccesstothird-party
HSMstoimplemententerprise-levelsecuritysolutionsandbulkencryptionfunctionality.
ThischapterdiscussedtheSQLServerhierarchicalencryptionmodel,whichdefines
therelationshipbetweenSMKs,DMKs,certificates,asymmetrickeys,andsymmetric
keys.SQLServerprovidesavarietyofT-SQLstatementstocreateandmanageencryption
keysandcertificates,whichyousawdemonstratedincodeexamplesthroughoutthe
chapter.SQLServeralsoprovidesseveralfunctionsforgeneratingone-wayhashes,
generatingdatasignatures,andencryptingdatabycertificate,asymmetrickey,symmetric
key,andpassphrase.
ThenextchaptercoversthetopicsofSQLwindowingfunctionsandcommontable
expressions(CTEs).
EXERCISES
1. [True/False]Symmetrickeyscanbeusedtoencryptother
symmetrickeysordata.
2. [Chooseallthatapply]SQLServerprovidesnativesupportfor
whichofthefollowingbuilt-inencryptionalgorithms?
a. DES
b. AES
c. Loki
d. Blowfish
e. RC4
3. [True/False]SQLServer2014T-SQLincludesaBACKUP
ASYMMETRICKEYstatement.
4. [Fillintheblank]Youmustsetthe___________optiontoturnon
EKMforyourserver.
5. [True/False]TDEautomaticallyencryptsthetempdb,model,and
masterdatabases.
6. [True/False]SQLServerautomaticallygeneratesrandom
initializationvectorswhenyouusesymmetricencryption.
CHAPTER9
CommonTableExpressionsand
WindowingFunctions
SQLServer2014continuessupportfortheextremelyusefulcommontableexpression
(CTE),firstintroducedinSQLServer2005.CTEscansimplifyyourqueriestomakethem
morereadableandmaintainable.SQLServeralsosupportsself-referentialCTEs,which
makeforverypowerfulrecursivequeries.
Inaddition,SQLServersupportswindowingfunctions,whichallowyoutopartition
yourresultsandapplynumberingandrankingvaluestotherowsintheresult-set
partitions.ThischapterbeginswithadiscussionofthepowerandbenefitsofCTEsand
finisheswithadiscussionofSQLServerwindowingfunctions.
CommonTableExpressions
CTEsareapowerfuladditiontoSQLServer.ACTEismoreliketemporarytablethat
generatesanamedresultsetthatexistsonlyduringthelifeofasinglequeryorDML
statementoruntilexplicitlydropped.ACTEisbuiltinthesamecodelineastheSELECT
statementortheDMLstatementthatusesit,whereascreatingandusingatemporarytable
isusuallyatwo-stepprocess.CTEsofferseveralbenefitsoverderivedtablesandviews,
includingthefollowing:
CTEsaretransient,existingonlyforthelifeofasinglequeryorDML
statement.Thismeansyoudon’thavecreatethemaspermanent
databaseobjectslikeviews.
AsingleCTEcanbereferencedmultipletimesbynameinasingle
queryorDMLstatement,makingyourcodemoremanageable.
Derivedtableshavetoberewrittenintheirentiretyeveryplacethey’re
referenced.
CTEscanbeusedtoenablegroupingbycolumnsthatarederived
fromascalarsubsetorafunctionthatisn’tdeterministic.
CTEscanbeself-referencing,providingapowerfulrecursion
mechanism.
QueriesreferencingaCTEcanbeusedtodefineacursor.
CTEscanrangeincomplexityfromextremelysimpletohighlyelaborateconstructs.
AllCTEsbeginwiththeWITHkeywordfollowedbythenameoftheCTEandalistofthe
columnsitreturns.ThisisfollowedbytheASkeywordandthebodyoftheCTE,whichis
theassociatedqueryorDMLstatementwithasemicolonasaterminatorfora
multistatementbatch.Listing9-1isaverysimpleexampleofaCTEdesignedtoshowthe
basicsyntax.
Listing9-1.SimpleCTE
WITHGetNamesCTE(BusinessEntityID,FirstName,
MiddleName,LastName)
AS
(
SELECT
BusinessEntityID,FirstName,MiddleName,LastName
FROMPerson.Person
)
SELECT
BusinessEntityID,
FirstName,
MiddleName,
LastName
FROMGetNamesCTE;
InListing9-1,theCTEisdefinedwiththenameGetNamesCTEandreturnscolumns
namedBusinessEntityID,FirstName,MiddleName,andLastName.TheCTE
bodyconsistsofasimpleSELECTstatementfromtheAdventureWorks2014
Person.Persontable.TheCTEhasanassociatedSELECTstatementimmediately
followingit.TheSELECTstatementreferencestheCTEinitsFROMclause.
WITHOVERLOADED
TheWITHkeywordisoverloadedinSQLServer,meaningit’susedinmanydifferent
waysformanydifferentpurposesinT-SQL.It’susedtospecifyadditionaloptionsin
DDLCREATEstatements,toaddtablehintstoqueriesandDMLstatements,andto
declareXMLnamespaceswhenusedintheWITHXMLNAMESPACESclause,justto
nameafew.Nowit’salsousedasthekeywordthatindicatesthebeginningofaCTE
definition.Becauseofthis,wheneveraCTEisn’tthefirststatementinabatch,the
statementprecedingitmustendwithasemicolon.Thisisonereasonwestrongly
recommendusingthestatement-terminatingsemicolonthroughoutyourcode.
SimpleCTEshavesomerestrictionsontheirdefinitionanddeclaration:
ACTEmustbefollowedbysingleINSERT,DELETE,UPDATE,or
SELECTstatement.
AllcolumnsreturnedbyaCTEmusthaveauniquename.Ifallthe
columnsreturnedbythequeryintheCTEbodyhaveuniquenames,
youcanleavethecolumnlistoutoftheCTEdeclaration.
ACTEcanreferenceotherpreviouslydefinedCTEsinthesame
WITHclause,butitcan’treferenceCTEsdefinedafterthecurrent
CTE(knownasaforwardreference).
Youcan’tusethefollowingkeywords,clauses,andoptionsinaCTE:
COMPUTE,COMPUTEBY,FORBROWSE,INTO,andOPTION
(queryhint).Also,youcan’tuseORDERBYunlessyouspecifythe
TOPclause.
MultipleCTEscanbedefinedinanonrecursiveCTE.Allthe
definitionsmustbecombinedwithoneofthesesetoperators:UNION
ALL,UNION,INTERSECT,orEXCEPT.
Asmentionedinthe“WITHOverloaded”sidebar,whenaCTEisnot
thefirststatementinabatch,theprecedingstatementmustendwitha
semicolonstatementterminator.
KeeptheserestrictionsinmindwhenyoucreateCTEs.
MultipleCommonTableExpressions
YoucandefinemultipleCTEsforasinglequeryorDMLstatementbyseparatingyour
CTEdefinitionswithcommas.Themainreasonfordoingthisistosimplifyyourcodeto
makeiteasiertoreadandmanage.CTEsprovideameansofvisuallysplittingyourcode
intosmallerfunctionalblocks,makingiteasiertodevelopanddebug.ThequeryinListing
9-2includesmultipleCTEs,withthesecondCTEreferencingthefirst.Theresultsare
showninFigure9-1.
Listing9-2.MultipleCTEs
WITHGetNamesCTE(BusinessEntityID,FirstName,
MiddleName,LastName)
AS(
SELECT
BusinessEntityID,FirstName,
MiddleName,LastName
FROMPerson.Person),
GetContactCTE(BusinessEntityID,FirstName,
MiddleName,LastName,
Email,HomePhoneNumber
)
AS(
SELECTgn.BusinessEntityID,gn.FirstName
,gn.MiddleName,gn.LastName
,ea.EmailAddress,pp.PhoneNumber
FROMGetNamesCTEgn
LEFTJOINPerson.EmailAddressea
ONgn.BusinessEntityID=ea.BusinessEntityID
LEFTJOINPerson.PersonPhonepp
ONgn.BusinessEntityID=pp.BusinessEntityID
ANDpp.PhoneNumberTypeID=2)
SELECTBusinessEntityID,FirstName
,MiddleName,LastName
,Email,HomePhoneNumber
FROMGetContactCTE;
Figure9-1.PartialresultsofaquerywithmultipleCTEs
CTEReadabilityBenefits
YoucanuseCTEstomakeyourqueriesmorereadablethanequivalentquerydesignsthat
usenestedsubqueries.Todemonstrate,thefollowingqueryusesnestedsubqueriesto
returnthesameresultastheCTE-basedqueryinListing9-2:
SELECT
gn.BusinessEntityID,
gn.FirstName,
gn.MiddleName,
gn.LastName,
gn.EmailAddress,
gn.HomePhoneNumber
FROM
(
SELECT
p.BusinessEntityID,
p.FirstName,
p.MiddleName,
p.LastName,
ea.EmailAddress,
ea.HomePhoneNumber
FROMPerson.Personp
LEFTJOIN
(
SELECT
ea.BusinessEntityID,
ea.EmailAddress,
pp.HomePhoneNumber
FROMPerson.EmailAddressea
LEFTJOIN
(
SELECT
pp.BusinessEntityID,
pp.PhoneNumberASHomePhoneNumber,
pp.PhoneNumberTypeID
FROMPerson.PersonPhonepp
)pp
ONea.BusinessEntityID=pp.BusinessEntityID
ANDpp.PhoneNumberTypeID=2
)ea
ONp.BusinessEntityID=ea.BusinessEntityID
)gn
TheCTE-basedversionofthisquery,asshowninListing9-2,simplifiesthecode,
encapsulatesthequerylogic,andismucheasiertoreadandunderstandthanthenested
subqueryversion,whichmakesiteasiertodebugandmaintaininthelongterm.
TheexampleinListing9-2containstwoCTEsnamedGetNamesCTEand
GetContactCTE.GetNamesCTEisborrowedfromListing9-1;itretrievesthe
namesfromthePerson.Persontable:
WITHGetNamesCTE(BusinessEntityID,FirstName,MiddleName,
LastName)
AS
(
SELECT
BusinessEntityID,FirstName,
MiddleName,LastName
FROMPerson.Person
)
ThesecondCTE,GetContactCTE,joinstheresultsofGetNamesCTEtothe
Person.EmailAddressandPerson.PersonPhonetables:
GetContactCTE
(
BusinessEntityID,FirstName,MiddleName,LastName,Email,
HomePhoneNumber
)
AS(
SELECTgn.BusinessEntityID,gn.FirstName
,gn.MiddleName,gn.LastName
,ea.EmailAddress,pp.PhoneNumber
FROMGetNamesCTEgn
LEFTJOINPerson.EmailAddressea
ONgn.BusinessEntityID=ea.BusinessEntityID
LEFTJOINPerson.PersonPhonepp
ONgn.BusinessEntityID=pp.BusinessEntityID
ANDpp.PhoneNumberTypelD=2)
NoticethattheWITHkeywordisusedonlyonceatthebeginningoftheentire
statement.ThesecondCTEdeclarationisseparatedfromthefirstbyacommaanddoesn’t
accepttheWITHkeyword.Finally,noticehowsimpleandreadabletheSELECTquery
associatedwiththeCTEsbecomeswhenthejoinsaremovedintoCTEs:
SELECT
BusinessEntityID,
FirstName,
MiddleName,
LastName,
EmailAddress,
HomePhoneNumber
FROMGetContactCTE;
TipYoucanreferenceaCTEfromwithinthebodyofanotherCTE,fromthe
associatedqueryorDMLstatement.BothtypesofCTEreferencesareshowninListing9-
2—GetNamesCTEisreferencedbyGetContactCTE,andGetContactCTEis
referencedinthequeryassociatedwiththeCTEs.
RecursiveCommonTableExpressions
ArecursiveCTEisonewheretheinitialCTEisexecutedrepeatedlytoreturnasubsetof
thedatauntilthecompleteresultsetisreturned.ACTEcanreferenceitselfinthebodyof
theCTE,whichisapowerfulfeatureforqueryinghierarchicaldatastoredintheadjacency
listmodel.RecursiveCTEsaresimilartononrecursiveCTEs,exceptthatthebodyofthe
CTEconsistsofmultiplesetsofqueriesthatgenerateresultsetswithmultiplerows
unionedtogetherwiththeUNIONALLsetoperator.Atleastoneofthequeriesinthe
bodyoftherecursiveCTEmustnotreferencetheCTE;thisqueryisknownastheanchor
query.RecursiveCTEsalsocontainoneormorerecursivequeriesthatreferencetheCTE.
Theserecursivequeriesareunionedtogetherwiththeanchorquery(orqueries)inthe
bodyoftheCTE.RecursiveCTEsrequireatop-levelUNIONALLoperatortounionthe
recursiveandnonrecursivequeriestogether.Multipleanchorqueriesmaybeunioned
togetherwithINTERSECT,EXCEPT,andUNIONoperators,andmultiplerecursive
queriescanbeunionedtogetherwithUNIONALL.Therecursionstopswhennorowsare
returnedfromthepreviousquery.Listing9-3isasimplerecursiveCTEthatretrievesa
resultsetconsistingofthenumbers1through10.
Listing9-3.SimpleRecursiveCTE
WITHNumbers(n)
AS(
SELECT1ASn
UNIONALL
SELECTn+1
FROMNumbers
WHEREn<10)
SELECTnFROMNumbers;
TheCTEinListing9-3beginswithadeclarationthatdefinestheCTEnameandthe
columnreturned:
WITHNumbers(n)
TheCTEbodycontainsasingleanchorquerythatreturnsasinglerowwiththe
number1inthencolumn:
SELECT1ASn
TheanchorqueryisunionedtogetherwiththerecursivequeryusingtheUNIONALL
setoperator.Therecursivequerycontainsaself-referencetotheNumbersCTE,adding1
tothencolumnwitheachrecursivereference.TheWHEREclauselimitstheresultsetto
thefirsttennumbers:
SELECTn+1FROMNumbersWHEREn<10
RecursiveCTEshaveamaximumrecursionlevelof100bydefault.Thismeansthe
recursivequeryintheCTEbodycanonlycallitself100times.Youcanusethe
MAXRECURSIONoptiontoincreasethemaximumrecursionlevelofCTEsonan
individualbasis.Listing9-4modifiestheCTEinListing9-3toreturnthenumbers1to
1,000.ThemodifiedqueryusestheMAXRECURSIONoptiontoincreasethemaximum
recursionlevel.WithouttheMAXRECURSIONoption,thisCTEwoulderroroutafterthe
first100levelsofrecursion.
Listing9-4.RecursiveCTEwiththeMAXRECURSIONOption
WITHNumbers(n)
AS(
SELECT0ASn
UNIONALL
SELECTn+1
FROMNumbers
WHEREn<1000)
SELECTn
FROMNumbersOPTION(MAXRECURSION1000);
TheMAXRECURSIONvaluespecifiedmustbebetween0and32,767.SQLServer
throwsanexceptioniftheMAXRECURSIONlimitissurpassed.AMAXRECURSIONvalue
of0indicatesthatnolimitshouldbeplacedonrecursionfortheCTE.Becarefulwiththis
option—ifyoudon’tproperlylimittheresultsinthequerywithaWHEREclause,youcan
easilyendupinaninfiniteloop.
TipCreatingapermanenttableofcountingnumberscanbemoreefficientthanusinga
recursiveCTEtogeneratenumbers,particularlyifyouplantoexecutetheCTEsthat
generatenumbersoften.
RecursiveCTEsareusefulforqueryingdatastoredinahierarchicaladjacencylist
format.Theadjacencylistprovidesamodelforstoringhierarchicaldatainrelational
databases.Intheadjacencylistmodel,eachrowofthetablecontainsapointertoitsparent
inthehierarchy.TheProduction.BillOfMaterialstableintheAdventureWorks
databaseisapracticalexampleoftheadjacencylistmodel.Thistablecontainstwo
importantcolumns,ComponentIDandProductAssemblyID,thatreflectthe
hierarchicalstructure.ComponentIDisauniquenumberidentifyingeverycomponent
thatAdventureWorksusestomanufactureitsproducts.ProductAssemblyIDisa
parentcomponentcreatedfromoneormoreAdventureWorksproductcomponents.Figure
9-2showstherelationshipbetweencomponentsandproductassembliesinthe
AdventureWorksdatabase.
Figure9-2.Component/productassemblyrelationship
TherecursiveCTEshowninListing9-5retrievesthecompleteAdventureWorks
hierarchicalbillofmaterials(BOM)foraspecifiedcomponent.Thecomponentusedin
theexampleistheAdventureWorkssilverMountain-10048-inchbike,ComponentID
774.PartialresultsareshowninFigure9-3.
Listing9-5.RecursiveBOMCTE
DECLARE@ComponentIDint=774;
WITHBillOfMaterialsCTE
(
BillOfMaterialsID,
ProductAssemblyID,
ComponentID,
Quantity,
Level
)
AS
(
SELECT
bom.BillOfMaterialsID,
bom.ProductAssemblyID,
bom.ComponentID,
bom.PerAssemblyQtyASQuantity,
0ASLevel
FROMProduction.BillOfMaterialsbom
WHEREbom.ComponentID=@ComponentID
UNIONALL
SELECT
bom.BillOfMaterialsID,
bom.ProductAssemblyID,
bom.ComponentID,
bom.PerAssemblyQty,
Level+1
FROMProduction.BillOfMaterialsbom
INNERJOINBillOfMaterialsCTEbomcte
ONbom.ProductAssemblyID=bomcte.ComponentID
WHEREbom.EndDateISNULL
)
SELECT
bomcte.ProductAssemblyID,
p.ProductID,
p.ProductNumber,
p.Name,
p.Color,
bomcte.Quantity,
bomcte.Level
FROMBillOfMaterialsCTEbomcte
INNERJOINProduction.Productp
ONbomcte.ComponentID=p.ProductID
ORDERBYbomcte.Level;
Figure9-3.PartialresultsoftherecursiveBOMCTE
LikethepreviousCTEexamples,Listing9-3beginswiththeCTEnameandcolumn
listdeclaration:
WITHBillOfMaterialsCTE
(
BillOfMaterialsID,ProductAssemblylD,Components,
Quantity,Level
)
TheanchorquerysimplyretrievestherowfromthetablewheretheComponentID
matchesthespecifiedID.Thisisthetop-levelcomponentintheBOM,setto774inthis
case.NoticethattheCTEcanreferenceT-SQLvariableslike@ComponentIDinthe
example:
SELECT
bom.BillOfMaterialsID,
bom.ProductAssemblylD,
bom.Components,
bom.PerAssemblyQtyASQuantity,
0ASLevel
FROMProduction.BillOfMaterialsbom
WHEREbom.ComponentID=@ComponentID
TherecursivequeryretrievessuccessivelevelsoftheBOMfromtheCTEwherethe
ProductAssemblyIDofeachrowmatchestheComponentIDofthehigher-level
rows.Thatistosay,therecursivequeryoftheCTEretrieveslower-levelrowsinthe
hierarchythatmatchthehierarchicalrelationshippreviouslyillustratedinFigure9-2:
SELECT
bom.BillOfMaterialsID,
bom.ProductAssemblyID,
bom.ComponentID,
bom.PerAssemblyQty,
Level+1
FROMProduction.BillOfMaterialsbom
INNERJOINBillOfMaterialsCTEbomcte
ONbom.ProductAssemblyID=bomcte.ComponentID
WHEREbom.EndDateISNULL
TheCTEhasaSELECTstatementassociatedwithitthatjoinstheresultstothe
Production.Producttabletoretrieveproduct-specificinformationlikethename
andcolorofthecomponent:
SELECT
bomcte.ProductAssemblyID,
p.ProductID,
p.ProductNumber,
p.Name,
p.Color,
bomcte.Quantity,
bomcte.Level
FROMBillOfMaterialsCTEbomcte
INNERJOINProduction.Productp
ONbomcte.ComponentID=p.ProductID;
TherestrictionsonsimpleCTEsdescribedearlierinthischapteralsoapplyto
recursiveCTEs.Inaddition,thefollowingrestrictionsapplyspecificallytorecursive
CTEs:
RecursiveCTEsmusthaveatleastoneanchorqueryandatleastone
recursivequeryspecifiedinthebodyoftheCTE.Allanchorqueries
mustappearbeforeanyrecursivequeries.
AllanchorqueriesmustbeunionedwithaUNION,UNIONALL,
INTERSECT,orEXCEPTsetoperator.Whenusingmultipleanchor
queriesandrecursivequeries,thelastanchorqueryandthefirst
recursivequerymustbeunionedtogetherwiththeUNIONALL
operator.Additionally,allrecursivequeriesmustbeunionedtogether
withUNIONALL.
Thedatatypesofallcolumnsintheanchorqueriesandrecursive
queriesmustmatch.
ThefromclauseoftherecursivemembershouldrefertotheCTE
nameonlyonce.
Therecursivequeriescan’tcontainthefollowingoperatorsand
keywords:GROUPBY,HAVING,LEFTJOIN,RIGHTJOIN,
OUTERJOIN,andSELECTDISTINCT.Recursivequeriesalso
can’tcontainaggregatefunctions(likeSUMandMAX),windowing
functions,subqueries,orhintsontherecursiveCTEreference.
WindowingFunctions
SQLServer2014supportswindowingfunctionsthatpartitionresultsandcanapply
numbering,ranking,andaggregatefunctionstoeachpartition.Thekeytowindowing
functionsistheOVERclause,whichallowsyoutodefinethepartitions,andinsomecases
theorderingofrowsinthepartition,foryourdata.ThissectiondiscussesSQLServer
2014windowingfunctionsandthenumbering,ranking,andaggregatefunctionsthat
supporttheOVERclause.
ROW_NUMBERFunction
TheROW_NUMBERfunctiontakestheOVERclausewithanORDERBYclauseandan
optionalPARTITIONBYclause.Listing9-6retrievesnamesfromthe
Person.Persontable.TheOVERclauseisusedtopartitiontherowsbyLastName
andordertherowsineachpartitionbyLastName,FirstName,andMiddleName.
TheROW_NUMBERfunctionisusedtoassignanumbertoeachrow.
Listing9-6.ROW_NUMBERwithPartitioning
SELECT
ROW_NUMBER()OVER
(
PARTITIONBY
LastName
ORDERBY
LastName,
FirstName,
MiddleName
)ASNumber,
LastName,
FirstName,
MiddleName
FROMPerson.Person;
ThepartitioncreatedinListing9-6actsasawindowthatslidesoveryourresultset
(hencethenamewindowingfunction).TheORDERBYclauseorderstherowsofeach
partitionbyLastName,FirstName,andMiddleName.SQLServerappliesthe
ROW_NUMBERfunctiontoeachpartition.ThenetresultisthattheROW_NUMBERfunction
numbersallrowsintheresultset,restartingthenumberingat1everytimeitencountersa
newLastName,asshowninFigure9-4.
Figure9-4.UsingROW_NUMBERtonumberrowsinpartitions
NoteWhenPARTITIONBYisused,itmustappearbeforeORDERBYintheOVER
clause.
TheROW_NUMBERfunctioncanalsobeusedwithoutthePARTITIONBYclause,in
whichcasetheentireresultsetistreatedasonepartition.Treatingtheentireresultsetasa
singlepartitioncanbeusefulinsomecases,butit’smorecommontopartition.
QueryPagingwithOFFSET/FETCH
SQLServergivesyouvariousoptionsforpagingthroughresultsets.Thetraditionalway
ofpaginatingistousetheTOPoperatortoselecttheTOPnnumberofrowsreturnedby
thequery.SQLServer2005introducedROW_NUMBER,whichyoucanusetoachievethe
samefunctionalityinaslightlydifferentmanner.SQLServer2012introducednew
keywordsintheSELECTstatementspecificallyinsupportofquerypagination.
TheOFFSETkeywordprovidessupportformucheasierpagination.Itessentially
allowsyoutospecifytherowfromwhichyouwanttostartreturningthedata.FETCH
thenletsyoureturnaspecifiednumberofrowsintheresultset.IfyoucombineOFFSET
andFETCH,alongwiththeORDERBYclause,youcanreturnanypartofthedatayou
likefromtheresultset,pagingthroughthedataasdesired.
Listing9-7showstheapproachtopaginationusingOFFSETandFETCH.Thestored
procedureusestheOFFSETandFETCHclausestoretrieverowsfromthe
Person.PersontableintheAdventureWorksdatabasebasedoninputparameter
valuesspecifiedintheprocedurecall.Theproceduredetermineshowthepaginationis
determinedusingthe@RowsPerPageand@StartPageNuminputparameters.
@RowsPerPagedetermineshowmanyrowsperpageshouldbeincludedintheresult
set.@StartPageNumdeterminesthepageforwhichtheresultsetshouldbereturned.
OFFSETspecifiesthenumberofrowstoskipfromthebeginningofthepossiblequery
result.FETCHspecifiesthenumberofrowstoreturnineachquerypage.
Listing9-7.OFFSET/FETCHExample
CREATEPROCEDUREPerson.GetContacts
@StartPageNumint,
@RowsPerPageint
AS
SELECT
LastName,
FirstName,
MiddleName
FROMPerson.Person
ORDERBY
LastName,
FirstName,
MiddleName
OFFSET(@StartPageNum-1)*@RowsPerPageROWS
FETCHNEXT@RowsPerPageROWSONLY;
GO
TheexampleprocedurecallthatusestheOFFSET/FETCHclauseEXEC
Person.GetContacts16,10passesan@RowsPerPageparametervalueof10
andan@StartPageNumparametervalueof16totheprocedureandreturnsthe10rows
forthe16thpage,asshowninFigure9-5.TheOFFSETkeywordintheSELECT
statementskipstherowsbeforethepagenumberspecifiedin@StartPageNumand
@RowsPerPage.Thisexampleskips150rowsandbeginstoreturnresultsatthe151st
row.TheFETCHkeywordreturnsthenumberofrowsspecifiedby@RowsPerPage(10).
ThequeryplanisshowninFigure9-6.
Figure9-5.UsingOFFSETandFETCHtoimplementclient-sidepaging
Figure9-6.Queryplanfortheclient-sidepagingimplementationusingOFFSETandFETCH
ThequeryinListing9-7isamuchmorereadableandelegantsolutionforquery
paginationthanusingtheTopclauseorROW_NUMBERfunctionwithCTEs.Theonly
exceptionwouldbeifyou’reusingOFFSET/FETCHandwanttoretrieveROW_NUMBER;
inthatcase,youwouldhavetoaddROW_NUMBERtoyourquery.Thusthe
OFFSET/FETCHclauseprovidesamuchcleanerwaytoimplementadhocpagination.
Therearesomerestrictions,though.KeepthefollowinginmindwhenusingOFFSET
andFETCH:
OFFSETandFETCHmustbeusedwithanORDERBYclause.
FETCHcan’tbeusedwithoutOFFSET;however,OFFSETcanbe
usedwithoutFETCH.
ThenumberofrowsspecifiedusingtheOFFSETclausemustbe
greaterthanorequalto0.
ThenumberofrowsspecifiedbytheFETCHclausemustbegreater
thanorequalto1.
QueriesthatuseOFFSETandFETCHcan’tusetheTOPoperator.
TheOFFSET/FETCHvaluesmustbeconstants,ortheymustbe
parametersthathaveintegervalues.
OFFSETandFETCHaren’tsupportedwiththeOVERclause.
OFFSETandFETCHaren’tsupportedwithindexedviewsorthe
view’sWITHCHECKOPTION.
Ingeneral,underSQLServer2012orlater,thecombinationofOFFSETandFETCH
providesforthecleanestapproachtopaginatingthroughqueryresults.
TheRANKandDENSE_RANKFunctions
TheRANKandDENSE_RANKfunctionsareSQLServer’srankingfunctions.Theyboth
assignanumericrankvaluetoeachrowinapartition;however,thedifferenceliesinhow
tiesaredealtwith.Forexample:
Ifyouhavethreevalues7,7,and9,thenRANKassignsranksas1,1,
and3.That’sbecausethetwo7saretiedforfirstplace,whereasthe9
isthirdinthelist.RANKdoesn’trespecttheearliertiewhen
computingtherankforthevalue9.
ButDENSE_RANKassignsranks1,1,and2.That’sbecause
DENSE_RANKlumpsboth7stogetherinrank1anddoesn’tcount
themseparatelywhencomputingtherankforthevalue9.
There’snorightorwrongwaytorankyourdata,absentanybusinessrequirements.
SQLServerprovidestwooptions,andyoucanchoosetheonethatfitsyourbusinessneed.
SupposeyouwanttofigureoutAdventureWorks’bestone-daysalesdatesforthe
calendaryear2012.Thisscenariocanbephrasedwithabusinessquestionlike,“What
werethebestone-daysalesdaysin2012?”RANKcaneasilygiveyouthatinformation,as
showninListing9-8.PartialresultsareshowninFigure9-7.
Listing9-8.RankingAdventureWorks’DailySalesTotals
WITHTotalSalesBySalesDate
(
DailySales,
OrderDate
)
AS
(
SELECT
SUM(soh.SubTotal)ASDailySales,
soh.OrderDate
FROMSales.SalesOrderHeadersoh
WHEREsoh.OrderDate>='20120101'
ANDsoh.OrderDate<'20130101'
GROUPBYsoh.OrderDate
)
SELECT
RANK()OVER
(
ORDERBY
DailySalesDESC
)ASRanking,
DailySales,
OrderDate
FROMTotalSalesBySalesDate
ORDERBYRanking;
Figure9-7.RankingAdventureWorks’dailysalestotals
Listing9-8isaCTEthatreturnstwocolumns:DailySalesandOrderDate.
DailySalesisthesumofallsalesgroupedbyOrderDate.Theresultsarelimitedby
theWHEREclausetoincludeonlysalesinthe2012salesyear:
WITHTotalSalesBySalesDate
(
DailySales,
OrderDate
)
AS
(
SELECT
SUM(soh.SubTotal)ASDailySales,
soh.OrderDate
FROMSales.SalesOrderHeadersoh
WHEREsoh.OrderDate>='20120101'
ANDsoh.OrderDate<'20130101'
GROUPBYsoh.OrderDate
)
TheRANKfunctionisusedwiththeOVERclausetoapplyrankingvaluestotherows
returnedbytheCTEindescendingorder(highesttolowest)bytheDailySales
column:
SELECT
RANK()OVER(ORDERBY
DailySalesDESC)ASRanking,DailySales,OrderDate
FROMTotalSalesBySalesDateORDERBYRanking;
LiketheROW_NUMBERfunction,RANKcanacceptthePARTITIONBYclauseinthe
OVERclause.Listing9-9buildsonthepreviousexampleandusesthePARTITIONBY
clausetorankthedailysalesforeachmonth.Thistypeofquerycananswerabusiness
questionlike,“WhatwereAdventureWorks’bestone-daysalesdaysforeachmonthof
2012?”PartialresultsareshowninFigure9-8.
Listing9-9.DeterminingtheDailySalesRankings,PartitionedbyMonth
WITHTotalSalesBySalesDatePartitioned
(
DailySales,
OrderMonth,
OrderDate
)
AS
(
SELECT
SUM(soh.SubTotal)ASDailySales,
DATENAME(MONTH,soh.OrderDate)ASOrderMonth,
soh.OrderDate
FROMSales.SalesOrderHeadersoh
WHEREsoh.OrderDate>='20120101'
ANDsoh.OrderDate<'20130101'
GROUPBYsoh.OrderDate
)
SELECT
RANK()OVER
(
PARTITIONBY
OrderMonth
ORDERBY
DailySalesDESC
)ASRanking,
DailySales,
OrderMonth,
OrderDate
FROMTotalSalesBySalesDatePartitioned
ORDERBYDATEPART(mm,OrderDate),
Ranking;
Figure9-8.Partialresultsofdailysalesrankings,partitionedbymonth
ThequeryinListing9-9,liketheexampleinListing9-8,beginswithaCTEto
calculateone-daysalestotalsfortheyear.ThemaindifferencesbetweenthisCTEandthe
previousexamplearethatListing9-9returnsanadditionalOrderMonthcolumnandthe
resultsarelimitedtotheyear2012.HereisthatCTE:
WITHTotalSalesBySalesDatePartitioned
(
DailySales,
OrderMonth,
OrderDate
)
AS
(
SELECT
SUM(soh.SubTotal)ASDailySales,
DATENAME(MONTH,soh.OrderDate)ASOrderMonth,
soh.OrderDate
FROMSales.SalesOrderHeadersoh
WHEREsoh.OrderDate>='20120101'
ANDsoh.OrderDate<'20130101'
GROUPBYsoh.OrderDate
)
TheSELECTqueryassociatedwiththeCTEusestheRANKfunctiontoassign
rankingstotheresults.ThePARTITIONBYclauseisusedtopartitiontheresultsby
OrderMonthsothattherankingsrestartat1foreachnewmonth.Forexample:
SELECT
RANK()OVER
(
PARTITIONBYOrderMonth
ORDERBY
DailySalesDESC
)ASRanking,
DailySales,
OrderMonth,
OrderDate
FROMTotalSalesBySalesDatePartitioned
ORDERBYDATEPART(mm,OrderDate),
Ranking;
WhentheRANKfunctionencounterstwoequalDailySalesamountsinthesame
partition,itassignsthesameranknumbertobothandskipsthenextnumberinthe
ranking.AsshowninFigure9-9,theDailySalestotalfortwodaysinOctober2012
was$7479.3221,resultingintheRANKfunctionassigningthetwodaysaRankingvalue
of25.TheRANKfunctionthenskipsRankingvalue26andassignsthenextrowa
Rankingof27.
Figure9-9.TheRANKfunctionskipsavalueinthecaseofatie
DENSE_RANK,likeRANK,assignsduplicatevaluesthesamerank,butwithone
importantdifference:itdoesn’tskipthenextrankinginthelist.Listing9-10modifies
Listing9-9tousetheRANKandDENSE_RANKfunctions.AsyoucanseeinFigure9-10,
DENSE_RANKstillassignsthesameRankingtobothrowsintheresult,butitdoesn’t
skipthenextRankingvalue,whereasRANKdoes.
Listing9-10.UsingDENSE_RANKtoRanktheBestDailySalesperMonth
WITHTotalSalesBySalesDatePartitioned
(
DailySales,
OrderMonth,
OrderDate
)
AS
(
SELECT
SUM(soh.SubTotal)ASDailySales,
DATENAME(MONTH,soh.OrderDate)ASOrderMonth,
soh.OrderDate
FROMSales.SalesOrderHeadersoh
WHEREsoh.OrderDate>='20120101'
ANDsoh.OrderDate<'20130101'
GROUPBYsoh.OrderDate
)
SELECT
RANK()OVER
(
PARTITIONBY
OrderMonth
ORDERBY
DailySalesDESC
)ASRanking,
DENSE_RANK()OVER
(
PARTITIONBY
OrderMonth
ORDERBY
DailySalesDESC
)ASDense_Ranking,
DailySales,
OrderMonth,
OrderDate
FROMTotalSalesBySalesDatePartitioned
ORDERBYDATEPART(mm,OrderDate),
Ranking;
Figure9-10.DENSE_RANKdoesn’tskiprankingvaluesafteratie
TheNTILEFunction
NTILEisanotherrankingfunctionthatfulfillsaslightlydifferentneed.Thisfunction
dividesyourresultsetintoapproximaten-tiles.Ann-tilecanbeaquartile(1/4,or25%
slices),aquintile(1/5,or20%slices),apercentile(1/100,or1%slices),orjustaboutany
otherfractionalsliceyoucanimagine.NTILEdividesresultsetsintoapproximaten-tiles
becausethenumberofrowsreturnedmaynotbeevenlydivisibleintotherequirednumber
ofgroups.Atablewith27rows,forinstance,isn’tevenlydivisibleintoquartilesor
quintiles.WhenyouqueryatablewiththeNTILEfunctionandthenumberofrowsisn’t
evenlydivisiblebythespecifiednumberofgroups,NTILEcreatesgroupsoftwodifferent
sizes.Thelargergroupsareallonerowlargerthanthesmallergroups,andthelarger
groupsarenumberedfirst.Intheexampleof27rowsdividedintoquintiles(1/5),thefirst
twogroupshave6rowseach,andthelastthreegroupshave5rowseach.
LiketheROW_NUMBERfunction,youcanincludebothPARTITIONBYandORDER
BYintheOVERclause.NTILErequiresanadditionalparameterthatspecifieshowmany
groupsitshoulddivideyourresultsinto.
NTILEisusefulforansweringbusinessquestionslike,“Whichsalespeoplemadeup
thetop4%ofthesalesforceinJuly2013?”and“Whatweretheirsalestotals?”Listing9-
11usesNTILEtodividetheAdventureWorkssalespeopleintofourgroups,eachone
representing4%ofthetotalsalesforce.TheORDERBYclauseisusedtospecifythat
rowsareassignedtothegroupsinorderoftheirtotalsales.Theresultsareshownin
Figure9-11.
Listing9-11.UsingNTILEtoGroupandRankSalespeople
WITHSalesTotalBySalesPerson
(
SalesPersonID,SalesTotal
)
AS
(
SELECT
soh.SalesPersonID,
SUM(soh.SubTotal)ASSalesTotal
FROMSales.SalesOrderHeadersoh
WHEREDATEPART(YEAR,soh.OrderDate)=2013
ANDDATEPART(MONTH,soh.OrderDate)=2
GROUPBYsoh.SalesPersonID
)
SELECT
NTILE(4)OVER(ORDERBYst.SalesTotalDESC)ASTile,
p.LastName,
p.FirstName,
p.MiddleName,
st.SalesPersonID,
st.SalesTotal
FROMSalesTotalBySalesPersonst
INNERJOINPerson.Personp
ONst.SalesPersonID=p.BusinessEntityID;
Figure9-11.AdventureWorkssalespeoplegroupedandrankedbyNTILE
ThecodebeginswithasimpleCTEthatreturnstheSalesPersonIDandsumofthe
orderSubTotalvaluesfromtheSales.SalesOrderHeadertable.TheCTElimits
itsresultstothesalesthatoccurredinthemonthofJulyintheyear2014.HereistheCTE:
WITHSalesTotalBySalesPerson(
SalesPersonID,
SalesTotal)
AS(
SELECT
son.SalesPersonID,
SUM(soh.SubTotal)ASSalesTotal
FROMSales.SalesOrderHeadersoh
WHEREDATEPART(YEAR,soh.OrderDate)=2014
ANDDATEPART(MONTH,soh.OrderDate)=7
GROUPBYsoh.SalesPersonID)
TheSELECTqueryassociatedwiththisCTEusesNTILE(4)togroupthe
AdventureWorkssalespeopleintofourgroupsofapproximately4%each.TheOVER
clausespecifiesthatthegroupsshouldbeassignedbasedonSalesTotalindescending
order.TheentireSELECTqueryisasfollows:
SELECT
NTILE(4)OVER(ORDERBYst.SalesTotalDESC)ASTile,
p.LastName,
p.FirstName,
p.MiddleName,
st.SalesPersonID,
st.SalesTotal
FROMSalesTotalBySalesPersonst
INNERJOINPerson.Personp
ONst.SalesPersonID=p.BusinessEntityID;
AggregateFunctions,AnalyticFunctions,
andtheOVERClause
Aspreviouslydiscussed,thenumberingandrankingfunctions(ROW_NUMBER,RANK,
andsoon)allworkwiththeOVERclausetodefinetheorderandpartitioningoftheirinput
rowsviatheORDERBYandPARTITIONBYclauses.TheOVERclausealsoprovides
windowingfunctionalitytoT-SQLaggregatefunctionssuchasSUM,COUNT,andSQL
CLRuser-definedaggregates.
Windowingfunctionscanhelpwithcommonbusinessquestionslikethoseinvolving
runningtotalsandslidingaverages.Forinstance,youcanapplytheOVERclausetothe
Purchasing.PurchaseOrderDetailtableintheAdventureWorksdatabasetoretrievethe
SUMofthedollarvaluesofproductsorderedintheformofarunningtotal.Youcan
furtherrestricttheresultsetinwhichyouwanttoperformtheaggregationbypartitioning
theresultsetbyPurchaseOrderId,essentiallygeneratingtherunningtotalseparatelyfor
eachpurchaseorder.AnexamplequeryisshowninListing9-12.Partialresultsareshown
inFigure9-12.
Listing9-12.UsingtheOVERClausewithSUM
SELECT
PurchaseOrderID,
ProductID,
OrderQty,
UnitPrice,
LineTotal,
SUM(LineTotal)
OVER(PARTITIONBYPurchaseOrderIDORDERBYProductId
RANGEBETWEENUNBOUNDEDPRECEDING
ANDCURRENTROW)
ASCumulativeOrderOty
FROMPurchasing.PurchaseOrderDetail;
Figure9-12.PartialresultsfromaquerygeneratingarunningSUM
NoticethefollowingnewclauseinListing9-12:
RANGEBETWEENUNBOUNDEDPRECEDINGANDCURRENTROW
Thisisknownasaframingclause.Inthiscase,itspecifiesthateachsumincludesall
valuesfromthefirstrowinthepartitionthroughtothecurrentrow.Aframingclauselike
thismakessenseonlywhenthereisordertotherows,andthatisthereasonforthe
ORDERBYProductIdclause.TheframingclauseincombinationwiththeORDER
BYclausegeneratetherunningsumthatyouseeinFigure9-12.
TipOtherframingclausesarepossible.TheRANGEBETWEENUNBOUNDED
PRECEDINGANDCURRENTROWinListing9-12willbethedefaultifnoframing
clauseisspecified.Keepthatpointinmind:it’scommonforquerywriterstobe
confoundedbyunexpectedresultsbecausetheydon’tknowadefaultframingclauseis
beingapplied.
Let’slookatanexampletoseehowthedefaultframingclausecanaffectthequery
results.Forexample,let’ssayyouwanttocalculateandreturnthetotalsalesamountby
PurchaseOrderwitheachlineitem.Basedonhowtheframingisdefined,youcanget
verydifferentresults,becausetotalcanmeangrandtotalorrunningtotal.Let’smodifythe
queryinListing9-12andspecifytheframingclauseRANGEBETWEENUNBOUNDED
PRECEDINGANDUNBOUNDEDFOLLOWINGalongwiththedefaultframingclause
andreviewtheresults.ThemodifiedqueryisshowninListing9-13,andtheresultsare
showninFigure9-13.
Listing9-13.QueryResultsDuetotheDefaultFramingSpecification
SELECT
PurchaseOrderID,
ProductID,
OrderQty,
UnitPrice,
LineTotal,
SUM(LineTotal)
OVER(PARTITIONBYPurchaseOrderIDORDERBY
ProductId)
ASTotalSalesDefaultFraming,
SUM(LineTotal)
OVER(PARTITIONBYPurchaseOrderIDORDERBY
ProductId
RANGEBETWEENUNBOUNDEDPRECEDING
ANDUNBOUNDEDFOLLOWING)
ASTotalSalesDefinedFraming
FROMPurchasing.PurchaseOrderDetail
ORDERBYPurchaseOrderID;
Figure9-13.Partialresultsfromthequerywithdifferentwindowingspecifications
InFigure9-13,youcanseethatthetotalsalesinthelasttwocolumnsdiffer
significantly.Column6,TotalSalesDefaultFraming,liststotalcumulativesales:
becauseframingisn’tspecifiedforthatcolumn,thedefaultframingRANGEBETWEEN
UNBOUNDEDPRECEDINGANDCURRENTROWisextendedtothecolumn,which
meanstheaggregateiscalculatedonlyuntilthecurrentrow.Butforcolumn7,
TotalSalesDefinedFraming,theframingclauseRANGEBETWEENUNBOUNDED
PRECEDINGANDUNBOUNDEDFOLLOWINGisspecified,meaningtheframingis
extendedforalltherowsinthepartitionandhencethetotaliscalculatedforsalesacross
theentirePurchaseOrder.Theobjectiveistocalculateandreturnthetotalsales
amountforthepurchaseorderwitheachlineitem,sonotspecifyingtheframingclause
yieldsarunningtotal.Thisexampleshowsthatit’simportanttospecifytheproper
framingclausetoachievethedesiredresultset.
Nowlet’slookatanotherexample.Listing9-14modifiesListing9-13:itagainapplies
theOVERclausetothePurchasing.PurchaseOrderDetailtableinthe
AdventureWorksdatabase,butthistimetoretrievethetwo-dayaverageofthetotaldollar
amountofproductsordered.ResultsaresortedbyDueDate.Noticethedifferentframing
clauseinthisquery:ROWSBETWEEN1PRECEDINGANDCURRENTROW.Rows
aresortedbydate.Foreachrow,thetwo-dayaverageconsidersthecurrentrowandthe
rowfromthepreviousday.PartialresultsareshowninFigure9-14.
Listing9-14.UsingtheOVERClausetoDefineFrameSizesThatReturnaTwo-Day
MovingAverage
SELECT
PurchaseOrderID,
ProductID,
Duedate,
LineTotal,
Avg(LineTotal)
OVER(ORDERBYDuedate
ROWSBETWEEN1PRECEDINGANDCURRENTROW)AS
[2DayAvg]
FROMPurchasing.PurchaseOrderDetail
ORDERBYDuedate;
Figure9-14.Partialresultsfromaqueryreturningatwo-daymovingaverage
Let’sreviewonelastscenariothatcalculatestherunningtotalofsalesbyProductID
toprovideinformationtomanagementaboutwhichproductsaresellingquickly.Listing9-
15modifiesthequeryfromListing9-14furthertodefinemultiplewindowsby
partitioningtheresultsetbyProductID.Youcanseehowtheframeexpandsasthe
calculationisdoneintheframe.OncetheProductIDchanges,theframeisresetandthe
calculationisrestarted.Figure9-15showsapartialresultset.
Listing9-15.DefiningFramesfromwithintheOVERClausetoCalculateaRunningTotal
SELECT
PurchaseOrderID,
ProductID,
OrderQty,
UnitPrice,
LineTotal,
SUM(LineTotal)
OVER(PARTITIONBYProductIdORDERBYDueDateRANGE
BETWEENUNBOUNDEDPRECEDINGAND
CURRENTROW)ASCumulativeTotal,
ROW_NUMBER()
OVER(PARTITIONBYProductIdORDERBYDueDate
)ASNo
FROMPurchasing.PurchaseOrderDetail
ORDERBYProductId,DueDate;
Figure9-15.PartialresultsshowingarunningtotalbyproductID
YoucanalsoseeinthequeryinListing9-15thatyouaren’tlimitedtousingone
aggregatefunctionintheSELECTstatement.Youcanspecifymultipleaggregate
functionsinthesamequery.
FramingcanbedefinedbyeitherROWSorRANGEwithalowerboundaryandan
upperboundary.Ifyoudefineonlythelowerboundary,thentheupperboundaryissetto
thecurrentrow.WhenyoudefinetheframingwithROWS,youcanspecifytheboundary
withanumberorscalarexpressionthatreturnsaninteger.Ifyoudon’tdefinethe
boundaryforframing,thenthedefaultvalueofRANGEBETWEENUNBOUNDED
PRECEDINGANDCURRENTROWisassumed.
AnalyticFunctionExamples
SQLServer2012introducedseveralhelpfulanalyticalfunctions.Someofthemoreuseful
ofthesearedescribedinthesubsectionstofollow.Somearestatisticsoriented;othersare
usefulforreportingscenariosinwhichyouneedtoaccessvaluesacrossrowsinaresult
set.
CUME_DISTandPERCENT_RANK
CUME_DISTandPERCENT_RANKaretwoanalyticalfunctionsthatwereintroducedin
SQLServer2012.SupposeyouwanttofigureouthowAdventureWorks’best,average,
andworstsalespeopleperformincomparisontoeachother.You’reespeciallyinterestedin
thedataforasalespersonnamedJillianCarson,whoyouknowexistsinthetablebypre-
queryingthedata.Thisscenariomightbephrasedwithabusinessquestionlike,“How
doessalespersonJillianCarsonrankwhencomparedtothetotalsalesofallthe
salespeople?”CUME_DISTcaneasilygiveyouthatinformation,asshowninListing9-
16.ThequeryresultsareshowninFigure9-16.
Listing9-16.UsingtheCUME_DISTFunction
SELECT
round(SUM(TotalDue),1)ASSales,
LastName,
FirstName,
SalesPersonId,
CUME_DIST()OVER(ORDERBYround(SUM(TotalDue),1))as
CUME_DIST
FROM
Sales.SalesOrderHeadersoh
JOINSales.vSalesPersonsp
ONsoh.SalesPersonID=sp.BusinessEntityID
GROUPBYSalesPersonID,LastName,FirstName;
Figure9-16.ResultsoftheCUME_DISTcalculation
ThequeryinListing9-16roundstheTotalDuefortheSalesamount,toimprove
thequeryvalue’sreadability.BecauseCUME_DISTreturnsthepositionoftherow,the
columnresultsarereturnedasadecimalpercent.Theresultscanbeformattedtoreturnas
apercentagebymultiplyingby100.TheresultinFigure9-16showsthat94.11%ofthe
totalsalespeoplehavetotalsaleslessthanorequaltoJillianCarson,asrepresentedbythe
cumulativedistributionvalueof0.9411.
Ifyouslightlyrephrasethequestionas“Inwhatpercentilearethetotalsalesof
salespersonJillianCarson?”PERCENT_RANKcanprovidetheanswer.Listing9-17isa
modifiedversionofListing9-16’squery,nowincludingacalltoPERCENT_RANK.
PartialresultsareshowninFigure9-17.
Listing9-17.UsingthePERCENT_RANKFunction
SELECT
round(SUM(TotalDue),1)ASSales,
LastName,
FirstName,
SalesPersonId,
CUME_DIST()OVER(ORDERBYround(SUM(TotalDue),1))as
CUME_DIST
,PERCENT_RANK()OVER(ORDERBYround(SUM(TotalDue),1))
asPERCENT_RANK
FROMSales.SalesOrderHeadersoh
JOINSales.vSalesPersonsp
ONsoh.SalesPersonID=sp.BusinessEntityID
GROUPBYSalesPersonID,LastName,FirstName;
Figure9-17.ResultsoftheCUME_DISTandPERCENT_RANKcalculationforsalespeople
ThePERCENT_RANKfunctionreturnsthepercentageoftotalsalesfromallsalesin
AdventureWorks.Asyoucanseeintheresults,thereare17uniquevalues:thefirstvalue
is0,andthelastvalueis1.Theotherrowshavevaluesbasedonthenumberofrowsless
than1.Inthisexample,JillianCarsonisatthe93.75%percentileofoverallsalesin
AdventureWorks,representedbyapercentrankvalueof0.9375.
NoteYoucanapplythePARTITIONBYclausetotheCUME_DISTand
PERCENT_RANKfunctionstodefinethewindowinwhichyouapplythosecalculations.
PERCENTILE_CONTandPERCENTILE_DISC
PERCENTILE_CONTandPERCENTILE_DISCarenewdistributionfunctionsthatare
essentiallytheinverseoftheCUME_DISTandPERCENT_RANKfunctions.Supposeyou
wanttofigureoutAdventureWorks’40thpercentilesalestotalforalltheaccounts.This
canbephrasedwiththebusinessquestion,“Whatisthe40thpercentileforallsalesforall
accounts?”PERCENTILE_CONTandPERCENTILE_DISCrequiretheWITHIN
GROUPclausetospecifytheorderingandcolumnsforthecalculation.
PERCENTILE_CONTinterpolatesoverallthevaluesinthewindow,sotheresultisa
calculatedvalue.PERCENTILE_DISCreturnsthevalueoftheactualcolumn.Both
PERCENTILE_CONTandPERCENTILE_DISCrequirethepercentileasanargument,
givenasavalueintherangefrom0.0to1.0.TheexampleinListing9-18calculatesthe
salestotalforthe40thpercentile,partitionedbyaccountnumber.Theexampleuses
PERCENTILE_CONTandPERCENTILE_DISCwiththemedianvalueof0.4asthe
percentiletocompute,meaningthe40thpercentilevalue.Thequeryresultsareshownin
Figure9-18.
Listing9-18.UsingPERCENTILE_CONTandPERCENTILE_DISC
SELECT
round(SUM(TotalDue),1)ASSales,
LastName,
FirstName,
SalesPersonId,
AccountNumber,
PERCENTILE_CONT(0.4)WITHINGROUP(ORDERBY
round(SUM(TotalDue),1))
OVER(PARTITIONBYAccountNumber)AS
PERCENTILE_CONT,
PERCENTILE_DISC(0.4)WITHINGROUP(ORDERBY
round(SUM(TotalDue),1))
OVER(PARTITIONBYAccountNumber)ASPERCENTILE_DISC
FROM
Sales.SalesOrderHeadersoh
JOINSales.vSalesPersonsp
ONsoh.SalesPersonID=sp.BusinessEntityID
GROUPBYAccountNumber,SalesPersonID,LastName,FirstName
Figure9-18.ResultsfromthePERCENTILE_CONTandPERCENTILE_DISCfunctions
YoucanseeinFigure9-18thatthePERCENTILE_CONTandPERCENTILE_DISC
valuesdifferbasedontheaccountnumber.Foraccountnumber10-4020-000003,
regardlessofthesalesperson,PERCENTILE_CONTis198391.28,whichisan
interpolatedvalueandmaynotexistinthedataset.PERCENTILE_DISCis176830.40,
whichisthevaluefromtheactualcolumn.Foraccount10-4020-000004,
PERCENTILE_CONTis308720.28andPERCENTILE_DISCis222309.60.
LAGandLEAD
LAGandLEADarenewoffsetfunctionsthatenableyoutoperformcalculationsbasedon
aspecifiedrowthatisbeforeorafterthecurrentrow.Thesefunctionsprovideamethodto
accessmorethanonerowatatimewithouthavingtocreateaself-join.LAGgivesyou
accesstotherowprecedingthecurrentrow,whereasLEADletsyouaccesstherowafter
thecurrentrow.
LAGhelpsanswerbusinessquestionssuchas,“Forallactiveproductsthathavenot
beendiscontinued,whatarethecurrentandpreviousproductioncosts?”Listing9-19
showsanexamplequerythatcalculatesthecurrentproductioncostandtheprevious
productioncostforallactiveproductsusingtheLAGfunction.Partialresultsareshownin
Figure9-19.
Listing9-19.UsingtheLAGFunction
WITHProductCostHistoryAS
(SELECT
ProductID,
LAG(StandardCost)
OVER(PARTITIONBYProductIDORDERBYProductID)
ASPreviousProductCost,
StandardCostASCurrentProductCost,
Startdate,Enddate
FROMProduction.ProductCostHistory
)
SELECT
ProductID,
PreviousProductCost,
CurrentProductCost,
StartDate,
EndDate
FROMProductCostHistory
WHEREEnddateISNULL
Figure9-19.ResultsoftheproductioncosthistorycomparisonusingtheLAGfunction
Inthisexample,Listing9-19usestheLAGfunctioninaCTEtocalculatethe
differencebetweenthecurrentproductioncostandthepreviousproductproductioncost
bypartitioningthedatasetbyProductID:
SELECT
ProductID,
LAG(StandardCost)
OVER(PARTITIONBYProductIDORDERBYProductID)
ASPreviousProductCost,
StandardCostASCurrentProductCost,
Startdate,Enddate
FROMProduction.ProductCostHistory
TheSELECTqueryassociatedwiththeCTEreturnstherowswiththelatest
productioncostfromthedataset,withEndDatebeingNULLinthecall:
SELECT
ProductID,
PreviousProductCost,
CurrentProductCost,
StartDate,
EndDate
FROMProductCostHistory
WHEREEnddateISNULL
LEAD,whichistheoppositeofLAG,helpsanswerbusinessquestionssuchas,“How
doeachmonth’ssalescomparewithsalesfromthefollowingmonthforall
AdventureWorkssalespeopleovertheyear2007?”Listing9-20showsanexamplequery
thatliststhenextmonth’stotalsalesrelativetothecurrentmonth’ssalesforyear2007
usingtheLEADfunction.PartialresultsareshowninFigure9-20.
Listing9-20.UsingtheLEADFunction
SELECT
LastName,
SalesPersonID,
Sum(SubTotal)CurrentMonthSales,
DateNAME(Month,OrderDate)Month,
DateName(Year,OrderDate)Year,
LEAD(Sum(SubTotal),1)
OVER(ORDERBYSalesPersonID,OrderDate)
TotalSalesNextMonth
FROMSales.SalesOrderHeadersoh
JOINSales.vSalesPersonsp
ONsoh.SalesPersonID=sp.BusinessEntityID
WHEREDateName(Year,OrderDate)=2007
GROUPBYFirstName,LastName,SalesPersonID,OrderDate
ORDERBYSalesPersonID,OrderDate;
Figure9-20.Resultsofcomparingeachemployee’ssalesperformanceforyear2007usingtheLEADfunction
InFigure9-20youcanseethatthelastrowreturnsNULLforthenextmonth’ssales,
becausethereisnoLEADforthelastrow.
FIRST_VALUEandLAST_VALUE
FIRST_VALUEandLAST_VALUEareoffsetfunctionsthatreturnthefirstandlastvalues
inthewindowdefinedusingtheOVERclause.FIRST_VALUEreturnsthefirstvaluein
thewindow,andLAST_VALUEreturnsthelastvalueinthewindow.
Thesefunctionshelpanswerquestionslike,“Whatarethebeginningandendingsales
amountsforanygivenmonthforagivensalesperson?”Listing9-21showsanexample
querythatanswersthisquestion,andFigure9-21showspartialqueryresults.
Listing9-21.UsingFIRST_VALUEandLAST_VALUE
SELECTDISTINCT
LastName,
SalesPersonID,
datename(year,OrderDate)OrderYear,
datename(month,OrderDate)OrderMonth,
FIRST_VALUE(SubTotal)
OVER(PARTITIONBYSalesPersonID,OrderDateORDER
BYSalesPersonID)
FirstSalesAmount,
LAST_VALUE(SubTotal)
OVER(PARTITIONBYSalesPersonID,OrderDateORDER
BYSalesPersonID)
LastSalesAmount,
OrderDate
FROMSales.SalesOrderHeadersoh
JOINSales.vSalesPersonsp
ONsoh.SalesPersonID=sp.BusinessEntityID
ORDERBYOrderDate;
Figure9-21.Resultsshowingthefirstandlastsalesamount
Thisexamplereturnsthefirstandlastsalesamountsforeachsalespersonbymonth
andyear.YoucanseeinFigure9-21thatinsomecases,FirstSalesAmountand
LastSalesAmountarethesame,whichmeanstherewasonlyonesaleinthose
months.Inmonthswithmorethanonesale,thevaluesforFirstSalesOrderand
LastSalesOrderarelisted.
Summary
CTEsarepowerfulSQLServerfeaturesthatcomeintwovarieties:recursiveand
nonrecursive.NonrecursiveCTEsallowyoutowriteexpressiveT-SQLcodethatiseasier
tocode,debug,andmanagethancomplexqueriesthatmakeextensiveuseofderived
tables.RecursiveCTEssimplifyqueriesofhierarchicaldataandletyoueasilygenerate
resultsetsconsistingofsequentialnumbers,whichareveryusefulinthemselves.
SQLServer’ssupportforwindowingfunctionsandtheOVERclausemakesitsimple
tocalculateaggregateswithwindowframingandordering.SQLServersupportsseveral
windowingfunctions,includingthefollowing:
ROW_NUMBERnumberstherowsofaresultsetsequentially,
beginningwith1.
RANKandDENSE_RANKrankaresultset,applyingthesamerank
valueinthecaseofatie.
NTILEgroupsaresultsetintoauser-specifiednumberofgroups.
CUME_DIST,PERCENTILE_CONT,PERCENT_RANK,and
PERCENTILE_DISCprovideanalyticalcapabilitiesinT-SQLand
enablecumulativedistributionvaluecalculations.
LAGandLEADprovideaccesstotherowsatagivenoffsetvalue.
FIRST_VALUEandLAST_VALUEreturnthefirstandlastrowfora
givenwindowdefinedbythepartitionsubclause.
YoucanalsousetheOVERclausetoapplywindowingfunctionalitytobuilt-in
aggregatefunctionsandSQLCLRuser-definedaggregates.
BothCTEsandwindowingfunctionsprovideusefulfunctionalityandextendthe
syntaxofT-SQL,allowingyoutowritemorepowerfulcodethaneverinasimplersyntax
thanwaspossiblewithoutthem.
EXERCISES
1. [True/false]WhenaCTEisnotthefirststatementinabatch,the
statementprecedingitmustendwithasemicolonstatement
terminator.
2. [Chooseallthatapply]ArecursiveCTErequireswhichofthe
following?
a. TheWITHkeyword
b. Ananchorquery
c. TheEXPRESSIONkeyword
d. Arecursivequery
3. [Fillintheblank]TheMAXRECURSIONoptioncanacceptavalue
between0and_________.
4. [Chooseone]SQLServersupportswhichofthefollowing
windowingfunctions?
a. ROW_NUMBER
b. RANK
c. DENSE_RANK
d. NTILE
e. Alloftheabove
5. [True/false]YoucanuseORDERBYintheOVERclausewhenused
withaggregatefunctions.
6. [True/false]WhenPARTITIONBYandORDERBYarebothused
intheOVERclause,PARTITIONBYmustappearfirst.
7. [Fillintheblank]ThenamesofallcolumnsreturnedbyaCTE
mustbe__________.
8. [Fillintheblank]Thedefaultframingclauseis
___________________.
9. [True/False]IfORDERBYisnotspecifiedforfunctionsthatdonot
requireanOVERclause,thewindowframeisdefinedfortheentire
partition.
10. [True/False]ChecksumcanbeusedwithanOVERclause.
CHAPTER10
DataTypesandAdvancedDataTypes
Transact-SQLisastrongly-typedlanguage.Columnsandvariablesmusthaveavaliddata
type,andthetypeisaconstraintofthecolumn.Inthischapter,wewillnotcoveralldata
typescomprehensively.Wewillskiptheobviouspartandconcentrateonspecific
informationandonmorecomplexandsophisticateddatatypesthatwereintroducedin
SQLServerovertime.
BasicDataTypes
Basicdatatypeslikeintegerorvarcharareprettymuchself-explanatory.Someofthese
typeshaveinterestingandimportant-to-knowpropertiesorbehavior,andeventhemost
used,likevarchar,areworthalook.
Characters
Manytools,liketheMicrosoftAccessUpsizingWizard,generatetablesinSQLServer
usingsomedefaultchoices.Forallcharacterstrings,theycreatenvarcharcolumnsby
default.ThenstandsforUNICODE,thedouble-bytesrepresentationofacharacter,with
enoughroomtofitallworldwidelanguagesigns(alsocalledlogogramsinliguistics),like
traditionalandsimplifiedChinese,Arabic,andFarsi.nvarcharmustbeusedwhenthe
columnhastostorenon-Europeanlanguages,butastheyinduceanobviousoverhead,you
shouldavoidcreatingunneedednvarcharorncharcolumns.
TherealsizeofthedatainbytesisreturnedbytheDATALENGTH()function,while
theLEN()stringfunction,designedtohideinternalstoragespecificsfromtheT-SQL
developer,willreturnthenumberofcharacters.Wetestthedifferentvaluesreturnedby
thesefunctionsinListing10-1.TheresultsareshowninFigure10-1.
Listing10-1.UnicodeHandling
DECLARE
@stringVARCHAR(50)='helloearth',
@nstringNVARCHAR(50)='helloearth';
SELECT
DATALENGTH(@string)asDatalengthString,
DATALENGTH(@nstring)asDatalengthNString,
LEN(@string)aslenString,
LEN(@nstring)aslenNString;
Figure10-1.TheResultsofLEN()andDATALENGTH()
Youcanseethethenvarcharstorageofour'helloearth'is22bytes.
Imaginea100million-rowtable:havingsuchacolumnwithanaverageof11-character
strings,thestorageneededtoaccomodatetheextrabyteswouldbe1.1GB.
NoteTorepresentaT-SQLidentifier,likealoginnameoratablename,youcanuse
thespecialsysnametype,whichcorrespondstonvarchar(128).
TheMaxDataTypes
IntheheadydaysofSQLServer2000,largeobject(LOB)datastorageandmanipulation
requireduseoftheoldstyletext,ntext,andimagedatatypes.Thesetypeshavebeen
deprecatedandwerereplacedwitheasier-to-usetypesinSQLServer2005,namelythe
varchar(max),nvarchar(max),andvarbinary(max)types.
Liketheoldertypes,eachofthesenewdatatypescanholdover2.1billionbytesof
characterorbinarydata,buttheyhandledatainamuchmoreefficientway.Theoldtext
orimagetypesrequiredadedicatedtypeofallocationthatcreatedab-treestructurefor
eachvalueinserted,regardlessofitssize.Thisofcoursehadasignificantperformance
impactwhenretrievingthecolumns’content,becausethestorageenginehadtofollow
pointerstothiscomplexallocationstructureforeachandeveryrowbeingread,evenifits
valuewasafewbyteslong.The(n)varchar(max)orvarbinary(max)aremore
clevertypesthatarehandleddifferentlydependingonthesizeofthevalue.Thestorage
enginecreatestheLOBstructureonlyifthedatainsertedcannotbekeptinthe8KBpage.
Also,unlikethelegacyLOBtypes,themaxdatatypesoperatesimilarlytothe
standardvarchar,nvarchar,andvarbinarydatatypes.Standardstring
manipulationfunctionssuchasLEN()andCHARINDEX(),whichdidn’tworkwellwith
theolderLOBdatatypes,workasexpectedwiththenewmaxdatatypes.Thenewdata
typesalsoeliminatetheneedforawkwardsolutionsinvolvingtheTEXTPTR,READTEXT,
andWRITETEXTstatementstomanipulateLOBdata.
NoteThevarchar(max),nvarchar(max),andvarbinary(max)datatypes
arecompletereplacementsfortheSQLServer2000text,ntext,andimagedata
types.Thetext,ntext,andimagedatatypesandtheirsupportfunctionswillbe
removedinafutureversionofSQLServer.Becausetheyaredeprecated,Microsoft
recommendsyouavoidtheseolderdatatypesfornewdevelopment.
Thenewmaxdatatypessupporta.WRITEclauseextensiontotheUPDATEstatement
toperformoptimizedminimallyloggedupdatesandappendstovarchar(max),
varbinary(max),andnvarchar(max)types.Youcanusethe.WRITEclauseby
appendingittotheendofthecolumnnameinyourUPDATEstatement.Theexamplein
Listing10-2comparesperformanceofthe.WRITEclausetoasimplestring
concatenationwhenupdatingacolumn.Theresultsofthissimplecomparisonareshown
inFigure10-2.
Listing10-2.Comparisonof.WRITEClauseandStringAppend
--Turnoffmessagesthatcanaffectperformance
SETNOCOUNTON;
--Createandinitiallypopulateatesttable
CREATETABLE#test(
IdintNOTNULLPRIMARYKEY,
Stringvarchar(max)NOTNULL
);
INSERTINTO#test(
Id,
String
)VALUES(
1,
''
),(
2,
''
);
--Initializevariablesandgetstarttime
DECLARE@iint=1;
DECLARE@quotevarchar(50)='Fourscoreandsevenyears
ago…';
DECLARE@start_timedatetime2(7)=SYSDATETIME();
--Loop2500timesanduse.WRITEtoappendto
avarchar(max)column
WHILE@i<2500
BEGIN
UPDATE#test
SETstring.WRITE(@quote,LEN(string),LEN(@quote))
WHEREId=1;
SET@i+=1;
END;
SELECT'.WRITEClause',DATEDIFF(ms,@start_time,
SYSDATETIME()),'ms';
--Resetvariablesandgetnewstarttime
SET@i=1;
SET@start_time=SYSDATETIME();
--Loop2500timesandusestringappendtoavarchar(max)
column
WHILE@i<2500
BEGIN
UPDATE#test
SETstring+=@quote
WHEREId=2;
SET@i+=1;
END;
SELECT'AppendMethod',DATEDIFF(ms,@start_time,
SYSDATETIME()),'ms';
SELECT
Id,
String,
LEN(String)
FROM#test;
DROPTABLE#test;
Figure10-2.Testingthe.WRITEClauseagainstSimpleStringConcatenation
Asyoucanseeinthisexample,the.WRITEclauseisappreciablymoreefficientthan
asimplestringconcatenationwhenupdatingamaxdatatypecolumn.Notethatthese
timeswereachievedononeofourdevelopmentmachines,andyourresultsmayvary
significantlydependingonyourspecificconfiguration.Youcanexpectthe.WRITE
methodtoperformmoreefficientlythansimplestringconcatenationwhenupdatingmax
datatypecolumns,however.
Youshouldnotethefollowingaboutthe.WRITEclause:
Thesecond.WRITEparameter,@offset,isazero-basedbigint
andcannotbenegative.Thefirstcharacterofthetargetstringisat
offset0.
Ifthe@offsetparameterisNULL,theexpressionisappendedtothe
endofthetargetstring.@lengthisignoredinthiscase.
Ifthethirdparameter,@length,isNULL,SQLServertruncates
anythingpasttheendofthestringexpression(thefirst.WRITE
parameter)afterthetargetstringisupdated.The@lengthparameter
isabigintandcannotbenegative.
Numerics
Therearetwotypesofnumeric:exactandapproximate.Integeranddecimalareexact
numbers.Itisworthknowingthatanyexactnumericcanbeusedasanauto-incremented
IDENTITYcolumn.Mostofthetimeofcourse,a32-bitintischosenasanauto-
incrementedsurrogatekey.
NoteWecallsurrogatekeyatechnical,non-naturaluniquekey,inotherwordsa
columnstoringvaluescreatedinsidethedatabase,andhavingnomeaningoutsideofit.
MostofthetimeinSQLServeritisanIDENTITY(auto-incremented)number,ofa
uniqueidentifier(aGloballyUniqueIdentifier,orGUID)thatwewillseelaterin
thischapter.
BecausethereisnounsignednumericinSQLServer,therangeofvaluesthatcanbe
generatedbytheIDENTITYpropertyisfrom−2,147,483,648to+2,147,483,647.Indeed,
astheIDENTITYpropertytakesaseedandanincrementasparameters,nothingprevents
youfromdeclaringitasinListing10-3:
Listing10-3.UsetheFullRangeof32-bitIntegerforIDENTITYColumns
CREATETABLEdbo.bigtable(
bigtableIdintidentity(-2147483648,1)NOTNULL
);
INSERTINTOdbo.bigtableDEFAULTVALUES;
INSERTINTOdbo.bigtableDEFAULTVALUES;
SELECT*FROMdbo.bigtable;
TheseedparameterofthebigtableIdcolumnIDENTITYpropertyissetasthe
lowestpossibleintvalue,insteadofthemostcommonlyseenIDENTITY(1,1)
declaration.TheresultsfollowinFigure10-3.
Figure10-3.TheFirstTwoIDENTITYValuesInserted
Thisallowsfortwicetherangeofavailablevaluesinyourkeyandmightsaveyou
fromchoosingabigint(64-bitinteger)toaccommodatevaluesforatableinwhichyou
expecttohavemorethan2billionrowsbutlessthan4billionrows.Onceagain,ona100-
millionrowtable,itwillsaveabout400MB,andprobablymuchmorethanthatbecause
therearestrongchancesthatthekeyvaluewillbeusedinindexesandforeignkeys.
NoteSomearereluctanttousethistipbecauseitcreateskeyswithnegativenumbers.
Theoretically,asurrogatekeyispreciselymeaninglessbynatureandshouldnotbeseen
bytheenduser.Itismerelytheretoprovideauniquevaluetoidentifyandreferencea
row.Sometimes,whenthesesurrogatekeysareshowntousers,theystarttoacquirealife
oftheirown,apurpose.Forexample,peoplestarttotalkaboutcustomer3425insteadof
usinghername—hencethedifficultywithnegativevalues.
Wetalkedaboutexactnumerictypes.Awordofcautionaboutapproximatetype:do
notuseapproximatenumerictypesforanythingotherthanscientificpurpose.Acolumn
definedasfloatorrealstoresfloating-pointvaluesasdefinedbytheIEEEStandard
forFloating-PointArithmetic(IEEE754),andanyresultofanoperationonfloator
realwillbeapproximate.Thinkaboutthenumberpi:youalwaysgiveanon-precise
representationofpi,andyouwillnevergettheprecisevalueofpibecauseyouneedto
roundortruncateitatsomedecimal.Tostoretheprecisedecimalvaluesthatmostofus
manipulateinbusinessapplications—amounts,measurements,etc.—youneedtouse
eithermoneyordecimalwhicharefixeddatatypes.
ThebitdatatypeismostlyusedtostoreBooleanvalues.Itcanbe0,1,orNULL,
anditconsumesonebyteofstorage,butwithanoptimization:ifyoucreateupto8-bit
columnsinyourtable,theywillsharethesamebyte.Sobitcolumnstakeverylittle
space.SQLServerrecognizesalsothestringvalues'TRUE'and'FALSE'whenthey
areappliedtoabit,andtheywillbeconvertedto1and0,respectively.
DateandTimeDataTypes
ThedateandtimetypeswereenrichedinSQLServer2008bythedistinctdateand
timetypes,andthemoreprecisedatetime2anddatetimeoffset.Beforethat,
onlydatetimeandsmalldatetimewereavailable.Table10-1summarizesthe
differencesbetweenallSQLServer2014dateandtimedatatypesbeforewedelvemore
intodetails.
Table10-1.SQLServer2012DateandTimeDataTypeComparison
ThedatedatatypeallowssolvingaverycommonproblemwehaduntilSQLserver
2008.Howcanweexpressdatewithouthavingtotaketimeintoaccount?Beforedate,it
wastrickytodoastraightcomparisonasshowninListing10-4.
Listing10-4.DateComparison
SELECT*
FROMPerson.StateProvince
WHEREModifiedDate='2008-03-11';
BecausetheModifiedDatecolumndatatypeisdatetime,SQLServerconverts
implictlythe'2008-03-11'valuetothefull'2008-03-1100:00:00.000'
datetimerepresentationbeforecarryingoutthecomparison.IftheModifiedDatetime
partisnot'00:00:00.000',nolinewillbereturned,whichisthecaseinourexample.
Withdatetime-likedatatypes,weareforcedtodothingsasshowninListing10-5.
Listing10-5.DateComparisonExecutedCorrectly
SELECT*
FROMPerson.StateProvince
WHEREModifiedDateBETWEEN'2008-03-11'AND'2008-03-12';
--or
SELECT*
FROMPerson.StateProvince
WHERECONVERT(CHAR(10),ModifiedDate,126)='2008-03-11';
Butbothtricksareunsatisfactory.Thefirstonehasaflaw:becausetheBETWEEN
operatorisinclusive,lineswithModifiedDatesetat'2008-03-12
00:00:00.000'wouldbeincluded.Tobesafe,weshouldhavewrittenthequeryasin
Listing10-6.
Listing10-6.CorrectingtheDateComparison
SELECT*
FROMProduction.Product
WHEREModifiedDateBETWEEN'2008-03-11'AND'2008-03-11
23:59:59.997';
--or
SELECT*
FROMPerson.StateProvince
WHEREModifiedDate>='2008-03-11'ANDModifiedDate<'2008-
03-12';
Thesecondexample,inListing10-5,hasaperformanceimplication,becauseitmakes
theconditionnon-sargable.
NoteWesaythatapredicateissargable(fromSearchARGument–able)whenitcan
takeadvantageofanindexseek.Here,noindexontheModifiedDatecolumncanbe
usedforaseekoperationifitsvalueisalteredinthequery,andthusdoesnotmatchwhat
wasindexedinthefirstplace.
So,thebestchoicewehadwastoenforce,maybebytrigger,thateveryvalueentered
inthecolumnhaditstimepartstrippedofforwrittenwith'00:00:00.000',butthat
timepartwasstilltakingupstoragespacefornothing.Now,thedatetype,costing3
bytes,storesadatewithonedayaccuracy.
Listing10-7showsasimpleusageofthedatedatatype,demonstratingthatthe
DATEDIFF()functionworkswiththedatetypejustasitdoeswiththedatetime
datatype.
Listing10-7.SampleDateDataTypeUsage
--August19,14C.E.
DECLARE@d1date='0014-08-19';
--February26,1983
DECLARE@d2date='1983-02-26';
SELECT@d1ASDate1,@d2ASDate2,DATEDIFF(YEAR,
@d1,@d2)ASYearsDifference;
TheresultsofthissimpleexampleareshowninFigure10-4.
Figure10-4.TheResultsoftheDateDataTypeExample
Incontrasttothedatedatatype,thetimedatatypeletsyoustoretime-onlydata.
Therangeforthetimedatatypeisdefinedona24-hourclock,from00:00:00.0000000
through23:59:59.9999999,withauser-definablefractionalsecondprecisionofupto
sevendigits.Thedefaultprecision,ifyoudon’tspecifyone,issevendigitsoffractional
secondprecision.Listing10-8demonstratesthetimedatatypeinaction.
Listing10-8.DemonstratingTimeDataTypeUsage
--6:25:19.1AM
DECLARE@start_timetime(1)='06:25:19.1';—1digit
fractionalprecision
--6:25:19.1234567PM
DECLARE@end_timetime='18:25:19.1234567';—default
fractionalprecision
SELECT@start_timeASstart_time,@end_timeASend_time,
DATEADD(HOUR,6,@start_time)ASStartTimePlus,
DATEDIFF(HOUR,@start_time,@end_time)AS
EndStartDiff;
InListing10-8,twodatatypeinstancesarecreated.The@start_timevariableis
explicitlydeclaredwithafractionalsecondprecisionofonedigit.Youcanspecifya
fractionalsecondprecisionofonetosevendigitswith100-nanosecond(ns)accuracy;the
fixedfractionalprecisionoftheclassicdatetimedatatypeisthreedigitswith3.33-
millisecond(ms)accuracy.Thedefaultfractionalprecisionforthetimedatatype,ifno
precisionisspecified,issevendigits.The@end_timevariableinthelistingisdeclared
withthedefaultprecision.Aswiththedateanddatetimedatatypes,the
DATEDIFF()andDATEADD()functionsalsoworkwiththetimedatatype.The
resultsofListing10-8areshowninFigure10-5.
Figure10-5.TheResultsoftheTimeDataTypeExample
Thecleverlynameddatetime2datatypeisanextensiontothestandarddatetime
datatype.Thedatetime2datatypecombinesthebenefitsofthedateandtimedata
types,givingyouthewiderdaterangeofthedatedatatypeandthegreaterfractional-
secondprecisionofthetimedatatype.Listing10-9demonstratessimpledeclarationand
usageofdatetime2variables.
Listing10-9.DeclaringandQueryingDatetime2Variables
DECLARE@start_dt2datetime2='1972-07-
06T07:13:28.8230234',
@end_dt2datetime2='2009-12-14T03:14:13.2349832';
SELECT@start_dt2ASstart_dt2,@end_dt2ASend_dt2;
TheresultsofListing10-9areshowninFigure10-6.
Figure10-6.DeclaringandSelectingDatetime2dataTypeVariables
Thedatetimeoffsetdatatypebuildsondatetime2byaddingtheabilityto
storeoffsetsrelativetotheInternationalTelecommunicationUnion(ITU)standardfor
CoordinatedUniversalTime(UTC)withyourdateandtimedata.Whencreatinga
datetimeoffsetinstance,youcanspecifyanoffsetthatcomplieswiththeISO8601
standard,whichisinturnbasedonUTC.Basically,theoffsetmustbespecifiedinthe
range-14:00to+14:00.TheZoffsetidentifierisshorthandfortheoffsetdesignated
“zulu,”or+00:00.Listing10-10showsthedatetimeoffsetdatatypeinaction.
Listing10-10.DatetimeoffsetDataTypeSample
DECLARE@start_dtodatetimeoffset='1492-10-
12T13:29:59.9999999-05:00';
SELECT@start_dtoASstart_to,DATEPART(YEAR,@start_dto)AS
start_year;
TheresultsofListing10-10areshowninFigure10-7.
Figure10-7.TheResultoftheDatetimeoffsetSample
AsamplingofpossibleoffsetsisshowninTable10-2.Notethatthislistisnot
exhaustive,butdemonstratessomecommonoffsets.
Table10-2.CommonStandardTimeZones
TimeZoneOffset Name Locations
–10:00 Hawaii-AleutianStandard Alaska(AleutianIslands),Hawaii
–08:00 PacificStandard USWestCoast;LosAngeles,CA
–05:00 EasternStandard USEastCoast;NewYork,NY
–04:00 AtlanticStandard Bermuda
+00:00 CoordinatedUniversal Dublin,Lisbon,London
+01:00 CentralEuropean Paris,Berlin,Madrid,Rome
+03:00 Baghdad Kuwait,Riyadh
+06:00 IndianStandard India
+09:00 JapanStandard Japan
UTCandMilitaryTime
SomepeopleseetheacronymUTCandthinkthatitstandsfor“UniversalTime
Coordination”or“UniversalTimeCode.”Unfortunately,theworldisnotsosimple.When
theITUstandardizedCoordinatedUniversalTime,itwasdecidedthatitshouldhavethe
sameacronymineverylanguage.Ofcourse,internationalagreementcouldnotbereached,
withtheEnglish-speakingcountriesdemandingtheacronymCUTandFrench-speaking
countriesdemandingthatTUC(tempsuniverselcoordonné)beused.Inthefinal
compromise,thenonsensicalUTCwasadoptedastheinternationalstandard.
Youmaynoticethatweuse“militarytime,”orthe24-hourclock,whenrepresenting
timeinthecodesamplesthroughoutthisbook.There’saverygoodreasonforthat—the
24-hourclockisanISOinternationalstandard.TheISO8601standardindicatesthattime
shouldberepresentedincomputersusingthe24-hourclocktopreventambiguity.
The24-hourclockbeginsat00:00:00,whichismidnightor12am.Noon,or12pm,is
representedas12:00:00.Onesecondbeforemidnightis23:59:59,or11:59:59pm.In
ordertoconvertthe24-hourclocktoam/pmtime,simplylookatthehours.Ifthehours
arelessthan12,thenthetimeisam.Ifthehoursareequalto12,youareinthenoonhour,
whichispm.Ifthehoursaregreaterthan12,subtract12andaddpmtoyourtime.
So,withallthesetypesatyourdisposal,whichdoyouchoose?Asarule,avoid
datetime:itdoesn’talignwiththeSQLStandard,takesgenerallymorespaceandhas
lowerprecisionthantheothertypes.Itcosts8bytes,rangesfrom1753through9999,and
roundsthetimeto3milliseconds.Forexample,let’strythecodeinListing10-11.
Listing10-11.DemonstrationofDatetimeRounding
SELECTCAST('2011-12-31T23:59:59.999'asdatetime)as
WhatTimeIsIt;
YoucanseetheresultinFigure10-8.
Figure10-8.TheResultsoftheDatetimeRoundingSample
The999millisecondswereroundedtothenextvalue,and998wouldhavebeen
roundedto997.Formostusagesthisisnotanissue,butdatetime2doesnothavethis
drawback,oratleastyouhavecontroloveritbydefiningtheprecision.
DateandTimeFunctions
OneofthedifficultiesofT-SQListhehandlingofdatesinthecode.Internally,thedate
andtimedatatypesarestoredinanumericrepresentation,butofcourse,theyhavetobe
madehuman-readableinastringformat.Theformatisimportantforinputoroutput,butit
hasnothingtodowithstorage,anditisacommonmisconceptiontoconsiderthatadateis
storedinaparticularformat.Theoutputismanagedbytheclient.Forexample,inSSMS,
datesarealwaysreturnedintheODBCAPIts(timestamp)format(yyyy-mm-dd
hh:mm:ss.…),regardlessofthecomputer’sregionalsettings.Ifyouwanttoforcea
particularformatinT-SQL,youwillneedtouseaconversionfunction.TheCONVERT()
functionisalegacyfunctionthatreturnsaformattedstringfromadateandtimedatatype
orvice-versa,whiletheFORMAT()function,introducedinSQLServer2012,usesthe
morecommon.NETformatstringsandanoptionalculturetoreturnaformatted
nvarcharvalue.WedemonstrateusageofthesetwofunctionsinListing10-12.
Listing10-12.CONVERT()andFORMAT()UsageSample
DECLARE@dt2datetime2='2011-12-31T23:59:59';
SELECTFORMAT(@dt2,'F','en-US')aswith_format,
CONVERT(varchar(50),@dt2,109)aswith_convert;
TheresultsareshowninFigure10-9.
Figure10-9.TheResultsoftheDatetime2FormattingSample
Ofcourse,datainputmustalsobedoneusingastringrepresentationthatcanbe
understoodbySQLServerasadate.Thisdependsonthelanguagesettingsofthesession.
Eachsessionhasalanguageenvironmentthatisthedefaultlanguageofthelogin,unlessa
SETLANGUAGEcommandchangeditatsometime.Youcanretrievethelanguageofthe
currentsessionwithoneofthetwowaysshowninListing10-13.
Listing10-13.HowtoChecktheCurrentLanguageoftheSession
SELECTlanguage
FROMsys.dm_exec_sessions
WHEREsession_id=@@SPID;
--or
SELECT@@LANGUAGE;
Formattingyourdatestringsforinputwithalanguagedependentformatisrisky,
becauseanyonerunningthecodeunderanotherlanguageenvironmentwouldgetanerror,
asshowninListing10-14.
Listing10-14.LanguageDependentDateStringRepresentations
DECLARE@langsysname;
SET@lang=@@LANGUAGE
SELECTCAST('12/31/2012'asdatetime2);--thisworks
SETLANGUAGE'spanish';
SELECT
CASEWHENTRY_CAST('12/31/2012'asdatetime2)ISNULL
THEN'Castfailed'
ELSE'Castsucceeded'
ENDASResult;
SETLANGUAGE@lang;
ThesecondCAST()attempt,usingtheTRY_CAST()topreventanexceptionfrom
beingraised,willreturn‘Castfailed’because'MM/dd/yyyy'isnotrecognizedasa
validdateformatinSpanish.IfwewouldhaveusedCAST()insteadofTRY_CAST(),
wewouldhavereceivedaconversionerrorintheSpanishlanguage,andthelastSET
LANGUAGEcommandwouldn’thavebeenexecuted,duetotheprecedingexception.
Youhavetwooptionstopreventthis.First,youcanusetheSETDATEFORMAT
instructionthatsetstheorderofthemonth,day,andyeardatepartsforinterpretingdate
characterstrings,asshowninListing10-15.
Listing10-15.UsageofSETDATEFORMAT
SETDATEFORMATmdy;
SETLANGUAGE'spanish';
SELECTCAST('12/31/2012'asdatetime2);--thisworksnow
Oryoucandecide—thisisabetteroption—tostickwithalanguage-neutralformat
thatwillberecognizedregardlessofwhatthelanguageenvironmentis.Youcandothatby
makingsureyoualwayshaveyourdatestringsformattedinanISO8601standardvariant.
InISO8601,dateandtimevaluesareorganizedfromthemosttotheleastsignificant,
startingwiththeyear.Thetwomostcommononesareyyyy-MM-ddTHH:mm:ss(notethe
Tcharactertoseparatedateandtime)andyyyyMMddHH:mm:ss.Ina.NETclientcode,
youcouldgeneratethoseformatswiththe.NETformatstrings,asshowninthepseudo-
codeexamplesofListing10-16.
Listing10-16.SamplesofISO8601DateFormattingin.NETPseudo-code
DateTime.Now.Format("s");
DateTime.Now.ToString("s",
System.Globalization.CultureInfo.InvariantCulture);
ThefirstlinecallstheFormat()methodofthetheDateTime.NETtype,andthe
secondlineusestheToString()methodof.NETobjects,thatcantakeaformatstring
andacultureasparameterswhenappliedtoaDateTime.
Withmorecompleteandprecisedateandtimedatatypescomesalsoawiderangeof
built-indate-andtime-relatedfunctions.YoumightalreadyknowtheGETDATE()and
CURRENT_TIMESTAMPfunctions.SinceSQLServer2008,youhavehadmorefunctions
forreturningthecurrentdateandtimeoftheserver.
TheSYSDATETIME()functionreturnsthesystemdateandtime,asreportedbythe
server’slocaloperatingsystem,asadatetime2valuewithouttimeoffsetinformation.
ThevaluereturnedbyGETDATE(),CURRENT_TIMESTAMPandSYSDATETIME()is
thedateandtimereportedbyWindowsonthecomputerwhereyourSQLServerinstance
isinstalled.
TheSYSUTCDATETIME()functionreturnsthesystemdateandtimeinformation
convertedtoUTCasadatetime2value.AswiththeSYSDATETIME()function,the
valuereturneddoesnotcontainadditionaltimeoffsetinformation.
TheSYSDATETIMEOFFSET()functionreturnsthesystemdateandtimeasa
datetimeoffsetvalue,includingthetimeoffsetinformation.Listing10-17usesthese
functionstodisplaythecurrentsystemdateandtimeinvariousformats.Theresultsare
showninFigure10-10.
Listing10-17.UsingtheDateandTimeFunctions
SELECTSYSDATETIME()AS[SYSDATETIME];
SELECTSYSUTCDATETIME()AS[SYSUTCDATETIME];
SELECTSYSDATETIMEOFFSET()AS[SYSDATETIMEOFFSET];
Figure10-10.TheCurrentSystemDateandTimeinaVarietyofFormats
TheTODATETIMEOFFSET()functionallowsyoutoaddtimeoffsetinformationto
dateandtimedatawithouttimeoffsetinformation.YoucanuseTODATETIMEOFFSETto
addtimeoffsetinformationtoadate,time,datetime,datetime2,or
datetimeoffsetvalue.Theresultreturnedbythefunctionisadatetimeoffset
valuewithtimeoffsetinformationadded.Listing10-18demonstratesbyaddingtime
offsetinformationtoadatetimevalue.TheresultsareshowninFigure10-11.
Listing10-18.AddinganOffsettoaDatetimeValue
DECLARE@currentdatetime=CURRENT_TIMESTAMP;
SELECT@currentAS[No_0ffset];
SELECTTODATETIMEOFFSET(@current,'-04:00')AS
[With_0ffset];
Figure10-11.ConvertingaDatetimeValuetoaDatetimeoffset
TheSWITCHOFFSET()functionadjustsagivendatetimeoffsetvalueto
anothergiventimeoffset.Thisisusefulwhenyouneedtoconvertadateandtimeto
anothertimeoffset.InListing10-19,weusetheSWITCHOFFSET()functiontoconvert
adatetimeoffsetvalueinLosAngelestoseveralotherregionaltimeoffsets.The
valuesarecalculatedforDaylightSavingTime.TheresultsareshowninFigure10-12.
Listing10-19.ConvertingaDatetimeoffsettoSeveralTimeOffsets
DECLARE@currentdatetimeoffset='2012-05-0419:30:00
-07:00';
SELECT'LosAngeles'AS[Location],@currentAS[Current
Time]
UNIONALL
SELECT'NewYork',SWITCHOFFSET(@current,'-04:00')
UNIONALL
SELECT'Bermuda',SWITCHOFFSET(@current,'-03:00')
UNIONALL
SELECT'London',SWITCHOFFSET(@current,'+01:00');
Figure10-12.DateandTimeInformationinSeveralDifferentTimeOffsets
TipYoucanusetheZtimeoffsetindatetimeoffsetliteralsasanabbreviationfor
UTC(+00:00offset).Youcannot,however,specifyZasthetimeoffsetparameterwiththe
TODATETIMEOFFSETandSWITCHOFFSETfunctions.
TimeZonesandOffsets
Timeoffsetsarenotthesamethingastimezones.Atimeoffsetisrelativelyeasyto
calculate—it’ssimplyaplusorminusoffsetinhoursandminutesfromtheUTCoffset
(+00:00),asdefinedbytheISO8601standard.Atimezone,however,isanidentifierfora
specificlocationorregionandisdefinedbyregionallawsandregulations.Timezonescan
haveverycomplexsetsofrulesthatincludesuchodditiesasDaylightSavingTime(DST).
SQLServerusestimeoffsetsincalculations,nottimezones.Ifyouwanttoperformdate
andtimecalculationsinvolvingactualtimezones,youwillhavetowritecustomcode.
Justkeepinmindthattimezonecalculationsarefairlyinvolved,especiallysince
calculationslikeDSTcanchangeovertime.Caseinpoint—thestartandenddatesfor
DSTwerechangedtoextendDSTintheUnitedStatesbeginningin2007.
TheUniqueidentifierDataType
InWindows,youseealotofGUIDs(GloballyUniqueIDentifiers)intheregistryandasa
waytoprovidecodeandmodules(likeCOMobjects)withuniqueidentifiers.GUIDsare
16-bytevaluesgenerallyrepresentedas32-characterhexadecimalstrings,andcanbe
storedinSQLServerintheuniqueidentifierdatatype.uniqueidentifier
couldbeusedtocreateuniquekeysacrosstables,serversordatacenters.Tocreateanew
GUIDandstoreitinauniqueidentifiercolumn,youusetheNEWID()function,
asdemonstratedinListing10-20.TheresultsareshowninFigure10-13.
Listing10-20.UsingUniqueidentifier
CREATETABLEdbo.Document(
DocumentIduniqueidentifierNOTNULLPRIMARYKEYDEFAULT
(NEWID())
);
INSERTINTOdbo.DocumentDEFAULTVALUES;
INSERTINTOdbo.DocumentDEFAULTVALUES;
INSERTINTOdbo.DocumentDEFAULTVALUES;
SELECT*FROMdbo.Document;
Figure10-13.ResultsGeneratedbytheNewid()Function
EachtimetheNEWID()functioniscalled,itgeneratesanewvalueusingan
algorithmbasedonapseudo-randomgenerator.Theriskoftwogeneratednumbersbeing
thesameisstatisticallynegligible:hencetheglobaluniquenessitoffers.
However,usageofuniqueidentifiercolumnsshouldbecarefullyconsidered,
becauseitbearssignificantconsequences.Wehavealreadytalkedabouttheimportanceof
datatypesize,andespeciallykeysize.Choosingauniqueidentifieroveranint
asaprimarykeycreatesanoverheadof12bytesperrowthatimpactsthesizeofthetable,
oftheprimarykeyindex,ofallotherindexesiftheprimarykeyisdefinedasclustered(as
itisbydefault),andofalltablesthathaveaforeignkeyassociatedtoit,andfinallyonall
indexesontheseforeignkeys.Needlesstosay,itcouldconsiderablyincreasethesizeof
yourdatabase.
Thereisanotherproblemwithuniqueidentifiervalues,becauseoftheir
inherentrandomness.Ifyourprimarykeyisclustered,thephysicalorderofthetable
dependsuponthevalueofthekey,andateachinsertorupdate,SQLServermustplacethe
newormodifiedlinesattherightplace,intherightdatapages.GUIDrandomvalueswill
causepagesplitsthatwillnoticeablydecreaseperformancesandgeneratetable
fragmentation.
Toaddressthislastissue,SQLServer2008introducedtheNEWSEQUENTIALID()
functiontouseasadefaultconstraintwithanuniqueidentifierprimarykey.
NEWSEQUENTIALID()generatessequentialGUIDsinincreasingorder.Itsusageis
showninListing10-21.ResultsareshowninFigure10-14;noticethattheGUIDdigits
aredisplayedingroupsinreverseorder.Intheresults,thefirstbyteofeachGUID
representsthesequentiallyincreasingvaluesgeneratedbyNEWSEQUENTIALID()with
eachrowinserted.
Listing10-21.GeneratingSequentialGUIDs
CREATETABLE#TestSeqID(
IDuniqueidentifierDEFAULTNEWSEQUENTIALID()PRIMARYKEY
NOTNULL,
NumintNOTNULL
);
INSERTINTO#TestSeqID(Num)
VALUES(1),(2),(3);
SELECTID,Num
FROM#TestSeqID;
DROPTABLE#TestSeqID;
Figure10-14.ResultsGeneratedbytheNEWSEQUENTIALIDFunction
TheHierarchyidDataType
Thehierarchyiddatatypeoffersanewtwistonanoldmodelforrepresenting
hierarchicaldatainthedatabase.ThisdatatypeintroducedinSQLServer2008offers
built-insupportforrepresentingyourhierarchicaldatausingoneofthesimplestmodels
available:materializedpaths.
RepresentingHierarchicalData
Therepresentationofhierarchicaldatainrelationaldatabaseshaslongbeenanareaof
interestforSQLdevelopers.Themostcommonmodelofrepresentinghierarchicaldata
withSQLServeristheadjacencylistmodel.Inthismodel,eachrowofatablemaintainsa
referencetoitsparentrow.Thefollowingillustrationdemonstrateshowtheadjacencylist
modelworksinanSQLtable.
TheAdventureWorkssampledatabasemakesuseoftheadjacencylistmodelinits
Production.BillOfMaterialstable,whereeverycomponentreferencesitsparent
assembly.
Thematerializedpathmodelrequiresthatyoustoretheactualhierarchicalpathfrom
therootnodetothecurrentnode.Thehierarchicalpathissimilartoamodernfilesystem
path,whereeachfolderordirectoryrepresentsanodeinthepath.Thehierarchyid
datatypesupportsgenerationandindexingofmaterializedpathsforhierarchicaldata
modeling.ThefollowingillustrationshowshowthematerializedpathmightlookinSQL.
Itisarelativelysimplemattertorepresentadjacencylistmodeldatausing
materializedpaths,asyou’llseelaterinthissectioninthediscussiononconverting
AdventureWorksadjacencylistdatatothematerializedpathmodelusingthe
hierarchyiddatatype.
Anothermodelforrepresentinghierarchicaldataisthenestedsetsmodel.Inthis
model,everyrowinthetableisconsideredasetthatmaycontainorbecontainedby
anotherset.Eachrowisassignedapairofnumbersdefiningthelowerandupperbounds
fortheset.Thefollowingillustrationshowsalogicalrepresentationofthenestedsets
model,withthelowerandupperboundsforeachsetshowntotheset’sleftandright.
Noticethatthesetsinthefigurearecontainedwithinoneanotherlogically,inastructure
fromwhichthismodelderivesitsname.
Inthissection,we’llusetheAdventureWorksProduction.BillOfMaterials
tableextensivelytodemonstratetheadjacencylistmodel,thematerializedpathmodel,
andthehierarchyiddatatype.Technicallyspeaking,abillofmaterials(BOM),or
“partsexplosion,”isadirectedacyclicgraph.Adirectedacyclicgraphisessentiallya
generalizedtreestructureinwhichsomesubtreesmaybesharedbydifferentpartsofthe
tree.Thinkofacakerecipe,representedasatree,inwhich“sugar”canbeusedmultiple
times(onceinthe“cakemix”subtree,onceinthe“frosting”subtree,andsoon).This
bookisnotaboutgraphtheory,though,sowe’llpassonthetechnicaldetailsandgettothe
BOMathand.AlthoughdirectedacyclicgraphisthetechnicaltermforatrueBOM,we’ll
berepresentingtheAdventureWorksBOMsasmaterializedpathhierarchiesusingthe
hierarchyiddatatype,soyou’llseethetermhierarchyusedalotinthissection.
InordertounderstandtheAdventureWorksBOMhierarchies,it’simportantto
understandtherelationshipbetweenproductassembliesandcomponents.Basically,a
productassemblyiscomposedofoneormorecomponents.Anassemblycanbecomea
componentforuseinotherassemblies,definingtherecursiverelationship.All
componentswithaproductassemblyofNULLaretop-levelcomponents,or“rootnodes,”
ofeachhierarchy.Ifahierarchyiddatatypecolumnisdeclaredaprimarykey,itcan
containonlyasinglehierarchyidrootnode.
Thehierarchyiddatatypestoreshierarchyinformationasanoptimized
materializedpath,whichisaveryefficientwaytostorehierarchicalinformation.Wewill
gothoughacompleteexampleofitsuse.
HierarchyidExample
Inthisexample,wewillconverttheAdventureWorksBOMstomaterializedpathform
usingthehierarchyiddatatype.Thefirststep,showninListing10-22,istocreatethe
tablethatwillcontainthehierarchyidBOMs.Todifferentiateitfromthe
Production.BillOfMaterialstable,wehavecalledthistable
Production.HierBillOfMaterials.
Listing10-22.CreatingtheHierarchyidBillofMaterialsTable
CREATETABLEProduction.HierBillOfMaterials
(
BomNodehierarchyidNOTNULLPRIMARYKEYNONCLUSTERED,
ProductAssemblyIDintNULL,
ComponentIDintNULL,
UnitMeasureCodenchar(3)NULL,
PerAssemblyQtydecimal(8,2)NULL,
BomLevelASBomNode.GetLevel()
);
TheProduction.HierBillOfMaterialstableconsistsoftheBomNode
hierarchyidcolumn,whichwillcontainthehierarchicalpathinformationforeach
component.TheProductAssemblyID,ComponentID,UnitMeasureCode,and
PerAssemblyQtyareallpulledfromthesourcetables.BomLevelisacalculated
columnthatcontainsthecurrentlevelofeachBomNode.Thenextstepistoconvertthe
adjacencylistBOMstohierarchyidform,whichwillbeusedtopopulatethe
Production.HierBillOfMaterialstable.ThisisdemonstratedinListing10-23.
Listing10-23.ConvertingAdventureWorksBOMstohierarchyidForm
;WITHBomChildren
(
ProductAssemblyID,
ComponentID
)
AS
(
SELECT
b1.ProductAssemblyID,
b1.ComponentID
FROMProduction.BillOfMaterialsb1
GROUPBY
b1.ProductAssemblyID,
b1.ComponentID
),
BomPaths
(
Path,
ComponentID,
ProductAssemblyID
)
AS
(
SELECT
hierarchyid::GetRoot()ASPath,
NULL,
NULL
UNIONALL
SELECT
CAST
('/'+CAST(bc.ComponentIdASvarchar(30))+'/'AS
hierarchyid)ASPath,
bc.ComponentID,
bc.ProductAssemblyID
FROMBomChildrenASbc
WHEREbc.ProductAssemblyIDISNULL
UNIONALL
SELECT
CAST
(bp.path.ToString()+
CAST(bc.ComponentIDASvarchar(30))+'/'AS
hierarchyid)ASPath,
bc.ComponentID,
bc.ProductAssemblyID
FROMBomChildrenASbc
INNERJOINBomPathsASbp
ONbc.ProductAssemblyID=bp.ComponentID
)
INSERTINTOProduction.HierBillOfMaterials
(
BomNode,
ProductAssemblyID,
ComponentID,
UnitMeasureCode,
PerAssemblyQty
)
SELECT
bp.Path,
bp.ProductAssemblyID,
bp.ComponentID,
bom.UnitMeasureCode,
bom.PerAssemblyQty
FROMBomPathsASbp
LEFTOUTERJOINProduction.BillOfMaterialsbom
ONbp.ComponentID=bom.ComponentID
ANDCOALESCE(bp.ProductAssemblyID,-1)
=COALESCE(bom.ProductAssemblyID,-1)
WHEREbom.EndDateISNULL
GROUPBY
bp.path,
bp.ProductAssemblyID,
bp.ComponentID,
bom.UnitMeasureCode,
bom.PerAssemblyQty;
Thisstatementisalittlemorecomplexthantheaveragehierarchyiddataexample
you’llprobablyruninto,sincemostpeoplecurrentlyouttherearedemonstrating
conversionofthesimple,single-hierarchyAdventureWorksorganizationalchart.The
AdventureWorksProduction.BillOfMaterialstableactuallycontainsseveral
individualhierarchies.
Wewillgothroughthecodestepbystepheretoshowyouexactlywhat’sgoingonin
thisstatement.Thefirstpartofthestatementisacommontableexpression(CTE)called
BomChildren.ItreturnsallProductAssemblyIDsandComponentIDsfromthe
Production.BillOfMaterialstable.
;WITHBomChildren
(
ProductAssemblyID,
ComponentID
)
AS
(
SELECT
b1.ProductAssemblyID,
b1.ComponentID
FROMProduction.BillOfMaterialsb1
GROUPBY
b1.ProductAssemblyID,
b1.ComponentID
),
Whiletheorganizationalchartrepresentsasimpletop-downhierarchywithasingle
rootnode,theBOMisactuallycomposedofdozensofseparatehierarchieswithnosingle
hierarchyidrootnode.BomPathsisarecursiveCTEthatreturnsthecurrent
hierarchyid,ComponentID,andProductAssemblyIDforeachrow.
BomPaths
(
Path,
ComponentID,
ProductAssemblyID
)
TheanchorqueryfortheCTEisintwoparts.Thefirstpartreturnstherootnodefor
theentirehierarchy.Inthiscase,therootjustrepresentsalogicalgroupingofallthe
BOM’stop-levelassemblies;itdoesnotrepresentanotherproductthatcanbecreatedby
mashingtogethereveryproductintheAdventureWorkscatalog.
SELECT
hierarchyid::GetRoot(),
NULL,
NULL
Thesecondpartoftheanchorqueryreturnsthehierarchyidpathtothetop-level
assemblies.Eachtop-levelassemblyhasitsComponentIdappendedtotherootpath,
representedbyaleadingforwardslash(/).
SELECT
CAST
('/'+CAST(bc.ComponentIdASvarchar(30))+'/'AS
hierarchyid)ASPath,
bc.ComponentID,
bc.ProductAssemblyID
FROMBomChildrenASbc
WHEREbc.ProductAssemblyIDISNULL
TherecursivepartoftheCTErecursivelyappendsforwardslash-separated
ComponentIdvaluestothepathtorepresenteachcomponentinanygivenassembly:
SELECT
CAST
(bp.path.ToString()+
CAST(bc.ComponentIDASvarchar(30))+'/'AS
hierarchyid)ASPath,
bc.ComponentID,
bc.ProductAssemblyID
FROMBomChildrenASbc
INNERJOINBomPathsASbp
ONbc.ProductAssemblyID=bp.ComponentID
)
ThenextpartofthestatementinsertstheresultsoftherecursiveBomPathsCTEinto
theProduction.HierBillOfMaterialstable.TheresultsoftherecursiveCTE
arejoinedtotheProduction.BillOfMaterialstableforacoupleofreasons:
toensurethatonlycomponentscurrentlyinuseareputintothe
hierarchybymakingsurethattheEndDateisNULLforeach
component
toretrievetheUnitMeasureCodeandPerAssemblyQty
columnsforeachcomponent
WeuseaLEFTOUTERJOINinthisstatementinsteadofanINNERJOINbecause
oftheinclusionofthehierarchyidrootnode,whichhasnomatchingrowinthe
Production.BillOfMaterialstable.Ifyouhadoptednottoincludethe
hierarchyidrootnode,youcouldturnthisjoinbackintoanINNERJOIN.
INSERTINTOProduction.HierBillOfMaterials
(
BomNode,
ProductAssemblyID,
ComponentID,
UnitMeasureCode,
PerAssemblyQty
)
SELECT
bp.Path,
bp.ProductAssemblyID,
bp.ComponentID,
bom.UnitMeasureCode,
bom.PerAssemblyQty
FROMBomPathsASbp
LEFTOUTERJOINProduction.BillOfMaterialsbom
ONbp.ComponentID=bom.ComponentID
ANDCOALESCE(bp.ProductAssemblyID,-1)
=COALESCE(bom.ProductAssemblyID,-1)
WHEREbom.EndDateISNULL
GROUPBY
bp.path,
bp.ProductAssemblyID,
bp.ComponentID,
bom.UnitMeasureCode,
bom.PerAssemblyQty;
ThesimplequeryinListing10-24showstheBOMafterconversiontomaterialized
pathformwiththehierarchyiddatatype,andorderedbythehierarchyidcolumn
todemonstratethatthehierarchyisreflectedfromthehierarchyidcontentitself.
PartialresultsareshowninFigure10-15.
Listing10-24.ViewingtheHierarchyidBOMs
SELECT
BomNode,
BomNode.ToString(),
ProductAssemblyID,
ComponentID,
UnitMeasureCode,
PerAssemblyQty,
BomLevel
FROMProduction.HierBillOfMaterialsORDERBYBomNode;
Figure10-15.PartialResultsofthehierarchicalBOMConversion
Asyoucansee,thehierarchyidcolumn,BomNode,representsthehierarchyasa
compactpathinavariable-lengthbinaryformat.ConvertingtheBomNodecolumnto
stringformatwiththeToString()methodresultsinaforwardslash-separatedpath
reminiscentofafilepath.TheBomLevelcolumnusestheGetLevel()methodto
retrievethelevelofeachnodeinthehierarchy.Thehierarchyidrootnodehasa
BomLevelof0.Thetop-levelassembliesareonlevel1,andtheirchildrenareonlevels2
andbelow.
HierarchyidMethods
Thehierarchyiddatatypeincludesseveralmethodsforqueryingandmanipulating
hierarchicaldata.TheIsDescendantOf()method,forinstance,canbeusedto
retrievealldescendantsofagivennode.TheexampleinListing10-25retrievesthe
descendantnodesofproductassembly749.TheresultsareshowninFigure10-16.
Listing10-25.RetrievingDescendantNodesofAssembly749
DECLARE@CurrentNodehierarchyid;
SELECT@CurrentNode=BomNode
FROMProduction.HierBillOfMaterials
WHEREProductAssemblyID=749;
SELECT
BomNode,
BomNode.ToString(),
ProductAssemblyID,
ComponentID,
UnitMeasureCode,
PerAssemblyQty,
BomLevel
FROMProduction.HierBillOfMaterials
WHERE@CurrentNode.IsDescendantOf(BomNode)=1;
Figure10-16.DescendantNodesofAssembly749
Table10-3isaquicksummaryofthehierarchyiddatatypemethods.
Table10-3.hierarchyidDataTypeMethods
Method Description
GetAncestor(n) Retrievesthenthancestorofthehierarchyidnodeinstance.
GetDescendant(n) Retrievesthenthdescendantofthehierarchyidnode
instance.
GetLevel() Getsthelevelofthehierarchyidnodeinstanceinthe
hierarchy.
GetRoot() Getsthehierarchyidinstancerootnode;GetRoot()isa
staticmethod.
IsDescendantOf(node) Returns1ifaspecifiednodeisadescendantofthe
hierarchyidinstancenode.
Parse(string) Convertsthegivencanonicalstring,inforwardslash-
separatedformat,toahierarchyidpath.
GetReparentedValue(old_root,
new_root) Returnsanodereparentedfromold_roottonew_root.
ToString() Convertsahierarchyidinstancetoacanonicalforward
slash-separatedstringrepresentation.
SpatialDataTypes
Sinceversion2008,SQLServerincludestwodatatypesforstoring,querying,and
manipulatingspatialdata.Thegeometrydatatypeisdesignedtorepresentflat-earth,or
Euclidean,spatialdatapertheOpenGeospatialConsortium(OGC)standard.The
geographydatatypesupportsround-earth,orellipsoidal,spatialdata.Figure10-17
showsasimpletwo-dimensionalflatgeometryforasmallarea,withapointplottedat
location(2,1).
Figure10-17.FlatSpatialRepresentation
Thespatialdatatypesstorerepresentationsofspatialdatausinginstancetypes.There
are12instancetypes,allderivedfromtheGeographyMarkupLanguage(GML)abstract
Geometrytype.Ofthose12instancetypes,only7areconcretetypesthatcanbe
instantiated;theother5serveasabstractbasetypesfromwhichothertypesderive.Figure
10-18showsthespatialinstancetypehierarchywiththeXML-basedGMLtop-level
elements.
Figure10-18.SpatialInstanceTypeHierarchy
Theavailablespatialinstancetypesincludethefollowing:
Point:Thisobjectrepresentsazero-dimensionalobjectrepresenting
asinglelocation.ThePointrequires,ataminimum,atwo-
dimensional(x,y)coordinatepair,butitmayalsohaveanelevation
coordinate(z)andanadditionaluser-definedmeasure.ThePoint
objecthasnoareaorlength.
MultiPoint:Thistyperepresentsacollectionofmultiplepoints.It
hasnoareaorlength.
LineString:Thisisaone-dimensionalobjectrepresentingoneor
moreconnectedlinesegments.Eachsegmentisdefinedbyastart
pointandanendpoint,andallsegmentsareconnectedinsuchaway
thattheendpointofonelinesegmentisthestartpointforthenextline
segment.TheLineStringhaslength,butnoarea.
MultiLineString:Thisisaone-dimensionalobjectcomposedof
multipleLineStringobjects.TheLineStringobjectsina
MultiLineStringdonotnecessarilyhavetobeconnectedtoone
another.TheMultiLineStringhasnoarea,butithasan
associatedlength,whichisthesumofthelengthsofall
LineStringobjectsintheMultiLineString.
Polygon:Thisisatwo-dimensionalobjectdefinedbyasequenceof
connectedpoints.ThePolygonobjectmusthaveasingleexterior
boundingring,whichdefinestheinteriorregionofthePolygon
object.Inaddition,thePolygonmayhaveinteriorboundingrings,
whichexcludeportionsoftheareainsidetheinteriorboundingring
fromthePolygon’sarea.Polygonobjectshavealength,whichis
thelengthoftheexteriorboundingring,andanarea,whichisthearea
definedbytheexteriorboundingringminustheareasdefinedbyany
interiorboundingrings.
MultiPolygon:ThisisacollectionofPolygonobjects.Likethe
Polygon,theMultiPolygonhasbothlengthandarea.
GeometryCollection:Thisisthebaseclassforthe“multi”types
(e.g.MultiPoint,MultiLine,andMultiPolygon).This
classcanbeinstantiatedandcancontainacollectionofanyspatial
objects.
YoucanpopulatespatialdatausingWell-KnownText(WKT)stringsorGML-
formatteddata.WKTstringsarepassedintothegeometryandgeographydatatypes’
STGeomFromText()staticmethodandrelatedstaticmethods.Spatialdatatypescanbe
populatedfromGML-formatteddatawiththeGeomFromGml()staticmethod.Listing
10-26showshowtopopulateaspatialdatatypewithaPolygoninstanceviaaWKT-
formattedstring.ThecoordinatesintheWKTPolygonarethebordersofthestateof
Wyoming,chosenforitssimplicity.TheresultoftheSELECTintheSSMSspatialdata
paneisshowninFigure10-19.
Listing10-26.RepresentingWyomingasaGeometryObject
DECLARE@Wyominggeometry;
SET@Wyoming=geometry::STGeomFromText('POLYGON(
(-104.05310841.698246,-104.05499341.564247,
-104.05350541.388107,-104.05120141.003227,
-104.93396840.994305,-105.27825940.996365,
-106.20289641.000111,-106.32854541.001316,
-106.86483840.998489,-107.30343641.000168,
-107.91803741.00341,-109.04763840.998474,
-110.00145740.997646,-110.06247740.99794,
-111.05028540.996635,-111.05091141.25848,
-111.05032341.578648,-111.04795141.996265,
-111.04602842.503323,-111.04844743.019962,
-111.0467343.284813,-111.04599843.515606,
-111.04962943.982632,-111.05078944.473396,
-111.05084244.664562,-111.0526544.995766,
-110.42889444.992348,-110.39200644.998688,
-109.99478945.002853,-109.79865344.99958,
-108.62457344.997643,-108.25856845.00016,
-107.89371544.999813,-106.25864444.996174,
-106.02057644.997227,-105.08446544.999832,
-105.0412645.001091,-104.05934944.997349,
-104.05897544.574368,-104.06054744.181843,
-104.05924244.145844,-104.0589943.852928,
-104.05742643.503738,-104.0586743.47916,
-104.0557143.003094,-104.05572542.614704,
-104.05300941.999851,-104.05310841.698246))',0);
SELECT@WyomingasWyoming;
Figure10-19.TheWyomingPolygon
Listing10-26demonstratesacoupleofinterestingitems.Thefirstpointisthatthe
coordinatesaregiveninlatitude-longitudeorder,notin(x,y).
(X,Y)OR(LATITUDE,LONGITUDE)?
Coordinatesinspatialdataaregenerallyrepresentedusing(x,y)coordinatepairs.
However,weoftensay“latitude-longitude”whenwerefertocoordinates.Theproblemis
thatlatitudeistheyaxis,whilelongitudeisthexaxis.TheWell-KnownTextformatwe’ll
discusslaterinthissectionrepresentsspatialdatausing(x,y)coordinatepairorderingfor
thegeometryandgeographydatatypes.ButtheGMLsyntaxexpressescoordinates
theotherwayaround,withlatitudebeforelongitude.Youneedtobeawareofthis
differencewhenenteringcoordinates.
Thesecondpointisthatthefinalcoordinatepair,(-104.053108,41.698246),isthe
sameasthefirstcoordinatepair.ThisisarequirementforPolygonobjects.
YoucanpopulateageographyinstancesimilarlyusingWKTorGML.Listing10-
27populatesageographyinstancewiththebordercoordinatesforthestateof
WyomingusingGML.TheresultwillbethesameasshownpreviouslyinFigure10-19.
Listing10-27.UsingGMLtoRepresentWyomingasaGeographyObject
DECLARE@Wyominggeography;
SET@Wyoming=geography::GeomFromGml('<Polygon
xmlns="http://www.opengis.net/gml">
<exterior>
<LinearRing>
<posList>
41.698246-104.05310841.999851-104.053009
43.003094-104.0557143.503738-104.057426
44.145844-104.05924244.574368-104.058975
45.001091-105.0412644.997227-106.020576
44.999813-107.89371544.997643-108.624573
45.002853-109.99478944.992348-110.428894
44.664562-111.05084243.982632-111.049629
43.284813-111.0467342.503323-111.046028
41.578648-111.05032340.996635-111.050285
40.997646-110.00145741.00341-107.918037
40.998489-106.86483841.000111-106.202896
40.994305-104.93396841.388107-104.053505
41.698246-104.053108
</posList>
</LinearRing>
</exterior>
</Polygon>',4269);
Likethegeometrydatatype,thegeographydatatypehassomeinteresting
features.Thefirstthingtonoticeisthatthecoordinatesaregiveninlatitude-longitude
order,becauseoftheGMLformat.AnotherthingtonoticeisthatinGMLformat,there
arenocommaseparatorsbetweencoordinatepairs.Allcoordinatesareseparatedby
whitespacecharacters.GMLalsorequiresyoutodeclaretheGMLnamespace
http://www.opengis.net/gml.
ThecoordinatepairsinListing10-27arealsolistedinreverseorderfromthe
geometryinstanceinListing10-26.Thisisrequiredbecausethegeographydatatype
representsellipsoidalspatialdata.EllipsoidaldatainSQLServerhasacoupleof
restrictionsonit:anobjectmustallfitinonehemisphereanditmustbeexpressedwitha
counterclockwiseorientation.Theselimitationsdonotapplytothegeometrydatatype.
TheselimitationsarediscussedfurtherintheHemisphereandOrientationsidebarinthis
section.
Thefinalthingtonoticeisthatwhenyoucreateageometryinstance,youmustspecify
aspatialreferenceidentifier(SRID).TheSRIDusedhereis4269,whichistheGCSNorth
AmericanDatum1983(NAD83).AdatumisanassociatedellipsoidmodelofEarthon
whichthecoordinatedataisbased.WeusedSRID4269becausethecoordinatesusedin
theexampleareborrowedfromtheUSCensusBureau’sTIGER/Linedata,whichisin
turnbasedonNAD83.Asyoucansee,usingthegeographydatatypeisslightlymore
involvedthanusingthegeometrydatatype,butitcanprovidemoreaccurateresultsand
additionalfunctionalityforEarth-basedgeographicinformationsystems(GISs).
HemisphereandOrientation
InSQLServer2008,thegeographydatatyperequiredspatialobjectstobecontainedin
asinglehemisphere—theycouldn’tcrosstheequator.Thatwasmostlyforperformance
reasons.BeginninginSQLServer2012,youcancreategeographyinstanceslargerthan
asinglehemispherebyusingthenewobjecttypenamedFULLGLOBE.
Youneedalsotospecifiytherightringorientation.Sowhyisringorientationso
important,andwhatisthe“right”ringorientation?Toanswerthesequestions,youhaveto
askyetanotherquestion:“WhatistheinsideofaPolygon?”Youmightinstinctivelysay
thattheinsideofaPolygonisthesmallestareaenclosedbythecoordinatesyousupply.
ButyoucouldendupinasituationwhereyourPolygonshouldbethelargerarea
enclosedbyyourcoordinates.IfyoucreatedaborderaroundtheNorthPole,forinstance,
isyourPolygontheareawithintheborderorisittherestoftheEarthminustheNorth
Pole?Youranswertothisquestiondetermineswhatthe“inside”ofthePolygonreally
is.
ThenextstepistotellSQLServerwheretheinsideofthePolygonlies.SQL
Server’sgeographyinstancemakesyoudefineyourcoordinatesincounterclockwise
order,sotheinsideofthePolygoniseverythingthatfallsontheleft-handsideofthe
linesconnectingthecoordinates.Inthefollowingillustration,theimageontheleftsideis
aninvalidorientationbecausethecoordinatesaredefinedinaclockwiseorder.Theimage
ontherightsideisavalidorientationbecauseitscoordinatesaredefinedina
counterclockwiseorder.Ifyoufollowthedirectionofthearrowsontheimage,you’ll
noticethattheareaontheleft-handsideofthearrowsisthearea“inside”thePolygon.
ThiseliminatesanyambiguityfromyourPolygondefinitions.
Keeptheserestrictionsinmindifyoudecidetousethegeographydatatypein
additionto,orinsteadof,thegeometrydatatype.
PolygonandMultiPolygonaretwoofthemoreinterestingandcomplexspatial
objectsyoucancreate.WeliketousethestateofUtahasareal-worldexampleofa
Polygonobjectforacoupleofreasons.First,theexteriorboundingringforthestateis
verysimple,composedofrelativelystraightlines.Second,theGreatSaltLakewithinthe
statecanbeusedasahighlyvisibleexampleofaninteriorboundingring.Figure10-20
showsthestateofUtah.
Figure10-20.ThestateofUtahwiththeGreat
ThestateofMichiganprovidesanexcellentexampleofaMultiPolygonobject.
Michiganiscomposedoftwodistinctpeninsulas,knownastheUpperPeninsulaand
LowerPeninsula,respectively.ThetwopeninsulasareseparatedbytheStraitsof
Mackinac,whichjoinLakeMichigantoLakeHuron.Figure10-21showstheMichigan
MultiPolygon.
Figure10-21.MichiganasaMultiPolygonSaltLakeasanInteriorBoundingRing
MichiganandtheGreatlakes
Michigan’stwopeninsulasareseparatedbytheStraitsofMackinac,whichisafive-mile-
widechannelthatjoinstwooftheGreatLakes,LakeMichiganandLakeHuron.Although
thesetwobodiesofwaterarehistoricallyreferredtoasseparatelakes,hydrologists
considerthemtobeonecontiguousbodyofwater.Hydrologyexpertssometimesreferto
thelakesasasingleentity,LakeMichigan-Huron.Ontheotherhand,itmakessenseto
considerthetwolakesasseparatefromapoliticalpointofview,sinceLakeMichiganis
whollywithinthebordersoftheUnitedStates,whiletheborderbetweentheUnitedStates
andCanadadividesLakeHuron.Forthepurposesofthissection,themostimportantfact
isthatthelakesseparateMichiganintotwopeninsulas,makingitagoodexampleofa
MultiPolygon.
Throughtheuseofthespatialinstancetypes,youcancreatespatialobjectsthatcover
theentirerangefromverysimpletoextremelycomplex.Onceyou’vecreatedspatial
objects,youcanusethegeometryandgeographydatatypemethodsonthemor
createspatialindexesonspatialdatatypecolumnstoincreasecalculationefficiency.
Listing10-28usesthegeographydatatypeinstancecreatedinListing10-22andthe
STIntersects()methodtoreportwhetherthetownofLaramieandtheStatueof
LibertyarelocatedwithinthebordersofWyoming.TheresultsareshowninFigure10-22.
Listing10-28.AretheStatueofLibertyandLaramieinWyoming?
DECLARE@Wyominggeography,
@StatueOfLibertygeography,
@Laramiegeography;
SET@Wyoming=geography::GeomFromGml('<Polygon
xmlns="http://www.opengis.net/gml">
<exterior>
<LinearRing>
<posList>
41.698246-104.05310841.999851-104.053009
43.003094-104.0557143.503738-104.057426
44.145844-104.05924244.574368-104.058975
45.001091-105.0412644.997227-106.020576
44.999813-107.89371544.997643-108.624573
45.002853-109.99478944.992348-110.428894
44.664562-111.05084243.982632-111.049629
43.284813-111.0467342.503323-111.046028
41.578648-111.05032340.996635-111.050285
40.997646-110.00145741.00341-107.918037
40.998489-106.86483841.000111-106.202896
40.994305-104.93396841.388107-104.053505
41.698246-104.053108
</posList>
</LinearRing>
</exterior>
</Polygon>',4269);
SET@StatueOfLiberty=geography::GeomFromGml('<Point
xmlns="http://www.opengis.net/gml">
<pos>
40.689124-74.044483
</pos>
</Point>',4269);
SET@Laramie=geography::GeomFromGml('<Point
xmlns="http://www.opengis.net/gml">
<pos>
41.312928-105.587253
</pos>
</Point>',4269);
SELECT'IstheStatueofLibertyinWyoming?',
CASE@Wyoming.STIntersects(@StatueOfLiberty)
WHEN0THEN'No'
ELSE'Yes'
ENDASAnswer
UNION
SELECT'IsLaramieinWyoming?',
CASE@Wyoming.STIntersects(@Laramie)
WHEN0THEN'No'
ELSE'Yes'
END;
Figure10-22.TheResultsoftheSTIntersection()MethodExample
SQLServeralsoallowsyoutocreatespatialindexesthatoptimizespatialdata
calculations.Spatialindexesarecreatedbydecomposingyourspatialdataintoab-tree-
basedgridhierarchyfourlevelsdeep.Eachlevelrepresentsafurthersubdivisionofthe
cellsaboveitinthehierarchy.Figure10-23showsasimpleexampleofadecomposed
spatialgridhierarchy.
Figure10-23.DecomposingSpaceforSpatialIndexing
TheCREATESPATIALINDEXstatementallowsyoutocreatespatialindexeson
spatialdatatypecolumns.Listing10-29isanexampleofaCREATESPATIALINDEX
statement.
Listing10-29.CreatingaSpatialIndex
CREATESPATIALINDEXSIX_LocationONMyTable
(SpatialColumn);
Spatialindexingisoneofthebiggestbenefitsofstoringspatialdatainsidethe
database.Asoneastutedeveloperpointedout,“Withoutspatialindexing,youmayaswell
storeyourspatialdatainflatfiles.”
NoteProSpatialwithSQLServer2012,byAlastairAitchison(Apress,2012),isa
fullydedicatedbookaboutSQLServerSpatial,afeaturemuchmorecomplexthatwhat
wepresenthere.
FILESTREAMSupport
SQLServerisoptimizedfordealingwithhighlystructuredrelationaldata,butSQL
developershavelonghadtodealwithheterogeneousunstructureddata.The
varbinary(max)LOB(LargeObject)datatypeprovidesausefulmethodofstoring
arbitrarybinarydatadirectlyindatabasetables;however,itstillhassomelimitations,
includingthefollowing:
Thereisahard2.1GBlimitonthesizeofbinarydatathatcanbe
storedinavarbinary(max)column,whichcanbeanissueifthe
documentsyouneedtostorearelarger.
Storingandmanaginglargevarbinary(max)datainSQLServer
canhaveanegativeimpactonperformance,owinglargelytothefact
thattheSQLServerenginemustmaintainproperlockingandisolation
levelstoensuredataintegrityinthedatabase.
Manydevelopersandadministratorshavecomeupwithcleversolutionstowork
aroundthisproblem.MostofthesesolutionsarefocusedonstoringLOBdataasfilesin
thefilesystemandstoringfilepathspointingtothosefilesinthedatabase.Thisintroduces
additionalcomplexitiestothesystemsinceyoumustmaintainthelinksbetweendatabase
entriesandphysicalfilesinthefilesystem.YoualsomustmanageLOBdatastoredinthe
filesystemusingexternaltools,outsideofthescopeofdatabasetransactions.Finally,this
typeofsolutioncandoubletheamountofworkrequiredtoproperlysecureyourdata,
sinceyoumustmanagesecurityinthedatabaseandseparatelyinthefilesystem.
SQLServerprovidesathirdoption:integratedFILESTREAMsupport.SQLServer
canstoreFILESTREAM-enabledvarbinary(max)dataasfilesinthefilesystem.SQL
ServercanmanagethecontentsoftheFILESTREAMcontainersonthefilesystemforyou
andcontrolaccesstothefiles,whiletheNTFileSystem(NTFS)providesefficientfile
streamingandfilesystemtransactionsupport.ThiscombinationofSQLServerandNTFS
functionalityprovidesseveraladvantageswhendealingwithLOBdata,including
increasedefficiency,manageability,andconcurrency.Microsoftprovidessomegeneral
guidelinesforuseofFILESTREAMoverregularLOBdatatypes,includingthefollowing:
WhentheaveragesizeofyourLOBsisgreaterthan1MB
WhenyouhavetostoreanyLOBsthatarelargerthan2.1GB
Whenfast-readaccessisapriority
WhenyouwanttoaccessLOBdatafrommiddle-tiercode
TipForsmallerandlimitedLOBdata,storingthedatadirectlyinthedatabasemight
makemoresensethanusingFILESTREAM.
EnablingFILESTREAMSupport
ThefirststeptousingFILESTREAMfunctionalityinSQLServerisenablingit.Youcan
enableFILESTREAMsupportthroughtheSQLServerConfigurationManager.Youcan
setFILESTREAMaccessintheSQLServerservicePropertiesFILESTREAMpage.Once
you’veenabledFILESTREAMsupport,youcansetthelevelofaccessfortheSQLServer
instancewithsp_configureandthenrestarttheSQLServerservice.Listing10-30
enablesFILESTREAMsupportontheSQLServerinstanceforthemaximumallowable
access.
Listing10-30.EnablingFILESTREAMSupportontheServer
EXECsp_configure'filestreamaccesslevel',2;
RECONFIGURE;
TheconfigurationvaluedefinestheaccesslevelforFILESTREAMsupport.Thelevels
supportedarelistedinTable10-4.
Table10-4.FILESTREAMAccessLevels
ConfigurationValue Description
0 Disabled(default)
1 AccessviaT-SQLonly
2 AccessviaT-SQLandfilesystem
YoucanusethequeryinListing10-31toseetheFILESTREAMconfiguration
informationatanytime.SampleresultsfromourlocalserverareshowninFigure10-24.
Listing10-31.ViewingFILESTREAMConfigurationInformation
SELECT
SERVERPROPERTY('ServerName')ASServerName,
SERVERPROPERTY('FilestreamSharename')ASShareName,
CASESERVERPROPERTY('FilestreamEffectiveLevel')
WHEN0THEN'Disabled'
WHEN1THEN'T-SQLAccessOnly'
WHEN2THEN'LocalT-SOL/FileSystemAccessOnly'
WHEN3THEN'LocalT-SOL/FileSystemandRemoteFile
SystemAccess'
ENDASEffective_Level,
CASESERVERPROPERTY('FilestreamConfiguredLevel')
WHEN0THEN'Disabled'
WHEN1THEN'T-SQLAccessOnly'
WHEN2THEN'LocalT-SOL/FileSystemAccessOnly'
WHEN3THEN'LocalT-SOL/FileSystemandRemoteFile
SystemAccess'
ENDASConfigured_Level;
Figure10-24.ViewingFILESTREAMConfigurationInformation
CreatingFILESTREAMFilegroups
Onceyou’veenabledFILESTREAMsupportonyourSQLServerinstance,youhaveto
createanSQLServerfilegroupwiththeCONTAINSFILESTREAMoption.This
filegroupiswhereSQLServerwillstoreFILESTREAMLOBfiles.AsAdventureWorks
2014isshippedwithoutaFILESTREAMfilegroup,weneedtoadditmanually.Listing
10-32showsthefinalgeneratedCREATEDATABASEstatementasifwehadcreatedthe
databasefromscratch.TheFILEGROUPclauseofthestatementthatcreatesthe
FILESTREAMfilegroupisshowninbold.
Listing10-32.CREATEDATABASEforAdventureWorksDatabase
CREATEDATABASE[AdventureWorks]
CONTAINMENT=NONE
ONPRIMARY
(NAME=N'AdventureWorks2014_Data',FILENAME
=N'C:\sqldata\MSSQL12.MSSQLSERVER\MSSQL\DATA\AdventureWorks2014_Data.mdf',
SIZE=226304KB,MAXSIZE=UNLIMITED,FILEGROWTH=16384KB
),
FILEGROUP[FILESTREAM1]CONTAINSFILESTREAMDEFAULT
(NAME=N'AdventureWordsFS',FILENAME
=N'C:\sqldata\MSSQL12.MSSQLSERVER\MSSQL\DATA\AdventureWordsFS',
MAXSIZE=UNLIMITED)
LOGON
(NAME=N'AdventureWorks2014_Log',FILENAME
=N'C:\sqldata\MSSQL12.MSSQLSERVER\MSSQL\DATA\AdventureWorks2014_log.ldf',
SIZE=5696KB,MAXSIZE=UNLIMITED,FILEGROWTH=10%);
TocreatethisFILESTREAMfilegrouponanalreadyexistingdatabase,weusedthe
ALTERDATABASEstatementasshowninListing10-33.
Listing10-33.AddingaFILESTREAMFilegrouptoanExistingDatabase
ALTERDATABASEAdventureWorks
ADDFILEGROUPFILESTREAM1CONTAINSFILESTREAM;
GO
ALTERDATABASEAdventureWorks
ADDFILE
(
NAME=N'AdventureWordsFS',
FILENAME=N'
C:\sqldata\MSSQL12.MSSQLSERVER\MSSQL\DATA\AdventureWordsFS'
)
TOFILEGROUPFILESTREAM1;
Youcanseethatthefilecreatedisinfactnotafile,butadirectorywherethefileswill
bestoredbySQLServer.
FILESTREAM-EnablingTables
Onceyou’veenabledFILESTREAMontheserverinstanceandcreatedaFILESTREAM
filegroup,you’rereadytocreateFILESTREAM-enabledtables.FILESTREAMstorageis
accessedbycreatingavarbinary(max)columninatablewiththeFILESTREAM
attribute.TheFILESTREAM-enabledtablemustalsohaveauniqueidentifier
columnwithaROWGUIDCOLattributeandauniqueconstraintonit.The
Production.DocumenttableintheAdventureWorkssampledatabaseisreadyfor
FILESTREAM.Infact,itsDocumentcolumnwasdeclaredasavarbinary(max)
withtheFILESTREAMattributeinAdventureWorks2008,butthisdependencywas
removedinAdventureWorks2012.Now,theDocumentcolumnisstilla
varbinary(max),andtherowguidcolumnisdeclaredasauniqueidentifier
withtheROWGUIDCOLattribute.ToconvertittoaFILESTREAM-enabledtable,we
createanewtablenamedProduction.DocumentFSandimportthelinesfrom
Production.Documentintothatnewtable.Let’sseehowitworksinListing10-34.
TheDocumentandrowguidcolumnsareshowninbold.
Listing10-34.Production.DocumentFILESTREAM-EnabledTable
CREATETABLEProduction.DocumentFS(
DocumentNodehierarchyidNOTNULLPRIMARYKEY,
DocumentLevelAS(DocumentNode.GetLevel()),
Titlenvarchar(50)NOTNULL,
OwnerintNOTNULL,
FolderFlagbitNOTNULL,
FileNamenvarchar(400)NOTNULL,
FileExtensionnvarchar(8)NOTNULL,
Revisionnchar(5)NOTNULL,
ChangeNumberintNOTNULL,
StatustinyintNOTNULL,
DocumentSummarynvarchar(max)NULL,
Documentvarbinary(max)FILESTREAMNULL,
rowguiduniqueidentifierROWGUIDCOLNOTNULL
UNIQUE,
ModifiedDatedatetimeNOTNULL
);
GO
INSERTINTOProduction.DocumentFS
(DocumentNode,Title,Owner,FolderFlag,FileName,
FileExtension,Revision,ChangeNumber,Status,
DocumentSummary,Document,rowguid,ModifiedDate)
SELECT
DocumentNode,Title,Owner,FolderFlag,FileName,
FileExtension,Revision,ChangeNumber,Status,
DocumentSummary,Document,rowguid,ModifiedDate
FROMProduction.Document;
Whenthetableiscreated,weinsertthecontentofProduction.Documentintoit.
Now,wecanopenWindowsExplorerandgotothelocationoftheFILESTREAM
directory.ThecontentofthedirectoryisshowninFigure10-25.Thefilenamesappearas
ajumbleofgroupeddigitsthatdon’tofferupmuchinformationabouttheLOBfiles’
contents,becauseSQLServermanagesthefilenamesinternally.
Figure10-25.LOBFilesStoredintheFILESTREAMFilegroup
CautionSQLServeralsocreatesafilenamedfilestream.hdr.Thisfileisused
bySQLServertomanageFILESTREAMdata.Donotopenormodifythisfile.
AccessingFILESTREAMData
YoucanaccessandmanipulateyourFILESTREAM-enabledvarbinary(max)
columnsusingstandardSQLServerSELECTqueriesandDMLstatementslikeINSERT
andDELETE.Listing10-35demonstratesqueryingthevarbinary(max)columnof
theProduction.DocumentFStable.TheresultsareshowninFigure10-26.
Listing10-35.QueryingaFILESTREAM-EnabledTable
SELECT
d.Title,
d.Document.PathName()ASLOB_Path,
d.DocumentASLOB_Data
FROMProduction.DocumentFSd
WHEREd.DocumentISNOTNULL;
Figure10-26.ResultsofQueryingtheFILESTREAM-enabledTable
ApropertycalledPathName()isexposedonFILESTREAM-enabled
varbinary(max)columnstoretrievethefullpathtothefilecontainingtheLOBdata.
ThequeryinListing10-35usesPathName()toretrievetheLOBpathalongwiththe
LOBdata.Asyoucanseefromthisexample,SQLServerabstractsawaytheNTFS
interactiontoalargedegree,allowingyoutoqueryandmanipulateFILESTREAMdataas
ifitwererelationaldatastoreddirectlyinthedatabase.
TipInmostcases,it’snotagoodideatoretrieveallLOBdatafromaFILESTREAM-
enabledtableinasinglequeryasinthisexample.ForlargetableswithlargeLOBs,this
cancausesevereperformanceproblemsandmakeclientapplicationsunresponsive.Inthis
case,however,theLOBdatabeingqueriedisactuallyverysmallinsize,andtherearefew
rowsinthetable.
SQLServer2008,2012and2014providesupportfortheOpenSqlFilestream
APIforaccessingandmanipulatingFILESTREAMdatainclientapplications.Afull
descriptionoftheOpenSqlFilestreamAPIisbeyondthescopeofthisbook,but
AcceleratedSQLServer2008,byRobWaltersetal.(Apress,2008),providesa
descriptionoftheOpenSqlFilestreamAPIwithsourcecodeforadetailedclient
application.
FileTableSupport
SQLServer2012improvedgreatlytheFILESTREAMtypebyintroducingfiletables.As
wehaveseen,touseFILESTREAMweneedtomanagethecontentonlythroughSQL
Server,byT-SQLorwiththeOpenSqlFilestreamAPI.Itisunfortunate,becausewe
haveaccesstoadirectoryonourfilesystem,whichcannotbemanagedsimplyand
publishescrypticfilenames.Inshort,wehaveagreatfunctionalitythatcouldbemore
flexibleanduser-friendly.Filetablebringsthattothetable.ItmakestheWindows
filesystemnamespacecompatiblewithSQLServertables.Withit,youcancreateatable
inSQLServerthatmerelyreflectsthecontentofadirectoryanditssubdirectories,and
youcanmanageitscontentatthefilesystemlevel,outofSQLServer,withregulartools
liketheWindowsExplorer,orbyfileI/OAPIsinyourclientapplication.Allchanges
madetothefilesystemwillbeimmediatelyreflectedinthefiletable.Infact,thefile
systemasweseeitinthesharedoesnotexistperse;itisakindofmiragecreatedbySQL
Server.FilesordirectorieswillbeinternallyhandledbySQLServerandfilestream
objects,andifyoutrytoaccesstherealdirectorywithWindowsExplorer,itwillbeas
jumbledasanyotherFILESTREAMdirectory.
Tobeabletousefiletables,youfirstneedtohaveactivatedthefilestreamsupportat
theinstancelevelaswehaveseenintheprevioussection.The
filestream_access_leveloptionneedstobesetto2toacceptfileI/Ostreaming
access.Inaddition,theFILESTREAMpropertyofthedatabasemustbesettoacceptnon-
transactedaccess.Wewillseehowtodothatinourexample.Wehavedownloadedazip
packagefromthehttp://openclipart.org/website,containingtheentire
collectionoffreecliparts.Itrepresentsalmost27,000imagefilesatthistime.Wewilladd
theminafiletable.First,inListing10-36,wecreateadedicateddatabasewitha
FILESTREAMfilegroupthatwillstoreourfiletable.TheFILESTREAMfilegroup
creationisshowninbold.
Listing10-36.CreatingaDatabasewithaFILESTREAMFilegroup
CREATEDATABASEcliparts
CONTAINMENT=NONE
ONPRIMARY
(NAME=N'cliparts',FILENAME
=N'C:\sqldata\MSSQL12.MSSQLSERVER\MSSQL\DATA\cliparts.mdf',
SIZE=5120KB,FILEGROWTH=1024KB),
FILEGROUP[filestreamFG1]CONTAINSFILESTREAM
(NAME=N'filestream1',FILENAME
=N'C:\sqldata\MSSQL12.MSSQLSERVER\MSSQL\DATA\filestream1')
LOGON
(NAME=N'cliparts_log',FILENAME
=N'C:\sqldata\MSSQL12.MSSQLSERVER\MSSQL\DATA\cliparts_log.ldf',
SIZE=1024KB,FILEGROWTH=10%);
GO
ALTERDATABASE[cliparts]SETFILESTREAM(
NON_TRANSACTED_ACCESS=FULL,DIRECTORY_NAME=N'cliparts'
);
NoteAsfiletablesarestoredinaFILESTREAMfilegroup,filetablesareincludedin
databasebackups,unlessyouperformfilegroupbackupsandyouexcludethe
FILESTREAMfilegroup.
InthelastlineofListing10-36,wesetthefilestreamoptionto
NON_TRANSACTED_ACCESS=FULL,whichwillensurethatfileswillbewritable
fromtheshareoutsideofSQLServer.Wealsospecifythedirectoryname'cliparts'.
Itwillbeshownasasub-directoryintheFILESTREAMshare.
Thepathwhereafiletablewillbefoundonthesharedependsonthedirectorysetat
thedatabaselevel,plusasub-directorysetwhenthetableiscreated.InListing10-37,we
createthefiletableandadirectorybyinsertingalineinthefiletable.
Listing10-37.CreatingtheFiletable
USE[cliparts];
GO
CREATETABLEdbo.OpenClipartsLibraryASFILETABLE
WITH
(
FILETABLE_DIRECTORY='OpenClipartsLibrary'
);
GO
INSERTINTOdbo.OpenClipartsLibrary(name,is_directory)
VALUES('import_20120501',1);
Tocreateafiletable,wesimplecreateatableASFILETABLE.Wespecifywiththe
optionFILETABLE_DIRECTORY='OpenClipartsLibrary'inwhichdirectory
inthesharethecontentofthetablewillbefound.
NoteThedirectoryofafiletablecanbechangedlaterwithanALTERTABLE.
Asyoucansee,thetablestructureisnotpartoftheCREATETABLEstatement.A
filetableschemaisfixed.WedescribethefiletablecolumnsinTable10-5.
Table10-5.FiletableStructure
Column Type Description
stream_id uniqueindetifier Theuniqueidoftheline,beingafile(aFILESTREAM
document)oradirectory.ThereisaUNIQUEconstraintonit.
file_stream varbinary(max) TheFILESTREAMcolumncontainingthefile.NULLifitisa
directory.
name nvarchar(255) Containsthenameofthefileordirectory.
path_locator hierarchyid Thepositionofthefileordirectoryinthedirectory’shierarchy.
Theprimarykeyofthetable.
parent_path_locator hierarchyid Thepath_locatoroftheparent(ie.,thedirectorycontainingthe
fileordirectory).Acalculatedcolumn.
file_type nvarchar(255) Thetype(extension)ofthefile.Acalculatedcolumn.NULLifit
isadirectory.
cached_file_size bigint Thesizeofthefileinbytes.Acalculatedcolumn.NULLifitisa
directory.
creation_time datetimeoffset(7) Thedateandtimeofcreation.Itissetbydefaultatthecurrent
dateandtimewhentheobjectiscreated.
last_write_time datetimeoffset(7) Thedateandtimeofthelastmodificationofthefileordirectory.
Canbesetmanuallylikecreation_time.
last_access_time datetimeoffset(7) Thedateandtimewhenthefilewaslastaccessed.Canbeset
manuallylikecreation_time.
is_directory bit 1ifitisadirectory.Calculated.
is_offline bit
1iftheextendedNTFSattributeOfflineissetonthefile.That
wouldmeanthatthefileisnotphysicallyinthedirectorybut
storedremotely.
is_hidden bit 1ifthefilehasthehiddenattribute.
is_readonly bit 1ifthefilehastheread-onlyattribute.
is_archive bit 1ifthefilehasthearchivebitset.
is_system bit 1ifthefilehasthesystemattribute.
is_temporary bit 1ifthefilehasthetemporaryattribute.
Toretrievethefiletablesinourdatabase,wecanquerythesys.filetables
catalogview.WealsocanfindtheminSSMSObjectExplorer,intheTables|FileTables
node,asshowninFigure10-27.
Figure10-27.FiletablesinSSMS
YoucanseetheshareitselfinWindowsExplorerbygoingtoNetwork,choosingyour
servernameandenteringthesharenameyousetintheSQLServerConfiguration
Manager.Youcanalsoright-clickonthefiletableintheSSMSObjectExplorer—aswe
seeinFigure10-27—andclickon“ExploreFileTableDirectory,”whichwillopena
WindowsExplorerwindowdirectlyonthefiletabledirectory.Youneedtoaccessit
throughthenetworkshare,andnotdirectlythroughthelocaldirectory,becausethelocal
directorywillonlyshowyouFILESTREAMGUIDnames,whilethenetworkshare,
managedbySQLServer,willshowyouavirtualdirectoryhierarchythatlookslikea
regularhierarchyofdirectoresandfiles.Thisislogicalanyway,asclientsarenot
supposedtoaccessdirectlylocalserverdirectories.Forourexample,wedidthatand
copiedthefullunzippedclipartsdirectoryandsubdirectories.Whenthecopywas
finished,aCOUNT(*)fromdbo.OpenClipartsLibraryreturned27,890lines.
Tomanagefilesanddirectories,youcandoitbyissuingT-SQLstatementsagainstthe
filetable,directlyinthesharewithWindowstools,orprogrammaticallywithWindowsI/O
APIs.AsanexampleofhowtodoitalsobyT-SQL,Listing10-38createsanewdirectory
undertheOpenClipartsLibraryrootdirectory.
Listing10-38.InsertingaDirectoryintheFiletable
INSERTINTOdbo.OpenClipartsLibrary(name,is_directory)
VALUES('directory01',1);
Settingtheis_directorycolumnto1isallyouhavetodotocreateadirectory.
YoucanalsomodifiythefileordirectorypropertiesbyWindowsI/OAPIsorbyT-SQL
queriesagainstthetable.InListing10-39,weinsertasubdirectoryorthenewlycreated
directory01andsetacreationdateasdifferentfromthecurrentdateandtime.
Listing10-39.InsertingaSubdirectory
INSERTINTOdbo.OpenClipartsLibrary
(name,is_directory,creation_time,path_locator)
SELECT
'directory02',1,dateadd(year,-1,sysdatetime()),
path_locator.GetDescendant(NULL,NULL)
FROMdbo.OpenClipartsLibrary
WHEREname='directory01'
ANDis_directory=1
ANDparent_path_locatorISNULL;
ThecodeinListing10-39createsadirectorynameddirectory02asasubdirectoryof
directory01bysettingthepath_locatorofthecreateddirectorywiththe
GetDescendant()hierarchyIdmethodofthedirectory01path_locatorcolumn.
GetDescendant(NULL,NULL)returnstheleastdescendantnodeofthecurrent
hierarchyIdvalue.Tobesurethatdirectory01istheonewecreatedattherootlevel,we
checkthatitsparent_path_locatorisNULL.Wealsosetmanuallythe
creation_datetobeoneyearago.
InFigure10-28,weverifywithWindowsExplorerthatthedirectorywaseffectively
created.Onceagain,youneedtodoitthroughthenetworkshare.
Figure10-28.TheNewlyCreatedDirectory02Directory
NoteYoucannotchangeafiletobeadirectoryorviceversa.Acheckconstraintonthe
filetableenforcesthatis_directorycannotbesetto1whenthefile_stream
columnisnotNULL.
Wheneveryouadd,moveordeleteafileontheshare,orbyT-SQLstatementsagainst
thefiletable,itwillbeimmediatelyreflectedatbothplaces.SQLServerinterceptsallI/O
operationsontheshareandconvertsthemintoDMLactionsonthefiletable.Filesystem
ruleslikenamelimitationsareenforcedbyconstraintsonthefiletable,andtryingtocreate
invalidfilesorfolders(withnamescontaining/?<>\:*|”)inthefiletablewillresultin
aconstraintviolation.
Thereishoweveranimportantdifferencebetweenmanagingthefiletablecontentby
T-SQLorattheWindowslevel.TheDMLstatementsagainstafiletablecanbepartofa
transactionandrolledback,whilecreating,modifying,moving,ordeletingfilesand
foldersbythemeansoftheWindowsI/OAPIscannotbepartofatransaction.That’sthe
reasonwhyweenablednon_transacted_accesssupportinourdatabase.Ifyou
wanttoenabletransactionalmodificationofafileinafiletableoutsideofT-SQLcontext,
youcanusetheOpenSqlFileStreamAPIinyourclientcode,whichwediscussed
previously.
FiletableFunctions
Youcanusededicatedfunctions,FILESTREAMrelatedfunctions,andhierarchyid
functionstomanipulatefilesandfoldersinafiletable.
TheFileTableRootPath()functionreturnsthedatabasesharedirectoryifcalled
withoutargument,orthefiletablesharedirectoryifcalledwiththenameofafiletable
providedinanvarcharargument,asshowninListing10-40.Theresultsareshownin
Figure10-29.
Listing10-40.UsingFileTableRootPath()
USEcliparts;
SELECTFileTableRootPath();
SELECTFileTableRootPath('dbo.OpenClipartsLibrary');
Figure10-29.TheResultsofFileTableRootPath()
Thefunctiontakesasecondoptionalparameter,@option,whichisusefultoreturn
thefullpathinNETBIOSformatorwiththefulldomainname(FDN)oftheserver.The
@optionpossiblevaluesaredetailedinTable10-6.
Table10-6.FileTableRootPath@options
@option
value Description
0ReturnsthepathinNETBIOSformat;thisisthedefaultvalue.ANETBIOScomputernamehas
amaximumof16charactersinuppercase.
1 Returnsthepathwithoutconversion.
2 Returnsthepathwiththefulldomainname(FDN)ofthemachine.
Togetthepathofaspecificfileorfolderinthefiletable,the
GetFileNamespacePath()functioncomesinhandy.Itiscalledasamethodofthe
file_streamcolumn,andtakestwooptionalparameters,thefirst,@is_full_path,
allowsthepathreturnedtoberelative(0)orabsolute(1).Calling
GetFileNamespacePath(1)willproducefullpathsandsavesyoufrom
concatenatingtheresultofFileTableRootPath()withtherelativepath.Thesecond
option,@option,hasthesamevaluesasthe@optionparameterofthe
FileTableRootPath()function.Wedemonstratetheusageof
GetFileNamespacePath()inListing10-41.
Listing10-41.UsingGetFileNamespacePath(),
SELECTfile_stream.GetFileNamespacePath(1)aspath
FROMdbo.OpenClipartsLibrary
WHEREis_directory=1
ORDERBYpath_locator.GetLevel(),path;
ThestatementinListing10-41returnsallthedirectoriesofabsolutepathsorderedby
theirlevelinthedirectories’hierarchyandtheirname.TheGetLevel()hierarchyid
functionappliedtothepath_locatorcolumnallowsyoutoreturnthecurrentlevelof
theiteminthefilesystemrelativetothefiletableroot.
Aswecansee,hierarchyidfunctionsareinterestingwaystomovethroughthe
hierarchy.AnexampleisgiveninListing10-42thatreturnsadirectoryandthenameofits
parentdirectory.ApartialresultisshowninFigure10-30.
Listing10-42.UsinghierarchyidFunctions
SELECTl1.name,l1.path_locator.GetLevel(),l2.nameas
parent_directory
FROMdbo.OpenClipartsLibraryl1
JOINdbo.OpenClipartsLibraryl2ON
l1.path_locator.GetAncestor(1)=l2.path_locator
WHEREl1.is_directory=1;
Figure10-30.TheResultsofUsinghierarchyidFunctions
ByusingtheGetAncestor()hierarchyidfunctiononthepath_locatorinthe
JOINclause,weretrievetheparentpath_locatoranddisplayitsname.Aneasier
waytodothatistousedirectlytheparent_path_locatorcomputedcolumnthat
maintainsaforeignkeyrelationshipwiththepath_locatorcolumninthesametable.
ThequeryinListing10-43returnsexactlythesameresultasthequeryinListing10-42.
Listing10-43.UsingParent_path_locatorColumn
SELECTl1.name,l1.path_locator.GetLevel(),l2.nameas
parent_directory
FROMdbo.OpenClipartsLibraryl1
JOINdbo.OpenClipartsLibraryl2ONl1.parent_path_locator
=l2.path_locator
WHEREl1.is_directory=1;
Thankstotherecursiverelationshipbetweenparent_path_locatorandpath_locator,we
cantraveldownthedirectory’spathwitharecursiveCommonTableExpression(CTE),as
followsinListing10-44.
Listing10-44.UsingaCTEtoTravelDowntheDirectories’Hierarchy
;WITHmycteAS(
SELECTname,path_locator.GetLevel()asLevel,
path_locator
FROMdbo.OpenClipartsLibrary
WHEREname='Yason'
ANDis_directory=1
UNIONALL
SELECTl1.name,l1.path_locator.GetLevel()asLevel,
l1.path_locator
FROMdbo.OpenClipartsLibraryl1
JOINmyctel2ONl1.parent_path_locator=l2.path_locator
WHEREl1.is_directory=1
)
SELECTname,Level
FROMmycte
ORDERBYlevel,name;
Ofcourse,asthepath_locatorcolumnisahierarchyid,wemightaswellexpress
itasinListing10-45.
Listing10-45.UsinghierarchyidFunctionstoTravelDowntheDirectory’sHierarchy
SELECTl1.name,l1.path_locator.GetLevel()asLevel
FROMdbo.OpenClipartsLibraryl1
JOINdbo.OpenClipartsLibraryl2ON
l1.path_locator.IsDescendantOf(l2.path_locator)=1OR
l1.path_locator=l2.path_locator
WHEREl1.is_directory=1
ANDl2.is_directory=1
ANDl2.name='Yason'
ORDERBYlevel,name;
InListing10-45,weusetheIsDescendantOf()functiontoretrievealldescendent
directoriesofthedirectorynamedYason.WehavecopiedafewdirectoriesinYason,and
thequeriesinListings10-44and10-45returnexactlythesameresultshowninFigure10-
31.
Figure10-31.TheResultsoftheQueriesinListings10-44and10-45
Finally,theGetPathLocator()functionreturnsapath_locatorvaluefora
filesystemfullpath.TheexampleinListing10-46retrievesthepath_locatorofthe
Yasondirectory,andusesittofindthematchinglineintheOpenClipartsLibrary
table.TheresultisshowninFigure10-32.
Listing10-46.UsingtheGetPathLocator()function.
DECLARE@path_locatorhierarchyid
SET@path_locator
=GetPathLocator('\\Sql2012\mssqlserver\cliparts\OpenClipartsLibrary\
import_20120501\Yason');
SELECT*
FROMdbo.OpenClipartsLibrary
WHEREpath_locator=@path_locator;
Figure10-32.TheLineFoundUsingtheGetPathLocator()Function
TriggersonFiletables
Filetablescanhavetriggerslikeanyothertables.Becausemakingchangesinthefiletable
shareattheWindowslevelresultsinSQLServercallsbehindthescene,atriggerwillalso
receivetheseevents.
NoteButreplicationandrelatedfeatures(includingtransactionalreplication,merge
replication,changedatacapture,andchangetracking)arenotsupportedwithFileTables.
YoucanseeaFileTableCompatibilitylistwithSQLServerfeaturesatthisaddress:
http://msdn.microsoft.com/en-us/library/gg492086.aspx.
WewilldemonstratethatwiththeaudittableandthetriggercreatedinListing10-47.
Listing10-47.CreatinganAuditTableandaTriggerontheOpenClipartsLibraryTable
CREATETABLEdbo.cliparts_log(
pathnvarchar(4000)notnull,
deletion_datedatetime2(0),
deletion_usersysname,
is_directorybit
)
GO
CREATETRIGGEROpenClipartsLibrary_logTrigger
ON[dbo].[OpenClipartsLibrary]
AFTERDELETE
ASBEGIN
IF@@ROWCOUNT=0RETURN;
SETNOCOUNTON;
INSERTINTOdbo.cliparts_log(path,deletion_date,
deletion_user,is_directory)
SELECTname,SYSDATETIME(),SUSER_SNAME(),is_directory
FROMdeleted
END;
First,wecreateanaudittablenamedcliparts_log.Wewanttokeeptrackoffile
anddirectorydeletions.Wewanttokeepthedateandtimeofdeletionandnameofthe
accountthatdeletedtheitem.Torecorddeletionintothetable,wecreateatriggernamed
OpenClipartsLibrary_logTriggerthatwillfireforeveryDELETEstatement
againsttheOpenClipartsLibrarytable.
Totestit,wegotothefiletablesharewithWindowsExploreranddeletethe
\Sql2014\mssqlserver\cliparts\OpenClipartsLibrary\import_20140501\acspike
directory.Itcontainstwofiles.WhatgetswritteninthetableisshowninFigure10-33.
Figure10-33.TheContentoftheCliparts_logTableaftertheDirectory’sDeletion
Summary
Inthischapter,wefirstdiscussedsomedetailstoknowaboutbasicdatatypes.Mastering
howbasicdatatypesworkallowsyoutounderstandtheimpacttheyhaveonthestorage,
andthereforeontheperformance,ofyourdatabase.Forinstance,thenvarchardata
typestoresUNICODEvaluesandconsumestwicethespaceofthesamevarchar
content.Ifusedlightly,itcanblowupthesizeofyourdatabasefile.The
varchar(max)andvarbinary(max)typesreplacethelegacytextandimage
datatypes.TheyallowaneasyandmoreperformanthandlingonLargeObjects(LOB)
insidethedatabase.Wethenspentsometimeonthedateandtimedatatypes.Theyhave
beenimprovedinSQLServer2008withnewtypesthataremorepreciseandcompact.
Wealsocoveredmoreadvanceddatatypes,likeuniqueidentifier,whichstoreda16-
bytegloballyuniqueidentifier,andhierarchyid,a.NET-baseddatatypethatcanbe
usedinahierarchicaltabletorepresentatreestructure,aswellasthespatialgeometry
andgeographydatatypes.
Finally,weexploredtheFILESTREAMtype.WithFILESTREAM,youcankeep
binarydocumentsinsideadatabasemoreefficiently.ThroughSQLServer,thedocument
willbestoredintheNTFSfilesystemandcanberetrieveddirectlywithI/OAPIs.
Transactionalcoherenceismaintainedonthefilesasiftheywereinsidethedatabasefile.
ThenewfiletablefeatureimprovesuponFILESTREAMbyofferingspecialdatabase
tablesstoringFILESTREAMdocumentsandfolderdefinitionsthatcanbeaccessedsimply
onthefilesystemwithanetworksharemanagedbySQLServer.
EXERCISES
1. [True/False]StoringcharacterstringswithEuropeanlanguage
accents(é,à,ö,forinstance)requiresyoutouseaUNICODE
encoding.
2. [Chooseallthatapply]WhichofthefollowingLOBdatatypesare
deprecated?
a. image
b. varchar(max)
c. text
d. ntext
e. Alloftheabove
3. [True/False]Thenewdatedatatypestorestimeoffset
information.
4. Whatmodeldoesthehierarchyiddatatypeusetorepresent
hierarchicaldatainthedatabase?
5. [Chooseone]WhichofthefollowingistrueofPolygonspatial
objectswhencreatedingeographydatatypeinstances?
f.Theymusthaveaclockwiseorientation.
g.Theymusthaveacounterclockwiseorientation.
h.Orientationdoesnotmatter.
i.Theycannotcrossuptotwohemispheres.
6. [Chooseone]Whichofthefollowingfunctionsadjustsagiven
datetimeoffsetvaluetoanotherspecifiedtimeoffset?
j.TODATETIMEOFFSET
k.SWITCHOFFSET
l.CHANGEOFFSET
m.CALCULATEOFFSET
7. [True/False]TheFILESTREAMfunctionalityinSQLServer2014
usesNTFStoprovidestreamingLOBdatasupport.
8. Whatisthenameofthefiletablecolumnthatallowsyoutoretrieve
thepathofthefileordirectoryonthefiletablenetworkshare?
CHAPTER11
Full-TextSearch
Full-textsearch(FTS)isapowerfulSQLServerfeatureallowingforadvancedsearches
usingmultiplelanguagestofindinformationindocumentsaswellasdocumentproperties.
FTSistightlyintegratedwithSQLServer2014andcanbeeasilymanagedwithSQL
ServerManagementStudio(SSMS)andmonitoredwithstandarddynamicmanagement
views.FTSbroadensthescopeofwhatisthoughtofasaT-SQLsearchbyproviding
meaningfulresultsfromsometimesseeminglyunstructuredtextualdata.SQLServer2012
introducedstatisticalsemanticswhichallowforsearchingondocumentmeaningas
opposedtosimplysearchingcontent.Basedonworddistributionsandotherfactors,
statisticalsemanticsallowsyoutofinddocumentswithsimilarcontents.
FTSArchitecture
Asmentionedearlier,theFTSarchitectureistightlyintegratedwiththeSQLServer
databaseengine.Infact,FTSconsistsoftwomaincomponents:thesqlserverprocess
(sqlserver.exe)andthefilterdaemonhost(fdhost.exe).Thefilterdaemonisresponsible
forretrievingthetextdatafromthetablesandapplyingwordbreaksaswellas
determiningthetypeoftextisbeingretrieved.Thefilterdaemonhostappliesdifferent
rulesbasedonwhetherthedocumentisaWorddocument,anExcelfile,orevenXML.
InformationispassedbetweentheSQLServerprocessandthefilterdaemonhost.
Becausethefdhostprocesshastheresponsibilitytodirectlyaccessandfilterthedata,the
processrequiresaseparatesecurityaccount.ThiskeepstheentireFTSprocessmuchmore
securethaninpreviousimplementations.
TheSQLServerprocessisprimarilyresponsibleformaintainingfull-textindexes,
controllingqueryoptimization,andmaintainingthestoplistandtheasauresobjects.A
stoplistisalistofnon-essentialswordswhichshouldbeignoredinmostlinguistic
searches.Athesaurusissomethingyoufilloutinordertoextendthereachofsearchesto
findmatchesthatFTSmaynothavebeenabletosuggestonitsown.Figure11-1shows
howthesearchitecturalcomponentsareputtotogether.
Figure11-1.FTSarchitecture(simplified)
HereisaquicksummaryofsomeofthebeneficialfeaturesofFTS:
Thefull-textengineishostedintheSQLServerprocess,eliminating
muchoftheoverheadassociatedwithinterservicecommunications.
IntegrationwiththeSQLServerprocesstobetterpredictquery
performancethroughtheuseofnewqueryoperators.
Full-textindexesaremaintainedbytheSQLServerprocessforbetter
optimization.
AbilitytocreatecustomizedstoplistsofwordstoignoreduringFTS,
andtheabilitytocreateathesaurusformoreefficientandaccurate
searching.
Dynamicmanagementviewsandfunctionsthatprovidegreater
transparencyinunderstandinghowFTSqueriesareprocessedand
executed.
CreatingFull-TextCatalogsandIndexes
ThefirststeptotakeadvantageofSQLServerFTSistocreatefull-textcatalogsandfull-
textindexes.Afull-textcatalogcancontainoneormorefull-textindexes,andeachfull-
textindexcanonlybeassignedtoonefull-textcatalog.Youcancreatefull-textcatalogs
andfull-textindexesinSSMSusingGUI(graphicaluserinterface)wizardsorT-SQL
statements.
CreatingFull-TextCatalogs
YoucanaccesstheGUIfull-textcatalogwizardbyright-clickingFullTextCatalogsinthe
SSMSObjectExplorer.TheNewFull-TextCatalogoptiononthepop-upcontextmenu
startsthewizard(seeFigure11-2).
Figure11-2.NewFull-TextCatalogContextMenuOption
AfterselectingNewFull-TextCatalog,SSMSpresentsthewizard’sNewFull-Text
Catalogwindow.Thiswindowallowsyoutodefinethenameofyourfull-textcatalog,the
full-textcatalog’sowner,anaccentsensitivitysetting,andwhetherornotthisfull-text
catalogisdesignatedasthedefaultforadatabase.TheNewFull-TextCatalogwindowis
showninFigure11-3.
Figure11-3.NewFull-TextCatalogWindow
Forthissamplefull-textcatalog,wechosethefollowingoptions:
Thefull-textcatalogisnamedAdventureWorksFTCat,anddboisdesignatedasthe
owner.
Thefirstcreatedfull-textcatalogisdesignatedthedefaultfull-textcatalogforthe
database.Whenanewfull-textindexiscreatedyouwillhaveachoicetocreateitinthe
defaultcatalogorinanyadditionalnon-defaultcatalogs.
TheaccentsensitivityissettoInsensitive,meaningthatwordswithaccentmarksare
treatedasequivalenttothosewithoutaccentmarks(e.g.,forsearchpurposes,resuméis
thesameasresume).
Youcanalsocreateandmanagefull-textcatalogsusingT-SQLstatements.Listing11-
1showshowtocreatethesamefull-textcatalogthatwecreatedpreviouslyinthissection
withtheSSMSwizard.
Listing11-1.CreatingaFull-TextCatalogwithT-SQL
CREATEFULLTEXTCATALOGAdventureWorksFTCat
WITHACCENT_SENSITIVITY=OFF
ASDEFAULT
AUTHORIZATIONdbo;
Onceyou’vecreatedyourfull-textcatalog,thenextstepistobuildfull-textindexes.
Wedescribefull-textindexcreationinthenextsection.Maximumperformancefull-text
catalogs,particularlythoseyouanticipatewillbecomeverylarge,shouldbecreatedon
filegroupsthatarelocatedontheirownphysicaldrives.Thisisalsousefulfor
administrativefunctionssuchasperformingfilegroupbackupsandrestoresindependentof
dataandlogfiles.
CreatingFull-TextIndexes
Aswithfull-textcatalogs,youhavetwooptionsforcreatingfull-textindexes—youcan
usetheGUIwizardinSSMS,oryoucanuseT-SQLstatements.Onceyou’vecreateda
full-textcatalog,asdescribedintheprevioussection,it’stimetodefineyourfull-text
indexes.Beginbyright-clickingatable;theexampleinFigure11-4usesthe
Production.ProductModeltable,intheSSMSObjectExplorertopullupthetablecontext
menu.Fromthecontextmenu,choosetheFull-TextIndex DefineFull-TextIndex
option,showninFigure11-4.
Figure11-4.“Full-TextIndex”ContextMenu
Thefull-textindexwizardshowsasplashscreenthefirsttimeyouaccessit.Youcan
choosetoturnoffthesplashscreenorjustignoreit.Onthenextscreen,showninFigure
11-5,thewizardallowsyoutoselectasingle-columnuniqueindexonthetable.Every
full-textindexrequiresasingle-columnuniqueindexthatallowsthefull-textindexto
referenceindividualrowsinthetable.Ifyoudon’thaveasingle-columnuniqueindex
definedonthetableyou’retryingtocreateafull-textindexon,thewizardwilldisplayan
errormessageassoonasyoutrytorunit.Inthisexample,we’vechosentousethetable’s
integerprimarykeyforthefull-textindex.
Figure11-5.SelectingaSingle-columnUniqueIndex
TipIt’srecommendedthatyouspecifyasingle-columnuniqueindexdefinedonan
integercolumnwhencreatingafull-textindex.Thiswillhelpmaximizeperformance
andminimizefull-textindexstoragerequirements.
Afteryouselectauniqueindex,you’llchoosethecolumnsthatwillprovidethe
searchablecontentforthefull-textindex.Youcanspecifychar,nchar,varchar,
nvarchar,xml,varbinary,varbinary(max),andimagecolumnsinthisstep.
InFigure11-6,thenvarcharandxmldatatypecolumnsofthetableareselectedto
participateinthefull-textindex.We’vealsoselectedEnglishastheword-breaker
languageforeachofthesecolumns.Theword-breakerlanguagespecificationdetermines
thelanguageusedforword-breakingandstemming.SQLServer2014currently
recognizesover50differentlanguages.
Figure11-6.SelectingColumnstoParticipateinFull-textSearches
NoteThetypecolumnisthenameofacolumnindicatingthedocumenttype(e.g.,
MicrosoftWord,Excel,PowerPoint,AdobePDF,andothers)whenyoufull-textindex
documentsstoredinvarbinary(max)orimagecolumns.Beawarethatsome
documenttypesrequireinstallationandconfigurationofadditionalIFiltercomponents.
Moreinformationaboutfull-textandthenewfiletablefeatureisavailableonMicrosoft
TechNetat
http://social.technet.microsoft.com/wiki/contents/articles/9809.store-
and-index-documents-in-sql-server-2012-an-end-to-end-
walkthrough.aspx.
Afteryou’veselectedthecolumnsthatwillparticipateinfull-textsearchesagainsta
table,youmustselectthechange-trackingoption.Changetrackingdetermineswhether
SQLServermaintainsachangelogforthefull-textindexedcolumns,andhowthelogis
usedtoupdatethefull-textindex.Figure11-7showsthechange-trackingoptionsavailable
throughthewizard.
Figure11-7.SelectingaChange-trackingOption
Thechange-trackingoptionsavailablethroughthewizardincludethefollowing:
Automatically:SQLServerupdatesthefull-textindexautomatically
whendataismodifiedinthecolumnsthatparticipateinthefull-text
index.Thisisthedefaultoption.
Manually:Thechange-trackinglogiseitherusedtoupdatethefull-
textindexviaSQLAgentonascheduledbasis,orthroughmanual
intervention.Thisoptionisusefulwhenautomaticfull-textindex
updatescouldslowdownyourserverduringbusinesshours.
Donottrackchanges:SQLServerdoesnottrackchanges.Updating
thefull-textindexrequiresyoutoissueanALTERFULLTEXT
INDEXstatementwiththeSTARTFULLorINCREMENTAL
POPULATIONclausetopopulatetheentirefull-textindex.
TipKeepinmindthatautomaticupdatestothefull-textindexarenotnecessarily
immediateupdates.Whenautomaticchangetrackingisspecified,theremaybesomelag
timebetweenchangesinthetabledataandupdatestothefull-textindex.
Thenextstepinthewizardallowsyoutoassignyourfull-textindextoafull-text
catalog.Youcanchooseapreexistingfull-textcatalog,likethe
AdventureWorksFTCatshowninFigure11-8,oryoucancreateanewfull-text
catalog.Youcanalsochooseafilegroupandfull-textstoplistforthefull-textindexinthis
step.
Figure11-8.AssigningaFull-textIndextoaCatalog
Thefinalstepsofthewizardallowyoutocreateafull-textindexpopulationschedule
andreviewyourpreviouswizardselections.Sinceautomaticpopulationisusedinthe
example,noscheduleisnecessary.
NoteItispossibleyoumayreceiveanerroronthepopulationschedulescreenwhen
usingSQLServer2014ExpressAdvancedServices.Thismightbeduetoabuginthe
application.Youcanignoretheerrorandcontinue.ExpressAdvancedServicesdoes
supportpopulationschedulessoyoucanavoidtheerrorbymanuallycreatingtheschedule
andbypassingtheGUI.ItalsomaybepossibletocreatetheschedulethroughtheGUI
laterbyselectingtheindexproperties.Formore,goto
http://connect.microsoft.com/SQLServer/feedback/details/740181/management-
studio-does-not-fully-manage-full-text-in-sql-server-express.
Inthereviewwindowofthewizard,showninFigure11-9,youcanlookatthechoices
you’vemadeineachstepofthewizardandgobacktopreviousstepstomakechangesif
necessary.OnceyouclicktheFinishbutton,thefull-textindexiscreatedinyourdatabase.
Figure11-9.ReviewWizardSelections
TheSSMSfull-textindexwizardisverythorough,butyoucanalsocreateandmanage
full-textindexesusingT-SQLstatements.Listing11-2showstheT-SQLstatements
requiredtocreateandenableafull-textindexwiththesameoptionspreviouslyselectedin
theSSMSwizardexample.
Listing11-2.CreatingaFull-TextIndexwithT-SQLStatements
CREATEFULLTEXTINDEX
ONProduction.ProductModel
(
CatalogDescriptionLANGUAGEEnglish,
InstructionsLANGUAGEEnglish,
NameLANGUAGEEnglish
)
KEYINDEXPK_ProductModel_ProductModelID
ON
(
AdventureWorksFTCat
)
WITH
(
CHANGE_TRACKINGAUTO
);
GO
ALTERFULLTEXTINDEX
ONProduction.ProductModelENABLE;
GO
TheCREATEFULLTEXTINDEXstatementbuildsthefull-textindexonthe
Production.ProductModeltablewiththespecifiedoptions.Inthisexample,the
CatalogDescription,Instructions,andNamecolumnsareallparticipatinginthefull-text
index.TheLANGUAGEclausespecifiesthattheEnglishlanguagewordbreakerwillbe
usedtoindexthecolumns.Awordbreakerisanaturallyoccurringbreakbetweenwords
basedonalanguage’slexicon.SettingthewordbreakerlanguagetoEnglishhelpsFTS
understandhowthesentencesarestructuredinordertobettersearchonindividualwords.
TheKEYINDEXclausespecifiestheprimarykeyofthetable,
PK_ProductModel_ProductModelID,asthesingle-columnuniqueindexforthetable.
Finally,theCHANGETRACKINGAUTOoptionturnsonautomaticchangetrackingfor
thefull-textindex.
TheALTERFULLTEXTINDEXstatementinthelistingenablesthefull-textindex
andstartsafullpopulation.ALTERFULLTEXTINDEXisaflexiblestatementthatcan
beusedtoaddcolumnsto,orremovecolumnsfrom,afull-textindex.Youcanalsouseit
toenableordisableafull-textindex,setthechange-trackingoptions,startorstopafull-
textindexpopulation,orchangefull-textindexstoplistsettings.
NoteStoplistsarelistsofwordsthatareconsideredunimportantforpurposesofFTS.
Thesewordsareknownasstopwords.Stopwordsarelanguagedependent,withthe
Englishsystemstoplistcontainingwordslikea,an,and,andthe(andmanyothers).SQL
Server2014providesasystemstoplistandallowsyoutocreateyourowncustomstoplists.
Wewilldiscussstoplistslaterinthischapter.
Full-TextQuerying
Afteryoucreateafull-textcatalogandafull-textindex,youcantakeadvantageofFTS
withSQLServer’sFTSpredicatesandfunctions.SQLServerprovidesfourwaystoquery
afull-textindex.TheFREETEXTandCONTAINSpredicatesretrieverowsfromatable
thatmatchagivenFTScriteria,inmuchthesamewaythattheEXISTSpredicatereturns
rowsthatmeetgivencriteria.TheFREETEXTTABLEandCONTAINSTABLEfunctions
returnrowsetswithtwocolumns:akeycolumn,whichisarowidentifier(theunique
indexvaluespecifiedwhenthefull-textindexwascreated)andarankcolumn,whichisa
relevancerating.
TheFREETEXTPredicate
TheFREETEXTpredicateoffersthesimplestmethodofusingFTStosearchcharacter-
basedcolumnsofafull-textindex.FREETEXTsearchesforwordsthatmatchinflectional
formsandthesaurusexpansionsandreplacements.TheFREETEXTpredicateacceptsa
columnnameorlistofcolumns,afree-textsearchstring,andanoptionallanguage
identifier(alocaleID,orLCID).Becauseitisapredicate,FREETEXTcanbeusedinthe
WHEREclauseofaSELECTqueryorDMLstatement.AllrowsforwhichtheFREETEXT
predicatereturnstrue(amatch)arereturned.Listing11-3showsasimpleFREETEXT
querythatusesthefull-textindexcreatedontheProduction.ProductModeltableinthe
previoussection.TheresultsareshowninFigure11-10.Thewildcardcharacter(*)passed
asaparametertotheFREETEXTpredicateindicatesthatallcolumnsparticipatinginthe
full-textindexshouldbesearchedforamatch.ThesecondFREETEXTparameteristhe
wordyouwanttomatch.
Listing11-3.SimpleFREETEXTFull-TextQuery
SELECT
ProductModelID,
Name,
CatalogDescription,
Instructions
FROMProduction.ProductModel
WHEREFREETEXT(*,N'sock');
Figure11-10.UsingFREETEXTtoFindSocks
TheFREETEXTpredicateautomaticallystemswordstofindinflectionalforms.The
queryinListing11-3returnsrowsthatcontainaninflectionalformofthewordsock—in
thiscase,FTSfindstworowsthatcontainthepluralformoftheword,socks.Noticethat
ifyouweretoreplacetheword“socks”with“sox”youreceivethesameresultset.Thisis
becauseFREETEXTalsoperformsFTSthesaurusexpansionsandreplacements
automatically,ifathesaurusfileisavailable.
TheintegrationofFTSwiththeSQLServerqueryengineresultsinamoreefficient
FTSexperience.InSQLServer2014,FTScantakeadvantageofoptimizedoperatorslike
theTableValuedFunction[FulltextMatch]operatorshowninFigure11-
11.ThequeryplanshownisgeneratedbythequeryinListing11-3.
Figure11-11.FREETEXTQueryExecutionPlan
FTSPerformanceOptimization
InpreviousreleasesofSQLServer,theFTSfunctionalitywasprovidedviaan
independentserviceknownasMSFTESQL(MicrosoftFull-TextEngineforSQLServer).
BecauseitwascompletelyseparatefromtheSQLServerqueryengine,theMSFTESQL
servicecouldnottakeadvantageofT-SQLoperatorstooptimizeperformance.Asan
example,considerthefollowingvariationonthequeryinListing11-3:
SELECT
ProductModelID,
Name,
CatalogDescription,
Instructions
FROMProduction.ProductModel
WHEREFREETEXT(*,N'sock')
ANDProductModelID<100;
ImagineforamomentthattheProduction.ProductModeltablehas1,000,000
rowsthatmatchtheFREETEXTpredicate.VersionsofSQLServerpriortoSQLServer
2008wereincapableofusingtheadditionalT-SQLProductModelID<100
predicateintheWHEREclausetolimittherowsaccessedbytheFTSservice.The
MSFTESQLservicehadtoreturnall1,000,000rowsfromtheFREETEXTpredicateand
thennarrowthemdown.BeginningwithSQL2008andcontinuinginSQLServer2014,
theFTSenginecanworkintandemwiththeSQLServerqueryenginetooptimizethe
queryplanandlimitthenumberofrowstouchedbytheFREETEXTpredicate.
TipYou’llseeheavyuseofthephraseinflectionalformsthroughoutthissection.
Inflectionalformsofwordsincludeverbconjugationslikego,goes,going,gone,and
went.Inflectionalformsalsoincludepluralandsingularnounvariantsofwords,likebike
andbikes.SearchingforanywordwithFREETEXTautomaticallyresultsinmatchesofall
supportedinflectionalforms.
Listing11-4demonstratesaFREETEXTquerythatretrievesallrowsthatcontain
inflectionalformsofthewordrideintheCatalogDescriptioncolumn.Another
wordforthisprocessiscalledstemming.Inflectionalformsthatarematchedinthisquery
includethepluralnounridersandtheverbriding.InthisFREETEXTquery,the
CatalogDescriptioncolumnnameisidentifiedbynametorestrictthesearchtoa
singlecolumn,andtheLANGUAGEspecifierisusedtoindicateLCID1033,whichisUS
English.TheresultsareshowninFigure11-12.
Listing11-4.FREETEXTQuerywithAutomaticWordStemming
SELECT
ProductModelID,
Name,
CatalogDescription,
Instructions
FROMProduction.ProductModel
WHEREFREETEXT(CatalogDescription,N'weld',LANGUAGE1033);
Figure11-12.AutomaticStemmingwithFREETEXT
Youcan’tseethewordsthatmatchedinthexmltypeCatalogDescription
(there’snotenoughspaceonthepagetoreproducetheentireresult).Restassuredthat
FREETEXThaslocatedvalidmatchesintherow.ForthefirstmatchtheXMLhasthetext
“Theheattreatedweldedaluminum,”whilethesecondmatchhasthetext“itiswelded
andheattreated.”
TheCONTAINSPredicate
InadditiontotheFREETEXTpredicate,SQLServer2014supportstheCONTAINS
predicate.CONTAINSallowsmoreadvancedfull-textqueryoptionsthantheFREETEXT
predicate.JustlikeFREETEXT,theCONTAINSpredicateacceptsacolumnnameorlist
ofcolumns,asearchcondition,andanoptionallanguageidentifierasparameters.The
CONTAINSpredicatecansearchforsimplestringslikeFREETEXT,butitalsoallows
sophisticatedsearchconditionsthatincludewordorphraseprefixes,wordsthatarein
closeproximitytootherwords,inflectionalwordforms,thesaurussynonyms,and
combinationsofsearchcriteria.
ThesimplestCONTAINSpredicatesarebasicwordsearches,similartoFREETEXT.
UnlikeFREETEXT,however,theCONTAINSpredicatedoesnotautomaticallysearchfor
inflectionalformsofwordsorthesaurusexpansionsandreplacements.Listing11-5
modifiesListing11-4todemonstrateasimpleCONTAINSquery.Theresultsareshownin
Figure11-13.Asyoucansee,acoupleofrowsthatdonotcontainanexactmatchforthe
wordweldareeliminatedfromtheresults.
Listing11-5.SimpleCONTAINSQuery
SELECT
ProductModelID,
Name,
CatalogDescription,
Instructions
FROMProduction.ProductModel
WHERECONTAINS(*,N'weld');
Figure11-13.ResultsoftheSimpleCONTAINSQuery
TouseinflectionalformsorthesaurusexpansionsandreplacementswithCONTAINS,
usetheFORMSOFgenerationterminyoursearchcondition.Listing11-6performsa
CONTAINSsearchontheNameandCatalogDescriptioncolumnsofthe
Production.ProductModeltable.Theresults,whichincludematchesfor
inflectionalformsofthewordsport,likesportsandsporting,areshowninFigure11-14.
Listing11-6.SampleCONTAINSQuerywithFORMSOFInflectionalGenerationTerm
SELECT
ProductModelID,
Name,
CatalogDescription
FROMProduction.ProductModel
WHERECONTAINS
(
(
Name,
CatalogDescription
),
N'FORMSOF(INFLECTIONAL,sport)'
);
Figure11-14.ResultsoftheCONTAINSQuerywithInflectionalFORMSOFTerm
TheCONTAINSpredicatealsoallowsyoutocombinesimplesearchtermslikethese
withtheAND(&),ANDNOT(&!),andOR(|)Booleanoperators.Listing11-7
demonstratescombiningtwosearchtermsinaCONTAINSpredicate.Theresultsofthis
samplequery,whichretrievesallrowscontaininginflectionalformsofthewordsport
(likesports)orthewordtubeintheNameorCatalogDescriptioncolumns,are
showninFigure11-15.
Listing11-7.CompoundCONTAINSSearchTerm
SELECT
ProductModelID,
Name,
CatalogDescription
FROMProduction.ProductModel
WHERECONTAINS
(
(
Name,
CatalogDescription
),
N'"tube"|FORMSOF(INFLECTIONAL,sport)'
);
Figure11-15.ResultsoftheCONTAINSQuerywithaCompoundSearchTerm
Listing11-7usesFORMSOFtoreturnmatchesforinflectionalforms.Youcanalsouse
theFORMSOF(THESAURUS,…)formattoreturnmatchesforexpansionsand
replacementsofwords,asdefinedinyourlanguage-specificthesaurusfiles.
CONTAINSalsosupportsprefixsearchesusingthewildcardasterisk(*)character.
Placethesearchwordorphrase,immediatelyfollowedbythewildcardcharacter,in
doublequotestospecifyaprefixsearch.Listing11-8demonstratesasimpleprefixsearch
toretrieveallrowsthathaveawordstartingwiththeprefixbotintheNamecolumn.The
resultsareshowninFigure11-16.
Listing11-8.CONTAINSPrefixSearch
SELECT
ProductModelID,
Name
FROMProduction.ProductModel
WHERECONTAINS(Name,N'"bot*"');
Figure11-16.ResultsoftheCONTAINSPrefixSearch
TheCONTAINSpredicatealsosupportstheNEAR(~)keywordforproximity
searches.NEARwillreturnmatchesforwordsthatareclosetooneanotherinthesource
columns.Listing11-9demonstratesaNEARproximitysearchthatlooksforinstancesof
thewordaluminumthatoccurincloseproximitytothewordjigintheInstructions
column.TheresultsareshowninFigure11-17.Thisexampleisconsideredageneric
proximitysearch.
Listing11-9.CONTAINSProximitySearch
SELECT
ProductModelID,
Name
FROMProduction.ProductModel
WHERECONTAINS(Instructions,N'aluminumNEARjig');
Figure11-17.CONTAINSProximityQueryResults
TipAvoidusinggenericproximitysearches.Thesewillbedeprecatedinfuture
versionsofSQLServer.Instead,usethecustomproximitysearchesdiscussedlaterinthis
chapter.
SQLServer2012introducedacustomproximitysearchfortheNEARclause.Itallows
youtoeasilysearchforwordswithinacustomizabledistancefromoneanother.Italso
allowsyoutodefinetheorderofthephrasesinyoursearch.Thedistanceisdeterminedby
thenumberofnon-searchablewordsbetweenthewordsincludedinyoursearch.Ifwe
taketheexampleinListing11-9andconvertittoacustomproximitysearch,wefindthat
inordertogetthesameresultswehavetoincludeadistanceofthree.Thismeansthata
maximumofthreewordsexistbetweenthewordsaluminumandjig.Listing11-10shows
therevisedcode.
Listing11-10.CONTAINSCustomSearch
SELECT
ProductModelID,
Name
FROMProduction.ProductModel
WHERECONTAINS(Instructions,'NEAR((aluminum,jig),3)');
Listing11-10givesyouthesameresultsasFigure11-17.Adistanceoftwowillgive
younoresultsbutanyothernumberabovethreegivesyouthesameresultsastheoriginal.
Keepinmindthedistancebetweenthewordsalsoincludesstopwords.Remember
stopwordsarewordsusuallynotincludedinsearches.Keepinmindtoothatthecustom
proximityclauseisnotlimitedtoonlytwosearchwords.Youcouldhavealsoincluded
wordslike“bike,”“weld,”and“frame”—forexample,NEAR((bike,weld,
frame),3).Youcanevenincludephraseslike“bikeriding”or“weldingframe.”
Whateveryouchoose,thedistanceisstillbasedonthedistancebetweenthefirstandlast
wordlistedinthecondition.
Bydefaultthecustomproximitysearchwillignoretheorderofthesearchwords.In
theexampleabove,jigcouldbewithinadistanceofthreeeitherbeforeoraftertheword
aluminum.Ifyouwanttocontroltheorderofthesearchwordsthenyouneedaddthe
TRUEclauseintheNEARstatement.Listing11-11showstwoexamples.Thefirsthasjig
beforealuminumandthesecondhasaluminumbeforejig.Noticethatonlythesecond
examplereturnsvalues.
Listing11-11.CustomSearchwithTRUEClause
SELECT
ProductModelID,
Name
FROMProduction.ProductModel
WHERECONTAINS(Instructions,'NEAR((jig,aluminum),3,
TRUE)');
SELECT
ProductModelID,
Name
FROMProduction.ProductModel
WHERECONTAINS(Instructions,'NEAR((aluminum,jig),3,
TRUE)');
Thecustomproximitysearchalsoallowsforsearchconditionswhichcombine
multiplegroupingofwordsusingexpressionslikeAND,OR,andANDNOT.Theadded
flexibilityoftheSQLServer2014customproximitysearchaddsadvancedfeaturesnot
availableinthegenericsearch.Goingforward,allsearchesshouldbedoneusingthe
customproperties.
TheFREETEXTTABLEandCONTAINSTABLE
Functions
SQLServerprovidesTVF-basedcounterpartstotheFREETEXTandCONTAINS
predicates,knownasFREETEXTTABLEandCONTAINSTABLE.Thesefunctions
operatelikethesimilarlynamedpredicates,butbothfunctionsreturnresultsetsconsisting
ofatablewithtwocolumns,namedKEYandRANK.TheKEYcolumncontainsthekey
indexvaluesrelatingbacktotheuniqueindexofmatchingrowsinthesourcetable,and
theRANKcolumncontainsrelevancerankings.
TheFREETEXTTABLEfunctionacceptsthenameofthetabletosearch,asingle
columnnameorcolumnlist,asearchstring,andanoptionallanguageidentifierjustlike
theFREETEXTpredicate.FREETEXTTABLEcanalsotakeanadditional“topnbyrank”
parametertolimittherowsreturnedtoaspecificnumberofthehighest-rankedrows.The
resultsofFREETEXTTABLEareusefulforjoiningbacktothesourcetableviatheKEY
columnoftheresults.Listing11-12demonstratesasimpleFREETEXTTABLEquerythat
locatesrowswherethewordaluminumappearsintheInstructionscolumnofthe
Production.ProductModeltable.Theresultsarejoinedbacktothesourcetableto
returntheProductModelIDandName,asshowninFigure11-18.
Listing11-12.FREETEXTTABLEResultsJoinedtoSourceTable
SELECT
ftt.[KEY],
ftt.[RANK],
pm.ProductModelID,
pm.NameFROMFREETEXTTABLE
(
Production.ProductModel,
Instructions,
N'aluminum'
)ftt
INNERJOINProduction.ProductModelpm
ONftt.[KEY]=pm.ProductModelID;
Figure11-18.ResultsoftheFREETEXTTABLEQuery
TheCONTAINSTABLEfunctionofferstheadvancedsearchcapabilitiesofthe
CONTAINSpredicateinafunctionform.TheCONTAINSTABLEfunctionacceptsthe
nameofthesourcetable,asinglecolumnnameorlistofcolumns,andaCONTAINS-
stylesearchcondition.LikeFREETEXTTABLE,theCONTAINSTABLEfunctionalso
acceptsanoptionallanguageidentifierand“topnbyrank”parameter.Listing11-13
demonstratestheCONTAINSTABLEfunctioninasimplekeywordsearchthatretrieves
KEYandRANKvaluesforallrowscontaininginflectionalformsofthewordtours.The
resultsareshowninFigure11-19.
Listing11-13.SimpleCONTAINSTABLEQuery
SELECT
[KEY],
[RANK]
FROMCONTAINSTABLE(
Production.ProductModel,
[Name],
N'FORMSOF(INFLECTIONAL,tours)'
);
Figure11-19.ResultsoftheCONTAINSTABLEQuerywithInflectionalForms
CONTAINSTABLEsupportsalloftheoptionssupportedbytheCONTAINSpredicate,
includingtheISABOUTterm,whichallowsyoutoassignweightstothematchedwordsit
locates.WithISABOUT,youassignaweightvaluebetween0.0and1.0toeachsearch
word.CONTAINSTABLEappliestheweighttotherelevancerankingsreturnedinthe
RANKcolumn.Listing11-14showstwoCONTAINSTABLEqueries.Thefirstquery
returnsallproductswiththewordsaluminumorpolishintheirXMLInstructions
column.ThesecondqueryusesISABOUTtoassigneachofthesewordsaweightbetween
0.0and1.0,whichisthenappliedtotheresultRANKforeachrow.Theresults,shownin
Figure11-20,demonstratehowISABOUTweightscanrearrangetherankingsofyour
CONTAINSTABLEqueryresults.
Listing11-14.ISABOUTinaCONTAINSTABLEQuery
SELECT
ct.[RANK],
ct.[KEY],
pm.[Name]
FROMCONTAINSTABLE
(
Production.ProductModel,
Instructions,
N'aluminumORpolish'
)ct
INNERJOINProduction.ProductModelpm
ONct.[KEY]=pm.ProductModelID
ORDERBYct.[RANK]DESC;
SELECT
ct.[RANK],
ct.[KEY],
pm.[Name]FROMCONTAINSTABLE
(
Production.ProductModel,
Instructions,
N'ISABOUT(aluminumWEIGHT(1.0),polishWEIGHT(0.1))'
)ct
INNERJOINProduction.ProductModelpm
ONct.[KEY]=pm.ProductModelID
ORDERBYct.[RANK]DESC;
Figure11-20.ChangingResultSetRankingswithISABOUT
ThesaurusesandStoplists
TheFREETEXTpredicateandFREETEXTTABLEfunctionautomaticallyperformword
stemmingforinflectionalformsandthesaurusexpansionsandreplacements.The
CONTAINSpredicateandCONTAINSTABLEfunctionrequireyoutoexplicitlyspecify
thatyouwantinflectionalformsandthesaurusexpansionsandreplacementswiththe
FORMSOFterm.Whileinflectionalformsincludeverbconjugationsandpluralformsof
words,thesaurusfunctionalityisbasedonuser-managedXMLfilesthatdefineword
replacementandexpansionpatterns.
Eachlanguage-specificthesaurusislocatedinanXMLfileintheFTDatadirectoryof
yourSQLServerinstallation.IfyouinstalledSQLServerwiththedefaultsettingsthenthe
directorywouldbelocatedinthepathC:\ProgramFiles\MicrosoftSQL
Server\MSSQL12.MSSQLSERVER\MSSQL\FTData.Thethesaurusfilesarenamedusing
theformattsnnn.xml,wherennnisathree-lettercoderepresentingaspecific
language.Thefilenametsenu.xml,forinstance,istheUSEnglishthesaurus.To
demonstratetheFTSthesauruscapabilities,we’llbeginbycreatinganewfull-textindex
ontheProduction.ProducttableusingthecodeinListing11-15.
Listing11-15.CreatingaFull-TextIndex
CREATEFULLTEXTINDEXONProduction.Product
(
NameLANGUAGEEnglish,
ColorLANGUAGEEnglish
)
KEYINDEXPK_Product_ProductID
ON(AdventureWorksFTCat)
WITH
(
CHANGE_TRACKINGAUTO,
STOPLIST=SYSTEM
);
GO
ALTERFULLTEXTINDEXONProduction.Product
ENABLE;
GO
YoucaneditthethesaurusXMLfileswithasimpletexteditororamoreadvanced
XMLeditor.Forthisexample,weopenedthetsenu.xmlthesaurusfileinNotepad,
madetheappropriatechanges,andsavedthefilebacktotheMSSQLFTDatadirectory.
Thecontentsofthetsenu.xmlfile,afterouredits,areshowninListing11-16.
Listing11-16.Tsenu.xmlUSEnglishXMLThesaurusFile
<XMLID="MicrosoftSearchThesaurus">
<thesaurusxmlns="x-schema:tsSchema.xml">
<diacritics_sensitive>0</diacritics_sensitive>
<expansion>
<sub>thin</sub>
<sub>flat</sub>
</expansion>
<replacement>
<pat>sapphire</pat>
<pat>indigo</pat>
<pat>navy</pat>
<sub>blue</sub>
</replacement>
</thesaurus>
</XML>
AftereditingtheXMLthesaurusfile,youcanusethesys.spfulltextloadthesaurusfile
storedprocedure(SP)toreloadthethesaurusfile.ThisprocedureacceptsanintegerLCID
parameter,asshowninListing11-17.TheLCIDusedinthelistingis1033,which
specifiesUSEnglish.
Listing11-17.ReloadingUSEnglishXMLThesaurus
EXECsys.sp_fulltext_load_thesaurus_file1033;
GO
NoteStartinginSQLServer2008,reloadingathesaurusinSQLServerdidnotrequire
anSQLServerservicerestart.
Thediacritics_sensitiveelementofthethesaurusfileindicateswhether
accentmarksarereplacedduringexpansionandreplacement.Forinstance,if
diacritics_sensitiveissetto0,thewordscafeandcaféareconsidered
equivalentforpurposesofthethesaurus.Ifdiacritics_sensitiveissetto1,
however,thesetwowordswouldbeconsidereddifferent.
Theexpansionelementindicatessubstitutionsthatshouldbeappliedduringthe
full-textquery.Thewordbeingsearchedisexpandedtomatchtheotherwordsinthe
expansionset.Intheexample,iftheuserqueriesforthewordthin,thesearchis
automaticallyexpandedtoincludematchesforthewordflat,andviceversa.Anexpansion
setcanincludeasmanysubstitutionsasyoucaretodefine,andthethesauruscancontain
asmanyexpansionsetsasyouneed.ThesampleFREETEXTqueryinListing11-18
showstheexpansionsetsinaction,withpartialresultsshowninFigure11-21.
Listing11-18.FREETEXTQuerywithThesaurusExpansionSets
SELECT
ProductID,
Name
FROMProduction.Product
WHEREFREETEXT(*,N'flat');
Figure11-21.PartialResultsoftheFull-textQuerywithExpansionSets
Thereplacementsectionofthethesaurusfileindicatesreplacementsforwordsthat
areusedinafull-textquery.Intheexample,we’vedefinedpatternslikenavy,sapphire,
andindigo,whichwillbereplacedwiththewordblue.Theresultisthatafull-textquery
forthesereplacementpatternswillbeconvertedinternallytoasearchforblue.Listing11-
19showsaFREETEXTquerythatusesthereplacementpatternsdefinedinthethesaurus.
Youcanuseanyofthereplacementpatternsdefinedinthethesaurusfileinthefull-text
querytogetthesameresult.Figure11-22showstheresults.
Listing11-19.FREETEXTQuerywithThesaurusReplacementPatterns
SELECT
ProductID,
Name,
Color
FROMProduction.Product
WHEREFREETEXT(*,N'navy');
Figure11-22.PartialResultsoftheFull-textQuerywithReplacementSets
PreviousversionsofFTShadsystem-definedlistsofnoisewords,whichprovideda
waytoessentiallyignorecommonlyoccurringwordsthatdon’thelpthesearch.
Commonlycitednoisewordsincludedthoselikethe,a,an,andothers.Thenoiseword
implementationinpreviousversionsstoredthenoisewordsinfilesinthefilesystem.
SQLServer2014implementstheclassicnoisewords,knowninFTSasstopwords.
StopwordsaremanagedinsidetheSQLServerdatabaseusingstructuresknownas
stoplists.Youcanusethesystem-suppliedstoplistsorcreateandmanageyourown
language-specificstoplistswiththeCREATEFULLTEXTSTOPLIST,ALTER
FULLTEXTSTOPLIST,andDROPFULLTEXTSTOPLISTstatements.Thestatement
inListing11-20createsastoplistbasedonthesystemstoplist.
Listing11-20.CreatingaFull-TextStoplist
CREATEFULLTEXTSTOPLISTAWStoplist
FROMSYSTEMSTOPLIST;
GO
StoplistsaremoreflexiblethantheoldnoisewordlistssinceyoucaneasilyuseT-SQL
statementstoaddwordstoyourstoplists.ConsiderAdventureWorksproductmodel
searcheswheretheword“instructions”appearsinseveraloftheXMLdocumentsinthe
Instructionscolumn.Youcanaddthewordinstructionstothepreviouslycreated
stoplistwiththeALTERFULLTEXTSTOPLISTstatement,andthenassociatethe
stoplistwiththefull-textindexontheProduction.ProductModeltableviathe
ALTERFULLTEXTINDEXstatement,asshowninListing11-21.Thiswilleffectively
ignorethewordinstructionsduringfull-textsearchesonthiscolumn.
Listing11-21.AddingtheWord“Instructions”totheStoplist
ALTERFULLTEXTSTOPLISTAWStoplist
ADDN'instructions'LANGUAGEEnglish;
GO
ALTERFULLTEXTINDEXONProduction.ProductModel
SETSTOPLISTAWStoplist;
GO
Afterapplicationofthenewlycreatedstoplist,afull-textqueryagainstthe
Production.ProductModeltableforthewordinstructions,asshowninListing11-
22,willreturnnoresults.
Listing11-22.Full-TextQuerywithNewlyCreatedStoplist
SELECT
ProductModelID,
Name
FROMProduction.ProductModel
WHEREFREETEXT(*,N'instructions');
StoredProceduresandDynamicManagementViews
andFunctions
SQLServer2014providesaccesstomanyofthelegacyFTSSPsavailableinprevious
releasesofSQLServer.Mostoftheseprocedureshavebeendeprecated,however,and
havebeenreplacedbyfullyintegratedT-SQLstatementsanddynamicmanagementviews
andfunctions.
SQLServer2014FTSusesthesys.sp_fulltext_load_thesaurus_file
procedurethatweintroducedearlierinthischaptertoloadanXMLthesaurusfile.
Anotherprocedureisthesys.sp_fulltext_resetfdhostaccountprocedure
thatupdatestheWindowsusernameandpasswordthatSQLServerusestostartthefilter
daemonservice.
AbigissuefordeveloperswhousedFTSinSQLServer2005andearlierwasthelack
oftransparency.BasicallyeverythingthatFTSdidwaswellhiddenfromview,and
developersandadministratorshadtotroubleshootFTSissuesinthedark.SQLServer
2008introducedsomecatalogviewsanddynamicmanagementfunctionsthatmadeFTS
moretransparent,andthiscontinuestobethecaseinSQLServer2014.
Ifyou’reexperiencingFTSqueryperformanceissues,the
sys.fulltext_index_fragmentscatalogviewcanprovideinsight.Thiscatalog
viewreportsfull-textindexfragmentsandtheirstatus.Youcanusetheinformationinthis
catalogviewtodecideifit’stimetoreorganizeyourfull-textindex.
Thesys.fulltext_stoplistsandsys.fulltext_stopwordscatalog
viewsletyouseetheuser-definedstopwordsandstoplistsdefinedinthecurrentdatabase.
Theinformationreturnedbythesecatalogviewsisusefulfortroubleshootingissueswith
certainwordsbeingignored(ornotbeingignored)infull-textqueries.The
sys.fulltext_system_stopwordscatalogviewreturnsarowforeverystopword
inthesystemstoplist,whichisusefulinformationtohaveifyouwanttousethesystem
stoplistasthebasisforyourownstoplists.
Thesys.dm_fts_parserfunctionisausefultoolfortroubleshootingfull-text
queries.Thisfunctionacceptsafull-textquerystring,anLCID,astoplistID,andan
accentsensitivitysetting.Theresultreturnedbythefunctionshowstheresultsproduced
bythewordbreakerandstemmerforanygivenfull-textquery.Thisinformationisvery
usefulifyouneedtotroubleshootorjustwanttobetterunderstandexactlyhowtheword
breakerandstemmeraffectyourqueries.Listing11-23isasimpledemonstrationof
stemmingthewordhadwiththesys.dm_fts_parserfunction.Resultsareshownin
Figure11-23.
Listing11-23.UsingSys.dm_fts_parsertoSeeWordBreakingandStemming
SELECT
keyword,
group_id,
phrase_id,
occurrence,
special_term,
display_term,
expansion_type,
source_term
FROMsys.dm_fts_parser
(
N'FORMSOF(FREETEXT,had)',
1033,
NULL,
0
);
Figure11-23.ResultsofWord-breakingandStemmingtheWord“Had”
StatisticalSemantics
Whenyoucreatedtheindex(seeFigure11-6)youhadtheoptiontoselectstatistical
semantics.StatisticalsemanticswasnewinSQLServer2012anditdramaticallychanged
whatitmeanttosearchdocuments.Everythingdiscusseduptonowwasfocusedon
searchingwordswithinadocument.Ifyouneededtofindallthewordssimilarto“weld,”
youcouldfindthembyusingFTSfunctionsagainsttextdatastoredintheSQLServer
engine.ButwhatifyouwantedtofindallthedocumentsstoredinyourSQLServer
databasethatwererelatedtofinanceoraparticularlawcase?Or,let’ssay,youneededto
searchthroughhundredsofresumestodeterminewhichonesbestfitaparticularjob
application.Thisiswherestatisticalsemanticsbecomeshelpful.Statisticalsemanticsis
usedtosearchforthemeaningofdocumentsandnotjusttheircontent.
ThestatisticalsemanticfeaturerequiresFTSbutisinstalledasaseparatefeature.The
installfileislocatedontheSQLServerinstalldisk.The64bitversionislocatedat…
\x64\SetupandthefilenameisSemanticLanguageDatabase.msi.Theinstallwizardis
straight-forward.Thewizardextractsthesemanticdatabasefilestoadirectory.The
defaultdirectoryisC:\ProgramFiles\MicrosoftSemanticLanguageDatabase.Youwill
thenwanttocopyormovethesedatabasefilestoanotherlocation,preferablythesame
locationasyourotherdatabasefiles,andthenattachthedatabase.Oncethedatabaseis
attached,runthecommandinListing11-24.
Listing11-24.InitializingtheStatisticalSemanticsDatabase
EXECsp_fulltext_semantic_register_language_statistics_db
@dbname=N'semanticsdb';
Onceinitialized,youcanverifythedatabaseisreadybyrunningthecodeinListing
11-25.Figure11-24showstheresults.
Listing11-25.VerifyingActiveStatisticalSemanticsDatabase
SELECT*FROM
sys.fulltext_semantic_language_statistics_database
Figure11-24.ResultsofQueryingtheSemanticsDatabase
Fromhereyoucannowgobacktothepropertiesofthe
Production.ProductModelFTSindexwecreatedearlierinthechapterand
checkmarktheStatisticalSemanticscolumnasshowninFigure11-25.
Figure11-25.EnablingStatisticalSemanticsonTableColumns
Nowthatstatisticalsemanticsisinstalledwecandothingslikesearchforkeyphrases
orfindrelateddocuments.TofindakeyphraseweusetheTVF
semantickeyphrasetable.Searchingforkeyphrasesonthe
Production.ProductModelnamecolumnyieldstheresultsweseeinFigure11-26.Runthe
codeinListing11-26togettheresults.
Listing11-26.UsingtheSemantickeyphrasetableFunction
SELECTTOP(10)KEYP_TBL.keyphrase
FROMSEMANTICKEYPHRASETABLE
(
Production.ProductModel,
Name
)ASKEYP_TBL
ORDERBYKEYP_TBL.scoreDESC;
GO
Figure11-26.ResultsfromSemantickeyphrasetableFunction
Semanticsearchingofferssomeinterestingpossibilitiesandbroadensthescopeof
traditionalFTS.IfyouincludetheSQLServer2014FileTablefeaturethenthe
possibilitieswidenevenfurther.FileTableallowsdocumentsstoredonafilesystemtobe
integratedandmanagedthroughSQLServer.Semanticsearchingcanbeperformed
againsttheseandanyotherdocumentmanagedbytheSQLServerengine.
Summary
FTSfunctionalityishighlyintegratedwithSQLServer,providingmoreefficientfull-text
queriesthaneverbefore.Full-textindexesandstoplistsarestoredinthedatabase,making
FTSmoremanageable,flexible,andscalable.
SQLServerprovidesthepowerfulFREETEXTandCONTAINSpredicates,and
FREETEXTTABLEandCONTAINSTABLEfunctions,toperformfull-textsearches.SQL
ServeralsosupportsthesaurusandstoplistfunctionalitytohelpcustomizeFTSaswellas
thenewCONTAINcustomsearchandstatisticalsemantics.SQLServer2014also
providesdynamicmanagementviewsandfunctionstomakeFTSmoretransparentand
easiertotroubleshootthanwasthecaseinpreviousversionsofSQLServer.
Exercises
1. [True/False]Stoplistsandfull-textindexesarestoredinthe
database.
2. [Chooseone]Youcancreateafull-textindexwithwhichofthe
followingmethods:
a. UsingawizardinSSMS
b. UsingtheT-SQLCREATEFULLTEXTINDEXstatement
c. Both(a)and(b)
d. Noneoftheabove
3. [Fillintheblanks]TheFREETEXTpredicateautomatically
performswordstemmingandthesaurus_________and
__________.
4. [Fillintheblank]Stoplistscontainstopwords,whicharewordsthat
are_________duringfull-textquerying.
5. [True/False]Thesys.dm_fts_parserdynamicmanagement
functionshowstheresultsproducedbywordbreakingand
stemming.
CHAPTER12
XML
SQLServer2014continuesthestandardforXMLintegrationincludedwiththeSQL
Server2008release.SQLServer2014XMLstillofferstightintegrationwithT-SQL
throughthexmldatatype,supportfortheWorldWideWebConsortium(W3C)XQuery
andXMLSchemarecommendations.
SQLServer2014’stightXMLintegrationandthexmldatatypeprovidestreamlined
methodsofperformingseveralXML-relatedtasksthatusedtorequireclunkycodeto
interfacewithCOMobjectsandothertoolsexternaltotheSQLServerengine.This
chapterdiscussesthexmldatatypeandtheXMLtoolsbuiltintoT-SQLtotakeadvantage
ofthisfunctionality.
Thenewmemory-optimizedtablesprovidedinSQLServer2014,donotsupportxml
datatypes.Currently,therowlimitsizeis8060bytesandthereisnooff-rowstorage
capability.Ifyouhaveaneedtorelatetoarowofdatawithanxmldatatype,wewould
recommendthatthedatabestoredinadisk-basedtablewithapointerbacktothe
memory-optimizedtable.
LegacyXML
T-SQLsupportforXMLwasintroducedwiththereleaseofSQLServer2000viatheFOR
XMLclauseoftheSELECTstatement,theOPENXMLrowsetprovider,andthe
sp_xml_preparedocumentandsp_xml_removedocumentsystemSPs.Inthis
section,we’lldiscussthelegacyOPENXML,sp_xml_preparedocument,and
sp_xml_removedocumentfunctionality.ThoughthesetoolsstillexistinSQLServer
2014andcanbeusedforbackward-compatibilityscripts,theyareawkwardandkludgyto
use.
OPENXML
OPENXMLisalegacyXMLfunctionthatprovidesarowsetviewofXMLdata.The
processofconvertingXMLdatatorelationalformisknownasshredding.OPENXMLis
technicallyarowsetprovider,whichmeansitscontentscanbequeriedandaccessedlikea
table.ThelegacySQLServerXMLfunctionalityrequiresthe
sp_xml_preparedocumentandsp_xml_removedocumentsystemSPstoparse
textintoanXMLdocumentandcleanupafterward.Theseproceduresareusedin
conjunctionwiththeOPENXMLfunctiontomoveXMLdatafromitstextual
representationintoaparsedinternalrepresentationofanXMLdocument,andfromthere
intoatabularformat.
ThismethodisratherclunkycomparedtothenewermethodsfirstintroducedbySQL
Server2005,butyoumightneeditifyou’rewritingcodethatneedstobebackward
compatible.TheOPENXMLmethodhascertaindisadvantagesbasedonitsheritage,some
ofwhicharelistedhere:
OPENXMLreliesonCOMtoinvoketheMicrosoftXMLCoreServicesLibrary
(MSXML)toperformXMLmanipulationandshredding.
Whenitisinvoked,MSXMLassignsone-eighthofSQLServer’stotalmemorytothe
taskofparsingandmanipulatingXMLdata.
IfyoufailtocallspxmlremovedocumentafterpreparinganXMLdocumentwith
thespxmlpreparedocumentprocedure,itwon’tberemovedfrommemoryuntilthe
SQLServerserviceisrestarted.
TipWestronglyrecommendusingxmldatatypemethodslikenodes(),value(),
andquery()toshredyourXMLdatainsteadofusingOPENXML.We’lldiscussthese
xmldatatypemethodslaterinthischapter,inthesectiontitled“TheXMLDataType
Methods.”
ThesamplequeryinListing12-1isasimpledemonstrationofusingOPENXMLto
shredXMLdata.ThepartialresultsofthisqueryareshowninFigure12-1.
Listing12-1.SimpleOPENXMLQuery
DECLARE@docHandleint;
DECLARE@xmlDocumentnvarchar(max)=N'<Customers>
<CustomerCustomerID="1234"ContactName="Larry"
CompanyName="APress">
<Orders>
<OrderCustomerID="1234"OrderDate="2006-04-
25T13:22:18"/>
<OrderCustomerID="1234"OrderDate="2006-05-
10T12:35:49"/>
</Orders>
</Customer>
<CustomerCustomerID="4567"ContactName="Bill"
CompanyName="Microsoft">
<Orders>
<OrderCustomerID="4567"OrderDate="2006-03-
12T18:32:39"/>
<OrderCustomerID="4567"OrderDate="2006-05-
11T17:56:12"/>
</Orders>
</Customer>
</Customers>';
EXECUTEsp_xml_preparedocument@docHandleOUTPUT,
@xmlDocument;
SELECT
Id,
ParentId,
NodeType,
LocalName,
Prefix,
NameSpaceUri,
DataType,
Prev,
[Text]
FROMOPENXML(@docHandle,N'/Customers/Customer');
EXECUTEsp_xml_removedocument@docHandle;
GO
Figure12-1.ResultsoftheOPENXMLQuery
ThefirststepinusingOPENXMListocallthesp_xml_preparedocumentSPto
convertanXML-formattedstringintoanXMLdocument:
DECLARE@docHandleint;
DECLARE@xmlDocumentnvarchar(max)=N'<Customers>
<CustomerCustomerID="1234"ContactName="Larry"
CompanyName="APress">
<Orders>
<OrderCustomerID="1234"OrderDate="2006-04-
25T13:22:18"/>
<OrderCustomerID="1234"OrderDate="2006-05-
10T12:35:49"/>
</Orders>
</Customer>
<CustomerCustomerID="4567"ContactName="Bill"
CompanyName="Microsoft">
<Orders>
<OrderCustomerID="4567"OrderDate="2006-03-
12T18:32:39"/>
<OrderCustomerID="4567"OrderDate="2006-05-
11T17:56:12"/>
</Orders>
</Customer>
</Customers>';
EXECUTEsp_xml_preparedocument@docHandleOUTPUT,
@xmlDocument;
Thesp_xml_preparedocumentprocedureinvokesMSXMLtoparseyourXML
documentintoaninternalDocumentObjectModel(DOM)treerepresentationofthe
nodes.Thesp_xml_preparedocumentprocedureacceptsuptothreeparameters,as
follows:
Thefirstparameter,calledhdoc,isanoutputparameterthatreturns
aninthandletotheXMLdocumentcreatedbytheSP.
ThesecondparameteristheoriginalXMLdocument.Thisparameter
isknownasxmltextandcanbeachar,nchar,varchar,
nvarchar,text,ntext,orxmldatatype.IfNULLispassedinor
thexmltextparameterisomitted,anemptyXMLdocumentis
created.ThedefaultforthisparameterisNULL.
Athirdoptionalparameter,xpathnamespaces,specifiesthe
namespacedeclarationsusedinOPENXMLXPathexpressions.Like
xmltext,thexpath_namespacesparametercanbeachar,
nchar,varchar,nvarchar,text,ntext,orxmldatatype.
Thedefaultxpath_namespacesvalueis<root
xmlns:mp=“urn:schemas-microsoft-com:xml-
metaprop”>.
TheOPENXMLrowsetprovidershredstheinternalDOMrepresentationoftheXML
documentintorelationalformat.Theresultoftherowsetprovidercanbequeriedlikea
tableorview,asshownfollowing:
SELECT
Id,
ParentId,
NodeType,
LocalName,
Prefix,
NameSpaceUri,
DataType,
Prev,
[Text]
FROMOPENXML(@docHandle,N'/Customers/Customer');
TheOPENXMLrowsetprovideracceptsuptothreeparameters:
Thefirstparameter,hdoc,istheintdocumenthandlereturnedby
thecalltothesp_xml_preparedocumentprocedure.
Thesecondparameter,knownasrowpattern,isannvarchar
XPathquerypatternthatdetermineswhichnodesoftheXML
documentarereturnedasrows.
Thethirdparameterisanoptionalflagsparameter.Thistinyint
valuespecifiesthetypeofmappingtobeusedbetweentheXMLdata
andtherelationalrowset.Ifspecified,flagscanbeacombinationof
thevalueslistedinTable12-1.
Table12-1.OPENXMLFlagsParameterOptions
Value Name Description
0DEFAULT Aflagsvalueof0tellsOPENXMLtodefaulttoattribute-centricmapping.
Thisisthedefaultvalueiftheflagsparameterisnotspecified.
1XML_ATTRIBUTES Aflagsvalueof1indicatesthatOPENXMLshoulduseattribute-centric
mapping.
2XML_ELEMENTS Aflagsvalueof2indicatesthatOPENXMLshoulduseelement-centric
mapping.
3XML_ATTRIBUTES
|XML_ELEMENTS
CombiningtheXML_ATTRIBUTESflagvaluewiththeXML_ELEMENTS
flagvalue(logicalOR)indicatesthatattribute-centricmappingshouldbe
appliedfirst,andelement-centricmappingshouldbeappliedtoallcolumns
notyetdealtwith.
8
Aflagsvalueof8indicatesthattheconsumeddatashouldnotbecopied
totheoverflowproperty@mp:xmltext.Thisvaluecanbecombined
(logicalOR)withanyoftheotherflagsvalues.
TheinternalXMLdocumentgeneratedbysp_xml_preparedocumentiscached
andwillcontinuetotakeupSQLServermemoryuntilitisexplicitlyremovedwiththe
sp_xml_removedocumentprocedure.Thesp_xml_removedocumentprocedure
acceptsasingleparameter,theintdocumenthandleinitiallygeneratedby
sp_xml_preparedocument:
EXECUTEsp_xml_removedocument@docHandle;
CautionAlwayscallsp_xml_removedocumenttofreeupmemoryusedbyXML
documentscreatedwithsp_xml_createdocument.AnyXMLdocumentscreatedwith
sp_xml_createdocumentremaininmemoryuntilsp_xml_removedocumentiscalled
ortheSQLServerserviceisrestarted.Microsoftadvisesthatnotfreeingupmemorywith
sp_xml_removedocumentcouldcauseyourservertorunoutofmemory.
OPENXMLResultFormats
ThesampleinListing12-1returnsatableinedgetableformat,whichisthedefault
OPENXMLrowsetformat.AccordingtoBOL,“Edgetablesrepresentthefine-grained
XMLdocumentstructure…inasingletable”
(http://msdn2.microsoft.com/en-us/library/
ms186918(SQL.11).aspx).Thecolumnsreturnedbytheedgetableformatare
showninTable12-2.
Table12-2.EdgeTableFormat
ColumnName DataType Description
id Bigint TheuniqueIDofthedocumentnode.TherootelementIDis0.
parentid Bigint Theidentifieroftheparentofthenode.Ifthenodeisatop-levelnode,
theparentidisNULL.
nodetype Int Thecolumnthatindicatesthetypeofthenode.Itcanbe1foranelement
node,2foranattributenode,or3foratextnode.
localname Nvarchar Thelocalnameoftheelementorattribute,orNULLiftheDOMobject
doesnothaveaname.
prefix Nvarchar Thenamespaceprefixofthenode.
namespaceuri Nvarchar ThenamespaceURIofthenode,orNULLifthere’snonamespace.
datatype Nvarchar Thedatatypeoftheelementorattributerow,whichisinferredfromthe
inlineDTDorinlineschema.
prev Bigint TheXMLIDoftheprevioussiblingelement,orNULLifthereisno
directprevioussibling.
text Ntext Theattributevalueorelementcontent.
OPENXMLsupportsanoptionalWITHclausetospecifyauser-definedformatforthe
returnedrowset.TheWITHclauseletsyouspecifythenameofanexistingtableora
schemadeclarationtodefinetherowsetformat.ByaddingaWITHclausetothe
OPENXMLqueryinListing12-1,youcanspecifyanexplicitschemafortheresulting
rowset.ThistechniqueisdemonstratedinListing12-2,withresultsshowninFigure12-2.
ThedifferencesbetweenListings12-2and12-1areshowninbold.
Listing12-2.OPENXMLandWITHClause,ExplicitSchema
DECLARE@docHandleint;
DECLARE@xmlDocumentnvarchar(max)=N'<Customers>
<CustomerCustomerID="1234"ContactName="Larry"
CompanyName="APress">
<Orders>
<OrderCustomerID="1234"OrderDate="2006-04-
25T13:22:18"/>
<OrderCustomerID="1234"OrderDate="2006-05-
10T12:35:49"/>
</Orders>
</Customer>
<CustomerCustomerID="4567"ContactName="Bill"
CompanyName="Microsoft">
<Orders>
<OrderCustomerID="4567"OrderDate="2006-03-
12T18:32:39"/>
<OrderCustomerID="4567"OrderDate="2006-05-
11T17:56:12"/>
</Orders>
</Customer>
</Customers>';
EXECUTEsp_xml_preparedocument@docHandleOUTPUT,
@xmlDocument;
SELECT
CustomerID,
CustomerName,
CompanyName,
OrderDate
FROMOPENXML(@docHandle,
N'/Customers/Customer/Orders/Order')
WITH
(
CustomerIDnchar(4)N'../../@CustomerID',
CustomerNamenvarchar(50)N'../../@ContactName',
CompanyNamenvarchar(50)N'../../@CompanyName',
OrderDatedatetime
);
EXECUTEsp_xml_removedocument@docHandle;
GO
Figure12-2.ResultsofOPENXMLwithanExplicitSchemaDeclaration
TheOPENXMLWITHclausecanalsousetheschemafromanexistingtabletoformat
therelationalresultset.ThisisdemonstratedinListing12-3.Thedifferencesbetween
Listing12-3and12-2areshowninbold.
Listing12-3.OPENXMLwithWITHClause,ExistingTableSchema
DECLARE@docHandleint;
DECLARE@xmlDocumentnvarchar(max)=N'<Customers>
<CustomerCustomerID="1234"ContactName="Larry"
CompanyName="APress">
<Orders>
<OrderCustomerID="1234"OrderDate="2006-04-
25T13:22:18"/>
<OrderCustomerID="1234"OrderDate="2006-05-
10T12:35:49"/>
</Orders>
</Customer>
<CustomerCustomerID="4567"ContactName="Bill"
CompanyName="Microsoft">
<Orders>
<OrderCustomerID="4567"OrderDate="2006-03-
12T18:32:39"/>
<OrderCustomerID="4567"OrderDate="2006-05-
11T17:56:12"/>
</Orders>
</Customer>
</Customers>';
EXECUTEsp_xml_preparedocument@docHandleOUTPUT,
@xmlDocument;
CREATETABLE#CustomerInfo
(
CustomerIDnchar(4)NOTNULL,
ContactNamenvarchar(50)NOTNULL,
CompanyNamenvarchar(50)NOTNULL
);
CREATETABLE#OrderInfo
(
CustomerIDnchar(4)NOTNULL,
OrderDatedatetimeNOTNULL
);
INSERTINTO#CustomerInfo
(
CustomerID,
ContactName,
CompanyName
)
SELECT
CustomerID,
ContactName,
CompanyName
FROMOPENXML(@docHandle,N'/Customers/Customer')
WITH#CustomerInfo;
INSERTINTO#OrderInfo
(
CustomerID,
OrderDate
)
SELECT
CustomerID,
OrderDate
FROMOPENXML(@docHandle,N'//Order')
WITH#OrderInfo;
SELECT
c.CustomerID,
c.ContactName,
c.CompanyName,
o.OrderDate
FROM#CustomerInfoc
INNERJOIN#OrderInfoo
ONc.CustomerID=o.CustomerID;
DROPTABLE#OrderInfo;
DROPTABLE#CustomerInfo;
EXECUTEsp_xml_removedocument@docHandle;
GO
TheWITHclauseusedbyeachOPENXMLqueryinListing12-3specifiesatablename.
OPENXMLusesthetable’sschematodefinetherelationalformatoftheresultreturned.
FORXMLClause
SQLServer2000introducedtheFORXMLclauseforusewiththeSELECTstatementto
efficientlyconvertrelationaldatatoXMLformat.TheFORXMLclauseishighlyflexible
andprovidesawiderangeofoptionsthatgiveyoufine-grainedcontroloveryourXML
result.
FORXMLRAW
TheFORXMLclauseappearsattheendoftheSELECTstatementandcanspecifyoneof
fivedifferentmodesandseveralmode-specificoptions.ThefirstFORXMLmodeisRAW
mode,whichreturnsdatainXMLformatwitheachrowrepresentedasanodewith
attributesrepresentingthecolumns.FORXMLRAWisusefulforadhocFORXML
querieswhiledebuggingandtesting.TheFORXMLRAWclauseallowsyoutospecifythe
elementnameforeachrowreturnedinparenthesesimmediatelyfollowingtheRAW
keyword(ifyouleaveitoff,thedefaultname,row,isused).ThequeryinListing12-4
demonstratesFORXMLRAW,withresultsshowninFigure12-3.
Listing12-4.SampleFORXMLRAWQuery
USEAdventureWorks2014;
GO
SELECT
ProductID,
Name,
ProductNumber
FROMProduction.Product
WHEREProductIDIN(770,903)
FORXMLRAW;
Figure12-3.ResultsoftheFORXMLRAWQuery
TheFORXMLclausemodessupportseveraladditionaloptionstocontroltheresulting
output.TheoptionssupportedbyallFORXMLmodesareshowninFigure12-4.
Figure12-4.FORXMLClauseOptions
TheoptionssupportedbyFORXMLRAWmodeincludethefollowing:
TheTYPEoptionspecifiesthattheresultshouldbereturnedasan
xmldatatypeinstance.ThisisparticularlyusefulwhenyouuseFOR
XMLinnestedsubqueries.Bydefault,withouttheTYPEoption,all
FORXMLmodesreturnXMLdataasacharacterstring.
TheROOToptionaddsasingletop-levelrootelementtotheXML
result.UsingtheROOToptionguaranteesawell-formedXML(single
rootelement)result.
TheELEMENTSoptionspecifiesthatcolumndatashouldbereturned
assubelementsinsteadofattributesintheXMLresult.The
ELEMENTSoptioncanhavethefollowingadditionaloptions:
XSINILspecifiesthatcolumnswithSQLnullsareincludedinthe
resultwithanxsi:nilattributesettotrue.
ABSENTspecifiesthatnoelementsarecreatedforSQLnulls.
ABSENTisthedefaultactionforhandlingnulls.
TheBINARYBASE64optionspecifiesthatbinarydatareturnedby
thequeryshouldberepresentedinBase64-encodedformintheXML
result.Ifyourresultcontainsanybinarydata,theBINARYBASE64
optionisrequired.
XMLSCHEMAreturnsaninlineXMLschemadefinition(theW3C
XMLSchemaRecommendationisavailableat
www.w3.org/XML/Schema).
XMLDATAappendsanXML-DataReduced(XDR)schematothe
beginningofyourXMLresult.Thisoptionisdeprecatedandshould
notbeusedforfuturedevelopment.Ifyoucurrentlyusethisoption,
MicrosoftrecommendschangingyourcodetousetheXMLSCHEMA
optioninstead.
AswediscusstheotherFORXMLmodes,wewillpointouttheoptionssupportedby
each.
FORXMLAUTO
Foraqueryagainstasingletable,theAUTOkeywordretrievesdatainaformatsimilarto
RAWmode,buttheXMLnodenameisthenameofthetableandnotthegenericlabel
row.Forqueriesthatjoinmultipletables,however,eachXMLelementisnamedforthe
tablesfromwhichtheSELECTlistcolumnsareretrieved.Theorderofthecolumnnames
intheSELECTlistdeterminetheXMLelementnestingintheresult.TheFORXML
AUTOclauseiscalledsimilarlytotheFORXMLRAWclause,asshowninListing12-5.
TheresultsareshowninFigure12-5.
Listing12-5.FORXMLAUTOQueryonaSingleTable
USEAdventureWorks2014;
GO
SELECT
ProductID,
Name,
ProductNumber
FROMProduction.Product
WHEREProductIDIN(770,903)
FORXMLAUTO;
Figure12-5.ResultsoftheFORXMLAUTOSingle-tableQuery
Listing12-6demonstratesusingFORXMLAUTOinaSELECTquerythatjoinstwo
tables.TheresultsareshowninFigure12-6.
Listing12-6.FORXMLAUTOQuerywithaJoin
SELECT
Product.ProductID,
Product.Name,
Product.ProductNumber,
Inventory.Quantity
FROMProduction.ProductProduct
INNERJOINProduction.ProductInventoryInventory
ONProduct.ProductID=Inventory.ProductID
WHEREProduct.ProductIDIN(770,3)
FORXMLAUTO;
Figure12-6.ResultsoftheFORXMLAUTOQuerywithaJoin
TheFORXMLAUTOstatementcanbefurtherrefinedbyaddingtheELEMENTS
option.JustaswiththeFORXMLRAWclause,theELEMENTSoptiontransformsthe
XMLcolumnattributesintosubelements,asdemonstratedinListing12-7,withresults
showninFigure12-7.
Listing12-7.FORXMLAUTOQuerywithELEMENTSOption
SELECT
ProductID,
Name,
ProductNumber
FROMProduction.Product
WHEREProductID=770
FORXMLAUTO,ELEMENTS;
Figure12-7.ResultsoftheFORXMLAUTOQuerywiththeELEMENTSOption
TheFORXMLAUTOclausecanacceptalmostallofthesameoptionsastheFOR
XMLRAWclause.TheonlyoptionthatyoucanusewithFORXMLRAWthat’snot
availabletoFORXMLAUTOistheuser-definedElementNameoption,sinceAUTOmode
generatesrownamesbasedonthenamesoftablesinthequery.
FORXMLEXPLICIT
TheFORXMLEXPLICITclauseisflexiblebutcomplex.Thisclauseallowsyouto
specifytheexacthierarchyofXMLelementsandattributesinyourXMLresult.This
structureisspecifiedintheSELECTstatementitselfusingaspecial
ElementName!TagNumber!AttributeName!Directivenotation.
TipTheFORXMLPATHclause,describedinthenextsection,alsoallowsyouto
explicitlydefineyourXMLresultstructure.TheFORXMLPATHclauseacceptsXPath-
stylesyntaxtodefinethestructureandnodenames,however,andismucheasiertouse
thanFORXMLEXPLICIT.Asageneralrecommendation,wewouldadviseusingFOR
XMLPATHinsteadofFORXMLEXPLICITfornewdevelopmentandconvertingold
FORXMLEXPLICITqueriestoFORXMLPATHwhenpossible.
InordertogetFORXMLEXPLICITtoconvertyourrelationaldatatoXMLformat,
there’sastrictrequirementontheresultsoftheSELECTquery—itmustreturndatain
universaltableformatthatincludesaTagcolumndefiningthelevelofthecurrenttagand
aParentcolumnwiththeparentlevelforthecurrenttag.Theremainingcolumnsinthe
queryaretheactualdatacolumns.Listing12-8demonstratesaFORXMLEXPLICIT
querythatreturnsinformationaboutaproduct,includingallofitsinventoryquantities,as
anestedXMLresult.TheresultsareshowninFigure12-8.
Listing12-8.FORXMLEXPLICITQuery
SELECT
1ASTag,
NULLASParent,
ProductIDAS[Products!1!ProductID!element],
NameAS[Products!1!ProductName],
ProductNumberAS[Products!1!ProductNumber],
NULLAS[Products!2!Quantity]
FROMProduction.Product
WHEREProductIDIN(770,3)
UNIONALL
SELECT
2ASTag,
1ASParent,
NULL,
NULL,
NULL,
Quantity
FROMProduction.ProductInventory
WHEREProductIDIN(770,3)
FORXMLEXPLICIT;
Figure12-8.ResultsoftheFORXMLEXPLICITQuery
TheFORXMLEXPLICITqueryinListing12-8definesthetop-levelelementswith
Tag=1andParent=NULL.ThenextlevelisdefinedwithTag=2andParent
=1,referencingbacktothetoplevel.Additionallevelscanbeaddedbyusingthe
UNIONkeywordwithadditionalqueriesthatincrementtheTagandParentreferences
foreachadditionallevel.
Eachcolumnofthequerymustbenamedwiththe
ElementName!TagNumber!AttributeName!Directiveformatthatwe
mentionedpreviously.Asspecifiedbythisformat,ElementNameisthenameofthe
XMLelement,inthiscaseProducts.TagNumberistheleveloftheelement,whichis
1fortop-levelelements.AttributeNameisthenameoftheattributeifyouwantthe
datainthecolumntobereturnedasanXMLattribute.Ifyouwanttheitemtobereturned
asanXMLelement,useAttributeNametospecifythenameoftheattribute,andset
theDirectivevaluetoelement.TheDirectivevaluesthatcanbespecified
includethefollowing:
Thehidedirectivevalue,whichisusefulwhenyouwanttoretrieve
valuesforsortingpurposesbutdonotwantthespecifiednode
includedintheresultingXML.
Theelementdirectivevalue,whichgeneratesanXMLelement
insteadofanattribute.
Theelementxsinildirectivevalue,whichgeneratesanelement
forSQLnullcolumnvalues.
Thexmldirectivevalue,whichgeneratesanelementinsteadofan
attribute,butdoesnotencodeentityvalues.
Thecdatadirectivevalue,whichwrapsthedatainaCDATAsection
anddoesnotencodeentities.
Thexmltextdirectivevalue,whichwrapsthecolumncontentina
singletagintegratedwiththedocument.
Theid,idref,andidrefsdirectivevalues,whichallowyouto
createinternaldocumentlinks.
TheadditionaloptionsthattheFORXMLEXPLICITclausesupportsareBINARY
BASE64,TYPE,ROOT,andXMLDATA.Theseoptionsoperatethesameastheydointhe
FORXMLRAWandFORXMLAUTOclauses.
FORXMLPATH
TheFORXMLPATHclausewasfirstintroducedinSQLServer2005.Itprovidesanother
waytoconvertrelationaldatatoXMLformatwithaspecificstructure,butismucheasier
tousethantheFORXMLEXPLICITclause.LikeFORXMLEXPLICIT,theFOR
XMLPATHclausemakesyoudefinethestructureoftheXMLresult.ButtheFORXML
PATHclauseallowsyoutouseasubsetofthewell-documentedandmuchmoreintuitive
XPathsyntaxtodefineyourXMLstructure.
TheFORXMLPATHclauseusescolumnnamestodefinethestructure,aswithFOR
XMLEXPLICIT.InkeepingwiththeXMLstandard,columnnamesintheSELECT
statementwithaFORXMLPATHclausearecasesensitive.Forinstance,acolumn
namedInventoryisdifferentfromacolumnnamedINVENTORY.Anycolumnsthat
donothavenamesareinlined,withtheircontentinsertedasXMLcontentforxmldata
typecolumnsorasatextnodeforotherdatatypes.Thisisusefulforincludingtheresults
ofnamelesscomputedcolumnsorscalarsubqueriesinyourXMLresult.
FORXMLPATHusesXPath-stylepathexpressionstodefinethestructureandnames
ofnodesintheXMLresult.Becausepathexpressionscancontainspecialcharacterslike
theforwardslash(/)andatsign(@),youwillusuallywanttousequotedcolumnaliases
asshowninListing12-9.TheresultsofthissampleFORXMLPATHqueryareshownin
Figure12-9.
Listing12-9.FORXMLPATHQuery
SELECT
p.ProductIDAS"Product/@ID",
p.NameAS"Product/Name",
p.ProductNumberAS"Product/Number",
i.QuantityAS"Product/Quantity"
FROMProduction.Productp
INNERJOINProduction.ProductInventoryi
ONp.ProductID=i.ProductID
WHEREp.ProductID=770
FORXMLPATH;
Figure12-9.ResultsoftheFORXMLPATHQuery
TheFORXMLPATHclauseimposessomerulesoncolumnnaming,sincethe
columnnamesdefinenotonlythenamesoftheXMLnodesgenerated,butalsothe
structureoftheXMLresult.YoucanalsouseXPathnodetestsinyourFORXMLPATH
clauses.TheserulesandnodetestsaresummarizedinTable12-3.
Table12-3.FORXMLPATHColumn-namingConventions
ColumnName Result
text() Thestringvalueofthecolumnisaddedasatextnode.
comment() ThestringvalueofthecolumnisaddedasanXMLcomment.
node() Thestringvalueofthecolumnisinsertedinlineunderthecurrentelement.
*Thisisthesameasnode().
data() Thestringvalueofthecolumnisinsertedasanatomicvalue.Spacesareinserted
betweenatomicvaluesintheresultingXML.
processing-
instruction(name)
ThestringvalueofthecolumnisinsertedasanXML-processinginstruction
namedname.
@name Thestringvalueofthecolumnisinsertedasanattributeofthecurrentelement.
Name Thestringvalueofthecolumnisinsertedasasubelementofthecurrentelement.
elem/name Thestringvalueofthecolumnisinsertedasasubelementofthespecified
elementhierarchy,undertheelementspecifiedbyelem.
elem/@name Thestringvalueofthecolumnisinsertedasanattributeofthelastelementinthe
specifiedhierarchy,undertheelementspecifiedbyelem.
TheFORXMLPATHclausesupportstheBINARYBASE64,TYPE,ROOT,and
ELEMENTSoptions,andtheuser-definedElementNameoptions.TheadditionalFOR
XMLPATHoptionsoperatethesameastheydofortheFORXMLAUTOandFORXML
RAWclauses.
ThexmlDataType
SQLServer’slegacyXMLfunctionalitycanbecumbersomeandclunkytouseattimes.
Fortunately,SQLServer2014providesmuchtighterXMLintegrationwithitsxmldata
type.ThexmldatatypecanbeusedanywherethatotherSQLServerdatatypesareused,
includingvariabledeclarations,columndeclarations,SPparameters,andUDFparameters
andreturntypes.TheT-SQLxmldatatypeprovidesbuilt-inmethodsthatallowyouto
queryandmodifyXMLnodes.Whenyoudeclareinstancesofthexmldatatype,youcan
createthemasuntyped(whichisthedefault),oryoucanassociatethemwithXML
schemastocreatetypedxmlinstances.Thissectiondiscussesbothtypedanduntyped
xmlinT-SQL.
ThexmldatatypecanholdcompleteXMLdocumentsorXMLfragments.AnXML
documentmustfollowalltherulesforwell-formedXML,includingthefollowing:
Well-formedXMLmusthaveatleastoneelement.
Everywell-formedXMLdocumenthasasingletop-level,orroot,
element.
Well-formedXMLrequiresproperlynestedelements(tagscannot
overlap).
Alltagsmustbeproperlyclosedinawell-formedXMLdocument.
Attributevaluesmustbequotedinawell-formedXMLdocument.
Specialcharactersinelementcontentmustbeproperlyentitized,or
convertedtoXMLentitiessuchas&fortheampersand
character.
AnXMLfragmentmustconformtoalltherulesforwell-formedXML,exceptthatit
mayhavemorethanonetop-levelelement.ThestoredinternalrepresentationofanXML
documentorfragmentstoredinanxmlvariableorcolumnmaxesoutataround2.1GBof
storage.
Untypedxml
Untypedxmlvariablesandcolumnsarecreatedbyfollowingthemwiththekeywordxml
inthedeclaration,asshowninListing12-10.
Listing12-10.UntypedxmlVariableandColumnDeclarations
DECLARE@xXML;
CREATETABLEXmlPurchaseOrders
(
PoNumintNOTNULLPRIMARYKEY,
XmlPurchaseOrderxml);
PopulatinganxmlvariableorcolumnwithanXMLdocumentorfragmentrequiresa
simpleassignmentstatement.Youcanimplicitlyorexplicitlyconvertchar,varchar,
nchar,nvarchar,varbinary,text,andntextdatatoxml.Therearesomerules
toconsiderwhenconvertingfromthesetypestoxml:
TheXMLparseralwaystreatsnvarchar,nchar,and
nvarchar(max)dataasatwo-byteUnicode-encodedXML
documentorfragment.
SQLServertreatschar,varchar,andnvarchar(max)dataas
asingle-byte-encodedXMLdocumentorfragment.Thecodepageof
thesourcestring,variable,orcolumnisusedforencodingbydefault.
ThecontentofvarbinarydataispasseddirectlytotheXML
parser,whichacceptsitasastream.IfthevarbinaryXMLdatais
Unicodeencoded,thebyte-ordermark/encodinginformationmustbe
includedinthevarbinarydata.Ifnobyte-ordermark/encoding
informationisincluded,thedefaultofUTF-8isused.
NoteThebinarydatatypecanalsobeimplicitlyorexplicitlyconvertedtoxml,but
itmustbetheexactlengthofthedataitcontains.Theextrapaddingappliedtobinary
variablesandcolumnswhenthedatatheycontainistooshortcancauseerrorsinthe
XML-parsingprocess.Usethevarbinarydatatypewhenyouneedtoconvertbinary
datatoXML.
Listing12-11demonstratesimplicitconversionfromnvarchartothexmldatatype.
TheCASTorCONVERTfunctionscanbeusedwhenanexplicitconversionisneeded.
Listing12-11.PopulatinganUntypedxmlVariable
DECLARE@xxml=N'<?xmlversion="1.0"?>
<Address>
<Latitude>47.642737</Latitude>
<Longitude>-122.130395</Longitude>
<Street>ONEMICROSOFTWAY</Street>
<City>REDMOND</City>
<State>WA</State>
<Zip>98052</Zip>
<Country>US</Country>
</Address>';
SELECT@x;
Typedxml
TocreateatypedxmlvariableorcolumninSQLServer2014,youmustfirstcreatean
XMLschemacollectionwiththeCREATEXMLSCHEMACOLLECTIONstatement.The
CREATEXMLSCHEMACOLLECTIONstatementallowsyoutospecifyaSQLServer
nameforyourschemacollectionandanXMLschematoadd.Listing12-12showshowto
createanXMLschemacollection.
Listing12-12.CreatingaTypedxmlVariable
CREATEXMLSCHEMACOLLECTIONAddressSchemaCollection
ASN'<?xmlversion="1.0"encoding="utf-16"?>
<xsd:schemaxmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:elementname="Address">
<xsd:complexType>
<xsd:sequence>
<xsd:elementname="Latitude"
type="xsd:decimal"/>
<xsd:elementname="Longitude"
type="xsd:decimal"/>
<xsd:elementname="Street"
type="xsd:string"/>
<xsd:elementname="City"
type="xsd:string"/>
<xsd:elementname="State"
type="xsd:string"/>
<xsd:elementname="Zip"
type="xsd:string"/>
<xsd:elementname="Country"
type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>';
GO
DECLARE@xXML(CONTENTAddressSchemaCollection);
SELECT@x=N'<?xmlversion="1.0"?>
<Address>
<Latitude>47.642737</Latitude>
<Longitude>-122.130395</Longitude>
<Street>ONEMICROSOFTWAY</Street>
<City>REDMOND</City>
<State>WA</State>
<Zip>98052</Zip>
<Country>US</Country>
</Address>';
SELECT@x;
DROPXMLSCHEMACOLLECTIONAddressSchemaCollection;
GO
ThefirststepincreatingatypedxmlinstanceistocreateanXMLschemacollection,
aswedidinListing12-12:
CREATEXMLSCHEMACOLLECTIONAddressSchemaCollection
ASN'<?xmlversion="1.0"encoding="utf-16"?>
<xsd:schema
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:elementname="Address">
<xsd:complexType>
<xsd:sequence>
<xsd:elementname="Latitude"
type="xsd:decimal"/>
<xsd:element
name="Longitude"type="xsd:decimal"/>
<xsd:elementname="Street"
type="xsd:string"/>
<xsd:elementname="City"
type="xsd:string"/>
<xsd:elementname="State"
type="xsd:string"/>
<xsd:elementname="Zip"
type="xsd:string"/>
<xsd:elementname="Country"
type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>';
TipTheWorldWideWebConsortium(W3C)maintainsthestandardsrelatedtoXML
schemas.TheofficialXMLSchemarecommendationsareavailableat
www.w3.org/TR/xmlschema-1/andwww.w3.0rg/TR/xmlschema-2/.These
W3CrecommendationsareanexcellentstartingpointforcreatingyourownXML
schemas.
Thenextstepistodeclarethevariableasxmltype,butwithanXMLschema
collectionspecificationincluded:
DECLARE@xXML(CONTENTAddressSchemaCollection);
Intheexample,weusedtheCONTENTkeywordbeforetheschemacollectionnamein
thexmlvariabledeclaration.SQLServerofferstwokeywords,DOCUMENTand
CONTENT,thatrepresentfacetsyoucanusetoconstraintypedxmlinstances.Usingthe
DOCUMENTfacetinyourtypedxmlvariableorcolumndeclarationconstrainsyourtyped
XMLdatasothatitmustcontainonlyonetop-levelrootelement.TheCONTENTfacet
allowszeroormoretop-levelelements.CONTENTisthedefaultifneitherisspecified
explicitly.
ThenextstepintheexampleistheassignmentofXMLcontenttothetypedxml
variable.Duringtheassignment,SQLServervalidatestheXMLcontentagainsttheXML
schemacollection.
SELECT@x=N'<?xmlversion="1.0"?>
<Address>
<Latitude>47.642737</Latitude>
<Longitude>-122.130395</Longitude>
<Street>ONEMICROSOFTWAY</Street>
<City>REDMOND</City>
<State>WA</State>
<Zip>98052</Zip>
<Country>US</Country>
</Address>';
SELECT@x;
TheDROPXMLSCHEMACOLLECTIONstatementinthelistingremovestheXML
schemacollectionfromSQLServer.
DROPXMLSCHEMACOLLECTIONAddressSchemaCollection;
YoucanalsoaddnewXMLschemasandXMLschemacomponentstoXMLschema
collectionswiththeALTERXMLSCHEMACOLLECTIONstatement.
ThexmlDataTypeMethods
Thexmldatatypehasseveralmethodsforqueryingandmodifyingxmldata.Thebuilt-in
xmldatatypemethodsaresummarizedinTable12-4.
Table12-4.xmlDataTypeMethods
Method Result
query(xquery) PerformsanXQueryqueryagainstanxmlinstance.Theresult
returnedisanuntypedxmlinstance.
value(xquery,sql_type) PerformsanXQueryqueryagainstanxmlinstanceandreturnsascalar
valueofthespecifiedSQLServerdatatype.
exist(xquery)
PerformsanXQueryqueryagainstanxmlinstanceandreturnsoneof
thefollowingbitvalues:1ifthexqueryexpressionreturnsa
nonemptyresult,0ifthexqueryexpressionreturnsanemptyresult,
NULLifthexmlinstanceisNULL.
modify(xml_dml) PerformsanXMLDataModificationLanguage(XMLDML)statement
tomodifyanxmlinstance.
nodes(xquery)as
table_name(column_name)
PerformsanXQueryqueryagainstanxmlinstanceandreturns
matchingnodesasanSQLresultset.Thetable_nameand
column_namespecifyaliasesforthevirtualtableandcolumntohold
thenodesreturned.Thesealiasesaremandatoryforthenodes()
method.
Thissectionintroduceseachofthesexmldatatypemethods.
ThequeryMethod
Thexmldatatypequery()methodacceptsanXQueryquerystringasitsonly
parameter.ThismethodreturnsallnodesmatchingtheXQueryasasingleuntypedxml
instance.Convenientlyenough,Microsoftprovidessampletypedxmldatainthe
ResumecolumnoftheHumanResources.JobCandidatetable.Thoughallofits
xmliswellformedwithasinglerootelement,theResumecolumnisfacetedwiththe
defaultofCONTENT.
Listing12-13showshowtousethequery()methodtoretrievenamesfromthe
resumesintheHumanResources.JobCandidatetable.
Listing12-13.UsingtheQueryMethodontheHumanResources.JobCandidateResume
XML
SELECTResume.query(N'declarenamespacens=
"http://schemas.microsoft.com/sqlserver/2004/07/adventure-
works/Resume";
/ns:Resume/ns:Name')AS[NameXML]
FROMHumanResources.JobCandidate;
ThefirstthingtonoticeisthenamespacedeclarationinsidetheXQueryqueryviathe
declarenamespacestatement.ThisisdonebecausetheResumecolumn’sxmldata
declaresanamespace.Infact,thenamespacedeclarationusedintheXQueryisexactlythe
sameasthedeclarationusedinthexmldata.ThedeclarationsectionoftheXQuerylooks
likethis:
declarenamespacens=
"http://schemas.microsoft.com/sqlserver/2004/07/adventure-
works/Resume";
TheactualqueryportionoftheXQueryqueryisasimplepathexpression:
/ns:Resume/ns:Name
AsampleoftheresultsofListing12-13areshowninFigure12-10(reformattedfor
easyreading).
Figure12-10.RetrievingJobCandidateNameswiththeQueryMethod(PartialResults)
TipSQLServer2014implementsasubsetoftheW3CXQueryrecommendation.
Chapter13discussesSQLServer’sXPathandXQueryimplementationsindetail.If
you’rejustgettingstartedwithXQuery,additionalresourcesincludetheW3C
recommendationavailableat
http://www.w3.org/standards/techs/xquery#w3c_all/,andonBOLat
http://msdn.microsoft.com/en-us/library/ms189075.aspx.
ThevalueMethod
Thexmldatatype’svalue()methodperformsanXQueryqueryagainstanxml
instanceandreturnsascalarresult.Thescalarresultofvalue()isautomaticallycastto
theT-SQLdatatypespecifiedinthecalltovalue().ThesamplecodeinListing12-14
usesthevalue()methodtoretrievealllastnamesfromAdventureWorksjobapplicant
resumes.TheresultsareshowninFigure12-11.
Listing12-14.xmlDataTypeValueMethodSample
SELECTResume.value(N'declarenamespacens=
"http://schemas.microsoft.com/sqlserver/2004/07/adventure-
works/Resume";
(/ns:Resume/ns:Name/ns:Name.Last)[1]',
'nvarchar(100)')AS[LastName]
FROMHumanResources.JobCandidate;
Figure12-11.UsingtheValueMethodtoRetrieveJobCandidateLastNames
Likethequery()methoddescribedpreviously,thevalue()methodsample
XQueryquerybeginsbydeclaringanamespace:
declarenamespacens=
"http://schemas.microsoft.com/sqlserver/2004/07/adventure-
works/Resume";
TheactualqueryportionoftheXQueryqueryisasimplepathexpression:
(/ns:Resume/ns:Name/ns:Name.Last)[1]
Becausevalue()returnsascalarvalue,thequeryisenclosedinparentheseswithan
XQuerynumericpredicate[1]followingittoforcethereturnofasingletonatomic
value.Thesecondparameterpassedintovalue()istheT-SQLdatatypethatvalue()
willcasttheresultto,inthiscasenvarchar.Thevalue()methodcannotcastits
resulttoaSQLCLRuser-definedtypeoranxml,image,text,ntext,or
sql_variantdatatype.
TheexistMethod
Thexmldatatypeprovidestheexist()methodfordeterminingifanXMLnodeexists
inanxmlinstance,orifanexistingXMLnodevaluemeetsaspecificsetofcriteria.The
exampleinListing12-15usestheexist()methodinaquerytoreturnall
AdventureWorksjobcandidatesthatreportedabachelor’sdegreelevelofeducation.The
resultsareshowninFigure12-12.
Listing12-15.xmlDataTypeExistMethodExample
SELECTResume.value(N'declarenamespacens=
"http://schemas.microsoft.com/sqlserver/2004/07/adventure-
works/Resume";
(/ns:Resume/ns:Name/ns:Name.Last)[1]',
'nvarchar(100)')AS[BachelorsCandidate]
FROMHumanResources.JobCandidate
WHEREResume.exist(N'declarenamespacens=
"http://schemas.microsoft.com/sqlserver/2004/07/adventure-
works/Resume";
/ns:Resume/ns:Education/ns:Edu.Level[.="Bachelor"]')
=1;
Figure12-12.UsingtheExistMethodtoRetrieveBachelor’sDegreeJobCandidates
Thefirstpartofthequeryborrowsfromthevalue()methodexampleinListing12-
13toretrievematchingjobcandidatenames:
SELECTResume.value(N'declarenamespacens=
"http://schemas.microsoft.com/sqlserver/2004/07/adventure-
works/Resume";
(/ns:Resume/ns:Name/ns:Name.Last)[1]',
'nvarchar(100)')AS[BachelorsCandidate]FROM
HumanResources.JobCandidate
Theexist()methodintheWHEREclausespecifiesthexmlmatchcriteria.Likethe
previoussamplequeries,theexist()methodXQueryquerybeginsbydeclaringa
namespace:
declarenamespacens=
"http://schemas.microsoft.com/sqlserver/2004/07/adventure-
works/Resume";
ThequeryitselfcomparestheEdu.LevelnodetexttothestringBachelor:
/ns:Resume/ns:Education/ns:Edu.Level[.="Bachelor"]
Ifthereisamatch,thequeryreturnsaresultandtheexist()methodreturns1.If
thereisnomatch,therewillbenonodesreturnedbytheXQueryquery,andtheexist()
methodwillreturn0.IfthexmlisNULL,exist()returnsNULL.Thequerylimitsthe
resultstoonlymatchingresumesbyreturningonlythosewhereexist()returns1.
ThenodesMethod
Thenodes()methodofthexmldatatyperetrievesXMLcontentinrelationalformat—
aprocessknownasshredding.Thenodes()methodreturnsarowsetcomposedofthe
xmlnodesthatmatchagivenXQueryexpression.Listing12-16retrievesproductnames
andIDsforthoseproductswiththewordAlloyintheMaterialnodeoftheir
CatalogDescriptioncolumn.Thetablequeriedis
Production.ProductModel.NoticethattheCROSSAPPLYoperatorisusedto
performthenodes()methodonallrowsoftheProduction.ProductModeltable.
Listing12-16.xmlDataTypeNodesExample
SELECT
ProductModelID,
Name,
Specs.query('.')ASResult
FROMProduction.ProductModel
CROSSAPPLYCatalogDescription.nodes('declarenamespacens=
"http://schemas.microsoft.com/sqlserver/2004/07/adventure-
works/ProductModelDescription";
/ns:ProductDescription/ns:Specifications/Material/text()
[contains(.,"Alloy")]')
ASNodeTable(Specs);
ThefirstpartoftheSELECTqueryretrievestheproductmodelID,theproductname,
andtheresultsofthenodes()methodviathequery()method:
SELECT
ProductModelId,
Name,
Specs.query('.')ASResult
FROMProduction.ProductModel
Onerestrictionofthenodes()methodisthattherelationalresultsgeneratedcannot
beretrieveddirectly.Theycanonlybeaccessedviatheexist(),nodes(),query(),
andvalue()methodsofthexmldatatype,orcheckedwiththeISNULLandIS
NOTNULLoperators.
TheCROSSAPPLYoperatorisusedwiththenodes()methodtogeneratethefinal
resultset.TheXQueryqueryusedinthenodes()methodbeginsbydeclaringa
namespace:
CROSSAPPLYCatalogDescription.nodes('declarenamespacens
=“http://schemas.microsoft.com/sqlserver/2004/07/adventure-
works/ProductModelDescription”;
ThequeryportionisapathexpressionthatretrievesXMLnodesinwhicha
Materialnode’stextcontainsthewordAlloy:
/ns:ProductDescription/ns:Specifications/Material/text()[
contains(.,“Alloy”)]')
Noticethatthenodes()methodrequiresyoutoprovidealiasesforboththevirtual
tablereturnedandthecolumnthatwillcontaintheresultrows.Inthisinstance,wechose
toaliasthevirtualtablewiththenameNodeTableandthecolumnwiththename
Specs.
ASNodeTable(Specs);
ThemodifyMethod
Thexmldatatypemodify()methodcanbeusedtomodifythecontentofanxml
variableorcolumn.Themodify()methodallowsyoutoinsert,delete,orupdatexml
content.Themainrestrictionsonthemodify()methodisthatitmustbeusedina
variableSETstatementorintheSETclauseofanUPDATEstatement.Theexamplein
Listing12-17demonstratesthemodify()methodonanuntypedxmlvariable.The
resultsareshowninFigure12-13.
Listing12-17.xmlDataTypeModifyMethodExample
DECLARE@xxml=N'<?xmlversion="1.0"?>
<Address>
<Street>lMICROSOFTWAY</Street>
<City>REDMOND</City>
<State>WA</State>
<Zip>98052</Zip>
<Country>US</Country>
<Website>http://www.microsoft.com</Website>
</Address>';
SELECT@x;
SET@x.modify('insert
(
<CompanyName>MicrosoftCorporation</CompanyName>,
<Url>http://msdn.microsoft.com</Url>,
<UrlDescription>MicrosoftDeveloper
Network</UrlDescription>
)
into(/Address)[1]');
SET@x.modify('replacevalueof
(/Address/Street/text())[1]
with"ONEMICROSOFTWAY"
');
SET@x.modify('
delete/Address/Website
');
SELECT@x;
Figure12-13.Before-and-afterResultsoftheModifyMethod
TipAlthoughtheSELECTandSETstatementsaresimilarintheirfunctionalitywhen
appliedtovariables,themodify()methodofthexmldatatypewillnotworkin
SELECTstatements—evenSELECTstatementsthatassignvaluestovariables.Usethe
SETstatementasdemonstratedinListing12-17tousethemodify()methodonanxml
variable.
ThesamplebeginsbycreatinganxmlvariableandassigningXMLcontenttoit:
DECLARE@xxml=N'<?xmlversion="1.0"?><Address>
<Street>lMICROSOFTWAY</Street>
<City>REDMOND</City>
<State>WA</State>
<Zip>98052</Zip>
<Country>US</Country>
<Website>http://www.microsoft.com</Website></Address>';
SELECT@x;
TheXMLDMLinsertstatementinsertsthreenewnodesintothexmlvariable,
rightbelowthetop-levelAddressnode:
SET@x.modify('insert
(
<CompanyName>MicrosoftCorporation</CompanyName>J
<Url>http://msdn.microsoft.com</Url>,
<UrlDescription>MicrosoftDeveloper's
Network</UrlDescription>
)
into(/Address)[1]');
Thereplacevalueofstatementspecifiedinthenextmodify()method
updatesthecontentoftheStreetnodewiththestreetaddressourgoodfriendsat
Microsoftprefer:ONEMICROSOFTWAY,insteadof1MICROSOFTWAY.
SET@x.modify('replacevalueof(/Address/Street/text())[l]
with"ONEMICROSOFTWAY"
');
Finally,theXMLDMLmethoddeletestatementisusedtoremovetheold
<Website>tagfromthexmlvariable’scontent:
SET@x.modifyC
delete/Address/Website
');
SELECT@x;
XMLIndexes
SQLServerprovidesXMLindexestoincreasetheefficiencyofqueryingxmldatatype
columns.XMLindexescomeintwoflavors:
PrimaryXMLindex:AnXMLcolumncanhaveasingleprimary
XMLindexdeclaredonit.TheprimaryXMLindexisdifferentfrom
thestandardrelationalindexesmostofusareusedto.Rather,itisa
persistedpreshreddedrepresentationofyourXMLdata.Basically,the
XMLdatastoredinacolumnwithaprimaryXMLindexisconverted
torelationalformandstoredinthedatabase.Bypersistinganxml
datatypecolumninrelationalform,youeliminatetheimplicit
shreddingthatoccurswitheveryqueryormanipulationofyourXML
data.InordertocreateaprimaryXMLindexonatable’sxml
column,aclusteredindexmustbeinplaceontheprimarykey
columnsforthetable.
SecondaryXMLindex:SecondaryXMLindexescanalsobecreated
onatable’sxmlcolumn.SecondaryXMLindexesarenonclustered
relationalindexescreatedonprimaryXMLindexes.Inordertocreate
secondaryXMLindexesonanxmlcolumn,aprimaryXMLindex
mustalreadyexistonthatcolumn.Youcandeclareanyofthree
differenttypesofsecondaryXMLindexonyourprimaryXML
indexes:
ThePATHindexisasecondaryXMLindexoptimizedfor
XPathandXQuerypathexpressionsthatrelyheavilyonpath
andnodevalues.ThePATHindexcreatesanindexonpathand
nodevaluesonthecolumnsoftheprimaryXMLindex.The
pathandnodevaluesareusedaskeycolumnsforefficientpath
seekoperations.
TheVALUEindexisoptimizedforqueriesbyvaluewherethe
pathisnotnecessarilyknown.Thistypeofindexistheinverse
ofthePATHindex,withtheprimaryXMLindexnodevalues
indexedbeforethenodepaths.
ThePROPERTYindexisoptimizedforqueriesthatretrieve
datafromothercolumnsofatablebasedonthevalueofnodes
orpathsinthexmldatatypecolumn.Thistypeofsecondary
indexiscreatedontheprimarykeyofthebasetable,node
paths,andnodevaluesoftheprimaryXMLindex.
ConsidertheexampleXQueryFLWOR(for,let,where,orderby,return)
expressioninListing12-18thatretrievesthelast,first,andmiddlenamesofalljob
applicantsintheHumanResources.JobCandidatetablewithaneducationlevelof
Bachelor.TheresultsofthisqueryareshowninFigure12-14.
Listing12-18.RetrievingJobCandidateswithBachelor’sDegrees
SELECTResume.query('declarenamespacens=
"http://schemas.microsoft.com/sqlserver/2004/07/adventure-
works/Resume";
for$min/ns:Resume
where$m/ns:Education/ns:Edu.Level[.="Bachelor"]
return<Name>
{
data(($m/ns:Name/ns:Name.Last)[1]),
data(($m/ns:Name/ns:Name.First)[1]),
data(($m/ns:Name/ns:Name.Middle)[1])
}</Name>')
FROMHumanResources.JobCandidate;
GO
Figure12-14.RetrievingCandidateNameswithaFLWORExpression
We’lldescribeFLWORexpressionsingreaterdetail,withexamples,inChapter13.
Forthepurposesofthisdiscussion,however,theresultsarenotasimportantaswhat’s
goingonunderthehood.ThisFLWORexpressionisreturningthelast,first,andmiddle
namesofallcandidatesforwhichtheEdu.LevelnodecontainsthevalueBachelor.As
showninFigure12-15,theexecutioncostofthisqueryis41.2849.Althoughthesubtree
costisanarbitrarynumber,itrepresentsthetotalcostinrelationshiptothebatch.Inthis
casethenumberislargeenoughinrelationshiptothebatchtowarrantinvestigation.
Figure12-15.TheExecutionCostoftheQuery
ByfarthemostexpensivepartofthisqueryiscontainedinastepcalledTableValued
Function[XMLReaderwithXPathFilter].ThisisthemainoperatorSQLServerusesto
shredXMLdataontheflywheneveryouqueryXMLdata.Inthisqueryplan,itisinvoked
twotimesatacostof13.052each,andthreemoretimesatacostof4.89054each,
accountingforover98percentofthequeryplancost(seeFigure12-16).
Figure12-16.TableValuedFunction[XMLReaderwithXPathFilter]Cost
AddingXMLindexestothiscolumnoftheHumanResources.JobCandidate
tablesignificantlyimprovesXQueryqueryperformancebyeliminatingon-the-flyXML
shredding.Listing12-19addsaprimaryandsecondaryXMLindextotheResume
column.
Listing12-19.AddingXMLIndexestotheResumeColumn
CREATEPRIMARYXMLINDEXPXML_JobCandidate
ONHumanResources.JobCandidate(Resume);
GO
CREATEXMLINDEXIXML_Education
ONHumanResources.JobCandidate(Resume)
USINGXMLINDEXPXML_JobCandidate
FORPATH;
GO
WiththeprimaryandsecondaryXMLindexesinplace,thequeryexecutioncostdrops
significantlyfrom41.2849to0.278555,asshowninFigure12-17.
Figure12-17.TheQueryExecutionCostwithXMLIndexes
ThegreaterefficiencyisbroughtaboutbytheXMLReaderwithXPathFilterstep
beingreplacedwithefficientindexseekoperatorsonbothclusteredandnonclustered
indexes.TheprimaryXMLindexeliminatestheneedtoshredXMLdataatquerytime
andthesecondaryXMLindexprovidesadditionalperformanceenhancementbyproviding
anonclusteredindexthatcanbeusedtoefficientlyfulfilltheFLWORexpressionwhere
clause.
TheCREATEPRIMARYXMLINDEXstatementintheexamplecreatesaprimary
XMLindexontheResumecolumnoftheHumanResources.JobCandidatetable.
TheprimaryXMLindexprovidesasignificantperformanceincreasebyitself,sinceit
eliminateson-the-flyXMLshreddingatquerytime.
CREATEPRIMARYXMLINDEXPXML_JobCandidateON
HumanResources.JobCandidate(Resume);
TheprimaryXMLindexisaprerequisiteforcreatingthesecondaryXMLindexthat
willprovideadditionalperformanceenhancementforXQueryqueriesthatspecifybotha
pathandapredicatebasedonnodecontent.TheCREATEXMLINDEXstatementinthe
examplecreatesthesecondaryXMLPATHindex.
CREATEXMLINDEXIXML_EducationONHumanResources.JobCandidate
(Resume)USINGXMLINDEXPXML_JobCandidateFORPATH;
TheUSINGXMLINDEXclauseoftheCREATEXMLINDEXstatementspecifies
thenameoftheprimaryXMLindexonwhichtobuildthesecondaryXMLindex.The
FORclausedeterminesthetypeofsecondaryXMLindexthatwillbecreated.Youcan
specifyaVALUE,PATH,orPROPERTYtypeasdescribedpreviously.
TheoptionalWITHclauseofbothoftheXMLindexcreationstatementsallowsyouto
specifyavarietyofXMLindexcreationoptions,asshowninTable12-5.
Table12-5.XMLIndexCreationOptions
Option Description
PAD_INDEX Thisoptionspecifieswhetherindexpaddingisonoroff.Thedefaultis
OFF.
FILLFACTOR
Thisoptionindicateshowfulltheleaflevelindexpagesshouldbemade
duringXMLindexcreationorrebuild.Valuesof0and100areequivalent.
TheFILLFACTORoptionisusedinconjunctionwiththePAD_INDEX
option.
SORT_IN_TEMPDB
Thisoptionspecifiesthatintermediatesortresultsshouldbestoredin
tempdb.Bydefault,SORT_IN_TEMPDBissettoOFFandintermediate
sortresultsarestoredinthelocaldatabase.
STATISTICS_NORECOMPUTE Thisoptionindicateswhetherdistributionstatisticsareautomatically
recomputed.ThedefaultisOFF.
DROP_EXISTING ThisoptionspecifiesthatthepreexistingXMLindexofthesamename
shouldbedroppedbeforecreatingtheindex.ThedefaultisOFF.
ALLOW_ROW_LOCKS ThisoptionallowsSQLServertouserowlockswhenaccessingtheXML
index.ThedefaultisON.
ALLOW_PAGE_LOCKS ThisoptionallowsSQLServertousepagelockswhenaccessingthe
XMLindex.ThedefaultisON.
MAXDOP
ThisoptiondeterminesthemaximumdegreeofparallelismSQLServer
canuseduringtheXMLindexcreationoperation.MAXDOPcanbeoneof
thefollowingvalues:0:Usesuptothemaximumnumberofprocessors
available.1:Usesonlyoneprocessor;noparallelprocessing.2through
64:Restrictsthenumberofprocessorsusedforparallelprocessingtothe
numberspecifiedorless.
XSLTransformations
OneofthepowerfulfeaturesavailabletoSQLServer2014isitsabilitytoexecute.NET
Framework-basedcodeviatheSQLCommonLanguageRuntime(SQLCLR).Youcan
usestandard.NETFrameworkclassestoaccessXML-basedfunctionalitythatisnot
supporteddirectlywithinT-SQL.OneusefulfeaturethatcanbeaccessedviaCLR
IntegrationistheW3CExtensibleStylesheetLanguageTransformations(XSLT).As
definedbytheW3C,XSLTisalanguagedesignedforthesolepurposeof“transforming
XMLdocumentsintootherXMLdocuments.”SQLServer2014providesaccesstoXSL
transformationsviaacombinationofthebuilt-inxmldatatypeandthe.NETFramework
XslCompiledTransformclass.
TipTheXSLT1.0standardisavailableatwww.w3.org/TR/xslt.
YoucanaccessXSLTfromSQLServertoperformserver-sidetransformationsofyour
relationaldataintootherXMLformats.I’vechosentouseXHTMLastheoutputformat
forthisexample,althoughsomewouldarguethatgeneratingXHTMLoutputisbestdone
awayfromSQLServer,inthemiddletierorpresentationlayer.Argumentscanalsobe
madeforperformingXSLtransformationsclosetothedata,forefficiencyreasons.I’dlike
toputthoseargumentsasideforthemoment,andfocusonthemainpurposeofthis
example,demonstratingthatadditionalXMLfunctionalityisavailabletoSQLServervia
SQLCLR.Listing12-20demonstratesthefirststepintheprocessofperformingserver-
sideXSLtransformationsusingFORXMLtoconvertrelationaldatatoanxmlvariable.
Listing12-20.UsingFORXMLtoConvertRelationalDatatoPopulateanxmlVariable
DECLARE@xmlxml=
(
SELECT
p.ProductNumberAS"@Id",
p.NameAS"Name",
p.ColorAS"Color",
p.ListPriceAS"ListPrice",
p.SizeUnitMeasureCodeAS"Size/@UOM",
p.SizeAS"Size",
p.WeightUnitMeasureCodeAS"Weight/@UOM",
p.WeightAS"Weight",
(
SELECTCOALESCE(SUM(i.Quantity),0)
FROMProduction.ProductInventoryi
WHEREi.ProductID=p.ProductID
)AS"QuantityOnHand"
FROMProduction.Productp
WHEREp.FinishedGoodsFlag=1
ORDERBYp.Name
FORXMLPATH('Product'),
ROOT('Products')
);
SELECT@xml;
TheresultingxmldocumentlookslikeFigure12-18.
Figure12-18.PartialResultsoftheFORXMLProductQuery
ThenextstepistocreatetheXSLTstylesheettospecifythetransformationandassign
ittoanxmldatatypevariable.Listing12-21demonstratesasimpleXSLTstylesheetto
convertXMLdatatoHTML.
Listing12-21.XSLTStyleSheettoConvertDatatoHTML
DECLARE@xsltxml=N'<?xmlversion="1.0"encoding="utf-16"?
>
<xsl:stylesheetversion="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:templatematch="/Products">
<html>
<head>
<title>AdventureWorksProductListing
Report</title>
<styletype="text/css">
tr.row-heading{
background-color:000099;
color:ffffff;
font-family:tahoma,arial,helvetica,sans-serif;
font-size:12px;
}
tr.row-light{
background-color:ffffff;
font-family:tahoma,arial,helvetica,sans-serif;
font-size:12px;
}
tr.row-dark{
background-color:00ffff;
font-family:tahoma,arial,helvetica,sans-
serif;
font-size:12px;
}
td.col-right{
text-align:right;
}
</style>
</head>
<body>
<table>
<trclass="row-heading">
<th>ID</th>
<th>ProductName</th>
<th>OnHand</th>
<th>ListPrice</th>
<th>Color</th>
<th>Size</th>
<th>Weight</th>
</tr>
<xsl:for-eachselect="Product">
<xsl:elementname="tr">
<xsl:choose>
<xsl:whentest="position()mod2=0">
<xsl:attributename="class">row-
light</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attributename="class">row-
dark</xsl:attribute>
</xsl:otherwise>
</xsl:choose>
<td><xsl:value-ofselect="@Id"/></td>
<td><xsl:value-ofselect="Name"/></td>
<tdclass="col-right">
<xsl:value-ofselect="QuantityOnHand"/>
</td>
<tdclass="col-right"><xsl:value-of
select="ListPrice"/></td>
<td><xsl:value-ofselect="Color"/></td>
<tdclass="col-right"><xsl:value-of
select="Size"/>
<xsl:value-ofselect="Size/@UOM"/>
</td>
<tdclass="col-right">
<xsl:value-ofselect="Weight"/>
<xsl:value-ofselect="Weight/@UOM"/>
</td>
</xsl:element>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>';
TipWewon’tdiveintothedetailsofXSLTstylesheetcreationinthisbook,but
informationcanbefoundattheofficialW3CXSLT1.0standardsite,at
http://www.w3.org/TR/xslt20/.ThebookProSQLServer2008XML(Apress,
2008)alsooffersadetaileddiscussionofXSLTonSQLServer.
ThefinalstepistocreateanSQLCLRSPthatacceptstherawXMLdataandthe
XSLTstylesheet,performstheXSLtransformation,andwritestheresultstoanHTML
file.TheSQLCLRSPcodeisshowninListing12-22.
Listing12-22.SQLCLRSPforXSLTransformations
usingSystem.Data.SqlTypes;
usingSystem.Xml;
usingSystem.Xml.Xsl;
namespaceApress.Samples
{
publicpartialclassXSLT
{
[Microsoft.SqlServer.Server.SqlProcedure]
publicstaticvoidXmlToHtml
(
SqlXmlRawXml,
SqlXmlXslStyleSheet,
SqlStringOutputPage
)
{
//CreateandloadtheXslCompiledTransformobject
XslCompiledTransformxslt=newXslCompiledTransform();
XmlDocumentxmldoc1=newXmlDocument();
xmldocl.LoadXml(XslStyleSheet.Value);
xslt.Load(xmldoc1);
//CreateandloadtheRawXMLdocument
XmlDocumentxml=newXmlDocument();
xml.LoadXml(RawXml.Value);
//CreatetheXmlTextWriterforoutputtoHTMLdocument
XmlTextWriterhtmlout=newXmlTextWriter
(
OutputPage.Value,
System.Text.Encoding.Unicode
);
//Performthetransformation
xslt.Transform
(
xml,
htmlout
);
//ClosetheXmlTextWriter
htmlout.Close();
}
}
};
SQLCLRSecuritySettings
ThereareafewadministrativedetailsyouneedtotakecareofbeforeyoudeploySQL
CLRcodetoSQLServer.Thefirstthingtodoissetthedatabasetotrustworthymodewith
theALTERDATABASEstatement,asshownfollowing:
ALTERDATABASEAdventureWorks2014SETTRUSTWORTHYON;
Abetteralternativetosettingyourdatabasetotrustworthymodeistosignyour
assemblieswithacertificate.WhilesigningSQLCLRassembliesisbeyondthescopeof
thisbook,authorsRobinDewsonandJulianSkinnercoverthistopicintheirbookPro
SQLServer2005Assemblies(Apress,2005).ThebookcoversSQL2005butthetopics
arestillrelevantandapplicabletoSQLServer2014.
FortheexampleinListing12-22,whichaccessesthelocalfilesystem,youalsoneed
tosettheCLRassemblypermissionleveltoExternal.YoucandothisthroughVisual
Studio,asshowninthefollowingillustration,oryoucanuseWITHPERMISSION_SET
clauseoftheCREATEASSEMBLYorALTERASSEMBLYstatementsinT-SQL.
ForSQLCLRcodethatdoesn’trequireaccesstoexternalresourcesorunmanaged
code,apermissionlevelofSafeisadequate.ForSQLCLRassembliesthatneedaccessto
externalresourceslikeharddrivesornetworkresources,Externalpermissionsarethe
minimumrequired.Unsafepermissionsarerequiredforassembliesthataccessunsafeor
unmanagedcode.AlwaysassigntheminimumrequiredpermissionswhendeployingSQL
CLRassembliestoSQLServer.
Finally,makesuretheSQLServerserviceaccounthaspermissionstoanyrequired
externalresources.Forthisexample,theserviceaccountneedspermissionstowritetothe
c:\DocumentsandSettings\AllUsers\Documentsdirectory.
AfteryouhavedeployedtheSQLCLRassemblytoSQLServerandsetthe
appropriatepermissions,youcancalltheXmlToHtmlproceduretoperformtheXSL
transformation,asshowninListing12-23.TheresultingHTMLfileisshowninFigure
12-19.
Listing12-23.PerformingaSQLCLRXSLTransformation
EXECUTEXmlToHtml@xml,
gxslt,
'c:\DocumentsandSettingsXAll
Users\Documents\adventureworks-inventory.html';
Figure12-19.ResultsoftheXML-to-HTMLTransformation
Summary
Inthischapter,wediscussedSQLServer2014’sintegratedXMLfunctionality.Webegan
withadiscussionoflegacyXMLfunctionalitycarriedforward,andinsomecases
improvedupon,fromthedaysofSQLServer2005.Thislegacyfunctionalityincludesthe
flexibleFORXMLclauseandtheOPENXMLrowsetprovider.
Wethendiscussedthepowerfulxmldatatypeanditsmanymethods:
Thequery()methodallowsyoutoretrieveXMLnodesusing
XQueryqueries.
Thevalue()methodletsyouretrievesingletonatomicvaluesusing
XQuerypathexpressionstolocatenodes.
Theexist()methoddetermineswhetheraspecificnodeexistsin
yourXMLdata.
Themodify()methodallowsyoutouseXMLDMLtomodifyyour
XMLdatadirectly.
Thenodes()methodmakesshreddingXMLdatasimple.
WealsopresentedSQLServer’sprimaryandsecondaryXMLindexes,whichare
designedtooptimizeXMLqueryperformance.Finally,wetouchedonSQLServer’sSQL
CLRintegrationanddemonstratedhowtouseittoaccess.NETFrameworkXML
functionalitynotdirectlyavailablethroughtheT-SQLlanguage.
Inthenextchapter,wewillcontinuethediscussionofSQLServerXMLby
introducingXPathandXQuerysupport,includingamoredetaileddiscussionofthe
options,functions,operators,andexpressionsavailableforqueryingandmanipulating
XMLonSQLServer.
Exercises
1. [Chooseallthatapply]SQLServer’sFORXMLclausesupports
whichofthefollowingmodes:
a. FORXMLRAW
b. FORXMLPATH
c. FORXMLAUTO
d. FORXMLEXPLICIT
e. FORXMLRECURSIVE
2. [Fillintheblank]Bydefault,theOPENXMLrowsetproviderreturns
datain____________tableformat.
3. [True/False]Thexmldatatypequery()methodreturnsits
resultsasanuntypedxmldatatypeinstance.
4. [Chooseone]ASQLServerprimaryXMLindexperformswhichof
thefollowingfunctions:
f.Itcreatesanonclusteredindexonyourxmldatatypecolumn
orvariable.
g.Itcreatesaclusteredindexonyourxmldatatypecolumnor
variable.
h.Itstoresyourxmldatatypecolumnsinapreshredded
relationalformat.
i.Itstoresyourxmldatatypecolumnsusinganinverseindex
format.
5. [True/False]WhenyouperformXQueryqueriesagainstanxml
datatypecolumnwithnoprimaryXMLindexdefinedonit,SQL
ServerautomaticallyshredsyourXMLdatatorelationalformat.
6. [True/False]YoucanutilizedefineanXMLdatatypecolumnona
memory-optimizedtableinSQLServer2014.
7. [True/False]YoucanaccessadditionalXMLfunctionalityonSQL
Serverthroughthe.NETFrameworkviaSQLServer’sSQLCLR
integration.
CHAPTER13
XQueryandXPath
AswedescribedinChapter12,SQLServer2014continuesthehighlevelofXML
integrationbeguninSQLServer2005.Aspartofthatintegration,SQLServer’sxmldata
typeprovidesbuilt-infunctionalityforshreddingXMLdataintorelationalformat,
queryingXMLnodesandsingletonatomicvaluesviaXQuery,andmodifyingXMLdata
viaXMLDataModificationLanguage(XMLDML).Thischapterfocusesonhowtoget
themostoutofSQLServer’simplementationofthepowerfulandflexibleXPathand
XQuerystandards.
TheXMLdatamodelrepresentsadeparturefromtherelationalmodelSQLServer
developersknowsowell.XMLisnotareplacementfortherelationalmodel,butitdoes
nicelycomplementrelationaldata.XMLisveryusefulforsharingdatawithawide
varietyofwebservicesandmessagesystemsincludingMSMQanddisparatesystems,and
highlystructuredXMLdatafromremotedatasourcesisoftenshreddedtorelational
formatforeasystorageandquerying.TheSQLServer2014xmldatatypeandXML-
specificqueryandconversiontoolsrepresentamarriageofsomeofthebestfeaturesof
relationaldatabaseandXMLtechnologies.
NoteThischapterisnotmeanttobeacomprehensiveguidetoXPathandXQuery,but
ratheranintroductiontoSQLServer’sXPathandXQueryimplementations,whichare
bothsubsetsoftheW3CXPath2.0andXQuery1.0recommendations.Inadditiontothe
discussioninthischapter,AppendixBprovidesareferencetotheXQueryDataModel
(XDM)typesystemasimplementedbySQLServer.
XPathandFORXMLPATH
TheFORXMLPATHclauseoftheSELECTstatementusesXPath2.0-stylepath
expressionstospecifythestructureoftheXMLresult.Listing13-1demonstratesasimple
FORXMLPATHquerythatreturnsthenamesande-mailaddressesofpeopleinthe
AdventureWorksdatabase.PartialresultsareshowninFigure13-1,whichyoucandisplay
byclickingontheXMLwithinthecolumn.
Listing13-1.RetrievingNamesandE-mailAddresseswithFORXMLPATH
SELECT
p.BusinessEntityIDAS"Person/ID",
p.FirstNameAS"Person/Name/First",
p.MiddleNameAS"Person/Name/Middle",
p.LastNameAS"Person/Name/Last",
e.EmailAddressAS"Person/Email"
FROMPerson.PersonpINNERJOINPerson.EmailAddresse
ONp.BusinessEntityID=e.BusinessEntityID
FORXMLPATH,ROOT('PersonEmailAddress');
Figure13-1.PartialResultsofRetrievingNamesandE-mailAddresseswithFORXMLPATH
BecausetheyareusedspecificallytodefinethestructureofanXMLresult,FORXML
PATHXPathexpressionsaresomewhatlimitedintheirfunctionality.Specifically,you
cannotusefeaturesthatcontaincertainfiltercriteriaoruseabsolutepaths.Briefly,here
aretherestrictions:
AFORXMLPATHXPathexpressionmaynotbeginorendwiththe
/stepoperator,anditmaynotbeginwith,endwith,orcontain//.
FORXMLPATHXPathexpressionscannotspecifyaxisspecifiers
suchaschild::orparent::.
The.(contextnode)and..(contextnodeparent)axisspecifiersare
notallowed.
ThefunctionsdefinedinPart4oftheXPathspecification,Core
FunctionLibrary,arenotallowed.
Predicates,whichareusedtofilterresultsets,arenotallowed.[
position()=4]isanexampleofapredicate.
Basically,theFORXMLPATHXPathsubsetallowsyoutospecifythestructureof
theresultingXMLrelativetotheimplicitrootnode.Thismeansthatadvanced
functionalityofXPathexpressionsaboveandbeyonddefiningasimplerelativepath
expressionisnotallowed.Ingeneral,XPath2.0featuresthatcanbeusedtolocatespecific
nodes,returnsetsofnodes,orfilterresultsetsarenotallowedwithFORXMLPATH.
Bydefault,FORXMLPATHusesthenamerowfortherootnodeofeachrowit
convertstoXMLformat.TheresultsofFORXMLPATHalsodefaulttoanelement-
centricformat,meaningthatresultsaredefinedintermsofelementnodes.
InListing12-1,we’vealiasedthecolumnnamesusingtheXPathexpressionsthat
definethestructureoftheXMLresult.BecausetheXPathexpressionsoftencontain
charactersthatarenotallowedinSQLidentifiers,youwillprobablywanttousequoted
identifiers.
SELECTp.BusinessEntityIDAS“Person/ID”,p.FirstNameAS“Person/Name/First”,
p.MiddleNameAS“Person/Name/Middle”,p.LastNameAS“Person/Name/Last”,
e.EmailAddressAS“Person/Email”
XPathexpressionsaredefinedasapathseparatedbystepoperators.Thestepoperator
(/)indicatesthatanodeisachildoftheprecedingnode.Forinstance,theXPath
expressionPerson/IDintheexampleindicatesthatanodenamedIDwillbecreatedas
achildofthenodenamedPersoninahierarchicalXMLstructure.
XPathAttributes
Alternatively,youcandefinearelationalcolumnasanattributeofanode.Listing13-2
modifiesListing13-1slightlytodemonstratesthis.We’veshownthedifferencesbetween
thetwolistingsinboldprint.PartialresultsareshowninFigure13-2,reformattedslightly
foreasierreading.
Listing13-2.FORXMLPATHCreatingXMLAttributes
SELECTp.BusinessEntityIDAS"Person/@ID",
e.EmailAddressAS"Person/@Email",
p.FirstNameAS"Person/Name/First",
p.MiddleNameAS"Person/Name/Middle",
p.LastNameAS"Person/Name/Last"
FROMPerson.PersonpINNERJOINPerson.EmailAddresse
ONp.BusinessEntityID=e.BusinessEntityIDFORXMLPATH;
Figure13-2.CreatingAttributeswithFORXMLPATH
TheboldportionoftheSELECTstatementinListing13-2generatesXMLattributes
oftheIDandEmailnodesbyprecedingtheirnamesintheXPathexpressionwiththe@
symbol.TheresultisthatIDandEmailbecomeattributesofthePersonelementinthe
result:
p.BusinessEntityIDAS"Person/@ID",e.EmailAddressAS
"Person/@Email",
ColumnswithoutNamesandWildcards
SomeoftheotherXPathexpressionfeaturesyoucanusewithFORXMLPATHinclude
columnswithoutnamesandwildcardexpressions,whichareturnedintoinlinecontent.
ThesampleinListing13-3demonstratesthis.
Listing13-3.UsingColumnswithoutNamesandWildcardswithFORXMLPATH
SELECTp.BusinessEntityIDAS"*",','+e.EmailAddress,
p.FirstNameAS"Person/Name/First",
p.MiddleNameAS"Person/Name/Middle",
p.LastNameAS"Person/Name/Last"FROMPerson.PersonpINNER
JOINPerson.EmailAddresse
ONp.BusinessEntityID=e.BusinessEntityIDFORXMLPATH;
Inthisexample,theXPathexpressionforBusinessEntityIDisthewildcard
character*.Thesecondcolumnisdefinedas','+EmailAddressandthecolumn
isnotgivenaname.Bothofthesecolumnsareturnedintoinlinecontentimmediately
belowtherowelement,asshowninFigure13-3.Thisisparticularlyusefulfunctionality
whencreatinglistswithinyourXMLdata,orwhenyourXMLdataconformstoaschema
thatlooksforcombined,concatenated,orlistdatainXMLtextnodes.
Figure13-3.ColumnswithoutNamesandWildcardExpressionsinFORXMLPATH
ElementGrouping
Asyousawinthepreviousexamples,FORXMLPATHgroupstogethernodesthathave
thesameparentelements.Forinstance,theFirst,Middle,andLastelementsareall
childrenoftheNameelement.Theyaregroupedtogetherinalloftheexamplesbecauseof
this.However,asshowninListing13-4,thisisnotthecasewhentheseelementsare
separatedbyanelementwithadifferentparentelement.
Listing13-4.TwoElementswithaCommonParentElementSeparated
SELECTp.BusinessEntityIDAS"@ID",
e.EmailAddressAS"@EmailAddress",
p.FirstNameAS"Person/Name/First",
pp.PhoneNumberAS"Phone/BusinessPhone",
p.MiddleNameAS"Person/Name/Middle",
p.LastNameAS"Person/Name/Last"
FROMPerson.Personp
INNERJOINPerson.EmailAddresse
ONp.BusinessEntityID=e.BusinessEntityID
INNERJOINPerson.PersonPhonepp
ONp.BusinessEntityID=pp.BusinessEntityID
ANDpp.PhoneNumberTypeID=3FORXMLPATH;
TheresultsofthisqueryincludeanewPhoneelementasadirectchildofthe
Personelement.Becausethisnewelementispositionedbetweenthe
Person/Name/FirstandPerson/Name/Middleelements,FORXMLPATH
createstwoseparatePerson/Nameelements:onetoencapsulatetheFirstelement,
andanothertoencapsulatetheMiddleandLastelements,asshowninFigure13-4.
Figure13-4.BreakingElementGroupingwithFORXMLPATH
ThedataFunction
TheFORXMLPATHXPathexpressionprovidessupportforafunctioncalleddata().If
thecolumnnameisspecifiedasdata(),thevalueistreatedasanatomicvalueinthe
generatedXML.Ifthenextitemgeneratedisalsoanatomicvalue,FORXMLPATH
appendsaspacetotheendofthedatareturned.Thisisusefulforusingsubqueriesto
createlistsofitems,asinListing13-5,whichdemonstratesuseofthedata()function.
Listing13-5.TheFORXMLPATHXPathdataNodeTest
SELECTDISTINCTsoh.SalesPersonIDAS"SalesPerson/@ID",(
SELECTsoh2.SalesOrderIDAS"data()"
FROMSales.SalesOrderHeadersoh2
WHEREsoh2.SalesPersonID=soh.SalesPersonIDFORXML
PATH(''))AS"SalesPerson/@Orders",
p.FirstNameAS"SalesPerson/Name/First",
p.MiddleNameAS"SalesPerson/Name/Middle",
p.LastNameAS"SalesPerson/Name/Last",
e.EmailAddressAS"SalesPerson/Email"
FROMSales.SalesOrderHeadersoh
INNERJOINPerson.Personp
ONp.BusinessEntityID=soh.SalesPersonID
INNERJOINPerson.EmailAddresse
ONp.BusinessEntityID=e.BusinessEntityID
WHEREsoh.SalesPersonIDISNOTNULLFORXMLPATH;
ThissampleretrievesallSalesPersonIDnumbersfromthe
Sales.SalesOrderHeadertable(eliminatingNULLsforsimplicity)andretrieves
theirnamesinthemainquery.Thesubqueryusesthedata()functiontoretrievealistof
eachsalesperson’ssalesordernumbersandplacestheminaspace-separatedlistinthe
OrdersattributeoftheSalesPersonelement.Asampleoftheresultsisshownin
Figure13-5.
Figure13-5.CreatingListswiththedataNodeTest
NodeTestsandFunctions
TheSQLServer2014FORXMLPATHexpressionprovidesaccesstoboththetext()
functionandthedata()nodetest.IntermsofFORXMLPATH,thetext()function
returnsthedatainthetextnodeasinlinetextwithnoseparator.Thedata()nodetest
returnsthedataintheXMLtextnodeasaspace-separatedconcatenatedlist.
InXQueryexpressions,thedata()nodetest,thetext()function,andtherelated
string()functionallreturnslightlydifferentresults.Thefollowingcodesnippet
demonstratestheirdifferences:
DECLARE@xxml;
SET@x=N'<a>123<b>456</b><c>789</c></a><a>987<b>654</b>
<c>321</c></a>';
SELECT@x.query('/a/text()');
SELECT@x.query('data(/a)');
SELECT@x.query('string(/a[1])');
Thetext()functioninthisexamplereturnstheconcatenatedtextnodesofthe<a>
elements;inthisexample,itreturns123987.
Thedata()nodetestreturnstheconcatenatedXMLtextnodesofthe<a>elements
andalltheirchildelements.Inthisexample,data()returns123456789
987654321,theconcatenationofthe<a>elementsandthe<b>and<c>subelements
theycontain.Thedata()nodetestputsaspaceseparatorbetweenthe<a>elements
duringtheconcatenation.
Thestring()functionissimilartothedata()nodetestinthatitconcatenatesthe
datacontainedinthespecifiedelementandallchildelements.Thestring()function
requiresasingletonnodeinstance,whichiswhywespecifiedstring(/a[i])inthe
example.Theresultofthestring()functionusedintheexampleis123456789.
We’lldiscussthetext()andstring()functionsingreaterdetaillaterinthischapter.
XPathandNULL
Inallofthepreviousexamples,FORXMLPATHmapsSQLNULLtoamissingelement
orattribute.ConsidertheresultsofListing13-1forKimAbercrombie,showninFigure
13-6.BecauseherMiddleNameinthetableisNULL,theName/Middleelementis
missingfromtheresults.
Figure13-6.NULLMiddleNameEliminatedfromtheFORXMLPATHResults
IfyouwantSQLNULL-valuedelementsandattributestoappearinthefinalresults,
usetheELEMENTSXSINILoptionoftheFORXMLclause,asshowninListing13-6.
Listing13-6.FORXMLwiththeELEMENTSXSINILOption
SELECT
p.BusinessEntityIDAS"Person/ID",
p.FirstNameAS"Person/Name/First",
p.MiddleNameAS"Person/Name/Middle",
p.LastNameAS"Person/Name/Last",
e.EmailAddressAS"Person/Email"FROMPerson.PersonpINNER
JOINPerson.EmailAddresse
ONp.BusinessEntityID=e.BusinessEntityIDFORXMLPATH,
ELEMENTSXSINIL;
WiththeELEMENTSXSINILoption,Kim’sresultsnowlookliketheresultsshown
inFigure13-7.TheFORXMLPATHclauseaddsareferencetothexsinamespace,and
elementscontainingSQLNULLareincludedbutmarkedwiththexsi:nil=“true”
attribute.
Figure13-7.NULLMarkedwiththexsi:nilAttribute
TheWITHXMLNAMESPACESClause
NamespacesupportisprovidedforFORXMLclausesandotherXMLfunctionsbythe
WITHXMLNAMESPACESclause.TheWITHXMLNAMESPACESclauseisaddedtothe
frontofyourSELECTqueriestospecifyXMLnamespacestobeusedbyFORXML
clausesorxmldatatypemethods.Listing13-7demonstratestheuseoftheWITH
XMLNAMESPACESclausewithFORXMLPATH.
Listing13-7.UsingWITHXMLNAMESPACEStoSpecifyNamespaces
WITH
XMLNAMESPACES('http://www.apress.com/xml/sampleSqlXmlNameSpace
asns)
SELECT
p.BusinessEntityIDAS"ns:Person/ID",
p.FirstNameAS"ns:Person/Name/First",
p.MiddleNameAS"ns:Person/Name/Middle",
p.LastNameAS"ns:Person/Name/Last",
e.EmailAddressAS"ns:Person/Email"
FROMPerson.Personp
INNERJOINPerson.EmailAddresse
ONp.BusinessEntityID=e.BusinessEntityID
FORXMLPATH;
TheWITHXMLNAMESPACESclauseinthisexampledeclaresanamespacecalledns
withtheURIhttp://www.apress.com/xml/sampleSqlXmlNameSpace.The
FORXMLPATHclauseaddsthisnamespaceprefixtothePersonelement,asindicatedin
theXPathexpressionsusedtodefinethestructureoftheresult.Asampleoftheresultsis
showninFigure13-8.
Figure13-8.AddinganXMLNamespacetotheFORXMLPATHResults
NodeTests
Inadditiontothepreviousoptions,theFORXMLPATHXPathimplementationsupports
fournodetests,includingthefollowing:
Thetext()nodetestturnsthestringvalueofacolumnintoatext
node.
Thecomment()nodetestturnsthestringvalueofacolumnintoan
XMLcomment.
Thenode()nodetestturnsthestringvalueofacolumnintoinline
XMLcontent;itisthesameasusingthewildcard*asthename.
Theprocessing-instruction(name)nodetestturnsthe
stringvalueofacolumnintoanXML-processinginstructionwiththe
specifiedname.
Listing13-8demonstratesuseofXPathnodetestsascolumnnamesinaFORXML
PATHquery.TheresultsareshowninFigure13-9.
Listing13-8.FORXMLPATHUsingXPathNodeTests
SELECT
p.NameStyleAS"processing-instruction(nameStyle)",
p.BusinessEntityIDAS"Person/@ID",
p.ModifiedDateAS"comment()",
pp.PhoneNumberAS"text()",
FirstNameAS"Person/Name/First",
MiddleNameAS"Person/Name/Middle",
LastNameAS"Person/Name/Last",
EmailAddressAS"Person/Email"
FROMPerson.Personp
INNERJOINPerson.EmailAddresse
ONp.BusinessEntityID=e.BusinessEntityID
INNERJOINPerson.PersonPhonepp
ONp.BusinessEntityID=pp.BusinessEntityID
FORXMLPATH;
Figure13-9.UsingNodeTestswithFORXMLPATH
Inthisexample,theNameStylecolumnvalueisturnedintoanXML-processing
instructioncallednameStyle,theModifiedDatecolumnisturnedintoanXML
comment,andthecontactPhoneNumberisturnedintoatextnodeforeachpersoninthe
AdventureWorksdatabase.
XQueryandthexmlDataType
XQueryrepresentsthemostadvancedstandardizedXMLqueryinglanguagetodate.
DesignedasanextensiontotheW3CXPath2.0standard,XQueryisacase-sensitive,
declarative,functionallanguagewitharichtypesystembasedontheXDM.TheSQL
Server2014xmldatatypesupportsqueryingofXMLdatausingasubsetofXQueryvia
thequery()method.BeforedivingintothedetailsoftheSQLServerimplementation,
wearegoingtostartthissectionwithadiscussionofXQuerybasics.
ExpressionsandSequences
XQueryintroducesseveraladvancesontheconceptsintroducedbyXPathandother
previousXMLquerytoolsandlanguages.TwoofthemostimportantconceptsinXQuery
areexpressionsandsequences.Asequenceisanorderedcollectionofitems—eithernodes
oratomicvalues.Thewordordered,asitappliestosequences,doesnotnecessarilymean
numericoralphabeticorder.Sequencesaregenerallyindocumentorder(theorderin
whichtheircontentsappearintherawXMLdocumentordata)bydefault,unlessyou
specifyadifferentordering.TheroughlyanalogousXPath1.0structurewasknownasa
nodeset,anamethatimpliesorderingwasunimportant.Unliketherelationalmodel,
however,theorderofnodesisextremelyimportanttoXML.InXML,theorderingof
nodesandcontentprovidesadditionalcontextandcanbejustasimportantasthedata
itself.TheXQuerysequencewasdefinedtoensurethattheimportanceofproperordering
isrecognized.Therearealsosomeotherdifferencesthatwewillcoverlaterinthissection.
SequencescanbereturnedbyXQueryexpressionsorcreatedbyenclosingoneofthe
followinginparentheses:
Listsofitemsseparatedbythecommaoperator(,)
Rangeexpressions
Filterexpressions
TipRangeexpressionsandtherangeexpressionkeywordtoarenotsupportedinSQL
Server2014XQuery.IfyouareconvertinganXQuerywithrangeexpressionslike(1to
10),youwillhavetomodifyittorunonSQLServer2014.
Asequencecreatedasalistofitemsseparatedbythecommaoperatormightlooklike
thefollowing:
(1,2,3,4,(5,6),7,8,(),9,10)
Thecommaoperatorevaluateseachoftheitemsinthesequenceandconcatenatesthe
result.Sequencescannotbenested,soanysequenceswithinsequencesare“flattenedout.”
Also,theemptysequence(asequencecontainingnoitems,denotedbyemptyparentheses:
())iseliminated.Evaluationoftheprevioussamplesequenceresultsinthefollowing
sequenceoftenitems:
(1,2,3,4,5,6,7,8,9,10)
Noticethatthenestedsequence(5,6)hasbeenflattenedout,andtheempty
sequence()isremovedduringevaluation.
TipSQLServer2014XQuerydoesnotsupporttheW3C-specifiedsequenceoperators
union,intersect,andexcept.IfyouareportingXQuerycodethatusesthese
operators,itwillhavetobemodifiedtorunonSQLServer2008.
Anothermethodofgeneratingasequenceiswithafilterexpression.Afilter
expressionisaprimaryexpressionfollowedbyzeroormorepredicates.Anexampleofa
filterexpressiontogenerateasequencemightlooklikethefollowing:
(//Coordinates/*/text())
Animportantpropertyofsequencesisthatasequenceofoneitemisindistinguishable
fromasingletonatomicvalue.Sothesequence(1.0)isequivalenttothesingleton
atomicvalue1.0.
Sequencescomeinthreeflavors:emptysequences,homogeneoussequences,and
heterogeneoussequences.Emptysequencesaresequencesthatcontainnoitems.As
mentionedbefore,theemptysequenceisannotatedwithasetofemptyparentheses:().
Homogeneoussequencesaresequencesofoneormoreitemsofthesameor
compatibletypes.Theexamplesalreadygivenareallexamplesofhomogenoussequences.
Heterogeneoussequencesaresequencesoftwoormoreitemsofincompatibletypes,
orsingletonatomictypesandnodes.Thefollowingisanexampleofaheterogeneous
sequence:
(“Harry”,299792458,xs:date(“2006-12-29Z”))
SQLServerdoesnotallowheterogeneoussequencesthatmixnodeswithsingleton
atomicvalues.Tryingtodeclarethefollowingsequenceresultsinanerror:
(<tag/>,“youareit!”)
NoteSingletonatomicvaluesaredefinedasvaluesthatareinthevaluespaceofthe
atomictypes.Thevaluespaceisthecompletesetofvaluesthatcanbeexpressedwitha
giventype.Forinstance,thecompletevaluespaceforthexs:booleantypeistrue
andfalse.SingletonatomicvaluesareindivisibleforpurposesoftheXDMstandard
(althoughyoucanextractportionsoftheircontentinsomesituations).Valuesthatfallinto
thisspacearedecimals,integers,dates,strings,andotherprimitivedatatypes.
PrimaryexpressionsarethebuildingblocksofXQuery.AnexpressioninXQuery
evaluatestoasingletonatomicvalueorasequence.Primaryexpressionscanbeanyof
severaldifferentitems,includingthefollowing:
Literals:Theseincludestringandnumericdatatypeliterals.String
literalscanbeenclosedineithersingleordoublequotesandmay
containtheXML-definedentityreferences>,<,&,
",and',orUnicodecharacterreferencessuchas€,
whichrepresentstheeurosymbol(€).
Variablereferences:TheseareXML-qualifiednames(QNames)
precededbya$sign.Avariablereferenceisdefinedbyitslocal
name.NotethatSQLServer2012doesnotsupportvariablereferences
withnamespaceURIprefixes,whichareallowedundertheW3C
recommendation.Anexampleofavariablereferenceis$count.
Parenthesizedexpressions:Theseareexpressionsenclosedin
parentheses.Parenthesizedexpressionsareoftenusedtoforcea
specificorderofoperatorevaluation.Forinstance,intheexpression
(3+4)*2,theparenthesesforcetheadditiontobeperformed
beforethemultiplication.
Contextitemexpressions:Theseareexpressionsthatevaluatetothe
contextitem.Thecontextitemisthenodeoratomicvaluecurrently
beingreferencedbytheXQueryqueryengine.
Functioncalls:ThesearecomposedofaQNamefollowedbyalistof
argumentsinparentheses.Functioncallscanreferencebuilt-in
functions.SQLServer2014doesnotsupportXQueryuser-defined
functions.
ThequeryMethod
Thequery()methodcanbeusedtoqueryandretrieveXMLnodesfromxmlvariables
orxml-typedcolumnsintables,asdemonstratedinListing13-9,withpartialresults
showninFigure13-10.
Listing13-9.RetrievingJobCandidateswiththequeryMethod
SELECTResume.query
(
N'//*:Name.First,
//*:Name.Middle,
//*:Name.Last,
//*:Edu.Level'
)
FROMHumanResources.JobCandidate;
Figure13-10.SampleJobCandidateReturnedbythequeryMethod
ThesimpleXQueryqueryretrievesallfirstnames,middlenames,lastnames,and
educationlevelsforallAdventureWorksjobcandidates.TheXQuerypathexpressionsin
theexampledemonstratesomekeyXQueryconcepts,includingthefollowing:
Thefirstitemofnoteisthe//axisatthebeginningofeachpath
expression.Thisaxisnotationisdefinedasshorthandforthe
descendant-or-self::node(),whichwe’lldescribeinmore
detailinthenextsection.Thisparticularaxisretrievesallnodeswitha
namematchingthelocationstep,regardlessofwhereitoccursinthe
XMLbeingqueried.
Intheexample,thefournodetestsspecifiedareName.First,
Name.Middle,Name.Last,andEdu.Level.Allnodeswiththe
namesthatmatchthenodetestsarereturnednomatterwherethey
occurintheXML.
The*namespacequalifierisawildcardthatmatchesanynamespace
occurringintheXML.Eachnodeintheresultnodesequenceincludes
anxmlnsnamespacedeclaration.
ThisXQueryqueryiscomposedoffourdifferentpathsdenotingthe
fourdifferentnodesequencestobereturned.Theyareseparatedfrom
oneanotherbycommas.
LocationPaths
ThelocationpathdetermineswhichnodesshouldbeaccessedbyXQuery.Followinga
locationpathfromlefttorightisgenerallyanalogoustomovingdownandtotherightin
yourXMLnodetree(thereareexceptions,ofcourse,whichwediscussinthesectionon
axisspecifiers).Ifthefirstcharacterofthepathexpressionisasingleforwardslash(/),
thenthepathexpressionisanabsolutelocationpath,meaningthatitstartsattherootof
theXML.Listing13-10demonstratestheuseofanXQueryabsolutelocationpath.The
resultsareshowninFigure13-11.
Listing13-10.QueryingwithanAbsoluteLocationPath
DECLARE@xxml=N'<?xmlversion="1.0"?>
<Geocode>
<InfoID="1">
<CoordinatesResolution="High">
<Latitude>37.859609</Latitude>
<Longitude>-122.291673</Longitude>
</Coordinates>
<LocationType="Business">
<Name>APress,Inc.</Name>
</Location>
</Info>
<InfoID="2">
<CoordinatesResolution="High">
<Latitude>37.423268</Latitude>
<Longitude>-122.086345</Longitude>
</Coordinates>
<LocationType="Business">
<Name>Google,Inc.</Name>
</Location>
</Info>
</Geocode>';
SELECT@x.query(N'/Geocode/Info/Coordinates');
Figure13-11.AbsoluteLocationPathQueryResult
TipTheleft-handforwardslashactuallystandsforaconceptualrootnodethat
encompassesyourXMLinput.Theconceptualrootnodedoesn’tactuallyexist,andcan
neitherbeviewedinyourXMLinputnoraccessedormanipulateddirectly.It’sthis
conceptualrootnodethatallowsXQuerytoproperlyprocessXMLfragmentsthatarenot
wellformed(i.e.,XMLwithmultiplerootnodes)asinput.Usingapathexpressionthat
consistsofonlyasingleforwardslashreturnseverynodebelowtheconceptualrootnode
inyourXMLdocumentorfragment.
Listing13-10definesanxmlvariableandpopulatesitwithanXMLdocument
containinggeocodingdataforacoupleofbusinesses.We’veusedanabsolutelocation
pathinthequerytoretrieveanodesequenceofthelatitudeandlongitudecoordinatesfor
theentireXMLdocument.
Arelativelocationpathindicatesapathrelativetothecurrentcontextnode.The
contextnodeisthecurrentnodebeingaccessedbytheXQueryengineatagivenpoint
whenthequeryisexecuted.Thecontextnodechangesduringexecutionofthequery.
Relativelocationpathsarespecifiedbyexcludingtheleadingforwardslash,asinthe
followingmodificationtoListing13-10:
SELECT@x.query(N'Geocode/Info/Coordinates');
And,aspreviouslymentioned,usingadoubleforwardslash(//)intheleadposition
returnsnodesthatmatchthenodetestanywheretheyoccurinthedocument.The
followingmodificationtoListing13-10demonstratesthis:
SELECT@x.query(N'//Coordinates');
Inaddition,thewildcardcharacter(*)canbeusedtomatchanynodebyname.The
followingexampleretrievestherootnode,allofthenodesonthenextlevel,andall
Coordinatesnodesbelowthat:
SELECT@x.query(N'//*/*/Coordinates');
BecausetheXMLdocumentintheexampleisasimpleone,allthevariationsof
Listing13-10returnthesameresult.FormorecomplexXMLdocumentsorfragments,the
resultsofdifferentrelativelocationpathscouldreturncompletelydifferentresults.
NodeTests
Thenodetestsinthepreviousexamplearesimplenamenodetests.Foranamenodetest
toreturnamatch,thenodesmusthavethesamenamesasthosespecifiedinthenodetests.
Inadditiontonamenodetests,SQLServer2014XQuerysupportsfournodekindtests,as
listedinTable13-1.
Table13-1.SupportedNodeTests
NodeKindTest Description
comment() Returnstrueforacommentnodeonly.
node() Returnstrueforanykindofnode.
processing-
instruction(“name”)
Returnstrueforaprocessinginstructionnode.Thenameparameterisan
optionalstringliteral.Ifitisincluded,onlyprocessinginstructionnodeswith
thatnamearereturned;ifnotincluded,allprocessinginstructionsare
returned.
text() Returnstrueforatextnodeonly.
TipKeepinmindthatXQuery,likeXML,iscasesensitive.Thismeansyournodetests
andotheridentifiersmustallbeofthepropercase.TheidentifierPersonalID,for
instance,doesnotmatchpersonalidinXMLorXQuery.Alsonotethatyourdatabase
collationcasesensitivitysettingsdonotaffectXQueryqueries.
Listing13-11demonstratesuseoftheprocessing-instruction()nodetestto
retrievetheprocessinginstructionfromtherootlevelofadocumentforoneproduct
model.TheresultsareshowninFigure13-12.
Listing13-11.SampleProcessing-instructionNodeTest
SELECTCatalogDescription.query(N'/processing-
instruction()')ASProcessing_Instr
FROMProduction.ProductModel
WHEREProductModelID=19;
Figure13-12.ResultsoftheProcessing-instructionNodeTestQuery
ThesamplecanbemodifiedtoretrieveallXMLcommentsfromthesourcebyusing
thecomment()nodetest,asinListing13-12.TheresultsareshowninFigure13-13.
Listing13-12.SamplecommentNodeTest
SELECTCatalogDescription.query(N'//comment()')ASComments
FROMProduction.ProductModel
WHEREProductModelID=19;
Figure13-13.ResultsofthecommentNodeTestQuery
Listing13-13demonstratesuseofanothernodetest,node(),toretrievethe
specificationsforproductmodel19.ResultsareshowninFigure13-14.
Listing13-13.SamplenodeNodeTest
SELECT
CatalogDescription.query(N'//*:Specifications/node()')AS
Specifications
FROMProduction.ProductModel
WHEREProductModelID=19;
Figure13-14.ResultsofthenodeNodeTestQuery
SQLServer2014XQuerydoesnotsupportothernodekindtestsspecifiedinthe
XQueryrecommendation.Specifically,theschema-element(),schema-
attribute(),anddocument-node()kindtestsarenotimplemented.SQLServer
2013alsodoesn’tprovidesupportfortypetests,whicharenodeteststhatletyouquery
nodesbasedontheirassociatedtypeinformation.
Namespaces
YoumightnoticethatthefirstnodeoftheresultshowninFigure13-14isnotenclosedin
XMLtags.ThisnodeisatextnodelocatedintheSpecificationsnodebeing
queried.Youmightalsonoticethatthe*namespacewildcardmentionedpreviouslyis
usedinthisquery.ThisisbecausenamespacesaredeclaredintheXMLofthe
CatalogDescriptioncolumn.Specificallytherootnodedeclarationlookslikethis:
<pl:ProductDescription
xmlns:pl=”http://schemas.microsoft.com/sqlserver/2004
07/adventure-works/ProductModelDescription”
xmlns:wm=”http://schemas.microsoft.com/sqlserver/2004/07/
adventure-works/ProductModelWarrAndMain”
xmlns:wf=”http://www.adventure-
works.com/schemas/OtherFeatures”
xmlns:html=”http://www.w3.org/1999/xhtml”ProductModelID=“l9”
ProductModelName=“Mountain100”>
TheSpecificationsnodeoftheXMLdocumentisdeclaredwiththepi
namespaceinthedocument.Notusinganamespaceinthequeryatall,asshownin
Listing13-14,resultsinanemptysequencebeingreturned(nomatchingnodes).
Listing13-14.QueryingCatalogDescriptionwithNoNamespaces
SELECTCatalogDescription.query(N'//Specifications/node()')
ASSpecifications
FROMProduction.ProductModel
WHEREProductModelID=19;
Inadditiontothewildcardnamespacespecifier,youcanusetheXQueryprologto
definenamespacesforuseinyourquery.Listing13-15showshowthepreviousexample
canbemodifiedtoincludethep1namespacewithanamespacedeclarationintheprolog.
Listing13-15.PrologNamespaceDeclaration
SELECTCatalogDescription.query
(
N'declarenamespace
p1
="http://schemas.microsoft.com/sqlserver/2004/07/adventure-
works/ProductModelDescription";
//p1:Specifications/node()'
)
FROMProduction.ProductModel
WHEREProductModelID=19;
Thekeywordsdeclarenamespaceallowyoutodeclarespecificnamespacesthat
willbeusedinthequery.Youcanalsousethedeclaredefaultelement
namespacekeywordstodeclareadefaultnamespace,asinListing13-16.
Listing13-16.PrologDefaultNamespaceDeclaration
SELECTCatalogDescription.query
(
N'declaredefaultelementnamespace
"http://schemas.microsoft.com/sqlserver/2004/07/adventure-
works/ProductModelDescription";
//Specifications/node()'
)
FROMProduction.ProductModel
WHEREProductModelID=19;
Declaringadefaultnamespacewiththedeclaredefaultelement
namespacekeywordsallowsyoutoeliminatenamespaceprefixesinyourlocationpaths
(forstepsthatfallwithinthescopeofthedefaultnamespace,ofcourse).Listings13-15
and13-16bothgeneratethesameresultasthequeryinListing13-13.
TipYoucanalsousetheT-SQLWITHXMLNAMESPACESclause,described
previouslyinthischapter,todeclarenamespacesforusebyxmldatatypemethods.
SQLServerdefinesanassortmentofpredeclarednamespacesthatcanbeusedinyour
queries.Withtheexceptionofthexmlnamespace,youcanredeclarethesenamespacesin
yourqueriesusingtheURIsofyourchoice.Thepredeclarednamespacesarelistedin
Table13-2.
Table13-2.SQLServerPredeclaredXQueryNamespaces
Namespace URI Description
Fn http://www.w3.org/2005/xpath-functions
XQuery1.0,
XPath2.0,
XSLT2.0
functionsand
operators
namespace.
Sqltypes http://schemas.microsoft.com/sqlserver/2004/sqltypes
This
namespace
providesSQL
Server2005to
basetype
mapping.
Xdt http://www.w3.org/2005/xpath-datatypes/
XQuery
1.0/XPath2.0
datatypes
namespace.
Xml http://www.w3.org/XML/1998/namespace DefaultXML
namespace.
Xs http://www.w3.org/2001/XMLSchema XMLschema
namespace.
Xsi http://www.w3.org/2001/
XMLschema
instance
namespace;
XMLSchema-
instance.
TipTheW3C-specifiedlocalfunctionsnamespace,local
(http://www.w3.org/2005/xquery-local-functions),isnotpredeclared
inSQLServer.SQLServer2014doesnotsupportXQueryuser-definedfunctions.
Anotherusefulnamespaceishttp://www.w3.org/2005/xqt-errors,which
isthenamespaceforXPathandXQueryfunctionandoperatorerrorcodes.IntheXQuery
documentation,thisURIisboundtothenamespaceerr,thoughthisisnotconsidered
normative.
AxisSpecifiers
Axisspecifiersdefinethedirectionofmovementofalocationpathsteprelativetothe
currentcontextnode.TheXQuerystandarddefinesseveralaxisspecifiers,whichcanbe
definedasforwardaxesorreverseaxes.SQLServer2014supportsasubsetoftheseaxis
specifiers,aslistedinTable13-3.
Table13-3.SQL2014SupportedAxisSpecifiers
AxisName Direction Description
child:: Forward Retrievesthechildrenofthecurrentcontextnode.
descendant:: Forward Retrievesalldescendentsofthecurrentcontextnode,recursivestyle.This
includeschildrenofthecurrentnode,childrenofthechildren,andsoon.
self:: Forward Containsjustthecurrentcontextnode.
descendant-
or-self:: Forward Containsthecontextnodeandchildrenofthecurrentcontextnode.
attribute:: Forward Returnsthespecifiedattribute(s)ofthecurrentcontextnode.Thisaxis
specifiermaybeabbreviatedusinganatsign(@).
parent:: Reverse Returnstheparentofthecurrentcontextnode.Thisaxisspecifiermaybe
abbreviatedastwoperiods(..).
Inaddition,thecontext-itemexpression,indicatedbyasingleperiod(.),returnsthe
currentcontextitem(whichcanbeeitheranodeoranatomicvalue).Thecurrentcontext
itemisthecurrentnodeoratomicvaluebeingprocessedbytheXQueryengineatany
givenpointduringqueryexecution.
NoteThefollowingaxes,definedasoptionalaxesbytheXQuery1.0specification,are
notsupportedbySQLServer2014:following-sibling::,following::,
ancestor::,preceding-sibling::,preceding::,ancestor-or-
self::,andthedeprecatednamespace::.IfyouareportingXQueryqueriesfrom
othersources,theymayhavetobemodifiedtoavoidtheseaxisspecifiers.
Inalloftheexamplessofar,theaxishasbeenomitted,andthedefaultaxisof
child::isassumedbyXQueryineachstep.Becausechild::isthedefaultaxis,the
twoqueriesinListing13-17areequivalent.
Listing13-17.QuerywithandWithoutDefaultAxes
SELECT
CatalogDescription.query(N'//*:Specifications/node()')AS
Specifications
FROMProduction.ProductModel
WHEREProductModelID=19;
SELECT
CatalogDescription.query(N'//child::*:Specifications/child::node()')
ASSpecifications
FROMProduction.ProductModel
WHEREProductModelID=19;
Listing13-18demonstratestheuseoftheparent::axistoretrieveCoordinates
nodesfromthesampleXML.
Listing13-18.SampleUsingtheparent::Axis
DECLARE@xxml=N'<?xmlversion="1.0"?>
<Geocode>
<InfoID="1">
<CoordinatesResolution="High">
<Latitude>37.859609</Latitude>
<Longitude>-122.291673</Longitude>
</Coordinates>
<LocationType="Business">
<Name>APress,Inc.</Name>
</Location>
</Info>
<InfoID="2">
<CoordinatesResolution="High">
<Latitude>37.423268</Latitude>
<Longitude>-122.086345</Longitude>
</Coordinates>
<LocationType="Business">
<Name>Google,Inc.</Name>
</Location>
</Info>
</Geocode>';
SELECT@x.query(N'//Location/parent::node()/Coordinates');
ThisparticularquerylocatesallLocationnodes,thenusestheparent::axisto
retrievetheirparentnodes(Infonodes),andfinallyreturnstheCoordinatesnodes,
whicharechildrenoftheInfonodes.TheendresultisshowninFigure13-15.
Figure13-15.RetrievingCoordinatesNodeswiththeparent::Axis
DynamicXMLConstruction
TheXQuery1.0recommendationisbasedonXPath2.0,whichisinturnbasedlargelyon
XPath1.0.TheXPath1.0recommendationwasdesignedtoconsolidatemanyofthebest
featuresofboththeW3CXSLTandXPointerrecommendations.Oneofthebenefitsof
XQuery’slineageisitsabilitytoqueryXMLanddynamicallyconstructwell-formedXML
documentsfromtheresults.ConsidertheexampleinListing13-19,whichusesanXQuery
directconstructortocreateanXMLdocument.Figure13-16showstheresults.
Listing13-19.XQueryDynamicXMLConstruction
DECLARE@xxml=N'<?xmlversion="1.0"?>
<Geocode>
<InfoID="1">
<LocationType="Business">
<Name>APress,Inc.</Name>
</Location>
</Info>
<InfoID="2">
<LocationType="Business">
<Name>Google,Inc.</Name>
</Location>
</Info>
</Geocode>';
SELECT@x.query(N'<Companies>
{
//Info/Location/Name
}
</Companies>');
Figure13-16.DynamicConstructionofXMLwithXQuery
ThedirectconstructorintheXQueryexamplelookslikethis:
<Companies>
{
//Info/Location/Name
}
</Companies>
The<Companies>and</Companies>openingandclosingtagsinthedirect
constructoractastheroottagfortheXMLresult.Theopeningandclosingtagscontain
thecontentexpression,whichconsistsofthelocationpathusedtoretrievethenodes.The
contentexpressioniswrappedincurlybracesbetweenthe<Companies>and
</Companies>tags:
{
//Info/Location/Name
}
TipIfyouneedtooutputcurlybracesinyourconstructedXMLresult,youcanescape
thembydoublingthemupinyourqueryusing{{and}}.
Youcanalsousetheelement,attribute,andtextcomputedconstructorsto
buildyourXMLresult,asdemonstratedinListing13-20,withtheresultshowninFigure
13-17.
Listing13-20.ElementandAttributeDynamicConstructors
SELECTCatalogDescription.query
(
N'declarenamespace
p1
="http://schemas.microsoft.com/sqlserver/2004/07/adventure-
works/ProductModelDescription";
//p1:Specifications/node()'
)
FROMProduction.ProductModel
WHEREProductModelID=19;
DECLARE@xxml=N'<?xmlversion="1.0"?>
<Geocode>
<InfoID="1">
<LocationType="Business">
<Name>APress,Inc.</Name>
<Address>
<Street>2560NinthSt,Ste219</Street>
<City>Berkeley</City>
<State>CA</State>
<Zip>94710-2500</Zip>
<Country>US</Country>
</Address>
</Location>
</Info>
</Geocode>';
SELECT@x.query
(
N'elementCompanies
{
elementFirstCompany
{
attributeCompanyID
{
(//Info/@ID)[1]
},
(//Info/Location/Name)[1]
}
}'
);
Figure13-17.ResultsoftheXQueryComputedElementConstruction
TheelementCompaniescomputedelementconstructorcreatestheroot
Companiesnode.TheFirstCompanynodeisconstructedasachildnodeusing
anotherelementconstructor:
elementCompanies
{
elementFirstCompany
{
...
}
}
ThecontentexpressionsoftheFirstCompanyelementsarewheretherealaction
takesplace:
elementFirstCompany
{
attributeCompanyID
{
(//Info/@ID)[1]
},
(//Info/Location/Name)[1]
}
TheCompanyIDdynamicattributeconstructorretrievestheIDattributefromthe
firstInfonode.Thepredicate[l]inthepathensuresthatonlythefirst//Info/@ID
isreturned.Thispathlocationcouldalsobewrittenlikethis:
//Info[l]/@ID
ThesecondpathlocationretrievesthefirstNamenodeforthefirstLocationnode
ofthefirstInfonode.Again,the[1]predicateensuresthatonlythefirstmatchingnode
isreturned.Thepathisequivalenttothefollowing:
//Info[l]/Location[l]/Name[l]
Toretrievethesecondnode,changethepredicateto[2],andsoon.
TipBydefinition,apredicatethatevaluatestoanumericsingletonvalue(suchasthe
integerconstant1)isreferredtoasanumericpredicate.TheeffectiveBooleanvalueis
trueonlywhenthecontextpositionisequaltothenumericpredicateexpression.When
thenumericpredicateis3,forinstance,thepredicatetruthvalueistrueonlyforthethird
contextposition.ThisisahandywaytolimittheresultsofanXQueryquerytoasingle
specificnode.
XQueryComments
XQuerycomments(nottobeconfusedwithXMLcommentnodes)areusedtodocument
yourqueriesinline.YoucanincludetheminXQueryexpressionsbyenclosingthemwith
the(:and:)symbols(justlikethesmileyfaceemoticon).Commentscanbeusedin
yourXQueryexpressionsanywhereignorablewhitespaceisallowed,andtheycanbe
nested.XQuerycommentshavenoeffectonqueryprocessing.Thefollowingexample
modifiesthequeryinListing13-19toincludeXQuerycomments:
SELECT@x.query(N'<Companies>(:Thisistherootnode:)
{
//Info/Location/Name(:Retrievesallcompanynames(:ALL
ofthem:):)}</Companies>');
YouwillseeXQuerycommentsusedinsomeoftheexampleslaterinthischapter.
DataTypes
XQuerymaintainsthestringvalueandtypedvalueforallnodesinthereferencedXML.
XQuerydefinesthestringvalueofanelementnodeastheconcatenatedstringvaluesof
theelementnodeandallitschildelementnodes.ThetypeofanodeisdefinedintheXML
schemacollectionassociatedwiththexmlvariableorcolumn.Asanexample,thebuilt-in
AdventureWorksProduction.ManuInstructionsSchemaCollectionXML
schemacollectiondefinestheLocationIDattributeoftheLocationelementasan
xsd:integer:
<xsd:attributename="LocationID"type="xsd:integer"
use="required"/>
EveryinstanceofthisattributeintheXMLoftheInstructionscolumnofthe
Production.ProductModeltablemustconformtotherequirementsofthisdata
type.Typeddatacanalsobemanipulatedaccordingtothefunctionsandoperatorsdefined
forthistype.ForuntypedXML,thetypeddataisdefinedasxdt:untypedAtomic.A
listingofXDMdatatypesavailabletoSQLServerviaXQueryisgiveninAppendixB.
Predicates
AnXQuerypredicateisanexpressionthatevaluatestooneofthexs:booleanvalues
trueorfalse.InXQuery,predicatesareusedtofiltertheresultsofanodesequence,
discardingnodesthatdon’tmeetthespecifiedcriteriafromtheresults.Predicateslimitthe
resultsbyconvertingtheresultofthepredicateexpressionintoanxs:booleanvalue,
referredtoasthepredicatetruthvalue.Thepredicatetruthvalueisdeterminedforeach
itemoftheinputsequenceaccordingtothefollowingrules:
1. Ifthetypeoftheexpressionisnumeric,thepredicatetruthvalueis
trueifthevalueofthepredicateexpressionisequaltothecontext
position;otherwiseforanumericpredicate,thepredicatetruth
valueisfalse.
2. Ifthetypeoftheexpressionisastring,thepredicateisfalseif
thelengthoftheexpressionis0.Forastringtypeexpressionwitha
lengthgreaterthan0,thepredicatetruthvalueistrue.
3. Ifthetypeoftheexpressionisxs:boolean,thepredicatetruth
valueisthevalueoftheexpression.
4. Iftheexpressionresultsinanemptysequence,thepredicatetruth
valueisfalse.
5. Ifthevalueofthepredicateexpressionisanodesequence,the
predicatetruthvalueistrueifthesequencecontainsatleastone
node;otherwiseitisfalse.
Queriesthatincludeapredicatereturnonlynodesinasequenceforwhichthe
predicatetruthvalueevaluatestotrue.Predicatesarecomposedofexpressions,
convenientlyreferredtoaspredicateexpressions,enclosedinsquarebrackets([]).You
canspecifymultiplepredicatesinapath,andtheyareevaluatedinorderofoccurrence
fromlefttoright.
NoteTheXQueryspecificationsaysthatmultiplepredicatesareevaluatedfromleftto
right,butitalsogivessomewiggleroomforvendorstoperformpredicateevaluationsin
otherorders,allowingthemtotakeadvantageofvendor-specificfeaturessuchasindexes
andotheroptimizations.Youdon’thavetoworrytoomuchabouttheinternalevaluation
orderofpredicates,though.Nomatterwhatorderpredicatesareactuallyevaluatedin,the
endresultshavetobethesameasifthepredicateswereevaluatedlefttoright.
ValueComparisonOperators
Aswementioned,thebasicfunctionofpredicatesistofilterresults.Resultsarefilteredby
specifiedcomparisons,andXQueryoffersarichsetofcomparisonoperators.These
operatorsfallintothreemaincategories:valuecomparisonoperators,generalcomparison
operators,andnodecomparisonoperators.Valuecomparisonoperatorscomparesingleton
atomicvaluesonly.Tryingtocomparesequenceswithvaluecomparisonoperatorsresults
inanerror.ThevaluecomparisonoperatorsarelistedinTable13-4.
Table13-4.ValueComparisonOperators
Operator Description
Eq Equal
Ne Notequal
Lt Lessthan
Le Lessthanorequalto
Gt Greaterthan
Ge Greaterthanorequalto
Valuecomparisonsfollowaspecificsetofrules:
1. Theoperandsontheleftandrightsidesoftheoperatorare
atomized.
2. Ifeitheratomizedoperandisanemptysequence,theresultisan
emptysequence.
3. Ifeitheratomizedoperandisasequencewithalengthgreaterthan
1,anerrorisraised.
4. Ifeitheratomizedoperandisoftypexs:untypedAtomic,itis
casttoxs:string.
5. Iftheoperandshavecompatibletypes,theyarecomparedusingthe
appropriateoperator.Ifthecomparisonofthetwooperandsusing
thechosenoperatorevaluatestotrue,theresultistrue;
otherwisetheresultisfalse.Iftheoperandshaveincompatible
types,anerroristhrown.
ConsiderthevaluecomparisonexamplesinListing13-21,withresultsshownin
Figure13-18.
Listing13-21.ValueComparisonExamples
DECLARE@xxml=N'<?xmlversion="1.0"?>
<Animal>
Cat
</Animal>';
SELECT@x.query(N'9eq9.0(:9isequalto9.0:)');
SELECT@x.query(N'4gt3(:4isgreaterthan3:)');
SELECT@x.query(N'(/Animal/text())[1]lt"Dog"(:Catis
lessthanDog:)');
Figure13-18.ResultsoftheXQueryValueComparisons
Listing13-22attemptstocomparetwovaluesofincompatibletypes,namelyan
xs:decimaltypevalueandanxs:stringvalue.Theresultistheerrormessage
shownintheresultsfollowing.
Listing13-22.IncompatibleTypeValueComparison
DECLARE@xxml=N'';
SELECT@x.query(N'3.141592eq"Pi"');
Msg2234,Level16,State1,Line2
XQuery[query()]:Theoperator"eq"cannotbeappliedto
"xs:decimal"and"xs:string"operands.
GeneralComparisonOperators
Generalcomparisonsareexistentialcomparisonsthatworkonoperandsequencesofany
length.Existentialsimplymeansthatifoneatomizedvaluefromthefirstoperand
sequencefulfillsavaluecomparisonwithatleastoneatomizedvaluefromthesecond
operandsequence,theresultistrue.Thegeneralcomparisonoperatorswilllookfamiliar
toprogrammerswhoareversedinothercomputerlanguages,particularlyC-style
languages.ThegeneralcomparisonoperatorsarelistedinTable13-5.
Table13-5.GeneralComparisonOperators
Operator Description
=Equal
!= Notequal
<Lessthan
>Greaterthan
<= Lessthanorequalto
>= Greaterthanorequalto
Listing13-23demonstratescomparisonsusinggeneralcomparisonsonXQuery
sequences.TheresultsareshowninFigure13-19.
Listing13-23.GeneralComparisonExamples
DECLARE@xxml='';
SELECT@x.query('(3.141592,1)=(2,3.141592)(:true:)
');
SELECT@x.query('(1.0,2.0,3.0)=1(:true:)');
SELECT@x.query('("Joe","Harold")<"Adam"(:false:)');
SELECT@x.query('xs:date("1999-01-01")<xs:date("2006-01-
01")(:true:)');
Figure13-19.GeneralXQueryComparisonResults
Here’showthegeneralcomparisonoperatorswork.Thefirstquerycomparesthe
sequences(3.141592,1)and(2,3.141592)usingthe=operator.The
comparisonatomizesthetwooperandsequencesandcomparesthemusingtherulesfor
theequivalentvaluecomparisonoperators.Sincetheatomicvalue3.141592existsin
bothsequences,theequalitytestresultistrue.
Thesecondexamplecomparesthesequence(1.0,2.0,3.0)totheatomicvalue
1.Theatomicvalues1.0and1arecompatibletypesandareequal,sotheequalitytest
resultistrue.ThethirdqueryreturnsfalsebecauseneitheroftheatomicvaluesDoe
orHaroldarelexicallylessthantheatomicvalueAdam.
Thefinalexamplecomparestwoxs:datevalues.Sincethedate1999-01-01is
lessthanthedate2006-01-01,theresultistrue.
XqueryDateFormat
TheXQueryimplementationinSQLServer2005hadaspecialrequirementconcerning
xs:date,xs:time,xs:dateTime,andderivedtypes.Accordingtoasubsetofthe
ISO8601standardthatSQLServer2005uses,dateandtimevalueshadtoincludea
mandatorytimeoffsetspecifier.SQLServer2014doesnotstrictlyenforcethisrule.When
youleavethetimeoffsetinformationoffanXQuerydateortimevalue,SQLServer2014
defaultstothezeromeridian(Zspecifier).
SQLServer2014alsodiffersfromSQLServer2005inhowithandlestimeoffset
information.InSQLServer2005,alldateswereautomaticallynormalizedtocoordinated
universaltime(UTC).SQLServer2014storesthetimeoffsetinformationyouindicate
whenspecifyingadateortimevalue.Ifatimezoneisprovided,itmustfollowthedateor
timevalue,andcanbeeitherofthefollowing:
ThecapitalletterZ,whichstandsforthezeromeridian,orUTC.Thezeromeridian
runsthroughGreenwich,England.
Anoffsetfromthezeromeridianintheformat[+/-]hh:mm.Forinstance,theUS
EasternTimezonewouldbeindicatedas-05:00.
HereareafewsampleISO8601formatteddatesandtimesacceptabletoSQLServer,
withdescriptions:
1999-05-16:May16,1999,notime,UTC
09:15:00-05:00:Nodate,9:15am,USandCanadaEasterntime
2003-12-25T20:00:00-08:00:December25,2003,8:00pm,USand
CanadaPacifictime
2004-07-06T23:59:59.987+01:00:July6,2004,11:59:59.987pm(.987
isfractionalseconds),CentralEuropeantime
UnlikethehomogenoussequencesinListing13-23,aheterogeneoussequenceisone
thatcombinesnodesandatomicvalues,oratomicvaluesofincompatibletypes(suchas
xs:stringandxs:decimal).Tryingtoperformageneralcomparisonwitha
heterogeneoussequencecausesanerrorinSQLServer,asdemonstratedbyListing13-24.
Listing13-24.GeneralComparisonwithHeterogeneousSequence
DECLARE@xxml='';
SELECT@x.query('(xs:date("2006-10-09"),6.02E23)>
xs:date("2007-01-01")');
TheerrorgeneratedbyListing13-24lookslikethefollowing:
Msg9311,Level16,State1,Line3
XOuery[queryQ]:Heterogeneoussequencesarenotallowedin
V,found
'xs:date'and'xs:double'.
SQLServeralsodisallowsheterogeneoussequencesthatmixnodesandatomicvalues,
asdemonstratedbyListing13-25.
Listing13-25.MixingNodesandAtomicValuesinSequences
DECLARE@xxml='';
SELECT@x.query('(1,<myNode>Testing</myNode>)');
Tryingtomixandmatchnodesandatomicvaluesinasequencelikethisresultsinan
errormessageindicatingthatyoutriedtocreateasequenceconsistingofatomicvalues
andnodes,similartothefollowing:
Msg2210,Level16,State1,Line3
XOuery[queryQ]:Heterogeneoussequencesarenotallowed:
found
'xs:integer'and'element(myl\lode,xdt:untyped)'
NodeComparisons
ThethirdtypeofcomparisonthatXQueryallowsisanodecomparison.Node
comparisonsallowyoutocompareXMLnodesindocumentorder.Thenodecomparison
operatorsarelistedinTable13-6.
Table13-6.NodeComparisonOperators
Operator Description
Is Nodeidentityequality
<< Leftnodeprecedesrightnode
>> Leftnodefollowsrightnode
Theisoperatorcomparestwonodestoeachotherandreturnstrueiftheleftnodeis
thesamenodeastherightnode.Notethatthisisnotatestoftheequalityofnodecontent
butratheroftheactualnodesthemselvesbasedonaninternallygeneratednodeID.
ConsiderthesamplenodecomparisonsinListing13-26withresultsshowninFigure13-
20.
Listing13-26.NodeComparisonSamples
DECLARE@xxml=N'<?xmlversion="1.0"?>
<Root>
<NodeA>TestNode</NodeA>
<NodeA>TestNode</NodeA>
<NodeB>TestNode</NodeB>
</Root>';
SELECT@x.query('((/Root/NodeA)[1]is(//NodeA)[1])(:true
:)');
SELECT@x.query('((/Root/NodeA)[1]is(/Root/NodeA)[2])(:
false:)');
SELECT@x.query('((/Root/NodeA)[2]<<(/Root/NodeB)[1])(:
true:)');
Figure13-20.ResultsoftheXQueryNodeComparisons
Thefirstqueryusestheisoperatortocompare(/Root/NodeA)[l]toitself.The
[l]numericpredicateattheendofthepathensuresthatonlyasinglenodeisreturned
forcomparison.Theright-handandleft-handexpressionsmustbothevaluatetoa
singletonoremptysequence.Theresultofthiscomparisonistrueonlybecause
(/Root/NodeA)[l]isthesamenodereturnedbythe(//NodeA)[l]pathonthe
right-handsideoftheoperator.
Thesecondquerycompares(/Root/NodeA)[l]with(/Root/NodeA)[2].
Eventhoughthetwonodeshavethesamenameandcontent,theyareinfactdifferent
nodes.Becausetheyaredifferentnodes,theisoperatorreturnsfalse.
ThefinalqueryretrievesthesecondNodeAnodewiththepath(/Root/NodeA)
[2].Thenitusesthe“operatortodetermineifthisnodeprecedestheNodeBnodefrom
thepath(/Root/NodeB)[l].SincethesecondNodeAprecedesNodeBindocument
order,theresultofthiscomparisonistrue.
Anodecomparisonresultsinanxs:booleanvalueorevaluatestoanempty
sequenceifoneoftheoperandsresultsinanemptysequence.Thisisdemonstratedin
Listing13-27.
Listing13-27.NodeComparisonThatEvaluatestoanEmptySequence
DECLARE@xxml=N'<?xmlversion="1.0"?>
<Root>
<NodeA>TestNode</NodeA>
</Root>';
SELECT@x.query('((/Root/NodeA)[1]is(/Root/NodeZ)[1])(:
emptysequence:)');
Theresultofthenodecomparisonisanemptysequencebecausetheright-handpath
expressionevaluatestoanemptysequence(becausenonodenamedNodeZexistsinthe
XMLdocument).
ConditionalExpressions(if…then…else)
Asshowninthepreviousexamples,XQueryreturnsxs:booleanvaluesorempty
sequencesastheresultofcomparisons.XQueryalsoprovidessupportfortheconditional
if…then…elseexpression.Theif…then…elseconstructreturnsanexpression
basedonthexs:booleanvalueofanotherexpression.TheformatfortheXQuery
conditionalexpressionisshowninthefollowing:
if(test-expression)thenthen-expressionelseelse-expression
Inthissyntax,test-expressionrepresentstheconditionalexpressionthatis
evaluated,theresultofwhichwilldeterminethereturnedresult.Whenevaluatingtest-
expression,XQueryappliesthefollowingrules:
1. Iftest-expressionresultsinanemptysequence,theresultis
false.
2. Iftest-expressionresultsinanxs:booleanvalue,the
resultisthexs:booleanvalueoftheexpression.
3. Iftest-expressionresultsinasequenceofoneormore
nodes,theresultistrue.
4. Ifthesestepsfail,astaticerrorisraised.
Iftest-expressionevaluatestotrue,then-expressionisreturned.Iftest-expression
evaluatestofalse,else-expressionisreturned.
TheXQueryconditionalisadeclarativeexpression.UnliketheC#if…else
statementandVisualBasic’sIf…Then…Elseconstruct,XQuery’sconditionalif…
then…elsedoesn’trepresentabranchinprocedurallogicorachangeinprogramflow.
Itactslikeafunctionthatacceptsaconditionalexpressionasinputandreturnsan
expressionasaresult.Inthisrespect,XQuery’sif…then…elsehasmoreincommon
withtheSQLCASEexpressionandtheC#?:operatorthantheifstatementin
procedurallanguages.IntheXQueryif…then…else,syntaxparenthesesarerequired
aroundtest-expression,andtheelseclauseismandatory.
ArithmeticExpressions
XQueryarithmeticexpressionsprovidesupportfortheusualsuspects—standard
mathematicaloperatorsfoundinmostmodernprogramminglanguages,includingthe
following:
Multiplication(*)
Division(div)
Addition(+)
Subtraction(-)
Modulo(mod)
IntegerDivisioninXQuery
SQLServer2014XQuerydoesnotsupporttheidivintegerdivisionoperator.
Fortunately,theW3CXQueryrecommendationdefinestheidivoperatorasequivalent
tothefollowingdivexpression:
($argldiv$arg2)castasxs:integer?
IfyouneedtoconvertXQuerycodethatusesidivtoSQLServer,youcanusethe
divandcastoperatorsasshowntoduplicateidivfunctionality.
XQueryalsosupportstheunaryplus(+)andunaryminus(-)operators.Becausethe
forwardslashcharacterisusedasapathseparatorinXQuery,thedivisionoperatoris
specifiedusingthekeyworddiv.Themodulooperator,mod,returnstheremainderof
division.
Ofthesupportedoperators,unaryplusandunaryminushavethehighestprecedence.
Multiplication,division,andmoduloarenext.Binaryadditionandsubtractionhavethe
lowestprecedence.Parenthesescanbeusedtoforcetheevaluationorderofmathematical
operations.
XQueryFunctions
XQueryprovidesseveralbuilt-infunctionsdefinedintheXQueryFunctionsand
Operatorsspecification(sometimesreferredtoasF&O),whichisavailableat
www.w3.org/TR/xquery-operators/.Built-inXQueryfunctionsareinthe
predeclarednamespacefn.
TipThefnnamespacedoesnothavetobespecifiedwhencallingabuilt-infunction.
Somepeopleleaveitofftoimprovereadabilityoftheircode.
We’velistedtheXQueryfunctionsthatSQLServer2014supportsinTable13-7.
Table13-7.SupportedBuilt-inXQueryFunctions
Function Description
fn:avg(x) Returnstheaverageofthesequenceofnumbersx.Forexample,fn:avg((10,
20,30,40,50))returns30.
fn:ceiling(n) Returnsthesmallestnumberwithoutafractionalpartthatisnotlessthann.For
example,fn:ceiling(1.1)returns2.
fn:concat(s1,
s2,…)
Concatenateszeroormorestringsandreturnstheconcatenatedstringasaresult.For
example,fn:concat(“hi”,“,”,“howareyou?”)returns“hi,how
areyou?”.
fn:contains(s1,
s2,)
Returnstrueifthestrings1containsthestrings2.Forexample,
fn:contains(“fish”,“is”)returnstrue.
fn:count(x) Returnsthenumberofitemsinthesequencex.Forexample,fn:count((1,
2,4,8,16))returns5.
fn:data(a) Returnsthetypedvalueofeachitemspecifiedbytheargumenta.Forexample,
fn:data((3.141592,“hello”))returns“3.141592hello”.
fn:distinct-
values(x)
Returnsthesequencexwithduplicatevaluesremoved.Forexample,
fn:distinct-values((1,2,3,4,5,4,5))returns“1234
5”.
fn:empty(i) Returnstrueifiisanemptysequence;returnsfalseotherwise.Forexample,
fn:empty((1,2,3))returnsfalse.
fn:expanded-
QName(u,l)
Returnsanxs:QName.Theargumentsuandlrepresentthexs:QName’s
namespaceURIandlocalname,respectively.
fn:false() Returnsthexs:booleanvaluefalse.Forexample,fn:false()returns
false.
fn:floor(n) Returnsthelargestnumberwithoutafractionalpartthatisnotgreaterthann.For
example,fn:floor(1.1)returns1.
fn:id(x)
ReturnsthesequenceofelementnodeswithIDvaluesthatmatchoneormoreofthe
IDREFvaluessuppliedinx.Theparameterxistreatedasawhitespace-separated
sequenceoftokens.
fn:last() Returnstheindexnumberofthelastiteminthesequencebeingprocessed.Thefirst
indexinthesequencehasanindexof1.
fn:local-
name(n) Returnsthelocalname,withoutthenamespaceURI,ofthespecifiednoden.
fn:local-name-
from-QName(q)
Returnsthelocalnamepartofthexs:QNameargumentq.Thevaluereturnedisan
xs:NCName.
fn:max(x) Returnstheitemwiththehighestvaluefromthesequencex.Forexample,
fn:max((1.0,2.5,9.3,0.3,-4.2))returns9.3.
fn:min(x) Returnstheitemwiththelowestvaluefromthesequencex.Forexample,
fn:min((“x”,“q”,“u”,“e”,“r”,“y”))returns“e”.
fn:namespace-
uri(n) ReturnsthenamespaceURIofthespecifiednoden.
fn:namespace-
uri-from-
QName(q)
ReturnsthenamespaceURIpartofthexs:QNameargumentq.Thevaluereturned
isanxs:NCName.
fn:not(b)
ReturnstrueiftheeffectiveBooleanvalueofbisfalse;returnsfalseifthe
effectiveBooleanvalueistrue.Forexample,
fn:not(xs:boolean(“true”))returnsfalse.
fn:number(n) Returnsthenumericvalueofthenodeindicatedbyn.Forexample,
fn:number(“/Root/NodeA[1]”).
fn:position() Returnstheindexnumberofthecontextiteminthesequencecurrentlybeing
processed.
fn:round(n) Returnsthenumberclosesttonthatdoesnothaveafractionalpart.Forexample,
fn:round(10.5)returns11.
fn:string(a) Returnsthevalueoftheargumenta,expressedasanxs:string.Forexample,
fn:string(3.141592)returns“3.141592”.
fn:string-
length(s)
Returnsthelengthofthestrings.Forexample,fn:string-
length(“abcdefghij”)returns10.
fn:substring(s,
m,n)
Returnsncharactersfromthestrings,beginningatpositionm.Ifnisnotspecified,
allcharactersfrompositionmtotheendofthestringarereturned.Thefirst
characterinthestringisposition1.Forexample,fn:substring(“Money”,
2,3)returns“one”.
fn:sum(x) Returnsthesumofthesequenceofnumbersinx.Forexample,fn:sum((1,
4,9,16,25))returns55.
fn:true() Returnsthexs:booleanvaluetrue.Forexample,fn:true()returnstrue.
Inaddition,twofunctionsfromthesql:namespacearesupported.The
sql:columnfunctionallowsyoutoexposeandbindSQLServerrelationalcolumndata
inXQueryqueries.ThisfunctionacceptsthenameofanSQLcolumnandexposesits
valuestoyourXQueryexpressions.Listing13-28demonstratesthesql:column
function.
Listing13-28.Thesql:columnFunction
DECLARE@xxml=N'';
SELECT@x.query(N'<Name>
<ID>
{
sql:column("p.BusinessEntityID")
}
</ID>
<FullName>
{
sql:column("p.FirstName"),
sql:column("p.MiddleName"),
sql:column("p.LastName")
}
</FullName>
</Name>')
FROMPerson.Personp
WHEREp.BusinessEntityID<=5
ORDERBYp.BusinessEntityID;
Theresultofthisexample,showninFigure13-21,isasetofXMLdocuments
containingtheBusinessEntitylDandfullnameofthefirstfivecontactsfromthe
Person.Persontable.
Figure13-21.Resultsofthesql:columnFunctionQuery
Thesqlvariablefunctiongoesanotherstep,allowingyoutoexposeT-SQL
variablestoXQuery.ThisfunctionacceptsthenameofaT-SQLvariableandallowsyou
toaccessitsvalueinyourXQueryexpressions.Listing13-29isanexamplethatcombines
thesql:columnandsql:variablefunctionsinasingleXQueryquery.
Listing13-29.XQuerysql:columnandsql:variableFunctionsExample
/*10%discount*/
DECLARE@discountNUMERIC(3,2);
SELECT@discount=0.10;
DECLARE@xxml;
SELECT@x='';
SELECT@x.query('<Product>
<Model-ID>{sql:column("ProductModelID")}</Model-ID>
<Name>{sql:column("Name")}</Name>
<Price>{sql:column("ListPrice")}</Price>
<DiscountPrice>
{sql:column("ListPrice")-
(sql:column("ListPrice")*sql:variable("@discount"))}
</DiscountPrice>
</Product>
')
FROMProduction.Productp
WHEREProductModelID=30;
TheXQuerygeneratesXMLdocumentsusingthesql:columnfunctiontoretrieve
theListPricefromtheProduction.Producttable.Italsousesthe
sql:variablefunctiontocalculateadiscountpricefortheitemsretrieved.Figure13-
22showspartialresultsofthisquery(formattedforeasierreading):
Figure13-22.PartialResultsoftheQuerywiththesql:columnandsql:variableFunctions
ConstructorsandCasting
TheXDMprovidesconstructorfunctionstodynamicallycreateinstancesofseveral
supportedtypes.Theconstructorfunctionsareallintheformatxs:TYP(value),where
TYPistheXDMtypename.MostoftheXDMdatatypeshaveconstructorfunctions;
however,thefollowingtypesdonothaveconstructorsinSQLServerXQuery:
xs:yearMonthDuration,xs:dayTimeDuration,xs:OName,xs:NMTOKEN,
andxs:NOTATION.
ThefollowingareexamplesofXQueryconstructorfunctions:
xs:boolean("1")(:returnstrue:)
xs:integer(1234)(:returns1234:)
xs:float(9.8723E+3)(:returns9872.3:)
xs:NCName("my-id")(:returnstheNCName"my-id":)
Numerictypescanbeimplicitlycasttotheirbasetypes(orothernumerictypes)by
XQuerytoensureproperresultsofcalculations.Theprocessofimplicitcastingisknown
astypepromotion.Forinstance,inthefollowingsampleexpression,thexs:integer
typevalueispromotedtoanxs:decimaltocompletethecalculation:
xs:integer(100)+xs:decimal(l00.99)
NoteOnlynumerictypescanbeimplicitlycast.Stringandothertypescannotbe
implicitlycastbyXQuery.
Explicitcastingisperformedusingthecastaskeywords.Examplesofexplicit
castingincludethefollowing:
xs:string("98d3f4")castasxs:hexBinary?(:98d3f4:)
100castasxs:double?(:1.0E+2:)
"0"castasxs:boolean?(:true:)
The?afterthetargetdatatypeistheoptionaloccurrenceindicator.Itisusedto
indicatethatanemptysequenceisallowed.SQLServerXQueryrequiresthe?afterthe
castasexpression.SQLServerBOLprovidesadetaileddescriptionoftheXQuery
typecastingrulesathttp://msdn.microsoft.com/en-
us/library/ms191231.aspx.
TheinstanceofBooleanoperatorallowsyoutodeterminethetypeofasingleton
value.Thisoperatortakesasingletonvalueonitsleftsideandatypeonitsright.The
xs:booleanvaluetrueisreturnediftheatomicvaluerepresentsaninstanceofthe
specifiedtype.Thefollowingexamplesdemonstratetheinstanceofoperator:
10instanceofxs:integer(:returnstrue:)100instanceof
xs:decimal(:returnstrue:)"hello"instanceof
xs:bytes(:returnsfalse:)
The?optionaloccurrenceindicatorcanbeappendedafterthedatatypetoindicate
thattheemptysequenceisallowable(thoughitisnotmandatory,aswiththecastas
operator),asinthisexample:
9.8273instanceofxs:double?(:returnstrue:)
FLWORExpressions
FLWORexpressionsprovideawaytoiterateoverasequenceandbindintermediate
resultstovariables.FLWORisanacronymforthekeywordsthatdefinethistypeof
expression:for,let,where,orderby,andreturn.Thissectiondiscusses
XQuery’spowerfulFLWORexpressions.
TheforandreturnKeywords
TheforandreturnkeywordshavelongbeenapartofXPath,thoughinnotnearlyso
powerfulaformastheXQueryFLWORexpression.Theforkeywordspecifiesthata
variableisiterativelyboundtotheresultsofthespecifiedpathexpression.Theresultof
thisiterativebindingprocessisknownasatuplestream.TheXQueryforexpressionis
roughlyanalogoustotheT-SQLSELECTstatement.Theforkeywordmust,ata
minimum,haveamatchingreturnclauseafterit.ThesampleinListing13-30
demonstratesabasicforexpression.
Listing13-30.BasicXQueryfor…returnExpression
SELECTCatalogDescription.query(N'declarenamespacens=
"http://schemas.microsoft.com/sqlserver/2004/07/adventure-
works/ProductModelDescription";
for$specin//ns:ProductDescription/ns:Specifications/*
returnfn:string($spec)')ASDescriptionFROM
Production.ProductModelWHEREProductModelID=19;
Theforclauseiteratesthroughallelementsreturnedbythepathexpression.Itthen
bindstheelementstothe$specvariable.Thetuplestreamthatisboundto$spec
consistsofthefollowingnodesindocumentorder:
$spec=<Material>AlmuminumAlloy</Material>
$spec=<Color>Availableinmostcolors</Color>
$spec=<ProductLine>Mountainbike</ProductLine>
$spec=<Style>Unisex</Style>
$spec=<RiderExperience>AdvancedtoProfessional
riders</RiderExperience>
Thereturnclauseappliesthefn:stringfunctiontothe$specvariabletoreturn
thestringvalueofeachnodeasitisbound.Theresultslooklikethefollowing:
AlmuminumAlloyAvailableinmostcolorsMountainbike
UnisexAdvancedtoProfessionalriders.
ThesamplecanbemodifiedtoreturnanXMLresult,usingthetechniquesdescribed
previouslyinthe“DynamicXMLConstruction”section.Listing13-31demonstrateswith
resultsshowninFigure13-23.
Listing13-31.XQueryfor…returnExpressionwithXMLResult
SELECTCatalogDescription.query(
N'declarenamespacens=
"http://schemas.microsoft.com/sqlserver/2004/07/adventure-
works/ProductModelDescription";
for$specin//ns:ProductDescription/ns:Specifications/*
return<detail>{
$spec/text()}</detail>')ASDescription
FROMProduction.ProductModelWHEREProductModelID=19;
Figure13-23.Resultsofthefor…returnExpressionwithXMLConstruction
XQueryallowsyoutobindmultiplevariablesintheforclause.Whenyoubind
multiplevariables,theresultistheCartesianproductofallpossiblevaluesofthe
variables.SQLServerprogrammerswillrecognizetheCartesianproductasbeing
equivalenttotheSQLCROSSJOINoperator.Listing13-32modifiestheprevious
examplefurthertogeneratetheCartesianproductoftheSpecificationsand
Warrantychildnodetext.
Listing13-32.XQueryCartesianProductwithforExpression
SELECTCatalogDescription.query(N'declarenamespacens=
"http://schemas.microsoft.com/sqlserver/2004/07/adventure-
works/ProductModelDescription";
for$specin//ns:ProductDescription/ns:Specifications/*,
$featin
//ns:ProductDescription/*:Features/*:Warranty/node()
return<detail>
{
$spec/text()
}+
{
fn:string($feat/.)
}
</detail>'
)ASDescription
FROMProduction.ProductModel
WHEREProductModelID=19;
The$specvariableisboundtothesamenodesshownpreviously.Asecondvariable
binding,forthevariable$feat,isaddedtotheforclauseinthisexample.Specifically,
thissecondvariableisboundtothechildnodesoftheWarrantyelement,asshown
following:
<pl:WarrantyPeriod>3years</pl:WarrantyPeriod>
<pl:Description>partsandlabor</pl:Description
TheCartesianproductofthetextnodesofthesetwotuplestreamsconsistsoften
possiblecombinations.ThefinalresultoftheXQueryexpressionisshowninFigure13-24
(formattedforeasierreading).
Figure13-24.CartesianProductXQuery
Aboundvariablecanbeusedimmediatelyafteritisbound,eveninthesamefor
clause.Listing13-33demonstratesthis.
Listing13-33.UsingaBoundVariableintheforClause
SELECTCatalogDescription.query
(
N'declarenamespacens=
"http://schemas.microsoft.com/sqlserver/2004/07/adventure-
works/ProductModelDescription";
for$specin//ns:ProductDescription/ns:Specifications,
$colorin$spec/Color
return<color>
{
$color/text()
}
</color>'
)ASColor
FROMProduction.ProductModel
WHEREProductModelID=19;
Inthisexample,the$specvariableisboundtotheSpecificationsnode.Itis
thenusedinthesameforclausetobindavaluetothevariable$color.Theresultis
showninFigure13-25.
Figure13-25.BindingaVariabletoAnotherBoundVariableintheforClause
ThewhereKeyword
Thewherekeywordspecifiesanoptionalclausetofiltertuplesgeneratedbythefor
clause.Theexpressioninthewhereclauseisevaluatedforeachtuple,andthosefor
whichtheeffectiveBooleanvalueevaluatestofalsearediscardedfromthefinalresult.
Listing13-34demonstratesuseofthewhereclausetolimittheresultstoonlythose
tuplesthatcontaintheletterA.TheresultsareshowninFigure13-26.
Listing13-34.whereClauseDemonstration
SELECTCatalogDescription.query
(
N'declarenamespacens=
"http://schemas.microsoft.com/sqlserver/2004/07/adventure-
works/ProductModelDescription";
for$specin//ns:ProductDescription/ns:Specifications/*
where$spec[contains(.,"A")]
return<detail>
{
$spec/text()
}
</detail>'
)ASDetail
FROMProduction.ProductModel
WHEREProductModelID=19;
Figure13-26.ResultsofaFLWORExpressionwiththewhereClause
Thefunctionsandoperatorsdescribedpreviouslyinthischapter(suchasthe
containsfunctionusedintheexample)canbeusedinthewhereclauseexpressionto
limitresultsasrequiredbyyourapplication.
TheorderbyKeywords
TheorderbyclauseisanoptionalclauseoftheFLWORstatement.Theorderby
clausereordersthetuplestreamgeneratedbytheforclause,usingcriteriathatyou
specify.Theorderbycriteriaconsistsofoneormoreorderingspecificationsthatare
madeupofanexpressionandanoptionalordermodifier.Orderingspecificationsare
evaluatedfromlefttoright.
Theoptionalordermodifieriseitherascendingordescendingtoindicatethe
directionofordering.Thedefaultisascending,asshowninListing13-35.Thesample
usestheorderbyclausetosorttheresultsindescending(reverse)order.Theresults
areshowninFigure13-27.
Listing13-35.orderbyClause
SELECTCatalogDescription.query(N'declarenamespacens=
"http://schemas.microsoft.com/sqlserver/2004/07/adventure-
works/ProductModelDescription";
for$specin//ns:ProductDescription/ns:Specifications/*
orderby$spec/.descending
return<detail>{$spec/text()}</detail>')ASDetail
FROMProduction.ProductModel
WHEREProductModelID=19;
Figure13-27.ResultsofaFLWORExpressionwiththeorderbyClause
TheletKeyword
SQLServer2012addedsupportfortheFLWORexpressionletclause.Theletclause
allowsyoutobindtuplestreamstovariablesinsidethebodyoftheFLWORexpression.
Youcanusetheletclausetonamerepeatingexpressions.SQLServerXQueryinserts
theexpressionassignedtotheboundvariableeverywherethevariableisreferencedinthe
FLWORexpression.Listing13-36demonstratestheletclauseinaFLWORexpression,
withresultsshowninFigure13-28.
Listing13-36.letClause
SELECTCatalogDescription.query
(
N'declarenamespacens=
"http://schemas.microsoft.com/sqlserver/2004/07/adventure-
works/ProductModelDescription";
for$specin//ns:ProductDescription/ns:Specifications/*
let$val:=$spec/text()
orderbyfn:string($val[1])ascending
return<spec>
{
$val
}
</spec>'
)ASDetail
FROMProduction.ProductModel
WHEREProductModelID=19;
Figure13-28.ResultsofaFLWORExpressionwiththeletClause
UTF-16Support
WhenSQLServerstoresunicodedatatypeswithncharandnvarcharitstoresusingUCS-
2encoding(UCS–UniversalCharacterSet),meaningitcountsevery2-bytecharacteras
singlecharacter.Inrecentyearsthecharaterlimitwasincreasedto31bits,anditwouldbe
difficulttostorethesecharactersgiventhefactthatweonlyhave2bytesper
character.ThisledtotheproblemofSQLServernothandlingsomeofthecharacters
properly.InthepreviousversionsofSQLServer,eventhoughSQLXMLsupportsUTF-
16,thestringfunctionsonlysupportedforUCS-2unicodevalues.Thismeansthateven
thoughthedatacanbestoredandretrievedwithoutlosingtheproperty,someofthestring
operationssuchasstringlengthorsubstringfunctionsprovidedwrongresultssincethey
don’trecognizesurrogatepairs.
Let’sreviewthiswithanexample,andinourcase,let’ssaywehavetostoreUTF-16
encodingsuchasmusicalsymboldrumcleff-1asapartofanameinourdatabase.Drum-
cleff-1isrepresentedbysurrogatevalues0xD834and0xDD25.Let’ssaywecalculatethe
lengthofthestringtoseeifSQLServerchecksforsurrogatepairs.Listing13-37
demonstratesthecreationofthesamplerowforourusageandListing13-38usestherow
thatwascreatedusingListing13-37todemonstrateUTF-16encodinghandlinginSQL
Server.ResultsforListing13-38areshowninFigure13-29.
Listing13-37.CreateRecordtoDemonstrateUTF-16
declare@BusinessEntityIdint
INSERTINTOPerson.BusinessEntity(rowguid,ModifiedDate)
VALUES(NEWID(),CURRENT_TIMESTAMP)
SET@BusinessEntityId=SCOPE_IDENTITY()
INSERTINTO[Person].[Person]
([BusinessEntityID]
,[PersonType]
,[NameStyle]
,[Title]
,[FirstName]
,[MiddleName]
,[LastName]
,[Suffix]
,[EmailPromotion]
,[AdditionalContactInfo]
,[Demographics]
,[rowguid]
,[ModifiedDate])
VALUES
(@BusinessEntityId,
'EM',
0,
NULL,
N'T'+nchar(0xD834)+nchar(0xDD25),
'J',
'Kim',
NULL,
0,
NULL,
'<IndividualSurvey
xmlns="http://schemas.microsoft.com/sqlserver/2004/07/adventure-
works/IndividualSurvey">
<TotalPurchaseYTD>0</TotalPurchaseYTD></IndividualSurvey>',
NEWID(),
CURRENT_TIMESTAMP)
Listing13-38.SQLServertoCheckforPresenceofSurrogates
SELECT
p.NameStyleAS"processing-instruction(nameStyle)",
p.BusinessEntityIDAS"Person/@ID",
p.ModifiedDateAS"comment()",
FirstNameAS"Person/Name/First",
Len(FirstName)AS"Person/FirstName/Length",
MiddleNameAS"Person/Name/Middle",
LastNameAS"Person/Name/Last"
FROMPerson.Personp
WHEREBusinessEntityID=20778
FORXMLPATH;
Figure13-29.ResultsofSQLServerUTF-16SurrogatePair
FromFigure13-29,youcanseethatthequeryreturnsthecolumnlengthtobe3
whereasthelengthshouldbe2becauselengthfunctioncalculatesthenumberof
charactersandwehave2charactersinourstring.Sincethesurrogatepairisnot
recognized,thenumberofcharactersislistedas3insteadof2.
Tomitigatetheaboveissue,inSQLServer2014thereisfullsupportforUTF-
16/UCS-4,meaningtheXqueryhandlesthesurrogatepairsproperlyandreturnsthe
correctresultsforstringoperationsandtheoperatorssuchas=,==,<,>=andLIKE.Note
thatsomeofthestringoperatorsmayalreadybesurrogateaware.Howeversincesomeof
theapplicationsarealreadydevelopedandbeingusedbasedontheolderbehavior,SQL
Server2012addedanewsetofflagstothecollationnamestoindicatethatthecollationis
UTF-16aware.The_SC(SupplementaryCharacters)flagwillbeappendedtotheversion
100collationnamesanditbeapplicablefornchar,nvarchar,andsql_variantdatatypes.
Let’smodifythecodesnippetwehavefromListing13-38andaddthe_SCcollation
tothequerytoseehowSQLServercalculatesthecolumnlengthproperly.Inthisexample
let’sincludethesupplementarycharacterscollationsothatSQLServerisUTF-16aware.
ThemodifiedcodesnippetisshowninListing13-39andresultsareshowninFigure13-
30.
Listing13-39.SurroagePairwithUTF-16and_SCcollation
SELECT
p.NameStyleAS"processing-instruction(nameStyle)",
p.BusinessEntityIDAS"Person/@ID",
p.ModifiedDateAS"comment()",
FirstNameAS"Person/Name/First",
Len(FirstNameCOLLATELatin1_General_100_CS_AS_SC)AS
"Person/FirstName/Length",
MiddleNameAS"Person/Name/Middle",
LastNameAS"Person/Name/Last"
FROMPerson.Personp
WHEREBusinessEntityID=20778
FORXMLPATH;
Figure13-30.ResultsofSQLServerUTF-16SurrogatePairwith_SCcollation
Figure13-30demonstratesthatbyusingsupplementarycharacterscollation,SQL
ServernowisUTF-16aware,anditcalculatesthecolumnlengthasitshould:weseethe
propervalueof2forthecolumnlength.
TomaintainbackwardcompatibilitySQLServerissurrogatepairawareonlywhenthe
compatibilitymodeissettoSQL11orhigher.IfthecompatabilitymodeissettoSQL10
orlower,thefn:string-lengthandfn:substringwillnotbesurrogateawareandtheolder
behaviorwillcontinue.
Summary
ThischapterhasexpandedthediscussionofSQLServerXMLfunctionalitythatwebegan
inChapter12.Inparticular,wefocusedontheSQLServerimplementationsofXPathand
XQuery.WeprovidedamoredetaileddiscussionoftheSQLServerFORXMLPATH
clauseXPathimplementation,includingXPathexpressionsyntax,axisspecifiers,and
supportednodetests.WealsodiscussedSQLServersupportforXMLnamespacesviathe
WITHXMLNAMESPACESclause.
WeusedthemajorityofthischaptertodetailSQLServersupportforXQuery,which
providesapowerfulsetofexpressiontypes,functions,operators,andsupportfortherich
XDMdatatypesystem.SQLServersupportforXQueryhasimprovedwiththereleaseof
SQLServer2014,includingnewoptionsliketheFLWORexpressionletclause,support
fordateandtimeliteralswithoutspecifyingexplicittimeoffsets,andUTF-16supportand
SupplementaryCharacterscollation.
ThenextchapterdiscussesSQLServer2014catalogviewsanddynamicmanagement
viewsandfunctionsthatprovideawaytolookunderthehoodofyourdatabasesand
serverinstances.
Exercises
1. [True/False]TheFORXMLPATHclausesupportsasubsetofthe
W3CXPathrecommendation.
2. [Chooseone]WhichofthefollowingsymbolsisusedinXQuery
andXPathasanaxisspecifiertoidentifyXMLattributes:
a. Anatsign(@)
b. Anexclamationpoint(!)
c. Aperiod(.)
d. Twoperiods(..)
3. [Fillintheblanks]Thecontextitem,indicatedbyasingleperiod(.)
inXPathandXQuery,specifiesthecurrent_________orscalar
_________beingaccessedatanygivenpointintimeduringquery
execution.
4. [Chooseallthatapply]YoucandeclarenamespacesforXQuery
expressionsinSQLServerusingwhichofthefollowingmethods:
e.TheT-SQLWITHXMLNAMESPACESclause
f.TheXQuerydeclaredefaultelementnamespace
statement
g.heT-SQLCREATEXMLNAMESPACEstatement
h.TheXQuerydeclarenamespacestatement
5. [Fillintheblanks]InXQuery,youcandynamicallyconstructXML
via____________constructorsor___________constructors.
6. [True/False]SQLServer2012supportsthefor,let,where,
orderby,andreturnclausesofXQueryFLWORexpressions.
7. [Fillintheblanks]_SCcollationenablesSQLServertobe
__________________.
8. [Chooseallthatapply]SQLServersupportsthefollowingtypesof
XQuerycomparisonoperators:
i.Arraycomparisonoperators
j.Generalcomparisonoperators
k.Nodecomparisonoperators
l.Valuecomparisonoperators
CHAPTER14
CatalogViewsandDynamicaentViews
SQLServerhasalwaysofferedaccesstometadatadescribingdatabases,tables,views,and
otherdatabaseobjects.PriortotheintroductionofcatalogviewsinSQLServer2005,the
primarymethodsofaccessingthismetadataincludedsystemtables,systemSPs,
INFORMATION_SCHEMAviews,andSQLDistributedManagementObjects(SQL-
DMO).Catalogviewsprovideaccesstoarichersetofdetailedinformationthananyof
theseoptionsprovidedinpreviousSQLServerreleases.SQLServerevenincludescatalog
viewsthatallowyoutoaccessserver-wideconfigurationmetadata.
NoteMetadataissimplydatathatdescribesdata.SQLServer2014databasesare
largely“self-describing.”Thedatadescribingtheobjects,structures,andrelationshipsthat
formadatabasearestoredinthedatabaseitself.Thisdatadescribingthedatabase
structureandobjectsiswhatwerefertoasmetadata.
SQLServer2014alsoprovidesdynamicmanagementviews(DMVs)anddynamic
managementfunctions(DMFs)thatallowyoutoaccessserver-stateinformation.TheSQL
ServerDMVsandDMFsprovidearelationaltabularviewofinternalSQLServerdata
structuresthatwouldotherwisebeinaccessible.SQLServer2014providesanewsetof
DMVsspecificallyfocusedonthememory,performance,andspaceusageofmemory-
optimizedtables.Examplesofmetadatathatcanbeaccessedincludeinformationabout
thestateofinternalmemorystructures,thecontentsofcachesandbuffers,andstatusesof
processesandcomponents.YoucanusetheinformationreturnedbyDMVsandDMFsto
diagnoseserverproblems,monitorserverhealth,andtuneperformance.Thischapter
discussescatalogviews,DMVs,andDMFs.
CatalogViews
Catalogviewsprovideinsightintodatabaseobjectsandserver-wideconfigurationoptions
inmuchthesamewaythatsystemtables,systemSPs,andINFORMATION_SCHEMA
viewsdidinpreviousreleasesofSQLServer.Catalogviewsofferadvantagesoverthese
oldermethodsofaccessingdatabaseandservermetadata,includingthefollowing:
Catalogviews,unlikesystemSPs,canbeusedinquerieswithresults
joinedtoothercatalogviewsortables.Youcanalsolimittheresults
returnedbycatalogviewswithaWHEREclause.
CatalogviewsofferSQLServer–specificinformationthatisn’t
availablethroughtheINFORMATION_SCHEMAviews.Thereasonis
thatalthoughINFORMATION_SCHEMAviewsarestillincludedin
SQLServertocomplywiththeISOstandard,theymaynotbe
regularlyupdated.Soit’sadvisabletousecatalogviewstoaccess
metadatainsteadofthesystemSPsorINFORMATION_SCHEMA
views.
Catalogviewsprovidericherinformationthansystemtablesand
simplifydataaccessfromsystemtablesregardlessofschemachanges
intheunderlyingsystemtables.Therearealsomorecatalogviews
availablethanlegacysystemtablesbecausesomecatalogviews
inheritrowsfromothercatalogviews.
Manycatalogviewsfollowaninheritancemodelinwhichsomecatalogviewsare
definedasextensionstoothercatalogviews.Thesys.tablescatalogview,for
instance,inheritscolumnsfromthesys.objectscatalogview.Somecatalogviews,
suchassys.allcolumns,aredefinedastheunionoftwoothercatalogviews.Inthis
example,thesys.allcolumnscatalogviewisdefinedastheunionofthe
sys.columnsandsys.systemcolumnscatalogviews.
SQLServersuppliesawiderangeofcatalogviewsthatreturnmetadataaboutall
differenttypesofdatabaseobjectsandserver-configurationoptions,SQLCLRassemblies,
XMLschemacollections,theSQLServerresourcegovernor,changetracking,andmore.
Ratherthangiveacompletelistofalltheavailablecatalogviews,thissectionprovides
someusageexamplesanddescriptionsofthefunctionalityavailablethroughcatalog
views.
TipBOLdetailsthecompletelistofavailablecatalogviews(therearemorethan100
ofthem)athttp://msdn.microsoft.com/en-
us/library/ms174365.aspx.
TableandColumnMetadata
Waybackinthepre-SQLServerIntegrationServices(SSIS)days,wespentagooddealof
ourtimecreatingcustomETL(extract,transform,andload)solutions.Oneofthe
problemswefacedwasthequirkynatureofthevariousbulk-copyAPIsavailable.Unlike
SQLServerDMLstatementslikeINSERT,whichspecifycolumnstopopulatebyname,
theavailablebulk-copyAPIsrequireyoutospecifycolumnstopopulatebytheirordinal
position.Thiscanleadtoallkindsofproblemsifthetablestructurechanges(forexample,
ifnewcolumnsareadded,columnsareremoved,ortheorderofexistingcolumnsis
changed).Onewaytodealwiththistypeofdisconnectistocreateyourownfunctionthat
mapscolumnnamestoordinalpositions.Youcanusecatalogviewstoaccessexactlythis
typeoffunctionality.InListing14-1,youjointhesys.schemas,sys.tables,
sys.columns,andsys.typescatalogviewstoreturncolumn-levelmetadataabout
theAdventureWorksPerson.Addresstable.TheresultsareshowninFigure14-1.
Listing14-1.RetrievingColumn-levelMetadatawithCatalogViews
SELECT
s.nameASschema_name,
t.nameAStable_name,
t.type_descAStable_type,
c.nameAScolumn_name,
c.column_id,
ty.nameASdata_type_name,
c.max_Length,
c.precision,
c.scale,
c.is_nullableFROMsys.schemassINNERJOINsys.tablest
ONs.schema_id=t.schema_idINNERJOINsys.columnsc
ONt.object_id=c.object_idINNERJOINsys.typesty
ONc.system_type_id=ty.system_type_idANDc.user_type_id
=ty.user_type_idWHEREs.name='Person'
ANDt.name='Address';
Figure14-1.Retrievingcolumn-levelmetadata
Thistypeofmetadataisalsousefulforadministrativeapplicationsordynamicqueries
thatneedtorunagainstseveraldifferenttablesforwhichyoudon’tnecessarilyknowthe
structureinadvance.
Whetherit’sforadministrativeapplications,bulkloading,ordynamicqueriesthat
needtorunagainstseveraldifferenttables,SQLServercatalogviewscanprovide
structureandattributeinformationfordatabaseobjects.SQLServer2014providesseveral
methodsofretrievingmetadata.
QueryingPermissions
Anotheradministrativetaskthatcanbeperformedthroughcatalogviewsisqueryingand
scriptingdatabaseobjectpermissions.Listing14-2beginsthisdemonstrationbycreatinga
coupleofnewusersnamedjackandjillintheAdventureWorksdatabase.Thejill
userisassignedpermissionstohumanresources–relatedobjects,andjackisassigned
permissionstoproductionobjects.
Listing14-2.CreatingthejackandjillUsers
CREATEUSERjillWITHOUTLOGIN;
CREATEUSERjackWITHOUTLOGIN;
GRANTSELECT,INSERT
ONSchema::HumanResourcesTOjill;
GRANTSELECT
ONdbo.ufnGetContactInformationTOjill;
GRANTEXECUTE
ONHumanResources.uspUpdateEmployeeLoginTOjill;
DENYSELECT
ONSchema::SalesTOjill;
DENYSELECT
ONHumanResources.Shift(ModifiedDate)TOjill;
GRANTSELECT,UPDATE,INSERT,DELETE
ONSchema::ProductionTOjackWITHGRANTOPTION;
Yougrantanddenypermissionstotheseusersonawideselectionofobjectsfor
demonstrationpurposes.ThequeryinListing14-3isamodifiedversionofanexample
firstpublishedbySQLServerMVPLouisDavidson.Thecodeusesthe
sys.databasepermissions,sys.databaseprincipals,andsys.objects
catalogviewstoquerythepermissionsgrantedanddeniedtodatabaseprincipalsinthe
database.TheresultsareshowninFigure14-2.
Listing14-3.QueryingPermissionsonAdventureWorksObjects
WITHPermissions(
permission,
type,
obj_name,
db_principal,
grant_type,
schema_name)AS
(
SELECTdp.permission_name,
CASEdp.class_desc
WHEN'OBJECT_OR_COLUMN'THEN
CASE
WHENminor_id>0THEN'COLUMN'
ELSEo.type_desc
END
ELSEdp.class_desc
END,
CASEdp.class_desc
WHEN'SCHEMA'THENSCHEMA_NAME(dp.major_id)
WHEN'OBJECT_OR_COLUMN'THEN
CASE
WHENdp.minor_id=0THENobject_name(dp.major_id)
ELSE
(
SELECTobject_name(o.object_id)+'.'+c.name
FROMsys.columnsc
WHEREc.object_id=dp.major_id
ANDc.column_id=dp.minor_id
)
END
ELSE'**UNKNOWN**'
END,
dpr.name,
dp.state_desc,
SCHEMA_NAME(o.schema_id)
FROMsys.database_permissionsdp
INNERJOINsys.database_principalsdpr
ONdp.grantee_principal_id=dpr.principal_id
LEFTJOINsys.objectso
ONo.object_id=dp.major_id
WHEREdp.major_id>0
)
SELECT
p.permission,
CASEtype
WHEN'SCHEMA'THEN'Schema::'+obj_name
ELSEschema_name+'.'+obj_name
ENDASname,
p.type,
p.db_principal,
p.grant_type
FROMPermissionsp
ORDERBY
p.db_principal,
p.permission;
GO
Figure14-2.Resultsofthepermissionsquery
AsyoucanseeinFigure14-2,thequeryretrievestheexplicitpermissionsgrantedto
anddeniedfromthejackandjilldatabaseprincipals.Thesepermissionsareshown
foreachobjectalongwithinformationabouttheobjectsthemselves.Thissimpleexample
canbeexpandedtoperformadditionaltasks,suchasscriptingobjectpermissions.
TipExplicitpermissionsarepermissionsexplicitlygrantedordeniedthroughT-SQL
GRANT,DENY,andREVOKEstatements.Theeffectivepermissionsofaprincipalarea
combinationoftheprincipal’sexplicitpermissions,permissionsinheritedfromtherolesor
groupstowhichtheprincipalbelongs,andpermissionsimpliedbyotherpermissions.You
canusethesys.fn_my_permissionssystemfunctiontoviewyoureffective
permissions.
DynamicManagementViewsand
Functions
Inadditiontocatalogviews,SQLServer2014providesmorethan204DMVsandDMFs
thatgiveyouaccesstointernalserver-stateinformation.DMVsandDMFsaredesigned
specificallyforthebenefitofdatabaseadministrators(DBAs),buttheycanalsoprovide
developerswithextremelyusefulinsightsintotheinternalworkingsofSQLServer.
Havingaccesstothisserver-stateinformationcanenhancetheserver-managementand-
administrationexperienceandhelptoidentifypotentialproblemsandperformanceissues
(forwhichdevelopersareincreasinglysharingresponsibility).
SQLServerprovidesDMVsandDMFsthatarescopedatthedatabaselevelandatthe
serverlevel.AllDMVsandDMFsareinthesysschema,andtheirnamesallstartwith
dm*.ThereareseveralcategoriesofDMVsandDMFs,withmostbeinggroupedtogether
usingstandardnameprefixes.Table14-1listssomeofthemostcommonlyused
categories.ThemajorityofthenewsystemviewsrelatedtoSQLServer2014memory-
optimizedtablescontaintheabbreviation%xtp%intheobjectname.
Table14-1.CommonlyUsedDMVandDMFCategories
Names Description
sys.dm_cdc_* ContainsinformationaboutChangeDataCapture(CDC)transactionsandlog
sessions
sys.dm_exec_* Returnsinformationrelatedtousercodeexecution
sys.dm_fts_* Retrievesinformationaboutintegratedfull-textsearch(iFTS)functionality
sys.dm_os_* Displayslow-leveldetailssuchaslocks,memoryusage,andscheduling
sys.dm_tran_* Providesinformationaboutcurrenttransactionsandlockresources
sys.dm_io_* AllowsyoutomonitornetworkanddiskI/O
sys.dm_db_* Returnsinformationaboutdatabasesanddatabase-levelobjects
sys.dm_db_xtp* Returnsinformationaboutdatabase-levelmemory-optimizedobjects(newinSQL
Server2014)
sys.dm_xtp* Returnsinformationrelatedtomemory-optimizedobjects(newinSQLServer2014)
Chapter5gaveanexampleofDMVandDMFusagewithanSPthatextracts
informationfromtheSQLServerquery-plancache.Thissectionexploresmoreusesfor
DMVsandDMFs.
IndexMetadata
SQLServermetadataisusefulforperformingtediousadministrativetaskslikeidentifying
potentialperformanceissues,updatingstatistics,andrebuildingindexes.Creatinga
customizedproceduretoperformthesetasksgivesyoutheabilitytocreatescriptsthatare
flexibleandtargetthemaintenancetasksbeingperformed,whichisn’tanoptionavailable
withthestandardmaintenanceplan.Listing14-4usescatalogviewstoidentifyalltables
intheAdventureWorksdatabasewithclusteredornonclusteredindexesdefinedonthem.
TheprocedurethengeneratesT-SQLALTERINDEXstatementstorebuildalltheindexes
definedonthesetablesandalsoupdatesthestatisticsandrecompilesstoredprocedures
andtriggers.Wehavekeptthisexamplefairlysimple,althoughitcanbeusedasabasis
formorecomplexindex-rebuildingproceduresthatmakedecisionsbasedonvarious
scenarioslikerebuildingindexesforallthedatabasesintheserverandthatalsoconsider
factorssuchasLOBtoreindextheobjects.Figure14-3showstheALTERINDEX
statementscreatedbytheprocedure.
Listing14-4.StoredProceduretoRebuildTableIndexes
CREATEPROCEDUREdbo.RebuildIndexes
@dbsysname='Adventureworks',
@onlinebit=1,
@maxfragint=10,
@rebuildthresholdint=30,
@WeekdayRebuildOfflineint=1
AS
BEGIN;
SETNOCOUNTON;
DECLARE
@objectidint,
@indexidint,
@indextypenvarchar(60),
@schemanamenvarchar(130),
@objectnamenvarchar(130),
@indexnamenvarchar(130),
@fragfloat,
@sqlcommandnvarchar(4000);
--Selecttablesandindexesfromthe
--sys.dm_db_index_physical_statsfunctionbasedonthe
thresholddefined
SELECT
object_idASobjectid,
index_idASindexid,
index_type_descASindextype,
avg_fragmentation_in_percentASfrag
INTO
#reindexobjects
FROM
sys.dm_db_index_physical_stats(DB_ID(@db),NULL,NULL,
NULL,'LIMITED')
WHERE
avg_fragmentation_in_percent>@maxfrag
ANDindex_id>0
--Declarethecursorforthelistofobjectstobe
processed.
DECLAREobjectsCURSORFOR
SELECTo.*FROM#reindexobjectso
INNERJOINsys.indexesiONi.object_id=o.objectid
WHEREi.is_disabled=0ANDi.is_hypothetical=0;
--Openthecursor.
OPENobjects;
WHILE(1=1)
BEGIN;
FETCHNEXTFROMobjectsINTO@objectid,@indexid,
@indextype,@frag;
IF@@FETCH_STATUS<0BREAK;
SELECT@objectname=QUOTENAME(o.name),@schemaname
=QUOTENAME(s.name)
FROMsys.objectsASo
JOINsys.schemasASsONs.schema_id=o.schema_id
WHEREo.object_id=@objectid;
SELECT@indexname=QUOTENAME(name)
FROMsys.indexes
WHEREobject_id=@objectidANDindex_id=@indexid;
SET@sqlcommand=N'ALTERINDEX'+@indexname+N'ON
'+
@schemaname+N'.'+@objectname;
IF@frag>@rebuildthreshold
BEGIN;
SET@sqlcommand=@sqlcommand+N'REBUILD';
IF(DATEPART(WEEKDAY,GETDATE())<>
@WeekdayRebuildOffline)
AND((@indextypeLike'HEAP')OR(@indextypelike
'%CLUSTERED%'))
SET@sqlcommand=@sqlcommand+N'WITH(ONLINE
=ON)';
END;
ELSE
SET@sqlcommand=@sqlcommand+N'REORGANIZE';
PRINTN'Executing:'+@sqlcommand;
EXEC(@sqlcommand);
END;
--Closeanddeallocatethecursor.
CLOSEobjects;
DEALLOCATEobjects;
--UPDATESTATISTICS&SP_RECOMPILE
DECLAREtablelistCURSORFOR
SELECTdistinctOBJECT_NAME(o.objectid)FROM
#reindexobjectso;
--Openthecursor.
OPENtablelist;
FETCHNEXTFROMtablelistINTO@objectname;
--Loopthroughthepartitions.
WHILE@@FETCH_STATUS=0
BEGIN;
--UpdateStatistics
SET@sqlcommand='UPDATESTATISTICS'+@objectname;
PRINTN'Executing:'+@sqlcommand;
EXEC(@sqlcommand);
--RecompileStoredProceduresandTriggers
SET@sqlcommand='EXECsp_recompile'+@objectname;
PRINTN'Executing:'+@sqlcommand;
EXEC(@sqlcommand);
FETCHNEXTFROMtablelistINTO@objectname;
END;
CLOSEtablelist;
DEALLOCATEtablelist;
DROPTABLE#reindexobjects;
END;
GO
Figure14-3.ALTERINDEXstatementstorebuildindexesonAdventureWorkstables
TheprocedureinListing14-4usestheDMV
sys.dm_db_index_physical_statstoretrievealistofalltablesinthedatabase
thathaveindexesdefinedonthembasedonthethresholdsdefinedforfragmentation:
SELECT
object_idASobjectid,
index_idASindexid,
index_type_descASindextype,
avg_fragmentation_in_percentASfrag
INTO
#reindexobjects
FROM
sys.dm_db_index_physical_stats(DB_ID(@db),NULL,NULL,
NULL,'LIMITED')
WHERE
avg_fragmentation_in_percent>@maxfrag
ANDindex_id>0
Theprocedurethenusesthecursortoloopthroughtheactiveindexes.Dependingon
theindex-rebuildthresholds,theproceduredetermineswhethertheindexhastoberebuilt
orreorganized.Theprocedurealsotakesintoconsiderationwhethertheprocesscanbe
performedonlineoroffline,basedonthedayoftheweek.Forexample,youmayconsider
rebuildingtheindexofflineduringweekendswhenthedatabaseisn’ttooactive.The
procedurethenexecutesALTERINDEXstatementsforeachindex:
DECLAREobjectsCURSORFOR
SELECTo.*FROM#reindexobjectso
INNERJOINsys.indexesiONi.object_id=o.objectid
WHEREi.is_disabled=0ANDi.is_hypothetical=0;
--Openthecursor.
OPENobjects;
WHILE(1=1)
BEGIN;
FETCHNEXTFROMobjectsINTO@objectid,@indexid,
@indextype,@frag;
IF@@FETCH_STATUS<0BREAK;
SELECT@objectname=QUOTENAME(o.name),@schemaname
=QUOTENAME(s.name)
FROMsys.objectsASo
JOINsys.schemasASsONs.schema_id=o.schema_id
WHEREo.object_id=@objectid;
SELECT@indexname=QUOTENAME(name)
FROMsys.indexes
WHEREobject_id=@objectidANDindex_id=@indexid;
SET@sqlcommand=N'ALTERINDEX'+@indexname+N'ON'
+
@schemaname+N'.'+@objectname;
IF@frag>@rebuildthreshold
BEGIN;
SET@sqlcommand=@sqlcommand+N'REBUILD';
IF(DATEPART(WEEKDAY,GETDATE())<>
@WeekdayRebuildOffline)
AND((@indextypeLike'HEAP')OR(@indextypelike
'%CLUSTERED%'))
SET@sqlcommand=@sqlcommand+N'WITH(ONLINE
=ON)';
END;
ELSE
SET@sqlcommand=@sqlcommand+N'REORGANIZE';
PRINTN'Executing:'+@sqlcommand;
EXEC(@sqlcommand);
END;
--Closeanddeallocatethecursor.
CLOSEobjects;
DEALLOCATEobjects;
Next,theprocedureusesthecursortoloopthroughtheobjects,updatesthestatistics,
andrecompilesthestoredproceduresandtriggers:
DECLAREtablelistCURSORFOR
SELECTdistinctOBJECT_NAME(o.objectid)FROM
#reindexobjectso;
--Openthecursor.
OPENtablelist;
FETCHNEXTFROMtablelistINTO@objectname;
--Loopthroughthepartitions.
WHILE@@FETCH_STATUS=0
BEGIN;
--UpdateStatistics
SET@sqlcommand='UPDATESTATISTICS'+@objectname;
PRINTN'Executing:'+@sqlcommand;
EXEC(@sqlcommand);
--RecompileStoredProceduresandTriggers
SET@sqlcommand='EXECsp_recompile'+@objectname;
PRINTN'Executing:'+@sqlcommand;
EXEC(@sqlcommand);
FETCHNEXTFROMtablelistINTO@objectname;
END;
CLOSEtablelist;
DEALLOCATEtablelist;
Theprocedurethencleansupthetemporaryobjectsthatwerecreated:
DROPTABLE#reindexobjects;
SessionInformation
Thesys.dm_exec_sessionsDMVreturnsonerowpersessionontheserver.The
informationreturnedissimilartothatreturnedbythesp_who2systemSP.Youcanuse
thisDMVtoretrieveinformationthatincludesthedatabaseID,sessionID,loginname,
clientprogramname,CPUtimeandmemoryusage,transactionisolationlevel,and
sessionsettingslikeANSI_NULLSandANSI_PADDING.Listing14-5isasimplequery
againstthesys.dm_exec_sessionsDMV.PartialresultsareshowninFigure14-4.
Listing14-5.RetrievingSessionInformation
SELECT
db_name(database_id)dbname,
session_id,
host_name,
program_name,
client_interface_name,
login_name,
cpu_time,
CASEWHENansi_nulls=0THEN'OFF'ELSE'ON'END
ansi_nulls,
CASEWHENansi_padding=0THEN'OFF'ELSE'ON'END
ansi_padding
FROMsys.dm_exec_sessions;
Figure14-4.Retrievingsessioninformationwithsys.dm_exec_sessions
Youcanalsousesys.dm_exec_sessionstoretrievesummarizedinformation
aboutsessions.Listing14-6presentssummaryinformationforeverycurrentsessionon
theserver.TheresultsareshowninFigure14-5.
Listing14-6.RetrievingSummarizedSessionInformation
SELECT
login_name,
SUM(cpu_time)AStot_cpu_time,
SUM(memory_usage)AStot_memory_usage,
AVG(total_elapsed_time)ASavg_elapsed_time,
SUM(reads)AStot_reads,
SUM(writes)AStot_writes,
SUM(logical_reads)AStot_logical_reads,
COUNT(session_id)astot_sessions
FROMsys.dm_exec_sessionsWHEREsession_id>50
GROUPBYlogin_name;
Figure14-5.Summarysessioninformation
ConnectionInformation
Inadditiontosessioninformation,youcanretrieveconnectioninformationviathe
sys.dm_exec_connectionsDMV.ThisDMVreturnsconnectioninformationfor
everysessionwithasessionidgreaterthan50(valuesof50andbelowareused
exclusivelybytheserver).Listing14-7usestheDMVtoretrieveconnectioninformation;
theresultsareshowninFigure14-6.NoticethatthisDMValsoreturnsclientnetwork
address,port,andauthenticationschemeinformationwithnofuss.
Listing14-7.RetrievingConnectionInformation
SELECT
Session_id,
client_net_address,
auth_scheme,
net_transport,
client_tcp_port,
local_tcp_port,
connection_id
FROMsys.dm_exec_connections;
Figure14-6.ConnectioninformationretrievedviaDMV
CurrentlyExecutingSQL
Thesys.dm_exec_requestsDMVallowsyoutoseeallcurrentlyexecutingrequests
onSQLServer.WhenyoucombinetheDMVsys.dm_exec_requestswith
sys.dm_exec_sessions,youcangetinformationabouttheSQLstatementsthatare
executingatthatpointintimeandwhetherthesessionisbeingblocked.Youcanusethese
DMVstoreturnthedetailsofcurrentlyexecutingSQL,asshowninListing14-8.Partial
resultsareshowninFigure14-7.
TipThesys.dm_exec_requestsDMVcanbeusedtoretrieveadditional
informationforcurrentlyexecutingrequestsforCPUtime,reads,writes,andtheamount
ofgrantedmemory,amongothers.Theinformationreturnedissimilartowhatisreturned
bythesys.dm_exec_sessionsDMVdescribedpreviouslyinthissection,butona
per-requestbasisinsteadofaper-sessionbasis.
Listing14-8.QueryingCurrentlyExecutingSQLStatements
SELECT
s.session_id,
r.request_id,
r.blocking_session_id,
DB_NAME(r.database_id)asdatabase_name,
r.[user_id],
r.statusASrequest_status,
s.statusASsession_status,
s.login_time,
s.is_user_process,
ISNULL(s.[host_name],'')AS[host_name],
ISNULL(s.[program_name],'')AS[program_name],
ISNULL(s.login_name,'')ASlogin_name,
ISNULL(r.wait_type,'')ASwait_type,
ISNULL(r.last_wait_type,'')ASlast_wait_type,
ISNULL(r.wait_resource,'')ASwait_resource,
r.transaction_id,
r.open_transaction_count,
r.cpu_timeASrequest_cpu_time,
r.logical_readsASrequest_logical_reads,
r.readsASrequest_reads,
r.writesASrequest_writes,
r.total_elapsed_timeASrequest_total_elapsed_time,
r.start_timeASrequest_start_time,
r.wait_timeASrequest_wait_time,
s.memory_usage,
s.cpu_timeASsession_cpu_time,
s.total_elapsed_timeASsession_total_elapsed_time,
s.last_request_start_timeAS
session_last_request_start_time,
s.last_request_end_timeASsession_last_request_end_time,
r.command,
r.sql_handle
FROMsys.dm_exec_sessionss
LEFTOUTERMERGEJOINsys.dm_exec_requestsr
ONs.session_id=r.session_id
WHEREr.session_id<>@@SPIDAND
((r.session_idISNOTNULLAND(s.is_user_process=1OR
r.statusNOTIN('background','sleeping')))OR
(s.session_idIN(SELECTDISTINCTblocking_session_id
FROMsys.dm_exec_requestsWHEREblocking_session_id!=0)))
OPTION(FORCEORDER);
Figure14-7.CurrentlyexecutingSQLstatements
TheprocedureinListing14-8usessys.dm_exec_sessionstoretrievethe
sessiondetailsandsys.dm_exec_requeststoretrievetherequeststatistics.The
fieldsession_idreturnstheIDforthecurrentsessionthatisbeingexecuted,and
blocking_session_idreturnstheheadblocker.Ifthequeryisn’tbeingblocked,
blocking_session_idis0.
Thequeryfilterthenreturnsallactivesessions.Ifthereisblockingforasession,the
queryfilteralsoreturnstheheadblocker,evenifthesessionisinactive:
((r.session_idISNOTNULLAND(
s.is_user_process=1ORr.status
NOTIN('background','sleeping')))OR
(s.session_idIN(
SELECTDISTINCTblocking_session_id
FROMsys.dm_exec_requests
WHEREblocking_session_id!=0)))
ThequeryhintOPTION(FORCEORDER)hasbeenaddedtosuppresswarning
messages.
AsyoucanseeintheresultsshowninFigure14-7,thereweretwoactivesessionsin
theSQLServer2014instancewhenweranthisquery.SessionID67isblockedbysession
ID65,andtherequest_wait_timefieldreturnsthewaittimeforsessionID67
(whichiscurrentlyblocked)inmilliseconds.Youcanreviewthecolumnswait_type
andwait_resourcetounderstandwhatthesessioniswaitingonandresolvethe
blockingissue.Ifyouhavemoreactivesessionsinyourserver,thequerywillreportthem
all.
Memory-OptimizedSystemViews
SQLServer2014introducesaseriesofnewviewstoassistwiththemanagementof
memory-optimizedobjects;seeTable14-2.Thesesystemviewsallowyoutobetter
monitormemoryusage,garbagecollection,indexusage,andtransactionstatisticsrelated
tomemory-optimizedobjects.Disk-basedtableshaveacounterpartviewthatletsyou
monitordisk-basedtablesinasimilarfashion.
Table14-2.Memory-OptimizedSystemViews
SystemView Description
dm_db_xtp_checkpoint_files Displaysinformationaboutcheckpointfiles,including
filesize,physicallocation,state,andlsninformation
dm_db_xtp_checkpoint_stats ReturnsstatisticsaboutIn-MemoryOLTPcheckpoint
operationsinthecurrentdatabase
dm_db_xtp_gc_cycle_stats
Outputsthecurrentstateofcommittedtransactionsthat
havedeletedoneormorerowsfromthegarbage-
collectioncycles
dm_db_xtp_hash_index_stats Returnsstatisticsthatareusefulforunderstanding,
managing,andtuninghashindexbucketcounts
dm_db_xtp_index_stats
Containsstatisticscollectedsincethelastdatabase
restart,specificallytrackingmemory-optimizedobjects
thataren’ttrackedinothersystemviews
dm_db_xtp_memory_consumers Returnsonerowofmemoryinformationabout
memory-optimizedobjects,whichthedatabaseengine
usesatagranularlevel
dm_db_xtp_merge_requests
Monitorsdatabasemergerequeststhatweregenerated
bySQLServerormanuallytriggeredusingthe
sys.sp_xtp_merge_checkpoint_files
systemprocedure
dm_db_xtp_nonclustered_index_stats
Returnsstatisticsabouttheusageofnonclustedindexes
inmemory-optimizedtablesinthecurrentdatabase.
Thestatisticsareresetafteradatabaserestart,because
in-memoryobjectsarerecreatedafterarestart
dm_db_xtp_object_stats
Monitorsthenumberofoperationsmadeagainstin-
memorytables,regardlessofthesuccessorfailureof
theoperation
dm_db_xtp_table_memory_stats Returnsthememoryusageforeachtableandindex
createdinmemory,expressedinKB
dm_db_xtp_transactions Returnsthecurrentactivetransactionsforin-memory
objects
dm_xtp_gc_queue_stats
Returnsinformationaboutthegarbage-collection
workerqueueprocessandstatistics,per
schedulers/coresonthemachine
dm_xtp_gc_stats
Returnsoverallstatisticsinformationaboutthecurrent
behavioroftheIn-MemoryOLTPgarbage-collection
process
dm_xtp_system_memory_consumers Returnssystem-levelmemoryconsumersforin-
memoryOLTP,expressedinbytes
dm_xtp_transaction_stats Returnsinformationaboutthetransactionsthathaverun
sincetheserverstarted
MostExpensiveQueries
Thesys.dm_exec_query_statsDMVallowsyoutoseetheaggregated
performancestatisticsforthecachedqueryplans.ThisDMVcontainsonerowforeach
queryplan;ithasmorethanonerowforstoredprocedurescontainingmultiplestatements.
YoucanusethisDMVinconjunctionwithsys.dm_exec_sql_text,whichshows
theSQLstatementtextbasedontheSQLhandle,andsys.dm_exec_query_plan,
whichshowstheshowplaninanXMLformattoretrievethemostexpensivequeriesfor
thecachedqueryplansintheserver(seeListing14-9).PartialresultsareshowninFigure
14-8.Youcanusethecolumnsmin_rows,max_rows,total_rows,and
last_rowstoanalyzetherowstatisticsforthequeryplansinceitwaslastcompiled.
Forexample,ifyouhavealong-runningquery,andyou’retryingtoanalyzethecausefor
thequery’sslowness,thisinformationwillhelpyoutounderstandthemaximumnumber
ofrowsandaveragenumbersofrowsreturnedbythequeryovertimeandtotunethe
query.
Listing14-9.QueryingtheMostExpensiveQueries
SELECT
DB_Name(qp.dbid)AS[DB],
qp.dbidAS[DBID],
qt.text,
SUBSTRING(qt.TEXT,
(qs.statement_start_offset/2)+1,
((CASEqs.statement_end_offset
WHEN-1
THENDATALENGTH(qt.TEXT)
ELSEqs.statement_end_offset
END-qs.statement_start_offset)/2)+1)AS
stmt_text,
qs.execution_count,
qs.total_rows,
qs.min_rows,
qs.max_rows,
qs.last_rows,
qs.total_logical_reads/qs.execution_countAS
avg_logical_reads,
qs.total_physical_reads/qs.execution_countAS
avg_physical_reads,
qs.total_logical_writes/qs.execution_countASavg_writes,
(qs.total_worker_time/1000)/qs.execution_countAS
avg_CPU_Time_ms,
qs.total_elapsed_time/qs.execution_count/1000AS
avg_elapsed_time_ms,
qs.last_execution_time,
qp.query_planAS[Plan]
FROMsys.dm_exec_query_statsqs
CROSSAPPLYsys.dm_exec_sql_text(qs.sql_handle)qt
CROSSAPPLYsys.dm_exec_query_plan(qs.plan_handle)qp
ORDERBY
execution_countDESC,qs.total_logical_readsdesc,
total_rowsdesc;
Figure14-8.Mostexpensivequeries
YoucanusetheDMVsys.dm_exec_query_statsand
sys.dm_exec_sql_texttoviewthequeriesthatareblockedintheserver,asshown
inListing14-10.PartialresultsareshowninFigure14-9.
Listing14-10.QueryingtheMost-BlockedQueries
SELECTTOP50
(total_elapsed_time-total_worker_time)
/qs.execution_countASaverage_time_blocked,
total_elapsed_time-total_worker_timeAS
total_time_blocked,
qs.execution_count,
qt.textblocked_query,
DB_NAME(qt.dbid)dbname
FROMsys.dm_exec_query_statsqs
CROSSAPPLYsys.dm_exec_sql_text(qs.sql_handle)qt
ORDERBYaverage_time_blockedDESC;
Figure14-9.Most-blockedqueries
AsyoucanseeinFigure14-9,thedbnamefieldliststhedatabasenameforsome
queriesanddoesn’treturnthedatabasenameforotherqueries.Thereasonisthat
sql_handleidentifiesonlythetextthatisbeingsubmittedtotheserver.Becauseonly
thetextissubmittedtotheserver,thequerytextmaybegenericenoughthatitcanbe
submittedtomultipledatabases;andinthiscase,sql_handlecan’tidentifythe
databasename.However,ifastoredprocedureresidesinadatabase,thedatabasename
canbeidentifiedandretrieved.InFigure14-9,ifyoulookattherows1and4,youcansee
thatbothqueriesreferencethesameselectstatement—thedifferenceisthatrow4uses
astoredprocedure,whereasrow1usesabatchSQLquery.Thedatabasenamewas
retrievedforrow4,butforrow1itwasn’t.
TempdbSpace
ThetempdbsystemdatabaseholdsapositionofprominenceforDBAs.Thetempdb
databaseconstitutesaglobalserver-wideresourcesharedbyallsessions,connections,and
databasesfortemporarystorageonasingleSQLServerinstance.Animproperlymanaged
tempdbcanbringaSQLServerinstancetoitsknees.Listing14-11demonstratesa
simpleusageofsys.dm_db_file_space_usagetoreportfreeandusedspacein
tempdb.Thedatabase_idforthesystemdatabasetempdbis2.Theresultsare
showninFigure14-10.
Listing14-11.QueryingFreeandUsedSpaceintempdb
SELECT
db_name(database_id)ASDatabase_Name,
SUM(unallocated_extent_page_count)ASfree_pages,
SUM(unallocated_extent_page_count)*8.0ASfree_KB,
SUM(user_object_reserved_page_count)ASuser_object_pages,
SUM(user_object_reserved_page_count)*8.0AS
user_object_pages,
SUM(internal_object_reserved_page_count)AS
internal_object_pages,
SUM(internal_object_reserved_page_count)*8.0AS
internal_object_KB
FROMsys.dm_db_file_space_usage
WHEREdatabase_id=2
GROUPBYdatabase_id;
Figure14-10.Freeandusedspaceintempdb
Thetempdbcanrunoutofspaceforvariousreasons—perhapstheobjectscreatedin
thetempdbhaven’tbeendropped,ortheapplicationisperformingsortoperationsthat
takeupallthespaceallocatedforthetempdb.Whentroubleshootingtempdbspace
usage,it’simportanttounderstandspaceallocationfortheobjectsthatcurrentlyresidein
thetempdb.Inadditiontothesys.dm_db_file_space_usageDMV,SQLServer
2014providesthesys.dm_db_partition_statsDMV,whichreturnsdetailed
allocationspertable.ThisDMVreturnsresultsbasedontheexecutiondatabasecontext.
TheDMVreturnsdetailsabouthowmuchspacehasbeenreservedforthein-row,LOB
dataandvariable-lengthdata;therow-overflowdataandhowmuchhasbeenused;and
therowcount.Ifthetableisn’tpartitioned,thenthepartitionnumberisreturnedas
1.Listing14-12demonstratesasimpleusageofsys.dm_db_partition_statsto
reporttheuserobjectsinthetempdbandthedetailsoftherowcount,reservedpages,
usedpages,andindextype.Figure14-11showspartialresultsetsforthequery.
Listing14-12.QueryingUserObjectAllocationsintempdb
SELECTobject_name(o.object_id)ASObject,
CASE
WHENindex_id=0then'heap'
WHENindex_id=1then'clusteredindex'
WHENindex_id>1then'nonclusteredindex'
ENDASIndexType,
SUM(reserved_page_count)ASReservedPages,
SUM(used_page_count)ASUsedPages,
SUM(casewhen(index_id<2)thenrow_countelse0end)
ASRows
FROMsys.dm_db_partition_statspJOINsys.objectsoON
p.object_id=o.object_id
WHEREtype_desc='USER_TABLE'
GROUPBYo.object_id,index_id
ORDERBYsum(used_page_count)DESC;
Figure14-11.Userobjectallocationsintempdb
Inaddition,youcanusetheDMV’ssys.dm_db_session_space_usageand
sys.dm_db_task_space_usagetoreturndetailsabouttempdbspaceusagebased
onaspecificsessionortasktofurthernarrowthespecificoffenderthatconsumesmost
tempdbspace.Listing14-13usesthesys.dm_db_session_space_usageand
sys.dm_db_task_space_usageDMVstoreturnthesession_id,therequest
associatedwiththesession,andtheobjectpageallocation.Figure14-12showsapartial
resultset.
Listing14-13.QueryingUserObjectAllocationsinthetempdbperSession
SELECTs.session_id,request_id,
SUM(s.internal_objects_alloc_page_count+
t.internal_objects_alloc_page_count)*8.0AS
internal_obj_pages_kb,
SUM(s.user_objects_alloc_page_count)asuser_obj_pages
FROMsys.dm_db_session_space_usagesJOIN
sys.dm_db_task_space_usaget
ONs.session_id=t.session_id
GROUPBYs.session_id,request_id;
Figure14-12.Userobjectallocationsintempdbwithsessiondata
ServerResources
Thesys.dm_os*DMVsandfunctionsallowyoutoquerydetailedinformationabout
yourserverandresources.Thisisusefulforretrievingtheserverrestarttimeormachine
andconfigurationdetailssuchaswhetheryou’reusinghyperthreading.The
sys.dm_os_sys_infoDMVreturnsdetailsaboutserverresources,informationabout
whethertheSQLServerinstanceisphysicalorvirtual,anddetailsofthevirtualization
environment.Thevalueinthecolumnvirtual_machine_type_desccanbeNone,
Hypervisor,orOther.Nonemeanstheserverisphysical,andHypervisormeansthe
instanceisrunninginthehypervisor.
Listing14-14retrievesserverconfigurationinformation,includingthenumberof
logicalCPUsontheserver,theratiooflogicaltophysicalCPUs,physicalandvirtual
memoryavailabletotheserver,thelastserverrestarttime,andthehyperthreadingratio.
TheresultsareshowninFigure14-13.
Listing14-14.RetrievingLow-levelConfigurationInformation
SELECT
cpu_countASlogical_CPUs,
hyperthread_ratio,
physical_memory_kb/1048576.00ASphysical_MB,
virtual_memory_kb/1048576.00ASvirtual_MB,
sqlserver_start_time,
virtual_machine_type_desc
FROMsys.dm_os_sys_info;
Figure14-13.Serverconfigurationdetails
AnotherusefulDMV,sys.dm_os_volume_stats,returnsvolumeinformation
forthemountpointsaswell.Youcanchecktoseewhetherthevolumeattributeisread-
onlyorgetthespaceutilizationbeforeperformingabulkoperation.Checkingthevolume
attributecancomeinhandywhenyouworkwiththeScalableSharedDatabase(SSD).
SSDletsyouattacharead-onlyvolumetomultipleSQLServerinstancestohelpscaleout
thedatabase.
Listing14-15demonstratesasimplequerythatliststhevolumeinformationforall
databasesincludingthedatabasename,filename,andvolumeIDandmountpoints,along
withthespaceused.PartialresultsareshowninFigure14-14.
Listing14-15.ReturningVolumeInformationforAllDatabases
SELECT
DB_NAME(f.database_id)ASDBName,
f.nameASFileName,
volume_mount_point,
volume_id,
logical_volume_name,
total_bytes,
available_bytes,
CAST(CAST(available_bytesASFLOAT)/CAST(total_bytesAS
FLOAT)ASDECIMAL(18,1))*100AS[SpaceUsed%],
v.is_read_only
FROMsys.master_filesf
CROSSAPPLYsys.dm_os_volume_stats(f.database_id,
f.file_id)v
ORDERBYf.database_idDESC;
Figure14-14.Returningvolumeinformationforalldatabases
WhentheSQLServerprocesscreatesadumpfileorminidumps,youhavetobrowse
throughtheSQLServererrorlogstolocatethedumpfileandstartinvestigatingtheissue.
Tofacilitateyourlocatingthedumpfile,SQLServer2012introducedaDMVcalled
sys.dm_server_memory_dumps.ThisDMVstoresalltheSQLServerdumpsso
thatyoucaneasilylocatethedumpfilepathalongwiththefile’sname,size,andcreation
date.
Listing14-16demonstratesaquerythatliststhedetailsoftheSQLdumps;theresults
areshowninFigure14-15.YoucanseethattheserverhastwoSQLminidumps;thepath
tothedumpsandthecreationtimemakeitsimpletolocatethedumpfiles.Youcanalso
correlatethedumpstotheapplicationlogfilestodeterminethecodethatcausedeach
dump.
Listing14-16.ListingSQLServerDumps
select*fromsys.dm_server_memory_dumps
Figure14-15.ReturningSQLServerdumpdetails
AnotherusefulDMVissys.dm_server_registry,whichlistsallSQLServer
registrysettings.Forexample,supposeyou’recallingCLRproceduresinthecode,and
youwanttocheckwhethertraceflag6527isnotenabledfortheSQLServerinstanceso
thatyoucanmakesureSQLServerwillgeneratememorydumponthefirstoccurrenceof
anout-of-memoryexception.ThisDMVmakesiteasierforyoutoperformthatcheck.
Listing14-17demonstratesthequeryusage,andFigure14-16showsapartialresultset.
Listing14-17.ListingSQLServerInstanceRegistrySettings
select*fromsys.dm_server_registry
Figure14-16.ReturningSQLServerinstanceregistrykeysandvalues
UnusedIndexes
Anotherimportantaspectofmanagingadatabaseisdeterminingwhichindexesareused
andwhichonesaren’t.Indexesconsumestoragespace,andthequeryoptimizerusesthem
toefficientlyaccessdataaswell.Ifanindexisn’tbeingused,thenthestoragespacethatis
beingconsumedbythatindexisanoverhead.SQLServerprovidesthe
sys.dm_db_index_usage_statsDMVtoreportwhichindexeshavebeenused
sincetheSQLServerservicewaslaststarted.Whenaqueryaccessestheindexes,the
objectiveistoseek.Iftheindexhasahighnumberofuser_scans,thenit’sacandidate
fortuningsoanindexseekcantakeplace.Iftheindexhasahighnumberofupdatesand
fewornoseeks,lookups,orscans,youcansafelyassumethattheindexisn’tbeingused,
andhenceitcanberemoved.
Listing14-18presentsaquerythatlistsallindexesthathaven’tbeenusedsincethe
servicewaslastrestartedfortheAdventureWorksdatabase.Partialresultsareshownin
Figure14-17.
Listing14-18.ListingUnusedIndexes
USEAdventureWorks;
SELECT
DB_NAME()ASDatabaseName,
OBJECT_SCHEMA_NAME(i.object_id,s.database_id)AS
SchemaName,
OBJECT_NAME(i.object_id)ASTableName,
i.nameASIndexName,
user_updates,
user_seeks,
user_scans,
user_lookups,
system_updates,
last_user_seek,
last_user_update
FROMsys.indexesi
LEFTJOINsys.dm_db_index_usage_statssONs.object_id
=i.object_idANDi.index_id=s.index_id
WHEREs.database_id=DB_ID()
ORDERBYlast_user_updateDESC;
Figure14-17.Indexesthathaven’tbeenusrdRecently
AsyoucanseeinFigure14-17,thequeryreturnsindex-usagedetailsforthetableand
thecorrespondingindex.user_scansreturnsthenumberoftimestheindexhasbeen
scanned.user_seeksreturnsthenumberoftimesindexseekshavetakenplace.
user_lookupsreturnsthenumberoftimestheindexhasbeenusedinbookmark
lookups.user_updatesreturnsthenumberoftimestheindexhasbeenupdated,and
system_updatesreturnsthenumberoftimestheindexwasupdatedbythesystem.In
thefigure,youcanseethattheindexesAK_Product_Nameand
IX_vProductAndDescriptionhaveuser_updatesbutno
user_seeks/scans/lookups,whichmeanstheseindexeshaven’tbeenusedsince
thelastsystemrestart.
Althoughtheindexeslistedbythisqueryhaven’tbeenusedsincethelastrestart,that’s
noguaranteethattheywon’tbeusedinthefuture.Insteadofdeletingtheindexbasedon
thequeries,ifyougatherindexusageinformationlikethisonaregularbasis,youcan
developapictureofindexusagepatterns.Youcanusethisinformationtooptimize
existingindexesandredesignordropirrelevantindexes.
WaitStats
Finally,let’slookatoneoftheDMVsthatwillhelpyouquicklynarrowdownIO,CPU,
network,locking,ormemoryperformanceissues.Thesys.dm_os_wait_stats
DMVcanhelpyouunderstandwhySQLServerhasbeenwaitingforaresourcesincethe
serverwasrestarted.Forexample,yourapplicationteammaynoticeaperformanceissue
andconcludethatmultipleprocessesareblockingeachother;however,therealissue
couldbethedelayassociatedwiththelogcachebeingflushedtothedisk.
Listing14-19showsaquerytolistthetop20waitssincetheserverwasrestartedor
thestatisticswerecleared.PartialresultsareshowninFigure14-18.
Listing14-19.ListingtheTop20WaitTypesfortheSQLServerInstance
SELECTTOP20
wait_type,
wait_time_ms/1000wait_time_secs,
CONVERT(DECIMAL(12,2),wait_time_ms*100.0
/SUM(wait_time_ms)OVER())Per_waiting
FROMsys.dm_os_wait_stats
ORDERBYwait_time_msDESC;
Figure14-18.Top20waittypesfortheSQLServerinstance
INFORMATION_SCHEMAViews
INFORMATION_SCHEMAviewsprovideyetanothermethodofretrievingmetadatain
SQLServer2014.DefinedbytheSQL-92standard,INFORMATION_SCHEMAviews
providetheadvantageofbeingcross-platformcompatiblewithotherSQL-92-compliant
databaseplatforms.Oneofthemajordisadvantagesisthattheyleaveoutalotofplatform-
specificmetadatalikedetailedSQLCLRassemblyinformation.Also,unlikesomeofthe
catalogviewsthatareserverwide,allINFORMATION_SCHEMAviewsaredatabase
specific.TheINFORMATION_SCHEMAviewsarelistedinTable14-3.
Table14-3.INFORMATION_SCHEMAViews
Name Description
CHECK_CONSTRAINTS Returnsarowofdescriptiveinformationforeachcheckconstraintinthe
currentdatabase.
COLUMN_DOMAIN_USAGE Returnsarowofmetadataforeachcolumninthecurrentdatabasethat
hasanaliasdatatype.
COLUMN_PRIVILEGES
Returnsarowofinformationforeachcolumninthecurrentdatabase
withaprivilegethathasbeengrantedby,orgrantedto,thecurrentuser
ofthedatabase.
COLUMNS Returnsdescriptiveinformationforeachcolumnthatcanbeaccessedby
thecurrentuserinthecurrentdatabase.
CONSTRAINT_COLUMN_USAGE
Returnsonerowofmetadataforeachcolumninthecurrentdatabase
thathasaconstraintdefinedonit,oneachtable-typeobjectforwhich
thecurrentuserhaspermissions.
CONSTRAINT_TABLE_USAGE
Returnsonerowofinformationforeachtableinthecurrentdatabase
thathasaconstraintdefinedonitforwhichthecurrentuserhas
permissions.
DOMAIN_CONSTRAINTS
Returnsarowofdescriptiveinformationforeachaliasdatatypeinthe
currentdatabasethatthecurrentusercanaccessandthathasarule
boundtoit.
DOMAINS Returnsarowofdescriptivemetadataforeachaliasdatatypeinthe
currentdatabasethatthecurrentusercanaccess.
KEY_COLUMN_USAGE Returnsarowofmetadataforeachcolumnthatisconstrainedbyakey
forwhichthecurrentuserhaspermissionsinthecurrentdatabase.
PARAMETERS
Returnsarowofdescriptiveinformationforeachparameterforalluser-
definedfunctions(UDFs)andSPsthatcanbeaccessedbythecurrent
userinthecurrentdatabase.ForUDFs,theresultsalsocontainarow
withreturnvalueinformation.
REFERENTIAL_CONSTRAINTS
ReturnsarowofmetadataforeachFOREIGNKEYconstraintdefined
inthecurrentdatabase,onobjectsforwhichthecurrentuserhas
permissions.
ROUTINE_COLUMNS
Returnsarowofdescriptiveinformationforeachcolumnreturnedby
table-valuedfunctions(TVFs)definedinthecurrentdatabase.This
INFORMATION_SCHEMAviewonlyreturnsinformationaboutTVFs
forwhichthecurrentuserhasaccess.
ROUTINES ReturnsarowofmetadataforeachSPandfunctioninthecurrent
databasethatisaccessibletothecurrentuser.
SCHEMATA Returnsarowofinformationforeachschemadefinedinthecurrent
database.
TABLE_CONSTRAINTS
Returnsarowofmetadataforeachtableconstraintinthecurrent
databaseontable-typeobjectsforwhichthecurrentuserhas
permissions.
TABLE_PRIVILEGES Returnsarowofdescriptivemetadataforeachtableprivilegethatis
eithergrantedby,orgrantedto,thecurrentuserinthecurrentdatabase.
TABLES Returnsarowofmetadataforeachtableinthecurrentdatabasefor
whichthecurrentuserhaspermissions.
VIEW_COLUMN_USAGE
Returnsarowofinformationforeachcolumninthecurrentdatabase
thatisusedinaviewdefinition,onobjectsforwhichthecurrentuser
haspermissions.
VIEW_TABLE_USAGE
Returnsarowofinformationforeachtablethatthecurrentuserhas
permissionsforinthecurrentdatabase.Thetablesreturnedarethosefor
whichthecurrentuserhaspermissions.
VIEWS Returnsarowofmetadataforeachviewthatcanbeaccessedbythe
currentuserinthecurrentdatabase.
NoteSomeofthechangesmadeinSQLServer2012and2014canbreakbackward
compatibilitywithSQLServer2008,2005,or2000INFORMATION_SCHEMAviewsand
applicationsthatrelyonthem.AlsonotethatSQLServer6.5andearlierdon’timplement
INFORMATION_SCHEMAviews.CheckBOLforspecificchangeinformationifyour
applicationusesINFORMATION_SCHEMAandrequiresbackwardcompatibility.
RetrievingcolumninformationwiththeINFORMATION_SCHEMA.COLUMNSview
issimilartousingthesys.columnscatalogview.Listing14-20demonstratesthis,with
resultsshowninFigure14-19.
Listing14-20.RetrievingColumnDatawithINFORMATION_SCHEMA.COLUMNS
SELECT
c.COLUMN_NAME,
c.ORDINAL_POSITIONFROM
INFORMATION_SCHEMA.COLUMNScWHEREc.TABLE_SCHEMA='Person'
ANDc.TABLE_NAME='Person'ORDERBYc.ORDINAL_POSITION;
Figure14-19.ColumnmetadataretrievedviaINFORMATION_SCHEMA
INFORMATION_SCHEMAviewsareusefulforapplicationsthatrequirecross-
platformorhighlevelsofISOcompatibility.Becausethey’reISOcompliant,
INFORMATION_SCHEMAviewsdon’treportalotofplatform-specificmetadata.TheISO
standardhasalsonotkeptupwiththedemandforaccesstoserver-widemetadata,sothere
isnostandardserver-scopedequivalenttoINFORMATION_SCHEMA.
Summary
Thischapterhasdiscussedcatalogviews,whichallowyoutoquerydatabaseandserver-
widemetadata.Catalogviewsletyouretrievecomprehensiveinformationabout
databases,databaseobjects,anddatabaseconfiguration.Youalsosawsomescenariosfor
usingcatalogviewsandcodeexamplesthatdemonstratedtheirutility.
ThechapteralsointroducedDMVsandDMFs,whichprovideanamazinglevelof
detailedinsightintotheinnerworkingsofSQLServer.SQLServer2014supportsthe
DMVsandDMFsintroducedinSQLServer2005andintroducesseveralmoretosupport
SQLServer2014functionalitylikememory-optimizedtables.AlthoughDMVsandDMFs
aretargetedtofulfilltheneedsofDBAs,theinformationtheyprovidecanbevaluableto
developerswhoaretroubleshootingperformanceproblemsorotherissues.
Finally,thechapterbrieflydiscussedtheISOstandardINFORMATION_SCHEMA
metadataviews.TheINFORMATION_SCHEMAviewsprovidelessdetailthancatalog
viewsandarescopedatthedatabaselevelonly,buttheydoprovidetheadvantageof
cross-platformportabilitywhenthatisarequirement.Becausetheyhavetoconformtothe
ISOSQLstandard,however,theyleaveoutalotofusefulplatform-specificmetadata.
ThenextchapterdiscussesCLRintegrationandtheimprovementsthatwerefirst
introducedinServer2012.
EXERCISES
1. [Fillintheblank]“Metadata”isdefinedas“datathatdescribes
__________.”
2. [Fillintheblank]________provideinsightintodatabaseobjects
andserver-wideconfigurationoptions.
3. [Chooseone]Manycatalogviewsaredefinedusingwhatmodel?
a. Europeanmodel
b. Inheritancemodel
c. FirstIn,FirstOutmodel
d. Proceduralmodel
4. [True/False]Dynamicmanagementviewsandfunctionsprovide
accesstointernalSQLServerdatastructuresthatwouldbe
otherwiseinaccessible.
5. [Chooseallthatapply]Theadvantagesprovidedby
INFORMATION_SCHEMAviewsinclude:
a. ISOSQLstandardcompatibility
b. Accesstoserver-scopedmetadata
c. Cross-platformcompatibility
d. Operatingsystemconfigurationmetadata
CHAPTER15
.NETClientProgramming
Whichismoreimportant:anefficientdatabaseorawell-designedclientapplicationthat
connectstothedatabase?Inourestimation,they’rebothequallyimportant.Afterall,your
databasecanbeverywelldesignedandextremelyefficient,butthatwon’tmattertothe
enduseriftheclientapplicationtheyusetoconnecttoyourdatabaseisslowand
unresponsive.ThisbookfocusesonSQLServerserver-sidedevelopmentfunctionality,
butwe’vedecidedtotakeamomenttointroducesomeofthetoolsavailabletocreate
efficientSQLServerclientapplications.The.NETFramework,inparticular,offers
severaloptionstomakeSQLServer2014clientconnectivitysimpleandefficient.This
chapterdiscussesusingADO.NETandthe.NETSqlClientasabasisforbuildingyour
owneasy-to-use,cutting-edgeSQLServerclientapplications,andyouventureinto
modernO/RMtrendswithLINQtoSQLandEntityFramework.
ADO.NET
TheSystem.Data.*namespacesconsistofclassesandenumerationsthatformthe
ADO.NETarchitecture,the.NETFramework’sprimarytoolfordatabaseaccess.Youcan
usetheclassesintheSystem.Data.*namespacestoconnecttoyourdatabasesand
accesstheminrealtime,orinadisconnectedfashionviatheDataSet,DataTable,
andDataAdapterclasses.Thefollowingaresomeofthemorecommonlyused
namespacesforSQLServerdataaccess,someofwhichyousawinChapter14whenyou
hadalookatSQLServer.NETIntegration:
TheSystem.Datanamespaceprovidesaccesstoclassesthat
implementtheADO.NETarchitecture,suchasDataSetand
DataTable.
TheSystem.Data.Commonnamespaceprovidesaccesstoclasses
thataresharedby.NETFrameworkdata-accessproviders,suchasthe
DbProviderFactoryclass.
TheprimarynamespacefornativeSQLServerconnectivityis
System.Data.SqlClient.Thisnamespaceincludesclassesthat
provideoptimizedaccesstoSQLServer(version7.0andhigher)via
SQLServerNativeClient.Theclassesinthisnamespacearedesigned
specificallytotakeadvantageofSQLServer–specificfeaturesand
don’tworkwithotherdatasources.
TheSystem.Data.Odbcnamespaceprovidesmanagedaccessto
old-fashionedODBCdrivers.ODBCwasdevelopedintheearly
1990sasaone-size-fits-allstandardforconnectingtoawidearrayof
varieddatasources.Becauseofitsmissionofstandardizingdata
accessacrossavarietyofdatasources,ODBCprovidesagenerally
“plainvanilla”interfacethatsometimesdoesn’ttakeadvantageof
SQLServerorotherdatabasemanagementsystem(DBMS)platform-
specificfeatures.ThismeansODBCisn’tasefficientastheSQL
client,butitprovidesausefuloptionforconnectingtoassorteddata
sourcessuchasExcelspreadsheetsandotherDBMSs.
MicrosoftalsoprovidestheSystem.Data.OleDbnamespaces,
whichcanconnecttoavarietyofdatasources,includingSQLServer.
It’sanoptionforapplicationsthatneedtoaccessdataonmultiple
platforms,suchasbothSQLServerandMicrosoftAccess.OLEDB
hasbeenrecentlydeprecatedbyMicrosoftinfavorofthemore
standardODBC,eventhoughOLEDBwascreatedafterODBC.
TheSystem.Data.SqlTypesnamespaceprovides.NETclasses
representingnative,nullableSQLServerdatatypes.These.NETSQL
Server–specificdatatypesforthemostpartusethesameinternal
representationastheequivalentSQLServernativedatatypes,helping
toreduceprecision-lossproblems.Usingthesetypescanalsospeedup
SQLServerconnectivity,becauseithelpseliminateimplicit
conversions.Andthesedatatypes,unlikethestandard.NETvalue
types,havebuilt-inNULL-handlingcapability.Table15-1liststhe
.NETSqlTypestypesandtheircorrespondingnativeT-SQLdata
types.
Table15-1.System.Data.SqlTypesConversions
System.Data.SqlTypesClass NativeT-SQLDataType
SqlBinary binary,image,timestamp,varbinary
SqlBoolean bit
SqlByte tinyint
SqlDateTime datetime,smalldatetime
SqlDecimal decimal,numeric
SqlDouble float
SqlGuid uniqueidentifier
SqlInt16 smallint
SqlInt32 int
SqlInt64 bigint
SqlMoney money,smallmoney
SqlSingle real
SqlString char,nchar,ntext,nvarchar,text,varchar
SqlXml xml
NoteAtthetimeofthiswriting,thereareno.NETSqlTypestypescorrespondingto
theSQLServerdatatypesintroducedinSQLServer2008(suchasdate,time,
datetimeoffset,anddatetime2).
The.NETSQLClient
The.NETnativeSQLclient(SQLNCLI)isthemostefficientwaytoconnecttoSQL
Serverfromaclientapplication.Withthepossibleexceptionsofupgradinglegacycode
anddesigningcodethatmustaccessnon-SQLServerdatasources,thenativeSQLclientis
theclientconnectivitymethodofchoice.Themainclassesforestablishingaconnection,
sendingSQLcommands,andretrievingresultswithSqlClientarelistedinTable15-2.
Table15-2.CommonlyUsedNativeSQLClientClasses
System.Data.SqlClientClass Description
SqlCommand RepresentsanSQLstatementorSPtoexecute.
SqlCommandBuilder Automaticallygeneratessingle-tablecommandstoreconcile
changesmadetoanADO.NETDataSet.
SqlConnection EstablishesaconnectiontoSQLServer.
SqlConnectionStringBuilder BuildsconnectionstringsforusebySqlConnectionobjects.
SqlDataAdapter
WrapsasetofSqlCommandobjectsandanSqlConnection
thatcanbeusedtofillanADO.NETDataSetandupdateanSQL
Serverdatabase.
SqlDataReader Providesmethodstoreadaforward-onlystreamofrowsfroman
SQLServerdatabase.
SqlException ProvidesaccesstoSQLServer–specificexceptions.Thisclasscan
beusedtocaptureanSQLServererrororwarning.
SqlParameter RepresentsaparametertoanSqlCommand.
SqlParameterCollection AcollectionofSqlParameterobjectsassociatedwithan
SqlCommand.
SqlTransaction EnablesanSQLServertransactiontobeinitiatedandmanaged
fromaclient.
ConnectedDataAccess
Listing15-1demonstratesSqlClientdataaccessviaanSqlDataReaderinstance.
ThisisthetypeofaccessyoumightuseinanASP.NETpagetoquicklyretrievevaluesfor
adrop-downlist,forexample.ThisexampleiswrittentorunasaC#consoleapplication.
TheSQLServerconnectionstringdefinedinthesqlconnectionvariableshouldbe
modifiedtosuityourlocalSQLServerenvironmentandsecurity.
Listing15-1.SqlDataReaderExample
usingSystem;
usingSystem.Data.SqlClient;
namespaceApress.Examples
{
classListing15_1
{
staticvoidMain(string[]args)
{
stringsqlconnection=@"DATASOURCE=SQL2014;"+
"INITIALCATALOG=AdventureWorks;"+
"INTEGRATEDSECURITY=SSPI;";
stringsqlcommand="SELECT"+
"DepartmentId,"+
"Name,"+
"GroupName"+
"FROMHumanResources.Department"+
"ORDERBYDepartmentId";
try
{
connection=newSqlConnection(sqlconnection);
connection.Open();
command=newSqlCommand(sqlcommand,
connection);
datareader=command.ExecuteReader();
while(datareader.Read())
{
Console.WriteLine
(
"{0}\t{1}\t{2}",
datareader["DepartmentId"].ToString(),
datareader["Name"].ToString(),
datareader["GroupName"].ToString()
);
}
}
catch(SqlExceptionex)
{
Console.WriteLine(ex.Message);
}
finally
{
connection.Close();
}
Console.Write("PressaKeytoContinue…");
Console.ReadKey();
}
}
}
Thisexampleisaverysimpleconsoleapplicationthatretrievesthelistofdepartments
fromtheHumanResources.DepartmenttableoftheAdventureWorksdatabase
andwritesthedatatothedisplay.TheexamplebeginsbyimportingtheSystemand
System.Data.SqlClientnamespaces.Althoughnotrequired,importingthe
namespacessavessomekeystrokesandhelpsmakecodemorereadablebyeliminatingthe
needtoprefixtheclassesandenumerationsusedwiththeirassociatednamespaces:
usingSystem;
usingSystem.Data.SqlClient;
ThebodyoftheclassdefinestheSQLServerconnectionstringandtheT-SQL
commandthatretrievesthedepartmentdata.TheDATA_SOURCEconnectionstring
optionissetattheservernamedSQL2014;changeitaccordinglytomatchyourown
servername.Whendefiningtheconnectionstring,youprefixthestringwiththe@sign,to
createaverbatimstringliteral.Thisisusefulbecauseaverbatimstringliteraldoesn’t
interpretspecialcharacterslike\.Withoutthat,ifyoudeclaresanamedinstanceasadata
source,suchasYOUR_SERVER\SQL2014,youwouldhavetoescapeitlikethis:
YOUR_SERVER\SQL2014.Withaverbatimstringliteral,the\doesn’tneedtobe
escapedL
stringsqlconnection=@"DATASOURCE=SQL2014;"+
"INITIALCATALOG=AdventureWorks;"+
"INTEGRATEDSECURITY=SSPI;";
stringsqlcommand="SELECT"+
"DepartmentId,"+
"Name,"+
"GroupName"+
"FROMHumanResources.Department"+
"ORDERBYDepartmentId";
SqlConnectionconnection=null;
TheSqlConnectionconnectionstringiscomposedofaseriesofkey/valuepairs
separatedbysemicolons,asshowninthefollowing:
DATASOURCE=SQL2014;INITIAL
CATALOG=AdventureWorks;INTEGRATEDSECURITY=SSPI;
SomeofthecommonlyusedSqlConnectionconnectionstringkeysarelistedin
Table15-3.
Table15-3.SqlConnectionConnectionStringKeys
ConnectionStringKeys Description
AttachDBFileName Nameofthefullpathtoanattachableprimarydatabasefile(MDF
file).
ConnectionTimeout Lengthoftime(inseconds)towaitforaserverconnectionbefore
stoppingtheattempt.
DataSource
NameorIPaddressofanSQLServerinstancetoconnectto.Usethe
server\instanceformatfornamedinstances.Aportnumbercan
beaddedtotheendofthenameornetworkaddressbyappendingit
withacomma.
Encrypt IndicatesthatSSLencryptionwillbeusedtocommunicatewithSQL
Server.
InitialCatalog Nameofthedatabasetoconnecttoonceaserverconnectionis
established.
IntegratedSecurity Whentrue,yes,orsspi,Windowsintegratedsecurityisusedto
connect.Whenfalseorno,SQLServersecurityisused.
MultipleActiveResultSets
Whentrue,aconnectioncanenablemultipleactiveresultsets
(MARS).Whenfalse,allresultsetsfromabatchmustbeprocessed
beforeanyotherbatchcanbeexecutedontheconnection.
Password PasswordfortheSQLServeraccountusedtologin.Usingintegrated
securityisrecommendedoverSQLServeraccountsecurity.
PersistSecurityInfo
Whenfalseorno,sensitivesecurityinformation(likeapassword)
isn’treturnedaspartoftheconnectioniftheconnectionhasbeen
opened.Therecommendedsettingisfalse.
UserID SQLServeraccountuserIDusedtologin.Integratedsecurityis
recommendedoverSQLServeraccountsecurity.
NoteThewww.connectionstrings.com/websiteisahandyreferenceof
connectionstringsforallmajordatabaseservers.
Thenextsectionofcodeisenclosedinatry…catchblockbecauseofthepossibility
thatadatabaseconnectionorothererrormightoccur.Ifanerrordoesoccur,controlis
passedtothecatchblockandtheerrormessageisdisplayed.Thetry…catchblock
includesthefinallyblock,whichcleansupthedatabaseconnectionwhetheran
exceptionisthrownornot:
try
{
...
}
catch(SqlExceptionex)
{
Console.WriteLine(ex.Message);
}
finally
{
connection.Close();
}
WhenconnectingtoSQLServerfromaclientapplication,it’saverygoodideatocode
defensivelywithtry…catchblocks.Defensivecodingsimplymeanstryingtoanticipate
theproblemsthatmayoccurandmakingsureyourcodehandlesthem.Followingthis
practiceindatabaseclientapplicationscansaveyoualotofheadachesdowntheroad.
SomeofthepossibleerrorsyoumayencounterinSQLServerclientapplicationsinclude
problemsconnectingtoSQLServer,tryingtoaccesstablesandotherdatabaseobjectsthat
havebeenchangedornolongerexist,andreturningNULLwhenyouexpectothervalues.
Intheexample’stry…catchblock,theSqlConnectionisinstantiatedand
openedusingtheconnectionstringdefinedpreviously.ThenanSqlCommandiscreated
ontheopenconnectionandexecutedwiththeExecuteReader()method.The
ExecuteReader()methodreturnsanSqlDataReaderinstance,whichallowsyou
toretrieveresult-setrowsinanefficientforward-onlyfashion.Thisexampleuses
SqlDataReaderinawhilelooptoquicklyretrieveallrowsanddisplaythemonthe
console:
try
{
connection=newSqlConnection(sqlconnection);
connection.Open();
command=newSqlCommand(sqlcommand,connection);
datareader=command.ExecuteReader();
while(datareader.Read())
{
Console.WriteLine
(
"{0}\t{1}\t{2}",
datareader["DepartmentId"].ToString(),
datareader["Name"].ToString(),
datareader["GroupName"].ToString()
);
}
}
TheresultsofthesimpleclientutilityfromListing15-1areshowninFigure15-1.
Figure15-1.Queryingthedatabasetableanditeratingtheresultset
DisconnectedDatasets
TheexampleinListing15-1demonstratedtheforward-onlyread-only
SqlDataReader,whichprovidesanefficientinterfacefordataretrievalbutisfarless
flexiblethanADO.NETdisconnecteddatasets.Adisconnecteddatasetisanin-memory
cacheofadataset.Itprovidesflexibilitybecauseyoudon’tneedaconstantconnectionto
thedatabaseinordertoqueryandmanipulatethedata.Listing15-2demonstrateshowto
usetheSqlDataAdaptertofillaDataSetandprinttheresults.Thedifferences
betweenListing15-2andListing15-1areshowninbold.
Listing15-2.UsingSqlDataReadertoFillaDataSet
usingSystem;
usingSystem.Data;
usingSystem.Data.SqlClient;
namespaceApress.Examples
{
classListing15_2
{
staticvoidMain(string[]args)
{
stringsqlconnection=@"DATASOURCE=SQL2014;"+
"INITIALCATALOG=AdventureWorks;"+
"INTEGRATEDSECURITY=SSPI;";
stringsqlcommand="SELECT"+
"DepartmentId,"+
"Name,"+
"GroupName"+
"FROMHumanResources.Department"+
"ORDERBYDepartmentId";
SqlDataAdapteradapter=null;
DataSetdataset=null;
try
{
adapter=newSqlDataAdapter(sqlcommand,
sqlconnection);
dataset=newDataSet();
adapter.Fill(dataset);
foreach(DataRowrowindataset.Tables[0].Rows)
{
Console.WriteLine
(
"{0}\t{1}\t{2}",
row["DepartmentId"].ToString(),
row["Name"].ToString(),
row["GroupName"].ToString()
);
}
}
catch(SqlExceptionex)
{
Console.WriteLine(ex.Message);
}
finally
{
if(dataset!=null)
dataset.Dispose();
if(adapter!=null)
adapter.Dispose();
}
Console.Write("PressaKeytoContinue…");
Console.ReadKey();
}
}
}
Thesecondversionoftheapplication,inListing15-2,generatesthesameresultsas
Listing15-1.ThefirstdifferenceisthatthisexampleimportstheSystem.Data
namespace,becausetheDataSetclassisamemberofSystem.Data.Again,thisisn’t
required,butitdoessavewearandtearonyourfingersbyeliminatingtheneedtoprefix
System.Dataclassesandenumerationswiththenamespace:
usingSystem;
usingSystem.Data;
usingSystem.Data.SqlClient;
TheSQLconnection-stringandquery-stringdefinitionsarethesameinbothexamples.
Listing15-2departsfromListing15-1bydeclaringanSqlDataAdapteranda
DataSetinsteadofanSqlConnection,SqlCommand,andSqlDataReader:
SqlDataAdapteradapter=null;
DataSetdataset=null;
ThecodetoretrievethedatacreatesanewSqlDataAdapterandDataSetand
thenpopulatestheDataSetviatheFill()methodoftheSqlDataAdapter:
adapter=newSqlDataAdapter(sqlcommand,sqlconnection);
dataset=newDataSet();
adapter.Fill(dataset);
ThemainloopiteratesthrougheachDataRowinthesingletablereturnedbythe
DataSetandwritestheresultstotheconsole:
foreach(DataRowrowindataset.Tables[0].Rows)
{
Console.WriteLine
(
"{0}\t{1}\t{2}",
row["DepartmentId"].ToString(),
row["Name"].ToString(),
row["GroupName"].ToString()
);
}
Thebalanceofthecodehandlesexceptions,performscleanupbydisposingofthe
DataSetandSqlDataAdapter,andwaitsforakeypressbeforeexiting:
if(dataset!=null)
dataset.Dispose();
if(adapter!=null)
adapter.Dispose();
ParameterizedQueries
ADO.NETprovidesasafemethodforpassingparameterstoanSPorSQLstatement,
knownasparameterization.The“classic”VisualBasic6/VBScriptmethodof
concatenatingparametervaluesdirectlyintoalongSQLquerystringisinefficientand
potentiallyunsafe(seethe“SQLInjectionandPerformance”sidebarlaterinthischapter
formoreinformation).Aconcatenatedstringquerymightlooklikethis:
stringsqlstatement="SELECT"+
"BusinessEntityID,"+
"LastName,"+
"FirstName,"+
"MiddleName"+
"FROMPerson.Person"+
"WHERELastName=N'"+name+"';";
ThevalueofthenamevariablecancontainadditionalSQLstatements,leavingSQL
ServerwideopentoSQLinjectionattacks.Let’simaginethatthenamevariableusedhere
comesdirectlyfromatextboxwheretheusercanenterthename.Anattackercouldenter
somespecialcharactersinordertotamperwiththegeneratedquery,asinthefollowing:
stringname="';
DELETEFROMPerson.Person;--";
ThisvalueforthenamevariableresultsinthefollowingdangerousSQLstatements
beingexecutedontheserver:
SELECT
BusinessEntityID,
LastName,
FirstName,
MiddleName
FROMPerson.Person
WHERELastName=N'';
DELETEFROMPerson.Person;--';
ParameterizedqueriesavoidSQLinjectionbysendingtheparametervaluestothe
serverseparatelyfromtheSQLstatement.Listing15-3demonstratesasimple
parameterizedquery.(TheresultsareshowninFigure15-2.)
Listing15-3.ParameterizedSQLQuery
usingSystem;
usingSystem.Data;
usingSystem.Data.SqlClient;
namespaceApress.Examples
{
classListing15_3
{
staticvoidMain(string[]args)
{
stringname="SMITH";
stringsqlconnection=@"SERVER=SQL2014;"+
"INITIALCATALOG=AdventureWorks;"+
"INTEGRATEDSECURITY=SSPI;";
stringsqlcommand="SELECT"+
"BusinessEntityID,"+
"FirstName,"+
"MiddleName,"+
"LastName"+
"FROMPerson.Person"+
"WHERELastName=@name";
SqlConnectionconnection=null;
SqlCommandcommand=null;
SqlDataReaderdatareader=null;
try
{
connection=newSqlConnection(sqlconnection);
connection.Open();
command=newSqlCommand(sqlcommand,
connection);
command.Parameters.Add("@name",
SqlDbType.NVarChar,50).Value=name;
datareader=command.ExecuteReader();
while(datareader.Read())
{
Console.WriteLine
(
"{0}\t{1}\t{2}\t{3}",
datareader["BusinessEntityID"].ToString(),
datareader["LastName"].ToString(),
datareader["FirstName"].ToString(),
datareader["MiddleName"].ToString()
);
}
}
catch(Exceptionex)
{
Console.WriteLine(ex.Message);
}
finally
{
connection.Close();
}
Console.WriteLine("Pressanykey…");
Console.ReadKey();
}
}
}
Figure15-2.Resultsoftheparameterizedquery
Listing15-3retrievesandprintsthecontactinformationforallpeopleinthe
AdventureWorksPerson.PersontablewhoselastnameisSmith.Theexamplebegins
byimportingtheappropriatenamespaces.TheSystem.Datanamespaceisreferenced
herebecauseitcontainstheSqlDbTypeenumerationthatisusedtodeclareparameter
datatypes:
usingSystem;
usingSystem.Data;
usingSystem.Data.SqlClient;
Theprogramdeclaresavariabletoholdtheparametervalue,theSqlClient
connectionstring,aparameterizedSQLSELECTstatement,andtheSqlConnection,
SqlCommand,andSqlDataReaderobjects:
stringname="SMITH";
stringsqlconnection=@"SERVER=SQL2014;"+
"INITIALCATALOG=AdventureWorks;"+
"INTEGRATEDSECURITY=SSPI;";
stringsqlcommand="SELECT"+
"BusinessEntityID,"+
"FirstName,"+
"MiddleName,"+
"LastName"+
"FROMPerson.Person"+
"WHERELastName=@name";
SqlConnectionconnection=null;
SqlCommandcommand=null;
SqlDataReaderdatareader=null;
Asinthepreviousexamples,try…catchisusedtocaptureruntimeexceptions.The
parameterizedSQLSELECTstatementcontainsareferencetoanSQLServerparameter
named@name.Next,aconnectionisestablishedtotheAdventureWorksdatabase:
connection=newSqlConnection(sqlconnection);
connection.Open();
AnSqlCommandiscreatedusingthepreviouslydefinedquerystring,andavalueis
assignedtothe@nameparameter.EverySqlCommandexposesan
SqlParameterCollectionpropertycalledParameters.TheAddmethodofthe
ParameterscollectionallowsyoutoaddparameterstotheSqlCommand.Inthis
example,theparameteraddedisnamed@name;it’sannvarchartypeparameter,and
itslengthis50.TheparametersintheParameterscollectionarepassedalongtoSQL
ServerwiththeSQLstatementwhentheExecuteReader(),ExecuteScalar(),
ExecuteNonOuery(),orExecuteXmlReader()methodoftheSqlCommandis
called.TheadditionofaParameterobjecttotheSqlCommandiscritical;thisisthe
portionofthecodethatinhibitsSQLinjectionattacks:
command=newSqlCommand(sqlcommand,connection);
command.Parameters.Add("@name",SqlDbType.NVarChar,
50).Value=name;
Inthisinstance,theExecuteReader()methodiscalledtoreturntheresultsvia
SqlDataReaderinstance,andawhileloopisusedtoiterateoveranddisplaythe
results:
datareader=command.ExecuteReader();
while(datareader.Read())
{
Console.WriteLine
(
"{0}\t{1}\t{2}\t{3}",
datareader["BusinessEntityID"].ToString(),
datareader["LastName"].ToString(),
datareader["FirstName"].ToString(),
datareader["MiddleName"].ToString()
);
}
SQLINJECTIONANDPERFORMANCE
SQLdevelopersandDBAshavelongknownofthepotentialsecurityrisksthatSQL
injectionattackscanpose.YouoftenhearaboutexploitsbasedonSQLinjections.As
anexample,in2011,hackersclaimedinapressreleasetohavestolenthepersonal
informationof1millionusersontheSonyPictureswebsiteviaasingleSQL
injectionattack.SoifdevelopersandDBAshaveknownallabouttheevilsofSQL
injectionforyears,whyaresomanydatabasesbeingcompromised?
Theproblemisn’tthatpeopledon’tknowwhatSQLinjectionis.MostDBAsand
developersinstinctivelyshudderatthesoundofthosetwolittlewords.Instead,it
appearsthatmanydeveloperseitherdon’tknowhoworarejustnotmotivatedto
properlycodetodefendagainstthisviciousattack.Alotofinjection-susceptiblecode
waswrittenontheVisualBasic6andclassicASPplatforms,wherequery
parameterizationwasabitofahassle.Manyprogrammershavecarriedtheirbad
codinghabitsoverto.NET,despitethefactthatqueryparameterizationwith
SqlClientiseasierthanever.
Asanaddedbenefit,whenyouproperlyparameterizeyourqueries,youcangeta
performanceboost.WhenSQLServerreceivesaparameterizedquery,it
automaticallycachesthequeryplangeneratedbytheoptimizer.Onsubsequent
executionsofthesameparameterizedquery,SQLServercanusethecachedquery
plan.Concatenatedstringquerieswithoutparameterizationgenerallycan’ttake
advantageofcachedquery-planreuse,soSQLServermustregeneratethequeryplan
everytimethequeryisexecuted.KeepthesebenefitsinmindwhendevelopingSQL
Serverclientcode.
Additionally,usingstoredproceduresinsteadofadhocqueriesbuiltintheclient
codesolvesalmostallSQLinjectionthreatsandallowsthebestpossiblequery-plan
reuse,unlessyoucreatedynamicSQLintheprocedurewiththeEXECUTE()
command.
Nonquery,Scalar,andXMLQuerying
TheexamplescoveredsofarinthischapterhaveallbeenSQLSELECTqueriesthat
returnrows.SQLstatementsthatdon’treturnresultsetsareclassifiedby.NETas
nonqueries.ExamplesofnonqueriesincludeUPDATE,INSERT,andDELETEstatements,
aswellasDDLstatementslikeCREATEINDEXandALTERTABLE.The.NET
FrameworkprovidestheExecuteNonQuery()methodoftheSqlCommandclassto
executestatementssuchasthese.Listing15-4isacodesnippetthatshowshowtoexecute
anonqueryusingtheExecuteNonQuery()methodofSqlCommand.
Listing15-4.ExecutingaNonquery
SqlCommandcommand=newSqlCommand
(
"CREATETABLE#temp"+
"("+
"IdINTNOTNULLPRIMARYKEY,"+
"NameNVARCHAR(50)"+
");",connection
);
command.ExecuteNonQuery();
Theexamplecreatesatemporarytablenamed#tempwithtwocolumns.Becausethe
statementisaDDLstatementthatreturnsnoresultset,theExecuteNonQuery()
methodisused.
Inadditiontoqueriesthatreturnnoresultsets,somequeriesreturnaresultset
consistingofasinglerowandasinglecolumn.Forthesequeries,.NETprovidesa
shortcutmethodofretrievingthevalue.TheExecuteScalar()methodretrievesthe
singlevaluereturnedasascalarvalueasa.NETObject.Usingthismethod,youcan
avoidthehassleofcreatinganSqlDataReaderinstanceanditeratingittoretrievea
singlevalue.Listing15-5isacodesnippetthatdemonstratestheExecuteScalar()
method.
Listing15-5.UsingExecuteScalartoRetrieveaRowCount
SqlCommandcommand=newSqlCommand
(
"SELECTCOUNT(*)"+
"FROMPerson.Person;",sqlconnection
);
Objectcount=command.ExecuteScalar();
IfyoucallExecuteScalar()onanSqlCommandthatreturnsmorethanonerow
orcolumn,onlythefirstrowofthefirstcolumnisretrieved.Yourbestbetistomakesure
youonlycallExecuteScalar()onqueriesthatreturnasinglescalarvalue(onerow,
onecolumn)toavoidpossibleconfusionandproblemsdowntheroad.
TipYoumayfindthatusingtheExecuteNonQuery()methodwithscalarOUTPUT
parametersismoreefficientthantheExecuteScalar()methodforserversunder
heavyworkload.
Anadditionaltechniqueforretrievingresultsin.NETisthe
ExecuteXmlReader()method.ThismethodoftheSqlCommandobjectusesan
XmlReadertoretrieveXMLresults,suchasthosegeneratedbyaSELECTquerywith
theFORXMLclause.Listing15-6demonstratesamodifiedversionofthecodeinListing
15-3thatusestheExecuteXmlReader()method.Differencesbetweenthislistingand
Listing15-3areinbold.
Listing15-6.ReadingXMLDatawithExecuteXmlReader()
usingSystem;
usingSystem.Data;
usingSystem.Data.SqlClient;
usingSystem.Xml;
namespaceApress.Examples
{
classListing15_6
{
staticvoidMain(string[]args)
{
stringname="SMITH";
stringsqlconnection=@"SERVER=SQL2014;"+
"INITIALCATALOG=AdventureWorks;"+
"INTEGRATEDSECURITY=SSPI;";
stringsqlcommand="SELECT"+
"BusinessEntityID,"+
"FirstName,"+
"COALESCE(MiddleName,'')ASMiddleName,"+
"LastName"+
"FROMPerson.Person"+
"WHERELastName=@name"+
"FORXMLAUTO;";
SqlConnectionconnection=null;
SqlCommandcommand=null;
XmlReaderxmlreader=null;
try
{
connection=newSqlConnection(sqlconnection);
connection.Open();
command=newSqlCommand(sqlcommand,
connection);
SqlParameterpar
=command.Parameters.Add("@name",SqlDbType.NVarChar,
50);
par.Value=name;
xmlreader=command.ExecuteXmlReader();
while(xmlreader.Read())
{
Console.WriteLine
(
"{0}\t{1}\t{2}\t{3}",
xmlreader["BusinessEntityID"].ToString(),
xmlreader["LastName"].ToString(),
xmlreader["FirstName"].ToString(),
xmlreader["MiddleName"].ToString()
);
}
}
catch(Exceptionex)
{
Console.WriteLine(ex.Message);
}
finally
{
if(xmlreader!=null)
xmlreader.Close();
if(command!=null)
command.Dispose();
if(connection!=null)
connection.Dispose();
}
Console.WriteLine("Pressanykey…");
Console.ReadKey();
}
}
}
ThefirstdifferencebetweenthislistingandListing15-3istheadditionofthe
System.Xmlnamespace,becausetheXmlReaderclassisbeingused:
usingSystem;
usingSystem.Data;
usingSystem.Data.SqlClient;
usingSystem.Xml;
TheSQLSELECTstatementisalsoslightlydifferent.Foronething,the
COALESCE()functionisusedontheMiddleNamecolumntoreplaceNULLmiddle
nameswithemptystrings.TheFORXMLclauseleavesNULLattributesoutofthe
generatedXMLbydefault.Missingattributeswouldgenerateexceptionswhentryingto
displaytheresults.TheFORXMLAUTOclauseisusedintheSELECTquerytoinform
SQLServerthatitneedstogenerateanXMLresult:
stringsqlcommand="SELECT"+
"BusinessEntityID,"+
"FirstName,"+
"COALESCE(MiddleName,'')ASMiddleName,"+
"LastName"+
"FROMPerson.Person"+
"WHERELastName=@name"+
"FORXMLAUTO;";
Thetry…catchblockusestheExecuteXmlReader()methodinsteadofthe
ExecuteReader()method.TheloopthatdisplaystheresultsisverysimilartoListing
15-3aswell.ThemaindifferenceinthislistingisthatanXmlReaderisusedinplaceof
anSqlDataReader:
xmlreader=command.ExecuteXmlReader();
while(xmlreader.Read())
{
Console.WriteLine
(
"{0}\t{1}\t{2}\t{3}",
xmlreader["BusinessEntityID"].ToString(),
xmlreader["LastName"].ToString(),
xmlreader["FirstName"].ToString(),
xmlreader["MiddleName"].ToString()
);
}
Theremainingcodeintheexampleperformsexceptionhandlingandpropercleanup,
asintheotherexamplelistings.
SqIBulkCopy
SQLServerprovidestoolssuchasSQLServerIntegrationServices(SSIS)andtheBulk
CopyProgram(BCP)tohelppopulateyourdatabasesfromexternaldatasources.Some
applicationscanbenefitfrombuilt-in.NETbulk-loadfunctionality.The.NETFramework
(versions2.0andhigher)SqlClientimplementstheSqlBulkCopyclasstomake
efficientbulkloadingeasy.SqlBulkCopycanbeusedtoloaddatafromadatabase
table,anXMLtable,aflatfile,oranyothertypeofdatasourceyouchoose.The
SqlBulkCopyexampleinListing15-7loadsUSPostalServiceZIPcodedatafroma
tab-delimitedflatfileintoanSQLServertable.Asampleofthesourcetextfileisshown
inTable15-4.
Table15-4.SampleTab-DelimitedZIPCodeData
ThecompletesampleZIPcodefileisincludedwiththedownloadablesourcecodefor
thisbook.ThetargettableisbuiltwiththeCREATETABLEstatementinListing15-7.
YouneedtoexecutethisstatementtocreatethetargettableintheAdventureWorks
database(oranothertargetdatabaseifyouchoose).
Listing15-7.CreatingtheZipCodesTargetTable
CREATETABLEdbo.ZipCodes
(
ZIPCHAR(5)NOTNULLPRIMARYKEY,
LatitudeNUMERIC(8,4)NOTNULL,
LongitudeNUMERIC(8,4)NOTNULL,
CityNVARCHAR(50)NOTNULL,
StateCHAR(2)NOTNULL
)
GO
ThecodepresentedinListing15-8usestheSqlBulkCopyclasstobulk-copythe
datafromtheflatfileintothedestinationtable.
Listing15-8.SqlBulkCopyClassExample
usingSystem;
usingSystem.Data;
usingSystem.Data.SqlClient;
usingSystem.Data.SqlTypes;
usingSystem.Diagnostics;
usingSystem.IO;
usingSystem.Globalization;
namespaceApress.Example
{
classListing15_8
{
staticstringsqlconnection="DATASOURCE=SQL2014;"
+
"INITIALCATALOG=AdventureWorks;"+
"INTEGRATEDSECURITY=SSPI;";
staticstringsourcefile="c:\\ZIPCodes.txt";
staticDataTableloadtable=null;
staticvoidMain(string[]args)
{
Stopwatchclock=newStopwatch();
clock.Start();
introwcount=DoImport();
clock.Stop();
Console.WriteLine("{0}RowsImportedin{1}
Seconds.",
rowcount,(clock.ElapsedMilliseconds/1000.0));
Console.WriteLine("PressaKey…");
Console.ReadKey();
}
staticintDoImport()
{
using(SqlBulkCopybulkcopier=new
SqlBulkCopy(sqlconnection))
{
bulkcopier.DestinationTableName
="dbo.ZIPCodes";
try
{
LoadSourceFile();
bulkcopier.WriteToServer(loadtable);
}
catch(SqlExceptionex)
{
Console.WriteLine(ex.Message);
}
}
returnloadtable.Rows.Count;
}
staticvoidLoadSourceFile()
{
loadtable=newDataTable();
DataColumnloadcolumn=newDataColumn();
DataRowloadrow=null;
loadcolumn.DataType=typeof(SqlString);
loadcolumn.ColumnName="ZIP";
loadcolumn.Unique=true;
loadtable.Columns.Add(loadcolumn);
loadcolumn=newDataColumn();
loadcolumn.DataType=typeof(SqlDecimal);
loadcolumn.ColumnName="Latitude";
loadcolumn.Unique=false;
loadtable.Columns.Add(loadcolumn);
loadcolumn=newDataColumn();
loadcolumn.DataType=typeof(SqlDecimal);
loadcolumn.ColumnName="Longitude";
loadcolumn.Unique=false;
loadtable.Columns.Add(loadcolumn);
loadcolumn=newDataColumn();
loadcolumn.DataType=typeof(SqlString);
loadcolumn.ColumnName="City";
loadcolumn.Unique=false;
loadtable.Columns.Add(loadcolumn);
loadcolumn=newDataColumn();
loadcolumn.DataType=typeof(SqlString);
loadcolumn.ColumnName="State";
loadcolumn.Unique=false;
loadtable.Columns.Add(loadcolumn);
using(StreamReaderstream=new
StreamReader(sourcefile))
{
stringrecord=stream.ReadLine();
while(record!=null)
{
string[]cols=record.Split('\t');
loadrow=loadtable.NewRow();
loadrow["ZIP"]=cols[0];
loadrow["Latitude"]=decimal.Parse(cols[1],
CultureInfo.InvariantCulture);
loadrow["Longitude"]
=decimal.Parse(cols[2],CultureInfo.InvariantCulture);
loadrow["City"]=cols[3];
loadrow["State"]=cols[4];
loadtable.Rows.Add(loadrow);
record=stream.ReadLine();
}
}
}
}
}
Thecodebeginsbyimportingrequirednamespaces,declaringthe
Apress.Examplenamespace,anddeclaringthemodulename.TheSystem.IO
namespaceisimportedfortheStreamReader,andtheSystem.Diagnostics
namespaceisimportedfortheStopwatchclasssothattheprogramcanreportthe
importtime.TheSystem.Globalizationnamespacegivesyouaccesstothe
CultureInfoclasstoallowasafeconversionofdecimalcolumns:
usingSystem;
usingSystem.Data;
usingSystem.Data.SqlClient;
usingSystem.Diagnostics;
usingSystem.IO;
usingSystem.Globalization;
TheclassdefinesanSQLconnectionstring,thesourcefilename,andaDataTable:
staticstringsqlconnection=@"DATASOURCE=SQL2014;"+
"INITIALCATALOG=AdventureWorks;"+
"INTEGRATEDSECURITY=SSPI;";
staticstringsourcefile="c:\\ZIPCodes.txt";
staticDataTableloadtable=null;
Theclasscontainsthreefunctions:Main(),DoImport(),and
LoadSourceFile().TheMain()functionbeginsbystartingaStopwatchtotime
theimportprocess.ThenitinvokestheDoImport()functionthatperformstheactual
importandreportsbackthenumberofrows.Finally,theStopwatchisstoppedandthe
numberofrowsimportedandnumberofsecondselapsedaredisplayed:
staticvoidMain(string[]args)
{
Stopwatchclock=newStopwatch();
clock.Start();
introwcount=DoImport();
clock.Stop();
Console.WriteLine("{0}RowsImportedin{1}Seconds.",
rowcount,(clock.ElapsedMilliseconds/1000.0));
Console.WriteLine("PressaKey…");
Console.ReadKey();
}
Thesecondfunction,DoImport(),initializesaninstanceoftheSqlBulkCopy
class.ItthencallstheLoadSourceFile()functiontopopulatetheDataTablewith
datafromthesourceflatfile.ThepopulatedDataTableispassedintothe
WriteToServer()methodoftheSqlBulkCopyobject.Thismethodperformsa
bulkcopyofalltherowsintheDataTabletothedestinationtable.TheDoImport()
functionendsbyreturningthenumberofrowsloadedintotheDataTable:
staticintDoImport()
{
using(SqlBulkCopybulkcopier=new
SqlBulkCopy(sqlconnection))
{
bulkcopier.DestinationTableName="dbo.ZIPCodes";
try
{
LoadSourceFile();
bulkcopier.WriteToServer(loadtable);
}
catch(SqlExceptionex)
{
Console.WriteLine(ex.Message);
}
}
returnloadtable.Rows.Count;
}
Thethirdandfinalfunction,LoadSourceFile(),initializesthestructureofthe
DataTableandloadsthesourcefiledataintoit:
staticvoidLoadSourceFile()
{
loadtable=newDataTable();
DataColumnloadcolumn=newDataColumn();
DataRowloadrow=null;
loadcolumn.DataType=typeof(SqlString);
loadcolumn.ColumnName=“ZIP”;
loadcolumn.Unique=true;
loadtable.Columns.Add(loadcolumn);
loadcolumn=newDataColumn();
loadcolumn.DataType=typeof(SqlDecimal);
loadcolumn.ColumnName=“Latitude”;
loadcolumn.Unique=false;
loadtable.Columns.Add(loadcolumn);
loadcolumn=newDataColumn();
loadcolumn.DataType=typeof(SqlDecimal);
loadcolumn.ColumnName=“Longitude”;
loadcolumn.Unique=false;
loadtable.Columns.Add(loadcolumn);
loadcolumn=newDataColumn();
loadcolumn.DataType=typeof(SqlString);
loadcolumn.ColumnName=“City”;
loadcolumn.Unique=false;
loadtable.Columns.Add(loadcolumn);
loadcolumn=newDataColumn();
loadcolumn.DataType=typeof(SqlString);
loadcolumn.ColumnName=“State”;
loadcolumn.Unique=false;
loadtable.Columns.Add(loadcolumn);
using(StreamReaderstream=new
StreamReader(sourcefile))
{
stringrecord=stream.ReadLine();
while(record!=null)
{
string[]cols=record.Split('\t');
loadrow=loadtable.NewRow();
loadrow[“ZIP”]=cols[0];
loadrow[“Latitude”]=decimal.Parse(cols[1],
CultureInfo.InvariantCulture);
loadrow[“Longitude”]=decimal.Parse(cols[2],
CultureInfo.InvariantCulture);
loadrow[“City”]=cols[3];
loadrow[“State”]=cols[4];
loadtable.Rows.Add(loadrow);
record=stream.ReadLine();
}
}
}
}
}
Youdoanexplicitconversiontodecimalsforlatitudeandlongitudefromthestrings
extractedfromthefile.Usingthedecimal.Parse()methodensuresthatthe
conversionunderstandsthe.(dot)asadecimalseparatorevenifthecodeisrunona
machineconfiguredforaculturewherethedecimalseparatorisn’tadot,suchasin
French.
Afteritcompletes,Listing15-8reportsthenumberofrowsbulk-loadedandthe
amountoftimerequired,asshowninFigure15-3.
Figure15-3.Reportofbulk-copyrowsimportedandthetimerequired
YoucanperformasimpleSELECTstatementliketheoneshowninListing15-9to
verifythatthedestinationtablewasproperlypopulated.PartialresultsareshowninFigure
15-4.
Listing15-9.VerifyingBulk-CopyResults
SELECT
ZIP,
Latitude,
Longitude,
City,
StateFROMdbo.ZipCodes;
Figure15-4.ZIPcodesbulk-loadedintothedatabase
MultipleActiveResultSets
PriortoSQLServer2005,client-sideapplicationswerelimitedtooneopenresultsetper
connectiontoSQLServer.Theworkaroundwastofullyprocessorcancelallopenresult
setsonasingleconnectionbeforeretrievinganewresultset,ortoopenmultiple
connections,eachwithitsownsingleopenresult.
SQLServer2014,likeSQLServer2005,allowsyoutousemultipleactiveresultsets
(MARS)functionality.MARSletsyouprocessmultipleopenresultsetsoverasingle
connection.Listing15-10demonstrateshowtouseMARStoperformthefollowingtasks
overasingleconnection:
1. Openaresultset,andbeginreadingit.
2. Stopreadingtheresultsetafterafewrows.
3. Openasecondresultset,andreadittocompletion.
4. Resumereadingthefirstresultset.
Listing15-10.OpeningTwoResultSetsoveraSingleConnection
usingSystem;
usingSystem.Data;
usingSystem.Data.SqlClient;
namespaceApress.Examples
{
classMARS
{
staticstringsqlconnection=@"SERVER=SQL2014;"+
"INITIALCATALOG=AdventureWorks;"+
"INTEGRATEDSECURITY=SSPI;"+
"MULTIPLEACTIVERESULTSETS=true;";
staticstringsqlcommand1="SELECT"+
"DepartmentID,"+
"Name,"+
"GroupName"+
"FROMHumanResources.Department;";
staticstringsqlcommand2="SELECT"+
"ShiftID,"+
"Name,"+
"StartTime,"+
"EndTime"+
"FROMHumanResources.Shift;";
staticSqlConnectionconnection=null;
staticSqlCommandcommand1=null;
staticSqlCommandcommand2=null;
staticSqlDataReaderdatareader1=null;
staticSqlDataReaderdatareader2=null;
staticvoidMain(string[]args)
{
try
{
connection=newSqlConnection(sqlconnection);
connection.Open();
command1=newSqlCommand(sqlcommand1,
connection);
command2=newSqlCommand(sqlcommand2,
connection);
datareader1=command1.ExecuteReader();
datareader2=command2.ExecuteReader();
inti=0;
Console.WriteLine("===========");
Console.WriteLine("Departments");
Console.WriteLine("===========");
while(datareader1.Read()&&i++<3)
{
Console.WriteLine
(
"{0}\t{1}\t{2}",
datareader1["DepartmentID"].ToString(),
datareader1["Name"].ToString(),
datareader1["GroupName"].ToString()
);
}
Console.WriteLine("======");
Console.WriteLine("Shifts");
Console.WriteLine("======");
while(datareader2.Read())
{
Console.WriteLine
(
"{0}\t{1}\t{2}\t{3}",
datareader2["ShiftID"].ToString(),
datareader2["Name"].ToString(),
datareader2["StartTime"].ToString(),
datareader2["EndTime"].ToString()
);
}
Console.WriteLine("======================");
Console.WriteLine("Departments,Continued");
Console.WriteLine("======================");
while(datareader1.Read())
{
Console.WriteLine
(
"{0}\t{1}\t{2}",
datareader1["DepartmentID"].ToString(),
datareader1["Name"].ToString(),
datareader1["GroupName"].ToString()
);
}
}
catch(SqlExceptionex)
{
Console.WriteLine(ex.Message);
}
finally
{
if(datareader1!=null)
datareader1.Dispose();
if(datareader2!=null)
datareader2.Dispose();
if(command1!=null)
command1.Dispose();
if(command2!=null)
command2.Dispose();
if(connection!=null)
connection.Dispose();
}
Console.WriteLine("Pressakeytoend…");
Console.ReadKey();
}
}
}
Listing15-10beginsbyimportingthenecessarynamespaces:
usingSystem;
usingSystem.Data;
usingSystem.Data.SqlClient;
TheclassfirstdeclaresanSQLconnectionstringandtwoSQLquerystrings.Italso
declaresanSqlConnection,twoSqlCommandobjects,andtwoSqlDataReader
objects.Theconnectionisthenopened,andtwoSqlCommandsarecreatedonthesingle
connectiontoretrievethetworesultsets:
staticstringsqlconnection=@"SERVER=SQL2014;"+
"INITIALCATALOG=AdventureWorks;"+
"INTEGRATEDSECURITY=SSPI;"+
"MULTIPLEACTIVERESULTSETS=true;";
staticstringsqlcommand1="SELECT"+
"DepartmentID,"+
"Name,"+
"GroupName"+
"FROMHumanResources.Department;";
staticstringsqlcommand2="SELECT"+
"ShiftID,"+
"Name,"+
"StartTime,"+
"EndTime"+
"FROMHumanResources.Shift;";
staticSqlConnectionconnection=null;
staticSqlCommandcommand1=null;
staticSqlCommandcommand2=null;
staticSqlDataReaderdatareader1=null;
staticSqlDataReaderdatareader2=null;
ThekeytoenablingMARSistheMULTIPLEACTIVERESULTSETS=true
key/valuepairintheconnectionstring.TheMainfunctioncreatesandopensthe
SqlConnection,theSqlCommandobjects,andtheSqlDataReaderobjects
requiredtocreatetwoactiveresultsetsoveroneconnection:
connection=newSqlConnection(sqlconnection);
connection.Open();
command1=newSqlCommand(sqlcommand1,connection);
command2=newSqlCommand(sqlcommand2,connection);
datareader1=command1.ExecuteReader();
datareader2=command2.ExecuteReader();
Thebalanceofthecodeloopsthroughtheresultsets,displayingthedataonthe
console.Thecodeinterruptsthefirstresultsetafterthreerowsareconsumed,consumes
thesecondresultsetinitsentirety,andthenfinishesthefirstresultset,alloverasingle
connection.TheresultsareshowninFigure15-5.
Figure15-5.Resultsofiteratingovertwoactiveresultsetsoveroneconnection
RemovingtheMULTIPLEACTIVERESULTSETS=trueoptionfromtheconnection
string,asshowninthecodesnippetinListing15-11,resultsintheinvalidoperation
exceptioninFigure15-6beingthrown.
Listing15-11.SQLConnectionStringwithoutMARSEnabled
staticstringsqlconnection="SERVER=SQL_2014;"+"INITIAL
CATALOG=AdventureWorks;"+"INTEGRATEDSECURITY=SSPI;";
Figure15-6.TryingtoopentworesultsetsononeconnectionwithoutMARS
LINQtoSQL
LanguageIntegratedQuery(LINQ)isasetoftechnologiesbuiltintoVisualStudioand
the.NETFrameworkthatallowsyoutoquerydatafromanydatasource.LINQshipswith
standardlibrariesthatsupportqueryingSQLdatabases,XML,andobjects.Additional
LINQ-enableddataprovidershavealreadybeencreatedtoqueryAmazon.com,
NHibernate,andLDAP,amongothers.LINQtoSQLencapsulatesLINQ’sbuilt-insupport
forSQLdatabasequerying.
LINQtoSQLprovidestwothings:abasicobject/relationalmapping(O/RM)
implementationforthe.NETFramework,andaquerylanguagederivedfromSQLbut
moreintegratedintothe.NETlanguage.LINQtoSQLletsyoucreate.NETclassesthat
modelyourdatabase,allowingyoutoqueryandmanipulatedatausingobject-oriented
methodologies.Insteadofenclosingqueriesintostringsthatyousendtotheserver,you
writethemwiththeLINQsyntax.AsobjectsarerecognizedthroughtheO/RMmapping,
thesyntaxisrecognizeddirectlylikeanyother.NETlanguageconstruct.Ithelpsto
decreasetheso-calledobject/relationalimpedancemismatchbetweenobject-oriented
languagesandSQL(inotherwords,theimpossibilityofgracefullyintegratingone
languageintotheother).
ThissectionintroducesLINQtoSQL.Foranin-depthintroduction,werecommend
thebookLINQforVisualC#2008byFabioClaudioFerracchiati(Apress,2008).
TipInadditiontoFerracchiati’sLINQforVisualC#books,Apresspublishesseveral
otherbooksonLINQ.Youcanviewthelistat
http://www.apress.com/catalogsearch/result/?q=LINQ&submit=Go.
TheMSDNwebsite(http://msdn.microsoft.com)alsohasseveralLINQ
resourcesavailable.
UsingtheDesigner
VisualStudioincludesaLINQtoSQLdesignerthatmakesmappingdatabaseschematoa
.NETrepresentationarelativelypainlessprocess.TheLINQtoSQLdesignercanbe
accessedinVisualStudiobyaddinganewLINQtoSQLClassesitemtoyour.NET
project,asshowninFigure15-7.Notethatsomeimportanceisplacedonthefilename
youchoose,becausethe.NETdatacontextclass(themainLINQtoSQLclass)createdis
basedonthenameyouchoose(withoutthe.dbmlextension).Inthiscase,wechosethe
nameAdventureWorks.dbml.
Figure15-7.AddingaLINQtoSQLClassesitemtoaproject
OncetheLINQtoSQLClassesitemhasbeenadded,youneedtocreateaMicrosoft
SQLServerSqlClientconnectionthatpointstoyourserveranddatabase.Youcanadd
adataconnectionthroughtheVisualStudioSQLServerObjectExplorer,asshownin
Figure15-8.
Figure15-8.AddingaconnectionthroughtheSQLServerObjectExplorer
Whenyou’veaddedtheconnectiontoyourdatabase,theSQLServerObjectExplorer
displaysthetablesandotherobjectscontainedinthedatabase.Youcanselecttablesand
SPsanddragthemfromtheSQLServerObjectExplorerontotheO/RMdesignersurface.
Figure15-9showstheselectionoftwotables,Person.Personand
Person.EmailAddress,intheSQLServerObjectExplorer.
Figure15-9.ViewingandselectingtablesintheSQLServerObjectExplorer
OncethetableshavebeendraggedontotheO/RMdesignersurface,VisualStudio
providesavisualrepresentationoftheclassesitcreatestomodelthedatabaseandthe
relationshipsbetweenthem.Figure15-10showsthedesignersurfacewiththe
Person.PersonandPerson.EmailAddresstablesaddedtoit.
Figure15-10.O/RMdesignersurfacewithtablesaddedtoit
QueryingwithLINQtoSQL
Onceyou’vecreatedyourLINQtoSQLO/RMclasseswiththedesigner,it’stimetowrite
queries.NotonlydoesLINQallowyoutoqueryanydatasourceincludingSQL,butit’s
alsointegrateddirectlyintoVisualBasicandC#viadedicatedkeywords.Thesenew
LINQ-specifickeywordsincludefrom,select,where,andothersthatwillseem
eerilyfamiliartoSQLdevelopers.Thesekeywords,combinedwithsomeotherfeatures,
provideapowerfulmechanismforperformingdeclarativequeriesdirectlyinyour
proceduralcode.
BasicLINQtoSQLQuerying
ThefirstLINQtoSQLqueryexample,inListing15-12,queriesthePersonspropertyof
theAdventureWorksDataContextclass.
Listing15-12.QueryingPersonswithLINQtoSQL
usingSystem;
usingSystem.Linq;
namespaceApress.Examples
{
classListing15_12
{
staticvoidMain(string[]args)
{
AdventureWorksDataContextdb=new
AdventureWorksDataContext();
db.Log=Console.Out;
varquery=frompindb.Persons
selectp;
foreach(Personpinquery)
{
Console.WriteLine
(
"{0}\t{1}\t{2}",
p.FirstName,
p.MiddleName,
p.LastName
);
}
Console.WriteLine("Pressakeytocontinue…");
Console.ReadKey();
}
}
}
Thefirstthingtonoticeaboutthisexampleisthenamespacedeclarations.Because
you’reusingLINQtoSQL,youhavetoimporttheSystem.Linqnamespace.This
namespacegivesaccesstotheLINQIQueryableinterface,providingobjectsthatcan
beenumeratedinaforeachloop,likeIEnumerable:
usingSystem;
usingSystem.Linq;
TheMain()methodoftheprogrambeginsbycreatinganinstanceofthe
AdventureWorksDataContext,whichyouqueryagainst.Noticethatyousetthe
LogpropertyoftheAdventureWorksDataContextinstancetoConsole.Out.
ThisdisplaystheactualSQLquerythatLINQtoSQLgeneratesontheconsole:
AdventureWorksDataContextdb=new
AdventureWorksDataContext();
db.Log=Console.Out;
AftertheAdventureWorksDataContextclassisinstantiated,queryingwiththe
newC#keywordsisassimpleasassigningaquerytoavariable.Thisexampletakes
advantageofthe.NETanonymoustypesfeature.Anonymoustypesallowyoutodeclare
variableswithoutanexplicittypeusingthevarkeyword.Whenyoudeclareavariable
usinganonymoustypes,thecompilerautomaticallyinfersthetypeatcompiletime.Thisis
animportantdistinctionfromObjectandvariantdatatypes,whichrepresentgeneral-
purposetypesthataredeterminedatruntime.Thequeryissimple,usingthefrom…in
clausetoindicatethesourceofthedataandtheselectkeywordtoreturnobjects.As
youcansee,theLINQtoSQLsyntaxhasadifferentorderthantheSQLsyntax.The
selectkeywordcomesattheendofthestatement:
varquery=frompindb.Persons
selectp;
ThefinalpartofthisexampleusesaforeachlooptoiterateoverallthePerson
objectsreturnedbythequeryandprintthenamestothedisplay.Partialresultsofthis
queryareshowninFigure15-11:
foreach(Personpinquery)
{
Console.WriteLine
(
"{0}\t{1}\t{2}",
p.FirstName,
p.MiddleName,
p.LastName
);
}
Figure15-11.QueryingPersonswithLINQtoSQL
Asmentionedpreviously,youcanusetheLogattributeofthedatacontextclassto
outputtheSQLcodegeneratedbyLINQtoSQL.Thisisusefulfordebuggingorfinding
outmoreabouthowLINQtoSQLworksinternally.TheSQLquerygeneratedbyListing
15-12isshowninListing15-13(reformattedforreadability).
Listing15-13.LINQtoSQL–GeneratedSQLQuery
SELECT[t0].[BusinessEntityID],[t0].[PersonType],
[t0].[NameStyle],[t0].[Title],[t0].
[FirstName],
[t0].[MiddleName],[t0].[LastName],[t0].
[Suffix],
[t0].[EmailPromotion],[t0].
[AdditionalContactInfo],
[t0].[Demographics],[t0].[rowguid],[t0].
[ModifiedDate]
FROM[Person].[Person]AS[t0]
LINQtoSQLprovidesseveralclausesinadditiontofromandselect.Table15-5
isasummaryofsomecommonlyusedLINQtoSQLqueryoperators.Thediscussionof
LINQtoSQLqueryoperatorscontinuesinthesectionsthatfollow.
Table15-5.UsefulLINQStandardQueryOperators
Function Keyword Description
Restriction where
Restricts/filterstheresultsreturnedbyaquery,returningonlytheitemsthat
matchthewherepredicatecondition.Youcanthinkofthisasequivalentto
theWHEREclauseinSQL.
Projection select Defines/restrictstheattributesthatshouldbereturnedintheresultcollection.
TheselectkeywordapproximatestheSQLSELECTclause.
Join join Performsaninnerjoinoftwosequencesbasedonmatchingkeysfromboth
sequences.ThisisequivalenttotheSQLINNERJOINclause.
Join join…
into
Canacceptanintoclausetoperformaleftouterjoin.Thisformofthe
joinkeywordisequivalenttotheSQLLEFTOUTERJOINclause.
Ordering orderby
Acceptsacomma-separatedlistofkeystosortyourqueryresults.Eachkey
canbefollowedbytheascendingordescendingkeyword.The
ascendingkeywordisthedefault.ThisisequivalenttotheSQLORDER
BYclause.
Grouping group
Allowsyoutogroupresultsbyaspecifiedsetofkeyvalues.Youcanusethe
group…intosyntaxtoperformadditionalqueryoperationsonthegrouped
results.ThebehaviorofthiskeywordapproximatestheSQLGROUPBY
clause.
Subexpressions let
Allowsyoutostoresubexpressionsinavariableduringthequery.Youcan
usethevariableinsubsequentqueryclauses.SQLdoesn’thaveanequivalent
forthisstatement,althoughsubqueriescanapproximatethebehaviorin
someinstances.ThebestequivalentforthiskeywordistheXQueryFLWOR
expressionletclause.
ThewhereClause
Thewhereclauseallowsyoutorestricttheresultsreturnedbyaquery,asshownin
Listing15-14.ReplacingthequeryinListing15-12withthisqueryrestrictsthePerson
objectsreturnedtoonlythosewiththeletterssmiintheirlastnames.
Listing15-14.QueryingPersonswith“smi”inTheirLastNames
varquery=frompindb.Persons
wherep.LastName.Contains("SMI")
selectp;
TheSQLcodegeneratedbythisLINQtoSQLqueryisslightlydifferentfromthe
previousSQLquery,asshowninListing15-15.
Listing15-15.LINQtoSQL-GeneratedSQLQuerywithaWHEREClause
execsp_executesql
N'SELECT[t0].[BusinessEntityID],[t0].[PersonType],
[t0].[NameStyle],[t0].[Title],[t0].
[FirstName],
[t0].[MiddleName],[t0].[LastName],
[t0].[Suffix],[t0].[EmailPromotion],
[t0].[AdditionalContactInfo],[t0].
[Demographics],
[t0].[rowguid],[t0].[ModifiedDate]
FROM[Person].[Person]AS[t0]
WHERE[t0].[LastName]
LIKE@p0',N'@p0
nvarchar(5)',@p0=N'%SMI%'
OneinterestingaspecttothisqueryisthatLINQtoSQLconvertstheContains
methodofthePersonobject’sLastNamepropertytoanSQLLIKEpredicate.Thisis
importantbecauseitmeansLINQtoSQLissmartenoughtorealizethatitdoesn’thaveto
retrieveanentiretable,instantiateobjectsforeveryrowofthetable,andthenuse.NET
methodstolimittheresultsontheclient.Thiscanbeasignificantperformance
enhancementoverthealternative.Furthermore,itusessp_executesqltoparameterize
thequery.
AnotherinterestingfeaturethatLINQtoSQLprovidesisqueryparameterization.In
thisinstance,thegeneratedSQLqueryincludesaparameternamed@p0thatisdefinedas
annvarchar(5)parameterandassignedavalueof%SMI%.
TheorderbyClause
LINQtoSQLalsoprovidesresultorderingviatheorderbyclause.Youcanusethe
orderbykeywordinaquerytospecifytheattributestosortby.Listing15-16buildson
thequeryinListing15-14byaddinganorderbyclausethatsortsresultsbythe
LastNameandFirstNameattributesofthePersonobject.
Listing15-16.OrderingLINQtoSQLQueryResults
varquery=frompindb.Persons
wherep.LastName.Contains("SMI")
orderbyp.LastName,p.FirstName
selectp;
ReplacingthequeryinListing15-12withthisqueryreturnsallPersonobjects
whoselastnamescontaintheletterssmi,andsortstheobjectsbytheirlastandfirstnames.
ThegeneratedSQLqueryisshowninListing15-17.It’ssimilartothepreviousquery
exceptthatLINQtoSQLhasaddedanSQLORDERBYclause.
Listing15-17.LINQtoSQL-GeneratedSQLQuerywithanORDERBYClause
execsp_executesql
N'SELECT[t0].[BusinessEntityID],[t0].[PersonType],
[t0].[NameStyle],[t0].[Title],[t0].
[FirstName],
[t0].[MiddleName],[t0].[LastName],
[t0].[Suffix],[t0].[EmailPromotion],
[t0].[AdditionalContactInfo],[t0].
[Demographics],
[t0].[rowguid],[t0].[ModifiedDate]
FROM[Person].[Person]AS[t0]
WHERE[t0].[LastName]LIKE@p0
ORDERBY[t0].[LastName],[t0].[FirstName]',N'@p0
nvarchar(5)',@p0=N'%SMI%'
ThejoinClause
LINQtoSQLalsoprovidesthejoinclause,whichallowsyoutoperforminnerjoinsin
yourqueries.Aninnerjoinrelatestwoentities,likePersonandEmailAddressinthe
example,basedoncommonvaluesofanattribute.TheLINQtoSQLjoinoperator
essentiallyworksthesamewayastheSQLINNERJOINoperator.Listing15-18
demonstratesaLINQtoSQLjoinquery.
Listing15-18.RetrievingPersonsandRelatedE-mailAddresses
usingSystem;
usingSystem.Linq;
namespaceApress.Examples
{
classListing15_18
{
staticvoidMain(string[]args)
{
AdventureWorksDataContextdb=new
AdventureWorksDataContext();
db.Log=Console.Out;
varquery=frompindb.Persons
joineindb.EmailAddresses
onp.BusinessEntityIDequals
e.BusinessEntityID
wherep.LastName.Contains("SMI")
orderbyp.LastName,p.FirstName
selectnew
{
LastName=p.LastName,
FirstName=p.FirstName,
MiddleName=p.MiddleName,
EmailAddress=e.EmailAddress1
};
foreach(varqinquery)
{
Console.WriteLine
(
"{0}\t{1}\t{2}\t{3}",
q.FirstName,
q.MiddleName,
q.LastName,
q.EmailAddress
);
}
Console.WriteLine("Pressakeytocontinue…");
Console.ReadKey();
}
}
}
THEEQUALSOPERATORANDNON-EQUIJOINS
C#usestheequalskeywordintheLINQjoin…onclauseinsteadofthefamiliar
==operator.Thisisdoneforclarity.TheLINQfrom…joinpatternmapsdirectlyto
theEnumerable.Join()LINQqueryoperator,whichrequirestwodelegatesthat
areusedtocomputevaluesforcomparison.Thedelegate/keyontheleftsideofthe
operatorconsumestheoutersequence,andtherightdelegate/keyconsumestheinner
sequence.Thedecisionwasmadetousetheequalskeywordtoclarifythisconcept
primarilybecauseimplementingafullqueryprocessorforLINQwouldhaveresulted
insignificantoverhead.Toperformothertypesofnon-equijoinsinLINQ,youcan
useacombinationoftheLINQGroupJoinoperatorandthewhereclause.
TheLINQtoSQLqueryinListing15-18usesthejoinoperatortoidentifythe
entitiestojoinandtheonclausespecifiesthejoincriteria.Inthisexample,thePerson
andEmailAddressentitiesarejoinedbasedontheirBusinessEntitylDattributes.
Becausethequeryneedstoreturnsomeattributesofbothentities,theselectclause
createsanewanonymoustypeonthefly.Partialresultsofthejoinqueryareshownin
Figure15-12:
varquery=frompindb.Persons
joineindb.EmailAddresses
onp.BusinessEntityIDequalse.BusinessEntityID
wherep.LastName.Contains("SMI")
orderbyp.LastName,p.FirstName
selectnew
{
LastName=p.LastName,
FirstName=p.FirstName,
MiddleName=p.MiddleName,
EmailAddress=e.EmailAddress1
};
Figure15-12.RetrievingPersonnamesandrelatede-mailaddresses
TheSQLquerygeneratedbyLINQtoSQLincludesanSQLINNERJOINclause
andonlyretrievesthecolumnsrequiredbythequery,asshowninListing15-19.
Listing15-19.LINQtoSQL-GeneratedSQLQuerywithanINNERJOINClause
execsp_executesql
N'SELECT[t0].[LastName],[t0].[FirstName],
[t0].[MiddleName],[t1].[EmailAddress]
FROM[Person].[Person]AS[t0]
INNERJOIN[Person].[EmailAddress]AS[t1]
ON[t0].[BusinessEntityID]=[t1].
[BusinessEntityID]
WHERE[t0].[LastName]LIKE@p0
ORDERBY[t0].[LastName],[t0].[FirstName]',N'@p0
nvarchar(5)',@p0=N'%SMI%'
DeferredQueryExecution
LINQtoSQLusesaqueryexecutionpatternknownasdeferredqueryexecution.When
youdeclareaLINQtoSQLquery,.NETcreatesanexpressiontree.Theexpressiontreeis
essentiallyadatastructurethatactsasaguidethatLINQtoSQLcanusetoexecuteyour
query.Theexpressiontreedoesn’tcontainthedataretrievedbythequery,butratherthe
informationrequiredtoexecutethequery.Deferredqueryexecutioncausestheexecution
ofthequerytobedelayeduntilthedatareturnedbythequeryisactuallyneeded—when
youiteratetheresultsinaforeachloop,forinstance.Youcanviewdeferredquery
executioninactionbyplacingbreakpointsontheforeachloopsofthecodeexamplesin
theprevioussections.LINQtoSQLwillnotgenerateandoutputitsSQLcodeuntilafter
theforeachloopiterationbegins.ThisisshowninFigure15-13.
Figure15-13.Testingdeferredqueryexecution
DeferredqueryexecutionisanimportantconceptthateveryLINQtoSQLdeveloper
needstobefamiliarwith.Ifthevalueofavariablethatthequerydependsonchanges
betweenthetimethequeryisdeclaredandthetimeit’sexecuted,thequerycanreturn
unexpectedresults.
FromLINQtoEntityFramework
AfterLINQwasdesigned,MicrosoftreleasedafullblownO/RMframeworknamed
EntityFramework(EF).LINQprovidesonlyaverybasicO/RMimplementation,where
onetableismappedtooneclass.EFoffersanabstractionlevel(aDataAccessLayer),
allowingyoutobuildaconceptualmodelandworkwithobjectsandcollectionsthatdon’t
necessarilymatchtherelationalschemaoftheunderlyingdatabaseandaren’ttiedtoa
physicalimplementation.BeforeEF,developerswhowantedtoworkwithanO/RMin
.NETmostlyusedNHibernate,theportoftheHibernateJavaFrameworkto.NET(some
arestillusingit,becauseNHibernateisfornowmorematureandfeature-richthanEF).
Microsoftcreateditsownframeworkandreleaseditin2008withthe.NETframework3.5
servicepack1.Itwasn’tperfectandgotalotofcriticism.In2010,thesecondversion,
EntityFramework4,wasreleasedwiththe.NETframework4(theversionnumberwas
obviouslychosentomatchthe.NETframeworkversion)andcorrectedmostofthe
problemsencounteredwiththefirstrelease.
EntityFramework4mapsdatabasestructuresto.NETclassesthatyoumanipulatein
yourclientapplicationlikeanyotherclasses.ThereisnocontactwithSQLcodeandno
needforanyknowledgeofthedatabasestructureorphysicalimplementation,matchinga
businessmodelratherthanaphysicalschema.ThisiscalledtheEntityDataModel
(EDM).Let’screateone.
InaVisualStudioC#project,right-clicktheprojectinSolutionExplorer.ClickAdd
NewItem,andselectADO.NETEntityDataModelintheDatasection.Enteranamefor
thedatamodel,andclickAdd.Awizardopensandaskswhetheryouwanttogeneratethe
modelfromadatabaseorcreateanemptymodel,asshowninFigure15-14.
Figure15-14.ChoosingEFmodelcontents
TheEmptyModeloptioncreatesamodel-firstEFdatamodelthatyoucoulduseto
modelaconceptualschema,createEFclasses,andgeneratedatabasetableslater.Inthis
case,chooseGenerateFromDatabase,andclickNext.Thenextstepallowsyoutochoose
orcreateadataconnectiontoSQLServer.Afterthat,youselectthedatabaseobjectsyou
wanttouseinyourmodel.Youcanaddtables,views,andstoredprocedureresults,as
showninFigure15-15.
Figure15-15.Selectingdatabaseobjects
Selectthefollowingtables:
HumanResources.Employee
Person.Person
Person.BusinessEntity
Person.EmailAddress
Person.PersonPhone
Person.PhoneNumberType
ThepageshowninFigure15-15hasacheckboxlabeledPluralizeOrSingularize
GeneratedObjectNames.ThisallowsyoutoautomaticallyapplyEnglishlanguagerules
tonameentitiesandentitysets.Ifthenamesofyourdatabasetablesareinpluralform,EF
createsentityclassesinpluralthatwilllookconfusinginyourcode.TheEntityType
generatedfromthetableskeepstheplural.Lookatthiscodeexample:
EmployeesEmployee=newEmployees();
WhatdoestheEmployeesclassrepresent?Asingleentity,soitshouldbe
Employee.ButifthedatabasetableisnamedEmployees,EFusesthisnametobuild
theentity.IfPluralizeOrSingularizeGeneratedObjectNamesischecked,EFremovesthe
s.
Whenthetablesareselected,clickFinishtoletthewizardcreatethedatamodel.The
modelgeneratedisshowninFigure15-16.
Figure15-16.Theentitydatamodel
Asyoucansee,EFcreatedoneclasspertableandkepttherelationshipsdefinedby
theforeignkeysinyourdatabaseschema.Some“new”typesaren’tyetsupportedby
EntityFramework,suchashierarchyidandspatialtypes.IfyoucreatedtheEDM
fromtheexample,yougetawarningabouttheOrganizationNodecolumnofthe
HumanResources.Employeetable,whichisahierarchyidtypecolumnand
can’tbeimportedintotheEDM.Toincludethestringrepresentationofthe
OrganizationNodecolumn,youcancreateacomputedcolumnin
HumanResources.EmployeeasshowninListing15-20.
Listing15-20.CreatingaComputedColumntoShowahierarchyidRepresentation
intheEDM
ALTERTABLE[HumanResources].[Employee]
ADDOrganizationNodeStringASOrganizationNode.ToString()
PERSISTED;
NoteThefuturereleaseofEntityFramework5willintegratespatialdatatypes.
IneachclassoftheEDM,youcanseealistofpropertiesthatarethetables’columns,
aswellasnavigationpropertiesthatreferencetheassociationbetweenentities.For
instance,thePersonPhoneentityhasanavigationpropertyreferencingthe
PhoneNumberTypeentity,andthePhoneNumberTypeentityhasanavigation
propertyreferencingthePersonPhoneentity.Eachentitythatispartofanassociationis
calledanend,andthepropertiesthatdefinethevaluesoftheassociationarecalledroles.
IfyouclickthelinejoiningthetwoentitiesintheEDMthatrepresentstheassociation,
youseetheassociation’sproperties,asshowninFigure15-17.
Figure15-17.Thepropertiesofanassociationbetweenentities
Themultiplicityofpropertiesallowyoutodefinethecardinalityofeachrole,andthe
OnDeletepropertiesreflectwhetheracascadingoptionhasbeendefinedontheforeign
keyinthedatabase.
CautionDonotsetOnDeletetocascadeinyourEDMifthereisnocascadingoption
intheforeignkeyatthedatabaselevel.EFwillassumethattheDELETEistakencareof
bythedatabaseengine.Itwillonlydeleteassociatedobjectsifthey’reinmemory.
WehavesaidthattheEDMisn’ttiedtoaphysicalimplementation.EntityFramework
maintainsthreelayersforbetterabstraction.Atdesigntime,alltheinformationisstoredin
an.edmxfile,butatruntime,EFseparatesthemodelintothreeXMLfilesthathave
differentstructures,asdetailedinTable15-6.
Table15-6.EntityFrameworkAbstractionLayers
File
extension Name Description
.csdl
Conceptual
Schema
Definition
Language
Definesaconceptualmodelthatisagnosticregardingthedatabase
physicalimplementation.Itdefinesentities,relationships,and
functions.
.ssdl
StoreSchema
Definition
Language
Describesthestoragemodeloftheconceptualschema.Itdefinesthe
nameoftheunderlyingtablesandcolumns,andthequeriesusedto
retrievethedatafromthedatabase.
.msl
Mapping
Specification
Language MapstheCSDLattributestotheSSDLcolumns.
Theselevelsallowyoutoswitchthebackenddatabasewithminimalchangetoyour
clientapplication.TherequestsyouwritetoEFaretranslatedtoSQLbehindthescenes.
QueryingEntities
OnceyouhavecreatedanEDM,youcanrefertoitinyourcodewithwhatiscalledan
objectcontext,justasyouhaveadatacontextinLINQtoSQL.TheEDMisavailableasa
classinheritingfromtheObjectContextclass.Let’sseeitinactioninListing15-21.
TheresultofthecodeexecutionisshowninFigure15-18.
Listing15-21.UsinganEFObjectContextinC#Code
usingSystem;
usingSystem.Linq;
usingSystem.Text;
namespaceEntityFramework
{
classProgram
{
staticvoidMain(string[]args)
{
using(varctx=new
AdventureWorksEntitiesEmployee())
{
varqry=fromeinctx.Employee
wheree.Gender=="F"
selectnew
{
e.Person.FirstName,
e.Person.LastName,
e.BirthDate
};
foreach(varempinqry.Take(5)){
Console.WriteLine("{0}{1},born{2}",
emp.FirstName,
emp.LastName,
emp.BirthDate.ToLongDateString()
);
}
Console.Read();
}
}
}
}
Figure15-18.Theresultofthecodeexecution
ThecodeinListing15-21isaconsoleapplication.Itreturnsfivelinesofemployees.
First,youcreateaninstanceoftheAdventureWorksEntitiesEmployeeclass,
whichinheritsfromObjectContext.Itgivesyouaccesstoentitiespresentinthe
AdventureWorksEntitiesEmployeeEDM.Thecontextallowsyoutoaccessits
entities,todefineandexecutequeries,andtoapplymodificationstodata.Youenclosethe
contextinstantiationinausingblockinordertoensurethattheinstancewillbefreedno
matterwhathappensintheblock:
using(varctx=newAdventureWorksEntitiesEmployee())
{
...
}
YoucanuseLINQqueriesagainstentities.ThisfunctionalityiscalledLINQto
Entities,andit’sverymuchlikeLINQtoSQL.InLINQtoSQLyouwouldwritethis:
varqry=fromeinctx.Employee
joinpinctx.Personone.BusinessEntityIdequals
p.BusinessEntityId
wheree.Gender=="F"
selectnew
{
p.FirstName,
p.LastName,
e.BirthDate
};
ButinEntityFramework,youcantakeadvantageofnavigationproperties,whichare
propertiesofanentitythatgiveaccesstotheotherendofanassociation.Anavigation
propertyreturnseitheroneentityoracollectionofentities,dependingonthecardinalityof
therelationship.Here,becausetheassociationis0..1,therecanbeonlyoneperson
associatedwithanemployee,soitreturnsonlyoneentityreference.Youcandirectlyuse
itspropertiestoretrievetheFirstNameandLastName.Additionally,tolimitthe
numberofpropertiesreturnedbythequery,youcreateananonymoustype(aclasswithout
aname),declaredontheflywithanew{…}constructtoretrieveonly
Person.FirstName,Person.LastName,andEmployee.BirthDate:
varqry=fromeinctx.Employee
wheree.Gender=="F"
selectnew
{
e.Person.FirstName,
e.Person.LastName,
e.BirthDate
};
Usingtheanonymoustypeimprovesperformance.InListing15-22youcanseetheT-
SQLquerygeneratedbyEFthatisretrievedusingSQLServerProfiler.
Listing15-22.T-SQLQueryGeneratedbyEF
SELECTTOP(5)
[Extent1].[BusinessEntityID]AS[BusinessEntityID],
[Extent2].[FirstName]AS[FirstName],
[Extent3].[LastName]AS[LastName],
[Extent1].[BirthDate]AS[BirthDate]
FROM[HumanResources].[Employee]AS[Extent1]
INNERJOIN[Person].[Person]AS[Extent2]ON[Extent1].
[BusinessEntityID]=[Extent2].[BusinessEntityID]
LEFTOUTERJOIN[Person].[Person]AS[Extent3]ON[Extent1].
[BusinessEntityID]=[Extent3].[BusinessEntityID]
WHEREN'F'=[Extent1].[Gender]
Youcanseethatonlytheneededcolumnsareselected.Thatreducesthecostofthe
queryandtheamountofdatathatneedstobecarriedbythequerybacktotheclient.
Then,youcansimplyloopintothequery’sresult,becausethequeryreturnsan
IQueryabledescendantobject.Tolimitthenumberofrowsreturned,youcallthe
methodTake()ontheIQueryable,whichtranslatestoaSELECTTOP,asyoucan
seeinthegeneratedT-SQLinListing15-22.Youcanalsoseethatdeferredexecutionis
workinginEntityFramework.Finally,youformattheBirthDatecolumn/propertyto
displayauser-friendlybirthdate:
foreach(varempinqry.Take(5)){
Console.WriteLine("{0}{1},born{2}",
emp.FirstName,
emp.LastName,
emp.BirthDate.ToLongDateString()
);
}
Sothatyoucanseetheresultintheconsolebeforeitdisappears,youaddacallto
Console.Read(),whichmakestheconsolewaituntilakeyispressed.
ThecontextcanalsogiveyoudirectaccesstoentitiesintheformofanObjectSet.
YoucanthinkofanObjectSetasakindofresultset.Youcandirectlycallan
ObjectSetandenumeratethroughit.So,thecodeinListing15-21canberewrittenas
inListing15-23.
Listing15-23.UsinganEFObjectSet
usingSystem;
usingSystem.Linq;
usingSystem.Text;
namespaceEntityFramework
{
classProgram
{
staticvoidMain(string[]args)
{
using(varctx=new
AdventureWorksEntitiesEmployee())
{
foreach(varempinctx.Employee.Where(e=>
e.Gender=="F").Take(5))
{
Console.WriteLine("{0}{1},born{2}",
emp.Person.FirstName,
emp.Person.LastName,
emp.BirthDate.ToLongDateString()
);
}
Console.Read();
}
}
}
}
Listing15-23directlyusestheEmployeeObjectSet.Youcanstillfilteritby
usingitsWhere()method.IncontrasttotheLINQqueryapproach,thisiscalleda
method-basedquery.TheWhere()methodtakesalambdaexpressionasitsparameter.
LambdaexpressionsareawaytoexpressparameterswithasyntaxderivedfromLambda
calculus,aformalnotationinmathematicallogic.YoucanuseLINQqueriesormethod-
basedquerying;choosewhatfeelsmorenaturaltoyou.
Finally,Listing15-24showsanexampleofdatamodificationwithEntityFramework.
TheresultisshowninFigure15-19.
Listing15-24.ModifyingDatainEF
usingSystem;
usingSystem.Linq;
usingSystem.Text;
namespaceEntityFramework
{
classProgram
{
staticvoidMain(string[]args)
{
using(varctx=new
AdventureWorksEntitiesEmployee())
{
varnewP=newBusinessEntity{
ModifiedDate=DateTime.Now,
rowguid=Guid.NewGuid()
};
Console.WriteLine("BusinessEntityIDbefore
insert:{0}",
newP.BusinessEntityID);
ctx.BusinessEntities.AddObject(newP);
ctx.SaveChanges();
Console.WriteLine("BusinessEntityIDafter
insert:{0}",
newP.BusinessEntityID);
}
Console.Read();
}
}
}
Figure15-19.Theresultofthecodeexecution
Thereareseveralwaystoinsertnewdata.Thisexampleusestheobject-initializer
syntax,availablesince.NET3.5.Intheobject-initializerblock,youassignvaluestothe
twopropertiesoftheBusinessEntityentitythatneedtobepopulated.The
ModifiedDatepropertyisadatetime,soyouusetheDateTime.Nowpropertyto
setthecurrentdateandtime;therowguidpropertystoresauniqueidentifier,so
youusetheGuid.NewGuid()methodtoretrieveavalue.Whentheobjectisfully
populated,youcanaddittotheentitybyusingtheAddObject()method:
varnewP=newBusinessEntity{
ModifiedDate=DateTime.Now,
rowguid=Guid.NewGuid()
};
...
ctx.BusinessEntities.AddObject(newP);
Thechangesmadetoentitiesarestoredinacollectioninthecontextandareappliedto
theunderlyingdatabaseonlywhentheSaveChanges()methodofthecontextiscalled.
ToseewhathappenswiththeBusinessEntityIdidentitycolumn,youreturnits
valuebeforeandafterthecalltoSaveChanges().Thekeyvalueisautomaticallyset
byEFtomatchtheidentityvaluegeneratedbySQLServer.ThequeryissuedbyEFwhen
youcallSaveChanges()isshowninListing15-25.
Listing15-25.TheDMLQueryGeneratedbyEF
execsp_executesql
N'insert[Person].[BusinessEntity]([rowguid],
[ModifiedDate])
values(@0,@1)
select[BusinessEntityID]
from[Person].[BusinessEntity]
where@@ROWCOUNT>0
and[BusinessEntityID]=scope_identity()',
N'@0uniqueidentifier,@1datetime2(7)',
@0='92EEC64E-BD11-4936-97C3-6528B5D1D97D',
@1='2012-05-2115:14:05.3493966'
Asyoucansee,aSELECTisissuedaftertheINSERToperationtoretrievethe
identityvalueandreturnittoEF.
ThissectionhasonlyscratchedthesurfaceofEntityFramework.Youhavetobeaware
ofit,evenifyoudon’tdoanyclientcoding,becauseit’sthewayofthefuturein.NET
dataaccess.Thequestionofwhetherthisagoodorbadthingis,fortunately(weadmita
bitofcowardicehere),outsidethescopeofthisbook.ThethinkingbehindLINQandEF
istoabstractoutdatabaseaccessandtohidetheT-SQLlanguagefromdevelopers,which
isconsideredapainbymanyclient-sidedevelopers.Thistrendpushestowardconsidering
DBMSstobejustdatastores.Inthismodel,objectslikeviewsandstoredprocedureshave
lessimportance,andthecraftsmanshipofwritinggoodT-SQLqueriesseemsoutdated.
Thishasadvantagesandpitfalls,themoreimportantofthelatterbeingperformanceissues
incomplexqueries.ThebestwaytoaddressthisproblemistobecomeproficientinEF;
andtostartonthatpath,youcanreadProEntityFramework4.0byScottKlein(Apress,
2010).
Summary
Althoughthefocusofthisbookisserver-sidedevelopment,agooddatabaseisusefulonly
ifenduserscanaccessthedatacontainedinitefficiently.That’swhereanefficientand
well-designedclient-sideapplicationcomesin.Thischapterdiscussedseveraloptions
availableforconnectingtoSQLServer2014from.NET.
ThechapterbeganwithadiscussionoftheADO.NETnamespacesandthe.NETSQL
ServerNativeClient(SqlClient),includingconnecteddataaccess,whichrequires
constantdatabaseconnectivity,anddisconnecteddatasets,whichallowuserstocachedata
locallyandconnecttoadatabaseasneeded.Although.NEToffersotheroptionsfor
connectingtoSQLServer,includingOLEDBandODBC,theprimarymethodof
connectingtoSQLServer(version7.0andhigher)isencapsulatedinADO.NETandthe
System.Data.SqlClientnamespace.
Youalsolearnedaboutparameterizedqueries,includingthetopicsofsecurityand
SQLinjection.Othertopicscoveredincludedthevariousmethodsandoptionsthat.NET
providestoquerySQLServer,bulk-copydataintoSQLServer,andopenmultipleresult
setsoverasingleactivedatabaseconnection.
YouroundedoutthischapterwithadiscussionoftheO/RMfunctionalitiesprovided
by.NETandVisualStudio.VisualStudio’sbuilt-invisualdesignerandautomatedclass
generationcanmakelightworkofmanyO/RMapplications.Theabilitytoabstractout
databaseaccessandtowritedeclarativeLINQtoSQLqueriesdirectlyinproceduralcode
elevatesdataqueryingtothelevelofafirst-classprogrammingconcept.
EXERCISES
1. [True/False]TheSystem.Data.SqlClientnamespace
providesoptimizedaccesstoSQLServerviatheSQLServerNative
Clientlibrary.
2. [Chooseone]Whichofthefollowingconceptsallowsforlocal
cachingofdata,withestablishmentofdatabaseconnectionsonan
as-neededbasis?
a. Connecteddataaccess
b. Disconnecteddatasets
c. Casualdataaccess
d. Partialdatasets
3. [Chooseallthatapply]Whichofthefollowingarebenefitsofquery
parameterization?
a. ProtectionagainstSQLinjectionattacks
b. Conversionofleadtogold
c. Increasedefficiencythroughqueryplanreuse
d. Decreasedpowerconsumptionbyatleast25%
4. [True/False]TurningonMARSbysetting
MULTIPLEACTIVERESULTSETS=trueinyourconnection
stringallowsyoutoopentworesultsetsbutrequiresatleasttwo
openconnections.
5. [True/False]VisualStudioincludesadrag-and-dropvisualO/RM
designer.
6. [Chooseone]LINQtoSQLuseswhichofthefollowingquery
executionpatterns?
a. Instantqueryexecution
b. Fast-forwardqueryexecution
c. Randomqueryexecution
d. Deferredqueryexecution
CHAPTER16
CLRIntegrationProgramming
OneofthemostprominentenhancementstoSQLServer2005wastheintroductionofthe
integratedSQLCommonLanguageRuntime,namedSQLCLRatthattime.Whatisnow
calledCLRintegrationisanSQLServer–specificversionofthe.NETCommonLanguage
Runtime,whichallowsyoutorun.NET-managedcodeinthedatabase.CLRintegration
programmingisabroadsubjectthatcouldeasilyfillanentirebook,andinfactitdoes—
ProSQLServer2005Assemblies,byRobinDewsonandJulianSkinner(Apress,2005),is
anexcellentresourceforin-depthcoverageofCLRintegrationprogramming.Thischapter
discussesthemethodsusedtoextendSQLServerfunctionalityinthepastandexplainsthe
basicsoftheCLRintegrationprogrammingmodelinSQLServer2014.
TheOldWay
InversionsofSQLServerpriortothe2005release,developerscouldextendSQLServer
functionalitybywritingextendedstoredprocedures(XPs).Writinghigh-qualityXPs
requiredastrongknowledgeoftheOpenDataServices(ODS)libraryandthepoorly
documentedC-styleExtendedStoredProcedureAPI.Anyonewhoattemptedtheoldstyle
ofXPprogrammingcantellyouitwasacomplexundertaking,inwhichasinglemisstep
couldeasilyresultinmemoryleaksandcorruptionoftheSQLServerprocessspace.
Additionally,thethreadingmodelusedbyXPsrequiredSQLServertorelyonthe
operatingsystemtocontrolthreadingintheXP.Thiscouldleadtomanyissues,suchas
unresponsivenessofXPcode.
CautionXPshavebeendeprecatedsinceSQLServer2005.UseCLRintegration
insteadofXPsforSQLServer2014development.
EarlierSQLServerreleasesalsoallowedyoutocreateOLEAutomationserverobjects
viathespOACreateSP.CreatingOLEAutomationserverscanbecomplexand
awkwardaswell.OLEAutomationserverscreatedwithspOACreatecanresultin
memoryleaksandinsomeinstancescorruptionoftheSQLServerprocessspace.
AnotheroptioninpreviousversionsofSQLServerwastocodeallbusinesslogic
exclusivelyinphysicallyseparatebusinessobjects.Althoughthismethodispreferredby
manydevelopersandadministrators,itcanresultinextranetworktrafficandalessrobust
securitymodelthancanbeachievedthroughtightintegrationwiththeSQLServer
securitymodel.
TheCLRIntegrationWay
TheCLRintegrationprogrammingmodelprovidesseveraladvantagesoveroldermethods
ofextendingSQLServerfunctionalityviaXPs,OLEAutomation,orexternalbusiness
objects.Theseadvantagesincludethefollowing:
AmanagedcodebasethatrunsontheCLRintegration.NET
FrameworkismanagedbytheSQLServerOperatingSystem(SQL
OS).ThismeansSQLServercanproperlymanagethreading,memory
usage,andotherresourcesaccessedviaCLRintegrationcode.
TightintegrationoftheCLRintoSQLServermeansSQLServercan
providearobustsecuritymodelforrunningcodeandmaintainstricter
controloverdatabaseobjectsandexternalresourcesaccessedbyCLR
code.
CLRintegrationismorethoroughlydocumentedinmoreplacesthan
theExtendedStoredProcedureAPIeverwas(orpresumablyeverwill
be).
CLRintegrationdoesn’ttieyoutotheClanguage-basedExtended
StoredProcedureAPI.Intheory,the.NETprogrammingmodel
doesn’ttieyoutoanyonespecificlanguage(althoughyoucan’tuse
dynamiclanguageslikeIronPythoninCLRintegration).
CLRintegrationallowsaccesstothefamiliar.NETnamespaces,data
types,andmanagedobjects,easingdevelopment.
CLRintegrationintroducesSQLServer–specificnamespacesthat
allowdirectaccesstotheunderlyingSQLServerdatabasesand
resources,whichcanbeusedtolimitorreducenetworktraffic
generatedbyusingexternalbusinessobjects.
There’samisperceptionexpressedbysomethatCLRintegrationisareplacementfor
T-SQLaltogether.CLRintegrationisn’tareplacementforT-SQL,butratherasupplement
thatworkshandinhandwithT-SQLtomakeSQLServer2014morepowerfulthanever.
SowhenshouldyouuseCLRcodeinyourdatabase?Therearenohardandfastrules
concerningthis,butherearesomegeneralguidelines:
ExistingcustomXPsonolderversionsofSQLServerareexcellent
candidatesforconversiontoSQLServerCLRintegrationassemblies
—thatis,ifthefunctionalityprovidedisn’talreadypartofSQLServer
2014T-SQL(forexample,encryption).
Codethataccessesexternalserverresources,suchascallsto
xpcmdshell,arealsoexcellentcandidatesforconversiontomore
secureandrobustCLRassemblies.
T-SQLcodethatperformslotsofcomplexcalculationsandstring
manipulationscanbeastrongcandidateforconversiontoCLR
integrationassemblies.
Highlyproceduralcodewithlotsofprocessingstepsmightbe
consideredforconversion.
Externalbusinessobjectsthatpulllargeamountsofdataacrossthe
wireandperformalotofprocessingonthatdatamightbeconsidered
forconversion.Youmightfirstconsiderthesebusinessobjectsfor
conversiontoT-SQLSPs,especiallyiftheydon’tperformmuch
processingonthedatainquestion.
Ontheflipside,herearesomegeneralguidelinesforitemsthatshouldnotbe
convertedtoCLRintegrationassemblies
Externalbusinessobjectsthatpullrelativelylittledataacrossthewire,
orthatpullalotofdataacrossthewirebutperformlittleprocessing
onthatdata,aregoodcandidatesforconversiontoT-SQLSPsinstead
ofCLRassemblies.
T-SQLcodeandSPsthatdon’tperformmanycomplexcalculationsor
stringmanipulationsgenerallywon’tbenefitfromconversiontoCLR
assemblies.
T-SQLcanbeexpectedtoalwaysbefasterthanCLRintegrationfor
set-basedoperationsondatastoredinthedatabase.
YoumightnotbeabletointegrateCLRassembliesintodatabasesthat
arehostedonanInternetServiceProvider’s(ISP’s)server,iftheISP
didn’tallowCLRintegrationatthedatabase-serverlevel.Thisis
mainlyforsecurityreasonsandbecausetherecanbelesscontrolof
thecodeinanassembly.
CLRintegrationisn’tsupportedontheSQLAzureplatform.
AswithT-SQLSPs,thedecisionaboutwhetherandtowhatextenttouseCLR
integrationinyourdatabasesdependsonyourneeds,includingorganizationalpoliciesand
procedures.Therecommendationspresentedhereareguidelinesofinstancesthatcan
makegoodbusinesscasesforconversionofexistingcodeandcreationofnewcode.
CLRIntegrationAssemblies
CLRintegrationexposes.NETmanagedcodetoSQLServerviaassemblies.Anassembly
isacompiled.NETmanagedcodelibrarythatcanberegisteredwithSQLServerusingthe
CREATEASSEMBLYstatement.Publiclyaccessiblemembersofclassesintheassemblies
arethenreferencedintheappropriateCREATEstatements,describedlaterinthischapter.
CreatingaCLRintegrationassemblyrequiresthatyoudothefollowing:
1. Designandprogram.NETclassesthatpubliclyexposethe
appropriatemembers.
2. Compilethe.NETclassesintomanagedcodeDLLmanifestfiles
containingtheassembly.
3. RegistertheassemblywithSQLServerviatheCREATE
ASSEMBLYstatement.
4. Registertheappropriateassemblymembersviatheappropriate
CREATEFUNCTION,CREATEPROCEDURE,CREATETYPE,
CREATETRIGGER,orCREATEAGGREGATEstatements.
CLRintegrationprovidesadditionalSQLServer–specificnamespaces,classes,and
attributestofacilitatedevelopmentofassemblies.VisualStudio2010,VisualStudio2011,
andVisualStudio13alsoincludeanSQLServerprojecttypethatassistsinquickly
creatingassemblies.Inaddition,tomaximizeyourSQLServerdevelopmentpossibilities
withVisualStudio,youcaninstalltheSQLServerDataTools(SSDT)fromtheMicrosoft
DataDeveloperCenterwebsite(http://msdn.microsoft.com/en-
us/data/tools.aspx)whichprovidesanintegratedenvironmentfordatabase
developersinVisualStudiobyallowingyoutocreateandmanagedatabaseobjectsand
dataandtoexecuteT-SQLqueriesdirectly.
PerformthefollowingstepstocreateanewassemblyusingVisualStudio2013:
1. SelectFile NewProjectfromthemenu.
2. GotoInstalled Templates SQLServer,asshowninFigure16-
1.
Figure16-1.VisualStudio2013NewProjectdialogbox
3. AnewSQLServerdatabaseprojectiscreatedintheSQLServer
2014targetplatform.Youcanverifythetargetplatformbyselecting
Project CLRDemoProperties,asshowninFigure16-2.This
bringsupthepropertiesofyourcurrentproject,whereyoucan
verifythetargetplatform;seeFigure16-3.
Figure16-4.Addinganewitemtoyourproject
5. VisualStudioasksyoutoselectthetypeofitemyouwouldliketo
add.ThisisdifferentfrompreviewversionofVisualStudio,where
youstartedfromaCLRProjecttype.Thisisnowtreatedaspartof
thedatabaseproject,andthereisanewoption:SQLCLRC#User
DefinedType(seeFigure16-5).Selectthisoption.
Figure16-5.AddinganewSQLCLRC#UserDefinedTypetoyourproject
6. VisualStudioautomaticallygeneratesatemplatefortheitemyou
selectinthelanguageofyourchoice,completewiththeappropriate
ImportsstatementsinVB.NETorusinginC#.
Inadditiontothestandard.NETnamespacesandclasses,CLRintegrationimplements
someSQLServer–specificnamespacesandclassestosimplifyinterfacingyourcodewith
SQLServer.Someofthemostcommonlyusednamespacesincludethefollowing:
System,whichincludesthebase.NETdatatypesandtheObject
baseclassfromwhichall.NETclassesinherit.
System.Data,whichcontainstheDataSetclassandotherclasses
forADO.NETdatamanagement.
System.Data.SqlClient,whichcontainstheSQLServer–
specificADO.NETdataprovider.
System.Data.SqlTypes,whichcontainsSQLServerdatatypes.
Thisisimportantbecause(unlikethestandard.NETdatatypes)these
typescanbesettoSQLNULLandaredefinedtoconformtothesame
operatorrules,behaviors,precision,andscaleastheirSQLServer
typecounterparts.
Microsoft.SqlServer.Server,whichcontainsthe
SqlContextandSqlPipeclassesthatallowassembliesto
communicatewithSQLServer.
Oncetheassemblyiscreatedandcompiled,it’sregisteredwithSQLServerviathe
CREATEASSEMBLYstatement.Listing16-1demonstratesaCREATEASSEMBLY
statementthatregistersaCLRintegrationassemblywithSQLServerfromanexternal
DLLfile.TheDLLfileusedintheexampleisn’tsuppliedinprecompiledforminthe
sampledownloadsforthisbookavailableontheApresswebsite,butyoucancompileit
yourselffromthecodeintroducedinListing16-2.CLRintegrationisn’tenabledby
default,soyoualsoneedtoenableitattheserverlevel.Here,youdothatusingthe
sp_configuresystemstoredprocedurepriortorunningtheCREATEASSEMBLY
statement.(CREATEASSEMBLYwouldsucceedevenifCLRintegrationwasdisabled;
anerrorwouldberaisedbySQLServeronlywhenaCLRintegrationcodemodulewas
calledbyauserlater.)TheRECONFIGUREstatementappliestheconfigurationchange
immediately.
Listing16-1.RegisteringaCLRIntegrationAssemblywithSQLServer
EXECsp_configure'CLREnabled';
RECONFIGURE;
CREATEASSEMBLYApressExamples
AUTHORIZATIONdbo
FROMN'C:\MyApplication\CLRDemo.DLL'
WITHPERMISSION_SET=SAFE;
GO
NoteThesecondportionofListing16-1won’tsucceeduntilyouhavecreatedtheDLL
showninListing16-2.Additionally,thelocationoftheDLLisdependentontheBuild
OutputPathsettingofyourdatabaseproject.SeeFigure16-6andFigure16-7fordetails.
TheCREATEASSEMBLYstatementintheexamplespecifiesanassemblynameof
EmailUDF.ThisnamemustbeavalidSQLServeridentifier,anditmustbeuniqueinthe
database.Youusethisassemblynamewhenreferencingtheassemblyinotherstatements.
TheAUTHORIZATIONclausespecifiestheowneroftheassembly,inthiscasedbo.
IfyouleaveouttheAUTHORIZATIONclause,itdefaultstothecurrentuser.
TheFROMclauseinthisexamplespecifiesthefullpathtotheexternalDLLfile.
Alternatively,youcanspecifyavarbinaryvalueinsteadofacharacterfilename.If
youuseavarbinaryvalue,SQLServerusesit,asit’salongbinarystringrepresenting
thecompiledassemblycode,andnoexternalfileneedstobespecified.
Finally,theWITHPERMISSION_SETclausegrantsasetofCodeAccessSecurity
(CAS)permissionstotheassembly.Validpermissionsetsincludethefollowing:
TheSAFEpermissionsetisthemostrestrictive,preventingthe
assemblyfromaccessingsystemresourcesoutsideofSQLServer.
SAFEisthedefault.
EXTERNAL_ACCESSallowsassembliestoaccesssomeexternal
resources,suchasfiles,thenetwork,theregistry,andenvironment
variables.
UNSAFEallowsassembliesunlimitedaccesstoexternalresources,
includingtheabilitytoexecuteunmanagedcode.
Aftertheassemblyisinstalled,youcanusevariationsoftheT-SQLdatabaseobject-
creationstatements(suchasCREATEFUNCTIONorCREATEPROCEDURE)toaccess
themethodsexposedbytheassemblyclasses.Thesestatementsaredemonstrated
individuallyinthefollowingsections.
User-DefinedFunctions
CLRintegrationUDFsthatreturnscalarvaluesaresimilartostandard.NETfunctions.
Theprimarydifferencesfromstandard.NETfunctionsarethattheSqlFunction
attributemustbeappliedtothemainfunctionofCLRintegrationfunctionsifyou’reusing
VisualStudiotodeployyourfunctionorifyouneedtosetadditionalattributevalueslike
IsDeterministicandDataAccess.Listing16-2demonstratesascalarUDFthat
acceptsaninputstringvalueandaregularexpressionpatternandreturnsabitvalue
indicatingamatch(1)ornomatch(0).TheUDFisnamedEmailMatch()andis
declaredasamethodoftheUDFExampleclassintheApress.Examplenamespace
usedforalltheexamplesinthischapter.
Listing16-2.RegularExpressionMatchUDF
usingSystem.Data.SqlTypes;
usingSystem.Text.RegularExpressions;
namespaceApress.Examples
{
publicstaticclassUDFExample
{
privatestaticreadonlyRegexemail_pattern=new
Regex
(
//Everythingbeforethe@sign(the"localpart")
"^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?
^_`{|}~-]+)*"+
//Subdomainsafterthe@sign
"@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+"+
//Top-leveldomains
"(?:[a-z]
{2}|com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum)\\b$"
);
[Microsoft.SqlServer.Server.SqlFunction
(
IsDeterministic=true
)]
publicstaticSqlBooleanEmailMatch(SqlStringinput)
{
SqlBooleanresult=newSqlBoolean();
if(input.IsNull)
result=SqlBoolean.Null;
else
result=(email_pattern.IsMatch(input.Value.ToLower())
==true)
?SqlBoolean.True:SqlBoolean.False;
returnresult;
}
}
}
Inordertocompilethiscode,youmustbuildyourdatabaseproject(seeFigure16-6).
DoingsocreatestheDLLthatyoudeploytoyourdatabase.
Figure16-7.Buildlocationofobjectsinyourproject
ThefirstpartofListing16-2specifiestherequirednamespacestoimport.ThisUDF
usestheSystem.Data.SqlTypesandSystem.Text.RegularExpressions
namespaces:
usingSystem.Data.SqlTypes;
usingSystem.Text.RegularExpressions;
TheUDFExampleclassandtheEmailMatchfunctionitexposesarebothdeclared
static.CLRintegrationfunctionsneedtobedeclaredasstatic.Astaticfunctionis
sharedamongallinstancesoftheclass.Here,theclassitselfisalsostatic,soitcan’tbe
instantiated;thisallowstheclasstobeloadedmorequicklyanditsmemorytobeshared
betweenSQLServersessions.Thefunctionisdecoratedwiththe
Microsoft.SqlServer.Server.SqlFunctionattributewiththe
IsDeterministicpropertysettotruetoindicatethefunctionisadeterministic
CLRintegrationmethod.Thefunctionbodyisrelativelysimple.Itacceptsan
SqlStringinputstringvalue.IftheinputstringisNULL,thefunctionreturnsNULL;
otherwisethefunctionusesthe.NETRegex.IsMatchfunctiontoperformaregular
expressionmatch.Iftheresultisamatch,thefunctionreturnsabitvalueof1;otherwiseit
returns0:
publicstaticclassUDFExample
{
privatestaticreadonlyRegexemail_pattern=newRegex
(
//Everythingbeforethe@sign(the"localpart")
"^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?
^_`{|}~-]+)*"+
//Subdomainsafterthe@sign
"@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+"+
//Top-leveldomains
"(?:[a-z]
{2}|com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum)\\b$"
);
[Microsoft.SqlServer.Server.SqlFunction
(
IsDeterministic=true
)]
publicstaticSqlBooleanEmailMatch(SqlStringinput)
{
SqlBooleanresult=newSqlBoolean();
if(input.IsNull)
result=SqlBoolean.Null;
else
result=(email_pattern.IsMatch(input.Value.ToLower())
==true)
?SqlBoolean.True:SqlBoolean.False;
returnresult;
}
}
TheregularexpressionpatternusedinListing16-2wascreatedbyJanGoyvaertsof
Regular-Expressions.info(www.regular-expressions.info).Jan’sregular
expressionvalidatese-mailaddressesaccordingtoRFC2822,thestandardfore-mail
addressformats.Althoughnotperfect,Janestimatesthatthisregularexpressionmatches
over99%of“e-mailaddressesinactualusetoday.”Performingthistypeofe-mailaddress
validationusingonlyT-SQLstatementswouldbecumbersome,complex,andinefficient.
TipIt’sconsideredgoodpracticetousetheSQLServerdatatypesforparametersand
returnvaluestoCLRIntegrationmethods(SqlString,SqlBoolean,SqlInt32,
andsoon).Standard.NETdatatypeshavenoconceptofSQLNULLandwillerroroutif
NULLispassedinasaparameter,calculatedinthefunction,orreturnedfromthefunction.
AftertheassemblyisinstalledviatheCREATEASSEMBLYstatementyouwrotein
Listing16-1,thefunctioniscreatedwiththeCREATEFUNCTIONstatementusingthe
EXTERNALNAMEclause,asshowninListing16-3.
Listing16-3.CreatingaCLRUDFfromtheAssemblyMethod
CREATEFUNCTIONdbo.EmailMatch(@inputnvarchar(4000))
RETURNSbit
WITHEXECUTEASCALLER
AS
EXTERNALNAMEApressExamples.
[Apress.Examples.UDFExample].EmailMatch
GO
Afterthis,theCLRfunctioncanbecalledlikeanyotherT-SQLUDF,asshownin
Listing16-4.TheresultsareshowninFigure16-8.
Listing16-4.ValidatingE-mailAddresseswithRegularExpressions
SELECT
'nospam-123@yahoo.com'ASEmail,
dbo.EmailMatch(N'nospam-123@yahoo.com')ASValid
UNION
SELECT
'123@456789',
dbo.EmailMatch('123@456789')
UNION
SELECT'BillyG@HOTMAIL.COM',
dbo.EmailMatch('BillyG@HOTMAIL.COM');
Figure16-8.Resultsofe-mailaddressvalidationwithregularexpressions
TipNormallyyoucanautomatetheprocessofcompilingyourassembly,registeringit
withSQLServer,andinstallingtheCLRIntegrationUDFwithVisualStudio’sBuildand
Deployoption.YoucanalsotesttheCLRIntegrationUDFwiththeVisualStudioDebug
andStartDebuggingoption.Thisdoesn’tworkwithVisualStudio2010,becauseit
doesn’trecognizeSQLServer2012,whichwasreleasedafterVisualStudio.InVisual
Studio11and2013,youcandeploytheassemblywithVisualStudio.Thisisjustadetail;
it’sstraightforwardtocopytheassemblyontheserverandregisteritmanuallywith
CREATEASSEMBLYasshowninListing16-1.
Asmentionedpreviously,CLRUDFsalsoallowtabularresultstobereturnedtothe
caller.ThisexampledemonstratesanothersituationinwhichCLRintegrationcanbea
usefulsupplementtoT-SQLfunctionality:accessingexternalresourcessuchasthefile
system,networkresources,oreventheInternet.Listing16-5usesaCLRfunctionto
retrievetheYahooTopNewsStoriesRSSfeedandreturntheresultsasatable.Table-
valuedCLRUDFsarealittlemorecomplexthanscalarfunctions.Thiscodecouldbe
addedtothesameVisualStudioprojectthatyoucreatedforthefirstCLRfunction
example.HereyoucreateanotherclassnamedYahooRSS.
Listing16-5.RetrievingtheYahooTopNewsStoriesRSSFeed
usingSystem;
usingSystem.Collections;
usingSystem.Data.SqlTypes;
usingMicrosoft.SqlServer.Server;
usingSystem.Xml;
namespaceApress.Examples{
publicpartialclassYahooRSS{
[Microsoft.SqlServer.Server.SqlFunction(
IsDeterministic=false,
DataAccess=DataAccessKind.None,
TableDefinition="titlenvarchar(256),"
+"linknvarchar(256),"
+"pubdatedatetime,"
+"descriptionnvarchar(max)",
FillRowMethodName="GetRow")
]
publicstaticIEnumerableGetYahooNews(){
XmlTextReaderxmlsource=
new
XmlTextReader("http://rss.news.yahoo.com/rss/topstories");
XmlDocumentnewsxml=newXmlDocument();
newsxml.Load(xmlsource);
xmlsource.Close();
returnnewsxml.SelectNodes("//rss/channel/item");
}
privatestaticvoidGetRow(
Objecto,
outSqlStringtitle,
outSqlStringlink,
outSqlDateTimepubdate,
outSqlStringdescription)
{
XmlElementelement=(XmlElement)o;
title
=element.SelectSingleNode("./title").InnerText;
link
=element.SelectSingleNode("./link").InnerText;
pubdate
=DateTime.Parse(element.SelectSingleNode("./pubDate").InnerText);
description
=element.SelectSingleNode("./description").InnerText;
}
}
}
Beforesteppingthroughthesourcelisting,let’saddresssecurity,becausethisfunction
accessestheInternet.Becausethefunctionneedstoaccessanexternalresource,itrequires
EXTERNAL_ACCESSpermissions.Inordertodeployanon-SAFEassembly,oneoftwo
setsofconditionsmustbemet:
ThedatabasemustbemarkedTRUSTWORTHY,andtheuserinstalling
theassemblymusthaveEXTERNALACCESSASSEMBLYor
UNSAFEASSEMBLYpermission.
Ortheassemblymustbesignedwithanasymmetrickeyorcertificate
associatedwithaloginthathasproperpermissions.
Tomeetthefirstsetofrequirements,dothefollowing:
1. ExecutetheALTERDATABASEAdventureWorksSET
TRUSTWORTHYON;statement.
2. InVisualStudio,selectProject CLRDemoProperties
SQLCLR,andchangethepermissionlevelto
EXTERNAL_ACCESS(seeFigure16-9).
Figure16-9.AlterthepermissionlevelofyourdatabaseprojectSQLCLR
3. IfyoumanuallyimporttheassemblyintoSQLServer,specifythe
EXTERNAL_ACCESSpermissionsetwhenissuingtheCREATE
ASSEMBLYstatement,asshowninListing16-6.
Listing16-6.CREATEASSEMBLYwithEXTERNAL_ACCESSPermissionSet
CREATEASSEMBLYApressExample
AUTHORIZATIONdbo
FROMN'C:\MyApplication\CLRDemo.DLL'
WITHPERMISSION_SET=EXTERNAL_ACCESS;
Asmentionedpreviously,signingassembliesisbeyondthescopeofthisbook.You
canfindadditionalinformationonsigningassembliesinthisMSDNDataAccess
Technologiesblogentry:
http://blogs.msdn.com/b/dataaccesstechnologies/archive/2011/10/29/deploying-
sql-clr-assembly-using-asymmetric-key.aspx.
Thecodelistingbeginswiththeusingstatements.Thisfunctionrequirestheaddition
oftheSystem.XmlnamespaceinordertoparsetheRSSfeedandthe
System.Collectionsnamespacetoallowthecollectiontobesearched,amongother
functionalityspecifictocollections:
usingSystem;
usingSystem.Collections;
usingSystem.Data.SqlTypes;
usingMicrosoft.SqlServer.Server;
usingSystem.Xml;
TheprimarypublicfunctionagainrequiresthattheSqlFunctionattributebe
declared.Thistimeseveraladditionalattributesneedtobedeclaredwithit:
[Microsoft.SqlServer.Server.SqlFunction(
IsDeterministic=false,
DataAccess=DataAccessKind.None,
TableDefinition="titlenvarchar(256),"
+"linknvarchar(256),"
+"pubdatedatetime,"
+"descriptionnvarchar(max)",
FillRowMethodName="GetRow")
]
publicstaticIEnumerableGetYahooNews()
{
XmlTextReaderxmlsource=
new
XmlTextReader("http://rss.news.yahoo.com/rss/topstories");
XmlDocumentnewsxml=newXmlDocument();
newsxml.Load(xmlsource);
xmlsource.Close();
returnnewsxml.SelectNodes("//rss/channel/item");
}
YouspecificallysettheIsDeterministicattributetofalsethistimetoindicate
thatthecontentsofanRSSfeedcanchangebetweencalls,makingthisUDF
nondeterministic.Becausethefunctiondoesn’treaddatafromsystemtablesusingthein-
processdataprovider,theDataAccessattributeissettoDataAccessKind.None.
ThisCLRTVFalsosetstheadditionalTableDefinitionattributedefiningthe
structureoftheresultsetforVisualStudio.Inaddition,itneedsthe
FillRowMethodNameattributetodesignatethefill-rowmethod.Thefill-rowmethod
isausermethodthatconvertseachelementofanIEnumerableobjectintoanSQL
Serverresultsetrow.
ThepublicfunctionisdeclaredtoreturnanIEnumerableresult.Thisparticular
functionopensanXmlTextReaderthatretrievestheYahooTopNewsStoriesRSSfeed
andstoresitinanXmlDocument.ThefunctionthenusestheSelectNodesmethodto
retrievenewsstorysummariesfromtheRSSfeed.TheSelectNodesmethodgenerates
anXmlNodeList.TheXmlNodeListclassimplementstheIEnumerableinterface.
Thisisimportantbecausethefill-rowmethodisfiredonceforeachobjectreturnedbythe
IEnumerablecollectionreturned(inthiscase,theXmlNodeList).
TheGetRowmethodisdeclaredasaC#voidfunction,whichmeansnovalueis
returnedbythefunction;themethodcommunicateswithSQLServerviaitsout
parameters.ThefirstparameterisanObjectpassedbyvalue—inthiscase,an
XmlElement.Theremainingparameterscorrespondtothecolumnsoftheresultset.The
GetRowmethodcaststhefirstparametertoanXmlElement(theparametercan’tbe
directlyanXmlElementbecausethefill-rowmethodsignaturemusthaveanObjectas
thefirstparameter).ItthenusestheSelectSingleNodemethodandInnerText
propertytoretrievethepropertextfromindividualchildnodesoftheXmlElement,
assigningeachtothepropercolumnsoftheresultsetalongtheway:
privatestaticvoidGetRow(
Objecto,
outSqlStringtitle,
outSqlStringlink,
outSqlDateTimepubdate,
outSqlStringdescription)
{
XmlElementelement=(XmlElement)o;
title=element.SelectSingleNode("./title").InnerText;
link=element.SelectSingleNode("./link").InnerText;
pubdate
=DateTime.Parse(element.SelectSingleNode("./pubDate").InnerText);
description
=element.SelectSingleNode("./description").InnerText;
}
TheCLRTVFcanbecalledwithaSELECTquery,asshowninListing16-7.The
resultsareshowninFigure16-10.
Listing16-7.QueryingaCLRIntegrationTVF
CREATEFUNCTIONdbo.GetYahooNews()
RETURNSTABLE(titlenvarchar(256),linknvarchar(256),
pubdatedatetime,descriptionnvarchar(max))
ASEXTERNALNAMEApressExamples.
[Apress.Examples.YahooRSS].GetYahooNews
GO
SELECT
title,
link,
pubdate,
description
FROMdbo.GetYahooNews();
Figure16-10.RetrievingtheYahooRSSfeedwiththeGetYahooNews()function
StoredProcedures
CLRintegrationSPsprovideanalternativetoextendSQLServerfunctionalitywhenT-
SQLSPsjustwon’tdo.Ofcourse,likeotherCLRintegrationfunctionality,thereisa
certainamountofoverheadinvolvedwithCLRSPs,andyoucanexpectthemtobeless
efficientthancomparableT-SQLcodeforset-basedoperations.Ontheotherhand,ifyou
needtoaccess.NETfunctionalityorexternalresources,orifyouhavecodethatis
computationallyintensive,CLRintegrationSPscanprovideanexcellentalternativeto
straightT-SQLcode.
Listing16-8showshowtouseCLRintegrationtoretrieveoperatingsystem
environmentvariablesandreturnthemasarecordsetviaanSP.Inthe
Apress.Examplesnamespace,youcreateaSampleProcclass.
Listing16-8.RetrievingEnvironmentVariableswithaCLRStoredProcedure
usingSystem;
usingSystem.Collections;
usingSystem.Data;
usingSystem.Data.SqlClient;
usingSystem.Data.SqlTypes;
usingMicrosoft.SqlServer.Server;
namespaceApress.Examples
{
publicpartialclassSampleProc
{
[Microsoft.SqlServer.Server.SqlProcedure()]
publicstaticvoidGetEnvironmentVars()
{
try
{
SortedListenvironment_list=newSortedList();
foreach(DictionaryEntrydein
Environment.GetEnvironmentVariables())
{
environment_list[de.Key]=de.Value;
}
SqlDataRecordrecord=newSqlDataRecord(
newSqlMetaData("VarName",
SqlDbType.NVarChar,1024),
newSqlMetaData("VarValue",
SqlDbType.NVarChar,4000)
);
SqlContext.Pipe.SendResultsStart(record);
foreach(DictionaryEntrydein
environment_list)
{
record.SetValue(0,de.Key);
record.SetValue(1,de.Value);
SqlContext.Pipe.SendResultsRow(record);
}
SqlContext.Pipe.SendResultsEnd();
}
catch(Exceptionex)
{
SqlContext.Pipe.Send(ex.Message);
}
}
}
};
AswiththepreviousCLRintegrationexamples,appropriatenamespacesareimported
atthetop:
usingSystem;
usingSystem.Collections;
usingSystem.Data;
usingSystem.Data.SqlClient;
usingSystem.Data.SqlTypes;
usingMicrosoft.SqlServer.Server;
TheGetEnvironmentVars()methodisdeclaredasapublicvoidfunction.The
SqlProcedure()attributeisappliedtothefunctioninthiscodetoindicatetoVisual
StudiothatthisisaCLRSP.ThebodyoftheSPiswrappedinatry…catchblockto
captureany.NETexceptions,whicharereturnedtoSQLServer.Ifanexceptionoccursin
the.NETcode,it’ssentbacktoSQLServerviatheSqlContext.Pipe.Sendmethod:
publicpartialclassSampleProc
{
[Microsoft.SqlServer.Server.SqlProcedure()]
publicstaticvoidGetEnvironmentVars()
{
try
{
…
}
catch(Exceptionex)
{
SqlContext.Pipe.Send(ex.Message);
}
}
}
};
THROWINGREADABLEEXCEPTIONS
WhenyouneedtoraiseanexceptioninaCLRSP,youhavetwooptions.Forcode
readabilityreasons,I’vechosenthesimpleroptionofallowingexceptionstobubble
upthroughthecallstack.Thisresultsin.NETFrameworkexceptionsbeingreturned
toSQLServer.The.NETFrameworkexceptionsreturnalotofextrainformation,
likecallstackdata,however.
Ifyouwanttoraiseanice,simpleSQLServer–styleerrorwithoutalltheextra.NET
Frameworkexceptioninformation,youcanuseamethodintroducedinthebookPro
SQLServer2005,byThomasRizzoetal.(Apress,2005).Thissecondmethod
involvesusingtheExecuteAndSend()methodoftheSqlContext.Pipeto
executeaT-SQLRAISERRORstatement.ThismethodisshowninthefollowingC#
codesnippet:
try{
SqlContext.Pipe.ExecuteAndSend(“RAISERROR('Thisis
aT-SQLError',16,1);”);
}
catch
{
//donothing
}
TheExecuteAndSend()methodcallexecutestheRAISERRORstatementonthe
currentcontextconnection.Thetry…catchblocksurroundingthecallpreventsthe
.NETexceptiongeneratedbytheRAISERRORtobehandledby.NETandreported
asanewerror.KeepthismethodinmindifyouwanttoraiseSQLServer–style
errorsinsteadofreturningtheverbose.NETFrameworkexceptioninformationto
SQLServer.
Astheprocedurebegins,alltheenvironmentvariablenamesandtheirvaluesare
copiedfromthe.NETHashtablereturnedbythe
Environment.GetEnvironmentVariables()functiontoa.NET
SortedList.Inthisprocedure,IchosetousetheSortedListtoensurethatthe
resultsarereturnedinorderbykey.IaddedtheSortedListfordisplaypurposes,but
it’snotrequired.GreaterefficiencycanbegainedbyiteratingtheHashtabledirectly
withoutaSortedList:
SortedListenvironment_list=newSortedList();
foreach(DictionaryEntrydein
Environment.GetEnvironmentVariables())
{
environment_list[de.Key]=de.Value;
}
TheprocedureusestheSqlContext.PipetoreturnresultstoSQLServerasa
resultset.ThefirststepofusingtheSqlContext.Pipetosendresultsbackistosetup
anSqlRecordwiththestructurethatyouwishtheresultsettotake.Forthisexample,
theresultsetconsistsoftwonvarcharcolumns:VarName,whichcontainsthe
environmentvariablenames;andVarValue,whichcontainstheircorrespondingvalues:
SqlDataRecordrecord=newSqlDataRecord(
newSqlMetaData("VarName",SqlDbType.NVarChar,1024),
newSqlMetaData("VarValue",SqlDbType.NVarChar,4000)
);
Next,thefunctioncallstheSendResultsStart()methodwiththe
SqlDataRecordtoinitializetheresultset:
SqlContext.Pipe.SendResultsStart(record);
Thenit’sasimplematterofloopingthroughtheSortedListofenvironment
variablekey/valuepairsandsendingthemtotheserverviatheSendResultsRow()
method:
foreach(DictionaryEntrydeinenvironment_list){
record.SetValue(0,de.Key);
record.SetValue(1,de.Value);
SqlContext.Pipe.SendResultsRow(record);
}
TheSetValue()methodiscalledforeachcolumnoftheSqlRecordtoproperly
settheresults,andthenSendResultsRow()iscalledforeachrow.Afteralltheresults
havebeensenttotheclient,theSendResultsEnd()methodofthe
SqlContext.Pipeiscalledtocompletetheresultsetandreturnthe
SqlContext.Pipetoitsinitialstate:
SqlContext.Pipe.SendResultsEnd();
TheGetEnvironmentVarsCLRSPcanbecalledusingtheT-SQLEXEC
statement,showninListing16-9.TheresultsareshowninFigure16-11.
Listing16-9.ExecutingtheGetEnvironmentVarsCLRProcedure
CREATEPROCEDUREdbo.GetEnvironmentVars
ASEXTERNALNAMEApressExamples.
[Apress.Examples.SampleProc].GetEnvironmentVars;
GO
EXECdbo.GetEnvironmentVars;
Figure16-11.RetrievingenvironmentvariableswithCLR
User-DefinedAggregates
User-definedaggregates(UDAs)areanexcitingadditiontoSQLServer’sfunctionality.
UDAsaresimilartothebuilt-inSQLaggregatefunctions(SUM,AVG,andsoon)inthat
theycanactonentiresetsofdataatonce,asopposedtooneitematatime.AnSQLCLR
UDAhasaccessto.NETfunctionalityandcanoperateonnumeric,character,date/time,or
evenuser-defineddatatypes.AbasicUDAhasfourrequiredmethods:
TheUDAcallsitsInit()methodwhentheSQLServerengine
preparestoaggregate.Thecodeinthismethodcanresetmember
variablestotheirstartstate,initializebuffers,andperformother
initializationfunctions.
TheAccumulate()methodiscalledaseachrowisprocessed,
allowingyoutoaggregatethedatapassedin.TheAccumulate()
methodmightincrementacounter,addarow’svaluetoarunning
total,orpossiblyperformothermorecomplexprocessingonarow’s
data.
TheMerge()methodisinvokedwhenSQLServerdecidestouse
parallelprocessingtocompleteanaggregate.Ifthequeryengine
decidestouseparallelprocessing,itcreatesmultipleinstancesofyour
UDAandcallstheMerge()methodtojointheresultsintoasingle
aggregation.
Terminate()isthefinalmethodoftheUDA.It’scalledafterall
rowshavebeenprocessedandanyaggregatescreatedinparallelhave
beenmerged.TheTerminate()methodreturnsthefinalresultof
theaggregationtothequeryengine.
TipInSQLServer2005,therewasaserializationlimitof8,000bytesforaninstance
ofanSQLCLRUDA,makingcertaintaskshardertoperformusingaUDA.Forinstance,
creatinganarray,ahashtable,oranotherstructuretoholdintermediateresultsduringan
aggregation(likeaggregatesthatcalculateastatisticalmodeormedian)couldcausea
UDAtoveryquicklyrunupagainstthe8,000-bytelimitandthrowanexceptionforlarge
datasets.SQLServer2008,2012,and2014don’thavethislimitation.
CreatingaSimpleUDA
TheexampleUDAinListing16-10determinesthestatisticalrangeforasetofnumbers.
Thestatisticalrangeforagivensetofnumbersisthedifferencebetweentheminimumand
maximumvaluesoftheset.TheUDAdeterminestheminimumandmaximumvaluesof
thesetofnumberspassedinandreturnsthedifference.
Listing16-10.SampleStatisticalRangeUDA
usingSystem;
usingSystem.Data;
usingSystem.Data.SqlClient;
usingSystem.Data.SqlTypes;
usingMicrosoft.SqlServer.Server;
namespaceApress.Examples{
[Serializable]
[Microsoft.SqlServer.Server.SqlUserDefinedAggregate(Format.Native)]
publicstructRange
{
SqlDoublemin,max;
publicvoidInit(){
min=SqlDouble.Null;
max=SqlDouble.Null;
}
publicvoidAccumulate(SqlDoublevalue)
{
if(!value.IsNull){
if(min.IsNull||value<min)
{
min=value;
}
if(max.IsNull||value>max)
{
max=value;
}
}
}
publicvoidMerge(Rangegroup)
{
if(min.IsNull||(!group.min.IsNull&&group.min
<min))
{
min=group.min;
}
if(max.IsNull||(!group.max.IsNull&&group.max
>max))
{
max=group.max;
}
}
publicSqlDoubleTerminate(){
SqlDoubleresult=SqlDouble.Null;
if(!min.IsNull&&!max.IsNull)
{
result=max-min;
}
returnresult;
}
}
}
ThisUDAbegins,likethepreviousCLRintegrationassemblies,byimportingthe
propernamespaces:
usingSystem;
usingSystem.Data;
usingSystem.Data.SqlClient;
usingSystem.Data.SqlTypes;
usingMicrosoft.SqlServer.Server;
Next,thecodedeclaresthestructthatrepresentstheUDA.Theattributes
SerializableandSqlUserDefinedAggregateareappliedtothestruct.You
usetheFormat.NativeserializationformatforthisUDA.Becausethisisasimple
UDA,Format.Nativeprovidesthebestperformanceandistheeasiesttoimplement.
MorecomplexUDAsthatusereferencetypesrequireFormat.UserDefined
serializationandmustimplementtheIBinarySerializeinterface:
[Serializable]
[Microsoft.SqlServer.Server.SqlUserDefinedAggregate(Format.Native)]
publicstructRange
{
}
Thestructdeclarestwomembervariables,minandmax,whichholdtheminimum
andmaximumvaluesencounteredduringtheaggregationprocess:
SqlDoublemin,max;
ThemandatoryInit()methodintheaggregatebodyinitializestheminandmax
membervariablestoSqlDouble.Null:
publicvoidInit(){
min=SqlDouble.Null;
max=SqlDouble.Null;
}
TheAccumulate()methodacceptsaSqlDoubleparameter.Thismethodfirst
checksthatthevalueisn’tNULL(NULLisignoredduringaggregation).Thenitchecksto
seeifthevaluepassedinislessthantheminvariable(orifminisNULL)and,ifso,
assignstheparametervaluetomin.Themethodalsochecksmaxandupdatesitifthe
parametervalueisgreaterthanmax(orifmaxisNULL).Inthisway,theminandmax
valuesaredeterminedontheflyasthequeryenginefeedsvaluesintothe
Accumulate()method:
publicvoidAccumulate(SqlDoublevalue)
{
if(!value.IsNull){
if(min.IsNull||value<min)
{
min=value;
}
if(max.IsNull||value>max)
{
max=value;
}
}
}
TheMerge()methodmergesaRangestructurethatwascreatedinparallelwiththe
currentstructure.ThemethodacceptsaRangestructureandcomparesitsminandmax
variablestothoseofthecurrentRangestructure.Itthenadjuststhecurrentstructure’s
minandmaxvariablesbasedontheRangestructurepassedintothemethod,effectively
mergingthetworesults:
publicvoidMerge(Rangegroup)
{
if(min.IsNull||(!group.min.IsNull&&group.min<min))
{
min=group.min;
}
if(max.IsNull||(!group.max.IsNull&&group.max>max))
{
max=group.max;
}
}
ThefinalmethodoftheUDAistheTerminate()function,whichreturnsan
SqlDoubleresult.ThisfunctionchecksforminormaxresultsthatareNULL.The
UDAreturnsNULLifeitherminormaxisNULL.IfneitherminnormaxisNULL,the
resultisthedifferencebetweenthemaxandminvalues:
publicSqlDoubleTerminate(){
SqlDoubleresult=SqlDouble.Null;
if(!min.IsNull&&!max.IsNull)
{
result=max-min;
}
returnresult;
}
NoteTheTerminate()methodmustreturnthesamedatatypethatthe
Accumulate()methodaccepts.Ifthesedatatypesdon’tmatch,anerrorwilloccur.
Also,asmentionedpreviously,it’sbestpracticetousetheSQLServer–specificdatatypes,
becausethestandard.NETtypeswillchokeonNULL.
Listing16-11isasimpletestofthisUDA.Thetestdeterminesthestatisticalrangeof
unitpricesthatcustomershavepaidforAdventureWorksproducts.Informationlikethis,
onaper-productorper-modelbasis,canbepairedwithadditionalinformationtohelpthe
AdventureWorkssalesteamssetoptimalpricepointsfortheirproducts.Theresultsare
showninFigure16-12.
Listing16-11.RetrievingStatisticalRangeswithaUDA
CREATEAGGREGATERange(@valuefloat)RETURNSfloat
EXTERNALNAMEApressExamples.[Apress.Examples.Range];
GO
SELECT
ProductID,
dbo.Range(UnitPrice)ASUnitPriceRange
FROMSales.SalesOrderDetail
WHEREUnitPrice>0
GROUPBYProductID;
Figure16-12.Resultsoftherangeaggregateappliedtounitprices
CautionThisUDAisanexample.It’sfastertouseregularT-SQLaggregation
functionsforthistypeofcalculation,especiallyifyouhavealargenumberofrowsto
process.
CreatinganAdvancedUDA
YoucancreatemoreadvancedCLRaggregatesthatusereferencedatatypesanduser-
definedserialization.WhencreatingaUDAthatusesreference(nonvalue)datatypessuch
asArrayLists,SortedLists,andObjects,CLRintegrationimposestheadditional
restrictionthatyoucan’tmarktheUDAforFormat.Nativeserialization.Instead,
theseaggregateshavetobemarkedforFormat.UserDefinedserialization,which
meanstheUDAmustimplementtheIBinarySerializeinterface,includingboththe
ReadandWritemethods.Basically,youhavetotellSQLServerhowtoserializeyour
datawhenusingreferencetypes.Thereisaperformanceimpactassociatedwith
Format.UserDefinedserializationasopposedtoFormat.Native.
Listing16-12isaUDAthatcalculatesthestatisticalmedianofasetofnumbers.The
statisticalmedianisthemiddlenumberofanorderedgroupofnumbers.Ifthesetcontains
anevennumberofvalues,thestatisticalmedianistheaverage(mean)ofthemiddletwo
numbersintheset.
Listing16-12.UDAtoCalculatetheStatisticalMedian
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Data;
usingSystem.Data.SqlTypes;
usingSystem.Runtime.InteropServices;
usingMicrosoft.SqlServer.Server;
namespaceApress.Examples{
[Serializable]
[Microsoft.SqlServer.Server.SqlUserDefinedAggregate(
Format.UserDefined,
IsNullIfEmpty=true,
MaxByteSize=-1)]
[StructLayout(LayoutKind.Sequential)]
publicstructMedian:IBinarySerialize
{
List<double>temp;//Listofnumbers
publicvoidInit()
{
//Createnewlistofdoublenumbers
this.temp=newList<double>();
}
publicvoidAccumulate(SqlDoublenumber)
{
if(!number.IsNull)//SkipoverNULLs
{
this.temp.Add(number.Value);//Ifnumberis
notNULL,addittolist
}
}
publicvoidMerge(Mediangroup)
{
//Mergetwosetsofnumbers
this.temp.InsertRange(this.temp.Count,
group.temp);
}
publicSqlDoubleTerminate(){
SqlDoubleresult=SqlDouble.Null;//Default
resulttoNULL
this.temp.Sort();//Sortlistofnumbers
intfirst,second;//Indexestomiddletwo
numbers
if(this.temp.Count%2==1)
{
//Ifthereisanoddnumberofvaluesgetthe
middlenumbertwice
first=this.temp.Count/2;
second=first;
}
else
{
//Ifthereisanevennumberofvaluesgetthe
middletwonumbers
first=this.temp.Count/2-1;
second=first+1;
}
if(this.temp.Count>0)//Iftherearenumbers,
calculatemedian
{
//Calculatemedianasaverageofmiddle
number(s)
result=(SqlDouble)(this.temp[first]
+this.temp[second])/2.0;
}
returnresult;
}
#regionIBinarySerializeMembers
//Customserializationreadmethod
publicvoidRead(System.IO.BinaryReaderr)
{
//Createanewlistofdoublevalues
this.temp=newList<double>();
//Getthenumberofvaluesthatwereserialized
intj=r.ReadInt32();
//Loopandaddeachserializedvaluetothelist
for(inti=0;i<j;i++)
{
this.temp.Add(r.ReadDouble());
}
}
//Customserializationwritemethod
publicvoidWrite(System.IO.BinaryWriterw)
{
//Writethenumberofvaluesinthelist
w.Write(this.temp.Count);
//Writeouteachvalueinthelist
foreach(doubledinthis.temp)
{
w.Write(d);
}
}
#endregion
}
}
ThisUDAbegins,liketheotherCLRintegrationexamples,withnamespaceimports.
YouaddtheSystem.Collections.Genericnamespacethistimesoyoucanuse
the.NETList<T>stronglytypedlist:
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Data;
usingSystem.Data.SqlTypes;
usingSystem.Runtime.InteropServices;
usingMicrosoft.SqlServer.Server;
TheMedianstructureintheexampleisdeclaredwiththeSerializableattribute
toindicatethatitcanbeserialized,andtheStructLayoutattributewiththe
LayoutKind.Sequentialpropertytoforcethestructuretobeserializedin
sequentialfashionforaUDAthathasaFormatdifferentfromNative.The
SqlUserDefinedAggregateattributedeclaresthreeproperties,asfollows:
Format.UserDefinedindicatesthattheUDAimplements
serializationmethodsthroughtheIBinarySerializeinterface.
ThisisrequiredbecausetheList<T>referencetypeisbeingusedin
theUDA.
IsNullIfEmptyissettotrue,indicatingthatNULLwillbe
returnedifnorowsarepassedtotheUDA.
MaxByteSizeissetto-1sothattheUDAcanbeserializedifit’s
greaterthan8,000bytes.(The8,000-byteserializationlimitwasa
strictlimitinSQLServer2005thatpreventedserializationoflarge
objects,likelargeArrayListobjects,intheUDA).
BecauseFormat.UserDefinedisspecifiedontheMedianstructure,itmust
implementtheIBinarySerializeinterface.Inthebodyofthestruct,youdefinea
List<double>namedtempthatholdsanintermediatetemporarylistofnumbers
passedintotheUDA:
[Serializable]
[Microsoft.SqlServer.Server.SqlUserDefinedAggregate(
Format.UserDefined,
IsNullIfEmpty=true,
MaxByteSize=-1)]
[StructLayout(LayoutKind.Sequential)]
publicstructMedian:IBinarySerialize
{
List<double>temp;//Listofnumbers
...
}
TheRead()andWrite()methodsoftheIBinarySerializeinterfaceare
usedtodeserializeandserializethelist,respectively:
#regionIBinarySerializeMembers
//Customserializationreadmethod
publicvoidRead(System.IO.BinaryReaderr)
{
//Createanewlistofdoublevalues
this.temp=newList<double>();
//Getthenumberofvaluesthatwereserialized
intj=r.ReadInt32();
//Loopandaddeachserializedvaluetothelist
for(inti=0;i<j;i++)
{
this.temp.Add(r.ReadDouble());
}
}
//Customserializationwritemethod
publicvoidWrite(System.IO.BinaryWriterw)
{
//Writethenumberofvaluesinthelist
w.Write(this.temp.Count);
//Writeouteachvalueinthelist
foreach(doubledinthis.temp)
{
w.Write(d);
}
}
#endregion
TheInitmethodoftheUDAinitializesthetemplistbycreatinganew
List<double>instance:
publicvoidInit(){
//Createnewlistofdoublenumbers
this.temp=newList<double>();
}
TheAccumulate()methodacceptsaSqlDoublenumberandaddsallnon-NULL
valuestothetemplist.AlthoughyoucanincludeNULLsinyouraggregateresults,keep
inmindthatT-SQLdevelopersareusedtotheNULLhandlingofbuilt-inaggregate
functionslikeSUMandAVG.Inparticular,developersareusedtotheiraggregatefunctions
discardingNULL.ThisisthemainreasonyoueliminateNULLinthisUDA:
publicvoidAccumulate(SqlDoublenumber)
{
if(!number.IsNull)//SkipoverNULLs
{
this.temp.Add(number.Value);//IfnumberisnotNULL,
addittolist
}
}
TheMerge()methodintheexamplemergestwolistsofnumbersifSQLServer
decidestocalculatetheaggregateinparallel.Ifso,theserverpassesalistofnumbersinto
theMerge()method.Thislistofnumbersmustthenbeappendedtothecurrentlist.For
efficiency,youusetheInsertRange()methodofList<T>tocombinethelists:
publicvoidMerge(Mediangroup)
{
//Mergetwosetsofnumbers
this.temp.InsertRange(this.temp.Count,group.temp);
}
TheTerminate()methodoftheUDAsortsthelistofvaluesandthendetermines
theindexesofthemiddlevalues.Ifthereisanoddnumberofvaluesinthelist,thereis
onlyasinglemiddlevalue;ifthereisanevennumberofvaluesinthelist,themedianis
theaverageofthemiddletwovalues.Ifthelistcontainsnovalues(whichcanoccurif
everyvaluepassedtotheaggregateisNULL),theresultisNULL;otherwisethe
Terminate()methodcalculatesandreturnsthemedian:
publicSqlDoubleTerminate(){
SqlDoubleresult=SqlDouble.Null;//Defaultresultto
NULL
this.temp.Sort();//Sortlistofnumbers
intfirst,second;//Indexestomiddletwonumbers
if(this.temp.Count%2==1)
{
//Ifthereisanoddnumberofvaluesgetthemiddle
numbertwice
first=this.temp.Count/2;
second=first;
}
else
{
//Ifthereisanevennumberofvaluesgetthemiddle
twonumbers
first=this.temp.Count/2-1;
second=first+1;
}
if(this.temp.Count>0)//Iftherearenumbers,
calculatemedian
{
//Calculatemedianasaverageofmiddlenumber(s)
result=(SqlDouble)(this.temp[first]
+this.temp[second])/2.0;
}
returnresult;
}
Listing16-13demonstratestheuseofthisUDAtocalculatethemedianUnitPrice
fromtheSales.SalesOrderDetailtableonaper-productbasis.Theresultsare
showninFigure16-13.
Listing16-13.CalculatingtheMedianUnitPricewithaUDA
CREATEAGGREGATEdbo.Median(@valuefloat)RETURNSfloat
EXTERNALNAMEApressExamples.[Apress.Examples.Median];
GO
SELECT
ProductID,
dbo.Median(UnitPrice)ASMedianUnitPrice
FROMSales.SalesOrderDetail
GROUPBYProductID;
Figure16-13.Medianunitpriceforeachproduct
CLRIntegrationUser-DefinedTypes
SQLServer2000hadbuilt-insupportforuser-defineddatatypes,buttheywerelimitedin
scopeandfunctionality.Theold-styleuser-defineddatatypeshadthefollowing
restrictionsandcapabilities:
Theyhadtobederivedfrombuilt-indatatypes.
Theirformatand/orrangecouldonlyberestrictedthroughT-SQL
rules.
Theycouldbeassignedadefaultvalue.
TheycouldbedeclaredasNULLorNOTNULL.
SQLServer2014providessupportforold-styleuser-defineddatatypesandrules,
presumablyforbackwardcompatibilitywithexistingapplications.TheAdventureWorks
databasecontainsexamplesofold-styleuser-defineddatatypes,likethedbo.Phone
datatype,whichisanaliasforthevarchar(25)datatype.
CautionRules(CHECKconstraintsthatcanbeappliedtouser-defineddatatypes)have
beendeprecatedsinceSQLServer2005andwillberemovedfromafutureversion.T-SQL
user-defineddatatypesarenowoftenreferredtoasaliastypes.
SQLServer2014supportsafarmoreflexiblesolutiontoyourcustomdatatypeneeds
intheformofCLRuser-definedtypes.CLRintegrationuser-definedtypesallowyouto
accessthepowerofthe.NETFramework.CommonexamplesofCLRUDTsinclude
mathematicalconceptslikepoints,vectors,complexnumbers,andothertypesnotbuilt
intotheSQLServertypesystem.Infact,CLRUDTsaresopowerfulthatMicrosofthas
begunincludingsomeasstandardinSQLServer.TheseCLRUDTsincludethespatial
datatypesgeographyandgeometry,andthehierarchyiddatatype.
CLRUDTsareusefulforimplementingdatatypesthatrequirespecialhandlingand
thatimplementtheirownspecialmethodsandfunctions.Complexnumbers,whicharea
supersetofrealnumbers,areoneexample.Complexnumbersarerepresentedwitha
“real”partandan“imaginary”partintheformata+bi,whereaisarealnumber
representingtherealpartofthevalue,bisarealnumberrepresentingtheimaginarypart,
andtheliteralletteriaftertheimaginarypartstandsfortheimaginarynumberi,whichis
thesquarerootof-1.Complexnumbersareoftenusedinmath,science,andengineering
tosolvedifficultabstractproblems.Someexamplesofcomplexnumbersinclude
101.9+3.7i,98+12i,-19i,and12+0i(whichcanalsoberepresentedas12).Becausetheir
formatisdifferentfromrealnumbersandcalculationswiththemrequirespecial
functionality,complexnumbersareagoodcandidateforCLR.TheexampleinListing16-
14implementsacomplexnumberCLRUDT.
NoteTokeeptheexamplesimple,onlyapartialimplementationisreproducedhere.
ThesampledownloadfileincludesthefullversionofthisCLRUDTthatincludesbasic
operatorsaswellasadditionaldocumentationandimplementationsofmanymore
mathematicaloperatorsandtrigonometricfunctions.
Listing16-14.ComplexNumbersUDT
usingSystem;
usingSystem.Data.SqlTypes;
usingMicrosoft.SqlServer.Server;
usingSystem.Text.RegularExpressions;
namespaceApress.Examples
{
[Serializable]
[Microsoft.SqlServer.Server.SqlUserDefinedType
(
Format.Native,
IsByteOrdered=true
)]
publicstructComplex:INullable
{
#region"ComplexNumberUDTFields/Components"
privateboolm_Null;
publicDoublereal;
publicDoubleimaginary;
#endregion
#region"ComplexNumberParsing,Constructor,and
Methods/Properties"
privatestaticreadonlyRegexrx=newRegex(
"^(?<Imaginary>[+-]?([0-9]+|[0-9]*\\.[0-9]+))
[i|I]$|"+
"^(?<Real>[+-]?([0-9]+|[0-9]*\\.[0-9]+))$|"+
"^(?<Real>[+-]?([0-9]+|[0-9]*\\.[0-9]+))"+
"(?<Imaginary>[+-]?([0-9]+|[0-9]*\\.[0-9]+))
[i|I]$");
publicstaticComplexParse(SqlStrings)
{
Complexu=newComplex();
if(s.IsNull)
u=Null;
else
{
MatchCollectionm=rx.Matches(s.Value);
if(m.Count==0)
throw(newFormatException("InvalidComplex
NumberFormat."));
Stringreal_str=m[0].Groups["Real"].Value;
Stringimaginary_str
=m[0].Groups["Imaginary"].Value;
if(real_str==""&&imaginary_str=="")
throw(newFormatException("InvalidComplex
NumberFormat."));
if(real_str=="")
u.real=0.0;
else
u.real=Convert.ToDouble(real_str);
if(imaginary_str=="")
u.imaginary=0.0;
else
u.imaginary
=Convert.ToDouble(imaginary_str);
}
returnu;
}
publicoverrideStringToString()
{
Stringsign="";
if(this.imaginary>=0.0)
sign="+";
returnthis.real.ToString()+sign
+this.imaginary.ToString()+"i";
}
publicboolIsNull
{
get
{
returnm_Null;
}
}
publicstaticComplexNull
{
get
{
Complexh=newComplex();
h.m_Null=true;
returnh;
}
}
publicComplex(Doubler,Doublei)
{
this.real=r;
this.imaginary=i;
this.m_Null=false;
}
#endregion
#region"ComplexNumberBasicOperators"
//Complexnumberaddition
publicstaticComplexoperator+(Complexn1,Complex
n2)
{
Complexu;
if(n1.IsNull||n2.IsNull)
u=Null;
else
u=newComplex(n1.real+n2.real,n1.imaginary
+n2.imaginary);
returnu;
}
#endregion
#region"ExposedMathematicalBasicOperatorMethods"
//Addcomplexnumbern2ton1
publicstaticComplexCAdd(Complexn1,Complexn2)
{
returnn1+n2;
}
//Subtractcomplexnumbern2fromn1
publicstaticComplexSub(Complexn1,Complexn2)
{
returnn1-n2;
}
#endregion
//othercomplexoperationsareavailableinthe
sourcecode
}
}
Thecodebeginswiththerequirednamespaceimportsandthenamespacedeclaration
fortheexample:
usingSystem;
usingSystem.Data.SqlTypes;
usingMicrosoft.SqlServer.Server;
usingSystem.Text.RegularExpressions;
NextisthedeclarationofthestructurethatrepresentsaninstanceoftheUDT.The
Serializable,Format.Native,andIsByteOrdered=trueattributesand
attributepropertiesareallsetontheUDT.Inaddition,allCLRUDTsmustimplementthe
INullableinterface.INullablerequiresthattheIsNullandNullpropertiesbe
defined:
[Serializable]
[Microsoft.SqlServer.Server.SqlUserDefinedType
(
Format.Native,
IsByteOrdered=true
)]
publicstructComplex:INullable
{
...
}
Table16-1showsafewofthecommonattributesthatareusedinCLRintegration
UDTdefinitions.
Table16-1.CommonCLRUDTAttributes
ThepublicandprivatefieldsaredeclaredinthebodyoftheComplexstructure.The
realandimaginarypublicfieldsrepresenttherealandimaginarypartsofthe
complexnumber,respectively.Them_Nullfieldisaboolvaluethatissettotrueif
thecurrentinstanceofthecomplextypeisNULLandissettofalseotherwise:
#region"ComplexNumberUDTFields/Components"
privateboolm_Null;
publicDoublereal;
publicDoubleimaginary;
#endregion
ThefirstmethoddeclaredintheUDTistheParsemethod(requiredbyallUDTs),
whichtakesastringvaluefromSQLServerandparsesitintoacomplexnumber.Parse
usesa.NETregularexpressiontosimplifyparsingabit:
privatestaticreadonlyRegexrx=newRegex(
"^(?<Imaginary>[+-]?([0-9]+|[0-9]*\\.[0-9]+))[i|I]$|"+
"^(?<Real>[+-]?([0-9]+|[0-9]*\\.[0-9]+))$|"+
"^(?<Real>[+-]?([0-9]+|[0-9]*\\.[0-9]+))"+
"(?<Imaginary>[+-]?([0-9]+|[0-9]*\\.[0-9]+))[i|I]$");
publicstaticComplexParse(SqlStrings)
{
Complexu=newComplex();
if(s.IsNull)
u=Null;
else
{
MatchCollectionm=rx.Matches(s.Value);
if(m.Count==0)
throw(newFormatException("InvalidComplexNumber
Format."));
Stringreal_str=m[0].Groups["Real"].Value;
Stringimaginary_str=m[0].Groups["Imaginary"].Value;
if(real_str==""&&imaginary_str=="")
throw(newFormatException("InvalidComplexNumber
Format."));
if(real_str=="")
u.real=0.0;
else
u.real=Convert.ToDouble(real_str);
if(imaginary_str=="")
u.imaginary=0.0;
else
u.imaginary=Convert.ToDouble(imaginary_str);
}
returnu;
}
Theregularexpression(a.k.a.regex)usesnamedgroupstoparsetheinputstringinto
Realand/orImaginarynamedgroups.Iftheregexissuccessful,atleastone(ifnot
both)ofthesenamedgroupswillbepopulated.Ifunsuccessful,bothnamedgroupswillbe
emptyandanexceptionoftypeFormatExceptionwillbethrown.Ifatleastoneof
thenamedgroupsisproperlyset,thestringrepresentationsareconvertedtoDoubletype
andassignedtotheappropriateUDTfields.Table16-2showssomesampleinputstrings
andthevaluesassignedtotheUDTfieldswhenthey’reparsed.
Table16-2.ComplexNumber-ParsingSamples
TheToString()methodisrequiredforallUDTsaswell.Thismethodconvertsthe
internalUDTdatatoitsstringrepresentation.Inthecaseofcomplexnumbers,
ToString()needstoperformthefollowingsteps:
1. Converttherealparttoastring.
2. Appendaplussign(+)iftheimaginarypartis0orpositive.
3. Appendtheimaginarypart.
4. Appendtheletteritoindicatethatitdoesinfactrepresenta
complexnumber.
Noticethatiftheimaginarypartisnegative,nosignisappendedbetweentherealand
imaginaryparts,becausethesignisalreadyincludedintheimaginarypart:
publicoverrideStringToString()
{
Stringsign="";
if(this.imaginary>=0.0)
sign="+";
returnthis.real.ToString()+sign
+this.imaginary.ToString()+"i";
}
TheIsNullandNullpropertiesarebothrequiredbyallUDTs.IsNullisabool
propertythatindicateswhetheraUDTinstanceisNULL.TheNullpropertyreturnsa
NULLinstanceoftheUDTtype.Onethingyouneedtobeawareofanytimeyouinvokea
UDT(oranyCLRintegrationobject)fromT-SQLisSQLNULL.Forpurposesofthe
ComplexUDT,youtakeacuefromT-SQLandreturnaNULLresultanytimeaNULLis
passedinasaparametertoanyUDTmethod.SoaComplexvalueplusNULLreturns
NULL,asdoesaComplexvaluedividedbyNULL,andsoon.Noticethatalotofcodein
thecompleteComplexUDTlistingisspecificallydesignedtodealwithNULL:
publicboolIsNull
{
get
{
returnm_Null;
}
}
publicstaticComplexNull
{
get
{
Complexh=newComplex();
h.m_Null=true;
returnh;
}
}
ThisparticularUDTincludesaconstructorfunctionthatacceptstwoDoubletype
valuesandcreatesaUDTinstancefromthem:
publicComplex(Doubler,Doublei)
{
this.real=r;
this.imaginary=i;
this.m_Null=false;
}
TipForaUDTdesignedasa.NETstructure,aconstructormethodisn’trequired.In
fact,adefaultconstructor(thattakesnoparameters)isn’tevenallowed.Tokeeplatercode
simple,Iaddedaconstructormethodtothisexample.
Inthenextregion,youdefineafewusefulcomplexnumberconstantsandexpose
themasstaticpropertiesoftheComplexUDT:
#region"UsefulComplexNumberConstants"
//Theproperty"i"istheComplexnumber0+1i.Defined
herebecause
//itisusefulinsomecalculations
publicstaticComplexi
{
get
{
returnnewComplex(0,1);
}
}
...
#endregion
Tokeepthislistingshortbuthighlighttheimportantpoints,thesampleUDTshows
onlytheadditionoperatorforcomplexnumbers.TheUDToverridesthe+operator.
RedefiningoperatorsmakesiteasiertowriteanddebugadditionalUDTmethods.These
overridden.NETmathoperatorsaren’tavailabletoT-SQLcode,sothestandardT-SQL
mathoperatorswon’tworkontheUDT:
//Complexnumberaddition
publicstaticComplexoperator+(Complexn1,Complexn2)
{
Complexu;
if(n1.IsNull||n2.IsNull)
u=Null;
else
u=newComplex(n1.real+n2.real,n1.imaginary
+n2.imaginary);
returnu;
}
PerformingmathematicaloperationsonUDTvaluesfromT-SQLmustbedonevia
explicitlyexposedmethodsoftheUDT.ThesemethodsintheComplexUDTareCAdd
andDiv,forcomplexnumberadditionanddivision,respectively.NotethatIchoseCAdd
(whichstandsfor“complexnumberadd”)asamethodnametoavoidconflictswiththeT-
SQLreservedwordADD.Iwon’tgotoodeeplyintotheinnerworkingsofcomplex
numbers,butIchosetoimplementthebasicoperatorsinthislistingbecausesome(like
complexnumberaddition)arestraightforwardoperations,whereasothers(likedivision)
areabitmorecomplicated.Themathoperatormethodsaredeclaredasstatic,sothey
canbeinvokedontheUDTdatatypeitselffromSQLServerinsteadofonaninstanceof
theUDT:
#region"ExposedMathematicalBasicOperatorMethods"
//Addcomplexnumbern2ton1
publicstaticComplexCAdd(Complexn1,Complexn2)
{
returnn1+n2;
}
//Subtractcomplexnumbern2fromn1
publicstaticComplexSub(Complexn1,Complexn2)
{
returnn1-n2;
}
#endregion
NoteStaticmethodsofaUDT(declaredwiththestatickeywordinC#orthe
SharedkeywordinVisualBasic)areinvokedfromSQLServerusingaformatlikethis:
Complex::CAdd(@nl,@n2).Nonshared,orinstance,methodsofaUDTare
invokedfromSQLServerusingaformatsimilartothis:@>nl.CAdd(@n2).Thestyleof
methodyouuse(sharedorinstance)isadeterminationyouneedtomakeonacase-by-
casebasis.
Listing16-15demonstrateshowtheComplexUDTcanbeused;theresultsare
showninFigure16-14.
Listing16-15.UsingtheComplexNumberUDT
CREATETYPEdbo.Complex
EXTERNALNAMEApressExamples.[Apress.Examples.Complex];
GO
DECLARE@ccomplex='+100-10i',
@dcomplex='5i';
SELECT'ADD:'+@c.ToString()+','+@d.ToString()AS
Op,
complex::CAdd(@c,@d).ToString()ASResult
UNION
SELECT'DIV:'+@c.ToString()+','+@d.ToString(),
complex::Div(@c,@d).ToString()
UNION
SELECT'SUB:'+@c.ToString()+','+@d.ToString(),
complex::Sub(@c,@d).ToString()
UNION
SELECT'MULT:'+@c.ToString()+','+@d.ToString(),
complex::Mult(@c,@d).ToString()
UNION
SELECT'PI:',
complex::Pi.ToString();
Figure16-14.PerformingoperationswiththeComplexUDT
Inadditiontothebasicoperations,theComplexclasscanbeeasilyextendedto
supportseveralmoreadvancedcomplexnumberoperatorsandfunctions.Thecodesample
downloadfilecontainsafulllistingofanexpandedComplexUDT,includingallthe
basicmathoperators,aswellaslogarithmicandexponentialfunctions(Log(),
Power(),etc.)andtrigonometricandhyperbolicfunctions(Sin(),Cos(),Tanh(),
etc.)forcomplexnumbers.
Triggers
Finally,youcanalsocreate.NETtriggers.Thisislogical;afterall,triggersarejusta
specializedtypeofstoredprocedures.Therearefewexamplesofreallyinteresting.NET
triggers.MostofwhatyouwanttodoinatriggercanbedonewithregularT-SQLcode.
WhenSQLServer2005wasreleased,yousawanexampleofa.NETtriggeronalocation
tablethatcallsawebservicetofindthecoordinatesofacityandaddsthemtoa
coordinatescolumn.Thiscouldatfirstsoundlikeacoolidea,butifyourememberthata
triggerisfiredinthescopeoftheDMLstatement’stransaction,youcanguessthatthe
latencyaddedtoeveryinsertandupdateonthetablemightbeaproblem.Usually,youtry
tokeepthetriggerimpactaslightaspossible.Listing16-16presentsanexampleofa
.NETtriggerbasedonyourpreviousregularexpressionUDF.Ittestsane-mailinsertedor
modifiedontheAdventureWorksPerson.EmailAddresstable,androllsback
thetransactionifitdoesn’tmatchthepatternofacorrecte-mailaddress.Let’sseeitin
action.
Listing16-16.TriggertoValidateanE-mailAddress
usingSystem;
usingSystem.Data;
usingSystem.Data.SqlClient;
usingMicrosoft.SqlServer.Server;
usingSystem.Text.RegularExpressions;
usingSystem.Transactions;
namespaceApress.Examples
{
publicpartialclassTriggers
{
privatestaticreadonlyRegexemail_pattern=new
Regex
(
//Everythingbeforethe@sign(the"localpart")
"^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-
9!#$%&'*+/=?^_`{|}~-]+)*"+
//Subdomainsafterthe@sign
"@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+"+
//Top-leveldomains
"(?:[a-z]
{2}|com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum)\\b$"
);
[Microsoft.SqlServer.Server.SqlTrigger(
Name="EmailAddressTrigger",
Target="[Person].[EmailAddress]",
Event="FORINSERT,UPDATE")]
publicstaticvoidEmailAddressTrigger()
{
SqlTriggerContexttContext
=SqlContext.TriggerContext;
//Retrievetheconnectionthatthetriggeris
using.
using(SqlConnectioncn
=newSqlConnection(@"contextconnection=true"))
{
SqlCommandcmd;
SqlDataReaderr;
cn.Open();
cmd=newSqlCommand(@"SELECTEmailAddressFROM
INSERTED",cn);
r=cmd.ExecuteReader();
try
{
while(r.Read())
{
if
(!email_pattern.IsMatch(r.GetString(0).ToLower()))
Transaction.Current.Rollback();
}
}
catch(SqlExceptionex)
{
//Catchtheexpectedexception.
}
finally
{
r.Close();
cn.Close();
}
}
}
}
}
Asyounowareusedto,youfirstdeclareyour.NETnamespaces.Tomanagethe
transaction,youhavetodeclaretheSystem.Transactionsnamespace.Inyour
VisualStudioproject,itmightnotberecognized.Youneedtoright-clicktheprojectinthe
SolutionExplorerandselect“addreference.”Then,gototheSQLServertab,andcheck
“System.Transactionsforframework4.0.0.0.”
Then,likeinyourpreviousUDF,youdeclaretheRegexobject.Thetriggerbody
follows.Inthefunction’sdecoration,younamethetrigger,andyoudeclareforwhich
targettableit’sintended.Youalsospecifyatwhateventsitwillfire.
[Microsoft.SqlServer.Server.SqlTrigger(
Name="EmailAddressTrigger",
Target="[Person].[EmailAddress]",
Event="FORINSERT,UPDATE")]
publicstaticvoidEmailAddressTrigger()
{...
Then,youdeclareaninstanceoftheSqlTriggerContextclass.Thisclass
exposesafewpropertiesthatgiveinformationaboutthetrigger’scontext,likewhat
columnsareupdated,whattheactionisthatfiredthetrigger,andincaseofaDDLtrigger,
italsogivesaccesstotheEventDataXMLstructurecontainingalltheexecutiondetails.
SqlTriggerContexttContext=SqlContext.TriggerContext;
Thenextlineopenstheso-calledcontextconnectiontoSQLServer.Thereisonlyone
waytoaccessthecontentofatable:withaT-SQLSELECTstatement.Evena.NETcode
executedinSQLServercan’tescapefromthisrule.Tobeabletoretrievethee-mailsthat
havebeeninsertedorupdated,youneedtoopenaconnectiontoSQLServerandquerythe
insertedvirtualtable.Forthat,youuseaspecialtypeofconnectionavailableinCLR
integrationnamedthecontextconnection,whichisdesignedtobefasterthanaregular
networkorlocalconnection.Thenyouuseadatareadertoretrievethee-mailsinthe
EmailAddresscolumn.Youloopthroughtheresultsandapplytheregularexpression
patterntoeachaddress.Ifitdoesn’tmatch,yourollbackthetransactionbyusingthe
Transaction.Current.Rollback()method.Youneedtoprotecttherollbackby
atry…catchblock,becauseitwillthrowanambiguousexception,statingthat
“Transactionisnotallowedtorollbackinauserdefinedroutine,triggeroraggregate
becausethetransactionisnotstartedinthatCLRlevel.”Thiscanbesafelyignored.
Anothererrorwillberaisedevenifthetry…catchblockisthere,anditmustbe
dealtwithattheT-SQLlevel.Youseethatinyourexamplelateron.
using(SqlConnectioncn
=newSqlConnection(@“contextconnection=true”))
{
SqlCommandcmd;
SqlDataReaderr;
cn.Open();
cmd=newSqlCommand(@“SELECTEmailAddressFROM
INSERTED”,cn);
r=cmd.ExecuteReader();
try
{
while(r.Read())
{
if
(!email_pattern.IsMatch(r.GetString(0).ToLower()))
Transaction.Current.Rollback();
}
}
catch(SqlExceptionex)
{
//Catchtheexpectedexception.
}
finally
{
r.Close();
cn.Close();
}
}
}
}
Nowthatthetriggeriswritten,let’stryitout.Whentheassemblyiscompiledand
addedtotheAdventureWorksdatabaseusingCREATEASSEMBLY,youcanaddthe
triggertothePerson.EmailAddresstable,asshowninListing16-17.
Listing16-17.CreationoftheCLRTriggertoValidateanE-mailAddress
CREATETRIGGERatr_Person_EmailAddress_ValidateEmail
ONPerson.EmailAddress
AFTERINSERT,UPDATE
ASEXTERNALNAMEApressExamples.
[Apress.Examples.Triggers].EmailAddressTrigger;
Younowtrytoupdatealinetoanobviouslyinvalide-mailaddressinListing16-18.
TheresultisshowninFigure16-15.
Listing16-18.SettinganInvalidE-mailAddress
UPDATEPerson.EmailAddress
SETEmailAddress='pro%sql@apress@com'
WHEREEmailAddress='dylan0@adventure-works.com';
Figure16-15.ResultoftheTrigger’sAction
Asyoucansee,thetriggerworkedandrolledbacktheUPDATEattempt,buttheerror
messagegeneratedfortheCLRcodeisn’tveryuser-friendly.Youneedtocatchthe
exceptioninyourT-SQLstatement.AmodifiedUPDATEdealingwiththatisshownin
Listing16-19.
Listing16-19.UPDATEStatementModifiedtoHandletheError
BEGINTRY
UPDATEPerson.EmailAddress
SETEmailAddress='pro%sql@apress@com'
WHEREEmailAddress='dylan0@adventure-works.com';
ENDTRY
BEGINCATCH
IFERROR_NUMBER()=3991
RAISERROR('invalidemailaddress',16,10)
ENDCATCH
ThisCLRtriggerisanexample,anditmightnotbethebestsolutiontoyoure-mail
checkingneeds,fortworeasons:firstlybecauseyouneedtohandletheCLRerrorinyour
callingcode,whichforcesustoencloseeverystatementmodifyingtheEmailAddress
inatry…catchblock,andsecondlybecauseofperformanceconsiderations.Your
CLRcodeloopsthroughaDataReaderandchecksitlineperline.Aset-orientedT-SQL
triggerliketheoneshowninListing16-20willcertainlybefaster,especiallyifthereare
manyrowsaffectedbytheINSERTorUPDATEstatement.
Listing16-20.T-SQLTriggertoValidateanE-mailAddress
CREATETRIGGERatr_Person_EmailAddress_ValidateEmail
ONPerson.EmailAddress
AFTERINSERT,UPDATE
ASBEGIN
IF@@ROWCOUNT=0RETURN
IFEXISTS(SELECT*FROMinsertedWHERE
dbo.EmailMatch(EmailAddress)=0)
BEGIN
RAISERROR('anemailisinvalid',16,10)
ROLLBACKTRANSACTION
END
END;
Summary
SQLServer2005introducedSQLCLRintegration,allowingyoutocreateUDFs,UDAs,
SPs,UDTs,andtriggersinmanaged.NETcode.SQLServer2008improvedonCLR
integrationbyallowingUDTsandUDAstohaveamaximumsizeof2.1GB(thesizeof
largeobject(LOB)sizelimit),whichisstillthecaseinSQLServer2014.
Inthischapter,youtalkedaboutCLRintegrationusageconsiderationsandscenarios
whenCLRintegrationcodemightbeconsideredagoodalternativetostrictT-SQL.You
alsodiscussedassembliesandsecurity,includingtheSAFE,EXTERNAL_ACCESS,and
UNSAFEpermissionsetsthatcanbeappliedonaper-assemblybasis.
Finally,youprovidedseveralexamplesofCLRintegrationcodethatcoverawide
rangeofpossibleuses,includingthefollowing:
CLRintegrationcanbeinvaluablewhenaccesstoexternalresources
isrequiredfromtheserver.
CLRintegrationcanbeusefulwhennon-tablespecificaggregations
arerequired.
CLRintegrationsimplifiescomplexdatavalidationsthatwouldbe
complexanddifficulttoperforminT-SQL.
CLRintegrationallowsyoutosupplementSQLServer’sdatatyping
systemwithyourownspecializeddatatypesthatdefinetheirown
built-inmethodsandproperties.
ThischapterhasservedasanintroductiontoCLRintegrationprogramming.Forin-
depthCLRintegrationprogramminginformation,IhighlyrecommendProSQLServer
2005Assemblies,byRobinDewsonandJulianSkinner(Apress,2005).Thoughwritten
forSQLServer2005,muchoftheinformationitcontainsisstillrelevanttoSQLServer
2014.Inthenextchapter,youintroduceclient-side.NETconnectivitytoSQLServer
2014.
EXERCISES
1. [Chooseallthatapply]SQLServer2014providessupportfor
whichofthefollowingCLRintegrationobjects:
a. UDFs
b. UDAs
c. UDTs
d. SPs
e. Triggers
f. User-definedcatalogs
2. [True/False]SQLServer2014limitsCLRintegrationUDAsand
UDTstoamaximumsizeof8000bytes.
3. [Chooseone]SAFEpermissionsallowyourCLRintegrationcode
to
g.Writetothefilesystem
h.Accessnetworkresources
i.Readthecomputer’sregistry
j.Executemanaged.NETcode
k.Alloftheabove
4. [True/False]CLRintegrationUDAsandUDTsmustbedefined
withtheSerializableattribute.
5. [Fillintheblank]ACLRintegrationUDAthatisdeclaredas
Format.UserDefinedmustimplementthe_________
interface.
6. [Chooseallthatapply]ACLRintegrationUDAmustimplement
whichofthefollowingmethods?
l.Init
m.Aggregate
n.Terminate
o.Merge
p.Accumulate
CHAPTER17
DataServices
Today’ssystemsaredisparate,andlargeenterpriseshavewidelyheterogeneous
environments,withWindowsandnon-Windowsplatformsforapplicationdevelopment.
Developers,whetherthey’reenterprisedevelopers,webdevelopers,independentsoftware
vendor(ISV))developers,orDBAs,havedifferentneedsanddifferentwaysofaccessing
thedatathatresidesinSQLServer.Forexample,ISVdeveloperslookforstabilityinthe
platform,enterprisedeveloperslookforrichdevelopmenttoolingexperienceand
interoperability,andwebdeveloperswantthelatestrichdevelopmentexperience.
Similarly,whataPHPdeveloperneedsisverydifferentfromwhata.NETdeveloper
needs.Toachievetherichdevelopmentexperience,developerscanchoosefromvarious
dataaccesslibrariessuchasADO.NET,SQLServer2014NativeClient(SNAC),JDBC,
ODBC,andPHP,basedontheapplication’srequirements.SinceSQLServer2000,the
platformhassupportedinteroperabilitywithWindowsandnon-Windowsenvironments.
SQLServer2000startedsupportingJavadevelopmentusingJDBCdrivers.PHP
applicationdevelopmentsupportwasaddedtoSQLServerwithSQLServer2005.With
SQLServer2014,supportforODBCdriverforLinuxhasbeenadded.Thissimplifies
PHPorotherapplicationdevelopmentonLinuxtoagreaterextent.
Themodelofchoicetoaddressdistributedcomputingandheterogeneous
environmentstodayistheServiceOrientedArchitecture(SOA)paradigm.Therehave
beendifferentwaystogenerateservicesfromqueryresultsovertheSQLServerversions.
Microsoftisnowconcentratingonapowerfulandveryflexibleframeworknamed
WindowsCommunicationFoundation(WCF).Inthischapter,youseehowtouseWCF
DataServicestoprovideservicesandtrendyRESTfulresourcesfromyourdatabases.
Bearwithusfortheexplanationoftheseconcepts.
Butfirst,thedataaccesslibrariessupportapowerfulnewSQLServer2014feature
namedLocalDatabaseruntime(LocalDB).Let’slookatthisveryinterestingwaytoship
solutionswithanembeddeddatabase.
SQLServer2014ExpressLocalDB
DevelopersalwayslookforsimplewaytoinstallandembedSQLServerwiththird-party
applicationsortouseasmalldatabaseenginetoconnecttodiverseremote-datastorage
types.Whenyouwantedtomeetanyoftheserequirementsforcreatingapplicationsprior
toSQLServer2012,theonlyoptionwastouseSQLServerExpressEdition.However,
developersdidn’twanttogothroughtonsofscreenstoinstalltheSQLServer.Ontopof
this,theyhadtoworryaboutsecurityandmanagementoftheSQLServerinstancethey
hadjustinstalled.
StartingwithSQLServer2012,SQLServersimplifiestheexperiencefordevelopers
byintroducingLocalDB,whichwastemporarilycalledServerlessSQLServerduringSQL
Server2012development.Thegoalofthisnewfeatureistosimplifyinstallationand
provideadatabaseasafilewithoutanyadministrationoverheadwhileprovidingthesame
featuresetsasSQLServerExpressEdition.
NoteDatabaseasafilemeansLocalDBallowstheuseofSQLServer,atraditional
client-serverapplication,inalocalcontext,moreorlesslikelocalapplicationssuchas
MicrosoftAccessandSQLite.
TheinstallationofLocalDBissimplifiedtoagreatextent,withnoprerequisites,no
reboots,andnooptionstoselect.Thereisonlyoneglobalinstallation,meaningonlyone
setofbinariesisinstalledpermajorversionofSQLServerforallLocalDBinstances;
thereisnoconstantlyrunningserviceoragentinthebox.TheinstanceofLocalDBis
startedwhentheapplicationconnectstoitandstoppedwhentheapplicationclosesthe
connection.
YoucandownloadLocalDBfromthesamepageastheold-fashionedSQLServer
2014ExpressEdition,atwww.microsoft.com/en-
us/download/details.aspx?id=42299.Twobuildsareavailable:
ENU\x64\SqlLocalDB.MSIfor64-bitsystemsandENU\x86\SqlLocalDB.MSI
for32-bitsystems.MSIfilesareMicrosoftInstallerpackagesthatyoucanrunbydouble-
clickingandtypinglikeanyexecutableinacmdorPowerShellsession.MSIinstallations
areusuallygraphicalwizard-driveninstallations.TheLocalDBinstallationdoesn’trequire
anyuserchoice,soyoucansimplyperformasilentinstallbyusingthefollowing
command:
SQLLocalDB.msi/Quiet
OnceLocalDBisinstalled,youcancreateandmanagetheinstancesbyusing
SQLLocalDB.exe,foundin%ProgramFiles%\MicrosoftSQL
Server\110\Tools\Binn.Fromnowon,eachtimeyoucallSQLLocalDB.exe,it
willbeinthisdirectorycontext.Becauseitisn’tinthepath,youneedtotellyourshell
wheretofindthetool.
NoteTheLocalDBruntime,whichisnothingmorethanaspecificsqlserver.exe
binary,canbefoundin%ProgramFiles%\MicrosoftSQL
Server\120\LocalDB\Binn.
Youcanusethefollowingcommandtofindoutthedetailsoftheexistinginstances:
SQLLocalDB.exeinfo
TocreateaLocalDBinstance,youcanuseSQLLocaldb.exeandspecifythename
oftheinstanceandtheversionnumberwiththecreateoption.Thecommandslisted
nextfirstcreateanSQLServer2014LocalDBinstancenamedSQLSrvWebApp1and
thenstarttheinstance.Finally,youusetheinfocommandtolisttheexistinginstances.
TheresultsareshowninFigure17-1.
SQLLocalDB.execreateSQLSrvWebApp112.0
SQLLocalDB.exestartSQLSrvWebApp1
SQLLocalDB.exeinfo
Figure17-1.QuerytocreateandstartaLocalDBinstancenamedSQLSrvWebApp1
Youmayhaveguessedthatifyouwanttodropaninstance,youcanusethe
SQLLocalDB.exedeletecommand.
TherearetwotypesofLocalDBinstances:automaticandnamed.Automaticinstances
arecreatedbydefault.TherecanbeonlyoneautomaticinstancepermajorversionofSQL
Server.ForSQLServer2014,theautomaticinstancenameisv12.0(whichistheinternal
versionnumberoftheSQLServer2014RTMrelease);theintentforthisinstanceisthatit
bepublicandsharedbymanyapplications.Namedinstancesarecreatedexplicitlybythe
userandaremanagedbyasingleapplication.So,ifyouhaveasmallwebapplicationthat
needstostartsmallandbeimplementedintheenterprise,thebetteroptionistocreatea
namedinstancewhenit’ssmallsothatyoucanisolateandmanagetheapplication.
ToconnecttoaLocalDBinstancewithyourSQLserverNativeClient,OLEDB,or
ODBCprovider,youmentionthe(localdb)keywordintheconnectionstring.Hereare
someexamplesofconnectionstringsthatconnecttoanautomaticinstance(firstline)and
namedinstance(secondline):
NewSQLConnection("Server=(localDB)\v12.0;AttachDBFile=
C:\ProgramFiles\MicrosoftSQLServer\Data
Files\AppDB1.mdf")'
NewSQLConnection("Server=(localDB)\WebApp1;AttachDBFile=
C:\ProgramFiles\MicrosoftSQLServer\Data
Files\WebApp1DB.mdf")'
ThiscodeinvokesLocalDBasachildprocessandconnectstoit.LocalDBrunsasan
applicationwhenyouinitiateaconnectionfromtheclient,andifthedatabaseisn’tused
bytheclientapplicationformorethan5minutes,LocalDBisshutdowntosavesystem
resources.
LocalDBissupportedinODBC,SQLNativeClient,andOLEDBclientproviders.If
theseclientprovidersencounterServer=(localdb)\<instancename>,theyknow
tocalltheLocalDBinstanceifitalreadyexistsortostarttheinstanceautomaticallyas
partoftheconnectionattempt.
Likewise,youcanconnecttoaLocalDBinstanceusingSQLServerManagement
Studio(theExpressorfullversion)orthesqlcmdcommand-linetool,byusingthesame
(localdb)keywordastheservername,asshowninthefollowing:
sqlcmd-S(localdb)\SQLSrvWebApp1
Forthistowork,youneedtomakesuretheLocalDBinstanceisstarted.Youcantestit
byusingtheinfocommandalongwiththeinstancename,asshownnext.Theresultof
thecommandisshowninFigure17-2.Theinstance’sstateisvisibleontheState:line:
SQLLocalDB.exeinfoSQLSrvWebApp1
Figure17-2.ResultsoftheSQLLocalDB.exeinfoSQLSrvWebApp1commandwhentheinstanceisstopped
YoucanseeinFigure17-2thattheinstanceisrunning.Ifithadstopped,youcould
startitusingthestartcommand(shownearlier),andthenyouwouldbeabletoconnect
toit.
NoteConnectingtothe(localdb)keywordissupportedin.NETversion4.0.2
onward.Ifyou’reusinganolder.NETversion,youcanconnecttoaLocalDBinstance,
butyouneedtousethenamedpipeaddressthatisreturnedbytheSQLLocalDB.exe
infocommand.YoucanseethataddressInFigure17-2.Theserver’saddressinthiscase
isnp:\.\pipe\LOCALDB#EC0F7CB5\tsql\query:that’swhatyouwouldneed
toenterintheServeraddressboxforanSSMSconnection,orafterthe–Sparameter
whencallingsqlcmd.
TheauthenticationandsecuritymodelofLocalDBissimplified.Thecurrentuseris
sysadminandistheownerofthedatabasesattachedtotheinstance.Nootherpermission
isapplied.BecausetheLocalDBprocessesrununderauser’saccount,thisalsoimplies
thatthedatabasefilesyouwanttouseonthisinstancemustbeinadirectorywherethe
userhasreadandwritepermissions.Also,whereasSQLServerhidesthephysicaldetails
ofthedatabasestorage,LocalDBfollowsanotherapproach:itgivesaccesstoadatabase
file.ALocalDBconnectionstringsupportstheAttachDbFileNameproperty,which
allowsyoutoattachadatabasefileduringconnection.TheC#consoleapplicationin
Listing17-1illustrateshowtousethedatabaseasafileapproachwithLocalDB.
Listing17-1.ConsoleApplicationtoConnecttoaLocalDBInstance
usingSystem;
usingSystem.Data.SqlClient;
usingSystem.Text;
namespacelocaldbClient
{
classProgram
{
staticvoidMain(string[]args)
{
try
{
SqlConnectionStringBuilderbuilder=
newSqlConnectionStringBuilder(@"Server=
(localdb)
\SQLSrvWebApp1;Integrated
Security=true");
builder.AttachDBFilename
=@"C:\Users\Administrator
\Documents\AdventureWorksLT2014_Data.mdf";
Console.WriteLine("connectionstring="
+builder.ConnectionString);
using(SqlConnectioncn=new
SqlConnection(builder.ConnectionString))
{
cn.Open();
SqlCommandcmd=cn.CreateCommand();
cmd.CommandText="SELECTNameFROM
sys.tables;";
SqlDataReaderrd=cmd.ExecuteReader();
while(rd.Read())
{
Console.WriteLine(rd.GetValue(0));
}
rd.Close();
cn.Close();
}
Console.WriteLine("Pressanykeytofinish.");
Console.ReadLine();
}
catch(Exceptionex)
{
Console.WriteLine(ex.Message);
Console.WriteLine("Pressanykeytofinish.");
Console.ReadLine();
}
}
}
}
TheinterestingelementofthecodeinListing17-1istheconnection-stringbuilder.
YoufirstcreateaSqlConnectionStringBuildertoconnecttothe
(localdb)\SQLSrvWebApp1LocalDB,andthenyouusetheconnectionbuilder’s
AttachDBFilenamepropertytoattachtheAdventureWorksLT2014datafileto
yourLocalDB:
SqlConnectionStringBuilderbuilder=
newSqlConnectionStringBuilder(@"Server=(localdb)
\SQLSrvWebApp1;IntegratedSecurity=true");
builder.AttachDBFilename
=@"C:\Users\Administrator\Documents\AdventureWorksLT2014_Data.mdf";
TheAdventureWorksLT2014_Data.mdffileisintheDocumentsdirectory,
soyouhavefullpermissionsoverit.Whenconnecting,youareautomaticallyinthe
database’scontext,asyoucanseebyexecutingthecode.Alistofthefirsttentablesinthe
AdventureWorksLTdatabaseisreturned,asshowninFigure17-3.Thegenerated
connectionstringisalsoprintedinthefigure.
Figure17-3.ResultsoftheLocalDBclientprogramexecution
DatabasesattachedtoLocalDBcanbethoughtofaspersonaldatabases—thusthe
databaseasafileapproach.YoucanofcourseuseallT-SQLDDLcommandstocreatea
databaseandthetablesinit.Youjustneedtospecifyforthedatabasefilesalocationon
whichyouhavepermissions.Ifyoucreateadatabasewithoutspecifyingalocation,
LocalDBchoosesyouruserdirectory.Forexample,thefollowingcommand
CREATEDATABASEApressDb;
creates.mdfand.ldffilesinyourpersonaldirectory,asshowninFigure17-4.
Figure17-4.TheApressDbdatabasefiles
YoushouldobviouslyspecifyadedicatedlocationwhenyoucreateaLocalDB
database.ThedatabasescreatedorattachedtoaLocalDBinstancewillstayattacheduntil
youdetachorremovethem,evenifyouattachedoneduringaconnectionwiththe
AttachDBFilenamecommand.So,youtheoreticallydon’tneedtoattachiteverytime
youconnect.However,ifyouusetheAttachDBFilenamecommand,thenameofthe
databaseinLocalDBisthefullpathofthedatabasefile.
selectnameFROMsys.databases;
It’seasiertokeeptheAttachDBFilenameoptionintheconnectionstringthat
allowsyoutoattachthedatabaseifitisn’talreadyattached,andenterthedatabasecontext
atconnectiontime,thusprovidingasmootherexperiencefromthedeveloper’spointof
view.
AsynchronousProgrammingwith
ADO.NET4.5
Let’stakeasimplescenarioofanapplicationrequirementtouploadmultiplefilesorthe
needtocreatereportswithpagination.Ineitherscenario,usingasynchronousmodelin
theapplicationcancausetheclientandservertoslowdownconsiderablyandresultin
highermemoryutilizationduetoI/Ooperations.Incaseslikethis,writingthecalls
asynchronouslyinsteadofsynchronouslycanimprovetheuserexperience;however,the
currentmodelhassomeissueswithmanageabilityanddebuggingcapabilitieswith
asynchronouscode.
Startingwith.NET4.5,thenewAsync.NETpatternisextendedtoADO.NET.Now
theconnectionoperationsSqlDataReaderandSqlBulkCopycanusethe
asynchronouscapabilities.Forexample,let’stakethesimplecaseshowninListing17-2
thatopensaconnectionandrunsastoredprocedurenameddbo.GetProductsagainst
aLocalDBinstance.
Listing17-2.ADO.NETCodetoRunaStoredProcedureSynchronously
privatevoidExecuteSP()
{
SqlConnectionStringBuildercnString=new
SqlConnectionStringBuilder();
cnString.DataSource=@"(localdb)\v12.0";
cnString.IntegratedSecurity=true;
using(SqlConnectioncn=new
SqlConnection(cnString.ConnectionString))
{
cn.Open();
SqlCommandcmd=newSqlCommand("EXEC
dbo.GetProducts",cn);
cmd.ExecuteReader();
}
}
Thiscodeopenstheconnectiontothedatabasesynchronouslyandrunsthestored
procedure,waitinguntiltheentireresultsetisreturned.Insteadofwaitingfortheprocess
tocomplete,itwouldbemoreefficienttoperformthisoperationasynchronously.Listing
17-3showsthecodefromListing17-2modifiedforasynchronousexecution.Changes
appearinbold.
Listing17-3.ADO.NETCodetoRunStoredProcedureAsynchronously
privateasyncTaskExecuteSP()
{
SqlConnectionStringBuildercnString=new
SqlConnectionStringBuilder();
cnString.DataSource=@"(localdb)\v12.0";
cnString.IntegratedSecurity=true;
using(SqlConnectioncn=new
SqlConnection(cnString.ConnectionString))
{
awaitcn.OpenAsync();
SqlCommandcmd=newSqlCommand("EXEC
dbo.GetProducts",cn);
awaitcmd.ExecuteReaderAsync();
}
}
IfyoucomparethecodeinListings17-2and17-3,thestructurehasnotchanged;
however,byincludingthekeywordawaitandmodifyingafewkeywords,youretain
readabilityandmanageabilitywhileaddingtheasynchronouscapability.
Everypossibilityforimprovingperformanceontheclientsideisinteresting.Keepin
mind,ofcourse,thatthebestwaytoensureoptimalperformanceindatabasequeryingis
toimprovethestructureandcodeontheserverside.
ODBCforLinux
Formanyyears,andovermanySQLServerversions,developerswhowantedtoaccess
SQLServerfromnon-Windowsenvironmentshadonlyoneoption:usingafreelibrary
namedFreeTDSthatwasoriginallycreatedtoaccessSybaseservers.
NoteTDSstandsforTabularDataStreamandisthenetworklayerprotocolusedby
SybaseandSQLServertoexchangepacketsbetweenthedatabaseserverandtheclient
library.Asyoumayknow,SQLServerwasinitsearlydaysajointdevelopmentbetween
SybaseandMicrosoft.
FreeTDSisfineandworkswell,butitdoesn’tcoverthenewerdatatypesand
functionalitiesSQLServerhastooffer,likeXML,date,time,anddatetime2,or
FILESTREAMdatatypes,orfeatureslikemultipleactiveresultsets(MARS).So,Linux
developerswantingtoaccessSQLServerfromPHPoranyCGIapplicationhadtostickto
alimitedsetoffunctionalities.IfyoueverwrotePHPcodetoaccessSQLServerina
Linuxenvironment,youmayhaveusedtheintegratedPHPMSSQLfunctionsthatcallthe
php5-odbclibrary.It’snothingmorethanalayerusingFreeTDSbehindthescenes.
InanefforttoprovideawiderrangeofpossibilitiesforaccessingSQLServer,
Microsoftdecidedtochangeitsdata-accessstrategy,whichwaspreviouslyinfavorof
OLEDB,byaligningwithODBCfornativeaccesstoSQLServer.OpenDatabase
Connectivity(ODBC)isanAPIfirstdesignedbyMicrosoftthatbecameakindofdefacto
standardforheterogeneousdatabaseaccess.Itallowsaccesstodifferentdatasourcesfrom
manylanguagesandenvironments.
Alongwiththischangeofstrategy,MicrosoftdevelopedanODBCdriverforLinux
thatwasreleasedinMarch2012.Youcandownloaditfrom
www.microsoft.com/en-us/download/details.aspx?id=28160.
Linuxisavailablethoughmanydistributions,whichhavetheirowncoreapplications,
distributionmechanisms,anddirectoryorganization.Atthetimeofthiswriting,Microsoft
offers64-bitpackagesfortheRedHatEnterprisedistributiononly.A32-bitversionis
planned.RedHatEnterprisedoesn’tnecessarilyhavethemostwidespreaddistribution,
andmanycompaniesuseotherdistributions,suchasDebian,Ubuntu,CentOS,andsoon.
TheMicrosoftODBCdrivercanbeinstalledfromotherdistributions,providingyouhave
awaytoinstallthelibrariestheODBCdriverisusing.
CautionIntheLinuxworld,mostofthetoolsusedareopensourceandcanbe
compileddirectlyonthesystem,tolinktotheavailableversionofthelibrariesusedinthe
code.ButtheODBCdriverforSQLServerisn’topensource,andonlythebinariesare
availabletodownload.That’swhyyouneedtoensurethatyougettheproperversionof
thelibrariesusedbytheODBCdriverinstalledontheLinuxbox.
Let’slookatashortexampleusingUbuntuServer.Ubuntuisaverypopular
distributionthatisbasedonDebian,anotherwidespreadLinuxdistribution.
Thedriveryoucandownloadattheaddresspreviouslymentionediscompressedinthe
tar.gzformat,thecommoncompressionformatinLinux.Oncedownloaded,youcan
extractitbyopeningashell,goingtothedirectorywherethecompressedfileis,and
executingthefollowingcommand:
tarxvzfsqlncli-11.0.1790.0.tar.gz
Thetarcommandextractsthearchiveintoanewdirectorynamedheresqlncli-
11.0.1790.0ontheversionoftheODBCdriver.
NoteThexvzfsetofoptionsusedwiththetarcommandiscommonlyusedto
extracttar.gzarchives.xmeanseXtract,andvmeansVerbose;theseoptionsallowthe
extraction’sdetailstobeprintedontheshelloutput.ztellstarthatitneedstodealwitha
gziparchive;andftellstarthatthenameofthefiletoextractwillfollow.
Thearchiveisextractedintoadirectory.Youenteritusingthecd(changedirectory)
command:
cdsqlncli-11.0.1790.0
ThestepstoinstallthedriveronUbuntuarevalidatthetimeofthiswritingwiththe
currentdriverrelease,whichissqlncli-11.0.1790.0forRedHatEnterprise6,andthe
currentUbuntuversion,whichis12.04PrecisePangolin.Thedriverbeinginstalledis
correctatthetimeofwriting,butLinuxminorandmajorversionupgradesoccur
regularly.ThismeanstheMicrosoftdrivermaybeoutofdate,oryoumayneedalater
versionwhenanewoneisbroughtout.However,we’redemonstratingonUbuntu12.04
withthe11.0.1790.0Microsoftdriver,andalthoughinfuturereleasestheprocessmay
vary,wecanhopefullyguideyouinageneralway.
Accordingtoitsdocumentation,theunixodbcversionneededtorunthedriveris
2.3.0.Usingtheapt-cachetoolthatmanagesthecacheofDebianandUbuntu
packages,youcancheckthecurrentunixodbcversiononyoursystem:
apt-cacheshowunixodbc
Theshowoptionreturnsdetailsaboutapackage,andonDebianandUbuntu,the
nameofthepackageissimplyunixodbc.TheresultisshowninFigure17-5.
Figure17-5.apt-cachecommandresult
ThecurrentversiononourUbuntuis2.2.14.Thelibsqlnclidownloadedfrom
Microsoftincludesascriptthatdownloadsandbuildstherequiredunixodbcversion.So
youfirstuninstallthecurrentunixodbcusingtheapt-getcommand,andthenyou
installthenewerunixodbcusingtheMicrosoftscript.Also,youneedtoprefixthe
commandswiththesudoinstructiontoexecutethemwithsuperuser(su)privileges,as
follows.
sudoapt-getremoveunixodbc
sudobash./build_dm.sh
Thereisacatchhere:atthetimeofthiswriting,thebuild_dm.shscript(aswellas
theinstall.shscriptthatyouseeshortly)hasaflaw.Ifyouopenitinatexteditor,
you’llseeonitsfirstlinethatitdeclaresitselfasascriptwrittenfortheshLinuxshell,
usingwhatiscalledtheshebangsyntax,asfollows:
#!/bin/sh
Thisallowsthefiletobeexecutedwithoutmentioningtheinterpreteronthecommand
line.Theshebanglineisread,andtheproperinterpreteriscalled.Theproblemhereisthat
thescriptisdeclaredasbeinganshscript,whereasitisinfactabashscript.shandbash
aretwodifferentLinuxshells.So,tomaketheshellwork,youneedtorunitexplicitly
withbash.Apartialresultofthebuild_dm.shcommandisshowninFigure17-6.
Figure17-6.build_dm.shcommandresult
Theunixodbcdrivermanagerisbuiltandcopiedtoadirectoryin/tmp.Thescript
tellsyouwhattodonext:gothereandusethemakeinstallcommandtocopythe
binariesattherightplace.Whatitdoesn’tsayisthatyouneedadministrativeprivilegesto
runbothcommands(shownonthesamelineinFigure17-6,separatedbyasemicolon).
So,youneedtorunthecommandsasfollows:
sudocd/tmp/unixODBC.22830.6255.24287/unixODBC-2.3.0
sudomakeinstall
Nowthatthedrivermanagerisinstalled,youcangotothenextstep:installingthe
Microsoftdriver.Thefirstthingtodoistochecktheversionsofthelibrariesrequestedby
thedriver.Youcanusethelddcommand,whichreturnsthesharedlibrariesdependencies
ofabinary,tocheckthelibrariesusedbythedriver:
lddlib64/libsqlncli-11.0.so.1790.0
.so(sharedobject)isthecommonextensionforsharedlibrariesonLinux.Onour
system,thecommandreturnstheresultsshowninFigure17-7.
Figure17-7.Resultsofthelddcommand
InFigure17-7,youseethatmostofthelibrariesarefound,excepttheSSLlibraries
libcrypto.so.10andlibssl.so.10.Here,10standsforthedynamicshared
objects’versionnumber.Youneedtofindoutwhetheranyversionsoftheselibrariesare
availableonyoursystem.Todothat,youusethefindcommandasfollows:
find/-namelibcrypto.so.*-print
Asyoumighthaveguessed,thefindcommandsearchesforfiles.Youaskittostart
itssearchattherootofthefilesystem(/),tosearchforlibcrypto.so.*,andtoprint
theresult.Wefoundthisreference:/lib/x86_64-linux-
gnu/libcrypto.so.1.0.0.Thatlookslikewhatyouneed,buthowdoyouallow
thedrivertoseeit?Youcreateasymboliclink—youcouldcallitashortcut—withthe
namerequestedbythedriver,whichisapointertotheinstalledlibrary.Thefollowing
commandsdojustthat:
sudoln-s/lib/x86_64-linux-gnu/libcrypto.so.1.0.0
/lib/x86_64-linux-gnu/libcrypto.so.10
sudoln-s/lib/x86_64-linux-gnu/libssl.so.1.0.0
/lib/x86_64-linux-gnu/libssl.so.10
Youusethelncommandtocreatealink,andthe–soptionspecifiesthatyoucreatea
symboliclink.
Nowyoucaninstallthedriver.Inthedriver’sdirectory,theinstall.shshellscript
allowsyoutocopythefilestothe/opt/microsoft/sqlnclilocationandcreatethe
symboliclinksinthepathtoletthedriveranditstoolsberecognizedonyoursystem.The
/optdirectoryischosenastheinstallpathbecauseit’swhereapplicationsnotinstalled
withthedistributionaresupposedtogo:
sudobash./install.shinstall--force
Onceagainyouusesudotorunthescriptunderadministrativeprivileges,andyou
usebashexplicitly.The—forceoptionisneededonthisdistributiontoprevent
dependencychecksperformedbythescriptfromcancelingtheinstallationprocess.
Theinstallationscriptrunsquickly,andwhenit’sfinished,youcantesttheODBC
driverbyusingthetwotoolsinstalledwithit:aLinuxversionofthebcp(BulkCopy)
tool,andaLinuxversionofthesqlcmdshell.Symboliclinksarecreatedbythe
installationscriptinthepath,soyoucanusesqlcmdwhereveryouareinthefilesystem.
Anexampleofstartingsqlcmdfollows:
sqlcmd-SSQL2014-Uapress-P@press!
ThiscommandconnectstotheSQL2014serverusingtheSQLloginapress,with
password@press!.Ifyoureceiveanerrorsayingthatthelibrarylibcrypto.so.10
(oranylibraryusedbytheODBCdriver)isn’tfound,youmayhavetoinvestigateand
installthelibraryorusethesymboliclinktechniquedescribedearlier.
NotethathereyouconnectusinganSQLloginandnotintegratedsecurity.That’s
logical,youmightthink:you’reonLinux,notloggedintoaWindowsdomain,sohow
couldintegratedsecuritywork?Well,itcan—notfully,butitcan.Forthat,yourLinux
boxmusthaveKerberosproperlyconfigured,whichisoutofthescopeofthisbook;
pleaserefertothisdocumentationentryforahigh-leveldescriptionoftherequirements
forittowork:http://msdn.microsoft.com/en-us/library/hh568450.
Notethatyoucan’timpersonateanaccount,andyou’relimitedtotheLinuxmachine
systemaccount.
JDBC
TousetheJDBCcomponent,firstdownloaditfrom
http://msdn.microsoft.com/en-us/sqlserver/aa937724.aspx.The
driverisaJDBC4driverthatisavailabletodownloadasaWindowsself-extract
executableoratar.gzcompressedfilefornon-Windowsenvironments.Oncethefileis
uncompressed,youhaveadirectorywithtwojarfilesandotherresourcessuchas
documentation.Putthesqljdbc4.jarfile,whichistheJDBC4driver,inyourJava
classpath.TheclasspathisthepathwhereJavasearchesforclassestorunortoimport.
Javadevelopmentisabroadsubject,sowedon’tgivemanydetailshere,butlet’slook
atashortexampleofusingtheJDBCdriver,mainlytoillustratetheuseoftheconnection
string.JDBCconnectioncanbedoneusingaconnectionstring,alsocalledaconnection
URL.InthecaseofSQLServer,it’sverysimilartotheADO.NETorODBCconnection
string.Thegeneralformofthestringisasfollows:
jdbc:sqlserver://[serverName[\instanceName][:portNumber]]
[;property=value[;property=value]]
Othermethods,likesettingpropertiesofaConnectionobject,canbeused;this
exampleusestheconnection-stringmethod.
Listing17-4showsashortbutcompleteexampleofaJavaclassthatletsyouconnect
toSQLServerandrunaquery.Tomakeitmoreinteresting,weassumedthatwewerein
anenvironmentusingAlwaysOnAvailabilityGroups,andweaddedthe
failoverPartneroptionintheconnectionstringtoallowforreconnectingtoamirror
ifthefirstserverdidn’trespond.
Listing17-4.JavaExampleUsingtheMicrosoftJDBCDriver
importjava.sql.*;
publicclassApressExample{
publicstaticvoidmain(String[]args){
StringconnectionUrl
="jdbc:sqlserver://SQL2014;integratedSecurity=true;databaseName=AdventureWorks2014;failoverPartner=SQL2014B";
Connectioncn=null;
Stringqry="SELECTTOP10FirstName,LastNameFROM
Person.Contact";
try{
cn=DriverManager.getConnection(connectionUrl);
runQuery(cn,qry);
}catch(SQLExceptionse){
try{
System.out.println("Connectiontoprincipal
serverfailed,tryingthemirrorserver.");
cn
=DriverManager.getConnection(connectionUrl);
runQuery(cn,qry);
}catch(Exceptione){
e.printStackTrace();
}
}catch(Exceptione){
e.printStackTrace();
}finally{
if(cn!=null)try{cn.close();
}catch(Exceptione){}
}
}
privatestaticvoidrunQuery(Connectioncn,StringSQL){
Statementstmt=null;
ResultSetrs=null;
try{
stmt=cn.createStatement();
rs=stmt.executeQuery(SQL);
while(rs.next()){
System.out.println(rs.getString(0));
}
rs.close();
stmt.close();
}catch(Exceptione){
e.printStackTrace();
}finally{
if(rs!=null)try{rs.close();
}catch(Exceptione){}
if(stmt!=null)try{stmt.close();
}catch(Exceptione){}
}
}
}
NoteIfyourapplicationaccessesSQLServerwithAlwaysOnthatlistensinmultiple
subnetswiththeJDBCdriver,it’simportanttosetthekeyword
MultiSubnetFailover=Trueintheconnectionstring.ThereasonisthatJDBC
driversdon’titeratethroughmultipleIPaddresses;ifthenetworknamelistenstomultiple
IPaddresses,theJDBCdriverspawnsparallelconnectionstotheIPaddressesandlistens
tothefirstonethatresponds.
Forthisexampletowork,saveitinafilenamedApressExample.java,and
compileitwiththeJavacompiler(javac.exeonWindows)aftermakingsurethe
sqljdbc4.jarfileisintheJavaclasspath.Youcouldalsoindicatethepathofthe
driverinthejavaccommandline,asshowninthefollowingexample:
javac.exe-classpath"C:\sqljdbc_4.0\enu\sqljdbc4.jar"
c:\apress\ApressExample.java
ThecompilationresultsinanApressExample.classfilethatyoucanrunwith
java.exe.Onceagain,theJDBCdrivermustbeintheclasspathforittowork.The
classpathisanenvironmentvariable,andanexampleofsettingitforthesessionand
runningthejavaclassinacmdsessiononWindowsisshownnext.Youmustbeinthe
directorywheretheApressExample.classfileis,forittowork:
setclasspath=c:\sqljdbc_4.0\enu\sqljdbc4.jar;.;%classpath%
javaApressExample
Thefirstlineaddsthepathofthesqljdbc4.jarfileandthecurrentdirectoryto
theclasspathenvironmentvariable,soitwillfindtheJDBCdriverandthe
ApressExampleclass.Thesecondlinerunsthecodeexample.
Nowthatyoucanruntheexample,let’scomebacktoitscontent.Thefirstthingyou
dointhecodeisimportthejava.sqlclassessoyouhavetheConnection,
Statement,andotherJDBCclasseshandy.Inthemain()methodofthe
ApressExampleclass,youdefinetheconnectionstringandsettheserver’saddressas
wellasthemirroringserver’saddress.WechosetobeauthenticatedbyWindows,using
IntegratedSecurity:
StringconnectionUrl
="jdbc:sqlserver://SQL2014;integratedSecurity=true;databaseName=AdventureWorks2014;failoverPartner=SQL2014B";
IfyouknowJDBC,youmaybesurprisednottofindaClass.forName()call,as
showninthefollowingsnippet:
Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
TheClass.forName()instructionisusedtoloadtheJDBCdriverandregisterit
totheJDBCDriverManager.Thisisn’trequiredanymoreifyouuseJDBC4,because
inJDBC4,driverscanbeloadedmagicallyjustbybeingontheclasspath.
TherestofthecodeisaprettystandardJavaexample.Let’sconcentrateontheline
thatopenstheconnection:
cn=DriverManager.getConnection(connectionUrl);
It’senclosedinatrycatchblock,inordertocatchaconnectionfailure.Ifsucha
failurehappens,thecatchblockrunstheexactsameconnectioncommand.Thisisto
allowautomaticreconnectionincaseofafailover.Atthesecondconnectionattempt,the
JDBCdriver—onceagainmagically—trieswiththeaddressdefinedinthe
failoverpartneroption.Thissecondattemptmustalsobeenclosedinatry
catchblock,incasetheotherserverdoesn’tanswereither.Becauseyouhavetowrite
theconnectioncodetwice,wechoseheretomovethecodethatusestheconnectiontorun
aqueryinaprivatemethodoftheclass,inordertocallitfromthemain()method.
Service-OrientedArchitectureandWCF
DataServices
Ifyou’readie-hardT-SQLdeveloperwhodoesn’tventuremuchintoMicrosoftclient-side
codeandalltheframeworksandlibraries,youmaycrackasmilewhilereadingthefew
nextparagraphs.T-SQLdevelopersareusedtodealingwithastableandold-fashioned
technologywithnofancynames,whichcouldgivetheimpressionthatit’ssooldandsolid
thatitwillneverchange.Ontheclientside,however,thingsareconstantlymoving.A
historyofdata-accessmethodsandwhataretodaycalleddataservices,becauseofthe
ServiceOrientedArchitecture(SOA)paradigm,couldfillabook,andthatbookwouldbe
fulloftwistsandturns.IntheearlydaysofSQLServer,thedata-accesslibrarieswerethe
nativedblibDLLandtheODBCAPI.ThiswassupersededbyOLEDB,thenbythe
SQLServerNativeClient.Today,we’rereturningtoODBCtoalignwithadefacto
standard,asyousawsinthe“ODBCforLinux”section.ServiceOrientedArchitectureand
WCF
Onthesubjectofdataservices,beforetheconcepteverexisted,developerstalked
aboutdistributedapplications:applicationsthatarebrokenintocomponentsandthatspan
multiplecomputers,allowingdistantinteroperability.Thecomponentsexchanged
informationusingabrokerlikeDistributedComponentObjectModel(DCOM)or
CommonObjectRequestBrokerArchitecture(CORBA)andusedaRemoteProcedure
Call(RPC)model.Withthereleaseofthe.NETframework,Microsoftdevelopeda
replacementforcreatingdistributed.NETcomponents,called.NETRemoting.Butthe
distributedcomponentsmodelhadsomeshortcomings:mainly,thenetworkprotocolsused
werenottailoredfortheWeb,anditwassometimestrickytoallowdistantcomputers
behindfirewallstoworktogether.Also,youhadtoimplementauniquetechnology,
whetheritwasDCOM,CORBA,.NETRemoting,orothers.Moreover,inthecaseof
DCOMand.NETRemoting,youhadtodeveloponWindowsandrunMicrosoftoperating
systemsandtechnologiesoneveryend.
TheSOAparadigmgainedattentionandpopularitybecauseitaddressedthese
limitations.ThegoalofSOAwastousestandardandwidelyusedprotocolslikeHTTP
andSMTPtoexchangeinformationbetweenthecomponentsofadistributedapplication
—exceptthatSOAusesdifferentterminology.Thecomponentsareservices,atermthat
emphasizestheirlooselycoupledandindependentnature;andthedistributedapplication
modelisnamedServiceOrientedArchitecture.UsingprotocolslikeHTTPallowsyouto
takeadvantageofexistingandproventechnologiesandinfrastructuresavailableonall
platformsanddesignedfortheInternet.Toensurethattheinformationexchangedis
understoodoneveryplatform,text-basedstructureslikeXMLandJavaScriptObject
Notation(JSON)areusedtogeneratemessagesthatarecreatedandconsumedbythese
services,whicharecalledwebservices(WS)becauseoftheiruseoftheHTTPprotocol.
ThesemessagesareexchangedmostlyusingaprotocolnamedSOAP(originallyan
acronymforSimpleObjectAccessProtocol).SOAPisanenvelopeinwhichXML
messagesareenclosed;itdefinesasetofpropertiesandfunctionalitiesforthemessage.
Sofarsogood,butanewmodelstartedtogainpopularityinthelastdecade:
RepresentationalStateTransfer(REST).It’sisasetofarchitecturalprinciplesforbuilding
servicescalledresources.ARESTresourceisdefinedbyanaddress,whichisanInternet
addressintheformofauniformresourceidentifier(URI—amoregenerictermforwhatis
calledanURLintheHTTPprotocol).Tocalltheresource,aRESTclientusesstandard
HTTPverbslikeGETandPUTtosendandreceivemessages.So,withREST,youusea
modelclosetowhataWebbrowserwoulddotocallresources;thatmakesitinteresting
mainlybecauseitletsyouuseproventechnologiesonbothsides,anditoffersnativelythe
scalabilityofthewebtechnologies.BecauseRESTismoreaboutofferingresourcesthan
exchangingmessagesperse,thismodelissometimescalledResourceOriented
Architecture(ROA),andasystemimplementingthismodelissaidtobeRESTful.
WithSOAquicklyreplacingdistributedcomponents,librariesorframeworkswere
neededintheMicrosoftworldtobuildwebservices.Thefirstgenerationofthesetools
wascalledASP.NETWebServices(ASMX)andwasreleasedfor.NET1.0.Itwas
quicklycompletedbyWebServicesEnhancement(WSE),whichaddedsomeSOAPWS
specifications.Thatwasanotherprogrammingmodeltolearn,anditwasstilllimited
becauseitdidn'timplementalltheSOApossibilitiesliketheRESTmodel.TobuildXML
messages,youusedthe.NETXMLlibraries;or,usingSQLServer2000,yougenerated
theXMLdirectlyusingtheFORXMLclause,andyouencloseditinaSOAPmessage
usingclientcode.InSQLServer,youcouldalsouseanISAPIextensiontoprovideXML
responsesdirectlyfromSQLserverthroughIIS,withoutusingASMX.
WhenSQLServer2005wasreleased,theISAPIextensionwasreplacedbyan
integratedHTTPendpointcapability.SQLServerwasthenabletoactnativelyasan
HTTPserver,toreceiveandsendbackSOAPmessages.Today,thisfeaturehasbeen
removedfromSQLServer2014,becauseitdidn’tofferacompleteenoughenvironmentto
buildwebservices.Asamatteroffact,ASMXdidn’tofferallofwhatwasneeded,either.
So,Microsoftdecidedtobuildacompleteandflexibleframeworktohandleall
interoperabilitytechnologies,whichitnowcallsConnectedSystems.Thatframeworkis
namedWindowsCommunicationFoundation(WCF).
WCFisintegratedinto.NETandisthewaytogowhentalkingaboutwebservices,
REST,distributedcomponents,andmessagequeuingintheMicrosoftworld.WCFoffers
severallayersthatprovideeverythingneededtocreateconnectedsystems.They’re
schematizedinFigure17-8.
Figure17-8.TheWCFlayersstack
Thecontractslayerconsistsofthecontracts(orinterfaces)definitionclassesthatallow
servicestopublishandagreeonthecontentoftheinformationtheyexchange.Youcan
definedatacontracts,messagecontacts,servicecontracts,andsoon.Theserviceruntime
layeroffersallthebehaviorsnecessarytoimplementtheservices,liketransaction
behavior,parameterfiltering,andsoon.Themessaginglayeroffersencodersandchannels
tohandlethemorephysicalandconcreteexchangeofmessagesandservices.Andfinally,
theactivationandhostinglayerletsyouruntheservices,asanEXE,aWindowsservice,a
COM+application,andsoon.
WCFcanbeusedtocreateservicesorremotingapplicationsortoimplementmessage
queuing.Here,weofcourseconcentrateonaspecificfeatureofWCFthatprovidesavery
simplewaytopublishdataasRESTresources:WCFDataServices.
NoteHereagain,thenameofthetechnologyhaschangedseveraltimesinafewyears.
In2007,weheardaboutprojectAstoria,whichaimedtodeliveraframeworkforcreating
andconsumingdataservicesusingSOA.Whenitwasreleasedin2008alongwith.NET
3.5,itsfinalnamewasADO.NETDataServices,whichwaslaterchangedtoWCFData
Services.
WCFDataServicessupportstheconceptofRESTforaccessingdataremotely.Aswe
brieflysaidbefore,REST-styleservicesprovidesimpleURI-basedquerying,asimpler
mechanismthantheSOAPprotocol.WCFDataServicestranslatesregularHTTPrequests
intocreate,read,update,anddelete(CRUD)operationsagainstadatasource,andit
exchangesdatabyusingtheOpenData(OData)protocol,anopenwebprotocolfor
queryingandupdatingdata.WCFDataServicesusesanHTTPrequest-to-CRUD
operationmapping,asshowninTable17-1.
Table17-1.HTTPRequeststoWCFDataServicesOperations
HTTPRequest WCFDataServicesOperation
GET Querythedatasource;retrievedata.
POST Createanewentityandinsertitintothedatasource.
PUT Updateanentityinthedatasource.
DELETE Deleteanentityfromthedatasource.
CreatingaWCFDataService
Aswithawebservice,thefirststeptocreatingaWCFdataserviceistocreateanew
ASP.NETwebapplicationproject,asshowninFigure17-9.
Figure17-9.CreatinganASP.NETwebapplicationinVisualStudio2010
DefiningtheDataSource
Onceyouhavecreatedawebapplicationproject,youneedtoaddasourceforyourdata.
TheeasiestwayistoaddanADO.NETentitydatamodel(EDM)byright-clickingthe
projectinSolutionExplorer,choosingAddaNewIteminVisualStudio,andselectingthe
ADO.NETEntityDataModeltemplateontheDatapageoftheAddNewItemwindow,as
showninFigure17-10.ThislaunchestheADO.NETEntityDataModelWizard.
Figure17-10.AddinganADO.NETEDMitemtoyourwebapplication
Chapter15coverstheEntityFramework,sowedon’tneedtogointodetailsher.
You’regeneratinganEDMfromtablesintheAdventureWorksdatabase.Includethe
Production.Product,Production.ProductPhoto,and
Production.ProductProductPhototablesofthedatabase,asshowninFigure
17-11.
Figure17-11.AddingtablestotheEDM
Onceyou’veaddedtablestoyourEDM,youcanviewthemintheEntityDataModel
Designer,asyouhaveseenpreviously.
CreatingtheDataService
Thenextstepafteryou’vedefinedyourEDMistoaddaWCFdataserviceitemtoyour
projectthroughtheNewItemmenuoption.TheAddNewItemwindowisshownin
Figure17-12withtheWCFDataServicetemplatehighlighted.
Figure17-12.AddingaWCFdataservice
TheWCFDataServicetemplateautomaticallygeneratesthedataservicelanding
page,namedProductPhotoDataService.svcinthisexample.Thisisthepage
youneedtocalltorequesttheservice.Itssourcefile,named
ProductPhotoDataService.svc.csinthisexample,usesthe
System.Data.Servicesnamespaceandcontainsaclassdefinitionfortheservice
thatdefinesaccessrulesforentitysetsandserviceoperations.Theclassdefinedinthisfile
requiressomemodificationbyhandwhereyouseetheautomaticallygeneratedTODO
comments.Youmustdefinethedatasourceclass—theEFentitiesclass—andata
minimumyoumustsettheentityaccessrulesasshowninListing17-5.
Listing17-5.AdventureWorksDataServiceClassDefinitionUsing
System.Data.Services;
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Data.Services;
usingSystem.Data.Services.Common;
usingSystem.Linq;
usingSystem.ServiceModel.Web;
usingSystem.Web;
namespaceWCFDataServicesSample
{
publicclassProductPhotoDataService
:DataService<AdventureWorksEntities>
{
//Thismethodiscalledonlyoncetoinitialize
service-widepolicies.
publicstaticvoid
InitializeService(DataServiceConfigurationconfig)
{
config.SetEntitySetAccessRule("Products",
EntitySetRights.AllRead);
config.SetEntitySetAccessRule("ProductPhotoes",
EntitySetRights.AllRead);
config.SetEntitySetAccessRule("ProductProductPhotoes",
EntitySetRights.AllRead);
config.DataServiceBehavior.MaxProtocolVersion
=DataServiceProtocolVersion.V2;
}
}
}
CautionYoucanusethewildcardcharacter(*)tosetrightsforallentitiesandservice
operationsatonce,butMicrosoftstronglyrecommendsagainstthis.Althoughit’suseful
fortestingpurposes,inaproductionenvironmentthiscanleadtoserioussecurity
problems.
Listing17-5mentionstheentitysetnamesthatwerepluralizedbyEF,whichiswhy
thecodeincludesthePhotoesfaultypluralform.Feelfreetocorrectitintheentity
modelsource.YousettheaccessrulestoAllRead,meaningtheserviceallowsqueries
bykeyorqueriesforallcontentsoftheentityset.TherightsallowedareshowninTable
17-2.
Table17-2.ServiceEntityandOperationAccessRights
AccessRights Entity/Operation Description
All Both Allowsfullread/writeaccesstotheentityandfullreadaccessto
operations.
AllRead Both
Allowsfullreadaccesstotheentityoroperation.It’sshorthandfor
ReadSingleandReadMultipleaccessrightscombinedwith
alogicalOR(|)operation.
AllWrite Entity
Allowsfullwriteaccesstotheentity.It’sshorthandfor
WriteAppend,WriteUpdate,andWriteDeleteaccess
rightscombinedwithalogicalOR(|)operation.
None Both Allowsnoreadorwriteaccess,anddoesn’tappearintheservices
metadatadocument.
ReadSingle Both Allowsforqueriesbykeyagainstanentityset.
ReadMultiple Both Allowsforqueriesfortheentirecontentsoftheset.
WriteAppend Entity Allowsnewresourcestobeappendedtotheset.
WriteDelete Entity Allowsexistingresourcestobedeletedfromtheset.
WriteUpdate Entity Allowsexistingresourcestobeupdatedintheset.
YoucantestyourWCFdataservicebyrunningitinDebugmodefromVisualStudio.
VisualStudioopensabrowserwindowwiththeaddresssettothestartpageforyour
project.Changeittotheaddressofthedataservice,whichinourcaseis
http://localhost:59560/ProductPhotoDataService.svc.
NoteYoucanalsosetyourWCFdataservicepage(.svcextension)astheproject
startpage.Inthatcase,youcandeletetheDefault.aspxpageintheproject,because
it’snotneeded.
Yourstartaddressandportnumberwillmostlikelybedifferent.TheWCFdata
servicerespondstoyourrequestwithalistingofentitiesforwhichyouhaveaccess,as
showninFigure17-13.
Figure17-13.CallingthepagefortheWCFdataservice
TipWCFDataServicessupportstwopayloadtypes.Thepayloadtypeisthestandard
formatforincomingrequestdataandoutgoingresultsdata.WCFDataServicessupports
bothJSONandtheAtomPublishingProtocolforpayloads.Ifyoucallthepageforyour
WCFdataserviceandtheresultslooklikeanonsensicalsyndicationfeedinsteadof
standardXML,youneedtoturnoffthefeed-readingviewinyourbrowser.InInternet
Explorer7,youcanunchecktheTools InternetOptions Content Settings Turn
OnFeedReadingViewoption.
Onceyou’veconfirmedthattheWCFdataserviceisupandrunning,youcanquery
theserviceusingacombinationofpathexpression–stylesyntaxintheURItolocate
entitiesandquerystringparameterstofurtherrestrictandcontroloutput.Thefollowing
aresomeexamplesofWCFdataservicequeries:
http://localhost:59560/ProductPhotoDataService.svc/Products
ThisqueryretrievesallProductentities.
http://localhost:59560/ProductPhotoDataService.svc/Products(749
ThisqueryretrievestheProductentitieswithaprimarykeyvalue
of749.TheprimarykeyoftheProductentityisProductID.
http://localhost:59560/ProductPhotoDataService.svc/Products?
$skip=10&$top=10:ThisqueryskipsthefirsttenProduct
entitiesandretrievesthefollowingten(items11through20)inkey
order.
http://localhost:59560/ProductPhotoDataService.svc/Products?
$top=20&$orderby=Name:Thisqueryretrievesthefirst20
Productentitiesordered(sorted)bytheNameattribute.
http://localhost:59560/ProductPhotoDataService.svc/Products?
$filter=ListPricegt
1000&$expand=ProductProductPhotoes/ProductPhoto:
ThisqueryretrievesallProductentitieswithaListPrice
attributethatisgreaterthan1,000.Theresultsincluderelated
ProductProductPhotoandProductPhotoentitiesexpanded
inline.Notethatintheexpandedoption,youneedtomentionfirstthe
entitysetandthentheentitieslinkedtotheset,whichiswhyyouhave
ProductProductPhotoesandthenProductPhoto.
ThisisjustasmallsamplingofthetypesofREST-stylequeriesyoucancreateusing
WCFDataServices.Infact,WCFDataServicessupportsseveralquerystringoptions,as
showninTable17-3.
Table17-3.QueryStringOptions
Option Description
$expand Expandsresultstoincludeoneormorerelatedentitiesinlineintheresults.
$filter
Restrictstheresultsreturnedbyapplyinganexpressiontothelastentitysetidentifiedinthe
URIpath.The$filteroptionsupportsasimpleexpressionlanguagethatincludeslogical,
arithmetic,andgroupingoperators,andanassortmentofstring,date,andmathfunctions.
$orderby
Orders(sorts)resultsbytheattributesspecified.Youcanspecifymultipleattributesseparated
bycommas,andeachattributecanbefollowedbyanoptionalascordescmodifier
indicatingascendingordescendingsortorder,respectively.
$skip Skipsagivennumberofrowswhenreturningresults.
$top Restrictsthenumberofentitiesreturnedtothespecifiednumber.
CreatingaWCFDataServiceConsumer
OnceyouhaveaWCFdataserviceupandrunning,creatingaconsumerapplicationis
relativelysimple.Forthisexample,youcreateasimple.NETapplicationthatcallsthe
servicetodisplaytheimageanddetailsofproductsselectedfromadrop-downlist.
Thefirststepinbuildingaconsumerapplicationistocreateclassesbasedonyour
EDM.Insteadofdoingsomanually,youcangeneratethecreationoftheseclassesby
usingtheAddServiceReferencecommandinVisualStudio,whichautomatically
generatesC#orVisualBasicclassesforuseinclientapplications.Forthisexample,we
createdanASP.NETwebapplication,right-clickedtheprojectintheSolutionExplorer,
andchosetheAddServiceReferencecommand.IntheAddServiceReferenceWindow,
weaddedtheWCFdataserviceaddressandclickedGo.VisualStudioqueriedthe
service’smetadata.Figure17-14showstheresultofthisrequest.
Figure17-14.AddingaservicereferenceinVisualStudio2010
SteptwooftheprocessistocreatetheDefault.aspxpageoftheclient
application.Thispageperformsthenecessarycallstotheservice.Youaren’ttiedtoaweb
application,however;youcanjustaseasilycallADO.NETdataservicesfromWindows
applications,Silverlightapplications,oranyotherplatformthatcaninitiateHTTPrequests
(althoughobjectdeserializationonplatformsthatdon’tsupport.NETclassescouldposea
bitofachallenge).Forthisclientapplication,wesimplyaddedadrop-downlist,animage
control,andatabletothewebform.Thenwewiredupthepageloadanddrop-downlist-
selection-changeevents.ThecodeisshowninListing17-6,withresultsshowninFigure
17-15.
Listing17-6.ASP.NETClientApplicationDefault.aspxPage
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Linq;
usingSystem.Web;
usingSystem.Web.UI;
usingSystem.Web.UI.WebControls;
usingWCFdsClient.PhotoServiceReference;
usingSystem.Data.Services.Client;
namespaceWCFdsClient
{
publicpartialclass_Default:System.Web.UI.Page
{
protectedvoidPage_Load(objectsender,EventArgse)
{
PopulateDropDown();
}
privatevoidPopulateDropDown()
{
AdventureWorksEntitiesctx=new
AdventureWorksEntities(
newUri
("http://localhost:59560/ProductPhotoDataService.svc")
);
varqry=frompinctx.Products
wherep.FinishedGoodsFlag
orderbyp.Name
selectp;
foreach(Productpinqry){
ProductDropDown.Items.Add(newListItem(p.Name,
p.ProductID.ToString()));
}
stringid=ProductDropDown.SelectedValue;
UpdateImage(id);
}
privatevoidUpdateImage(stringid){
ProductImage.ImageUrl
=string.Format("GetImage.aspx?id={0}",id);
}
protectedvoid
ProductDropDownlist_SelectedIndexChanged(objectsender,
EventArgse)
{
stringid=ProductDropDown.SelectedValue;
AdventureWorksEntitiesctx=new
AdventureWorksEntities(
new
Uri("http://localhost:59560/ProductPhotoDataService.svc")
);
varqry=frompinctx.Products
wherep.ProductID==Convert.ToInt32(id)
selectp;
//DataServiceOuery<Product>qry
=ctx.CreateOuery<Product>(string.Format("/Product({0})",
id));
foreach(Productpinqry)
{
TableProduct.Rows[0].Cells[1].Text=p.Class;
TableProduct.Rows[1].Cells[1].Text=p.Color;
TableProduct.Rows[2].Cells[1].Text=p.Size+"
"+p.SizeUnitMeasureCode;
TableProduct.Rows[3].Cells[1].Text=p.Weight
+""+p.WeightUnitMeasureCode;
TableProduct.Rows[4].Cells[1].Text
=p.ListPrice.ToString();
TableProduct.Rows[5].Cells[1].Text
=p.ProductNumber;
}
UpdateImage(id);
}
}
}
Figure17-15.CallingtheWCFdataservicefromaconsumerapplication
Thefirstpartofthecodeimportsthenecessarynamespaces.The
System.Data.Services.ClientnamespaceisrequiredtocreateWCFData
Servicesclientqueries.Youneedtoaddareferencetothe
System.Data.Services.Clientcomponentlibrarytoyourproject.The
WCFdsClient.PhotoServiceReferencenamespaceisareferencetotheEDM
classes’namespace:
usingWCFdsClient.PhotoServiceReference;
usingSystem.Data.Services.Client;
ThePageLoadeventoftheDefault.aspxpagecallsalittlefunctioncalled
PopulateDropDownthatpopulatesthedrop-downlistwiththenamesandIDsofall
“finishedgoods”productsthatAdventureWorkskeepsinitsdatabase:
PopulateDropDown();
ThePopulateDropDownfunctionbeginsbycreatinganinstanceofthe
AdventureWorksEntitiesEDMdatacontextthatpointstotheURIoftheWCF
dataservice.YousawdatacontextsinChapter15.Here,inWCFDataServices,theobject
isasiblingnamedaDataServiceContext:
AdventureWorksEntitiesctx=newAdventureWorksEntities(
newUri
("http://localhost:59560/ProductPhotoDataService.svc")
);
Next,thisfunctionusesaLINQqueryontheAdventureWorksEntities
DataServiceContextthatreturnsaDataServiceOuery.Thequeryfiltersthe
ProductentitieswhoseFinishedGoodsFlagattributesaresettotrue.Resultsare
sortedbytheNameattribute:
varqry=frompinctx.Products
wherep.FinishedGoodsFlag
orderbyp.Name
selectp;
ThequeryreturnsanIEnumerableresultthatcanbeiteratedusingforeach.In
thisexample,theNameandProductIDattributesareiteratedandaddedtothedrop-
downlist:
foreach(Productpinqry){
ProductDropDown.Items.Add(newListItem(p.Name,
p.ProductID.ToString()));
}
Finally,theproductimageisupdatedbasedonthevalueselectedinthedrop-downlist:
stringid=ProductDropDown.SelectedValue;
UpdateImage(id);
YoualsowiretheSelectedIndexChangedeventofthedrop-downlistsothatthe
imageandotherdatabeingdisplayedareupdatedwhentheuserselectsanewproduct.
Thefirstthingthisfunctiondoesisretrievethecurrentlyselectedvaluefromthedrop-
downlist:
stringid=ProductDropDown.SelectedValue;
Then,aswiththePopulateDropDownfunction,thisfunctionqueriestheWCFdata
servicetoretrievetheproductselectedfromthedrop-downlist:
AdventureWorksEntitiesctx=newAdventureWorksEntities(
new
Uri("http://localhost:59560/ProductPhotoDataService.svc")
);
varqry=frompinctx.Products
wherep.ProductID==Convert.ToInt32(id)
selectp;
Next,thefunctioniteratestheresultsandupdatesthedisplay,includingthesummary
informationtableandtheproductimage:
foreach(Productpinqry)
{
TableProduct.Rows[0].Cells[1].Text=p.Class;
TableProduct.Rows[1].Cells[1].Text=p.Color;
TableProduct.Rows[2].Cells[1].Text=p.Size+""
+p.SizeUnitMeasureCode;
TableProduct.Rows[3].Cells[1].Text=p.Weight+""
+p.WeightUnitMeasureCode;
TableProduct.Rows[4].Cells[1].Text
=p.ListPrice.ToString();
TableProduct.Rows[5].Cells[1].Text=p.ProductNumber;
}
UpdateImage(id);
TheUpdateImagefunction,calledbytwooftheeventhandlersinthisexample,
consistsofasinglelinethatchangestheURLoftheproductimage:
ProductImage.ImageUrl=string.Format("GetImage.aspx?id=
{o}",id);
NoteInordertoactuallyshowtheimagesonawebpage,wehadtoresorttoanold
ASP.NETtrick.Becausetheimagesarestoredinthedatabase,wehadtocreateasecond
pageintheprojectcalledGetImage.aspxtoretrievetheappropriateimage.This
methodcallstheWCFdataserviceandreturnsthebinaryproductphotoimageasaJPEG
image.Wewon’tgointothedetailsherebecausethey’renotessentialtounderstanding
WCFDataServices,butthesourcecodeisavailableinthedownloadablesamplefilesfor
thecurious.
Nowthatyou’veseenhowtocreateabasicWCFdataserviceconsumer,let’sreview
someoftheSQLServer2014featuressupportedinADO.NET4.5.ADO.NET4.5enables
supportfornullbitcompressionusingsparsecolumnstooptimizedatatransferoverthe
wire.Imagineatableinwhichmorethanhalfthecolumnsarenullableandhavenull
valuesforalltherows.Whenyouusenullbitcompressionandasparsecolumnschema,
youcansaveonstorageaswellasoptimizedatatransferoverthewire.
ADO.NET4.5alsoaddssupportforLocalDB.RememberthatLocalDBneedstobe
startedforyourcodetobeabletoaccessit.
Summary
SQLServer2012introducedanadditiontoSQLServerExpressnamedLocalDBthatlets
youusedatabasesasfilesinapplicationsandsimplifiesembeddingdatabasecapabilities
inlocal,easy-to-deployapplications.Atthesametime,SQLServerdata-accesslibraries
keepimproving,providingaheterogeneousenvironmentwithLinuxsystemsandJava
code.
InSQLServer2005,MicrosoftintroducedHTTPSOAPendpoints,whichallowed
developerstoexposeSPsandUDFsinthedatabaseaswebservicemethods.Becauseit
wasn’tafull-featuredandsolidenoughimplementation,andalsobecauseMicrosoftwants
tofocusonaunifiedframeworkforconnectedsystems,HTTPendpointshavebeen
removedfromSQLServer2014.
ThechapterendedwithanintroductiontoWCFDataServices.Withbuilt-insupport
forentitydatamodelsandthepowerfulADO.NETEDMdesigner,REST-stylequerying,
andboththeJSONandAtompayloadformats,WCFDataServicescanprovidea
lightweightalternativetoSOAP-basedwebservicesandisagoodwaytoprovide
interoperabilityacrosssystems.
EXERCISES
1. [True/False]ALocalDBinstancecanberunasaWindowsservice.
2. [True/False]Youcan’taccessanXMLdata-typecolumnifyou
accessSQLServerfromaLinuxcomputer.
3. [True/False]HTTPSOAPendpointscanbecreatedinSQLServer
2014.
4. [Fillintheblank]VisualStudio2010and2012providea
_________projecttemplatetocreatenewwebservices.
5. [True/False]VisualStudio2012includesagraphicalEDM
designer.
6. [Chooseone]WCFDataServicesacceptswhichtypeofquery
requests?
a. SQLqueries
b. XSLTqueries
c. REST-stylequeries
d. Englishlanguagequeries
CHAPTER18
ErrorHandlingandDynamicSQL
PriortoSQLServer2005,errorhandlingwaslimitedalmostexclusivelytothe@@error
systemfunctionandtheRAISERRORstatement,oritwasperformedthroughclient-side
exceptionhandling.T-SQLinSQLServer2014stillprovidesaccesstothesetools,butit
alsosupportsmodernstructurederrorhandlingsimilartothatofferedbyotherhigh-level
languagessuchasC++,C#,andVisualBasic.ThischapterdiscusseslegacyT-SQLerror-
handlingfunctionalityandthenewerstructurederror-handlingmodelinT-SQL.The
chapterintroducestoolsusefulfordebuggingserver-sidecode,includingT-SQL
statementsandtheVisualStudioIDE.
ThechapteralsodiscussesdynamicSQL,whichisoftenmoredifficulttodebugand
managethanstandard(nondynamic)T-SQLstatements.DynamicSQL,althoughauseful
tool,alsohassecurityimplications,asyou’llsee.
ErrorHandling
SQLServer2012providedseveralimprovementsinerrorhandlingoverSQLServer2008
andpriorreleasesthathavebeencarriedintoSQLServer2014.Thissectiondiscusses
legacyerrorhandling,SQLServer2008TRY…CATCHstructurederrorhandling,aswellas
theTHROWstatementintroducedinSQL2014.
NoteItmayseemoddtostillbereferringin2014toanerror-handlingmechanism
introducedinSQLServer2000.Therealityisthatyou’relikelytoencounterthe
@@errorstatementinmuchofyourcode;anddespitecertainlimitationsandrestrictions,
itremainsusefulforerrorhandling.
LegacyErrorHandling
InSQLServer2000,theprimarymethodofhandlingexceptionswasthroughthe
@@errorsystemfunction.Thisfunctionreturnsanintvaluerepresentingthecurrent
errorcode.An@@errorvalueof0meansnoerroroccurred.Oneofthemajor
limitationsofthisfunctionisthatit’sautomaticallyresetto0aftereverysuccessful
statement.Thismeansyoucan’thaveanystatementsbetweenthecodeyouthinkmight
produceanexceptionandthecodethatchecksthevalueof@@error.Italsomeansthat
after@@errorischecked,it’sautomaticallyresetto0,soyoucan’tbothcheckthevalue
of@@errorandreturn@@errorfrominanSP.Listing18-1demonstratesanSPthat
generatesanerrorandattemptstoprinttheerrorcodefromwithintheprocedureand
returnthevalueof@@errortothecaller.
Listing18-1.IncorrectErrorHandlingwith@@error
CREATEPROCEDUREdbo.TestError(@eintOUTPUT)
AS
BEGIN
INSERTINTOPerson.Person(BusinessEntityID)
VALUES(1);
PRINTN'Errorcodeinprocedure='+CAST(@@errorAS
nvarchar(10));
SET@e=@@error;
END
GO
DECLARE@retint,
@eint;
EXEC@ret=dbo.TestError@eOUTPUT;
PRINTN'Returnederrorcode='+CAST(@eASnvarchar(10));
PRINTN'Returnvalue='+CAST(@retASnvarchar(10));
TheTestErrorprocedureinListing18-1demonstratesoneproblemwith
@@error.Theresultofexecutingtheprocedureshouldbesimilartothefollowing:
Msg515,Level16,State2,ProcedureTestError,Line4
CannotinsertthevalueNULLintocolumn'PersonType',table
'AdventureWorks.Person.Person';columndoesnotallownulls.
INSERTfails.
Thestatementhasbeenterminated.
Errorcodeinprocedure=515
Returnederrorcode=0
Returnvalue=-6
Asyoucansee,theerrorcodegeneratedbythefailedINSERTstatementis515when
printedintheSP,butavalueof0(noerror)isreturnedtothecallerviatheOUTPUT
parameter.TheproblemiswiththefollowinglineintheSP:
PRINTN'Errorcodeinprocedure='+CAST(@@errorAS
nvarchar(10));
ThePRINTstatementautomaticallyresetsthevalueof@@errorafteritexecutes,
meaningyoucan’ttestorretrievethesamevalueof@@errorafterward(itwillbe0
everytime).Theworkaroundistostorethevalueof@@errorinalocalvariable
immediatelyafterthestatementyoususpectmightfail(inthiscase,theINSERT
statement).Listing18-2demonstratesthismethodofusing@@error.
Listing18-2.CorrectedErrorHandlingwith@@error
CREATEPROCEDUREdbo.TestError2(@eintOUTPUT)
AS
BEGIN
INSERTINTOPerson.Person(BusinessEntityID)
VALUES(1);
SET@e=@@error;
PRINTN'Errorcodeinprocedure='+CAST(@eAS
nvarchar(10));
END
GO
DECLARE@retint,
@eint;
EXEC@ret=dbo.TestError2@eOUTPUT;
PRINTN'Returnederrorcode='+CAST(@eASnvarchar(10));
PRINTN'Returnvalue='+CAST(@retASnvarchar(10));
Bystoringthevalueof@@errorimmediatelyafterthestatementyoususpectmight
causeanerror,youcantestorretrievethevalueasoftenasyoulikeforfurtherprocessing.
Thefollowingistheresultofthenewprocedure:
Msg515,Level16,State2,ProcedureTestError2,Line4
CannotinsertthevalueNULLintocolumn'PersonType',table
'AdventureWorks.Person.Person';
columndoesnotallownulls.INSERTfails.
Thestatementhasbeenterminated.
Errorcodeinprocedure=515
Returnederrorcode=515
Returnvalue=-6
Inthiscase,theproper@@errorcodeisbothprintedandreturnedtothecallerbythe
SP.AlsoofnoteisthattheSPreturnvalueisautomaticallysettoanonzerovaluewhen
theerroroccurs.
TheRAISERRORStatement
RAISERRORisaT-SQLstatementthatallowsyoutothrowanexceptionatruntime.The
RAISERRORstatementacceptsamessageIDnumberormessagestring,severitylevel,
stateinformation,andoptionalargumentparametersforspecialformattingcodesinerror
messages.Listing18-3usesRAISERRORtothrowanexceptionwithacustomerror
message,aseveritylevelof17,andastateof127.
Listing18-3.RaisingaCustomExceptionwithRAISERROR
RAISERROR('Thisisanexception.',17,127);
WhenyoupassastringerrormessagetotheRAISERRORstatement,asinListing18-
3,adefaulterrorcodeof50000israised.IfyouspecifyamessageIDnumberinstead,the
numbermustbebetween13000and2147483647,anditcan’tbe50000.Theseveritylevel
isanumberbetween0and25,witheachlevelrepresentingtheseriousnessoftheerror.
Table18-1liststheseveritylevelsrecognizedbySQLServer.
Table18-1.SQLServerErrorSeverityLevels
Range Description
0–10 Informationalmessages
11–18 Errors
19–25 Fatalerrors
TipOnlymembersofthesysadminfixedserverroleofuserswithALTERTRACE
permissionscanspecifyseveritylevelsgreaterthan18withRAISERROR,andtheWITH
LOGoptionmustbeused.
ThestatevaluepassedtoRAISERRORisauser-definedinformationalvaluebetween
1and127.Thestateinformationcanbeusedtohelplocatespecificerrorsinyourcode
whenusingRAISERROR.Forinstance,youcanuseastateof1forthefirstRAISERROR
statementinagivenSPandastateof2forthesecondRAISERRORstatementinthesame
SP.ThestateinformationprovidedbyRAISERRORisn’tasnecessaryinSQLServer2014
becauseyoucanretrievemuchmoredescriptiveandpreciseinformationfromthe
functionsavailableinCATCHblocks.
TheRAISERRORstatementsupportsanoptionalWITHclauseforspecifying
additionaloptions.TheWITHLOGoptionlogstheerrorraisedtotheapplicationlogand
theSQLerrorlog,theWITHNOWAIToptionsendstheerrormessagetotheclient
immediately,andtheWITHSETERRORoptionsetsthe@@errorsystemfunction(ina
CATCHblock)toanindicatedmessageIDnumber.Thisshouldbeusedwithaseverityof
10orlesstoset@@errorwithoutcausingothersideeffects(forexample,batch
termination).
RAISERRORcanbeusedinaTRYorCATCHblocktogenerateerrors.IntheTRY
block,ifRAISERRORgeneratesanerrorwithaseveritybetween11and19,control
passestotheCATCHblock.Forerrorswithaseverityof10orlower,processingcontinues
intheTRYblock.Forerrorswithaseverityof20orhigher,theclientconnectionis
terminatedandcontroldoesn’tpasstotheCATCHblock.Forthesehigh-severityerrors,
theerrorisreturnedtothecaller.
Try…CatchExceptionHandling
SQLServer2014supportstheTRY…CATCHmodelofexceptionhandling,whichis
commoninothermodernprogramminglanguagesandwasfirstintroducedinSQLServer
2008.IntheT-SQLTRY…CATCHmodel,youwrapthecodeyoususpectcouldcausean
exceptioninaBEGINTRY…ENDTRYblock.Thisblockisimmediatelyfollowedbya
BEGINCATCH…ENDCATCHblockthatisinvokedonlyifthestatementsintheTRY
blockcauseanerror.Listing18-4demonstratesTRY…CATCHexceptionhandlingwitha
simpleSP.
Listing18-4.SampleTRY…CATCHErrorHandling
CREATEPROCEDUREdbo.TestError3(@eintOUTPUT)
AS
BEGIN
SET@e=0;
BEGINTRY
INSERTINTOPerson.Address(AddressID)
VALUES(1);
ENDTRY
BEGINCATCH
SET@e=ERROR_NUMBER();
PRINTN'ErrorCode='+CAST(@eASnvarchar(10));
PRINTN'ErrorProcedure='+ERROR_PROCEDURE();
PRINTN'ErrorMessage='+ERROR_MESSAGE();
ENDCATCH
END
GO
DECLARE@retint,
@eint;
EXEC@ret=dbo.TestError3@eOUTPUT;
PRINTN'Errorcode='+CAST(@eASnvarchar(10));
PRINTN'Returnvalue='+CAST(@retASnvarchar(10));
TheresultissimilartoListing18-2,butSQLServer’sTRY…CATCHsupportgivesyou
morecontrolandflexibilityovertheoutput,asshownhere:
(0row(s)affected)
ErrorCode=544
ErrorProcedure=TestError3
ErrorMessage=Cannotinsertexplicitvalueforidentity
columnintable
'Address'whenIDENTITY_INSERTissettoOFF.
Returnederrorcode=544
Returnvalue=-6
TheT-SQLstatementsintheBEGINTRY…ENDTRYblockexecutenormally.Ifthe
blockcompleteswithouterror,theT-SQLstatementsbetweentheBEGINCATCH…END
CATCHblockareskipped.IfanexceptionisthrownbythestatementsintheTRYblock,
controltransferstothestatementsintheBEGINCATCH…ENDCATCHblock.
TheCATCHblockexposesseveralfunctionsfordeterminingexactlywhaterror
occurredandwhereitoccurred.Listing18-4usessomeofthesefunctionstoreturn
additionalinformationabouttheexceptionthrown.Thesefunctionsareavailableonly
betweentheBEGINCATCH…ENDCATCHkeywords,andonlyduringerrorhandling
whencontrolhasbeentransferredtotheCATCHblockbyanexceptionthrowninaTRY
block.IfusedoutsideofaCATCHblock,allofthesefunctionsreturnNULL.Thefunctions
availablearelistedinTable18-2.
Table18-2.CATCHBlockFunctions
FunctionName Description
ERROR_LINE() Returnsthelinenumberonwhichtheexceptionoccurred
ERROR_MESSAGE() Returnsthecompletetextofthegeneratederrormessage
ERROR_PROCEDURE() ReturnsthenameoftheSPortriggerwheretheerroroccurred
ERROR_NUMBER() Returnsthenumberoftheerrorthatoccurred
ERROR_SEVERITY() Returnstheseverityleveloftheerrorthatoccurred
ERROR_STATE() Returnsthestatenumberoftheerrorthatoccurred
TRY…CATCHblockscanbenested.YoucanhaveTRY…CATCHblocksinotherTRY
blocksorCATCHblockstohandleerrorsthatmightbegeneratedinyourexception-
handlingcode.
YoucanalsotestthestateoftransactionsinaCATCHblockbyusingthe
XACT_STATEfunction.It’sstronglyrecommendedthatyoutestyourtransactionstate
beforeissuingaCOMMITTRANSACTIONorROLLBACKTRANSACTIONstatementin
yourCATCHblock,toensureconsistency.Table18-3liststhereturnvaluesfor
XACT_STATEandhowyoushouldhandleeachinyourCATCHblock.
Table18-3.XACT_STATEFunctionReturnValues
XACT_STATE Meaning
-1 Anuncommittabletransactionispending.IssueaROLLBACKTRANSACTIONstatement.
0 Notransactionispending.Noactionisnecessary.
1Acommittabletransactionispending.IssueaCOMMITTRANSACTIONstatement.
TheT-SQLTRY…CATCHmethodoferrorhandlinghascertainlimitationsattachedto
it.Forone,TRY…CATCHcanonlycaptureerrorsthathaveaseveritygreaterthan10that
don’tclosethedatabaseconnection.Thefollowingerrorsaren’tcaught:
Errorswithaseverityof10orlower(informationalmessages)aren’t
caught.
Errorswithaseverityof20orhigher(connection-terminationerrors)
aren’tcaught,becausetheyclosethedatabaseconnection
immediately.
Mostcompile-timeerrors,suchassyntaxerrors,aren’tcaughtby
TRY…CATCH,althoughthereareexceptions(forexample,whenusing
dynamicSQL).
Statement-levelrecompilationerrors,suchasobject-nameresolution
errors,aren’tcaught,duetoSQLServer’sdeferred-nameresolution.
AlsokeepinmindthaterrorscapturedbyaTRY…CATCHblockaren’treturnedtothe
caller.Youcan,however,usetheRAISERRORstatement(describedinthenextsection)to
returnerrorinformationtothecaller.
TRY_PARSE,TRY_CONVERT,andTRY_CAST
SQLServer2012introducedadditionalenhancementstotheTRYcommand.The
TRY_PARSE,TRY_CONVERT,andTRY_CASTfunctionsoffererror-handlingsimplicity
tosomecommonT-SQLproblems.Forexample,theTRY_PARSEfunctionattemptsto
convertastringvaluetoadatetypeornumerictype.Iftheattemptfails,SQLreturnsa
NULLvalue.InpreviousversionsofSQLServer,youusedCASTorCONVERTandhadto
writecodetocaptureanyerrors.ThesyntaxfortheTRY_PARSEcommandisasfollows:
TRY_PARSE(string_valueASdata_type[USINGculture])
Theculturestatementallowsyoutospecifythelanguageformatusedforthe
conversion.ThisissetregardlessofthedefaultSQLServercollation.Ifnocultureis
specified,thecommandusesthedefaultlanguageontheserver.Listing18-5showsafew
examples.TheoutputisshowninFigure18-1.
Listing18-5.ExamplesofTRY_PARSE
DECLARE@fauxdateASvarchar(10)
DECLARE@realdateASVARCHAR(10)
SET@fauxdate='iamnotadate'
SET@realdate='01/05/2012'
SELECTTRY_PARSE(@fauxdateASDATE);
SELECTTRY_PARSE(@realdateASDATE);
SELECTTRY_PARSE(@realdateASDATEUSING'Fr-FR');
SELECTIIF(TRY_PARSE(@realdateASDATE)ISNULL,'False',
'True')
Figure18-1.OutputoftheTRY_PARSEfunction
Thefirstqueryattemptstoconvertanon-datestringtoadateandfailsbyreturning
NULL.Thesecondquerysucceedsandreturnsthedate2012-05-01.Thethirdquery
returnsthesamedatebutconvertsittotheFrenchdateformat.Thefinalqueryshowshow
youcanuseconditionalprocessingtoreturnanyvalueyouwantbasedonwhetherthe
conversionsucceedsorfails.
ThenextfunctionisTRY_CONVERT.IthasthesamefunctionalityastheCONVERT
functionbutreturnsNULLinsteadofanerroriftheconversionfails.Youcanuse
TRY_CONVERTwhenyouwanttotestthepossibilityofconvertingonedatatypeto
anotherdatatype.Thesyntaxisasfollows:
TRY_CONVERT(data_type[(length)],expression[,style
])
data_typeisthedatatypeyouwanttoconverttheexpressioninto,andstyle
determinesformatting.Listing18-6showsseveralexamples,andFigure18-2showsthe
output.
Listing18-6.TRY_CONVERTExamples
DECLARE@sampletextASVARCHAR(10)
SET@sampletext='123456'
SELECTTRY_CONVERT(INT,@sampletext);
SELECTTRY_CONVERT(DATE,@sampletext);
SELECTIIF(TRY_CONVERT(binary,@sampletext)ISNULL,
'FALSE','TRUE');
Figure18-2.OutputofTRY_CONVERT
Thelistingsetsthevariabletoatextvalue,whichcaneasilybeconvertedtoan
integer.ThefirstTRY_CONVERTsuccessfullyperformstheconversion,butthesecond
failsbecausethetextvaluecan’timplicitlybeconvertedtoadate.Thefinalexample
showsthattheconversionsucceededwithareturnresultofTRUE.
Nowlet’slookatTRY_CAST.It’sthetechnicalequivalentofTRY_CONVERT,but
theformatisdifferent.ThesyntaxforTRY_CASTisthefollowing:
TRY_CAST(expressionASdata_type[(length)])
Listing18-7usesthesameexamplesasListing18-5butchangesthesyntaxtouse
TRY_CAST.TheoutputisthesameasinFigure18-2.
Listing18-7.ExamplesUsingTRY_CAST
DECLARE@sampletextASVARCHAR(10)
SET@sampletext='123456'
SELECTTRY_CAST(@sampletextASINT);
SELECTTRY_CAST(@sampletextASDATE);
SELECTIIF(TRY_CAST(@sampletextASBINARY)ISNULL,'FALSE',
'TRUE');
TipAlthoughthey’reuseful,keepinmindacoupleofthingsaboutTRY_PARSE,
TRY_CONVERT,andTRY_CAST.Parsingstringscanbeacostlyprocess,sousethe
functionssparingly.MicrosoftrecommendsusingTRY_PARSEonlyforconverting
stringstodateornumericvalues.Forallotherconversions,useCASTorCONVERT.Also
keepinmindthatTRY_CONVERTandTRY_CASTthrowerrorsforexplicitconversions
—theseconversionsaren’tpossible.Forachartofimplicitandexplicitconversions,see
BooksOnline(BOL)athttp://msdn.microsoft.com/en-
us/library/ms191530.aspx.
ThrowStatement
SQLServer2014introducedtheTHROWstatement.It’ssimilartowhatyoufindin
programminglanguageslikeC++andC#andcanbeusedinsteadofRAISERROR.A
primarybenefitofusingTHROWinsteadofRAISERRORisthatitdoesn’trequireanerror
messageIDtoexistinsys.messages.TheTHROWstatementcanoccureitherina
CATCHblockoroutsidetheTRY…CATCHstatements.Ifnoparametersaredefined,then
THROWmustbeintheCATCHblock.Listing18-8showsexamplesofboth.Itusesthe
sameINSERTstatementsasthepreviousexamples.
Listing18-8.ExamplesoftheTHROWStatement
--1.UsingTHROWwithoutparameters
BEGINTRY
INSERTINTOPerson.Address(AddressID)
VALUES(1);
ENDTRY
BEGINCATCH
PRINT'Thisisanerror';
THROW
ENDCATCH;
--2.UsingTHROWwithparameters
THROW52000,'Thisisalsoanerror',1
BEGINTRY
INSERTINTOPerson.Address(AddressID)
VALUES(1);
ENDTRY
BEGINCATCH
THROW
ENDCATCH
(0row(s)affected)
Thisisanerror
Msg544,Level16,State1,Line2
Cannotinsertexplicitvalueforidentitycolumnintable
'Address'when
IDENTITYINSERTissettoOFF.
MSG52000,Level16,State1,Line1
Thereareacoupleofthingstonotice:First,theonlyseveritylevelreturnedbyTHROW
is16.Thestatementdoesn’tallowforanyotherlevel,whichisanotherdifferencebetween
THROWandRAISERROR.AlsonoticethatanystatementpriortotheTHROWstatementin
theCATCHblockmustendinasemicolon.Thisisyetanotherreasontomakesureall
yourblockstatementsterminateinsemicolons.
Ifyou’reaccustomedtousingTHROWinotherprogramminglanguages,youshould
findthisahelpfuladditiontoSQLServer2014.
DebuggingTools
InprocedurallanguageslikeC#,debuggingcodeissomewhateasierthanindeclarative
languageslikeT-SQL.Inprocedurallanguages,youcaneasilyfollowtheflowofa
program,settingbreakpointsateachatomicstepofexecution.Indeclarativelanguages,
however,asinglestatementcanperformdozensorhundredsofstepsinthebackground,
mostofwhichyouprobablyaren’tevenawareofatexecutiontime.Thegoodnewsisthat
theSQLServerteamdidn’tleaveuswithouttoolstodebugandtroubleshootT-SQLcode.
TheunpretentiousPRINTstatementprovidesaverysimpleandeffectivemethodof
debugging.
PRINTStatementDebugging
ThePRINTstatementisasimpleandusefulserver-sidedebuggingtool.Simplyprinting
constantsandvariablevaluestostandardoutputduringscriptorSPexecutionoften
providesenoughinformationtoquicklylocateproblemcode.PRINTworksfromwithin
SPsandbatches,butitdoesn’tworkinUDFsbecauseofthebuilt-inrestrictionson
functionscausingsideeffects.ConsidertheexamplecodeinListing18-9,whichtriesto
achieveanendresultwhere@iisequalto10.Theendresultofthecodeisnot@>i=
10,sothelistinghasacoupleofPRINTstatementstouncoverthereason.
Listing18-9.DebuggingScriptwithPRINT
DECLARE@iint;
PRINTN'Initialvalueof@i='+COALESCE(CAST(@iAS
nvarchar(10)),N'NULL');
SET@i+=10;
PRINTN'Finalvalueof@i='+COALESCE(CAST(@iAS
nvarchar(10)),N'NULL');
Theresult,showninFigure18-3,indicatesthatthedesiredendresultisn’toccurring
becauseIfailedtoinitializethevariable@ito0atthebeginningofthescript.Theinitial
valueof@>iisNULL,sotheendresultisNULL.Onceyou’veidentifiedtheissue,fixing
itisarelativelysimplematterinthiscase.
Figure18-3.ResultsofPRINTstatementdebugging
InadditiontothePRINTstatement,youcanusetheRAISERRORstatementwitha
NOWAITclausetosendamessageorstatusindicationimmediatelytotheclient.Whereas
PRINTwaitsforthebuffertoflush,RAISERRORwiththeNOWAITclausesendsthe
messageimmediately.
TraceFlags
SQLServer2014providesseveraltraceflagsthatcanhelpwithdebugging,particularly
whenyoususpectyouhaveaproblemwithSQLServersettings.Traceflagscanturnonor
offspecificSQLServerbehaviorortemporarilychangeotherservercharacteristicsfora
serverorsession.Asanexample,traceflag1204returnstheresourcesandtypesoflocks
participatinginadeadlock,andthecurrentcommandaffected.
TipManytraceflagsareundocumentedandmayonlyberevealedtoyoubyMicrosoft
ProductSupportServiceswhenyoureportaspecificissue;butthosethataredocumented
canprovideveryusefulinformation.BOLprovidesacompletelistofdocumentedSQL
Server2014traceflagsunder“TraceFlags.”
TurningonoroffatraceflagisassimpleasusingtheDBCCTRACEONandDBCC
TRACEOFFstatements,asshowninListing18-10.
Listing18-10.TurningTraceFlag1204OnandOff
DBCCTRACEON(1204,-l);
GO
DBCCTRACEOFF(1204,-l);
GO
Traceflagsmayreportinformationviastandardoutput,theSQLServerlog,or
additionallogfilescreatedforthatspecifictraceflag.CheckBOLforspecificinformation
aboutthemethodsthatspecifictraceflagsreportbacktoyou.
SSMSIntegratedDebugger
SQLServer2005didawaywiththeintegrateduserinterfacedebuggerinSSMS,although
itwaspreviouslyapartofQueryAnalyzer(QA).Apparently,thethoughtwasthatVisual
StudiowouldbethedebuggingtoolofchoiceforsteppingthroughT-SQLcodeand
settingbreakpointsinSPs.IntegratedSSMSdebuggingwasbroughtbackinSQLServer
2012andiscarriedforwardinSQLServer2014.TheSSMSmainmenucontainsseveral
debuggingactionsaccessiblethroughthenewDebugmenu,asshowninFigure18-4.
Figure18-4.TheSSMSDebugmenu
TheoptionsaresimilartothoseavailablewhendebuggingVisualStudioprojects.
Fromthismenu,youcanstartdebugging,stepinto/overyourcodeonestatementatatime,
andmanagebreakpoints.Figure18-5showsanSSMSdebuggingsessionthathasjusthita
breakpointinthebodyofaSP.
Figure18-5.SteppingintocodewiththeSSMSdebugger
TheSSMSdebuggerprovidesseveralwindowsthatprovideadditionaldebugging
information,includingtheCallStack,Breakpoints,Command,Output,Locals,andWatch
windows.
VisualStudioT-SQLDebugger
VisualStudio2013alsooffersanexcellentfacilityforsteppingthroughSPsandUDFs
justlikeanyVisualBasicorC#application.YoucanaccessVisualStudio’sT-SQL
debuggerthroughtheDebugmenuitem.PriortoSQLServer2014,thedebug
functionalitywasavailablebypointingatyourSQLServerinstanceandtheSPorfunction
youwishtodebugundertheappropriatedatabase.Thenyouwouldright-clickthe
procedureorfunctionandselectDebugProcedureorDebugFunctionfromthepop-up
contextmenu.Figure18-6demonstratesbyselectingDebugProcedureforthe
dbo.uspGetBillOfMaterialsSPintheAdventureWorks2012database.
Figure18-6.Debuggingthedbo.uspGetBillOfMaterialsprocedureinSQLServer2012
TipIt’smucheasiertoconfigureVisualStudioT-SQLdebuggingonalocallyinstalled
instanceofSQLServerthantosetupremotedebugging.BOLoffersinformationabout
settingupbothlocalandremoteSQLServerdebugging,inthearticle“DebuggingSQL”
(http://msdn.microsoft.com/en-us/library/cc646024.aspx).
SQLServer2014debugfunctionalityisnowonlyavailableviathetoolbarormenu
item.Theright-clickdebuggingfunctionalitywasremovedfromtheSQLServerObject
Explorer.Figure18-7demonstratesthelocationoftheDebugmenuandtoolbaritems.
Figure18-7.Debuggingthedbo.uspGetBillOfMaterialsprocedureinSQLServer2014
Ifyourfunctionorprocedurerequiresparameters,right-clicktheprocedureandselect
ExecuteStoredProcedure(seeFigure18-8).TheExecuteProcedurewindowopensand
asksyoutoentervaluesfortherequiredparameters(seeFigure18-9).Forthisexample,I
entered770forthe@StartProductIDparameterand7/10/2010forthe@CheckDate
parameterrequiredbythedbo.uspGetBillOfMaterialsprocedure.
Figure18-9.Enteringparametervalues
Afteryouentertheparameters,theprocedurebeginsrunning.Youmustchoosetorun
theprocedureinDebugmodetobeabletostepthroughthecode.VisualStudioshowsthe
scriptandhighlightseachlineinyellowasyoustepthroughit,asshowninFigure18-10.
Figure18-10.SteppingthroughanSPinDebugmode
InDebugmode,youcansetbreakpointsbyclickingtheleftborderandusingthe
Continue(F5),StopDebugging(Shift+F5),StepOver(F10),StepInto(F11),andStep
Out(Shift+F11)commands,justlikewhenyoudebugC#orVisualBasicprograms.You
canalsoaddwatchesandviewlocalstoinspectparameterandvariablevaluesasyour
codeexecutes.AnyresultsetsandreturnvaluesfromtheSPareshownintheVisual
StudioOutputwindow,asinFigure18-11.
Figure18-11.TheVisualStudioOutputwindow
DynamicSQL
SQLServerMVPErlandSommarskogsaiditbest:“dynamicSQLisacurseanda
blessing”.1Putsimply,dynamicSQLisameansofconstructingSQLstatementsas
stringsinyourserver-side(orevenclient-side)applicationsandexecutingthem
dynamicallyonthefly.Whenusedproperly,dynamicSQLcanbeusedtogenerate
complexqueriesatruntime,insomecasestoimproveperformance,andtodotasksthat
justaren’tpossible(orareextremelydifficult)instandard,nondynamicT-SQL.
Thedownsideisthattherearenumerouswaystoshootyourselfinthefootwith
dynamicSQL.Ifnotusedproperly,dynamicSQLcanopensecurityholesinyoursystem
thatarebigenoughtodriveatruckthrough.Thissectiondiscussesthevariousmethodsof
executingdynamicSQL,aswellassomeoftherisksandrewardsthatErlandalludesto.
TheEXECUTEStatement
Themostbasicformofserver-sidedynamicSQLisachievedbypassinganSQLqueryor
otherinstructionasastringtotheEXECUTEstatement(oftenabbreviatedEXEC).
EXECUTEacceptsachar,varchar,nchar,ornvarcharconstant,variable,or
expressionthatcontainsvalidT-SQLstatements.Listing18-11showsthemostbasicform
ofdynamicSQLwithanEXECUTEstatementandastringconstant.
Listing18-11.BasicEXECUTEStatement
EXECUTE(N'SELECTProductIDFROMProduction.Product');
Asyoucansee,thereisnorealadvantagetoperformingdynamicSQLonastring
constant.AsimpleSELECTstatementwithouttheEXECUTEwouldperformthesame
functionandreturnthesameresult.ThetruepowerofdynamicSQListhatyoucanbuild
anSQLstatementorquerydynamicallyandexecuteit.Listing18-12demonstrateshow
thiscanbedone.
Listing18-12.MoreComplexDynamicSQLExample
DECLARE@min_product_idint=500;
DECLARE@sql_stmtnvarchar(128)=
N'SELECTProductID'+
N'FROMProduction.Product'+
N'WHEREProductID>='+CAST(@min_product_idAS
nvarchar(10));
EXECUTE(@sql_stmt);
Nowthatyou’veseenthissimplecodesample,let’sexploreallthethingsthatare
wrongwithit.
SQLInjectionandDynamicSQL
InListing18-12,thevariable@sqlstmtcontainsthedynamicSQLquery.Thequeryis
builtdynamicallybyappendingtheminimumproductIDtotheWHEREclause.Thisisn’t
therecommendedmethodofperformingthistypeofquery,andit’sshownheretomakea
point.
Oneoftheproblemswiththismethodisthatyoulosesomeofthebenefitsofcached
query-planexecution.SQLServer2014hassomegreatfeaturesthatcanhelpinthisarea,
includingparametersniffingandtheabilitytoturnonforcedparameterization,butthere
aremanyexceptionstoSQLServer’sabilitytoautomaticallyparameterizequeriesor
clauses.Toguaranteeefficientreuseofcachedqueryexecutionplansasthetextofyour
querychanges,youshouldparameterizequeriesyourself.
ButthebigproblemhereisSQLinjection.Althoughnotreallyaproblemwhen
appendinganintegervaluetotheendofadynamicquery(asinListing18-12),SQL
injectioncanprovideabackdoorforhackerstryingtoaccessordestroyyourdatawhen
youconcatenatestringstocreatedynamicSQLqueries.Takealookattheinnocent-
lookingdynamicSQLqueryinListing18-13.Youseehowahackercouldwreakhavoc
withthisqueryafterthelisting.
Listing18-13.BasicDynamicSQLQuerywithaStringAppended
DECLARE@product_namenvarchar(50)=N'Mountain';
DECLARE@sql_stmtNVARCHAR(128)=N'SELECTProductID,Name'
+
N'FROMProduction.Product'+
N'WHERENameLIKE'''+
@product_name+N'%''';
EXECUTE(@sql_stmt);
ThisqueryreturnstheproductIDsandnamesofallproductsthatbeginwiththeword
Mountain.TheproblemiswithhowSQLServerinterpretstheconcatenatedstring.The
EXECUTEstatementseesthefollowingresultafterallthestringconcatenationsaredone:
SELECTProductID,Name
FROMProduction.Product
WHERENameLIKE'Mountain%'
Asimplesubstitutionfor@productnamecanexecuteotherunwantedstatementson
yourserver.Thisisespeciallytruewithdatacomingfromanexternalsource(forexample,
fromthefrontendorapplicationlayer).ConsiderthefollowingchangetoListing18-13:
DECLARE@product_namenvarchar(50)=
N''';DROPTABLEProduction.Product;--'
Asbefore,thestringconcatenationsresultinastatementtobeexecuted.However,this
timethestatementpassedtoEXECUTElooksasfollows:
SELECTProductID,Name
FROMProduction.Product
WHERENameLIKE'';
DROPTABLEProduction.Product;--%'
ThesimpledynamicSQLqueryisnowtwoqueries,thesecondofwhichdropsthe
Production.Producttablefromthedatabase!Nowconsiderifthevalueofthe
@productnamevariablehadbeenretrievedfromauserinterface,likeawebpage.A
malicioushackercouldeasilyissuearbitraryINSERT,UPDATE,DELETE,DROP
TABLE,TRUNCATETABLE,orotherstatementstodestroydataoropenabackdoorinto
yoursystem.Dependingonhowsecureyourserveris,hackersmaybeabletouseSQL
injectiontograntthemselvesadministratorrights,retrieveandmodifydatastoredinyour
server’sfilesystem,takecontrolofyourserver,oraccessnetworkresources.
Theonlyjustificationforusingthestring-concatenationmethodwithEXECUTEisif
youhavetodynamicallynamethetablesorcolumnsinyourstatements.Thisisfarrarer
thanmanypeoplethink.Infact,theonlytimethisisusuallynecessaryisifyouneedto
dynamicallygenerateSQLstatementsarounddatabase,table,orcolumnnames—ifyou’re
creatingadynamicpivottable-typequeryorcodinganadministrationtoolforSQLServer,
forinstance.
IfyoumustusestringconcatenationwiththeEXECUTEmethod,besuretotakethe
followingprecautionswiththestringsbeingpassedinfromtheuserinterface:
Don’tevertrustdatafromthefrontend.Alwaysvalidatethedata.If
you’reexpectingonlythelettersAthroughZandthenumbers0
through9,rejectallothercharactersintheinputdata.
Disallowapostrophes,semicolons,parentheses,anddoublehyphens
(—)intheinputifpossible.Thesecharactershavespecialsignificance
toSQLServerandshouldbeavoided.Ifyoumustallowthese
characters,scrutinizetheinputthoroughlybeforeusingthem.
Ifyouabsolutelymustallowapostrophesinyourdata,escapethem
(doublethem)beforeacceptingtheinput.
Rejectstringsthatcontainbinarydata,escapesequences,and
multilinecommentmarkers(/*and*/).
ValidateXMLinputdataagainstanXMLschemawhenpossible.
Takeextra-specialcarewheninputdatacontainsxp_orsp_,because
itmayindicateanattempttorunproceduresorXPsonyourserver.
TipIfyou’reconcatenatingone-parttableandobjectnamesintoSQLstatementson
theserverside,youcanusetheQUOTENAMEfunctiontosafelyquotethem.QUOTENAME
doesn’tworkfortwo-,three-,andfour-partnames,however.
Usually,datavalidationsliketheoneslistedpreviouslyareperformedontheclient
side,onthefrontend,intheapplicationlayer,orinthemiddletiersofmultitiersystems.
Inhighlysecureandcriticalapplications,itmaybeimportanttoalsoperformserver-side
validationsorsomecombinationofclient-andserver-sidevalidations.Triggersandcheck
constraintscanperformthistypeofvalidationondatabeforeit’sinsertedintoatable,and
youcancreateUDFsorSPstoperformvalidationsondynamicSQLbeforeexecutingit.
Listing18-14showsasimpleUDFthatusestheNumberstablecreatedinChapter4to
performbasicvalidationonastring,ensuringthatitcontainsonlythelettersAthroughZ,
thedigits0through9,andtheunderscorecharacter_,whichisacommonvalidationused
onusernames,passwords,andothersimpledata.
Listing18-14.SimpleT-SQLString-ValidationFunction
CREATEFUNCTIONdbo.ValidateString(@stringnvarchar(4000))
RETURNSint
AS
BEGIN
DECLARE@resultint=0;
WITHNumbers(Num)
AS
(
SELECT1
UNIONALL
SELECTNum+1
FROMNumbers
WHERENum<=LEN(@string)
)
SELECT@result=SUM
(
CASE
WHENSUBSTRING(@string,n.Num,1)LIKEN'[A-Z0-
9\_]'ESCAPE'\'
THEN0
ELSE1
END
)
FROMNumbersn
WHEREn.Num<=LEN(@string)
OPTION(MAXRECURSION0);
RETURN@result;
END
GO
ThefunctioninListing18-14usesacommontableexpression(CTE)tovalidateeach
characterinthegivenstring.Theresultisthetotalnumberofinvalidcharactersinthe
string:avalueof0indicatesthatallthecharactersinthestringarevalid.Morecomplex
validationscanbeperformedwiththeLIKEoperatororproceduralcodetoensurethat
dataisinaprescribedformataswell.
TroubleshootingDynamicSQL
AbigdisadvantageofusingdynamicSQLisindebuggingandtroubleshootingcode.
ComplexdynamicSQLqueriescanbedifficulttotroubleshoot,andverysimplesyntaxor
othererrorscanbehardtolocate.Fortunatelythereisafairlysimplefixforthat:write
yourtroublesomequerydirectlyinT-SQL,replacingparameterswithpotentialvalues.
Highlightthecode,andparse—orexecute—it.Anysyntaxerrorsaredetectedand
describedbySQLServerimmediately.Fixtheerrors,andrepeatuntilallerrorshavebeen
fixed.Thenandonlythenrevertthevaluesbacktotheirparameternamesandputthe
statementbackindynamicSQL.
AnotherhandymethodoftroubleshootingistoprintthedynamicSQLstatement
beforeexecutingit.Highlight,copy,andattempttoparseorrunitinSSMS.Youshouldbe
abletoquicklyandeasilylocateanyproblemsandfixthemasnecessary.
OneoftherestrictionsondynamicSQListhatitcan’tbeexecutedinaUDF.This
restrictionisinplacebecauseUDFscan’tproducesideeffectsthatchangethedatabase.
DynamicSQLoffersinfiniteopportunitiestocircumventthisrestriction,soit’ssimplynot
allowed.
Thesp_executesqlStoredProcedure
Thesp_executesqlSPprovidesasecondmethodofexecutingdynamicSQL.When
usedcorrectly,it’ssaferthantheEXECUTEmethodforconcatenatingstringsand
executingthem.LikeEXECUTE,sp_executesqltakesastringconstantorvariableas
aSQLstatementtoexecute.UnlikeEXECUTE,theSQLstatementparametermustbean
ncharornvarchar.
Thesp_executesqlprocedureoffersadistinctadvantageovertheEXECUTE
method:youcanspecifyyourparametersseparatelyfromtheSQLstatement.Whenyou
specifytheparametersseparatelyinsteadofconcatenatingthemintoonelargestring,SQL
Serverpassestheparameterstosp_executesqlseparately.SQLServerthen
substitutesthevaluesoftheparametersintheparameterizedSQLstatement.Becausethe
parametervaluesaren’tconcatenatedintotheSQLstatement,sp_executesqlprotects
againstSQLinjectionattacks.sp_executesqlparameterizationalsoimprovesreuseof
thequeryexecutionplancache,whichhelpswithperformance.
Alimitationofthisapproachisthatyoucan’tusetheparametersinyourSQL
statementinplaceoftable,column,orotherobjectnames.Listing18-15showshowto
parameterizethepreviousexample.
Listing18-15.DynamicSQLsp_executesqlParameterized
DECLARE@product_nameNVARCHAR(50)=N'Mountain%';
DECLARE@sql_stmtNVARCHAR(128)=N'SELECTProductID,Name'
+
N'FROMProduction.Product'+
N'WHERENameLIKE@name';
EXECUTEsp_executesql@sql_stmt,
N'@nameNVARCHAR(50)',
@name=@product_name;
TipIt’sstronglyrecommendedthatyouuseparameterizedquerieswheneverpossible
whenusingdynamicSQL.Ifyoucan’tparameterize(forexample,youneedto
dynamicallychangethetablenameinaquery),besuretothoroughlyvalidatethe
incomingdata.
DynamicSQLandScope
DynamicSQLexecutesinitsownbatch.Thismeansvariablesandtemporarytables
createdinadynamicSQLstatementorstatementbatcharen’tdirectlyavailabletothe
callingroutine.ConsidertheexampleinListing18-16.
Listing18-16.LimitedScopeofDynamicSQL
DECLARE@sql_stmtNVARCHAR(512)=N'CREATETABLE
#Temp_ProductIDs'+
N'('+
N'ProductIDintNOTNULLPRIMARYKEY'+
N');'+
N'INSERTINTO#Temp_ProductIDs(ProductID)'+
N'SELECTProductID'+
N'FROMProduction.Product;';
EXECUTE(@sql_stmt);
SELECTProductID
FROM#Temp_ProductIDs;
The#Temp_ProductIDstemporarytableiscreatedinadynamicSQLbatch,soit
isn’tavailableoutsideofthebatch.Thiscausesthefollowingerrormessagetobe
generated:
(504row(s)affected)
Msg208,Level16,State0,Line9
Invalidobjectname'#Temp_ProductIDs'.
Themessage(504row(s)affected)indicatesthatthetemporary-table
creationandINSERTINTOstatementofthedynamicSQLexecutedproperlyand
withouterror.TheproblemiswiththeSELECTstatementafterEXECUTE.Becausethe
#Temp_ProductIDstablewascreatedinthescopeofthedynamicSQLstatement,the
temporarytableisdroppedimmediatelywhenthedynamicSQLstatementcompletes.This
meansthatonceSQLServerreachestheSELECTstatement,the#Temp_ProductIDs
tablenolongerexists.Onewaytoworkaroundthisissueistocreatethetemporarytable
beforethedynamicSQLexecutes.ThedynamicSQLisabletoaccessandupdatethe
temporarytablecreatedbythecaller,asshowninListing18-17.
Listing18-17.CreatingaTempTableAccessibletoDynamicSQL
CREATETABLE#Temp_ProductIDs
(
ProductIDintNOTNULLPRIMARYKEY
);
DECLARE@sql_stmtNVARCHAR(512)=N'INSERTINTO
#Temp_ProductIDs(ProductID)'+
N'SELECTProductID'+
N'FROMProduction.Product;';
EXECUTE(@sql_stmt);
SELECTProductID
FROM#Temp_ProductIDs;
Tablevariablesandothervariablesdeclaredbythecalleraren’taccessibletodynamic
SQL,however.Variablesandtablevariableshavewell-definedscope:they’reonly
availabletothebatch,function,orprocedureinwhichthey’recreated,nottodynamic
SQLorothercalledroutines.
Client-SideParameterization
ParameterizationofdynamicSQLqueriesisn’tjustagoodideaontheserverside;it’salso
agreatideatoparameterizequeriesinsteadofbuildingdynamicSQLstringsontheclient
side.Inadditiontothesecurityimplications,queryparameterizationprovidesreuseof
cachedqueryexecutionplans,makingqueriesmoreefficientthantheirconcatenatedstring
counterparts.Microsoft.NETlanguagesprovidethetoolsnecessarytoparameterize
queriesfromtheapplicationlayerintheSystem.Data.SqlClientand
System.Datanamespaces.Chapter16discussedparameterizationontheclientside.
Summary
SQLServerhaslongsupportedsimpleerrorhandlingusingthe@@errorsystemfunction
toretrieveerrorinformationandtheRAISERRORstatementtothrowexceptions.SQL
Server2014continuestosupportthesemethodsofhandlingerrors,butitalsoprovides
modern,structuredTRY…CATCHandTHROWexceptionhandlingsimilartoothermodern
languages.T-SQLTRY…CATCHexceptionhandlingincludesseveralfunctionsthatexpose
error-specificinformationintheCATCHblock.SQLServer2012introducedamore
streamlinederror-handlingapproachtocommonprogrammingscenariosbyintroducing
TRY_PARSE,TRY_CONVERT,andTRY_CASTfunctions.
InadditiontotheSSMSintegrateddebugger,whichcanbeaccessedthroughthe
Debugmenu,SQLServerandVisualStudioprovidetoolsthatareusefulfor
troubleshootinganddebuggingyourT-SQLcode.Theseincludesimpletoolslikethe
PRINTstatementandtraceflags,andevenmorepowerfultoolslikeVisualStudio
debugging,whichletsyousetbreakpoints,stepintocode,andusemuchofthesame
functionalitythatisusefulwhendebuggingC#andVisualBasicprograms.
ThischapteralsodiscusseddynamicSQL,atoolthatisveryusefulandpowerfulinits
ownrightbutisoftenincorrectlyused.MisuseofdynamicSQLcanexposeyour
databases,servers,andothernetworkresources,leavingyourITinfrastructurevulnerable
toSQLinjectionattacks.ImproperuseofdynamicSQLcanalsoimpactapplication
performance.SQLinjectionandqueryperformancearethetwomostcompellingreasons
totakeextraprecautionswhenusingdynamicSQL.
ThenextchaptergivesanoverviewofSQLServer2014queryperformancetuning.
EXERCISES
1. [Fillintheblank]The___________systemfunctionautomatically
resetsto0aftereverysuccessfulstatementexecution.
2. [Chooseone]Whichofthefollowingfunctions,availableonlyin
theCATCHblockinSQLServer,returnstheseveritylevelofthe
errorthatoccurred?
a. ERR_LEVEL()
b. EXCEPTION_SEVERITY()
c. EXCEPTION_LEVEL()
d. ERROR_SEVERITY()
3. [True/False]TheRAISERRORstatementallowsyoutoraiseerrors
inSQLServer.
4. [True/False]VisualStudioprovidesintegrateddebugging,which
allowsyoutostepintoT-SQLfunctionsandSPsandset
breakpoints.
5. [Chooseallthatapply]ThepotentialproblemswithdynamicSQL
includewhichofthefollowing?
a. Potentialperformanceissues
b. SQLinjectionattacks
c. Generalexceptionerrorscausedbyinterferencewith
graphicsdrivers
d. Alloftheabove
______________________
1TheCurseandBlessingsofDynamicSQLbyErlandSommarskog;
CHAPTER19
PerformanceTuning
Inmostproductionenvironments,databaseandserveroptimizationhavelongbeenthe
domainofDBAs.Thisincludesserversettings,hardwareoptimizations,indexcreation
andmaintenance,andmanyotherresponsibilities.SQLdevelopers,however,are
responsibleforensuringthattheirqueriesperformoptimally.SQLServeristrulya
developer’sDBMS,andasaresultthedeveloperresponsibilitiescanoverlapwiththoseof
theDBA.Thisoverlapincludesrecommendingdatabasedesignandindexingstrategies,
troubleshootingpoorlyperformingqueries,andmakingotherperformance-enhancement
recommendations.Thischapterdiscussesvarioustoolsandstrategiesforquery
optimizationandperformanceenhancementandtuningqueries.
SQLServerStorage
SQLServerisdesignedtoabstractawaymanyofthelogicalandphysicalaspectsof
storageanddataretrieval.Inaperfectworld,youwouldn’thavetoworryaboutsuch
things—youwouldbeabletojust“setitandforgetit.”Unfortunately,theworldisn’t
perfect,andhowSQLServerstoresdatacanhaveanoticeableimpactonquery
performance.UnderstandingSQLServerstoragemechanismsisessentialtoproperly
troubleshootingperformanceissues.Withthatinmind,thissectionoffersabriefoverview
ofhowSQLServerstoresyourdata.
TipThissectiongivesonlyasummarizeddescriptionofhowSQLServerstoresdata.
ThebestdetaileddescriptionoftheSQLServerstorageengineinternalsisinthebook
InsideMicrosoftSQLServer2012Internals,byKalenDelaneyetal.(MicrosoftPress,
2012).
FilesandFilegroups
SQLServerstoresdatabasesinfiles.Eachdatabaseconsistsofatleasttwofiles:a
databasefilewithan.mdfextensionandalogfilewithan.ldfextension.Youcanalso
addadditionalfilestoaSQLServerdatabase,normallywithan.ndfextension.
Filegroupsarelogicalgroupingsoffilesforadministrationandallocationpurposes.By
default,SQLServercreatesalldatabasefilesinasingleprimaryfilegroup.Youcanadd
filegroupstoanexistingdatabaseorspecifyadditionalfilegroupsatcreationtime.When
creatingin-memoryoptimizedtables,you’rerequiredtocreateanewfilegroupwiththe
CONTAINSMEMORY_OPTIMIZED_DATAsyntax.Chapter6coversin-memory
optimizedtablesandprovidesamoredetaileddiscussionoftherequirementsforthenew
filegrouptype.Therearesignificantperformancebenefitstousingmultiplefilegroups,
whichcomefromplacingthedifferentfilegroupsondifferentphysicaldrives.It’s
commonpracticetoincreaseperformancebyplacingdatafilesinaseparatefilegroupand
physicaldrivefromnonclusteredindexes.It’salsocommontoplacelogfilesonaseparate
physicaldrivefrombothdataandnonclusteredindexes.
Understandinghowphysicalseparationoffilesimprovesperformancerequiresan
explanationoftheread/writepatternsinvolvedwitheachtypeofinformationthatSQL
Serverstores.Databasedatagenerallyusesarandom-accessread/writepattern.Thehard
driveheadconstantlyrepositionsitselftoreadandwriteuserdatatothedatabase.
Nonclusteredindexesarealsousuallyrandom-accessinnature;theharddrivehead
repositionsitselftotraversethenonclusteredindex.Oncenodesthatmatchthequery
criteriaarefoundinthenonclusteredindex,ifcolumnsmustbeaccessedthataren’tinthe
nonclusteredindex,theharddrivemustagainrepositionitselftolocatetheactualdata
storedinthedatafile.Thetransactionlogfilehasacompletelydifferentaccesspattern
thaneitherdataornonclusteredindexes:SQLServerwritestothetransactionlogina
serialfashion.Theseconflictingaccesspatternscanresultinheadthrashing,orconstant
repositioningoftheharddriveheadtoreadandwritethesedifferenttypesofinformation.
Dividingyourfilesbytypeandplacingthemonseparatephysicaldriveshelpsimprove
performancebyreducingheadthrashingandallowingSQLServertoperformI/O
activitiesinparallel.
Youcanalsoplacemultipledatafilesinasinglefilegroup.Whenyoucreatea
databasewithmultiplefilesinasinglefilegroup,SQLServerusesaproportionalfill
strategyacrossthefilesasdataisaddedtothedatabase.ThismeansSQLServertriesto
fillallfilesinafilegroupatapproximatelythesametime.Logfiles,whicharen’tpartofa
filegroup,arefilledusingaserialstrategy.Ifyouaddadditionallogfilestoadatabase,
theywon’tbeuseduntilthecurrentlogfileisfilled.
TipYoucanmoveatablefromitscurrentfilegrouptoanewfilegroupbydroppingthe
currentclusteredindexonthetableandcreatinganewclusteredindex,specifyingthenew
filegroupintheCREATECLUSTEREDINDEXstatement.
SpaceAllocation
Whenreadingdata,SQLServerusesarandom-accessfiletolocatethedatathatresidesin
aspecificlocationratherthanreadingthedatafromthebeginning.Toenabletherandom-
accessfile,thesystemshouldhaveconsistentlysizedallocationunitsinthefilestructure.
SQLServerallocatesspaceinthedatabaseinunitscalledextentsandpagestoaccomplish
this.Apageisan8KBblockofcontiguousstorage.Anextentconsistsofeightlogically
contiguouspages,or64KBofstorage.SQLServerhastwotypesofextents:uniform
extents,whichareownedcompletelybyasingledatabaseobject,andmixedextents,which
canbesharedbyuptoeightdifferentdatabaseobjects.Whenanewtableorindexis
created,thepagesareallocatedfrommixedextents.Whenthetableorindexgrows
beyondeightpages,thentheallocationsaredoneinuniformextentstomakethespace
allocationefficient.
Thisphysicallimitationonthesizeofpagesisthereasonforthehistoriclimitationson
datatypessuchasvarcharandnvarchar(upto8,000and4,000characters,
respectively)androwsize(8,060bytes).It’salsowhyspecialhandlingisrequired
internallyforLOBdatatypessuchasvarchar(max),varbinary(max),andxml,
becausethedatatheycontaincanspanmanypages.
SQLServerkeepstrackofallocatedextentswithwhataretermedallocationmaps:
globalallocationmap(GAM)pagesandsharedglobalallocationmap(SGAM)pages.
GAMpagesusebitstotrackallextentsthathavebeenallocated.SGAMpagesusebitsto
trackmixedextentswithoneormorefreepagesavailable.Indexallocationmap(IAM)
pagestrackalltheextentsusedbyanindexortable,andthey’reusedtonavigatethrough
datapages.Pagefreespace(PFS)pagestrackthefreespaceoneachpagethatstoresLOB
values.ThecombinationofGAMandSGAMpagesallowsSQLServertoquicklyallocate
freeextents,uniform/fullmixedextents,andmixedextentswithfreepagesasnecessary,
whereasIAMandPFSareusedtodecidewhenanobjectneedsextentallocation.
ThebehavioroftheSQLServerstorageenginecanhaveadirectbearingon
performance.Forinstance,considerthecodeinListing19-1,whichcreatesatablewith
narrowrows.NotethatSQLServercanoptimizestorageforvariable-lengthdatatypes
likevarcharandnvarchar,sothisexampleforcestheissuebyusingfixed-length
chardatatypes.
Listing19-1.CreatingaNarrowTable
CREATETABLEdbo.SmallRows
(
IdintNOTNULL,
LastNamenchar(50)NOTNULL,
FirstNamenchar(50)NOTNULL,
MiddleNamenchar(50)NULL
);
INSERTINTOdbo.SmallRows
(
Id,
LastName,
FirstName,
MiddleName
)
SELECT
BusinessEntityID,
LastName,
FirstName,
MiddleName
FROMPerson.Person;
Therowsinthedbo.SmallRowstableare304byteswide.ThismeansSQLServer
canfitabout25rowsonasingle8KBpage.Youcanverifythiswiththeundocumented
sys.fn_PhysLocFormatterfunction,asshowninListing19-2.Partialresultsare
showninFigure19-1.Thesys.fn_PhysLocFormatterfunctionreturnsthephysical
locatorintheform(fileipage:slot).Asyoucanseeinthefigure,SQLServerfits
25rowsoneachpage(rowsarenumbered0to24).
NoteThesys.fn_PhysLocFormatterfunctionisundocumentedandnot
supportedbyMicrosoft.It’susedherefordemonstrationpurposes,becauseit’shandyfor
lookingatrowallocationsonpages;butdon’tuseitinproductioncode.
Listing19-2.LookingatDataAllocationsfortheSmallRowsTable
SELECT
sys.fn_PhysLocFormatter(%%physloc%%)AS[Row_Locator],
Id
FROMdbo.SmallRows;
Figure19-1.SQLServerfits25rowsperpageforthedbo.SmallRowstable
Bywayofcomparison,thecodeinListing19-3createsatablewithwiderows—3,604
byteswide,tobeexact.ThefinalSELECTqueryretrievestherow-locatorinformation,
demonstratingthatSQLServercanfitonlytworowsperpageforthedbo.LargeRows
table.TheresultsareshowninFigure19-2.
Listing19-3.CreatingaTablewithWideRows
CREATETABLEdbo.LargeRows
(
IdintNOTNULL,
LastNamenchar(600)NOTNULL,
FirstNamenchar(600)NOTNULL,
MiddleNamenchar(600)NULL
);
INSERTINTOdbo.LargeRows
(
Id,
LastName,
FirstName,
MiddleName
)
SELECT
BusinessEntityID,
LastName,
FirstName,
MiddleName
FROMPerson.Person;
SELECT
sys.fn_PhysLocFormatter(%%physloc%%)AS[Row_Locator],
Id
FROMdbo.LargeRows;
Figure19-2.SQLServerfitsonlytworowsperpageforthedbo.LargeRowstable
Nowthatyou’vecreatedtwotableswithdifferentrowwidths,thequeryinListing19-
4queriesbothtableswithSTATISTICSIOturnedontodemonstratethedifferencethis
makestoyourI/O.
Listing19-4.I/OComparisonofNarrowandWideTables
SETSTATISTICSIOON;
SELECT
Id,
LastName,
FirstName,
MiddleName
FROMdbo.SmallRows;
SELECT
Id,
LastName,
FirstName,
MiddleName
FROMdbo.LargeRows;
Theresultsreturned,shownnext,demonstrateasignificantdifferenceinbothlogical
readsandread-aheadreads:
(19972row(s)affected)
Table'SmallRows'.Scancount1,logicalreads799,physical
reads0,read-aheadreads8,
loblogicalreads0,lobphysicalreads0,lobread-ahead
reads0.
(19972row(s)affected)
Table'LargeRows'.Scancount1,logicalreads9986,
physicalreads0,read-aheadreads
10002,loblogicalreads0,lobphysicalreads0,lobread-
aheadreads0.
TheextraI/Osincurredbythequeryonthedbo.LargeRowstablesignificantly
affectthequeryplan’sestimatedI/Ocost.Thequeryplanforthedbo.SmallRows
queryisshowninFigure19-3,withanestimatedI/Ocostof0.594315.
Figure19-3.EstimatedI/Ocostforthedbo.SmallRowsquery
Thequeryagainstthedbo.LargeRowstableissignificantlycostlier,withan
estimatedI/Ocostof7.39942—nearly12.5timesgreaterthanthedbo.SmallRows
query.Figure19-4showsthehighercostforthedbo.LargeRowsquery.
Figure19-4.EstimatedI/Ocostforthedbo.LargeRowsquery
Asyoucanseefromthesesimpleexamples,SQLServerhastoreadsignificantlymore
pageswhenatableisdefinedwithwiderows.ThisincreasedI/Ocostcancausea
significantperformancedrainwhenperformingSQLServerqueries—eventhosequeries
thatareotherwisehighlyoptimized.YoucanminimizethecostofI/Obyminimizingthe
widthofcolumnswherepossibleandalwaysusingtheappropriatedatatypeforthejob.In
theexamplesgiven,avariable-widthcharacterdatatype(varchar)wouldsignificantly
reducethestoragerequirementsofthesampletables.AlthoughI/Ocostisoftena
secondaryconsiderationfordevelopersandDBAs,andfrequentlyisaddressedonlyafter
slowqueriesbegintocausedragonasystem,it’sagoodideatokeepthecostofI/Oin
mindwheninitiallydesigningyourtables.
Partitions
PartitioningthetablesandindexesbyrangewasintroducedinSQLServer2005.This
functionalityallowsthedatatobepartitionedintorowsetsbasedonthepartitioning
columnvalueandthepartitionscanbeplacedintoonemorefilegroupsinthedatabaseto
improvetheperformanceofthequeryandmanageabilitywhiletreatingthemasasingle
object.
Partitioningisdefinedbyapartitionschemethatmapsthepartitionsdefinedbythe
partitionfunctiontoasetoffilesorfilegroupsthatyoudefine.Apartitionfunction
specifieshowtheindexorthetableispartitioned.Thecolumnvalueusedtodefinethe
partitioncanbeofanydatatypeexceptLOBdataortimestamp.SQLServer2008
supports1,000partitionsbydefault,whichmeetsmostapplicationneeds;however,in
somecases,duetoindustryregulations,youneedtoretainthedailydataformorethan3
years.Inthosecases,youneedthedatabasetosupportmorethan1,000partitions.SQL
Server2008R2introducedsupportfor15,000partitions,butyouneedtorunastored
proceduretoenablethissupport.SQLServer2014providessupportfor15,000partitions
bydefaultandalsoprovidesnativesupportforhigh-availabilityanddisaster-recovery
featuressuchasAlwaysOn,replication,databasemirroring,andlogshipping.
Partitioningisusefulforgroupingdatafromalargetableintosmallerchunkssothat
thedatacanbemaintainedindependentlyfordatabaseoperationssuchasspeedingup
queries(primarilywithscans),loadingdata,reindexing,andsoon.Partitioningcan
improvequeryperformancewhenthepartitioningkeyispartofthequeryandthesystem
hasenoughprocessorstoprocessthequery.Notalltablesneedtobepartitioned;you
shouldconsidercharacteristicssuchashowlargethetableis,howit’sbeingaccessed,and
queryperformanceagainstthetablesbeforeconsideringwhethertopartitionthedata.
Thefirststepinpartitioningatableistodeterminehowtherowsinthetablewillbe
dividedbetweenthepartitions,usingapartitionfunction.Toeffectivelydesignapartition
function,youneedtospecifylogicalboundaries.Ifyouspecifytwoboundaries,thenthree
partitionsarecreated;and,dependingonwhetherthedataisbeingpartitionedleftorright,
theupperorlowerboundaryconditionisset.
Thepartitionfunctiondefineslogicalboundaries,andthepartitionschemedefinesthe
physicallocation(filegroups)forthem.Oncethepartitionfunctionisdefinedtosetthe
logicalboundaryandthepartitionschemeisdefinedtomapthelogicalboundaryto
filegroups,youcancreatethepartitionedtable.
Likethetable,youcanpartitionindexes.Topartitionaclusteredindex,thepartition
keymustbespecifiedintheclusteredindex.Partitioninganonclusteredindexdoesn’t
requirethepartitionkey;ifthepartitionkeyisn’tspecified,thenSQLServerincludesthe
partitioncolumnsintheindex.Indexesthataredefinedwithpartitionedtablescanbe
alignedornonaligned;anindexisalignedifthetableandtheindexlogicallyhavethe
samepartitionstrategy.
Ingeneral,partitioningismostusefulwhendatahasatimecomponent.Largetables
suchasorderdetails—wheremostoftheDMLoperationsareperformedonthecurrent
month’sdataandpreviousmonthsaresimplyusedforselects—maybegoodcandidatesto
partitionbymonth.Thisenablesthequeriestomodifythedatafoundinasinglepartition
ratherthanscanningthoughtheentiretabletolocatethedatatobemodified,hence
enhancingqueryperformance.
Partitionscanbesplitormergedeasilyinasliding-windowscenario.Youcansplitor
mergepartitionsonlyifalltheindexesarealignedandthepartitionschemeandfunctions
match.Partitionalignmentdoesn’tmeanbothobjectshavetousethesamepartition
function;butifbothobjectshavethesamepartitionscheme,functions,andboundaries,
they’reconsideredtobealigned.Whenbothobjectshavethesamepartitioningschemeor
filegroups,they’restoragealigned.Storagealignmentcanbephysicalorlogical;inboth
cases,queryperformanceisimproved.
DataCompression
Inadditiontominimizingthewidthofcolumnsbyusingtheappropriatedatatypeforthe
job,SQLServer2014providesbuilt-indata-compressionfunctionality.Bycompressing
yourdatadirectlyinthedatabase,SQLServercanreduceI/Ocontentionandminimize
storagerequirements.ThereissomeCPUoverheadassociatedwithcompressionand
decompressionofdataduringqueriesandDMLactivities,butdatacompressionis
particularlyusefulforhistoricaldatastoragewhereaccessandmanipulationdemands
aren’tashighastheymightbeforthemostrecentdata.Thissectiondiscussesthetypesof
compressionthatSQLServersupportsaswellastheassociatedoverheadand
recommendedusageofeach.
RowCompression
SQLServer2005introducedanoptimizationtothestorageformatforthedecimaldata
typeinSP2.Thevardecimaltypeprovidesoptimizedvariable-lengthstoragefor
decimaldata,whichoftenresultsinsignificantspacesavings—particularlywhen
decimalcolumnscontainalotofzeros.Thisoptimizationisinternaltothestorage
engine,soit’scompletelytransparenttodevelopersandendusers.InSQLServer2008,
thisoptimizationwasexpandedtoincludeallfixed-lengthnumeric,date/time,and
characterdatatypes,inafeatureknownasrowcompression.
NoteThevardecimalcompressionoptionsandSPstomanagethisfeature,
includingsp_db_vardecimal_storage_formatand
sp_estimated_rowsize_reduction_for_vardecimal,aredeprecated,
becauseSQLServer2014rollsthisfunctionalityintothenewrow-compressionfeature.
SQLServer2014providestheuseful
sp_estimate_data_compression_savingsproceduretoestimatethesavings
yougetfromapplyingcompressiontoatable.Listing19-5estimatesthespacesavedby
applyingrowcompressiontotheProduction.TransactionHistorytable.This
particulartablecontainsfixed-lengthint,datetime,andmoneycolumns.Theresults
areshowninFigure19-5.
Listing19-5.EstimatingRow-CompressionSpaceSavings
EXECsp_estimate_data_compression_savings'Production',
'TransactionHistory',
NULL,
NULL,
'ROW';
Figure19-5.Rowcompressionspacesavingsestimateforatable
NoteWechangedthenamesofthelastfourcolumnsinthisexamplesotheywouldfit
intheimage.Theabbreviationsaresize_cur_cmpforSizewithcurrent
compressionsetting(KB),size_req_cmpforSizewithrequested
compressionsetting(KB),size_sample_cur_cmpforSamplesize
withcurrentcompressionsetting(KB),andsize_sample_req_cmp
forSamplesizewithrequestedcompressionsetting(KB).
TheresultsshowninFigure19-5indicatethatthecurrentsizeoftheclusteredindex
(index_id=1)isabout6.1MB,whereasthetwononclusteredindexes(index_id=1
and2)totalabout2.9MB.SQLServerestimatesthatitcancompressthistabledowntoa
sizeofabout4.0MBfortheclusteredindexand2.6MBforthenonclusteredindexes.
TipIfyourtabledoesn’thaveaclusteredindex,theheapisindicatedintheresultswith
anindex_idof0.
YoucanturnonrowcompressionforatablewiththeDATACOMPRESSION=ROW
optionoftheCREATETABLEandALTERTABLEDDLstatements.Listing19-6turns
onrowcompressionfortheProduction.TransactionHistorytable.
Listing19-6.TurningonRowCompressionforaTable
ALTERTABLEProduction.TransactionHistoryREBUILD
WITH(DATA_COMPRESSION=ROW);
YoucanverifythattheALTERTABLEstatementhasappliedrowcompressionto
yourtablewiththesp_spaceusedprocedure,asshowninListing19-7.Theresultsare
showninFigure19-6.
Listing19-7.ViewingSpaceUsedbyaTableafterApplyingRowCompression
EXECsp_spaceusedN'Production.TransactionHistory';
Figure19-6.Spaceusedbythetableafterapplyingrowcompression
Asyoucanseeinthefigure,thesizeofthedatausedbythe
Production.TransactionHistorytablehasdroppedtoabout4.0MB.The
indexesaren’tautomaticallycompressedbytheALTERTABLEstatement.Tocompress
thenonclusteredindexes,youneedtoissueALTERINDEXstatementswiththe
DATA_COMPRESSION=ROWoption.YoucanusetheDATA_COMPRESSION=
NONEoptiontoturnoffrowcompressionforatableorindex.
Rowcompressionusesvariable-lengthformatstostorefixed-lengthdata,andSQL
Serverstoresanoffsetvalueineachrecordforeachvariable-lengthvalueitstores.Priorto
SQLServer2008,thisoffsetvaluewasfixedat2bytesofoverheadpervariable-length
value.SQLServer2008introducedanewrecordformatthatusesa4-bitoffsetfor
variable-lengthcolumnsthatare8bytesinlengthorless.
PageCompression
SQLServer2014alsohasthecapabilitytocompressdataatthepagelevelusingtwo
methods:column-prefixcompressionandpage-dictionarycompression.Whereasrow
compressionisgoodforminimizingthestoragerequirementsforhighlyuniquefixed-
lengthdataattherowlevel,pagecompressionhelpsminimizethestoragespacerequired
byduplicateddatastoredinpages.
Thecolumn-prefixcompressionmethodlooksforrepeatedprefixesincolumnsofdata
storedonapage.Figure19-7showsasamplepagefromatable,withrepeatedprefixesin
columnsunderlined.
Figure19-7.Pagewithrepeatedcolumnprefixesidentified
TocompressthecolumnprefixesidentifiedinFigure19-7,SQLServercreatesan
anchorrecord.Thisisarowinthetablejustlikeanyotherrow,exceptthatitservesthe
specialpurposeofstoringthelongestvalueinthecolumncontainingaduplicatedcolumn
prefix.Theanchorrecordislaterusedbythestorageenginetore-createthefull
representationsofthecompressedcolumnvalueswhenthey’reaccessed.Thisspecialtype
ofrecordisaccessibleonlyinternallybythestorageengineandcan’tberetrievedor
modifieddirectlybynormalqueriesorDMLoperations.Figure19-8showsthecolumn
prefix–compressedversionofthepagefromFigure19-7.
Figure19-8.Pagewithcolumn-prefixcompressionapplied
Thereareseveralitemsofnoteinthecolumnprefix–compressedpageshowninFigure
19-8.First,theanchorrecordhasbeenaddedtothepage.Column-prefixcompression
usesbytepatternstoindicateprefixes,makingthecolumn-prefixmethoddata-type
agnostic.Inthisinstance,theBusinessEntityIDcolumnisanintdatatype;butas
youcansee,ittakesadvantageofdata-typecompressionaswell.The
BusinessEntityIDcolumnvaluesareshowninbothintandvarbinaryformats
todemonstratethatthey’recompressedaswell.
Thenextinterestingfeatureofcolumn-prefixcompressionisthatSQLServerreplaces
theprefixofeachcolumnwithanindicatorofhowmanybytesneedtobeprependedfrom
theanchor-recordvaluetore-createtheoriginalvalue.NULLisusedtoindicatethatthe
valueinthetableisthefullanchor-recordvalue.
NoteThestorageengineusesmetadataassociatedwitheachvaluetoindicatethe
differencebetweenanactualNULLinthecolumnandaNULLindicatingaplaceholderfor
theanchor-recordvalue.
Intheexample,eachcolumninthefirstrowisreplacedwithNULLsthatactas
placeholdersforthefullanchor-recordvalues.Thesecondrow’sBusinessEntityID
columnindicatesthatthefirst2bytesofthevalueshouldbereplacedwiththefirst2bytes
oftheBusinessEntitylDanchor-recordcolumn.TheFirstNamecolumnofthis
rowindicatesthatthefirst7bytesofthevalueshouldbereplacedwiththefirst7bytesof
theFirstNameanchor-recordcolumn,andsoon.
Page-dictionarycompressionisthesecondtypeofcompressionthatSQLServeruses
tocompresspages.Itcreatesanon-pagedictionaryofvaluesthatoccurmultipletimes
acrossanycolumnsandrowsonthepage.Itthenreplacesthoseduplicatevalueswith
indexesintothedictionary.ConsiderFigure19-9,whichshowsadatapagewithduplicate
values.
Figure19-9.Uncompressedpagewithduplicatevaluesacrosscolumnsandrows
TheduplicatevaluesArthurandMartinareaddedtothedictionaryandreplaced
inthedatapagewithindexesintothedictionary.ThevalueMartinisreplacedwiththe
indexvalue(0)everywhereitoccursinthedatapage,andthevalueArthurisreplaced
withtheindexvalue(1).ThisisdemonstratedinFigure19-10.
Figure19-10.Pagecompressedwithpage-dictionarycompression
WhenSQLServerperformspagecompressionondatapagesandleaf-indexpages,it
firstappliesrowcompression,andthenitappliespage-dictionarycompression.
NoteForperformancereasons,SQLServerdoesn’tapplypage-dictionarycompression
tonon-leafindexpages.
Youcanestimatethesavingsyou’llgetthroughpagecompressionwiththe
sp_estimate_data_compression_savingsprocedure,asshowninListing19-
8.TheresultsareshowninFigure19-11.
Listing19-8.EstimatingData-CompressionSavingswithPageCompression
EXECsp_estimate_data_compression_savings'Person',
'Person',
NULL,
NULL,
'PAGE';
Figure19-11.Pagecompressionspacesavingsestimate
AsyoucanseeinFigure19-11,SQLServerestimatesthatitcanusepagecompression
tocompressthePerson.Persontablefrom29.8MBinsizedowntoabout18.2MB—
aconsiderablesavings.YoucanapplypagecompressiontoatablewiththeALTER
TABLEstatement,asshowninListing19-9.
Listing19-9.ApplyingPageCompressiontothePerson.PersonTable
ALTERTABLEPerson.PersonREBUILD
WITH(DATA_COMPRESSION=PAGE);
Aswithrowcompression,youcanusethesp_spaceusedproceduretoverifyhow
muchspacepagecompressionsavesyou.
Pagecompressionisgreatforsavingspace,butitdoesn’tcomewithoutacost.
Specifically,youpayforthespacesavingswithincreasedCPUoverheadforSELECT
queriesandDMLstatements.So,whenshouldyouusepagecompression?Microsoft
makesthefollowingrecommendations:
Ifthetableorindexissmallinsize,thentheoverheadyouincurfrom
compressionprobablywon’tbeworththeextraCPUoverhead.
IfthetableorindexisheavilyaccessedforqueriesandDMLactions,
theextraCPUoverheadcansignificantlyimpactperformance.It’s
importanttoidentifyusagepatternswhendecidingwhetherto
compressthetableorindex.
Usethesp_estimate_data_compression_savings
proceduretoestimatespacesavings.Iftheestimatedspacesavingsis
insignificant(ornonexistent),thentheextraCPUoverheadwill
probablyoutweighthebenefits.
SparseColumns
Inadditiontorowcompressionandpagecompression,SQLServerprovidessparse
columns,whichletyouoptimizeNULLvaluestorageincolumns:whenaNULLvalueis
storedinthecolumn,ittakesup0bytes..Thetrade-off(andyouknewtherewouldbe
one)isthatthecostofstoringnon-NULLvaluesgoesupby4bytesforeachvalue.
Microsoftrecommendsusingsparsecolumnswhendoingsowillresultinatleast20%to
40%spacesavings.Foranintcolumn,forinstance,atleast64%ofthevaluesmustbe
NULLtoachievea40%spacesavingswithsparsecolumns.
Todemonstratesparsecolumnsinaction,let’suseaquerythatgeneratescolumnswith
alotofNULLsinthem.ThequeryshowninListing19-10createsapivot-stylereportthat
liststheCustomerIDnumbersassociatedwitheverysalesorderdowntherightsideof
theresults,andaselectionofproductnamesfromthesalesorders.Theintersectionof
eachCustomerIDandproductnamecontainsthenumberofeachitemorderedbyeach
customer.ANULLindicatesthatacustomerdidn’torderanitem.Partialresultsofthis
queryareshowninFigure19-12.
Listing19-10.PivotQuerythatGeneratesColumnswithManyNULLs
SELECT
CustomerID,
[HLRoadFrame-Black,58],
[HLRoadFrame-Red,58],
[HLRoadFrame-Red,62],
[HLRoadFrame-Red,44],
[HLRoadFrame-Red,48],
[HLRoadFrame-Red,52],
[HLRoadFrame-Red,56],
[LLRoadFrame-Black,58]
FROM
(
SELECTsoh.CustomerID,p.NameASProductName,
COUNT
(
CASEWHENsod.LineTotalISNULLTHENNULL
ELSE1
END
)ASNumberOfItems
FROMSales.SalesOrderHeadersoh
INNERJOINSales.SalesOrderDetailsod
ONsoh.SalesOrderID=sod.SalesOrderID
INNERJOINProduction.Productp
ONsod.ProductID=p.ProductID
GROUPBY
soh.CustomerID,
sod.ProductID,
p.Name
)src
PIVOT
(
SUM(NumberOfItems)FORProductName
IN
(
"HLRoadFrame-Black,58",
"HLRoadFrame-Red,58",
"HLRoadFrame-Red,62",
"HLRoadFrame-Red,44",
"HLRoadFrame-Red,48",
"HLRoadFrame-Red,52",
"HLRoadFrame-Red,56",
"LLRoadFrame-Black,58"
)
)ASpvt;
Figure19-12.Pivotquerythatreturnsthenumberofeachitemorderedbyeachcustomer
Listing19-11createstwosimilartablestoholdtheresultsgeneratedbythequeryin
Listing19-10.ThetablesgeneratedbytheCREATETABLEstatementsinListing19-11
havethesamestructure,exceptthatSparseTableincludesthekeywordSPARSEinits
columndeclarations,indicatingthatthesearesparsecolumns.
Listing19-11.CreatingSparseandNonsparseTables
CREATETABLENonSparseTable
(
CustomerIDintNOTNULLPRIMARYKEY,
"HLRoadFrame-Black,58"intNULL,
"HLRoadFrame-Red,58"intNULL,
"HLRoadFrame-Red,62"intNULL,
"HLRoadFrame-Red,44"intNULL,
"HLRoadFrame-Red,48"intNULL,
"HLRoadFrame-Red,52"intNULL,
"HLRoadFrame-Red,56"intNULL,
"LLRoadFrame-Black,58"intNULL
);
CREATETABLESparseTable
(
CustomerIDintNOTNULLPRIMARYKEY,
"HLRoadFrame-Black,58"intSPARSENULL,
"HLRoadFrame-Red,58"intSPARSENULL,
"HLRoadFrame-Red,62"intSPARSENULL,
"HLRoadFrame-Red,44"intSPARSENULL,
"HLRoadFrame-Red,48"intSPARSENULL,
"HLRoadFrame-Red,52"intSPARSENULL,
"HLRoadFrame-Red,56"intSPARSENULL,
"LLRoadFrame-Black,58"intSPARSENULL
);
AfterusingthequeryinListing19-10topopulatethesetwotables,youcanusethe
sp_spaceusedproceduretoseethespacesavingsthatsparsecolumnsprovide.Listing
19-12executessp_spaceusedonthesetwotables,bothofwhichcontainidentical
data.TheresultsshowninFigure19-13demonstratethattheSparseTabletakesup
onlyabout25%ofthespaceusedbytheNonSparseTable,becauseNULLvaluesin
sparsecolumnstakeupnostoragespace.
Listing19-12.CalculatingtheSpaceSavingsofSparseColumns
EXECsp_spaceusedN'NonSparseTable';
EXECsp_spaceusedN'SparseTable';
Figure19-13.Spacesavingsprovidedbysparsecolumns
SparseColumnSets
Inadditiontosparsecolumns,SQLServerprovidessupportforXMLsparsecolumnsets.
AnXMLcolumnsetisdefinedasanxmldatatypecolumn,anditcontainsnon-NULL
sparsecolumndatafromthetable.AnXMLsparsecolumnsetisdeclaredusingthe
COLUMNSETFORALLSPARSECOLUMNSoptiononanxmlcolumn.Asasimple
example,theAdventureWorksProduction.Producttablecontainsseveralproducts
thatdon’thaveassociatedsize,color,orotherdescriptiveinformation.Listing19-13
createsatablecalledProduction.SparseProductthatdefinesseveralsparse
columnsandasparsecolumnset.
Listing19-13.CreatingandPopulatingaTablewithaSparseColumnSet
CREATETABLEProduction.SparseProduct
(
ProductIDintNOTNULLPRIMARYKEY,
Namedbo.NameNOTNULL,
ProductNumbernvarchar(25)NOTNULL,
Colornvarchar(15)SPARSENULL,
Sizenvarchar(5)SPARSENULL,
SizeUnitMeasureCodenchar(3)SPARSENULL,
WeightUnitMeasureCodenchar(3)SPARSENULL,
Weightdecimal(8,2)SPARSENULL,
Classnchar(2)SPARSENULL,
Stylenchar(2)SPARSENULL,
SellStartDatedatetimeNOTNULL,
SellEndDatedatetimeSPARSENULL,
DiscontinuedDatedatetimeSPARSENULL,
SparseColumnSetxmlCOLUMN_SETFORALL_SPARSE_COLUMNS
);
GO
INSERTINTOProduction.SparseProduct
(
ProductID,
Name,
ProductNumber,
Color,
Size,
SizeUnitMeasureCode,
WeightUnitMeasureCode,
Weight,
Class,
Style,
SellStartDate,
SellEndDate,
DiscontinuedDate
)
SELECT
ProductID,
Name,
ProductNumber,
Color,
Size,
SizeUnitMeasureCode,
WeightUnitMeasureCode,
Weight,
Class,
Style,
SellStartDate,
SellEndDate,
DiscontinuedDate
FROMProduction.Product;
GO
YoucanviewthesparsecolumnsetinXMLformwithaqueryliketheoneinListing
19-14.TheresultsinFigure19-14showthatthefirstfiveproductsdon’thaveanysparse
columndataassociatedwiththem,sothesparsecolumndatatakesupnospace.By
contrast,products317and318bothhaveColorandClassdataassociatedwiththem.
Listing19-14.QueryingaXMLSparseColumnSetasXML
SELECTTOP(7)
ProductID,
SparseColumnSetFROMProduction.SparseProduct;
Figure19-14.ViewingsparsecolumnsetsinXMLformat
AlthoughSQLServermanagessparsecolumnsetsusingXML,youdon’tneedto
knowXMLtoaccesssparsecolumnsetdata.Infact,youcanaccessthecolumnsdefined
insparsecolumnsetsusingthesamequeryandDMLstatementsyou’vealwaysused,as
showninListing19-15.TheresultsofthisqueryareshowninFigure19-15.
Listing19-15.QueryingSparseColumnSetsbyName
SELECT
ProductID,
Name,
ProductNumber,
SellStartDate,
Color,
Class
FROMProduction.SparseProduct
WHEREProductIDIN(1,317);
Figure19-15.QueryingsparsecolumnsetswithSELECTqueries
Sparsecolumnsetsprovidethebenefitsofsparsecolumns,withNULLstakingupno
storagespace.However,thedownsideisthatnon-NULLsparsecolumnsthatareapartofa
columnsetarestoredinXMLformat,addingsomestorageoverheadascomparedwith
theirnonsparse,non-NULLcounterparts.
Indexes
Yourqueryperformancemaybegintolagovertimeforseveralreasons.Itmaybethat
databaseusagepatternshavechangedsignificantly,ortheamountofdatastoredinthe
databasehasincreasedsignificantly,orthedatabasehasfallenoutofmaintenance.
Whateverthereason,theknee-jerkreactionofmanydevelopersandDBAsistothrow
indexesattheproblem.Althoughindexesareindeedusefulforincreasingperformance,
theyconsumeresources,bothinstorageandmaintenance.Beforecreatingnewindexesall
overyourdatabase,it’simportanttounderstandhowtheywork.Thissectionprovidesan
overviewofSQLServer’sindexingmechanisms.
Heaps
InSQLServerparlance,aheapissimplyanunorderedcollectionofdatapageswithno
clusteredindex.SQLServerusesindexallocationmap(IAM)pagestotrackallocation
unitsofthefollowingtypes:
HeaporB-tree(HOBT)allocationunits,whichtrackstorage
allocationfortablesandindexes
LOBallocationunits,whichtrackstorageallocationforLOBdata
SmallLOB(SLOB)allocationunits,whichtrackstorageallocation
forrow-overflowdata
AsanyDBAwilltellyou,atablescan,whichisSQLServer’s“bruteforce”data-
retrievalmethod,isabadthing(althoughnotnecessarilytheworstthingthatcanhappen).
Inatablescan,SQLServerliterallyscanseverydatapagethatwasallocatedbytheheap.
Anyqueryagainsttheheapcausesatable-scanoperation.Todeterminewhichpageshave
beenallocatedfortheheap,SQLServermustreferbacktotheIAM.Atablescanis
knownasanallocationorderscanbecauseitusestheIAMtoscanthedatapagesinthe
orderinwhichtheywereallocatedbySQLServer.
Heapsarealsosubjecttofragmentation,andtheonlywaytoeliminatefragmentation
fromtheheapistocopytheheaptoanewtable,createaclusteredindexonthetable,or
performperiodicmaintenancetokeeptheindexfrombeingfragmented.Forwardpointers
introduceanotherperformance-relatedissuetoheaps.Whenarowwithvariable-length
columnsisupdatedwithrowlengthlargerthanthepagesize,theupdatedrowmayhaveto
bemovedtoanewpage.WhenSQLServermustmovetherowinaheaptoanew
location,itleavesaforwardpointertothenewlocationattheoldlocation.Iftherowis
movedagain,SQLServerleavesanotherforwardpointer,andsoon.Forwardpointers
resultinadditionalI/Os,makingtablescansevenlessefficient(andyouthoughtthat
wasn’tpossible!).Tablescansaren’tentirelybadifyouhavetoperformrow0based
operationsorifyou’requeryingagainsttableswithsmalldatasetssuchaslookuptables,
whereaddinganindexcreatesmaintenanceoverhead.
TipQueryingaheapwithnoclusteredornonclusteredindexesalwaysresultsina
costlytablescan.
ClusteredIndexes
Ifaheapisanunorderedcollectionofdatapages,howdoyouimposeorderontheheap?
Theanswerisaclusteredindex.Aclusteredindexturnsanunorderedheapintoa
collectionofdatapagesorderedbythespecifiedclustered-indexcolumns.Clustered
indexesaremanagedinthedatabaseasB-treestructures.
ThetopleveloftheclusteredindexB-treeisknownastherootnode,thebottom-level
nodesareknownasleafnodes,andallnodesinbetweentherootnodeandleafnodesare
collectivelyreferredtoasintermediatenodes.Inaclusteredindex,theleafnodescontain
theactualdatarowsforatable,andallleafnodespointtothenextandpreviousleaf
nodes,formingadoublylinkedlist.TheclusteredindexholdsaspecialpositioninSQL
Serverindexingbecauseitsleafnodescontaintheactualtabledata.Becausethepage
chainforthedatapagescanbeorderedonlyoneway,therecanbeonlyoneclustered
indexdefinedpertable.Thequeryoptimizerusestheclusteredindexforseeks,because
thedatacanbefounddirectlyattheleaflevelifaclusteredindexisused.Theclustered-
indexB-treestructureisshowninFigure19-16.
Figure19-16.ClusteredindexB-treestructure
GuaranteedOrder
Despitethefactthatthedatapagesinaclusteredindexareorderedbytheclustered-index
columns,youcan’tdependontablerowsbeingreturnedinclustered-indexorderunless
youspecifyanORDERBYclauseinyourqueries.Thereareacoupleofreasonsforthis,
includingthefollowing:
Yourquerymayjoinmultipletables,andtheoptimizermaychooseto
returnresultsinanotherorderbasedonindexesonanothertable.
Theoptimizermayuseanallocation-orderscanofyourclustered
index,whichwillreturnresultsintheorderinwhichdatapageswere
allocated.
ThebottomlineisthattheSQLqueryoptimizermaydecidethat,forwhateverreason,
it’smoreefficienttoreturnresultsunorderedorinanorderotherthanclustered-index
order.Becauseofthis,youcan’tdependonresultsalwaysbeingreturnedinthesame
orderwithoutanexplicitORDERBYclause.I’veseenmanycasesofdevelopersbeing
bittenbecausetheirclient-sidecodeexpectedresultsinaspecificorder,andaftermonths
ofreceivingresultsinthecorrectorder,theoptimizerdecidedthatreturningresultsina
differentorderwouldbemoreefficient.Don’tfallvictimtothisfalseoptimism—use
ORDERBYwhenorderedresultsareimportant.
Manyareundertheimpressionthataclustered-indexscanisthesamethingasatable
scan.Inonesense,thisiscorrect—whenSQLServerperformsanunorderedclustered-
indexscan,itrefersbacktotheIAMtoscanthedatapagesoftheclusteredindexusingan
allocation-orderscan,justlikeatablescan.
However,SQLServerhasanotheroptionforclusteredindexes:theorderedclustered
indexscan.Inanorderedclustered-indexscan,orleaf-levelscan,SQLServercanfollow
thedoublylinkedlistattheleaf-nodelevelinsteadofreferringbacktotheIAM.Theleaf-
levelscanhasthebenefitofscanninginclustered-indexorder.Tablescansdon’thavethe
optionofaleaf-levelscanbecausetheleaf-levelpagesaren’torderedorlinked.
Clusteredindexesalsoeliminatetheperformanceproblemsassociatedwithforward
pointersintheheap,althoughyoudohavetopayattentiontofragmentation,pagesplits,
andfillfactorwhenyouhaveaclusteredindexonyourtable.Fillfactordetermineshow
manyrowscanbefilledintheindexpage.Whentheindexpageisfullandnewrowsneed
tobeinserted,SQLServercreatesanewindexpageandtransfersrowstothenewpage
fromthepreviouspage;thisiscalledaspagesplit.Youcanreducepagesplitsbysetting
theproperfillfactortodeterminehowmuchfreespacethereisintheindexpages.
Sowhenshouldyouuseaclusteredindex?Asageneralrule,weliketoputaclustered
indexonnearlyeverytablewecreate,althoughitisn’tarequirementtohaveclustered
indexesforalltables.Youhavetodecidewhichcolumnsyouwishtocreateinyour
clusteredindexes.Herearesomegeneralrecommendationsforcolumnstoconsiderin
yourclusteredindexdesign:
Columnsthatprovideahighdegreeofuniqueness.Monotonically
increasingcolumns,suchasIDENTITYandSEQUENCEcolumns,are
idealbecausetheyalsoreducetheoverheadassociatedwithpage
splitsthatresultfrominsertandupdateoperations.
Columnsthatreturnarangeofvaluesusingoperatorslike>=,<,and
BETWEEN.Whenyouusearangequeryonclusteredindexcolumns,
afterthefirstmatchisfound,theremainingvaluesareguaranteedto
belinked/adjacentintheB-tree.
Columnsthatareusedinqueriesthatreturnlargeresultsetsofdata
fromthosecolumns.
ColumnsthatareusedintheONclauseofaJOIN.Usually,theseare
primary-keyorforeign-keycolumns.SQLServercreatesaunique
clusteredindexonthecolumnwhentheprimarykeyisaddedtothe
table.
ColumnsthatareusedinGROUPBYorORDERBYclauses.A
clusteredindexonthesecolumnscanhelpSQLServerimprove
performancewhenorderingqueryresultsets.
Youshouldalsomakeyourclusteredindexesasnarrowaspossible(oftenasingleint
oruniqueidentifiercolumn),becausethisdecreasesthenumberoflevelsthatmust
betraversedandhencereducesI/O.Anotherreasonisthatthey’reautomaticallyappended
toallnonclusteredindexesonthesametableasrowlocators,sokeepingtheclustered-
indexkeysmallreducesthesizeofnonclusteredindexesaswell.
NonclusteredIndexes
NonclusteredindexesprovideanothertoolforindexingrelationaldatainSQLServer.
Likeclusteredindexes,SQLServerstoresnonclusteredindexesasB-treestructures.
Unlikeclusteredindexes,however,eachleafnodeinanonclusteredindexcontainsthe
nonclusteredkeyvalueandarowlocator.Thetablerowsarestoredapartfromthe
nonclusteredindex—intheclusteredindexifoneisdefinedonthetableorinaheapifthe
tablehasnoclusteredindex.Figure19-17.showsthenonclusteredindexB-treestructure.
Recallfromtheprevioussectiononclusteredindexesthatdatarowscanonlybestoredin
onesortedorder,andthisisachievedviaaclusteredindex.Ordercanonlybeachievedvia
theclusteredindex.
Ifatablehasaclusteredindex,allnonclusteredindexesdefinedonthetable
automaticallyincludetheclustered-indexcolumnsastherowlocator.Ifthetableisaheap,
SQLServercreatesrowlocatorstotherowsfromthecombinationofthefileidentifier,
pagenumber,andslotonthepage.Therefore,ifyouaddaclusteredindexatalaterdate,
beawarethatyouneedtorebuildyournonclusteredindexestousetheclustered-index
columnasarowlocatorratherthanfileidentifier.
Figure19-17.NonclusteredindexB-treestructure
NonclusteredindexesareassociatedwiththeRID-lookupandkey-lookupoperations.
RIDlookupsarebookmarklookupsintotheheapusingrowidentifiers(RIDs),whereas
keylookupsarebookmarklookupsontableswithclusteredindexes.OnceSQLServer
locatestheindexrowsthatfulfillaquery,ifthequeryrequiresmorecolumnsthanthe
nonclusteredindexcovers,thenthequeryenginemustusetherowlocatortofindtherows
intheclusteredindexortheheaptoretrievenecessarydata.Thesearetheoperations
referredtoasRIDandkeylookups,andthey’recostly—socostly,infact,thatmany
performance-tuningoperationsarebasedoneliminatingthem.
NotePriorversionsofSQLServerhadthebookmarklookupoperation.InSQLServer
2014,thisoperationhasbeensplitintotwodistinctoperations—theRIDlookupandthe
keylookup—todifferentiatebetweenbookmarklookupsagainstheapsandclustered
indexes.
OnemethodofdealingwithRIDandkeylookupsistocreatecoveringindexes.A
coveringindexisanonclusteredindexthatcontainsallthecolumnsnecessarytofulfilla
givenqueryorsetofqueries.Ifanonclusteredindexdoesn’tcoveraquery,thenforeach
row,SQLServerhastolookuptherowtoretrievevaluesforthecolumnsthataren’t
includedinthenonclusteredindex.IfyouperformthelookupusingRID,thereisextraI/O
foreachrowintheresultset.Butwhenyoudefineacoveringindex,thequeryenginecan
determinethatalltheinformationitneedstofulfillthequeryisstoredinthenonclustered
indexrows,soitdoesn’tneedtoperformalookupoperation.
SQLServerofferstheoptiontoINCLUDEcolumnsintheindex.Anincludedcolumn
isn’tanindexkey,soitallowsthecolumnstoappearontheleafpagesofthenonclustered
indexandhenceimprovesqueryperformance.
TipProlificauthorandSQLServerMVPAdamMachanicdefinesaclusteredindexas
acoveringindexforeverypossiblequeryagainstatable.Thisdefinitionprovidesagood
toolfordemonstratingthatthere’snotmuchdifferencebetweenclusteredand
nonclusteredindexes,andithelpstoreinforcetheconceptofindexcovering.
TheexamplequeryinListing19-16showsasimplequeryagainstthe
Person.Persontablethatrequiresabookmarklookup,whichisitselfshowninthe
queryplaninFigure19-18.
Listing19-16.QueryRequiringaBookmarkLookup
SELECT
BusinessEntityID,
LastName,
FirstName,
MiddleName,
TitleFROMPerson.PersonWHERELastName=N'Duffy';
Figure19-18.Bookmarklookupinthequeryplan
Sowhyisthereabookmarklookup(referencedasakeylookupoperatorinthequery
plan)?Theanswerliesinthequery.ThisparticularqueryusestheLastNamecolumnin
theWHEREclausetolimitresults,sothequeryenginedecidestousethe
IX_Person_LastName_FirstName_MiddleNamenonclusteredindextofulfillthe
query.ThisnonclusteredindexcontainstheLastName,FirstName,and
MiddleNamecolumns,aswellastheBusinessEntityIDcolumn,whichisdefined
astheclusteredindex.ThelookupoperationisrequiredbecausetheSELECTclausealso
specifiesthattheTitlecolumnneedstobereturnedintheresultset.Becausethe
Titlecolumnisn’tincludedinthecoveringindex,SQLServerhastoreferbacktothe
table’sdatapagestoretrieveit.
CreatinganindexwiththeTitlecolumnincludedinthenonclusteredindexas
showninListing19-17removesthelookupoperationfromthequeryplanforthequeryin
Listing19-16.AsshowninFigure19-19,the
IX_Covering_Person_LastName_FirstName_MiddleNameindexcoversthe
query.
TipAnotheralternativetoeliminatethiscostlylookupoperationwouldbetomodify
thenonclusteredindexusedintheexampletoincludetheTitlecolumn,whichwould
createacoveringindexforthequery.
Listing19-17.QueryUsingaCoveringIndex
CREATENONCLUSTEREDINDEX
[IX_Covering_Person_LastName_FirstName_MiddleName]ON
[Person].[Person]
(
[LastName]ASC,
[FirstName]ASC,
[MiddleName]ASC
)INCLUDE(Title)
WITH(PAD_INDEX=OFF,STATISTICS_NORECOMPUTE=OFF,
SORT_IN_TEMPDB=OFF,DROP_EXISTING=OFF,ONLINE=OFF,
ALLOW_ROW_LOCKS=ON,ALLOW_PAGE_LOCKS=ON)ON[PRIMARY]
GO
Figure19-19.Thecoveringindexeliminatesthelookupoperation
Youcandefineupto999nonclusteredindexespertable.Youshouldcarefullyplan
yourindexingstrategyandtrytominimizethenumberofindexesyoudefineonasingle
table.Nonclusteredindexescanrequireasubstantialamountofadditionalstorage,and
thereisadefiniteoverheadinvolvedinautomaticallyupdatingthemwheneverthetable
datachanges.Whendecidinghowmanyindexestoaddtoatable,considertheusage
patternscarefully.Tableswithdatathatdoesn’tchange—orrarelychanges—mayderive
greaterbenefitfromhavinglotsofindexesdefinedonthemthantableswhosedatais
modifiedoften.
Nonclusteredindexesareusefulforthefollowingtypesofqueries:
Queriesthatreturnonerow,orafewrows,withhighselectivity.
Queriesthatcanuseanindexwithhighselectivity(generallygreater
than95%).Selectivityisameasureoftheuniquekeyvaluesinan
index.SQLServeroftenignoresindexeswithlowselectivity.
Queriesthatreturnsmallrangesofdatathatwouldotherwiseresultin
aclusteredindexortablescan.Thesetypesofqueriesoftenusesimple
equalitypredicates(=)intheWHEREclause.
Queriesthatarecompletelycoveredbythenonclusteredindex.
FilteredIndexes
InSQLServer2014,filteredindexesprovideawaytocreatemoretargetedindexesthat
requirelessstorageandcansupportmoreefficientqueries.Filteredindexesareoptimized
nonclusteredindexesthatallowyoutoeasilyaddfilteringcriteriatorestricttherows
includedintheindexwithaWHEREclause.Afilteredindeximprovestheperformanceof
queriesbecausetheindexissmallerthananonclusteredindex,andthestatisticsaremore
accuratebecausetheycoveronlytherowsinthefilteredindex.Addingafilteredindexto
atablewhereanonclusteredindexisunnecessaryreducesdiskstorageforthe
nonclusteredindex,andthestatisticsupdatethecostaswell.Listing19-18createsa
filteredindexontheSizecolumnoftheProduction.Producttablethatexcludes
NULL.
Listing19-18.CreatingandTestingaFilteredIndexontheProduction.Product
Table
CREATENONCLUSTEREDINDEXIX_Product_Size
ONProduction.Product
(
Size,
SizeUnitMeasureCode)
WHERESizeISNOTNULL;
GO
SELECT
ProductID,
Size,
SizeUnitMeasureCodeFROMProduction.ProductWHERESize
='L';
GO
TipFilteredindexesareparticularlywellsuitedforindexingnon-NULLvaluesof
sparsecolumns.
OptimizingQueries
OneofthemoreinterestingtasksthatSQLdevelopersandDBAsmustperformis
optimizingqueries.Toborrowanoldcliché,queryoptimizationisasmuchartasscience.
TherearealotofmovingpartsintheSQLqueryengine,andyourtaskistogivethe
optimizerasmuchgoodinformationasyoucansothatitcanmakegooddecisionsat
runtime.
Performanceisgenerallymeasuredintermsofresponsetimeandthroughput,defined
asfollows:
ResponsetimeisthetimeittakesSQLServertocompleteatasksuch
asaquery.
ThroughputisameasureofthevolumeofworkthatSQLServercan
completeinafixedperiodoftime,suchasthenumberoftransactions
perminute.
Severalotherfactorsaffectoverallsystemperformancebutareoutsidethescopeof
thisbook.Applicationresponsiveness,forinstance,dependsonseveraladditionalfactors
likenetworklatencyandUIarchitecture,bothofwhicharebeyondSQLServer’scontrol.
Thissectiontalksabouthowtousequeryplanstodiagnoseperformanceissues.
ReadingQueryPlans
WhenyousubmitaT-SQLscriptorstatementtotheSQLServerqueryengine,SQL
Servercompilesyourcodeintoaqueryplan.Thequeryplaniscomposedofaseriesof
physicalandlogicaloperatorsthattheoptimizerhaschosentocompleteyourquery.The
optimizerbasesitschoiceofoperatorsonawidearrayoffactorslikedata-distribution
statistics,cardinalityoftables,andavailabilityofusefulindexes.SQLServerusesacost-
basedoptimizer,meaningtheexecutionplanitchooseswillhavethelowestestimated
cost.
SQLServercanreturnqueryplansinavarietyofformats.Mypreferenceisthe
graphicalqueryexecutionplan,whichisusedinexamplesthroughoutthebook.Figure
19-20showsaqueryplanforasimplequerythatjoinstwotables.
Figure19-20.Queryexecutionplanforaninnerjoinquery
YoucangenerateagraphicalqueryplanforagivenquerybyselectingQuery
IncludeActualExecutionPlanfromtheSSMSmenuandthenrunningyourSQL
statements.Alternatively,youcanselectQuery DisplayEstimatedExecutionPlan
withoutrunningthequery.
Agraphicalqueryplanisreadfromrighttoleftandtoptobottom.Itcontainsarrows
indicatingtheflowofdatathroughthequeryplan.Thearrowsshowtherelativeamountof
databeingmovedfromoneoperatortothenext,withwiderarrowsindicatinglarger
numbersofrows,asshowninFigure19-20.Youcanpositionthemousepointerontopof
anyoperatororarrowinthegraphicalqueryplantodisplayapop-upwithadditional
informationabouttheoperatorordataflowbetweenoperators,suchasthenumberofrows
beingactedonandtheestimatedrowsize.Youcanalsoright-clickanoperatororarrow
andselectPropertiesfromthepop-upmenutoviewevenmoredescriptiveinformation.
Inadditionyoucanright-clickintheExecutionPlanwindowandselectSave
ExecutionPlanAstosaveyourgraphicalexecutionplanasanXMLqueryplan.Query
plansaresavedwitha.sqlplanfileextensionandcanbeviewedingraphicalformatin
SSMSbydouble-clickingthefile.Thisisparticularlyusefulfortroubleshootingqueries
remotely,becauseyourusersorotherdeveloperscansavethegraphicalqueryplanande-
mailittoyou,andyoucanopenitupinalocalinstanceofSSMSforfurtherinvestigation.
ActualorEstimated?
Estimatedexecutionplansareusefulindeterminingtheoptimizer’sintent.Theword
estimatedinthenamecanbeabitmisleadingbecauseallqueryplansarebasedonthe
optimizer’sestimatesofyourdatadistribution,tablecardinality,andmore.
Therearesomedifferencesbetweenestimatedandactualqueryplans,however.
BecauseanactualqueryplanisgeneratedasyourT-SQLstatementsareexecuted,the
optimizercanaddinformationtothequeryplanasitruns.Thisadditionalinformation
includesitemslikeactualrebindsandrewinds,valuesthatreturnthenumberoftimesthe
init()methodiscalledintheplan,andtheactualnumberofrows.
Whendealingwithtemporaryobjects,actualqueryplanshavebetterinformation
availableconcerningwhichoperatorsarebeingused.Considerthefollowingsimplescript,
whichcreates,populates,andqueriesatemporarytable:
CREATETABLE#tl(
BusinessEntityIDintNOTNULL,
LastNamenvarchar(50),
FirstNamenvarchar(50),
MiddleNamenvarchar(50));
CREATEINDEXtl_LastNameON#tl(LastName);
INSERTINTO#tl(
BusinessEntityID,
LastName,
FirstName,
MiddleName)
SELECT
BusinessEntityID,
LastName,
FirstName,
MiddleNameFROMPerson.Person;
SELECT
BusinessEntityID,
LastName,
FirstName,
MiddleNameFROM#tlWHERELastName=N'Duffy';
DROPTABLE#tl;
Intheestimatedqueryplanforthiscode,theoptimizerindicatesthatitwilluseatable
scan,asshownnext,tofulfilltheSELECTqueryattheendofthescript:
Theactualqueryplan,however,usesamuchmoreefficientnonclusteredindexseek
withabookmarklookupoperationtoretrievethetworelevantrowsfromthetable,as
shownhere:
Thedifferencebetweentheestimatedandactualqueryplansinthiscaseisthe
informationavailableatthetimethequeryplanisgenerated.Whentheestimatedquery
planiscreated,thereisnotemporarytableandnoindexonthetemporarytable,sothe
optimizerguessesthatatablescanwillberequired.Whentheactualqueryplanis
generated,thetemporarytableanditsnonclusteredindexbothexist,sotheoptimizer
comesupwithabetterqueryplan.
Inadditiontographicalqueryplans,SQLServersupportsXMLqueryplansandtext
queryplans,anditcanreportadditionalruntimestatistics.Thisadditionalinformationcan
beaccessedusingthestatementsshowninTable19-1.
Table19-1.StatementstoGenerateQueryPlans
Statement Description
SET
SHOWPLAN_ALL
ON/OFF
Returnsatext-basedestimatedexecutionplanwithoutexecutingthequery
SET
SHOWPLAN_TEXT
ON/OFF
Returnsatext-basedestimatedexecutionplanwithoutexecutingthequery,butthe
informationreturnedmaybelessthanwhatyougetfromchoosing
SHOWPLAN_ALL.
SET
SHOWPLAN_XML
ON/OFF
ReturnsanXML-basedestimatedexecutionplanwithoutexecutingthequery
SET
STATISTICSIO
ON/OFF
ReturnsstatisticsinformationaboutlogicalI/Ooperationsduringexecutionofaquery
SET
STATISTICS
PROFILE
ON/OFF
Returnsactualqueryexecutionplansinresultsetsfollowingtheresultsetgenerated
byeachqueryexecuted
SET
STATISTICS
TIMEON/OFF
Returnsstatisticsaboutthetimerequiredtoparse,compile,andexecutestatementsat
runtime
Oncethequeryiscompiled,itcanbeexecuted,andtheexecutionneednotnecessarily
happenafterthequeryiscompiled.So,ifthequeryisexecutedseveraldaysafterithas
beencompiled,theunderlyingdatamayhavechanged,andtheplanthathasbeen
compiledmaynotbeoptimalduringtheexecutiontime.So,whenthisqueryisbeing
executedSQLServerfirstcheckstoseeiftheplanisstillvalid.Ifthequeryoptimizer
decidesthattheplanissuboptimal,afewstatementsortheentirebatchwillberecompiled
toproduceadifferentplan.Thesecompilationsarecalledrecompilations;andalthough
sometimesit’snecessarytorecompilequeries,thisprocesscanslowdownqueryorbatch
executionsconsiderably,soit’soptimaltoreducerecompilations.
Somecausesforrecompilationsareasfollows:
Schemachangessuchasaddingordroppingcolumns,constraints,
indexes,statistics,andsoon
Runningsp_recompileonastoredprocedureortrigger
Usingsetoptionsafterthebatchhasstarted,suchas
ANSI_NULL_DFLT_OFF,ANSI_NULLS,ARITHABORT,andsoon
Oneofthemaincausesforexcessiverecompilationsistheuseoftemporarytablesin
queries.IfyoucreateatemporarytableinStoredProcAandreferencethetemporary
tableinastatementinStoredProcB,thenthestatementmustberecompiledeverytime
StoredProcAruns.Atablevariablemaybeagoodoptiontoreplaceatemporarytable
forasmallnumberofrows.
Sometimesyoumayexperiencesuboptimalqueryperformance,andthereareseveral
causes.OneofthecommoncausesisusingnonSearchARGumentable(nonSARGable)
expressionsinWHEREclausesorjoins,whichpreventsSQLServerfromusingtheindex.
Usingtheseexpressionscanslowqueriessignificantlyaswell.SomenonSARGable
expressionsareinequalityexpressioncomparisons,functions,implicitdata-type
conversions,andtheLIKEkeyword.Oftentheseexpressionscanberewrittentousean
index.Considerthefollowingsimplescript,whichfindsnamesstartingwithC:
SELECTTitle,FirstName,LastNameFROMperson.personWHERE
SUBSTRING(FirstName,1,1)='C'
Thisquerycausesatablescan.Butifthequeryisrewrittenasfollows,theoptimizer
willuseaclusteredseekifaproperindexexistsinthetable,henceimproving
performance:
SELECTTitle,FirstName,LastNameFROMperson.personWHERE
FirstNameLIKE'C%'
Sometimesyoudohavetousefunctionsinqueriesforcalculations.Inthesecases,if
youreplacethefunctionwithanindexedcomputedcolumn,theSQLServerquery
optimizercangenerateaplanthatwilluseanindex.SQLServercanmatchanexpression
tothecomputedcolumntousestatistics;however,theexpressionshouldmatchthe
computedcolumndefinitionexactly.
Methodology
Themethodologythathasservedmewellwhentroubleshootingperformanceissues
involvesthefollowingeightsteps:
1. Recognizetheissue.Beforeyoucantroubleshootaperformance
issue,youmustfirstdeterminethatthereisanactualissue.
Recognizinganissuecanbeginwithsomethingassimpleasend
userscomplainingthattheirapplicationsarerunningslowly.
2. Identifythesource.Onceyou’verecognizedthatthereisanissue,
youneedtoidentifyitasaSQLServer-relatedproblem.For
instance,ifyoureceivereportsofdatabase-enabledapplications
runningslowly,it’simportanttonarrowthesourceoftheproblem.
Iftheissueisrelatedtonetworkbandwidthorlatency,forinstance,
itcan’tberesolvedthroughsimplequeryoptimization.Ifit’saT-
SQLissue,youcanusetoolslikeSQLProfilerandqueryplansto
identifytheproblematiccode.
3. Reviewthebaseline.Onceyou’veidentifiedtheissueandthe
source,evaluatethebaseline.Forinstance,iftheenduseris
complainingthattheapplicationrunsslowly,youneedto
understandthedefinitionofslowandalsowhethertheissueis
reproducible.Slowcouldmeanreportsaren’trenderedwithin1
minute,oritcouldmeanreportsaren’trenderedwithin10
milliseconds.Withoutaproperbaseline,youhavenothingto
comparetoandcan’tascertainwhethertheissuereallyexists.
4. Analyzethecode.Onceyou’veidentifiedT-SQLcodeasthesource
oftheproblem,it’stimetodigdeeperandanalyzetherootcauseof
theproblem.Theoperatorsreturnedingraphicalqueryplans
provideanexcellentindicatorofthesourceofmanyproblems.For
example,youmayspotacostlyclusteredindexscanoperatorwhere
youexpectedamoreefficientnonclusteredindexseek.
5. Definepossiblesolutions.Aftertheissueshavebeenidentifiedin
thecode,it’stimetocomeupwithpotentialsolutions.Ifbookmark-
lookupoperationsareslowingqueryperformance,forinstance,you
maydeterminethataddinganewnonclusteredindexormodifying
anexistingoneisapossiblefixfortheissue.Anotherpossible
solutionmightbechangingthequerytoreturnfewercolumnsthat
arealreadycoveredbyanindex.
6. Evaluatethesolutions.Acriticalstepafterdefiningpossible
solutionsistoevaluatethepracticalityofthosesolutions.Many
thingsaffectwhetherasolutionispractical.Forinstance,youmay
beforbiddentochangeindexesonproductionservers,inwhichcase
addingormodifyingindexestosolveanissuemaybeimpractical.
Ontheotherhand,yourclientapplicationsmaydependonallthe
columnscurrentlybeingreturnedinthequery’sresultsets,so
changingthequerytoreturnfewercolumnsmaynotbeaworkable
solution.
Duringthisstepoftheprocess,youalsoneedtodeterminethe
impactofyoursolutionsonotherpartsofthesystem.Addingor
modifyinganindexontheservertosolveaqueryperformance
problemmayfixtheproblemforasinglequery,butitmayalso
introducenewperformanceproblemsforotherqueriesorDML
statements.Theseconflictingneedsshouldbeevaluated.
7. Implementthesolution.Duringthisstepoftheprocess,youactually
applyyoursolution.You’llmostlikelyhaveasubprocessherein
whichyouapplythesolutionfirsttoadevelopmentenvironment
andthentoaqualityassurance(QA)environment,andfinally
promoteittotheproductionenvironment.
8. Examinetheimpactofthesolution.Afterimplementingyour
solution,youshouldrevisitittoensurethatitfixestheproblem.
Thisisaveryimportantstepthatmanypeopleignore—theyrevisit
theirsolutionsonlywhenanotherissueoccurs.Byschedulinga
timetorevisityoursolution,youcantakeaproactiveapproachand
headoffproblemsbeforetheyaffectendusers.
ScalabilityisanotherimportantfactortoconsiderwhenwritingT-SQL.Scalabilityisa
measureofhowwellyourcodeworksunderincreasingdemands.Forinstance,aquery
mayprovideacceptableperformancewhenthesourcetablecontains100,000rowsand10
enduserssimultaneouslyquerying.However,thesamequerymaysufferperformance
problemswhenthetablegrowsto1,000,000rowsandthenumberofendusersgrowsto
100.Increasingstressonasystemtendstouncoverscalabilityandperformanceissuesthat
weren’tpreviouslyapparentinyourcodebase.Aspressureonyourdatabasegrows,it’s
importanttomonitorchangingaccesspatternsandincreasingdemandsonthesystemto
proactivelyhandleissuesbeforetheyaffectendusers.
It’salsoimportanttounderstandwhenanissueisn’treallyaproblem,oratleastnot
onethatrequiresagreatdealofattention.Asageneralrule,weliketoapplythe80/20
rulewhenoptimizingqueries:asaruleofthumb,focusyoureffortsonoptimizingthe
20%ofcodethatisexecuted80%ofthetime.IfyouhaveanSPthattakesalongtimeto
executebutisrunonlyonceaday,andasecondprocedurethattakesasignificantamount
oftimebutisrun10,000timesaday,you’dbewellservedtofocusyoureffortsonthe
latterprocedure.
Waits
Yourmaingoalindesigningandwritinganapplicationistoenableuserstogetaccurate
resultsetsinanefficientway.So,whenyoucomeacrossaperformanceissue,theplaceto
startisthequery.Foranygivensession,thequeryorthreadcanbeinoneoftwostates:
it’seitherrunningorwaitingonsomething.Whenthequeryisrunning,itmaybe
compilingorexecuting.Andwhenthequeryiswaiting,itmaybewaitingforI/O,
network,memory,locksorlatches,andsoon,oritmaybeforcedtowaittomakesurethe
processyieldsforotherprocesses.Whateverthecase,whenthequeryiswaitingona
resource,SQLServerlogsthewaittypefortheresourcethequeryiswaitingon.Youcan
thenusethisinformationtounderstandwhyqueryperformanceisaffected.
Tohelpyoubetterunderstandresourceusage,youneedtobefamiliarwiththree
performancemetricsthatcanplayaroleinqueryperformance:CPU,Duration,and
LogicalReads.CPUisessentiallytheworkertimespenttoexecutethequery;Durationis
thetimetheworkerthreadtakestoexecutethequery,whichincludesthetimeittakesto
waitfortheresourcesaswellasthetimeittakestoexecutethequery;andLogicalReads
isthenumberofdatapagesreadbythequeryexecutionfromthebufferpoolormemory.
Ifthepagedoesn’texistinthebufferpool,thenSQLServerperformsaphysicalreadto
readthepageintothebufferpool.Becauseyou’remeasuringqueryperformance,logical
readsareconsideredtomeasureperformance,notphysicalreads.Youcancalculatewait
timeusingtheformulaDuration–CPU.
Usingwaitstatsisamethodologythatcanhelpyouidentifyopportunitiestotune
queryperformance,andSQLServer2014has649waittypes.Let’ssayyourapplication
hassomeusersreadfromthetableandotheruserswritetothesametable.Atanygiven
time,ifrowsarebeinginsertedintothetable,thequerythatistryingtoreadthoserows
hastostopprocessingbecausetheresourceisunavailable.Oncetherowinsertionis
completed,thereadprocessgetsasignalthattheresourceisavailableforthisprocess;and
whenaschedulerisavailabletoprocessthereadthread,thequeryisprocessed.Thetime
SQLServerspendstoacquirethesystemresourceinthisexampleiscalledawait.The
timeSQLServerspendswaitingfortheprocesstobesignedwhentheresourceis
availableiscalledresourcewaittime.Oncetheprocessissignaled,theprocesshastowait
fortheschedulertobeavailablebeforetheprocesscancontinue,andthisiscalledsignal
waittime.Resourcewaittimeandsignalwaittimecombinedgivethewaittimein
milliseconds.
YoucanquerythewaittypesusingtheDMVssys.dm_os_waiting_tasks,
sys.dm_os_wait_stats,andsys.dm_exec_requests.
sys.dm_os_waiting_tasksandsys.dm_exec_requestsreturndetailsabout
whichtasksarewaitingcurrently,whereassys.dm_os_wait_statsliststhe
aggregateofthewaitssincetheinstancewaslastrestarted.So,youneedtocheck
sys.dm_os_waiting_tasksforaqueryperformanceanalysis.
Let’slookatanexampleofhowwaitscanhelpyoutunequeries.Youmighthave
comeacrossasituationwhereyou’retryingtoinsertasetofrowsintoatable,butthe
insertprocesshangsandisn’tresponsive.Whenyouquerysp_who2,itdoesn’tshowany
blocking;however,theinsertprocesswaitsforalongtimebeforeitcompletes.Let’ssee
howyoucanusewaitstatstodebugthisscenario.Listing19-19isascriptthatinserts
rowsintoawaitsdemotablecreatedinAdventureWorkswithusersessionID54.
Listing19-19.ScripttoDemonstrateWaits
useadventureworks
go
CREATETABLE[dbo].[waitsdemo](
[Id][int]NOTNULL,
[LastName][nchar](600)NOTNULL,
[FirstName][nchar](600)NOTNULL,
[MiddleName][nchar](600)NULL
)ON[PRIMARY]
GO
declare@idint=1
while(@id<=50000)
begin
insertintowaitsdemo
select@id,'Foo','User',NULL
SET@id=@id+1
end
Toidentifywhytheinsertqueryisbeingblocked,youcanquerythe
sys.dm_exec_requestsandsys.dm_exec_sessionsDMVstoseethe
processesthatarecurrentlyexecutingandalsoquerytheDMV
sys.dm_os_waiting_taskstoseethelistofprocessesthatarecurrentlywaiting.
TheDMVqueriesareshowninListing19-20,andpartialresultsareshowninFigure19-
21.Inthisexample,theinsertqueryusingsessionID54iswaitingonthe
shrinkdatabasetaskwithsessionID98.
Listing19-20.DMVtoQueryCurrentProcessesandWaitingTasks
--Listwaitinguserrequests
SELECT
er.session_id,er.wait_type,er.wait_time,
er.wait_resource,er.last_wait_type,
er.command,et.text,er.blocking_session_id
FROMsys.dm_exec_requestsASer
JOINsys.dm_exec_sessionsASes
ONes.session_id=er.session_id
ANDes.is_user_process=1
CROSSAPPLYsys.dm_exec_sql_text(er.sql_handle)ASet
GO
--Listwaitingusertasks
SELECT
wt.waiting_task_address,wt.session_id,wt.wait_type,
wt.wait_duration_ms,wt.resource_description
FROMsys.dm_os_waiting_tasksASwt
JOINsys.dm_exec_sessionsASes
ONwt.session_id=es.session_id
ANDes.is_user_process=1
GO
--Listusertasks
SELECT
t.session_id,t.request_id,t.exec_context_id,
t.scheduler_id,t.task_address,
t.parent_task_address
FROMsys.dm_os_tasksASt
JOINsys.dm_exec_sessionsASes
ONt.session_id=es.session_id
ANDes.is_user_process=1
GO
Figure19-21.Resultsofsys.dm_os_waiting_tasks
Theresultsshowthatprocess54isindeedwaiting;thewaittypeiswritelog,which
meanstheI/Otothelogfilesisslow.Whenyoucorrelatethistosession_id98,which
istheshrinkdatabasetask,youcanidentifythattherootcausefortheperformance
issuewiththeinsertqueryistheshrinkdatabaseprocess.Oncethe
shrinkdatabaseoperationcompletes,theinsertquerystartstoprocess,asshownin
Figure19-22.
Figure19-22.ResultsofaDMVtoshowtheblockingthread
Notallwaittypesneedtobemonitoredconstantly.Somewaittypes,likebroker_*
andclr_*,canbeignoredifyouaren’tusingaservicebrokerorCLRinyourdatabases.
Thisexampleonlytouchedthetipoftheiceberg;waitscanbeapowerfulmechanismto
helpyouidentifyandresolvequeryperformanceissues.
Figure19-23.NewXEventssession
ExtendedEvents
ExtendedEvents(XEvents)isadiagnosticsystemthatcanhelpyoutroubleshoot
performanceproblemswithSQLServer.ItwasfirstintroducedinSQL2008andthen
wentthroughacompletemakeoverinSQLServer2012,withadditionaleventtypes,a
newuserinterface,andtemplatessimilartoSQLServerProfiler.Let’sreviewtheXEvents
userinterfacefirstandthenlookathowyoucantroubleshootwithit.
TheXEventsuserinterfaceisintegratedwithManagementStudio;thereisaseparate
ExtendedEventsnodeinthetree.TostartanewXEventssession,expandthe
ManagementnodeandthenexpandExtendedEvents.Right-clickSessions,andthenclick
NewSession.Figure19-23showstheXEventsuserinterface.
XEventsoffersarichdiagnosticframeworkthatishighlyscalableandletsyoucollect
smallorlargeamountsofdatatotroubleshootperformanceissues.Ithasthesame
capabilitiesasSQLProfiler;so,youmayask,whyshouldyouuseXEventsandnotSQL
Profiler?AnybodywhohasworkedwithSQLServercantellyouthatSQLProfileradds
significantresourceoverheadwhentracingtheserver,whichcansometimesbringthe
servertoitsknees.ThereasonforthisoverheadisthatwhenyouuseSQLProfilertotrace
activitiesontheserver,alltheeventsarestreamedtotheclientandfilteredbasedon
criteriasetbyyouontheclientside;manyresourcesarerequiredtoprocesstheevents.
WithXEvents,filteringhappensontheserverside,soeventsthatareneededaren’tsentto
theclient—henceyougetbetterperformancewithaprocessthatislesschatty.Another
reasontobeginusingXEventsisthatSQLProfilerhasbeenmarkedfordeprecation.
XEventssessionscanbebasedonpredefinedtemplates,oryoucancreateasessionby
choosingspecificevents.YoucanalsoautostartanXEventssessiononserverstartup—a
featurethatisn’tavailableinSQLProfiler.Figure19-24showstheautostartoption.
Figure19-24.ObjectExplorerdatabasetablepop-upcontextmenu
TheEventslibrarylistsallsearchableevents,categorizedandgroupedbasedon
events.Youcansearcheventsbasedontheirnamesand/ordescriptions.Onceyouselect
theeventsyouwanttotrack,youcansetfiltercriteria.Afterthefiltershavebeendefined,
youcanselectthefieldsyouwanttotrack.Thecommonfieldsthataretrackedare
selectedbydefault.Figure19-25showsasamplesessiontocaptureSQLstatementsfor
performancetuning.
Figure19-25.SampleXEventssessionconfigurationforSQLperformancetuning
Afteryou’vedefinedallthecriteria,youcansetthetargetdependingonwhatyou
wanttodowiththedata:captureittoafile,forwardittoin-memorytargets,orwriteitto
alivereader.Figure19-26showsthepossibletargetsforasession.
Figure19-27.SampledatafromtheXEventssessionforSQLperformancetuning
Nowlet’sconsideracommonproblem:abusinessuseriscomplainingthatan
applicationisslowandthereisalotofblocking.Youneedtofigureoutwherethe
problemis,giventhattheapplicationisthird-partysoftware.Thechallengeistoidentifya
pieceofapplicationfunctionalityandthequeriesbehindthisfunctionalitythatarecausing
theperformanceissue.Youhavemultipleareastoinvestigate,includingclients,network,
blocking,CPU,andI/Oissues.Onewaytoapproachtheproblemistorunthe
PerformanceMonitor(perfmon)tool,startaprofilertrace,andtrytotietheapplication
issuetotheservermetrics;butthereisnodirectwaytogetthedetailsonthequerychain
oftheleadblockerthatcausesandfollowstheblockingissuewithoutusingXEvents.
IftheapplicationisbuiltonthelatestODBCdriversorthenewADO.NET4.5,the
applicationwillattachaConnectionIdidentifier.Thisisaguidetotheserverwhenthe
connectionsaremade,whichmakestheprocessoftracingorcorrelatingactivitiesbetween
clientandservermuchsimpler.Alongwiththis,theclientsendsanotheridentifiercalled
ActivityId,whichprovidesinformationabouttheprocessthatiscurrentlyexecuting.
WithConnectionIdandActivityId,youhavetheinformationrequiredtobuilda
completeimageoftheactivitiestakingplaceintheserver;andyoucaneffectivelytrace
theserveractivitiestoidentifythebottlenecks.
XEventsmakescommonproblemslikepagesplitsorlockingmucheasiertoidentify
andresolvewithpropercodechanges.Totrackpagesplits,youcansetupanXEvents
sessionusingascriptlikethatshowninListing19-21.
Listing19-21.XEventsSessionScripttoTroubleshootLoginTimeouts
CREATEEVENTSESSION[Troubleshootpagesplit]ONSERVER
ADDEVENTsqlserver.page_split(
ACTION(sqlserver.client_app_name,sqlserver.database_id,sqlserver.database_name,sqlserver.plan_handle,sqlserver.server_instance_name,sqlserver.server_principal_name,sqlserver.server_principal_sid,sqlserver.session_id,sqlserver.session_nt_username,sqlserver.sql_text,sqlserver.transaction_id,sqlserver.username)),
ADDEVENTsqlserver.rpc_completed(
ACTION(sqlserver.client_app_name,sqlserver.database_id,sqlserver.database_name,sqlserver.plan_handle,sqlserver.server_instance_name,sqlserver.server_principal_name,sqlserver.server_principal_sid,sqlserver.session_id,sqlserver.session_nt_username,sqlserver.sql_text,sqlserver.transaction_id,sqlserver.username)),
ADDEVENTsqlserver.rpc_starting(
ACTION(sqlserver.client_app_name,sqlserver.database_id,sqlserver.database_name,sqlserver.plan_handle,sqlserver.server_instance_name,sqlserver.server_principal_name,sqlserver.server_principal_sid,sqlserver.session_id,sqlserver.session_nt_username,sqlserver.sql_text,sqlserver.transaction_id,sqlserver.username)),
ADDEVENTsqlserver.sp_statement_completed(
ACTION(sqlserver.client_app_name,sqlserver.database_id,sqlserver.database_name,sqlserver.plan_handle,sqlserver.server_instance_name,sqlserver.server_principal_name,sqlserver.server_principal_sid,sqlserver.session_id,sqlserver.session_nt_username,sqlserver.sql_text,sqlserver.transaction_id,sqlserver.username)),
ADDEVENTsqlserver.sp_statement_starting(
ACTION(sqlserver.client_app_name,sqlserver.database_id,sqlserver.database_name,sqlserver.plan_handle,sqlserver.server_instance_name,sqlserver.server_principal_name,sqlserver.server_principal_sid,sqlserver.session_id,sqlserver.session_nt_username,sqlserver.sql_text,sqlserver.transaction_id,sqlserver.username))
ADDTARGETpackage0.event_file(SET
filename=N'C:\Temp\Troubleshootpagesplit.xel')
WITH(MAX_MEMORY=4096
KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=30
SECONDS,MAX_EVENT_SIZE=0
KB,MEMORY_PARTITION_MODE=NONE,TRACK_CAUSALITY=OFF,STARTUP_STATE=OFF)
GO
NowyoucanstarttheXEventssessioncreatedinListing19-21andbeginidentifying
thequeriesandthesessiondetailsthatcausethesepagesplits.Thiswillhelpyounarrow
downtheissueveryquicklyandtroubleshootthecause.
Summary
SQLServerstoresdatain8KBpagesthatitallocatesincontiguousgroupsof8pages
each,whichareknownasextents.Inaperfectworld,SQLServer’slogicalandphysical
storagemechanismswouldn’tmakeadifferencetoyouasadeveloper.Intherealworld,
however,anunderstandingofstorageengineoperationisimportantformaximizing
performance.ThischapterbegananoverviewoftheSQLServerstorageengineandhow
itaffectsperformance.
IndexesaretheprimarymeansofincreasingqueryperformanceonSQLServer.We
continuedthediscussionbyaddressingtheconceptsofheaps,clusteredindexes,and
nonclusteredindexes,withdetailsofhoweachaffectstheoverallperformanceofyour
queriesandDMLstatements.
Optimizingqueriesdependsonmaximizingtwocriticalaspects:responsetimeand
throughput.SQLServerprovidesqueryplansandstatistics,inadditiontootherexternal
tools,tohelpdiagnoseperformanceissues.Thechapterwrappedupwithasuggested
methodologyfordealingwithperformanceissues.Usingamethodologyliketheeight-
stepprocessdescribedherecanhelpyouquicklynarrowdownthesourceofperformance
issues;define,evaluate,andimplementsolutions;andtakeaproactiveapproachin
addressingfutureperformance-relatedissues.
UsingtroubleshootingtechniquessuchaswaitstatsandDMVscanhelpyoulocate
performanceissuesandprovideinformationyoucanusetoderiveacompletepictureof
whatisgoingoninthesystem.Combiningthiswithahigh-performanceevent-monitoring
infrastructuresuchasExtendedEventsgivesyouproactivecapabilitiesformonitoring
serverssoyoucanidentifyissuesandresolvetheminatimelyfashion.
Wehopeyou’veenjoyedreadingthisbookasmuchaswe’veenjoyedbringingitto
you.WewishyouallthebestinyourT-SQLdevelopmenteffortsandhopeyoufindthis
bookhelpfulinyourdevelopmentendeavors.
Exercises
1. [Chooseallthatapply]SQLServer2014useswhichofthe
followingtypesoffilestostoredatabaseinformation?
a. Datafiles(.mdfextension)
b. Transactionlogfiles(.ldfextension)
c. Additionaldatafiles(.ndfextension)
d. Richtextfiles(.rtfextension)
2. [True/False]In-MemorytablesarecreatedinthedefaultPRIMARY
filegroup.
3. [True/False]SQLServerstoresdatain8KBstorageunitsknownas
pages.
4. [Chooseone]Eightcontiguous8KBpagesofstorageinSQL
Serverareknownaswhichofthefollowing?
a. Afilegroup
b. Achunk
c. Anextent
d. Afile
5. [Fillintheblank]Aheapisan_________collectionofdatapages.
6. [Fillintheblank]Clusteredindexesandnonclusteredindexesare
managedbySQLServeras_______________structures.
7. [Fillintheblank]_______________sessionscanbeusedtotrace
waits.
8. [Chooseone]Anoptimizednonclusteredindexisa{filtered|
parameterized|unsorted}index
9. [Chooseallthatapply]SQLServerperformanceismeasuredusing
whichofthefollowingterms?
a. Throughput
b. Luminescence
c. Responsetime
d. Alloftheabove
APPENDIXA
ExerciseAnswers
Thisappendixcontainstheanswerstotheexercisesattheendofeachchapter.The
answersaregroupedbychapterandnumberedtomatchtheassociatedexercisesinthe
correspondingchapter.
Chapter1
1. Imperativelanguagesrequireyoutoprovidethecomputerwith
step-by-stepdirectionstoperformatask—essentially,youtellthe
computerhowtoachievetheendresult.Declarativelanguages
allowyoutotellthecomputerwhattheendresultshouldbeand
trustthecomputertotakeappropriateactiontoachieveit.
2. ACIDstandsfor“atomicity,consistency,isolation,durability.”
Theserepresentthebasicpropertiesofadatabasethatguarantee
reliabilityofdatastorage,processing,andmanipulations.
3. ThesevenindextypesthatSQLServersupportsareclustered
indexes;nonclusteredindexes;XMLindexes;spatialindexes;full-
textindexes;andtwoin-memorytableindextypes,nonclustered
hashindexandmemory-optimizednonclusteredindex.
4. AllofthefollowingarerestrictionsonallSQLServerUDFs:(1)
theycannotperformDMLorDDLstatements,(2)theycannot
changethestateofthedatabase(nosideeffects),(3)theycannot
usedynamicSQL,and(4)theycannotutilizecertain
nondeterministicfunctions.
5. False.AllnewlydeclaredvariablesaresettoNULLoncreation.
Youshouldalwaysinitializenewlycreatedvariablesimmediately
aftercreation.
Chapter2
1. SSDTisanintegratedproject-orienteddevelopmentenvironment
fordatabaseandapplicationdevelopment.SSDTisthereplacement
forBusinessIntelligenceDevelopmentStudio(BIDS).
2. ThecorrectanswersareA,B,C,andD.SQLServer2014SSMS
providesintegratedObjectExplorer,IntelliSense,codesnippets,
andacustomizablekeyboardmappingscheme.
3. SSISisconsideredanETL(extract,transform,load)tool.
4. True.SQLCMDscriptingvariablescanbesetviacommand-line
optionsandenvironmentvariables,andinscriptviatheSQLCMD
:setvarcommand.
5. ThecorrectanswerisD,AlloftheAbove.BCPcangenerateformat
filesthatcanbeusedwiththeSSISBulkInserttask,withtheT-
SQLBULKINSERTstatement,orwithBCPitself.BCPcanalso
importdataintotableswithoutaformatfileandexportdatafroma
tabletoafile.
6. YoucanqueryExtendedEventstracefilesdirectly.WithaSQL
Profilertrace,youhavetoloadthecapturedtracedatatoatableand
thenqueryit.DirectqueryingagainstProfilertracedataisnot
supported.
7. SQLServer2005,SQLServer2008,SQLServer2008R2,SQL
Server2012,SQLServer2014,andSQLAzure.
Chapter3
1. True.SQL3VLsupportsthethreeBooleanresultstrue,false,and
unknown.
2. ThecorrectanswerisA.InSQL,NULLrepresentsanunknownora
missingvalue.NULLdoesnotrepresentanumericvalueof0ora
zero-lengthstring.
3. False.SQL’sBEGIN…ENDconstructdefinesastatementblockbut
doesnotlimitthescopeofvariablesdeclaredwithinthestatement
block.ThisiscontrarytothebehaviorofC#’scurlybraces({}).
4. TheBREAKstatementforcesaWHILElooptoterminate
immediately.
5. False.TRY…CATCHcan’tcapturesyntaxerrors,errorsthatcausea
brokenconnection,orerrorswithseverityof10orless,among
others.
6. SQLCASEexpressionscomeinbothsimpleandsearchedCASE
expressionforms.
7. ThecorrectanswersareAandB.T-SQLprovidessupportforread-
onlycursorsandforward-onlycursors.Thereisnosuchthingasa
backward-onlycursororawrite-onlycursor.
8. ThefollowingcodemodifiestheexampleinListing3-10toreturn
thetotalsales(TotalDue)byregioninpivot-tableformat.The
requiredchangetothecodeisshowninbold:
--DeclarevariablesDECLARE@sql
nvarchar(4000);
DECLARE@temp_pivottable(
TerritorylDintNOTNULLPRIMARYKEY,
CountryRegionnvarchar(20)NOTNULL,
CountryRegionCodenvarchar(3)NOTNULL
);
--Getcolumnnamesfromsourcetablerows
INSERTINTO@temp_pivot(TerritorylD,
CountryRegion,
CountryRegionCode)SELECTTerritorylD,
Name,
CountryRegionCodeFROMSales.SalesTerritory
GROUPBYTerritorylD,Name,
CountryRegionCode;
--GeneratedynamicSOLquerySET@sql
=N'SELECT'+SUBSTRING(
(
SELECTN',SUM(CASEWHENt.TerritoryID='
+CAST(TerritoryIDASNVARCHAR(3))+
N'THENsoh.TotalDueELSE0END)AS'
+QUOTENAME(CountryRegion)AS"*"
FROM@temp_pivot
FORXMLPATH('')),2,4000)+
N'FROMSales.SalesOrderHeadersoh'+
N'INNERJOINSales.SalesTerritoryt'+
N'ONsoh.TerritoryID=t.TerritoryID;';
--PrintandexecutedynamicSQLPRINT@sql;
EXEC(@sql);
Chapter4
1. SQLServersupportsthreetypesofT-SQLUDFs:scalarUDFs,
multistatementTVFs,andinlineTVFs.
2. True.TheRETURNSNULLONNULLINPUToptionisa
performance-enhancingoptionthatautomaticallyreturnsNULLif
anyoftheparameterspassedintoascalarUDFareNULL.
3. False.TheENCRYPTIONoptionperformsasimplecode
obfuscationthatiseasilyreverse-engineered.Infact,several
programsandscriptsareavailableonlinethatallowanyoneto
decryptyourcodewiththepushofabutton.
4. ThecorrectanswersareA,B,andD.MultistatementTVFs(aswell
asallotherTVFs)donotallowyoutoexecutePRINTstatements,
callRAISERROR,orcreatetemporarytables.Inmultistatement
TVFs,youcandeclaretablevariables.
5. ThefollowingcodecreatesadeterministicscalarUDFthataccepts
afloatparameter,convertsitfromdegreesFahrenheittodegrees
Celsius,andreturnsafloatresult.NoticethattheWITH
SCHEMABINDINGoptionisrequiredtomakethisscalarUDF
deterministic:
CREATEFUNCTIONdbo.FahrenheitToCelsius
(@Degreesfloat)
RETURNSfloat
WITHSCHEMABINDING
AS
BEGIN
RETURN(@Degrees-32.0)*(5.0/9.0);END;
Chapter5
1. False.TheSPRETURNstatementcanreturnonlyanintscalar
value.
2. OnemethodofprovingthattwoSPsthatcalleachotherrecursively
arelimitedto32levelsofrecursionintotalisshownhere.
Differencesfromthecodeintheoriginallistingareshowninbold:
CREATEPROCEDUREdbo.FirstProc(@iint)
AS
BEGIN
PRINT@i;
SET@i+=l;
EXECdbo.SecondProc@i;
END;GO
CREATEPROCEDUREdbo.SecondProc(@iint)
AS
BEGIN
PRINT@i;
SET@i+=1;
EXECdbo.FirstProc@i;END;GO
EXECdbo.FirstProc1;
3. ThecorrectanswerisD.Table-valuedparametersmustbedeclared
READONLY.
4. ThecorrectanswersareAandB.Youcanusethesprecompile
systemSPortheWITHRECOMPILEoptiontoforceSQLServer
torecompileanSP.FORCERECOMPILEandDBCC
RECOMPILEALLSPSarenotvalidoptions/statements.
Chapter6
1. ThecorrectanswersareAandB.DeveloperEdition,Enterprise
Edition,andEvaluationEditionofthesoftwaresupportthenewin-
memoryfeatures.
2. False.BIN2collationonastringdatatypecolumnisnecessaryonly
ifitisbeingusedinanindexoranORDERBYclause.
3. ThecorrectanswersisC,rangeindex.Thereisnoconceptofa
clusteredindexonanin-memorytable,andhashindexesarebest
suitedforsingle-itempointlookups.
4. True.Bydefault,ifthedurabilityoptionforamemory-optimized
tableisnotspecified,itdefaultstodurable(SCHEMA_AND_DATA).
5. False.Allmemory-optimizedtablesrequireanindex,butonly
tablesthataredurable(SCHEMA_AND_DATAoption)requirea
primarykeyconstraint.
6. ThecorrectanswersareA,B,andC.ExecuteasOwner,Self,and
Userarevalidexecutioncontexts.Theonlylistedexecutioncontext
thatisnotvalidisEXECUTEASCALLER.Thisexecutioncontext
doesnotallowSQLServertohardcodeexecutionrightsatthetime
thestoredprocedureiscompiled.
Chapter7
1. True.InDDLtriggers,theEVENTDATAfunctionreturns
informationabouttheDDLeventthatfiredthetrigger.
2. True.InaDMLtrigger,anUPDATEeventistreatedasaDELETE
followedbyanINSERT,soboththedeletedandinserted
virtualtablesarepopulatedforUPDATEevents.
3. False.DMLtriggersarenotavailableforSQLServer2014in-
memorytables.
4. ThecorrectanswersareA,C,andE.SQLServer2014supports
logontriggers,DDLtriggers,andDMLtriggers.
5. TheSETNOCOUNTONstatementpreventsextraneousrows
affectedmessages.
6. ThecorrectanswerisA.TheCOLUMNSUPDATEDfunctionreturns
avarbinarystringwithbitssettorepresentaffectedcolumns.
7. True.@@R0WC0UNTatthebeginningofatriggerreturnsthe
numberofrowsaffectedbytheDMLstatementthatfiredthe
trigger.
8. False.YoucannotcreateanyAFTERtriggersonaview.
Chapter8
1. True.Symmetrickeyscanbeusedtoencryptdataorother
symmetrickeys.
2. ThecorrectanswersareA,B,andE.SQLServer2012provides
nativesupportforDES,AES,andRC4encryption.Althoughthe
LokiandBlowfishalgorithmsarerealencryptionalgorithms,SQL
Serverdoesnotprovidenativesupportforthem.
3. False.SQLServer2014T-SQLprovidesnoBACKUP
ASYMMETRICKEYstatement.
4. YoumustturnontheEKMprovider-enabledoptionwith
spconfiguretoactivateEKMonyourserver.
5. False.TDEautomaticallyencryptsthetempdbdatabase,butit
doesnotencryptthemodelandmasterdatabases.
6. True.SQLServerautomaticallygeneratesrandominitialization
vectorswhenyouencryptdatawithsymmetricencryption.
Chapter9
1. True.WhenaCTEisnotthefirststatementinabatch,the
statementprecedingitmustendwithasemicolonstatement
terminator.
2. ThecorrectanswersareA,B,andD.RecursiveCTEsrequirethe
WITHkeyword,ananchorquery,andarecursivequery.SQLServer
doesnotsupportanEXPRESSIONkeyword.
3. TheMAXRECURSIONoptioncanacceptavaluebetween0and
32767.
4. ThecorrectanswerisE,AlloftheAbove.SQLServersupportsthe
ROWNUMBER,RANK,DENSE_RANK,andNTILEfunctions.
5. False.YoucannotuseORDERBYwiththeOVERclausewhenused
withaggregatefunctions.
6. True.WhenPARTITIONBYandORDERBYarebothusedinthe
OVERclause,PARTITIONBYmustappearfirst.
7. ThenamesofallcolumnsreturnedbyaCTEmustbeunique.
8. ThedefaultframingclauseisRANGEBETWEENUNBOUNDED
PRECEDINGANDCURRENTROW.
9. True.WhenOrderbyisnotspecified,thereisnostartingor
endingpointfortheboundary.So,theentirepartitionisusedforthe
windowframe.
Chapter10
1. False.EuropeanlanguageaccentsareincludedintheANSI-
encodedcharacters.YouneedUnicodefornon-Latincharacters.
2. ThecorrectanswersareA,C,andD.imageand(n)texthave
beendeprecatedsinceSQLServer2005.
3. False.Thedatedatatypedoesnotstoretimezoneinformation.
Usethedatetimeoffsetdatatypeifyouneedtostoretime
zoneinformationwithyourdate/timedata.
4. Thehierarchyiddatatypeusesthematerializedpathmodelto
representhierarchiesinthedatabase.
5. ThecorrectanswerisB.Thegeographydatatyperequires
Polygonobjectstohaveacounterclockwiseorientation.Also,
spatialobjectscreatedwiththegeographydatatypemustbe
containedinasinglehemisphere.
6. ThecorrectanswerisB.TheSWITCHOFFSETfunctionadjustsa
givendatetimeoffsetvaluetoanotherspecifiedtimeoffset.
7. True.FILESTREAMfunctionalityutilizesNTFSfunctionalityto
providestreamingBLOBdatasupport.
8. Thecolumnisnamedpath_locator.Itisahierarchyid
typecolumn.
Chapter11
1. True.Stoplistsandfull-textindexesarestoredinthedatabase.
2. ThecorrectanswerisC.Youcancreateafull-textindexusingthe
wizardinSSMSortheT-SQLCREATEFULLTEXTINDEX
statement.
3. TheFREETEXTpredicateautomaticallyperformswordstemming
andthesaurusreplacementsandexpansions.
4. Stoplistscontainstopwords,whicharewordsthatareignored
duringfull-textquerying.
5. True.Thesys.dmftsparserdynamic-managementfunction
showstheresultsproducedbywordbreakingandstemming.
Chapter12
1. ThecorrectanswersareA,B,C,andD.TheSQLServerFORXML
clausesupportstheFORXMLRAW,FORXMLPATH,FORXML
AUTO,andFORXMLEXPLICITmodes.FORXML
RECURSIVEisnotavalidFORXMLmode.
2. OPENXMLreturnsresultsinedgetableformatbydefault.
3. True.Thexmldatatypequery()methodreturnsresultsas
untypedxmlinstances.
4. ThecorrectanswerisC.ASQLServerprimaryXMLindexstores
xmldatatypecolumnsinapreshreddedrelationalformat.
5. True.Whenyouhaven’tdefinedaprimaryXMLindexonanxml
datatypecolumn,performingXQueryqueriesagainstthecolumn
causesSQLServertoperformon-the-flyshreddingofyourXML
data.Thiscanresultinasevereperformancepenalty.
6. True.AdditionalXMLfunctionality,availablethroughthe.NET
Framework,canbeaccessedviaSQLServer’sSQLCLR
integration.
Chapter13
1. True.TheFORXMLPATHclausesupportsasubsetoftheW3C
XPathrecommendationforexplicitlyspecifyingyourXMLresult
structure.
2. ThecorrectanswerisA.Theatsign(@)isusedtoidentifyattribute
nodesinbothXPathandXQuery.
3. Thecontextitem(indicatedbyasingleperiod)specifiesthecurrent
nodeorscalarvaluebeingaccessedatanygivenpointintime
duringqueryexecution.
4. ThecorrectanswersareA,B,andD.YoucandeclareXML
namespacesforSQLServerXQueryexpressionswiththeWITH
XMLNAMESPACESclause,thedeclaredefaultelement
namespacestatement,orthedeclarenamespacestatement.
ThereisnoCREATEXMLNAMESPACEstatement.
5. InXQuery,youcandynamicallyconstructXMLviadirect
constructorsorcomputedconstructors.
6. True.SQLServer2014supportsallfiveclausesofFLWOR
expressions:for,let,where,orderby,andreturn.Note
thatSQLServer2005didnotsupporttheletclause.
7. _SCcollationenablesSQLServertobeUTF-16aware.
8. ThecorrectanswersareB,C,andD.XQueryprovidesthreetypes
ofcomparisonoperators:generalcomparisonoperators,node
comparisonoperators,andvaluecomparisonoperators.
Chapter14
1. Metadatais“datathatdescribesdata.”
2. Catalogviewsprovideinsightintodatabaseobjectsandserver-wide
configurationoptions.
3. ThecorrectanswerisB.Manycatalogviewsaredefinedusingan
inheritancemodel.Intheinheritancemodel,catalogviewsinherit
columnsfromothercatalogviews.Somecatalogviewsarealso
definedastheunionoftwoothercatalogviews.
4. True.Dynamic-managementviewsandfunctionsprovideaccessto
internalSQLServerdatastructuresthatwouldbeotherwise
inaccessible.DMVsandDMFspresenttheseinternaldatastructures
inrelationaltabularformat.
5. ThecorrectanswersareAandC.INFORMATION_SCHEMAviews
providetheadvantagesofISOSQLstandardcompatibilityand,asa
consequence,cross-platformcompatibility.
Chapter15
1. True.TheSystem.Data.SqlClientnamespaceprovides
supportfortheSQLServerNativeClientlibrary,whichprovides
optimizedaccesstoSQLServer.
2. ThecorrectanswerisB.Disconnecteddatasetscacherequireddata
locallyandallowyoutoconnecttoadatabaseonlyasneeded.
3. ThecorrectanswersareAandC.Thebenefitsofquery
parameterizationincludeprotectionagainstSQLinjectionattacks
andincreasedefficiencythroughqueryplanreuse.
4. False.WhenyouturnonMARS,youcanopentwoormoreresult
setsoverasingleopenconnection.MARSrequiresonlyoneopen
connection.
5. True.VisualStudioprovidesavisualO/RMdesignerwithadrag-
and-dropinterface.
6. ThecorrectanswerisD.LINQtoSQLusesdeferredquery
execution,meaningitdoesnotexecuteyourqueryuntilthedata
returnedbythequeryisactuallyneeded.
Chapter16
1. ThecorrectanswersareA,B,C,D,andE.SQLServer2014
providessupportforSQLCLRUDFs,UDAs,UDTs,SPs,and
triggers.
2. False.SQLServer2014expandsthelimitonMaxByteSizefor
UDAsandUDTstomorethan2billionbytes.InSQLServer2005,
therewasan8,000-bytelimitonthesizeofUDAsandUDTs.
3. ThecorrectanswerisD.SAFEpermissionsallowSQLCLRcode
toexecutemanaged.NETcode.EXTERNALACCESSpermissions
arerequiredtowritetothefilesystem,accessnetworkresources,
andreadthecomputer’sregistry.
4. True.SQLCLRUDAsandUDTsmustbedeclaredwiththe
Serializableattribute.
5. ASQLCLRUDAthatisdeclaredasFormat.UserDefined
mustimplementtheIBinarySerializeinterface.
6. ThecorrectanswersareA,C,D,andE.ASQLCLRUDAis
requiredtoimplementthefollowingmethods:Init,Terminate,
Merge,andAccumulate.TheAggregatemethodisnota
requiredmethodforUDAs.
Chapter17
1. False.ALocalDBinstancecannotrunasaservice.
2. False.YoucanaccessXMLcolumnsfromLinuxbyusingthe
MicrosoftODBCdriverforLinux.
3. False.HTTPSOAPendpointsweredeprecatedinSQLServer2008.
4. VisualStudio2010and2012providestheASP.NETWebService
templateforcreatingnewwebservices.
5. True.VisualStudioincludesabuilt-ingraphicalEDMdesigner
beginningwithSP1.
6. ThecorrectanswerisC.WCFDataServicesacceptsREST-style
queriesinrequests.
Chapter18
1. The@@errorsystemfunctionautomaticallyresetsto0afterevery
successfulstatementexecution.
2. ThecorrectanswerisD.TheERROR_SEVERITY()function,
availableonlyintheCATCHblockinSQLServer,returnsthe
severityleveloftheerrorthatoccurred.
3. True.TheRAISERRORstatementallowsyoutoraiseerrorsinSQL
Server.
4. True.VisualStudioprovidesintegrateddebuggingofT-SQL
functionsandSPs.UsingVisualStudio,youcanstepintoT-SQL
codeandsetbreakpoints.
5. ThecorrectanswersareAandB.Thepotentialproblemswith
dynamicSQLincludeperformanceissuescausedbylackofquery
planreuse,andexposuretoSQLinjectionattacks.
Chapter19
1. ThecorrectanswersareA,B,andC.SQLServer2014usesdata
fileswithan.mdfextension,transactionlogfileswithan.ldf
extension,andadditionaldatafileswithan.ndfextension.
2. False.In-memoryoptimizedtablesmustbecreatedinamemory-
optimizedfilegroup,specifiedbytheCONTAINS
MEMORY_OPTIMIZED_DATAsyntaxwhencreatingthefilegroup.
3. True.SQLServerstoresdatain8KBstorageunitsknownaspages.
4. ThecorrectanswerisC.Eightcontiguous8KBpagesofstoragein
SQLServerareknownasanextent.
5. Aheapisanunorderedcollectionofdatapages.
6. ClusteredindexesandnonclusteredindexesaremanagedbySQL
ServerasB-treestructures.
7. ExtendedEventssessionscanbeusedtotracewaits.
8. Anoptimizednonclusteredindexiscalledafilteredindex.
9. ThecorrectanswersareAandC.SQLServerperformanceis
measuredintermsofthroughputandresponsetime.
BaseTypes
xs:anySimpleType Basetypeforallsimplebuilt-intypes.
xs:anyType Basetypeforxs:anySimpleTypeandcomplexbuilt-intypes.
Date/TimeTypes
xs:date
RepresentsaGregoriancalendar–baseddatevalueexactlyonedayin
length,intheformatyyyy-mm-dd[time_offset].time_offset
canbeacapitalZforzero-meridian(UTC)orintheformat+/-hh:mmto
representaUTCoffset.Anexampleofavalidxs:dateis2006-12-
25Z,whichrepresentsDecember25,2006,UTCtime.
xs:dateTime
RepresentsaGregoriancalendar–baseddateandtimevaluewithprecision
to1/1000thofasecond.Theformatisyyyy-mm-ddThh:
mm:ss.sss[time_offset].Timeisspecifiedusinga24-hourclock.
Aswithxs:date,time_offsetcanbeacapitalZ(UTC)oraUTC
offsetintheformat+/-hh:mm.Avalidxs:dateTimevalueis2006-
10-30T13:00:59.500-05:00,whichrepresentsOctober30,2006,
1:00:59.5PM,USEasternStandardtime.UnlikeinSQLServer2005,in
SQLServer2012thexs:dateTimetypemaintainsthetimezone
informationyouassigninsteadofautomaticallyconvertingalldate/time
valuestoasingletimezone.Thetimezonealsoisn’tmandatoryinSQL
Server2012.
xs:duration
RepresentsaGregoriancalendar–basedtemporal(time-based)duration,
usingtheformatPyyyyYmmMddDThhHmmMss.sssS.
P0010Y03M12DT00H00M00.000S,forinstance,represents10years,3
months,12days.
xs:gDay
RepresentsaGregoriancalendar–basedday.Theformatis
dd[time_offset](noticethethreeprecedinghyphen[-]characters).
Thetime_offsetisoptional.Avalidxs:gDayvalueis09Z,which
standsfortheninthdayofthemonth,UTCtime.
xs:gMonth
RepresentsaGregoriancalendar–basedmonth.Theformatis—
mm[time_offset](noticethetwoprecedinghyphencharacters).
time_offsetisoptional.Avalidxs:gMonthvalueis-12,which
standsforDecember.
xs:gMonthDay
RepresentsaGregoriancalendar–basedmonthandday.Theformatis—
mm-dd[time_offset](noticethetwoprecedinghyphens).The
time_offsetisoptional.Avalidxs:gMonthDayvalueis—02-29
forFebruary29.
xs:gYear
RepresentsaGregoriancalendar–basedyear.Theformatis
yyyy[time_offset].Thetime_offsetisoptional.Theyearcan
alsohaveaprecedinghyphencharacterindicatinganegative(BCE
—“beforetheChristianEra”)yearasopposedtoapositive(CE
—“ChristianEra”)date.Avalidxs:gYearvalueis-0044for44BCE.
Noticethatallfourdigitsarerequiredintheyearrepresentation,evenfor
yearsthatcanbenormallyrepresentedwithfewerthanfourdigits.
xs:gYearMonth
RepresentsaGregoriancalendar–basedyearandmonth.Theformatis
yyyy-mm[time_offset].Thetime_offsetisoptionalandcanbe
ZoraUTCoffset.Avalidxs:gYearMonthvalueis2001-01for
January2001.
xs:time
Representsatimevaluewithprecisionto1/1000thofasecond,usinga24-
hourclockrepresentation.Theformatishh:mm:ss.sss
[time_offset].Aswithothertemporaldatatypes,time_offset
canbeZ(UTC)oraUTCoffsetintheformat+/-hh:mm.Avalid
xs:timevalueis23:59:59.000-06:00,whichrepresents11:59:59
PM,USCentralStandardtime.Thecanonicalrepresentationofmidnightin
24-hourformatis00:00:00.
BinaryTypes
xs:base64Binary
RepresentsBase64-encodedbinarydata.Base64-encodingsymbolsare
definedinRFC2045(www.ietf.org/rfc/rfc2045.txt)asA
throughZ,athroughz,0through9,+,/,andthetrailing=sign.
Whitespacecharactersarealsoallowed,andlowercaselettersare
considereddistinctfromuppercaseletters.Anexampleofavalid
xs:base64Binaryvalueis
QVByZXNzIEJvb2tzIEFuZCBTUUwgU2VydmVyIDIwMDU=.
xs:hexBinary
Representshexadecimal-encodedbinarydata.Thesymbolsdefinedfor
encodingdatainhexadecimalformatare0through9,AthroughF,anda
throughf.Upper-andlowercaselettersAthroughFareconsidered
equivalentbythisdatatype.Anexampleofavalidxs:hexBinaryvalue
is6170726573732E636F6D.
BooleanType
xs:Boolean RepresentsaBooleanbinarytruthvalue.Thevaluessupportedaretrue
(1)andfalse(0).Anexampleofavalidxs:booleanvalueistrue.
NumericTypes
xs:byte Representsan8-bitsignedintegerintherange-128to+127.
xs:decimal
Representsanexactdecimalvalueupto38digitsinlength.Thesenumbers
canhaveupto28digitsbeforethedecimalpointandupto10digitsafter
thedecimalpoint.Avalidxs:decimalvalueis8372.9381.
xs:double
Representsadouble-precisionfloating-pointvaluepatternedaftertheIEEE
standardforfloating-pointtypes.Therepresentationofvaluesissimilarto
xs:floatvaluesnE[+/-]e,wherenisthemantissafollowedbythe
letterEoreandanexponente.Therangeofvalidvaluesforxs:double
isapproximately-1.79E+308to-2.23E-308fornegativenumbers,0,
and+2.23E-308to+1.79E+308forpositivenumbers.
xs:float
Representsanapproximatesingle-precisionfloatingpointvalueperthe
IEEE754-1985standard.TheformatforvaluesofthistypeisnEe,where
nisadecimalmantissafollowedbytheletterEoreandanexponent.The
valuerepresentsn·10e.Therangeforxs:floatvaluesisapproximately
-3.4028e+38to-1.401298E-45fornegativenumbers,0,and
+1.401298E-45to+3.4028e+38forpositivenumbers.Thespecial
values-INFand+INFrepresentnegativeandpositiveinfinity.SQL
Serverdoesn’tsupporttheXQuery-specifiedspecialvalueNaN,which
standsfor“notanumber.”Avalidxs:floatvalueis1.98E+2.
xs:int Representsa32-bitsignedintegerintherange-2147483648to
+2147483647.
xs:integer Representsanintegervalueupto28digitsinlength.Avalid
xs:integervalueis76372.
xs:long Representsa64-bitsignedintegerintherange
-9223372036854775808to+9223372036854775807.
xs:negativeInteger Representsanegativenonzerointegervaluederivedfromthe
xs:integertype.Itcanbeupto28digitsinlength.
xs:nonNegativeInteger Representsapositiveorzerointegervaluederivedfromthexs:integer
type.Itcanbeupto28digitsinlength.
xs:nonPositiveInteger Representsanegativeorzerointegervaluederivedfromthe
xs:integertype.Itcanbeupto28digitsinlength.
xs:positiveInteger Representsapositivenonzerointegervaluederivedfromthe
xs:integertype.Itcanbeupto28digitsinlength.
xs:short Representsa16-bitsignedintegerintherange-37268to+32767.
xs:unsignedByte Representsanunsigned8-bitintegerintherange0to255.
xs:unsignedInt Representsanunsigned32-bitintegerintherange0to+4294967295.
xs:unsignedLong Representsanunsigned64-bitintegerintherange0to
+18446744073709551615.
xs:unsignedShort Representsanunsigned16-bitintegerintherange0to+65535.
StringTypes
xs:ENTITIES Aspace-separatedlistofENTITYtypes.
xs:ENTITY EquivalenttotheENTITYtypefromtheXML1.0standard.Thelexical
spacehasthesameconstructionasanxs:NCName.
xs:ID EquivalenttotheIDattributetypefromtheXML1.0standard.Anxs:ID
valuehasthesamelexicalconstructionasanxs:NCName.
xs:IDREF RepresentstheIDREFattributetypefromtheXML1.0standard.The
lexicalspacehasthesameconstructionasanxs:NCName.
xs:IDREFS Aspace-separatedlistofIDREFattributetypes.
xs:language
Alanguageidentifierstringrepresentingnaturallanguageidentifiersas
specifiedbyRFC3066(www.ietf.org/rfc/rfc3066.txt).A
completelistoflanguagecodesismaintainedbytheIANAregistryat
www.iana.org/assignments/language-subtag-registry.
Languageidentifiersmustconformtotheregularexpressionpattern[a-
zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*.Anexampleofavalid
languageidentifieristlh,whichistheidentifierfortheKlingonlanguage.
AnXMLnamestring.AnamestringmustmatchtheXML-specified
productionforName.Perthestandard,aNamemustbeginwithaletter,an
underscore,oracolon,andmaythencontainacombinationofletters,
xs:Name numbers,underscores,colons,periods,hyphens,andvariousother
charactersdesignatedintheXMLstandardascombiningcharactersand
extenders.RefertotheXMLstandardatwww.w3.org/TR/2000/WD-
xml-2e-20000814#NT-Nameforspecificinformationaboutthese
additionalallowableNamecharacters.
xs:NCName Anoncolonizedname.Theformatforanxs:NCNameisthesameasfor
xs:Name,butwithoutcoloncharacters.
xs:NMTOKEN
AnNMTOKENtypefromtheXML1.0standard.Anxs:NMTOKENvalueis
composedofanycombinationofletters,numbers,underscores,colons,
periods,hyphens,andXMLcombiningcharactersandextenders.
xs:NMTOKENS Aspace-separatedlistofxs:NMTOKENvalues.
xs:normalizedString
AnXMLwhitespace-normalizedstring,whichisonethatdoesn’tcontain
thewhitespacecharacters#x9(tab),#xA(linefeed),and#xD(carriage
return).
xs:string AnXMLcharacterstring.
xs:token
AnXMLwhitespace-normalizedstringwiththefollowingadditional
restrictionson#x20(space)characters:itcanhavenoleadingortrailing
spaces,anditcan’tcontainanysequencesoftwospacecharactersinarow.
APPENDIXC
Glossary
ACID
Anacronymforatomicity,consistency,isolation,durability.Thesefourconceptsof
transactionaldatastores,includingSQLdatabases,ensuredataintegrity.
adjacencylistmodel
Therepresentationofallarcsoredgesofagraphasalist.InSQL,thisisoften
implementedasaself-referentialtableinwhicheachrowmaintainsapointertoitsparent
nodeinthegraph.
ADO.NETDataServices
AlsoknownasProjectAstoria.ADO.NETDataServicesprovidesmiddle-tiersupportfor
accessingSQLServerdatabasesthroughREST-stylequeriesandentitydatamodels
(EDMs).
anchorquery
Thenonrecursivequeryspecifiedinthebodyofacommontableexpression.
applicationprogramminginterface(API)
Awell-definedinterfaceprovidedbyanapplicationorservicetosupportrequestsand
communicationsfromotherapplications.
assembly
InSQLServer,a.NETassemblyisacompiledSQLCLRexecutableorDLL.
asymmetricencryption
Encryptionthatrequirestwodifferentkeys:onetoencryptdataandanothertodecryptit.
Themostcommonformofasymmetricencryptionispublickeyencryption,inwhichthe
twokeysaremathematicallyrelated.
atomic,list,anduniondatatypes
TheXMLSchema1.1Part2:DataTypesspecificationworkingdraft
(http://www.w3.org/TR/xmlschema11-2/)definesnobuilt-inuniondatatypes.
Atomicdatatypesareindivisibledatatypesthatderivefromthe
xs:anyAtomicTypetype.Examplesincludexs:boolean,xs:date,and
xs:integer.
listdatatypesareconstructedofsequencesofothertypes.
uniondatatypesareconstructedfromtheorderedunionoftwoormoredatatypes,ora
restrictedsubsetofadatatype.
axis
Specifierthatindicatestherelationshipbetweenthenodesselectedbythelocationstep
andthecontextnode.Examplesofaxisspecifiersincludechild,parent,and
ancestor.
BulkCopyProgram(BCP)
Acommand-lineutilitysuppliedwithSQLServerforthepurposeofquicklyloadinglarge
datasetsintotables.
catalogview
ViewthatreturnsaSQLServerdatabaseandserver-specificmetadata.
certificate
Anelectronicdocumentconsistingofanasymmetrickeywithadditionalmetadatasuchas
anexpirationdateandadigitalsignaturethatallowsittobeverifiedbyathirdpartylikea
certificateauthority(CA).
checkconstraint
Aconditionplacedonatablethatrestrictstherangeofvalidvaluesforoneormore
columns.
closed-worldassumption(CWA)
Alogicformalismstatingthatwhatisnotknowntobetrue,isfalse.SQLdatabasesviolate
theCWAthroughtheintroductionofNULLs.
clusteredindex
Anindexthatcontainsatable’srowdatainitsleaf-levelnodes.
comment
InXQuery,codethatisignoredduringprocessing.XQuerycommentsaredenotedbythe
(:and:)delimitersinXQueryqueries.TheyshouldnotbeconfusedwithXML
commentnodes,whicharedesignatedwith<!—and—>delimiters.
T-SQLallowssingle-linecommentsthatbeginwith—ormultilinecommentsenclosed
in/*and*/delimiters.
computedconstructor
AnalternativewaytocreateXMLnodesbyspecifyingthetypeofnodetobecreated
throughtheuseofspecialkeywords.
contentexpression
Partofacomputedconstructor,enclosedinbraces,thatgeneratesXMLnodecontent.
contextitemexpression
Anexpressionthatevaluatestothecontextnode.
contextnode
Thenodecurrentlybeingprocessed.Eachnodeofeachset/sequencereturnedbyastepin
alocationpathisusedinturnasacontextnode.Subsequentstepsdefinetheiraxesin
relationtothecurrentcontextnode.Forinstance,withthesampleXPathexpression
/Root/Person/Address,theRootnodeisthefirstcontextnode.AllPerson
nodesreturnedbelowRootbecomethecontextnodeinturn,andtheAddressnodesare
retrievedrelativetothesecontextnodes.
databaseencryptionkey
AnencryptionkeyusedbyTransparentDataEncryptiontoencryptentireSQLServer
databases.
databasemasterkey
Adatabase-levelencryptionkeyusedtosecureotherkeysinthedatabase.
datadomain
Foracolumn,allvalidvaluesthatmaybestoredinthatcolumn.Thedatadomaincanbe
restrictedthroughtheuseofdatatypes,checkconstraints,referentialintegrity/foreignkey
constraints,andtriggers.
datapage
ThesmallestunitofstoragethatSQLServercanallocate.Thedatapageconsistsof8KB
oflogicallycontiguousstorage.
datum
Asetofreferencepointsagainstwhichpositioncanbemeasured.Ageodeticdatumis
oftenassociatedwithamodeloftheshapeoftheEarthtodefineageographiccoordinate
system.
emptysequence
AnXPath2.0/XQuery1.0sequencecontainingzeroitems.
entitydatamodel(EDM)
Anabstractlogicalrepresentationofaphysicaldatabase,usedtoimplementdatabase
connectivityinthemiddleorclienttiers.
ExtendedEvents(XEvents)
Alightweightdiagnosticsystemthatcanhelpyoutroubleshootperformanceproblems
withSQLServer.
extensiblekeymanagement(EKM)
ASQLServer2012encryptionoptionthatallowsyoutophysicallystoreencryptionkeys
onthird-partyhardwaresecuritymodules(HSMs).
extent
SQLServer’sbasicallocationunitofstorage.Anextentis64KBinsizeandconsistsof
eightlogicallycontiguousdatapages,eachofwhichis8KBinsize.
Extract,Transform,Load(ETL)
Processesthatinvolvepullingdatafromdisparatedatasources,cleaningandscrubbingthe
data,manipulatingit(transform),andstoringitinthedatabase.
facet
Aschemacomponentusedtoconstraindatatypes.Acoupleofcommonlyusedfacetsare
whiteSpace,whichcontrolshowwhitespaceinstringvaluesishandled,andlength,
whichrestrictsvaluestoaspecificnumberofunitsinlength.
filterexpression
Aprimaryexpressionfollowedbyzeroormorepredicates.
FLWORexpression
Expressionsthatsupportiterationandbindingvariables.FLWORisanacronymforthe
XQuerykeywordsfor,let,where,orderby,andreturn.
foreignkeyconstraint
AlogicalcouplingoftwoSQLtablesthroughthevaluesofspecifiedcolumns.
full-textcatalog
AlogicalgroupingofSQLServerfull-textindexesformanagementpurposes.
full-textindex
Indexthatenablesadvancedtext-basedsearchestobeperformedagainstadatabasetable.
full-textsearch(FTS)
TheSQLServer2012implementationoftheSQLServerfull-textsearchenginewiththe
SQLServerqueryengine.
FunctionsandOperators(F&O)
TheXQuery1.0andXPath2.0FunctionsandOperatorsspecification,availableat
www.w3.org/TR/xquery-operators/.
generalcomparison
AnexistentiallyquantifiedXQuerycomparisonthatmaybeappliedtooperandsequences
ofanylength.Ingeneralcomparisons,thenodesareatomizedandtheatomicvaluesof
bothoperandsarecomparedusingvaluecomparisons.Ifanyofthevaluecomparisons
evaluatetotrue,theresultistrue.
GeographyMarkupLanguage(GML)
AstandardfortherepresentationofgeographicdatausingXML.
groupingset
ASQLServer2012featurethatallowsyoutodefinesetsofgroupingcolumnsinyour
queries.
hash
Theresultofapplyingamathematicalfunctionortransformationtodatatogeneratea
smaller“fingerprint”ofthedata.Generally,themostusefulhashfunctionsareone-way,
collision-freehashesthatguaranteeahighlevelofuniquenessintheirresults.
heap
Anunorderedcollectionofdatapages.Anytablewithoutaclusteredindexisaheap.
heterogeneoussequence
AnXQuerysequenceofatomicvaluesofdifferenttypesand/orXMLnodes.SQLServer
XQuerydoesn’tsupportheterogeneoussequencesconsistingofatomicvaluesandnodes.
homogenoussequence
AnXQuerysequenceconsistingentirelyofnodesorentirelyofsingletonatomicvaluesof
compatibledatatypes.
indirectrecursion
Recursionbyatriggerthatoccurswhenatriggerfires,causinganothertriggerofthesame
typetofire,whichcausesthefirsttriggertofireagain.
inflectionalforms
Arethedifferenttensesofaverborthesingularandpluralformsofnouns.SQLServer
integratedfull-textsearch(FTS)cansearchforinflectionalformsofaword,including
verbtensesandpluralformsofnouns.
initializationvector(IV)
Ablockofbitsthatisusedtoobfuscatethefirstblockofdataduringtheencryption
process.
LanguageIntegratedQuery(LINQ)
Asetoffeaturesthataddsnativedatasource-agnosticqueryingcapabilitiesto.NET
languagesusingadeclarativesyntax.
locationpath
Aseriesofstepsseparatedbythesolidus(forwardslash)character,evaluatedfromleftto
right.ApathisanXPathorXQueryexpressionthataddressesaspecificsubsetofnodesin
anXMLdocument.Eachsteponalocationpathgeneratesasequenceofitems.Location
pathscanberelativeorabsolute.Absolutelocationpathsbeginwithasinglesolidus
character;relativelocationpathsdonot.
logontrigger
AtriggerthatfiresinresponsetoaserverLOGONevent.
materializedpathmodel
Amodelforstoringhierarchicaldata,inwhichtheentirepathtotherootnodeisstored
witheachnodeinthehierarchy.
MultipleActiveResultSets(MARS)
Afeaturethatallowsyoutosimultaneouslyopenmultipleresultsetsonasingleopen
connection.
nestedsetsmodel
Amodelinwhichhierarchicaldataisrepresentedasacollectionofsetscontainingother
sets.Thelowerandupperboundsofeachsetdefinethecontentsoftheset.
node
IntheDocumentObjectModel(DOM),everythinginanXMLdocumentisanode.The
entiredocumentisatree-likestructurethatmakesconnectionsbetweenthearbitrary
attributesornodes.XPath2.0andXQuery1.0treatXMLdataasahierarchicaltree
structure,similarto(butnotexactlythesameas)theDocumentObjectModel(DOM)that
webprogrammersoftenusetomanipulateHTMLandXML.XPathandXQueryXML
treesarecomposedoftheseventypesofnodesdefinedintheW3CXQuery1.0andXPath
2.0DataModel(XDM),fulldescriptionsofwhichareavailableat
www.w3.org/TR/xpath-datamodel/#node-identity.Thesenodetypes
includethefollowing:
AttributenodesrepresentXMLattributes.
CommentnodesencapsulateXMLcomments.
DocumentnodesencapsulateXMLdocuments.
ElementnodesencapsulateXMLelements.
NamespacenodesrepresentthebindingofanamespaceURItoa
namespaceprefix(orthedefaultnamespace).
Processinginstructionnodesencapsulateprocessinginstructions.
TextnodesencapsulateXMLcharactercontent.
XPath1.0definesthenodetypesitusesinPart5oftheXPath1.0specification.The
maindifferencebetweenXPath1.0nodesandXDMnodesisthatXPath1.0definesthe
rootnodeofadocumentinplaceofthedocumentnodesoftheXDM.Anothermajor
differenceisthatintheXDM,elementnodesareeitherexplicitlyorimplicitly(basedon
content)assignedtypeinformation.
nodecomparison
ComparisonofnodesinXQuerybasedontheirdocumentorderoridentity.
nodetest
Aconditionthatmustbetrueforeachnodegeneratedbyastep.Anodetestcanbebased
onthenameofthenode,thekindofnode,orthetypeofnode.
nonclusteredindex
AnindexthatstorestheclusteringkeyorrowIDoftherowdatainitsleafnodes,
dependingonwhetherthetableisaclusteredtableoraheap.
object-relationalmapping(O/RM)
Atechniqueformappingdatabetweenrelationaldatabasesandobject-oriented
programminglanguages.
open-worldassumption(OWA)
Alogicformalismstatingthatthetruthofastatementisindependentofwhetherit’s
knowntobetrue.
optionaloccurrenceindicator
The?character,whenusedinconjunctionwiththecastaskeywords.Itindicatesthat
theemptysequenceisallowed.
parameterization
TheactofusingnamedorpositionalmarkersinplaceofconstantvaluesinaT-SQLquery
orstatement.TheactualvaluesarepassedtoSQLServerindependentlyoftheactual
query.
pathexpression
Seelocationpath.
predicate
InT-SQL,anexpressionthatevaluatestoaSQLtruthvalue.Predicatesareusedtocontrol
programflowandtolimittheresultsofqueriesandtheeffectofstatements.
AnXQuerypredicateisanexpressionenclosedinbrackets([])thatisusedtofiltera
sequence.Thepredicateexpressionsaregenerallycomparisonexpressionsofsomesort
(equality,inequality,andsoon).
predicatetruthvalue
InXQuery,aBooleanvaluederivedfromtheresultofanexpressionthroughasetofrules
definedintheXQueryrecommendation.
primaryexpression
ThebasicprimitiveoftheXQuerylanguage.Aprimaryexpressioncanbealiteral,a
variablereference,acontextitemexpression,adatatypeconstructor,orafunctioncall.
queryplan
AsequenceoflogicalandphysicaloperatorsanddataflowsthattheSQLqueryoptimizer
returnsforusebythequeryprocessortoretrieveormodifydata.
recompilation
Theprocessofcompilinganewqueryplanforagivenquery,statement,orstored
procedurewhenaplanalreadyexistsinthequery-plancache.Recompilationcanbe
triggeredbySQLServerduetochangesthathaveoccurredsincethepriorqueryplanwas
generatedforthestatement,oritcanbeforcedbyuseractionsandT-SQLoptions.
recursion
Amethodofdefiningfunctions,commontableexpressions,procedures,ortriggersinsuch
awaythattheycallthemselvesorcausethemselvestobecalledmultipletimes.
rowconstructor
ASQLServer2012featurethatallowsyoutospecifymultiplerowsinasingleVALUES
clauseoftheINSERTstatement.
scalarfunction
Afunctionthatreturnsasingleatomicvalueasitsresult.
searchedCASEexpression
AnexpressionthatallowsyoutospecifyoneormoreSQLpredicatesinWHENclauses.
sequence
Anorderedcollectionofzeroormoreitems,asdefinedinXPath2.0andXQuery1.0.The
wordorderedisimportant,becauseitdifferentiatesasequencefromaset,which,asmost
T-SQLprogrammersknow(orquicklycometorealize),isunordered.XPath1.0defined
itsresultsintermsofnodesets,whichareunorderedandcan’tcontainduplicates.XQuery
changesthisterminologytonodesequences,whichrecognizetheimportanceofnode
orderinXMLandcancontainduplicates.
servercertificate
Acertificatecreatedinthemasterdatabaseforthepurposeofencryptinganentire
databaseviatransparentdataencryption(TDE).
servicemasterkey(SMK)
AnencryptionkeymanagedattheSQLServerservicelevel.TheSMKisusedtoencrypt
allotherkeysintheSQLServerencryptionkeyhierarchy.
shredding
TheprocessofconvertingXMLdataintorelationalstylerowsandcolumns.
simpleCASEexpression
AnexpressiondefinedwithconstantsorvalueexpressionsinitsWHENclauses.The
simpleCASEevaluatestoaseriesofsimpleequalityexpressions.
SOAP
SimpleObjectAccessProtocol,anXML-basedprotocoldesignedforexchanging
structuredinformationindistributed,decentralizedenvironments.
spatialdata
DatausedtorepresentobjectsandpointsontheEarth.
spatialindex
Amechanismforincreasingtheefficiencyofgeographiccalculationssuchasthedistance
betweenpoints,orwhetheranobjectcontainsanotherpointorobject.
SQLServerDataTools
Asetoftoolsthatprovidesanintegratedenvironmentfordatabaseandapplication
development.
SQLinjection
Atechniquethatexploitssecurityvulnerabilitiesintheapplicationlayerandmiddletier,
allowinguserstoexecutearbitrarySQLstatementsonaserver.
step
InXQuery,apartofapathexpressionthatgeneratesasequenceofitemsandthenfilters
thesequence.Eachstepiscomposedofanaxis,anodetest,andzeroormorepredicates.
tabletype
Analiastypethatdefinesatablestructureforusewithtable-valuedparameters.
three-valuedlogic(3VL)
AlogicsystemthattheSQLlanguagesupportswiththreetruthvalues:true,false,and
unknown.
transparentdataencryption(TDE)
ASQLServer2012featurethatallowsyoutoencryptanentiredatabaseatonce.
untypedXML
AnXMLdatainstancethatisnotassociatedwithanXMLschemacollection.
user-definedaggregate(UDA)
ASQLCLRroutinethatappliesafunctionorcalculationtoanentiresetofvalues.
user-definedtype(UDT)
ASQLCLR-baseddatatype.
valuecomparison
AcomparisonofsinglevaluesinXQuery.
well-formedXML
XMLdatathatfollowstheW3CXMLrecommendationforwell-formeddata.Itincludesa
singlerootelementandproperlynestedelements,andit’sproperlyentitized.
well-knowntext(WKT)
Aplain-textformatfordefininggeospatialdata.
windowingfunctions
Functionsthatcanpartitionandpossiblyorderdatasetsbeforethey’reappliedtothe
datasetpartitions.
WorldWideWebConsortium(W3C)
Astandardsbodywiththestatedmissionof“developinginteroperabletechnologies…to
leadtheWebtoitsfullpotential.”
XML
ExtensibleMarkupLanguage,arestrictedformofStandardizedGeneralMarkup
Language(SGML)designedtobeeasilyserved,received,andprocessedontheWeb.
XMLschema
ThebasicdatatypesutilizedbyXQuery.Part2oftheXMLSchema1.1standarddefines
XMLSchemadatatypes.
XPath
XMLPathLanguage,anexpressionlanguagedesignedtoallowprocessingofvaluesthat
conformtotheXPathDataModel(XDM).
XQuery
XMLQueryLanguage,anXMLquerylanguagedesignedtoretrieveandinterpretdata
fromdiverseXMLsources.
XQuery/XPathDataModel(XDM)
TheXQuery1.0andXPath2.0DataModel,definedbytheW3Cat
www.w3.org/TR/2006/PR-xpath-datamodel-20061121/.SeeXQuery.
XSL
ExtensibleStylesheetLanguageXSLtransformations(XSLT),alanguageforexpressing
stylesheets,consistingofalanguagefortransformingXMLdocumentsandanXML
vocabularyforspecifyingformattingsemantics.SeeXSLT.
XSLT
XSLTransformationsExtensibleStylesheetLanguage(XSL),alanguagefortransforming
XMLdocumentsintootherXMLdocuments.Forinstance,XSLTcanbeusedtotransform
anXMLdocumentintoanXHTMLdocument.SeeXSL.
APPENDIXD
SQLCMDQuickReference
SQLCMDisthestandardtext-basedtoolforexecutingbatchesofT-SQLonSQLServer.
Asatext-basedtool,SQLCMDprovidesalightweightbutpowerfultoolforautomatingT-
SQLbatches.ThisappendixisdesignedasaquickreferencetoSQLCMD.The
descriptionsofmanyofthefeaturesandthefunctionalitygivenheredifferfromBOLin
someinstances;thedescriptionsprovidedinthisappendixarebasedonextensivetesting
ofSQLCMD.
Command-LineOptions
SQLCMDprovidesseveralcommand-lineoptionstoprovideflexibilityinconnectingto
SQLServerandexecutingT-SQLbatchesinadatabase.ThefullformatforSQLCMDis
shownhere:
sqlcmd[[-Ulogin_id][-Ppassword]|[-E]][-C]
[-Sserver[\instance]][-ddb_name][-Hworkstation]
[-llogintimeout][-tquerytimeout][-hheaders][-s
column_separator][-wcolumn_width]
[-apacket_size][-I][-L[c]][-W][-r[o|1]][-q"query"]
[-Q"query"andexit]
[-cbatch_term][-e][-merrorLevel][-VSeverityLevel][-
b][-N][-K]
[-iinput_file[,input_file2[,...]]][-ooutput_file][-
u]
[-vvar="value"[,var2="value2"][,...]][-X[1][-x]
[-?]
[-znew_password][-Znew_password][-fcodepage
|i:in_codepage[,o:out_codepage]]
[-k[l|2]][-ydisplay_width][-Ydisplay_width]
[-p[1][-R][-A]
Theavailablecommand-lineoptionsarelistedinTableD-1.TheSQLCMDcommand-
lineoptionsarecasesensitive,so,forexample,-visadifferentoptionfrom-V.
TableD-1.SQLCMDCommand-LineOptions
Option Description
-? DisplaystheSQLCMDhelp/syntaxscreen.
-A TellsSQLCMDtologintoSQLServerwithadedicatedadministratorconnection.
Thistypeofconnectionisusuallyusedfortroubleshooting.
-apacket_size Requestscommunicationswithaspecificpacketsize.Thedefaultis4096.
packet_sizemustbeintherange512to32767.
-b
SpecifiesthatSQLCMDexitsonanerrorandreturnsanERRORLEVELvalueto
theoperatingsystem.Whenthisoptionisset,aSQLerrorofseverity11orgreater
returnsanERRORLEVELof1;anerrorormessageofseverity10orlessreturnsan
ERRORLEVELof0.Ifthe-Voptionisalsoused,SQLCMDreportsonlythe
errorswithaseveritygreaterthanorequaltotheseverity_level(level11or
greater)specifiedwiththe-Voption.
-cbatch_term Specifiesthebatchterminator.Bydefault,it’stheGOkeyword.Avoidusing
specialcharactersandreservedwordsasthebatchterminator.
-C Specifiesthattheservercertificatecanbetrustedimplicitlywithoutvalidation
usedbytheclient.
-ddb_name
SpecifiesthedatabasetouseafterSQLCMDconnectstoSQLServer.
Alternatively,youcansetthisoptionviatheSQLCMDDBNAMEenvironment
variable.Ifthedatabasespecifieddoesn’texist,SQLCMDexitswithanerror.
-E
Usesatrustedconnection(Windowsauthenticationmode)toconnecttoSQL
Server.ThisoptionignorestheSQLCMDUSERandSQLCMDPASSWORD
environmentvariables,andyoucan’tuseitwiththe-Uand-Poptions.
-e Prints(echoes)inputscriptstothestandardoutputdevice(usuallythescreenby
default).
-fcodepage|
i:in_codepage
[,oout_codepage]
Specifiesthecodepagesforinputandoutput.Ifi:isspecified,in_codepage
istheinputcodepage.Ifo:isspecified,out_codepageistheoutputcode
page.Ifi:ando:aren’tspecified,thecodepagesuppliedisthecodepagefor
bothinputandoutput.Tospecifyacodepage,useitsnumericidentifier.The
followingcodepagesaresupportedbySQLServer2005:
CodePageNumber CodePageName
437 MS-DOSUSEnglish
850 Multilingual(MS-DOSLatin1)
874 Thai
932 Japanese
936 Chinese(Simplified)
949 Korean
950 Chinese(Traditional)
1250 CentralEuropean
1251 Cyrillic
1252 Latin1(ANSI)
1253 Greek
1254 Turkish
1255 Hebrew
1256 Arabic
1257 Baltic
1258 Vietnamese
-Hworkstation The-Hoptionsetstheworkstationname.Youcanuse-Htodifferentiatebetween
sessionswithcommandssuchassp_who.
-hheaders
Specifiesthenumberofrowsofdatatoprintbeforeanewcolumnheaderis
generated.Thevaluemustbefrom-1(noheaders)to2147483647.Thedefault
valueof0printsheadingsonceforeachsetofresults.
-I
SetstheconnectionQUOTED_IDENTIFIERoptiontoON.Turningonthe
QUOTED_IDENTIFIERoptionmakesSQLServerfollowtheANSISQL-92
rulesforquotedidentifiers.ThisoptionissettoOFFbydefault.
-iinput_file
[,input_file2]
[,…]
SpecifiesthatSQLCMDshouldusefilesthatcontainbatchesofT-SQLstatements
forinput.Thefilesareprocessedinorderfromlefttoright.Ifanyofthefilesdon’t
exist,SQLCMDexitswithanerror.YoucanusetheGObatchterminatorinyour
SQLscriptfiles.
-k[1|2]
-kremovescontrolcharactersfromtheoutput.If1isspecified,controlcharacters
arereplacedoneforonewithspaces.If2isspecified,consecutivecontrol
charactersarereplacedwithasinglespace.
-K
Specifiestheintentoftheapplicationworkloadthatisconnectingtotheserverthat
isasecondaryreplicaintheAlwaysOnavailabilitygroup.Theonlyvaluethat
canbespecifiedcurrentlyisReadOnly.
-L[c]
-LreturnsalistingofavailableSQLServermachinesonthenetworkandlocal
computer.Ifthe-Lcformatisused,a“clean”listingisreturnedwithoutheading
information.Thelistingislimitedtoamaximumof3,000servers.Notethat
becauseofthewaySQLServerbroadcaststogatherserverinformation,any
serversthatdon’trespondinatimelymanneraren’tincludedinthelist.Youcan’t
usethe-Loptionwithotheroptions.
-ltimeout Specifiesthelogintimeout.Thetimeoutvaluemustbefrom0to65534.The
defaultvalueis8seconds,andavalueof0isnotimeout(infinite).
-merror_level
Definesanerror-messagecustomizationlevel.Onlyerrorswithaseveritygreater
thanthespecifiedlevelaredisplayed.Iferror_levelis-1,allmessagesare
returned,eveninformationalmessages.
-N Specifiesthattheclientconnectionisencrypted.
-ooutput_file SpecifiesthefiletowhichSQLCMDshoulddirectoutput.If-oisn’tspecified,
SQLCMDdefaultstostandardoutput(usuallythescreen).
-Ppassword
SpecifiesapasswordtologintoSQLServerwhenusingSQLauthentication
mode.If-Pisomitted,SQLCMDlooksfortheSQLCMDPASSWORDenvironment
variabletogetthepasswordtologin.IftheSQLCMDPASSWORDenvironment
variableisn’tfound,SQLCMDpromptsyouforthepasswordtologinusingSQL
authenticationmode.Ifneither-Pnor-Uisspecifiedandthecorresponding
environmentvariablesaren’tset,SQLCMDattemptstologinusingWindows
authenticationmode.
-p[1] -pprintsperformancestatisticsforeachresultset.Specifying1producescolon-
separatedoutput.
-Q“query”and-q
“query”
BothexecuteaSQLquery/commandfromthecommandline.-qremainsin
SQLCMDafterquerycompletion.-QexitsSQLCMDaftercompletion.
-R Specifiesclientregionalsettingsforcurrencyanddate/timeformatting.
-r[0|1]
-rredirectserror-messageoutputtothestandarderror-outputdevice—themonitor
bydefault.If1isspecified,allerrormessagesandinformationalmessagesare
redirected.If0ornonumberisspecified,onlyerrormessageswithaseverityof
11orgreaterareredirected.Theredirectiondoesn’tworkwiththe-ooption;it
doesworkifstandardoutputisredirectedwiththeWindowscommand-line
redirector(>).
-Sserver
[\instance]
SpecifiestheSQLServerserverornamedinstancetowhichSQLCMD
shouldconnect.Ifthisoptionisn’tspecified,SQLCMDconnectstothedefault
SQLServerinstanceonthelocalmachine.
-s
column_separator
Setsthecolumn-separatorcharacter.Bydefault,thecolumnseparatorisaspace
character.Column_separatorcanbeenclosedinquotes,whichisusefulifyou
wanttouseacharacterthattheoperatingsystemrecognizesasaspecialcharacter,
suchasthegreater-thansign(>).
-ttimeout
SpecifiestheSQLquery/commandtimeoutinseconds.Thetimeoutvaluemustbe
intherange0to65535.If-tisn’tspecified,orifit’ssetto0,queries/commands
don’ttimeout.
-Ulogin_id
SpecifiestheuserloginIDtologintoSQLServerusingSQLauthentication
mode.Ifthe-Uoptionisomitted,SQLCMDlooksfortheSQLCMDUSER
environmentvariabletogettheloginpassword.Ifthe-Uoptionisomitted,
SQLCMDattemptstousethecurrentuser’sWindowsloginnametologin.
-u SpecifiesthattheoutputofSQLCMDisinUnicodeformat.Usethisoptionwith
the-ooption.
-V
severity_level
SpecifiesthelowestseveritylevelthatSQLCMDreportsback.Errorsand
messagesofaseveritylessthanseverity_levelarereportedas0.
Severity_levelmustbeintherange1to25.Inacommand-linebatchfile,-
VreturnstheseveritylevelofanySQLServererrorsencounteredviathe
ERRORLEVELsothatyourbatchfilecantakeappropriateaction.
-vvar=“value”
[,var2=
“value2”]
[,…]
SetsscriptingvariablesthatSQLCMDcanuseinyourscriptstothespecified
values.Scriptingvariablesaredescribedlaterinthisappendix.
-W
Removestrailingspacesfromacolumn.Youcanusethisoptionwiththe-s
optionwhenpreparingdatathatistobeexportedtoanotherapplication.Youcan’t
use-Winconjunctionwiththe-Yor-yoption.
-wcolumn_width
Specifiesthescreenwidthforoutput.Thewidthvaluemustbeintherange9to
65535.Thedefaultof0isequivalenttothewidthoftheoutputdevice.Forscreen
output,thedefaultisthewidthofthescreen.Forfiles,thedefaultwidthis
unlimited.
-X[1]
-Xdisablesoptionsthatcancompromisesecurityinbatchfiles.Specifically,-X
doesthefollowing:
DisablestheSQLCMD:!!and:EDcommands
PreventsSQLCMDfromusingoperatingsystem
environmentvariables
DisablestheSQLCMDstartupscript
Ifadisabledcommandisencountered,SQLCMDissuesawarningandcontinues
processing.Iftheoptional1isspecifiedwith-X,SQLCMDexitswithanerror
whenadisabledcommandisencountered.DescriptionsofSQLCMDcommands,
scriptvariables,environmentvariables,andthestartupscriptaredetailedlaterin
thisappendix.
-x ForcesSQLCMDtoignorescriptingvariables.
-Ydisplay_width Limitsthenumberofcharactersreturnedforthechar,nchar,varchar(8,000
bytesorless),nvarchar(4,000bytesorless),andsql_variantdatatypes.
-ydisplay_width
Limitsthenumberofcharactersreturnedforvariable-lengthdatatypessuchas
varchar(max),varbinary(max),xml,text,andfixed-lengthorvariable-
lengthuser-definedtypes(UDTs).
-Znew_password
and-z
new_password
WhenusedwithSQLauthentication(the-Uand-Poptions),-Zand-zchange
theSQLloginpassword.Ifthe-Poptionisn’tspecified,SQLCMDpromptsyou
forthecurrentpassword.-zchangesthepasswordandentersinteractivemode.-
ZexitsSQLCMDimmediatelyafterthepasswordischanged.
ScriptingVariables
SQLCMDsupportsscriptingvariables,whichallowyoutodynamicallyreplacescript
contentatexecutiontime.Thisletsyouuseasinglescriptinmultiplescenarios.Byusing
scriptingvariables,forinstance,youcanexecuteasinglescriptagainstdifferentserversor
databaseswithoutmodification.SQLCMDallowsyoutosetyourowncustomscripting
variableswiththe-vcommand-lineoption.Ifmorethanonescriptingvariableis
specifiedwiththesamename,thevariablewiththehighestprecedence(accordingtothe
followinglist)isused:
1. System-levelenvironmentvariableshavethehighestprecedence.
2. User-levelenvironmentvariablesarenext.
3. VariablessetviathecommandshellSEToptionarenext.
4. VariablessetviatheSQLCMD-vcommand-lineoptionarenext.
5. VariablessetinsideaSQLCMDbatchviathe:SETVARcommand
havethelowestprecedence.
NoteThe-Xand-xoptionsdisablestartup-scriptexecutionandenvironment-variable
access,respectively.-xalsopreventsSQLCMDfromdynamicallyreplacingscripting-
variablereferencesinyourcodewiththeappropriatevalues.Thisisafeaturedesignedfor
secureenvironmentswherescripting-variableusagecouldcompromisesecurity.
SQLCMDalsoprovidesseveralpredefinedscriptingvariables,whicharelistedin
TableD-2.Youcansetthepredefinedread-onlySQLCMDscriptingvariablesviathe
commandshellSEToptionorthroughSQLCMDcommand-lineoptions;youcan’talter
themfromwithinaSQLCMDscriptwith:SETVAR.
TableD-2.SQLCMDScriptingVariables
Commands
SQLCMDrecognizesasetofcommandsthataren’tpartofT-SQL.TheseSQLCMD
commandsaren’trecognizedbyotherquerytools;they’renotevenrecognizedbySSMS
(exceptwhenyourunitinSQLCMDmode).SQLCMDcommandsallbeginonaline
withacolon(:)toidentifythemasdifferentfromT-SQLstatements.Youcanintersperse
SQLCMDcommandswithinyourT-SQLscripts.TableD-3liststheSQLCMDcommands
available.
TipForbackwardcompatibilitywitholderosqlscripts,youcanenterthefollowing
commandswithoutacolonprefix:!!,ED,RESET,EXIT,andQUIT.Also,SQLCMD
commandsarecaseinsensitive,theymustappearatthebeginningofaline,andtheymust
beontheirownline.ASQLCMDcommandcan’tbefollowedonthesamelinebyaT-
SQLstatementoranotherSQLCMDcommand.
TableD-3.SQLCMDCommands
Command Description
:!! Invokesthecommandshell.Thiscommandexecutesthespecifiedoperatingsystem
commandinthecommandshell.
:CONNECT
server
[\instance]
ConnectstoaSQLServerinstance.
[-ltimeout]
[-Uuser[-
Ppassword]
]
Theservername(server)andinstancename(\instance)arespecifiedinthe
command.When:CONNECTisexecuted,thecurrentconnectionisclosed.Youcanuse
thefollowingoptionswiththe:CONNECTcommand:-lspecifiesthelogintimeout
(specifiedinseconds;0equalsnotimeout);-UspecifiestheSQLauthentication
username;and-PspecifiestheSQLauthenticationpassword.
:ED
Startsthetexteditortoeditthecurrentbatchorthelastexecutedbatch.The
SQLCMDEDITORenvironmentvariabledefinestheapplicationusedastheSQLCMD
editor.ThedefaultistheWindowsEDITutility.
:ERROR
destination
Redirectserrormessagestothespecifieddestination.destinationcanbeafile
name,STDOUTforstandardoutput,orSTDERRforstandarderroroutput.
:EXIT[()|
(query)]
Hasthreeforms::EXITaloneimmediatelyexitswithoutexecutingthebatchandwithno
returncode.:EXIT()executesthecurrentbatchandexitswithnoreturncode.
:EXIT(query)executesthebatch,includingthequeryspecified,andreturnsthefirst
valueofthefirstresultrowofthequeryasa4-byteintegertotheoperatingsystem.
GO[n] Thebatchterminator.Itexecutesthestatementsinthecache.Ifnisspecified,GOexecutes
thestatementntimes.
:HELP DisplaysalistofSQLCMDcommands.
:LIST Liststhecontentsofthecurrentbatchofstatementsinthestatementcache.
:LISTVAR ListsalltheSQLCMDscriptingvariables(thathavebeenset)andtheircurrentvalues.
:ONERROR
action
SpecifiestheactionSQLCMDshouldtakewhenanerrorisencountered.actioncanbe
oneoftwovalues:EXITstopsprocessingandexits,returningtheappropriateerrorcode.
IGNOREdisregardstheerrorandcontinuesprocessing.
:OUT
destination
Redirectsoutputtothespecifieddestination.destinationcanbeafilename,
STDOUTforstandardoutput,orSTDERRforstandarderroroutput.Outputissentto
STDOUTbydefault.
:PERFTRACE
destination
Redirectsperformancetrace/timinginformationtothespecifieddestination.
destinationcanbeafilename,STDOUTforstandardoutput,orSTDERRforstandard
erroroutput.TraceinformationissenttoSTDOUTbydefault.
:QUIT QuitsSQLCMDimmediately.
:Rfilename Readsinthecontentsofthespecifiedfileandappendsittothestatementcache.
:RESET Resets/clearsthestatementcache.
:SERVERLIST
ListsallSQLServerinstancesonthelocalmachineandanyserversbroadcastingonthe
localnetwork.IfSQLCMDdoesn’treceivetimelyresponsesfromaserveronthe
network,itmaynotbelisted.
:SETVARvar
[value]
AllowsyoutosetorremoveSQLCMDscriptingvariables.ToremoveaSQLCMD
scriptingvariable,usethe:SETVARvarformat.TosetaSQLCMDscriptingvariableto
avalue,usethe:SETVARvarvalueformat.
:XMLON|OFF
IndicatestoSQLCMDthatyouexpectXMLoutputfromSQLServer(thatis,the
SELECTstatement’sFORXMLclause).Use:XMLONbeforeyourSQLbatchisrunand
:XMLOFFafterthebatchhasexecuted(aftertheGObatchterminator).
Index
A
Accumulate()method
ACID.SeeAtomicity,consistency,isolation,durability(ACID)
ACM.SeeAssociationforComputingMachinery(ACM)
Adjacencylistmodel
ADO.NET
4.5,asynchronousprogramming
code
runstoredprocedureasynchronously
runstoredproceduresynchronously
dataservices
System.Data.Common
System.Datanamespace
System.Data.Odbc
System.Data.OleDb
System.Data.SqlClient
System.Data.SqlTypes
AdventureWorks
BOM
CREATEDATABASE
DataServiceClass
LT2014datafile
sampledatabase
AmericanNationalStandardsInstitute(ANSI)
Analyticfunctions
CUME_DISTandPERCENT_RANKfunctions
FIRST_VALUEandLAST_VALUE
LAGandLEAD
PERCENTILE_CONTandPERCENTILE_DISCfunction
Anchorquery
ANSI.SeeAmericanNationalStandardsInstitute(ANSI)
ANSI-encodedcharacters
Applicationprogramminginterface(API)
ApressDbdatabasefiles
apt-cachecommand
AssociationforComputingMachinery(ACM)
Asymmetricencryption
Asymmetrickeys
algorithmsandlimits
ALTERASYMMETRICKEY
AsymKeylDfunction
DecryptByAsymKey
DMK
EncryptByAsymKeyfunction
HSM
publicandprivatekeys
SignByAsymKeyfunction
varbinarysignature
Asynchronousprogramming
codestructure
storedprocedure
Atomicdatatypes
Atomicity,consistency,isolation,durability(ACID)
AUTHORIZATIONclause
AUTOmode
B
BACKUPASYMMETRICKEYstatement
BCP.SeeBulkCopyProgram(BCP)
Bestpractices,SPs
API
BEGIN/ENDTRANSACTION
CLR
DBMSs
dbo.sp_help
functionality
modularizationandsecurity
nullability
queryoptimization
scalarfunction
SELECTstatements
UNIONALLoperator
BIDS.SeeBusinessIntelligenceDevelopmentStudio(BIDS)
Billofmaterials(BOM)
AdventureWorks
hierarchyid
recursiveCTE
BOL.SeeBooksOnline(BOL)
BomChildren
BooksOnline(BOL)
BREAKstatement
build_dm.shcommand
BulkCopyProgram(BCP)
BusinessIntelligenceDevelopmentStudio(BIDS)
Bw-treearchitecture
C
CartesianproductXQuery
CASEexpressions
CHOOSEfunction
COALESCEandNULLIFfunctions
IIFstatement
pivottables
searchexpression
simpleexpression
CASE-stylepivottable
CatalogDescriptionwithnonamespaces
Catalogviews
advantages
inheritancemodel
metadata
queryingpermissions
SQLServerdatabaseandserver-specificmetadata
tableandcolumnmetadata
Certificateauthority(CA)
Certificates
CertIDfunction
CREATECERTIFICATEstatement
DecryptByCertfunction
decryptionfunctions
DER
EncryptByCertfunction
EXECUTABLEFILEclause
FROMASSEMBLYclause
public/privatekey
SignByCertfunction
SQLServer
TestCertificate
varbinary
varchar
Changedatacapture(CDC)
built-inauditingfunctionality
DMLaudit
actiontable
CASEexpression
CREATETRIGGERstatement
loggingtable
@@ROWCOUNTfunction
rowinsertion
SELECTstatement
SETNOCOUNTON
testing
triggerloggingtable
UPDATEstatement
nestedandrecursivetriggers
sharingdata
UPDATE()andCOLUMNS_UPDATED()functions
Checkconstraint
CHOOSEstatement
Closed-worldassumption(CWA)
CLRDemoDatabaseProjectproperties
CLRintegrationprogram
advantages
assemblies
AUTHORIZATIONclause
CLRDemoDatabaseProjectproperties
CREATEASSEMBLYstatement
databaseprojectpropertiesmenu
FROMclause
.NETnamespacesandclasses
project
T-SQLdatabaseobject-creationstatements
VisualStudio2013
WITHPERMISSION_SETclause
guidelines
ODS
storedprocedures(seeStoredprocedures)
triggers(seeTriggers)
UDAs(seeUser-definedaggregates(UDAs))
UDFs(seeUser-definedfunctions(UDFs))
UDTs(seeUser-defineddatatypes(UDTs))
Clusteredindexes
COALESCE()function
Codesnippets
category
createfunction
createstoredprocedure
CREATETABLE
InsertSnippetcommand
manager
T-SQLEditor
Columnstoreindex
Command-lineoptions
CommonLanguageRuntime(CLR)
CommonObjectRequestBrokerArchitecture(CORBA)
Commontableexpressions(CTE)
benefits
BomChildren
definition
DMLstatement
exercises
32-levelrecursionlimit
multiple
overloading
parent_path_locatorandpath_locator
readablilitybenefits
recursive.RecursiveCTE
simple
SP
syntax
Compiledstoredprocedures
Complexnumber
Computedconstructors
CONTAINSpredicate
compoundCONTAINSsearchterm
customsearch
FORMSOFinflectionalgenerationterm
FREETEXTpredicate
prefixsearch
proximitysearch
simpleCONTAINSquery
Contentexpression
Contextitemexpression
Contextnode
Control-of-flowstatements
BEGINandENDkeywords
GOTOStatement
IF…ELSEstatement
RETURNStatement
WAITFORstatement
WHILE,BREAKandCONTINUEstatements
CoordinatedUniversalTime(UTC)
dateandtimedata
andmilitarytime
CUME_DISTandPERCENT_RANKfunctions
CURRENT_TIMESTAMPfunctions
Cursors
administrativetasks
AdventureWorksdatabase
ALTERINDEXstatement
comparisons
DBCCs
dbo.RebuildIndexesprocedure
description
designpatterns
@IndexListtable
options
RBAR
SQL’sset-basedprocess
T-SQLextendedsyntax
WHILEloops
D
Databaseconsolecommands(DBCCs)
Databasemasterandencryptionkey
Databasemasterkeys(DMK)
Datacontrollanguage(DCL)
Datadefinitionlanguage(DDL)
auditloggingresults
CREATETABLEstatement
CREATETRIGGERstatement
definition
DROPTRIGGERstatement
EVENTDATA()function
eventtypesandgroups
nodes()andvalue()methods
Datadomainandpage
Data()function
DATALENGTH()function
Datamanipulationlanguage(DML)
auditing
CREATETRIGGERstatement
definition
disablingandenablingtriggers
HumanResources.Employeetable
INSERTandDELETEstatement
multipletriggers
multistatementTVF
@@ROWCOUNTsystemfunction
SELECTandUPDATE
SETNOCOUNTONstatement
statement
trigger
UPDATEstatement
Data()nodetest
Dataservices.SeealsoServiceOrientedArchitecture(SOA);SQLServer2012ExpressLocalDB
ISV
JDBC(seeJavaDatabaseConnectivity(JDBC))
ODBC(seeOpenDataBaseConnectivity(ODBC))
REST-styleservices
WCF(seeWindowsCommunicationFoundation(WCF))
Datatypes
characters
dateandtime
datecomparison
DATEDIFF()function
datetimeoffset
datetime2variables
example
functions
sample
SQLServer2012
standardtimezones
@start_timevariable
LOB
numerics
nvarchardatatype
(n)varchar(max)/varbinary(max)
timezonesandoffsets
transactionalcoherence
UTCandmilitarytime
WRITEclauseandstringappend
XML
description
existmethod
modifymethod
nodesmethod
querymethod
valuemethod
Datawarehousing(DW)
Datum
DaylightSavingTime(DST)
DCL.SeeDatacontrollanguage(DCL)
DDL.SeeDatadefinitionlanguage(DDL)
DDLtriggers
Debuggingtools
PRINTstatement
SSMSintegration
traceflags
VisualStudioT-SQLdebugger(seeVisualStudioT-SQLdebugger)
Declarativereferentialintegrity(DRI)
Directivevalue
cdata
element
elementxsinil
hide
id,idrefandidrefs
xml
xmltext
DistributedComponentObjectModel(DCOM)
DMFs.SeeDynamicmanagementfunctions(DMFs)
DML.SeeDatamanipulationlanguage(DML)
DMVs.SeeDynamicmanagementviews(DMVs)
DRI.SeeDeclarativereferentialintegrity(DRI)
Dynamicmanagementfunctions(DMFs)
Dynamicmanagementviewsandfunctions(DMVsandDMFs)
categories
connectioninformation
expensivequeries
blockedqueries
cachedqueryplan
indexmetadata
ALTERINDEXstatements
fragmentation
storedprocedure
temporaryobjects
triggers
memory-optimizedsystemviews
retrieve
serverresources
configurationdetails
dumpfiles
instancekeysandvalues
volumeinformation
SQLexecution
OPTION(FORCEORDER)
statements
sys.dm_exec_requests
sys.dm_exec_sessions
summarization
tempdbspacesystem
objectallocations
queries
sessiondata
unusedindexes
waitstats
Dynamicpivottablequery
DynamicSQL
debuggingandtroubleshootingcode
EXECUTEstatement
injection
pivottablequery
E
EF.SeeEntityframework(EF)
EM.SeeEnterpriseManager(EM)
Emptysequence
Encryptionfunctionality
asymmetrickeys
certificates
CREATESYMMETRICKEYstatement
DecryptByKeyfunctions
DecryptByPassPhrase
DES
DMK
DPAPI
DROPKey
EKM
EncryptByPassPhrasefunction
HashBytesfunction
Identityvalueclause
KeyGUIDfunction
RC2
saltandauthenticators
servercertificate
SMK
SQLserver
TDE
TestSymmetricKey
varbinaryformat
ENCRYPTIONoption
EnterpriseManager(EM)
Entitydatamodel(EDM)
Entityframework(EF)
databaseobjects
model
properties
structures
Errorhandling
legacy
RAISERRORstatement
THROWstatement
TRY_CASTfunction
TRY…CATCHmodel(seeTRY…CATCHexceptionhandling)
TRY_CONVERTfunction
TRY_PARSEcommand
ExecuteAndSend()method
ExecuteReader()method
EXECUTEstatement
Extendedevents(XEvents)
configuration
filters
ODBCdrivers
pagesplits/locking
performance-tuningsession
session
SQLServers
targettype
templates
userinterface
Extensiblekeymanagement(EKM)
ExtensibleMarkupLanguage(XML)
AUTOmode
clause
datatype
existmethod
EXPLICITclause
indexes
legacy
modifymethod
nodesmethod
OPENXML
PATHclause
querymethod
RAWmode
schema
SGML
SQLCLRsecuritysettings
SQLServer2014
SQLServer’sprimaryandsecondaryXMLindexes
typed
untyped
valuemethod
WorldWideWebConsortium
XSLtransformations
ExtensibleStylesheetLanguage(XSL)SeealsoXSLtransformations(XSLT)
ExtensibleStylesheetLanguageTransformations(XSLT)
definition
SQLCLRSPcode
SQLServer
stylesheettoconvertdatatoHTML
XMLdocumentintoXHTMLdocument
Extent
Extract,transformandload(ETL)
F
Facet
Filegroupaddition
file_streamcolumn
FILESTREAMsupport
accesslevels
AdventureWorks2014
configurationinformation
enablingtables
existingdatabase
filetable(seeFiletablesupport)
LOBdata
NTFS
SQLServer
FileTableRootPath()function
Filetablesupport
databasecreation
directory
ExploreFileTableDirectory
functions
OpenSqlFilestream
SQLServertables
SSMS
structure
subdirectory
triggers
T-SQLcontext
T-SQLstatements
Filteredindexes
FIRST_VALUEandLAST_VALUEfunction
FLWORexpressions
andfilter
forandreturnkeywords
letkeyword
orderbykeywords
UTF-16support
wherekeyword
Foreignkeyconstraint
Forwardreference
FORXMLPATHclause
FREETEXTpredicate
FTI.SeeFull-textindex(FTI)
Full-textcatalogs
Full-textindex(FTI)
assignation
change-trackingoption
contextmenu
managementpurposes
Production.ProductModeltable
reviewwizardselections
searchesofdataanddocuments
selectablecolumns
single-columnuniqueindex
SSMS
T-SQLstatements
word-breakerlanguage
Full-textquerying
Full-textsearch(FTS)
architecture
AdventureWorksFTCat
beneficialfeatures
CONTAINSpredicate
fdhostprocess
FREETEXTpredicate
FREETEXTTABLEandCONTAINSTABLEfunctions
FTI(seeFull-textindex(FTI))
full-textquerying
andindexes
menuoption
perfomanceoptimization
proceduresanddynamicmanagementviewsandfunctions
simplified
SQLServer
sqlserverprocess
statisticalsemantics
stoplistandtheasauresobjects
thesaurusesandstoplists
T-SQLstatements
Window
SQLServer2012
T-SQLsearch
Full-textstoplist
FunctionsandOperators(F&O)
G
GAM.SeeGlobalallocationmap(GAM)
GeographyMarkupLanguage(GML)
GETDATE()functions
GetEnvironmentVarsCLRprocedure
GetFileNamespacePath()function
GetPathLocator()function
GetYahooNews()function
1Gigabyteprice
Globalallocationmap(GAM)
Groupingset
H
Hardwaresecuritymodule(HSM)
Hashandheap
Hashindexes
vs.clusteredindexIOstatistics
vs.clusteredindexrange
vs.disk-basedclusteredindex
LIKEoperations
Heterogeneoussequence
Hierarchyiddatatype
AdventureWorksBOMs
billofmaterialstable
BomNodecolumn
description
methods
partialresults
Production.HierBillOfMaterialstable
representation
Homogenoussequence
HumanResources.JobCandidateResumeXML
I
IAM.SeeIndexallocationmap(IAM)
IBinarySerializeinterface
IIFstatement
Imperativevs.declarativelanguages
IndependentSoftwareVendor(ISV)
Indexallocationmap(IAM)
Indexes
actualqueryplans
clusteredindexes
computedcolumn
executionplans
filteredindexes
graphicalqueryplans
guaranteedorder
heaps
methodology
nonclusteredindexes
readingqueryplans
recompilations
waits
XEvents(seeExtendedEvents(XEvents))
Indirectrecursion
Inflectionalform
INFORMATION_SCHEMAviews
columninformation
lists
Initializationvector(IV)
Init()method
InlineTVFs
CASEexpressions
comma-delimitedlisttoretrieveproductinformation
control-of-flowstatement
CREATEFUNCTIONstatement
FnCommaSplitfunction
Jackson
NumandElement
SELECTstatement
string-splittingfunction
T-SQL
In-MemoryOLTPtableindexes
hashandrangeindexes
in-memoryvs.disk-basedindexes
In-Memoryprogramming
drivers
hardware
OLTPworkloads
InternationalTelecommunicationUnion(ITU)
IsDescendantOf()method
J,K
JavaDatabaseConnectivity(JDBC)
Class.forName()
classpath
javaccommandline
sqljdbc4.jarfile
L
LAGandLEADfunctions
Languageintegratedquery(LINQ)
AdventureWorksDataContextclass
INNERJOINclause
joinclause
Main()method
.NETlanguages
orderbyclause
retrievingpersonnamesandrelatede-mailaddresses
SQLdatabasequerying
standardqueryoperators
whereclause
Largeobjects(LOB)
FILESTREAM
FILESTREAMfilegroup
SQLServerandNTFS
standardvarchar,nvarcharandvarbinarydatatypes
TEXTPTR,READTEXTandWRITETEXTstatements
varbinary(max)
LayoutKind.Sequentialproperty
lddcommand
Legacy,errorhandling
@@errorsystemfunction
OUTPUTparameter
PRINTstatement
TestErrorprocedure
LEN()stringfunction
LINQ.SeeLanguageintegratedquery(LINQ)
Linux.SeeOpenDataBaseConnectivity(ODBC)
LocalDBclientprogramexecution
Logograms
Logontriggers
CREATETRIGGERstatement
creation
EVENTDATA()function
login
ROLLBACKTRANSACTIONstatement
sampledatatable
serverLOGONevent
SQLserver
M
MARS.SeeMultipleactiveresultsets(MARS)
Materializedpathmodel
MAXRECURSIONoption
Memory-optimizedcontainer
Memory-optimizedfilegroups
Memory-optimizedindex
Memory-optimizedtable
AddressLine1Column
creation
datainsertion
indexproperties
limitations
ManagementStudio
properties
Merge()method
Metadata
MicrosoftJDBCdriver
Microsoft.SqlServer.Server
Moore’sLawtransistor
Multipleactiveresultsets(MARS)
activeresultsets
singleconnection
SqlDataReaderobjects
MultipleCTEs
MultistatementTVFs
bin-packingproblem
businessrules
CREATEFUNCTIONkeywordandRETURNSclause
declaration
DML
fulfillment
GROUPBY
individualinventoryandorder-detailitems
INSERTINTOandSELECTclauses
InventoryDetailssubquery
inventory/orderfillscenario
loop-basedsolution
numberstable
productpulllist
@resulttablevariable
SELECTquery
set-basedproblem
WHERE/JOINclauses
N
Nestedsetsmodel
.NETassembly
.NETclientprogramming
ADO.NET(seeADO.NET)
connecteddataaccess
catchblock
databasetableanditerating
ExecuteReader()method
SqlConnection
SqlDataReader
System.Data.SqlClient
deferredqueryexecution
designer
disconnecteddatasets
EF(seeEntityFramework(EF))
ExecuteXmlReader()method
LINQ(seeLanguageIntegratedQuery(LINQ))
LINQtoSQL
MARS(seeMultipleactiveresultsets(MARS))
nonquery
parameterization
declaration
ExecuteReader()method
injection
SQLstatements
stringquery
queryingentities
SQL
SqlBulkCopy
XML
Newid()Function
Node
comparison
test
types
Nonclusteredindex
bookmarklookup
B-treestructures
clustered-indexcolumns
coveringindex
description
nonclusteredhashindex
queriestypes
RID-lookupandkey-lookupoperations
NonrecursiveCTE
NTFileSystem(NTFS)
NTILEfunction
OVERclause
PARTITIONBYandORDERBY
andranksalespeople
SalesPersonID
SELECTquery
O
Object-relationalmapping(O/RM)
OFFSETandFETCHclauses
client-sidepaging
ORDERBYclause
pagination
queryplan
restrictions
SQLServer
@StartPageNumand@RowsPerPage
OpenClipartsLibrarytable
OpenDatabaseConnectivity(ODBC)
Apt-cachecommand
build_dm.shcommand
Linux
lncommand
sqlcmd
tar.gzformat
TDS
temporarySPs
unixodbcdriver
OpenDataServices(ODS)
OpenGeospatialConsortium(OGC)
Open-worldassumption(OWA)
OPENXML
DocumentObjectModel
edgetableformat
explicitschemadeclaration
fine-grainedXMLdocumentstructure
flagsparameteroptions
legacyXMLfunction
MicrosoftXMLCoreServicesLibrary
rowsetprovider
simpleOPENXMLquery
spxmlpreparedocumentprocedure
WITHclause
Optionaloccurrenceindicator
OVERclause
framesizes
framingclause
ORDERBYandPARTITIONBYclauses
PurchaseOrderDetailtable
runningtotal
SUM
TotalSalesDefaultFraming
windowfunctions
windowingspecifications
P
Pagecompressions
column-prefixcompression
methods
page-dictionarycompression
recommendations
Pagefreespace(PFS)
Parameterization
Parametersniffing
overridden
Production.GetProductsByName
Production.Producttable
queryplan
Parent_path_locatorColumn
Parsemethod
Pathexpression
PathName()
PERCENTILE_CONTandPERCENTILE_DISCfunction
Performanceenhancementandtuning
indexes(seeIndexes)
SQLserverstorage
description
fileandfilegroups
pagecompression
partitions
rowcompression
spaceallocation
sparsecolumns
PFS.SeePagefreespace(PFS)
PIVOToperatorpivottable
Primaryexpressions,XQuery
contextitem
datatypeconstructor/functioncall
functioncalls
literals
parenthesized
variable
PRINTstatement
Proceduralcode
control-of-flowstatements(seeControl-of-flowstatements)
cursors(seeCursors)
SQL3VL
T-SQLcontrol-of-flowconstructs
3VL(seeThree-valuedlogic(3VL))
Proceduralcode.SeeCASEexpressions
Q
Queryplan
QUOTED_IDENTIFIERoption
R
RAISERRORstatement
RAM’sprice
Rangeindexes
B-treedisk-basedtable
comparisons
memory-optimizednonclusteredindex
nonclusteredindex
single-pointlookup
RANKandDENSE_RANKfunctions
AdventureWorks’dailysalestotals
differences
OrderMonthcolumn
OVERclause
PARTITIONBYclause
rankingvalue
SELECTquery
WHEREclause
Read()andWrite()methods
Recompilation,SPs
dbo.GetRecompiledProcs
Executestatement
plan_generation_num
queryplans
SalesPersonIdparameter
Sales.SalesOrderHeader
selectivity
SQLServer
statement-level
statistics
Recursion,SPs
dbo.SolveTowers
Hanoipuzzle
multipletimes
RecursiveCTE
AdventureWorksdatabase
anchorquery
BOM
ComponentIDandProductAssemblyID
MAXRECURSIONoption
nameandcolumnlist
restrictions
SELECTstatement
simple
UNIONALL
RepresentationalStateTransfer(REST)
RETURNSNULLONNULLINPUToption
Rowcompressions
Rowconstructor
ROW_NUMBERfunction
S
Scalarfunctions
CASEexpression
CREATEFUNCTIONstatement
creation-timeoptions
CTE
proceduralcode
AdventureWorksdatabase
CASEexpressions
CREATETABLEstatement
dbo.EncodeNYSIISfunction
encodestrings
INSERTstatement
name-basedsearching
numberstable
NYSIISencodingrules
SOUNDEXalgorithm
WHEREclause
recursion
RETURNSkeyword
SELECTstatements
singleatomicvalue
Semantickeyphrasetablefunction
Semanticsdatabase
SendResultsRow()method
SendResultsStart()method
Servercertificate
Servicemasterkeys(SMK)
administrativetasks
Alterstatement
BackupandRestore
ControlServer
Forcekeyword
SQLServerencryptionkeyhierarchy
ServiceOrientedArchitecture(SOA)
DCOMandCORBA
HTTPrequests
ODBC
RESTful
WCFdataservice(seeWCFdataservice)
WCFlayersstack
webservices(WS)
SETNOCOUNTONstatement
SetValue()method
SGAM.SeeSharedglobalallocationmap(SGAM)
Sharedglobalallocationmap(SGAM)
Shredding
SimpleObjectAccessProtocol(SOAP)
Solidstatedrive(SSD
SOUNDEXalgorithm
Spatialdatatypes
coordinatepair
flatspatialrepresentation
GML
MichiganandtheGreatLakes
MultiPolygon
OGCstandard
polygon
spatialinstancetype(seeSpatialinstancetype)
SRID
theUSCensusBureau’sTIGER/Linedata
WKTstrings
Wyoming
Spatialindex
Spatialinstancetypes
GeometryCollection
hierarchy
LineString
MultiLineString
MultiPoint
MultiPolygon
Point
Polygon
Spatialreferenceidentifier(SRID)
sp_executesqlstoredprocedure
client-sideparameterization
dynamicSQLexecutes
limitation
parameterization
Splitter
SPRETURNstatement
SPs.SeeStoredprocedures(SPs)
SQL.SeeLanguageintegratedquery(LINQ);StructuredQueryLanguage(SQL)
SQLCASEexpressions
SQLCMD
command-lineoptions
commands
scriptingvariable
utility
interactiveprompt
scriptingvariables
SQLCMDDBNAMEenvironmentvariable
SQLCMDPASSWORDenvironmentvariable
SQLCMDUSERenvironmentvariable
SQLDistributedManagementObjects(SQL-DMO)
SQL-DMO.SeeSQLDistributedManagementObjects(SQL-DMO)
SQLinjection
EXECUTEmethod
queries
T-SQLstringvalidationfunction
SQLLocalDB.exeinfoSQLSrvWebApp1command
SQLpredicate
SQLServer
datatools
injection
SQLServer2012
SQLServer2012ExpressLocalDB
AttachDBFilename
automaticinstances
createandstartoption
databasenames
localdbkeyword
.mdfand.ldffile
MSIinstallations
namedinstance
securitymodels
Serverless
sqlcmdcommand
SQLServer2012NativeClient(SNAC).SeeSQLServer2012ExpressLocalDB
SQLServerDataTools(SSDT)
SQLServerIntegrationServices(SSIS)
SQLServerManagementStudio(SSMS)
codesnippets
context-sensitive
editingoptions
EM
features
full-textindexwizard
graphicalqueryexecutionplans
IntelliSense
keyboardshortcutscheme
newfull-textcatalog
ObjectExplorer
project-managementfeatures
T-SQLdebugging
SQLserverstorage
data
description
fileandfilegroups
pagecompression
partitions
rowcompression
spaceallocation
dataallocations
dbo.SmallRowsquery
estimatedI/Ocost
GAM
IAMandPFS
I/Ocomparison
limitation
narrowrows
random-accessfile
SELECTquery
SGAM
sparsecolumns
andnonsparsetables
NULLvalue
sets
spacesavings,columns
SQLServeruses
SQLserverXQueryexpressions
SQL2014supportedaxisspecifiers
SqlTriggerContextclass
SqlUserDefinedAggregateattribute
SSDT.SeeSQLServerDataTools(SSDT)
SSIS.SeeSQLServerIntegrationServices(SSIS)
SSMS.SeeSQLServerManagementStudio(SSMS)
SSMSintegration
STIntersection()method
Storedprocedures(SPs)
AdventureWorksbusiness
aggregatefunctions
ALTERPROCEDURE
bestpractices
businessreportingtask
CREATEPROCEDURE
CTE
dbo.MyProc
description
DLL
DMFs
DMVs
DropProcedure
Environment.GetEnvironmentVariables()functions
exception
execution
front-endapplications
GetEnvironmentVarsCLRprocedure
GetProcStatsprocedure
intparameter
memory-optimization
metadata
namespaces
Native_Compilation
nativemachinecode
ODBC
OLTPdatabase
parameters
Person.GetEmployee_inmem
@ProductID
Production.Producttable
productlists
query-planrecompilation
recompilation
recursion.Recursion,SPs
returncode
RETURNstatement
runningsum
runningtotal,sales
Sales.SalesOrderDetail
SampleProcclass
Schemabinding
SendResultsStart()method
SqlProcedure()
statistics
subroutines
sys.dmexecsqltext
table-valuedparameters
tempdbdatabase
temporarytables
TVFs
UDFs
String()function
StructuredQueryLanguage(SQL)
BOL
CLRassembly
databases
indexes
profiler
schemas
SPs
statements
components
orderofexecution
relationalmodel
subsets
three-valuedlogic
tables
transactionlogs
UDFs
views
SWITCHOFFSET()function
SYSDATETIME()function
SYSDATETIMEOFFSET()function
Sys.dm_fts_parser
System.Data.SqlClient
System.Data.SqlClientnamespace
System.Data.SqlTypes
SYSUTCDATETIME()function
T
TableProduction.HierBillOfMaterials
Table-valuedfunctions(TVFs)
inlinefunction.InlineTVFs
multistatement.MultistatementTVFs
Table-valuedparameters
CreateTypestatement
HumanResources.GetEmployees
HumanResources.LastNameTableType
intermediateformat
UDFs
variables
TabularDataStream(TDS)
TCL.SeeTransactionalControlLanguage(TCL)
Terminate()function
Terminate()method
Three-valuedlogic(3VL)
CWA
ISNULLandISNOTNULL
NULL
propositions
quickreferencechart
true,false,andunknown
THROWstatement
TODATETIMEOFFSET()function
ToString()method
TransactionalControlLanguage(TCL)
Transact-SQL(T-SQL)
elements
defensivecoding
namingconventions
oneentryandoneexit
SELECT*statement
variableinitialization
whitespace
history
imperativevs.declarativelanguages
SQL(seeStructuredQueryLanguage(SQL))
Transistors
Transparentdataencryption(TDE)
Triggers
CDC(seeChangedatacapture(CDC))
DDL(seeDatadefinitionlanguage(DDL))
DML(seeDatamanipulationlanguage(DML))
E-mailaddress,validation
INSERT/UPDATEstatement
invalidE-mailaddress
Logon
namespaces
Transaction.Current.Rollback()method
T-SQLtrigger
UPDATEstatement
validation
views
TRY_CASTfunction
TRY…CATCHexceptionhandling
CATCHblockfunctions
limitations
samplecode
XACT_STATEfunction
TRY_CONVERTfunction
TRY_PARSEcommand
T-SQL.SeeTransact-SQL(T-SQL)
T-SQLUDFstypes
TypedXMLvariable
U
UDFs.SeeUser-definedfunctions(UDFs)
Uniondatatypes
Uniqueidentifierdatatype
GUIDs
Newid()function
NEWSEQUENTIALID()function
sequentialGUIDs
usageof
UntypedXMLvariable
UPDATE()andCOLUMNS_UPDATED()functions
COALESCE()function
COLUMNPROPERTY()function
description
NOCOUNTON
@@ROWCOUNT
standardsizes
testing
triggerdefinition
unit-of-measurevalidation
User-definedaggregates(UDAs)
Accumulate()method
advancescreation
Merge()method
properties
Read()andWrite()methods
statisticalmedian
Terminate()method
Merge()method
methods
namespaces
results
SQLCLRroutine
statisticalrange
structdeclaration
Terminate()function
User-defineddatatypes(UDTs)
advantages
attributes
declaration
description
IsNullandNullproperties
NULL
Parsemethod
staticproperties
ToString()method
User-definedfunctions(UDFs)
CREATEASSEMBLYwithEXTERNAL_ACCESSpermissionset
CREATEFUNCTIONstatement
description
EmailMatchfunction
encryption
exercises
expression
fill-rowmethod
GetYahooNews()Function
IsMatchfunction
parameters
project
restrictions
database
deterministicfunction
nondeterministicfunctions
requirements
results
scalarfunctions.Scalarfunctions
SqlFunction
TVFs(seeTable-valuedfunctions(TVFs))
YahooRSS
User-definedtype(UDT)
UTF-16
createrecord
and_SCcollation
SQLServer
V
Valuecomparison
VisualStudioT-SQLdebugger
dbo.uspGetBillOfMaterialsprocedure
debugmode
outputwindow
W
WCFdataservice
applicationproject
consumer
creation
definition
entityaccessrules
entitydatamodel
pagecalling
payloadtypes
queries
serviceentity/operation
stringoptions
Webservices(WS)
Well-formedXML
Well-knowntext(WKT)strings
WestcoastcustomerswithsimpleCASEexpression
Whitespace
Windowfunctions
datasetpartitions
exercises
NTILEfunction
OFFSET/FETCHclauses
OVERclause
RANKandDENSE_RANKfunctions(seeRANKandDENSE_RANKfunctions)
ROW_NUMBERfunction
WindowsCommunicationFoundation(WCF)
applicationproject
consumerapplication
aspxpage
features
foreach
namespace
PageLoadevent
PopulateDropDown()function
servicereference
UpdateImage()function
creation
dataservices(seeWCFdataservice)
definition
entitydatamodel
WITHRECOMPILEoption
WITHSCHEMABINDINGoption
Word-breakingandstemming
WorldWideWebConsortium(W3C)
WRITEClause
andstringappend
simplestringconcatenation
UPDATEstatement
WyomingPolygon
X,Y,Z
XMLclause
XMLdatamodel
XMLEXPLICITclause
XMLindex
creationoptions
executioncostofthequery
primary
queryexecutioncost
readerwithXPathfilter
resumecolumn
retrievingcandidatenameswithFLWORexpression
retrievingjobcandidateswithbachelor’sdegrees
secondary
SQLServer
XMLPATHclause
XMLRAWmode
XPath
attributes
columnswithoutnamesandwildcards
datafunction
elementgrouping
expressions
FORXMLPATHuses
namesande-mailaddresses
node
nodetests
andNULL
sequence
SQLServer
WITHXMLNAMESPACESclause
XMLpathlanguage
andXQueryXMLtrees
XQuery
arithmeticexpressions
axisspecifiers
comments
comparison
conditionalexpressions(if…then…else)
constructorsandcasting
datatypes
dateformat
dynamicXMLconstruction
expressionsandsequences
FLWORexpressions(seeFLWORexpressions)
functions
generalcomparisonoperators
integerdivision
locationpaths
namespaces
node
comparisons
DOM
tests
predicates
primitive
querymethod
sequence
SQLServer
step