Problem with using SUBSTRING and CHARINDEX - sql

I have a column (RCV1.ECCValue) in a table which 99% of the time has a constant string format- example being:
T0-11.86-273
the middle part of the two hyphens is a percentage. I'm using the below sql to obtain this figure which is working fine and returns 11.86 on the above example. when the data in that table is in above format
'Percentage' = round(SUBSTRING(RCV1.ECCValue,CHARINDEX('-',RCV1.ECCValue)+1, CHARINDEX('-',RCV1.ECCValue,CHARINDEX('-',RCV1.ECCValue)+1) -CHARINDEX('-',RCV1.ECCValue)-1),2) ,
However...this table is updated from an external source and very occasionally the separators differ, for example:
T0-11.86_273
when this occurs I get the error:
Invalid length parameter passed to the LEFT or SUBSTRING function.
I'm very new to SQL and have got myself out of many challenges but this one has got me stuck. Any help would be mostly appreciated. Is there a better way to extract this percentage value?

Replace '_' with '-' to string in CHARINDEX while specifying length to the substring
'Percentage' = round(SUBSTRING(RCV1.ECCValue,CHARINDEX('-',RCV1.ECCValue)+1, CHARINDEX('-',replace(RCV1.ECCValue,'_','-'),CHARINDEX('-',RCV1.ECCValue)+1) -CHARINDEX('-',RCV1.ECCValue)-1),2) ,

If you can guarantee the structure of these strings, you can try parsename
select round(parsename(translate(replace('T0-11.86_273','.',''),'-_','..'),2), 2)/100
Breakdown of steps
Replace . character in the percentage value with empty string using replace.
Replace - or _, whichever is present, with . using translate.
Parse the second element using parsename.
Round it up to 2 digits, which will also
automatically cast it to the desired numeric type.
Divide by 100
to restore the number as percentage.
Documentation & Gotchas

Use NULLIF to null out such values
round(
SUBSTRING(
RCV1.ECCValue,
NULLIF(CHARINDEX('-', RCV1.ECCValue), 0) + 1,
NULLIF(CHARINDEX('-',
RCV1.ECCValue,
NULLIF(CHARINDEX('-', RCV1.ECCValue), 0) + 1
), 0)
- NULLIF(CHARINDEX('-', RCV1.ECCValue), 0) - 1
),
2)
I strongly recommend that you place the repeated values in CROSS APPLY (VALUES to avoid having to repeat yourself. And do use whitespace, it's free.

Related

Remove all trailing decimal points from a number stored as a string

I have a couple of strings (nvarchar data type), one is a whole number and one has decimal points trailing. My goal is to remove decimals and have all values as a whole number.
I tried the code below but it gives me an error for the value with no decimals. Is there a way to accomplish this without a case expression. I'll be using this new column in a join.
SELECT [SOW]
--,LEFT([SOW], CHARINDEX('.', [SOW])-1) as 'TestColumn'
FROM [dbo].[t_Schedule_kdm]
WHERE sow in ('15229.11','11092')
Output:
11092
15229.11
My desired Output:
11092
15229
Just append a dot character so that you'll always find an index:
LEFT(SOW, CHARINDEX('.', SOW + '.') - 1)
It's not clear whether you need to cast the result of that expression to an integer value.
Convert first to the most precision number you could ever have e.g. decimal(9,2) then convert to an int. You can't convert directly from a decimal string to an int.
SELECT [Value]
, CONVERT(int,CONVERT(decimal(9,2),[Value]))
FROM (
VALUES ('15229.11'),('11092')
) x ([Value]);

SQL Query to select a value between two known strings

I need a SQL query to get the value between two known strings in a text column.
The column name is d_info and the table name is Details.
The text is an XML fragment, but stored as a text value.
What I need is to get the value between the bookends <nettoeinkommen> and </nettoeinkommen> which is 718 in this example.
I also need the output to be saved in new column named income with data type float(8).
land>DE</land></wohnanschrift><taetigkeit>rentner</taetigkeit><dkbkundenstatus><bestandskunde>false</bestandskunde></dkbkundenstatus><haushaltsangaben><einnahmen><einkommen><nettoeinkommen>718</nettoeinkommen></einkommen><kindergeld>0</kindergeld><vermietungverpachtungnetto>0</vermietungverpachtungnetto><elterngeld>0</elterngeld><rentenunbefristet>0</rentenunbefristet><unselbststaendigetaetigkeit>740</unselbststaendigetaetigkeit><geringfuegigebeschaeftigung>0</geringfuegigebeschaeftigung></einnahmen><ausgaben><warmmiete>550</warmmiete><ratenimmobilienfinanzierung>0</ratenimmobilienfinanzierung>
I tried this code:
SELECT cast(SUBSTRING(d_info, CHARINDEX('<nettoeinkommen>', d_info)
, CHARINDEX('</nettoeinkommen>', d_info) - CHARINDEX('<nettoeinkommen>', d_info)) as float(8)) as income
from dbo.Details
But it's returning an Error converting data type varchar to real.
When I remove the cast function, the script works but it returns <nettoeinkommen>718 instead of only 718.
Thanks.
It is starting at the start of the tag not the end of it.
SELECT cast(
SUBSTRING(
d_info,
CHARINDEX('<nettoeinkommen>', d_info) + len('<nettoeinkommen>'),
CHARINDEX('</nettoeinkommen>', d_info) - (CHARINDEX('<nettoeinkommen>', d_info) + len('<nettoeinkommen>'))
) as float(8)) as income
from dbo.Details
you might even have these defined in variables:
SELECT cast(
SUBSTRING(
d_info,
CHARINDEX(#startTag, d_info) + len(#startTag),
CHARINDEX(#endTag, d_info) - (CHARINDEX(#startTag,d_info)+ len(#startTag))
) as float(8)) as income
from dbo.Details
I think the code is much easier to understand with the variables.
You need to add the length of your opening tag from the start index and subtract from the length of your substring statement:
SUBSTRING(d_info, CHARINDEX('<nettoeinkommen>', d_info)+16,
CHARINDEX('</nettoeinkommen>', d_info) - CHARINDEX('<nettoeinkommen>', d_info)-16)
As it seems, you are querieing plain xml data, for such purpose sql-server provides xquery functionality:
SELECT CAST(r.d_info AS XML).value('(/haushaltsangaben/einnahmen/einkommen/nettoeinkommen)[1]', 'decimal(19,2)')
FROM
(
SELECT '<taetigkeit>rentner</taetigkeit>
<dkbkundenstatus>
<bestandskunde>false</bestandskunde>
</dkbkundenstatus>
<haushaltsangaben>
<einnahmen>
<einkommen>
<nettoeinkommen>718</nettoeinkommen>
</einkommen>
</einnahmen>
</haushaltsangaben>' AS d_info
) AS r
If you intend to query more info from your source, you will end up with a bunch of stacked substring, patindex functions or even your own defined functions. This should be more readable and mantainable.
Using XQuery: https://learn.microsoft.com/en-us/sql/t-sql/xml/query-method-xml-data-type
As for your initial issue The SUBSTRING function in SQL returns the subset from a string starting from a given index for a specific length. For example SELECT SUBSTRING('whatever',5,4) returns 'ever'.
In case of CHARINDEX it gives the index for the first found match of a given pattern within a string. Example SELECT CHARINDEX('ever','whatever') should return 5, as 'ever' starts at the fifth position in 'whatever').
Now in your case you need to add the length of '<nettoeinkommen>' to the starting charindex and substract the length of '</nettoeinkommen>' from the length of the substring:
Also consider using decimal or numeric type instead of float, if you need to precise calculations: https://technet.microsoft.com/en-us/library/ms187912(v=sql.105).aspx

Translate function not returning relevant string in amazon redshift

I am trying to use a simple Translate function to replace "-" in a 23 digit string. The example of one such string is "1049477-1623095-2412303" The expected outcome of my query should be 104947716230952412303
The list of all "1049477-1623095-2412303" is present in a single column "table1". The name of the column is "data"
My query is
Select TRANSLATE(t.data, '-', '')
from table1 as t
However, it is returning 104947716230952000000 as the output.
At first, I thought it is an overflow error since the resulting integer is 20 digit so I also tried to use following
SELECT CAST(TRANSLATE(t.data,'-','') AS VARCHAR)
from table1 as t
but this is not working as well.
Please suggest a way so that I could have my desirable output
This is too long for a comment.
This code:
select translate('1049477-1623095-2412303', '-', '')
is going to return:
'104947716230952412303'
The return value is a string, not a number.
There is no way that it can return '104947716230952000000'. I could only imagine that happening if somehow the value is being converted to a numeric or bigint type.
Try regexp_replace()
Taking your own example, execute:
select regexp_replace('[string / column_name]','-');
It can be achieve RPAD try below code.
SELECT RPAD(TRANSLATE(CAST(t.data as VARCHAR),'-','') ,20,'00000000000000000000')

SQL get decimal with only 2 places with no round

I have a query (SQL Server) that returns a decimal. I only need 2 decimals without rounding:
In the example above I would need to get: 3381.57
Any clue?
You could accomplish this via the ROUND() function using the length and precision parameters to truncate your value instead of actually rounding it :
SELECT ROUND(3381.5786, 2, 1)
The second parameter of 2 indicates that the value will be rounded to two decimal places and the third precision parameter will indicate if actual rounding or truncation is performed (non-zero values will truncate instead of round).
Example
You can see an interactive example of this in action here.
Another possibility is to use TRUNCATE:
SELECT 3381.5786, {fn TRUNCATE(3381.5786,2)};
LiveDemo
If you want to control the representation, you need to output the value as a string. One method is to convert to a decimal and then to a string:
select cast(cast(total as decimal(10, 2)) as varchar(255))
Another method is to convert to a string using str(). However, this often requires the removal of spaces:
select replace(str(total, 10, 2), ' ', '')

How can I use LEFT & RIGHT Functions in SQL to get last 3 characters?

I have a Char(15) field, in this field I have the data below:
94342KMR
947JCP
7048MYC
I need to break down this, I need to get the last RIGHT 3 characters and I need to get whatever is to the LEFT. My issue is that the code on the LEFT is not always the same length as you can see.
How can I accomplish this in SQL?
Thank you
SELECT RIGHT(RTRIM(column), 3),
LEFT(column, LEN(column) - 3)
FROM table
Use RIGHT w/ RTRIM (to avoid complications with a fixed-length column), and LEFT coupled with LEN (to only grab what you need, exempt of the last 3 characters).
if there's ever a situation where the length is <= 3, then you're probably going to have to use a CASE statement so the LEFT call doesn't get greedy.
You can use RTRIM or cast your value to VARCHAR:
SELECT RIGHT(RTRIM(Field),3), LEFT(Field,LEN(Field)-3)
Or
SELECT RIGHT(CAST(Field AS VARCHAR(15)),3), LEFT(Field,LEN(Field)-3)
Here an alternative using SUBSTRING
SELECT
SUBSTRING([Field], LEN([Field]) - 2, 3) [Right3],
SUBSTRING([Field], 0, LEN([Field]) - 2) [TheRest]
FROM
[Fields]
with fiddle
select right(rtrim('94342KMR'),3)
This will fetch the last 3 right string.
select substring(rtrim('94342KMR'),1,len('94342KMR')-3)
This will fetch the remaining Characters.