Самый эффективный способ получать посты с постметами

36

Мне нужно получить кучу постов с их метаданными. Конечно, вы не можете получить метаданные со стандартным запросом к сообщениям, поэтому вам, как правило, приходится делать a get_post_custom()для каждого сообщения.

Я пытаюсь с одним пользовательским запросом, как это:

$results = $wpdb->get_results("
    SELECT  p.ID,
        p.post_title,
        pm1.meta_value AS first_field,
        pm2.meta_value AS second_field,
        pm3.meta_value AS third_field
    FROM    $wpdb->posts p LEFT JOIN $wpdb->postmeta pm1 ON (
            pm1.post_id = p.ID  AND
            pm1.meta_key    = 'first_field_key'
        ) LEFT JOIN $wpdb->postmeta pm2 ON (
            pm2.post_id = p.ID  AND
            pm2.meta_key    = 'second_field_key'
        ) LEFT JOIN $wpdb->postmeta pm3 ON (
            pm3.post_id = p.ID  AND
            pm3.meta_key    = 'third_field_key'
        )
    WHERE   post_status = 'publish'
");

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

Итак, вопрос 1: есть ли объединение, подзапрос или что-то еще, чтобы ввести мета-поля с несколькими значениями?

Но вопрос 2: оно того стоит? Сколько postmetaобъединений таблиц я должен добавить, прежде чем подход с двумя запросами станет предпочтительным? Я мог бы собрать все данные поста в одном запросе, затем собрать все соответствующие постметы в другом и объединить мета с данными поста в одном наборе результатов в PHP. Будет ли это быстрее, чем один очень сложный запрос SQL, если это вообще возможно?

Я всегда думаю: «Дайте как можно больше работы с базой данных». Не уверен в этом!

Стив Тейлор
источник
Я не уверен, если вы даже хотите сделать соединения. комбинация get_posts () и get_post_meta () возвращает вам те же данные. На самом деле, использование объединений менее эффективно, поскольку вы можете получать данные, которые не будете использовать позже.
rexposadas
2
Разве мета-данные не кэшируются автоматически?
Мэнни Флермонд
@ rxn, если я вернусь к нескольким сотням сообщений (это пользовательский тип сообщений), конечно, это довольно большая нагрузка на БД get_posts(), тогда get_post_meta()для каждого из них? @MannyFleurmond, сложно найти точную информацию о встроенном кешировании WP, но AFAIK будет кешировать данные по запросу. Вызов серверу для получения этих данных является вызовом AJAX, и я не думаю, что что-то еще будет захватывать вещи до этого.
Стив Тейлор
На самом деле, я собираюсь для нескольких запросов и кеширования результатов. Оказывается, нам нужны не только мета-посты, включая поля с несколькими значениями, нам также нужны данные о пользователях, подключенных к постам через мета-поля (два набора из них), плюс мета-данные о них. Чистый SQL определенно за окном!
Стив Тейлор

Ответы:

58

Публикация метаинформации автоматически кэшируется в памяти для стандартного WP_Query(и основного запроса), если вы не указали этого специально, используя update_post_meta_cacheпараметр.

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

Как мета-кеширование работает для обычных запросов:

Если для update_post_meta_cacheпараметра WP_Queryне установлено значение false, то после извлечения сообщений из БД update_post_caches()будет вызвана функция, которая, в свою очередь, вызывает update_postmeta_cache().

Эта update_postmeta_cache()функция является оберткой для update_meta_cache(), и она по сути вызывает простой SELECTсо всеми идентификаторами полученных сообщений. Это позволит получить все постметы для всех постов в запросе и сохранить эти данные в кеше объектов (используя wp_cache_add()).

Когда вы делаете что-то вроде этого get_post_custom(), он сначала проверяет этот кеш объекта. Так что на этом этапе не нужно делать дополнительные запросы для получения мета-поста. Если вы получили сообщение в a WP_Query, то мета уже находится в памяти, и он получает его прямо оттуда.

Преимущества здесь во много раз превосходят выполнение сложного запроса, но наибольшее преимущество заключается в использовании объектного кэша. Если вы используете постоянное решение для кэширования памяти, такое как XCache или memcached или APC или что-то в этом роде, и у вас есть плагин, который может привязать к нему объектный кэш (например, W3 Total Cache), то весь кэш объектов сохраняется в быстрой памяти уже. В этом случае для получения ваших данных требуется ноль запросов; это уже в памяти. Постоянное кэширование объектов во многих отношениях удивительно.

Другими словами, ваш запрос, вероятно, загружается и загружается медленнее, чем при использовании правильного запроса и простого решения для постоянной памяти. Используйте нормальное WP_Query. Сэкономь себе немного усилий.

Дополнительно: update_meta_cache() умный, кстати. Он не будет получать метаинформацию для постов, в которых уже хранится метаинформация. Это не получает ту же самую мету дважды, в основном. Супер эффективный.

Дополнительные дополнительные: «Дайте как можно больше работы с базой данных.» ... Нет, это Интернет. Применяются разные правила. В общем, вы всегда хотите дать как можно меньше работы с базой данных, если это возможно. Базы данных работают медленно или плохо настроены (если вы не настроили это специально, вы можете поспорить, что это правда). Часто они используются многими сайтами и в некоторой степени перегружены. Обычно у вас больше веб-серверов, чем баз данных. В общем, вы хотите просто и быстро вывести нужные данные из БД, а затем выполнить их сортировку с использованием кода на стороне веб-сервера. Как общий принцип, конечно, разные случаи все разные.

эфирное масло
источник
30

Я бы порекомендовал сводный запрос. Используя ваш пример:

SELECT  p.ID,   
        p.post_title, 
        MAX(CASE WHEN wp_postmeta.meta_key = 'first_field' then wp_postmeta.meta_value ELSE NULL END) as first_field,
        MAX(CASE WHEN wp_postmeta.meta_key = 'second_field' then wp_postmeta.meta_value ELSE NULL END) as second_field,
        MAX(CASE WHEN wp_postmeta.meta_key = 'third_field' then wp_postmeta.meta_value ELSE NULL END) as third_field,

 FROM    wp_posts p LEFT JOIN wp_postmeta pm1 ON ( pm1.post_id = p.ID)                      
GROUP BY
   wp_posts.ID,wp_posts.post_title
Этан Сейферт
источник
Этот ответ должен быть помечен как правильный.
Люк
Если вы ищете запрос к базе данных, это правильный ответ
Алексей Попов
Этот запрос уменьшил мое время, когда я использовал WP_Query, с ~ 25 секунд до ~ 3 секунд. Мое требование состояло в том, чтобы запустить это только один раз, чтобы не требовалось кэширование.
Куш
11

Я сталкивался со случаем, когда хочу также быстро получить множество сообщений со связанной с ними метаинформацией. Мне нужно получить O (2000) сообщений.

Я попробовал это, используя предложение Отто - запустить WP_Query :: query для всех постов, а затем перебрать и запустить get_post_custom для каждого поста. В среднем это заняло около 3 секунд .

Затем я попробовал сводный запрос Итана (хотя мне не нравилось вручную запрашивать каждый мета-ключ, который меня интересовал). Мне все еще пришлось пройтись по всем извлеченным сообщениям, чтобы десериализовать meta_value. Это заняло в среднем около 1,3 секунды .

Затем я попытался использовать функцию GROUP_CONCAT и нашел лучший результат. Вот код:

global $wpdb;
$wpdb->query('SET SESSION group_concat_max_len = 10000'); // necessary to get more than 1024 characters in the GROUP_CONCAT columns below
$query = "
    SELECT p.*, 
    GROUP_CONCAT(pm.meta_key ORDER BY pm.meta_key DESC SEPARATOR '||') as meta_keys, 
    GROUP_CONCAT(pm.meta_value ORDER BY pm.meta_key DESC SEPARATOR '||') as meta_values 
    FROM $wpdb->posts p 
    LEFT JOIN $wpdb->postmeta pm on pm.post_id = p.ID 
    WHERE p.post_type = 'product' and p.post_status = 'publish' 
    GROUP BY p.ID
";

$products = $wpdb->get_results($query);

// massages the products to have a member ->meta with the unserialized values as expected
function massage($a){
    $a->meta = array_combine(explode('||',$a->meta_keys),array_map('maybe_unserialize',explode('||',$a->meta_values)));
    unset($a->meta_keys);
    unset($a->meta_values);
    return $a;
}

$products = array_map('massage',$products);

Это заняло в среднем 0,7 секунды . Это примерно четверть времени решения WP get_post_custom () и около половины решения сводных запросов.

Может быть, это будет кому-то интересно.

Тревор Миллс
источник
Мне было бы интересно узнать, какие результаты вы получите, используя постоянное решение для кэширования объектов. Кэширование объектов иногда будет медленнее для базового случая, в зависимости от вашей базы данных и конфигурации, но реальные результаты с большинством хостов будут давать очень разные результаты. Кэширование на основе памяти смехотворно быстро.
Отто
Привет @ Отто. Независимо от того, какой метод я использую для получения данных, я определенно хочу кэшировать результат. Я пытался использовать переходный API для этого, но у меня проблемы с памятью. Сериализованная строка для моих 2000 объектов синхронизируется с ~ 8M и set_transient () завершается неудачно (память исчерпана). Кроме того, необходимо изменить параметр MySQL max_allowed_packet. Я посмотрю в кеширование в файл, но пока не уверен в производительности. Есть ли способ кэширования в память, которая сохраняется при разных запросах?
Тревор Миллс
Да, если у вас есть постоянный кэш памяти (XCache, memcached, APC и т. Д.), И вы используете плагин кеширования объектов (W3 Total Cache поддерживает многие типы кешей памяти), то он сохраняет весь кеш объектов в памяти, давая вам многократное ускорение почти всего.
Отто
Я возвращаю 6000 элементов для использования в схеме фильтрации магистральных / подчеркивающих js. Это заняло пользовательский запрос 6s, который я даже не смог запустить как WP_Query, потому что он истек, и сделал запрос 2s. Хотя array_map немного замедляет его работу ...
Джейк
Есть ли поддержка создания высокопроизводительной поддержки для возврата всех метаданных в WP_Query?
atwellpub
2

Я оказался в ситуации, когда мне нужно было выполнить эту задачу, чтобы в конечном итоге создать документ CSV, и в итоге я работал непосредственно с MySQL для этого. Мой код объединяет таблицы post и meta для получения информации о ценах woocommerce. Ранее опубликованное решение требовало, чтобы я использовал псевдонимы таблиц в sql для правильной работы.

SELECT p.ID, p.post_title, 
    MAX(CASE WHEN pm1.meta_key = '_price' then pm1.meta_value ELSE NULL END) as price,
    MAX(CASE WHEN pm1.meta_key = '_regular_price' then pm1.meta_value ELSE NULL END) as regular_price,
    MAX(CASE WHEN pm1.meta_key = '_sale_price' then pm1.meta_value ELSE NULL END) as sale_price,
    MAX(CASE WHEN pm1.meta_key = '_sku' then pm1.meta_value ELSE NULL END) as sku
    FROM wp_posts p LEFT JOIN wp_postmeta pm1 ON ( pm1.post_id = p.ID)                 
    WHERE p.post_type in('product', 'product_variation') AND p.post_status = 'publish'
    GROUP BY p.ID, p.post_title

Тем не менее, предупреждаю, что woocommerce создала более 300 тысяч строк в моей мета-таблице, поэтому она была очень большой и поэтому очень медленной.

Терри Кернан
источник
1

НЕТ SQL ВЕРСИИ:

Получите все сообщения и все их мета значения (метас) без SQL:

Допустим, у вас есть список идентификаторов записей, хранящихся в виде массива идентификаторов, что-то вроде

$post_ids_list = [584, 21, 1, 4, ...];

Теперь получение всех сообщений и всех метасов в одном запросе невозможно без использования хотя бы небольшого количества SQL, поэтому мы должны выполнить 2 запроса (все еще только 2):

1. Получить все сообщения (используя WP_Query )

$request = new WP Query([
  'post__in' => $post_ids_list,
  'ignore_sticky_posts' => true, //if you want to ignore the "stickiness"
]);

(Не забудьте позвонить, wp_reset_postdata();если вы делаете «цикл» впоследствии;))

2. Обновите мета кеш

//don't be confused here: "post" means content type (post X user X ...), NOT post type ;)
update_meta_cache('post', $post_ids_list);

Для получения метаданных просто используйте стандарт, get_post_meta()который, как указал @Otto:
сначала заглядывает в кеш :)

Примечание: если вам на самом деле не нужны другие данные из постов (например, заголовок, контент, ...), вы можете сделать только 2. :-)

jave.web
источник
0

используя форму решения trevor и модифицируя ее для работы с вложенным SQL. Это не проверено.

global $wpdb;
$query = "
    SELECT p.*, (select pm.* From $wpdb->postmeta AS pm WHERE pm.post_id = p.ID)
    FROM $wpdb->posts p 
    WHERE p.post_type = 'product' and p.post_status = 'publish' 
";
$products = $wpdb->get_results($query);
Джонатан Йоостен
источник
-1

Я столкнулся с проблемой мета полей с несколькими значениями. Проблема с самим WordPress. Посмотри в wp-includes / meta.php. Посмотрите на эту строку:

$where[$k] = ' (' . $where[$k] . $wpdb->prepare( "CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$meta_compare_string})", $meta_value );

Проблема с оператором CAST. В запросе мета-значений переменная $ meta_type имеет значение CHAR. Я не знаю деталей о том, как CASTing значение в CHAR влияет на сериализованную строку, но чтобы исправить это, вы можете удалить приведение, чтобы SQL выглядел так:

$where[$k] = ' (' . $where[$k] . $wpdb->prepare( "$alias.meta_value {$meta_compare} {$meta_compare_string})", $meta_value );

Теперь, несмотря на то, что это работает, вы теряете внутренности WordPress, так что другие вещи могут сломаться, и это не постоянное исправление, при условии, что вам нужно обновить WordPress.

Я исправил это, скопировав SQL-запрос, сгенерированный WordPress для нужного мне мета-запроса, и затем написав PHP, чтобы добавить дополнительные операторы AND для нужных мне мета-значений, и использовать $ wpdb-> get_results ($ sql ) для окончательного вывода. Хаки, но это работает.

Гарри Лав
источник
Я не пробовал, но использование get_meta_sqlфильтра, который следует за этой строкой, конечно, было бы предпочтительнее взлома основного кода.
Стив Тейлор