Access SQL count number of people group by week number - sql

I need to count how many people are working given a week number.
Here's my people table (date is US format) :
name | surname| date_of_entry | date_of_exit
-----|--------|---------------|-------------
foo | bar | 1/1/2006 | 1/8/2006
foo1 | bar1 | 1/5/2010 |
foo2 | bar2 | 2/3/2015 | 3/4/2015
and I'd like for a given year to have all weeks number with the proper number of people working during this period.I hope you understand me because english is not my native language sorry.
I've done some research and from what i understand i need to create a table with all weeks starting from 1/1/2006 and ending "now" (because people with no exit date are still working) according to the example above to be able to test for each person if he was working during this week so I can count him in my query.
I'm still a student programmer but it seems to be a pretty complex SQL query to me.
Expected output for a query for year 2006 (with the new year starting a monday) :
week_number | count
------------|------
1 | 1
2 | 1
3 | 0
etc.. | 0
until 1/5/2010 where all weeks have 1 in the field "count"
and then query for year 2015 :
week_number | count
------------|------
1 | 1
2 | 1
... | 1
9 | 2
... | 2
14 | 1
... | 1
If anybody can help me to resolve this it would be awesome, thanks!

You are lucky I have to change my mind for 15 minutes.
Step 1 : Create Calendar table
Create a new table named Calendar with the following fields
id : autonumber
Cal_Year : number
Cal_Week : number
In a module, add the following code and execute it (F5) :
Private Sub Create_Calendar_table()
Dim Y As Integer
Dim W As Integer
For Y = 2006 To 2016
For W = 1 To 52
DoCmd.RunSQL "INSERT INTO Calendar (cal_year, cal_week) VALUES (" & Y & "," & W & ")"
Next W
Next Y
End Sub
Calendar table is now ready to use :
ID Cal_year Cal_week
1 2006 1
2 2006 2
3 2006 3
4 2006 4
5 2006 5
and so on...
Step 2 : Create the query
Note that I am in Europe so my dates are DD/MM. This won't affect your results.
I decompose so you understand the process.
First we need to create a date from the year/week in the calendar table, this can be achieved like this
SELECT Cal_year, Cal_week, DateAdd("ww",Cal_week,DateSerial(Cal_year,1,1)) AS thedate
FROM Calendar
Cal_year Cal_week thedate
2006 1 8/01/2006
2006 2 15/01/2006
2006 3 22/01/2006
2006 4 29/01/2006
2006 5 5/02/2006
and so on...
Next, since we will work on ranges of dates, it is important a to attribute the current date when the people's exit_date is NULL, like this :
nz(date_of_exit, Now)
The field is prepared.
Then, the trick.
We need to JOIN our calendar table with the people table in manner that will return a record for every week on which a person is present.
The key to achieve this is the ON...BETWEEN...AND
SELECT C.Cal_year, C.Cal_Week, P.pname, P.psurname, P.date_of_entry, nz(P.date_of_exit, Now) AS exit_date
FROM [Calendar] C
INNER JOIN ( SELECT [Name] AS pname, [surname] as psurname, date_of_entry, date_of_exit
FROM people
) P
ON (DateAdd("ww",C.Cal_week,DateSerial(C.Cal_year,1,1)) BETWEEN P.date_of_entry AND nz(P.date_of_exit, Now))
ORDER BY C.Cal_year, C.Cal_Week
Cal_year Cal_Week pname psurname date_of_entry exit_date
2006 1 foo bar 1/01/2006 8/01/2006
2010 1 foo1 bar1 5/01/2010 22/04/2016 13:04:39
2010 2 foo1 bar1 5/01/2010 22/04/2016 13:04:39
2010 3 foo1 bar1 5/01/2010 22/04/2016 13:04:39
2010 4 foo1 bar1 5/01/2010 22/04/2016 13:04:39
Note that if you need ALL WEEKS since 2006, even those for which nobody is present, just change the INNER JOIN with a LEFT JOIN
And finally, we exploit the previous query to count the presences by doing a GROUP BY on year and week of the calendar table, and we specify the year 2015 in the WHERE clause otherwise it will count everything since 2006. Which implies that it is very easy to count the presences for any year.
SELECT yyyy, ww , count(*) AS cnt
FROM
(
SELECT C.Cal_year AS yyyy, C.Cal_Week AS ww
FROM [Calendar] C
INNER JOIN ( SELECT [Name] AS pname, [surname] as psurname,
date_of_entry,
date_of_exit
FROM people
) P ON (DateAdd("ww",C.Cal_week,DateSerial(C.Cal_year,1,1)) BETWEEN P.date_of_entry AND nz(P.date_of_exit, Now))
)
WHERE yyyy=2015
GROUP BY yyyy, ww
ORDER BY yyyy, ww
yyyy ww cnt
2015 1 1
2015 2 1
2015 3 1
2015 4 1
2015 5 1
2015 6 1
2015 7 1
2015 8 1
2015 9 2
2015 10 2
2015 11 2
2015 12 2
2015 13 2
2015 14 1
2015 15 1
2015 16 1
Well, it took me 40 minutes finally...

You can create a query to find the weeks working:
Select
[name],
surname,
year,
week
From
PeopleTable,
WeekTable
Where
(date_of_entry <= week_start And DateDiff("d", week_start, date_of_exit) >= 3)
Or
(date_of_entry >= week_start And date_of_exit <= week_end
And
DateDiff("d", date_of_entry, date_of_exit >= 3)
Or
(DateDiff("d", date_of_entry, week_end) >= 3 And date_of_exit >= week_end)
Group By
[name],
surname,
year,
week
Now, save this and create a new query where you use WeekTable as source (to list all weeks) with an outer join to the query above (to list the worked weeks). In this, Group By the year and week and add a count to get the count of working employees for each week.

Related

Linear Interpolation in SQL

I work with crashes and mileage for the same year which is Year in table. Crashes are are there for every record, but annual mileage is not. NULLs for mileage could be at the beginning or at the end of the time period for certain customer. Also, couple of annual mileage records can be missing as well. I do not know how to overcome this. I try to do it in CASE statement but then I do not know how to code it properly. Issue needs to be resolved in SQL and use SQL Server.
This is how the output looks like and I need to have mileage for every single year for each customer.
The info I am pulling from is proprietary database and the records themselves should be untouched as is. I just need code in query which will modify my current output to output where I have mileage for every year. I appreciate any input!
Year
Customer
Crashes
Annual_Mileage
2009
123
5
3453453
2010
123
1
NULL
2011
123
0
54545
2012
123
14
376457435
2013
123
3
63453453
2014
123
4
NULL
2015
123
15
6346747
2016
123
0
NULL
2017
123
2
534534
2018
123
7
NULL
2019
123
11
NULL
2020
123
15
565435
2021
123
12
474567546
2022
123
7
NULL
Desired Results
Year
Customer
Crashes
Annual_Mileage
2009
123
5
3453453
2010
123
1
175399 (prior value is taken)
2011
123
0
54545
2012
123
14
376457435
2013
123
3
63453453
2014
123
4
34900100 (avg of 2 adjacent values)
2015
123
15
6346747
2016
123
0
3440641 (avg of 2 adjacent values)
2017
123
2
534534
2018
123
7
534534 ( prior value is taken)
2019
123
11
549985 (avg of 2 adjacent values)
2020
123
15
565435
2021
123
12
474567546
2022
123
7
474567546 (prior value is taken)
SELECT Year,
Customer,
Crashes,
CASE
WHEN Annual_Mlg IS NOT NULL THEN Annual_Mlg
WHEN Annual_Mlg IS NULL THEN
CASE
WHEN PREV.Annual_Mlg IS NOT NULL
AND NEXT.Annual_Mlg IS NOT NULL
THEN ( PREV.Annual_Mlg + NEXT.Annual_Mlg ) / 2
ELSE 0
END
END AS Annual_Mlg
FROM #table
The above code doesn't work, but I just need to start somehow and that what I have currently.
I understand what I need to do I just do not know how to code it in SQL.
After i applied row_number () function i got this output for first 2 clients and for the rest of the 4 clients row_number() function gave correct output. i have no idea why is that. I thought may be because i used "full join" before to combine mileage and crashes table?
enter image description here
Your use of #table tells me that you're using MS SQL Server (a temporary table, probably in a stored procedure).
You want to:
select all the rows in #table
joined with the matching row (if any) for the previous year, and
joined with the matching row (if any) for the next year
Then it's easy. Assuming the primary key on your #table is composed of the year and customer columns, something like this ought to do you:
select t.year ,
t.customer ,
t.crashes ,
annual_milage = coalesce(
t.annual_milage ,
( coalesce( p.annual_mileage, 0 ) +
coalesce( n.annual_mileage, 0 )
) / 2
)
from #table t -- take all the rows
left join #table p on p.year = t.year - 1 -- with the matching row for
and p.customer = t.customer -- the previous year (if any)
left join #table n on n.year = t.year + 1 -- and the matching row for
and n.customer = t.customer -- the next year (if any)
Notes:
What value you default to if the previous or next year doesn't exist is up to you (zero? some arbitrary value?)
Is the previous/next year guaranteed to be the current year +/- 1?
If not, you may have to use derived tables as the source for the
prev/next data, selecting the closest previous/next year (that sort
of thing rather complicates the query significantly).
Edited To Note:
If you have discontiguous years for each customer such that the "previous" and "next" years for a given customer are not necessarily the current year +/- 1, then something like this is probably the most straightforward way to find the previous/next year.
We use a derived table in our from clause, and assign a sequential number in lieu of year for each customer, using the ranking function row_number() function. This query, then
select row_nbr = row_number() over (
partition by x.customer
order by x.year
) ,
x.*
from #table x
would produce results along these lines:
row_nbr
customer
year
...
1
123
1992
...
2
123
1993
...
3
123
1995
...
4
123
2020
...
1
456
2001
...
2
456
2005
...
3
456
2020
...
And that leads us to this:
select year = t.year ,
customer = t.customer ,
crashes = t.crashes ,
annual_mileage = coalesce(
t.mileage,
coalesce(
t.annual_mileage,
(
coalesce(p.annual_mileage,0) +
coalesce(n.annual_mileage,0)
) / 2
),
)
from (
select row_nbr = row_number() over (
partition by x.customer
order by x.year
) ,
x.*
from #table x
) t
left join #table p on p.customer = t.customer and p.row_nbr = t.row_nbr-1
left join #table n on n.customer = t.customer and n.row_nbr = t.row_nbr+1

Postgres count number or rows and group them by timestamp

Let's assume I have one table in postgres with just 2 columns:
ID which is PK for the table (bigint)
time which is type of timestamp
Is there any way how to get IDs grouped by time BY YEAR- when the time is date 18 February 2005 it would fit in 2005 group (so result would be)
year number of rows
1998 2
2005 5
AND if the number of result rows is smaller than some number (for example 3) SQL will return the result by month
Something like
month number of rows
(February 2018) 5
(March 2018) 2
Is that possible some nice way in postgres SQL?
You can do it using window functions (as always).
I use this table:
TABLE times;
id | t
----+-------------------------------
1 | 2018-03-14 20:04:39.81298+01
2 | 2018-03-14 20:04:42.92462+01
3 | 2018-03-14 20:04:45.774615+01
4 | 2018-03-14 20:04:48.877038+01
5 | 2017-03-14 20:05:08.94096+01
6 | 2017-03-14 20:05:16.123736+01
7 | 2017-03-14 20:05:19.91982+01
8 | 2017-01-14 20:05:32.249175+01
9 | 2017-01-14 20:05:35.793645+01
10 | 2017-01-14 20:05:39.991486+01
11 | 2016-11-14 20:05:47.951472+01
12 | 2016-11-14 20:05:52.941504+01
13 | 2016-10-14 21:05:52.941504+02
(13 rows)
First, group by month (subquery per_month).
Then add the sum per year with a window function (subquery with_year).
Finally, use CASE to decide which one you will output and remove duplicates with DISTINCT.
SELECT DISTINCT
CASE WHEN yc > 5
THEN mc
ELSE yc
END AS count,
CASE WHEN yc > 5
THEN to_char(t, 'YYYY-MM')
ELSE to_char(t, 'YYYY')
END AS period
FROM (SELECT
mc,
sum(mc) OVER (PARTITION BY date_trunc('year', t)) AS yc,
t
FROM (SELECT
count(*) AS mc,
date_trunc('month', t) AS t
FROM times
GROUP BY date_trunc('month', t)
) per_month
) with_year
ORDER BY 2;
count | period
-------+---------
3 | 2016
3 | 2017-01
3 | 2017-03
4 | 2018
(4 rows)
Just count years. If it's at least 3, then you group by years, else by months:
select
case (select count(distinct extract(year from time)) from mytable) >= 3 then
to_char(time, 'yyyy')
else
to_char(time, 'yyyy-mm')
end as season,
count(*)
from mytable
group by season
order by season;
(Unlike many other DBMS, PostgreSQL allows to use alias names in the GROUP BY clause.)

How to replace all values in grouped column except first row

I have table like this:
ID Region CreatedDate Value
--------------------------------
1 USA 2016-01-01 5
2 USA 2016-02-02 10
3 Canada 2016-02-02 2
4 USA 2016-02-03 7
5 Canada 2016-03-03 3
6 Canada 2016-03-04 10
7 USA 2016-03-04 1
8 Cuba 2016-01-01 4
I need to sum column Value grouped by Region and CreatedDate by year and month. The result will be
Region Year Month SumOfValue
--------------------------------
USA 2016 1 5
USA 2016 2 17
USA 2016 3 1
Canada 2016 2 2
Canada 2016 3 13
Cuba 2016 1 4
BUT I want to replace all repeated values in column Region with empty string except first met row. The finish result must be:
Region Year Month SumOfValue
--------------------------------
USA 2016 1 5
2016 2 17
2016 3 1
Canada 2016 2 2
2016 3 13
Cuba 2016 1 4
Thank you for a solution. It will be advantage if solution will replace also in column Year
You need to use SUM and GROUP BY to get the SumOfValue. For the formatting, you can use ROW_NUMBER:
WITH Cte AS(
SELECT
Region,
[Year] = YEAR(CreatedDate),
[Month] = MONTH(CreatedDate),
SumOfValue = SUM(Value),
Rn = ROW_NUMBER() OVER(PARTITION BY Region ORDER BY YEAR(CreatedDate), MONTH(CreatedDate))
FROM #tbl
GROUP BY
Region, YEAR(CreatedDate), MONTH(CreatedDate)
)
SELECT
Region = CASE WHEN Rn = 1 THEN c.Region ELSE '' END,
[Year],
[Month],
SumOfValue
FROM Cte c
ORDER BY
c.Region, Rn
ONLINE DEMO
Although this can be done in TSQL, I suggest you do the formatting on the application side.
Query that follows the same order as the OP.

How to aggregate 7 days in SQL

I was trying to aggregate a 7 days data for FY13 (starts on 10/1/2012 and ends on 9/30/2013) in SQL Server but so far no luck yet. Could someone please take a look. Below is my example data.
DATE BREAD MILK
10/1/12 1 3
10/2/12 2 4
10/3/12 2 3
10/4/12 0 4
10/5/12 4 0
10/6/12 2 1
10/7/12 1 3
10/8/12 2 4
10/9/12 2 3
10/10/12 0 4
10/11/12 4 0
10/12/12 2 1
10/13/12 2 1
So, my desired output would be like:
DATE BREAD MILK
10/1/12 1 3
10/2/12 2 4
10/3/12 2 3
10/4/12 0 4
10/5/12 4 0
10/6/12 2 1
Total 11 15
10/7/12 1 3
10/8/12 2 4
10/9/12 2 3
10/10/12 0 4
10/11/12 4 0
10/12/12 2 1
10/13/12 2 1
Total 13 16
--------through 9/30/2013
Please note, since FY13 starts on 10/1/2012 and ends on 9/30/2012, the first week of FY13 is 6 days instead of 7 days.
I am using SQL server 2008.
You could add a new computed column for the date values to group them by week and sum the other columns, something like this:
SELECT DATEPART(ww, DATEADD(d,-2,[DATE])) AS WEEK_NO,
SUM(Bread) AS Bread_Total, SUM(Milk) as Milk_Total
FROM YOUR_TABLE
GROUP BY DATEPART(ww, DATEADD(d,-2,[DATE]))
Note: I used DATEADD and subtracted 2 days to set the first day of the week to Monday based on your dates. You can modify this if required.
Use option with GROUP BY ROLLUP operator
SELECT CASE WHEN DATE IS NULL THEN 'Total' ELSE CONVERT(nvarchar(10), DATE, 101) END AS DATE,
SUM(BREAD) AS BREAD, SUM(MILK) AS MILK
FROM dbo.test54
GROUP BY ROLLUP(DATE),(DATENAME(week, DATE))
Demo on SQLFiddle
Result:
DATE BREAD MILK
10/01/2012 1 3
10/02/2012 2 4
10/03/2012 2 3
10/04/2012 0 4
10/05/2012 4 0
10/06/2012 2 1
Total 11 15
10/07/2012 1 3
10/08/2012 4 7
10/10/2012 0 4
10/11/2012 4 0
10/12/2012 2 1
10/13/2012 2 1
Total 13 16
You are looking for a rollup. In this case, you will need at least one more column to group by to do your rollup on, the easiest way to do that is to add a computed column that groups them into weeks by date.
Take a lookg at: Summarizing Data Using ROLLUP
Here is the general idea of how it could be done:
You need a derived column for each row to determine which fiscal week that record belongs to. In general you could subtract that record's date from 10/1, get the number of days that have elapsed, divide by 7, and floor the result.
Then you can GROUP BY that derived column and use the SUM aggregate function.
The biggest wrinkle is that 6 day week you start with. You may have to add some logic to make sure that the weeks start on Sunday or whatever day you use but this should get you started.
The WITH ROLLUP suggestions above can help; you'll need to save the data and transform it as you need.
The biggest thing you'll need to be able to do is identify your weeks properly. If you don't have those loaded into tables already so you can identify them, you can build them on the fly. Here's one way to do that:
CREATE TABLE #fy (fyear int, fstart datetime, fend datetime);
CREATE TABLE #fylist(fyyear int, fydate DATETIME, fyweek int);
INSERT INTO #fy
SELECT 2012, '2011-10-01', '2012-09-30'
UNION ALL
SELECT 2013, '2012-10-01', '2013-09-30';
INSERT INTO #fylist
( fyyear, fydate )
SELECT fyear, DATEADD(DAY, Number, DATEADD(DAY, -1, fy.fstart)) AS fydate
FROM Common.NUMBERS
CROSS APPLY (SELECT * FROM #fy WHERE fyear = 2013) fy
WHERE fy.fend >= DATEADD(DAY, Number, DATEADD(DAY, -1, fy.fstart));
WITH weekcalc AS
(
SELECT DISTINCT DATEPART(YEAR, fydate) yr, DATEPART(week, fydate) dt
FROM #fylist
),
ridcalc AS
(
SELECT
ROW_NUMBER() OVER (ORDER BY yr, dt) AS rid, yr, dt
FROM weekcalc
)
UPDATE #fylist
SET fyweek = rid
FROM #fylist
JOIN ridcalc
ON DATEPART(YEAR, fydate) = yr
AND DATEPART(week, fydate) = dt;
SELECT list.fyyear, list.fyweek, p.[date], COUNT(bread) AS Bread, COUNT(Milk) AS Milk
FROM products p
JOIN #fylist list
ON p.[date] = list.fydate
GROUP BY list.fyyear, list.fyweek, p.[date] WITH ROLLUP;
The Common.Numbers reference above is a simple numbers table that I use for this sort of thing (goes from 1 to 1M). You could also build that on the fly as needed.

Fetch monthly records by total and by detail from single query in SQL Server 2005

I am working with SQL Server 2005.
I want to fetch monthlyTotalAppoinment and monthlyEmployeewiseTotal from appointment table in single result.
Appointment Table
appoinmentId
appoinmentDate
employeeId
I can successfully fetch monthlyTotalAppoinment and also got employeewisetotaappoinment from following query, but I want monthly employeewiseappoinment.
SELECT *
FROM (SELECT Datename(M, Dateadd(M, NUMBER - 1, 0)) AS month
FROM MASTER..SPT_VALUES
WHERE TYPE = 'p'
AND NUMBER BETWEEN 1 AND 12) months
LEFT JOIN (SELECT Datename(MM, APPOINMENTDATE) month,
Count(APPOINMENTID) AS TotalAppointment
FROM APPOINTMENT
GROUP BY Datename(MM, APPOINMENTDATE)) appoinment
ON months.MONTH = appoinment.MONTH
I am getting following output.
but I want following output
appoinementId employeeId appoinemntDate
------------- ----------- ---------------
1 4 8/25/2012 12:00:00 AM
2 4 8/25/2012 12:00:00 AM
3 4 8/25/2012 12:00:00 AM
4 4 8/25/2012 12:00:00 AM
5 4 8/25/2012 12:00:00 AM
6 4 9/25/2012 12:00:00 AM
7 2 9/25/2012 12:00:00 AM
8 2 9/25/2012 12:00:00 AM
9 2 9/25/2012 12:00:00 AM
10 4 9/25/2012 12:00:00 AM
11 4 10/25/2012 12:00:00 AM
12 2 10/25/2012 12:00:00 AM
13 4 10/25/2012 12:00:00 AM
for above data cuming output(For EmployeeId 4)
Month MonthData Totalappoinemnt TotalEmployeewiseAppointmemt
------------- ----------- -------------- ------------------------------
January.. NULL.. NULL.. NULL..
Augest Augest 5 9
September September 5 9
October October 3 9
But i want following
Month MonthData Totalappoinemnt TotalEmployeewiseAppointmemt
------------- ----------- -------------- ------------------------------
January.. NULL.. NULL.. NULL..
Augest Augest 5 5
September September 5 2
October October 3 2
I'm missing some minor points in your question, but the big issues are dealt with in this query:
SELECT t1.*,
t2.EMP_COUNT
FROM (SELECT Datename(MONTH, APPOINEMNTDATE) Month_Name,
Count(*) app_count
FROM APPOINTMENTTABLE
GROUP BY Datename(MONTH, APPOINEMNTDATE))T1
LEFT JOIN (SELECT Count(*) emp_count,
Datename(MONTH, APPOINEMNTDATE) Month_Name
FROM APPOINTMENTTABLE
WHERE EMPLOYEEID = 4
GROUP BY Datename(MONTH, APPOINEMNTDATE))T2
ON t1.MONTH_NAME = t2.MONTH_NAME
A working example can be found here.
What is missing?
Couldn't figure out why you had 2 columns for months. If there is a reason for this I'll revise the code.
I only listed months with details available. I saw that January was also in the example. If you want all 12 months to show even if no data is available, let me know and it will be added.
Didn't use the exact same column names. I'm sure you can change them if you need to :-)