web-dev-qa-db-fra.com

Query Multiple Post chacun avec sa propre méta requête

J'ai 2 types de post: disons products et product_variations. Je voudrais pouvoir modifier la requête via pre_get_posts pour obtenir tous les produits avec meta_keys "_visible" = "true" et "_available" = "true" ET toutes les variantes de produit avec meta_key "_featured" = "true".

Les variables_produit n’ayant pas méta_keys "_visible"/"_available" et vice-versa, une méta_query AND directe ne renvoie aucun résultat. La relation OR n’est pas tout à fait correcte, car deux champs sont liés à AND pour un type de publication, puis OR à l’autre type de publication.

Pour rendre cela légèrement plus visuel parce que je viens de réaliser combien il est complexe d’écrire:

**product**        
_visible = true      
_available = true

**product_variation**      
_featured = true

Est-ce possible via une requête args?
Est-ce possible via posts_where?
Dois-je exécuter une requête distincte et ensuite fusionner les résultats?

4
helgatheviking

Est-ce possible via une requête args?

Je ne pense pas.

Est-ce possible via 'posts_where'?

Il est probablement faisable d’utiliser des filtres some 'posts_*', pas seulement avec 'posts_where'. Ou peut-être en utilisant 'posts_request' filter pour complètement survoler la requête.

Dois-je exécuter une requête distincte et ensuite fusionner les résultats?

Ce serait le choix le plus simple, le plus facile à personnaliser, par exemple, une limite pour les produits et une limite différente pour les variantes de produit.

De plus, cette approche n'est pas forcément la pire en termes de performances, car deux requêtes ne sont pas toujours plus lentes qu'une seule si cette dernière est très complexe.

La 4ème alternative serait de construire un SQL complètement personnalisé. Une requête (vaguement testée) qui pourrait faire l'affaire est la suivante:

$products_and_variations = $wpdb->get_results("
  SELECT * FROM (

    SELECT products.* FROM {$wpdb->posts} products
      LEFT JOIN {$wpdb->postmeta} meta1 ON meta1.post_id = products.ID
      LEFT JOIN {$wpdb->postmeta} meta2 ON meta2.post_id = products.ID
      WHERE products.post_type = 'product'
      AND products.post_status = 'publish'
      AND (meta1.meta_key = '_visible' AND meta1.meta_value = '1')
      AND (meta2.meta_key = '_available' AND meta2.meta_value = '1')
      GROUP BY products.ID

    UNION

    SELECT variations.* FROM {$wpdb->posts} variations
      LEFT JOIN {$wpdb->postmeta} meta3 ON meta3.post_id = variations.ID
      WHERE variations.post_type = 'product_variation'
      AND variations.post_status = 'publish'
      AND (meta3.meta_key = '_featured' AND meta3.meta_value = '1')
      GROUP BY variations.ID
  )
  posts
    ORDER BY post_date DESC
    LIMIT 0, 100
");

Vous pouvez remarquer:

  • comme c'est complexe
  • comment ne pas utiliser une limite pour les produits et une limite différente pour les variations: il est seulement possible de limiter les merged results.

Avant d'utiliser quelque chose comme ça, je testais ses performances en les comparant avec l'approche à 2 requêtes et n'utilisais la requête personnalisée que s'il y avait une amélioration appréciable des performances.

4
gmazzap

Voici une autre approche avec pre_get_posts.

  • Utilisez get_posts() (x2 requêtes) pour obtenir all les identifiants de publication de chaque type de publication en fonction de ses besoins. Ajoutez le paramètre fields ('fields' => 'ids') à vos arguments de requête pour récupérer uniquement les identifiants de publication, car c’est ce dont nous avons besoin, rien d’autre. Cela améliorera considérablement les performances et ne sera pas aussi dur en ressources. Donc, vous allez vous retrouver avec deux tableaux d'identifiants de post

  • Vous devez maintenant fusionner les deux tableaux avec array_merge() pour créer un "super" tableau qui contiendra tous les identifiants de publication.

  • Vous voudrez peut-être envisager de sauvegarder ceci dans un transitoire et de le vider lorsqu'un message est mis à jour, mis à la corbeille, vierge ou publié. Cela garantira que les performances de votre site ne sont pas comprises.

  • Maintenant que vous avez un tableau d'ID correspondant à vos besoins, vous pouvez simplement passer ce tableau d'ID de publication à post__in dans pre_get_posts

Exemple ( et non testé car il ne s'agit que d'une illustration. Malheureusement, je n'ai pas le temps de bien coder )

function get_all_special_posts()
{
     /*
     * This is here were you would want to introduce your transient
     */
    $args1 = [
        'post_type' => 'post_type_1',
        'fields' => 'ids',
        'nopaging' => true,
        'meta_query' => [[ /*Your specific custom fields */ ]]
    ];
    $query1 = get_posts( $args1 );

    $args2 = [
        'post_type' => 'post_type_2',
        'fields' => 'ids',
        'nopaging' => true,
        'meta_query' => [[ /*Your specific custom fields */ ]]
    ];
    $query2 = get_posts( $args2 );

    $post_ids = array_merge( $query1, $query2 );

    /*
     * Set your transient here, you would want to store $post_ids in the transient
     */

    return $post_ids;
}

N'oubliez pas de créer une fonction pour vider votre transitoire sur la publication, la corbeille, la suppression et la mise à jour. Regardez transition_post_status pour cela. Crochet très polyvalent. Utilisez $post->post_type pour cibler uniquement vos deux types de publication souhaités.

add_action( 'pre_get_posts', function ( $q )
{
    if ( !is_admin() && $q->is_main_query() && $q->is_home() ) {
        $q->set( 'post__in', get_all_special_posts() );
    }
});

Juste pour noter, je voudrais d’abord vérifier si get_all_special_posts() possède réellement un tableau valide et qu’il n’est pas vide avant de le passer à post__in

4
Pieter Goosen