LEFT JOIN的WHERE条件没有出现正确的结果

时间:2011-08-09 17:58:31

标签: mysql sql wordpress

我正在为客户编写一个特殊的Wordpress插件,并且有一个有效的关键字/自定义字段搜索功能。您输入关键字或词组,然后在多个字段中搜索关键字,只返回不同的结果。

如果我搜索一份名为“自由先锋与光明”的报纸,我会得到5篇文章。

SELECT
  SQL_CALC_FOUND_ROWS
  DISTINCT
  wp_posts.* FROM wp_posts
LEFT JOIN `wp_postmeta` ON `wp_posts`.`ID` = `wp_postmeta`.`post_id`
WHERE
  1=1
  AND `post_type` = 'post'
  AND `post_status` = 'publish'
  AND (`wp_postmeta`.`meta_key` = 'newspaper_title' AND `wp_postmeta`.`meta_value` = 'The Herald of Freedom & Torch Light')
GROUP BY wp_posts.ID
ORDER BY wp_posts.post_date DESC
LIMIT 0, 10

如果我尝试使用“政治”主题搜索相同的报纸,我会得到0个结果(当它应该不低于3时)。

SELECT
  SQL_CALC_FOUND_ROWS
  DISTINCT
  wp_posts.* FROM wp_posts
LEFT JOIN `wp_postmeta` ON `wp_posts`.`ID` = `wp_postmeta`.`post_id`
WHERE
  1=1
  AND `post_type` = 'post'
  AND `post_status` = 'publish'
  AND (`wp_postmeta`.`meta_key` = 'newspaper_title' AND `wp_postmeta`.`meta_value` = 'The Herald of Freedom & Torch Light')
  AND (`wp_postmeta`.`meta_key` = 'article_subject' AND `wp_postmeta`.`meta_value` = 'Politics')
GROUP BY wp_posts.ID
ORDER BY wp_posts.post_date DESC
LIMIT 0, 10

我一直在搜索,大多数关于JOINS的问题的答案都有多个条件说“将条件移到连接中”。好吧,我已经通过以下查询完成了这项工作,并且在没有正确搜索我给出的关键字的情况下,它显示了16个结果(包括我已过滤掉的帖子修订版!)。不仅如此,通过移动条件,仅像报纸标题的1条件搜索将会出现相同的16个结果!

SELECT
  SQL_CALC_FOUND_ROWS
  DISTINCT
  wp_posts.* FROM wp_posts
LEFT JOIN `wp_postmeta` ON
  `wp_posts`.`ID` = `wp_postmeta`.`post_id`
  AND (`wp_postmeta`.`meta_key` = 'newspaper_title' AND `wp_postmeta`.`meta_value` = 'The Herald of Freedom & Torch Light')
  AND (`wp_postmeta`.`meta_key` = 'article_subject' AND `wp_postmeta`.`meta_value` = 'Politics')
WHERE
  1=1
  AND `post_type` = 'post'
  AND `post_status` = 'publish'
GROUP BY wp_posts.ID
ORDER BY wp_posts.post_date DESC
LIMIT 0, 10

我如何为这些多个条件重写我的SQL才能很好地一起玩?我有7个其他字段可以使用类似条件的搜索功能。

4 个答案:

答案 0 :(得分:2)

您的问题是您尝试逻辑比较不同行中包含的值。单行不能具有既是“newspaper_title”又是“article_subject”的meta_key。如果您将AND更改为OR,那么您将收到或不是两者的记录。

我认为这里的解决方案是使用数据透视表来表示元值。这里的想法是将每个post_id中包含多行的信息聚合成一行,然后在where子句目标中聚合,其中所有列的值都为1.我根据您的信息将脚本作为示例放在一起。提供了:

请确保此脚本在测试环境中运行且与现有数据不冲突

create table wp.posts (post_id int, description varchar(25), post_date date);
create table wp.meta (post_id int, meta_key varchar(15), meta_value varchar(25));

-- Setup post records
insert into wp.posts values
    (1, 'Post #1', MAKEDATE(2011, 5)),  (2, 'Post #2', MAKEDATE(2011, 8)),
    (3, 'Post #3', MAKEDATE(2011, 30)), (4, 'Post #4', MAKEDATE(2011, 5)),
    (5, 'Post #5', MAKEDATE(2011, 7)),  (6, 'Post #6', MAKEDATE(2011, 2));

-- Setup meta data for post records                           
insert into wp.meta values
(1, 'newspaper_title', 'NY Post'),    (2, 'newspaper_title', 'NY Post'),          
(1, 'day', 'Monday'),                 (2, 'day', 'Wednesday'),
(1, 'article_subject', 'Local'),      (2, 'article_subject', 'Politics'),

(3, 'newspaper_title', 'The Times'),  (4, 'newspaper_title', 'The Times'),     
(3, 'day', 'Friday'),                 (4, 'day', 'Tuesday'),   
(3, 'article_subject', 'Politics'),   (4, 'article_subject', 'Politics'),

(5, 'newspaper_title', 'The Herald'), (6, 'newspaper_title', 'Daily Tribune'),    
(5, 'day', 'Sunday'),                 (6, 'day', 'Wednesday'), 
(5, 'article_subject', 'Arts'),       (6, 'article_subject', 'Local');

-- Show all the data
SELECT p.description, p.post_date, meta_key, meta_value
FROM wp.posts p JOIN wp.meta m ON (p.post_id = m.post_id)
ORDER BY p.post_id;

-- Search based on newspaper_title = 'The Times' AND article_subject = 'Politics'    
SELECT p.*
FROM wp.posts p
JOIN
  (
    SELECT post_id,
           max(CASE WHEN (meta_key = 'newspaper_title' AND meta_value = 'The Times')  
               THEN 1 ELSE 0 END) targetNewspaper,
           max(CASE WHEN (meta_key = 'article_subject' AND meta_value = 'Politics') 
               THEN 1 ELSE 0 END) targetSubject
    FROM wp.meta
    GROUP BY post_id
  ) m
ON (p.post_id = m.post_id)
WHERE targetNewspaper = 1 AND targetSubject = 1
ORDER BY p.post_date;

脚本中的最终查询是您所追求的。使用测试数据集,它返回:

post_id     description               post_date                 
----------- ------------------------- ------------------------- 
4           Post #4                   2011-01-05                
3           Post #3                   2011-01-30  

对于您需要检查的每个属性,您将在元查询中添加如上所示的其他case语句,并将where条件添加到条件以检查是否找到它。 (即newTargetedValue = 1)

根据OP评论进行更新:

在我看来,得分或计数方法不如使用数据透视表那样灵活。内部/数据透视表基本上是根据您提供的案例为已匹配的属性设置标志。 (值将为1或0)在您当前的示例中,您只是将所有这些组合在一起,因此必须设置所有内容,以便可以使用分数或计数。如果您以后需要在逻辑上比较这些属性以适应更高级的搜索,则计数/分数将不再有效。我将尝试用一个例子来解释。

假设我要求您添加到问题中已经提供的搜索结果中,我希望所有帖子的元值都为'day'='Sunday'而不管论文是什么。所以总之我希望:

  • “泰晤士报”的所有“政治”专栏。
  • 连同“星期日”发生的所有帖子(无论报纸在哪里)

这不适用于计数/分数,因为匹配的行可以返回1,2或3行,具体取决于匹配的属性数量。

  • Count = 1(即星期日发布文章属性)
  • Count = 2匹配的任何2个属性(即星期日的帖子和关于政治的文章)
  • Count = 3匹配所有标准(即“The Times”周日版中的政治文章)

使用数据透视表,您仍然可以使用逻辑表达式:(为了清晰起见,包括元标记)

SELECT p.*, m.targetNewspaper, targetSubject, targetDay
FROM wp.posts p
JOIN
  (
    SELECT post_id,
           max(CASE WHEN (meta_key = 'newspaper_title' AND meta_value = 'The Times')  
               THEN 1 ELSE 0 END) targetNewspaper,
           max(CASE WHEN (meta_key = 'article_subject' AND meta_value = 'Politics') 
               THEN 1 ELSE 0 END) targetSubject,
           max(CASE WHEN (meta_key = 'day' AND meta_value = 'Sunday')               
               THEN 1 ELSE 0 END) targetDay
    FROM wp.meta
    GROUP BY post_id
  ) m
ON (p.post_id = m.post_id)
WHERE (targetNewspaper = 1 AND targetSubject = 1) OR targetDay = 1
ORDER BY p.post_date;

结果如下:

post_id  description   post_date   targetNewspaper   targetSubject   targetDay            
-------- ------------- ----------- ----------------- --------------- ----------- 
4        Post #4       2011-01-05  1                 1               0                    
5        Post #5       2011-01-07  0                 0               1                    
3        Post #3       2011-01-30  1                 1               0                    

是的,它看起来有点复杂,但是一旦你有了初步的想法,就如何添加更多的搜索目标以及如何在逻辑上比较它们来获取你所追踪的记录一样,这是非常简单的。

希望这些解释让事情变得更容易消化。

答案 1 :(得分:1)

您的代码尝试查找meta_key同时为“newspaper_title”和“article_subject”的行。那当然是不可能的。您真正想要问的是,“wp_posts中的哪些行在wp_postmeta中有一行”news_title“,另一行行有”article_subject“ ”

你可以使用多个JOIN来做到这一点,但是当你有越来越多的标准需要检查时,它们会很快爆炸。另一种方法是检查符合任何条件的行数是否与条件数匹配:

SELECT
    PT.parent_id
FROM
    Parent_Table PT
LEFT OUTER JOIN Child_Table CT ON
    CT.parent_id = PT.parent_id AND
    CT.tag IN (@tag1, @tag2)
GROUP BY
    PT.parent_id
HAVING
    COUNT(DISTINCT CT.tag) = 2

您可以更改上述查询以改为使用子查询,例如2 = (SELECT COUNT(*)...)

根据您的具体情况(请原谅任何轻微的语法问题,因为我通常不使用MySQL):

SELECT
    wp_posts.*
FROM
    wp_posts wp
INNER JOIN (
    SELECT
        wp2.id,
        COUNT(*) AS cnt
    FROM
        wp_posts wp2
    INNER JOIN wp_postmeta wpm ON
        wpm.post_id = wp2.id AND
        (wpm.meta_key = 'newspaper_title' AND wpm.meta_value = 'The Herald of Freedom & Torch Light') OR
        (wpm.meta_key = 'article_subject' AND wpm.meta_value = 'Politics')
    GROUP BY
        wp2.id
    ) AS SQ ON SQ.id = wp.id AND SQ.cnt = 2

答案 2 :(得分:0)

LEFT JOIN是一个OUTER连接,这意味着您将从左侧的表中获取所有行,如果未找到匹配,则匹配行或空值

切换到INNER JOIN以限制仅返回的行数,这些行在右侧的表中有匹配。

答案 3 :(得分:0)

我想我会在你的帮助下分享我发现的东西。

1。)这是架构问题。

我们保留了Wordpress的post / post-meta格式,并使用插件将我们的记录数据存储在后元表中。这创造了一个主要问题:我们没有设计包含我们数据的权威表,而是有一个帖子(只有一个标题)和一系列相关的后元行(sorta键值样式)。

因此,使用1个查询进行搜索是不切实际的。

2.)客户要求跨多个领域进行搜索(迟到)。

我使用了现有的关键字搜索,这些关键字搜索在所有相关的后元行中运行,并尝试对其进行扩展。这当时起作用了,因为它抓住了第一个结果并将其返回......而且它并不关心多个匹配。一旦我尝试将多个条件仅应用于后元表,我就推动了SQL 应该所做的限制。

我们真的应该自己制作一张桌子。

3.)我们真的应该从一开始就制作自己的桌子。

我知道客户可能希望以非常具体的方式进行搜索。而且,我应该预见到在第二个表中将实际列数据分布在多行上的困难。除了边缘情况,这是糟糕的设计。

结论

如果您需要同时搜索帖子的几乎每个属性依赖Wordpress的自定义字段。除了搜索之外,它们对的所有内容都非常强大。建立自己的表格,将其链接到帖子,在自定义表格中搜索并加入有效的帖子 - 而不是相反!