多次连接后,Order by 子句的行为不正确

Posted

技术标签:

【中文标题】多次连接后,Order by 子句的行为不正确【英文标题】:Order by clause not behaving correctly after several joins 【发布时间】:2015-06-16 05:50:51 【问题描述】:

这是我的查询:

SELECT DISTINCT `post_data`. * , pv.`seller_id` , pv.`islimited` , pv.`isquantity` , pv.`isslider`, `price`.`original_price` , `price`.`discount_percentage` , `timelimit`.`start_date` , `timelimit`.`expire_date` , `quantity`.`in_stock`, `currency`.`currency_symbol`, `seller`.`directory`, `post_to_cat`.`cat_id`, count(`sales`.`sales_id`) as sale FROM `post_view` AS pv
INNER JOIN `post_data` ON pv.`post_id` = `post_data`.`post_id` AND pv.`status` = 1 
INNER JOIN `price` ON pv.`post_id` = `price`.`post_id` 
INNER JOIN `currency` ON `price`.`currency_id` = `currency`.`currency_id` 
INNER JOIN `seller` ON pv.`seller_id` = `seller`.`seller_id` 
INNER JOIN `post_to_cat` ON `post_to_cat`.`cat_id` = 1 AND `post_to_cat`.`post_id` = `post_data`.`post_id` 
LEFT JOIN `timelimit` ON ( CASE WHEN pv.`islimited` = 1 THEN `timelimit`.`post_id` ELSE -1 END ) = pv.`post_id`
LEFT JOIN `quantity` ON  ( CASE WHEN pv.`isquantity` = 1 THEN `quantity`.`post_id` ELSE -1 END ) = pv.`post_id`
LEFT JOIN `sales` ON `sales`.`post_id` = pv.`post_id` AND `sales`.`status` = 1 
WHERE pv.`status` = 1
ORDER BY pv.`post_id` DESC LIMIT 1 

ORDER BY DESC 不起作用,它只是返回表中的第一行,但我想获得最高的 post_id 值行。我犯了什么错误?

【问题讨论】:

你有count()函数,没有group by它只有一行。 LIMIT 1 你期待什么?它会按照您的要求返回 1 行。 是的,但我希望它能够返回最后添加的帖子!因为我正在按 post_id 对 desc 进行排序 为什么在你的时间限制和数量加入你加入到pv.post_id?这对我来说似乎不对。 没关系,我只是想了解一下。我已经给出了建议。如果它不起作用,请发布一些示例数据(最好在SQL Fiddle 和预期结果中,以便我可以尝试重新创建问题。 【参考方案1】:

正如@Alex 在 cmets 中所说,您最后有一个 LIMIT 1,您可能应该将最后一个 LEFT JOIN 括起来以提高可读性。

【讨论】:

我使用限制 1 仅检索 1 个结果。这对结果有影响吗?? 好吧,在你的问题中,听起来问题是你只得到了一个结果,我认为你首先想要最高的 post_id,就这样? 您是否尝试过在您的选择语句中使用MAX(pv.post_id),然后按其他列分组?这将是目前的替代方案。【参考方案2】:

正如@McAdam331 所说,我们需要数据样本和 sql fiddle 来调查您的查询出了什么问题。但目前我有一些建议如何改进和调试您的查询。

首先,我在查询中看到的主表和最左边的表是post_view,所以如果你想获得最大ID,所有其他表都应该是LEFT JOIN。仅当您认为其他表可以以某种方式过滤您的主表并且顺序或结果可能是 其他表相关时,您才应该使用INNER JOIN。但在你的情况下,我认为没有理由使用INNER JOIN

第二点是你很奇怪的ON条件:

LEFT JOIN `timelimit` ON ( CASE WHEN pv.`islimited` = 1 THEN `timelimit`.`post_id` ELSE -1 END ) = pv.`post_id`
LEFT JOIN `quantity` ON  ( CASE WHEN pv.`isquantity` = 1 THEN `quantity`.`post_id` ELSE -1 END ) = pv.`post_id`

我已将它们转换为另一个

CASE WHEN pv.`islimited`=1 THEN `timelimit`.`start_date` ELSE NULL END as start_date ,
CASE WHEN pv.`islimited`=1 THEN `timelimit`.`expire_date` ELSE NULL END as expire_date,
CASE WHEN pv.`isquantity`=1 THEN  `quantity`.`in_stock` ELSE NULL END as in_stock,

但我还是不喜欢。这对我来说似乎非常无用。当我阅读CASE WHEN pv.islimited=1 THEN timelimit.start_date ELSE NULL END as start_date 时没有任何意义,所以如果标记pv.islimited=0 你不需要start_date?你确定吗?

我可以建议的最后一件事是:尝试使用我的甚至是您的查询。但是在调试时逐步添加每个表。所以第一个查询:

SELECT 
pv.`post_id`, pv.`seller_id` , pv.`islimited` , pv.`isquantity` ,
pv.`isslider`
FROM `post_view` AS pv
WHERE pv.`status` = 1 
ORDER BY pv.`post_id` DESC 
LIMIT 1 

如果返回正确的post_id添加下一个表:

SELECT 
pv.`post_id`, pv.`seller_id` , pv.`islimited` , pv.`isquantity` ,
pv.`isslider`, 
`post_data`. * 
FROM `post_view` AS pv
LEFT JOIN `post_data` 
ON pv.`post_id` = `post_data`.`post_id` 
WHERE pv.`status` = 1 
  AND `post_data`.`slug` = 'abc' 
ORDER BY pv.`post_id` DESC 
LIMIT 1 

检查结果。并一步一步地继续。

是的,这需要时间。但那是调试过程。这可能是完成该查询的最快方法。 :-)

SELECT `post_data`. * ,
pv.`post_id`, pv.`seller_id` , pv.`islimited` , pv.`isquantity` ,
pv.`isslider`, `price`.`original_price` , `price`.`discount_percentage` , 
CASE WHEN pv.`islimited`=1 THEN `timelimit`.`start_date` ELSE NULL END as start_date ,
CASE WHEN pv.`islimited`=1 THEN `timelimit`.`expire_date` ELSE NULL END as expire_date,
CASE WHEN pv.`isquantity`=1 THEN  `quantity`.`in_stock` ELSE NULL END as in_stock,
 `currency`.`currency_symbol`, `seller`.`directory`, `post_to_cat`.`cat_id`, count(`sales`.`sales_id`) as sale 
FROM `post_view` AS pv
LEFT JOIN `post_data` 
ON pv.`post_id` = `post_data`.`post_id` 
LEFT JOIN `price` 
ON pv.`post_id` = `price`.`post_id` 
LEFT JOIN `currency` 
ON `price`.`currency_id` = `currency`.`currency_id` 
LEFT JOIN `seller` 
ON pv.`seller_id` = `seller`.`seller_id` 
LEFT JOIN `post_to_cat` 
ON `post_to_cat`.`cat_id` = 1 
  AND `post_to_cat`.`post_id` = pv.`post_id` 
LEFT JOIN `timelimit` 
ON `timelimit`.`post_id` = pv.`post_id`
LEFT JOIN `quantity` 
ON quantity`.`post_id` = pv.`post_id`
LEFT JOIN `sales` 
ON `sales`.`post_id` = pv.`post_id` 
  AND `sales`.`status` = 1 
WHERE pv.`status` = 1 
  AND `post_data`.`slug` = 'abc' 
GROUP BY pv.`post_id` 
ORDER BY pv.`post_id` DESC 
LIMIT 1 

编辑 1 - 最后一个 GROUP BY pv.post_id 是根据 @McAdam331 通知添加的关于没有 GROUP BYcount() 函数的通知

【讨论】:

问题由@McAdam331 解决。但是您的详细建议确实很重要,并且在这方面对我有所帮助,并且可能会遇到更多即将出现的问题。谢谢:) 是的,我明白了。在他的回答下查看我的评论。只是为了确保它完全解决了您的问题。祝你的项目好运:-) 这些都是非常棒的点,有些我什至没有想到。亚历克斯是对的,@king4aol,虽然我的查询可能在这种情况下有效,但它有一些潜在的缺陷,你将来可能会看到。如果这是针对您将来要继续使用的任何类型的项目,我肯定会立即进行一些测试,而不是等待它崩溃。【参考方案3】:

我认为这里的问题主要是由于执行聚合(使用 COUNT())函数,没有任何分组依据。虽然,您似乎并不一定需要它,因为您只希望相关帖子的计数。

如果您尝试为单个帖子收集所有这些信息,我会调整您的 WHERE 子句以设置一个条件,只为具有最大 ID 的帖子收集该信息。

不要按 ID 排序并限制为 1,而是使用子查询来获取最大的 id,如下所示:

...
WHERE pv.status = 1 AND post_data.slug = 'abc' AND pv.post_id = (SELECT MAX(post_id) FROM post_view);

【讨论】:

感谢@McAdam331,完全符合我的需要! :) @king4aol 没问题,我很高兴它有帮助。如果这是您使用的解决方案,请随时单击答案旁边的复选标记接受它,以便未来的读者能够看到您的问题的解决方案。 我怀疑这是正确的解决方案。即使问题被正确检测到。您至少可以将其更改为 WHERE post_data.slug = 'abc' AND pv.post_id = (SELECT MAX(post_id) FROM post_view WHERE pv.status = 1); 但正如您所看到的,如果最后一个 post_id 未加入到 post_data.slug = 'abc',即使这个也会破坏结果。 @Alex 这是一个很好的收获。 OP,您可以尝试添加GROUP BY pv.post_id,然后像原来那样按降序排列,看看是否仍然有效? 我确实编辑了我的答案。我猜正在等待@king4aol 测试结果。

以上是关于多次连接后,Order by 子句的行为不正确的主要内容,如果未能解决你的问题,请参考以下文章

具有多列的Order by子句的语法不正确

简述SELECT语句中的FROM、WHERE以及ORDER BY子句的作用。SQL Server

在连接中使用 Where 子句,以及 Group by 和 Order By

T-SQL为啥除以零不会在order by子句中产生错误

优化连接查询中的 order by 子句

当有 order by 子句时,openJPA 外连接可选多对一