我正在一个项目中,在其中创建自定义帖子类型和通过与我的自定义帖子类型相关联的元框输入的自定义数据。不管出于什么原因,我决定以这样的方式对元框进行编码,以使每个元框中的输入都是数组的一部分。例如,我要存储经度和纬度:

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


无论出于什么原因,我都喜欢为每个元框有一个单一的元后输入的想法。在save_post钩子上,我像这样保存数据:

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


之所以这样做是因为我有3个metabox,我希望每个帖子只有3个postmeta值;但是,我现在意识到了一个潜在的问题。我可能想使用WP_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 postmeta的一部分,因此无法正常工作。

所以,我的问题是,有没有一种方法可以利用meta_query来查询序列化数组,例如我有这种情况吗?

#1 楼

不,这是不可能的,甚至有危险。

我强烈建议您反序列化数据并修改保存例程。与此类似的操作应将您的数据转换为新格式:

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


然后您就可以使用单个键根据需要进行查询

如果需要存储多个经度和多个纬度,则可以存储多个具有相同名称的帖子元。只需使用get_post_meta的第三个参数,它会将它们全部返回为数组

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

MySQL认为它只是一个字符串,并且不能将其拆分为结构化数据。将其分解为结构化数据正是上面的代码所做的

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

除了部分字符串搜索的开销外,后期元查询也很慢,序列化的数据可能会发生变化取决于内容的长度,这使搜索变得非常昂贵,甚至取决于您要搜索的值,这并非不可能。
关于将记录/实体/对象作为序列化对象存储在Meta中的注意事项

您可能想将交易记录存储在post meta中,或者将某种其他数据结构存储在user meta中,然后遇到上述问题。

这里的解决方案不是可以将其分解为单个帖子元,但要意识到它不应该以开始为元,而应该是自定义帖子类型。例如,日志或记录可以是自定义帖子类型,以原始帖子为父或通过分类术语加入

安全性和序列化对象

通过serialize函数存储序列化的PHP对象可能很危险,这很不幸,因为将对象传递给WordPress意味着它将被序列化。这是因为在反序列化对象时,将创建一个对象,并且将执行其所有唤醒方法和构造函数。直到用户设法潜入精心制作的输入,当从数据库中读取数据并由WordPress反序列化数据时,导致远程执行代码时,这似乎并不重要。

这可以是避免使用JSON代替,这也使查询更容易,但是正确存储数据并避免以结构化的序列化数据开头更容易/更快。

如果我有ID列表怎么办?

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

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

 
$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 );捕获所有发布元并循环浏览键,例如:

 $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调用非常便宜,并且不会触发额外的数据库查询。

完全避开问题>如果您知道需要对子值进行搜索/查询/过滤,为什么不存储具有该值的其他帖子元以便您可以搜索呢?

结论

,因此您不需要将结构化数据作为字符串存储在数据库中,并且如果您打算对这些值进行搜索/查询/过滤,则也不需要这样做。

它可能可以使用正则表达式和LIKE,但这是极其不可靠的,不适用于大多数类型的数据,并且在数据库上非常非常缓慢和繁重。如果结果是单独的值,也无法像结果一样进行数学运算

评论


对于路过的人,不要停止阅读:下面提供了更多有用的(和最新的)答案

– Erenor Paz
17年4月13日在10:07

如果我有一组要保存的ID怎么办-并且它们每个都不代表一个不同的键,我可以将它们保存在诸如“纬度”等下,它只是所有键的一个(例如,保存关系时)。那该怎么办? @rabni的解决方案?

–火车头病
17年6月16日在7:52

您可以多次存储一个键,键值对不是唯一的。至于关系,这就是分类法的目的,如果您使用meta将多个事物映射到某物上,则将它们放在分类法术语中

– Tom J Nowell♦
17年6月19日在17:31



#2 楼

我也遇到这种情况。
这是我做的事情:

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


希望获得帮助

评论


我真的很喜欢这个解决方案。不幸的是,这在$ value也是ID时不适用。在这种情况下,我建议创建一个函数以在保存数据之前向每个数组元素添加一个字符,并创建另一个函数以在使用数据之前删除该字符。不用担心,序列化的i:2索引不会与“真实”数据的i:D2混淆。然后,meta查询参数应变为'value'=> sprintf(':“ D%s”;',$ value),您将保留此美妙答案的正确功能!

– Erenor Paz
17年4月13日在9:30

这个解决方案对我有用

–Rakhi
18年1月12日在9:32

这对我来说也很完美。当我看到可接受的解决方案时,确实有些惊慌

– Shane Jones
18年2月26日在10:48

@Erenor Paz,我刚刚发布了一个既适用于ID又适用于字符串的解决方案:wordpress.stackexchange.com/a/299325/25264

– Pablo S G Pacheco
18年3月29日在19:09

使用LIKE是使服务器停机的好方法(更不用说误报了),您最好具有很好的缓存。

–马克·卡普伦
18-4-2在5:23



#3 楼

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

您认为通过序列化实现的总体性能节省和收益在很大程度上不会引起注意。您可能会获得稍小的数据库大小,但是如果您曾经查询过这些字段并尝试以任何有用的有意义的方式进行比较,则SQL事务的开销将非常沉重。

相反,请保存序列化信息,以防止您不希望以此性质查询,而只能通过直接WP API调用get_post_meta()以被动方式访问数据-从该函数中,您可以解压缩序列化的数据条目也可以访问其数组属性。

实际上分配了true的值;

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

将数据作为数组返回,您可以迭代访问为按正常。

您可以专注于其他数据库/站点优化,例如缓存,CSS和JS最小化,以及根据需要使用诸如CDN之类的服务。仅举几例。...WordPress Codex是一个很好的起点,可以发现有关该主题的更多信息:HERE

#4 楼

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

array(37,87);


存储为序列化数组,像这样

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


注意i:0作为数组的第一个位置,i:37作为第一个值。模式是相同的。但是,让我们转到解决方案


1)REGEXP解决方案

无论元数据保存为字符串还是数字/ id,此解决方案都对我有效。但是,它使用的是REGEXP,它的速度不如使用LIKE

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


2)LIKE解决方案

我不确定性能区别,但这是一个使用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'
            )
        )
    );


评论


REGEXP在某些情况下会很好,但是如果可以使用LIKE,我认为这是首选方法。我认为这是一个旧链接,但仍然非常有用:Thingsilearn.wordpress.com/2008/02/28/…:-)

– Erenor Paz
18年3月30日在8:44

@ErenorPaz你是对的。喜欢更快。但这是一个适用于字符串和数字的解决方案

– Pablo S G Pacheco
18-3-30在20:37



是的..所以,答案是(一如既往):根据情况,是否可以使用“ LIKE”;最好,否则REGEXP也会做:-)

– Erenor Paz
18年3月31日在13:10

@ErenorPaz,我编辑了答案,添加了一个新的解决方案,该解决方案使用LIKE,但适用于数字和字符串。我不确定性能,因为它必须使用OR来比较结果

– Pablo S G Pacheco
18年4月1日在19:41

没错!我需要得到像这样的结果。

– kuldip Makadiya
18-09-27在11:23

#5 楼

我刚刚处理了序列化字段,可以查询它们。
不使用meta_query而是使用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_type匹配,因此wp_postmeta记录的数量将更少。
然后我添加了一个where语句,通过对meta_key进行过滤进一步减少了行数

这些ID可以很好地结束在get_posts所需的数组中。

PS。为了获得良好的子查询性能,需要MySQL v5.6或更高版本

#6 楼

这个例子确实帮助了我。它专门用于S2Members插件(可序列化用户元数据)。但是它允许您查询meta_key中序列化数组的一部分。

它通过使用MySQL REGEXP函数工作。我轻松地对其进行了修改,以查询我的自定义注册字段之一,并使它立即运行。

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


#7 楼

在阅读了一系列有关通过序列化数组运行WP_Query过滤的技巧后,我终于做到了:通过使用implode结合$wpdb自定义SQL查询创建一个逗号分隔值数组,利用FIND_IN_SET来搜索逗号分隔列表,请求的值。

(这与Tomas的答案类似,但是对于SQL查询来说,它的性能要求较低)

1。在functions.php中:

yourname_save_post()函数中的functions.php文件中(或您要设置元框的任何地方),使用

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。在模板PHP文件中:

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

现在,我们使用$wpdb构建我们的自定义SQL查询。 m使用FIND_IN_SET会返回所有发布数据,并且在SELECT *内您可以回显您想要的内容(如果您不知道其中包含什么,请执行foreach。它不会为您设置“循环”(我更喜欢这样),但是如果您愿意的话,可以轻松地对其进行修改以建立循环(请看一下编解码器中的print_r($posts);,您可能需要更改setup_postdata($post);才能仅选择发布ID和SELECT *为正确的$wpdb->get_results类型- -请参阅$wpdb的法典以获取有关该主题的信息)。

Whelp有点费力,但是由于$wpdb不支持对wp_query进行序列化或com处理可能将值分开可能是您的最佳选择!

希望对您有所帮助。

#8 楼

如果在元查询中使用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%' )


#9 楼

如果我的元数据是数组类型,那么我将使用此方法通过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);


评论


当帖子ID与序列化字符串的ID具有相同的值时,这可能导致不想要的结果

– Erenor Paz
17年4月13日在10:05

#10 楼

我对以上答案感到好奇,其中meta_query定位于键latitude而不是_coordinates。必须去测试元查询中是否真的有可能针对序列化数组中的特定键。 :)

显然不是这种情况。

因此,请注意,指向目标的正确密钥是_coordinates而不是latitude


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



注释:


这种方法仅使精确匹配成为可能。因此不可能实现所有大于50的纬度。
要包含子字符串匹配项,可以使用'value' => sprintf(':"%%%s%%";', $value),。 (尚未测试)


#11 楼

我也有同样的问题。也许您需要'type'参数?请查看以下相关问题:
自定义字段查询-元值是数组

也许尝试:

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


评论


感谢您的建议,但这不是我想要的。问题是我要匹配的值是在数据库中序列化的数组的一部分。

– tollmanz
2011年5月13日,3:10

是的,你是对的。今天早上我尝试过,但对我也不起作用。我有同样的问题。将元键的值存储为数组。我开始认为无法做到这一点,而我可能不得不将它们存储为具有相同名称的单独的元字段……而只是妥善管理它们的删除/更新。

–user4356
2011年5月13日15:05



@ user4356 ...这正是我要做的。我希望减少每篇文章要插入的行数,但是我想那是不可能的。

– tollmanz
2011年5月14日19:15

#12 楼

使用Magic Fields插件时遇到了类似的情况。这可能会解决问题

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


评论


谢谢你的建议!我认为这几乎可以实现,但是实际上不起作用,因为将序列化的数组与另一个序列化的数组进行比较是没有道理的,除非我正在寻找精确匹配。

– tollmanz
2011年5月13日在3:08



然后,不应将此标记为正确答案,这样做对您来说是不负责任的。因此正确的答案是“不,这不可能”

– Tom J Nowell♦
2012年8月20日15:14

同意,WP也为您处理序列化,在这种情况下不需要serialize()。

–亚当
2012年8月20日16:11

实际上,当使用“ Magic Fields”插件完全按照他说的做时,@ seth-stevenson的答案很好。由于该插件默认情况下会序列化某些数据类型,因此这是进行精确匹配的最佳方法。

–zmonteca
2013年1月9日下午5:02

@TomJNowell完成!刚花了我5个月;)

– tollmanz
13年1月17日在22:15