it-swarm.asia

كيفية تطبيق علامة "افتراضية" لا يمكن تعيينها إلا في صف واحد

على سبيل المثال ، مع جدول مشابه لهذا:

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

لا يهم إذا تم تنفيذ العلم على أنه char(1) أو bit أو أيا كان. أريد فقط أن أتمكن من فرض القيد الذي لا يمكن تعيينه إلا على صف واحد.

31
Jack says try topanswers.xyz

SQL Server 2008 - فهرس فريد تمت تصفيته

CREATE UNIQUE INDEX IX_Foo_chk ON dbo.Foo(chk) WHERE chk = 'Y'
31
Mark Storey-Smith

SQL Server 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 (بفضلgbn لهذه المعلومات)

16
Jack says try topanswers.xyz

وحي:

نظرًا لأن 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.

14
Vincent Malgrat

أعتقد أن هذه حالة هيكلة جداول قاعدة البيانات الخاصة بك بشكل صحيح. لجعله أكثر واقعية ، إذا كان لديك شخص بعناوين متعددة وتريد أن يكون العنوان الافتراضي ، أعتقد أنه يجب عليك تخزين معرّف العنوان للعنوان الافتراضي في جدول الشخص ، وليس لديك عمود افتراضي في جدول العناوين:

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

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

يمكنك جعل DefaultAddressID قابلة للإلغاء ، ولكن بهذه الطريقة يفرض الهيكل القيد الخاص بك.

13
Decker97

الخلية:

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 على أنها صحيحة. يمكن أن يحتوي صف واحد على الأكثر على chk=true

قد تعتبر أنه تحسين لإضافة مشغل لتغيير false إلى true عند الإدراج/التحديث كحل بديل لعدم وجود قيود على الفحص - المنظمة البحرية الدولية ليست تحسينًا بالرغم من ذلك.

تمنيت أن أتمكن من استخدام حرف (0) لأنه

هو أيضًا لطيف جدًا عندما تحتاج إلى عمود يمكن أن يأخذ قيمتين فقط: عمود يتم تعريفه على أنه CHAR (0) NULL يشغل بتًا واحدًا فقط ويمكن أن يأخذ فقط القيم NULL و ''

لسوء الحظ ، مع MyISAM و InnoDB على الأقل ، فهمت

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

--تعديل

هذا ليس حلاً جيدًا على كل حال لأنه على MySQL ، boolean هو مرادف لـ tinyint(1) ، وبالتالي يسمح بقيم غير فارغة من 0 أو 1. من الممكن أن يكون bit خيارًا أفضل

12
Jack says try topanswers.xyz

خادم قاعدة البيانات:

كيف افعلها:

  1. أفضل طريقة هي فهرس تمت تصفيته. يستخدم DRI
    SQL Server 2008+

  2. عمود محسوب بتفرد. يستخدم DRI
    انظر إجابة جاك دوغلاس. SQL Server 2005 وما قبله

  3. عرض مفهرس/مادي يشبه الفهرس المفلتر. يستخدم DRI
    جميع الإصدارات.

  4. اثار. يستخدم الرمز وليس DRI.
    جميع الإصدارات

كيف لا تفعل ذلك:

  1. تحقق من القيد باستخدام UDF. هذا ليس آمنًا لعزل التزامن ولقطة.
    انظر واحداثنانثلاثةأربعة
10
gbn

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"

--تعديل

أو (أفضل بكثير) ، استخدم فهرس جزئي فريد :

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"
10
Jack says try topanswers.xyz

الأساليب الممكنة باستخدام التقنيات المنفذة على نطاق واسع:

1) إلغاء امتيازات "الكاتب" على الطاولة. إنشاء إجراءات CRUD التي تضمن تطبيق القيد في حدود المعاملة.

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 أكبر/أصغر قيمة أو ما شابه. ربما استخدام طرق العرض لتغليف هذا المنطق.

6
onedaywhen

هذا النوع من المشاكل هو سبب آخر لماذا سألت هذا السؤال:

إعدادات التطبيق في قاعدة البيانات

إذا كان لديك جدول إعداد تطبيق في قاعدة البيانات الخاصة بك ، فقد يكون لديك إدخال يشير إلى معرف السجل الوحيد الذي تريد اعتباره "خاصًا". ثم ستقوم فقط بالبحث عن المعرف من جدول الإعدادات ، وبهذه الطريقة لا تحتاج إلى عمود كامل لتعيين عنصر واحد فقط.

6
CenterOrbit

بالنسبة لأولئك الذين يستخدمون 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 ;

للتأكد من نظافة جدولك وعمل الإجراء المخزن ، بافتراض أن المعرف 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 ;

للتأكد من أن الجدول نظيف وأن المشغل يعمل ، بافتراض أن المعرف 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;

جربها !!!

5
RolandoMySQLDBA

في SQL Server 2000 والإصدارات الأحدث ، يمكنك استخدام طرق العرض المفهرسة لتطبيق قيود معقدة (أو جداول متعددة) مثل القيود التي تطلبها.
أيضًا لدى أوراكل تنفيذ مماثل لوجهات النظر المادية مع قيود التحقق المؤجلة.

انظر مشاركتي هنا.

4
spaghettidba

معيار SQL-92 الانتقالي ، يتم تطبيقه على نطاق واسع على سبيل المثال SQL Server 2000 وما فوق:

سحب امتيازات "الكاتب" من الجدول. إنشاء طريقتي عرض لـ WHERE chk = 'Y' و WHERE chk = 'N' على التوالي ، بما في ذلك WITH CHECK OPTION. عن WHERE chk = 'Y' view ، ضمّن شرط بحث يفيد بأن أصلها لا يمكن أن يتجاوز أحدها. منح امتيازات "الكاتب" على وجهات النظر.

رمز المثال لطرق العرض:

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
3
onedaywhen

إليك حل لـ 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)

أنشئ عمودًا افتراضيًا فارغًا إذا كنت لا تريد فرض الموانع الفريدة:

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           |
+------+------+-------------+
3
Matthias Winkelmann

معيار FULL SQL-92 القياسي: استخدم استعلامًا فرعيًا في قيد CHECK ، لم يتم تطبيقه على نطاق واسع مثل مدعوم في Access2000 (ACE2007 ، Jet 4.0 ، أيا كان) وما فوق عندما يكون في ANSI-92 Query Mode .

رمز المثال: ملاحظة 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
onedaywhen

لقد تفقدت الإجابات فقط ، لذا ربما فاتني إجابة مماثلة. الفكرة هي استخدام عمود تم إنشاؤه إما 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 بذلك ، ولست متأكدًا من عدد البائعين الآخرين الذين يقبلونها.

0
Lennart