วิธีการติดตั้งแฟล็ก 'เริ่มต้น' ที่สามารถตั้งค่าได้ในแถวเดียวเท่านั้น


31

ตัวอย่างเช่นมีตารางที่คล้ายกับสิ่งนี้:

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

มันไม่สำคัญว่าจะมีการนำธงไปใช้เป็นchar(1)a bitหรืออะไรก็ตาม ฉันแค่ต้องการให้สามารถบังคับใช้ข้อ จำกัด ที่สามารถตั้งค่าได้ในแถวเดียวเท่านั้น


ได้รับแรงบันดาลใจจากคำถามนี้ซึ่ง จำกัด เฉพาะ MySQL
Jack Douglas

2
วิธีถามคำถามแสดงให้เห็นว่าการใช้ตารางจะต้องเป็นคำตอบที่ผิด แต่บางครั้งการเพิ่มตารางอื่นเป็นความคิดที่ดี และการเพิ่มตารางเป็นฐานข้อมูลที่ไม่เชื่อเรื่องพระเจ้าอย่างสมบูรณ์
Mike Sherrill 'Cat Recall'

คำตอบ:



16

SQL Server 2000, 2005:

คุณสามารถใช้ประโยชน์จากความจริงที่ว่าอนุญาตให้มีเพียงหนึ่ง null ในดัชนีที่ไม่ซ้ำกัน:

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(ขอบคุณ @gbn สำหรับข้อมูลนี้)


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ข้อ จำกัด หากคุณไม่ต้องการให้มีค่า Null (มันไม่ชัดเจนสำหรับฉันจากข้อกำหนดของคำถาม) มีเพียงแถวเดียวเท่านั้นที่สามารถมีค่า 'Y' ได้ทุกกรณี
Vincent Malgrat

+1 ฉันเห็นสิ่งที่คุณหมายถึง - คุณถูกต้องไม่จำเป็น (แต่อาจจะเป็นผู้ชนะเล็กน้อยโดยเฉพาะอย่างยิ่งถ้ารวมกับdefault)
แจ็คดักลาส

2
@jack: คำพูดของคุณทำให้ฉันรู้ว่ามีความเป็นไปได้ที่ง่ายกว่านี้ถ้าคุณยอมรับว่าคอลัมน์นั้นอาจเป็นได้Yหรือnullดูการอัพเดทของฉัน
Vincent Malgrat

1
ตัวเลือกที่ 2 มีประโยชน์เพิ่มเติมดัชนีจะเล็กตามnullที่ถูกข้ามไป - อาจมีค่าใช้จ่ายของความชัดเจนบางอย่าง
แจ็คดักลาส

13

ฉันคิดว่านี่เป็นกรณีของการสร้างตารางฐานข้อมูลของคุณอย่างถูกต้อง หากต้องการให้เป็นรูปธรรมมากขึ้นหากคุณมีบุคคลที่มีหลายที่อยู่และคุณต้องการให้เป็นค่าเริ่มต้นฉันคิดว่าคุณควรเก็บ addressID ของที่อยู่เริ่มต้นในตารางบุคคลไม่มีคอลัมน์เริ่มต้นในตารางที่อยู่:

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

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

คุณสามารถทำให้ DefaultAddressID เป็นโมฆะ แต่วิธีนี้โครงสร้างบังคับข้อ จำกัด ของคุณ


12

MySQL:

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: เราสามารถใช้โซลูชัน MySQL กับ DRI ได้หรือไม่
GBN

เป็นบิตน่าเกลียด แต่เพราะของnull, false, true- ฉันจะสงสัยว่ามีอะไรบางอย่าง ... neater
แจ็คดักลาส

@Jack - +1 สำหรับการลองใช้วิธี DRI ที่บริสุทธิ์ใน MySQL
RolandoMySQLDBA

ฉันจะแนะนำให้หลีกเลี่ยงการใช้เท็จที่นี่เนื่องจากข้อ จำกัด ที่ไม่ซ้ำกันจะช่วยให้ค่าเท็จเช่นเดียวที่จะจัดจำหน่าย หาก null แสดงถึง false ควรใช้อย่างสม่ำเสมอตลอด - การหลีกเลี่ยงความผิดอาจถูกบังคับใช้หากมีการตรวจสอบเพิ่มเติม (เช่น JSR-303 / hibernate-validator)
Steve Chambers

1
MySQL / MariaDB เวอร์ชันล่าสุดใช้คอลัมน์เสมือนซึ่งฉันเชื่อว่าอนุญาตให้ใช้โซลูชันที่หรูหรากว่านี้เล็กน้อยที่แสดงไว้ด้านล่างที่dba.stackexchange.com/a/144847/94908
MattW

10

เซิร์ฟเวอร์ SQL:

ทำอย่างไร:

  1. วิธีที่ดีที่สุดคือดัชนีที่ถูกกรอง ใช้ DRI
    SQL Server 2008+

  2. คอลัมน์ที่คำนวณด้วยเอกลักษณ์ ใช้ DRI
    ดูคำตอบของ Jack Douglas SQL Server 2005 และก่อน

  3. มุมมองที่จัดทำดัชนี / ปรากฏให้เห็นซึ่งเป็นเหมือนดัชนีที่กรอง ใช้ DRI
    ทุกรุ่น

  4. ไก ใช้รหัสไม่ใช่ DRI
    ทุกรุ่น

วิธีที่จะไม่ทำ:

  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

ปัญหาแบบนี้เป็นอีกสาเหตุหนึ่งที่ฉันถามคำถามนี้:

การตั้งค่าแอปพลิเคชันในฐานข้อมูล

หากคุณมีตารางการตั้งค่าแอปพลิเคชันในฐานข้อมูลของคุณคุณอาจมีรายการที่จะอ้างอิง ID ของหนึ่งระเบียนที่คุณต้องการได้รับการพิจารณาว่าเป็น 'พิเศษ' จากนั้นคุณจะค้นหาว่า ID คืออะไรจากตารางการตั้งค่าของคุณด้วยวิธีนี้คุณไม่จำเป็นต้องมีทั้งคอลัมน์สำหรับการตั้งค่ารายการเดียว


นี่เป็นข้อเสนอแนะที่ยอดเยี่ยม: มีความสอดคล้องกับการออกแบบที่เป็นมาตรฐานทำงานกับแพลตฟอร์มฐานข้อมูลใด ๆ และใช้งานได้ง่ายที่สุด
Nick Chammas

+1 แต่โปรดทราบว่า "ทั้งคอลัมน์" อาจไม่ใช้พื้นที่ทางกายภาพใด ๆ ทั้งนี้ขึ้นอยู่กับ RDBMS ของคุณ :)
Jack

6

แนวทางที่เป็นไปได้โดยใช้เทคโนโลยีที่นำมาใช้อย่างกว้างขวาง:

1) เพิกถอนสิทธิ์ 'นักเขียน' บนโต๊ะ สร้างขั้นตอน CRUD ที่ทำให้แน่ใจว่ามีการบังคับใช้ข้อ จำกัด ที่ขอบเขตของธุรกรรม

2) 6NF: วางCHAR(1)คอลัมน์ เพิ่มตารางการอ้างอิงที่ จำกัด เพื่อให้แน่ใจว่า cardinality ไม่เกินหนึ่ง:

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
ไม่มี DRI based solution สำหรับ MySQL หรือไม่ รหัสเท่านั้น? ฉันอยากรู้อยากเห็นเพราะฉันเริ่มใช้งาน MySQL มากขึ้นเรื่อย ๆ ...
gbn

4

ใน SQL Server 2000 และใหม่กว่าคุณสามารถใช้มุมมองแบบดัชนีเพื่อใช้ข้อ จำกัด ที่ซับซ้อน (หรือหลายตาราง) เช่นเดียวกับที่คุณขอ
นอกจากนี้ออราเคิลยังมีการใช้งานที่คล้ายกันสำหรับมุมมองที่เป็นรูปธรรมพร้อมข้อ จำกัด การตรวจสอบรอตัดบัญชี

เห็นโพสต์ของฉันที่นี่


คุณช่วยให้ "เนื้อสัตว์" เพิ่มขึ้นอีกเล็กน้อยในคำตอบนี้เช่นรหัสสั้น ๆ หรือไม่? ตอนนี้เป็นเพียงความคิดทั่วไปและลิงก์
Nick Chammas

มันคงเป็นเรื่องยากสักหน่อยที่จะใส่ตัวอย่างที่นี่ หากคุณคลิกที่ลิงก์คุณจะพบ "เนื้อสัตว์" ที่คุณกำลังมองหา
spaghettidba

3

Standard Transitional SQL-92 มีการนำไปใช้อย่างกว้างขวางเช่น SQL Server 2000 และสูงกว่า:

เพิกถอนสิทธิ์ 'นักเขียน' จากตาราง สร้างสองมุมมองสำหรับWHERE chk = 'Y'และตามลำดับรวมทั้งWHERE chk = 'N' WITH CHECK OPTIONสำหรับWHERE chk = 'Y'มุมมองให้รวมเงื่อนไขการค้นหาเข้ากับเอฟเฟกต์ที่ cardinality ไม่เกินหนึ่ง ให้สิทธิ์ 'นักเขียน' ในมุมมอง

โค้ดตัวอย่างสำหรับมุมมอง:

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 ของคุณจะสนับสนุนสิ่งนี้มันจะทำให้เป็นอนุกรมอย่างบ้าคลั่งดังนั้นหากคุณมีผู้ใช้มากกว่าหนึ่งรายคุณอาจมีปัญหา
Jack Douglas

หากผู้ใช้หลายคนกำลังแก้ไขพร้อมกันพวกเขาจะต้องยืนอยู่ในแถว (เป็นอันดับ) - บางครั้งก็โอเคบ่อยครั้งที่มันไม่ได้ (คิดว่า OLTP หนักหรือการทำธุรกรรมนาน)
แจ็คดักลาส

3
ขอบคุณสำหรับการชี้แจง ฉันต้องบอกว่าผู้ใช้หลายคนมักจะตั้งค่าแถวเริ่มต้นเพียงอย่างเดียวแล้วตัวเลือกการออกแบบ (คอลัมน์ธงในตารางเดียวกัน) เป็นที่น่าสงสัย
oneday เมื่อ

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

มาตรฐานเต็มรูปแบบ SQL-92: ใช้แบบสอบถามย่อยในCHECKข้อ จำกัด ไม่ได้ดำเนินการกันอย่างแพร่หลายเช่นการสนับสนุนใน Access2000 (ACE2007, เจ็ต 4.0 อะไรก็ตาม) และสูงกว่าเมื่ออยู่ใน-92 ANSI โหมดแบบสอบถาม

รหัสตัวอย่าง: CHECKข้อ จำกัด ของบันทึกย่อใน Access จะอยู่ในระดับตารางเสมอ เนื่องจาก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 มาก
แจ็คดักลาส

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 อนุญาต, ไม่แน่ใจว่ามีผู้ค้ารายอื่นที่รับได้จำนวนเท่าใด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.