我正在编写一个数据泵,它可以从source_table
获取包含code
,some_data
,oz1
,{{1}列的信息},oz2
,oz3
,我会将其存储在oz4
中相同结构。与此同时,我想更新另一个具有不同结构的表格(比如target_table
) - 记录oz_table
,code
- 这是四个记录而不是一个记录,其中包含四个值(最大值因为oz
的空值和NULL值不存储)。
我对现有的ozX
使用MERGE
命令(Microsoft T-SQL)(一条记录中有4盎司 - 旧方法)。使用target_table
机制将INSERTed / UPDATEd记录收集到表变量OUTPUT
中。 (如果源记录消失,则故意永远不会删除目标记录;因此,没有DELETE操作。)
到目前为止,我有一个这样的代码:
@info_table
到目前为止一切正常。现在我想处理CREATE PROCEDURE dbo.data_pump
AS
BEGIN
SET NOCOUNT ON
DECLARE @result int = -555 -- init (number of affected records)
DECLARE @info_table TABLE (
action nvarchar(10),
code int,
oz1 nvarchar(40),
oz2 nvarchar(40),
oz3 nvarchar(40),
oz4 nvarchar(40)
)
BEGIN TRANSACTION tran_data_pump
BEGIN TRY
MERGE target_table AS target
USING (SELECT code, some_data, oz1, oz2, oz3, oz4
FROM source_table) AS source
ON target.code = source.code
WHEN MATCHED AND (COALESCE(target.some_data, '') != COALESCE(source.some_data, '')
OR COALESCE(target.oz1, '') != COALESCE(source.oz1, '')
OR COALESCE(target.oz2, '') != COALESCE(source.oz2, '')
OR COALESCE(target.oz3, '') != COALESCE(source.oz3, '')
OR COALESCE(target.oz4, '') != COALESCE(source.oz4, '')
) THEN
UPDATE
SET target.some_data = source.some_data,
target.oz1 = source.oz1,
target.oz2 = source.oz2,
target.oz3 = source.oz3,
target.oz4 = source.oz4
WHEN NOT MATCHED THEN
INSERT (code, some_data,
oz1, oz2, oz3, oz4)
VALUES (source.code, source.some_data,
source.oz1, source.oz2, source.oz3, source.oz4)
OUTPUT
$action AS action, -- INSERT or UPDATE
inserted.code AS code,
inserted.oz1 AS oz1,
inserted.oz2 AS oz2,
inserted.oz3 AS oz3,
inserted.oz4 AS oz4
INTO @info_table;
SET @result = @@ROWCOUNT
COMMIT TRANSACTION tran_data_pump
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION tran_data_pump
SET @result = -1 -- transaction-failed indication
END CATCH
RETURN @result -- OK, number of the transfered records
END
以插入/更新@info_table
。对于操作oz_table
,应首先删除包含UPDATE
的记录,并插入新记录。订单并不重要,插入的记录的新数量可能不同。 oz中的code
或空字符串不应产生任何记录。对于NULL
操作,仅插入新记录的情况更简单。
更新:稍微修改了一下这个问题,以澄清问题的核心。数据表可以这样定义:
INSERT
查看完整的测试脚本(创建数据库,表格,在http://pastebin.com/wBz3Tzwn调用CREATE TABLE dbo.source_table (
ID int IDENTITY PRIMARY KEY NOT NULL,
code int,
some_data nvarchar(50),
oz1 nvarchar(40),
oz2 nvarchar(40),
oz3 nvarchar(40),
oz4 nvarchar(40)
)
CREATE TABLE dbo.target_table (
ID int IDENTITY PRIMARY KEY NOT NULL,
code int,
some_data nvarchar(50),
oz2 nvarchar(40),
oz3 nvarchar(40),
oz1 nvarchar(40),
oz4 nvarchar(40)
)
CREATE TABLE dbo.oz_table (
ID int IDENTITY PRIMARY KEY NOT NULL,
code int,
oz nvarchar(40) NOT NULL
)
怎么做得很好?我需要有效的解决方案,因为日期量可能很大,操作应该尽可能快。
答案 0 :(得分:4)
如果我正确理解了你的问题陈述,那么下面的方法可能是一种解决方法 -
-- declare the temp tables
DECLARE @info_table TABLE (
action nvarchar(10),
ID int,
oz1 nvarchar(40),
oz2 nvarchar(40),
oz3 nvarchar(40),
oz4 nvarchar(40)
)
--create intermediate table to store the results
CREATE TABLE #temp_alternative_table (ID int,oz nvarchar(40))
-- insert some dummy values
INSERT INTO @info_table (action,ID,oz1,oz2,oz3,oz4)
VALUES
('INSERT',1, '85', '94', '78', '90'),
('UPDATE',2, '75', '88', '91', '78')
--SELECT * FROM @info_table
-- doing unpivot and transforming one row many columns to many rows one column and inserting into intermediate temp table
INSERT INTO #temp_alternative_table
SELECT *
FROM (
SELECT
Action
, ID
, [Oz]
FROM @info_table
UNPIVOT
(
[Oz] FOR tt IN (oz1, oz2, oz3, oz4)
) unpvt
) t
-- delete from main table all the records for which the action is UPDATE (stored in intermediate temp table for the same ID as of main table)
DELETE at
FROM alternative_table at
INNER JOIN #temp_alternative_table tat
ON at.ID = tat.ID
WHERE tat.action = 'UPDATE'
-- now insert all the records in main table
INSERT INTO alternative_table (ID,Oz)
SELECT ID,Oz
FROM #temp_alternative_table
如果您正在寻找,请告诉我。希望这会有所帮助。
答案 1 :(得分:2)
既然你在谈论效率,首先应该有适当的索引。
source_table
和target_table
应在code
上拥有唯一索引。
它应该是唯一的,否则主MERGE
在尝试多次更新同一行时会失败。
oz_table
应在code
上具有非唯一索引。
@info_table
应将code
作为主键。
对于来自一个code
的相同MERGE
,不可能有两个不同的操作,因此code
应该是唯一的:
DECLARE @info_table TABLE
(
action nvarchar(10),
code int PRIMARY KEY,
oz1 nvarchar(40),
oz2 nvarchar(40),
oz3 nvarchar(40),
oz4 nvarchar(40)
);
不需要额外的临时表。我们已经有一个 - @info_table
。
在MERGE
之后,当填充@info_table
时,我们需要执行两个步骤:1)从oz_table
删除一些行,2)向oz_table
添加一些行。
首先从oz_table
中删除MERGE
更新的那些行。
事实上,MERGE
插入的那些行在oz_table
中不会以任何方式存在,因此我们可以使用简单的DELETE
语句。
无需按UPDATE
操作进行明确过滤。这样的过滤器不会删除任何行。
如果在此数据泵过程之外有可能更改oz_table
,则需要额外的过滤器。
DELETE FROM dbo.oz_table
WHERE dbo.oz_table.code IN
(
SELECT code FROM @info_table
)
;
两个表上code
的索引都有助于加入它们。
然后,只需插入更新和插入的行。
INSERT INTO dbo.oz_table(code, oz)
SELECT
T.code
,CA.oz
FROM
@info_table AS T
CROSS APPLY
(
VALUES
(T.oz1),
(T.oz2),
(T.oz3),
(T.oz4)
) AS CA (oz)
WHERE
CA.oz IS NOT NULL
AND CA.oz <> ''
-- The NULL or empty strings should not produce any record
;
我更喜欢使用CROSS APPLY
and VALUES
instead of UNPIVOT
。我们想删除NULL
和空值,所以我认为最好明确地编写过滤器,这样每个阅读代码的人都会看到它。使用UNPIVOT
,您需要知道它会隐式删除NULL
。您仍然需要添加过滤器以删除空值。在这种情况下,性能可能大致相同,但您最好检查实际数据。
如果有可能同时调用此数据泵过程多次,则必须采取额外步骤以防止可能的并发问题(不正确的结果或死锁)。我更喜欢使用sp_getapplock
来保证只有一个存储过程实例可以随时运行。
答案 2 :(得分:1)
稍微不同的方法是在target_table
上定义更新/插入触发器。使用此方法,您的数据泵只需要考虑您的初始目标表。触发器将转换并将合并的数据插入alternative_table
表。
如果您愿意承担将原始oz列名称存储为备用表中的搜索键所需的额外数据存储成本,则可以使用另一个合并语句,该语句将提高触发器的整体性能,如下所示: / p>
-- Create example table
CREATE TABLE [dbo].[alternative_table](
[ID] [int] NOT NULL,
[ColumnKey] [nvarchar](5) NOT NULL,
[oz] [nvarchar](100) NULL,
CONSTRAINT [PK_alternative_table] PRIMARY KEY CLUSTERED
(
[ID] ASC,
[ColumnKey] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
-- Create trigger responsible for populating Alternative table.
CREATE TRIGGER dbo.MergeWithAlternative
ON dbo.target_table
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
MERGE [alternative_table] AS [target]
USING (
SELECT
[ID]
, [ColumnKey]
, [Oz]
FROM inserted
UNPIVOT
(
[Oz] FOR [ColumnKey] IN (oz1, oz2, oz3, oz4)
) unpvt
) AS [source]
ON [target].ID = [source].ID AND [target].[ColumnKey] = [source].[ColumnKey]
WHEN MATCHED THEN
UPDATE
SET [target].oz = [source].[Oz]
WHEN NOT MATCHED THEN
INSERT
(
ID
,[ColumnKey]
,[oz]
)
VALUES
(
source.ID
,source.[ColumnKey]
,source.[Oz]
);
END
如果您不愿意将列名存储为键查找,那么简单的删除/插入操作也将起作用:
-- Create example table
CREATE TABLE [dbo].[alternative_table](
[ID] [int] NOT NULL,
[oz] [nvarchar](100) NULL
) ON [PRIMARY]
GO
-- Create trigger responsible for populating Alternative table.
CREATE TRIGGER dbo.MergeWithAlternative
ON dbo.target_table
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
DELETE [dbo].[alternative_table]
WHERE [ID] IN (SELECT ID FROM deleted)
INSERT INTO [dbo].[alternative_table]
(
[ID]
,[oz]
)
SELECT [ID]
,[Oz]
FROM inserted
UNPIVOT
(
[Oz] FOR [ColumnKey] IN (oz1, oz2, oz3, oz4)
) unpvt
END
使用此方法不再需要表变量和填充它的输出子句。
答案 3 :(得分:0)
由于我们需要通过替换旧记录并添加新记录来构建oz_table,我更喜欢先截断它,然后通过INSERTING所有记录从头开始重建它。 我将使用两个cte,第一个用于读取@info_table,而第二个用于构建UNION所有四个盎司列的所有行。然后只需将UNION插入oz_Table。
Truncate table dbo.oz_table
with cte as(
Select Code, oz1, oz2, oz3, oz4 from @info_table
), cte2 as(
Select Code, oz1 as oz From cte
UNION ALL Select Code, oz2 as oz From cte
UNION ALL Select Code, oz3 as oz From cte
UNION ALL Select Code, oz4 as oz From cte
)
Insert into dbo.oz_table(Code, oz)
select Code, oz from cte2
Where oz is not null and oz<>''