meta_query mit Metawerten als serialize Arrays


37

Ich arbeite an einem Projekt, in dem ich einen benutzerdefinierten Beitragstyp erstelle und benutzerdefinierte Daten über Metafelder eingebe, die meinem benutzerdefinierten Beitragstyp zugeordnet sind. Aus irgendeinem Grund habe ich beschlossen, die Meta-Boxen so zu codieren, dass die Eingaben in jeder Metabox Teil eines Arrays sind. Zum Beispiel speichere ich Längen- und Breitengrade:

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

Aus irgendeinem Grund gefiel mir die Idee, für jede Metabox einen eigenen Postmeta-Eintrag zu haben. Am save_postHaken speichere ich die Daten so:

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

Ich habe das gemacht, weil ich drei Metaboxen habe und ich mag es, nur 3 Postmeta-Werte für jeden Post zu haben. Allerdings habe ich jetzt ein potenzielles Problem damit erkannt. Ich möchte möglicherweise WP_Query verwenden, um nur bestimmte Beiträge basierend auf diesen Metawerten abzurufen. Zum Beispiel möchte ich möglicherweise alle Posts mit Breitengraden über 50 erhalten. Wenn ich diese Daten einzeln in der Datenbank hätte, vielleicht mithilfe des Schlüssels latitude, würde ich Folgendes tun:

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

Da ich den Breitengrad als Teil der _coordinatesPostmeta habe, würde dies nicht funktionieren.

Meine Frage ist also, gibt es eine Möglichkeit, meta_queryein serialisiertes Array wie in diesem Szenario abzufragen?

Antworten:


37

Nein, das ist nicht möglich und könnte sogar gefährlich sein.

Ich empfehle Ihnen nachdrücklich, Ihre Daten zu unserialisieren und Ihre Speicherroutine zu ändern. Ähnliches sollte Ihre Daten in das neue Format konvertieren:

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

Dann können Sie mit einzelnen Schlüsseln nach Belieben abfragen

Wenn Sie mehrere Längen- und Breitengrade speichern müssen, können Sie mehrere Post-Metas mit demselben Namen speichern. Verwenden Sie einfach den dritten Parameter von get_post_meta, und alle werden als Array zurückgegeben

Warum können Sie in serialisierten Daten keine Abfragen durchführen?

MySQL sieht es nur als String und kann es nicht in strukturierte Daten aufteilen. Das Aufteilen in strukturierte Daten ist genau das, was der obige Code bewirkt

Möglicherweise können Sie Teilmengen des Datums abfragen, dies ist jedoch sehr unzuverlässig, teuer, langsam und sehr zerbrechlich mit vielen Randfällen. Serialisierte Daten sind nicht für SQL-Abfragen vorgesehen und werden nicht regelmäßig und konstant formatiert.

Abgesehen von den Kosten für die Teilstringsuche sind Post-Meta-Abfragen langsam und serialisierte Daten können sich abhängig von Dingen wie der Länge des Inhalts ändern, was die Suche unglaublich teuer, wenn nicht unmöglich macht, je nachdem, welchen Wert Sie suchen

Ein Hinweis zum Speichern von Datensätzen / Entitäten / Objekten als serialisierte Objekte in Meta

Möglicherweise möchten Sie einen Transaktionsdatensatz in Post-Meta oder eine andere Art von Datenstruktur in Benutzer-Meta speichern und dann das oben beschriebene Problem ausführen.

Die Lösung besteht hier nicht darin, es in einzelne Post-Metas aufzuteilen, sondern zu erkennen, dass es sich nie um Metas handeln sollte, sondern um einen benutzerdefinierten Post-Typ. Beispielsweise kann ein Protokoll oder Datensatz ein benutzerdefinierter Beitragstyp sein, wobei der ursprüngliche Beitrag übergeordnet ist, oder er kann über einen Taxonomiebegriff verknüpft werden

Sicherheit und serialisierte Objekte

Das Speichern von serialisierten PHP-Objekten über die serializeFunktion kann gefährlich sein , was bedauerlich ist, da das Übergeben eines Objekts an WordPress bedeutet, dass es serialisiert wird. Dies liegt daran, dass beim Deserialisieren des Objekts ein Objekt erstellt und alle zugehörigen Aktivierungsmethoden und Konstruktoren ausgeführt werden. Dies scheint keine große Sache zu sein, bis es einem Benutzer gelingt, eine sorgfältig ausgearbeitete Eingabe zu machen, die zur Ausführung von Code aus der Ferne führt, wenn die Daten aus der Datenbank gelesen und von WordPress de-serialisiert werden.

Dies kann vermieden werden, indem stattdessen JSON verwendet wird, wodurch die Abfragen ebenfalls einfacher werden. Es ist jedoch viel einfacher / schneller, die Daten nur korrekt zu speichern und zunächst strukturierte serialisierte Daten zu vermeiden.


5
Hören

Was ist, wenn ich eine Reihe von IDs zum Speichern habe - und sie stellen nicht jeweils einen anderen Schlüssel dar, unter dem ich sie speichern könnte, wie z. B. 'Latitude' usw., es ist nur ein Schlüssel für alle (z. B. beim Speichern von Beziehungen usw.). Was ist dann zu tun? @ Rabni Lösung?
trainoasis

1
Sie können einen Schlüssel mehrmals speichern, Schlüsselwertpaare sind nicht eindeutig. Relationen sind genau das, wofür Taxonomien gedacht sind. Wenn Sie Meta verwenden, um mehrere Dinge auf etwas abzubilden, geben Sie sie stattdessen in einen Taxonomiebegriff ein
Tom J Nowell

24

Ich stoße auch auf diese Situation. Hier was ich getan habe:

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

Ich hoffe das hilft


1
Diese Lösung hat mir sehr gut gefallen. Dies gilt leider nicht, wenn $valuees sich auch um eine ID handelt. In diesem Fall schlage ich vor, Funktionen zu erstellen, um vor dem Speichern der Daten jedem Array-Element ein Zeichen hinzuzufügen, und eine weitere Funktion, um das Zeichen vor der Verwendung der Daten zu entfernen. In diesem Fall wird der serialisierte i:2Index nicht mit i:D2den "echten" Daten verwechselt . Der Meta-Abfrage-Parameter sollte dann werden 'value' => sprintf(':"D%s";', $value),und Sie behalten die korrekte Funktionalität dieser wundervollen Antwort!
Erenor Paz

Diese Lösung funktioniert für mich
Vishal

Das hat auch bei mir perfekt geklappt. Ich hatte eine kleine Panik, als ich die akzeptierte Lösung sah
Shane Jones

@Erenor Paz, ich habe gerade eine Lösung gepostet, die sowohl mit ID als auch mit Strings funktioniert: wordpress.stackexchange.com/a/299325/25264
Pablo SG Pacheco

Die Verwendung von LIKEist eine großartige und schnelle Möglichkeit, Ihren Server herunterzufahren (ganz zu schweigen von False Positives).
Mark Kaplun

10

Wenn Sie Einträge in die WP-Datenbank serialisieren, verlieren Sie in der Tat die Möglichkeit, Ihre Daten auf effiziente Weise abzufragen.

Die allgemeine Einsparung und Steigerung der Leistung, die Sie durch die Serialisierung erzielen, wird sich kaum bemerkbar machen. Möglicherweise erhalten Sie eine etwas kleinere Datenbankgröße, aber die Kosten für SQL-Transaktionen werden hoch sein, wenn Sie diese Felder jemals abfragen und versuchen, sie auf sinnvolle Weise zu vergleichen.

Speichern Sie stattdessen die Serialisierung für Daten, die Sie nicht auf diese Weise abfragen möchten, sondern auf die Sie nur passiv über den direkten WP-API-Aufruf get_post_meta()zugreifen. Mit dieser Funktion können Sie einen serialisierten Eintrag entpacken, um auch auf seine Array-Eigenschaften zuzugreifen.

In der Tat zugewiesen den Wert von wahr wie in;

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

Gibt die Daten als Array zurück, auf das Sie wie gewohnt zugreifen können.

Sie können sich auf andere Datenbank- / Site-Optimierungen wie Caching, CSS und JS-Minimierung konzentrieren und diese Dienste bei Bedarf als CDN verwenden. Um nur einige zu nennen ... WordPress Codex ist ein guter Ausgangspunkt, um mehr zu diesem Thema zu erfahren : HIER


3

Ich habe mich gerade mit serialisierten Feldern beschäftigt und könnte sie abfragen. Nicht mit der meta_query, sondern mit einer SQL-Abfrage.

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

Die Abfrage sucht zuerst nach Posts mit dem passenden post_type, sodass weniger wp_postmeta-Datensätze gefiltert werden müssen. Dann habe ich eine where-Anweisung hinzugefügt, um die Zeilen durch Filtern weiter zu reduzierenmeta_key

Die IDs landen gut in einem Array, das für get_posts benötigt wird.

PS. MySQL v5.6 oder höher wird für eine gute Leistung bei Unterabfragen benötigt


1

Dieses Beispiel hat mir sehr geholfen. Es ist speziell für das S2Members-Plugin (das Benutzer-Metadaten serialisiert). Sie können jedoch einen Teil eines serialisierten Arrays im meta_key abfragen.

Es funktioniert mit der MySQL REGEXP-Funktion.

Hier ist die Quelle

Hier ist der Code, mit dem alle in den USA lebenden Benutzer befragt werden. Ich änderte es leicht, um eines meiner benutzerdefinierten Registrierungsfelder abzufragen, und es funktionierte in kürzester Zeit.

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

Ich denke, es gibt zwei Lösungen, die versuchen können, das Problem zu lösen, dass Ergebnisse sowohl als Zeichenfolge als auch als Ganzzahlen gespeichert werden. Es ist jedoch wichtig zu erwähnen, dass es nicht möglich ist, die Integrität der als Ganzzahl gespeicherten Ergebnisse zu gewährleisten, da diese Werte als serialisierte Arrays gespeichert werden und der Index und die Werte genau nach demselben Muster gespeichert werden. Beispiel:

array(37,87);

wird wie folgt als serialisiertes Array gespeichert

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

Beachten Sie die i:0als erste Position des Arrays und i:37als ersten Wert. Das Muster ist das gleiche. Aber gehen wir zu den Lösungen


1) REGEXP-Lösung

Diese Lösung funktioniert für mich unabhängig davon, ob der Metawert als Zeichenfolge oder Zahl / ID gespeichert wird. Allerdings nutzt es REGEXP, was nicht so schnell ist wie mitLIKE

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

2) WIE Lösung

Ich bin mir über den Leistungsunterschied nicht sicher, aber dies ist eine Lösung, die LIKEsowohl für Zahlen als auch für Zeichenfolgen verwendet und funktioniert

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

REGEXPist in bestimmten Situationen nett, aber wenn Sie verwenden können LIKE, denke ich, dass es die vorzuziehende Methode ist. Ein alter Link, aber meiner Meinung nach immer noch recht nützlich: thingsilearn.wordpress.com/2008/02/28/… :-)
Erenor Paz

@ErenorPaz Du hast recht. LIKEist schneller. Aber dies ist eine Lösung, die sowohl für Zeichenfolgen als auch für Zahlen funktioniert
Pablo SG Pacheco

Ja..so lautet die Antwort (wie immer): Abhängig von der Situation, wenn Sie "LIKE" verwenden können; Es ist vorzuziehen, sonst wird REGEXP auch tun :-)
Erenor Paz

@ErenorPaz, ich habe meine Antwort bearbeitet und eine neue Lösung hinzugefügt, die LIKEsowohl für Zahlen als auch für Zeichenfolgen funktioniert. Ich bin mir nicht sicher über die Leistung, weil es die Ergebnisse mitOR
Pablo SG Pacheco

Genau !!! was ich brauche, um das gleiche Ergebnis wie dieses zu bekommen .... Danke Mann !!!
Kuldip Makadiya

0

Nachdem ich eine Reihe von Tipps zum Ausführen einer WP_QueryFilterung nach serialisierten Arrays gelesen habe , habe ich Folgendes getan: Erstellen eines Arrays mit durch Kommas getrennten Werten mithilfe von implode in Verbindung mit einer $wpdbbenutzerdefinierten SQL-Abfrage FIND_IN_SET, bei der die Liste mit durch Kommas getrennten Werten nach dem angeforderten Wert durchsucht wird.

(Dies ähnelt der Antwort von Tomas, ist jedoch für die SQL-Abfrage etwas weniger leistungsintensiv.)

1. In functions.php:

yourname_save_post()Verwenden Sie in Ihrer functions.php-Datei (oder wo immer Sie die Meta-Box einrichten) die Funktion

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

um ein Array mit kommagetrennten Werten zu erstellen.

Sie möchten auch Ihre Ausgabevariable in der yourname_post_meta()Admin-Meta-Box-Konstruktionsfunktion auf ändern

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

2. In der PHP-Vorlagendatei:

Test: Wenn Sie a ausführen get_post_meta( $id );, sollten Sie checkboxArrayanstelle eines serialisierten Arrays ein Array mit durch Kommas getrennten Werten sehen.

Jetzt erstellen wir unsere benutzerdefinierte SQL-Abfrage mit $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
}

Beachten Sie, das FIND_IN_SETist, wo die Magie passiert.

Nun ... da ich SELECT *dies verwende, werden alle Post-Daten zurückgegeben und in der können foreachSie das, was Sie wollen, wiedergeben (tun print_r($posts);Sie dies, wenn Sie nicht wissen, was enthalten ist. Es wird keine "Schleife" für eingerichtet Sie (ich bevorzuge es auf diese Weise), aber es kann leicht geändert werden, um die Schleife einzurichten, wenn Sie dies vorziehen (werfen Sie einen Blick setup_postdata($post);in den Codex, Sie müssen wahrscheinlich ändern SELECT *, um nur Beitrags-IDs und $wpdb->get_resultsden richtigen $wpdbTyp auszuwählen - - Weitere $wpdbInformationen zu diesem Thema finden Sie im Kodex .)

Welpe, es hat ein bisschen Mühe gekostet, aber da serialisierte oder durch Kommas getrennte Werte wp_querynicht unterstützt 'compare' => 'IN'werden, ist dieses Shim Ihre beste Option!

Hoffe das hilft jemandem.


0

Wenn Sie den likeVergleichsoperator in Ihrer Meta-Abfrage verwenden, sollte es gut funktionieren, in einem serialisierten Array nachzuschauen.

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

Ergebnisse in:

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

0

Wenn meine Metadaten vom Typ Array sind, verwende ich diese Methode für die Abfrage nach 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);

Dies kann zu unerwünschten Ergebnissen führen, wenn eine Beitrags-ID denselben Wert wie die ID des serialisierten Strings hat
Erenor Paz,

0

Ich wurde neugierig auf die obigen Antworten, bei denen meta_queryder Schlüssel latitudestatt gezielt wurde _coordinates. Ich musste testen, ob es in Meta-Abfragen wirklich möglich war, einen bestimmten Schlüssel innerhalb eines serialisierten Arrays zu ermitteln. :)

Das war offensichtlich nicht der Fall.

Beachten Sie also, dass der richtige Schlüssel zum Ziel _coordinatesanstelle von lautet latitude.

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

ANMERKUNGEN:

  1. Dieser Ansatz ermöglicht es nur, exakte Übereinstimmungen zu erzielen. Dinge wie alle Breiten über 50 sind also nicht möglich.

  2. Um Teilstring-Übereinstimmungen einzuschließen, könnte man verwenden 'value' => sprintf(':"%%%s%%";', $value),. (nicht getestet)


-1

Ich habe die gleiche frage Benötigen Sie den Parameter 'type'? Schauen Sie sich diese verwandte Frage an: Benutzerdefinierte Feldabfrage - Meta-Wert ist Array

Vielleicht versuchen Sie:

    $ args = array (
    'post_type' => 'mein-post-typ',
    'meta_query' => array (
        Array (
            'key' => 'width',
            'value' => '50',
            'compare' => '>',
            'type' => 'numeric'
        )
    )
    );

Danke für den Vorschlag, aber das ist nicht ganz das, wonach ich suche. Das Problem besteht darin, dass der Wert, den ich abgleichen möchte, Teil eines Arrays ist, das in der Datenbank serialisiert ist.
tollmanz

Ja, du hast recht. Ich habe es heute Morgen versucht und es hat auch bei mir nicht funktioniert. Ich habe das gleiche Problem. Speichern eines Werts eines Metaschlüssels als Array. Ich fange an zu glauben, dass dies nicht möglich ist, und ich muss sie stattdessen möglicherweise als separate Metafelder mit demselben Namen speichern ... und das Löschen / Aktualisieren einfach ordnungsgemäß verwalten.
user4356

@ user4356 ... genau das werde ich tun. Ich hatte gehofft, die Anzahl der Zeilen, die ich für jeden Beitrag einfügen würde, zu verringern, aber ich denke, das ist nicht möglich.
tollmanz

-1

Ich bin auf etwas Ähnliches gestoßen, als ich das Magic Fields-Plugin verwendet habe. Dies könnte den Trick machen

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

1
Danke für den Vorschlag! Ich denke, dies ist so nah wie möglich, aber es funktioniert nicht wirklich, da der Vergleich eines serialisierten Arrays mit einem anderen serialisierten Array keinen Sinn ergibt, es sei denn, ich habe nach einer genauen Übereinstimmung gesucht.
tollmanz

5
Dann sollte dies nicht als die richtige Antwort markiert werden und es ist unverantwortlich von Ihnen, dies zu tun. Die richtige Antwort wäre also "Nein, es ist nicht möglich"
Tom J Nowell

1
Zustimmen, auch WP übernimmt die Serialisierung für Sie, serialize()ist in diesem Fall nicht erforderlich ...
Adam

2
Eigentlich ist @seth-stevenson eine großartige Antwort, wenn Sie genau das tun, was er gesagt hat, indem Sie das Plugin "Magic Fields" verwenden. Da dieses Plugin standardmäßig bestimmte Datentypen serialisiert, ist dies der beste Weg, um eine EXAKTE Übereinstimmung zu erzielen.
Zmonteca

@ TomJNowell Fertig! Ich habe gerade 5 Monate
gebraucht
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.