#var IS Null condition causing clustered index scans - sql

I have a query
Select Column1, column2.... column30
From Table
Where (#var IS NULL OR Col1 = #var)
This can get resolved to a clustered index scan where every row in a table getting checked against this condition #var IS NULL and the index set up on the column Col1 is disregarded. Is there a way to re-write this query to enable the optimizer to use the index on col1? Thank you!

Related

Why the indexes not speeding up the query?

I have the following query:
SELECT TOP 1000 *
FROM MyTable
WHERE Status = 'N' AND Type is not null
ORDER BY mytable.id
MyTable has 130 million rows.
I also created these indexes:
CREATE INDEX "MyTableIndex_1" ON MyTable (Status);
CREATE INDEX "MyTableIndex_2" ON MyTable (Type);
The ID column was already a clustered index.
Somehow the query is still very slow.
What am I missing?
try a multi column index
CREATE INDEX "MyTableIndex_StatusType" ON MyTable (Status, Type);
if that doesn't work then do some research on 'Covering Indexes'
Type is not null , the index on this column might not help.
BTW, can you try update index statistics before the query?

Does Adding Indexes speed up String Wildcard % searches?

We are conducting a wildcard search on a database table with column string. Does creating a non-clustered index on columns help with wildcard searches? Will this improve performance?
CREATE TABLE [dbo].[Product](
[ProductId] [int] NOT NULL,
[ProductName] [varchar](250) NOT NULL,
[ModifiedDate] [datetime] NOT NULL,
...
CONSTRAINT [PK_ProductId] PRIMARY KEY CLUSTERED
(
[ProductId] ASC
)
)
Proposed Index:
CREATE NONCLUSTERED INDEX [IX_Product_ProductName] ON [dbo].[Product] [ProductName])
for this query
select * from dbo.Product where ProductName like '%furniture%'
Currently using Microsoft SQL Server 2019.
Creating a normal index will not help(*), but a full-text index will, though you would have to change your query to something like this:
select * from dbo.Product where ProductName CONTAINS 'furniture'
(* -- well, it can be slightly helpful, in that it can reduce a scan over every row and column in your table into a scan over merely every row and only the relevant columns. However, it will not achieve the orders of magnitude performance boost that we normally expect from indexes that turn scans into single seeks.)
For a double ended wildcard search as shown, an index cannot help you by restricting the rows SQL Server has to look at - a full table scan will be carried out. But it can help with the amount of data that has to be retrieved from disk.
Because in ProductName like '%furniture%', ProductName could start or end with any string, so no index can reduce the rows that have to be inspected.
However if a row in your Product table is 1,000 characters and you have 10,000 rows, you have to load that much data. But if you have an index on ProductName, and ProductName is only 50 characters, then you only have to load 10,000 * 50 rather than 10,000 * 1000.
Note: If the query was a single ended wildcard search with % at end of 'furniture%', then the proposed index would certainly help.
First you can use FTS to search words into sentences even partially (beginning by).
For those ending by or for those containing you can use a rotative indexing technic:
CREATE TABLE T_WRD
(WRD_ID BIGINT IDENTITY PRIMARY KEY,
WRD_WORD VARCHAR(64) COLLATE Latin1_General_100_BIN NOT NULL UNIQUE,
WRD_DROW AS REVERSE(WRD_WORD) PERSISTED NOT NULL UNIQUE,
WRD_WORD2 VARCHAR(64) COLLATE Latin1_General_100_CI_AI NOT NULL) ;
GO
CREATE TABLE T_WORD_ROTATE_STRING_WRS
(WRD_ID BIGINT NOT NULL REFERENCES T_WRD (WRD_ID),
WRS_ROTATE SMALLINT NOT NULL,
WRD_ID_PART BIGINT NOT NULL REFERENCES T_WRD (WRD_ID),
PRIMARY KEY (WRD_ID, WRS_ROTATE));
GO
CREATE OR ALTER TRIGGER E_I_WRD
ON T_WRD
FOR INSERT
AS
SET NOCOUNT ON;
-- splitting words
WITH R AS
(
SELECT WRD_ID, TRIM(WRD_WORD) AS WRD_WORD, 0 AS ROTATE
FROM INSERTED
UNION ALL
SELECT WRD_ID, RIGHT(WRD_WORD, LEN(WRD_WORD) -1), ROTATE + 1
FROM R
WHERE LEN(WRD_WORD) > 1
)
SELECT *
INTO #WRD
FROM R;
-- inserting missing words
INSERT INTO T_WRD (WRD_WORD, WRD_WORD2)
SELECT WRD_WORD, LOWER(WRD_WORD) COLLATE SQL_Latin1_General_CP1251_CI_AS
FROM #WRD
WHERE WRD_WORD NOT IN (SELECT WRD_WORD
FROM T_WRD);
-- inserting cross reference words
INSERT INTO T_WORD_ROTATE_STRING_WRS
SELECT M.WRD_ID, ROTATE, D.WRD_ID
FROM #WRD AS M
JOIN T_WRD AS D
ON M.WRD_WORD = D.WRD_WORD
WHERE NOT EXISTS(SELECT 1/0
FROM T_WORD_ROTATE_STRING_WRS AS S
WHERE S.WRD_ID = M.WRD_ID
AND S.WRS_ROTATE = ROTATE);
GO
Then now you can insert into the first table all the words you want from your sentences and finding it by ending of partially in querying those two tables...
As an example, word:
WITH
T AS (SELECT 'électricité' AS W)
INSERT INTO T_WRD
SELECT W, LOWER(CAST(W AS VARCHAR(64)) COLLATE SQL_Latin1_General_CP1251_CI_AS) AS W2
FROM T;
You can now use :
SELECT * FROM T_WRD;
SELECT * FROM T_WORD_ROTATE_STRING_WRS;
To find those partial words
It depends on the optimizer. Like usually requires a full table scan. if the optimizer can scan an index for matches than it will do an index scan which is faster than a full table scan.
if the optimizer does not select an index scan you can force it to use an index. You must measure performance times to determine if using an index scan decreases search time
Use with (index(index_name)) to force an index scan e.g.
select * from t1 with (index(t1i1)) where v1 like '456%'
SQL Server Index - Any improvement for LIKE queries?
If you use %search% pattern, the optimizer will always perform a full table scan.
Another technique for speeding up searches is to use substrings and exact match searches.
Yes, the part before the first % is matched against the index. Of course however, if your pattern starts with %, then a full scan will be performed instead.

Are the following SQL querys troublesome or fine from performance perspective?

Can somebody tell me if the following query's are good or bad from performance perspective ?
CREATE INDEX tbl_index ON tbl_example(column1, column2)
SELECT ID, column1, column2
FROM tbl_example
WHERE column1 = #param1
AND column2 = #param2
SELECT ID, column1, column2
FROM tbl_example
WHERE column2 = #param2
Actually, neither query may be able to use the index. In the first query, the WHERE clause can use the index. However, in addition to column1 and column2, you are also selecting ID. The index does not include the ID value, which means that SQL Server would have to do a lookup on the clustered index to find that value. Given the cost of such a lookup, the optimizer may choose to not use the index at all.
For the second query, the WHERE clause restricts using column2. Unfortunately, column2 does not form a leftmost portion of the index, so the index cannot be used in this query either.
Here is an example of query which should be able to use the index:
SELECT column1, column2
FROM tbl_example
WHERE column1 = #param1
AND column2 = #param2;
The following would also work:
SELECT column1, column2
FROM tbl_example
WHERE column1 = #param1;
You could also stick with your very first query and instead include ID column in the leaf node of the index:
CREATE INDEX tbl_index ON tbl_example(column1, column2) INCLUDE (ID)
This should make it possible for your original first query to use the index.

Where Clause Index Scan - Index Seek

I have below table:
CREATE TABLE Test
(
Id int IDENTITY(1,1) NOT NULL,
col1 varchar(37) NULL,
testDate datetime NULL
)
insert Test
select null
go 700000
select cast(NEWID() as varchar(37))
go 300000
And below indexes:
create clustered index CIX on Test(ID)
create nonclustered index IX_RegularIndex on Test(col1)
create nonclustered index IX_RegularDateIndex on Test(testDate)
When I query on my table:
SET STATISTICS IO ON
select * from Test where col1=NEWID()
select * from Test where TestDate=GETDATE()
First is making index scan whereas the second index seek. I expect that both of them must make index seek. Why does the first make index scan?
There is an implicit convert generated becuase the NEWID() function returns a value which is of uniqueidentifier datatype, and that is different than your VARCHAR datatype declared for the column.
Just try hovering your mouse over the SELECT part of the plan, where there is a "warning" sign.
Due to the fact that there is a mismatch between compared datatypes, the optimizer can't look at statistics and estimate how many rows with that NEWID() value there are in the table.
And because of the implicit convert, the optimizer thus decides that it is better to go and get all the rows (thus the SCAN), then pass them through the FILTER operation, where it does a conversion of the value of Col1 to a uniqueidentifier datatype and then removing the additional rows that do not match the filter condition.
As opposed to GETDATE() which returns a datetime value, which is of the same datatype as your testDate column, so no datatype conversion is needed and values can be compared as they are.

Stored procedure SQL execution plan

I'm a bit stuck with a stored procedure that is executing really slow. The stored procedure basically contains a query that uses an incoming parameter (in_id) and is put in a cursor like this:
open tmp_cursor for
select col1, col2, col3
from table1 tab
where ((in_id is null) or (tab.id = in_id)); -- tab.id is the PK
When I get an execution plan for the SQL query separately with predefined value, I get good results with the query using an index. However when I call the procedure from my application, I see that no index is being used and the table gets full scan, thus giving slow performance.
If I remove the first part of WHERE clause "(in_id is null)" the performance from the application is fast again.
How come the index isn't used during the call from my application (in_id is passed in)?
in_id is null
I have answered a similar question here https://stackoverflow.com/a/26633820/3989608
Some facts about NULL values and INDEX:
Entirely NULL keys are not entered into a ‘normal’ B*Tree in Oracle
Therefore, if you have a concatenated index on say C1 and C2, then you will likely find NULL values in it – since you could have a row where C1 is NULL but C2 is NOT NULL – that key value will be in the index.
Some portion of the demonstration by Thomas Kyte regarding the same:
ops$tkyte#ORA9IR2> create table t
2 as
3 select object_id, owner, object_name
4 from dba_objects;
Table created.
ops$tkyte#ORA9IR2> alter table t modify (owner NOT NULL);
Table altered.
ops$tkyte#ORA9IR2> create index t_idx on t(object_id,owner);
Index created.
ops$tkyte#ORA9IR2> desc t
Name Null? Type
----------------------- -------- ----------------
OBJECT_ID NUMBER
OWNER NOT NULL VARCHAR2(30)
OBJECT_NAME VARCHAR2(128)
ops$tkyte#ORA9IR2> exec dbms_stats.gather_table_stats(user,'T');
PL/SQL procedure successfully completed.
Well, that index can certainly be used to satisfy “IS NOT NULL” when applied to OBJECT_ID:
ops$tkyte#ORA9IR2> set autotrace traceonly explain
ops$tkyte#ORA9IR2> select * from t where object_id is null;
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=34)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=3 Card=1 Bytes=34)
2 1 INDEX (RANGE SCAN) OF 'T_IDX' (NON-UNIQUE) (Cost=2 Card=1)
In fact – even if the table did not have any NOT NULL columns, or we didn’t want/need to have a concatenated index involving OWNER – there is a transparent way to find the NULL OBJECT_ID values rather easily:
ops$tkyte#ORA9IR2> drop index t_idx;
Index dropped.
ops$tkyte#ORA9IR2> create index t_idx_new on t(object_id,0);
Index created.
ops$tkyte#ORA9IR2> set autotrace traceonly explain
ops$tkyte#ORA9IR2> select * from t where object_id is null;
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=34)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=3 Card=1 Bytes=34)
2 1 INDEX (RANGE SCAN) OF 'T_IDX_NEW' (NON-UNIQUE) (Cost=2 Card=1)
Source : Something about nothing by Thomas Kyte
Assuming that in_id is a query parameter - not a column name:
The query has to have only ONE exec plan, regardless of the input. So if you pass parameter in_id as NULL, then it is supposed to return ALL rows. If you pass non-NULL in_id is should return only a single PK value.
So Oracle chooses the "worst possible" exec. plan to deal with "worst possible" scenario. The "generic" queries are road to hell. Simply split the query into two.
select col1, col2, col3
from table1 tab
where in_id is null or in_id is not null;
This will use FULL table scan, which is the best way how to get all the rows.
select col1, col2, col3
from table1 tab
where tab.id = in_id; -- tab.id is the PK
This will use UNIQUE index scan, which is the best way how to get a single indexed row.
select col1, col2, col3 from table1 tab where (tab.id = nvl(in_id,tab.id));
May be help.. or you may use oracle hint
+Use_concat