たとえば、次のような表の場合:
create table foo(bar int identity, chk char(1) check (chk in('Y', 'N')));
フラグがa char(1)
、a bit
または何として実装されているかは問題ではありません。単一の行にのみ設定できるという制約を強制できるようにしたいだけです。
たとえば、次のような表の場合:
create table foo(bar int identity, chk char(1) check (chk in('Y', 'N')));
フラグがa char(1)
、a bit
または何として実装されているかは問題ではありません。単一の行にのみ設定できるという制約を強制できるようにしたいだけです。
回答:
SQL Server 2008-フィルター処理された一意のインデックス
CREATE UNIQUE INDEX IX_Foo_chk ON dbo.Foo(chk) WHERE chk = 'Y'
SQL Server 2000、2005:
一意のインデックスでは1つの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に感謝します)
Oracle:
Oracleでは、すべての索引付けされた列がNULLのエントリは索引付けされないため、関数ベースの一意索引を使用できます。
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);
このインデックスは、最大で1つの行のみをインデックス化します。
このインデックスの事実を知って、ビット列をわずかに異なる方法で実装することもできます。
create table foo(bar integer, chk char(1) check (chk ='Y') UNIQUE);
ここでは、列に指定できる値はchk
なりますY
とNULL
。最大で1行のみが値を持つことができますY.
not null
制約が必要ですか?
not null
が必要ない場合は、制約を追加できます(質問の仕様からは明らかではありませんでした)。いずれの場合も、値「Y」を持つことができるのは1行のみです。
default
)?
Y
かnull
であることに同意する場合、さらに簡単な可能性があることを認識させてくれました。私の更新を参照してください。
null
かもしれないいくつかの明確さを犠牲に- sがスキップされる
これは、データベーステーブルを正しく構成する場合だと思います。もっと具体的に言うと、複数のアドレスを持つ人がいて、そのうちの1人をデフォルトにしたい場合、デフォルトのアドレスのaddressIDをアドレステーブルに保存するのではなく、個人のテーブルに保存する必要があると思います。
Person
-------
PersonID
Name
etc.
DefaultAddressID (fk to addressID)
Address
--------
AddressID
Street
City, State, Zip, etc.
DefaultAddressIDをNULL可能にすることもできますが、この方法では構造が制約を強制します。
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ではチェック制約は無視されるため、false null
またはtrue false
として検討する必要がありtrue
ます。最大で1行が持つことができますchk=true
あなたはそれの変化にトリガーを追加するための改善を検討するfalse
にtrue
チェック制約の欠如のための回避策として、挿入/更新に- IMOそれはしかし改善ではありません。
char(0)を使用できるようにしたかったのは、
また、2つの値のみを取ることができる列が必要な場合にも非常に便利です。CHAR(0)NULLとして定義されている列は、1ビットのみを占有し、値NULLおよび ''のみを取ることができます
残念ながら、少なくともMyISAMとInnoDBでは、
ERROR 1167 (42000): The used storage engine can't index column 'chk'
-編集
これは、MySQL上の以降のすべての後に良い解決策ではない、boolean
の同義語でtinyint(1)
あり、そのことは可能である0または1以上の非NULL値ができbit
、より良い選択であると
null
、もっときれいなものがあるのかな?false
true
SQLサーバー:
どうやってするの:
最適な方法は、フィルター選択されたインデックスです。DRI
SQL Server 2008+を使用
一意性を持つ計算列。DRIの使用
Jack Douglasの回答を参照してください。SQL Server 2005以前
フィルター選択されたインデックスのようなインデックス付き/マテリアライズドビュー。DRI
すべてのバージョンを使用します。
引き金。DRIではなくコードを使用します。
すべてのバージョン
それをしない方法:
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"
広く実装されている技術を使用した可能なアプローチ:
1)テーブルの「ライター」権限を取り消します。トランザクション境界で制約が適用されることを保証するCRUDプロシージャを作成します。
2)6NF:CHAR(1)
列をドロップします。基数が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
値が1 である行、またはseq
値が最大/最小値などの行になるように、アプリケーションのセマンティクスを変更します。おそらくビューを使用して、このロジックをカプセル化します。
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;
試してみる !!!
SQL Server 2000以降など、広く実装されている標準の移行SQL-92:
テーブルから「ライター」権限を取り消します。WHERE chk = 'Y'
とをWHERE chk = 'N'
それぞれ含む2つのビューを作成しますWITH CHECK OPTION
。WHERE chk = 'Y'
ビュー、そのカーディナリティが1を超えることができない旨の検索条件が含まれます。ビューに対する「ライター」権限を付与します。
ビューのサンプルコード:
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
これは、もう少しエレガントな仮想列を使用した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 |
+------+------+-------------+
標準FULL SQL-92:CHECK
制約でサブクエリを使用します。たとえば、Access2000(ACE2007、Jet 4.0など)およびANSI-92クエリモードの場合は上記でサポートされるなど、広く実装されていません。
サンプルコード: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'
));
私は答えをざっと読んだだけなので、同様の答えを逃したかもしれません。アイデアは、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)
);
知る限り、これはSQL2003で有効です(どこにとらわれないソリューションを探しているのか)。DB2はそれを許可しますが、それを受け入れる他のベンダーの数はわかりません。