'डिफ़ॉल्ट' ध्वज को कैसे लागू किया जाए जो केवल एक पंक्ति में ही सेट किया जा सकता है


31

उदाहरण के लिए, इस तालिका के समान:

create table foo(bar int identity, chk char(1) check (chk in('Y', 'N')));

इससे कोई फ़र्क नहीं पड़ता कि ध्वज को ए char(1), bitया जो भी लागू किया गया हो। मैं सिर्फ यह सुनिश्चित करना चाहता हूं कि यह केवल एक ही पंक्ति में स्थापित किया जा सकता है।


इस सवाल से प्रेरित है जो MySQL
जैक डगलस

2
जिस तरह से प्रश्न का उत्तर दिया गया है, वह बताता है कि तालिका का उपयोग करना गलत उत्तर होना चाहिए। लेकिन कभी-कभी (अधिकांश समय?) एक और तालिका जोड़ना एक अच्छा विचार है। और एक टेबल जोड़ना पूरी तरह से डेटाबेस अज्ञेय है।
माइक शेरिल 'कैट रिकॉल'

जवाबों:



16

SQL सर्वर 2000, 2005:

आप इस तथ्य का लाभ उठा सकते हैं कि एक अद्वितीय सूचकांक में केवल एक नल की अनुमति है:

create table t( id int identity, 
                chk1 char(1) not null default 'N' check(chk1 in('Y', 'N')), 
                chk2 as case chk1 when 'Y' then null else id end );
create unique index u_chk on t(chk2);

2000 के लिए, आपको आवश्यकता हो सकती है SET ARITHABORT ON(इस जानकारी के लिए @gb पर धन्यवाद)


14

आकाशवाणी:

चूंकि Oracle उन प्रविष्टियों को अनुक्रमणित नहीं करता है जहाँ सभी अनुक्रमित स्तंभ शून्य हैं, आप फ़ंक्शन-आधारित अनन्य अनुक्रमणिका का उपयोग कर सकते हैं:

create table foo(bar integer, chk char(1) not null check (chk in('Y', 'N')));
create unique index idx on foo(case when chk='Y' then 'Y' end);

यह सूचकांक केवल कभी-कभी एक पंक्ति को अधिकतम अनुक्रमित करेगा।

इस सूचकांक तथ्य को जानने के बाद, आप बिट कॉलम को थोड़ा अलग तरीके से लागू कर सकते हैं:

create table foo(bar integer, chk char(1) check (chk ='Y') UNIQUE);

यहां कॉलम के लिए संभावित मान chkहो जाएगा Yऔर NULL। केवल एक पंक्ति में अधिकतम मूल्य हो सकता हैY.


chk को एक not nullबाधा की आवश्यकता है ?
जैक डगलस

@jack: not nullयदि आप नल नहीं चाहते हैं तो आप एक बाधा जोड़ सकते हैं (यह प्रश्न चश्मा से मेरे लिए स्पष्ट नहीं था)। किसी भी स्थिति में केवल एक पंक्ति में 'Y' का मान हो सकता है।
विंसेंट मालाग्रेट

+1 मैं देखता हूं कि आपका क्या मतलब है - आप सही कह रहे हैं यह आवश्यक नहीं है (लेकिन शायद थोड़ा नटखट है, खासकर यदि संयुक्त रूप से default)?
जैक डगलस

2
@jack: आपकी टिप्पणी ने मुझे एहसास दिलाया कि एक और भी सरल संभावना उपलब्ध है यदि आप स्वीकार करते हैं कि कॉलम Yया तो हो सकता है या null, मेरा अपडेट देखें।
विंसेंट मालाग्राट

1
विकल्प 2 में अतिरिक्त लाभ है कि सूचकांक छोटा होगा क्योंकि nulls को छोड़ दिया जाता है - कुछ स्पष्टता की कीमत पर
जैक डगलस

13

मुझे लगता है कि यह आपके डेटाबेस तालिकाओं को सही ढंग से संरचित करने का मामला है। इसे और अधिक ठोस बनाने के लिए, यदि आपके पास कई पते वाले व्यक्ति हैं और आप चाहते हैं कि वह डिफ़ॉल्ट हो, तो मुझे लगता है कि आपको व्यक्ति की तालिका में डिफ़ॉल्ट पते का पता संग्रहित करना चाहिए, पता तालिका में डिफ़ॉल्ट स्तंभ नहीं है:

Person
-------
PersonID
Name
etc.
DefaultAddressID (fk to addressID)

Address
--------
AddressID
Street
City, State, Zip, etc.

आप DefaultAddressID को अशक्त बना सकते हैं, लेकिन इस तरह से संरचना आपके अवरोध को लागू करती है।


12

माई एसक्यूएल:

create table foo(bar serial, chk boolean unique);
insert into foo(chk) values(null);
insert into foo(chk) values(null);
insert into foo(chk) values(false);
insert into foo(chk) values(true);

select * from foo;
+-----+------+
| bar | chk  |
+-----+------+
|   1 | NULL |
|   2 | NULL |
|   3 |    0 |
|   4 |    1 |
+-----+------+

insert into foo(chk) values(true);
ERROR 1062 (23000): Duplicate entry '1' for key 2
insert into foo(chk) values(false);
ERROR 1062 (23000): Duplicate entry '0' for key 2

MySQL में चेक की कमी को नजरअंदाज कर दिया जाता है, इसलिए हमें विचार करना चाहिए nullया falseगलत और trueसत्य के रूप में। अधिकतम 1 पंक्ति में हो सकता हैchk=true

चेक इंसट्रक्शन की कमी के लिए आपको वर्कअराउंड के रूप में इन्सर्ट / अपडेट falseमें बदलाव के लिए ट्रिगर जोड़ने में सुधार पर विचार हो सकता है true- IMO हालांकि यह एक सुधार नहीं है।

मुझे उम्मीद है कि यह चार (0) का उपयोग करने में सक्षम होगा क्योंकि यह

यह भी काफी अच्छा है जब आपको एक ऐसे स्तंभ की आवश्यकता होती है जो केवल दो मान ले सकता है: एक स्तंभ जिसे CHAR के रूप में परिभाषित किया गया है (0) NULL केवल एक बिट पर कब्जा करता है और केवल NULL और 'मान ले सकता है

दुर्भाग्य से, कम से कम MyISAM और InnoDB के साथ, मुझे मिलता है

ERROR 1167 (42000): The used storage engine can't index column 'chk'

--edit

यह MySQL के बाद से एक अच्छा समाधान नहीं है, के booleanलिए एक पर्याय हैtinyint(1) , और इसलिए 0 या 1 से गैर-शून्य मानों की अनुमति देता है। यह संभव है कि bitएक बेहतर विकल्प होगा


यह मेरी टिप्पणी RolandoMySQLDBA के जवाब का जवाब दे सकता है: क्या हम DRI के साथ MySQL समाधान कर सकते हैं?
gbn

यह की वजह से हालांकि एक सा बदसूरत है null, false, true- मैं आश्चर्य करते हैं कि वहाँ कुछ neater है ...
जैक डगलस

@ Jack - +1 MySQL में शुद्ध DRI दृष्टिकोण पर अच्छी कोशिश के लिए।
रोलैंडमाइसीडीडीबीए

मैं यहाँ झूठ के प्रयोग से बचने की सलाह दूंगा क्योंकि अद्वितीय बाधा केवल एक ऐसे झूठे मूल्य की आपूर्ति करने की अनुमति देगी। यदि अशक्त झूठ का प्रतिनिधित्व करता है, तो इसका लगातार उपयोग किया जाना चाहिए - यदि अतिरिक्त सत्यापन उपलब्ध है (जैसे JSR-303 / हाइबरनेट-सत्यापनकर्ता) तो झूठ का परिहार लागू किया जा सकता है।
स्टीव चेम्बर्स

1
MySQL / MariaDB के हाल के संस्करणों ने आभासी कॉलमों को लागू किया है, जो मेरा मानना ​​है कि dba.stackexchange.com/a/144847/94908
MattW

10

एस क्यू एल सर्वर:

यह कैसे करना है:

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

  2. अद्वितीयता के साथ संगणित स्तंभ। DRI का उपयोग
    जैक डगलस के उत्तर को देखें। SQL सर्वर 2005 और उससे पहले

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

  4. उत्प्रेरक। कोड का उपयोग करता है, डीआरआई का नहीं।
    सभी संस्करण

यह कैसे न करें:

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

10

PostgreSQL:

create table foo(bar serial, chk char(1) unique check(chk='Y'));
insert into foo default values;
insert into foo default values;
insert into foo(chk) values('Y');

select * from foo;
 bar | chk
-----+-----
   1 |
   2 |
   3 | Y

insert into foo(chk) values('Y');
ERROR:  duplicate key value violates unique constraint "foo_chk_key"

--edit

या (बहुत बेहतर), एक अद्वितीय आंशिक सूचकांक का उपयोग करें :

create table foo(bar serial, chk boolean not null default false);
create unique index foo_i on foo(chk) where chk;
insert into foo default values;
insert into foo default values;
insert into foo(chk) values(true);

select * from foo;
 bar | chk
-----+-----
   1 | f
   2 | f
   3 | t
(3 rows)

insert into foo(chk) values(true);
ERROR:  duplicate key value violates unique constraint "foo_i"

6

इस तरह की समस्या एक और कारण है जिसके कारण मैंने यह सवाल पूछा:

डेटाबेस में अनुप्रयोग सेटिंग्स

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


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

+1 लेकिन ध्यान दें कि "संपूर्ण स्तंभ" आपके RDBMS के आधार पर किसी भी भौतिक स्थान का उपयोग नहीं कर सकता है :)
जैक डगलस

6

व्यापक रूप से कार्यान्वित प्रौद्योगिकियों का उपयोग करके संभव दृष्टिकोण:

1) मेज पर 'लेखक' विशेषाधिकार रद्द करें। सीआरयूडी प्रक्रियाएं बनाएं जो यह सुनिश्चित करती हैं कि लेन-देन सीमाओं में बाधा को लागू किया गया है।

2) 6NF: CHAR(1)कॉलम ड्रॉप करें। अपनी कार्डिनैलिटी एक से अधिक नहीं हो सकती है, यह सुनिश्चित करने के लिए विवश तालिका जोड़ें

alter table foo ADD UNIQUE (bar);

create table foo_Y
(
 x CHAR(1) DEFAULT 'x' NOT NULL UNIQUE CHECK (x = 'x'), 
 bar int references foo (bar)
);

एप्लिकेशन शब्दार्थों को बदलें ताकि माना गया कि 'डिफ़ॉल्ट' नई तालिका में पंक्ति है। संभवतः इस तर्क को समझने के लिए विचारों का उपयोग करें।

3) CHAR(1)कॉलम ड्रॉप करें। seqपूर्णांक कॉलम जोड़ें । एक अनोखी बाधा डालते हैं seq। एप्लिकेशन शब्दार्थों को बदलें ताकि माना गया 'डिफ़ॉल्ट' वह पंक्ति हो जहां seqमूल्य एक या seqसबसे बड़ा / सबसे छोटा मान या समान मान हो। संभवतः इस तर्क को समझने के लिए विचारों का उपयोग करें।


5

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
क्या MySQL के लिए कोई DRI आधारित समाधान नहीं है? केवल कोड? मैं उत्सुक हूं क्योंकि मैं MySQL का अधिक से अधिक उपयोग करना शुरू कर रहा हूं ...
gbn

4

SQL सर्वर 2000 में और आप जटिल दृश्यों (या मल्टी-टेबल) को लागू करने के लिए अनुक्रमित दृश्यों का उपयोग कर सकते हैं जैसे आप पूछ रहे हैं।
इसके अलावा ओरेकल के पास स्थगित चेक बाधाओं के साथ भौतिक विचारों के लिए समान कार्यान्वयन है।

मेरी पोस्ट यहाँ देखें ।


क्या आप इस उत्तर में थोड़ा और "मांस" प्रदान कर सकते हैं, जैसे कोड का एक छोटा स्निपेट? अभी यह सामान्य विचारों और कड़ियों की एक जोड़ी मात्र है।
निक चम्मास

यहां एक उदाहरण फिट करना थोड़ा मुश्किल होगा। यदि आप उस लिंक पर क्लिक करते हैं, तो आपको "मांस" मिलेगा, जिसकी आप तलाश कर रहे हैं।
स्पेगेटीडब्बा

3

मानक संक्रमणकालीन एसक्यूएल -92, व्यापक रूप से कार्यान्वित उदाहरण के लिए एसक्यूएल सर्वर 2000 और इसके बाद के संस्करण:

तालिका से 'लेखक' विशेषाधिकार रद्द करें। सहित WHERE chk = 'Y'और WHERE chk = 'N'क्रमशः के लिए दो विचार बनाएँ WITH CHECK OPTION। के लिएWHERE chk = 'Y' देखने के लिए, प्रभाव है कि उसकी गणनसंख्या एक अधिक नहीं हो सकता करने के लिए एक खोज हालत शामिल हैं। विचारों पर अनुदान 'लेखक' विशेषाधिकार।

विचारों के लिए उदाहरण कोड:

CREATE VIEW foo_chk_N
AS
SELECT *
  FROM foo AS f1
 WHERE chk = 'N' 
WITH CHECK OPTION

CREATE VIEW foo_chk_Y
AS
SELECT *
  FROM foo AS f1
 WHERE chk = 'Y' 
       AND 1 >= (
                 SELECT COUNT(*)
                   FROM foo AS f2
                  WHERE f2.chk = 'Y'
                )
WITH CHECK OPTION

भले ही आपका RDBMS इस बात का समर्थन करता है, लेकिन यह पागलों की तरह अनुक्रमित होगा, यदि आपके पास एक से अधिक उपयोगकर्ता हैं, तो आपको एक समस्या हो सकती है
जैक डगलस

यदि कई उपयोगकर्ता एक साथ संशोधन कर रहे हैं तो उन्हें लाइन में खड़ा होना होगा (क्रमबद्ध) - कभी-कभी यह ठीक है, अक्सर यह नहीं होता है (भारी ओएलटीपी या लंबे लेनदेन के बारे में सोचें)।
जैक डगलस

3
स्पष्टीकरण देने के लिए धन्यवाद। मुझे यह कहना चाहिए कि यदि कई उपयोगकर्ता अक्सर एकमात्र डिफ़ॉल्ट पंक्ति सेट कर रहे हैं तो डिज़ाइन विकल्प (उसी तालिका में ध्वज स्तंभ) संदिग्ध है।
onedaywhen

3

यहां MySQL और MariaDB के लिए वर्चुअल कॉलम का उपयोग करके समाधान किया गया है जो थोड़ा अधिक सुरुचिपूर्ण है। इसके लिए MySQL> = 5.7.6 या MariaDB> = 5.2 की आवश्यकता है:

MariaDB [db]> create table foo(bar varchar(255), chk boolean);

MariaDB [db]> describe foo;
+-------+--------------+------+-----+---------+-------+
| Field | Type         | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| bar   | varchar(255) | YES  |     | NULL    |       |
| chk   | tinyint(1)   | YES  |     | NULL    |       |
+-------+--------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

एक वर्चुअल कॉलम बनाएँ जो NULL है यदि आप यूनीक कॉन्ट्रिंट को लागू नहीं करना चाहते हैं:

MariaDB [db]> ALTER table foo ADD checked_bar varchar(255) as (IF(chk, bar, null)) PERSISTENT UNIQUE;

(MySQL के लिए, STOREDइसके बजाय का उपयोग करें PERSISTENT।)

MariaDB [db]> insert into foo(bar, chk) values('a', false);
Query OK, 1 row affected (0.00 sec)

MariaDB [db]> insert into foo(bar, chk) values('a', false);
Query OK, 1 row affected (0.01 sec)

MariaDB [salt_dev]> insert into foo(bar, chk) values('a', false);
Query OK, 1 row affected (0.00 sec)

MariaDB [db]> insert into foo(bar, chk) values('a', true);
Query OK, 1 row affected (0.00 sec)

MariaDB [db]> insert into foo(bar, chk) values('a', true);
ERROR 1062 (23000): Duplicate entry 'a' for key 'checked_bar'

MariaDB [db]> insert into foo(bar, chk) values('b', true);
Query OK, 1 row affected (0.00 sec)

MariaDB [db]> select * from foo;
+------+------+-------------+
| bar  | chk  | checked_bar |
+------+------+-------------+
| a    |    0 | NULL        |
| a    |    0 | NULL        |
| a    |    0 | NULL        |
| a    |    1 | a           |
| b    |    1 | b           |
+------+------+-------------+

1

मानक पूर्ण एसक्यूएल -92: एक CHECKबाधा में एक उपकेंद्र का उपयोग करें , व्यापक रूप से कार्यान्वित नहीं किया जाता है जैसे एएनएसआई -92 क्वेरी मोड में Access2000 (ACE2007, जेट 4.0, जो भी हो) और इसके बाद के संस्करण में समर्थित है।

उदाहरण कोड: CHECKप्रवेश में नोट की कमी हमेशा तालिका स्तर होती है। क्योंकि CREATE TABLEप्रश्न में कथन एक पंक्ति-स्तरीय CHECKबाधा का उपयोग करता है , इसे अल्पविराम जोड़कर थोड़ा संशोधित करने की आवश्यकता है:

create table foo(bar int identity, chk char(1), check (chk in('Y', 'N')));

ALTER TABLE foo ADD 
   CHECK (1 >= (
                SELECT COUNT(*) 
                  FROM foo AS f2 
                 WHERE f2.chk = 'Y'
               ));

1
किसी भी RDBMS में जो मैंने उपयोग किया है, अच्छा नहीं है ... caveats abound
जैक डगलस

0

मैंने केवल उत्तरों के माध्यम से स्किम किया, इसलिए शायद मैं भी ऐसा ही उत्तर देने से चूक गया। विचार एक उत्पन्न स्तंभ का उपयोग करना है जो या तो pk है या एक स्थिरांक है जो pk के मान के रूप में मौजूद नहीं है

create table foo 
(  bar int not null primary key
,  chk char(1) check (chk in('Y', 'N'))
,  some_name generated always as ( case when chk = 'N' 
                                        then bar 
                                        else -1 
                                   end )
, unique (somename)
);

AFAIK यह SQL2003 में मान्य है (जब से आप एक अज्ञेय समाधान की तलाश में हैं)। DB2 इसे अनुमति देता है, न कि कितने अन्य विक्रेता इसे स्वीकार करते हैं।

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