Return string of description given string of IDs (separated by commas) - sql

I have one table A that has a column C and a lookup table (lookup) that provides a description given an ID.
Here the setup:
table A with column C values:
1,2,3
2,3,4
table lookup:
1, 'This'
2, 'is'
3, 'tricky'
4, 'SQL'
Provide a SQL (SQL Server 2005) statement that returns the following strings:
Input: 1,2,3 Output: 'This','Is','tricky'
Input: 2,3,4 Output: 'Is','tricky','SQL'
basically turning the string of IDs (from an input table A) into a string of descriptions

The Samples that come with SQL Server 2005 include a CLR function called Split(). It's the best way of splitting comma-separated lists like this by far.
Suppose you have a table called inputs, with a column called input.
I forget what the particular outputs of dbo.Split() are... so work with me here. Let's call the fields id and val, where id tells us which entry it is in the list.
WITH
separated AS (
SELECT i.input, s.id, s.val
FROM
dbo.inputs AS i
CROSS APPLY
dbo.Split(i.input) AS s
)
, converted AS (
SELECT s.input, s.id, m.string
FROM
separated AS s
JOIN
dbo.mapping AS m
ON m.number = CAST(s.val AS varchar(5))
)
SELECT c.input, (SELECT string + ' ' FROM converted AS c2 WHERE c2.input = c.input ORDER BY id FOR XML PATH('')) AS converted_string
FROM converted AS c
GROUP BY c.input;

Related

concat two strings and put smaller string at first in sql server

for concating two varchars from columns A and B ,like "1923X" and "11459" with the hashtag, while I always want the smallest string become at first, what should I do in SQL server query?
inputs:
Two Columns
A="1923X"
B="11459"
procedure:
while we are checking two inputs from right to left, in this example the second character value in B (1) is smaller than the second character in A (9) so B is smaller.
result: new column C
"11459#1923X"
Original answer:
If you need to order the input strings, not only by second character, STRING_AGG() is also an option:
DECLARE #a varchar(5) = '1923X'
DECLARE #b varchar(5) = '11459'
SELECT STRING_AGG(v.String, '#') WITHIN GROUP (ORDER BY v.String) AS Result
FROM (VALUES (#a), (#b)) v (String)
Output:
Result
11459#1923X
Update:
You changed the requirements (now the strings are stored in two columns), so you need a different statement:
SELECT
A,
B,
C = (
SELECT STRING_AGG(v.String, '#') WITHIN GROUP (ORDER BY v.String)
FROM (VALUES (A), (B)) v (String)
)
FROM (VALUES ('1923X', '11459')) t (a, b)

DB2 : Find distinct from a comma separated values

Find distinct from a comma separated values in ANSI SQL. I am trying this on DB2 database.
Scenario
Id Val
1 A,B,C
2 A,D,A,C,B
3 B,A,C,C,D
Expected output
Id Val
1 A,B,C
2 A,D,C,B
3 B,A,C,D
DB2 offers a way to tokens strings using XML. Using this, you can split the string into tokens and then use listagg(distinct):
select v.id, listagg(distinct tokens.token, ',')
from (values (1, 'A,B,C'), (2, 'A,D,A,C,B'), (3, 'B,A,C,C,D')) v(id, val),
xmltable('for $id in tokenize($s, ",") return <i>{string($id)}</i>' passing v.val as "s"
columns seq for ordinality, token varchar(20) path '.'
) tokens
group by v.id;
Here is a db<>fiddle.
Note: I strongly recommend that you fix the data model. Storing multiple values in a string is bad way to store data in any database.
Depending on your platform and version of Db2, you may have the functions SPLIT() and LISTAGG() available.
with dist as (
select
distinct id, element
from tbl, table(split(val,','))
)
select
id
, listagg(element) within group (order by column_values)
as disinct_list
from dist
group by id
;
EDIT
corrected name of the column returned by SPLIT(), IBM provided version is ELEMENT we happened to have an older User Defined version that used COLUMN_VALUE.

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.

Return json list of values in SQL Server 2016 using data from 2 columns

I have a database with 2 columns
A B
-- --
X 1995
Y 2005
C 1962
D 2003
I'm trying to create a SQL statement that will take a string of comma delimited values and return a json list of values in B where any value in the string is in A
so if the comma delimited string was 'X,C' the json list would be [1995,1962]
I've been using json path to try this, but I can't get it exactly like I want it and I've been spinning my wheels for too long
This is what I've tried:
Select mt.B as json_list_b_values
From [dbo].[myTable] mt
Where mt.A in (Select value From String_Split('X,C', ',')) for json path
This is the ouput:
[ {"json_list_b_values":"1995"}, {"json_list_b_values":"1962"} ]
As you're on 2016 you can't use STRING_AGG, but you can use the old tried and tested FOR XML PATH and STUFF method:
DECLARE #list varchar(8000) = 'X,C';
WITH VTE AS(
SELECT *
FROM(VALUES('X',1995),
('Y',2005),
('C',1962),
('D',2003)) V(A,B))
SELECT '[' + STUFF((SELECT CONCAT(',',V.B)
FROM VTE V
CROSS APPLY STRING_SPLIT(#list,',') SS
WHERE SS.[value] = V.A
FOR XML PATH('')),1,1,'') + ']';
I am no SQL expert. So please bear with me.
Step 1 - Handle the CSV input
You are already doing this by using the IN clause. I would store these results in a table variable
Step 2 - Convert the query results to a simple JSON array
I can think of the function STRING_AGG(). This will concatenate the rows into a flat string.
E.g. Join the FirstName column into a comma delimited string
SELECT STRING_AGG ( ISNULL(FirstName,'N/A'), ',') AS csv
FROM Person.Person;
Will produce the following
John,N/A,Mike,Peter,N/A,N/A,Alice,Bob
Code snippet based on your example
I am using a table variable #result1 to hold the result from Step 1.
SELECT '[ "' + string_agg( json_list_b_values, '", "') + '" ]' FROM #result1
MSDN reference for STRING_AGG()
https://learn.microsoft.com/en-us/sql/t-sql/functions/string-agg-transact-sql?view=sql-server-2017
CORRECTION
The function STRING_AGG() is available in SQL 2017.

Select distinct values of comma-separated values column excluding subsets in PostgreSQL

Assume having a table foo with column bar that carries comma-separated values,
('a,b',
'a,b,c',
'a,b,c,d',
'd,e')
How can I select the largest combination and exclude all the subsets included in that combination (the largest one)?
Example on the above data-set. The result should be:
('a,b,c,d', 'd,e') and the first two entities ('a,b', 'a,b,c') are excluded as they are subset of ('a,b,c,d').
Taking in consideration that all the values in the comma-separated string are sorted alphabetically.
I tried the below query, but the results seem a little far away from what I need:
select distinct a.bar from foo a inner join foo b
on a.bar like '%'|| b.bar||'%'
and a.bar != b.bar
You can use string_to_array() to split the strings into an array. With the contains operator, #>, you can check whether an array contains another. (See "9.18. Array Functions and Operators".)
Use that in a NOT EXISTS clause. fi.ctid <> fo.ctid is there to make sure the physical addresses of the compared pair of rows is not equal, as of course an array of one row would contain the array compared to the same row.
SELECT fo.bar
FROM foo fo
WHERE NOT EXISTS (SELECT *
FROM foo fi
WHERE fi.ctid <> fo.ctid
AND string_to_array(fi.bar, ',') #> string_to_array(fo.bar, ','));
SQL Fiddle
But: Don't use comma-separated strings in a relational database. You've got something way better. It's called "table".
First process the string into sets of characters, and then cross join the character-sets with itself, excluding rows where the character-sets on both sides are the same.
Next, aggregate and use BOOL_OR in a HAVING clause to filter out any character-set that is a subset of any other character-set.
With a sample table declared in the CTE, the query becomes:
WITH foo(bar) AS (SELECT '("a,b" , "a,b,c" , "a,b,c,d" , "d,e")'::TEXT)
SELECT bar, string_to_array(elems[1], ',') not_subset
FROM foo
CROSS JOIN regexp_matches(bar, '[\w|,]+', 'g') elems
CROSS JOIN regexp_matches(bar, '[\w|,]+', 'g') elems2
WHERE elems2[1] != elems[1]
-- my regex also matches the ',' between sets which need to be ignored
-- alternatively, i have to refine the regex
AND elems2[1] != ','
AND elems[1] != ','
GROUP BY 1, 2
HAVING NOT BOOL_OR(string_to_array(elems[1], ',') <# string_to_array(elems2[1], ','))
produces the output
bar not_subset
'("a,b" , "a,b,c" , "a,b,c,d" , "d,e")' {'d','e'}
'("a,b" , "a,b,c" , "a,b,c,d" , "d,e")' {'a','b','c','d'}
Example in SQL Fiddle