I’m writing a special plug-in for WordPress for a client and have a working keyword / custom-field search feature that works. You enter your keyword or phrase and it searches multiple fields for the keywords, returning only distinct results.
If I search for a newspaper titled “The Herald of Freedom & Light” I get 5 article results.
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
If I attempt to search for the same newspaper with a subject of “Politics” I get 0 results (when it should be no less than 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
I’ve searched around, and most answers to questions about JOINS with multiple conditions say “move the conditions into the join”. Well, I’ve done that with the following query and it turns up 16 results (including post revisions I’ve filtered out!) without properly searching through the keywords I gave. Not only that, but by moving the conditions a 1-condition search like just for the newspaper_title will turn up the same 16 results!
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
How must I rewrite my SQL for these multiple conditions to play nicely together? I’ve got 7 other fields to work into this search functionality with similar conditions.
Your problem is that you’re trying to logically compare values that are contained in different rows. A single row can’t have a meta_key that is both “newspaper_title” and “article_subject”. If you change the AND to an OR, then you’ll receive records that are either or and not both.
I think the solution here is to use a pivot table for the meta values. The idea here is to aggregate the information contained in multiple rows into a single row per post_id then in the where clause target where all the columns have a value of 1. I’ve put together a script as an example based on what information you’ve provided:
Please ensure this script is run on a test environment and does not conflict with your existing data
The final query in the script is the one you’re after. With the test dataset it returns:
For each attribute you need to check, you would add an additional case statement as shown above in the meta query and add to the where clause the condition to check whether it was found. (i.e. newTargetedValue = 1)
Update based on OP comment:
In my opinion, the score or count method isn’t as flexible as using a pivot table. The inner/pivot table is essentially setting flags for the attributes that have matched based on the cases you’ve provided. (The value will be 1 or 0) In your current example, you’re just ANDing all those together so everything has to be set so a score or count could be used. If you later were required to logically compare those attributes to accommodate a more advanced search, the count/score no longer works. I’ll try to explain with an example.
Say I asked you to add to the search results you’ve already provided in the question, where I want all posts that had a meta value of ‘day’ = ‘Sunday’ regardless of the paper. So in short I want:
That wouldn’t work with a count/score because matching rows can return 1, 2, or 3 rows depending on how many attributes match.
With a pivot table, you can still use logical expressions: (Including the meta flags for clarity)
Here are the results:
Yes, it looks somewhat complex, but once you have the initial idea it’s pretty straight-forward as to how you go about adding more search targets and how to logically compare them to get the records you’re after.
Hope that explanation made things a little more digestible.