简体   繁体   中英

SQL query based on Group by, need derived column based on min and max

Create table test123 (
    CustId int,
    [Level]  int,
    RowNum int,
    USAFlag bit
)

insert into test123(CustId,[Level],RowNum,USAFlag)values
(101,1,1,0),
(102,2,1,0),
(102,2,2,1),
(103,3,1,0),
(103,3,2,1),
(103,3,3,0),
(104,4,1,1),
(104,4,2,0),
(104,4,3,0),
(104,4,4,1),
(105,2,1,1),
(105,2,2,0),
(106,2,1,0),
(106,2,2,0),
(107,3,1,0),
(107,3,2,0),
(107,3,3,1),
(108,1,1,1)

OUtput

CustID USARootLeaf
101 ONlyONeLevel_NonUSA
102 Leaf_USA
103 Root_Leaf_NonUSA
104 Root_Leaf_USA
105 Root_USA
106 Root_Leaf_NonUSA
107 Leaf_USA
108 OnlyOneLvel_USA

Logic: If Level is 1 then USARootLeaf value should be OnlyOneLvel_USA or OnlyOneLvel_NonUSA based on USAFlag value

If Level is >1, then USARootLeaf value should be Root_Leaf_USA,Root_Leaf_NonUSA,Root_USA,Leaf_USA based on min(level) and max(level) value of USAFlag is true/false

I have never used so many CTE's in a query before and I am sure there is a more efficient way, but this works.

--Add Row Numbers
WITH preselect AS (SELECT ROW_NUMBER() OVER(PARTITION BY CustId ORDER BY 
                   CustId)'RowNum',CustId
                   FROM test123),

--Get the first and last Row
preselect2 AS (SELECT MIN(RowNum)'MinRow',MAX(RowNum)'MaxRow',CustId
               FROM preselect
               GROUP BY CustId),

--Join to table with data
preselect3 AS (SELECT ROW_NUMBER() OVER(PARTITION BY t.CustId ORDER BY 
               t.CustId)'RowNumber',t.CustId,t.Level,
               t.RowNum,t.USAFlag,p2.MinRow,p2.MaxRow
               FROM test123 t
               JOIN preselect2 p2 ON t.CustId = p2.CustId),

--Filter out any row that isn't the first or last in the group
preselect4 AS (SELECT *
               FROM preselect3
               WHERE RowNumber = MinRow
                     OR
                     RowNumber = MaxRow),

--Get count of each CustId
preselect5 AS (SELECT CustId,COUNT(*)'Cnt' 
               FROM test123
               GROUP BY CustId),

--Determine the properties for each CustId
preselect6 AS (
 SELECT p4.CustId, 
 SUM(CASE WHEN Cnt > 1 THEN 1 ELSE 0 END) AS 'Multi',
 SUM(CASE WHEN RowNumber = 1 AND USAFlag = 1 THEN 1 ELSE 0 END)AS 'RootUSA',
 SUM(CASE WHEN RowNumber > 1 AND USAFlag = 1 THEN 1 ELSE 0 END)AS 'LeafUSA'
 FROM preselect4 p4
     JOIN preselect5 p5 ON p4.CustId = p5.CustId
 GROUP BY p4.CustId)

--Apply values based on their properties
SELECT CustId, 
 CASE WHEN Multi = 0 AND RootUSA = 0 THEN 'OnlyOneLevel_NonUSA'
      WHEN Multi = 0 AND RootUSA = 1 THEN 'OnlyOneLevel_USA'
      WHEN Multi > 1 AND RootUSA = 0 AND LeafUSA = 1 THEN 'Leaf_USA'
      WHEN Multi > 1 AND RootUSA = 0 AND LeafUSA = 0 THEN 'Root_Leaf_NonUSA'
      WHEN Multi > 1 AND RootUSA = 1 AND LeafUSA = 1 THEN 'Root_Leaf_USA'
      WHEN Multi > 1 AND RootUSA = 1 AND LeafUSA = 0 THEN 'Root_USA'
      ELSE 'N/A' END AS 'USARootLeaf'
FROM preselect6               

This could be refactored to be smaller, but here is the a guess at the implementation based on the requirements above. Note that I changed [Level] to thisLevel in my DDL.

mysql> select q.custid, concat(q.type,concat('_',q.starsandstripes)) as type from (
    -> select
    ->        z.custid,
    ->        case when minlevel = 1 and maxlevel = 1 then 'OnlyOneLevel'
    ->        else 
    ->           case when lastflag = 1 and firstflag = 0 then 'Leaf'
    ->           when lastflag = 0 and firstflag = 1 then 'Root'
    ->           when lastflag = firstflag then 'Root_Leaf'
    ->           end
    ->        end as type,
    ->        starsandstripes
    ->        from 
    -> (
    -> select 
    ->    za.custid,
    ->    case when max(za.usaflag) = 1
    ->       then 'USA' 
    ->       else 'NonUSA' 
    ->    end
    ->      as starsAndStripes
    -> from test123 za join
    ->     (
    ->         select 
    ->            custid, 
    ->            max(rownum) as maxrow, 
    ->            min(rownum) as minrow
    ->         from test123 zc
    ->         group by custid
    ->     ) zb 
    ->     on za.custid = zb.custid 
    ->       and (za.rownum = zb.maxrow
    ->       or za.rownum = zb.minrow)
    -> group by za.custid
    -> ) z,
    -> (
    ->    select
    ->       a.custid,
    ->       (select usaflag from test123 d where d.custid = a.custid and d.rownum = b.maxrow) as lastFlag,
    ->       (select usaflag from test123 d where d.custid = a.custid and d.rownum = b.minrow) as firstFlag,
    ->       min(thisLevel) as minLevel,
    ->       max(thisLevel) as maxLevel
    ->    from test123 a join
    ->    (
    ->        select custid, max(rownum) as maxrow, min(rownum) as minrow
    ->        from test123 c
    ->        group by custid
    ->    ) b 
    ->    on a.custid = b.custid
    ->    group by custid,
    ->    (select usaflag from test123 d where d.custid = a.custid and d.rownum = b.maxrow),
    ->    (select usaflag from test123 d where d.custid = a.custid and d.rownum = b.minrow)
    -> ) x
    ->    where z.custid = x.custid
    -> ) q;

Output:

+--------+---------------------+
| custid | type                |
+--------+---------------------+
|    101 | OnlyOneLevel_NonUSA |
|    102 | Leaf_USA            |
|    103 | Root_Leaf_NonUSA    |
|    104 | Root_Leaf_USA       |
|    105 | Root_USA            |
|    106 | Root_Leaf_NonUSA    |
|    107 | Leaf_USA            |
|    108 | OnlyOneLevel_USA    |
+--------+---------------------+
8 rows in set (0.00 sec)    

Thanks Bryan and Jason for your quick inputs. I found a better solution myself.

with one as
(
Select CustId,[Level],
STUFF((SELECT ',' + cast(USAFlag as char(1)) FROM test123 WHERE (
CustId=Result.CustId) FOR XML PATH ('')),1,1,'') AS USAList
From test123 AS Result
GROUP BY CustId,[Level]
)
Select CustId,[level],USAList,
case 
when Level=1 and substring(USAList,1,1)='1' then 'ONlyONeLevel_USA'
when Level=1 and substring(USAList,1,1)='0' then 'ONlyONeLevel_NonUSA'
when Level>1 and substring(USAList,1,1)='1' and substring(USAList,level*2-
1,1)='1' then 'Root_Leaf_USA'
when Level>1 and substring(USAList,1,1)='0' and substring(USAList,level*2-
1,1)='0' then 'Root_Leaf_NonUSA'
when Level>1 and substring(USAList,1,1)='1'  then 'Root_USA'
when Level>1 and substring(USAList,level*2-1,1)='1'  then 'Leaf_USA'
else 'InvalidLevel'
end as USARootLeaf
from one

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM