如何优化此查询以摆脱子查询?

时间:2010-09-18 16:17:33

标签: sql mysql

我有一个包含2个表的数据库:

表1:

CREATE TABLE IF NOT EXISTS `sales` (
  `sale_id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `sale_total` int(11) NOT NULL,
  `sale_date` date NOT NULL,
  `sale_status` int(11) NOT NULL,
  PRIMARY KEY (`sale_id`)
) ;

表2:

CREATE TABLE IF NOT EXISTS `users` (
  `user_id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(200) NOT NULL,
  `lastname` varchar(200) NOT NULL,
  `mail` varchar(200) NOT NULL,
  PRIMARY KEY (`user_id`)
);

我需要优化以下查询,以便它不使用子查询。我这可以使用连接完成,但我不确切知道如何。

SELECT name, lastname, mail
FROM users
WHERE user_id IN (
   SELECT user_id
   FROM sales
   WHERE sale_date < '2009-01-01'
   AND sale_total >100
   AND sale_status =4
)
AND user_id NOT IN (
    SELECT user_id
    FROM sales
    WHERE sale_date >= '2009-01-01'
)

3 个答案:

答案 0 :(得分:3)

使用连接替换IN子查询,使用null-left-joins替换NOT IN,使用GROUP BY为每个用户只返回一行:

SELECT users.name, users.lastname, users.mail
FROM users
JOIN sales AS s0 ON s0.user_id=users.user_id
LEFT JOIN sales AS s1 ON s1.user_id=users.user_id AND sale_date>='2009-01-01'
WHERE s1.sale_id IS NULL
AND s0.sale_date < '2009-01-01' AND s0.sale_total>100 AND s0.sale_status=4
GROUP BY users.user_id

答案 1 :(得分:2)

加入怎么样:

SELECT u.name, u.lastname, u.mail FROM users u
INNER JOIN sales s ON s.user_id = u.user_id
WHERE s.sale_date < '2009-01-01'
AND s.sale_total >100
AND s.sale_status =4

第二个查询是不必要的 - 它已经使用s.sale_date&lt; '2009-01-01'(也许你应该详细解释你的查询):

AND user_id NOT IN (
    SELECT user_id
    FROM sales
    WHERE sale_date >= '2009-01-01'
)

如果有必要 - 它会是这样的(未经测试):

SELECT u.name, u.lastname, u.mail FROM users u
INNER JOIN sales s ON s.user_id = u.user_id
RIGHT OUTER JOIN sales e ON e.user_id = u.user_id
WHERE s.sale_date < '2009-01-01'
AND s.sale_total >100
AND s.sale_status =4
AND e.sale_date >= '2009-01-01'
AND e.user_id is null

答案 2 :(得分:0)

我认为这是解决它的一种富有表现力的方式。您使用一个联接包括sales_status = 4等所有适当的销售。然后您使用带有having子句的第二个联接来排除在'2009-01-01'之后销售的任何用户。

性能取决于数据库引擎为您的数据提供的查询计划,因此您应该测试它以确保在替换原始查询之前性能更好。

select name, lastname, mail
from users 
inner join sales s1
    on users.user_id = s1.user_id
        and s1.sale_date < '2009-01-01'
        and s1.sale_total >100
        and s1.sale_status = 4
left join sales s2
    on s2.user_id = users.user_id
group by name, lastname, mail
having max(s2.sale_date) < '2009-01-01'