【问题标题】:SQL Server stored procedure: UPDATE with lowest value, else INSERT if there's already a valueSQL Server 存储过程:使用最低值更新,如果已经有值则插入
【发布时间】:2017-08-17 22:17:15
【问题描述】:

希望一次迁移 20K 行左右。离开 SQL 超过 10 年,一直在苦苦挣扎。

我可能完全错了。需要用Table2.Value1 中的最小值更新Table1.Value2,除非已经有Value2。如果是后者,我需要插入一行,其值为Table1.Value1

Table1.Value1 是行的每个 ID 的最小值。 Value2 必须是下一个最低值。

当前表1:

ID1, 123, [empty]
ID4, 111, [empty]

当前表2:

ID1, 224
ID1, 331
ID4, 210
ID4, 551

Table1 - 所需状态:

ID1, 123, 224
ID1, 331, [empty]
ID4, 111, 210
ID4, 551, [empty]

Table2 - 所需状态:

[empty]

这是我尝试过的,更新部分可以正常工作。插入永远不起作用。我想我把自己编码到了一个角落。

CREATE PROCEDURE dbo.Broken
AS
    --DECLARE VARIABLES
    DECLARE @ID INT,
            @Value1 INT,
            @Value2 INT,
            @tmpValue INT

    --DECLARE COUNTER
    DECLARE @Counter INT
    SET @Counter = 1

    --DECLARE CURSOR FOR QUERY
    DECLARE cTable1 CURSOR READ_ONLY FOR
        SELECT ID,Value1, Value2
        FROM Table1 

    OPEN cTable1

    --FETCH VARIABLES
    FETCH NEXT FROM cTable1 INTO @ID, @Value1, @Value2

    --LOOP 
    WHILE @@FETCH_STATUS = 0
    BEGIN
        IF (@Value2 = '' OR @Value2 is NULL)
        BEGIN
            --UPDATE Table1.Value2 with next lowest Table1.Value2 > Table1.Value1
            SET @tmpValue = (SELECT MIN(Value1) FROM Table2  WHERE Value1 > @Value1 AND ID = @ID)

            UPDATE Table1 
            SET Value2 = @tmpValue     
            WHERE ID = @ID

            --DELETE AFFECTED ROW FROM Table2
            DELETE FROM Table2 
            WHERE Value1 = @tmpValue AND ID = @ID;
        END
        ELSE
        BEGIN
            --INSERT ROW IN Table1 WITH ID, Value1 FROM Table2 
            SET @ID = (SELECT MIN(ID) FROM Table2)
            SET @tmpValue = (SELECT MIN(Value1) FROM Table2 WHERE ID = @ID)

            INSERT INTO Table1(ID, Value1, Value2) 
            VALUES (@ID, @tmpValue, '')

            DELETE FROM Table2 
            WHERE ID = @ID
        END

        --FETCH NEXT VARIABLES
        FETCH NEXT FROM cTable1 INTO @ID, @Value1, @Value2
    END

    --CLOSE THE CURSOR TABLE
    CLOSE cTable1
    DEALLOCATE cTable1

大约有 20000 行,所以我希望存储过程能够完成所有工作。这是一次性的,所以我不太担心开销。

【问题讨论】:

  • 如果Table2 中有另一行与ID1, 111 怎么办?那么ID1, 444呢?在这些情况下,最终结果应该是什么?无论如何,思考集,而不是游标——MIN(Value) GROUP BY IDUNIONROW_NUMBER()CROSS APPLY 在这里应该派上用场,以便在一个表中构建结果。然后,您可以在事务中使用INSERT INTOMERGE 或单独的INSERT/UPDATE 语句使Table1 看起来像那个结果(然后Table2 可以简单地删除其所有行)。如果您已经 10 年没有使用 SQL,那么您还记得游标的存在是一种耻辱。 :-P
  • 存储过程是实现您的目标所必需的吗?这可以通过使用 CASE 语句和 INSERT INTO 将表抓取到临时表来轻松完成
  • @JeroenMostert,我需要审查 MERGE,但我认为这是实现它的另一种方式。
  • @Isaiah3015,不,我绝对不喜欢使用存储过程。最初我尝试在没有的情况下构建它,但不能。

标签: sql sql-server sql-server-2008 procedure


【解决方案1】:

这样的更新所需的基本查询非常清楚:

UPDATE Table1
SET VALUE2 = (
    SELECT MIN(t2.Value1) 
    FROM Table2 t2
    WHERE t2.Value1 > Table1.Value1 
      AND t2.ID = Table1.ID
    )

然后我会像这样执行插入:

INSERT INTO Table1 (ID, Value1, Value2)
SELECT ID,
    Value1,
    [Empty]
From Table2 t2
LEFT JOIN Table1 t1
    ON t2.ID = t1.ID
    AND t2.Value1 = t1.Value1
LEFT JOIN Table1 t1a
    ON t2.ID = t1a.Id
    AND t2.Value1 = t1a.Value2
WHERE t1.Id IS NULL
    AND t1a.Id IS NULL

DELETE FROM Table2

请注意,这符合您声明的要求,但可能不符合您的实际要求 - @JeroenMostert 的评论涵盖了其中的一些问题。

【讨论】:

  • 我的更新部分工作正常。对困惑感到抱歉。我已经修正了措辞。我认为您建议的 UPDATE 语句不起作用,因为它不检查 T1.Value2 中是否存在值,除非我弄错了。很容易插入该检查,但这是否比我当前的 UPDATE 语句更好的解决方案?我非常缺乏实践,所以我必须刷新你建议的 LEFT JOIN。
【解决方案2】:

以下查询似乎产生了您想要的输出:

declare @Table1 table (
    Id varchar(30) not null,
    Value1 int null,
    Value2 int null
);

declare @Table2 table (
    Id varchar(30) not null,
    Value int null
);

insert into @Table1 (Id, Value1)
values
    ('ID1', 123),
    ('ID4', 111);

insert into @Table2 (Id, Value)
values
    ('ID1', 224),
    ('ID1', 331),
    ('ID4', 210),
    ('ID4', 551);

select t2.Id,
    case
        when t2.Value = min(t2.Value) over(partition by t1.Id) then t1.Value1
        else t2.Value
    end as [V1],
    case
        when t2.Value = min(t2.Value) over(partition by t1.Id) then t2.Value
    end as [V2]
from @Table1 t1
    left join @Table2 t2 on t1.Id = t2.Id
where t1.Value1 < t2.Value;

考虑到这一点,一个简单的MERGE 就可以解决问题:

merge @Table1 t
using (
    select t2.Id,
    case
        when t2.Value = min(t2.Value) over(partition by t1.Id) then t1.Value1
        else t2.Value
    end as [V1],
    case
        when t2.Value = min(t2.Value) over(partition by t1.Id) then t2.Value
    end as [V2]
from @Table1 t1
    left join @Table2 t2 on t1.Id = t2.Id
where t1.Value1 < t2.Value
) s on s.Id = t.Id and s.V1 = t.Value1
when not matched by target then
    insert (Id, Value1, Value2)
    values (s.Id, s.V1, s.V2)
when matched then
    update set Value2 = s.V2;

我可能对某些极端情况有误,但原则上这应该可行。

【讨论】:

    【解决方案3】:

    试试这个

    ;With updateData as (
    Select Id , Value1 , row_number() over ( partition by id order by value1 ) rowNumber from table2
    )
    Update table1 set value2 = t.value1 from updateData t where t.Id = table1.Id and t.rowNumber=1 
    
    ;With insertData as(
    Select Id , Value1 , row_number() over ( partition by id order by value1 ) rowNumber from table2
    )
    Insert into table1(id,value1)
    Select t.id , t.value1 from insertData t
    Where  t.rowNumber>1
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-03-02
      • 1970-01-01
      • 2010-09-06
      • 2013-11-07
      • 1970-01-01
      • 1970-01-01
      • 2017-03-01
      相关资源
      最近更新 更多