sqloracle-database

Only include records that all rows within a group meet a specific condtion


I want to amend my current query to only display the accounts (all fields) that do not have Keep_Flag = 'N' in the query results.

The query's intent is to identify accounts who met a specific condition in order for them to qualify for additional rate discounts. They have to meet the condition for every single month otherwise they are rejected from discount program (the query will pull the past 12 months from today's date).

Query that creates the monthly qualification as a 'Y' or 'N':

SELECT id, account_id, reg_con, tagg_reading, read_date,
CASE
    WHEN reg_con * tagg_reading < 60 
         AND TO_CHAR(read_date, 'MM-DD') BETWEEN '06-15' AND '10-15' THEN 'Y'
    WHEN reg_con * tagg_reading < 90 
         AND (TO_CHAR(read_date, 'MM-DD') BETWEEN '10-16' AND '12-31' OR TO_CHAR(read_date, 'MM-DD') BETWEEN '01-01' AND '06-14') THEN 'Y'
    ELSE 'N'
END AS Keep_Flag
FROM test;

I have created a fiddle example with dummy data.

In the below screenshot, my expected results would only include Account_ID 1001. Account_ID 1002 would not qualify as it includes results that has a Keep_Flag ='N'.

enter image description here

The results I would like to see with the amended query

enter image description here


Solution

  • You could add an analytic/window function call to get the lowest flag value for each account ID, either in the same level of query by repeating the case expression, or more simply using a CTE or inline view (subquery):

    SELECT id, account_id, reg_con, tagg_reading, read_date, Keep_Flag,
        MIN(Keep_Flag) OVER (PARTITION BY account_id) AS Min_Keep_Flag
    FROM (
        SELECT id, account_id, reg_con, tagg_reading, read_date,
        CASE
            WHEN reg_con * tagg_reading < 60 
                 AND TO_CHAR(read_date, 'MM-DD') BETWEEN '06-15' AND '10-15' THEN 'Y'
            WHEN reg_con * tagg_reading < 90 
                 AND (TO_CHAR(read_date, 'MM-DD') BETWEEN '10-16' AND '12-31' OR TO_CHAR(read_date, 'MM-DD') BETWEEN '01-01' AND '06-14') THEN 'Y'
            ELSE 'N'
        END AS Keep_Flag
        FROM test
    )
    ORDER BY id;
    
    ID ACCOUNT_ID REG_CON TAGG_READING READ_DATE KEEP_FLAG MIN_KEEP_FLAG
    1 1001 4 20 10-DEC-23 Y Y
    2 1001 4 12 05-JAN-24 Y Y
    3 1001 2 40 25-FEB-24 Y Y
    4 1001 3 25 05-MAR-24 Y Y
    5 1001 1 10 20-APR-24 Y Y
    6 1001 4 12 10-MAY-24 Y Y
    7 1001 2 20 19-JUN-24 Y Y
    8 1001 1 35 25-JUL-24 Y Y
    9 1001 3 14 05-AUG-24 Y Y
    10 1001 1 10 20-SEP-24 Y Y
    11 1001 4 13 10-OCT-24 Y Y
    12 1001 2 33 15-NOV-24 Y Y
    13 1001 3 27 10-DEC-24 Y Y
    14 1002 1 10 10-DEC-23 Y N
    15 1002 4 36 05-JAN-24 N N
    16 1002 2 24 24-FEB-24 Y N
    17 1002 4 26 05-MAR-24 N N
    18 1002 2 22 20-APR-24 Y N
    19 1002 1 30 10-MAY-24 Y N
    20 1002 2 20 19-JUN-24 Y N
    21 1002 1 35 25-JUL-24 Y N
    22 1002 3 14 05-AUG-24 Y N
    23 1002 1 10 20-SEP-24 Y N
    24 1002 4 13 10-OCT-24 Y N
    25 1002 2 23 15-NOV-24 Y N
    26 1002 1 41 10-DEC-24 Y N

    And then filter out all the rows which don't have 'Y' as the lowest flag value, which exclude the accounts with any 'N' flags:

    SELECT id, account_id, reg_con, tagg_reading, read_date, Keep_Flag
    FROM (
        SELECT id, account_id, reg_con, tagg_reading, read_date, Keep_Flag,
            MIN(Keep_Flag) OVER (PARTITION BY account_id) AS Min_Keep_Flag
        FROM (
            SELECT id, account_id, reg_con, tagg_reading, read_date,
            CASE
                WHEN reg_con * tagg_reading < 60 
                     AND TO_CHAR(read_date, 'MM-DD') BETWEEN '06-15' AND '10-15' THEN 'Y'
                WHEN reg_con * tagg_reading < 90 
                     AND (TO_CHAR(read_date, 'MM-DD') BETWEEN '10-16' AND '12-31' OR TO_CHAR(read_date, 'MM-DD') BETWEEN '01-01' AND '06-14') THEN 'Y'
                ELSE 'N'
            END AS Keep_Flag
            FROM test
        )
    )
    WHERE Min_Keep_Flag = 'Y'
    ORDER BY id;
    
    ID ACCOUNT_ID REG_CON TAGG_READING READ_DATE KEEP_FLAG
    1 1001 4 20 10-DEC-23 Y
    2 1001 4 12 05-JAN-24 Y
    3 1001 2 40 25-FEB-24 Y
    4 1001 3 25 05-MAR-24 Y
    5 1001 1 10 20-APR-24 Y
    6 1001 4 12 10-MAY-24 Y
    7 1001 2 20 19-JUN-24 Y
    8 1001 1 35 25-JUL-24 Y
    9 1001 3 14 05-AUG-24 Y
    10 1001 1 10 20-SEP-24 Y
    11 1001 4 13 10-OCT-24 Y
    12 1001 2 33 15-NOV-24 Y
    13 1001 3 27 10-DEC-24 Y

    Or with CTEs instead of nested inline views:

    WITH cte1 AS (
        SELECT id, account_id, reg_con, tagg_reading, read_date,
        CASE
            WHEN reg_con * tagg_reading < 60 
                 AND TO_CHAR(read_date, 'MM-DD') BETWEEN '06-15' AND '10-15' THEN 'Y'
            WHEN reg_con * tagg_reading < 90 
                 AND (TO_CHAR(read_date, 'MM-DD') BETWEEN '10-16' AND '12-31' OR TO_CHAR(read_date, 'MM-DD') BETWEEN '01-01' AND '06-14') THEN 'Y'
            ELSE 'N'
        END AS Keep_Flag
        FROM test
    ),
    cte2 AS (
        SELECT id, account_id, reg_con, tagg_reading, read_date, Keep_Flag,
            MIN(Keep_Flag) OVER (PARTITION BY account_id) AS Min_Keep_Flag
        FROM cte1
    )
    SELECT id, account_id, reg_con, tagg_reading, read_date, Keep_Flag
    FROM cte2
    WHERE Min_Keep_Flag = 'Y'
    ORDER BY id;
    
    ID ACCOUNT_ID REG_CON TAGG_READING READ_DATE KEEP_FLAG
    1 1001 4 20 10-DEC-23 Y
    2 1001 4 12 05-JAN-24 Y
    3 1001 2 40 25-FEB-24 Y
    4 1001 3 25 05-MAR-24 Y
    5 1001 1 10 20-APR-24 Y
    6 1001 4 12 10-MAY-24 Y
    7 1001 2 20 19-JUN-24 Y
    8 1001 1 35 25-JUL-24 Y
    9 1001 3 14 05-AUG-24 Y
    10 1001 1 10 20-SEP-24 Y
    11 1001 4 13 10-OCT-24 Y
    12 1001 2 33 15-NOV-24 Y
    13 1001 3 27 10-DEC-24 Y

    fiddle