How to use subqueries in Oracle? - oracle

I have the following table:
table1
-------------------------
date | ID | name
-------------------------
13-jul-15 | 1 | abc
13-jul-15 | 2 | abc
14-jul-15 | 1 | def
13-jul-15 | 3 | abc
15-jul-15 | 3 | def
...
What I want to do is match the ID and represent as below:
date1 | name | date2 | name | ID
------------------------------------------
13-jul-15 | abc | 14-jul-15 | def | 1
13-jul-15 | abc | | | 2
13-jul-15 | abc | 15-jul-15 | def | 3
...
I have used the following code, but not getting the result.
CREATE PROCEDURE get_details ( oresults1 OUT SYS_REFCURSOR ) AS
BEGIN
SELECT *
FROM ((SELECT date, ID FROM table1 WHERE name= "abc") T1
UNION ALL
(SELECT date, ID FROM table1 WHERE name= "def") T2
)
WHERE T1.ID= T2.ID
ORDER BY ID;
END;
What have I done wrong?

Here are a couple of alternatives:
with table1 as (select to_date('13/07/2015', 'dd/mm/yyyy') dt, 1 id, 'abc' name from dual union all
select to_date('13/07/2015', 'dd/mm/yyyy') dt, 2 id, 'abc' name from dual union all
select to_date('14/07/2015', 'dd/mm/yyyy') dt, 1 id, 'def' name from dual union all
select to_date('13/07/2015', 'dd/mm/yyyy') dt, 3 id, 'abc' name from dual union all
select to_date('15/07/2015', 'dd/mm/yyyy') dt, 3 id, 'def' name from dual)
-- end of mimicking your table1. See below for the query
select t1.dt date1,
t1.name name1,
t2.dt date2,
t2.name name2,
t1.id
from table1 t1
left outer join table1 t2 on (t1.id = t2.id and t1.name = 'abc' and t2.name = 'def')
where t1.name = 'abc'
order by t1.id;
DATE1 NAME1 DATE2 NAME2 ID
---------- ----- ---------- ----- ----------
13/07/2015 abc 14/07/2015 def 1
13/07/2015 abc 2
13/07/2015 abc 15/07/2015 def 3
with table1 as (select to_date('13/07/2015', 'dd/mm/yyyy') dt, 1 id, 'abc' name from dual union all
select to_date('13/07/2015', 'dd/mm/yyyy') dt, 2 id, 'abc' name from dual union all
select to_date('14/07/2015', 'dd/mm/yyyy') dt, 1 id, 'def' name from dual union all
select to_date('13/07/2015', 'dd/mm/yyyy') dt, 3 id, 'abc' name from dual union all
select to_date('15/07/2015', 'dd/mm/yyyy') dt, 3 id, 'def' name from dual)
-- end of mimicking your table1. See below for the query
select t1.dt date1,
t1.name name1,
t2.dt date2,
t2.name name2,
t1.id
from (select id, dt, name from table1 where name = 'abc') t1
left outer join (select id, dt, name from table1 where name = 'def') t2 on (t1.id = t2.id)
where t1.name = 'abc'
order by t1.id;
DATE1 NAME1 DATE2 NAME2 ID
---------- ----- ---------- ----- ----------
13/07/2015 abc 14/07/2015 def 1
13/07/2015 abc 2
13/07/2015 abc 15/07/2015 def 3

You can also use pivot function available in Oracle
WITH table_(date#, id#, name#) AS
(SELECT to_date('13-jul-15', 'dd-mon-yy'), 1, 'abc' FROM dual UNION all
SELECT to_date('13-jul-15', 'dd-mon-yy'), 2, 'abc' FROM dual UNION all
SELECT to_date('14-jul-15', 'dd-mon-yy'), 1, 'def' FROM dual UNION all
SELECT to_date('13-jul-15', 'dd-mon-yy'), 3, 'abc' FROM dual UNION all
SELECT to_date('15-jul-15', 'dd-mon-yy'), 3, 'def' FROM dual)
--------
-- End of data preparation
--------
SELECT *
FROM table_
PIVOT (MIN(date#) AS date#, MIN(name#) AS NAME# FOR name# IN ('abc' AS ABC, 'def' AS DEF));
Output
| ID# | ABC_DATE# | ABC_NAME# | DEF_DATE# | DEF_NAME# |
|-----|------------------------|-----------|------------------------|-----------|
| 1 | July, 13 2015 00:00:00 | abc | July, 14 2015 00:00:00 | def |
| 2 | July, 13 2015 00:00:00 | abc | | |
| 3 | July, 13 2015 00:00:00 | abc | July, 15 2015 00:00:00 | def |

Related

ORACLE, ignore null to sum

I want to add two columns(number type) of a row, together but when one of those columns is null then the result is null. I handlled it by NVL function(NVL(col1,0) + NVL(col2,0)). But i want to return null when both are null(instead of 0).
how can i handle it?
Is there function in ORACLE that ignore null to sum two columns of a row?
| col1 | col2 | |
|:---- |:-----:|:-----------------------------------:|
| 1 | 2 | --> result have to be : 3 |
| 1 | null | --> result have to be : 1 |
| null | null | --> result have to be : null |
coalesce(col1+col2,col1,col2) would be easier:
with t(col1,col2) as (
select 0,1 from dual union all
select 2,null from dual union all
select null,3 from dual union all
select null,null from dual
)
select
col1,col2,
coalesce(col1+col2,col1,col2) sum_cols
from t;
Results:
COL1 COL2 SUM_COLS
---------- ---------- ----------
0 1 1
2 null 2
null 3 3
null null null
4 rows selected.
or subquery with sum if you have more columns to sum:
(select sum(column_value) from table(sys.odcinumberlist(col1,col2,...,colN)))
Example:
with t(col1,col2) as (
select 0,1 from dual union all
select 2,null from dual union all
select null,3 from dual union all
select null,null from dual
)
select
col1,col2,
coalesce(col1+col2,col1,col2) sum_cols,
(select sum(column_value) from table(sys.odcinumberlist(col1,col2))) sum_cols2
from t;
DBFiddle:
https://dbfiddle.uk/?rdbms=oracle_18&fiddle=eda8de9746f1d0def3c290420adbb705
A CASE expression might be the easiest way to go here:
SELECT CASE WHEN col1 IS NULL AND col2 IS NULL
THEN NULL
ELSE NVL(col1, 0) + NVL(col2, 0) END AS output
FROM yourTable;

Get month wise yearly report in oracle

all I have a employee table with the following fields employee name, wages date, wages I want to sum records month-wise.here is table data. I m using oracle database 11g
and here is the output I want.
You can use ROLLUP in your GROUP BY
WITH t(NAME, dt, w) AS (
SELECT 'adam', DATE '2020-01-01', 200 FROM dual UNION ALL
SELECT 'adam', DATE '2020-02-01', 200 FROM dual UNION ALL
SELECT 'adam', DATE '2020-03-01', 200 FROM dual UNION ALL
SELECT 'jhone', DATE '2020-01-01', 100 FROM dual UNION ALL
SELECT 'jhone', DATE '2020-02-01', 200 FROM dual UNION ALL
SELECT 'jhone', DATE '2020-03-01', 151 FROM dual
)
SELECT NAME, NVL(TO_CHAR(dt, 'fmMon'), 'total') AS mon, SUM(w) AS sum_w
FROM t
GROUP BY NAME, ROLLUP(TO_CHAR(dt, 'fmMon'));
+-----------------+
|NAME |MON |SUM_W|
+-----------------+
|adam |Feb |200 |
|adam |Jan |200 |
|adam |Mar |200 |
|adam |total|600 |
|jhone|Feb |200 |
|jhone|Jan |100 |
|jhone|Mar |151 |
|jhone|total|451 |
+-----------------+
If you need to transpose your result, you can PIVOT it:
WITH t(NAME, dt, w) AS (
SELECT 'adam', DATE '2020-01-01', 200 FROM dual UNION ALL
SELECT 'adam', DATE '2020-02-01', 200 FROM dual UNION ALL
SELECT 'adam', DATE '2020-03-01', 200 FROM dual UNION ALL
SELECT 'jhone', DATE '2020-01-01', 100 FROM dual UNION ALL
SELECT 'jhone', DATE '2020-02-01', 200 FROM dual UNION ALL
SELECT 'jhone', DATE '2020-03-01', 151 FROM dual
)
SELECT *
FROM (
SELECT NAME, NVL(TO_CHAR(dt, 'fmMon'), 'total') AS mon, SUM(w) AS sum_w
FROM t
GROUP BY NAME, ROLLUP(TO_CHAR(dt, 'fmMon'))
)
PIVOT (
SUM(sum_w)
FOR mon IN ('Jan','Feb','Mar','total')
);
+-------------------------------+
|NAME |'Jan'|'Feb'|'Mar'|'total'|
+-------------------------------+
|adam |200 |200 |200 |600 |
|jhone|100 |200 |151 |451 |
+-------------------------------+
You can use conditional aggregation as follows:
Select name,
Sum(case when to_char(date,'mon') = 'jan' then wages end) as jan,
Sum(case when to_char(date,'mon') = 'feb' then wages end) as feb,
...
Sum(wages) as total
From yourTable
Group by name;
You need to use the where condition to only consider one year data.

Oracle - how to update a unique row based on MAX effective date which is part of the unique index

Oracle - Say you have a table that has a unique key on name, ssn and effective date. The effective date makes it unique. What is the best way to update a current indicator to show inactive for the rows with dates less than the max effective date? I can't really wrap my head around it since there are multiple rows with the same name and ssn combinations. I haven't been able to find this scenario on here for Oracle and I'm having developer's block. Thanks.
"All name/ssn having a max effective date earlier than this time yesterday:"
SELECT name, ssn
FROM t
GROUP BY name, ssn
HAVING MAX(eff_date) < SYSDATE - 1
Oracle supports multi column in, so
UPDATE t
SET current_indicator = 'inactive'
WHERE (name,ssn,eff_date) IN (
SELECT name, ssn, max(eff_date)
FROM t
GROUP BY name, ssn
HAVING MAX(eff_date) < SYSDATE - 1
)
Use a MERGE statement using an analytic function to identify the rows to update and then merge on the ROWID pseudo-column so that Oracle can efficiently identify the rows to update (without having to perform an expensive self-join by comparing the values):
MERGE INTO table_name dst
USING (
SELECT rid,
max_eff_date
FROM (
SELECT ROWID AS rid,
effective_date,
status,
MAX( effective_date ) OVER ( PARTITION BY name, ssn ) AS max_eff_date
FROM table_name
)
WHERE ( effective_date < max_eff_date AND status <> 'inactive' )
OR ( effective_date = max_eff_date AND status <> 'active' )
) src
ON ( dst.ROWID = src.rid )
WHEN MATCHED THEN
UPDATE
SET status = CASE
WHEN src.max_eff_date = dst.effective_date
THEN 'active'
ELSE 'inactive'
END;
So, for some sample data:
CREATE TABLE table_name ( name, ssn, effective_date, status ) AS
SELECT 'aaa', 1, DATE '2020-01-01', 'inactive' FROM DUAL UNION ALL
SELECT 'aaa', 1, DATE '2020-01-02', 'inactive' FROM DUAL UNION ALL
SELECT 'aaa', 1, DATE '2020-01-03', 'inactive' FROM DUAL UNION ALL
SELECT 'bbb', 2, DATE '2020-01-01', 'active' FROM DUAL UNION ALL
SELECT 'bbb', 2, DATE '2020-01-02', 'inactive' FROM DUAL UNION ALL
SELECT 'bbb', 3, DATE '2020-01-01', 'inactive' FROM DUAL UNION ALL
SELECT 'bbb', 3, DATE '2020-01-03', 'active' FROM DUAL;
The query only updates the 3 rows that need changing and:
SELECT *
FROM table_name;
Outputs:
NAME | SSN | EFFECTIVE_DATE | STATUS
:--- | --: | :------------- | :-------
aaa | 1 | 01-JAN-20 | inactive
aaa | 1 | 02-JAN-20 | inactive
aaa | 1 | 03-JAN-20 | active
bbb | 2 | 01-JAN-20 | inactive
bbb | 2 | 02-JAN-20 | active
bbb | 3 | 01-JAN-20 | inactive
bbb | 3 | 03-JAN-20 | active
db<>fiddle here

Oracle: Combine two group by queries (which use aggregate function count()) by union or so to get a consolidated result

I have two tables. TABLE_A and TABLE_B.
Both tables maintain columns to save CREATION_USER. But this column has different name in respective tables.
My motive is to get a count of records each user has created in both tables.
That is, combining result of these two queries with few conditions. The user name should not get repeated and for user names who have created records in both tables, the count should be their sum.
SELECT A.CREATION_USER_A AS "USER",
COUNT(*)
FROM TABLE_A A
GROUP BY A.CREATION_USER_A;
SELECT B.CREATION_USER_B AS "USER",
COUNT(*)
FROM TABLE_B B
GROUP BY B.CREATION_USER_B;
For e.g.,
USER_A has created 2 records in TABLE_A,
USER_B has created 3 records in TABLE_B and
USER_C has created 4 records in TABLE_A and 3 records in TABLE_B.
So the output should look like this:
| USER | COUNT |
| USER_A | 2 |
| USER_B | 3 |
| USER_C | 7 |
I have written a query which does this but it performs really bad.
SELECT A.CREATION_USER_A AS "USER",
(COUNT(A.CREATION_USER_A)+(SELECT COUNT(CREATION_USER_B) FROM TABLE_B WHERE CREATION_USER_B = A.CREATION_USER_A)) AS "COUNT"
FROM TABLE_A A
GROUP BY A.CREATION_USER_A
UNION
SELECT B.CREATION_USER_B,
COUNT(B.CREATION_USER_B)
FROM TABLE_B B
WHERE B.CREATION_USER_B NOT IN (SELECT CREATION_USER_A FROM TABLE_A)
GROUP BY B.CREATION_USER_B;
Please suggest a way to get this done.
You can simply build a set given by the union (keeping duplicates) of all the records in your tables, and then count the records grouping by creation user:
Bulding some sample data:
create table table_a(id, creation_user_a) as (
select 1, 'USER_A' from dual union all
select 1, 'USER_A' from dual union all
select 1, 'USER_C' from dual union all
select 1, 'USER_C' from dual union all
select 1, 'USER_C' from dual union all
select 1, 'USER_C' from dual
);
create table table_b(id, creation_user_b) as (
select 1, 'USER_B' from dual union all
select 1, 'USER_B' from dual union all
select 1, 'USER_B' from dual union all
select 1, 'USER_C' from dual union all
select 1, 'USER_C' from dual union all
select 1, 'USER_C' from dual
)
The query:
select count(1), creation_user
from ( /* the union of all the records from table_a and table_b */
select creation_user_a as creation_user from table_a
union all /* UNION ALL keeps duplicates */
select creation_user_B from table_b
)
group by creation_user
order by creation_user
The result:
2 USER_A
3 USER_B
7 USER_C
The explain plan:
---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 12 | 96 | 8 (25)| 00:00:01 |
| 1 | SORT ORDER BY | | 12 | 96 | 8 (25)| 00:00:01 |
| 2 | HASH GROUP BY | | 12 | 96 | 8 (25)| 00:00:01 |
| 3 | VIEW | | 12 | 96 | 6 (0)| 00:00:01 |
| 4 | UNION-ALL | | | | | |
| 5 | TABLE ACCESS FULL| TABLE_A | 6 | 48 | 3 (0)| 00:00:01 |
| 6 | TABLE ACCESS FULL| TABLE_B | 6 | 48 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------------
An alternative (but more complicated, and possibly slower - you'd need to test both to check) solution to Aleksej's answer is to use a full outer join to join both grouped by queries, like so:
WITH table_a AS (SELECT 'USER_A' creation_user_a, 10 val FROM dual UNION ALL
SELECT 'USER_A' creation_user_a, 20 val FROM dual UNION ALL
SELECT 'USER_C' creation_user_a, 30 val FROM dual UNION ALL
SELECT 'USER_C' creation_user_a, 40 val FROM dual UNION ALL
SELECT 'USER_C' creation_user_a, 50 val FROM dual UNION ALL
SELECT 'USER_C' creation_user_a, 60 val FROM dual),
table_b AS (SELECT 'USER_B' creation_user_b, 10 val FROM dual UNION ALL
SELECT 'USER_B' creation_user_b, 20 val FROM dual UNION ALL
SELECT 'USER_B' creation_user_b, 30 val FROM dual UNION ALL
SELECT 'USER_C' creation_user_b, 40 val FROM dual UNION ALL
SELECT 'USER_C' creation_user_b, 50 val FROM dual UNION ALL
SELECT 'USER_C' creation_user_b, 60 val FROM dual)
-- end of mimicking your tables with data in them. See the SQL below:
SELECT COALESCE(a.creation_user_a, b.creation_user_b) "USER",
nvl(a.cnt_a, 0) + nvl(b.cnt_b, 0) total_records
FROM (SELECT creation_user_a,
COUNT(*) cnt_a
FROM table_a
GROUP BY creation_user_a) a
FULL OUTER JOIN (SELECT creation_user_b,
COUNT(*) cnt_b
FROM table_b
GROUP BY creation_user_b) b ON a.creation_user_a = b.creation_user_b
ORDER BY "USER";
USER TOTAL_RECORDS
------ -------------
USER_A 2
USER_B 3
USER_C 7
Thank you for helping me guys. I have found a simpler and more efficient solution. It works.
SELECT CREATION_USER, SUM(TOTAL_COUNT) TOTAL_COUNT FROM
(SELECT /*+ PARALLEL */ A.CREATION_USER_A CREATION_USER,
COUNT(A.CREATION_USER_A) TOTAL_COUNT
FROM TABLE_A A
GROUP BY A.CREATION_USER_A
UNION
SELECT /*+ PARALLEL */ B.CREATION_USER_B CREATION_USER,
COUNT(B.CREATION_USER_B) TOTAL_COUNT
FROM TABLE_B B
GROUP BY B.CREATION_USER_B)
GROUP BY CREATION_USER;

Oracle query by column1 where column2 is the same

I have a table like this in Oracle 9i DB:
+------+------+
| Col1 | Col2 |
+------+------+
| 1 | a |
| 2 | a |
| 3 | a |
| 4 | b |
| 5 | b |
+------+------+
Col1 is the primary key, Col2 is indexed.
I input col1 as condition for my query and I want to get col1 where col2 is the same as my input.
For example I query for 1 and the result should be 1,2,3.
I know I can use self join for this, I would like to know if there is a better way to do this.
I'd call this a semi-join: does it satisfy your 'no self joins' requirement?:
SELECT *
FROM YourTable
WHERE Col2 IN ( SELECT t2.Col2
FROM YourTable t2
WHERE t2.Col1 = 1 );
I'd be inclined to avoid the t2 range variable like this:
WITH YourTableSearched
AS ( SELECT Col2
FROM YourTable
WHERE Col1 = 1 )
SELECT *
FROM YourTable
WHERE Col2 IN ( SELECT Col2
FROM YourTableSearched );
but TNH I would probably do this:
WITH YourTableSearched
AS ( SELECT Col2
FROM YourTable
WHERE Col1 = 1 )
SELECT *
FROM YourTable
NATURAL JOIN YourTableSearched;
It's possible. Whether it's better (i.e. more performant) than using a self-join, particularly if there is an index on col1, col2, is anyone's guess.
Assuming col1 is unique, you could do:
SELECT col1
FROM (SELECT col1,
col2,
MAX(CASE WHEN col1 = :p_col1_value THEN col2 END) OVER () col2_comparison
FROM your_table)
WHERE col2 = col2_comparison;
And with :p_col1_value = 1:
COL1
----------
1
2
3
And with :p_col1_value = 5:
COL1
----------
4
5

Resources