JSON_CONTAINS string everywhere in the structure - php

I have a JSON column in my MySQL database (5.7.25) that can contain any type of JSON structure (so I do not have any property that is common to all the rows)
How can I perform a query through laravel ELOQUENT in order to search rows that match a string in one of the property of the JSON column?
I tried with this:
JSON_CONTAINS(`payload`, "key")
But mySQL throws the following exception:
Invalid JSON text in argument 2 to function json_contains: "Invalid value." at position 0
For example if the structure is similar to this:
{first_name:"John", "last_name":"Smith", "company":"Demo Company"}
I would like to search for all the rows that contain the word "Demo" in any of the properties (Also if the string is a substring of the json property value).
The only working way I found was the following:
JSON_SEARCH(payload, 'one', '%Demo%') IS NOT NULL
But it seems to be a bit slow. So I am asking if there's a faster way

The post title is different from the actual question, but for people who are wondering how to search inside anywhere in JSON, you could use simply a LIKE.
Given a json:
{
"rule": 4,
"triggers": {
"then": {
"field": "Sessions_Remaining__c",
...
},
"when": ""
}
}
You wont get any value by using:
SELECT * WHERE JSON_CONTAINS(json,'"Sessions_Remaining__c"')
So you can use instead:
SELECT * WHERE json LIKE '%"Sessions_Remaining__c"%';

Related

Laravel Backpack: coordinates field

I have a field named map_box which contains coordinates (see the example below) and cannot be serve as string.
Previously, before Backpack, I use to cast the field to an array 'map_box' => 'array' and the result was the following: note that the coordinates are not string
[
-73.661,
45.589
],
Now, I am trying to achieve the same result, but I keep getting an array of strings. I have tried using the repeatable field with numbers field in it, but the numbers are string. I have also tried to cast the field, then use the text field but this returns an error (as the form is expecting a string and not an array).
My goal is to be able to edit this field in a CRUD controller while being able to serve them with the correct format in my API. Any ideas on how I could achieve the same result as above? I can also use the following format, if this one is possible:
{
lon: -73.661,
lat: 45.589
}
Thanks!
i am not sure if i understand you correctly ... what backpack has to do with this casting?
however if you have something like:
$value='"max_box":{lon: -73.661, lat: 45.589}';
or any string and you want to get float number positive or negative you can use this:
preg_match_all('/-?\d+\.\d+/', $value, $matches);
the $matches[0] will be an array of your numbers

Making a Laravel 5.4 query on a JSON field containing a JSON array

i'm trying to query a JSON field containing an array of values.
For exemple sake we'll name the table "User" and the field "Friends". Here's how the Friends field looks like :
[{
"id": 1,
"img": "img-1.jpg",
"name": "Name 1"
},
{
"id": 2,
"img": "img-2.jpg",
"name": "Name 2"
},
{
"id": 3,
"img": "img-3",
"name": "Name 3"
}]
So what I would like to do is on the User table query everything from the Friends field where there is an id equals to 3.
So something like : User::where('friends->id', 3)->orderBy('id', 'desc')->get();
Of course, the exemple above works perfectly if the field did not contain an array, so if it was just :
{
"id": 1,
"img": "img-1.jpg",
"name": "Name 1"
}
Desperate, and even though I know it's not very logical, I have tried with "whereIn" : User::whereIn('friends->id', [3])->get(). Or stuff like : User::where('friends->[0]->id', 3)->get(), User::where('friends->[*]->id', 3)->get(), User::where('friends->*->id', 3)->get().
I have also tried with JSON_CONTAINS or JSON_SEARCH : User::whereRaw('JSON_CONTAINS(friends->"$.id", "3")')->get() and many different variants but nothing does it.
Before coming here I have read a few interesting articles on the matter (they are listed bellow), but I seem to be the only one who have ever stored a JSON array in a MySQL database, how is that possible ? ^^
https://mattstauffer.com/blog/new-json-column-where-and-update-syntax-in-laravel-5-3/
https://themsaid.com/laravel-mysql-json-colum-fast-lookup-20160709
http://www.qcode.in/use-mysql-json-field-in-laravel/
So if anyone could help me solve this problem I would really appreciate it.
Side notes : my current MySQL version is 5.7.11, so it does support JSON fields and Laravel doesn't throw any errors, it just returns an empty array.
Your whereRaw attempt is very close. If you were storing a single object, your path would be $.id. However, since you're storing an array of objects, your path is $[*].id. This should work for you:
User::whereRaw('JSON_CONTAINS(friends->"$[*].id", "3")')->get();
The friends->"$[*].id" selector (which is just a shortcut for JSON_EXTRACT()) will return a json array of the ids. JSON_CONTAINS() will then check if that json array contains the specified id.
Another option would be to build a json search string to use for JSON_CONTAINS(). For example, this query should also work:
User::whereRaw('JSON_CONTAINS(friends, \'{"id": 3}\')')->get();
This avoids the first call to JSON_EXTRACT(), so you're only calling one json method. I do not know which version would actually be faster, or if there would be any difference.
Also, on a side note related to JSON_SEARCH(), this function will only work if you are searching for string values. Since your json shows that the ids are represented as integers instead of strings, JSON_SEARCH() will not work. MySQL claims this is intended behavior (bug 79233 and dup bug 79316).
Just for reference, here is the documentation for the json search methods.

Select, where JSON Array contains

So in Laravel 5 there's the handy thing called JSON Where Clauses using MySQL's new ability to store and fetch JSON stored in a column:
User::where('meta->colors', 'red')->get()
would return all rows, where colors in the column meta would be set to red.
Now let's say colors is not a string, but an array containing multiple colors (colors => ['red', 'blue', 'green']).
What would be an efficient way to retrieve all rows, where colors contains e.g. the value red?
JSON_CONTAINS() does exactly what you're looking for:
JSON_CONTAINS(target, candidate[, path])
Indicates by returning 1 or 0 whether a given candidate JSON document is contained within a target JSON document, or—if a path argument was supplied—whether the candidate is found at a specific path within the target. — 12.16.3 Functions That Search JSON Values
Currently, Laravel's query builder does not provide a corresponding API. There's an open internals proposal for it though.
In the meantime, you can execute a raw query:
\DB::table('users')->whereRaw(
'JSON_CONTAINS(meta->"$.colors", \'["red"]\')'
)->get();
Which would return all users that have "red" in their meta->colors JSON field. Note that the -> operator requires MySQL 5.7.9+.
You can also call the whereRaw() directly on an Eloquent model.
Laravel 5.6
As of the 5.6 release, Laravel's query builder contains a new whereJsonContains method.
I think a way would be using the like operator:
User::where('meta->colors', 'like', '%"red"%')
However, this would only work if the values never contain the character " and the delimiters wouldn't change.
An update for this answer, according to MySQL or MariaDb, the correct syntax must be JSON_CONTAINS(#json, 'red', '$.colors'), and is necessary to use JSON_EXTRACT.
So them, the code inside Laravel (for version 5.5 or less).
Like say #Elwin, meta column must contains the following JSON: { "colors": ["red", "blue", "green"] }
User::whereRaw("JSON_CONTAINS(JSON_EXTRACT(meta, '$.colors'), '\"{$color}\"')")
Remember to use double quotes in value sentence.
JSON_CONTAINS(JSON_EXTRACT(meta, '$.colors'), '"red"')
The whereIn method verifies that a given column's value is contained within the given array.
Try this:
$colorArray = ['red', 'blue', 'green'];
$user = User::whereIn($meta->color, $colorArray)->get();
More about Laravel's whereIn.

Select condition within a hash column using Doctrine mongoDB ODM query builder

I have the following structure within a mongoDB collection:
{
"_id" : ObjectId("5301d337fa46346a048b4567"),
"delivery_attempts" : {
"0" : {
"live_feed_id" : 107,
"remaining_attempts" : 2,
"delivered" : false,
"determined_status" : null,
"date" : 1392628536
}
}
}
// > db.lead.find({}, {delivery_attempts:1}).pretty();
I'm trying to select any data from that collection where remaining_attempts are greater than 0 and a live_feed_id is equal to 107. Note that the "delivery_attempts" field is of a type hash.
I've tried using an addAnd within an elemMatch (not sure if this is the correct way to achieve this).
$qb = $this->dm->createQueryBuilder($this->getDocumentName());
$qb->expr()->field('delivery_attempts')
->elemMatch(
$qb->expr()
->field('remaining_attempts')->gt(0)
->addAnd($qb->expr()->field('live_feed_id')->equals(107))
);
I do appear to be getting the record detailed above. However, changing the greater than
test to 3
->field('remaining_attempts')->gt(3)
still returns the record (which is incorrect). Is there a way to achieve this?
EDIT: I've updated the delivery_attempts field type from a "Hash" to a "Collection". This shows the data being stored as an array rather than an object:
"delivery_attempts" : [
{
"live_feed_id" : 107,
"remaining_attempts" : 2,
"delivered" : false,
"determined_status" : null,
"date" : 1392648433
}
]
However, the original issue still applies.
You can use a dot notation to reference elements within a collection.
$qb->field('delivery_attempts.remaining_attempts')->gt(0)
->field('delivery_attempts.live_feed_id')->equals(107);
It works fine for me if I run the query on mongo.
db.testQ.find({"delivery_attempts.remaining_attempts" : {"$gt" : 0}, "delivery_attempts.live_feed_id" : 107}).pretty()
so it seems something wrong with your PHP query, I suggest running profiler to see which query is actually run against mongo
db.setProfilingLevel(2)
This will log all operation since you enable profiling. Then you can query the log to see which the actual queries
db.system.profile.find().pretty()
This might help you to find the culprit.
It sounds like your solved your first problem, which was using the Hash type mapping (instead for storing BSON objects, or associative arrays in PHP) instead of the Collection mapping (intended for real arrays); however, the query criteria in the answer you submitted still seems incorrect.
$qb->field('delivery_attempts.remaining_attempts')->gt(0)
->field('delivery_attempts.live_feed_id')->equals(107);
You said in your original question:
I'm trying to select any data from that collection where remaining_attempts are greater than 0 and a live_feed_id is equal to 107.
I assume you'd like that criteria to be satisfied by a single element within the delivery_attempts array. If that's correct, the criteria you specified above may match more than you expect, since delivery_attempts.remaining_attempts can refer to any element in the array, as can the live_feed_id criteria. You'll want to use $elemMatch to restrict the field criteria to a single array element.
I see you were using elemMatch() in your original question, but the syntax looked a bit odd. There should be no need to use addAnd() (i.e. an $and operator) unless you were attempting to apply two query operators to the same field name. Simply add extra field() calls to the same query expression you're using for the elemMatch() method. One example of this from ODM's test suite is QueryTest::testElemMatch(). You can also use the debug() method on the query to see the raw MongoDB query object created by ODM's query builder.

Manipulate/Edit a json encoded string value in a database row

I'm working with laravel 4 and the eloquent implementation.
I want to edit/manipulate a value in a database field which contains a json encoded string.
Example: I have a database row with the name "meta". The value of "meta" is a json encoded string.
The example json encoded string:
{"name":"steven","lastname":"builder"}
How can I manipulate the value of that json string?
For example "name" ?
I've found mutators but I dont know how to work with them.
http://laravel.com/docs/eloquent#accessors-and-mutators
This works like most ORM's in that you simply make the change like you would to a normal object's properties, and just save the changes. In this example:
You've already got who you want to update, so we'll just call that object $meta, and you've already manipulated your json, so we'll call it $manip_json, and we'll say the column name is just meta_col
$meta->meta_col = $manip_json;
$meta->save();

Categories