Having clause with subquery - sql

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.

Related

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

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;

How to sum a value from the prev column to create a new value for the next column

Link to Image
Please see the image above to show you an example of what I am trying to do. Thanks all.
I have a sales column with sales figures, I want to create a new forecast column and start it where the sales column has ended in this case 472031 + 34546. See the example
Sales Column
34546
56497
89245
122952
160134
187809
227312
264421
308869
342777
388785
430483
472031
the latest sales figure was 472031 by adding 34546 then the below column will be 506,576 and so on.
See below for examples. Thanks in advance.
Sales Forecast Column
506,576
541,122
575,668
610,213
644,759
679,305
713,850
748,396
782,942
817,487
852,033
886,579
921,124
955,670
990,216
1,024,761
1,059,307
1,093,853
1,128,398
1,162,944
1,197,490
1,232,035
Create Script
create table sales(
id int,
sales int);
insert into sales(id, sales) values (1,34546);
insert into sales(id, sales) values (2,56497);
insert into sales(id, sales) values (3,89245);
insert into sales(id, sales) values (4,122952);
insert into sales(id, sales) values (5,160134);
insert into sales(id, sales) values (6,187809);
insert into sales(id, sales) values (7,227312);
insert into sales(id, sales) values (8,264421);
insert into sales(id, sales) values (9,308869);
insert into sales(id, sales) values (10,342777);
insert into sales(id, sales) values (11,388785);
insert into sales(id, sales) values (12,430483);
insert into sales(id, sales) values (13,472031);
Queries
Example 1
select sales.sales, (id *34546)+472031 from sales;
Example2
WITH CTE AS (
SELECT
rownum = ROW_NUMBER() OVER (ORDER BY id),
(((ROW_NUMBER() OVER (ORDER BY id)) - 1) * 34546) as extraSales
FROM sales
)
SELECT
472031 + cur.extraSales
FROM CTE cur
INNER JOIN CTE prev on prev.rownum = cur.rownum - 1
SQL Fiddles
http://sqlfiddle.com/#!6/4bca1/9
http://sqlfiddle.com/#!6/4bca1/30
To calculate the new sales, you can do
select s.*,
(case when sales = 0
then row_number() over (partition by sales order by period) * 34546
end) as inc_sales
from sales s;
Then, you want to add in the sales on the date just before. It so happens that if sales are increasing then this is the maximum of the sales. The result is:
select s.*,
((case when sales = 0
then row_number() over (partition by sales order by period) * 34546
end) +
max(sales) over ()
) as forecast
from sales s;

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!

Cumulative Total in MS Sql server [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Calculate a Running Total in SqlServer
I need to get the cumulative (running) total of a column in ms-sql server. I.e. if there is a column named “Marks”, then corresponding to each row cumulative sum will the sum of current and previous rows. Can we attain the result without using joins? Because my query is pretty big.
I have included a sample table and data:
CREATE TABLE "SCORE_CHART"
(
"STUDENT_NAME" NVARCHAR(20),
"MARKS" INT
)
INSERT INTO SCORE_CHART (STUDENT_NAME, MARKS) VALUES ('STUD1', 95);
INSERT INTO SCORE_CHART (STUDENT_NAME, MARKS) VALUES ('STUD2', 90);
INSERT INTO SCORE_CHART (STUDENT_NAME, MARKS) VALUES ('STUD3', 98);
SELECT STUDENT_NAME, MARKS FROM SCORE_CHART;
Expected result:
In oracle it’s easy to write like:
SELECT
STUDENT_NAME,
MARKS,
SUM(MARKS) OVER (ORDER BY STUDENT_NAME) CUM_SUM
FROM SCORE_CHART
ORDER BY STUDENT_NAME;
Thanks in advance.
The same query is supported from 2012 onwards. In older versions there are several approaches. Refer this http://www.sqlperformance.com/2012/07/t-sql-queries/running-totals
try this:
you can get the cumulative sum just by joining the same table itself
SELECT S1.STUDENT_NAME, S1.MARKS ,sum(S2.MARKS) CUM_SUM
FROM SCORE_CHART S1 join SCORE_CHART S2
on S1.STUDENT_NAME>=S2.STUDENT_NAME
group by S1.STUDENT_NAME, S1.MARKS
order by S1.STUDENT_NAME, S1.MARKS
SQL Fiddle demo
You said no joins, what about a apply? ;)
SELECT STUDENT_NAME, MARKS, running.total
FROM SCORE_CHART a
cross apply
(
select SUM(marks) total
from score_chart b
where b.student_name <= a.student_name
) running
ORDER BY STUDENT_NAME;
With a index on student_name speed should be okay!
Check the query for Recursive CTE.
;with CTE as (select ROW_NUMBER() over (order by (select 0)) as id,STUDENT_NAME,MARKS from SCORE_CHART)
,CTE1 as (
select id,STUDENT_NAME,marks,marks as CUM_SUM from CTE where id=1
UNION ALL
select c.id,c.STUDENT_NAME,c.marks,c.marks+c1.CUM_SUM as CUM_SUM from CTE1 c1 inner join CTE c on c.id-1=c1.id)
select * from CTE1
Use Recursive CTE to achive this.
JUSt doing a join doesn't seem to guarantee order but comes up with the final answer ok:
select
x.STUDENT_NAME
, sum(y.marks) marks
from
SCORE_CHART x
join SCORE_CHART y
on x.STUDENT_NAME <= y.STUDENT_NAME
group by x.STUDENT_NAME
order by x.STUDENT_NAME
just seem the NO JOINS rule - will re-think
EDIT - running ok now: LIVE FIDDLE HERE
Creating the data
CREATE TABLE "SCORE_CHART"
(
"STUDENT_NAME" NVARCHAR(20),
"MARKS" INT
)
INSERT INTO SCORE_CHART (STUDENT_NAME, MARKS)
VALUES
('STUD1', 95),
('STUD2', 90),
('STUD3', 98)
Using a recursive CTE:
;WITH
init_cte(row,STUDENT_NAME,MARKS)
AS
(
SELECT
ROW_NUMBER() OVER (ORDER BY STUDENT_NAME),
STUDENT_NAME,
MARKS
FROM SCORE_CHART
)
,MinMax_cte(MinRow,MaxRow) AS (SELECT MIN(row),MAX(row) FROM init_cte)
,recursive_cte (row,STUDENT_NAME,MARKS,RUNNING_MARKS) AS
(
SELECT row,STUDENT_NAME,MARKS,MARKS
FROM init_cte
WHERE row = (SELECT MinRow FROM MinMax_cte)
UNION ALL
SELECT Y.row,y.STUDENT_NAME,y.MARKS,x.RUNNING_MARKS + y.MARKS
FROM recursive_cte x
INNER JOIN init_cte y
ON y.row = x.row + 1
WHERE y.row <= (SELECT [MaxRow] from MinMax_cte)
)
SELECT * FROM recursive_cte
As mentioned in a comment to you OP there is a similar question HERE ON SO
In that question Sam Saffron put forward a very elegant way of doing a running total using UPDATE. This is is applied to your data:
Using the same data created above but with the UPDATE trick:
CREATE TABLE #t ( ROW int, STUDENT_NAME NVARCHAR(20) , MARKS int, MARKS_RUNNING int)
INSERT INTO #t
SELECT
ROW_NUMBER() OVER (ORDER BY STUDENT_NAME),
STUDENT_NAME,
MARKS,
0
FROM SCORE_CHART
DECLARE #total int
SET #total = 0
UPDATE #t SET marksrunning = #total, #total = #total + MARKS
SELECT * FROM #t

Sorting with sql

I have a question on how to sort data using sql. For that I made up a simple example to illustrate my problem.
if object_id('MyTable', 'U') is not null drop table dbo.MyTable;
create table MyTable
(
country varchar(10) not null
, town varchar( 10 ) not null
, amount int not null
)
insert into MyTable values
( 'US', 'NYC', 100 )
, ( 'US', 'BAL', 150 )
, ( 'US', 'WAS', 200 )
, ( 'CA', 'MON', 100 )
, ( 'CA', 'TOR', 150 )
, ( 'CA', 'VAN', 200 )
How can I sort the data in a sense the all "countries are sorted by the amount in descending order AND that the towns in alphabetical order for each country.
Thanks,
Christian
To sort in SQL, use Order By: http://msdn.microsoft.com/en-us/library/ms188385.aspx
So if you wanted sorted by Country, then Amount, then Town, you'd add an Order By clause after your Where class like:
ORDER BY Country, Amount DESC, Town
I think that this should do it:
SELECT
country,
SUM(amount) OVER(PARTITION BY country) as CountryTotal,
town,
amount
FROM MyTable
ORDER BY CountryTotal, country, town
I am not sure whether you want to order by total amount of a country or just amount .
for total amount of country use below.
select
mt.country,
ctotal.countrycotal,
mt.town,
mt.amount
from
(
SELECT
country,
SUM(amount) as countrycotal,
FROM MyTable
group BY country
) ctotal
inner join Mytable mt on ctotal.country = mt.country
order by ctotal.countrytotal,ctotal.country,mt.town
for just amount use below
select * from Mytable
order by mt.amount,mt.country,mt.town