Home > Net >  JOIN Two tables in SQL with one to many mapping and do not join for an I'd if any one of the ro
JOIN Two tables in SQL with one to many mapping and do not join for an I'd if any one of the ro

Time:02-26

I have to join 2 tables with one to many mapping. I have to select the rows for an Id of all rows satisfy the condition or else do not select it.

Example: Table A

Id Company_Name
1 ABC
2 DEF
3 GHI

TABLE B

ID REGION
1 ASIA
1 AMERICA
1 AUSTRALIA
2 ASIA
2 AFRICA
3 ASIA
3 AMERICA

Here I want to join table A and table B only when the company of present in both asia and America only.

Output:

ID company_name region
3 Def. Asia
3 Def. America

It should not select ID 1 since it is also present in Australia. It should also not select 2 as it is not present in America.

CodePudding user response:

You can use:

SELECT a.id,
       a.company_name,
       b.region
FROM   tablea a
       INNER JOIN
       ( SELECT id,
                region,
                COUNT(DISTINCT CASE WHEN region IN ('ASIA', 'AMERICA') THEN region END)
                  OVER (PARTITION BY id) AS num_asia_america,
                COUNT(DISTINCT CASE WHEN region NOT IN ('ASIA', 'AMERICA') THEN region END)
                  OVER (PARTITION BY id) AS num_other
         FROM   tableb
       ) b
       ON (a.id = b.id
          AND num_asia_america = 2
          AND num_other = 0)

Which, for the sample data:

CREATE TABLE tablea (Id, Company_Name) AS
SELECT 1, 'ABC' FROM DUAL UNION ALL
SELECT 2, 'DEF' FROM DUAL UNION ALL
SELECT 3, 'GHI' FROM DUAL;

CREATE TABLE tableb (ID, REGION) AS
SELECT 1, 'ASIA'      FROM DUAL UNION ALL
SELECT 1, 'AMERICA'   FROM DUAL UNION ALL
SELECT 1, 'AUSTRALIA' FROM DUAL UNION ALL
SELECT 2, 'ASIA'      FROM DUAL UNION ALL
SELECT 2, 'AFRICA'    FROM DUAL UNION ALL
SELECT 3, 'ASIA'      FROM DUAL UNION ALL
SELECT 3, 'AMERICA'   FROM DUAL;

Outputs:

ID COMPANY_NAME REGION
3 GHI AMERICA
3 GHI ASIA

Update

How can I refactor the query if it is user choice to bring data for which region? In that case count can be dynamic.

If your users can dynamically pass in between 1 and 3 regions then you can use named bind variables to accept the region values:

SELECT a.id,
       a.company_name,
       b.region
FROM   tablea a
       INNER JOIN
       ( SELECT id,
                region,
                COUNT(CASE WHEN region = :region1 THEN region END)
                  OVER (PARTITION BY id) AS num_region1,
                COUNT(CASE WHEN region = :region2 THEN region END)
                  OVER (PARTITION BY id) AS num_region2,
                COUNT(CASE WHEN region = :region3 THEN region END)
                  OVER (PARTITION BY id) AS num_region3,
                COUNT(
                  CASE
                  WHEN (region <> :region1 OR :region1 IS NULL)
                  AND  (region <> :region2 OR :region2 IS NULL)
                  AND  (region <> :region3 OR :region3 IS NULL)
                  THEN region
                  END
                ) OVER (PARTITION BY id) AS num_other
         FROM   tableb
       ) b
       ON (a.id = b.id
          AND (num_region1 > 0 OR :region1 IS NULL)
          AND (num_region2 > 0 OR :region2 IS NULL)
          AND (num_region3 > 0 OR :region3 IS NULL)
          AND num_other = 0)

Or, if you want to pass in a completely dynamic list then you can define a collection:

CREATE TYPE string_list AS TABLE OF VARCHAR2(20);

Then pass the collection as a bind variable:

SELECT a.id,
       a.company_name,
       b.region
FROM   tablea a
       INNER JOIN
       ( SELECT id,
                region,
                COUNT(
                  DISTINCT
                  CASE
                  WHEN region MEMBER OF :your_collection
                  THEN region
                  END
                ) OVER (PARTITION BY id) AS num_regions,
                COUNT(
                  CASE
                  WHEN region NOT MEMBER OF :your_collection
                  THEN region
                  END
                ) OVER (PARTITION BY id) AS num_other
         FROM   tableb
       ) b
       ON (a.id = b.id
          AND num_regions = CARDINALITY(:your_collection)
          AND num_other = 0)

db<>fiddle here

CodePudding user response:

select a.ID, a.company_name, b.region
from table_a a
     inner join
     table_b b on a.ID = b.ID
where a.id in(select id from table_b where region in('ASIA','AMERICA'))
  and a.id in(select id from table_b group by id having count(distinct(region)) = 2)
order by a.id, a.region
  • Related