Periksa apakah ada baris, jika tidak, masukkan


237

Saya perlu menulis prosedur tersimpan T-SQL yang memperbarui baris dalam sebuah tabel. Jika baris tidak ada, masukkan. Semua langkah ini dibungkus oleh suatu transaksi.

Ini untuk sistem pemesanan, jadi harus atomik dan andal . Ini harus mengembalikan true jika transaksi dilakukan dan penerbangan dipesan.

Saya baru mengenal T-SQL , dan tidak yakin bagaimana cara menggunakannya @@rowcount. Ini yang saya tulis sampai sekarang. Apakah saya di jalan yang benar? Saya yakin ini adalah masalah yang mudah bagi Anda.

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


Jawaban:


158

Lihatlah perintah MERGE . Anda dapat melakukannya UPDATE, INSERT& DELETEdalam satu pernyataan.

Berikut ini adalah implementasi bekerja pada penggunaan MERGE
- Ini memeriksa apakah penerbangan penuh sebelum melakukan pembaruan, selain itu memasukkan.

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

Lalu ...

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
Juga, lihat mengapa Anda mungkin suka WITH (HOLDLOCK) untuk MERGE itu.
Eugene Ryabtsev

4
Saya pikir MERGE didukung setelah 2005 (jadi 2008+).
samis

3
MERGE without WITH (UPDLOCK) dapat memiliki pelanggaran kunci utama, yang akan menjadi buruk dalam kasus ini. Lihat [Apakah MERGE pernyataan atom dalam SQL2008?] ( Stackoverflow.com/questions/9871644/… )
James

156

Saya menganggap satu baris untuk setiap penerbangan? Jika begitu:

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

Saya berasumsi apa yang saya katakan, karena cara Anda melakukan hal-hal dapat memesan tiket lebih dari penerbangan, karena akan memasukkan baris baru ketika ada 10 tiket maks dan Anda memesan 20.


Iya. Ada 1 baris per penerbangan. Tetapi kode Anda melakukan SELECT tetapi tidak memeriksa apakah penerbangan penuh sebelum ke UPDATE. Bagaimana cara melakukannya?

2
Karena kondisi lomba itu hanya benar jika tingkat isolasi transaksi saat ini Serializable.
Jarek Przygódzki

1
@ Martin: Jawabannya terfokus pada pertanyaan yang ada. Dari pernyataan OP sendiri, "Semua langkah ini dibungkus oleh transaksi". Jika transaksi diterapkan dengan benar, masalah aman utas tidak boleh menjadi masalah.
Gregory A Beamer

14
@GregoryABeamer - Cukup menempelkannya di BEGIN TRAN ... COMMITbawah tingkat isolasi standar tidak akan menyelesaikan masalah. OP menetapkan bahwa atomik dan andal adalah persyaratan. Jawaban Anda gagal mengatasinya dalam bentuk atau bentuk apa pun.
Martin Smith

2
Ini akan menjadi benang-aman jika (UPDLOCK, HOLDLOCK) ditambahkan ke SELECT: IF EXISTS (SELECT * FROM Bookings (UPDLOCK, HOLDLOCK) WHERE FLightID = @Id)?
Jim,

67

Lewati updlock, rowlock, petunjuk holdlock saat menguji keberadaan baris.

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

Petunjuk updlock memaksa kueri untuk mengambil kunci pembaruan di baris jika sudah ada, mencegah transaksi lain dari memodifikasinya sampai Anda melakukan atau memutar kembali.

Petunjuk penahanan memaksa kueri untuk mengambil kunci rentang, mencegah transaksi lain dari menambahkan baris yang cocok dengan kriteria filter Anda sampai Anda komit atau memutar kembali.

Petunjuk baris kunci memaksa granularity kunci ke tingkat baris alih-alih tingkat halaman default, sehingga transaksi Anda tidak akan memblokir transaksi lain yang mencoba memperbarui baris yang tidak terkait di halaman yang sama (tetapi berhati-hatilah dengan trade-off antara pertikaian yang berkurang dan peningkatan mengunci overhead - Anda harus menghindari mengambil sejumlah besar kunci tingkat baris dalam satu transaksi).

Lihat http://msdn.microsoft.com/en-us/library/ms187373.aspx untuk informasi lebih lanjut.

Perhatikan bahwa kunci diambil sebagai pernyataan yang membawanya dieksekusi - memohon mulai tran tidak memberi Anda kekebalan terhadap transaksi lain menjepit kunci pada sesuatu sebelum Anda sampai ke sana. Anda harus mencoba dan faktor SQL Anda untuk menahan kunci untuk waktu sesingkat mungkin dengan melakukan transaksi sesegera mungkin (memperoleh terlambat, lepaskan lebih awal).

Perhatikan bahwa kunci tingkat baris mungkin kurang efektif jika PK Anda merupakan masalah besar, karena hashing internal pada SQL Server mengalami degenerasi untuk nilai 64-bit (nilai kunci yang berbeda mungkin hash ke id kunci yang sama).


4
Mengunci SANGAT penting untuk menghindari overbooking. Apakah benar untuk mengasumsikan bahwa kunci yang dinyatakan dalam pernyataan IF diadakan sampai akhir pernyataan IF, yaitu untuk satu pernyataan pembaruan? Maka mungkin lebih bijak untuk menunjukkan kode di atas menggunakan marka awal blok end untuk mencegah pemula dari menyalin & menempel kode Anda dan masih salah.
Simon B.

Apakah ada masalah jika PK saya adalah varchar (TIDAK maks maks) atau kombinasi dari tiga kolom VARCHAR?
Uap

Saya membuat pertanyaan terkait dengan jawaban ini di - stackoverflow.com/questions/21945850/... Pertanyaannya adalah apakah kode ini dapat digunakan untuk memasukkan jutaan baris.
Uap

Solusi ini akan memaksakan terlalu banyak penguncian overhead dalam kasus ketika banyak thread sering menguji baris yang sudah ada. Saya kira ini bisa diatasi dengan semacam penguncian ganda diperiksa melalui existspemeriksaan ekstra preventif tanpa penguncian petunjuk.
Vadzim

38

saya sedang menulis solusi saya. metode saya tidak tahan 'jika' atau 'bergabung'. metode saya mudah.

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

Sebagai contoh:

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

Penjelasan:

(1) PILIH col1, col2 DARI TableName WHERE col1 = @ par1 DAN col2 = @ par2 Ia memilih dari nilai yang dicari TableName

(2) SELECT @ par1, @ par2 WHERE NOT EXISTS Dibutuhkan jika tidak ada dari (1) subquery

(3) Menyisipkan nilai langkah TableName (2)


1
itu hanya untuk memasukkan, bukan memperbarui.
Cem

Sebenarnya masih mungkin metode ini gagal karena pemeriksaan keberadaan dilakukan sebelum penyisipan - lihat stackoverflow.com/a/3790757/1744834
Roman Pekar

3

Saya akhirnya bisa menyisipkan baris, dengan syarat belum ada, menggunakan model berikut:

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

yang saya temukan di:

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


1
Ini hanya jawaban tautan tempel-tempel ... lebih cocok sebagai komentar.
Ian

2

Ini adalah sesuatu yang baru-baru ini harus saya lakukan:

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

Anda dapat menggunakan Fungsi Gabung untuk mencapai. Kalau tidak, Anda dapat melakukan:

declare @rowCount int

select @rowCount=@@RowCount

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

0

Solusi lengkapnya ada di bawah (termasuk struktur kursor). Terima kasih banyak kepada Cassius Porcus untuk begin trans ... commitkode dari posting di atas.

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

Sisipkan ke dalam tabel (kolom1, kolom2, kolom3) PILIH $ kolom1, $ kolom2, $ kolom3 KECUALI PILIH kolom1, kolom2, kolom3 dari tabel
Aaron

1
Ada banyak jawaban yang sangat terangkat untuk pertanyaan ini. Bisakah Anda menguraikan untuk menjelaskan apa yang ditambahkan jawaban ini ke jawaban yang ada?
francis

-2

Pendekatan terbaik untuk masalah ini adalah pertama-tama membuat kolom basis data UNIK

ALTER TABLE table_name ADD UNIQUE KEY

THEN INSERT IGNORE INTO table_name , nilai tidak akan dimasukkan jika menghasilkan kunci duplikat / sudah ada dalam tabel.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.