3

So, I want to query the notifications table in Laravel by comparing a certain ID with the data column. This is how data column looks like:

{
    "Message": "some message",
    "id": 3
}

Now, I need to select all the notifications that have an ID that is equal to 3. Here is how I was trying to do it:

DB::table('notifications')->where('data->id', '3')->get();

But this throws the following error:

SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '>'$."id"' = ?' at line 1 (SQL: select * from notifications where data->'$."id"' = 3)

I am losing my mind here, can anyone help me out?

sepehr
  • 17,110
  • 7
  • 81
  • 119
Chilipepper
  • 182
  • 1
  • 12
  • 1
    Laravel stores JSON as text, so you can't query it this way. You need change the way that you store this information in order to query it, otherwise you will need to do a fulltext search like `data LIKE '%"id": 3%'`, but this kind of queries are very slow. – Elias Soares Sep 18 '17 at 14:55
  • Have a look at this: https://stackoverflow.com/questions/44669673/laravel-eloquent-search-in-json-values-of-database – Dov Benyomin Sohacheski Sep 18 '17 at 15:03

4 Answers4

8

There's nothing wrong with your query. It's your environment.

Problem

Laravel's MySqlGrammar translates the field->key notation in field names (on Laravel side) into field->'$.key'-style extractions (on MySQL side):

/**
 * Wrap the given JSON selector.
 *
 * @param  string  $value
 * @return string
 */
protected function wrapJsonSelector($value)
{
    $path = explode('->', $value);

    $field = $this->wrapValue(array_shift($path));

    $path = collect($path)->map(function ($part) {
        return '"'.$part.'"';
    })->implode('.');

    // Here:
    return sprintf('%s->\'$.%s\'', $field, $path);
}

I just confirmed that MariaDB does not support the -> extraction operator as an alias to the JSON_EXTRACT() function. However, the same query works against a vanilla MySQL 5.7 server.

Assuming this test table:

╔════╤══════════════════╗
║ id │ payload          ║
╟────┼──────────────────╢
║  1 │ {"a": 1, "b": 2} ║
╚════╧══════════════════╝

A query that uses the -> extraction operator:

SELECT payload->"$.b" FROM test;

fails against MariaDB 10.2.8 while it yields a correct 2 against a MySQL 5.7.19 server.

Solutions

The right solution depends on what you're using on production.

Replace MariaDB

If you're using MySQL, replace MariaDB with MySQL in your development env. On a macOS machine managed by homebrew, it'd be as easy as:

brew services stop mysql
brew uninstall mariadb
brew install mysql
brew services start mysql

your data will remain intact.

Rewrite your queries

However, if you're using MariaDB in production, you need to rewrite your queries to use JSON_EXTRACT() function as Elias already mentioned. As you can see you need to be much more verbose with the Laravel API.

The above query would be:

SELECT JSON_EXTRACT(payload, "$.b") FROM test;
sepehr
  • 17,110
  • 7
  • 81
  • 119
3

The problem is that only MySQL supports the -> operator, then the query will fail on MariaDB, but:

Both MariaDB and MySQL supports the JSON_EXTRACT function, that parses the JSON and get a value from it.

You can solve it by doing a query like this:

SELECT * FROM notifications WHERE JSON_EXTRACT(`notifications.data`, "$.id") = 5

To do it with Laravel's Query Builder, you'll need to use DB::raw method:

DB::table('notifications')->where(DB::raw('JSON_EXTRACT(`notifications.data`, "$.id")'), '=', 3);

Please try it and tell me if go wrong.

Note: The JSON_EXTRACT() is only available on MySQL >= 5.7 or MariaDB >= 10.2.3

Thanks to @Sepehr, I didn't know that the -> was an MySQL Operator, neither that JSON type existed. :-)

Elias Soares
  • 9,884
  • 4
  • 29
  • 59
  • 1
    Even though your answer works "around" the problem, it does not answer the question. Your statement that Laravel stores JSON as a string [is false](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Database/Schema/Blueprint.php#L695-L698), it's a JSON-typed field, not a string-typed one. Besides that, since Laravel 5.3, you can target JSON-typed field keys [using the `->` notation](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php#L235-L246) instead of using `JSON_EXTRACT` in raw queries that you've suggested. – sepehr Sep 19 '17 at 02:08
  • You're still not answering the actual problem. Both the [JSON type](https://dev.mysql.com/doc/refman/5.7/en/json.html) and the [`->` operator](https://dev.mysql.com/doc/refman/5.7/en/json-search-functions.html#operator_json-column-path) exist. But you are right on the storage type; Laravel's JSON-typed fields are getting created as `longtext`-typed fields and are strings. But you should still be able to query them using the arrow notation without syntax errors. It's in the [docs](https://laravel.com/docs/5.3/queries#json-where-clauses). – sepehr Sep 19 '17 at 02:43
  • The actual problem is that the [`->` extraction operator](https://dev.mysql.com/doc/refman/5.7/en/json-search-functions.html#operator_json-column-path) that gets [built by Laravel's MySQL grammar](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php#L235-L246) throws that syntax error when executed against MariaDB. – sepehr Sep 19 '17 at 02:43
  • I just confirmed this, MariaDB does not support the `->`. However the same works against a MySQL installation. – sepehr Sep 19 '17 at 04:10
  • @sepehr, so as I said on my answer, the JSON_EXTRACT solves the problem, right? :-) – Elias Soares Sep 19 '17 at 12:33
  • Interesting! Thank you both for your input. I gave up at some point and just re did my query in a different way. Everything is working fine now. Btw, I tried the JSON_EXTRACT method but it didn't work either. It was throwing an error that the method does not exist. – Chilipepper Sep 20 '17 at 19:49
  • @EliasSoares As I said, your initial input was not answering the question, it was a workaround without even mentioning the root cause. But now that you have edited your answer [and deleted your comments], it makes perfect sense – sepehr Sep 20 '17 at 21:01
  • @Chiipepper Your issue with `JSON_EXTRACT` must have been a version issue with your installation of MariaDB. Elias has mentioned the version requirement above, check whether you're meeting that, `mysql --version`. – sepehr Sep 20 '17 at 21:45
  • Yep, that's correct, the version was quite out of date. – Chilipepper Sep 21 '17 at 17:19
3

I made Laravel MariaDB driver for json support. Get it here: ybr-nx/laravel-mariadb

ybr-nx
  • 121
  • 1
  • 1
0

The issue is with the ->, i think you have a var called $data with some collection in it. If that so then the correct way is:

DB::table('notifications')->where($data->id, '3')->get();

Or if you have a Model related to notifications table then:

Notification::where($data->id, '3')->get();

If the Model is called Notification (following Eloquent convention)

But if you tried to find all ID's equals 3 then just:

DB::table('notifications')->where('id', '3')->get();
  • That `->` notation in OP's question is a [special Laravel notation](https://github.com/laravel/framework/blob/5.3/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php#L254-L257) to access JSON fields' keys. It will be eventually translated to MySQL's corresponding [operator](https://dev.mysql.com/doc/refman/5.7/en/json-search-functions.html#operator_json-column-path). Having said that, your answer is totally irrelevant to the problem. Welcome to SO though. – sepehr Sep 19 '17 at 02:03
  • I do understand about JSON I guess I misinterpreted the question – David Garay Sep 19 '17 at 16:28