MySQL 5.7+, JSON_SET value in nested path - php

For a recent development project, we're using MySQL 5.7, so we can take advantages of the latest JSON-functions...
I'm building an UPDATE-query, where an nested json-object should be inserted / added into the attributes-column, of type JSON, see query below.
UPDATE `table` SET `table`.`name` = 'Test',
`table`.`attributes` = JSON_SET(
`table`.`attributes`,
"$.test1", "Test 1",
"$.test2.test3", "Test 3"
)
When I execute this query, the attributes-field contains the data
{"test1": "Test 1"}
instead of the wanted
{"test1", "Test 1", "test2": {"test3", "Test 3"}}
Also tried to use JSON_MERGE, but when I execute it multiple times, it creates an JSON-object like
{"test1": ["Test 1", "Test 1", "Test 1"... etc.], "test2": {"test3": ["Test 3", "Test 3", "Test 3"... etc.]}}
So, JSON_SET isn't working when nodes don't exist? JSON_MERGE merges till infinity?
The keys used in the JSON-object can be defined by the user, so it's not possible to create an empty JSON-object for all possible keys. Do we really need to execute an JSON_CONTAINS / JSON_CONTAINS_PATH query before each UPDATE query to determine if we need to use JSON_SET or JSON_MERGE / JSON_APPEND?
We're looking for a way to have a query which always works, so when "$.test4.test5.test6" is given, it will extend the current JSON-object, adding the full path... How can this be done?

As of MySQL version 5.7.13, assuming you desire an end result of
{"test1": "Test 1", "test2": {"test3": "Test 3"}}
In your example the attributes column that is being updated is set to {"test1": "Test 1"}
Looking at your initial UPDATE query, we can see $.test2.test3 does not exist.
So it can not be set as
JSON_SET() Inserts or updates data in a JSON document and returns the
result. Returns NULL if any argument is NULL or path, if given, does
not locate an object.
Meaning MySQL can add $.test2, but since $.test2 is not an object, MySQL can not add on to $.test2.test3.
So you would need to define $.test2 as a json object by doing the following.
mysql> SELECT * FROM testing;
+----+---------------------+
| id | attributes |
+----+---------------------+
| 1 | {"test1": "Test 1"} |
+----+---------------------+
1 row in set (0.00 sec)
mysql> UPDATE testing
-> SET attributes = JSON_SET(
-> attributes,
-> "$.test1", "Test 1",
-> "$.test2", JSON_OBJECT("test3", "Test 3")
-> );
Query OK, 1 row affected (0.03 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> SELECT * FROM testing;
+----+---------------------------------------------------+
| id | attributes |
+----+---------------------------------------------------+
| 1 | {"test1": "Test 1", "test2": {"test3": "Test 3"}} |
+----+---------------------------------------------------+
1 row in set (0.00 sec)
So instead of relying on the MySQL dot notation, you would need to explicitly tell MySQL that the key exists as a JSON object.
This is similar to how PHP also defines non-existent object property values.
$a = (object) ['test1' => 'Test 1'];
$a->test2->test3 = 'Test 3';
//PHP Warning: Creating default object from empty value
To get rid of the error, you would need to first define $a->test2 as an object.
$a = (object) ['test1' => 'Test 1'];
$a->test2 = (object) ['test3' => 'Test 3'];
Alternatively you could test and create the objects prior to using the dot notation, to set the values. Though with larger datasets this may be undesirable.
mysql> UPDATE testing
-> SET attributes = JSON_SET(
-> attributes, "$.test2", IFNULL(attributes->'$.test2', JSON_OBJECT())
-> ),
-> attributes = JSON_SET(
-> attributes, "$.test4", IFNULL(attributes->'$.test4', JSON_OBJECT())
-> ),
-> attributes = JSON_SET(
-> attributes, "$.test4.test5", IFNULL(attributes->'$.test4.test5', JSON_OBJECT())
-> ),
-> attributes = JSON_SET(
-> attributes, "$.test2.test3", "Test 3"
-> );
Query OK, 1 row affected (0.02 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> SELECT * FROM testing;
+----+---------------------------------------------------------------------------+
| id | attributes |
+----+---------------------------------------------------------------------------+
| 1 | {"test1": "Test 1", "test2": {"test3": "Test 3"}, "test4": {"test5": {}}} |
+----+---------------------------------------------------------------------------+
1 row in set (0.00 sec)
Though in either case if the original data is not provided the JSON_OBJECT function call will empty out the nested object's property value(s). But as you can see from the last JSON_SET query, $.test1 was not provided in the definition of attributes, and it remained intact, so those properties that are unmodified can be omitted from the query.

Now, as of MySQL version 5.7.22 the easiest way is to use JSON_MERGE_PATCH like this:
UPDATE `table` SET `attributes` =
JSON_MERGE_PATCH(`attributes`, '{"test2": {"test3": "Test 3"}, "test4": {"test5": {}}}')
which gives the expected result of {"test1": "Test 1", "test2": {"test3": "Test 3"}, "test4": {"test5": {}}} as in your example.

Fyrye, thanks for the awnser, appreciate it a lot! Because of the data hasn't a fixed structure and can be different for every single record, I needed a solution where I could generate a query which would automatically generate the total JSON-object in a single query.
I really like your solution using the JSON_SET(attributes, "$.test2", IFNULL(attributes->'$.test2',JSON_OBJECT())) method. Because I continued my search, I also figured out a solution myself using JSON_MERGE function.
When i'm executing an update, i'm using JSON_MERGE to merge an empty JSON-object onto the field in the database, for all keys with subnodes, so the're available in the JSON-field in the database and after that, using JSON_SET to update values. So the complete query looks like this:
UPDATE table SET
-> attributes = JSON_MERGE(
-> attributes, '{"test2": {}, "test4": {"test5": {}}}'),
-> attributes = JSON_SET(attributes, "$.test2.test3", "Test 3");
After executing this query, the result will look something like this:
mysql> SELECT * FROM testing;
+----+---------------------------------------------------------------------------+
| id | attributes |
+----+---------------------------------------------------------------------------+
| 1 | {"test1": "Test 1", "test2": {"test3": "Test 3"}, "test4": {"test5": {}}} |
+----+---------------------------------------------------------------------------+
1 row in set (0.00 sec)
I don't know which method is better at this time, both work for now. Will do some speed tests in the future to check how they preform when 1 update 10.000 rows!

After searching everywhere like many of you I've found the best possible solution listed here: https://forums.mysql.com/read.php?20,647956,647969#msg-647969
From the site:
he nodes and subnodes, but doesn't contains any data...
So in above example, the object will be like:
{"nodes": {}}
When executing an update, i'm using JSON_MERGE to merge the empty JSON-object onto the field in the database, so all the nodes / subnodes are available in the JSON-field in te database and after that, using JSON_SET to update values. So the complete query looks like this:
UPDATE table SET attributes = JSON_MERGE(attributes, '{"nodes": {}'), attributes = JSON_SET(attributes, "$.nodes.node2", "Node 2")
For now, this is working.
But it's an weird workaround. Maybe this can be reviewed in previous MySQL versions, so JSON_SET also creates parent-nodes when subnodes are set ?

Related

Query specific text inside json text data in MYSQL

I'd like to query from the reviewed_by table below where the "company" is "AAA" and "review" is "Need Review"
Here's mysql table :
+-----------+
| DATA_TYPE |
+-----------+
| text |
+-----------+
+-------------------------+
| reviewed_by |
+-------------------------+
|[{"company":"AAA","review":"OK","reviewed_at":"2021-01-26 08:59:26"}]|
|[{"company":"BBB","review":"OK","reviewed_at":"2021-01-26 08:59:26"}]|
|[{"company":"AAA","review":"Need Review","reviewed_at":"N\/A"}]|
+-------------------------+
Here's the #1 query i've tried :
SELECT * FROM `t_transaction`
WHERE `reviewed_by`
LIKE '%`"company":"AAA","review":"Need Review"`%'
Here's the #2 query i've tried :
SELECT * FROM `t_transaction`
WHERE `reviewed_by`
LIKE '%"company":"AAA","review":"Need Review"%'
ci3 query :
$like = ['reviewed_by','"company":"AAA","review":"Need Review"'];
$this->db->select('*')
->from('t_transacion')
->group_by('id')
->like($like[0],$like[1]);
The result i've got from those 2 queries was nothing,
How can i do this type of query (and also if using codeigniter 3) ?
MySql has some functions that allow you to do search over a json field. See documentation.
The reviewed_by column is a json array and you want to seach the first element of that array. Using the function JSON_EXTRACT you can extract data from the json field. In your case to get the json in the first position in the array so we execute JSON_EXTRACT(reviewed_by, '$[0]') which will return {"company":"...","review":"..","reviewed_at":"..."}. From the returned json we can call again the JSON_EXTRACT function to get a value given a key. If we select JSON_EXTRACT(JSON_EXTRACT(reviewed_by, '$[0]'), "$.company") this will return the company value from inside the json.
There are different ways to select what you want. I will give you two option and they have pros and cons. Take a look at this stackoverflow.
First approach using the where clause:
SELECT reviewed_by
FROM t_transaction
WHERE JSON_EXTRACT(JSON_EXTRACT(reviewed_by, '$[0]'), "$.company") = "AAA"
AND JSON_EXTRACT(JSON_EXTRACT(reviewed_by, '$[0]'), "$.review") = "Need Review";
Second approach using the having clause:
SELECT JSON_EXTRACT(reviewed_by, '$[0]') AS json
FROM t_transaction
HAVING json -> "$.company" = "AAA"
AND json -> "$.review" = "Need Review";

How do I append MYSQL JSON

I have the following in my database when I run SELECT type FROM office;
+--------------------------------------------------------+
| type |
+--------------------------------------------------------+
| a:2:{i:0;s:16:"Shared Workspace";i:1;s:9:"Workshops";} |
+--------------------------------------------------------+
How can I add an extra JSON value for another type of office. Basically I'd like it to add the type "Private Offices". So the value in my DB will then look like:
+-----------------------------------------------------------------------------------+
| type
+-----------------------------------------------------------------------------------+
| a:3:{i:0;s:16:"Shared Workspace";i:1;s:9:"Workshops";i:2;s:15:"Private Offices";} |
+-----------------------------------------------------------------------------------+
That's not JSON format. It looks like the output of PHP's serialize() function.
There's no SQL function to append values to serialized PHP data. You should fetch it into a PHP application, unserialize() it into a PHP array, then add data to the PHP array and update it back into the database.
Something like the following (some details have been omitted, like the WHERE conditions for the specific row you reference).
<?php
$pdo = new PDO(...);
$typeSerialized = $pdo->query("SELECT type FROM office WHERE ...")->fetchColumn();
$typeArray = unserialize($typeSerialized);
$typeArray[] = "Private Offices";
$typeSerialized = serialize($typeArray);
$stmt = $pdo->prepare("UPDATE office SET type = ? WHERE ...");
$stmt->execute([$typeSerialized]);

How to read geometry data type from MySQL database using PHP

I have a table in MySQL that stores polygons. I can read this back on the command line using the following query:
mysql> SELECT polygonid, AsText(thepolygon) FROM polygons;
+-----------+----------------------------------------------------------------------------------------------------------------+ | polygonid | AsText(thepolygon) |
+-----------+----------------------------------------------------------------------------------------------------------------+ | 1 | POLYGON((36.96318 127.002881,37.96318 127.002881,37.96318
128.002881,36.96318 128.002881,36.96318 127.002881)) | +-----------+----------------------------------------------------------------------------------------------------------------+ 1 row in set, 1 warning (0.02 sec)
When I try to read this in PHP using the same query, polygonid comes back correctly, but thepolygon comes back as empty:
$query = "SELECT polygonid, AsText(thepolygon) FROM polygons";
$result = mysqli_query($con, $query);
while ($row = mysqli_fetch_array($result)) {
var_dump($row['polygonid']);
var_dump($row['thepolygon']);
[...]
results in
string(1) "1" NULL
meaning that 'thepolygon' comes back as NULL, but the 'polygonid' comes back just fine.
If I change the query to
SELECT polygonid, thepolygon FROM polygons
then I do get back binary data:
string(1) "1" string(97)
"�t{I{B#�1�3/�_#�t{I�B#�1�3/�_#�t{I�B#��`#�t{I{B#��`#�t{I{B#�1�3/�_#"
string
It's almost as if astext() does not work.
What am I doing wrong?
Thanks for any input at all!
Looks like it might just be because you've not given the AsText() selection an alias which can be picked up from the PHP array.
If you print out $row you might be able to see that your array does not have a thepolygon key.
Have you tried this?
$query = "SELECT polygonid, AsText(thepolygon) AS thepolygon FROM polygons";
It works on the command line because you're just printing out whatever is selected in the query, but in PHP you're trying to print out array keys - i.e. the name of the fields selected. Your MySQL query does not select a field called thepolygon, so it doesn't exist in the array either.

MySQL/PHP: return two columns as an associative array as key => value pairs [duplicate]

This question already has answers here:
Php PDO want to fetch values from 2 columns and create array (one column value as key another as array)
(2 answers)
Closed 1 year ago.
I apologize if this might have been asked before, or if this isn't even possible, but here is what I am hoping to get from a particular table using MySQL.
Say, I have the following table:
+-------------+------------------+
| property | value |
+-------------+------------------+
| style | unisex |
| color | blue |
| type | small |
+-------------+------------------+
I would like to be able to fetch the two columns property and value as an associative array from MySQL, so the end result would be:
array(
'style' => 'unisex',
'color' => 'blue',
'type' => 'small',
);
Just to provide context so people don't misinterpret what I'm asking:
I already know how I can do this using PHP once I have the result back from a generic SQL statement that would return each row's columns as key => value pairs. I was just wondering if it was possible to return one column's value as a key and the second value as the value to the key for each row from MySQL directly to continue with in PHP.
There is no out of the box fetch mode that will give you an array indexed like that (how would it know which is the key? what if there's more than 2 columns?).
You can use PDO::FETCH_NUM and array_map() to build a new array, using the first column as key and the second as value:
<?php
$query = $db->prepare("SELECT property, value FROM table");
$query->execute();
$row = $query->fetchAll(PDO::FETCH_NUM);
$rowFormatted = array_map(function ($el) {
return [$el[0] => $el[1]];
}, $row);
var_dump($rowFormatted);
Sidenote: it looks like you're heading into an Entity-Attribute-Value antipattern. This will bring you problems along the road, so consider redesigning your schema. Take a look at this resources to learn more:
https://www.slideshare.net/billkarwin/extensible-data-modeling
https://martinfowler.com/bliki/UserDefinedField.html
http://karwin.blogspot.cl/2009/05/eav-fail.html
Old question, but thought I'd at least provide a MySQL only solution since that is what was asked for:
$stmt = $db->prepare('SELECT `property`, `value` FROM `table` WHERE 1');
$stmt->execute();
$results = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
$results will be formatted as you're expecting - an array with property as the key and value as the value.
Resource: https://www.php.net/manual/en/pdo.constants.php

MySQLi_Connect cannot update field for multiple rows of sql table at same time

In my php code, I am trying to update all rows of an sql database table that have a particular value for 2 different fields/columns. When I run the code, the updates are not being made to the sql table.
Assume I have a database called "databasename" with a table called "Pets" with these columns: "Cat" (varchar), "Dog" (varchar) & "Favorite" (boolean). I want to mark all rows that have Cat = Sylvester & Dog = Clifford with a value of 1 in the favorite column.
Here is my code:
<?php
$connect = mysqli_connect("localhost","root","","databasename");
$dog='Clifford';
$cat='Sylvester';
$query="SET sql_safe_updates=0";
$query.="UPDATE Pets SET Favorite = 1 WHERE Dog= $dog AND Cat = $cat";
mysqli_multi_query($connect,$query);
?>
If you print out the query string, you'll see that it is:
$query="SET sql_safe_updates=0UPDATE Pets SET Favorite = 1 WHERE Dog= $dog AND Cat = $cat";
I'm not surprised this doesn't work. Try just starting the query with update; there is no reason for the sql safe updates mode.

Categories