31

Here are the args for my query :

$args = array(
    'post_type' => 'news',
    'meta_query' => array(
        array(
            'key' => 'topics',
            'value' => 'sports',
        )
    )
);

This works when topics is a string, but not when it is an array. I'd like this query to work when topics is for example array( 'sports', 'nonprofit', etc. )

Is there a way to build meta queries with arrays as a meta_key ?

2
  • Please clarify--do you mean that the stored value of "topics" is an array? Or that the stored value is a string, and you want to pass multiple terms to the query in an array?
    – MathSmath
    Commented Jun 15, 2012 at 14:03
  • @MathSmath, I mean that the stored value is an array.
    – mike23
    Commented Jun 15, 2012 at 14:08

5 Answers 5

58

Feeding the query an array of possible values

If the value in the database is a string and you want to feed the query several values:

$args = array(
    'post_type' => 'news',
    'meta_query' => array(
        array(
            'key' => 'topics',
            'value' => array ( 'sports', 'nonprofit', 'community' ),
            'compare' => 'IN'
        )
    )
);

Searching for a specific value in a serialized array of data

If the value in the database is an array of several topics and you want to search for a single topic within that array (Note that an array in the database can be retrieved as such, but lives in the database in serialized form, which is a string also):

$args = array(
    'post_type' => 'news',
    'meta_query' => array(
        array(
            'key' => 'topics',
            'value' => 'sports',
            'compare' => 'LIKE'
        )
    )
);

Using 'LIKE' as the compare value isn't as clear-cut an instruction as you might have hoped for, but it is the best option to go with.

Next to that, your only other option would be retrieving all posts that have the meta_key "topics" set and iterating over them manually or,in other words, check for the value within the loop and display the posts on said condition.

EDIT 2021

Though still valid, the above answer is 9 years old.
Have a look at @sMyle's and at @Kaji's excellent answers as well.

28

To go off of Johannes' response, since it is a serialized array, if you happen to be storing something like user id's (which was my case), you may need to handle it a little differently.

Post meta was being saved like:

array( "1", "23", "99");

So yes they are integers but through update_post_meta they were being saved as strings.

'meta_query' => array(
            array(
                    'key'     => 'my_meta_key',
                    'value'   => serialize( strval( 1 ) ),
                    'compare' => 'LIKE'
                )
            )

So you're actually doing a LIKE comparison with the serialized string version of what you're looking for.

EDIT 2022:

One issue to be aware of when doing this if it's NOT a string that is saved (saved as integer), and you use serialize( intval( $value ) ) , you run into the potential issue of matching against the index instead of the value.

For example:

array( 1234, 5678 );

Is saved like this:

a:1:{i:0;i:1234;i:1;i:5678;}

If you use serialize( intval( 1 ) ) in meta query, you will match on i:1; which is the index, NOT the value, so be careful and if you control the codebase, always save as a string so you don't match on the index.

5
  • 1
    serialize(strval(1)) solved my problem, Thanks
    – Behzad
    Commented Jan 10, 2016 at 20:45
  • Came across this old answer by chance today. I like your addition. +1 Commented Oct 20, 2016 at 6:07
  • 2
    I just came across this as well, my thing is that I need to get all posts where user_id is not in array, but the above solution doesn't work so I made it like this: 'meta_query' => array( array( 'key' => 'my_meta_key', 'value' => ':' . $user_id . ';', 'compare' => 'NOT LIKE' ) ) Because when serialized all values are saved like: ':value;'
    – Bobz
    Commented Mar 19, 2018 at 4:29
  • Clever. I dig nifty, not all that conventional approaches. Didn't exactly expect to find that here this morning. Props. Commented Aug 16, 2020 at 8:30
  • 1
    Wow, this saved my day! Commented May 17, 2022 at 6:20
10

Another slight improvement from @sMyles' answer.

I have had cases where IDs have been stored both as strings (such as when taken from a form input) and as integers (e.g. update_post_meta($post_id, authorized_users', array(get_current_user_id()));). This is kind of like the well-known issue with wp_set_object_terms() where you can use term IDs to set the terms, but if you don't cast them as integers first you stand about a 50% chance of creating new terms with those numbers as their names instead.

This can result in them being stored quite differently in a serialized array, as can be seen from the excerpts of just such a case from my test site's database:

a:1:{i:0;s:1:"1";} // 's' for 'string', also note the double quotes
a:1:{i:0;i:1;} // 'i' for 'integer', no quotes

Both of the above, when fed through print_r() will render as

Array
(
    [0] => 1
)

To fix this, I made a slight tweak to the meta_query by adding a relation and another version of the query that cast the value as an integer instead of a string.

Here's the final result:

        'meta_query' => array(
            'relation' => 'OR', // Lets it know that either of the following is acceptable
            array(
                'key' => 'bcm_enm_authorized_users',
                'value'   => serialize(strval(get_current_user_id())), // Saved as string
                'compare' => 'LIKE'
            ),
            array(
                'key' => 'bcm_enm_authorized_users',
                'value'   => serialize(intval(get_current_user_id())), // Saved as integer
                'compare' => 'LIKE'
            ),
        ),

EDIT: Just realized that this method could run the risk of collisions with array indexes, which could allow someone illicit access to materials if they're not in the array, but their user ID appears as an index. As such, while this works if you have the issue discussed, better practice is to ensure that any values you want to search for are cast as strings prior to saving them so that you can use @sMyles' method instead.

2
  • This should be the selected answer, most reliable Commented Oct 23, 2019 at 16:26
  • 1
    Yes you're absolutely correct (on your edit), and go figure I accidentally did this in my own code base saving as integer instead of string, so always save as string (added an edit in my answer as well)
    – sMyles
    Commented Feb 24, 2022 at 22:07
3

I stuggled with something similar today. I have to query a ACF (Advanced Custom Fields) relationship field with multiple related users (array).

After updating the field via php the query didn't work. After updating it via the ACF UI the query worked.

Problem was, that my php code set the relationship values to be int-values, the UI set it to string-values. To make sure both work I use this query now (fitted to the example here):

$args = array(
    'post_type' => 'news',
    'meta_query' => array(
        'relation' => 'OR',
        array(
            'key' => 'topics',
            'value' => '1;',  // works for int-array
            'compare' => 'LIKE'
        ),
        array(
            'key' => 'topics',
            'value' => '"1"',  // works for string-array
            'compare' => 'LIKE'
        ),
    )
);
2

I would go for Johannes's answer. However, I want to improve that because using that meta_query, you will meet a case like this

your value is

array('sports','movies', 'sports2');

when you search

$args = array(
    'post_type' => 'news',
    'meta_query' => array(
        array(
            'key' => 'topics',
            'value' => 'sports',
            'compare' => 'LIKE'
        )
    )
);

then the result will return both 'sport' and 'sport2'.

To fix that, change the meta_query args into

$args = array(
    'post_type' => 'news',
    'meta_query' => array(
        array(
            'key' => 'topics',
            'value' => 'sports";',
            'compare' => 'LIKE'
        )
    )
);

It's because the value is serialize in database, and each item will be separated by a semicolons. Thus, above args will work

If items in the value are number, you just need to remove the double quote "

$args = array(
        'post_type' => 'news',
        'meta_query' => array(
            array(
                'key' => 'topics',
                'value' => '1;',
                'compare' => 'LIKE'
            )
        )
    );

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.