Advertisement
AgentFire

Untitled

Jun 15th, 2020
1,540
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
SQL 10.25 KB | None | 0 0
  1. --
  2. -- Definition for schema stats :
  3. --
  4.  
  5. CREATE SCHEMA stats
  6.   AUTHORIZATION [dbo]
  7. GO
  8.  
  9. --
  10. -- Definition for table Clients :
  11. --
  12.  
  13. CREATE TABLE dbo.Clients (
  14.   id INT IDENTITY(1, 1) NOT NULL,
  15.   Name nvarchar(50) NOT NULL,
  16.   RegisteredAt datetime2(0) DEFAULT getdate() NOT NULL,
  17.   CONSTRAINT PK_Clients PRIMARY KEY CLUSTERED (id)
  18.     WITH (
  19.       PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF, STATISTICS_NORECOMPUTE = OFF,
  20.       ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
  21. )
  22. ON [PRIMARY]
  23. GO
  24.  
  25. --
  26. -- Definition for table Products :
  27. --
  28.  
  29. CREATE TABLE dbo.Products (
  30.   id INT IDENTITY(1, 1) NOT NULL,
  31.   Name nvarchar(200) NOT NULL,
  32.   Price INT NOT NULL,
  33.   CONSTRAINT PK_Products PRIMARY KEY CLUSTERED (id)
  34.     WITH (
  35.       PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF, STATISTICS_NORECOMPUTE = OFF,
  36.       ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON),
  37.   CONSTRAINT CHECK_Products_Consistency CHECK ([Price]>=(0))
  38. )
  39. ON [PRIMARY]
  40. GO
  41.  
  42. --
  43. -- Definition for table ClientFirstPurchases :
  44. --
  45.  
  46. CREATE TABLE stats.ClientFirstPurchases (
  47.   ClientId INT NOT NULL,
  48.   PurchaseId INT NOT NULL,
  49.   CONSTRAINT PK_ClientFirstPurchases_ClientPurchase PRIMARY KEY CLUSTERED (ClientId, PurchaseId)
  50.     WITH (
  51.       PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF, STATISTICS_NORECOMPUTE = OFF,
  52.       ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
  53. )
  54. ON [PRIMARY]
  55. GO
  56.  
  57. --
  58. -- Definition for table Purchases :
  59. --
  60.  
  61. CREATE TABLE dbo.Purchases (
  62.   id INT IDENTITY(1, 1) NOT NULL,
  63.   TIMESTAMP datetime2(0) DEFAULT getdate() NOT NULL,
  64.   ClientId INT NOT NULL,
  65.   ProductId INT NOT NULL,
  66.   CONSTRAINT PK_Purchases PRIMARY KEY CLUSTERED (id)
  67.     WITH (
  68.       PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF, STATISTICS_NORECOMPUTE = OFF,
  69.       ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
  70. )
  71. ON [PRIMARY]
  72. GO
  73.  
  74. --
  75. -- Definition for table BuyingDays :
  76. --
  77.  
  78. CREATE TABLE stats.BuyingDays (
  79.   id INT IDENTITY(0, 1) NOT NULL,
  80.   DayFrom INT NOT NULL,
  81.   DayTo INT NOT NULL,
  82.   CONSTRAINT PK_BuyingDays PRIMARY KEY CLUSTERED (id)
  83.     WITH (
  84.       PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF, STATISTICS_NORECOMPUTE = OFF,
  85.       ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON),
  86.   CONSTRAINT CHECK_BuyingDays_Consistency CHECK ([DayFrom]>=(0) AND [DayTo]>=[DayFrom])
  87. )
  88. ON [PRIMARY]
  89. GO
  90.  
  91. --
  92. -- Definition for view ClientIdsWithFirstPurchaseInBuyingDays :
  93. --
  94. GO
  95. SET ANSI_NULLS ON
  96. SET QUOTED_IDENTIFIER ON
  97. GO
  98.  
  99. CREATE VIEW stats.ClientIdsWithFirstPurchaseInBuyingDays WITH SCHEMABINDING
  100. AS
  101.  
  102. SELECT
  103.     BuyingDayId = BD.id,
  104.     ClientId = C.id
  105. FROM stats.BuyingDays BD
  106. INNER JOIN stats.ClientFirstPurchases CFP
  107.     ON (1 = 1) -- CROSS JOIN in an Indexed View.
  108. INNER JOIN dbo.Purchases P
  109.     ON (P.id = CFP.PurchaseId)
  110. INNER JOIN dbo.Clients C
  111.     ON (C.id = P.ClientId)
  112. WHERE
  113.     DATEDIFF(DAY, C.RegisteredAt, P.[TIMESTAMP]) BETWEEN BD.DayFrom AND BD.DayTo
  114. GO
  115.  
  116. SET ARITHABORT ON
  117. GO
  118. SET CONCAT_NULL_YIELDS_NULL ON
  119. GO
  120. SET QUOTED_IDENTIFIER ON
  121. GO
  122. SET ANSI_NULLS ON
  123. GO
  124. SET ANSI_PADDING ON
  125. GO
  126. SET ANSI_WARNINGS ON
  127. GO
  128. SET NUMERIC_ROUNDABORT OFF
  129. GO
  130. CREATE UNIQUE CLUSTERED INDEX UQCL_TheView ON stats.ClientIdsWithFirstPurchaseInBuyingDays
  131.   (BuyingDayId, ClientId)
  132. WITH (
  133.   PAD_INDEX = OFF,
  134.   IGNORE_DUP_KEY = OFF,
  135.   DROP_EXISTING = OFF,
  136.   STATISTICS_NORECOMPUTE = OFF,
  137.   SORT_IN_TEMPDB = OFF,
  138.   ONLINE = OFF,
  139.   ALLOW_ROW_LOCKS = ON,
  140.   ALLOW_PAGE_LOCKS = ON)
  141. ON [PRIMARY]
  142. GO
  143.  
  144. --
  145. -- Definition for synonym TheView :
  146. --
  147.  
  148. CREATE SYNONYM stats.TheView FOR [stats].[ClientIdsWithFirstPurchaseInBuyingDays]
  149. GO
  150.  
  151. --
  152. -- Data for table dbo.Clients  (LIMIT 0,500)
  153. --
  154.  
  155. SET IDENTITY_INSERT dbo.Clients ON
  156. GO
  157.  
  158. INSERT INTO dbo.Clients (id, Name, RegisteredAt)
  159. VALUES
  160.   (1, N'Василий', N'2020-06-15 12:39:35')
  161. GO
  162.  
  163. SET IDENTITY_INSERT dbo.Clients OFF
  164. GO
  165.  
  166. --
  167. -- Data for table dbo.Products  (LIMIT 0,500)
  168. --
  169.  
  170. SET IDENTITY_INSERT dbo.Products ON
  171. GO
  172.  
  173. INSERT INTO dbo.Products (id, Name, Price)
  174. VALUES
  175.   (1, N'Помадка', 10)
  176. GO
  177.  
  178. SET IDENTITY_INSERT dbo.Products OFF
  179. GO
  180.  
  181. --
  182. -- Data for table dbo.Purchases  (LIMIT 0,500)
  183. --
  184.  
  185. SET IDENTITY_INSERT dbo.Purchases ON
  186. GO
  187.  
  188. INSERT INTO dbo.Purchases (id, TIMESTAMP, ClientId, ProductId)
  189. VALUES
  190.   (2, N'2020-06-15 12:39:59', 1, 1)
  191. GO
  192.  
  193. INSERT INTO dbo.Purchases (id, TIMESTAMP, ClientId, ProductId)
  194. VALUES
  195.   (3, N'2020-06-15 12:40:43', 1, 1)
  196. GO
  197.  
  198. SET IDENTITY_INSERT dbo.Purchases OFF
  199. GO
  200.  
  201. --
  202. -- Data for table stats.BuyingDays  (LIMIT 0,500)
  203. --
  204.  
  205. SET IDENTITY_INSERT stats.BuyingDays ON
  206. GO
  207.  
  208. INSERT INTO stats.BuyingDays (id, DayFrom, DayTo)
  209. VALUES
  210.   (0, 0, 5)
  211. GO
  212.  
  213. INSERT INTO stats.BuyingDays (id, DayFrom, DayTo)
  214. VALUES
  215.   (1, 3, 10)
  216. GO
  217.  
  218. SET IDENTITY_INSERT stats.BuyingDays OFF
  219. GO
  220.  
  221. --
  222. -- Data for table stats.ClientFirstPurchases  (LIMIT 0,500)
  223. --
  224.  
  225. INSERT INTO stats.ClientFirstPurchases (ClientId, PurchaseId)
  226. VALUES
  227.   (1, 2)
  228. GO
  229.  
  230. --
  231. -- Definition for indices :
  232. --
  233.  
  234. CREATE NONCLUSTERED INDEX IN_Clients_RegisteredAt ON dbo.Clients
  235.   (RegisteredAt)
  236. WITH (
  237.   PAD_INDEX = OFF,
  238.   DROP_EXISTING = OFF,
  239.   STATISTICS_NORECOMPUTE = OFF,
  240.   SORT_IN_TEMPDB = OFF,
  241.   ONLINE = OFF,
  242.   ALLOW_ROW_LOCKS = ON,
  243.   ALLOW_PAGE_LOCKS = ON)
  244. ON [PRIMARY]
  245. GO
  246.  
  247. CREATE NONCLUSTERED INDEX IN_ClientFirstPurchases_Purchase ON stats.ClientFirstPurchases
  248.   (PurchaseId)
  249. INCLUDE (ClientId)
  250. WITH (
  251.   PAD_INDEX = OFF,
  252.   DROP_EXISTING = OFF,
  253.   STATISTICS_NORECOMPUTE = OFF,
  254.   SORT_IN_TEMPDB = OFF,
  255.   ONLINE = OFF,
  256.   ALLOW_ROW_LOCKS = ON,
  257.   ALLOW_PAGE_LOCKS = ON)
  258. ON [PRIMARY]
  259. GO
  260.  
  261. CREATE NONCLUSTERED INDEX IN_Purchases_ClientTimestamp ON dbo.Purchases
  262.   (ClientId, TIMESTAMP)
  263. WITH (
  264.   PAD_INDEX = OFF,
  265.   DROP_EXISTING = OFF,
  266.   STATISTICS_NORECOMPUTE = OFF,
  267.   SORT_IN_TEMPDB = OFF,
  268.   ONLINE = OFF,
  269.   ALLOW_ROW_LOCKS = ON,
  270.   ALLOW_PAGE_LOCKS = ON)
  271. ON [PRIMARY]
  272. GO
  273.  
  274. ALTER TABLE stats.BuyingDays
  275. ADD CONSTRAINT UQ_BuyingDays_FromTo
  276. UNIQUE NONCLUSTERED (DayFrom, DayTo)
  277. WITH (
  278.   PAD_INDEX = OFF,
  279.   IGNORE_DUP_KEY = OFF,
  280.   STATISTICS_NORECOMPUTE = OFF,
  281.   ALLOW_ROW_LOCKS = ON,
  282.   ALLOW_PAGE_LOCKS = ON)
  283. ON [PRIMARY]
  284. GO
  285.  
  286. --
  287. -- Definition for foreign keys :
  288. --
  289.  
  290. ALTER TABLE stats.ClientFirstPurchases
  291. ADD CONSTRAINT FK_ClientFirstPurchases_Client FOREIGN KEY (ClientId)
  292.   REFERENCES dbo.Clients (id)
  293.   ON UPDATE NO ACTION
  294.   ON DELETE CASCADE
  295. GO
  296.  
  297. ALTER TABLE stats.ClientFirstPurchases
  298. ADD CONSTRAINT FK_ClientFirstPurchases_Purchase FOREIGN KEY (PurchaseId)
  299.   REFERENCES dbo.Purchases (id)
  300.   ON UPDATE NO ACTION
  301.   ON DELETE NO ACTION
  302. GO
  303.  
  304. ALTER TABLE dbo.Purchases
  305. ADD CONSTRAINT FK_Purchases_Client FOREIGN KEY (ClientId)
  306.   REFERENCES dbo.Clients (id)
  307.   ON UPDATE NO ACTION
  308.   ON DELETE CASCADE
  309. GO
  310.  
  311. ALTER TABLE dbo.Purchases
  312. ADD CONSTRAINT FK_Purchases_Product FOREIGN KEY (ProductId)
  313.   REFERENCES dbo.Products (id)
  314.   ON UPDATE NO ACTION
  315.   ON DELETE CASCADE
  316. GO
  317.  
  318. --
  319. -- Definition for triggers :
  320. --
  321. GO
  322. SET ANSI_NULLS ON
  323. SET QUOTED_IDENTIFIER ON
  324. GO
  325.  
  326. CREATE TRIGGER dbo.TRI_Purchases_Stats ON dbo.Purchases
  327. WITH EXECUTE AS CALLER
  328. FOR INSERT
  329. AS
  330. BEGIN
  331.  
  332.     ; WITH L1 AS (
  333.         SELECT
  334.             PurchaseId = I.id,
  335.             I.ClientId,
  336.             RN = ROW_NUMBER() OVER (PARTITION BY I.ClientId ORDER BY I.id ASC)
  337.         FROM INSERTED I
  338.         WHERE
  339.             NOT EXISTS (
  340.                 SELECT
  341.                     NULL
  342.                 FROM stats.ClientFirstPurchases CFP
  343.                 WHERE
  344.                     CFP.ClientId = I.ClientId
  345.             )
  346.     ), L2 AS (
  347.         SELECT
  348.             L1.PurchaseId,
  349.             L1.ClientId
  350.         FROM L1
  351.         WHERE L1.RN = 1
  352.     )
  353.     MERGE stats.ClientFirstPurchases T
  354.     USING L2 S
  355.         ON (T.ClientId = S.ClientId AND T.PurchaseId = S.PurchaseId)
  356.     WHEN NOT MATCHED BY TARGET
  357.         THEN INSERT (ClientId, PurchaseId)
  358.         VALUES (S.ClientId, S.PurchaseId)
  359.     ;
  360.  
  361. END
  362. GO
  363.  
  364. /*
  365.     Необходимый запрос: количество клиентов, совершивших первую покупку через 0-5 дней после регистрации.
  366.  
  367.     В задании умышленно опущены требования к производительности и безопасности, поэтому я оставляю последнее
  368.     на "как есть", а по производительности у меня два решения - первое "в лоб", второе требует
  369.     дополнительной таблицы первых покупок, диапазонов дней, триггера и кластерной вьюхи.
  370.     Вышеперечисленное (кроме триггера) выведено в отдельную схему stats, т.к. не является частью бизнес-архитектуры.
  371. */
  372.  
  373. -- Решение 1. Использует SCAN на клиентах и оптимальный SEEK на покупках.
  374. -- Execution Plan (картинкой): https://snipboard.io/GPV8is.jpg
  375. -- Execution Plan (схемкой): https://www.brentozar.com/pastetheplan/?id=SkcXq646L
  376.  
  377. SELECT
  378.     ClientsCount = COUNT (*)
  379. FROM dbo.Clients C
  380. CROSS APPLY (
  381.     SELECT TOP (1)
  382.         P.[TIMESTAMP]
  383.     FROM dbo.Purchases P
  384.     WHERE
  385.         P.ClientId = C.id
  386.     ORDER BY
  387.         P.[TIMESTAMP] ASC
  388. ) FirstPurchase
  389. WHERE
  390.     DATEDIFF (DAY, C.RegisteredAt, FirstPurchase.[TIMESTAMP]) BETWEEN 0 AND 5
  391.    
  392. -- Решение 2. Безусловно, это-очень-сильно-преждевременная оптимизация, т.к. требования к производительности не указаны,
  393. -- но я обязан показать и этот вариант.
  394. -- Решение использует триггер на Purchases, но не подразумевает удаление из этой таблицы.
  395. -- Решение позволяет задать любое количество диапазонов дней (не только 0..5) в таблице stats.BuyingDays
  396.  
  397. ; WITH L1 AS (
  398.     SELECT
  399.         V.BuyingDayId,
  400.         ClientsCount = COUNT(*)
  401.     FROM stats.TheView V WITH (NOEXPAND)
  402.     GROUP BY
  403.         V.BuyingDayId
  404. )
  405. SELECT
  406.     BD.DayFrom,
  407.     BD.DayTo,
  408.     ClientsCount = ISNULL(L1.ClientsCount, 0)
  409. FROM L1
  410. RIGHT OUTER JOIN stats.BuyingDays BD
  411.     ON (BD.id = L1.BuyingDayId)
  412.    
  413. -- Execution Plan (картинкой): https://snipboard.io/mOYdsT.jpg
  414. -- Execution Plan (схемкой): https://www.brentozar.com/pastetheplan/?id=Hkq_qaN6U
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement