Darloz
Darloz

Reputation: 165

Select from subquery where multiple rows

I'm having some trouble with a dynamic SQL search query where i would like to be able to find all objects that have any of the fields I'm searching for. Below is the datastructure.

objectregister                      fieldvalue
| id |  name   |         | id |  objid  | fieldid | value  (illustration)  |
+----+---------+         +----+---------+---------+------------------------+
| 1  |  CUBE   |         | 1  |    1    |    12   |   4    (BLUE)          |
| 2  |  SQUARE |         | 2  |    2    |    12   |   4    (BLUE)          |
                         | 3  |    1    |    22   |   27   (SMALL)         |
                         | 4  |    2    |    22   |   9    (BIG)           |

Test Bench with DB structure:

CREATE TABLE IF NOT EXISTS `objectregister` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(1024) COLLATE utf8_swedish_ci NOT NULL,
  PRIMARY KEY (`id`)
);

INSERT INTO `objectregister` (`id`, `name` ) VALUES
(1, 'CUBE'),
(2, 'SQUARE');

CREATE TABLE IF NOT EXISTS `fieldvalue` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `objid` int(11) NOT NULL,
  `fieldid` int(11) NOT NULL,
  `value` varchar(2048) COLLATE utf8_swedish_ci NOT NULL,
  UNIQUE KEY `id` (`id`)
);

INSERT INTO `fieldvalue` (`id`, `objid`, `fieldid`, `value`) VALUES
(1, 1, 12, 4),
(2, 2, 12, 4),
(3, 1, 22, 27),
(4, 2, 22, 9);


SELECT `id`, `name` FROM `objectregister` WHERE id IN 
(
    SELECT * FROM 
    (
        SELECT `objid` 
        FROM `fieldvalue` 
        WHERE 1  AND ( (fieldvalue.fieldid = '12' AND fieldvalue.value = '4')  OR (fieldvalue.fieldid = '22' AND fieldvalue.value = '27')  ) 
        GROUP BY objid 
    ) as subquery
  )

  +----+--------+
  | id | name   |
  +----+--------+
  |  1 | CUBE   |
  |  2 | SQUARE |
  +----+--------+

https://rextester.com/XTR72354

Example:

I would like to find all objects that are blue. In this case: CUBE, SQUARE

I would like to find all object that are blue AND big . In this case: SQUARE

Therefore, I gues I would first need to select all possible objid 's that matches any of the search in a subquery. Get them into one single line so I later can select on WHERE both of them match? But how do I do that? Would i need multiple SUB query unions? GROUP_CONCAT? TEMP table?

This is the current stage of my query as of writing (that would return both rows in subquery, but would need the WHERE on outer query):

SELECT `id`, `name` FROM `objectregister` WHERE id IN 
(
    SELECT * FROM 
    (
        SELECT `objid` 
        FROM `fieldvalue` 
        WHERE 1  AND ( (fieldvalue.fieldid = '12' AND fieldvalue.value = '4')  OR (fieldvalue.fieldid = '22' AND fieldvalue.value = '27')  ) 
        GROUP BY objid 
    ) as subquery
)

Upvotes: 0

Views: 3138

Answers (3)

Rick James
Rick James

Reputation: 142298

  • EAV schema has a lot of problems.

  • For fieldvalue, get rid of id, instead have PRIMARY KEY(objid, fieldid).

  • Also for fieldvalue, have INDEX(fieldid) . (Probably not worth including value since it is too long.)

  • Avoid IN ( SELECT ... ); change to JOIN .. ON or EXISTS( SELECT 1 ... )

I would like to find all objects that are blue. In this case: CUBE, SQUARE

SELECT obj.name
    FROM ( SELECT objid
               FROM fieldvalue
               WHERE fieldid = '12'
                 AND `value` = '4'
         ) AS x
    JOIN objectregister AS obj ON x.objid = obj.id;

Alternatively, change the first line to

SELECT GROUP_CONCAT(obj.name)

I would like to find all object that are blue AND big . In this case: SQUARE

SELECT obj.name
    FROM ( SELECT objid FROM fieldvalue WHERE fieldid = '12' AND `value` =  '4' ) AS x
    JOIN ( SELECT objid FROM fieldvalue WHERE fieldid = '22' AND `value` = '27' ) AS y
                               USING(objid)
    JOIN objectregister AS obj ON x.objid = obj.id;

The philosophy here might be "Start with what you know; work toward what you need to know" instead of "Let's check every objid to see which ones apply".

Upvotes: 0

Strawberry
Strawberry

Reputation: 33945

Similarly...

SELECT o.*
  FROM objectregister o 
  JOIN fieldvalue v 
    ON v.objid = o.id 
 WHERE value IN (4,9) 
 GROUP 
    BY o.id 
HAVING COUNT(DISTINCT value) = 2;

If values have different meanings in different contexts, then you also need the fieldids. A short (but inefficient way of writing that is WHERE (fieldid,value) IN((12,4),(etc))

Upvotes: 0

GMB
GMB

Reputation: 222462

One option is to use exists:

select r.*
from objectregister r
where 
    exists (
        select 1 from fieldvalue f where f.objid = r.id and f.fieldid = 12 and f.value = 4
    ) and exists (
        select 1 from fieldvalue f where f.objid = r.id and f.fieldid = 22 and f.value = 27
    )

With an index on fieldvalue(objid, fieldid, value), this should be an efficient solution.

You can join, aggregate and filter with a having clause:

select r.id, r.name
from objectregister r
inner join fieldvalue f on f.objid = r.id
group by r.id, r.name
having max(f.fieldid = 12 and f.value = 4) = 1 and max(f.fieldid = 22 and f.value = 27) = 1

Upvotes: 1

Related Questions