0

我创建了这个 SQL 来查找 X 天没有订购的客户。

它正在返回一个结果集,所以这篇文章主要是为了获得第二意见,以及可能的优化。

SELECT o.order_id,
       o.order_status,
       o.order_created,
       o.user_id,
       i.identity_firstname,
       i.identity_email,

  (SELECT COUNT(*)
   FROM orders o2
   WHERE o2.user_id=o.user_id
     AND o2.order_status=1) AS order_count,

  (SELECT o4.order_created
   FROM orders o4
   WHERE o4.user_id=o.user_id
     AND o4.order_status=1
   ORDER BY o4.order_created DESC LIMIT 1) AS last_order
FROM orders o
INNER JOIN user_identities ui ON o.user_id=ui.user_id
INNER JOIN identities i ON ui.identity_id=i.identity_id
   AND i.identity_email!=''
INNER JOIN subscribers s ON i.identity_id=s.identity_id
  AND s.subscriber_status=1
  AND s.subsriber_type=e
  AND s.subscription_id=1
WHERE DATE(o.order_created) = "2013-12-14"
  AND o.order_status=1
  AND o.user_id NOT IN
    (SELECT o3.user_id
     FROM orders o3
     WHERE o3.user_id=o.user_id
       AND o3.order_status=1
       AND DATE(o3.order_created) > "2013-12-14")

你们能找到这个 SQL 的任何潜在问题吗?日期是动态插入的。

我投入生产的最终 SQL 基本上只包括 o.order_id、i.identity_id 和 o.order_count - 这个 order_count 需要是正确的。其他选定的字段和“last_order”子查询将不包括在内,仅用于测试。

这应该给我一个用户列表,这些用户在特定日期有最后一个订单,并且是时事通讯订阅者。我特别怀疑 WHERE 子句中 NOT IN 部分和 order_count 子查询的正确性。

4

2 回答 2

2

有几个问题:

A. 在可索引列上使用函数

DATE(order_created)您正在通过与某个常数进行比较来搜索订单。这是一个糟糕的主意,因为 a)DATE()函数对每一行 (CPU) 执行,b) 数据库不能在列上使用索引(假设存在)

B. 使用WHERE ID NOT IN (...)

使用 aNOT IN (...)几乎总是一个坏主意,因为优化器通常会遇到这种结构的问题,并且经常会错误地执行计划。您几乎总是可以将其表示为带有条件的外连接,该WHERE条件使用连接列的条件过滤未命中IS NULL(并增加了不需要的附带好处DISTINCT,因为只返回一个未命中)

C. 离开过滤掉大部分行的连接太晚了

越早通过不进行连接来屏蔽行越好。您可以通过在联接表列表中加入不太可能匹配较早的表来做到这一点,并将非键条件放入联接而不是 where 子句以尽早排除行。无论如何,一些优化器,但我经常发现他们没有

D. 避免像瘟疫这样的相关子查询!

您有几个相关的子查询 -为主表的每一行执行的子查询。这真是一个非常糟糕的主意。同样,有时优化器可以将它们制作成一个连接,但为什么要依赖(希望)它。大多数相关的子查询可以表示为一个连接;你的例子也不例外。

考虑到上述情况,有一些具体的变化:

  • o2 和 o4 是同一个join,所以 o4 可以完全省掉——只要转换成 join 后使用 o2
  • DATE(order_created) = "2013-12-14"应该写成order_created between "2013-12-14 00:00:00" and "2013-12-14 23:59:59"

这个查询应该是你想要的:

SELECT
    o.order_id,
    o.order_status,
    o.order_created,
    o.user_id,
    i.identity_firstname,
    i.identity_email,
    count(o2.user_id) AS order_count,
    max(o2.order_created) AS last_order
FROM orders o
LEFT JOIN orders o2 ON o2.user_id = o.user_id AND o2.order_status=1
LEFT JOIN orders o3 ON o3.user_id = o.user_id 
    AND o3.order_status=1
    AND o3.order_created >= "2013-12-15 00:00:00"
JOIN user_identities ui ON o.user_id=ui.user_id
JOIN identities i ON ui.identity_id=i.identity_id AND i.identity_email != ''
JOIN subscribers s ON i.identity_id=s.identity_id
  AND s.subscriber_status=1
  AND s.subsriber_type=e
  AND s.subscription_id=1
WHERE o.order_created between "2013-12-14 00:00:00" and "2013-12-14 23:59:59"
AND o.order_status=1
AND o3.order_created IS NULL -- This gets only missed joins on o3
GROUP BY
    o.order_id,
    o.order_status,
    o.order_created,
    o.user_id,
    i.identity_firstname,
    i.identity_email;

最后一行是如何实现与NOT IN (...)使用LEFT JOIN

免责声明:未经测试。

于 2014-01-17T12:21:51.897 回答
0

由于您没有发布任何表声明或示例数据,因此无法真正评论结果,但是您的查询有 3 个相关的子查询,这可能会使其性能不佳(好的,其中一个用于 last_order 并且仅用于测试)。

消除相关的子查询并用连接替换它们会给出这样的结果:-

SELECT o.order_id,
        o.order_status,
        o.order_created,
        o.user_id,
        i.identity_firstname,
        i.identity_email,
        Sub1.order_count,
        Sub2.last_order
FROM orders o
INNER JOIN user_identities ui ON o.user_id=ui.user_id
INNER JOIN identities i ON ui.identity_id=i.identity_id
   AND i.identity_email!=''
INNER JOIN subscribers s ON i.identity_id=s.identity_id
  AND s.subscriber_status=1
  AND s.subsriber_type=e
  AND s.subscription_id=1
LEFT OUTER JOIN
(
    SELECT user_id, COUNT(*) AS order_count
    FROM orders 
    WHERE order_status=1
    GROUP BY user_id
) Sub1
ON o.user_id = Sub1.user_id
LEFT OUTER JOIN
(
    SELECT user_id, MAX(order_created) as last_order
    FROM orders 
    WHERE order_status=1
    GROUP BY user_id
) AS Sub2
ON o.user_id = Sub2.user_id
LEFT OUTER JOIN
(
    SELECT DISTINCT user_id
    FROM orders 
    WHERE order_status=1
    AND DATE(order_created) > "2013-12-14"
) Sub3
ON o.user_id = Sub3.user_id
WHERE DATE(o.order_created) = "2013-12-14"
  AND o.order_status=1
  AND Sub3.user_id IS NULL
于 2014-01-17T10:30:23.043 回答