我有一些用Informix风格的SQL编写的查询。具体而言,此查询选择客户订单中的项目。 (我稍微简化了表格结构,但我保留了有问题的部分。)
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
加入的原因是因为某个类别的商品存储在不同的库存表中.ship_box上的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
适用于整个广告资源/品牌/制造商表,然后LEFT JOIN
对订单商品进行处理。 Informix OUTER (...)
能够处理我要求的那个表的小选择(顺序中的项目)。
我做错了什么?有没有办法编写ANSI样式的查询,不会给我带来性能影响?如果必须,我会回到Informix风格的JOIN
,但我真的希望还有另一种方式。
谢谢。
编辑: 以下是SET EXPLAIN
的结果:
JOIN
):估计费用:15629 OUTER inventory, brand, manufacturer
?)答案 0 :(得分:2)
您需要拆分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 Server实例,但我发现执行计划有很大差异,我的查询显示执行一次Nested Loops (Left Outer Join)
,而你的查询显示Nested Loops (Inner Join)
执行了3次。当然看起来像是罪魁祸首。
您的LEFT JOIN ship_boxes
实际上是INNER JOIN
,因为您使用JOIN shipping_companies
加入该表。如果上述查询的结果不符合要求,则应将LEFT JOIN
更改为JOIN
。
答案 1 :(得分:0)
我对原始查询的分解很接近但有一些显着差异。
首先,shipping_company
绝对是一个内部联接。这是有道理的,因为这似乎是至少已发送给托运人的物品的读数。托运人可能尚未将所有内容加载到框中,因此从ship_boxes
开始向下是外部联接。
一个没有意义的外部联接是inventory
。库存中没有的物品是否已发送给托运人?也许我正在读这个关系错了,但在此期间,我把它改成了内部 - 也就是在连接链中跟随它的brand
和manufacturer
。这使得ship_boxes
成为唯一幸存的外部联接。
另一件令人好奇的事情是ship_boxes
与ship_items
和order_items
的双重关系。这会将整个盒子锁定为一个订单。如果整个订单都是一副扑克牌,那么那个盒子里就会浪费很多空间。假设一个盒子可以很容易地包含多个订单,我就消除了这个连接。现在,我意识到“ship_box”并不一定是整个集装箱。它可以是一个纸箱,其大小只是为了符合订单或订单的一部分。这没什么区别。连接到该框的order_id
可以来自ship_items
。在order_id
中有一个重复的ship_boxes
字段是一个不必要的冗余,据我所知,它在执行计划中没有任何区别。
我的最终查询,使用SQL Server:
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;
我创建了表并加载了一些测试数据。结果集是正确的,执行计划看起来很简单,正如我所期望的那样。
现在,如果我对库存的假设是错误的,那么将该链更改回外部联接实际上并不会改变执行计划。