Comparing rows of the same table with multiple conditions - sql

Here is a table
ID | Player | Position | Points
1 | Ryan | QB | 75
2 | Matt | RB | 80
3 | Mike | WR | 66
4 | Jay | QB | 71
6 | Alvin | TE | 73
7 | Adrian | TE | 84
8 | Hill | WR | 71
9 | Charles| RB | 53
10 | Bell | WR | 87
11 | Rob | TE | 49
12 | Alex | RB | 92
13 | Drew | QB | 84
14 | Mack | TE | 59
15 | Nick | WR | 33
I want to report all the players in the position of the player having highest points and top 2 players of the other positions. In this example, "Alex" has the highest points and is a "RB". So I want to report all players from "RB" and top 2 from "QB", "TE", "WR" and order by points with in each group. I'm using sqlite3. I can do this programmatically using python and sqlite3 but i was wondering if this could be done only using sql
ID | Player | Position | Points
12 | Alex | RB | 92
2 | Matt | RB | 80
9 | Charles| RB | 53
13 | Drew | QB | 84
1 | Ryan | QB | 75
10 | Bell | WR | 87
8 | Hill | WR | 71
7 | Adrian | TE | 84
6 | Alvin | TE | 73
Thanks for your help

This is tricky in "traditional" SQLite. I would recommend union all:
with top1 as (
select t.*
from t
order by points desc
limit 1
)
select t.*
from t
where t.position = (select t1.position from top1 t1)
union all
select t.*
from t
where t.position <> (select t1.position from top1 t1) and
(select count(*)
from t t2
where t2.position = t.position and
t2.points >= t.points
) <= 2;
This assumes that the points values are unique. Ties are much harder to deal with in SQLite.
I might recommend that you consider upgrading to SQLite version 3.25.0 or use another database. Such a query would be much simpler using ISO/ANSI standard window functions.
With window functions, it would look like:
select t.*
from (select t.*,
row_number() over (partition by position order by points desc) as seqnum,
first_value(position) over (order by points desc) as first_position
from t
) t
where seqnum <= 2 or position = first_position

Related

SQL server 2008: join 3 tables and select last entered record from child table against each parent record

I have following 3 tables and last entered reasoncode from Reasons table against each claimno in claims table.
Reasons:
Rid |chargeid| enterydate user reasoncode
-----|--------|-------------|--------|----------
1 | 210 | 04/03/2018 | john | 99
2 | 212 | 05/03/2018 | juliet | 24
5 | 212 | 26/12/2018 | umar | 55
3 | 212 | 07/03/2018 | borat | 30
4 | 211 | 03/03/2018 | Juliet | 20
6 | 213 | 03/03/2018 | borat | 50
7 | 213 | 24/12/2018 | umer | 60
8 | 214 | 01/01/2019 | john | 70
Charges:
chargeid |claim# | amount
---------|-------|---------
210 | 1 | 10
211 | 1 | 24.2
212 | 2 | 5.45
213 | 2 | 76.30
214 | 1 | 2.10
Claims:
claimno | Code | Code
--------|-------|------
1 | AH22 | AH22
2 | BB32 | BB32
Expected result would be like this:
claimno | enterydate | user | reasoncode
--------|-------------|--------|-----------
1 | 01/01/2019 | john | 70
2 | 26/12/2018 | umer | 55
I have applied many solutions but no luck. Following is the latest solution I was trying using SQL Server 2008 but still got incorrect result.
With x As
(
select r.chargeid,r.enterydate,ch.claimno from charges ch
join (select chargeid,max(enterydate) enterydate,user from Reasons group by chargeid) r on r.chargeid = ch.chargeid
)
select x.*,r1.user, r1.reasoncode from x
left outer join Reasons r1 on r1.chargeid = x.chargeid and r1.enterydate = x.enterydate
--group by x.claimno
Is this what you want?
select claimno, enterydate, user, reasoncode
from (select c.claimno, r.*,
row_number() over (partition by c.claimno order by r.entrydate desc) as seqnum
from charges c join
reasons r
on c.chargeid = r.chargeid
) cr
where seqnum = 1;
You can try using row_number()
select * from
(
select r.chargeid,r.enterydate,ch.claimno,user,reasoncode,
row_number() over(partition by ch.claimno order by r1.enterydate desc) as rn
from charges ch left outer join Reasons r1 on r1.chargeid = ch.chargeid
)A where rn=1

How to apply TOP statement to only 1 column while selecting multiple columns from a table?

I am trying to select multiple columns from a table, but I want to select top certain number of records based on one column. I tried this :
select roll_no ,marks as Percentage
from database
where marks in (select top (3) *
from database
where subject = ''
order by marks desc) order by percentage desc
and I am getting the error:
Only one expression can be specified in the select list when the
sub-query is not introduced with EXISTS or more than specified number
of records.
I also tried :
select roll_no ,marks as Percentage
from database
where marks in (select top (3) marks
from database
where subject = ''
order by marks desc) order by percentage desc
which returns the right result for some subjects but for others..it is displaying top marks from other subjects as well.
eg :
+---------+-------+
| roll_no | marks |
+---------+-------+
|10003 | 87 |
|10006 | 72 |
|10003 | 72 |
|10002 | 67 |
|10004 | 67 |
+---------+-------+
How to frame the query correctly?
sample data :
+---------+-------+---------+
| roll_no | marks |subject |
+---------+-------+---------+
|10001 | 45 | Maths |
|10001 | 72 | Science |
|10001 | 64 | English |
|10002 | 52 | Maths |
|10002 | 35 | Science |
|10002 | 75 | English |
|10003 | 52 | Maths |
|10003 | 35 | Science |
|10003 | 75 | English |
|10004 | 52 | Maths |
|10004 | 35 | Science |
|10004 | 75 | English |
+---------+-------+---------+
If I'm right and you are looking for the best 3 marks for each subject, then you can get it with the following:
DECLARE #SelectedSubject VARCHAR(50) = 'Maths'
;WITH FilteredSubjectMarks AS
(
SELECT
D.Subject,
D.Roll_no,
D.Marks,
MarksRanking = DENSE_RANK() OVER (ORDER BY D.Marks DESC)
FROM
[Database] AS D
WHERE
D.Subject = #SelectedSubject
)
SELECT
F.*
FROM
FilteredSubjectMarks AS F
WHERE
F.MarksRanking <= 3
You can use window functions to rank your marks column (specifically dense_rank, which allows duplicate rankings whilst retaining sequential numbering) and then return all rows with a rank of 3 or less:
declare #t table(roll_no int identity(1,1),marks int);
insert into #t(marks) values(2),(4),(5),(8),(6),(1),(3),(2),(1),(8);
with t as
(
select roll_no
,marks
,dense_rank() over (order by marks desc) as r
from #t
)
select *
from t
where r <= 3;
Output:
+---------+-------+---+
| roll_no | marks | r |
+---------+-------+---+
| 4 | 8 | 1 |
| 10 | 6 | 1 |
| 5 | 6 | 2 |
| 3 | 5 | 3 |
+---------+-------+---+

SQL, Update with most recent data info

I have 2 tables shown below:
Table 1
Student ID - DATE_NO - SCORE
Table 2
STUDENT_ID - DATE_NO - HT - WT
Table 1 has the physical test scores and the date of the test for each student while Table 2 lists their height (HT) and weight (WT) and the date they were measured.
Example Data:
Table 1
Student ID | DATE_NO | SCORE |
125 | 3 | 90 |
572 | 6 | 75 |
687 | 11 | 95 |
Table 2
Student_ID | DATE_NO | HT | WT |
125 | 2 | 70 | 150 |
125 | 3 | 72 | 155 |
125 | 6 | 72 | 160 |
572 | 2 | 70 | 200 |
572 | 5 | 70 | 225 |
572 | 8 | 70 | 215 |
572 | 9 | 70 | 220 |
687 | 4 | 65 | 140 |
687 | 7 | 67 | 150 |
687 | 11 | 70 | 155 |
687 | 12 | 67 | 160 |
I am not guaranteed to have the exact same DATE_NO for both HT/WT and the Test score date. I want the most recent HT and WT for each student when they took their physical test. Based on the example data above, the optimal join would give me the table below:
Modified Table 1
Student ID | DATE_NO | HT | WT |
125 | 3 | 72 | 155 |
572 | 6 | 70 | 225 |
687 | 11 | 70 | 155 |
I'd like to use the UPDATE statement on Table 1, so after altering Table 1 with HT int and WT int, I attempt to do the following:
UPDATE T1
SET HT = T2.HT, WT = T2.WT
FROM Table_1 as T1
INNER JOIN Table_2 AS T2 ON T1.STUDENT_ID = T2.STUDENT_ID
WHERE (T1.DATE_NO) >= (T2.DATE_NO)
But the result gives me the FIRST record that meets the criteria. Switching greater than to less than [ >= to <= ] Make the HT/WT for each student the entries for Month 6,8, and 12) when it should be month 3,8, and 11. Any suggestions?
FYI: Won't be able to apply any solutions till Friday.
Is it something like this you're looking for:
UPDATE Q
SET
T1_HT = T2_HT
, T1_WT = T2_WT
FROM
(
SELECT
T1.HT T1_HT
, T1.WT T1_WT
, T2.HT T2_HT
, T2.WT T2_WT
, ROW_NUMBER() OVER (PARTITION BY T1.STUDENT_ID ORDER BY T2.DATE_NO DESC) R
FROM
Table_1 T1
JOIN Table_2 T2 ON
T1.STUDENT_ID = T2.STUDENT_ID
AND T2.DATE_NO <= T1.DATE_NO
) Q
WHERE R = 1
SELECT ts.student_id,
ts.date_no,
hw.ht,
hw.wt
FROM test_scores ts,
ht_wt hw
WHERE hw.student_id = ts.student_id
AND hw.date_no <= ts.date_no
AND hw.date_no =
(SELECT max(date_no)
FROM ht_wt
WHERE date_no <= ts.date_no
AND student_id = ts.student_id)
sql fiddle here

SQL order by highest to lowest in one table referencing another table in an UPDATE

Hey all I have the following tables that need in order to get data from one that matches the other and have it from highest to lowest depending on the int of TempVersion.
UPDATE
net_Users
SET
net_Users.DefaultId = b.TId
FROM
(SELECT
TOP 1 IndivId,
TId
FROM
UTeams
WHERE
UTeams.[Active] = 1
ORDER BY
TempVersion DESC
) AS b
WHERE
net_Users.IndivId = b.IndivId
In the above I am trying to order from the highest TempVersion to the lowest.
The query above seems to just update 1 of those records with the TempVersion and stop there. I am needing it to loop to find all associated users with the same IndivId matching.
Anyone able to help me out with this?
sample data
net_Users:
name | DefaultId | IndivId | etc...
--------+-----------+---------+-------
Bob | | 87 | etc...
Jan | | 231 | etc...
Luke | | 8 | etc...
UTeams:
IndivId | TempVersion | etc...
--------+-------------+-------
8 | 44 | etc...
17 | 18 | etc...
8 | 51 | etc...
8 | 2 | etc...
7 | 22 | etc...
8 | 125 | etc...
87 | 10 | etc...
14 | 88 | etc...
8 | 5 | etc...
15 | 54 | etc...
65 | 11 | etc...
87 | 15 | etc...
39 | 104 | etc...
And the output I would be needing is (going to choose IndivId 8):
In net_users:
Name | DefaultId | IndivId | etc...
-----+-----------+---------+-------
Luke | 125 | 8 | etc...
Luke | 51 | 8 | etc...
Luke | 44 | 8 | etc...
Luke | 5 | 8 | etc...
Luke | 2 | 8 | etc...
I think this is what you were trying to do:
update net_Users
set net_Users.DefaultId = coalesce((
select top 1 TId
from UTeams
where UTeams.[Active] = 1
and net_Users.IndivId = UTeams.IndivId
order by u.TempVersion desc
)
,net_Users.DefaultId
)
another way using cross apply()
update n
set DefaultId = coalesce(x.Tid,n.DefaultId)
from net_Users as n
cross apply (
select top 1 TId
from UTeams as u
where u.[Active] = 1
and n.IndivId = u.IndivId
order by u.TempVersion desc
) as x
another way to do that with a common table expression and row_number()
with cte as (
select
n.IndivId
, n.DefaultId
, u.Tid
, rn = row_number() over (
partition by n.IndivId
order by TempVersion desc
)
from net_users as n
inner join UTeams as u
on n.IndivId = u.IndivId
where u.[Active]=1
)
update cte
set DefaultId = Tid
where rn = 1

Pair entry of every nth row with entry of every (n+1)th row

I have a result table
id | name | wins
----+-------------------
57 | Paul | 10
64 | Sven | 9
62 | Peter | 9
59 | Marina | 8
58 | Carlos | 4
60 | Pamela | 3
61 | Marcus | 2
63 | Hank | 1
Where I want to pair every nth entry with every (n+1)th entry, such that the resulting table looks like that:
id | name | id | name
----+-------------------
57 | Paul | 64 | Sven
62 | Peter | 59 | Marina
58 | Carlos | 60 | Pamela
61 | Marcus | 63 | Hank
Which SQL statement would achieve that?
;WITH cte AS (
SELECT *,ROW_NUMBER() OVER (ORDER BY Wins DESC) as RowNum
FROM
#Table
)
SELECT *
FROM
cte c1
LEFT JOIN cte c2
ON c1.RowNum + 1 = c2.RowNum
WHERE
c1.RowNum % 2 <> 0
Generate a ROW_NUMBER to use, seeing you have a third Column replace (SELECT NULL) in the Order by statement with that third column.
Then select all rows that are Odd Row numbers (remainder of RowNum divided by 2 <> 0 ) and self join back to itself with RowNum + 1. If you have an odd number of Rows you might consider using LEFT JOIN so you don't drop off the 1 row that won't have a match.