How to convert Columns into Rows or Vica Versa - sql

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)

Related

how to merge three rows into one row

how to change output of the sql query from 1 to 2
1:- user_id subject name marks
1001 maths 67
1001 pyhsics 78
1001 chemistry 87
1002 maths 89
1002 physics 56
1002 chemistry 76
2:- user_id maths physics chemistry
1001 67 78 87
1002 89 56 76
I'm expecting sql query
Use crosstab (pivot tables) to turn rows into columns:
SELECT *
FROM crosstab(
'SELECT user_id, subject_name, marks FROM t'
) AS ct(user_id int, maths int, physics int, chemistry int);
Another option, with a slightly different purpose, is to use conditional aggregates with FILTER:
SELECT
user_id,
SUM(marks) FILTER (WHERE subject_name = 'maths') AS maths,
SUM(marks) FILTER (WHERE subject_name = 'physics') AS physics,
SUM(marks) FILTER (WHERE subject_name = 'chemistry') AS chemistry
FROM t
GROUP BY user_id;
Demo: db<>fiddle

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.

SQL JOIN return only the first match

I think this is a basic question, but I couldn’t figure it out. I am new to this so please bear with me.
I am analyzing players data from the FIFA game, and I want to get a table with the highest rating of each individual player and the earliest age when that happened.
This is an example of the data:
id
name
position
rating
age
1
James
RW
70
20
1
James
RW
71
21
2
Frank
CB
73
23
2
Frank
CB
73
24
3
Miles
CM
75
27
3
Miles
CM
74
28
This is what the query should return:
id
name
position
rating
age
1
James
RW
71
21
2
Frank
CB
73
23
3
Miles
CM
75
27
I thought I could first get the highest overall for each player, and then do a JOIN to get the age, but that gives the other years a player had the same highest rating.
id
name
position
rating
age
1
James
RW
71
21
2
Frank
CB
73
23
2
Frank
CB
73
24
3
Miles
CM
75
27
Thank you,
One approach uses ROW_NUMBER with QUALIFY:
SELECT *
FROM yourTable
WHERE true
QUALIFY ROW_NUMBER() OVER (PARTITION BY id ORDER BY rating DESC, age) = 1;
Try this one (could be a bit faster):
with mytable as (
select 1 as id, "James" as name, "RW" as position, 70 as rating, 20 as age union all
select 1, "James", "RW", 71, 21 union all
select 2, "Frank", "CB", 73, 23 union all
select 2, "Frank", "CB", 73, 24 union all
select 3, "Miles", "CM", 75, 27 union all
select 3, "Miles", "CM", 74, 28
)
select array_agg(t order by rating desc, age asc limit 1)[OFFSET(0)].*,
from mytable as t
group by t.id
Try this one too if you want. I think it's what you need.
select p1.id,
p1.name,
p1.pos,
max(p1.rating),
p1.age
from players p1
join (select name, min(age) min_age
from players
group by 1) p2 on p1.name = p2.name and p1.age = p2.min_age
group by p1.id, p1.name, p1.pos, p1.age

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

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.

merge rows in sql server

Select name, Subject, Score from Class
Tables
name | Subject | Score
-----------------------
Ola maths 20
Ola pop 15
Ola eng 74
Dodo maths 21
Dodo pop 19
Dodo eng 54
Please any idea on how to make it display like this:
name | Subject | Score
-----------------------
Ola maths 20
pop 15
eng 74
Dodo maths 21
pop 19
eng 54
Please any assistant will be appreciated
if you absolutely have to do this in SQL:
with cte as
(
select rn=row_number() over (partition by name order by 1)
,*
from [table]
)
select n = case when rn=1 then name else '' end, Subject, Score
from cte
order by name,rn
But if you do this then you are losing the ability to use this data again.
As previous people have already comment, this might not be your best approach but the following query returns your expected result:
create table Class (Name varchar(10), Subject varchar(10), Score int)
insert into Class
values ('Ola', 'maths', 20),
('Ola', 'pop', 15),
('Ola', 'eng', 15),
('Dodo', 'maths', 20),
('Dodo', 'pop', 15),
('Dodo', 'eng', 15)
select
case when Row# = 1 then t.Name else null end,
Subject,
Score
from (
select
ROW_NUMBER() OVER(PARTITION BY Name ORDER BY Name ASC) AS Row#,
Name,
Subject,
Score
from Class) t
drop table Class