Bir satır olup olmadığını kontrol edin, aksi takdirde ekleyin


237

Bir tablodaki bir satırı güncelleştiren bir T-SQL saklı yordamı yazmak gerekiyor. Satır yoksa ekleyin. Tüm bu adımlar bir işlem tarafından sarılır.

Bu bir rezervasyon sistemi içindir, bu yüzden atomik ve güvenilir olmalıdır . İşlem yapıldıysa ve uçuş rezervasyonu yapıldıysa doğru dönmelidir.

T-SQL'de yeniyim ve nasıl kullanılacağından emin değilim @@rowcount. Şimdiye kadar yazdığım bu. Doğru yolda mıyım? Eminim senin için kolay bir problemdir.

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


Yanıtlar:


158

MERGE komutuna bir göz atın . Sen yapabilirsin UPDATE, INSERTve DELETEbir açıklamada.

İşte kullanım üzerinde çalışan bir uygulama MERGE
- Bir güncelleme yapmadan önce uçuşun dolu olup olmadığını kontrol eder, başka bir ek yapar.

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

Ve sonra ...

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

6
Ayrıca, bu MERGE için neden (HOLDLOCK) ile beğeneceğinizi görün .
Eugene Ryabtsev

4
Bence MERGE 2005'ten sonra destekleniyor (yani 2008+).
samis

3
WITH (UPDLOCK) olmadan MERGE, bu durumda kötü olabilecek birincil anahtar ihlallerine sahip olabilir. Bkz. [MERGE, SQL2008'de atomik bir ifade mi?] ( Stackoverflow.com/questions/9871644/… )
James

156

Her uçuş için tek bir sıra var mı? Öyleyse:

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

En çok 10 bilet olduğunda ve 20 rezervasyon yaparken yeni bir sıra ekleyeceğinden, bir şeyler yapmanın yolu bir uçuş rezervasyonunu yapabileceğinden, söylediklerimi varsayıyorum.


Evet. Uçuş başına 1 sıra var. Ancak kodunuz SELECT yapar ancak uçuşun GÜNCELLEME'ye kadar dolu olup olmadığını kontrol etmez. Bu nasıl yapılır?

2
Yarış koşulları nedeniyle, sadece mevcut işlem yalıtım seviyesi Seri hale getirilebilirse doğrudur.
Jarek Przygódzki

1
@ Martin: Cevap eldeki soruya odaklandı. OP'nin "Tüm bu adımlar bir işlem tarafından sarıldı" ifadesinden. İşlem doğru bir şekilde uygulanırsa, iş parçacığı güvenli sorunu bir sorun olmamalıdır.
Gregory A Beamer

14
@GregoryABeamer - Yalnızca BEGIN TRAN ... COMMITvarsayılan yalıtım düzeyine yapıştırmak sorunu çözmez. OP, atomik ve güvenilir gereksinimler olduğunu belirtti. Cevabınız bunu herhangi bir şekil veya biçimde ele alamıyor.
Martin Smith

2
SEÇİM'e (UPDLOCK, HOLDLOCK) eklenirse, bu iş parçacığı için güvenli olur IF EXISTS (SELECT * FROM Bookings (UPDLOCK, HOLDLOCK) WHERE FLightID = @Id)mu?
Jim

67

Sıranın varlığını test ederken yukarı yönlü, satır kilidi, kilit ipuçları verin.

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 ipucu, sorguyu zaten varsa satıra bir güncelleme kilidi almaya zorlar, böylece başka işlemlerin siz işleme veya geri alma işlemine kadar değiştirmesini önler.

Bekletme ipucu, sorguyu aralık kilidi almaya zorlar, böylece başka işlemlerin siz tamamlayana veya geri dönene kadar filtre ölçütlerinizle eşleşen bir satır eklemesini önler.

Satır kilidi ipucu, ayrıntı düzeyini varsayılan sayfa düzeyi yerine satır düzeyine zorlar, böylece işleminiz aynı sayfadaki alakasız satırları güncellemeye çalışan diğer işlemleri engellemez (ancak azaltılmış çekişme ile ek yükü kilitleme - tek bir işlemde çok sayıda satır düzeyinde kilit almaktan kaçınmalısınız).

Daha fazla bilgi için http://msdn.microsoft.com/tr-tr/library/ms187373.aspx adresine bakın .

Kilitlerin, bunları alan ifadeler yürütüldükçe alındığını unutmayın - start tran çağırmak, başka bir işleme karşı kilitleri kıstırmadan önce kilitlemek için size dokunulmazlık sağlamaz. İşleminizi mümkün olan en kısa sürede gerçekleştirerek (geç edin, erken bırakın) SQL'inizi kilitleri mümkün olan en kısa süre boyunca tutmaya çalışmalısınız.

SQL Server'daki dahili karma 64-bit değerler için dejenere olduğundan (farklı anahtar değerleri aynı kilit kimliğine hash olabilir), PK'nuz bir bigint ise satır düzeyi kilitlerin daha az etkili olabileceğini unutmayın.


4
Kilitleme overbooking önlemek için çok önemlidir. IF deyiminde bildirilen bir kilidin, IF deyiminin sonuna kadar, yani bir güncelleme deyimi için tutulduğunu varsaymak doğru mudur? Daha sonra, yeni başlayanların kodunuzu kopyalayıp yapıştırmasını ve yine de yanlış yapmasını önlemek için başlangıç ​​bitiş bloğu işaretleyicilerini kullanarak yukarıdaki kodu göstermek akıllıca olabilir.
Simon

PK'm bir varchar (en fazla NOT DEĞİL) veya üç VARCHAR sütununun birleşimi ise bir sorun var mı?
Steam

Bu cevapla ilgili bir soru yaptım - stackoverflow.com/questions/21945850/… Soru, bu kodun milyonlarca satır eklemek için kullanılabileceği.
Steam

Bu çözüm, birçok iş parçacığının genellikle mevcut satırları test ettiği durumlarda çok fazla kilitleme yükü uygular. Sanırım bu önleyici ekstra existskontrol ile kilitleme ipuçları olmadan bir çift ​​kontrol kilidi ile çalışabilir.
Vadzim

38

Çözümümü yazıyorum. yöntemim 'if' veya 'birleştirme' anlamına gelmez. benim yöntemim kolay.

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

Örneğin:

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

Açıklama:

(1) SEL1 sütun1, sütun2 TabloNADAN NEREDE col1 = @ par1 VE sütun2 = @ par2 TabloAdı aranan değerlerinden seçer

(2) @ par1, @ par2 SELECT NEREDE OLMADI (1) alt sorgudan alır

(3) TableName (2) adım değerlerine ekler


1
sadece ekleme içindir, güncelleme için değildir.
Cem

Aslında bu yöntemin başarısız olması hala neden olup olmadığını kontrol etmeden önce var - bkz. Stackoverflow.com/a/3790757/1744834
Roman Pekar

3

Sonunda, aşağıdaki modeli kullanarak, zaten mevcut olmaması şartıyla bir satır ekleyebildim:

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

hangi buldum:

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


1
Bu bir kopyala-yapıştır bağlantısı sadece cevap ... yorum olarak daha uygun.
Ian

2

Bu son zamanlarda yapmak zorunda olduğum bir şey:

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

Bunu gerçekleştirmek için Birleştirme İşlevselliğini kullanabilirsiniz. Aksi takdirde şunları yapabilirsiniz:

declare @rowCount int

select @rowCount=@@RowCount

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

0

Tam çözüm aşağıdadır (imleç yapısı dahil). begin trans ... commitYukarıdaki kod yazdığı için Cassius Porcus'a çok teşekkürler .

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 [DatabaseName1].dbo.[TableName1] SELECT * FROM [DatabaseName2].dbo.[TableName2]
 WHERE [YourPK] not in (select [YourPK] from [DatabaseName1].dbo.[TableName1])

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

Tabloya EKLE (sütun1, sütun2, sütun3) SELECT $ sütun1, $ sütun2, $ sütun3 SEÇİM SEÇ sütun1, sütun2, sütun3 tablodan
Aaron

1
Bu soruya çok fazla cevap verilmiş cevap var. Bu cevabın mevcut cevaplara neler kattığını açıklayabilir misiniz?
francis

-2

Bu soruna en iyi yaklaşım ilk olarak veritabanı sütununu BENZERSİZ yapmaktır

ALTER TABLE table_name ADD UNIQUE KEY

THEN INSERT IGNORE INTO table_name , değer yinelenen bir anahtarla sonuçlanırsa / tabloda zaten eklenmez.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.