我有下表(省略了几个字段):
CREATE TABLE [Device](
[DeviceId] [int] IDENTITY(1,1) NOT NULL,
[AuthorizedDate] [datetime2](0) NULL,
[DeauthorizedDate] [datetime2](0) NULL,
[MemberId] [int] NOT NULL,
...
CONSTRAINT [PK_Device] PRIMARY KEY CLUSTERED
(
[DeviceId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY]
) ON [PRIMARY]
Run Code Online (Sandbox Code Playgroud)
该表用于跟踪每个成员的设备 - 他们现在只能同时激活 5 台设备,并且在 90 天内只能进行 3 次更改。此表不足以强制执行 3 更改规则(我不会详细说明)。为此,我决定创建一个日志表:
CREATE TABLE [SwapLog](
[SwapLogId] [int] IDENTITY(1,1) NOT NULL,
[MemberId] [int] NOT NULL,
[DeauthorizationDate] [datetime] NOT NULL,
[DeauthorizedDeviceId] [int] NOT NULL,
[AuthorizationDate] [datetime] NULL,
[AuthorizedDeviceId] [int] NULL,
CONSTRAINT [PK_SwapLog] PRIMARY KEY CLUSTERED
(
[wapLogId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
Run Code Online (Sandbox Code Playgroud)
计划是在设备被取消授权时填充此表,然后在新设备随后被授权时填写 AuthorizationDate 和 Id
我需要填充 SwapLog 表,以便我可以正确执行规则。所以,如果Device表有以下记录:
DeviceId AuthorizedDate DeauthorizedDate MemberId
60 2010-11-09 08:06:38 2010-11-10 00:00:00 2389
1795 2012-01-04 08:57:12 2013-09-02 15:32:59 2389
4467 2011-02-21 14:25:24 2013-09-02 15:33:52 2389
4733 2011-03-02 20:11:54 NULL 2389
11835 2011-11-02 11:38:33 NULL 2389
12707 2011-11-23 16:35:45 NULL 2389
46663 2012-04-13 13:14:56 NULL 2389
56651 2012-07-05 20:10:05 NULL 2389
86247 2012-10-11 12:50:20 NULL 2389
114493 2012-12-05 19:46:30 NULL 2389
123867 2013-03-15 19:39:38 NULL 2389
127595 2013-08-01 18:17:16 NULL 2389
522826 2013-10-09 16:20:31 NULL 2389
554246 2013-10-28 11:23:29 NULL 2389
574637 2013-11-07 16:48:36 NULL 2389
609731 2013-11-25 12:22:47 NULL 2389
651808 2013-12-19 11:25:35 NULL 2389
775957 2014-02-10 13:05:51 NULL 2389
778288 2014-02-11 10:52:19 NULL 2389
824090 2014-03-12 14:34:28 NULL 2389
180842 2013-02-14 07:00:37 2013-08-23 09:49:58 225897
180857 2013-02-14 08:56:42 2013-08-23 09:50:01 225897
181974 2013-02-15 09:44:42 2013-08-23 09:50:04 225897
182026 2013-02-15 12:03:02 2013-08-23 09:50:07 225897
183139 2013-02-16 04:14:20 2014-04-11 09:44:47 225897
186993 2013-02-18 01:42:30 2014-04-11 09:44:55 225897
188424 2013-02-19 11:17:27 2014-04-11 09:45:02 225897
190263 2013-02-21 01:00:11 2014-04-11 09:45:10 225897
359562 2013-07-08 10:07:29 2014-04-11 09:45:17 225897
411095 2013-07-31 09:48:24 2013-08-16 15:08:39 225897
452576 2013-08-20 01:35:52 2013-08-23 09:50:09 225897
462050 2013-08-26 04:21:57 2013-09-06 16:16:14 225897
503700 2013-09-25 08:05:14 2013-10-15 15:02:43 225897
532174 2013-10-16 06:35:07 2013-10-16 12:33:32 225897
532177 2013-10-16 06:45:48 2013-10-16 12:33:35 225897
532301 2013-10-16 11:32:48 2013-10-16 12:33:38 225897
532340 2013-10-16 12:49:47 2013-10-23 13:13:29 225897
533323 2013-10-17 07:12:42 2013-10-23 13:13:37 225897
588192 2013-11-14 04:17:38 2013-12-17 09:16:34 225897
444955 2013-11-20 10:50:22 2013-12-11 09:09:05 225897
600932 2013-11-21 02:06:15 2013-12-17 12:58:32 225897
613745 2013-11-28 09:29:26 2014-02-13 17:21:25 225897
626805 2013-12-05 23:50:03 2014-02-13 17:21:35 225897
637629 2013-12-11 09:09:06 2014-02-13 17:21:42 225897
649121 2013-12-17 09:16:35 2014-02-13 17:21:38 225897
649238 2013-12-17 12:58:32 2014-02-13 17:21:31 225897
445006 2014-02-13 17:25:16 2014-02-14 13:41:05 225897
781025 2014-02-13 17:59:50 2014-02-14 13:41:11 225897
780992 2014-02-18 19:53:23 2014-03-20 08:30:12 225897
793156 2014-02-19 12:19:09 2014-03-20 09:12:42 225897
804491 2014-02-26 11:16:06 2014-03-20 10:40:43 225897
833582 2014-03-19 05:15:26 2014-04-11 09:45:57 225897
834404 2014-03-20 07:58:53 2014-04-11 09:45:52 225897
532426 2014-03-20 08:30:12 2014-04-11 09:45:30 225897
834414 2014-03-20 09:12:43 2014-04-11 09:45:46 225897
834446 2014-03-20 10:40:43 2014-04-11 09:45:36 225897
870418 2014-04-14 12:58:32 NULL 225897
871297 2014-04-15 07:16:26 NULL 225897
898213 2014-05-08 06:11:23 NULL 225897
Run Code Online (Sandbox Code Playgroud)
我希望 SwapLog 表具有以下内容:
SwapLogId MemberId DeauthorizationDate DeauthorizedDeviceId AuthorizationDate AuthorizedDeviceId
1 2389 2010-11-10 00:00:00 60 NULL NULL
2 2389 2013-09-02 15:32:59 1795 2013-10-09 16:20:31 522826
3 2389 2013-09-02 15:33:52 4467 2013-10-28 11:23:29 554246
Run Code Online (Sandbox Code Playgroud)
通过这种方式,我可以判断设备 1795 何时被取消授权,以及究竟是哪个设备在其位置获得了授权。
问题是我用来迁移数据的查询出错了。我在 authorizationDate 字段中收到重复的数据:
MemberId DeauthorizedDate DeauthorizedDeviceId AuthorizedDate AuthorizedDeviceId
2389 2013-09-02 15:32:59 1795 2013-10-09 16:20:31 522826
2389 2013-09-02 15:32:59 1795 2013-10-28 11:23:29 554246
2389 2013-09-02 15:32:59 1795 2013-11-07 16:48:36 574637
2389 2013-09-02 15:32:59 1795 2013-11-25 12:22:47 609731
2389 2013-09-02 15:33:52 4467 2013-10-09 16:20:31 522826
2389 2013-09-02 15:33:52 4467 2013-10-28 11:23:29 554246
2389 2013-09-02 15:33:52 4467 2013-11-07 16:48:36 574637
2389 2013-09-02 15:33:52 4467 2013-11-25 12:22:47 609731
Run Code Online (Sandbox Code Playgroud)
这是查询:
SELECT
d.[MemberId]
,d.[DeauthorizedDate]
,d.[DeviceId] AS [DeauthorizedDeviceId]
,d2.[AuthorizedDate]
,d2.[DeviceId] AS [AuthorizedDeviceId]
FROM
[Personalization].[Device] d
LEFT JOIN [Personalization].[Device] d2 ON d.MemberId = d2.MemberId
WHERE
d.DeauthorizedDate < d2.AuthorizedDate
AND d2.AuthorizedDate <= DATEADD(day, 90, d.DeauthorizedDate)
AND d.MemberId = 2389
ORDER BY MemberId, d.DeauthorizedDate
Run Code Online (Sandbox Code Playgroud)
我可以用 c# 写一些东西来做到这一点,但我怀疑这需要很长时间,因为我有大量的设备记录。任何帮助表示赞赏。
有几种方法可以解决这个问题,以下是递归解决方案。
为了达到最佳性能,它需要 Devices 表上的几个索引,如果不是一般有用,可以在构建 SwapLog 行后删除:
CREATE UNIQUE NONCLUSTERED INDEX uq1
ON Personalization.Device
(MemberId, AuthorizedDate)
INCLUDE
(DeviceId);
CREATE UNIQUE NONCLUSTERED INDEX uq2
ON Personalization.Device
(MemberId, DeauthorizedDate)
INCLUDE
(DeviceId)
WHERE
DeauthorizedDate IS NOT NULL;
Run Code Online (Sandbox Code Playgroud)
递归查询本身是:
WITH R AS
(
-- Anchor: Earliest deauthorization per member
-- and its associated authorization, if any
SELECT
FirstDeauthPerMember.MemberId,
FirstDeauthPerMember.DeauthorizedDate,
DeauthorizedDeviceId = FirstDeauthPerMember.DeviceId,
Auth.AuthorizedDate,
AuthorizedDeviceId = Auth.DeviceId,
RunningAuthDate = ISNULL(Auth.AuthorizedDate, FirstDeauthPerMember.DeauthorizedDate)
FROM
(
SELECT
DeauthPerMember.MemberId,
DeauthPerMember.DeauthorizedDate,
DeauthPerMember.DeviceId
FROM
(
SELECT
D.MemberId,
D.DeauthorizedDate,
D.DeviceId,
RN = ROW_NUMBER() OVER (
PARTITION BY D.MemberId
ORDER BY D.DeauthorizedDate)
FROM Personalization.Device AS D
WHERE
D.DeauthorizedDate IS NOT NULL
) AS DeauthPerMember
WHERE
DeauthPerMember.RN = 1
) AS FirstDeauthPerMember
OUTER APPLY
(
SELECT TOP (1)
D2.AuthorizedDate,
D2.DeviceId
FROM Personalization.Device AS D2
WHERE
D2.MemberId = FirstDeauthPerMember.MemberId
AND D2.AuthorizedDate > FirstDeauthPerMember.DeauthorizedDate
AND D2.AuthorizedDate <= DATEADD(DAY, 90, FirstDeauthPerMember.DeauthorizedDate)
ORDER BY
D2.AuthorizedDate ASC
) AS Auth
UNION ALL
-- Recursive part: next deauthorization in order
-- and its associated authorization, if any
SELECT
D6.MemberId,
D6.DeauthorizedDate,
D6.DeauthorizedDeviceId,
D6.AuthorizedDate,
D6.AuthorizedDeviceId,
D6.RunningAuthDate
FROM
(
SELECT
D5.MemberId,
D5.DeauthorizedDate,
D5.DeauthorizedDeviceId,
D5.AuthorizedDate,
D5.AuthorizedDeviceId,
D5.RunningAuthDate
FROM
(
SELECT
D.MemberId,
D.DeauthorizedDate,
DeauthorizedDeviceId = D.DeviceId,
D4.AuthorizedDate,
AuthorizedDeviceId = D4.DeviceId,
RN = ROW_NUMBER() OVER (
ORDER BY D.DeauthorizedDate),
RunningAuthDate = ISNULL(D4.AuthorizedDate, R.RunningAuthDate)
FROM Personalization.Device AS D
JOIN R
ON R.MemberId = D.MemberId
OUTER APPLY
(
SELECT
D3.AuthorizedDate,
D3.DeviceId
FROM
(
SELECT
D2.AuthorizedDate,
D2.DeviceId,
RN = ROW_NUMBER() OVER (
ORDER BY D2.AuthorizedDate)
FROM Personalization.Device AS D2
WHERE
D2.MemberId = D.MemberId
AND D2.AuthorizedDate > ISNULL(R.AuthorizedDate, R.RunningAuthDate)
AND D2.AuthorizedDate > D.DeauthorizedDate
AND D2.AuthorizedDate <= DATEADD(DAY, 90, D.DeauthorizedDate)
) AS D3
WHERE
D3.RN = 1
) AS D4
WHERE
D.DeauthorizedDate IS NOT NULL
AND D.DeauthorizedDate > R.DeauthorizedDate
) AS D5
WHERE
D5.RN = 1
) AS D6
)
SELECT
R.MemberId,
R.DeauthorizedDate,
R.DeauthorizedDeviceId,
R.AuthorizedDate,
R.AuthorizedDeviceId
FROM R
ORDER BY
R.MemberId,
R.DeauthorizedDate
OPTION (MAXRECURSION 0);
Run Code Online (Sandbox Code Playgroud)
输出:
SQLFiddle(由于 SQLFiddle 权限限制而使用 dbo 架构)
执行计划: