डिलीट ट्रिगर पर रिकॉर्ड को डिलीट करने वाले के बारे में जानकारी पास करना


11

ऑडिट ट्रेल स्थापित करने में मुझे कोई समस्या नज़र नहीं आ रही है जो किसी तालिका में रिकॉर्ड को अपडेट या सम्मिलित कर रहा है, हालांकि, रिकॉर्ड को हटाने वाले ट्रैकिंग अधिक समस्याग्रस्त लगते हैं।

मैं सम्मिलित करें / अद्यतन फ़ील्ड "UpdatedBy" में शामिल करके आवेषण / अद्यतन ट्रैक कर सकते हैं। यह INSERT / UPDATE ट्रिगर को "UpdatedBy" फ़ील्ड के माध्यम से एक्सेस करने की अनुमति देता है inserted.UpdatedBy। हालाँकि, डिलीट ट्रिगर के साथ कोई डेटा डाला / अपडेट नहीं किया गया है। क्या डिलीट ट्रिगर पर जानकारी को पास करने का कोई तरीका है जिससे यह पता चल सके कि रिकॉर्ड को किसने डिलीट किया है?

यहाँ एक इन्सर्ट / अपडेट ट्रिगर है

ALTER TRIGGER [dbo].[trg_MyTable_InsertUpdate] 
ON [dbo].[MyTable]
FOR INSERT, UPDATE
AS  

INSERT INTO AuditTable (IdOfRecordedAffected, UserWhoMadeChanges) 
VALUES (inserted.ID, inserted.LastUpdatedBy)
FROM inserted 

SQL सर्वर 2012 का उपयोग करना


1
इस जवाब को देखें । SUSER_SNAME()जो रिकॉर्ड नष्ट करने के लिए महत्वपूर्ण है।
परिजन शाह

1
धन्यवाद परिजन, हालांकि मुझे नहीं लगता कि SUSER_SNAME()यह एक वेब एप्लिकेशन जैसी स्थिति में काम करेगा जहां एक उपयोगकर्ता पूरे अनुप्रयोग के लिए डेटाबेस संचार के लिए उपयोग किया जा सकता है।
वेबवार्ता

1
आपने यह उल्लेख नहीं किया कि आप एक वेब ऐप कॉल कर रहे थे।
परिजन शाह

क्षमा करें किन्नर, मुझे एप्लिकेशन प्रकार के लिए अधिक विशिष्ट होना चाहिए था।
वेबवार्ता

जवाबों:


10

क्या डिलीट ट्रिगर पर जानकारी को पास करने का कोई तरीका है जिससे यह पता चल सके कि रिकॉर्ड को किसने डिलीट किया है?

हाँ: एक बहुत ही शांत (और उपयोग की सुविधा के तहत) का उपयोग करके कहा जाता है CONTEXT_INFO। यह अनिवार्य रूप से सत्र मेमोरी है जो सभी स्कोपों ​​में मौजूद है और लेनदेन से बाध्य नहीं है। इसका उपयोग जानकारी (कोई भी जानकारी - अच्छी तरह से, जो कि सीमित स्थान में फिट होती है) को ट्रिगर करने के लिए और साथ ही उप-प्रोक / एक्सईसी कॉल के बीच आगे-पीछे हो सकता है। और मैंने इसका उपयोग इसी सटीक स्थिति के लिए पहले भी किया है।

  • संदर्भ जानकारी एक वार्बिनरी (128) है

  • इसके माध्यम से सेट करें: CONTEXT_INFO सेट करें

  • इसके माध्यम से प्राप्त करें: CONTEXT_INFO ()

यह कैसे काम करता है यह देखने के लिए निम्नलिखित के साथ परीक्षण करें। ध्यान दें कि मैं करने से CHAR(128)पहले परिवर्तित कर रहा हूँ CONVERT(VARBINARY(128), ..। इससे वापस करने के लिए परिवर्तित करने के लिए बनाने के लिए बल खाली-गद्दी के लिए है VARCHARजब यह से बाहर हो रही CONTEXT_INFO()के बाद से VARBINARY(128)साथ सही-गद्देदार है 0x00रों।

SELECT CONTEXT_INFO();
-- Initially = NULL

DECLARE @EncodedUser VARBINARY(128);
SET @EncodedUser = CONVERT(VARBINARY(128),
                            CONVERT(CHAR(128), 'I deleted ALL your records! HA HA!')
                          );
SET CONTEXT_INFO @EncodedUser;

SELECT CONTEXT_INFO() AS [RawContextInfo],
       RTRIM(CONVERT(VARCHAR(128), CONTEXT_INFO())) AS [DecodedUser];

परिणाम:

0x492064656C6574656420414C4C20796F7572207265636F7264732120484120484121202020202020...
I deleted ALL your records! HA HA!

यह सब एक साथ डालें:

  1. एप्लिकेशन को एक "डिलीट" संग्रहीत कार्यविधि को कॉल करना चाहिए जो उपयोगकर्ता नाम (या जो भी) पास करता है जो रिकॉर्ड को हटा रहा है। मुझे लगता है कि यह पहले से ही इस्तेमाल किया जा रहा मॉडल है क्योंकि यह लगता है जैसे आप पहले से ही सम्मिलित और अपडेट संचालन पर नज़र रख रहे हैं।

  2. "हटाएं" संग्रहीत कार्यविधि करता है:

    DECLARE @EncodedUser VARBINARY(128);
    SET @EncodedUser = CONVERT(VARBINARY(128),
                                CONVERT(CHAR(128), @UserName)
                              );
    SET CONTEXT_INFO @EncodedUser;
    
    -- DELETE STUFF HERE
  3. ऑडिट ट्रिगर करता है:

    -- Set the INT value in LEFT (currently 50) to the max size of [UserWhoMadeChanges]
    INSERT INTO AuditTable (IdOfRecordedAffected, UserWhoMadeChanges) 
       SELECT del.ID, COALESCE(
                         LEFT(RTRIM(CONVERT(VARCHAR(128), CONTEXT_INFO())), 50),
                         '<unknown>')
       FROM DELETED del;
  4. कृपया ध्यान दें कि, जैसा कि @SeanGallardy ने एक टिप्पणी में बताया है, अन्य प्रक्रियाओं और / या तदर्थ प्रश्नों के कारण इस तालिका से रिकॉर्ड हटा रहे हैं, यह संभव है कि या तो:

    • CONTEXT_INFOसेट नहीं किया गया है और अभी भी है NULL:

      इस कारण से मैंने मान को डिफ़ॉल्ट INSERT INTO AuditTableकरने के लिए ऊपर का अद्यतन किया है COALESCE। या, यदि आप एक डिफ़ॉल्ट नहीं चाहते हैं और एक नाम की आवश्यकता है, तो आप कुछ ऐसा कर सकते हैं:

      DECLARE @UserName VARCHAR(50); -- set to the size of AuditTable.[UserWhoMadeChanges]
      SET @UserName = LEFT(RTRIM(CONVERT(VARCHAR(128), CONTEXT_INFO())), 50);
      
      IF (@UserName IS NULL)
      BEGIN
         ROLLBACK TRAN; -- cancel the DELETE operation
         RAISERROR('Please set UserName via "SET CONTEXT_INFO.." and try again.', 16 ,1);
      END;
      
      -- use @UserName in the INSERT...SELECT
    • CONTEXT_INFOउस मान पर सेट किया गया है जो मान्य उपयोगकर्ता नाम नहीं है, और इसलिए यह AuditTable.[UserWhoMadeChanges]फ़ील्ड के आकार से अधिक हो सकता है :

      इस कारण से मैंने यह LEFTसुनिश्चित करने के लिए एक फ़ंक्शन जोड़ा कि जो कुछ भी पकड़ा गया है CONTEXT_INFOवह टूट नहीं जाएगा INSERT। जैसा कि कोड में उल्लेख किया गया है, आपको बस क्षेत्र 50के वास्तविक आकार को निर्धारित करने की आवश्यकता है UserWhoMadeChanges


SQL सर्वर 2016 और नए के लिए अद्यतन

SQL सर्वर 2016 ने इस प्रति-सत्र मेमोरी का एक बेहतर संस्करण जोड़ा: सत्र संदर्भ। नया सत्र संदर्भ मूल रूप से "की" प्रकार sysname(यानी NVARCHAR(128)) और "मूल्य" होने के साथ की-वैल्यू जोड़े का हैश टेबल है SQL_VARIANT। अर्थ:

  1. अन्य उपयोगों के साथ संघर्ष की संभावना कम होने के कारण अब मूल्यों का अलगाव होता है
  2. आप विभिन्न प्रकारों को संग्रहित कर सकते हैं, मूल्य के माध्यम से वापस जाने पर विषम व्यवहार के बारे में चिंता करने की आवश्यकता नहीं है CONTEXT_INFO()(विवरण के लिए, कृपया मेरी पोस्ट देखें: क्यों नहीं CONTEXT_INFO () SET CONTEXT_INFO द्वारा सटीक मान सेट करें? )
  3. आपको बहुत अधिक स्थान मिलता है : 8000 बाइट्स अधिकतम "मूल्य", सभी कुंजियों पर कुल 256kb तक (128 बाइट्स अधिकतम की तुलना में CONTEXT_INFO)

जानकारी के लिए, कृपया निम्नलिखित दस्तावेज पृष्ठ देखें:


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

@SeanGallardy क्या आप ऐसा होने का एक वास्तविक उदाहरण प्रदान कर सकते हैं? सत्र == @@SPID। यह पेर-सेशन / कनेक्शन मेमोरी है। एक सत्र दूसरे सत्र के संदर्भ जानकारी को अधिलेखित नहीं कर सकता है। और जब सत्र लॉग ऑफ करता है तो मान चला जाता है। "पहले से निर्धारित आइटम" जैसी कोई चीज नहीं है।
सोलोमन रटज़की 5

1
मैंने यह नहीं कहा "दूसरे सत्र का" मैंने कहा कि सत्र के दायरे में कोई भी वस्तु ऐसा कर सकती है। तो, एक देव अपनी खुद की "प्रासंगिक" जानकारी रखने के लिए एक स्पार्क लिखता है और अब आपका ओवरराइट हो गया है। एक आवेदन था जो मुझे उसी पैटर्न का उपयोग करना था, मैंने देखा है कि ऐसा होता है ... यह एचआर सॉफ्टवेयर था। मैं आपको बताता हूं कि एक नए एसपी को लिखने वाले देवों में से एक "बग" के कारण लोगों को समय पर भुगतान नहीं किया गया था, जिसने सत्र के लिए संदर्भ जानकारी को गलत तरीके से अपडेट किया, जो कि "माना" गया था। बस एक उदाहरण देते हुए कि मैं वास्तव में इस पद्धति का उपयोग करने के लिए क्यों नहीं देखा गया हूं।
शॉन गेलार्डी

@ सीन गार्डी ओके, उस बिंदु को स्पष्ट करने के लिए धन्यवाद। लेकिन यह अभी भी केवल आंशिक रूप से मान्य बिंदु है। उस स्थिति के होने के लिए, उस "अन्य" प्रॉप को इस एक के अंदर बुलाया जाना होगा। या, यदि आप किसी अन्य खरीद के बारे में बात कर रहे हैं जो इस तालिका से हटाकर ट्रिगर को किक कर सकता है, तो यह कुछ ऐसा है जिसके लिए परीक्षण किया जा सकता है। यह एक दौड़ की स्थिति है, जिसका कुछ हिसाब होना चाहिए (जिस तरह वे सभी मल्टीथ्रेड एप में हैं), और इस तकनीक का उपयोग नहीं करने का एक कारण है। और इसलिए मैं ऐसा करने के लिए एक मामूली अद्यतन करूंगा। इस भोगवाद को ऊपर लाने के लिए धन्यवाद।
सोलोमन रटज़की

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

5

आप उस तरह से नहीं कर सकते, जब तक कि आप एप्लिकेशन स्तर एक के बजाय SQL सर्वर उपयोगकर्ता आईडी रिकॉर्ड करना नहीं चाहते।

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


में तुम्हारी बात समझ रहा हूँ। मैं वास्तव में एप्लिकेशन स्तर के उपयोगकर्ता को लॉग करना चाहूंगा।
वेबवार्ता

डेविड, वास्तव में आप ट्रिगर्स को जानकारी पास कर सकते हैं। कृपया विवरण के लिए मेरा उत्तर देखें :)।
सोलोमन रटज़की

यहाँ अच्छा सुझाव है, मैं वास्तव में इस मार्ग को पसंद करता हूं। दो पक्षियों को पकड़कर मारता है जो असली डिलीट को ट्रिगर करने के समान चरण में है। चूंकि यह कॉलम इस तालिका के प्रत्येक रिकॉर्ड के लिए NULL होने जा रहा है, इसलिए ऐसा लगता है कि यह SQL सर्वर SPARSEकॉलम का एक अच्छा उपयोग होगा ?
Airn5475

2

क्या डिलीट ट्रिगर पर जानकारी को पास करने का कोई तरीका है जिससे यह पता चल सके कि रिकॉर्ड को किसने डिलीट किया है?

हाँ, जाहिरा तौर पर दो तरीके हैं ;-)। यदि यहाँ मेरे अन्य उत्तरCONTEXT_INFO में सुझाए अनुसार उपयोग करने के बारे में कोई आरक्षण है , तो मैंने एक और तरीका सोचा है जिसमें अन्य कोड / प्रक्रियाओं से क्लीनर कार्यात्मक पृथक्करण है: एक स्थानीय अस्थायी तालिका का उपयोग करें।

अस्थायी तालिका के नाम में से हटाए जाने वाले तालिका का नाम शामिल होना चाहिए क्योंकि यह किसी भी अन्य कोड से अलग रखने में मदद करेगा जो उसी सत्र में चलने के लिए हो सकता है। की तर्ज पर कुछ:
#<TableName>DeleteAudit

एक स्थानीय टेम्प टेबल के लिए एक लाभ CONTEXT_INFOयह है कि अगर कोई अन्य खरीद में है - जो किसी तरह इस विशेष "हटाएं" खरीद से कॉल करता है - बस गलत तरीके से एक ही अस्थायी तालिका नाम का उपयोग होता है, तो उपप्रकार एक) एक नया स्थानीय बनाएगा अनुरोधित नाम की अस्थायी तालिका जो इस प्रारंभिक अस्थायी तालिका से अलग होगी (भले ही इसका एक ही नाम हो), और बी) उप-प्रक्रिया में नए स्थानीय अस्थायी तालिका के खिलाफ कोई भी डीएमएल बयान किसी भी डेटा को प्रभावित नहीं करेगा माता-पिता की प्रक्रिया में यहां बनाई गई स्थानीय अस्थायी तालिका, इसलिए डेटा की कोई ओवरराइटिंग नहीं है। बेशक, अगर कोई सबप्रोसेस इस अस्थायी तालिका के नाम के खिलाफ डीएमएल स्टेटमेंट जारी करता है, तो पहले उसी नाम की क्रेट टेबल जारी किए बिना, तो वे डीएमएल स्टेटमेंट इस तालिका में डेटा को प्रभावित करेंगे । लेकिन, इस बिंदु पर हम वास्तव में हो रहे हैंकिनारे-केसी, यहाँ तक कि ओवरलैपिंग के उपयोग की संभावना से भी अधिक CONTEXT_INFO(हाँ, मुझे पता है कि यह हुआ है, यही कारण है कि मैं "एज-केस" के बजाय "ऐसा कभी नहीं होगा" कहता हूं)।

  1. एप्लिकेशन को एक "डिलीट" संग्रहीत कार्यविधि को कॉल करना चाहिए जो उपयोगकर्ता नाम (या जो भी) पास करता है जो रिकॉर्ड को हटा रहा है। मुझे लगता है कि यह पहले से ही इस्तेमाल किया जा रहा मॉडल है क्योंकि यह लगता है जैसे आप पहले से ही सम्मिलित और अपडेट संचालन पर नज़र रख रहे हैं।

  2. "हटाएं" संग्रहीत कार्यविधि करता है:

    CREATE TABLE #MyTableDeleteAudit (UserName VARCHAR(50));
    INSERT INTO #MyTableDeleteAudit (UserName) VALUES (@UserName);
    
    -- DELETE STUFF HERE
  3. ऑडिट ट्रिगर करता है:

    -- Set the datatype and length to be the same as the [UserWhoMadeChanges] field
    DECLARE @UserName VARCHAR(50);
    IF (OBJECT_ID(N'tempdb..#TriggerTestDeleteAudit') IS NOT NULL)
    BEGIN
       SELECT @UserName = UserName
       FROM #TriggerTestDeleteAudit;
    END;
    
    -- catch the following conditions: missing table, no rows in table, or empty row
    IF (@UserName IS NULL OR @UserName NOT LIKE '%[a-z]%')
    BEGIN
      /* -- uncomment if undefined UserName == badness
       ROLLBACK TRAN; -- cancel the DELETE operation
       RAISERROR('Please set UserName via #TriggerTestDeleteAudit and try again.', 16 ,1);
       RETURN; -- exit
      */
      /* -- uncomment if undefined UserName gets default value
       SET @UserName = '<unknown>';
      */
    END;
    
    INSERT INTO AuditTable (IdOfRecordedAffected, UserWhoMadeChanges) 
       SELECT del.ID, @UserName
       FROM DELETED del;

    मैंने एक ट्रिगर में इस कोड का परीक्षण किया है और यह उम्मीद के मुताबिक काम करता है।

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