MySQL joining tables of different structure - mysql

I am having trouble either getting any result or a correct result in the following problem - http://www.sqlfiddle.com/#!9/696ed2/4
Overall goal is to list all transactions of users who are linked together as 'Customers'. So if John is looking at his dashboard, he will see which books Alice (his customer) has rented (including book title), and which books were sold (he won't be able to see the title of that book).
When two multiple tables are joined to the parent table, where both depending tables have an 'active' flag set against each row, I can't seem to get only active rows.
# USERS
CREATE TABLE `users` (
`id` int(11) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`active` boolean DEFAULT NULL
);
INSERT INTO `users` (`id`, `name`, `active`) VALUES
(1, 'John', 1),
(2, 'Alice', 1),
(3, 'Jess', 1),
(4, 'Bob', 1);
# BOOKS
CREATE TABLE `books` (
`id` int(11) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`active` boolean DEFAULT NULL
);
INSERT INTO `books` (`id`, `name`, `active`) VALUES
(1, 'On the Road', 1),
(2, 'Neuromancer', 0),
(3, 'Modern History', 1),
(4, 'Red Mars', 1);
# TRANSACTIONS
CREATE TABLE `transactions` (
`id` int(11) NOT NULL,
`user_1_id` int(11) NOT NULL,
`user_2_id` int(11) DEFAULT NULL,
`book_id` int(11) DEFAULT NULL,
`timestamp` varchar(20) DEFAULT NULL,
`type` enum('Rent', 'Sold') NOT NULL
);
INSERT INTO `transactions` (`id`, `user_1_id`, `user_2_id`, `book_id`, `timestamp`, `type`) VALUES
(1, 1, 2, 1, '1465238591', 'Rent'),
(2, 2, 1, 2, '1465238592', 'Rent'),
(3, 2, 3, 3, '1465238593', 'Rent'),
(4, 3, 4, NULL, '1465238594', 'Sold'),
(5, 2, 3, NULL, '1465238595', 'Sold'),
(6, 3, 4, NULL, '1465238596', 'Sold'),
(7, 2, 2, 4, '1465238597', 'Rent'),
(8, 1, 3, 1, '1465238598', 'Rent'),
(9, 2, 4, 2, '1465238598', 'Rent');
# RELATIONSHIPS
CREATE TABLE `relationships` (
`id` int(11) NOT NULL,
`user_1_id` int(11) DEFAULT NULL,
`user_2_id` int(11) NOT NULL,
`type` enum('Customer', 'Supplier') NOT NULL
);
INSERT INTO `relationships` (`id`, `user_1_id`, `user_2_id`, `type`) VALUES
(1, 1, 2, 'Customer'),
(2, 2, 1, 'Customer'),
(3, 2, 4, 'Customer'),
(3, 1, 4, 'Supplier'),
(3, 3, 1, 'Customer');
Query:
SELECT DISTINCT
t.id,
t.type,
t.timestamp,
u1.name as user_1_name,
u2.name as user_2_name,
b.name as book_name
FROM transactions t
LEFT JOIN relationships r ON (r.user_1_id = 1 AND r.type = 'Customer')
LEFT JOIN books b ON (b.id = t.book_id AND b.active)
LEFT JOIN users u1 ON (u1.id = t.user_1_id) # AND u1.active
LEFT JOIN users u2 ON (u2.id = t.user_2_id) # AND u2.active
WHERE (r.user_2_id = t.user_1_id OR t.user_2_id = 1 AND t.user_1_id != 1)
# AND b.active AND u1.active AND u2.active
[Result]:
| id | type | timestamp | user_1_name | user_2_name | book_name |
|----|------|------------|-------------|-------------|----------------|
| 3 | Rent | 1465238593 | Alice | Jess | Modern History |
| 2 | Rent | 1465238592 | Alice | John | (null) | <<< Should not be here
| 7 | Rent | 1465238597 | Alice | Alice | Red Mars |
| 5 | Sold | 1465238595 | Alice | Jess | (null) | <<< Correct
| 9 | Rent | 1465238598 | Alice | Bob | (null) | <<< Should not be here
In the above result, the problem is that book Neuromancer has flag 'active' set to 0, so should not appear in the result. I have played with placing AND b.active at different places, but the results are always wrong. (See http://www.sqlfiddle.com/#!9/696ed2/5)
Looking at the mess above, I am not even sure my approach is any good, any suggestions are welcome.

As D. Smania mentioned in comments you need to make sure b.active is either 1 or NULL but based on your LEFT JOIN condition b.active will always be 1 so you need to do the join only on id and rely on the WHERE condition for comparison. This should yield the results you asked for:
SELECT DISTINCT
t.id,
t.type,
t.timestamp,
u1.name AS user_1_name,
u2.name AS user_2_name,
b.name AS book_name
FROM transactions t
LEFT JOIN relationships r ON (r.user_1_id = 1 AND r.type = 'Customer')
LEFT JOIN books b ON (b.id = t.book_id)
LEFT JOIN users u1 ON (u1.id = t.user_1_id)
LEFT JOIN users u2 ON (u2.id = t.user_2_id)
WHERE (r.user_2_id = t.user_1_id OR t.user_2_id = 1 AND t.user_1_id != 1)
AND (b.active OR b.active IS NULL)
AND u1.active AND u2.active
SQL Fiddle
One note - in your first WHERE condition it's not clear to me whether you mean:
(r.user_2_id = t.user_1_id OR (t.user_2_id = 1 AND t.user_1_id != 1))
or
((r.user_2_id = t.user_1_id OR t.user_2_id = 1) AND t.user_1_id != 1)
It's always best to be explicit with your logic grouping when you have adjacent AND and OR conditions.

Related

MySQL Update a table by min of another table

I would like to update Table Lease from Table History
CREATE TABLE Lease
(`LeaseID` int, `Name` varchar(3), `Users` varchar(15), `WhoSignID` int, `NoteDate` date)
;
INSERT INTO Lease
(`LeaseID`, `Name`, `Users`, `WhoSignID`, `NoteDate`)
VALUES
(1, 'AAA', '1000,1001', NULL, NULL),
(2, 'BBB', '1002', NULL, NULL),
(3, 'CCC', '1003,1004', NULL, NULL),
(4, 'DDD', '1005,1006, 1007', NULL, NULL)
;
CREATE TABLE History
(`HistoryID` int, `LeaseID` int, `User` int, `SignDate` date)
;
INSERT INTO History
(`HistoryID`, `LeaseID`, `User`, `SignDate`)
VALUES
(1, 1, 1000, '2020-01-05'),
(2, 1, 1001, '2020-01-04'),
(3, 1, 1001, '2020-01-02'),
(4, 1, 1000, '2020-01-03'),
(6, 2, 1002, '2020-05-01'),
(7, 2, 1002, '2020-05-03')
;
I looking of a Mysql Update to update Table Lease :
NoteDate and WhoSignID based on SignDate and User
where Minimum of SignDate of User
Table Lease After Update
LeaseID | Name | Users | WhoSignID | NoteDate
1 | AAA | 1000,1001 | 1001 | 2020-01-02
2 | BBB | 1002 | 1002 | 2020-05-01
...
I appreciate any assist
Your Lease table has a serious design problem, because it is storing users as a CSV list. Instead, you should have each user value on a separate record. That being said, it appears that the CSV user list is immaterial to your current problem, which only required finding the earliest date for each lease. If so, then a simple update join should suffice:
UPDATE Lease l
INNER JOIN
(
SELECT h1.LeaseID, h1.User, h2.MinSignDate
FROM History h1
INNER JOIN
(
SELECT LeaseID, MIN(SignDate) AS MinSignDate
FROM History
GROUP BY LeaseID
) h2
ON h2.LeaseID = h1.LeaseID AND
h2.MinSignDate = h1.SignDate
) h
ON h.LeaseID = l.LeaseID
SET
WhoSignID = h.User,
NoteDate = h.MinSignDate;

select parent row where ALL children rows meet criterias

I have two table, rates and criterias. parent_id in criterias refers to id in rates.
I need to select the rates where ALL children rows in table criterias WHERE criteria_1 AND criteria_2 equal to NULL.
In the example below, only flat rate should be selected
rates
id | name
--------------------
1 | summer rate
2 | flat rate
3 | student rate
conditions
id | parent_id | criteria_1 | criteria_2
------------------------------------------------------
1 | 1 | 523 | 563
2 | 1 | null | null
3 | 2 | null | null
4 | 2 | null | null
5 | 3 | 777 | null
I tried NOT EXIST, but it return it return any rate where one children have two null criteria
try using this subquery with inner join.
select * from
(select * from rates where name = 'flat rate') t1
inner join
(select * from criterias where coalesce(criteria_1, 0) = 0 and coalesce(criteria_2, 0) = 0) t2
on t2.parent_id = t1.id
Please see the following query it should work. You need to compare 2 result set to find rate with ALL null childrens.
SELECT
a.parent_id
FROM(
SELECT
parent_id,
COUNT(*) AS total_count
FROM criterias c
WHERE c. criteria_1 IS NULL AND c.criteria_2 IS NULL
GROUP BY 1
) a
INNER JOIN (
SELECT
parent_id,
COUNT(*) AS total_count
FROM criterias c
GROUP BY 1
)b ON a.parent_id = b.parent_id AND a.total_count = b.total_count
I would use some aggregate function with an having clause grouped by parent_id.
Using a min or max would return a numerical value if there is at least one non-null value per parent_id but will be null if there are only null. So just need to use an having min(<field>) is null to find a parent_id with only null value.
select *
from rates r
where id in(
select parent_id
from criterias
group by parent_id
having min(criteria_1) is null
and min(criteria_2) is null
);
or With an inner join (if you prefer)
select *
from rates r
inner join (
select parent_id
from criterias
group by parent_id
having min(criteria_1) is null
and min(criteria_2) is null
) c ON c.parent_id = r.id;
Validated with :
create table rates(
id int,
name varchar(20)
);
create table criterias (
id int,
parent_id int,
criteria_1 int null,
criteria_2 int null
);
insert into rates values (1, 'summer rate');
insert into rates values (2, 'flate rate');
insert into rates values (3, 'student rate');
insert into rates values (4, 'old rate');
insert into rates values (5, 'any rate');
insert into criterias values (1, 1, 523, 563);
insert into criterias values (2, 1, null, null);
insert into criterias values (3, 2, null, null);
insert into criterias values (4, 2, null, null);
insert into criterias values (5, 1, 777, null);
insert into criterias values (6, 4, null, null);
insert into criterias values (7, 5, null, null);
insert into criterias values (8, 5, null, null);
/*insert into criterias values (9, 5, 1, null);*/
select *
from rates r
where id in(
select parent_id
from criterias
group by parent_id
having min(criteria_1) is null
and min(criteria_2) is null
);
Result:
id name
2 flate rate
4 old rate
5 any rate

How do I select the max(timestamp) from a relational mysql table fast

We are developing a ticket system and for the dashboard we want to show the tickets with it's latest status. We have two tables. The first one for the ticket itself and a second table for the individual edits.
The system is running already, but the performance for the dashboard is very bad (6 seconds for ~1300 tickets). At first we used a statemant which selected 'where timestamp = (select max(Timestamp))' for every ticket. In the second step we created a view which only includes the latest timestamp for every ticket, but we are not able to also include the correct status into this view.
So the main Problem might be, that we can't build a table in which for every ticket the lastest ins_date and also the latest status is selected.
Simplyfied database looks like:
CREATE TABLE `ticket` (
`id` int(10) NOT NULL,
`betreff` varchar(100) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `ticket_relation` (
`id` int(11) NOT NULL,
`ticket` int(10) NOT NULL,
`info` varchar(10000) DEFAULT NULL,
`status` int(1) NOT NULL DEFAULT '0',
`ins_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`ins_user` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `ticket` (`id`, `betreff`) VALUES
(1, 'Technische Frage'),
(2, 'Ticket 2'),
(3, 'Weitere Fragen');
INSERT INTO `ticket_relation` (`id`, `ticket`, `info`, `status`, `ins_date`, `ins_user`) VALUES
(1, 1, 'Betreff 1', 0, '2019-05-28 11:02:18', 123),
(2, 1, 'Betreff 2', 3, '2019-05-28 12:07:36', 123),
(3, 2, 'Betreff 3', 0, '2019-05-29 06:49:32', 123),
(4, 3, 'Betreff 4', 1, '2019-05-29 07:44:07', 123),
(5, 2, 'Betreff 5', 1, '2019-05-29 07:49:32', 123),
(6, 2, 'Betreff 6', 3, '2019-05-29 08:49:32', 123),
(7, 3, 'Betreff 7', 2, '2019-05-29 09:49:32', 123),
(8, 2, 'Betreff 8', 1, '2019-05-29 10:49:32', 123),
(9, 3, 'Betreff 9', 2, '2019-05-29 11:49:32', 123),
(10, 3, 'Betreff 10', 3, '2019-05-29 12:49:32', 123);
I have created a SQL Fiddle: http://sqlfiddle.com/#!9/a873b6/3
The first three Statements are attempts that won't work correct or way too slow. The last one is the key I think, but I don't understand, why this gets the status wrong.
The attempt to create the table with latest ins_date AND status for each ticket:
SELECT
ticket, status, MAX(ins_date) as max_date
FROM
ticket_relation
GROUP BY
ticket
ORDER BY
ins_date DESC;
This query gets the correct (latest) ins_date for every ticket, but not the latest status:
+--------+--------+----------------------+
| ticket | status | max_date |
+--------+--------+----------------------+
| 3 | 1 | 2019-05-29T12:49:32Z |
+--------+--------+----------------------+
| 2 | 0 | 2019-05-29T10:49:32Z |
+--------+--------+----------------------+
| 1 | 0 | 2019-05-28T12:07:36Z |
+--------+--------+----------------------+
Expected output would be this:
+--------+--------+----------------------+
| ticket | status | max_date |
+--------+--------+----------------------+
| 3 | 3 | 2019-05-29T12:49:32Z |
+--------+--------+----------------------+
| 2 | 1 | 2019-05-29T10:49:32Z |
+--------+--------+----------------------+
| 1 | 3 | 2019-05-28T12:07:36Z |
+--------+--------+----------------------+
Is there a efficient way to select the latest timestamp and status for every ticket in the tiket-table?
Other approach is to think filtering not GROUPing..
Query
SELECT
ticket_relation_1.ticket
, ticket_relation_1.status
, ticket_relation_1.ins_date
FROM
ticket_relation AS ticket_relation_1
LEFT JOIN
ticket_relation AS ticket_relation_2
ON
ticket_relation_1.ticket = ticket_relation_2.ticket
AND
ticket_relation_1.ins_date < ticket_relation_2.ins_date
WHERE
ticket_relation_2.id IS NULL
ORDER BY
ticket_relation_1.id DESC
Result
| ticket | status | ins_date |
| ------ | ------ | ------------------- |
| 3 | 3 | 2019-05-29 12:49:32 |
| 2 | 1 | 2019-05-29 10:49:32 |
| 1 | 3 | 2019-05-28 12:07:36 |
see demo
This query would require a index KEY(ticket, ins_date, id) to get max performance..
One solution would be to use a subquery to compute the latest insert date for each ticket, and then to join the results with the original table, like:
SELECT t.ticket, t.status, t.ins_date
FROM ticket_relation t
INNER JOIN (
SELECT ticket, max(ins_date) max_ins_date
FROM ticket_relation
GROUP BY ticket
) x ON t.ticket = x.ticket AND t.ins_date = x.max_ins_date
For better performance with this query, you want an index on (ticket, ins_date).
Anoter option would be to use a NOT EXISTS condition to ensure that only the latest record is selected, like:
SELECT t.ticket, t.status, t.ins_date
FROM ticket_relation t
WHERE NOT EXISTS (
SELECT 1
FROM ticket_relation t1
WHERE t1.ticket = t.ticket AND t1.ins_date > t.ins_date)
)
NB: when dealing with GROUP BY, all non-aggregated columns must appear in the GROUP BY clause. Else, you will get either an error or unprectictable results (depending on whether server option ONLY_FULL_GROUP_BY is, respectively, enabled or disabled).
If you are able to upgrade to a recent version of mysql (8.0), then window functions can be used to simplify the query and possibly increase its performance, like:
SELECT ticket, status, ins_date
FROM (
SELECT
ticket,
status,
ins_date,
row_number() over(partition by ticket order by ins_date desc) rn
FROM ticket_relation
) x WHERE rn = 1
You can try below query -
SELECT
ticket, status, ins_date as max_date
FROM ticket_relation a
where ins_date in (select max(ins_date) from ticket_relation b where a.ticket=b.ticket)

MYSQL: select the top N candidates

I have the following DB scheme for university elections:
for each department, I have the following positions:
1 CHEF (which is the candidate_position = 1)
& 6 Members (which is the candidate_position = 2)
I want to obtain the winners of the election in each department.
to obtain the winner of CHEF position in "Informatique" department, I did the following query:
SELECT doctor.firstname, doctor.lastname, votes
FROM (SELECT COUNT(*) AS votes FROM candidate_votes WHERE candidate_votes.candidate_position = 1 GROUP BY candidate_votes.candidate_id) AS votes, doctor
INNER JOIN department_candidates ON department_candidates.doctor_id = doctor.id
INNER JOIN department ON department.id = department_candidates.department_id AND department.name = 'Informatique'
INNER JOIN candidate_votes ON candidate_votes.candidate_id = doctor.id AND candidate_votes.candidate_position = 1
GROUP BY candidates_votes.candidate_id
please note I didn't use LIMIT 1 because may be there is a tie (or draw) of votes between multiple candidates
based on the results, I think that my query of selecting the winner of Chef position is right, But I want some help to know how to select the first 6 candidates of Member position ?
Data set:
--
-- Table structure for table `candidate_votes`
--
DROP TABLE IF EXISTS `candidate_votes`;
CREATE TABLE IF NOT EXISTS `candidate_votes` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`candidate_id` int(11) NOT NULL,
`voter_id` int(11) NOT NULL,
`candidate_position` tinyint(1) NOT NULL COMMENT '1: chef, 2: member',
`date` date NOT NULL,
PRIMARY KEY (`id`),
KEY `fk-candidate_votes-voter_id` (`voter_id`),
KEY `fk-candidate_votes-candidate_id_idx` (`candidate_id`)
) ENGINE=InnoDB AUTO_INCREMENT=36 DEFAULT CHARSET=utf8;
--
-- Dumping data for table `candidate_votes`
--
INSERT INTO `candidate_votes` (`id`, `candidate_id`, `voter_id`, `candidate_position`, `date`) VALUES
(24, 2, 1, 1, '2018-05-26'),
(25, 1, 1, 2, '2018-05-26'),
(26, 6, 1, 2, '2018-05-26'),
(27, 5, 1, 2, '2018-05-26'),
(28, 7, 1, 2, '2018-05-26'),
(29, 8, 1, 2, '2018-05-26'),
(30, 9, 1, 2, '2018-05-26'),
(31, 2, 2, 1, '2018-05-16'),
(32, 3, 7, 1, '2018-05-22'),
(33, 3, 8, 1, '2018-05-22'),
(34, 4, 6, 2, '2018-05-29'),
(35, 7, 6, 2, '2018-05-29');
-- --------------------------------------------------------
--
-- Table structure for table `department`
--
DROP TABLE IF EXISTS `department`;
CREATE TABLE IF NOT EXISTS `department` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `department-name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
--
-- Dumping data for table `department`
--
INSERT INTO `department` (`id`, `name`) VALUES
(1, 'Informatique'),
(2, 'Mathematique'),
(4, 'physique');
-- --------------------------------------------------------
--
-- Table structure for table `department_candidates`
--
DROP TABLE IF EXISTS `department_candidates`;
CREATE TABLE IF NOT EXISTS `department_candidates` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`department_id` int(11) NOT NULL,
`doctor_id` int(11) NOT NULL,
`candidate_position` tinyint(1) NOT NULL COMMENT '1: chef, 2: member',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8;
--
-- Dumping data for table `department_candidates`
--
INSERT INTO `department_candidates` (`id`, `department_id`, `doctor_id`, `candidate_position`) VALUES
(5, 1, 3, 1),
(7, 1, 4, 2),
(8, 1, 1, 2),
(9, 1, 2, 1),
(10, 1, 6, 2),
(11, 1, 5, 2),
(12, 1, 7, 2),
(13, 1, 8, 2),
(14, 1, 9, 2);
-- --------------------------------------------------------
--
-- Table structure for table `doctor`
--
DROP TABLE IF EXISTS `doctor`;
CREATE TABLE IF NOT EXISTS `doctor` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`firstname` varchar(255) NOT NULL,
`lastname` varchar(255) NOT NULL,
`department_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
--
-- Dumping data for table `doctor`
--
INSERT INTO `doctor` (`id`, `firstname`, `lastname`, `department_id`) VALUES
(1, 'doc1_fn', 'doc1_ln', 1),
(2, 'doc2_fn', 'doc2_ln', 1),
(3, 'doc3_fn', 'doc3_ln', 1),
(4, 'doc4_fn', 'doc4_ln', 1),
(5, 'doc5_fn', 'doc5_ln', 1),
(6, 'doc6_fn', 'doc6_ln', 1),
(7, 'doc7_fn', 'doc7_ln', 1),
(8, 'doc8_fn', 'doc8_ln', 1),
(9, 'doc9_fn', 'doc9_ln', 1);
-- --------------------------------------------------------
Sqlfiddle DEMO
Consider the following:
SELECT x.*
, CASE WHEN #prev_position = candidate_position THEN CASE WHEN #prev_total = total THEN #i:=#i ELSE #i:=#i+1 END ELSE #i:=1 END i
, #prev_position := candidate_position prev_position
, #prev_total := total prev_total
FROM
(
SELECT candidate_id
, candidate_position
, COUNT(*) total
FROM candidate_votes
GROUP
BY candidate_id
, candidate_position
) x
JOIN
( SELECT #prev_position := null,#prev_total:=null,#i:=0) vars
ORDER
BY candidate_position
, total DESC;
+--------------+--------------------+-------+------+---------------+------------+
| candidate_id | candidate_position | total | i | prev_position | prev_total |
+--------------+--------------------+-------+------+---------------+------------+
| 2 | 1 | 2 | 1 | 1 | 2 |
| 3 | 1 | 2 | 1 | 1 | 2 |
| 7 | 2 | 2 | 1 | 2 | 2 |
| 8 | 2 | 1 | 2 | 2 | 1 |
| 9 | 2 | 1 | 2 | 2 | 1 |
| 1 | 2 | 1 | 2 | 2 | 1 |
| 4 | 2 | 1 | 2 | 2 | 1 |
| 5 | 2 | 1 | 2 | 2 | 1 |
| 6 | 2 | 1 | 2 | 2 | 1 |
+--------------+--------------------+-------+------+---------------+------------+
In this example, i represents rank. For position 1, we can see that two candidates tied for first place. For position 2, there was one outright winner, with all remaining candidates tying for second place.
Apparently I was trying to be too clever in my other answer, you can achieve a simple ranking table like this:
SELECT cast(dc.candidate_position AS UNSIGNED) AS position, dc.doctor_id, doc.firstname, doc.lastname, v.votes
FROM department_candidates dc
JOIN department dept ON dept.id=dc.department_id AND dept.name='Informatique'
JOIN doctor doc ON doc.id=dc.doctor_id
JOIN (SELECT candidate_position AS cp, candidate_id AS cid, count(candidate_id) AS votes
FROM candidate_votes
GROUP BY cid) v
ON v.cid=doc.id AND v.cp = dc.candidate_position
ORDER BY position, v.votes DESC
Output:
position doctor_id firstname lastname votes
1 3 doc3_fn doc3_ln 2
1 2 doc2_fn doc2_ln 2
2 7 doc7_fn doc7_ln 2
2 4 doc4_fn doc4_ln 1
2 1 doc1_fn doc1_ln 1
2 6 doc6_fn doc6_ln 1
2 5 doc5_fn doc5_ln 1
2 8 doc8_fn doc8_ln 1
2 9 doc9_fn doc9_ln 1
Demo
This query will give you the number of votes required to be a winner for a given position (note I have parameterised it with variables):
SET #position = 2;
SET #numwinners = 3;
SELECT #rank := #rank+1 AS rank, votes
FROM (SELECT COUNT(candidate_id) AS votes
FROM candidate_votes
WHERE candidate_position = #position
GROUP BY candidate_id
ORDER BY votes DESC) vr
JOIN (select #rank := 0) r
GROUP BY rank
HAVING rank = #numwinners
Output for your data with #position=2 and #numwinners=3:
rank votes
3 1
Having figured out how many votes are required to be a winner, we just need to find all candidates that have the required number of votes. Since the results are based on number of votes, ties are automatically taken care of. This query will generate that output:
SET #position = 2;
SET #numwinners = 3;
SELECT doc.firstname, doc.lastname, v.votes
FROM department_candidates dc
JOIN department dept ON dept.id=dc.department_id AND dept.name='Informatique'
JOIN doctor doc ON doc.id=dc.doctor_id
JOIN (SELECT candidate_id AS cid, count(candidate_id) AS votes
FROM candidate_votes
WHERE candidate_position = #position
GROUP BY cid) v
ON v.cid=dc.id
WHERE v.votes >= (SELECT votes
FROM (SELECT #rank := #rank+1 AS rank, votes
FROM (SELECT COUNT(candidate_id) AS votes
FROM candidate_votes
WHERE candidate_position = #position
GROUP BY candidate_id
ORDER BY votes DESC) vr
JOIN (select #rank := 0) r
GROUP BY rank
HAVING rank = #numwinners
) vt
)
ORDER BY v.votes DESC
Output for your data with #position=2 and #numwinners=3:
firstname lastname votes
doc7_fn doc7_ln 2
doc4_fn doc4_ln 1
doc1_fn doc1_ln 1
doc6_fn doc6_ln 1
doc5_fn doc5_ln 1
doc8_fn doc8_ln 1
doc9_fn doc9_ln 1
Output for your data with #position=1 and #numwinners=1:
firstname lastname votes
doc3_fn doc3_ln 2
doc2_fn doc2_ln 2
If you want to ensure the query works even if the value of #numwinners is greater than the number of candidates, change:
HAVING rank = #numwinners
to
HAVING rank = LEAST(#numwinners, (SELECT COUNT(DISTINCT candidate_id)
FROM candidate_votes
WHERE candidate_position = #position))
Demo

SELECT from multiple tables with cross-references and self-references [brain cracker]

Anyone up to an SQL challenge? Cause all my efforts so far are barely enough to simplify the problem and put it in a question...
Here it goes. In the example below we need to include:
All paid flights
Flights to a country (paid or not) if a person has made another flight to a paid city in that country
It's already tricky, but there is more to it.
If a person flies to a city with no entry fee, but it is located in a
country that DOES have a fee, that flight is still considered paid
and must be included as well.
EDIT: I've added flight 110, which should help reveal unnecessarily added free fligts.
Below is the result set that should come out of the SQL query:
+--------------------------------------------------------------+
| Desired result set |
+--------------------------------------------------------------+
| FlightNumber | ID | Name | LocationID | location.Name |
+--------------------------------------------------------------+
| 102 | 2 | Tom | 500 | NL - NoFee | -> because Tom has a paid flight to Amsterdam
| 103 | 2 | Tom | 501 | Amsterdam (NL) - Fee | -> because Amsterdam is a paid location
| 105 | 4 | Bob | 501 | Amsterdam (NL) - Fee | -> because Amsterdam is a paid location
| 107 | 6 | Bill | 503 | ITA - Fee | -> because ITA is a paid location
| 108 | 7 | Ryan | 503 | ITA - Fee | -> because ITA is a paid location
| 109 | 7 | Ryan | 505 | Venice (ITA) - NoFee | -> because Venice is located inside ITA
+--------------------------------------------------------------+
Does anyone know how get this sweet result set with SQL?
A good place to start:
SELECT flights.FlightNumber, people.ID, people.Name, flights.LocationID, locations.Name
FROM flights
INNER JOIN people ON (people.ID = flights.ID)
INNER JOIN locations ON (locations.LocationID = flights.LocationID)
CREATE/INSERT
CREATE TABLE `people` (
`ID` INT NOT NULL,
`Name` VARCHAR(45) NULL,
PRIMARY KEY (`ID`) );
CREATE TABLE `locations` (
`LocationID` INT NOT NULL,
`Name` VARCHAR(45) NULL,
`EntryFee` TINYINT(1) NULL,
`ParentLocationID` INT NULL,
PRIMARY KEY (`LocationID`) );
CREATE TABLE `flights` (
`FlightNumber` INT NOT NULL,
`ID` INT NULL,
`LocationID` INT NULL,
PRIMARY KEY (`FlightNumber`) ,
INDEX `fk_purchases_buyers_idx` (`LocationID` ASC) ,
INDEX `fk_flights_people1_idx` (`ID` ASC) ,
CONSTRAINT `fk_purchases_buyers`
FOREIGN KEY (`LocationID`)
REFERENCES `locations` (`LocationID`)
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `fk_flights_people1`
FOREIGN KEY (`ID`)
REFERENCES `people` (`ID`)
ON DELETE NO ACTION
ON UPDATE NO ACTION);
INSERT INTO `people` (`ID`, `Name`) VALUES
(1, 'John'),
(2, 'Tom'),
(3, 'Kate'),
(4, 'Bob'),
(5, 'Mike'),
(6, 'Bill'),
(7, 'Ryan');
INSERT INTO `locations` (`LocationID`, `Name`, `EntryFee`, `ParentLocationID`) VALUES
(500, 'NL - NoFee', 0, NULL),
(501, 'Amsterdam (NL) - Fee', 1, 500),
(502, 'Rotterdam (NL) - NoFee', 0, 500),
(503, 'ITA - Fee', 1, NULL),
(504, 'Rome (ITA) - Fee', 1, 503),
(505, 'Venice (ITA) - NoFee', 0, 503);
INSERT INTO `flights` VALUES
(100, 1, 500),
(101, 1, 502),
(102, 2, 500),
(103, 2, 501),
(104, 3, 500),
(105, 4, 501),
(106, 5, 502),
(107, 6, 503),
(108, 7, 503),
(109, 7, 505),
(110, 6, 502);
Unimportant note: I know this example is not completely logical in a sense of storing countries and cities in the same table and having flight records to a country and to a city. But this is just an example. At least it's more readable than t1.col1-like stuff.
Following should work :
SELECT F.FlightNumber, P.ID, P.Name, F.LocationID, LOC.Name AS Loc_Name
FROM flights F
INNER JOIN people P ON P.ID = F.ID
INNER JOIN (SELECT L1.LocationID, L1.`Name`, L1.`ParentLocationID`
FROM locations L1
LEFT JOIN locations L2 ON L1.`ParentLocationID` = L2.LocationID
WHERE L1.`EntryFee` = 1 OR L2.`EntryFee` = 1) AS LOC ON LOC.LocationID = F.LocationID
UNION
SELECT F.FlightNumber, PAID_FL.ID, PAID_FL.Name, F.LocationID, PAID_FL.Loc_Name
FROM flights F
INNER JOIN (SELECT F.FlightNumber, P.ID, P.Name, LOC.Name AS Loc_Name,LOC.`ParentLocationID` AS LocationID
FROM flights F
INNER JOIN people P ON P.ID = F.ID
INNER JOIN (SELECT L1.LocationID, L1.`ParentLocationID`, L2.`Name`
FROM locations L1
LEFT JOIN locations L2 ON L1.`ParentLocationID` = L2.LocationID
WHERE L1.`EntryFee` = 1 OR L2.`EntryFee` = 1) AS LOC ON LOC.LocationID = F.LocationID) PAID_FL ON F.ID = PAID_FL.ID AND F.LocationID = PAID_FL.LocationID
I didn't do any testing, but I think this will work:
SELECT F.FlightNumber, P.ID, P.Name, L.LocationID, L.Name
FROM flights F
INNER JOIN people P ON P.ID = F.ID
INNER JOIN locations L ON L.LocationID = F.LocationID
WHERE P.ID IN (SELECT F.ID FROM flights F INNER JOIN locations L ON L.LocationID = F.LocationID WHERE L.EntryFee = 1)
See it at SQLFiddle.com.
SELECT x.*
, COALESCE(y.entryfee,x.entryfee,0) fee
FROM locations x
LEFT
JOIN locations y
ON y.parentlocationid = x.locationid;