webentwicklung-frage-antwort-db.com.de

Wie erweitere ich WP_Query, um eine benutzerdefinierte Tabelle in die Abfrage aufzunehmen?

Ich habe Tage über dieses Problem jetzt gewesen. Anfänglich ging es darum, wie die Follower-Daten eines Benutzers in einer Datenbank gespeichert werden, für die ich hier bei WordPress Answers einige nette Empfehlungen erhalten habe. Nach den Empfehlungen habe ich eine neue Tabelle wie diese hinzugefügt:

id  leader_id   follower_id
1   2           4
2   3           10
3   2           10

In der obigen Tabelle hat die erste Zeile einen Benutzer mit einer ID von 2, auf den ein Benutzer mit einer ID von 4 folgt. In der zweiten Zeile folgt einem Benutzer mit einer ID von 3 ein Benutzer mit einer ID Dieselbe Logik gilt für die dritte Reihe.

Jetzt möchte ich im Wesentlichen WP_Query erweitern, sodass ich nur die von den Vorgesetzten eines Benutzers abgerufenen Posts auf die von beschränken kann. Unter Berücksichtigung der obigen Tabelle sollten die Ergebnisse also nur Posts nach Benutzer-ID 2 und Benutzer-ID 3 enthalten, wenn ich die Benutzer-ID 10 an WP_Query übergebe.

Ich habe viel gesucht, um eine Antwort zu finden. Ich habe auch kein Tutorial gesehen, das mir helfen soll, die WP_Query-Klasse zu erweitern. Ich habe Mike Schinkels Antworten (Erweiterung von WP_Query) auf ähnliche Fragen gesehen, aber ich habe wirklich nicht verstanden, wie ich sie auf meine Bedürfnisse anwenden kann. Es wäre toll, wenn mir jemand dabei helfen könnte.

Links zu Mikes Antworten wie gewünscht: Link 1 , Link 2

29
John

Ich beantworte diese Frage sehr spät und entschuldige mich dafür. Ich war viel zu sehr mit Fristen beschäftigt, um mich darum zu kümmern.

Ein großes Dankeschön an @ m0r7if3r und @kaiser für die Bereitstellung der Basislösungen, die ich erweitern und in meine Anwendung implementieren konnte. Diese Antwort enthält Details zu meiner Anpassung der von @ m0r7if3r und @kaiser angebotenen Lösungen.

Lassen Sie mich zunächst erklären, warum diese Frage überhaupt gestellt wurde. Aus der Frage und den Kommentaren könnte man ersehen, dass ich versuche, WP_Query dazu zu bringen, Beiträge von allen Benutzern (Leitern) abzurufen, denen ein bestimmter Benutzer (Follower) folgt. Die Beziehung zwischen dem Follower und dem Leader wird in einer benutzerdefinierten Tabelle follow gespeichert. Die häufigste Lösung für dieses Problem besteht darin, die Benutzer-IDs aller Leiter eines Followers aus der folgenden Tabelle zu ziehen und in einem Array abzulegen. Siehe unten:

global $wpdb;
$results = $wpdb->get_results($wpdb->prepare('SELECT leader_id FROM cs_follow WHERE follower_id = %s', $user_id));

foreach($results as $result)
    $leaders[] = $result->leader_id;

Sobald Sie das Array von Führungskräften haben, können Sie es als Argument an WP_Query übergeben. Siehe unten:

if (isset($leaders)) $authors = implode(',', $leaders); // Necessary as authors argument of WP_Query only accepts string containing post author ID's seperated by commas

$args = array(
    'post_type'         => 'post',
    'posts_per_page'    => 10,
    'author'            => $authors
);

$wp_query = new WP_Query( $args );

// Normal WordPress loop continues

Die obige Lösung ist der einfachste Weg, um die gewünschten Ergebnisse zu erzielen. Es ist jedoch nicht skalierbar. In dem Moment, in dem Sie einen Follower haben, der Zehntausenden von Führungskräften folgt, wird das resultierende Array von Führungskraft-IDs extrem groß und zwingt Ihre WordPress-Site, 100 MB - 250 MB Speicher bei jedem Laden der Seite zu belegen und die Site schließlich zum Absturz zu bringen. Die Lösung des Problems besteht darin, eine SQL-Abfrage direkt in der Datenbank auszuführen und relevante Posts abzurufen. Zu diesem Zeitpunkt kam die Lösung von @ m0r7if3r zur Rettung. Auf Empfehlung von @ kaiser habe ich mich vorgenommen, beide Implementierungen zu testen. Ich habe ungefähr 47.000 Benutzer aus einer CSV-Datei importiert, um sie für eine neue Testinstallation von WordPress zu registrieren. Die Installation lief unter dem Thema Twenty Eleven. Anschließend habe ich eine for-Schleife ausgeführt, damit etwa 50 Benutzer jedem anderen Benutzer folgen. Der Unterschied in der Abfragezeit für die Lösung von @kaiser und @ m0r7if3r war atemberaubend. Normalerweise dauerte die Lösung von @ kaiser für jede Abfrage etwa 2 bis 5 Sekunden. Die von mir angenommene Variation tritt auf, wenn WordPress Abfragen für die spätere Verwendung zwischenspeichert. Andererseits zeigte die Lösung von @ m0r7if3r eine durchschnittliche Abfragezeit von 0,02 ms. Zum Testen beider Lösungen hatte ich für die Spalte leader_id die Indizierung ON. Ohne Indexierung stieg die Abfragezeit drastisch an.

Die Speichernutzung bei Verwendung einer Array-basierten Lösung lag zwischen 100 und 150 MB und ging bei Ausführung von Direct SQL auf 20 MB zurück.

Ich habe mit der Lösung von @ m0r7if3r einen Riesenerfolg erlebt, als ich die Follower-ID an die posts_where-Filterfunktion übergeben musste. Zumindest erlaubt WordPress meines Wissens keine Möglichkeit, eine Variable an Filer-Funktionen zu übergeben. Sie können zwar globale Variablen verwenden, aber ich wollte globale Variablen vermeiden. Am Ende habe ich WP_Query erweitert, um das Problem endlich zu beheben. Hier ist die endgültige Lösung, die ich implementiert habe (basierend auf der Lösung von @ m0r7if3r).

class WP_Query_Posts_by_Leader extends WP_Query {
    var $follower_id;

    function __construct($args=array()) {
        if(!empty($args['follower_id'])) {
            $this->follower_id = $args['follower_id'];
            add_filter('posts_where', array($this, 'posts_where'));
        }

        parent::query($args);
    }

    function posts_where($where) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'follow';
        $where .= $wpdb->prepare(" AND post_author IN (SELECT leader_id FROM " . $table_name . " WHERE follower_id = %d )", $this->follower_id);
        return $where;
    }
}


$args = array(
    'post_type'         => 'post',
    'posts_per_page'    => 10,
    'follower_id'       => $follower_id
);

$wp_query = new WP_Query_Posts_by_Leader( $args );

Hinweis: Ich habe die obige Lösung schließlich mit 1,2 Millionen Einträgen in der folgenden Tabelle ausprobiert. Die durchschnittliche Abfragezeit lag bei 0,060 ms.

11
John

Wichtiger Hinweis: Der richtige Weg, dies zu tun, besteht NICHT darin, Ihre Tabellenstruktur zu ändern, sondern wp_usermeta zu verwenden. Dann müssen Sie kein benutzerdefiniertes SQL erstellen, um Ihre Posts abzufragen (obwohl Sie noch benutzerdefiniertes SQL benötigen, um eine Liste aller Personen zu erhalten, die einem bestimmten Supervisor Bericht erstatten - beispielsweise im Abschnitt "Admin"). Da das OP jedoch nach dem Schreiben von benutzerdefiniertem SQL gefragt hat, finden Sie hier die derzeit empfohlene Vorgehensweise zum Injizieren von benutzerdefiniertem SQL in eine vorhandene WordPress-Abfrage.

Wenn Sie komplexe Verknüpfungen ausführen, können Sie nicht einfach den Filter posts_where verwenden, da Sie die Verknüpfung, die Auswahl und möglicherweise auch die Gruppierung nach oder die Reihenfolge nach Abschnitten der Abfrage ändern müssen.

Verwenden Sie am besten den Filter "posts_clauses". Dies ist ein äußerst nützlicher Filter (der nicht missbraucht werden sollte!), Mit dem Sie die verschiedenen Teile der SQL-Anweisung anhängen/ändern können, die von den vielen Codezeilen in WordPress Core automatisch generiert werden. Die Filterrückrufsignatur lautet: function posts_clauses_filter_cb( $clauses, $query_object ){ } und erwartet, dass Sie $clauses zurückgeben.

Die Klauseln

$clauses ist ein Array, das die folgenden Schlüssel enthält. Jeder Schlüssel ist eine SQL-Zeichenfolge, die direkt in der endgültigen SQL-Anweisung verwendet wird, die an die Datenbank gesendet wird:

  • woher
  • gruppiere nach
  • beitreten
  • sortieren nach
  • deutlich
  • felder
  • grenzen

Wenn Sie der Datenbank eine Tabelle hinzufügen (tun Sie dies nur, wenn Sie post_meta, user_meta oder Taxonomien absolut nicht nutzen können), müssen Sie wahrscheinlich mehr als eine dieser Klauseln berühren, z. B. die fields (das "SELECT") "Teil der SQL - Anweisung), die join (alle Ihre Tabellen, außer der in Ihrer" FROM "- Klausel) und möglicherweise die orderby.

Änderung der Klauseln

Am besten referenzieren Sie dazu den entsprechenden Schlüssel aus dem $clauses-Array, das Sie vom Filter erhalten haben:

$join = &$clauses['join'];

Wenn Sie jetzt $join ändern, ändern Sie $clauses['join'] direkt, sodass die Änderungen in $clauses gespeichert werden, wenn Sie ihn zurückgeben.

Erhalt der ursprünglichen Klauseln

Es besteht die Möglichkeit, dass Sie das vorhandene SQL, das WordPress für Sie generiert hat, beibehalten möchten (nein, im Ernst, hören Sie zu). Wenn nicht, sollten Sie sich stattdessen wahrscheinlich den posts_request-Filter ansehen - das ist die vollständige mySQL-Abfrage, kurz bevor sie an die Datenbank gesendet wird, sodass Sie sie mit Ihrer eigenen vollständig löschen können. Warum würdest du das tun wollen? Sie wahrscheinlich nicht.

Um die vorhandene SQL in den Klauseln beizubehalten, müssen Sie daran denken, an die Klauseln anzuhängen und sie nicht zuzuweisen (z. B .: $join .= ' {NEW SQL STUFF}'; nicht $join = '{CLOBBER SQL STUFF}'; verwenden. Beachten Sie, dass jedes Element des $clauses-Arrays eine Zeichenfolge ist, wenn Sie sie anhängen möchten Dazu möchten Sie wahrscheinlich ein Leerzeichen vor anderen Zeichen-Token einfügen, andernfalls erstellen Sie wahrscheinlich einen SQL-Syntaxfehler.

Sie können einfach davon ausgehen, dass in jeder Klausel immer etwas enthalten ist, und denken Sie daran, jede neue Zeichenfolge mit einem Leerzeichen zu beginnen, wie in: $join .= ' my_table :

$join = &$clauses['join'];
if (! empty( $join ) ) $join .= ' ';
$join .= "JOIN my_table... "; // <-- note the space at the end
$join .= "JOIN my_other_table... ";


return $clauses;

Das ist mehr als alles andere eine stilistische Sache. Das Wichtigste, an das Sie sich erinnern sollten, ist: Lassen Sie immer ein Leerzeichen VOR Ihrer Zeichenfolge, wenn Sie an eine Klausel anhängen, in der bereits SQL enthalten ist!

Etwas zusammensetzen

Die erste Regel bei der Entwicklung von WordPress ist versuchen Sie, so viele Kernfunktionen wie möglich zu verwenden. Dies ist der beste Weg, um Ihre Arbeit zukunftssicher zu machen. Angenommen, das Kernteam entscheidet, dass WordPress jetzt SQLite oder Oracle oder eine andere Datenbanksprache verwendet. Jedes handgeschriebene mySQL kann ungültig werden und Ihr Plugin oder Theme beschädigen! Es ist besser, WP so viel SQL wie möglich selbst generieren zu lassen und nur die benötigten Bits hinzuzufügen.

Die erste Aufgabe besteht also darin, mithilfe von WP_Query so viel wie möglich von Ihrer Basisabfrage zu generieren. Die genaue Methode, mit der wir dies tun, hängt weitgehend von where ab, in der die Liste der Beiträge erscheinen soll. Wenn es sich um einen Unterabschnitt der Seite handelt (nicht um Ihre Hauptabfrage), würden Sie get_posts() verwenden. Wenn es sich um die Hauptabfrage handelt, können Sie vermutlich query_posts() verwenden und damit fertig sein. Die richtige Vorgehensweise besteht jedoch darin, die Hauptabfrage abzufangen, bevor sie die Datenbank erreicht (und Serverzyklen beansprucht). Verwenden Sie daher den Filter request.

Okay, Sie haben also Ihre Abfrage generiert und die SQL wird gerade erstellt. Tatsächlich wurde es erstellt, aber nicht an die Datenbank gesendet. Mit dem Filter posts_clauses fügen Sie Ihre Mitarbeiterbeziehungstabelle in den Mix ein. Nennen wir diese Tabelle {$ wpdb-> Präfix}. 'user_relationship' und es ist eine Kreuzungstabelle. (Übrigens empfehle ich, diese Tabellenstruktur zu generieren und in eine richtige Schnittstellentabelle mit den folgenden Feldern umzuwandeln: 'relationship_id', 'user_id', 'related_user_id', 'relationship_type'; dies ist viel flexibler und leistungsfähiger. .. Aber ich schweife ab).

Wenn ich verstehe, was Sie tun möchten, möchten Sie die ID eines Anführers weitergeben und dann nur die Beiträge der Anhänger dieses Anführers anzeigen. Ich hoffe, ich habe das richtig verstanden. Wenn es nicht stimmt, müssen Sie das, was ich sage, nehmen und an Ihre Bedürfnisse anpassen. Ich bleibe bei deiner Tabellenstruktur: Wir haben einen leader_id und einen follower_id. Der JOIN befindet sich also auf {$wpdb->posts}.post_author als Fremdschlüssel für die 'follower_id' in Ihrer 'user_relationship'-Tabelle.

add_filter( 'posts_clauses', 'filter_by_leader_id', 10, 2 ); // we need the 2 because we want to get all the arguments

function filter_by_leader_id( $clauses, $query_object ){
  // I don't know how you intend to pass the leader_id, so let's just assume it's a global
  global $leader_id;

  // In this example I only want to affect a query on the home page.
  // This is where the $query_object is used, to help us avoid affecting
  // ALL queries (since ALL queries pass through this filter)
  if ( $query_object->is_home() ){
    // Now, let's add your table into the SQL
    $join = &$clauses['join'];
    if (! empty( $join ) ) $join .= ' '; // add a space only if we have to (for bonus marks!)
    $join .= "JOIN {$wpdb->prefix}employee_relationship EMP_R ON EMP_R.follower_id = {$wpdb->posts}.author_id";

    // And make sure we add it to our selection criteria
    $where = &$clauses['where'];
    // Regardless, you always start with AND, because there's always a '1=1' statement as the first statement of the WHERE clause that's added in by WP/
    // Just don't forget the leading space!
    $where .= " AND EMP_R.leader_id={$leader_id}"; // assuming $leader_id is always (int)

    // And I assume you'll want the posts "grouped" by user id, so let's modify the groupby clause
    $groupby = &$clauses['groupby'];
    // We need to prepend, so...
    if (! empty( $groupby ) ) $groupby = ' ' . $groupby; // For the show-offs
    $groupby = "{$wpdb->posts}.post_author" . $groupby;
  }

  // Regardless, we need to return our clauses...
  return $clauses;
}
12
Tom Auger

Sie können dies mit einer vollständigen SQL-Lösung tun, indem Sie den Filter posts_where verwenden. Hier ist ein Beispiel dafür:

if( some condition ) 
    add_filter( 'posts_where', 'wpse50305_leader_where' );
    // lol, question id is the same forward and backward

function wpse50305_leader_where( $where ) {
    $where .= $GLOBALS['wpdb']->prepare( ' AND post_author '.
        'IN ( '.
            'SELECT leader_id '.
            'FROM custom_table_name '.
            'WHERE follower_id = %s'.
        ' ) ', $follower_id );
    return $where;
}

Ich denke, dass es einen Weg gibt, dies auch mit JOIN zu tun, aber ich kann nicht darauf kommen. Ich werde weiter damit spielen und die Antwort aktualisieren, wenn ich sie bekomme.

Alternativ können Sie es, wie von @kaiser vorgeschlagen, in zwei Teile aufteilen: die Leiter abrufen und die Abfrage ausführen. Ich habe das Gefühl, dass dies weniger effizient ist, aber es ist sicherlich der verständlichere Weg. Sie müssten die Effizienz selbst testen, um festzustellen, welche Methode besser ist, da verschachtelte SQL-Abfragen sehr langsam werden können.

AUS DEN KOMMENTAREN:

Sie sollten die Funktion in Ihren functions.php einfügen und die add_filter() ausführen, bevor die query()-Methode von WP_Query aufgerufen wird. Unmittelbar danach sollten Sie remove_filter() ausführen, damit die anderen Abfragen davon nicht betroffen sind.

8
mor7ifer

Vorlagen-Tag

Fügen Sie einfach beide Funktionen in Ihre functions.php -Datei ein. Passen Sie dann die 1. Funktion an und fügen Sie Ihren benutzerdefinierten Tabellennamen hinzu. Dann benötigen Sie einen Versuch/Fehler, um die aktuelle Benutzer-ID innerhalb des resultierenden Arrays zu entfernen (siehe Kommentar).

/**
 * Get "Leaders" of the current user
 * @param int $user_id The current users ID
 * @return array $query The leaders
 */
function wpse50305_get_leaders( $user_id )
{
    global $wpdb;

    return $wpdb->query( $wpdb->prepare(
        "
            SELECT `leader_id`, `follower_id`
            FROM %s
                WHERE `follower_id` = %s
            ORDERBY `leader_id` ASC
        ",
        // Edit the table name
        "{$wpdb->prefix}custom_table_name"
        $user_id
    ) );
}

/**
 * Get posts array that contain posts by 
 * "Leaders" the current user is following
 * @return array $posts Posts that are by the current "Leader
 */
function wpse50305_list_posts_by_leader()
{
    get_currentuserinfo();
    global $current_user;

    $user_id = $current_user->ID;

    $leaders = wpse5035_get_leaders( $user_id );
    // could be that you need to loop over the $leaders
    // and get rid of the follower ids

    return get_posts( array(
        'author' => implode( ",", $leaders )
    ) );
}

Innerhalb der Vorlage

Hier können Sie mit Ihren Ergebnissen machen, was Sie wollen.

foreach ( wpse50305_list_posts_by_leader() as $post )
{
    // do something with $post
}

NOTEWir haben keine Testdaten usw., daher ist das obige ein bisschen ein Ratespiel. Stellen Sie sicher, dass Sie diese Antwort mit dem, was für Sie funktioniert hat, bearbeitet, damit wir für spätere Leser ein zufriedenstellendes Ergebnis erzielen. Ich werde die Bearbeitung genehmigen, falls Sie zu wenig Wiederholungen haben. Sie können diese Notiz dann auch löschen. Vielen Dank.

5
kaiser

Hinweis: Diese Antwort hier soll eine ausführliche Diskussion in den Kommentaren vermeiden

  1. Hier ist der OPs-Code aus den Kommentaren, um die erste Gruppe von Testbenutzern hinzuzufügen. Ich muss zu einem realen Beispiel modifiziert werden.

    for ( $j = 2; $j <= 52; $j++ ) 
    {
        for ( $i = ($j + 1); $i <= 47000; $i++ )
        {
            $rows_affected = $wpdb->insert( $table_name, array( 'leader_id' => $i, 'follower_id' => $j ) );
        }
    }
    

    OP Über Test Dazu habe ich etwa 47K Benutzer aus einer CSV-Datei hinzugefügt. Später wurde eine for-Schleife ausgeführt, damit die ersten 45 Benutzer jedem anderen Benutzer folgen.

    • Dies führte dazu, dass 3.704.951 Datensätze in meiner benutzerdefinierten Tabelle gespeichert wurden.
    • Anfänglich gab mir die Lösung von @ m0r7if3r eine Abfragezeit von 95 Sekunden, die nach dem Einschalten der Indizierung für die Spalte leader_id auf 0,020 ms sank. Der gesamte PHP verbrauchte Speicher betrug ungefähr 20 MB.
    • Auf der anderen Seite hat Ihre Lösung bei eingeschalteter Indizierung ca. 2 bis 5 Sekunden für die Abfrage benötigt. Der gesamte PHP verbrauchte Speicher lag bei 117 MB.
  2. Meine Antwort auf diesen ↑ Test:

    ein "realer" Test: Lassen Sie jeden Benutzer einer $leader_amount = Rand( 0, 5 ); folgen und fügen Sie dann jedem Benutzer die Anzahl der $leader_amount x $random_ids = Rand( 0, 47000 ); hinzu. Bisher wissen wir: Meine Lösung wäre extrem schlecht, wenn ein Benutzer sich gegenseitig folgt. Weiter: Sie werden zeigen, wie Sie den Test durchgeführt haben und wo genau Sie die Timer hinzugefügt haben.

    Ich muss auch feststellen, dass die ↑ obige Zeiterfassung nicht wirklich gemessen werden kann, da es auch Zeit kosten würde, die Schleife zusammen zu berechnen. Besser wäre es, den resultierenden Satz von IDs in einer zweiten Schleife zu durchlaufen.

hier weiterverarbeiten

3
kaiser