सबसेट समुच्चय पर मॉडलिंग बाधाओं?


14

मैं PostgreSQL का उपयोग कर रहा हूं, लेकिन मुझे लगता है कि अधिकांश टॉप-एंड db की कुछ समान क्षमताएं हैं, और इसके अलावा, उनके लिए समाधान मेरे लिए समाधान को प्रेरित कर सकते हैं, इसलिए इस PostgreSQL-विशिष्ट पर विचार न करें।

मुझे पता है कि मैं इस समस्या को हल करने की कोशिश करने वाला पहला व्यक्ति नहीं हूं, इसलिए मुझे लगता है कि यह यहां पूछने लायक है, लेकिन मैं मॉडलिंग लेखांकन डेटा की लागत का मूल्यांकन करने की कोशिश कर रहा हूं, ताकि हर लेनदेन मौलिक रूप से संतुलित हो। लेखांकन डेटा केवल परिशिष्ट है। समग्र बाधा (छद्म संहिता में लिखित) यहाँ लगभग समान दिख सकती है:

CREATE TABLE journal_entry (
    id bigserial not null unique, --artificial candidate key
    journal_type_id int references  journal_type(id),
    reference text, -- source document identifier, unique per journal
    date_posted date not null,
    PRIMARY KEY (journal_type_id, reference)
);

CREATE TABLE journal_line (
    entry_id bigint references journal_entry(id),
    account_id int not null references account(id),
    amount numeric not null,
    line_id bigserial not null unique,
    CHECK ((sum(amount) over (partition by entry_id) = 0) -- this won't work
);

जाहिर है कि इस तरह की चेक की कमी कभी काम नहीं करेगी। यह प्रति पंक्ति पर कार्य करता है और संपूर्ण db पर जाँच कर सकता है। इसलिए यह हमेशा विफल रहेगा और इसे धीमा कर देगा।

तो मेरा सवाल यह है कि इस बाधा को मॉडल करने का सबसे अच्छा तरीका क्या है? मैंने मूल रूप से अब तक दो विचारों को देखा है। आश्चर्य है कि अगर ये केवल एक ही हैं, या अगर किसी के पास एक बेहतर तरीका है (इसके अलावा इसे ऐप स्तर या एक संग्रहीत खरीद के लिए छोड़ दें)।

  1. मैं मूल प्रविष्टि की पुस्तक और अंतिम प्रविष्टि (सामान्य पत्रिका बनाम सामान्य नेतृत्व) की पुस्तक के बीच अंतर की लेखांकन दुनिया की अवधारणा से एक पृष्ठ उधार ले सकता हूं। इस संबंध में मैं इसे जर्नल प्रविष्टि से जुड़ी पत्रिका लाइनों की एक सरणी के रूप में मॉडल कर सकता हूं, सरणी पर बाधा को लागू कर सकता हूं (पोस्टग्रेसीक्यूएल शब्दों में, योग (राशि का चयन करें) = 0 से अनावश्यक (je.line_items)। एक ट्रिगर का विस्तार कर सकता है और। इन्हें एक लाइन आइटम तालिका में सहेजें, जहां व्यक्तिगत कॉलम बाधाओं को अधिक आसानी से लागू किया जा सकता है, और जहां अनुक्रमित आदि अधिक उपयोगी हो सकते हैं। यह वह दिशा है जो मैं झुक रहा हूं।
  2. मैं एक बाधा ट्रिगर को कोड करने की कोशिश कर सकता हूं जो इस प्रति लेनदेन को इस विचार के साथ लागू करेगा कि 0 की श्रृंखला की राशि हमेशा 0 होगी।

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

क्या इस समस्या को हल करने के अन्य तरीके हैं जो प्रत्येक तालिका में लाखों रिकॉर्ड बनाएंगे? क्या मैं कुछ भूल रहा हूँ? क्या कोई ट्रेडऑफ है जो मैंने याद किया है?

संस्करणों के बारे में क्रेग की बात के जवाब में, कम से कम, यह पोस्टग्रेक्यूएल 9.2 और उच्चतर पर चलना होगा (शायद 9.1 और उच्चतर, लेकिन शायद हम सीधे 9.2 के साथ जा सकते हैं)।

जवाबों:


13

जैसा कि हमें कई पंक्तियों को देखना है, इसे एक साधारण बाधा के साथ लागू नहीं किया जा सकता है CHECK

हम बहिष्करण बाधाओं को भी दूर कर सकते हैं । वे कई पंक्तियों का विस्तार करेंगे, लेकिन केवल असमानता की जांच करेंगे। कई पंक्तियों के योग की तरह जटिल ऑपरेशन संभव नहीं है।

जो उपकरण आपके मामले में सबसे अच्छा लगता है वह एक है CONSTRAINT TRIGGER(या यहां तक ​​कि सिर्फ एक मैदान TRIGGER- वर्तमान कार्यान्वयन में एकमात्र अंतर यह है कि आप ट्रिगर के समय को समायोजित कर सकते हैं SET CONSTRAINTS

तो वह आपका विकल्प 2 है

एक बार जब हम हर समय लागू होने वाले बाधा पर भरोसा कर सकते हैं, तो हमें पूरी तालिका को किसी भी अधिक जांचने की आवश्यकता नहीं है। वर्तमान लेनदेन में डाली गई केवल पंक्तियों की जाँच करना - लेनदेन के अंत में - पर्याप्त है। प्रदर्शन ठीक होना चाहिए।

के रूप में भी

लेखांकन डेटा केवल परिशिष्ट है।

... हमें केवल नई सम्मिलित पंक्तियों की देखभाल करने की आवश्यकता है । (मान लिया जाए UPDATEया DELETEसंभव न हो।)

मैं सिस्टम कॉलम का उपयोग करता हूं xidऔर इसे फ़ंक्शन से तुलना करता हूं txid_current()- जो xidवर्तमान लेनदेन को वापस करता है। प्रकारों की तुलना करने के लिए, कास्टिंग की आवश्यकता है ... यह उचित रूप से सुरक्षित होना चाहिए। इस संबंधित पर विचार करें, बाद में एक सुरक्षित विधि के साथ उत्तर दें:

डेमो

CREATE TABLE journal_line(amount int); -- simplistic table for demo

CREATE OR REPLACE FUNCTION trg_insaft_check_balance()
    RETURNS trigger AS
$func$
BEGIN
   IF sum(amount) <> 0
      FROM journal_line 
      WHERE xmin::text::bigint = txid_current()  -- consider link above
         THEN
      RAISE EXCEPTION 'Entries not balanced!';
   END IF;

   RETURN NULL;  -- RETURN value of AFTER trigger is ignored anyway
END;
$func$ LANGUAGE plpgsql;

CREATE CONSTRAINT TRIGGER insaft_check_balance
    AFTER INSERT ON journal_line
    DEFERRABLE INITIALLY DEFERRED
    FOR EACH ROW
    EXECUTE PROCEDURE trg_insaft_check_balance();

आस्थगित किया जाता है , इसलिए यह केवल लेनदेन के अंत में जांचा जाता है।

टेस्ट

INSERT INTO journal_line(amount) VALUES (1), (-1);

काम करता है।

INSERT INTO journal_line(amount) VALUES (1);

विफल रहता है:

त्रुटि: प्रविष्टियाँ संतुलित नहीं!

BEGIN;
INSERT INTO journal_line(amount) VALUES (7), (-5);
-- do other stuff
SELECT * FROM journal_line;
INSERT INTO journal_line(amount) VALUES (-2);
-- INSERT INTO journal_line(amount) VALUES (-1); -- make it fail
COMMIT;

काम करता है। :)

यदि आपको लेन-देन की समाप्ति से पहले अपने अवरोध को लागू करने की आवश्यकता है, तो आप लेनदेन के किसी भी बिंदु पर, यहां तक ​​कि शुरुआत में भी ऐसा कर सकते हैं:

SET CONSTRAINTS insaft_check_balance IMMEDIATE;

सादे ट्रिगर के साथ तेज़

यदि आप बहु-पंक्ति के साथ काम करते हैं तो INSERTयह प्रति कथन ट्रिगर करने के लिए अधिक प्रभावी है - जो बाधा ट्रिगर के साथ संभव नहीं है :

बाधा ट्रिगर केवल निर्दिष्ट किया जा सकता है FOR EACH ROW

इसके बजाय एक सादे ट्रिगर का उपयोग करें और आग FOR EACH STATEMENT...

  • का विकल्प खो देते हैं SET CONSTRAINTS
  • प्रदर्शन हासिल करें।

DELETE संभव है

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


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

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

@ChrisTravers। मैंने एक अद्यतन जोड़ा और संभव को संबोधित किया DELETE। मुझे नहीं पता होगा कि लेखांकन में क्या विशिष्ट या आवश्यक है - मेरी विशेषज्ञता का क्षेत्र नहीं। बस वर्णित समस्या का एक (बहुत प्रभावी IMO) समाधान प्रदान करने की कोशिश कर रहा है।
एरविन ब्रान्डसेट्टर

@Erwin Brandstetter मैं उस के बारे में चिंता नहीं करता हटा देगा। यदि लागू हो तो विलोपन, बाधाओं के एक बहुत बड़े समूह के अधीन होगा और यूनिट परीक्षण वहां पूरी तरह से अपरिहार्य हैं। मैं ज्यादातर जटिलता लागतों पर विचारों के बारे में सोच रहा था। किसी भी दर पर हटाए गए कोस्कैड fkey पर बहुत आसानी से हटाया जा सकता है।
क्रिस ट्रैवर्स

4

निम्न SQL सर्वर समाधान केवल बाधाओं का उपयोग करता है। मैं अपने सिस्टम में कई स्थानों पर समान दृष्टिकोण का उपयोग कर रहा हूं।

CREATE TABLE dbo.Lines
  (
    EntryID INT NOT NULL ,
    LineNumber SMALLINT NOT NULL ,
    CONSTRAINT PK_Lines PRIMARY KEY ( EntryID, LineNumber ) ,
    PreviousLineNumber SMALLINT NOT NULL ,
    CONSTRAINT UNQ_Lines UNIQUE ( EntryID, PreviousLineNumber ) ,
    CONSTRAINT CHK_Lines_PreviousLineNumber_Valid CHECK ( ( LineNumber > 0
            AND PreviousLineNumber = LineNumber - 1
          )
          OR ( LineNumber = 0 ) ) ,
    Amount INT NOT NULL ,
    RunningTotal INT NOT NULL ,
    CONSTRAINT UNQ_Lines_FkTarget UNIQUE ( EntryID, LineNumber, RunningTotal ) ,
    PreviousRunningTotal INT NOT NULL ,
    CONSTRAINT CHK_Lines_PreviousRunningTotal_Valid CHECK 
        ( PreviousRunningTotal + Amount = RunningTotal ) ,
    CONSTRAINT CHK_Lines_TotalAmount_Zero CHECK ( 
            ( LineNumber = 0
                AND PreviousRunningTotal = 0
              )
              OR ( LineNumber > 0 ) ),
    CONSTRAINT FK_Lines_PreviousLine 
        FOREIGN KEY ( EntryID, PreviousLineNumber, PreviousRunningTotal )
        REFERENCES dbo.Lines ( EntryID, LineNumber, RunningTotal )
  ) ;
GO

-- valid subset inserts
INSERT INTO dbo.Lines(EntryID ,
        LineNumber ,
        PreviousLineNumber ,
        Amount ,
        RunningTotal ,
        PreviousRunningTotal )
VALUES(1, 0, 2, 10, 10, 0),
(1, 1, 0, -5, 5, 10),
(1, 2, 1, -5, 0, 5);

-- invalid subset fails
INSERT INTO dbo.Lines(EntryID ,
        LineNumber ,
        PreviousLineNumber ,
        Amount ,
        RunningTotal ,
        PreviousRunningTotal )
VALUES(2, 0, 1, 10, 10, 5),
(2, 1, 0, -5, 5, 10) ;

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

@Chris: मैं इसे (हटाने के बाद Postgres में बस ठीक काम करता है लगता है dbo.और GO:) एसक्यूएल-बेला
ypercubeᵀᴹ

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

BTW, दोनों समाधान upvoted। अन्य को सूचीबद्ध करने के लिए बेहतर माना जा सकता है क्योंकि यह कम जटिल लगता है। हालाँकि मुझे लगता है कि यह एक बहुत ही दिलचस्प समाधान है और यह मेरे लिए बहुत जटिल बाधाओं के बारे में सोचने के नए तरीके खोलता है। धन्यवाद!
क्रिस ट्रैवर्स

और आपको सुरक्षित होने के लिए पिछली पंक्ति के उप-योग को देखने के लिए किसी ट्रिगर की आवश्यकता नहीं है। FK_Lines_PreviousLineविदेशी कुंजी बाधा द्वारा यह ध्यान रखा जाता है ।
ypercube y
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.