I have a table [MyTable] with a column [MyColumn] NVarchar(50). I have a nonclustered index on this column, now while running the below two queries:
SELECT 1
FROM [MyTable] M
WHERE M.[MyColumn] = #MyColumn
SELECT 1
FROM [MyTable] M
WHERE M.[MyColumn] = COALESCE(#MyColumn, M.[MyColumn] )
I noticed the first query is using Index Seek (NonClustered) and the second one is using Index Scan (Non Clustered). May I know how will I make use of index seek with coalesce or isnull ?
May I know how will I make use of
index seek with coalesce or isnull ?
Perhaps not an answer to your question but you can have two different queries. One for the case where #MyColumn is null and one for the case where you want to use #MyColumn in the where clause.
IF #MyColumn IS NULL
BEGIN
SELECT 1
FROM [MyTable] M
END
ELSE
BEGIN
SELECT 1
FROM [MyTable] M
WHERE M.[MyColumn] = #MyColumn
END
This isn't easy, since as Alex pointed out using the functions forces a scan, since the optimizer knows it needs to check every row.
What you CAN do is created a Computed Column for the result of your function, and index that column.
There's not really a prettier way to get a seek.
EDIT:
In rereading your question, this may not be an option for you unless you rethink your logic. You are integrating a variable into the function, and there is absolutely no way to index that.
EDIT 2:
Instead of your current logic, try something like:
...
WHERE (M.[MyColumn] = #MyColumn
OR #MyColumn IS NULL)
Using functions like COALESCE or ISNULL in the where clause is asking the server to search for the results of those functions - which are unknown until they are executed for every row in the resulting set, so there is no way for it to make use of an index.
To take full advantage of the index don't use functions in the WHERE clause, modify it with standard conditions e.g. WHERE MyColumn = #MyColumn OR #MyColumn IS NULL
I guess you will use this query in a more complex one, possibly withEXISTS:
EXISTS
( SELECT 1
FROM [MyTable] M
WHERE M.[MyColumn] = COALESCE(#MyColumn, M.[MyColumn] )
)
Try this instead:
EXISTS
( SELECT 1
FROM [MyTable] M
WHERE M.[MyColumn] = #MyColumn
)
OR EXISTS
( SELECT 1
FROM [MyTable] M
WHERE #MyColumn IS NULL
)
Or this one:
CASE WHEN #MyColumn IS NULL
THEN 1
ELSE
( SELECT 1
FROM [MyTable] M
WHERE M.[MyColumn] = #MyColumn
)
END
In the query with the coalesce clause the optimizer knows "MyColumn" is a range of values so it will decide to use a scan on the index. The only method to use a seek when there is a non null variable passed in is to code two stored procs and call the appropiate one via logic testing of the variable.
If you have a situation as simple as your example and you wish to use an Index Seek when the variable is NOT NULL then you should code the query as :
If #MyColumn is NULL
Begin
EXEC MyStoredProcWithMyColumn=Mycolumn
END
ELSE
Begin
EXEC MyStoredProcWithMyColumn=Variable #MyColumn
END
after creating two stored procedures one which returns the data using the where clause with the variable and one with the where cluase for the column equal to itself.
Related
I'm trying to add this as a Formula (Computed Column) but I'm getting an error message saying it is not valid.
Can anyone see what is wrong with the below formula?
IIF
(
select * from Config where Property = 'AutomaticExpiry' and Value = 1,
case when [ExpiryDate] IS NULL OR sysdatetimeoffset()<[ExpiryDate] then 1 else 0 end,
case when [ExpiryDate] IS NULL then 1 else 0 end
)
From BOL: ALTER TABLE computed_column_definition
computed_column_expression Is an expression that defines the value of
a computed column. A computed column is a virtual column that is not
physically stored in the table but is computed from an expression that
uses other columns in the same table. For example, a computed column
could have the definition: cost AS price * qty. The expression can be
a noncomputed column name, constant, function, variable, and any
combination of these connected by one or more operators. The
expression cannot be a subquery or include an alias data type.
Wrap the login in function. Something like this:
CREATE FUNCTION [dbo].[fn_CustomFunction]
(
#ExpireDate DATETIME2
)
RETURNS BIT
AS
BEGIN;
DECLARE #Value BIT = 0;
IF EXISTS(select * from Config where Property = 'AutomaticExpiry' and Value = 1)
BEGIN;
SET #Value = IIF (sysdatetimeoffset()< #ExpireDate, 1, 0)
RETURN #value;
END;
RETURN IIF(#ExpireDate IS NULL, 1, 0);
END;
GO
--DROP TABLE IF EXISTS dbo.TEST;
CREATE TABLE dbo.TEST
(
[ID] INT IDENTITY(1,1)
,[ExpireDate] DATETIME2
,ComputeColumn AS [dbo].[fn_CustomFunction] ([ExpireDate])
)
GO
INSERT INTO dbo.TEst (ExpireDate)
VALUES ('2019-01-01')
,('2018-01-01')
,(NULL);
SELECT *
FROM dbo.Test;
Youre trying to do something, what we're not quite sure - you've made a classic XY problem mistake.. You have some task, like "implement auto login expiry if it's on in the prefs table" and you've devised this broken solution (use a computed column/IIF) and have sought help to know why it's broken.. It's not solving the actual core problem.
In transitioning from your current state to one where you're solving the problem, you can consider:
As a view:
CREATE VIEW yourtable_withexpiry AS
SELECT
*,
CASE WHEN [ExpiryDate] IS NULL OR config.[Value] = 1 AND SysDateTimeOffset() < [ExpiryDate] THEN 1 ELSE 0 END AS IsValid
FROM
yourtable
LEFT JOIN
config
ON config.property = 'AutomaticExpiry'
As a trigger:
CREATE TRIGGER trg_withexpiry ON yourtable
AFTER INSERT OR UPDATE
AS
IF NOT EXISTS(select * from Config where Property = 'AutomaticExpiry' and Value = 1)
RETURN;
UPDATE yourtable SET [ExpiryDate] = DATE_ADD(..some current time and suitable offset here..)
FROM yourtable y INNER JOIN inserted i ON y.pk = i.pk;
END;
But honestly, you should be doing this in your front end app. It should be responsible for reading/writing session data and keeping things up to date and kicking users out if they're over time etc.. Using the database for this is, to a large extent, putting business logic/decision processing into a system that shouldn't be concerned with it..
Have your front end language implement a code that looks up user info upon some regular event (like page navigation or other activity) and refreshes the expiry date as a consequence of the activity, only if the expiry date isn't passed. For sure too keep the thing valid if the expiry is set to null if you want a way to have people active forever (or whatever)
One of our programmers tends to use isnull in MS SQL to compare with NULLs.
That is instead of writing Where ColumnName Is Null he writes Where IsNull(ColumnName,0)=0
I think that the optimizer will convert the latter into the former anyway, but if it does not - is there a way to prove that the latter is less effective, since it
1.Compares with null,
2.Converts to integer,
3.Compares 2 integers
instead of just comparing with null.
Both ways are really fast for me to be able to use the execution plans (and also I think, that the optimizer plays its part). Is there a way to prove him that just comparing with Null without IsNull is more effective (unless it's not).
Another obvious issue is the ISNULL precludes the use of indexes.
Run this setup:
create table T1 (
ID int not null primary key,
Column1 int null
)
go
create index IX_T1 on T1(Column1)
go
declare #i int
set #i = 10000
while #i > 0
begin
insert into T1 (ID,Column1) values (#i,CASE WHEN #i%1000=0 THEN NULL ELSE #i%1000 END)
set #i = #i - 1
end
go
Then turn on execution plans and run the following:
select * from T1 where Column1 is null
select * from T1 where ISNULL(Column1,0)=0
The first uses an index seek (using IX_T1) and is quite efficient. The second uses an index scan on the clustered index - it has to look at every row in the table.
On my machine, the second query took 90% of the time, the first 10%.
IsNull is not well used if you are using it in the where clause and comparing it to 0, the use of isnull is to replace the value null
http://msdn.microsoft.com/en-us/library/ms184325.aspx
For example:
SELECT Description, DiscountPct, MinQty, ISNULL(MaxQty, 0.00) AS 'Max Quantity'
FROM Sales.SpecialOffer;
I have a table that has a column of values that can be rowTypeID = (1,2,3, or null) . I would like to write a query that returns any row that doesn't have a value of 3 in it. In this example I want all NULL rows along with all 1,2 rows I just don't want rows with the value of 3
Set ANSI null ON is currently set for the database.
I'm curious as to why I can't write
select * from myTable where myCol <> 3
This query will not return any rows that have NULL in the myCol column
I have to write
select * from my Table where myCol <> 3 or myCol Is NULL
Do I always have to include the IS NULL or can I set it up so a where clause myCol <>3 will return rows that have Null as value for my Col
I think your approach is fine:
SELECT *
FROM MyTable
WHERE myCol <> 3 OR myCol IS NULL
Since you are asking for alternatives, another way to do it is to make your column NOT NULL and store another (otherwised unused) value in the database instead of NULL - for example -1. Then the expression myCol <> 3 will match your fake-NULL just as it would with any other value.
SELECT *
FROM MyTable
WHERE myCol <> 3
However in general I would recommend not to use this approach. The way you are already doing it is the right way.
Also it might be worth mentioning that several other databases support IS DISTINCT FROM which does exactly what you want:
SELECT *
FROM MyTable
WHERE myCol IS DISTINCT FROM 3
MySQL has the NULL-safe equal which can also be used for this purpose:
SELECT *
FROM MyTable
WHERE NOT myCol <=> 3
Unfortunately SQL Server doesn't yet support either of these syntaxes.
You must handle the NULLs one way or another, since expressions involving NULL evaluate to Unknown. If you want, you could instead do:
select *
from MyTable
where isnull(MyColumn, -1) <> 3
But this involves a magic number (-1), and is arguably less readable than the original test for IS NULL.
Edit: and, as SQLMenace points out, is not SARGable.
Whenever you test for a value all NULLs are omitted – after all, you are testing whether the value in some column passes certain criteria and NULL is not a value.
Do I always have to include the IS NULL or can I set it up so a where clause myCol <>3 will return rows that have Null as value for my Col?
You always, always, always have to include is null.
Because 3 does not equal Not/Applicable and it does not equal Unkown.
because you can't compare NULL to anything else, NULL is not even equal to NULL
DECLARE #i INT
DECLARE #i2 INT
SELECT #i = NULL, #i2 = NULL
IF #i = #i2
PRINT 'equal'
ELSE
PRINT 'not equal'
Consider following piece of code:
declare #var bit = 0
select * from tableA as A
where
1=
(case when #var = 0 then 1
when exists(select null from tableB as B where A.id=B.id)
then 1
else 0
end)
Since variable #var is set to 0, then the result of evaluating searched case operator is 1. In the documentation of case it is written that it is evaluated until first WHEN is TRUE. But when I look at execution plan, I see that tableB is scanned as well.
Does anybody know why this happens? Probably there are ways how one can avoid second table scan when another logical condition is evaluated to TRUE?
Because the plan that is compiled and cached needs to work for all possible values of #var
You would need to use something like
if (#var = 0)
select * from tableA
else
select * from tableA as A
where exists(select * from tableB as B where A.id=B.id)
Even OPTION RECOMPILE doesn't look like it would help actually. It still doesn't give you the plan you would have got with a literal 0=0
declare #var bit = 0
select * from
master.dbo.spt_values as A
where
1=
(case when 0 = #var then 1
when exists(select null from master.dbo.spt_values as B where A.number=B.number)
then 1
else 0
end)
option(recompile)
Plan http://img189.imageshack.us/img189/3977/executionplan.jpg
select * from
master.dbo.spt_values as A
where
1=
(case when 0 = 0 then 1
when exists(select null from master.dbo.spt_values as B where A.number=B.number)
then 1
else 0
end)
Plan http://img193.imageshack.us/img193/3977/executionplan.jpg
RE: Question in comments. Try the following with the "Include Actual Execution Plan" option enabled.
declare #var bit = datepart(second,GETDATE())%2
print #var
if (#var = 0)
select * from
master.dbo.spt_values --8BA71BA5-3025-4967-A0C8-38B9FBEF8BAD
else
select * from
master.dbo.spt_values as A --8BA71BA5-3025-4967-A0C8-38B9FBEF8BAD
where exists(select null from master.dbo.spt_values as B where A.number=B.number)
Then try
SELECT usecounts, cacheobjtype, objtype, text, query_plan
FROM sys.dm_exec_cached_plans
CROSS APPLY sys.dm_exec_sql_text(plan_handle)
CROSS APPLY sys.dm_exec_query_plan(plan_handle)
where text like '%8BA71BA5-3025-4967-A0C8-38B9FBEF8BAD%'
The Compiled Plan will look like
Plan http://img178.imageshack.us/img178/3977/executionplan.jpg
The Actual Execution Plan will show only one path was actually executed though.
if tableB has few rows, a table scan is the fastest way to go.
best source for dynamic search conditions:
Dynamic Search Conditions in T-SQL by Erland Sommarskog
there are a lot of subtle implications on how you do this as to if an index can be used or not. If you are on the proper release of SQL Server 2008 you can just add OPTION (RECOMPILE) to the query and the local variable's value at run time is used for the optimizations.
Consider this, OPTION (RECOMPILE) will take this code (where no index can be used with this mess of ORs):
WHERE
(#search1 IS NULL or Column1=#Search1)
AND (#search2 IS NULL or Column2=#Search2)
AND (#search3 IS NULL or Column3=#Search3)
and optimize it at run time to be (provided that only #Search2 was passed in with a value):
WHERE
Column2=#Search2
and an index can be used (if you have one defined on Column2)
My code is SELECT COUNT(*) FROM name_list WHERE [name]='a' LIMIT 1
It appears there is no limit clause in SQL Server. So how do i say tell me if 'a' exist in name_list.name?
IF EXISTS(SELECT * FROM name_list WHERE name = 'a')
BEGIN
-- such a record exists
END
ELSE
BEGIN
-- such a record does not exist
END
Points to note:
don't worry about the SELECT * - the database engine knows what you are asking
the IF is just for illustration - the EXISTS(SELECT ...) expression is what answers your question
the BEGIN and END are strictly speaking unnecessary if there is only one statement in the block
COUNT(*) returns a single row anyway, no need to limit.
The ANSI equivalent for LIMIT is TOP: SELECT TOP(1) ... FROM ... WHERE...
And finally, there is EXISTS: IF EXISTS (SELECT * FROM ... WHERE ...).
The TOP clause is the closest equivalent to LIMIT. The following will return all of the fields in the first row whose name field equals 'a' (altough if more than one row matches, the row that ets returned will be undefined unless you also provide an ORDER BY clause).
SELECT TOP 1 * FROM name_list WHERE [name]='a'
But there's no need to use it if you're doing a COUNT(*). The following will return a single row with a single field that is number of rows whose name field eqals 'a' in the whole table.
SELECT COUNT(*) FROM name_list WHERE [name]='a'
IF (EXISTS(SELECT [name] FROM name_list where [name] = 'a'))
begin
//do other work if exists
end
You can also do the opposite:
IF (NOT EXISTS(SELECT [name] FROM name_list where [name] = 'a'))
begin
//do other work if not exists
end
No nono that is wrong.
First there is top, so you have to say something like:
select top 1 1 from name_list where [name]='a'
You'll get a row with only a unnamed field with 1 out of the query if there is data, and no rows at all if there is no data.
This query returns exactly what you intended:
SELECT TOP 1 CASE WHEN EXISTS(SELECT * WHERE [name] = 'a') THEN 1 ELSE 0 END FROM name_list