SQL Server 2005 - RIGHT() not working when adding to CHARINDEX() - sql-server-2005

I'm trying to use the RIGHT function to get the substring of a value if it consists of a '/', but it doesn't work when I add a number to the CHARINDEX value; only without.
Here is a sample of the code:
SELECT CASE
WHEN
CHARINDEX('/',REPLACE(ISNULL(d.target_grade,'NA'), 'N/A', 'NA')) = 0
THEN
REPLACE(ISNULL(d.target_grade,'NA'),'N/A','NA')
ELSE
RIGHT(d.target_grade, CHARINDEX('/',REPLACE(ISNULL(d.target_grade,'NA'), 'N/A', 'NA'))+1)
END as target_grade
FROM tbl --etc.
This returns for example
target_grade
-------------
C/D
It should return though this
target_grade
-------------
D
If I remove the +1, however, the RIGHT function works exactly as it should
target_grade
-------------
/D
What am I doing wrong here? Is my logic flawed?

I recommend avoiding overly-complex string manipulations by leveraging the power of the CASE statement. Try something like this:
CASE
when d.target_grade is null then 'NA'
when d.target_grade = 'N/A' then 'NA'
when charindex('/', d.target_grade) = 0 then d.target_grade
else substring(d.target_grade, charindex('/', d.target_grade) + 1, XX) -- Replace XX with the max posssible length of d.target_grade
END

Since RIGHT wants "how many characters to keep" rather than "where to start the string from", your current logic is wrong.
Simpler, if you already have "where to start the string from" is to use SUBSTRING:
SELECT CASE
WHEN
CHARINDEX('/',REPLACE(ISNULL(d.target_grade,'NA'), 'N/A', 'NA')) = 0
THEN
REPLACE(ISNULL(d.target_grade,'NA'),'N/A','NA')
ELSE
SUBSTRING(d.target_grade,
CHARINDEX('/',REPLACE(ISNULL(d.target_grade,'NA'), 'N/A', 'NA'))+1
,8000)
END as target_grade
FROM tbl

Silly me, like #MarkBannister said, charindex counts from the left of the string, but right counts from the right of the string. Therefore the above should be
SELECT CASE
WHEN
CHARINDEX('/',REPLACE(ISNULL(d.target_grade,'NA'), 'N/A', 'NA')) = 0
THEN
REPLACE(ISNULL(d.target_grade,'NA'),'N/A','NA')
ELSE
-- Use -1 NOT 1
RIGHT(d.target_grade, CHARINDEX('/',REPLACE(ISNULL(d.target_grade,'NA'), 'N/A', 'NA'))-1)
END as target_grade
FROM tbl --etc.
-1 NOT +1

Related

Converting a nested decode into equivalent CASE statement (needed for conversion from Oracle to PostgreSQL)

I am on Oracle 11.2.0.4. I have a nested DECODE statement that I need to convert into CASE statement. Can someone help on it. I am not sure of how it is done and in fact I don't fully understand the logic of it. If someone can explain what it basically intends to do and what would be the rewritten function using CASE that is Very useful to me. Here is the function...(note: do not worry about table joins , there are 3 tables and one condition etc. please focus on the DECODE and its conversion to CASE). Also I must manually convert and tools are not an option.
CREATE OR REPLACE FUNCTION TMP_Func
RETURN NUMBER
IS
V NUMBER;
BEGIN
SELECT DECODE (
NVL (tab1.flag1, '~'),
'D', DECODE (tab1.code_oid, NVL (tab3.bu_id, '-'), 1, 0),
'C', DECODE (tab1.code_oid, NVL (tab3.cost_id, '-'), 1, 0),
DECODE (tab2.oid,
DECODE (tab1.co_id, NULL, tab2.oid, tab1.co_id), 1,
0))
INTO V
FROM tab1, tab2, tab3
WHERE tab2.OID = tab1.sec_id;
RETURN V;
END;
something like this :
select CASE WHEN COALESCE(tab1.flag1,'~') = 'D' THEN
CASE WHEN tab1.code_oid=COALESCE(tab3.bu_id, '-') THEN 1 else 0 end
WHEN COALESCE(tab1.flag1,'~')='C' THEN
CASE WHEN tab1.code_oid=COALESCE(tab3.cost_id, '-') THEN 1 else 0 end
else
CASE WHEN tab2.oid=COALESCE(tab1.co_id,tab2.oid) THEN 1 else 0 end
end
FROM tab1, tab2, tab3
WHERE tab2.OID = tab1.sec_id;
NVL is replaced by COALESCE
DECODE(a,b,c,d,e,...,f) is replaced by :
CASE WHEN a=b THEN c
WHEN a=d THEN e
...
else f
end
your last decode ( DECODE (tab1.co_id, NULL, tab2.oid, tab1.co_id), 1,
0))) is in fact an NVL( tab1.co_id,tab2.oid)
That would be something like this:
SELECT CASE
WHEN NVL (tab1.flag1, '~') = 'D'
THEN
CASE
WHEN tab1.code_oid = NVL (tab3.bu_id, '-') THEN 1
ELSE 0
END
WHEN NVL (tab1.flag1, '~') = 'C'
THEN
CASE
WHEN tab1.code_oid = NVL (tab3.cost_id, '-') THEN 1
ELSE 0
END
ELSE
CASE
WHEN tab2.oid =
CASE
WHEN tab1.co_id IS NULL THEN tab2.oid
ELSE tab1.co_id
END
THEN
1
ELSE
0
END
END
INTO v
FROM ...
Note that your FROM clause contains 3 tables, but WHERE joins just two of them. What about tab3? Also, consider switching to ANSI join.
The following should do what you want:
SELECT CASE COALESCE(TAB1.FLAG1, '~')
WHEN 'D' THEN CASE
WHEN TAB1.CODE_OID = COALESCE(TAB3.BU_ID, '-') THEN 1
ELSE 0
END
WHEN 'C' THEN CASE
WHEN TAB1.CODE_OID = COALESCE(TAB3.COST_ID, '~') THEN 1
ELSE 0
END
ELSE CASE
WHEN TAB2.OID = COALESCE(TAB1.CO_ID, TAB2.OID) THEN 1
ELSE 0
END
END
INTO V
FROM TAB1
INNER JOIN TAB2
ON TAB2.OID = TAB2.SEC_ID
CROSS JOIN TAB3;
Note that NVL and COALESCE are slightly different in a couple of respects, although neither seems to be a factor here. First, NVL always takes two arguments, while COALESCE can take as many as you care to supply - it returns the first non-NULL argument. Second, NVL always evaluates both of its arguments (e.g. if a function is specified for one or both of the arguments to NVL, both functions are called), while COALESCE only evaluates as many of the arguments are necessary to find a non-NULL result; thus, if COALESCE is given two functions for arguments and the first returns a non-NULL value, the second function is never called. Not an issue here, but perhaps important (due to side effects) in other cases.
Best of luck.

Invalid argument for function integer IBM DB2

I need to filter out rows in table where numer_lini column has number in it and it is between 100 and 999, below code works just fine when i comment out line where i cast marsnr to integer. However when i try to use it i get error: Invalid character found in a character string argument of the function "INTEGER". when looking at the list seems like replace and translate filters only numbers just fine and select only contains legit numbers (list of unique values is not long so its easy to scan by eye). So why does it fail to cast something? I also tried using integer(marsnr), but it produces the same error. I need casting because i need numeric range, otherwise i get results like 7,80 and so on. As I mentioned Im using IBM DB2 database.
select numer_lini, war_trasy, id_prz1, id_prz2
from alaska.trasa
where numer_lini in (
select marsnr
from (
select
distinct numer_lini marsnr
from alaska.trasa
where case
when replace(translate(numer_lini, '0','123456789','0'),'0','') = ''
then numer_lini
else 'no'
end <> 'no'
)
where cast(marsnr as integer) between 100 and 999
)
fetch first 300 rows only
If you look at the optimized SQL from the Db2 explain, you will see that Db2 has collapsed your code into a single select.
SELECT DISTINCT Q2.NUMER_LINI AS "NUMER_LINI",
Q2.WAR_TRASY AS "WAR_TRASY",
Q2.ID_PRZ1 AS "ID_PRZ1",
Q2.ID_PRZ2 AS "ID_PRZ2",
Q1.NUMER_LINI
FROM ALASKA.TRASA AS Q1,
ALASKA.TRASA AS Q2
WHERE (Q2.NUMER_LINI = Q1.NUMER_LINI)
AND (100 <= INTEGER(Q1.NUMER_LINI))
AND (INTEGER(Q1.NUMER_LINI) <= 999)
AND (CASE WHEN (REPLACE(TRANSLATE(Q1.NUMER_LINI,
'0',
'123456789',
'0'),
'0',
'') = '') THEN Q1.NUMER_LINI
ELSE 'no' END <> 'no')
Use a CASE to force Db2 to do the "is integer" check first. Also, you don't check for the empty string.
E.g. with this table and data
‪create‬‎ ‪TABLE‬‎ ‪alaska‬‎.‪trasa‬‎ ‪‬‎(‪numer_lini‬‎ ‪VARCHAR‬‎(‪10‬‎)‪‬‎,‪‬‎ ‪war_trasy‬‎ ‪INT‬‎ ‪‬‎,‪‬‎ ‪id_prz1‬‎ ‪INT‬‎,‪‬‎ ‪id_prz2‬‎ ‪INT‬‎)‪;
insert into alaska.trasa values ('',1,1,1),('99',1,1,1),('500',1,1,1),('3000',1,1,1),('00300',1,1,1),('AXS',1,1,1);
This SQL works
select numer_lini, war_trasy, id_prz1, id_prz2
from alaska.trasa
where case when translate(numer_lini, '','0123456789') = ''
and numer_lini <> ''
then integer(numer_lini) else 0 end
between 100 and 999
Although that does fail if there is an embedded space in the input. E.g. '30 0'. To cater for that, a regular expressing is probably preferred. E.g.
select numer_lini, war_trasy, id_prz1, id_prz2
from alaska.trasa
where case when regexp_like(numer_lini, '^\s*[+-]?\s*((\d+\.?\d*)|(\d*\.?\d+))\s*$'))
then integer(numer_lini) else 0 end
between 100 and 999

Is there a function that returns a default value for a string that is not a valid number

I would like to know if there is an nvl function for alphabetic characters, or I have to use a case in the select statement?
Ex :
EMPNO
-----
73A69
7369B
C7369
7369
736,9
73,69
73e,69
73,e69
I want to get this as a result:
0
0
0
7369
736,9
73,69
Oracle 12cR2 and above :
select TO_NUMBER ( EMPNO DEFAULT 0 ON CONVERSION ERROR ) FROM t;
I'm not sure why you're referring to this as nvl, but if you're just trying to convert a non-numeric string to 0, I'd indeed use a case expression with a regexp_like call:
SELECT CASE WHEN REGEXP_LIKE(empno, '^[0-9]+$') THEN TO_NUMBER(empno) ELSE 0 END
FROM emp;
this is my final solution
SELECT CASE WHEN REGEXP_LIKE(empno, '^[0-9]+([\,\.][0-9]+)?$') THEN
TO_NUMBER(empno) ELSE 0 END
FROM emp;
Are you looking for a regular expression?
select (case when regexp_like(empno, '[^0-9,]' then 0 else empno end)
If you want to avoid case, you can play tricks, such as:
select coalesce(regexp_substr(empno, '^[0-9,]+$', 1, 1), '0')
But this is essentially the same logic.
Your main problem is that the numbers you find can contain a point or a comma and both represent the decimal separator. You've posted an answer yourself:
SELECT
CASE WHEN REGEXP_LIKE(empno, '^[0-9]+([\,\.][0-9]+)?$')
THEN TO_NUMBER(empno) ELSE 0 END
FROM emp;
but that will fail on either the comma or the point, because the DBMS does not by default interprete both as a decimal separator.
You could of course simply select the string:
SELECT
CASE WHEN REGEXP_LIKE(empno, '^[0-9]+([\,\.][0-9]+)?$')
THEN empno ELSE '0' END
FROM emp;
If you need to select the string converted to a number, make the string consistent (i.e. replace the point with a comma or vice versa) and display the appropriate format when using TO_CHAR:
SELECT
CASE WHEN REGEXP_LIKE(empno, '^[0-9]+([\,\.][0-9]+)?$')
THEN TO_NUMBER(REPLACE(empno, ',', '.'), '90.9999999') ELSE 0 END
FROM emp;
Accordingly, as of Oracle 12c Release 2 you could use:
SELECT TO_NUMBER(REPLACE(empno, ',', '.') DEFAULT 0 ON CONVERSION ERROR, '90.9999999')
FROM emp;
(This latter query would, however, accept '12,' and '12.' as valid numbers (namely as the number 12), which your regular expression forbids.)

SQL - string comparison ignores space

This query:
SELECT CASE WHEN 'abc ' = 'abc' THEN 1 ELSE 0 END
Returns 1, even though 'abc ' clearly is not equal to 'abc'. Similarly,
SELECT CASE WHEN 'abc ' LIKE '%c' THEN 1 ELSE 0 END
Also returns 1. However, a very similar query:
SELECT * FROM #tempTable WHERE Name LIKE '%c'
Did not return a row where Name = 'abc '.
SQL Server 2008 R2, Windows 7 & 2008 R2, x64.
= ignores trailing space
len ignores training space
like does not ignore trailing space
SELECT CASE WHEN 'abc ' = 'abc' and DATALENGTH('abc ') = DATALENGTH('abc')
THEN 1 ELSE 0 END
You can assert DATALENGTH is not relevant but it is still the solution.
Turns out that the Name column was NVARCHAR (even though it contained ASCII characters only) and NVARCHAR behaves differently than VARCHAR:
SELECT CASE WHEN N'abc ' LIKE 'abc' THEN 1 ELSE 0 END
Returns 0, ditto for column instead of literal. The following does return 1 still:
SELECT CASE WHEN N'abc ' = 'abc' THEN 1 ELSE 0 END
So = and LIKE work differently, another peculiar difference.
If you need to compare things in this way but are restricted because your columns are of data type VARCHAR, something like this will basically fill the trailing space with an 'X' which will accomplish a failed comparison:
DECLARE #1 VARCHAR(5), #2 VARCHAR(5)
SET #1 = 'ABC '
SET #2 = 'ABC'
IF REPLACE(#1,' ','X') = REPLACE(#2,' ','X')
PRINT 'Equal'
ELSE
PRINT 'Not Equal'
Not rocket science, but at least a work around if you encounter a similar situation that you need to deal with :)
TA, I am not sure how you got zero by simply mentioning as unicode(N). I run your query and it giving me 1 only.
You can compare the LEN('abc ') and DATALENGTH('abc ') and can use those as per you requirement or you can replace the trailing space with some character to solve your problem.
I had a similar issue with a nvarchar column and wanted to fix the data so I did the following to find the data
select 'x' + username + 'x' from aspnet_users
where 'x' + username + 'x' <> 'x' + rtrim(username) + 'x'
The where clause compares the username as is with the trimmed version
'x1234 x' <> 'x1234x'
To fix the data I just did an update
update aspnet_Users
set username = rtrim(username)
where 'x' + username + 'x' <> 'x' + rtrim(username) + 'x'

Looking for an alternative to messy SQL ISNULL/NULLIF

I have a SQL query running on SQL Server 2012 that needs to compare a bit value and return a string if that value is 1 and an empty string if it is zero.
Originally I had it as a CASE statement like this:
CASE WHEN myBit = 0 THEN
-- do other comparisons etc to build up the return string.
+'myString'
ELSE
-- do other comparisons etc to build up the return string.
'' END
The problem is that all of the code in 'do other' section is the same. All I want to do is append a string to the returned value if the bit is zero and nothing if it is 1.
So I refactored it to only have the common code once and then append to the string at the end like this:
-- do other comparisons etc to build up the return string. +
ISNULL(NULLIF(Cast(ISNULL(CAST(NULLIF(myBit, 0) AS NVARCHAR), 'myString') AS varchar),'0'),'')
However the above seems very messy not least because of the CAST statements required.
I'm looking for a clean and neat way of doing this but have run out of ideas - anyone have a better way of achieving this? Thanks.
Just add your CASE statement inline. Don't forget to return an empty string when mybit=1, or the whole thing will return NULL.
Select
-- do other comparisons etc to build up the return string.
+ Case When #mybit=0 Then mystring else '' End
You can also use IIF and CONCAT as you are on SQL Server 2012.
SELECT CONCAT('Start',
IIF(#mybit=0,'myString',''),
'End')
IIF is a bit more concise than CASE. CONCAT might be beneficial in that it casts non string types to strings automatically and concatenating NULL is treated the same as concatenating an empty string.
Also you can use a simple case here as;
Select 'Your other string ' +
Case mybit When 0 then 'mystring' else '' End As Results
--Results will be like
mybit Results
0 'Your other string mystring'
1 'Your other string '
OR if you want nothing (null) to return if mybit <> 0 then use a simple case without else part as;
Select 'Your other string ' + Case mybit When 0 then 'mystring' End As Results
--Results will be like
mybit Results
0 'Your other string mystring'
1 null
SQL-SERVER-DEMO for both cases
Can you put the common code into a subquery? It would resemble this:
select case when myBit = 0 then value else value + 'what you append' end returnvalue
from
(subquery with common code) abc
Or maybe a function
select case when myBit = 0 then yourfunction()
else yourfunction + 'what you append' end returnvalue