Daily Avg in SQL - sql

I have a simple sql below. I want to calculate the daily average. But I will have to change the parameters and the number of days every day when I run my sql. What is a better way to eliminate these repeated steps?
Declare start_dt as datetime
Declare end-dt as datetime
Set start_dt ='10/01/16'
Set end_dt = '10/31/16
Select
Product_Name,
Count(Qty) /30 --want to calculate the average last 30 days
From temp
Where delivery_dt >=start_dt
And delivery_dt<end_dt
Group by product_name
As noticed above, I have to manually change the parameters for the date range and the numbers of days to get my average. Is there a way in sql server that I don't have to change the parameters and numbers of my average? And I would like my average results shown as float.

Maybe this can help you.
DECLARE #Start_dt DATETIME
DECLARE #End_dt DATETIME
SET #Start_dt = GETDATE()
SET #End_dt = DATEADD(DAY, 30, #Start_dt);
SELECT Product_Name,
Count(Qty)/DATEDIFF(DAY,#Start_dt,#End_dt) AS [Daily Average] --want to calculate the average last 30 days
FROM temp
WHERE (delivery_dt >= #Start_dt) AND (delivery_dt < #End_dt)
GROUP BY Product_Name
However, you've mentioned that you often use this kind of sql query so I would recommend that you create stored procedure for this one and just call it a day.
For creating procedures you can take a look at microsoft documentation.

You can create a stored procedure with parameter endDate and dateDifference,
for example:
CREATE PROCEDURE usp_Get_Average_Quantiity(#EndDate DateTime, #DateDifference int)
AS
Declare #StartDate DateTime = DATEADD(Day, #DateDifference, #EndDate)
Select
Product_Name,
CAST(COUNT(Qty) AS FLOAT) / #DateDifference -
From temp
Where delivery_dt >=#StartDate
And delivery_dt<#EndDate
Group by product_name
To return float I use:
CAST(COUNT(Qty) AS FLOAT) / #DateDifference
#DateDifference will probably be 30 in your case, but you can change it according to your specific scenario without changing the procedure. Same is with endDate.

Related

How to get the total of a previous month having passed the current month as a parameter in a SQL Server stored procedure?

Through the following query, I am able to get the total for the current month from a data table.
SELECT SUM(MONTODEBITO) - SUM(MONTOCREDITO)
FROM PRUEBAOPEX
WHERE MONTH(FECHA) = 1 AND YEAR(FECHA) = 2021
Since I am working inside a stored procedure, I have declared a variable that is equal to the following query.
CREATE PROCEDURE SP_VALORS
(#NIVEL VARCHAR(15),
#MES INT,
#AÑO INT)
AS
BEGIN
DECLARE #TOTALMESACTUAL FLOAT, #TOTALMESPASADO FLOAT
SET #TOTALMESACTUAL = (SELECT SUM(MONTODEBITO) - SUM(MONTOCREDITO)
FROM PRUEBAOPEX
WHERE MONTH(FECHA) = #MES
AND YEAR(FECHA) = #AÑO)
END
When executing the procedure, it returns the value correctly.
I also need to declare another variable that displays the total for the month before the month I pass as a parameter in my procedure.
I have the following example:
SELECT SUM(MONTODEBITO) - SUM(MONTOCREDITO)
FROM PRUEBAOPEX
WHERE MONTH(FECHA) = 2-1 AND YEAR(FECHA) = 2021
When I send 2 (February) to 12 (December) as a parameter, it shows me correctly.
More than all my doubt is in the case of sending 1 (January) as a parameter. It should show me the total for December of the previous year. How could I declare this variable?
Thank you for any help you may receive.
You should be using date boundaries, not using syntax like WHERE MONTH(SomeDate) = #SomeMonthNumber. This means you can make a SARGable query, and you can easily apply better logic to the date boundary you want.
An easy way to get a date from a month and year value is to use DATEFROMPARTS. As you want the whole of a specific month, you can use 1 for the day of the month.
After making some other changes, as there are some odd choices in your procedure (variables that are declared and not used, a lack of the procedure returning anything), I suspect you want something like this:
--I have dropped the prefix as I recommend in the comments of your prior question.
CREATE PROCEDURE dbo.VALORS #NIVEL varchar(15), #MES int, #AÑO int, #TOTALMESACTUAL float = NULL OUTPUT AS
BEGIN
SELECT #TOTALMESACTUAL = SUM(MONTODEBITO - MONTOCREDITO) --There's no need for a subquery here
FROM dbo.PRUEBAOPEX
WHERE FECHA >= DATEFROMPARTS(#AÑO, #MES, 1)
AND FECHA < DATEADD(DAY, 1, DATEFROMPARTS(#AÑO, #MES, 1));
END;
If you wanted to get the prior month, you can easily apply a further DATEADD to subtract a month from the expression (DATEADD(MONTH, -1, <Expression>)).
Also, I suspect that float is not the right choice for your data type, and that a base-10 data type would be better; though without knowing what SUM(MONTODEBITO - MONTOCREDITO) represents, I haven't changed the data type.
You will get better performance from the current code by changing it like this:
DECLARE #Month DateTime = DatefromParts(#AÑO, #MES, 1)
SET #TOTALMESACTUAL = (SELECT SUM(MONTODEBITO) - SUM(MONTOCREDITO)
FROM PRUEBAOPEX
WHERE #Month <= FECHA and FECHA < Dateadd(month, 1, #Month)
)
The improved performance is because the FECHA column now remains unaltered for the query, and will therefore work better (at all) with any indexes you have.
More importantly, this is also very easy to convert to get the previous month:
DECLARE #Month DateTime = DatefromParts(#AÑO, #MES, 1)
Set #Month = DATEADD(month, -1, #Month)
SET #TOTALMESACTUAL = (SELECT SUM(MONTODEBITO) - SUM(MONTOCREDITO)
FROM PRUEBAOPEX
WHERE #Month <= FECHA and FECHA < Dateadd(month, 1, #Month)
)

SQL Server: use parameter instead of GETDATE()

I have a stored procedure that uses selects like the following which works fine so far.
In this case for example it selects all records with a date from the previous month, i.e. March 2014 (column: dateEsc, formatted as nvarchar(20), example date: 2014-03-25).
My Select (example):
SELECT COUNT(*) AS groupCount
FROM Log_Esc
WHERE
CONVERT(DATE, dateEsc, 120) >= CONVERT(DATE, CONVERT(VARCHAR(6), DATEADD(month, -1, GETDATE()), 112) + '01', 112)
How do I have to change this if instead of the current Date (GETDATE()) I want to use a variable date input as the reference.
This input would be any date and is formatted as nvarchar(20) as well, example: 2014-04-03.
So instead of calculating the previous month compared to the current month from GETDATE() I would like to calculate the same from the variable date input.
Many thanks for any help with this, Tim.
First of all I think this query is better than the one you have:
SELECT COUNT(*) AS groupCount
FROM Log_Esc
WHERE DATE >= dateadd(month,datediff(month,0,dateadd(month,GETDATE(),-1)),0)
AND DATE < dateadd(month,datediff(month,0,GETDATE()),0)
If there is an index on the DATE field this can do a seek.
If you have a parameter #indate defined as date or datetime then this will work
SELECT COUNT(*) AS groupCount
FROM Log_Esc
WHERE DATE >= dateadd(month,datediff(month,0,dateadd(month,#indate,-1)),0)
AND DATE < dateadd(month,datediff(month,0,#indate),0)
See this question for more information on flooring a date to a month: Floor a date in SQL server
So what you want is a parameter:
Specifying Parameters in a Stored Procedure
Parameters allow you to pass user input to modify output.
An example
CREATE PROCEDURE dbo.Param1
#param int
AS
BEGIN
select 7 *#param as Value
END
EXEC dbo.Param1 5 -- 7 *5
EXEC dbo.Param1 -10 -- 7 * -10
Perhaps this'll give you some creative ideas for how you might implement parameters to accomplish your group count.

Counting the amount of Days in a date range

I'm trying to get a query that will multiply a static number by the amount of days in a date range the problem i'm having is that when a single day is selected it returns a result of 0 instead of 1: Ex:
Declare #Startdate DATE
Declare #enddate DATE
SET #Startdate='9/1/2013'
SET #enddate='9/1/2013'
SELECT 1154*(Select DATEDIFF(DAY, #startdate, #enddate))
This example returns 0 instead of 1. Should I be using something other than DateDiff?
Additional clarification - This will be used as part of a report where the date range will be dynamically entered by person calling the report.
Could just add 1:
Declare #Startdate DATE
Declare #enddate DATE
SET #Startdate='9/1/2013'
SET #enddate='9/1/2013'
SELECT (DATEDIFF(day,#Startdate,#enddate)+1)*1154
Update, as noted don't need the inner SELECT

A better way? Have date in query always use a date in the current year without maintenance

SELECT Date_Received, DateAdd(Year, DateDiff(year, Cast('3/01/2010 12:00:00AM' as DateTime) ,
GetDate())-1, Cast('3/01/2010 12:00:00AM' as DateTime)) as minimum_date
FROM [Volunteers].[dbo].[Applications]
WHERE Date_received >= DateAdd(Year, DateDiff(year, Cast('3/01/2010 12:00:00AM' as DateTime),
GetDate())-1, Cast('3/01/2010 12:00:00AM' as DateTime))
In several subqueries where I need to check that a date is within an acceptable range. I need to avoid using a simple constant as I really don't want to update it or a config file each new school year.
My current solution is to enter the date into the query and use some complicated DATEADD tricks to get the current year(or previous year) into the date I am using in the comparison. The exact code is above. Is there a cleaner way for me to do this?
Thanks
Edit
The business requirement is to find applications submitted between 3/01 and 7/31.
We are running background checks and it costs us money for each check we do. Identifying applications submitted during this time period helps us determine if we should do a full, partial or no background check. I will also need to check if dates concerning the previous year.
We will be doing this every year and we need to know if they were in the current year. Maintaining the queries each year to update the dates is not something I want to do.
So I am looking for a good technique to keep the year parts of the dates relevant without having to update the query or a config file.
Old TSQL trick: cast the date to a string in a format that starts with the four-digit year, using substring to take the first four characters of that, cast it back to a date.
Actually, the reason that it's an old TSQL trick is that, if I recall correctly, there wasn't a year() function back then. Given that there's one now, using year( getdate() ) , as others' have answered, is probably the better answer.
SELECT YEAR(GETDATE())
will give you the current year.
If you need to query by month and year a lot, you should also consider making those properties into persisted, computed fields:
ALTER TABLE dbo.Applications
ADD DateReceivedMonth AS MONTH(Date_Received) PERSISTED
ALTER TABLE dbo.Applications
ADD DateReceivedYear AS YEAR(Date_Received) PERSISTED
SQL Server will now extract the MONTH and YEAR part of your Date_Received and place them into two new columns. Those are persisted, e.g. stored along side with your table data. SQL Server will make sure to keep them up to date automatically, e.g. if you change Date_Received, those two new columns will be recomputed (but not on every SELECT).
Now, your queries might be a lot easier:
SELECT (list of fields)
FROM dbo.Applications
WHERE DateReceivedYear = 2010 AND DateReceivedMonth BETWEEN 3 AND 7
Since these are persisted fields, you can even put an index on them to speed up queries against them!
Is there any reason you cannot simply use the Year function?
Select Date_Received
, Year(GetDate())
- Year('3/01/2010 12:00:00AM') - 1
+ Year('3/01/2010 12:00:00AM')
From [Volunteers].[dbo].[Applications]
Where Date_received >= ( Year(GetDate())
- Year('3/01/2010 12:00:00AM') - 1
+ Year('3/01/2080 12:00:00AM') )
Another way would be to use a common-table expression
With Years As
(
Select Year(GetDate()) As CurrentYear
, Year('3/01/2010 12:00:00AM') As ParamYear
, Year('3/01/2080 12:00:00AM') As BoundaryYear
)
Select Date_Received
, CurrentYear - Years.ParamYear - 1 + Years.ParamYear
From [Volunteers].[dbo].[Applications]
Cross Join Years
Where Date_received >= ( Years.CurrentYear
- Years.ParamYear - 1 + Years.BoundaryYear )
TSQL Function returns four digit year dependent on year. This behaves much like the standard SQL YEAR functions [Thomas - nod] which 'CAN' be tweaked using sp_configure on the advanced options, however, the code below is provided as a framework for CUSTOM requirements and can be modified as required. e.g. return as int, use with standard DATETIME functions in SQL to achieve what is needed. e.g. When working with "dirty" data I had to migrate, I used it with the PATINDEX() function to strip non-numeric values etc.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: Andrew McLintock
-- Create date: 13 July 2016
-- Description: Return 4-digit YEAR
-- =============================================
/*
SELECT Staging.fn_4year('06')
SELECT Staging.fn_4year('56')
SELECT Staging.fn_4year('99')
SELECT Staging.fn_4year('1906')
SELECT Staging.fn_4year('2025')
*/
CREATE FUNCTION Staging.fn_4year
(
#year_in varchar (4)
)
RETURNS varchar(4)
AS
BEGIN
DECLARE #yeartmp int, #Retval varchar(4)
SET #yeartmp = CAST(REPLACE(#year_in,' ','') AS INT)
IF LEN(CAST(#yeartmp AS Varchar)) = 4
BEGIN
Return cast(#yeartmp as varchar(4))
END
IF LEN(#year_in) = 2
BEGIN
SET #Retval = CAST(iif(#yeartmp > 49, #yeartmp + 1900, #yeartmp + 2000) AS varchar(4))
END
RETURN #Retval
END
GO
Consider keeping a set of datetime variables help readability and maintainability. I'm not sure I've captured all your requirements, especially with reference to 'previous year'. If it's as simple as finding applications submitted between 3/01 and 7/31, then this should work. If you need to determine those that were submitted Aug 1 (last year) through Feb 28 (current year), this solution could be modified to suit.
DECLARE #Start smalldatetime, #End smalldatetime, #CurrYear char(4)
SELECT #CurrYear = YEAR(getdate())
SELECT #Start = CAST( 'mar 1 ' + #CurrYear as smalldatetime),
#End = CAST( 'jul 31 ' + #CurrYear as smalldatetime)
SELECT *
FROM Applications
WHERE Date_Received
BETWEEN #Start AND #End

Best practice: Searching table against day, month or year

I've got a table with a "date" column, where a user input will be queried against (using stored procedure)..and results will be shown on a datagrid..
now a user can either enter a year, Year/month , Year/month/day.. (from drop down lists)
i know there r many possible ways to handle the different queries.. however i am trying to figure out which would be best practice:
Solution 1: having 3 different stored procedures , one for every case.
Solution 2: having 1 stored procedure, with 1 extra parameter as searchlvl , then using IF ELSE statements to decide what lvl of search should be applied.
Solution 3: having 1 stored procedure, and sending the datetime as 3 different parameters , then checking IF parameter is null , and using that to decide search lvl
Solution 4: your suggestions :)
NOTE: i know how to do partial search(using datepart), my question is about best practice among the 3 solutions i offered or any other solution offered in the answers..
Like which would be faster, lighter on database and such..
and which would be slower, heavier..
There are no levels.
When user selects year 2009, you search rows where date >= '2009.01.01 00:00' and < '2010.01.01 00:00'.
When he selects month 01 of year 2009 you search where date >= '2009.01.01 00:00' and < '2009.02.01 00:00'.
Of course you don't pass dates as strings, you should use CONVERT() or pass dates as DATETIME type. This is universal solution and will be fast, because it will use indexes. You can create stored procedure that takes two dates, it will allow to search by every date range, not only year/month/day.
I'd do none of the above.
You should design you stored procedure to take three different ints, one for day, one for month and one for year. Leave the parameters nullable, but establish a convention so only meaningful parameter combinations are used. Then you construct a MINDATE and MAXDATE from the parameters.
Searching Datetime columns based on day/year/month requires a query like:
SELECT * FROM table WHERE date > MINDATE AND date < MAXDATE
which is pretty inefficient but not a definite problem.
Another approach (if the table is huge) would be to create an indexed view with year/month/day integer columns and search for exact matches there. To create such a view use DATEPART().
You can use datepart to get the parts of you date you want to filter against as
declare #table table(
DateVal DATETIME
)
INSERT INTO #table SELECT GETDATE()
DECLARE #Year INT,
#Month INT,
#Day INT
SELECT #Year = 2009
SELECT DATEPART(YY, DateVal) DateYear,
DATEPART(MM, DateVal) DateMonth,
DATEPART(DD, DateVal) DateDay,
*
FROM #table
WHERE (DATEPART(YY, DateVal) = #Year OR #Year IS NULL)
AND (DATEPART(MM, DateVal) = #Month OR #Month IS NULL)
AND (DATEPART(DD, DateVal) = #Day OR #Day IS NULL)
I'd pass in year/month/date as separate parameters into one stored proc, say default of NULL.
Then, I'd use DATEADD to build up from/to datetimes and use that
...
SELECT
#ToYear = ISNULL(#ToYear, DATEPART(year, GETDATE()), --or some base value, such as "1900"
#ToMonth = ...
...
SELECT
#DateTo = DATEADD(year, #ToYear, DATEADD(month, #ToMonth, DATEADD(day, #ToDay, 0), 0), 0)
....
SELECT * FROM myTable WHERE DateColumn >= #DateFrom AND DateColumn <= #DateTo
I would not use any functions on columns or conditional logic to switch between selects