meta_query з метазначеннями як серіалізаційні масиви


37

Я працюю над проектом, в якому я створюю користувальницький тип публікації та власні дані, введені через метаполе, пов’язані з моїм користувацьким типом публікації. З будь-якої причини я вирішив зашифрувати метаполе таким чином, щоб вхідні дані у кожен метабокс були частиною масиву. Наприклад, я зберігаю довготу та широту:

<p> 
    <label for="latitude">Latitude:</label><br /> 
    <input type="text" id="latitude" name="coordinates[latitude]" class="full-width" value="" /> 
</p> 
<p>     
    <label for="longitude">Longitude:</label><br /> 
    <input type="text" id="longitude" name="coordinates[longitude]" class="full-width" value="" /> 
</p>

З будь-якої причини мені сподобалася ідея мати єдиний запис постмета для кожного метаполе. На save_postгачку я зберігаю дані так:

update_post_meta($post_id, '_coordinates', $_POST['coordinates']);

Я зробив це, тому що у мене є три метабокси і мені подобається просто мати 3 значення постмета для кожної публікації; однак я зараз зрозумів потенційну проблему з цим. Я, можливо, хочу використовувати WP_Query лише для витягування певних постів на основі цих мета-значень. Наприклад, я можу захотіти отримати всі повідомлення, що мають значення широти понад 50. Якби я мав ці дані в базі даних окремо, можливо, використовуючи ключ latitude, я би зробив щось на кшталт:

$args = array(
    'post_type' => 'my-post-type',
    'meta_query' => array(
        array(
            'key' => 'latitude',
            'value' => '50',
            'compare' => '>'
        )
    )
 );
$query = new WP_Query( $args );

Оскільки у мене є широта як частина _coordinatesпостмета, це не працює.

Отже, моє питання полягає в тому, чи є спосіб використовувати meta_queryдля запиту серіалізований масив, як у мене в цьому сценарії?

Відповіді:


37

Ні, це неможливо, і навіть може бути небезпечно.

Настійно рекомендую несеріалізувати свої дані та змінювати розпорядок збереження. Щось подібне до цього має перетворити ваші дані у новий формат:

$args = array(
    'post_type' => 'my-post-type',
    'meta_key' => '_coordinates',
    'posts_per_page' => -1
 );
$query = new WP_Query( $args );
if($query->have_posts()){
    while($query->have_posts()){
        $query->the_post();
        $c = get_post_meta($post->id,'_coordinates',true);
        add_post_meta($post->ID,'_longitude',$c['longitude']);
        add_post_meta($post->ID,'_latitude',$c['latitude']);
        delete_post_meta($post->ID,'_coordinates',$c);
    }
}

Тоді ви зможете запитувати, як хочете, за допомогою окремих клавіш

Якщо вам потрібно зберігати кілька довжин і декількох широт, ви можете зберігати кілька поштових мета з тим самим іменем. Просто використовуйте третій параметр get_post_meta, і він поверне їх усі як масив

Чому ви не можете запитувати всередині серіалізованих даних?

MySQL вважає це лише рядком і не може розбити його на структуровані дані. Розбиття їх на структуровані дані саме те, що робиться вище кодом

Можливо, ви зможете запитувати часткові шматки дати, але це буде супер ненадійно, дорого, повільно і дуже крихко, з великою кількістю крайових справ. Серіалізовані дані не призначені для SQL запитів і не форматуються регулярно та постійно.

Окрім витрат на частковий пошук рядків, мета-запити на пост повільні, а серіалізовані дані можуть змінюватися залежно від таких речей, як довжина вмісту, що робить пошук неймовірно дорогим, якщо не неможливим залежно від значення, яке ви шукаєте

Примітка про зберігання записів / об'єктів / об'єктів як серіалізованих об'єктів у мета

Можливо, ви захочете зберегти запис транзакцій у post meta або інший тип структури даних у мета мета користувача, а потім зіткнетесь із проблемою вище.

Тут вирішення полягає не в тому, щоб розбити його на окремий мета-пост, а усвідомити, що це ніколи не повинно бути мета, для початку, а спеціальний тип публікації. Наприклад, журнал або запис може бути типовою публікацією з початковою публікацією як батьківською або об'єднаною через термін таксономії

Безпека та серіалізовані об'єкти

Зберігання серіалізованих об’єктів PHP через serializeфункцію може бути небезпечним , що шкода, оскільки передача об'єкта WordPress означатиме, що він стає серіалізованим. Це відбувається тому, що коли десеріалізується об'єкт, створюється об’єкт, і всі його методи та конструктори пробуджуються. Це може не здатися великою справою, поки користувачеві не вдасться прокрасти ретельно складений вклад, що призведе до віддаленого виконання коду, коли дані зчитуються з бази даних та десерифікуються WordPress.

Цього можна уникнути, використовуючи натомість JSON, що також спрощує запити, але набагато простіше / швидше просто зберігати дані правильно та уникати структурованих серіалізованих даних для початку.


5
Для людей, які проходять повз, не переставайте читати: більш корисні (та останні) відповіді можна знайти нижче
Erenor Paz

Що робити, якщо у мене є масив ідентифікаторів для збереження - і кожен з них не являє собою інший ключ, я міг би їх зберегти під такими, як "широта" тощо, це лише одна клавіша для всіх (наприклад, при збереженні відносин тощо). Що робити тоді? @ рішення Рабні?
траїноз

1
Ви можете зберігати ключ не один раз, пари значень ключів не є унікальними. Що стосується стосунків, то для чого потрібні таксономії, якщо ви використовуєте мета для відображення декількох речей на щось, замість них введіть термін таксономії
Том Дж. Ноуелл

24

Я також стикаюся з цією ситуацією. Ось що я зробив:

$args = array(
    'post_type' => 'my-post-type',
    'meta_query' => array(
        array(
            'key' => 'latitude',
            'value' => sprintf(':"%s";', $value),
            'compare' => 'LIKE'
        )
    )
);

Сподіваюся, що це допоможе


1
Мені дуже сподобалося це рішення. На жаль, це не застосовується, коли $valueтакож є ідентифікатор. У цьому випадку я пропоную створити функції для додавання символу до кожного елемента масиву перед збереженням даних та іншу функцію для видалення символу перед використанням даних. Цей ват, серіалізований i:2індекс не буде плутати з i:D2"реальними" даними. Параметр мета-запиту повинен стати 'value' => sprintf(':"D%s";', $value),і ви збережете правильну функціональність цієї чудової відповіді!
Еренор Пас

Це рішення працює для мене
Вішал

Це також прекрасно працювало для мене. У мене виникла міні-паніка, коли я побачив прийняте рішення
Шейн Джонс,

@Erenor Paz, я щойно опублікував рішення, яке добре працює як з ідентифікаторами, так і з рядками: wordpress.stackexchange.com/a/299325/25264
Pablo SG Pacheco

використання LIKE- це чудовий і швидкий спосіб збити ваш сервер (не кажучи вже про помилкові позитиви), вам краще мати дуже хороший кешування.
Марк Каплун

10

Ви дійсно втратите можливість запиту ваших даних будь-яким ефективним способом при серіалізації записів у базу даних WP.

Загальна економія та приріст ефективності, на вашу думку, ви досягаєте серіалізацією, не буде помітною в значній мірі. Ви можете отримати трохи менший розмір бази даних, але вартість транзакцій SQL буде великою, якщо ви коли-небудь запитуватимете ці поля та намагатиметесь порівнювати їх будь-яким корисним, значущим чином.

Натомість збережіть серіалізацію для даних, до яких ви не збираєтесь запитувати, але замість цього ви отримаєте доступ лише у пасивному режимі шляхом прямого виклику API WP get_post_meta()- з цієї функції ви можете розпакувати серіалізований запис для доступу до його властивостей масиву.

Фактично присвоєно значення true як in;

$meta = get_post_meta( $post->ID, 'key', true );

Повертає дані як масив, доступний для повторення, як зазвичай.

Ви можете зосередитись на інших оптимізаціях баз даних / сайтів, таких як кешування, CSS та JS мінімізація та використання таких служб як CDN, якщо вам потрібно. Назвіть лише декілька .... WordPress Codex - хороша відправна точка для розкриття деталей на цю тему: ТУТ


3

Я щойно торкнувся серіалізованими полями і міг їх запитувати. Не використовуючи мета_запит, але використовуючи SQL-запит.

global $wpdb; 

$search = serialize('latitude').serialize(50);

$query = $wpdb->prepare("SELECT `post_id`
FROM `wp_postmeta`
WHERE `post_id` IN (SELECT `ID` FROM `wp_posts` WHERE `post_type` = 'my-post-type')
AND `meta_key` = '_coordinates'
AND `meta_value` LIKE '%s'",'%'.$search.'%');

$ids = $wpdb->get_col($query);

$args = array(
    'post__in' => $ids
    'post_type' => 'team' //add the type because the default will be 'post'
);

$posts = get_posts($args);

Запит спочатку шукає публікацію з відповідним post_type, тому кількість фільтрів wp_postmeta буде менше фільтрувати. Тоді я додав оператор, де далі зменшити рядки, фільтруючи даліmeta_key

Ідентифікатори непогано потрапляють у масив у міру необхідності для get_posts.

PS. MySQL v5.6 або новішої версії потрібен для хорошої продуктивності підзапиту


1

Цей приклад мені справді допоміг. Це спеціально для плагіна S2Members (який серіалізує метадані користувача). Але це дозволяє запитувати частину серіалізованого масиву всередині мета-ключа.

Він працює за допомогою функції MySQL REGEXP.

Ось джерело

Ось код, який запитує всіх користувачів, які живуть у США. Я легко змінив його для запиту одного з моїх спеціальних полів реєстрації, і він працював у найкоротші терміни.

  <?php
global $wpdb;
$users = $wpdb->get_results ("SELECT `user_id` as `ID` FROM `" . $wpdb->usermeta . 
          "` WHERE `meta_key` = '" . $wpdb->prefix . "s2member_custom_fields' AND 
           `meta_value` REGEXP '.*\"country_code\";s:[0-9]+:\"US\".*'");
if (is_array ($users) && count ($users) > 0)
    {
        foreach ($users as $user)
            {
                $user = /* Get full User object now. */ new WP_User ($user->ID);
                print_r($user); /* Get a full list of properties when/if debugging. */
            }
    }
?>

1

Я думаю, що є 2 рішення, які можуть спробувати вирішити проблему збереження результатів як String, так і Integers. Однак важливо сказати, як зазначали інші, що не можна гарантувати цілісність результатів, що зберігаються як Integer, оскільки, оскільки ці значення зберігаються як серіалізовані масиви, індекс та значення зберігаються точно з однаковою схемою. Приклад:

array(37,87);

зберігається як серіалізований масив, як цей

a:2:{i:0;i:37;i:1;i:87;}

Зазначте i:0як перше положення масиву і i:37як перше значення. Шаблон такий же. Але перейдемо до рішень


1) Рішення REGEXP

Це рішення працює для мене незалежно від збереження мета-значення у вигляді рядка чи числа / id. Однак він використовує REGEXP, що не так швидко, як використанняLIKE

$args = array(
    'post_type' => 'my-post-type',
    'meta_query' => array(
        array(
            'key' => 'latitude',
            'value' => '\;i\:' . $value . '\;|\"' . $value . '\";',
            'compare' => 'REGEXP'
        )
    )
);

2) ЛІКУВАННЯ Рішення

Я не впевнений у різниці продуктивності, але це рішення, яке використовує, LIKEа також працює як для числа, так і для рядків

 $args = array(
        'post_type' => 'my-post-type',
        'meta_query' => array(
            'relation' => 'OR',
            array(
                'key' => 'latitude',
                'value' => sprintf(':"%s";', $value),
                'compare' => 'LIKE'
            ),
            array(
                'key' => 'latitude',
                'value' => sprintf(';i:%d;', $value),
                'compare' => 'LIKE'
            )
        )
    );

REGEXPприємно в певних ситуаціях, але якщо ви можете використовувати LIKE, я думаю, що це кращий метод. Стара посилання, але все-таки досить корисна, на мій погляд: thingsilearn.wordpress.com/2008/02/28/… :-)
Erenor Paz

@ErenorPaz Ви праві. LIKEшвидше. Але це рішення, яке працює як для рядків, так і для чисел
Pablo SG Pacheco

Так .. так, відповідь (як завжди): залежно від ситуації, якщо ви можете використовувати "LIKE"; бажано, інакше REGEXP так само зробить :-)
Еренор Пас

@ErenorPaz, я змінив свою відповідь, додавши нове рішення, яке використовує, LIKEале працює як для чисел, так і для рядків. Я не впевнений у виконанні, оскільки він має порівняти результати, використовуючиOR
Pablo SG Pacheco,

Саме !!! що мені потрібно отримати результат такий же, як це .... Спасибі Людина !!!
kuldip Makadiya

0

Прочитавши купу порад щодо запуску WP_Queryфільтрації за допомогою серіалізованих масивів, ось, як я нарешті це зробив: створивши масив значень, розділених комами, використовуючи implode в поєднанні зі $wpdbспеціальним запитом SQL, який використовує FIND_IN_SETдля пошуку списку, розділеного комами, для запитуваного значення.

(це схоже на відповідь Томаша, але це трохи менша продуктивність для запиту SQL)

1. У function.php:

У вашому файлі function.php (або в іншому місці, де ви налаштовуєте мета-вікно), yourname_save_post()використовуйте функцію

update_post_meta($post->ID, 'checkboxArray', implode(",", $checkboxArray)); //adding the implode

для створення масиву, що містить знаки, розділені комами.

Ви також захочете змінити свою вихідну змінну в yourname_post_meta()функції побудови мета-вікна адміністратора на

$checkboxArray = explode(",", get_post_custom($post->ID)["checkboxArray"][0]); //adding the explode

2. У файлі шаблону PHP:

Тест: якщо ви запускаєте a, get_post_meta( $id );ви повинні бачити checkboxArrayмасив, що містить знаки, розділені комами, а не серіалізований масив.

Тепер ми будуємо наш власний SQL-запит за допомогою $wpdb.

global $wpdb;

$search = $post->ID;

$query = "SELECT * FROM wp_posts
          WHERE FIND_IN_SET( $search, (
              SELECT wp_postmeta.meta_value FROM wp_postmeta
              WHERE wp_postmeta.meta_key = 'blogLocations'
              AND wp_postmeta.post_id = wp_posts.ID )
          )
          AND ( wp_posts.post_type = 'post' )
          AND ( wp_posts.post_status = 'publish' );";

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

foreach ($posts as $post) {
    //your post content here
}

Зауважте FIND_IN_SET, саме тут відбувається магія.

Тепер ... оскільки я використовую SELECT *це, повертає всі дані публікації, і всередині foreachвас можна відговорити те, що ви хочете від цього (зробіть, print_r($posts);якщо ви не знаєте, що включено. Це не налаштовує "цикл" для ви (я вважаю за краще це таким чином), але його можна легко змінити, щоб налаштувати цикл, якщо вам зручніше (подивіться на setup_postdata($post);кодекс, вам, ймовірно, потрібно буде змінити, SELECT *щоб вибрати лише ідентифікатори допису та $wpdb->get_resultsправильний $wpdbтип - - див. кодекс $wpdbтакож для інформації з цього питання).

Вільхе, це зайняло трохи зусиль, але оскільки wp_queryне підтримує виконання 'compare' => 'IN'серіалізованих знаків або знаків, розділених комами, ця обшивка - ваш найкращий варіант!

Сподіваюся, що це комусь допоможе.


0

Якщо ви використовуєте likeоператор порівняння у своєму мета-запиті, він повинен добре працювати, щоб заглянути в серіалізований масив.

$wp_user_search = new WP_User_Query(array(
    'meta_query' => array(
        array(
            'key'     => 'wp_capabilities',
            'value'   => 'subscriber',
            'compare' => 'not like'
            )
        )
    )
);

призводить до:

[query_where] => WHERE 1=1 AND (
  ( wp_usermeta.meta_key = 'wp_capabilities' 
  AND CAST(wp_usermeta.meta_value AS CHAR) NOT LIKE '%subscriber%' )

0

Якщо мої метадані мають тип масиву, я використовую цей метод для запиту мета:

$args = array(
    'post_type' => 'fotobank',
    'posts_per_page' => -1,
    'meta_query' => array(
            array(
                   'key' => 'collections',
                   'value' => ':"'.$post->ID.'";',
                   'compare' => 'LIKE'
            )
     )
);
$fotos = new WP_Query($args);

Це може призвести до небажаних результатів, коли ідентифікатор публікації має те саме значення, що ідентифікатор серіалізованого рядка
Erenor Paz

0

Мені цікаво відповіли вище, де meta_queryнацілений ключ latitudeзамість _coordinates. Довелося перевірити, чи дійсно в мета-запитах вдалося націлити конкретний ключ всередині серіалізованого масиву. :)

Очевидно, це було не так.

Отже, зауважте, що _coordinatesзамість правильного ключа для націлювання latitude.

$args = array(
     'post_type' => 'my-post-type',
     'meta_query' => array(
         array(
             'key' => '_coordinates',
             'value' => sprintf(':"%s";', $value),
             'compare' => 'LIKE'
         )
     )
 );

ПРИМІТКИ:

  1. Такий підхід дає змогу орієнтуватися лише на точні збіги. Тож такі речі, як усі широти більше 50 , неможливі.

  2. Щоб включити відповідність підрядків, можна скористатися 'value' => sprintf(':"%%%s%%";', $value),. (не тестували)


-1

У мене те саме питання. Можливо, вам потрібен параметр 'type'? Ознайомтесь із цим пов’язаним питанням: Спеціальний запит поля - мета-значення - це масив

Можливо, спробуйте:

    $ args = масив (
    'post_type' => 'мій пост-тип',
    'meta_query' => масив (
        масив (
            'ключ' => "широта",
            'значення' => '50',
            'порівняти' => '>',
            'type' => 'числовий'
        )
    )
    );

Дякую за пропозицію, але це не зовсім те, що я хочу. Проблема полягає в тому, що значення, яке я намагаюся зіставити, є частиною масиву, який серіалізується в базі даних.
толманз

Так, ти маєш рацію. Я спробував це сьогодні вранці, і мені це не вийшло. У мене те саме питання. Збереження значення мета-ключа як масиву. Я починаю вважати, що цього неможливо зробити, і, можливо, мені доведеться зберігати їх як окремі метаполя з тим самим іменем ... і просто керувати видаленням / оновленням належним чином.
user4356

@ user4356 ... саме це я і буду робити. Я сподівався скоротити кількість рядків, які я вставлю для кожної публікації, але, мабуть, це неможливо.
толманз

-1

Я наткнувся на щось подібне, використовуючи плагін Magic Fields. Це може зробити трюк

$values_serialized = serialize(array('50'));
$args = array(
    'post_type' => 'my-post-type',
    'meta_query' => array(
        array(
            'key' => 'latitude',
            'value' => $values_serialized,
            'compare' => '>'
        )
    )
);

1
Дякую за пропозицію! Я думаю, що це так близько, як можна дістатися, але насправді це не спрацює, тому що порівняння серіалізованого масиву з іншим серіалізованим масивом не має сенсу, якщо я не шукав точного відповідності.
tollmanz

5
Тоді це не повинно бути позначено як правильну відповідь, і ви безвідповідально зробите це. Правильна відповідь, таким чином, буде «Ні, це неможливо»
Том Дж. Ноуелл

1
Погодьтеся, також WP обробляє серіалізацію для вас, serialize()в цьому випадку не потрібно ...
Адам

2
Насправді @ seth-stevenson відповідь чудова, коли робити саме те, що він сказав, використовуючи плагін "Magic Fields". Оскільки цей плагін за замовчуванням серіалізує певний тип даних, це найкращий спосіб зробити точну відповідність.
zmonteca

@TomJNowell Готово! Щойно взяв у мене 5 місяців;)
tollmanz
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.