如何尊重 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_products_by_xxx()
类型的函数中,即 return product_id
数组,正确 selected 和排序。
- 将所有产品列的复杂性封装在一个采用
product_id array
作为参数的独特函数 list_products()
中。
- 执行
select * from list_products(select_products_by_xxx())
以获得每个xxx
函数的期望结果。
例如,以相反的顺序 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())) |
现在我想知道是否有任何其他更好的方法来降低成本,同时保持功能之间的可分离性。
我看到两个有前途的策略:
- 至于
explain
子句和问题本身,似乎在 list_products()
中对 table product
进行了完整扫描。由于可能有数千种产品,更好的方法是扫描传递的数组。
xxx
函数可以重构为 return setof int
而不是 int[]
。但是,集合不能作为函数参数传递。
你很接近,你只需要添加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_id
是 UNIQUE
...
- unnest + left join returns 每个给定 ID 恰好一行 - 如果有的话,保留给定 ID 中的重复项。
product_id = any (tid)
折叠重复项。 (它通常会导致更昂贵的查询计划的原因之一。)
如果给定数组中没有重复项,则没有区别。如果可能存在重复项并且您想要折叠它们,那么您的任务是不明确的,因为未定义保留哪个位置。
这是我的(极其简化的)产品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_products_by_xxx()
类型的函数中,即 returnproduct_id
数组,正确 selected 和排序。 - 将所有产品列的复杂性封装在一个采用
product_id array
作为参数的独特函数list_products()
中。 - 执行
select * from list_products(select_products_by_xxx())
以获得每个xxx
函数的期望结果。
例如,以相反的顺序 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())) |
现在我想知道是否有任何其他更好的方法来降低成本,同时保持功能之间的可分离性。
我看到两个有前途的策略:
- 至于
explain
子句和问题本身,似乎在list_products()
中对 tableproduct
进行了完整扫描。由于可能有数千种产品,更好的方法是扫描传递的数组。 xxx
函数可以重构为 returnsetof int
而不是int[]
。但是,集合不能作为函数参数传递。
你很接近,你只需要添加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_id
是 UNIQUE
...
- unnest + left join returns 每个给定 ID 恰好一行 - 如果有的话,保留给定 ID 中的重复项。
product_id = any (tid)
折叠重复项。 (它通常会导致更昂贵的查询计划的原因之一。)
如果给定数组中没有重复项,则没有区别。如果可能存在重复项并且您想要折叠它们,那么您的任务是不明确的,因为未定义保留哪个位置。