有啥方法可以获取由于执行单个语句而失败的特定外键约束?

Posted

技术标签:

【中文标题】有啥方法可以获取由于执行单个语句而失败的特定外键约束?【英文标题】:Any way to get the specific foreign key constraint that is failing as a result of executing a single statement?有什么方法可以获取由于执行单个语句而失败的特定外键约束? 【发布时间】:2021-11-28 22:12:47 【问题描述】:

我使用 SQLite 作为 ios 应用程序的后端。我面临一个错误,由于外键约束失败,特定的删除操作没有执行。尽管将罪魁祸首缩小到四个不同的外键约束,我仍然不确定它是哪一个。 SQLite 有没有办法告诉我是哪个特定的外键约束导致了错误?

【问题讨论】:

SQLite 不提供该信息。另请阅读:***.com/questions/37904233/… 和 ***.com/questions/5208245/… @forpas 似乎对 android 过于具体。他们没有提到任何在 iOS / 使用 Xcode 上获取相同信息的方法 它们也特定于 SQLite 的文档,其中提到此信息不提供给客户端应用程序。 @forpas 我检查了 SQLite 是否在客户端应用程序之外提供此信息——例如使用 CLI 与 SQLite 数据库交互。但我没有看到任何证实或否认它的东西。你知道吗?如果是这样,我可以从模拟器中复制数据库并在终端中与之交互 阅读我的答案,我描述了一种识别违反外键约束的方法。 【参考方案1】:

您始终可以使用查询来预先检查和确定冲突。类似于:-

CREATE TABLE IF NOT EXISTS parent (id INTEGER PRIMARY KEY,name TEXT);
INSERT INTO parent (name) VALUES ('parent1'),('parent2'),('parent3'),('parent4');

/* This being the pre-check query */
SELECT 
    2 IN (SELECT id FROM parent) AS FK1CHECK, 
    3 IN (SELECT id FROM parent) AS FK2CHECK, 
    10 IN (SELECT id FROM parent) AS FK3CHECK; 

其中 2(存在)、3(存在)和 10(将​​是 FK 冲突)

显然,您将绑定要检查的 3 个参数/值,因此查询将是:-

SELECT 
    ? IN (SELECT id FROM parent) AS FK1CHECK, 
    ? IN (SELECT id FROM parent) AS FK2CHECK, 
    ? IN (SELECT id FROM parent) AS FK3CHECK;

为简单起见,只有 1 个父表,但会根据情况进行调整

结果:-

1   1   0

即F3CHECK 为 0,表示会导致 FK 冲突

附加

关于评论:-

我明白为什么第三个查询会失败,但我不明白为什么它会是外键约束失败。它只是一条不存在的记录,您还没有在这里定义任何外键约束。所以无论如何都不应该有任何外键约束失败。努力想知道这与我的问题有什么关系

没有 3 个查询,它是通过 3 个子查询输出 3 个列的单个查询,每个子查询都指示关系是否有效。也就是说,它实际上是在做 FK 约束会做的事情。

当您正在努力理解时,下面将逐步演示:-

/* drop the demo tables if they already exist */
DROP TABLE IF EXISTS child1;
DROP TABLE IF EXISTS child2;
DROP TABLE IF EXISTS child3;
DROP TABLE IF EXISTS parent;
/* turn on Foreign Key Support */
PRAGMA foreign_keys = ON;
/* Show the Foreign Key Support status */
PRAGMA foreign_keys;

/* create the parent table which will be a parent to 3 child tables */
CREATE TABLE IF NOT EXISTS parent (id INTEGER PRIMARY KEY, name TEXT);
/* create the 3 child tables with a Foreign Key constraint */
CREATE TABLE IF NOT EXISTS child1 (id INTEGER PRIMARY KEY, parentid INTEGER REFERENCES parent(id));
CREATE TABLE IF NOT EXISTS child2 (id INTEGER PRIMARY KEY, parentid INTEGER REFERENCES parent(id));
CREATE TABLE IF NOT EXISTS child3 (id INTEGER PRIMARY KEY, parentid INTEGER REFERENCES parent(id));

/* Add some parent rows */
INSERT INTO parent VALUES (1,'P1'),(2,'P2'),(3,'P3');

/* Attempt to insert three rows 
    child1 row with relationship to parent P1 (i.e. parentid = 1), 
    child2 with relationship to P2 (i.e. parentid = 2) and
    child3 with relationship to P3 (i.e. parentid = 3)
    but first PRE CHECK
*/
/* THE PRECHECK */
SELECT (SELECT 1 /*parentid value for child1 insert*/ IN (SELECT id FROM parent)) AS check1,
    (SELECT 2 /* parentid value for child2 insert */ IN (SELECT id FROM parent)) AS check2,
    (SELECT 3 /* parentid value for child3 insert */ IN (SELECT id FROM parent)) AS check3
;
/* This results in 1,1,1 i.e. ok to insert all three*/ :-


/* The the inserts */
INSERT INTO child1 (parentid) VALUES(1 /* parentid value for child1 insert*/);
INSERT INTO child2 (parentid) VALUES(2 /* parentid value for child2 insert*/);
INSERT INTO child3 (parentid) VALUES(3 /* parentid value for child3 insert*/);

/* Show the resultant data */
SELECT * FROM parent 
    LEFT JOIN child1 ON child1.parentid = parent.id
    LEFT JOIN child2 ON child2.parentid = parent.id
    LEFT JOIN child3 ON child3.parentid = parent.id
;

/* Attempt to insert three rows 
    child1 row with relationship to parent P2 (i.e. parentid = 1), 
    child2 with relationship to P3 (i.e. parentid = 2) and
    child3 with relationship to non-existent (i.e. parentid = 3)
    but first PRE CHECK
*/
SELECT (SELECT 2 /*parentid value for child1 insert*/ IN (SELECT id FROM parent)) AS check1,
    (SELECT 3 /* parentid value for child2 insert */ IN (SELECT id FROM parent)) AS check2,
    (SELECT 10 /* parentid value for child3 insert */ IN (SELECT id FROM parent)) AS check3 
;
/* result will be 1,1,0 i.e. the 3rd insert will fail */

INSERT INTO child1 (parentid) VALUES(2 /* parentid value for child1 insert*/);
INSERT INTO child2 (parentid) VALUES(3 /* parentid value for child2 insert*/);
INSERT INTO child3 (parentid) VALUES(10 /* parentid value for child3 insert*/);

运行时,第一个结果来自 PRAGMA foreign_keys :-

即外键支持为真1,所以外键支持开启

第二个结果是预检查:-

因为 check1 为 1,那么第一次插入不会导致外键冲突/失败。 因为 check2 为 1,那么第二次插入不会导致外键冲突/失败 因为 check3 为 1,则第三次插入不会导致外键冲突/失败

插入后的第三个结果是查询:-

即已插入相应的 child1/2/3 行。

第四个/最后一个是第二组插入的 PRE-CHECK:-

正如突出显示的那样,check3 表明如果插入继续进行,将发生外键冲突/失败。结果可用于确定后续逻辑并确定哪些外键会导致冲突。

最后因为上面是使用 SQLite 工具 (Navicat) 进行的,然后尝试插入,消息是:-

INSERT INTO child1 (parentid) VALUES(2 /* parentid value for child1 insert*/)
> Affected rows: 1
> Time: 0.083s


INSERT INTO child2 (parentid) VALUES(3 /* parentid value for child2 insert*/)
> Affected rows: 1
> Time: 0.074s


INSERT INTO child3 (parentid) VALUES(10 /* parentid value for child3 insert*/)
> FOREIGN KEY constraint failed
> Time: 0s

即正如 PRE-CHECK(check3 为 0)所确定的那样,插入 child3 的尝试失败并出现预期结果。

当然,以上内容必须量身定制。如果提供更具体的细节,本可以给出更具体的答案。

【讨论】:

我明白为什么第三个查询会失败,但我不明白为什么它会是外键约束的失败。它只是一条不存在的记录,您还没有在这里定义任何外键约束。所以无论如何都不应该有任何外键约束失败。努力想知道这与我的问题有何关系【参考方案2】:

据我所知,无法直接从 SQLite 获取引发错误的特定外键约束。

出于调试目的,您可以通过运行以下命令来关闭外键约束检查(默认情况下关闭):

PRAGMA foreign_keys = OFF;

然后运行您的代码并插入违反约束的行(您不会收到任何错误)。

之后,运行:

SELECT * FROM pragma_foreign_key_check();

您将获得一个包含 4 列的结果集:tablerowidparentfkid,用于数据库中违反外键约束的任何表的行。

检查我在类似问题中给出的documentation for pragma_foreign_key_check() 和answer。

【讨论】:

以上是关于有啥方法可以获取由于执行单个语句而失败的特定外键约束?的主要内容,如果未能解决你的问题,请参考以下文章

sqlserver外键关系有啥用?

oracle11g中有执行语句和执行脚本,有啥区别?

有啥方法可以分块而不是完整地划分和运行 testng 测试

存储过程和sql语句有啥区别

有啥方法可以在 listView 中获取特定视图?

如何在单个语句中执行选择?