I'm looking to make a SQL query, but I can't do it... and I can't find an example like mine.
I have a simple table People with 3 columns, 7 records :
I'd like to get for each team, the average points of 2 bests people.
My Query:
SELECT team
, (SELECT AVG(point)
FROM People t2
WHERE t1.team = t2.team
ORDER
BY point DESC
LIMIT 2) as avg
FROM People t1
GROUP
BY team
Current result: (average on all people of each team)
Apparently, it's not possible to use a limit into subquery. "ORDER BY point DESC LIMIT 2" is ignored.
Result expected:
I want the average points of 2 bests people (with highest points) for each team, not the average points of all people of each team.
How can I do that? If anyone has any idea..
I'm on MySQL Database
Link of Fiddle : http://sqlfiddle.com/#!9/8c80ef/1
Thanks !
You can try this.
try to make a order number by a subquery, which order by point desc.
then only get top 2 row by each team, if you want to get other top number just modify the number in where clause.
CREATE TABLE `People` (
`id` int(11) NOT NULL,
`name` varchar(20) NOT NULL,
`team` varchar(20) NOT NULL,
`point` int(4) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `People` (`id`, `name`, `team`, `point`) VALUES
(1, 'Luc', 'Jupiter', 10),
(2, 'Marie', 'Saturn', 0),
(3, 'Hubert', 'Saturn', 0),
(4, 'Albert', 'Jupiter', 50),
(5, 'Lucy', 'Jupiter', 50),
(6, 'William', 'Saturn', 20),
(7, 'Zeus', 'Saturn', 40);
ALTER TABLE `People`
ADD PRIMARY KEY (`id`);
ALTER TABLE `People`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=8;
Query 1:
SELECT team,avg(point) totle
FROM People t1
where (
select count(*)
from People t2
where t2.id >= t1.id and t1.team = t2.team
order by t2.point desc
) <=2 ## if you want to get other `top` number just modify this number
group by team
Results:
| team | totle |
|---------|-------|
| Jupiter | 50 |
| Saturn | 30 |
This is a pain in MySQL. If you want the two highest point values, you can do:
SELECT p.team, AVG(p2.point)
FROM people p
WHERE p.point >= (SELECT DISTINCT p2.point
FROM people p2
WHERE p2.team = p.team
ORDER BY p2.point DESC
LIMIT 1, 1 -- get the second one
);
Ties make this tricky, and your question isn't clear on what to do about them.
Related
I am creating one SQL query for stock entry. In the last column, I need the total of the previous stock purchase.
S No Product Code Qty Qty Total
1 PO1 5 5
2 PO1 12 17
3 PO1 10 27
4 PO1 8 35
5 PO1 9 44
6 PO1 16 60
In every row of the last column, I am adding all the previous quantity for example S no. 1 quantity is 5. In S No. 2 I am adding quantity 5 and 12=17. S No 3 I am adding 5 + 12+10=27 and Soo on.
I am sorry if it is a duplicate question. I search google and StackOverflow but didn't get the answer. I am new to MySQL
I have added query below. I am new to SQL, any help appreciated,
Thanks in Advance.
CREATE TABLE `stock_table` (
`ID` int(11) NOT NULL,
`product_code` varchar(20) NOT NULL,
`qty` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `stock_table` (`ID`, `product_code`, `qty`) VALUES
(1, 'PO1', 5),
(2, 'PO1', 12),
(3, 'PO1', 10),
(4, 'PO1', 8),
(5, 'PO1', 9),
(6, 'PO1', 16);
You need a running sum. If you are using MySQL v 8.0, You can use SUM window function -
SELECT `ID`,
`product_code`,
`qty`,
SUM(`qty`) OVER (PARTITION BY `product_code` ORDER BY `ID`) Qty_Total
FROM `stock_table`;
fiddle
Your version of MariaDB does not support window functions. That leaves you with two options. One is a correlated subquery; the second is variables.
The first is easier to implement:
select st.*,
(select sum(st2.qty)
from stock_table st2
where st2.product_code = st.product_code and
st2.id <= st.id
) as running_qty
from stock_table st;
For performance, you want an index on stock_table(product_code, id, qty).
You can use correlated subquery to sum up the desired column as the row proceeds by increasing ID value :
select t.*,
( select sum(`qty`) from `stock_table` where `ID` <= t.`ID` ) as "Qty Total"
from `stock_table` t
order by t.`ID`;
Demo
P.S. : as the sample data has unique product_code value, I didn't need to include the part related to product_code column's matching within the subquery, if it's the case, then consider converting your derived column to :
( select sum(`qty`)
from `stock_table`
where `ID` <= t.`ID`
and `product_code` = t.`product_code` ) as "Qty Total"
I've read through the answers on MySQL order by before group by but applying it to my query ends up with a subquery in a subquery for a rather simple case so I'm wondering if this can be simplified:
Schema with sample data
For brevity I've omitted the other fields on the members table. Also, there's many more tables joined in the actual application but those are straightforward to join. It's the membership_stack table that's giving me issues.
CREATE TABLE members (
id int unsigned auto_increment,
first_name varchar(255) not null,
PRIMARY KEY(id)
);
INSERT INTO members (id, first_name)
VALUES (1, 'Tyler'),
(2, 'Marissa'),
(3, 'Alex'),
(4, 'Parker');
CREATE TABLE membership_stack (
id int unsigned auto_increment,
member_id int unsigned not null,
sequence int unsigned not null,
team varchar(255) not null,
`status` varchar(255) not null,
PRIMARY KEY(id),
FOREIGN KEY(member_id) REFERENCES members(id)
);
-- Algorithm to determine correct team:
-- 1. Only consider rows with the highest sequence number
-- 2. Order statuses and pick the first one found:
-- (active, completed, cancelled, abandoned)
INSERT INTO membership_stack (member_id, sequence, team, status)
VALUES (1, 1, 'instinct', 'active'),
(1, 1, 'valor', 'abandoned'),
(2, 1, 'valor', 'active'),
(2, 2, 'mystic', 'abandoned'),
(2, 2, 'valor', 'completed'),
(3, 1, 'instinct', 'completed'),
(3, 2, 'valor', 'active');
I can't change the database schema because the data is synchronized with an external data source.
Query
This is what I have so far:
SELECT m.id, m.first_name, ms.sequence, ms.team, ms.status
FROM membership_stack AS ms
JOIN (
SELECT member_id, MAX(sequence) AS sequence
FROM membership_stack
GROUP BY member_id
) AS t1
ON ms.member_id = t1.member_id
AND ms.sequence = t1.sequence
RIGHT JOIN members AS m
ON ms.member_id = m.id
ORDER BY m.id, FIELD(ms.status, 'active', 'completed', 'cancelled', 'abandoned');
This works as expected but members may appear multiple times if their "most recent sequence" involves more than one team. What I need to do is aggregate again on id and select the FIRST row in each group.
However that poses some issues:
There is no FIRST() function in MySQL
This entire resultset would become a subtable (subquery), which isn't a big deal here but the queries are quite big on the application.
It needs to be compatible with ONLY_FULL_GROUP_BY mode as it is enabled on MySQL 5.7 by default. I haven't checked but I doubt that FIELD(ms.status, 'active', 'completed', 'cancelled', 'abandoned') is considered a functionally dependent field on this resultset. The query also needs to be compatible with MySQL 5.1 as that is what we are running at the moment.
Goal
| id | first_name | sequence | team | status |
|----|------------|----------|----------|-----------|
| 1 | Tyler | 1 | instinct | active |
| 2 | Marissa | 2 | valor | completed |
| 3 | Alex | 2 | valor | active |
| 4 | Parker | NULL | NULL | NULL |
What can I do about this?
Edit: It has come to my attention that some members don't belong to any team. These members should be included in the resultset with null values for those fields. Question updated to reflect new information.
You can use a correlated subquery in the WHERE clause with LIMIT 1:
SELECT m.id, m.first_name, ms.sequence, ms.team, ms.status
FROM members AS m
JOIN membership_stack AS ms ON ms.member_id = m.id
WHERE ms.id = (
SELECT ms1.id
FROM membership_stack AS ms1
WHERE ms1.member_id = ms.member_id
ORDER BY ms1.sequence desc,
FIELD(ms1.status, 'active', 'completed', 'cancelled', 'abandoned'),
ms1.id asc
LIMIT 1
)
ORDER BY m.id;
Demo: http://rextester.com/HGU18448
Update
To include members who have no entries in the membership_stack table you should use a LEFT JOIN, and move the subquery condition from the WHERE clause to the ON clause:
SELECT m.id, m.first_name, ms.sequence, ms.team, ms.status
FROM members AS m
LEFT JOIN membership_stack AS ms
ON ms.member_id = m.id
AND ms.id = (
SELECT ms1.id
FROM membership_stack AS ms1
WHERE ms1.member_id = ms.member_id
ORDER BY ms1.sequence desc,
FIELD(ms1.status, 'active', 'completed', 'cancelled', 'abandoned'),
ms1.id asc
LIMIT 1
)
ORDER BY m.id;
Demo: http://rextester.com/NPI79503
I would do this using variables.
You are looking for the one membership_stack row that is maximal for your special ordering. I'm focusing just on that. The join back to members is trivial.
select ms.*
from (select ms.*,
(#rn := if(#m = member_id, #rn + 1,
if(#m := member_id, 1, 1)
)
) as rn
from membership_stack ms cross join
(select #m := -1, #rn := 0) params
order by member_id, sequence desc,
field(ms.status, 'active', 'completed', 'cancelled', 'abandoned')
) ms
where rn = 1;
The variables is how the logic is implemented. The ordering is key to getting the right result.
EDIT:
MySQL is quite finicky about LIMIT in subqueries. It is possible that this will work:
select ms.*
from membership_stack ms
where (sequence, status) = (select ms2.sequence, ms2.status
from membership_stack ms2
where ms2.member_id = ms.member_id
order by ms2.member_id, ms2.sequence desc,
field(ms2.status, 'active', 'completed', 'cancelled', 'abandoned')
limit 1
);
I ran into a problem trying to pull one action per user with the least priority, the priority is based on other columns content and is an integer,
This is the initial query :
SELECT
CASE
...
END AS dummy_priority,
id,
user_id
FROM
actions
Result :
id user_id priority
1 2345 1
2 2345 3
3 2999 5
4 2999 2
5 3000 10
Desired result :
id user_id priority
1 2345 1
4 2999 2
5 3000 10
Following what i want i tried
SELECT x.id, x.user_id, MIN(x.priority)
FROM (
SELECT
CASE
...
END AS priority,
id,
user_id
FROM
actions
) x
GROUP BY x.user_id
Which didn't work
Error Code: 1055. Expression #1 of SELECT list is not in GROUP BY
clause and contains nonaggregated column 'x.id' which is not
functionally dependent on columns in GROUP BY clause;
Most examples of this I found were extracting just the user_id and priority and then doing an inner join with both of them to get the row, but I can't do that since (priority, user_id) isn't unique
A simple verifiable example would be
CREATE TABLE `actions` (
`id` int(11) NOT NULL,
`user_id` int(11) DEFAULT NULL,
`priority` int(11) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
INSERT INTO `actions` (`id`, `user_id`, `priority`) VALUES
(1, 2345, 1),
(2, 2345, 3),
(3, 2999, 5),
(4, 2999, 2),
(5, 3000, 10);
how to extract the desired result (please hold in mind that this table is a subquery)?
The proper way to do this would involve a subquery of some sort . . . and that would require repeating the case definition.
Here is another method, using the substring_index()/group_concat() trick:
SELECT SUBSTRING_INDEX(GROUP_CONCAT(x.id ORDER BY x.priority), ',', 1) as id,
x.user_id, MIN(x.priority)
FROM (SELECT (CASE ...
END) AS priority,
id, user_id
FROM actions a
) x
GROUP BY x.user_id;
And that proper way in full...
SELECT x...
, CASE...x... priority
FROM my_table x
JOIN
( SELECT user_id
, MIN(CASE...) priority
FROM my_table
GROUP
BY user_id
) y
ON y.user_id = x.user_id
AND y.priority = CASE...x...;
This should work ...
SELECT id , user_id, priority FROM actions act
INNER JOIN
(SELECT
user_id, MIN(priority) AS priority
FROM
actions
GROUP BY user_id) pri
ON act.user_id = pri.user_id AND act.priority = pri.prority
I have the following table: Tree. I am trying to select the highest Primary Key ID per scenario_id
id user_id scenario_id
----------------------------------
100 1 10
200 1 10
300 1 5
400 1 5
500 1 5
SELECT * FROM tree
WHERE user_id = 1
GROUP BY scenario_id
ORDER BY id DESC
With my above query I don't get the largest ID. I get 300 and 100 -- But I want to get 200 and 500.
Here is the table dump to test:
CREATE TABLE IF NOT EXISTS `tree` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`scenario_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1;
INSERT INTO `tree` (`id`, `user_id`, `scenario_id`) VALUES
(5, 1, 5),
(100, 1, 10),
(200, 1, 10),
(300, 1, 5),
(400, 1, 5),
(500, 1, 5);
Use an aggregate function to get a specific value for a group
SELECT scenario_id, max(id) as max_id
FROM tree
WHERE user_id = 1
GROUP BY scenario_id
If you would like to keep your select * and avoid grouping to get these results from the same record you could also use a self join:
SELECT t1.*
FROM
tree t1 LEFT JOIN tree t2 ON t1.scenario_id = t2.scenario_id AND t2.id > t1.id
WHERE
t2.id IS NULL;
Sometimes this can be useful to pull additional fields that you can't get as efficiently using a group by/aggregate solution.
I have a table with timestamped rows: say, some feed with authors:
CREATE TEMPORARY TABLE `feed` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`author` VARCHAR(255) NOT NULL,
`tm` DATETIME NOT NULL
);
I'd like to sort by tm DESC but in such a way that rows from one author do stick together.
For instance, having
INSERT INTO `feed` VALUES
( 5, 'peter', NOW()+1 ),
( 4, 'helen', NOW()-1 ),
( 3, 'helen', NOW()-2 ),
( 2, 'peter', NOW()-10 ),
( 1, 'peter', NOW()-11 );
The result set should be sorted by tm DESC, but all peter posts go first because his post is the most recent one. The next set of rows should originate from the author with the 2nd most recent post. And so on.
5 peter
2 peter
1 peter
3 helen
2 helen
First we sort authors by recent post, descending. Then, having this "rating", we sort the feed with authors sorted by recent post.
Create in line view calculating the Min Tm and then join to it.
SELECT f.*
FROM feed f
INNER JOIN (SELECT MAX(TM) MAXTM,
author
FROM Feed
GROUP BY Author)m
ON f.author = m.author
ORDER BY m.MAXTM DESC,
f.author
DEMO
You could try something like this:
select *
from feed
order by
(select max(tm) from feed f2 where f2.author = feed.author) desc,
tm desc
This sorts first by the time of the most recent post of the author, then by tm.
SELECT *
FROM `feed`
LEFT JOIN (
SELECT
#rownum:=#rownum+1 AS `rowid`,
`author`,
MAX(`tm`) AS `max_tm`
FROM (SELECT #rownum:=0) r, `feed`
GROUP BY `author`
ORDER BY `max_tm` DESC
) `feedsort` ON(`feed`.`author` = `feedsort`.`author`)
ORDER BY
`feedsort`.`rowid` ASC,
`feed`.`tm` DESC;
This solves the problem but I'm sure there's a better solution