MySQL: get rows with value in array of strings [duplicate] - mysql

I'm still pretty new to handling JSON fields in MySQL. All the solutions I've come across deal with objects that have key/values; unable to find one that handles JSON arrays.
Anyways, what I want to do is to be able to select all rows where the interestIds contain 2 in them. How do I do that? Thanks.
Users table
+----+-------------+
| id | interestIds |
+----+-------------+
| 1 | [1, 2] |
| 2 | [3, 2] |
| 3 | [2, 4] |
+----+-------------+
Sample test query:
SET #userId = 2;
SELECT * FROM Users
WHERE #userId IN JSON_CONTAINS(#user, interestIds, '$[1]');
I am confused as how to use the JSON_* functions; not sure what to put for the 3rd parameter...

You can use the following solution, using JSON_CONTAINS:
SELECT *
FROM Users
WHERE JSON_CONTAINS(interestIds, '2') = 1;
The third (optional) paramater path gives you the posibility to use this function only on a specific part of your JSON value. So the following example checks if 2 is the second value of the array:
SELECT *
FROM test
WHERE JSON_CONTAINS(interestIds, '2', '$[1]') = 1;
demo on dbfiddle.uk

Use JSON_SEARCH which returns path to element you are searching, or null if not found:
SELECT *
FROM users
WHERE JSON_SEARCH(interestids, 'one', '2') IS NOT NULL
Live Demo
If you're storing many-to-many relationship using simple JSON array, there are better ways to do it. Consider creating user_interest table and doing it the right and simpler way. That is if your JSON actually looks like you have shown us and does not contain dynamic key-value pairs.

SQL> select id
from users
where JSON_CONTAINS(interestIds, "2","$");
+----+
| id |
+----+
| 1 |
| 2 |
| 3 |
+----+
3 rows in set (0.0015 sec)

Wrap your select to JSON_ARRAYAGG
Like:
SELECT JSON_ARRAYAGG(JSON_OBJECT(....)) FROM table....

Related

Removing an element in an MySQL's JSON array in a column

I have a "table" named users with a column name "group_in" which stores an array like [1,2,3].
I am trying to remove an element with a value (eg. 1) for a specified row (eg id:1).
Before:
id| group_in
1 | **[1,2,3]**
2 | [1,3]
After:
id | group_in
1 | **[2,3]**
2 | [1,3]
I have tried the following:-
Update users
SET group_in = JSON_REMOVE(group_in,JSON_UNQUOTE(JSON_search(group_in, 'one', 1)))
where id = 1
but I got back is
id | group_in
1 | null
2 | [1,3]
Screenshot of my table and query result for your reference.
My table
Result gotten
Please help me if you know how to solve it
Thank you 🙏
I believe JSON_SEARCH works on strings, not sure, if it was extended to integer searches as well.
One way is to flatten the array and then recombine it while excluding the value as needed.
Query -
update users set group_in = (select new_grp from (
select id,json_arrayagg(grp) new_grp from users,
JSON_TABLE(group_in, "$[*]" COLUMNS(grp INT PATH '$')) as grp_id
where id=1
and grp<>1
group by id
)X
)
where id=1;
Refer fiddle here.
Try this,
UPDATE `channels` SET `group_in` = JSON_REMOVE(`group_in`, '$[2]') WHERE `id` = 1 ;

How to regexp in MySql json string

Let's assume this users table:
-----------------------------------------
| id | ... | info |
-----------------------------------------
| 1 | ... | {"items":["132","136"]} |
I need to make a request to fetch users that have items with id == 136.
This following is the sql I built but it does not work and I dont understand why:
SELECT _u.id FROM users _u WHERE _u.info REGEXP '("items":)([)("136")(])'
Thank you in advance!
Here is one approach using the MySQL JSON functions:
SELECT *
FROM yourTable
WHERE JSON_SEARCH(JSON_EXTRACT(json, "$.items"), 'one', "136") IS NOT NULL;
Demo
The call to JSON_EXTRACT first extracts the JSON array under the items key. Then, we use JSON_SEARCH to try to find an element "136".
Edit:
If you are certain that the JSON to be searched would always just be one key items along with a single level JSON array, then REGEXP might be viable here:
SELECT *
FROM yourTable
WHERE json REGEXP '"items":\\[.*"136".*\\]';
Demo

PostgreSQL - return jsonb keys in one column

I have the query to search for ids in jsonb column, the array can contain many id's.
Say I have data like this
id | act | act_id | from_ids | object_ids | post_date
2 post 1 {"2":"1494308197","3":"1494308198","4":"1494308199"} {"items":["104564"]} 1494308197
And a query like this
SELECT an.*
FROM activity_network an
WHERE an.from_ids ?| ARRAY['2','3'];
That query will return the row because it finds 2 and 3. But how can I return what it finds in it's own column. So that it returns 2,3 in text or json format or something like that in the results as well.
I tried this
SELECT an.*, jsonb_each_text(from_ids) b
FROM activity_network an
WHERE an.from_ids ?| ARRAY['2','3'];
But that creates 3 rows with a b column each one with the value 2, 3 and 4. I want 1 row with b column containing both 2 and 3 which is what I searched on.
Is that possible?
example result that I'm looking for. notice the last column. I put it as column delimited for demo purpose. it can be any format I can use.
2 | post | 1 | {"2":"1494308197","3":"1494308198","4":"1494308199} | {"items":["104564"]} | 1494308197 | 2,3}
here I explode/implode it. Quite ugly way.
t=# with p as (
with c as (
select '{"2":"1494308197","3":"1494308198","4":"1494308199"}'::json j
)
select json_object_keys(j),j->json_object_keys(j) v
from c
)
select concat('{',string_agg(concat('"',json_object_keys,'"',':',v)::text,','),'}')::json
from p
where json_object_keys::int = ANY (ARRAY [2,4]);
concat
-------------------------------------
{"2":"1494308197","4":"1494308199"}
(1 row)
Time: 0.348 ms
The function jsonb_exists_all sounds like what you want. It requires that all the elements in the array exist as top-level keys in the object.
You can find that and other undocumented functions for jsonb using the \df *jsonb* command in psql.
example paste:
test=# SELECT * from twj WHERE jsonb_exists_any(from_ids, ARRAY['2','3']);
id | act | from_ids
----+------+-----------------------------------------------------------
1 | post | {"2": "1494308197"}
3 | post | {"2": "1494308197", "3": "1494308198", "4": "1494308199"}
(2 rows)
test=# SELECT * from twj WHERE jsonb_exists_all(from_ids, ARRAY['2','3']);
id | act | from_ids
----+------+-----------------------------------------------------------
3 | post | {"2": "1494308197", "3": "1494308198", "4": "1494308199"}
(1 row)
The ?| operator you are using calls the jsonb_exists_any function.

MySQL - GROUP_CONCAT if value is not a substring

I have a column called "Permissions" in my table. The permissions are strings which can be:
"r","w","x","rw","wx","rwx","xwr"
etc. Please note the order of characters in the string is not fixed. I want to GROUP_CONCAT() on the "Permissions" column of my table. However this causes very large strings.
Example: "r","wr","wx" group concatenated is "r,wr,wx" but should be "r,w,x" or "rwx". Using distinct() clause doesn't seem to help much. I am thinking that if I could check if a permission value is a substring of the other column then I should not concatenate it, but I don't seem to find a way to accomplish that.
Any column based approach using solely string functions would also be appreicated.
EDIT:
Here is some sample data:
+---------+
| perm |
+---------+
| r,x,x,r |
| x |
| w,rw |
| rw |
| rw |
| x |
| w |
| x,x,r |
| r,x |
+---------+
The concatenated result should be:
+---------+
| perm |
+---------+
| r,w,x |
+---------+
I don't have control over the source of data and would like not to create new tables ( because of restricted privileges and memory constraints). I am looking for a post-processing step that converts each column value to the desired format.
A good idea would be to first normalize your data.
You could, for example try this way (I assume your source table is named Files):
Create simple table called PermissionCodes with only column named Code (type of string).
Put r, w, and x as values into PermissionCodes (three rows total).
In a subquery join Files to PermissionCodes on a condition that Code exists as a substring in Permissions.
Perform your GROUP_CONCAT aggregation on the result of the subquery.
If it is a case here, that for the same logical entires in Files there exists multiple permission sets that overlaps (i.e. for some file there is a row with rw and another row with w) then you would limit your subquery to distinct combinations of Files' keys and Code.
Here's a fiddle to demonstrate the idea:
http://sqlfiddle.com/#!9/6685d6/4
You can try something like:
SELECT user_id, GROUP_CONCAT(DISTINCT perm)
FROM Permissions AS p
INNER JOIN (SELECT 'r' AS perm UNION ALL
SELECT 'w' UNION ALL
SELECT 'x') AS x
ON p.permission LIKE CONCAT('%', x.perm, '%')
GROUP BY user_id
You can include any additional permission code in the UNION ALL of the derived table used to JOIN with Permissions table.
Demo here

Get path with specific number of values

I have a SQL table with the following values:
+---------+----------+
| post_id | path |
+---------+----------+
| 1 | 1/ |
| 2 | 1/2/ |
| 3 | 1/2/3/ |
| 4 | 1/2/3/4/ |
| 5 | 1/2/5/ |
+---------+----------+
How can I create a query that would get the path with the exact number of values separated by slashes?
For example, if I wanted all post_ids where the path is exactly 1/%/%/ (where each % represents a single number), meaning return anything of the form 1/2/3/, 1/2/5/, but not 1/2/3/4/.
Here's one option using regexp:
select *
from yourtable
where path regexp '1/[0-9]/[0-9]/$'
SQL Fiddle Demo
There are several ways to do that:
MySQL LIKE operator.
The LIKE operator provides two wildcard characters, the percentage % ( match any string of zero or more characters), and underscore _ ( match any single character ).
SELECT * FROM `table` WHERE `path` LIKE '1/_/_/'
SELECT * FROM `table` WHERE `path` LIKE '1/%/%/'
MySQL Regular Expressions.
SELECT * FROM `table` WHERE `path` regexp '^1/[0-9]/[0-9]/$'
Hierarchical Data in MySQL
Since this structure involves hierarchical data maybe you should consider to change the table structure to something that represents actual hierarchy. http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/ has an excellent tutorial about the subject.