As part of some dynamic SQL (ick), I've implemented the 'sort NULLs last' solution described here: Sorting null-data last in database query
ORDER BY CASE column WHEN NULL THEN 1 ELSE 0 END, column
My question is: On non-nullable columns that have ISNULL() applied to them, will the query optimizer strip this out when it realises that it will never apply?
It's not clear why your question mentions the ISNULL function when that isn't in your code.
ORDER BY CASE column WHEN NULL THEN 1 ELSE 0 END, column
First of all this code doesn't work, it is equivalent to CASE WHEN column = NULL which is not what you need.
It would need to be
ORDER BY CASE WHEN column IS NULL THEN 1 ELSE 0 END, column
The optimisation question is easy to test.
CREATE TABLE #T
(
X INT NOT NULL PRIMARY KEY
)
SELECT *
FROM #T
ORDER BY X
SELECT *
FROM #T
ORDER BY CASE WHEN X IS NULL THEN 1 ELSE 0 END, X
DROP TABLE #T
The plan shows a sort operation in the second plan indicating that this was not optimised out as you hoped and the pattern is less efficient than ORDER BY X.
Related
I am trying to do an order by with several different columns. The first column has several conditions.
The base order was something like
SELECT *
FROM Table T
ORDER BY T.A, T.B, T.C
Most of the time column A is an int. Sometimes A is an int with a letter appended at the end. I want the order by to be my the number portion. I was able to achieve that by modifying the query to the following which has been working for months now.
SELECT *
FROM Table T
ORDER BY
CASE WHEN ISNUMERIC(a.[HUDLine]) = 0 THEN CAST(SUBSTRING(T.A,1, PATINDEX('%[^0-9]%',T.A - 1) AS INT)
ELSE CAST (T.A AS INT) end
, T.B, T.C
Recently a new requirement came up which allows the value of "OFFLINE" to exist in column A.
I want to modify the ORDER BY to keep the same logic as before with the exception of all records with "OFFLINE" are at the end.
First, I am very sorry about your requirements, it makes absolutely no sense that bizzarely mixed values are being stored in a varchar column.
Second try this
CASE WHEN a.[HUDLine] = 'OFFLINE'
THEN 2147483647 --This is the maximum signed int value
ELSE
(CASE WHEN ISNUMERIC(a.[HUDLine]) = 0
THEN CAST(SUBSTRING(T.A,1, PATINDEX('%[^0-9]%',T.A - 1) AS INT)
ELSE CAST (T.A AS INT)
END)
END
Show that to whoever is in charge of your datamodel, and politely ask them store meanigful numeric data in a numeric column.
Why does the following simple query return null when there are no matching rows (<Condition> is not met by any row)?
SELECT ISNULL(MyField, 0) FROM [MyTable] WHERE <Condition>
I have tried COALESCE() as well, with similar results. How can I return zero when there are no matching rows?
This will work, provided you expect condition to reduce the result set to either 0 or 1 row:
SELECT ISNULL((SELECT MyField FROM [MyTable] WHERE <Condition>),0)
That is, create an outer query with no FROM clause (which will therefore always generate exactly one row) and then use a subquery to obtain your 0 or 1 row of actual data.
Use this:
SELECT ISNULL(COUNT(MyField), 0) FROM [MyTable] WHERE <Condition>
It'll return 0 if row is missing.
You cannot convert 0 rows to 1 row with a null value with any sql built-in fuction because that may cause mis-interpretation of data
But you can customize your result using the below logic (same as you do in .net).
If (select COUNT(MyField) FROM [MyTable] WHERE <Condition>)=0
select 0 as [MyField]
else
SELECT ISNULL(MyField, 0) as [MyField] FROM [MyTable] WHERE <Condition>
I'm using a table a table called T and have a column called C_I (Index) and C_D (Data).
Now I would like to only have the row in the index if C_D = null.
CREATE INDEX T_INDEX ON T(C_I) STORAGE(BUFFER_POOL KEEP);
How do I get some WHERE C_D IS NULL clause into this create statement?
Let me first make sure I understand the question correctly:
You want to speed-up SELECT .. WHERE C_D IS NULL but you do not want to speed-up any of the queries that search for a non-NULL C_D.
You also want to make sure no "unnecessary" non-NULL values are in the index, to save space.
If that understanding is correct, then what you need is a functional index. I'e. an index on a function on a field, not a field itself...
CREATE INDEX T_IE1 ON T (CASE WHEN C_D IS NULL THEN 1 ELSE NULL END) COMPRESS
...which you would then query as...
SELECT * FROM T WHERE (CASE WHEN C_D IS NULL THEN 1 ELSE NULL END) = 1
...which is equivalent to...
SELECT * FROM T WHERE C_D IS NULL
...but faster since it uses the index:
This saves space because single-column indexes do not store NULLs. Also, use COMPRESS since index will ever only contain one key so there is no need to waste space on repeating the same key over and over again in the index structure.
NOTE: Under Oracle 11, you could also create a function-based virtual column (based on the CASE expression above), then index and query on that column directly, to save some repetitive typing.
--- EDIT ---
If you are also interested in querying on C_I together with C_D IS NULL, you could...
CREATE UNIQUE INDEX T_IE2 ON T (C_I, CASE WHEN C_D IS NULL THEN 1 ELSE NULL END)
...and query it with (for example)...
SELECT * FROM T WHERE C_I > 'some value' AND (CASE WHEN C_D IS NULL THEN 1 ELSE NULL END) = 1
...which is equivalent of...
SELECT * FROM T WHERE C_I > 'some value' AND C_D IS NULL
...but faster, since it uses the index T_IE2.
This is in fact the only index that you need on your table (it "covers" the primary key, so you no longer need a separate index just on C_I). Which also means a same ROWIDs is never stored in more than one index, which saves space.
NOTE: COMPRESS no longer makes sense for index T_IE2.
--- EDIT 2 ---
If you care about simplicity more than space, you can just create a composite index on {C_I, C_D}. Oracle stores NULL values in composite index as long as there is at least one non-NULL value in the same tuple:
CREATE UNIQUE INDEX T_IE3 ON T (C_I, C_D)
This uses the index:
SELECT * FROM T WHERE C_I > 1 AND C_D IS NULL
As in previous EDIT, this is the only index that you need on your table.
CREATE INDEX T_INDEX ON T ( CASE WHEN CD IS NULL THEN C_I ELSE NULL END);
This works because Oracle will not put the null values returned by the CASE statement into the index.
Let me "repackage" my original answer.
Create the table like this:
CREATE TABLE T ...;
CREATE INDEX T_PK_IDX ON T (C_I, CASE WHEN C_D IS NULL THEN 1 ELSE NULL END);
ALTER TABLE T ADD CONSTRAINT T_PK PRIMARY KEY (C_I) USING INDEX T_PK_IDX;
And query like this:
SELECT * FROM T
WHERE
C_I > 'some value'
AND (CASE WHEN C_D IS NULL THEN 1 ELSE NULL END) = 1
Query plan:
My question is similar to this one: How to display a table order by code (like 01, 02… then null columns)?, but for SQL Server.
In short, I have a SELECT statement, that returns the following:
ColumnA ColumnB
X NULL
Y 1
Z 2
..where the ordering is done by ColumnB.
How can we force the (columnB = NULL) type of rows to the bottom? ie, the expected result is this:
ColumnA ColumnB
Y 1
Z 2
X NULL
Thank you SOF community.
...or in order to avoid value clashing...
SELECT
ColumnA,
ColumnB
FROM YourTable
ORDER BY
CASE WHEN ColumnB IS NULL THEN 1 ELSE 0 END ASC,
ColumnB
You can also use isnull:
select * from thetable order by isnull(columnb, 99999)
isnull will replace null with the value you provide to it, so in this case, if the column is null, it will replace it with 99999. You can set the value to some big number so it will be at the bottom of the order.
hoping to help someone,
I just wanted to add that I have had a similiar issue, using row_number and partition by -
when it is zero put it at the end sort of thing
and I used the script below (partial view):
,T.MONTHS_TO_AUTOGROWTH
,the_closest_event=ROW_NUMBER() OVER (PARTITION BY SERVERID, DRIVE ORDER BY
CASE WHEN MONTHS_TO_AUTOGROWTH > 0 THEN MONTHS_TO_AUTOGROWTH ELSE 9999
END )
the result is ordered by MONTHS_TO_AUTOGROWTH but zero comes last
Is there a possibility to order the result by an ORDER clause that contains an expression, something like
SELECT colX0 FROM tbp_name ORDER BY (colX1 IS NOT NULL)
or also a more complex expression ?
UPDATE:
In the meanwhile I have found a possibility to solve the above problem:
ORDER BY (case WHEN colX1 IS NULL THEN 1 ELSE 0 END ) ASC
however the question remains, if there is a possibility to order direct by an expression.
No, SQL Server does not support direct conversion of an expression to true/false.
IMHO, one reason is the 3-valued logic. This has 3 outcomes, not 2, of either column is NULL. The NULL is first in SQL generally, always Server but can be specified last in other RDBMS.
ORDER BY (colX1 = colX2)
Using CASE mitigates this and removes ambiguity
ORDER BY
CASE
WHEN colX1 = colX2 THEN 1
WHEN colX1 <> colX2 THEN 2
ELSE 3 NULL case
END
You have to use CASE as per your update, as well ensuring datatypes match (or at least implicitly convertable) in WHEN clauses.
you can use
ORDER BY CASE WHEN condition= 1 THEN 1 ELSE 2 END
you can order by the ordinal position of the column, if you want to SEE the data that you're sorting by... for example, if you want to order by the 1st column, just say 'ORDER BY 1'. Here is an example where I add an expression in the select clause.. and then I order by it in the order by clause
SELECT colX0,
(case WHEN colX1 IS NULL THEN 1 ELSE 0 END )
FROM tbp_name
ORDER BY 2
You'd need to put it in your select first
SELECT
colX0,
CASE WHEN colX1 IS NOT NULL THEN 0 ELSE 1 END AS [COMPUTED1]
FROM tbp_name
ORDER BY COMPUTED1
It's something like that anyway off the top of my head.
http://www.tizag.com/sqlTutorial/sqlcase.php