如何在PostgreSQL中插入另一个表中的值?

时间:2013-10-15 09:32:49

标签: postgresql subquery parameterized-query

我有一张引用其他表格的表格:

CREATE TABLE scratch
(
  id SERIAL PRIMARY KEY,
  name TEXT NOT NULL,
  rep_id INT NOT NULL REFERENCES reps,
  term_id INT REFERENCES terms
);
CREATE TABLE reps (
  id    SERIAL PRIMARY KEY,
  rep   TEXT NOT NULL UNIQUE
);
CREATE TABLE terms (
  id    SERIAL PRIMARY KEY,
  terms TEXT NOT NULL UNIQUE
);

我希望根据名称 rep 条款值添加一条新记录,即我没有相应的< strong> rep_id 也不是 term_id

现在我唯一的想法是:

insert into scratch (name, rep_id, term_id)
             values ('aaa', (select id from reps where rep='Dracula' limit 1), (select id from terms where terms='prepaid' limit 1));

我的问题是这个。我正在尝试使用参数化查询API(来自使用node-postgres包的节点),其中插入查询如下所示:

insert into scratch (name, rep_id, term_id) values ($1, $2, $3);

然后将$ 1,$ 2和$ 3的值数组作为单独的参数传递。最后,当我对参数化查询感到满意时,我们的想法是将它们提升为准备好的语句,以便使用最有效和最安全的方式来查询数据库。

但是,我很困惑,我怎么能用我的例子做这个,其中不同的表必须被推断。

P.S。我正在使用PostgreSQL 9.2,并且对PostgreSQL特定的解决方案没有任何问题。

编辑1

C:\Users\markk>psql -U postgres
psql (9.2.4)
WARNING: Console code page (437) differs from Windows code page (1252)
         8-bit characters might not work correctly. See psql reference
         page "Notes for Windows users" for details.
Type "help" for help.

postgres=# \c dummy
WARNING: Console code page (437) differs from Windows code page (1252)
         8-bit characters might not work correctly. See psql reference
         page "Notes for Windows users" for details.
You are now connected to database "dummy" as user "postgres".
dummy=# DROP TABLE scratch;
DROP TABLE
dummy=# CREATE TABLE scratch
dummy-# (
dummy(#   id SERIAL NOT NULL PRIMARY KEY,
dummy(#   name text NOT NULL UNIQUE,
dummy(#   rep_id integer NOT NULL,
dummy(#   term_id integer
dummy(# );
NOTICE:  CREATE TABLE will create implicit sequence "scratch_id_seq" for serial column "scratch.id"
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "scratch_pkey" for table "scratch"
NOTICE:  CREATE TABLE / UNIQUE will create implicit index "scratch_name_key" for table "scratch"
CREATE TABLE
dummy=# DEALLOCATE insert_scratch;
ERROR:  prepared statement "insert_scratch" does not exist
dummy=# PREPARE insert_scratch (text, text, text) AS
dummy-# INSERT INTO scratch (name, rep_id, term_id)
dummy-# SELECT $1, r.id, t.id
dummy-# FROM reps r, terms t
dummy-# WHERE r.rep = $2 AND t.terms = $3
dummy-# RETURNING id, name, $2 rep, $3 terms;
PREPARE
dummy=# DEALLOCATE insert_scratch2;
ERROR:  prepared statement "insert_scratch2" does not exist
dummy=# PREPARE insert_scratch2 (text, text, text) AS
dummy-# INSERT INTO scratch (name, rep_id, term_id)
dummy-#              VALUES ($1, (SELECT id FROM reps WHERE rep=$2 LIMIT 1), (SELECT id FROM terms WHERE terms=$3 LIMIT 1))
dummy-# RETURNING id, name, $2 rep, $3 terms;
PREPARE
dummy=# EXECUTE insert_scratch ('abc', 'Snowhite', '');
 id | name | rep | terms
----+------+-----+-------
(0 rows)


INSERT 0 0
dummy=# EXECUTE insert_scratch2 ('abc', 'Snowhite', '');
 id | name |   rep    | terms
----+------+----------+-------
  1 | abc  | Snowhite |
(1 row)


INSERT 0 1
dummy=# EXECUTE insert_scratch ('abcd', 'Snowhite', '30 days');
 id | name |   rep    |  terms
----+------+----------+---------
  2 | abcd | Snowhite | 30 days
(1 row)


INSERT 0 1
dummy=# EXECUTE insert_scratch2 ('abcd2', 'Snowhite', '30 days');
 id | name  |   rep    |  terms
----+-------+----------+---------
  3 | abcd2 | Snowhite | 30 days
(1 row)


INSERT 0 1
dummy=#

编辑2

我们可以利用rep_id是必需的这一事实,即使terms_id是可选的并且使用以下版本的INSERT-SELECT:

PREPARE insert_scratch (text, text, text) AS
INSERT INTO scratch (name, rep_id, term_id)
SELECT $1, r.id, t.id
FROM reps r
LEFT JOIN terms t ON t.terms = $3
WHERE r.rep = $2
RETURNING id, name, $2 rep, $3 terms;

但是,这个版本有两个问题:

  1. 缺少terms值(即'')和无效terms值之间没有区别(即完全从术语表中缺少非空值)。两者都被视为缺失条款。 (但是带有两个子查询的INSERT会遇到同样的问题)
  2. 版本取决于rep是必需的。但是如果rep_id也是可选的呢?
  3. 编辑3

    找到第2项的解决方案 - 消除对所需代表的依赖。另外使用WHERE语句有一个问题,即如果rep无效,sql不会失败 - 它只是插入0行,而我想在这种情况下显式失败。我的解决方案只是使用虚拟的一行CTE:

    PREPARE insert_scratch (text, text, text) AS
    WITH stub(x) AS (VALUES (0))
    INSERT INTO scratch (name, rep_id, term_id)
    SELECT $1, r.id, t.id
    FROM stub
    LEFT JOIN terms t ON t.terms = $3
    LEFT JOIN reps r ON r.rep = $2
    RETURNING id, name, rep_id, term_id;
    

    如果rep丢失或无效,此sql将尝试在rep_id字段中插入NULL,并且由于字段为NOT NULL,将引发错误 - 正是我需要的。如果进一步我决定使rep可选 - 没问题,同样的SQL也适用于它。

1 个答案:

答案 0 :(得分:2)

INSERT into scratch (name, rep_id, term_id)
SELECT 'aaa'
        , r.id 
        , t.id
FROM reps r , terms t -- essentially an outer join
WHERE r.rep = 'Dracula'
  AND t.terms = 'prepaid'
        ;

注意:

  • 你不需要丑陋的LIMIT,因为r.rep和t.terms是唯一的(候选键)
  • 您可以用FROM a, b
  • 替换FROM a FULL OUTER JOIN b
  • scratch表可能需要UNIQUE约束on (rep_id, term_it)term_id的可为空性值得怀疑)

更新:与准备好的查询as found in the Documentation

相同
PREPARE hoppa (text, text,text) AS
        INSERT into scratch (name, rep_id, term_id)
        SELECT $1 , r.id , t.id
        FROM reps r , terms t -- essentially an outer join
        WHERE r.rep = $2
        AND t.terms = $3
        ;
EXECUTE hoppa ('bbb', 'Dracula' , 'prepaid' );

SELECT * FROM scratch;

UPDATE2:测试数据

DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp ;
SET search_path=tmp;

CREATE TABLE reps ( id    SERIAL PRIMARY KEY, rep   TEXT NOT NULL UNIQUE);
CREATE TABLE terms ( id    SERIAL PRIMARY KEY, terms TEXT NOT NULL UNIQUE);
CREATE TABLE scratch ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, rep_id INT NOT NULL REFERENCES reps, term_id INT REFERENCES terms);

INSERT INTO  reps(rep) VALUES( 'Dracula' );
INSERT INTO  terms(terms) VALUES( 'prepaid' );

结果:

NOTICE:  drop cascades to 3 other objects
DETAIL:  drop cascades to table tmp.reps
drop cascades to table tmp.terms
drop cascades to table tmp.scratch
DROP SCHEMA
CREATE SCHEMA
SET
CREATE TABLE
CREATE TABLE
CREATE TABLE
INSERT 0 1
INSERT 0 1
INSERT 0 1
PREPARE
INSERT 0 1
 id | name | rep_id | term_id 
----+------+--------+---------
  1 | aaa  |      1 |       1
  2 | bbb  |      1 |       1
(2 rows)