简体   繁体   中英

Laravel Eloquent Search in JSON

I'm having an issue with searching in a JSON column. Laravel 8.x and MySQL 5.7.x

Given the following column 'industry_focus' , I'm trying to match and return all records containing any of the values

["1","2","3","4","5","6","7","8"]

My query looks like this:

$related = Broker::query()
->where('broker_name', '!=', null)
->where('is_active', '=', true)
->where('state', '=', $state->code);

foreach ($same_industries as $key => $value) {
    if ($key === 0) {
        $related->whereJsonContains('industry_focus', (string) $value->id);
    } else {
        $related->orWhereJsonContains('industry_focus', (string) $value->id);
    }
}

$related = $related
->limit(10)
->get();

And it produces a raw query looking like this:

SELECT *
FROM `brokers`
WHERE `broker_name` IS NOT NULL
    AND `is_active` = 1
    AND `state` = 'NY'
    AND json_contains(`industry_focus`, '\"2\"')
    OR json_contains(`industry_focus`, '\"3\"')
    OR json_contains(`industry_focus`, '\"4\"')
    OR json_contains(`industry_focus`, '\"5\"')
    OR json_contains(`industry_focus`, '\"6\"')
    OR json_contains(`industry_focus`, '\"7\"')
    OR json_contains(`industry_focus`, '\"8\"')
    OR json_contains(`industry_focus`, '\"9\"')
    OR json_contains(`industry_focus`, '\"10\"')
    OR json_contains(`industry_focus`, '\"11\"')
    OR json_contains(`industry_focus`, '\"12\"')
    OR json_contains(`industry_focus`, '\"13\"')
    OR json_contains(`industry_focus`, '\"14\"')
    OR json_contains(`industry_focus`, '\"15\"')
    OR json_contains(`industry_focus`, '\"16\"')
limit 10

Unfortunately, this is wrong and is returning all records ignoring any and all conditions. I think there's a parenthesis missing before the first AND json_contains and a closing one at the end? Is this a bug, or am I missing something. Is there a better way to write this?

EDIT: Ok, so I managed to fix this myself somehow looking at examples elsewhere. This is what works for me:

$related = Broker::query()
    ->where('broker_name', '!=', null)
    ->where('is_active', '=', true)
    ->where('state', '=', $state->code);

$related->where(function ($query) use ($same_industries) {
    foreach ($same_industries as $key => $value) {
        if ($key === 0) {
            $query->whereJsonContains('industry_focus', (string) $value->id);
        } else {
            $query->orWhereJsonContains('industry_focus', (string) $value->id);
        }
    }
});

$related = $related
    ->limit(10)
    ->get();

First of all, you need to review boolean algebra. There's an operator precedence between AND and OR that is biting you in this case.

A AND B OR C OR D

Is equivalent to

(A AND B) OR C OR D

But what you need is:

A AND (B OR C OR D)

To get this in a boolean expression, you need to use the parentheses explicitly to override the natural operator precedence.

What else would make this query easier is what I tell many people who are using JSON in MySQL:

Don't use JSON in MySQL if you mean to use the elements inside the JSON document as discrete data elements, and search for them with SQL expressions.

In this case, you have a multi-valued attribute industry_focus . The normal way to implement a multi-valued attribute is to make a second table, and store one value per row.

CREATE TABLE broker_industry_focus (
  broker_id INT NOT NULL,
  industry_focus INT NOT NULL,
  PRIMARY KEY (broker_id, industry_focus)
);
INSERT INTO broker_industry_focus VALUES (123, 7), (123, 11);

Once you do that you can search more easily:

SELECT *
FROM brokers AS b
JOIN broker_industry_focus AS f 
    ON b.id = f.broker_id
WHERE b.broker_name IS NOT NULL
    AND b.is_active = 1
    AND b.state = 'NY'
    AND f.industry_focus IN (1,2,3,4,5,6,7,8);

You might also like my presentation How to Use JSON in MySQL Wrong .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM