Using CASE, WHEN, THEN, END in a select query with MySQL - mysql

I'm working on a baseball related website. I have a table with a batting lineup for two baseball teams:
+----+----------+--------------+--------+
| id | playerId | battingOrder | active |
+----+----------+--------------+--------+
Batting order is an integer between 1 and 20. This corresponds to the following logic:
Batting Order 1-9 — Away Team Lineup
Batting Order 10 — Away Team Pitcher
Batting Order 11-19 — Home Team Lineup
Batting Order 20 — Home Team Pitcher
The active field is a tinyint 0 or 1, representing the pitcher on the mound and the batter on the plate.
Known Fact: There will always be one active pitcher from one team and one active batter from the opposite team.
I need to write a query that returns a row for a home team player that corresponds to the next batter in the battingOrder. (the one that that occurs after the active batter's battingOrder)
Example:
If the player in battingOrder 13 is active, the query should return the player in batting order 14.
If the player in battingOrder 19 is active, the query should return the player in batting order 11 (the lineup loops back to the first player for the team).
I've never used a CASE query before, but I came up with the following:
SELECT *
FROM lineups
WHERE battingOrder =
CASE (
SELECT battingOrder
FROM lineups
WHERE battingOrder > 10 AND active = 1
LIMIT 1
)
WHEN 11 THEN 12
WHEN 12 THEN 13
WHEN 13 THEN 14
WHEN 14 THEN 15
WHEN 15 THEN 16
WHEN 16 THEN 17
WHEN 17 THEN 18
WHEN 18 THEN 19
WHEN 19 THEN 11
END
LIMIT 1;
It seems to work, but what edge cases and/or pitfalls have I walked into? Is this efficient? I'm particulary interested in a solution to my problem that does not use a nested query.

Select LNext.player As NextPlayer
From lineups As L
Left Join lineups As LNext
On LNext.BattingOrder Between 11 And 20
And LNext.BattingOrder = Case
When L.BattingOrder = 19 Then 11
Else L.BattingOrder + 1
End
Where L.battingOrder Between 11 And 20
And L.active = 1
In fact, you could make it handle both home and away like so:
Select LNext.player As NextPlayer
From lineups As L
Left Join lineups As LNext
On LNext.BattingOrder = Case
When L.BattingOrder = 19 Then 11
When L.BattingOrder = 9 Then 1
Else L.BattingOrder + 1
End
Where L.active = 1

Related

MySQL Create Ordinal

I'm trying to create a temp table or view that I can use for charting league members changing handicaps over the season. This requires creating a pivot table.
Even before that, however, I need to create an ordinal column to represent the nth match the person played that season. I can't use date_played since players can play on all different dates, and I'd like each player's 2nd match to line up vertically, and their third to do so as well, etc.
I used code from the answer to this question which seemed like it should work. Here's a copy of the relevant section:
SELECT books.*,
if( #libId = libraryId,
#var_record := #var_record + 1,
if(#var_record := 1 and #libId := libraryId, #var_record, #var_record)
) AS Ordinal
FROM books
JOIN (SELECT #var_record := 0, #libId := 0) tmp
ORDER BY libraryId;
`lwljhb_lwl_matches` # One record for each match played
id
date_played
other fields about the match
id date_played
1 2017-08-23
2 2017-08-29
3 2017-09-26
4 2017-08-24
5 2017-09-02
6 2017-09-21
7 2017-08-24
8 2017-08-31
9 2017-09-05
10 2017-09-15
11 2017-09-17
`lwljhb_users`
id
display_name
id display_name
1 Alan
2 Bill
3 Dave
`lwljhb_lwl_players` # One record per player per match
id
match_id # Foreign key to matches.id
player_id # Foreign key to users.id
end_hcp
other fields about the player's performance in the linked match
id match_id player_id end_hcp
1 1 1 720
2 2 1 692
3 3 1 694
4 4 2 865
5 5 2 868
6 6 2 842
7 7 3 363
8 8 3 339
9 9 3 332
10 10 3 348
11 11 3 374
Normally there would be two records in PLAYERS for each MATCH record, but I didn't add them here. The fact that the id and match_id are the same in every record of PLAYERS is artificial because this isn't real data.
Before I used this snippet, my code looked like this:
SELECT u.display_name,
m.date_played,
p.end_hcp
FROM `lwljhb_lwl_matches` AS m
INNER JOIN `lwljhb_lwl_players` AS p
INNER JOIN `lwljhb_users` AS u
ON m.id = p.match_id AND
p.player_id = u.id
WHERE league_seasons_id = 12 AND
playoff_round = 0
ORDER BY u.display_name, m.date_played
and generated data that looks like this:
display_name date_played end_hcp
Alan 2017-08-23 720
Alan 2017-08-29 692
Alan 2017-09-26 694
Bill 2017-08-24 865
Bill 2017-09-02 868
Bill 2017-09-21 842
Dave 2017-08-24 363
Dave 2017-08-31 339
Dave 2017-09-05 332
Dave 2017-09-15 348
Dave 2017-09-17 374
At any time during the season there will be different numbers of matches played by each of the players, but by the end of the season, they will have all played the same number of matches.
I tried to incorporate Alin Stoian's code into mine like so, changing a variable name and field names as I thought appropriate.
SET #var_record = 1;
SELECT u.display_name,
m.date_played,
p.end_hcp,
if( #player = u.display_name,
#var_record := #var_record + 1,
if(#var_record := 1 and #player := u.display_name, #var_record, #var_record)
) AS Ordinal
FROM `lwljhb_lwl_matches` AS m
INNER JOIN `lwljhb_lwl_players` AS p
INNER JOIN `lwljhb_users` AS u
JOIN (SELECT #var_record := 0, #player := 0) tmp
ON m.id = p.match_id AND
p.player_id = u.id
WHERE league_seasons_id = 12 AND
playoff_round = 0
ORDER BY u.display_name, m.date_played
I was hoping for a new column with the ordinals, but the new column is all zeros. Before I move on to trying the pivot table, I have to get these ordinals, so I hope someone here can show me my mistake.
Try this:
SELECT u.display_name,
m.date_played,
p.end_hcp,
#var_record := if ( #player = u.display_name,
#var_record + 1,
if(#player := u.display_name, 1, 1)
) AS Ordinal
FROM `lwljhb_lwl_matches` AS m
INNER JOIN `lwljhb_lwl_players` AS p
INNER JOIN `lwljhb_users` AS u
JOIN (SELECT #var_record := 0, #player := 0) tmp
ON m.id = p.match_id AND
p.player_id = u.id
WHERE league_seasons_id = 12 AND
playoff_round = 0
ORDER BY u.display_name, m.date_played
Demo
OK, it turns out that I wasn't getting all 1s in my Ordinal column after all, just in the 1st 25 records. Since it was obviously wrong I didn't look further.
The sample data I provided is in physical order as well as logical order, but MY ACTUAL data is not. That was the only difference I could think of, so I investigated further and found that in the 1st 1,000 records of output I got 7 records with 2 as the ordinal, not coincidentally where the m.ids were consecutive.
I created a view to put the data in order like in the sample and the JOINed that to the rest of the tables but still got bad data for the Ordinal.
When I swapped out the view for a temporary table it worked. It turns out that an ORDER BY in a view will be ignored if there's an ORDER by joining to it.
Bill Karwin pointed this out to me in a different question (46892912).

mysql multiple column order

I'm using mysql database to store companies in the same city. And I'm fetching them by using below query. However it doesnt sort the result as I want it to be.
I want the resulting companies to be ordered like this: First priority is the companies which distances are lower than 6 KM and those companies must be the ordered by the FIELD(sectorid,....) parameter , second priority is the companies which distances are greater than 6KM and those companies must be ordered in the same way (with FIELD parameter)
But we couldnt find any solution to our problem. I'm showing the correct results and our query in below. I would be glad if someone points me to right direction
Example Result Must be Something Like This:
Company Name: Distance: SectorID:
CompanyA 3 10
CompanyB 2 11
CompanyC 4 13
CompanyX 8 10
CompanyY 7 11
CompanyZ 9 13
Our Current Query:
SELECT companies.id,
companies.name,
companies.summary,
companies.ratingScore,
companies.companyLogo,
companies.sectorId,
companies.lattitude,
companies.longitude,
companies.address,
company_photos.photoUrl,
(SELECT EXISTS
(SELECT 1
FROM favorites
WHERE favorites.companyId = companies.id
AND userId = 109)) AS favorited,
(SELECT COUNT(*)
FROM ratings
WHERE companies.id = ratings.companyId
AND ratings.isReviewed = 1) AS comments,
(ACOS(SIN(RADIANS(41.212641))*SIN(RADIANS(lattitude))+COS(RADIANS(41.212641))*COS(RADIANS(lattitude))*COS(RADIANS(longitude)-RADIANS(29.020058))) *6371) AS distance
FROM companies
LEFT JOIN company_photos ON company_photos.companyId = companies.id
AND company_photos.photoOrder = 0
WHERE companies.isActivated = 1
AND companies.isReviewed = 1
AND companies.isCustomer = 1
AND companies.isBlocked = 0
ORDER BY FIELD (sectorId, 10, 11, 13, 7, 12, 2, 15, 17), distance
The Result I'm Getting Is:
Company Name: Distance: SectorID:
CompanyA 3 10
CompanyX 8 10
CompanyB 2 11
CompanyY 7 11
CompanyC 4 13
CompanyZ 9 13
Thanks
order by case when distance<6 then 1 else 2 end, --sorts distance < 6 first and then distance > 6
sectorid --with in each group specified above sorting is on sectorid

Mysql best students in every class in a school

In MySql I need to select top student in every class in a school in termid=10 to get discount for next term enrollment .
Please notice that total is not in table(I put in below for clearing problem)
I have this workbook table for all students workbook:
id studentid classid exam1 exam2 total termid
1 2 11 20 40 60 10
2 1 22 40 20 60 10
3 4 11 40 20 60 10
4 5 33 10 60 70 10
5 7 22 10 40 50 10
6 8 11 10 30 40 10
7 9 33 20 45 65 10
8 11 11 null null null 10
9 12 54 null null null 02
10 13 58 null null null 02
1st challenge is : exam1 and exam2 are VARCHAR and total is not in table (as i explained).
2nd challenge is : as you can see in id=8 std #11 has not numbers
3rd challenge is : may be two students have top level so they must be in result.
I need result as :
id studentid classid exam1 exam2 total termid
1 2 11 20 40 60 10
3 4 11 40 20 60 10
4 5 33 10 60 70 10
2 1 22 40 20 60 10
i have this query but not work good as i mention.
SELECT DISTINCT id,studentid,classid,exam1,exam2,total,termid ,(CAST(exam1 AS DECIMAL(9,2))+CAST(exam2 AS DECIMAL(9,2))) FROM workbook WHERE ClassId = '10';
You can get the total for the students by just adding the values (MySQL will convert the values to numbers). The following gets the max total for each class:
select w.classid, max(coalesce(w.exam1, 0) + coalesce(w.exam2, 0)) as maxtotal
from workbook w
group by w.classid;
You can then join this back to the original data to get information about the best students:
select w.*, coalesce(w.exam1, 0) + coalesce(w.exam2, 0) as total
from workbook w join
(select w.classid, max(coalesce(w.exam1, 0) + coalesce(w.exam2, 0)) as maxtotal
from workbook w
group by w.classid
) ww
on w.classid = ww.classid and (coalesce(w.exam1, 0) + coalesce(w.exam2, 0)) = ww.maxtotal;
Another approach is to join the table with itself. You find out the max for each class and then join all students of this class which match the class max:
max for each class (included in the final statement already):
SELECT classid, MAX(CAST(exam1 AS UNSIGNED) + CAST(exam2 AS UNSIGNED)) as 'maxtotal'
FROM students
WHERE NOT ISNULL(exam1)
AND NOT ISNULL(exam2)
GROUP BY classid
The complete statement:
SELECT s2.*, s1.maxtotal
FROM (SELECT classid, MAX(CAST(exam1 AS UNSIGNED) + CAST(exam2 AS UNSIGNED)) as 'maxtotal'
FROM students
WHERE NOT ISNULL(exam1)
AND NOT ISNULL(exam2)
GROUP BY classid) s1
JOIN students s2 ON s1.classid = s2.classid
WHERE s1.maxtotal = (CAST(s2.exam1 AS UNSIGNED) + CAST(s2.exam2 AS UNSIGNED));
SQL Fiddle: http://sqlfiddle.com/#!2/9f117/1
Use a simple Group by Statement:
SELECT
studentid,
classid,
max(coalesce(exam1,0)) as max_exam_1,
max(coalesce(exam2,0)) as max_exam_2,
sum(coalesce(exam1,0) + coalesce(exam2,0)) as sum_exam_total,
termid
FROM
workbook
WHERE
termid=10
GROUP BY
1,2
ORDER BY
5
Try something like this:
SELECT id,studentid,classid,exam1,exam2,(CAST(exam1 AS DECIMAL(9,2))+CAST(exam2 AS DECIMAL(9,2))) AS total,termid FROM `workbook` WHERE ((CAST(exam1 AS DECIMAL(9,2))+CAST(exam2 AS DECIMAL(9,2)))) > 50
Thanks all my friends
I think combine between 2 answer in above is best :
SELECT s2.*, s1.maxtotal
FROM (SELECT ClassId, MAX(
coalesce(exam1,0)+
coalesce(exam2,0)
) as 'maxtotal'
FROM workbook
WHERE
(
termid = '11'
)
GROUP BY ClassId) s1
JOIN workbook s2 ON s1.ClassId = s2.ClassId
WHERE s1.maxtotal = (
coalesce(exam1,0)+
coalesce(exam2,0)
) AND (s1.maxtotal >'75');
last line is good for s1.maxtotal=0 (some times student scores have not be entered and all equals 0 so all will shown as best students) or some times we need minimum score (to enroll in next term).
So thanks all

MySQL count without grouping

I am running the following query to understand to get users' first attempt to answer a question listed next to their second attempt.
SELECT
s.id AS attempt_id_first, m.id AS attempt_id_second, s.user_id
FROM
attempt s
INNER JOIN attempt m on s.user_id = m.user_id
WHERE
s.id<m.id
I end up with this:
attempt_first attempt_second user_id
7 17 1
9 10 2
9 15 2
10 15 2
4 6 9
24 25 15
29 34 19
29 36 19
34 36 19
I would like to have a new column that counts the number of attempts by users so that:
7 17 1 1
9 10 2 3
9 15 2 3
10 15 2 3
4 6 9 1
24 25 15 1
29 34 19 3
29 36 19 3
34 36 19 3
I am sure this is trivial, but I cannot get it to work. Help anyone?
I think this is it: Just display the results, and throw in an extra count subquery:
select
userid,
id,
(select
count('x')
from
attempt x
where
x.userid = a.userid) as attempcount
from
attempt a
If you like to keep the first and second attempt in separate columns, you can of course embed the subselect in your original query.
It seems wrong, though. Firstly, you need to have at least two attemps, otherwise none will show. You can solve that by changing inner join to left join and move the condition in the where clause to that join. Secondly, the 'second attempt' is not the second attempt per say. Actually, for each of the attempts you get all next attempts. Look at the example of user 2. You accidentally get three rows (where there are three attemps), but you get attempt 9 and 10, as well as attempt 9 and 15 as well as 10 and 15. 9, 15 is incorrect, since 15 isn't the attempt that followed 9. The more attempts a user has, the more of these false results you will get.
If you want one attempt listed next to the next one, with the count, I would suggest:
SELECT s.user_id, s.id AS attempt_id_first,
(select s2.id
from attempt s2
where s2.user_id = s.user_id and
s2.id > s.id
order by s2.id
limit 1
) as attempt_id_second,
(select count(*)
from attempt s3
where s3.user_id = s.user_id
) as totalAttempts
FROM attempt s ;
This only lists each attempt once with the next one. The count is included as the last column.

Sub Query or Join MySQL?

table league
team_id name wins losses played recorded created
1 dodgers 10 4 14 1364790000 1353215830
2 angels 9 6 15 1364790000 1353661376
3 pirates 12 3 15 1364790000 1353543466
team_id name wins losses played recorded created
1 dodgers 22 9 31 1367274480 1353215830
2 angels 14 17 31 1367274480 1353661376
3 pirates 19 13 32 1367274480 1353543466
4 yankees 10 9 19 1367274480 1365577298
5 brewers 7 11 18 1367274480 1365394448
Would like Results as:
team_id name wins losses played
1 dodgers 12 5 17
2 angels 5 11 16
3 pirates 7 10 17
4 yankees 10 9 19
5 brewers 7 11 18
I've tried several queries with joins and have had no success. Every day the team, wins, lossed and played are captured and time stamped on the recorded column. The team was created on the created column. (All unix timestamps) There are several rows in between the 2 dates I'm trying for, but I don't need them for this query.
What I wanted to do was to get April's Won/Loss/Played for existing and new teams, I tried several queries, here are a couple that did not give me the desired results:
SELECT a.name as name, a.wins-b.wins as wins, a.losses-b.losses as losses, a.played-b.played as played from league a join league b on a.id=b.id where a.recorded= 1367274480 and b.recorded= 1364790000
and
SELECT new.*, new.wins-old.wins as newwins, new.losses-old.losses as newlosses FROM league new LEFT JOIN league old ON new.id=old.id WHERE (new.recorded=1367274480 and old.recorded=1364790000) or (new.created > 1364790000 and new.recorded=1367274480) GROUP BY new.id
You want every row for the later records and the same number of rows for the earlier, so you need to use LEFT JOIN to get NULL's for teams created between two dates, but WHERE recorded condition for the smaller table should be moved as ON condition for the join.
Also keep in mind that 2-NULL = NULL, so you need to change NULL's into 0's with coalesce().
SELECT a.name AS name,
a.wins - COALESCE( b.wins, 0 ) AS wins,
a.losses - COALESCE( b.losses, 0 ) AS losses,
a.played - COALESCE( b.played, 0 ) AS played
FROM league a LEFT JOIN league b
ON a.team_id = b.team_id AND b.recorded =1364790000
WHERE a.recorded =1367274480
The way the data is set up, it seems that you don't have a recorded value on each day. However, each column would be increasing, so you can take the difference between the max and min vals for the month.
Try this:
SELECT l.name as name,
max(l.wins)-min(l.wins) as wins,
max(l.losses)-min(l.losses) as losses,
max(l.played)-min(l.played) as played
from league l
where l.recorded <= 1367274480 and l.recorded >= 1364790000
group by l.name