Writing a request for the number of hired/fired each year - sql

I'm trying to write an SQL request to count the number of Employees hired/fired each year.
I can have each Employee's dates with this select:
SELECT HiredDate, FiredDate FROM Employees;
I can list each year with this select:
SELECT to_char(e1.HiredDate, 'YYYY') Year FROM Employees e1
UNION
SELECT to_char(e2.FiredDate, 'YYYY') Year FROM Employees e2;
But I don't manage to count the number of hired/fired each year.
EDIT
Employees sample data:
Name | HiredDate | FiredDate
--------------------------------
John | 01/02/2003 | 03/04/2013
Jack | 05/06/2006 | 07/08/2013
Jean | 03/04/2006 | null
James | 01/02/2013 | null
Expected results:
Year | HiredNumber | FiredNumber
---------------------------------
2003 | 1 | 0
2006 | 2 | 0
2013 | 1 | 2

There may be years with no hiring and years with no firings. So the easiest way to solve this problem is with two sub-queries, one for each count and join them with a full outer join.
with e1 as (
select extract(year from hireddate) as emp_year
, count(hireddate) as hired_count
from employees
where hireddate is not null
group by extract(year from hireddate)
)
, e2 as (
select extract(year from fireddate) as emp_year
, count(fireddate) as fired_count
from employees
where fireddate is not null
group by extract(year from fireddate)
)
select coalesce (e1.emp_year, e2.emp_year) as emp_year
, nvl(e1.hired_count, 0) as hired_count
, nvl(e2.fired_count, 0) as fired_count
from e1
full outer join e2
on e1.emp_year = e2.emp_year
order by 1
Notes
This will exclude any years with neither hirings nor firings. It's easy enough to generate such things.
Presumably hireddate is mandatory but the not null check is retained for symmetry :)
". It works well in SQL Developer but can't be set as a Visual datasource"
Here is a variant without the FULL OUTER JOIN:
select emp_year
, sum(hired_count) as hired_count
, sum(fired_count) as fired_count
from (
select extract(year from hireddate) as emp_year
, count(hireddate) as hired_count
, 0 as fired_count
from employees
where hireddate is not null
group by extract(year from hireddate)
union all
select extract(year from fireddate) as emp_year
, 0 as hired_count
, count(fireddate) as fired_count
from employees
where fireddate is not null
group by extract(year from fireddate)
)
group by emp_year
order by 1

SELECT 'Hired' What, to_char(e1.HiredDate, 'YYYY') Year, COUNT(*) TheCount
FROM Employees e1
GROUP BY to_char(e1.HiredDate, 'YYYY')
UNION ALL
SELECT 'Fired' What, to_char(e2.FiredDate, 'YYYY') Year, COUNT(*) TheCount
FROM Employees e2
GROUP BY to_char(e2.FiredDate, 'YYYY');

Related

How to duplicate data in sql with conditions

I havea table as table_A . table_A includes these columns
-CountryName
-Min_Date
-Max_Date
-Number
I want to duplicate data with seperating by months. For example
Argentina | 2015-01-04 | 2015-04-07 | 100
England | 2015-02-08 | 2015-03-11 | 90
I want to see a table as this (Monthly seperated)
Argentina | 01-2015 | 27 //(days to end of the min_date's month)
Argentina | 02-2015 | 29 //(days full month)
Argentina | 03-2015 | 31 //(days full month)
Argentina | 04-2015 | 7 //(days from start of the max_date's month)
England | 02-2015 | 21 //(days)
England | 03-2015 | 11 //(days)
I tried too much thing to made this for each records. But now my brain is so confusing and my project is delaying.
Does anybody know how can i solve this. I tried to duplicate each rows with datediff count but it is not working
WITH cte AS (
SELECT CountryName, ISNULL(DATEDIFF(M,Min_Date ,Max_Date )+1,1) as count FROM table_A
UNION ALL
SELECT CountryName, count-1 FROM cte WHERE count>1
)
SELECT CountryName,count FROM cte
-Generate all the dates between min and max dates for each country.
-Then get the month start and month end dates for each country,year,month.
-Finally get the date differences of the month start and month end.
WITH cte AS (
SELECT Country, min_date dt,min_date,max_date FROM t
UNION ALL
SELECT Country, dateadd(dd,1,dt),min_date,max_date FROM cte WHERE dt < max_date
)
,monthends as (
SELECT country,year(dt) yr,month(dt) mth,max(dt) monthend,min(dt) monthstart
FROM cte
GROUP BY country,year(dt),month(dt))
select country
,cast(mth as varchar(2))+'-'+cast(yr as varchar(4)) yr_month
,datediff(dd,monthstart,monthend)+1 days_diff
from monthends
Sample Demo
EDIT: Another option would be to generate all the dates once (the example shown here generates 51 years of dates from 2000 to 2050) and then joining it to the table to get the days by month.
WITH cte AS (
SELECT cast('2000-01-01' as date) dt,cast('2050-12-31' as date) maxdt
UNION ALL
SELECT dateadd(dd,1,dt),maxdt FROM cte WHERE dt < maxdt
)
SELECT country,year(dt) yr,month(dt) mth, datediff(dd,min(dt),max(dt))+1 days_diff
FROM cte c
JOIN t on c.dt BETWEEN t.min_date and t.max_date
GROUP BY country,year(dt),month(dt)
OPTION (MAXRECURSION 0)
I think you have the right idea. But you need to construct the months:
WITH cte AS (
SELECT CountryName, Min_Date as dte, Min_Date, Max_Date
FROM table_A
UNION ALL
SELECT CountryName, DATEADD(month, 1, dte), Min_Date, Max_Date
FROM cte
WHERE dte < Max_date
)
SELECT CountryName, dte
FROM cte;
Getting the number of days in the month is a bit more complicated. That requires some thought.
Oh, I forgot about EOMONTH():
select countryName, dte,
(case when dte = min_date
then datediff(day, min_date, eomonth(dte)) + 1
when dte = max_date
then day(dte)
else day(eomonth(dte))
end) as days
from cte;
Using a Calendar Table makes this stuff pretty easy. RexTester: http://rextester.com/EBTIMG23993
begin
create table #enderaric (
CountryName varchar(16)
, Min_Date date
, Max_Date date
, Number int
)
insert into #enderaric values
('Argentina' ,'2015-01-04' ,'2015-04-07' ,'100')
, ('England' ,'2015-02-08' ,'2015-03-11' ,'90')
end;
-- select * from #enderaric
--*/"
declare #FromDate date;
declare #ThruDate date;
set #FromDate = '2015-01-01';
set #ThruDate = '2015-12-31';
with x as (
select top (cast(sqrt(datediff(day, #FromDate, #ThruDate)) as int) + 1)
[number]
from [master]..spt_values v
)
/* Date Range CTE */
,cal as (
select top (1+datediff(day, #FromDate, #ThruDate))
DateValue = convert(date,dateadd(day,
row_number() over (order by x.number)-1,#FromDate)
)
from x cross join x as y
order by DateValue
)
select
e.CountryName
, YearMonth = convert(char(7),left(convert(varchar(10),DateValue),7))
, [Days]=count(c.DateValue)
from #enderaric as e
inner join cal c on c.DateValue >= e.min_date
and c.DateValue <= e.max_date
group by
e.CountryName
, e.Min_Date
, e.Max_Date
, e.Number
, convert(char(7),left(convert(varchar(10),DateValue),7))
results in:
CountryName YearMonth Days
---------------- --------- -----------
Argentina 2015-01 28
Argentina 2015-02 28
Argentina 2015-03 31
Argentina 2015-04 7
England 2015-02 21
England 2015-03 11
More about calendar tables:
Aaron Bertrand - Generate a set or sequence without loops
generate-a-set-1
generate-a-set-2
generate-a-set-3
David Stein - Creating a Date Table/Dimension on SQL 2008
Michael Valentine Jones - F_TABLE_DATE

Querying for an ID that has the most number of reads

Suppose I have a table like the one below:
+----+-----------+
| ID | TIME |
+----+-----------+
| 1 | 12-MAR-15 |
| 2 | 23-APR-14 |
| 2 | 01-DEC-14 |
| 1 | 01-DEC-15 |
| 3 | 05-NOV-15 |
+----+-----------+
What I want to do is for each year ( the year is defined as DATE), list the ID that has the highest count in that year. So for example, ID 1 occurs the most in 2015, ID 2 occurs the most in 2014, etc.
What I have for a query is:
SELECT EXTRACT(year from time) "YEAR", COUNT(ID) "ID"
FROM table
GROUP BY EXTRACT(year from time)
ORDER BY COUNT(ID) DESC;
But this query just counts how many times a year occurs, how do I fix it to highest count of an ID in that year?
Output:
+------+----+
| YEAR | ID |
+------+----+
| 2015 | 2 |
| 2012 | 2 |
+------+----+
Expected Output:
+------+----+
| YEAR | ID |
+------+----+
| 2015 | 1 |
| 2014 | 2 |
+------+----+
Starting with your sample query, the first change is simply to group by the ID as well as by the year.
SELECT EXTRACT(year from time) "YEAR" , id, COUNT(*) "TOTAL"
FROM table
GROUP BY EXTRACT(year from time), id
ORDER BY EXTRACT(year from time) DESC, COUNT(*) DESC
With that, you could find the rows you want by visual inspection (the first row for each year is the ID with the most rows).
To have the query just return the rows with the highest totals, there are several different ways to do it. You need to consider what you want to do if there are ties - do you want to see all IDs tied for highest in a year, or just an arbitrary one?
Here is one approach - if there is a tie, this should return just the lowest of the tied IDs:
WITH groups AS (
SELECT EXTRACT(year from time) "YEAR" , id, COUNT(*) "TOTAL"
FROM table
GROUP BY EXTRACT(year from time), id
)
SELECT year, MIN(id) KEEP (DENSE_RANK FIRST ORDER BY total DESC)
FROM groups
GROUP BY year
ORDER BY year DESC
You need to count per id and then apply a RANK on that count:
SELECT *
FROM
(
SELECT EXTRACT(year from time) "YEAR" , ID, COUNT(*) AS cnt
, RANK() OVER (PARTITION BY "YEAR" ORDER BY COUNT(*) DESC) AS rnk
FROM table
GROUP BY EXTRACT(year from time), ID
) dt
WHERE rnk = 1
If this return multiple rows with the same high count per year and you want just one of them randomly, you can switch to a ROW_NUMBER.
This should do what you're after, I think:
with sample_data as (select 1 id, to_date('12/03/2015', 'dd/mm/yyyy') time from dual union all
select 2 id, to_date('23/04/2014', 'dd/mm/yyyy') time from dual union all
select 2 id, to_date('01/12/2014', 'dd/mm/yyyy') time from dual union all
select 1 id, to_date('01/12/2015', 'dd/mm/yyyy') time from dual union all
select 3 id, to_date('05/11/2015', 'dd/mm/yyyy') time from dual)
-- End of creating a subquery to mimick a table called "sample_data" containing your input data.
-- See SQL below:
select yr,
id most_frequent_id,
cnt_id_yr cnt_of_most_freq_id
from (select to_char(time, 'yyyy') yr,
id,
count(*) cnt_id_yr,
dense_rank() over (partition by to_char(time, 'yyyy') order by count(*) desc) dr
from sample_data
group by to_char(time, 'yyyy'),
id)
where dr = 1;
YR MOST_FREQUENT_ID CNT_OF_MOST_FREQ_ID
---- ---------------- -------------------
2014 2 2
2015 1 2

Oracle Database sql query. Having?

Show the name of all the employees who were hired on the day of the week on which the highest number of employees were hired.
Table:
Steven 06/17/1987
Neena 09/21/1989
Lex 01/13/1993
Alex 01/03/1990
Bruce 05/21/1991
Diana 02/07/1999
Kevin 11/16/1999
Trenna 10/17/1995
Curtis 01/29/1997
Randall 03/15/1998
Peter 07/09/1998
Eleni 01/29/2000
Ellen 05/11/1996
Jonath 03/24/1998
Kimber 05/24/1999
Jenni 09/17/1987
Michael 02/17/1996
Pat 08/17/1997
Shelley 06/07/1994
William 06/07/1994
What I have so far.
SELECT FIRST_NAME, to_char(hire_date,'d') AS DOW FROM EMPLOYEES;
Steven 4
Neena 5
Lex 4
Alex 4
Bruce 3
Diana 1
Kevin 3
Trenna 3
Curtis 4
Randall 1
Peter 5
Eleni 7
Ellen 7
Jonath 3
Kimbe 2
Jenni 5
Michael 7
Pat 1
Shelley 3
William 3
Sunday is 1, monday is 2, ... so on...
Now i need to select the one with the max repeating number.
Which by looking at the table we will know it's 3 (tuesday). I know i will need to use a subquery to get it, is it having?
I would be inclined to use analytic functions for this:
select e.*
from (SELECT to_char(hire_date, 'd') AS DOW, count(*) as cnt,
row_number() over (order by count(*) desc) as seqnum
FROM EMPLOYEES
) dow join
EMPLOYEEs e
on dow.DOW = to_char(e.hire_date, 'd') and seqnum = 1;
One way, extending your query above (SQL Fiddle Example):
SELECT FIRST_NAME, to_char("hire_date", 'd') AS DOW
FROM EMPLOYEES
WHERE to_char("hire_date", 'd') =
(
SELECT b.DOW
FROM
(
select a.*, ROWNUM rnum
from (
SELECT to_char("hire_date", 'd') AS DOW, COUNT(1) AS cnt
FROM EMPLOYEES
GROUP BY to_char("hire_date", 'd')
ORDER BY cnt DESC
) a
where rownum = 1
) b
)
select *
from employees
where to_char(hire_date, 'd') = (
select max(to_char(hire_date, 'd')) keep (dense_rank last order by count(*))
from employees
group by to_char(hire_date, 'd')
);
SQLFiddle

MySQL AVG() in sub-query

What one query can produce table_c?
I have three columns: day, person, and revenue_per_person. Right now I have to use two queries since I lose 'person' when producing table_b.
table_a uses all three columns:
SELECT day, person, revenue_per_person
FROM purchase_table
GROUP BY day, person
table_b uses only two columns due to AVG() and GROUP BY:
SELECT day, AVG(revenue) as avg_revenue
FROM purchase_table
GROUP BY day
table_c created from table_a and table_b:
SELECT
CASE
WHEN revenue_per_person > avg_revenue THEN 'big spender'
ELSE 'small spender'
END as spending_bucket
FROM ????
Maybe this could help, try this one
SELECT a.day,
CASE
WHEN a.revenue_per_person > b.avg_revenue THEN 'big spender'
ELSE 'small spender'
END as spending_bucket
FROM
(
SELECT day, person, AVG(revenue) revenue_per_person
FROM purchase_table
GROUP BY day, person
) a INNER JOIN
(
SELECT day, AVG(revenue) as avg_revenue
FROM purchase_table
GROUP BY day
) b ON a.day = b.day
You might want to use analytic functions.
An Oracle example showing if a person's salary is greater than average salary in his department.
08:56:54 HR#vm_xe> ed
Wrote file s:\toolkit\service\buffer.sql
1 select
2 department_id
3 ,employee_id
4 ,salary
5 ,avg_salary
6 ,case when salary > avg_salary then 1 else 0 end case_is_greater
7 from (
8 select
9 department_id
10 ,employee_id
11 ,salary
12 ,round(avg(salary) over(partition by department_id),2) avg_salary
13 from employees
14 )
15* where department_id = 30
08:58:56 HR#vm_xe> /
DEPARTMENT_ID EMPLOYEE_ID SALARY AVG_SALARY CASE_IS_GREATER
------------- ----------- ---------- ---------- ---------------
30 114 11000 4150 1
30 115 3100 4150 0
30 116 2900 4150 0
30 117 2800 4150 0
30 118 2600 4150 0
30 119 2500 4150 0
6 rows selected.
Elapsed: 00:00:00.01
If you are using a database that supports windows functions, you can do this as:
SELECT (CASE WHEN revenue_per_person > avg_revenue THEN 'big spender'
ELSE 'small spender'
END) as spending_bucket
FROM (select pt.*,
avg(revenue) over (partition by day, person) as revenue_per_person,
avg(revenue) over (partition by day) as avg_revenue,
row_number() over (partition by day, person order by day) as seqnum
from purchase_table pt
) t
where seqnum = 1
The purpose of seqnum is to just get one row per person/day combination.

SQL server full join query

I have a full join sql query and i am retrieving the data from the same table.the problem is i am getting the null value where i am expecting the column name.
Example:
I am having a table where there are two columns typeOfPost,dob.
DOB TypeOfPost
--------- --------------
20/11/1998 Manager
1/1/2000 Sales
13/6/1999 Manager
20/1/1987 Manager
1/11/1985 Sales
Now when I am writing a join query like
select DATENAME(month,dob) as Red,count(TypeOfPost)
from tablename
where TypeOfPost='Manager'
group by DATENAME(month,dob) as A
full join
select DATENAME(month,dob) as Green,count(TypeOfPost)
from tablename
where TypeOfPost='Sales'
group by DATENAME(month,dob) as B on B.Green = A.Red
Output-- Expected Output--
--------------------- ---------------------
Month Man Sal Month Man Sal
-------- ----- ------ -------- ----- ------
January 1 1 January 1 1
NULL 1 NULL June 1 NULL
November 1 1 November 1 1
Now here the problem rise, I want 'June' in the column Month instead of NULL value.
So is there any way to get that??
Help me out.
Thanks.
One option is to
use a CASE statement in a subselect
Determine for given record if it is a manager or sales
substitute with 1 or 0 accordingly
SELECT and GROUP from this subselect the final results.
SQL Statement
SELECT Month
, SUM(Man) AS Man
, SUM(Sal) AS Sal
FROM (
SELECT DATENAME(MONTH, DOB) AS Month
, CASE WHEN TypeOfPost = 'Manager' THEN 1 ELSE 0 END AS Man
, CASE WHEN TypeOfPost = 'Sales' THEN 1 ELSE 0 END AS Sal
FROM tableName
) g
GROUP BY
Month
or
SELECT Month
, SUM(Man)
, SUM(Sal)
FROM (
SELECT DATENAME(MONTH, DOB) AS Month
, COUNT(TypeOfPost) AS Man
, 0 AS Sal
FROM tableName
WHERE TypeOfPost = 'Manager'
GROUP BY
DATENAME(MONTH, DOB)
UNION ALL
SELECT DATENAME(MONTH, DOB) AS Month
, 0 AS Man
, COUNT(TypeOfPost) AS Sal
FROM tableName
WHERE TypeOfPost = 'Sales'
GROUP BY
DATENAME(MONTH, DOB)
) g
GROUP BY
Month