SQL选择联接表中日期之前的日期的记录

时间:2019-01-15 19:08:23

标签: sql sql-server sql-server-2012 greatest-n-per-group

我有两个表,我需要能够同时更新它们,第一个表是项目列表:

ITEMS

Item* | Rev* | RDate        | ECO  | New
------+------+--------------+------+----
A     | 0A   | 2019-01-01   | E123 | 1
A     | 01   | 2018-01-01   | E456 | 0
B     | 0A   | 2018-12-31   | E765 | 0
C     | 01   | 2018-10-25   | E456 | 0

第二个是带有修订的父子表,但是我必须从Item表中填写Child Rev

Parent* | ParentRev* | Child* | ChildRev | VDate*
--------+------------+--------+----------+-----------
Y       | 0B         | C      | NULL     | 2019-01-01
Y       | 0C         | D      | NULL     | 2019-01-13
Z       | 01         | A      | NULL     | 2018-06-25
Z       | 02         | A      | NULL     | 2019-01-11
Z       | 0A         | B      | NULL     | 2019-01-01

注释:

  • 主键列标有*
  • VDate不应该是主键的一部分,但是数据集是错误的并且有重复项,因此我需要添加它

我研究了诸如Select first row in each GROUP BY group?之类的不同问题,但找不到在返回多个字段的联接表上使用基于行的条件的问题。无论如何,我使用它来填写ChildRev为NULL的记录,但其中不包含ECO

UPDATE T 
SET [ChildRev] = (SELECT TOP 1 I.[Rev] AS [ChildRev]
                  FROM [Items] AS I
                  WHERE (I.[Item] = T.[Child]
                    AND I.[RDate] <= T.[VDate]) 
                  ORDER BY I.[RDate] DESC
                 )
FROM [Tree] AS T
WHERE T.[ChildRev] IS NULL

这就是我得到的:

Parent | ParentRev | Child | ChildRev | VDate      | ECO
-------+-----------+-------+----------+------------+------
Y      | 0B        | C     | 01       | 2019-01-01 | NULL
Y      | 0C        | D     | NULL     | 2019-01-13 | NULL
Z      | 01        | A     | 01       | 2018-06-25 | NULL
Z      | 02        | A     | 0A       | 2019-01-11 | NULL
Z      | 0A        | B     | 0A       | 2019-01-01 | NULL

我正在处理Tree表中的4.5M +条记录和Item表中的1.2M +条记录,并且每天都在增长。我有两个问题:

  1. 是否有更好(更快)的方式来更新Tree表? (如果包含ECO,则奖励)

    当我添加新的Items时,它们在1字段中标记为New(可能使用触发器)

  2. 如何使用新的Tree

  3. 检查/更新Items

请注意,我无法真正控制数据的加载顺序(表格或日期)。


更新

因此,显然Select first row in each GROUP BY group?基本上是解决方案,我只是没有意识到。专门介绍如何使用CTE更新我的数据表。感谢@Xedni给我的启发;我只真正使用CTE进行递归查询。因此,我最终得到了2个相似的CTE,

  1. 当我向Tree表中添加新记录时,我添加了AND ChildRev IS NULL以限制更新:

    WITH CTE AS
    (
        SELECT ...
    )
    UPDATE CTE
    SET ChildRev = ItemRev
    WHERE RID = 1
      AND ChildRev IS NULL
    
  2. 当我向Materials表中添加新记录时,我添加了WHERE...ANY子句:

    WITH CTE AS
    (
        SELECT 
            ...
            RID = ROW_NUMBER() OVER (PARTITION BY t.Parent, t.ParentRev, t.Child 
                                     ORDER BY i.RDate DESC)
        FROM #Tree t
        JOIN #Items i
          ON t.Child = i.Item
         AND i.RDate <= t.VDate
        WHERE I.Process = ANY (SELECT Item FROM #Items WHERE New = 1)
    )
    UPDATE CTE
    SET ChildRev = ItemRev
    WHERE RID = 1
    

1 个答案:

答案 0 :(得分:1)

您可以通过联接获取所需的值,而不用在UPDATE子句中使用相关子查询。首先,创建一个看起来与相关子查询几乎相同的派生表,并获取需要用来标识#Items中要与#Tree中的行关联的所有唯一值。由于没有任何迹象表明所提到的表具有唯一性约束,因此我不得不对此进行猜测。

设置示例数据

-- Setting up sample data
if object_id('tempdb.dbo.#Items') is not null drop table #Items
create table #Items
(
    Item char(1),
    Rev char(2),
    RDate date,
    ECO char(4),
    New bit
)

insert into #Items (Item, Rev, RDate, ECO, New)
values 
    ('A', '0A', '2019-01-01', 'E123', 1),
    ('A', '01', '2018-01-01', 'E456', 0),
    ('B', '0A', '2018-12-31', 'E765', 0),
    ('C', '01', '2019-01-01', 'E456', 0)

if object_id('tempdb.dbo.#Tree') is not null drop table #Tree
create table #Tree
(
    Parent char(1),
    ParentRev char(2),
    Child char(1),
    ChildRev char(2),
    VDate date,
    ECO char(4)
)
insert into #Tree (Parent, ParentRev, Child, ChildRev, VDate)
values
    ('Y', '0B', 'C', NULL, '2019-01-01'),
    ('Y', '0C', 'D', NULL, '2019-01-13'),
    ('Z', '01', 'A', NULL, '2018-06-25'),
    ('Z', '02', 'A', NULL, '2019-01-11'),
    ('Z', '0A', 'B', NULL, '2019-01-01')

现在,您有了派生表,将#tree中的行映射到具有您希望从#items开始的日期的行,再次将其联接到#items表中以获得{{ 1}},ECO以及您想要的其他任何内容。

Rev

通常来说,这可能会比相关子查询的性能更好,尽管根据所存在的索引,您的里程可能有所不同。另外,如果您确实要遍历450万条这样的记录,请考虑将其分成几批,或者想出一种方法来预过滤需要提前更新的内容。

关于启动新行时启动此过程,您有两个选择。

  1. 在任何过程中插入设置-- Actual Update Statement update a set ChildRev = c.Rev, Eco = c.Eco from #Tree a -- Consruct a derived table basically mapping the rows in #tree to the rows with the desired dates you want. inner join ( select t.Child, t.ParentRev, MaxRDate = max(i.RDate) from #Tree t inner join #Items i on t.Child = i.Item and i.RDate <= t.VDate group by t.Child, t.ParentRev ) b on a.Child = b.Child and a.ParentRev = b.ParentRev -- Finally, join the "intermidate mapping table" to #Items to get the values (eco, rev, etc.) you actually want inner join #Items c on b.Child = c.Item and b.MaxRDate = c.RDate select top 1000 * from #Tree 标志的数据的过程中,都要使它同时启动此过程(或类似的操作在同一事务中同时进行)。
  2. 如果这不是一个选择,则理论上您可以在new表上使用触发器执行相同的操作,并根据需要启动此过程。尽管TBH我还是建议使用TBH,因为它更容易在同一位置包含您需要的所有逻辑,并且没有触发器的额外开销,这在某种程度上也混淆了保持数据同步的过程。 li>

另一个选择

我刚想出的另一种方法是在单个查询中完成所有操作。使用带有Items RID的CTE(或派生表;随心所欲)。然后在row_number

RID = 1