Setze Alias ​​für meta_query Argumente in get_posts ()

8

Gibt es eine Möglichkeit, einen Alias ​​für die Argumente meta_query festzulegen, wenn a ausgeführt wird get_posts()? Eine meiner Fragen ist schlecht. Zur Optimierung muss ich nur in der Lage sein, dieselbe verknüpfte Tabelle wiederzuverwenden, anstatt 3 Tabellen zu verbinden, wenn nur eine benötigt wird.

Mein aktuelles Beispiel ...

$args = array(
    'meta_query' => array(
        'relation' => 'AND',
        array(
            'key' => 'abc_type',
            'value' => array('puppy', 'kitten'),
            'compare' => 'IN',
        ),
        array(
            'relation' => 'OR',
            array(
                'relation' => 'AND',
                array(
                    'key' => 'abc_type',
                    'value' => 'puppy',
                    'compare' => '=',
                ),
                array(
                    'key' => 'abc_color',
                    'value' => 'pink',
                    'compare' => '=',
                ),
            ),
            array(
                'relation' => 'AND',
                array(
                    'key' => 'abc_type',
                    'value' => 'kitten',
                    'compare' => '=',
                ),
                array(
                    'key' => 'abc_size',
                    'value' => 'large',
                    'compare' => '=',
                ),
            ),
        ),
    )
);
get_posts($args);

was sich im Grunde genommen in reinem SQL übersetzt ...

SELECT posts.* FROM posts
INNER JOIN postmeta ON ( posts.ID = postmeta.post_id )
INNER JOIN postmeta AS mt1 ON ( posts.ID = mt1.post_id )
INNER JOIN postmeta AS mt2 ON ( posts.ID = mt2.post_id )
INNER JOIN postmeta AS mt3 ON ( posts.ID = mt3.post_id )
WHERE 1=1
AND
( 
  ( postmeta.meta_key = 'abc_type' AND postmeta.meta_value IN ('puppy','kitten') ) 
  AND 
  ( 
    ( 
      ( mt1.meta_key = 'abc_type' AND mt1.meta_value = 'puppy' ) 
      AND 
      ( mt2.meta_key = 'abc_color' AND mt2.meta_value > 'pink' )
    ) 
    OR 
    ( 
      ( mt3.meta_key = 'abc_type' AND mt3.meta_value = 'kitten' )
      AND
      ( mt4.meta_key = 'abc_size' AND mt4.meta_value = 'large' )
    )
  )
) AND posts.post_type = 'abc_mypost' AND ((posts.post_status = 'publish'))
GROUP BY posts.ID ORDER BY posts.post_title ASC;

Dies bedeutet jedoch, dass 2 zusätzliche Joins für das benutzerdefinierte Metafeld hinzugefügt werden abc_typeund die Leistung daher einen großen Erfolg hat. Gibt es eine Möglichkeit, denselben Alias ​​für mehrere meta_query-Argumente zu referenzieren? Grundsätzlich mt1und mt3völlig unnötig, sollte ich nur in der Lage sein, auf die erste postmetaTabelle zu verweisen , die mit der ersten verwendet wird ( postmeta.meta_key = 'abc_type' AND postmeta.meta_value IN ('puppy','kitten') ). Oder zumindest, wenn ich für jeden einen benutzerdefinierten Alias ​​festlegen kann, könnte ich darauf verweisen.

Eine optimalere Abfrage wäre ...

SELECT posts.* FROM posts
INNER JOIN postmeta ON ( posts.ID = postmeta.post_id )
INNER JOIN postmeta AS mt1 ON ( posts.ID = mt1.post_id )
INNER JOIN postmeta AS mt2 ON ( posts.ID = mt2.post_id )
WHERE 1=1
AND
( 
  ( postmeta.meta_key = 'abc_type' AND postmeta.meta_value IN ('puppy','kitten') ) 
  AND 
  ( 
    ( 
      ( postmeta.meta_key = 'abc_type' AND postmeta.meta_value = 'puppy' ) 
      AND 
      ( mt1.meta_key = 'abc_color' AND mt1.meta_value > 'pink' )
    ) 
    OR 
    ( 
      ( postmeta.meta_key = 'abc_type' AND postmeta.meta_value = 'kitten' )
      AND
      ( mt2.meta_key = 'abc_color' AND mt2.meta_value = 'green' )
    )
  )
) AND posts.post_type = 'abc_mypost' AND ((posts.post_status = 'publish'))
GROUP BY posts.ID ORDER BY posts.post_title ASC;

Gedanken?

Abgenutzte Pfoten
quelle
Haben Sie eine Lösung gefunden oder ist dies noch ein offenes Problem?
Fuxia
Dies ist immer noch ein offenes Problem. Ich bin mir jedoch nicht sicher, ob dies im Moment möglich ist. Am Ende musste ich eine direkte MySQL-Abfrage verwenden, anstatt sie durchzugehen get_posts().
Scruffy Paws
1
Ich denke, das ist eine sehr interessante Frage. Deshalb habe ich es nur ein bisschen aufgepeppt. :)
Fuxia
Der posts_whereFilter könnte nützlich sein.
Nathan Johnson

Antworten:

4

Schauen Sie sich den in meta_query_find_compatible_table_aliasdefinierten Filter an wp-includes/class-wp-meta-query.php. Dokumentation dieses Filters:

/**
 * Filters the table alias identified as compatible with the current clause.
 *
 * @since 4.1.0
 *
 * @param string|bool $alias        Table alias, or false if none was found.
 * @param array       $clause       First-order query clause.
 * @param array       $parent_query Parent of $clause.
 * @param object      $this         WP_Meta_Query object.
 */
return apply_filters( 'meta_query_find_compatible_table_alias', $alias, $clause, $parent_query, $this );

Es ist wahrscheinlich, dass die aufrufende Funktion find_compatible_table_aliasfalse zurückgibt und die Abfrage daher die mt*Aliase erstellt. Hier ist ein Beispielcode, der diesen Filter verwendet, obwohl ich persönlich für etwas eintreten würde, das etwas einfacher zu verstehen ist. Das Ändern solcher Abfragen kann später zu Unmengen von Kopfschmerzen führen, und es ist möglicherweise überhaupt nicht ersichtlich, wo die Abfrage durcheinander gebracht wird, insbesondere wenn Sie in Zukunft andere Entwickler hinzuziehen. Das gesagt...

// Reuse the same alias for the abc_type meta key.
function pets_modify_meta_query( $alias, $meta_query ) {
    if ( 'abc_type' === $meta_query['key'] ) {
        return 'mt1';
    }

    return $alias;
}

// Filter the query.
add_filter( 'meta_query_find_compatible_table_alias', 'pets_modify_meta_query', 10, 2 );

$args = array(
    'meta_query' => array(
        'relation' => 'AND',
        array(
            'key' => 'abc_type',
            'value' => array('puppy', 'kitten'),
            'compare' => 'IN',
        ),
        array(
            'relation' => 'OR',
            array(
                'relation' => 'AND',
                array(
                    'key' => 'abc_type',
                    'value' => 'puppy',
                    'compare' => '=',
                ),
                array(
                    'key' => 'abc_color',
                    'value' => 'pink',
                    'compare' => '=',
                ),
            ),
            array(
                'relation' => 'AND',
                array(
                    'key' => 'abc_type',
                    'value' => 'kitten',
                    'compare' => '=',
                ),
                array(
                    'key' => 'abc_size',
                    'value' => 'large',
                    'compare' => '=',
                ),
            ),
        ),
    )
);

$q = new WP_Query($args);
echo '<pre>', print_r($q->request, true); die;

Dies führt zu einer Abfrage wie

SELECT SQL_CALC_FOUND_ROWS
    wp_posts.ID
FROM wp_posts
INNER JOIN wp_postmeta ON ( wp_posts.ID = wp_postmeta.post_id )
INNER JOIN wp_postmeta AS mt1 ON ( wp_posts.ID = mt1.post_id )
WHERE
    1=1
AND
(
    ( mt1.meta_key = 'abc_type' AND mt1.meta_value IN ('puppy','kitten') )
    AND
    (
        (
            ( mt1.meta_key = 'abc_type' AND mt1.meta_value = 'puppy' )
            AND
            ( wp_postmeta.meta_key = 'abc_color' AND wp_postmeta.meta_value = 'pink' )
        )
        OR
        (
            ( mt1.meta_key = 'abc_type' AND mt1.meta_value = 'kitten' )
            AND
            ( mt1.meta_key = 'abc_size' AND mt1.meta_value = 'large' )
        )
    )
)
AND
    wp_posts.post_type = 'post'
AND (
    wp_posts.post_status = 'publish'
    OR
    wp_posts.post_status = 'future'
    OR
    wp_posts.post_status = 'draft'
    OR wp_posts.post_status = 'pending'
)
GROUP BY wp_posts.ID ORDER BY wp_posts.post_date DESC LIMIT 0, 10
Phatskat
quelle
3

Mit den Filtern posts_whereund können Sie posts_joindie Abfrage ändern. Es ist nicht sehr elegant, aber Sie sollten in der Lage sein, mit diesen beiden Filtern zu spielen, damit Ihr SQL optimiert wird. Es ist ein bisschen brutal, aber ich kann keinen besseren Weg in der WP_Query-Klasse sehen. Das heißt aber nicht, dass es das nicht gibt.

//* Make sure to not suppress filters
$args = array(
  'suppress_filters' => false,
  //* rest of args unchanged
);

add_filter( 'posts_where', function( $sql ) {
  $sql = str_replace(
    "( mt1.meta_key = 'abc_type' AND mt1.meta_value = 'puppy' )",
    "( postmeta.meta_key = 'abc_type' AND postmeta.meta_value = 'puppy' )",
    $sql
  );

  $sql = str_replace(
    "( mt3.meta_key = 'abc_type' AND mt3.meta_value = 'kitten' )",
    "( postmeta.meta_key = 'abc_type' AND postmeta.meta_value = 'kitten' )",
    $sql
  );

  $sql = str_replace( [ 'mt2', 'mt4' ], [ 'mt1', 'mt2' ], $sql );
  return $sql;
});

add_filter( 'posts_join', function( $sql ) {
  $sql = str_replace(
    " INNER JOIN wp_postmeta AS mt4 ON ( wp_posts.ID = mt4.post_id )",
    "",
    $sql
  );
  $sql = str_replace(
    " INNER JOIN wp_postmeta AS mt3 ON ( wp_posts.ID = mt3.post_id )",
    "",
    $sql
  );
  return $sql;
});

Es sollte wahrscheinlich einige Überprüfungen geben, damit Sie nicht versehentlich andere Abfragen ändern. Das bleibt als Übung für den Leser.

Nathan Johnson
quelle
0

Sie können Ihre Abfrage optimieren, indem Sie die erste Metaabfrage entfernen, da sie redundant ist.

$args = array(
    'meta_query' => array(
        'relation' => 'OR',
        array(
            'relation' => 'AND',
            array(
                'key' => 'abc_type',
                'value' => 'puppy',
                'compare' => '=',
            ),
            array(
                'key' => 'abc_color',
                'value' => 'pink',
                'compare' => '=',
            ),
        ),
        array(
            'relation' => 'AND',
            array(
                'key' => 'abc_type',
                'value' => 'kitten',
                'compare' => '=',
            ),
            array(
                'key' => 'abc_size',
                'value' => 'large',
                'compare' => '=',
            ),
        ),
    ),
);
get_posts($args);

Auf diese Weise erhalten Sie nur entweder pink puppyoder large kitten, wie Sie beabsichtigen, glaube ich.

Ich bin der Meinung, dass Sie sich bei der Optimierung der internen MySQL-Abfragen von WordPress davon fernhalten sollten, da Sie sich möglichen Nebenwirkungen aussetzen würden. Sie sollten sich besser darauf verlassen, dass Abfragen zwischengespeichert werden und mehr PHP-Verarbeitung für den (größeren) Datensatz durchführen. Ich glaube, dies wird insgesamt zu einer besseren Leistung führen, da der Engpass nicht die Datenmenge ist, die Sie aus der Datenbank extrahieren, sondern die Schwierigkeit, mit der sie erfasst werden (wie viele Abfragen). PHP kommt ziemlich schnell durch Arrays.

Daher glaube ich, dass eine solche Situation schneller ist, wenn man bedenkt, dass das Post-Meta zwischengespeichert wird:

$args = array(
    'meta_query' => array( 
        array(
            'key' => 'abc_type',
            'value' => array('puppy', 'kitten'),
            'compare' => 'IN',
        ),
    ),
);

$final_posts = array();
foreach( $get_posts($args) as $post ) {
    if ( 'puppy' === get_post_meta( $post->ID, 'abc_type', true ) ) {
        if ( 'pink' === get_post_meta( $post->ID, 'abc_color', true ) ) {
            $final_posts[] = $post;
        }
    } else {
        // This is definitely a kitten
        if ( 'large' === get_post_meta( $post->ID, 'abc_size', true ) ) {
            $final_posts[] = $post;
        }
    }
}
Vlad Olaru
quelle
-2

Ich bin nicht wirklich ein Datenbank-Typ, aber ich habe einmal einen im Fernsehen gespielt ...

Würde das nicht Teil

SELECT posts.* FROM posts
INNER JOIN postmeta ON ( posts.ID = postmeta.post_id )
INNER JOIN postmeta AS mt1 ON ( posts.ID = mt1.post_id )
INNER JOIN postmeta AS mt2 ON ( posts.ID = mt2.post_id )

besser ersetzt werden durch

SELECT posts.* FROM posts
INNER JOIN postmeta ON (( posts.ID = postmeta.post_id ) and 
( posts.ID = mt1.post_id ) and
( posts.ID = mt2.post_id ))

Das könnte wahrscheinlich noch weiter vereinfacht werden. Mit einigen Alias, die an der richtigen Stelle gespeichert sind, können Sie den Rest Ihrer Abfrage verwenden.

Nur ein Gedanke...

Rick Hellewell
quelle
1
Sein Problem ist, dass er verwendet get_posts(), also schreibt er die Abfrage nicht selbst.
Jacob Peattie
okie dokie. Deshalb ist es 'nur ein Gedanke' und warum ich kein Datenbank-Typ bin.
Rick Hellewell
@ RickHellewell Ich bin mir nicht 100% sicher, wie dies die Frage beantwortet oder was es tut, obwohl es interessant ist. Hinterlassen Sie vielleicht hilfreiche Notizen als Kommentare, die auf das Wesentliche verweisen, um die Abstimmungen zu vermeiden?
Tom J Nowell
Nun, @TomJNowell, diese Antwort war vor 2 1/2 Jahren ... und vielleicht habe ich seitdem ein bisschen mehr gelernt und bin (hoffentlich) gelegentlich besser darin, Antworten zu liefern ...
Rick Hellewell