Group By first day of month and join with a separate table - sql

I have 2 tables in SQL
one with monthly sales targets:
Date Target
1/7/17 50000
1/8/17 65000
1/9/17 50000
1/10/17 48000
etc...
the other with sales orders:
TxnDate JobNum Value
3/7/17 100001 20000
3/7/17 100002 11000
8/7/17 100003 10000
10/8/17 100004 15000
15/9/17 100005 20000
etc...
what I want is a table with following:
Date Target Sales
1/7/17 50000 41000
1/8/17 65000 15000
1/9/17 50000 20000
please help me I'm a newbie to coding and this is doing my head in.. :)

Assuming your 1st table is targetSales and your 2nd table is Sales and your database is SQL Server:
select
t.date
, t.target
, isnull(sum(s.value), 0) as Sales
from targetSales t
left join Sales s
on (month(t.date) = month(s.date)
and year(t.date) = year(s.date))
group by t.date
, t.target
You can follow a similar approach if you use a different database, just find the equivalents of month() and year() functions for your RDBMS.

try this
select tb1.date,tb1.target,tb2.value from table1 as tb1
INNER JOIN (select sum(value) as sales, date from table2 group by date) as tb2
on tb1.date = tb2.date,
you can use this script for daily targets

An another way around, looks like in target table the date is always the first day of the month. So in the sales table, just round the TxnDate column value to first day of the month.
Query
select t1.[date],
max(t1.[target]) as [target],
coalesce(sum(t2.[value]), 0) as [value]
from [targets] t1
left join [sales] t2
on t1.[Date] = dateadd(day, - datepart(day, t2.[txnDate]) + 1, t2.[txnDate])
group by t1.[Date];
demo

If you take any datetime value in SQL Server, calculate the number of months from that date to zero datediff(month,0,TxnDate) then add that number of moths to zero dateadd(month, ... , 0) you get the first day of the month for the original datetime value. This works in all versions of SQL Server. With this we can sum the values of the orders by the first day of the month, then join to targets using that date.
CREATE TABLE Orders
([TxnDate] datetime, [JobNum] int, [Value] int)
;
INSERT INTO Orders
([TxnDate], [JobNum], [Value])
VALUES
('2017-07-03 00:00:00', 100001, 20000),
('2017-07-03 00:00:00', 100002, 11000),
('2017-07-08 00:00:00', 100003, 10000),
('2017-08-10 00:00:00', 100004, 15000),
('2017-09-15 00:00:00', 100005, 20000)
;
CREATE TABLE Targets
([Date] datetime, [Target] int)
;
INSERT INTO Targets
([Date], [Target])
VALUES
('2017-07-01 00:00:00', 50000),
('2017-08-01 00:00:00', 65000),
('2017-09-01 00:00:00', 50000),
('2017-10-10 00:00:00', 48000)
;
GO
9 rows affected
select dateadd(month,datediff(month,0,TxnDate), 0) month_start, sum(Value) SumValue
from Orders
group by dateadd(month, datediff(month,0,TxnDate), 0)
GO
month_start | SumValue
:------------------ | -------:
01/07/2017 00:00:00 | 41000
01/08/2017 00:00:00 | 15000
01/09/2017 00:00:00 | 20000
select
t.[Date], t.Target, coalesce(o.SumValue,0)
from targets t
left join (
select dateadd(month,datediff(month,0,TxnDate), 0) month_start, sum(Value) SumValue
from Orders
group by dateadd(month, datediff(month,0,TxnDate), 0)
) o on t.[Date] = o.month_start
GO
Date | Target | (No column name)
:------------------ | -----: | ---------------:
01/07/2017 00:00:00 | 50000 | 41000
01/08/2017 00:00:00 | 65000 | 15000
01/09/2017 00:00:00 | 50000 | 20000
10/10/2017 00:00:00 | 48000 | 0
dbfiddle here

This is not the best solution but this will give you a correct result.
select date,target,(
select sum(value)
from sales_orders s
where datepart(m,s.TxnDate) = datepart(m,targets.Date)
and datepart(year,s.TxnDate) = datepart(year,targets.Date)
) as sales
from targets

Related

Find out how many tickets were open each day historically

I have a table that looks like the below
ID | CreatedDate | CloseDate
271 | 01-Jan-2018 | 02-Jan-2018
278 | 03-Jan-2018 | 05-Jan-2018
333 | 03-Jan-2018 | NULL
I have been tasked to find out for each day in a month, how many tickets remained open going into the next day. A ticket would be classed as remaining open if the CloseDate was not on the same date as the created date, or the CloseDate is NULL.
My ideal output from the above would look like this
Day | Tickets Remaining
01-Jan-2018 | 1
02-Jan-2018 | 0
03-Jan-2018 | 2
04-Jan-2018 | 2
05-Jan-2018 | 1
Does this make sense? Using SQL Server 2016..
If you don't have a number/tally table handy, you can use a recursive CTE:
with cte as (
select id, createddate as dte, coalesce(closeddate, convert(date, getdate())) as closeddate
from t
union all
select id, dateadd(day, 1, dte), closeddate
from cte
where dte < closeddate
)
select dte, count(*)
from cte
group by dte;
Note: If any of the timespans can exceed 100 days, then you need to add option (maxrecursion 0).
If there are only a handful of dates, you can use a derived table and left join:
select v.dte, count(t.id)
from (values (convert(date, '2018-01-01')),
(convert(date, '2018-01-02')),
(convert(date, '2018-01-03')),
(convert(date, '2018-01-04')),
(convert(date, '2018-01-05')),
) v(dte) left join
t
on v.dte >= t.createddate and
(v.dte <= t.closeddate or t.closeddate is null)
group by v.dte
order by v.dte;

Split a record to multiple rows of record

I have table as below
Master Table
ID Name
1 Bubble
Child Table
ID MasterTableID StartDate EndDate Qty UnitMeasurement
1 1 1/2/2019 1/6/2019 1000 sqft
2 1 1/2/2019 1/4/2019 3000 sqft
I need to select the record above and show it in 5 rows since 1/2 - 1/6 were 5 months.
Date Qty
1/2/2019 200
1/3/2019 200
1/4/2019 200
1/5/2019 200
1/6/2019 200
Second row record to 3 rows record
Date Qty
1/2/2019 1000
1/3/2019 1000
1/4/2019 1000
I'm using SQL Server.
May I know it is possible to do so?
you can use Recursively + CTE and filter using inner join on id
CREATE TABLE T
([ID] int, [MasterTableID] int, [StartDate] datetime, [EndDate] datetime, [Qty] int, [UnitMeasurement] varchar(4))
;
INSERT INTO T
([ID], [MasterTableID], [StartDate], [EndDate], [Qty], [UnitMeasurement])
VALUES
(1, 1, '2019-01-02 00:00:00', '2019-01-06 00:00:00', 1000, 'sqft'),
(2, 1, '2019-01-02 00:00:00', '2019-01-04 00:00:00', 3000, 'sqft')
;
GO
2 rows affected
with cte as (
select [EndDate] as [Date],ID,datediff(day,[StartDate], [EndDate]) diff , [Qty] / (datediff(day,[StartDate], [EndDate]) + 1) as qty
from T
union all
select dateadd(day,-1,[Date]) [Date],T1.ID,T2.diff - 1 as diff,T2.qty
from T T1
inner join cte T2 on T1.ID = T2.ID
where diff >0
)
select ID,[Date],qty
from cte
order by ID,[Date]
GO
ID | Date | qty
-: | :------------------ | ---:
1 | 02/01/2019 00:00:00 | 200
1 | 03/01/2019 00:00:00 | 200
1 | 04/01/2019 00:00:00 | 200
1 | 05/01/2019 00:00:00 | 200
1 | 06/01/2019 00:00:00 | 200
2 | 02/01/2019 00:00:00 | 1000
2 | 03/01/2019 00:00:00 | 1000
2 | 04/01/2019 00:00:00 | 1000
db<>fiddle here
This is achievable using cte. since your dateformat is ddMMyyy, we need to convert this to MMddyyy so we can use dateadd(month...
CREATE TABLE #Temp
(id int, [StartDate] varchar(30), [EndDate] varchar(30), [Qty] int, [UnitMeasurement] varchar(4))
;
INSERT INTO #Temp
(id, [StartDate], [EndDate], [Qty], [UnitMeasurement])
VALUES
(1, '1/2/2019', '1/6/2019', 1000, 'sqft'),
(2, '1/2/2019', '1/4/2019', 3000, 'sqft')
;
GO
with cte as
(
Select id, cast(convert(varchar
, convert(datetime, [StartDate], 103), 101) as date) as startdate
, cast(convert(varchar
, convert(datetime, [EndDate], 103), 101) as date) as enddate
, [Qty]
, 1 as ctr from #Temp
union all
Select id, dateadd(month, 1, startdate), enddate, qty, ctr + 1
From cte
Where startdate < enddate
)
Select t1.id, qty/t2.ct, startdate from cte t1
cross apply (select count(1) ct, id from cte group by id) t2
where t2.id = t1.id
order by t1.id asc
Option (MaxRecursion 0)
drop table #Temp
output:
try like below for generating date
DECLARE #StartDate DATE = '1/2/2019'
, #EndDate DATE = '1/6/2019'
SELECT DATEADD(DAY, nbr - 1, #StartDate)
FROM ( SELECT ROW_NUMBER() OVER ( ORDER BY c.object_id ) AS Nbr
FROM sys.columns c
) nbrs
WHERE nbr - 1 <= DATEDIFF(DAY, #StartDate, #EndDate)
or you can use recursion
Declare #FromDate Date = '1/2/2019',
#ToDate Date = '1/6/2019'
;With DateCte (Date) As
(
Select #FromDate Union All
Select DateAdd(Day, 1, Date)
From DateCte
Where Date <= #ToDate
)
Select Date
From DateCte
Option (MaxRecursion 0)

Display data for all date ranges including missing dates

I'm having a issue with dates. I have a table with given from and to dates for an employee. For an evaluation, I'd like to display each date of the month with corresponding values from the second sql table.
SQL Table:
EmpNr | datefrom | dateto | hours
0815 | 01.01.2019 | 03.01.2019 | 15
0815 | 05.01.2019 | 15.01.2019 | 15
0815 | 20.01.2019 | 31.12.9999 | 40
The given employee (0815) worked during 01.01.-15.01. 15 hours, and during 20.01.-31.01. 40 hours
I'd like to have the following result:
0815 | 01.01.2019 | 15
0815 | 02.01.2019 | 15
0815 | 03.01.2019 | 15
0815 | 04.01.2019 | NULL
0815 | 05.01.2019 | 15
...
0815 | 15.01.2019 | 15
0815 | 16.01.2019 | NULL
0815 | 17.01.2019 | NULL
0815 | 18.01.2019 | NULL
0815 | 19.01.2019 | NULL
0815 | 20.01.2019 | 40
0815 | 21.01.2019 | 40
...
0815 | 31.01.2019 | 40
as for the dates, we have:
declare #year int = 2019, #month int = 1;
WITH numbers
as
(
Select 1 as value
UNion ALL
Select value + 1 from numbers
where value + 1 <= Day(EOMONTH(datefromparts(#year,#month,1)))
)
SELECT b.empnr, b.hours, datefromparts(#year,#month,numbers.value) Datum FROM numbers left outer join
emptbl b on b.empnr = '0815' and (datefromparts(#year,#month,numbers.value) >= b.datefrom and datefromparts(#year,#month,numbers.value) <= case b.dateto )
which is working quite well, yet I have the odd issue, that this code is only shoes the dates between 01.01.2019 and 03.01.2019
thank you very much in advance!
Did you check, if datefrom and dateto is in correct range?
Minimum value of DateTime field is 1753-01-01 and maximum value is 9999-12-31.
Look at your source table to check initial values.
The recursive CTE needs to begin with MIN(datefrom) and MAX(dateto):
DECLARE #t TABLE (empnr INT, datefrom DATE, dateto DATE, hours INT);
INSERT INTO #t VALUES
(815, '2019-01-01', '2019-01-03', 15),
(815, '2019-01-05', '2019-01-15', 15),
(815, '2019-01-20', '9999-01-01', 40),
-- another employee
(999, '2018-01-01', '2018-01-31', 15),
(999, '2018-03-01', '2018-03-31', 15),
(999, '2018-12-01', '9999-01-01', 40);
WITH rcte AS (
SELECT empnr
, MIN(datefrom) AS refdate
, ISNULL(NULLIF(MAX(dateto), '9999-01-01'), CURRENT_TIMESTAMP) AS maxdate -- clamp year 9999 to today
FROM #t
GROUP BY empnr
UNION ALL
SELECT empnr
, DATEADD(DAY, 1, refdate)
, maxdate
FROM rcte
WHERE refdate < maxdate
)
SELECT rcte.empnr
, rcte.refdate
, t.hours
FROM rcte
LEFT JOIN #t AS t ON rcte.empnr = t.empnr AND rcte.refdate BETWEEN t.datefrom AND t.dateto
ORDER BY rcte.empnr, rcte.refdate
OPTION (MAXRECURSION 1000) -- approx 3 years
Demo on db<>fiddle
It could be in your select, try:
SELECT b.empnr, b.hours, datefromparts(#year,#month,numbers.value) Datum
FROM numbers
LEFT OUTER JOIN emptbl b ON b.empnr = '0815' AND
datefromparts(#year,#month,numbers.value) BETWEEN b.datefrom AND b.dateto
Your CTE produces only 31 number and therefore it is showing only January dates.
declare #year int = 2019, #month int = 1;
WITH numbers
as
(
Select 1 as value
UNion ALL
Select value + 1 from numbers
where value + 1 <= Day(EOMONTH(datefromparts(#year,#month,1)))
)
SELECT *
FROM numbers
https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=a24e58ef4ce522d3ec914f90907a0a9e
You can try below code,
with t0 (i) as (select 0 union all select 0 union all select 0),
t1 (i) as (select a.i from t0 a ,t0 b ),
t2 (i) as (select a.i from t1 a ,t1 b ),
t3 (srno) as (select row_number()over(order by a.i) from t2 a ,t2 b ),
tbldt(dt) as (select dateadd(day,t3.srno-1,'01/01/2019') from t3)
select tbldt.dt
from tbldt
where tbldt.dt <= b.dateto -- put your condition here
https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=b16469908b323b8d1b98d77dd09bab3d

Calculate total time without overlapping time

MSSQL 2016
Currently I have the following table:
https://i.stack.imgur.com/W4GX6.png
OwnerName = Jan is working this day from 07.00 till 15.30. This means his total working time should be 8.5 hours. However, when I SUM the total minutes I got a lot more since he has overlapping activities. How could I exclude these overlapping activities and calculate his total working time on this day:
2017-09-12.
Now, this table contains not only data for 2017-09-12 but for every day there are activities planned. So the query should take this in account aswell. Furthermore, there are multiple owners in this table i.e. Luc. this is also somehting that the query should handle.
Can someone help me? :)
EDIT: To make things clearer. The result should be in the following format
|------------------------|------------------|----------------|
| Ownername | Date | Time in hours |
|------------------------|------------------|----------------|
| Jan | 2017-09-12 | 8.5 |
| Luc | 2017-09-12 | 8.5 |
| John | 2017-09-12 | 8.5 |
| Doe | 2017-09-11 | 7 |
| Jan | 2017-09-13 | 4 |
| Doe | 2017-09-14 | 8.5 |
| Tom | 2017-09-14 | 7 |
My current guess is that I first have to determine which activities are overlapping. I think I have to use the BETWEEN statement to do that. Then use the CASE statement to not add this time to the total time in hours for that day.
I think your problem is with SUM keyword. Instead try using DATEDIFF.
It will be accurate and if you have multiple records for each day use CURSOR so that you can take evry record uniqeuly.
For example use DATEDIFF in a CURSOR and THEN SUM the result of that
Ok, try the following solution. It removes intervals that are contained in some other one and then it uses the LAG window function to access the previous row and update the intervals to have non-overlapping ones. Then we can simply perform the SUM.
WITH
inputs ( name, date_fr, date_to ) AS (
select 'Dan' , cast('07-Jun-17 08:00:00' as DATETIME), cast('07-Jun-17 10:00:00' as DATETIME) union all
select 'Dan' , cast('07-Jun-17 11:00:00' as DATETIME), cast('07-Jun-17 12:00:00' as DATETIME) union all
select 'Jan' , cast('08-Jun-17 08:15:00' as DATETIME), cast('08-Jun-17 15:00:00' as DATETIME) union all
select 'Jan' , cast('08-Jun-17 08:15:00' as DATETIME), cast('08-Jun-17 10:00:00' as DATETIME) union all
select 'Jan' , cast('07-Jun-17 08:30:00' as DATETIME), cast('07-Jun-17 15:00:00' as DATETIME) union all
select 'Jan' , cast('07-Jun-17 09:00:00' as DATETIME), cast('07-Jun-17 9:30:00' as DATETIME) union all
select 'Jan' , cast('07-Jun-17 08:00:00' as DATETIME), cast('07-Jun-17 10:00:00' as DATETIME)
)
-- End of simulated input (for testing only, not part of the solution).
SELECT name, year(date_fr) year, month(date_fr) month, day(date_fr) day,
sum(cast(datediff(minute, date_fr, date_to) as decimal(10,4)))/60
FROM
(
SELECT t.name,
CASE WHEN lag(date_to) over (partition by name order by date_fr) > date_fr
THEN lag(date_to) over (partition by name order by date_fr)
ELSE date_fr
END date_fr,
t.date_to
FROM
(
SELECT name, date_fr, date_to
FROM inputs i1
WHERE NOT EXISTS (SELECT 1 FROm inputs i2 WHERE i1.name = i2.name and i2.date_fr < i1.date_fr and i2.date_to > i1.date_to) -- this removes fully nested intervals
) t
) tt
WHERE tt.date_fr < tt.date_to
GROUP BY name, year(date_fr), month(date_fr), day(date_fr)

Query to return all the days of a month

This problem is related to this, which has no solution in sight: here
I have a table that shows me all sessions of an area.
This session has a start date.
I need to get all the days of month of the start date of the session by specific area (in this case)
I have this query:
SELECT idArea, idSession, startDate FROM SessionsPerArea WHERE idArea = 1
idArea | idSession | startDate |
1 | 1 | 01-01-2013 |
1 | 2 | 04-01-2013 |
1 | 3 | 07-02-2013 |
And i want something like this:
date | Session |
01-01-2013 | 1 |
02-01-2013 | NULL |
03-01-2013 | NULL |
04-01-2013 | 1 |
........ | |
29-01-2013 | NULL |
30-01-2013 | NULL |
In this case, the table returns me all the days of January.
The second column is the number of sessions that occur on that day, because there may be several sessions on the same day.
Anyone can help me?
Please try:
DECLARE #SessionsPerArea TABLE (idArea INT, idSession INT, startDate DATEtime)
INSERT #SessionsPerArea VALUES (1,1,'2013-01-01')
INSERT #SessionsPerArea VALUES (1,2,'2013-01-04')
INSERT #SessionsPerArea VALUES (1,3,'2013-07-02')
DECLARE #RepMonth as datetime
SET #RepMonth = '01/01/2013';
WITH DayList (DayDate) AS
(
SELECT #RepMonth
UNION ALL
SELECT DATEADD(d, 1, DayDate)
FROM DayList
WHERE (DayDate < DATEADD(d, -1, DATEADD(m, 1, #RepMonth)))
)
SELECT *
FROM DayList t1 left join #SessionsPerArea t2 on t1.DayDate=startDate and t2.idArea = 1
This will work:
DECLARE #SessionsPerArea TABLE (idArea INT, idSession INT, startDate DATE)
INSERT #SessionsPerArea VALUES
(1,1,'2013-01-01'),
(1,2,'2013-01-04'),
(1,3,'2013-07-02')
;WITH t1 AS
(
SELECT startDate
, DATEADD(MONTH, DATEDIFF(MONTH, '1900-01-01', startDate), '1900-01-01') firstInMonth
, DATEADD(DAY, -1, DATEADD(MONTH, DATEDIFF(MONTH, '1900-01-01', startDate) + 1, '1900-01-01')) lastInMonth
, COUNT(*) cnt
FROM #SessionsPerArea
WHERE idArea = 1
GROUP BY
startDate
)
, calendar AS
(
SELECT DISTINCT DATEADD(DAY, c.number, t1.firstInMonth) d
FROM t1
JOIN master..spt_values c ON
type = 'P'
AND DATEADD(DAY, c.number, t1.firstInMonth) BETWEEN t1.firstInMonth AND t1.lastInMonth
)
SELECT d date
, cnt Session
FROM calendar c
LEFT JOIN t1 ON t1.startDate = c.d
It uses simple join on master..spt_values table to generate rows.
Just an example of calendar table. To return data for a month adjust the number of days between < 32, for a year to 365+1. You can calculate the number of days in a month or between start/end dates with query. I'm not sure how to do this in SQL Server. I'm using hardcoded values to display all dates in Jan-2013. You can adjust start and end dates for diff. month or to get start/end dates with queries...:
WITH data(r, start_date) AS
(
SELECT 1 r, date '2012-12-31' start_date FROM any_table --dual in Oracle
UNION ALL
SELECT r+1, date '2013-01-01'+r-1 FROM data WHERE r < 32 -- number of days between start and end date+1
)
SELECT start_date FROM data WHERE r > 1
/
START_DATE
----------
1/1/2013
1/2/2013
1/3/2013
...
...
1/31/2013