meta_query z meta wartościami jako tablice serializacji

37

Pracuję nad projektem, w którym tworzę niestandardowy typ postu i niestandardowe dane wprowadzane za pomocą pól meta powiązanych z moim niestandardowym typem postu. Z jakiegokolwiek powodu postanowiłem zakodować meta-boxy w taki sposób, aby dane wejściowe w każdym metaboksie były częścią tablicy. Na przykład przechowuję długość i szerokość geograficzną:

<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>

Z jakiegokolwiek powodu podobał mi się pomysł posiadania osobnego wpisu postmeta dla każdego metaboxa. Na save_posthaku zapisuję dane w następujący sposób:

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

Zrobiłem to, ponieważ mam trzy metaboksy i lubię mieć 3 wartości postmeta dla każdego postu; zdałem sobie jednak sprawę z tego potencjalnego problemu. Mogę chcieć użyć WP_Query do wyciągania tylko niektórych postów na podstawie tych meta wartości. Na przykład mogę chcieć uzyskać wszystkie posty, które mają wartości szerokości geograficznej powyżej 50. Gdybym miał te dane w bazie danych osobno, być może używając klucza latitude, zrobiłbym coś takiego:

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

Ponieważ mam szerokość geograficzną jako część _coordinatespostmeta, to nie zadziałałoby.

Moje pytanie brzmi: czy istnieje sposób wykorzystania meta_queryzapytania do szeregowanej tablicy, jak w tym scenariuszu?

tollmanz
źródło

Odpowiedzi:

37

Nie, nie jest to możliwe, a nawet może być niebezpieczne.

Zdecydowanie zalecamy odserializowanie danych i zmodyfikowanie procedury zapisywania. Coś podobnego do tego powinno przekonwertować dane do nowego formatu:

$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);
    }
}

Następnie będziesz mógł wyszukiwać według indywidualnych potrzeb za pomocą poszczególnych kluczy

Jeśli musisz przechowywać wiele długości geograficznych i wiele szerokości geograficznych, możesz zapisać wiele meta postów o tej samej nazwie. Wystarczy użyć trzeciego parametru get_post_meta, a zwróci je wszystkie jako tablicę

Dlaczego nie możesz wysyłać zapytań do danych seryjnych?

MySQL postrzega go jako ciąg znaków i nie może podzielić go na ustrukturyzowane dane. Podział tego na dane ustrukturyzowane jest dokładnie tym, co robi powyższy kod

Być może będziesz w stanie zapytać o częściowe fragmenty daty, ale będzie to bardzo niewiarygodne, drogie, powolne i bardzo delikatne, z wieloma przypadkami krawędzi. Dane zserializowane nie są przeznaczone do zapytań SQL i nie są sformatowane w regularny i stały sposób.

Oprócz kosztów częściowego wyszukiwania ciągów, postowe meta-zapytania są powolne, a serializowane dane mogą się zmieniać w zależności od takich rzeczy, jak długość treści, co sprawia, że ​​wyszukiwanie jest niezwykle kosztowne, jeśli nie niemożliwe, w zależności od poszukiwanej wartości

Uwaga na temat przechowywania rekordów / jednostek / obiektów jako obiektów zserializowanych w Meta

Możesz zapisać rekord transakcji w meta post lub inny rodzaj struktury danych w meta użytkownika, a następnie napotkać powyższy problem.

Rozwiązaniem tutaj nie jest podzielenie go na pojedyncze meta post, ale uświadomienie sobie, że nigdy nie powinien być meta na początku, ale niestandardowy typ postu. Na przykład dziennik lub rekord może być niestandardowym typem postu, z oryginalnym postem jako rodzicem lub być połączonym terminem taksonomicznym

Bezpieczeństwo i obiekty szeregowane

Przechowywanie serializowanych obiektów PHP za pomocą serializefunkcji może być niebezpieczne , co jest niefortunne, ponieważ przekazanie obiektu do WordPress spowoduje, że zostanie on poddany serializacji. Wynika to z tego, że gdy obiekt jest zdserializowany, tworzony jest obiekt i uruchamiane są wszystkie jego metody wzbudzania oraz konstruktory. Może to nie wydawać się wielką sprawą, dopóki użytkownik nie podejmie przemyślnie spreparowanego wejścia, co doprowadzi do zdalnego wykonania kodu, gdy dane zostaną odczytane z bazy danych i zserializowane przez WordPress.

Można tego uniknąć, używając zamiast tego JSON, co również ułatwia zapytania, ale o wiele łatwiej / szybciej jest po prostu poprawnie przechowywać dane i na początku unikać uporządkowanych danych szeregowych.

Tom J Nowell
źródło
5
Dla przechodzących ludzi nie przestawaj czytać: bardziej przydatne (i najnowsze) odpowiedzi znajdują się poniżej
Erenor Paz
Co jeśli mam tablicę identyfikatorów do zapisania - i nie każdy z nich reprezentuje inny klucz, który mógłbym zapisać pod „szerokością geograficzną” itp., To tylko jeden klucz dla wszystkich (na przykład podczas zapisywania relacji itp.). Co wtedy zrobić? @ rozwiązanie rabni?
trainoasis
1
Możesz przechowywać klucz więcej niż jeden raz, pary wartości klucza nie są unikalne. Jeśli chodzi o relacje, to po to są taksonomie, jeśli używasz meta do mapowania wielu rzeczy na coś, zamiast tego umieść je w terminologii taksonomicznej
Tom J Nowell
24

Wpadłem również na tę sytuację. Oto co zrobiłem:

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

Mam nadzieję, że to pomoże

rabni
źródło
1
Naprawdę podobało mi się to rozwiązanie. Niestety nie dotyczy $valueto również identyfikatora. W takim przypadku sugeruję utworzenie funkcji, aby dodać znak do każdego elementu tablicy przed zapisaniem danych oraz innej funkcji, aby usunąć znak przed użyciem danych. W tym przypadku szeregowany i:2indeks nie będzie mylony z i:D2„prawdziwymi” danymi. Parametr meta kwerendy powinien stać się, 'value' => sprintf(':"D%s";', $value),a Ty zachowasz poprawną funkcjonalność tej wspaniałej odpowiedzi!
Erenor Paz
To rozwiązanie działa dla mnie
Vishal,
To również działało idealnie dla mnie. Wpadłem w panikę, kiedy zobaczyłem zaakceptowane rozwiązanie
Shane Jones
@Erenor Paz, właśnie opublikowałem rozwiązanie, które działa dobrze z ID i ciągami: wordpress.stackexchange.com/a/299325/25264
Pablo SG Pacheco
używanie LIKEto świetny i szybki sposób na obniżenie poziomu serwera (nie wspominając o fałszywych alarmach), dlatego lepiej mieć bardzo dobre buforowanie.
Mark Kaplun,
10

Naprawdę stracisz możliwość kwerendowania swoich danych w jakikolwiek efektywny sposób podczas szeregowania wpisów do bazy danych WP.

Ogólna oszczędność wydajności i zysk, który według ciebie osiągasz dzięki serializacji, nie będzie zauważalny w znacznym stopniu. Możesz uzyskać nieco mniejszy rozmiar bazy danych, ale koszt transakcji SQL będzie wysoki, jeśli kiedykolwiek zapytasz te pola i spróbujesz je porównać w jakikolwiek użyteczny, znaczący sposób.

Zamiast tego zapisz serializację dla danych, których nie zamierzasz odpytywać w tym charakterze, ale zamiast tego uzyskasz dostęp tylko w sposób pasywny przez bezpośrednie wywołanie WP API get_post_meta()- z tej funkcji możesz rozpakować szeregowany wpis, aby uzyskać dostęp do jego właściwości tablicy.

W rzeczywistości przypisano wartość true jak w;

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

Zwróci dane w postaci tablicy, dostępnej w celu iteracji, jak zwykle.

Możesz skupić się na innych optymalizacjach baz danych / witryn, takich jak buforowanie, minimalizacja CSS i JS oraz używanie takich usług jak CDN, jeśli potrzebujesz. Żeby wymienić tylko kilka ... Kodeks WordPress to dobry punkt wyjścia, by dowiedzieć się więcej na ten temat: TUTAJ

Adam
źródło
3

Właśnie poradziłem sobie z serializowanymi polami i mogę je zapytać. Nie używając meta_query, ale używając zapytania 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);

Zapytanie najpierw wyszukuje post z pasującym typem post_, więc liczba rekordów wp_postmeta będzie mniejsza do filtrowania. Następnie dodałem instrukcję where, aby dodatkowo zmniejszyć liczbę wierszy poprzez filtrowaniemeta_key

Identyfikatory ładnie kończą się w tablicy, zgodnie z potrzebą get_posts.

PS. MySQL w wersji 5.6 lub nowszej jest potrzebny do dobrej wydajności podkwerend

Tomas
źródło
1

Ten przykład naprawdę mi pomógł. Jest specjalnie dla wtyczki S2Members (która serializuje metadane użytkownika). Ale pozwala na zapytanie o część szeregowej tablicy w obrębie meta_key.

Działa przy użyciu funkcji REGEXP MySQL.

Oto źródło

Oto kod, który odpytuje wszystkich użytkowników mieszkających w USA. Z łatwością zmodyfikowałem go, aby przeszukiwał jedno z moich niestandardowych pól rejestracji i działał w mgnieniu oka.

  <?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. */
            }
    }
?>
BC Smith
źródło
1

Myślę, że istnieją 2 rozwiązania, które mogą spróbować rozwiązać problem przechowywania wyników zarówno jako Ciąg, jak i Liczby całkowite. Jednak ważne jest, aby powiedzieć, jak zauważyli inni, że nie można zagwarantować integralności wyników przechowywanych jako liczba całkowita, ponieważ ponieważ te wartości są przechowywane jako szeregowane tablice, indeks i wartości są przechowywane dokładnie z tym samym wzorcem. Przykład:

array(37,87);

jest przechowywany jako szeregowa tablica, taka jak ta

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

Zwróć uwagę na i:0pierwszą pozycję tablicy i i:37pierwszą wartość. Wzór jest taki sam. Ale przejdźmy do rozwiązań


1) Rozwiązanie REGEXP

To rozwiązanie działa dla mnie niezależnie od tego, czy meta wartość jest zapisywana jako ciąg znaków lub liczba / identyfikator. Jednak używa REGEXP, co nie jest tak szybkie jak używanieLIKE

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

2) PODOBNE rozwiązanie

Nie jestem pewien różnicy w wydajności, ale jest to rozwiązanie, które używa, LIKEa także działa zarówno na liczbę, jak i na ciągi znaków

 $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'
            )
        )
    );
Pablo SG Pacheco
źródło
REGEXPw niektórych sytuacjach jest miły, ale jeśli możesz użyć LIKE, myślę, że jest to preferowana metoda. Stary link, ale nadal bardzo przydatny, moim zdaniem: Thingsilearn.wordpress.com/2008/02/28/… :-)
Erenor Paz
@ErenorPaz Masz rację. LIKEjest szybszy. Ale to rozwiązanie działa zarówno na ciągi, jak i liczby
Pablo SG Pacheco
Tak .. więc odpowiedź brzmi (jak zawsze): w zależności od sytuacji, jeśli można użyć „LIKE”; jest lepiej, w przeciwnym razie REGEXP również to zrobi :-)
Erenor Paz
@ErenorPaz, zredagowałem swoją odpowiedź, dodając nowe rozwiązanie, które wykorzystuje, LIKEale działa zarówno na liczby, jak i na ciągi znaków. Nie jestem pewien co do wydajności, ponieważ musi ona porównać wyniki przy użyciuOR
Pablo SG Pacheco
Dokładnie !!! które muszę uzyskać taki sam wynik .... Dzięki stary !!!
kuldip Makadiya
0

Po przeczytaniu kilku wskazówek dotyczących uruchamiania WP_Queryfiltrowania według szeregowanych tablic, oto, jak w końcu to zrobiłem: tworząc tablicę wartości oddzielonych przecinkami za pomocą implode w połączeniu z $wpdbniestandardowym zapytaniem SQL używanym FIND_IN_SETdo przeszukiwania listy oddzielonych przecinkami żądanej wartości.

(jest to podobne do odpowiedzi Tomasa, ale jest nieco mniej wydajne w przypadku zapytania SQL)

1. W functions.php:

W pliku functions.php (lub gdziekolwiek ustawiasz meta box) w yourname_save_post()użyciu funkcji

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

aby utworzyć tablicę zawierającą wartości oddzielone przecinkami.

Będziesz także chciał zmienić zmienną wyjściową w yourname_post_meta()funkcji budowy meta-skrzynki administratora na

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

2. W szablonie pliku PHP:

Test: jeśli uruchomisz tablicę get_post_meta( $id );, powinieneś zobaczyć ją checkboxArrayjako tablicę zawierającą wartości oddzielone przecinkami zamiast tablicy zserializowanej.

Teraz budujemy nasze niestandardowe zapytanie SQL przy użyciu $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
}

Zauważ FIND_IN_SET, że tam właśnie dzieje się magia.

Teraz ... ponieważ używam SELECT *tego, zwraca wszystkie dane postu, a wewnątrz foreachmożesz wydobyć z niego to, czego chcesz (zrób, print_r($posts);jeśli nie wiesz, co jest zawarte. Nie konfiguruje „pętli” dla ty (wolę to w ten sposób), ale możesz go łatwo zmodyfikować, aby skonfigurować pętlę, jeśli wolisz (spójrz na setup_postdata($post);kodeks, prawdopodobnie będziesz musiał zmienić, SELECT *aby wybrać tylko identyfikatory postów i $wpdb->get_resultsodpowiedni $wpdbtyp - - patrz kodeks $wpdbrównież w celu uzyskania informacji na ten temat).

Cóż, zajęło to trochę wysiłku, ale ponieważ wp_querynie obsługuje wykonywania wartości 'compare' => 'IN'zserializowanych lub oddzielonych przecinkami, podkładka ta jest najlepszą opcją!

Mam nadzieję, że to komuś pomoże.

Gifford N.
źródło
0

Jeśli użyjesz likeoperatora porównania w meta zapytaniu, powinno dobrze działać, aby zajrzeć do tablicy zserializowanej.

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

prowadzi do:

[query_where] => WHERE 1=1 AND (
  ( wp_usermeta.meta_key = 'wp_capabilities' 
  AND CAST(wp_usermeta.meta_value AS CHAR) NOT LIKE '%subscriber%' )
benklocek
źródło
0

Jeśli moje metadane są typu tablic, używam tej metody do zapytania według meta:

$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);
Den Media
źródło
Może to prowadzić do niepożądanych wyników, gdy identyfikator posta ma taką samą wartość jak identyfikator serializowanego ciągu
Erenor Paz
0

Zainteresowałem się powyższymi odpowiedziami, w których meta_querycelowałem w klucz latitudezamiast _coordinates. Musiałem przejść i sprawdzić, czy w meta-kwerendach naprawdę można było kierować określony klucz w szeregowanej tablicy. :)

Oczywiście tak nie było.

Zauważ, że _coordinateszamiast tego jest prawidłowy klucz do celu latitude.

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

UWAGI:

  1. Takie podejście umożliwia celowanie tylko w dokładne mecze. Tak więc rzeczy takie jak wszystkie szerokości geograficzne większe niż 50 nie są możliwe.

  2. Aby dołączyć dopasowania podłańcuchowe, można użyć 'value' => sprintf(':"%%%s%%";', $value),. (nie testowałem)

jgangso
źródło
-1

Mam to samo pytanie. Może potrzebujesz parametru „typ”? Sprawdź to powiązane pytanie: Niestandardowe zapytanie o pole - wartość meta to Array

Może spróbuj:

    $ args = tablica (
    „post_type” => „my-post-type”,
    'meta_query' => tablica (
        szyk(
            „klucz” => „szerokość geograficzna”,
            „wartość” => „50”,
            „porównaj” => „>”,
            „typ” => „numeryczny”
        )
    )
    );
użytkownik4356
źródło
Dzięki za sugestię, ale nie o to mi chodziło. Problem polega na tym, że wartość, którą próbuję dopasować, jest częścią tablicy szeregowanej w bazie danych.
tollmanz
Masz rację. Próbowałem dziś rano i to też nie działało dla mnie. Mam ten sam problem. Przechowywanie wartości meta klucza jako tablicy. Zaczynam myśleć, że nie da się tego zrobić i zamiast tego być może będę musiał przechowywać je jako osobne pola meta o tej samej nazwie ... i po prostu odpowiednio je usuwać / aktualizować.
user4356
@ user4356 ... właśnie to zamierzam zrobić. Miałem nadzieję zmniejszyć liczbę wierszy, które wstawiłbym dla każdego postu, ale chyba nie jest to możliwe.
tollmanz
-1

Wpadłem na coś podobnego podczas korzystania z wtyczki Magic Fields. To może załatwić sprawę

$values_serialized = serialize(array('50'));
$args = array(
    'post_type' => 'my-post-type',
    'meta_query' => array(
        array(
            'key' => 'latitude',
            'value' => $values_serialized,
            'compare' => '>'
        )
    )
);
Seth Stevenson
źródło
1
Dzieki za sugestie! Myślę, że jest to tak blisko, jak to tylko możliwe, ale tak naprawdę nie zadziała, ponieważ porównywanie szeregowej tablicy z inną szeregową tablicą nie ma sensu, chyba że szukałem dokładnego dopasowania.
tollmanz
5
To nie powinno być oznaczone jako poprawna odpowiedź i jest to nieodpowiedzialne. Prawidłowa odpowiedź brzmiałaby zatem: „Nie, to niemożliwe”
Tom J Nowell
1
Zgadzam się, również WP obsługuje dla Ciebie serializację, serialize()w tym przypadku nie jest wymagane ...
Adam
2
Właściwie odpowiedź @ seth-stevenson jest świetna, gdy robisz dokładnie to, co powiedział, używając wtyczki „Magic Fields”. Ponieważ wtyczka domyślnie szereguje pewien typ danych, jest to najlepszy sposób na dopasowanie DOKŁADNE.
zmonteca
@TomJNowell Gotowe! Właśnie zajęło mi to 5 miesięcy;)
tollmanz 17.01.13