Der effizienteste Weg um Posts mit Postmeta zu bekommen

36

Ich brauche eine Reihe von Posts mit ihren Metadaten. Natürlich können Sie mit einer Standardpost-Abfrage keine Metadaten abrufen, daher müssen Sie in der Regel eine get_post_custom()für jeden Post durchführen.

Ich versuche es mit einer benutzerdefinierten Abfrage, wie folgt:

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

Scheint zu funktionieren. Es wird ausgelöst, wenn Sie eines dieser Metafelder so verwenden, dass mehrere Metawerte für denselben Beitrag zulässig sind. Ich kann mir keine Verbindung vorstellen, um das zu tun.

Frage 1: Gibt es eine Verknüpfung, Unterabfrage oder was auch immer, um Metafelder mit mehreren Werten einzufügen?

Aber Frage 2: Lohnt es sich? Wie viele postmetaTabellenverknüpfungen füge ich hinzu, bevor ein Ansatz mit zwei Abfragen bevorzugt wird? Ich könnte alle Post-Daten in einer Abfrage erfassen, dann alle relevanten Post-Metas in einer anderen und das Meta mit den Post-Daten in einer Ergebnismenge in PHP kombinieren. Wäre das schneller als eine einzelne, immer komplexer werdende SQL-Abfrage, wenn das überhaupt möglich wäre?

Ich denke immer: "Gib der Datenbank so viel Arbeit wie möglich." Ich bin mir nicht sicher!

Steve Taylor
quelle
Ich bin mir nicht sicher, ob du überhaupt die Joins machen willst. Die Kombination von get_posts () und get_post_meta () gibt Ihnen die gleichen Daten zurück. Tatsächlich ist die Verwendung der Joins weniger effizient, da Sie möglicherweise Daten abrufen, die Sie später nicht mehr benötigen.
REXPOSADAS
2
Werden Metadaten nicht trotzdem automatisch zwischengespeichert?
Manny Fleurmond
@rxn, wenn mehrere hundert Posts zurückkommen (es handelt sich um einen benutzerdefinierten Post-Typ), ist das sicherlich eine ziemlich schwere Belastung für die Datenbank, get_posts()dann get_post_meta()für jeden dieser Posts ? @MannyFleurmond, es ist schwer, genaue Informationen über das in WP integrierte Caching zu finden, aber AFAIK würde es Sachen pro Anfrage zwischenspeichern. Der Aufruf an den Server, um diese Daten abzurufen, ist ein AJAX-Aufruf, und ich glaube nicht, dass irgendetwas anderes zuvor Dinge abrufen wird.
Steve Taylor
Eigentlich gehe ich für mehrere Abfragen und Zwischenspeichern der Ergebnisse. Es stellt sich heraus, dass wir nicht nur Post-Meta benötigen, einschließlich Felder mit mehreren Werten, sondern auch Daten zu Benutzern, die über Metafelder (zwei Sätze davon) mit den Posts verbunden sind, sowie Benutzer-Meta. Pure SQL ist definitiv aus dem Fenster!
Steve Taylor

Antworten:

58

Post-Meta-Informationen werden für einen Standard WP_Query(und die Hauptabfrage) automatisch im Arbeitsspeicher zwischengespeichert , es sei denn, Sie weisen ihn ausdrücklich an, dies nicht mithilfe des update_post_meta_cacheParameters zu tun .

Daher sollten Sie hierfür keine eigenen Abfragen schreiben.

So funktioniert das Meta-Caching bei normalen Abfragen:

Wenn der update_post_meta_cacheParameter für den WP_Querynicht auf false gesetzt ist, wird nach dem Abrufen der Beiträge aus dem DB die update_post_caches()Funktion aufgerufen, die wiederum aufruft update_postmeta_cache().

Die update_postmeta_cache()Funktion ist ein Wrapper für update_meta_cache()und ruft im Wesentlichen eine einfache SELECTID mit allen IDs der abgerufenen Posts auf. Dadurch erhält es das gesamte Postmetall für alle Posts in der Abfrage und speichert diese Daten im Objektcache (mithilfe von wp_cache_add()).

Wenn Sie so etwas tun get_post_custom(), wird zuerst der Objektcache überprüft. Daher sind an dieser Stelle keine zusätzlichen Abfragen erforderlich, um das Post-Meta abzurufen. Wenn Sie den Beitrag in a erhalten haben WP_Query, befindet sich das Meta bereits im Speicher und wird direkt von dort abgerufen.

Die Vorteile sind hier um ein Vielfaches größer als bei einer komplexen Abfrage. Der größte Vorteil ergibt sich jedoch aus der Verwendung des Objektcaches. Wenn Sie eine permanente Speicher-Caching-Lösung wie XCache oder memcached oder APC oder ähnliches verwenden und ein Plugin haben, das Ihren Objekt-Cache daran binden kann (z. B. W3 Total Cache), wird Ihr gesamter Objekt-Cache im schnellen Speicher gespeichert bereits. In diesem Fall sind keine Abfragen erforderlich, um Ihre Daten abzurufen. es ist schon in Erinnerung. Das dauerhafte Zwischenspeichern von Objekten ist in vielerlei Hinsicht fantastisch.

Mit anderen Worten, Ihre Abfrage wird wahrscheinlich immer langsamer geladen als eine ordnungsgemäße Abfrage und eine einfache persistente Speicherlösung. Verwenden Sie die normale WP_Query. Sparen Sie sich etwas Mühe.

Zusätzlich: update_meta_cache() ist schlau, Übrigens. Es werden keine Metainformationen für Posts abgerufen, deren Metainformationen bereits zwischengespeichert sind. Im Grunde genommen bekommt es nicht zweimal das gleiche Meta. Super effizient.

Zusätzlicher Zusatz: "Geben Sie der Datenbank so viel Arbeit wie möglich." ... Nein, das ist das Web. Es gelten andere Regeln. Im Allgemeinen möchten Sie der Datenbank immer so wenig Arbeit wie möglich widmen, wenn dies machbar ist. Datenbanken sind langsam oder schlecht konfiguriert (wenn Sie sie nicht speziell konfiguriert haben, können Sie mit gutem Geld darauf wetten, dass dies wahr ist). Oft werden sie von vielen Websites gemeinsam genutzt und sind bis zu einem gewissen Grad überlastet. Normalerweise haben Sie mehr Webserver als Datenbanken. Im Allgemeinen möchten Sie die gewünschten Daten so schnell und einfach wie möglich aus der Datenbank holen und sie dann mit dem Code auf der Seite des Webservers aussortieren. Grundsätzlich sind natürlich alle Fälle unterschiedlich.

Otto
quelle
30

Ich würde eine Pivot-Abfrage empfehlen. Verwenden Sie Ihr Beispiel:

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
Ethan Seifert
quelle
Diese Antwort sollte als richtig markiert sein.
Luke,
Wenn Sie nach einer Datenbankabfrage suchen, ist dies die richtige Antwort
Alex Popov
Diese Abfrage reduzierte meine Zeit, als ich WP_Query verwendete, von ~ 25 Sekunden auf ~ 3 Sekunden. Meine Anforderung war, dies nur einmal auszulösen, sodass kein Caching erforderlich war.
Kush
11

Ich bin auf einen Fall gestoßen, in dem ich auch schnell viele Beiträge mit den dazugehörigen Metainformationen abrufen möchte. Ich muss O (2000) Posts abrufen.

Ich habe es mit Ottos Vorschlag versucht - WP_Query :: query für alle Posts ausführen und dann get_post_custom für jeden Post durchlaufen und ausführen. Dies dauerte durchschnittlich etwa 3 Sekunden .

Ich habe dann Ethans Pivot-Abfrage ausprobiert (obwohl ich nicht gerne manuell nach jedem meta_key fragen musste, der mich interessiert hat). Ich musste immer noch alle abgerufenen Beiträge durchlaufen, um den meta_value zu unserialisieren. Dies dauerte durchschnittlich etwa 1,3 Sekunden .

Ich habe dann versucht, die GROUP_CONCAT-Funktion zu verwenden, und das beste Ergebnis gefunden. Hier ist der Code:

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

Dies dauerte durchschnittlich 0,7 Sekunden . Das ist ungefähr ein Viertel der Zeit der WP-Lösung get_post_custom () und ungefähr die Hälfte der Pivot-Abfragelösung.

Vielleicht ist das für jemanden von Interesse.

Trevor Mills
quelle
Mich würde interessieren, welche Ergebnisse Sie mit einer dauerhaften Objekt-Cache-Lösung erzielen. Abhängig von Ihrer Datenbank und Konfiguration ist der Objektcache für den Basisfall manchmal langsamer, aber echte Ergebnisse mit einer Mehrheit der Hosts führen zu sehr unterschiedlichen Ergebnissen. Speicherbasiertes Caching ist unglaublich schnell.
Otto,
Hey @Otto. Unabhängig davon, mit welcher Methode ich die Daten erhalte, möchte ich das Ergebnis unbedingt zwischenspeichern. Ich habe versucht, die transiente API zu verwenden, stoße aber auf Speicherprobleme. Die serialisierte Zeichenfolge für meine 2000 Objekte taktet mit ~ 8M und set_transient () schlägt fehl (Speicher erschöpft). Außerdem muss die MySQL-Einstellung max_allowed_packet geändert werden. Ich werde versuchen, es in eine Datei zu zwischenspeichern, bin mir aber noch nicht sicher, wie es dort abläuft. Gibt es eine Möglichkeit, den Speicher zwischenzuspeichern, der über Anforderungen hinweg bestehen bleibt?
Trevor Mills
Ja, wenn Sie über einen dauerhaften Speichercache (XCache, memcached, APC usw.) verfügen und ein Objekt-Caching-Plugin verwenden (W3 Total Cache unterstützt viele Arten von Speichercaches), wird der gesamte Objektcache im Speicher gespeichert Vielfache Beschleunigung von so ziemlich allem.
Otto
Ich gebe 6000 Elemente zurück, die in einem Backbone- / Unterstrich-js-Filterungsschema verwendet werden sollen. Dies erforderte eine benutzerdefinierte 6s-Abfrage, die ich nicht einmal als WP_Query ausführen konnte, da das Zeitlimit abgelaufen war, und machte daraus eine 2s-Abfrage. Obwohl die array_map es wieder ziemlich verlangsamt ...
Jake
Gibt es Unterstützung für den Aufbau einer Hochleistungsunterstützung für die Rückgabe aller Metadaten innerhalb einer WP_Query?
Atwellpub
2

Ich befand mich in einer Situation, in der ich diese Aufgabe erledigen musste, um letztendlich ein CSV-Dokument zu erstellen. Am Ende arbeitete ich direkt mit mysql zusammen, um dies zu tun. Mein Code verknüpft die Post- und Metatabellen, um Woocommerce-Preisinformationen abzurufen. Die zuvor veröffentlichte Lösung erforderte, dass ich Tabellenaliase in der SQL verwende, um ordnungsgemäß zu funktionieren.

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

Seien Sie jedoch gewarnt, woocommerce hat über 300.000 Zeilen in meiner Metatabelle erstellt, daher war diese sehr groß und daher sehr langsam.

Terry Kernan
quelle
1

KEINE SQL VERSION:

Holen Sie sich alle Beiträge und alle ihre Meta-Werte (Metas) ohne SQL:

Angenommen, Sie haben eine Liste von Beitrags-IDs, die in Form eines Arrays von IDs gespeichert sind

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

Jetzt ist es nicht möglich, alle Posts und Metas in einer Abfrage zu erhalten, ohne mindestens ein bisschen SQL zu verwenden. Wir müssen also zwei Abfragen durchführen (immer noch nur zwei):

1. Holen Sie sich alle Beiträge (mit WP_Query )

$request = new WP Query([
  'post__in' => $post_ids_list,
  'ignore_sticky_posts' => true, //if you want to ignore the "stickiness"
]);

(Vergiss nicht anzurufen, wp_reset_postdata();wenn du danach eine "Schleife" machst ;))

2. Aktualisieren Sie den Meta-Cache

//don't be confused here: "post" means content type (post X user X ...), NOT post type ;)
update_meta_cache('post', $post_ids_list);

Um die Metadaten zu erhalten, verwenden Sie einfach den Standard, get_post_meta()der, wie @Otto betonte: zuerst
in den Cache schaut :)

Hinweis: Wenn Sie keine weiteren Daten aus den Posts benötigen (wie Titel, Inhalt, ...) , können Sie nur 2 machen. :-)

jave.web
quelle
0

Verwenden Sie das Lösungsformular trevor und ändern Sie es, um mit verschachteltem SQL zu arbeiten. Dies wird nicht getestet.

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);
Jonathan Joosten
quelle
-1

Ich bin auch auf das Problem der Metafelder mit mehreren Werten gestoßen. Das Problem ist mit WordPress selbst. Schau in wp-includes / meta.php. Suchen Sie nach dieser Zeile:

$where[$k] = ' (' . $where[$k] . $wpdb->prepare( "CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$meta_compare_string})", $meta_value );

Das Problem ist mit der CAST-Anweisung. In einer Abfrage nach Metawerten wird die Variable $ meta_type auf CHAR gesetzt. Ich weiß nicht genau, wie sich das Casting des Werts auf CHAR auf die serialisierte Zeichenfolge auswirkt. Um dies zu beheben, können Sie die Umwandlung entfernen, sodass die SQL wie folgt aussieht:

$where[$k] = ' (' . $where[$k] . $wpdb->prepare( "$alias.meta_value {$meta_compare} {$meta_compare_string})", $meta_value );

Jetzt, obwohl das funktioniert, sind Sie mit den WordPress-Interna fertig, so dass andere Dinge möglicherweise kaputt gehen und es ist keine dauerhafte Lösung, vorausgesetzt, Sie müssen WordPress aktualisieren.

Ich habe das Problem behoben, indem ich das von WordPress generierte SQL für die gewünschte Meta-Abfrage kopiere und dann PHP schreibe, um zusätzliche AND-Anweisungen für die gesuchten Meta-Werte zu verwenden und $ wpdb-> get_results ($ sql ) für die endgültige Ausgabe. Hacky, aber es funktioniert.

Harry Love
quelle
Ich habe es nicht ausprobiert, aber es get_meta_sqlwäre natürlich besser, den Filter, der auf diese Zeile folgt , zu nutzen, als Kerncode zu hacken.
Steve Taylor