sqlsql-server

Join to table with filter condition as records


I have a couple of tables in SQL, first one called attribute that looks like this:

SELECT 'A' AS AttributeA, 'B' AS AttributeB, 2020 AS AttributeC
UNION ALL
SELECT 'A' AS AttributeA, 'B' AS AttributeB, 2021 AS AttributeC
UNION ALL
SELECT 'B' AS AttributeA, 'C' AS AttributeB, 2022 AS AttributeC

enter image description here

Second table called condition looks like this:

SELECT 'IN (''A'')' AS ConditionA, 'IN (''B'')' AS ConditionB, '< 2021' AS ConditionC, 'Bad' AS Grade
UNION ALL
SELECT 'IN (''A'')' AS ConditionA, 'IN (''B'')' AS ConditionB, 'IN (2021)' AS ConditionC, 'Good' AS Grade
UNION ALL
SELECT 'NOT IN (''A'')' AS ConditionA, 'IN (''C'')' AS ConditionB, '> 2021' AS ConditionC, 'Excellent' AS Grade

enter image description here

The aim is to join the two together such that AttributeA satisfies ConditionA, and AttributeB satisfies ConditionB and AttributeC satisfies ConditionC etc, so that the Grade column is added onto the attribute table, like this:

enter image description here

If the data is good, there should never be an overlap of rules such that more than one record in the condition table satisfy the attribute record. But that's not as important right now because I can check for duplicates later.

I already have a solution which requires dynamic SQL to cycle through the condition table, build up the where clause conditions, and use that to filter records from the attributes table whilst appending the Grade column.

Another solution works similarly but vice versa, where I cycle through the distinct records of the attribute table, see which one satisfies the condition table, and start creating the record that way.

Both work, however I'm just wondering if there is a cleverer way to somehow join the two together so I don't need to use a cursor to cycle through the records individually?


Solution

  • You can generate one query for all conditions and chek rows in main table by one request.

    From conditions table you can generate check conditions

    id ConditionA ConditionB ConditionC Grade
    1 IN ('A') IN ('B') < 2021 Bad
    2 IN ('A') IN ('B') IN (2021) Good
    3 NOT IN ('A') IN ('C') > 2021 Excellent

    conditions

    cond
    (1,case when AttributeA IN ('A') and AttributeB IN ('B') and AttributeC < 2021 then 'Bad' end)
    (2,case when AttributeA IN ('A') and AttributeB IN ('B') and AttributeC IN (2021) then 'Good' end)
    (3,case when AttributeA NOT IN ('A') and AttributeB IN ('C') and AttributeC > 2021 then 'Excellent' end)

    There condition Id (for identity) and concatenated conditions.

    with static query

    select *
      ,concat('(',Id,',case when '
                      ,'AttributeA ',ConditionA
              ,' and ','AttributeB ',ConditionB
              ,' and ','AttributeC ',ConditionC
              ,' then ''', Grade,''' end)') cond
    from conditions
    

    Then generate main query

    select * 
    from tableA a
    cross apply( values 
       -- generated part 
         (1,case when AttributeA IN ('A') and AttributeB IN ('B') and   AttributeC< 2021 then 'Bad' end)
        ,(2,case when AttributeA IN ('A') and AttributeB IN ('B') and AttributeC IN (2021) then 'Good' end)
        ,(3,case when AttributeA NOT IN ('A') and AttributeB IN ('C') and AttributeC > 2021 then 'Excellent' end) 
       -- end of generated part 
      )c(ConditionId,res)
    where res is not null
    

    We get rows, where complicated condition is satisfied. If attribute row satisfied many conditions, you can take all or best fit.

    id AttributeA AttributeB AttributeC ConditionId res
    1 A B 2020 1 Bad
    2 A B 2021 2 Good
    3 B C 2022 3 Excellent

    Full query example

    declare @sqlExpr varchar(1000);
    
    set @sqlExpr=
      'select * 
       from tableA a 
       cross apply( values '+CHAR(13)
      +(select string_agg(
       concat('    (',Id,',','case when ','AttributeA ',ConditionA,' and ','AttributeB ',ConditionB,' and ','AttributeC ',ConditionC,' then ''',Grade,''' end) ') 
       ,','+ CHAR(13) )cond
        from conditions
       )
      +'
       )c(ConditionId,res) 
       where res is not null'
    ;
    
    exec (@sqlExpr);
    

    @sqlExpr is

    select * 
       from tableA a 
       cross apply( values 
        (1,case when AttributeA IN ('A') and AttributeB IN ('B') and AttributeC < 2021 then 'Bad' end) ,
        (2,case when AttributeA IN ('A') and AttributeB IN ('B') and AttributeC IN (2021) then 'Good' end) ,
        (3,case when AttributeA NOT IN ('A') and AttributeB IN ('C') and AttributeC > 2021 then 'Excellent' end) 
       )c(ConditionId,res) 
       where res is not null
    

    fiddle

    This example is for SQL Server (cross apply).
    You can use cross join lateral for PostgreSql and other similar method in your DBMS.