Meta consulta complexa com 3 chaves

8

Eu acho que o problema está essencialmente relacionado à estrutura de consulta sql e eu não sou um especialista ....

Preciso procurar postagens (tipo de postagem personalizada) por 2 parâmetros:

  1. pd_city

  2. pd_country

Observe que a relação meta_query é 'OR'; portanto, se um dos dois acima for LIKE, teremos alguns resultados.

A terceira chave (is_sponsored) é usada para classificar as postagens! Pode ser 1 ou 0 e as postagens cujo valor "is_sponsored" igual a 1 devem ser listadas na parte superior.

Então aqui está a coisa do 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 );

Também preciso filtrar os resultados com "posts_orderby" para obter os patrocinados para o topo:

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;
}

O problema real é que, com essa consulta, TODOS OS POSTS de "sfp_post_category" são retornados, não apenas aqueles que correspondem a "pd_city" ou "pd_country" porque TODOS OS POSTS POSSUEM a meta-chave "is_sponsored" (e o valor é definido como 1 ou 0). Mais uma vez: "is_sponsored" é necessário para a classificação!

Quando var_dump

var_dump( $sfp_search->request );

... O sql do WordPress fica assim:

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

Como faço para eliminar todas as postagens que não correspondem a "pd_city" ou "pd_country" dos resultados?

Dameer
fonte

Respostas:

6

O culpado

O culpado da questão são as meta-consultas que não suportam relações diferentes e / ou aninhadas - uma falha a propósito, que já me deixou maluco antes. Em uma instância recente com um cenário de pesquisa também.

O que você deseja fazer simplesmente não pode ser realizado com WP_Queryapenas um loop.
Como você parece ter notado, se você coloca a chave de classificação na meta_querymatriz ou fora dela como um argumento de consulta geral não faz diferença. Se você definir a relação de meta consulta ORe especificar um local meta_keydos argumentos da consulta sem definir o meta_valueparâmetro associado , a consulta sempre retornará pelo menos todas as postagens em que essa meta_key está definida.
Pela maneira e por uma questão de exaustividade: Quando você usa um único meta_query com !=como um valor para meta_compare, a consulta retornará todos os resultados com o meta_keyconjunto e não igual ao dado meta_value- ele vai nãoretorne todas as postagens que não tenham as meta_keyusadas. Outro ponto em que as meta consultas falham.

Solução 1

Eu vejo duas opções. Por um lado, você pode omitir a is_sponsoredmeta-chave da consulta, também omitir paginação, obter as postagens corretas e fazer a classificação com uma segunda instância WP_Query, passando os IDs de postagem filtrados por meio do post__inparâmetro:

$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;

Observe que o $orderbyparâmetro de WP_Queryassumirá vários valores separados por um espaço. Sua modificação de pesquisa pode ser mais complexa do que o necessário.

Solução 2

Como eu gosto da sua ideia de var_dumping a requestpropriedade do objeto de consulta , deixe-me acionar uma sugestão secundária rápida - e, note, não testada - secundária:

Se você modificou levemente o SQL especificado, alterando o operador lógico de OR mt2.meta_key = 'is_sponsored'para ANDe movendo-o de acordo, poderá puxar as postagens com $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
    "
);

Nesse ponto, você também tem duas opções:
itere sobre a $sfp_post_idsmatriz com um simples foreache puxe os dados de postagem get_post()individualmente dentro desse loop ou, se desejar as delicadezas de WP_Querypaginação, tags de modelo e assim por diante, avance $sfp_post_idspara o post__inparâmetro como na solução 1.

Johannes Pille
fonte
2

Isso tudo acontece devido à ORrelação meta_querye à maneira como o WordPress gera a sequência de consultas real. Acabei de ligar para o posts_clausesfiltro para modificar os wheree orderbypedaços de consulta:

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;
}

Basta adicionar o filtro antes de configurar o objeto WP_Query e depois removê-lo após executar sua consulta para não afetar outras consultas:

    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 );

Lembre-se de deixar de fora o meta_keye orderbydos argumentos da consulta.

Parham
fonte
1

Isso é complicado :)

Eu sugeria que você pode não precisar ter o array( 'key' => 'is_sponsored' )valor na matriz 'meta_query' e que você pode fazer isso adicionando uma 'meta_key' à matriz principal, mas parece que você tentou isso. Você obteve os mesmos resultados?

JOINs podem complicar as coisas. Você se envolveu posts_orderby. Você já pensou em se conectar posts_fieldse adicionar uma subconsulta que lhe daria seu meta_value?

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

s_ha_dum
fonte