I am trying to find the employee hire_date (day of week) where the most employees were hired. In my test CASE below the answer should be Tuesday.
As you can see I can list all the days but I'm having a problem narrowing down the result to 1 row.
Any help would be greatly appreciated. I listed my failed attempt. If there is a more efficient way to rewrite the query I would prefer any input.
CREATE TABLE employees (employee_id, first_name, last_name, hire_date) AS
SELECT 1, 'Lisa', 'Saladino', DATE '2001-04-03' FROM DUAL UNION ALL
SELECT 2, 'Abby', 'Abbott', DATE '2001-04-04' FROM DUAL UNION ALL
SELECT 3, 'Beth', 'Cooper', DATE '2001-04-05' FROM DUAL UNION ALL
SELECT 4, 'Carol', 'Orr', DATE '2001-04-06' FROM DUAL UNION ALL
SELECT 5, 'Nancy', 'Turner', DATE '2001-04-07' FROM DUAL UNION ALL
SELECT 6, 'Cheryl', 'Ford', DATE '2001-04-08' FROM DUAL UNION ALL
SELECT 7, 'Leslee', 'Gold', DATE '2001-04-10' FROM DUAL UNION ALL
SELECT 8, 'Jill', 'Coralnick', DATE '2001-04-11' FROM DUAL UNION ALL
SELECT 9, 'Faith', 'Aaron', DATE '2001-04-17' FROM DUAL;
SELECT TO_CHAR(HIRE_DATE,'DAY') DAY, count(*) cnt FROM EMPLOYEES GROUP BY TO_CHAR(HIRE_DATE,'DAY')
DAY CNT
TUESDAY 3
FRIDAY 1
SUNDAY 1
SATURDAY 1
WEDNESDAY 2
THURSDAY 1
/* not working */
SELECT e.*
FROM EMPLOYEES e
INNER JOIN
(SELECT employee_id, TO_CHAR(HIRE_DATE,'DAY') DAY
FROM EMPLOYEES
GROUP BY TO_CHAR(HIRE_DATE,'DAY')
HAVING COUNT(1)=(SELECT MAX(COUNT(1))FROM EMPLOYEES GROUP BY TO_CHAR(HIRE_DATE,'DAY'))) AS empdays
ON TO_CHAR(e.HIRE_DATE, 'DAY') = empdays.DAY;
You neither need a subquery or nor a join to use if the DB's version is 12c+, but just use the FETCH clause following ORDER BY in order to sort the counts descendingly such as
SELECT TO_CHAR(hire_date, 'DAY') AS day, COUNT(*) AS cnt
FROM employees
GROUP BY TO_CHAR(hire_date, 'DAY')
ORDER BY cnt DESC
FETCH FIRST 1 ROW WITH TIES
This works for me:
select DAY, cnt
from (SELECT TO_CHAR(HIRE_DATE,'DAY') DAY
,count(*) cnt
FROM EMPLOYEES
GROUP BY TO_CHAR(HIRE_DATE,'DAY'))
where cnt = (select max(cnt)
from (SELECT TO_CHAR(HIRE_DATE,'DAY') DAY
,count(*) cnt
FROM EMPLOYEES
GROUP BY TO_CHAR(HIRE_DATE,'DAY')))
Produces following results
DAY
CNT
Tuesday
3
Refer to this db<>fiddle
In a comment, you asked for dense_rank example; here it is:
SQL> with temp as
2 (select to_char(hire_date, 'Day') day,
3 count(*) cnt,
4 dense_rank() over (order by count(*) desc) rnk
5 from employees
6 group by to_char(hire_date, 'Day')
7 )
8 select day, cnt
9 from temp
10 where rnk = 1;
DAY CNT
------------------------------------ ----------
Tuesday 3
SQL>
Related
I have a problem with my employee_det table, where I am categorizing year wise active employee status.
for example1: an employee joined in 01-01-2017 and released from company in 02-02-2018 then he/she fall under 2017 bucket.
example2: If an employee joined in 01-02-2018 and released in 01-15-2019 then he will be under 2018 bucket.
if an employee joined in 01-01-2017 and he is still continuing in company then he must fall under 2019.
I have written the following query and which is giving me accurate results, but next year I need to add one more entry in WHERE condition, instead of that is there is any generalized way to solve this.
select emp_id, ename, year(effective_start_date) as year_bucket
from employee_det
where worker_status = 'Active'
and manager_name like '%srinivas%'
and (
( date(effective_start_date) <= '2017-12-31'
and date(effective_end_date)>='2017-12-31' )
or
( date(effective_start_date) <= '2018-12-31'
and date(effective_end_date)>='2018-12-31' )
or
( date(effective_start_date) <= current_date()
and date(effective_end_date)>=current_date()
)
You seem to want the start year for employees who have ended and the current year for active employees. So:
select emp_id, ename,
(case when effective_end_date > current_date
then year(current_date)
else year(effective_start_date)
end) as year_bucket
from employee_det
where worker_status = 'Active' and
manager_name like '%srinivas%';
Below is for BigQuery Standard SQL
#standardSQL
SELECT emp_id, ename,
EXTRACT(YEAR FROM IF(effective_end_date >= CURRENT_DATE, CURRENT_DATE, effective_start_date)) year_bucket
FROM `project.dataset.employee_det`
WHERE worker_status = 'Active'
AND manager_name LIKE '%srinivas%'
You can test, play with above using dummy data as in example below
#standardSQL
WITH `project.dataset.employee_det` AS (
SELECT 1 emp_id, 'employee1' ename, DATE '2017-01-01' effective_start_date, DATE '2018-02-02' effective_end_date, 'Active' worker_status, 'srinivas' manager_name UNION ALL
SELECT 2, 'employee2', '2018-01-02', '2019-01-15', 'Active', 'srinivas' UNION ALL
SELECT 3, 'employee3', '2017-01-01', '2019-04-15', 'Active', 'srinivas'
)
SELECT emp_id, ename,
EXTRACT(YEAR FROM IF(effective_end_date >= CURRENT_DATE, CURRENT_DATE, effective_start_date)) year_bucket
FROM `project.dataset.employee_det`
WHERE worker_status = 'Active'
AND manager_name LIKE '%srinivas%'
with result
Row emp_id ename year_bucket
1 1 employee1 2017
2 2 employee2 2018
3 3 employee3 2019
Update - excluding employees whose start and end YEAR is the same
You can just use one "generic" clause as below
WHERE EXTRACT(YEAR FROM effective_start_date) != EXTRACT(YEAR FROM effective_end_date)
so, the whole query now will be as in below example
#standardSQL
WITH `project.dataset.employee_det` AS (
SELECT 1 emp_id, 'employee1' ename, DATE '2017-01-01' effective_start_date, DATE '2018-02-02' effective_end_date, 'Active' worker_status, 'srinivas' manager_name UNION ALL
SELECT 2, 'employee2', '2018-01-02', '2019-01-15', 'Active', 'srinivas' UNION ALL
SELECT 3, 'employee3', '2017-01-01', '2019-04-15', 'Active', 'srinivas' UNION ALL
SELECT 4, 'employee4', '2017-01-01', '2017-04-15', 'Active', 'srinivas'
)
SELECT emp_id, ename,
EXTRACT(YEAR FROM IF(effective_end_date >= CURRENT_DATE, CURRENT_DATE, effective_start_date)) year_bucket
FROM `project.dataset.employee_det`
WHERE worker_status = 'Active'
AND manager_name LIKE '%srinivas%'
AND EXTRACT(YEAR FROM effective_start_date) != EXTRACT(YEAR FROM effective_end_date)
with result
Row emp_id ename year_bucket
1 1 employee1 2017
2 2 employee2 2018
3 3 employee3 2019
as you can see - employee4 is not included in any bucket
I'm trying to get a YTD count for each of unique employees who have had any revenue in the current or preceding months
Table1
Month Employee Revenue
01-04-18 A 867
01-04-18 B
01-04-18 C
01-04-18 D
01-05-18 A 881
01-05-18 B
01-05-18 C 712
01-05-18 D
01-06-18 A 529
01-06-18 B 456
01-06-18 C
01-06-18 D 878
Expected Output
Month Count
01-04-18 1
01-05-18 2
01-06-18 4
In the 1st month only A had any revenue so the count is 1, in the 2nd month A & C had revenue till date so the count is 2 and finally in the 3rd month A, B, C & D have had revenue in the current or preceding months (C had revenue in month 2 but not month 3) so the count is 4.
Is there any way to get this result?
Thank you for your help
This is tricky, because you have an aggregation and a window function. I would go for the approach of marking the first month where a use has revenue and then using that information:
select month,
sum(sum(case when seqnum = 1 and revenue is not null then 1 else 0 end)) over (order by month)
from (select t.*,
row_number() over (partition by employee order by (case when revenue is not null then month end) nulls last) as seqnum
from t
) t
group by month;
The row_number() is enumerating the months for each employee putting the ones with revenue first. So, if there is a month with revenue, it goes first.
The outer aggregation then does a cumulative sum check both for the sequence and whether the revenue is not null.
I'd take a slightly different approach, still using an aggregate of an analytic function inside an inline view, but sticking to count() as I think the intent is slightly cleaeer:
select month,
count(has_revenue) as result
from (
select month, employee,
case when count(revenue)
over (partition by employee order by month) > 0
then employee end as has_revenue
from table1
)
group by month
For the inline view, the analytic count for each month/employee uses the default window of unbounded preceding to current row, so it ignore any rows in future months; and only gives a not-null response if that count is non-zero. The outer count ignore nulls in that generated column expression.
Demo with your sample data in a CTE:
with table1 (month, employee, revenue) as (
select date '2018-04-01', 'A', 867 from dual
union all select date '2018-04-01', 'B', null from dual
union all select date '2018-04-01', 'C', null from dual
union all select date '2018-04-01', 'D', null from dual
union all select date '2018-05-01', 'A', 881 from dual
union all select date '2018-05-01', 'B', null from dual
union all select date '2018-05-01', 'C', 712 from dual
union all select date '2018-05-01', 'D', null from dual
union all select date '2018-06-01', 'A', 529 from dual
union all select date '2018-06-01', 'B', 456 from dual
union all select date '2018-06-01', 'C', null from dual
union all select date '2018-06-01', 'D', 878 from dual
)
select month,
count(has_revenue) as result
from (
select month, employee,
case when count(revenue)
over (partition by employee order by month) > 0
then employee end as has_revenue
from table1
)
group by month
order by month;
MONTH RESULT
---------- ----------
2018-04-01 1
2018-05-01 2
2018-06-01 4
This is cumulative over all rows in your data set, but you only showed data from one year. If your data has multiple years, and you aren't filtering to a single year already, then add the year into the partitioning:
select month, employee,
case when count(revenue)
over (partition by employee, trunc(month, 'YYYY') order by month) > 0
then employee end as has_revenue
from table1
In this case I'd use a compound table expression to pull the distinct months from your table, then use COUNT(DISTINCT to count the distinct employees, using the appropriate join criteria. Or, in other words:
WITH cteMonths AS (SELECT DISTINCT MONTH
FROM TABLE1)
SELECT m.MONTH, COUNT(DISTINCT t1.EMPLOYEE)
FROM cteMonths m
INNER JOIN TABLE1 t1
ON t1.MONTH <= m.MONTH AND
t1.REVENUE IS NOT NULL
GROUP BY m.MONTH
ORDER BY m.MONTH;
SQLFiddle here
Best of luck.
I have below data
empid date amount
1 12-FEB-2017 10
1 12-FEB-2017 10
1 13-FEB-2017 10
1 14-FEB-2017 10
I need a query to return the total amount for a given id and date i.e, below result set
empid date amount
1 12-FEB-2017 20
1 13-FEB-2017 10
1 14-FEB-2017 10
but the think is, from the UI i will be getting the date as input.. if they pass the date return the result for that date .. if they dont pass the date return the result for most recent date.
below is the query that I wrote .. but it is working partially..
SELECT sum(amount),empid,date
FROM employee emp,
where
((date= :ddd) OR aum_valutn_dt = (select max(date) from emp))
AND emp.id = '1'
group by (empid,date)
Please help..
I think you could do something like this
but it is pretty bad you should try to do it some other way
it is doing extra work to get the most recent date
select amt, empid, date
from
(
select amt, empid, date, rank() over (order by date desc) date_rank
from
(SELECT sum(amount) amt,empid,date
FROM employee emp
where emp.id = '1'
and (date = :ddd or :ddd is null)
group by empid, date)
)
where date = :ddd or (:ddd is null and date_rank=1)
Here's another option; scans TEST table twice so ... mind the performance.
SQL> with test (empid, datum, amount) as
2 (select 1, date '2017-02-12', 10 from dual union all
3 select 1, date '2017-02-12', 10 from dual union all
4 select 1, date '2017-02-13', 10 from dual union all
5 select 1, date '2017-02-14', 10 from dual
6 )
7 select t.empid, t.datum, sum(t.amount) sum_amount
8 from test t
9 where t.datum = (select max(t1.datum)
10 from test t1
11 where t1.empid = t.empid
12 and (t1.datum = to_date('&&par_datum', 'dd.mm.yyyy')
13 or '&&par_datum' is null)
14 )
15 group by t.empid, t.datum;
Enter value for par_datum: 13.02.2017
EMPID DATUM SUM_AMOUNT
---------- ---------- ----------
1 13.02.2017 10
SQL> undefine par_datum
SQL> /
Enter value for par_datum:
EMPID DATUM SUM_AMOUNT
---------- ---------- ----------
1 14.02.2017 10
SQL>
SELECT sum(amount),empid,date
FROM employee emp,
where date =nvl((:ddd ,(select max(date) from emp))
AND emp.id = '1'
group by (empid,date)
My solution is following:
with t (empid, datum, amount) as
(select 1, date '2017-02-12', 10 from dual union all
select 1, date '2017-02-12', 10 from dual union all
select 1, date '2017-02-13', 10 from dual union all
select 1, date '2017-02-14', 10 from dual
)
select empid, datum, s
from (select empid, datum, sum(amount) s, max(datum) over (partition by empid) md
from t
group by empid, datum)
where datum = nvl(to_date(:p, 'yyyy-mm-dd'), md);
Calculate maximal date in the subquery and then, in outer subquery, compare the date with nvl(to_date(:p, 'yyyy-mm-dd'), md). If the paremeter is null, then the date field is compared with maximal date.
I have a table that looks like this:
+--------------------+---------+
| Month (date) | amount |
+--------------------+---------+
| 2016-10-01 | 20 |
| 2016-08-01 | 10 |
| 2016-07-01 | 17 |
+--------------------+---------+
I'm looking for a query (sql statement) which satisfies the following conditions:
Give me the value of the previous month.
If there is no value for the previous month lock back in time until one can be found.
If there is just a value for the current month give me this value.
In the example table the row I'm looking for would be this:
+--------------------+---------+
| 2016-08-01 | 10 |
+--------------------+---------+
Has anyone a idea for a non complex select query?
Thanks in advance,
Peter
You may need the following:
SELECT *
FROM ( SELECT *
FROM test
WHERE TRUNC(SYSDATE, 'month') >= month
ORDER BY CASE
WHEN TRUNC(SYSDATE, 'month') = month
THEN 0 /* if current month, ordered last */
ELSE 1 /* previous months are ordered first */
END DESC,
month DESC /* among previous months, the greatest first */
)
WHERE ROWNUM = 1
Another way using MAX
WITH tbl AS (
SELECT TO_DATE('2016-10-01', 'YYYY-MM-DD') AS "month", 20 AS amount FROM dual
UNION
SELECT TO_DATE('2016-08-01', 'YYYY-MM-DD') AS "month", 10 AS amount FROM dual
UNION
SELECT TO_DATE('2016-07-01', 'YYYY-MM-DD') AS "month", 5 AS amount FROM dual
)
SELECT *
FROM tbl
WHERE TRUNC("month", 'MONTH') = NVL((SELECT MAX(t."month")
FROM tbl t
WHERE t."month" < TRUNC(SYSDATE, 'MONTH')),
TRUNC(SYSDATE, 'MONTH'));
I would use row_number():
select t.*
from (select t.*,
row_number() over (order by (case when to_char(dte, 'YYYY-MM') = to_char(sysdate, 'YYYY-MM') then 1 else 2 end) desc,
dte desc
) as seqnum
from t
) t
where seqnum = 1;
Actually, you don't need row_number() for this:
select t.*
from (select t.*
from t
order by (case when to_char(dte, 'YYYY-MM') = to_char(sysdate, 'YYYY-MM') then 1 else 2 end) desc,
dte desc
) t
where rownum = 1;
It's not the nicest query but it should work.
select amount, date from (
select amount, date, row_number over(partition by HERE_PUT_ID order by
case trunc(date, 'month') when trunc(sysdate, 'month') then to_date('00010101', 'yyyymmdd') else trunc(date, 'month') end
desc) r)
where r = 1;
I guess you have some id in table so put id column instead of HERE_PUT_ID if you want query for whole table just delete: partition by HERE_PUT_ID
I added more data for testing, and an "id" column (a more realistic scenario) to show how this would work. If there is no "id" in your data, simply delete any reference to it from the solution.
Notes - month is a reserved Oracle word, don't use it as a column name. The solution assumes the date column contains dates that are already truncated to the beginning of the month. The trick in "order by" in the dense_rank last is to assign a value (ANY value!) when the month is the current month; by default, the value assigned to all other months is NULL, which by default come after any non-null value in an ascending order.
You may want to test the various solutions for efficiency if execution time is important.
with
inputs ( id, mth, amount ) as (
select 1, date '2016-10-01', 20 from dual union all
select 1, date '2016-08-01', 10 from dual union all
select 1, date '2016-07-01', 17 from dual union all
select 2, date '2016-10-01', 30 from dual union all
select 2, date '2016-09-01', 25 from dual union all
select 3, date '2016-10-01', 20 from dual union all
select 4, date '2016-08-01', 45 from dual union all
select 4, date '2016-06-01', 30 from dual
)
-- end of TEST DATA - the solution (SQL query) is below this line
select id,
max(mth) keep(dense_rank last order by
case when mth = trunc(sysdate, 'mm') then 0 end, mth) as mth,
max(amount) keep(dense_rank last order by
case when mth = trunc(sysdate, 'mm') then 0 end, mth) as amount
from inputs
group by id
order by id -- ORDER BY is optional
;
ID MTH AMOUNT
--- ---------- -------
1 2016-08-01 10
2 2016-09-01 25
3 2016-10-01 20
4 2016-08-01 45
You could sort the data in the direction you want to:
with MyData as
(
SELECT to_date('2016-10-01','YYYY-MM-DD') MY_DATE, 20 AMOUNT FROM DUAL UNION
SELECT to_date('2016-08-01','YYYY-MM-DD') MY_DATE, 10 AMOUNT FROM DUAL UNION
SELECT to_date('2016-07-01','YYYY-MM-DD') MY_DATE, 17 AMOUNT FROM DUAL
),
MyResult AS (
SELECT
D.*
FROM MyData D
ORDER BY
DECODE(
12*TO_CHAR(MY_DATE,'YYYY') + TO_CHAR(MY_DATE,'MM'),
12*TO_CHAR(SYSDATE,'YYYY') + TO_CHAR(SYSDATE,'MM'),
-1,
12*TO_CHAR(MY_DATE,'YYYY') + TO_CHAR(MY_DATE,'MM'))
DESC
)
SELECT * FROM MyResult WHERE RowNum = 1
Say, I have the following data:
select 1 id, date '2007-01-16' date_created, 5 sales, 'Bob' name from dual union all
select 2 id, date '2007-04-16' date_created, 2 sales, 'Bob' name from dual union all
select 3 id, date '2007-05-16' date_created, 6 sales, 'Bob' name from dual union all
select 4 id, date '2007-05-21' date_created, 4 sales, 'Bob' name from dual union all
select 5 id, date '2013-07-16' date_created, 24 sales, 'Bob' name from dual union all
select 6 id, date '2007-01-17' date_created, 15 sales, 'Ann' name from dual union all
select 7 id, date '2007-04-17' date_created, 12 sales, 'Ann' name from dual union all
select 8 id, date '2007-05-17' date_created, 16 sales, 'Ann' name from dual union all
select 9 id, date '2007-05-22' date_created, 14 sales, 'Ann' name from dual union all
select 10 id, date '2013-07-17' date_created, 34 sales, 'Ann' name from dual
I want to get results like the following:
Name Total_cumulative_sales Total_sales_current_month
Bob 41 24
Ann 91 34
In this table, for Bob, his total sales is 41 starting from the beginning. And for this month which is July, his sales for this entire month is 24. Same goes for Ann.
How do I write an SQL to get this result?
Try this way:
select name, sum(sales) as Total_cumulative_sales ,
sum(
case trunc(to_date(date_created), 'MM')
when trunc(sysdate, 'MM') then sales
else 0
end
) as Total_sales_current_month
from tab
group by name
SQL Fiddle Demo
More information
Trunc
Case Statement
SELECT Name,
SUM(Sales) Total_sales,
SUM(CASE WHEN MONTH(date_created) = MONTH(GetDate()) AND YEAR(date_created) = YEAR(GetDate()) THEN Sales END) Total_sales_current_month
GROUP BY Name
Should work, but there's probably a more elegant way to specify "in the current month".
This should work for sales over a number of years. It will get the cumulative sales over any number of years. It won't produce a record if there are no sales in the latest month.
WITH sales AS
(select 1 id, date '2007-01-16' date_created, 5 sales, 'Bob' sales_name from dual union all
select 2 id, date '2007-04-16' date_created, 2 sales, 'Bob' sales_name from dual union all
select 3 id, date '2007-05-16' date_created, 6 sales, 'Bob' sales_name from dual union all
select 4 id, date '2007-05-21' date_created, 4 sales, 'Bob' sales_name from dual union all
select 5 id, date '2013-07-16' date_created, 24 sales, 'Bob' sales_name from dual union all
select 6 id, date '2007-01-17' date_created, 15 sales, 'Ann' sales_name from dual union all
select 7 id, date '2007-04-17' date_created, 12 sales, 'Ann' sales_name from dual union all
select 8 id, date '2007-05-17' date_created, 16 sales, 'Ann' sales_name from dual union all
select 9 id, date '2007-05-22' date_created, 14 sales, 'Ann' sales_name from dual union all
select 10 id, date '2013-07-17' date_created, 34 sales, 'Ann' sales_name from dual)
SELECT sales_name
,total_sales
,monthly_sales
,mon
FROM (SELECT sales_name
,SUM(sales) OVER (PARTITION BY sales_name ORDER BY mon) total_sales
,SUM(sales) OVER (PARTITION BY sales_name,mon ORDER BY mon) monthly_sales
,mon
,max_mon
FROM ( SELECT sales_name
,sum(sales) sales
,mon
,max_mon
FROM (SELECT sales_name
,to_number(to_char(date_created,'YYYYMM')) mon
,sales
,MAX(to_number(to_char(date_created,'YYYYMM'))) OVER (PARTITION BY sales_name) max_mon
FROM sales
ORDER BY 2)
GROUP BY sales_name
,max_mon
,mon
)
)
WHERE max_mon = mon
;