Проверьте, существует ли строка, иначе вставьте

Мне нужно написать хранимую процедуру 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?)

Ответ 1

Посмотрите команда 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

Ответ 2

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

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

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

Ответ 3

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

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 */

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

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

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

Подробнее см. http://msdn.microsoft.com/en-us/library/ms187373.aspx.

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

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

Ответ 4

Я пишу свое решение. мой метод не выдерживает "if" или "merge". мой метод прост.

INSERT INTO TableName (col1,col2)
SELECT @par1, @par2
   WHERE NOT EXISTS (SELECT col1,col2 FROM TableName
                     WHERE [email protected] AND [email protected])

Пример:

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

Объяснение:

(1) SELECT col1, col2 FROM TableName WHERE col1 = @par1 И col2 = @par2 Он выбирает из найденных значений TableName

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

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

Ответ 5

Это то, что я только недавно сделал:

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

Ответ 6

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

declare @rowCount int

select @[email protected]@RowCount

if @rowCount=0
begin
--insert....

Ответ 7

Полное решение ниже (включая структуру курсора). Большое спасибо Cassius Porcus за код begin trans ... commit от публикации выше.

declare @mystat6 bigint
declare @mystat6p varchar(50)
declare @mystat6b bigint

DECLARE mycur1 CURSOR for

 select result1,picture,bittot from  all_Tempnogos2results11

 OPEN mycur1

 FETCH NEXT FROM mycur1 INTO @mystat6, @mystat6p , @mystat6b

 WHILE @@Fetch_Status = 0
 BEGIN

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

 if not exists (select * from all_Tempnogos2results11_uniq with (updlock, rowlock, holdlock)
                     where all_Tempnogos2results11_uniq.result1 = @mystat6 
                        and all_Tempnogos2results11_uniq.bittot = @mystat6b )
     insert all_Tempnogos2results11_uniq values (@mystat6 , @mystat6p , @mystat6b)

 --else
 --  /* update */

 commit /* locks are released here */

 FETCH NEXT FROM mycur1 INTO @mystat6 , @mystat6p , @mystat6b

 END

 CLOSE mycur1

 DEALLOCATE mycur1
 go

Ответ 8

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

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/[email protected]

Ответ 9

INSERT INTO Database.dbo.Table SELECT * FROM Database.dbo.Table
 WHERE ID not in (select ID from Database.dbo.Table)

Ответ 10

INSERT INTO table ( column1, column2, column3 )
SELECT $column1, $column2, $column3
EXCEPT SELECT column1, column2, column3
FROM table