DELETE कथन ने संदर्भ बाधा के साथ संघर्ष किया


10

मेरी स्थिति इस तरह दिखती है:

तालिका STOCK_ARTICLES:

ID *[PK]*
OTHER_DB_ID
ITEM_NAME

तालिका स्थान:

ID *[PK]*
LOCATION_NAME

तालिका WORK_PLACE:

ID *[PK]*
WORKPLACE_NAME

तालिका INVENTORY_ITEMS:

ID *[PK]*
ITEM_NAME
STOCK_ARTICLE *[FK]*
LOCATION *[FK]*
WORK_PLACE *[FK]*

INVENTORY_ITEMS में 3 FK, संबंधित अन्य तालिकाओं में "ID" कॉलम का संदर्भ देते हैं, जाहिर है।

यहां प्रासंगिक तालिकाएँ STOCK_ARTICLE और INVENTORY_ITEMS हैं।

अब कई चरणों (एसक्यूएल स्क्रिप्ट) से मिलकर एक एसक्यूएल नौकरी है जो किसी अन्य डेटाबेस (OTHER_DB) के साथ ऊपर वर्णित डेटाबेस को "सिंक्रनाइज़" करती है । इस नौकरी के अंदर का एक कदम "सफाई" के लिए है। यह STOCK_ITEMS के सभी रिकॉर्ड को हटा देता है, जहाँ एक ही ID के साथ अन्य डेटाबेस में कोई संगत रिकॉर्ड नहीं है। यह इस तरह दिख रहा है:

DELETE FROM STOCK_ARTICLES
 WHERE
    NOT EXISTS
     (SELECT OTHER_DB_ID FROM
     [OTHER_DB].[dbo].[OtherTable] AS other
               WHERE other.ObjectID = STOCK_ARTICLES.OTHER_DB_ID)

लेकिन यह कदम हमेशा विफल रहता है:

DELETE कथन ने "FK_INVENTORY_ITEMS_STOCK_ARTICLES" संदर्भ बाधा के साथ विरोध किया। डेटाबेस "FIRST_DB", तालिका "dbo.INVENTORY_ITEMS", कॉलम 'STOCK_ARTICLES' में संघर्ष हुआ। [SQLSTATE 23000] (त्रुटि 547) कथन को समाप्त कर दिया गया है। [SQLSTATE 01000] (त्रुटि 3621)। कदम फेल हो गया।

इसलिए समस्या यह है कि जब यह INVENTORY_ITEMS द्वारा संदर्भित किए जाते हैं तो STOCK_ARTICLES से रिकॉर्ड को हटा नहीं सकते। लेकिन इस सफाई के लिए काम करने की जरूरत है। जिसका अर्थ है कि मुझे शायद सफाई स्क्रिप्ट का विस्तार करना है ताकि यह पहले उन रिकॉर्ड की पहचान करे, जिन्हें STOCK_ITEMS से हटा दिया जाना चाहिए, लेकिन इसलिए नहीं कि संबंधित ID को INVENTORY_ITEMS के अंदर से संदर्भित किया जाता है। फिर इसे पहले उन रिकॉर्ड्स को INVENTORY_ITEMS के अंदर डिलीट करना चाहिए, और उसके बाद STOCK_ARTICLES के अंदर के रिकॉर्ड्स को डिलीट करना चाहिए। क्या मैं सही हू? SQL कोड तब कैसा दिखेगा?

धन्यवाद।

जवाबों:


13

यह विदेशी कुंजी बाधाओं का पूरा बिंदु है: वे आपको डेटा को हटाने से रोकते हैं जो संदर्भात्मक अखंडता बनाए रखने के लिए कहीं और भेजा जाता है।

दो विकल्प हैं:

  1. INVENTORY_ITEMSपहले पंक्तियों को हटाएं , फिर पंक्तियों को STOCK_ARTICLES
  2. ON DELETE CASCADEकुंजी परिभाषा में उपयोग करें ।

1: सही क्रम में हटाना

ऐसा करने का सबसे कुशल तरीका क्वेरी की जटिलता के आधार पर भिन्न होता है जो यह तय करता है कि कौन सी पंक्तियों को हटाना है। एक सामान्य पैटर्न हो सकता है:

BEGIN TRANSACTION
SET XACT_ABORT ON
DELETE INVENTORY_ITEMS WHERE STOCK_ARTICLE IN (<select statement that returns stock_article.id for the rows you are about to delete>)
DELETE STOCK_ARTICLES WHERE <the rest of your current delete statement>
COMMIT TRANSACTION

यह सरल प्रश्नों के लिए या किसी एकल स्टॉक आइटम को हटाने के लिए ठीक है, लेकिन आपके डिलीट स्टेटमेंट में एक WHERE NOT EXISTSक्लॉज नेस्टिंग है कि भीतर WHERE INएक बहुत ही अकुशल योजना का उत्पादन हो सकता है इसलिए एक यथार्थवादी डेटा सेट आकार के साथ परीक्षण करें और यदि आवश्यक हो तो क्वेरी को पुनर्व्यवस्थित करें।

ट्रांजेक्शन स्टेटमेंट पर भी ध्यान दें: आप यह सुनिश्चित करना चाहते हैं कि दोनों डिलीट पूरी हो जाएं या दोनों में से कोई भी ऐसा न करें। यदि ऑपरेशन पहले से ही एक लेन-देन के भीतर हो रहा है, तो आपको अपने वर्तमान लेनदेन और त्रुटि से निपटने की प्रक्रिया से मेल खाने के लिए स्पष्ट रूप से इसे बदलना होगा।

2: का उपयोग करें ON DELETE CASCADE

यदि आप अपनी विदेशी कुंजी में कैस्केड विकल्प जोड़ते हैं तो SQL सर्वर स्वचालित रूप से आपके लिए ऐसा करेगा, INVENTORY_ITEMSबाधाओं को हटाने के लिए उन बाधाओं को संतुष्ट करने के लिए जो आपके द्वारा हटाए जा रहे पंक्तियों के लिए कुछ भी नहीं होना चाहिए। बस ON DELETE CASCADEFK की परिभाषा में इस तरह जोड़ें :

ALTER TABLE <child_table> WITH CHECK 
ADD CONSTRAINT <fk_name> FOREIGN KEY(<column(s)>)
REFERENCES <parent_table> (<column(s)>)
ON DELETE CASCADE

यहां एक फायदा यह है कि डिलीट एक एटॉमिक स्टेटमेंट रिड्यूस है (हालांकि, हमेशा की तरह, 100% रिमूवल नहीं) ट्रांजैक्शन और लॉक सेटिंग्स के बारे में चिंता करने की जरूरत है। कैसकेड कई माता-पिता / बच्चे / भव्य-बच्चे / ... के स्तर पर भी संचालित हो सकता है यदि माता-पिता और सभी वंशजों के बीच केवल एक ही रास्ता है (जहां यह काम नहीं कर सकता है, उदाहरण के लिए "कई कैस्केड पथ की खोज")।

नोट: मैं, और कई अन्य, कैस्केड हटाए जाने को खतरनाक मानते हैं, इसलिए यदि आप इस विकल्प का उपयोग करते हैं तो इसे अपने डेटाबेस डिज़ाइन में ठीक से दस्तावेज़ करने के लिए बहुत सावधानी बरतें ताकि आप और अन्य डेवलपर्स बाद में खतरे से अधिक यात्रा न करें । मैं इस कारण से जहां भी संभव हो कैस्केडिंग डिलीट से बचता हूं।

जब किसी को छोड़ने और पुनः पंक्तियों के बजाय का उपयोग करके डेटा अद्यतन करता है एक आम सोपानी हटाए साथ की वजह से समस्या है UPDATEया MERGE। यह अक्सर देखा जाता है जहां "उन पंक्तियों को अपडेट करें जो पहले से मौजूद हैं, उन्हें डालें जो नहीं हैं" (कभी-कभी यूपीएसईआरटी ऑपरेशन कहा जाता है) की आवश्यकता होती है और MERGEबयान से अनजान लोगों को यह करना आसान लगता है:

DELETE <all rows that match IDs in the new data>
INSERT <all rows from the new data>

से

-- updates
UPDATE target 
SET    <col1> = source.<col1>
  ,    <col2> = source.<col2>
       ...
  ,    <colN> = source.<colN>
FROM   <target_table> AS target JOIN <source_table_or_view_or_statement> AS source ON source.ID = target.ID
-- inserts
INSERT  <target_table>
SELECT  *
FROM    <source_table_or_other> AS source
LEFT OUTER JOIN
        <target_table> AS target
        ON target.ID = source.ID
WHERE   target.ID IS NULL

यहाँ समस्या यह है कि डिलीट स्टेटमेंट चाइल्ड रो को कैस्केड करेगा, और इंसर्ट स्टेटमेंट उन्हें रीक्रिएट नहीं करेगा, इसलिए पैरेंट टेबल को अपडेट करते समय आप गलती से चाइल्ड टेबल (एस) से डेटा खो देते हैं।

सारांश

हां, आपको पहले चाइल्ड रो को डिलीट करना होगा।

एक और विकल्प है ON DELETE CASCADE:।

लेकिन ON DELETE CASCADEखतरनाक हो सकता है , इसलिए देखभाल के साथ उपयोग करें।

साइड नोट: उपयोग MERGE(या- UPDATEऔर- INSERTजहां MERGEउपलब्ध नहीं है) जब आपको UPSERTऑपरेशन की आवश्यकता होती है , तो नहीं DELETE -जगह-के साथ-साथ INSERTअन्य लोगों द्वारा उपयोग किए गए जाल में गिरने से बचने के लिए ON DELETE CASCADE


2

आप केवल एक बार हटाने के लिए आईडी प्राप्त कर सकते हैं, उन्हें अस्थायी तालिका में संग्रहीत कर सकते हैं और संचालन को हटाने के लिए उपयोग कर सकते हैं। फिर आपके पास बेहतर नियंत्रण है कि आप क्या हटा रहे हैं।

यह ऑपरेशन विफल नहीं होना चाहिए:

SELECT sa.ID INTO #StockToDelete
FROM STOCK_ARTICLES sa
LEFT JOIN [OTHER_DB].[dbo].[OtherTable] other ON other.ObjectID = sa.OTHER_DB_ID
WHERE other.ObjectID IS NULL

DELETE ii
FROM INVENTORY_ITEMS ii
JOIN #StockToDelete std ON ii.STOCK_ARTICLE = std.ID

DELETE sa
FROM STOCK_ARTICLES sa
JOIN #StockToDelete std ON sa.ID = std.ID

2
हालांकि अगर बड़ी संख्या में STOCK_ARTICLES पंक्तियों को हटाने से यह अस्थायी तालिका बनाने के कारण अन्य विकल्पों की तुलना में खराब प्रदर्शन करने की संभावना है (छोटी पंक्तियों के लिए अंतर महत्वपूर्ण होने की संभावना नहीं है)। यह भी सुनिश्चित करने के लिए उचित लेनदेन के निर्देशों का उपयोग करने के लिए ध्यान रखें कि तीन बयानों को एक परमाणु इकाई के रूप में निष्पादित किया जाता है यदि समवर्ती पहुंच असंभव नहीं है, अन्यथा आप INVENTORY_ITEMSदोनों DELETEएस के बीच नए जोड़े जा रहे त्रुटियों को देख सकते हैं ।
डेविड स्पिललेट

1

मैं भी इस मुद्दे में भाग गया, और मैं इसे हल करने में सक्षम था। यहाँ मेरी स्थिति है:

मेरे मामले में, मेरे पास एक एनालिटिक्स (MYTARGET_DB) रिपोर्टिंग के लिए उपयोग किया जाने वाला डेटाबेस है, जो एक स्रोत प्रणाली (MYSOURCE_DB) से खींचता है। 'MYTARGET_DB' तालिकाओं में से कुछ उस प्रणाली के लिए अद्वितीय हैं, और डेटा वहाँ बनाया और प्रबंधित किया जाता है; अधिकांश टेबल 'MYSOURCE_DB' से हैं और एक ऐसा काम है जो डेटा को 'MYSOURCE_DB' से 'MYTARGET_DB' में हटाता / सम्मिलित करता है।

लुकअप तालिकाओं में से एक [PRODUCT] SOURCE से है, और TARGET में संग्रहीत एक डेटा तालिका [InventoryOutsourced] है। तालिकाओं में डिज़ाइन की गई संदर्भात्मक अखंडता है। इसलिए जब मैं हटाने / सम्मिलित करने का प्रयास करता हूं तो मुझे यह संदेश मिलता है।

Msg 50000, Level 16, State 1, Procedure uspJobInsertAllTables_AM, Line 249
The DELETE statement conflicted with the REFERENCE constraint "FK_InventoryOutsourced_Product". The conflict occurred in database "ProductionPlanning", table "dbo.InventoryOutsourced", column 'ProdCode'.

मैंने जो वर्कअराउंड बनाया है वह [InventoryOutsourced] से [@tempTable] टेबल चर में डेटा डालने के लिए है, [InventoryOutsourced] में डेटा हटाएं, सिंक्रोन नौकरियां चलाएं, [@tempTable] से [InventoryOutsourced] डालें। इससे अखंडता बनी रहती है, और विशिष्ट डेटा संग्रह भी बरकरार रहता है। जो दोनों दुनिया में सबसे अच्छा है। उम्मीद है की यह मदद करेगा।

BEGIN TRY
    BEGIN TRANSACTION InsertAllTables_AM

        DECLARE
        @BatchRunTime datetime = getdate(),
        @InsertBatchId bigint
            select @InsertBatchId = max(IsNull(batchid,0)) + 1 from JobRunStatistic 

        --<DataCaptureTmp/> Capture the data tables unique to this database, before deleting source system reference tables
            --[InventoryOutsourced]
            DECLARE @tmpInventoryOutsourced as table (
                [ProdCode]      VARCHAR (12)    NOT NULL,
                [WhseCode]      VARCHAR (4)     NOT NULL,
                [Cases]          NUMERIC (8)     NOT NULL,
                [Weight]         NUMERIC (10, 2) NOT NULL,
                [Date] DATE NOT NULL, 
                [SourcedFrom] NVARCHAR(50) NOT NULL, 
                [User] NCHAR(50) NOT NULL, 
                [ModifiedDatetime] DATETIME NOT NULL
                )

            INSERT INTO @tmpInventoryOutsourced (
                [ProdCode]
               ,[WhseCode]
               ,[Cases]
               ,[Weight]
               ,[Date]
               ,[SourcedFrom]
               ,[User]
               ,[ModifiedDatetime]
               )
            SELECT 
                [ProdCode]
                ,[WhseCode]
                ,[Cases]
                ,[Weight]
                ,[Date]
                ,[SourcedFrom]
                ,[User]
                ,[ModifiedDatetime]
            FROM [dbo].[InventoryOutsourced]

            DELETE FROM [InventoryOutsourced]
        --</DataCaptureTmp> 

... Delete Processes
... Delete Processes    

        --<DataCaptureInsert/> Capture the data tables unique to this database, before deleting source system reference tables
            --[InventoryOutsourced]
            INSERT INTO [dbo].[InventoryOutsourced] (
                [ProdCode]
               ,[WhseCode]
               ,[Cases]
               ,[Weight]
               ,[Date]
               ,[SourcedFrom]
               ,[User]
               ,[ModifiedDatetime]
               )
            SELECT 
                [ProdCode]
                ,[WhseCode]
                ,[Cases]
                ,[Weight]
                ,[Date]
                ,[SourcedFrom]
                ,[User]
                ,[ModifiedDatetime]
            FROM @tmpInventoryOutsourced
            --</DataCaptureInsert> 

    COMMIT TRANSACTION InsertAllTables_AM
END TRY

0

मैंने पूरी तरह से परीक्षण नहीं किया है, लेकिन ऐसा कुछ काम करना चाहिए।

--cte of Stock Articles to be deleted
WITH StockArticlesToBeDeleted AS
(
SELECT ID FROM STOCK_ARTICLES
 WHERE
    NOT EXISTS
     (SELECT OTHER_DB_ID FROM
     [OTHER_DB].[dbo].[OtherTable] AS other
               WHERE other.ObjectID = STOCK_ARTICLES.OTHER_DB_ID)
)
--delete from INVENTORY_ITEMS where we have a match on deleted STOCK_ARTICLE
DELETE a FROM INVENTORY_ITEMS a join
StockArticlesToBeDeleted b on
    b.ID = a.STOCK_ARTICLE;

--now, delete from STOCK_ARTICLES
DELETE FROM STOCK_ARTICLES
 WHERE
    NOT EXISTS
     (SELECT OTHER_DB_ID FROM
     [OTHER_DB].[dbo].[OtherTable] AS other
               WHERE other.ObjectID = STOCK_ARTICLES.OTHER_DB_ID);
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.