【问题标题】:Conditionally insert a row if it does not exist already如果行不存在,则有条件地插入一行
【发布时间】:2019-11-25 20:33:45
【问题描述】:

[注意:我找到了一些答案,例如991165916636698,但它们的语法不够清楚。]

我想在表中插入一行,但前提是该行不存在。插入行的列值来自变量(过程参数)而不是另一个表,所以我不会使用merge

我不想使用单独的 if exists 后跟 insert,而是希望在单个 (insert) 语句中完成此操作。

我有@bookID@userID@reviewDate@reviewYear 作为我的proc 的参数,我想将它们插入到表my_table 的新行中。

所以我有这个:

insert into my_table
    (bookID, reviewYear, userID, reviewDate)
select
    @bookID, @reviewYear, @userID, @reviewDate   -- Proc arguments, values for new row
from my_table
where not exists (
    select bookID                                -- Find existing matching row
    from my_table
    where bookID = @bookID
        and reviewYear = @reviewYear
)

换句话说,insert 仅在不存在具有相同bookIDreviewYear 的现有行时才会添加新行。因此,给定用户可以为给定年份的给定图书添加评论,但前提是没有用户这样做。

我的理解是否正确,或者是否有更简单的语法来完成相同的事情(在单个语句中)?


附录(2020 年 1 月 10 日)

正如所指出的,select 将选择多行,整个insert 语句最终将插入许多行,可能与my_table 中的当前行一样多。

第二次尝试,将distinct 子句添加到select

insert into my_table
    (bookID, reviewYear, userID, reviewDate)
select distinct                                  -- Only one possible match
    @bookID, @reviewYear, @userID, @reviewDate   -- Proc arguments, values for new row
from my_table
where not exists (
    select bookID                                -- Find existing matching row
    from my_table
    where bookID = @bookID
        and reviewYear = @reviewYear
)

【问题讨论】:

  • 那是一条语句?是的,这是正确的。您当然可以添加唯一约束以强制不添加重复项。但仍然会像你一样写你的插入。
  • 我不明白你的目标。是什么让一种语法比另一种更“简单”,你为什么想要它?少打字?你已经完成了打字,那么你在追求什么,为什么?
  • @TabAlleman - “更简单”是指“不那么复杂”和“更少的计算开销”。对于这个特定问题,我想问是否有更简单的方法来做同样的事情,但使用更简单的子查询或根本没有子查询。

标签: sql sql-server


【解决方案1】:

我建议改为捕获错误:

create unique index unq_my_table on my_table(bookID, reviewYear)

begin try
    insert into my_table (bookID, reviewYear, userID, reviewDate)
        values ( @bookID, @reviewYear, @userID, @reviewDate )  -- Proc arguments, values for new row

end try
begin catch
-- do something here if you want
end catch;

您的代码不起作用,因为您是从表格中选择的。您将获得与表中一样多的插入 - 并且您可能会插入重复项。

为防止重复,让数据库确保唯一性。这是他们可以保证的事情之一。唯一的索引/约束可以做到这一点。

【讨论】:

    【解决方案2】:

    已编辑:

    根据上面更清晰的描述(可能还有我的咖啡),我应该注意,您可以只使用变量来使用 MERGE。使用上面的参数,这是一种方法:

    WITH Src as (
        SELECT 
            @bookID AS bookID, 
            @reviewYear AS reviewYear, 
            @userID AS userID, 
            @reviewDate AS reviewDate
        )
    MERGE my_table AS TARGET
    USING Src AS SOURCE
    ON TARGET.bookID = SOURCE.bookID
        AND TARGET.reviewYear = SOURCE.reviewYear
    WHEN NOT MATCHED [BY TARGET]
        THEN INSERT (bookID, reviewYear, userID, reviewDate)
        VALUES (SOURCE.bookID, SOURCE.reviewYear, SOURCE.userID, SOURCE.reviewDate)
    

    原答案:

    实际上,我按照发布的方式运行了这段代码,但它没有正确地将数据输入到表格中。您的基本问题是您的 SELECT ... FROM my_table。这将尝试在表中插入与表包含的行数一样多的行。因此,如果表为空,则不会插入任何行,但如果表有 20 行,则会插入另外 20 行。

    这是一个正确的方法来做到这一点。它使用您的基本逻辑,但从 INSERT 语句中取出条件检查。

    CREATE TABLE #my_table (BookID int, ReviewYear int, UserId int, ReviewDate date)
    
    DECLARE @BookID int = 1,
        @ReviewYear int = 1999,
        @UserId Int = 111,
        @ReviewDate date = '2019-09-11'
    
    IF NOT EXISTS (
        select \*                                -- Find existing matching row
        from #my_table
        where bookID = @bookID
            and reviewYear = @reviewYear
        )
    insert into #my_table 
        (bookID, reviewYear, userID, reviewDate)
    VALUES 
        (@bookID, @reviewYear, @userID, @reviewDate)   -- Proc arguments, values for new row
    
    SELECT \*
    FROM #my_table
    

    【讨论】:

    • 这仍然执行单独的selectinsert 语句,这是我试图避免的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多