How to get Count and Sum based on ID - sql-server

I have a following table called lobby
QueueID FkBranch IsActive Status AddedLocalTime CompletedTime FkAssistTypeID
553279 16 1 5 7/12/2019 20:06 7/12/2019 21:10 2
553278 16 1 5 7/12/2019 20:07 7/12/2019 21:11 1
553277 16 1 5 7/12/2019 20:08 7/12/2019 21:10 1
553276 16 1 5 7/12/2019 20:09 7/12/2019 21:11 1
553275 16 1 5 7/13/2019 20:10 7/13/2019 21:10 2
553274 16 1 5 7/13/2019 20:11 7/13/2019 21:11 2
553278 17 1 5 7/14/2019 20:07 7/14/2019 21:11 1
553277 17 1 5 7/14/2019 20:08 7/14/2019 21:10 1
553276 18 1 5 7/14/2019 20:09 7/14/2019 21:11 2
553275 18 1 5 7/15/2019 20:10 7/15/2019 21:10 2
553274 18 1 5 7/15/2019 20:11 7/15/2019 21:11 2
And Branch table and Its data as follows
BranchID BranchName IsActive
16 Delhi 1
17 Karnataka 1
18 Telangana 1
Now I need to get a count of FkAssistTypeID of each location between AddedLocalTime and also need to take summation of the time difference of AddedLocalTime and CompletedTime.
I have a function to get the time Difference of two dates and it as follows
dbo.fnTimetoSeconds(AddedLocalTime, CompletedTime, NULL)
CREATE FUNCTION [dbo].[fnTimetoSeconds]
(
#dateOne DATETIME,#dateTwo DATETIME,#dateToConvert DATETIME
)
RETURNS INT
AS
BEGIN
DECLARE #date DATETIME
DECLARE #retValue INT
IF(#dateToConvert IS NULL)
BEGIN
SET #dateToConvert = CASE WHEN(#dateTwo>#dateOne) THEN #dateTwo-#dateOne ELSE #dateOne-#dateTwo END
END
SET #date = DATEADD(D, 0, DATEDIFF(D, 0, #dateToConvert))
IF(DATEPART(yy,#dateToConvert) = 1900)
BEGIN
SET #retValue = DATEDIFF(s,#date,#dateToConvert) + CASE WHEN DATEDIFF(D, 0, #dateToConvert) > 0 THEN DATEDIFF(D, 0, #dateToConvert) ELSE 0 END * 3600 * 24
END
ELSE
BEGIN
SET #retValue = DATEDIFF(s,#date,#dateToConvert)
END
RETURN #retValue
END
My expected output is,
* Please be noted, This Average column need to calculate, Suppose
when FkAssistTypeID = 1 and AddedLocalTime between 7/12/2019 and 7/14/2019 then by passing that row's AddedLocalTime and CompletedTime values fnTimetoSeconds taking time differance and take the summation of each time diffrences and divide it by count .
I need to add the above output to a temporary table. How can I do this?
I just tried this, but this is not the expected
select
b.BranchId AS ID,
b.BranchName,
count(case l.FkAssistTypeId when 1 then 1 end) as CountOf1,
SUM(CASE WHEN (l.FkAssistTypeId = 1) THEN COALESCE((dbo.fnTimetoSeconds(CompletedTime, AssistedTime, NULL)),0) ELSE 0 END) AS Average
from Branch b left join Lobby l
on b.BranchId = l.FkBranchId
where l.IsActive = 1 AND b.IsTestBranch = 0 AND CAST(l.AddedLocalTime as DATE) = '2019-07-12'
group by b.BranchId, b.BranchName

How about something like this :
SELECT Vals.ID,
Vals.BranchName ,
Vals.CountOf1,
CASE WHEN (Vals.CountOf1 = 0) THEN 0 ELSE Vals.mySum/Vals.CountOf1 END as AveSecs
INTO tmpTbl
FROM
(SELECT
b.BranchId AS ID,
b.BranchName,
count(l.FkAssistTypeI) as CountOf1,
SUM(DATEDIFF(second, CompletedTime, AssistedTime)) AS mySum
FROM Branch b
LEFT OUTER JOIN Lobby l
ON b.BranchId = l.FkBranchId
WHERE l.IsActive = 1
AND b.IsTestBranch = 0
AND l.FkAssistTypeI = 1
AND l.AddedLocalTime >= '2019/07/12'
AND l.AddedLocalTime < DATEADD(day, 1, '2019/07/15')
GROUP BY b.BranchId, b.BranchName) as Vals
The query first gets the count and the sum between 2019/07/12 00:00 and 2019/07/15 00:00 (not including). Then it inserts into a temp table (as you indicated) while calculating the average value. Note that you will need to test it and adjust it a bit since I have not run it. Also, I just used date diff to calculate the time in seconds, but you can use your function if needed. They should come up with the same value most times.

Not sure why you use the scalar function to get the date difference, even with the current logic, it's possible to do it in the query without the need of the function.
SELECT
b.BranchID
, b.BranchName
, ISNULL(l.CountOF1,0) CountOF1
, ISNULL(l.Average ,0) Average
FROM
#Branch b
LEFT JOIN (
SELECT
FkBranch
, IsActive
, COUNT(*) CountOF1
, AVG(DATEDIFF(SECOND, AddedLocalTime, CompletedTime)) Average
FROM #lobby l
WHERE
FkAssistTypeID = 1
AND AddedLocalTime BETWEEN '7/12/2019 00:00:00' AND '7/14/2019 23:59:59'
GROUP BY FkBranch, IsActive
) l ON l.FkBranch = b.BranchID AND l.IsActive = b.IsActive
WHERE
b.IsActive = 1

Related

How to get date ranges according to date data inside database table

I have a #TMP table filled with dates with its corresponding remarks. It looks like this:
Date isworkdays isweekdays
2019-08-16 1 1
2019-08-17 0 0
2019-08-18 0 0
2019-08-19 1 1
2019-08-20 1 1
2019-08-21 1 1
2019-08-22 1 1
2019-08-23 1 1
2019-08-24 0 0
2019-08-25 0 0
2019-08-26 1 1
2019-08-27 1 1
2019-08-28 1 1
2019-08-29 1 1
2019-08-30 1 1
2019-08-31 0 0
2019-09-01 0 0
2019-09-02 1 1
2019-09-03 1 1
2019-09-04 1 1
2019-09-05 1 1
2019-09-06 1 1
2019-09-07 0 0
2019-09-08 0 0
2019-09-09 1 1
2019-09-10 1 1
2019-09-11 1 1
2019-09-12 1 1
2019-09-13 1 1
2019-09-14 0 0
2019-09-15 0 0
2019-09-16 1 1
2019-09-17 1 1
2019-09-18 1 1
2019-09-19 1 1
2019-09-20 1 1
2019-09-21 0 0
2019-09-22 0 0
2019-09-23 1 1
2019-09-24 1 1
2019-09-25 1 1
2019-09-26 1 1
2019-09-27 1 1
2019-09-28 0 0
2019-09-29 0 0
2019-09-30 1 1
Date column are obviously a series of dates from 2019-08-16 to 2019-10-16
isworkingdays column indicates "1" if the date is from Monday to Friday, and "0" if its Saturday and Sunday. isweekdays column indicates "1" if the date is not holiday and "0" if its holiday.
I want to count the days betweem 2019-08-16 to 2019-09-16 only to those dates with isworkdays = 1, and for Saturday and Sunday count it as 1
This is what I have done so far
declare #userinput date
SELECT SUM(IsWorkDay) AS DayCount FROM AMIFIN..PDC T
LEFT JOIN dbo.#TMP c ON c.[Date] <= T.Check_Date
WHERE CAST(#userinput AS Date) <= CAST((c.[Date]) as date) AND c.IsWorkDay = 1
but it returns 3952743 days
Conditional aggregation should work here:
SELECT
SUM(CASE WHEN isworkdays = 1 THEN 1 ELSE 0.5 END) AS DayCount
FROM AMIFIN..PDC T
LEFT JOIN dbo.#TMP c
ON c.[Date] <= T.Check_Date
WHERE
#userinput < c.[Date];
This idea here is to count 1 for a weekday, and 1/2 for a weekend day. The half counting logic is necessary, because it could be that we end up counting an odd number of weekend days.
I have figured it out. Thanks to all who posted an answer it gives me a lot of idea.
Here is my solution:
Declare #cnt INT = 0
DECLARE #postponedate date = cast('2019-08-20' as date); --Will be the current date of postponement
DECLARE #checkdate date = cast('2019-08-27' as date); --Date of the check
Declare #fdate date = (select [Date] FROM #TMP WHERE [Date] = #checkdate); --dates to be compared
WHILE #postponedate <= #fdate
BEGIN
DECLARE #IsWorkDay INT = (select IsWorkDay FROM #TMP WHERE cast([Date] as date) = #postponedate)
DECLARE #IsWeekDay INT = (select IsWeekDay FROM #TMP WHERE cast([Date] as date) = #postponedate)
SET #cnt = CASE WHEN #IsWorkDay = 1 THEN
#cnt + 1
ELSE
#cnt + 0.5
END
SET #postponedate = DATEADD(Day, 1, #postponedate)
PRINT #postponedate
PRINT #cnt
END
I think your query will be like this:
SELECT SUM(DATEDIFF(DAY, date, date) + 1) AS Total
FROM tbl_TMP
WHERE date >= '2019-08-16' AND date <= '2019-09-30'
AND isworkdays=1 AND isweekdays=1
GROUP BY isworkdays,isweekdays
Note: "+1" because DATEDIFF returns No. of days in INT DataType

How to insert "empty" row extracting a month list?

I've this sp, which return a list of data, for each "month" (i.e. each row is a month). Somethings like that:
SELECT
*,
(CAST(t1.NumActivities AS DECIMAL) / t1.NumVisits) * 100 AS PercAccepted,
(CAST(t1.Accepted AS DECIMAL) / t1.Estimated) * 100 AS PercValue
FROM
(SELECT
MONTH(DateVisit) AS Month,
COUNT(*) AS NumVisits,
SUM(CASE WHEN DateActivity is not null THEN 1 ELSE 0 END) AS NumActivities,
SUM(Estimate) AS Estimated,
SUM(CASE WHEN DateActivity is not null THEN Estimate ELSE 0 END) AS Accepted
FROM [dbo].[Activities]
WHERE
DateVisit IS NOT NULL
AND (#year IS NULL OR YEAR(DateVisit) = #year)
AND (#clinicID IS NULL OR ClinicID = #clinicID)
GROUP BY MONTH(DateVisit)) t1
This is a result:
Month NumVisits NumActivities Estimated Accepted PercAccepted PercValue
1 5 1 13770.00 2520.00 20.00000000000 18.30065359477124
2 2 2 7900.00 7900.00 100.00000000000 100.00000000000000
3 1 0 2730.00 0.00 0.00000000000 0.00000000000000
8 1 1 3000.00 3000.00 100.00000000000 100.00000000000000
But as you can see, I could "miss" some Month (for example, here April "4" is missed).
Is it possible to insert, for the missing month/row, an empty (0) record? Such as:
Month NumVisits NumActivities Estimated Accepted PercAccepted PercValue
1 5 1 13770.00 2520.00 20.00000000000 18.30065359477124
2 2 2 7900.00 7900.00 100.00000000000 100.00000000000000
3 1 0 2730.00 0.00 0.00000000000 0.00000000000000
4 0 0 0 0 0 0
...
Here is a example with sample data:
CREATE TABLE #Report
(
Id INT,
Name nvarchar(max),
Percentage float
)
INSERT INTO #Report VALUES (1,'ONE',2.01)
INSERT INTO #Report VALUES (2,'TWO',3.01)
INSERT INTO #Report VALUES (5,'Five',5.01)
;WITH months(Month) AS
(
SELECT 1
UNION ALL
SELECT Month+1
FROM months
WHERE Month < 12
)
SELECT *
INTO #AllMonthsNumber
from months;
Your select query:
The left join will gives you the NULL for other months so just use ISNULL('ColumnName','String_to_replace')
\/\/\/\/
SELECT Month, ISNULL(Name,0), ISNULL(Percentage,0)
FROM AllMonthsNumber A
LEFT JOIN #Report B
ON A.Month = B.Id
EDIT:
Yes you can do it without creating AllMonthNumber Table:
You can use master..spt_values (found here) system table which contains the numbers so just with some where condition.
SELECT Number as Month, ISNULL(B.Name,0), ISNULL(Percentage,0)
FROM master..spt_values A
LEFT JOIN #Report B ON A.Number = B.Id
WHERE Type = 'P' AND number BETWEEN 1 AND 12

How to Group Items in a Count statement

I'm trying to create a query that will return Total Claims reported in 0-3 days, 4-7 days, 8-14 days, and 15+
Select DATEDiff(DD,LossDate,DateReported) As TimeToReport,Count(ClaimId) As Num from LossRun
where PolicyNum='1234567890'
And PolTerm='201403'
Group By DATEDiff(DD,LossDate,DateReported)
order by DATEDiff(DD,LossDate,DateReported);
This is what i get
TimeToReport NumofClaims
0 5
1 3
2 1
3 4
4 3
5 2
6 2
7 2
8 1
12 1
13 1
14 2
15 2
48 1
52 1
107 1
121 1
147 1
533 1
Basically i want to see the total for 0-3, 4-7,8-14,and the rest,,,, timeToReport
You can try to use SUM with CASW WHEN
select
SUM(CASW WHEN TimeToReport <= 3 THEN NumofClaims ELSE 0 END) '0~3 day',
SUM(CASW WHEN TimeToReport >= 4 AND TimeToReport <=7 THEN NumofClaims END) '4-7 days',
SUM(CASW WHEN TimeToReport >= 8 AND TimeToReport <=14 THEN NumofClaims ELSE 0 END) '8-14 days',
SUM(CASW WHEN TimeToReport >= 15 THEN NumofClaims ELSE 0 END) '15+ day'
from (
Select DATEDiff(DD,LossDate,DateReported) As TimeToReport,Count(ClaimId) As Num
from LossRun
where PolicyNum='1234567890'
And PolTerm='201403'
Group By DATEDiff(DD,LossDate,DateReported)
) t
The most simple way is going to be by creating your own temp table which includes the min and max for each bucket and then joining to it.
declare #t table (OrderedID int, EmpID int, EffDate date, Salary money)
insert into #t
values
(1,1234,'20150101',1)
,(2,1234,'20160101',2)
,(3,1234,'20170101',8)
,(4,1234,'20180101',15)
,(1,2351,'20150101',17)
,(5,1234,'20190101',4)
,(5,1234,'20190101',2)
,(5,1234,'20190101',9)
declare #Bin table (MinVal int, MaxVal int)
insert into #Bin
values
(1,3)
,(4,6)
,(7,9)
,(10,15)
,(15,20)
,(20,25)
Select
B.MinVal,count(T.EmpID) as EmpsInBin
From #t T
inner join #Bin B on T.Salary between B.MinVal and B.MaxVal
group by B.MinVal
Output
MinVal EmpsInBin
1 3
4 1
7 2
10 1
15 2

T-SQL to create one data point for each hour over past 24 hours

Please how may we do this:
1) Generate 24 rows one for each hour from current time back 24 hours
2) Aggregate data from another table over the past 24 hours into these 24 data points.
I have seen solutions suggesting number tables from 0-23, but these might make it difficult if we need this to start from NOW, then run back 24 hours Get every hour for a time range
e.g [5:00am, 4:00am, 3:00am ... 12:am, 11pm ... 7:00am,6:00am]
Source Table:
select d,h,count(1)cnt from msgs
where dt>= DateAdd(hh, -24, sysdatetime())
group by d,h order by 1 desc,2 desc
Sample Data
d h cnt
2015-06-05 16 11
2015-06-05 13 44
2015-06-05 12 16
2015-06-05 11 31
2015-06-05 10 10
2015-06-05 9 12
2015-06-05 8 1
2015-06-04 21 1
2015-06-04 20 2
2015-06-04 18 5
2015-06-04 16 2
I have missing hours, i would need a query that fills out the missing hours with 0
As an alternative solution, you could use this query to provide all 24 hour ranges. Then simply aggregate and sum these values against your original query to return only 24 rows.
;WITH hrs AS
(
SELECT h = 1
UNION ALL
SELECT h + 1
FROM hrs
WHERE h + 1 <= 24
)
SELECT
d = left(convert(varchar(50),DateAdd(hour, -1 * h, getdate()), 21),10),
h = DatePart(hour, DateAdd(hour, -1 * h, getdate())),
cnt = 0
FROM hrs
You could try joining to this function:
CREATE FUNCTION ufn_Last24Hrs
(
#start DateTime2(7)
)
RETURNS #Result TABLE (d char(10), h int)
AS
BEGIN
DECLARE #current DateTime2(7) = #start
WHILE (#current > DateAdd(hour, -24, #start))
BEGIN
INSERT INTO #Result
VALUES
(
REPLACE(CONVERT(char(10), #current, 102) , '.', '-'),
DATEPART(hour, #current)
)
SET #current = DateAdd(hour, -1, #current)
END
RETURN;
END;
GO
SELECT * FROM ufn_Last24Hrs(SYSDATETIME());
SELECT
d,h,COUNT(1)cnt
FROM
ufn_Last24Hrs(SYSDATETIME()) hrs
left join msgs
ON msgs.d = hrs.d
and msgs.h = hrs.h
WHERE dt>= DateAdd(hour, -24, SYSDATETIME())
GROUP BY d,h
ORDER BY 1 DESC, 2 DES

Trying avoid using cursor

I have been given a query and trying to figure out a way to remove the cursor yet maintaining functionality, because the starting table can get into the millions of rows.
Example of data in table:
ID DollarValue Month RowNumber
1 $10 1/1/2014 1
1 $15 2/1/2014 2
1 -$40 3/1/2014 3
1 $50 4/1/2014 4
2 -$11 1/1/2014 1
2 $11 2/1/2014 2
2 $5 3/1/2014 3
Expected results:
ID DollarValue Month RowNumber TestVal
1 $10 1/1/2014 1 1
1 $15 2/1/2014 2 0
1 -$40 3/1/2014 3 -1
1 $50 4/1/2014 4 1
2 -$11 1/1/2014 1 -1
2 $11 2/1/2014 2 0
2 $5 3/1/2014 3 1
Here is the logic (pseudocode)that happens inside the cursor:
If a #ID <> #LastId AND #Month <> #LastMonth
Set #RunningTotal = #DollarValue
Set #LastMonth = '12/31/2099'
Set #LastID = #ID
Set #TestVal = Sign(#DollarValue)
Else
If Sign(#RunningTotal) = Sign(#RunningTotal + #DollarValue)
Set #TestVal = 0
Else
Set #TestVal = Sign(#DollarValue)
Set #RunningTotal = #RunningTotal + #DollarValue
Any idea how I can change this to set based?
You can use the windowed version of SUM to calculate running totals:
;WITH CTE AS (
SELECT ID, DollarValue, Month, RowNumber,
SUM ( DollarValue ) OVER (PARTITION BY ID ORDER BY RowNumber) as RunningTotal
FROM #mytable
)
SELECT C1.ID, C1.DollarValue, C1.Month, C1.RowNumber,
CASE WHEN C1.RowNumber = 1 THEN SIGN(C1.DollarValue)
WHEN SIGN(C1.RunningTotal) = SIGN(C2.RunningTotal) THEN 0
ELSE SIGN(C1.RunningTotal)
END AS TestVal
FROM CTE AS C1
LEFT JOIN CTE AS C2 ON C1.ID = C2.ID AND C1.RowNumber = C2.RowNumber + 1
Using LEFT JOIN on RowNumber you can get the previous record and compare the current running total with the previous one. Then use a simple CASE to apply rules pertinent to changes in SIGN of running total.
SQL FIDDLE Demo
P.S. It seems the above solution wont work in versions prior to SQL Server 2012. In this case the running total calculation inside the CTE has to be replaced by the "conventional" version.
This is 2008 solution
WITH CTE AS (
SELECT
AA.[ID]
,AA.[Month]
,AA.[RowNumber]
,AA.[DollarValue]
,SIGN(SUM(BB.[DollarValue])) AS RunTotalSign
FROM YourTable AS AA
LEFT JOIN YourTable AS BB
ON (AA.[ID] = BB.[ID] AND BB.[RowNumber] <= AA.[RowNumber])
GROUP BY AA.[ID],AA.[Month],AA.[DollarValue],AA.[RowNumber])
)
SELECT
AA.[ID]
,AA.[Month]
,AA.[RowNumber]
,AA.[DollarValue]
,CASE WHEN AA.RunTotalSign = CC.RunTotalSign Then 0
ELSE AA.RunTotalSign
END
AS TestVal
FROM CTE AS AA
LEFT JOIN CTE AS CC
ON (AA.[ID] = CC.[ID] AND AA.[RowNumber] = CC.[RowNumber]+1)

Resources