मैं बूलियन कॉलम के लिए एक सही मूल्य और अन्य सभी को गलत मान देने के लिए एक रिकॉर्ड को कैसे मजबूर कर सकता हूं?


20

मैं लागू करना चाहता हूं कि तालिका में केवल एक रिकॉर्ड को अन्य प्रश्नों या विचारों के लिए "डिफ़ॉल्ट" मान माना जाता है जो उस तालिका तक पहुंच सकते हैं।

मूल रूप से, मैं यह गारंटी देना चाहता हूं कि यह क्वेरी हमेशा एक पंक्ति में वापस आएगी:

SELECT ID, Zip 
FROM PostalCodes 
WHERE isDefault=True

मैं SQL में कैसे करूँगा?


1
"मैं यह गारंटी देना चाहता हूं कि यह क्वेरी हमेशा एक पंक्ति में वापस आएगी" - हमेशा? PostalCodesखाली होने पर क्या ? यदि एक पंक्ति में पहले से ही संपत्ति है, तो इसे तब तक झूठे पर सेट होने से रोका जाना चाहिए जब तक कि एक ही SQL कथन के भीतर कोई अन्य पंक्ति (यदि मौजूद नहीं है) सही है? क्या शून्य पंक्तियों में लेनदेन सीमाओं के बीच संपत्ति हो सकती है? क्या तालिका में अंतिम पंक्ति को संपत्ति होने के लिए मजबूर किया जाना चाहिए और हटाए जाने से रोका जाना चाहिए? अनुभव मुझे बताता है कि "वास्तव में एक पंक्ति की गारंटी" वास्तव में कुछ अलग करने का मतलब है, अक्सर "सबसे अधिक एक पंक्ति में"।
13:29 पर onedaywhen

जवाबों:


8

संपादित करें: इससे पहले कि हम MySQL को जानते थे, यह SQL सर्वर पर आधारित है

ऐसा करने के लिए 4 5 तरीके हैं: सबसे कम से कम वांछित के क्रम में

हमें नहीं पता कि आरडीबीएमएस क्या है, हालांकि इसके लिए यह सब लागू नहीं हो सकता है

  1. सबसे अच्छा तरीका एक फ़िल्टर्ड इंडेक्स है। यह विशिष्टता बनाए रखने के लिए DRI का उपयोग करता है।

  2. अद्वितीयता के साथ संगणित स्तंभ (जैक डगलस का जवाब देखें) (संपादित 2 द्वारा जोड़ा गया)

  3. एक अनुक्रमित / भौतिकवादी दृश्य जो DRI का उपयोग करके फ़िल्टर किए गए सूचकांक की तरह है

  4. ट्रिगर (अन्य उत्तरों के अनुसार)

  5. एक UDF के साथ बाधा की जाँच करें। यह संगामिति और स्नैपशॉट अलगाव के लिए सुरक्षित नहीं हैएक दो तीन चार देखें

ध्यान दें कि "एक डिफ़ॉल्ट" की आवश्यकता तालिका पर है, न कि संग्रहीत कार्यविधि। संग्रहित प्रक्रिया में एक यूडीएफ के साथ एक चेक बाधा के रूप में समान समसामयिक समस्याएं होंगी

नोट: SO पर कई बार पूछा गया:


gbn - फ़िल्टर किए गए इंडेक्स / DRI समाधान की तरह दिखता है SQL 2008 केवल इतना है कि मैं कोशिश कर रहा विकल्प 2 को छोड़
दूंगा

10

नोट: यह उत्तर देने से पहले यह स्पष्ट था कि पूछने वाला कुछ MySQL विशिष्ट चाहता था। यह उत्तर SQL सर्वर के पक्षपाती है।

एक UDF को कॉल करने वाली तालिका में एक CHECK बाधा लागू करें और सुनिश्चित करें कि इसका रिटर्न मान <= 1. है। UDF तालिका में पंक्तियों को केवल वहीं गिन सकता है । यह सुनिश्चित करेगा कि तालिका में 1 से अधिक डिफ़ॉल्ट पंक्ति नहीं है।isDefault = TRUE

आपको यह सुनिश्चित करने के लिए कि यह UDF बहुत तेज़ी से चलता है , आपको कॉलम पर एक बिटमैप या फ़िल्टर किए गए अनुक्रमणिका isDefault(अपनी प्लेटफ़ॉर्म के आधार पर) को जोड़ना चाहिए ।

चेतावनियां

  • चेक बाधाओं की कुछ सीमाएं हैं । अर्थात्, उन्हें हर पंक्ति के लिए लागू किया जाता है जिसे संशोधित किया जाता है, भले ही उन पंक्तियों को लेनदेन में अभी तक प्रतिबद्ध नहीं किया गया हो। इसलिए यदि आप लेन-देन शुरू करते हैं, तो डिफ़ॉल्ट होने के लिए एक नई पंक्ति सेट करें और फिर पुरानी पंक्ति को अनसुना कर दें, आपका चेक बाधा शिकायत करेगा। ऐसा इसलिए है क्योंकि यह नई पंक्ति को डिफ़ॉल्ट बनाने के बाद निष्पादित करेगा, यह पता लगाएं कि अब दो चूक हैं, और विफल रहें। तब समाधान यह सुनिश्चित करने के लिए होता है कि आप नया डिफ़ॉल्ट सेट करने से पहले पहले डिफ़ॉल्ट पंक्ति को अनसेट कर दें, भले ही आप यह कार्य किसी लेन-देन में कर रहे हों।
  • जैसा कि gbn ने बताया है , यदि आप SQL सर्वर में स्नैपशॉट अलगाव का उपयोग कर रहे हैं, तो UDF असंगत परिणाम दे सकता है। हालाँकि, एक इनलाइन UDF इस समस्या में नहीं चलता है, और चूंकि UDF जो केवल एक काउंट करता है, इनलाइन-सक्षम है, यह यहाँ कोई समस्या नहीं है।
  • एक डेटाबेस इंजन की प्रोसेसिंग पावर की ताकत एक बार में डेटा के सेट पर काम करने की क्षमता में है। UDF- समर्थित चेक बाधा के साथ, इंजन इस UDF को क्रमिक रूप से संशोधित करने वाली प्रत्येक पंक्ति पर लागू करने तक सीमित रहेगा। आपके उपयोग के मामले में यह संभावना नहीं है कि आप PostalCodesटेबल पर बड़े पैमाने पर अपडेट करेंगे , हालांकि यह उन परिस्थितियों के लिए एक प्रदर्शन चिंता का विषय है जहां सेट-आधारित गतिविधि की संभावना है।

इन सभी घटनाओं को देखते हुए, मैं एक जांच बाधा के बजाय फ़िल्टर किए गए अद्वितीय सूचकांक के gbn के सुझाव का उपयोग करने की सलाह देता हूं ।


4

यहाँ एक संग्रहीत प्रक्रिया (MySQL बोली) है:

DELIMITER $$
DROP PROCEDURE IF EXISTS SetDefaultForZip;
CREATE PROCEDURE SetDefaultForZip (NEWID INT)
BEGIN
    DECLARE FOUND_TRUE,OLDID INT;

    SELECT COUNT(1) INTO FOUND_TRUE FROM PostalCode WHERE isDefault = TRUE;
    IF FOUND_TRUE = 1 THEN
        SELECT ID INTO OLDID FROM PostalCode WHERE isDefault = TRUE;
        IF NEWID <> OLDID THEN
            UPDATE PostalCode SET isDefault = FALSE WHERE ID = OLDID;
            UPDATE PostalCode SET isDefault = TRUE  WHERE ID = NEWID;
        END IF;
    ELSE
        UPDATE PostalCode SET isDefault = TRUE WHERE ID = NEWID;
    END IF;
END;
$$
DELIMITER ;

यह सुनिश्चित करने के लिए कि आपकी तालिका साफ है और संग्रहीत कार्यविधि काम कर रही है, मान लिया गया कि ID 200 डिफ़ॉल्ट है, इन चरणों को चलाएँ:

ALTER TABLE PostalCode DROP INDEX isDefault_ndx;
UPDATE PostalCodes SET isDefault = FALSE;
ALTER TABLE PostalCode ADD INDEX isDefault_ndx (isDefault);
CALL SetDefaultForZip(200);
SELECT ID FROM PostalCodes WHERE isDefault = TRUE;

एक संग्रहीत प्रक्रिया के बजाय, एक ट्रिगर के बारे में कैसे?

DELIMITER $$
CREATE TRIGGER postalcodes_bu BEFORE UPDATE ON PostalCodes FOR EACH ROW
BEGIN
    DECLARE FOUND_TRUE,OLDID INT;
    IF NEW.isDefault = TRUE THEN
        SELECT COUNT(1) INTO FOUND_TRUE FROM PostalCode WHERE isDefault = TRUE;
        IF FOUND_TRUE = 1 THEN
            SELECT ID INTO OLDID FROM PostalCode WHERE isDefault = TRUE;
            UPDATE PostalCodes SET isDefault = FALSE WHERE ID = OLDID;
        END IF;
    END IF;
END;
$$
DELIMITER ;

यह सुनिश्चित करने के लिए कि आपकी तालिका साफ है और ट्रिगर काम कर रहा है, मान लिया गया कि ID 200 डिफ़ॉल्ट है, इन चरणों को चलाएँ:

DROP TRIGGER postalcodes_bu;
ALTER TABLE PostalCode DROP INDEX isDefault_ndx;
UPDATE PostalCodes SET isDefault = FALSE;
ALTER TABLE PostalCode ADD INDEX isDefault_ndx (isDefault);
DELIMITER $$
CREATE TRIGGER postalcodes_bu BEFORE UPDATE ON PostalCodes FOR EACH ROW
BEGIN
    DECLARE FOUND_TRUE,OLDID INT;
    IF NEW.isDefault = TRUE THEN
        SELECT COUNT(1) INTO FOUND_TRUE FROM PostalCode WHERE isDefault = TRUE;
        IF FOUND_TRUE = 1 THEN
            SELECT ID INTO OLDID FROM PostalCode WHERE isDefault = TRUE;
            UPDATE PostalCodes SET isDefault = FALSE WHERE ID = OLDID;
        END IF;
    END IF;
END;
$$
DELIMITER ;
UPDATE PostalCodes SET isDefault = TRUE WHERE ID = 200;
SELECT ID FROM PostalCodes WHERE isDefault = TRUE;

3

आप नियमों को लागू करने के लिए ट्रिगर का उपयोग कर सकते हैं। जब कोई UPDATE या INSERT स्टेटमेंट ट्रू पर सेट होता है, तो ट्रिगर में SQL अन्य सभी पंक्तियों को गलत पर सेट कर सकता है।

इसे लागू करते समय आपको अन्य स्थितियों पर विचार करना होगा। उदाहरण के लिए, क्या होगा यदि एक से अधिक पंक्ति (UPDATE या INSERT सेट) isDefault True? नियम तोड़ने से बचने के लिए क्या नियम लागू होंगे?

इसके अलावा, क्या यह संभव है जहां कोई डिफ़ॉल्ट नहीं है? जब कोई अपडेट सही से गलत पर isDefault सेट करता है तो क्या होगा।

एक बार जब आप नियमों को परिभाषित करते हैं, तो आप उन्हें ट्रिगर में बना सकते हैं।

फिर, जैसा कि एक अन्य जवाब में संकेत दिया गया है, आप नियमों को लागू करने में मदद करने के लिए एक चेक बाधा लागू करना चाहते हैं।


3

एक उदाहरण तालिका और डेटा सेट करने के बाद:

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[MyTable]') AND type in (N'U'))
DROP TABLE [dbo].[MyTable]
GO

CREATE TABLE dbo.MyTable
(
    [id] INT IDENTITY(1,1) PRIMARY KEY CLUSTERED
    , [IsDefault] BIT DEFAULT 0
)
GO

INSERT dbo.MyTable DEFAULT VALUES
GO 100

यदि आप IsDefault ध्वज सेट करने के लिए एक संग्रहीत कार्यविधि बनाते हैं और बेस टेबल को बदलने के लिए अनुमतियाँ हटाते हैं, तो आप इसे नीचे क्वेरी के साथ लागू कर सकते हैं। इसे हर बार टेबल स्कैन की आवश्यकता होगी।

DECLARE @Id INT
SET @Id = 10

UPDATE
    dbo.MyTable
SET
    IsDefault = CASE WHEN [id] = @Id THEN 1 ELSE 0 END 
GO

तालिका के आकार के आधार पर, पूर्ण स्कैन से बचने के लिए IsDefault (DESC) पर एक सूचकांक नीचे दिए गए एक या दोनों प्रश्नों में हो सकता है।

DECLARE @Id INT
SET @Id = 10

UPDATE
    dbo.MyTable
SET
    IsDefault = CASE WHEN [id] = @Id THEN 1 ELSE 0 END
FROM
    dbo.MyTable
WHERE
    [id] = @Id
OR  IsDefault = 1

UPDATE
    dbo.MyTable
SET
    IsDefault = CASE WHEN [id] = @Id THEN 1 ELSE 0 END
FROM
    dbo.MyTable
WHERE
    [id] = @Id
OR  ([id] != @Id AND IsDefault = 1)

यदि आप आधार तालिका से अनुमति नहीं निकाल सकते हैं, तो अन्य तरीकों से अखंडता को लागू करने की आवश्यकता है और SQL2008 का उपयोग कर रहे हैं, तो आप एक फ़िल्टर किए गए अद्वितीय सूचकांक का उपयोग कर सकते हैं:

CREATE UNIQUE INDEX IX_MyTable_IsDefault  ON dbo.MyTable (IsDefault) WHERE IsDefault = 1

1

MySQL के लिए जिसमें अनुक्रमित फ़िल्टर नहीं है, आप निम्न की तरह कुछ कर सकते हैं। यह इस तथ्य का शोषण करता है कि MySQL अद्वितीय अनुक्रमित में कई NULLs की अनुमति देता है, और एक समर्पित तालिका का उपयोग करने के लिए एक समर्पित तालिका और एक विदेशी कुंजी का उपयोग करता है जिसमें केवल NULL और 1 मान हो सकते हैं।

इस समाधान के साथ, सही / गलत के बजाय, आप सिर्फ 1 / NULL का उपयोग करेंगे।

CREATE TABLE DefaultFlag (id TINYINT UNSIGNED NOT NULL PRIMARY KEY);
INSERT INTO DefaultFlag (id) VALUES (1);

CREATE TABLE PostalCodes (
    ID INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
    Zip VARCHAR (32) NOT NULL,
    isDefault TINYINT UNSIGNED NULL DEFAULT NULL,
    CONSTRAINT FOREIGN KEY (isDefault) REFERENCES DefaultFlag (id),
    UNIQUE KEY (isDefault)
);

INSERT INTO PostalCodes (Zip, isDefault) VALUES ('123', 1   );
/* Only one row can be default */
INSERT INTO PostalCodes (Zip, isDefault) VALUES ('abc', 1   );
/* ERROR 1062 (23000): Duplicate entry '1' for key 'isDefault' */

/* MySQL allows multiple NULLs in unique indexes */
INSERT INTO PostalCodes (Zip, isDefault) VALUES ('456', NULL);
INSERT INTO PostalCodes (Zip, isDefault) VALUES ('789', NULL);

/* only NULL and 1 are admitted in isDefault thanks to foreign key */
INSERT INTO PostalCodes (Zip, isDefault) VALUES ('789', 2   );
/* ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`test`.`PostalCodes`, CONSTRAINT `PostalCodes_ibfk_1` FOREIGN KEY (`isDefault`) REFERENCES `DefaultFlag` (`id`))*/

केवल एक चीज जो गलत हो सकती है, मुझे लगता है, अगर कोई व्यक्ति DefaultFlagतालिका में एक पंक्ति जोड़ता है । मुझे लगता है कि यह एक बड़ी चिंता नहीं है, और यदि आप वास्तव में सुरक्षित रहना चाहते हैं तो आप इसे हमेशा अनुमतियों के साथ ठीक कर सकते हैं।


0
SELECT ID, Zip FROM PostalCodes WHERE isDefault=True 

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

एक 'डिफॉल्ट्स' टेबल रखें, उसमें पोस्टलकोड, वह आईडी है, जिसे आप खोज रहे हैं। सामान्य तौर पर, एक रिकॉर्ड के अनुसार अपनी तालिकाओं का नाम देना भी अच्छा होता है, इसलिए आपकी प्रारंभिक तालिका का नाम 'पोस्टल कोड' कहलाना बेहतर होगा। डिफॉल्ट्स एक एकल पंक्ति तालिका होगी, जब तक आपको कुछ अन्य कॉन्फ़िगरेशन (जैसे कि इतिहासकार) के खिलाफ अपने डिफॉल्ट्स को विशेषज्ञ करने की आवश्यकता नहीं होती है।

अंतिम क्वेरी जो आप चाहते हैं वह इस प्रकार है:

select Zip from PostalCode p, Defaults d where p.ID=d.PostalCode;
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.