我正在 node.js 中编写一个函数来查询 PostgreSQL 表。
如果该行存在,我想从该行返回 id 列。
如果它不存在,我想插入它并返回 id ( insert into ... returning id
)。
我一直在尝试case
和if else
声明的变体,但似乎无法让它发挥作用。
我正在 node.js 中编写一个函数来查询 PostgreSQL 表。
如果该行存在,我想从该行返回 id 列。
如果它不存在,我想插入它并返回 id ( insert into ... returning id
)。
我一直在尝试case
和if else
声明的变体,但似乎无法让它发挥作用。
单个 SQL 语句中的解决方案。不过需要 PostgreSQL 8.4或更高版本。
考虑以下演示:
测试设置:
CREATE TEMP TABLE tbl (
id serial PRIMARY KEY
,txt text UNIQUE -- obviously there is unique column (or set of columns)
);
INSERT INTO tbl(txt) VALUES ('one'), ('two');
插入/选择命令:
WITH v AS (SELECT 'three'::text AS txt)
,s AS (SELECT id FROM tbl JOIN v USING (txt))
,i AS (
INSERT INTO tbl (txt)
SELECT txt
FROM v
WHERE NOT EXISTS (SELECT * FROM s)
RETURNING id
)
SELECT id, 'i'::text AS src FROM i
UNION ALL
SELECT id, 's' FROM s;
第一个CTE v不是绝对必要的,但可以实现您只需输入一次值。
如果“行”存在,则第二个CTE 选择id
来自。tbl
第三个 CTE i 将“行”插入tbl
当(且仅当)它不存在时,返回id
。
最终SELECT
返回id
. 我添加了一个src
指示“源”的列 - “行”是否预先存在并且id
来自 SELECT,或者“行”是新的,id
.
此版本应该尽可能快,因为它不需要额外的 SELECT fromtbl
并使用 CTE。
为了确保在多用户环境中避免可能出现的竞争条件:对于在 Postgres 9.5或更高版本
中使用新的 UPSERT 的更新技术:
我建议在数据库端进行检查,然后将 id 返回给 nodejs。
例子:
CREATE OR REPLACE FUNCTION foo(p_param1 tableFoo.attr1%TYPE, p_param2 tableFoo.attr1%TYPE) RETURNS tableFoo.id%TYPE AS $$
DECLARE
v_id tableFoo.pk%TYPE;
BEGIN
SELECT id
INTO v_id
FROM tableFoo
WHERE attr1 = p_param1
AND attr2 = p_param2;
IF v_id IS NULL THEN
INSERT INTO tableFoo(id, attr1, attr2) VALUES (DEFAULT, p_param1, p_param2)
RETURNING id INTO v_id;
END IF;
RETURN v_id:
END;
$$ LANGUAGE plpgsql;
而不是在 Node.js 端(我在这个例子中使用 node-postgres):
var pg = require('pg');
pg.connect('someConnectionString', function(connErr, client){
//do some errorchecking here
client.query('SELECT id FROM foo($1, $2);', ['foo', 'bar'], function(queryErr, result){
//errorchecking
var id = result.rows[0].id;
};
});
Something like this, if you are on PostgreSQL 9.1
with test_insert as (
insert into foo (id, col1, col2)
select 42, 'Foo', 'Bar'
where not exists (select * from foo where id = 42)
returning foo.id, foo.col1, foo.col2
)
select id, col1, col2
from test_insert
union
select id, col1, col2
from foo
where id = 42;
It's a bit longish and you need to repeat the id to test for several times, but I can't think of a different solution that involves a single SQL statement.
If a row with id=42
exists, the writeable CTE will not insert anything and thus the existing row will be returned by the second union part.
When testing this I actually thought the new row would be returned twice (therefor a union
not a union all
) but it turns out that the result of the second select statement is actually evaluated before the whole statement is run and it does not see the newly inserted row. So in case a new row is inserted, it will be taken from the "returning" part.
create table t (
id serial primary key,
a integer
)
;
insert into t (a)
select 2
from (
select count(*) as s
from t
where a = 2
) s
where s.s = 0
;
select id
from t
where a = 2
;