Sql 数据库中的临时外键

Temporal Foreign Key in Sql Database

link 表在确定的时间段内具有唯一关系的最佳做法是什么?

示例 1

Tab_Civil_Status:

ID NAME ID_STATUS
1 Jenny 1
2 Mike 2

Tab_Civil_Status_Desc:

ID_STATUS STATUS_DESC
1 Single
2 Married

Tab_Civil_Events:

PERSON-ID EVENT REF_Date new-Status
2 Wedding 10/5/2017 2

Tab_Civil_Status 的查询应提供以下内容:

案例A:

Select * FROM `Tab_Civil_Status` […] Where REF_Date = 1/1/2020
ID NAME ID_STATUS
1 Jenny 1
2 Mike 2

案例 B:

Select * FROM `Tab_Civil_Status` […] Where REF_Date = 1/1/2016
ID NAME ID_STATUS
1 Jenny 1
2 Mike 1

示例 2

Tab_Orders:

ID Order_Code Product_Id
1 a 1
2 b 2

Tab_Products:

Product_Id Product_Name
1 Apple
2 Meta

Tab_Products_事件:

Product_Id EVENT REF_Date Old_Name New_Name
2 Change_Name 1/12/2020 Facebook Meta

这些是简化的示例,我正在寻找的是针对类似情况构建表和查询的最佳实践;要应用的逻辑而不是要使用的实际代码。

a - 最好的做法是在上述情况下跟踪更改的“事件 Table”吗?或者有更好的方法?

我知道可能有几种方法可以达到相同的范围,下面是另一个示例

Tab_Products_Events_Sample_2:

Product_Id EVENT REF_Date Name
2 Given _Name 1/12/2010 Facebook
2 Change_Name 1/12/2019 Meta

b - 有比下面更有效的查询吗?同样基于以上观点

@Selected_date = '1/1/2019'

SELECT 
    o.[Order_Code], 
    ifnull(e.[Old_Name],p.[Product_Name]) as p_name
FROM Tab_Orders as o
LEFT JOIN Tab_Products as p
ON o.[Product_Id]  = p.[Product_Id]
CROSS APPLY (
   SELECT top 1 t.[Old_Name] 
   FROM Tab_Products_Events as t
   WHERE o.[Product_Id]  = t.[Product_Id]
        and t.[REF_Date] >= @Selected_date
   ORDER BY t.[REF_Date] desc
) e

结果

Order_Code p_name
a Apple
b Facebook

谢谢

注意:我的回答是基于第一个例子

第一种方法:生成日期范围

您应该首先使用类似的查询提取每个人员状态的日期范围:

SELECT 
    PERSON_ID, new_Status, REF_Date, 
    ISNULL(LEAD(REF_Date) OVER(PARTITION BY PERSON_ID ORDER BY REF_DATE), GETDATE()) End_DATE
FROM
    Tab_Civil_Events

例如,考虑以下示例数据:

CREATE TABLE Tab_Civil_Events
(
    PERSON_ID INT,  
    EVENT VARCHAR(50),  
    REF_Date DATETIME,  
    new_Status INT
)

INSERT INTO Tab_Civil_Events(PERSON_ID, EVENT, REF_Date, new_Status)
VALUES (2, 'Wedding', '10/5/2017', 2),
       (2, 'Divorce', '10/5/2018', 1),
       (2, 'Wedding', '10/5/2019', 2),
       (2, 'Divorce', '10/5/2020', 1)

SELECT 
    PERSON_ID, new_Status, REF_Date, 
    ISNULL(LEAD(REF_Date) OVER (PARTITION BY PERSON_ID ORDER BY REF_DATE), 
    GETDATE()) End_DATE
FROM
    Tab_Civil_Events

这将导致以下 table:

PERSON_ID new_Status REF_Date End_DATE
2 2 2017-10-05T00:00:00Z 2018-10-05T00:00:00Z
2 1 2018-10-05T00:00:00Z 2019-10-05T00:00:00Z
2 2 2019-10-05T00:00:00Z 2020-10-05T00:00:00Z
2 1 2020-10-05T00:00:00Z 2022-01-29T09:47:52.7Z

第二步是创建一个通用的 table 表达式来查询数据。例如:

DECLARE @QueryDate DATETIME  = '20190501'

;WITH CTE_1 AS 
(
    SELECT 
        PERSON_ID, 
        new_Status, 
        REF_Date, 
        ISNULL(LEAD(REF_Date) OVER (PARTITION BY PERSON_ID ORDER BY REF_DATE), 
        GETDATE()) End_DATE
    FROM 
        Tab_Civil_Events
)
SELECT 
    CTE_1.PERSON_ID, 
    dsc.STATUS 
FROM
    CTE_1 
INNER JOIN 
    Tab_Civil_Status_Desc dsc ON CTE_1.new_Status = dsc.ID 
WHERE 
    @QueryDate BETWEEN REF_Date AND End_DATE

使用我之前提供的示例数据,此查询将给出以下结果:

PERSON_ID STATUS
2 Single

将日期值更改为 20191101

SET @QueryDate = '20191101'

将产生以下值:

PERSON_ID STATUS
2 Married

确保您已经创建了适当的索引来支持您的查询。

例如:

CREATE NONCLUSTERED  INDEX IX_PersonId_RefDate 
ON Tab_Civil_Events(PERSON_ID, REF_DATE) 
INCLUDE(new_status);

第二种方法:检索最近的事件日期

您应该首先检索值小于给定日期的每个人的最大事件日期:

SELECT 
    PERSON_ID,
    MAX(Ref_Date) AS event_date 
FROM 
    Tab_Civil_Events
WHERE 
    Ref_Date <= @QueryDate
GROUP BY 
    PERSON_ID

接下来,您应该将此查询与事件 table 连接起来,以获取如下状态信息:

DECLARE @QueryDate DATETIME  = '20190501'

SELECT 
    tbl.PERSON_ID, dsc.STATUS
FROM
    Tab_Civil_Events tbl 
INNER JOIN 
    Tab_Civil_Status_Desc dsc ON tbl.new_Status = dsc.ID 
INNER JOIN
    (SELECT 
         PERSON_ID,
         MAX(Ref_Date) AS event_date 
     FROM 
         Tab_Civil_Events
     WHERE 
         Ref_Date <= @QueryDate
     GROUP BY 
         PERSON_ID) t1 ON tbl.PERSON_ID = t1.PERSON_ID 
                       AND t1.event_date = tbl.ref_date

第三种方法:时间 tables

在您提到您可以更改数据存储方式后,我发布了这一部分。

使用 SQL Server 2016 或更新版本,您可以使用时态 tables 来简化数据查询。

official documentation所述:

SQL Server 2016 introduced support for temporal tables (also known as system-versioned temporal tables) as a database feature that brings built-in support for providing information about data stored in the table at any point in time rather than only the data that is correct at the current moment in time.

时间 table 被实现为一对 table,当前 table 和历史 table。在这些 table 中的每一个中,开始和结束日期都被存储并用于定义每一行的有效期。

您可以参考以下资源了解更多关于此类数据结构的信息: