I have a table like this:
time length name
00:01:00 2 a
00:11:22 2 a
01:01:00 45 a
00:23:00 3 b
and I want to retrieve data from the table in the form:
a b
time length time length
00:01:00 2 00:23:00 3
00:11:22 2
01:01:00 2
so it is a simple task of rearranging data, atm I am doing this in a bash script, but I wonder if there is an easy way to do it in Oracle?
You can use analytical function ROW_NUMBER and full outer join as follows:
WITH CTE1 AS
(SELECT T.*, ROW_NUMBER() OVER (ORDER BY LENGTH, TIME) AS RN FROM YOUR_TABLE T WHERE NAME = 'a'),
CTE2 AS
(SELECT T.*, ROW_NUMBER() OVER (ORDER BY LENGTH, TIME) AS RN FROM YOUR_TABLE T WHERE NAME = 'b')
SELECT A.TIME, A.LENGTH, B.TIME, B.LENGTH
FROM CTE1 A FULL OUTER JOIN CTE2 B
ON A.RN = B.RN
Note: You need to use proper order by to order the records as per your requirement. I have used LENGTH, TIME
You can use a multi-column pivot, by adding an extra column that links the related A and B values; presumably by time order, something like:
select time_col, length_col, name_col,
dense_rank() over (partition by name_col order by time_col) as rnk
from your_table;
TIME_COL LENGTH_COL N RNK
-------- ---------- - ----------
00:01:00 2 a 1
00:11:22 2 a 2
01:01:00 45 a 3
00:23:00 3 b 1
and then pivot based on that:
select *
from (
select time_col, length_col, name_col,
dense_rank() over (partition by name_col order by time_col) as rnk
from your_table
)
pivot (
max(time_col) as time_col, max(length_col) as length_col
for name_col in ('a' as a, 'b' as b)
);
RNK A_TIME_C A_LENGTH_COL B_TIME_C B_LENGTH_COL
---------- -------- ------------ -------- ------------
1 00:01:00 2 00:23:00 3
2 00:11:22 2
3 01:01:00 45
I've left the rnk value in the output; if you don't want that you can list the columns in the select list:
select a_time_col, a_length_col, b_time_col, b_length_col
from ...
Or you could do the same thing with conditional aggregation (which is what pivot uses under the hood anyway):
select
max(case when name_col = 'a' then time_col end) as time_col_a,
max(case when name_col = 'a' then length_col end) as length_col_a,
max(case when name_col = 'b' then time_col end) as time_col_b,
max(case when name_col = 'b' then length_col end) as length_col_b
from (
select time_col, length_col, name_col,
dense_rank() over (partition by name_col order by time_col) as rnk
from your_table
)
group by rnk
order by rnk;
TIME_COL LENGTH_COL_A TIME_COL LENGTH_COL_B
-------- ------------ -------- ------------
00:01:00 2 00:23:00 3
00:11:22 2
01:01:00 45
db<>fiddle
Related
I have a query which will fetch two rows only and I want to bring second row data into columns with different column name.
Below is the original query result.
The expected result is like
Expected result.
Please help how shd I proceed, not able to figure out with PIVOT.
Here's one option; see comments within code.
SQL> with
2 your_query (column1, column2, column3) as
3 -- this is what your current query returns
4 (select 1, 'ABC', 123 from dual union all
5 select 2, 'XYZ', 456 from dual
6 ),
7 temp as
8 -- distinguish 1st from 2nd row
9 (select y.*,
10 row_number() over (order by column1) rn
11 from your_query y
12 )
13 -- finally, cross join two rows and conditionally display columns.
14 -- MAX is here to avoid empty "cells"
15 select max(case when a.rn = 1 then a.column1 end) as col1,
16 max(case when a.rn = 1 then a.column2 end) as col2,
17 max(case when a.rn = 1 then a.column3 end) as col3,
18 --
19 max(case when b.rn = 2 then b.column1 end) as col4,
20 max(case when b.rn = 2 then b.column2 end) as col5,
21 max(case when b.rn = 2 then b.column3 end) as col6
22 from temp a cross join temp b;
COL1 COL COL3 COL4 COL COL6
---------- --- ---------- ---------- --- ----------
1 ABC 123 2 XYZ 456
SQL>
I have data like below:
group
seq
activity
A
1
scan
A
2
visit
A
3
pay
B
1
drink
B
2
rest
I expect to have 1 new column "hist" like below:
group
seq
activity
hist
A
1
scan
NULL
A
2
visit
scan
A
3
pay
scan, visit
B
1
drink
NULL
B
2
rest
drink
I was trying to solve with LAG function, but LAG only returns one row from previous instead of multiple.
Truly appreciate any help!
Use a correlated sub-query:
SELECT t.*,
(SELECT LISTAGG(activity, ',') WITHIN GROUP (ORDER BY seq)
FROM table_name l
WHERE t."GROUP" = l."GROUP"
AND l.seq < t.seq
) AS hist
FROM table_name t
Or a hierarchical query:
SELECT t.*,
SUBSTR(SYS_CONNECT_BY_PATH(PRIOR activity, ','), 3) AS hist
FROM table_name t
START WITH seq = 1
CONNECT BY
PRIOR seq + 1 = seq
AND PRIOR "GROUP" = "GROUP"
Or a recursive sub-query factoring clause:
WITH rsqfc ("GROUP", seq, activity, hist) AS (
SELECT "GROUP", seq, activity, NULL
FROM table_name
WHERE seq = 1
UNION ALL
SELECT t."GROUP", t.seq, t.activity, r.hist || ',' || r.activity
FROM rsqfc r
INNER JOIN table_name t
ON (r."GROUP" = t."GROUP" AND r.seq + 1 = t.seq)
)
SEARCH DEPTH FIRST BY "GROUP" SET order_rn
SELECT "GROUP", seq, activity, SUBSTR(hist, 2) AS hist
FROM rsqfc
Which, for the sample data:
CREATE TABLE table_name ("GROUP", seq, activity) AS
SELECT 'A', 1, 'scan' FROM DUAL UNION ALL
SELECT 'A', 2, 'visit' FROM DUAL UNION ALL
SELECT 'A', 3, 'pay' FROM DUAL UNION ALL
SELECT 'B', 1, 'drink' FROM DUAL UNION ALL
SELECT 'B', 2, 'rest' FROM DUAL;
All output:
GROUP
SEQ
ACTIVITY
HIST
A
1
scan
null
A
2
visit
scan
A
3
pay
scan,visit
B
1
drink
null
B
2
rest
drink
db<>fiddle here
To aggregate strings in Oracle we use LISAGG function.
In general, you need a windowing_clause to specify a sliding window for analytic function to calculate running total.
But unfortunately LISTAGG doesn't support it.
To simulate this behaviour you may use model_clause of the select statement. Below is an example with explanation.
select
group_
, activity
, seq
, hist
from t
model
/*Where to restart calculation*/
partition by (group_)
/*Add consecutive numbers to reference "previous" row per group.
May use "seq" column if its values are consecutive*/
dimension by (
row_number() over(
partition by group_
order by seq asc
) as rn
)
measures (
/*Other columnns to return*/
activity
, cast(null as varchar2(1000)) as hist
, seq
)
rules update (
/*Apply this rule sequentially*/
hist[any] order by rn asc =
/*Previous concatenated result*/
hist[cv()-1]
/*Plus comma for the third row and tne next rows*/
|| presentv(activity[cv()-2], ',', '') /**/
/*lus previous row's value*/
|| activity[cv()-1]
)
GROUP_ | ACTIVITY | SEQ | HIST
:----- | :------- | --: | :---------
A | scan | 1 | null
A | visit | 2 | scan
A | pay | 3 | scan,visit
B | drink | 1 | null
B | rest | 2 | drink
db<>fiddle here
Few more variants (without subqueries):
SELECT--+ NO_XML_QUERY_REWRITE
t.*,
regexp_substr(
listagg(activity, ',')
within group(order by SEQ)
over(partition by "GROUP")
,'^([^,]+,){'||(row_number()over(partition by "GROUP" order by seq)-1)||'}'
)
AS hist1
,xmlcast(
xmlquery(
'string-join($X/A/B[position()<$Y]/text(),",")'
passing
xmlelement("A", xmlagg(xmlelement("B", activity)) over(partition by "GROUP")) as x
,row_number()over(partition by "GROUP" order by seq) as y
returning content
)
as varchar2(1000)
) hist2
FROM table_name t;
DBFIddle: https://dbfiddle.uk/?rdbms=oracle_21&fiddle=9b477a2089d3beac62579d2b7103377a
Full test case with output:
with table_name ("GROUP", seq, activity) AS (
SELECT 'A', 1, 'scan' FROM DUAL UNION ALL
SELECT 'A', 2, 'visit' FROM DUAL UNION ALL
SELECT 'A', 3, 'pay' FROM DUAL UNION ALL
SELECT 'B', 1, 'drink' FROM DUAL UNION ALL
SELECT 'B', 2, 'rest' FROM DUAL
)
SELECT--+ NO_XML_QUERY_REWRITE
t.*,
regexp_substr(
listagg(activity, ',')
within group(order by SEQ)
over(partition by "GROUP")
,'^([^,]+,){'||(row_number()over(partition by "GROUP" order by seq)-1)||'}'
)
AS hist1
,xmlcast(
xmlquery(
'string-join($X/A/B[position()<$Y]/text(),",")'
passing
xmlelement("A", xmlagg(xmlelement("B", activity)) over(partition by "GROUP")) as x
,row_number()over(partition by "GROUP" order by seq) as y
returning content
)
as varchar2(1000)
) hist2
FROM table_name t;
GROUP SEQ ACTIV HIST1 HIST2
------ ---------- ----- ------------------------------ ------------------------------
A 1 scan
A 2 visit scan, scan
A 3 pay scan,visit, scan,visit
B 1 drink
B 2 rest drink, drink
The need is to update only the top row of each group of a table from the data of other table.
I need to update table A with details from table B
Table A
---------
ID Name Date PCNO
1 abc 1/1/12 123
2 def 1/1/12 234
3 fgh 1/2/12 222
4 asd 1/2/12 234
TABLE B
-----------
ID Name Date PCNO
1 adsf 1/1/12 4343
2 sdf 1/2/12 9347
For each top record of table A grouped by "Date" and ordered by PCNO desc, I would like to update the values from table B.
Do i use rank for this purpose.?
You can use rank or dense-rank (or even row-number) to get identify the 'top' row, though you may need t consider what to do if ties are possible in your real data:
select a.id, a.name, a.date_col, a.pcno,
dense_rank() over (partition by date_col order by pcno desc) as rnk
from table_a a;
ID NAME DATE_COL PCNO RNK
---------- ---- ---------- ---------- ----------
2 def 2012-01-01 234 1
1 abc 2012-01-01 123 2
4 asd 2012-01-02 234 1
3 fgh 2012-01-02 222 2
And you can join to table B to get the new values for the top-ranked:
select a.id, a.name, a.date_col, a.pcno,
dense_rank() over (partition by a.date_col order by a.pcno desc) as rnk,
case when dense_rank() over (partition by a.date_col order by a.pcno desc) = 1
then b.name else a.name end as new_name,
case when dense_rank() over (partition by a.date_col order by a.pcno desc) = 1
then b.pcno else a.pcno end as new_pcno
from table_a a
join table_b b on b.date_col = a.date_col;
ID NAME DATE_COL PCNO RNK NEW_ NEW_PCNO
---------- ---- ---------- ---------- ---------- ---- ----------
2 def 2012-01-01 234 1 adsf 4343
1 abc 2012-01-01 123 2 abc 123
4 asd 2012-01-02 234 1 sdf 9347
3 fgh 2012-01-02 222 2 fgh 222
and you can then use that in a merge statement:
merge into table_a target
using (
select a.id, a.name, a.date_col, a.pcno,
dense_rank() over (partition by a.date_col order by a.pcno desc) as rnk,
case when dense_rank() over (partition by a.date_col order by a.pcno desc) = 1
then b.name else a.name end as new_name,
case when dense_rank() over (partition by a.date_col order by a.pcno desc) = 1
then b.pcno else a.pcno end as new_pcno
from table_a a
join table_b b on b.date_col = a.date_col
) source
on (source.id = target.id)
when matched then update
set target.name = source.new_name, target.pcno = source.new_pcno
where source.rnk = 1;
or maybe
merge into table_a target
using (
select a.id, a.name, a.date_col, a.pcno,
case when dense_rank() over (partition by a.date_col order by a.pcno desc) = 1
then b.name else a.name end as new_name,
case when dense_rank() over (partition by a.date_col order by a.pcno desc) = 1
then b.pcno else a.pcno end as new_pcno
from table_a a
join table_b b on b.date_col = a.date_col
) source
on (source.id = target.id)
when matched then update
set target.name = source.new_name, target.pcno = source.new_pcno
where target.name != source.new_name or target.pcno != source.new_pcno;
either of which reports 2 rows merged, and then:
select * from table_a;
ID NAME DATE_COL PCNO
---------- ---- ---------- ----------
1 abc 2012-01-01 123
2 adsf 2012-01-01 4343
3 fgh 2012-01-02 222
4 sdf 2012-01-02 9347
You may need to adjust it if there isn't always going to be a match for a date, though the inner join ought to take care of that.
db<>fiddle demo
i have below data.
table A
id
1
2
3
table B
id name data1 data2 datetime
1 cash 12345.00 12/12/2012 11:10:12
1 quantity 222.12 14/12/2012 11:10:12
1 date 20/12/2012 12/12/2012 11:10:12
1 date 19/12/2012 13/12/2012 11:10:12
1 date 13/12/2012 14/12/2012 11:10:12
1 quantity 330.10 17/12/2012 11:10:12
I want to retrieve data in one row like below:
tableA.id tableB.cash tableB.date tableB.quantity
1 12345.00 13/12/2012 330.10
I want to retrieve based on max(datetime).
The data model appears to be insane-- it makes no sense to join an ORDER_ID to a CUSTOMER_ID. It makes no sense to store dates in a VARCHAR2 column. It makes no sense to have no relationship between a CUSTOMER and an ORDER. It makes no sense to have two rows in the ORDER table with the same ORDER_ID. ORDER is also a reserved word so you cannot use that as a table name. My best guess is that you want something like
select *
from customer c
join (select order_id,
rank() over (partition by order_id
order by to_date( order_time, 'YYYYMMDD HH24:MI:SS' ) desc ) rnk
from order) o on (c.customer_id=o.order_id)
where o.rnk = 1
If that is not what you want, please (as I asked a few times in the comments) post the expected output.
These are the results I get with my query and your sample data (fixing the name of the ORDER table so that it is actually valid)
SQL> ed
Wrote file afiedt.buf
1 with orders as (
2 select 1 order_id, 'iphone' order_name, '20121201 12:20:23' order_time from dual union all
3 select 1, 'iphone', '20121201 12:22:23' from dual union all
4 select 2, 'nokia', '20110101 13:20:20' from dual ),
5 customer as (
6 select 1 customer_id, 'paul' customer_name from dual union all
7 select 2, 'stuart' from dual union all
8 select 3, 'mike' from dual
9 )
10 select *
11 from customer c
12 join (select order_id,
13 rank() over (partition by order_id
14 order by to_date( order_time, 'YYYYMMDD HH24:MI:SS' ) desc ) rnk
15 from orders) o on (c.customer_id=o.order_id)
16* where o.rnk = 1
SQL> /
CUSTOMER_ID CUSTOM ORDER_ID RNK
----------- ------ ---------- ----------
1 paul 1 1
2 stuart 2 1
Try something like
SELECT *
FROM CUSTOMER c
INNER JOIN ORDER o
ON (o.CUSTOMER_ID = c.CUSTOMER_ID)
WHERE TO_DATE(o.ORDER_TIME, 'YYYYMMDD HH24:MI:SS') =
(SELECT MAX(TO_DATE(o.ORDER_TIME, 'YYYYMMDD HH24:MI:SS')) FROM ORDER)
Share and enjoy.
I'm struggling with a subselect in oracle. I want to include the latest price from another table.
Here is my current attempt:
SELECT tab1.*
(select price from
old_prices
where part_no=tab1.article_no
order by valid_from desc) as old_price,
FROM articles tab1
order by article_no
The sub select returns several rows which I think is the problem. But I do not know how to limit the number of rows in Oracle.
SQL> create table articles (article_no,name)
2 as
3 select 1, 'PEN' from dual union all
4 select 2, 'PAPER' from dual
5 /
Table created.
SQL> create table old_prices (part_no,valid_from,price)
2 as
3 select 1, date '2008-01-01', 10 from dual union all
4 select 1, date '2009-01-01', 11 from dual union all
5 select 1, date '2010-01-01', 12 from dual union all
6 select 1, date '2011-01-01', 13 from dual union all
7 select 2, date '2010-01-01', 89.95 from dual union all
8 select 2, date '2011-01-01', 94.95 from dual union all
9 select 2, date '2012-01-01', 99.95 from dual
10 /
Table created.
SQL> select a.article_no
2 , max(a.name) keep (dense_rank last order by p.valid_from) name
3 , max(p.price) keep (dense_rank last order by p.valid_from) price
4 from articles a
5 , old_prices p
6 where a.article_no = p.part_no
7 group by a.article_no
8 /
ARTICLE_NO NAME PRICE
---------- ----- ----------
1 PEN 13
2 PAPER 99.95
2 rows selected.
Regards,
Rob.
If it's the latest price you're after:
SELECT tab1.*, p.price old_price
FROM articles tab1
, old_prices p
where p.part_no = tab1.article_no
and valid_from = (
select MAX(valid_from)
from old_prices p2
where p2.part_no = p.part_no
)
order by article_no
I want to include the lastest price
I presume you mean latest.
OK, well that's a bit of a problem to start with, there are several ways of doing this:
SELECT o.price
FROM old_prices o
WHERE o.part_no=&part_no
AND o.ondate=(SELECT MAX(o2.ondate)
FROM old_prices o2
WHERE o2.part_no=&part_no);
Seems the most obvious choice but its rather innefficient.
You could try....
SELECT ilv.price
FROM (SELECT o.price
FROM old_price o
WHERE o.part_no=&part_no
ORDER BY ondate DESC) ilv
WHERE rownum=1;
Or....
SELECT TO_NUMBER(
SUBSTR(
MAX(TO_CHAR(o.ondate, 'YYYYMMDDHH24MISS') || price)
, 15)
) as latest_price
FROM old_price o
WHERE o.part_no=&part_no;
To limit rows use ROWNUM < 10. This is a pseudocolumn returning the row number of each line of your resultset.
EDIT:
You need to add another subselect query (hope this is the right place for your need)
SELECT tab1.*
select (
(select price from old_prices
where part_no=tab1.article_no order by valid_from desc
) as x
where rownum = 1
) as old_price
FROM articles tab1
order by article_no
SELECT tab1.*
(select
price
from (
SELECT
part_no
, price
, row_number () over (partition by part_no order by valid_from desc ) rn
FROM
old_prices
) P
where rn =1
and tab1.article_no = P.part_no
) as old_price
FROM articles tab1
order by article_no
more efficient would be
SELECT
tab1.*
, P.price
FROM
articles tab1
, ( SELECT
part_no
, price
, row_number () over (partition by part_no order by valid_from desc ) rn
FROM
old_prices
) P
WHERE
P.part_no(+) = tab1.article_no
P.rn(+) = 1
;
with old_prices as(
select level * 15 price ,
mod (level ,5) part_no , --this is just to create a grouping type partno
(sysdate - level ) valid_from
from dual
connect by level < 100)
,
articles as(
select level ,
mod(level , 5 ) article_no ,
(sysdate + level) someOtherDateField
From dual
connect by level < 5
)
SELECT tab1.* ,
old_price.*
from articles tab1
left join
(
select price,
part_no ,
valid_from ,
rank() over(partition by part_no order by valid_from desc) rk
from old_prices
) old_price
on tab1.article_no = old_price.part_no
and old_price.rk = 1
order by article_no ;
Here's another way!
LEVEL ARTICLE_NO SOMEOTHERDATEFIELD PRICE PART_NO VALID_FROM RK
---------------------- ---------------------- ------------------------- ---------------------- ---------------------- ------------------------- ----------------------
1 1 25/05/11 07:30:54 15 1 23/05/11 07:30:54 1
2 2 26/05/11 07:30:54 30 2 22/05/11 07:30:54 1
3 3 27/05/11 07:30:54 45 3 21/05/11 07:30:54 1
4 4 28/05/11 07:30:54 60 4 20/05/11 07:30:54 1