Contains performs MUCH slower with variable vs constant string SQL Server - sql

For some unknown reason I'm running into a problem when passing a variable to a full text search stored procedure performs many times slower than executing the same statement with a constant value. Any idea why and how can that be avoided?
This executes very fast:
SELECT * FROM table
WHERE CONTAINS (comments, '123')
This executes very slowly and times out:
DECLARE #SearchTerm nvarchar(30)
SET #SearchTerm = '123'
SET #SearchTerm = '"' + #SearchTerm + '"'
SELECT * FROM table
WHERE CONTAINS (comments, #SearchTerm)
Does this make any sense???

I've seen the same issue with trying to use a variable for top. SQL Server is not able to tune a query that is using a variable in this way.
You should try using the execsql command.

does this run slow: SELECT * FROM table WHERE CONTAINS (comments, N'123') ??
you are using a varchar '123' in the first example and a nvarchar variable in the second example. This type conversion could be causing you the problem. What is the column defined as?
Also why wrap the variable's value in " double qoutes, but not do the same in the first example. When you run the exact same queries using a literal and a variable do the run differently?

I think Matt b is right. The first query you are searching for
123
In the second query, you are searching for
'123'
The second query with the quotes is probably returning no results, and your program is probably timing out, not the query.

Related

Replace function SQL

I have problem that replace function does not work
DECLARE #Tabela nvarchar(25)
DECLARE #query nvarchar(max)
SET #Tabela = '_#tmp_tt2_POS_racuni_'
SET #query = 'SELECT * INTO '+#Tabela+((replace(convert(varchar(10), getdate(),121),'''-''',''''))+'-'+(replace(convert(nvarchar(10),getdate(),108),''':''','''')))+'NP'+' FROM _tabels'
PRINT #query
SELECT *
INTO _#tmp_tt2_POS_racuni_2021-12-21-11:15:27NP
FROM _tabels
Completion time: 2021-12-21T11:15:27.0724917+01:00
You should use FORMAT and specify the format you want directly instead of going through intermediate formats. For example :
select format(getdate(),'yyyyMMddhhmmss')
Produces 20211221124017. FORMAT is slower than CONVERT but in this case it's only called once. It's far more important to write a readable query that produces the correct result.
That said, it's probably better to use table partitioning instead of creating lots of temporary tables with a date in the name. All supported SQL Server versions and editions support partitioning, even LocalDB
The quotes you use are two too many.
You are using replace(date,''':''',''''). This will replace ':' with ''. However, the getdate() doesn't have quotes itself. I guess you did that because of the dynamic sql you are using - but for the dates, you should omit the quotes:
replace(date,':','')
Firstly, let's get onto the real problem that is discussed at lengths in the comments; this is a terrible idea.
The fact you want to create a table for an exact point in time smells very strongly of an XY Problem. What is the real problem you are trying to solve with this? Most likely what you really want is a partitioned table or a temporal table, so that you can query the data for an exact point in time. Which you need, we don't know, but I would suggest that you rethink your "solution" here.
As for the problem, it's working exactly as intended. Let's look at your REPLACE in solitude:
replace(convert(varchar(10), getdate(),121),'''-''','''')
So, in the above, you want to replace '-' (a hyphen wrapped in single quotes) with '' (2 single quotes). You don't want to replace a hyphen (-) with a zero length string; that would be REPLACE(..., '-','').
The style you are using, 121 gives the format yyyy-mm-dd hh:mi:ss.mmm, which doesn't contain a single single quote ('), so no wonder it isn't finding the pattern.
Though you don't need REPLACE on that date at all. YOu are taking the first 10 characters or the style and then removing the hyphens (-) to get yyyyMMdd, but there is already a style for that; style 112.
The above could be rewritten as:
DECLARE #Tabela sysname;
DECLARE #query nvarchar(max);
SET #Tabela = N'_#tmp_tt2_POS_racuni_';
SET #query = N'SELECT * INTO dbo.'+QUOTENAME(CONCAT(#Tabela,CONVERT(nvarchar(8),GETDATE(),112),,N'-'.REPLACE(CONVERT(nvarchar(10),GETDATE(),108),':',''),N'',N'NP')+N' FROM dbo._tabels;'
PRINT #query;

Getting different results from LIKE query and stored procedure for (starts and ends with) search

I am trying to implement a stored procedure that gets the two parameters #startsWith and #endsWith and constructs this query string:
#startswith + '%' + #endsWith
To search for entries of a single column (Name) that start end end with the parameters. Here is the stored procedure:
CREATE PROCEDURE termNameStartsEndsWith(
#startsWith AS nvarchar,
#endsWith AS nvarchar
)
AS
BEGIN
SELECT * FROM Term WHERE
Name LIKE (#startsWith + '%' + #endsWith)
END;
However, I get unexpected results when one of the two query parameters is empty (''). Here is an example where I would expect only results where the Term column entry starts with 'water', but i get a bunch of additional rows:
I dont get these results when executing as a query:
So I expect that the problem is coming from the empty string concatenation being handled differently in a stored procedure? If so, how can I adapt the procedure accordingly?
Thanks for the help in advance.
As noted by Larnu in the comments, the issue isn't the query, it's your parameter declarations.
You have two NVARCHAR(n) parameters declared, but there is no length declared for either of them. From the documentation (emphasis added):
When n is not specified in a data definition or variable declaration statement, the default length is 1. When n is not specified with the CAST function, the default length is 30.
So both parameters are exactly one character long. Conveniently, SQL Server will let you assign a longer value to that parameter, and then just take the first character and silently truncate the rest.
Modify your parameters to have length definitions, and you should be in business.

How do you pass values for a parameter by position when you need to check multiple values?

I created a stored procedure (spBalanceRange) with 2 optional parameters. They've been set to a default value and the sp works fine when I pass only 1 value per parameter by position. However, I have a situation where I'm trying to pass, by position, two strings immediately followed by a wildcard. I want the user to be able to search for Vendor names that start with either 'C%' or 'F%'. Here's the gist of the CREATE PROC statement:
CREATE PROC spBalanceRange
#VendorVar varchar(40) = '%',
#BalanceMin money = 1.0
...
Here's what I've tried so far, but doesn't work:
EXEC spBalanceRange '(C%|F%)', 200.00;
EXEC spBalanceRange 'C%|F%', 200.00;
Is there a way to check for 2 or more string values with a wildcard when passed by position? Thanks.
EDIT: According to your comments you are looking for the first letter of a vendor's name only.
In this special case I could suggest an easy, not well performing but really simple approach. CHARINDEX returns a number greater than zero, if a character appears within a string. So you just have to pass in all your lookup-first-characters as a simple "chain":
DECLARE #DummyVendors TABLE(VendorName VARCHAR(100));
INSERT INTO #DummyVendors VALUES
('Camel Industries')
,('Fritz and Fox')
,('some other');
DECLARE #ListOfFirstLetters VARCHAR(100)='CF';
SELECT VendorName
FROM #DummyVendors AS dv
WHERE CHARINDEX(LEFT(dv.VendorName,1),#ListOfFirstLetters)>0
This was the former answer
Checking against more than one value needs either a dedicated list of compares
WHERE val=#prm1 OR val=#prm2 OR ... (you know the count before)
...or you use the IN-clause
WHERE LEFT(VenoderName,1) IN ('C','F', ...)
...but you cannot pass the IN-list with a parameter like ... IN(#allValues)
You might think about a created TYPE to pass in all your values like a table and use an INNER JOIN as filter: https://stackoverflow.com/a/337864/5089204 (and a lot of other examples there...)
Or you might think of dynamic SQL: https://stackoverflow.com/a/5192765/5089204
And last but not least you might think of one of the many split string approaches. This is one of my own answers, section "dynamic IN-statement": https://stackoverflow.com/a/33658220/5089204
I'm answering my own question, and maybe other solutions exist but here is what had to happen with my stored procedure in order to pass variables by position:
CREATE PROC spBalanceRange
#VendorVar varchar(40) = '%',
#BalanceMin money = 1.0
AS
IF (#VendorVar = '%' AND #BalanceMin IS NULL OR #BalanceMin = '')
BEGIN
PRINT 'BalanceMin cannot be null.';
END
IF (#VendorVar = % AND #BalanceMin IS NOT NULL)
BEGIN
(sql statement using parameters)
END
EXEC spBalanceRange '[C,F]%', 200.00;
That's what I know.

SQL Server - Replacing Single Quotes and Using IN

I am passing a comma-delimited list of values into a stored procedure. I need to execute a query to see if the ID of an entity is in the comma-delimited list. Unfortunately, I think I do not understand something.
When I execute the following stored procedure:
exec dbo.myStoredProcedure #myFilter=N'1, 2, 3, 4'
I receive the following error:
"Conversion failed when converting the varchar value '1, 2, 3, 4' to data type int."
My stored procedure is fairly basic. It looks like this:
CREATE PROCEDURE [dbo].[myStoredProcedure]
#myFilter nvarchar(512) = NULL
AS
SET NOCOUNT ON
BEGIN
-- Remove the quote marks so the filter will work with the "IN" statement
SELECT #myFilter = REPLACE(#myFilter, '''', '')
-- Execute the query
SELECT
t.ID,
t.Name
FROM
MyTable t
WHERE
t.ID IN (#myFilter)
ORDER BY
t.Name
END
How do I use a parameter in a SQL statement as described above? Thank you!
You could make function that takes your parameter, slipts it and returns table with all the numbers in it.
If your are working with lists or arrays in SQL Server, I recommend that you read Erland Sommarskogs wonderful stuff:
Arrays and Lists in SQL Server 2005
You need to split the string and dump it into a temp table. Then you join against the temp table.
There are many examples of this, here is one at random.
http://blogs.microsoft.co.il/blogs/itai/archive/2009/02/01/t-sql-split-function.aspx
Absent a split function, something like this:
CREATE PROCEDURE [dbo].[myStoredProcedure]
#myFilter varchar(512) = NULL -- don't use NVARCHAR for a list of INTs
AS
SET NOCOUNT ON
BEGIN
SELECT
t.ID,
t.Name
FROM
MyTable t
WHERE
CHARINDEX(','+CONVERT(VARCHAR,t.ID)+',',#myFilter) > 0
ORDER BY
t.Name
END
Performance will be poor. A table scan every time. Better to use a split function. See: http://www.sommarskog.se/arrays-in-sql.html
I would create a function that takes your comma delimited string and splits it and returns a single column table variable with each value in its own row. Select that column from the returned table in your IN statement.
I found a cute way of doing this - but it smells a bit.
declare #delimitedlist varchar(8000)
set #delimitedlist = '|1|2|33|11|3134|'
select * from mytable where #delimitedlist like '%|' + cast(id as varchar) + '|%'
So... this will return all records with an id equal to 1, 2, 33, 11, or 3134.
EDIT:
I would also add that this is not vulnerable to SQL injection (whereas dynamic SQL relies on your whitelisting/blacklisting techniques to ensure it isn't vulnerable). It might have a performance hit on large sets of data, but it works and it's secure.
I have a couple of blog posts on this as well, with a lot of interesting followup comments and dialog:
More on splitting lists
Processing list of integers

T-SQL: Cannot pass concatenated string as argument to stored procedure

Scenario:
Need to pass n arguments to a stored procedure. One of the arguments is of type varchar(x). That varchar argument needs to be constructed from a handful of other varchar variables. This problem uses SQL Server 2005, but this behaviour applies to all versions of SQL Server.
Setup:
DECLARE #MyString varchar(500), #MyBar varchar(10), #MyFoo varchar(10)
SELECT #MyBar= 'baz '
SELECT #MyFoo= 'bat '
-- try calling this stored procedure!
EXEC DoSomeWork #MsgID, 'Hello ' + #MyBar + '" world! "' + #MyFoo + '".'
This produces the exception in SQL Server: Incorrect syntax near '+'. Typically you might think that the datatype would be wrong (i.e. the variables are of different types, but that would produce a different error message).
Here's a correct implementation that compiles without error:
SELECT #MyString= 'Hello ' + #MyBar + '" world! "' + #MyFoo + '".';
EXEC DoSomeWork #ID, #MyString
Question: Why is it that T-SQL can't handle the concatenation of a varchar as an argument? It knows the types, as they were declared properly as varchar.
The EXECUTE statement simply has a different grammar then other statements like SELECT and SET. For instance, observe the syntax section at the top of the following two pages.
EXECUTE statement: http://msdn.microsoft.com/en-us/library/ms188332.aspx
SET statement: http://msdn.microsoft.com/en-us/library/ms189484.aspx
The syntax for EXECUTE only accepts a value
[[#parameter =] {value | #variable
[OUTPUT] | [DEFAULT]]
Whereas the syntax for SET accepts an expression
{#local_variable = expression}
A value is basically just a hard coded constant, but an expression is going to be evaluated. It's like having the varchar 'SELECT 1 + 1'. It's just a varchar value right now. However, you can evaluate the string like this:
EXEC('SELECT 1 + 1')
I suppose all I'm pointing out is that the EXEC command doesn't allow expressions by definition, which you apparently found out already. I don't know what the intention of the developers of T-SQL where when they made it that way. I suppose the grammar would just get out of hand if you where allowed to throw subqueries within subqueries in the parameter list of a stored procedure.
T-SQL Expression: http://msdn.microsoft.com/en-us/library/ms190286.aspx
You cannot do something like this either
exec SomeProc getdate()
you have to put all that stuff in a param like you are doing at your bottom query
It might be because it is non deterministic (at least for functions)
It's a limitation on the EXEC statement. See The curse and blessings of dynamic SQL for more information.