Сортировка по мета-значению с включением записей без метаданных

10 июн. 2013 г., 01:34:49
Просмотры: 41.4K
Голосов: 53

Я модифицирую встроенный поиск WordPress используя фильтр pre_get_posts, позволяя пользователю сортировать записи (включая множество произвольных типов записей) по различным полям.

Проблема в том, что когда я указываю WordPress сортировать по мета-значению, он исключает все записи, у которых это мета-значение не установлено. Это приводит к изменению количества результатов при смене сортировки, например, с "Цены" на "Дату", потому что у "Записей" нет установленной "Цены", а у "Товаров" есть.

Это не то, что мне нужно, поэтому я хотел бы узнать, есть ли способ включить ВСЕ записи - даже те, у которых отсутствует мета-значение, по которому идет сортировка - и поместить записи без значения в конец.

Я знаю, как сортировать по нескольким полям, но это не помогает.

Спасибо

Похоже, я не единственный с таким вопросом: Как включить записи как с определенным meta_key, так и без него в аргументах wp_query?, но там нет решения.

Обновление

Я попробовал ответ, но не уверен, правильно ли я понял, вот что у меня сейчас:

<?php
function my_stuff ($qry) {
    $qry->set('meta_query', array(array(
        'key' => 'item_price', 
        'value' => '', 
        'compare' => 'NOT EXISTS'
    )));

    $qry->set('orderby', 'meta_value date'); # Сортировка работает как с meta_value, так и с meta_value_num - я пробовал оба
    $qry->set('order', 'ASC DESC');
    $qry->set('meta_key', 'item_price');
}

Мета-значение является числом (оно используется для хранения цены, как предполагает название)

Обновление 2

Я закомментировал параметры сортировки и теперь у меня только это:

<?php
$qry->set('meta_query', array(array(
    'key' => 'item_price', 
    'value' => '', 
    'compare' => 'NOT EXISTS'
)));

С этим кодом запрос, похоже, возвращает все записи, у которых нет ключа item_price, и ни одной записи, у которой он есть. То есть проблема теперь обратная.

Если я добавлю код сортировки, я получаю 0 результатов.

Редактирование: ...три года спустя... :P У меня снова возникла эта проблема. Я попробовал все предложенные ответы, и ни один не работает. Не понимаю, почему некоторые люди думают, что они работают, но у меня они точно не работают.

Решение, к которому я пришел - использование фильтра save_post - гарантирует, что все записи имеют произвольное поле, по которому я хочу сортировать. Немного раздражает, что приходится это делать, но если делать это на ранней стадии, проблем обычно не возникает.

В данном случае я создавал "счетчик просмотров" для записей и хотел, чтобы пользователи могли сортировать по самым читаемым записям. Опять же, записи, которые никогда не просматривались (думаю, это маловероятно, но всё же), исчезали при сортировке по количеству просмотров. Я добавил этот фрагмент кода, чтобы убедиться, что все записи имеют счетчик просмотров:

add_action('save_post', function ($postId) {
    add_post_meta($postId, '_sleek_view_count', 0, true);
});
11
Комментарии

Покажите, пожалуйста, ваш код. Так будет проще ответить.

kaiser kaiser
10 июн. 2013 г. 02:24:14

Во-первых: meta_query и tax_query — это всегда array( array() ), так как они объединяют несколько массивов. Во-вторых — как уже упоминалось в моем ответе — для чисел нужно использовать meta_value_num. Возможно, также потребуется явно определить meta_value_num (см. запись на странице WP_Query в Codex). Наконец, не имеет смысла указывать сортировку одновременно в направлениях ASC и DESC. Это невозможно. Разделитель пробела работает только для orderby, и нельзя указать сортировку первого поля по возрастанию, а второго — по убыванию. Для этого нужен фильтр posts_clauses.

kaiser kaiser
10 июн. 2013 г. 03:42:38

И убедитесь, что ваши значения meta_value_num являются действительными числами. Часто бывает, что кто-то утверждает, что это число, но на самом деле оно сохраняется как строка в базе данных.

kaiser kaiser
10 июн. 2013 г. 03:43:23

Спасибо за помощь, я попробую это и отпишусь. Причина использования ASC DESC в том, чтобы сортировать по meta_value в порядке ASC и по date в порядке DESC, насколько я могу судить, это работает.

powerbuoy powerbuoy
10 июн. 2013 г. 04:06:12

Также, сама сортировка работает нормально. Единственная проблема в том, что записи (или пользовательские типы записей) без мета-значения не отображаются. (В отношении meta_value_num)

powerbuoy powerbuoy
10 июн. 2013 г. 04:29:58

Чтобы уточнить, вы хотите включить записи как с определенным мета-значением, так и записи, у которых это мета-значение не установлено?

Howdy_McGee Howdy_McGee
15 апр. 2014 г. 21:20:52

@Howdy_McGee это верно. Некоторые из моих пользовательских типов записей ИМЕЮТ это значение установленным. Некоторые — нет. И встроенные типы записей (как POST и PAGE) его не имеют. Поэтому когда я пытаюсь сортировать по этому полю, показываются только записи С пользовательским полем.

powerbuoy powerbuoy
15 апр. 2014 г. 21:31:23

Я видел похожие вопросы раньше, у @G.M. есть хороший ответ, но вы должны показывать все результаты на одной странице, так что если это не работает для вас, тогда вы либо показываете записи с meta_key, либо записи без meta_key, но не оба варианта. Вот пост, на который я ссылаюсь.

Howdy_McGee Howdy_McGee
15 апр. 2014 г. 21:35:10

А, понятно, спасибо. Это было для проекта, который я уже давно завершил, но я учту это на будущее. Хотя я предпочел бы продолжать использовать обычный "WP loop" с обычной пагинацией...

powerbuoy powerbuoy
15 апр. 2014 г. 23:38:05

Я создал тикет в Trac, чтобы рассмотреть возможность добавления решения в основные классы запросов, которое сделает подобные вещи возможными: https://core.trac.wordpress.org/ticket/42907

J.D. J.D.
15 дек. 2017 г. 00:11:13

@powerbuoy пожалуйста, ознакомьтесь с моим ответом, где приведено решение.

Paul Paul
23 февр. 2019 г. 18:36:01
Показать остальные 6 комментариев
Все ответы на вопрос 17
9
25

Легко и просто, протестировано в 2018 году, используется в продакшене на данный момент.

Обновление 2022: Изменено так, что запрос NOT_EXISTS идет перед запросом EXISTS, и уточнено влияние нескольких ключей на параметр orderby.

$query->set( 'meta_query', array(
    'relation' => 'OR',
    array(
        'key' => 'custom_meta_key', 
        'compare' => 'NOT EXISTS'
    ),
    array(
        'key' => 'custom_meta_key', 
        'compare' => 'EXISTS'
    ),
) );
$query->set( 'orderby', 'meta_value title' ); 

Этот код проверяет все записи с указанным мета-ключом и без него, без указания конкретного значения. Мета-запрос надежно предоставляет ключ для сортировки. Код был протестирован. Параметр meta_value/meta_value_num в orderby будет использовать последний ключ в цепочке.

Практический пример

/**
 * Изменяет запрос перед получением записей. Устанавливает 
 * параметры `meta_query` и `orderby`, если параметр `orderby` 
 * не задан (сортировка по умолчанию).
 * 
 * @param   WP_Query  $query  Полный объект `WP_Query`.
 * @return  void
 */
function example_post_ordering( $query ) {
    
    // Если не в админке, 
    // и запрос является основным, 
    // и запрос не является запросом для одной записи, 
    // и параметр orderby не задан...
    // Примечание: можно добавить проверку типов записей и т.д.
    if ( ! is_admin() 
    && $query->is_main_query() 
    && ! $query->is_singular() 
    && empty( $query->get( 'orderby' ) ) ) {
        
        // Просто установка `meta_key` недостаточна, так как это 
        // приведет к игнорированию записей без значения для 
        // указанного ключа. Этот мета-запрос зарегистрирует 
        // `meta_key` для сортировки, но не проигнорирует записи без значения.
        $query->set( 'meta_query', array(
            'relation' => 'OR',
            array(
                'key' => 'custom_meta_key', 
                'compare' => 'NOT EXISTS'
            ),
            array(
                'key' => 'custom_meta_key', 
                'compare' => 'EXISTS'
            ),
        ) );

        // Сортировка по мета-значению, затем по заголовку, если 
        // несколько записей имеют одинаковое значение мета-ключа.
        // Используйте `meta_value_num`, если значения числовые.
        $query->set( 'orderby', 'meta_value title' );
    }

}

add_action( 'pre_get_posts', 'example_post_ordering', 10 );

Этот код будет сортировать записи по custom_meta_key по умолчанию и не будет игнорировать записи без значения для этого ключа.

18 апр. 2018 г. 18:23:10
Комментарии

Просто прочитав код, видно, что он выбирает записи, у которых есть custom_meta_key, и записи, у которых нет custom_meta_key. Можете добавить реальный рабочий пример с сортировкой.

powerbuoy powerbuoy
18 апр. 2018 г. 20:33:10

Вы правы, это все, что он делает, но следующая строка отвечает за сортировку по meta_value (мета-ключа, по которому идет запрос). $query->set( 'orderby', 'meta_value title' ); (Сортировка по meta_value, затем по заголовку, когда несколько записей имеют одинаковое значение мета-ключа). Это должно быть сделано в хуке pre_get_posts, используя переданную переменную $query. Учтите, что вопрос был о том, как сортировать по meta_value, не игнорируя записи без значения для этого мета-ключа.

noahmason noahmason
18 апр. 2018 г. 21:18:00

@powerbuoy Смотрите обновленный практический пример

noahmason noahmason
18 апр. 2018 г. 21:55:47

Хорошо, попробую в следующий раз, когда столкнусь с этой проблемой.

powerbuoy powerbuoy
27 апр. 2018 г. 21:13:34

Сработало для меня в пользовательском вызове get_posts(), чтобы поднять посты с метаполем _featured вверх, а затем отсортировать по дате. Спасибо!

Nate Beaty Nate Beaty
9 нояб. 2018 г. 20:03:22

У меня сработало только при обратном порядке NOT EXISTS и EXISTS. Без этого сортировка игнорировалась.

mikemike mikemike
3 янв. 2021 г. 18:48:42

@mikemike спасибо за информацию, переключение помогло мне решить проблему

bigdaveygeorge bigdaveygeorge
11 янв. 2021 г. 19:45:54

@mikemike понял - я только что внес это изменение выше.

noahmason noahmason
9 июл. 2022 г. 20:08:50

Я как раз столкнулся с этой проблемой, и это сработало просто отлично,

AuRise AuRise
5 июл. 2024 г. 17:32:37
Показать остальные 4 комментариев
4
12

Этот метод вернет все записи, включая те, у которых есть и нет запрашиваемого meta_key, но при сортировке могут происходить странные вещи.

add_action('pre_get_posts', 'my_stuff');
function my_stuff ($qry) {
    $qry->set(
        'meta_query',
        array(
            'relation' => 'OR', # Соответствия этому meta_query должны добавляться к тем, которые соответствуют запросу 'meta_key'
            array(
                'key' => 'item_price', 
                'value' => 'bug #23268', 
                'compare' => 'NOT EXISTS'
            )
        )
    );

    $qry->set('orderby', 'meta_value date'); # Сортировка работает с meta_value, а также с meta_value_num — я пробовал оба варианта
    $qry->set('order', 'ASC DESC');
    $qry->set('meta_key', 'item_price');
}

Я нашел это, экспериментируя с различными ответами на этот вопрос и анализируя сгенерированный SQL методом проб и ошибок. Похоже, что установка array('meta_query' => array('relation' => 'OR')) выводит соответствующий LEFT JOIN вместо INNER JOIN, который необходим для включения записей без метаданных. Указание NOT EXISTS предотвращает фильтрацию записей, у которых отсутствует метаполе, в условии WHERE. Для этого конкретного WP_Query сгенерированный 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
    LEFT JOIN wp_postmeta AS mt1 ON (wp_posts.ID = mt1.post_id AND mt1.meta_key = 'item_price')
    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 = 'private')
    AND (wp_postmeta.meta_key = 'item_price'
        -- Обратите внимание, здесь мы даем SQL разрешение выбрать случайную
        -- строку из wp_postmeta, если у этой записи отсутствует 'item_price':
        OR  mt1.post_id IS NULL )
    GROUP BY wp_posts.ID
    ORDER BY wp_postmeta.meta_value,wp_posts.post_date DESC
    LIMIT 0, 10

В результате мы получаем список всех записей с метазначением item_price и тех, у которых его нет. Все записи с item_price будут отсортированы правильно относительно друг друга, но записи без item_price будут использовать какое-то случайное другое метазначение (например, _edit_last, которое в моей базе данных часто равно 1, или какое-то другое внутреннее метаполе WordPress, которое совершенно произвольно) для своего wp_postmeta.meta_value в условии ORDER BY. Таким образом, хотя этот метод близок к идеалу и может работать для определенных данных, он сломан. Поэтому могу сказать только одно: если значения вашего item_price случайно не конфликтуют со случайными метаполями, которые MySQL выбирает для записей без item_price, это может сработать. Если вам нужно лишь гарантировать, что записи с item_price правильно отсортированы относительно друг друга, без учета порядка остальных записей, это может быть приемлемо. Но, похоже, это ограничение WordPress. Поправьте меня, если я ошибаюсь — надеюсь, есть способ решить эту проблему ;-).

Похоже, что для INNER JOIN wp_postmeta MySQL случайным образом выбирает строку из нескольких строк postmeta, связанных с записью, когда meta_key отсутствует у данной записи. С точки зрения SQL, нам нужно выяснить, как заставить WordPress выводить ORDER BY mt1.meta_value. Этот столбец корректно содержит NULL, когда запрашиваемый meta_key отсутствует, в отличие от wp_postmeta.meta_value. Если бы мы могли это сделать, SQL отсортировал бы эти NULL (отсутствующие записи) перед любыми другими значениями, что дало бы нам четко определенный порядок: сначала все записи без указанного метаполя, затем записи с этим полем. Но вся проблема в том, что 'orderby' => 'meta_value' может ссылаться только на 'meta_key' => 'item_price', а неалиасированный wp_postmeta всегда является INNER JOIN, а не LEFT JOIN, что означает, что wp_postmeta.meta_value и wp_postmeta.meta_key никогда не могут быть NULL.

Поэтому, видимо, приходится признать, что это невозможно с встроенным WP_Query в текущей версии WordPress (wordpress-3.9.1). Досадно. Так что если вам действительно нужно, чтобы это работало корректно, вероятно, придется подключиться к WordPress в другом месте и напрямую изменить сгенерированный SQL.

31 июл. 2014 г. 08:32:41
Комментарии

Выглядит очень перспективно! Я попробую это в следующий раз, когда столкнусь с такой проблемой. Хотел бы сразу дать вам ответ, но предпочитаю сначала убедиться, что это работает у меня.

powerbuoy powerbuoy
5 авг. 2014 г. 19:09:06

У меня после этого вообще ничего не отображалось. Ничего не появилось после реализации этого решения.

Jake Jake
26 окт. 2016 г. 14:32:47

@Jake Да, та же история. Сегодня снова столкнулся с этой проблемой и попробовал это решение. Возвращает 0 результатов.

powerbuoy powerbuoy
7 июн. 2017 г. 12:00:52

Какую версию WordPress вы используете? Я думаю, этот пост описывает использование внутреннего, недокументированного API, который не поддерживается WordPress и, вероятно, работает только на wordpress-3.9.1 или не слишком многих версиях после нее.

binki binki
7 июн. 2017 г. 17:40:40
0

Проблема, с которой сталкиваются здесь все, связана с порядком мета-запросов. Для правильной сортировки необходимо разместить запрос "NOT EXISTS" перед запросом "EXISTS".

Причина в том, что WordPress использует meta_value из последнего оператора "LEFT JOIN" в условии "ORDER BY".

Например:

$pageQuery = new WP_Query([
    'meta_query' => [
        'relation' => 'OR',
        ['key' => 'item_price', 'compare' => 'NOT EXISTS'], // это должно быть первым!
        ['key' => 'item_price', 'compare' => 'EXISTS'],
    ],
    'order' => 'DESC',
    'orderby' => 'meta_value_num',
    'post_status' => 'publish',
    'post_type' => 'page',
    'posts_per_page' => 10,
]);
23 февр. 2019 г. 18:33:29
3

Есть два возможных решения этой проблемы:

1. У всех записей есть мета-данные

Лучшее решение, которое я нашел - присвоить остальным записям/товарам цену 0. Это можно сделать вручную или пройтись циклом по всем записям и, если цена пустая, обновить ее.

Чтобы упростить управление в будущем, можно использовать хук save_post и задавать значение при первом добавлении записи (только если оно пустое).

2. Множественные запросы

Можно выполнить первый запрос, как вы делаете, и сохранить ID возвращенных записей. Затем выполнить другой запрос для всех записей с сортировкой по дате, исключая ID из первого запроса.

После этого можно отдельно вывести результаты обоих запросов в нужном порядке и получить желаемый результат.

20 апр. 2014 г. 17:01:04
Комментарии

Три года спустя у меня снова возникла та же проблема :P Пришлось использовать метод save_post (я обновил свой вопрос с кодом, который использовал).

powerbuoy powerbuoy
7 июн. 2017 г. 12:11:42

Это возможно без использования хуков или выполнения нескольких запросов. Смотрите мой ответ.

Jacob Raccuia Jacob Raccuia
11 июл. 2020 г. 07:51:50

Вам следовало показать примеры кода.

Steve Moretz Steve Moretz
6 авг. 2021 г. 12:50:35
0

Я также столкнулся с похожей проблемой, и следующее решение помогло мне:

$args = array(
'post_type' => 'kosh_products',
'posts_per_page' => -1,
'meta_query' => array(
    'relation' => 'OR',
    'category_sort_order' => array(
        'key' => '_sort_order',
        'compare' => 'EXISTS'
    ),
    'category_sort_order_not_exists' => array(
        'key' => '_sort_order',
        'compare' => 'NOT EXISTS'
    ), 
),
'orderby' => array( 
    'category_sort_order' => 'ASC',
    'date' => 'ASC'
));
$query = new WP_Query( $args );

Я нашел описание в WordPress Codex с заголовком "Сортировка ('orderby') с несколькими мета-ключами": https://codex.wordpress.org/Class_Reference/WP_Query#Order_.26_Orderby_Parameters Пример сортировки запросов в WordPress

25 мая 2019 г. 22:24:40
1

Думаю, у меня есть решение.

Вы можете использовать два meta_key: один, который есть у всех записей (например "_thumbnail_id"), и meta_key, который вы хотите использовать для фильтрации.

Ваши аргументы будут выглядеть так:

$qry->set(
    'meta_query',
    array(
        'relation' => 'OR',
        array(
            'key' => 'item_price', 
            'value' => '', 
            'compare' => 'EXISTS'
        ),
        array(
            'key' => 'item_price', 
            'value' => '', 
            'compare' => 'EXISTS'
        )
    )
);

$qry->set('orderby', 'meta_value date'); # Сортировка работает как с meta_value, так и с meta_value_num - я пробовал оба варианта
$qry->set('order', 'ASC DESC');
$qry->set('meta_key', 'item_price');
22 авг. 2013 г. 21:06:01
Комментарии

Проблема здесь в сравнении с пустой строкой, удалите это и все заработает 'value' => '', также второе сравнение должно быть NOT EXISTS и последняя инструкция set не требуется

nodws nodws
24 апр. 2018 г. 18:19:50
0

Я сам столкнулся с этой проблемой для числовых мета-значений и обнаружил, что порядок запроса также важен. В моем случае запрос NOT EXISTS должен быть первым.

Пример:

$query->set( 'orderby', 'meta_value_num' );
$query->set( 'meta_query', [
    'relation' => 'OR',
    [ 'key' => 'your_meta_name', 'compare' => 'NOT EXISTS' ],
    [
        'key' => 'your_meta_name',
        'compare' => 'EXISTS',
    ],
] );

Также важно для правильной сортировки числовых значений установить параметр ’orderby’ в значение ’meta_value_num’. В противном случае вы получите странные результаты для числовых значений, например:

1, 2, 20, 21, 3, 4, 5 …

Вместо:

1, 2, 3, 4, 5 … 20, 21

12 янв. 2019 г. 03:16:08
0

Мета-запрос OR, сочетающий NOT EXISTS и EXISTS, работает, но вызывает медленный запрос.

Это решение работает значительно быстрее.

Ключевым моментом был SQL-запрос в ORDER BY: order by YOUR_FIELD is NULL, YOUR_FIELD

add_filter('posts_orderby', function (string $orderby, WP_Query $query) {
    if ('MY_CUSTOM_SORT' === $query->get('orderby')) {
        $orderby = "mta.meta_value is NULL, mta.meta_value ='', mta.meta_value ASC";
    }
    return $orderby;
}, 10, 2);

add_filter('posts_join', function (string $join, WP_Query $query) {
    if ('MY_CUSTOM_SORT' === $query->get('orderby')) {
        $join .= " LEFT JOIN wp_postmeta AS mta ON ( wp_posts.ID = mta.post_id AND mta.meta_key = 'SOME_META_KEY')";
    }
    return $join;
}, 10, 2);

И в аргументах вашего WP_Query:

$args['orderby'] = 'MY_CUSTOM_SORT';
19 окт. 2022 г. 01:26:42
0

Это очень старый вопрос, но ни один из ответов не был достаточно надежным для меня, чтобы использовать его и быть довольным результатами. Приведенное ниже решение по-прежнему использует WP_Query, но манипулирует операторами join и order by.

Оно использует функцию MySQL под названием ORDER BY IF, которая позволяет эффективно применять логику во время выполнения оператора ORDER BY. Это важно, потому что другие решения просто группируют результаты без какого-либо значения либо в начале, либо в конце списка, что бесполезно, когда вы пытаетесь упорядочить по двум ключам.

В приведенном ниже примере у меня есть пользовательский тип записи (CPT) 'businesses'. Я хочу, чтобы все они были упорядочены по названию, но любые записи с is_premium должны идти первыми. Я добавил комментарии к коду, чтобы было понятно, что происходит в каждом месте.

<?php
$paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
$args = [
    'post_type' => 'business',
    'posts_per_page' => 12,
    'meta_query' => [
        'relation' => 'OR',
        [
            'key' => 'is_premium', 
            'compare' => 'NOT EXISTS' // NOT EXISTS сначала важно
        ],
        [
            'key' => 'is_premium', 
            'compare' => 'EXISTS'
        ],
    ]
    ,
    'orderby' => [
        'meta_value' => 'DESC', // Это поле is_premium
        'title' => 'ASC',
    ],
    'paged' => $paged
];

// Добавляем собственный join к запросу
function custom_join($join) {
    global $wpdb;

    if( ! is_admin() ) {
        $join .= $wpdb->prepare(
        ' LEFT JOIN ' . $wpdb->postmeta . ' cpm ON cpm.post_id = ' . $wpdb->posts . '.ID AND cpm.meta_key = %s'
        , 'is_premium' );
    }

    return $join;
}
add_filter('posts_join','custom_join');

// Добавляем собственный order by к запросу
function custom_orderby($orderby_statement){
    global $wpdb;

    if ( ! is_admin() ) {
        // Здесь мы сортируем по meta_value ТОЛЬКО если оно равно 1, в противном случае игнорируем его для этого оператора сортировки,
        // что означает, что запись будет отсортирована по следующему оператору (title), так же как и другие результаты
        $orderby_statement = "IF(cpm.meta_value = 1, 1, 0) DESC, wp_posts.post_title ASC ";
    }

    return $orderby_statement;
}
add_filter('posts_orderby','custom_orderby', 10, 2 ); 

// Запрос
$business = new WP_Query( $args );

// Удаляем фильтры
remove_filter('posts_orderby','custom_orderby');
remove_filter('posts_join','custom_join');
3 янв. 2021 г. 19:41:17
0

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

function addDefaultMetaValue($post_id) {
    add_post_meta($post_id, 'item_price', 0, true);
}
add_action('save_post', 'addDefaultMetaValue');

Если вы используете пользовательский тип записи (custom post type), замените add_action('save_post', 'addDefaultMetaValue'); на add_action('save_post_{post_type}', 'addDefaultMetaValue');, например add_action('save_post_product', 'addDefaultMetaValue');

20 авг. 2015 г. 19:17:40
6

Для этого можно использовать значение orderby как meta_value.

$query = new WP_Query( array ( 
    'meta_key'   => 'your_keys_name', // Укажите имя вашего мета-ключа
    'orderby'    => 'meta_value',     // Сортировка по значению мета-поля
    'order'      => 'DESC',           // Сортировка по убыванию
    'meta_query' => array( array(
         'key'     => 'your_meta_key', // Ключ мета-поля для сравнения
         'value'   => '',              // Пустое значение
         'compare' => 'NOT EXISTS',    // Условие: мета-поле не существует
         // 'type'    => 'CHAR',       // Тип данных (закомментировано)
    ) )
) );

Если у вас числовые значения, используйте meta_value_num вместо meta_value.

Примечание: Этот код не тестировался, но он должен работать. Главное — указать значения meta_key и key. Иначе нельзя сравнивать отсутствующие значения, что позволит запросить оба типа записей. Это немного хакерский подход, но если работает...

10 июн. 2013 г. 02:38:53
Комментарии

Спасибо за ответ, пожалуйста, проверьте мой обновленный вопрос, я не уверен, что правильно вас понял.

powerbuoy powerbuoy
10 июн. 2013 г. 03:05:54

У меня до сих пор не получилось заставить это работать, поэтому если у вас есть решение, я бы хотел узнать, что я делаю не так. Также я установил награду на SO, если вы хотите её получить: http://stackoverflow.com/questions/17016770/wordpress-order-by-meta-value-if-it-exists-else-date/17183618

powerbuoy powerbuoy
20 июн. 2013 г. 05:45:54

Две вещи. 'your_keys_name' и 'your_meta_key' должны быть одной и той же строкой, а не разными, иначе похоже, что вы неверно поняли вопрос. Во-вторых, я протестировал это на своей локальной настройке, и оно исключает все записи, где ключ существует (через meta_query), а также исключает записи, где ключ отсутствует (через meta_key), в результате чего не отображаются никакие записи. Однако этот ответ — шаг к чему-то, что работает, по крайней мере ;-).

binki binki
31 июл. 2014 г. 07:16:07

О, интересно, что этот ответ действительно работает, если просто добавить 'relation' => 'OR' в meta_query. Странные дела о_о.

binki binki
31 июл. 2014 г. 07:18:19

@binki Просто предложите [правку] к моему вопросу и измените то, что, по вашему мнению, нужно изменить. Это сайт, управляемый сообществом :)

kaiser kaiser
31 июл. 2014 г. 12:58:04

@kaiser, Я ещё поэкспериментировал, даже протестировал, и в результате получился небольшой монолог. Полагаю, мой новый ответ — подходящее место для моих размышлений ;-).

binki binki
31 июл. 2014 г. 16:24:43
Показать остальные 1 комментариев
0

Это решение сработало для меня:

add_action( 'pre_get_posts', 'orden_portfolio' );
function orden_portfolio( $query ) {

    if( ! is_admin() ) {

        $query->set( 'orderby', 'meta_value_num' );
        $query->set( 'order', 'ASC' );
        $query->set( 'meta_query', [
            'relation' => 'OR',
            [ 
                'key' => 'ce_orden', 
                'compare' => 'NOT EXISTS' ],
            [
                'key' => 'ce_orden',
                'compare' => 'EXISTS',
            ],
        ] );

        return $query;

    }

}

Однако это решение сначала показывает записи с пустым meta_value. Вот другое решение, которое показывает сортировку по возрастанию (ASC) и записи с NULL в конце:

function custom_join($join) {
    global $wpdb;

    if( ! is_admin() ) {
        $join .= $wpdb->prepare(
        ' LEFT JOIN ' . $wpdb->postmeta . ' cpm ON cpm.post_id = ' . $wpdb->posts . '.ID AND cpm.meta_key = %s'
        , 'ce_orden' );
    }

    return $join;
}

add_filter('posts_join','custom_join');

function custom_orderby($orderby_statement){
    global $wpdb;

    if ( ! is_admin() ) {
        $orderby_statement = "CAST( COALESCE(cpm.meta_value,99999) as SIGNED INTEGER) ASC";
    }

    return $orderby_statement;
}

add_filter('posts_orderby','custom_orderby', 10, 2 ); 
28 окт. 2019 г. 20:03:50
0

Мне удалось решить это с помощью одного запроса и правильных аргументов. Для WordPress 4.1+

$args = array( 
    'posts_per_page' => -1, 
    'post_status' => 'publish',
    'meta_query' => array(
        'relation' => 'OR',
        array(
            'key' => 'custom_sort',
            'compare' => 'EXISTS'
        ),
        array(
            'key' => 'custom_sort',
            'compare' => 'NOT EXISTS'
        )
    ),
    'orderby' => 'meta_value_num title',
    'order' => 'ASC',
));

$query = new WP_Query($args);

Смотрите мой полный ответ здесь: https://wordpress.stackexchange.com/a/370841/15209

11 июл. 2020 г. 07:51:26
3

В итоге я обошёл эту проблему с помощью небольшого хака (ИМХО), но в моём случае он сработал.

Вы можете подключиться к фильтрам posts_join_paged и posts_orderby, чтобы изменить строки JOIN и ORDER BY. Это позволит вам сортировать по любому полю, при условии что вы сначала присоедините его, вместо того чтобы WP_Query предполагал, что поле должно существовать для конкретной записи. Затем вы можете удалить параметры meta_key, orderby и order из аргументов вашего WP_Query.

Ниже приведён пример. В начале каждой функции мне пришлось добавить проверки для исключения определённых случаев, так как эти фильтры применяются ко всем запросам через WP_Query. Возможно, вам нужно будет адаптировать это под свои нужды.

Документация по этим двум фильтрам, к сожалению, практически отсутствует... так что удачи! :)

add_filter('posts_join_paged', 'edit_join', 999, 2);
add_filter('posts_orderby', 'edit_orderby', 999, 2);

/**
 * Изменяет JOIN запроса
 *
 * @param string $join_paged_statement
 * @param WP_Query $wp_query
 * @return string
 */
function edit_join($join_paged_statement, $wp_query)
{
    global $wpdb;
    if (
        !isset($wp_query->query)
        || $wp_query->is_page
        || $wp_query->is_admin
        || (isset($wp_query->query['post_type']) && $wp_query->query['post_type'] != 'my_custom_post_type')
    ) {
        return $join_paged_statement;
    }

    $join_to_add = "
        LEFT JOIN {$wpdb->prefix}postmeta AS my_custom_meta_key
            ON ({$wpdb->prefix}posts.ID = my_custom_meta_key.post_id
                AND my_custom_meta_key.meta_key = 'my_custom_meta_key')
    ";

    // Добавляем только если ещё не добавлено
    if (strpos($join_paged_statement, $join_to_add) === false) {
        $join_paged_statement = $join_paged_statement . $join_to_add;
    }

    return $join_paged_statement;
}

/** 
 * Изменяет ORDER BY запроса
 *
 * @param string $orderby_statement
 * @param WP_Query $wp_query
 * @return string
 */
function edit_orderby($orderby_statement, $wp_query)
{
    if (
        !isset($wp_query->query)
        || $wp_query->is_page
        || $wp_query->is_admin
        || (isset($wp_query->query['post_type']) && $wp_query->query['post_type'] != 'my_custom_post_type')
    ) {
        return $orderby_statement;
    }

    $orderby_statement = "my_custom_meta_key.meta_value DESC";

    return $orderby_statement;
}
15 апр. 2014 г. 21:17:20
Комментарии

Код работает. Но meta_value обрабатывается как строка. Поэтому 6 оценивается выше, чем 50. Можно ли внести изменения, чтобы они обрабатывались как числа?

Drivingralle Drivingralle
24 янв. 2017 г. 19:59:03

@Drivingralle cast(my_custom_meta_key.meta_value as unsigned) DESC должно сработать...

tfrommen tfrommen
2 февр. 2017 г. 23:34:21

Спасибо @tfrommen. $orderby_statement = "cast(my_custom_meta_key.meta_value as unsigned) DESC"; отлично работает.

Drivingralle Drivingralle
6 февр. 2017 г. 15:22:34
0

Я в итоге воспользовался фильтром 'get_meta_sql', который предоставляет WordPress

add_filter( 'get_meta_sql', 'adjust_the_meta_sql' ), 10, 2 );

Функция выглядит следующим образом

function adjust_the_meta_sql( $sql, $queries ) {
            if ( 'my_post_type' === filter_input( INPUT_GET, 'post_type' )
                && '_my_key' === $queries[0]['key'] ) {
                $sql['join']  = preg_replace( '/INNER JOIN wp_postmeta ON \(([^)]+)\)/', 'LEFT JOIN wp_postmeta ON ($1 ' . $sql['where'] . ')', $sql['join'] );
                $sql['where'] = '';
            }
            return $sql;
        }

Это решает две проблемы с SQL-запросом, генерируемым WordPress.

  1. Преобразует INNER JOIN в LEFT JOIN
  2. Переносит условие WHERE в условие ON.

Ваш конкретный запрос может отличаться, поэтому обязательно протестируйте и при необходимости адаптируйте решение.

18 мая 2022 г. 10:01:29
1

Я думаю, что @kaiser пытался сделать следующее: указать запросу вернуть все записи, которые имеют этот мета-ключ, применив своего рода "фиктивное условие WHERE", чтобы не фильтровать ни одну из этих записей. Таким образом, если вы знаете, что все возможные значения ваших произвольных полей — это x, y, z, вы могли бы сказать "WHERE meta_key IN(x,y,z)", но идея в том, что можно полностью избежать этой проблемы, используя условие != (''):

$query = new WP_Query( array ( 
    'orderby'    => 'meta_value_num',
    'order'      => 'DESC',
    'meta_query' => array( array(
         'key'     => 'item_price',
         'value'   => '',
         'compare' => '!=',
    ) )
) );

Также не тестировал, но кажется, что стоит попробовать :-).

23 авг. 2013 г. 01:58:26
Комментарии

Не могу проверить это прямо сейчас, но почти уверен, что это вернет только записи, где item_price установлен и он не равен ''.

powerbuoy powerbuoy
23 авг. 2013 г. 14:13:11
0

Ребята, спасибо за все правильные ответы, но сейчас (2022) ключевым моментом является вложенность:

$query->set( 'orderby', array(
     'meta_value_num' => 'DESC',
     'title' => 'ASC' )
); // приоритеты сортировки

$query->set( 'meta_query', array(
     'featured' => array(
          'relation' => 'OR',
          array(
               'key' => '_featured',
               'value' => '1', // сначала показываем записи со значением = 1
          ),
          array( // эти вложенные правила имеют меньший приоритет для сортировки, но гарантируют показ всех остальных записей
               'relation' => 'OR',
               array(
                    'key' => '_featured',
                    'compare' => 'NOT EXISTS', // объяснение излишне
               ),
               array(
                    'key' => '_featured',
                    'value' => '0', // случай, когда значение существует, но должно рассматриваться как отсутствующее
               )
          )
     )
));

Бонусный случай: я не тестировал это, но если мета-поле содержит не 0/1 значение, третье правило должно выглядеть так:

array(
      'key' => '_featured',
      'value' => '[значение приоритета]',
      'compare' => '!='
)
5 дек. 2022 г. 21:53:28