I have a string like this:)
"<"FOSTIMON 75 M.J.>|4|4|4|3|3|3|3|3|3|3|3|3||||||||||||||||||||||||||||"
I need the sum of the numbers (4+4+4+3+...+3), expected result = 39
Thank you for the help!
THIS ANSWERS THE ORIGINAL QUESTION BEFORE EDITING.
Most databases support executing some form of prepared statement. In your case, probably the simplest method is to construct a SQL statement and then execute it dynamically.
The syntax for this varies dramatically from database to database. Out of randomness, I'm choosing SQL Server, but the functionality (although not the syntax) is available in almost any database:
declare #str nvarchar(max) = '|4|4|4|3|3|3|3|3|3|3|3|3||||||||||||||||||||||||||||';
set #str = 'select ' + replace(#str, '|', '+') + ' + 0';
exec sp_executesql #str;
Here is a rextester for this particular version.
Note that this works because + is a unary (numeric) operator, analogous to - but it does not change the sign.
Try this
Assuming the first part of the string upto the first "|" can be ignored
Not sure what version of SQL you're using but if its less than SQL2016 then use this string splitter
DECLARE #s NVARCHAR(50) = '<"FOSTIMON 75 M.J.>|4|4|4|3|3|3|3|3|3|3|3|3||||||||||||||||||||||||||||'
SELECT
SUM(CONVERT(INT, S.[Value]))
FROM dbo.DelimitedSplit8K(#s, '|') S
WHERE S.RN >= 2 AND S.RN <= 13
If you're on SQL2012 + then you can utilise TRY_PARSE and get rid of the WHERE clause altogether
SELECT
SUM(
CASE
WHEN TRY_PARSE(S.[Value] AS INT) IS NOT NULL
THEN CONVERT(INT, S.[Value])
ELSE 0
END
--CONVERT(INT, S.[Value])
)
FROM dbo.DelimitedSplit8K(#s, '|') S
If you're using SQL2016 + then use the inbuilt function, String_Split
SELECT
SUM(
CASE
WHEN TRY_PARSE(S.[Value] AS INT) IS NOT NULL
THEN CONVERT(INT, S.[Value])
ELSE 0
END
)
FROM STRING_SPLIT(#s, '|') S
Related
I've got a string like AAAA.BBB.CCCC.DDDD.01.A and I'm looking to manipulate this and end up with AAAA-BBB
I've achieved this by writing this debatable piece of code
declare #string varchar(100) = 'AAAA.BBB.CCCC.DDDD.01.A'
select replace(substring(#string,0,charindex('.',#string)) + substring(#string,charindex('.',#string,CHARINDEX('.',#string)),charindex('.',#string,CHARINDEX('.',#string)+1)-charindex('.',#string)),'.','-')
Is there any other way to achieve this which is more elegant and readable ?
I was looking at some string_split operations, but can't wrap my head around it.
If you are open to some JSON transformations, the following approach is an option. You need to transform the text into a valid JSON array (AAAA.BBB.CCCC.DDDD.01.A is transformed into ["AAAA","BBB","CCCC","DDDD","01","A"]) and get the required items from this array using JSON_VALUE():
Statement:
DECLARE #string varchar(100) = 'AAAA.BBB.CCCC.DDDD.01.A'
SET #string = CONCAT('["', REPLACE(#string, '.', '","'), '"]')
SELECT CONCAT(JSON_VALUE(#string, '$[0]'), '-', JSON_VALUE(#string, '$[1]'))
Result:
AAAA-BBB
Notes: With this approach you can easily access all parts from the input string by index (0-based).
I think this is a little cleaner:
declare #string varchar(100) = 'AAAA.BBB.CCCC.DDDD.01.A'
select
replace( -- replace '.' with '-' (A)
substring(#string, 1 -- in the substring of #string starting at 1
,charindex('.', #string -- and going through 1 before the index of '.'(B)
,charindex('.',#string)+1) -- that is after the first index of the first '.'
-1) -- (B)
,'.','-') -- (A)
Depending on what is in your string you might be able to abuse PARSENAME into doing it. Intended for breaking up names like adventureworks.dbo.mytable.mycolumn it works like this:
DECLARE #x as VARCHAR(100) = 'aaaa.bbb.cccc.ddddd'
SELECT CONCAT( PARSENAME(#x,4), '-', PARSENAME(#x,3) )
You could also look at a mix of STUFF to delete the first '.' and replace with '-' then LEFT the result by the index of the next '.' but it's unlikely to be neater than this or Kevin's proposal
Using string split would likely be as unwieldy:
SELECT CONCAT(MAX(CASE WHEN rn = 1 THEN v END), '-', MAX(CASE WHEN rn = 2 THEN v END))
FROM (
SELECT row_number () over (order by (select 0)) rn, value as v
FROM string_split(#x,'.')
) y WHERE rn IN (1,2)
Because the string is split to rows which then need to be numbered in order to filter and pull the parts you want. This also relies on the strings coming out of string split in the order they were in the original string, which MS do not guarantee will be the case
I've got a temporary table which I can select out of as follows:
select
testId
,testoutput
from
#testResults
This yields results such as the following:
ID Result
1 4303
2 -150000*200*3= -90000000
3 4.2016
4 3205000
The second value is due to some of our tests outputting the calculation rather than value for readability in later reports.
I'd like to turn that calculation back into a number, which I believe is doable via sp_executesql...
I've changed my select to:
select
testId
,case
WHEN isnumeric(trim(testoutput)) = 1 THEN pr.expectedOutput
WHEN CHARINDEX('=', testoutput) != 0 then
exec sp_executesql N'rtrim(left(testoutput, CHARINDEX(''='', testoutput) - 1))'
END
from
#testOuput
i.e. when there's an "=" sign, then split off what's the left of the sign and try to exec sp_executesql on it.
This throws all sorts of syntax errors, which I can't get my head around.
I'd really appreciate any help with the syntax of the sp_executesql
You can also extract the result of the calculation:
SQL Server without TRIM or TRY_PARSE
SELECT testId, CASE
WHEN ISNUMERIC(LTRIM(RTRIM(testoutput))) = 1 THEN pr.expectedOutput
WHEN CHARINDEX('=', testoutput) > 0 THEN
LTRIM(RTRIM(RIGHT(testoutput, CHARINDEX('=', REVERSE(testoutput))-1)))
END
FROM #testOuput
SQL Server with TRIM and TRY_PARSE
SELECT testId, CASE
WHEN TRY_PARSE(TRIM(testoutput) AS NUMERIC) IS NOT NULL THEN pr.expectedOutput
WHEN CHARINDEX('=', testoutput) > 0 THEN
TRIM(RIGHT(testoutput, CHARINDEX('=', REVERSE(testoutput))-1))
END
FROM #testOuput
demo on dbfiddle.uk
I have the following code in a stored procedure and am trying to conditionally format a calculated number based on its length (if the number is less than 4 digits, pad with leading zeros). However, my case statement is not working. The "formattedNumber2" result is the one I'm looking for.
I'm assuming the case statement treats the variable strangely, but I also don't know of a way around this.
DECLARE #Number int = 5
SELECT
CASE
WHEN (LEN(CONVERT(VARCHAR, #Number)) > 4)
THEN #Number
ELSE RIGHT('0000' + CAST(#Number AS VARCHAR(4)), 4)
END AS formattedNumber,
LEN(CONVERT(VARCHAR, #Number)) AS numberLength,
RIGHT('0000' + CAST(#Number AS VARCHAR(4)), 4) AS formattedNumber2
I get the following results when I run the query:
formattedNumber numberLength formattedNumber2
-------------------------------------------------
5 1 0005
SQL DEMO
The problem is you are using different data type on your case , integer and string. So the CASE stay with the first type he find and convert the rest.
CASE WHEN (LEN(convert(VARCHAR, #Number)) > 4) THEN convert(VARCHAR, #Number)
This can be done a lot easier with format() since version 2012.
format(n,
'0000')
And that would also handle negative values, which your current approach apparently doesn't.
Prior 2012 it can be handled with basically replicate() and + (string concatenation).
isnull(replicate('-',
-sign(n)), '')
+
isnull(replicate('0',
4
-
len(cast(abs(n) AS varchar(10)))
),
'')
+
cast(abs(n) AS varchar(10))
(It targets integer values, choose a larger length for the varchar casts for bigint.)
db<>fiddle
I have a string containing numbers delimited by a pipe like so 23|12|12|32|43.
Using SQL I want to extract each number, add 10 and then sum to get a total.
Here is another alternative:
declare #str nvarchar(max) = '23|12|12|32|43';
set #str = 'select '+replace(#str, '|', '+');
exec(#str);
The answer using a recursive common table expression:
WITH cte AS (
SELECT
'23|12|12|32|43' + '|' AS string
,0 AS total
UNION ALL
SELECT
RIGHT(string, LEN(string) - PATINDEX('%|%', string))
,CAST(LEFT(string, PATINDEX('%|%', string) - 1) AS INT) + 10
FROM cte
WHERE PATINDEX('%|%', string) > 0
)
SELECT SUM(total) AS total FROM cte
As the recursion terminator I have put in a check to see if any more pipes exist in the string, however this then missed the last element which I have got around by concatenating an extra pipe on to the end of my original string, I think there is probably a better way to express the WHERE clause.
Here is another way of doing it:
DECLARE #s VARCHAR(1000) = '23|12|12|32|43'
SELECT CAST('<root><e>' + REPLACE(#s, '|', '</e><e>') + '</e></root>' AS XML)
.value('sum(/root/e) + count(/root/e) * 10', 'INT')
This uses casting to XML data type and functions provided by it.
I posted this just as an example, your approach has a much better performance.
This question already has answers here:
How to concatenate text from multiple rows into a single text string in SQL Server
(47 answers)
Closed 7 years ago.
I have a sql function that includes this code:
DECLARE #CodeNameString varchar(100)
SELECT CodeName FROM AccountCodes ORDER BY Sort
I need to concatenate all results from the select query into CodeNameString.
Obviously a FOREACH loop in C# code would do this, but how do I do it in SQL?
If you're on SQL Server 2005 or up, you can use this FOR XML PATH & STUFF trick:
DECLARE #CodeNameString varchar(100)
SELECT
#CodeNameString = STUFF( (SELECT ',' + CodeName
FROM dbo.AccountCodes
ORDER BY Sort
FOR XML PATH('')),
1, 1, '')
The FOR XML PATH('') basically concatenates your strings together into one, long XML result (something like ,code1,code2,code3 etc.) and the STUFF puts a "nothing" character at the first character, e.g. wipes out the "superfluous" first comma, to give you the result you're probably looking for.
UPDATE: OK - I understand the comments - if your text in the database table already contains characters like <, > or &, then my current solution will in fact encode those into <, >, and &.
If you have a problem with that XML encoding - then yes, you must look at the solution proposed by #KM which works for those characters, too. One word of warning from me: this approach is a lot more resource and processing intensive - just so you know.
DECLARE #CodeNameString varchar(max)
SET #CodeNameString=''
SELECT #CodeNameString=#CodeNameString+CodeName FROM AccountCodes ORDER BY Sort
SELECT #CodeNameString
#AlexanderMP's answer is correct, but you can also consider handling nulls with coalesce:
declare #CodeNameString nvarchar(max)
set #CodeNameString = null
SELECT #CodeNameString = Coalesce(#CodeNameString + ', ', '') + cast(CodeName as varchar) from AccountCodes
select #CodeNameString
For SQL Server 2005 and above use Coalesce for nulls and I am using Cast or Convert if there are numeric values -
declare #CodeNameString nvarchar(max)
select #CodeNameString = COALESCE(#CodeNameString + ',', '') + Cast(CodeName as varchar) from AccountCodes ORDER BY Sort
select #CodeNameString
from msdn Do not use a variable in a SELECT statement to concatenate values (that is, to compute aggregate values). Unexpected query results may occur. This is because all expressions in the SELECT list (including assignments) are not guaranteed to be executed exactly once for each output row
The above seems to say that concatenation as done above is not valid as the assignment might be done more times than there are rows returned by the select
Here is another real life example that works fine at least with 2008 release (and later).
This is the original query which uses simple max() to get at least one of the values:
SELECT option_name, Field_M3_name, max(Option_value) AS "Option value", max(Sorting) AS "Sorted"
FROM Value_list group by Option_name, Field_M3_name
ORDER BY option_name, Field_M3_name
Improved version, where the main improvement is that we show all values comma separated:
SELECT from1.keys, from1.option_name, from1.Field_M3_name,
Stuff((SELECT DISTINCT ', ' + [Option_value] FROM Value_list from2
WHERE COALESCE(from2.Option_name,'') + '|' + COALESCE(from2.Field_M3_name,'') = from1.keys FOR XML PATH(''),TYPE)
.value('text()[1]','nvarchar(max)'),1,2,N'') AS "Option values",
Stuff((SELECT DISTINCT ', ' + CAST([Sorting] AS VARCHAR) FROM Value_list from2
WHERE COALESCE(from2.Option_name,'') + '|' + COALESCE(from2.Field_M3_name,'') = from1.keys FOR XML PATH(''),TYPE)
.value('text()[1]','nvarchar(max)'),1,2,N'') AS "Sorting"
FROM ((SELECT DISTINCT COALESCE(Option_name,'') + '|' + COALESCE(Field_M3_name,'') AS keys, Option_name, Field_M3_name FROM Value_list)
-- WHERE
) from1
ORDER BY keys
Note that we have solved all possible NULL case issues that I can think of and also we fixed an error that we got for numeric values (field Sorting).