meta_query avec des méta-valeurs sous forme de tableaux sérialisés

36

Je travaille sur un projet dans lequel je crée un type d'article personnalisé et des données personnalisées entrées via des méta-boîtes associées à mon type d'article personnalisé. Pour une raison quelconque, j'ai décidé de coder les boîtes de méta de manière à ce que les entrées de chaque métabox fassent partie d'un tableau. Par exemple, je stocke la longitude et la latitude:

<p> 
    <label for="latitude">Latitude:</label><br /> 
    <input type="text" id="latitude" name="coordinates[latitude]" class="full-width" value="" /> 
</p> 
<p>     
    <label for="longitude">Longitude:</label><br /> 
    <input type="text" id="longitude" name="coordinates[longitude]" class="full-width" value="" /> 
</p>

Pour une raison quelconque, l’idée d’avoir une entrée postmeta singulière pour chaque métabox me plaisait. Sur le hook save_post , j'enregistre les données comme suit:

update_post_meta($post_id, '_coordinates', $_POST['coordinates']);

Je l’ai fait parce que j’ai trois métaboxes et j’aime bien avoir seulement 3 valeurs postmeta pour chaque post; Cependant, j'ai maintenant compris un problème potentiel avec cela. Je souhaiterais peut-être utiliser WP_Query pour extraire uniquement certains messages basés sur ces méta-valeurs. Par exemple, je souhaiterais peut-être obtenir tous les articles dont les valeurs de latitude sont supérieures à 50. Si j'avais ces données dans la base de données individuellement, en utilisant peut-être la clé latitude , je ferais quelque chose comme:

$args = array(
    'post_type' => 'my-post-type',
    'meta_query' => array(
        array(
            'key' => 'latitude',
            'value' => '50',
            'compare' => '>'
        )
    )
 );
$query = new WP_Query( $args );

Puisque j'ai la latitude dans le cadre de _coordinates postmeta, cela ne fonctionnerait pas.

Ma question est donc la suivante: existe-t-il un moyen d'utiliser meta_query pour interroger un tableau sérialisé, comme dans ce scénario?

    
posée tollmanz 09.05.2011 - 04:06

12 réponses

35

Non, ce n'est pas possible.

Je vous recommande strongment de ne pas sérialiser vos données et de modifier votre routine de sauvegarde. Une solution similaire devrait convertir vos données au nouveau format:

$args = array(
    'post_type' => 'my-post-type',
    'meta_key' => '_coordinates',
    'posts_per_page' => -1
 );
$query = new WP_Query( $args );
if($query->have_posts()){
    while($query->have_posts()){
        $query->the_post();
        $c = get_post_meta($post->id,'_coordinates',true);
        add_post_meta($post->ID,'_longitude',$c['longitude']);
        add_post_meta($post->ID,'_latitude',$c['latitude']);
        delete_post_meta($post->ID,'_coordinates',$c);
    }
}

Vous pourrez alors interroger à votre guise avec des clés individuelles

Si vous devez stocker plusieurs longitudes et plusieurs latitudes, vous pouvez stocker plusieurs méta-posts avec le même nom. Utilisez simplement le troisième paramètre de get_post_meta , et il les retournera tous sous forme de tableau

Pourquoi ne pouvez-vous pas interroger des données sérialisées?

MySQL le considère comme une simple chaîne et ne peut pas le diviser en données structurées. Le code ci-dessus le divise en données structurées

Vous pourrez peut-être rechercher des fragments partiels de date, mais ce sera super peu fiable, coûteux, lent et très fragile, avec de nombreux cas extrêmes. Les données sérialisées ne sont pas destinées aux requêtes SQL et ne sont pas formatées de manière régulière et constante.

Mis à part le coût des recherches de chaînes partielles, les méta-requêtes de publication sont lentes et les données sérialisées peuvent changer en fonction d'éléments tels que la longueur du contenu, rendant la recherche incroyablement coûteuse, voire impossible, en fonction de la valeur recherchée.

Note sur le stockage des enregistrements / entités / objets en tant qu’objets sérialisés en méta

Vous voudrez peut-être stocker un enregistrement de transaction dans le méta-article ou un autre type de structure de données dans le méta-utilisateur, puis rencontrez le problème ci-dessus.

La solution ici n’est pas de la diviser en méta de publication individuelle, mais de réaliser qu’elle n’aurait jamais dû être une méta, mais plutôt un type de publication personnalisé. Par exemple, un journal ou un enregistrement peut être un type de publication personnalisé, avec la publication d'origine en tant que parent ou joint via un terme de taxonomie

    
réponse donnée Tom J Nowell 20.08.2012 - 17:19
23

Je rencontre aussi cette situation. Voici ce que j'ai fait:

$args = array(
    'post_type' => 'my-post-type',
    'meta_query' => array(
        array(
            'key' => 'latitude',
            'value' => sprintf(':"%s";', $value),
            'compare' => 'LIKE'
        )
    )
);

J'espère que cette aide

    
réponse donnée rabni 16.04.2015 - 14:38
10

Vous allez vraiment perdre la possibilité d'interroger vos données de manière efficace lors de la sérialisation d'entrées dans la base de données WP.

L’économie de performance globale et le gain que vous pensez obtenir grâce à la sérialisation ne seront pas remarquables dans une large mesure. Vous obtiendrez peut-être une taille de base de données légèrement inférieure, mais le coût des transactions SQL sera lourd si vous interrogez ces champs et essayez de les comparer de manière utile et significative.

Au lieu de cela, enregistrez la sérialisation pour les données que vous n'avez pas l'intention d'interroger dans cette nature, mais que vous n'accéderiez que de manière passive à l'appel direct de l'API WP get_post_meta() . Cette fonction vous permet de décompresser une entrée sérialisée ses propriétés de tableau aussi.

En fait, la valeur true a été définie, comme dans

.

$meta = get_post_meta( $post->ID, 'key', true );

Renverra les données sous forme de tableau, que vous pourrez parcourir de nouveau, comme d'habitude.

Vous pouvez vous concentrer sur d’autres optimisations de base de données / site telles que la mise en cache, la minification CSS et JS et l’utilisation de tels services comme un CDN si vous le souhaitez. Pour n'en nommer que quelques-uns ... WordPress Codex est un bon point de départ pour en savoir plus sur ce sujet: ICI

    
réponse donnée userabuser 20.08.2012 - 16:36
3

Je viens de traiter des champs sérialisés et je peux les interroger. N'utilisez pas meta_query mais utilisez une requête SQL.

global $wpdb; 

$search = serialize('latitude').serialize(50);

$query = $wpdb->prepare("SELECT 'post_id'
FROM 'wp_postmeta'
WHERE 'post_id' IN (SELECT 'ID' FROM 'wp_posts' WHERE 'post_type' = 'my-post-type')
AND 'meta_key' = '_coordinates'
AND 'meta_value' LIKE '%s'",'%'.$search.'%');

$ids = $wpdb->get_col($query);

$args = array(
    'post__in' => $ids
    'post_type' => 'team' //add the type because the default will be 'post'
);

$posts = get_posts($args);

La requête recherche d’abord une publication avec le type de message correspondant afin que le nombre d’enregistrements wp_postmeta soit moins filtré. Ensuite, j'ai ajouté une instruction where pour réduire davantage les lignes en filtrant sur meta_key

Les identifiants se retrouvent dans un tableau, comme nécessaire pour get_posts.

PS. MySQL v5.6 ou supérieur est nécessaire pour obtenir de bonnes performances sous-requêtes

    
réponse donnée Tomas 22.08.2013 - 11:41
1

Cet exemple m'a vraiment aidé. C'est spécifiquement pour le plugin S2Members (qui sérialise les métadonnées de l'utilisateur). Mais cela vous permet d'interroger une partie d'un tableau sérialisé dans la méta_key.

Cela fonctionne en utilisant la fonction MySQL REGEXP.

Ici est la source

Voici le code qui interroge tous les utilisateurs résidant aux États-Unis. Je l'ai facilement modifié pour interroger l'un de mes champs d'enregistrement personnalisés et je l'ai fait fonctionner en un rien de temps.

  <?php
global $wpdb;
$users = $wpdb->get_results ("SELECT 'user_id' as 'ID' FROM '" . $wpdb->usermeta . 
          "' WHERE 'meta_key' = '" . $wpdb->prefix . "s2member_custom_fields' AND 
           'meta_value' REGEXP '.*\"country_code\";s:[0-9]+:\"US\".*'");
if (is_array ($users) && count ($users) > 0)
    {
        foreach ($users as $user)
            {
                $user = /* Get full User object now. */ new WP_User ($user->ID);
                print_r($user); /* Get a full list of properties when/if debugging. */
            }
    }
?>
    
réponse donnée BC Smith 12.12.2012 - 19:43
1

Je pense qu'il existe deux solutions pour tenter de résoudre le problème du stockage des résultats sous forme de chaîne et d'entiers. Cependant, il est important de noter, comme d'autres l'ont souligné, qu'il n'est pas possible de garantir l'intégrité des résultats stockés sous forme de nombre entier, car ces valeurs étant stockées sous forme de tableaux sérialisés, l'index et les valeurs sont stockés exactement avec le même modèle. Exemple:

array(37,87);

est stocké en tant que tableau sérialisé, comme ceci

a:2:{i:0;i:37;i:1;i:87;}

Notez que i:0 est la première position du tableau et i:37 est la première valeur. Le motif est le même. Mais passons aux solutions

1) Solution REGEXP

Cette solution fonctionne pour moi quelle que soit la méta-valeur enregistrée en tant que chaîne ou numéro / id. Cependant, il utilise REGEXP , ce qui est moins rapide que d'utiliser LIKE

$args = array(
    'post_type' => 'my-post-type',
    'meta_query' => array(
        array(
            'key' => 'latitude',
            'value' => '\;i\:' . $value . '\;|\"' . $value . '\";',
            'compare' => 'REGEXP'
        )
    )
);

2) Solution similaire

Je ne suis pas sûr de la différence de performances, mais c'est une solution qui utilise LIKE et qui fonctionne aussi bien pour les nombres que pour les chaînes

 $args = array(
        'post_type' => 'my-post-type',
        'meta_query' => array(
            'relation' => 'OR',
            array(
                'key' => 'latitude',
                'value' => sprintf(':"%s";', $value),
                'compare' => 'LIKE'
            ),
            array(
                'key' => 'latitude',
                'value' => sprintf(';i:%d;', $value),
                'compare' => 'LIKE'
            )
        )
    );
    
réponse donnée Pablo S G Pacheco 29.03.2018 - 21:07
0

Après avoir lu quelques astuces pour exécuter un filtrage WP_Query par des tableaux sérialisés, voici comment je l’ai finalement fait: en créant un tableau de valeurs séparées par des virgules à l’aide de implode conjointement à une requête SQL personnalisée $wpdb utilisant FIND_IN_SET pour rechercher la valeur demandée dans la liste séparée par des virgules.

(cela ressemble à la réponse de Tomas, mais c'est un peu moins gourmand en performances pour la requête SQL)

1. Dans functions.php:

Dans votre fichier functions.php (ou partout où vous configurez la méta-boîte) dans la fonction yourname_save_post() , utilisez

update_post_meta($post->ID, 'checkboxArray', implode(",", $checkboxArray)); //adding the implode

pour créer le tableau contenant des valeurs séparées par des virgules.

Vous voudrez également modifier votre variable de sortie dans la fonction de construction de la boîte de méta-boîte yourname_post_meta() admin en

.
$checkboxArray = explode(",", get_post_custom($post->ID)["checkboxArray"][0]); //adding the explode

2. Dans le modèle de fichier PHP:

Test: si vous exécutez un get_post_meta( $id ); , vous devriez voir checkboxArray comme un tableau contenant vos valeurs séparées par des virgules au lieu d’un tableau sérialisé.

Nous construisons maintenant notre requête SQL personnalisée à l'aide de $wpdb .

global $wpdb;

$search = $post->ID;

$query = "SELECT * FROM wp_posts
          WHERE FIND_IN_SET( $search, (
              SELECT wp_postmeta.meta_value FROM wp_postmeta
              WHERE wp_postmeta.meta_key = 'blogLocations'
              AND wp_postmeta.post_id = wp_posts.ID )
          )
          AND ( wp_posts.post_type = 'post' )
          AND ( wp_posts.post_status = 'publish' );";

$posts = $wpdb->get_results($query);

foreach ($posts as $post) {
    //your post content here
}

Notez le FIND_IN_SET , c’est là que la magie opère.

Maintenant ... étant donné que j'utilise SELECT * , cela retourne toutes les données de publication et vous pouvez faire écho à ce que vous voulez dans cette foreach (faites une print_r($posts); si vous ne savez pas ce qui est inclus. Il ne crée pas "la boucle" pour vous (je le préfère de cette façon), mais il peut facilement être modifié pour mettre en place la boucle si vous préférez (regardez setup_postdata($post); dans le codex, vous devrez probablement changer SELECT * pour ne sélectionner que les identifiants de publication et $wpdb->get_results du type $wpdb correct - consultez le codex de $wpdb également pour obtenir des informations sur celui sujet).

Whelp, il a fallu un peu d'effort, mais comme wp_query ne supporte pas les valeurs 'compare' => 'IN' sérialisées ou séparées par des virgules, cette cale est votre meilleure option!

J'espère que cela aide quelqu'un.

    
réponse donnée Gifford N. 22.01.2015 - 23:10
0

Si vous utilisez l'opérateur de comparaison like dans votre méta requête, le fonctionnement dans un tableau sérialisé devrait fonctionner correctement.

$wp_user_search = new WP_User_Query(array(
    'meta_query' => array(
        array(
            'key'     => 'wp_capabilities',
            'value'   => 'subscriber',
            'compare' => 'not like'
            )
        )
    )
);

résulte en:

[query_where] => WHERE 1=1 AND (
  ( wp_usermeta.meta_key = 'wp_capabilities' 
  AND CAST(wp_usermeta.meta_value AS CHAR) NOT LIKE '%subscriber%' )
    
réponse donnée benklocek 03.07.2015 - 23:14
0

Si mes métadonnées sont un type de tableau, j'utilise cette méthode pour les requêtes méta:

$args = array(
    'post_type' => 'fotobank',
    'posts_per_page' => -1,
    'meta_query' => array(
            array(
                   'key' => 'collections',
                   'value' => ':"'.$post->ID.'";',
                   'compare' => 'LIKE'
            )
     )
);
$fotos = new WP_Query($args);
    
réponse donnée Den Media 07.07.2016 - 08:50
0

Je suis curieux de connaître les réponses ci-dessus, où meta_query a ciblé la clé latitude au lieu de _coordinates . Nous devions vérifier si les méta-requêtes permettaient de cibler une clé spécifique dans un tableau sérialisé. :)

Ce n'était évidemment pas le cas.

Par conséquent, notez que la clé correcte à cibler est _coordinates au lieu de latitude .

$args = array(
     'post_type' => 'my-post-type',
     'meta_query' => array(
         array(
             'key' => '_coordinates',
             'value' => sprintf(':"%s";', $value),
             'compare' => 'LIKE'
         )
     )
 );

REMARQUES:

  1. Cette approche permet uniquement de cibler des correspondances exactes. Donc, des choses comme toutes les latitudes supérieures à 50 ne sont pas possibles.

  2. Pour inclure des correspondances de sous-chaîne, vous pouvez utiliser 'value' => sprintf(':"%%%s%%";', $value), . (n'ont pas été testés)

réponse donnée jgangso 06.07.2017 - 13:57
-1

J'ai la même question. Peut-être avez-vous besoin du paramètre 'type'? Découvrez cette question connexe: Requête de champ personnalisé - La méta-valeur est un tableau

Peut-être essayez-vous:

    $args = array(
    'post_type' => 'my-post-type',
    'meta_query' => array(
        array(
            'key' => 'latitude',
            'value' => '50',
            'compare' => '>',
            'type' => 'numeric'
        )
    )
    );
    
réponse donnée user4356 12.05.2011 - 19:44
-1

J'ai rencontré un problème similaire lors de l'utilisation du plugin Magic Fields. Cela pourrait faire l'affaire.

$values_serialized = serialize(array('50'));
$args = array(
    'post_type' => 'my-post-type',
    'meta_query' => array(
        array(
            'key' => 'latitude',
            'value' => $values_serialized,
            'compare' => '>'
        )
    )
);
    
réponse donnée Seth Stevenson 12.05.2011 - 23:10

Lire d'autres questions sur les étiquettes