meta_query avec les méta-valeurs comme sérialiser les tableaux

37

Je travaille sur un projet dans lequel je crée un type d'article personnalisé et des données personnalisées saisies 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 save_postcrochet, je sauvegarde 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 que j’ai envie d’avoir 3 valeurs postmeta pour chaque poste; Cependant, j'ai maintenant compris un problème potentiel avec cela. Je souhaiterai peut-être utiliser WP_Query pour extraire uniquement certains messages basés sur ces méta-valeurs. Par exemple, je souhaiterai peut-être obtenir tous les articles dont la latitude est supérieure à 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 _coordinatespostmeta, cela ne fonctionnerait pas.

Ma question est donc la suivante: existe-t-il un moyen meta_queryd’interroger un tableau sérialisé comme dans ce scénario?

tollmanz
la source

Réponses:

37

Non, ce n'est pas possible et pourrait même être dangereux.

Je vous recommande fortement de ne pas sérialiser vos données et de modifier votre routine de sauvegarde. Quelque chose de semblable à ceci 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 très 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.

Outre les coûts des recherches partielles sur les chaînes, les méta-requêtes post sont lentes et les données sérialisées peuvent changer en fonction de facteurs 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 d'enregistrements / entités / objets en tant qu'objets sérialisés dans Meta

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

La solution ici n'est pas de la diviser en méta de post individuel, mais de réaliser que cela n'aurait jamais dû être une méta, mais plutôt un type de post 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.

Objets sécurisés et sérialisés

Stocker des objets PHP sérialisés via la serializefonction peut être dangereux , ce qui est regrettable car le fait de passer un objet à WordPress signifie qu'il sera sérialisé. En effet, lorsque l'objet est désérialisé, il est créé et toutes ses méthodes de réveil et ses constructeurs sont exécutés. Cela peut ne pas sembler un grave problème tant qu'un utilisateur ne parvient pas à extraire une entrée soigneusement conçue, ce qui conduit à l'exécution de code à distance lorsque les données sont lues dans la base de données et désérialisées par WordPress.

Cela peut être évité en utilisant plutôt JSON, ce qui facilite également les requêtes, mais il est beaucoup plus facile / rapide de simplement stocker les données correctement et d'éviter les données sérialisées structurées.

Tom J Nowell
la source
5
Pour les passants, n'arrêtez pas de lire: des réponses plus utiles (et récentes) se trouvent ci
Erenor Paz
Que se passe-t-il si j'ai un tableau d'identifiants à sauvegarder - et qu'ils ne représentent pas chacun une clé différente sous laquelle je pourrais les sauvegarder, tels que "latitude", etc., ce n'est qu'une clé pour tous (comme lors de la sauvegarde de relations, etc.). Que faire alors? La solution de @ rabni?
trainoasis
1
Vous pouvez stocker une clé plusieurs fois. Les paires de valeurs clé ne sont pas uniques. En ce qui concerne les relations, c'est à quoi servent les taxonomies. Si vous utilisez la méta pour associer plusieurs éléments à quelque chose, mettez-les à la place dans un terme de taxonomie
Tom J Nowell
24

Je cours aussi dans 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

rabni
la source
1
J'ai vraiment aimé cette solution. Malheureusement, ceci n'est pas applicable lorsque le $valueest aussi un identifiant. Dans ce cas, je suggère de créer des fonctions pour ajouter un caractère à chaque élément du tableau avant d'enregistrer les données et une autre fonction pour supprimer le caractère avant d'utiliser les données. Ce wat, l' i:2index sérialisé ne sera pas confondu avec le i:D2des "vraies" données. Le paramètre de méta-requête devrait alors devenir 'value' => sprintf(':"D%s";', $value),et vous garderez la fonctionnalité correcte de cette réponse merveilleuse!
Erenor Paz le
Cette solution fonctionne pour moi
Vishal le
Cela a également fonctionné parfaitement pour moi. J'ai eu une mini panique quand j'ai vu la solution acceptée cependant
Shane Jones
@Erenor Paz, je viens de mettre en ligne une solution qui fonctionne bien avec ID et Strings: wordpress.stackexchange.com/a/299325/25264
Pablo SG Pacheco
utiliser LIKEest un moyen rapide et efficace de mettre votre serveur hors service (sans parler des faux positifs), il vaut mieux avoir une très bonne mise en cache.
Mark Kaplun
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.

Les gains de performances globaux et les gains que vous pensez avoir obtenus 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.

Enregistrez plutôt 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 par l'appel direct de l'API WP get_post_meta()- à partir de cette fonction, vous pouvez décompresser une entrée sérialisée pour accéder également à ses propriétés de tableau.

En fait assigné la valeur de true comme dans;

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

Renverra les données sous forme de tableau, accessible pour une itération normale.

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 services tels qu'un CDN, si vous en avez besoin. Pour n'en nommer que quelques-uns .... WordPress Codex est un bon point de départ pour en découvrir plus sur ce sujet: ICI

Adam
la source
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 commence par rechercher post avec le type post_type correspondant, de sorte que le nombre d'enregistrements wp_postmeta sera moins filtré. Ensuite, j'ai ajouté une instruction where pour réduire davantage les lignes en filtrant surmeta_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 en sous-requête

Tomas
la source
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.

Voici 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. */
            }
    }
?>
BC Smith
la source
1

Je pense qu'il y a deux solutions qui peuvent essayer de résoudre le problème des résultats stockés à la fois 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é comme un tableau sérialisé, comme ceci

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

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


1) Solution REGEXP

Cette solution fonctionne pour moi indépendamment de la méta-valeur enregistrée en tant que chaîne ou numéro / id. Cependant, il utilise REGEXP, ce qui n'est pas si rapide que d'utiliserLIKE

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

2) comme solution

Je ne suis pas sûr de la différence de performances, mais cette solution utilise LIKEet 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'
            )
        )
    );
Pablo SG Pacheco
la source
REGEXPest bien dans certaines situations, mais si vous pouvez utiliser LIKE, je pense que c'est la méthode préférable. Un vieux lien, mais toujours utile, à mon avis: thingsilearn.wordpress.com/2008/02/28/... :-)
Erenor Paz
@ErenorPaz Vous avez raison. LIKEest plus rapide. Mais c'est une solution qui fonctionne à la fois pour les chaînes et pour les nombres
Pablo SG Pacheco
Oui, la réponse est (comme toujours): en fonction de la situation, si vous pouvez utiliser "LIKE"; c'est préférable, sinon REGEXP fera aussi bien :-)
Erenor Paz
@ErenorPaz, j'ai modifié ma réponse en ajoutant une nouvelle solution qui utilise LIKEmais fonctionne à la fois pour les nombres et les chaînes. Je ne suis pas sûr de la performance car il doit comparer les résultats avecOR
Pablo SG Pacheco
Exactement !!! dont j'ai besoin pour obtenir le résultat même comme ça .... Merci Man !!!
kuldip Makadiya
0

Après avoir lu quelques astuces pour exécuter un WP_Queryfiltrage 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 en utilisant implode conjointement avec une $wpdbrequête SQL personnalisée utilisant la FIND_IN_SETrecherche de 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 exigeant 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) yourname_save_post(), utilisez la fonction

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 changer votre variable de sortie dans la yourname_post_meta()fonction de construction de la boîte méta d’administrateur:

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

2. Dans le fichier de modèle PHP:

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

Maintenant, nous construisons notre requête SQL personnalisée en utilisant $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
}

Remarquez le FIND_IN_SET, c'est là que la magie se produit.

Maintenant ... depuis que je suis en utilisant SELECT *ceci retourne toutes les données de poste et dans le foreachvous pouvez faire écho à ce que vous voulez que (faire un print_r($posts);si vous ne savez pas ce qui est inclus. Il ne définit pas « la boucle » pour vous (je le préfère de cette façon), mais il peut être facilement modifié pour mettre en place la boucle si vous préférez (regardez setup_postdata($post);dans le codex, vous aurez probablement besoin de changer SELECT *pour sélectionner uniquement les identifiants de post et $wpdb->get_resultsle $wpdbtype correct - - voir le codex pour $wpdbégalement des informations à ce sujet).

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

J'espère que ça aide quelqu'un.

Gifford N.
la source
0

Si vous utilisez l' likeopérateur de comparaison dans votre méta-requête, la recherche 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%' )
Benklocek
la source
0

Si mes métadonnées sont de type 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);
Den Media
la source
Cela pourrait entraîner des résultats indésirables lorsqu'un identifiant de publication a la même valeur que l'identifiant de la chaîne sérialisée
Erenor Paz
0

Je suis curieux de connaître les réponses ci-dessus, où le meta_queryciblé la clé latitudeau 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.

Donc, notez que la clé correcte à cibler est _coordinatesau 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. Des choses comme toutes les latitudes supérieures à 50 ne sont pas possibles.

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

jgangso
la source
-1

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

Peut-être essayer:

    $ args = array (
    'post_type' => 'mon-post-type',
    'meta_query' => array (
        tableau (
            'clé' => 'latitude',
            'valeur' ​​=> '50',
            'compare' => '>',
            'type' => 'numérique'
        )
    )
    )
utilisateur4356
la source
Merci pour la suggestion, mais ce n'est pas tout à fait ce que je recherche. Le problème est que la valeur que je tente de faire correspondre fait partie d'un tableau sérialisé dans la base de données.
tollmanz
Oui, vous avez raison. J'ai essayé cela ce matin et cela n'a pas fonctionné pour moi non plus. J'ai le même problème. Stocker une valeur d'une méta clé en tant que tableau. Je commence à penser que cela ne peut pas être fait et que je devrais plutôt les stocker dans des méta-champs séparés portant le même nom ... et gérer simplement leur suppression / mise à jour correctement.
user4356
@ user4356 ... c'est exactement ce que je vais faire. J'espérais réduire le nombre de lignes que j'insérerais pour chaque poste, mais je suppose que ce n'est pas possible.
tollmanz
-1

J'ai rencontré quelque chose de similaire en utilisant le 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' => '>'
        )
    )
);
Seth Stevenson
la source
1
Merci pour la suggestion! Je pense que c'est aussi proche que possible, mais cela ne marchera pas, car comparer un tableau sérialisé à un autre ne serait logique que si je cherchais une correspondance exacte.
Tollmanz
5
Dans ce cas, cela ne devrait pas être indiqué comme la bonne réponse et il est irresponsable de votre part de le faire. La bonne réponse serait donc «non, ce n'est pas possible»
Tom J Nowell
1
D'accord, aussi WP gère la sérialisation pour vous, serialize()n'est pas nécessaire dans ce cas ...
Adam
2
En fait, @ seth-stevenson répond est très bien quand il fait exactement ce qu'il a dit, en utilisant le plugin "Magic Fields". Comme ce plugin sérialise certains types de données par défaut, c'est le meilleur moyen de faire une correspondance EXACT.
Zmonteca
@TomJNowell Done!
Cela