जांचें कि क्या कोई पंक्ति मौजूद है, अन्यथा डालें


237

मुझे एक टी-एसक्यूएल संग्रहीत प्रक्रिया लिखने की ज़रूरत है जो एक तालिका में एक पंक्ति को अपडेट करती है। यदि पंक्ति मौजूद नहीं है, तो उसे डालें। यह सब कदम एक लेनदेन द्वारा लिपटे हुए हैं।

यह एक बुकिंग प्रणाली के लिए है, इसलिए इसे परमाणु और विश्वसनीय होना चाहिए । यदि लेन-देन के लिए प्रतिबद्ध है और फ्लाइट बुक की गई है, तो इसे वापस लौटना चाहिए।

मैं टी-एसक्यूएल के लिए नया हूं , और इसका उपयोग करने के तरीके पर निश्चित नहीं हूं @@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?)


संबंधित प्रश्न - stackoverflow.com/questions/21889843/…
स्टीम

जवाबों:


158

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

6
इसके अलावा, देखें कि आप उस MERGE के लिए (HOLDLOCK) क्यों पसंद कर सकते हैं ।
यूजीन रियात्सेव

4
मुझे लगता है कि 2005 के बाद MERGE समर्थित है (इसलिए 2008+)।
Samis

3
बिना (UPDLOCK) के MERGE में प्राथमिक कुंजी उल्लंघन हो सकते हैं, जो इस मामले में बुरा होगा। देखें [SQL2008 में MERGE का परमाणु विवरण है?] ( Stackoverflow.com/questions/9871644/… )
जेम्स

156

मैं प्रत्येक उड़ान के लिए एक पंक्ति मानता हूं? यदि ऐसा है तो:

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

मुझे लगता है कि मैंने क्या कहा, क्योंकि आपके काम करने का तरीका एक उड़ान को ओवरबुक कर सकता है, क्योंकि इसमें 10 टिकट अधिकतम होने पर एक नई पंक्ति सम्मिलित होगी और आप 20 बुकिंग कर रहे हैं।


हाँ। प्रति उड़ान 1 पंक्ति है। लेकिन आपका कोड SELECT करता है लेकिन यह चेक नहीं करता है कि फ्लाइट UPDATE से पहले भरी हुई है या नहीं। यह कैसे करना है?

2
वर्तमान लेन-देन अलगाव स्तर Serializable है, तो केवल दौड़ की स्थिति के कारण यह सही है।
जारेक प्रोजगोडकी

1
@ मर्टिन: जवाब हाथ में लिए सवाल पर केंद्रित था। ओपी के अपने बयान से "यह सब कदम एक लेनदेन द्वारा लपेटा गया"। यदि लेन-देन सही ढंग से लागू किया गया है, तो थ्रेड सुरक्षित समस्या एक मुद्दा नहीं होनी चाहिए।
ग्रेगरी ए बीमर

14
@GregoryABeamer - बस इसे एक BEGIN TRAN ... COMMITअलग-थलग डिफ़ॉल्ट स्तर पर चिपका देने से समस्या हल नहीं होगी। ओपी ने निर्दिष्ट किया कि परमाणु और विश्वसनीय आवश्यकताएं थीं। आपका उत्तर किसी भी आकार या रूप में इसे संबोधित करने में विफल रहता है।
मार्टिन स्मिथ

2
क्या यह थ्रेड-सुरक्षित होगा (यदि UPDLOCK, HOLDLOCK) को SELECT में जोड़ा गया था IF EXISTS (SELECT * FROM Bookings (UPDLOCK, HOLDLOCK) WHERE FLightID = @Id):?
जिम

67

रॉन्गलॉक, रोलॉक, होल्डलॉक संकेत जब पंक्ति के अस्तित्व के लिए परीक्षण करते हैं।

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

अपडेटलॉक संकेत क्वेरी को पंक्ति पर एक अद्यतन लॉक लेने के लिए मजबूर करता है यदि यह पहले से मौजूद है, तो अन्य लेनदेन को संशोधित करने से रोकें जब तक कि आप प्रतिबद्ध या वापस रोल न करें।

होल्डलॉक संकेत क्वेरी को एक सीमा लॉक करने के लिए मजबूर करता है, अन्य लेन-देन को आपके फ़िल्टर मापदंड से मेल खाने वाली पंक्ति को जोड़ने से रोकता है जब तक आप कमिट या रोल बैक नहीं करते हैं।

उपद्रवी संकेत डिफ़ॉल्ट पृष्ठ स्तर के बजाय पंक्ति स्तर पर लॉक ग्रैन्युलैरिटी को बाध्य करता है, इसलिए आपका लेनदेन एक ही पृष्ठ में असंबंधित पंक्तियों को अपडेट करने की कोशिश कर रहे अन्य लेनदेन को ब्लॉक नहीं करेगा (लेकिन कम विवाद के बीच ट्रेड-ऑफ के बारे में पता होना और इसमें वृद्धि होना लॉकिंग ओवरहेड - आपको एक ही लेनदेन में बड़ी संख्या में पंक्ति-स्तरीय ताले लेने से बचना चाहिए)।

अधिक जानकारी के लिए http://msdn.microsoft.com/en-us/library/ms187373.aspx देखें ।

ध्यान दें कि ताले को उन कथनों के रूप में लिया जाता है जो उन्हें ले जाते हैं - आरंभिक शुरुआत को लागू करने से आपको किसी अन्य लेनदेन के खिलाफ प्रतिरक्षा प्राप्त नहीं होती है, इससे पहले कि आप इसे प्राप्त करें। आपको अपने एसक्यूएल को जल्द से जल्द लेन-देन करके कम से कम संभव समय के लिए ताले रखने की कोशिश करनी चाहिए।

ध्यान दें कि यदि आपके PK एक बिगिन है, तो पंक्ति-स्तरीय ताले कम प्रभावी हो सकते हैं, क्योंकि SQL सर्वर पर आंतरिक हैशिंग 64-बिट मानों के लिए पतित है (विभिन्न मुख्य मान उसी लॉक आईडी के लिए हैश हो सकते हैं)।


4
ओवरबुकिंग से बचने के लिए लॉकिंग बहुत महत्वपूर्ण है। क्या यह मान लेना सही है कि IF स्टेटमेंट में घोषित एक लॉक आईएफ स्टेटमेंट के अंत तक आयोजित होता है, यानी एक अपडेट स्टेटमेंट के लिए? तब हो सकता है कि ऊपर दिए गए कोड को स्टार्ट एंड ब्लॉक मार्कर्स का उपयोग करके ऊपर से कोड दिखाना और अपने कोड को चिपकाने और फिर भी गलत होने से रोकने के लिए बुद्धिमान होना चाहिए।
साइमन बी।

क्या कोई समस्या है यदि मेरा पीके एक वर्चर है (हालांकि अधिकतम नहीं) या तीन वर्कर कॉलम का संयोजन?
स्टीम

मैंने इस उत्तर से संबंधित एक प्रश्न बनाया - stackoverflow.com/questions/21945850/… प्रश्न यह है कि क्या इस कोड का उपयोग लाखों पंक्तियों को सम्मिलित करने के लिए किया जा सकता है।
स्टीम

यह समाधान उन मामलों में बहुत अधिक लॉकिंग ओवरहेड लगाएगा जब कई धागे अक्सर पहले से मौजूद पंक्तियों का परीक्षण करते हैं। मुझे लगता है कि यह existsसंकेत के बिना निवारक अतिरिक्त चेक के माध्यम से एक तरह की दोहरी जाँच लॉकिंग के साथ काम किया जा सकता है।
वडज़िम

38

मैं अपना समाधान लिख रहा हूं। मेरा तरीका 'अगर' या 'मर्ज' नहीं है। मेरा तरीका आसान है।

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

उदाहरण के लिए:

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

स्पष्टीकरण:

(1) सेलेक्ट कॉल 1, टेबल 2 कोलोनेम से, जहां से कोल 1 = @ पार 1 और कॉल 2 = @ पैरा 2 टेबलनेम से चुने गए मानों का चयन करता है

(2) का चयन करें @ par1, @ par2 जहां यह मौजूद नहीं है (1) उपखंड से मौजूद नहीं है

(3) तालिका नाम (2) चरण मानों में सम्मिलित करता है


1
यह केवल डालने के लिए है, अद्यतन करने के लिए नहीं।
सीमेंट

यह वास्तव में अभी भी संभव है इस विधि के कारण अस्तित्व की जांच सम्मिलित करने से पहले की जाती है - देखें stackoverflow.com/a/3790757/1744834
रोमन पाकर

3

मैं अंत में एक पंक्ति सम्मिलित करने में सक्षम था, इस शर्त पर कि यह पहले से मौजूद नहीं था, निम्नलिखित मॉडल का उपयोग कर:

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/87hdow4ld1.fsf@stark.xeocode.com


1
यह एक कॉपी-पेस्ट लिंक केवल उत्तर है ... टिप्पणी के रूप में बेहतर अनुकूल है।
इयान

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

1

आप प्राप्त करने के लिए मर्ज फ़ंक्शनलिटी का उपयोग कर सकते हैं । अन्यथा आप कर सकते हैं:

declare @rowCount int

select @rowCount=@@RowCount

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

0

पूर्ण समाधान नीचे है (कर्सर संरचना सहित)। 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 [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

INSERT INTO टेबल (column1, column2, column3) का चयन करें $ कॉलम 1, $ column2, $ column3 EXCEPT SELECT column1, column2, column3 from table
आरोन

1
इस प्रश्न के बहुत उच्च उत्कीर्ण उत्तर हैं। क्या आप बता सकते हैं कि यह उत्तर मौजूदा उत्तरों में क्या जोड़ता है?
फ्रैंकन

-2

इस समस्या के लिए सबसे अच्छा तरीका सबसे पहले डेटाबेस कॉलम UNIQUE बना रहा है

ALTER TABLE table_name ADD UNIQUE KEY

THEN INSERT IGNORE INTO table_name यदि यह डुप्लिकेट कुंजी / तालिका में पहले से मौजूद है, तो मान डाला नहीं जाएगा।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.