Search code examples
sqlpostgresqloracleplsqldeveloper

Split a row into columns in SQL


E1 E2 E3 EXPLANATION AMOUNT
01 PERSONNEL EXPENSES 5000
01 1 OFFICERS 5000
01 1 1 BASIC SALARIES 3000
01 1 2 INCREASES AND COMPENSATION 2000
02 GOODS AND SERVICES PURCHASE EXPENSES 8000
02 1 SERVICE PROCUREMENT 8000
02 1 1 STATIONERY PURCHASES 6000
02 1 2 OFFICE SUPPLIES PURCHASES 2000

I want to obtain an output in the second table format using the table kept in the database as follows.

A1 A2 A3 AMOUNT
PERSONNEL EXPENSES 5000
PERSONNEL EXPENSES OFFICERS 5000
PERSONNEL EXPENSES OFFICERS BASIC SALARIES 3000
PERSONNEL EXPENSES OFFICERS INCREASES AND COMPENSATION 2000
GOODS AND SERVICES PURCHASE EXPENSES
GOODS AND SERVICES PURCHASE EXPENSES SERVICE PROCUREMENT 8000
GOODS AND SERVICES PURCHASE EXPENSES SERVICE PROCUREMENT STATIONERY PURCHASES 6000
GOODS AND SERVICES PURCHASE EXPENSES SERVICE PROCUREMENT OFFICE SUPPLIES PURCHASES 2000

For this, I wrote a manual code in SQL as follows. But how can I make this more efficient?

SELECT 
CASE WHEN E1 = '01' PERSONNEL EXPENSES'
     WHEN E1 = '02' THEN 'GOODS AND SERVICES PURCHASE EXPENSES'
     ELSE NULL END AS A1,
     
CASE WHEN E1 = '01' AND E2 = '1' THEN 'OFFICERS'
     WHEN E1 = '02' AND E2 = '1' THEN 'SERVICE PROCUREMENT'
     ELSE NULL END AS A2,

AMOUNT 
FROM EXPENSES

How can I do this by taking from the column in the EXPENSES.EXPLANATION table without writing explanations like in this code?


Solution

  • Do you really use both PostgreSQL and Oracle? If so, I can't help much about the former, but - for Oracle, here's one option. Doesn't look very pretty, though.

    Sample data suggests that this is about some kind of a hierarchy:

    SQL> select * From test;
    
    E1 E2 E3 EXPLANATION                              AMOUNT
    -- -- -- ------------------------------------ ----------
    01       personnel expenses                         5000
    01 1     officers                                   5000
    01 1  1  basic salaries                             3000
    01 1  2  increases and compensation                 2000
    02       goods and services purchase expenses       8000
    02 1     service procurement                        8000
    02 1  1  stationary purchases                       6000
    02 1  2  office supplies purchases                  2000
    
    8 rows selected.
    

    Read comments within code; they explain what each CTE (and final SELECT) do:

    SQL> with temp as
      2  -- create hierarchy (two columns, PARENT and CHILD)
      3  (          select 'X' e1      , null e2     , 'master row' explanation, 0 amount from dual
      4   union all select e1          , 'X' e2      , explanation             ,   amount from test where e2 is null
      5   union all select e1 ||'-'||e2, e1          , explanation             ,   amount from test where e2 is not null and e3 is null
      6   union all select e3          , e1 ||'-'||e2, explanation             ,   amount from test where e2 is not null and e3 is not null
      7  ),
      8  temp2 as
      9  -- SYS_CONNECT_BY_PATH creates a whole path of all explanations
     10  (select level lvl,
     11     sys_connect_by_path(explanation, '/') pt,
     12     amount
     13   from temp
     14   where level > 1
     15   start with e2 is null
     16   connect by nocycle prior e1 = e2
     17  )
     18  -- finally, extract separate result columns
     19  select
     20         substr(pt, instr(pt, '/', 1, 2) + 1,
     21                    case when lvl = 2 then length(pt) else instr(pt, '/', 1, 3) - instr(pt, '/', 1, 2) - 1 end
     22               ) a1,
     23         substr(pt, instr(pt, '/', 1, 3) + 1,
     24                    case when lvl = 3 then length(pt) else instr(pt, '/', 1, 4) - instr(pt, '/', 1, 3) - 1 end
     25               ) a2,
     26         substr(pt, instr(pt, '/', 1, 4) + 1,
     27                    case when lvl = 4 then length(pt) else instr(pt, '/', 1, 5) - instr(pt, '/', 1, 4) - 1 end
     28               ) a3,
     29         amount
     30  from temp2 t;
    

    Result:

    A1                                   A2                        A3                                       AMOUNT
    ------------------------------------ ------------------------- ------------------------------------ ----------
    personnel expenses                                                                                        5000
    personnel expenses                   officers                                                             5000
    personnel expenses                   officers                  basic salaries                             3000
    personnel expenses                   officers                  increases and compensation                 2000
    goods and services purchase expenses                                                                      8000
    goods and services purchase expenses service procurement                                                  8000
    goods and services purchase expenses service procurement       stationary purchases                       6000
    goods and services purchase expenses service procurement       office supplies purchases                  2000
    
    8 rows selected.
    
    SQL>