meta_query con meta valores como serializar matrices

37

Estoy trabajando en un proyecto en el que estoy creando un tipo de publicación personalizada y datos personalizados ingresados ​​a través de meta cuadros asociados con mi tipo de publicación personalizada. Por alguna razón, decidí codificar los meta cuadros de tal manera que las entradas en cada meta cuadro sean parte de una matriz. Por ejemplo, estoy almacenando longitud y latitud:

<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>

Por alguna razón, me gustó la idea de tener una entrada de postmeta singular para cada metabox. En el save_postgancho, guardo los datos así:

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

Hice esto porque tengo tres metaboxes y me gusta tener solo 3 valores postmeta para cada publicación; Sin embargo, ahora me he dado cuenta de un problema potencial con esto. Es posible que desee usar WP_Query para extraer solo ciertas publicaciones basadas en estos meta valores. Por ejemplo, es posible que desee obtener todas las publicaciones que tengan valores de latitud superiores a 50. Si tuviera estos datos en la base de datos individualmente, tal vez usando la clave latitude, haría algo como:

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

Como tengo la latitud como parte de la _coordinatespostmeta, esto no funcionaría.

Entonces, mi pregunta es, ¿hay alguna manera de utilizar meta_querypara consultar una matriz serializada como la que tengo en este escenario?

tollmanz
fuente

Respuestas:

37

No, no es posible e incluso podría ser peligroso.

Le recomiendo que deserialice sus datos y modifique su rutina de guardado. Algo similar a esto debería convertir sus datos al nuevo formato:

$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);
    }
}

Luego podrá consultar como desee con teclas individuales

Si necesita almacenar múltiples longitudes y latitudes múltiples, puede almacenar múltiples metadatos con el mismo nombre. Simplemente use el tercer parámetro de get_post_meta, y los devolverá a todos como una matriz

¿Por qué no puede consultar dentro de los datos serializados?

MySQL lo ve solo como una cadena y no puede dividirlo en datos estructurados. Separarlo en datos estructurados es exactamente lo que hace el código anterior

Es posible que pueda consultar fragmentos parciales de fecha, pero esto será poco confiable, costoso, lento y muy frágil, con muchos casos extremos. Los datos serializados no están destinados a consultas SQL, y no están formateados de manera regular y constante.

Además de los costos de las búsquedas de cadenas parciales, las meta meta consultas son lentas y los datos serializados pueden cambiar dependiendo de cosas como la longitud del contenido, lo que hace que la búsqueda sea increíblemente costosa, si no imposible, dependiendo del valor que esté buscando

Una nota sobre el almacenamiento de registros / entidades / objetos como objetos serializados en meta

Es posible que desee almacenar un registro de transacción en la meta posterior, o algún otro tipo de estructura de datos en la meta del usuario, y luego encontrarse con el problema anterior.

La solución aquí no es dividirlo en meta publicaciones individuales, sino darse cuenta de que nunca debería haber sido meta para empezar, sino un tipo de publicación personalizado. Por ejemplo, un registro o registro puede ser un tipo de publicación personalizada, con la publicación original como padre o unida mediante un término de taxonomía

Seguridad y objetos serializados

Almacenar objetos PHP serializados a través de la serializefunción puede ser peligroso , lo cual es desafortunado ya que pasar un objeto a WordPress significa que se serializa. Esto se debe a que cuando el objeto se deserializa, se crea un objeto y se ejecutan todos sus métodos y constructores de activación. Esto podría no parecer un gran problema hasta que un usuario se las arregla para introducir una entrada cuidadosamente diseñada, lo que lleva a la ejecución remota de código cuando los datos se leen de la base de datos y WordPress los deserializa.

Esto se puede evitar mediante el uso de JSON, que también facilita las consultas, pero es mucho más fácil / rápido almacenar los datos correctamente y evitar los datos serializados estructurados para empezar.

Tom J Nowell
fuente
55
Para las personas que pasan, no dejen de leer: más respuestas útiles (y recientes) se encuentran a continuación
Erenor Paz
¿Qué sucede si tengo una variedad de ID para guardar? Y cada una de ellas no representa una clave diferente en la que podría guardarlas, como 'latitud', etc., es solo una clave para todos (como al guardar relaciones, etc.). ¿Qué hacer entonces? ¿La solución de @ rabni?
trainoasis
1
Puede almacenar una clave más de una vez, los pares de valores clave no son únicos. En cuanto a las relaciones, para eso están las taxonomías, si estás usando meta para mapear varias cosas en algo, ponlas en un término de taxonomía
Tom J Nowell
24

También me encuentro con esta situación. Aquí lo que hice:

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

Espero que esto ayude

rabni
fuente
1
Realmente me gustó esta solución. Desafortunadamente, esto no es aplicable cuando $valuetambién es una identificación. En ese caso, sugiero crear funciones para agregar un carácter a cada elemento de la matriz antes de guardar los datos y otra función para eliminar el carácter antes de usar los datos. Este wat, el i:2índice serializado no se confundirá con el i:D2de los datos "reales". ¡El parámetro de meta consulta entonces se convertirá 'value' => sprintf(':"D%s";', $value),y mantendrá la funcionalidad correcta de esta maravillosa respuesta!
Erenor Paz
Esta solución está funcionando para mí
Vishal
Esto también funcionó perfectamente para mí. Sin embargo
Shane Jones
@Erenor Paz, acabo de publicar una solución que funciona bien con ID y Strings: wordpress.stackexchange.com/a/299325/25264
Pablo SG Pacheco
usar LIKEes una manera excelente y rápida de desactivar su servidor (sin mencionar los falsos positivos) es mejor que tenga un muy buen almacenamiento en caché.
Mark Kaplun
10

Realmente va a perder la capacidad de consultar sus datos de manera eficiente al serializar entradas en la base de datos de WP.

El ahorro general de rendimiento y la ganancia que cree que está logrando con la serialización no se notará en gran medida. Es posible que obtenga un tamaño de base de datos un poco más pequeño, pero el costo de las transacciones SQL será alto si alguna vez consulta esos campos e intenta compararlos de una manera útil y significativa.

En su lugar, guarde la serialización para datos que no tiene la intención de consultar de esa naturaleza, sino que solo accedería de manera pasiva mediante la llamada directa a la API de WP get_post_meta(): desde esa función también puede desempaquetar una entrada serializada para acceder a sus propiedades de matriz.

De hecho, se le asigna el valor de verdadero como en;

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

Devolverá los datos como una matriz, accesible para que pueda iterar de la forma habitual.

Puede centrarse en otras optimizaciones de bases de datos / sitios, como el almacenamiento en caché, la minificación CSS y JS y el uso de servicios como CDN si lo requiere. Por nombrar solo algunos ... WordPress Codex es un buen punto de partida para descubrir más sobre ese tema: AQUÍ

Adán
fuente
3

Acabo de tratar con campos serializados y podría consultarlos. No usando meta_query sino usando una consulta 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 consulta primero busca la publicación con el post_type correspondiente, por lo que la cantidad de registros wp_postmeta será menor para filtrar. Luego agregué una instrucción where para reducir aún más las filas al filtrarmeta_key

Los ID terminan muy bien en una matriz según sea necesario para get_posts.

PD. Se necesita MySQL v5.6 o superior para un buen rendimiento de subconsulta

Tomás
fuente
1

Este ejemplo realmente me ayudó. Es específicamente para el complemento S2Members (que serializa los metadatos del usuario). Pero le permite consultar una parte de una matriz serializada dentro de la meta_key.

Funciona utilizando la función MySQL REGEXP.

Aquí esta la fuente

Aquí está el código que consulta a todos los usuarios que viven en los EE. UU. Lo modifiqué fácilmente para consultar uno de mis campos de registro personalizados y lo hice funcionar en poco tiempo.

  <?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
fuente
1

Creo que hay 2 soluciones que pueden tratar de resolver el problema de los resultados almacenados como String e Integers. Sin embargo, es importante decir, como señalaron otros, que no es posible garantizar la integridad de los resultados almacenados como Integer, ya que estos valores se almacenan como matrices serializadas, el índice y los valores se almacenan exactamente con el mismo patrón. Ejemplo:

array(37,87);

se almacena como una matriz serializada, como esta

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

Tenga en cuenta el i:0como la primera posición de la matriz y i:37como el primer valor. El patrón es el mismo. Pero vamos a las soluciones


1) Solución REGEXP

Esta solución funciona para mí independientemente del metavalor que se guarde como cadena o número / id. Sin embargo, usa REGEXP, que no es tan rápido como usarLIKE

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

2) COMO solución

No estoy seguro de la diferencia de rendimiento, pero esta es una solución que usa LIKEy también funciona para números y cadenas

 $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
fuente
REGEXPes bueno en ciertas situaciones, pero si puedes usarlo LIKE, creo que es el método preferible. Un viejo enlace, pero todavía bastante útil, en mi opinión: thingsilearn.wordpress.com/2008/02/28/… :-)
Erenor Paz
@ErenorPaz Tienes razón. LIKEes más rápido. Pero esta es una solución que funciona tanto para cadenas como para números
Pablo SG Pacheco el
Sí ... entonces, la respuesta es (como siempre): dependiendo de la situación, si puede usar "LIKE"; es preferible, de lo contrario REGEXP también lo hará :-)
Erenor Paz
@ErenorPaz, edité mi respuesta agregando una nueva solución que usa LIKEpero funciona tanto para números como para cadenas. No estoy seguro sobre el rendimiento porque tiene que comparar los resultados usandoOR
Pablo SG Pacheco
Exactamente !!! que necesito para obtener el mismo resultado ... ¡¡¡Gracias, hombre !!!
kuldip Makadiya
0

Después de leer un montón de consejos para ejecutar un WP_Queryfiltrado por matrices serializadas, así es como finalmente lo hice: creando una matriz de valores separados por comas mediante implode junto con una $wpdbconsulta SQL personalizada que utiliza FIND_IN_SETpara buscar en la lista separada por comas el valor solicitado.

(Esto es similar a la respuesta de Tomás, pero requiere un poco menos de rendimiento para la consulta SQL)

1. En functions.php:

En su archivo functions.php (o donde sea que esté configurando el cuadro meta) en la yourname_save_post()función use

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

para crear la matriz que contiene valores separados por comas.

También querrá cambiar su variable de salida en la yourname_post_meta()función de construcción de meta cuadro de administrador a

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

2. En el archivo PHP de plantilla:

Prueba: si ejecuta un get_post_meta( $id );, debería ver checkboxArraycomo una matriz que contiene sus valores separados por comas en lugar de una matriz serializada.

Ahora, construimos nuestra consulta SQL personalizada usando $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
}

Observe el FIND_IN_SET, ahí es donde sucede la magia.

Ahora ... ya que estoy usando SELECT *esto, devuelve todos los datos de la publicación y dentro de él foreachpuedes hacer eco de lo que quieres de eso (hazlo print_r($posts);si no sabes lo que está incluido. No configura "el bucle" para usted (lo prefiero de esta manera), pero se puede modificar fácilmente para configurar el bucle si lo prefiere (eche un vistazo al setup_postdata($post);códice, probablemente tendrá que cambiar SELECT *para seleccionar solo ID de publicaciones y $wpdb->get_resultsel $wpdbtipo correcto ) - Consulte el códice para obtener $wpdbtambién información sobre ese tema).

¡Cielos, tomó un poco de esfuerzo, pero como wp_queryno admite hacer 'compare' => 'IN'valores serializados o separados por comas, esta cuña es su mejor opción!

Espero que esto ayude a alguien.

Gifford N.
fuente
0

Si usa el likeoperador de comparación en su meta consulta, debería funcionar bien mirar dentro de una matriz serializada.

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

resultados 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
fuente
0

Si mis metadatos son de tipo matriz, usaré este método para consultas por meta:

$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
fuente
Esto podría conducir a resultados no deseados cuando una ID de publicación tiene el mismo valor que la identificación de la cadena serializada
Erenor Paz
0

Tengo curiosidad por las respuestas anteriores, donde meta_queryapuntaron la clave en latitudelugar de _coordinates. Tuve que ir y probar si realmente era posible en meta consultas para apuntar a una clave específica dentro de una matriz serializada. :)

Obviamente ese no fue el caso.

Entonces, tenga en cuenta que la clave correcta para apuntar es en _coordinateslugar de latitude.

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

NOTAS

  1. Este enfoque solo permite apuntar a coincidencias exactas. Así que cosas como todas las latitudes mayores de 50 no son posibles.

  2. Para incluir coincidencias de subcadenas, se podría usar 'value' => sprintf(':"%%%s%%";', $value),. (no lo he probado)

jgangso
fuente
-1

Tengo la misma pregunta. ¿Quizás necesita el parámetro 'tipo'? Consulte esta pregunta relacionada: Consulta de campo personalizada : el valor meta es matriz

Quizás intente:

    $ args = array (
    'post_type' => 'my-post-type',
    'meta_query' => array (
        formación(
            'key' => 'latitud',
            'value' => '50',
            'compare' => '>',
            'type' => 'numérico'
        )
    )
    );
usuario4356
fuente
Gracias por la sugerencia, pero esto no es exactamente lo que busco. El problema es que el valor que intento hacer coincidir es parte de una matriz que se serializa dentro de la base de datos.
tollmanz
Sí, tienes razón. Lo intenté esta mañana y tampoco funcionó para mí. Tengo el mismo problema. Almacenar un valor de una metaclave como una matriz. Estoy empezando a pensar que esto no se puede hacer y, en su lugar, podría tener que almacenarlos como metacampos separados con el mismo nombre ... y simplemente administrar la eliminación / actualización de ellos correctamente.
user4356
@ user4356 ... eso es exactamente lo que voy a hacer. Tenía la esperanza de reducir el número de filas que insertaría para cada publicación, pero supongo que eso no es posible.
tollmanz
-1

Me encontré con algo similar mientras usaba el complemento Magic Fields. Esto podría hacer el truco

$values_serialized = serialize(array('50'));
$args = array(
    'post_type' => 'my-post-type',
    'meta_query' => array(
        array(
            'key' => 'latitude',
            'value' => $values_serialized,
            'compare' => '>'
        )
    )
);
Seth Stevenson
fuente
1
¡Gracias por la sugerencia! Creo que esto es lo más cercano posible, pero en realidad no funcionará porque comparar una matriz serializada con otra matriz serializada no tiene sentido a menos que esté buscando una coincidencia exacta.
tollmanz
55
Entonces esto no debe marcarse como la respuesta correcta y es irresponsable de su parte hacerlo. Por lo tanto, la respuesta correcta sería 'No, no es posible'
Tom J Nowell
1
De acuerdo, también WP maneja la serialización por usted, serialize()no es necesario en este caso ...
Adam
2
En realidad, la respuesta de @ seth-stevenson es excelente cuando hace exactamente lo que dijo, usando el complemento "Campos mágicos". Como ese complemento serializa cierto tipo de datos de forma predeterminada, esta es la mejor manera de hacer una coincidencia EXACTA.
zmonteca
@TomJNowell ¡Listo! Solo me tomó 5 meses;)
tollmanz