Determine if a Varchar field has a Numeric entry - sql

I've got a field in a table that has a DataType of varchar(10). This field contains numeric values that are formatted as a varchar, for the sole purpose of being used to join two tables together. Some sample data would be:
AcctNum AcctNumChar
2223333 2223333
3324444 3324444
For some records, the table sometimes thinks this field (AcctNumChar) is numeric and the join doesn't work properly. I then have to use an Update statement to re-enter the value as a varchar.
Is there any way to determine whether or not the field has a varchar or numeric value in it, using a query? I'm trying to narrow down which records are faulty without having to wait for one of the users to tell me that their query isn't returning any hits.

You can use isnumeric() for a generic comparison, for instance:
select (case when isnumeric(acctnum) = 1 then cast(acctnum as decimal(10, 0))
end)
In your case, though, you only seem to want integers:
(case when acctnum not like '%[^0-9]%' then cast(acctnum as decimal(10, 0))
end)
However, I would strongly suggest that you update the table to change the data type to a number, which appears to be the correct type for the value. You can also add a computed column as:
alter table t add AcctNum_Number as
(case when acctnum not like '%[^0-9]%' then cast(acctnum as decimal(10, 0))
end)
Then you can use the computed column rather than the character column.

There are several ways to check if varchar column contains numeric value but I recommend to you to us TRY_CONVERT function.
It will give you NULL if the value cannot be converted to number. For example, to get all records that have numeric values, you can do this:
SELECT *
FROM [table]
WHERE TRY_CONVERT(INT, [value]) IS NOT NULL

You can use CAST and CONVERT (Transact-SQL) functions here to solve your purpose.
reference here - https://msdn.microsoft.com/en-IN/library/ms187928.aspx.

IsNumeric worked, TRY_CONVERT didn't (SQL wouldn't recognize it as a built-in function for some reason). Anyway, for the record I ran the following query and got all of my suspect records:
SELECT *
FROM ACCT_LIST
where IsNumeric([ACCT_NUM_CHAR]) = 0

Use PATINDEX function:
DECLARE #s VARCHAR(20) = '123123'
SELECT PATINDEX('%[^0-9]%', #s)
If #s variable will have something different from range 0-9 this function will return the index of first occurence of non digit symbol. If all symbols are digits it will return 0.

Related

SQL Server subquery behaviour

I have a case where I want to check to see if an integer value is found in a column of a table that is varchar, but is a mix of values that can be integers some are just strings. My first thought was to use a subquery to just select the rows with numeric-esque values. The setup looks like:
CREATE TABLE #tmp (
EmployeeID varchar(50) NOT NULL
)
INSERT INTO #tmp VALUES ('aa1234')
INSERT INTO #tmp VALUES ('1234')
INSERT INTO #tmp VALUES ('5678')
DECLARE #eid int
SET #eid = 5678
SELECT *
FROM (
SELECT EmployeeID
FROM #tmp
WHERE IsNumeric(EmployeeID) = 1) AS UED
WHERE UED.EmployeeID = #eid
DROP TABLE #tmp
However, this fails, with: "Conversion failed when converting the varchar value 'aa1234' to data type int.".
I don't understand why it is still trying to compare #eid to 'aa1234' when I've selected only the rows '1234' and '5678' in the subquery.
(I realize I can just cast #eid to varchar but I'm curious about SQL Server's behaviour in this case)
You can't easily control the order things will happen when SQL Server looks at the query you wrote and then determines the optimal execution plan. It won't always produce a plan that follows the same logic you typed, in the same order.
In this case, in order to find the rows you're looking for, SQL Server has to perform two filters:
identify only the rows that match your variable
identify only the rows that are numeric
It can do this in either order, so this is also valid:
identify only the rows that are numeric
identify only the rows that match your variable
If you look at the properties of this execution plan, you see that the predicate for the match to your variable is listed first (which still doesn't guarantee order of operation), but in any case, due to data type precedence, it has to try to convert the column data to the type of the variable:
Subqueries, CTEs, or writing the query a different way - especially in simple cases like this - are unlikely to change the order SQL Server uses to perform those operations.
You can force evaluation order in most scenarios by using a CASE expression (you also don't need the subquery):
SELECT EmployeeID
FROM #tmp
WHERE EmployeeID = CASE IsNumeric(EmployeeID) WHEN 1 THEN #eid END;
In modern versions of SQL Server (you forgot to tell us which version you use), you can also use TRY_CONVERT() instead:
SELECT EmployeeID
FROM #tmp
WHERE TRY_CONVERT(int, EmployeeID) = #eid;
This is essentially shorthand for the CASE expression, but with the added bonus that it allows you to specify an explicit type, which is one of the downsides of ISNUMERIC(). All ISNUMERIC() tells you is if the value can be converted to any numeric type. The string '1e2' passes the ISNUMERIC() check, because it can be converted to float, but try converting that to an int...
For completeness, the best solution - if there is an index on EmployeeID - is to just use a variable that matches the column data type, as you suggested.
But even better would be to use a data type that prevents junk data like 'aa1234' from getting into the table in the first place.

Case when to transform string to int if is is not already an int

So, I have the problem that I have a Teradata SQL Code which is quite flexible through macro variables, so the INPUT can be different kinds of Tables. In those
Tables there is alwasy the information s_id, but sometimes the s_id is a string and sometime a int. So I want to transform the strings to an int and if it is already an int then it should just copy the value.
SELECT DISTINCT &i as F_ID,
/*In different Tables the Sid can appear as an string or integer. This CASE WHEN transforms a string into a Integer in case of a not numeric ID*/
CASE WHEN TYPE(data.&columnSId.) not like '%CHAR%' and to_number(data.&columnSId.) is NULL
THEN data.&columnSId.
WHEN TYPE(data.&columnSId.) like '%CHAR%'
THEN CAST(to_number(data.&columnSId.) as FLOAT)
ELSE 0
END as s_id,
FROM database.table
However somehow in cases when the s_id is already an int it nevertheless tries to use the function to_number which will fail and end in this error:
Teradata execute: Function 'to_number' called with an invalid number
or type of parameters.
Do you know how I can prevent this error? Why it even look at the other case when the first one is already true?
It's a parse time error, trying to apply To_Number on a numeric value. I don't think you can do what you want without Dynamic SQL.
But this returns a similar result by casting a numeric value to a string first:
Coalesce(Cast(To_Number(Trim(data.&columnSId.)) AS INT), 0)
Of course it's not very efficient for larger tables.

Get the greatest value in a nvarchar column

I'm developing a database with SQL Server 2012 SP2.
I have a table with a NVARCHAR(20) column, and it will have numbers: "000001", "000002", etc.
I need to get the greatest value in that column and convert it to int. How can I do it?
I have found that I can convert a nvarchar to int with this sql sentence:
SELECT CAST(YourVarcharCol AS INT) FROM Table
But I don't know how can I get the max value in that column because the numbers are nvarchar.
UPDATE:
By the way, this column is NVARCHAR because I need to store text on it. I'm testing my solution and I need to store ONLY numbers to test it.
If your numbers are padded like in the example given and all have the same width, you can just sort them alphanumerically and then cast the max-value to INT or BIGINT (depending on your numbers range).
If there are very many rows it was much faster, especially if there is an index on this column...
Something like
SELECT TOP 1 * FROM YourTable
ORDER BY NumberColumn DESC
or, if you need the max-value only:
SELECT MAX(NumberColumn) FROM YourTable
If you have to deal with negative numbers or differently padded numbers you have to cast them first
SELECT TOP 1 * FROM YourTable
ORDER BY CAST(NumberColumn AS INT) DESC
or
SELECT MAX(CAST(NumberColumn AS INT)) FROM YourTable
Please note:
If you've got very many rows, the second might get rather slow. Read about sargable
If your NumberColumn might include invalid values, you have to check, Read about ISNUMERIC().
The best solution - in any case - was to use an indexed numeric column to store these values
Try this one...
I think MAX is enough.
SELECT max(CAST(YourVarcharCol AS INT)) FROM Table
Try this
SELECT MAX(t.Y) from (SELECT CAST(YourVarcharCol AS INT) as Y FROM Table) t
You should try this on finding the highest value:
SELECT MAX(CAST(YourVarcharCol AS INT)) AS FROM Table
If all the data follow the same padding and formatting pattern, a simple max(col) would do.
However, if not, you have to cast the values to int first. Searching on a columns cast to some other datatype will not use an index, if there's any, but will scan the whole table instead. It may or may not be OK for you, depending on requirements and number of rows in the table. If performance is what you need, then create a calculated column as try_cast( col as int), and create an index on it.
Note that you should not use cast, but try_cast instead, to guard against values that can't be cast (if you use a datatype to store something which is essencially of another datatype, it always opens up a possibility for errors).
Of couse, if you can change the original column's type to int, it would be the best.
This will return max int value
SELECT MAX(IIF(ISNUMERIC(YourVarcharCol) = 1, YourVarcharCol, '0') * 1) FROM Table
You can use like this
select max(cast(ColumnName AS INT ))from TableName

Remove decimal values using SQL query

I am having following values in database table :
12.00
15.00
18.00
20.00
I want to remove all decimal ZEROS from all values , So how can I do this using SQL query. I tried replace query but that is not working.
I want values like :
12
15
18
20
My replace query :
select height(replace (12.00, '')) from table;
Please help.
Since all your values end with ".00", there will be no rounding issues, this will work
SELECT CAST(columnname AS INT) AS columnname from tablename
to update
UPDATE tablename
SET columnname = CAST(columnname AS INT)
WHERE .....
Here column name must be decimal.
select CAST(columnname AS decimal(38,0)) from table
Simply update with a convert/cast to INT:
UPDATE YOUR_TABLE
SET YOUR_COLUMN = CAST(YOUR_COLUMN AS INT)
WHERE -- some condition is met if required
Or convert:
UPDATE YOUR_TABLE
SET YOUR_COLUMN = CONVERT(INT, YOUR_COLUMN)
WHERE -- some condition is met if required
To test you can do this:
SELECT YOUR_COLUMN AS CurrentValue,
CAST(YOUR_COLUMN AS INT) AS NewValue
FROM YOUR_TABLE
As I understand your question, You have one table with column as datatype decimal(18,9).
And the column contains the data as follows:-
12.00
15.00
18.00
20.00
Now if you want to show record on UI without decimal value means like (12,15,18,20) then there are two options:-
Either cast this column as int in Select Clause
or may be you want to update this column value like (12,15,18,20).
To apply, First very simple just use the cast in select clause
select CAST(count AS INT) from tablename;
But if you want to update your column data with int value then you have to update you column datatype
and to do that
ALTER TABLE tablename ALTER COLUMN columnname decimal(9,0)
Then execute this
UPDATE tablename
SET count = CAST(columnname AS INT)
First of all, you tried to replace the entire 12.00 with '', which isn't going to give your desired results.
Second you are trying to do replace directly on a decimal. Replace must be performed on a string, so you have to CAST.
There are many ways to get your desired results, but this replace would have worked (assuming your column name is "height":
REPLACE(CAST(height as varchar(31)),'.00','')
EDIT:
This script works:
DECLARE #Height decimal(6,2);
SET #Height = 12.00;
SELECT #Height, REPLACE(CAST(#Height AS varchar(31)),'.00','');
If it's a decimal data type and you know it will never contain decimal places you can consider setting the scale property to 0. For example to decimal(18, 0). This will save you from replacing the ".00" characters and the query will be faster. In such case, don't forget to to check if the "prevent saving option" is disabled (SSMS menu "Tools>Options>Designers>Table and database designer>prevent saving changes that require table re-creation").
Othewise, you of course remove it using SQL query:
select replace(cast([height] as varchar), '.00', '') from table
Your data type is DECIMAL with decimal places, say DECIMAL(10,2). The values in your database are 12, 15, 18, and 20.
12 is the same as 12.0 and 12.00 and 12.000 . It is up to the tool you are using to select the data with, how to display the numbers. Yours either defaults to two digits for decimals or it takes the places from your data definition.
If you only want integers in your column, then change its data type to INT. It makes no sense to use DECIMAL then.
If you want integers and decimals in that column then stay with the DECIMAL type. If you don't like the way you are shown the values, then format them in your application. It's up to that client program to decide for instance if to display point or comma for the decimal separator. (The database can be used from different locations.)
Also don't rely on any database or session settings like a decimal separator being a point and not a comma and then use REPLACE on it. That can work for one person and not for the other.
You can use the floor function.
Example:
Select FLOOR(${selectedColumn}) from ${tableName}

Error converting data type varchar

I currently have a table with a column as varchar. This column can hold numbers or text. During certain queries I treat it as a bigint column (I do a join between it and a column in another table that is bigint)
As long as there were only numbers in this field had no trouble but the minute even one row had text and not numbers in this field I got a "Error converting data type varchar to bigint." error even if in the WHERE part I made sure none of the text fields came up.
To solve this I created a view as follows:
SELECT TOP (100) PERCENT ID, CAST(MyCol AS bigint) AS MyCol
FROM MyTable
WHERE (isnumeric(MyCol) = 1)
But even though the view shows only the rows with numeric values and casts Mycol to bigint I still get a Error converting data type varchar to bigint when running the following query:
SELECT * FROM MyView where mycol=1
When doing queries against the view it shouldn't know what is going on behind it! it should simply see two bigint fields! (see attached image, even mssql management studio shows the view fields as being bigint)
OK. I finally created a view that works:
SELECT TOP (100) PERCENT id, CAST(CASE WHEN IsNumeric(MyCol) = 1 THEN MyCol ELSE NULL END AS bigint) AS MyCol
FROM dbo.MyTable
WHERE (MyCol NOT LIKE '%[^0-9]%')
Thanks to AdaTheDev and CodeByMoonlight. I used your two answers to get to this. (Thanks to the other repliers too of course)
Now when I do joins with other bigint cols or do something like 'SELECT * FROM MyView where mycol=1' it returns the correct result with no errors. My guess is that the CAST in the query itself causes the query optimizer to not look at the original table as Christian Hayter said may be going on with the other views
Ideally, you want to try to avoid storing the data in this form - would be worth splitting the BIGINT data out in to a separate column for both performance and ease of querying.
However, you can do a JOIN like this example. Note, I'm not using ISNUMERIC() to determine if it's a valid BIGINT because that would validate incorrect values which would cause a conversion error (e.g. decimal numbers).
DECLARE #MyTable TABLE (MyCol VARCHAR(20))
DECLARE #OtherTable TABLE (Id BIGINT)
INSERT #MyTable VALUES ('1')
INSERT #MyTable VALUES ('Text')
INSERT #MyTable VALUES ('1 and some text')
INSERT #MyTable VALUES ('1.34')
INSERT #MyTable VALUES ('2')
INSERT #OtherTable VALUES (1)
INSERT #OtherTable VALUES (2)
INSERT #OtherTable VALUES (3)
SELECT *
FROM #MyTable m
JOIN #OtherTable o ON CAST(m.MyCol AS BIGINT) = o.Id
WHERE m.MyCol NOT LIKE '%[^0-9]%'
Update:
The only way I can find to get it to work for having a WHERE clause for a specific integer value without doing another CAST() on the supposedly bigint column in the where clause too, is to use a user defined function:
CREATE FUNCTION [dbo].[fnBigIntRecordsOnly]()
RETURNS #Results TABLE (BigIntCol BIGINT)
AS
BEGIN
INSERT #Results
SELECT CAST(MyCol AS BIGINT)
FROM MyTable
WHERE MyCol NOT LIKE '%[^0-9]%'
RETURN
END
SELECT * FROM [dbo].[fnBigIntRecordsOnly]() WHERE BigIntCol = 1
I don't really think this is a great idea performance wise, but it's a solution
To answer your question about the error message: when you reference a view name in another query (assuming it's a traditional view not a materialised view), SQL Server effectively does a macro replacement of the view definition into the consuming query and then executes that.
The advantage of doing this is that the query optimiser can do a much better job if it sees the whole query, rather than optimising the view separately as a "black box".
A consequence is that if an error occurs, error descriptions may look confusing because the execution engine is accessing the underlying tables for the data, not the view.
I'm not sure how materialised views are treated, but I would imagine that they are treated like tables, since the view data is cached in the database.
Having said that, I agree with previous answers - you should re-think your table design and separate out the text and integer data values into separate columns.
Try changing your view to this :
SELECT TOP 100 PERCENT ID,
Cast(Case When IsNumeric(MyCol) = 1 Then MyCol Else null End AS bigint) AS MyCol
FROM MyTable
WHERE (IsNumeric(MyCol) = 1)
Have you tried to convert other table's bigint field into varchar? As for me it makes sense to perform more robust conversion... It shouldn't affect your performance too much if varchar field is indexed.
Consider creating a redundant bigint field to hold the integer value of af MyCol.
You may then index the new field to speed up the join.
Try using this:
SELECT
ID,
CAST(MyCol AS bigint) as MyCol
FROM
(
SELECT TOP (100) PERCENT
ID,
MyCol
FROM
MyTable
WHERE
(isnumeric(MyCol) = 1)
) as tmp
This should work since the inner select only return numeric values and the outer select can therefore convert all values from the first select into a numeric. It seems that in your own code SQL tries to cast before executing the isnumeric function (maybe it has something to do with optimizing).
Try doing the select in 2 stages.
first create a view that selects all columns where my col is nummeric.
Then do a select in that view where you cast the varchar field.
The other thing you could look at is your design of tables to remove the need for the cast.
EDIT
Are some of the numbers larger than bigint?
Are there any spaces, leading, trailing or in the number?
Are there any format characters? Decimal points?