2009-03-12 23 views
189

Potrzebuję napisać procedurę składowaną T-SQL, która aktualizuje wiersz w tabeli. Jeśli wiersz nie istnieje, włóż go. Wszystkie te kroki są zawijane przez transakcję.Sprawdź, czy istnieje wiersz, w przeciwnym razie wstaw

To jest dla systemu rezerwacji, więc musi być atomowy i niezawodny. Musi zwrócić wartość true, jeśli transakcja została zatwierdzona, a lot zarezerwowany.

Jestem nowy w T-SQL i nie wiem, jak korzystać z @@rowcount. To jest to, co napisałem do tej pory. Czy jestem na właściwej drodze? Jestem pewien, że to dla ciebie łatwy problem.

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

możliwy duplikat [Solu dla INSERT OR UPDATE na serwerze SQL Server (http://stackoverflow.com/questions/108403/solutions-for-insert-or-update-on-sql-server) –

+0

powiązane pytanie - https://stackoverflow.com/ pytania/21889843/unique-constraint-vs-checking-before-insert – Steam

Odpowiedz

129

Zakładam jeden rząd na każdy lot? Jeśli tak, to:

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

Zakładam, co powiedziałem, jako sposób robienia rzeczy może zatłoczonego lot, jak będzie wstawić nowy wiersz, gdy istnieje 10 bilety max i chcesz zarezerwować 20.

+0

Tak. W każdym locie jest 1 rząd. Ale twój kod wykonuje SELECT, ale nie sprawdza, czy lot jest pełny przed UPDATE. Jak to zrobić? –

+49

-1 To nie jest bezpieczne dla wątków. –

+0

Ze względu na warunki wyścigu jest to poprawne tylko wtedy, gdy poziom izolacji transakcji jest możliwy Serializowalny. –

2

to jest coś, co niedawno było zrobić:

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 To nie jest bezpieczne dla wątków. –

+0

@TheTXI - Czy możesz uczynić ten wątek bezpiecznym? – Steam

1

można korzystać z funkcji Merge osiągnąć. W przeciwnym razie możesz wykonać:

declare @rowCount int 

select @[email protected]@RowCount 

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

błędny kod! roWcount –

+0

MERGE działa tylko na serwerze SQL 2008 lub nowszym. – Steam

136

Spójrz na polecenie MERGE. Możesz wykonać UPDATE, INSERT & DELETE w jednym zestawieniu.

Oto działająca implementacja na temat używania MERGE
- Sprawdza, czy lot jest pełny przed wykonaniem aktualizacji, w przeciwnym razie wstawi.

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 

A potem ...

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

Zobacz także, dlaczego może Ci się spodobać [WITH (HOLDLOCK)] (http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx) dla tego MERGE . –

+3

Myślę, że MERGE jest obsługiwany po 2005 (tak 2008+). – samosaris

+2

MERGE bez WITH (UPDLOCK) może mieć naruszenia klucza głównego, co w tym przypadku byłoby złe. Zobacz [Czy MERGE jest atomową instrukcją w SQL2008?] (Http://stackoverflow.com/questions/9871644/is-merge-an-atomic-statement-in-sql2008) – James

58

Przełęcz updlock, dulka, HOLDLOCK podpowiedzi podczas testowania na istnienie szeregu.

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

Podpowiedź updlock wymusza zapytanie do podjęcia blokada aktualizacji w wierszu, jeśli już istnieje, zapobiegając innych transakcji z modyfikując go, dopóki nie popełnić lub cofnąć.

Wskazówka dotycząca blokowania zmusza zapytanie do wykonania blokady zasięgu, uniemożliwiając innym transakcjom dodawanie wiersza spełniającego kryteria filtru do momentu zatwierdzenia lub wycofania.

Podpowiedź rowlocka wymusza ziarnistość blokady na poziomie wiersza zamiast na domyślnym poziomie strony, więc transakcja nie blokuje innych transakcji próbujących aktualizować niepowiązane wiersze na tej samej stronie (ale należy pamiętać o kompromis między ograniczonymi sporami i wzrost obciążenia związanego z blokowaniem - należy unikać wykonywania dużej liczby blokad na poziomie wiersza w pojedynczej transakcji).

Aby uzyskać więcej informacji, patrz http://msdn.microsoft.com/en-us/library/ms187373.aspx.

Pamiętaj, że zamki są traktowane jako instrukcje, które je wykonują - wywołanie polecenia start nie daje ci odporności na inną transakcję, która szczypanie blokuje coś, zanim do niego dojdziesz.Powinieneś spróbować i uzasadnić swój SQL, aby trzymać zamki na jak najkrótszy czas, dokonując transakcji tak szybko, jak to możliwe (nabywaj późno, zwolnij wcześniej).

Zauważ, że blokady na poziomie wiersza mogą być mniej skuteczne, jeśli twój PK jest bigintem, ponieważ wewnętrzne mieszanie na serwerze SQL jest zdegenerowane dla wartości 64-bitowych (różne wartości klucza mogą być mieszane z tym samym identyfikatorem blokady).

+4

Blokowanie jest BARDZO ważne, aby uniknąć overbooking. Czy słuszne jest założenie, że blokada zadeklarowana w instrukcji IF jest utrzymywana do końca instrukcji IF, tj. Do jednej instrukcji aktualizacji? W takim razie dobrze byłoby pokazać powyższy kod za pomocą znaczników początku bloku, aby uniemożliwić początkującym kopiowanie i wklejenie kodu, a mimo to robić to źle. –

+0

Czy jest jakiś problem, jeśli mój PK jest varcharem (nie jest to MAX) lub kombinacją trzech kolumn VARCHAR? – Steam

+0

Zrobiłem pytanie związane z tą odpowiedzią na - https://stackoverflow.com/questions/21945850/a-thread-safe-way-to-check-if-a-existows-przed-inserting-is-my -code-correct Pytanie czy ten kod może być użyty do wstawienia milionów wierszy. – Steam

33

Piszę moje rozwiązanie. moja metoda nie oznacza "jeśli" lub "scal". moja metoda jest łatwa.

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

Na przykład:

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

Objaśnienie:

(1) kol1 SELECT kolumna2 Z NazwaTabeli GDZIE kol1 = @ Par1 I kolumna2 = @ par2 wybiera się z NazwaTabeli przeszukiwane wartości

(2) WYBIERZ @ par1, @ par2 GDZIE NIE ISTNIA Potrwa, jeśli nie istnieje z (1) s ubquery

(3) wstawienie do NazwaTabeli (2) Etap wartości

+0

Twój kod nigdy się nie aktualizuje, a istniejący już wiersz. – Ethan

+0

służy tylko do wstawiania, a nie aktualizowania. – Cem

0

Pełna roztworu jest poniżej (w tym struktura kursorem). Podziękowania dla Cassius Porcus za kod begin trans ... commit z zamieszczania powyżej.

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

I wreszcie udało się wstawić wiersz, pod warunkiem, że nie istnieje, stosując następujący wzór:

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 
    ) 
) 

który znalazłem na stronie:

http://www.postgresql.org/message-id/[email protected]

+0

To jest odpowiedź typu "tylko kopiuj-wklej" ... lepiej nadaje się jako komentarz. – 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 EXCEPT SELECT kolumna1, kolumna2, kolumna3 z tabeli – Aaron

+1

Istnieje wiele bardzo uprzywilejowanych odpowiedzi na to pytanie. Czy mógłbyś wyjaśnić, co ta odpowiedź dodaje do istniejących odpowiedzi? – francis

0
INSERT INTO Database.dbo.Table SELECT * FROM Database.dbo.Table 
WHERE ID not in (select ID from Database.dbo.Table) 
Powiązane problemy