'Oracle Query : One to Many Relationship

In my application there are one-to-many relationship as explained below.

Table one : Application

app_id app_name
1 ABC
2 XYZ

Table two : Application_attribute [One application can have multiple attribute and variable list of attribute]

App_attr_id app_id attr_name attr_value
1 1 attr1 white
2 1 attr2 12
3 1 attr3 45
4 2 attr1 red
5 2 attr2 12
6 2 attr4 45
7 2 attr7 62

Each application can have variable list of attributes.

Query Requirement

I want to fetch list of application based on multiple attribute.

Example get list of application whose attributes are attr1=white,attr2=12,attr3=45

In above case, problem can be solved by joining application table with application_attribute table 3 times but attribute will vary per application so it will not be generic solution.

Query Solution for my requirement

SELECT a.*
  FROM application a,
       application_attribute at1,
       application_attribute at2,
       application_attribute at3
 WHERE a.app_id = at1.app_id
   AND a.app_id = at2.app_id
   AND a.app_id = at3.app_id
   AND at1.attr_name = 'attr1'
   AND at1.attr_value = 'white'
   AND at2.attr_name = 'attr2'
   AND at2.attr_value = '12'
   AND at3.attr_name = 'attr3'
   AND at3.attr_value = '45'

Expected Result

app_id app_name
1 ABC

One option is two create dynamic query. Is it possible to write generic query which can be used to search n number of attribute like 3,4,5..n ?



Solution 1:[1]

You want to select applications where exist certain attributes. So, select from the application table and have a where clause checkink the existence of the attributes with EXISTS or IN:

select * 
from aplication
where app_id in (select app_id from application_attribute where attr_name = 'attr1' and attr_value = 'white')
  and app_id in (select app_id from application_attribute where attr_name = 'attr2' and attr_value = '12')
  and app_id in (select app_id from application_attribute where attr_name = 'attr3' and attr_value = '45')
order by app_id;

As to generic: Simply build the query with a programming language and a loop over the desired attributes. In Oracle you can use PL/SQL for this.

Solution 2:[2]

You can use EXISTS and a HAVING clause in the correlated sub-query:

SELECT *
FROM   application a
WHERE  EXISTS (
         SELECT 1
         FROM   application_attribute aa
         WHERE  a.app_id = aa.app_id
         AND    (aa.attr_name, aa.attr_value)
                  IN (('attr1', 'white'), ('attr2', '12'), ('attr3', '45'))
         HAVING COUNT(/*DISTINCT*/ aa.attr_name) = 3 
       )

Note: If there can be duplicate attribute values then you can COUNT(DISTINCT ...) rather than just COUNT(...).

Which, for the sample data:

CREATE TABLE application (app_id, app_name) AS
SELECT 1, 'ABC' FROM DUAL UNION ALL
SELECT 2, 'XYZ' FROM DUAL;

CREATE TABLE Application_attribute (App_attr_id, app_id, attr_name, attr_value) AS
SELECT 1, 1, 'attr1', 'white' FROM DUAL UNION ALL
SELECT 2, 1, 'attr2', '12' FROM DUAL UNION ALL
SELECT 3, 1, 'attr3', '45' FROM DUAL UNION ALL
SELECT 4, 2, 'attr1', 'red' FROM DUAL UNION ALL
SELECT 5, 2, 'attr2', '12' FROM DUAL UNION ALL
SELECT 6, 2, 'attr4', '45' FROM DUAL UNION ALL
SELECT 7, 2, 'attr7', '62' FROM DUAL;

Outputs:

APP_ID APP_NAME
1 ABC

db<>fiddle here

Solution 3:[3]

You can join tables only once by conditionally matching the values of attr_name and attr_value columns while filtering out the result only for the returning records with exactly triple attribute names such as

SELECT a.app_id, a.app_name
  FROM application a
  JOIN application_attribute aa
    ON aa.app_id = a.app_id
 WHERE DECODE( aa.attr_name, 'attr1', aa.attr_value ) = 'white' -- you can make these literals parametric by prefixing with : or & such as &p_attr1 and enter 'white' whenever prompted
    OR DECODE( aa.attr_name, 'attr2', aa.attr_value ) = '12'
    OR DECODE( aa.attr_name, 'attr3', aa.attr_value ) = '45'  
 GROUP BY a.app_id, a.app_name
HAVING COUNT(DISTINCT aa.attr_name)=3  

Demo

Solution 4:[4]

If we re-write your query using joins there will be the 3 variables that you need to change in the WHERE. This will make it much easier to modify them.

Select a.* 
from application a
left join application-attribute at1 on a.app_id = at1.app_id and at1.attr_name = 'attr1'
left join application_attribute at2 on a.app_id = at2.app_id and at2.attr_name = 'attr2'
left join application_attribute at3 on a.app_id = at3.app_id and at3.attr_name = 'attr3'
left join application_attribute at4 on a.app_id = at4.app_id and at4.attr_name = 'attr4'
where  at1.attr_value = 'white' 
   and at2.attr_value = '12' 
   and at3.attr_value = '45'
/*   and at4.attr_value = not needed */
;

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Thorsten Kettner
Solution 2 MT0
Solution 3 Barbaros Özhan
Solution 4