How Can I optimize this query (OR inside AND validation)? - sql

I'm having problems with this query:
SELECT col1, col2,col3,...,coln FROM MyTable
WHERE col1 = #value1
AND
(
ISNULL(col2,'c') = ISNULL(#value2,ISNULL(col2,'c'))
OR
ISNULL(col3,'c') = ISNULL(#value2,ISNULL(col3,'c'))
)
AND coln = 'valueN'
I have to stop the execution, it's so slow. But editing:
SELECT col1, col2,col3,...,coln FROM MyTable
WHERE col1 = #value1
AND
(
ISNULL(col2,'c') = ISNULL(#value2,ISNULL(col2,'c'))
)
AND coln = 'valueN'
This query is faster. Can someone help me? How can I replace the or statement or replace the query but validating col1 and col2?.
Thanks.
UPDATE:
Thank you very much guys. Really my query does not use '=' but use 'Like', sorry about that. However I use your suggestions to build my query and it works fine:
SELECT col1, col2,col3,...,coln FROM MyTable
WHERE col1 like '%' + #value1 + '%'
AND
(
(#value2 IS NULL)
OR
(col2 IS NOT NULL AND col2 LIKE '%' + #value2 + '%')
OR
(col3 IS NOT NULL AND col3 LIKE '%' + #value2 + '%')
)
AND coln = 'valueN'
I use this query with a page where I have many fields to filter a search and I need that a col2 textbox apply to col3 in database too, I want to mean, only one textbox for name1 and name2 in database.
Sorry about my wrong question and thanks for your suggestions.

ISNULL(col2,'c') = ISNULL('value2',ISNULL(col2,'c'))
is the same as
col2 = 'value2' or (col2 is null and 'value2' is null)
Replace the occurrences and you will most likely have a better performance.
Update
There's one fundamental difference between this solution and the one proposed by #onedaywhen: when the value provided in 'value2' (which I suppose is just a parameter assembled into a SQL string) is NULL, OP only wants to bring back only records where col2 is NULL. Take a closer look into OP's logic, you will see that there. OP's logic always filters: when the parameter is NULL, OP wants records where col2 is NULL.
#onedaywhen's solution brings every record when the parameter is NULL. Although this is a very common query, it's not what OP is looking for.

ISNULL(col2,'c') = ISNULL(#value2,ISNULL(col2,'c'))
is the same as
( ( col2 = #value2 ) OR ( #value2 IS NULL ) )
I'm not sure if this will improve performance, though: I've read that, at least for SQL Server 2005 and earlier, to get an efficient, scalable and performant solution either use IF ELSE control of flow blocks (one per parameter combination) or use dynamic SQL.
UPDATE: ...and here's the proof:
Query 1: When the parameter is a value that is not null:
DECLARE #value2 VARCHAR(10);
SET #value2 = 'Apples';
WITH T
AS
(
SELECT *
FROM (
VALUES (1, 'When col2 is null', NULL),
(2, 'When col2 is the same value as #value2', 'Apples'),
(3, 'When col2 is not the same value as #value2', 'Oranges')
) AS T (ID, narrative, col2)
)
SELECT *,
CASE WHEN ISNULL(col2,'c') = ISNULL(#value2,ISNULL(col2,'c')) THEN 'T' END AS OP,
CASE WHEN ( ( col2 = #value2 ) OR ( #value2 IS NULL ) ) THEN 'T' END AS OneDayWhen,
CASE WHEN col2 = #value2 or (col2 is null and #value2 is null) THEN 'T' END AS Adrian
FROM T;
Output 1:
ID narrative col2 OP OneDayWhen Adrian
----------- ------------------------------------------ ------- ---- ---------- ------
1 When col2 is null NULL NULL NULL NULL
2 When col2 is the same value as #value2 Apples T T T
3 When col2 is not the same value as #value2 Oranges NULL NULL NULL
Note all agree for all rows :)
Query 2: When the parameter is a null:
DECLARE #value2 VARCHAR(10);
SET #value2 = NULL;
WITH T
AS
(
SELECT *
FROM (
VALUES (1, 'When col2 is null', NULL),
(2, 'When col2 is the same value as #value2', 'Apples'),
(3, 'When col2 is not the same value as #value2', 'Oranges')
) AS T (ID, narrative, col2)
)
SELECT *,
CASE WHEN ISNULL(col2,'c') = ISNULL(#value2,ISNULL(col2,'c')) THEN 'T' END AS OP,
CASE WHEN ( ( col2 = #value2 ) OR ( #value2 IS NULL ) ) THEN 'T' END AS OneDayWhen,
CASE WHEN col2 = #value2 or (col2 is null and #value2 is null) THEN 'T' END AS Adrian
FROM T;
Output 2:
ID narrative col2 OP OneDayWhen Adrian
----------- ------------------------------------------ ------- ---- ---------- ------
1 When col2 is null NULL T T T
2 When col2 is the same value as #value2 Apples T T NULL
3 When col2 is not the same value as #value2 Oranges T T NULL
Note OP and OneDayWhen match for all rows, Adrian only matches for row ID = 1.

Related

SQL statement with CASE WHEN in ORDER BY causes type convert error

Executing the following code triggers an error:
System.Data.SqlClient.SqlException: 'Cannot convert a char value to money. The char value has incorrect syntax.
All was fine until I have added the second parameter used for sorting purposes.
The code is simplification for clarity.
query as String = "
SELECT a, b, c
FROM DataTable
WHERE c = #PARAM
ORDER BY
CASE #SORTCOLUMN
WHEN 1 THEN a
WHEN 2 THEN b
WHEN 3 THEN c
END"
Dim param As String = "myprarameter"
Dim sortcolumn as Integer = 1
result = connection.Query(Of MyType)(query, New With {Key .PARAM = param, Key .SORTCOLUMN = sortcolumn})
UPDATE:
After hours spent on testing I have narrowed it down to purely SQL issue, not Dapper nor .NET Framework.
Here are my findings:
All works fine until all columns used within CASE WHEN are of the same type (or types and even values which can be easily converted into each other). Once types of columns are different and values cannot be converted, it returns type conversion error. Seems it is trying to convert type of the column selected by CASE WHEN to the type of the first WHENcolumn.
Herewith two examples. I have removed variables for simplicity.
This works:
CREATE TABLE #TestTable1
(col1 money, col2 money)
INSERT INTO #TestTable1
(col1, col2)
VALUES
(1, 30),
(2, 20),
(3, 10)
SELECT col1, col2
FROM #TestTable1
ORDER BY
CASE 2
WHEN 1 THEN col1
WHEN 2 THEN col2
END
DROP TABLE #TestTable1
But this does NOT work. Returns:
Cannot convert a char value to money. The char value has incorrect syntax.
CREATE TABLE #TestTable2
(col1 money, col2 varchar(20))
INSERT INTO #TestTable2
(col1, col2)
VALUES
(1, 'cc'),
(2, 'bb'),
(3, 'aa')
SELECT col1, col2
FROM #TestTable2
ORDER BY
CASE 2
WHEN 1 THEN col1
WHEN 2 THEN col2
END
DROP TABLE #TestTable2
I am using Azure SQL with compatibility level 150.
I have updated Title and Tags accordingly.
UPDATE 2:
I am trying to add a complication to #forpas solution in form of a second parameter which will tell the order. I have used CASE within CASE but this returns a number of syntax errors.
The ORDER part only. The rest has not changed.
ORDER BY
CASE #SORTORDER
WHEN 'a' THEN
(CASE #SORTCOLUMN
WHEN 1 THEN col1
WHEN 2 THEN col2
END,
CASE #SORTCOLUMN
WHEN 3 THEN col3
WHEN 4 THEN col4
END) ASC
WHEN 'd' THEN
(CASE #SORTCOLUMN
WHEN 1 THEN col1
WHEN 2 THEN col2
END,
CASE #SORTCOLUMN
WHEN 3 THEN col3
WHEN 4 THEN col4
END) DESC
END
One solution is to split the CASE statement to as many CASE statements needed so each one contains columns with the same or similar convertable data types:
CREATE TABLE #TestTable2
(col1 money, col2 decimal(18, 2), col3 varchar(20))
INSERT INTO #TestTable2
(col1, col2, col3)
VALUES
(1, 5.5, 'cc'),
(2, 1.8, 'bb'),
(3, 3.3, 'aa');
DECLARE #SORTCOLUMN INT = 1; -- 1 or 2 or 3
SELECT col1, col2, col3
FROM #TestTable2
ORDER BY
CASE #SORTCOLUMN
WHEN 1 THEN col1
WHEN 2 THEN col2
END,
CASE #SORTCOLUMN
WHEN 3 THEN col3
END
DROP TABLE #TestTable2
See the demo.
Simply create the SQL as nvarchar and execute it as follows:
DECLARE #SQL NVARCHAR(MAX) = N'SELECT a, b, c FROM DataTable
WHERE c = #PARAM
ORDER BY ' + (CASE #SORTCOLUMN WHEN 1 THEN 'a' WHEN 2 THEN 'b' WHEN 3 THEN 'c' ELSE 'a' END);
EXEC sp_Executesql #SQL;
in VB
query as String = "DECLARE #SQL NVARCHAR(MAX) = N'SELECT a, b, c FROM DataTable
WHERE c = #PARAM
ORDER BY ' + (CASE #SORTCOLUMN WHEN 1 THEN 'a' WHEN 2 THEN 'b' WHEN 3 THEN 'c' ELSE 'a' END);
EXEC sp_Executesql #SQL;"
try this.
query as String = "
SELECT a, b, c
FROM DataTable
WHERE c = #PARAM
ORDER BY
CASE #SORTCOLUMN
WHEN '1' THEN a
WHEN '2' THEN b
WHEN '3' THEN c
END"

Select where in list or else select all most elegantly

For a select statement I set a part of the where condition externally in a configuration file as parameter, like this (COL2 is filled with string values):
SELECT COL1
,COL2
FROM TABLE1
WHERE COL2 = $external_parameter
Now I have a more complex case, where I need the external parameter to be a list. Whenever a value of COL2 is in the list, it should select, however if no list is provided or some kind of "empty" tag, every value should be selected again. I came up with an idea, but it does not work that way:
SELECT COL1
,COL2
FROM TABLE1
WHERE $external_list = ('') or COL2 IN $external_list
Like this, the statement would be true for all elements of COL2 when an empty list is provided, and true for every element in the list that matches with the list if a filled list is provided.
However, if I provide a filled list it will not work on the comparison in the first half of the where statement:
('entry_a', 'entry_b') = ('')
Is there any query that would make it work - best in a one line where statement?
Try something like this,
SELECT COL1
,COL2
FROM TABLE1
WHERE COL2 IN ($EXTERNAL_PARAMETER)
OR 1 = (
CASE
WHEN $EXTERNAL_PARAMETER = ''
THEN 1
ELSE 0
END
)
Try this way
SELECT COL1
,COL2
FROM TABLE1
WHERE COL2 IN (
CASE
WHEN $EXTERNAL_PARAMETER = ''
THEN COL2
ELSE $EXTERNAL_PARAMETER
END)
Try this
IF $EXTERNAL_PARAMETER = ''
SET $EXTERNAL_PARAMETER = NULL
SELECT COL1
,COL2
FROM TABLE1
WHERE COL2 = ISNULL($EXTERNAL_PARAMETER, COL2 )

Apply conditions in projection level

Is there a simple way to apply conditions in projection level? I mean In a series of fields(columns) how can we choose only the fields those have specific conditions?
Example:
Assume we have a query with below result:
Id Col1 Col2 Col3 Col4 Col5 Col6 Col7 Col8 Col9 Col10 Col11
- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
1 V1_1 NULL NULL V4_1 NULL NULL NULL NULL NULL V10_1 NULL
2 V1_2 NULL NULL NULL NULL NULL NULL NULL NULL V10_2 NULL
3 V1_3 NULL NULL V4_3 NULL NULL NULL NULL NULL V10_3 NULL
how can we select only the fields which contains none null values while we don't know which of them contains none null values? so result would be this:
Id Col1 Col4 Col10
- ---- ---- ----
1 V1_1 V4_1 V10_1
2 V1_2 NULL V10_2
3 V1_3 V4_3 V10_3
Usually I do such these queries with hardcoding which I may query sys.all_obejcts, sys.all_columns in MsSQL Server and all_tab_cols, all_tables , etc in Oracle. and use none, one or more cursors , case statements, pivot, multiple ifs or dynamic sql in it.
in example: for a table with above data in T-SQL I can do:
DECLARE #sql VARCHAR(500)
SET #sql='SELECT Id,'
IF EXISTS (SELECT Col1 FROM tblTEST WHERE Col1 IS NOT NULL) SET #sql=#sql+'Col1,'
/* 9 other IF statements here */
IF EXISTS (SELECT Col11 FROM tblTEST WHERE Col11 IS NOT NULL) SET #sql=#sql+'Col11,'
SET #sql=SUBSTRING(#sql,1,LEN(#sql)-1)+' FROM tblTEST'
EXEC(#sql)
but as you see the idea is not common at all and it's a little hard to do such coding for such these scenarios
Do you know a simple and better way or a pattern for such this queries?
The RDBMS does not matter, a good solution for any RDBMS is really useful.
Please I want answers to be a good idea in general not only for the example I mentioned.
This is a T-SQL solution. All you should have to do is change #TargetTable to your desired table. Hope this helps! I commented my code explaining everything, but if you have any questions, let me know!
IF OBJECT_ID('myTable') IS NOT NULL
DROP TABLE dbo.myTable;
CREATE TABLE myTable
(
ID INT IDENTITY(1,1),
Col1 VARCHAR(25),
Col2 VARCHAR(25),
Col3 VARCHAR(25)
)
INSERT INTO dbo.myTable(Col1,Col2,Col3)
VALUES ('v1_1',NULL,NULL),
('v1_2','v2_2',NULL),
('v1_3',NULL,NULL);
/* Now that I've set up the problem, here's for the actual code*/
--Set your desired table
DECLARE #TargetTable VARCHAR(100) = 'myTable'
--If this global temp table exists, drop it
IF OBJECT_ID('##tempTable') IS NOT NULL
EXEC('DROP TABLE ##tempTable');
--This is going to take one character and cast it to NCHAR(1)
--If there are no characters, it will simply return NULL
DECLARE #AllColumns VARCHAR(MAX);
SELECT #AllColumns = COALESCE(#AllColumns + ',','') + 'CAST(LEFT(' + QUOTENAME(COLUMN_NAME) + ',1) AS NCHAR(1)) AS ' + QUOTENAME(COLUMN_NAME)
FROM INFORMATION_SCHEMA.COLUMNS A
WHERE TABLE_NAME = #TargetTable
/*Load your char results into a global temp table*/
EXEC('SELECT ' + #AllColumns + ' INTO ##tempTable FROM ' + #TargetTable);
/*Here's where the fun beings*/
/* #DesiredCol holds the name of columns that are not null.
UNPIVOT the data.
Where clause gets rid of columns with only NULL values.
Group by only allows distinct columns.
You the select from your ORIGINAL table, not my funky nchar(1) global temp table.
*/
EXEC(
'DECLARE #DesiredCol VARCHAR(MAX);
SELECT #DesiredCol = COALESCE(#DesiredCol + '','','''') + QUOTENAME(col)
FROM ##temptable
UNPIVOT
(
val FOR col IN ([ID],[Col1],[Col2],[Col3])
) pvt
WHERE pvt.val IS NOT NULL
GROUP BY col
EXEC (''SELECT '' + #DesiredCol + '' FROM ##' + #TargetTable + ''')'
)
--Cleanup
IF OBJECT_ID('##tempTable') IS NOT NULL
EXEC('DROP TABLE ##tempTable');

Stored procedure setting a value if row is empty

How would I go about setting a value if a row is empty ('')?
I was thinking something like,
Got var with default value called #defaultValue to set it where the row in a table is ''.
if (select col1 from table1 where col1 = '')
set (select col1 from table1 where col1 = '') = #DefaultValue
is there a better way?
code is just a draft its not even tested..
If you want to update the table with #DefaultValue, you can use WHERE clause in the UPDATE query:
UPDATE table1
SET col1=#DefaultValue
WHERE col1=''
OR col1 IS NULL
OR
If you are trying to select #DefaultValue if the column is empty or null, you can do this:
SELECT CASE WHEN (col1 IS NULL OR col1='')
THEN #DefaultValue
ELSE col1
END AS Col1
FROM table1
select case when col1 ='' then #DefaultValues else col1 end from table
DEMO
declare #default int
set #default=1
declare #tbl table(col1 int)
insert into #tbl values(1),(''),(2)
select case when col1='' or col1 is null then #default else col1 end from #tbl

SQL If parameter is null search all

I have a JSF page where users can search by five options, and can choose how many of those options they search by (0-5). The database has no NULL values.
Currently I have a horrendous piece of code that builds that SQL statement, but I would like to just have one parameterized statement.
SELECT *
FROM table1
WHERE col1 = 'dave'
AND col2 = 'Smith'
AND col3 = '3/2/2014'
AND col4 = '12345'
AND col5 = '67890'
ORDER BY col5 DESC
Is there a way to detect an empty/null value in the search criteria e.g col5=''
And just return all results for that column?
So that if the search criteria was just col1='dave' then the statment would act as if the input was
SELECT *
FROM table1
WHERE col1 = 'dave'
ORDER BY col5 DESC
You can do this in SQL:
SELECT *
FROM table1
WHERE (col1 = #col1 or #col1 is null) and
(col2 = #col2 or #col2 is null) and
. . .
ORDER BY col5 DESC;
But, you may not want to. If you are using indexes to speed your searches, then the use of or can impede the use of the indexes in many databases (but apparently not in Oracle). This is also true of coalesce(), which could be used as:
WHERE col1 = coalesce(#col1, col1) and
col2 = coalesce(#col2, col2) and
. . .
If you are constructing the query anyway, then do it in the statement. The logic is like:
if #col1 is not null then
#where = concat(#where, " and col1 = '", #col1, "'")
end if;
if #col2 is not null then
#where = concat(#where, " and col2 = '", #col2, "'")
end if;
This will construct the correct where clause that can use available indexes.
There is one downside to this approach. You cannot pre-compile the statement for all parameter values. The statement would need to be recreated every time new values are input (this would normally be an acceptable amount of overhead because you are dealing with user input).
Try to use trim to change empty value '' to null and then use coalesce to replace null with value from col5 as below
SELECT *
FROM table1
WHERE col1 = 'dave'
AND col2 = 'Smith'
AND col3 = '3/2/2014'
AND col4 = '12345'
AND col5 = coalesce(trim('67890'),col5)
ORDER BY col5 DESC
SELECT *
FROM table1
WHERE (col1 = :col1 OR :col1 IS NULL)
AND (col2 = :col2 OR :col2 IS NULL)
AND (col3 = :col3 OR :col3 IS NULL)
AND (col4 = :col4 OR :col4 IS NULL)
AND (col5 = :col5 OR :col5 IS NULL)
ORDER BY col5 DESC
It is more convenient to use named parameter instead of "?" in JAVA