SQL Puzzle - Transpose Rows and Shift Values among columns

This SQL Puzzle is inspired from the problem which I solved recently, and I think the problem is quite interesting to be shared as a SQL Puzzle. It includes shifting the values among columns and also transpose of rows. I hope you will enjoy solving the problem.

We have an existing table and data with following rules -
1) We have a table of hierarchy going upto 4 levels for different PRODUCTS and Hierarchy TYPE.
2) We have only 3 rows per Product
3) Each hierarchy can be of PRODUCTION / SALE / SUPPORT type
4) Each Hierarchy may have GM -> RM -> MGR -> EXEC roles
5) In some cases any of the role from Hierarchy can be missing, like it can be GM -> MRG -> EXEC or GM -> MGR.

Following is the script for sample table and data:

create table product_staff_hierarchy
(
  product varchar2(100),
  level1_ecode number(10),
  level1_ename varchar2(100),
  level1_erole varchar2(100),
  level2_ecode number(10),
  level2_ename varchar2(100),
  level2_erole varchar2(100),
  level3_ecode number(10),
  level3_ename varchar2(100),
  level3_erole varchar2(100),
  level4_ecode number(10),
  level4_ename varchar2(100),
  level4_erole varchar2(100),
  hierarchy_type varchar2(100)
);

insert into product_staff_hierarchy
select 'MOBILE',  1,  'M1',  'GM',  2,  'M2',  'RM' ,  3,    'M3',   'MGR',  4,   'M4',   'EXEC', 'PRODUCTION' FROM DUAL UNION ALL
select 'MOBILE',  5,  'M5',  'GM',  6,  'M6',  'MGR',  7,    'M7',   'EXEC', NULL, NULL,  NULL,   'SALE'       FROM DUAL UNION ALL
select 'MOBILE',  8,  'M8',  'GM',  9,  'M9',  'RM',   10,   'M10',  'EXEC', NULL, NULL,  NULL,   'SUPPORT'    FROM DUAL UNION ALL
select 'LAPTOP',  11, 'L11', 'RM',  12, 'L12', 'EXEC', NULL, NULL,   NULL,   NULL, NULL,  NULL,   'PRODUCTION' FROM DUAL UNION ALL
select 'LAPTOP',  13, 'L13', 'GM',  14, 'L14', 'MGR',  15,   'L15',  'EXEC', NULL, NULL,  NULL,   'SALE'       FROM DUAL UNION ALL
select 'LAPTOP',  16, 'L16', 'GM',  17, 'L17', 'MGR',  NULL, NULL,   NULL,   NULL, NULL,  NULL,   'SUPPORT'    FROM DUAL UNION ALL
select 'SPEAKER', 18, 'S18', 'GM',  19, 'S19', 'RM',   20,   'S20',  'MGR',  21,   'L21', 'EXEC', 'SALE'       FROM DUAL UNION ALL
select 'SPEAKER', 22, 'S22', 'MGR', 23, 'S23', 'EXEC', NULL, NULL,   NULL,   NULL, NULL,  NULL,   'PRODUCTION' FROM DUAL UNION ALL
select 'SPEAKER', 24, 'S24', 'GM',  25, 'S25', 'EXEC', NULL, NULL,   NULL,   NULL, NULL,  NULL,   'SUPPORT'    FROM DUAL UNION ALL
select 'MOUSE',  26, 'M26', 'GM',  27, 'M27', 'MGR',  28,   'M28', 'EXEC',  NULL, NULL,  NULL,   'PRODUCTION' FROM DUAL UNION ALL
select 'MOUSE',  29, 'M29', 'RM',  30, 'M30', 'MGR',  31,   'M31', 'EXEC',  NULL, NULL,  NULL,   'SALE'       FROM DUAL UNION ALL
select 'MOUSE',  32, 'M32', 'GM',  33, 'M33', 'RM',   34,   'M34', 'EXEC',  NULL, NULL,  NULL,   'SUPPORT'    FROM DUAL UNION ALL
select 'KEYBOARD',  35, 'K35', 'GM',  36, 'K36', 'RM',   37,   'K27', 'MGR',   NULL, NULL,  NULL,   'PRODUCTION' FROM DUAL UNION ALL
select 'KEYBOARD',  38, 'K38', 'MGR', 39, 'K29', 'EXEC', NULL, NULL,   NULL,   NULL, NULL,  NULL,   'SUPPORT'    FROM DUAL UNION ALL
select 'CHARGER', 40, 'C40', 'RM',  41, 'C41', 'EXEC', NULL, NULL,   NULL,   NULL, NULL,  NULL,   'PRODUCTION' FROM DUAL UNION ALL
select 'CHARGER', 42, 'C42', 'GM',  43, 'C43', 'RM',   44,   'C44', 'MGR',   NULL, NULL,  NULL,   'SALE'       FROM DUAL;

Now here is the Problem Statement:
We want to transpose above data in the following format
1) Per Product, we would have only one row.
2) We will have ROLE_TYPE column for each combination like SALE_EXEC or SUPPORT_RM
3) Values related to product and role combination should go in their designated columns
4) Multiple hierarchies of each type should be transposed into one row per product
5) The structure of final output should be as per the following table structure

create table product_staff_new
(
  product varchar2(100),
  production_gm_ecode   number(10),
  production_gm_ename   varchar2(100),
  production_rm_ecode   number(10),
  production_rm_ename   varchar2(100),
  production_mgr_ecode  number(10),
  production_mgr_ename  varchar2(100),
  production_exec_ecode number(10),
  production_exec_ename varchar2(100),
  sale_gm_ecode         number(10),
  sale_gm_ename         varchar2(100),
  sale_rm_ecode         number(10),
  sale_rm_ename         varchar2(100),
  sale_mgr_ecode        number(10),
  sale_mgr_ename        varchar2(100),
  sale_exec_ecode       number(10),
  sale_exec_ename       varchar2(100),
  support_gm_ecode      number(10),
  support_gm_ename      varchar2(100),
  support_rm_ecode      number(10),
  support_rm_ename      varchar2(100),
  support_mgr_ecode     number(10),
  support_mgr_ename     varchar2(100),
  support_exec_ecode    number(10),
  support_exec_ename    varchar2(100)
);


The original problem was having 8 Products, 10 Levels, and 8 columns per level, and was originally solved with PL/SQL - loops and dynamic SQL. When this requirement came to me, I was able to solve it with only one SQL and gained a-lot of performance.

Do share your approach in the comment box. Have fun writing the SQL or PL/SQL, Enjoy :)

More SQL Puzzles:
- Graph Shortest Path Solution by SQL
- Diamond Shaped Star Pattern by SQL
- Sorting Versions stored in Varchar2 Column
- SQL Puzzle - Grouping Deals
- SQL Puzzle - Consecutive Wins
- SQL Puzzle - Issue Tracker
- SQL Interview Question Answers

8 comments:

  1. sir, can i get the sql which you have used to solve this problem?

    ReplyDelete
  2. Hi Nimish,

    As you mentioned in the post , per product there will be one row in the output ,so it means there will be three rows , and each product will be having one gm,rm,mgr,exec but in the data set multiple RM or MRG is present for example if query :-

    select * from PRODUCT_STAFF_HIERARCHY where
    product='LAPTOP' and HIERARCHY_TYPE='PRODUCTION';

    you will get the below output :-
    LAPTOP PRODUCTION 11 L11 RM EXEC
    LAPTOP PRODUCTION 35 L35 GM RM

    so there are two RM for the one product.. So in that case one row for each product cannot be possible..

    Please provide your inputs if i have misunderstood something.

    ReplyDelete
    Replies
    1. Thank you for sharing the data issue, I have added 3 more products to remove the duplicates.

      Delete
  3. This comment has been removed by the author.

    ReplyDelete
  4. Hi Nimish,
    i have sent the puzzle query on LinkedIn messenger, kindly review once.

    Regards,
    Mohit

    ReplyDelete
  5. Hi Nimish, Please see below query that solves the puzzle. Do apprise if that the correct pattern of data required

    insert into PRODUCT_STAFF_NEW

    select PRODUCT,

    max(case when hierarchy_type = 'PRODUCTION' then (case when level1_erole = 'GM' then level1_ecode end) end) as production_gm_ecode ,

    max(case when hierarchy_type = 'PRODUCTION' then (case when level1_erole = 'GM' then level1_ename end) end) as production_gm_ename,

    max(case when hierarchy_type = 'PRODUCTION' then (case when level2_erole = 'RM' then level2_ecode

    when level1_erole = 'RM' then level1_ecode end) end) as production_rm_ecode,

    max(case when hierarchy_type = 'PRODUCTION' then (case when level2_erole = 'RM' then level2_ename

    when level1_erole = 'RM' then level1_ename end) end) as production_rm_ename,

    max(case when hierarchy_type = 'PRODUCTION' then (case when level3_erole = 'MGR' then level3_ecode

    when level2_erole = 'MGR' then level2_ecode

    when level1_erole = 'MGR' then level1_ecode end) end) as production_mgr_ecode,

    max(case when hierarchy_type = 'PRODUCTION' then (case when level3_erole = 'MGR' then level3_ename

    when level2_erole = 'MGR' then level2_ename

    when level1_erole = 'MGR' then level1_ename end) end) as production_mgr_ename,

    max(case when hierarchy_type = 'PRODUCTION' then (case when level4_erole = 'EXEC' then level4_ecode

    when level3_erole = 'EXEC' then level3_ecode

    when level2_erole = 'EXEC' then level2_ecode

    when level1_erole = 'EXEC' then level1_ecode end) end) as production_exec_ecode,

    max(case when hierarchy_type = 'PRODUCTION' then (case when level4_erole = 'EXEC' then level4_ename

    when level3_erole = 'EXEC' then level3_ename

    when level2_erole = 'EXEC' then level2_ename

    when level1_erole = 'EXEC' then level1_ename end) end ) as production_exec_ename
    -- same for SALE and SUPPORT
    from PRODUCT_STAFF_HIERARCHY group by PRODUCT;

    ReplyDelete
  6. Hi Nimish, Please see below query that solves the puzzle. Do apprise if that the correct pattern of data required.
    insert into PRODUCT_STAFF_NEW
    select PRODUCT,
    max(case when hierarchy_type = 'PRODUCTION' then (case when level1_erole = 'GM' then level1_ecode end) end) as production_gm_ecode ,
    max(case when hierarchy_type = 'PRODUCTION' then (case when level1_erole = 'GM' then level1_ename end) end) as production_gm_ename,
    max(case when hierarchy_type = 'PRODUCTION' then (case when level2_erole = 'RM' then level2_ecode
    when level1_erole = 'RM' then level1_ecode end) end) as production_rm_ecode,
    max(case when hierarchy_type = 'PRODUCTION' then (case when level2_erole = 'RM' then level2_ename
    when level1_erole = 'RM' then level1_ename end) end) as production_rm_ename,
    max(case when hierarchy_type = 'PRODUCTION' then (case when level3_erole = 'MGR' then level3_ecode
    when level2_erole = 'MGR' then level2_ecode
    when level1_erole = 'MGR' then level1_ecode end) end) as production_mgr_ecode,
    max(case when hierarchy_type = 'PRODUCTION' then (case when level3_erole = 'MGR' then level3_ename
    when level2_erole = 'MGR' then level2_ename
    when level1_erole = 'MGR' then level1_ename end) end) as production_mgr_ename,
    max(case when hierarchy_type = 'PRODUCTION' then (case when level4_erole = 'EXEC' then level4_ecode
    when level3_erole = 'EXEC' then level3_ecode
    when level2_erole = 'EXEC' then level2_ecode
    when level1_erole = 'EXEC' then level1_ecode end) end) as production_exec_ecode,
    max(case when hierarchy_type = 'PRODUCTION' then (case when level4_erole = 'EXEC' then level4_ename
    when level3_erole = 'EXEC' then level3_ename
    when level2_erole = 'EXEC' then level2_ename
    when level1_erole = 'EXEC' then level1_ename end) end ) as production_exec_ename,

    Not able to post complete query, but posted for Production part, can be replicated for Same and support

    ReplyDelete