Question

I have serialized post meta values. It looks like this:

enter image description here

In array:

$kisiArray = array(
   'option1' => 7,
   'option2' => 'bar',
   'option3' => 'apple',
   'option4' => 'orange'
);

And I want to make a custom query with meta_query args. This is my query args:

<?php 
$args = array(
    'order'          => 'DESC',
    'meta_query'     => array(
        array(
            'key'     => 'themeOps_option1',
            'value'   => 6,
            'compare' => '>=',
        )
    )
);
?>

But it doesn't display any result.

Was it helpful?

Solution

serialised array in a meta_value for query

It's not a good idea to save serialised array in a meta_value if you plan to query with any value within that serialized array later. So, the best option is to save meta data as key / value pair.

Query Serialized array

Although it's not a good approach, it's still possible to query a serialized array within meta_value using regular expression.

Warning: Performance wise this is not a good idea at all. So if you have any other way to achieve the same thing, better do that. I'm giving the answer only to show that it's possible. So use it only as a last resort.

Example CODE:

// this array is serialized and saved in meta_value for meta_key 'themeOps'
$serializedArray = array(
    'option1' => 7,
    'option2' => 'bar',
    'option3' => 'apple',
    'option4' => 'orange'
);

// this is the WP_Query argument
$args = array(
    'meta_query' => array(
        array(
            'key'     => 'themeOps',
            // this compares if 'option1' >= 6 within the serialized array in meta_value
            'value'   => wpse311157_serialize_num_greater_equals_regex( 'option1', 6 ),
            'compare' => 'REGEXP',
        )
    )
);

As you can see, I've used 'compare' => 'REGEXP' and used the function wpse311157_serialize_num_greater_equals_regex( 'option1', 6 ) to generate the proper regular expression (first param is the name of the array key and the second param is the number to compare with >=).

Now, let's implement the wpse311157_serialize_num_greater_equals_regex function. Since meta_value is a serialized array, it'll look something like: a:1:{s:3:"key";i:7;}. To match that, our CODE will look like:

function wpse311157_serialize_num_greater_equals_regex( $key, $num ) { 
    return 'a\:[1-9][0-9]*\:\{.*' . preg_quote( serialize( $key ) ) . 'i\:(' . wpse311157_num_greater_equals_regex( $num ) . ');.*\}';
}

Now we need to implement the wpse311157_num_greater_equals_regex( $num ) function by converting >= comparison into regular expression. This is not very efficient, but this is the only option we have.

RegEx algorithm to compare >=

The algorithm is simple:

(A) For any n digit number, any number with (n+1) digits is greater than that number.

(B) Additionally, we need maximum n number of rules to check other n digit numbers that are greater than or equal to this number.

For example, say we want to compare: num >= 12

So, RegEx: [1-9][0-9][0-9]+ will always satisfy it, as it'll match 3 or more digit numbers.

Now, to match 2 digit numbers that are >= 12, we need 2 rules:

  1. 1[2-9] => this will match numbers: 12 to 19
  2. [2-9][0-9] => this will match numbers: 20 to 99

So the final RegEx for num >= 12 is: 1[2-9]|[2-9][0-9]|[1-9][0-9][0-9]+

With this algorithm, let's create our wpse311157_num_greater_equals_regex( $num ) function:

function wpse311157_num_greater_equals_regex( $num ) {
    $digits = wpse311157_num_digits( $num );
    $num_i = $num;
    $regex = '';        
    for( $i = 1; $i <= $digits; $i++ ) { 
        $digit = $num_i % 10; 
        $num_i = (int) ( $num_i / 10 );
        $regex_i = ''; 
        $need_rule = true;
        if( 1 === $i ) { 
            if( 9 === $digit ) { 
                $regex_i = '9';
            }   
            else {
                $regex_i = '[' . $digit . '-9]';
            }   
        }   
        else {
            // no rule for 9
            if( 9 === $digit ) { 
                $need_rule = false;
            }
            else if( 8 === $digit ) {
                $regex_i = '9';
            }
            else {
                $regex_i = '[' . ( $digit + 1 ) . '-9]';
            }
        }

        if( $need_rule ) {
            if( $i < $digits ) {
                $regex_i = $num_i . $regex_i;
            }

            for( $j = 1; $j < $i; $j++ ) {
                $regex_i = $regex_i . '[0-9]';
            }

            if( empty( $regex ) ) {
                $regex = $regex_i;
            }
            else {
                $regex = $regex . '|' . $regex_i;
            }
        }
    }

    $regex = $regex . '|[1-9]';
    for( $i = 1; $i < $digits; $i++ ) {
        $regex = $regex . '[0-9]';
    }
    $regex = $regex . '[0-9]+';

    return $regex;
}

function wpse311157_num_digits( $num ) { 
    // not considering 0 or negative numbers
    if( $num < 1 ) return -1; 

    return floor( log10( $num ) + 1 );
}

That's all, now you'll be able to compare a value with >= within a serialized array.

OTHER TIPS

Outstanding answer from @Fayaz! Thank you! I took the Code and changed it to match the size (count) of a serialized array which is at the beginning of the serialized string like "a:4" = 4 entries in the array. This is what i came up with:

'meta_query' => array(
    'user_sets' => array(
         'key' => 'user_sets',
         'value'   => serialize_num_greater_equals_regex( $_GET['sets'] ),
         'compare' => 'REGEXP'

     )
)

and a simplified function:

function serialize_num_greater_equals_regex( $num ) {
    return 'a\:(' . num_greater_equals_regex( $num ) . ')\:\{s.*';
}

So now you can filter the results by the size of the array! By the way i use the method with "users_list_table_query_args" hook which is used by WP_User_Query class! With the hook you can alter the users.php output.

Licensed under: CC-BY-SA with attribution
Not affiliated with wordpress.stackexchange
scroll top