Сложный мета-запрос с 3 ключами

8

Я думаю, что проблема в основном относится к структуре запросов SQL, и я не эксперт ....

Мне нужно искать сообщения (пользовательский тип сообщения) по 2 параметрам:

  1. pd_city

  2. pd_country

Обратите внимание, что отношение meta_query имеет значение «ИЛИ», поэтому, если любой из двух выше, как «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", а не только те, которые соответствуют "pd_city" или "pd_country", потому что ВСЕ ПОСТЫ ИМЕЮТ "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"?

Dameer
источник

Ответы:

6

Виновник

Виновником этого вопроса являются мета-запросы, не поддерживающие различные и / или вложенные отношения - кстати, этот недостаток и раньше приводил меня в бешенство. В недавнем случае со сценарием поиска также.

То, что вы хотите сделать, просто не может быть выполнено с WP_Queryодной петлей.
Как вы уже заметили, неважно, поместите ли вы сортирующий ключ в meta_queryмассив или вне его в качестве аргумента общего запроса. Если вы установите отношение к мета-запросу ORи зададите meta_keyлюбое место в аргументах запроса, не задавая сопутствующий meta_valueпараметр, запрос всегда будет возвращать по крайней мере все сообщения, для которых установлен этот meta_key.
Кстати и ради полноты: когда вы используете один meta_query со !=значением для meta_compare, запрос будет возвращать все результаты с meta_keyнабором и не равным заданному meta_value- он не будетвозвращать любые посты, которые вообще не meta_keyиспользуются. Еще один момент, когда мета-запросы терпят неудачу.

Решение 1

Я вижу два варианта. Во-первых, вы можете опустить is_sponsoredмета-ключ в запросе, также опустить нумерацию страниц, получить правильные посты и выполнить сортировку со вторым экземпляром WP_Query, передав ему отфильтрованные идентификаторы постов через post__inпараметр:

$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, позвольте мне сделать быстрое - и, заметьте, не проверенное - вторичное предложение:

Если вы слегка изменили данный SQL, изменив логический оператор OR mt2.meta_key = 'is_sponsored'на ANDи переместив его соответствующим образом, вы можете получить сообщения с $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

Все это происходит из-за ORотношения meta_queryи способа, которым WordPress генерирует фактическую строку запроса. В итоге я подключился к posts_clausesфильтру, чтобы изменить whereи orderbyчасти запроса:

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_keyи orderbyиз запроса.

Пархам
источник
1

Это сложно :)

Я собирался предположить, что вам, возможно, не нужно иметь array( 'key' => 'is_sponsored' )значение в массиве meta_query, и что вы можете сделать это, добавив ключ meta_key в основной массив, но похоже, что вы пробовали это. Вы получили те же результаты?

JOINs может усложнить ситуацию Вы подключены 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

s_ha_dum
источник