英文:
Error after second read from CTE or TEMPORARY TABLE in Postgres
问题
I'm your Chinese translator, I will only provide the translated text without additional content:
我正在尝试编写一个查询,在该查询中,我通过某些条件选择记录,更新这些记录的一个列,然后在一个事务中将它们返回。
我尝试了两个选项。首先是使用CTE:
BEGIN;
WITH unwatched_rows AS (
SELECT * FROM my_table WHERE is_readed = false FOR UPDATE
)
UPDATE my_table SET is_readed = true WHERE id IN (SELECT id FROM unwatched_rows);
SELECT * FROM unwatched_rows;
COMMIT;
在这种情况下,我得到一个错误:“关系“unwatched_rows”不存在”。我了解到这是因为我不能多次使用CTE。据我了解,CTE不适用于我的情况。
第二个选项是使用“CREATE TEMPORARY TABLE”:
BEGIN;
CREATE TEMPORARY TABLE unwatched_rows AS (
SELECT * FROM my_table WHERE is_readed = false FOR UPDATE
);
UPDATE my_table SET is_readed = true WHERE id IN (SELECT id FROM unwatched_rows);
SELECT * FROM unwatched_rows;
COMMIT;
我在DBeaver中运行这个查询。第一次运行时,它正常工作,但当我尝试再次运行时,我得到一个错误:“关系“unwatched_rows”已经存在”。我不明白为什么会发生这种情况。临时表不应该在提交后被删除吗?我做错了什么,如何解决这个问题?
英文:
I am trying to write a query where I select records by some condition, update one column of those records and then return them, all in one transaction.
I have tried two options. First with CTE:
BEGIN;
WITH unwatched_rows AS (
SELECT * from my_table WHERE is_readed = false FOR UPDATE
)
UPDATE my_table SET is_readed = true WHERE id IN (SELECT id FROM unwatched_rows);
SELECT * FROM unwatched_rows;
COMMIT;
In this case I get an error relation "unwatched_rows" does not exist
. How I learned this is because i can't use CTE
more than once. And as I understand it, CTE
is not suitable for my situation
Second option with CREATE TEMPORARY TABLE
:
BEGIN;
CREATE TEMPORARY TABLE unwatched_rows AS (
SELECT * from my_table WHERE is_readed = false FOR UPDATE
);
UPDATE my_table SET is_readed = true WHERE id IN (SELECT id FROM unwatched_rows);
SELECT * FROM unwatched_rows;
COMMIT;
I am running this query through DBeaver. For first time it works fine, but when I try to run this query again I get an error relation "unwatched_rows" already exists
. I don't understand why this is happening. Shouldn't the temporary table be dropped right after the commit?
What am I doing wrong and how to solve it?
答案1
得分: 1
如果您希望在提交后立即删除临时表,您需要在创建时指定 ON COMMIT DROP
:
BEGIN;
CREATE TEMPORARY TABLE unwatched_rows ON COMMIT DROP AS (
SELECT * from my_table WHERE is_readed = false FOR UPDATE
);
UPDATE my_table SET is_readed = true WHERE id IN (SELECT id FROM unwatched_rows);
SELECT * FROM unwatched_rows;
COMMIT;
另一种解决方案是在重新创建临时表之前删除它:
BEGIN;
DROP TABLE IF EXISTS unwatched_rows;
CREATE TEMPORARY TABLE unwatched_rows AS (
SELECT * from my_table WHERE is_readed = false FOR UPDATE
);
UPDATE my_table SET is_readed = true WHERE id IN (SELECT id FROM unwatched_rows);
SELECT * FROM unwatched_rows;
COMMIT;
英文:
If you want the temporary table to be removed right after the commit, You will have to specify ON COMMIT DROP
when create it :
BEGIN;
CREATE TEMPORARY TABLE unwatched_rows ON COMMIT DROP AS (
SELECT * from my_table WHERE is_readed = false FOR UPDATE
);
UPDATE my_table SET is_readed = true WHERE id IN (SELECT id FROM unwatched_rows);
SELECT * FROM unwatched_rows;
COMMIT;
An other solution is to drop your temporary table before created it again :
BEGIN;
DROP TABLE IF EXISTS unwatched_rows;
CREATE TEMPORARY TABLE unwatched_rows AS (
SELECT * from my_table WHERE is_readed = false FOR UPDATE
);
UPDATE my_table SET is_readed = true WHERE id IN (SELECT id FROM unwatched_rows);
SELECT * FROM unwatched_rows;
COMMIT;
答案2
得分: 1
You can certainly use a CTE (with ...) once. However you used it incorrectly. A CTE is essentially a view which exists only for the duration of the query where it is created. In query the semi-colon (;) after the update
terminated the statement and thus the CTE itself goes out-of-scope. So for the select
statement it no longer exists. Postgres allows DML in a CTE so to correct your statement create a second CTE that does the update. So:
使用CTE(with ...)是可以的,但是您使用它不正确。CTE本质上是一个视图,仅在创建它的查询持续时间内存在。在update
之后的分号(;)终止了该语句,因此CTE本身超出了作用范围。因此,对于select
语句,它不再存在。Postgres允许在CTE中使用DML,因此要纠正您的语句,创建第二个执行更新的CTE。因此:
with unwatched_rows as (
select id
from my_table
where is_readed = false for update
)
, upt as (
update my_table
set is_readed = true
where id in (select id from unwatched_rows)
)
select *
from unwatched_rows;
As far as wrapping into a function this is actually quite simple. It does not require a temp table not, for that matter, a CTE. It reduces to a single SQL statement:
至于将其封装为函数,这实际上非常简单。它不需要临时表,也不需要CTE。它可以简化为一个SQL语句:
create or replace function unwatched_rows()
returns setof my_table.id%type
language sql
as $$
update my_table
set is_readed = true
where not is_readed
returning id;
$$;
See here for demo illustrating both.
请参阅此处以查看演示,其中说明了这两种方法。
英文:
You can certainly use a CTE (with ...) once. However you used it incorrectly. A CTE is essentially a view which exists only for the duration of the query where it is created. In query the semi-colon (;) after the update
terminated the statement and thus the CTE itself goes out-of-scope. So for theselect
statement it no longer exists. Postgres allows DML in a CTE so to correct your statement create a second CTE that does the update. So:
with unwatched_rows as (
select id
from my_table
where is_readed = false for update
)
, upt as (
update my_table
set is_readed = true
where id in (select id from unwatched_rows)
)
select *
from unwatched_rows;
As far as wrapping into a function this is actually quite simple. It does not require a temp table not, for that matter, a CTE. It reduces to a single SQL statement:
create or replace function unwatched_rows()
returns setof my_table.id%type
language sql
as $$
update my_table
set is_readed = true
where not is_readed -- same as: is_readed = false
returning id;
$$;
See here for demo illustrating both.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论