A maneira mais eficiente de obter postagens com postmeta

32

Eu preciso de um monte de posts com seus metadados. É claro que você não pode obter metadados com uma consulta de postagens padrão, então geralmente você precisa fazer um get_post_custom() para cada postagem.

Estou tentando com uma consulta personalizada, como esta:

$results = $wpdb->get_results("
    SELECT  p.ID,
        p.post_title,
        pm1.meta_value AS first_field,
        pm2.meta_value AS second_field,
        pm3.meta_value AS third_field
    FROM    $wpdb->posts p LEFT JOIN $wpdb->postmeta pm1 ON (
            pm1.post_id = p.ID  AND
            pm1.meta_key    = 'first_field_key'
        ) LEFT JOIN $wpdb->postmeta pm2 ON (
            pm2.post_id = p.ID  AND
            pm2.meta_key    = 'second_field_key'
        ) LEFT JOIN $wpdb->postmeta pm3 ON (
            pm3.post_id = p.ID  AND
            pm3.meta_key    = 'third_field_key'
        )
    WHERE   post_status = 'publish'
");

Parece funcionar. Ele dispara se você usar algum desses meta-campos de uma maneira que permita múltiplos valores meta para ele na mesma postagem. Não consigo pensar em uma junção para fazer isso.

Então, pergunta 1: há uma associação, uma subconsulta ou qualquer outra coisa para gerar metadados de vários valores?

Mas pergunta 2: Vale a pena? Quantas postmeta junções de tabelas adiciono antes de uma abordagem de duas consultas se tornar preferível? Eu poderia pegar todos os dados de postagem em uma consulta, depois pegar todas as postmetas relevantes em outra e combinar a meta com os dados de postagem em um conjunto de resultados no PHP. Isso acabaria sendo mais rápido do que uma consulta SQL cada vez mais complexa, se isso for possível?

Eu sempre penso: "Dê tanto trabalho quanto possível ao banco de dados." Não tenho certeza sobre isso!

    
por Steve Taylor 10.01.2012 / 23:18
fonte

7 respostas

55

A meta-informação de postagem é armazenada em cache automaticamente na memória para um WP_Query padrão (e a consulta principal), a menos que você diga especificamente para não usar o parâmetro update_post_meta_cache .

Portanto, você não deve escrever suas próprias consultas para isso.

Como o meta caching funciona para consultas normais:

Se o parâmetro update_post_meta_cache para o WP_Query não estiver definido como false, depois que as postagens forem recuperadas do banco de dados, a função update_post_caches() será chamada, o que, por sua vez, chama update_postmeta_cache() .

A função update_postmeta_cache() é um wrapper para update_meta_cache() e basicamente chama um SELECT simples com todos os IDs das postagens recuperadas. Isso fará com que obtenha toda a postmeta, para todas as postagens da consulta, e salve esses dados no cache de objetos (usando wp_cache_add() ).

Quando você faz algo como get_post_custom() , está verificando primeiro esse cache de objetos. Então, não está fazendo consultas extras para obter o post meta neste momento. Se você recebeu o post em um WP_Query , então o meta já está na memória e fica direto de lá.

As vantagens aqui são muitas vezes maiores do que fazer uma consulta complexa, mas a maior vantagem vem do uso do cache de objetos. Se você usar uma solução de cache de memória persistente como XCache ou memcached ou APC ou algo assim, e tiver um plug-in que possa vincular seu cache de objetos a ele (W3 Total Cache, por exemplo), todo o cache de objetos será armazenado na memória rápida já. Nesse caso, há consultas zero necessárias para recuperar seus dados. já está na memória. O cache de objetos persistentes é impressionante em muitos aspectos.

Em outras palavras, sua consulta provavelmente é carregada e carregada mais lentamente do que usando uma consulta adequada e uma solução simples de memória persistente. Use o% normalWP_Query. Poupe algum esforço.

Adicional: update_meta_cache() é inteligente, BTW. Ele não irá recuperar meta informações para postagens que já tenham suas meta informações armazenadas em cache. Não obtém a mesma meta duas vezes, basicamente. Super eficiente.

Adicional adicional: "Dê o máximo de trabalho possível ao banco de dados." ... Não, esta é a web. Regras diferentes se aplicam. Em geral, você sempre quer dar o menor trabalho possível ao banco de dados, se for viável. Bancos de dados são lentos ou mal configurados (se você não configurou especificamente, você pode apostar que isso é verdade). Muitas vezes eles são compartilhados entre muitos sites e sobrecarregados em algum grau. Normalmente você tem mais servidores web do que bancos de dados. Em geral, você quer apenas obter os dados desejados do banco de dados o mais rápido e simplesmente possível, depois fazer a classificação usando o código do lado do servidor da web. Como princípio geral, é claro, casos diferentes são todos diferentes.

    
por Otto 24.01.2012 / 06:56
fonte
26

Eu recomendaria uma consulta dinâmica. Usando seu exemplo:

SELECT  p.ID,   
        p.post_title, 
        MAX(CASE WHEN wp_postmeta.meta_key = 'first_field' then wp_postmeta.meta_value ELSE NULL END) as first_field,
        MAX(CASE WHEN wp_postmeta.meta_key = 'second_field' then wp_postmeta.meta_value ELSE NULL END) as second_field,
        MAX(CASE WHEN wp_postmeta.meta_key = 'third_field' then wp_postmeta.meta_value ELSE NULL END) as third_field,

 FROM    wp_posts p LEFT JOIN wp_postmeta pm1 ON ( pm1.post_id = p.ID)                      
GROUP BY
   wp_posts.ID,wp_posts.post_title
    
por Ethan Seifert 24.01.2012 / 06:21
fonte
8

Eu me deparei com um caso em que quero também recuperar rapidamente muitas postagens com suas meta-informações associadas. Eu preciso recuperar postagens de O (2000).

Eu tentei usando a sugestão do Otto - executando o WP_Query :: query para todas as postagens e, em seguida, percorrendo e executando get_post_custom para cada post. Isso levou, em média, cerca de 3 segundos para ser concluído .

Eu então tentei a consulta de pivô de Ethan (embora eu não gostasse de ter que perguntar manualmente para cada meta_key em que eu estava interessado). Ainda tive que percorrer todos os posts recuperados para desserializar o meta_value. Isso levou, em média, cerca de 1,3 segundo para ser concluído .

Eu tentei usar a função GROUP_CONCAT e encontrei o melhor resultado. Aqui está o código:

global $wpdb;
$wpdb->query('SET SESSION group_concat_max_len = 10000'); // necessary to get more than 1024 characters in the GROUP_CONCAT columns below
$query = "
    SELECT p.*, 
    GROUP_CONCAT(pm.meta_key ORDER BY pm.meta_key DESC SEPARATOR '||') as meta_keys, 
    GROUP_CONCAT(pm.meta_value ORDER BY pm.meta_key DESC SEPARATOR '||') as meta_values 
    FROM $wpdb->posts p 
    LEFT JOIN $wpdb->postmeta pm on pm.post_id = p.ID 
    WHERE p.post_type = 'product' and p.post_status = 'publish' 
    GROUP BY p.ID
";

$products = $wpdb->get_results($query);

// massages the products to have a member ->meta with the unserialized values as expected
function massage($a){
    $a->meta = array_combine(explode('||',$a->meta_keys),array_map('maybe_unserialize',explode('||',$a->meta_values)));
    unset($a->meta_keys);
    unset($a->meta_values);
    return $a;
}

$products = array_map('massage',$products);

Isso demorou em média 0,7 segundos . Isso é cerca de um quarto do tempo da solução get_post_custom () do WP e cerca de metade da solução de consulta do pivot.

Talvez isso seja do interesse de alguém.

    
por Trevor Mills 07.10.2012 / 17:23
fonte
2

Eu encontrei-me em uma situação que eu precisava fazer essa tarefa para, finalmente, criar um documento CSV, acabei trabalhando diretamente com o mysql para fazer isso. Meu código une as tabelas de postagem e meta para recuperar informações de preços de woocommerce, a solução postada anteriormente exigia que eu usasse alias de tabela no sql para funcionar corretamente.

SELECT p.ID, p.post_title, 
    MAX(CASE WHEN pm1.meta_key = '_price' then pm1.meta_value ELSE NULL END) as price,
    MAX(CASE WHEN pm1.meta_key = '_regular_price' then pm1.meta_value ELSE NULL END) as regular_price,
    MAX(CASE WHEN pm1.meta_key = '_sale_price' then pm1.meta_value ELSE NULL END) as sale_price,
    MAX(CASE WHEN pm1.meta_key = '_sku' then pm1.meta_value ELSE NULL END) as sku
    FROM wp_posts p LEFT JOIN wp_postmeta pm1 ON ( pm1.post_id = p.ID)                 
    WHERE p.post_type in('product', 'product_variation') AND p.post_status = 'publish'
    GROUP BY p.ID, p.post_title

No entanto, lembre-se de que o woocommerce criou 300K + linhas na minha meta-tabela, por isso era muito grande e, portanto, muito lento.

    
por Terry Kernan 09.09.2015 / 15:19
fonte
0

usando o formulário de solução trevor e modificando-o para trabalhar com SQL aninhado. Isso não é testado.

global $wpdb;
$query = "
    SELECT p.*, (select pm.* From $wpdb->postmeta AS pm WHERE pm.post_id = p.ID)
    FROM $wpdb->posts p 
    WHERE p.post_type = 'product' and p.post_status = 'publish' 
";
$products = $wpdb->get_results($query);
    
por Jonathan Joosten 24.09.2014 / 10:01
fonte
0

SEM VERSÃO SQL:

Obtenha todas as postagens e todos os seus valores meta (metas) sem SQL:

Digamos que você tenha uma lista de IDs de postagem armazenados como uma matriz de IDs, algo como

$post_ids_list = [584, 21, 1, 4, ...];

Agora, não é possível obter todas as postagens e todas as metas em uma consulta sem usar pelo menos um pouco de SQL, então devemos fazer 2 consultas (ainda apenas 2):

1. Obtenha todas as postagens (usando WP_Query )

$request = new WP Query([
  'post__in' => $post_ids_list,
  'ignore_sticky_posts' => true, //if you want to ignore the "stickiness"
]);

(Não se esqueça de chamar wp_reset_postdata(); se você estiver fazendo um "loop " depois;))

2. Atualize o cache de meta

//don't be confused here: "post" means content type (post X user X ...), NOT post type ;)
update_meta_cache('post', $post_ids_list);

Para obter os metadados, basta usar o padrão get_post_meta() que, como apontado pelo @Otto:
examina o cache primeiro:)

Observação: Se você realmente não precisa de outros dados das postagens (como título, conteúdo, ...) você pode fazer apenas 2. : -)

    
por jave.web 31.01.2017 / 13:27
fonte
-1

Também encontrei o problema dos meta-campos de vários valores. O problema é com o próprio WordPress. Veja em wp-includes / meta.php. Procure esta linha:

$where[$k] = ' (' . $where[$k] . $wpdb->prepare( "CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$meta_compare_string})", $meta_value );

O problema é com a declaração CAST. Em uma consulta para valores meta, a variável $ meta_type é definida como CHAR. Eu não sei os detalhes sobre como CASTing o valor para CHAR afeta a string serializada, mas para corrigi-lo, você pode remover o cast para que o SQL seja assim:

$where[$k] = ' (' . $where[$k] . $wpdb->prepare( "$alias.meta_value {$meta_compare} {$meta_compare_string})", $meta_value );

Agora, mesmo que isso funcione, você está mexendo com os elementos internos do WordPress, então outras coisas podem quebrar, e não é uma correção permanente, supondo que você precisará atualizar o WordPress.

A maneira que eu consertei é copiar o SQL gerado pelo WordPress para a meta consulta que eu quero e então escrever algum PHP para adicionar instruções AND extras para os meta_values que estou procurando e usar $ wpdb- > get_results ($ sql) para a saída final. Hacky, mas funciona.

    
por Harry Love 23.04.2012 / 21:25
fonte

Tags