Oracle SQL: Transform rows to multiple columns - sql

I'm using Oracle 11G and need a way to turn rows into new groups of columns in a select statement. We're transitioning to a 1:3 relationship for some of our data and need a way to get it into a view. Can you help us transform data that looks like this:
+---------+------------+
| User_Id | Station_Id |
+---------+------------+
| 1 | 203 |
| 1 | 204 |
| 2 | 203 |
| 3 | 487 |
| 3 | 3787 |
| 3 | 738 |
+---------+------------+
into this:
+---------+-------------+-------------+---------------+
| User_Id | Station_One | Station_Two | Station_Three |
+---------+-------------+-------------+---------------+
| 1 | 203 | 204 | Null |
| 2 | 203 | Null | Null |
| 3 | 487 | 3787 | 738 |
+---------+-------------+-------------+---------------+
Let me know what ever other specifics you would like and thank you for any help you can give!

You can use row_number and self joins:
with cte as
(
select userid, stationid,
row_number() over(partition by userid order by stationid) rn
from tbl
)
select distinct c1.userid,
c1.stationid station_one,
c2.stationid station_two,
c3.stationid station_three
from cte c1
left join cte c2 on c1.userid=c2.userid and c2.rn=2
left join cte c3 on c1.userid=c3.userid and c3.rn=3
where c1.rn=1
See the demo
You can also do it with row_number and subqueries:
with cte as
(
select userid, stationid,
row_number() over(partition by userid order by stationid) rn
from tbl
)
select distinct userid,
(select stationid from cte c where c.userid=cte.userid and c.rn=1) station_one,
(select stationid from cte c where c.userid=cte.userid and c.rn=2) station_two,
(select stationid from cte c where c.userid=cte.userid and c.rn=3) station_three
from cte
See the demo

The easiest way to accomplish this in my experience is to use conditional aggregation:
WITH mydata AS (
SELECT 1 AS user_id, 203 AS station_id FROM dual
UNION ALL
SELECT 1 AS user_id, 204 AS station_id FROM dual
UNION ALL
SELECT 2 AS user_id, 203 AS station_id FROM dual
UNION ALL
SELECT 3 AS user_id, 487 AS station_id FROM dual
UNION ALL
SELECT 3 AS user_id, 3787 AS station_id FROM dual
UNION ALL
SELECT 3 AS user_id, 738 AS station_id FROM dual
)
SELECT user_id
, MAX(CASE WHEN rn = 1 THEN station_id END) AS station_one
, MAX(CASE WHEN rn = 2 THEN station_id END) AS station_two
, MAX(CASE WHEN rn = 3 THEN station_id END) AS station_three
FROM (
SELECT user_id, station_id, ROW_NUMBER() OVER ( PARTITION BY user_id ORDER BY rownum ) AS rn
FROM mydata
) GROUP BY user_id;
Just replace the mydata CTE in the above query with whatever your table's name is:
SELECT user_id
, MAX(CASE WHEN rn = 1 THEN station_id END) AS station_one
, MAX(CASE WHEN rn = 2 THEN station_id END) AS station_two
, MAX(CASE WHEN rn = 3 THEN station_id END) AS station_three
FROM (
SELECT user_id, station_id, ROW_NUMBER() OVER ( PARTITION BY user_id ORDER BY rownum ) AS rn
FROM mytable
) GROUP BY user_id;

Related

Recursive CTE sql query for calculating Rolling returns

My first time writing a recursive CTE in SQL to calculate the rolling returns.
Formula : 100*(1+Returns) for first value, and then (RollingReturns)*(1+Returns)
The table is as below:
+----+--------+--------+----------------+
| ID | Date | Return | RollingReturns |
+----+--------+--------+----------------+
| 1 | 1/1/20 | 0.50% | 100.5 |
| 1 | 1/2/20 | 1.00% | 101.51 |
| 1 | 1/3/20 | -0.7% | 100.74 |
| 1 | 1/4/20 | 0.50% | 101.25 |
+----+--------+--------+----------------+
My attempt at writing the SQL query:
WITH rn_cte AS (
SELECT ROW_NUMBER() OVER (ORDER BY DATE ASC) AS RN, DATE
FROM TABLE WHERE ID = 1
ORDER BY RN
)
rr_cte
AS
(
SELECT RN,P.DATE,RETURNS,RETURNS AS ROLLINGRETURNS
FROM TABLE P
JOIN rn_cte ON rn_cte.DATE = p.DATE
WHERE P.ID = 1 AND RN = 1
UNION ALL
SELECT RN,pm.DATE,pm.RETURNS,(rr_cte.ROLLINGRETURNS)*(1+pm.RETURNS) AS ROLLINGRETURNS
FROM TABLE pm WHERE pm.ID = 1
JOIN rr_cte ON rr_cte.RN = pm.RN+1
ORDER BY pm.DATE ASC
)
SELECT *
FROM rr_cte
It gives me an error, not sure what is wrong in it.
Error
^found "RR_CTE" (at char 145) expecting `SELECT' or `'(''
Any help is appreciated.
Thanks in advance!
You want something like this:
WITH rn AS (
SELECT ROW_NUMBER() OVER (PARTITION BY id ORDER BY DATE ASC) AS RN, t.*
FROM TABLE t
WHERE ID = 1
),
cte AS (
SELECT rn.rn, rn.id, rn.date, return,
100 * (1 + rn.return) as rollingreturn
FROM rn
UNION ALL
SELECT rn.rn, rn.id, rn.date, rn.return,
cte.rollingreturn * (1 + rn.return)
FROM cte JOIN
rn
ON cte.id = rn.id AND rn.rn = cte.rn + 1
)
SELECT *
FROM cte;

SQL: Reverse tree traversal on single result

My table looks like this:
id | name | type_id | desc | parent_id
1 | Foo | 1 | Foo | NULL
2 | Bar | 2 | Bar | 1
3 | FB | 2 | FB | 1
4 | Foo1 | 1 | Foo1 | NULL
5 | Bar1 | 2 | Bar1 | 4
6 | FB1 | 2 | FB1 | 4
And I want to provide an ID of the lowest node, returning everything up to the highest node in a single row (There is other data that I'm returning along with this).
For example, I want to provide ID 3, and the results to look like so:
xxxxx (other data) | id | name | type_id | desc | parent_id | id | name | type_id | desc | parent_id
xxxxxxx | 3 | FB | 2 | FB | 1 | 1 | Foo | 1 | Foo | NULL
Unfortunately, I haven't found anything that can work for me. I have a CTE but it goes top down and each node is its own row:
WITH RECURSIVE cte AS (
select T.*
from table as T
where T.id = 3
union all
select T.*
from table as T
inner join cte as C
on T.parent_id = C.id
)
SELECT * FROM cte
When I do this, I only get one result:
id | name | type_id | desc | parent_id
3 | FB | 2 | FB | 1
Any help would be appreciated, thanks!
The logic of the common-table expression looks good; it generates one row for the original id, and then one row per parent. To pivot the resulting rows to columns, you can then use conditional aggregation - this requires that you decide in advance the maximum number of levels. For two levels, this would be:
with recursive cte as (
select t.*, 1 lvl
from table as t
where t.id = 3
union all
select t.*, c.lvl + 1
from table as t
inner join cte as c on t.parent_id = c.id
)
select
max(id) filter(where lvl = 1) id,
max(name) filter(where lvl = 1) name,
max(type_id) filter(where lvl = 1) type_id,
max(descr) filter(where lvl = 1) descr,
max(parent_id) filter(where lvl = 1) parent_id,
max(id) filter(where lvl = 2) id2,
max(name) filter(where lvl = 2) name2,
max(type_id) filter(where lvl = 2) type_id2,
max(descr) filter(where lvl = 2) descr2,
max(parent_id) filter(where lvl = 2) parent_id2,
from cte
You might also want to consider accumating the rows as an array of json objects:
with recursive cte as (
select t.*, 1 lvl
from table as t
where t.id = 3
union all
select t.*, c.lvl + 1
from table as t
inner join cte as c on t.parent_id = c.id
)
select jsonb_agg(to_jsonb(c) order by lvl) res
from cte c
I have used Oracle 11g using Pivot, Row_number and hierarchical queries to solve this.
Demo
WITH CTE1 AS (SELECT A.*, LEVEL AS LVL FROM TABLE1 A
START WITH ID IN (2,3)
CONNECT BY PRIOR PARENT_ID = ID)
select * from (
select x.*, row_number() over (order by id desc) rn from (
SELECT DISTINCT ID, NAME, TYPE_ID, DESCRIPTION, PARENT_ID FROM CTE1 ORDER BY ID DESC) x) y
pivot
( min(id) ID, min(name) name, min(type_id) type_id,
min(description) description, min(parent_id) for rn in (1, 2, 3)
);

Getting the third moving date

I need to get the third moving date based on address for each person. Either status change or address change will make DT change. For example, for PERSON_ID:1, the third moving date should be 07/16/2016. Thanks!
The data is as below:
PERSON_ID STATUS DT ADDRESS
1 12 5/6/2016 3
1 6 5/8/2016 3
1 7 6/5/2016 3
1 1 6/13/2016 3
1 12 6/20/2016 1
1 17 7/8/2016 1
1 1 7/11/2016 1
1 12 7/16/2016 2
1 3 12/6/2016 2
2 5 3/11/2016 5
2 1 5/15/2016 4
2 6 7/18/2016 6
2 12 7/21/2016 6
Using row_number() and group by for the min(dt) per address:
Note: This will not work correctly if the person moves between the same addresses.
select
Person_id
, dt = convert(char(10),dt,120)
, Address
from (
select
person_id
, dt = min(dt)
, address
, rn = row_number() over (partition by person_id order by min(dt))
from t
group by person_id, address
) s
where rn = 3
rextester demo: http://rextester.com/VLTUU16478
returns:
+-----------+------------+---------+
| Person_id | dt | Address |
+-----------+------------+---------+
| 1 | 2016-07-16 | 2 |
| 2 | 2016-07-18 | 6 |
+-----------+------------+---------+
To solve this correctly for a person moving between the same addresses, you have to address the gaps and islands problem.
Adding an additional subquery to the above solution so we can identify and group by the islands:
select
Person_id
, dt = convert(char(10),dt,120)
, Address
from (
select
person_id
, dt = min(dt)
, address
, rn = row_number() over (partition by person_id order by min(dt))
from (
select
person_id
, address
, dt
, island = row_number() over (partition by person_id order by dt)
- row_number() over (partition by person_id, address order by dt)
from t
) s
group by person_id, address, island
) s
where rn = 3
rextester demo: http://rextester.com/PPIH49666
returns:
+-----------+------------+---------+
| Person_id | dt | Address |
+-----------+------------+---------+
| 1 | 2016-07-16 | 3 |
| 2 | 2016-07-18 | 5 |
+-----------+------------+---------+
I'm not sure i understood what you need to get but I think what you're trying to do is something like:
SELECT * FROM
(
SELECT person_id,adress,[status],DT, rank() over (partition by person_id order by adress,dt)-1 as movement
FROM #t
WHERE person_id=1
) t
WHERE t.movement=3
Question is not entirely clear
select PERSON_ID, STATUS, DT, ADDRESS
from ( select PERSON_ID, STATUS, DT, ADDRESS
, row_number() over (partition by person order by dt) as rn
from ( select PERSON_ID, STATUS, DT, ADDRESS
, row_number() over ( partition by PERSON_ID, address order by dt) as rn
from table
) tt
where tt.rn = 1
) rr
where rr.rn = 3

Get latest rows by date from aggregate

Hey i'm kinda stuck with this query. Using SQL-server
i have in the table, UNIQUE(date, medId, userId)
I have this table
date | medId | userId | Quantity
2016-06-10 | 2 | 1 | 28
2016-06-07 | 1 | 1 | 19
2016-06-06 | 1 | 1 | 10
i want to get the row with the max date, per group of medId,userId, in this case
i would get
2016-06-10 | 2 | 1 | 28
2016-06-07 | 1 | 1 | 19
thanks in advance!
i've tried this
SELECT
a.userMedStockDate,
a.userMedStockMedId,
a.userMedStockUserId,
a.userMedStockQuantity
FROM (SELECT
MAX(userMedStockDate) AS userMedStockDate,
userMedStockQuantity,
userMedStockUserId,
userMedStockMedId,
ROW_NUMBER() OVER (partition by userMedStockMedId,userMedStockUserId
ORDER BY MAX(userMedStockDate) desc) AS rnk
FROM UserMedStock
GROUP BY
userMedStockUserId,
userMedStockQuantity,
userMedStockMedId) a
WHERE a.rnk = 1
[SOLVED]
this should work
select * from
(
select
[date] , medId, userId ,Quantity
,row_number() over (partition by medId, userId order by [date] desc) as rowid
from yourtable
) as x
where rowid = 1
Could also try this:
select y.* from
table1 y inner join
(
SELECT [Date] = MAX([Date]), medId, userId
FROM table1
GROUP BY medId, userId
) x on y.[Date] = x.[Date] and y.medId = x.medId and y.userId = x.userId
i changed the fields to my actual table but here
SELECT
a.userMedStockDate, a.userMedStockMedId, a.userMedStockUserId, a.userMedStockQuantity
FROM(
SELECT
MAX(userMedStockDate) AS userMedStockDate,
userMedStockQuantity,
userMedStockUserId,
userMedStockMedId,
ROW_NUMBER()OVER(partition by userMedStockMedId, userMedStockUserId ORDER BY MAX(userMedStockDate) desc) AS rnk
FROM UserMedStock
GROUP BY userMedStockUserId, userMedStockQuantity, userMedStockMedId
) a
WHERE a.rnk = 1

make a select query with group by

This my table with sample data.
id | path | category (1-6) | secter_id | date
----------------------------------------------
1 | ddd | 5 | a | 10-01
2 | ddgg | 6 | a | 10-03
3 | fff | 5 | a | 10-02
I want to filter the latest category 5 and 6 rows for each sector id.
Expected result
id path | category| secter_id | date
--------------------------------------
2 | ddgg | 6 | a | 10-03
3 | fff | 5 | a | 10-02
Is this possible do only sql?
This query should do it for you
SELECT A.ID,
A.PATH,
A.CATEGORY,
A.SECTOR_ID,
A.dDATE
FROM yourTable A
INNER JOIN
(SELECT CATEGORY,
MAX(dDate) AS dDate
FROM yourTable
GROUP BY CATEGORY) B
ON A.CATEGORY = B.CATEGORY
AND A.dDate = B.dDate
Here is a SQLFiddle with the query
You can try with this code, is not elegant but it should work.
Select id,path,category,secter_id,date
FROM myTable a
INNER JOIN (SELECT category, MAX(date) date FROM myTable GROUP BY Category) b ON a.category = b.Category AND a.date = b.Date
WHERE A.Category IN (5,6)
You can try this -
SELECT id,path,category,secter_id, date
FROM
(
SELECT id,path,category,secter_id, date,
DENSE_RANK() OVER (PARTITION BY category ORDER BY DATE DESC) date_rank
FROM sample_table t
WHERE category in (5,6)
)
WHERE date_rank = 1;
try this
select path,category,secter_id,date from
(
select path,category,secter_id,date,dense_rank() over(PARTITION by category order by date desc)as rk
from tbl WHERE category in (5,6)
)data
where rk=1
select * from (
select
id, path , category, secter_id, date ,
row_number() over (partition by category order by date desc) as rnk
from your_table
)
where rnk = 1;
Try this
SELECT [id]
,[path]
,[category]
,[secter_id]
,[date]
FROM [MyTable]
WHERE date IN (SELECT MAX(date)
FROM [MyTable]
WHERE category IN (SELECT DISTINCT category FROM MyTable)
GROUP BY category)