SQL Join Multiple Subqueries - Count Open / Closed by Date - sql

I'm trying to count all open tickets / closed tickets groups by date. Some dates will have 0 values for both but I'd still like to show the date. I feel like I'm close but can't seem to get the grouping correct, it's just giving a total.
DECLARE #DateFrom AS DATE = '11/16/2016'
DECLARE #DateTo AS DATE = GETDATE()
WITH DateRanges AS
(SELECT #DateFrom AS 'DateValue'
UNION ALL
SELECT DATEADD(DAY, 1, DateValue)
FROM DateRanges
WHERE DateValue < #DateTo)
SELECT CONVERT(varchar(10),DateValue, 101) AS "DateVal",
(SELECT
COUNT(OPENDATE)
FROM DateRanges AS a
LEFT OUTER JOIN MAINTABLE
ON a.DateValue = convert(varchar(10), DATEADD(hh, DATEDIFF(HH, GetUTCDATE(), GETDATE()), OPENDATE), 101)
) AS opn,
(SELECT
COUNT(CLOSEDDATE)
FROM DateRanges AS b
LEFT OUTER JOIN MAINTABLE
ON b.DateValue = convert(varchar(10), DATEADD(hh, DATEDIFF(HH, GetUTCDATE(), GETDATE()), CLOSEDDATE), 101)
) AS cls
FROM DateRanges
GROUP BY CONVERT(varchar(10),DateValue, 101)
This produces the following:
DateVal | opn | cls
11/16/2016 | 3 | 3
11/17/2016 | 3 | 3
11/18/2016 | 3 | 3
MainTable
ID | OPENDATE | CLOSEDDATE
123 | 11/16/2016 | 11/16/2016
124 | 11/16/2016 | 11/18/2016
125 | 11/18/2016 | 11/18/2016
Expected Output
DateVal | opn | cls
11/16/2016 | 2 | 1
11/17/2016 | 0 | 0
11/18/2016 | 1 | 2
Thank you for your help

You can use your approach, but you need correlated subqueries, instead of totals:
WITH DateRanges AS (
SELECT #DateFrom AS 'DateValue'
UNION ALL
SELECT DATEADD(DAY, 1, DateValue)
FROM DateRanges
WHERE DateValue < #DateTo
)
SELECT CONVERT(varchar(10), DateValue, 101) AS "DateVal",
(SELECT COUNT(mt.REQDATE)
FROM MAINTABLE mt
WHERE dr.DateValue = convert(varchar(10), DATEADD(hour, DATEDIFF(hour, GetUTCDATE(), GETDATE()), mt.OPENDATE), 101)
) AS opn,
(SELECT COUNT(CLSDDATE)
FROM MAINTABLE mt
WHERE dr.DateValue = convert(varchar(10), DATEADD(hh, DATEDIFF(hour, GetUTCDATE(), GETDATE()), mt.CLOSEDDATE), 101)
) AS cls
FROM DateRanges dr;
Also note that you should not need an aggregation in the outer query.

Related

Get Last and Next Appointments

I have the following table in SQL Server and would like to get the last and next appointments for each customer.
Note: If the first appointment is in the future, last appointment should be N/A. Similarly if the last appointment is in the past, next appointment will be N/A. If the last appointment is older than 30 days it should not be shown (if there is no future appointment - considered an inactive customer).
CustomerId (int) | Date (date) | Time (time)
1 | 20210801 | 11:00
1 | 20210802 | 13:00
1 | 20210805 | 10:00
1 | 20210811 | 16:00
1 | 20210821 | 17:00
2 | 20210801 | 11:00
2 | 20210802 | 11:00
2 | 20210803 | 11:00
2 | 20210804 | 11:00
3 | 20210831 | 11:00
4 | 20210526 | 10:00
In this case the result should be (Assuming the date is today 7 August 2021):
CustomerId (int) | LastAppointment (varchar) | NextAppointment (varchar)
1 | 05 Aug 2021 - 10:00 | 11 Aug 2021 - 16:00
2 | 04 Aug 2021 - 11:00 | N/A
3 | N/A | 31 Aug 2021 - 11:00
Can anyone help me please? An example would be appreciated.
You simply need to work with datetime values and then use conditional aggregation to select the required date for each customer. Using a CTE first to simplify converting the dates as much as possible, this looks like:
with ap as (
select CustomerId, Convert(datetime,Left(Concat([date], ' ', [time]),15)) app
from t
), groups as (
select CustomerId,
Max(case when app <= GetDate() then app end) LastAppointment,
Min(case when app > GetDate() then app end) NextAppointment
from ap
group by customerId
)
select CustomerID,
IsNull(Format(LastAppointment, 'dd MMM yyyy - hh:mm'), 'N/A') LastAppointment,
IsNull(Format(NextAppointment, 'dd MMM yyyy - hh:mm'), 'N/A') NextAppointment
from groups
where DateAdd(day,-30,GetDate()) < isnull(lastappointment,GetDate())
see DB<>Fiddle
Also note this query only touches the table once and performs a single logical read.
You need conditional aggregation:
SELECT CustomerId,
COALESCE(
MAX(CASE
WHEN CAST(Date AS DATETIME) + CAST(Time AS DATETIME) < GETDATE()
THEN FORMAT(CAST(Date AS DATETIME) + CAST(Time AS DATETIME), 'dd MMM yyyy - HH:mm')
END
), 'N/A'
) LastAppointment,
COALESCE(
MIN(CASE
WHEN CAST(Date AS DATETIME) + CAST(Time AS DATETIME) > GETDATE()
THEN FORMAT(CAST(Date AS DATETIME) + CAST(Time AS DATETIME), 'dd MMM yyyy - HH:mm')
END
), 'N/A'
) NextAppointment
FROM tablename
GROUP BY CustomerId
HAVING COALESCE(DATEDIFF(
d,
MAX(CASE
WHEN CAST(Date AS DATETIME) + CAST(Time AS DATETIME) < GETDATE()
THEN CAST(Date AS DATETIME) + CAST(Time AS DATETIME)
END
),
GETDATE()
), 0) < 30
See the demo.
Results:
CustomerId
LastAppointment
NextAppointment
1
05 Aug 2021 - 10:00
11 Aug 2021 - 16:00
2
04 Aug 2021 - 11:00
N/A
3
N/A
31 Aug 2021 - 11:00
NOTE : This solution works but it is very bad in terms of performance, check this answer for a better approach
Something like this
SELECT DISTINCT customerid,
Isnull(CONVERT(VARCHAR,
(SELECT TOP 1 Concat(date, ' ', TIME)
FROM appointments B
WHERE b.customerid = a.customerid
AND ([date] < CONVERT(DATE, Getdate())
OR ([date] = CONVERT(DATE, Getdate())
AND [time] <= CONVERT(TIME, Getdate())))
ORDER BY [date] DESC)), 'N/A') AS lastappointment,
Isnull(CONVERT(VARCHAR,
(SELECT TOP 1 Concat(date, ' ', TIME)
FROM appointments B
WHERE b.customerid = a.customerid
AND ([date] > CONVERT(DATE, Getdate())
OR ([date] = CONVERT(DATE, Getdate())
AND [time] > CONVERT (TIME, Getdate())))
ORDER BY [date])), 'N/A') AS nextappointment
FROM appointments A
WHERE Datediff(DAY,
(SELECT TOP 1 date
FROM appointments B
WHERE b.customerid = a.customerid
AND [date] <= CONVERT(DATE, Getdate())
ORDER BY [date] DESC), CONVERT(DATE, Getdate())) <= 30
OR (((
(SELECT TOP 1 date
FROM appointments B
WHERE b.customerid = a.customerid
AND [date] > CONVERT(DATE, Getdate())
ORDER BY [date]) > CONVERT(DATE, Getdate())))
OR ((
(SELECT TOP 1 date
FROM appointments B
WHERE b.customerid = a.customerid
AND [date] > CONVERT(DATE, Getdate())
ORDER BY [date]) = CONVERT(DATE, Getdate()))
AND (
(SELECT TOP 1 [time]
FROM appointments B
WHERE b.customerid = a.customerid
AND [date] > CONVERT(DATE, Getdate())
ORDER BY [date]) > CONVERT(TIME, Getdate()))))
I called your table appointments and the condition is to select customer with last appointment in the past 30 days OR with a future appointment.
I tested with column types Date for Date and Time(7) for time.
Base table is used only single time because of optimization purpose. Use LAG() function and others necessary condition for picking actual set of data.
-- SQL SERVER
SELECT p.CustomerId
, CASE WHEN p.chk_condition = 1
THEN CONVERT(varchar(13), p.prev_Date, 113) + ' - ' + LEFT(p.prev_time, 5)
WHEN p.chk_condition = 2
THEN CONVERT(varchar(13), p.Date, 113) + ' - ' + LEFT(p.time, 5)
ELSE 'N/A'
END "LastAppointment"
, CASE WHEN p.chk_condition != 2
THEN CONVERT(varchar(13), p.Date, 113) + ' - ' + LEFT(p.time, 5)
ELSE 'N/A'
END "NextAppointment"
FROM ( SELECT t.*
, CASE WHEN t.prev_Date < GETDATE() AND t.Date >= GETDATE()
THEN 1
WHEN t.prev_Date < GETDATE() AND t.Date <= GETDATE()
THEN 2
ELSE 0
END chk_condition
, ROW_NUMBER() OVER (PARTITION BY CustomerId ORDER BY t.Date DESC, t.prev_Date DESC) row_num
FROM (SELECT CustomerId, Date, Time
, LAG(Date) OVER (PARTITION BY CustomerId ORDER BY "Date", "Time") "prev_Date"
, LAG(Time) OVER (PARTITION BY CustomerId ORDER BY "Date", "Time") "prev_Time"
FROM appointment) t
WHERE CASE WHEN t.prev_Date < GETDATE() AND t.Date >= GETDATE()
THEN 1
WHEN t.prev_Date IS NULL
THEN CASE WHEN DATEDIFF(day, t.Date, GETDATE()) >= 30
THEN 0
ELSE 1
END
WHEN t.prev_Date < GETDATE() AND t.Date <= GETDATE()
THEN 1
END = 1 ) p
WHERE p.row_num = 1
ORDER BY p.CustomerId;
Please check this url https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=3813d09cf25ed14d249970654995b085

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)

Multiple counts and merge columns

I current have a query that grabs the number of parts made per hour between two dates:
DECLARE #StartDate datetime
DECLARE #EndDate datetime
SET #StartDate = '10/10/2018'
SET #EndDate = '11/11/2018'
SELECT
CONVERT(VARCHAR(10), CAST(presstimes AS DATE), 111) AS ForDate,
DATEPART(HOUR, presstimes) AS OnHour,
COUNT(*) AS Totals
FROM
partmasterlist
WHERE
((presstimes >= #StartDate AND presstimes < dateAdd(d, 1, #EndDate))
AND (((presstimes IS NOT NULL))))
GROUP BY
CONVERT(VARCHAR(10), CAST(presstimes AS DATE), 111),
DATEPART(HOUR, presstimes)
ORDER BY
CONVERT(VARCHAR(10), CAST(presstimes AS DATE), 111) ASC;
Output:
Date Hour QTY
---------------------
2018/11/06 11 16
2018/11/06 12 20
2018/11/06 13 29
2018/11/06 14 26
Now I need to add another qty column to count where "trimmingtimes" is set.
I can't figure out how to full join the date and hour columns (e.g. presstimes might have 20qty for Hour 2, but trimmingtimes is NULL for Hour 2);
Input:
ID presstimes trimmingtimes
-----------------------------------------------------------------
1 2018-10-10 01:15:23.000 2018-10-10 01:15:23.000
2 2018-10-10 01:15:23.000 NULL
3 2018-10-10 02:15:23.000 NULL
4 NULL 2018-10-10 03:15:23.000
Output:
Date hour Press QTY T QTY
------------------------------------
10/10/18 1 2 1
10/10/18 2 1 0
10/10/18 3 0 1
I suspect you want something like this:
select convert(date, v.dt) as date,
datepart(hour, v.dt) as hour,
sum(ispress) as num_press,
sum(istrim) as num_trim
from partmasterlist pml cross apply
(values (pml.presstime, 1, 0), (pml.trimmingtime, 0, 1)
) v(dt, ispress, istrim)
group by convert(date, v.dt), datepart(hour, v.dt)
order by convert(date, v.dt), datepart(hour, v.dt);
You can add a where clause for a particular range.

months between two dates in sql server with starting and end date of each of them in sql server

i want to get months between two dates with their starting and end dates.Suppose if i enter startdate as "2017-04-01" and enddate as "2017-07-31", i want list of months i.e April,May,June,July with their starting and end date respectively.Kindly suggest me how it can be achieved.
One method is a recursive CTE:
with cte as (
select dateadd(day, 1 - day(#startdate), #startdate) as som,
eomonth(#startdate) as eom
union all
select dateadd(month, 1, som), eomonth(dateadd(month, 1, som))
from cte
where dateadd(month, 1, som) < #enddate
)
select *
from cte;
If you want the name of the month, then you can use datename(month, som).
Without recursion, using master.dbo.spt_values as a substitute for a numbers table:
declare #StartDate date = '20170401'
, #EndDate date = '20170731';
;with Months as (
select top (datediff(month,#startdate,#enddate)+1)
[Month] = dateadd(month, row_number() over (order by number) -1, #StartDate)
, MonthEnd = dateadd(day,-1,dateadd(month, row_number() over (order by number), #StartDate))
from master.dbo.spt_values
order by [Month]
)
select * from Months;
rextester demo: http://rextester.com/FXQJ4048
returns:
+------------+------------+
| Month | MonthEnd |
+------------+------------+
| 2017-04-01 | 2017-04-30 |
| 2017-05-01 | 2017-05-31 |
| 2017-06-01 | 2017-06-30 |
| 2017-07-01 | 2017-07-31 |
+------------+------------+
When generating a set or sequence in SQL Server, methods that avoid recursion and loops perform significantly better as the number of values increases.
Reference:
Generate a set or sequence without loops - 1 - Aaron Bertrand
Generate a set or sequence without loops - 2 - Aaron Bertrand
Generate a set or sequence without loops - 3 - Aaron Bertrand
To get the start and end dates of each month within a given range, when the value of the #StartDate parameter is not the first day of the month:
The first option is to truncate the #StartDate parameter to the first of the month, the second option is to adjust the expressions in the common table expression to truncate the values there:
declare #StartDate date = '20170415'
, #EndDate date = '20170715';
/* Option 1: truncate #StartDate to the beginning of the month */
--set #StartDate = dateadd(month, datediff(month, 0, #StartDate), 0);
/* Option 2: Truncate #StartDate to month in the common table expression: */
;with Months as (
select top (datediff(month,#StartDate,#EndDate)+1)
[Month] = dateadd(month
, datediff(month, 0, #StartDate) + row_number() over (order by number) -1
, 0)
, MonthEnd = dateadd(day,-1,dateadd(month
, datediff(month, 0, #StartDate) + row_number() over (order by number)
,0))
from master.dbo.spt_values
order by [Month]
)
select * from Months;
Here you go...
created the schema
create table abc(
date1 date
)
//Inserting data into it
insert into abc values(getdate()),
(DATEADD(Month, -1, getdate())),
(DATEADD(Month, -2, getdate())),
(DATEADD(Month, -3, getdate())),
(DATEADD(Month, -4, getdate()))
and finally the Select Query to fetch the data between Start date and end date:
select (datename(Month, date1)+' '+convert(varchar(2), date1, 103)) as [Date] from abc
where convert(varchar(10), date1, 120) between '2017-05-02' and '2017-07-02'
Another approach to fetch the between two dates data:
select (datename(Month, date1)+' '+convert(varchar(2), date1, 103)) as [Date] from abc
where date1 >= (DATEADD(Month, -3, getdate())) AND date1 <=getdate();
And the returned result is:
this is the Fiddle where you can test this query -> SQL FIDDLE
Simple and easy...good luck bro :)
Try this:
DECLARE #Start DATE ='2017-04-01',
#End DATE ='2017-07-31'
SELECT *, Datename(mm, date),
Dateadd(mm, Datediff(mm, 0, date), 0) AS FirstDateOfMonth,
Dateadd (dd, -1, Dateadd(mm, Datediff(mm, 0, date) + 1, 0)) as
LastDateOfMonth
FROM dbo.TableName
WHERE Cast(date AS DATE) BETWEEN #Start AND #End
If this is not just a one-off report, then I would create a calendar table, and use that to "group by". This will also let you do many other date related calculations.
You can find one at simple calendar or one here at Stackoverflow
Then your code could look like this:
SELECT top 10000 * FROM dbo.calendar DD
WHERE DD.TimeStampFrom>='2017-04-01' AND DD.TimeStampFrom <='2017-07-31'
AND DAY(DD.TimeStampFrom)=1
I created a stored procedure for that, may be you can convert that into user defined Function.
Posting that code below,
create procedure ListMonths
#date1 date,#date2 date
as
begin
create Table #tempTable
(mnth varchar(10))
while #date1<#date2
begin
insert into #tempTable
select DATENAME(month,#date1)
set #date1 = DATEADD(MONTH,1,#date1)
end
select * from #tempTable;
drop table #tempTable;
end
To execute the stored procedure:
exec ListMonths '2017-04-01','2018-01-31'
output
+------------+
| mnth |
+------------+
| April |
| May |
| June |
| July |
| August |
| September |
| October |
| November |
| December |
| January |
+------------+
result

Draw a dynamic table in SQL

Good morning, I'm trying to draw a dynamic table with some data, this query, draw a table that has a day, his week, and some data that I want to calculate dynamically.
This is my query
use Alfri
;with monthDates
as
(
select DATEADD(month, 0, CONVERT(DATE,'2013-09-09',102)) as d
,DATEPART(week, DATEADD(month, datediff(month, 0, '2013-09-09'),0)) as w,
(
SELECT SUM(
CASE WHEN arrive_yard IS NOT NULL THEN
DATEDIFF(mi, Solicitado, Libre)-DATEDIFF(mi, arrive_yard, leave_yard)
ELSE
DATEDIFF(mi, Solicitado, Libre)
END
) as Tiempo
FROM MovimientoHoras
WHERE CONVERT(DATE, Solicitado, 102) = '2013-10-11'
) as info
union all
select DATEADD(day, 1, d)
,DATEPART(week, DATEADD(day, 1, d))
, info
from monthDates
where d < DATEADD(month, datediff(month, 0, '2013-10-09')+1,-1)
)
SELECT * FROM monthDates
This query draw me a table like this.
d |w |info |
2013-09-09 | 36 | 2780|
2013-09-10 | 37 | 2780|
2013-09-11 | 37 | 2780|
2013-09-12 | 37 | 2780|
2013-09-13 | 37 | 2780|
2013-09-14 | 37 | 2780|
2013-09-15 | 37 | 2780|
2013-09-16 | 37 | 2780|
But the info's column isn't calculling dynamically and this is my dilenma.
The point is that column d is calculated dynamically and that's the value that I want to use in info's column query, something like this WHERE CONVERT(DATE, Solicitado, 102) = d) as info instead of WHERE CONVERT(DATE, Solicitado, 102) = '2013-10-11') as info where D is the date changing in every row, the way that I'm trying it just giving me same data of '2013-10-11'
Something like a While to change a day in that subquery
Thanks
The basic approach is to separate the part that generates dates from the part that calculates the info for that date:
;with monthDates as (
select
cast('20130909' as date) as d,
datepart(week, dateadd(month, datediff(month, 0, '2013-09-09'), 0)) as w
union all
select
dateadd(day, 1, d),
datepart(week, dateadd(day, 1, d))
from
monthDates
where
d < dateadd(month, datediff(month, 0, '2013-10-09') + 1, -1)
)
select
m.d,
m.w,
sum(
datediff(mi, Solicitado, Libre)
- case when arrive_yard is not null then
datediff(mi, arrive_yard, leave_yard)
else 0 end
) info
from
monthDates m
left outer join
MovimientoHoras h
on cast(Solicitado as date) = m.d
group by
m.d,
m.w
Example SQLFiddle