Query won't convert a nvarchar(30) to a decimal - sql

I have the following query, that when it is run gives the following error:
Conversion failed when converting the nvarchar value '247.300' to data type int.
The query is as follows:
SELECT UD.SerialNumber,
UD.ReadingDate,
UD.ChannelName,
CONVERT(decimal(18,2), ChannelValue) - LAG(CONVERT(decimal(18,2), ChannelValue),1,
CONVERT(decimal(18,2), ChannelValue)) OVER (PARTITION BY ChannelName,
CASE ChannelValue WHEN 0 THEN 0 ELSE 1 END ORDER BY ReadingDate ASC)
FROM [Staging].[UriData] UD
WHERE UD.ChannelName IN (SELECT ChannelName FROM [Staging].[ActiveChannels])
ORDER BY ReadingDate;
Here is a fiddle: DB Fiddle
The only difference between the fiddle (which works fine) and the query here is that I have to have the ChannelValue column NVARCHAR as some of the data will be in text. I have filtered out all of the text data in the query.
I have also tested it with the following query and the data doesn't show up. If it wasn't a number I would expect it to appear here.
select ChannelValue
from [Staging].[UriData]
where try_convert(numeric(18, 2), ChannelValue) is null and ChannelValue is not null;
The expected outcome is that the number '247.300' is converted into a number and processed. Why won't it convert?

If you read the error message it complains about int.
this is from the implicit cast here
CASE ChannelValue WHEN 0 THEN 0 ELSE 1 END
Use an explicit cast
CASE CONVERT(decimal(19,3), ChannelValue) WHEN 0 THEN 0 ELSE 1 END
then values such as 247.300 will convert fine and be comparable to the integer 0.
Though you also say "I have filtered out all of the text data in the query.". In that case you should always use TRY_CONVERT anyway in case operations get ordered such that the conversion happens before the filter.

You cannot easily filter out text values in SQL Server -- the database reserves the right to re-arrange operations and this might cause an error.
So, use TRY_CONVERT():
SELECT UD.SerialNumber,
UD.ReadingDate,
UD.ChannelName,
(TRY_CONVERT(decimal(18, 2), ChannelValue) -
TRY_CONVERT(DECIMAL(18, 2), LAG(ChannelValue), 1, ChannelValue) OVER (PARTITION BY ChannelName, CASE ChannelValue WHEN '0' THEN 0 ELSE 1 END ORDER BY ReadingDate ASC))
)
FROM [Staging].[UriData] UD
WHERE UD.ChannelName IN (SELECT ChannelName
FROM [Staging].[ActiveChannels]
)
ORDER BY ReadingDate;
If ChannelValue is a string, then the comparison in the CASE should be to a string value.
Under most circumstances, you would only need to do the conversion once. However, to avoid a problem with the subtraction, you also have to convert before the subtraction.
You can use this logic to simplify:
SELECT UD.SerialNumber,
UD.ReadingDate,
UD.ChannelName,
(ChannelValue_d -
LAG(ChannelValue_d), 1, ChannelValue_d) OVER (PARTITION BY ChannelName, CASE ChannelValue WHEN '0' THEN 0 ELSE 1 END ORDER BY ReadingDate ASC)
)
FROM [Staging].[UriData] UD CROSS APPLY
(VALUES (TRY_CONVERT(decimal(18, 2), ChannelValue) )
) v(ChannelValue_d)
WHERE UD.ChannelName IN (SELECT ChannelName
FROM [Staging].[ActiveChannels]
)
ORDER BY ReadingDate;

Related

How to MAX() on any column type at SQL Server

I have a view with many columns and a query builder that will eventually add a MAX statement on the query. Problem is, when the column type is BIT I cannot use MAX on it, so I need to check type before. I cannot check the data type of the column during the query builder, so I need a SQL solution that works fine for any data type.
Here is an example (the SELECT within the CTE represents a VIEW which can have any type of data on mycol, for this first example I'm simulating a BIT. The MAX portion is where I need to work on):
ORIGINAL
WITH cte (mycol) AS
(
SELECT CAST(1 as BIT) as mycol
)
SELECT
MAX(mycol) as mycol_max
FROM cte
I got this error:
Operand data type bit is invalid for max operator.
CASTING
If I do the following it will work:
WITH cte (mycol) AS
(
SELECT CAST(1 as BIT) as mycol
)
SELECT
MAX(CAST(mycol as INT)) as mycol_max
FROM cte
However, if mycol is a string (SELECT 'test' as mycol), I got this error:
Conversion failed when converting the varchar value 'test' to data type int.
So, I tried to check type using some different approaches:
ISNUMERIC()
WITH cte (mycol) AS
(
SELECT 'test' as mycol
)
SELECT
MAX(CASE
WHEN 1 = ISNUMERIC(mycol)
THEN CAST(mycol AS INT)
ELSE mycol
END) AS mycol_max
FROM cte
NOT LIKE %[^0-9]%
WITH cte (mycol) AS
(
SELECT 'test' as mycol
)
SELECT
MAX(CASE
WHEN "mycol" NOT LIKE '%[^0-9]%'
THEN CAST(mycol AS INT)
ELSE mycol
END) AS mycol_max
FROM cte;
TRY_PARSE
WITH cte (mycol) AS
(
SELECT 'test' as mycol
)
SELECT
MAX(CASE
WHEN TRY_PARSE(mycol AS INT) IS NOT NULL
THEN CAST(mycol AS INT)
ELSE mycol
END) AS mycol_max
FROM cte
All of these returned the error
Conversion failed when converting the varchar value 'test' to data type int.
Also, if mycol is an integer:
WITH cte (mycol) AS
(
SELECT 1 as mycol
)
...
I got
Argument data type int is invalid for argument 1 of parse function.
So, all I need is to allow anything into the MAX() statement. I tried checking and converting, but didn't get any satisfactory solution. How can I do it?
Thanks
Using SQL Server 2017.
BIT Won't behave as int so MAX won't really work unless you just choose max LEN which is still not optimal. You might try ORDER BY DESC but I doubt it will help. You might as well add another column that will specify what exactly MAX you're looking for.
SELECT TOP 1 CAST(1 as BIT) as mycol
FROM TB1
ORDER BY mycol DESC

Missing expression error when creating a view

I have the following oracle query to create a view :
CREATE VIEW uvw_Dashboard_Templates
AS
SELECT LL.ID
,LL.LoadDate
,LL.FileName
,LL.TemplateType
,LL.AnalystName
,LL.RecDate
,LL.CompanyID
,LL.CompanyName
,LL.Recommendation
,LL.Loaded
,LL.ErrorText
,CASE
WHEN LL.NewCompany = 1 AND LL.Loaded = 0 THEN 0
ELSE LL.NewCompany END NewCompany
,RH.rec_date LastRecDate
,RH.rec_code LastRecCode
,CONVERT(NUMBER(1), CASE
WHEN LL.Loaded = 1 AND NVL(LL.Recommendation, 'Rec') <>
NVL(RH.rec_code,'LastRec') THEN 1
ELSE 0 END) RecChanged
FROM tblTemplates_LoadLog LL
LEFT JOIN (
SELECT company_id, rec_date, rec_code
FROM (
SELECT company_id
, rec_date
, UPPER(rec_code) rec_code
, ROW_NUMBER() OVER(PARTITION BY company_id ORDER BY rec_date DESC) RowNumber
FROM tblRecHist
) OrderedList
WHERE RowNumber = 2) RH
ON LL.CompanyID = RH.company_id
which is throwing a
ORA-00936: missing expression error on running.
What is the possible cause of this?
change this:
,CONVERT('1', CASE
The Oracle/PLSQL CONVERT function converts a string from one character set to another.
CONVERT( string1, char_set_to [, char_set_from] )
you gotta give in string there
It looks like you're trying to use the SQL Server convert() function. Oracle does have its own convert() function, but it's not related at all:
CONVERT converts a character string from one character set to another.
The closest equivalent I can see to the SQL Server function would be to cast it:
,CAST(CASE
WHEN LL.Loaded = 1 AND NVL(LL.Recommendation, 'Rec') <>
NVL(RH.rec_code,'LastRec') THEN 1
ELSE 0 END AS NUMBER(1)) RecChanged
Describing the view would show the column as NUMBER(1), which I assume is the point of converting/casting it in the first place, since you know it conforms to the scale/precision constraint already.

Why this sql will cause type conversion error?

WITH tb_testl AS (
SELECT 1 AS id ,'hehe' AS value
UNION ALL
SELECT 1 AS id, '1' AS value
UNION ALL
SELECT 2 AS id, '2' AS value
UNION ALL
SELECT 2 AS id, '2' AS value
), tb_test2 AS (
SELECT CONVERT(INT , value) AS value FROM tb_testl WHERE id = 2
)
SELECT * FROM tb_test2 WHERE value = 2;
this sql will cause error
Conversion failed when converting the varchar value 'hehe' to data
type int.
but the table tb_test2 dosen't have the value 'hehe' which is in the anthor table tb_test1. And I found that this sql will work well if I don't append the statement WHERE value = 2; .I've tried ISNUMBERIC function but it didn't work.
version:mssql2008 R2
With respect to the why this occurs:
There is a Logical Processing Order, which describes the order in which clauses are evaluated. The order is:
FROM
ON
JOIN
WHERE
GROUP BY
WITH CUBE or WITH ROLLUP
HAVING
SELECT
DISTINCT
ORDER BY
TOP
You can also see the processing order when you SET SHOWPLAN_ALL ON. For this query, the processing is as follows:
Constant scan - this is the FROM clause, which consists of hard coded values, hence the constants.
Filter - this is the WHERE clause. While it looks like there are two where clauses (WHERE id = 2 and WHERE value = 2). SQL Server sees this differently, it considers a single WHERE clause: WHERE CONVERT(INT , value) = 2 AND id = 2.
Compute scaler. This is the CONVERT function in the select.
Because both WHERE clauses are executed simultaneously, the hehe value is not filtered out of the CONVERT scope.
Effectively, the query is simplified to something like:
SELECT CONVERT(INT, tb_testl.value) AS Cvalue
FROM (
SELECT 1 AS id
, 'hehe' AS value
UNION ALL
SELECT 1 AS id
, '1' AS value
UNION ALL
SELECT 2 AS id
, '2' AS value
UNION ALL
SELECT 2 AS id
, '2' AS value
) tb_testl
WHERE CONVERT(INT, tb_testl.value) = 2
AND tb_testl.id = 2
Which should clarify why the error occurs.
With SQL, you cannot read code in the same way as imperative languages like C. Lines of SQL code are not necessarily (mostly not at all, in fact) executed in the same order it is written in. In this case, it's an error to think the inner where is executed before the outer where.
SQL Server does not guarantee the order of processing of statements (with one exception below). That is, there is no guarantee that WHERE filtering happens before the SELECT. Or that one CTE is evaluated before another. This is considered an advantage because it allows SQL Server to rearrange the processing to optimize performance (although I consider the issue that you are seeing a bug).
Obviously, the problem is in this part of the code:
tb_test2 AS (
SELECT CONVERT(INT, value) AS value
FROM tb_testl
WHERE id = 2
)
(Well, actually, it is where tb_test2 is referenced.)
What is happening is that SQL Server pushes the CONVERT() to where the values are being read, so the conversion is attempted before the WHERE clause is processed. Hence, the error.
In SQL Server 2012+, you can easily solve this using TRY_CNVERT():
tb_test2 AS (
SELECT TRY_CONVERT(INT, value) AS value
FROM tb_testl
WHERE id = 2
)
However, that doesn't work in SQL Server 2008. You can use the fact that CASE does have some guarantees on the order of processing:
tb_test2 AS (
SELECT (CASE WHEN value NOT LIKE '%[^0-9]%' THEN CONVERT(INT, value)
END) AS value
FROM tb_testl
WHERE id = 2
)
error caused by this part of statement
), tb_test2 AS (
SELECT CONVERT(INT , value) AS value FROM tb_testl WHERE id = 2
value has type of varchar and 'hehe' value cannot be converted to integer
WITH tb_testl AS (
SELECT 1 AS id ,'hehe' AS value
UPDATE: sql try convert all value(s) to integer in you statement. to avoid error rewrite statement as
WITH tb_testl AS (
SELECT 1 AS id ,'hehe' AS value
UNION ALL SELECT 1 AS id, '1' AS value
UNION ALL SELECT 2 AS id, '2' AS value
UNION ALL SELECT 2 AS id, '2' AS value
), tb_test2 AS (
SELECT value AS value FROM tb_testl WHERE id = 2
),
tb_test3 AS (
SELECT cast(value as int) AS value FROM tb_test2
)
SELECT * FROM tb_test3

Summary Stats and Corresponding Dates - SQL Server

I was hoping someone perhaps could help. This problem was presented to me recently and I thought it would be easy, but (personally) found it a bit of a struggle. I can do it in Excel and SSRS - but I was curious if I was able to do it in SQL Server...
I would like to create a set of summary statistics (Max, Min) for a dataset. Easy enough... But I wanted to associate the corresponding date with those values.
Here is what my data looks like:
I have yearly data (not exactly - but beside the point) and I produce a pivoted summary like this using a series of CASE WHEN statements. This is fine - the output is seen on the right (above).
Each time I output this data - I like to provide a summary of the all the historic data (I only show the most recent data for sake of brevity). So... The question is how do I take an output like the one shown below (on different dates) and provide a summary data set like the one I have on the right?
So - a little background. I have already managed to join the Min and Max values using a UNION and that bit is fine. The tricky bit (I think) is how to form an INNER JOIN, using a sub query, with the Max or Min result values to return the corresponding Max or Min date, for each Type? Now it is highly likely that I am being a bit of an idiot and missing something obvious....but... Would really appreciate any help from anyone...
Many thanks in advance
This query will do the job, and for all TYPE
SELECT
Description, [CAR], [CAT], [MAT], [EAT], [PAR], [MAR], [FAR], [MOT], [LOT], [COT], [ROT]
FROM
(SELECT
unpvt.TYPE
,unpvt.Description
,unpvt.value
FROM (
SELECT
t.TYPE
,CONVERT(sql_variant,MAX(maxResult.MAX_RESULT)) as MAX_RESULT
,CONVERT(sql_variant,MIN(minResult.MIN_RESULT)) as MIN_RESULT
,CONVERT(sql_variant,MAX(CASE WHEN maxResult.MAX_RESULT IS NOT NULL THEN t.DATE ELSE NULL END)) as MAX_DATE
,CONVERT(sql_variant,MIN(CASE WHEN minResult.MIN_RESULT IS NOT NULL THEN t.DATE ELSE NULL END)) as MIN_DATE
FROM
table_name t -- You need to set your table name
LEFT JOIN (SELECT
TYPE
,MIN(RESULT) as MIN_RESULT
FROM
table_name -- You need to set your table name
GROUP BY
TYPE) minResult
on minResult.TYPE = t.TYPE
and minResult.MIN_RESULT = t.RESULT
LEFT JOIN (SELECT
TYPE
,MAX(RESULT) as MAX_RESULT
FROM
table_name -- You need to set your table name
GROUP BY
TYPE) maxResult
on maxResult.TYPE = t.TYPE
and maxResult.MAX_RESULT = t.RESULT
GROUP BY
t.TYPE) U
unpivot (
value
for Description in (MAX_RESULT, MIN_RESULT, MAX_DATE, MIN_DATE)
) unpvt) P
PIVOT
(
MAX(value)
FOR TYPE IN ([CAR], [CAT], [MAT], [EAT], [PAR], [MAR], [FAR], [MOT], [LOT], [COT], [ROT])
)AS PVT
DEMO : SQLFIDDLE
CONVERT(sql_variant, is a cast for columns to a common data type. This is a requirement of the UNPIVOT operator when you are running with subquery FROM.
It is possible to use the PIVOT command if your SQLServer is 2005 or better, but the raw data for the pivot need to be in a specific format, and the query I came up with is ugly
WITH minmax AS (
SELECT TYPE, RESULT, [date]
, row_number() OVER (partition BY TYPE ORDER BY TYPE, RESULT) a
, row_number() OVER (partition BY TYPE ORDER BY TYPE, RESULT DESC) d
FROM t)
SELECT info
, cam = CASE charindex('date', info)
WHEN 0 THEN cast(cast(cam AS int) AS varchar(50))
ELSE cast(cam AS varchar(50))
END
, car = CASE charindex('date', info)
WHEN 0 THEN cast(cast(car AS int) AS varchar(50))
ELSE cast(cam AS varchar(50))
END
, cat = CASE charindex('date', info)
WHEN 0 THEN cast(cast(cat AS int) AS varchar(50))
ELSE cast(cam AS varchar(50))
END
FROM (SELECT TYPE, 'maxres' info, RESULT value FROM minmax WHERE 1 = d
UNION ALL
SELECT TYPE, 'minres' info, RESULT value FROM minmax WHERE 1 = a
UNION ALL
SELECT TYPE, 'maxdate' info , [date] value FROM minmax WHERE 1 = d
UNION ALL
SELECT TYPE, 'mindate' info , [date] value FROM minmax WHERE 1 = a) DATA
PIVOT
(max(value) FOR TYPE IN ([CAM], [CAR], [CAT])) pvt
It's only a proof of concept so in SQLFiddle I have used a reducet set of fake data (3 row per 3 Type)
After the data preparation
SELECT TYPE, 'maxres' info, RESULT value FROM minmax WHERE 1 = d
UNION ALL
SELECT TYPE, 'minres' info, RESULT value FROM minmax WHERE 1 = a
UNION ALL
SELECT TYPE, 'maxdate' info , [date] value FROM minmax WHERE 1 = d
UNION ALL
SELECT TYPE, 'mindate' info , [date] value FROM minmax WHERE 1 = a
the value column is implicitly casted to the more complex datatype, in this case DateTime (you cannot have different data type in the same column), to see the data in the intended way an explicit cast is in needed, and is done with the CASE and CAST in
, cam = CASE charindex('date', info)
WHEN 0 THEN cast(cast(cam AS int) AS varchar(50))
ELSE cast(cam AS varchar(50))
END
the CASE check the data type, looking for the substring 'date' in the info column, then cast the row value back to INT for the minres and maxres column and in any case cast the value to varchar(50) to have the same data type again
UPDATE
With the sql_variant the CASE CAST block is not needed, thanks Ryx5
WITH minmax AS (
SELECT TYPE, RESULT, [date]
, row_number() OVER (partition BY TYPE ORDER BY TYPE, RESULT) a
, row_number() OVER (partition BY TYPE ORDER BY TYPE, RESULT DESC) d
FROM table_name)
SELECT info
, [CAM], [CAR], [CAT]
FROM (SELECT TYPE, 'maxres' info, cast(RESULT as sql_variant) value
FROM minmax WHERE 1 = d
UNION ALL
SELECT TYPE, 'minres' info, cast(RESULT as sql_variant) value
FROM minmax WHERE 1 = a
UNION ALL
SELECT TYPE, 'maxdate' info , cast([date] as sql_variant) value
FROM minmax WHERE 1 = d
UNION ALL
SELECT TYPE, 'mindate' info , cast([date] as sql_variant) value
FROM minmax WHERE 1 = a) DATA
PIVOT
(max(value) FOR TYPE IN ([CAM], [CAR], [CAT])) pvt

How do I sort a VARCHAR column in SQL server that contains numbers?

I have a VARCHAR column in a SQL Server 2000 database that can contain either letters or numbers. It depends on how the application is configured on the front-end for the customer.
When it does contain numbers, I want it to be sorted numerically, e.g. as "1", "2", "10" instead of "1", "10", "2". Fields containing just letters, or letters and numbers (such as 'A1') can be sorted alphabetically as normal. For example, this would be an acceptable sort order.
1
2
10
A
B
B1
What is the best way to achieve this?
One possible solution is to pad the numeric values with a character in front so that all are of the same string length.
Here is an example using that approach:
select MyColumn
from MyTable
order by
case IsNumeric(MyColumn)
when 1 then Replicate('0', 100 - Len(MyColumn)) + MyColumn
else MyColumn
end
The 100 should be replaced with the actual length of that column.
There are a few possible ways to do this.
One would be
SELECT
...
ORDER BY
CASE
WHEN ISNUMERIC(value) = 1 THEN CONVERT(INT, value)
ELSE 9999999 -- or something huge
END,
value
the first part of the ORDER BY converts everything to an int (with a huge value for non-numerics, to sort last) then the last part takes care of alphabetics.
Note that the performance of this query is probably at least moderately ghastly on large amounts of data.
select
Field1, Field2...
from
Table1
order by
isnumeric(Field1) desc,
case when isnumeric(Field1) = 1 then cast(Field1 as int) else null end,
Field1
This will return values in the order you gave in your question.
Performance won't be too great with all that casting going on, so another approach is to add another column to the table in which you store an integer copy of the data and then sort by that first and then the column in question. This will obviously require some changes to the logic that inserts or updates data in the table, to populate both columns. Either that, or put a trigger on the table to populate the second column whenever data is inserted or updated.
SELECT *, CONVERT(int, your_column) AS your_column_int
FROM your_table
ORDER BY your_column_int
OR
SELECT *, CAST(your_column AS int) AS your_column_int
FROM your_table
ORDER BY your_column_int
Both are fairly portable I think.
you can always convert your varchar-column to bigint as integer might be too short...
select cast([yourvarchar] as BIGINT)
but you should always care for alpha characters
where ISNUMERIC([yourvarchar] +'e0') = 1
the +'e0' comes from http://blogs.lessthandot.com/index.php/DataMgmt/DataDesign/isnumeric-isint-isnumber
this would lead to your statement
SELECT
*
FROM
Table
ORDER BY
ISNUMERIC([yourvarchar] +'e0') DESC
, LEN([yourvarchar]) ASC
the first sorting column will put numeric on top.
the second sorts by length, so 10 will preceed 0001 (which is stupid?!)
this leads to the second version:
SELECT
*
FROM
Table
ORDER BY
ISNUMERIC([yourvarchar] +'e0') DESC
, RIGHT('00000000000000000000'+[yourvarchar], 20) ASC
the second column now gets right padded with '0', so natural sorting puts integers with leading zeros (0,01,10,0100...) in correct order (correct!) - but all alphas would be enhanced with '0'-chars (performance)
so third version:
SELECT
*
FROM
Table
ORDER BY
ISNUMERIC([yourvarchar] +'e0') DESC
, CASE WHEN ISNUMERIC([yourvarchar] +'e0') = 1
THEN RIGHT('00000000000000000000' + [yourvarchar], 20) ASC
ELSE LTRIM(RTRIM([yourvarchar]))
END ASC
now numbers first get padded with '0'-chars (of course, the length 20 could be enhanced) - which sorts numbers right - and alphas only get trimmed
I solved it in a very simple way writing this in the "order" part
ORDER BY (
sr.codice +0
)
ASC
This seems to work very well, in fact I had the following sorting:
16079 Customer X
016082 Customer Y
16413 Customer Z
So the 0 in front of 16082 is considered correctly.
This seems to work:
select your_column
from your_table
order by
case when isnumeric(your_column) = 1 then your_column else 999999999 end,
your_column
This query is helpful for you. In this query, a column has data type varchar is arranged by good order.For example- In this column data are:- G1,G34,G10,G3. So, after running this query, you see the results: - G1,G10,G3,G34.
SELECT *,
(CASE WHEN ISNUMERIC(column_name) = 1 THEN 0 ELSE 1 END) IsNum
FROM table_name
ORDER BY IsNum, LEN(column_name), column_name;
This may help you, I have tried this when i got the same issue.
SELECT *
FROM tab
ORDER BY IIF(TRY_CAST(val AS INT) IS NULL, 1, 0),TRY_CAST(val AS INT);
The easiest and efficient way to get the job done is using TRY_CAST
SELECT my_column
FROM my_table
WHERE <condition>
ORDER BY TRY_CAST(my_column AS NUMERIC) DESC
This will sort all numbers in descending order and push down all non numeric values
SELECT FIELD FROM TABLE
ORDER BY
isnumeric(FIELD) desc,
CASE ISNUMERIC(test)
WHEN 1 THEN CAST(CAST(test AS MONEY) AS INT)
ELSE NULL
END,
FIELD
As per this link you need to cast to MONEY then INT to avoid ordering '$' as a number.
SELECT *,
ROW_NUMBER()OVER(ORDER BY CASE WHEN ISNUMERIC (ID)=1 THEN CONVERT(NUMERIC(20,2),SUBSTRING(Id, PATINDEX('%[0-9]%', Id), LEN(Id)))END DESC)Rn ---- numerical
FROM
(
SELECT '1'Id UNION ALL
SELECT '25.20' Id UNION ALL
SELECT 'A115' Id UNION ALL
SELECT '2541' Id UNION ALL
SELECT '571.50' Id UNION ALL
SELECT '67' Id UNION ALL
SELECT 'B48' Id UNION ALL
SELECT '500' Id UNION ALL
SELECT '147.54' Id UNION ALL
SELECT 'A-100' Id
)A
ORDER BY
CASE WHEN ISNUMERIC (ID)=0 /* alphabetical sort */
THEN CASE WHEN PATINDEX('%[0-9]%', Id)=0
THEN LEFT(Id,PATINDEX('%[0-9]%',Id))
ELSE LEFT(Id,PATINDEX('%[0-9]%',Id)-1)
END
END DESC