This question follows on from MYSQL join results set wiped results during IN () in where clause?
So, short version of the question. How do you turn the string returned by GROUP_CONCAT into a comma-seperated expression list that IN() will treat as a list of multiple items to loop over?
N.B. The MySQL docs appear to refer to the "( comma, seperated, lists )" used by IN () as 'expression lists', and interestingly the pages on IN() seem to be more or less the only pages in the MySQL docs to ever refer to expression lists. So I'm not sure if functions intended for making arrays or temp tables would be any use here.
Long example-based version of the question: From a 2-table DB like this:
SELECT id, name, GROUP_CONCAT(tag_id) FROM person INNER JOIN tag ON person.id = tag.person_id GROUP BY person.id;
+----+------+----------------------+
| id | name | GROUP_CONCAT(tag_id) |
+----+------+----------------------+
| 1 | Bob | 1,2 |
| 2 | Jill | 2,3 |
+----+------+----------------------+
How can I turn this, which since it uses a string is treated as logical equivalent of ( 1 = X ) AND ( 2 = X )...
SELECT name, GROUP_CONCAT(tag.tag_id) FROM person LEFT JOIN tag ON person.id = tag.person_id
GROUP BY person.id HAVING ( ( 1 IN (GROUP_CONCAT(tag.tag_id) ) ) AND ( 2 IN (GROUP_CONCAT(tag.tag_id) ) ) );
Empty set (0.01 sec)
...into something where the GROUP_CONCAT result is treated as a list, so that for Bob, it would be equivalent to:
SELECT name, GROUP_CONCAT(tag.tag_id) FROM person INNER JOIN tag ON person.id = tag.person_id AND person.id = 1
GROUP BY person.id HAVING ( ( 1 IN (1,2) ) AND ( 2 IN (1,2) ) );
+------+--------------------------+
| name | GROUP_CONCAT(tag.tag_id) |
+------+--------------------------+
| Bob | 1,2 |
+------+--------------------------+
1 row in set (0.00 sec)
...and for Jill, it would be equivalent to:
SELECT name, GROUP_CONCAT(tag.tag_id) FROM person INNER JOIN tag ON person.id = tag.person_id AND person.id = 2
GROUP BY person.id HAVING ( ( 1 IN (2,3) ) AND ( 2 IN (2,3) ) );
Empty set (0.00 sec)
...so the overall result would be an exclusive search clause requiring all listed tags that doesn't use HAVING COUNT(DISTINCT ... ) ?
(note: This logic works without the AND, applying to the first character of the string. e.g.
SELECT name, GROUP_CONCAT(tag.tag_id) FROM person LEFT JOIN tag ON person.id = tag.person_id
GROUP BY person.id HAVING ( ( 2 IN (GROUP_CONCAT(tag.tag_id) ) ) );
+------+--------------------------+
| name | GROUP_CONCAT(tag.tag_id) |
+------+--------------------------+
| Jill | 2,3 |
+------+--------------------------+
1 row in set (0.00 sec)
The GROUP_CONCAT() function in MySQL is used to concatenate data from multiple rows into one field. This is an aggregate (GROUP BY) function which returns a String value, if the group contains at least one non-NULL value. Otherwise, it returns NULL.
The SEPARATOR specifies a literal value inserted between values in the group. If you do not specify a separator, the GROUP_CONCAT function uses a comma (,) as the default separator. The GROUP_CONCAT function ignores NULL values.
The difference here is while CONCAT is used to combine values across columns, GROUP_CONCAT gives you the capability to combine values across rows. It's also important to note that both GROUP_CONCAT and CONCAT can be combined to return desired results.
Instead of using IN()
, would using FIND_IN_SET()
be an option too?
http://dev.mysql.com/doc/refman/5.0/en/string-functions.html#function_find-in-set
mysql> SELECT FIND_IN_SET('b','a,b,c,d');
-> 2
Here's a full example based on the example problem in the question, confirmed as tested by the asker in an earlier edit to the question:
SELECT name FROM person LEFT JOIN tag ON person.id = tag.person_id GROUP BY person.id
HAVING ( FIND_IN_SET(1, GROUP_CONCAT(tag.tag_id)) ) AND ( FIND_IN_SET(2, GROUP_CONCAT(tag.tag_id)) );
+------+
| name |
+------+
| Bob |
+------+
You can pass a string as array, using a split separator, and explode it in a function, that will work with the results.
For a trivial example, if you have a string array like this: 'one|two|tree|four|five', and want to know if two is in the array, you can do this way:
create function str_in_array( split_index varchar(10), arr_str varchar(200), compares varchar(20) )
returns boolean
begin
declare resp boolean default 0;
declare arr_data varchar(20);
-- While the string is not empty
while( length( arr_str ) > 0 ) do
-- if the split index is in the string
if( locate( split_index, arr_str ) ) then
-- get the last data in the string
set arr_data = ( select substring_index(arr_str, split_index, -1) );
-- remove the last data in the string
set arr_str = ( select
replace(arr_str,
concat(split_index,
substring_index(arr_str, split_index, -1)
)
,'')
);
-- if the split index is not in the string
else
-- get the unique data in the string
set arr_data = arr_str;
-- empties the string
set arr_str = '';
end if;
-- in this trivial example, it returns if a string is in the array
if arr_data = compares then
set resp = 1;
end if;
end while;
return resp;
end
|
delimiter ;
I want to create a set of usefull mysql functions to work with this method. Anyone interested please contact me.
For more examples, visit http://blog.idealmind.com.br/mysql/how-to-use-string-as-array-in-mysql-and-work-with/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With