meta_query com meta-valores como serializar matrizes

37

Estou trabalhando em um projeto no qual estou criando um tipo de postagem personalizado e dados personalizados inseridos por meio de meta boxes associadas ao meu tipo de postagem personalizada. Por alguma razão, decidi codificar as meta boxes de forma que as entradas em cada metabox fizessem parte de uma matriz. Por exemplo, estou armazenando longitude e 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>

Por qualquer motivo, gostei da ideia de ter uma entrada pós-meta singular para cada metabox. No save_postgancho, eu salvo os dados da seguinte forma:

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

Fiz isso porque tenho três metaboxes e gosto de ter apenas 3 valores pós-meta para cada postagem; no entanto, agora percebi um problema em potencial com isso. Talvez eu queira usar o WP_Query para extrair apenas determinadas postagens com base nesses meta-valores. Por exemplo, eu posso querer obter todas as postagens com valores de latitude acima de 50. Se eu tivesse esses dados no banco de dados individualmente, talvez usando a chave latitude, faria algo como:

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

Como tenho a latitude como parte do _coordinatespostmeta, isso não funcionaria.

Então, minha pergunta é: existe uma maneira de utilizar meta_querypara consultar uma matriz serializada como eu tenho neste cenário?

tollmanz
fonte

Respostas:

37

Não, não é possível e pode até ser perigoso.

Eu recomendo fortemente que você desserialize seus dados e modifique sua rotina de salvamento. Algo semelhante a isso deve converter seus dados para o novo 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);
    }
}

Em seguida, você poderá consultar como quiser com chaves individuais

Se você precisar armazenar várias longitudes e várias latitudes, poderá armazenar várias post meta com o mesmo nome. Basta usar o terceiro parâmetro de get_post_meta, e ele retornará todos eles como uma matriz

Por que você não pode consultar dados serializados?

O MySQL vê isso apenas como uma string e não pode separá-lo em dados estruturados. Dividir em dados estruturados é exatamente o que o código acima faz

Você pode procurar por partes parciais da data, mas isso será super não confiável, caro, lento e muito frágil, com muitos casos extremos. Os dados serializados não se destinam a consultas SQL e não são formatados de maneira regular e constante.

Além dos custos de pesquisas parciais de cadeias de caracteres, as pós-consultas meta são lentas e os dados serializados podem mudar dependendo de itens como o tamanho do conteúdo, tornando a pesquisa incrivelmente cara, se não impossível, dependendo do valor que você está procurando

Uma observação sobre o armazenamento de registros / entidades / objetos como objetos serializados no Meta

Convém armazenar um registro de transação na pós-meta ou algum outro tipo de estrutura de dados na meta do usuário e, em seguida, encontrar o problema acima.

A solução aqui não é dividi-la em pós-meta individual, mas perceber que nunca deveria ter sido meta, mas um tipo de postagem personalizado. Por exemplo, um log ou registro pode ser um tipo de postagem personalizada, com a postagem original como pai ou associada a um termo de taxonomia

Segurança e objetos serializados

Armazenar objetos PHP serializados por meio da serializefunção pode ser perigoso , o que é lamentável, pois passar um objeto para o WordPress significa que ele será serializado. Isso ocorre porque quando o objeto é desserializado, um objeto é criado e todos os seus métodos e construtores de ativação são executados. Isso pode não parecer grande coisa até que um usuário consiga roubar uma entrada cuidadosamente criada, levando à execução remota de código quando os dados são lidos no banco de dados e desserializados pelo WordPress.

Isso pode ser evitado usando JSON, o que também facilita as consultas, mas é muito mais fácil / rápido armazenar os dados corretamente e evitar dados serializados estruturados para começar.

Tom J Nowell
fonte
5
Para as pessoas que passam, não pare de ler: respostas mais úteis (e recentes) são encontradas abaixo
Erenor Paz
E se eu tiver uma matriz de IDs para salvar - e cada um deles não representar uma chave diferente em que eu possa salvá-los, como 'latitude' etc., é apenas uma chave para todos (como ao salvar relações etc.). O que fazer então? solução do @ rabni?
trainoasis 16/06
11
Você pode armazenar uma chave mais de uma vez, os pares de valores de chave não são exclusivos. Quanto às relações, que é o que taxonomias são para, se você estiver usando meta para mapear várias coisas em algo, colocá-los em um termo taxonomia vez
Tom J Nowell
24

Eu também encontro essa situação. Aqui o que eu fiz:

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

Espero que esta ajuda

rabni
fonte
11
Eu realmente gostei desta solução. Infelizmente, isso não é aplicável quando $valuetambém é um ID. Nesse caso, sugiro criar funções para adicionar um caractere a cada elemento da matriz antes de salvar os dados e outra função para remover o caractere antes de usar os dados. Dessa forma, o i:2índice serializado não será confundido com os i:D2dados "reais". O parâmetro de meta consulta deve se tornar 'value' => sprintf(':"D%s";', $value),e você manterá a funcionalidade correta dessa resposta maravilhosa!
Erenor Paz
Esta solução está funcionando para mim
Vishal
Isso também funcionou perfeitamente para mim. Teve um mini pânico quando eu vi a solução aceita embora
Shane Jones
@Erenor Paz, Acabei de publicar uma solução que funciona bem tanto com ID e Cordas: wordpress.stackexchange.com/a/299325/25264
Pablo SG Pacheco
O uso LIKEé uma maneira excelente e rápida de derrubar o servidor (sem mencionar falsos positivos); é melhor você ter um cache muito bom.
27468 Mark Kaplun #
10

Você realmente perderá a capacidade de consultar seus dados de qualquer maneira eficiente ao serializar entradas no banco de dados do WP.

A economia e o ganho geral de desempenho que você acha que está alcançando pela serialização não serão notados em grande parte. Você pode obter um tamanho de banco de dados um pouco menor, mas o custo das transações SQL será alto se você consultar esses campos e tentar compará-los de qualquer maneira útil e significativa.

Em vez disso, salve a serialização para dados que você não pretende consultar dessa natureza, mas acessaria apenas de forma passiva pela chamada direta da API do WP get_post_meta()- a partir dessa função, é possível descompactar uma entrada serializada para acessar também as propriedades da matriz.

De fato, atribuiu o valor de true como em;

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

Retornará os dados como uma matriz, acessível para você repetir normalmente.

Você pode se concentrar em outras otimizações de banco de dados / site, como cache, minificação CSS e JS e usar esses serviços como uma CDN, se necessário. Para citar apenas alguns .... O WordPress Codex é um bom ponto de partida para descobrir mais sobre esse tópico: AQUI

Adão
fonte
3

Acabei de lidar com campos serializados e poderia consultá-los. Não usando a meta_query, mas usando uma 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);

A consulta primeiro pesquisa por postagem com o post_type correspondente, para que a quantidade de registros wp_postmeta seja menor para filtrar. Em seguida, adicionei uma instrução where para reduzir ainda mais as linhas filtrandometa_key

Os IDs acabam bem em uma matriz, conforme necessário para get_posts.

PS. É necessário o MySQL v5.6 ou superior para obter um bom desempenho da subconsulta

Tomas
fonte
1

Este exemplo realmente me ajudou. É especificamente para o plug-in S2Members (que serializa os metadados do usuário). Mas permite consultar uma parte de uma matriz serializada dentro da meta_key.

Funciona usando a função REGEXP do MySQL.

Aqui está a fonte

Aqui está o código que consulta todos os usuários que vivem nos EUA. Modifiquei-o facilmente para consultar um dos meus campos de registro personalizados e o funcionou rapidamente.

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

Eu acho que existem 2 soluções que podem tentar resolver o problema dos resultados sendo armazenados como String e Inteiros. No entanto, é importante dizer, como outros apontaram, que não é possível garantir a integridade dos resultados armazenados como Inteiro, porque, como esses valores são armazenados como matrizes serializadas, o índice e os valores são armazenados exatamente com o mesmo padrão. Exemplo:

array(37,87);

é armazenado como uma matriz serializada, como esta

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

Observe i:0como a primeira posição da matriz e i:37como o primeiro valor. O padrão é o mesmo. Mas vamos às soluções


1) Solução REGEXP

Esta solução funciona para mim, independentemente do meta-valor que está sendo salvo como string ou número / ID. No entanto, ele usa REGEXP, o que não é tão rápido quanto usarLIKE

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

2) COMO A Solução

Não tenho certeza sobre a diferença de desempenho, mas esta é uma solução que usa LIKEe também funciona para números e seqüências de caracteres

 $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
fonte
REGEXPé legal em certas situações, mas se você pode usar LIKE, acho que é o método preferível. Um link antigo, mas ainda bastante útil, na minha opinião: thingsilearn.wordpress.com/2008/02/28/... :-)
Erenor Paz
@ErenorPaz Você está certo. LIKEé mais rápido. Mas esta é uma solução que funciona para ambas as cordas e números
Pablo SG Pacheco
Sim ... então, a resposta é (como sempre): dependendo da situação, se você pode usar "LIKE"; é preferível, caso contrário, REGEXP vai fazer tão bem :-)
Erenor Paz
@ErenorPaz, editei minha resposta adicionando uma nova solução que usa, LIKEmas funciona para números e seqüências de caracteres. Eu não tenho certeza sobre o desempenho porque tem que comparar os resultados usandoOR
Pablo SG Pacheco
Exatamente !!! qual eu preciso obter resultado mesmo assim .... Valeu cara !!!
Kuldip Makadiya
0

Depois de ler várias dicas para executar uma WP_Queryfiltragem por matrizes serializadas, eis como finalmente o fiz: criando uma matriz de valores separados por vírgula usando implode em conjunto com uma $wpdbconsulta SQL personalizada que utiliza FIND_IN_SETpara pesquisar o valor solicitado na lista separada por vírgula.

(isso é semelhante à resposta de Tomas, mas é um pouco menos eficiente para a consulta SQL)

1. Em functions.php:

No seu arquivo functions.php (ou onde quer que você esteja configurando a meta box) na yourname_save_post()função use

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

para criar a matriz que contém valores separados por vírgula.

Você também deseja alterar sua variável de saída na yourname_post_meta()função de construção da caixa de administração para

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

2. No arquivo PHP de modelo:

Teste: se você executar um, get_post_meta( $id );deverá ver checkboxArraycomo uma matriz que contém seus valores separados por vírgula, em vez de uma matriz serializada.

Agora, criamos nossa 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 o FIND_IN_SET, é aí que a mágica acontece.

Agora ... desde que eu estou usando SELECT *isso retorna todos os dados de postagem e dentro do que foreachvocê pode fazer eco do que você quer (faça um print_r($posts);se você não souber o que está incluído. Ele não configura "o loop" para você (eu prefiro assim), mas pode ser facilmente modificado para configurar o loop, se você preferir (dê uma olhada no setup_postdata($post);codex, você provavelmente precisará alterar SELECT *para selecionar apenas IDs de postagem e $wpdb->get_resultso $wpdbtipo correto - - veja o codex $wpdbtambém para obter informações sobre esse assunto).

Whelp, demorou um pouco de esforço, mas como wp_querynão suporta 'compare' => 'IN'valores serializados ou separados por vírgula, esse calço é a melhor opção!

Espero que isso ajude alguém.

Gifford N.
fonte
0

Se você usar o likeoperador de comparação em sua meta consulta, ele deverá funcionar bem para procurar dentro de uma matriz serializada.

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

resulta em:

[query_where] => WHERE 1=1 AND (
  ( wp_usermeta.meta_key = 'wp_capabilities' 
  AND CAST(wp_usermeta.meta_value AS CHAR) NOT LIKE '%subscriber%' )
benklocek
fonte
0

Se meus metadados forem do tipo array, eu uso este método para consulta 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
fonte
Isto poderia levar a resultados indesejados quando um post ID tem o mesmo valor que o ID da string serializada
Erenor Paz
0

Fiquei curioso sobre as respostas acima, onde o meta_queryalvo a chave em latitudevez de _coordinates. Tive que testar se realmente era possível, nas meta-consultas, segmentar uma chave específica dentro de uma matriz serializada. :)

Obviamente não era esse o caso.

Portanto, observe que a chave correta para segmentar é em _coordinatesvez de latitude.

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

NOTAS:

  1. Essa abordagem torna possível apenas segmentar correspondências exatas. Portanto, coisas como todas as latitudes maiores que 50 não são possíveis.

  2. Para incluir correspondências de substring, pode-se usar 'value' => sprintf(':"%%%s%%";', $value),. (não testei)

jgangso
fonte
-1

Eu tenho a mesma pergunta. Talvez você precise do parâmetro 'type'? Confira esta pergunta relacionada: Consulta de campo personalizado - Meta Value is Array

Talvez tente:

    $ args = matriz (
    'post_type' => 'meu-post-type',
    'meta_query' => matriz (
        matriz (
            'chave' => 'latitude',
            'valor' => '50',
            'compare' => '>',
            'type' => 'numérico'
        )
    )
    );
user4356
fonte
Obrigado pela sugestão, mas não é exatamente isso que estou procurando. O problema é que o valor que estou tentando corresponder faz parte de uma matriz serializada no banco de dados.
Tollmanz 13/05/11
Sim, você está certo. Eu tentei isso esta manhã e também não funcionou para mim. Eu tenho o mesmo problema. Armazenando um valor de uma meta chave como uma matriz. Estou começando a pensar que isso não pode ser feito e, em vez disso, talvez seja necessário armazená-los como meta-campos separados com o mesmo nome ... e apenas gerenciar a exclusão / atualização deles adequadamente.
user4356
@ user4356 ... é exatamente isso que vou fazer. Eu estava esperando reduzir o número de linhas que eu inseriria para cada postagem, mas acho que isso não é possível.
Tollmanz
-1

Encontrei algo semelhante ao usar o plug-in Magic Fields. Isso pode fazer o truque

$values_serialized = serialize(array('50'));
$args = array(
    'post_type' => 'my-post-type',
    'meta_query' => array(
        array(
            'key' => 'latitude',
            'value' => $values_serialized,
            'compare' => '>'
        )
    )
);
Seth Stevenson
fonte
11
Obrigado pela sugestão! Eu acho que isso é o mais próximo possível, mas na verdade não funcionará porque comparar uma matriz serializada com outra matriz serializada não fazia sentido, a menos que eu estivesse procurando uma correspondência exata.
Tollmanz
5
Então isso não deve ser marcado como a resposta correta e é irresponsável da sua parte fazê-lo. A resposta correta, portanto, seria 'Não, não é possível'
Tom J Nowell
11
Concordo, também WP alças de serialização para você, serialize()não é necessária neste caso ...
Adam
2
Na verdade, a resposta @ seth-stevenson é ótima ao fazer exatamente o que ele disse, usando o plug-in "Magic Fields". Como esse plug-in serializa certo tipo de dados por padrão, esta é a melhor maneira de fazer uma correspondência EXATA.
Zmonteca
@TomJNowell Done! Só me levou 5 meses;)
tollmanz