如何在 SQLite3 中获取与给定 table 具有一对一关系的 table 列表?

How to get a list of tables that have one-on-one relationship to a given table in SQLite3?

有没有办法获取与 SQLite3 中的给定 table 具有一对一关系的 table 列表?

例如这里的tableab与tableabcabd都是一对一的关系。 return abcabd 是否有针对给定 table 名称 ab 的一个或多个查询?

-- By default foreign key is diabled in SQLite3
PRAGMA foreign_keys = ON; 

CREATE TABLE a (
    aid          INTEGER      PRIMARY KEY
);

CREATE TABLE b (
    bid          INTEGER      PRIMARY KEY
);

CREATE TABLE ab (
    aid          INTEGER,
    bid          INTEGER,
    PRIMARY KEY (aid, bid)
    FOREIGN KEY (aid)  REFERENCES a(aid)
    FOREIGN KEY (bid)  REFERENCES b(bid)
);

-- tables 'ab' and 'abc' have a one-on-one relationship
CREATE TABLE abc (
    aid          INTEGER,
    bid          INTEGER,
    name         TEXT          NOT NULL,
    PRIMARY KEY (aid, bid)  FOREIGN KEY (aid, bid)  REFERENCES ab(aid, bid)
);

-- tables 'ab' and 'abd' have a one-on-one relationship
CREATE TABLE abd (
    aid          INTEGER,
    bid          INTEGER,
    value        INTEGER       CHECK( value > 0 ),
    PRIMARY KEY (aid, bid)  FOREIGN KEY (aid, bid)  REFERENCES ab(aid, bid)
);

CREATE TABLE w (
    id           INTEGER      PRIMARY KEY
);

以下繁琐的过程可能会得到我想要的 table 列表:

  1. 获取 table ab 的主键:

    SELECT l.name FROM pragma_table_info('ab') as l WHERE l.pk > 0;

  2. 获取其他 table 的外键(这种情况适用于 table abd):

    SELECT * from pragma_foreign_key_list('abd');

  3. 进行解析以获取一对一关系的 table 列表。

不过,我希望一定有更优雅的方法。

对于 SQL 服务器,有 sys.foreign_keysreferenced_object_id 可用(参见 post)。也许在 SQLite?

中有类似的东西

编辑:再添加两个 table 用于测试

-- tables 'ab' and 'abe' have a one-on-one relationship
CREATE TABLE abe (
    aid          INTEGER,
    bid          INTEGER,
    value        INTEGER       CHECK( value < 0 ),
    PRIMARY KEY (aid, bid)  FOREIGN KEY (aid, bid)  REFERENCES ab
);

-- tables 'ab' and 'abf' have a one-on-one relationship
CREATE TABLE abf (
    aidQ          INTEGER,
    bidQ          INTEGER,
    value        INTEGER,
    PRIMARY KEY (aidQ, bidQ)  FOREIGN KEY (aidQ, bidQ)  REFERENCES ab(aid, bid)
);

编辑:验证 table abe

的 FK
sqlite> PRAGMA foreign_keys;
1
sqlite> .schema abe
CREATE TABLE abe (
    aid          INTEGER,
    bid          INTEGER,
    value        INTEGER       CHECK( value < 0 ),
    PRIMARY KEY (aid, bid)  FOREIGN KEY (aid, bid)  REFERENCES ab
);
sqlite> DELETE FROM abe;
sqlite> INSERT INTO abe (aid, bid, value) VALUES (2, 1, -21);
sqlite> INSERT INTO abe (aid, bid, value) VALUES (-2, 1, -21);
Error: FOREIGN KEY constraint failed
sqlite> SELECT * FROM ab;
1|1
1|2
2|1

Is there a way to get a list of tables that have one-on-one relationship to a given table in SQLite3?

不确定,因为编写外键约束不定义关系(而是支持关系),即关系可以在没有 FK 约束的情况下存在。

外键约束定义:-

  • a) 强制参照完整性的规则
  • b) 可选地 maintains/alters 当引用的列发生更改时的引用完整性(ON DELETEON UPDATE

因此查看外键列表只会告诉您 where/if 已对 FK 约束进行编码。

表示以下将获得具有约束的 tables 和引用的 tables.

更优雅见仁见智,所以由你决定:-

WITH cte_part(name,reqd,rest) AS (
    SELECT name,'',substr(sql,instr(sql,' REFERENCES ') + 12)||' REFERENCES '
    FROM sqlite_master 
    WHERE sql LIKE '% REFERENCES %(%'
    UNION ALL
    SELECT 
        name,
        substr(rest,0,instr(rest,' REFERENCES ')),
        substr(rest,instr(rest,' REFERENCES ') + 12)
    FROM cte_part
    WHERE length(rest) > 12 
)
SELECT DISTINCT
    CASE
        WHEN length(reqd) < 1 THEN name
        ELSE 
            CASE substr(reqd,1,1)
                WHEN '''' THEN substr(replace(reqd,substr(reqd,1,1),''),1,instr(reqd,'(')-3)
                WHEN '[' THEN substr(replace(replace(reqd,'[',''),']',''),1,instr(reqd,'(')-3)
                WHEN '`' THEN substr(replace(reqd,substr(reqd,1,1),''),1,instr(reqd,'(')-3)
                ELSE  substr(reqd,1,instr(reqd,'(')-1)
            END
    END AS tablename
FROM cte_part
;

例如 use/results :-

  • Navicat 截图

这是对上述的改编,其中包括在适当情况下引用父项的子项table:-

WITH cte_part(name,reqd,rest) AS (
    SELECT name,'',substr(sql,instr(sql,' REFERENCES ') + 12)||' REFERENCES '
    FROM sqlite_master 
    WHERE sql LIKE '% REFERENCES %(%'
    UNION ALL
    SELECT 
        name,
        substr(rest,0,instr(rest,' REFERENCES ')),
        substr(rest,instr(rest,' REFERENCES ') + 12)
    FROM cte_part
    WHERE length(rest) > 12 
)
SELECT DISTINCT
    CASE
        WHEN length(reqd) < 1 THEN name
        ELSE 
            CASE substr(reqd,1,1)
                WHEN '''' THEN substr(replace(reqd,substr(reqd,1,1),''),1,instr(reqd,'(')-3)
                WHEN '[' THEN substr(replace(replace(reqd,'[',''),']',''),1,instr(reqd,'(')-3)
                WHEN '`' THEN substr(replace(reqd,substr(reqd,1,1),''),1,instr(reqd,'(')-3)
                ELSE  substr(reqd,1,instr(reqd,'(')-1)
            END
    END AS tablename,
    CASE WHEN length(reqd) < 1 THEN '' ELSE name END AS referrer
FROM cte_part
;

结果示例:-

  • artists table 被 albums 引用为 SQL 用于创建 相册 table 是 CREATE TABLE 'albums'([AlbumId] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL ,[Title] TEXT NOT NULL ,[ArtistId] INTEGER NOT NULL , FOREIGN KEY ([ArtistId]) REFERENCES 'artists'([ArtistId]))

  • FOREIGN KEY ([ArtistId]) REFERENCES 'artists'([ArtistId]))

  • 员工table根据CREATE TABLE 'employees'(.... REFERENCES 'employees'([EmployeeId]))

    自我引用

补充评论:-

(I am still trying to understand your code...)

该代码基于从 sqlite_master 中选择行,其中该行用于 table(类型 = 'table'),而不是索引、触发器或视图,并且其中sql 列包含单词 REFERENCES,前后有一个 space,后面还有一个左括号。

  • 最后一个条件用来剔除 CREATE TABLE oops (`REFERENCES` TEXT, `x REFERENCES Y`);

对于每个选定的行,输出 3 列:-

  1. name 这是从 sqlite_master、
  2. 的名称列中提取的 table 的名称
  3. reqd最初是一个空字符串(即initial)
  4. rest 后缀为 REFERENCES 的 sql 的其余部分 table。

UNION ALL 添加的行基于新添加到 CTE 的内容,即三列是按照 :-

提取的
  1. 名字是名字
  2. reqd 是从其余列到第一个 REFERENCES 项(即 table 和引用列)的 sql
  3. rest 是参考术语
  4. 之后的 sql

与任何递归一样,需要检测结束,这是当整个 sql 语句已减少到小于 12(即“ REFERENCES ”的长度,用于拆分sql声明)。

这就是所谓的 RECURSIVE CTE

最后查询得到的CTE。如果 reqd 字段为空,则 tablename 列是名称列,否则(即 reqd 列包含数据(sql 的一部分))提取 table 名称(部分向上如果未包含在左括号中(`,' 或 [ with ]))或从括号之间提取。

如果包含所有 CTE 列(部分数据已被截断),则最终查询结果如下:-

可以清楚地看到提取的 sql 逐渐减少

答案是原则上的,没有经过广泛测试以考虑所有情况,可能需要裁剪。

备选

虽然不是单个查询解决方案,但以下仅需要 submission/execution 一系列查询,因此与平台无关。

它围绕使用两个 tables:-

  • sqlite_master
  • 的工作副本
  • 一个工作 table 来存储 SELECT pragma_foreign_key_list(?)
  • 的输出

两个 table 都是通过 CREATE-SELECT 创建的,尽管它们都没有复制任何行,因此 table 是空的。

触发器应用于 sqlite_master 的工作副本以插入存储 SELECT 结果的 table pragma_foreign_key_list(table_name_from_insert) ;

相关行是通过 SELECT INSERT 从 sqlite_master 复制的,因此触发会填充存储 table。

以下是测试代码:-

DROP TABLE IF EXISTS fklist;
DROP TABLE IF EXISTS master_copy;
DROP TRIGGER IF EXISTS load_fklist;
/* Working version of foreign_key_list to store ALL results of SELECT pragma_foreign_key_list invocation */
CREATE TABLE IF NOT EXISTS fklist AS SELECT '' AS child,* 
    FROM pragma_foreign_key_list((SELECT name FROM sqlite_master WHERE type = 'not a type' LIMIT 1));
/* Working version of sqlite master */
CREATE TABLE IF NOT EXISTS master_copy AS SELECT * FROM sqlite_master WHERE type = 'not a type';
/* Add an after insert trigger for master copy to add to fklist */
CREATE TRIGGER IF NOT EXISTS load_fklist 
    AFTER INSERT ON master_copy
    BEGIN
        INSERT INTO fklist SELECT new.name,* FROM pragma_foreign_key_list(new.name);
    END
;

/* Populate master_copy from sqlite_master (relevant rows)
    and thus build the fklist
*/
INSERT INTO master_copy SELECT * 
    FROM sqlite_master 
    WHERE type = 'table' 
        AND instr(sql,' REFERENCES ') > 0
;
SELECT * FROM fklist;
DROP TABLE IF EXISTS fklist;
DROP TABLE IF EXISTS master_copy;
DROP TRIGGER IF EXISTS load_fklist;

根据之前的答案使用类似的测试基础,上述结果为:-