sql - SQL在联接表中选择日期早于日期的记录
问题描述
我有两个表,我需要能够双向更新它们,第一个表是项目列表:
项目
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
第二个是父子表,有revisions,但是我要从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
不应该是主键的一部分,但是数据集不好并且有重复,所以我需要添加它
我研究了不同的问题,例如在每个 GROUP BY 组中选择第一行?,但我找不到一个在返回多个字段的连接表上使用基于行的条件。无论如何,我正在使用它来填写 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+ 记录,并且每天都在增长。我有两个问题:
有没有更好(更快)的方法来更新
Tree
表格?(如果包含 则奖励ECO
)当我添加 new
Items
时,它们1
在New
字段中用 a 标记(可能使用触发器)我将如何
Tree
使用新的检查/更新表Items
请注意,我无法真正控制数据加载的顺序(表格或日期)。
更新
那么,显然选择每个 GROUP BY 组中的第一行?基本上是解决方案,我只是没有意识到。特别是关于如何使用 CTE 更新我的数据表。感谢@Xedni 启发我;我只真正将 CTE 用于递归查询。所以,我最终得到了 2 个类似的 CTE,
当我向
Tree
表中添加新记录时,我添加了AND ChildRev IS NULL
以限制更新:WITH CTE AS ( SELECT ... ) UPDATE CTE SET ChildRev = ItemRev WHERE RID = 1 AND ChildRev IS NULL
当我向
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
解决方案
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
表以获取ECO
,Rev
以及您想要的任何其他内容。
-- 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
一般来说,这可能比相关子查询执行得更好,尽管根据存在的索引,您的里程可能会有所不同。此外,如果您真的要处理 450 万条这样的记录,请考虑将其分成几批,或者想办法预先过滤您需要提前更新的内容。
至于在新行出现时启动此过程,您有两个选择。
- 在任何插入设置
new
标志的数据的过程中,让它同时启动这个过程(或者在同一个事务中做类似的事情)。 Items
如果这不是一个选项,理论上您可以使用桌面上的触发器执行相同的操作,并根据需要启动此过程。尽管 TBH 我推荐前者,因为它更容易在同一个地方包含您需要的所有逻辑,并且没有触发器的额外开销,这也有点混淆了保持数据同步的过程。
另一种选择
我刚刚制定的另一种方法是在单个查询中完成所有操作。使用带有row_number
RID 的 CTE(或派生表;随您所愿)。然后更新那个地方RID = 1
;with src as
(
select
t.Parent,
t.ParentRev,
t.Child,
t.ChildRev,
t.VDate,
t.ECO,
Item = i.Item,
ItemRev = i.Rev,
ItemRDate = i.RDate,
ItemECO = i.ECO,
ItemNew = i.NEW,
RID = row_number() over (partition by t.Parent, t.ParentRev, t.Child order by i.RDate desc)
from #Tree t
inner join #Items i
on t.Child = i.Item
and i.RDate <= t.VDate
)
update src
set ECO = ItemECO,
ChildREv = ItemRev
where RID = 1
推荐阅读
- haskell - 在 Haskell 中检测冗余约束?
- opencart-3 - 在 opencart 中调用供应商文件夹类
- php - 需要在 laravel groupby 日期中加入 3 个表
- r - 将 hh:mm:ss 格式的时间转换为秒或分钟
- mobile - 我可以将授权码与 PKCE Flow 一起用于移动应用程序吗?
- sql - 存储过程将不同表中的值相乘
- sql - Flutter 将数据插入数据库 sqflite
- google-apps-script - 与谷歌表格中的连接匹配
- php - WordPress 插件开发。包含文件
- charts - 如何动态移动chart.js中图表上的点?