mysql - grouping results taking the first in an ordered subquery - mysql

In mysql, I'm having trouble pulling a single row for each foreign_id based on the largest value. Strangely, different versions of mysql works (listed below)
id foreign_id value
---------------------
1 1 1000
2 1 2000
3 2 2000
4 2 1000
5 3 2000
I try to pull ids 2,3,5 not 1,3,5
CREATE TABLE `docs` (
`id` int(11) NOT NULL,
`foreign_id` int(6) DEFAULT NULL,
`value` int(8) UNSIGNED NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
ALTER TABLE `docs`
ADD PRIMARY KEY (`id`),
ADD KEY `foreign_id_index` (`foreign_id`);
INSERT INTO `docs` (`id`, `foreign_id`, `value`) VALUES
(1, 1, 1000), (2, 1, 2000), (3, 2, 2000), (4, 2, 1000), (5, 3, 2000)
select
docs.id, docs.foreign_id, docs.value
FROM docs
INNER JOIN
(select id, max(value) from docs group by foreign_id) sub
ON sub.id = docs.id
# expected results are ids (2,3,5), not (1,3,5)

It's simpler than it looks
SELECT D.id, D.foreign_id, max_vals.max_val as value
FROM docs D
JOIN
(SELECT foreign_id, MAX(value) as max_val
FROM docs
GROUP BY foreign_id) max_vals
ON D.foreign_id=max_vals.foreign_id and D.value=max_vals.max_val
In this case, you need JOIN and not INNER JOIN
There is a difficult case where there are 2 or more foreign_ids with the same MAX value...

Related

Get Rows contains Pattern MYSQL

I have very simple table like below
CREATE TABLE `tbl_data` (
`id` int(11) NOT NULL,
`won` tinyint(4) NOT NULL,
`time` datetime NOT NULL DEFAULT current_timestamp()
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Dumping data for table `tbl_data`
--
INSERT INTO `tbl_data` (`id`, `won`, `time`) VALUES
(1, 1, '2022-10-18 05:21:37'),
(2, 2, '2022-10-18 05:21:37'),
(5, 0, '2022-10-18 05:22:02'),
(6, 2, '2022-10-18 05:22:02'),
(7, 2, '2022-10-18 05:22:18'),
(8, 1, '2022-10-18 05:22:18');
--
-- Indexes for dumped tables
--
--
-- Indexes for table `tbl_data`
--
ALTER TABLE `tbl_data`
ADD PRIMARY KEY (`id`);
--
-- AUTO_INCREMENT for dumped tables
--
--
-- AUTO_INCREMENT for table `tbl_data`
--
ALTER TABLE `tbl_data`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=9;
COMMIT;
What I am trying to achieve is select all rows which contains pattern of won like 0,2,2
so it will select id called 5,6,7 in above example.
if my pattern is like 1,2
it should select ids called 1 and 2 like this.
Database Fiddle is here
https://www.db-fiddle.com/#&togetherjs=RDJuie555L
I am finding solution from last hour but not able to achieve the goal. Let me know if any expert here can help me for same.
Thanks!
WITH cte AS (
SELECT id id0,
LEAD(id) OVER w id1,
LEAD(id,2) OVER w id2,
CONCAT_WS(',',
won,
LEAD(won) OVER w,
LEAD(won,2) OVER w) won_list
FROM tbl_data
WINDOW w AS (ORDER BY id)
)
SELECT tbl_data.*
FROM tbl_data
JOIN cte ON tbl_data.id IN (cte.id0, cte.id1, cte.id2)
WHERE cte.won_list = '0,2,2'
id
won
time
5
0
2022-10-18 05:22:02
6
2
2022-10-18 05:22:02
7
2
2022-10-18 05:22:18
fiddle
PS. If there exists more than one copy of needed pattern then the rows will mix. Add ORDER BY id clause to the outer query for to sort output rows and any of cte.idN (N=0..2) to its output list for to see separate pattern groups.

MySQL - GROUP results BY a single column alongside ORDER BY and LIMIT

I want to select a list of tasks from my database. The tasks have a category_id. I want to get a singlur task per category_id. So if I, for example, had 10 tasks that are linked to like 6 categories that would result in 6 results. The 6 results I want are determined by their id, the lowest id among the GROUP BY is the correct record for that GROUP. Also the maximum result set can be no larger than 20 ('LIMIT').
SELECT * FROM `task` WHERE `datetime`<NOW() `task_status_id`=1 GROUP BY `category_id` ORDER BY `id` ASC LIMIT 20
What is wrong with the above query, I got no clue, I'm also at a loss getting any google results for this.
ADDED LATER
http://sqlfiddle.com/#!9/fa39cf
CREATE TABLE `category` (
`id` int(10) UNSIGNED NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `category` (`id`) VALUES
(1),
(2),
(3);
CREATE TABLE `task` (
`id` int(10) UNSIGNED NOT NULL,
`category_id` int(10) UNSIGNED NOT NULL,
`task_status_id` int(10) UNSIGNED DEFAULT '1',
`datetime` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `task` (`id`, `category_id`, `task_status_id`, `datetime`) VALUES
(3, 2, 1, '2018-07-24 11:20:26'),
(4, 2, 1, '2018-07-24 11:20:26'),
(5, 3, 1, '2018-07-24 11:21:35'),
(6, 3, 1, '2018-07-24 11:21:35');
You can try first finding the smallest id for each category and then joining it with the task table to get the remaining details.
SELECT t.* FROM task t
JOIN (SELECT category_id, min(id) id from task group by category_id) tc
ON (t.id = tc.id)
LIMIT 20

MySQL Joining 3 Quiz Tables with Calculated Totals By Attribute/User

I have the following tables which I am trying to combine into one query (with the ultimate goal being to output a CSV file from PHP):
users
id, name, email
eg. data
1, John, email#email.com
2, Jane, email#email.com
questions - a static list of questions, each question is of one of 3 types stored as "attribute," which is a single letter [A, B, C]
id, text, attribute
eg. data
1, How cool are dogs?, A
2, How cool are cats?, B
3, How cool are fish?, A
4, How cool are mice?, C
5, How cool are birds?, B
users_questions - where answer is an integer [1-5]
id, user_id, question_id, answer
eg. data
1, 1, 1, 2
2, 1, 2, 5
3, 1, 3, 1
4, 1, 4, 1
5, 1, 5, 4
6, 2, 1, 4
7, 2, 2, 1
8, 2, 3, 3
9, 2, 4, 2
10, 2, 5, 2
Desired results:
I'm trying to combine all of this data as one query with the questions for each user totalled grouped by attribute so the output format is something like:
users.id, users.name, users.email , A_question_total, B_question_total, C_question_total
1, John , email#email.com, 3, 9, 1
2, Jane , email#email.com, 7, 3, 2
What I have currently:
I've tried the queries below which all give me almost what I need:
I'm able to select everything joined together, but this duplicates users and questions and doesn't give me the question totals by user/attribute:
Select * FROM users
JOIN users_questions ON users.id = users_questions.user_id
JOIN questions ON questions.id = users_questions.question_id;
I can also select all the question totals by user/attribute, but then I'd have to grab the users separately and then connect them together in PHP.
SELECT questions.attribute, users_questions.user_id, SUM(users_questions.answer) AS `total` FROM `questions` LEFT OUTER JOIN `users_questions` ON questions.id = users_questions.`question_id` GROUP BY users_questions.user_id, questions.attribute;
I'm wondering if there is a way with complex joining, grouping, subqueries, etc. to be able to do this all in one query. I'm struggling as to how to combine the above two queries and also convert what is essentially separate calculated total "rows" in columns for each user.
Here's the sql dump of the example data:
DROP TABLE IF EXISTS `questions`;
CREATE TABLE `questions` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`text` varchar(30) DEFAULT NULL,
`attribute` enum('A','B','C') DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `questions` (`id`, `text`, `attribute`)
VALUES
(1,'How cool are dogs?','A'),
(2,'How cool are cats?','B'),
(3,'How cool are fish?','A'),
(4,'How cool are mice?','C'),
(5,'How cool are birds?','B');
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(10) DEFAULT NULL,
`email` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `users` (`id`, `name`, `email`)
VALUES
(1,'John','email#email.com'),
(2,'Jane','email#email.com');
DROP TABLE IF EXISTS `users_questions`;
CREATE TABLE `users_questions` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(11) unsigned DEFAULT NULL,
`question_id` int(11) unsigned DEFAULT NULL,
`answer` tinyint(1) unsigned DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `users_questions` (`id`, `user_id`, `question_id`, `answer`)
VALUES
(1,1,1,2),
(2,1,2,5),
(3,1,3,1),
(4,1,4,1),
(5,1,5,4),
(6,2,1,4),
(7,2,2,1),
(8,2,3,3),
(9,2,4,2),
(10,2,5,2);
Any help is appreciated!
I think this query (SQLFiddle) will solve your problem:
Select u.id, u.name, u.email,
SUM(case q.attribute when 'A' then uq.answer else 0 end) as A_question_total,
SUM(case q.attribute when 'B' then uq.answer else 0 end) as B_question_total,
SUM(case q.attribute when 'C' then uq.answer else 0 end) as C_question_total
FROM users u
JOIN users_questions uq ON u.id = uq.user_id
JOIN questions q ON q.id = uq.question_id
group by u.id
Output:
id name email A_question_total B_question_total C_question_total
1 John email#email.com 3 9 1
2 Jane email#email.com 7 3 2

Joining table with min(amount) does not work

I have 3 tables, but data is only fetch from 2 tables.
I'm trying to get the lowest bids for selected items and display user name with the lowest bid.
Currently query works until when we display user name, it shows wrong user name, which does not match the bid.
Below is working example of structure and query.
SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE `bid` (
`id` int(11) NOT NULL,
`amount` float NOT NULL,
`user_id` int(11) NOT NULL,
`item_id` int(11) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=latin1;
INSERT INTO `bid` (`id`, `amount`, `user_id`, `item_id`) VALUES
(1, 9, 1, 1),
(2, 5, 2, 1),
(3, 4, 3, 1),
(4, 3, 4, 1),
(5, 4, 2, 2),
(6, 22, 5, 1);
-- --------------------------------------------------------
CREATE TABLE `item` (
`id` int(11) NOT NULL,
`name` varchar(100) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1;
INSERT INTO `item` (`id`, `name`) VALUES
(1, 'chair'),
(2, 'sofa'),
(3, 'table'),
(4, 'box');
-- --------------------------------------------------------
CREATE TABLE `user` (
`id` int(11) NOT NULL,
`name` varchar(100) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1;
INSERT INTO `user` (`id`, `name`) VALUES
(1, 'James'),
(2, 'Don'),
(3, 'Hipes'),
(4, 'Sam'),
(5, 'Zakam');
ALTER TABLE `bid`
ADD PRIMARY KEY (`id`);
ALTER TABLE `item`
ADD PRIMARY KEY (`id`);
ALTER TABLE `user`
ADD PRIMARY KEY (`id`);
ALTER TABLE `bid`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=7;
ALTER TABLE `item`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=5;
ALTER TABLE `user`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=5;
Query 1:
SELECT b.id, b.item_id, MIN(b.amount) as amount, b.user_id, p.name
FROM bid b
LEFT JOIN user p ON p.id = b.user_id
WHERE b.item_id in (1, 2)
GROUP BY b.item_id
ORDER BY b.amount, b.item_id
Results:
| id | item_id | amount | user_id | name |
|----|---------|--------|---------|-------|
| 5 | 2 | 4 | 2 | Don |
| 1 | 1 | 3 | 1 | James |
Explanation of query:
Get the selected items (1, 2).
get the lowest bid for thous items - MIN(b.amount)
display user names, who has given the bid - LEFT JOIN user p on p.id = b.user_id (this is not working or I'm doing something wrong)
[Note] I can't use sub-query, I'm doing this in doctrine2 (php code) which limits mysql sub-query
No, you are not necessarily fetching the user_id who has given the bid. You group by item_id, so you get one result row per item. So you are aggregating and for every column you say what value you want to see for that item. E.g.:
MIN(b.amount) - the minimum amount of the item's records
MAX(b.amount) - the maximum amount of the item's records
AVG(b.amount) - the avarage amount of the item's records
b.amount - one of the amounts of the item's records arbitrarily chosen (as there are many amounts and you don't specify which you want to see, the DBMS simply choses one of them)
This said, b.user_id isn't necessarily the user who made the lowest bid, but just one random user of the users who made a bid.
Instead find the minimum bids and join again with your bid table to access the realted records:
select bid.id, bid.item_id, bid.amount, user.id as user_id, user.name
from bid
join
(
select item_id, min(amount) as amount
from bid
group by item_id
) as min_bid on min_bid.item_id = bid.item_id and min_bid.amount = bid.amount
join user on user.id = bid.user_id
order by bid.amount, bid.item_id;
You can solve this using a subquery. I am not 100% sure if this is the most efficient way, but at least it works.
SELECT b1.id, b1.item_id, b1.amount, b1.user_id, p.name
FROM bid b1
LEFT JOIN user p ON p.id = b1.user_id
WHERE b1.id = (
SELECT b2.id
FROM bid b2
WHERE b2.item_id IN (1, 2)
ORDER BY b2.amount LIMIT 1
)
This first selects for the lowest bid with for item 1 or 2 and then uses the id of that bid to find the information you need.
Edit
You are saying that Doctrine does not support subqueries. I have not used Doctrine a lot, but something like this should work:
$subQueryBuilder = $entityManager->createQueryBuilder();
$subQuery = $subQueryBuilder
->select('b2.id')
->from('bid', 'b2')
->where('b2.item_id IN (:items)')
->orderBy('b2.amount')
->setMaxResults(1)
->getDql();
$queryBuilder = $entityManager->createQueryBuilder();
$query = $queryBuilder
->select('b1.id', 'b1.item_id', 'b1.amount', 'b1.user_id', 'p.name')
->from('bid', 'b1')
->leftJoin('user', 'p', 'with', 'p.id = b1.user_id')
->where('b1.id = (' . $subQuery . ')')
->setParameter('items', [1, 2])
->getQuery()->getSingleResult();

Function to find first available option based on count of records and condition

I need to write an SQL statement to get the first 'free' poule (pool / collection of teams) for my team. Let's explain a bit.
I have two tables, one table poules with 4 poules each having a TEAMQTY of 4 (the max. number of teams allowed in a poule):
ID TOURNID NAME TEAMQTY
1 1 Poule 1 4
2 1 Poule 2 4
3 1 Poule 3 4
4 1 Poule 4 4
and a table teams
ID TOURNID NAME POULEID
1 1 Team 1 1
2 1 Team 2 1
3 1 Team 3 1
4 1 Team 4 1
I want to write a function in mysql which based on the situation above suggest a pouleid of 2 since poule 1 is completely filled up with teams. IOW I should be able to insert 4 more teams in PouleId 2, after that my function should return PouleID 3 as a suggestion.
I'm new to mysql (an sql noob) and I've tried:
SELECT id FROM POULES WHERE TOURNID = 1 AND
teamqty > (SELECT COUNT(ID) FROM TEAMS WHERE TOURNID = 1) LIMIT 1
Needless to say my experiment sql code is useless..
Do I need a while loop here or would an SQL statement do?
Here's my supporting code:
CREATE TABLE IF NOT EXISTS `poules` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`TOURNID` int(11) NOT NULL,
`NAME` varchar(20) NOT NULL,
`TEAMQTY` int(11) NOT NULL,
PRIMARY KEY (`ID`),
KEY `TOURNID` (`TOURNID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=5 ;
INSERT INTO `poules` (`ID`, `TOURNID`, `NAME`, `TEAMQTY`) VALUES
(1, 1, '1', 4),
(2, 1, '2', 4),
(3, 1, '3', 4),
(4, 1, '4', 4);
CREATE TABLE IF NOT EXISTS `teams` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`TOURNID` int(11) NOT NULL,
`NAME` varchar(50) NOT NULL,
`POULEID` int(11) DEFAULT NULL,
PRIMARY KEY (`ID`),
UNIQUE KEY `NAME` (`NAME`),
KEY `TOURNID` (`TOURNID`))
ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=6 ;
INSERT INTO `teams` (`ID`, `TOURNID`, `NAME`, `POULEID`) VALUES
(1, 1, '1', 1),
(2, 1, '2', 1),
(3, 1, '3', 1),
(4, 1, '4', 1);
TIA Mike
you can do left join with a subquery that gets total team count and compares with team count in the main table
you can use limit to get the one result based on order by on team count.
select p.id as pouleid, ifnull(t.teamcount,0), p.tournid
from poules p
left join ( select count(pouleid) as teamcount, pouleid, tournid
from teams
group by pouleid, tournid
)t
on p.id = t.pouleid
and p.tournid = t.tournid
where ifnull(t.teamcount,0) < p.teamqty