Reputation:
Is there a way in MySQL to use JSON_SEARCH()
or INSTR()
to filter rows by the whole word inside of any available JSON values without knowing their keys?
Test data - but keys can be other locales, not only those provided here:
{ "hr_HR":"Konzum Zagreb Tower", "sr_RS":"Konzum in Zagreb Tower", "en_US":"Konzum Zagreb Tower" }
{ "hr_HR":"Wolt Zagreba", "sr_RS":"Wolt Zagrebu", "en_US":"Wolt Zagreb" }
{ "hr_HR":"Glovo Zagreb", "de_DE":"Glovo Zagreb", "en_US":"Glovo Zagreb" }
{ "hr_HR":"Bolt Zagreb", "it_IT":"Bolt Zagreb", "en_US":"Bolt Zagreb" }
{ "hr_HR":"Mlinar - Zagreb", "sr_RS":"Mlinar - Zagreb", "fr_FR":"Zagreb Mlinar" }
{ "hr_HR":"Curry Bowl Zagreb", "sr_RS":"Curry Bowl Zagreb", "en_GB":"Curry Bowl Zagreb" }
{ "hr_HR":"INA - Siget", "sr_RS":"INA - Siget", "en_IE":"INA - Siget" }
{ "hr_HR":"INA", "sr_RS":"INA", "en_US":"INA" }
{ "hr_HR":"INA - Selska", "sr_RS":"INA - Selska", "en_US":"INA - Selska" }
This one does not work as expected (meaning my expectation was that *
would be marking any key):
SELECT id, name FROM provider
WHERE
INSTR(
CONCAT( ' ', LOWER( name->>'$.*' ), ' ' ),
CONCAT( ' ', 'zagreb', ' ' )
) > 0;
The query that works for a given key/locale (hardcoded):
SELECT id, name FROM provider
WHERE
INSTR(
CONCAT( ' ', LOWER( name->>'$.hr_HR' ), ' ' ),
CONCAT( ' ', 'zagreb', ' ' )
) > 0;
But I don't want to hardcode all keys/locales into a query and use OR
to check them all:
SELECT id, name
FROM provider
WHERE
INSTR(CONCAT(' ', LOWER(NAME->>'$.hr_HR'), ' '), CONCAT(' ', 'zagreb', ' ')) > 0
OR INSTR(CONCAT(' ', LOWER(NAME->>'$.sr_RS'), ' '), CONCAT(' ', 'zagreb', ' ')) > 0
OR INSTR(CONCAT(' ', LOWER(NAME->>'$.en_US'), ' '), CONCAT(' ', 'zagreb', ' ')) > 0
OR INSTR(CONCAT(' ', LOWER(NAME->>'$.en_GB'), ' '), CONCAT(' ', 'zagreb', ' ')) > 0
OR INSTR(CONCAT(' ', LOWER(NAME->>'$.en_IE'), ' '), CONCAT(' ', 'zagreb', ' ')) > 0
OR INSTR(CONCAT(' ', LOWER(NAME->>'$.fr_FR'), ' '), CONCAT(' ', 'zagreb', ' ')) > 0
OR INSTR(CONCAT(' ', LOWER(NAME->>'$.it_IT'), ' '), CONCAT(' ', 'zagreb', ' ')) > 0;
Upvotes: 0
Views: 46
Reputation: 562871
In MySQL 8.0 you can use JSON_TABLE()
twice, to get keys and then to get values:
SELECT id, l.locale, j.location
FROM provider
CROSS JOIN JSON_TABLE(JSON_KEYS(name), '$[*]'
COLUMNS (locale VARCHAR(10) PATH '$')) AS l
CROSS JOIN JSON_TABLE(JSON_EXTRACT(name, CONCAT('$.', l.locale)), '$'
COLUMNS (`location` varchar(20) PATH '$')) AS j
WHERE j.location LIKE '%Zagreb%' ;
Output:
+----+--------+---------------------+
| id | locale | location |
+----+--------+---------------------+
| 1 | en_US | Konzum Zagreb Tower |
| 1 | hr_HR | Konzum Zagreb Tower |
| 2 | en_US | Wolt Zagreb |
| 2 | hr_HR | Wolt Zagreba |
| 2 | sr_RS | Wolt Zagrebu |
| 3 | de_DE | Glovo Zagreb |
| 3 | en_US | Glovo Zagreb |
| 3 | hr_HR | Glovo Zagreb |
| 4 | en_US | Bolt Zagreb |
| 4 | hr_HR | Bolt Zagreb |
| 4 | it_IT | Bolt Zagreb |
| 5 | fr_FR | Zagreb Mlinar |
| 5 | hr_HR | Mlinar - Zagreb |
| 5 | sr_RS | Mlinar - Zagreb |
| 6 | en_GB | Curry Bowl Zagreb |
| 6 | hr_HR | Curry Bowl Zagreb |
| 6 | sr_RS | Curry Bowl Zagreb |
+----+--------+---------------------+
If you can change the format of the JSON documents:
INSERT INTO provider (name) VALUES
('[{ "locale": "hr_HR", "location": "Konzum Zagreb Tower"}, {"locale": "sr_RS", "location":"Konzum in Zagreb Tower"}, {"locale": "en_US", "location":"Konzum Zagreb Tower" }]'),
('[{ "locale": "hr_HR", "location": "Wolt Zagreba"}, {"locale": "sr_RS", "location":"Wolt Zagrebu"}, {"locale": "en_US", "location":"Wolt Zagreb" }]'),
('[{ "locale": "hr_HR", "location": "Glovo Zagreb"}, {"locale": "de_DE", "location":"Glovo Zagreb"}, {"locale": "en_US", "location":"Glovo Zagreb" }]'),
('[{ "locale": "hr_HR", "location": "Bolt Zagreb"}, {"locale": "it_IT", "location":"Bolt Zagreb"}, {"locale": "en_US", "location":"Bolt Zagreb" }]'),
('[{ "locale": "hr_HR", "location": "Mlinar - Zagreb"}, {"locale": "sr_RS", "location":"Mlinar - Zagreb"}, {"locale": "fr_FR", "location":"Zagreb Mlinar" }]'),
('[{ "locale": "hr_HR", "location": "Curry Bowl Zagreb"}, {"locale": "sr_RS", "location":"Curry Bowl Zagreb"}, {"locale": "en_GB", "location":"Curry Bowl Zagreb" }]'),
('[{ "locale": "hr_HR", "location": "INA - Siget"}, {"locale": "sr_RS", "location":"INA - Siget"}, {"locale": "en_IE", "location":"INA - Siget" }]'),
('[{ "locale": "hr_HR", "location": "INA"}, {"locale": "sr_RS", "location":"INA"}, {"locale": "en_US", "location":"INA" }]'),
('[{ "locale": "hr_HR", "location": "INA - Selska"}, {"locale": "sr_RS", "location":"INA - Selska"}, {"locale": "en_US", "location":"INA - Selska" }]');
Then it could be done with a single use of JSON_TABLE()
.
SELECT id, j.locale, j.location
FROM provider
CROSS JOIN JSON_TABLE(name, '$[*]'
COLUMNS (`locale` VARCHAR(10) PATH '$.locale', `location` varchar(20) PATH '$.location')) AS j
WHERE j.location LIKE '%Zagreb%' ;
Or if you forget about storing data in JSON, it will be even easier:
SELECT id, location
FROM provider
WHERE locale LIKE '%Zagreb%';
This brings up the point that using JSON is harder than using normal rows and columns in a relational database. Trying to use JSON as though it is a relational table is an exercise in the Inner-Platform Effect.
Upvotes: 1