通过postmeta获得帖子的最有效方法


35

我需要获得一堆包含其元数据的帖子。当然,您无法通过标准的帖子查询来获取元数据,因此通常必须get_post_custom()对每个帖子都执行一次。

我正在尝试一个自定义查询,如下所示:

$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'
");

似乎可以工作。如果您以允许在同一帖子上使用多个元值的方式使用这些元字段中的任何一个,则会跳闸。我想不出要这样做的联接。

因此,问题1:是否存在联接,子查询或其他任何方式来引入多值元字段?

但是问题2:值得吗?postmeta在使用2查询方法之前,我要添加多少个表联接?我可以在一个查询中获取所有发布数据,然后在另一个查询中获取所有相关的postmeta,然后在PHP的一个结果集中将元数据与发布数据合并。如果有可能的话,它会比单个更复杂的SQL查询更快吗?

我一直认为,“将尽可能多的工作交给数据库。” 不确定这一点!


我不确定您是否要加入联接。get_posts()和get_post_meta()的组合可以为您提供相同的数据。实际上,使用联接的效率较低,因为您可能正在检索以后将不使用的数据。
rexposadas 2012年

2
反正不是自动缓存发布的元数据吗?
Manny Fleurmond

@rxn,如果我要返回几百个帖子(它们是自定义帖子类型),那么肯定对DB来说负担很重get_posts(),那么get_post_meta()对于其中的每一个?@MannyFleurmond,很难找到有关WP内置缓存的硬信息,但AFAIK会根据请求缓存内容。调用服务器以获取此数据是AJAX调用,我认为没有其他东西可以在此之前获取内容。
史蒂夫·泰勒

实际上,我要进行多个查询并缓存结果。事实证明,我们不仅需要发布元,包括具有多个值的字段,还需要有关通过元字段(两组)连接到发布的用户的数据,以及它们上的用户元。纯SQL绝对是不可能的!
史蒂夫·泰勒

Answers:


58

WP_Query除非您通过使用update_post_meta_cache参数明确指示不要这样做,否则后元信息会自动为标准(和主查询)缓存在内存中。

因此,您不应为此编写自己的查询。

元缓存如何用于普通查询:

如果的update_post_meta_cache参数WP_Query未设置为false,则从数据库中检索帖子后,update_post_caches()将调用函数,该函数又调用update_postmeta_cache()

update_postmeta_cache()函数是的包装器update_meta_cache(),它本质上是调用具有SELECT检索到的帖子的所有ID的简单方法。这将获取查询中所有帖子的所有postmeta,并将该数据保存在对象缓存中(使用wp_cache_add())。

当您执行类似的操作时get_post_custom(),它将首先检查该对象的缓存。因此,此时无需进行额外的查询来获取帖子元。如果您在中找到了帖子WP_Query,则该元已经在内存中,并且可以直接从那里获取信息。

这里的优势比进行复杂的查询要大很多倍,但是最大的优势来自于使用对象缓存。如果您使用诸如XCache或memcached或APC之类的持久性内存缓存解决方案,并且具有可以将对象缓存与其绑定的插件(例如W3 Total Cache),那么整个对象缓存将存储在快速内存中已经。在这种情况下,检索数据所需的查询为零;它已经在内存中了。持久对象缓存在许多方面都很棒。

换句话说,查询的加载和加载可能比使用正确的查询和简单的持久性内存解决方案要慢。使用正常WP_Query。节省您的精力。

另外: update_meta_cache()很聪明,顺便说一句。它不会为已缓存其元信息的帖子检索元信息。基本上,它不会两次获得相同的元数据。超级高效。

其他附加内容: “为数据库提供尽可能多的工作。” ...不,这是Web。适用不同的规则。通常,如果可行,您总是希望对数据库进行尽可能少的工作。数据库运行缓慢或配置不正确(如果您没有专门配置数据库,则可以押注这是事实)。通常,它们在许多站点之间共享,并且在某种程度上超载。通常,您的Web服务器比数据库更多。通常,您只想尽可能快,简单地从数据库中获取所需的数据,然后使用Web服务器端代码对数据进行排序。当然,作为一般原则,不同的情况都是不同的。


30

我建议使用透视查询。使用您的示例:

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

该答案应标记为正确。
路加福音

如果您要查找数据库查询,这是正确的答案
Alex Popov

当我使用WP_Query时,此查询将我的时间从〜25秒减少到〜3秒。我的要求是只触发一次,因此不需要缓存。
库什

10

我遇到一种情况,我也想快速检索大量带有相关元信息的帖子。我需要检索O(2000)帖子。

我使用Otto的建议进行了尝试-为所有帖子运行WP_Query :: query,然后为每个帖子循环遍历并运行get_post_custom。 这平均大约需要3秒钟才能完成

然后,我尝试了Ethan的数据透视查询(尽管我不喜欢必须手动询问我感兴趣的每个meta_key)。我仍然必须遍历所有检索到的帖子才能反序列化meta_value。 平均而言,此过程大约需要1.3秒

然后,我尝试使用GROUP_CONCAT函数,并找到了最佳结果。这是代码:

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

这平均需要0.7秒。这大约是WP get_post_custom()解决方案的四分之一,而大约是透视查询解决方案的一半。

也许这会对某人感兴趣。


我会对使用持久对象缓存解决方案获得的结果感兴趣。对于基本情况,对象缓存有时会变慢,具体取决于您的数据库和配置,但是在大多数主机的情况下,实际结果会产生相差很大的结果。基于内存的缓存非常快。
奥托(Otto)

嘿@Otto。无论我使用哪种方法获取数据,我都绝对要缓存结果。我尝试使用瞬态API来执行此操作,但是遇到内存问题。我的2000个对象的序列化字符串的时钟为〜8M,set_transient()失败(内存耗尽)。另外,必须更改max_allowed_pa​​cket MySQL设置。我将研究将其缓存到文件中,但是我不确定那里的性能。有没有一种方法可以缓存到在请求之间持久存在的内存?
Trevor Mills 2012年

是的,如果您有持久性内存缓存(XCache,memcached,APC等),并且使用对象缓存插件(W3 Total Cache支持多种类型的内存缓存),则它将所有对象缓存存储在内存中,从而为您提供了几乎所有东西的倍数加速。
奥托(Otto)

我返回了6000个要在主干/下划线js过滤方案中使用的项目。这花了6s的自定义查询,由于超时,我什至无法作为WP_Query运行,并将其设为2s查询。尽管array_map使它变慢了很多……
Jake

是否支持构建高性能支持以返回WP_Query中的所有元数据?
atwellpub

2

我发现自己处在需要完成此任务以最终从中创建CSV文档的情况下,我最终直接与mysql一起完成了此任务。我的代码将发布表和元表连接起来以检索woocommerce定价信息,以前发布的解决方案要求我在sql中使用表别名才能正常工作。

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

但请注意,woocommerce在我的元表中创建了300K +行,因此它很大,因此非常慢。


1

没有SQL版本:

不使用SQL获取所有帖子及其所有元值(元):

假设您有一个以ID数组形式存储的帖子ID列表,例如

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

现在,如果不使用至少一点SQL,就不可能在1个查询中获取所有帖子和所有元数据,因此我们必须执行2个查询(仍然只有2个):

1.获取所有帖子(使用WP_Query

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

wp_reset_postdata();如果您之后进行“循环”操作,请不要忘记致电;))

2.更新元缓存

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

要获取元数据,只需使用get_post_meta()@Otto指出的标准:
首先查看缓存:)

注意: 如果您实际上不需要帖子中的其他数据(例如标题,内容等),则只需执行2。 :-)


0

使用解决方案形式trevor并对其进行修改以与嵌套SQL一起使用。这未经测试。

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

-1

我也遇到了多值元字段问题。问题出在WordPress本身。查看wp-includes / meta.php。查找以下行:

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

问题出在CAST语句上。在查询元值时,$ meta_type变量设置为CHAR。我不知道有关将值CAST转换为CHAR会如何影响序列化字符串的详细信息,但是要解决此问题,可以删除强制转换,使SQL看起来像这样:

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

现在,即使这行得通,但您仍在与WordPress内部混为一谈,因此其他事情可能会中断,并且如果您需要升级WordPress,这并不是永久性的解决方法。

我已修复的方法是复制WordPress为我想要的元查询生成的SQL,然后编写一些PHP以附加我要查找的meta_values的AND语句,并使用$ wpdb-> get_results($ sql )作为最终输出。哈克,但它可以工作。


我没有尝试过,但是利用get_meta_sql遵循这一行的过滤器当然比黑客破解核心代码更好。
史蒂夫·泰勒
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.