如果存在,则更新行的存储过程

时间:2016-11-16 17:00:27

标签: sql sql-server tsql stored-procedures

我正在运行一个存储过程,如果它们不存在则插入值,如果存在则更新。我有一张数据来自

的表格
Table1
Name (nvarchar)
Data (int)
Timestamp (datetime)

数据看起来像这样

Name1       5       2016-11-16 09:46:40.490
Name2       10      2016-11-16 09:48:35.240
Name1       7       2016-11-16 09:35:24.350
Name2       8       2016-11-15 02:27:44.670

我正在尝试将名称插入到新表中,平均整数,并按天分组。这是第二个表

Table2
Name (nvarchar)
Data (int)
Timestamp (date)

数据看起来像这样。 Name1当天是平均的。 Name2是不同的日子,所以他们没有平均。

Name1      6       2016-11-16 00:00:00.000
Name2      10      2016-11-16 00:00:00.000
Name2      8       2016-11-15 00:00:00.000

我将时间戳转换为日期,以便在一天中更容易合并。我的存储过程看起来像这样

IF NOT EXISTS (SELECT t.Name, t.Timestamp
    FROM Table2 t
    JOIN Table1 a
    ON t.Name = a.Name AND t.Timestamp = CONVERT(date, a.Timestamp)
    GROUP BY t.Name, t.Timestamp)

INSERT INTO Table2 (Name, Timestamp, Data)
SELECT 
    Name,
    CAST(Timestamp AS DATE) as Date,
    AVG(Data) as Average_Data
FROM Table1
GROUP BY CAST(Timestamp AS DATE), Name

ELSE
UPDATE Table1
SET
WHERE

所以我第一次运行,没有问题。这些值被正确添加和分组。但是,第二次运行时,它总是插入。

在删除之前,我的更新语句看起来像是这样的

SET Name = Name, Timestamp = Timestamp, Data = Date
WHERE Name = Name, Timestamp = Timestamp

我知道这是不对的,但它似乎永远不会点击更新,因为插入始终运行。如果我在If If Exists中运行Select,我会看到数据,它应该将其视为存在。

我想我需要帮助清理If Not Exists并创建一个有效的Update语句。

编辑:

更新了以下代码

IF EXISTS(SELECT Name, Timestamp FROM Table2)
UPDATE 
    Table2
SET 
    Name = a.Name,
    Timestamp = CONVERT(date, a.Timestamp),
    Data = AVG(a.Data)
FROM
    Table2 t
INNER JOIN
    Table1 a
ON t.Name = a.Name
WHERE t.Name = a.Name AND t.Timestamp = CONVERT(date, a.Timestamp)

ELSE
    INSERT INTO Table2 (Name, Timestamp, Data)
        SELECT 
            Name,
            CAST(Timestamp AS DATE) as Date,
            AVG(Data) as Average_Data
        FROM Table1
        GROUP BY CAST(Timestamp AS DATE), Name
END

2 个答案:

答案 0 :(得分:2)

我喜欢使用MERGE语句而不是连续的UPDATE和INSERT。 由于聚合,在下面的答案中,我使用带有MERGE的CTE。

-- CREATE AND INSERT TABLES 
DROP TABLE TABLE1
CREATE TABLE 
TABLE1
   (    Name nvarchar(5)
    ,   Data int
    ,   Timestamp date
    )
INSERT INTO TABLE1  VALUES  ('Name1',5  ,'2016-11-16')
                    ,       ('Name2',10 ,'2016-11-16')
                    ,       ('Name1',7  ,'2016-11-16')
                    ,       ('Name2',8  ,'2016-11-15')
                    ,       ('Name3',8  ,'2016-11-15')
                    ,       ('Name3',10 ,'2016-11-15')
                    ,       ('Name3',9  ,'2016-11-16')
                    ,       ('Name3',11 ,'2016-11-16')


DROP TABLE TABLE2
CREATE TABLE 
TABLE2
   (    Name nvarchar(5)
    ,   Data int
    ,   Timestamp datetime
    )
INSERT INTO TABLE2 VALUES  ('Name1',0 ,'2016-11-16')
                          ,('Name2',0 ,'2016-11-16')
                          ,('Name2',0 ,'2016-11-15')

    SELECT * FROM TABLE2 -- SHOW TABLE2 BEFORE MERGE


BEGIN TRANSACTION
--  HERE IS WHERE THE CODE THAT REPLACES YOUR QUERY ACTUALLY BEGINS
   ;WITH    CTE_AVG_DATA_TABLE1
        AS (SELECT      Name 
            ,           Timestamp
            ,           AVG(Data) AS [AVG(Data)]
            FROM        TABLE1
            GROUP BY    Name 
            ,           Timestamp
            )

        MERGE   Table2              AS TARGET
        USING   CTE_AVG_DATA_TABLE1 AS SOURCE
            ON      TARGET.Name = SOURCE.Name
                AND TARGET.TIMESTAMP = SOURCE.TIMESTAMP
        WHEN MATCHED THEN 
            UPDATE      
            SET     TARGET.Name         = SOURCE.Name
            ,       TARGET.Timestamp    = CONVERT(date, SOURCE.Timestamp)
            ,       TARGET.Data         = SOURCE.[AVG(Data)]
        WHEN NOT MATCHED THEN 
            INSERT (Name, Timestamp, Data)
            VALUES (SOURCE.NAME, SOURCE.TIMESTAMP, SOURCE.[AVG(Data)])
        ;

    SELECT * FROM TABLE2 -- SHOW TABLE2 AFTER MERGE
ROLLBACK /*ALLOWS THIS TEST CODE TO BE RUN OVER AND OVER 
           ADDING ROWS TO INSERT STATEMENTS ABOVE TO SHOW THAT IT WORKS
         */

答案 1 :(得分:1)

我个人更喜欢将旧式技术用于通常所说的“upsert”。 MERGE运行良好,但调试问题真的很痛苦,因为它一次性完成所有工作。

这是我更喜欢的方法类型,因为它将插入和更新分开,这提供了更大的灵活性并且更容易调试问题。您还可以在插入中切换左连接以使用带有相关子查询的NOT EXISTS,但大多数情况下性能差异可以忽略不计。

UPDATE t
SET 
    Name = a.Name,
    Timestamp = CONVERT(date, a.Timestamp),
    Data = AVG(a.Data)
FROM
    Table2 t
INNER JOIN
    Table1 a
ON t.Name = a.Name
WHERE t.Name = a.Name AND t.Timestamp = CONVERT(date, a.Timestamp)


INSERT INTO Table2 (Name, Timestamp, Data)
SELECT 
    Name,
    CAST(Timestamp AS DATE) as Date,
    AVG(Data) as Average_Data
FROM Table1 a
left join Table2 t on a.Name = t.Name
where t.Name is null
GROUP BY Name, CAST(Timestamp AS DATE)
    , Name

- 编辑 -

我发布时,我甚至没有注意到您的更新中的汇总。你可以通过cte轻松绕过这个。

with cte as
(
    select Name = a.Name
        , Timestamp = CONVERT(date, a.Timestamp)
        , AverageData = AVG(a.Data)
    FROM
        Table2 t
    INNER JOIN
        Table1 a
    ON t.Name = a.Name
    WHERE t.Name = a.Name AND t.Timestamp = CONVERT(date, a.Timestamp)
    GROUP BY Name, CAST(Timestamp AS DATE)
)

update t
set Name = c.Name
    , Timestamp = c.Timestamp
    , Date = c.AverageData
from Table2 t
join cte c on c.Name = t.Name
    and c.Timestamp = t.Timestamp