Optimize SQL query on two tables - mysql

I have an SQL query on tables having a lot of rows. So this query runs for a very long time. How can I optimize this query?
These tables already have indexes on id and friend_id
SELECT u.id, u.first, u.last,
group_concat(u2.first, " " , u2.last) MyFriends
FROM Users u
INNER JOIN Friends f ON f.user_id = u.id
INNER JOIN Users u2 ON u2.id = f.friend_id
GROUP BY u.id;
These are the table structures:
CREATE TABLE Users (
id int(10) unsigned NOT NULL,
first varchar(50) DEFAULT NULL,
last varchar(50) DEFAULT NULL,
city varchar(20) DEFAULT NULL,
country varchar(20) DEFAULT NULL,
Age tinyint(3) unsigned NOT NULL,
KEY users_idx_id (id))
ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE Friends (
user_id int(10) unsigned NOT NULL,
friend_id int(10) unsigned NOT NULL,
KEY idx_friends (friend_id))
ENGINE=InnoDB DEFAULT CHARSET=latin1;

A many-to-many mapping table (Friends) needs improved indexes. Drop all the indexes you have now and add
PRIMARY KEY(user_id, friend_id),
INDEX(friend_id, user_id)
More discussion: http://mysql.rjweb.org/doc.php/index_cookbook_mysql#many_to_many_mapping_table
Age is a moving target. Think about a better way to store that.
There are about 6 countries with names longer than 20. "Saint Vincent and the Grenadines" is 32.
As for cities, 'Poselok Uchebnogo Khozyaystva Srednego Professionalno-Tekhnicheskoye Uchilishche Nomer Odin' is 91 chars.
ALTER TABLE Friends
DROP INDEX idx_friends,
ADD PRIMARY KEY(user_id, friend_id),
ADD INDEX(friend_id, user_id);
Every table should have a PRIMARY KEY:
ALTER TABLE Users
DROP INDEX users_idx_id,
ADD PRIMARY KEY(user_id)
Read about AUTO_INCREMENT.
The "execution plan" can be had by running EXPLAIN SELECT .... However it won't provide many clues in this case.

Related

Why this simple SELECT is taking 6 seconds when user has many comments?

I have this select to show users some notifications when someone comments in one post.
I noticed that users that has posts with many comments it can take 6 seconds +.
select 'comments' prefix, c.foto, c.data as data, c.user,
concat(k.user, ' comments your post') as logs
from comments c
inner join posts p on c.foto = p.id
inner join cadastro k on c.user = k.id
where p.user = 1 and c.user <> 1 and c.delete = 0
order by c.data desc
limit 5
I'd like to show users notifications, someone comments your post, to do so, I used inned join on posts (to know if the comment is from user '1') and inner join cadastro (to get user nick name - user who comments user 1 post).
checking on where if user is 1, c.user <> 1 (not show his own comments notifications) and c.delete (comment not deleted).
my tables:
`posts` (
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`user` int(11) UNSIGNED NOT NULL,
`foto` varchar(400),
`data` datetime NOT NULL,
`delete` tinyint(1) NOT NULL DEFAULT '0',
FOREIGN KEY (`user`) REFERENCES cadastro (`id`),
PRIMARY KEY (`id`)
)
`comments` (
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`foto` int(11) UNSIGNED NOT NULL,
`user` int(11) UNSIGNED NOT NULL,
`texto` varchar(3000) NOT NULL,
`data` datetime NOT NULL,
`delete` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `foto_delete` (foto, `delete`),
FOREIGN KEY (`foto`) REFERENCES posts (`id`) ON DELETE CASCADE
)
any ideas why it is taking so long when an user has about 200.000 comments? (if user has 1000, it is fast).
Without indexes, to run your Query the engine usually scans all rows looking for the required values in the ON, WHERE, as well the ORDER BY clause.
A simple thing you can do is to create the indexes:
CREATE INDEX cadastro_id ON cadastro(id);
CREATE INDEX posts_id ON posts(id);
CREATE INDEX posts_user ON posts(user);
CREATE INDEX comments_foto ON comments(foto);
CREATE INDEX comments_user ON comments(user);
CREATE INDEX comments_delete ON comments(delete);
CREATE INDEX comments_data ON comments(data);
Measure the current time it takes, then apply these Indexes and measure again, and tell here.
See also:
https://dev.mysql.com/doc/refman/5.7/en/create-index.html
https://dev.mysql.com/doc/refman/5.7/en/order-by-optimization.html

Can I improve my movie selecting SQL query

I've created a database to store movies data. My tables are the following:
movies:
CREATE TABLE IF NOT EXISTS `movies` (
`movieId` int(11) NOT NULL AUTO_INCREMENT,
`imdbId` varchar(255) DEFAULT NULL,
`imdbRating` float DEFAULT NULL,
`movieTitle` varchar(255) NOT NULL,
`movieLength` varchar(255) NOT NULL,
`imdbRatingCount` varchar(255) NOT NULL,
`poster` varchar(255) NOT NULL,
`year` varchar(255) NOT NULL,
PRIMARY KEY (`movieId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
I have a table in which i store movie actors:
CREATE TABLE IF NOT EXISTS `actors` (
`actorId` int(10) NOT NULL AUTO_INCREMENT,
`actorName` varchar(255) NOT NULL,
PRIMARY KEY (`actorId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
And one other in which i store the relation between the movies and actors: (movieActor)
CREATE TABLE IF NOT EXISTS `movieActor` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`movieId` int(10) NOT NULL,
`actorId` int(10) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Now when i want to select a list of movies in which are the selected actors my query is:
SELECT *
FROM movies m inner join
(SELECT movieId FROM movieActor WHERE actorId IN(1,2,3) GROUP BY movieId having count(*) = 3) ma ON m.movieId = ma.movieId
WHERE imdbRating IS NOT NULL ORDER BY imdbRating DESC
This is working perfectly, but i don't know that this is the optimal table structure and query to accomplish this. Are there any better table structure to store data or query the list?
First of all, use indexes on your tables. In my opinion it should be useful to have 3 indexes on movieActor. MovieId - ActorID - MovieIdActorId.
Second try tu use foreign keys. These help to identify the best execution plan for your dbs.
Third try to avoid generating temp tables in your execution plan of your query. Subselects often creates temp tables which are used when the database has to temporarily save something in the RAM. To check this, write EXPLAIN in front of goer query.
I would write it like this:
SELECT m.*, movieActor
FROM movies m inner join
movieActor ma ON m.movieId = ma.movieId
WHERE imdbRating IS NOT NULL
and actorId IN(1,2,3)
GROUP BY movieId
having count(*) = 3)
ORDER BY imdbRating DESC
(Not tested)
Just try to optimize it with the EXPLAIN keyword. It also can help you to create the right indexes.

MYSQL Select One Row With Less

I'm making a database for a unit and I need a query that selects the vet with less appointments assigned so I can assign the next appointment to him or her. I don't know how to start, but I'm pretty sure I'll have to use variables here. Those are my tables:
CREATE TABLE IF NOT EXISTS staff (
stafId MEDIUMINT UNSIGNED AUTO_INCREMENT,
stafAdd VARCHAR(150) NOT NULL,
stafConNum VARCHAR(15) NOT NULL,
stafEma VARCHAR(40) NOT NULL,
stafFirNam VARCHAR(20) NOT NULL,
stafLasNam VARCHAR(30) NOT NULL,
stafPos ENUM('nurse', 'vet') NOT NULL,
PRIMARY KEY (stafId)
) engine = InnoDB;
CREATE TABLE IF NOT EXISTS vet (
vetId MEDIUMINT UNSIGNED AUTO_INCREMENT,
FOREIGN KEY (vetId) REFERENCES staff(stafId),
PRIMARY KEY (vetId)
) engine = InnoDB;
CREATE TABLE IF NOT EXISTS appointment (
appoId MEDIUMINT UNSIGNED AUTO_INCREMENT,
appoDat DATETIME NOT NULL,
appoPetId MEDIUMINT UNSIGNED,
FOREIGN KEY (appoPetId) REFERENCES pet(petId),
appoVetId MEDIUMINT UNSIGNED,
FOREIGN KEY (appoVetId) REFERENCES vet(vetId),
PRIMARY KEY (appoId)
) engine = InnoDB;
You should start by looking up the mysql MIN() function. Follow that up with learning about JOINs and you'll be breezing through this.
You could get the vet's with the number of appointment like this:
SELECT
*
FROM
(
SELECT
vet.vetId,
COUNT(*) AS nbrOfAppointment
FROM
vet
JOIN appointment
ON vet.vetId = appointment.appoVetId
) AS tbl
ORDER BY tbl.nbrOfAppointment ASC
This Request gives you the number of appointement per vet ordered by number of appointement.
Select vet.id, count(*) as nb_appointement
from vet
inner join appointement app on vet.vetId = app.appoVetId
group by vet.id
order by nb_appointement asc

SQL select entries in other table linked by foreign keys

I have redesigned my database structure to use PRIMARY and FOREIGN KEYs to link the entries in my 3 tables together, and I am having problems trying to write queries to select data in one table given data in a another table. Here is an example of my 3 CREATE TABLE statements:
CREATE TABLE IF NOT EXISTS players (
id INT(10) NOT NULL AUTO_INCREMENT,
username VARCHAR(16) NOT NULL,
uuid VARCHAR(200) NOT NULL DEFAULT 0,
joined TIMESTAMP DEFAULT 0,
last_seen TIMESTAMP DEFAULT 0,
PRIMARY KEY (id)
);
/* ^
One |
To
| One
v
*/
CREATE TABLE IF NOT EXISTS accounts (
id INT(10) NOT NULL AUTO_INCREMENT,
account_id INT(10) NOT NULL,
pass_hash VARCHAR(200) NOT NULL,
pass_salt VARCHAR(200) NOT NULL,
created BIGINT DEFAULT 0,
last_log_on BIGINT DEFAULT 0,
PRIMARY KEY (id),
FOREIGN KEY (account_id) REFERENCES players(id) ON DELETE CASCADE
) ENGINE=InnoDB;
/* ^
One |
To
| Many
v
*/
CREATE TABLE IF NOT EXISTS purchases (
id INT(10) NOT NULL AUTO_INCREMENT,
account_id INT(10) NOT NULL,
status VARCHAR(20) NOT NULL,
item INT NOT NULL,
price DOUBLE DEFAULT 0,
description VARCHAR(200) NOT NULL,
buyer_name VARCHAR(200) NOT NULL,
buyer_email VARCHAR(200) NOT NULL,
transaction_id VARCHAR(200) NOT NULL,
payment_type VARCHAR(20) NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (account_id) REFERENCES accounts(account_id) ON DELETE CASCADE
) ENGINE=InnoDB;
Say for example, I want to select all the usernames of users who purchased anything greater than $30. All the usernames are stored in the players table, which is linked to the accounts table and that is linked to the purchases table. Is this this the best way to design this relational database? If so, how would I run queries similar to the above example?
I was able to get get all of a users purchase history given their username, but I did it with 2 sub-queries... Getting that data should be easier than that!
Here is the SELECT query I ran to get all of a players purchase data:
SELECT *
FROM purchases
WHERE account_id = (SELECT id FROM accounts WHERE account_id = (SELECT id FROM players WHERE username = 'username'));
Also, when I try to make references to the other tables using something like 'players.username', I get an error saying that the column doesn't exist...
I appreciate any help! Thanks!
Your design is ok in my opinion. The relation between players and account is one-to-many and not one-to-one since this way, you can have two tuples referencing a single player.
I would write the query you need as:
SELECT DISTINCT p.id, p.username
FROM players p INNER JOIN accounts a ON (p.id = a.account_id)
INNER JOIN purchases pc ON (a.id = pc.account_id)
WHERE (pc.price > 30);
As Sam suggested, I added DISTINCT to avoid repeating id and username in case a user have multiple purchases.
Note the id is here to avoid confusion among repeated usernames.

Join table on already joined table

I'm struggling to put together a select statement that joins 3 tables.
Here's the database:
CREATE TABLE Beerstyles
(
style_id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
style_name VARCHAR(50) NOT NULL
)ENGINE=InnoDB;
CREATE TABLE Breweries
(
brew_id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
booth_num VARCHAR(10) NOT NULL,
brew_name VARCHAR(50) NOT NULL
)ENGINE=InnoDB;
CREATE TABLE Beers
(
beer_id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
beer_name VARCHAR(50) NOT NULL,
alc_vol DECIMAL(2,1) NOT NULL,
fk_style_id INT NOT NULL,
fk_brew_id INT NOT NULL
)ENGINE=InnoDB;
CREATE TABLE Favorites
(
fav_id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
user_id VARCHAR(55) NOT NULL,
fk_beer_id INT NOT NULL,
fav_comment VARCHAR(255)
)ENGINE=InnoDB;
ALTER TABLE Beers ADD CONSTRAINT FK_BeerStyle_Style FOREIGN KEY (fk_style_id) REFERENCES Beerstyles (style_id);
ALTER TABLE Beers ADD CONSTRAINT FK_BeerBrew_Brew FOREIGN KEY (fk_brew_id) REFERENCES Breweries (brew_id);
ALTER TABLE Favorites ADD CONSTRAINT FK_FavBeer_Beer FOREIGN KEY (fk_beer_id) REFERENCES Beers (beer_id);
And here's the first part:
SELECT * FROM Favorites JOIN Beers ON Favorites.fk_beer_id = Beers.beer_id
I need to mix in the brew_name, but haven't been able to get it right. When I try to join Breweries (ON Favorites.fk_brew_id = Breweries.brew_id) i get an error saying "Unknown column 'Favorites.fk_brew_id' in 'on clause'"
Hope you guys can help me out :)
There is no fk_brew_id in the Favorite table, but you have to add the JOIN condition to the Beers table not to the Favorite table like this:
SELECT
bw.brew_name,
b.beer_name,
f.fav_comment,
...
FROM Favorites AS f
INNER JOIN Beers AS b ON f.fk_beer_id = b.beer_id
INNER JOIN Breweries AS bw ON b.fk_brew_id = bw.brew_id;
SELECT *
FROM Favorites
JOIN Beers
ON Favorites.fk_beer_id = Beers.beer_id
JOIN Breweries
ON Beers.fk_brew_id = Breweries.brew_id
Of course is real life you shoudl never use select *. When you have a join you are repeating columns and causing your query to be slower.