SQL finding average between dates - sql

I have the user write in two dates, say: '2013-7-8','2013-7-15'
The stored procedure then executes and returns and updated table.
Two columns include:
Avg_Paid_Volume ---- cns_amt
numb_im_looking_for -------------------------10000
numb_im_looking_for ------------------------ 20000
numb_im_looking_for -------------------------30000
... etc
cns_amt is just volume over the specified time period.
I'm looking for the average paid volume across between the specified two dates, which i called #dt_from and #dt_to -> in this case it would be 10000/8, 20000/8 and 30000/8
I would like to have those values inputed into my table (into the 'numb_im_looking_for')

Declare #dt_to datetime
Declare #dt_from datetime
set #dt_to = convert(datetime,'2013-7-8')
set #dt_from = convert(datetime,'2013-7-15')
Update tableName
Set avg_paid_volume = (cns_amt / datediff(d,#dt_to,#dt_from))
datediff is key here

Related

How to get WeekDay in between two days in SQL server

Please help, I have a below sample data. How to find week days "Tuesday" and count between two days.
CREATE TABLE EmpDetails1 (id INT, name VARCHAR(25),startdate datetime,enddate datetime)
INSERT INTO EmpDetails1 VALUES(1,'TEST','01/01/2016','01/10/2016');
INSERT INTO EmpDetails1 VALUES(2,'TEST','01/01/2016','01/25/2016');
id name startdate enddate
1 Test 1 1/1/16 1/10/16
2 Test 2 1/1/16 1/25/16
output:
date count
1/5/16 1
1/12/16 3
I have tried with below query but not getting correct result
SELECT name,
DATENAME(WEEKDAY, startdate) as w1,
DATENAME(WEEKDAY, enddate) as w2,
startdate,enddate, count(*) OVER(PARTITION BY startdate,enddate) AS CountOfOrders from EmpDetails1 group by startdate , enddate,name
As mentioned already, you need a calendar. You can generate one dynamically but far better to create a static one since it serves so many useful purposes. This is but one of many discussions about how to generate one.
A calendar is just a special form of a tally table and I use Itzik's discussion as a basis for that. Both of these concepts are things you need to understand in most SQL query writing environments.
Once you have a calendar, you simply join your data table to the calendar and filter as needed. I did not understand exactly what you were trying to accomplish so I simply create the set of rows for "Tuesday".
declare #EmpDetails1 table (id int, name varchar(20), startdate date, enddate date);
insert #EmpDetails1 (id, name, startdate, enddate) values
(1, 'Test 1', '20210101', '20210110'),
(2, 'Test 2', '20210116', '20210126');
select emp.*, cal.*, datename(weekday, cal.caldt) as [day of week(eng)]
from #EmpDetails1 as emp
inner join calendar as cal
on cal.caldt between emp.startdate and emp.enddate
and datename(weekday, cal.caldt) = 'Tuesday'
order by emp.id, cal.caldt
;
Fiddle here to demonstrate. I must highlight the lazy usage of * as the column list but this is just a simple demo. Production code should generally always specify the columns needed completely.
If you examine the calendar table discussion, you will see that the day of week can be easily added to the table - it will never change. This will avoid the effort to calculate it in the query.
T-SQL Code:
CREATE PROCEDURE tuesdayCount #id_number INT AS
BEGIN
DECLARE #S_Date DATETIME
DECLARE #E_Date DATETIME
SET #S_Date = (SELECT startdate FROM EmpDetails1 WHERE id = #id_number)
SET #E_Date = (SELECT enddate FROM EmpDetails1 WHERE id = #id_number)
WHILE #S_Date <= #E_Date
IF (FORMAT(#S_Date, '%a') = 'Tue')
INSERT INTO TuesdayDates VALUES(#S_Date, 1)
SET #S_Date = DATEADD(DAY,1, #S_Date)
END
EXECUTE tuesdayCount #id_number=1;
EXECUTE tuesdayCount #id_number=2;
Code Steps:
First line I created a procedure named tuesdayCount with an input parameter(id_number)
Then I declared 2 variables (S_Date and E_Date) with DATETIME data type, then set them equal to startdate and endnote column values in the first row.(Please take note of id_number sp parameter in the where clause.)
Then I defined a while loop and if_ test to ensure the date is really a Tuesday. If that's so, then I inserted the date value(S_Date) into TuesdayDates table which I created myself beforehand to put the result set there. (maybe not so logical; but I did it anyway.)
After defining the sp proc, I called my functions with id_number parameter which is in fact the row number of your data set (EmpDetails1 Table)
Not a perfect solution :) but I hope It helps somehow.

SQL Server : average count of alerts per day, not including days with no alerts

I have a table that acts as a message log, with the two key tables being TIMESTAMP and TEXT. I'm working on a query that grabs all alerts (from TEXT) for the past 30 days (based on TIMESTAMP) and gives a daily average for those alerts.
Here is the query so far:
--goback 30 days start at midnight
declare #olderdate as datetime
set #olderdate = DATEADD(Day, -30, DATEDIFF(Day, 0, GetDate()))
--today at 11:59pm
declare #today as datetime
set #today = dateadd(ms, -3, (dateadd(day, +1, convert(varchar, GETDATE(), 101))))
print #today
--Grab average alerts per day over 30 days
select
avg(x.Alerts * 1.0 / 30)
from
(select count(*) as Alerts
from MESSAGE_LOG
where text like 'The process%'
and text like '%has alerted%'
and TIMESTAMP between #olderdate and #today) X
However, I want to add something that checks whether there were any alerts for a day and, if there are no alerts for that day, doesn't include it in the average. For example, if there are 90 alerts for a month but they're all in one day, I wouldn't want the average to be 3 alerts per day since that's clearly misleading.
Is there a way I can incorporate this into my query? I've searched for other solutions to this but haven't been able to get any to work.
This isn't written for your query, as I don't have any DDL or sample data, thus I'm going to provide a very simple example instead of how you would do this.
USE Sandbox;
GO
CREATE TABLE dbo.AlertMessage (ID int IDENTITY(1,1),
AlertDate date);
INSERT INTO dbo.AlertMessage (AlertDate)
VALUES('20190101'),('20190101'),('20190105'),('20190110'),('20190115'),('20190115'),('20190115');
GO
--Use a CTE to count per day:
WITH Tots AS (
SELECT AlertDate,
COUNT(ID) AS Alerts
FROM dbo.AlertMessage
GROUP BY AlertDate)
--Now the average
SELECT AVG(Alerts*1.0) AS DayAverage
FROM Tots;
GO
--Clean up
DROP TABLE dbo.AlertMessage;
You're trying to compute a double-aggregate: The average of daily totals.
Without using a CTE, you can try this as well, which is generalized a bit more to work for multiple months.
--get a list of events per day
DECLARE #Event TABLE
(
ID INT NOT NULL IDENTITY(1, 1)
,DateLocalTz DATE NOT NULL--make sure to handle time zones
,YearLocalTz AS DATEPART(YEAR, DateLocalTz) PERSISTED
,MonthLocalTz AS DATEPART(MONTH, DateLocalTz) PERSISTED
)
/*
INSERT INTO #Event(EntryDateLocalTz)
SELECT DISTINCT CONVERT(DATE, TIMESTAMP)--presumed to be in your local time zone because you did not specify
FROM dbo.MESSAGE_LOG
WHERE UPPER([TEXT]) LIKE 'THE PROCESS%' AND UPPER([TEXT]) LIKE '%HAS ALERTED%'--case insenitive
*/
INSERT INTO #Event(DateLocalTz)
VALUES ('2018-12-31'), ('2019-01-01'), ('2019-01-01'), ('2019-01-01'), ('2019-01-12'), ('2019-01-13')
--get average number of alerts per alerting day each month
-- (this will not return months with no alerts,
-- use a LEFT OUTER JOIN against a month list table if you need to include uneventful months)
SELECT
YearLocalTz
,MonthLocalTz
,AvgAlertsOfAlertingDays = AVG(CONVERT(REAL, NumDailyAlerts))
FROM
(
SELECT
YearLocalTz
,MonthLocalTz
,DateLocalTz
,NumDailyAlerts = COUNT(*)
FROM #Event
GROUP BY YearLocalTz, MonthLocalTz, DateLocalTz
) AS X
GROUP BY YearLocalTz, MonthLocalTz
ORDER BY YearLocalTz ASC, MonthLocalTz ASC
Some things to note in my code:
I use PERSISTED columns to get the month and year date parts (because I'm lazy when populating tables)
Use explicit CONVERT to escape integer math that rounds down decimals. Multiplying by 1.0 is a less-readable hack.
Use CONVERT(DATE, ...) to round down to midnight instead of converting back and forth between strings
Do case-insensitive string searching by making everything uppercase (or lowercase, your preference)
Don't subtract 3 milliseconds to get the very last moment before midnight. Change your semantics to interpret the end of a time range as exclusive, instead of dealing with the precision of your datatypes. The only difference is using explicit comparators (i.e. use < instead of <=). Also, DATETIME resolution is 1/300th of a second, not 3 milliseconds.
Avoid using built-in keywords as column names (i.e. "TEXT"). If you do, wrap them in square brackets to avoid ambiguity.
Instead of dividing by 30 to get the average, divide by the count of distinct days in your results.
select
avg(x.Alerts * 1.0 / x.dd)
from
(select count(*) as Alerts, count(distinct CAST([TIMESTAMP] AS date)) AS dd
...

Converting a time excel formula to t-sql

So I'm new to SQL (I believe it's T-SQL) and I'm trying to convert a function I used in Excel to SQL.
L2 becomes Column 1
G2 becomes Column 2
=(INT(L2)-INT(G2))*("17:00"-"08:45")+MEDIAN(MOD(L2,1),"17:00","08:45")-MEDIAN(MOD(G2,1),"17:00","08:45")
What this does is calculate the business hours worked between 8:45AM and 05:00PM.
If work goes from 4:00PM to 9:00AM the next day, the result should be 01:15:00.
If it goes over several days (4:00PM on the 1st to 9:00AM on the 4th) it should be 17:45:00.
I'd prefer not to have a separate function because I don't know how to use them as I'm quite new to this - I'd prefer to have it as something I can write within the SELECT * , <code here here> FROM db.name section.
Thanks in advance
I know you said you don't want this in a function, but they really aren't hard to use and the logic you require for this is too complex in SQL Server to be sensibly contained inline (Though it can be, if you really want to be that guy).
This function has no error handling if any of your parameters are not suitable, though I will leave that up to you as a learning exercise on NULL values, process flows and fully thinking through all the possibilities that you may need to deal with:
-- This bit creates your function. You can rename the function from fnWorkingDays to anything you want, though try to keep your naming conventions sensible:
create function fnWorkingDays(#Start datetime
,#End datetime
)
returns decimal(10,2)
as
begin
-- Declare the start and end times of your working day:
declare #WorkingStart time = '08:45:00.000'
declare #WorkingEnd time = '17:00:00.000'
-- Work out the number of minutes outside the working day in 24 Hour Notation:
declare #OvernightMinutes int = datediff(minute -- Work out the difference in minutes,
,cast(#workingend as datetime) -- between the end of the working day (CASTing a TIME as DATETIME gives you 1900-01-01 17:00:00)
,dateadd(d,1,cast(#WorkingStart as datetime)) -- and the start of the next working day (CAST the TIME value as DATETIME [1900-01-01 08:45:00] and then add a day to it [1900-01-02 08:45:00])
)
-- There is no need to retain the minutes that fall outside your Working Day, to if the very start or very end of your given period fall outside your Working Day, discard those minutes:
declare #TrueStart datetime = (select case when cast(#Start as time) < #WorkingStart
then dateadd(d,datediff(d,0,#Start),0) + cast(#WorkingStart as datetime)
else #Start
end
)
declare #TrueEnd datetime = (select case when cast(#End as time) > #WorkingEnd
then dateadd(d,datediff(d,0,#End),0) + cast(#WorkingEnd as datetime)
else #End
end
)
-- You can now calculate the number of minutes in your true working period, and then subtract the total overnight periods in minutes to get your final value.
-- So firstly, if your Working Period is not long enough to stretch over two days, there is not need to do any more than calculate the difference between the True Start and End:
return (select case when datediff(minute,#Start,#End) < #OvernightMinutes
then datediff(minute,#TrueStart,#TrueEnd)
-- If you do need to calculate over more than one day, calculate the total minutes between your True Start and End, then subtract the number of Overnight Minutes multiplied by the number of nights.
-- This works because DATEDIFF calculated the number of boundaries crossed, so when using DAYS, it actually counts the number of midnights between your two dates:
else (datediff(minute,#TrueStart,#TrueEnd) - (datediff(d,#TrueStart,#TrueEnd) * #OvernightMinutes))/1440.
-- If you want to return your value in a slightly different format, you could use variations of these two, though you will need to change the RETURNS DECIMAL(10,2) at the top to RETURNS NVARCHAR(25) if you use the last one:
-- else datediff(minute,#TrueStart,#TrueEnd) - (datediff(d,#TrueStart,#TrueEnd) * #OvernightMinutes)
-- else cast((datediff(minute,#TrueStart,#TrueEnd) - (datediff(d,#TrueStart,#TrueEnd) * #OvernightMinutes))/60 as nvarchar(5)) + ' Hours ' + cast((datediff(minute,#TrueStart,#TrueEnd) - (datediff(d,#TrueStart,#TrueEnd) * #OvernightMinutes))%60 as nvarchar(5)) + ' Minutes'
end
)
end
go
And this is how you call the function:
select dbo.fnWorkingDays('2016-09-04 12:00:00.000', '2016-09-06 12:10:00.000') as WorkingDays
You can replace the two DATETIME values about with the appropriate column names to get your desired result inline:
select dbo.fnWorkingDays(Dates.StartDate, Dates.EndDate) as WorkingDays
from (select '2016-09-04 12:00:00.000' as StartDate
,'2016-09-06 12:10:00.000' as EndDate
) as Dates

SQL, Stock movements where no zero stock records

I'm trying to create a stock movement file from historical stock records, see picture:
I've been able to get the green movements and the orange value (had some luck union all to a 0 stock record for max(day) + 1 and removing anything less than today). Movements are then generated with a lag function.
The difficulty is there are no 0 stock records. i.e. fifth row in the example above there is no record so unable to create the correct movement to 0 with the -2. It would create movements of -10, -4, -4, 5, -3, -1, -3
Is there any easier way to identify the gaps and create phantom 0's, I know the dates (these are a mix of weekly that will become daily)?
There are several ways to get the missing gaps in this case it looks like you have a date every week (or 7 days) and in some case there is no date. So you can build a date table with a while loop and do a join and assinging Stock Opening Balance as 0 when null in your table. Here is something similar I am using table variables to simply but using a temp table would probably be better for you and if you do you don't need 2 tables just insert the missing to your temp table.
DECLARE #TableWithGaps AS TABLE ([Date] DATE, StockOpeningBalance INT)
INSERT INTO #TableWithGaps ([Date],StockOpeningBalance)
VALUES ('2016-04-04',10)
,('2016-04-11',6)
,('2016-04-18',2)
,('2016-05-09',7)
,('2016-05-16',4)
,('2016-05-23',3)
DECLARE #Dates AS TABLE ([Date] DATE)
DECLARE #MinDate DATE = '3/28/2016'
DECLARE #MaxDate DATE = '6/20/2016'
--Could also get first record as minimum date and last record, or set #maxdate = GETDATE()
--SELECT #MinDate = DATEADD(DAY,7,MIN([Date])), #MaxDate = MAX([Date]) FROM #TableWithGaps
WHILE #MinDate < DATEADD(DAY,1,#MaxDate)
BEGIN
INSERT INTO #Dates ([Date]) VALUES (#MinDate)
SET #MinDate = DATEADD(DAY,7,#MinDate)
END
SELECT
d.[Date]
,StockOpeningBalance = ISNULL(StockOpeningBalance,0)
,LAG(ISNULL(StockOpeningBalance,0),1,0) OVER (ORDER BY d.[Date])
,Movements = ISNULL(StockOpeningBalance,0) - LAG(ISNULL(StockOpeningBalance,0),1,0) OVER (ORDER BY d.[Date])
FROM
#Dates d
LEFT JOIN #TableWithGaps s
ON d.[Date] = s.[Date]
Also note, I prefer temp tables over table variables and they temp tables will perform better with a larger data set. If you start doing a ton with dates you may want to look at a date dimension table for purposes like this. If you are using Microsoft SSAS it has a built in tool to generate one that you can google/search for pretty quickly.

How can I create a list of weeks in access?

How do I create a query which breaks down a frequency of counts based on a list of weeks between 2 different dates in Access?
At the moment I have the following code in t-sql, but would like to have it run in Access.
declare #fromdate smalldatetime
declare #todate smalldatetime
declare #toptr smalldatetime
declare #fromptr smalldatetime
set #fromdate = '1/11/2010'
set #todate = '27/12/2010'
set #fromptr = dateadd(dd,1 - datepart(weekday,#fromdate), #fromdate)
while #fromptr < #todate
begin
print 'from: ' + cast(#fromptr as nvarchar) + ' --> ' + cast(#toptr as nvarchar)
set #fromptr = dateadd(dd,7, #fromptr)
set #toptr = dateadd(dd,7, #fromptr)
insert into #weeks values (#fromptr, #toptr)
end
I want to somehow bind some rows with lots of dates in them and aggregate them per 'week- ending date' from the dates creating in the table variable. Access doesn't seem to allow this kind of sql query, so was wondering if there was another way of doing this:
1) either by not using an intermediate table at all, 2) and/or converting the above code into access compatible
This will group by week (starting with Sunday) and be faster than other date calculation methods like DateAdd, DateDiff, DatePart, and Format.
SELECT
CDate((([DateColumn] - 1) \ 7) * 7 + 1) AS WeekStartingDate,
Sum([OrderCount]) AS SumOfOrders
FROM
Orders
GROUP BY
CDate((([DateColumn] - 1) \ 7) * 7 + 1);
If you want to see week ending date, add 7 at the end instead of 1. The GROUP BY expression can probably be just ([DateColumn] - 1) \ 7 but I'm not sure.
The backslash performs integer division, dividing by 7 converts a week of dates to a single integer, and the -1 adjusts for the fact that the "zero date" is a Saturday rather than a Sunday. To use a different starting day of the week, adjust the -1 and +1 by the same amount. To use Monday, for example, it would be -2 and +2.
This is language and region independent by depending on VB's internal representation of dates as numbers.
You can use Format in Access queries: http://msdn.microsoft.com/en-us/library/aa159657(v=office.10).aspx
SELECT Format(Date,"ww") FROM Table
GROUP BY Format(Date,"ww")
The plain-vanilla solution is to introduce a Calendar table which may look something like
Calendar
------------------------
FullDate date
CalendarYear integer
DayNumberInWeek integer
DayNumberInMonth integer
DayNumberInYear integer
DayNumberInEpoch integer
WeekNumberInYear integer
WeekNumberInEpoch integer
MonthNumberInYear integer
MonthNumberInEpoch integer
... and many more that you may need to group by
Then if you have table Counters
Counters
-----------
FullDate date
Value integer -- cumulative for one day
You can:
select
WeekNumberInYear
, sum(Value)
from Calendar as a
join Counters as b on b.FullDate = a.FullDate
where CalendarYear = 2010
group by WeekNumberInYear ;
The easiest way to populate the Calendar is to spend some time in Excel, create 10-20 years worth of rows and simply import in a DB.
Nothing specific to Access here, but hope you get the idea.