web-dev-qa-db-de.com

meta_query mit Metawerten als serialize Arrays

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. Auf dem Haken save_post speichere ich die Daten wie folgt:

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

Ich habe dies getan, 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 mit dem Schlüssel 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 des Postmetas _coordinates habe, würde dies nicht funktionieren.

Meine Frage ist also, gibt es eine Möglichkeit, meta_query zum Abfragen eines serialisierten Arrays wie in diesem Szenario zu verwenden?

36
tollmanz

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 es werden alle 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 Edge-Fä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 Funktion serialize 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 wird und alle zugehörigen Aktivierungsmethoden und Konstruktoren ausgeführt werden. Dies scheint keine große Sache zu sein, bis es einem Benutzer gelungen ist, 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 strukturierte serialisierte Daten zunächst zu vermeiden.

36
Tom J Nowell

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

23
rabni

Wenn Sie Einträge in die Datenbank WP 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, auf die Sie nicht auf diese Weise abfragen möchten, sondern nur passiv über den direkten API-Aufruf WP get_post_meta() zugreifen. In dieser Funktion können Sie einen serialisierten Eintrag entpacken Greifen Sie auch auf seine Array-Eigenschaften zu.

In der Tat zugewiesen den Wert von true 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

10
userabuser

Ich habe mich gerade mit serialisierten Feldern befasst und könnte sie abfragen. Verwenden Sie nicht die meta_query, sondern eine 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 nach meta_key weiter zu reduzieren.

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

3
Tomas

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 garantieren, 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;}

Notieren Sie sich den i:0 als erste Position des Arrays und den i:37 als 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. Es verwendet jedoch REGEXP, was nicht so schnell ist wie LIKE

$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 nicht sicher über den Leistungsunterschied, aber dies ist eine Lösung, die LIKE verwendet und sowohl für Zahlen als auch für Strings 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'
            )
        )
    );
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
BC Smith

Nachdem ich einige Tipps zum Ausführen einer WP_Query-Filterung nach serialisierten Arrays gelesen habe, habe ich es endlich so gemacht: Erstellen eines Arrays von durch Kommas getrennten Werten mit implode in Verbindung mit einer benutzerdefinierten $wpdb-SQL-Abfrage unter Verwendung von FIND_IN_SET zum Durchsuchen der durch Kommas getrennten Liste nach angeforderter Wert.

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

1. In functions.php:

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

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 Funktion yourname_post_meta() admin meta box construction auf ändern

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

2. In der Vorlage PHP Datei:

Test: Wenn Sie eine get_post_meta( $id ); ausführen, sollten Sie checkboxArray als Array anzeigen, das Ihre durch Kommas getrennten Werte anstelle eines serialisierten Arrays enthält.

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 den FIND_IN_SET, dort geschieht die Magie.

Nun ... da ich SELECT * verwende, gibt dies alle Post-Daten zurück und innerhalb der foreach können Sie das, was Sie wollen, ausgeben (führen Sie eine print_r($posts); aus, wenn Sie nicht wissen, was enthalten ist. Es funktioniert nicht Sie können "the loop" nicht für Sie einrichten (ich bevorzuge es auf diese Weise), aber es kann leicht modifiziert werden, um die Schleife einzurichten, wenn Sie dies vorziehen (werfen Sie einen Blick auf setup_postdata($post); im Codex, Sie müssen wahrscheinlich SELECT * ändern Um nur Beitrags-IDs und $wpdb->get_results für den richtigen $wpdb-Typ auszuwählen, lesen Sie den Codex für $wpdb, um Informationen zu that subject zu erhalten.

Welpe, es hat ein bisschen Mühe gekostet, aber da wp_query das Ausführen von 'compare' => 'IN' serialisierten oder durch Kommas getrennten Werten nicht unterstützt, ist dieses Shim Ihre beste Option!

Hoffe das hilft jemandem.

0
Gifford N.

Ich wurde neugierig auf die obigen Antworten, bei denen der meta_query auf den Schlüssel latitude anstatt auf _coordinates abzielte. 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 _coordinates anstelle von latitude ist.

$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. Also sind Dinge wie alle Breiten größer als 50 nicht möglich.

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

0
jgangso

Wenn Sie den Vergleichsoperator like in Ihrer Metaabfrage verwenden, sollte es problemlos 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
benklocek

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