具有3个键的复杂元查询


8

我认为问题本质上与sql查询结构有关,我不是专家...。

我需要通过2个参数搜索帖子(自定义帖子类型):

  1. pd_city

  2. pd_country

请注意,meta_query关系为“ OR”,因此,如果以上两个条件中的任何一个为LIKE,我们都将得到一些结果。

第三个键(is_sponsored)用于对帖子进行排序!它可以是1或0,并且其“ is_sponsored”值等于1的帖子应列在顶部。

所以这是WordPress的事情:

    $sfp_query_args = array(
        'sfp_complex_search' => 'yeap', 
        'tax_query' => array( array( 'taxonomy' => 'sfp_post_category', 'terms' => $term_id ) ),
        //'meta_key' => 'is_sponsored',
        'post_type' => 'sfpposts',
        'post_status' => 'publish',
        'showposts' => (int)$per_page,
        'paged' => $paged, 
        'meta_query' => array( 'relation' => 'OR', 
                array( 'key' => 'pd_city', 'value' => $sfp_search_meta, 'compare' => 'LIKE' ), 
                array( 'key' => 'pd_country', 'value' => $sfp_search_meta, 'compare' => 'LIKE' ), 
                array( 'key' => 'is_sponsored' )
                )
    );
$sfp_search = new WP_Query( $sfp_query_args );

我还需要使用“ posts_orderby”过滤结果,以便将那些结果赞助到顶部:

add_filter( 'posts_orderby', 'sfp_modify_search' );
function sfp_modify_search( $orderby ) {
    if( !is_admin() && is_page( $this->options[ 'sfp_page_entries_search' ] ) ) {
        global $wpdb;
        $orderby = " CASE WHEN mt2.meta_value = 0 THEN 1 END, $wpdb->posts.post_date DESC ";
    }
    return $orderby;
}

真正的问题实际上在于,使用此查询将返回“ sfp_post_category”中的所有POST,不仅返回那些匹配“ pd_city”或“ pd_country”的查询,因为所有POST都具有“ is_sponsored”元键(并将值设置为1或0)。再次:需要“ is_sponsored”进行排序!

当var_dump

var_dump( $sfp_search->request );

... WordPress的sql看起来像这样:

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) 
INNER JOIN wp_postmeta AS mt1 ON (wp_posts.ID = mt1.post_id) 
INNER JOIN wp_postmeta AS mt2 ON (wp_posts.ID = mt2.post_id) 
WHERE 1=1 
AND ( wp_term_relationships.term_taxonomy_id IN (77) ) 
AND wp_posts.post_type = 'sfpposts' 
AND (wp_posts.post_status = 'publish') 
AND ( (wp_postmeta.meta_key = 'pd_city' 
AND CAST(wp_postmeta.meta_value AS CHAR) 
LIKE '%something%') 
OR (mt1.meta_key = 'pd_country' 
AND CAST(mt1.meta_value AS CHAR) 
LIKE '%something%') 
OR mt2.meta_key = 'is_sponsored' ) 
GROUP BY wp_posts.ID 
ORDER BY CASE WHEN mt2.meta_value = 0 THEN 1 END, wp_posts.post_date DESC 
LIMIT 0, 10

如何从结果中消除所有与“ pd_city”或“ pd_country”不匹配的帖子?

Answers:


6

罪魁祸首

罪魁祸首是元查询不支持不同和/或嵌套的关系-顺便说一句,这也使我发疯了。在最近的情况下也具有搜索方案。

您想做的事情无法WP_Query仅凭一个循环就完成。
就像您已经注意到的那样,将排序键meta_query作为常规查询参数放在数组中还是放在数组外部都没有什么不同。如果您在不设置附带参数的情况下将meta查询关系设置为ORmeta_key在查询args中指定了任意位置meta_value,则查询将始终至少返回设置了meta_key的所有所有帖子。
顺便说一句,为了完整起见:当您使用单个meta_query !=作为的值时meta_compare,查询将返回所有带有meta_key集合且不等于给定结果的结果meta_value-它将不会返回所有根本没有帖子meta_key完全使用。元查询失败的另一点。

解决方案1

我看到两个选择。首先,您可以省略is_sponsored查询中的meta键,也忽略分页,获取正确的帖子,并使用的第二个实例进行排序WP_Query,并通过post__in参数将经过过滤的帖子ID传递给它:

$sfp_search_args = array(
    'sfp_complex_search' => 'yeap', 
    'tax_query' => array( array( 'taxonomy' => 'sfp_post_category', 'terms' => $term_id ) ),
    'post_type' => 'sfpposts',
    'post_status' => 'publish',
    'meta_query' => array(
        'relation' => 'OR', 
        array( 'key' => 'pd_city', 'value' => $sfp_search_meta, 'compare' => 'LIKE' ), 
        array( 'key' => 'pd_country', 'value' => $sfp_search_meta, 'compare' => 'LIKE' )
    )
);

$sfp_search = new WP_Query( $sfp_search_args );
$post_ids = array();
while ( $sfp_search->have_posts() ) : $sfp_search->next_post();
    $post_ids[] = $sfp_search->post->ID;
endwhile;

$sfp_ordered_args(
    'post__in' => $post_ids,
    // note that 'showposts' is deprected
    'posts_per_page' => (int)$per_page, 
    'paged' => $paged,
    'meta_key' => 'is_sponsored',
    'order' => 'DESC',
    'orderby' => 'meta_value_num date'
);
$sfp_ordered = new WP_Query( $sfp_ordered_args );
while ( $sfp_ordered->have_posts() ) : $sfp_ordered->next_post();
    // display posts
endwhile;

请注意,的$orderby参数WP_Query将采用由空格分隔的多个值。您的搜索修改可能比必要的复杂。

解决方案2

由于我喜欢您关于var_dumping查询对象的request属性的想法,因此让我提出一个快速的-并且注意,未经测试的-次要建议:

如果您通过更改OR mt2.meta_key = 'is_sponsored'to 的逻辑运算符AND并进行相应的移动来稍微修改了给定的SQL,则可以使用以下命令拉出帖子$wpdb

$sfp_post_ids = $wpdb->get_col(
    "
    SELECT 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) 
    INNER JOIN wp_postmeta AS mt1 ON (wp_posts.ID = mt1.post_id) 
    INNER JOIN wp_postmeta AS mt2 ON (wp_posts.ID = mt2.post_id) 
    WHERE 1=1 
    AND ( wp_term_relationships.term_taxonomy_id = $term_id ) 
    AND wp_posts.post_type = 'sfpposts' 
    AND (wp_posts.post_status = 'publish') 
    AND ( (wp_postmeta.meta_key = 'pd_city' 
    AND CAST(wp_postmeta.meta_value AS CHAR) 
    LIKE '%$sfp_search_meta%') 
    OR (mt1.meta_key = 'pd_country' 
    AND CAST(mt1.meta_value AS CHAR) 
    LIKE '%$sfp_search_meta%') )
    AND mt2.meta_key = 'is_sponsored' 
    GROUP BY wp_posts.ID 
    ORDER BY CASE WHEN mt2.meta_value = 0 THEN 1 END, wp_posts.post_date DESC
    "
);

此时,您还有两个选择:
要么$sfp_post_ids简单地遍历数组,foreach然后get_post()在该循环中逐个提取发布数据,要么,如果您希望获得WP_Query-分页,模板标记等优点,则可以$sfp_post_ids将其作为post__in参数如解决方案1。


2

这一切都是由于WordPress产生实际查询字符串的OR关系meta_query和方式而发生的。我最终迷上了posts_clauses过滤器,以修改whereorderby的查询:

public function wpse_68002_orderby_fix($pieces){
    global $wpdb;
    $pieces['where']  .= " AND $wpdb->postmeta.meta_key = 'your_meta_key'"; // <--- update here with your meta_key name
    $pieces['orderby']  = "$wpdb->postmeta.meta_value ASC";
    return $pieces;
}

只需在设置WP_Query对象之前添加过滤器,然后确保在运行查询后将其删除以不影响其他查询:

    add_filter( 'posts_clauses', 'wpse_68002_orderby_fix', 20, 1 );
    $query = new WP_Query($args);
    $result = $query->get_posts();
    remove_filter( 'posts_clauses', 'wpse_68002_orderby_fix', 20 );

请记住meta_keyorderby从查询args中删除and 。


1

那很复杂:)

我打算建议您可能不需要array( 'key' => 'is_sponsored' )在'meta_query'数组中包含该值,并且可以通过在主数组中添加'meta_key'来实现此目的,但是您似乎已经尝试过了。您得到了相同的结果吗?

JOIN可以使事情复杂化。您迷上了posts_orderby。您是否考虑过插入posts_fields并添加一个可以获取meta_value的子查询?

(SELECT meta_value FROM $wpdb->postmeta WHERE meta_key = 'is_sponsored' AND post_id = {$wpdb->posts}.ID) as is_sponsored

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.