When sorting a nvarchar column in SQL that has a numeric value, SQL Server returns bad sorting and I don't know why!
Is there any particular way SQL Server sorts varchar columns having numeric values?
Thanks
This has a fail-safe which will take into account non numerical values, and place them at the end of your result set.
SELECT [nvarcharColumn]
FROM [Table]
ORDER BY CAST(ISNULL(NULLIF(ISNUMERIC([nvarcharColumn]),0),2147483647) as INT)
If you want it sorted as numbers, you will have to convert it to numbers.
Check here is a list of numbers in an nVarChar column being sorted:
With MyTable AS
(
Select Cast ('1' as nVarChar) nVarCharColumn UNION
Select '100' UNION
Select '99'
)
Select *
From MyTable
Order by 1
Logically 99 should come before the 100, right? But it is sorted on nVarChar at a time, so the first 1 of the 100 is smaller than the first 9 and therefore it comes before 99.
nVarCharColumn
------------------------------
1
100
99
(3 row(s) affected)
If you CAST then you get the desired output
With MyTable AS
(
Select Cast ('1' as nVarChar) nVarCharColumn UNION
Select '100' UNION
Select '99'
)
Select *
From MyTable
Order by CAST (nVarCharColumn as Int)
Here is the proper output
nVarCharColumn
------------------------------
1
99
100
(3 row(s) affected)
Here's a twist in the table. What if there is a string value in the column as well?
;With MyTable AS
(
Select Cast ('1' as nVarChar) nVarCharColumn UNION
Select '100' UNION
Select '99' UNION
Select 'String Value'
)
Select *
From MyTable
Order by CAST (nVarCharColumn as Int)
Error!!
Msg 245, Level 16, State 1, Line 22
Conversion failed when converting the nvarchar value 'String Value' to data type int.
So then the SORT part checks IsNumeric - no need to do the same thing in the SELECT part.
With MyTable AS
(
Select Cast ('1' as nVarChar) nVarCharColumn UNION
Select '100' UNION
Select '99' UNION
Select 'String Value'
)
Select *
From MyTable
Order by CAST ( Case When ISNUMERIC (nVarCharColumn)=1 Then nVarCharColumn Else -99 End as Int)
Get the proper output
nVarCharColumn
------------------------------
String Value
1
99
100
(4 row(s) affected)
you can type cast the values in number like int or float
SELECT nVarCharValue FROM table ORDER BY CAST(nVarCharValue AS INT)
Your sort value should be converted to a numeric value.
ORDER BY CONVERT(INT, YourVarcharCol) ASC
Hope this helps,
John
Related
I have a view named My_View which contains one varchar column having numeric decimal data.
While,select calculating avg in am getting error
ORA-01722: Invalid number for Fr Locale
Here is my oracle query which i tried but getting the error:
select (AVG(MY_COLUMN))
from MY_TABLE;
select TO_NUMBER((AVG(MY_COLUMN)), '90.9999', 'NLS_NUMERIC_CHARACTERS='',.''')
from MY_TABLE
GROUP BY MY_COLUMN;
How to get rid of this error?
Starting with Oracle 12.2, you can use the on conversion error clause of to_number() to return a default value when the conversion fails. This is handy for your use case: you can return null on conversion error, which aggregate function avg() will happily ignore.
select avg(
to_number(
my_column default null on conversion error,
'9999d9999',
'nls_numeric_characters = ''.,'''
)
) my_avg
from my_table;
Problem seems to be in data that isn't "numeric" (why do you keep numbers in VARCHAR2 columns)? For example, it contains '123A56'. What is AVG of that value?
A simple option is to use REGEXP_LIKE and perform numeric operations only on "valid" values. For example:
SQL> with test (col) as
2 (select '1234.56' from dual union all -- valid
3 select '131' from dual union all -- valid
4 select 'ABC' from dual union all -- invalid
5 select 'xy.23' from dual union all -- invalid
6 select '.3598' from dual union all -- invalid
7 select '12.34.56'from dual -- invalid
8 )
9 select col,
10 to_number(col, '9999D9999', 'nls_numeric_characters = ''.,''') col_as_num
11 from test
12 where regexp_like(col, '^\d+\.?\d+$');
COL COL_AS_NUM
-------- ----------
1234.56 1234,56
131 131
SQL>
Now you can AVG such values:
SQL> with test (col) as
2 (select '1234.56' from dual union all -- valid
3 select '131' from dual union all -- valid
4 select 'ABC' from dual union all -- invalid
5 select 'xy.23' from dual union all -- invalid
6 select '.3598' from dual union all -- invalid
7 select '12.34.56'from dual -- invalid
8 )
9 select avg(to_number(col, '9999D9999', 'nls_numeric_characters = ''.,''')) result
10 from test
11 where regexp_like(col, '^\d+\.?\d+$');
RESULT
----------
682,78
SQL>
I have a table tblTags (Date, Tagindex, Value)
The values in the table are:
Date Tagindex Value
---------------------------------
2017-10-21 0 21
2017-10-21 1 212
2017-10-21 2 23
2017-10-21 0 34
2017-10-21 1 52
2017-10-21 2 65
I want the result as :
Date 0 1 2
-------------------------------
2017-10-21 21 212 23
2017-10-21 34 52 65
For this I wrote the followring query
select *
from
(SELECT a.Date, a.Tagindex,a.value
FROM tblTag a) as p
pivot
(max(value)
for Tagindex in ( [tblTag])
) as pvt
But I get these errors:
Msg 8114, Level 16, State 1, Line 10
Error converting data type nvarchar to int.
Msg 473, Level 16, State 1, Line 10
The incorrect value "tblTag" is supplied in the PIVOT operator.
How to solve this issue.
I think can use a query like this:
;with t as (
select *
, row_number() over (partition by [Date],[Tagindex] order by (select 0)) seq
from tblTag
)
select [Date],
max(case when [Tagindex] = 0 then [Value] end) '0',
max(case when [Tagindex] = 1 then [Value] end) '1',
max(case when [Tagindex] = 2 then [Value] end) '2'
from t
group by [Date], seq;
SQL Server Fiddle Demo
SQL Server Fiddle Demo - with pivot
Note: In above query I use row_number() function to create a sequence number for each Date and Tagindex, But the trick is in using (select 0) that is a temporary field to use in order by part, that will not trusted to return arbitrary order of inserted rows.So, if you need to achieve a trusted result set; you need to have an extra field like a datetime or an auto increment field.
Try this:
DECLARE #tblTag TABLE
(
[Date] DATE
,[TagIndex] TINYINT
,[Value] INT
);
INSERT INTO #tblTag ([Date], [TagIndex], [Value])
VALUES ('2017-10-21', 0, 21)
,('2017-10-21', 1, 212)
,('2017-10-21', 2, 23)
,('2017-10-22', 0, 34)
,('2017-10-22', 1, 52)
,('2017-10-22', 2, 65);
SELECT *
FROM #tblTag
PIVOT
(
MAX([value]) FOR [Tagindex] IN ([0], [1], [2])
) PVT;
You need to say exactly which are the PIVOT columns. If you are going to have different values for the TagIndex and you cannot hard-coded them, you need to use dynamic PIVOT.
Also, you need to be sure you have a way to group the tagIndex values in one row. For example, different date (as in my test data), ID column which is marking when a row is inserted or something else (group ID column or date added column).
i have below table :
id amount
12 974
11 929
9 837,5
4 606,5
and i have taken amount datatype as varchar(100). Now when i am trying to convert into decimal then at that time it giving me conversion error.
i have written the following query:
select id,cast(amount as decimal(10,2)) as amount from table order by amount desc
With the above query i am getting error : Error converting data type varchar to numeric.
How can i solve this issue?
create function [dbo].[udf_splitstring] (#tokens varchar(max),
#delimiter varchar(5))
returns #split table (
token varchar(200) not null )
as
begin
declare #list xml
select #list = cast('<a>'
+ replace(#tokens, #delimiter, '</a><a>')
+ '</a>' as xml)
insert into #split
(token)
select ltrim(t.value('.', 'varchar(200)')) as data
from #list.nodes('/a') as x(t)
return
end
CREATE TABLE Table5
([id] int, [amount] varchar(100))
INSERT INTO Table5
([id], [amount])
VALUES
(12,'974'),
(11,'929'),
(9 ,'837,5'),
(4 ,'606,5')
select id,cast(token as decimal(10,2)) as amount from Table5
cross apply (select token from udf_splitstring([amount], ',') )a
id amount
12 974.00
11 929.00
9 837.00
9 5.00
4 606.00
4 5.00
or
2)
select id,amount,cast(replace (amount,',','.' )as decimal(10,2)) as amount1 from Table5
id amount amount1
12 974 974.00
11 929 929.00
9 837,5 837.50
4 606,5 606.50
3)
SELECT *,
TRY_PARSE( [amount] AS NUMERIC(10,2) USING 'El-GR' ) x
FROM Table5
If the string contain comma, you can not convert to decimal directly,
you can convert the string to Money type
;WITH t(id,amount)AS(
SELECT 12,'974' UNION
SELECT 11,'929' UNION
SELECT 9,'837,5.123' UNION
SELECT 4,'606,5'
)
SELECT id,CONVERT(MONEY,t.amount) FROM t WHERE ISNUMERIC(t.amount)=1
id
----------- ---------------------
4 6065.00
9 8375.123
11 929.00
12 974.00
I find this behaviour very strange and counterintuitive. (Even for SQL).
set ansi_nulls off
go
;with sampledata(Value, CanBeNull) as
(
select 1, 1
union
select 2, 2
union
select 3, null
union
select 4, null
union
select 5, null
union
select 6, null
)
select ROW_NUMBER() over(partition by CanBeNull order by value) 'RowNumber',* from sampledata
Which returns
1 3 NULL
2 4 NULL
3 5 NULL
4 6 NULL
1 1 1
1 2 2
Which means that all of the nulls are being treated as part of the same group for the purpose of calculating the row number. It doesn't matter whether the SET ANSI_NULLLS is on or off.
But since by definition the null is totally unknown then how can the nulls be grouped together like this? It is saying that for the purposes of placing things in a rank order that apples and oranges and the square root of minus 1 and quantum black holes or whatever can be meaningfully ordered. A little experimentation suggests that the first column is being used to generate the rank order as
select 1, '1'
union
select 2, '2'
union
select 5, null
union
select 6, null
union
select 3, null
union
select 4, null
generates the same values. This has significant implications which have caused problems in legacy code I am dealing with. Is this the expected behaviour and is there any way of mitigating it other than replacing the null in the select query with a unique value?
The results I would have expected would have been
1 3 NULL
1 4 NULL
1 5 NULL
1 6 NULL
1 1 1
1 2 2
Using Dense_Rank() makes no difference.
Yo.
So the deal is that when T-SQL is dealing with NULLs in predicates, it uses ternary logic (TRUE, FALSE or UNKNOWN) and displays the behavior that you have stated that you expect from your query. However, when it comes to grouping values, T-SQL treats NULLs as one group. So your query will group the NULLs together and start numbering the rows within that window.
For the results that you say you would like to see, this query should work...
WITH sampledata (Value, CanBeNull)
AS
(
SELECT 1, 1
UNION
SELECT 2, 2
UNION
SELECT 3, NULL
UNION
SELECT 4, NULL
UNION
SELECT 5, NULL
UNION
SELECT 6, NULL
)
SELECT
DENSE_RANK() OVER (PARTITION BY CanBeNull ORDER BY CASE WHEN CanBeNull IS NOT NULL THEN value END ASC) as RowNumber
,Value
,CanBeNull
FROM sampledata
I want to split date part like year and assign it to a variable in a stored procedure.
I run that stored procedure in sql azure. it throws error "Reference to database and/or server name in 'MASTER..spt_values' is not supported in this version of SQL Server."
Code:
declare #Year int
SET #Year =DATEPART(YYYY,GETDATE())
create table #SundayDates (Sunday datetime,NextSunday datetime)
INSERT INTO #SundayDates(Sunday,NextSunday)
SELECT max(dates),MAX(DATEADD(DD,+7,dates)) AS last_sunday from
(
SELECT dateadd(day,number-1,DATEADD(year,#year-1900,0)) AS dates
FROM MASTER..spt_values WHERE type='p' and
number between 1 and DATEDIFF(day,DATEADD(year,#year-1900,0),DATEADD(year,#year-1900+1,0))
) as t
WHERE DATENAME(weekday,dates)='sunday' GROUP BY DATEADD(month,datediff(month,0,dates),0)
This query gives the same results (as a result set, rather than inserting into a temp table, but can be easily adapted to do so) and doesn't rely on the spt_values table that the error message tells you isn't allowed:
;With Numbers (Num) as (
select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all
select 6 union all select 7 union all select 8 union all select 9 union all select 10 union all select 11
), MonthEnds as (
select DATEADD(month,DATEDIFF(year,'20010101',CURRENT_TIMESTAMP)*12 + n.Num,'20010131') as EndDate
from Numbers n
), LastSundays as (
select DATEADD(day,-n.Num,EndDate) as EndDate
from
MonthEnds me
cross join
Numbers n
where
n.Num between 0 and 6 and
DATEPART(weekday,DATEADD(day,-n.Num,EndDate)) = DATEPART(weekday,'20130512')
)
select EndDate,DATEADD(day,7,EndDate) as FollowingSunday
from LastSundays