插入多个表并选择第一个结果

时间:2015-11-18 13:53:31

标签: sql postgresql postgresql-9.4

我们说我有两张表实现了一个非常简单的发票系统(注意:架构无法更改):

create table invoices(
  id serial primary key,
  parent_invoice_id int null references invoices(id),
  name text not null
);
create table line_items(
  id serial primary key,
  invoice_id int not null references invoices(id),
  amount int not null
);

用户有能力克隆"发票并将其引用原始"父母"发票。在系统中,克隆后直接需要发票(但不需要line_items)。因此,在克隆发票后,必须返回新发票。这是我用来克隆发票的SQL:

with new_invoice_row as (
  insert into invoices (parent_invoice_id, name)
  values (12345/*invoice_to_clone_id*/, 'Hello World')
  returning *
),
new_line_item_rows as (
  insert into line_items (invoice_id, amount)
  select
    new_invoice_row.id, line_item.amount
  from line_items
  cross join new_invoice_row
  where
    line_item.invoice_id = 12345/*invoice_to_clone_id*/
  returning id
)
select * from new_invoice_row;

问题:

  1. cross join表现良好吗?我想能够删除cross join以减少必须进行加入,但它不会运行(错误:missing FROM-clause entry for table "new_invoice_row"):

    ...
    insert into line_items (invoice_id, amount)
    select
      new_invoice_row.id, line_item.amount
    from line_items
    where
      line_item.invoice_id = 12345
    returning id
    ...
    
  2. 是否可以删除returning id语句的new_line_item_rows部分?我们不需要新的订单项,因此如果可以提高性能,我希望避免额外的开销。

  3. 我应该停止使用查询并将所有这些移动到一个函数中吗?系统最初使用的是MS SQL数据库,因此我更熟悉使用declare并且有多个语句使用该变量。

2 个答案:

答案 0 :(得分:1)

第一个查询只能返回idparent_invoice_id。 使用第二个值以避免重写参数(作为防止打字错误的保护)。 交叉连接是必要且正确的。 您可以在第二个查询中跳过returning *。 虽然使用起来可能很方便,但不需要功能。

with new_invoice_row as (
  insert into invoices (parent_invoice_id, name)
  values (12345, 'Hello World')
  returning id, parent_invoice_id
),
new_line_item_rows as (
  insert into line_items (invoice_id, amount)
  select
    new_invoice_row.id, line_items.amount
  from line_items
  cross join new_invoice_row
  where
    line_items.invoice_id = new_invoice_row.parent_invoice_id
)
select * from new_invoice_row;

答案 1 :(得分:0)

create table invoices(
  id serial primary key,
  parent_invoice_id int null references invoices(id),
  name text not null
);
INSERT INTO invoices(parent_invoice_id, name) VALUES
 ( NULL, 'One')
,( 1, 'two')
,( NULL, 'three')
 ;
create table line_items(
  id serial primary key,
  invoice_id int not null references invoices(id),
  amount int not null
);
INSERT INTO line_items (invoice_id, amount) VALUES
 (1, 10)
,(1, 11)
,(2, 21)
,(2, 22)
,(3, 33)
   ;


-- for demonstration purposes: the clone+insert as a prepared statement
-- (this is *not* necessary, only convenient)
PREPARE clone_the_invoice (INTEGER, text, INTEGER) AS
    WITH new_invoice_row as (
    INSERT into invoices (parent_invoice_id, name)
    VALUES ( $1 /*invoice_to_clone_id*/, $2 /*name */ )
    RETURNING id)
, new_line_item_rows as (
    INSERT into line_items (invoice_id, amount)
    SELECT new_invoice_row.id, $3 /* amount */
    FROM new_invoice_row
    RETURNING id
    )
SELECT * FROM new_line_item_rows
        ;

 -- call the prepared statement.
 -- This will clone invoice#2, 
 -- and insert one row in items, referring to the cloned row
 -- it returns the new item's id, which is sufficient to
 -- find the invoice.id too, when needed.
 -- -----------------------------------------------------------------
EXECUTE clone_the_invoice (2, 'four', 123);


 -- Chek the result
SELECT
  iv.id
  , iv.parent_invoice_id
  , iv.name
  , li.id AS lineid
  , li.amount
FROM invoices iv
JOIN line_items li ON li.invoice_id = iv.id
        ;

结果:

CREATE TABLE
INSERT 0 3
CREATE TABLE
INSERT 0 5
PREPARE
 id 
----
  6
(1 row)

 id | parent_invoice_id | name  | lineid | amount 
----+-------------------+-------+--------+--------
  1 |                   | One   |      1 |     10
  1 |                   | One   |      2 |     11
  2 |                 1 | two   |      3 |     21
  2 |                 1 | two   |      4 |     22
  3 |                   | three |      5 |     33
  4 |                 2 | four  |      6 |    123
(6 rows)

对于非平凡的情况,FK将需要一个支持索引(这不会自动添加,因此您应该手动执行此操作)

CREATE INDEX ON invoices (parent_invoice_id);
CREATE INDEX ON line_items (invoice_id);

更新:如果您坚持要退回新发票,请转到:

PREPARE clone_the_invoice2 (INTEGER, text, integer) AS
    WITH new_invoice_row as (
    INSERT into invoices (parent_invoice_id, name)
    VALUES ( $1 /*invoice_to_clone_id*/, $2 )
    RETURNING *
    )
, new_line_item_rows as (
    INSERT into line_items (invoice_id, amount)
    SELECT new_invoice_row.id, $3
    FROM new_invoice_row
    RETURNING *
    )
SELECT iv.*
FROM new_invoice_row iv
JOIN new_line_item_rows new ON new.invoice_id = iv.id
    ;

更新2(OP似乎也希望克隆细节线

    -- Clone an invoice
    -- INCLUDING all associated line_items
    -- --------------------------------------
PREPARE clone_the_invoice3 (INTEGER, text) AS
    WITH new_invoice_row as (
    INSERT into invoices (parent_invoice_id, name)
    VALUES      ( $1 /*invoice_to_clone_id*/
                , $2 /* name */
                )
    RETURNING *
    )
, new_line_item_rows as (
    INSERT into line_items (invoice_id, amount)
    SELECT cl.id                -- the cloned invoice
        , it.amount
    FROM line_items it
    CROSS JOIN new_invoice_row cl
    WHERE it.invoice_id = $1    -- The original invoice
    RETURNING *
    )
SELECT iv.*
FROM new_invoice_row iv
JOIN new_line_item_rows new ON new.invoice_id = iv.id
        ;


EXECUTE clone_the_invoice3 (2, 'four');