views:

77

answers:

6

I express the relationship between records and searchtags that can be attached to records like so:

TABLE RECORDS
id
name

TABLE SEARCHTAGS 
id
recordid
name

I want to be able to SELECT records based on the searchtags that they have. For example, I want to be able to SELECT all records that have searchtags:

(1 OR 2 OR 5) AND (6 OR 7) AND (10)

Using the above data structure, I am uncertain how to structure the SQL to accomplish this.

Any suggestions?

Thanks!

A: 

not sure how to do it in mysql, but in t-sql, you could do something like:

SELECT id, name FROM RECORDS where id in (SELECT recordid from SEARCHTAGS where id in (1,2,5,6,7,10))

I may not be understanding your question entirely... but I gave it my best.

Mike
`(1 OR 2 OR 5) AND (6 OR 7) AND (10)` != `(1 OR 2 OR 5 OR 6 OR 7 OR 10)`
Tomalak
yeah, I misread
Mike
seems like a lot of people are making this mistake
Mike
A: 

Try:

SELECT R.*
FROM RECORDS R, SEARCHTAGS S
WHERE R.id == S.recordid
AND S.name in (1,2,5,6,7,10);

Don't know if you need S.name or S.id, but this is an example.

lugte098
+1  A: 
SELECT
  id, name
FROM
  records
WHERE
  EXISTS (
    SELECT 1 FROM searchtags WHERE recordid = records.id AND id IN (1, 2, 5)
  )
  AND EXISTS (
    SELECT 1 FROM searchtags WHERE recordid = records.id AND id IN (6, 7)
  )
  AND EXISTS (
    SELECT 1 FROM searchtags WHERE recordid = records.id AND id IN (10)
  )
Tomalak
+1 for the quickest correct answer.
Daniel Vassallo
A: 
 select RECORDS.name
    from RECORDS join SEARCHTAGS 
    on RECORDS.id = SEARCHTAGS.recordid
    where RECORDS.id in (1,2,...)
Lina
+2  A: 

You may want to try the following:

SELECT    r.id, r.name
FROM      records r
WHERE     EXISTS (SELECT NULL FROM searchtags WHERE recordid = r.id AND id IN (1, 2, 5)) AND 
          EXISTS (SELECT NULL FROM searchtags WHERE recordid = r.id AND id IN (6, 7)) AND 
          EXISTS (SELECT NULL FROM searchtags WHERE recordid = r.id AND id IN (10));

Test case: Note that only records 1 and 4 will satisfy the query criteria.

CREATE TABLE records (id int, name varchar(10));
CREATE TABLE searchtags (id int, recordid int);

INSERT INTO records VALUES (1, 'a');
INSERT INTO records VALUES (2, 'b');
INSERT INTO records VALUES (3, 'c');
INSERT INTO records VALUES (4, 'd');

INSERT INTO searchtags VALUES (1,  1);
INSERT INTO searchtags VALUES (2,  1);
INSERT INTO searchtags VALUES (6,  1);
INSERT INTO searchtags VALUES (10, 1);
INSERT INTO searchtags VALUES (1,  2);
INSERT INTO searchtags VALUES (2,  2);
INSERT INTO searchtags VALUES (3,  2);
INSERT INTO searchtags VALUES (1,  3);
INSERT INTO searchtags VALUES (10, 3);
INSERT INTO searchtags VALUES (5,  4);
INSERT INTO searchtags VALUES (7,  4);
INSERT INTO searchtags VALUES (10, 4);

Result:

+------+------+
| id   | name |
+------+------+
|    1 | a    |
|    4 | d    |
+------+------+
2 rows in set (0.01 sec)
Daniel Vassallo
Yes, that works. Thanks.
Travis
A: 

I misread the question first time around, and thought it was asking for

(1 AND 2 AND 5) OR (6 AND 7) OR (10)

instead of the correct

(1 OR 2 OR 5) AND (6 OR 7) AND (10)

All the answers so far have concentrated on answering the specific example, rather than addressing the more general question "and suppose I want a different set of criteria next time".

Actual question

I can't do much better than the selected answer for the actual question (Query 1):

SELECT r.id, r.name
  FROM Records AS r
 WHERE EXISTS(SELECT * FROM SearchTags AS s
               WHERE r.id = s.recordid AND s.id IN (1, 2, 5))
   AND EXISTS(SELECT * FROM SearchTags AS s
               WHERE r.id = s.recordid AND s.id IN (6, 7))
   AND EXISTS(SELECT * FROM SearchTags AS s
               WHERE r.id = s.recordid AND s.id IN (10));

This could be written as a join to 3 aliases for the SearchTags table.

Alternative question

There are several ways to answer the alternative - I think this is the most nearly neat and extensible. Clearly, the one item (10) is easy (Query 2):

SELECT r.id, r.name
  FROM records AS r JOIN searchtags AS t ON r.id = t.recordid
 WHERE t.id IN (10)  -- or '= 10' but IN is consistent with what follows

The two items (6 or 7) can be done with (Query 3):

SELECT r.id, r.name
  FROM records AS r JOIN searchtags AS t ON r.id = t.recordid
 WHERE t.id IN (6, 7)
 GROUP BY r.id, r.name
HAVING COUNT(*) = 2

The three items (1, 2, 5) can be done with (Query 4):

SELECT r.id, r.name
  FROM records AS r JOIN searchtags AS t ON r.id = t.recordid
 WHERE t.id IN (1, 2, 5)
 GROUP BY r.id, r.name
HAVING COUNT(*) = 3

And the whole collection can be a UNION of the three terms.

Generalizing the solutions

The downside of this solution is that the SQL must be manually crafted for each set of items. If you want to automate the 'SQL generation', you need the control data - the sets of interesting search tags - in a table:

CREATE TABLE InterestingTags(GroupID INTEGER, TagID INTEGER);
INSERT INTO InterestingTags(1, 1);
INSERT INTO InterestingTags(1, 2);
INSERT INTO InterestingTags(1, 5);
INSERT INTO InterestingTags(2, 6);
INSERT INTO InterestingTags(2, 7);
INSERT INTO InterestingTags(3, 10);

For the query asking for '(1 OR 2 OR 5) AND (...)' (conjunctive normal form), you can write (Query 5):

SELECT r.id, r.name
  FROM records AS r JOIN
       searchtags AS s ON r.id = s.recordID JOIN
       interestingtags AS t ON s.id = t.tagID
 GROUP BY r.id, r.name
HAVING COUNT(DISTINCT t.GroupID) = (SELECT COUNT(DISTINCT GroupID)
                                      FROM InterestingTags);

This checks that the number of distinct 'interesting groups of tags' for a given record is equal to the total number of 'interesting groups of tags'.

For the query asking for '(1 AND 2 AND 5) OR (...)' (disjunctive normal form), you can write a join with InterestingTags and check that the Record has as many entries as the group of tags (Query 6):

SELECT i.id, i.name
  FROM (SELECT q.id, q.name, c.GroupSize,
               COUNT(DISTINCT t.GroupID) AS GroupCount
          FROM records AS q JOIN
               searchtags AS s ON q.id = s.recordID JOIN
               interestingtags AS t ON s.id = t.tagID JOIN
               (SELECT GroupID, COUNT(*) AS GroupSize
                  FROM InterestingTags
                 GROUP BY GroupID) AS c ON c.GroupID = t.GroupID
         GROUP BY q.id, q.name, c.GroupSize
       ) AS i
 WHERE i.GroupCount = i.GroupSize;

Test Data

I took the test data from Daniel Vassalo's answer and augmented it with some extra values:

CREATE TABLE records (id int, name varchar(10));
CREATE TABLE searchtags (id int, recordid int);

INSERT INTO records VALUES (1, 'a');
INSERT INTO records VALUES (2, 'b');
INSERT INTO records VALUES (3, 'c');
INSERT INTO records VALUES (4, 'd');

INSERT INTO records VALUES (11, 'A11');
INSERT INTO records VALUES (21, 'B12');
INSERT INTO records VALUES (31, 'C13');
INSERT INTO records VALUES (41, 'D14');
INSERT INTO records VALUES (51, 'E15');
INSERT INTO records VALUES (61, 'F16');

INSERT INTO searchtags VALUES (1,  1);
INSERT INTO searchtags VALUES (2,  1);
INSERT INTO searchtags VALUES (6,  1);
INSERT INTO searchtags VALUES (10, 1);
INSERT INTO searchtags VALUES (1,  2);
INSERT INTO searchtags VALUES (2,  2);
INSERT INTO searchtags VALUES (3,  2);
INSERT INTO searchtags VALUES (1,  3);
INSERT INTO searchtags VALUES (10, 3);
INSERT INTO searchtags VALUES (5,  4);
INSERT INTO searchtags VALUES (7,  4);
INSERT INTO searchtags VALUES (10, 4);

INSERT INTO searchtags VALUES (1,  11);
INSERT INTO searchtags VALUES (2,  11);
INSERT INTO searchtags VALUES (5,  11);
INSERT INTO searchtags VALUES (6,  21);
INSERT INTO searchtags VALUES (7,  21);
INSERT INTO searchtags VALUES (10, 31);
INSERT INTO searchtags VALUES (1,  41);
INSERT INTO searchtags VALUES (6,  41);
INSERT INTO searchtags VALUES (10, 41);
INSERT INTO searchtags VALUES (2,  51);
INSERT INTO searchtags VALUES (5,  51);
INSERT INTO searchtags VALUES (10, 51);
INSERT INTO searchtags VALUES (7,  61);
INSERT INTO searchtags VALUES (2,  61);
INSERT INTO searchtags VALUES (1,  61);

CREATE TABLE InterestingTags(GroupID INTEGER, TagID INTEGER);
INSERT INTO InterestingTags VALUES(1, 1);
INSERT INTO InterestingTags VALUES(1, 2);
INSERT INTO InterestingTags VALUES(1, 5);
INSERT INTO InterestingTags VALUES(2, 6);
INSERT INTO InterestingTags VALUES(2, 7);
INSERT INTO InterestingTags VALUES(3, 10);

Test results

The outputs that I got were:

Query 1

1   a
4   d
41  D14

Query 2

1   a
3   c
4   d
31  C13
41  D14
51  E15

Query 3

21  B12

Query 4

11  A11

Query 5

1   a
41  D14
4   d

Query 6

4   d
31  C13
3   c
1   a
41  D14
51  E15

Clearly, if I wanted the output in a specific order, I would add an ORDER BY clause to the queries.

Jonathan Leffler