Display the Alternative columns from table row - oracle

enter image description here
Display the Alternative columns from table row

Convert the salary column to a string data type (so that salary and name have the same data type) and then UNPIVOT:
Oracle Setup:
CREATE TABLE test_data ( id, name, salary ) AS
SELECT 100, 'A', 1000 FROM DUAL UNION ALL
SELECT 101, 'B', 2000 FROM DUAL UNION ALL
SELECT 102, 'C', 3000 FROM DUAL
Query:
SELECT id, value
FROM (
SELECT id, name, TO_CHAR( salary ) AS salary
FROM test_data
)
UNPIVOT ( value FOR key IN ( name, salary ) )
Output:
ID | VALUE
--: | :----
100 | A
100 | 1000
101 | B
101 | 2000
102 | C
102 | 3000
db<>fiddle here

Alternatively, UNION ALL can also work for you, If you agreed to change the datatype of Salary column -
SELECT ID, Name "Name & Salary"
FROM TABLE1
UNION ALL
SELECT ID, Salary
FROM TABLE1
ORDER BY ID, Name

Related

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 Stored Procedure - Number of heat waves (number of pattern matches in a series)

I need to postprocess a Oracle dataset in order to find the number of heat waves.
By definition, a heat waves occurs when the data value is greater than a threshold at least two consecutive times.
For example, given the threshold=20 and the sequence
23 31 32 17 16 23 16 21 22 18
the heat waves are 2:
{23,31,32} and {21,22}
and the lenght of the longest one is 3 (size of bigger subset)
My input dataset consists of several sequences; a sample input result set is:
-----------------------------
| ID | DAY | VALUE |
-----------------------------
| 100 | 1/1/17 | 20 |
| 100 | 2/1/17 | 21 |
| 200 | 1/1/17 | 12 |
| 200 | 2/1/17 | 24 |
| ... ... ...
In other words, I have a sequence per each ID and I need to output something like that:
-----------------------
| ID | #heat waves |
-----------------------
| 100 | 3 |
| 200 | 1 |
Here the current version of my stored procedure:
create or replace PROCEDURE sp (
p_query IN VARCHAR2,
cursor_ out sys_refcursor
) AS
processed processed_data_table := processed_data_table();
c sys_refcursor;
BEGIN
OPEN c FOR p_query;
processed.EXTEND;
processed(processed.count) := processed_data_obj();
fetch c INTO processed(processed.count).ID,
processed(processed.count).DAY, processed(processed.count).VALUE;
while c%found
processed.EXTEND;
processed(processed.count) := processed_data_obj();
fetch c INTO processed(processed.count).ID,
processed(processed.count).DAY, processed(processed.count).VALUE;
END loop;
CLOSE c;
processed.TRIM;
// HERE I NEED TO PROCESS processed TABLE AND STORE RESULT IN output
TABLE
OPEN cursor_ FOR
SELECT *
FROM TABLE( output);
END sp;
Anyone could help me providing a solution?
Thanks
In Oracle 12c, use MATCH_RECOGNIZE:
select id, count(*) "# of heatwaves" from series_data
match_recognize ( partition by id
order by day
one row per match
after match skip past last row
pattern ( over_threshold{2,} )
define
over_threshold as value > 20 )
group by id
UPDATE: Also show longest heat wave for each series
To get the longest heatwave in each series, we have to introduce a MEASURES clause to the MATCH_RECOGNIZE, as below:
select id,
max(heatwave_length) "longest heatwave",
count(distinct heatwave_number) "# of heatwaves"
from series_data
match_recognize ( partition by id
order by day
measures
FINAL COUNT(*) as heatwave_length,
MATCH_NUMBER() heatwave_number
all rows per match
after match skip past last row
pattern ( over_threshold{2,} )
define
over_threshold as value > 20 )
group by id
order by id;
Full example with data:
with series_data ( id, day, value ) as
( SELECT 100, date '2017-01-01', 23 from dual union all
SELECT 100, date '2017-01-02', 31 from dual union all
SELECT 100, date '2017-01-03', 32 from dual union all
SELECT 100, date '2017-01-04', 44 from dual union all
SELECT 100, date '2017-01-05', 16 from dual union all
SELECT 100, date '2017-01-06', 23 from dual union all
SELECT 100, date '2017-01-07', 16 from dual union all
SELECT 100, date '2017-01-08', 21 from dual union all
SELECT 100, date '2017-01-09', 22 from dual union all
SELECT 100, date '2017-01-10', 18 from dual union all
SELECT 200, date '2017-01-01', 23 from dual union all
SELECT 200, date '2017-01-02', 31 from dual union all
SELECT 200, date '2017-01-03', 32 from dual union all
SELECT 200, date '2017-01-04', 17 from dual union all
SELECT 200, date '2017-01-05', 16 from dual union all
SELECT 200, date '2017-01-06', 23 from dual union all
SELECT 200, date '2017-01-07', 16 from dual union all
SELECT 200, date '2017-01-08', 21 from dual union all
SELECT 200, date '2017-01-09', 22 from dual union all
SELECT 200, date '2017-01-10', 22 from dual union all
SELECT 200, date '2017-01-11', 6 from dual union all
SELECT 200, date '2017-01-12', 22 from dual union all
SELECT 200, date '2017-01-13', 22 from dual )
select id,
max(heatwave_length) "longest heatwave",
count(distinct heatwave_number) "# of heatwaves"
from series_data
match_recognize ( partition by id
order by day
measures
FINAL COUNT(*) as heatwave_length,
MATCH_NUMBER() heatwave_number
all rows per match
after match skip past last row
pattern ( over_threshold{2,} )
define
over_threshold as value > 20 )
group by id
order by id;
Results:
ID longest heatwave # of heatwaves
----- -------------- --------------
100 4 2
200 3 3

Find highest and lowest selling item in a table

I have two tables as follows--
ORDERS
create table orders (
ono number(5) not null primary key,
cno number(5) references customers,
eno number(4) references employees,
received date,
shipped date);
ODETAILS
create table odetails (
ono number(5) not null references orders,
pno number(5) not null references parts,
qty integer check(qty > 0),
primary key (ono,pno));
ODETAILS Table
Now I'm trying to figure out the highest and lowest selling product. Logically PNO 10601 which has the highest QTY of 4 is the highest selling product. the following query returns the highest selling product.
SELECT PNO FROM
(SELECT od.PNO, SUM(od.QTY) AS TOTAL_QTY
FROM ODETAILS od
GROUP BY od.PNO
ORDER BY SUM(od.QTY) DESC)
WHERE ROWNUM =1
--Thanks to Bob Jarvis
How do I add a DATE WHERE clause to the SQL above so that I can find out the highest selling product for a given month(lets say DECEMBER) ? The DATE that I'm referring to is from ORDERS table and RECEIVED attribute. I guess I need to join the tables first as well
SQL Fiddle
Oracle 11g R2 Schema Setup:
create table orders (
ono number(5) not null primary key,
cno number(5),
eno number(4),
received date,
shipped date
);
INSERT INTO orders
SELECT 1020, 1, 1, DATE '2015-12-21', NULL FROM DUAL UNION ALL
SELECT 1021, 1, 1, DATE '2015-12-20', DATE '2015-12-20' FROM DUAL UNION ALL
SELECT 1022, 1, 1, DATE '2015-12-18', DATE '2015-12-20' FROM DUAL UNION ALL
SELECT 1023, 1, 1, DATE '2015-12-21', NULL FROM DUAL UNION ALL
SELECT 1024, 1, 1, DATE '2015-12-20', DATE '2015-12-20' FROM DUAL;
create table odetails (
ono number(5) not null references orders(ono),
pno number(5) not null,
qty integer check(qty > 0),
primary key (ono,pno)
);
INSERT INTO odetails
SELECT 1020, 10506, 1 FROM DUAL UNION ALL
SELECT 1020, 10507, 1 FROM DUAL UNION ALL
SELECT 1020, 10508, 2 FROM DUAL UNION ALL
SELECT 1020, 10509, 3 FROM DUAL UNION ALL
SELECT 1021, 10601, 4 FROM DUAL UNION ALL
SELECT 1022, 10601, 1 FROM DUAL UNION ALL
SELECT 1022, 10701, 1 FROM DUAL UNION ALL
SELECT 1023, 10800, 1 FROM DUAL UNION ALL
SELECT 1024, 10900, 1 FROM DUAL;
Query 1 - The onoand pnos for the pno which has sold the maximum total quantity in December 2015:
SELECT ono,
pno,
TOTAL_QTY
FROM (
SELECT q.*,
RANK() OVER ( ORDER BY TOTAL_QTY DESC ) AS rnk
FROM (
SELECT od.ono,
od.PNO,
SUM( od.QTY ) OVER ( PARTITION BY od.PNO ) AS TOTAL_QTY
FROM ODETAILS od
INNER JOIN
orders o
ON ( o.ono = od.ono )
WHERE TRUNC( o.received, 'MM' ) = DATE '2015-12-01'
-- WHERE EXTRACT( MONTH FROM o.received ) = 12
) q
)
WHERE rnk = 1
Change the WHERE clause to get the results for any December rather than just December 2015.
Results:
| ONO | PNO | TOTAL_QTY |
|------|-------|-----------|
| 1021 | 10601 | 5 |
| 1022 | 10601 | 5 |
Query 2 - The ono and pnos for the items which have sold the maximum quantity in a single order in December 2015:
SELECT ono,
pno,
qty
FROM (
SELECT od.*,
RANK() OVER ( ORDER BY od.qty DESC ) AS qty_rank
FROM ODETAILS od
INNER JOIN
orders o
ON ( o.ono = od.ono )
WHERE TRUNC( o.received, 'MM' ) = DATE '2015-12-01'
-- WHERE EXTRACT( MONTH FROM o.received ) = 12
)
WHERE qty_rank = 1
Change the WHERE clause to get the results for any December rather than just December 2015.
Results:
| ONO | PNO | QTY |
|------|-------|-----|
| 1021 | 10601 | 4 |
... where received between to_date('12/01/2015','MM/DD/YYYY') and to_date('12/31/2015','MM/DD/YYYY')
I believe I have solved it!
SELECT PNO
FROM (SELECT OD.PNO, SUM(OD.QTY) AS TOTAL_QTY
FROM ODETAILS OD INNER JOIN ORDERS ON OD.ONO = ORDERS.ONO
WHERE EXTRACT(MONTH FROM ORDERS.RECEIVED) = &MONTH_NUMBER
GROUP BY OD.PNO
ORDER BY SUM(OD.QTY) DESC)
WHERE ROWNUM =1;
You can add some to_char calls to your query on the date columns to parse out year and month, or just month if you want all years divided by month (month and year seems more useful), then add that to your where clause. See my self-contained example:
with odetails as
(
select 1 as ono, 1 as pno, 4 as qty from dual
union all
select 1 as ono, 2 as pno, 1 as qty from dual
union all
select 1 as ono, 3 as pno, 2 as qty from dual
union all
select 1 as ono, 4 as pno, 1 as qty from dual
union all
select 2 as ono, 2 as pno, 1 as qty from dual
union all
select 2 as ono, 3 as pno, 2 as qty from dual
),
orders as
(
select 1 as ono, 1 as cno, 1 as eno, to_date('2015-10-12', 'YYYY-MM-DD') as received, to_date('2015-10-15', 'YYYY-MM-DD') as shipped from dual
union all
select 2 as ono, 1 as cno, 1 as eno, to_date('2015-11-12', 'YYYY-MM-DD') as received, to_date('2015-11-15', 'YYYY-MM-DD') as shipped from dual
)
select pno
from
(
select od.pno, Sum(od.qty) as total_qty, to_char(received, 'YYYY-MM') as year_month
from odetails od
join orders o
on o.ono = od.ono
group by od.pno, to_char(received, 'YYYY-MM')
order by Sum(od.qty) desc
)
where rownum = 1
and year_month = '2015-11'
;
This gives you PNO of 3, since it has the highest quantity in november of 2015.

How to convert mmyyyy text into regular date format in oracle?

I have column of reporting_period in a table which has values like this:
ID | reporting_period
-----------------------
123 | 092015
456 | 082015
I want to convert this column values to a regular date like:
ID | reporting_period
------------------------
123 | 09/01/2015
456 | 08/01/2015
you need use to_date function to convert your string to date
and then to_char function if you need present your date in a different from default format
select to_char(to_date('092015','mmyyyy'),'mm/dd/yyyy') as dt from dual
dt
1 09/01/2015
WITH data (ID, reporting_period) AS (
SELECT 123, '092015' FROM DUAL UNION ALL
SELECT 456, '082015' FROM DUAL
)
SELECT
ID, TO_DATE(REPORTING_PERIOD, 'MMYYYY') reporting_period_date
FROM
data
select TO_DATE(SUBSTR(reporting_period,1,2) || '/01/' || SUBSTR(reporting_period,3,4),'MM/DD/YYYY') AS reporting_period
FROM MyTable

Combining column values from different tables

I stuck. I have 2 tables - look at image no.1 Table columns And i would like to build query, that will give me the result - it is showed on image no 2. the result of query.
I have 2 queries and I would like to mix them up, to obtain the list from image no.2. Please help me, how to build a query.
Query no1: SELECT department_name, department_id FROM DEPARTMENTS WHERE department_id between 90 AND 110;
Query no 2: SELECT last_name, department_id from employees WHERE department_id between 90 AND 110;
Query:
with departments (department_id, department_name) as (
select 90, 'Executive' from dual union all
select 100, 'Finance' from dual union all
select 110, 'Accounting' from dual
),
employees (employee_id, last_name, department_id) as (
select 1003, 'King' , 90 from dual union all
select 1005, 'De Hann' , 90 from dual union all
select 1009, 'Gietz' , 110 from dual union all
select 1013, 'Popp' , 100 from dual union all
select 1014, 'Chen' , 100 from dual union all
select 1015, 'Higgins' , 110 from dual union all
select 1029, 'Greenberg', 100 from dual union all
select 1040, 'Kochar' , 90 from dual union all
select 1043, 'Faviet' , 100 from dual union all
select 1045, 'Urman' , 100 from dual union all
select 1049, 'Sciarra' , 100 from dual
)
-- end input data; begin actual query --
select c_name, department_id from
( select department_name as c_name, department_id, 0 as categ from departments
union all
select ' ' || last_name as c_name, department_id, 1 from employees
order by department_id, categ, c_name
);
Result:
C_NAME DEPARTMENT_ID
------------- -------------
Executive 90
De Hann 90
King 90
Kochar 90
Finance 100
Chen 100
Faviet 100
Greenberg 100
Popp 100
Sciarra 100
Urman 100
Accounting 110
Gietz 110
Higgins 110
You don't need the "with ..." part; just use the query that begins at the SELECT statement after the two factored subqueries (after the "input data"). I even ordered by last name within each department for you; if that is not needed, just delete "c_name" from the ORDER BY clause.
I called the first column c_name; you may call it whatever you want, but calling it department_name when it also holds employee last names didn't make much sense to me. To call it whatever you want, change the SELECT statement from SELECT c_name, department_id to SELECT c_name AS whatever, department_id...
SELECT c.last_name,
d.department_id,
d.department_name
FROM employee c
JOIN deptartment d ON d.department_id=c.department_id
WHERE d.department_id BETWEEN 90 AN 110
OUTPUT from my sample table
+-------+----+------------+
| KING | 10 | ACCOUNTING |
| BLAKE | 30 | SALES |
| CLARK | 10 | ACCOUNTING |
| JONES | 20 | RESEARCH |
| SCOTT | 20 | RESEARCH |
+-------+----+------------+

Resources