截至日期查询

时间:2016-08-12 22:46:31

标签: sql-server

我试图对时态表实现类似的功能 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

0 个答案:

没有答案