检查后插入多行

Posted

技术标签:

【中文标题】检查后插入多行【英文标题】:Insert multiple rows after a check 【发布时间】:2021-08-24 20:18:33 【问题描述】:

我有一个用例,我有 200 个表。我需要从所有 200 个表中获取最新记录,并将它们存储在临时表中。现在使用每个暂存记录需要检查它是否已经存在 在 Final table 中,该记录的状态列是打开还是关闭。

初始表:(所有 200 个表的通用架构) ID、时间戳、名称

暂存表: ID、时间戳、名称

决赛桌: ID、时间戳、名称、状态、计数

我的做法:

按时间戳和限制 1 排序将给出每个表中的最新记录 将 200 个表中的所有最新记录联合起来(200 条带联合的 select 语句) 暂存表现在将有 200 条记录 检查每条记录是否已经存在于Final table中,如果存在且status="open"需要 增加计数,如果 status="close" 或没有找到任何匹配,它应该是 在最终表中作为新记录插入 遇到了 TSQL "IF NOT EXISTS () BEGIN END ELSE BEGIN END" 和 while 循环(不知道在这种情况下如何使用)

所有这些过程每 15 分钟发生一次。

任何更好的方法或建议以及如何处理检查和插入每一行的最后一步。 我是 SQL 新手。

更多信息: 这些初始表在配置单元中,其中 200 个不同的进程试图同时写入表,因此每次写入都会发生表锁定,其余进程应该等待,所以我为每个进程准备了每个表。每次暂存不会有200条记录,我给出了最坏的情况。理想情况下,它在任何给定点的范围为 0 到 10,但它必须每 15 分钟检查一次所有 200 个表。这个来自 hive 的 staging table 被带入 sql server 并推送到 Final table 以服务器其他目的

【问题讨论】:

“合并”语句会更好。您不需要临时表,只需插入,或者如果存在冲突,则更新。 docs 200 个表很可能是设计缺陷 忘记了 200 张桌子。是的,最终的问题是为什么 “我需要从所有 200 个表中获取最新记录,并将它们存储在 staging 表中。” 事实上,您有 200 个具有相同定义的表肯定是一种设计缺陷。 ...或 something other than MERGE 以保持理智。 【参考方案1】:

虽然你有 200 个表都具有相同的方案听起来很奇怪,但下面的 MERGE-Statement 应该可以实现你想要的。

WITH STAGING_DATA ([ID], [TIMESTAMP], [NAME]) 
as 
(
    SELECT TOP 1 [ID], [TIMESTAMP], [NAME] FROM <TABLE_1> ORDER BY [TIMESTAMP] DESC
    UNION ALL
    SELECT TOP 1 [ID], [TIMESTAMP], [NAME] FROM <TABLE_2> ORDER BY [TIMESTAMP] DESC
    UNION ALL
    ...
    UNION ALL
    SELECT TOP 1 [ID], [TIMESTAMP], [NAME] FROM <TABLE_N> ORDER BY [TIMESTAMP] DESC
)
MERGE INTO <FINAL_TABLE> AS TARGET  
USING (
        SELECT [ID], [TIMESTAMP], [NAME] FROM STAGING_DATA
      )
       AS SOURCE ([ID], [TIMESTAMP], [NAME])
       ON   TARGET.ID = SOURCE.ID AND TARGET.STATUS = 'OPEN'
WHEN MATCHED THEN
    UPDATE SET [COUNT] = ISNULL([COUNT], 0) + 1
WHEN NOT MATCHED BY TARGET THEN
    INSERT ([ID], [TIMESTAMP], [NAME], [STATUS], [COUNT]) VALUES ([ID], [TIMESTAMP], [NAME], 'OPEN', 0)

STAGING_DATA CTE 正在收集所有数据(每个表中按时间戳排序的前 1 个数据集),merge 语句负责将结果合并到最终表中。合并语句还检查是否已经存在具有相同 ID 和状态“OPEN”的数据集,在这种情况下,它只是通过将计数器增加 1 来更新最终表中的相应数据集。如果找不到数据集(或有'OPEN' 以外的另一个状态)我们将一个新数据集添加到最终表中。

ORDER BY 和 UNION ALL 语句:

只要它们在 CTE 内,ORDER BY 就可以与 UNION ALL 一起使用。至少当我使用以下设置在 SQL Server 2012、2017 和 2019 上对其进行测试时:

WITH STAGING_DATA ([ID], [TIMESTAMP], [NAME]) 
as 
(
    SELECT TOP 1 [ID], [TIMESTAMP], [NAME] 
    FROM (VALUES 
                    ('1',   '2021-01-01 00:00:00.000', 'Käser'), 
                    ('74',  '2021-01-01 00:00:00.000', 'Valérie Maier'), 
                    ('2',   '2021-01-01 00:00:00.000', 'Jäggi'), 
                    ('84',  '2021-01-01 00:00:00.000', 'D'), 
                    ('83',  '2021-01-01 00:00:00.000', 'Wyss')
                    ) as DATA ([ID], [TIMESTAMP], [NAME])
    ORDER BY [ID] ASC

    UNION ALL

    SELECT TOP 1 [ID], [TIMESTAMP], [NAME] 
    FROM (VALUES 
                    ('1',   '2021-01-01 00:00:00.000', 'Käser'), 
                    ('74',  '2021-01-01 00:00:00.000', 'Valérie Maier'), 
                    ('2',   '2021-01-01 00:00:00.000', 'Jäggi'), 
                    ('84',  '2021-01-01 00:00:00.000', 'D'), 
                    ('83',  '2021-01-01 00:00:00.000', 'Wyss')
                    ) as DATA ([ID], [TIMESTAMP], [NAME])
    ORDER BY [ID] DESC

    UNION ALL

    SELECT TOP 2 [ID], [TIMESTAMP], [NAME] 
    FROM (VALUES 
                    ('1',   '2021-01-01 00:00:00.000', 'Käser'), 
                    ('74',  '2021-01-01 00:00:00.000', 'Valérie Maier'), 
                    ('2',   '2021-01-01 00:00:00.000', 'Jäggi'), 
                    ('84',  '2021-01-01 00:00:00.000', 'D'), 
                    ('83',  '2021-01-01 00:00:00.000', 'Wyss')
                    ) as DATA ([ID], [TIMESTAMP], [NAME])
    ORDER BY [ID] ASC

    UNION ALL
    
    SELECT TOP 2 [ID], [TIMESTAMP], [NAME] 
    FROM (VALUES 
                    ('1',   '2021-01-01 00:00:00.000', 'Käser'), 
                    ('74',  '2021-01-01 00:00:00.000', 'Valérie Maier'), 
                    ('2',   '2021-01-01 00:00:00.000', 'Jäggi'), 
                    ('84',  '2021-01-01 00:00:00.000', 'D'), 
                    ('83',  '2021-01-01 00:00:00.000', 'Wyss')
                    ) as DATA ([ID], [TIMESTAMP], [NAME])
    ORDER BY [ID] DESC
)
SELECT [ID], [TIMESTAMP], [NAME] FROM STAGING_DATA

【讨论】:

必须尝试,在 MERGE 中 INSERT with OUTPUT 子句和 Inserted.ID 给出插入记录的 ID。所有这些都作为另一个工具的脚本运行。不通过 SSMS @Lucid:只要它运行的是 TSQL,工具本身就不会起作用。 OUTPUT 子句也可以与 Merge 语句一起使用。将它与合并语句一起使用时,您甚至还有一个可用的附加列($action varchar(10),包含“INSERT”、“UPDATE”或“DELETE”)(请参阅 MS 文档:docs.microsoft.com/en-us/sql/t-sql/queries/…) 当与目标不匹配时,插入([ID]、[TIMESTAMP]、[NAME]、[STATUS]、[COUNT])输出 INSERTED.ID 值([ID]、[TIMESTAMP]、 [NAME], 'OPEN', 0) 抛出错误“'OUTPUT' 附近的语法不正确。” 输出子句需要位于语句的最前面:WHEN NOT MATCHED BY TARGET THEN INSERT ([ID], [TIMESTAMP], [NAME], [STATUS], [COUNT])值([ID]、[TIMESTAMP]、[NAME]、'OPEN'、0)输出 INSERTED.ID 我建议您查看文档中 MERGE 语句的语法:docs.microsoft.com/en-us/sql/t-sql/statements/…【参考方案2】:

您插入暂存表的方法在逻辑上是可行的,不幸的是,在 SQL Server 中,您不能使用包含 ORDER BYUNION 查询,因此以下内容将不起作用

SELECT TOP(1) ID,[timestamp], [name] FROM dbo.TblA ORDER BY timestamp
UNION ALL 
SELECT TOP(1) ID,[timestamp], [name] FROM dbo.TblB ORDER BY timestamp 
UNION ALL 
SELECT TOP(1) ID,[timestamp], [name] FROM dbo.TblC ORDER BY timestamp

如果要执行UNION,则必须将ORDER BY 放入子查询中,然后执行UNION。它看起来像这样:

--INSERT INTO dbo.Staging (ID, [timestamp], [name])
SELECT q1.ID, q1.[timestamp], q1.[name] FROM 
(SELECT TOP(1) ID, [timestamp], [name] FROM dbo.TblA ORDER BY [timestamp] DESC) AS q1
UNION ALL 
SELECT q2.ID, q2.[timestamp], q2.[name] FROM 
(SELECT TOP(1) ID, [timestamp], [name] FROM dbo.TblB ORDER BY [timestamp] DESC) AS q2
UNION ALL 
SELECT q3.ID, q3.[timestamp], q3.[name] FROM 
(SELECT TOP(1) ID, [timestamp], [name] FROM dbo.TblC ORDER BY [timestamp] DESC) AS q3

这确实很丑陋,我不知道你是否会更好地使用 200 个单独的 INSERT 语句,但现在让我们坚持使用这种方法。因此,您现在可以暂存这些记录:

INSERT INTO dbo.Staging (ID, [timestamp], [name])
SELECT q1.ID, q1.[timestamp], q1.[name] FROM 
(SELECT TOP(1) ID, [timestamp], [name] FROM dbo.TblA ORDER BY [timestamp] DESC) AS q1
UNION ALL 
SELECT q2.ID, q2.[timestamp], q2.[name] FROM 
(SELECT TOP(1) ID, [timestamp], [name] FROM dbo.TblB ORDER BY [timestamp] DESC) AS q2
UNION ALL 
SELECT q3.ID, q3.[timestamp], q3.[name] FROM 
(SELECT TOP(1) ID, [timestamp], [name] FROM dbo.TblC ORDER BY [timestamp] DESC) AS q3

我假设您在每次运行之前TRUNCATE 暂存表,因此它将仅包含您将要加载到最终表中的记录。我自己更喜欢使用INNER JOINs 和LEFT OUTER JOINs 的组合来查找不存在的和已经存在的(在我看来使调试和开发更容易,但其他人可能不同意),但是有@987654333 @ 方法(我不会在这里展示)。

因此,要加载决赛桌,您可以执行以下操作:

-- increment existing open records 
-- the INNER JOIN guarantees an existing record that matches ID
UPDATE final SET final.[count] = final.[count] + 1
FROM dbo.Staging AS stage 
INNER JOIN dbo.Final AS final ON final.ID = stage.ID AND final.[status] = 'open';

-- add closed records
-- same comment about the INNER JOIN
INSERT INTO dbo.Final(ID, [timestamp], [name], [status], [count])
SELECT final.ID, final.[timestamp], final.[name], 'open', 1 
FROM dbo.Staging AS stage 
INNER JOIN dbo.Final AS final ON final.ID = stage.ID AND final.[status] = 'closed'

-- no match, insert these records
-- the LEFT OUTER JOIN with the WHERE clause guarantees no matching record
INSERT INTO dbo.Final(ID, [timestamp], [name], [status], [count])
SELECT stage.ID, stage.[timestamp], stage.[name], 'open', 1
FROM dbo.Staging AS stage 
LEFT OUTER JOIN dbo.Final AS final ON final.ID = stage.ID 
WHERE final.ID IS NULL;

我只是在 ID 值上进行了匹配,但您可以在 ON 子句中轻松修改被视为匹配的内容。

【讨论】:

我在 hive 中进行联合,至少我在使用 ORDER BY 和使用 UNION ALL 时遇到了同样的问题。子查询中的 ORDER BY 和 LIMIT 也适用于 hive。正如@InD 在 CTE 中建议的那样,它们可以在没有子查询的情况下工作。

以上是关于检查后插入多行的主要内容,如果未能解决你的问题,请参考以下文章

插入后使用触发器更新多行(sql server)

在单个事务中插入多行时 Oracle 查询性能下降

在 JPA 中插入多行的最有效方法

MySQLi - 插入多行 - 空 POST 数组

PHP数组未向数据库插入多行

使用 T-SQL 中的一个参数将多个值插入多行