11

我正在 node.js 中编写一个函数来查询 PostgreSQL 表。
如果该行存在,我想从该行返回 id 列。
如果它不存在,我想插入它并返回 id ( insert into ... returning id)。

我一直在尝试caseif else声明的变体,但似乎无法让它发挥作用。

4

4 回答 4

10

单个 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 的更新技术:

于 2012-04-09T20:53:00.773 回答
8

我建议在数据库端进行检查,然后将 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;      

  };

});
于 2012-04-07T20:28:05.360 回答
2

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.

于 2012-04-07T21:31:12.117 回答
0
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
;
于 2012-04-07T20:29:46.287 回答