Limit count in sql - mysql

I have a query that looks like the below
SELECT
venueid as VENUES, venue2.venue AS LOCATION,
(SELECT COUNT(*) FROM events WHERE (VENUES = venueid) AND eventdate < CURDATE()) AS number
FROM events
INNER JOIN venues as venue2 ON events.venueid=venue2.id
GROUP BY VENUES
ORDER BY number DESC
I want to limit the count to count the last 5 rows in the table (sorting by id) however when I add a limt 0,5 the results don't seem to change. When counting where do you add in the limit to limit the amount of rows that are being counted?
CREATE TABLE venues (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
venue VARCHAR(255)
) DEFAULT CHARACTER SET utf8 ENGINE=InnoDB;
CREATE TABLE categories (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
category VARCHAR(255)
) DEFAULT CHARACTER SET utf8 ENGINE=InnoDB;
CREATE TABLE events (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
eventdate DATE NOT NULL,
title VARCHAR(255),
venueid INT,
categoryid INT
) DEFAULT CHARACTER SET utf8 ENGINE=InnoDB;
INSERT INTO venues (id, venue) VALUES
(1, 'USA'),
(2, 'UK'),
(3, 'Japan');
INSERT INTO categories (id, category) VALUES
(1, 'Jazz'),
(2, 'Rock'),
(3, 'Pop');
INSERT INTO events (id, eventdate, title, venueid, categoryid) VALUES
(1,20121003,'Title number 1',1,3),
(2,20121010,'Title number 2',2,1),
(3,20121015,'Title number 3',3,2),
(4,20121020,'Title number 4',1,3),
(5,20121022,'Title number 5',2,1),
(6,20121025,'Title number 6',3,2),
(7,20121030,'Title number 7',1,3),
(8,20121130,'Title number 8',1,1),
(9,20121230,'Title number 9',1,2),
(10,20130130,'Title number 10',1,3);
The expected result should look like the below
|VENUES |LOCATION |NUMBER |
|1 | USA | 3 |
|2 | UK | 1 |
|3 | Japan | 1 |
As of the time of posting id 9,8,7,6,5 are the last 5 events before the current date.
See SQL Fiddle link below for full table details.
http://sqlfiddle.com/#!2/21ad85/32

This query gives you the five rows that you are trying to group and count:
SELECT *
FROM events
WHERE eventdate < CURDATE()
ORDER BY eventdate DESC
LIMIT 5
Now you can use this query as a subquery. You can join with the result of a subquery just as if it were an ordinary table:
SELECT
venueid as VENUES,
venue2.venue AS LOCATION,
COUNT(*) AS number
FROM
(
SELECT *
FROM events
WHERE eventdate < CURDATE()
ORDER BY eventdate DESC
LIMIT 5
) AS events
INNER JOIN venues as venue2 ON events.venueid=venue2.id
GROUP BY VENUES
ORDER BY number DESC
http://sqlfiddle.com/#!2/21ad85/37

Related

How to select last created record in a group by clause in mysql?

I am using mysql 8.0.23
I have three tables, chats, chat_users and chat_messages
I want to select the chat_id, the last message (with maximum createdAt date for a particular group. Said in other words, the message order by created_at desc within the group), from_user_id values for all the chats where user with id 1 is a member.
The tables sql and DDLs is are like below
create table chats
(
id int unsigned auto_increment primary key,
created_at timestamp default CURRENT_TIMESTAMP not null
);
create table if not exists chat_users
(
id int unsigned auto_increment
primary key,
chat_id int unsigned not null,
user_id int unsigned not null,
constraint chat_users_user_id_chat_id_unique
unique (user_id, chat_id),
constraint chat_users_chat_id_foreign
foreign key (chat_id) references chats (id)
);
create index chat_users_chat_id_index
on chat_users (chat_id);
create index chat_users_user_id_index
on chat_users (user_id);
create table chat_messages
(
id int unsigned auto_increment primary key,
chat_id int unsigned not null,
from_user_id int unsigned not null,
content varchar(500) collate utf8mb4_unicode_ci not null,
created_at timestamp default CURRENT_TIMESTAMP not null constraint chat_messages_chat_id_foreign
foreign key (chat_id) references chats (id),
);
create index chat_messages_chat_id_index
on chat_messages (chat_id);
create index chat_messages_from_user_id_index
on chat_messages (from_user_id);
The query that I tried so far and is not working properly is
SET #userId = 1;
select
c.id as chat_id,
content,
chm.from_user_id
from chat_users
inner join chats c on chat_users.chat_id = c.id
inner join chat_messages chm on c.id = chm.chat_id
where chat_users.user_id = #userId
group by c.id
order by c.id desc, max(chm.created_at) desc
My query above does not return the content field from the last created message, although I am trying to order by max(chm.created_at) desc. This order by after group by clause is executed after the grouping I think and not within the items from the group..
I know that I can probably select in the select statement the max date but I want to select last content value within the group not select max(ch.created_at) as last_created_at_msg_within_group
I don't know how to select the content field from the item that has the highest chm.created_at from within the group that I do by grouping with c.id
Example test data
chats
1 2021-07-23 20:51:01
2 2021-07-23 20:51:01
3 2021-07-23 20:51:01
chats_users
1 1 1
2 1 2
3 2 1
4 2 2
5 3 1
6 3 2
chat_messages
1 1 1 lastmsg 2021-07-28 21:50:31
1 1 2 themsg 2021-07-23 20:51:01
The logic in this case should return
chat_id content from_user_id
1 lastmsg 1
PS:
Before posting here I did my homework and studied similar questions in the forum, but they were trying to get last inserted row from a group and were not like mine.
Here's what I came up with, for a solution for MySQL 8.0 with window functions:
select * from (
select
c.id as chat_id,
content,
chm.from_user_id,
chm.created_at,
row_number() over (partition by c.id order by chm.created_at desc) as rownum
from chat_users
inner join chats c on chat_users.chat_id = c.id
inner join chat_messages chm on c.id = chm.chat_id
where chat_users.user_id = #userId
) as t
where rownum = 1;

Group by wether or not the cell exists in another table as well

My SQL syntax is MariaDB (MySQL)
I have a table with organisation spokepersons, and a table with VIP organizations, and a table with presentations. How do I group or sort by wether the spokeperson's organisation is VIP, so that VIP organisation spokepersons show up on top when retrieving all presentations?
Table presentations: int presentation_id, int person_id, varchar title, date date
Table persons: int person_id, varchar name, varchar function, varchar organisation
Table VIP_orgs: int org_id, varchar org_name
Query that doesn't work:
CREATE TABLE persons (
person_id INT AUTO_INCREMENT,
name VARCHAR(64),
organisation VARCHAR(64),
PRIMARY KEY (person_id)
);
INSERT INTO `persons` (name, organisation) VALUES
("Guy Fieri", "VIP-org"),
("Fiona", "VIP inc."),
("Mr. Robot", "Evil Corp"),
("Marcus Antonius", "Rome"),
("Cicero", "Rome"),
("Shrek", "VIP inc.");
CREATE TABLE presentations (
presentation_id INT AUTO_INCREMENT,
person_id INT,
PRIMARY KEY (presentation_id)
);
INSERT INTO `presentations` (person_id) VALUES
(1),(1),(1),(1), -- guy fieri has 4
(2),
(3),(3),(3),(3),(3),
(4),(4),(4),(4),
(5),(5),(5),
(6),(6),(6),(6);
CREATE TABLE VIP_orgs (
org_id INT AUTO_INCREMENT,
org_name VARCHAR(64),
PRIMARY KEY (org_id)
);
INSERT INTO `VIP_orgs` (org_name) VALUES
("VIP-org"),
("VIP inc.");
SELECT organisation, COUNT(*) AS count
FROM `presentations`
JOIN `persons` ON `presentations`.person_id = `persons`.person_id
GROUP BY (SELECT org_name FROM `VIP_orgs` WHERE `VIP_orgs`.org_name = organisation), organisation
ORDER BY count DESC;
What I expect it to do:
return a table org_name, (total combined number of presentations by each spokeperson of that org)
Sorted by count of presentations, grouped by organisation, VIP organisations grouped on top.
The VIP and non-VIP parts should be sorted by count independently. The returned table should thus look something like this:
name count
VIP inc. 5
VIP-org 4
Rome 7
Evil Corp 5
The query works 50%: it counts all presentations and sorts it, but it doesn't seem to group by VIP organizations. In actuality the returned table looks like this:
name count
Rome 7
VIP inc. 5
Evil Corp 5
VIP-org 4
The schema doesn't look right. I would suggest creating an organisations table with a vip BOOLEAN column and add foreign key in persons table. Make the following changes in the schema:
CREATE TABLE `organisations` (
organisation_id INT AUTO_INCREMENT,
name VARCHAR(64),
vip BOOLEAN,
PRIMARY KEY (organisation_id)
);
INSERT INTO `organisations` (name, vip) VALUES
("VIP-org", True),
("VIP inc.", True),
("Evil Corp", False),
("Rome", False);
CREATE TABLE persons (
person_id INT AUTO_INCREMENT,
name VARCHAR(64),
organisation_id INT,
PRIMARY KEY (person_id),
FOREIGN KEY (organisation_id) REFERENCES `organisations`(organisation_id)
);
INSERT INTO `persons` (name, organisation_id) VALUES
("Guy Fieri", 1),
("Fiona", 2),
("Mr. Robot", 3),
("Marcus Antonius", 4),
("Cicero", 4),
("Shrek", 2);
Now the query would look something like this:
SELECT `organisations`.name as organisation, COUNT(*) AS count
FROM `presentations`
JOIN `persons` ON `presentations`.person_id = `persons`.person_id
JOIN `organisations` ON `organisations`.organisation_id = `persons`.organisation_id
GROUP BY `organisations`.organisation_id
ORDER BY `organisations`.vip DESC, count DESC;
Output:
+--------------+------------+
| organisation | count |
+--------------+------------+
| VIP inc. | 5 |
| VIP-org | 4 |
| Rome | 7 |
| Evil Corp | 5 |
+--------------+------------+
You can see the result here: db <> fiddle
Instead of grouping by, I needed to sort. DOh!
Edit: this doesn't quite work. It does not sort by count. If I put the ORDER BY count clause first, it puts all vip orgs on the bottom.
Edit 2: using EXISTS, it seems to work
SELECT organisation, COUNT(*) AS count
FROM `presentations`
JOIN `persons` ON `presentations`.person_id = `persons`.person_id
GROUP BY organisation
ORDER BY EXISTS (SELECT org_name FROM `VIP_orgs` WHERE `VIP_orgs`.org_name = organisation) DESC, count DESC;

MySQL - Find MAX of grouped SUM (without LIMIT)

I would like to get the user_id and the sum of amount for the users who have largest summed amount. I cannot use LIMIT because that will return only 1 record (summed amount may be same for multiple users)
Here is my data schema and some records
CREATE TABLE transactions (
id BIGINT(20) NOT NULL AUTO_INCREMENT,
user_id BIGINT(20) NOT NULL,
amount FLOAT NOT NULL, PRIMARY KEY (id)
);
INSERT INTO transactions (user_id, amount) VALUES
(1, 1000),
(1, 1000),
(1, 1000),
(2, 2000),
(2, 1000),
(3, 1000);
Here are the expected result.
+---------+------+
| user_id | sum |
+---------+------+
| 1 | 3000 |
| 2 | 3000 |
+---------+------+
I can get the above result by using the following sql. However, I don't know is there any better approach or not. Is it necessary to repeat the same subquery twice? Thanks.
SELECT T1.user_id, T1.sum
FROM (
SELECT user_id, SUM(amount) as sum
FROM transactions
GROUP BY user_id
) T1
WHERE T1.sum = (
SELECT MAX(T2.sum)
FROM (
SELECT user_id, SUM(amount) as sum
FROM transactions
GROUP BY user_id
) T2
)
GROUP BY T1.user_id;
Well you can simplify your query to
SELECT user_id, SUM(amount) as sum
FROM transactions
GROUP BY user_id
HAVING SUM(amount) = (
SELECT SUM(amount) as sum
FROM transactions
GROUP BY user_id
ORDER BY SUM(amount) DESC
LIMIT 1
)

SELECT newest record of any GROUP of records (ignoring records with one record)

Having trouble with a query to return the newest order of any grouped set of orders having more than 1 order. CREATE & INSERTs for the test data are below.
This query returns the unique customer id's I want to work with, along with the grouped order_id's. Of these records, I only need the most recent order (based on date_added).
SELECT COUNT(customer_id), customer_id, GROUP_CONCAT(order_id) FROM orderTable GROUP BY customer_id HAVING COUNT(customer_id)>1 LIMIT 10;
mysql> SELECT COUNT(customer_id), customer_id, GROUP_CONCAT(order_id) FROM orderTable GROUP BY customer_id HAVING COUNT(customer_id)>1 LIMIT 10;
+--------------------+-------------+------------------------+
| COUNT(customer_id) | customer_id | GROUP_CONCAT(order_id) |
+--------------------+-------------+------------------------+
| 2 | 0487 | F9,Z33 |
| 3 | 1234 | 3A,5A,88B |
+--------------------+-------------+------------------------+
2 rows in set (0.00 sec)
I'm looking for order Z33 (customer_id 0487) and 3A (customer_id 1234).
For clarification, I do not want orders for customers that have only ordered once.
Any help or tips to get me pointed in the right direction appreciated.
Sample table data:
--
-- Table structure for table orderTable
CREATE TABLE IF NOT EXISTS orderTable (
customer_id varchar(10) NOT NULL,
order_id varchar(4) NOT NULL,
date_added date NOT NULL,
PRIMARY KEY (customer_id,order_id)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Dumping data for table orderTable
INSERT INTO orderTable (customer_id, order_id, date_added) VALUES
('1234', '5A', '1997-01-22'),
('1234', '88B', '1992-05-09'),
('0487', 'F9', '2002-01-23'),
('5799', 'A12F', '2007-01-23'),
('1234', '3A', '2009-01-22'),
('3333', '7FHS', '2009-01-22'),
('0487', 'Z33', '2004-06-23');
==========================================================
Clarification of the query.
The question was to only include those customers that had more... hence my query has it INSIDE with the GROUP BY... This way it ONLY GIVES the customer in question that HAD multiple orders, but at the same time, only gives the most recent date OF the last order for the person... Then the PreQuery is re-joined to the orders table by the common customer ID, but only for the order that matches the last date as detected in the prequery. If a customer only had a single order, its inner PreQuery count would have only been 1 and thus excluded from the final PreQuery result set.
select ot.*
from
( select
customer_id,
max( date_added ) as LastOrderDate,
from
orderTable
having
count(*) > 1
group by
customer_id ) PreQuery
join orderTable ot
on PreQuery.Customer_ID = ot.Customer_ID
and PreQuery.LastOrderDate = ot.date_added

Retrieving Nth subquery for INSERT

Abstract
From a table holding various posts of users to a forum, another table shall be daily updated with the top 20 posters. Posts are stored in posts, daily high-scores are held in hiscore.
Tables
posts:
post_id(PK:INT) | user_id(INT) | ... | timestamp(TIMESTAMP)
hiscore:
user_id(INT) | rank(INT)
Query
TRUNCATE TABLE `hiscore` ;
INSERT INTO `hiscore` (`user_id`,`rank`)
(
SELECT `user_id`, ???
FROM `posts`
WHERE `timestamp` BETWEEN blah AND blah
GROUP BY `user_id`
ORDER BY COUNT(`post_id`) DESC
LIMIT 20
)
The actual question
What is to be inserted in the above query instead of ??? to account for the rank?
Is there a variable like #NTH_SUBQUERY that'll substitute for 5 on the fifth run of the SELECT subquery?
UPDATE: The table hiscore is supposed to only hold the top 20 posters. I know the table structure can be optimized. The focus of the answers should be on how to determine the current retrieved row of the sub-query.
INSERT INTO `hiscore` (`user_id`,`rank`)
(
SELECT `user_id`, #rank = #rank + 1
FROM `posts`, (SELECT #rank := 0) r
WHERE `timestamp` BETWEEN blah AND blah
GROUP BY `user_id`
ORDER BY COUNT(`post_id`) DESC
LIMIT 20
)
You seems too fancy on truncate, for you cases
hiscore:
the_date (DATE) | user_id(INT) | rank(INT)
and built a key on the_date, rank
insertion
set #pos=0;
insert into hiscore
select cur_date(), user_id, #pos:=#pos+1
from ...
to keep the table size manageable, you probably can delete once in few months
Or you can set an auto_increment on rank
create table hiscore
(
the_date date not null,
rank int(3) not null auto_increment,
user_id int(10) not null,
primary key (the_date, rank)
);
So, the rank is auto incremented (which is the same as order by number of daily posts descending)