MySQL indexing to efficiently query table join with many to many relationship - mysql

I've been struggling to create the correct index to improve performance for a specific query we run on our database, I hope one of you can help point me in the right direction.
I have 2 tables as per below. I have a query to find all employees of companies that belong to a specific category with first_name like "Be%" for example. I've tried creating a multiple index on category_id, company_id but this hasn't helped. What should be the correct way to index my tables to achieve better performance for this query? Thank you in advance.
SELECT
e.*
FROM employee e
INNER JOIN company c ON e.company_id = c.company_id
WHERE c.category_id = 6
AND e.first_name LIKE "Be%"
GROUP BY e.employee_id
TABLE company
| company_id | category_id |
+------------+-------------+
| ... | ... |
+------------+-------------+
| 47 | 6 |
+------------+-------------+
| .. | ... |
+------------+-------------+
| 252 | 6 |
+------------+-------------+
TABLE employee
| employee_id | company_id | first_name | ... |
+-------------+------------+------------+-----+
| 2582250 | 47 | Ben | ... |
+-------------+------------+------------+-----+
| 3447890 | 252 | Ryan | ... |
+-------------+------------+------------+-----+
| 7125966 | 252 | Beth | ... |
+-------------+------------+------------+-----+
| ... | ... | ... | ... |
+-------------+------------+------------+-----+
CREATE TABLES below and sqlfiddle.
CREATE TABLE company (
`company_id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`category_id` INT UNSIGNED NOT NULL DEFAULT 0
) ENGINE=InnoDB;
CREATE TABLE employee (
`employee_id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`company_id` INT UNSIGNED NOT NULL,
`first_name` VARCHAR(255)
) ENGINE=InnoDB;

You must have indexes in many:many tables. Follow the advice outlined here.
WHERE c.category_id = 6
AND e.first_name LIKE "Be%"
needs
c: INDEX(category_id, company_id) -- which will be called for in the link above
e: INDEX(first_name, employee_id)
The Optimizer will either start with c, then reach into e. Or vice versa. I am providing you the optimal indexes for either direction.

Related

Combine two tables with same Primary key

This seems like a simple question but I can't seem to find an answer. I have two tables. Table 1:
+---------+-----------+--------+-------+----------+
| userid | username | date | time | footsize |
+---------+-----------+--------+-------+----------+
| 1 | user1 | 103999 | 1010 | 9 |
| 2 | user2 | 484883 | 984 | 6 |
+---------+-----------+--------+-------+----------+
and Table 2:
+---------+-----------+----------+
| userid | natural | synthetic|
+---------+-----------+----------+
| 1 | y | n |
| 2 | n | y |
+---------+-----------+----------+
What I'd like to do is delete table 2.
But I need to move the columns and data natural and synthetic from table 2 and insert them into table 1, using userid as a primary key to make sure the data goes to the right customer.
I tried using the join statements but I can't seem to move them from joining to inserting without an error.
The general (loose) idea I want is
select userid from table1, select * from table2.
Insert into table1, table2.natural, table2.synthetic where table1.userid = table2.userid;
So that table 1 looks like this:
+---------+-----------+--------+-------+----------+-----------+----------+
| userid | username | date | time | footsize | natural | synthetic|
+---------+-----------+--------+-------+----------+-----------+----------+
| 1 | user1 | 103999 | 1010 | 9 | y | n |
| 2 | user2 | 484883 | 984 | 6 | n | y |
+---------+-----------+--------+-------+----------+-----------+----------+
I'm aware that's not a real query, but it should clarify what I'm trying to do. Thanks!
first you create a table that has all the columns
CREATE TABLE `table3` (
`userid` INT NOT NULL AUTO_INCREMENT,
`username` VARCHAR(45) NULL,
`date` INT NULL,
`time` INT NULL,
`footsize` INT NULL,
`natural` VARCHAR(45) NULL,
`synthetic` VARCHAR(45) NULL,
PRIMARY KEY (`userid`));
insert into table3
select t1.userid, t1.username, t1.`date`, t1.`time`, t1.footsize,
t2.natural, t2.synthetic
from table1 as t1
join table2 as t2
on t1.userid = t2.userid

Mysql - Select, store in var, select in same query

I am trying to understand how to do this. Not much help found.
I have two tables.
First table:
CREATE TABLE `first` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`parent_id` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
)
Second table:
CREATE TABLE `second` (
`id` int(11) NOT NULL,
`foo` varchar(3) NOT NULL ,
`bar` varchar(255) NOT NULL )
Id in both tables is same value, they are related that way.
I have to fetch some entries, for example with parent_id = '0' and to check if those entries have children. If they has I should get their ids too.
It could be done easily with little php and two queries but I would like to do it better. I tried something like:
SELECT `first`.*, `second`.*, children.*
FROM `first`
JOIN `second` ON `second`.id = `first`.id
INNER JOIN( SELECT
// Something
)AS children ON children.id = `second`.id
WHERE `first`.parent_id = '0'
GROUP BY `first`.id, `second`.la
But I did not make it.
How could I Select those result and their children in same query? Thanks!
EDIT
First:
-------------------------------------------------
| id | parent_id |
--------------------------------------------------
| 1 | 0 |
--------------------------------------------------
| 2 | 1 |
--------------------------------------------------
| 3 | 2 |
--------------------------------------------------
| 4 | 1 |
--------------------------------------------------
Second table:
-------------------------------------------------
| id | foo | name
--------------------------------------------------
| 1 | asd | apple
--------------------------------------------------
| 2 | asd | banana
--------------------------------------------------
| 3 | gsf | orange
--------------------------------------------------
| 4 | gre | potato
--------------------------------------------------
Desired Result would be to get row where id is 1 (joined both tables) and all ids of rows witch have parent_id 1.
Thanks!

Need help in merging three tables foreign keys

I have created three tables like this,
1.
CREATE TABLE person (
id int NOT NULL AUTO_INCREMENT,
name varchar(50) NOT NULL,
age int,
PRIMARY KEY (id)
);
2.
CREATE TABLE address (
id int NOT NULL AUTO_INCREMENT,
city varchar(50) NOT NULL,
post_code int NOT NULL,
person_id int NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (person_id) REFERENCES person(id)
);
3
CREATE TABLE subjects (
id int NOT NULL AUTO_INCREMENT,
subjects_s varchar(50) NOT NULL,
address_id int NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (address_id) REFERENCES address(id)
);
Now in tables i have some informations like this:
person
+----+--------+------+
| id | name | age |
+----+--------+------+
| 1 | Sohan | 17 |
| 2 | Farhan | 18 |
+----+--------+------+
address
+----+-------+-----------+-----------+
| id | city | post_code | person_id |
+----+-------+-----------+-----------+
| 1 | Tongi | 1711 | 1 |
| 2 | Dhaka | 1230 | 2 |
+----+-------+-----------+-----------+
subjects
+----+--------------------+------------+
| id | subjects_s | address_id |
+----+--------------------+------------+
| 1 | Accounting Finance | 1 |
| 2 | Physics Math | 2 |
+----+--------------------+------------+
Now I want to show all these data together. How can I do this? Please help!
You should be able to use a SQL join statement to combine these.
The syntax is detailed in the MySQL join documentation.
With your tables, your query should look something like this:
SELECT person.*, address.*, subjects.*
FROM person
JOIN address ON person.id = address.person_id
JOIN subjects ON address.id = subjects.address_id
Keep in mind, this example uses an inner join, which may not be the right type of join depending on the data in your tables. I'd recommend reading the documentation I linked above for further guidance.

Subquery for faster result

I have this query which takes me more than 117 seconds on a mysql database.
select users.*, users_oauth.* FROM users LEFT JOIN users_oauth ON users.user_id = users_oauth.oauth_user_id WHERE (
(MATCH (user_email) AGAINST ('sometext')) OR
(MATCH (user_firstname) AGAINST ('sometext')) OR
(MATCH (user_lastname) AGAINST ('sometext')) )
ORDER BY user_date_accountcreated DESC LIMIT 1400, 50
How can I use a subquery in order to optimize it ?
The 3 fields are fulltext :
ALTER TABLE `users` ADD FULLTEXT KEY `email_fulltext` (`user_email`);
ALTER TABLE `users` ADD FULLTEXT KEY `firstname_fulltext` (`user_firstname`);
ALTER TABLE `users` ADD FULLTEXT KEY `lastname_fulltext` (`user_lastname`);
There is only one search input in a website to search in different table users fields.
If the limit is for example LIMIT 0,50, the query will run in less than 3 seconds but when the LIMIT increase the query becomes very slow.
Thanks.
Use a single FULLTEXT index:
FULLTEXT(user_email, user_firstname, user_lastname)
And change the 3 matches to just one:
MATCH (user_email, user_firstname, user_lastname) AGAINST ('sometext')
Here's another issue: ORDER BY ... DESC LIMIT 1400, 50. Read about the evils of pagination via OFFSET . That has a workaround, but I doubt if it would apply to your statement.
Do you really have thousands of users matching the text? Does someone (other than a search engine robot) really page through 29 pages? Think about whether it makes sense to really have such a long-winded UI.
And a 3rd issue. Consider "lazy eval". That is, find the user ids first, then join back to users and users_oauth to get the rest of the columns. It would be a single SELECT with the MATCH in a derived table, then JOIN to the two tables. If the ORDER BY an LIMIT can be in the derived table, it could be a big win.
Please indicate which table each column belongs to -- my last paragraph is imprecise because of not knowing about the date column.
Update
In your second attempt, you added OR, which greatly slows things down. Let's turn that into a UNION to try to avoid the new slowdown. First let's debug the UNION:
( SELECT * -- no mention of oauth columns
FROM users -- No JOIN
WHERE users.user_id LIKE ...
ORDER BY user_id DESC
LIMIT 0, 50
)
UNION ALL
( SELECT * -- no mention of oauth columns
FROM users
WHERE MATCH ...
ORDER BY user_id DESC
LIMIT 0, 50
)
Test it by timing each SELECT separately. If one of the is still slow, then let's focus on it. Then test the UNION. (This is a case where using the mysql commandline tool may be more convenient than PHP.)
By splitting, each SELECT can use an optimal index. The UNION has some overhead, but possibly less than the inefficiency of OR.
Now let's fold in users_oauth.
First, you seem to be missing a very important INDEX(oauth_user_id). Add that!
Now let's put them together.
SELECT u.*
FROM ( .... the entire union query ... ) AS u
LEFT JOIN users_oauth ON users.user_id = users_oauth.oauth_user_id
ORDER BY user_id DESC -- yes, repeat
LIMIT 0, 50 -- yes, repeat
Yes #Rick
I changed the index fulltext to:
ALTER TABLE `users`
ADD FULLTEXT KEY `fulltext_adminsearch` (`user_email`,`user_firstname`,`user_lastname`);
And now there is some php conditions, $_POST['search'] can be empty:
if(!isset($_POST['search'])) {
$searchId = '%' ;
} else {
$searchId = $_POST['search'] ;
}
$searchMatch = '+'.str_replace(' ', ' +', $_POST['search']);
$sqlSearch = $dataBase->prepare(
'SELECT users.*, users_oauth.*
FROM users
LEFT JOIN users_oauth ON users.user_id = users_oauth.oauth_user_id
WHERE ( users.user_id LIKE :id OR
(MATCH (user_email, user_firstname, user_lastname)
AGAINST (:match IN BOOLEAN MODE)) )
ORDER BY user_id DESC LIMIT 0,50') ;
$sqlSearch->execute(array('id' => $searchId,
'match' => $searchMatch )) ;
The users_oauth table has a column with user_id:
Table users:
+--------------------------+-----------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------------------+-----------------+------+-----+---------+----------------+
| user_id | int(8) unsigned | NO | PRI | NULL | auto_increment |
| user_activation_key | varchar(40) | YES | | NULL | |
| user_email | varchar(40) | NO | UNI | | |
| user_login | varchar(30) | YES | | NULL | |
| user_password | varchar(40) | YES | | NULL | |
| user_firstname | varchar(30) | YES | | NULL | |
| user_lastname | varchar(50) | YES | | NULL | |
| user_lang | varchar(2) | NO | | en
+--------------------------+-----------------+------+-----+---------+----------------+
Table users_oauth:
+----------------------+-----------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------------+-----------------+------+-----+---------+----------------+
| oauth_id | int(8) unsigned | NO | PRI | NULL | auto_increment |
| oauth_user_id | int(8) unsigned | NO | | NULL | |
| oauth_google_id | varchar(30) | YES | UNI | NULL | |
| oauth_facebook_id | varchar(30) | YES | UNI | NULL | |
| oauth_windowslive_id | varchar(30) | YES | UNI | NULL | |
+----------------------+-----------------+------+-----+---------+----------------+
The Left Join is long, the request takes 3 seconds with, 0,0158 seconds wihtout.
It would be more rapid to make a sql request for each 50 rows.
Would it be more rapid with a subquery ? How to make it with a subquery ?
Thanks

Mysql query of multiple tables joined

I have the following tables:
machine_machine
id | machineid
1 | EE100034442
item_item
id | upc | name
2 | 10001 | Snickers
machine_setup
id | machine_id | selection | item_id
3 | 1 | A1 | 1
Im trying to get the following output by joining the tables.
machine_setup.machine_id=machine_machine.machineid, machine_setup.selection, item_item.upc, item_item.name
EE100034442 A1 10001 Snickers
Table machine_setup will by the main referenced table as it has multiple selection for each machine_id.
Based on the only id's I can see at the moment to join on, consider this:
create table machine_machine
( id int auto_increment primary key,
machineid varchar(50) not null
);
create table item_item
( id int auto_increment primary key,
upc varchar(30) not null,
name varchar(100) not null
);
create table machine_setup
( id int auto_increment primary key,
machine_id int not null,
selection varchar(30) not null
);
insert machine_machine(machineid) values ('EE100034442');
insert item_item(upc,name) values ('10001','Snickers');
insert machine_setup(machine_id,selection) values (1,'A1'),(1,'A2'),(1,'A(n)');
select mm.machineid,ms.selection,ii.upc,ii.name
from machine_setup ms
join machine_machine mm
on mm.id=ms.machine_id
join item_item ii
on ii.id=ms.machine_id;
+-------------+-----------+-------+----------+
| machineid | selection | upc | name |
+-------------+-----------+-------+----------+
| EE100034442 | A1 | 10001 | Snickers |
| EE100034442 | A2 | 10001 | Snickers |
| EE100034442 | A(n) | 10001 | Snickers |
+-------------+-----------+-------+----------+
I'm not quite sure I understand the question, but the sql you want is like;
Select machine1.machineid, setup.Selection, item.upc, item.name
From Machine_machine machine1 --Set alias for the table
Inner Join machine_setup setup on setup.machine_id = machine1.id --This looks like a link table to me
Inner Join item_item item on setup.item_id = item.id -- in your example this wouldn't link as item_id is 1 in the machine_setup
In your example the machine_setup item_id is set to 1, which means it wouldn't link to the item_item table. i'm assuming this is a mistake.
Let me know if you need more information.