2009-03-12 3 views
189

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

Возможный дубликат [Solu для INSERT или UPDATE на SQL Server] (http://stackoverflow.com/questions/108403/solutions-for-insert-or-update-on-sql-server) –

+0

связанный вопрос - https://stackoverflow.com/ вопросы/21889843/unique-constraint-vs-check-before-insert – Steam

ответ

129

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

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

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

+0

Да. В полете есть 1 ряд. Но ваш код делает SELECT, но не проверяет, заполнен ли полёт до UPDATE. Как это сделать? – 2009-03-12 18:31:44

+49

-1 Это не потокобезопасность. –

+0

Из-за условий гонки это верно только в том случае, если текущий уровень изоляции транзакций является Serializable. –

2

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

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 
+5

-1 Это не потокобезопасность. –

+0

@TheTXI - Не могли бы вы сделать эту тему безопасной? – Steam

1

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

declare @rowCount int 

select @[email protected]@RowCount 

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

код с ошибкой! roWcount –

+0

MERGE работает только на SQL Server 2008 или выше. – Steam

136

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

Кроме того, посмотрите, почему вам может понравиться [WITH (HOLDLOCK)] (http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx) для этого MERGE , –

+3

Я думаю, что MERGE поддерживается после 2005 года (так 2008+). – samosaris

+2

MERGE без WITH (UPDLOCK) может иметь нарушения первичного ключа, что в этом случае было бы плохо. См. [Является ли MERGE атомарным утверждением в SQL2008?] (Http://stackoverflow.com/questions/9871644/is-merge-an-atomic-statement-in-sql2008) – James

58

Pass UPDLOCK, уключина, HOLDLOCK подсказки при проверке на наличие ряда.

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 намек заставляет запрос взять блокировку обновления на строку, если она уже существует, предотвращая другие транзакциям изменять его, пока вы не зафиксируете или откат.

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

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

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

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

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

+4

Блокировка ОЧЕНЬ важна, чтобы избежать переупорядочения. Правильно ли предположить, что блокировка, объявленная в операторе IF, сохраняется до конца оператора IF, то есть для одного оператора обновления? Тогда было бы разумно показать вышеприведенный код, используя маркеры начального блока, чтобы предотвратить появление новых символов в копировании и вставке вашего кода и все еще ошибочно. –

+0

Есть ли проблема, если мой PK является varchar (хотя и не максимальным) или комбинацией из трех столбцов VARCHAR? – Steam

+0

Я задал вопрос, связанный с этим ответом, по адресу: https://stackoverflow.com/questions/21945850/a-thread-safe-way-to-check-if-a-row-exists-before-inserting-is-my -code-correct Вопрос: может ли этот код использоваться для вставки миллионов строк. – Steam

33

Я пишу свое решение. мой метод не выдерживает «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 ИЗ TableName ГДЕ col1 = @ par1 и col2 = @ par2 Он выбирает из TableName искали значения

(2) SELECT @ par1, @ par2 НЕ СУЩЕСТВУЕТ Требуется, если не существует (1) s ubquery

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

+0

Ваш код никогда не обновляется и не существует. – Ethan

+0

только для вставки, а не для обновления. – Cem

0

Полное решение будет ниже (в том числе структуры курсора). Большое спасибо 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 
0

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

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]

+0

Это ссылка только для копирования-вставки ... лучше подходит как комментарий. – Ian

-3
INSERT INTO table (column1, column2, column3) 
SELECT $column1, $column2, $column3 
EXCEPT SELECT column1, column2, column3 
FROM table 
+0

INSERT INTO table (column1, column2, column3) SELECT $ column1, $ column2, $ column3 ЗА ИСКЛЮЧЕНИЕМ столбца SELECT1, столбец2, столбец3 из таблицы – Aaron

+1

На этот вопрос очень много ответов. Не могли бы вы объяснить, что этот ответ добавляет к существующим ответам? – francis

0
INSERT INTO Database.dbo.Table SELECT * FROM Database.dbo.Table 
WHERE ID not in (select ID from Database.dbo.Table) 
Смежные вопросы