Tricky SQL query involving consecutive values - mysql

I need to perform a relatively easy to explain but (given my somewhat limited skills) hard to write SQL query.
Assume we have a table similar to this one:
exam_no | name | surname | result | date
---------+------+---------+--------+------------
1 | John | Doe | PASS | 2012-01-01
1 | Ryan | Smith | FAIL | 2012-01-02 <--
1 | Ann | Evans | PASS | 2012-01-03
1 | Mary | Lee | FAIL | 2012-01-04
... | ... | ... | ... | ...
2 | John | Doe | FAIL | 2012-02-01 <--
2 | Ryan | Smith | FAIL | 2012-02-02
2 | Ann | Evans | FAIL | 2012-02-03
2 | Mary | Lee | PASS | 2012-02-04
... | ... | ... | ... | ...
3 | John | Doe | FAIL | 2012-03-01
3 | Ryan | Smith | FAIL | 2012-03-02
3 | Ann | Evans | PASS | 2012-03-03
3 | Mary | Lee | FAIL | 2012-03-04 <--
Note that exam_no and date aren't necessarily related as one might expect from the kind of example I chose.
Now, the query that I need to do is as follows:
From the latest exam (exam_no = 3) find all the students that have failed (John Doe, Ryan Smith and Mary Lee).
For each of these students find the date of the first of the batch of consecutively failing exams. Another way to put it would be: for each of these students find the date of the first failing exam that comes after their last passing exam. (Look at the arrows in the table).
The resulting table should be something like this:
name | surname | date_since_failing
------+---------+--------------------
John | Doe | 2012-02-01
Ryan | Smith | 2012-01-02
Mary | Lee | 2012-03-04
How can I perform such a query?
Thank you for your time.

You can take advantage of the fact that if someone passed the most recent exam, then they have not failed any exams since their most recent pass: therefore the problem reduces to finding the first exam failed since the most recent pass:
SELECT name, surname, MIN(date) date_since_fail
FROM results NATURAL LEFT JOIN (
SELECT name, surname, MAX(date) lastpass
FROM results
WHERE result = 'PASS'
GROUP BY name, surname
) t
WHERE result = 'FAIL' AND date > IFNULL(lastpass,0)
GROUP BY name, surname
See it on sqlfiddle.

I should use a subquery that fetch last passed exam,
somthing like:
SET #query_exam_no = 3;
SELECT
name,
surname,
MIN(IF(date > last_passed_exam, date, NULL)) AS date_failing_since
FROM
exam_results
LEFT JOIN (
SELECT
name,
surname,
MAX(date) AS last_passed_exam
FROM exam_results
WHERE result = 'PASS'
GROUP BY name, surname
) AS last_passed_exams USING (name, surname)
HAVING
MAX(IF(exam_no = #query_exam_no, result, NULL)) = 'FAIL'
GROUP BY name, surname

This is enough:
select t.name,
t.surname,
t.date as 'date_since_failing'
from tablename t
inner join
(
select name,
surname,
max(exam_no) as exam_no
from tablename
group by name, surname
having min(result) = 'FAIL'
) aux on t.name = aux.name and t.surname = aux.surname and t.exam_no = aux.exam_no

The condition you are asking for is good for nothing you can do it without it. Here is the working example.
select
e.name,
e.sur_name,
min(e.date) as `LastFailed`
from exams as e
where e.result = 'Fail'
group by e.name
order by e.name
This produces this result
name sur_name LastFailed
Ann Evans 2012-02-03
John Doe 2012-02-01
Mary Lee 2012-01-04
Ryan Smith 2012-01-02

Related

How to select a column's value, only if all other entries with the same value exist and match? (MYSQL)

The title is a bit messy, but here's an example
suppose we have table:
| name | room |
=================
| John | 4 |
| John | 6 |
| John | 9 |
| Smith | 4 |
| Smith | 6 |
| Brian | 4 |
| Brian | 6 |
| Brian | 9 |
I want to select John and Brian because they both have exactly rooms 4, 6 and 9, but not Smith, since he doesn't have the room 9. (If we had another person who ONLY has room 4 and 6, then it'd select that other person as well as Smith).
I know I need to do some kind of correlated query, but I'm not sure how to actually get it to do something like
for a check for b
If you want groups of names that share the exact same rooms, I would recommend group_concat():
select rooms, group_concat(name) as names
from (select name, group_concat(room order by room) as rooms
from t
group by name
) n
group by rooms;
If you want only combinations with more than one name, then add having count(*) > 1 to the outer select.

How to count unique items in column in Access

I need help. I can't seem to find the logic behind this code.
I am working on a voting system, and I need to output the results of the votes.
I want to count all of the rows that has a unique name in it and output how many.
My table goes like this.
voterid | pres | vpres | sec | trea | PIO
---------------------------------------------
1 | John | Mitch | James | Jack | Eman
2 | John | Pao | Bryan | Jack | Faye
3 | Kelvin | Pao | James | Jeck | Faye
Output should be
Pres | Votes
--------------
John | 2
Kelvin | 1
Here's my code.
SELECT DISTINCT
pres,
(SELECT COUNT(pres) FROM (SELECT DISTINCT pres FROM tblVote AS Votes)) AS Votes
FROM tblVote
Thanks in advance!
I think you are just looking for a simple GROUP BY query:
SELECT pres, COUNT(*) AS Votes
FROM tblVote
GROUP BY pres

How do I backfill missing mysql data from one table?

I have a MySQL table called employee that looks like this:
ID | User | Phone_No | Phone_No_Count
1 | Fred | 9999 | 1
2 | John | 8888 | 2
3 | Pablo | 123 | 1
4 | John | | 0
5 | John | 8888 | 2
6 | Pablo | | 0
7 | John | 456 | 1
Phone_No_Count is a count of the Phone_No column, if there is no Phone_No then Phone_No_Count is set to zero.
I want to backfill the missing Phone_No entries using Phone_No entries which have the highest Phone_No_Count.
e.g. User John has 2 Phone_No's (8888 and 456) so I just want to use 8888 as it has the highest Phone_No_Count (2)
The backfilled data in employee would then look like this:
ID | User | Phone_No | Phone_No_Count
1 | Fred | 9999 | 1
2 | John | 8888 | 2
3 | Pablo | 123 | 1
4 | John | 8888 | 0
5 | John | 8888 | 2
6 | Pablo | 123 | 0
7 | John | 456 | 1
I can then update the Phone_No_Count separately, which I know how to do anyway.
All the examples I've seen online are for backfilling multiple tables or if it's just one table they don't have the required logic for this.
Can somebody please help as this has been frying my brain all day!!
One way to go about this kind of update you can use user defined variables in your query and store the phone for the user which has the maximum of phone count (i.e a correlated subquery) then join this data with your table and do update
update Table1 t1a
inner join(
select t1.id,
t1.`User`,
#p:= case
when t1.Phone_No is null then #c
else t1.Phone_No END Phone_No,
#c:=(select Phone_No from Table1 where t1.`User`=`User` order by `Phone_No_Count` DESC limit 1 ) max_phone
from Table1 t1,(select #p:=0,#c:=0) t
order by t1.`User`,t1.`Phone_No_Count` DESC
) t2 on(t1a.id=t2.id)
set t1a.Phone_No = t2.Phone_No
Fiddle Demo
The trick is to get the phone number for the highest count. Unfortunately, MySQL doesn't let you have subqueries on the same query being updated, but you can do this with a trick. This allows you to use update/join syntax:
update employee e join
(select e.user,
substring_index(group_concat(phone_no order by phone_no_count desc
), ',', 1) as new_phone_no
from employee e
group by e.user
) toupdate
on e.user = toupdate.user
set e.phone_no = toupdate.new_phone_no
where e.phone_no is null;

MySql Show values not in the second table

I am trying to offer tickets for other events to people who have booked for one event. I have two tables like
---Concerts----------------
| ConcertID | Concert |
---------------------------
| 101 | Mozart |
| 102 | Beethoven |
| 103 | Chopin |
---------------------------
---Tickets--------------------------------------------
| TicketNum | Name | email | ConcertID |
------------------------------------------------------
| 5001 | Amy Jones | aaa#aaa.com | 101 |
| 5002 | Amy Jones | aaa#aaa.com | 102 |
| 5003 | John Doe | bbb#bbb.com | 102 |
| 5004 | Adam Smith | ccc#ccc.com | 101 |
| 5005 | Jerry Coe | ddd#ddd.com | 102 |
------------------------------------------------------
I am trying to construct a statement like
SELECT Tickets.email, Concerts.ConcertID, Concerts.Concert FROM Tickets, Concerts
WHERE (Show Concerts Not Booked)
AND Concerts.ConcertID = Tickets.ConcertID
AND email = "bbb.com"
This show then return the two concerts that John Doe is not booked on to i.e. 101 Mozart and 102 Chopin.
It's the (Show Concerts Not Booked) that I am struggling with
I can construct a query to show the intersection of which concerts he is booked on to, but I cannot show the ones that he is not booked on to.
Any help gratefully appreciated.
Select c.ConcertID, C.Concert
From Concerts c
Left join Tickets t On t.ConcertID = c.ConcertID
Where t.ConcertID is null and t.Name = 'John Doe'
Would do that.
Without the aliases might make what is going on clearer
Select Concerts.ConcertID, Concerts.Concert
From Concerts
Left join Tickets On Tickets.ConcertID = Concerts.ConcertID
Where Tickets.ConcertID is null and Tickets.Name = 'John Doe'
Think of it this way, if you wanted a list of all the concerts and whether John had bought a ticket
you would do
Select *
From Concerts
Left join Tickets On Tickets.ConcertID = Concerts.ConcertID
Where Tickets.Name = 'John Doe'
and get
101 Mozart NULL NULL NULL NULL
102 Beethoven 5003 John Doe bbb#bbb.com 102
103 Chopin NULL NULL NULL NULL
so the extra condition in the where statement filters out the rows where no ticket exists.
Well that was irritating, after playing about with #JungSu Heo's sql fiddle, I found this worked
Select *
From Concerts c
Left join Tickets t On t.ConcertID = c.ConcertID and t.Name = 'John Doe'
Where t.ConcertID is null
Makes sense after a bit of thought, because John Doe was in where clause the nulls were being chopped out. Do it in the join clause, then John Doe is only used to constrain the outer join.
Thank you learnt something, though I wouldn't normally see something like this because I'd have had a names/bookers type table.
I can't understand why Tony's query is not working. could you try this?
SELECT c.ConcertID, c.Concert
FROM Concerts c
WHERE c.ConcertID NOT IN
(
SELECT ConcertID
FROM Tickets
WHERE Name = 'John Doe'
)
If you post you data at http://www.sqlfiddle.com/, we would appreciate you. easy to test.

Use MySQL to create a string out of a subquery?

What I'm hoping to do is create a string out of a table WITHIN a query so that I may be able to place that string in another query I'm creating. Say, I have this for a table:
index | position | name
----------------------------------------
1 | member | John Smith
2 | chair | Mary Jones
3 | member | Mary Jones
4 | contact | Grace Adams
5 | director | Grace Adams
6 | member | Grace Adams
7 | treasurer | Bill McDonnell
8 | vice chair | Bill McDonnell
9 | member | Ishmael Rodriguez
I'm looking for the result as follows:
name | positions
----------------------------------------
John Smith | member
Mary Jones | chair,member
Grace Adams | contact,director,member
Bill McDonnell | treasurer,vice chair
Ishmael Rodriguez | member
I was hoping I could use some variant of CONCAT_WS() to get my result, like this...
SELECT
a.NAME,
CONCAT_WS(
',',
(
SELECT
position
FROM
TABLE
WHERE
NAME = a.NAME
)
)AS positions FROM ---------------
Obviously, this isn't working out for me. Any ideas?
Use GROUP_CONCAT[docs]
SELECT name, GROUP_CONCAT(position) result
FROM tableName
GROUP BY name
ORDER BY `index`
SQLFiddle Demo
Use GROUP_CONCAT like so:
SELECT name, GROUP_CONCAT(position SEPARATOR ',')
FROM Table
GROUP BY name