我试图对时态表实现类似的功能 https://msdn.microsoft.com/en-us/library/dn935015.aspx
由于一些复杂的业务需求,我无法使用SQL Server的时态表,因此我尝试实现自己的版本。
基本上,我需要一种方法来确定特定时间点的数据。
该表将存储增量,并使用位掩码来确定实际更改的列(将数据更改为NULL是一个有效的方案,这就是我们需要此更新掩码的原因)
我确实有原型工作,但我的查询似乎非常昂贵。我不是每列一个子查询,而是尝试将它们组合成一个查询(具有类似游标的行为)。
表:
CREATE TABLE user_efdt
(
user_id INT not null,
date_effective DATE not null,
first_name NVARCHAR(100),
last_name NVARCHAR(100),
address1 NVARCHAR(100),
position NVARCHAR(100),
modified_fields VARBINARY(128) NOT NULL
)
ALTER TABLE user_efdt
ADD CONSTRAINT PK_user_efdt
PRIMARY KEY CLUSTERED (user_id, date_effective);
GO
数据设置:
DECLARE @first_name_bit INT
DECLARE @last_name_bit INT
DECLARE @address1_bit INT
DECLARE @position_bit INT
SET @first_name_bit = 3
SET @last_name_bit = 4
SET @address1_bit = 5
SET @position_bit = 6
-- user john does gets added to system on 1-may-16 with first name and last name
INSERT INTO user_efdt (user_id, date_effective, first_name, last_name, address1, position, modified_fields)
VALUES (1000, '1-may-16', 'john', 'doe', null, null, CAST(POWER(2, @first_name_bit) + POWER(2, @last_name_bit) AS VARBINARY))
-- user mary ann gets added to system on 1-may-16 with first name, last name, address and position
INSERT INTO user_efdt (user_id, date_effective, first_name, last_name, address1, position, modified_fields)
VALUES (2000, '1-may-16', 'mary', 'ann', '123 main st', 'manager', CAST(POWER(2, @first_name_bit) + POWER(2, @last_name_bit) + POWER(2, @address1_bit) + POWER(2, @position_bit) AS VARBINARY))
-- john doe gets address updated on 1-apr-16
INSERT INTO user_efdt (user_id, date_effective, first_name, last_name, address1, position, modified_fields)
VALUES (1000, '1-apr-16', null, null, '456 el dorado st', null, CAST(POWER(2, @address1_bit) AS VARBINARY))
-- john doe gets position updated
INSERT INTO user_efdt (user_id, date_effective, first_name, last_name, address1, position, modified_fields)
VALUES (1000, '1-mar-16', null, null, null, 'engineer', CAST(POWER(2, @position_bit) AS VARBINARY))
-- john doe gets position updated again
INSERT INTO user_efdt (user_id, date_effective, first_name, last_name, address1, position, modified_fields)
VALUES (1000, '1-jun-16', null, null, null, 'engineer 2', CAST(POWER(2, @position_bit) AS VARBINARY))
-- john doe gets position updated to NULL (erased)
INSERT INTO user_efdt (user_id, date_effective, first_name, last_name, address1, position, modified_fields)
VALUES (1000, '1-jul-16', null, null, null, null, CAST(POWER(2, @position_bit) AS VARBINARY))
-- mary ann gets address updated
INSERT INTO user_efdt (user_id, date_effective, first_name, last_name, address1, position, modified_fields)
VALUES (2000, '1-jun-16', null, null, '1443 hoover st', null, CAST(POWER(2, @address1_bit) AS VARBINARY))
-- mary ann gets position updated
INSERT INTO user_efdt (user_id, date_effective, first_name, last_name, address1, position, modified_fields)
VALUES (2000, '1-jul-16', null, null, null, 'manager 2', CAST(POWER(2, @position_bit) AS VARBINARY))
这是我的查询版本:
CREATE PROCEDURE as_of_date_query (@date DATE)
AS
BEGIN
DECLARE @first_name_bit INT
DECLARE @last_name_bit INT
DECLARE @address1_bit INT
DECLARE @position_bit INT
SET @first_name_bit = 3
SET @last_name_bit = 4
SET @address1_bit = 5
SET @position_bit = 6
SELECT
q1.date_effective, fn.val first_name, ln.val last_name,
addr.val address1, pos.val position
FROM
(SELECT
user_id, MAX(date_effective) date_effective
FROM
user_efdt
WHERE
date_effective <= @date
GROUP BY
user_id) q1
LEFT JOIN
(SELECT DISTINCT
user_id,
FIRST_VALUE(first_name) OVER (PARTITION BY user_id ORDER BY date_effective DESC) val
FROM
user_efdt
WHERE
(modified_fields & POWER(2, @first_name_bit)) > 0
AND date_effective <= @date) fn ON q1.user_id = fn.user_id
LEFT JOIN
(SELECT DISTINCT
user_id,
FIRST_VALUE(last_name) OVER (PARTITION BY user_id ORDER BY date_effective DESC) val
FROM
user_efdt
WHERE
(modified_fields & POWER(2, @last_name_bit)) > 0
AND date_effective <= @date) ln ON q1.user_id = ln.user_id
LEFT JOIN (
SELECT DISTINCT user_id, FIRST_VALUE(address1) OVER (PARTITION BY user_id ORDER BY date_effective DESC) val
FROM user_efdt
WHERE (modified_fields & POWER(2, @address1_bit)) > 0
AND date_effective <= @date
) addr ON q1.user_id = addr.user_id
LEFT JOIN (
SELECT DISTINCT user_id, FIRST_VALUE(position) OVER (PARTITION BY user_id ORDER BY date_effective DESC) val
FROM user_efdt
WHERE (modified_fields & POWER(2, @position_bit)) > 0
AND date_effective <= @date
) pos ON q1.user_id = pos.user_id
END
预期结果:
EXEC as_of_date_query '1-mar-16'
-- 2016-03-01 NULL NULL NULL engineer
EXEC as_of_date_query '5-apr-16'
-- 2016-04-01 NULL NULL 456 el dorado st engineer
EXEC as_of_date_query '15-may-16'
-- 2016-05-01 john doe 456 el dorado st engineer
-- 2016-05-01 mary ann 123 main st manager
EXEC as_of_date_query '5-jun-16'
-- 2016-06-01 john doe 456 el dorado st engineer 2
-- 2016-06-01 mary ann 1443 hoover st manager
EXEC as_of_date_query '1-jul-16'
-- 2016-07-01 john doe 456 el dorado st NULL
-- 2016-07-01 mary ann 1443 hoover st manager 2