how to sum up value within one cell SQL - sql

I have some binary values such as 00, 0000, 001000.11111000, 1111100000
I need to sum it up so it turns into 0, 0, 1, 5, 5 ( sum 0s and 1s up)
how can we do that in SQL please?
Thanks

Assumption:
The binary values are stored as string.
Each value is in its own cell in a table. Something like:
BinaryValues (Consider it a column name)
00
0000
001000
and so on.
You want to add up the individual digits to get the sum.
SQL Product you are usind supports functions, looping, string manipulation like substring, extracting string length etc.
As per my best knowledge these are primitives available in all SQL products.
Solution:
Write a function (call it by any name. Ex: AddBinaryDigits) which will take the binary value in string format as input.
Inside the function and do a string manipulation. Extract each digit and add it up. Return the sum as result.
Call the function:
If using binary values stored in a table:
SELECT AddBinaryDigits(BinaryValues) FROM <WhatEverTableName>
If using fixed value:
SELECT AddBinaryDigits('00')
SELECT AddBinaryDigits('0000')
SELECT AddBinaryDigits('001000')
and so on.
Edited to include the request to create function.
CREATE FUNCTION <funtionName>
(
#ParameterName AS VARCHAR(expected string length like 10/15/20 etc.)
)
RETURNS INT
BEGIN
SQL Code to sum
RETURN SummedUpValue
END

Use the below query. If needed convert it into function.
create function dbo.fnSumChars(#someInt VARCHAR(20))
RETURNS INT
AS
BEGIN
DECLARE #count INT = LEN(#someInt),
#counter INT = 1
DECLARE #Sum INT = 0
WHILE #counter <= #count
BEGIN
SELECT #sum += CAST(SUBSTRING(CAST(#someInt AS VARCHAR), #counter, 1) AS int)
SELECT #counter += 1
END
RETURN #sum --5
END
This is the function and you can call this function like below
SELECT dbo.fnSumChars('1111100000')

If these are already in string format, this is the easiest:
select len(replace('1111100000', '0', ''))
No need for a function either, because it can be inlined in the query. Functions, even the light ones, incure perf penalty.

Related

Is there any way to loop a string in SQL Server?

I am trying to loop a varchar in SQL Server, one of the columns has the format
"F1 100 F2 400 F3 600"
What I need is to take the numbers and divide by 10: "F1 10 F2 40 F3 60", for the moment I have a stored procedure which calls this function:
ALTER FUNCTION [name_offunction]
(#Chain varchar(120))
RETURNS varchar(120
AS
BEGIN
DECLARE #Result varchar(120), #Pos int, #Concat varchar(120)
WHILE LEN(#Chain) > 0
BEGIN
SET #Pos = CHARINDEX(' ', #Chain)
SET #Result = CASE
WHEN SUBSTRING(#Chain, 1, #Pos-1) LIKE '%[^A-Z]%'
THEN SUBSTRING(#Chain, 1, #Pos-1)
WHEN SUBSTRING(#Chain, 1, #Pos-1) NOT LIKE '%[^A-Z]%'
THEN CAST(CAST(SUBSTRING(#Chain, 1, #Pos-1) / 10 AS INT)AS CHAR)
END
SET #Chain = REPLACE(#Chain, SUBSTRING(#Chain, 1, #Pos), '')
SET #Concat += #Result + ' '
END
RETURN #Concat
We seem to have 2 problems here. Firstly the fact that you want to loop in SQL, however, SQL is a set based language. This means that it performs great at set-based operations but poorly at iterative ones, such as a loop.
Next is that you have what appears to be delimited data, and that you want to affect that delimited data in some way, and the reconstruct the data into a delimited string. Storing delimited data in a database is always a design flaw, and you should really be fixing said design.
I would therefore propose you move to an inline table-value function over a scalar function.
Firstly, as it appears that the ordinal position of the values is important we can't use SQL Server's built in STRING_SPLIT, as it is documented to not guarantee the order of the values will be the same. I am therefore going to use DelimitedSplit8K_LEAD which gives the ordinal position.
Then we can use TRY_CONVERT to check to see if the value is an int (I assume this is the correct data type), and if it is divide by 10. Finally we can reconstruct the data using STRING_AGG.
Outside of a function this would look like this:
DECLARE #Chain varchar(120) = 'F1 100 F2 400 F3 600';
SELECT STRING_AGG(COALESCE(CONVERT(varchar(10),TRY_CONVERT(int,DS.item)/10),DS.item),' ') WITHIN GROUP (ORDER BY DS.Item)
FROM dbo.DelimitedSplit8K_LEAD(#Chain,' ') DS;
As a function, you could therefore do this:
CREATE FUNCTION dbo.YourFunction (#Chain varchar(120))
RETURNS TABLE AS
RETURN
SELECT STRING_AGG(COALESCE(CONVERT(varchar(10),TRY_CONVERT(int,DS.item)/10),DS.item),' ') WITHIN GROUP (ORDER BY DS.Item) AS NewChain
FROM dbo.DelimitedSplit8K_LEAD(#Chain,' ') DS;
GO
And call is as such:
SELECT YF.NewChain
FROM dbo.YourTable YT
CROSS APPLY dbo.YourFunction (YT.Chain) YF;
db<>fiddle
Note that STRING_AGG was introduced in SQL Server 2017; if you're using an older version (you don't note this is the question) you'll need to use the "old" FOR XML PATH solution, shown here.

How can we read a varchar column, take the integer part out and add new column incrementing that integer part using script

I need to write a SCRIPT for below scenario:
We have a column X with rows value for this column X as X01,X02,X03,X04........
The problem I am stuck with is that I needed to add another row to this table based on the value of the last row that is X04, Well I am able to identify the logic that I need to work which is given below:
I need to read value X04
Take the integer part 04
Increment by 1 => 05
Save column value as X05
I am able to pass with the 1st step which is not very hard. The problem that I am facing is the next steps. I have researched and tried quite a lot commands but none worked.
Any help is highly appreciated. Thanks.
You seem to be describing:
select concat(left(max(x), 1),
right(concat('00', try_convert(int, right(max(x), 2)) + 1), 2)
from t;
This is doing the following:
Taking the left most character.
Converting the two right characters to a number and adding one.
Converting that back to a zero-padded string.
Here is a db<>fiddle.
Now: That you want to increment a string value seems broken. You should just use an identity column or sequence to assign a number. You can format the value as a string when you query the table -- or use a computed column to store that.
Try below Script
CREATE TABLE #table (x varchar(20))
INSERT INTO #table VALUES('X01'),('X02'),('X03'),('X04')
DECLARE #maxno NVARCHAR(20)
DECLARE #maxstring NVARCHAR(20)
DECLARE #finalno NVARCHAR(20)
DECLARE #loopminno INT =1 -- you can change based on the requirement
DECLARE #loopmaxno INT =10 -- how many number we want to increment
WHILE #loopminno < #loopmaxno
BEGIN
select #maxno = MAX(CAST(SUBSTRING(x, PATINDEX('%[0-9]%', x), 100) as INT))
, #maxstring = MAX(SUBSTRING(x, 1, PATINDEX('%[0-9]%',x)-1))
from #table
where PATINDEX('%[1-9]%',x)>0
SELECT #finalno = #maxstring + CASE WHEN CAST(#maxno AS INT)<9 THEN '0' ELSE '' END + CAST(#maxno+1 AS VARCHAR(20))
INSERT INTO #table
SELECT #finalno
SET #loopminno = #loopminno+1
END

Remove all characters not like desired value

I'm using SQL server 2012, and I have an issue with certain values. I want to extract a specific set of values from a string (which is in the entire column) and want to just retrieve the specific value.
The value is: SS44\\230433\586 and in other value it's 230084android, and the third orderno 239578
The common denominator is that all numbers start with 23, and are 6 characters long. All other values have to be removed from the string. I tried rtrim and a ltrim but that didn't give me the desired output.
I'm not sure as to how to do this without regex.
You can use PATINDEX to find the start of the number and SUBSTRING to get the next 6 digits:
declare #Value varchar(50) = 'SS44\\230433\586'
select substring(#Value, patindex('%23%', #Value), 6)
If you want to be a bit more careful with the searching, you can use PATINDEX and check next 4 symbols - are they digits:
patindex('%23[0-9][0-9][0-9][0-9]%', #Value)
Eventually, you can store the result returned and check is there a match:
declare #Value varchar(50) = 'SS44\\230433\586'
declare #StartIndex int
set #StartIndex = patindex('%23[0-9][0-9][0-9][0-9]%', #Value)
select IIF(#StartIndex > 0, substring(#Value, #StartIndex, 6), null)

Extract number between two substrings in sql

I had a previous question and it got me started but now I'm needing help completing this. Previous question = How to search a string and return only numeric value?
Basically I have a table with one of the columns containing a very long XML string. There's a number I want to extract near the end. A sample of the number would be this...
<SendDocument DocumentID="1234567">true</SendDocument>
So I want to use substrings to find the first part = true so that Im only left with the number.
What Ive tried so far is this:
SELECT SUBSTRING(xml_column, CHARINDEX('>true</SendDocument>', xml_column) - CHARINDEX('<SendDocument',xml_column) +10087,9)
The above gives me the results but its far from being correct. My concern is that, what if the number grows from 7 digits to 8 digits, or 9 or 10?
In the previous question I was helped with this:
SELECT SUBSTRING(cip_msg, CHARINDEX('<SendDocument',cip_msg)+26,7)
and thats how I got started but I wanted to alter so that I could subtract the last portion and just be left with the numbers.
So again, first part of the string that contains the digits, find the two substrings around the digits and remove them and retrieve just the digits no matter the length.
Thank you all
You should be able to setup your SUBSTRING() so that both the starting and ending positions are variable. That way the length of the number itself doesn't matter.
From the sound of it, the starting position you want is right After the "true"
The starting position would be:
CHARINDEX('<SendDocument DocumentID=', xml_column) + 25
((adding 25 because I think CHARINDEX gives you the position at the beginning of the string you are searching for))
Length would be:
CHARINDEX('>true</SendDocument>',xml_column) - CHARINDEX('<SendDocument DocumentID=', xml_column)+25
((Position of the ending text minus the position of the start text))
So, how about something along the lines of:
SELECT SUBSTRING(xml_column, CHARINDEX('<SendDocument DocumentID=', xml_column)+25,(CHARINDEX('>true</SendDocument>',xml_column) - CHARINDEX('<SendDocument DocumentID=', xml_column)+25))
Have you tried working directly with the xml type? Like below:
DECLARE #TempXmlTable TABLE
(XmlElement xml )
INSERT INTO #TempXmlTable
select Convert(xml,'<SendDocument DocumentID="1234567">true</SendDocument>')
SELECT
element.value('./#DocumentID', 'varchar(50)') as DocumentID
FROM
#TempXmlTable CROSS APPLY
XmlElement.nodes('//.') AS DocumentID(element)
WHERE element.value('./#DocumentID', 'varchar(50)') is not null
If you just want to work with this as a string you can do the following:
DECLARE #SearchString varchar(max) = '<SendDocument DocumentID="1234567">true</SendDocument>'
DECLARE #Start int = (select CHARINDEX('DocumentID="',#SearchString)) + 12 -- 12 Character search pattern
DECLARE #End int = (select CHARINDEX('">', #SearchString)) - #Start --Find End Characters and subtract start position
SELECT SUBSTRING(#SearchString,#Start,#End)
Below is the extended version of parsing an XML document string. In the example below, I create a copy of a PLSQL function called INSTR, the MS SQL database does not have this by default. The function will allow me to search strings at a designated starting position. In addition, I'm parsing a sample XML string into a variable temp table into lines and only looking at lines that match my search criteria. This is because there may be many elements with the words DocumentID and I'll want to find all of them. See below:
IF EXISTS (select * from sys.objects where name = 'INSTR' and type = 'FN')
DROP FUNCTION [dbo].[INSTR]
GO
CREATE FUNCTION [dbo].[INSTR] (#String VARCHAR(8000), #SearchStr VARCHAR(255), #Start INT, #Occurrence INT)
RETURNS INT
AS
BEGIN
DECLARE #Found INT = #Occurrence,
#Position INT = #Start;
WHILE 1=1
BEGIN
-- Find the next occurrence
SET #Position = CHARINDEX(#SearchStr, #String, #Position);
-- Nothing found
IF #Position IS NULL OR #Position = 0
RETURN #Position;
-- The required occurrence found
IF #Found = 1
BREAK;
-- Prepare to find another one occurrence
SET #Found = #Found - 1;
SET #Position = #Position + 1;
END
RETURN #Position;
END
GO
--Assuming well formated xml
DECLARE #XmlStringDocument varchar(max) = '<SomeTag Attrib1="5">
<SendDocument DocumentID="1234567">true</SendDocument>
<SendDocument DocumentID="1234568">true</SendDocument>
</SomeTag>'
--Split Lines on this element tag
DECLARE #SplitOn nvarchar(25) = '</SendDocument>'
--Let's hold all lines in Temp variable table
DECLARE #XmlStringLines TABLE
(
Value nvarchar(100)
)
While (Charindex(#SplitOn,#XmlStringDocument)>0)
Begin
Insert Into #XmlStringLines (value)
Select
Value = ltrim(rtrim(Substring(#XmlStringDocument,1,Charindex(#SplitOn,#XmlStringDocument)-1)))
Set #XmlStringDocument = Substring(#XmlStringDocument,Charindex(#SplitOn,#XmlStringDocument)+len(#SplitOn),len(#XmlStringDocument))
End
Insert Into #XmlStringLines (Value)
Select Value = ltrim(rtrim(#XmlStringDocument))
--Now we have a table with multple lines find all Document IDs
SELECT
StartPosition = CHARINDEX('DocumentID="',Value) + 12,
--Now lets use the INSTR function to find the first instance of '">' after our search string
EndPosition = dbo.INSTR(Value,'">',( CHARINDEX('DocumentID="',Value)) + 12,1),
--Now that we know the start and end lets use substring
Value = SUBSTRING(value,(
-- Start Position
CHARINDEX('DocumentID="',Value)) + 12,
--End Position Minus Start Position
dbo.INSTR(Value,'">',( CHARINDEX('DocumentID="',Value)) + 12,1) - (CHARINDEX('DocumentID="',Value) + 12))
FROM
#XmlStringLines
WHERE Value like '%DocumentID%' --Only care about lines with a document id

Remove alphanumeric characters from the Varchar columns and then convert to Float

I have a Laboratory-Test table with 120 columns all with datatype varchar (which supposed to be FLOAT) but these columns also contain characters like ^,*,A-Z,a-z, commas, sentences with full stop "." at the end. I am using the following function to keep all the numeric values including ".".
The issue is this . (dot ), if I use #KeepValues as varchar(50) = '%[^0-9]%' then it will remove all the dots (e.g 1.05*L become 105) which is not something I want.
Could you please help me to resolved this would be very helpful or any alternative solution would be great
Create Function [dbo].[RAC]
(#Temp VarChar(1000))
Returns VarChar(1000)
AS
Begin
Declare #KeepValues as varchar(50) = '%[^0-9.]%'
While PatIndex(#KeepValues, #Temp) > 0
Set #Temp = Stuff(#Temp, PatIndex(#KeepValues, #Temp), 1, '')
Return #Temp
End
My T-SQL CASE statement is :
,CASE WHEN LTRIM(RTRIM(DBO.RAC([INR]))) NOT IN ('','.')
THEN round(AVG(NULLIF(CAST(DBO.RAC([INR]) as FLOAT), 0)), 2)
END AS [INR]
Since you have SQL2012, you can take advantage of the TRY_CONVERT() function
CREATE FUNCTION [dbo].[RAC] (#input varchar(max))
RETURNS TABLE AS
RETURN (
WITH number_list AS (SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 1)) i FROM sys.objects a)
SELECT TOP 1 TRY_CONVERT(float,LEFT(#input,i)) float_conversion
FROM number_list
WHERE i <= LEN(#input) AND TRY_CONVERT(float,LEFT(#input,i)) IS NOT NULL
ORDER BY i DESC
)
GO
If you have an actual number_list, which is very useful, use that instead.
DECLARE #table TABLE (data varchar(max))
INSERT #table VALUES
('123.124'),
('123.567 blah.'),
('123.567E10 blah.'),
('blah 45.2')
SELECT *
FROM #table
OUTER APPLY [dbo].[RAC](data) t
You need a somewhat basic Regular Expression that will allow you to get digits with a single decimal between two sets of digits (or perhaps digits with no decimal at all). This requires using SQLCLR for the RegEx function. You can find numerous examples of those, or you can use the freely available SQLCLR library SQL# (SQLsharp) (which I am the author of, but the function needed to answer this question is in the Free version).
DECLARE #Expression NVARCHAR(100) = N'\d+(\.\d+)?(e[-+]?\d+)?';
SELECT
SQL#.RegEx_MatchSimple(N'This is a test. Number here 1.05*L.',
#Expression, 1, 'IgnoreCase') AS [TheNumber],
CONVERT(FLOAT, SQL#.RegEx_MatchSimple(N'This is a test. Number here 1.05*L.',
#Expression, 1, 'IgnoreCase')) AS [Float],
CONVERT(FLOAT, SQL#.RegEx_MatchSimple(N'Another test. New number 1.05e4*L.',
#Expression, 1, 'IgnoreCase')) AS [Float2],
CONVERT(FLOAT, SQL#.RegEx_MatchSimple(N'One more test. Yup 1.05e-4*L.',
#Expression, 1, 'IgnoreCase')) AS [Float3]
/*
Returns:
TheNumber Float Float2 Float3
1.05 1.05 10500 0.000105
*/
The only issue with the pattern would be if there is another number in the text (you did say there are full sentences) prior to the one that you want. If you are 100% certain that the value you want will always have a decimal, you could use a simpler expression as follows:
\d+\.\d+(e[-+]?\d+)?
The regular expression allows for optional ( e / e+ / e- ) notation.
PATINDEX supports pattern matching, but only for T-SQL patterns and getting a pattern to do what you need may be impossible.
It sounds like you will need to use a regular expression for this you will need a CLR user defined function or you can do it using external to SQL Server by writing an app.
The marked answer to this question will help you get what you need.
Here is a copy of the code for ease of reference:
using System;
using System.Data;
using System.Text.RegularExpressions;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
public partial class UserDefinedFunctions
{
[Microsoft.SqlServer.Server.SqlFunction]
public static SqlString StripNonNumeric(SqlString input)
{
Regex regEx = new Regex(#"\D");
return regEx.Replace(input.Value, "");
}
};