ORA-00904: "PREV_TEMP": invalid identifier with LAG function - sql

Whats is wrong with this query?
It returns:
ORA-00904: "PREV_TEMP": invalid identifier
SELECT Id, RecordDate, Temperature, LAG(Temperature) OVER (ORDER BY RecordDate) as prev_temp
FROM Weather
WHERE Temperature > prev_temp;
SQL schema:
Create table If Not Exists Weather (Id int, RecordDate date, Temperature int)
Truncate table Weather
insert into Weather (Id, RecordDate, Temperature) values ('1', '2015-01-01', '10')
insert into Weather (Id, RecordDate, Temperature) values ('2', '2015-01-02', '25')
insert into Weather (Id, RecordDate, Temperature) values ('3', '2015-01-03', '20')
insert into Weather (Id, RecordDate, Temperature) values ('4', '2015-01-04', '30')

What is wrong with the query is that column aliases cannot be re-used in the SELECT, WHERE, FROM, or GROUP BY clauses where they are defined. This applies to window functions, as well as everything else. And this is a rule in SQL, not Oracle (although some databases relax the restriction on GROUP BY).
In your case, there are basically two solutions, a subquery and a CTE:
WITH w AS (
SELECT w.*,
LAG(Temperature) OVER (ORDER BY RecordDate) as prev_temperature
FROM weather w
)
SELECT Id, RecordDate, Temperature, prev_temp
FROM w
WHERE Temperature > prev_temp;

You cannot use directly, but need to use in a subquery to be able to use the returning value from analytic function
SELECT *
FROM
(
SELECT Id, RecordDate, Temperature,
LAG(Temperature) OVER (ORDER BY RecordDate) as prev_temp
FROM Weather
)
WHERE Temperature > prev_temp;

Related

How to query latest records in SQL Server

I've tried to to use similar asks but still not able to get my desired result. Here is three sample records.
create table #temps
(
airport varchar(10),
country varchar(10),
plane varchar(10),
id int,
flight_date datetime
)
insert into #temps
values ('IAD', 'USA', 'a777', '195', ' 7/26/2022 11:39:00 AM')
insert into #temps
values ('IAD', 'USA', 'a777', '195', ' 8/12/2022 9:51:00 AM')
insert into #temps
values ('BOS', 'USA', 'a777', '195', ' 8/12/2022 9:51:00 AM')
I tried to retrieve the latest record which is from BOS airport (discard the impossible of the same flight dates occurred from different airports)
I used the ROW_NUMBER such as below and and wanted to return the max rank = 3.
SELECT DISTINCT
a.airport, a.country, a.flight_date, a.plane, id,
ROW_NUMBER() OVER (PARTITION BY id ORDER BY flight_date ASC) AS Ct
FROM
#temps a
I also tried the max such as
SELECT A.airport, A.id, A.flight_date, A.country
FROM #temps A
INNER JOIN (SELECT id, MAX(flight_date) as MAX_FLIGHT_DATE
FROM #temps
GROUP BY id) B ON (A.flight_date = B.MAX_FLIGHT_DATE)
Is there a better technique that can return the record from BOS airport?
Thanks!
joe
You could use ORDER by.
SELECT TOP(3) airport,
country,
plane,
id,
flight_date
FROM #temps
ORDER BY flight_date DESC;

Having clause with subquery

I have below scenario
CREATE TABLE plch_sales
(
region VARCHAR2 (100),
product VARCHAR2 (100),
amount NUMBER
)
/
INSERT INTO plch_sales VALUES ('North', 'Magic Wand', 1000);
INSERT INTO plch_sales VALUES ('North', 'Skele-Gro', 1000);
INSERT INTO plch_sales VALUES ('North', 'Timeturner ', 1000);
INSERT INTO plch_sales VALUES ('South', 'Portkey', 1000);
INSERT INTO plch_sales VALUES ('South', 'Quaffle', 1000);
INSERT INTO plch_sales VALUES ('West', 'Imperius', 1000);
INSERT INTO plch_sales VALUES ('West', 'Gringotts', 1000);
COMMIT;
why the below query is producing no rows? the database is oracle.
select region, sum(amount) sm_amount from PLCH_SALES group by region having sum(amount) > (select sum(amount)/3 from PLCH_SALES);
You need to first do the sum() and then divide it by 3 like the below -
SELECT region, SUM(amount) sm_amount
FROM PLCH_SALES
GROUP BY region
HAVING SUM(amount) > (SELECT SUM(amount)FROM PLCH_SALES)/3;
OR you can alternatively try using window() aggregate function
select distinct region,sm_amount from
(
select region,sum(amount) over(partition by region) as sm_amount, sum(amount) over()/3 as total
from plch_sales
)a where sm_amount>total
The way you have written will not work with having clause .
There are multiple ways to do it like with CTE , Parent_table or like below :
select region, sum(amount) sm_amount
from PLCH_SALES
group by region
having sum(amount) >(sum(amount)/3 )
;
Your query should work. The subquery returns:
2333.333333333333333333333333333333333333
The query without the having returns:
REGION SM_AMOUNT
West 2000
North 3000
South 2000
So clearly, 'North' should be in the result set.
However, it does not in Oracle. Here is a db<>fiddle. It does work with a subquery:
select x.*
from (select region, sum(amount) as sm_amount, (select sum(amount)/3 from PLCH_SALES) as thirds
from PLCH_SALES
group by region
) x
where sm_amount > thirds ;
And it does work with a CTE:
with thirds as (
select sum(s2.amount)/3 as val from PLCH_SALES s2
)
select region, sum(amount) sm_amount
from PLCH_SALES
group by region
having sum(amount) > (select val from thirds);
This sounds like a bug.
The query works in other databases:
Postgres
SQL Server
MySQL
And no doubt any other database where one would try it.

Oracle SQL: how to group same value in different group

Database:
Oracle Database 12c Release 12.2.0.1.0
Following is my test case script:
create table test
(
id number(1),
sdate date,
tdate date,
prnt_id number(1)
);
insert into test (id, sdate, tdate, prnt_id) values (1, to_date('10/17/2012','mm/dd/yyyy'), to_date('10/16/2014','mm/dd/yyyy'), 2);
insert into test (id, sdate, tdate, prnt_id) values (1, to_date('10/16/2014','mm/dd/yyyy'), to_date('2/16/2016','mm/dd/yyyy'), 2);
insert into test (id, sdate, tdate, prnt_id) values (1, to_date('2/16/2016','mm/dd/yyyy'), to_date('9/30/2016','mm/dd/yyyy'), 3);
insert into test (id, sdate, tdate, prnt_id) values (1, to_date('9/30/2016','mm/dd/yyyy'), to_date('3/16/2017','mm/dd/yyyy'), 3);
insert into test (id, sdate, tdate, prnt_id) values (1, to_date('3/16/2017','mm/dd/yyyy'), to_date('1/16/2019','mm/dd/yyyy'), 2);
insert into test (id, sdate, tdate, prnt_id) values (1, to_date('1/16/2019','mm/dd/yyyy'), to_date('10/16/2019','mm/dd/yyyy'), 2);
insert into test (id, sdate, tdate, prnt_id) values (1, to_date('10/16/2019','mm/dd/yyyy'), to_date('12/1/2999','mm/dd/yyyy'), 2);
commit;
select * from test order by sdate;
Question:
I want to modify the above Select SQL which returns all 7 rows from test table, selects all the columns plus two more columns.
First additional column (min_sdate) will return 10/17/2012 for rows 1,2 and 2/16/2016 for rows 3,4 and 3/16/2017 for rows 5,6,7.
Second additional column (max_tdate) will return 2/16/2016 for rows 1,2 and 3/16/2017 for rows 3,4 and 12/1/2999 for rows 5,6,7.
Basically, I'm trying to group by prnt_id column but instead of two groups (prnt_id: 2 and 3), I want three groups (prnt_id: 2,3,2), and then for those three groups get the min(sdate) and max(tdate).
I was thinking I could use analytical function min() and max() with window clause to achieve this, but not sure how to frame the SQL.
Any or all help will be appreciated. Thanks!
This is a form of gaps-and-islands. Assuming that the dates tile with no gaps, you can use the difference of row numbers to identify the islands:
select t.*,
min(sdate) over (partition by id, prnt_id, seqnum - seqnum_2),
max(edate) over (partition by id, prnt_id, seqnum - seqnum_2)
from (select t.*,
row_number() over (partition by id order by sdate) as seqnum,
row_number() over (partition by id, prnt_id order by sdate) as seqnum_2
from test t
) t;
Why this works is a little tricky to explain. But if you look at the results of the subquery, you will be able to see how the difference in row numbers defines the groups you want to define.

Oracle - Calculating time differences

Let's say I have following data:
Create Table Pm_Test (
Ticket_id Number,
Department_From varchar2(100),
Department_To varchar2(100),
Routing_Date Date
);
Insert Into Pm_Test Values (1,'A','B',To_Date('20140101120005','yyyymmddhh24miss'));
Insert Into Pm_Test Values (1,'B','C',To_Date('20140101130004','yyyymmddhh24miss'));
Insert Into Pm_Test Values (1,'C','D',To_Date('20140101130004','yyyymmddhh24miss'));
Insert Into Pm_Test Values (1,'D','E',To_Date('20140201150004','yyyymmddhh24miss'));
Insert Into Pm_Test Values (2,'A','B',To_Date('20140102120005','yyyymmddhh24miss'));
Insert Into Pm_Test Values (3,'D','B',To_Date('20140102120005','yyyymmddhh24miss'));
Insert Into Pm_Test Values (3,'B','A',To_Date('20140102170005','yyyymmddhh24miss'));
For the following requirements I already added two virtual columns, I think they might be necessary:
Select t.*,
Count(Ticket_id) Over (Partition By Ticket_id Order By Ticket_id) Cnt_Id,
Row_Number() Over (Partition By Ticket_id Order By Ticket_id ) row_number
From Pm_Test t;
1) I want to measure how long each ticket stayed in a department (routing_date of successor_department - routing_date of predecessor department) by adding the column PROCESSING_TIME:
2) I want to measure the total processing time by adding the column TOTAL_PROCESSING_TIME:
What SQL statements would be necessary to do so?
Thank you very much in advance!
To solve your problem, the way you described, the following sql should get you there. One thing to keep in mind, this data model doesn't seem the most efficient to capture processing times, if that's its true intent as the first department to get the ticket isn't measured.
select dept.ticket_id, department_from, department_to, routing_date, dept_processing_time, total_ticket_processing_time
from
(select ticket_id, max(routing_date) - min(routing_date) total_ticket_processing_time
from pm_test
group by ticket_id) total
join
(select ticket_id, department_from, department_to, routing_date,
coalesce(routing_date - lag(routing_date) over (partition by ticket_id order by routing_date), 0) dept_processing_time
from pm_test) dept
on (total.ticket_id = dept.ticket_id);
This query produces desired output. Analytic functions max(), min() and lag() used for calculations.
Results are in hours, like in your question.
SQLFiddle
select t.ticket_id, t.department_from, t.department_to,
to_char(t.routing_date, 'mm.dd.yy hh24:mi:ss') rd,
count(ticket_id) over (partition by ticket_id) cnt_id,
row_number() over (partition by ticket_id order by t.routing_date ) rn,
round(24 * (t.routing_date-
nvl(lag(t.routing_date) over (partition by ticket_id
order by t.routing_date), routing_date) ) , 8) dept_time,
round(24 * (max(t.routing_date) over (partition by ticket_id)
- min(t.routing_date) over (partition by ticket_id)), 8) total_time
from pm_test t

SQL Query to identify "Top Performers" [?]

I'm still learning Oracle SQL and would like your guidance.
Let say, we have MONTHLY_SALES_TOTALS table that has 3 fields: name, region, amount. We need to determine the best sales people per region. Best means that their amount is equal to the maximum for the region.
CREATE TABLE montly_sales_totals
(
name varchar(20),
amount numeric(9),
region varchar(30)
);
INSERT ALL
INTO montly_sales_totals (name, amount, region) VALUES ('Peter', 55555, 'east')
INTO montly_sales_totals (name, amount, region) VALUES ('Susan', 55555, 'east')
INTO montly_sales_totals (name, amount, region) VALUES ('Mark', 1000000, 'south')
INTO montly_sales_totals (name, amount, region) VALUES ('Glenn', 50000, 'east')
INTO montly_sales_totals (name, amount, region) VALUES ('Paul', 500000, 'south')
SELECT * from dual;
Possible solution:
SELECT m1.name, m1.region, m1.amount
FROM montly_sales_totals m1
JOIN
(SELECT MAX(amount) max_amount, region FROM montly_sales_totals GROUP BY region) m2
ON (m1.region = m2.region)
WHERE m1.amount = m2.max_amount
ORDER by 2,1;
SQL Fiddle: http://sqlfiddle.com/#!4/6a2d8/6
Now my questions:
How efficient is such query?
How can/should it be simplified and/or improved?
I could not use Top since the number of "max" rows vary by region. Is it another direct functionality I could've used instead?
I would use RANK():
SELECT *
FROM (
SELECT name, amount, region,
RANK() OVER (PARTITION BY region ORDER BY amount DESC) rnk
FROM montly_sales_totals
) t
WHERE t.rnk = 1
Here's a modified version of the SQL Fiddle
There are a number of ways one can go about this. Here's another:
select S.region, S.name, V.regionmax
from sales as S
inner join
(
select region, max(amount) as regionmax
from sales group by region
) as V
on S.region = V.region and S.amount = regionmax
As to efficiency, the main factor is the use of the proper index(es). Inline views can perform very well.
I like CTE syntax, but using that website the time taken is the same 2ms, so I can't beat yours :)
with Maximums as (
SELECT region,
MAX(amount) max_amount
FROM montly_sales_totals GROUP BY region
)
SELECT m1.name, m1.region, m1.amount
FROM montly_sales_totals m1, Maximums
WHERE (m1.amount = Maximums.max_amount)
and (m1.region = Maximums.region)
ORDER by 2,1;
you can do this by using the function too...
select * from (select m1.*, row_number( ) over (partition by m1.region order by m1.amount desc,m1.name desc ) max_sal from montly_sales_totals m1 ) where max_sal =1 ;
this query can do one extra thing if both employee sal are same!