web-dev-qa-db-fra.com

Requête personnalisée avec exclusion de catégorie et "liste blanche" post-méta

J'ai deux widgets, l'un montre Nouvelles tandis que l'autre montre tout sauf les Nouvelles (nous appellerons le second Blog ). La partie blog fonctionne actuellement correctement.

Les publications de News sont classées comme suit: In the News OR ont la publication personnalisée meta in-the-news, définie sur 1. Il est important de noter que toutes les publications n'auront pas la publication personnalisée définie.

Le principal problème auquel je suis confronté est que je dois comparer les méta de post en fonction de leur classement, d’une manière ou d’une autre.

Je pourrais avoir besoin de faire une requête de base de données personnalisée pour ce comportement, au moins pour les Nouvelles. Est-il possible de faire une requête personnalisée et de la convertir en un objet WP_Query standard?


Ce que j'ai essayé

Requête pour Nouvelles .

Problème: Affiche uniquement les messages de la catégorie 75 qui ont in-the-news = 1.

Résultat souhaité: Afficher tous les messages de la catégorie 75 quelle que soit la valeur de in-the-news, ainsi que tous les articles supplémentaires ne relevant pas de cette catégorie et ayant in-the-news = 1

query_posts(array(
  'ignore_sticky_posts' => true,
  'posts_per_page' => 3,
  'cat' => "75", // Only posts within category 75 (News)

  // Including posts tagged to show "In the News"
  'meta_query' => array(
    'relation' => 'OR',
    array(
      'key' => 'in-the-news',
      'value' => '1',
      'compare' => '=',
    ),
    array( 
      'key' => 'in-the-news',
      'compare' => 'NOT EXISTS',
      'value' => '',
    ),
  )
));

Voici la requête pour le Blog :

Cette requête semble fonctionner correctement, affichant tous les messages hors de la catégorie 75 et masquant les messages avec in-the-news = 1.

query_posts(array(
  'ignore_sticky_posts' => true,
  'posts_per_page' => 3,
  'cat' => "-75", // All posts EXCLUDING those in category 75 (News)

  'meta_query' => array(
    'relation' => 'OR',
    array(
      'key' => 'in-the-news',
      'value' => '0',
      'compare' => '=',
    ),
    array( 
      'key' => 'in-the-news',
      'compare' => 'NOT EXISTS',
      'value' => '',
    ),
  )
));

Solution

Merci à s_ha_dum pour les exemples étonnants, voici la dernière requête que j'utilise. La logique ici est que nous faisons une requête MySQL personnalisée pour un tableau d'ID de publication, puis une requête WP_Query standard en utilisant les ID retournés.

Note: La logique ici était un peu plus compliquée que le post original. Fondamentalement, nous avions à l'origine "News" et "Blog". Mais maintenant, nous avons aussi "Risque". De plus, la méta in-the-news posta été changée en show-in-blog car nous inversons la fonctionnalité. Je ne peux pas m'attendre à ce que quelqu'un d'autre en comprenne le raisonnement au départ, car même moi, je le trouve ridicule (Mais le client le veut, ainsi soit-il!)

Voici ma requête, pour le widget "Blog". De légères variations sont apportées pour la zone "Actualités". Je ne suis pas sûr si la requête pourrait être nettoyée. Je ne suis pas sûr non plus que ce soit une mauvaise idée d'exécuter autant de sélections dans la même requête.

global $wpdb;

$query = // Splitting the SQL query to it's own code block for syntax highlighting!

  SELECT `ID`
  FROM {$wpdb->posts} posts
  WHERE (

    /* Exclude posts in News (94) 
       Ignore show-in-blog option for News here, 
       as News is displayed on the same page */
    94 NOT IN ( 
      SELECT `term_taxonomy_id` 
      FROM {$wpdb->term_relationships}
      WHERE `object_id` = posts.`ID`
    )

    AND

    /* Exclude posts in Risk (96), 
       But show Risk posts that have the postmeta "show-in-blog" */
    (

      96 NOT IN ( 
        SELECT `term_taxonomy_id` 
        FROM {$wpdb->term_relationships}
        WHERE `object_id` = posts.`ID`
      )
      OR
      (
        96 IN ( 
          SELECT `term_taxonomy_id` 
          FROM {$wpdb->term_relationships}
          WHERE `object_id` = posts.`ID`
        )
        AND
        1 IN (
          SELECT `meta_value` 
          FROM {$wpdb->postmeta}
          WHERE 
          `meta_key` = 'show-in-blog'
          AND `post_id` = posts.`ID`
        )
      ) /* OR */

    ) /* AND */

  ) /* WHERE */

  ORDER BY `post_date` DESC;

$args = array(
  // Only include IDs returned by the above query
  'post__in' => $wpdb->get_col( $query ),
  'posts_per_page' => 10,
);

$wp_query = new WP_Query( $args );

get_template_part('loop', 'frontpage');
1
Radley Sustaire

Est-il possible de faire une requête personnalisée et de la convertir en un objet WP_Query standard?

Oui. Vous pouvez faire quelque chose comme ...

$post_ids = $wpdb->get_col("SELECT ID FROM {$wpdb->posts} WHERE ....");
$posts_qry = new WP_Query(array('post__in'=> $posts_ids,'orderby' => 'post__in'));

La deuxième requête préservera l'ordre des résultats de la première.

Je suis à peu près sûr que vous ne pouvez pas faire cette première requête avec WP_Query, ou toute autre fonction. La logique est trop compliquée. Vous auriez besoin d'une clause JOIN ou WHERE que WP_Query ne prend pas en charge. Malheureusement, écrire SQL sera également compliqué, juste pour obtenir la catégorie.

Complètement écrire à la main la requête est bien sûr votre première option. Le mieux que je puisse faire sans écrire à la main une SQL serait:

$post_ids = new WP_Query(array(
  'fields' => 'ids',
  'ignore_sticky_posts' => true,
  'posts_per_page' => 3,
  'cat' => "75", // Only posts within category 75 (News)
));

$post_ids_2 = new WP_Query(array(
  'fields' => 'ids',
  'ignore_sticky_posts' => true,
  'posts_per_page' => 3,
  'post__not_in' => $post_ids->posts,
  'meta_query' => array(
    array(
      'key' => '_edit_lock',
      'value' => '0',
      'compare' => '=',
    ),
  )
));
$post_ids = $post_ids->posts + $post_ids_2->posts;
$posts_qry = new WP_Query(array('post__in'=> $posts_ids,'orderby' => 'post__in'));

C'est 3 requêtes (ouch) et la commande peut être un problème si vous êtes inquiet à ce sujet.

Votre autre option est de modifier la requête via deux filtres. Les arguments WP_Query vont traiter de la partie in-the-news = 1.

$post_ids_v3 = new WP_Query(array(
  'ignore_sticky_posts' => true,
  'posts_per_page' => 20,
  'my_variable' => true,
  // Including posts tagged to show "In the News"
  'meta_query' => array(
    array(
      'key' => 'in-the-news',
      'value' => '1',
      'compare' => '=',
    )
  )
));

Notez que vous oubliez votre composant de catégorie et transmettez un paramètre non officiel supplémentaire appelé my_variable. Je ne sais pas si le fait de transmettre des variables supplémentaires comme celle-ci est inhérent au projet, alors sachez qu'il peut s'agir d'un comportement non officiel. Vous allez utiliser cela pour ajouter votre composant de catégorie dans la requête avec deux filtres.

function posts_join_wpse_98652($join,$qry) {
  global $wpdb;
  if (true === $qry->get('my_variable')) {
    $join .= " JOIN {$wpdb->term_relationships} ON ({$wpdb->posts}.ID = {$wpdb->term_relationships}.object_id)";
  }
  return $join;
}
add_filter('posts_join','posts_join_wpse_98652',1,2);

function posts_where_wpse_98652($where,$qry) {
  global $wpdb;
  if (true === $qry->get('my_variable')) {
    $where .= " OR {$wpdb->term_relationships}.term_taxonomy_id IN (75)";
  }
  return $where;
}
add_filter('posts_where','posts_where_wpse_98652',1,2);

Ce n'est même pas près de bien testé, mais cela semble fonctionner lorsque j'essaie. Pourtant, ces choses peuvent être difficiles à obtenir, peut-être que j'ai commis une erreur. À peine testé. Peut-être un buggy. Caveat emptor. Pas de remboursement.

2
s_ha_dum