如何将 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:

的结果
  1. 原始查询:预计成本:18
  2. 我的重写(显式 JOIN 的):估计成本:15629
  3. @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。是否可以将不在库存中的物品发送给托运人?也许我对这种关系的理解有误,但与此同时,我将其更改为内部 - 也是连接链中紧随其后的 brandmanufacturer。这使得 ship_boxes 成为唯一幸存的外部连接。

另一件令人好奇的事情是 ship_boxesship_itemsorder_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;

我创建了表格并加载了一些测试数据。结果集是正确的,执行计划看起来简单而正确,符合我的预期。

现在,如果我关于库存的假设是错误的,将该链改回外部连接实际上不会改变执行计划。