Error in ORDER BY clause using CASE WHEN - sql-server-2016

I'm trying to allow ASC/DESC sort order to be defined by a parameter in a stored procedure.
After lots of research, I found this approach (with simplification):
SELECT *
FROM MyTable
ORDER BY CASE WHEN #reverse = 1 THEN
MyColumn
END DESC,
CASE WHEN #reverse = 0 THEN
MyColumn
END ASC
However, this code throws the following error:
Msg 408, Level 16, State 1, Line 8
A constant expression was encountered in the ORDER BY list, position 2.
Why is this happening? Clearly MyColumn isn't a constant - it is a column name.
Using SQL Server 2016 in Compatibility Mode 2016 (130)
Thanks

After some search this line helped me understand more..
ordering by an expression must evaluate to a constant
so as Lamak pointed out,1=0 evaluates to false and you didn't define an else condition..so null is undefined and it throws error
to get rid of that try like below
ORDER BY CASE WHEN 1 = 1 THEN
MyColumn
END DESC,
CASE WHEN 1 = 0 THEN
col2 else col2--not your column,added this to make example clearer
END ASC
also beware ,expressions in order by must be unique,so your query won't work(even if it succeeds) and throws different error,you can use ISNULL as well

The real underlying cause of all this grief is the fact that:
ORDER BY MyColumn ASC
requires the
ASC
to be a hard-coded string (like SELECT, FROM, etc.) and can't be a string variable. ;-((
In order to overcome this limitation,
and the problem caused by trying to use CASE to overcome it,
I have made 95% of the query fill a table-variable,
then I have one of two queries which SELECT from it with the correct ORDER BY clause.

Related

Oracle CASE missing right parenthesis for a "in" limit

I have a QRY im developing in Oracle for spotfire. In the where statement, I have a decision case statement and if its True, im trying to pass a list of items to match a column, below is what I have, but its throwing a missing right parenthesis error and I cannot determine why. In short, when a variable is determined True (in this case 9>8 for the example, I need it to result those items, else, result the entire column with no limits.
Note: This works fine when its only 1 item being passed, i.e. 'BOB' but as soon as its multiple, this error occurs.
and Column1 = (CASE When 9>8 Then ('BOB','TOM') Else Column1 END)
Case expressions are best avoided in the where clause. Instead, write the logic with AND and OR:
And (
(9>8 AND Column1 IN ('BOB','TOM'))
OR 9<=8 -- You say you check a variable here, don't forget to check for NULL
)
Oracle does not have a boolean type for use in SQL queries.
Instead, just use basic logic:
and ( (9 > 8 and Column1 in ('BOB','TOM')) or
9 <= 8
)

Hive - SELECT inside WHEN clause of CASE function gives an error

I am trying to write a query in Hive with a Case statement in which the condition depends on one of the values in the current row (whether or not it is equal to its predecessor). I want to evaluate it on the fly, this way, therefore requiring a nested query, not by making it another column first and comparing 2 columns. (I was able to do the latter, but that's really second-best). Does anyone know how to make this work?
Thanks.
My query:
SELECT * ,
CASE
WHEN
(SELECT lag(field_with_duplicates,1) over (order by field_with_duplicates) FROM my_table b
WHERE b.id=a.id) = a.field_with_duplicates
THEN “Duplicate”
ELSE “”
END as Duplicate_Indicator
FROM my_table a
Error:
java.sql.SQLException: org.apache.spark.sql.AnalysisException: cannot recognize input near 'SELECT' 'lag' '(' in expression specification; line 4 pos 9
Notes:
The reason I needed the complicated 'lag' function is that the unique Id's in the table are not consecutive, but I don't think that's where it's at: I tested by substituting another simpler inner query and got the same error message.
Speaking of 'duplicates', I did search on this issue before posting, but the only SELECT's inside CASE's I found were in the THEN statement, and if that works the same, it suggests mine should work too.
You do not need the subquery inside CASE:
SELECT a.* ,
CASE
WHEN prev_field_with_duplicates = field_with_duplicates
THEN “Duplicate”
ELSE “”
END as Duplicate_Indicator
FROM (select a.*,
lag(field_with_duplicates,1) over (order by field_with_duplicates) as prev_field_with_duplicates
from my_table a
)a
or even you can use lag() inside CASE instead without subquery at all (I'm not sure if it will work in all Hive versions ):
CASE
WHEN lag(field_with_duplicates,1) over (order by field_with_duplicates) = field_with_duplicates
THEN “Duplicate”
ELSE “”
END as Duplicate_Indicator
Thanks to #MatBailie for the answer in his comment. Don't I feel silly...
Resolved

order by sql data using condtions (case) based on derived columns

I want to put a condtion to sort SQL data based on the value of derived columns as follows:
SELECT DISTINCT sp.ID
, sp.Status
, sp.Rank
, sp.Price
, sp.SalePrice
, sp.Width
, sp.Height
, sp.QOH
, (sp.SalePrice*sp.QOH) As 'sp.Value'
, (sp.Price*sp.QOH) As 'sp.StandardValue'
FROM table
WHERE -- Conditions
ORDER BY
CASE WHEN 'sp.SalePrice' > 0 THEN 'sp.Value' END DESC,
CASE WHEN 'sp.SalePrice' = 0 THEN 'sp.StandardValue' END DESC
Gves this error:
Msg 145, Level 15, State 1, Line 1
ORDER BY items must appear in the select list if SELECT DISTINCT is specified.
if i try
ORDER BY
CASE WHEN sp.SalePrice > 0 THEN (sp.SalePrice*sp.QOH) As "sp.Value" END DESC,
CASE WHEN sp.SalePrice = 0 THEN (sp.Price*sp.QOH) As sp.StandardValue" END DESC
Gives error:
Incorrect syntax near the keyword 'As'.
it starts giving the same select distinct error if i try to remove aliases * as from order by cluase & only leave the multiplication part
I'm going to add a bunch of suggestions about general conventions below, but as to the main problem consider what you're trying to do with this example:
My_Table:
id value
1 1
2 2
3 1
Now, try to show distinct value ordering by id. Which should it be?
value
1
2
or
value
2
1
So, you're asking SQL Server to do something that may be impossible or at least unclear.
Now, as to conventions... Maybe some of this is just how you've posted here, but I would make the following suggestions for your code:
Avoid using reserved and SQL language words for names. This would include table, rank, and status.
Avoid using special characters in names. This would include sp.value. Sure, you can do it with a quoted identifier, but some front-ends, etc. might not support them even if SQL does and you don't really buy anything by using them in most cases.
Use the quoted identifier when you have to quote names. If you absolutely must violate one of the above two suggestions, use the standard quoted identifiers for SQL Server, which are [ and ]. If you want to quote aliases, use these as well (and you shouldn't have to typically quote aliases BTW). This helps to avoid the problem that Mark B points out.
Your CASE statement can be better written as one ordering column. Also, you should include an ELSE in most cases to avoid unhandled conditions. This may not be needed here as long as you can't have NULLs or negative values in any of the involved columns.
CASE
WHEN sp.SalePrice > 0 THEN (sp.SalePrice*sp.QOH)
WHEN sp.SalePrice = 0 THEN (sp.Price*sp.QOH)
END
I would personally avoid using the table alias (which it looks like you accidentally left out of your query) as part of your column aliases. It makes it much more confusing IMO because it makes it look like that aliased column is actually a column in the table.
Your second example is closest as it specifies the columns correctly. However, you cannot alias columns in the order by. This would work:
ORDER BY
CASE WHEN sp.SalePrice > 0 THEN sp.SalePrice*sp.QOH END DESC,
CASE WHEN sp.SalePrice = 0 THEN sp.Price*sp.QOH END DESC
Or alternatively just use the alias you defined in the result set:
ORDER BY
CASE WHEN sp.SalePrice > 0 THEN [sp.Value] END DESC,
CASE WHEN sp.SalePrice = 0 THEN [sp.StandardValue] END DESC
Note the brackets [].... this is required to define the column name as you used a dotted name in the alias... otherwise sp.Value would be considered to be the Value column in the sp table.
using ' around field names turns them into strings. Either remove the quotes entirely, or use " instead.

Applying the MIN aggregate function to a BIT field

I want to write the following query:
SELECT ..., MIN(SomeBitField), ...
FROM ...
WHERE ...
GROUP BY ...
The problem is, SQL Server does not like it, when I want to calculate the minimum value of a bit field it returns the error Operand data type bit is invalid for min operator.
I could use the following workaround:
SELECT ..., CAST(MIN(CAST(SomeBitField AS INT)) AS BIT), ...
FROM ...
WHERE ...
GROUP BY ...
But, is there something more elegant? (For example, there might be an aggregate function, that I don't know, and that evaluates the logical and of the bit values in a field.)
One option is MIN(SomeBitField+0). It reads well, with less noise (which I would qualify as elegance).
That said, it's more hack-ish than the CASE option. And I don't know anything about speed/efficiency.
Since there are only two options for BIT, just use a case statement:
SELECT CASE WHEN EXISTS (SELECT 1 FROM ....) THEN 1 ELSE 0 END AS 'MinBit'
FROM ...
WHERE ...
This has the advantage of:
Not forcing a table scan (indexes on BIT fields pretty much never get used)
Short circuiting TWICE (once for EXISTS and again for the CASE)
It is a little more code to write but it shouldn't be terrible. If you have multiple values to check you could always encapsulate your larger result set (with all the JOIN and FILTER criteria) in a CTE at the beginning of the query, then reference that in the CASE statements.
This query is the best solution:
SELECT CASE WHEN MIN(BitField+0) = 1 THEN 'True' ELSE 'False' END AS MyColumn
FROM MyTable
When you add the BitField+0 it would automatically becomes like int
select min(convert(int, somebitfield))
or if you want to keep result as bit
select convert(bit, min(convert(int, somebitfield)))
Try the following
Note: Min represent And aggregate function , Max represent Or aggregate function
SELECT ..., MIN(case when SomeBitField=1 then 1 else 0 end), MIN(SomeBitField+0)...
FROM ...
WHERE ...
GROUP BY ...
same result
This small piece of code has always worked with me like a charm:
CONVERT(BIT, MIN(CONVERT(INT, BitField))) as BitField
AVG(CAST(boolean_column AS FLOAT)) OVER(...) AS BOOLEAN_AGGREGATE
Give a fuzzy boolean :
1 indicate that's all True;
0 indicate that's all false;
a value between ]0..1[ indicate partial matching and can be some percentage of truth.

How does one filter based on whether a field can be converted to a numeric?

I've got a report that has been in use quite a while - in fact, the company's invoice system rests in a large part upon this report (Disclaimer: I didn't write it). The filtering is based upon whether a field of type VarChar(50) falls between two numeric values passed in by the user.
The problem is that the field the data is being filtered on now not only has simple non-numeric values such as '/A', 'TEST' and a slew of other non-numeric data, but also has numeric values that seem to be defying any type of numeric conversion I can think of.
The following (simplified) test query demonstrates the failure:
Declare #StartSummary Int,
#EndSummary Int
Select #StartSummary = 166285,
#EndSummary = 166289
Select SummaryInvoice
From Invoice
Where IsNull(SummaryInvoice, '') <> ''
And IsNumeric(SummaryInvoice) = 1
And Convert(int, SummaryInvoice) Between #StartSummary And #EndSummary
I've also attempted conversions using bigint, real and float and all give me similar errors:
Msg 8115, Level 16, State 2, Line 7
Arithmetic overflow error converting
expression to data type int.
I've tried other larger numeric datatypes such as BigInt with the same error. I've also tried using sub-queries to sidestep the conversion issue by only extracting fields that have numeric data and then converting those in the wrapper query, but then I get other errors which are all variations on a theme indicating that the value stored in the SummaryInvoice field can't be converted to the relevant data type.
Short of extracting only those records with numeric SummaryInvoice fields to a temporary table and then querying against the temporary table, is there any one-step solution that would solve this problem?
Edit: Here's the field data that I suspect is causing the problem:
SummaryInvoice
11111111111111111111111111
IsNumeric states that this field is numeric - which it is. But attempting to convert it to BigInt causes an arithmetic overflow. Any ideas? It doesn't appear to be an isolated incident, there seems to have been a number of records populated with data that causes this issue.
It seems that you are gonna have problems with the ISNUMERIC function, since it returns 1 if can be cast to any number type (including ., ,, e0, etc). If you have numbers longer than 2^63-1, you can use DECIMAL or NUMERIC. I'm not sure if you can use PATINDEX to perform an regex look on SummaryInvoice, but if you can, then you should try this:
SELECT SummaryInvoice
FROM Invoice
WHERE ISNULL(SummaryInvoice, '') <> ''
AND CASE WHEN PATINDEX('%[^0-9]%',SummaryInvoice) > 0 THEN CONVERT(DECIMAL(30,0), SummaryInvoice) ELSE -1 END
BETWEEN #StartSummary And #EndSummary
You can't guarantee what order the WHERE clause filters will be applied.
One ugly option to decouple inner and outer.
SELECT
*
FROM
(
Select TOP 2000000000
SummaryInvoice
From Invoice
Where IsNull(SummaryInvoice, '') <> ''
And IsNumeric(SummaryInvoice) = 1
ORDER BY SummaryInvoice
) foo
WHERE
Convert(int, SummaryInvoice) Between #StartSummary And #EndSummary
Another using CASE
Select SummaryInvoice
From Invoice
Where IsNull(SummaryInvoice, '') <> ''
And
CASE WHEN IsNumeric(SummaryInvoice) = 1 THEN Convert(int, SummaryInvoice) ELSE -1 END
Between #StartSummary And #EndSummary
YMMV
Edit: after question update
use decimal(38,0) not int
Change ISNUMERIC(SummaryInvoice) to ISNUMERIC(SummaryInvoice + '0e0')
AND with IsNumeric(SummaryInvoice) = 1, will not short circuit in SQL Server.
But may be you can use
AND (CASE IsNumeric(SummaryInvoice) = 1 THEN Convert(int, SummaryInvoice) ELSE 0 END)
Between #StartSummary And #EndSummary
Your first issue is to fix your database structure so bad data cannot get into the field. You are putting a band-aid on a wound that needs stitches and wondering why it doesn't heal.
Database refactoring is not fun, but it needs to be done when there is a data integrity problem. I assume you aren't really invoicing someone for 11,111,111,111,111,111,111,111,111 or 'test'. So don't allow those values to ever get entered (if you can't change the structure to the correct data type, consider a trigger to prevent bad data from going in) and delete the ones you do have that are bad.