Save a comma separated array in a string in a table [duplicate] - sql

This question already has answers here:
Split function equivalent in T-SQL?
(16 answers)
Closed 2 years ago.
I inherited a table that has these columns
ID, Name, Subjects
-- ---- --------
33 Mike Math,English,Physics
24 Paul Art,French,Med,English,Math
58 Sami Physics,Biology
22 Nora Math,English,Art
76 Mona Math,English,French,Med,Physics
39 Lila Physics
19 Dave Math,Biology,Physics
48 Jade English,French,Physics
82 Mark Med,Biology,Physics
23 Nina Biology,English,Physics
I am trying to break this into my structured table.
ID, Name, Subject
-- ---- --------
33 Mike Math
33 Mike English
33 Mike Physics
24 Paul Art
24 Paul French
24 Paul Med
24 Paul English
I tried with using STRING_SPLIT in the select statement
SELECT ID, Name, STRING_SPLIT(Subjects, ',') AS SUbject
FROM Students
but that did not work
'STRING_SPLIT' is not a recognized built-in function name.
How can I split these subjects into rows?
This script should generate the table and data
declare #Students as table (ID int, Name varchar(4), Subjects varchar(100))
INSERT INTO #Students (ID, Name, Subjects)
VALUES
(33,'Mike','Math,English,Physics'),
(24,'Paul','Art,French,Med,English,Math'),
(58,'Sami','Physics,Biology'),
(22,'Nora','Math,English,Art'),
(76,'Mona','Math,English,French,Med,Physics'),
(39,'Lila','Physics'),
(19,'Dave','Math,Biology,Physics'),
(48,'Jade','English,French,Physics'),
(82,'Mark','Med,Biology,Physics'),
(23,'Nina','Biology,English,Physics')
SELECT * FROM #Students
Response to suggested duplicate
Although this question was closed suggesting it similar to Split function equivalent in T-SQL? it is actually not.
That question is a simple like which a simple FROM string_split() can work for. I got the answer thanks to Gordon. I thought it might help other with the same issue. If you have a similar issue, you may find the answer down.

Just another option using a little XML
Example
Declare #YourTable Table ([ID] varchar(50),[Name] varchar(50),[Subjects] varchar(50))
Insert Into #YourTable Values
(33,'Mike','Math,English,Physics')
,(24,'Paul','Art,French,Med,English,Math')
,(58,'Sami','Physics,Biology')
,(22,'Nora','Math,English,Art')
,(76,'Mona','Math,English,French,Med,Physics')
,(39,'Lila','Physics')
,(19,'Dave','Math,Biology,Physics')
,(48,'Jade','English,French,Physics')
,(82,'Mark','Med,Biology,Physics')
,(23,'Nina','Biology,English,Physics')
Select A.ID
,A.Name
,B.*
From #YourTable A
Cross Apply (
Select RetSeq = row_number() over (order by 1/0)
,RetVal = ltrim(rtrim(B.i.value('(./text())[1]', 'varchar(max)')))
From ( values (cast('<x>' + replace((Select replace([Subjects],',','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.'))) as A(x)
Cross Apply x.nodes('x') AS B(i)
) B
Returns
ID Name RetSeq RetVal
33 Mike 1 Math
33 Mike 2 English
33 Mike 3 Physics
24 Paul 1 Art
24 Paul 2 French
24 Paul 3 Med
24 Paul 4 English
24 Paul 5 Math
58 Sami 1 Physics
58 Sami 2 Biology
22 Nora 1 Math
22 Nora 2 English
22 Nora 3 Art
76 Mona 1 Math
76 Mona 2 English
76 Mona 3 French
76 Mona 4 Med
76 Mona 5 Physics
39 Lila 1 Physics
19 Dave 1 Math
19 Dave 2 Biology
19 Dave 3 Physics
48 Jade 1 English
48 Jade 2 French
48 Jade 3 Physics
82 Mark 1 Med
82 Mark 2 Biology
82 Mark 3 Physics
23 Nina 1 Biology
23 Nina 2 English
23 Nina 3 Physics

You can use string_split() in the most recent versions of SQL Server:
SELECT ID, Name, ss.value as subject
FROM Students s CROSS APPLY
string_split(s.subjects, ',') ss;
You can also play with JSON or define your own split() function, although in older versions, I would just use a recursive CTE:
with cte as (
select s.id, convert(varchar(max), null) as subject, convert(varchar(max), subjects + ',') as rest
from students s
union all
select id, left(rest, charindex(',', rest) - 1),
stuff(rest, 1, charindex(',', rest), '')
from cte
where rest <> ''
)
select *
from cte
where subject is not null;
Here is a db<>fiddle.

Related

SQLite impute missing values by mean for every group

I have an SQLite table as shown below.
students
grades
Nick
34
Nick
42
Nick
86
Nick
Null
John
38
John
12
John
74
John
Null
Colin
87
Colin
23
Colin
46
Colin
42
What I want to do is impute Null values with the mean of each student's grades.
For example, missing value for Nick will be 54 and for John 41.3.
How can I do this in SQL code? I am using SQLite.
Use a correlated subquery in the UPDATE statement:
UPDATE tablename AS t1
SET grades = (
SELECT ROUND(AVG(t2.grades), 1)
FROM tablename AS t2
WHERE t2.students = t1.students
)
WHERE t1.grades IS NULL;
See the demo.

Split a column based on a character in BigQuery

I have a table as shown below on BigQuery
Name | Score
Tim | 63 > 89 > 90
James| 67 > 44
I want to split the Score column into N separate columns where N is the maximum score length in the entire table. I would like the table to be as follow.
Name| Score_1 | Score_2 | Score_3
Tim | 63 | 89 | 90
James| 67 | 44 | 0 or NA
I tried the Split command but I end up doing a new row for each Name-Score combination.
For BigQuery Standard SQL
Below is simple case and assumes you know in advance the expected max score length (3 in below example)
#standardSQL
WITH `project.dataset.your_table` AS (
SELECT 'Tim' name, '63 > 89 > 90' score UNION ALL
SELECT 'James', '67 > 44'
)
SELECT
name,
score[SAFE_OFFSET(0)] AS score_1,
score[SAFE_OFFSET(1)] AS score_2,
score[SAFE_OFFSET(2)] AS score_3
FROM (
SELECT name, SPLIT(score, ' > ') score
FROM `project.dataset.your_table`
)
with result
Row name score_1 score_2 score_3
1 Tim 63 89 90
2 James 67 44 null
Of course above approach means - if you have many scores - like 10 or 20 or more - you will need to add respective number of extra lines like below
score[SAFE_OFFSET(20)] AS score_21
So, above gives you what you wanted from schema of output point of view
At the same time, below makes more sense to me and in most practical cases is better and most optimal :
#standardSQL
WITH `project.dataset.your_table` AS (
SELECT 'Tim' name, '63 > 89 > 90' score UNION ALL
SELECT 'James', '67 > 44'
)
SELECT name, score
FROM `project.dataset.your_table`, UNNEST(SPLIT(score, ' > ')) score
with result
Row name score
1 Tim 63
2 Tim 89
3 Tim 90
4 James 67
5 James 44

T-SQL Incremental Update Group Number

I have a table like this. For each student, for each term column change I want to increment the semester number. When the student ID Changes the Semester Number should re begin from 1. My Semester Number is null for now and I wish to update it. Any easy fast solution? I read about Dens_Rank and I am not sure if it is applicable here.
StudentId | Term |Course_Number| Semester_Number(Expected)
1 0010 ENG 1
1 0010 AGR 1
1 0020 MAT 2
1 0020 ... 2
1 0110 ... 3
1 0110 ... 3
2 0010 ENG 1
2 0010 MAT 1
2 0020 PHY 2
3 0010 MAT 1
3 ...
3
If I understand correctly, you can do:
select t.*,
dense_rank() over (order by studentid, term) as semester_number
from t;
I have improved my undertanding:
select t.*,
dense_rank() over (partition by studentid order by term) as semester_number
from t;

Group columns in query

I have a query where I fetch the following columns:
ID Name Age Hobby
ID, name and age comes from Table A
Hobby comes from Table B
Example of results I can get is the following:
ID Name Age Hobby
0 John 35 Fishing
0 John 35 Tennis
0 John 35 Hiking
1 Jane 31 Fishing
2 Nate 42 Fishing
2 Nate 42 Tennis
What I would like to have as result is the following instead:
ID Name Age Hobby
0 John 35 Fishing, Tennis, Hiking
1 Jane 31 Fishing
2 Nate 42 Fishing, Tennis
Any ideas of how to achieve that?
Try this :
;WITH CTE AS(
SELECT DISTINCT ID,NAME,AGE
FROM TableName
)
SELECT *,
STUFF(SELECT ','+ Hobby FROM TableName t1 WHERE t1.ID=CTE.ID FOR XML PATH(''),1,1,'')
FROM CTE

How to convert Columns into Rows or Vica Versa

I have two tables
Student:
Name Class Maths Science English Hindi
Sonia 2 98 67 53 58
Vijay 7 89 68 45 51
Abhishek Mishra 6 87 89 52 53
Rupal 8 74 76 59 64
Gaurav 10 90 78 43 41
Subject:
Subject Total_Marks
Maths 100
Science 100
English 75
Hindi 75
When I select name sonia it should provide following SQL output:
Subject Total_Marks Obtained Marks
Maths 100 98
Science 100 67
English 75 53
Hindi 75 58
Okay, Without UNPIVOT you can achieve the desired result, like this -
DECLARE #subject table (subject varchar(10), total_marks int)
DECLARE #student table (name varchar(100),class int,maths int, science int,english int,hindi int)
INSERT INTO #subject
VALUES ('Maths', 100), ('Science', 100), ('English', 75), ('Hindi', 75)
INSERT INTO #student
VALUES ('sonia', 2, 98, 67, 53, 58),
('vijay', 7, 89, 68, 45, 51)
SELECT ri.subject, ri.total_marks, le.val as ObtainedMarks FROM
(
SELECT name, class, t.sub, t.val FROM #student
CROSS APPLY (VALUES ('maths', maths), ('science', science), ('english', english), ('hindi', hindi)) AS t(sub, val)
) le
INNER JOIN
(
SELECT * FROM #subject
) ri
ON le.sub = ri.subject
WHERE le.name = 'sonia'
Here i have used CROSS APPLY with VALUES clause to make key-value pair combination of subject name as key and its column value as value.
Result
subject total_marks ObtainedMarks
-------------------------------------
Maths 100 98
Science 100 67
English 75 53
Hindi 75 58
This reeks of a school problem, but giving you benefit of doubt, you should try UNPIVOT syntax rather than PIVOT syntax.
See MSDN documentation for both here
Using UNPIVOT followed by LEFT JOIN, the query should like below
select UP.Subject,Total_Marks, Obtained_Marks from
(
select
S.Name as Name,
S.Maths as Maths,
S.Science as Science,
S.English as English,
S.Hindi as Hindi
from Student S
where S.Name like 'sonia'
)source
UNPIVOT
(
Obtained_Marks for Subject in (Maths,Science, English, Hindi)
)UP
LEFT JOIN Subject S
ON S.Subject=UP.Subject
INSERT queries used for schema:
create table subject(subject varchar(10), total_marks int)
insert into subject values('Maths',100),('Science', 100),('English', 75),('Hindi', 75)
create table student(name varchar(100),class int,maths int, science int,english int,hindi int)
insert into student values
('sonia',2,98,67,53,58),
('vijay',7,89,68,45,51)