用于可变列数查询的 Postgres CrossTab
Postgres CrossTab for query with variable number of columns
上下文
我正在 运行 为站点设置车辆路径问题的一个变体。布局是这样的。您有取货点、卡车和正在取货的物品。真正的诀窍在于,客户不会 运行 一天轮班的次数相同,因此这是可定制的。所以我会在 运行 时知道这一点,但不会提前。我正在尝试创建 return 的查询结果,其中包含每个公交车站的行、每个班次的列和每个项目 ID 的 json 数组,如下所示:
id | address | city | postalcode | shift1 | shift2 | shift3
-----+------------------+-----------+------------+----------+--------+--------
220 | 1471 N Lymestone | Anytown | 12345 | [14, 16] | [12] | [14]
221 | 108 D Ave | Anytown | 12345 | [15, 17] | | [15,16]
222 | 1434 Bryan Ave | Anytown | 12345 | [16] | [1,19] |
Table结构
我这里有三个相关的 table; stops
table 仅具有停靠点的地理位置,items
table 具有拾取项目的 id,shifts
table 具有班次和 itemstopassignemnt
table(请原谅命名,我继承了它)有一个 stop_id
shift_id
和 item_id
分配一个要拾取的项目在那个轮班的那个站。
查询
所以在 SO 上四处寻找之后,我提出了以下查询:
SELECT * FROM crosstab(
$$SELECT stop_id,
'shift' || shift_id,
json_agg(item_id)
FROM itemstopassignemnt
GROUP BY stop_id, shift_id
ORDER BY 1,2$$
) AS (stop_id INTEGER, shift1 JSON, shift2 JSON, shift3 JSON);
这适用于三班倒的情况。我可以将它放在以编程方式生成 sql 的函数中,以便它为用户添加的许多班次动态构建它。我认为该函数必须 return setof record
或 table()
。这是我的问题:
- 这是解决问题的最佳方法吗?
- 是否可以仅使用交叉表而不是动态生成的交叉表来完成此操作 sql?
- 如果没有,那么设置函数的最佳方法是什么?具体来说,我应该使用哪种 return 类型,因为无论如何我都可以在没有交叉表的情况下以编程方式完成此操作(循环轮班 table 并创建子选择)在这里使用交叉表是否有优势?
select
stop_id,
jsonb_object_agg('shift' || shift_id, items) as shift_items
from (
select
stop_id,
shift_id,
coalesce (
array_agg (item_id) filter (where item_id is not null),
array[]::int[]
) as items
from
itemstopassignemnt
right join
(
select shift_id, stop_id
from
(select distinct shift_id from itemStopAssignemnt) a
cross join
(select distinct stop_id from itemStopAssignemnt) b
) r using (stop_id, shift_id)
group by 1,2
) s
group by 1
;
stop_id | shift_items
---------+--------------------------------------------------------
220 | {"shift1": [14, 16], "shift2": [12], "shift3": [14]}
221 | {"shift1": [15, 17], "shift2": [], "shift3": [15, 16]}
222 | {"shift1": [16], "shift2": [1, 9], "shift3": []}
我没有加入stops
来缩短它,但应该是微不足道的。
上下文
我正在 运行 为站点设置车辆路径问题的一个变体。布局是这样的。您有取货点、卡车和正在取货的物品。真正的诀窍在于,客户不会 运行 一天轮班的次数相同,因此这是可定制的。所以我会在 运行 时知道这一点,但不会提前。我正在尝试创建 return 的查询结果,其中包含每个公交车站的行、每个班次的列和每个项目 ID 的 json 数组,如下所示:
id | address | city | postalcode | shift1 | shift2 | shift3
-----+------------------+-----------+------------+----------+--------+--------
220 | 1471 N Lymestone | Anytown | 12345 | [14, 16] | [12] | [14]
221 | 108 D Ave | Anytown | 12345 | [15, 17] | | [15,16]
222 | 1434 Bryan Ave | Anytown | 12345 | [16] | [1,19] |
Table结构
我这里有三个相关的 table; stops
table 仅具有停靠点的地理位置,items
table 具有拾取项目的 id,shifts
table 具有班次和 itemstopassignemnt
table(请原谅命名,我继承了它)有一个 stop_id
shift_id
和 item_id
分配一个要拾取的项目在那个轮班的那个站。
查询
所以在 SO 上四处寻找之后,我提出了以下查询:
SELECT * FROM crosstab(
$$SELECT stop_id,
'shift' || shift_id,
json_agg(item_id)
FROM itemstopassignemnt
GROUP BY stop_id, shift_id
ORDER BY 1,2$$
) AS (stop_id INTEGER, shift1 JSON, shift2 JSON, shift3 JSON);
这适用于三班倒的情况。我可以将它放在以编程方式生成 sql 的函数中,以便它为用户添加的许多班次动态构建它。我认为该函数必须 return setof record
或 table()
。这是我的问题:
- 这是解决问题的最佳方法吗?
- 是否可以仅使用交叉表而不是动态生成的交叉表来完成此操作 sql?
- 如果没有,那么设置函数的最佳方法是什么?具体来说,我应该使用哪种 return 类型,因为无论如何我都可以在没有交叉表的情况下以编程方式完成此操作(循环轮班 table 并创建子选择)在这里使用交叉表是否有优势?
select
stop_id,
jsonb_object_agg('shift' || shift_id, items) as shift_items
from (
select
stop_id,
shift_id,
coalesce (
array_agg (item_id) filter (where item_id is not null),
array[]::int[]
) as items
from
itemstopassignemnt
right join
(
select shift_id, stop_id
from
(select distinct shift_id from itemStopAssignemnt) a
cross join
(select distinct stop_id from itemStopAssignemnt) b
) r using (stop_id, shift_id)
group by 1,2
) s
group by 1
;
stop_id | shift_items
---------+--------------------------------------------------------
220 | {"shift1": [14, 16], "shift2": [12], "shift3": [14]}
221 | {"shift1": [15, 17], "shift2": [], "shift3": [15, 16]}
222 | {"shift1": [16], "shift2": [1, 9], "shift3": []}
我没有加入stops
来缩短它,但应该是微不足道的。