Select maximum value of each member from table [duplicate] - mysql

This question already has answers here:
Fetch the rows which have the Max value for a column for each distinct value of another column
(35 answers)
Closed 8 years ago.
I want to select the best result of each member from the mysql table, for a given discipline.
(if there are entries with the same value, the entries with the lowest event start date should be taken)
DDLs:
CREATE TABLE `results` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`discipline` int(11) NOT NULL,
`member` int(11) DEFAULT '0',
`event` int(11) DEFAULT '0',
`value` int(11) DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `member_2` (`member`,`discipline`,`event`)
);
INSERT INTO results VALUES
(1,1,2,4,10),
(2,1,1,4, 8),
(3,1,2,5, 9),
(4,2,3,5, 9),
(5,1,2,6,11),
(6,1,2,7,11),
(7,1,2,1,11),
(8,1,2,3, 7);
CREATE TABLE `events` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
`startDate` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO events VALUES
(1 ,'Not in scope','2012-05-23'),
(3 ,'Test 0', '2014-05-09'),
(4 ,'Test 1', '2014-05-10'),
(5 ,'Test 2', '2014-05-11'),
(6 ,'Test 3', '2014-05-12'),
(7 ,'Test 4', '2014-05-13');
SELECT * FROM results;
+----+------------+--------+-------+-------+
| id | discipline | member | event | value |
+----+------------+--------+-------+-------+
| 1 | 1 | 2 | 4 | 10 |
| 2 | 1 | 1 | 4 | 8 |
| 3 | 1 | 2 | 5 | 9 |
| 4 | 2 | 3 | 5 | 9 |
| 5 | 1 | 2 | 6 | 11 |
| 6 | 1 | 2 | 7 | 11 |
| 7 | 1 | 2 | 1 | 11 |
| 8 | 1 | 2 | 3 | 7 |
+----+------------+--------+-------+-------+
SELECT * FROM events;
+----+--------------+---------------------+
| id | name | startDate |
+----+--------------+---------------------+
| 1 | Not in scope | 2012-05-23 00:00:00 |
| 3 | Test 0 | 2014-05-09 00:00:00 |
| 4 | Test 1 | 2014-05-10 00:00:00 |
| 5 | Test 2 | 2014-05-11 00:00:00 |
| 6 | Test 3 | 2014-05-12 00:00:00 |
| 7 | Test 4 | 2014-05-13 00:00:00 |
+----+--------------+---------------------+
Result should be:
+---------+------------+--------+-------+-------+
| id | discipline | member | event | value |
+---------+------------+--------+-------+-------+
| 3 | 1 | 1 | 4 | 8 |
| 5 | 1 | 2 | 6 | 11 |
+---------+------------+--------+-------+-------+
My first approach was to group by member id, but it's not that easy. So I tried a lot of different approaches from the web and from my colleages.
The last one was:
select res.*
from `results` as res
join (select id, max(value)
from results
join events on results.event = events.id
where discipline = 1
events.name like 'Test%'
Group by id
Order by events.startDate ASC) as tmpRes
on res.id = tmpRes.id
group by member
order by value DESC
But the result in this example would be a random result id for member 2.

Should be correct now, but let me know if there's a mistake...
SELECT r.*
FROM events e
JOIN results r
ON r.event = e.id
JOIN
( SELECT r.member
, MIN(e.startdate) min_startdate
FROM events e
JOIN results r
ON r.event = e.id
JOIN
( SELECT member
, MAX(value) max_value
, discipline
FROM events e
JOIN results r
ON r.event = e.id
WHERE discipline = 1
AND name LIKE 'Test%'
GROUP
BY member
) x
ON x.member = r.member
AND x.max_value = r.value
AND x.discipline = r.discipline
AND e.name LIKE 'Test%'
GROUP
BY member
) y
ON y.member = r.member
AND y.min_startdate = e.startdate;
Although fast, because these queries can get rather complex and cumbersome, there's an undocumented hack that achieves the same result. It goes something like this...
SELECT *
FROM
( SELECT r.*
FROM events e
JOIN results r
ON r.event = e.id
WHERE discipline = 1
AND name LIKE 'Test%'
ORDER
BY member
, value DESC
, startdate
) x
GROUP
BY member;

If I understand your question correctly, you need to group on member in the sub-query. Try the following:
select res.*
from `results` as res
join (select member, min(event) AS minEvent, max(value) AS maxValue
from results
where discipline = 1
Group by member) as tmpRes
on res.member = tmpRes.member AND res.event=tmpRes.minEvent AND res.value=tmpRes.maxValue
order by res.value
EDIT (bast on most recent comment): If that's the case, you'll need to join on the Events table. Unless the startDate field is actually a temporal field, it's going to be a big mess.
It would have made things easier with all the requirements included in the original question.

Related

How to Allocate Random Jobs to List of Agents in Mysql?

I am looking to write a script that will allow me to allocate random jobs to 7 different agents. The following are my 2 tables:
DROP TABLE IF EXISTS jobs;
CREATE TABLE `Jobs` (
`Job_id` SERIAL PRIMARY KEY,
`Start_Date` date DEFAULT NULL,
`End_Date` date DEFAULT NULL,
`Ref_no` int NOT NULL
) ;
INSERT INTO Jobs(Job_id,Start_Date, End_Date, Ref_no) VALUES
(1,'2018-09-01','2021-08-31',123456789),
(2,'2019-10-03','2020-10-02',987654321),
(3,'2020-11-01','2021-10-02',543210123),
(4,'2020-12-01','2022-11-30',481216181),
(5,'2018-04-01','2020-03-31',246810121),
(6,'2019-05-30','2020-05-29',369121518),
(7,'2019-11-01','2020-10-31',581114179);
DROP TABLE IF EXISTS agents;
CREATE TABLE `Agents` (
`Agent_id` SERIAL PRIMARY KEY,
`Agent_Name` varchar(255) NOT NULL UNIQUE
) ;
INSERT INTO Agents(Agent_id, Agent_Name) VALUES
(1,'Humpty'),
(2,'Mickey'),
(3,'Minnie'),
(4,'Daffy'),
(5,'Ellie'),
(6,'Jack'),
(7,'Jill');
Now what I would like to do is write a script that would randomly allocate the jobs across the 7 agents. Would really appreciate it if somebody could advise on how I could start this off.
Thanks in advance.
In MySQL 8.x you can combine RAND() with ROW_NUMBER() to associate random rows. For example:
select *
from (
select *, row_number() over(order by rand()) as rn
from `Jobs`
) j
join (
select *, row_number() over(order by rand()) as rn
from `Agents`
) a on j.rn = a.rn
See running example at DB Fiddle.
Here's a method for versions of MySQL prior to 8.0...
SELECT x.*
, MOD(#i:=#i+1,7) + 1 i -- where '7' is the number of agents.
FROM
( SELECT j.*
FROM jobs j
ORDER
BY RAND()
) x
JOIN (SELECT #i := 0) vars
ORDER
BY i;
Sample output:
+--------+------------+------------+-----------+------+
| Job_id | Start_Date | End_Date | Ref_no | i |
+--------+------------+------------+-----------+------+
| 5 | 2018-04-01 | 2020-03-31 | 246810121 | 1 |
| 3 | 2020-11-01 | 2021-10-02 | 543210123 | 2 |
| 1 | 2018-09-01 | 2021-08-31 | 123456789 | 3 |
| 7 | 2019-11-01 | 2020-10-31 | 581114179 | 4 |
| 4 | 2020-12-01 | 2022-11-30 | 481216181 | 5 |
| 6 | 2019-05-30 | 2020-05-29 | 369121518 | 6 |
| 2 | 2019-10-03 | 2020-10-02 | 987654321 | 7 |
+--------+------------+------------+-----------+------+

Shared tenant objects

I have a multi tenant application with a single database. I've a "entity" table where all objects are stored. "sahred_entity" table is used to store objects that are shared by a Tenant X to Tenant Y. For example "Tenant 2" can share "Entity with ID 4" to "Tenant 1".
In the example below "Entity with ID 4" is shared to "Tenant 1" and "Tenant 3"
+--------+--------------------------------------------------
| Table | Create Table
+--------+--------------------------------------------------
| entity | CREATE TABLE `entity` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`tenant_id` int(10) unsigned NOT NULL,
`added_at` timestamp NOT NULL,
`color` varchar(20) NOT NULL,
`size` varchar(5) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1 |
+--------+--------------------------------------------------
+---------------+---------------------------------------
| Table | Create Table
+---------------+---------------------------------------
| shared_entity | CREATE TABLE `shared_entity` (
`tenant_to` int(10) unsigned NOT NULL,
`tenant_from` int(10) unsigned NOT NULL,
`entity_id` int(10) unsigned NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |
+---------------+---------------------------------------
The sample data is
select * from entity;
+----+-----------+---------------------+--------+------+
| id | tenant_id | added_at | color | size |
+----+-----------+---------------------+--------+------+
| 1 | 1 | 2019-03-07 00:00:00 | red | m |
| 2 | 1 | 2019-03-07 00:00:00 | green | xl |
| 3 | 2 | 2019-03-07 00:00:00 | green | xl |
| 4 | 2 | 2019-03-07 00:00:00 | red | m |
| 5 | 3 | 2019-03-07 00:00:00 | yellow | l |
+----+-----------+---------------------+--------+------+
select * from shared_entity;
+-----------+-------------+-----------+
| tenant_to | tenant_from | entity_id |
+-----------+-------------+-----------+
| 1 | 2 | 4 |
| 3 | 2 | 4 |
+-----------+-------------+-----------+
Now I need to create a simple search query. For now I found two ways how to do it. The first is via self joining
SELECT e.* FROM `entity` as e
LEFT JOIN entity as e1 ON (e.id = e1.id AND e1.tenant_id = 1)
LEFT JOIN entity as e2 ON (e.id = e2.id AND e2.id IN (4))
WHERE (e1.id IS NOT NULL OR e2.id IS NOT NULL) AND e.`color` = 'red';
The second is via sub query and union
SELECT * FROM
(
SELECT * FROM entity as e1 WHERE e1.tenant_id = 1
UNION
SELECT * FROM entity as e2 WHERE e2.id IN(4)
) as entity
WHERE color = 'red';
Both of queries return expected result
+----+-----------+---------------------+-------+------+
| id | tenant_id | added_at | color | size |
+----+-----------+---------------------+-------+------+
| 1 | 1 | 2019-03-07 00:00:00 | red | m |
| 4 | 2 | 2019-03-07 00:00:00 | red | m |
+----+-----------+---------------------+-------+------+
But which approach is better for large tables? How to create right index? Or maybe there is a better solution?
You could also use the following query to get the same results
SELECT *
FROM entity
WHERE (tenant_id = 1 or id = 4) AND color = 'red'
It is not clear to me why you need all the joins
Every table should have a PRIMARY KEY. shared_entity needs PRIMARY KEY(tenant_from, tenant_to, entity_id); any order would probably suffice.
As for performance, hogan's suggestion, together with INDEX(color), is fine for a small table:
SELECT *
FROM entity
WHERE (tenant_id = 1 OR id = 4)
AND color = 'red'
But OR prevents most forms of optimization. If color is selective enough, then this is not a problem; it will simply scan through all the "red" items checking each for tenent_id and for id.
If there are thousands of red items, this will run faster:
( SELECT *
FROM entity
WHERE tenant_id = 1
AND color = 'red' )
UNION DISTINCT
( SELECT *
FROM entity
WHERE id = 4
AND color = 'red' )
together with
INDEX(color, tenant_id) -- in either order
-- PRIMARY KEY(id) -- already exists and is unique
UNION DISTINCT can be sped up to UNION ALL if you know that tenant-1 and id-4 don't refer to the same row.

MySQL: COUNT with implicit GROUP BY

I've been trying to guess how to solve my problem for some time and I cannot seem to find a solution, so I come to you, experts.
What I've got
A MySQL table with the following structure and values (as an example):
+----+---------+----------------+-----------------+--------------+
| id | item_id | attribute_name | attribute_value | deleted_date |
+----+---------+----------------+-----------------+--------------+
| 1 | 2 | action | call | NULL |
| 2 | 2 | person | Joseph | NULL |
| 3 | 2 | action | fault | NULL |
| 4 | 2 | otherattr | otherval | NULL |
| 5 | 5 | action | call | NULL |
| 6 | 5 | person | Mike | NULL |
| 7 | 5 | action | sprint | NULL |
| 8 | 8 | action | call | NULL |
| 9 | 8 | person | Joseph | NULL |
| 10 | 8 | action | block | NULL |
| 11 | 8 | action | call | NULL |
+----+---------+----------------+-----------------+--------------+
What I need
I'd like a query to return me how many items (item_id) have at least one attribute_name with 'action' and with attribute_value as 'call', grouped by 'person', but only counting one of them.
So, if - like in the example, at ids 8 and 11 - there is an item_id with two "action" = "call", only COUNT one of them.
The query should return something like this:
+--------+--------------+
| person | action_calls |
+--------+--------------+
| Joseph | 2 |
| Mike | 1 |
+--------+--------------+
The problem
The problem is that I don't know how to do that in a simple way that would not make a huge performance increment, as this query will be returning and searching along a lot of rows - and returning a lot of them, too, in some cases.
The only thing that comes to my mind is with nested and nested queries, and I'd like to avoid that.
If I make a COUNT(DISTINCT), it only returns '1' in 'Joseph', because the value is always 'call', and if I GROUP BY b.item_id, it returns me two rows with Joseph (and, in this case too, it counts both 'call' attributes, so it wouldn't be the solution neither).
What I've tried
The query that I've tried is the following:
SELECT a.attribute_value AS person, COUNT(b.attribute_value) AS action_calls
FROM `er_item_attributes` a, `er_item_attributes` b
WHERE a.attribute_name = 'person'
AND b.item_id IN (SELECT DISTINCT item_id FROM er_item_parents WHERE parent_id IN (1234,4567))
AND b.item_id = a.item_id
AND b.attribute_name = 'action'
AND b.attribute_value = 'call'
AND b.deleted_date IS NULL
GROUP BY a.attribute_value, b.attribute_name
Additional information
The item_id, as you can see, will be also chosen from an inner WHERE clause, because the ones that are valid are in another table (just like a parent - son table). The parent_id numbers are for an example and are not relevant.
To sum up
How can I make a COUNT in MySQL to behave like a COUNT GROUP BY without nesting SELECTs that could deteriorate the performance?
If any further information was needed, comment it and I will try to add it.
Also, any recommendations on another way to query the information needed to improve performance will be welcome.
Thank you everyone for your time and help!
Kind regards.
Try this!
SELECT attribute_value AS person, COUNT(*) FROM `stack_1239`
WHERE item_id IN (
SELECT item_id FROM `stack_1239` WHERE attribute_name = 'action' AND attribute_value = 'call'
)
AND attribute_name = 'person'
GROUP BY person;
:)
DROP TABLE IF EXISTS eav_hell;
CREATE TABLE eav_hell
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,entity INT NOT NULL
,attribute VARCHAR(20) NOT NULL
,value VARCHAR(20) NOT NULL
);
INSERT INTO eav_hell
VALUES
( 1 ,2 ,'action','call'),
( 2 ,2 ,'person','Joseph'),
( 3 ,2 ,'action','fault'),
( 4 ,2 ,'otherattr','otherval'),
( 5 ,5 ,'action','call'),
( 6 ,5 ,'person','Mike'),
( 7 ,5 ,'action','sprint'),
( 8 ,8 ,'action','call'),
( 9 ,8 ,'person','Joseph'),
(10 ,8 ,'action','block'),
(11 ,8 ,'action','call');
SELECT e1.entity
, e1.value person
, e2.value action
, COUNT(*)
FROM eav_hell e1
LEFT
JOIN eav_hell e2
ON e2.entity = e1.entity
AND e2.attribute = 'action'
AND e2.value = 'call'
WHERE e1.attribute = 'person'
GROUP
BY entity
, person
, action;
+--------+--------+--------+----------+
| entity | person | action | COUNT(*) |
+--------+--------+--------+----------+
| 2 | Joseph | call | 1 |
| 5 | Mike | call | 1 |
| 8 | Joseph | call | 2 |
+--------+--------+--------+----------+
Edit:
SELECT e1.value person
, e2.value action
, COUNT(DISTINCT e1.entity)
FROM eav_hell e1
LEFT
JOIN eav_hell e2
ON e2.entity = e1.entity
AND e2.attribute = 'action'
AND e2.value = 'call'
WHERE e1.attribute = 'person'
GROUP
BY person
, action;
+--------+--------+---------------------------+
| person | action | COUNT(DISTINCT e1.entity) |
+--------+--------+---------------------------+
| Joseph | call | 2 |
| Mike | call | 1 |
+--------+--------+---------------------------+

Populate sql column from query result

I have 2 tables
requests
requests_votes
request structure is:
id | req_name | content | hits
------------------------------
1 | Sample | Some | NULL
2 | Sample | Some | NULL
3 | Sample | Some | NULL
4 | Sample | Some | NULL
5 | Sample | Some | NULL
requests_votes structure is:
id | requestid | user_id
------------------------
10 | 2 | 2556
18 | 2 | 2522
33 | 3 | 120
44 | 2 | 1559
98 | 5 | 253
width the following query im able to calculate how votes have an item(request)
SELECT `requestid` , count( requests_votes.id ) AS totals
FROM `requests_votes`
INNER JOIN `requests` ON requests.id = requests_votes.requestid
GROUP BY `requestid`
output:
requestid | totals
------------------
2 | 3
3 | 1
5 | 1
i want to populate the hits column inside requests table with count( requests_votes.id ) result.
I've go trough the similar questions asked here on stackoverflow, but couldn't find the solution.
You can use JOIN in an UPDATE query:
UPDATE `requests` T1 JOIN
(SELECT `requestid` , count( requests_votes.id ) AS totals
FROM `requests_votes`
INNER JOIN `requests` ON requests.id = requests_votes.requestid
GROUP BY `requestid`) T2 ON T1.id=T2.requestid
SET T1.hits=T2.totals

count the sum of a column where data is contained in multiple columns

I have a debating competition where I want to find out which team has won and lost the most debates. The problem I am having is the id of the two teams competing in the debate is in two different columns (hostid, visitid).
I have the below so far which gives me what I want however it only shows the visitid data.
CREATE TABLE teams (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255)
) DEFAULT CHARACTER SET utf8 ENGINE=InnoDB;
CREATE TABLE debates (
debateid INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
debatedate DATE NOT NULL,
hostid INT,
visitid INT,
winnerid INT
) DEFAULT CHARACTER SET utf8 ENGINE=InnoDB;
INSERT INTO teams (id, name) VALUES
(1,'team one'),
(2,'team two'),
(3,'team three'),
(4,'team four'),
(5,'team five'),
(6,'team six');
INSERT INTO debates (debateid, debatedate,hostid, visitid, winnerid ) VALUES
(1,'2012-01-11', 1,2,1),
(2,'2012-01-11', 3,4,4),
(3,'2012-02-11', 5,6,5),
(4,'2012-02-11', 1,4,1),
(5,'2012-02-11', 2,5,5),
(6,'2012-02-11', 3,6,3),
(7,'2012-03-11', 6,1,1),
(8,'2012-03-11', 5,2,5),
(9,'2012-03-11', 3,4,4);
SELECT
visitid AS id,
t.name AS name,
sum(visitid= deb.winnerid) as w,
sum(visitid != deb.winnerid) as l
FROM debates AS deb
JOIN teams t ON t.id = deb.visitid
WHERE visitid != -1
AND debatedate < CURDATE( )
GROUP BY id
ORDER BY w DESC
RESULT
-----------------------------------------
| ID | NAME | W | L |
| 4 | team four | 2 | 1 |
| 5 | team five | 1 | 0 |
| 1 | team one | 1 | 0 |
| 6 | team six | 0 | 2 |
| 2 | team two | 0 | 2 |
-----------------------------------------
How can I combine these two columns, I am aware of union but I can't think of a way to implement this in this situation or what method I should use?
If I had it working as intended the result would be the below eg where hostid or visitid = winnerid
-----------------------------------------
| ID | NAME | W | L |
| 1 | team one | 3 | 0 |
| 5 | team five | 3 | 0 |
| 4 | team four | 2 | 1 |
| 3 | team three | 1 | 2 |
| 2 | team two | 0 | 3 |
| 6 | team six | 0 | 3 |
-----------------------------------------
See fiidle for example
SELECT t.id,
t.name,
SUM(t.id = d.winnerid) AS w,
SUM(t.id != d.winnerid) AS l
FROM debates AS d
JOIN teams AS t ON t.id IN (d.hostid, d.visitid)
WHERE d.visitid != -1 -- not sure what purpose this serves
AND d.debatedate < CURDATE()
GROUP BY t.id
ORDER BY w DESC
See it on sqlfiddle.