Select from a comma separated list in a colum - sql

I have a table, Table 1,
and I want to select all regions that are neighbours of region 3. What would be my query for this? The NEIGHBOUR column is a CHAR column.
I know the table should not be set up this way, but that is what I have to work with, as I don't have the rights to the database.

Fix your data model! There are numerous reasons why this is broken.
But if you ware stuck with it, you can use:
select t.*
from t
where ',' + neighbor + ',' like '%,3,%';
You can also unnest the value using string_split():
select t.*
from t cross apply
string_split(t.neighbor, ',') s
where s.value = '3';

You can use STRING_SPLIT() as
SELECT *
FROM Data
WHERE Region IN
(
SELECT Value
FROM STRING_SPLIT((SELECT Neighbor FROM Data WHERE Region = 3), ',')
);
The query 'll returns 0 rows because there is no region in the table marked as a neighbor for region 3.
If you change (3, 'Name3', '5,8,12'), to (3, 'Name3', '1,2'),, then it'll returns the regions 1 and 2 because they 're neighbors of region 3.
Here is a db<>fiddle.
Another way without using a string splitter
SELECT *
FROM Data D
JOIN (VALUES((SELECT Neighbor FROM Data WHERE Region = 3))) T(V)
ON CONCAT(',', T.V, ',') LIKE CONCAT('%,', D.Region,',%');

Related

Split single row value to multiple rows in Snowflake

I have a table where the column data has a combination of values seperated by ';'. I would like to split them into rows for each column value.
Table data
Now I would like to split them into multiple rows for each value like
I have tried using the below SQL statement.
SELECT DISTINCT COL_NAME FROM "DB"."SCHEMA"."TABLE,
LATERAL FLATTEN(INPUT=>SPLIT(COL_NAME,';'))
But the output is not as expected. Attaching the query output below.
Basically the query does nothing to my data.
It could be achieved using SPLIT_TO_TABLE table function:
This table function splits a string (based on a specified delimiter) and flattens the results into rows.
SELECT *
FROM tab, LATERAL SPLIT_TO_TABLE(column_name, ';')
I was able to resolve this by using LATERAL FLATTERN like a joining table and selecting the value from it.
SELECT DISTINCT A.VALUE AS COL_NAME
FROM "DB"."SCHEMA"."TABLE",
LATERAL SPLIT_TO_TABLE(COL_NAME,';')A
Looks your data has multiple delimiters , We can leverage STRTOK_SPLIT_TO_TABLE function using multiple delimiters..
STRTOK_SPLIT_TO_TABLE
WITH data AS (
SELECT *
FROM VALUES
('Greensboro-High Point-Winston-Salem;Norfolk-Portsmouth-Newport News Washington, D.C. Roanoke-Lynchburg Richmond-Petersburg')
v( cities))
select *
from data, lateral strtok_split_to_table(cities, ';-')
order by seq, index;
Result:
Your first attempt was very close, you just need to access the out of the flatten, instead of the input to the flatten
so using this CTE for data:
WITH fake_data AS (
SELECT *
FROM VALUES
('Greensboro-High Point-Winston-Salem;Norfolk-Portsmouth-Newport News;Washington, D.C.;Roanoke-Lynchburg;Richmond-Petersburg'),
('Knoxville'),
('Knoxville;Memphis;Nashville')
v( COL_NAME)
)
if you had aliased you tables, and accessed the parts.
SELECT DISTINCT f.value::text as col_name
FROM fake_data d,
LATERAL FLATTEN(INPUT=>SPLIT(COL_NAME,';')) f
;
which is what you did in your provided answer, but via SPLIT_TO_TABLE
SELECT DISTINCT f.value as col_name
FROM fake_data d,
TABLE(SPLIT_TO_TABLE(COL_NAME,';')) f
;
STRTOK_SPLIT_TO_TABLE also is the same thing:
SELECT DISTINCT f.value as col_name
FROM fake_data d,
TABLE(strtok_split_to_table(COL_NAME,';')) f
;
Which can also be done via a strtok_to_array and FLATTEN that
SELECT DISTINCT f.value as col_name
FROM fake_data d,
TABLE(FLATTEN(input=>STRTOK_TO_ARRAY(COL_NAME,';'))) f
;
COL_NAME
Greensboro-High Point-Winston-Salem
Norfolk-Portsmouth-Newport News
Washington, D.C.
Roanoke-Lynchburg
Richmond-Petersburg
Knoxville
Memphis
Nashville

How to select 2 cross split string column in single query

CREATE TABLE #StudentClasses
(
ID INT,
Student VARCHAR(100),
Classes VARCHAR(100),
CCode VARCHAR(30)
)
GO
INSERT INTO #StudentClasses
SELECT 1, 'Mark', 'Maths,Science,English', 'US,UK,AUS'
UNION ALL
SELECT 2, 'John', 'Science,English', 'BE,DE'
UNION ALL
SELECT 3, 'Robert', 'Maths,English', 'CA,IN'
GO
SELECT *
FROM #StudentClasses
GO
SELECT ID, Student, value ,value
FROM #StudentClasses
CROSS APPLY STRING_SPLIT(Classes, ',')
CROSS APPLY STRING_SPLIT(CCode, ',')
This must be put in the very first place: Do not store delimited data! If there is any chance to change your table's design, you should use related side-tables to store data this kind...
Your question is not much better than the one before. Without your expected result any suggestion must be guessing.
What I guess: You want to transform 'Maths,Science,English', 'US,UK,AUS' in a way, that Maths goes along with US, Science along with UK and English matches AUS. Try this
SELECT sc.ID
,sc.Student
,A.[key] AS Position
,A.[value] AS Class
,B.[value] AS CCode
FROM #StudentClasses sc
CROSS APPLY OPENJSON('["' + REPLACE(Classes,',','","') + '"]') A
CROSS APPLY OPENJSON('["' + REPLACE(CCode,',','","') + '"]') B
WHERE A.[key]=B.[key];
You did not tell us your SQL Server's version... But you tagged with Azure. Therefore I assume, that v2016 is okay for you. With a lower version (or a lower compatibility level of the given database) there is no JSON support.
Why JSON at all? This is the best way at the moment to split CSV data and get the fragments together with their position within the array. Regrettfully STRING_SPLIT() does not guarantee to return the expected order. With versions lower than v2016 there are several more or less ugly tricks...
If you need your result side-by-side you should read about conditional aggregation.
use select all or use alias
CREATE TABLE #StudentClasses
(ID INT, Student VARCHAR(100), Classes VARCHAR(100),CCode varchar(30))
INSERT INTO #StudentClasses
SELECT 1, 'Mark', 'Maths,Science,English', 'US,UK,AUS'
UNION ALL
SELECT 2, 'John', 'Science,English', 'BE,DE'
UNION ALL
SELECT 3, 'Robert', 'Maths,English', 'CA,IN'
SELECT *,v1.value as clases,v2.value as codes
FROM #StudentClasses
CROSS APPLY STRING_SPLIT(Classes, ',') v2
CROSS APPLY STRING_SPLIT(CCode,
',') v1

Split column data into multiple rows

I have data currently in my table like below under currently section. I need the selected column data which is comma delimited to be converted into the format marked in green (Read and write of a category together)
Any ways to do it in SQL Server?
Please do look at the proposal data carefully....
Maybe I wasn't clear before: this isn't merely the splitting that is the issue but to group all reads and writes of a category together(sometimes they are just merely reads/writes), it's not merely putting comma separated values in multiple rows.
-- script:
use master
Create table prodLines(id int , prodlines varchar(1000))
--drop table prodLines
insert into prodLines values(1, 'Automotive Coatings (Read), Automotive Coatings (Write), Industrial Coatings (Read), S.P.S. (Read), Shared PL''s (Read)')
insert into prodLines values(2, 'Automotive Coatings (Read), Automotive Coatings (Write), Industrial Coatings (Read), S.P.S. (Read), Shared PL''s (Read)')
select * from prodLines
Using Jeff's DelimitedSplit8K
;
with cte as
(
select id, prodlines, ItemNumber, Item = ltrim(Item),
grp = dense_rank() over (partition by id order by replace(replace(ltrim(Item), '(Read)', ''), '(Write)', ''))
from #prodLines pl
cross apply dbo.DelimitedSplit8K(prodlines, ',') c
)
select id, prodlines, prod = stuff(prod, 1, 1, '')
from cte c
cross apply
(
select ',' + Item
from cte x
where x.id = c.id
and x.grp = c.grp
order by x.Item
for xml path('')
) i (prod)
Take a look at STRING_SPLIT, You can do something like:
SELECT user_access
FROM Product
CROSS APPLY STRING_SPLIT(user_access, ',');
or what ever you care about.
Simply use LATERAL VIEW EXPLODE function:
select access from Product
LATERAL VIEW explode(split(user_access, '[,]')) usrAccTable AS access;

SQL - Sum content of cell

I have a database with a table that very simplified looks like this
Column 1 Column 2 (varchar)
Vector 1 23^34^45^65
Vector 2 0^54^10^31
Now I want to sum the numbers in the cells of column 2 together. That is, I want it to look like this:
Column 1 Column 2 (varchar)
Vector 1 167
Vector 2 95
How do I do this in SQL?
Perhaps you want something like this:
SELECT a.col1, sum_of_values = SUM(d.val)
FROM (VALUES ('Vector 1', '23^34^45^65'), ('Vector 2', '0^54^10^31')) a (col1, col2)
CROSS APPLY (SELECT CONVERT(xml, '<a>' + REPLACE(a.col2, '^', '</a><a>')+'</a>')) AS b(doc)
CROSS APPLY b.doc.nodes('a') c (item)
CROSS APPLY (SELECT c.item.value('.', 'int')) d (val)
GROUP BY a.col1
Output:
col1 sum_of_values
-------- -------------
Vector 1 167
Vector 2 95
Explanation:
The VALUES clause is a placeholder for your data.
By REPLACE'ing caret (^) with XML tags we can use methods on the xml datatype to efficiently split values.
CROSS APPLY with the nodes() method of the xml datatype returns a new row per item, and an xml column containing the item value.
value('.', 'int') converts the inner text of an <a> element to an int.
The GROUP BY and SUM aggregate these results, and reduce the number of rows back to the original two.
Edit: You could move the split into its own function. Like this:
CREATE FUNCTION dbo.SplitIntOnCaret (#list varchar(max)) RETURNS TABLE AS RETURN
SELECT Position = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
, Value = item.value('.', 'int')
FROM (SELECT CONVERT(xml, '<a>' + REPLACE(#list, '^', '</a><a>')+'</a>')) AS a(doc)
CROSS APPLY doc.nodes('a') c (item)
So your final query could look like this:
SELECT l.list_id, sum_of_values = SUM(s.value)
FROM dbo.MyLists l
CROSS APPLY dbo.SplitIntOnCaret(l.list) AS s
GROUP BY l.list_id
If you cannot change the way your data is stored, you will have to
Split the String (see Turning a Comma Separated string into individual rows)
Compute the SUM using GROUP BY.

Is there a set-based solution for executing an rCTE over multiple rows?

I have a table that stores a number of rows, each declaring a point (x, y). Each row references the next point in a pattern, so the query to retrieve an entire pattern is recursively defined through a linked list structure. I've developed a rCTE to retrieve a full structure that looks like this...
WITH list (id, next, x, y) AS (
-- rCTE anchor expression
SELECT id, next, x, y
FROM POINT
WHERE id = #id
UNION ALL
-- rCTE recursive expression
SELECT POINT.id, POINT.next, POINT.x, POINT.y
FROM POINT
INNER JOIN list ON POINT.id = list.next)
SELECT x, y FROM list
Additionally, I have another table that references the starting point for each of these patterns stored in the above-described table.
The end goal is to develop a query that will return a 2-dimensional result with all of the points for each pattern consolidated into a comma-delimited list. This return value would look something like this:
id | name | points
-----------------------
1 | test | 1,2,1,3,1,4
(The odd-number indices in the points are x and the even are y.)
Now the naive solution to achieve this is to loop the parent table and for each of the parent table rows set #id and run the rCTE. That would work and I have a rough implementation of this already, but I don't like this solution. Is there a better, more "SQL" way to achieve this goal?
Below is the solution I came up with, where "AREA" is the parent table which references the beginning point in a given area.
WITH list (id, next, latitude, longitude, path) AS (
-- rCTE anchor expression
SELECT POINT.id, next, latitude, longitude, path = 1
FROM AREA INNER JOIN POINT ON AREA."start" = POINT.ID
UNION ALL
-- rCTE recursive expression
SELECT POINT.id, POINT.next, POINT.latitude, POINT.longitude, list.path + 1
FROM POINT INNER JOIN list ON POINT.id = list.next)
SELECT a.name, points = STUFF(
(SELECT ',' + CAST(latitude AS VARCHAR) + ',' + CAST(longitude AS VARCHAR)
FROM list b
WHERE a.name = b.name
ORDER BY b.path ASC
FOR XML PATH('')),
1, 1, '')
FROM list a