40

I have a table with a column for storing a JSON value for each row. Currently, these are not standardized. I'd like to end up with each of these JSON values being standardized in number and title of attributes. Is there a query I can use to determine whether or not each JSON value contains a specified attribute?

As an example, here are what some of the JSON values look like:

{"name":"Item 1","cost":"4.99"}
{"name":"Item 2"}
{"name":"Item 3","cost":""}
{"name":"Item 4"}

How do I:

  • Determine which rows have a "cost" attribute (items 1 & 3)
  • Determine which rows do not have a "cost" attribute (items 2 & 4)
  • Determine which rows have a value set for the "cost" attribute (item 1)

Thank you! This is my first time asking (at least recently) so any help is greatly appreciated!

Matthew J Gravelyn
  • 441
  • 1
  • 4
  • 7

4 Answers4

69

I assume you're using MySQL 5.7, which adds the JSON data type. Use JSON_EXTRACT(colname, '$.cost') to access the cost property. It will be NULL is there's no such property.

  1. WHERE JSON_EXTRACT(colname, '$.cost') IS NOT NULL
  2. WHERE JSON_EXTRACT(colname, '$.cost') IS NULL
  3. WHERE JSON_EXTRACT(colname, '$.cost') != ''

It will also be NULL if the value in the JSON is null; if you need to distinguish this case, see Can't detect null value from JSON_EXTRACT

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • 1
    Bummer that I can't differentiate between the attribute not being there vs. the attribute being there, but with no value. Still, this is pretty close and will help me greatly. Thanks! – Matthew J Gravelyn Sep 27 '16 at 19:55
  • Do you every have `"cost": null` in your JSON data? – Barmar Sep 27 '16 at 19:56
  • Some of the rows have a "cost" attribute, but not all. My goal was to try and quickly sort out the rows that didn't have it, so I could add one in, then go through with a JSON modify function to update just that piece, rather than updating the JSON string as a whole. – Matthew J Gravelyn Sep 27 '16 at 21:49
  • That should work. The only thing you can't distinguish is no cost attribute from `"cost": null`. `"cost": ""` is still distinguishable. – Barmar Sep 27 '16 at 21:50
  • Your answer is actually accurate. MySQL does distinguish the SQL `NULL` from JSON's `null`. You could remove the last statement. Refer to this answer for more details: https://stackoverflow.com/a/41172432/1128918 – Gustavo Straube Aug 14 '19 at 13:19
  • I've replaced it with a link to that question, thanks. – Barmar Aug 14 '19 at 15:31
5

If you need to only check, whether the json key exists, you can use JSON_CONTAINS_PATH. I expect it to be faster than JSON_EXTRACT.

mySQL 8.0 docs

Example:

I have a permission column in the DB. The structure looks like this:

{
    "users": {
        "user1" : "rw",
        "user2" : "r"
    },
    "groups": {
        "root": "rw",
        "anotherGroup" : "r"
    }
}

If I want to get all the items, that have the root group (regarding of the actual permissions), I use this:

SELECT * FROM `filesystem_folders` where JSON_CONTAINS_PATH(permissions, 'one', '$.groups.root');

I made just a very simple time comparison between the select above which took 12.90s (10000 reads). Vs the following select using JSON_EXTRACT

 SELECT * FROM `filesystem_folders` where JSON_EXTRACT(permissions, '$.groups.root') IS NOT NULL;

which took 15.13s. So, quite a small difference.

Peter Matisko
  • 2,113
  • 1
  • 24
  • 47
1

Actually you can do it by comparing the JSON_EXTRACT result to itself:

WHERE JSON_EXTRACT(colname, '$."cost"') = JSON_EXTRACT(colname, '$."cost"')

This is going to be somehow "false" / null, if there is no corresponding key inside your JSON column.

This way you are able to find all entries that have the key you search for. If you want all the entries that dont have the key you somehow can't use the NOT operator. You have to do it like this:

SELECT * FROM tablename WHERE id NOT IN (SELECT id FROM tablename WHERE WHERE JSON_EXTRACT(colname, '$."cost"') = JSON_EXTRACT(colname, '$."cost"'))

I know it looks like voodoo, but it seems to work.

sanastasiadis
  • 1,182
  • 1
  • 15
  • 23
Lucian Depold
  • 1,999
  • 2
  • 14
  • 25
0

If you are using a MySQL without JSON functions (pre 5.7), or want to check for any tag in lists of, or unpredictably nested JSON.

Here is an example of how to do it with LIKE:

SELECT case when colname like '%cost%' then 1 else 0 end as has_cost,
       case when colname like '%cost":""%' then 1 else 0 end as has_cost_with_no_value
FROM tablename
Stian
  • 1,221
  • 1
  • 19
  • 26
  • MySQL supports JSON queries, this is way, way overboard for what you need to do. See other answers. – ggdx Dec 09 '19 at 10:50
  • This a very dirty way and not right at all. MySQL provides native functionality (see other posts) to resolve JSON tasks – Thanasis Dec 12 '22 at 15:54
  • Pre version 5.7? As far as i am aware JSON support was first implemented in 5.7 – Stian Dec 15 '22 at 16:49