Reputation: 31
I have the following table:
CREATE TABLE IF NOT EXISTS `test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`left_id` int(11) NOT NULL DEFAULT '0',
`right_id` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8;
INSERT INTO `test` (`id`, `left_id`, `right_id`) VALUES (2, 111, 90),
(3, 111, 91),(4, 111, 92),(5, 111, 93),(6, 111, 94),(7, 111, 95),
(8, 222, 90),(9, 222, 91),(10, 222, 92),(11, 222, 93),(12, 222, 94),
(13, 222, 95),(14, 333, 90),(15, 333, 91),(16, 333, 92),(17, 333, 93),
(18, 333,94),(19, 333, 100);
which looks like
id | left_id | right_id
2 | 111 | 90
3 | 111 | 91
4 | 111 | 92
5 | 111 | 93
6 | 111 | 94
7 | 111 | 95
8 | 222 | 90
9 | 222 | 91
10 | 222 | 92
11 | 222 | 93
12 | 222 | 94
13 | 222 | 95
14 | 333 | 90
15 | 333 | 91
16 | 333 | 92
17 | 333 | 93
18 | 333 | 94
19 | 333 | 100
left_id
is a record of a "news" and right_id
records the categories that the news record belongs to.
Now I want all left_ids (distinct) that have EXACTLY/ALL right_ids 90 & (91 OR 92) & 93 & 94 & 95
In long sentence:
Give me (distinct) the left_id
, where right_id
is
(90 & 91 & 93 & 94 & 95)
OR
(90 & 92 & 93 & 94 & 95)
The result must be 111
and 222
; not 333
, because 333
has NOT 95
.
Upvotes: 2
Views: 79
Reputation: 6702
You could use a bit mask. You find the position p in a set, shift the value 1
p times left and once right. 1st position results in binary 0001, 2nd in 0010, 3rd in 0100 aso. Aggregate this by binary OR.
Now you can easily build conditions. mask = mask & aggregate
means every bit set in mask is also set in aggregate. mask & aggregate
means at least one bit of the mask is set in aggregate.
SELECT
`left_id`, # 1 2 4 8 16 32
BIT_OR(1<<FIND_IN_SET(`right_id`, '90,91,92,93,94,95')>>1) `right_or`
FROM
`ids`
GROUP BY
`left_id`
HAVING 1|8|16|32 = (1|8|16|32) & `right_or`
AND (2|4) & `right_or`
;
same with condition 91 or 95:
SELECT
`left_id`,
BIT_OR(1<<FIND_IN_SET(`right_id`, '90,91,92,93,94,95')>>1) `right_or`
FROM
`ids`
GROUP BY
`left_id`
HAVING 1|4|8|16 = (1|4|8|16) & `right_or`
AND (2|32) & `right_or`
;
Of course you can precalculate constants, e.g. 1|4|8|16 = 29. I wanted to show a readable example.
Upvotes: 0
Reputation: 5316
You can use GROUP_CONCAT()
together with REGEXP
for that in order to match exact values:
SELECT
left_id,
GROUP_CONCAT(DISTINCT right_id ORDER BY right_id) gc
FROM
test
GROUP BY
left_id
HAVING
gc REGEXP '90.+(91|92).+(93).+(94).+(95)'
Output is:
+---------+-------------------+
| left_id | gc |
+---------+-------------------+
| 111 | 90,91,92,93,94,95 |
| 222 | 90,91,92,93,94,95 |
+---------+-------------------+
2 rows in set
Upvotes: 2
Reputation: 929
I think this query is independent of any ordering in id's and any possible duplicate entries:
SELECT left_id
FROM `test`
WHERE right_id IN (90,91,93,94,95)
GROUP BY left_id
HAVING COUNT(DISTINCT right_id) = 5
UNION
SELECT left_id
FROM `test`
WHERE right_id IN (90,92,93,94,95)
GROUP BY left_id
HAVING COUNT(DISTINCT right_id) = 5
Upvotes: 1
Reputation: 190
Try this
select left_id, group_concat(right_id) value
from test
group by 1
having value = ('90,91,93,94,95')
or value = ('90,92,93,94,95')
Upvotes: 0
Reputation: 33945
SELECT a.* -- [OR DISTINCT a.left_id]
FROM test a
JOIN
( SELECT x.left_id
FROM test x
WHERE x.right_id IN(90,93,94,95)
GROUP
BY x.left_id HAVING COUNT(*) = 4
) b
ON b.left_id = a.left_id
WHERE a.right_id IN (91,92);
Upvotes: 0
Reputation: 133380
I think you could use the property of group_concat and check if the result is the same of the string you provided ..
select distinct left_id, group_concat(right_id) from test
having group_concat(right_id) = ('90,91,93,94,95')
or group_concat(right_id) = ('90,92,93,94,95')
group_by left_id
eventually adapt the format of string to the formatted resul fo group_concat
Upvotes: 0