使用元值作为序列化数组的meta_query


37

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

<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钩子上,我像这样保存数据:

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

我这样做是因为我有三个metabox,每个帖子只有3个postmeta值;但是,我现在意识到了一个潜在的问题。我可能想使用WP_Query仅根据这些元值提取某些帖子。例如,我可能想获取所有纬度值大于50的帖子。如果我单独在数据库中拥有此数据,也许使用key 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像在这种情况下那样查询序列化的数组?

Answers:


37

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

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

$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来避免这种情况,这也使查询更容易,但是正确地正确存储数据并避免开始使用结构化序列化数据要容易得多/更快。


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

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

1
您可以存储一个以上的键,键值对不是唯一的。至于关系,那是,什么分类是如果你使用的元到许多东西映射到的东西,把它们放在一个分类项,而不是
汤姆Ĵ诺埃尔

24

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

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

希望有帮助


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

此解决方案为我工作
Vishal

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

@Erenor Paz,我刚刚发布了一个可以同时使用ID和字符串的解决方案:wordpress.stackexchange.com/a/299325/25264
Pablo SG Pacheco,

使用LIKE是使服务器宕机的好方法(更不用说误报了),您最好具有很好的缓存。
马克·卡普伦'18

10

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

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

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

实际上分配了true的值;

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

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

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


3

我刚刚处理了序列化字段,可以查询它们。不使用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

根据get_posts的需要,这些ID可以很好地以数组结尾。

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


1

这个例子确实帮助了我。它专门用于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. */
            }
    }
?>

1

我认为有2种解决方案可以尝试解决将结果存储为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可以同时用于数字和字符串的解决方案

 $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

@ErenorPaz你是对的。LIKE是比较快的。但这是一个适用于字符串和数字的解决方案
Pablo SG Pacheco'Mar

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

@ErenorPaz,我编辑了答案,添加了一个新的解决方案,该解决方案使用LIKE但可用于数字和字符串。我不确定性能,因为它必须使用OR
Pablo SG Pacheco '18

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

0

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

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

1.在functions.php中:

在您的functions.php文件中(或您要在其中设置meta框的地方)yourname_save_post()使用函数

update_post_meta($post->ID, 'checkboxArray', implode(",", $checkboxArray)); //adding the implode

创建包含逗号分隔值的数组。

您还需要在yourname_post_meta()admin meta box构造函数中将输出变量更改为

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

2.在模板PHP文件中:

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

现在,我们使用构建自定义的SQL查询$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类型- - $wpdb有关主题的信息,另请参见法典。

Whelp,它花费了一些精力,但是由于wp_query不支持执行'compare' => 'IN'序列化或逗号分隔的值,因此此垫片是您的最佳选择!

希望这对某人有帮助。


0

如果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%' )

0

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

$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

0

我对以上答案感到好奇,其中的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'
         )
     )
 );

笔记:

  1. 这种方法仅使精确匹配成为可能。所以像所有纬度大于50的事情都是不可能的。

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


-1

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

也许尝试:

    $ args =数组(
    'post_type'=>'my-post-type',
    'meta_query'=>数组(
        数组(
            'key'=>'latitude',
            '值'=>'50',
            '比较'=>'>',
            '类型'=>'数字'
        )
    )
    );

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

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

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

-1

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

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

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

5
然后,不应将其标记为正确答案,这样做对您来说是不负责任的。因此正确的答案是“不,这是不可能的”
汤姆·J·诺维尔

1
同意,WP也可以为您处理序列化,serialize()在这种情况下不需要...
2012年

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

@TomJNowell完成!刚花了我5个月的时间;)
tollmanz 2013年
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.