Accessデータベースエンジン(ACE、Jetなど)は制約のサブクエリをサポートしますが、エントリレベルの標準SQL-92をサポートしておらず、Accessの制約はMSとAccessチームによってほとんど文書化されていないため、 SQLDBMSCHECK
と呼ぶことを躊躇します。CHECK
たとえば、CHECK
影響を受ける行ごとにアクセス制約がチェックされることを示すことができます(SQL-92では、SQLステートメントごとにチェックする必要があると指定されています)が、これがバグなのか、参照するドキュメントがないためにわからない機能なのかに。
これは、サブクエリを構成するCHECK制約の非常に単純な例です。フルSQL-92に準拠しており、Accessで適切に機能します。アイデアは、テーブルを最大2行に制限することです(次のSQL DDLにはANSI-92クエリモードが必要です。たとえば、などのADO接続を使用しますAccess.CurrentProject.Connection
)。
CREATE TABLE T1
(
c INTEGER NOT NULL UNIQUE
);
ALTER TABLE T1 ADD
CONSTRAINT max_two_rows
CHECK (
NOT EXISTS (
SELECT 1
FROM T1 AS T
HAVING COUNT(*) > 2
)
);
ただし、これはSQL-92である別の例であり、Accessで作成できます(一部の有効なCHECK
sはAccessで失敗し、マシンを再起動する必要がある恐ろしいクラッシュが発生します:(しかし正しく機能しません。テーブルに正確に2行を許可します(またはゼロ行:空のテーブルに対して制約はテストされません):
CREATE TABLE T2
(
c INTEGER NOT NULL UNIQUE
);
ALTER TABLE T2 ADD
CONSTRAINT exactly_two_rows
CHECK (
NOT EXISTS (
SELECT 1
FROM T2 AS T
HAVING COUNT(*) <> 2
)
);
同じステートメントに2つの行を挿入しようとします(例:テーブルT1
に少なくとも1つの行があると仮定):
SELECT DT1.c
FROM (
SELECT DISTINCT 1 AS c
FROM T1
UNION ALL
SELECT DISTINCT 2
FROM T1
) AS DT1;
ただし、これによりCHECK
噛み付きます。これ(およびさらなるテスト)CHECK
は、各行がテーブルに追加された後にがテストされることを意味しますが、SQL-92は、制約がSQLステートメントレベルでテストされることを指定します。
CHECK
Access2010までトリガー機能がなく、頻繁に使用される特定のテーブルには真のキーがないことを考えると、Accessに本当にテーブルレベルの制約があることはそれほど驚くべきことではありません(たとえば、'シーケンス'有効な状態の時間テーブルのキー)。Access2010トリガーには、ステートメントレベルではなく、行レベルでテストされるのと同じバグ/機能があります。
以下は、上記の2つのシナリオを再現するためのVBAです。コピーしてVBA/VB6標準の.basモジュールに貼り付けます(Excelを使用するなど)。参照は必要ありません。tempフォルダーに新しい.mdbを作成し、制約が機能する/機能しないことを示すテーブル、データ、およびテストを作成します(ヒント:ブレークポイントを設定し、コードをステップ実行し、コメントを読みます)。
Sub AccessCheckSubqueryButProblem()
On Error Resume Next
Kill Environ$("temp") & "\DropMe.mdb"
On Error GoTo 0
Dim cat
Set cat = CreateObject("ADOX.Catalog")
With cat
.Create _
"Provider=Microsoft.Jet.OLEDB.4.0;" & _
"Data Source=" & _
Environ$("temp") & "\DropMe.mdb"
With .ActiveConnection
Dim Sql As String
Sql = _
"CREATE TABLE T1 " & vbCr & _
"( " & vbCr & _
" c INTEGER NOT NULL UNIQUE " & vbCr & _
");"
.Execute Sql
Sql = _
"ALTER TABLE T1 ADD " & vbCr & _
" CONSTRAINT max_two_rows " & vbCr & _
" CHECK ( " & vbCr & _
" NOT EXISTS ( " & vbCr & _
" SELECT 1 " & vbCr & _
" FROM T1 AS T " & vbCr & _
" HAVING COUNT(*) > 2 " & vbCr & _
" ) " & vbCr & _
" );"
.Execute Sql
Sql = _
"INSERT INTO T1 (c) VALUES (1);"
.Execute Sql
Sql = _
"INSERT INTO T1 (c) VALUES (2);"
.Execute Sql
' The third row should (and does)
' cause the CHECK to bite
On Error Resume Next
Sql = _
"INSERT INTO T1 (c) VALUES (3);"
.Execute Sql
MsgBox Err.Description
On Error GoTo 0
Sql = _
"CREATE TABLE T2 " & vbCr & _
"( " & vbCr & _
" c INTEGER NOT NULL UNIQUE " & vbCr & _
");"
.Execute Sql
Sql = _
"ALTER TABLE T2 ADD " & vbCr & _
" CONSTRAINT exactly_two_rows " & vbCr & _
" CHECK ( " & vbCr & _
" NOT EXISTS ( " & vbCr & _
" SELECT 1 " & vbCr & _
" FROM T2 AS T " & vbCr & _
" HAVING COUNT(*) <> 2 " & vbCr & _
" ) " & vbCr & _
" );"
.Execute Sql
' INSERTing two rows in the same SQL statement
' should succeed according to SQL-92
' but fails (and we have no docs from MS
' to indicate whether this is a bug/feature)
On Error Resume Next
Sql = _
"INSERT INTO T2 " & vbCr & _
" SELECT c " & vbCr & _
" FROM T1;"
.Execute Sql
MsgBox Err.Description
On Error GoTo 0
End With
Set .ActiveConnection = Nothing
End With
End Sub