Pulling floats to sum data in array structure using SQL - sql

I'm trying to pull numbers from an array structure and then I want to sum them.
Example row entry:
{"DBA":50.0},{"RST":132.0},{"ZIT":752}
I would want to sum all of the number values so 50 + 132 + 752 = 934
What I have tried: col = column name
SELECT SUBSTRING(col, LEN(LEFT(col, CHARINDEX (':', col))) + 1, LEN(col) - LEN(LEFT(col,
CHARINDEX (':', col))) - LEN(RIGHT(col, LEN(col) - CHARINDEX ('}', Benefit))) - 1)
FROM table
This works to grab the first value (so 50.0) in the above example, but will not grab each value. Any idea how I can make this query grab multiple values and then sum them together?

I would, personally, convert your data into actual well formed JSON. Then you can easily SUM the values:
DECLARE #YourString nvarchar(MAX) = N'{"DBA":50.0},{"RST":132.0},{"ZIT":752}';
SELECT SUM(TRY_CONVERT(decimal(5,1),[value]))
FROM (VALUES(CONCAT('{',REPLACE(REPLACE(#YourString,'{',''),'}',''),'}')))V(JSONString)
CROSS APPLY OPENJSON(V.JSONString);
Or you could add a WITH to the OPENJSON call and then add (+) the values:
DECLARE #YourString nvarchar(MAX) = N'{"DBA":50.0},{"RST":132.0},{"ZIT":752}';
SELECT OJ.DBA + OJ.RST + OJ.ZIT
FROM (VALUES(CONCAT('{',REPLACE(REPLACE(#YourString,'{',''),'}',''),'}')))V(JSONString)
CROSS APPLY OPENJSON(V.JSONString)
WITH (DBA decimal(5,1),
RST decimal(5,1),
ZIT decimal(5,1)) OJ;

The content is almost a valid JSON, so you may try to fix it and parse it with built-in JSON support using OPENJSON() (a valid JSON content is [{"DBA":50.0},{"RST":132.0},{"ZIT":752}]):
SELECT
t.[Column],
[Sum] = (
SELECT SUM(CONVERT(numeric(10, 1), j2.value))
FROM OPENJSON(CONCAT('[', t.[Column], ']')) j1
CROSS APPLY OPENJSON(j1.[value]) j2
)
FROM (VALUES
('{"DBA":50.0},{"RST":132.0},{"ZIT":752}')
) t ([Column])

Related

Split strings in a column based on text values and numerical values such as patindex

I have a column that displays stock market options data like below:
GME240119C00020000
QQQ240119C00305000
NFLX240119P00455000
I want to be able to split these up so they show up like:
GME|240119|C|00020000
QQQ|240119|C|00305000
NFLX|240119|P|00455000
I was able to split the first portion with the ticker name by using the code below, but I don't know how to split the rest of the strings.
case patindex('%[0-9]%', str)
when 0 then str
else left(str, patindex('%[0-9]%', str) -1 )
end
from t
edit: for anyone who is wondering, I used Dale's solution below to get my desired outcome. I edited the query he provided to make the parts show up as individual columns
select
substring(T.contractSymbol,1,C1.Position-1) as a
,substring(T.contractSymbol,C1.Position,6) as b
,substring(S1.Part,1,1) as c
,substring(S1.Part,2,len(S1.Part)) as d
from Options_Data_All T
cross apply (
values (patindex('%[0-9]%', T.contractSymbol))
) C1 (Position)
cross apply (
values (substring(contractSymbol, C1.Position+6, len(T.contractSymbol)))
) S1 (Part);
Just keep doing what you started doing by using SUBSTRING. So as you did find the first number and actually in your case, based on the data provided, everything else is fixed length, so you don't have to search anymore, just split the string.
declare #Test table (Contents nvarchar(max));
insert into #Test (Contents)
values
('GME240119C00020000'),
('QQQ240119C00305000'),
('NFLX240119P00455000');
select
substring(T.Contents,1,C1.Position-1) + '|' + substring(T.Contents,C1.Position,6) + '|' + substring(S1.Part,1,1) + '|' + substring(S1.Part,2,len(S1.Part))
from #Test T
cross apply (
values (patindex('%[0-9]%', T.Contents))
) C1 (Position)
cross apply (
values (substring(Contents, C1.Position+6, len(T.Contents)))
) S1 (Part);
Returns:
Data
GME|240119|C|00020000
QQQ|240119|C|00305000
NFLX|240119|P|00455000
If one can assume that all but the first column are fixed width then a simple SUBSTRING solution would suffice e.g.
select
substring(Contents,1,len(Contents)-15)
+ '|' + substring(Contents,len(Contents)-14,6)
+ '|' + substring(Contents,len(Contents)-8,1)
+ '|' + substring(Contents,len(Contents)-7,8) [Data]
from #Test;
Note: CROSS APPLY is just a fancy way to use a sub-query to avoid needing to repeat a calculation.

Replace strings between two characters using T-SQL

I need to update a string to amend any aliases - which can be 'H1.', 'H2.', 'H3.'... etc - to all be 'S.' and am struggling to work out the logic.
For example I have this:
'H1.HUB_CUST_ID, H2.HUB_SALE_ID, H3.HUB_LOC_ID'
But I want this:
'S.HUB_CUST_ID, S.HUB_SALE_ID, S.HUB_LOC_ID'
If you could use wildcards in REPLACE, I'd do something like this REPLACE(#string, 'H%.H', 'S.H').
Theoretically, there is no limit to how many H# aliases there could be. In practice there will almost definitely be less than 10.
Is there a better way than a nested replace of H1 - H10 separately, which both looks messy in a script and carries a small risk if more tables are joined in future?
SQL Server doesn't support pattern replacement. You are better off using a different language, that does support pattern/REGEX replacement or implementing a CLR function.
That said, however, considering you said that the value would always be below 10 you could brute force it, but it's not "pretty".
SELECT REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(YourString,'H1.','S.'),'H2.','S.'),'H3.','S.'),'H4.','S.'),'H5.','S.'),'H6.','S.'),'H7.','S.'),'H8.','S.'),'H9.','S.')
FROM YourTable ...
You can convert your string to XML and then convert it into simple table:
DECLARE #txt nvarchar(max) = N'H1.HUB_CUST_ID, H2.HUB_SALE_ID, H3.HUB_LOC_ID',
#x xml
SELECT #x = '<a al="' + REPLACE(REPLACE(#txt,', ','</a><a al="'),'.','">')+ '</a>'
SELECT t.c.value('#al', 'nvarchar(max)') as alias_name,
t.c.value('.','nvarchar(max)') as col_name
FROM #x.nodes('/a') t(c)
Output:
alias_name col_name
H1 HUB_CUST_ID
H2 HUB_SALE_ID
H3 HUB_LOC_ID
You can put results into temp table, amend them using LIKE 'some basic pattern' and then build new string.
If you don't care about the result order, you can unaggregate and reaggregate:
select t.*, v.new_val
from t cross apply
(select string_agg(concat('S1', stuff(s.value, 1, charindex('.'), '') - 1, ',') within group (order by (select null) as newval
from string_split(t.col, ',') s
) s;
Note: This assumes that all values start with the prefix you want to replace -- as your sample data suggests. A case expression can be used if there are exceptions.
You can actually get the original ordering -- assuming no duplicates -- using charindex():
select t.*, v.new_val
from t cross apply
(select string_agg(concat('S1', stuff(s.value, 1, charindex('.'), '') - 1, ',')
within group (order by charindex(s.value, t.col)
) as newval
from string_split(t.col, ',') s
) s;

How to get maximum value of a specific part of strings?

I have below records
Id Title
500006 FS/97/98/037
500007 FS/97/04/035
500008 FS/97/01/036
500009 FS/97/104/040
I should split Title field and get 4th part of text and return maximum value. In this example my query should return 040 or 40.
select max(cast(right(Title, charindex('/', reverse(Title) + '/') - 1) as int))
from your_table
SQLFiddle demo
You can use PARSENAME function since you always have 4 parts(confirmed in comments section)
select max(cast(parsename(replace(Title,'/','.'),1) as int))
from yourtable
If you want to split the data in the Title column and get the part from the splitted text by position, you may try with one JSON-based approach with a simple string transformation. You need to transform the data in the Title column into a valid JSON array (FS/97/98/037 into ["FS","97","08","037"]) and after that to parse thе data with OPENJSON(). The result from OPENJSON() (using default schema and parsing JSON array) is a table with columns key, value and type, and the key column holds the index of the items in the JSON array:
Note, that using STRING_SPLIT() is not an option here, because the order of the returned rows is not guaranteed.
Table:
CREATE TABLE Data (
Id varchar(6),
Title varchar(50)
)
INSERT INTO Data
(Id, Title)
VALUES
('500006', 'FS/97/98/037'),
('500007', 'FS/97/04/035'),
('500008', 'FS/97/01/036'),
('500009', 'FS/97/104/040')
Statement:
SELECT MAX(j.[value])
FROM Data d
CROSS APPLY OPENJSON(CONCAT('["', REPLACE(d.Title, '/', '","'), '"]')) j
WHERE (j.[key] + 1) = 4
If you data has fixed format with 4 parts, even this approach may help:
SELECT MAX(PARSENAME(REPLACE(Title, '/', '.'), 1))
FROM Data
You can also try the below query.
SELECT Top 1
CAST('<x>' + REPLACE(Title,'/','</x><x>') + '</x>' AS XML).value('/x[4]','int') as Value
from Data
order by 1 desc
You can find the live demo Here.

sql extract rightmost number in string and increment

i have transaction codes like
"A0004", "1B2005","20CCCCCCC21"
I need to extract the rightmost number and increment the transaction code by one
"AA0004"----->"AA0005"
"1B2005"------->"1B2006"
"20CCCCCCCC21"------>"20CCCCCCCC22"
in SQL Server 2012.
unknown length of string
right(n?) always number
dealing with unsignificant number of string and number length is out of my league.
some logic is always missing.
LEFT(#a,2)+RIGHT('000'+CONVERT(NVARCHAR,CONVERT(INT,SUBSTRING( SUBSTRING(#a,2,4),2,3))+1)),3
First, I want to be clear about this: I totally agree with the comments to the question from a_horse_with_no_name and Jeroen Mostert.
You should be storing one data point per column, period.
Having said that, I do realize that a lot of times the database structure can't be changed - so here's one possible way to get that calculation for you.
First, create and populate sample table (Please save us this step in your future questions):
DECLARE #T AS TABLE
(
col varchar(100)
);
INSERT INTO #T (col) VALUES
('A0004'),
('1B2005'),
('1B2000'),
('1B00'),
('20CCCCCCC21');
(I've added a couple of strings as edge cases you didn't mention in the question)
Then, using a couple of cross apply to minimize code repetition, I came up with that:
SELECT col,
LEFT(col, LEN(col) - LastCharIndex + 1) +
REPLICATE('0', LEN(NumberString) - LEN(CAST(NumberString as int))) +
CAST((CAST(NumberString as int) + 1) as varchar(100)) As Result
FROM #T
CROSS APPLY
(
SELECT PATINDEX('%[^0-9]%', Reverse(col)) As LastCharIndex
) As Idx
CROSS APPLY
(
SELECT RIGHT(col, LastCharIndex - 1) As NumberString
) As NS
Results:
col Result
A0004 A0005
1B2005 1B2006
1B2000 1B2001
1B00 1B01
20CCCCCCC21 20CCCCCCC22
The LastCharIndex represents the index of the last non-digit char in the string.
The NumberString represents the number to increment, as a string (to preserve the leading zeroes if they exists).
From there, it's simply taking the left part of the string (that is, up until the number), and concatenate it to a newly calculated number string, using Replicate to pad the result of addition with the exact number of leading zeroes the original number string had.
Try This
DECLARE #test nvarchar(1000) ='"A0004", "1B2005","20CCCCCCC21"'
DECLARE #Temp AS TABLE (ID INT IDENTITY,Data nvarchar(1000))
INSERT INTO #Temp
SELECT #test
;WITH CTE
AS
(
SELECT Id,LTRIM(RTRIM((REPLACE(Split.a.value('.' ,' nvarchar(max)'),'"','')))) AS Data
,RIGHT(LTRIM(RTRIM((REPLACE(Split.a.value('.' ,' nvarchar(max)'),'"','')))),1)+1 AS ReqData
FROM
(
SELECT ID,
CAST ('<S>'+REPLACE(Data,',','</S><S>')+'</S>' AS XML) AS Data
FROM #Temp
) AS A
CROSS APPLY Data.nodes ('S') AS Split(a)
)
SELECT CONCAT('"'+Data+'"','-------->','"'+CONCAT(LEFT(Data,LEN(Data)-1),CAST(ReqData AS VARCHAR))+'"') AS ExpectedResult
FROM CTE
Result
ExpectedResult
-----------------
"A0004"-------->"A0005"
"1B2005"-------->"1B2006"
"20CCCCCCC21"-------->"20CCCCCCC22"
STUFF(#X
,LEN(#X)-CASE PATINDEX('%[A-Z]%',REVERSE(#X)) WHEN 0 THEN LEN(#X) ELSE PATINDEX('%[A-Z]%',REVERSE(#X))-1 END+1
,LEN(((RIGHT(#X,CASE PATINDEX('%[A-Z]%',REVERSE(#X)) WHEN 0 THEN LEN(#X) ELSE PATINDEX('%[A-Z]%',REVERSE(#X))-1 END)/#N)+1)#N)
,((RIGHT(#X,CASE PATINDEX('%[A-Z]%',REVERSE(#X)) WHEN 0 THEN LEN(#X) ELSE PATINDEX('%[A-Z]%',REVERSE(#X))-1 END)/#N)+1)#N)
works on number only strings
99 becomes 100
mod(#N) increments

Trying to extract number between 2 characters '|' MS SQL

I have column and need to extract number between 2 pipes |, example data inside is AAA|12345678|#RRR. I need to get this number 12345678.
my code is:
SELECT SUBSTRING(column_name,CHARINDEX('|',column_name) + 1, CHARINDEX('|',column_name) - CHARINDEX('|',column_name) - 1)
FROM [name].[name].[table_name]
Using your own code:
SELECT SUBSTRING(column_name,CHARINDEX('|',column_name) + 1,
CHARINDEX('|',column_name) - CHARINDEX('|',column_name) - 1)
FROM [name].[name].[table_name]
The second part of substring is not correct. It should be:
SELECT SUBSTRING(column_name,CHARINDEX('|',column_name) + 1,
CHARINDEX('|',column_name, CHARINDEX('|',column_name)))
FROM [name].[name].[table_name]
The nested CHARINDEX will look for the position of the second pipe. and the SUBSTRING will start from the first pipe and continue to the second
Assuming the 2nd position, you can use a little XML or ParseName()
XML Example
Declare #YourTable table (ID int,column_name varchar(max))
Insert Into #YourTable values
(1,'AAA|12345678|#RRR')
Select ID
,SomeValue = Cast('<x>' + replace(column_name,'|','</x><x>')+'</x>' as xml).value('/x[2]','varchar(max)')
From #YourTable
ParseName() Example
Select ID
,SomeValue = parsename(replace(column_name,'|','.'),2)
From #YourTable
Both would Return
ID SomeValue
1 12345678
String extraction is generally tricky in SQL Server. But if you only have one numeric value and are looking for it, then the code isn't that bad:
select patindex('%[0-9]|%', str),
substring(str, patindex('%|[0-9]%', str), patindex('%[0-9]|%', str) - patindex('%|[0-9]%', str) + 1)
from (values ('AAA|12345678|#RRR')) v(str)
I would use PARSENAME() :
select parsename(replace(str, '|', '.'), 2)
from ( values ('AAA|12345678|#RRR')
) v(str);