-
Notifications
You must be signed in to change notification settings - Fork 0
/
sql_challenge_questions.sql
1586 lines (1480 loc) · 57.6 KB
/
sql_challenge_questions.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
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
-- https://github.com/Microsoft/sql-server-samples/releases/download/adventureworks/AdventureWorks2019.bak
USE [AdventureWorks2019]
GO
/*
* Create a procedure that's called from a CATCH block and prints the following information. Output will be a message as opposed to a result.
* Error Number
* Error Severity
* Error State
* Error Procedure
* Error Line
* Error Message
*/
CREATE OR ALTER PROCEDURE [dbo].[p_report_error_test_procedure]
AS
BEGIN
SET NOCOUNT, XACT_ABORT ON;
BEGIN TRY
SELECT 1 / 0; -- divide by zero error
END TRY
BEGIN CATCH
EXEC [dbo].[p_report_error];
END CATCH
END
GO
CREATE OR ALTER PROCEDURE [dbo].[p_report_error]
AS
BEGIN
SET NOCOUNT, XACT_ABORT ON;
PRINT 'Error ' + CAST(ERROR_NUMBER() AS VARCHAR(10));
PRINT 'Severity ' + CAST(ERROR_SEVERITY() AS VARCHAR(10));
PRINT 'State ' + CAST(ERROR_STATE() AS VARCHAR(10));
PRINT 'Procedure ' + CAST(ERROR_PROCEDURE() AS VARCHAR(50));
PRINT 'Line ' + CAST(ERROR_LINE() AS VARCHAR(10));
PRINT ERROR_MESSAGE();
END
GO
EXEC [dbo].[p_report_error_test_procedure];
GO
-- Cleanup
DROP PROCEDURE [dbo].[p_report_error];
DROP PROCEDURE [dbo].[p_report_error_test_procedure];
GO
/*
* Create a procedure that accepts a BusinessEntityID and returns the following information:
* LoginID
* JobTitle
* BirthDate
* MaritalStatus
* Gender
* HireDate
* Current PayRate and PayFrequency
* Current DepartmentName
*
* Check incoming parameters for validity and only return information if the employee is currently employed.
*
* Test
* EXEC [dbo].[p_get_employee_info] 4
* EXEC [dbo].[p_get_employee_info] 16
* EXEC [dbo].[p_get_employee_info] 224
* EXEC [dbo].[p_get_employee_info] 234
* EXEC [dbo].[p_get_employee_info] 250
*/
CREATE OR ALTER PROCEDURE [dbo].[p_get_employee_info] @BusinessEntityID INT
AS
BEGIN
SET NOCOUNT, XACT_ABORT ON;
IF NOT EXISTS (SELECT [BusinessEntityID] FROM [HumanResources].[Employee] WHERE [BusinessEntityID] = @BusinessEntityID)
BEGIN
RAISERROR ('Employee not found.',16,1);
END
IF NOT EXISTS (SELECT [CurrentFlag] FROM [HumanResources].[Employee] WHERE [BusinessEntityID] = @BusinessEntityID)
BEGIN
RAISERROR ('Employee is not active.',16,1);
END
;WITH CurrentPayRate AS (
SELECT ROW_NUMBER() OVER (PARTITION BY [Rate] ORDER BY [RateChangeDate] DESC) AS RowNum
FROM [HumanResources].[EmployeePayHistory]
)
SELECT TOP(1) e.[LoginID]
, e.[JobTitle]
, e.[BirthDate]
, e.[MaritalStatus]
, e.[Gender]
, e.[HireDate]
, [PayRate] = eph.[Rate]
, eph.[PayFrequency]
, [DepartmentName] = d.[Name]
FROM [HumanResources].[Employee] AS e
, [HumanResources].[EmployeePayHistory] AS eph
, [HumanResources].[EmployeeDepartmentHistory] AS edh
, [HumanResources].[Department] AS d
WHERE e.[BusinessEntityID] = eph.[BusinessEntityID]
AND e.[BusinessEntityID] = edh.[BusinessEntityID]
AND edh.[DepartmentID] = d.[DepartmentID]
AND e.[BusinessEntityID] = @BusinessEntityID
AND edh.[EndDate] IS NULL
ORDER BY eph.[RateChangeDate] DESC;
END
GO
-- Cleanup
DROP PROCEDURE [dbo].[p_get_employee_info];
GO
/*
* Create a procedure that returns: ProductID, ProductName, CultureID, and Description.
* The procedure should accept two parameters, ProductID and CultureID.
* If not specified by the caller, the parameters should default to a NULL value.
* If specified the result should be limited to a matching ProductID and CultureID.
* If not specified, the procedure should return all values.
*
* Test
* EXEC [dbo].[p_get_product_info] NULL, NULL
* EXEC [dbo].[p_get_product_info] NULL, 'ar'
* EXEC [dbo].[p_get_product_info] 980, NULL
* EXEC [dbo].[p_get_product_info] 931, 'zh-cht'
* EXEC [dbo].[p_get_product_info] 931, 'en'
* EXEC [dbo].[p_get_product_info] 931, 'fr'
*/
CREATE OR ALTER PROCEDURE [dbo].[p_get_product_info] @ProductID INT = NULL, @CultureID NCHAR(6) = NULL
AS
BEGIN
SET NOCOUNT, XACT_ABORT ON;
SELECT [ProductID]
, [ProductName] = [Name]
, [CultureID]
, [Description]
FROM [Production].[vProductAndDescription]
WHERE [ProductID] = IIF(@ProductID IS NULL, [ProductID], @ProductID)
AND [CultureID] = IIF(@CultureID IS NULL, [CultureID], @CultureID);
END
GO
-- Cleanup
DROP PROCEDURE [dbo].[p_get_product_info];
GO
/*
* Create a scalar function that accepts a single tinyint parameter named @Status.
* The function should return the following:
* When a status of 1 is passed, return the unicode string 'Pending Approval'
* When a status of 2 is passed, return the unicode string 'Approved'
* When a status of 3 is passed, return the unicode string that 'Obsolete'
* Else, the function should return the unicode string '** Invalid **'
*
* Test
* SELECT [dbo].[f_get_document_status_text](0);
* SELECT [dbo].[f_get_document_status_text](1);
* SELECT [dbo].[f_get_document_status_text](2);
* SELECT [dbo].[f_get_document_status_text](3);
*/
CREATE OR ALTER FUNCTION [dbo].[f_get_document_status_text] (@Status TINYINT) RETURNS NVARCHAR(16)
-- Do not call the function on a NULL input (WITH RETURNS NULL ON NULL INPUT).
-- Because no data manipulation is being performed, tell the optimizer not to build a spool table (SCHEMABINDING).
WITH RETURNS NULL ON NULL INPUT, SCHEMABINDING
AS
BEGIN
RETURN CASE @Status
WHEN 1 THEN 'Pending Approval'
WHEN 2 THEN 'Approved'
WHEN 3 THEN 'Obsolete'
ELSE '** Invalid **'
END;
END
GO
-- Cleanup
DROP FUNCTION [dbo].[f_get_document_status_text];
GO
/*
* Create a inline table valued function that accepts from 1 to 6 CustomerIDs.
* The function should return a table that contains the following columns from the SalesOrderHeader table:
* [CustomerID]
* [SalesOrderID]
* [OrderDate]
* [SubTotal]
* [TaxAmt]
*
* Test
*
SELECT *
FROM [dbo].[f_get_customer_sales](11000, 11001, 11002, 11003, 11004, 1100);
*
* Result Key
*
* CustomerID SalesOrderID OrderDate SubTotal TaxAmt
* 11000 43793 2011-06-21 00:00:00.000 3399.99 271.9992
* 11000 51522 2013-06-20 00:00:00.000 2341.97 187.3576
* 11000 57418 2013-10-03 00:00:00.000 2507.03 200.5624
* 11001 43767 2011-06-17 00:00:00.000 3374.99 269.9992
* 11001 51493 2013-06-18 00:00:00.000 2419.93 193.5944
* 11001 72773 2014-05-12 00:00:00.000 588.96 47.1168
* 11002 43736 2011-06-09 00:00:00.000 3399.99 271.9992
* 11002 51238 2013-06-02 00:00:00.000 2294.99 183.5992
* 11002 53237 2013-07-26 00:00:00.000 2419.06 193.5248
* 11003 43701 2011-05-31 00:00:00.000 3399.99 271.9992
* 11003 51315 2013-06-07 00:00:00.000 2318.96 185.5168
* 11003 57783 2013-10-10 00:00:00.000 2420.34 193.6272
* 11004 43810 2011-06-25 00:00:00.000 3399.99 271.9992
* 11004 51595 2013-06-24 00:00:00.000 2376.96 190.1568
* 11004 57293 2013-10-01 00:00:00.000 2419.06 193.5248
*/
CREATE OR ALTER FUNCTION [dbo].[f_get_customer_sales] (@CustomerId1 INT = 0, @CustomerId2 INT = 0, @CustomerId3 INT = 0, @CustomerId4 INT = 0, @CustomerId5 INT = 0, @CustomerId6 INT = 0)
RETURNS TABLE
AS
RETURN (
SELECT [CustomerID], [SalesOrderID], [OrderDate], [SubTotal], [TaxAmt]
FROM [Sales].[SalesOrderHeader]
WHERE [CustomerID] IN (@CustomerId1, @CustomerId2, @CustomerId3, @CustomerId4, @CustomerId5, @CustomerId6)
)
GO
-- Cleanup
DROP FUNCTION [dbo].[f_get_customer_sales];
GO
/*
* Create a multi-statement table valued function that accepts a single integer parameter @ScrapCompareLevel.
* The function should return a table that contains the following columns:
* [Product Name], [Scrap Quantity], [Scrap Reason], [Scrap Status]
*
* The function should test the Production.WorkOrder ScrapReasonID. If it is not null, the row should be included in the result set.
* For each row in the result set, if the ScrapQty is greater than the @ScrapCompareLevel the status should be set to Critical, otherwise it should be set to Normal.
*
* Test
SELECT ProductName AS [Product Name], ScrapQty AS [Scrap Quantity], ScrapReasonDef AS [Scrap Reason], ScrapStatus AS [Scrap Status]
FROM dbo.f_get_products_scrap_status(20)
*
* Result Key - limited to the first 10 rows
* Product Name Scrap Quantity Scrap Reason Scrap Status
* BB Ball Bearing 35 Brake assembly not as ordered Critical
* Blade 206 Brake assembly not as ordered Critical
* Chain Stays 3 Brake assembly not as ordered Normal
* Down Tube 2 Brake assembly not as ordered Normal
* Front Derailleur 53 Brake assembly not as ordered Critical
* Head Tube 26 Brake assembly not as ordered Critical
* HL Fork 26 Brake assembly not as ordered Critical
* HL Mountain Frame - Silver, 38 2 Brake assembly not as ordered Normal
* HL Mountain Seat Assembly 6 Brake assembly not as ordered Normal
* HL Road Frame - Black, 48 1 Brake assembly not as ordered Normal
*/
CREATE OR ALTER FUNCTION [dbo].[f_get_products_scrap_status] (@ScrapCompareLevel INT)
RETURNS @ScrapStats TABLE ([ProductName] NVARCHAR(50), [ScrapQty] SMALLINT, [ScrapReasonDef] NVARCHAR(50), [ScrapStatus] NVARCHAR(8))
AS
BEGIN
INSERT INTO @ScrapStats ([ProductName], [ScrapQty], [ScrapReasonDef], [ScrapStatus])
SELECT TOP(10) p.[Name]
, wo.[ScrappedQty]
, sr.[Name]
, CASE WHEN wo.[ScrappedQty] > @ScrapCompareLevel THEN 'Critical' ELSE 'Normal' END AS [ScrapStatus]
FROM [Production].[WorkOrder] AS wo, [Production].[ScrapReason] AS sr, [Production].[Product] AS p
WHERE wo.[ProductID] = p.[ProductID]
AND wo.[ScrapReasonID] = sr.[ScrapReasonID]
AND wo.[ScrapReasonID] IS NOT NULL
RETURN
END
GO
-- Cleanup
DROP FUNCTION [dbo].[f_get_products_scrap_status];
GO
/*
* Select the total for Subtotal, Tax, and Freight for all orders.
* Show results: Sales, Taxes, Freight, Total Due.
*
* Result Key:
*
* Sales Taxes Freight Total Due
* --------------------- --------------------- --------------------- ---------------------
* 109846381.4039 10186974.4602 3183430.2518 123216786.1159
*/
SELECT [Sales] = SUM([SubTotal])
, [Taxes] = SUM([TaxAmt])
, [Freight] = SUM([Freight])
, [Total Due] = SUM([TotalDue])
FROM [Sales].[SalesOrderHeader];
GO
/*
* Select the Tax pct for all orders. Estimate the pct relative to sales.
* Show results: Tax, Sales, Tax pct.
*
* Result Key:
*
* TaxAmt SubTotal Tax pct
* --------------------- --------------------- ---------------------
* 1.6149 20.1865 7.99
* ...
* (31465 rows affected)
*/
SELECT [TaxAmt]
, [SubTotal]
, [Tax pct] = ([TaxAmt] / [SubTotal]) * 100
FROM [Sales].[SalesOrderHeader]
ORDER BY [Tax pct];
GO
/*
* Select the Freight pct for all orders. Estimate the pct relative to sales.
* Show results: Freight, Sales, Freight pct
*
* Result Key:
*
* Freight SubTotal Freight pct
* --------------------- --------------------- ---------------------
* 25.0109 1000.4375 2.49
* ...
* (31465 rows affected)
*/
SELECT [Freight]
, [SubTotal]
, [Freight pct] = ([Freight] / [SubTotal]) * 100
FROM [Sales].[SalesOrderHeader]
ORDER BY [Freight pct];
GO
/*
* Select the average value of an order by year and month.
* Show results: Year, Month, Avg Value of Orders
*
* Result Key:
*
* Year Month Order Avg Value
* ----------- ----------- ---------------------
* 2011 5 11716.4166
* ...
* (38 rows affected)
*/
SELECT [Year] = YEAR([OrderDate])
, [Month] = MONTH([OrderDate])
, [Order Avg Value] = AVG([SubTotal])
FROM [Sales].[SalesOrderHeader]
GROUP BY YEAR([OrderDate]), MONTH([OrderDate])
ORDER BY YEAR([OrderDate]), MONTH([OrderDate]);
GO
/*
* Select all products that have a color value.
* Show results: Product Name
*
* Result Key:
*
* Name
* --------------------------------------------------
* LL Crankarm
* ...
* (256 rows affected)
*/
SELECT [Name]
FROM [Production].[Product]
WHERE [Color] IS NOT NULL;
GO
/*
* Get the summary of product lines with the number of products in each product line.
*
* Result Key:
*
* ProductLine Product Count
* ----------- -------------
* NULL 226
* M 91
* R 100
* S 35
* T 52
* ...
* (5 rows affected)
*/
SELECT [ProductLine], [Product Count] = COUNT(ISNULL([ProductLine],0))
FROM [Production].[Product]
GROUP BY [ProductLine]
ORDER BY [ProductLine];
GO
/*
* Select all product names that end in wheel.
*
* Result Key:
*
* Name
* --------------------------------------------------
* Freewheel
* HL Mountain Front Wheel
* HL Mountain Rear Wheel
* HL Road Front Wheel
* HL Road Rear Wheel
* LL Mountain Front Wheel
* LL Mountain Rear Wheel
* LL Road Front Wheel
* LL Road Rear Wheel
* ML Mountain Front Wheel
* ML Mountain Rear Wheel
* ML Road Front Wheel
* ML Road Rear Wheel
* Touring Front Wheel
* Touring Rear Wheel
* ...
* (15 rows affected)
*/
SELECT [Name]
FROM [Production].[Product]
WHERE [Name] LIKE '%wheel';
GO
/*
* Find if there are any products with a list price less than the standard cost. Show product id and name for those products.
*
* Result Key:
*
* ProductID Name
* ----------- --------------------------------------------------
* ...
* (0 rows affected)
*/
SELECT [ProductID], [Name]
FROM [Production].[Product]
WHERE [ListPrice] < [StandardCost];
GO
/*
* Select the year summary with rollup of sales by sales person id. Handle the case when there is no sales person associated to the sale.
* Show results: Year, Sales Person Id, Sales.
*
* Result Key:
*
* Year Sales Person ID Sales
* ----------- --------------- ---------------------
* NULL NULL 109846381.4039
* 2011 NULL 12641672.2129
* 2011 0 3863120.2134
* 2011 274 28926.2465
* ...
* (67 rows affected)
*/
SELECT [Year] = YEAR([OrderDate])
, [Sales Person ID] = COALESCE([SalesPersonID],0)
, [Sales] = SUM([SubTotal])
FROM [Sales].[SalesOrderHeader]
GROUP BY ROLLUP (YEAR([OrderDate]), COALESCE([SalesPersonID],0))
ORDER BY YEAR([OrderDate]), COALESCE([SalesPersonID],0);
GO
/*
* Select all order details with a negative margin.
* Show Results: SalesOrderID, SalesOrderDetailID, Margin.
* Define Margin: Sales - Total Cost
*
* Result Key:
*
* SalesOrderID SalesOrderDetailID Margin
* ------------ ------------------ ----------
* 43659 8 -28.955700
* ...
*/
SELECT sd.[SalesOrderID], sd.[SalesOrderDetailID], [Margin] = sd.[LineTotal] - sd.[OrderQty] * p.[StandardCost]
FROM [Sales].[SalesOrderDetail] AS sd, [Production].[Product] AS p
WHERE p.[ProductID] = sd.[ProductID]
AND sd.[LineTotal] - sd.[OrderQty] * p.[StandardCost] < 0;
GO
/*
* Select all orders where one or more details have a negative margin.
* Show Sesults: SalesOrderID, Total Negative Margin, Total Order Margin.
*
* Result Key:
*
* SalesOrderID Total Negative Margin Total Order Margin
* ------------ --------------------- ------------------
* 51739 -518.420316 10731.007367
* ...
*/
SELECT sod.[SalesOrderID]
, [Total Negative Margin] = SUM(IIF(sod.[LineTotal] - sod.[OrderQty] * p.[StandardCost] < 0, sod.[LineTotal] - sod.[OrderQty] * p.[StandardCost], NULL))
, [Total Order Margin] = SUM(sod.[LineTotal] - sod.[OrderQty] * p.[StandardCost])
FROM [Sales].[SalesOrderDetail] AS sod, [Production].[Product] AS p
WHERE p.[ProductID] = sod.[ProductID]
AND sod.[SalesOrderID] IN (
SELECT sod.[SalesOrderID]
FROM [Sales].[SalesOrderDetail] AS sod, [Production].[Product] AS p
WHERE p.[ProductID] = sod.[ProductID]
AND sod.[LineTotal] - sod.[OrderQty] * p.[StandardCost] < 0
)
GROUP BY sod.[SalesOrderID]
ORDER BY 3 DESC;
GO
/*
* Select the count of all rows, "customer ids" and "sales person ids" in the SalesOrderHeader table.
* Show Results: Rows, Count of Customer Ids, Count of SalesPerson Ids.
*
* Result Key:
*
* Rows CustomerID SalesPersonID
* ----------- ----------- -------------
* 31465 31465 3806
*/
SELECT [Rows] = COUNT(*)
, [CustomerID] = COUNT([CustomerID])
, [SalesPersonID] = COUNT([SalesPersonID])
FROM [Sales].[SalesOrderHeader];
GO
/*
* Select the sales total value and number of items per year, month.
* Show Results: Year, Month, Sales total, Items total.
*
* Result Key:
*
* YEAR MONTH SALES TOTAL ITEMS TOTAL
* ----------- ----------- --------------------------------------- -----------
* 2011 5 503805.916900 825
*/
SELECT [YEAR] = YEAR(soh.[OrderDate])
, [MONTH] = MONTH(soh.[OrderDate])
, [SALES TOTAL] = SUM(sod.[LineTotal])
, [ITEMS TOTAL] = SUM(sod.[OrderQty])
FROM [Sales].[SalesOrderHeader] AS soh
, [Sales].[SalesOrderDetail] AS sod
WHERE soh.[SalesOrderID] = sod.[SalesOrderID]
GROUP BY YEAR(soh.[OrderDate]), MONTH(soh.[OrderDate])
ORDER BY 1, 2;
GO
/*
* Select the average value of an order by year, month.
* Include the average number of lines and the average number of items per order.
* Show Results: Year, Month, Avg Value of Orders, Avg Number of Items, Avg Number of Lines.
*
* Result Key:
*
* YEAR MONTH Avg Value of Orders Avg Number of Items Avg Number of Lines
* ----------- ----------- --------------------- ------------------- -------------------
* 2011 5 11716.4166 19 8
* ...
*/
;WITH SalesOrdersSummary AS (
SELECT [YEAR] = YEAR(soh.[OrderDate])
, [MONTH] = MONTH(soh.[OrderDate])
, soh.[SalesOrderID]
, soh.[SubTotal]
, [Number of Items] = SUM(sod.[OrderQty])
, [Number of lines] = COUNT(soh.[SalesOrderID])
FROM [Sales].[SalesOrderHeader] AS soh, [Sales].[SalesOrderDetail] AS sod
WHERE soh.[SalesOrderID] = sod.[SalesOrderID]
GROUP BY soh.[OrderDate], soh.[SalesOrderID], soh.[SubTotal]
)
SELECT [YEAR]
, [MONTH]
, [Avg Value of Orders] = AVG([SubTotal])
, [Avg Number of Items] = AVG([Number of Items])
, [Avg Number of Lines] = AVG([Number of lines])
FROM SalesOrdersSummary
GROUP BY [YEAR], [MONTH]
ORDER BY 1, 2;
GO
/*
* Select the total sales, cost, margin and margin percent per country.
* Define Margin: Sales Value - Cost Value
* Define Margin %: (1 - Cost/Sales) * 100
*
* Result Key:
*
* Country Total Cost Margin Margin Pct
* --------- --------------- ------------ -------------- ----------
* Australia 10655335.959317 7221080.5803 3434255.379017 32.230300
* ...
*/
SELECT [Country] = cr.[Name]
, [Total] = SUM(sod.[LineTotal])
, [Cost] = SUM(p.[StandardCost] * sod.[OrderQty])
, [Margin] = SUM(sod.[LineTotal]) - SUM(p.[StandardCost] * sod.[OrderQty])
, [MarginPct] = ((SUM(sod.[LineTotal]) - SUM(p.[StandardCost] * sod.[OrderQty])) / SUM(sod.[LineTotal])) * 100
FROM [Sales].[SalesOrderHeader] AS soh
, [Sales].[SalesTerritory] AS st
, [Person].[CountryRegion] AS cr
, [Sales].[SalesOrderDetail] AS sod
, [Production].[Product] AS p
WHERE soh.[TerritoryID] = st.[TerritoryID]
AND st.[CountryRegionCode] = cr.[CountryRegionCode]
AND soh.[SalesOrderID] = sod.[SalesOrderID]
AND p.[ProductID] = sod.[ProductID]
GROUP BY cr.[Name]
ORDER BY cr.[Name];
GO
/*
* Select the top 5 salespersons by margin per year.
* Show information as: Year, Employee ID, Employee Name (Last, First), Margin.
* For every year, show the top 5
*
* Result Key:
*
* Sales Year Sales Person ID Employee Margin
* ----------- --------------- -------------------- ------------
* 2011 279 Reiter, TsviMichael 36183.911168
* ...
*/
;WITH SalesPersonIDs AS (
SELECT [Sales Person ID] = SP.[BusinessEntityID]
, PR.[LastName] + ', ' + PR.[FirstName] + CASE WHEN PR.[MiddleName] IS NULL THEN '' ELSE PR.[MiddleName] END AS [Employee]
FROM [Sales].[SalesPerson] AS SP, [Person].[Person] AS PR
WHERE PR.[BusinessEntityID] = SP.[BusinessEntityID]
), AnnualSalePersonMargin AS (
SELECT [Sales Year] = YEAR(H.[OrderDate])
, H.[SalesPersonID]
, [Margin] = SUM(D.[LineTotal]) - SUM(P.[StandardCost] * D.[OrderQty])
, [RowNum] = ROW_NUMBER() OVER (PARTITION BY YEAR(H.[OrderDate]) ORDER BY YEAR(H.[OrderDate]), SUM(D.[LineTotal]) - SUM(P.[StandardCost] * D.[OrderQty]) DESC)
FROM SalesPersonIDs AS ID
, [Sales].[SalesOrderHeader] AS H
, [Sales].[SalesOrderDetail] AS D
, [Production].[Product] AS P
WHERE H.[SalesPersonID] = ID.[Sales Person ID]
AND H.[SalesOrderID] = D.[SalesOrderID]
AND P.[ProductID] = D.[ProductID]
GROUP BY H.[SalesPersonID], YEAR(H.[OrderDate])
)
SELECT T2.[Sales Year], T2.[SalesPersonID], T1.[Employee], T2.[Margin]
FROM SalesPersonIDs AS T1, AnnualSalePersonMargin AS T2
WHERE T1.[Sales Person ID] = T2.[SalesPersonID]
AND T2.[RowNum] <= 5;
GO
/*
* Select all 2012 customers that did not return in 2013.
*
* Result Key:
*
* Customer Id
* -----------
* 20561
* ...
*/
SELECT [Customer Id] = O.[CustomerID]
FROM [Sales].[SalesOrderHeader] AS O
WHERE YEAR(O.[OrderDate]) = 2012
EXCEPT
SELECT [Customer Id] = O.[CustomerID]
FROM [Sales].[SalesOrderHeader] AS O
WHERE YEAR(O.[OrderDate]) = 2013;
GO
/*
* Select the Quarterly percent of Total Sales change (quarter over quarter) for 2011, 2012, 2013, 2014.
*
* Result Key:
*
* Current Quarter Year Current Quarter Current Quarter Sales Previous Quarter Year Previous Quarter Previous Quarter Sales Performance Pct
* -------------------- --------------- --------------------- --------------------- ---------------- ---------------------- ---------------------
* 2011 2 962716.7417 NULL NULL NULL NULL
* 2011 3 5042490.5827 2011 2 962716.7417 523.77
* ...
* 2014 2 7212854.7323 2014 1 12845074.079 56.15
*/
;WITH [QuaterSales] AS (
SELECT [Year] = YEAR([OrderDate])
, [Quarter] = DATEPART(QUARTER, [OrderDate])
, [Quarter Id] = (YEAR([OrderDate]) - 2010) * 4 + (DATEPART(QUARTER, [OrderDate]) - 1)
, [Sales] = SUM([SubTotal])
FROM [Sales].[SalesOrderHeader]
GROUP BY YEAR([OrderDate]), DATEPART(QUARTER, [OrderDate])
)
SELECT [Current Quarter Year] = CQ.[Year]
, [Current Quarter] = CQ.[Quarter]
, [Current Quarter Sales] = CQ.[Sales]
, [Previous Quarter Year] = PQ.[Year]
, [Previous Quarter] = PQ.[Quarter]
, [Previous Quarter Sales] = PQ.[Sales]
, [Performance Pct] = (CQ.[Sales] / PQ.[Sales]) * 100
FROM [QuaterSales] AS CQ
LEFT JOIN [QuaterSales] AS PQ
ON CQ.[Quarter Id] - 1 = PQ.[Quarter Id]
ORDER BY 1, 2;
GO
/*
* Select SalesPerson monthly quota achievement pct for 2012.
* Define achievement as: Total Sales / Quota
* Assume Quota value is the monthly quota, and doesn't change over month
* Show results: [Last Name, First Name], Employee ID, Year, Month, SalesQuota, QuotaPct
*
* Result Key:
*
* Employee BusinessEntityID Year Month SalesQuota Month Total PCT QUOTA
* ----------- ---------------- ----------- ----------- --------------------- --------------------- ---------------------
* Amy Alberts 287 2012 6 NULL 73732.4685 NULL
* ...
*/
SELECT [Employee] = T2.[FirstName] + ', ' + T2.[LastName]
, [BusinessEntityID] = T1.[BusinessEntityID]
, [Year] = YEAR(T3.[OrderDate])
, [Month] = MONTH(T3.[OrderDate])
, T1.[SalesQuota]
, [Month Total] = SUM(T3.[SubTotal])
, [PCT QUOTA] = (SUM(T3.[SubTotal]) / T1.[SalesQuota]) * 100
FROM [Sales].[SalesPerson] AS T1, [Person].[Person] AS T2, [Sales].[SalesOrderHeader] AS T3
WHERE T1.[BusinessEntityID] = T3.[SalesPersonID]
AND T1.[BusinessEntityID] = T2.[BusinessEntityID]
AND YEAR(T3.[OrderDate]) = 2012
GROUP BY T2.[FirstName] + ', ' + T2.[LastName], T1.[BusinessEntityID], YEAR(T3.[OrderDate]), MONTH(T3.[OrderDate]), T1.[SalesQuota]
ORDER BY 1, 2, 3, 4;
GO
-- Write a query that will display 'Undefined color' when no color is defined in the [Production].[Product] table, otherwise report the color.
-- Option #1
SELECT [ProductID], [Color] = IIF([Color] IS NULL, 'Undefined color', [Color])
FROM [Production].[Product];
GO
-- Option #2
SELECT [ProductID], [Color] = COALESCE([Color], 'Undefined color')
FROM [Production].[Product];
GO
-- Find the average value of a Sales Order and return those orders that are less than the average.
-- Option #1
DROP TABLE IF EXISTS #LineTotals;
SELECT [SalesOrderID], [LineTotals] = SUM([LineTotal])
INTO #LineTotals
FROM [Sales].[SalesOrderDetail]
GROUP BY [SalesOrderID];
GO
DECLARE @AverageValue FLOAT = (SELECT AVG([LineTotals]) FROM #LineTotals);
SELECT [SalesOrderID], [SubTotal] = FORMAT([LineTotals], 'g8')
FROM #LineTotals
WHERE [LineTotals] < @AverageValue
ORDER BY [SalesOrderID];
GO
-- Option #2
DECLARE @SalesOrderAverageValue REAL = (SELECT AVG([SubTotal]) FROM [Sales].[SalesOrderHeader]);
SELECT [SalesOrderID] ,[SubTotal]
FROM [Sales].[SalesOrderHeader]
WHERE [SubTotal] < @SalesOrderAverageValue;
GO
/*
* Find the percentage of Sales that are less than the average value of a sale.
* Result Key:
* Total Sales Sales below Average Pct Sales below Average
* ------------- ------------------- ---------------------------------------
* 31465 27458 87.265215318607
*/
-- Option #1
DROP TABLE IF EXISTS #LineTotals;
DROP TABLE IF EXISTS #BelowAverage;
GO
SELECT [SalesOrderID], [LineTotals] = SUM([LineTotal])
INTO #LineTotals
FROM [Sales].[SalesOrderDetail]
GROUP BY [SalesOrderID];
GO
SELECT [SalesOrderID], [SubTotal] = FORMAT([LineTotals], 'g8')
INTO #BelowAverage
FROM #LineTotals
WHERE [LineTotals] < (SELECT AVG([LineTotals])
FROM #LineTotals)
ORDER BY [SalesOrderID];
GO
DECLARE @TotalSalesCount FLOAT = (SELECT TOP(1) COUNT(*)FROM #LineTotals);
DECLARE @CountBelowAvg FLOAT = (SELECT TOP(1) COUNT(*) FROM #BelowAverage);
DECLARE @Avg DECIMAL(17,16) = (@CountBelowAvg / @TotalSalesCount);
DECLARE @AvgFormat VARCHAR(100) = (FORMAT(@Avg, 'P14'));
SELECT [Total Sales Sales] = @TotalSalesCount, [Sales below Average] = @CountBelowAvg, [Pct Sales below Average] = LEFT(@AvgFormat, NULLIF(LEN(@AvgFormat)-3,-3));
GO
/*
* Write a script that creates index [SalesOrderDetail_CarrierTracking] on [Sales].[SalesOrderDetail].
* For [CarrierTrackingNumber], [SalesOrderID], [SalesOrderDetailID] make sure the index does not exist before attempting to create it.
*/
-- Option #1
DECLARE @index_id INT = (
SELECT DISTINCT ([object_id])
FROM sys.dm_db_index_operational_stats(DB_ID(N'AdventureWorks2019'), OBJECT_ID(N'AdventureWorks2019.Sales.SalesOrderDetail'), NULL, NULL)
);
IF (SELECT [Name] FROM [sys].[indexes] WHERE object_id = @index_id AND [Name] = 'SalesOrderDetail_CarrierTracking') IS NOT NULL
BEGIN
DROP INDEX [SalesOrderDetail_CarrierTracking] ON [Sales].[SalesOrderDetail];
END
CREATE NONCLUSTERED INDEX [SalesOrderDetail_CarrierTracking] ON [Sales].[SalesOrderDetail] ([SalesOrderID] ASC, [SalesOrderDetailID] ASC, [CarrierTrackingNumber] ASC);
GO
-- Option #2
DECLARE @SalesOrderDetail_Id INT = OBJECT_ID('Sales.SalesOrderDetail', 'U');
IF EXISTS(SELECT 1 FROM sys.indexes WHERE [name] = 'SalesOrderDetail_CarrierTracking' AND [object_id] = @SalesOrderDetail_Id)
BEGIN
DROP INDEX SalesOrderDetail_CarrierTracking ON Sales.SalesOrderDetail;
END
CREATE NONCLUSTERED INDEX SalesOrderDetail_CarrierTracking ON Sales.SalesOrderDetail ([CarrierTrackingNumber], [SalesOrderID], [SalesOrderDetailID]);
GO
-- Check which numbers between 101 and 200 are primes.
-- Option #1
DECLARE @LowerLimit INT = 100, @UpperLimit INT = 200, @N INT, @P INT;
DECLARE @Numbers TABLE([Number] INT NULL);
DECLARE @Composite TABLE([Number] INT NULL);
SET @P = @UpperLimit;
WHILE @P > @LowerLimit
BEGIN
INSERT INTO @Numbers([Number])
VALUES (@P);
SET @N = 2;
WHILE @N <= @UpperLimit/2
BEGIN
IF ((@P % @N = 0 AND @P <> @N) OR (@P IN (0, 1)))
BEGIN
INSERT INTO @Composite([Number])
VALUES(@P);
BREAK
END
SET @N = @N + 1;
END
SET @P = @P - 1;
END
SELECT [Primes 100~200] = [Number]
FROM @Numbers
WHERE [Number] NOT IN (SELECT [Number] FROM @Composite)
ORDER BY [Number];
GO
-- Option #2
DECLARE @FirstPrimes TABLE([P] INT);
DECLARE @Primes TABLE([P] INT);
INSERT INTO @FirstPrimes
VALUES (2);
DECLARE @LastCheck INT = ROUND(SQRT(200), 0, 1) + 1;
DECLARE @I INT = 3;
WHILE @I < @LastCheck
BEGIN
IF NOT EXISTS(SELECT 1 FROM @FirstPrimes WHERE @I % [P] = 0)
INSERT INTO @FirstPrimes
VALUES (@I);
SET @I += 1;
END
SET @LastCheck = 200;
SET @I = 101;
WHILE @I <= @LastCheck
BEGIN
IF NOT EXISTS(SELECT 1 FROM @FirstPrimes WHERE @I % [P] = 0) INSERT INTO @Primes
VALUES (@I);
SET @I += 1;
END
SELECT [Primes 100~200] = [P]
FROM @Primes;
GO
/*
* Write the Fibonacci sequence for a given value of N = 25.
* Make the script flexible enough that N can be changed to any arbitrary number and the script should still work.
*/
-- Option #1
DECLARE @N INT = 25;
;WITH [Fibonacci]([Counter], [F0] ,[F1]) AS (
SELECT CAST(0 AS FLOAT), CAST(0 AS FLOAT), CAST(1 AS FLOAT)
UNION ALL
SELECT F.[Counter] + 1, F.[F1], F.[F0] + F.[F1]
FROM [Fibonacci] AS F
WHERE F.[Counter] < @N
)
SELECT [N] = F.[Counter], [Fibonacci Sequence] = F.[F0]
FROM [Fibonacci] F OPTION (MAXRECURSION 0);
GO
-- Option #2
DECLARE @N INT = 25, @F0 BIGINT = 0, @F1 BIGINT = 1, @F2 BIGINT = 0, @I INT = 0;
WHILE @I <= @N
BEGIN
IF @I = 0
BEGIN
PRINT RIGHT(' ' + CAST(@I AS NVARCHAR(2)), 3) + ' | ' + RIGHT(' ' + CAST(0 AS NVARCHAR(9)), 9);
END
ELSE IF @I = 1
BEGIN
PRINT RIGHT(' ' + CAST(@I AS NVARCHAR(2)), 3) + ' | ' + RIGHT(' ' + CAST(1 AS NVARCHAR(9)), 9);
END
ELSE
BEGIN
SET @F0 = @F1 + @F2;
PRINT RIGHT(' ' + CAST(@I AS NVARCHAR(2)), 3) + ' | ' + RIGHT(' ' + CAST(@F0 AS NVARCHAR(9)), 9);
SET @F2 = @F1;
SET @F1 = @F0;
END
SET @I += 1
END
PRINT ' ';
GO
/*
* Using master..spt_values as a numbers table
* select * from master..spt_values WHERE [type] = 'P'
* References:
* https://docs.microsoft.com/en-us/sql/t-sql/functions/floor-transact-sql?view=sql-server-ver15
* http://infocenter-archive.sybase.com/help/index.jsp
* spt_value is used often to generate large tables i.e. if you CROSS JOIN spt_value with itself it produces about 6.25 M rows.
* Often used by Itzak Ben-Gan to create his infamous NUMBER table.
*/
DECLARE @N INT = 25, @F INT = 0;
SELECT [I'm a Fibonacci number!] = FLOOR(POWER((1 + SQRT(5)) / 2.0, [number]) / SQRT(5) + 0.5)
FROM [master]..[spt_values]
WHERE [type] = 'P'
AND [number] BETWEEN @F AND @N;
GO
/*
* Generate a list of 1000 random numbers between 10 and 19, both ends inclusive.
* Show the frequency table.
* Note: The frequency table values should be different from one run to the next one.
*/
-- Option #1
DROP TABLE IF EXISTS #FrequencyTable;
GO
CREATE TABLE #FrequencyTable([RandomNumber] INT);
GO
DECLARE @StartN INT = 10, @StopN INT = 19, @ListSize INT = 1000, @Count INT = 1;
WHILE (@Count <= @ListSize)
BEGIN
INSERT INTO #FrequencyTable([RandomNumber])
SELECT [RandomNumber] = FLOOR(RAND() * (@StopN - @StartN + 1)) + @StartN;
SET @Count += 1;
END
SELECT [R] = [RandomNumber], [ROWS] = COUNT(*)
FROM #FrequencyTable
GROUP BY [RandomNumber]
ORDER BY 1;
GO
-- Option #2
DECLARE @I INT = 0;
DECLARE @RandNumbers TABLE([N] INT, [R] INT);
WHILE @I < 1000
BEGIN
INSERT INTO @RandNumbers
VALUES (@I ,CAST(ROUND(RAND() * 1000000, 0, 1) AS INT) % 10 + 10);
SET @I += 1;
END
SELECT [R], [ROWS] = COUNT(*)
FROM @RandNumbers
GROUP BY [R]
ORDER BY [R];
GO
/*
* Without using the STRING_SPLIT() function, given a comma separated list of numbers as a string, create a table with the numbers and the running sum.
* Input: '1, 2, 3, 4, 316, 323, 324, 325, 326, 327, 328, 329'
*/
DECLARE @textlist NVARCHAR(MAX) = ' 1, 2, 3, 4, 316, 323, 324, 325, 326, 327, 328, 329';
DECLARE @textvalue NVARCHAR(MAX), @N INT, @RS INT = 0, @FirstComma INT;
DECLARE @Numbers TABLE([N] INT, [RunningSum] INT);
WHILE LEN(@textlist) > 0
BEGIN
SET @FirstComma = CHARINDEX(',', @textlist);
IF @FirstComma = 0
BEGIN
SET @textvalue = RTRIM(LTRIM(@textlist));
SET @textlist = '';
END