Query a table to make each row become 3 rows - sql

I have a table (T1) as follows in SQL server:
id subject student1 student2 student3 score1 score2 score3
1 Maths Peter Leo John 11 12 13
2 Phy Mary May Leo 21 22 23
Is it possible to use a query to make the following result:
id subject name score
1 Maths Peter 11
1 Maths Leo 12
1 Maths John 13
2 Phy Mary 21
2 Phy May 22
2 Phy Leo 23

Although this is an unpivot query, I actually find it easier to do this with explicit logic:
select t1.id, t1.subject,
(case when n.n = 1 then student1
when n.n = 2 then student2
when n.n = 3 then student3
end) as student,
(case when n.n = 1 then score1
when n.n = 2 then score2
when n.n = 3 then score3
end) as score
from t1 cross join
(select 1 as n union all select 2 union all select 3) n;
This should have comparable performance to unpivot. And, it should have better performance than three union all operations (because that requires scanning the table three times).

Assuming the table is called classes, you can union it to itself:
(SELECT student1 as student, subject from Classes)
UNION
(SELECT student2 as student, subject from Classes)
UNION
(SELECT student3 as student, subject from Classes)

Related

Using a Selected table for two sets of joins

I'm rationalising some old SQL tables that exist at a lot of remote sites, so I need to build a query that will make new good tables out of the bad old ones. So for this, we have table1 which has the columns DataGroup and Case1 as nvarchar, but these are enums in the application, so I've made new tables to store the enums, but I need to get the IDs. Unfortunately, we need to store all of the enums for this table in a single table, so the ExData table contains 4 columns: id, name, ExGroupId and DataGroupId.
As DataGroup in table1 is text, we need to look that up for the int id as well from a kvp table DataGroupTable
This is the query I have so far:
SELECT
<Other Columns>,
t1.ExDataId AS Case1
FROM
table1
LEFT JOIN (
SELECT
DataGroupTable.name AS dataGroup,
ExData.id AS ExDataId,
ExData.name AS ExDataName,
ExGroup.name AS ExGroupName
FROM
ExData
LEFT JOIN DataGroupTable ON DataGroupTable.id = ExData.dataGroupId
LEFT JOIN ExGroup ON ExGroup.id = ExData.ExGroupId
) t1 ON t1.dataGroup = table1.DataGroup
AND t1.ExGroupName = 'case1'
AND t1.ExDataName = table1.Case1
GO
... But while this works to retrieve Case1, how would I go about getting Case2?
I have 7 cases to handle, and whilst I could solve this with liberal copy-pasting, that is far from elegant.
Additionally, this is all going into an INSERT statment, so ideally this should return Case1, Case2 etc as ExDataId's
Please help.
Sample Data as requested, All id's will start from 0, but I have made all of the below unique for clarity.
table1:
DataGroup Case1 Case2 Case3 <Other Columns>
ABCD bob bob chris 1
ABCD pete gary chris 2
EFGH bob mike rod 3
DataGroupTable:
id name
11 ABCD
12 EFGH
ExGroup:
id name
21 case1
22 case2
23 case3
ExData:
id name ExGroupId dataGroupId
31 bob 21 11
32 pete 21 11
33 bob 21 12
34 bob 22 11
35 gary 22 11
36 mike 22 12
37 chris 23 11
38 rod 23 12
Ideal Result:
<Other Columns> Case1 Case2 Case3
1 31 34 37
2 32 35 38
3 33 36 38
How about a Common Table Expression ?
WITH ExDataCTE AS (
SELECT
DataGroupTable.name AS dataGroup,
ExData.id AS ExDataId,
ExData.name AS ExDataName,
ExGroup.name AS ExGroupName
FROM
ExData
LEFT JOIN DataGroupTable ON DataGroupTable.id = ExData.dataGroupId
LEFT JOIN ExGroup ON ExGroup.id = ExData.ExGroupId)
SELECT
<Other Columns>,
t1.ExDataId AS Case1,
t2.ExDataId AS Case2,
t3.ExDataId AS Case3
FROM
table1
LEFT JOIN ExDataCTE t1 ON (t1.dataGroup = table1.DataGroup
AND t1.ExGroupName = 'case1'
AND t1.ExDataName = table1.Case1)
LEFT JOIN ExDataCTE t2 ON (t2.dataGroup = table1.DataGroup
AND t2.ExGroupName = 'case2'
AND t2.ExDataName = table1.Case2)
LEFT JOIN ExDataCTE t3 ON (t3.dataGroup = table1.DataGroup
AND t3.ExGroupName = 'case3'
AND t3.ExDataName = table1.Case3)

How to find out which column value changed in SQL

I have two tables with 100 of columns, and I want to find out how which column value changed in those two tables. I can find which row has changed but I want to which column was changed not the whole row.
Table 1
name ID Dept Email EmpID Salary Gender
Rob 1 IT I#i.com 100 5000 M
Mary 2 HR M#m.com 20 6000 F
Jack 3 IT J#j.com 30 7000 M
Harry 4 Fin h#h.com 50 5000 M
Jay 5 Eng Null 60 5000 M
Ken 6 HR K#K.com 70 Null M
Table 2
name ID Dept Email EmpID Salary Gender
Rob 1 IT I#i.com 100 5000 M
Mary 2 HR M#m.com 20 6000 F
Jack 3 IT J#j.com 150 7000 M
Harry 4 Fin h#h.com 50 Null M
Jay 5 Eng Jy#jy.com 60 5000 M
Ken 6 HR K#K.com 70 6000 M
As we can see Email for Jay existed, Emp ID for Jack was changed, Salary for Harry is null in Table 2 and Salary was added for Ken.
Expected Output (If this is possible, As I don't want to see all the row values, I just want to see which column value changed, as ID is a unique identifier, I would like to know which ID did column value change)
ID columnvaluechanged
3 EmpID
4 Salary
5 Email
6 Salary
Here is one way using unpivot technique
;WITH tab1
AS (SELECT id,
colName,
value
FROM Yourtable1
CROSS apply (VALUES (NAME,'name'),(Dept,'Dept'),(Email,'Email'),
(Cast(EmpID AS VARCHAR(50)),'EmpID'),
(Cast(Salary AS VARCHAR(50)),'Salary'),
(Gender,'Gender')) cs (value, colName)),
tab2
AS (SELECT id,
colName,
value
FROM Yourtable2
CROSS apply (VALUES (NAME,'name'),(Dept,'Dept'),(Email,'Email'),
(Cast(EmpID AS VARCHAR(50)),'EmpID'),
(Cast(Salary AS VARCHAR(50)),'Salary'),
(Gender,'Gender')) cs (value, colName))
SELECT t1.ID,
t1.colName,
t1.value AS tab1_value,
t2.value AS tab2_value
FROM tab1 t1
INNER JOIN tab2 t2
ON t1.ID = t2.ID
AND t1.colName = t2.colName
AND Isnull(t1.value, '') <> Isnull(t2.value, '')
Demo

Average by rows with sqlite

I'm working with a sqlite database.
The tables are:
ID_TABLE POINTS_A_TABLE POINTS_B_TABLE
id number id_a points_a id_a points_a
-------------- ---------------- ----------------
smith 1 smith 11 ...
gordon 22 gordon 11
butch 3 butch 11
sparrow 25 sparrow
white 76 white 46
and so on. After these commands
select id,
points_a_table.points_a, points_b_table.points_a, points_c_table.points_a, points_d_table.points_a
from id_table
left join points_a_table on points_a_table.id_a = id_table.id
left join points_b_table on points_b_table.id_a = id_table.id
left join points_c_table on points_c_table.id_a = id_table.id
left join points_d_table on points_d_table.id_a = id_table.id
group by id
I got this result, on each row I have the id and the points associated with the id.
Now I'd like to get an average of points by row, sorted by average in descending order.
What I want is:
sparrow| 56 [(44+68)/2]
white | 41 ([46+67+11)/3]
smith | 33 [(11+25+65)/3]
butch | 24 [(11+26+11)/3]
gordon | 11 [11/1]
How can I do that?
Thanks.
If you smush together all point tables, you can then simply compute the average for each group:
SELECT id,
avg(points_a)
FROM (SELECT id_a AS id, points_a FROM points_a_table
UNION ALL
SELECT id_a AS id, points_a FROM points_b_table
UNION ALL
SELECT id_a AS id, points_a FROM points_c_table
UNION ALL
SELECT id_a AS id, points_a FROM points_d_table)
GROUP BY id
ORDER BY avg(points_a) DESC;

SQL Select Master Records and Display Number of Detail Records for Each

I have a Master and Detail table, the Detail linking to the Master record on a FK reference.
I need to display all the data from the Master table, and the corresponding number of details for each record, i.e.
MASTER TABLE
ID Name Age
1 John 15
2 Jane 14
3 Joe 15
DETAIL
MasterID Subjects
1 Trigonometry
1 Chemistry
1 Physics
1 History
2 Trigonometry
2 Physics
Thus, when I ran the SQL statement, I would have the following result:
ID Name Age #Subjects
1 John 15 4
2 Jane 14 2
3 Joe 15 0
Thanks!
This may be useful
SELECT mt.ID, mt.NAME, mt.AGE, COUNT(d.MasterID) as [#Subjects]
FROM MasterTable mt
LEFT OUTER JOIN Detail d on mt.ID = d.ID
GROUP BY mt.ID, mt.NAME, mt.AGE
ORDER BY mt.ID
select id,
name,
age,
( select count(*)
from detail
where master.id = detail.id ) as record_count
from master
syntax adjusted depending on what db you are using

Tricky SQL - Select non-adjacent numbers

Given this data on SQL Server 2005:
SectionID Name
1 Dan
2 Dan
4 Dan
5 Dan
2 Tom
7 Tom
9 Tom
10 Tom
How would I select records where the sectionID must be +-2 or more from another section for the same name.
The result would be:
1 Dan
4 Dan
2 Tom
7 Tom
9 Tom
Thanks for reading!
SELECT *
FROM mytable a
WHERE NOT EXISTS
(SELECT *
FROM mytable b
WHERE a.Name = b.Name
AND a.SectionID = b.SectionID + 1)
Here's LEFT JOIN variant of Anthony's answer (removes consecutive id's from the results)
SELECT a.*
FROM mytable a
LEFT JOIN mytable b ON a.Name = b.Name AND a.SectionID = b.SectionID + 1
WHERE b.SectionID IS NULL
EDIT: Since there is another interpretation of the question (simply getting results where id's are more than 1 number apart) here is another attempt at an answer:
WITH alternate AS (
SELECT sectionid,
name,
EXISTS(SELECT a.sectionid
FROM mytable b
WHERE a.name = b.name AND
(a.sectionid = b.sectionid-1 or a.sectionid = b.sectionid+1)) as has_neighbour,
row_number() OVER (PARTITION by a.name ORDER BY a.name, a.sectionid) as row_no
FROM mytable a
)
SELECT sectionid, name
FROM alternate
WHERE row_no % 2 = 1 OR NOT(has_neighbour)
ORDER BY name, sectionid;
gives:
sectionid | name
-----------+------
1 | Dan
4 | Dan
2 | Tom
7 | Tom
9 | Tom
Logic: if a record has neighbors with same name and id+/-1 then every odd row is taken, if it has no such neighbors then it gets the row regardless if it is even or odd.
As stated in the comment the condition is ambiguous - on start of each new sequence you might start with odd or even rows and the criteria will still be satisfied with different results (even with different number of results).