Pivot multiple columns Oracle SQL - sql

I have a table called "Books"
ID | lan_id | main_title | part
---------------------------------
1 | 1 | Quick guide | 1
2 | 1 | Quick guide | 4
---------------------------------
what I want to achieve is:
--------------------------------------------
ID | 1 | 2
Lan_id | 1 | 1
main_tile | Quick guide | Quick guide
part | 1 | 4
--------------------------------------------
I think I need a pivot, but I have no idea how to do this.

Use UNPIVOT then PIVOT:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_name ( ID, lan_id, main_title, part ) AS
SELECT 1, 1, 'Quick guide', 1 FROM DUAL UNION ALL
SELECT 2, 1, 'Quick guide', 4 FROM DUAL;
Query 1:
SELECT *
FROM ( SELECT ROWNUM AS rn,
TO_CHAR( id ) AS id,
TO_CHAR( lan_id ) AS lan_id,
main_title,
TO_CHAR( part ) AS part
FROM table_name )
UNPIVOT ( value FOR key IN (
id,
lan_id,
main_title,
part
) )
PIVOT ( MAX( value ) FOR rn IN (
1 AS "1",
2 AS "2"
) )
ORDER BY key
Results:
| KEY | 1 | 2 |
|------------|-------------|-------------|
| ID | 1 | 2 |
| LAN_ID | 1 | 1 |
| MAIN_TITLE | Quick guide | Quick guide |
| PART | 1 | 4 |

You can use both UNPIVOT and PIVOT as follows.
WITH Books (Id,
lan_id,
main_title,
part)
AS (SELECT 1, 1, 'Quick guide', 1 FROM DUAL
UNION ALL
SELECT 2, 1, 'Quick guide', 4 FROM DUAL)
SELECT *
FROM (SELECT *
FROM (SELECT TO_CHAR (id) id,
TO_CHAR (lan_id) lan_id,
main_title,
TO_CHAR (part) part,
TO_CHAR (ROWNUM) rn
FROM Books) UNPIVOT (columns
FOR header
IN (Id, lan_id, main_title, part))) PIVOT (MAX (
columns)
FOR rn
IN (1 AS row1,
2 AS row2));
Output:
Header row1 row2
----- ---- ----
ID 1 2
LAN_ID 1 1
MAIN_TITLE Quick guide Quick guide
PAIR 1 4

Related

Creating a SQL view to query whether a node is a descendant of a specific node

I have 2 SQL tables with the following data structure:
Table 1 : FAVORITES
Columns:
pk
fk_user (the user table is irrelevant for now)
fk_tree_node
Table 2 : TREE
Columns:
pk
fk_parent_node
I want to create a view from so that I can query, whether a node is favorited/descendant of a favorited node or not. So for every entry in FAVORITES it the view would have several entries where the user is associated with either the favorited node, or a descendant of it.
View: FAV_OR_DESCENDANT
Columns:
fk_tree_node
fk_user
pk
Queries would work like this
SELECT *
FROM FAV_OR_DESCENDANT
WHERE fk_user = 0
The results I'd expect to get for a given tree would look like this:
Tree:
TREE:
+--------+----+----------------+
| rownum | pk | pk_parent_node |
+--------+----+----------------+
| 1 | 1 | null |
| 2 | 2 | 1 |
| 3 | 3 | 2 |
| 4 | 4 | 1 |
+--------+----+----------------+
FAVORITES:
+--------+----+---------+--------------+
| rownum | pk | fk_user | fk_tree_node |
+--------+----+---------+--------------+
| 1 | 0 | 0 | 1 |
+--------+----+---------+--------------+
Tree representation:
1 <-- User 0 has only favorited this single node
/ \
2 4
/
3
Result data in FAV_OR_DESCENDANT:
+--------+---------+--------------+
| rownum | fk_user | fk_tree_node |
+--------+---------+--------------+
| 1 | 0 | 1 |
| 2 | 0 | 2 |
| 3 | 0 | 3 |
| 4 | 0 | 4 |
+--------+---------+--------------+
I know how to write this query if I'm asking for all favorited nodes/descendant nodes for a specific user. However, I'm struggling in translating that into an SQL query that would create a view:
SELECT DISTINCT *
FROM tree
START WITH tree.pk IN (
SELECT fk_tree_node
FROM favorites
WHERE fk_user = 0
)
CONNECT BY PRIOR tree.pk = tree.fk_parent_node
Other questions I found where more centered around making queries or were not limited by SQL. I'd be thankful for every hint in the right direction.
Since you need to get all descendant nodes, you need to aggregate all parent nodes, for example using sys_connect_by_path (or you can use hierarchical recursive subquery factoring):
with
TREE(pk, pk_parent_node) as (
select 1 , null from dual union all
select 2 , 1 from dual union all
select 3 , 2 from dual union all
select 4 , 1 from dual
)
,FAVORITES( pk, fk_user, fk_tree_node) as (
select 0, 0, 1 from dual
)
,v_tree as (
select pk,pk_parent_node,sys_connect_by_path(pk,'/') p_path
from TREE
start with pk_parent_node is null
connect by prior pk = pk_parent_node
)
select *
from v_tree;
Results:
PK PK_PARENT_NODE P_PATH
---------- -------------- ------------------------------
1 NULL /1
2 1 /1/2
3 2 /1/2/3
4 1 /1/4
In fact you can already check if P_PATH contains a favorite node, but since you want a view with users, we can aggregate them into a new column:
with
TREE(pk, pk_parent_node) as (
select 1 , null from dual union all
select 2 , 1 from dual union all
select 3 , 2 from dual union all
select 4 , 1 from dual
)
,FAVORITES( pk, fk_user, fk_tree_node) as (
select 0, 0, 1 from dual union all
select 1, 1, 3 from dual
)
,v_tree as (
select pk,pk_parent_node,sys_connect_by_path(pk,'/')||'/' p_path
from TREE
start with pk_parent_node is null
connect by prior pk = pk_parent_node
)
select
v.*, v2.*
from v_tree v
outer apply(
select
xmlelement(USERS, xmlagg(xmlelement(ID, f.pk) order by f.pk)) as users
from FAVORITES f
where p_path like '%/'||f.fk_tree_node||'/%'
) v2;
Results:
PK PK_PARENT_NODE P_PATH USERS
---------- -------------- ------------------------------ ------------------------------------------------------------
1 NULL /1/ <USERS><ID>0</ID></USERS>
2 1 /1/2/ <USERS><ID>0</ID></USERS>
3 2 /1/2/3/ <USERS><ID>0</ID><ID>1</ID></USERS>
4 1 /1/4/ <USERS><ID>0</ID></USERS>
I've added one more user 1 to make it more clear.
So now you just need to add a predicate to filter users:
with
TREE(pk, pk_parent_node) as (
select 1 , null from dual union all
select 2 , 1 from dual union all
select 3 , 2 from dual union all
select 4 , 1 from dual
)
,FAVORITES( pk, fk_user, fk_tree_node) as (
select 0, 0, 1 from dual union all
select 1, 1, 3 from dual
)
,v_tree as (
select pk,pk_parent_node,sys_connect_by_path(pk,'/')||'/' p_path
from TREE
start with pk_parent_node is null
connect by prior pk = pk_parent_node
)
,v_final_view as (
select
v.*, v2.*
from v_tree v
outer apply(
select
xmlelement(USERS, xmlagg(xmlelement(ID, f.pk) order by f.pk)) as users
from FAVORITES f
where p_path like '%/'||f.fk_tree_node||'/%'
) v2
)
select *
from v_final_view
where
xmlexists(
'$USERS/USERS[ID=$USER_ID]'
passing
users as USERS,
1 as USER_ID -- your input param - user id
)
;
Results:
PK PK_PARENT_NODE P_PATH USERS
---- -------------- ------------ ------------------------------------
3 2 /1/2/3/ <USERS><ID>0</ID><ID>1</ID></USERS>
Of course, it's just an example of this approach, so you can use other functions for aggregation or even create materialized view for performance.

Possible to use a column name in a UDF in SQL?

I have a query in which a series of steps is repeated constantly over different columns, for example:
SELECT DISTINCT
MAX (
CASE
WHEN table_2."GRP1_MINIMUM_DATE" <= cohort."ANCHOR_DATE" THEN 1
ELSE 0
END)
OVER (PARTITION BY cohort."USER_ID")
AS "GRP1_MINIMUM_DATE",
MAX (
CASE
WHEN table_2."GRP2_MINIMUM_DATE" <= cohort."ANCHOR_DATE" THEN 1
ELSE 0
END)
OVER (PARTITION BY cohort."USER_ID")
AS "GRP2_MINIMUM_DATE"
FROM INPUT_COHORT cohort
LEFT JOIN INVOLVE_EVER table_2 ON cohort."USER_ID" = table_2."USER_ID"
I was considering writing a function to accomplish this as doing so would save on space in my query. I have been reading a bit about UDF in SQL but don't yet understand if it is possible to pass a column name in as a parameter (i.e. simply switch out "GRP1_MINIMUM_DATE" for "GRP2_MINIMUM_DATE" etc.). What I would like is a query which looks like this
SELECT DISTINCT
FUNCTION(table_2."GRP1_MINIMUM_DATE") AS "GRP1_MINIMUM_DATE",
FUNCTION(table_2."GRP2_MINIMUM_DATE") AS "GRP2_MINIMUM_DATE",
FUNCTION(table_2."GRP3_MINIMUM_DATE") AS "GRP3_MINIMUM_DATE",
FUNCTION(table_2."GRP4_MINIMUM_DATE") AS "GRP4_MINIMUM_DATE"
FROM INPUT_COHORT cohort
LEFT JOIN INVOLVE_EVER table_2 ON cohort."USER_ID" = table_2."USER_ID"
Can anyone tell me if this is possible/point me to some resource that might help me out here?
Thanks!
There is no such direct as #Tejash already stated, but the thing looks like your database model is not ideal - it would be better to have a table that has USER_ID and GRP_ID as keys and then MINIMUM_DATE as seperate field.
Without changing the table structure, you can use UNPIVOT query to mimic this design:
WITH INVOLVE_EVER(USER_ID, GRP1_MINIMUM_DATE, GRP2_MINIMUM_DATE, GRP3_MINIMUM_DATE, GRP4_MINIMUM_DATE)
AS (SELECT 1, SYSDATE, SYSDATE, SYSDATE, SYSDATE FROM dual UNION ALL
SELECT 2, SYSDATE-1, SYSDATE-2, SYSDATE-3, SYSDATE-4 FROM dual)
SELECT *
FROM INVOLVE_EVER
unpivot ( minimum_date FOR grp_id IN ( GRP1_MINIMUM_DATE AS 1, GRP2_MINIMUM_DATE AS 2, GRP3_MINIMUM_DATE AS 3, GRP4_MINIMUM_DATE AS 4))
Result:
| USER_ID | GRP_ID | MINIMUM_DATE |
|---------|--------|--------------|
| 1 | 1 | 09/09/19 |
| 1 | 2 | 09/09/19 |
| 1 | 3 | 09/09/19 |
| 1 | 4 | 09/09/19 |
| 2 | 1 | 09/08/19 |
| 2 | 2 | 09/07/19 |
| 2 | 3 | 09/06/19 |
| 2 | 4 | 09/05/19 |
With this you can write your query without further code duplication and if you need use PIVOT-syntax to get one line per USER_ID.
The final query could then look like this:
WITH INVOLVE_EVER(USER_ID, GRP1_MINIMUM_DATE, GRP2_MINIMUM_DATE, GRP3_MINIMUM_DATE, GRP4_MINIMUM_DATE)
AS (SELECT 1, SYSDATE, SYSDATE, SYSDATE, SYSDATE FROM dual UNION ALL
SELECT 2, SYSDATE-1, SYSDATE-2, SYSDATE-3, SYSDATE-4 FROM dual)
, INPUT_COHORT(USER_ID, ANCHOR_DATE)
AS (SELECT 1, SYSDATE-1 FROM dual UNION ALL
SELECT 2, SYSDATE-2 FROM dual UNION ALL
SELECT 3, SYSDATE-3 FROM dual)
-- Above is sampledata query starts from here:
, unpiv AS (SELECT *
FROM INVOLVE_EVER
unpivot ( minimum_date FOR grp_id IN ( GRP1_MINIMUM_DATE AS 1, GRP2_MINIMUM_DATE AS 2, GRP3_MINIMUM_DATE AS 3, GRP4_MINIMUM_DATE AS 4)))
SELECT qcsj_c000000001000000 user_id, GRP1_MINIMUM_DATE, GRP2_MINIMUM_DATE, GRP3_MINIMUM_DATE, GRP4_MINIMUM_DATE
FROM INPUT_COHORT cohort
LEFT JOIN unpiv table_2
ON cohort.USER_ID = table_2.USER_ID
pivot (MAX(CASE WHEN minimum_date <= cohort."ANCHOR_DATE" THEN 1 ELSE 0 END) AS MINIMUM_DATE
FOR grp_id IN (1 AS GRP1,2 AS GRP2,3 AS GRP3,4 AS GRP4))
Result:
| USER_ID | GRP1_MINIMUM_DATE | GRP2_MINIMUM_DATE | GRP3_MINIMUM_DATE | GRP4_MINIMUM_DATE |
|---------|-------------------|-------------------|-------------------|-------------------|
| 3 | | | | |
| 1 | 0 | 0 | 0 | 0 |
| 2 | 0 | 1 | 1 | 1 |
This way you only have to write your calculation logic once (see line starting with pivot).

How to create a query with all of dependencies in hierarchical organization?

I've been trying hard to create a query to see all dependencies in a hierarchical organization. But the only I have accuaried is to retrieve the parent dependency. I have attached an image to show what I need.
Thanks for any clue you can give me.
This is the code I have tried with the production table.
WITH CTE AS
(SELECT
H1.systemuserid,
H1.pes_aprobadorid,
H1.yomifullname,
H1.internalemailaddress
FROM [dbo].[ext_systemuser] H1
WHERE H1.pes_aprobadorid is null
UNION ALL
SELECT
H2.systemuserid,
H2.pes_aprobadorid,
H2.yomifullname,
H2.internalemailaddress
FROM [dbo].[ext_systemuser] H2
INNER JOIN CTE c ON h2.pes_aprobadorid=c.systemuserid)
SELECT *
FROM CTE
OPTION (MAXRECURSION 1000)
You are almost there with your query. You just have to include all rows as a starting point. Also the join should be cte.parent_id = ext.user_id and not the other way round. I've done an example query in postgres, but you shall easily adapt it to your DBMS.
with recursive st_units as (
select 0 as id, NULL as pid, 'Director' as nm
union all select 1, 0, 'Department 1'
union all select 2, 0, 'Department 2'
union all select 3, 1, 'Unit 1'
union all select 4, 3, 'Unit 1.1'
),
cte AS
(
SELECT id, pid, cast(nm as text) as path, 1 as lvl
FROM st_units
UNION ALL
SELECT c.id, u.pid, cast(path || '->' || u.nm as text), lvl + 1
FROM st_units as u
INNER JOIN cte as c on c.pid = u.id
)
SELECT id, pid, path, lvl
FROM cte
ORDER BY lvl, id
id | pid | path | lvl
-: | ---: | :--------------------------------------- | --:
0 | null | Director | 1
1 | 0 | Department 1 | 1
2 | 0 | Department 2 | 1
3 | 1 | Unit 1 | 1
4 | 3 | Unit 1.1 | 1
1 | null | Department 1->Director | 2
2 | null | Department 2->Director | 2
3 | 0 | Unit 1->Department 1 | 2
4 | 1 | Unit 1.1->Unit 1 | 2
3 | null | Unit 1->Department 1->Director | 3
4 | 0 | Unit 1.1->Unit 1->Department 1 | 3
4 | null | Unit 1.1->Unit 1->Department 1->Director | 4
db<>fiddle here
I've reached this code that it is working but when I include a hierarchy table of more than 1800 the query is endless.
With cte AS
(select systemuserid, systemuserid as pes_aprobadorid, internalemailaddress, yomifullname
from #TestTable
union all
SELECT c.systemuserid, u.pes_aprobadorid, u.internalemailaddress, u.yomifullname
FROM #TestTable as u
INNER JOIN cte as c on c.pes_aprobadorid = u.systemuserid
)
select distinct * from cte
where pes_aprobadorid is not null
OPTION (MAXRECURSION 0)

Order by "In clause"

I have a query wherein I use "In" clause in it.
Now I wish to have the result set in same order as my In clause.
For example -
select Id,Name from mytable where id in (3,6,7,1)
Result Set :
|Id | Name |
------------
| 3 | ABS |
| 6 | NVK |
| 7 | USD |
| 1 | KSK |
I do not want to use any temp table.
Is it possible to achieve the goal in one query?
You can use CTE as well
with filterID as
(
3 ID, 1 as sequence
union
6, 2
union
7, 3
union
1, 4
)
select mytable.* from mytable
inner join filterID on filterID.ID = mytable.ID
order by filterID.sequence ;
In T-SQL, you can do this using a big case:
select Id, Name
from mytable
where id in (3, 6, 7, 1)
order by (case id when 3 then 1 when 6 then 2 when 7 then 3 else 4 end);
Or with charindex():
order by charindex(',' + cast(id as varchar(255)) + ',',
',3,6,7,1,')

SQL query update by grouping

I'm dealing with some legacy data in an Oracle table and have the following
--------------------------------------------
| RefNo | ID |
--------------------------------------------
| FOO/BAR/BAZ/AAAAAAAAAA | 1 |
| FOO/BAR/BAZ/BBBBBBBBBB | 1 |
| FOO/BAR/BAZ/CCCCCCCCCC | 1 |
| FOO/BAR/BAZ/DDDDDDDDDD | 1 |
--------------------------------------------
For each of the /FOO/BAR/BAZ/% records I want to make the ID a Unique incrementing number.
Is there a method to do this in SQL?
Thanks in advance
EDIT
Sorry for not being specific. I have several groups of records /FOO/BAR/BAZ/, /FOO/ZZZ/YYY/. The same transformation needs to occur for each of these other (example) groups. The recnum can't be used I want ID to start from 1, incrementing, for each group of records I have to change.
Sorry for making a mess of my first post. Output should be
--------------------------------------------
| RefNo | ID |
--------------------------------------------
| FOO/BAR/BAZ/AAAAAAAAAA | 1 |
| FOO/BAR/BAZ/BBBBBBBBBB | 2 |
| FOO/BAR/BAZ/CCCCCCCCCC | 3 |
| FOO/BAR/BAZ/DDDDDDDDDD | 4 |
| FOO/ZZZ/YYY/AAAAAAAAAA | 1 |
| FOO/ZZZ/YYY/BBBBBBBBBB | 2 |
--------------------------------------------
Let's try something like this(Oracle version 10g and higher):
SQL> with t1 as(
2 select 'FOO/BAR/BAZ/AAAAAAAAAA' as RefNo, 1 as ID from dual union all
3 select 'FOO/BAR/BAZ/BBBBBBBBBB', 1 from dual union all
4 select 'FOO/BAR/BAZ/CCCCCCCCCC', 1 from dual union all
5 select 'FOO/BAR/BAZ/DDDDDDDDDD', 1 from dual union all
6 select 'FOO/ZZZ/YYY/AAAAAAAAAA', 1 from dual union all
7 select 'FOO/ZZZ/YYY/BBBBBBBBBB', 1 from dual union all
8 select 'FOO/ZZZ/YYY/CCCCCCCCCC', 1 from dual union all
9 select 'FOO/ZZZ/YYY/DDDDDDDDDD', 1 from dual
10 )
11 select row_number() over(partition by ComPart order by DifPart) as id
12 , RefNo
13 From (select regexp_substr(RefNo, '[[:alpha:]]+$') as DifPart
14 , regexp_substr(RefNo, '([[:alpha:]]+/)+') as ComPart
15 , RefNo
16 , Id
17 from t1
18 ) q
19 ;
ID REFNO
---------- -----------------------
1 FOO/BAR/BAZ/AAAAAAAAAA
2 FOO/BAR/BAZ/BBBBBBBBBB
3 FOO/BAR/BAZ/CCCCCCCCCC
4 FOO/BAR/BAZ/DDDDDDDDDD
1 FOO/ZZZ/YYY/AAAAAAAAAA
2 FOO/ZZZ/YYY/BBBBBBBBBB
3 FOO/ZZZ/YYY/CCCCCCCCCC
4 FOO/ZZZ/YYY/DDDDDDDDDD
I think that actual updating the ID column wouldn't be a good idea. Every time you add new groups of data you would have to run the update statement again. The better way would be creating a view and you will see desired output every time you query it.
rownum can be used as an incrementing ID?
UPDATE legacy_table
SET id = ROWNUM;
This will assign unique values to all records in the table. This link contains documentation about Oracle Pseudocolumn.
You can run the following:
update <table_name> set id = rownum where descr like 'FOO/BAR/BAZ/%'
This is pretty rough and I'm not sure if your RefNo is a single value column or you just made it like that for simplicity.
select
sub.RefNo
row_number() over (order by sub.RefNo) + (select max(id) from TABLE),
from (
select FOO+'/'+BAR+'/'+BAZ+'/'+OTHER as RefNo
from TABLE
group by FOO+'/'+BAR+'/'+BAZ+'/'+OTHER
) sub