I have an sql table that has transaction history of all the clients. I want to find what is the average difference in time between two transactions.
ClientCode Date
DL2xxx 2016-04-18 00:00:00.000
DL2xxx 2016-04-18 00:00:00.000
E19xxx 2016-04-18 00:00:00.000
E19xxx 2016-04-18 00:00:00.000
E19xxx 2016-04-18 00:00:00.000
JDZxxx 2016-04-18 00:00:00.000
Given above are the first few lines of the table the date given is the date transaction happened. I want to take an average of difference in days when successive transactions happen. Say for a client he makes transactions of Day 1, Day 3, Day 10, and Day 15. So differences are {2, 7, 5} average of which is 4.66. If only one transaction takes place this should be 0.
ClientCode AverageDays
DL2xxx <float_value>
DL2xxx <float_value>
E19xxx <float_value>
This is what the output should look like where each unique client code occurs only once.
You can use a query like below if you table name is T
see live demo
select
ClientCode,
AvgDays =ISNULL(AVG(d),0)
from
(
select
*,
d=DATEDIFF(
d,
dateofT,
LEAD(DateofT) over(
partition by ClientCode
order by DateofT asc ))
from t
)t
group by ClientCode
If Windowing functions aren't available to you, here's an alternative
--CREATE SAMPLE DATA
CREATE TABLE #TMP(ClientID INT, EventDate DATE)
GO
INSERT INTO #TMP VALUES
(1,DATEADD(DD,RAND()*365,'20180101'))
,(2,DATEADD(DD,RAND()*365,'20180101'))
,(3,DATEADD(DD,RAND()*365,'20180101'))
,(4,DATEADD(DD,RAND()*365,'20180101'))
,(5,DATEADD(DD,RAND()*365,'20180101'))
GO 50
--PRE SQL 2012 Compatible
SELECT A.ClientID
,AVG(DATEDIFF(DD,C.EventDate,A.Eventdate)) AS ClientAvg
FROM #TMP A
CROSS APPLY (SELECT ClientID, MAX(EventDate) EventDate FROM #TMP B
WHERE A.ClientID = B.ClientID AND A.EventDate > B.EventDate
GROUP BY ClientID) C
GROUP BY A.ClientID
ORDER BY A.ClientID
You can use LAG() function to compare a date to it's previous date by client, then group by client and calculate the average.
IF OBJECT_ID('tempdb..#Transactions') IS NOT NULL
DROP TABLE #Transactions
CREATE TABLE #Transactions (
ClientCode VARCHAR(100),
Date DATE)
INSERT INTO #Transactions (
ClientCode,
Date)
VALUES
('DL2', '2016-04-18'),
('DL2', '2016-04-19'),
('DL2', '2016-04-26'),
('E19', '2016-01-01'),
('E19', '2016-01-11'),
('E19', '2016-01-12')
;WITH DayDifferences AS
(
SELECT
T.ClientCode,
T.Date,
DayDifference = DATEDIFF(
DAY,
LAG(T.Date) OVER (PARTITION BY T.ClientCode ORDER BY T.Date ASC),
T.Date)
FROM
#Transactions AS T
)
SELECT
D.ClientCode,
AverageDayDifference = AVG(ISNULL(CONVERT(FLOAT, D.DayDifference), 0))
FROM
DayDifferences AS D
GROUP BY
D.ClientCode
Using the observation that the sum of differences within a group is simply the max - min of that group, you can use the simple group by select:
select IIF(COUNT(*) > 1,
(CAST(DATEDIFF(day, MIN(DateofT), MAX(DateofT)) AS FLOAT)) / (COUNT(*) - 1), 0.0)
AS AVGDays, ClientCode
FROM t GROUP BY ClientCode
Related
I was trying to select a record between two date ranges but I keep getting duplicate record when two date range overlaps as shown below.
Here is an example.
Policy Info
Policy # Policy Effective Date Policy termination date Year
001 2018-10-01 2019-10-01 2018
002 2019-10-01 2020-10-01 2019
003 2020-10-01 2021-10-01 2020
004 2021-10-01 2022-10-01 2022
Policy Limit
LimitID Effective Date Termination Date Limit
1 2018-10-01 2021-10-01 1000
2 2018-10-01 3000-01-01 2500
How can I select Limit ID: 1 for Policy #: 001,002 003 or for the years 2018, 2019, 2020 and for any policy effective date greater than 2021-01-01 use Limit ID = 2
I tried the following but it keeps creating dupicate
((limit.effective_from_date < policy.effective_to_date
AND limit.effective_to_date > policy.effective_from_date
)
OR
(limit.effective_from_date = policy.effective_from_date
AND limit.effective_to_date = CONVERT(datetime, '01/01/3000', 102)))
but the above condition creates a duplicate. Is there any effective way of selecting a record within overlapping date ranges.
Any help will be appreciated!
Your problem is that you have overlapping periods for Policy Limits and you need to choose one. For what I understand from your data and I'm inferring a lot, you need to get the first limit for the FIRST period that it's [Policy Limit].[Effective Date] is earlier than the [Policy Info].[Policy Effective Date]
while [Policy Limit].[Termination Date] is later than [Policy Info].[Policy Termination Date].
If all my guessing is correct, you can do something like
drop table if exists #PolicyInfo
drop table if exists #PolicyLimit
CREATE TABLE #PolicyInfo (
Policy INT,
Policy_Effective_Date DATE,
Policy_termination_date DATE,
[Year] int
)
CREATE TABLE #PolicyLimit(
LimitID INT,
Effective_Date DATE,
Termination_Date DATE,
Limit INT
)
INSERT INTO #PolicyInfo (Policy, Policy_Effective_Date, Policy_termination_date, [Year])
VALUES
(001, '2018-10-01', '2019-10-01', 2018),
(002, '2019-10-01', '2020-10-01', 2019),
(003, '2020-10-01', '2021-10-01', 2020),
(004, '2021-10-01', '2022-10-01', 2022)
INSERT INTO #PolicyLimit (LimitID, Effective_Date, Termination_Date, Limit)
VALUES
(1, '2018-10-01','2021-10-01',1000),
(2, '2018-10-01','3000-01-01',2500)
;with cte AS (
-- Join PolicyInfo with PolicyLimit
-- condition: Policy_Effective_Date are between Effective_Date, pl.Termination_Date
-- AND
-- Policy_Termination_Date are between Effective_Date, pl.Termination_Date
SELECT *,
-- rank with partion by Policy
ROW_NUMBER() OVER (PARTITION BY [pi].Policy ORDER BY pl.Effective_Date, pl.Termination_Date) rn
FROM #PolicyInfo [pi]
INNER JOIN #PolicyLimit pl ON
[pi].Policy_Effective_Date BETWEEN pl.Effective_Date AND pl.Termination_Date
AND [pi].Policy_termination_date BETWEEN pl.Effective_Date AND pl.Termination_Date
)
SELECT Policy, LimitID
FROM cte
WHERE rn = 1 -- Select the first Limit per partition
I have a table of data which looks like this
ID CreatedDate
A123 2015-01-01
B124 2016-01-02
A125 2016-01-03
A126 2016-01-04
What I would like to do is group by month (as text) for this year only. I have some up with the following query but it returns data from all years not just this one:
Select Count(ID), DateName(month,createddate) from table
Where (DatePart(year,createddate)=datepart(year,getdate())
Group by DateName(month,createddate)
This returns
Count CreatedDate
4 January
Instead of
Count CreatedDate
3 January
Where have I gone wrong? I'm sure it's something to do with converting the date to month where it goes wrong
Just tested your code:
;WITH [table] AS (
SELECT *
FROM (VALUES
('A123', '2015-01-01'),
('B124', '2016-01-02'),
('A125', '2016-01-03'),
('A126', '2016-01-04')
) as t(ID, CreatedDate)
)
SELECT COUNT(ID),
DATENAME(month,CreatedDate)
FROM [table]
WHERE DATEPART(year,CreatedDate)=DATEPART(year,getdate())
GROUP BY DATENAME(month,CreatedDate)
Output was
3 January
I removed ( near WHERE
select count(id) as Count,
case when month(createddate)=1 THEN 'Januray' END as CreatedDate
from [table]
--where year(createddate)=2016 optional if you only want the 2016 count
group by month(createddate),year(createdDate)
I'm trying to get sum of budget transaction line by month .Is there an error on my Query.
The result of my query is :
RECIDLine RecIDHeader Date Amount
5637157326 5637149076 2012-08-01 00:00:00.000 850.00
5637157342 5637149079 2012-12-01 00:00:00.000 1000.00
5637157343 5637149079 2012-12-01 00:00:00.000 80.00
5637157344 5637149079 2012-12-01 00:00:00.000 2700.00
But i want to get somthing like this :
RECIDLine RecIDHeader Date Amount
5637157326 5637149076 2012-08-01 00:00:00.000 850.00
5637157342 5637149079 2012-12-01 00:00:00.000 3780.00
This is my query :
IF OBJECT_ID('tempdb..#BudgetTransTmp') IS NOT NULL
DROP TABLE #BudgetTransTmp
Select
BudgetTransactionLine.RECID AS RecIdLine,
BUDGETTRANSACTIONHEADER.RECID AS RecIdHeader,
BudgetTransactionLine.DATE,
SUM(CAST((BudgetTransactionLine.TransactionCurrencyAmount ) as decimal(18,2))) AS Amount
INTO #BudgetTransTmp
FROM MicrosoftDynamicsAX.dbo.BudgetTransactionLine AS BudgetTransactionLine
INNER JOIN MicrosoftDynamicsAX.dbo.BUDGETTRANSACTIONHEADER AS BUDGETTRANSACTIONHEADER
ON BUDGETTRANSACTIONHEADER.RECID = BudgetTransactionLine.BUDGETTRANSACTIONHEADER
AND BUDGETTRANSACTIONHEADER.budgetTransactionType = '3'
AND BUDGETTRANSACTIONHEADER.PARTITION = #Partition
WHERE BudgetTransactionLine.PARTITION =#Partition
AND BudgetTransactionCode.DATAAREAID = 'USMF'
AND BudgetTransactionLine.DATE >= PeriodCalandarTmp.StartDate
AND BudgetTransactionLine.DATE <= PeriodCalandarTmp.EndDate
GROUP BY BudgetTransactionLine.DATE,
BUDGETTRANSACTIONHEADER.RECID,
BudgetTransactionLine.RECID
select * from #BudgetTransTmp
And I need to keep BudgetTransactionLine.RECID in select
You should not include BudgetTransactionLine.RecId (RecIdLine) in your GROUP BY.
If you need this column, then you must use one of Aggregate Function (for example in the SELECT part MIN(BudgetTransactionLine.RecId) AS RecIdLine.
just put you whole data in Simple CTE and just join with your Temp table. Please go through the query carefully insert your code according to that
Below the Sample data
declare #Table1 TABLE
(RECIDLine BIGint, RecIDHeader BIGint, Date varchar(30), Amount DECIMAL(18,2))
;
INSERT INTO #Table1
(RECIDLine, RecIDHeader, Date, Amount)
VALUES
(5637157326, 5637149076, '2012-08-01 00:00:00.000', 850.00),
(5637157342, 5637149079, '2012-12-01 00:00:00.000', 1000.00),
(5637157343, 5637149079, '2012-12-01 00:00:00.000', 80.00),
(5637157344, 5637149079, '2012-12-01 00:00:00.000', 2700.00)
;
;WITH CTE AS (
select RecIDHeader,Date,SUM(Amount)Amount,ROW_NUMBER()OVER(PARTITION BY RecIDHeader ORDER BY Date)RN from #Table1
GROUP BY RecIDHeader,Date )
Select T.RECIDLine,C.RecIDHeader,C.Amount,C.Amount FROM CTE C
INNER JOIN (select MIN(RECIDLine) RECIDLine, RecIDHeader from #Table1
GROUP BY RecIDHeader)T
ON T.RecIDHeader = C.RecIDHeader
I have a table in MSSQL with the following structure:
PersonId
StartDate
EndDate
I need to be able to show the number of distinct people in the table within a date range or at a given date.
As an example i need to show on a daily basis the totals per day, e.g. if we have 2 entries on the 1st June, 3 on the 2nd June and 1 on the 3rd June the system should show the following result:
1st June: 2
2nd June: 5
3rd June: 6
If however e.g. on of the entries on the 2nd June also has an end date that is 2nd June then the 3rd June result would show just 5.
Would someone be able to assist with this.
Thanks
UPDATE
This is what i have so far which seems to work. Is there a better solution though as my solution only gets me employed figures. I also need unemployed on another column - unemployed would mean either no entry in the table or date not between and no other entry as employed.
CREATE TABLE #Temp(CountTotal int NOT NULL, CountDate datetime NOT NULL);
DECLARE #StartDT DATETIME
SET #StartDT = '2015-01-01 00:00:00'
WHILE #StartDT < '2015-08-31 00:00:00'
BEGIN
INSERT INTO #Temp(CountTotal, CountDate)
SELECT COUNT(DISTINCT PERSON.Id) AS CountTotal, #StartDT AS CountDate FROM PERSON
INNER JOIN DATA_INPUT_CHANGE_LOG ON PERSON.DataInputTypeId = DATA_INPUT_CHANGE_LOG.DataInputTypeId AND PERSON.Id = DATA_INPUT_CHANGE_LOG.DataItemId
LEFT OUTER JOIN PERSON_EMPLOYMENT ON PERSON.Id = PERSON_EMPLOYMENT.PersonId
WHERE PERSON.Id > 0 AND DATA_INPUT_CHANGE_LOG.Hidden = '0' AND DATA_INPUT_CHANGE_LOG.Approved = '1'
AND ((PERSON_EMPLOYMENT.StartDate <= DATEADD(MONTH,1,#StartDT) AND PERSON_EMPLOYMENT.EndDate IS NULL)
OR (#StartDT BETWEEN PERSON_EMPLOYMENT.StartDate AND PERSON_EMPLOYMENT.EndDate) AND PERSON_EMPLOYMENT.EndDate IS NOT NULL)
SET #StartDT = DATEADD(MONTH,1,#StartDT)
END
select * from #Temp
drop TABLE #Temp
You can use the following query. The cte part is to generate a set of serial dates between the start date and end date.
DECLARE #ViewStartDate DATETIME
DECLARE #ViewEndDate DATETIME
SET #ViewStartDate = '2015-01-01 00:00:00.000';
SET #ViewEndDate = '2015-02-25 00:00:00.000';
;WITH Dates([Date])
AS
(
SELECT #ViewStartDate
UNION ALL
SELECT DATEADD(DAY, 1,Date)
FROM Dates
WHERE DATEADD(DAY, 1,Date) <= #ViewEndDate
)
SELECT [Date], COUNT(*)
FROM Dates
LEFT JOIN PersonData ON Dates.Date >= PersonData.StartDate
AND Dates.Date <= PersonData.EndDate
GROUP By [Date]
Replace the PersonData with your table name
If startdate and enddate columns can be null, then you need to add
addditional conditions to the join
It assumes one person has only one record in the same date range
You could do this by creating data where every start date is a +1 event and end date is -1 and then calculate a running total on top of that.
For example if your data is something like this
PersonId StartDate EndDate
1 20150101 20150201
2 20150102 20150115
3 20150101
You first create a data set that looks like this:
EventDate ChangeValue
20150101 +2
20150102 +1
20150115 -1
20150201 -1
And if you use running total, you'll get this:
EventDate Total
2015-01-01 2
2015-01-02 3
2015-01-15 2
2015-02-01 1
You can get it with something like this:
select
p.eventdate,
sum(p.changevalue) over (order by p.eventdate asc) as total
from
(
select startdate as eventdate, sum(1) as changevalue from personnel group by startdate
union all
select enddate, sum(-1) from personnel where enddate is not null group by enddate
) p
order by p.eventdate asc
Having window function with sum() requires SQL Server 2012. If you're using older version, you can check other options for running totals.
My example in SQL Fiddle
If you have dates that don't have any events and you need to show those too, then the best option is probably to create a separate table of dates for the whole range you'll ever need, for example 1.1.2000 - 31.12.2099.
-- Edit --
To get count for a specific day, it's possible use the same logic, but just sum everything up to that day:
declare #eventdate date
set #eventdate = '20150117'
select
sum(p.changevalue)
from
(
select startdate as eventdate, 1 as changevalue from personnel
where startdate <= #eventdate
union all
select enddate, -1 from personnel
where enddate < #eventdate
) p
Hopefully this is ok, can't test since SQL Fiddle seems to be unavailable.
Can someone steer me in the right direction for solving this issue with a set-based solution versus cursor-based?
Given a table with the following rows:
Date Value
2013-11-01 12
2013-11-12 15
2013-11-21 13
2013-12-01 0
I need a query that will give me a row for each date between 2013-11-1 and 2013-12-1, as follows:
2013-11-01 12
2013-11-02 12
2013-11-03 12
...
2013-11-12 15
2013-11-13 15
2013-11-14 15
...
2013-11-21 13
2013-11-21 13
...
2013-11-30 13
2013-11-31 13
Any advice and/or direction will be appreciated.
The first thing that came to my mind was to fill in the missing dates by looking at the day of the year. You can do this by joining to the spt_values table in the master DB and adding the number to the first day of the year.
DECLARE #Table AS TABLE(ADate Date, ANumber Int);
INSERT INTO #Table
VALUES
('2013-11-01',12),
('2013-11-12',15),
('2013-11-21',13),
('2013-12-01',0);
SELECT
DateAdd(D, v.number, MinDate) Date
FROM (SELECT number FROM master.dbo.spt_values WHERE name IS NULL) v
INNER JOIN (
SELECT
Min(ADate) MinDate
,DateDiff(D, Min(ADate), Max(ADate)) DaysInSpan
,Year(Min(ADate)) StartYear
FROM #Table
) dates ON v.number BETWEEN 0 AND DaysInSpan - 1
Next I would wrap that to make a derived table, and add a subquery to get the most recent number. Your end result may look something like:
DECLARE #Table AS TABLE(ADate Date, ANumber Int);
INSERT INTO #Table
VALUES
('2013-11-01',12),
('2013-11-12',15),
('2013-11-21',13),
('2013-12-01',0);
-- Uncomment the following line to see how it behaves when the date range spans a year end
--UPDATE #Table SET ADate = DateAdd(d, 45, ADate)
SELECT
AllDates.Date
,(SELECT TOP 1 ANumber FROM #Table t WHERE t.ADate <= AllDates.Date ORDER BY ADate DESC)
FROM (
SELECT
DateAdd(D, v.number, MinDate) Date
FROM
(SELECT number FROM master.dbo.spt_values WHERE name IS NULL) v
INNER JOIN (
SELECT
Min(ADate) MinDate
,DateDiff(D, Min(ADate), Max(ADate)) DaysInSpan
,Year(Min(ADate)) StartYear
FROM #Table
) dates ON v.number BETWEEN 0 AND DaysInSpan - 1
) AllDates
Another solution, not sure how it compares to the two already posted performance wise but it's a bit more concise:
Uses a numbers table:
Linky
Query:
DECLARE #SDATE DATETIME
DECLARE #EDATE DATETIME
DECLARE #DAYS INT
SET #SDATE = '2013-11-01'
SET #EDATE = '2013-11-29'
SET #DAYS = DATEDIFF(DAY,#SDATE, #EDATE)
SELECT Num, DATEADD(DAY,N.Num,#SDATE), SUB.[Value]
FROM Numbers N
LEFT JOIN MyTable M ON DATEADD(DAY,N.Num,#SDATE) = M.[Date]
CROSS APPLY (SELECT TOP 1 [Value]
FROM MyTable M2
WHERE [Date] <= DATEADD(DAY,N.Num,#SDATE)
ORDER BY [Date] DESC) SUB
WHERE N.Num <= #DAYS
--
SQL Fiddle
It's possible, but neither pretty nor very performant at scale:
In addition to your_table, you'll need to create a second table/view dates containing every date you'd ever like to appear in the output of this query. For your example it would need to contain at least 2013-11-01 through 2013-12-01.
SELECT m.date, y.value
FROM your_table y
INNER JOIN (
SELECT md.date, MAX(my.date) AS max_date
FROM dates md
INNER JOIN your_table my ON md.date >= my.date
GROUP BY md.date
) m
ON y.date = m.max_date