如何将 SQL 从旧的 Informix 风格转换为 ANSI 风格?
How to convert SQL from old Informix-style to ANSI-style?
我有一些用 Informix 风格编写的查询 SQL。具体来说,此查询选择客户订单中的项目。 (我稍微简化了 table 结构,但我保留了有问题的部分。)
SELECT ordi.line_no, ordi.item_code, ordi.desc, ordi.price,
shpi.location, shpi.status, shpi.ship_code,
box.box_no, box.tracking_no, shpc.ship_co, mfr.mfr_name,
sum(shpi.ship_qty), sum(shpi.net_cost)
FROM order_items ordi, ship_items shpi, OUTER ship_boxes box,
shipping_companies shpc, OUTER (inventory invt, brand, manufacturer mfr)
WHERE ordi.order_id = ?
AND shpi.order_id = ordi.order_id AND shpi.line_no = ordi.line_no
AND box.order_id = ordi.order_id AND box.box_no = shpi.box_no
AND shp.shipper_code = shpi.shipper_code
AND invt.item_code = ordi.item_code
AND brand.brand_no = invt.brand_no
AND mfr.mfr_code = brand.mfr_code
GROUP BY 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
ORDER BY ordi.line_no ASC;
(OUTER
加入库存的原因是因为某些 class 物品存储在不同的库存 table 中。[=55 上的 OUTER
=] 用于尚未包装的物品。)
我用标准的 ANSI 风格 JOIN
重写了它。这是我得到的:
SELECT ordi.line_no, ordi.item_code, ordi.desc, ordi.price, shpi.location,
shpi.status, shpi.ship_code, box.box_no, box.tracking_no, shpc.ship_co,
mfr.mfr_name, sum(shpi.ship_qty), sum(shpi.net_cost)
FROM order_items ordi
JOIN ship_items shpi ON shpi.order_id = ordi.order_id
AND shpi.line_no = ordi.line_no
LEFT JOIN ship_boxes box ON box.order_id = ordi.order_id
AND box.box_no = shpi.box_no
JOIN shipping_companies shpc ON shpc.shipper_code = shpi.shipper_code
LEFT JOIN (inventory invt
JOIN brand ON brand.brand_no = invt.brand_no
JOIN manufacturer mfr ON mfr.mfr_code = brand.mfr_code
) ON invt.item_code = ordi.item_code
WHERE ordi.order_id = ?
GROUP BY 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
ORDER BY ordi.line_no ASC;
结果集完全一样,但是性能命中了将近2个数量级。对于包含 50 件商品的订单,第一个查询大约需要 50 毫秒,而第二个大约需要 5 秒。 运行 Explain 给出第一个查询的成本为 25,第二个查询的成本为 14403。我能够确定复杂的库存连接的差异:Informix 样式的查询将其执行为 3 INDEX PATH
/ NESTED LOOP JOIN
,每个成本为 1; ANSI JOIN
作为 SEQUENTIAL SCAN
执行,当时成本为 383,加起来超过 14K 点。
似乎 ANSI JOIN
在整个库存/品牌/制造商 table 上工作,然后 LEFT JOIN
将其添加到订单项目。 Informix OUTER (...)
能够处理我要求的 table 的一小部分选择(订单中的项目)。
我做错了什么?有没有一种方法可以编写不会给我带来性能影响的查询 ANSI 样式?如果必须的话,我会回到 Informix 风格 JOIN
,但我真的希望有另一种方式。
谢谢。
编辑: 以下是 SET EXPLAIN
:
的结果
- 原始查询:预计成本:18
- 我的重写(显式
JOIN
的):估计成本:15629
- @HartCO 的建议(拆分库存部分):估计成本:18(但数据是否相同?为什么不像
OUTER inventory, brand, manufacturer
?)
您需要拆分 Inventory
加入部分并将其更改为 LEFT JOIN
:
SELECT ordi.line_no , ordi.item_code , ordi.DESC , ordi.price
, shpi.location , shpi.STATUS , shpi.ship_code , box.box_no
, box.tracking_no , shpc.ship_co , mfr.mfr_name
, sum(shpi.ship_qty)
, sum(shpi.net_cost)
FROM order_items ordi
JOIN ship_items shpi ON shpi.order_id = ordi.order_id
AND shpi.line_no = ordi.line_no
LEFT JOIN ship_boxes box ON box.order_id = ordi.order_id
AND box.box_no = shpi.box_no
LEFT JOIN shipping_companies shpc ON shpc.shipper_code = box.shipper_code
LEFT JOIN inventory invt ON invt.item_code = ordi.item_code
LEFT JOIN brand ON brand.brand_no = invt.brand_no
LEFT JOIN manufacturer mfr ON mfr.mfr_code = brand.mfr_code
WHERE ordi.order_id = ?
GROUP BY 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
ORDER BY ordi.line_no ASC;
注意:我只有一个 SQL 服务器实例要测试,但我发现执行计划有很大差异,我的查询显示 Nested Loops (Left Outer Join)
执行一次,而你的显示 Nested Loops (Inner Join)
被执行了 3 次。看起来确实是罪魁祸首。
您的 LEFT JOIN ship_boxes
实际上是一个 INNER JOIN
,因为您使用 JOIN shipping_companies
加入了那个 table。如果上述查询的结果不是您想要的,您应该将两者从 LEFT JOIN
更改为 JOIN
.
我对原始查询的分解很接近,但有一些显着差异。
首先,shipping_company
肯定是内连接。这是有道理的,因为这似乎是至少已发送给托运人的物品的读数。托运人可能还没有把所有东西都装进箱子里,所以从 ship_boxes
往下是外连接。
一个没有意义的外连接是inventory
。是否可以将不在库存中的物品发送给托运人?也许我对这种关系的理解有误,但与此同时,我将其更改为内部 - 也是连接链中紧随其后的 brand
和 manufacturer
。这使得 ship_boxes
成为唯一幸存的外部连接。
另一件令人好奇的事情是 ship_boxes
与 ship_items
和 order_items
的双重关系。这会将整个盒子锁定为一个订单。如果整个订单是一副扑克牌,那么那个盒子里会浪费很多 space。假设一个盒子很容易包含多个订单,我消除了这种联系。现在,我意识到 "ship_box" 不一定是整个集装箱。它可以是一个大小刚好适合订单或部分订单的纸板箱。那没有区别。连接到盒子的order_id
可以从ship_items
得到。在 ship_boxes
中有一个重复的 order_id
字段是不必要的冗余,据我所知,这对执行计划没有影响。
我的最终查询,使用 SQL 服务器:
select ordi.line_no, ordi.item_code, ordi.item_desc, ordi.price,
shpi.location, shpi.status, shpi.ship_code,
box.box_no, box.tracking_no, shpc.ship_co, mfr.mfr_name,
sum(shpi.ship_qty), sum(shpi.net_cost)
from order_items ordi
join ship_items shpi
on shpi.order_id = ordi.order_id
and shpi.line_no = ordi.line_no
left join ship_boxes box
on box.box_no = shpi.box_no --AND box.order_id = ordi.order_id
join shipping_companies shpc
on shpc.shipper_code = shpi.shipper_code
join inventory invt
on invt.item_code = ordi.item_code
join brand
on brand.brand_no = invt.brand_no
join manufacturer mfr
on mfr.mfr_code = brand.mfr_code
where ordi.order_id = 1
group by ordi.line_no, ordi.item_code, ordi.item_desc, ordi.price,
shpi.location, shpi.status, shpi.ship_code,
box.box_no, box.tracking_no, shpc.ship_co, mfr.mfr_name
order by ordi.line_no;
我创建了表格并加载了一些测试数据。结果集是正确的,执行计划看起来简单而正确,符合我的预期。
现在,如果我关于库存的假设是错误的,将该链改回外部连接实际上不会改变执行计划。
我有一些用 Informix 风格编写的查询 SQL。具体来说,此查询选择客户订单中的项目。 (我稍微简化了 table 结构,但我保留了有问题的部分。)
SELECT ordi.line_no, ordi.item_code, ordi.desc, ordi.price,
shpi.location, shpi.status, shpi.ship_code,
box.box_no, box.tracking_no, shpc.ship_co, mfr.mfr_name,
sum(shpi.ship_qty), sum(shpi.net_cost)
FROM order_items ordi, ship_items shpi, OUTER ship_boxes box,
shipping_companies shpc, OUTER (inventory invt, brand, manufacturer mfr)
WHERE ordi.order_id = ?
AND shpi.order_id = ordi.order_id AND shpi.line_no = ordi.line_no
AND box.order_id = ordi.order_id AND box.box_no = shpi.box_no
AND shp.shipper_code = shpi.shipper_code
AND invt.item_code = ordi.item_code
AND brand.brand_no = invt.brand_no
AND mfr.mfr_code = brand.mfr_code
GROUP BY 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
ORDER BY ordi.line_no ASC;
(OUTER
加入库存的原因是因为某些 class 物品存储在不同的库存 table 中。[=55 上的 OUTER
=] 用于尚未包装的物品。)
我用标准的 ANSI 风格 JOIN
重写了它。这是我得到的:
SELECT ordi.line_no, ordi.item_code, ordi.desc, ordi.price, shpi.location,
shpi.status, shpi.ship_code, box.box_no, box.tracking_no, shpc.ship_co,
mfr.mfr_name, sum(shpi.ship_qty), sum(shpi.net_cost)
FROM order_items ordi
JOIN ship_items shpi ON shpi.order_id = ordi.order_id
AND shpi.line_no = ordi.line_no
LEFT JOIN ship_boxes box ON box.order_id = ordi.order_id
AND box.box_no = shpi.box_no
JOIN shipping_companies shpc ON shpc.shipper_code = shpi.shipper_code
LEFT JOIN (inventory invt
JOIN brand ON brand.brand_no = invt.brand_no
JOIN manufacturer mfr ON mfr.mfr_code = brand.mfr_code
) ON invt.item_code = ordi.item_code
WHERE ordi.order_id = ?
GROUP BY 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
ORDER BY ordi.line_no ASC;
结果集完全一样,但是性能命中了将近2个数量级。对于包含 50 件商品的订单,第一个查询大约需要 50 毫秒,而第二个大约需要 5 秒。 运行 Explain 给出第一个查询的成本为 25,第二个查询的成本为 14403。我能够确定复杂的库存连接的差异:Informix 样式的查询将其执行为 3 INDEX PATH
/ NESTED LOOP JOIN
,每个成本为 1; ANSI JOIN
作为 SEQUENTIAL SCAN
执行,当时成本为 383,加起来超过 14K 点。
似乎 ANSI JOIN
在整个库存/品牌/制造商 table 上工作,然后 LEFT JOIN
将其添加到订单项目。 Informix OUTER (...)
能够处理我要求的 table 的一小部分选择(订单中的项目)。
我做错了什么?有没有一种方法可以编写不会给我带来性能影响的查询 ANSI 样式?如果必须的话,我会回到 Informix 风格 JOIN
,但我真的希望有另一种方式。
谢谢。
编辑: 以下是 SET EXPLAIN
:
- 原始查询:预计成本:18
- 我的重写(显式
JOIN
的):估计成本:15629 - @HartCO 的建议(拆分库存部分):估计成本:18(但数据是否相同?为什么不像
OUTER inventory, brand, manufacturer
?)
您需要拆分 Inventory
加入部分并将其更改为 LEFT JOIN
:
SELECT ordi.line_no , ordi.item_code , ordi.DESC , ordi.price
, shpi.location , shpi.STATUS , shpi.ship_code , box.box_no
, box.tracking_no , shpc.ship_co , mfr.mfr_name
, sum(shpi.ship_qty)
, sum(shpi.net_cost)
FROM order_items ordi
JOIN ship_items shpi ON shpi.order_id = ordi.order_id
AND shpi.line_no = ordi.line_no
LEFT JOIN ship_boxes box ON box.order_id = ordi.order_id
AND box.box_no = shpi.box_no
LEFT JOIN shipping_companies shpc ON shpc.shipper_code = box.shipper_code
LEFT JOIN inventory invt ON invt.item_code = ordi.item_code
LEFT JOIN brand ON brand.brand_no = invt.brand_no
LEFT JOIN manufacturer mfr ON mfr.mfr_code = brand.mfr_code
WHERE ordi.order_id = ?
GROUP BY 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
ORDER BY ordi.line_no ASC;
注意:我只有一个 SQL 服务器实例要测试,但我发现执行计划有很大差异,我的查询显示 Nested Loops (Left Outer Join)
执行一次,而你的显示 Nested Loops (Inner Join)
被执行了 3 次。看起来确实是罪魁祸首。
您的 LEFT JOIN ship_boxes
实际上是一个 INNER JOIN
,因为您使用 JOIN shipping_companies
加入了那个 table。如果上述查询的结果不是您想要的,您应该将两者从 LEFT JOIN
更改为 JOIN
.
我对原始查询的分解很接近,但有一些显着差异。
首先,shipping_company
肯定是内连接。这是有道理的,因为这似乎是至少已发送给托运人的物品的读数。托运人可能还没有把所有东西都装进箱子里,所以从 ship_boxes
往下是外连接。
一个没有意义的外连接是inventory
。是否可以将不在库存中的物品发送给托运人?也许我对这种关系的理解有误,但与此同时,我将其更改为内部 - 也是连接链中紧随其后的 brand
和 manufacturer
。这使得 ship_boxes
成为唯一幸存的外部连接。
另一件令人好奇的事情是 ship_boxes
与 ship_items
和 order_items
的双重关系。这会将整个盒子锁定为一个订单。如果整个订单是一副扑克牌,那么那个盒子里会浪费很多 space。假设一个盒子很容易包含多个订单,我消除了这种联系。现在,我意识到 "ship_box" 不一定是整个集装箱。它可以是一个大小刚好适合订单或部分订单的纸板箱。那没有区别。连接到盒子的order_id
可以从ship_items
得到。在 ship_boxes
中有一个重复的 order_id
字段是不必要的冗余,据我所知,这对执行计划没有影响。
我的最终查询,使用 SQL 服务器:
select ordi.line_no, ordi.item_code, ordi.item_desc, ordi.price,
shpi.location, shpi.status, shpi.ship_code,
box.box_no, box.tracking_no, shpc.ship_co, mfr.mfr_name,
sum(shpi.ship_qty), sum(shpi.net_cost)
from order_items ordi
join ship_items shpi
on shpi.order_id = ordi.order_id
and shpi.line_no = ordi.line_no
left join ship_boxes box
on box.box_no = shpi.box_no --AND box.order_id = ordi.order_id
join shipping_companies shpc
on shpc.shipper_code = shpi.shipper_code
join inventory invt
on invt.item_code = ordi.item_code
join brand
on brand.brand_no = invt.brand_no
join manufacturer mfr
on mfr.mfr_code = brand.mfr_code
where ordi.order_id = 1
group by ordi.line_no, ordi.item_code, ordi.item_desc, ordi.price,
shpi.location, shpi.status, shpi.ship_code,
box.box_no, box.tracking_no, shpc.ship_co, mfr.mfr_name
order by ordi.line_no;
我创建了表格并加载了一些测试数据。结果集是正确的,执行计划看起来简单而正确,符合我的预期。
现在,如果我关于库存的假设是错误的,将该链改回外部连接实际上不会改变执行计划。