web-dev-qa-db-fra.com

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

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 hook save_post, 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 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 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 );

Comme j'ai la latitude dans le _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?

36
tollmanz

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 super peu fiable, coûteux, lent et très fragile, avec beaucoup de cas Edge. 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 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 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 fonction serialize 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 entraîne l'exécution de code à distance lorsque les données sont lues à partir de 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.

36
Tom J Nowell

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

23
rabni

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

L’économie de performances globale et le gain que vous pensez obtenir grâce à la sérialisation ne seront pas remarquables dans une mesure majeure. 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 ne souhaitez pas interroger dans cette nature, mais n’y accéderiez que de manière passive par l’appel direct WP API get_post_meta() - à partir de cette fonction, vous pouvez décompresser une entrée sérialisée. accéder à ses propriétés de tableau aussi.

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

10
userabuser

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 la publication avec le post_type 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 en sous-requête

3
Tomas

Je pense qu'il existe deux solutions qui peuvent essayer de résoudre le problème des résultats stockés à la fois sous forme de chaîne et sous forme d'entiers. Cependant, il est important de noter, comme d'autres l'ont fait remarquer, 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:0 comme première position du tableau et i:37 comme 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'utiliser LIKE

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

2) LIKE Solution

Je ne suis pas sûr de la différence de performances, mais cette solution utilise LIKE et fonctionne aussi bien en nombre qu'en 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'
            )
        )
    );
1
Pablo S G Pacheco

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

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 en utilisant implode conjointement avec une requête SQL personnalisée $wpdb utilisant FIND_IN_SET pour rechercher les éléments séparés par des virgules. valeur demandée.

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

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

2. Dans le fichier modèle 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 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 opère.

Maintenant ... depuis que j'utilise SELECT * ceci retourne toutes les données postales et dans la foreach vous pouvez faire écho à ce que vous voulez à partir de cela (faites un print_r($posts); si vous ne savez pas ce qui est inclus. 'configurez 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 (jetez un oeil à setup_postdata($post); dans le codex, vous devrez probablement changer SELECT * pour sélectionner uniquement les identifiants de publication et $wpdb->get_results dans le type correct $wpdb - voir le codex pour $wpdb également pour des informations sur that subject).

Cela a demandé un peu d’effort, mais comme wp_query ne supporte pas de faire des valeurs 'compare' => 'IN' sérialisées ou séparées par des virgules, cette cale est votre meilleure option!

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

0
Gifford N.

Je suis curieux des réponses ci-dessus, où le 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.

Donc, notez que la clé correcte pour 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. Ainsi, 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é)

0
jgangso

Si vous utilisez l'opérateur de comparaison like 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%' )
0
benklocek

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);
0
Den Media