A maneira mais eficiente de obter postagens com o postmeta

36

Preciso receber várias postagens com seus metadados. É claro que você não pode obter metadados com uma consulta de postagens padrão; portanto, você geralmente 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 qualquer um desses meta-campos de uma maneira que permita vários valores-meta na mesma postagem. Não consigo pensar em uma junção para fazer isso.

Então, pergunta 1: existe uma junção, subconsulta ou qualquer outra coisa para trazer metacarpos de valores múltiplos?

Mas pergunta 2: vale a pena? Quantas postmetaassociações de tabela adiciono antes que uma abordagem de 2 consultas se torne preferível? Eu poderia pegar todos os dados de postagem em uma consulta, depois pegar todos os postmeta relevantes em outra e combinar a meta com os dados de postagem em um conjunto de resultados em PHP. Isso acabaria sendo mais rápido que uma única consulta SQL cada vez mais complexa, se isso é possível?

Eu sempre penso: "Dê o máximo de trabalho possível ao banco de dados". Não tenho certeza sobre este!

Steve Taylor
fonte
Não tenho certeza se você quer fazer as junções. a combinação de get_posts () e get_post_meta () devolve os mesmos dados. Na verdade, é menos eficiente usar as junções, pois você pode recuperar dados que não usará mais tarde.
Rexposadas
2
As postagens de metadados da postagem não são armazenadas em cache automaticamente?
precisa saber é o seguinte
@ rxn, se eu tiver centenas de posts voltando (eles são um tipo de post personalizado), certamente é uma carga de banco de dados bastante pesada get_posts(), então get_post_meta()para cada um deles? @MannyFleurmond, é difícil encontrar informações difíceis sobre o cache interno do WP, mas o AFAIK armazenaria em cache as informações por solicitação. A chamada para o servidor para coletar esses dados é uma chamada AJAX, e acho que nada mais estará pegando coisas antes dela.
21312 Steve Steve
Na verdade, vou fazer várias consultas e armazenar em cache os resultados. Acontece que não precisamos apenas de meta meta, incluindo campos com vários valores, também precisamos de dados sobre usuários conectados às postagens por meio de campos meta (dois conjuntos destes), além de meta usuário sobre eles. O SQL puro está definitivamente fora da janela!
21412 Steve Steve

Respostas:

58

As meta-informações de postagem são armazenadas em cache automaticamente na memória para um padrão WP_Query(e a consulta principal), a menos que você diga especificamente para não fazer isso usando o update_post_meta_cacheparâmetro

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

Como o meta caching funciona para consultas normais:

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

A update_postmeta_cache()função é um invólucro update_meta_cache()e essencialmente chama um simples SELECTcom todos os IDs das postagens recuperadas. Isso fará com que ele obtenha todos os postmeta, para todas as postagens na consulta, e salve esses dados no cache do objeto (usando wp_cache_add()).

Quando você faz algo parecido get_post_custom(), ele verifica primeiro o cache do objeto. Portanto, não é necessário fazer consultas adicionais para obter a meta da postagem neste momento. Se você recebeu a postagem em um WP_Query, a meta já está na memória e a obtém diretamente a partir daí.

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á zero de consultas 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 carrega e carrega mais lentamente do que usar uma consulta adequada e uma solução simples de memória persistente. Use o normal WP_Query. Poupe algum esforço.

Adicional: update_meta_cache() é inteligente, BTW. Ele não recuperará as meta informações para postagens que já tenham suas meta informações 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 deseja dar o mínimo de trabalho possível ao banco de dados, se for possível. Os bancos de dados são lentos ou mal configurados (se você não o configurou especificamente, pode apostar que isso é verdade). Muitas vezes, eles são compartilhados entre muitos sites e sobrecarregados até certo ponto. Normalmente você tem mais servidores web do que bancos de dados. Em geral, você deseja apenas obter os dados que deseja do banco de dados o mais rápido e simples possível e, em seguida, classificá-los usando o código do servidor da web. Como princípio geral, é claro, casos diferentes são todos diferentes.

Otto
fonte
30

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
Ethan Seifert
fonte
Esta resposta deve ser marcada como correta.
22412 Luke
Se você estiver procurando por uma consulta de banco de dados esta é a resposta correta
Alex Popov
Essa consulta reduziu meu tempo quando eu estava usando o WP_Query de ~ 25 segundos para ~ 3 segundos. Meu requisito era disparar isso apenas uma vez, para que nenhum cache fosse necessário.
Kush
11

Me deparei com um caso em que também quero recuperar rapidamente muitas postagens com suas meta informações associadas. Preciso recuperar postagens O (2000).

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

Então tentei a consulta dinâmica de Ethan (embora não gostasse de pedir manualmente cada meta_key em que me interessasse). Eu ainda tinha que percorrer todas as postagens recuperadas para desserializar o meta_value. Isso levou, em média, cerca de 1,3 segundos para ser concluído .

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 levou em média 0,7 segundos . Isso ocorre cerca de um quarto do tempo da solução get_post_custom () do WP e cerca de metade da solução de consulta dinâmica.

Talvez isso seja do interesse de alguém.

Trevor Mills
fonte
Eu estaria interessado em quais resultados você obtém com uma solução de cache de objeto persistente. Às vezes, o cache de objetos é mais lento para o caso base, dependendo do banco de dados e da configuração, mas os resultados do mundo real com a maioria dos hosts fornecem resultados muito variados. O cache baseado em memória é ridiculamente rápido.
Otto
Hey @Otto. Independentemente do método usado para obter os dados, eu definitivamente quero armazenar em cache o resultado. Eu tentei usar a API transitória para fazer isso, mas estou atingindo problemas de memória. A seqüência de caracteres serializada para meus objetos de 2000 tem um clock de ~ 8M e set_transient () falha (a memória está esgotada). Além disso, é necessário alterar a configuração max_allowed_packet do MySQL. Vou procurar em cache para arquivar, mas ainda não tenho certeza do desempenho lá. Existe uma maneira de armazenar em cache a memória que persiste nas solicitações?
Trevor Mills
Sim, se você tiver um cache de memória persistente (XCache, memcached, APC, etc) e usar um plug-in de cache de objetos (o W3 Total Cache suporta muitos tipos de caches de memória), ele armazenará todo o cache de objetos na memória, fornecendo a você um aceleração muitas vezes de praticamente tudo.
Otto
Estou retornando 6000 itens para usar em um esquema de filtragem de backbone / sublinhado js. Isso levou uma consulta personalizada de 6s que eu não conseguia nem executar como WP_Query porque o tempo limite expirou e a transformou em uma consulta 2s. Embora o array_map o retarde um pouco ...
Jake
Existe algum suporte para criar suporte de alto desempenho para retornar todos os metadados em um WP_Query?
Atwellpub 12/09/14
2

Eu me encontrei em uma situação na qual precisava executar esta tarefa para 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 as informações de preços do woocommerce, a solução postada anteriormente exigia que eu usasse aliases 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

Porém, lembre-se de que o woocommerce criou mais de 300 mil linhas na minha tabela meta, por isso era muito grande e, portanto, muito lento.

Terry Kernan
fonte
1

SEM VERSÃO SQL:

Obtenha todas as postagens e todos os seus meta-valores (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; portanto, devemos fazer duas consultas (ainda apenas duas):

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 ligar wp_reset_postdata();se você estiver fazendo um "loop" depois;))

2. Atualize o meta cache

//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 o @Otto apontou:
examina primeiro o cache :)

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

jave.web
fonte
0

usando o formulário de solução trevor e modificando-o para trabalhar com SQL aninhado. Isso não foi 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);
Jonathan Joosten
fonte
-1

Corri para o problema de vários campos meta de valor também. O problema está no próprio WordPress. Procure 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 está na instrução CAST. Em uma consulta para valores meta, a variável $ meta_type é definida como CHAR. Não sei os detalhes de como CASTing o valor para CHAR afeta a sequência serializada, mas para corrigi-lo, você pode remover a conversão para que o SQL fique 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 internos do WordPress, para que outras coisas possam quebrar, e não é uma solução permanente, supondo que você precise atualizar o WordPress.

A maneira como eu o corrigi é copiar o SQL gerado pelo WordPress para a meta consulta que eu quero e, em seguida, escrever um pouco de PHP para adicionar instruções AND adicionais aos meta_values ​​que estou procurando e usar $ wpdb-> get_results ($ sql ) para a saída final. Hacky, mas funciona.

Harry Love
fonte
Eu não tentei, mas usar o get_meta_sqlfiltro que segue esta linha seria, obviamente, preferível ao hackear o código principal.
27512 Steve