为什么 PostgreSQL 允许外键中存在某些类型不匹配?

Hol*_*ler 14 postgresql foreign-key datatypes

在教授数据库基础课程时,一名学生询问外键的数据类型与他们引用的事物(例如主键)的数据类型不匹配。

例如,可以存储在INTEGER列中的所有数字都可以表示为TEXT,因此只要应用适当的类型转换/转换,TEXT列的数据就可以用于引用列中的数据。INTEGER

我们在教学中使用 PostgreSQL(因为它的文档非常好等等),所以我们就去看了一下。你瞧,关于外键的“简化”章节告诉我们:

当然,受约束列的数量和类型需要与引用列的数量和类型相匹配。

不过,有关CREATE TABLE 的“功能完整”部分的进一步研究并未明确提及数据类型。这部分只讲价值观。

我们尝试了各种数据类型的组合,其中一些比其他组合更有说服力(例如上面的INTEGER-变体)。TEXTDBMS 不相信并回复42804:不兼容的类型。

到目前为止,一切都很好。想象一下,当我们发现 PostgreSQL 的各种整数类型实际上可以工作时,我们会多么惊讶。

他们甚至正确地考虑了符号,这意味着他们不仅仅是匹配位。

当然,这应该有一个方向:拥有一个由列INTEGER引用的列BIGINT总是有效的,因为适合引用列的所有内容也适合引用列。

令人惊讶的是,PostgreSQL 允许另一个方向(在本例中使用INTEGERSMALLINT):

CREATE TABLE this_should_not_work
(
    this_should_not_work_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    data                    TEXT
);

CREATE TABLE this_should_not_work_detail
(
    detail_id                  INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    detail_data                TEXT,
  
  -- This Foreign Key references a column of a different type, which should not be possible
    fk_this_should_not_work_id SMALLINT REFERENCES this_should_not_work (this_should_not_work_id)
);
Run Code Online (Sandbox Code Playgroud)

这是上述问题的可执行版本:db-fiddle

db-fiddle 还具有序列选项和INSERT在第二次插入时触发失败的语句。

请注意,上面链接的文档和 db-fiddle 适用于 PostgreSQL 13,但问题(?)也可以在 PostgreSQL 14 上重现。

我知道外键类型不匹配是数据库设计问题。问题是,为什么 PostgreSQL 会指出明显的情况(INTEGER- TEXT),而不指出更微妙的情况(INTEGER- SMALLINT)?

PS:当与 结合使用时,可能会造成很多破坏ON UPDATE CASCADE,因为由于数据不适合引用表,引用表的更新失败 - 这会产生相当“创造性”的错误消息。

Lau*_*lbe 20

使用来源,卢克!

\n

ATAddForeignKeyConstraintsrc/backend/commands/tablecmds.c,我们找到了有关需求的真相:

\n
        /*\n         * There had better be a primary equality operator for the index.\n         * We\'ll use it for PK = PK comparisons.\n         */\n        ppeqop = get_opfamily_member(opfamily, opcintype, opcintype,\n                                     eqstrategy);\n\n        if (!OidIsValid(ppeqop))\n            elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",\n                 eqstrategy, opcintype, opcintype, opfamily);\n\n
Run Code Online (Sandbox Code Playgroud)\n

因此,目标类型的唯一索引必须支持相等比较。

\n
        /*\n         * Are there equality operators that take exactly the FK type? Assume\n         * we should look through any domain here.\n         */\n        fktyped = getBaseType(fktype);\n\n        pfeqop = get_opfamily_member(opfamily, opcintype, fktyped,\n                                     eqstrategy);\n        if (OidIsValid(pfeqop))\n        {\n            pfeqop_right = fktyped;\n            ffeqop = get_opfamily_member(opfamily, fktyped, fktyped,\n                                         eqstrategy);\n        }\n        else\n        {\n            /* keep compiler quiet */\n            pfeqop_right = InvalidOid;\n            ffeqop = InvalidOid;\n        }\n
Run Code Online (Sandbox Code Playgroud)\n

如果引用列的数据类型和目标索引支持的引用列之间存在相等运算符,那就很好。

\n
        if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))\n        {\n            /*\n             * Otherwise, look for an implicit cast from the FK type to the\n             * opcintype, and if found, use the primary equality operator.\n             * This is a bit tricky because opcintype might be a polymorphic\n             * type such as ANYARRAY or ANYENUM; so what we have to test is\n             * whether the two actual column types can be concurrently cast to\n             * that type.  (Otherwise, we\'d fail to reject combinations such\n             * as int[] and point[].)\n             */\n            Oid         input_typeids[2];\n            Oid         target_typeids[2];\n\n            input_typeids[0] = pktype;\n            input_typeids[1] = fktype;\n            target_typeids[0] = opcintype;\n            target_typeids[1] = opcintype;\n            if (can_coerce_type(2, input_typeids, target_typeids,\n                                COERCION_IMPLICIT))\n            {\n                pfeqop = ffeqop = ppeqop;\n                pfeqop_right = opcintype;\n            }\n        }\n
Run Code Online (Sandbox Code Playgroud)\n

否则,必须存在从引用列的类型到被引用列的隐式转换。

\n
        if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))\n            ereport(ERROR,\n                    (errcode(ERRCODE_DATATYPE_MISMATCH),\n                     errmsg("foreign key constraint \\"%s\\" cannot be implemented",\n                            fkconstraint->conname),\n                     errdetail("Key columns \\"%s\\" and \\"%s\\" "\n                               "are of incompatible types: %s and %s.",\n                               strVal(list_nth(fkconstraint->fk_attrs, i)),\n                               strVal(list_nth(fkconstraint->pk_attrs, i)),\n                               format_type_be(fktype),\n                               format_type_be(pktype))));\n
Run Code Online (Sandbox Code Playgroud)\n

如果两者都不正确,则会出错。

\n

integer因此,您可以使用 from到 的外键smallint,因为这些类型之间存在属于索引运算符系列的相等运算符:

\n
\\do =\n                                              List of operators\n   Schema   \xe2\x94\x82 Name \xe2\x94\x82        Left arg type        \xe2\x94\x82       Right arg type        \xe2\x94\x82 Result type \xe2\x94\x82  Description  \n\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\xaa\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\xaa\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\xaa\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\xaa\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\xaa\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\n ...\n pg_catalog \xe2\x94\x82 =    \xe2\x94\x82 integer                     \xe2\x94\x82 smallint                    \xe2\x94\x82 boolean     \xe2\x94\x82 equal\n ...\n(63 rows)\n
Run Code Online (Sandbox Code Playgroud)\n

text但是,和 之间没有隐式转换integer,因此这些类型之间不能有外键引用。

\n
\\dC\n                                         List of casts\n         Source type         \xe2\x94\x82         Target type         \xe2\x94\x82      Function      \xe2\x94\x82   Implicit?   \n\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\xaa\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\xaa\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\xaa\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\n ...\n integer                     \xe2\x94\x82 bigint                      \xe2\x94\x82 int8               \xe2\x94\x82 yes\n integer                     \xe2\x94\x82 bit                         \xe2\x94\x82 bit                \xe2\x94\x82 no\n integer                     \xe2\x94\x82 boolean                     \xe2\x94\x82 bool               \xe2\x94\x82 no\n integer                     \xe2\x94\x82 "char"                      \xe2\x94\x82 char               \xe2\x94\x82 no\n integer                     \xe2\x94\x82 double precision            \xe2\x94\x82 float8             \xe2\x94\x82 yes\n integer                     \xe2\x94\x82 money                       \xe2\x94\x82 money              \xe2\x94\x82 in assignment\n integer                     \xe2\x94\x82 numeric                     \xe2\x94\x82 numeric            \xe2\x94\x82 yes\n integer                     \xe2\x94\x82 oid                         \xe2\x94\x82 (binary coercible) \xe2\x94\x82 yes\n integer                     \xe2\x94\x82 real                        \xe2\x94\x82 float4             \xe2\x94\x82 yes\n integer                     \xe2\x94\x82 regclass                    \xe2\x94\x82 (binary coercible) \xe2\x94\x82 yes\n integer                     \xe2\x94\x82 regcollation                \xe2\x94\x82 (binary coercible) \xe2\x94\x82 yes\n integer                     \xe2\x94\x82 regconfig                   \xe2\x94\x82 (binary coercible) \xe2\x94\x82 yes\n integer                     \xe2\x94\x82 regdictionary               \xe2\x94\x82 (binary coercible) \xe2\x94\x82 yes\n integer                     \xe2\x94\x82 regnamespace                \xe2\x94\x82 (binary coercible) \xe2\x94\x82 yes\n integer                     \xe2\x94\x82 regoper                     \xe2\x94\x82 (binary coercible) \xe2\x94\x82 yes\n integer                     \xe2\x94\x82 regoperator                 \xe2\x94\x82 (binary coercible) \xe2\x94\x82 yes\n integer                     \xe2\x94\x82 regproc                     \xe2\x94\x82 (binary coercible) \xe2\x94\x82 yes\n integer                     \xe2\x94\x82 regprocedure                \xe2\x94\x82 (binary coercible) \xe2\x94\x82 yes\n integer                     \xe2\x94\x82 regrole                     \xe2\x94\x82 (binary coercible) \xe2\x94\x82 yes\n integer                     \xe2\x94\x82 regtype                     \xe2\x94\x82 (binary coercible) \xe2\x94\x82 yes\n integer                     \xe2\x94\x82 smallint                    \xe2\x94\x82 int2               \xe2\x94\x82 in assignment\n ...\n
Run Code Online (Sandbox Code Playgroud)\n