如何编写此查询以避免笛卡尔积?
How to write this query to avoid cartesian product?
我想为订单创建一个 CSV 导出文件,显示 warehouse_id 每个 order_item 的发货地(如果有)。
为简洁起见,这里是相关架构:
create table o (id integer);
订单有很多order_item个:
create table oi (id integer, o_id integer, sku text, quantity integer);
对于 CSV 中的每个 order_item,我们希望显示一个 warehouse_id 从哪里发货。但这并没有存储在 order_item 中。它存储在发货中。
一个订单可以拆分成多个货件,可能来自不同的仓库。
create table s (id integer, o_id integer, warehouse_id integer);
货件也有很多货件:
create table si (id integer, s_id integer, oi_id integer, quantity_shipped integer);
我如何为每个 order_item 提取 warehouse_id,因为 warehouse_id 正在发货,但并非每个订单都已发货(可能没有发货记录或 shipment_items).
我们正在做这样的事情(简化):
select oi.sku, s.warehouse_id from oi
left join s on s.o_id = oi.o_id;
但是,如果一个订单有 2 个订单商品,我们称它们为 sku A 和 B。该订单被分成两批货,其中 A 从仓库“50”发货,然后第二批货从“200”发货 B .
我们想要的是像这样的 CSV 输出:
sku | warehouse_id
-----|--------------
A | 50
B | 200
但是我们得到的是某种笛卡尔积:
=================================
Here is the sample data:
select * from o;
id
----
1
(1 row)
select * from oi;
id | o_id | sku | quantity
----+------+-----+----------
1 | 1 | A | 1
2 | 1 | B | 1
(2 rows)
select * from s;
id | o_id | warehouse_id
----+------+--------------
1 | 1 | 50
2 | 1 | 200
(2 rows)
select * from si;
id | s_id | oi_id
----+------+------
1 | 1 | 1
2 | 2 | 2
(2 rows)
select oi.sku, s.warehouse_id from oi left join s on s.o_id = oi.o_id;
sku | warehouse_id
-----+--------------
A | 50
A | 200
B | 50
B | 200
(4 rows)
更新 ========
Per spencer,为了更清楚起见,我添加了一个具有不同 pk id 的不同示例。以下是 2 个示例订单。订单 2 包含项目 A、B、C。 A、B 从 200 号发货,C 从 201 号发货。订单 3 有 2 件商品 E 和 A。E 尚未发货,A 从同一个仓库“700”发货两次,(就像在后面一样订单)。
# select * from o;
id
----
2
3
(2 rows)
# select * from oi;
id | o_id | sku | quantity
-----+------+-----+----------
100 | 2 | A | 1
101 | 2 | B | 1
102 | 2 | C | 1
103 | 3 | E | 1
104 | 3 | A | 2
(5 rows)
# select * from s;
id | o_id | warehouse_id
-----+------+--------------
200 | 2 | 700
201 | 2 | 800
202 | 3 | 700
203 | 3 | 700
(4 rows)
# select * from si;
id | s_id | oi_id
-----+------+-------
300 | 200 | 100
301 | 200 | 101
302 | 201 | 102
303 | 202 | 104
304 | 203 | 104
(5 rows)
我认为这行得通,我使用 left join 保留报告中的 order_items,无论订单是否发货,我使用 group by 压缩来自同一仓库的多批货物。我相信这就是我需要的。
# select oi.o_id, oi.id, oi.sku, s.warehouse_id from oi left join si on si.oi_id = oi.id left join s on s.id = si.s_id group by oi.o_id, oi.id, oi.sku, s.warehouse_id order by oi.o_id;
o_id | id | sku | warehouse_id
------+-----+-----+--------------
2 | 102 | C | 800
2 | 101 | B | 700
2 | 100 | A | 700
3 | 104 | A | 700
3 | 103 | E |
(5 rows)
我认为你需要 si
table:
select oi.sku, s.warehouse_id
from si join
oi
on si.o_id = oi.o_id join
s
on s.s_id = si.s_id;
si
似乎是 table 之间的正确连接点 table。我不确定为什么还有另一个不使用它的连接键。
已发货的订单商品...
SELECT oi.id
, oi.sku
, s.warehouse_id
FROM oi
JOIN si ON si.oi_id = oi.id
JOIN s ON s.id = si.s_id
订购尚未发货的商品,使用反连接排除 si 中存在匹配行的行
SELECT oi.id
, oi.sku
, s.warehouse_id
FROM oi
JOIN s ON s.o_id = oi.o_id -- fk to fk shortcut join
-- anti-join
LEFT
JOIN si ON si.oi_id = oi.id
WHERE si.oi_id IS NULL
但这仍然会产生(部分)笛卡尔积。我们可以添加一个 GROUP BY 子句来折叠行...
GROUP BY si.oi_id
这不会避免产生中间笛卡尔积;添加 GROUP BY 子句会折叠集合。但是不确定将从 s
列值中的哪些匹配行返回。
这两个查询可以通过 UNION ALL 操作结合起来。如果我这样做,我可能会添加一个鉴别器列(每个查询中具有不同值的附加列,它会告诉哪个查询返回了一行。)
此套装可能符合 OP 问题中概述的规格。但我不认为这真的是需要归还的套装。确定一件物品应该从哪个仓库发货可能涉及多个因素...订购的总数量、每个仓库的可用数量、可以从一个仓库完成订单、哪个仓库离交货目的地更近等。
我不想给任何人留下这样的印象,即此查询实际上是 "fix" 笛卡尔积问题...此查询只是隐藏了一个更大的问题。
我想为订单创建一个 CSV 导出文件,显示 warehouse_id 每个 order_item 的发货地(如果有)。
为简洁起见,这里是相关架构:
create table o (id integer);
订单有很多order_item个:
create table oi (id integer, o_id integer, sku text, quantity integer);
对于 CSV 中的每个 order_item,我们希望显示一个 warehouse_id 从哪里发货。但这并没有存储在 order_item 中。它存储在发货中。
一个订单可以拆分成多个货件,可能来自不同的仓库。
create table s (id integer, o_id integer, warehouse_id integer);
货件也有很多货件:
create table si (id integer, s_id integer, oi_id integer, quantity_shipped integer);
我如何为每个 order_item 提取 warehouse_id,因为 warehouse_id 正在发货,但并非每个订单都已发货(可能没有发货记录或 shipment_items).
我们正在做这样的事情(简化):
select oi.sku, s.warehouse_id from oi
left join s on s.o_id = oi.o_id;
但是,如果一个订单有 2 个订单商品,我们称它们为 sku A 和 B。该订单被分成两批货,其中 A 从仓库“50”发货,然后第二批货从“200”发货 B .
我们想要的是像这样的 CSV 输出:
sku | warehouse_id
-----|--------------
A | 50
B | 200
但是我们得到的是某种笛卡尔积:
=================================
Here is the sample data:
select * from o;
id
----
1
(1 row)
select * from oi;
id | o_id | sku | quantity
----+------+-----+----------
1 | 1 | A | 1
2 | 1 | B | 1
(2 rows)
select * from s;
id | o_id | warehouse_id
----+------+--------------
1 | 1 | 50
2 | 1 | 200
(2 rows)
select * from si;
id | s_id | oi_id
----+------+------
1 | 1 | 1
2 | 2 | 2
(2 rows)
select oi.sku, s.warehouse_id from oi left join s on s.o_id = oi.o_id;
sku | warehouse_id
-----+--------------
A | 50
A | 200
B | 50
B | 200
(4 rows)
更新 ========
Per spencer,为了更清楚起见,我添加了一个具有不同 pk id 的不同示例。以下是 2 个示例订单。订单 2 包含项目 A、B、C。 A、B 从 200 号发货,C 从 201 号发货。订单 3 有 2 件商品 E 和 A。E 尚未发货,A 从同一个仓库“700”发货两次,(就像在后面一样订单)。
# select * from o;
id
----
2
3
(2 rows)
# select * from oi;
id | o_id | sku | quantity
-----+------+-----+----------
100 | 2 | A | 1
101 | 2 | B | 1
102 | 2 | C | 1
103 | 3 | E | 1
104 | 3 | A | 2
(5 rows)
# select * from s;
id | o_id | warehouse_id
-----+------+--------------
200 | 2 | 700
201 | 2 | 800
202 | 3 | 700
203 | 3 | 700
(4 rows)
# select * from si;
id | s_id | oi_id
-----+------+-------
300 | 200 | 100
301 | 200 | 101
302 | 201 | 102
303 | 202 | 104
304 | 203 | 104
(5 rows)
我认为这行得通,我使用 left join 保留报告中的 order_items,无论订单是否发货,我使用 group by 压缩来自同一仓库的多批货物。我相信这就是我需要的。
# select oi.o_id, oi.id, oi.sku, s.warehouse_id from oi left join si on si.oi_id = oi.id left join s on s.id = si.s_id group by oi.o_id, oi.id, oi.sku, s.warehouse_id order by oi.o_id;
o_id | id | sku | warehouse_id
------+-----+-----+--------------
2 | 102 | C | 800
2 | 101 | B | 700
2 | 100 | A | 700
3 | 104 | A | 700
3 | 103 | E |
(5 rows)
我认为你需要 si
table:
select oi.sku, s.warehouse_id
from si join
oi
on si.o_id = oi.o_id join
s
on s.s_id = si.s_id;
si
似乎是 table 之间的正确连接点 table。我不确定为什么还有另一个不使用它的连接键。
已发货的订单商品...
SELECT oi.id
, oi.sku
, s.warehouse_id
FROM oi
JOIN si ON si.oi_id = oi.id
JOIN s ON s.id = si.s_id
订购尚未发货的商品,使用反连接排除 si 中存在匹配行的行
SELECT oi.id
, oi.sku
, s.warehouse_id
FROM oi
JOIN s ON s.o_id = oi.o_id -- fk to fk shortcut join
-- anti-join
LEFT
JOIN si ON si.oi_id = oi.id
WHERE si.oi_id IS NULL
但这仍然会产生(部分)笛卡尔积。我们可以添加一个 GROUP BY 子句来折叠行...
GROUP BY si.oi_id
这不会避免产生中间笛卡尔积;添加 GROUP BY 子句会折叠集合。但是不确定将从 s
列值中的哪些匹配行返回。
这两个查询可以通过 UNION ALL 操作结合起来。如果我这样做,我可能会添加一个鉴别器列(每个查询中具有不同值的附加列,它会告诉哪个查询返回了一行。)
此套装可能符合 OP 问题中概述的规格。但我不认为这真的是需要归还的套装。确定一件物品应该从哪个仓库发货可能涉及多个因素...订购的总数量、每个仓库的可用数量、可以从一个仓库完成订单、哪个仓库离交货目的地更近等。
我不想给任何人留下这样的印象,即此查询实际上是 "fix" 笛卡尔积问题...此查询只是隐藏了一个更大的问题。