Aggregation between dates - sql

I have the below query where I'm trying to calculate sum of salaries for over a period of year..
select sum(case when date_key between to_char(sysdate,'yyyymm')
and to_char(add_months(sysdate,-12), 'yyyymm')
then salary end) as annual_salary
from employee
group by emp_key
When I execute the query I'm getting null's in the result set..
I actually have valid figures for salaries in employee table.
Where am I going wrong?

Just invert the 2 bounds, they are not in the correct order:
...
between to_char(add_months(sysdate,-12), 'yyyymm')
and to_char(sysdate,'yyyymm')

select sum(case when date_key between
to_char(add_months(sysdate,-12), 'yyyymm') and to_char(sysdate,'yyyymm')
then salary end) as annual_salary
from employee
group by emp_key

select
sum(case when date_key between to_char(sysdate,'yyyymm') and to_char(add_months(sysdate,-12),'yyyymm') then salary else 0 end) as annual_salary
from employee group by emp_key
CASE WHEN THEN expr1 ELSE expr2 END
if you don't have "ELSE expr2" Oracle thinks it's NULL

Related

Can't use column alias in GROUP BY

I can run this in mysql with no problem
SELECT
DATE_FORMAT(trans_date, '%Y-%m') month,
COUNTRY, COALESCE(COUNT(*), 0) trans_count,
COALESCE(SUM(CASE WHEN state ='approved' THEN 1 END), 0) approved_count,
COALESCE(SUM(amount), 0) trans_total_amount,
COALESCE(SUM(CASE WHEN state ='approved' THEN amount END), 0) approved_total_amount
FROM
Transactions
GROUP BY
month, COUNTRY
ORDER BY
month;
but the same query doesn't run in Orcale, I can't use GROUP BY using aggregation alias, and I can't aggregate without using GROUP BY.
I can call subquery over subquery or use CTE, but it is just so tedious.
What is a good query for type of issue?
As mentioned in another answer, You can not add aliases in GROUP BY but you can add aliases in ORDER BY. Also, DATE_FORMAT is MySql function. It is TO_CHAR in Oracle.
So your final query should be as following:
SELECT
TO_CHAR(TRANS_DATE, 'YYYY-MM') AS MONTH,
COUNTRY,
COUNT(*) AS TRANS_COUNT,
SUM(CASE WHEN STATE = 'approved' THEN 1 ELSE 0 END) AS APPROVED_COUNT,
SUM(AMOUNT) AS TRANS_TOTAL_AMOUNT,
SUM(CASE WHEN STATE = 'approved' THEN AMOUNT ELSE 0 END) AS APPROVED_TOTAL_AMOUNT
FROM TRANSACTIONS
GROUP BY TO_CHAR(TRANS_DATE, 'YYYY-MM'), COUNTRY
ORDER BY MONTH;
Oracle doesn't support aliases for the GROUP BY. Also, the COALESCE() is unnecessary in this case:
SELECT DATE_FORMAT(trans_date, '%Y-%m') as month, COUNTRY,
COUNT(*) as trans_count,
SUM(CASE WHEN state ='approved' THEN 1 ELSE 0 END) as approved_count,
SUM(amount) as trans_total_amount,
SUM(CASE WHEN state = 'approved' THEN amount ELSE 0 END) as approved_total_amount
FROM Transactions
GROUP BY DATE_FORMAT(trans_date, '%Y-%m'), COUNTRY
ORDER BY month;

AVG duplication SQL

I'm currently having issues creating two columns with AVG for different date ranges.
I've tried the below code to try and resolve this.
WITH Tbl AS(
SELECT FORMAT(SaleDate,'MM')+'.'+FORMAT(SaleDate,'yyyy') AS SALE_MY, Employee, NewScheme
FROM Salereport
WHERE Business Area='Sales'
)
SELECT
AgentName,
(SELECT AVG(NewScheme) FROM Tbl WHERE SALE_MY='01.2019' OR SALE_MY='02.2019' OR SALE_MY='03.2019'),
(SELECT AVG(NewScheme) FROM Tbl WHERE SALE_MY='04.2019' OR SALE_MY='05.2019' OR SALE_MY='06.2019')
FROM Tbl
GROUP BY Employee
Result is just the same AVG for everyone.
Use conditional aggregation:
SELECT Employee,
AVG(CASE WHEN SaleDate >= '2019-01-01' AND SaleDate < '2019-04-01'
THEN NewScheme
END),
AVG(CASE WHEN SaleDate >= '2019-01-04' AND SaleDate < '2019-04-07'
THEN NewScheme
END),
FROM Salereport
WHERE Business Area = 'Sales'
GROUP BY Employee;
When working with dates, you should be using date operations. The only time you normally need to convert to a string is to format dates in the result set.
Incidentally, your version is taking the average across all employees. No subquery is needed, but if you were to use one, you would want a correlated subquery.
Try this:
WITH Tbl AS(
SELECT FORMAT(SaleDate,'MM')+'.'+FORMAT(SaleDate,'yyyy') AS SALE_MY, Employee, NewScheme
FROM Salereport
WHERE Business Area='Sales'
)
SELECT Employee,
AVG(CASE WHEN SALE_MY IN ('01.2019', '02.2019', '03.2019') THEN NewScheme ELSE NULL END) AS Q1_Avg,
AVG(CASE WHEN SALE_MY IN ('04.2019', '05.2019', '06.2019') THEN NewScheme ELSE NULL END) AS Q2_Avg
FROM Tbl
GROUP BY Employee

Get the Highest Value in different Select SUM

I want to get the highest value in my query
Select SUM(CASE WHEN Day='Monday' THEN 1 END) AS'Total Monday',
SUM(CASE WHEN Day='Tuesday' THEN 1 END) AS'Total Tuesday'
FROM tbl_sched
WHERE teacherID='2014279384'
The Output would be TotalMonday ='1' and TotalTuesday ='2'
I need to get the highest value from the outputs which in this case is TotalTuesday=2
select max(daycnt) from
(Select SUM(CASE WHEN Day='Monday' THEN 1 END) AS daycnt
from tbl_sched WHERE teacherID='2014279384'
union all
Select SUM(CASE WHEN Day='Tuesday' THEN 1 END) AS daycnt
from tbl_sched WHERE teacherID='2014279384')
If you need the max between many columns:
Something interesting in SQLServer 2008 and above
SELECT (SELECT Max(v)
FROM (VALUES ([Total Monday]), ([Total Tuesday]), ...) AS value(v)) as [MaxDate]
From
(
Select SUM(CASE WHEN Day='Monday' THEN 1 END) AS'Total Monday',
SUM(CASE WHEN Day='Tuesday' THEN 1 END) AS'Total Tuesday'
..........
FROM tbl_sched
WHERE teacherID='2014279384'
)a
Another option:
SELECT Case When [Total Monday] > [Total Tuesday] then [Total Monday] else [Total Tuesday] End as maxvalue
FROM
(
Select SUM(CASE WHEN Day='Monday' THEN 1 END) AS'Total Monday',
SUM(CASE WHEN Day='Tuesday' THEN 1 END) AS'Total Tuesday'
FROM tbl_sched
WHERE teacherID='2014279384'
)a
I'd say the query below is better in terms of performance and highlights the intention better, because basically we are just GROUPing by days and COUNTing the groups, we don't need CASE's or SUM's (in which case SQL Server will have to go over all the records of the selected teacher).
SELECT TOP 1 Day, COUNT(*) AS Total
FROM tbl_sched
WHERE teacherID='2014279384'
AND Day IN ('Monday','Tuesday')
GROUP BY Day
ORDER BY Total DESC
You can just group by Day, sort by COUNT(*) DESC and get the top count:
SELECT TOP (1)
TotalCount = COUNT(*)
FROM
dbo.tbl_sched
WHERE
teacherID = '2014279384'
GROUP BY
Day
ORDER BY
TotalCount DESC
;
You can also include Day into the output to return the day that had the topmost result.
You can achieve this by using Max Function
Select MAX(SUM(CASE WHEN Day='Monday' THEN 1 END)) AS 'Total Monday',
MAX(SUM(CASE WHEN Day='Tuesday' THEN 1 END)) AS 'Total Tuesday'
FROM tbl_sched
WHERE teacherID='2014279384'

How to create an alias for a filtered column in a SQL query

The SQL query below is giving me the desired results, but the use of CASE statements seems incorrect.
Is there another way to create an alias for a filtered column? Is there a more efficient way to achieve the same results?
Here is the current query:
SELECT
year,
college_name,
count(id_999999999) as count_id_999999999,
count(id_999999999)/count(student_id) as percent_id_999999999,
count(id_other) as count_id_other,
count(id_other)/count(student_id) as percent_id_other,
count(student_id) as total_id
FROM (SELECT fiscal_year, semester,
CASE
WHEN student_id IN ('999999999') THEN 'id - 999999999'
END
AS id_999999999,
CASE
WHEN student_id NOT IN ('999999999') THEN 'id - Other'
END
AS id_other,
student_id
FROM enrolment_data) enrol
GROUP BY year, college_name
ORDER BY year, college_name;
And here are the desired results:
The query is overly complicated. You don't need a subquery. I would write the query as:
SELECT year, college_name,
sum(case when student_id IN ('999999999') then 1 else 0 end) as count_id_999999999,
avg(case when student_id IN ('999999999') then 1.0 else 0 end) as percent_id_999999999,
sum(case when student_id NOT IN ('999999999') then 1 else 0 end) as count_id_other,
avg(case when student_id NOT IN ('999999999') then 1.0 else 0 end) as percent_id_other
count(*) as total_id
FROM enrolment_data ed
GROUP BY year, college_name
ORDER BY year, college_name;

change rows to columns and count

how to calculate count based on rows?
SOURCE TABLE
each employee can take 2 days off
Employee-----First_Day_Off-----Second_Day_Off
1------------10/21/2009--------12/6/2009
2------------09/3/2009--------12/6/2009
3------------09/3/2009--------NULL
4
5
.
.
.
Now i need a table that shows the dates and number of people taking off on that day
Date---------First_Day_Off-------Second_Day_Off
10/21/2009---1-------------------0
12/06/2009---1--------------------1
09/3/2009----2--------------------0
Any ideas?
Oracle 9i+, using Subquery Factoring (WITH):
WITH sample AS (
SELECT a.employee,
a.first_day_off AS day_off,
1 AS day_number
FROM YOUR_TABLE a
WHERE a.first_day_off IS NOT NULL
UNION ALL
SELECT b.employee,
b.second_day_off,
2 AS day_number
FROM YOUR_TABLE b
WHERE b.second_day_off IS NOT NULL)
SELECT s.day_off AS date,
SUM(CASE WHEN s.day_number = 1 THEN 1 ELSE 0 END) AS first_day_off,
SUM(CASE WHEN s.day_number = 2 THEN 1 ELSE 0 END) AS second_day_off
FROM sample s
GROUP BY s.day_off
Non Subquery Version
SELECT s.day_off AS date,
SUM(CASE WHEN s.day_number = 1 THEN 1 ELSE 0 END) AS first_day_off,
SUM(CASE WHEN s.day_number = 2 THEN 1 ELSE 0 END) AS second_day_off
FROM (SELECT a.employee,
a.first_day_off AS day_off,
1 AS day_number
FROM YOUR_TABLE a
WHERE a.first_day_off IS NOT NULL
UNION ALL
SELECT b.employee,
b.second_day_off,
2 AS day_number
FROM YOUR_TABLE b
WHERE b.second_day_off IS NOT NULL) s
GROUP BY s.day_off
It is a bit awkward to handle these queries, since you have days off stored in different columns. A better layout would be to have something like
EMPLOYEE_ID DAY_OFF
Then you would have multiple rows if an employee took multiple days off
EMPLOYEE_ID DAY_OFF
1 10/21/2009
1 12/6/2009
2 09/3/2009
2 12/6/2009
3 09/3/2009
...
In that case, you could find out how many days off each person took by using the following query:
SELECT EMPLOYEE_ID, COUNT(*) AS NUM_DAYS_OFF FROM DAYS_OFF_TABLE GROUP BY EMPLOYEE_ID
And the number of people who took days off on each date like this:
SELECT DAY_OFF, COUNT(*) AS NUM_PEOPLE FROM DAYS_OFF_TABLE GROUP BY DAY_OFF
But I digress...
You can try to use an SQL CASE statement to help with this:
SELECT Employee, CASE
WHEN First_Day_Off is NULL AND Second_Day_Off is NULL THEN 0
WHEN First_Day_Off is NOT NULL AND Second_Day_Off is NULL THEN 1
WHEN First_Day_Off is NULL AND Second_Day_Off is NOT NULL THEN 1
ELSE 2
END AS NUM_DAYS_OFF
FROM DAYS_OFF_TABLE
(note that you may need to change around the syntax slightly depending on your database.
Getting dates and number of people who took off on that day might be more complicated.
I don't know if this would work, but you can try it:
SELECT
Date_Off,
COUNT(*) AS Num_People
FROM
(SELECT
First_Day_Off, COUNT(*) AS Num_People FROM DAYS_OFF_TABLE WHERE First_Day_Off IS NOT NULL GROUP BY First_Day_Off
UNION
SELECT Second_Day_Off, COUNT(*) AS Num_People FROM DAYS_OFF_TABLE WHERE Second_Day_Off IS NOT NULL GROUP BY Second_Day_Off)
GROUP BY
Num_People