Oracle: how to query some columns as rows - sql

I use Oracle 11 XE and have the following table:
CREATE TABLE tst
(val_a NUMBER,
val_b NUMBER,
val_c NUMBER,
val_sum NUMBER,
id NUMBER,
dt DATE)
Some sample data:
INSERT INTO tst
VALUES(12,15,17,44,1,TO_DATE('2018-03-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO tst
VALUES(14,16,11,41,1,TO_DATE('2018-03-03 00:00:00', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO tst
VALUES(6,7,8,21,2,TO_DATE('2018-03-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'));
I need to specify two dates and get the following result (NEW_VAL are values SUM, A, B and C for ID=1 and DT=2018-03-03, OLD_VAL are values for ID=1 and DT=2018-03-01):
ID X NEW_VAL OLD_VAL
--- --- --------- --------
1 SUM 41 44
A 14 12
B 16 15
C 11 17
Below is the query I've implemented:
select id, x, new_val, old_val from(
select tst_new.id id0, 1, tst_new.id, 'SUM' x, tst_new.val_sum new_val, tst_old.val_sum old_val from tst tst_new,
(select * from tst where dt=to_date('01.03.2018', 'dd.mm.yyyy')) tst_old
where tst_new.dt=to_date('03.03.2018', 'dd.mm.yyyy') and tst_new.id = tst_old.id and tst_new.id = 1
UNION ALL
select tst_new.id, 2, null, 'A', tst_new.val_a, tst_old.val_a from tst tst_new,
(select * from tst where dt=to_date('01.03.2018', 'dd.mm.yyyy')) tst_old
where tst_new.dt=to_date('03.03.2018', 'dd.mm.yyyy') and tst_new.id = tst_old.id and tst_new.id = 1
UNION ALL
select tst_new.id, 3, null, 'B', tst_new.val_b, tst_old.val_b from tst tst_new,
(select * from tst where dt=to_date('01.03.2018', 'dd.mm.yyyy')) tst_old
where tst_new.dt=to_date('03.03.2018', 'dd.mm.yyyy') and tst_new.id = tst_old.id and tst_new.id = 1
UNION ALL
select tst_new.id, 4, null, 'C', tst_new.val_c, tst_old.val_c from tst tst_new,
(select * from tst where dt=to_date('01.03.2018', 'dd.mm.yyyy')) tst_old
where tst_new.dt=to_date('03.03.2018', 'dd.mm.yyyy') and tst_new.id = tst_old.id and tst_new.id = 1
order by 1, 2
)
It does provide required result but looks terrible. Is there any way to get that result easier?
Also, if there is no data for the particular date, result should contain ID, X and empty cells. My query just returns nothing if there is no data for any of two dates. How to make query return empty cells if there are no values for that date?
UPDATE: I've seen examples with pivot, but in my case not only columns as rows is required, but also querying data from the same table for different dates. Also, it's not clear how to get empty cells if there is no date for the particular date.

Inner subquery is result of unpivot, outer - pivoting back:
select *
from (select to_char(dt, 'dd.mm.yyyy') dt, vals, dt_vals from tst
unpivot (dt_vals for vals in (val_a, val_b, val_c, val_sum)))
pivot (sum(dt_vals) for dt in ('01.03.2018', '03.03.2018'))
order by 1
VALS '01.03.2018' '03.03.2018'
------- ------------ ------------
VAL_A 18 14
VAL_B 22 16
VAL_C 25 11
VAL_SUM 65 41
Next, you need to specify the rule how to filter these values:
NEW_VAL are values SUM, A B and C for ID = 1 and DT = 2018-03-03, OLD_VAL are values for ID = 1 and DT = 2018-03-01
I just hardcoded it "as is":
select *
from (select to_char(dt, 'dd.mm.yyyy') dt, vals, dt_vals from tst
unpivot (dt_vals for vals in (val_a, val_b, val_c, val_sum))
where id = 1
)
pivot (sum(dt_vals) for dt in ('01.03.2018', '03.03.2018'))
order by 1
VALS '01.03.2018' '03.03.2018'
------- ------------ ------------
VAL_A 12 14
VAL_B 15 16
VAL_C 17 11
VAL_SUM 44 41

Related

Oracle get row where column value changed

Say I have a table, something like
ID CCTR DATE
----- ------ ----------
1 2C 8/1/2018
2 2C 7/2/2018
3 2C 5/4/2017
4 2B 3/2/2017
5 2B 1/1/2017
6 UC 11/23/2016
There are other fields, but I made it simple. So I create a query where i have the date in descending order. I was to return the row where there was a change in CCTR. So in this case it would return ID 4. Basically i want to find the previous value of CCTR before it changed, in this case from 2B to 2C.
How do I do this? Ive tried to google it, but can't seem to find the right method.
You can use the LAG() window function to peek at the previous row and compare it. If your data is:
create table t2 (
id number(6),
cctr varchar2(10),
date1 date
);
insert into t2 (id, cctr, date1) values (1, '2C', date '2018-08-01');
insert into t2 (id, cctr, date1) values (2, '2C', date '2018-07-02');
insert into t2 (id, cctr, date1) values (3, '2C', date '2017-05-04');
insert into t2 (id, cctr, date1) values (4, '2B', date '2017-03-02');
insert into t2 (id, cctr, date1) values (5, '2B', date '2017-01-01');
insert into t2 (id, cctr, date1) values (6, 'UC', date '2016-11-23');
Then the query would be:
select * from t2 where date1 = (
select max(date1)
from (
select
id, date1, cctr, lag(cctr) over(order by date1 desc) as prev
from t2
) x
where prev is not null and cctr <> prev
);
Result:
ID CCTR DATE1
------- ---------- -------------------
4 2B 2017-03-02 00:00:00
You may use first_value analytic function to detect the changes in CCTR column :
select fv as value, cctr
from
(
with t(ID,CCTR) as
(
select 1,'2C' from dual union all
select 2,'2C' from dual union all
select 3,'2C' from dual union all
select 4,'2B' from dual union all
select 5,'2B' from dual union all
select 6,'UC' from dual
)
select id, cctr, first_value(id) over (partition by cctr order by id ) fv
from t
order by id
)
where id = fv;
VALUE CCTR
----- ----
1 2C
4 2B
6 UC
Rextester Demo

compare one row with multiple rows

Ex: I have other main table which is having below data
Create table dbo.Main_Table
(
ID INT,
SDate Date
)
Insert Into dbo.Main_Table Values (1,'01/02/2018')
Insert Into dbo.Main_Table Values (2,'01/30/2018')
Create table dbo.test
(
ID INT,
SDate Date
)
Insert Into dbo.test Values (1,'01/01/2018')
Insert Into dbo.test Values (1,'01/02/2018')
Insert Into dbo.test Values (1,'01/30/2018')
Insert Into dbo.test Values (2,'10/01/2018')
Insert Into dbo.test Values (2,'01/02/2018')
Insert Into dbo.test Values (2,'01/30/2018')
I would like to compare data in main table data with test table. We have to join based on ID and if date match found then "yes" else "No". We have to compare one row with multiple rows.
Please let me know if any questions , thanks for you;re help
Something like this?
SQL> with main_table (id, sdate) as
2 (select 1, date '2018-01-02' from dual union all
3 select 2, date '2018-01-30' from dual union all
4 select 3, date '2018-07-25' from dual
5 ),
6 test_table (id, sdate) as
7 (select 1, date '2018-01-02' from dual union all
8 select 2, date '2018-08-30' from dual
9 )
10 select m.id,
11 m.sdate,
12 case when m.sdate = t.sdate then 'yes' else 'no' end status
13 from main_table m left join test_table t on t.id = m.id
14 order by m.id;
ID SDATE STATUS
---------- -------- ------
1 02.01.18 yes
2 30.01.18 no
3 25.07.18 no
SQL>
[EDIT, after reading the comment - if you find a match, you don't need that ID at all]
Here you are:
SQL> with test (id, sdate) as
2 (select 1, date '2018-01-01' from dual union all
3 select 1, date '2018-01-02' from dual union all
4 select 1, date '2018-01-30' from dual union all
5 --
6 select 2, date '2018-10-01' from dual union all
7 select 2, date '2018-01-02' from dual union all
8 select 2, date '2018-01-30' from dual
9 )
10 select id, sdate
11 from test t
12 where not exists (select null
13 from test t1
14 where t1.id = t.id
15 and t1.sdate = to_date('&par_sdate', 'yyyy-mm-dd'));
Enter value for par_sdate: 2018-01-01
ID SDATE
---------- ----------
2 2018-01-30
2 2018-01-02
2 2018-10-01
SQL> /
Enter value for par_sdate: 2018-01-02
no rows selected
SQL>

Oracle deduping

I have the following data:
ID ID2 DATE
A AA 2017-01-01
A BB 2017-01-01
A CC 2017-01-01
B DD 2018-01-01
B DD 2018-01-01
C EE 2018-02-01
I would like to dedupe by ID keeping only one ID2 and one date per row. I am trying this sql command, but it doesn't dedupe:
SELECT DISTINCT A.ID, A.ID2, A.DATE
FROM TABLE A
GROUP BY A.ID;
Any help will be appreciated.
As it seems that you don't care which ID2 and DATUM (as you can't name a column "DATE"; it is reserved for the datatype) you'd want to keep, a simple option is
SQL> with test (id, id2, datum) as
2 (select 'a', 'aa', date '2017-01-01' from dual union all
3 select 'a', 'bb', date '2017-01-01' from dual union all
4 select 'a', 'cc', date '2017-01-01' from dual union all
5 select 'b', 'dd', date '2018-01-01' from dual union all
6 select 'b', 'dd', date '2018-01-01' from dual union all
7 select 'c', 'ee', date '2018-02-01' from dual
8 )
9 select id, min(id2) id2, min(datum) datum
10 from test
11 group by id;
ID ID2 DATUM
--- --- ----------
a aa 2017-01-01
b dd 2018-01-01
c ee 2018-02-01
SQL>
You can use row_number():
select id, id2, date
from (select t.*, row_number() over (partition by id order by id) as seqnum
from t
) t
where seqnum = 1;
You can change the order by if there is a particular date that you want, such as the minimum or maximum date.

oracle how to get column value from other table

I have the following tables with some columns:
foo
reportdate baar
01.04.16 1
baar_value
b_value from_date to_date
1 01.01.16 01.01.17
1 01.01.15 01.01.16
The logic of what I want is a little bit complex but I am trying to find a way to reference the reportdate from table foo in baar_value-where clause in the following query:
SELECT *
FROM foo
WHERE baar IS NOT NULL
AND NOT EXISTS
(SELECT *
FROM baar_value
WHERE b_value = baar
AND reportdate
BETWEEN from_date AND to_date
)
how check if foo.reportdate is between baar_value.from_date and baar_value_to_date?
From your description maybe you simply need the following:
SELECT *
FROM foo
WHERE baar IS NOT NULL
AND NOT EXISTS
(SELECT *
FROM baar_value
WHERE b_value = baar
AND foo.reportdate BETWEEN from_date AND to_date
)
Test:
SQL> create table foo(reportdate, baar) as
2 select date '2016-04-01', 1 from dual;
Table created.
SQL> create table baar_value( b_value, from_date, to_date) as
2 select 1, date '2016-01-01', date '2017-01-01' from dual union all
3 select 1, date '2015-01-01', date '2016-01-01' from dual;
Table created.
SQL> SELECT *
2 FROM foo
3 WHERE baar IS NOT NULL
4 AND NOT EXISTS
5 (SELECT *
6 FROM baar_value
7 WHERE b_value = baar
8 AND foo.reportdate BETWEEN from_date AND to_date
9 );
no rows selected

Oracle SQL (Toad): Expand table

Suppose I have an SQL (Oracle Toad) table named "test", which has the following fields and entries (dates are in dd/mm/yyyy format):
id ref_date value
---------------------
1 01/01/2014 20
1 01/02/2014 25
1 01/06/2014 3
1 01/09/2014 6
2 01/04/2015 7
2 01/08/2015 43
2 01/09/2015 85
2 01/12/2015 4
I know from how the table has been created that, since there are value entries for id = 1 for February 2014 and June 2014, the values for March through May 2014 must be 0. The same applies to July and August 2014 for id = 1, and for May through July 2015 and October through November 2015 for id = 2.
Now, if I want to calculate, say, the median of the value column for a given id, I will not arrive at the correct result using the table as it stands - as I'm missing 5 zero entries for each id.
I would therefore like to create/use the following (potentially just temporary table)...
id ref_date value
---------------------
1 01/01/2014 20
1 01/02/2014 25
1 01/03/2014 0
1 01/04/2014 0
1 01/05/2014 0
1 01/06/2014 3
1 01/07/2014 0
1 01/08/2014 0
1 01/09/2014 6
2 01/04/2015 7
2 01/05/2015 0
2 01/06/2015 0
2 01/07/2015 0
2 01/08/2015 43
2 01/09/2015 85
2 01/10/2015 0
2 01/11/2015 0
2 01/12/2015 4
...on which I could then compute the median by id:
select id, median(value) as med_value from test group by id
How do I do this? Or would there be an alternative way?
Many thanks,
Mr Clueless
In this solution, I build a table with all the "needed dates" and value of 0 for all of them. Then, instead of a join, I do a union all, group by id and ref_date and ADD the values in each group. If the date had a row with a value in the original table, then that's the resulting value; and if it didn't, the value will be 0. This avoids a join. In almost all cases a union all + aggregate will be faster (sometimes much faster) than a join.
I added more input data for more thorough testing. In your original question, you have two id's, and for both of them you have four positive values. You are missing five values in each case, so there will be five zeros (0) which means the median is 0 in both cases. For id=3 (which I added) I have three positive values and three zeros; the median is half of the smallest positive number. For id=4 I have just one value, which then should be the median as well.
The solution includes, in particular, an answer to your specific question - how to create the temporary table (which most likely doesn't need to be a temporary table at all, but an inline view). With factored subqueries (in the WITH clause), the optimizer decides if to treat them as temporary tables or inline views; you can see what the optimizer decided if you look at the Explain Plan.
with
inputs ( id, ref_date, value ) as (
select 1, to_date('01/01/2014', 'dd/mm/yyyy'), 20 from dual union all
select 1, to_date('01/02/2014', 'dd/mm/yyyy'), 25 from dual union all
select 1, to_date('01/06/2014', 'dd/mm/yyyy'), 3 from dual union all
select 1, to_date('01/09/2014', 'dd/mm/yyyy'), 6 from dual union all
select 2, to_date('01/04/2015', 'dd/mm/yyyy'), 7 from dual union all
select 2, to_date('01/08/2015', 'dd/mm/yyyy'), 43 from dual union all
select 2, to_date('01/09/2015', 'dd/mm/yyyy'), 85 from dual union all
select 2, to_date('01/12/2015', 'dd/mm/yyyy'), 4 from dual union all
select 3, to_date('01/01/2016', 'dd/mm/yyyy'), 12 from dual union all
select 3, to_date('01/03/2016', 'dd/mm/yyyy'), 23 from dual union all
select 3, to_date('01/06/2016', 'dd/mm/yyyy'), 2 from dual union all
select 4, to_date('01/11/2014', 'dd/mm/yyyy'), 9 from dual
),
-- the "inputs" table constructed above is for testing only,
-- it is not part of the solution.
ranges ( id, min_date, max_date ) as (
select id, min(ref_date), max(ref_date)
from inputs
group by id
),
prep ( id, ref_date, value ) as (
select id, add_months(min_date, level - 1), 0
from ranges
connect by level <= 1 + months_between( max_date, min_date )
and prior id = id
and prior sys_guid() is not null
),
v ( id, ref_date, value ) as (
select id, ref_date, sum(value)
from ( select id, ref_date, value from prep union all
select id, ref_date, value from inputs
)
group by id, ref_date
)
select id, median(value) as median_value
from v
group by id
order by id -- ORDER BY is optional
;
ID MEDIAN_VALUE
-- ------------
1 0
2 0
3 1
4 9
If ref_date is date and is second
with int1 as (select id
, max(ref_date) as max_date
, min(ref_date) as min_date from test group by id )
, s(n) as (select level -1 from dual connect by level <= (select max(months_between(max_date, min_date)) from int1 ) )
select i.id
, add_months(i.min_date,s.n) as ref_date
, nvl(value,0) as value
from int1 i
join s on add_months(i.min_date,s.n) <= i.max_date
LEFT join test t on t.id = i.id and add_months(i.min_date,s.n) = t.ref_date
And with median
with int1 as (select id
, max(ref_date) as max_date
, min(ref_date) as min_date from test group by id )
, s(n) as (select level -1 from dual connect by level <= (select max(months_between(max_date, min_date)) from int1 ) )
select i.id
, MEDIAN(nvl(value,0)) as value
from int1 i
join s on add_months(i.min_date,s.n) <= i.max_date
LEFT join test t on t.id = i.id and add_months(i.min_date,s.n) = t.ref_date
group by i.id