Multi-character delimiter with STRING_SPLIT - sql-server

I want to split values based on multiple values like ! and %, operators, but STRING_SPLIT only allows 1-character delimiters.
My string is like this:
99001316 - ABCD 250 ML!%!%99001234 - CDEF 500 ML!%!%99001995 - OWEW 50 ML
And what I want is to separate the numeric and text value.
When I do this:
SELECT value FROM STRING_SPLIT(#x, '%',1)
I'm getting
value
99001234 - ABCD 250 ML!
!
99001230 - CDEF 500 ML!
!
99001995 - OWEW 50 ML
And what I want is
value
string
99001234
ABCD 250 ML!
99001230
CDEF 500 ML!
99001995
OWEW 50 ML

One option is to parse the string via JSON. You may notice the delimiter of '!%!%'
Declare #X varchar(max) = '99001316 - ABCD 250 ML!%!%99001230 - CDEF 500 ML!%!%99001395 - OWEW 50 ML'
Select Value = rtrim(left(value,charindex('-',Value+'-')-1))
,String = ltrim(substring(value,charindex('-',Value+'-')+1,len(Value)))
From OpenJSON( '["'+replace(string_escape(#X,'json'),'!%!%','","')+'"]' )
Resutls
Value String
99001316 ABCD 250 ML
99001230 CDEF 500 ML
99001395 OWEW 50 ML

Another way to do it.
SQL
DECLARE #tokens NVARCHAR(max) = N'99001316 - ABCD 250 ML!%!%99001234 - CDEF 500 ML!%!%99001995 - OWEW 50 ML';
WITH rs AS
(
SELECT value AS token
FROM STRING_SPLIT(REPLACE(#tokens,'!%!%','%') , '%')
)
SELECT *
, LEFT(token, pos - 1) AS [value]
, RIGHT(token, LEN(token) - pos - 1) AS [string]
FROM rs
CROSS APPLY (SELECT CHARINDEX('-', token)) AS t(pos);
Output
+------------------------+-----+-----------+-------------+
| token | pos | value | string |
+------------------------+-----+-----------+-------------+
| 99001316 - ABCD 250 ML | 10 | 99001316 | ABCD 250 ML |
| 99001234 - CDEF 500 ML | 10 | 99001234 | CDEF 500 ML |
| 99001995 - OWEW 50 ML | 10 | 99001995 | OWEW 50 ML |
+------------------------+-----+-----------+-------------+

Here are some more ideas
DECLARE #X VARCHAR(MAX) = '99001316 - ABCD 250 ML!%!%99001230 - CDEF 500 ML!%!%99001395 - OWEW 50 ML';
WITH s1 AS (
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS ord1
, s1.value
FROM STRING_SPLIT(REPLACE(#X, '!%!%', '|'), '|') s1
),
s2 AS (
SELECT s1.ord1
, ROW_NUMBER() OVER (PARTITION BY s1.ord1 ORDER BY (SELECT 1)) AS ord2
, s2.value
FROM s1
CROSS APPLY STRING_SPLIT(REPLACE(s1.value, ' - ', '|'), '|') s2
)
SELECT MAX(CASE WHEN s2.ord2 = 1 THEN s2.value END) AS id
, MAX(CASE WHEN s2.ord2 = 2 THEN s2.value END) AS text
FROM s2
GROUP BY s2.ord1
You can include ordinal column with STRING_SPLIT on Azure (passing 1 in 3-rd parameter) and remove the ROW_NUMBER usage for both ord columns above.

Related

Denomination distribution calculation

Some background story: Company A gives out vouchers to winners of a challenge. The SQL that I am currently writing needs to decide the required voucher denomination that sums to the value awarded to a person. I have a table that stores the denominations available for vouchers, depending on the country and currency.
In the example below, a particular person is awarded with €80 worth of vouchers.
The query below displays results of a lookup table for voucher denominations available for a particular country.
SELECT * FROM tblDenominationScheme WHERE CountryCode IN ('AT', 'US')
Result:
No. | CountryCode | VoucherName | VoucherValue
-------------------------------------------------
1 | AT | €50 Shop A | 50
2 | AT | €25 Shop A | 25
3 | AT | €15 Shop A | 15
4 | AT | €10 Shop A | 10
5 | US | $50 Store B | 50
6 | US | $10 Store B | 10
7 | US | $5 Store B | 5
My current SQL is as below to determine the required voucher denominations for €80 voucher:
DECLARE #CountryCode1 VARCHAR(2) = 'AT'
DECLARE #ChallengerID INT = 1172
DECLARE #RoundedAmount1 INT = 80
DECLARE #Vouchers INT
DECLARE #AmountAwarded INT = 0
SET #AmountAwarded = #RoundedAmount1
DROP TABLE IF EXISTS #tempVoucher
CREATE TABLE #tempVoucher
(
CountryCode VARCHAR(2),
ChallengerID INT,
AmountAwarded INT,
Vouchers INT,
)
WHILE (#RoundedAmount1 > 0)
BEGIN
SET #Vouchers = 0
SELECT TOP 1 #Vouchers = VoucherValue FROM tblDenominationScheme WHERE CountryCode = #CountryCode1 AND VoucherValue <= #RoundedAmount1 ORDER BY VoucherValue DESC
IF (#Vouchers > 0)
BEGIN
SET #RoundedAmount1 = #RoundedAmount1 - #Vouchers
END
ELSE
BEGIN
SELECT TOP 1 #Vouchers = VoucherValue FROM tblDenominationScheme WHERE CountryCode = #CountryCode1 ORDER BY VoucherValue
SET #RoundedAmount1 = #RoundedAmount1 - #RoundedAmount1
END
INSERT INTO #tempVoucher VALUES (#CountryCode1,#ChallengerID, #AmountAwarded, #Vouchers)
END
SELECT * FROM #tempVoucher
Result from the SQL above:
No. | CountryCode | ChallengerID | AmountAwarded | Vouchers
--------------------------------------------------------------
1 | AT | 1172 | 80 | 50
2 | AT | 1172 | 80 | 25
3 | AT | 1172 | 80 | 10
NOTE: The value in AmountAwarded column will be the same for all 3 rows. The amount in the Vouchers column for the 3 rows should sum up to 80.
The result above is obviously incorrect, because if you sum up the values in the Vouchers column, it gives you 85, which is 5 more than the AmountAwarded
Expected result (or at least closest):
No. | CountryCode | ChallengerID | AmountAwarded | Vouchers
--------------------------------------------------------------
1 | AT | 1172 | 80 | 50
2 | AT | 1172 | 80 | 10
3 | AT | 1172 | 80 | 10
4 | AT | 1172 | 80 | 10
Anyone able to help?
This might be an expensive query, but gets you different options to deliver up to 7 vouchers to get you the expected result. This, however, will generate a huge amount of reads if the rows increase or the amount of vouchers can be greater.
DECLARE #CountryCode1 VARCHAR(2) = 'AT'
DECLARE #RoundedAmount1 INT = 80;
WITH cteDenominations AS(
SELECT No, VoucherValue
FROM tblDenominationScheme
WHERE CountryCode = #CountryCode1
UNION ALL
SELECT 10000, 0
),
ctePermutations AS(
SELECT a.No AS a_No,
a.VoucherValue AS a_Value,
b.No AS b_No,
b.VoucherValue AS b_Value,
c.No AS c_No,
c.VoucherValue AS c_Value,
d.No AS d_No,
d.VoucherValue AS d_Value,
e.No AS e_No,
e.VoucherValue AS e_Value,
f.No AS f_No,
f.VoucherValue AS f_Value,
g.No AS g_No,
g.VoucherValue AS g_Value,
ROW_NUMBER() OVER(ORDER BY a.No, b.No, c.No, d.No) Permutation
FROM cteDenominations a
JOIN cteDenominations b ON a.VoucherValue >= b.VoucherValue
JOIN cteDenominations c ON b.VoucherValue >= c.VoucherValue
JOIN cteDenominations d ON c.VoucherValue >= d.VoucherValue
JOIN cteDenominations e ON d.VoucherValue >= e.VoucherValue
JOIN cteDenominations f ON e.VoucherValue >= f.VoucherValue
JOIN cteDenominations g ON f.VoucherValue >= g.VoucherValue
WHERE #RoundedAmount1 = a.VoucherValue
+ b.VoucherValue
+ c.VoucherValue
+ d.VoucherValue
+ e.VoucherValue
+ f.VoucherValue
+ g.VoucherValue
)
SELECT Permutation,
u.No,
u.VoucherValue
FROM ctePermutations
CROSS APPLY (VALUES(a_No, a_Value),
(b_No, b_Value),
(c_No, c_Value),
(d_No, d_Value),
(e_No, e_Value),
(f_No, f_Value),
(g_No, g_Value))u(No, VoucherValue)
WHERE VoucherValue > 0
AND Permutation = 1 --Remove this to get all possibilities
;
Looks like you need to solve a equation:
80 = n1*v1 + k2*n2...
where v1,v2 ... are values which you store in database
And you need to find n1, n2 ... , which are in {0, N}
There is no way how to implement it in SQL. Except - over all possible values, but it's not the smarter way.
Also, see this info:
https://math.stackexchange.com/questions/431367/solving-a-first-order-diophantine-equation-with-many-terms
Logic
Find the largest amount (that is less than or equal to starting amount) vouchers of 1 denomination can make.
Subtract this value from starting amount to get remainder,
Find the largest amount (that is less than or equal to remainder) a number of vouchers of 1 smaller denomination can make.
Subtract this value from previous remainder.
Go back to step 3
Features:
Handles multiple best combinations.
Small number of combinations are searched.
On my laptop: 100 runs take about 3 seconds
Notes
Performance may be improved by saving output of VoucherCombinations to a table variable and then using it in subsequent CTEs.
Code:
DECLARE #Vouchers TABLE( CountryCode CHAR( 2 ), VoucherValue DECIMAL( 10, 2 ))
INSERT INTO #Vouchers VALUES( 'AT', 50 ), ( 'AT', 40 ), ( 'AT', 25 ), ( 'AT', 20 ), ( 'AT', 15 ), ( 'AT', 10 ), ( 'US', 50 ), ( 'US', 10 ), ( 'US', 5 );
-- Small number table
-- Limits maximum count of Vouchers of a given denomination.
DECLARE #Numbers TABLE( Num INT )
INSERT INTO #Numbers VALUES( 1 ), ( 2 ), ( 3 ), ( 4 ), ( 5 ), ( 6 ), ( 7 ), ( 8 ), ( 9 ), ( 10 )
DECLARE #TargetAmount DECIMAL( 10, 2 ) = 60;
DECLARE #CountryCode CHAR( 2 ) = 'AT';
;WITH VoucherCombinations
AS (
-- Anchor
SELECT ROW_NUMBER() OVER( ORDER BY VoucherValue DESC ) AS ParentGroupID,
ROW_NUMBER() OVER( ORDER BY VoucherValue DESC ) AS SubGroupID,
1 AS IterationID,
VoucherValue, Num AS VoucherCumulativeCount,
CAST( VoucherValue * Num AS DECIMAL( 10, 2 )) AS TotalDenominationValue,
CAST( #TargetAmount - ( VoucherValue * Num ) AS DECIMAL( 10, 2 )) AS Remainder
FROM #Vouchers
-- Find the largest amount a given Voucher denomination can produce that is less than or equal to #TargetAmount
INNER JOIN #Numbers ON ( VoucherValue * Num ) <= #TargetAmount AND #TargetAmount - ( VoucherValue * Num ) < VoucherValue
WHERE CountryCode = #CountryCode
UNION ALL
-- Recursive query
SELECT SubGroupID,
SubGroupID * 10 + ROW_NUMBER() OVER( ORDER BY V.VoucherValue DESC ) AS SubGroupID,
IterationID + 1,
V.VoucherValue, VoucherCumulativeCount + N.Num AS VoucherCount,
CAST( V.VoucherValue * N.Num AS DECIMAL( 10, 2 )) AS TotalDenominationValue,
CAST( Remainder - ( V.VoucherValue * N.Num ) AS DECIMAL( 10, 2 )) AS Remainder
FROM VoucherCombinations AS VP
-- For each denomination look at the smaller denominations
INNER JOIN #Vouchers AS V ON VP.VoucherValue > V.VoucherValue
INNER JOIN #Numbers AS N ON V.VoucherValue * N.Num <= Remainder AND Remainder - ( V.VoucherValue * N.Num ) < V.VoucherValue
WHERE CountryCode = #CountryCode
),
-- Discard invalid combinations i.e. remainder is not 0
VoucherPoolValid AS(
SELECT *, DENSE_RANK() OVER( ORDER BY VoucherCumulativeCount ASC ) AS BestCombos
FROM VoucherCombinations
WHERE Remainder = 0
),
-- Find best combinations i.e. smallest number of Vouchers; Note: logic supports having more than 1 best combination
VoucherPoolBestCombos AS(
SELECT *, ROW_NUMBER() OVER( ORDER BY BestCombos ASC ) AS ComboID
FROM VoucherPoolValid
WHERE BestCombos = 1
),
-- Return all denominations for each combination
VoucherPoolAllDetails AS(
SELECT *
FROM VoucherPoolBestCombos
UNION ALL
SELECT Parent.*, BestCombos, ComboID
FROM VoucherPoolAllDetails AS Child
INNER JOIN VoucherCombinations AS Parent ON Child.ParentGroupID = Parent.SubGroupID
WHERE Child.SubGroupID <> Child.ParentGroupID
)
SELECT * FROM VoucherPoolAllDetails
ORDER BY ComboID

Recursively replace string using a CTE

I am having lookup_table as following:-
| id | Val |
+------+-----+
| 1 | A |
| 11 | B |
| 111 | C |
| 1111 | D |
I am creating words using the values from lookup_table like $id! and saving it into another table.
example : bad - $11!$1!$1111!
So my data_table will be something like,
| expression |
+-----------------+
| $111!$1!$11! | -- cab
| $1111!$1!$1111! | -- dad
| $11!$1!$1111! | -- bad
I want to reverse-build the word from the data_table.
What I've tried: used CHARINDEX on $ and ! to get the first id from expression and tried to replace it with matching val from look_up table recursively using CTE. I was not getting the exact result I was getting, but with some filetring, I got something close.
The query I've tried :
;WITH cte AS
(SELECT replace(expression,'$' + CONVERT(varchar(10),id) + '!' ,val) AS 'expression',
cnt =1
FROM data_table
JOIN lookup_table ON id =
SUBSTRING(
SUBSTRING(expression, CHARINDEX('$', expression) + 1, LEN(expression) - CHARINDEX('$', expression)), 1, CHARINDEX('!', expression) - 2)
UNION ALL
SELECT replace(expression,'$' + CONVERT(varchar(10),id) + '!' ,val) AS 'expression' ,
cnt = cnt +1
FROM cte
JOIN lookup_table ON id =
SUBSTRING(
SUBSTRING(expression, CHARINDEX('$', expression) + 1, LEN(expression) - CHARINDEX('$', expression)), 1, CHARINDEX('!', expression) - (cnt +2))
WHERE CHARINDEX('$', expression) > 0 )
SELECT expression
FROM cte
WHERE CHARINDEX('$', expression) = 0
Current output :
| expression |
+------------+
| DAD |
| CAB |
Expected output:
| expression |
+------------+
| DAD |
| CAB |
| BAD |
fiddle
What am I doing wrong?
Edit: There was a typo in the data setup in fiddler. d in bad was having five 1s instead of four. Thanks, DarkRob for pointing it out.
You may try this. Instead of using recursive cte you may use multiple cte to create your expression. Since sql already introduced this string_split function to convert your row cell into rows on particular delimeter, this will make our work lot easier.
First we convert each cell value into individual rows. Further we can easily get our expression word by using inner join with lookup table. At the last using stuff we'll again get our string as we need.
;WITH CTE AS (
SELECT ROW_NUMBER() OVER (ORDER BY EXPRESSION) AS SLNO, * FROM data_table
),
CT AS (
SELECT *, REPLACE(VALUE,'$','') AS NEWVAL
FROM CTE CROSS APPLY string_split(EXPRESSION,'!') WHERE VALUE <> ''
),
CTFINAL AS (
SELECT * FROM CT INNER JOIN lookup_table AS LT ON CT.NEWVAL=LT.id
)
--SELECT * FROM CTFINAL
SELECT DISTINCT SLNO,
STUFF( (SELECT '' + VAL + '' FROM CTFINAL AS CFF WHERE CFF.SLNO=CF.SLNO FOR XML PATH('')), 1,0,'') AS MYVAL
FROM CTFINAL AS CF

How to extract text from string between known characters, if characters appear more than once

I have text, that include for example #(sharp) character. String contains parameters. Parameters begin with # and end with #.
declare #TEXT varchar(200) = 'Dear #NAMEOFGUEST# , we glad to see youSOMEHOTEL tomorrow.'
declare #scanChar char(1)='#'
select
SUBSTRING(#TEXT, CHARINDEX(#scanChar, #TEXT) + 1, (((LEN(#TEXT)) - CHARINDEX(#scanChar, REVERSE(#TEXT))) - CHARINDEX(#scanChar, #TEXT)))
Return:
NAMEOFGUEST
It's the correct result.
When string contains only one parameter #NAMEOFGUEST# it works. If we add SOMEHOTEL to into the #, as #SOMEHOTEL# result is not as we want.
declare #TEXT varchar(200) = 'Dear #NAMEOFGUEST# , we glad to see you #SOMEHOTEL# tomorrow.'
declare #scanChar char(1)='#'
Returns:
NAMEOFGUEST# , we glad to see you #SOMEHOTEL
I want the same result as in previous, like NAMEOFGUEST only.
Using CHARINDEX(#FindString, #PrintData, CHARINDEX(#FindString, #PrintData) + 1) you can find the second occurences of the #, then based on that the remaining calculation can be done.
The following query will work.
DECLARE #PrintData AS VARCHAR (200) = 'Dear #NAMEOFGUEST# , we glad to see you #SOMEHOTEL# tomorrow.';
DECLARE #FindString AS CHAR (1) = '#';
DECLARE #LenFindString AS INT = LEN(#FindString);
SELECT SUBSTRING(#PrintData,
CHARINDEX(#FindString, #PrintData) + #LenFindString,
CHARINDEX(#FindString, #PrintData, CHARINDEX(#FindString, #PrintData) + 1) - (CHARINDEX(#FindString, #PrintData) + #LenFindString)
);
Demo on db<>fiddle
You can use a recursive approach like this:
A mockup-table to simulate a set oriented scenario
declare #tbl TABLE(ID INT IDENTITY, SomeComment VARCHAR(100),SomeString varchar(200));
INSERT INTO #tbl VALUES('3 Terms','Dear #NAMEOFGUEST# , we glad to see you #SOMEHOTEL# tomorrow. And even #AThirdOne# is here.')
,('1 Term','Dear #NAMEOFGUEST# , we glad to see you soon.')
,('No Term','Dear Guest, nice to see you.')
,('invalid 1','Dear Guest, nice to #see you.')
,('invalid ?','Dear #Guest, nice# to see you.');
declare #scanChar char(1)='#';
--the query
WITH recCTE AS
(
SELECT t.ID
,t.SomeComment
,t.SomeString AS TextToWork
,1 AS TermIndex
,D.*
FROM #tbl t
OUTER APPLY(SELECT CHARINDEX(#scanChar,t.SomeString)) A(StartingAt)
OUTER APPLY(SELECT CHARINDEX(#scanChar,t.SomeString,A.StartingAt+1)) B(EndingAt)
OUTER APPLY(SELECT CASE WHEN A.StartingAt>0 AND B.EndingAt >0 THEN SUBSTRING(t.SomeString,A.StartingAt+1,B.EndingAt- A.StartingAt-1) END) C(TermCandidate)
OUTER APPLY(SELECT A.StartingAt,B.EndingAt,C.TermCandidate,SUBSTRING(t.SomeString,B.EndingAt+1,1000) AS RestString) D
UNION ALL
SELECT t.ID
,t.SomeComment
,t.RestString
,t.TermIndex+1
,D.*
FROM recCTE t
OUTER APPLY(SELECT CHARINDEX(#scanChar,t.RestString)) A(StartingAt)
OUTER APPLY(SELECT CHARINDEX(#scanChar,t.RestString,A.StartingAt+1)) B(EndingAt)
OUTER APPLY(SELECT CASE WHEN A.StartingAt>0 AND B.EndingAt >0 THEN SUBSTRING(t.RestString,A.StartingAt+1,B.EndingAt- A.StartingAt-1) END) C(TermCandidate)
OUTER APPLY(SELECT A.StartingAt,B.EndingAt,C.TermCandidate,SUBSTRING(t.RestString,B.EndingAt+1,1000) AS RestString) D
WHERE (LEN(t.RestString) - LEN(REPLACE(t.RestString,#scanChar,'')))%2=0 AND CHARINDEX(#scanChar,t.RestString)>0
)
SELECT ID
,SomeComment
,TermIndex
--this will exclude "Guest, nice" due to the blank
,CASE WHEN CHARINDEX(' ',TermCandidate)>0 THEN NULL ELSE TermCandidate END AS Term
FROM recCTE
ORDER BY ID,TermIndex;
The result
+----+-------------+-----------+-------------+
| ID | SomeComment | TermIndex | Term |
+----+-------------+-----------+-------------+
| 1 | 3 Terms | 1 | NAMEOFGUEST |
+----+-------------+-----------+-------------+
| 1 | 3 Terms | 2 | SOMEHOTEL |
+----+-------------+-----------+-------------+
| 1 | 3 Terms | 3 | AThirdOne |
+----+-------------+-----------+-------------+
| 2 | 1 Term | 1 | NAMEOFGUEST |
+----+-------------+-----------+-------------+
| 3 | No Term | 1 | NULL |
+----+-------------+-----------+-------------+
| 4 | invalid 1 | 1 | NULL |
+----+-------------+-----------+-------------+
| 5 | invalid ? | 1 | NULL |
+----+-------------+-----------+-------------+

Checking Palindrome

We have a string variable where we capture string listed below:
String-like >>
Temp Table Temp | Temp1 Table1 Temp1 | Temp2 Table2 Temp2 | ABD EFG
EFG
Now we need to check, in this particular string how many Palindromes exists.
So, can you help me with this, that how may I fetch the number of Palindrome counts exists.
Note: "|" this pipeline exists after every successful string completion.
Answer should be: 3
The query which I have written, I used Reverse() / Replace() functions but not able to understand how to split the string after every pipeline symbol.
So, please help me in doing that, I am a beginner in SQL Server.
It seems you are confusing your requirement with searching for palindromes, so I have put together a solution to your question as well as a few methods should anyone else come across this question looking for and answer relating to actual palindromes:
Answer to your question as it is here
To do this, you can split your string on the delimiter and then split the result again on the spaces (I have included the function I've used here at the end). With this ordered list of words, you can compare the words in order to the words in reverse order to see if they are the same:
declare #s nvarchar(100) = 'Temp Table Temp | Temp1 Table1 Temp1 | Temp2 Table2 Temp2 | ABD EFG EFG';
with w as
(
select s.item as s
,ss.rn
,row_number() over (partition by s.item order by ss.rn desc) as rrn
,ss.item as w
from dbo.fn_StringSplit4k(#s,'|',null) as s
cross apply dbo.fn_StringSplit4k(ltrim(rtrim(s.item)),' ',null) as ss
)
select w.s
,case when sum(case when w.w = wr.w then 1 else 0 end) = max(w.rn) then 1 else 0 end as p
from w
join w as wr
on w.s = wr.s
and w.rn = wr.rrn
group by w.s
order by w.s
Which outputs:
+----------------------+---+
| s | p |
+----------------------+---+
| ABD EFG EFG | 0 |
| Temp1 Table1 Temp1 | 1 |
| Temp2 Table2 Temp2 | 1 |
| Temp Table Temp | 1 |
+----------------------+---+
Solution for actual palindromes
Firstly to check if a string value is a proper palindrome (ie: spelled the same forwards and backwards) this is a trivial comparison of the original string with it's reverse value, which in the example below correctly outputs 1:
declare #p nvarchar(100) = 'Temp Table elbaT pmeT';
select case when #p = reverse(#p)
then 1
else 0
end as p
To do this across a set of delimited values within the same string, you should firstly feel bad for storing your data in a delimited string within your database and contemplate why you are doing this. Seriously, it's incredibly bad design and you should fix it as soon as possible. Once you have done that you can apply the above technique.
It that is genuinely unavoidable however, you can split your string using one of many set based table valued functions and then apply the above operation on the output:
declare #ps nvarchar(100) = 'Temp Table elbaT pmeT | Temp1 Table1 1elbaT 1pmeT | Temp2 Table2 Temp2 | ABD EFG EFG';
select ltrim(rtrim(s.item)) as s
,case when ltrim(rtrim(s.item)) = reverse(ltrim(rtrim(s.item))) then 1 else 0 end as p
from dbo.fn_StringSplit4k(#ps,'|',null) as s
Which outputs:
+---------------------------+---+
| s | p |
+---------------------------+---+
| Temp Table elbaT pmeT | 1 |
| Temp1 Table1 1elbaT 1pmeT | 1 |
| Temp2 Table2 Temp2 | 0 |
| ABD EFG EFG | 0 |
+---------------------------+---+
String split function
create function [dbo].[fn_StringSplit4k]
(
#str nvarchar(4000) = ' ' -- String to split.
,#delimiter as nvarchar(1) = ',' -- Delimiting value to split on.
,#num as int = null -- Which value to return, null returns all.
)
returns table
as
return
-- Start tally table with 10 rows.
with n(n) as (select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1)
-- Select the same number of rows as characters in #str as incremental row numbers.
-- Cross joins increase exponentially to a max possible 10,000 rows to cover largest #str length.
,t(t) as (select top (select len(isnull(#str,'')) a) row_number() over (order by (select null)) from n n1,n n2,n n3,n n4)
-- Return the position of every value that follows the specified delimiter.
,s(s) as (select 1 union all select t+1 from t where substring(isnull(#str,''),t,1) = #delimiter)
-- Return the start and length of every value, to use in the SUBSTRING function.
-- ISNULL/NULLIF combo handles the last value where there is no delimiter at the end of the string.
,l(s,l) as (select s,isnull(nullif(charindex(#delimiter,isnull(#str,''),s),0)-s,4000) from s)
select rn
,item
from(select row_number() over(order by s) as rn
,substring(#str,s,l) as item
from l
) a
where rn = #num
or #num is null;

How can I get multiple columns values into single row in oracle?

My exact requirement is that if the output of the query 'select amount, quantity from temp_table where type = 5;' is:
amount | quantity
10 | 5
20 | 7
12 | 10
Then, the output should be displayed as:
amount1 | amount2 | amount3 | quantity1 | quantity2 | quantity3
10 | 20 | 12 | 5 | 7 | 10
A possible solution might be:
SELECT LISTAGG(amount, '|') WITHIN GROUP (order by amount)
|| LISTAGG(quantity, '|') WITHIN GROUP (order by amount) as result
FROM temp_table where type = 5;
*Have in mind, that the values of the columns amount and quantity are separated by spaces, thus the ' ' in the listagg() expression. You can change it to '|' or anything else if you like.
Cheers
Use a PIVOT:
SELECT "1_AMOUNT" AS Amount1,
"2_AMOUNT" AS Amount2,
"3_AMOUNT" AS Amount3,
"4_AMOUNT" AS Amount4,
"5_AMOUNT" AS Amount5,
"1_QUANTITY" AS Quantity1,
"2_QUANTITY" AS Quantity2,
"3_QUANTITY" AS Quantity3,
"4_QUANTITY" AS Quantity4,
"5_QUANTITY" AS Quantity5
FROM ( SELECT amount, quantity, ROWNUM rn FROM temp_table WHERE type = 5 )
PIVOT ( MAX( amount ) AS amount,
MAX( quantity ) AS quantity
FOR rn IN ( 1, 2, 3, 4, 5 ) );

Resources