-
Notifications
You must be signed in to change notification settings - Fork 19
/
16_TVFs_Demo.sql
326 lines (249 loc) · 8.01 KB
/
16_TVFs_Demo.sql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
/******************************************************************************
Course videos: https://www.red-gate.com/hub/university/courses/t-sql/tsql-for-beginners
Course scripts: https://litknd.github.io/TSQLBeginners
Table Valued Functions (with a bit of CROSS APPLY)
EXAMPLES FOR STUDY
Documentation:
https://docs.microsoft.com/en-us/sql/relational-databases/user-defined-functions/create-user-defined-functions-database-engine#Scalar
https://www.red-gate.com/simple-talk/sql/t-sql-programming/sql-server-functions-the-basics
*****************************************************************************/
/* ✋🏻 Doorstop ✋🏻 */
RAISERROR ( N'Did you mean to run the whole thing?', 20, 1 ) WITH LOG ;
GO
/******************************************************************************
Set up sample data
*****************************************************************************/
USE master ;
GO
IF DB_ID ( 'TSQLSchool' ) IS NOT NULL DROP DATABASE TSQLSchool ;
GO
CREATE DATABASE TSQLSchool ;
GO
USE TSQLSchool ;
GO
--DROP IF EXISTS is SQL Server 2016 SP3
DROP TABLE IF EXISTS dbo.doggos, dbo.jobbos, dbo.doggoswithjobbos ;
GO
--Let's normalize this schema!
CREATE TABLE dbo.doggos
(dogid INT IDENTITY,
doggo VARCHAR(128) NOT NULL,
CONSTRAINT pk_doggos PRIMARY KEY CLUSTERED(dogid)) ;
GO
CREATE TABLE dbo.jobbos
(jobid INT IDENTITY,
jobbo VARCHAR(128) NOT NULL,
CONSTRAINT pk_jobbos PRIMARY KEY CLUSTERED(jobid)) ;
GO
CREATE TABLE dbo.doggoswithjobbos
(dobbojobboid INT IDENTITY,
dogid INT NOT NULL,
jobid INT NOT NULL,
CONSTRAINT pk_doggoswithjobbos PRIMARY KEY CLUSTERED(dobbojobboid),
INDEX uq_dogid_jobid(dogid, jobid)) ;
GO
--And now, some foreign keys
ALTER TABLE dbo.doggoswithjobbos
ADD CONSTRAINT fk_doggoswithjobbos_dogid FOREIGN KEY(dogid)REFERENCES dbo.doggos(dogid) ;
GO
ALTER TABLE dbo.doggoswithjobbos
ADD CONSTRAINT fk_doggoswithjobbos_jobid FOREIGN KEY(jobid)REFERENCES dbo.jobbos(jobid) ;
GO
INSERT dbo.doggos(doggo)VALUES('Mister'), ('Stormy') ;
GO
INSERT dbo.jobbos(jobbo)
VALUES
('Tootstah'),
('Cuddler'),
('Patrollster'),
('Mushroom'),
('Best Fren') ;
GO
INSERT dbo.doggoswithjobbos(dogid, jobid)
VALUES
(1, 1),
(1, 3),
(1, 5),
(2, 2),
(2, 4),
(2, 5) ;
--Not very readable, is it?
SELECT * FROM dbo.doggoswithjobbos ;
GO
/******************************************************************************
Create some scalar UDFs
Scalar: returns one value
UDFs: user defined functions
*****************************************************************************/
--CREATE OR ALTER is SQL Server 2016 SP1+
--Before that you have DROP and you have CREATE
--Scalar UDF from last week
CREATE OR ALTER FUNCTION dbo.jobidtojobbo(@jobid INT)
RETURNS VARCHAR(128)
AS
BEGIN
DECLARE @jobbo VARCHAR(128) ;
SELECT @jobbo=jobbo FROM dbo.jobbos WHERE jobid=@jobid ;
RETURN @jobbo ;
END ;
GO
--Single statement tvf
CREATE OR ALTER FUNCTION dbo.jobidtojobbo_tvf(@jobid INT)
RETURNS TABLE
AS
RETURN
SELECT jobbo FROM dbo.jobbos WHERE jobid=@jobid ;
GO
--Scalar UDF from last week
CREATE OR ALTER FUNCTION dbo.dogidtodoggo(@dogid INT)
RETURNS VARCHAR(128)
AS
BEGIN
DECLARE @doggo VARCHAR(128) ;
SELECT @doggo=doggo FROM dbo.doggos WHERE dogid=@dogid ;
RETURN @doggo ;
END ;
GO
--Single statement tvf
CREATE OR ALTER FUNCTION dbo.dogidtodoggo_tvf(@dogid INT)
RETURNS TABLE
AS
RETURN
SELECT doggo FROM dbo.doggos WHERE dogid=@dogid ;
GO
/******************************************************************************
Example: UDF in SELECT and WHERE
*****************************************************************************/
--Scalar UDFs (from last week)
SELECT
dbo.dogidtodoggo ( dogid ) AS doggo,
dbo.jobidtojobbo ( jobid ) AS jobbo
FROM dbo.doggoswithjobbos
WHERE
dbo.dogidtodoggo ( dogid ) = 'Mister' ;
GO
--Hmm, I can't just swap in the TVF names?
--Well, I guess I have to think, then.
SELECT
dbo.dogidtodoggo_tvf ( dogid ) AS doggo,
dbo.jobidtojobbo_tvf ( jobid ) AS jobbo
FROM dbo.doggoswithjobbos
WHERE
dbo.dogidtodoggo_tvf ( dogid ) = 'Mister' ;
GO
--Compare estimated and actual
--TFV using subqueries
--This syntax is awkward, LOL
SELECT
(SELECT doggo FROM dbo.dogidtodoggo_tvf ( dogid )) AS doggo,
(SELECT jobbo FROM dbo.jobidtojobbo_tvf ( jobid )) AS jobbo
FROM dbo.doggoswithjobbos
WHERE
(SELECT doggo FROM dbo.dogidtodoggo_tvf ( dogid )) = 'Mister';
GO
--Example with joins (no function at all)
SELECT
d.doggo,
j.jobbo
FROM dbo.doggoswithjobbos AS dwj
JOIN dbo.doggos AS d ON dwj.dogid = d.dogid
JOIN dbo.jobbos AS j ON dwj.jobid = j.jobid
WHERE
d.doggo = 'Mister' ;
--Compare that to this:
--We can use CROSS APPLY syntax with TVFs
--This gives us a way to join to these single statement TVFs in an efficient way
SELECT
d.doggo,
j.jobbo
FROM dbo.doggoswithjobbos AS dwj
CROSS APPLY (SELECT doggo FROM dbo.dogidtodoggo_tvf ( dwj.dogid )) AS d
CROSS APPLY (SELECT jobbo FROM dbo.jobidtojobbo_tvf ( dwj.jobid )) AS j
WHERE
d.doggo = 'Mister';
GO
/* Docs on APPLY (introduced in SQL Server 2005):
https://docs.microsoft.com/en-us/sql/t-sql/queries/from-transact-sql?view=sql-server-ver15#using-apply
For super nerdery...
Apply is a "correlated join", for more info see Paul White's answer here: https://dba.stackexchange.com/questions/75048/outer-apply-vs-left-join-performance
*/
/******************************************************************************
Example: UDF in GROUP BY
*****************************************************************************/
--Scalar UDFs from last week
SELECT
dbo.jobidtojobbo ( jobid ) AS jobbo,
COUNT ( * ) AS doggocount
FROM dbo.doggoswithjobbos
GROUP BY dbo.jobidtojobbo ( jobid ) ;
GO
--Rewrite with the TVF....?
SELECT
(SELECT jobid FROM dbo.jobidtojobbo_tvf ( jobid )) AS jobbo,
COUNT ( * ) AS doggocount
FROM dbo.doggoswithjobbos
GROUP BY (SELECT jobid FROM dbo.jobidtojobbo_tvf ( jobid )) ;
GO
--OK, well let's use CROSS APPLY
SELECT
j.jobbo,
COUNT ( * ) AS doggocount
FROM dbo.doggoswithjobbos
CROSS APPLY (SELECT jobbo FROM dbo.jobidtojobbo_tvf ( jobid )) AS j
GROUP BY j.jobbo ;
--Compare to equivalent with no function
SELECT
j.jobbo,
COUNT (*) AS doggocount
FROM dbo.doggoswithjobbos AS dwj
JOIN dbo.doggos AS d ON dwj.dogid = d.dogid
JOIN dbo.jobbos AS j ON dwj.jobid = j.jobid
GROUP BY j.jobbo ;
GO
/******************************************************************************
You can nest functions in different ways
Let's also compare something else while we show one form of nesting:
single statement vs multi-statement TVFs
*****************************************************************************/
--Single statement TVF (with nested TVF)
CREATE OR ALTER FUNCTION dbo.jobidtodoggos_tvf(@jobid INT)
RETURNS TABLE
AS
RETURN
--STRING_AGG is SQL Server 2017+
SELECT STRING_AGG(CAST(d.doggo AS VARCHAR(MAX)), ',') AS doggos
FROM dbo.doggoswithjobbos
CROSS APPLY (SELECT doggo FROM dbo.dogidtodoggo_tvf(dogid) ) AS d
WHERE jobid = @jobid;
GO
--multi-statement TVF (with nested TVF)
CREATE OR ALTER FUNCTION dbo.jobidtodoggos_mstvf(@jobid INT)
RETURNS @mytable TABLE (doggos NVARCHAR(MAX))
AS
BEGIN
INSERT @mytable (doggos)
SELECT STRING_AGG(CAST(d.doggo AS VARCHAR(MAX)), ',') AS doggos
FROM dbo.doggoswithjobbos
CROSS APPLY (SELECT doggo FROM dbo.dogidtodoggo_tvf(dogid) ) AS d
WHERE jobid = @jobid;
RETURN;
END
GO
--Compare and contrast estimated plans
SELECT
j.jobbo AS jobbo,
(SELECT doggos from dbo.jobidtodoggos_mstvf(jobid)) AS doggo_list
FROM dbo.jobbos AS j;
SELECT
j.jobbo AS jobbo,
(SELECT doggos from dbo.jobidtodoggos_tvf(jobid)) AS doggo_list
FROM dbo.jobbos AS j;
--Compare to:
SELECT
j.jobbo,
STRING_AGG(d.doggo, ',') AS doggo_list
FROM dbo.doggoswithjobbos AS dwj
JOIN dbo.doggos AS d ON dwj.dogid = d.dogid
JOIN dbo.jobbos AS j ON dwj.jobid = j.jobid
GROUP BY j.jobbo ;
GO