SQL: how to get counts from two groups that falls into one set of ranges

Viewed 65

I have been struggling on how I can achieve the below output:

Background: Range is the number of items within ProductA and ProductB. They both have two different number of items. ProductA usually falls into different range category then ProductB. I need to get one range for ProductA and ProductB.

ProductA ProductB Ranges
14 16 0-10
45 40 10-20
16 18 20-30
23 1 30-40
4 5 40-50
34 67 50-60
4 12 60-70
5 7 70-80
44 56 80-90
5 7 90-100

Table should be read as : e.g. ProductA has 34 number of items and falls into range 30-40, but ProductB (67 number of items) falls into different range in this case 60-70.

Here is the code - I need to find a way to get one range category(currently I have two separately: for ProductA and ProductB) that will be assigned for ProductA and ProductB and will give me one range for both:

WITH ProductA AS (
    select
        distinct m.ProductA as product_count_A,
        case
            when utilizationA <= 10 THEN '0-10'
            when utilizationA <= 20
            AND utilizationA > 10 THEN '10-20'
            when utilizationA <= 30
            AND utilizationA > 20 THEN '20-30'
            when utilizationA <= 40
            AND utilizationA > 30 THEN '30-40'
            when utilizationA <= 50
            AND utilizationA > 40 THEN '40-50'
            when utilizationA <= 60
            AND utilizationA > 50 THEN '50-60'
            when utilizationA <= 70
            AND utilizationA > 60 THEN '60-70'
            when utilizationA <= 80
            AND utilizationA > 70 THEN '70-80'
            when utilizationA <= 90
            AND utilizationA > 80 THEN '80-90'
            when utilizationA <= 100
            AND utilizationA > 90 THEN '90-100'
        END "UtilizationA"
    from
        PRODUCTS_TABLE
),
ProductB AS (
    Select
        distinct ProductB as product_count_B,
        CASE
            when utilizationB <= 10 THEN '0-10'
            when utilizationB <= 20
            AND utilizationB > 10 THEN '10-20'
            when utilizationB <= 30
            AND utilizationB > 20 THEN '20-30'
            when utilizationB <= 40
            AND utilizationB > 30 THEN '30-40'
            when utilizationB <= 50
            AND utilizationB > 40 THEN '40-50'
            when utilizationB <= 60
            AND utilizationB > 50 THEN '50-60'
            when utilizationB <= 70
            AND utilizationB > 60 THEN '60-70'
            when utilizationB <= 80
            AND utilizationB > 70 THEN '70-80'
            when utilizationB <= 90
            AND utilizationB > 80 THEN '80-90'
            when utilizationB <= 100
            AND utilizationB > 90 THEN '90-100'
        END "UtilizationB"
    from
        PRODUCTS_TABLE
)
SELECT
    DISTINCT "UtilizationA",
    "UtilizationB",
    COUNT(ProductB.product_count_B) as ProductB_RES,
    COUNT(ProductA.product_count_A) as ProductA_RES
from
    ProductB,
    ProductA
GROUP BY
    "UtilizationA",
    "UtilizationB";
2 Answers

UNPIVOT the columns to rows and convert the utilization to ranges and then PIVOT back from rows to columns:

SELECT a_product AS productA,
       b_product AS productB,
       utilization
FROM   (
  SELECT type,
         CASE
         WHEN utilization <= 10 THEN '0-10'
         WHEN utilization <= 20 THEN '10-20'
         WHEN utilization <= 30 THEN '20-30'
         WHEN utilization <= 40 THEN '30-40'
         WHEN utilization <= 50 THEN '40-50'
         WHEN utilization <= 60 THEN '50-60'
         WHEN utilization <= 70 THEN '60-70'
         WHEN utilization <= 80 THEN '70-80'
         WHEN utilization <= 90 THEN '80-90'
         ELSE '90-100'
         END AS utilization,
         product
  FROM   products_table
  UNPIVOT (
    (product, utilization) FOR type IN (
      (producta, utilizationa) AS 'A',
      (productb, utilizationb) AS 'B'
    )
  )
)
PIVOT (
  COUNT(DISTINCT product) AS product
  FOR type IN (
    'A' AS a,
    'B' AS b
  )
)

Which, for the sample data:

CREATE TABLE products_table(producta, utilizationa, productb, utilizationb) AS
SELECT 1,  1, 1,  1 FROM DUAL UNION ALL
SELECT 1, 11, 2,  1 FROM DUAL UNION ALL
SELECT 1, 21, 1, 11 FROM DUAL UNION ALL
SELECT 1, 31, 1, 21 FROM DUAL UNION ALL
SELECT 1, 41, 1, 21 FROM DUAL UNION ALL
SELECT 1, 51, 1, 51 FROM DUAL UNION ALL
SELECT 1, 61, 1, 71 FROM DUAL UNION ALL
SELECT 1, 71, 1, 81 FROM DUAL;

Outputs:

PRODUCTA PRODUCTB UTILIZATION
1 2 0-10
1 1 10-20
1 1 20-30
1 0 30-40
1 0 40-50
1 1 50-60
1 0 60-70
1 1 70-80
0 1 80-90

db<>fiddle here

As I have some background on Business Intelligence this looks similarly to problem I sometimes had on my reports: we tried to merge cpu p95 with cpu_max utilization to count how many hosts are eligible to one group/range set by p95 and separate count, but the same grouping, for cpu_max in other column.

Here's my take on this: http://sqlfiddle.com/#!18/babfb/1/0 (As I am not familiar with Oracle syntax I'll do it the MSSQL way, just for the notion of the idea)

With the input dataset in the format of:

host p95 max_cpu
a 5 19
b 6 12
c 2 5
d 4 10
e 50 77
f 12 20
g 30 45
h 5 45
a 20 60
CREATE TABLE PRODUCTS_TABLE
    ("host" varchar(1), "p95" int, "max_cpu" int)
;
    
INSERT INTO PRODUCTS_TABLE
    ("host", "p95","max_cpu")
VALUES
    ('a', 5, 19),
    ('b', 6, 12),
    ('c', 2, 5),
    ('d', 4, 10),
    ('e', 50, 77),
    ('f', 12, 20),
    ('g', 30, 45),
    ('h', 5, 45),
    ('a', 20, 60)
;

I am preparing two sets of counts (ProductA_Count, ProductB_Count) and their assignements to appropriate ranges to finally JOIN them by the rank on the resulting query.

WITH ProductA AS (
    select
        host,
        case
            when p95 <= 10 THEN '0-10'
            when p95 <= 20
            AND p95 > 10 THEN '10-20'
            (...your conditions here...)
            when p95 <= 100
            AND p95 > 90 THEN '90-100'
        END "utilA"
        from PRODUCTS_TABLE   
        group by host, p95
),
ProductA_Count AS (
        select utilA, count(utilA)  as 'utilA_ct'
        from ProductA
        group by utilA
),
ProductB AS (
    select
        host,
        case
            when max_cpu <= 10 THEN '0-10'
            when max_cpu <= 20
            (...your conditions here, duplicated, you may want to simplify it...)
            when max_cpu <= 100
            AND max_cpu > 90 THEN '90-100'
        END "utilB"     
     from PRODUCTS_TABLE
),
ProductB_Count AS (
        select utilB, count(utilB)  as 'utilB_ct'
        from ProductB
        group by utilB
)
SELECT utilA_ct,utilB_ct,utilA
FROM ProductA_Count
LEFT OUTER JOIN ProductB_Count ON ProductA_Count.utilA=ProductB_Count.utilB
;

This results with

utilA_ct utilB_ct utilA
5 2 0-10
2 3 10-20
1 (null) 20-30
1 2 40-50
Related