How to update status to wrong numeric if at least on character exist? - sql

I work on SQL server 2012 I have issue I can't update status to Numbers only when whole field have digits only from 0 to 9 .
but if it have only one character or precision then it will be not valid .
1222 valid
223g not valid
create table #acceptnumbersOnly
(
KeyValue nvarchar(50),
Status nvarchar(50)
)
insert into #acceptnumbersOnly(KeyValue)
values
('233'),
('g25k'),
('25k'),
('gkg'),
('145'),
('45.5')
Expected result will be :
KeyValue Status
233 Numbers only
g25k Not Valid Numbers Only
25k Not Valid Numbers Only
gkg Not Valid Numbers Only
145 Numbers only
45.5 Not Valid Numbers Only

Something like this
update #acceptnumbersOnly
set
[Status]=iif(KeyValue LIKE '%[^0-9]%', 'Not Valid Numbers Only', 'Numbers only');
Results
KeyValue Status
233 Numbers only
g25k Not Valid Numbers Only
25k Not Valid Numbers Only
gkg Not Valid Numbers Only
145 Numbers only
45.5 Not Valid Numbers Only

I grew up with the Case...When logic. This works for me.
SELECT KeyValue,
CASE
WHEN KeyValue LIKE '%[^0-9]%' THEN 'Not Valid Numbers Only'
ELSE 'Numbers only'
END AS Status
FROM #acceptnumbersOnly
I always thought IIF was for MS Access. I just found out about the IIF function right now. Apparently IIF was introduced in SQL Server 2012+. I would think the Case...When is a better solution, as it is portable across all versions of SQL Server. MAybe it's a moot point, because there probably are not too many people out there using pre-2012 SQL Server.

Related

Query to find if a column contains both number and decimal only

I have a column to check if contains number from 0-9 and a decimal. Since in the version of SQL am using the below does not seem working
select *
from tablename
whwere columnname like '%[^.0-9]%'
Also tried using column name like '%[0-9]%' and columnname not like '%.%' but if there is a negative sign it is not getting captured. Please advise.
The column data type is float. So can someone provide me a query to check if the column contains values from 0-9 and also it can contain decimal values these two are permitted. If say for example if I have value 9,9.99 ,-1.24 the query should output -1.24 I need this value other than decimal and number –
The issue with your LIKE clause is bad predicate logic ...like '%[^.0-9]%'should be NOT LIKE '%[^0-9.]%'
Take this sample data.
DECLARE #table TABLE (SomeNbr VARCHAR(32));
INSERT #table VALUES ('x'),('0'),('0.12'),('999'),('-29.33'),('88.33.22'),('9-9-'),('11-');
What you were trying to do would be accomplished like this:
SELECT t.someNbr
FROM #table AS t
WHERE someNbr NOT LIKE '%[^0-9.]%';
The problem here is we'll also return "88.33.22" and miss "-29.33", both valid float values. You can handle hyphens by adding a hyphen to your LIKE pattern:
SELECT t.someNbr, LEN(t.SomeNbr)-LEN(REPLACE(t.SomeNbr,'.',''))
FROM #table AS t
WHERE someNbr NOT LIKE '%[^0-9.-]%';
But now we also pick up "9-9-" and stuff with 2+ dots. To ensure that each starts with a number OR a hyphen, to ensure hyphens only exist in the front of the string (if at all) and that we a maximum of one dot:
--==== This will do a good job but can still be broken
SELECT t.someNbr
FROM #table AS t
WHERE someNbr NOT LIKE '%[^0-9.-]%' -- Can only contain numbers, dots and hyphens
AND LEN(t.SomeNbr)-LEN(REPLACE(t.SomeNbr,'.','')) < 2 -- can have up to 1 dot
AND LEN(t.SomeNbr)-LEN(REPLACE(t.SomeNbr,'-','')) < 2 -- can have up to 1 hyphen
AND PATINDEX('%-%',t.SomeNbr) < 2 -- hyphen can only be in the front
This does the trick and returns:
someNbr
--------------------------------
0
0.12
999
-29.33
All that said - **DONT DO THIS ANY OF THIS ^^^ **. There is no need to parse numbers in this way except to show others why not to. I can still break this. They way I return valid floats in a scenario like this is with TRY_CAST or TRY_CONVERT. This returns what you need and will perform better.
--==== Best Solution
SELECT t.someNbr
FROM #table AS t
WHERE TRY_CAST(t.SomeNbr AS float) IS NOT NULL;

Query to ignore rows which have non hex values within field

Initial situation
I have a relatively large table (ca. 0.7 Mio records) where an nvarchar field "MediaID" contains largely media IDs in proper hexadecimal notation (as they should).
Within my "sequential" query (each query depends on the output of the query before, this is all in pure T-SQL) I have to convert these hexadecimal values into decimal bigint values in order to do further calculations and filtering on these calculated values for the subsequent queries.
--> So far, no problem. The "sequential" query works fine.
Problem
Unfortunately, some of these Media IDs do contain non-hex characters - most probably because there was some typing errors by the people which have added them or through import errors from the previous business system.
Because of these non-hex chars, the whole query fails (of course) because the conversion hits an error.
For my current purpose, such rows must be skipped/ignored as they are clearly wrong and cannot be used (there are no medias / data carriers in use with the current business system which can have non-hex character IDs).
Manual editing of the data is not an option as there are too many errors and it is not clear with what the data must be replaced.
Challenge
To create a query which only returns records which have valid hex values within the media ID field.
(Unfortunately, my SQL skills are not enough to create the above query. Your help is highly appreciated.)
The relevant section of the larger query looks like this (xxxx is where your help comes in :-))
select
pureMediaID
, mediaID
, CUSTOMERID
,CONTRACT_CUSTOMERID
from
(
select concat('0x', Replace(Ltrim(Replace(mediaID, '0', ' ')), ' ', '0')) AS pureMediaID
--, CUSTOMERID
, *
from M_T_CONTRACT_CUSTOMERS
where mediaID is not null
and mediaID like '0%'
and xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
) as inner1
EDIT: As per request I have added here some good and some bad data:
Good:
4335463357
4335459809
1426427996
4335463509
4335515039
4335465134
4427370396
4335415661
4427369036
4335419089
004BB03433
004e7cf9c6
00BD23133
00EE13D8C1
00CCB5522C
00C46522C
00dbbe3433
Bad:
4564589+
AB6B8BFC.8
7B498DFCnm
DB218DFChb
d<tgfh8CFC
CB9E8AFCzj
B458DFCjhl
rytzju8DFC
BFCtdsjshj
DB9888FCgf
9BC08CFCyx
EB198DFCzj
4B628CFChj
7B2B8DFCgg
After I did upgrade the compatibility level of the SQL instance to SQL2016 (it was below 2012 before) I could use try_convert with same syntax as the original convert function as donPablo has pointed out. With that the query could run fully through and every MediaID which is not a correct hex value gets nicely converted into a null value - really, really nice.
Exactly what I needed.
Unfortunately, the solution of ALICE... didn't work out for me as this was also (strangely) returning records which had the "+" character within them.
Edit: The added comment of Alice... where you create a calculated field like this:
CASE WHEN "KEY" LIKE '%[^0-9A-F]%' THEN 0 ELSE 1 end as xyz
and then filter in the next query like this:
where xyz = 1
works also with SQL Instances with compatibility level < SQL 2012.
Great addition for people which still have to work with older SQL instances.
An option (although not ideal in terms of performance) is to check the characters in the MediaID through a case statement and regular expression
Hexadecimals cannot contain characters other than A-F and numbers between 0 and 9
CASE WHEN MediaID LIKE '%[0-9A-F]%' THEN 1 ELSE 0 END
I would recommend writing a function that can be used to evaluate MediaID first and checks if it is hexadecimal and then running the query for conversion

Error converting data type nvarchar to numeric - SQL Server

I am trying to take an average of a column in my database. The column is AMOUNT and it is stored as NVARCHAR(300),null.
When I try to convert it to a numeric value I get the following error:
Msg 8114, Level 16, State 5, Line 1
Error converting datatype NVARCHAR to NUMBER
Here is what I have right now.
SELECT AVG(CAST(Reimbursement AS DECIMAL(18,2)) AS Amount
FROM Database
WHERE ISNUMERIC(Reimbursement) = 1
AND Reimbursement IS NOT NULL
You would think that your code would work. However, SQL Server does not guarantee that the WHERE clause filters the database before the conversion for the SELECT takes place. In my opinion this is a bug. In Microsoft's opinion, this is an optimization feature.
Hence, your WHERE is not guaranteed to work. Even using a CTE doesn't fix the problem.
The best solution is TRY_CONVERT() available in SQL Server 2012+:
SELECT AVG(TRY_CONVERT(DECIMAL(18,2), Reimbursement)) AS Amount
FROM Database
WHERE ISNUMERIC(Reimbursement) = 1 AND Reimbursement IS NOT NULL;
In earlier versions, you can use CASE. The CASE does guarantee the sequential ordering of the clauses, so:
SELECT AVG(CASE WHEN ISNUMERIC(Reimbursement) = 1 AND Reimbursement IS NOT NULL
THEN CONVERT(DECIMAL(18,2), Reimbursement))
END)
FROM Database;
Because AVG() ignores NULL values, the WHERE is not necessary, but you can include it if you like.
Finally, you could simplify your code by using a computed column:
alter database add Reimbursement_Value as
(CASE WHEN ISNUMERIC(Reimbursement) = 1 AND Reimbursement IS NOT NULL
THEN CONVERT(DECIMAL(18,2), Reimbursement))
END);
Then you could write the code as:
select avg(Reimbursement_Value)
from database
where Reimbursement_Value is not null;
Quote from MSDN...
ISNUMERIC returns 1 for some characters that are not numbers, such as plus (+), minus (-), and valid currency symbols such as the dollar sign ($). For a complete list of currency symbols, see money and smallmoney
select isnumeric('+')---1
select isnumeric('$')---1
so try to add to avoid non numeric numbers messing with your ouput..
WHERE Reimbursement NOT LIKE '%[^0-9]%'
If you are on SQLServer 2012,you could try using TRY_Convert which outputs null for conversion failures..
SELECT AVG(try_convert( DECIMAL(18,2),Reimbursement))
from
table
I am guessing that since it is Nvarchar you are going to find some values in there with a '$','.', or a (,). I would run a query likt this:
SELECT Amount
FROM database
WHERE Amount LIKE '%$%' OR
Amount LIKE '%.%' OR
Amount LIKE '%,%'
See what you get and my guess you will get some rows returned and then update those rows and try it again.
Currently your query would pull all numbers that are not all numeric which is a reason why it is failing too. Instead try running this:
SELECT AVG(CAST(Reimbursement AS DECIMAL(18,2)) AS Amount
FROM Database
--Changed ISNUMERIC() = to 0 for true so it will only pull numeric numbers.
WHERE ISNUMERIC(Reimbursement) = 0 and Reimbursement IS NOT NULL

Return Character from Numeric Field using Cast & Coalesce

Using SQL Server 2008 R2
Relatively basic SQL user, apologies if this is a simple question but need a little help to tidy up the output of a fairly complex script I have been given.
I have a number of columns being returned for which where there is a NULL, I want to replace all NULL's with a standard set of characters, currently "---". Using ISNULL works for most columns. However, for some columns we are looking at 2 tables to find a value so have after doing some research on here, I have modified a line I am having trouble with as follows:
Previous
isnull (ff.ff_sales,aa.ff_sales) as 'Total Revenue'
Latest
cast(coalesce(ff.ff_sales,aa.ff_sales,'') as FLOAT) as 'Total Revenue'
The initial line returned 'NULL' if both ff.ff_sales & aa.ff_sales were empty, now with the latest line using cast & coalesce I get '0'. However, I am trying to achieve a situation where I get '---' as per all other fields where a NULL exists. I don't want it to return '0' for a Sales field as this is misleading. I have tried using VARCHAR instead of FLOAT but am unsure if this is the right thing to do at this stage?
1st column is using ISNULL, 2nd column current output with cast & coalesce, 3rd column is what I want to get to:
Total Revenue Total Revenue Total Revenue
67755 67755 67755
6.123 6.123 6.123
494.75 494.75 494.75
0 0 0
1139909 1139909 1139909
12346.45 12346.45 12346.45
129.866 129.866 129.866
NULL 0 ---
NULL 0 ---
554 554 554
Thanks for your help!
You can use case to decide what should be output in this scenario, like so:
select
case
when isnull (ff.ff_sales,aa.ff_sales) is null then '---'
else cast(isnull (ff.ff_sales,aa.ff_sales) as varchar)
end as 'Total Revenue'
This will force the output to be returned as varchar though, because you cannot cast '---' as a numeric type.
Alternatively, you could just let the value be NULL in DB, and in your presentation layer, replace a DBNull or equivalent value with '---'. This will let you keep total revenue as a numeric field at the DB level.

SQL query - LEFT 1 = char, RIGHT 3-5 = numbers in Name

I need to filter out junk data in SQL (SQL Server 2008) table. I need to identify these records, and pull them out.
Char[0] = A..Z, a..z
Char[1] = 0..9
Char[2] = 0..9
Char[3] = 0..9
Char[4] = 0..9
{No blanks allowed}
Basically, a clean record will look like this:
T1234, U2468, K123, P50054 (4 record examples)
Junk data looks like this:
T12.., .T12, MARK, TP1, SP2, BFGL, BFPL (7 record examples)
Can someone please assist with a SQL query to do a LEFT and RIGHT method and extract those characters, and do a LIKE IN or something?
A function would be great though!
The following should work in a few different systems:
SELECT *
FROM TheTable
WHERE Data LIKE '[A-Za-z][0-9][0-9][0-9][0-9]%'
AND Data NOT LIKE '% %'
This approach will indeed match P2343, P23423JUNK, and other similar text but requires that the format is A0000*.
Now, if the OP implies a format of 1st position is a character and all succeeding positions are numeric, as in A0+, then use the following (in SQL Server and a good deal of other database systems):
SELECT *
FROM TheTable
WHERE SUBSTRING(Data, 1, 1) LIKE '[A-Za-z]'
AND SUBSTRING(Data, 2, LEN(Data) - 1) NOT LIKE '%[^0-9]%'
AND LEN(Data) >= 5
To incorporate this into a SQL Server 2008 function, since this appears to be what you'd like most, you can write:
CREATE FUNCTION ufn_IsProperFormat(#data VARCHAR(50))
RETURNS BIT
AS
BEGIN
RETURN
CASE
WHEN SUBSTRING(#Data, 1, 1) LIKE '[A-Za-z]'
AND SUBSTRING(#Data, 2, LEN(#Data) - 1) NOT LIKE '%[^0-9]%'
AND LEN(#Data) >= 5 THEN 1
ELSE 0
END
END
...and call into it like so:
SELECT *
FROM TheTable
WHERE dbo.ufn_IsProperFormat(Data) = 1
...this query needs to change for Oracle queries because Oracle doesn't appear to support bracket notation in LIKE clauses:
SELECT *
FROM TheTable
WHERE REGEXP_LIKE(Data, '^[A-za-z]\d{4,}$')
This is the expansion gbn is doing in his answer, but these versions allow for varying string lengths without the OR conditions.
EDIT: Updated to support examples in SQL Server and Oracle for ensuring the format A0+, so that A1324, A2342388, and P2342 match but A2342JUNK and A234 do not.
The Oracle REGEXP_LIKE code was borrowed from Mark's post but updated to support 4 or more numeric digits.
Added a custom SQL Server 2008 approach which implements these techniques.
Depends on your database. Many have regex functions (note examples not tested so check)
e.g. Oracle
SELECT x
FROM table
WHERE REGEXP_LIKE(x, '^[A-za-z][:digit:]{4}$')
Sybase uses LIKE
Given that you're allowing between 3 and 6 digits for the number in your examples then it's probably better to use the ISNUMERIC() function on the 2nd character onwards:
SELECT *
FROM TheTable
-- start with a letter
WHERE Data LIKE '[A-Za-z]%'
-- everything from 2nd character onwards is a number
AND ISNUMERIC( SUBSTRING( Data, 2, 50 ) ) = 1
-- number doesn't have a decimal place
AND Data NOT LIKE '%.%'
For more information look at the ISNUMERIC function on MSDN.
Also note that:
I've limited the 2nd part with the number to 50 characters maximum, change this to suit your needs.
Strictly speaking you should check for currency symbols etc, as ISNUMERIC allows them, as well as +/- and some others
A better option might be to create a function that checks that each character after the first is between 0 and 9 (or 1 and 0 if you're using ASCII codes).
You can't use Regular Expressions in SQL Server, so you have to use OR. Correcting David Andres' answer...
WHERE
(
Data LIKE '[A-Za-z][0-9][0-9][0-9]'
OR
Data LIKE '[A-Za-z][0-9][0-9][0-9][0-9]'
OR
Data LIKE '[A-Za-z][0-9][0-9][0-9][0-9][0-9]'
)
David's answer allows "D1234junk" through
You also only need "[A-Z]" if you don't have case sensitivity