accumulated calculation in sql server - sql

I want to do this query on sql server
I can do this with loop but would like to know if there is any easier way that has better performance
I have table for referrals ,
scenario 1 : if A has 5 points and refer B that has 7 then the query should show 12 pts for A (A points + B points)
scenario 2 : if A has 5 and refer to B that has 7 and A refer to C that has 3 points and B refer to D that has 4 pts and so on..
in this case A takes all points of people A + B + C + D
my table look like this
Refs
sID bigint
sName varchar(50)
sPoints int
sRefID bigint

You can do this using recursive SQL. Try this:
With CTETable (sID, sRefID, sName, sPoints)
AS
(
SELECT Refs.sID, Refs.sRefID, Refs.sName, Refs.sPoints FROM Refs
UNION ALL
SELECT Refs.sID, Refs.sRefID, CTETable.sName, Refs.sPoints
FROM Refs INNER JOIN CTETable ON CTETable.sID = Refs.sRefID
)
Select sName, Sum(sPoints)
From CTETable
Group By CTETable.sName
This will yeild:
sName TotalPoints
A 360
B 210
C 130
D 80
E 90
F 90
G 60
H 40
I 20
J 50

Related

Query to join two tables using two different columns from the first table

I have two tables .
Table A:
Table A ID Table Name owner1ID owner2ID
1 Work1 85 91
2 Work2 86 92
3 Work3 87 93
4 Work4 88 94
5 Work5 89 95
6 Work6 90 96
Table B:
OwnerID 0WNERFIRSTNAME 0WNERlASTNAME
85 A M
86 B N
87 C O
88 D P
90 E Q
91 F R
89 G S
92 H T
86 I U
94 J V
93 K W
95 L X
Can you please help me out in getting a query where i need the table which contains TABLEID OWNERFIRSTNAME and OWNERSECONDNAME.
Expected output:
TableAID 0WNER1FIRSTNAME 0WNER1LASTNAME 0WNER2FIRSTNAME 0WNER2LASTNAME
1 A M F R
You need to join on to TableB twice.
That means you need to give each instance of the table an alias, so you can differentiate which instance you're referring to...
SELECT
TableA.TableAID,
TableB1.0WNERFIRSTNAME AS 0WNER1FIRSTNAME,
TableB1.0WNERlASTNAME AS 0WNER1LASTNAME,
TableB2.0WNERFIRSTNAME AS 0WNER2FIRSTNAME,
TableB2.0WNERlASTNAME AS 0WNER2LASTNAME
FROM
TableA
INNER JOIN
TableB TableB1
ON TableB1.OwnerID = TableA.owner1ID
INNER JOIN
TableB TableB2
ON TableB2.OwnerID = TableA.owner2ID
P.S. Don't Spell 0WNERFIRSTNAME with a ZERO, Spell it OWNERFIRSTNAME!
While MatBaile's answer is the most common practice, your own example shows some problems. First is that we lose info about table 6 for which second owner is not found in second table. This can be easily corrected with left join:
select a.id, a.table_name,
b1.OwnerFirstName O1FN, b1.OwnerLastName O1LN,
b2.OwnerFirstName O2FN, b2.OwnerLastName O2LN
from a
left join b b1 on b1.OwnerId = a.Owner1Id
left join b b2 on b2.OwnerId = a.Owner2Id
What gives us:
ID TABLE_NAME O1FN O1LN O2FN O2LN
---------- ---------- ---- ---- ---- ----
1 Work1 A M F R
2 Work2 I U H T <-- two first owners
2 Work2 B N H T <-- two first owners
4 Work4 D P J V
3 Work3 C O K W
5 Work5 G S L X
6 Work6 E Q <-- null second owner
And second problem - for table 2 we got two entries, because in your example there are two owners with id = 86. I suspect that this is typo, but this can happen in similiar cases. You can leave it as is, or take only last row (if owner changed and you have info about this in some date column), or you can list all owners using listagg(), or take max value. Things are worse when there are more rows connected to 1. and 2. owner, your output is multiplied.
As a curiosity here is unpivot-pivot solution. In this case this query looks more complicated, but if there were 10 columns you had to do 10 joins and in this query only lists of columns requires change.
select *
from (
select id, table_name, type, ownerfirstname, ownerlastname
from (select * from a unpivot (ownerId for type in (owner1ID as 1, owner2ID as 2))) a
join b using (ownerId))
pivot (listagg(ownerfirstname||' '||ownerlastname, ', ') within group (order by null) owner
for type in (1, 2))
SQL Fiddle demo
ID TABLE_NAME 1_OWNER 2_OWNER
---------- ---------- ---------- ----------
1 Work1 A M F R
2 Work2 B N, I U H T <-- listagg() used to aggregate data
3 Work3 C O K W
4 Work4 D P J V
5 Work5 G S L X
6 Work6 E Q

Determine hierarchical relationship, but in reverse

Consider the following table in Oracle
sortOrder thisID levelNo
------------------- ------------------- ---------------------
1 A 0
2 B 1
3 C 1
4 D 2
5 E 3
6 F 3
7 G 1
8 H 0
9 I 1
Which could be seen visually as
A
B
C
D
E
F
G
H
I
How could I determine the child parent relationship, to output the following below? The relationship is based on the sortOrder and levelNo.
thisID parentID
------------------- ---------------------
A A
B A
C A
D C
E D
F D
G A
H H
I H
I am familiar with using queries to determine the level based on a hierarchical parent-child relationship, but haven't figured out a way to do it in reverse.
Please try
SELECT
T.thisID
, CASE T.levelNo
WHEN 0
THEN T.thisID
ELSE (
SELECT thisID FROM Table1
WHERE sortOrder = (
SELECT MAX(sortOrder) FROM Table1
WHERE (levelNo = T.levelNo - 1)
AND sortOrder < T.sortOrder
)
)
END parent
FROM Table1 T
ORDER BY sortOrder;
See it in action: SQL Fiddle.
Please comment if and as further detail / adjustment is required.

SQL - only select results that do not have a specific status value in a corresponding table

I have two tables, and I only want to get the Student IDs where they have perfect attendance for all months (they do not have a PerfectAttendance value of N for any month). These tables will have hundreds of millions of rows, so I was trying to come up with an approach that doesn't require a full separate subquery. If anyone has any recommendations, please let me know:
Table Student:
ID Name
------------
1 A
2 B
Table Attendance:
ID Month PerfectAttendance
---------------------------------
1 1 Y
1 2 Y
1 3 Y
1 4 Y
1 5 Y
1 6 Y
1 7 Y
1 8 Y
1 9 Y
1 10 Y
1 11 Y
1 12 Y
2 1 Y
2 2 Y
2 3 Y
2 4 Y
2 5 Y
2 6 Y
2 7 Y
2 8 Y
2 9 Y
2 10 Y
2 11 Y
2 12 N
SELECT *
FROM dbo.Student S
WHERE NOT EXISTS(SELECT 1 FROM dbo.Attendance
WHERE PerfectAttendance = 'N'
AND ID = S.ID);
My suggestion for this would be to query the table and get the number of months that each student has perfect attendance. Once you've done that, you can filter on the count being 12 (since there are twelve months).
Try this:
SELECT s.id, s.name, COUNT(*) AS numPerfectMonths
FROM student s JOIN attendence a ON s.id = a.id
WHERE a.perfectAttendance = 'Y'
GROUP BY s.id
HAVING COUNT(*) = 12;
Here is the SQL Fiddle for you.
EDIT
I made the assumption you will have 12 rows for each student. However, let's say you ran this in October and you want to see which students have a perfect attendance up to that point. You can use a subquery to pull for students without perfect attendance, and filter them out using NOT IN like so:
SELECT id
FROM student
WHERE id
NOT IN(SELECT s.id
FROM student s JOIN attendance a ON s.id = a.id
WHERE a.perfectAttendance = 'N'
GROUP BY s.id
HAVING COUNT(*) > 0);
Have an updated SQL Fiddle. To test this one, try deleting one of the rows for id number 1, and you'll still see that they are returned with perfect attendance.
Assuming you have 12 records per student in attendance table based on your data , you can do it with GROUP BY and HAVING clause.
SELECT S.ID, S.NAME
FROM Student S
JOIN Attendance A
on S.ID = A.ID
AND A.PerfectAttendance = 'Y'
GROUP BY S.ID, S.NAME
HAVING COUNT(*) = 12
I think Lamak's answer is probably the clearest and best-performing, but here is another variation on the GROUP BY method suggested by others, when you don't specifically look for a total of 12 months:
;WITH PerfectAttendance AS (
SELECT a.id
FROM Attendance a
GROUP BY a.id
HAVING MIN(a.PerfectAttendance) = 'Y'
)
SELECT s.id, s.Name
FROM PerfectAttendance p
JOIN Student s ON p.id = s.id;

cross joining two tables

I have a table that looks like this, lets call this table B.
id boardid schoolid subject cnt1 cnt2 cnt3 ....
=================================================================
1 20 21 f
2 20 21 r
3 20 21 w
4 20 21 m
5 20 30 r
6 20 30 w
7 20 30 m
Suppose the counts are just integers. Notice that there is no subject = f for schoolid = 30. Similarly, for most schools, some subject dosnt exist. You might have a schoolid that has just r, w or some that are just r, m, f..
So what I want to do is have 4 consistent rows for each school, and the row that dosnt exist I want dummy values. I thought about creating a secondary table
drop table #A
Select * into #A FROM
(
select [subject_s] = 'r', orderNo = 1
union all
select [subject_s] = 'w', orderNo = 2
union all
select [subject_s] = 'm', orderNo = 3
union all
select [subject_s] = 'f', orderNo = 4
) z
and doing some joins on them, but I've gotten NO where. I've tried inner join, left outer, cross join, everything. I've even tried to make cartesian product. I think my cartesian product messes up because I have orderno in there so it makes 16 rows per row in the main table. Actually typing this out, I realize if I remove the orderno, apply the cartesian product and then add orderno in later, it might work but I am interested to see what you guys can come up with. I am stumped.
End result
id boardid schoolid subject cnt1 cnt2 cnt3 ....
=================================================================
1 20 21 r
2 20 21 w
3 20 21 m
4 20 21 f
5 20 30 r
6 20 30 w
7 20 30 m
7 20 30 f
Try the following:
SELECT S.boardid, S.schoolid, A.[subject], B.cnt1, B.cnt2, B.cnt3
FROM (SELECT DISTINCT boardid, schoolid FROM YourTable) S
CROSS JOIN #A A
LEFT JOIN YourTable B
ON B.boardid = S.boardid AND B.schoolid = S.schoolid
AND A.[subject] = B.[subject]
Since I do not know which RDBMS you are using I tried the following with sqlite and a simpler table:
sqlite> create table schools (name varchar, subject varchar, teacher varchar);
sqlite> select * from schools;
School1|Maths|Mr Smith
School2|English|Jack
School3|English|Jimmy
School3|Maths|Jane
School4|Computer Science|Bob
sqlite> select
schoolnames.name,
subjects.subject,
ifnull(teachers.teacher, "Unknown")
from (select distinct name from schools) schoolnames
join (select distinct subject from schools) subjects
left join schools teachers
on schoolnames.name = teachers.name
and subjects.subject = teachers.subject;
School1|Maths|Mr Smith
School1|English|Unknown
School1|Computer Science|Unknown
School2|Maths|Unknown
School2|English|Jack
School2|Computer Science|Unknown
School3|Maths|Jane
School3|English|Jimmy
School3|Computer Science|Unknown
School4|Maths|Unknown
School4|English|Unknown
School4|Computer Science|Bob
I'd use:
SELECT
boardid, schoolid, dist_subject, id, cnt1, ...
FROM
(SELECT
boardid, schoolid, dist_subject
FROM
(SELECT
DISTINCT subject AS dist_subject
FROM b ) s full outer join
(SELECT
boardid, schoolid
FROM b
GROUP BY
boardid, schoolid ) g ) sg LEFT OUTER JOIN
b ON
sg.boardID = b.boardID AND
sg.schoolid = b.schoolID
sg.dist_subject = b.subject

SQL: Getting the full record with the highest count

I'm trying to write sql that produces the desired result from the data below.
data:
IDNum Opt1 Opt2 Opt3 Count
1 A A E 1
1 A B J 4
2 A A E 9
3 B A F 1
3 B C K 14
4 A A M 3
5 B D G 5
6 C C E 13
6 C C M 1
desired result:
IDNum Opt1 Opt2 Opt3 Count
1 A B J 4
2 A A E 9
3 B C K 14
4 A A M 3
5 B D G 5
6 C C E 13
Essentially I want, for each ID Num, the full record with the highest count. I tried doing a group by, but if I group by Opt1, Opt2, Opt3, this doesn't work because it returns the highest count for each (ID Num, Opt2, Opt3, Opt4) combination which is not what I want. If I only group by ID Num, I can get the max for each ID Num but I lose the information as to which (Opt1, Opt2, Opt3) combination gives this count.
I feel like I've done this before, but I don't often work with sql and I can't remember how. Is there an easy way to do this?
Edit
Prior to op clarifying question for access this would have worked. I am not famillar with access to know if this query would be supported.
I think this will work on SQL Server.
select * from data
inner join (select idnum, max(count) from data
group by idNum )sub
on sub.IdNum=data.IdNum && sub.Count=data.Count
Of course if you have two id's with the same count it would return both rows...
Something like this:
SELECT * FROM table AS t1
JOIN ( SELECT id, max(count) as Id FROM table GROUP BY id ) AS t2
ON t1.id = t2.id AND t1.id = t2.id
This assumes that no idnum has the same max count or you'll get two idnums
Try this query:
SELECT * FROM my_table
GROUP BY IDNum
HAVING Count = MAX(Count)
It should work on Access, but I didn't test it.