
{"id":2465,"date":"2018-07-23T13:13:57","date_gmt":"2018-07-23T12:13:57","guid":{"rendered":"http:\/\/dasini.net\/blog\/?p=2465"},"modified":"2019-04-02T10:10:45","modified_gmt":"2019-04-02T09:10:45","slug":"30-mins-with-mysql-json-functions","status":"publish","type":"post","link":"https:\/\/dasini.net\/blog\/2018\/07\/23\/30-mins-with-mysql-json-functions\/","title":{"rendered":"30 mins with MySQL JSON functions"},"content":{"rendered":"<p><em><a href=\"http:\/\/dasini.net\/blog\/2018\/07\/23\/30-mins-avec-les-fonctions-json-de-mysql\/\">Lire cet article en fran\u00e7ais<\/a><\/em><\/p>\n<p><em><span style=\"text-decoration: underline;\">Note<\/span>: You may also be interested by\u00a0<a href=\"http:\/\/dasini.net\/blog\/2015\/11\/17\/30-mins-with-json-in-mysql\/\" target=\"_blank\" rel=\"noopener noreferrer\">30 mins with JSON in MySQL<\/a><\/em><\/p>\n<p><span style=\"text-decoration: underline;\"><em>Note<\/em><\/span> 2: Handling JSON documents could be also done with <a href=\"http:\/\/dasini.net\/blog\/2019\/04\/02\/mysql-json-document-store\/\" target=\"_blank\" rel=\"noopener noreferrer\">MySQL Document Store<\/a>.<\/p>\n<p><a href=\"https:\/\/www.json.org\" target=\"_blank\" rel=\"noopener noreferrer\">JSON<\/a> (JavaScript Object Notation) is a popular way for moving data between various systems, including databases.\u00a0 Starting with 5.7\u00a0MySQL supports a native JSON data type (internal binary format for efficiency) and a set of built-in JSON functions that allows you to\u00a0perform operations on JSON documents.<\/p>\n<p>This blog post is not a complete overview of the entire MySQL JSON functions set (<a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/json-functions.html\" target=\"_blank\" rel=\"noopener noreferrer\">RTFM instead<\/a>) but rather an <span style=\"text-decoration: underline;\">arbitrary<\/span> presentation of some of them.<\/p>\n<p><em><span style=\"text-decoration: underline;\">Note<\/span>: MySQL 8 enables\u00a0an alternative way of working with <a href=\"https:\/\/www.mysql.com\/products\/enterprise\/document_store.html\" target=\"_blank\" rel=\"noopener noreferrer\">MySQL as a document store<\/a>. (Not cover in this blog post).<\/em><\/p>\n<p>I&rsquo;m using <strong>MySQL 8.0<\/strong>.11, downloadable <a href=\"https:\/\/dev.mysql.com\/downloads\/mysql\/\" target=\"_blank\" rel=\"noopener noreferrer\">here<\/a>.<\/p>\n<p><a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/json.html\" target=\"_blank\" rel=\"noopener noreferrer\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" title=\"JSON logo\" src=\"https:\/\/i0.wp.com\/dasini.net\/blog\/wp-content\/uploads\/json_icon.png?resize=200%2C106\" alt=\"MySQL native JSON data type\" width=\"200\" height=\"106\" \/><\/a><\/p>\n<h2>JSON Utility Functions<\/h2>\n<h3>JSON_PRETTY<\/h3>\n<p>Improve readability with <a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/json-utility-functions.html#function_json-pretty\" target=\"_blank\" rel=\"noopener noreferrer\">JSON_PRETTY<\/a><\/p>\n<p>By default, display a JSON document in MySQL looks like something like this :<\/p>\n<pre class=\"lang:mysql decode:true\" title=\"Display a JSON document\">SELECT doc FROM restaurants LIMIT 1\\G\n*************************** 1. row ***************************\ndoc: {\"_id\": \"564b3259666906a86ea90a99\", \"name\": \"Dj Reynolds Pub And Restaurant\", \"grades\": [{\"date\": {\"$date\": 1409961600000}, \"grade\": \"A\", \"score\": 2}, {\"date\": {\"$date\": 1374451200000}, \"grade\": \"A\", \"score\": 11}, {\"date\": {\"$date\": 1343692800000}, \"grade\": \"A\", \"score\": 12}, {\"date\": {\"$date\": 1325116800000}, \"grade\": \"A\", \"score\": 12}], \"address\": {\"coord\": [-73.98513559999999, 40.7676919], \"street\": \"West   57 Street\", \"zipcode\": \"10019\", \"building\": \"351\"}, \"borough\": \"Manhattan\", \"cuisine\": \"Irish\", \"restaurant_id\": \"30191841\"}<\/pre>\n<p>You can have a prettier display with <strong>JSON_PRETTY<\/strong> :<\/p>\n<pre class=\"lang:mysql decode:true\" title=\"Pretty display a JSON document with JSON_PRETTY\">SELECT JSON_PRETTY(doc) FROM restaurants LIMIT 1\\G\n*************************** 1. row ***************************\nJSON_PRETTY(doc): {\n  \"_id\": \"564b3259666906a86ea90a99\",\n  \"name\": \"Dj Reynolds Pub And Restaurant\",\n  \"grades\": [\n    {\n      \"date\": {\n        \"$date\": 1409961600000\n      },\n      \"grade\": \"A\",\n      \"score\": 2\n    },\n    {\n      \"date\": {\n        \"$date\": 1374451200000\n      },\n      \"grade\": \"A\",\n      \"score\": 11\n    },\n    {\n      \"date\": {\n        \"$date\": 1343692800000\n      },\n      \"grade\": \"A\",\n      \"score\": 12\n    },\n    {\n      \"date\": {\n        \"$date\": 1325116800000\n      },\n      \"grade\": \"A\",\n      \"score\": 12\n    }\n  ],\n  \"address\": {\n    \"coord\": [\n      -73.98513559999999,\n      40.7676919\n    ],\n    \"street\": \"West   57 Street\",\n    \"zipcode\": \"10019\",\n    \"building\": \"351\"\n  },\n  \"borough\": \"Manhattan\",\n  \"cuisine\": \"Irish\",\n  \"restaurant_id\": \"30191841\"\n}<\/pre>\n<h3>JSON_STORAGE_SIZE<\/h3>\n<p>Return the number of bytes used to store the binary representation of a JSON document with\u00a0<a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/json-utility-functions.html#function_json-storage-size\" target=\"_blank\" rel=\"noopener noreferrer\">JSON_STORAGE_SIZE<\/a>.<\/p>\n<pre class=\"lang:mysql decode:true\" title=\"Document size with JSON_STORAGE_SIZE\">SELECT max(JSON_STORAGE_SIZE(doc)) FROM restaurants;\n+-----------------------------+\n| max(JSON_STORAGE_SIZE(doc)) |\n+-----------------------------+\n|                         916 |\n+-----------------------------+\n\nSELECT avg(JSON_STORAGE_SIZE(doc)) FROM restaurants;\n+-----------------------------+\n| avg(JSON_STORAGE_SIZE(doc)) |\n+-----------------------------+\n|                    537.2814 |\n+-----------------------------+\n\nSELECT min(JSON_STORAGE_SIZE(doc)) FROM restaurants;\n+-----------------------------+\n| min(JSON_STORAGE_SIZE(doc)) |\n+-----------------------------+\n|                         255 |\n+-----------------------------+<\/pre>\n<p>In this collection, the heavier document is 916 bytes, the lighter is 255 and the average size is 537.2814<\/p>\n<p><em><span style=\"text-decoration: underline;\">Note<\/span>:\u00a0This is the space used to store the JSON document as it was inserted into the column, prior to any <a href=\"https:\/\/mysqlserverteam.com\/partial-update-of-json-values\/\" target=\"_blank\" rel=\"noopener noreferrer\">partial updates<\/a> that may have been performed on it afterwards.<\/em><\/p>\n<h2>Functions That Search JSON Values<\/h2>\n<h3>JSON_EXTRACT (-&gt;) \/ JSON_UNQUOTE \/ -&gt;&gt; operator<\/h3>\n<p><a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/json-search-functions.html#function_json-extract\" target=\"_blank\" rel=\"noopener noreferrer\">JSON_EXTRACT<\/a> (or <a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/json-search-functions.html#operator_json-column-path\" target=\"_blank\" rel=\"noopener noreferrer\">-&gt;<\/a>) returns data from a JSON document.<\/p>\n<p><a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/json-modification-functions.html#function_json-unquote\" target=\"_blank\" rel=\"noopener noreferrer\">JSON_UNQUOTE<\/a> unquotes JSON value and returns the result as a utf8mb4 string.<\/p>\n<p><a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/json-search-functions.html#operator_json-inline-path\" target=\"_blank\" rel=\"noopener noreferrer\">-&gt;&gt;<\/a> the JSON unquoting extraction operator is a shortcut for JSON_UNQUOTE(JSON_EXTRACT())<\/p>\n<pre class=\"lang:mysql decode:true \" title=\"Returns data from a JSON document with JSON_EXTRACT\">SELECT JSON_EXTRACT(doc, \"$.cuisine\") FROM restaurants LIMIT 1\\G\n*************************** 1. row ***************************\nJSON_EXTRACT(doc, \"$.cuisine\"): \"Irish\"\n\n\nSELECT doc-&gt;\"$.cuisine\" FROM restaurants LIMIT 1\\G\n*************************** 1. row ***************************\ndoc-&gt;\"$.cuisine\": \"Irish\"<\/pre>\n<p>Both queries above are similar.<\/p>\n<p>If you want the same result but without quotes use\u00a0<strong>-&gt;&gt;<\/strong> or <strong>JSON_UNQUOTE<\/strong>(<strong>JSON_EXTRACT<\/strong>()) :<\/p>\n<pre class=\"lang:mysql decode:true\" title=\"unquotes the extracted result with JSON_UNQUOTE &amp; JSON_EXTRACT\">SELECT JSON_UNQUOTE(JSON_EXTRACT(doc, \"$.cuisine\")) FROM restaurants LIMIT 1\\G\n*************************** 1. row ***************************\nJSON_UNQUOTE(JSON_EXTRACT(doc, \"$.cuisine\")): Irish\n\n\nSELECT doc-&gt;&gt;\"$.cuisine\" FROM restaurants LIMIT 1\\G\ndoc-&gt;&gt;\"$.cuisine\": Irish<\/pre>\n<p>Both queries above are similar.<\/p>\n<h3>JSON_CONTAINS<\/h3>\n<p>Search whether the value of specified key matches a specified value with\u00a0<a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/json-search-functions.html#function_json-contains\" target=\"_blank\" rel=\"noopener noreferrer\">JSON_CONTAINS<\/a>.<\/p>\n<pre class=\"lang:mysql decode:true \" title=\"Whether a document contains Creole cuisine with JSON_CONTAINS\">SELECT count(*) \nFROM restaurants \nWHERE JSON_CONTAINS(doc, '\"Creole\"', '$.cuisine');\n+----------+\n| count(*) |\n+----------+\n|       24 |\n+----------+\n\n\nSELECT doc-&gt;&gt;\"$.name\" \nFROM restaurants \nWHERE JSON_CONTAINS(doc, '\"Creole\"', '$.cuisine');\n+-----------------------------------------------+\n| doc-&gt;&gt;\"$.name\"                                |\n+-----------------------------------------------+\n| Belvedere Restaurant                          |\n| Chez Macoule Restaurant                       |\n| Paradise Venus Restaurant                     |\n| Heavenly Fritaille Restaurant                 |\n| Yolie'S Bar &amp; Restaurant                      |\n| Yo-Yo Fritaille                               |\n| Kal Bakery &amp; Restaurant                       |\n| Bon Appetit Restaurant                        |\n| Katou Fin Restaurant                          |\n| Alhpa Restaurant                              |\n| Lakay Buffet Restaurant                       |\n| La Tranquilite Restaurant                     |\n| La Caye Restaurant                            |\n| Nous Les Amis Restaurant &amp; Bakery             |\n| Yoyo Fritaille                                |\n| Fresh Crown Restaurant                        |\n| Tonel Restaurant &amp; Lounge                     |\n| Grace Devine Pastry And Restaurant Restaurant |\n| Viva Bubble Tea                               |\n| Cafe Creole Restaurant N Bakery               |\n| Delly'S Place Restaurant &amp; Fritaille          |\n| Creole Plate                                  |\n| Chez Nous Restaurant &amp; Fritaille              |\n| Combite Creole                                |\n+-----------------------------------------------+<\/pre>\n<h3>JSON_CONTAINS_PATH<\/h3>\n<p>Indicate whether a JSON document contains data at a given path or paths with <a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/json-search-functions.html#function_json-contains-path\" target=\"_blank\" rel=\"noopener noreferrer\">JSON_CONTAINS_PATH<\/a>.<\/p>\n<p>Let&rsquo;s insert a dummy document in the collection restaurants<\/p>\n<pre class=\"lang:mysql decode:true \" title=\"Insert a JSON document in the collection\">INSERT INTO restaurants (doc) VALUES ('{\"_id\": \"1234\", \"name\": \"Daz Restaurant\", \"cuisine\": \"West Indian\", \"restaurant_id\": \"4321\"}');<\/pre>\n<p>How many documents without grades? :<\/p>\n<pre class=\"lang:mysql decode:true \" title=\"Count number of documents without grades\">SELECT count(*), JSON_CONTAINS_PATH(doc, 'one', '$.grades') cp \nFROM restaurants \nGROUP BY cp;\n+----------+------+\n| count(*) | cp   |\n+----------+------+\n|        1 |    0 |\n|    25359 |    1 |\n+----------+------+<\/pre>\n<p>Ok, only 1. We can easily check the structure of this document :<\/p>\n<pre class=\"lang:mysql decode:true\" title=\"Document without grade \">SELECT JSON_PRETTY(doc) \nFROM restaurants \nWHERE JSON_CONTAINS_PATH(doc, 'one', '$.grades') = 0\\G\n*************************** 1. row ***************************\nJSON_PRETTY(doc): {\n  \"_id\": \"1234\",\n  \"name\": \"Daz Restaurant\",\n  \"cuisine\": \"West Indian\",\n  \"restaurant_id\": \"4321\"\n}<\/pre>\n<h2>A bridge between 2 models<\/h2>\n<p>To paraphrase <a href=\"https:\/\/twitter.com\/stoker\" target=\"_blank\" rel=\"noopener noreferrer\">David Stokes<\/a> (MySQL Community Manager) in his book <a href=\"https:\/\/www.mhprofessional.com\/9781260135442-usa-mysql-and-json-a-practical-programming-guide-group\" target=\"_blank\" rel=\"noopener noreferrer\"><strong>MySQL and JSON &#8211; A practical Programming Guide<\/strong><\/a>.<\/p>\n<p><em>\u00ab\u00a0<\/em><\/p>\n<p><em>The advantages of traditional relational data and schemaless data are both large. But in some cases, data in a schema needs to be schemaless, or schemaless-data needs to be in a schema.\u00a0<\/em><\/p>\n<p><em>\u00ab\u00a0<\/em><\/p>\n<p>Making such metamorphosis is very easy to do with MySQL!<\/p>\n<h3>Relational to JSON<\/h3>\n<h4>JSON_OBJECT<\/h4>\n<p>Evaluates a list of key-value pairs and returns a JSON object containing those pairs with\u00a0<a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/json-creation-functions.html#function_json-object\" target=\"_blank\" rel=\"noopener noreferrer\">JSON_OBJECT<\/a>.<\/p>\n<p>A traditional SQL query with a relational result set. The JSON document output non-JSON data :<\/p>\n<pre class=\"lang:mysql decode:true\" title=\"Classical relational model\">SELECT doc-&gt;&gt;\"$.name\" \nFROM restaurants \nWHERE JSON_CONTAINS(doc, '\"Creole\"', '$.cuisine') \nLIMIT 2;\n+-------------------------+\n| doc-&gt;&gt;\"$.name\"          |\n+-------------------------+\n| Belvedere Restaurant    |\n| Chez Macoule Restaurant |\n+-------------------------+<\/pre>\n<p>This result set could be convert in a JSON format, actually a JSON object :<\/p>\n<pre class=\"lang:mysql decode:true \" title=\"Output in JSON format with JSON_OBJECT\">SELECT JSON_OBJECT(\"Name\", doc-&gt;&gt;\"$.name\") \nFROM restaurants \nWHERE JSON_CONTAINS(doc, '\"Creole\"', '$.cuisine') \nLIMIT 2;\n+-------------------------------------+\n| JSON_OBJECT(\"Name\", doc-&gt;&gt;\"$.name\") |\n+-------------------------------------+\n| {\"Name\": \"Belvedere Restaurant\"}    |\n| {\"Name\": \"Chez Macoule Restaurant\"} |\n+-------------------------------------+<\/pre>\n<p>Other example :<\/p>\n<pre class=\"lang:mysql decode:true \" title=\"JSON_OBJECT\">SELECT Name, Population \nFROM City \nWHERE CountryCode='fra' \nORDER BY Population DESC \nLIMIT 5;\n+-----------+------------+\n| Name      | Population |\n+-----------+------------+\n| Paris     |    2125246 |\n| Marseille |     798430 |\n| Lyon      |     445452 |\n| Toulouse  |     390350 |\n| Nice      |     342738 |\n+-----------+------------+\n\n\nSELECT JSON_OBJECT(\"CityName\",Name, \"CityPop\", Population) \nFROM City \nWHERE CountryCode='fra' \nORDER BY Population DESC \nLIMIT 5;\n+-----------------------------------------------------+\n| JSON_OBJECT(\"CityName\",Name, \"CityPop\", Population) |\n+-----------------------------------------------------+\n| {\"CityPop\": 2125246, \"CityName\": \"Paris\"}           |\n| {\"CityPop\": 798430, \"CityName\": \"Marseille\"}        |\n| {\"CityPop\": 445452, \"CityName\": \"Lyon\"}             |\n| {\"CityPop\": 390350, \"CityName\": \"Toulouse\"}         |\n| {\"CityPop\": 342738, \"CityName\": \"Nice\"}             |\n+-----------------------------------------------------+<\/pre>\n<h4>JSON_OBJECTAGG<\/h4>\n<p>Takes two column names or expressions and returns a JSON object containing key-value pairs with\u00a0<a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/group-by-functions.html#function_json-objectagg\" target=\"_blank\" rel=\"noopener noreferrer\">JSON_OBJECTAGG<\/a>.<\/p>\n<p>Grouping rows are very often useful. This why we implemented some JSON aggregate functions like this one.<\/p>\n<pre class=\"lang:mysql decode:true \" title=\"JSON_OBJECTAGG\">SELECT JSON_OBJECTAGG(Name, CountryCode) \nFROM City  \nGROUP BY id \nORDER BY RAND() \nLIMIT 5;\n+-----------------------------------+\n| JSON_OBJECTAGG(Name, CountryCode) |\n+-----------------------------------+\n| {\"Reno\": \"USA\"}                   |\n| {\"Hanam\": \"KOR\"}                  |\n| {\"Laizhou\": \"CHN\"}                |\n| {\"Yogyakarta\": \"IDN\"}             |\n| {\"Tantoyuca\": \"MEX\"}              |\n+-----------------------------------+<\/pre>\n<ul>\n<li><em><span style=\"text-decoration: underline;\">Note<\/span>:\u00a0<\/em>\n<ul>\n<li><em>It&rsquo;s usually <strong>not a good idea<\/strong> to use ORDER BY RAND(). It works like a charm for small dataset, but it&rsquo;s a true performance killer with huge datasets.<\/em><\/li>\n<li><em>The best practice is to do it in the application or pre-compute random value in the database<\/em>.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<h4>JSON_ARRAY<\/h4>\n<p>Evaluate a list of values and returns a JSON array containing those values with\u00a0<a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/json-creation-functions.html#function_json-array\" target=\"_blank\" rel=\"noopener noreferrer\">JSON_ARRAY<\/a>.<\/p>\n<p>Next example is an hierarchical query using a recursive\u00a0 <a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/with.html\" target=\"_blank\" rel=\"noopener noreferrer\">Common Table Expression<\/a> aka recursive CTE (or\u00a0WITH Syntax)<\/p>\n<pre class=\"lang:mysql decode:true \" title=\"Hierarchical Query using a Recursive CTE\">WITH RECURSIVE emp_ext (id, name, path) AS ( \n    SELECT id, name, CAST(id AS CHAR(200)) \n    FROM employees \n    WHERE manager_id IS NULL \n    UNION ALL \n    SELECT s.id, s.name, CONCAT(m.path, \",\", s.id) \n    FROM emp_ext m \n        JOIN employees s ON m.id=s.manager_id \n) \nSELECT id,name, path FROM emp_ext ORDER BY path;\n+------+---------+-----------------+\n| id   | name    | path            |\n+------+---------+-----------------+\n|  333 | Yasmina | 333             |\n|  198 | John    | 333,198         |\n|   29 | Pedro   | 333,198,29      |\n| 4610 | Sarah   | 333,198,29,4610 |\n|   72 | Pierre  | 333,198,29,72   |\n|  692 | Tarek   | 333,692         |\n|  123 | Adil    | 333,692,123     |\n+------+---------+-----------------+<\/pre>\n<p>JSON format output with JSON_OBJECT &amp; JSON_ARRAY :<\/p>\n<pre class=\"lang:mysql decode:true\" title=\"Hierarchical Query using a Recursive CTE JSON format output\">WITH RECURSIVE emp_ext (id, name, path) AS ( \n    SELECT id, name, CAST(id AS CHAR(200)) \n    FROM employees \n    WHERE manager_id IS NULL \n    UNION ALL \n    SELECT s.id, s.name, CONCAT(m.path, \",\", s.id) \n    FROM emp_ext m \n        JOIN employees s ON m.id=s.manager_id \n) \nSELECT JSON_OBJECT(\"ID\",id, \"Name\",name, \"Path\", JSON_ARRAY(path)) \nFROM emp_ext \nORDER BY path;\n+-------------------------------------------------------------+\n| JSON_OBJECT(\"ID\",id, \"Name\",name, \"Path\", JSON_ARRAY(path)) |\n+-------------------------------------------------------------+\n| {\"ID\": 333, \"Name\": \"Yasmina\", \"Path\": [\"333\"]}             |\n| {\"ID\": 198, \"Name\": \"John\", \"Path\": [\"333,198\"]}            |\n| {\"ID\": 29, \"Name\": \"Pedro\", \"Path\": [\"333,198,29\"]}         |\n| {\"ID\": 4610, \"Name\": \"Sarah\", \"Path\": [\"333,198,29,4610\"]}  |\n| {\"ID\": 72, \"Name\": \"Pierre\", \"Path\": [\"333,198,29,72\"]}     |\n| {\"ID\": 692, \"Name\": \"Tarek\", \"Path\": [\"333,692\"]}           |\n| {\"ID\": 123, \"Name\": \"Adil\", \"Path\": [\"333,692,123\"]}        |\n+-------------------------------------------------------------+<\/pre>\n<h4>JSON_ARRAYAGG<\/h4>\n<p>Aggregate a result set as a single JSON array whose elements consist of the rows with\u00a0<a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/group-by-functions.html#function_json-arrayagg\" target=\"_blank\" rel=\"noopener noreferrer\">JSON_ARRAYAGG<\/a>.<\/p>\n<p>With this other JSON aggregate functions we will see different combinations of JSON format output :<\/p>\n<pre class=\"lang:mysql decode:true \" title=\"JSON_ARRAYAGG\">SELECT CountryCode, JSON_ARRAYAGG(City.Name) \nFROM City \n    JOIN Country ON (City.CountryCode=Country.Code) \nWHERE Continent='Europe' \nGROUP BY 1 \nLIMIT 5;\n+-------------+--------------------------------------------------------------------------------------------------------------+\n| CountryCode | JSON_ARRAYAGG(City.Name)                                                                                     |\n+-------------+--------------------------------------------------------------------------------------------------------------+\n| ALB         | [\"Tirana\"]                                                                                                   |\n| AND         | [\"Andorra la Vella\"]                                                                                         |\n| AUT         | [\"Graz\", \"Linz\", \"Salzburg\", \"Innsbruck\", \"Wien\", \"Klagenfurt\"]                                              |\n| BEL         | [\"Antwerpen\", \"Brugge\", \"Gent\", \"Schaerbeek\", \"Charleroi\", \"Namur\", \"Li\u00e8ge\", \"Mons\", \"Bruxelles [Brussel]\"]  |\n| BGR         | [\"\u0160umen\", \"Sofija\", \"Stara Zagora\", \"Plovdiv\", \"Pleven\", \"Varna\", \"Sliven\", \"Burgas\", \"Dobric\", \"Ruse\"]      |\n+-------------+--------------------------------------------------------------------------------------------------------------+<\/pre>\n<pre class=\"lang:mysql decode:true \" title=\"JSON_OBJECT &amp; JSON_ARRAYAGG\">SELECT JSON_OBJECT(\"CountryCode\",CountryCode), JSON_OBJECT(\"CityName\",JSON_ARRAYAGG(City.Name)) \nFROM City \n    JOIN Country ON (City.CountryCode=Country.Code) \nWHERE Continent='Europe' \nGROUP BY 1 \nLIMIT 5;\n+----------------------------------------+----------------------------------------------------------------------------------------------------------------------------+\n| JSON_OBJECT(\"CountryCode\",CountryCode) | JSON_OBJECT(\"CityName\",JSON_ARRAYAGG(City.Name))                                                                           |\n+----------------------------------------+----------------------------------------------------------------------------------------------------------------------------+\n| {\"CountryCode\": \"ALB\"}                 | {\"CityName\": [\"Tirana\"]}                                                                                                   |\n| {\"CountryCode\": \"AND\"}                 | {\"CityName\": [\"Andorra la Vella\"]}                                                                                         |\n| {\"CountryCode\": \"AUT\"}                 | {\"CityName\": [\"Wien\", \"Graz\", \"Linz\", \"Salzburg\", \"Innsbruck\", \"Klagenfurt\"]}                                              |\n| {\"CountryCode\": \"BEL\"}                 | {\"CityName\": [\"Schaerbeek\", \"Mons\", \"Namur\", \"Brugge\", \"Li\u00e8ge\", \"Antwerpen\", \"Charleroi\", \"Gent\", \"Bruxelles [Brussel]\"]}  |\n| {\"CountryCode\": \"BGR\"}                 | {\"CityName\": [\"Burgas\", \"\u0160umen\", \"Dobric\", \"Sliven\", \"Pleven\", \"Stara Zagora\", \"Ruse\", \"Varna\", \"Plovdiv\", \"Sofija\"]}      |\n+----------------------------------------+----------------------------------------------------------------------------------------------------------------------------+<\/pre>\n<pre class=\"lang:mysql decode:true \" title=\"JSON_OBJECT &amp; JSON_OBJECT\">SELECT JSON_OBJECT(\"Code\",CountryCode, \"CityName\", JSON_ARRAYAGG(City.Name)) \nFROM City \n    JOIN Country ON (City.CountryCode=Country.Code) \nWHERE Continent='Europe' \nGROUP BY CountryCode \nLIMIT 5;\n+-------------------------------------------------------------------------------------------------------------------------------------------+\n| JSON_OBJECT(\"Code\",CountryCode, \"CityName\", JSON_ARRAYAGG(City.Name))                                                                     |\n+-------------------------------------------------------------------------------------------------------------------------------------------+\n| {\"Code\": \"ALB\", \"CityName\": [\"Tirana\"]}                                                                                                   |\n| {\"Code\": \"AND\", \"CityName\": [\"Andorra la Vella\"]}                                                                                         |\n| {\"Code\": \"AUT\", \"CityName\": [\"Graz\", \"Linz\", \"Salzburg\", \"Innsbruck\", \"Wien\", \"Klagenfurt\"]}                                              |\n| {\"Code\": \"BEL\", \"CityName\": [\"Bruxelles [Brussel]\", \"Antwerpen\", \"Brugge\", \"Gent\", \"Schaerbeek\", \"Charleroi\", \"Namur\", \"Li\u00e8ge\", \"Mons\"]}  |\n| {\"Code\": \"BGR\", \"CityName\": [\"Ruse\", \"\u0160umen\", \"Sofija\", \"Stara Zagora\", \"Plovdiv\", \"Pleven\", \"Varna\", \"Sliven\", \"Burgas\", \"Dobric\"]}      |\n+-------------------------------------------------------------------------------------------------------------------------------------------+<\/pre>\n<h3>JSON to Relational<\/h3>\n<p>Ok let&rsquo;s transform JSON data into relational data!<\/p>\n<h4>JSON_TABLE<\/h4>\n<p>Extract data from a JSON document and returns it as a relational table having the specified columns with\u00a0<a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/json-table-functions.html#function_json-table\" target=\"_blank\" rel=\"noopener noreferrer\">JSON_TABLE<\/a>.<\/p>\n<p>Actually I highly recommend you to spend time in the documentation for this powerful function, that allows you to map JSON data into a temporary relational table and then query from this table.<\/p>\n<p>Enough blabla, let&rsquo;s see some examples :<\/p>\n<pre class=\"lang:mysql decode:true\" title=\"JSON_TABLE\">SELECT GNP \nFROM countryinfo, JSON_TABLE(doc, \"$\" COLUMNS (GNP int PATH \"$.GNP\")) AS jst \nWHERE _id='FRA';\n+---------+\n| GNP     |\n+---------+\n| 1424285 |\n+---------+<\/pre>\n<pre class=\"lang:mysql decode:true\" title=\"JSON_TABLE\">SELECT GNP, Name, LifeExpectancy \nFROM countryinfo, JSON_TABLE(doc, \"$\" COLUMNS (GNP int PATH \"$.GNP\", Name char(255) PATH \"$.Name\", LifeExpectancy int PATH \"$.demographics.LifeExpectancy\")) AS jst \nWHERE _id IN ('FRA', 'USA');\n+---------+---------------+----------------+\n| GNP     | Name          | LifeExpectancy |\n+---------+---------------+----------------+\n| 1424285 | France        |             79 |\n| 8510700 | United States |             77 |\n+---------+---------------+----------------+<\/pre>\n<pre class=\"lang:mysql decode:true \" title=\"JSON_TABLE\">SELECT name AS \"Creole Cuisine\" \nFROM restaurant.restaurants, JSON_TABLE(doc, \"$\" COLUMNS (name char(100) PATH \"$.name\", cuisine char(100) PATH \"$.cuisine\")) AS jst \nWHERE cuisine='Creole';\n+-----------------------------------------------+\n| Creole Cuisine                                |\n+-----------------------------------------------+\n| Belvedere Restaurant                          |\n| Chez Macoule Restaurant                       |\n| Paradise Venus Restaurant                     |\n| Heavenly Fritaille Restaurant                 |\n| Yolie'S Bar &amp; Restaurant                      |\n| Yo-Yo Fritaille                               |\n| Kal Bakery &amp; Restaurant                       |\n| Bon Appetit Restaurant                        |\n| Katou Fin Restaurant                          |\n| Alhpa Restaurant                              |\n| Lakay Buffet Restaurant                       |\n| La Tranquilite Restaurant                     |\n| La Caye Restaurant                            |\n| Nous Les Amis Restaurant &amp; Bakery             |\n| Yoyo Fritaille                                |\n| Fresh Crown Restaurant                        |\n| Tonel Restaurant &amp; Lounge                     |\n| Grace Devine Pastry And Restaurant Restaurant |\n| Viva Bubble Tea                               |\n| Cafe Creole Restaurant N Bakery               |\n| Delly'S Place Restaurant &amp; Fritaille          |\n| Creole Plate                                  |\n| Chez Nous Restaurant &amp; Fritaille              |\n| Combite Creole                                |\n+-----------------------------------------------+<\/pre>\n<h4>JSON_TABLE &#8211; Nested Data<\/h4>\n<p>Walk down the JSON document path and retrieve nested data.<\/p>\n<p>For example, extract all grades for Hawaiian cuisine restaurants :<\/p>\n<pre class=\"lang:mysql decode:true\" title=\"JSON_TABLE - Nested data\">SELECT name, cuisine, gradeID, grade \nFROM restaurants,JSON_TABLE(doc, \"$\" COLUMNS (name char(100) PATH \"$.name\", cuisine char(100) PATH \"$.cuisine\", NESTED PATH \"$.grades[*]\" COLUMNS (gradeID FOR ORDINALITY, grade char(20) PATH \"$.grade\"))) AS jst \nWHERE cuisine='Hawaiian';\n+------------------+----------+---------+-------+\n| name             | cuisine  | gradeID | grade |\n+------------------+----------+---------+-------+\n| Makana           | Hawaiian |       1 | C     |\n| Makana           | Hawaiian |       2 | C     |\n| Makana           | Hawaiian |       3 | A     |\n| Makana           | Hawaiian |       4 | C     |\n| Makana           | Hawaiian |       5 | A     |\n| General Assembly | Hawaiian |       1 | A     |\n| General Assembly | Hawaiian |       2 | A     |\n| General Assembly | Hawaiian |       3 | A     |\n| General Assembly | Hawaiian |       4 | A     |\n| Onomea           | Hawaiian |       1 | A     |\n| Onomea           | Hawaiian |       2 | A     |\n+------------------+----------+---------+-------+<\/pre>\n<h4>JSON_TABLE &#8211; Missing Data<\/h4>\n<p>Specify what to do when data is missing.<\/p>\n<p>Default behavior :<\/p>\n<pre class=\"lang:mysql decode:true \" title=\"JSON_TABLE - Missing Data\">SELECT name, cuisine, borough \nFROM restaurant.restaurants,JSON_TABLE(doc, \"$\" COLUMNS (name char(100) PATH \"$.name\", cuisine char(100) PATH \"$.cuisine\", borough char(100) PATH \"$.borough\")) AS jst  \nLIMIT 2;\n+--------------------------------+-------------+-----------+\n| name                           | cuisine     | borough   |\n+--------------------------------+-------------+-----------+\n| Daz Restaurant                 | West Indian | NULL      |\n| Dj Reynolds Pub And Restaurant | Irish       | Manhattan |\n+--------------------------------+-------------+-----------+<\/pre>\n<p>Enforce the default behavior :<\/p>\n<pre class=\"lang:mysql decode:true \" title=\"JSON_TABLE - Missing Data\">SELECT name, cuisine, borough \nFROM restaurant.restaurants,JSON_TABLE(doc, \"$\" COLUMNS (name char(100) PATH \"$.name\", cuisine char(100) PATH \"$.cuisine\", borough char(100) PATH \"$.borough\" NULL ON EMPTY)) AS jst \nLIMIT 2;\n+--------------------------------+-------------+-----------+\n| name                           | cuisine     | borough   |\n+--------------------------------+-------------+-----------+\n| Daz Restaurant                 | West Indian | NULL      |\n| Dj Reynolds Pub And Restaurant | Irish       | Manhattan |\n+--------------------------------+-------------+-----------+<\/pre>\n<p>Raise an error :<\/p>\n<pre class=\"lang:mysql decode:true \" title=\"JSON_TABLE - Missing Data\">SELECT name, cuisine, borough \nFROM restaurant.restaurants,JSON_TABLE(doc, \"$\" COLUMNS (name char(100) PATH \"$.name\", cuisine char(100) PATH \"$.cuisine\", borough char(100) PATH \"$.borough\" ERROR ON EMPTY)) AS jst \nLIMIT 2;\nERROR 3665 (22035): Missing value for JSON_TABLE column 'borough'<\/pre>\n<p>Specify a default value :<\/p>\n<pre class=\"lang:mysql decode:true \" title=\"JSON_TABLE - Missing Data\">SELECT name, cuisine, borough \nFROM restaurant.restaurants,JSON_TABLE(doc, \"$\" COLUMNS (name char(100) PATH \"$.name\", cuisine char(100) PATH \"$.cuisine\", borough char(100) PATH \"$.borough\" DEFAULT '\"&lt;UNKNOW&gt;\"' ON EMPTY)) AS jst \nLIMIT 2;\n+--------------------------------+-------------+-----------+\n| name                           | cuisine     | borough   |\n+--------------------------------+-------------+-----------+\n| Daz Restaurant                 | West Indian | &lt;UNKNOW&gt;  |\n| Dj Reynolds Pub And Restaurant | Irish       | Manhattan |\n+--------------------------------+-------------+-----------+<\/pre>\n<h2>Wrapup<\/h2>\n<p>I&rsquo;ll stop here this introduction to this rich MySQL JSON functions world. I presented a subset of these functions but it definitely worth to spend some time to discover the entire set e.g. how to create, modify, indexing, &#8230; JSON documents.<\/p>\n<p>Furthermore, if your workload does not fit in the relational model, you should use the <a href=\"https:\/\/mysqlserverteam.com\/mysql-8-0-announcing-ga-of-the-mysql-document-store\/\" target=\"_blank\" rel=\"noopener noreferrer\">MySQL 8 Document Store<\/a>, that provide a CRUD API and\u00a0some other cool stuffs. I&rsquo;ll blog about it soon, so stay tune!<\/p>\n<p>Anyway I&rsquo;ll recommend you to read :\u00a0<a href=\"https:\/\/lefred.be\/content\/top-10-reasons-for-nosql-with-mysql\/\" target=\"_blank\" rel=\"noopener noreferrer\">Top 10 reasons for NoSQL with MySQL<\/a>.<\/p>\n<h2>Misc<\/h2>\n<p><span style=\"text-decoration: underline;\">Documentation<\/span><\/p>\n<ul>\n<li><a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/json-function-reference.html\" target=\"_blank\" rel=\"noopener noreferrer\">JSON Functions Reference<\/a><\/li>\n<li><a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/group-by-functions.html#function_json-objectagg\" target=\"_blank\" rel=\"noopener noreferrer\">Aggregate (GROUP BY) Function Descriptions<\/a><\/li>\n<\/ul>\n<p><span style=\"text-decoration: underline;\">Articles<\/span><\/p>\n<ul>\n<li><a href=\"http:\/\/dasini.net\/blog\/2015\/11\/17\/30-mins-with-json-in-mysql\/\" target=\"_blank\" rel=\"noopener noreferrer\">30 mins with JSON in MySQL<\/a><\/li>\n<li><a href=\"http:\/\/dasini.net\/blog\/2015\/11\/30\/json-et-colonnes-generees-avec-mysql\/\" target=\"_blank\" rel=\"noopener noreferrer\">JSON et colonnes g\u00e9n\u00e9r\u00e9es avec MySQL (in French)<\/a><\/li>\n<li><a href=\"https:\/\/mysqlserverteam.com\/new-json-functions-in-mysql-5-7-22\/\" target=\"_blank\" rel=\"noopener noreferrer\">New JSON functions in MySQL 5.7.22<\/a><\/li>\n<li><a href=\"https:\/\/mysqlserverteam.com\/mysql-8-0-from-sql-tables-to-json-documents-and-back-again\/\" target=\"_blank\" rel=\"noopener noreferrer\">MySQL 8.0: From SQL Tables to JSON Documents (and back again)<\/a><\/li>\n<\/ul>\n<p><span style=\"text-decoration: underline;\">Other resources<\/span><\/p>\n<ul>\n<li>You&rsquo;ll find some of the sample databases used in this article <a href=\"https:\/\/dev.mysql.com\/doc\/index-other.html\" target=\"_blank\" rel=\"noopener noreferrer\">here<\/a>.<\/li>\n<li>Restaurants collection could be find <a href=\"https:\/\/github.com\/freshdaz\/MySQL-dump-of-restaurants-table\" target=\"_blank\" rel=\"noopener noreferrer\">here<\/a>.<\/li>\n<li>Some books that could be useful : <a href=\"http:\/\/dasini.net\/blog\/2018\/07\/12\/plenty-of-new-mysql-books\/\" target=\"_blank\" rel=\"noopener noreferrer\">here<\/a>.<\/li>\n<\/ul>\n<p>Thanks for using MySQL!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>JSON (JavaScript Object Notation) is a popular way for moving data between various systems, including databases.  Starting with 5.7 MySQL implemented a native JSON data type and a set of JSON functions that allows you to perform operations on JSON values.<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"footnotes":""},"categories":[257,203,425],"tags":[422,310,312],"class_list":["post-2465","post","type-post","status-publish","format-standard","hentry","category-json","category-mysql-en","category-nosql-en","tag-cte","tag-json-en","tag-nosql-en"],"aioseo_notices":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p9LfWW-DL","jetpack-related-posts":[{"id":1515,"url":"https:\/\/dasini.net\/blog\/2015\/11\/17\/30-mins-with-json-in-mysql\/","url_meta":{"origin":2465,"position":0},"title":"30 mins with JSON in MySQL","author":"Olivier DASINI","date":"17 novembre 2015","format":false,"excerpt":"MySQL 5.7 is GA and has over than 150 new features. One of them is a Native JSON Data Type and JSON Functions: \"Allows for efficient and flexible storage, search and manipulation of schema-less data. Enhancements include a new internal binary format, support for easy integration within SQL, and index\u2026","rel":"","context":"Dans &quot;json&quot;","block_context":{"text":"json","link":"https:\/\/dasini.net\/blog\/category\/json\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":8442,"url":"https:\/\/dasini.net\/blog\/2025\/10\/14\/querying-the-unstructured-natural-language-to-sql-for-json-data\/","url_meta":{"origin":2465,"position":1},"title":"Querying the Unstructured: Natural Language to SQL for JSON Data","author":"Olivier DASINI","date":"14 octobre 2025","format":false,"excerpt":"Bridging natural language processing with semi-structured data brings both opportunity and complexity. MySQL HeatWave GenAI\u2019s NL2SQL feature shows how natural language can simplify data interaction \u2014 even for JSON documents. Yet, because JSON embeds both data and metadata within a single column, LLMs may struggle without explicit schema cues. By\u2026","rel":"","context":"Dans &quot;AI&quot;","block_context":{"text":"AI","link":"https:\/\/dasini.net\/blog\/category\/ai\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/dasini.net\/blog\/wp-content\/uploads\/2025\/10\/Querying-the-Unstructured-Natural-Language-to-SQL-for-JSON-Data-400.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":2443,"url":"https:\/\/dasini.net\/blog\/2018\/07\/12\/plenty-of-new-mysql-books\/","url_meta":{"origin":2465,"position":2},"title":"Plenty of new MySQL books","author":"Olivier DASINI","date":"12 juillet 2018","format":false,"excerpt":"In the old days, when we wanted to strengthen our skills the only option was to buy a good book. Nowadays one can find a lot of resources on the Internet, however quality is often poor. Fortunately there are still some great people who are brave enough to write new\u2026","rel":"","context":"Dans &quot;Book&quot;","block_context":{"text":"Book","link":"https:\/\/dasini.net\/blog\/category\/book\/"},"img":{"alt_text":"MySQL and JSON: A Practical Programming Guide - Discover how to use JavaScript Object Notation (JSON) with MySQL","src":"https:\/\/i0.wp.com\/images-na.ssl-images-amazon.com\/images\/I\/416UKa-bH%2BL._SX332_BO1%2C204%2C203%2C200_.jpg?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":2874,"url":"https:\/\/dasini.net\/blog\/2019\/04\/02\/mysql-json-document-store\/","url_meta":{"origin":2465,"position":3},"title":"MySQL JSON Document Store","author":"Olivier DASINI","date":"2 avril 2019","format":false,"excerpt":"MySQL 8.0 provides another way to handle JSON documents, actually in a \"Not only SQL\" (NoSQL) approach... In other words, if you need\/want to manage JSON documents (collections) in a non-relational manner, with CRUD (acronym for Create\/Read\/Update\/Delete) operations then you can use MySQL 8.0! Did you know that?","rel":"","context":"Dans &quot;Document Store&quot;","block_context":{"text":"Document Store","link":"https:\/\/dasini.net\/blog\/category\/document-store\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":3463,"url":"https:\/\/dasini.net\/blog\/2019\/09\/19\/mysql-8-0-17-new-features-summary\/","url_meta":{"origin":2465,"position":4},"title":"MySQL 8.0.17 &#8211; New Features Summary","author":"Olivier DASINI","date":"19 septembre 2019","format":false,"excerpt":"This presentation is a summary of the MySQL 8.0.17 new features.","rel":"","context":"Dans &quot;Group Replication&quot;","block_context":{"text":"Group Replication","link":"https:\/\/dasini.net\/blog\/category\/group-replication-en\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":2746,"url":"https:\/\/dasini.net\/blog\/2019\/03\/14\/mysql-functional-indexes\/","url_meta":{"origin":2465,"position":5},"title":"MySQL Functional Indexes","author":"Olivier DASINI","date":"14 mars 2019","format":false,"excerpt":"Since MySQL 5.7 one can put indexes on expressions, aka functional indexes, using generated columns. Basically you first need to use the generated column to define the functional expression, then indexed this column. Quite useful when dealing with JSON functions, you can find an example here and the documentation there.\u2026","rel":"","context":"Dans &quot;Astuce&quot;","block_context":{"text":"Astuce","link":"https:\/\/dasini.net\/blog\/category\/astuce\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"jetpack_likes_enabled":true,"_links":{"self":[{"href":"https:\/\/dasini.net\/blog\/wp-json\/wp\/v2\/posts\/2465","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dasini.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dasini.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dasini.net\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/dasini.net\/blog\/wp-json\/wp\/v2\/comments?post=2465"}],"version-history":[{"count":37,"href":"https:\/\/dasini.net\/blog\/wp-json\/wp\/v2\/posts\/2465\/revisions"}],"predecessor-version":[{"id":2942,"href":"https:\/\/dasini.net\/blog\/wp-json\/wp\/v2\/posts\/2465\/revisions\/2942"}],"wp:attachment":[{"href":"https:\/\/dasini.net\/blog\/wp-json\/wp\/v2\/media?parent=2465"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dasini.net\/blog\/wp-json\/wp\/v2\/categories?post=2465"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dasini.net\/blog\/wp-json\/wp\/v2\/tags?post=2465"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}