将元值作为序列化数组的META_QUERY

时间:2011-05-09 作者:tollmanz

我正在从事一个项目,在该项目中,我正在创建一个自定义帖子类型,并通过与我的自定义帖子类型关联的元框输入自定义数据。无论出于何种原因,我决定以这样一种方式对元框进行编码,即每个元框中的输入都是数组的一部分。例如,我存储经度和纬度:

<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>
无论出于何种原因,我喜欢为每个元数据库都设置一个单一的postmeta条目。在save_post hook,我这样保存数据:

update_post_meta($post_id, \'_coordinates\', $_POST[\'coordinates\']);
我这样做是因为我有三个代谢箱,我喜欢每个帖子有三个postmeta值;然而,我现在意识到了这可能存在的一个问题。我可能想使用WP\\u Query只提取基于这些元值的某些帖子。例如,我可能想获取纬度值大于50的所有帖子。如果我在数据库中单独保存这些数据,也许可以使用latitude, 我会这样做:

$args = array(
    \'post_type\' => \'my-post-type\',
    \'meta_query\' => array(
        array(
            \'key\' => \'latitude\',
            \'value\' => \'50\',
            \'compare\' => \'>\'
        )
    )
 );
$query = new WP_Query( $args );
因为纬度是_coordinates 过了预计时间,这就行不通了。

所以,我的问题是,有没有办法利用meta_query 要像在这个场景中一样查询序列化数组吗?

12 个回复
最合适的回答,由SO网友:Tom J Nowell 整理而成

No, it is not possible, and could even be dangerous.

序列化数据是一种攻击向量,也是一个主要的性能问题。

我强烈建议您取消序列化数据并修改保存例程。类似于此的内容应将数据转换为新格式:

$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();
        // get the data
        $c = get_post_meta( $post->ID, \'_coordinates\', true );

        // save it in the new format, separate post meta, taxonomy term etc
        add_post_meta( $post->ID, \'_longitude\', $c[\'longitude\'] );
        add_post_meta( $post->ID, \'_latitude\', $c[\'latitude\'] );

        // clean up the old post meta
        delete_post_meta( $post->ID, \'_coordinates\', $c );
    }
}
然后,您可以根据需要使用各个键进行查询

如果需要存储多个经度和多个纬度,可以使用相同的名称存储多个post meta。只需使用的第三个参数get_post_meta, 它将以数组的形式返回它们

为什么不能查询内部序列化数据

MySQL将其视为一个字符串,无法将其分解为结构化数据。将其分解为结构化数据正是上面的代码所做的

您可能能够查询部分数据块,但这将是非常不可靠、昂贵、缓慢且非常脆弱的,并且有很多边缘情况。序列化数据不适用于SQL查询,也不是以常规和恒定的方式格式化的。

除了部分字符串搜索的成本外,post meta查询速度很慢,序列化数据可能会根据内容的长度等因素发生变化,这使得搜索非常昂贵,如果不是不可能的话,这取决于您正在搜索的值

那怎么办LIKE?

您可能会看到一些善意的问题,建议使用LIKE 为了实现这一点。这不能解决问题吗?This is not the solution, it is fools gold.

有几个主要问题:

  • false matches, 正在搜索test 具有LIKE 也将匹配test, testing, untested, 和其他值对于具有键或对象的数组,无法将其压缩为子键,无法进行排序,速度非常慢,成本非常高LIKE 将只适用于不现实的特定有限情况,并且会带来严重的性能损失。

    关于将记录/实体/对象作为序列化对象存储在Meta中的注意事项您可能希望在post-Meta中存储事务记录,或者在user-Meta中存储其他类型的数据结构,然后遇到上述问题。

    这里的解决方案不是将其分解为单独的post meta,而是要意识到它本来就不应该是meta,而是一种自定义的post类型。例如,日志或记录可以是自定义的日志类型,原始日志可以作为父日志,也可以通过分类术语连接

    安全和序列化对象

    Storing serialized PHP objects via the serialize function can be dangerous, 不幸的是,将对象传递给WordPress将意味着它将被序列化。这是因为当对象反序列化时,将创建一个对象,并执行其所有唤醒方法和构造函数。这似乎不是什么大不了的事,除非用户设法潜入精心编制的输入,从而在从数据库读取数据并由WordPress反序列化时执行远程代码。

    这可以通过使用JSON来避免,这也使得查询更容易,但只正确存储数据和避免结构化序列化数据一开始就更容易/更快。

    如果我有一个ID列表怎么办

    您可能想给WP一个数组,或者将其转换为逗号分隔的列表,但您不必这样做!

    Post元密钥不是唯一的,您可以多次存储同一密钥,例如:

    
    $id = ...;
    add_post_meta( $id, \'mylist\', 1 );
    add_post_meta( $id, \'mylist\', 2 );
    add_post_meta( $id, \'mylist\', 3 );
    add_post_meta( $id, \'mylist\', 4 );
    add_post_meta( $id, \'mylist\', 5 );
    add_post_meta( $id, \'mylist\', 6 );
    
    但是我怎样才能把数据拿回来呢?你有没有注意到get_post_meta 是否有始终设置为true的第三个参数?将其设置为false:

    $mylist = get_post_meta( $id, \'mylist\', false );
    foreach ( $mylist as $number ) {
        echo \'<p>\' . $number . \'</p>;
    }
    

    如果我有一个命名项数组怎么办

    如果我想以允许我查询字段的方式存储此数据结构,该怎么办?

    {
      "foo": "bar",
      "fizz": "buzz"
      "parent": {
        "child": "value"
      }
    }
    
    很简单,用前缀将其拆分:

    add_post_meta( $id, "tomsdata_foo", "bar" );
    add_post_meta( $id, "tomsdata_fizz", "buzz" );
    add_post_meta( $id, "tomsdata_parent_child", "value" );
    
    如果需要循环这些值,请使用get_post_meta( $id ); 要获取所有post meta并循环键,例如:

    $all_meta = get_post_meta( $id );
    $look_for = \'tomsdata_parent\';
    foreach ( $all_meta as $key => $value ) {
        if ( substr($string, 0, strlen($look_for)) !== $look_for ) {
            continue; // doesn\'t match, skip!
        }
        echo \'<p>\' . $key . \' = \' . $value . \'</p>\';
    }
    
    将输出:

    <p>tomsdata_parent_child = value</p>
    
    <记住,当WP获取一篇文章时,它会同时获取所有的文章元,所以get_post_meta 调用非常便宜,不会触发额外的数据库查询

    如果您知道需要搜索/查询/筛选子值,那么为什么不使用该值存储一个额外的post meta以便您可以搜索它呢?

    结论

    So you don\'t need to store structured data as a string in the database, and you shouldn\'t if you plan to search/query/filter on those values.

    可以使用正则表达式和LIKE, 但这是extremely unreliable, 对大多数类型的数据都不起作用,而且速度非常慢,数据库负担也很重。如果结果是单独的值,你也不能像对结果进行数学运算那样

SO网友:rabni

我也遇到了这种情况。以下是我所做的:

$args = array(
    \'post_type\' => \'my-post-type\',
    \'meta_query\' => array(
        array(
            \'key\' => \'latitude\',
            \'value\' => sprintf(\':"%s";\', $value),
            \'compare\' => \'LIKE\'
        )
    )
);
希望这有帮助

SO网友:Adam

在将条目序列化到WP数据库时,您确实将失去以任何有效方式查询数据的能力。

您认为通过序列化可以实现的总体性能节约和收益在很大程度上不会引人注目。您可能会获得较小的数据库大小,但如果您查询这些字段并尝试以任何有用、有意义的方式对其进行比较,那么SQL事务的成本将很高。

相反,请保存您不打算以这种性质查询的数据的序列化,而只通过直接WP API调用以被动方式访问这些数据get_post_meta() - 从该函数中,您可以解压缩序列化条目以访问其数组属性。

事实上分配了true 如中所示;

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

将以数组形式返回数据,您可以按照正常方式进行迭代。

您可以关注其他数据库/站点优化,如缓存、CSS和JS缩小,并在需要时将此类服务用作CDN。仅举几个例子。。。。WordPress Codex是揭示该主题更多信息的良好起点:HERE

SO网友:Pablo S G Pacheco

我认为有两种解决方案可以尝试解决结果同时存储为字符串和整数的问题。然而,正如其他人指出的那样,必须指出,不可能保证存储为整数的结果的完整性,因为当这些值存储为序列化数组时,索引和值的存储模式完全相同。示例:

array(37,87);
存储为序列化数组,如下所示

a:2:{i:0;i:37;i:1;i:87;}
请注意i:0 作为阵列的第一个位置i:37 作为第一个值。模式是一样的。但让我们来看看解决方案

<小时>

1) REGEXP Solution

无论元值保存为字符串或数字/id,此解决方案都适用于我REGEXP, 这比使用LIKE

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

2) LIKE Solution

我不确定性能差异,但这是一个使用LIKE 也适用于数字和字符串

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

SO网友:Tomas

我刚刚处理了序列化字段,可以查询它们。不使用meta\\u查询,而是使用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);
查询首先搜索具有匹配post\\u类型的post,以便筛选的wp\\u Posteta记录数量更少。然后,我添加了一个where语句,通过过滤进一步减少行数meta_key

根据get\\u帖子的需要,这些ID最终很好地位于一个数组中。

PS.MySQL v5。6或更高版本才能获得良好的子查询性能

SO网友:BC Smith

这个例子对我很有帮助。它专门针对S2Members插件(序列化用户元数据)。但它允许您在meta\\u键内查询序列化数组的一部分。

它通过使用MySQL REGEXP函数工作。

Here 是源

下面是查询所有居住在美国的用户的代码。我轻松地修改了它,以查询我的一个自定义注册字段,并很快使其工作。

  <?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. */
            }
    }
?>

SO网友:Gifford N.

在阅读了一系列关于运行WP_Query 通过序列化数组过滤,我最终是这样做的:通过使用内爆和$wpdb 自定义SQL查询利用FIND_IN_SET 在逗号分隔的列表中搜索请求的值。

(这与Tomas的答案类似,但对于SQL查询来说,性能要求不高)

1. In functions.php:

在您的功能中。php文件(或设置元框的任何位置)yourname_save_post() 功能使用

update_post_meta($post->ID, \'checkboxArray\', implode(",", $checkboxArray)); //adding the implode
创建包含逗号分隔值的数组。

您还需要在yourname_post_meta() 管理元框构造功能

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

2. In the template PHP file:

测试:如果运行get_post_meta( $id ); 你应该看看checkboxArray 作为包含逗号分隔值的数组,而不是序列化数组。

现在,我们使用$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
}
请注意FIND_IN_SET, 这就是魔法发生的地方。

现在因为我正在使用SELECT * 这将返回foreach 你可以从中呼应出你想要的(做一个print_r($posts); 如果你不知道里面有什么。它不会为您设置“循环”(我更喜欢这种方式),但如果您愿意,可以很容易地修改它来设置循环(看看setup_postdata($post); 在抄本中,您可能需要更改SELECT * 仅选择帖子ID和$wpdb->get_results 到正确的$wpdb 类型--请参阅codex$wpdb 也可获取关于该主题的信息)。

小崽子,这需要一点努力,但自从wp_query 不支持执行\'compare\' => \'IN\' 序列化或逗号分隔的值此填充是您的最佳选择!

希望这对别人有帮助。

SO网友:benklocek

如果您使用like 在元查询中使用比较运算符,可以很好地查看序列化数组内部。

$wp_user_search = new WP_User_Query(array(
    \'meta_query\' => array(
        array(
            \'key\'     => \'wp_capabilities\',
            \'value\'   => \'subscriber\',
            \'compare\' => \'not like\'
            )
        )
    )
);
结果如下:

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

SO网友:Den Media

如果我的元数据是数组类型,我将使用此方法按元数据进行查询:

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

SO网友:jgangso

我对上面的答案很好奇meta_query 以密钥为目标latitude 而不是_coordinates. 必须去测试是否真的可以在元查询中以序列化数组中的特定键为目标。:)

显然情况并非如此。

So, note that the correct key to target is _coordinates instead of latitude.

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

NOTES:

<这种方法只能针对精确的匹配。因此,像所有纬度都大于50这样的事情是不可能发生的。

要包含子字符串匹配,可以使用\'value\' => sprintf(\':"%%%s%%";\', $value),. (尚未测试)

SO网友:user4356

我有同样的问题。也许您需要“type”参数?查看此相关问题:Custom Field Query - Meta Value is Array

或许可以尝试:

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

SO网友:Seth Stevenson

我在使用Magic Fields插件时遇到了类似的情况。这可能会奏效

$values_serialized = serialize(array(\'50\'));
$args = array(
    \'post_type\' => \'my-post-type\',
    \'meta_query\' => array(
        array(
            \'key\' => \'latitude\',
            \'value\' => $values_serialized,
            \'compare\' => \'>\'
        )
    )
);

结束

相关推荐

如何避免在插件的Metabox中重新发明自定义字段轮子?

以下是场景:在我的插件中,我想在帖子/页面编辑器上显示一个元框。我希望metabox完全像WordPress的“自定义字段”metabox(添加另一个、删除、更新、自动填充下拉列表、输入新链接、AJAX magic等);有几个细微的区别:我要的不是“Name”和“Value”,而是“Target”、“Name”和“Value”与其将它们全部保存为自动显示在WP自定义字段元框中的“公共”自定义字段,不如将它们全部存储为“专用”命名空间字段中的单个多维数组。。。即。,_myplugin_custom_fiel