如何尊重 PostgreSQL select 语句中数组的顺序

How to respect the order of an array in a PostgreSQL select sentence

这是我的(极其简化的)产品table和一些测试数据。

drop table if exists product cascade;

create table product (
  product_id  integer not null,
  reference   varchar,
  price       decimal(13,4),
  
  primary key (product_id)
);

insert into product (product_id, reference, price) values 
(1001, 'MX-232',    100.00),
(1011, 'AX-232',     20.00),
(1003, 'KKK 11',     11.00),
(1004, 'OXS SUPER',   0.35),
(1005, 'ROR-MOT',   200.00),
(1006, '234PPP',     30.50),
(1007, 'T555-NS',   110.25),
(1008, 'LM234-XS',  101.20),
(1009, 'MOTOR-22',   12.50),
(1010, 'MOTOR-11',   30.00),
(1002, 'XUL-XUL1',   40.00);

在现实生活中,列出产品列是一项教学任务,充满了联接、case-when-end 子句等。另一方面,还有大量查询需要完成,如按品牌分类的产品、特色产品、按标题、标签、范围或价格等分类的产品

我不想在每次执行查询时都重复和维护复杂的产品列列表,因此,我目前的方法是将查询过程分为两个任务:

例如,以相反的顺序 select product_id(如果这对应用程序有意义 select),这样的函数就可以做到这一点。

create or replace function select_products_by_inverse () 
returns int[]
as $$
  select 
    array_agg(product_id order by product_id desc)  
  from 
    product;
$$ language sql;

经测试可以正常工作

select * from select_products_by_inverse();

select_products_by_inverse                              |
--------------------------------------------------------|
{1011,1010,1009,1008,1007,1006,1005,1004,1003,1002,1001}|

为了封装查询的“列表”部分,我使用了这个函数(同样,为了示例的利益,它极其简化并且没有任何连接或大小写)。

create or replace function list_products (
    tid int[]
) 
returns table (
  id        integer,
  reference varchar,
  price     decimal(13,4)
)
as $$
  select
    product_id,
    reference,
    price
  from
    product
  where
    product_id = any (tid);
$$ language sql;

有效,但不遵守传递数组中产品的顺序。

select * from list_products(select_products_by_inverse());

id  |reference|price   |
----|---------|--------|
1001|MX-232   |100.0000|
1011|AX-232   | 20.0000|
1003|KKK 11   | 11.0000|
1004|OXS SUPER|  0.3500|
1005|ROR-MOT  |200.0000|
1006|234PPP   | 30.5000|
1007|T555-NS  |110.2500|
1008|LM234-XS |101.2000|
1009|MOTOR-22 | 12.5000|
1010|MOTOR-11 | 30.0000|
1002|XUL-XUL1 | 40.0000|

所以,问题是我正在传递 product_id 的自定义有序数组,但 list_products() 函数不遵守数组内的顺序。

显然,我可以在 list_products() 中包含一个 order by 子句,但请记住,顺序必须由 select_products_by_xxx() 函数确定以保持 list_products() 唯一。

有什么想法吗?


编辑

@adamkg 解决方案简单有效:添加一个通用的 order by 子句,如下所示:

order by array_position(tid, product_id);

然而,这意味着两次订购产品:第一次在 select_products_by_xxx() 内,然后在 list_products().

explain 探索呈现以下结果:

QUERY PLAN                                                            |
----------------------------------------------------------------------|
Sort  (cost=290.64..290.67 rows=10 width=56)                          |
  Sort Key: (array_position(select_products_by_inverse(), product_id))|
  ->  Seq Scan on product  (cost=0.00..290.48 rows=10 width=56)       |
        Filter: (product_id = ANY (select_products_by_inverse()))     |

现在我想知道是否有任何其他更好的方法来降低成本,同时保持功能之间的可分离性。

我看到两个有前途的策略:

你很接近,你只需要添加ORDER BY array_position(tid, product_id)

testdb=# create or replace function list_products (
    tid int[]
) 
returns table (
  id        integer,
  reference varchar,
  price     decimal(13,4)
)
as $$
  select
    product_id,
    reference,
    price
  from
    product
  where
    product_id = any (tid)
-- add this:
order by array_position(tid, product_id);
$$ language sql;
CREATE FUNCTION
testdb=# select * from list_products(select_products_by_inverse());
  id  | reference |  price   
------+-----------+----------
 1011 | AX-232    |  20.0000
 1010 | MOTOR-11  |  30.0000
 1009 | MOTOR-22  |  12.5000
 1008 | LM234-XS  | 101.2000
 1007 | T555-NS   | 110.2500
 1006 | 234PPP    |  30.5000
 1005 | ROR-MOT   | 200.0000
 1004 | OXS SUPER |   0.3500
 1003 | KKK 11    |  11.0000
 1002 | XUL-XUL1  |  40.0000
 1001 | MX-232    | 100.0000
(11 rows)



对于长数组,您通常会通过取消嵌套数组并加入主数组来获得(很多!)更高效的查询计划 table。在简单的情况下,这甚至可以保留数组的原始顺序而不添加 ORDER BY。行按顺序处理。但是没有任何保证,并且顺序可能会因更多连接或并行执行等而被破坏。可以肯定的是,添加 WITH ORDINALITY:

CREATE OR REPLACE FUNCTION list_products (tid int[])  -- VARIADIC?
  RETURNS TABLE (
   id        integer,
   reference varchar,
   price     decimal(13,4)
   )
  LANGUAGE sql STABLE AS
$func$
  SELECT product_id, p.reference, p.price
  FROM   unnest(tid) WITH ORDINALITY AS t(product_id, ord)
  JOIN   product p USING (product_id)  -- LEFT JOIN ?
  ORDER  BY t.ord
$func$;

快速、简单、安全。参见:

  • PostgreSQL unnest() with element number
  • Join against the output of an array unnest without creating a temp table

您可能想加入修饰符 VARIADIC,这样您就可以使用数组 ID 列表(默认最多 100 个项目)调用该函数.参见:

  • Return rows matching elements of input array in plpgsql function
  • Call a function with composite type as argument from native query in jpa

我会声明 STABLE function volatility.

您可以使用 LEFT JOIN 而不是 JOIN 来确保返回 所有 给定的 ID - 如果具有给定 ID 的行具有 NULL 值失踪了。

db<>fiddle here

注意数组中重复项的细微的逻辑差异。虽然 product_idUNIQUE ...

  • unnest + left join returns 每个给定 ID 恰好一行 - 如果有的话,保留给定 ID 中的重复项。
  • product_id = any (tid) 折叠重复项。 (它通常会导致更昂贵的查询计划的原因之一。)

如果给定数组中没有重复项,则没有区别。如果可能存在重复项并且您想要折叠它们,那么您的任务是不明确的,因为未定义保留哪个位置。