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

Inserting into multiple tables and selecting the first result

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

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
);

用户可以"clone" 发票并参考原始"parent" 发票。在系统中,克隆后直接需要invoice(但不需要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. 是否可以删除 new_line_item_rows 语句的 returning id 部分?不需要新的订单项,因此如果可以提高性能,我想避免额外的开销。

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

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 (  /*invoice_to_clone_id*/,  /*name */ )
    RETURNING id)
, new_line_item_rows as (
    INSERT into line_items (invoice_id, amount)
    SELECT new_invoice_row.id,  /* 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 (  /*invoice_to_clone_id*/,  )
    RETURNING *
    )
, new_line_item_rows as (
    INSERT into line_items (invoice_id, amount)
    SELECT new_invoice_row.id, 
    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      (  /*invoice_to_clone_id*/
                ,  /* 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 =     -- 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');

第一个查询可以return只有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;