PostgreSQL 9.6列删除和带有CTE的SQL函数的副作用


15

如果我有一个包含3列的表(例如A,B和D),并且我不得不引入一个新表(例如C)来替换D的当前位置。我将使用以下方法:

  1. 引入2个新列作为C和D2。
  2. 将D的内容复制到D2。
  3. 删除D。
  4. 将D2重命名为D。

新订单将为A,B,C和D。

我认为这是合法的做法,因为(到目前为止)它没有产生任何问题。

但是,今天,当在同一张表上执行语句的函数返回以下错误时,我遇到了一个问题:

table row type and query-specified row type do not match

以及以下详细信息:

Query provides a value for a dropped column at ordinal position 13

我尝试重新启动PostgreSQL,执行a VACUUM FULL,最后按照此处此处的建议删除并重新创建该函数,但是这些解决方案均无效(除了它们尝试解决系统表已更改的情况外)。

由于可以使用非常小的数据库,因此我将其导出,删除并重新导入,从而解决了我的功能问题


我知道这样一个事实,即不应该通过修改系统表来弄乱列的自然顺序(用弄脏手pg_attribute等),如下所示:

是否可以更改Postgres中列的自然顺序?

从我的函数抛出的错误来看,我现在意识到用我的方法移动列的顺序也是不行的。谁能为我的工作为什么也出错提供一些启发?


Postgres版本是9.6.0。

这是函数:

CREATE OR REPLACE FUNCTION "public"."__post_users" ("facebookid" text, "useremail" text, "username" text) RETURNS TABLE (authentication_code text, id integer, key text, stripe_id text) AS '

-- First, select the user:
WITH select_user AS
(SELECT
users.id
FROM
users
WHERE
useremail = users.email),

-- Second, update the user (if user exists):
update_user AS
(UPDATE
users
SET
authentication_code = GEN_RANDOM_UUID(),
authentication_date = current_timestamp,
facebook_id = facebookid
WHERE EXISTS (SELECT * FROM select_user)
AND
useremail = users.email
RETURNING
users.authentication_code,
users.id,
users.key,
users.stripe_id),

-- Third, insert the user (if user does not exist):
insert_user AS
(INSERT INTO
users (authentication_code, authentication_date, email, key, name, facebook_id)
SELECT
GEN_RANDOM_UUID(),
current_timestamp,
useremail,
GEN_RANDOM_UUID(),
COALESCE(username, SUBSTRING(useremail FROM ''([^@]+)'')),
facebookid
WHERE NOT EXISTS (SELECT * FROM select_user)
RETURNING
users.authentication_code,
users.id,
users.key,
users.stripe_id)

-- Finally, select the authentication code, ID, key and Stripe ID:
SELECT
*
FROM
update_user
UNION ALL
SELECT
*
FROM
insert_user' LANGUAGE "sql" COST 100 ROWS 1
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER

我进行重命名/重新排序的列都facebook_idstripe_id(之前这些添加了新列,这是改名的原因,但不是由该查询感动)。

按特定顺序排列列纯粹是无聊的。但是,提出此问题的原因是出于担心,对于在生产模式下使用函数的人来说,简单的重命名和删除列可能会触发实际问题(就像我自己所做的那样)。


看一下如何更改PostgreSQL数据库表中列的位置?在堆栈溢出上可能会有所帮助,尽管他们大多建议不要对列进行重新排序。
joanolo

Answers:


16

9.6和9.6.1上可能的错误

对我来说,这完全像个虫子...

我不知道为什么会发生,但是我可以确认它会发生。这是重现此问题的最简单的设置(在版本9.6.0和9.6.1中)。

CREATE TABLE users
(
    id SERIAL PRIMARY KEY,
    email TEXT NOT NULL,
    column_that_we_will_drop TEXT
) ;

-- Function that uses the previous table, and that has a CTE
CREATE OR REPLACE FUNCTION __post_users
    (_useremail text) 
RETURNS integer AS
$$
-- Need a CTE to produce the error. A 'constant' one suffices.
WITH something_even_if_useless(a) AS
(
    VALUES (1)
)
UPDATE
    users
SET
    id = id
WHERE 
    -- The CTE needs to be referenced, if the next
    -- condition were not in place, the problem is not reproduced
    EXISTS (SELECT * FROM something_even_if_useless)
    AND email = _useremail
RETURNING
    id
$$
LANGUAGE "sql" ;

完成此设置后,下一条语句将起作用

SELECT * FROM __post_users('a@b.com');

此时,我们删除一列:

ALTER TABLE users 
    DROP COLUMN column_that_we_will_drop ;

此更改使下一条语句生成错误

SELECT * FROM __post_users('a@b.com');

这与@Andy提到的相同:

ERROR: table row type and query-specified row type do not match
SQL state: 42804
Detail: Query provides a value for a dropped column at ordinal position 3.
Context: SQL function "__post_users" statement 1
    SELECT * FROM __post_users('a@b.com');

删除并重新创建该函数不能解决问题。
VACUUM FULL(表或整个数据库)不能解决问题。


该错误报告已传递至适当的PostgreSQL邮件列表,我们得到了非常快速的响应

我无法在HEAD或9.6分支技巧中重现此内容。我相信此补丁已经修复了该补丁,该补丁在9.6.1之后发布了:

https://git.postgresql.org/gitweb/?p=postgresql.git&a=commitdiff&h=f4d865f22

但是感谢您的报告!

问候,汤姆巷


版本9.6.2

在2017年3月6日,我可以确认我无法在版本9.6.2上重现此行为。也就是说,此版本中的错误似乎已得到纠正。

更新

@Jana的评论:“我可以确认该错误存在于9.6.1中,并且已在9.6.2中得到修复。该修复程序也列在postgres发布网站上:修复了在运行期间虚假的“查询为被删除的列提供了值”错误在具有删除的列的表上执行INSERT或UPDATE“



4
我正在使用9.6.0,@ joanolo是正确的,我可以使用他的方法重现该错误。如果Tom不能重现它,则可能是该特定版本(我认为是9.6.1?)的一个独立错误。如答案中所述,当在表中删除使用CTE影响表的函数时,会出现问题。因此,问题不仅仅在于重新排序,这就是我试图解决的问题。我将编辑标题以反映这一点。
安迪

我可以确认该错误存在于9.6.1中并已在9.6.2中修复。该修复程序还列在postgres发布网站上修复了在对具有被删除列的表进行INSERT或UPDATE期间出现的虚假“查询为被删除的列提供值”错误
Jana

0

我知道您以前可能已经听说过,但这是一个可怕的想法。

  • 逻辑顺序只会影响类似 SELECT *
  • 逻辑顺序的作用仅限于外观。

因此,即使一点都不重要也不会阻止您,并且我们承认我们只是在玩带有行结构的Photoshop并痴迷于显示,所以让我们解释一些更多的事情。

  • PostgreSQL中不存在逻辑顺序
  • 实物订购有真正的好处(餐桌包装)
  • PostgreSQL都不允许您对物理顺序进行任何控制CREATE TABLE(尽管优先级高得多)

因此,PostgreSQL是一个不好的显示层。总而言之,尽管ALTER运行良好,但您不应该尝试使用龙。两者ALTER TABLE,并且告诫部分的本作提,

当前仅某些DDL命令TRUNCATE以及的表重写形式ALTER TABLE不是MVCC安全的。这意味着在截断或重写提交之后,如果并发事务使用的是在提交DDL命令之前拍摄的快照,则该表将对并发事务显示为空。对于仅在DDL命令启动之前未访问相关表的事务,这将是一个问题-这样做的任何事务都将至少持有ACCESS SHARE表锁,这将阻塞DDL命令,直到该事务完成。因此,对于在目标表上进行连续查询,这些命令不会在表内容中引起任何明显的不一致,但是它们可能导致目标表的内容与数据库中其他表之间的明显不一致。

而且,如果这还不够,那么您仍然要假装这是一个好主意,而是一个可怕的主意。然后试试这个

  1. 转储表 pg_dump -t
  2. 在转储中手动重新排列列。
  3. 将内容加载到TEMP表中。
  4. BEGIN 交易
  5. DROP 完全是旧桌子,
  6. RENAME 临时表到产品表。
  7. COMMIT

如果所有这些听起来都是多余的,请记住,更新数据库中的行需要重写这些行(假设它们不是 TOAST。您必须解析数据并重建表模式,但是无论哪种方式都必须重写如果必须执行此任务,这就是我要执行的操作。

但是,所有这些通常都是在说。没有人复制您的结果。

您提供了一个我们无法运行的测试用例

ERROR:  column users.authentication_code does not exist
LINE 24: users.authentication_code,

而且,您还没有告诉我们您所使用的确切版本。


谢谢您的详尽解释,我已经编辑了问题以包括特定的版本。
安迪

4
该缺陷是在9.6.0版中可重现
ypercubeᵀᴹ

0

我通过备份和还原数据库来解决此错误。

Heroku的步骤

  • 打开维护模式: heroku maintenance:on
  • 备份数据库: heroku pg:backups:capture
  • 恢复数据库: heroku pg:backups:restore
  • 重新启动应用程序: heroku restart
  • 关闭维护模式: heroku maintenance:off

0

我也遇到了这个错误。对于那些不想完全备份/还原数据库的用户。知道简单地复制表即可。但是,没有复制表的“魔术”方法。我做到了:

SELECT * INTO mytable_copy FROM mytable;
ALTER TABLE mytable RENAME TO mytable_backup; -- just in case. you never know
ALTER TABLE mytable_copy RENAME TO mytable;

之后,您仍然需要手动重新创建索引,外键和默认值。像这样重新创建表使错误消失了。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.