meta_query com valores meta como matrizes serializadas

36

Estou trabalhando em um projeto no qual estou criando um tipo de postagem personalizado e dados personalizados inseridos por meio de caixas meta associadas ao meu tipo de postagem personalizado. Por alguma razão eu decidi codificar as caixas meta de tal maneira que as entradas em cada metabox são 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 idéia de ter uma entrada postmeta singular para cada metabox. No gancho save_post , salvei os dados da seguinte forma:

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

Eu fiz isso porque tenho três metaboxes e gosto de ter apenas 3 valores postmeta para cada postagem; no entanto, agora percebi um possível problema com isso. Eu posso querer usar WP_Query para retirar apenas alguns posts com base nesses valores meta. Por exemplo, 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 , eu 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 _coordinates postmeta, isso não funcionaria.

Então, minha pergunta é, existe uma maneira de utilizar meta_query para consultar um array serializado como eu tenho neste cenário?

    
por tollmanz 09.05.2011 / 04:06
fonte

12 respostas

35

Não, não é possível.

Eu recomendo strongmente que você não serialize 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 múltiplas longitudes e múltiplas latitudes, você pode armazenar múltiplas postagens com o mesmo nome. Basta usar o terceiro parâmetro de get_post_meta e ele retornará todos como uma matriz

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

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

Você pode consultar por partes parciais de data, mas isso será super pouco confiável, caro, lento e muito frágil, com muitos casos de borda. Os dados serializados não são destinados a consultas SQL e não são formatados de forma regular e constante.

Além dos custos de pesquisas de string parciais, as meta-consultas posteriores são lentas e os dados serializados podem ser alterados dependendo de sua duração, tornando a pesquisa incrivelmente cara, se não impossível, dependendo do valor que você está pesquisando.

Uma nota sobre o armazenamento de registros / entidades / objetos como objetos serializados no Meta

Você pode querer armazenar um registro de transação no post meta, ou algum outro tipo de estrutura de dados no meta do usuário, e depois 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 para começar, mas um tipo de postagem personalizado. Por exemplo, um registro ou registro pode ser um tipo de postagem personalizado, com a postagem original como pai ou vinculado por meio de um termo de taxonomia

    
por Tom J Nowell 20.08.2012 / 17:19
fonte
23

Eu também me deparo com 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

    
por rabni 16.04.2015 / 14:38
fonte
10

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

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

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

Na verdade, atribuiu o valor de true como em

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

Os dados serão retornados como uma matriz, acessível para você fazer a iteração de acordo com o normal.

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

    
por userabuser 20.08.2012 / 16:36
fonte
3

Acabei de lidar com campos serializados e posso consultá-los. Não usando o 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 procura por postagem com o post_type correspondente, de modo que a quantidade de registros wp_postmeta será menor para filtrar. Em seguida, adicionei uma instrução where para reduzir as linhas ainda mais, filtrando em meta_key

Os IDs acabam bem em um array conforme necessário para get_posts.

PS. O MySQL v5.6 ou superior é necessário para um bom desempenho da subconsulta

    
por Tomas 22.08.2013 / 11:41
fonte
1

Este exemplo realmente me ajudou. É especificamente para o plugin S2Members (que serializa os metadados do usuário). Mas ele permite que você consulte uma parte de um array serializado dentro do meta_key.

Funciona usando a função REGEXP do MySQL.

Aqui é a fonte

Aqui está o código que consulta todos os usuários que moram nos EUA. Eu o modifiquei facilmente para consultar um dos meus campos de registro personalizados e tive que trabalhar em pouco tempo.

  <?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. */
            }
    }
?>
    
por BC Smith 12.12.2012 / 19:43
fonte
1

Acho que existem duas soluções que podem tentar resolver o problema de os resultados serem armazenados como String e Integers. No entanto, é importante dizer, como outros apontaram, que não é possível garantir a integridade dos resultados armazenados como Integer, pois 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 um array serializado, como este

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

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

1) Solução REGEXP

Esta solução funciona para mim, independentemente do valor meta que está sendo salvo como string ou number / id. No entanto, ele usa REGEXP , o que não é tão rápido quanto usar LIKE

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

2) Solução LIKE

Não tenho certeza sobre a diferença de desempenho, mas essa é uma solução que usa LIKE e também funciona para números e sequências

 $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'
            )
        )
    );
    
por Pablo S G Pacheco 29.03.2018 / 21:07
fonte
0

Depois de ler um monte de dicas para executar uma filtragem WP_Query por matrizes serializadas, eis como finalmente consegui: criando uma matriz de valores separados por vírgula usando implodir em conjunto com uma consulta SQL $wpdb personalizada utilizando FIND_IN_SET para pesquisar a lista separada por vírgula pelo valor solicitado.

(isso é semelhante à resposta do Tomas, mas é um pouco menos intensivo em desempenho para a consulta SQL)

1. Em functions.php:

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

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

para criar o array contendo valores separados por vírgula.

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

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

2. No arquivo PHP do modelo:

Teste: se você executar um get_post_meta( $id ); , deverá ver checkboxArray como uma matriz contendo seus valores separados por vírgulas em vez de um array serializado.

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 ... já que estou usando SELECT * , isso retorna todos os dados do post e dentro do foreach você pode fazer o que quiser (faça um print_r($posts); se você não sabe o que está incluído, ele não configura o "loop" para você (eu prefiro assim), mas ele pode ser facilmente modificado para configurar o loop se você preferir (dê uma olhada em setup_postdata($post); no codex, você provavelmente precisará alterar SELECT * para selecionar apenas IDs de postagens e $wpdb->get_results para o tipo $wpdb correto - veja o codex para $wpdb também para informações sobre que assunto).

Whelp, foi preciso um pouco de esforço, mas como wp_query não suporta valores% serializados ou separados por vírgulas, esse shim é sua melhor opção!

Espero que isso ajude alguém.

    
por Gifford N. 22.01.2015 / 23:10
fonte
0

Se você usa o operador de comparação like em sua meta-consulta, ele deve 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%' )
    
por benklocek 03.07.2015 / 23:14
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);
    
por Den Media 07.07.2016 / 08:50
fonte
0

Fiquei curioso sobre as respostas acima, em que meta_query segmentou a chave latitude em vez de _coordinates . Tive que testar se realmente era possível, em meta-consultas, direcionar uma chave específica dentro de um array serializado. :)

Isso obviamente não foi o caso.

Portanto, observe que a chave correta a ser segmentada é _coordinates em vez 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 só permite segmentar correspondências exatas. Então, 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)

por jgangso 06.07.2017 / 13:57
fonte
-1

Eu tenho a mesma pergunta. Talvez você precise do parâmetro 'type'? Confira esta pergunta relacionada: Consulta de campo personalizado - o valor do meta é uma matriz

Talvez tente:

    $args = array(
    'post_type' => 'my-post-type',
    'meta_query' => array(
        array(
            'key' => 'latitude',
            'value' => '50',
            'compare' => '>',
            'type' => 'numeric'
        )
    )
    );
    
por user4356 12.05.2011 / 19:44
fonte
-1

Eu encontrei algo similar enquanto usava o plugin 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' => '>'
        )
    )
);
    
por Seth Stevenson 12.05.2011 / 23:10
fonte