SQL performance based on the WHERE clause - sql

I am trying to reuse a query, instead of creating several scenarios. Basically I am getting a COUNT from a big table.
The question is: am I going to lose performance on the long run if I am using
SELECT COUNT(id) FROM [Documents] WHERE UploadDate > '1900/1/1' AND UploadDate < GETDATE()
Instead of a simple
SELECT COUNT(id) FROM [Documents]
Basically the 2 queries would return the same thing, because I want to return ALL the records in this case. But under some other circumstances, the date pair will use different parameters.

If you want the count based on some parameters, I do not think there will a significant difference, if you have an index defined on UploadDate. If you are using a stored procedure, it might look like the following:
CREATE PROCEDURE dbo.GetCount
(
#FromDate DATETIME2 = '1900-01-01',
#ToDate DATETIME2 = '9999-12-31',
-- other filters may come here
)
AS
BEGIN
SELECT COUNT(id)
FROM [Documents]
WHERE UploadDate > #FromDate AND #ToDate < #ToDate
END
GO
-- get all
EXEC dbo.GetCount
-- get from date
EXEC dbo.GetCount #FromDate = '2015-03-01'
-- to date
EXEC dbo.GetCount #ToDate = '2016-03-01'
-- both
EXEC dbo.GetCount #FromDate = '2015-03-01', #ToDate = '2016-03-01'
You can also try to obtain the minimum SQL to run, by creating a dynamic SQL:
CREATE PROCEDURE dbo.GetCount
(
#FromDate DATETIME2 = NULL,
#ToDate DATETIME2 = NULL
-- other filters may come here
)
AS
BEGIN
DECLARE #SQL NVARCHAR(4000) = N'
SELECT COUNT(id) FROM [Documents]
WHERE 1 = 1
'
IF (#FromDate IS NOT NULL) #SQL = #SQL + ' UploadDate > #FromDate'
IF (#ToDate IS NOT NULL) #SQL = #SQL + ' UploadDate > #ToDate'
EXECUTE sp_executesql #SQL, '#FromDate DATETIME2, #ToDate DATETIME2', #FromDate, #ToDate
END
This offers the flexibility of open interval to the left and/or right.
If you are dynamically generating queries from the application layer (e.g. LINQ2SQL), the best option is generate it based on your parameters. This will lead to the shortest and fastest queries:
var query = DbContext.Documents;
if (fromDate.HasValue) query = query.Where(UploadDate > fromDate.Value);
if (toDate.HasValue) query = query.Where(UploadDate < toDate.Value);
int count = query.Count();
The last two solutions also allow to easily add new filters.

If you want to count everything and then things that meet a condition:
SELECT COUNT(id) as total_cnt,
SUM(CASE WHEN UploadDate > '1900-01-01' AND UploadDate < GETDATE()
THEN 1 ELSE 0
END) as specific_cnt
FROM [Documents] d;

If you're worried about varying performance based on the range values you specify in your query, because of parameter sniffing, then you can add OPTION (RECOMPILE) at the end of your query.
SELECT COUNT(id)
FROM [Documents]
WHERE UploadDate > '1900/1/1' AND UploadDate < GETDATE()
OPTION (RECOMIPLE)
This will make sure that your newly generated plan is the best possible that the Optimizer could come up with, at that moment, on its own, for that date range, if you decide to check starting from '2011/1/1' for example.

Related

What I'm trying to do is get a running stock level for every day for the past year for each item [duplicate]

This question already has answers here:
Calculate a Running Total in SQL Server
(15 answers)
Closed 1 year ago.
I have the below SQL query that works but it's very slow. it takes about 1 min to run the query. This would be made into a stored procedure. which is not the problem. but that store procedure would be called for every item of which there are about 600 items. The estimated run time probably would end up taking about 10 hours. Does anyone have any suggestions of a better way of doing it?
What I'm trying to do is get a running stock level for every day for the past year for each item.
If you need any more information. Please let me know.
DECLARE #StartDate AS DATETIME
DECLARE #EndDate AS DATETIME
DECLARE #CurrentDate AS DATETIME
DECLARE #ItemName As Varchar(450)
DECLARE #QOH DECIMAL(19,4)
SET #QOH = 0
SET #ItemName = 'TUR001-02'
SET #StartDate = '2020-04-01'
SET #EndDate = GETDATE()
SET #CurrentDate = #StartDate
CREATE TABLE #TempTable
(
Date datetime,
ItemName char(450),
QOH DECIMAL(19,4)
);
WHILE (#CurrentDate < #EndDate)
BEGIN
DECLARE #daySales DECIMAL(19,4)
SELECT #daySales = SUM(Quantity)
FROM qbInvoiceLineDetail
WHERE TxnDate = #CurrentDate AND FullName = #ItemName;
SET #QOH = #QOH - #daySales
INSERT INTO #TempTable (Date, ItemName, QOH)
SELECT #CurrentDate, #ItemName, #QOH;
SET #CurrentDate = DATEADD(DAY, 1, #CurrentDate);
END
SELECT * FROM #TempTable
DROP TABLE #TempTable
You can use a tally table to generate your dates between start and end, then insert all your data in one hit into your table.
Caveat - this is untested as I have nothing to check it against, assumes the dates are dates only, if they include time then will need to use convert - hopefully will be what you are looking for:
/*first, create a tally table - this should be a permanent feature */
select top 1000 N=Identity(int, 0, 1)
into dbo.Digits
from master.dbo.syscolumns a cross join master.dbo.syscolumns
declare #StartDate datetime='20200401', #EndDate datetime=GetDate()
select DateAdd(day,N,#startDate) currentDate, FullName ItemName, Sum(Quantity) over(order by d.N) QOH
from Digits d
left join qbInvoiceLineDetail q on q.TxnDate=DateAdd(day,N,#startDate)
where DateAdd(day,N,#startDate)<=#EndDate
group by TxnDate, ItemName

Pass date values (dynamically changing) to input parameters of stored procedure

If you see the below stored procedure it has 2019-01-01,2019-12-31 as input.
What I would like to do is, I need to pass 2019-01-01,2019-01-31 and then 2019-02-01,2019-02-28 and same till December.
Can anyone please help in how to pass date time by month dynamically.
Exec storedprcedurename '2019-01-01','2019-12-31'
You can make use of while loop and date add to execute it. Also, you may need to set a max date so that it does not go into infinite loop.
Declare #datemin date = '2019-01-01', #maxdate date = '2019-12-31'
While #datemin <= #maxdate
Begin
Declare #dateend date = dateadd(day, -1, dateadd(month,1,#datemin ))
Exec storedprcedurename #datemin , #dateend
set #datemin = dateadd(month, 1, #datemin)
end
I would probably do it with dynamic SQL. Normally, I wouldn't recommend dynamic SQL without using the #params parameter to sp_executesql, but in this case, you're controlling the input values yourself.
So first we need to define a dataset with the dates to use to pass to the stored procedure. It's enough to get the first day of each month in the result set - we can use EOMONTH-function to get the last day of each month.
I'm doing it using a CTE below.
--Define startdate
DECLARE #startdate date='2019-01-01';
--Number of months
DECLARE #numMonths int=12;
--variable to store some dynamic SQL in
DECLARE #dynSQL nvarchar(max)=N'';
--CTE to get the dates
WITH tens AS(
SELECT n FROM(VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t(n)
), thousands AS(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL))-1 as n
FROM tens t0
CROSS JOIN tens t1
CROSS JOIN tens t2
CROSS JOIN tens t3
), dates AS(
SELECT TOP(#numMonths) DATEADD(month,n,#startDate) as startDate
FROM thousands
ORDER BY n
)
--And now we construct the dynamic SQL statement to call the proc based on the CTE dates
SELECT
#dynSQL = #dynSQL +
N'exec storedprocedurename ''' +
cast(startDate as nvarchar(50)) +
N''',''' +
cast(EOMONTH(startDate) as nvarchar(50)) +
N''';
'
FROM dates ORDER BY startDate;
--Make sure the dynamic SQL string looks correct Before executing
PRINT #dynSQL;
--When you are sure you got it right, comment the PRINT statement and uncomment below
--exec sp_executesql #dynSQL;
You could do it with a cursor, and the code would probably be a bit easier to read. But I'm brought up to shy away from cursors...

If else in Where Clause SQL

I faced a problem with a SQL query. I have a table with 10 fields.
I need to create a query, which gets date by field ProductionYear(int) between 2 variables #startDate(int) and #endDate(int). Both of these variables are unnecessary. And I need to build a SQL query with following conditions:
If(#endDate = 0)
Select Id from MyTable where ProductionYear > #startDate
else
Select Id from MyTable where ProductionYear BETWEEN #startDate and #endDate.
How can I build a query with those conditions?
You can incorporate this into a single query:
Select Id
from MyTable
where ProductionYear >= #startDate and
(ProductionYear <= #endDate or #endDate = 0);
Your two queries are inconsistent on whether #startDate is included. BETWEEN includes the comparison values, but > does not.
If you want #startDate to also be optional:
Select Id
from MyTable
where (ProductionYear >= #startDate or #startDate = 0) and
(ProductionYear <= #endDate or #endDate = 0);
Some additional comments. Calling a "year" a "date" is confusing. Your parameters should probably be called #startYear and #endYear.
These queries are going to result in full table scans of your table. This is probably not a big deal, because the granularity is by year. If the granularity were more refined, you might want to use an index. In that case, perhaps the best approach is dynamic SQL.
you can try by using case when
Select Id from MyTable
where ProductionYear BETWEEN (case when #startDate>#endDate then #endDate
else #startDate end) and
(case when #startDate>#endDate then #startDate else #endDate end)

Generate random records for datetime columns by stored procedure in SQL

I want to generate 5 random records from a field which is a datetime column and contains several records of (OrderDate) for a given date range using stored procedure for the table named Orders
CREATE PROCEDURE test
#StartDate DATETIME = NULL,
#EndDate DATETIME = NULL,
AS
BEGIN
SELECT OrderDate = DATEADD(......)
FROM Orders
END
May I get some help!
A while loop works ok for this purpose, especially if you're concerned with limiting your randomness to a bounded date range.
The downside is that potentially many insert queries get executed vs. a single insert for a recursive CTE as in the other answer.
create procedure dbo.spGenDates2
#MinDate datetime,
#MaxDate datetime,
#RecordCount int = 5
as
SET NOCOUNT ON;
DECLARE #Range int, #DayOffset int, #Cnt int
SET #Range = DATEDIFF(dd, #MinDate, #MaxDate)
SET #Cnt = 1
WHILE #Cnt <= #RecordCount
BEGIN
SET #DayOffset = RAND() * (#Range + 1)
INSERT INTO _test (Dt) VALUES(DATEADD(dd, #DayOffset, #MinDate))
SET #Cnt = #Cnt + 1
END
Based on your syntax I'm assuming you're using SQL Server...
Note that you cannot reliably use the sql random number generator function RAND() within the context of a single query because it does not get reseeded per row so you end up receiving the same, single random number for each row result. Instead, an approach using NEWID() converted into a numeric does the trick when generating random values within the execution of a single query.
Here's a procedure that will give you n number of sample dates in the near past.
create procedure dbo.spGenDates
#MaxDate datetime,
#RecordCount int = 5
as
WITH dates as (
SELECT DATEADD(MILLISECOND, ABS(CHECKSUM(NEWID())) * -1, #MaxDate) D,
1 as Cnt
UNION ALL
SELECT DATEADD(MILLISECOND, ABS(CHECKSUM(NEWID())) * -1, #MaxDate) D,
x.Cnt + 1 as Cnt
FROM dates x
WHERE x.Cnt < #RecordCount
)
INSERT INTO _test (Dt)
SELECT D
FROM dates
The wording of the question has been clarified (see comments on another answer) to be a desire to SELECT 5 random sample dates within a bounded range from a table.
A query like this will yield the desired result.
SELECT TOP (5) OrderDate
FROM Orders
WHERE OrderDate >= #StartDate
AND OrderDate < #EndDate
ORDER BY NEWID()

Defining type as a variable - DATEADD(type, value, date) - SQL [duplicate]

This question already has answers here:
SQL Dynamic DatePart when using DateDiff
(6 answers)
Closed 5 years ago.
I had to change the date in one of a column of my SQL table (datetime field), increment YEAR by 1. The simple way to perform this action would be (unless someone knows a better way) to update table and SET column with DATEADD()
-- sample 1
-- DATEADD(type, value, date)
SELECT DATEADD(YEAR, 1, GETDATE())
Since I'm a lazy programmer, I don't want to keep updating the tables every time we run in to such situation. So I decided to write a small script (function) which gets TYPE, VALUE from the user and perform the operation. But I ran into a situation where I can't use TYPE as a variable.
-- Sample 2
-- Error in the code
DECLARE #type VARCHAR(10) = 'YEAR'
,#increment INT = 1
SELECT DATEADD(#type, #increment, GETDATE())
I can write a case statement where based on the 'TYPE' value I can select the update statement.
-- Sample 3
DECLARE #type VARCHAR(10) = 'YEAR'
,#increment INT = 1
UPDATE Table_name
SET Column_date = CASE #type WHEN 'YEAR' THEN DATEADD(YEAR, #increment, Column_date)
WHEN 'MONTH' THEN DATEADD(MONTH, #increment, Column_date)
-- and so on
END
But is there a way to perform the action without the case statement, or can anyone make sample code '2' run?
P.S. Its more of a knowledge based question - Its not holding me back, I'm just curious to know if its possible.
Thanks! :)
You could just get all of the type values from the user and just update them all at once.. Just default to 0 the ones they don't want to change
DECLARE #Year INT = 0,
#Month INT = 0,
#Day INT = 0
UPDATE Table_name
SET Column_date = DATEADD(YEAR, #Year, DATEADD(Month, #Month, DATEADD(Day, #Day, Column_date)))
An alternative to already provided solution is to use a dynamic SQL:
Setup:
-- drop table dbo.DateTest
create table dbo.DateTest
(
TheDate DATE,
UpdatedDate DATE
)
TRUNCATE TABLE dbo.DateTest
GO
INSERT INTO dbo.DateTest (TheDate) VALUES ('20150203'), ('20150506'), ('20141231')
GO
Code:
select * from dbo.DateTest
DECLARE #type VARCHAR(8) = 'year' -- this must be a DATEADD recognizable token
DECLARE #increment INT = 2
DECLARE #Sql NVARCHAR(1000) = N'
UPDATE dbo.DateTest SET UpdatedDate = DATEADD(' + #type + ', #increment, TheDate)'
EXEC sp_executesql #Sql, N' #increment INT', #increment
select * from dbo.DateTest
I wish date part could be provided as parameter, but it does not seem to be possible.