Imposta alias per argomenti meta_query in get_posts ()


8

C'è un modo per impostare un alias sugli argomenti meta_query quando si esegue un get_posts()? Una delle mie domande sta andando male. Per ottimizzare ho solo bisogno di essere in grado di riutilizzare la stessa tabella unita invece di unire in 3 tabelle quando ne è necessaria solo una.

Il mio esempio attuale ...

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

che in pratica si traduce in questo in SQL diretto ...

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;

Tuttavia, questo sta aggiungendo 2 join extra per il metacampo personalizzato abc_typee come tale le prestazioni hanno avuto un grande successo. C'è un modo per poter fare riferimento allo stesso alias per più argomenti meta_query? Fondamentalmente, mt1e mt3sono totalmente inutili, dovrei essere in grado di fare riferimento alla prima postmetatabella utilizzata con la prima ( postmeta.meta_key = 'abc_type' AND postmeta.meta_value IN ('puppy','kitten') ). O almeno se posso impostare un alias personalizzato su ciascuno di questi, potrei fare riferimento a quello.

Una query più ottimale sarebbe ...

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;

Pensieri?


Hai trovato una soluzione o è ancora un problema aperto?
fuxia

Questo è ancora un problema aperto. Non sono sicuro che al momento sia possibile. Ho finito per usare una semplice query MySQL invece di passare attraverso get_posts().
Scruffy Paws,

1
Penso che sia una domanda molto interessante. Quindi l'ho solo aromatizzato un po '. :)
fuxia

Il posts_wherefiltro potrebbe essere utile.
Nathan Johnson,

Risposte:


4

Dai un'occhiata al meta_query_find_compatible_table_aliasfiltro definito in wp-includes/class-wp-meta-query.php. La documentazione di questo filtro:

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

È probabile che la funzione chiamante find_compatible_table_aliasstia restituendo false e quindi la query crea gli mt*alias. Ecco un po 'di codice di esempio che utilizza questo filtro, anche se vorrei personalmente sostenere qualcosa che è un po' più facile da capire. La modifica di query come questa può portare a tonnellate di mal di testa lungo la strada e potrebbe non essere affatto evidente dove la query viene incasinata, specialmente se in futuro porti altri sviluppatori. Detto ciò...

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

Ciò si traduce in una query simile

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

3

È possibile utilizzare i filtri posts_wheree posts_joinper modificare la query. Non è molto elegante, ma dovresti essere in grado di pasticciare con questi due filtri in modo che il tuo sql sia più ottimizzato. È un po 'brutale, ma non riesco a vedere un modo migliore nella classe WP_Query. Ciò non significa che non ci sia.

//* 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;
});

Probabilmente dovrebbero esserci dei controlli in modo da non modificare accidentalmente altre query. Questo è lasciato come esercizio per il lettore.


0

È possibile ottimizzare la query rimuovendo la prima meta query in quanto è ridondante, in questo modo:

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

In questo modo otterrai solo uno pink puppyo large kitten, come intendi, credo.

Per quanto riguarda l'ottimizzazione delle query MySQL interne di WordPress, credo che dovresti evitare ciò poiché ti esporresti a possibili effetti collaterali. Faresti meglio a fare affidamento sul fatto che le query sono memorizzate nella cache e eseguono un po 'più di elaborazione PHP sul set di dati (più grande). Credo che questo porterà a migliori prestazioni complessive poiché il collo di bottiglia non è la quantità di dati che si stanno estraendo dal database, ma la difficoltà con cui vengono raccolti (quante query). PHP è abbastanza veloce poiché passa attraverso le matrici.

Quindi credo che una situazione come questa sia più veloce, considerando che la meta post viene memorizzata nella cache:

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

-2

Non sono davvero un tipo di database, ma ne ho giocato uno in TV una volta ...

Non sarebbe questa parte

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 )

essere meglio sostituito con

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

Ciò potrebbe probabilmente essere semplificato ancora di più..con alcuni alias bloccati lì nel posto giusto in modo da poter utilizzare il resto della query.

Solo un pensiero...


1
Il suo problema è che sta usando get_posts(), quindi non sta scrivendo la query da solo.
Jacob Peattie,

D'accordo. Ecco perché è "solo un pensiero" e perché non sono un tipo di database.
Rick Hellewell,

@RickHellewell Non sono sicuro al 100% di come questo risponda alla domanda o di cosa faccia, sebbene sia interessante. Forse lasciare note utili come commenti che si collegano a sintesi per evitare i voti negativi?
Tom J Nowell

Bene, @TomJNowell, quella risposta è stata 2 anni e mezzo fa .... e forse ho imparato un po 'di più da allora, e sono (si spera) occasionalmente migliore nel fornire risposte ....
Rick Hellewell
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.