T-SQL stored procedure: Select * From Where Value is not null - sql-server-2008

I have a table with several columns. I want a stored procedure that will filter a Select * statement for each of those values if the parameter for those values is not null. Would I have to write a like a sql server string or whatever, and if that parameter is not null, just append the parameter value to the string? Or is there a simpler built in mechanism for that sort of thing? I know you have to do the string thing for Oracle. But ms sql server has always struck me as more user friendly. I thought I would check first before I dove in.
Thanks

The easy route, assuming col is not nullable, or it is and you don't want NULL rows to match:
WHERE col LIKE COALESCE(#param, col)
-- or the longer version:
WHERE (col LIKE #param OR #param IS NULL)
(Where #param is either NULL or something like '%asdf%'.)
If col is nullable and you do want NULL rows to match, you could try this:
WHERE COALESCE(col, 'x') LIKE COALESCE(#param, col, 'x')
There are other ways to do it, as this could potentially lead to bad plans based on your parameterization settings and what parameters are used the first time it is cached (this can lead to poor plan choice due to "parameter sniffing"), but that is probably largely irrelevant here because your WHERE clause is going to force a table scan anyway.
A common alternative when plan quality becomes an issue is to use dynamic SQL, e.g.
DECLARE #sql NVARCHAR(MAX) = N'SELECT ... FROM ... WHERE 1 = 1';
IF #param IS NOT NULL
BEGIN
SET #sql += ' AND col LIKE ''' + REPLACE(#param, '''', '''''') + '%''';
END
It can be helpful in cases like this to make sure the optimize for ad hoc workloads setting is enabled.
For information on parameter sniffing and dynamic SQL, see these posts by Erland Sommarskog:
http://www.sommarskog.se/query-plan-mysteries.html
http://www.sommarskog.se/dynamic_sql.html

Related

Possible to use IF in a query?

I'm using Grafana to plot data from a MySQL datasource. Is it possible to, in a panel's query editor, use an IF ... THEN ... type statement. I would like to create a variable that I could put in the IF. I want the variable to be a condition, not necessarily to be used directly in the query.
For example:
//IN THE DATA SOURCE:
CREATE TABLE Example (Id INT, ANIMALS VARCHAR(15));
INSERT INTO Example VALUES (1,'Dog'), (2,'Fish'), (3,'Cat'), (4,'Lizard')
For a variable Test with values "Mammal',"Reptile", "Other":
//WHAT I'D LIKE IN GRAFANA QUERY EDITOR:
IF($Test = "Mammal") THEN
SELECT * FROM Example WHERE Id = 1 OR Id =3;
ELSE
SELECT * FROM Example WHERE Id = 2 OR Id =4;
END IF;
Is this kind of condition based query even possible? If so, what is the proper syntax to get it to work? Is there any way I can use Grafana variables to have a similar effect?
Use query. Query starts with SELECT keyword. Don't use any IF ELSE conditions before query, e.g.:
SELECT *
FROM Example
WHERE
Data IN ( ${variable:csv} )
This WHERE condition syntax will work with single value, multi value Grafana dashboard variables and also with All value (no custom All value, but blank=auto). Of course this condition is mainly for INT column types. STRING types may need different one (e.g. with LIKE and regexp matching).
Code all your logic (dependency on the dashboard variable) in the WHERE section. Use query inspector to see SQL which is generated and tweak it to correct SQL syntax.
Instead of an if, you can use or. It's really useful for conditionally checking variables:
select * from Example
where (Id in (1,3) or '$Test' != 'Mammal')
and (Id in (2,4) or '$Test' == 'Mammal')

Assigning multi value parameter (values) to a local parameter in sql query

I have a situation where I need to store the multi value parameter to a local parameter in sql query.
In the SSRS report builder I have created two multi value parameters #Customer and #LogisticsGroup
And in my SQL query , I have to assign those values to local parameters something like this
DECLARE #Acct NVARCHAR(100) , #LgstGroup NVARCHAR(MAX)
SELECT #Acct = (#Customer)
,#LgstGroup = (#LogisticsGroup)
But with this kind of approach I'm able to select only one value , if I select two values then the query is failing.
I tried this , but it seems like incorrect syntax.
DECLARE #Acct NVARCHAR(100) , #LgstGroup NVARCHAR(MAX)
SELECT #Acct IN (#Customer)
,#LgstGroup IN (#LogisticsGroup)
Please help to resolve this issue. Thanks much
You can store delimited values in a parameter. So for example, #Customer might have a string of ID's in it like 1,2,3. The report can concatenate the multiple values into this format using the Join function.
Now, in the SQL, one option is to use the LIKE operator to search the string. You could write something like:
#Customer like '%,' + Column_Name + ',%'
However, this approach is inefficient and you have to be careful of partial value matches.
A better approach is to create a user-defined table-valued function that can split the values and treat them like a table. There are plenty of examples out there, it's a pretty simple function. Then in practice it would look like this:
WHERE Column_Name in (select * from Split(#Customer))
OR
INNER JOIN Split(#Customer) ON...

Is there a way to set multiple values in one variable in SQL Server 2008?

I am a newbie in coding.
Here is what I did:
DECALRE #v VARCHAR(100)
SET #v = (SELECT TOP 100 NAMES FROM TestTable WITH(NOLOCK))
SELECT #v AS SampleData
But it returned an error:
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
Are there any way to set multiple values in one variable in SQL Server 2008?
Thanks in advance.
There is one type of variable designed for holding multiple values. It's called a table variable:
declare #v table (Name varchar(100) not null)
insert into #v(Name )
select top 100 name from TestTable /* no ORDER BY means this is ill-defined */
You can insert/update/delete in this table variable and query from it via selects in exactly the same way as any other table.
Note though that this looks like you're breaking things down into "procedural" steps - first I'll get the top 100 names, then I'll do X, then I'll do Y. In SQL, you should try to put as much as possible into single queries, and leave it to the optimizer to determine what order to do things in, which subresults should be stored, etc.

Is OK to enclosing all values in SQL statement with single quotes?

Is it ok to enclose all values in SQL statement with single quotes ? For example:
This is simple table called books:
id | title
1 | Some book name
2 | Second book name
Is it OK to write statement like this:
SELECT * FROM books WHERE id = '1'
I've tested that query in SQL server 2008 and MySQL 5 and it works good, but I am curious is there any performance issue, because ID field is acctualy integer.
And second question is it OK to write statement like this:
SELECT * FROM books WHERE id = N'1'
N prefix is used in SQL server for UTF-8 fields, but I've tested that in SQL server and MySQL and both worked OK. I don't know if SQLite support N prefix, because I didn't test that.
The reason why I am asking this is because I am building database class that will work with popular SQL databases (SQL Server, MySQL, SQLite and maybe MS Access), so when performing selecting, inserting or updating data I don't have to worry about field datatype. I can always enclose value with N'Some value', but I am curious is this correct and is there any performance issues?
In SQL Server, you can get an implicit conversion when doing this. Sometimes it won't affect the plan or have noticeable impacts on performance, but sometimes it might. They are generally deemed to be bad. Inspect the plans for these two queries against AdventureWorks2012:
SELECT * FROM Sales.SalesOrderHeader WHERE SalesOrderID = 43659;
SELECT * FROM Sales.SalesOrderHeader WHERE SalesOrderID = '43659';
The latter contains a CONVERT_IMPLICIT (hover over the tooltip for the CI seek).
You also want to be very careful about switching between VARCHAR and NVARCHAR - in some cases this can be dreadful for performance, depending on the underlying data type and whether the literal has the N prefix.
Long story short: don't do this. Write your ORM thingy so that it understands the different data types and handles them appropriately.
SELECT ... WHERE int_type = '123' is fine to do. SQL will convert '123' to an integer once and be done with it.
However, SELECT ... WHERE char_type = 123 is no okay, because SQL will have to convert every cell into an integer. See that char_type = '0123' and char_type = '00123' will also match. So it has to do more work.
Thanks to #MartinSmith for pointing out a resource for precedence in casting: http://msdn.microsoft.com/en-us/library/ms190309.aspx
Here's an example bug in MySQL that says the implicit typecast from quoted string of digits to an integer causes a severe performance issue: http://bugs.mysql.com/bug.php?id=43319
It's best to not quote numbers.
SQL Server and MySQL both perform implicit type conversions, but it doesn't make it a good practice; use the appropriate native type when you can. This will let you avoid trying to spot the difference between:
SELECT * FROM books WHERE id = '1'
and
SELECT * FROM books WHERE id = 'l'
and
SELECT * FROM books WHERE id = 'O'
and
SELECT * FROM books WHERE id = '0'
(that's a one, a lower case L, a capital 'o', and a zero respectively)

SQL 2008: Prevent Full-text lookup in query when not needed

From working on this specific situation, it was news to me that the logic operators are not short circuited in SQL.
I routinely do something along these lines in the where clause (usually when dealing with search queries):
WHERE
(#Description IS NULL OR #Description = myTable.Description)
Which, even if it's not short-circuited in this example, doesn't really matter. However, when dealing with the fulltext search functions, it does matter.. If the second part of that query was CONTAINS(myTable.Description, #Description), it wouldn't work because the variable is not allowed to be null or empty for these functions.
I found out the WHEN statements of CASE are executed in order, so I can change my query like so to ensure the fulltext lookup is only called when needed, along with changing the variable from null to '""' when it is null to allow the query to execute:
WHERE
(CASE WHEN #Description = '""' THEN 1 WHEN CONTAINS(myTable.Description, #Description) THEN 1 ELSE 0 END = 1)
The above code should prevent the full-text query piece from executing unless there is actually a value to search with.
My question is, if I run this query where #Description is '""', there is still quite a bit of time in the execution plan spent dealing with clustered index seeks and fulltextmatch, even though that table and search does not end up being used at all: is there any way to avoid this?
I'm trying to get this out of a hardcoded dynamic query and into a stored procedure, but if the procedure ends up being slower, I'm not sure I can justify it.
It's not ideal, but maybe something like this would work:
IF #Description = ''
BEGIN
SELECT ...
END
ELSE
BEGIN
SELECT ...
WHERE CONTAINS(mytable.description, #Description)
END
That way you avoid mysql and also running the FT scan when it's not needed.
As a few general notes, I usually find CONTAINSTABLE to be a bit faster. Also, since the query plan is going to be very different whether you're using my solution or yours, watch out for parameter sniffing. Parameter sniffing is when the optimizer builds a plan based on a passed in specific parameter value.
In case anyone else runs into a scenario like this, this is what I ended up doing, which is pretty close to what M_M was getting at; I broke away the full-text pieces and placed them behind branches:
DECLARE #TableBfullSearch TABLE (TableAId int)
IF(#TableBSearchInfo IS NOT NULL)
INSERT INTO #TableBfullSearch
SELECT
TableAId
FROM
TableB
WHERE
...(fulltext search)...
DECLARE #TableCfullSearch TABLE (TableAId int)
IF(#TableCSearchInfo IS NOT NULL)
INSERT INTO #TableCfullSearch
SELECT
TableAId
FROM
TableC
WHERE
...(fulltext search)...
--main query with this addition in the where clause
SELECT
...
FROM
TableA
WHERE
...
AND (#TableBSearchInfo IS NULL OR TableAId IN (SELECT TableAId FROM #TableBfullSearch))
AND (#TableCSearchInfo IS NULL OR TableAId IN (SELECT TableAId FROM #TableCfullSearch))
I think that's probably about as good as it'll get without some sort of dynamic query