Mysql select query group concat
-
01-07-2021 - |
سؤال
I have a select query to build and I can't get it work propery. I was hopeing for a few sudggestions from more advanced MySQL developers. So my tables are:
CREATE TABLE IF NOT EXISTS `gv` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`option_id` int(11) NOT NULL,
`group_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=7 ;
INSERT INTO `gv` (`id`, `option_id`, `group_id`) VALUES
(1, 1, 1),
(2, 2, 2),
(3, 3, 2),
(4, 4, 3),
(5, 5, 4),
(6, 6, 4);
CREATE TABLE IF NOT EXISTS `igv` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`item_id` int(11) NOT NULL,
`gv_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=9 ;
INSERT INTO `igv` (`id`, `item_id`, `gv_id`) VALUES
(1, 1, 1),
(2, 1, 3),
(3, 2, 1),
(4, 2, 2),
(6, 3, 5),
(7, 4, 2),
(8, 2, 6);
CREATE TABLE IF NOT EXISTS `items` (
`item_id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=6 ;
INSERT INTO `items` (`item_id`) VALUES
(1),
(2),
(3),
(4),
(5);
Now I will explain what these tables are doing:
gv comes from group_values. A group is like a property for an item. Ex: Color.
- each row holds values for a group. Ex: Red ( option_id ) for group Color ( group_id ).
igv comes from item group values. An item is the main entity in my project, like a product.
- each row holds relations between items and group_values. Ex: An item with Red Color.
- it's linked to the gv table by gv_id ( igv.gv_id = gv.id ).
items comes from items :) I have simplified for this example to only one column, item_id.
What I need to query:
Well, in my app I have a selected item ( my_item ) ( with 0 or more group values attached ). I need all the items that have exactly the same group_values defined, or no values specified for my_item groups. In php I can extract all the attached groups as an array with ids, also all the group_values. But I found no way to select what I need to from this database. I will appreciate very much any input provided into this massive time-consumming select.
Thanks!
A short sketched-up example. On the left we have the item that is matching all the right items ( separated with comma ).
المحلول
OK, so to start with we need to find all items with a group matching another item:
SELECT igv.item_id, sigv.item_id FROM igv
INNER JOIN gv ON igv.gv_id = gv.id
INNER JOIN gv sgv ON gv.group_id = sgv.group_id
INNER JOIN igv sigv ON sigv.gv_id = sgv.id
WHERE igv.item_id = items.item_id
AND sigv.item_id = similar.item_id
We then are only interested when the option does not match:
SELECT igv.item_id, sigv.item_id dissimilar FROM igv
INNER JOIN gv ON igv.gv_id = gv.id
INNER JOIN gv sgv ON gv.group_id = sgv.group_id
INNER JOIN igv sigv ON sigv.gv_id = sgv.id
WHERE igv.item_id = items.item_id
AND sigv.item_id = similar.item_id
AND gv.option_id != sgv.option_id
Then we are only interested in the items that are not selected above. This can be done using either NOT EXISTS
or a LEFT JOIN
. Here's how to do it with NOT EXISTS
:
SELECT items.item_id, similar.item_id similar_id
FROM items similar
INNER JOIN items
WHERE items.item_id != similar.item_id
AND NOT EXISTS (
SELECT igv.item_id, sigv.item_id dissimilar FROM igv
INNER JOIN gv ON igv.gv_id = gv.id
INNER JOIN gv sgv ON gv.group_id = sgv.group_id
INNER JOIN igv sigv ON sigv.gv_id = sgv.id
WHERE sigv.item_id = similar.item_id
AND igv.item_id = items.item_id
AND gv.option_id != sgv.option_id
)
Result:
ITEM_ID SIMILAR_ID
1 3
1 5
2 4
2 5
3 1
3 4
3 5
4 2
4 3
4 5
5 1
5 2
5 3
5 4
نصائح أخرى
Update:
Based on the logic from your comment, if I understood it correctly, and your test data, the following table shows which result you want for which item_id you provide for the query.
id_provided | result
--------------------
1 2 & 5
2 1 & 4 & 5
3 5
4 2 & 5
5 -
If this is correct, the following query gives the desired result:
SELECT
COALESCE(i2.item_id, i.item_id)
FROM
items i
LEFT JOIN igv ON igv.item_id = i.item_id
LEFT JOIN gv ON igv.gv_id = gv.id
LEFT JOIN gv gv2 ON gv.group_id = gv2.group_id AND gv.option_id = gv2.option_id
LEFT JOIN igv igv2 ON gv2.id = igv2.gv_id
LEFT JOIN items i2 ON igv2.item_id = i2.item_id
WHERE i.item_id = $yourItemId
AND i2.item_id != i.item_id OR i2.item_id IS NULL
I'm not sure if I understood what you want and I'm not sure if the following query gets what you want, cause you didn't say what the expected output would be and your test data is not sufficient. Anyway, here's my shot:
SELECT DISTINCT
i.item_id
FROM
items i
LEFT JOIN igv ON igv.item_id = i.item_id
LEFT JOIN gv ON igv.gv_id = gv.id
GROUP BY i.item_id
HAVING
GROUP_CONCAT(CONCAT(COALESCE(gv.option_id, 0), COALESCE(gv.group_id, 0)) ORDER BY gv.option_id, gv.group_id SEPARATOR ':')
=
(
SELECT
GROUP_CONCAT(CONCAT(COALESCE(sq_gv.option_id, 0), COALESCE(sq_gv.group_id, 0)) ORDER BY sq_gv.option_id, sq_gv.group_id SEPARATOR ':')
FROM
items sq_i
LEFT JOIN igv sq_igv ON sq_igv.item_id = sq_i.item_id
LEFT JOIN gv sq_gv ON sq_igv.gv_id = sq_gv.id
WHERE sq_i.item_id = $yourItemId /*insert the item_id here*/
GROUP BY sq_i.item_id
)