Проверьте, существует ли строка, в противном случае вставьте - PullRequest
220 голосов
/ 12 марта 2009

Мне нужно написать хранимую процедуру T-SQL, которая обновляет строку в таблице. Если строка не существует, вставьте ее. Все эти шаги обернуты транзакцией.

Это для системы бронирования, поэтому она должна быть атомарной и надежной . Должно возвращаться значение true, если транзакция была совершена и рейс забронирован.

Я новичок в T-SQL , и не уверен, как использовать @@rowcount. Это то, что я написал до сих пор. Я на правильном пути? Я уверен, что это простая проблема для вас.

-- BEGIN TRANSACTION (HOW TO DO?)

UPDATE Bookings
 SET TicketsBooked = TicketsBooked + @TicketsToBook
 WHERE FlightId = @Id AND TicketsMax < (TicketsBooked + @TicketsToBook)

-- Here I need to insert only if the row doesn't exists.
-- If the row exists but the condition TicketsMax is violated, I must not insert 
-- the row and return FALSE

IF @@ROWCOUNT = 0 
BEGIN

 INSERT INTO Bookings ... (omitted)

END

-- END TRANSACTION (HOW TO DO?)

-- Return TRUE (How to do?)

Ответы [ 11 ]

153 голосов
/ 13 марта 2009

Посмотрите на команду MERGE . Вы можете сделать UPDATE, INSERT & DELETE в одном выражении.

Вот рабочая реализация по использованию MERGE
- Перед обновлением проверяется, заполнен ли рейс, в противном случае выполняется вставка.

if exists(select 1 from INFORMATION_SCHEMA.TABLES T 
              where T.TABLE_NAME = 'Bookings') 
begin
    drop table Bookings
end
GO

create table Bookings(
  FlightID    int identity(1, 1) primary key,
  TicketsMax    int not null,
  TicketsBooked int not null
)
GO

insert  Bookings(TicketsMax, TicketsBooked) select 1, 0
insert  Bookings(TicketsMax, TicketsBooked) select 2, 2
insert  Bookings(TicketsMax, TicketsBooked) select 3, 1
GO

select * from Bookings

А потом ...

declare @FlightID int = 1
declare @TicketsToBook int = 2

--; This should add a new record
merge Bookings as T
using (select @FlightID as FlightID, @TicketsToBook as TicketsToBook) as S
    on  T.FlightID = S.FlightID
      and T.TicketsMax > (T.TicketsBooked + S.TicketsToBook)
  when matched then
    update set T.TicketsBooked = T.TicketsBooked + S.TicketsToBook
  when not matched then
    insert (TicketsMax, TicketsBooked) 
    values(S.TicketsToBook, S.TicketsToBook);

select * from Bookings
146 голосов
/ 12 марта 2009

Я предполагаю одну строку для каждого полета? Если так:

IF EXISTS (SELECT * FROM Bookings WHERE FLightID = @Id)
BEGIN
    --UPDATE HERE
END
ELSE
BEGIN
   -- INSERT HERE
END

Полагаю, что я сказал, поскольку ваш способ действий может перебить билет на рейс, так как он вставит новую строку, когда максимум 10 билетов и вы бронируете 20.

63 голосов
/ 15 апреля 2010

Пропускать подсказки при обновлении, блокировке строки и блокировке при проверке наличия строки.

begin tran /* default read committed isolation level is fine */

if not exists (select * from Table with (updlock, rowlock, holdlock) where ...)
    /* insert */
else
    /* update */

commit /* locks are released here */

Подсказка по обновлению заставляет запрос принять блокировку обновления для строки, если она уже существует, не позволяя другим транзакциям изменять ее, пока вы не подтвердите или не откатитесь.

Подсказка о блокировке заставляет запрос принять блокировку диапазона, не позволяя другим транзакциям добавлять строку, соответствующую критериям вашего фильтра, до тех пор, пока вы не подтвердите или не откатитесь.

Подсказка о блокировке строк вынуждает детализацию блокировки на уровень строк вместо уровня страницы по умолчанию, поэтому ваша транзакция не будет блокировать другие транзакции, пытающиеся обновить несвязанные строки на той же странице (но помните о компромиссе между уменьшением конкуренции и увеличение накладных расходов на блокировку - вам следует избегать принятия большого количества блокировок на уровне строк в одной транзакции).

См. http://msdn.microsoft.com/en-us/library/ms187373.aspx для получения дополнительной информации.

Обратите внимание, что блокировки принимаются как выполняющие их операторы - вызов метода begin trans не дает вам иммунитета против другой транзакции, которая блокирует блокировки до того, как вы к ней попадете. Вы должны попытаться использовать свой SQL-код для удержания блокировок в кратчайшие сроки, зафиксировав транзакцию как можно скорее (запоздайте поздно, отпустите раньше).

Обратите внимание, что блокировки на уровне строк могут быть менее эффективными, если ваш PK является bigint, поскольку внутреннее хеширование в SQL Server вырождено для 64-битных значений (разные значения ключа могут хешироваться для одного и того же идентификатора блокировки).

36 голосов
/ 25 февраля 2013

я пишу свое решение. мой метод не стоит «если» или «слить». Мой метод прост.

INSERT INTO TableName (col1,col2)
SELECT @par1, @par2
   WHERE NOT EXISTS (SELECT col1,col2 FROM TableName
                     WHERE col1=@par1 AND col2=@par2)

Например:

INSERT INTO Members (username)
SELECT 'Cem'
   WHERE NOT EXISTS (SELECT username FROM Members
                     WHERE username='Cem')

Пояснение:

(1) ВЫБРАТЬ col1, col2 ОТ ИМЕНИ TableN ГДЕ col1 = @ par1 И col2 = @ par2 Выбирает из TableName искомые значения

(2) SELECT @ par1, @ par2, ГДЕ НЕ СУЩЕСТВУЕТ Требуется, если не существует из (1) подзапроса

(3) Вставляет в пошаговые значения TableName (2)

2 голосов
/ 06 января 2015

Я наконец смог вставить строку при условии, что она еще не существует, используя следующую модель:

INSERT INTO table ( column1, column2, column3 )
(
    SELECT $column1, $column2, $column3
      WHERE NOT EXISTS (
        SELECT 1
          FROM table 
          WHERE column1 = $column1
          AND column2 = $column2
          AND column3 = $column3 
    )
)

который я нашел по адресу:

http://www.postgresql.org/message-id/87hdow4ld1.fsf@stark.xeocode.com

2 голосов
/ 12 марта 2009

Это то, что мне недавно пришлось сделать:

set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[cjso_UpdateCustomerLogin]
    (
      @CustomerID AS INT,
      @UserName AS VARCHAR(25),
      @Password AS BINARY(16)
    )
AS 
    BEGIN
        IF ISNULL((SELECT CustomerID FROM tblOnline_CustomerAccount WHERE CustomerID = @CustomerID), 0) = 0
        BEGIN
            INSERT INTO [tblOnline_CustomerAccount] (
                [CustomerID],
                [UserName],
                [Password],
                [LastLogin]
            ) VALUES ( 
                /* CustomerID - int */ @CustomerID,
                /* UserName - varchar(25) */ @UserName,
                /* Password - binary(16) */ @Password,
                /* LastLogin - datetime */ NULL ) 
        END
        ELSE
        BEGIN
            UPDATE  [tblOnline_CustomerAccount]
            SET     UserName = @UserName,
                    Password = @Password
            WHERE   CustomerID = @CustomerID    
        END

    END
1 голос
/ 12 марта 2009

Вы можете использовать функциональность Слияние для достижения. В противном случае вы можете сделать:

declare @rowCount int

select @rowCount=@@RowCount

if @rowCount=0
begin
--insert....
0 голосов
/ 12 июля 2018

Лучший подход к этой проблеме - сначала сделать столбец базы данных УНИКАЛЬНЫМ

ALTER TABLE table_name ADD UNIQUE KEY

THEN INSERT IGNORE INTO table_name, значение не будет вставлено, если оно приведет к дублированию ключа / уже существует в таблице.

0 голосов
/ 03 апреля 2017
INSERT INTO [DatabaseName1].dbo.[TableName1] SELECT * FROM [DatabaseName2].dbo.[TableName2]
 WHERE [YourPK] not in (select [YourPK] from [DatabaseName1].dbo.[TableName1])
0 голосов
/ 28 марта 2015
INSERT INTO table ( column1, column2, column3 )
SELECT $column1, $column2, $column3
EXCEPT SELECT column1, column2, column3
FROM table
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...