【问题标题】:How to select all the records that have different values only on particular columns?如何选择仅在特定列上具有不同值的所有记录?
【发布时间】:2014-08-21 20:31:45
【问题描述】:

我正试图考虑可以完成这项工作的查询,但我决定向您寻求帮助。

我有一个 Answer 表和一个 AnswerHistory 表。在我的 BLL 中,我有两个单独的方法来更新 Answer 表。当用户更改 Answer 表的 Title 或 Body 列时使用第一个。无论这两者中的哪一个被更新,都会在更改发生之前创建一个新的 AnswerHistory,其中包含 Answer 实体的所有字段。意思是,如果我将 Answer 实体的标题从“Title 1”更改为“Title 2”,我将有一个新的 AnswerHistory 实体,在 Title 列中有“Title 1”,以及此更改的日期发生。此外,另一个函数将 IsAccepted 列从 0 更改为 1,反之亦然。当然,它还创建了一个 AnswerHistory 实体。

总结一下: - 函数 1 更改标题和正文,创建一个新的 AnswerHistory 实体(Answer 实体的副本,在更改发生之前); - 函数 2 仅更改 IsAccepted 列。还会创建一个新的 Answer 实体(Answer 实体的副本,在更改发生之前)。

我想执行这样一个查询,该查询将检索由于运行函数 1 而创建的所有 AnswerHistory 实体。意思是,我想选择在某些用户更改标题或正文后出现的所有 AnswerHistories原始的 Answer 实体。我不想检索用户更改 Answer 的 IsAccepted 属性时创建的那些。

如果我在 Answer 表中有以下数据,

Id  QeustionId  Title     Body     IsAccepted   CreationUserId  CreationDate        
1   1           Title1_3  Body1_3  1            2               2014-07-22
2   2           Title2_1  Body2_1  1            2               2014-07-22
3   3           Title3_2  Body3_2  0            2               2014-07-22

AnswerHistory 表中的这些实体

Id  AnswerId    Title     Body      IsAccepted  ModificationDate    
13  1           Title1_1  Body1_1   0           2014-08-06          
14  1           Title1_1  Body1_1   1           2014-09-06          
15  1           Title1_2  Body1_2   1           2014-10-06          
16  2           Title2_1  Body2_1   0           2014-08-06          
17  3           Title3_1  Body3_1   0           2014-08-06          

我的查询应该返回:

Id  AnswerId    Title     Body      IsAccepted  ModificationDate            
14  1           Title1_1  Body1_1   1           2014-09-06          
15  1           Title1_2  Body1_2   1           2014-10-06              
17  3           Title3_1  Body3_1   0           2014-08-06   

这可能吗?如果是,那怎么办?

顺便说一句,我不想改变我的架构并创建不同的历史表来处理不同的场景——当用户更改问题的标题或正文时,另一个对于 IsAccepted 更改。

编辑: 我正在使用 Microsoft SQL Server 2012。

【问题讨论】:

  • @paqogomez 我假设他的意思是微软的 SQL Server,这意味着 mssql
  • SQL Server 的哪个版本?
  • 为什么 16 2 Title2_1 Body2_1 0 2014-08-06 不在结果中。
  • 历史确实应该使用时间点列来处理。
  • 16-2 不在结果中,因为它是由于更改 IsAccepted 列的值而产生的(查看答案表)。我决定采用这种方法,而不是时间点方法,因为我不(并且仍然不)想要数据可重复性(不确定这是否是正确的术语)。使用这种设计,我有我需要知道的一切,无需重复,使用更少的空间:)

标签: sql sql-server


【解决方案1】:

为什么不在创建时将它们直接放入答案历史表中?在两个表之间拆分数据会使查询更难编写。

让我们从 CTE 开始,以获取真实的历史记录(包括当前值)。然后添加一个方法来进行比较。我将假设 SQL Server 2012+,因为 lead() 函数将是最有用的。

with changes as (
      select Id, AnswerId, Title, Body, IsAccepted, ModificationDate
      from AnswerHistory
      union all
      select NULL as Id, AnswerId, Title, Body, IsAccepted, ModificationDate
      from Answer
     ),
     changeslead as (
      select c.*,
             lead(title) over (partition by AnswerId order by ModificationDate) as next_title,
             lead(body) over (partition by AnswerId order by ModificationDate) as next_body
      from changes c
     )
select id, AnswerId, Title, Body, IsAccepted, ModificationDate
from changeslead
where title <> next_title or body <> next_body;

这假设titlebody 不是NULL,这与问题的措辞一致。

【讨论】:

    【解决方案2】:

    首先我创建一些测试表

    使用临时数据库 --创建测试表 创建表答案 (ID int PRIMARY KEY, QuestionId int not null, Title varchar(25) not null, Body varchar(25) not null, IsAccepted bit not null, CreationUserId int not null, CreationDate date not null)

    CREATE TABLE AnswerHistory 
    (Id int not null PRIMARY KEY, AnswerId int not null, Title varchar(25) not null, Body varchar(25) not null, IsAccepted bit not null, ModificationDate date not null)
    CREATE INDEX IX_AnswerHistory_AnswerID ON AnswerHistory(AnswerID)
    go
    
    --Populate Test Tables
    INSERT Answer(Id, QuestionId, Title, Body, IsAccepted, CreationUserId, CreationDate) VALUES (1, 1, 'Title1_3', 'Body1_3', 1, 2, '2014-07-22')
    INSERT Answer(Id, QuestionId, Title, Body, IsAccepted, CreationUserId, CreationDate) VALUES (2, 2, 'Title2_1', 'Body2_1', 1, 2, '2014-07-22')
    INSERT Answer(Id, QuestionId, Title, Body, IsAccepted, CreationUserId, CreationDate) VALUES (3, 3, 'Title3_2', 'Body3_2', 0, 2, '2014-07-22')
    INSERT INTO AnswerHistory(Id, AnswerId, Title, Body, IsAccepted, ModificationDate) VALUES(13, 1, 'Title1_1', 'Body1_1', 0, '2014-08-06')
    INSERT INTO AnswerHistory(Id, AnswerId, Title, Body, IsAccepted, ModificationDate) VALUES(14, 1, 'Title1_1', 'Body1_1', 1, '2014-09-06')
    INSERT INTO AnswerHistory(Id, AnswerId, Title, Body, IsAccepted, ModificationDate) VALUES(15, 1, 'Title1_2', 'Body1_2', 1, '2014-10-06')
    INSERT INTO AnswerHistory(Id, AnswerId, Title, Body, IsAccepted, ModificationDate) VALUES(16, 2, 'Title2_1', 'Body2_1', 0, '2014-08-06')
    INSERT INTO AnswerHistory(Id, AnswerId, Title, Body, IsAccepted, ModificationDate) VALUES(17, 3, 'Title3_1', 'Body3_1', 0, '2014-08-06')
    

    创建测试表后,我使用子查询使用 UNION ALL 子句将当前的 Answers 和 Answer History 表合并为一组记录。修改日期设置为 9999 年 12 月 31 日,因此它总是在最后一个历史项目之后。

    SELECT Id, AnswerId, Title, Body, IsAccepted, ModificationDate FROM AnswerHistory
    UNION ALL 
    SELECT NULL as Id, ID AS AnswerId, Title, Body, IsAccepted, CONVERT(date,'9999-12-31')as ModificationDate FROM Answer
    

    然后我们使用 SQL server 中的 LEAD 函数来获取每一行的下一个 Title 和 Body。

    SELECT 
        u.Id, 
        u.AnswerId, 
        u.Title, 
        u.Body, 
        u.IsAccepted, 
        u.ModificationDate,
        Lead(Title) OVER (PARTITION BY u.AnswerId ORDER BY u.ModificationDate) as nextTitle,
        Lead(Body) OVER (PARTITION BY u.AnswerId ORDER BY  u.ModificationDate) as nextBody
        FROM 
            (
            SELECT Id, AnswerId, Title, Body, IsAccepted, ModificationDate FROM AnswerHistory
    
            UNION ALL 
            SELECT NULL as Id, ID AS AnswerId, Title, Body, IsAccepted, CONVERT(date,'9999-12-31')as ModificationDate FROM Answer
            )u --Union of historic and current queries
    

    这给了我们

    Id          AnswerId    Title           Body            IsAccepted ModificationDate nextTitle       nextBody
    ----------- ----------- --------------- --------------- ---------- ---------------- --------------- ---------------
    13          1           Title1_1        Body1_1         0          2014-08-06       Title1_1        Body1_1
    14          1           Title1_1        Body1_1         1          2014-09-06       Title1_2        Body1_2
    15          1           Title1_2        Body1_2         1          2014-10-06       Title1_3        Body1_3
    NULL        1           Title1_3        Body1_3         1          9999-12-31       NULL            NULL
    16          2           Title2_1        Body2_1         0          2014-08-06       Title2_1        Body2_1
    NULL        2           Title2_1        Body2_1         1          9999-12-31       NULL            NULL
    17          3           Title3_1        Body3_1         0          2014-08-06       Title3_2        Body3_2
    NULL        3           Title3_2        Body3_2         0          9999-12-31       NULL            NULL
    
    (8 row(s) affected)
    

    LEAD() 不能在 WHERE 子句中使用,因此必须将其包装在另一个子查询中以包装结果,以便我们应用过滤器。

    SELECT 
    w.Id ,w.AnswerId ,w.Title,w.Body ,w.IsAccepted ,w.ModificationDate 
    FROM 
        (SELECT 
        u.Id, 
        u.AnswerId, 
        u.Title, 
        u.Body, 
        u.IsAccepted, 
        u.ModificationDate,
        Lead(Title) OVER (PARTITION BY u.AnswerId ORDER BY  u.AnswerId,u.ModificationDate) as nextTitle,
        Lead(Body) OVER (PARTITION BY u.AnswerId ORDER BY  u.ModificationDate) as nextBody
        FROM 
            (
            SELECT Id, AnswerId, Title, Body, IsAccepted, ModificationDate FROM AnswerHistory
            UNION ALL 
            SELECT NULL as Id, ID AS AnswerId, Title, Body, IsAccepted, CONVERT(date,'9999-12-31')as ModificationDate FROM Answer
            )u --Union of historic and current queries
        )w --Subquery with windowed Functions
    WHERE w.Body <> w.nextBody OR w.Title <> w.nextTitle
    

    有关 LEAD 的更多信息,请查看 Microsoft http://msdn.microsoft.com/en-us/library/hh213125(v=sql.110).aspx

    如果您使用的是早期版本的 SQL Server,您将无法使用此功能,并且实现起来会更加尴尬

    【讨论】:

      猜你喜欢
      • 2018-12-06
      • 1970-01-01
      • 2021-03-22
      • 1970-01-01
      • 2013-11-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多