{"id":107890,"date":"2025-12-02T16:07:23","date_gmt":"2025-12-02T16:07:23","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=107890"},"modified":"2025-12-08T15:56:57","modified_gmt":"2025-12-08T15:56:57","slug":"mysql-vs-postgresql-json-data-type","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/mysql-vs-postgresql-json-data-type\/","title":{"rendered":"MySQL vs PostgreSQL: JSON Data Types"},"content":{"rendered":"\n<p><strong>In this article, the latest in the <a href=\"https:\/\/www.red-gate.com\/simple-talk\/tag\/aishabukar_mysql_postgresql\/\" target=\"_blank\" rel=\"noreferrer noopener\">MySQL vs PostgreSQL series<\/a>, we&#8217;ll compare how they each handle <strong><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/oracle-databases\/json-for-absolute-beginners-part-1-introduction\/\" target=\"_blank\" rel=\"noreferrer noopener\">JSON (JavaScript Object Notation)<\/a><\/strong> data, how they store, query, and manage it, and which offers more flexibility or better performance depending on your needs.<\/strong><\/p>\n\n\n\n<p><a href=\"https:\/\/www.mysql.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">MySQL<\/a> and <a href=\"https:\/\/www.postgresql.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">PostgreSQL<\/a> are two of the most popular relational database systems in the world. Both are open-source, widely used in web and enterprise applications, and support <a href=\"https:\/\/aws.amazon.com\/what-is\/structured-data\/\" target=\"_blank\" rel=\"noreferrer noopener\">structured data<\/a> in tables.<\/p>\n\n\n\n<p>Modern applications, however, often work with <a href=\"https:\/\/www.geeksforgeeks.org\/dbms\/what-is-semi-structured-data\/\" target=\"_blank\" rel=\"noreferrer noopener\">semi-structured data<\/a> that doesn\u2019t always neatly fit into tables with rows and columns. This type of data gets its name because it still has some organization but doesn\u2019t follow a strict format.<\/p>\n\n\n\n<p><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/oracle-databases\/json-for-absolute-beginners-part-1-introduction\/\" target=\"_blank\" rel=\"noreferrer noopener\">JSON (JavaScript Object Notation)<\/a> is a popular way to store and share this kind of data. It\u2019s a text format easy for both people and computers to understand.<\/p>\n\n\n\n<p>Because of its simplicity, JSON is widely used in web applications, <a href=\"https:\/\/www.ibm.com\/think\/topics\/api\" target=\"_blank\" rel=\"noreferrer noopener\">APIs<\/a>, and databases to store and transfer data efficiently. Here&#8217;s an example of what JSON data typically looks like:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">{\n\n&nbsp; \"name\": \"Aisha\",\n\n&nbsp; \"age\": 10,\n\n&nbsp; \"email\": \"aisha@simpletalk.com\"\n\n}<\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-json-support-in-databases\">JSON Support in Databases<\/h2>\n\n\n\n<p>Modern web and mobile apps often share data using JSON through APIs. <a href=\"https:\/\/www.red-gate.com\/simple-talk\/blogs\/json-and-the-arguments\/\" target=\"_blank\" rel=\"noreferrer noopener\">Storing JSON<\/a> in a database makes it easier to handle flexible and changing data and, unlike regular tables, it can store nested information, which is useful for complex data.<\/p>\n\n\n\n<p>It&#8217;s also great for data that changes often or has unpredictable fields. Instead of changing the database structure, developers can just update the JSON data, making development faster and more flexible.<\/p>\n\n\n\n<p>As applications grow more dynamic, especially with APIs, web services and mobile apps, the need to store and process JSON data inside a database has become increasingly important.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-json-support-in-mysql-explained\">JSON Support in MySQL, Explained<\/h2>\n\n\n\n<p>MySQL <a href=\"https:\/\/dev.mysql.com\/doc\/refman\/9.2\/en\/json.html\" target=\"_blank\" rel=\"noreferrer noopener\">introduced support<\/a> for the JSON data type in <a href=\"https:\/\/dev.mysql.com\/doc\/relnotes\/mysql\/5.7\/en\/\" target=\"_blank\" rel=\"noreferrer noopener\">version 5.7<\/a>. Before this, JSON had to be stored as plain text, which made it harder to query or validate. The native support of the JSON data type allows developers to <a href=\"https:\/\/www.baeldung.com\/sql\/mysql-json-column\" target=\"_blank\" rel=\"noreferrer noopener\">store structured JSON data<\/a> in a column and work with it more easily and efficiently.<\/p>\n\n\n\n<p>When you insert JSON into a JSON column, MySQL stores it in a special binary format, not as plain text. This format allows for faster access and better performance when querying specific parts of the JSON document.<\/p>\n\n\n\n<p>MySQL also allows you to create <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/mysql\/the-nuances-of-mysql-indexes\/\" target=\"_blank\" rel=\"noreferrer noopener\">indexes<\/a> on virtual columns derived from JSON values, but you can\u2019t directly index the entire JSON object.<\/p>\n\n\n\n<p>When you store JSON in MySQL, the engine uses a compact binary format under the hood, but the overall disk usage ends up being very close to what you\u2019d need if you saved the same text as <code>LONGTEXT<\/code>. Here\u2019s what to expect:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li><strong>Base Size \u2243 Text Length:<\/strong> MySQL reserves roughly as many bytes as the UTF-8 text of your JSON document. In other words, if your JSON text is 1 KB, you\u2019ll see about 1 KB of storage use before overhead.<\/li>\n\n\n\n<li><strong>Binary Overhead<\/strong>: On top of that, MySQL adds small bookkeeping structures &#8211; things like metadata tags and lookup dictionaries that let it navigate and modify JSON quickly. For a simple string element, this overhead is typically 4\u201310 extra bytes, depending on how big the string is and where it lives in your JSON object or array.<\/li>\n\n\n\n<li><strong>Maximum Document Size:<\/strong> A single JSON value can be as large as your<code> max_allowed_packet<\/code> setting (often several megabytes by default), so you\u2019re only limited by that network\/packet size, not by the JSON type itself.<\/li>\n\n\n\n<li><strong>Measuring Storage Use with JSON_STORAGE_SIZE()<\/strong>: MySQL provides the <code>JSON_STORAGE_SIZE()<\/code> function to see exactly how many bytes a JSON value consumes in its binary form. You can use this to monitor exactly how much space your JSON data requires and adjust your schema or storage strategy accordingly.<\/li>\n<\/ul>\n<\/div>\n\n\n<p>If you\u2019re storing lots of very small JSON snippets, the per-element overhead can add up. In that case, consider whether some of the data could live in traditional columns or if you can consolidate multiple small JSON values into a single document. <\/p>\n\n\n\n<p>Otherwise, for most real-world payloads, the approximately ~5\u201310% overhead is a fair trade-off for faster parsing and richer query support.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">JSON Functions in MySQL<\/h2>\n\n\n\n<p>MySQL has a set of built-in JSON functions that let you build JSON documents, extract values, update or remove elements, and even validate or pretty-print your data. Here are some common JSON functions and how to use them:<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">How To Create JSON Documents<\/h4>\n\n\n\n<p>MySQL allows you to create JSON documents using the following functions:<\/p>\n\n\n\n<p>i. <strong>JSON_OBJECT(key, value)<\/strong>: The <code>JSON_OBJECT()<\/code> function creates a JSON object based on the keys and values you provide. Here\u2019s an example on how to use this:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SELECT JSON_OBJECT(\n  'id', 101,\n  'name', 'banana',\n  'price', 1.25\n) AS product;\n<\/pre><\/div>\n\n\n\n<p>This query returns a JSON object created from the key and values provided:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"528\" height=\"108\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-16.png\" alt=\"What the query returns.\" class=\"wp-image-107891\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-16.png 528w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-16-300x61.png 300w\" sizes=\"auto, (max-width: 528px) 100vw, 528px\" \/><\/figure>\n\n\n\n<p>ii. <strong>JSON_ARRAY()<\/strong>: The <code>JSON_ARRAY()<\/code> function allows you to build a JSON array from a list of values.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SELECT JSON_ARRAY('apple', 'banana', 'cherry') AS fruits;<\/pre><\/div>\n\n\n\n<p>This query should return a JSON array containing the values we listed above:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"395\" height=\"106\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-17.png\" alt=\"The returned JSON array.\" class=\"wp-image-107892\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-17.png 395w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-17-300x81.png 300w\" sizes=\"auto, (max-width: 395px) 100vw, 395px\" \/><\/figure>\n\n\n\n<p>The <code>JSON_ARRAY() <\/code>function also allows you to nest elements within the array.<\/p>\n\n\n\n<p>iii. <strong>JSON_QUOTE()<\/strong>: The <code>JSON_QUOTE()<\/code> function takes a plain text string and turns it into a JSON-safe string. It wraps the string in double quotes and escapes any special characters like quotes or backslashes inside the string.<\/p>\n\n\n\n<p>Suppose you have a user comment stored in a string, and you want to convert it into a JSON-safe format, here&#8217;s how you can do this using the <code>JSON_QUOTE()<\/code> function:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SELECT JSON_QUOTE('Hi there! It\\'s a great day :)');<\/pre><\/div>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"550\" height=\"156\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-18.png\" alt=\"\" class=\"wp-image-107893\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-18.png 550w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-18-300x85.png 300w\" sizes=\"auto, (max-width: 550px) 100vw, 550px\" \/><\/figure>\n\n\n\n<p>MySQL automatically wraps the string in double quotes and escapes any necessary characters (like the apostrophe &#8216; or backslashes).<\/p>\n\n\n\n<p>Aggregate functions such as the <code>JSON_ARRAYAGG()<\/code> and <code>JSON_OBJECTAGG()<\/code> are also available in MySQL. To learn more about how to use the aggregate function, please visit the official <a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.4\/en\/aggregate-functions-and-modifiers.html\" target=\"_blank\" rel=\"noreferrer noopener\">documentation.<\/a><\/p>\n\n\n\n<h4 class=\"wp-block-heading\">How To Search JSON Documents<\/h4>\n\n\n\n<p>MySQL offers a handy set of functions that allows you to search and extract data from your JSON documents. Here are some of the available functions::<\/p>\n\n\n\n<p>i.<strong>&nbsp; JSON_EXTRACT(json_doc, path)<\/strong>: The <code>JSON_EXTRACT()<\/code> function fetches data from a JSON document using a <a href=\"https:\/\/www.sciencedirect.com\/topics\/computer-science\/path-expression\" target=\"_blank\" rel=\"noreferrer noopener\">&#8220;path&#8221; expression<\/a>. Here\u2019s a typical example on how to use this function:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SET @doc = '{\"user\": {\"name\": \"Alice\",\"age\": 25}}';\nSELECT JSON_EXTRACT(@doc, '$.user.name') AS user_name;\n<\/pre><\/div>\n\n\n\n<p>This returns the username (Alice) which was the requested path:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"222\" height=\"98\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-19.png\" alt=\"The returned username 'Alice', which was the requested path.\" class=\"wp-image-107894\"\/><\/figure>\n\n\n\n<p>ii.<strong> JSON_UNQUOTE()<\/strong>: You can use the <code>JSON_UNQUOTE()<\/code> function to remove surrounding quotes from a JSON string. Let\u2019s try an example where we remove the surrounding quotes from the username &#8220;Alice&#8221; that we extracted earlier:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SELECT JSON_UNQUOTE(JSON_EXTRACT(@doc, '$.user.name')) AS user_name; <\/pre><\/div>\n\n\n\n<p>Here is the expected result:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"217\" height=\"102\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-20.png\" alt=\"The expected result\" class=\"wp-image-107895\"\/><\/figure>\n\n\n\n<p>iii. <strong>JSON_CONTAINS(target, candidate[, path])<\/strong>: The <code>JSON_CONTAINS()<\/code> function checks whether a JSON document contains a candidate value (optionally at a path):<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SET @colors = '[\"red\",\"green\",\"blue\"]';\nSELECT JSON_CONTAINS(@colors, '\"green\"') AS has_green;\n<\/pre><\/div>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"225\" height=\"91\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-21.png\" alt=\"\" class=\"wp-image-107896\"\/><\/figure>\n\n\n\n<p>The expected result is 1, which represents a <a href=\"https:\/\/www.baeldung.com\/java-boolean-true-primitive-vs-constant\" target=\"_blank\" rel=\"noreferrer noopener\">Boolean true<\/a>, confirming that the document contains the color green.<\/p>\n\n\n\n<p>iv.<strong> JSON_KEYS(json_doc[, path])<\/strong>: The <code>JSON_KEYS()<\/code> function returns a list of keys at the top level (or at a given path). Here&#8217;s an example on how this function works:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SELECT JSON_KEYS('{\"a\":1, \"b\":2, \"c\":3}') AS list_of_keys;<\/pre><\/div>\n\n\n\n<p>This is the expected result:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"222\" height=\"97\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-22.png\" alt=\"\" class=\"wp-image-107897\"\/><\/figure>\n\n\n\n<p>There are many more JSON functions available in MySQL for advanced searching and manipulation. You can explore the full list in the official <a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.4\/en\/json-search-functions.html#function_json-extract\" target=\"_blank\" rel=\"noreferrer noopener\">documentation<\/a>.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">How To Modify JSON Documents<\/h4>\n\n\n\n<p>MySQL has a pretty neat list of some in-built functions that make it easy to add, update, or remove values within your JSON documents. Below are some of the commonly used functions you can take advantage of:<\/p>\n\n\n\n<p>i.<strong> JSON_INSERT(json_doc, path, val):<\/strong> The <code>JSON_INSERT()<\/code> function adds a new key\/value to the JSON document only if the key doesn\u2019t already exist. Here&#8217;s an example on how to use this function:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SELECT JSON_INSERT('{\"name\": \"Alice\"}', '$.age', 25);<\/pre><\/div>\n\n\n\n<p>The output:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"575\" height=\"131\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-23.png\" alt=\"\" class=\"wp-image-107898\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-23.png 575w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-23-300x68.png 300w\" sizes=\"auto, (max-width: 575px) 100vw, 575px\" \/><\/figure>\n\n\n\n<p>If you try to insert at a path that already exists, it won\u2019t change the value.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SELECT JSON_INSERT('{\"name\": \"Alice\"}', '$.name', 'Bob');<\/pre><\/div>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"594\" height=\"138\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-24.png\" alt=\"\" class=\"wp-image-107899\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-24.png 594w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-24-300x70.png 300w\" sizes=\"auto, (max-width: 594px) 100vw, 594px\" \/><\/figure>\n\n\n\n<p>ii. <strong>JSON_ARRAY_INSERT(json_doc, path, val):<\/strong> The <code>JSON_ARRAY_INSERT()<\/code> function inserts a value into a JSON array at the specified position. The path must point to a valid array index.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SELECT JSON_ARRAY_INSERT('[10, 20, 30]', '$[1]', 15);<\/pre><\/div>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"561\" height=\"147\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-25.png\" alt=\"\" class=\"wp-image-107900\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-25.png 561w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-25-300x79.png 300w\" sizes=\"auto, (max-width: 561px) 100vw, 561px\" \/><\/figure>\n\n\n\n<p>Here, 15 is inserted at index 1.<\/p>\n\n\n\n<p>iii.<strong> JSON_REMOVE(json_doc, path):<\/strong> The <code>JSON_REMOVE()<\/code> function removes a value from the JSON document at the specified path. Here is an example on how you can use the <code>JSON_REMOVE()<\/code> function:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SELECT JSON_REMOVE('{\"name\": \"Alice\", \"age\": 25}', '$.age')  AS json_remove; <\/pre><\/div>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"297\" height=\"83\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-26.png\" alt=\"\" class=\"wp-image-107901\"\/><\/figure>\n\n\n\n<p>You can also remove elements from arrays too:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SELECT JSON_REMOVE('[10, 20, 30]', '$[1]');<\/pre><\/div>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"231\" height=\"80\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-27.png\" alt=\"\" class=\"wp-image-107902\"\/><\/figure>\n\n\n\n<p>iv. <strong>JSON_REPLACE(json_doc, path, val)<\/strong>: The <code>JSON_REPLACE()<\/code> function replaces the value only if the key or path exists. If it doesn\u2019t, nothing changes. Here&#8217;s an example:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SELECT JSON_REPLACE('{\"name\": \"Alice\", \"age\": 25}', '$.age', 26) AS JSON_REPLACE;<\/pre><\/div>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"378\" height=\"122\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-28.png\" alt=\"\" class=\"wp-image-107903\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-28.png 378w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-28-300x97.png 300w\" sizes=\"auto, (max-width: 378px) 100vw, 378px\" \/><\/figure>\n\n\n\n<p>Let&#8217;s now try to replace a non-existing key:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SELECT JSON_REPLACE('{\"name\": \"Alice\"}', '$.age', 30) AS JSON_REPLACE;<\/pre><\/div>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"283\" height=\"119\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-29.png\" alt=\"\" class=\"wp-image-107904\"\/><\/figure>\n\n\n\n<p>As expected, the output doesn&#8217;t change because there is no key called &#8220;age&#8221;. MySQL offers a wide range of built-in functions to extract, modify, and work with JSON data, many of which haven&#8217;t been covered in this article. For a complete list and more advanced use cases, be sure to check out the official MySQL <a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.4\/en\/json-modification-functions.html\" target=\"_blank\" rel=\"noreferrer noopener\">documentation.<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">JSON Path Expressions in MySQL<\/h2>\n\n\n\n<p>In MySQL, JSON path expressions are utilized to pinpoint specific elements within a JSON document. These expressions are integral to various JSON functions, such as <code>JSON_EXTRACT()<\/code>, <code>JSON_SET()<\/code> and <code>JSON_CONTAINS()<\/code>, allowing precise data retrieval and modification.<\/p>\n\n\n\n<p><strong>BASIC SYNTAX<\/strong><\/p>\n\n\n\n<p>$: Denotes the root of the JSON document.<\/p>\n\n\n\n<p>.: Accesses a member of a JSON object.<\/p>\n\n\n\n<p>[]: Accesses elements within a JSON array by index.<\/p>\n\n\n\n<p>*: Wildcard to match all members or elements.<\/p>\n\n\n\n<p><em><strong>Here are some examples:<\/strong><\/em><\/p>\n\n\n\n<p>$.name: Accesses the name key at the root level.<\/p>\n\n\n\n<p>$.address.city: Accesses the city key within the address object.<\/p>\n\n\n\n<p>$[0]: Accesses the first element in a JSON array.<\/p>\n\n\n\n<p>$[*]: Accesses all elements in a JSON array.<\/p>\n\n\n\n<p>You might have noticed the use of path expressions in our earlier examples. Below are a few common examples that explain what these JSON path expressions actually mean when used.<\/p>\n\n\n\n<p>Assume we have a table called &#8216;users&#8217; with a &#8216;details&#8217; column of type JSON:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">CREATE TABLE users (\nid INT AUTO_INCREMENT PRIMARY KEY,\nname VARCHAR(50),\ndetails JSON\n);\nINSERT INTO users (name, details) VALUES\n('Alice', '{\"age\": 25, \"skills\": [\"Python\", \"SQL\", \"HTML\"]}'),\n('Bob', '{\"age\": 30, \"skills\": [\"Java\", \"C++\", \"JavaScript\"]}'); \n<\/pre><\/div>\n\n\n\n<p>1.<strong> Extracting data from a member of a JSON document<\/strong><\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SELECT JSON_EXTRACT(details, '$.age') AS age FROM users; <\/pre><\/div>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"167\" height=\"136\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-30.png\" alt=\"\" class=\"wp-image-107905\"\/><\/figure>\n\n\n\n<p>This query retrieves the age from each &#8216;details&#8217; JSON object.<\/p>\n\n\n\n<p>2.<strong> Accessing an element by array index []<\/strong><\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SELECT JSON_EXTRACT(details, '$.skills[0]') AS first_skill FROM users;<\/pre><\/div>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"197\" height=\"144\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-31.png\" alt=\"\" class=\"wp-image-107906\"\/><\/figure>\n\n\n\n<p>This query retrieves the first skill from the<strong> skills <\/strong>array.<\/p>\n\n\n\n<p>3. <strong>Using Wildcards *<\/strong><\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SELECT JSON_EXTRACT(details, '$.skills[*]') AS all_skills FROM users;<\/pre><\/div>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"372\" height=\"142\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-32.png\" alt=\"\" class=\"wp-image-107907\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-32.png 372w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-32-300x115.png 300w\" sizes=\"auto, (max-width: 372px) 100vw, 372px\" \/><\/figure>\n\n\n\n<p>The wildcard * retrieves all elements in the<strong> skills<\/strong> array as a JSON array.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Limitations of JSON in MySQL<\/h2>\n\n\n\n<p>While MySQL does support working with JSON data, there are a few notable limitations, such as:<\/p>\n\n\n\n<p>1. <strong>Indexing Limitations:<\/strong> MySQL does not natively index JSON fields like a traditional column. You can only index virtual columns that extract part of a JSON document. There&#8217;s also no built-in GIN-style indexing like in PostgreSQL that speeds up searches inside large JSON data.<\/p>\n\n\n\n<p>2.<strong> No Support for JSONB (Binary + Index-Friendly Format)<\/strong>: Unlike PostgreSQL\u2019s JSONB, which is optimized for both storage and querying, MySQL stores JSON in a binary format internally but doesn&#8217;t expose the same level of indexing and flexibility. Although MySQL&#8217;s binary format is efficient for storage and parsing, it doesn\u2019t offer the same indexing or performance gains during search.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-json-support-in-postgresql-explained\">JSON Support in PostgreSQL, Explained<\/h2>\n\n\n\n<p><a href=\"https:\/\/www.postgresql.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">PostgreSQL<\/a> is widely recognized for its advanced support for JSON data, making it a favorite for applications that work heavily with APIs, user-defined data, or flexible schemas. PostgreSQL provides two types for storing JSON data:<\/p>\n\n\n\n<p>i. JSON: It stores the data as plain text (just like the original input).<\/p>\n\n\n\n<p>ii. JSONB (Binary JSON):&nbsp;It stores JSON in a decomposed binary format that is faster to process and easier to query.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">How PostgreSQL Stores JSON and JSONB Data<\/h3>\n\n\n\n<p>When you use the <strong>JSON<\/strong> data type in PostgreSQL, the data is stored exactly as you enter it, as a plain text string. It keeps the formatting, spacing, and even the order of keys. It\u2019s like saving a block of text that just happens to be in JSON format.<\/p>\n\n\n\n<p>It&#8217;s good if you just want to store and retrieve JSON as-is, but is slower for searching or modifying values inside the JSON and uses more disk space than JSONB (due to formatting).<\/p>\n\n\n\n<p>JSONB stands for<strong> JSON Binary<\/strong>. It stores the same kind of data but converts it into a compact binary format. This means PostgreSQL reorders the keys, removes unnecessary whitespace, and stores the data in a way that\u2019s faster to search and manipulate.<\/p>\n\n\n\n<p>It&#8217;s faster for querying and indexing, and is more space-efficient due to compression. It also supports advanced indexing with <a href=\"https:\/\/www.postgresql.org\/docs\/current\/gin.html\" target=\"_blank\" rel=\"noreferrer noopener\">GIN (Generalized Inverted Indexes)<\/a>. However, it&#8217;s slightly slower to insert because of the conversion to binary.<\/p>\n\n\n\n<p>Let\u2019s take a look at some of the key differences between JSON and JSONB using an example. First, let\u2019s create a table to test both:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">CREATE TABLE json_test (\n    id SERIAL PRIMARY KEY,\n    data_json  JSON,\n    data_jsonb JSONB\n);\nNow, let's insert a JSON object with an extra whitespace, a specific key order, and duplicate keys:\nINSERT INTO json_test (data_json, data_jsonb)\nVALUES (\n  '{\n    \"name\":    \"Laptop\",\n    \"price\": 1000,\n    \"in_stock\": true,\n    \"price\": 1200\n  }',\n  '{\n    \"name\":    \"Laptop\",\n    \"price\": 1000,\n    \"in_stock\": true,\n    \"price\": 1200\n  }'\n);\n\nSELECT * FROM json_test;\n<\/pre><\/div>\n\n\n\n<p>In the output, you&#8217;ll notice that <code>data_json<\/code> preserves formatting such as spaces, line breaks and even duplicate keys &#8211; for example, multiple &#8220;price&#8221; entries. However, accessing duplicate keys can lead to unpredictable results.<\/p>\n\n\n\n<p>On the other hand, <code>data_jsonb<\/code> automatically removes extra whitespace, reorders the keys, and keeps only the last occurrence of any duplicate key, ensuring more consistent behavior for querying and indexing.<\/p>\n\n\n\n<p>This makes <code>jsonb<\/code> much more efficient for lookups and indexing, while <code>json<\/code> is better suited if you need to preserve the exact input format (like logging raw API responses).<\/p>\n\n\n\n<p>Notably, <code>jsonb<\/code> in PostgreSQL enforces stricter rules for numeric values, and will reject numbers that fall outside the range supported by PostgreSQL\u2019s numeric type.<\/p>\n\n\n\n<p>In contrast, the plain <code>json<\/code> type does not perform this validation &#8211; it stores the number as text, even if it&#8217;s extremely large or not valid for computation. Let\u2019s try this out in an example:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">INSERT INTO json_test (data_json)\nVALUES ('{\"large_number\": 1e1000000}');\n<\/pre><\/div>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"632\" height=\"219\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-33.png\" alt=\"\" class=\"wp-image-107908\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-33.png 632w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-33-300x104.png 300w\" sizes=\"auto, (max-width: 632px) 100vw, 632px\" \/><\/figure>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">INSERT INTO json_test (data_jsonb)\nVALUES ('{\"large_number\": 1e1000000}');\n<\/pre><\/div>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"558\" height=\"241\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-34.png\" alt=\"\" class=\"wp-image-107909\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-34.png 558w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-34-300x130.png 300w\" sizes=\"auto, (max-width: 558px) 100vw, 558px\" \/><\/figure>\n\n\n\n<p>The JSON type stored the number conveniently, while the JSONB type rejected the number because it falls outside the numeric range.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">JSON Functions and Operators in PostgreSQL<\/h2>\n\n\n\n<p>In PostgreSQL, JSON path expressions are used to navigate and extract values from JSON or JSONB documents. They help you point to specific keys, nested values or array elements inside your JSON data.<\/p>\n\n\n\n<p>PostgreSQL provides two main ways to work with JSON paths:<\/p>\n\n\n\n<p>i. Operators: such as -&gt;, -&gt;&gt;, #&gt;, #&gt;&gt;<\/p>\n\n\n\n<p>ii. Functions: such as <code>jsonb_path_query()<\/code>, <code>jsonb_set()<\/code><\/p>\n\n\n\n<p>Here are some built-in operators and functions to access and manipulate JSON or JSONB (binary JSON) data directly:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Operator<\/strong> &nbsp;<\/td><td><strong>Index type<\/strong><\/td><td><strong>What It Does<\/strong><\/td><td><strong>Example<\/strong><\/td><td><strong>Output<\/strong><\/td><\/tr><tr><td>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; -&gt;<\/td><td>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; text<\/td><td>Returns the JSON value for a given key, still in JSON format &nbsp;<\/td><td>SELECT &#8216;{&#8220;name&#8221;: &#8220;Aisha&#8221;, &#8220;age&#8221;: 30}&#8217;::json-&gt;&#8217;name&#8217; &nbsp;<\/td><td>&#8220;Aisha&#8221; (as JSON) &nbsp;<\/td><\/tr><tr><td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -&gt;&gt;<\/td><td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; text<\/td><td>Gets the value as plain text (not JSON)&nbsp;&nbsp; &nbsp; &nbsp;<\/td><td>SELECT&nbsp; &#8216;{&#8220;city&#8221;: &#8220;Lagos&#8221;, &#8220;country&#8221;: &#8220;Nigeria&#8221;}&#8217;::json-&gt;&gt;&#8217;country&#8217; &nbsp;<\/td><td>Nigeria<\/td><\/tr><tr><td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -&gt;<\/td><td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int<\/td><td>Gets the JSON value at a specific array index, still in JSON format&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<\/td><td>SELECT &#8216;[&#8220;blue&#8221;, &#8220;green&#8221;, &#8220;red&#8221;]&#8217;::json-&gt;1<\/td><td>&#8220;green&#8221; (as JSON) &nbsp;<\/td><\/tr><tr><td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -&gt;&gt;<\/td><td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int<\/td><td>Gets the array item at the given index as plain text<\/td><td>SELECT &#8216;[&#8220;pen&#8221;, &#8220;book&#8221;, &#8220;bag&#8221;]&#8217;::json-&gt;&gt;0 &nbsp; &nbsp; &nbsp;<\/td><td>&nbsp; pen<\/td><\/tr><tr><td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<\/td><td>text[] (path)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<\/td><td>Retrieves a nested JSON object using a path array<\/td><td>SELECT &#8216;{&#8220;user&#8221;: {&#8220;info&#8221;: {&#8220;email&#8221;: &#8220;me@example.com&#8221;}}}&#8217;::json#&gt;'{user,info}&#8217;;<\/td><td>{&#8220;email&#8221;: &#8220;me@example.com&#8221;} &nbsp;<\/td><\/tr><tr><td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #&gt;&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<\/td><td>text[] (path)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<\/td><td>Retrieves a nested value as plain text&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<\/td><td>SELECT &#8216;{&#8220;user&#8221;: {&#8220;tags&#8221;: [&#8220;dev&#8221;, &#8220;writer&#8221;]}}&#8217;::json#&gt;&gt;'{user,tags,1}&#8217;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<\/td><td>writer &nbsp;<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>There are several powerful operators available only for JSONB in PostgreSQL, such as @&gt;, &lt;@, ?, ?|, and more. For a full list of these operators and how they work, check out the official <a href=\"https:\/\/www.postgresql.org\/docs\/9.5\/functions-json.html\" target=\"_blank\" rel=\"noreferrer noopener\">PostgreSQL documentation.<\/a><\/p>\n\n\n\n<p>PostgreSQL also provides a rich set of JSON functions that help you create, query, and manipulate both JSON and jSONB data types. Below are some commonly used functions that you may find helpful:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Function<\/strong><\/td><td><strong>What It does<\/strong><\/td><td><strong>Example<\/strong><\/td><td><strong>Output<\/strong><\/td><\/tr><tr><td>jsonb_insert()&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<\/td><td>Inserts a value at a specified location in a JSONB array or object&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<\/td><td>SELECT jsonb_insert(&#8216;[10, 20, 30]&#8217;::jsonb, &#8216;{1}&#8217;, &#8217;15&#8217;);<\/td><td>[10, 15, 20, 30]<\/td><\/tr><tr><td>jsonb_set()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<\/td><td>Replaces or adds a value at a specified path in a JSONB document&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; &nbsp;<\/td><td>SELECT jsonb_set(&#8216;{&#8220;a&#8221;:1}&#8217;::jsonb, &#8216;{a}&#8217;, &#8216;100&#8217;);<\/td><td>{&#8220;a&#8221;:100}<\/td><\/tr><tr><td>jsonb_path_query()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<\/td><td>Queries JSONB using a JSONPath expression<\/td><td>SELECT jsonb_path_query(&#8216;{&#8220;tags&#8221;:[&#8220;sql&#8221;,&#8221;pg&#8221;]}&#8217;::jsonb, &#8216;$.tags[*]&#8217;);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<\/td><td>&#8220;sql&#8221; &#8220;pg&#8221; &nbsp;<\/td><\/tr><tr><td>json_extract_path(json, VARIADIC path) &nbsp;<\/td><td>Gets a nested value by specifying a path<\/td><td>SELECT json_extract_path(&#8216;{&#8220;a&#8221;:{&#8220;b&#8221;:10}}&#8217;, &#8216;a&#8217;, &#8216;b&#8217;);<\/td><td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 10<\/td><\/tr><tr><td>json_typeof(json)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<\/td><td>&nbsp; Returns the type of the value at the top level (object, array, etc.)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<\/td><td>SELECT json_typeof(&#8216;{&#8220;a&#8221;: 1}&#8217;);<\/td><td>&#8220;object&#8221;<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>There are other useful functions which were not included in this article. For a full list of the JSON functions and its properties, check out the official <a href=\"https:\/\/www.postgresql.org\/docs\/current\/functions-json.html\" target=\"_blank\" rel=\"noreferrer noopener\">PostgreSQL guide.<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Indexing JSON Data with GIN in PostgreSQL<\/h2>\n\n\n\n<p>When working with JSONB data in PostgreSQL, you can make your queries much faster by creating a <a href=\"https:\/\/www.postgresql.org\/docs\/current\/gin.html\" target=\"_blank\" rel=\"noreferrer noopener\">GIN (Generalized Inverted Index)<\/a>. This index is especially useful for searching inside JSON documents using keys and values. Without indexing, PostgreSQL scans every row to find matches inside your JSON.<\/p>\n\n\n\n<p>A GIN index allows PostgreSQL to jump straight to the rows that contain matching keys or values &#8211; just like an index in a book.<\/p>\n\n\n\n<p>Let&#8217;s start by creating a table and inserting some data:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">CREATE TABLE products (\nid SERIAL PRIMARY KEY,\ndetails JSONB\n);\nINSERT INTO products (details) VALUES\n('{\"name\": \"Laptop\", \"brand\": \"Dell\", \"in_stock\": true}'),\n('{\"name\": \"Phone\", \"brand\": \"Apple\", \"in_stock\": false}');<\/pre><\/div>\n\n\n\n<p>Let\u2019s check out the table and take note of the processing time:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SELECT * FROM products WHERE details @&gt; '{\"brand\": \"Apple\"}';<\/pre><\/div>\n\n\n\n<p>Now, let&#8217;s create a GIN index:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">CREATE INDEX idx_products_details ON products USING GIN (details); <\/pre><\/div>\n\n\n\n<p>Let\u2019s run the <code>SELECT<\/code> statement again and compare the query time:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SELECT * FROM products WHERE details @&gt; '{\"brand\": \"Apple\"}';<\/pre><\/div>\n\n\n\n<p>Notice how the GIN Index is faster? This is useful when you have large datasets. The only drawback is that GIN indexes only work on <strong>JSONB<\/strong>, not JSON.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">JSON(MySQL) vs. JSON (PostgreSQL)<\/h2>\n\n\n\n<p>MySQL stores JSON data in a binary format, while PostgreSQL stores JSON data in a text format. One key difference is how each handles formatting &#8211; MySQL\u2019s JSON strips away whitespace and formatting for storage efficiency, while PostgreSQL\u2019s JSON type preserves the original formatting, including spaces and line breaks.<\/p>\n\n\n\n<p>Another important distinction is how they handle duplicate keys in a JSON object. MySQL enforces strict JSON rules and does not allow duplicate keys. If you try to insert a JSON document with duplicate keys, MySQL will throw an error and reject it, and only one unique key per object is allowed.<\/p>\n\n\n\n<p>In contrast, PostgreSQL\u2019s json type does allow duplicate keys and stores them exactly as provided. However, keep in mind that querying such documents may yield unpredictable results since key order and duplication can affect which value gets returned. Let\u2019s explore this in an example:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">CREATE TABLE json_test (\n    id SERIAL PRIMARY KEY,\n    data_json  JSON\n);\nINSERT INTO json_test (data_json)\nVALUES (\n  '{\n    \"name\":    \"Laptop\",\n    \"price\": 1000,\n    \"in_stock\": true,\n    \"price\": 1200\n  }'\n);\nSELECT * FROM json_test;\n<\/pre><\/div>\n\n\n\n<p><strong>MySQL\u2019s Result:<\/strong><\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"641\" height=\"128\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-35.png\" alt=\"\" class=\"wp-image-107910\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-35.png 641w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-35-300x60.png 300w\" sizes=\"auto, (max-width: 641px) 100vw, 641px\" \/><\/figure>\n\n\n\n<p><strong>PostgreSQL\u2019s Result:<\/strong><\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"864\" height=\"475\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-36.png\" alt=\"\" class=\"wp-image-107911\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-36.png 864w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-36-300x165.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/11\/image-36-768x422.png 768w\" sizes=\"auto, (max-width: 864px) 100vw, 864px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">MySQL vs PostgreSQL: Comparison of JSON Data Type<\/h2>\n\n\n\n<p>As modern applications increasingly rely on semi-structured data formats like JSON for storing user preferences, settings, and API responses, both MySQL and PostgreSQL have adapted to support JSON storage and manipulation.<\/p>\n\n\n\n<p>While both databases offer JSON functionality, they do so in very different ways. MySQL provides a simpler, more lightweight approach suitable for basic use cases, while PostgreSQL offers advanced tools, rich querying capabilities, and better performance for complex JSON operations &#8211; especially when using <strong>JSONB<\/strong>.<\/p>\n\n\n\n<p>Below is a detailed comparison table highlighting how each database handles JSON data across storage, querying, indexing, and performance. This will help you understand the strengths and trade-offs of each, and decide which is best suited for your application\u2019s needs.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Category<\/strong><\/td><td><strong>MySQL<\/strong><\/td><td><strong>PostgreSQL<\/strong><\/td><\/tr><tr><td><strong>JSON Type Introduced&nbsp;&nbsp;<\/strong><\/td><td>Version 5.7&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<\/td><td>Version 9.2 (JSON), 9.4 (JSONB)<\/td><\/tr><tr><td><strong>Available Data Types&nbsp;&nbsp;&nbsp;&nbsp;<\/strong><\/td><td>Only JSON&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<\/td><td>JSON and JSONB<\/td><\/tr><tr><td><strong>Storage Format<\/strong><\/td><td>JSON is stored in a binary format internally&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<\/td><td>JSON: Stores exact text as input JSONB: Stores as binary, parsed and optimized &nbsp;<\/td><\/tr><tr><td><strong>Formatting Preservation&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<\/strong><\/td><td>Does not preserve formatting and whitespaces.<\/td><td>JSON: Preserves formatting and whitespaces JSONB: Strips whitespace and formatting. &nbsp;<\/td><\/tr><tr><td><strong>Duplicate Key Handling<\/strong><\/td><td>Does not preserve duplicate keys. Keeps only the last value of duplicate keys.<\/td><td>JSON: Preserves duplicate keys JSONB: Removes duplicate keys and keeps only the last value of duplicate keys &nbsp;<\/td><\/tr><tr><td><strong>Maximum Size Limit<\/strong><\/td><td>1GB<\/td><td>1GB for both JSON and JSONB &nbsp;<\/td><\/tr><tr><td><strong>Indexing Support&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/strong>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<\/td><td>&nbsp;No direct indexing on JSON Can create virtual columns and index them<\/td><td>&nbsp;It has a powerful GIN indexing on JSONB fields<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>While JSON is incredibly flexible, it does come with a few limitations, one of them being its limited support for certain data types like dates and timestamps. This can make working with time-related data a bit tricky.<\/p>\n\n\n\n<p>If you haven\u2019t explored our <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/mysql\/mysql-vs-postgresql-date-and-time-data-types\/\" target=\"_blank\" rel=\"noreferrer noopener\">Date and Time data type series<\/a> yet, now\u2019s a great time to check it out! It\u2019ll help you understand how to handle those types of data more effectively.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-json-support-in-mysql-and-postgresql-which-is-best\">JSON Support in MySQL and PostgreSQL: Which is Best?<\/h2>\n\n\n\n<p>If you&#8217;re working with simple JSON documents and your use case doesn\u2019t involve deep querying or advanced indexing, MySQL is a solid and easy-to-use option. It offers native JSON support, a good set of functions, and performs well for lightweight tasks. It\u2019s great if you\u2019re already invested in the MySQL ecosystem and your JSON needs are basic.<\/p>\n\n\n\n<p>However, if your application requires advanced querying (like deep nested searches), flexible indexing (such as full-text and key\/value searches), or needs to store large or complex JSON documents efficiently, then PostgreSQL, particularly with its JSONB type, is the better choice. It offers superior indexing with GIN and better performance for querying semi-structured data.<\/p>\n\n\n\n<section id=\"faq\" class=\"faq-block my-5xl\">\n    <h2>FAQs: JSON Support in MySQL and PostgreSQL<\/h2>\n\n                        <h3 class=\"mt-4xl\">1. Does MySQL support JSON data types?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Yes. MySQL introduced native JSON support in version 5.7. It allows you to store JSON documents and use built-in functions like <code>JSON_OBJECT<\/code> and <code>JSON_EXTRACT<\/code> for querying and manipulation.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">2. What is the difference between PostgreSQL json and jsonb?<\/h3>\n            <div class=\"faq-answer\">\n                <p>PostgreSQL offers two JSON types:<\/p>\n<ul>\n<li><strong>json<\/strong> stores data as plain text.<\/li>\n<li><strong>jsonb<\/strong> stores data in a binary format, enabling faster indexing and advanced operations. For most use cases, jsonb is recommended.<\/li>\n<\/ul>\n            <\/div>\n                    <h3 class=\"mt-4xl\">3. Which database is better for JSON performance: MySQL or PostgreSQL?<\/h3>\n            <div class=\"faq-answer\">\n                <p>PostgreSQL generally outperforms MySQL for complex JSON queries due to its jsonb type and support for GIN indexes. MySQL is simpler for basic JSON storage but lacks advanced indexing options.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">4. Can you index JSON fields in MySQL and PostgreSQL?<\/h3>\n            <div class=\"faq-answer\">\n                <ul>\n<li><strong>MySQL:<\/strong> You can create virtual columns from JSON fields and index them.<\/li>\n<li><strong>PostgreSQL:<\/strong> Supports GIN and BTREE indexes on jsonb fields, making queries much faster.<\/li>\n<\/ul>\n            <\/div>\n                    <h3 class=\"mt-4xl\">5. When should I use JSON in a relational database?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Use JSON for semi-structured data or when schema flexibility is needed. However, avoid overusing JSON for highly relational data, as it can impact performance and complicate queries.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">6. What are common JSON functions in MySQL and PostgreSQL?<\/h3>\n            <div class=\"faq-answer\">\n                <ul>\n<li><strong>MySQL:<\/strong> <code>JSON_OBJECT<\/code>, <code>JSON_ARRAY<\/code>, <code>JSON_EXTRACT<\/code>, <code>JSON_TABLE<\/code><\/li>\n<li><strong>PostgreSQL:<\/strong> <code>jsonb_each<\/code>, <code>jsonb_array_elements<\/code>, <code>jsonb_set<\/code>, <code>jsonb_path_query<\/code><\/li>\n<\/ul>\n            <\/div>\n                    <h3 class=\"mt-4xl\">7. Is jsonb always better than json in PostgreSQL?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Yes, in most cases. jsonb offers better performance, indexing, and supports advanced operators. Use json only if you need to preserve exact formatting.<\/p>\n            <\/div>\n            <\/section>\n\n\n\n<section class=\"\">\n    <h1 class=\"font-display mt-0 mb-sm\"><\/h1>\n    <div class=\"mb-3xl\">\n            <\/div>\n    <ul class=\"list-none pl-0 mb-0\">\n        <\/ul>\n<\/section>\n\n\n<section id=\"my-first-block-block_f8ff0a1cabeb5c59a2c71af4255293d5\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">Subscribe to the Simple Talk newsletter<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            Get selected articles, event information, podcasts and other industry content delivered straight to your inbox every two weeks.                                    <\/div>\n            <\/div>\n                            <a href=\"https:\/\/www.red-gate.com\/simple-talk\/subscribe\/\" class=\"btn btn--secondary btn--lg\">Subscribe now<\/a>\n                    <\/div>\n    <\/div>\n<\/section>","protected":false},"excerpt":{"rendered":"<p>MySQL and PostgreSQL both support JSON, but they handle it in very different ways. This article compares their JSON data types, performance, and use cases to help you choose the right database for your application.&hellip;<\/p>\n","protected":false},"author":341597,"featured_media":107923,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143523,53,145792,143534],"tags":[159268,5854,158978,4150],"coauthors":[158988],"class_list":["post-107890","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-databases","category-featured","category-mysql","category-postgresql","tag-aishabukar_mysql_postgresql","tag-mysql","tag-postgresql","tag-sql"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/107890","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/341597"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=107890"}],"version-history":[{"count":7,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/107890\/revisions"}],"predecessor-version":[{"id":107965,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/107890\/revisions\/107965"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media\/107923"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=107890"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=107890"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=107890"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=107890"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}