web-dev-qa-db-fra.com

Tri sur la méta valeur mais incluez les posts qui n'en ont pas

J'ai modifié la recherche intégrée WP à l'aide du filtre pre_get_posts, ce qui permet à l'utilisateur de trier les articles (y compris un ensemble de types d'articles personnalisés) en fonction de différents champs.

Le problème que j'ai cependant, c'est que lorsque je dis à WP de trier par une méta-valeur, cela exclut toutes les publications qui n'ont pas cette méta-valeur définie. Cela entraîne le changement du nombre de résultats si vous modifiez le tri de "Prix" en "Date", car les "Messages" n'ont pas "Prix" défini mais "Articles".

Ce n'est pas ce que je veux, alors j'aimerais savoir s'il existe un moyen d'inclure TOUS les messages - même ceux qui n'ont pas la méta-valeur sur laquelle je fais le tri - et de placer ceux qui ne le sont pas.

Je sais comment trier sur plus d'un domaine, mais cela n'aide pas.

Merci

Il semble que je ne sois pas le seul à poser cette question: Manière d'inclure les publications avec et sans certaines méta_key dans les arguments de wp_query? mais il n'y a pas de solution là-bas.

Mise à jour

J'ai essayé la réponse mais je ne suis pas sûr d'avoir bien compris. Voici ce que j'ai maintenant:

<?php
function my_stuff ($qry) {
    $qry->set('meta_query', array(array(
        'key' => 'item_price', 
        'value' => '', 
        'compare' => 'NOT EXISTS'
    )));

    $qry->set('orderby', 'meta_value date'); # Sorting works with meta_value as well as meta_value_num - I've tried both
    $qry->set('order', 'ASC DESC');
    $qry->set('meta_key', 'item_price');
}

La méta valeur est un nombre (il est utilisé pour stocker un prix comme son nom l'indique)

Mise à jour 2

J'ai commenté les commandes et tout ce que j'ai à présent est le suivant:

<?php
$qry->set('meta_query', array(array(
    'key' => 'item_price', 
    'value' => '', 
    'compare' => 'NOT EXISTS'
)));

Avec ce code, la requête semble renvoyer toutes les publications qui ne possèdent pas la clé item_price et aucune des publications qui en disposent. C'EST À DIRE. le problème est maintenant inversé.

Si j'ajoute également le code de commande, j'obtiens 0 résultats.

Edit: ... trois ans plus tard ...: P J'ai eu ce problème à nouveau. J'ai essayé toutes les réponses données et aucune ne fonctionne. Je ne sais pas pourquoi certaines personnes semblent penser qu'elles travaillent mais elles ne travaillent pas pour moi du moins.

La solution qui m'a été retenue utilise le filtre save_post - en s'assurant que toutes les publications ont le champ personnalisé que je souhaite trier. C'est un peu agaçant que je sois obligé de le faire, mais si vous le faites tôt, vous n'aurez probablement aucun problème.

Dans ce cas, je construisais un "compteur de vues" sur les messages et je voulais que les utilisateurs puissent trier les messages les plus lus. Encore une fois, les messages qui n’ont jamais été visionnés (je suppose que c’est assez improbable - mais qui restent) ont disparu lors du tri sur le nombre de vues. J'ai ajouté ce morceau de code pour m'assurer que toutes les publications ont un nombre de vues:

add_action('save_post', function ($postId) {
    add_post_meta($postId, '_sleek_view_count', 0, true);
});
30
powerbuoy

Il y a deux solutions possibles à cela:

1. Tous les messages ont méta

La meilleure solution que j'ai trouvée ici consiste à attribuer à tous les articles/produits un prix de 0. Vous pouvez le faire manuellement ou parcourir tous les articles. Si le prix est vide, mettez-le à jour.

Pour rendre cela gérable à l'avenir, vous pouvez vous connecter à save_post et leur donner une valeur lors de leur première addition (uniquement si elle est vide).

2. Plusieurs requêtes

Vous pouvez exécuter la première requête comme vous le faites et stocker les identifiants des publications renvoyées. Vous pouvez ensuite exécuter une autre requête pour toutes les publications et chaque date de commande, à l'exclusion de les ID renvoyés depuis la première requête.

Vous pouvez ensuite imprimer séparément les deux résultats et vous obtiendrez les résultats souhaités.

5
Steven Jones

Cette méthode renverra tous les articles, y compris ceux avec et sans le meta_key demandé, mais fera des choses étranges lors de la commande.

add_action('pre_get_posts', 'my_stuff');
function my_stuff ($qry) {
    $qry->set(
        'meta_query',
        array(
            'relation' => 'OR', # Matches to this meta_query should be added to those matching the 'meta_key' query
            array(
                'key' => 'item_price', 
                'value' => 'bug #23268', 
                'compare' => 'NOT EXISTS'
            )
        )
    );

    $qry->set('orderby', 'meta_value date'); # Sorting works with meta_value as well as meta_value_num - I've tried both
    $qry->set('order', 'ASC DESC');
    $qry->set('meta_key', 'item_price');
}

J'ai trouvé cela en manipulant toutes les réponses différentes à cette question et en analysant le SQL généré par essais et erreurs. Il semble que définir array('meta_query' => array('relation' => 'OR')) génère un LEFT JOIN approprié au lieu de INNER JOIN nécessaire pour inclure les publications manquant les métadonnées. La spécification du NOT EXISTS empêche la clause WHERE de filtrer les publications dépourvues du champ méta. Pour ce WP_Query particulier, le code SQL généré est (indentation/nouvelles lignes ajoutées):

SELECT SQL_CALC_FOUND_ROWS
    wp_posts.ID
    FROM wp_posts
    INNER JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id)
    INNER JOIN wp_postmeta ON wp_posts.ID = wp_postmeta.post_id
    LEFT JOIN wp_postmeta AS mt1 ON (wp_posts.ID = mt1.post_id AND mt1.meta_key = 'item_price')
    WHERE 1=1
    AND ( wp_term_relationships.term_taxonomy_id IN (2) )
    AND wp_posts.post_type = 'post'
    AND (wp_posts.post_status = 'publish'
        OR wp_posts.post_status = 'private')
    AND (wp_postmeta.meta_key = 'item_price'
        -- Oh look, here we give SQL permission to choose a random
        -- row from wp_postmeta when this particular post is missing
        -- 'item_price':
        OR  mt1.post_id IS NULL )
    GROUP BY wp_posts.ID
    ORDER BY wp_postmeta.meta_value,wp_posts.post_date DESC
    LIMIT 0, 10

Le résultat est une liste de tous les messages avec meta_value de item_price et ceux manquants de item_price. Tous les posts avec item_price seront ordonnés correctement les uns par rapport aux autres, mais posts manquants item_price utilisera une valeur aléatoire other meta value (par exemple, _edit_last qui semble être 1 assez souvent dans ma base de données ou une autre métadonnée wordpress interne complètement arbitraire) pour son wp_postmeta.meta_value dans la clause ORDER BY. Ainsi, alors que cette méthode est proche et peut sembler fonctionner pour certaines données, elle est cassée. Donc, tout ce que je peux dire, c'est que si vos valeurs item_price n'entrent pas en conflit avec les champs de méta aléatoires choisis par MySQL pour les publications manquantes, item_price, cela pourrait fonctionner correctement pour vous. Si tout ce dont vous avez besoin est une garantie que vos publications avec item_price sont correctement ordonnées les unes par rapport aux autres sans tenir compte de la commande des autres publications, cela peut être correct. Mais je pense que ceci est juste une lacune dans Wordpress. S'il vous plaît, corrigez-moi, j'espère que je me trompe et qu'il existe un moyen de résoudre ce problème ;-).

Il semble que pour INNER JOIN wp_postmeta, MySQL choisisse une ligne aléatoire parmi plusieurs lignes postmeta associées à la publication lorsque la variable meta_key est absente de la publication donnée. Du point de vue SQL, nous devons savoir comment dire à wordpress de générer ORDER BY mt1.meta_value. Cette colonne est correctement NULL lorsque notre meta_key demandé est manquant, contrairement à wp_postmeta.meta_value. Si nous pouvions le faire, SQL trierait ces NULLentrées manquantes) avant toute autre valeur, nous donnant un ordre bien défini: tout d'abord, tous les posts manquant du champ postmeta particulier, deuxièmement, les posts ayant le champ. Mais c’est tout le problème: 'orderby' => 'meta_value' ne peut faire référence qu’à 'meta_key' => 'item_price'et le wp_postmeta sans alias est toujours un INNER JOIN au lieu de toujours un LEFT JOIN, ce qui signifie que wp_postmeta.meta_value et wp_postmeta.meta_key peuvent jamais être NULL.

Donc, je suppose que je dois dire que ce n’est pas possible avec le WP_Query intégré à wordpress tel qu’il est maintenant documenté (dans wordpress-3.9.1). Déranger. Donc, si vous avez réellement besoin que cela fonctionne correctement, vous devez probablement _ (avoir besoin de vous connecter à WordPress ailleurs et de modifier directement le code SQL généré .

8
binki

Easy Peasy, vient de tester 2018, en utilisant actuellement dans la production.

$query->set( 'meta_query', array(
    'relation' => 'OR',
    array(
        'key' => 'custom_meta_key', 
        'compare' => 'EXISTS'
    ),
    array(
        'key' => 'custom_meta_key', 
        'compare' => 'NOT EXISTS'
    )
) );
$query->set( 'orderby', 'meta_value title' ); 

Ceci vérifie tous les éléments avec et sans la clé méta, sans valeur spécifiée. la méta-requête fournit la clé de la commande de manière fiable. Il a été testé. Cependant, je ne sais pas comment cela fonctionnera lorsque la méta-requête utilise plusieurs clés.

Exemple pratique

/**
 * Modifies query before retrieving posts. Sets the 
 * `meta_query` and `orderby` param when no `orderby` 
 * param is set, (default ordering).
 * 
 * @param   WP_Query  $query  The full `WP_Query` object.
 * @return  void
 */
function example_post_ordering( $query ) {

    // if not in wp-admin, 
    // and the query is the main query, 
    // and the query is not a singular query, 
    // and the query does not have an orderby param set...
    // Note: check for post types, etc. here as desired.
    if ( ! is_admin() 
    && $query->is_main_query() 
    && ! $query->is_singular() 
    && empty( $query->get( 'orderby' ) ) ) {

        // Setting just `meta_key` is not sufficient, as this 
        // will ignore posts that do not yet, or never will have 
        // a value for the specified key. This meta query will 
        // register the `meta_key` for ordering, but will not 
        // ignore those posts without a value for this key.
        $query->set( 'meta_query', array(
            'relation' => 'OR',
            array(
                'key' => 'custom_meta_key', 
                'compare' => 'EXISTS'
            ),
            array(
                'key' => 'custom_meta_key', 
                'compare' => 'NOT EXISTS'
            )
        ) );

        // Order by the meta value, then by the title if multiple 
        // posts share the same value for the provided meta key.
        // Use `meta_value_num` if the meta values are numeric.
        $query->set( 'orderby', 'meta_value title' );
    }

}

add_action( 'pre_get_posts', 'example_post_ordering', 10 );

Ceci classera les publications par custom_meta_key par défaut et n'ignorera pas les publications sans valeur pour cette clé.

4
noahmason

Je pense avoir une solution.

Vous pouvez utiliser deux meta_keys, un dont toutes les publications ont (like "_thumbnail_id") et le meta_key que vous souhaitez utiliser comme filtre.

Donc vos arguments:

$qry->set(
    'meta_query',
    array(
        'relation' => 'OR',
        array(
            'key' => 'item_price', 
            'value' => '', 
            'compare' => 'EXISTS'
        ),
        array(
            'key' => 'item_price', 
            'value' => '', 
            'compare' => 'EXISTS'
        )
    )
);

$qry->set('orderby', 'meta_value date'); # Sorting works with meta_value as well as meta_value_num - I've tried both
$qry->set('order', 'ASC DESC');
$qry->set('meta_key', 'item_price');
2
Rafael M31

Si cela convient, vous pouvez ajouter une méta-valeur par défaut chaque fois qu'une publication est enregistrée ou mise à jour, si la méta-valeur n'existe pas.

function addDefaultMetaValue($post_id) {
    add_post_meta($post_id, 'item_price', 0, true);
}
add_action('save_post', 'addDefaultMetaValue');

Si vous utilisez un type de publication personnalisé, remplacez add_action('save_post', 'addDefaultMetaValue'); par add_action('save_post_{post_type}', 'addDefaultMetaValue'); p. Ex. add_action('save_post_product', 'addDefaultMetaValue');

1
rjpedrosa

J'ai eu le problème par moi-même pour les méta-valeurs numériques et a souligné que l'ordre de la requête est également important. Pour moi, la requête NOT EXISTS doit être la première.

Exemple:

$query->set( 'orderby', 'meta_value_num' );
$query->set( 'meta_query', [
    'relation' => 'OR',
    [ 'key' => 'your_meta_name', 'compare' => 'NOT EXISTS' ],
    [
        'key' => 'your_meta_name',
        'compare' => 'EXISTS',
    ],
] );

Il est également important, pour obtenir la bonne direction pour les valeurs numériques, que le ’orderby’ général soit défini sur ’meta_value_num’. Sinon, vous obtenez des résultats étranges pour les valeurs numériques, e. g.:

1, 2, 20, 21, 3, 4, 5…

Au lieu de:

1, 2, 3, 4, 5… 20, 21

0
KittMedia

Il existe une valeur orderby possible de meta_value pour cela.

$query = new WP_Query( array ( 
    'meta_key'   => 'your_keys_name',
    'orderby'    => 'meta_value',
    'order'      => 'DESC',
    'meta_query' => array( array(
         'key'     => 'your_meta_key',
         'value'   => '',
         'compare' => 'NOT EXISTS',
         // 'type'    => 'CHAR',
    ) )
) );

Si vous avez des valeurs numériques, utilisez plutôt meta_value_num à la place.

Avertissement: Ceci n'est pas testé, mais il devrait fonctionne. Le point est que vous devez spécifier vos valeurs meta_key et key. Sinon, vous ne pouvez pas comparer avec des valeurs inexistantes, ce qui devrait permettre d'interroger les deux types de publication. C'est une sorte de bidouille, mais tant que ça marche ...

0
kaiser

J'ai fini par contourner cela avec un peu de bidouille (IMHO), mais cela a fonctionné pour moi dans mon cas.

Vous pouvez accéder aux filtres posts_join_paged et posts_orderby pour mettre à jour les chaînes de jointure et de commande. Cela vous permettra de classer par tout ce que vous voulez, à condition que vous le rejoigniez d'abord plutôt que WP_Query en supposant que le champ doit exister pour ce poste particulier. Vous pouvez ensuite supprimer les meta_key, orderby et `order de vos arguments WP_Query.

Voici un exemple. Au sommet de chaque fonction, je devais m'échapper pour certains cas, car cela l'ajouterait à tout ce qui utilise WP_Query. Vous devrez peut-être modifier cela pour répondre à vos besoins particuliers.

La documentation sur ces deux filtres fait cruellement défaut alors ... bonne chance! :)

add_filter('posts_join_paged', 'edit_join', 999, 2);
add_filter('posts_orderby', 'edit_orderby', 999, 2);

/**
 * Edit join
 *
 * @param string $join_paged_statement
 * @param WP_Query $wp_query
 * @return string
 */
function edit_join($join_paged_statement, $wp_query)
{
    global $wpdb;
    if (
        !isset($wp_query->query)
        || $wp_query->is_page
        || $wp_query->is_admin
        || (isset($wp_query->query['post_type']) && $wp_query->query['post_type'] != 'my_custom_post_type')
    ) {
        return $join_paged_statement;
    }

    $join_to_add = "
        LEFT JOIN {$wpdb->prefix}postmeta AS my_custom_meta_key
            ON ({$wpdb->prefix}posts.ID = my_custom_meta_key.post_id
                AND my_custom_meta_key.meta_key = 'my_custom_meta_key')
    ";

    // Only add if it's not already in there
    if (strpos($join_paged_statement, $join_to_add) === false) {
        $join_paged_statement = $join_paged_statement . $join_to_add;
    }

    return $join_paged_statement;
}

/** 
 * Edit orderby
 *
 * @param string $orderby_statement
 * @param WP_Query $wp_query
 * @return string
 */
function edit_orderby($orderby_statement, $wp_query)
{
    if (
        !isset($wp_query->query)
        || $wp_query->is_page
        || $wp_query->is_admin
        || (isset($wp_query->query['post_type']) && $wp_query->query['post_type'] != 'my_custom_post_type')
    ) {
        return $orderby_statement;
    }

    $orderby_statement = "my_custom_meta_key.meta_value DESC";

    return $orderby_statement;
}
0
Scruffy Paws

J'ai également rencontré un problème similaire et la solution suivante m'a aidé:

$args = array(
'post_type' => 'kosh_products',
'posts_per_page' => -1,
'meta_query' => array(
    'relation' => 'OR',
    'category_sort_order' => array(
        'key' => '_sort_order',
        'compare' => 'EXISTS'
    ),
    'category_sort_order_not_exists' => array(
        'key' => '_sort_order',
        'compare' => 'NOT EXISTS'
    ), 
),
'orderby' => array( 
    'category_sort_order' => 'ASC',
    'date' => 'ASC'
));
$query = new WP_Query( $args );

J'ai trouvé une description sur WordPress Codex avec le titre "" orderby "avec plusieurs" meta_key's ": https://codex.wordpress.org/Class_Reference/WP_Query#Order_.26_Orderby_Parameters enter image description here 

0
Ilya Kogan

Je pense que ce que @kaiser essayait de faire était de dire à la requête de renvoyer tous les posts qui ont cette méta clé en appliquant une sorte de pseudo où condition à pas filtrer ces posts. Donc, si vous connaissez tout les valeurs que vos champs personnalisés peuvent prendre sont x, y, z, vous pouvez dire "WHERE meta_key IN (x, y, z) " mais l’idée est que vous pouvez éviter ce problème tous ensemble en disant! = ('') :

$query = new WP_Query( array ( 
    'orderby'    => 'meta_value_num',
    'order'      => 'DESC',
    'meta_query' => array( array(
         'key'     => 'item_price',
         'value'   => '',
         'compare' => '!=',
    ) )
) );

Également non testé, mais ça vaut le coup d'essayer :-).

0
Jon

Le problème rencontré par tout le monde ici est lié à l'ordre des méta-requêtes. Pour trier correctement, vous devez placer la requête "NOT EXISTS" before la requête "EXISTS".

La raison en est que WordPress utilise la meta_value de la dernière instruction "LEFT JOIN" dans la clause "ORDER BY".

Par exemple:

$pageQuery = new WP_Query([
    'meta_query' => [
        'relation' => 'OR',
        ['key' => 'item_price', 'compare' => 'NOT EXISTS'], // this comes first!
        ['key' => 'item_price', 'compare' => 'EXISTS'],
    ],
    'order' => 'DESC',
    'orderby' => 'meta_value_num',
    'post_status' => 'publish',
    'post_type' => 'page',
    'posts_per_page' => 10,
]);
0
Paul