row counter with condition in two different columns - sql

I have the following tables with sport results (e.g. football):
tblGoals (RowId, GameRowIdm PlayerRowId, TeamRowId, GoalMinute)
RowId | GameRowId | PlayerRowId | TeamRowId | GoalMinute
--------------------------------------------------------
1 | 1 | 1 | 1 | 25
2 | 1 | 2 | 2 | 45
3 | 1 | 3 | 1 | 66
tblPlayers (RowId, PlayerName)
RowId | PlayerName
------------------
1 | John Snow
2 | Frank Underwood
3 | Jack Bauer
tblGames (RowId, TeamHomeRowId, TeamGuestRowId)
RowId | TeamHomeRowId | TeamGuestRowId | GameDate
---------------------------------------------------
1 | 1 | 2 | 2015-01-01
Now I want get a list of all goals. The list should look like this:
GoalMinute | PlayerName | GoalsHome | GoalsGuest
-----------------------------------------------------
25 | John Snow | 1 | 0
45 | Frank Underwood | 1 | 1
66 | Jack Bauer | 2 | 1
GoalsHome and GoalsGuest should be a counter of the shot goals for the team. So e.g. if you check the last row, the result is 2:1 for home team.
To get this list of goals, I used this statement:
SELECT t_gol.GoalMinute,
t_ply.PlayerName,
CASE WHEN
t_gol.TeamRowId = t_gam.TeamHomeRowId
THEN ROW_NUMBER() OVER (PARTITION BY t_gam.TeamHomeRowId ORDER BY t_gam.TeamHomeRowId)
END AS GoalsHome,
CASE WHEN
t_gol.TeamRowId = t_gam.TeamGuestRowId
THEN ROW_NUMBER() OVER (PARTITION BY t_gam.TeamGuestRowId ORDER BY t_gam.TeamGuestRowId)
END AS GoalsGuest
FROM dbo.tblGoalsFussball AS t_gol
LEFT JOIN dbo.tblPlayersFussball AS t_ply ON (t_ply.RowId = t_gol.PlayerRowId)
LEFT JOIN dbo.tblGames AS t_gam ON (t_gam.RowId = t_gol.GameRowId)
WHERE t_gol.GameRowId = #match_row
But what I get is this here:
GoalMinute | PlayerName | GoalsHome | GoalsGuest
-----------------------------------------------------
25 | John Snow | 1 | NULL
45 | Frank Underwood | NULL | 2
66 | Jack Bauer | 3 | NULL
Maybe ROW_NUMBER() is the wrong approach?

I would do the running total using sum() as a windowed aggregate function with the over ... clause, which works in SQL Server 2012+.
select
g.RowId, g.GameDate, t.GoalMinute, p.PlayerName,
GoalsHome = COALESCE(SUM(case when TeamRowId = g.TeamHomeRowId then 1 end) OVER (PARTITION BY gamerowid ORDER BY goalminute),0),
GoalsGuest = COALESCE(SUM(case when TeamRowId = g.TeamGuestRowId then 1 end) OVER (PARTITION BY gamerowid ORDER BY goalminute),0)
from tblGoals t
join tblPlayers p on t.PlayerRowId = p.RowId
join tblGames g on t.GameRowId = g.RowId
order by t.GameRowId, t.GoalMinute
Another approach (that also works in older versions) is to use a self-join and sum up the rows with lower goalminutes. For ease of reading I've used a common table expression to split the goals into two columns for home and guest team:
;with t as (
select
g.GoalMinute, g.PlayerRowId, g.GameRowId,
case when TeamRowId = ga.TeamHomeRowId then 1 end HomeGoals,
case when TeamRowId = ga.TeamGuestRowId then 1 end GuestGoals
from tblGoals g
join tblGames ga on g.GameRowId = ga.RowId
)
select
g.RowId, g.GameDate, t.GoalMinute, p.PlayerName,
GoalsHome = (select sum(coalesce(HomeGoals,0)) from t t2 where t2.GoalMinute <= t.GoalMinute and t2.GameRowId = t.GameRowId),
GoalsGuest = (select sum(coalesce(GuestGoals,0)) from t t2 where t2.GoalMinute <= t.GoalMinute and t2.GameRowId = t.GameRowId)
from t
join tblPlayers p on t.PlayerRowId = p.RowId
join tblGames g on t.GameRowId = g.RowId
order by t.GameRowId, t.GoalMinute
The CTE isn't necessary though, you could just as well use a derived table
Sample SQL Fiddle

I think the easiest way is with subqueries..
SELECT
tgs.GoalMinute,
tpl.PlayerName,
( SELECT
COUNT(t.RowId)
FROM
tblgoals AS t
WHERE t.GoalMinute <= tgs.GoalMinute
AND t.GameRowId = tgm.RowId
AND t.TeamRowId = tgm.TeamHomeRowId
) AS HomeGoals,
( SELECT
COUNT(t.RowId)
FROM
tblgoals AS t
WHERE t.GoalMinute <= tgs.GoalMinute
AND t.GameRowId = tgm.RowId
AND t.TeamRowId = tgm.TeamGuestRowId
) AS GuestGoals
FROM
tblgoals AS tgs
JOIN tblplayers AS tpl ON tgs.RowId = tpl.RowId
JOIN tblGames AS tgm ON tgm.RowId = tgs.GameRowId
ORDER BY tgs.GoalMinute

Related

Linking tables on multiple criteria

I've got myself in a bit of a mess on something I'm doing where I'm trying to get two tables linked together based on multiple bits of info.
I want to link one table to another based on the basic rules of(in this hierarchy)
where main linking is where orderid matches between the two tables
records from table 2 where valid=Y,
from those i want the valid records which has the highest seqn1 number and then from those the one that has the highest seqn2 value
table1
orderid | date | otherinfo
223344 | 22/10/2020 | okokkokokooeodijjf
table2
orderid | seqn1 | seqn2 | valid | additonaldata
223344 | 1 | 3 | y | sdfsfsf
223344 | 2 | 1 | y | sffferfr
223344 | 2 | 2 | y | sfrfrefr -- This row
223344 | 2 | 3 | n | rfrg66rr
223344 | 2 | 4 | n | adwere
223344 | 3 | 4 | n | adwere
so would want the final record to be
orderid | date | otherinfo | seqn1 | seqn2 | valid | additonaldata
223344 | 22/10/2020 | okokkokokooeodijjf | 2 | 2 | y | sfrfrefr
I started off with the code below but I'm not sure I'm doing it right and I can't seem to get it to pay attention to the valid flag when i try to add it in.
SELECT * FROM table1
left JOIN table2
ON table1.orderid = table2.orderid
AND table2.seqn1 = (SELECT MAX(table2.seqn1) FROM table2 WHERE table1.orderid = table2.orderid)
AND table2.seqn2 = (SELECT MAX(table2.seqn2) FROM table2 WHERE table1.orderid = table2.orderid
AND table2.seqn1 = (SELECT MAX(table2.seqn1) FROM table2 WHERE table1.orderid = table2.orderid))
Could someone help me amend the code please.
Use row_number analytic function with partition by orderid and order by SEQNRs in the order you need. No need for multiple subselects. To add more selections for the single row, use CASE to map your values to numbers and order by them also.
Fiddle here.
with l as (
select *,
rank() over(partition by orderid order by seqn1 desc, seqn2 desc) as rn
from line
where valid = 'y'
)
select *
from header as h
join l
on h.orderid = l.orderid
and l.rn = 1
How about something like this:
;
with cte_table2 as
(
SELECT ordered
,MAX(seqn1) as seqn1
,MAX(seqn2) as seqn2
FROM table2
where valid = 'y'
group by ordered --check if you need to add 'valid' to the group by but I don't think so.
)
SELECT
t1.*
,t3.otherinfo
--,t3.[OtherFields]
from table1 t1
inner join cte_table2 t2 on t1.orderid = t2.orderid -- first match on id
left join table2 t3 on t3.orderid = t2.orderid and t3.seqn1 = t2.seqn1 and t3.seqn2 = t2.seqn2

Optimized Query that selects 3 salaries from the past 2 years

I have a query that will show the past 3 salaries within a 2 year period (if there are three) I have the query up and running the problem is, is its extremely slow... I'm wondering if there was a better way to write this query. I'm some-what new to oracle.
Here are my Tables
TABLE 1: Salary
ASSIGN_ID | start_date | end_date | salary |
1 | 11/27/2017 | 1/05/2018 | 50000.0 |
2 | 1/06/2018 | 6/08/2018 | 76000.0 |
3 | 6/09/2018 | 12/31/4712 | 80500.0 |
TABLE 2: Assignments
ASSIGN_ID | per_ID | start_date | end_date |
1 | 1 | 11/2/2017 | 1/05/2018 |
2 | 1 | 1/06/2018 | 6/08/2018 |
3 | 1 | 6/09/2018 | 12/31/4712 |
4 | 2 | 5/12/2016 | 7/18/2017 |
5 | 2 | 7/19/2017 | 12/31/4712 |
Table 3: Person
per_id | first_name | last_name |
1 | John | Smith |
2 | Jane | Doe |
Our end dates default to 12/31/4712 if they're are currently active in the assignment
My Query looks like this:
SELECT
per.first_name,
per.last_name,
(CASE WHEN sal1.start_date >= add_months(CURRENT_DATE, -24)
THEN sal1.salary
ELSE NULL END) oldest_salary,
(CASE WHEN sal2.start_date >= add_months(CURRENT_DATE, -24)
THEN sal2.salary
ELSE NULL END) prior_salary,
sal3.salary current_salary,
FROM
person per
INNER JOIN assignments asg1 ON asg1.per_id = per.per_id
INNER JOIN assignments asg2 ON asg2.per_id = asg1.per_id
INNER JOIN assignments asg3 ON asg3.per_id = asg2.per_id
INNER JOIN salary sal1 ON sal1.assign_id = asg1.assign_id
INNER JOIN salary sal2 ON sal2.assign_id = asg2.assign_id
INNER JOIN salary sal3 ON sal3.assign_id = asg3.assign_id
WHERE asg3.start_date =
(SELECT MAX(asg.start_date
FROM assignments asg
WHERE asg.assign_id = asg3.assign_id)
AND (asg3.start_date - 1) BETWEEN asg2.start_date and asg2.end_date
AND (asg2.start_date - 1) BETWEEN asg1.start_date and asg1.end_date
AND sal1.salary != sal2.salary
AND sal2.salary != sal3.salary
ORDER BY 2,1
Is there a simpler way to do this? because when I run my script it processes forever. I think I might need better joins. like I said i'm new and my understanding of joins is weak.
A simpler form:
SELECT
z.first_name,
z.last_name,
--typical cross-db compatible pivot method
MAX(CASE WHEN z.rown = 1 THEN z.salary END) as recentsalary,
MAX(CASE WHEN z.rown = 2 THEN z.salary END) as oldersalary,
MAX(CASE WHEN z.rown = 3 THEN z.salary END) as oldestsalary
FROM
(
SELECT
per.first_name,
per.last_name,
--number assignments from 1=recent to N older
row_number() over(partition by a.per_id order by a.start_date desc) rown
s.salary
FROM --join up all
person p
INNER JOIN assignments a ON a.per_id = p.per_id
INNER JOIN salary s ON s.assign_id = s.assign_id
WHERE a.end_date > ADD_MONTHS(SYSDATE, -36) --only recent 3 years
) z
WHERE z.rown <= 3 --only the most recent 3 assignments
GROUP BY first_name, last_name --achieve pivot
It works by:
Join up all data so people, assignments and salaries are known
Only consider assignments ended since 3 years ago
Number the assignments in youngest to oldest order (1=youngest)
Pivot the top 3 numberings into 3 columns for recent, older and oldest salary, per person

Need T-SQL query to get multiple choice answer if matches

Example:
Table Question_Answers:
+------+--------+
| q_id | ans_id |
+------+--------+
| 1 | 2 |
| 1 | 4 |
| 2 | 1 |
| 3 | 1 |
| 3 | 2 |
| 3 | 3 |
+------+--------+
User_Submited_Answers:
| q_id | sub_ans_id |
+------+------------+
| 1 | 2 |
| 1 | 4 |
| 2 | 1 |
| 3 | 1 |
| 3 | 2 |
| 3 | 4 |
+------+------------+
I need a T-SQL query if this rows matches count 1 else 0
SELECT
t1.q_id,
CASE WHEN COUNT(t2.sub_ans_id) = COUNT(*)
THEN 1
ELSE 0 END AS is_correct
FROM Question_Answers t1
LEFT JOIN User_Submited_Answers t2
ON t1.q_id = t2.q_id AND
t1.ans_id = t2.sub_ans_id
GROUP BY t1.q_id
Try the following code:
select qa.q_id,case when qa.ans_id=sqa.ans_id then 1 else 0 end as result from questionans qa
left join subquestionans sqa
on qa.q_id=sqa.q_id and qa.ans_id=sqa.ans_id
This should give you expected result for every question.
select q_id, min(Is_Correct)Is_Correct from (
select Q.q_id,case when count(A.sub_ans_id)=count(*) then 1 else 0 end as Is_Correct
from #Q Q left join #A A on Q.q_id=A.q_id and Q.ans_id=A.sub_ans_id
group by Q.q_id
UNION ALL
select A.q_id,case when count(Q.ans_id)=count(*) then 1 else 0 end as Is_Correct
from #Q Q right join #A A on Q.q_id=A.q_id and Q.ans_id=A.sub_ans_id
group by A.q_id ) I group by q_id
MySQL solution (sql fiddle):
SELECT tmp.q_id, MIN(c) as correct
FROM (
SELECT qa.q_id, IF(qa.q_id = usa.q_id, 1, 0) as c
FROM question_answers qa
LEFT JOIN user_submited_answers usa
ON qa.q_id = usa.q_id AND qa.ans_id = usa.sub_ans_id
UNION
SELECT usa.q_id, IF(qa.q_id = usa.q_id, 1, 0) as c
FROM question_answers qa
RIGHT JOIN user_submited_answers usa
ON qa.q_id = usa.q_id AND qa.ans_id = usa.sub_ans_id
) tmp
GROUP BY tmp.q_id;
Now, step by step explanation:
In order to get the right output we will need to:
extract from question_answers table the answers which were not filled in by the user (in your example: q_id = 3 with ans_id = 3)
extract from user_submited_answers table the wrong answers which were filled in by the user (in your example: q_id = 3 with sub_ans_id = 4)
To do that we can use a full outer join (for mysql left join + right join):
SELECT *
FROM question_answers qa
LEFT JOIN user_submited_answers usa
ON qa.q_id = usa.q_id AND qa.ans_id = usa.sub_ans_id
UNION
SELECT *
FROM question_answers qa
RIGHT JOIN user_submited_answers usa
ON qa.q_id = usa.q_id AND qa.ans_id = usa.sub_ans_id;
From the previous query results, the rows which we are looking for (wrong answers) contains NULL values (based on the case, in question_answers table or user_submited_answers table).
The next step is to mark those rows with 0 (wrong answer) using an IF or CASE statement: IF(qa.q_id = usa.q_id, 1, 0).
To get the final output we need to group by q_id and look for 0 values in the grouped rows. If there is at least one 0, the answer for that question is wrong and it should be marked as that.
Check sql fiddle: SQL Fiddle

SQL counting and grouping with condition

On my postgresql db I have a score table with the columns user_id, item_id, succeeded (bool) and created_at.
I want to count the number of succeeded items for each user and each item (where succeeded = 't'). But I only want to count succeeded item occurred after the last failure.
I try the following code without success.
SELECT COUNT(*), item_id
FROM score
WHERE score.succeeded = 't' AND user_id = XX
GROUP BY score.item_id
HAVING MAX(score.created_at) > score.created_at AND score.succeeded = 'f'
exemple
data:
user_id | item_id | succeeded | created_at
------------------------------------------
12 | 1 | true | 2016-04-01
12 | 1 | false | 2016-04-02
12 | 1 | true | 2016-04-03
12 | 2 | true | 2016-04-01
12 | 2 | true | 2016-04-02
12 | 2 | true | 2016-04-03
12 | 3 | true | 2016-04-01
12 | 3 | true | 2016-04-02
12 | 3 | false | 2016-04-03
Excepted result (for user 12):
item_id | succeeded_count
-------------------------
1 | 1
2 | 3
3 | 0
Add a NOT EXISTS condition. It will make sure only true rows having no later false row are counted.
SELECT COUNT(*), s1.item_id
FROM score s1
WHERE score.succeeded = 't'
AND s1.user_id = XX
AND NOT EXISTS (select 1 from score s2
where s2.user_id = s1.user_id
and s2.item_id = s1.item_id
and s2.succeeded = 'f'
and s2.created_at > s1.created_at)
GROUP BY s1.item_id
You should be filtering the rows in the where clause. Assuming that succeeded only takes the values of 'f' or 't':
select s.item_id, count(*)
from score s
where s.created_at > (select max(s2.created_at)
from score s2
where s2.item_id = s.item_id and s2.succeeded = 'f'
)
group by s.item_id;
Note: This will filter out values of 0. You can get all of them by using join instead:
select s.item_id, count(s2.item_id)
from scores s left join
(select item_id, max(created_at) maxca
from scores
where succeeded = 'f'
group by item_id
) ss
on s.item_id = ss.item_id and s.created_at >= ss.maxca
group by item_id;
If succeeded can take on values other than 'f', then you can add a where clause to the outer query or use conditional aggregation.

SQL Finding multiple values difficulty

I'm trying to generate the correct SQL for a project.
Here is a sample dataset:
DateTime | EmpID | Function | Location
--------------------------------------------------
1/23/2015 2:00PM | 123 | 1 | 1
1/23/2015 2:10PM | 123 | 2 | 1
1/23/2015 2:20PM | 123 | 1 | 2
1/23/2015 2:40PM | 123 | 2 | 2
1/24/2015 2:00PM | 321 | 1 | 2
1/24/2015 2:15PM | 321 | 2 | 2
1/24/2015 2:30PM | 321 | 1 | 3
I need to pull a count of all records where functionid = 1 and location MUST EQUAL both 1 and 2. So the first row and the third row would be returned and considered a count of 1.
Hopefully I'm making sense with this. Basically I need to know how many times an employee was at two locations. Any help would be appreciated.
Group by EmpId and count locations.
SELECT *
FROM MyTable T1
WHERE Function = 1 AND
NOT EXISTS (SELECT 1
FROM MyTable T2
WHERE T1.EmpId = T2.EmpId AND
T1.Function = T2.Function AND
T2.Location NOT IN (1, 2))
GROUP BY EmpId
HAVING Count(DISTINCT Location) > 1
Have not tested it but think this will work
SELECT EmpID, COUNT(EmpID) AS NumOfTimes
From [Table Name]
WHERE FunctionID = 1 AND (Location = 1 OR Location = 2)
GROUP BY EmpID
HAVING NumOfTimes = 2
SELECT EmpID , COUNT(*) total, COUNT (CASE WHEN Location = 1 THEN 1 END) was_in_1,COUNT (CASE WHEN Location = 2 THEN 1 END) was_in_2
FROM table
WHERE Function = 1
GROUP BY EmpID
HAVING MAX(CASE WHEN Location = 1 THEN 1 ELSE 0 END) = MAX(CASE WHEN Location = 2 THEN 1 ELSE 0 END)
but if you would like to know if the employee was in any 2 locations then jarlh gave the right comment
group by EmpID
having count(distinct location) >= 2
SELECT A.EmpID, COUNT(*)
FROM YOURTABLENAME A
INNER JOIN YOURTABLENAME B
ON A.EmpID= B.EmpID
WHERE A.Location = 1 and B.Location = 2
and A.Function = B.Function and A.Function = 1
GROUP BY A.EmpID