query left join or data insert? - mysql

OK - I have three tables, structured below
tbl_1
------
userid
teamid
teamname
eliminated
tbl_2
------
teamid
week
team
tbl_3
------
team
NFLname
nfl-schedule
------
week
time
awayteam (same as "team" in tbl_3)
hometeam (same as "team" in tbl_3)
This is a query on a "survivor league" where I need to get the teamid, teamname, eliminated, team, week, and NFLname for each week. If a user hasn't selected a team for this week, week 2 for instance, I want to see a blank row for that week. I"m assuming I could backfill the rows into the database for each teamid, but was wondering if I could do this simply with sql and some inner joins?
select a.teamid, a.teamname, a.eliminated, b.team, b.week, c.NFLnamefrom `tbl_1` a
left join `tbl_2` b on a.teamid = b.teamid
left join `tbl_3` c on c.`team` = b.team
where a.userid = XXX

You can use left joins for this, but you have to take care of the order you use the tables.
Another problem would be when there is a pick for this week from another user, so I think you would have to use a subquery like this:
select w.week,t.teamid,t.teamname,t.eliminated,t.team,t.NFLname
from nfl-schedule w
left join (
select a.teamid, a.teamname, a.eliminated, b.team, b.week, c.NFLname
from `tbl_1` a
left join `tbl_2` b on a.teamid = b.teamid
left join `tbl_3` c on c.`team` = b.team
where a.userid = XXX
) t on w.week = t.week

Related

SQL subquery to return MIN of a column and corresponding values from another column

I'm trying to query
number of courses passed,
the earliest course passed
time taken to pass first course, for each student who is not currently expelled.
The tricky part here is 2). I constructed a sub-query by mapping the course table onto itself but restricting matches only to datepassed=min(datepassed). The query appears to work for a very sample, but when I try to apply it to my full data set (which would return ~1 million records) the query takes impossibly long to execute (left it for >2 hours and still wouldn't complete).
Is there a more efficient way to do this? Appreciate all your help!
Query:
SELECT
S.id,
COUNT(C.course) as course_count,
C2.course as first_course,
DATEDIFF(MIN(C.datepassed),S.dateenrolled) as days_to_first
FROM student S
LEFT JOIN course C
ON C.studentid = S.id
LEFT JOIN (SELECT * FROM course GROUP BY studentid HAVING datepassed IN (MIN(datepassed))) C2
ON C2.studentid = C.studentid
WHERE YEAR(S.dateenrolled)=2013
AND U.id NOT IN (SELECT id FROM expelled)
GROUP BY S.id
ORDER BY S.id
Student table
id status dateenrolled
1 graduated 1/1/2013
3 graduated 1/1/2013
Expelled table
id dateexpelled
2 5/1/2013
Course table
studentid course datepassed
1 courseA 5/1/2014
1 courseB 1/1/2014
1 courseC 2/1/2014
1 courseD 3/1/2014
3 courseA 1/1/2014
3 couseB 2/1/2014
3 courseC 3/1/2014
3 courseD 4/1/2014
3 courseE 5/1/2014
SELECT id, course_count, days_to_first, C2.course first_course
FROM (
SELECT S.id, COUNT(C.course) course_count,
DATEDIFF(MIN(datepassed),S.dateenrolled) as days_to_first,
MIN(datepassed) min_datepassed
FROM student S
LEFT JOIN course C ON C.studentid = S.id
WHERE S.dateenrolled BETWEEN '2013-01-01' AND '2013-12-31'
AND S.id NOT IN (SELECT id FROM expelled)
GROUP BY S.id
) t1 LEFT JOIN course C2
ON C2.studentid = t1.id
AND C2.datepassed = t1.min_datepassed
ORDER BY id
I would try something like:
SELECT s.id, f.course,
COALESCE( DATEDIFF( c.first_pass,s.dateenrolled), 0 ) AS days_to_pass,
COALESCE( c.num_courses, 0 ) AS courses
FROM student s
LEFT JOIN
( SELECT studentid, MIN(datepassed) AS first_pass, COUNT(*) AS num_courses
FROM course
GROUP BY studentid ) c
ON s.id = c.studentid
JOIN course f
ON c.studentid = f.studentid AND c.first_pass = f.datepassed
LEFT JOIN expelled e
ON s.id = e.id
WHERE s.dateenrolled BETWEEN '2013-01-01' AND '2013-12-31'
AND e.id IS NULL
This query assumes a student can pass only one course on a given day, otherwise you can get more than one row for a student as its possible to have many first courses.
For performance it would help to have an index on dateenrolled in student table and a composite index on (studentid,datepassed) in courses table.

SELECT latest data from 2 joined tables

I have looked pretty hard through other SQL query questions but have been unable to narrow down a response that seems to work in my case... so here goes.
I have two mySQL tables:
players:
pid
pname
player_stats:
pid
statdate
rank
score
I would like to show output of the players name and their latest score and rank like this:
player1 | rank10 | 123345
player2 | rank15 | 480993
I have played around with max(statdate) and GROUP BY on pname but the closest I have gotten is below, which gives me the right number of rows but not the latest date (thus not the latest rank or score).
SELECT p.pname, s.rank, s.score
FROM players p INNER JOIN player_stats s ON p.pid = s.pid
GROUP BY p.pname
as mentioned this is close but the rank/score are not always the last date's
You can have an extra join in a subquery which separately get the latest date for every pid.
The subquery contains only two columns: pid and statdate. To get the other columns you need to join it with the other table.
SELECT a.pname, b.rank, b.score
FROM players a
INNER JOIN player_stats b
ON a.pid = b.pid
INNER JOIN
(
SELECT pid, MAX(statdate) statdate
FROM player_stats
GROUP BY pid
) c ON b.pid = c.pid
AND b.statdate = c.statdate

Select data based on another table

I have three tables, I'll just list the important columns
db_players
id | name
players
id | teamid | careerid
db_teams
The db_teams id links to the players teamid.
I need to run a query where I select rows from db_players as long as db_teams.id isn't in a row in players as teamid where the careerid = 1.
I've never attempted this type of query with mysql before, I know I could do two queries and involve php but I'm intrigued as to whether it's possible with a pure db query.
Thanks.
EDIT - simpler now.
SELECT dp.first_name
FROM tbl_foot_career_db_players dp
INNER JOIN tbl_foot_career_players p
ON p.playerid != dp.id
WHERE p.careerid = 1
The idea is that I want to return all rows from tbl_foot_career_db_players WHERE the id from that table isn't present in a row in tbl_foot_career_players in the column playerid. And the tbl_foot_career_players.careerid must also equal 1.
List all db_players that are not in players with career = 1
SELECT d.*
FROM db_players d
LEFT JOIN players p
ON p.player_id = d.id
AND p.career = 1
WHERE p.id IS NULL
Try to JOIN them with db_players id is not in players teamid.
SELECT db_players.* FROM db_players
LEFT JOIN players ON players.id != db_players.id
LEFT JOIN db_teams ON players.teamid = db_teams.id
WHERE careerid = 1

WHERE value IS NOT IN (subquery)

I've been struggling with this query.
I have two tables. One with coupons and Invoicenumbers. One with Invoicenumbers and customer names.
I need to get the customers who have not used a given coupon.
Here are the tables:
Promotion table:
Promotions
Invoice | Coupon
----------------
1 | couponA
2 | couponB
3 | couponB
Orders Table:
Orders
Invoice | Customer
------------------
1 | Jack
2 | Jack
3 | Jill
So Jack has used coupons A and B. And Jill has only used coupon B.
If my query were select customers who have not used coupon A, I should get Jill.
This works, but it seems clumsy and slow. Is there a better way?
SELECT Customer
FROM Promotions INNER JOIN Orders
ON Promotions.Invoice = Orders.Invoice
WHERE Customer NOT IN(
SELECT Customer
FROM Promotions INNER JOIN Orders
ON Promotions.Invoice = Orders.Invoice
WHERE Coupon = couponA)
GROUP BY Customer
Thanks for looking!
edit:
Here's an SQLFiddle schema
http://sqlfiddle.com/#!2/21d31/6
Updated: We should use prefer to use joins for better performance when its easy to do for us. Join vs. sub-query
Sql Fiddle
Select distinct Customer from orders o
join
(
SELECT distinct Customer as changedname FROM Orders o2
join
(
Select distinct invoice from Promotions where Coupon='couponA'
) t3
on o2.invoice = t3.invoice
) t2
on o.customer != t2.changedname;
Note: I changed column name customer for t3 because two joined tables must have different column names
Explanation:
Using inner or sub query is expensive when you have big data. use joins instead, lets learn converting subquery to join
With Subquery We had:
Select distinct Customer from orders where customer not in
(SELECT distinct Customer FROM Orders where invoice in
(Select distinct invoice from Promotions where Coupon='couponA'));
Converting sub-query to join
First step:
Select distinct Customer from orders o
join
(
SELECT distinct Customer as changedname FROM Orders where invoice in
(Select distinct invoice from Promotions where Coupon='couponA')
) t2
on o.customer != t2.changedname;
2nd step:
Select distinct Customer from orders o
join
(
SELECT distinct Customer as changedname FROM Orders o2 where invoice
join
(
Select distinct invoice from Promotions where Coupon='couponA'
) t3
on o2.invoice = t3.invoice
) t2
on o.customer != t2.changedname;
And that's it, much faster for tables having numerous rows
Original answer:
Use not in. Have a look.
Select distinct Customer from orders where customer not in
(SELECT distinct Customer FROM Orders where invoice in
(Select distinct invoice from Promotions where Coupon='couponA'));
Edit I have added distinct to make query faster
SQL Fiddle
SELECT DISTINCT o2.customer FROM ORDER o2
LEFT JOIN (promotions p1
JOIN Orders o1 ON p1.cuopon = 'CuoponA' AND p1.invoice = o1.invoice ) p3
ON o2.customer = p3.customer
WHERE p3.customer IS NULL
Try this query instead:
SELECT DISTINCT Customer
FROM Orders o1
WHERE NOT EXISTS (
SELECT 1
FROM Orders o2
INNER JOIN Promotions ON Promotions.Invoice = o2.Invoice
WHERE o1.Customer = o2.Customer AND Coupon = 'couponB')
The idea is to get rid of the GROUP BY by removing a join in the top part of the query, and also eliminate the NOT IN by making a coordinated subquery.
Here is a link to sqlfiddle.
Try this with a right join
SELECT Customer, Coupon
FROM Promotions
RIGHT JOIN Orders ON Promotions.Invoice = Orders.Invoice
AND Coupon = 'couponA'
GROUP BY Customer
HAVING Coupon IS NULL

Multiple Left Joins

I'm trying to create a single query containing optional aggregate values from multiple tables and having some difficulty. I'm going to try to simplify my problem to as few fields as possible.
Tables
applications
appid | pageid
contests
id | appid | winnerid
signups
id | contestid | firstname | lastname
referrals
id | signupid
tickets
id | signupid
Purpose of Query
I'm trying to combine the applications and contests tables based on the pageid parameter, join the signups table to get the winner when available, and then a count of all signups, referrals, and tickets or simply a zero value when none is available.
What I've got so far
SELECT t1.*, `Winner`.Name AS Winner, IFNULL(`srt`.Signups,0) AS Signups,
IFNULL(`srt`.Referrals,0) AS Referrals,
IFNULL(`srt`.Tickets,0) AS Tickets
FROM applications a, (contests c LEFT JOIN
/* Join signups table to retrieve winner's first/last name */
(SELECT id, contestid, CONCAT(firstname, ' ' , lastname) AS Name
FROM signups) `Winner`
ON c.winnerid = `Winner`.id
AND c.contestid = `Winner`.contestid) LEFT JOIN
/* Join signups, referrals, and tickets to retrieve counts */
(SELECT s.signupid, COUNT(*) AS Signups, Referrals, Tickets
FROM (signups s LEFT JOIN
(SELECT r.signupid, COUNT(r.id) AS Referrals
FROM signups s, referrals r
WHERE s.signupid = r.signupid) `Referrals`
ON s.signupid = `Referrals`.signupid) LEFT JOIN
(SELECT t.signupid, COUNT(t.id) AS Tickets
FROM signups s, tickets t
WHERE s.signupid = t.signupid) `Tickets`
ON s.signupid = `Tickets`.signupid) `srt`
ON signupid = `srt`.signupid
WHERE a.id = c.applicationid
AND a.pageid = #pageid
ORDER BY c.id IN (SELECT id FROM contests WHERE active = 1) desc, c.addeddate desc;
Problem
I'm positive this isn't the most efficient way to do what I'm trying to do and regardless it doesn't work. The values for the signups/referrals/tickets simply returns a count for every record in each of the tables. The winner join works fine, and if I limit it to just the signups, that works as well. The signups table is linked to the contests table, and then the referrals and tickets are related directly to the signups table. Any help on this complex query would be greatly appreciated.
Forgive me if I am misunderstanding, and also for my lack of knowledge of differences between MSSQL and mysql, but I would try something like this:
SELECT
( SELECT CONCAT(IFNULL(firstname,''), ' ' , IFNULL(lastname,''))
FROM Signups
WHERE id = c.winnerid) as Winner,
( SELECT COUNT(*)
FROM Signups
WHERE contestid = c.id) as Signups,
( SELECT COUNT(*)
FROM referrals r LEFT JOIN signups su ON r.signupid = su.id
WHERE su.contestid = c.id) as Referrals,
( SELECT COUNT(*)
FROM tickets t LEFT JOIN signups su ON t.signupid = su.id
WHERE su.contestid = c.id) as Tickets
FROM
Contests c