Найефективніший спосіб отримувати повідомлення за допомогою постмета


36

Мені потрібно отримати купу публікацій з їх метаданими. Звичайно, ви не можете отримати метадані зі стандартним запитом повідомлень, тому зазвичай потрібно робити 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приєднань таблиці я додати до того, як підхід із 2 запитами стане кращим? Я міг схопити всі дані публікації в одному запиті, потім захопити всі відповідні постмета в іншому і поєднати мета з даними публікації в одному наборі результатів в PHP. Чи буде це в кінцевому підсумку швидше, ніж один, все складніший SQL-запит, якщо це можливо?

Я завжди думаю: "Надайте якомога більше роботи над базою даних". Не впевнений у цьому!


Я не впевнений, чи хочете ви навіть приєднатися. комбінація get_posts () та get_post_meta () повертає ті самі дані. Насправді, менш ефективно використовувати з'єднання, оскільки ви можете отримувати дані, які ви не будете використовувати згодом.
rexposadas

2
Чи не розміщено автоматично кешовані метадані автоматично автоматично?
Менні Флермонд

@rxn, якщо у мене повертається кілька сотень дописів (вони є спеціальним типом публікацій), напевно це досить велике завантаження БД get_posts(), то get_post_meta()для кожного з них? @MannyFleurmond, важко знайти важку інформацію про вбудований кеш-пам'ять WP, але AFAIK він кешуватиме дані за запит. Заклик до сервера захопити ці дані - це дзвінок AJAX, і я не думаю, що ще щось захопить його.
Стів Тейлор

Насправді я збираюся на кілька запитів і кешувати результати. Виявляється, нам потрібні не тільки мета meta, включаючи поля, що мають кілька значень, нам також потрібні дані про користувачів, підключених до публікацій через метаполя (два набори з них), а також мета мета користувача на них. Чистий 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() розумно, BTW. Він не отримуватиме метаінформацію для публікацій, у яких вже є кешована мета інформація. У принципі, він не отримує однакові мета двічі. Супер ефективний.

Додатковий додатковий: "Надайте якомога більше роботи над базою даних." ... Ні, це веб. Діють різні правила. Загалом, ви завжди хочете приділити якомога менше роботи над базою даних, якщо це можливо. Бази даних є повільними або погано налаштованими (якщо ви їх не налаштували спеціально, ви можете поставити гроші на те, що це правда). Часто вони поділяються між багатьма сайтами і певною мірою перевантажуються. Зазвичай у вас більше веб-серверів, ніж баз даних. Взагалі, ви хочете просто отримати потрібні дані з БД якомога швидше і простіше, а потім виконайте їх сортування за допомогою коду на стороні веб-сервера. Як загальний принцип, звичайно, різні випадки всі різні.


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 :: запит для всіх публікацій, а потім перегляньте та запустивши get_post_custom для кожної публікації. В середньому на це пішло близько 3 секунд .

Потім я спробував запит Етона на вивірку (хоча мені не сподобалося вручну запитувати кожен цікавий мета-ключ). Мені все одно довелося переглядати всі отримані повідомлення, щоб несеріалізувати мета-значення. В середньому на це пішло приблизно 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 об'єктів працює на ~ 8М, а set_transient () виходить з ладу (пам'ять вичерпана). Крім того, доведеться змінити параметр My_QL max_allowed_packet. Я вивчу кешування файлів, але поки не впевнений у виконанні. Чи є спосіб кешування пам'яті, що зберігається в запитах?
Тревор Міллс

Так, якщо у вас є стійкий кеш пам'яті (XCache, memcached, APC тощо), і ви використовуєте плагін об'єкта кешування об'єктів (W3 Total Cache підтримує багато типів кеш-пам’яті), він зберігає весь об’єкт кеш-пам’яті, надаючи вам багаторазове прискорення майже всього.
Отто

Повертаю 6000 елементів для використання в схемі фільтрації магістральних / підкреслених js. Для цього потрібен спеціальний запит 6s, який я навіть не міг запустити як WP_Query, оскільки він вичерпався, і зробив його запитом 2s. Хоча array_map сповільнює його назад трохи ...
Джейк

Чи є підтримка для створення високопродуктивної підтримки для повернення всіх метаданих у межах WP_Query?
atwellpub

2

Я опинився в ситуації, що мені потрібно було виконати це завдання, щоб у кінцевому підсумку створити документ CSV, і в кінцевому підсумку працював безпосередньо з mysql, щоб це зробити. Мій код приєднується до поштових та мета-таблиць, щоб отримати інформацію про ціни на 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

Будьте попереджені, проте, комерція створила 300 К + рядків у моїй мета таблиці, тому вона була дуже великою, а тому дуже повільною.


1

ВІД ВЕРСІЇ SQL:

Отримуйте всі повідомлення та всі їх мета-значення (metas) без SQL:

Скажімо, у вас є список ідентифікаторів публікацій, що зберігаються як масив ідентифікаторів, щось подібне

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

Тепер отримати всі повідомлення та всі метаси за 1 запит неможливо без використання принаймні трохи 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. :-)


0

використовуючи рішення form 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-include / 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, щоб застосувати додаткові І оператори для мета-значень, які я шукаю, і використовую $ wpdb-> get_results ($ sql ) для кінцевого результату. Хаккі, але це працює.


Я не пробував цього, але використання get_meta_sqlфільтра, що відповідає цьому рядку, звичайно, було б кращим, ніж злом основного коду.
Стів Тейлор
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.