I'm trying to allow my users to sort search results by different custom fields I have.
I'm using the pre_get_posts filter and everything works fine except for one thing.
The problem I'm having is that when a custom field is used to sort by, only the posts that have that custom field set will show up in the search.
Obviously this is not acceptable as the number of search result changes when the user changes how to sort them.
What I want instead is that the posts that have the custom field show up first, in order, and then the rest of the posts show up sorted on date.
Here's the relevant code I have:
<?php
add_filter('pre_get_posts', 'h5b_search_pre_get_posts');
function h5b_search_pre_get_posts ($qry) {
$validOrders = array('price', 'date', 'popularity');
$orderBy = (isset($_GET['myorder']) and in_array($_GET['myorder'], $validOrders)) ? $_GET['myorder'] : 'price';
if ($qry->is_main_query() and $qry->query_vars['s'] != '') {
# This only includes the posts that have "item_price" set
if ($orderBy == 'price') {
$qry->set('orderby', 'meta_value_num date');
$qry->set('order', 'ASC DESC');
$qry->set('meta_key', 'item_price');
}
# This works fine and includes all posts (obviously)
elseif ($orderBy == 'date') {
$qry->set('orderby', 'date');
$qry->set('order', 'DESC');
}
}
}
Edit: Here's what the actual MySQL Query looks like. How can I change this so that it sorts on wp_postmeta.meta_value if it exists - if not, sort on date?
SELECT
SQL_CALC_FOUND_ROWS wp_posts.ID
FROM
wp_posts
INNER JOIN
wp_postmeta
ON
(wp_posts.ID = wp_postmeta.post_id)
WHERE
1=1 AND
((
(wp_posts.post_title LIKE '%lorem%') OR
(wp_posts.post_content LIKE '%lorem%')
)) AND
wp_posts.post_type IN ('post', 'page', 'attachment', 'items', 'locations') AND
(wp_posts.post_status = 'publish' OR wp_posts.post_author = 1 AND wp_posts.post_status = 'private') AND
(wp_postmeta.meta_key = 'item_price' )
GROUP BY
wp_posts.ID
ORDER BY
wp_postmeta.meta_value+0,wp_posts.post_date DESC LIMIT 0, 6
Preferably I'd solve it using the WP_Query methods but if I have to I might consider running my own SQL.
Edit2: I have a feeling I need to use something like IF NOT NULL - how would you do that with WP_Query? Is it even possible?
Edit (again): Here's the same question (I think :P): https://wordpress.stackexchange.com/questions/28409/way-to-include-posts-both-with-without-certain-meta-key-in-args-for-wp-query
It's not going to be pretty (or optimized), but I think you can use an IF or CASE here:
ORDER BY
CASE wp_postmeta.meta_value WHEN IS NOT NULL THEN wp_postmeta.meta_value END ASC,
CASE wp_postmeta.meta_value WHEN IS NULL THEN wp_posts.post_date END DESC
Note: I haven't tried this out myself, so there might be a syntax error, but it should work in theory
Further reading:
Can you add an if statement in ORDER BY?
Problem here is not that data is not sorted by date column, problem is that there is no data with empty meta_value returned.
The reason why you do not see entries with no meta specified, is that it may be filtered out by INNER JOIN for those cases, where wp_postmeta.post_id is null. Change join to be LEFT OUTER JOIN.
SELECT
SQL_CALC_FOUND_ROWS wp_posts.ID
FROM
wp_posts
LEFT OUTER JOIN
wp_postmeta
ON
(wp_posts.ID = wp_postmeta.post_id)
WHERE
1=1 AND
((
(wp_posts.post_title LIKE '%lorem%') OR
(wp_posts.post_content LIKE '%lorem%')
)) AND
wp_posts.post_type IN ('post', 'page', 'attachment', 'items', 'locations') AND
(wp_posts.post_status = 'publish' OR wp_posts.post_author = 1 AND wp_posts.post_status = 'private') AND
(wp_postmeta.meta_key = 'item_price' OR wp_postmeta.meta_key is NULL )
GROUP BY
wp_posts.ID
ORDER BY
wp_postmeta.meta_value+0,wp_posts.post_date DESC LIMIT 0, 6
UPDATE
To change join type in WP_Query use $join property to set posts_join filter as described in Plugin API/Filter Reference/posts join
See also WP_Query Class reference.
I know, this thread is around 6 months old already, but it seems there never was a sufficient answer. I was just running into the exactly same problem as the original author of the problem. So this is my final SQL query I found to work and return the desired result:
SELECT SQL_CALC_FOUND_ROWS *
FROM wp_posts
INNER JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id)
LEFT JOIN wp_postmeta AS mt1 ON (wp_posts.ID = mt1.post_id AND mt1.meta_key = 'showPostInBanner' AND mt1.meta_value='1')
LEFT JOIN wp_postmeta AS mt2 ON (wp_posts.ID = mt2.post_id)
WHERE 1=1 AND ( wp_term_relationships.term_taxonomy_id IN (34,47,51,50,68,78,82,90,97,155,227,253,285,294,314,373,425,436,452,456,521,627,667,680,694,710,730,741,751) )
AND wp_posts.post_type = 'post' AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private')
GROUP BY wp_posts.ID
ORDER BY mt1.meta_value+0 DESC, wp_posts.post_date DESC LIMIT 0, 4
Based on this (my query was trying to get a slightly different result) your query should look like this to make it work (if I didn't include any new errors - but my query definitely works now):
SELECT SQL_CALC_FOUND_ROWS *
FROM wp_posts
LEFT JOIN wp_postmeta AS mt1 ON (wp_posts.ID = mt1.post_id AND mt1.meta_key = 'showPostInBanner' AND mt1.meta_value='1')
LEFT JOIN wp_postmeta AS mt2 ON (wp_posts.ID = mt2.post_id)
WHERE 1=1 AND ((
(wp_posts.post_title LIKE '%lorem%') OR
(wp_posts.post_content LIKE '%lorem%')
)) AND
AND wp_posts.post_type IN ('post', 'page', 'attachment', 'items', 'locations') AND
(wp_posts.post_status = 'publish' OR wp_posts.post_author = 1 AND wp_posts.post_status = 'private')
GROUP BY wp_posts.ID
ORDER BY mt1.meta_value+0 DESC, wp_posts.post_date DESC LIMIT 0, 6
For Edit2:
Yes it is possible using meta_query. Try
'meta_query' => array(
array(
'key' => 'meta_key',
'value' => 'some value', //try to put null here
'compare' => '!='
)
),
I think i have a solution..
use two meta_key, one that all posts have (like "_thumbnail_id"),
and the meta_key you wish use as filter..
so your args:
$qry->set(
'meta_query',
array(
'relation' => 'OR',
array( 'key' => 'item_price', 'value' => '', 'compare' => 'EXISTS' ),
array( 'key' => '_thumbnail_id', 'value' => '', 'compare' => 'EXISTS' )
));
$qry->set('orderby', 'meta_value date');
$qry->set('order', 'ASC DESC');
$qry->set('meta_key', 'item_price');
Related
I'm having an issue with the WP theme I'm developing. I have a category.php file that I use to list all posts based on their category. In this file, I have the following code:
$post_query = new WP_Query(array(
'post_type' => 'post',
'cat' => $cat_id
));
while($post_query->have_posts() ) { ...
This works just fine. But I also have a blog.php file in which I want to list all articles regardless of their category. I'm using this:
$post_query = new WP_Query(array(
'post_type' => 'post',
'orderby' => 'date'
));
while($post_query->have_posts() ) { ...
Now this doesn't work. I'm not getting any result, even when I don't use any argument in WP_Query. When I dump the $post_query variable, I'm able to see the MySQL query and it seems like WP is somehow filtering even in the second case. Here are the two queries:
First case:
SELECT wp_posts.* FROM wp_posts LEFT JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id) WHERE 1=1 AND ( wp_term_relationships.term_taxonomy_id IN (2) ) AND wp_posts.post_type = 'post' AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'acf-disabled' OR wp_posts.post_status = 'private') GROUP BY wp_posts.ID ORDER BY wp_posts.ID DESC
Second case:
SELECT wp_posts.* FROM wp_posts LEFT JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id) WHERE 1=1 AND ( wp_term_relationships.term_taxonomy_id IN (6) ) AND wp_posts.post_type = 'post' AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'acf-disabled' OR wp_posts.post_status = 'private') GROUP BY wp_posts.ID ORDER BY wp_posts.ID DESC
I'm not sure why this part is used in the second case: wp_term_relationships.term_taxonomy_id IN (6)
What am I missing?
Mystery solved: I'm using a plugin to handle the multi-language aspect of the blog (Polylang), and it seems to create some custom taxonomies to list the articles that are in the current language only.
The wp_term_relationships comes from Polylang plugin which is using relationhsip taxonomy.
As the plugin is hooked on some main query by altering it with a tax_query to show only current language contents.
Moreover, if you want to play with some custom tax_query, you would then need to not overwrite the Polylang's one, be careful.
I'm trying to figure out why this query returns all post types and not just the ones specified. Any help would be helpful.
SELECT DISTINCT wp_posts.*
FROM wp_posts, wp_term_relationships, wp_terms, wp_term_taxonomy
WHERE 1=1
AND wp_posts.ID = wp_term_relationships.object_id
AND wp_terms.term_id = wp_term_taxonomy.term_id
AND wp_term_taxonomy.term_taxonomy_id = wp_term_relationships.term_taxonomy_id
AND wp_posts.post_type IN ('insight')
AND wp_posts.post_status = 'publish'
AND wp_posts.post_status != 'private'
AND wp_posts.post_status != 'future'
AND wp_posts.post_status != 'trash'
AND wp_terms.slug LIKE '%great%'
OR wp_posts.post_content LIKE '%great%'
LIMIT 0, 10
Your problem probably it's in your OR, you should use the OR inside parentheses (). As you are doing, the query will ignore all your clauses before. Try this:
AND ( wp_terms.slug LIKE '%great%' OR wp_posts.post_content LIKE '%great%' )
My hosting provider wants to suspend website due to slow queries. Here are they:
Time running : 34
Query :
SELECT SQL_CALC_FOUND_ROWS
wp_posts.ID
FROM
wp_posts
LEFT JOIN
wp_icl_translations t ON wp_posts.ID = t.element_id
AND t.element_type LIKE 'post\_%'
LEFT JOIN
wp_icl_languages l ON t.language_code = l.code
AND l.active = 1
WHERE
1 = 1
AND (((wp_posts.post_title LIKE '%Europe%')
OR (wp_posts.post_excerpt LIKE '%Europe%')
OR (wp_posts.post_content LIKE '%Europe%')))
AND wp_posts.post_type IN ('post' , 'page',
'attachment',
'slides',
'careers',
'partners',
'team',
'news',
'casestudies')
AND (wp_posts.post_status = 'publish'
OR wp_posts.post_author = 1
AND wp_posts.post_status = 'private')
AND (t.language_code = 'en'
OR t.language_code IS NULL)
ORDER BY wp_posts.post_title LIKE '%Europe%' DESC , wp_posts.post_date DESC
LIMIT 0 , 10
Time running : 50
Query :
SELECT SQL_CALC_FOUND_ROWS
wp_posts.ID
FROM
wp_posts
LEFT JOIN
wp_icl_translations t ON wp_posts.ID = t.element_id
AND t.element_type LIKE 'post\_%'
LEFT JOIN
wp_icl_languages l ON t.language_code = l.code
AND l.active = 1
WHERE
1 = 1
AND (((wp_posts.post_title LIKE '%1%')
OR (wp_posts.post_excerpt LIKE '%1%')
OR (wp_posts.post_content LIKE '%1%')))
AND (wp_posts.post_password = '')
AND wp_posts.post_type IN ('post' , 'page',
'attachment',
'slides',
'careers',
'partners',
'team',
'news',
'casestudies',
'privatearea')
AND (wp_posts.post_status = 'publish')
AND (t.language_code = 'en'
OR t.language_code IS NULL)
ORDER BY wp_posts.post_title LIKE '%1%' DESC , wp_posts.post_date DESC
LIMIT 0 , 10
How I see this query had been executed after using search form in website, right ? How can I solve this issue ? (also I have WPML plugin installed)
I'm running a site on Wordpress and I'm trying to get information from the postmeta table based on 2 (or more) fields. Here is my query so far:
SELECT wp_postmeta.* FROM wp_postmeta
LEFT JOIN wp_posts ON wp_posts.ID = wp_postmeta.post_id
WHERE wp_posts.post_status = 'publish'
AND wp_posts.post_type = 'post'
AND ( wp_postmeta.meta_key = 'relevantLine' AND wp_postmeta.meta_value = '339' )
AND (
( wp_postmeta.meta_key = 'brandOne' AND wp_postmeta.meta_value = '30' )
OR ( wp_postmeta.meta_key = 'brandTwo' AND wp_postmeta.meta_value = '30' )
OR ( wp_postmeta.meta_key = 'brandThree' AND wp_postmeta.meta_value = '30' )
)
AND wp_posts.post_date >= '2014-03-25'
AND wp_posts.post_date <= '2014-11-27'
GROUP BY wp_posts.ID
ORDER BY wp_posts.post_date DESC
I'm trying to access the records that have the postmeta key "relevantLine" set to 339 AND the postmeta key "brandOne" set to 30 (or "brandTwo" set to 30, or "brandThree" set to 30).
Does anyone have any idea how to do this?
The above query isn't working.
Many thanks
PS. I know I could use the wp query functionality but I would like to run the query this way if possible.
You can rewrite your as below
SELECT m.* ,m1.*
FROM wp_postmeta m
JOIN wp_posts p ON p.ID = m.post_id
JOIN wp_postmeta m1 ON p.ID = m1.post_id
WHERE p.post_status = 'publish'
AND p.post_type = 'post'
AND m.meta_key = 'relevantLine' AND m.meta_value = '339'
AND m1.meta_key IN ('brandOne','brandThree','brandTwo')
AND m1.meta_value = '30'
AND p.post_date >= '2014-03-25'
AND p.post_date <= '2014-11-27'
GROUP BY p.ID
ORDER BY p.post_date DESC
This structure is call EAV entity attribute value and for matching between different keys you have to join the table as the different keys you want to compare,I have added only one join to wp_postmeta and for the keys of single value that is 30 i have used IN() to simplify your query.
Note Using GROUP BY without any aggregate function will give you
indeterminate results
My site has installed woocommerce. When I visit taxonomy page, the below SQL is executed
SELECT SQL_CALC_FOUND_ROWS wp_posts.ID
FROM wp_posts
INNER JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id)
INNER JOIN wp_postmeta ON (wp_posts.ID = wp_postmeta.post_id)
WHERE 1=1
AND ( wp_term_relationships.term_taxonomy_id IN (90) )
AND wp_posts.post_type IN ('post', 'page', 'product')
AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'expired' OR wp_posts.post_author = 1 AND wp_posts.post_status = 'private')
AND ( (wp_postmeta.meta_key = '_visibility' AND CAST(wp_postmeta.meta_value AS CHAR) IN ('visible','catalog')) )
AND wp_posts.post_password = ''
GROUP BY wp_posts.ID
ORDER BY wp_posts.post_title ASC
LIMIT 0, 10
Can I remove the following where clause
AND ( (wp_postmeta.meta_key = '_visibility' AND CAST(wp_postmeta.meta_value AS CHAR) IN ('visible','catalog')) )
The reason I wanna do this is because I want to display post & page results together.
UPDATE 1
The SQL statement above only display product result only as there is a WHERE clause which filter out post and page result. Therefore, I want to know if there is any hook to remove that particular WHERE clause statement so that the result includes post, page and product.