SQL Server 2014. Fastest way to update a numeric field - sql

I can think of this 2 options, but not sure if there is any other one. What will be faster?
DECLARE #dateVar datetime = '20170101';
UPDATE SALESLINE
SET REMAINSALESFINANCIAL= 0
WHERE CONVERT(DATE, CREATEDDATETIME) < CONVERT(DATE, #dateVar)
or
DECLARE #dateVar datetime = '20170101';
UPDATE SALESLINE
SET REMAINSALESFINANCIAL= 0
WHERE CONVERT(DATE, CREATEDDATETIME) < CONVERT(DATE, #dateVar)
AND REMAINSALESFINANCIAL != 0
REMAINSALESFINANCIAL is numeric
I have an index on CREATEDDATETIME

Assuming that REMAINSALESFINANCIAL does not contain any NULLs you can just use
UPDATE SALESLINE
SET REMAINSALESFINANCIAL = 0
WHERE CREATEDDATETIME < CONVERT(DATE, #dateVar)
AND REMAINSALESFINANCIAL <> 0
As CONVERT(DATE, #dateVar) will have a time part of 00:00:00 and so any datetime that is less than this must have a date part of the previous day or earlier.
Casting a datetime column to date doesn't prevent index use but it is more optimal not to.

I agree with the other answers that the second option should be faster but there are additional considerations from a performance perspective.
If the assigned value has a time of midnight, the explicit conversion is unnecessary.
Instead of a local variable, I suggest the query be parameterized so that stats histogram is used instead of average cardinality to better optimize the query. This is automatic if your actual query uses a stored procedure parameter directly or is called as a parameterized query from application code. If parameter sniffing is an issue (unlikely here, IMHO), add an OPTION RECOMPILE query hint.
EXEC sp_executesql N'
UPDATE SALESLINE
SET REMAINSALESFINANCIAL= 0
WHERE
CREATEDDATETIME < #dateVar
AND REMAINSALESFINANCIAL <> 0;'
, N'#dateVar datetime'
, #dateVar = '20180101';

The second version is better. If you have some rows where REMAINSALESFINANCIAL = 0, then the engine will not even try to update those rows, filtering them out earlier in the process. Note: this assumes that REMAINSALESFINANCIAL is never NULL.
In general, having a function on a column in the WHERE is a bad idea. In this case, though, SQL Server will still use an index, so that is not a problem.
It would be interesting to compare the performance with an index on (CREATEDDATETIME) versus (CREATEDDATETIME, REMAINSALESFINANCIAL). In the first case, it has to work harder to find the rows to update. In the second, it has to update the index when it changes the values.

Related

SQL query based on two values in a column with a date range

So here is what I'm attempting to accomplish. I want to have returned all rows within a specified date range where the value in a column is one value 'C' or another 'X'. I'm not huge in SQL and much more comfortable in scriptwriting for excel or google sheets. I would assume there's a pretty simple solution but here is what I have written so far that's not working.
Declare #FromDate DateTime
Declare #ToDate DateTime
Set #FromDate='20201027'
Set #ToDAte ='20201102'
select * from oehdr where OrderStatus='C' or OrderStatus='X'
The errors I've received have been "Invalid SQL, No Memory" and a script error when I modify the current attempt.
There's not enough info in the question. We don't know the name of the date column, or whether the time is included, or whether the ToDate value is inclusive of the whole day. It's not even 100% clear exactly what DB you're using, as there's some conflicting info with the question.
But I'll do the best I can making some guessues:
Declare #FromDate DateTime = '20201027'
Declare #ToDate DateTime = '20201102'
SELECT *
FROM oehdr
WHERE OrderStatus IN ('C', 'X')
AND OrderDate >= #FromDate AND OrderDate < DATEADD(day, 1, #ToDate)
Notice I did not use the BETWEEN operator. BETWEEN is inclusive at both ends of the range, which is rarely what I want for dates. Instead, when I want the full day, I add one day and use an exclusive condition for the end of the range.

Variables make query performance worse

I have this query and the performance slows down drastically when I declare variables:
DECLARE #StartDate DATETIME,
#EndDate DATETIME
SET #StartDate = '2018-08-13'
SET #EndDate = '2018-08-19'
SELECT *
FROM [DIME_WH].[dbo].[FactOrderLines2] FL (nolock)
WHERE DD_OrderDate >= #StartDate
AND DD_OrderDate <= #EndDate
This is much slower than this SQL statement:
SELECT *
FROM [DIME_WH].[dbo].[FactOrderLines2] FL (nolock)
WHERE DD_OrderDate >= '2018-08-01'
AND DD_OrderDate <= '2018-08-17'
Both queries will return the same results in the end.
SELECT * FROM [DIME_WH].[dbo].[FactOrderLines2] FL (nolock)
WHERE DD_OrderDate >= '2018-08-01'
AND DD_OrderDate <= '2018-08-17'
When constant is used in parameter, then Optimiser create special plan for this query.so if same query is executed with same value then plan is reuse, if value is change then another plan is created.
So Parameter with constant value is fast.
SELECT *
FROM [DIME_WH].[dbo].[FactOrderLines2] FL (nolock)
WHERE DD_OrderDate >= #StartDate
AND DD_OrderDate <= #EndDate
When variable is use in parameter.Then Optimizer create Execution plan for the First parameter value that was passed .
For Example #StartDate='2018-08-01' and #EndDate='2018-08-07' value were pass for first time.
Then optimal execution plan is created by optimiser. This plan is good enough for this value.
Next Time #StartDate='2018-08-01' and #EndDate='2018-08-31' value is pass then same previous plan is use which may not be optimal for this parameter.
In another word same plan which was Optimal for first value is Sub optimal for another value.
so query may perform poor and slow.This is known as Parameter sniffing.
There are several ways to overcome this problem.
Parameter Sniffing
Note : In this thread we are only focussing on why variable performance is slow while other factor remaining constant.
This is because SQL Server does not know the values of your variables at optimization time - when it makes an estimate and can not look up any statistics for it (as a one possibility), so it's (most likely) just scans the whole table instead of make a lookup (seek). They can be "sniffed" if used inside of stored procedure or parameterized with sp_executesql
The problem could be parameter sniffing, maybe not. I'll skip that topic since #KumarHarsh already covered it. The most important question here is: What data type is FactOrderLines.DD_OrderDate? This is important for performance reasons as well as correctness.
First for performance. If DD_OrderDate is a DATE datatype and your variables or parameters are DATETIME then the optimizer has to jump through extra hoops to utilize your index or will be forced to do a scan instead of a seek. Note the following sample data:
USE tempdb;
GO
IF OBJECT_ID('#FactOrderLines') IS NOT NULL DROP TABLE #FactOrderLines;
GO
CREATE TABLE #FactOrderLines(someId INT IDENTITY, DD_OrderDate DATETIME NOT NULL);
CREATE CLUSTERED INDEX nc_factOrderLines ON #FactOrderLines(DD_OrderDate);
INSERT #FactOrderLines(DD_OrderDate)
SELECT TOP (10000) DATEADD(DAY,CHECKSUM(NEWID())%100, getdate())
FROM sys.all_columns;
GO
Now let's compare the execution plans for the following queries:
-- AS DATE
DECLARE #StartDate DATE = '2018-08-01',
#EndDate DATE = '2018-08-20';
SELECT *
FROM #FactOrderLines
WHERE DD_OrderDate >= #StartDate
AND DD_OrderDate <= #EndDate
OPTION (RECOMPILE)
GO
-- AS DATETIME
DECLARE #StartDate DATETIME = '2018-08-01',
#EndDate DATETIME = '2018-08-31';
SELECT *
FROM #FactOrderLines
WHERE DD_OrderDate >= #StartDate
AND DD_OrderDate <= #EndDate
OPTION (RECOMPILE);
Execution plans:
For this reason - you want to make sure that you're using the same datatype for your variables/parameters as for the column they are working against.
Now about correctness; note this query:
DECLARE #StartDate DATE = '2018-08-01',
#EndDate DATE = '2018-08-20';
SELECT
[getdate as datetime] = GETDATE(),
[#enddate as datetime] = CAST(#EndDate AS DATETIME),
[getdate as date] = CAST(GETDATE() AS DATE),
[datetime equality] = IIF(GETDATE() > #EndDate,'yep','nope'),
[date equality] = IIF(CAST(GETDATE() AS DATE) > #EndDate,'yep','nope');
Results:
getdate as datetime #enddate as datetime getdate as date datetime equality date equality
----------------------- ----------------------- --------------- ----------------- -------------
2018-08-20 13:52:46.247 2018-08-20 00:00:00.000 2018-08-20 yep nope
Values of the date format translate into datetime as 0 hour, 0 second...

SQL query Performance when using Datetime in where / On clause

I have the 5l record in table. need to query...Which is one is query fast,Input parameter #RegistrationFrom DATE,#RegistrationTo DATE
Approach #1:
WHERE
CAST(Act.RegistrationOn AS DATE) BETWEEN CAST(#RegistrationFrom AS DATE)
AND CAST(#RegistrationTo AS DATE)
Approach #2 - convert into datetime:
DECLARE #From DATETIME, #Todate DATETIME;
SELECT #From = #RegistrationFrom;
SELECT #Todate = DATEADD(day, 1, #RegistrationTo);
WHERE
Act.RegistrationOn BETWEEN #From AND #Todate
Approach #3:
WHERE CONVERT(VARCHAR, Act.RegistrationOn,101) BETWEEN #From AND #Todate
I ma getting fast response approach 3 than above 1,2.
How it is working?
I would write the query as:
SELECT #Todate = DATEADD(day, 1, #RegistrationTo);
. . .
WHERE Act.RegistrationOn >= #From AND
Act.RegistrationOn < #Todate
Whether you use variables or a cast on the variable should have no or little affect on performance. More important considerations are the use of indexes and interpretability (does the code do what you intend).
Aaron Bertrand has a very good blog on why you shouldn't use BETWEEN for dates.
As for the first version . . . it is actually more reasonable than you might think. In general, function calls prevent the use of indexes on columns. However, SQL Server makes an exception for conversion of a datetime to date. So, it will still use an index.
That said, I would still go with the above version.
Try it like this:
WITH MyPrms AS
(
SELECT CAST(#RegistrationFrom AS DATE) AS fromD
,CAST(#RegistrationTo AS DATE) + 1 AS toD
)
SELECT *
FROM MyPrms
CROSS JOIN Act
WHERE Act.Registration>=fromD AND Act.RegistrationOn<toD;
It is fully inlineable (in VIEWS, functions...) and sargable (optimizer can use indexes)
EDIT just for clearity
By adding +1 to your upper border date (which was casted to a timeless DATE before, the upper border is midnight after the last day. By using a < you will get all data from the full day. BETWEEN includes the border which can lead to unexpected errors...
Both options are equivalent from performance point of view:
Approach 1
WHERE Act.RegistrationOn AS DATE BETWEEN CAST(#RegistrationFrom AS DATE) AND CAST(#RegistrationTo AS DATE)
Approach 2
WHERE RegistrationOn BETWEEN #From AND #Todate
Each casting in Approach 1 is executed only once and not for each row as you might expected.
For clarity, I would take option 2 or even better send the parameter with the right type to this query.
Also, an index on RegistrationOn column would definitively help. Please note a CAST on RegistrationOn column has been removed from Approach 1 as this would prevent SQL Server to use this index.
WHERE CONVERT(VARCHAR, Act.RegistrationOn,101) BETWEEN #From AND #Todate

SQL statement between date

This is driving me crazy and not sure what I'm missing here..
so here is my data column looks like:
StartDateTime:
---------------
2012-01-17 11:13:46.530
2012-01-17 11:17:22.530
2012-02-17 11:31:22.223
here is my query trying to get:
select * from tablName
where convert(varchar(10), startDateTime, 101) between '2012-01-17' and '2012-01-17'
based on the above I should be getting TWO rows? but it does not, it return zero rows. what will be the correct way of doing?
PS:
I've looked at the MSDN site too:
Your query would only match dates that are between 2012-01-17 00:00:00 and 2012-01-17 00:00:00. So, the only matches would be when the date is exactly 2012-01-17 00:00:00.
Instead, I would do this:
declare #dateInput as DateTime
set #dateInput = '2012-01-17'
select *
from tablName
where startDateTime >= #dateInput
and startDateTime < dateadd(day, 1, #dateInput)
Note: SQL Server 2008+ has a new data type Date with no time component that can make these types of queries more readable.
There is now more information so I'll add a more appropriate answer.
The requirements are now a stored procedure passed a Date type parameter, not DateTime, and the desire is to return rows from a table based on criterion against a DateTime field named StartDateTime...
create procedure dbo.spGetEntriesForOneDay
#DesiredDate DateTime
as
SET NOCOUNT ON;
SET #DesiredDate = DATEADD(day, DATEDIFF(day, 0, #DesiredDate), 0)
SELECT Field1, Field2 -- see note 1
FROM dbo.TableName
WHERE StartDateTime >= #DesiredDate -- see note 2
AND StartDateTime < DATEADD(day, 1, #DesiredDate) -- see note 3
NOTE 1: Don't use * in production code, especially in a stored procedure. Besides being wasteful by returning columns you probably don't need and precluding the optimization of a covering index on a subset of the columns required you would need to recompile this stored procedure whenever the underlying table is altered in order to avoid unpredictable results.
NOTE 2: Avoid wrapping fields in functions. A field not wrapped in a function can potentially be matched by the optimizer to an index while a field wrapped in a function never will.
NOTE 3: #Martin Smith and #RedFilter are correct in that .997 precision assumes DateTime datatype forever; this approach is more future proof because is makes no assumptions of data type precision.
You're using a datetime field (I'm guessing).
Don't forget the time:
select * from tablName
where startDateTime between '2012-01-17' and '2012-01-17 23:59:59.997'
You can use the DateDiff function in the where clause. It would look like this:
select col1, col2 from tablName where DateDiff(day, startDateTime, #DesiredDate) = 0

How to make faster this statement : "paramDate Between startDate and NULL"?

This query is taking long time when endDate is null (i think that its about case statement, before case statement it was fast)
SELECT *
FROM HastaKurumlari
WHERE CONVERT(SMALLDATETIME,'21-05-2009',103)
BETWEEN startDate
AND (CASE WHEN endDate IS NULL THEN GETDATE() ELSE endDate END)
What should i use, when endDate is null to make it faster ?
Here's the query without CONVERT or CASE:
SELECT *
FROM HastaKurumlari
WHERE '21-05-2009' between startDate and IsNull(endDate,getdate())
To make sure Sql Server doens't evaluate getdate() for every row, you could cache it, although I'm pretty sure Sql Server is smart enough by default:
declare #now datetime
set #now = getdate()
SELECT *
FROM HastaKurumlari
WHERE '21-05-2009' between startDate and IsNull(endDate,#now)
Posting the query plan could help explain why the query is slow:
SET SHOWPLAN_TEXT ON
go
SELECT *
FROM HastaKurumlari
WHERE CONVERT(SMALLDATETIME,'21-05-2009',103)
BETWEEN startDate
AND (CASE WHEN endDate IS NULL THEN GETDATE() ELSE endDate END)
If it is performance critical, then perhaps just don't use null for the open end-date - use the maximum supported datetime instead (probably lots of 9s).
I'd also do the conversion separately:
DECLARE #when datetime
SET #when = CONVERT(SMALLDATETIME,'21-05-2009',103)
SELECT *
FROM HastaKurumlari
WHERE #when
BETWEEN startDate AND endDate
There is still something a bit different in the above and your original; if you can explain the intent of the GETDATE() check I might be able to tidy (read:fix) it a bit.
As a starting point, factor out GETDATE() so that its called just once, and you should see an improvement in speed.
The way you've written it you are asking for GETDATE() to be evaluated every time enddate is null.
Since GETDATE() is a non-deterministic function the query cannot be optimised and will tend to under perform.
You could try the coalesce function:
select *
from HastaKurumlari
where convert(smalldatetime, '21-05-2009', 103)
between startDate and coalesce(endDate, getdate());
The only way to be certain is to try any alternatives and view the execution plan generated for each query.