EXPLAIN SELECT.., why TYPE = ALL? - mysql

Having these 3 tables:
users
CREATE TABLE `users` (
`user_id` MEDIUMINT(8) UNSIGNED NOT NULL AUTO_INCREMENT,
`first_name` VARCHAR(64) NOT NULL,
`last_name` VARCHAR(64) NOT NULL,
PRIMARY KEY (`user_id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=1;
posts
CREATE TABLE `posts` (
`post_id` MEDIUMINT(8) UNSIGNED NOT NULL AUTO_INCREMENT,
`category_id` MEDIUMINT(8) UNSIGNED NOT NULL,
`author_id` MEDIUMINT(8) UNSIGNED NOT NULL,
`title` VARCHAR(128) NOT NULL,
`text` TEXT NOT NULL,
PRIMARY KEY (`post_id`),
INDEX `FK_posts__category_id` (`category_id`),
INDEX `FK_posts__author_id` (`author_id`),
CONSTRAINT `FK_posts__author_id` FOREIGN KEY (`author_id`) REFERENCES `users` (`user_id`) ON UPDATE CASCADE,
CONSTRAINT `FK_posts__category_id` FOREIGN KEY (`category_id`) REFERENCES `categories` (`category_id`) ON UPDATE CASCADE ON DELETE CASCADE
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=1;
categories
CREATE TABLE `categories` (
`category_id` MEDIUMINT(8) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(64) NOT NULL,
PRIMARY KEY (`category_id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=1;
And data in tables:
INSERT INTO `users` (`user_id`, `first_name`, `last_name`) VALUES
(1, 'John', 'Doe'),
(2, 'Pen', 'Poe'),
(3, 'Robert', 'Roe');
INSERT INTO `categories` (`category_id`, `name`) VALUES
(1, 'Category 1'),
(2, 'Category 2'),
(3, 'Category 3'),
(4, 'Category 4');
INSERT INTO `posts` (`post_id`, `category_id`, `author_id`, `title`, `text`) VALUES
(1, 1, 1, 'title 1', 'text 1'),
(2, 1, 2, 'title 2', 'text 2');
I want to make a simple select (and let MySQL EXPLAIN it):
EXPLAIN SELECT p.post_id, p.title, p.text, c.category_id, c.name, u.user_id, u.first_name, u.last_name
FROM posts AS p
JOIN categories AS c
ON c.category_id = p.category_id
JOIN users AS u
ON u.user_id = p.author_id
WHERE p.category_id = 1
I got this:
What I don't understand is, why has MySQL to do a full table scan at u (users). I mean there will be only two users it has to retrieve data about (with id 1 and 2), and these two can be found by primary key user_id. Can somebody with more experience help me to understand this? Is there a better way of creating indexes so MySQL don't has to make a full scan on the users table to retrieve data about the post authors?
Thanks you!

So with such a small amount a index search is going to be slower than a sequential search. Thus MySQL is choosing to use a simple table read.
It has to do with operational efficiency here. Lets simply the operations that MySQL has to do to read the entire table vs using a index.
Full read:
Open table
Read each line one at a time and match criteria
Return result set
That is 5 operations.
Index Read
Open table
For the criteria read the index for each row
Using the index pointer locate the row on disk for each row
Return resultset
In this case 8 operations.
This is very simplified but unless you have enough data your indexes can slow you down. As the table grows MySQL might choose a different query path. That is why you dont force the use of indexes.

You only have ~3 rows in your users table, according to your test data and your EXPLAIN report.
The optimizer can produce skewed results if you have too few rows in the tables. It may do a table-scan for a tiny table, even if it would use an index for the same query against the same tables with a few hundred or a few thousand rows.
So when doing development, it's important to have a non-trivial amount of test data in your tables if you want to get accurate optimizer reports.

Related

MySQL: query with two many to many relations and duplicates, with double intermediate table

This question is about selecting data across many-to-many relations in MySQL. Is related to another two questions, but with some differences:
MySQL: query with two many to many relations and duplicates
MySQL: query with two many to many relations and duplicates, with full data from subqueries
Those questions used a simple mockup database with simple many to many relations:
article
article_author
author
article_tag
tag
Now I will introduce next level of complexity. We want each author to be able to tag each of their articles. Thus, we will connect tags to the intermediate table article_author instead of to author directly.
article
article_author
author
article_author_tag
tag
Here is in MySQL:
CREATE TABLE `article` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `author` (
`id` INT NOT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
);
CREATE TABLE `tag` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `article_author` (
`id` int NOT NULL AUTO_INCREMENT,
`author_id` INT NOT NULL,
`article_id` int NOT NULL,
`createdAt` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_index` (`author_id`,`article_id`),
KEY `fk_article_author_author1_idx` (`author_id`),
KEY `fk_article_author_article1_idx` (`article_id`),
CONSTRAINT `fk_article_author_article1` FOREIGN KEY (`article_id`) REFERENCES `article` (`id`),
CONSTRAINT `fk_article_author_author1` FOREIGN KEY (`author_id`) REFERENCES `author` (`id`)
);
CREATE TABLE `article_author_tag` (
`article_author_id` int NOT NULL,
`tag_id` int NOT NULL,
PRIMARY KEY (`article_author_id`,`tag_id`),
KEY `fk_article_author_tag_article_author1_idx` (`article_author_id`),
KEY `fk_article_author_tag_tag1_idx` (`tag_id`),
CONSTRAINT `fk_article_author_tag_article_author1` FOREIGN KEY (`article_author_id`) REFERENCES `article_author` (`id`),
CONSTRAINT `fk_article_author_tag_tag1` FOREIGN KEY (`tag_id`) REFERENCES `tag` (`id`)
);
INSERT INTO article (id, name) VALUES (1, 'first article'), (2, 'second article');
INSERT INTO `author` (id, name) VALUES (1, 'first author'), (2, 'second author');
INSERT INTO tag (id, name) VALUES (1, 'first tag'), (2, 'second tag');
INSERT INTO article_author (author_id, article_id) VALUES (1, 1), (2, 1);
INSERT INTO article_author_tag (article_author_id, tag_id) VALUES (1, 1), (1, 2), (2, 1), (2, 2);
And now, I want just to select the tags that authors of an article used to tag it, as a JSON array; but I can't get rid of duplicates:
SELECT
JSON_ARRAYAGG(tag.id)
FROM article_author
JOIN article_author_tag ON article_author_tag.article_author_id = article_author.id
JOIN tag ON article_author_tag.tag_id = tag.id
WHERE article_author.article_id = 1;
Here it is in a db<>fiddle: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=253f30ecd2f87b06c3894ef02b2ee35d
Any idea how can I get rid of them?
Edit:
I can do it with CONCAT and GROUP_CONCAT, and then casting to JSON. But it looks quite hacky:
SELECT
CAST(CONCAT('[', GROUP_CONCAT(DISTINCT tag.id SEPARATOR ','), ']') AS JSON) AS tags
FROM article_author
JOIN article_author_tag ON article_author_tag.article_author_id = article_author.id
JOIN tag ON article_author_tag.tag_id = tag.id
WHERE article_author.article_id = 1;
Here it is in a db<>fiddle: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=20087a9036acb00637be8d2f58747ba5
Any other idea will be welcome!
There is no distinct functionality for json yet (something like JSON_ARRAYAGG(distinct tag.id)), but there is a common workaround for it:
SELECT JSON_EXTRACT(JSON_OBJECTAGG(tag.id,tag.id),"$.*")
FROM article_author
JOIN article_author_tag ON article_author_tag.article_author_id = article_author.id
JOIN tag ON article_author_tag.tag_id = tag.id
WHERE article_author.article_id = 1;
JSON_OBJECTAGG works as an implict distinct, because json tags are distinct by definition, so adding {"1": 1} twice results in just one of those remaining. Afterwards, you JSON_EXTRACT just the values to get the format you intended (e.g. without the artificially added tags).
Another method would be to feed the json function with the already correct, distinct data:
SELECT JSON_ARRAYAGG(id)
FROM (
SELECT distinct tag.id
FROM article_author
JOIN article_author_tag
ON article_author_tag.article_author_id = article_author.id
JOIN tag ON article_author_tag.tag_id = tag.id
WHERE article_author.article_id = 1
) subquery;
You first prepare the data the way you want it (e.g. the distinct tag-ids), then use JSON_ARRAYAGG to format your output.

Making inventory lists from a master list in mysql [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 5 years ago.
Improve this question
I have inventories at different locations and want to be able to have a seperate inventory list for each location. The inventories should contain the same stuff at each location, so I want to use a "master list" that stores the name and standard quantity of each item. Then each location should have an actual quantity for each item.
I can copy the whole list for each location, but I want to be able to update the master list instead. I have tried searching the web for the solution, but I can't find what I'm looking for, or I don't know what to search for. Any ideas how I can solve this?
Thanks
You need something like this (simplified version containing only required fields):
Database structure:
CREATE TABLE IF NOT EXISTS `items` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`item_name` varchar(50) NOT NULL,
`required_quantity` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
INSERT INTO `items` (`id`, `item_name`, `required_quantity`) VALUES
(1, 'Item 1', 10),
(2, 'Item 2', 20);
-- --------------------------------------------------------
CREATE TABLE IF NOT EXISTS `locations` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`location_name` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
--
-- Zrzut danych tabeli `locations`
--
INSERT INTO `locations` (`id`, `location_name`) VALUES
(1, 'Location 1'),
(2, 'Location 2');
-- --------------------------------------------------------
CREATE TABLE IF NOT EXISTS `locations_items` (
`location_id` int(11) NOT NULL,
`item_id` int(11) NOT NULL,
`actual_quantity` int(11) NOT NULL,
UNIQUE KEY `location_id` (`location_id`,`item_id`),
KEY `item_id` (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `locations_items` (`location_id`, `item_id`, `actual_quantity`) VALUES
(1, 1, 5),
(2, 2, 7);
ALTER TABLE `locations_items`
ADD CONSTRAINT `locations_items_ibfk_1` FOREIGN KEY (`location_id`) REFERENCES `locations` (`id`) ON UPDATE CASCADE,
ADD CONSTRAINT `locations_items_ibfk_2` FOREIGN KEY (`item_id`) REFERENCES `items` (`id`) ON UPDATE CASCADE;
Query to get every location and every item with required (basic quantity) and actual quantity per location:
SELECT
q.location_id,
q.location_name,
q.item_id,
q.item_name,
q.required_quantity,
IFNULL(li.actual_quantity, 0) AS actual_quantity
FROM
(SELECT
l.id AS location_id,
l.location_name,
i.id AS item_id,
i.item_name,
i.required_quantity
FROM
locations AS l
CROSS JOIN items AS i
) AS q
LEFT JOIN locations_items AS li ON q.location_id = li.location_id AND q.item_id = li.item_id
SQL Fiddle Demo
You have common items list in table items where you can enter standard quantity which will be required for all locations. And if you need to update it, do it in the same table and it will apply to all locations at once.
Insert/update actual location in table locations_items which is unique per location and item. There you can record actual quantities for every location.
And query lets you to fetch all items required/actual quantities in all locations. You can of course finetune it with additional conditions to get particular location(s) or item(s).

Selecting data from three tables

Currently at the moment I have managed to join two tables to retrieve the information that is need.
I have now decided to try and retrieve another piece of information from a another table ( users.user_id ) but the query I'm trying to use doesn't seem to work. If someone could help with the query would be great.
Here is my current query that works fine.
"SELECT films.movie_title, films.rating, films.actor, reviewed.review
FROM films
INNER JOIN reviewed
ON films.movie_id=reviewed.movie_id";
Here is the query being used to get data from three tables but wont work
"SELECT films.movie_title, films.rating, films.actor, reviewed.review users.name
FROM films
OUTER JOIN reviewed, users
ON films.movie_id=reviewed.movie_id && films.user_id=users.user_id";
Database: film
Table structure for table films
CREATE TABLE IF NOT EXISTS `films` (
`movie_id` int(4) NOT NULL AUTO_INCREMENT,
`movie_title` varchar(100) NOT NULL,
`actor` varchar(100) NOT NULL,
`rating` varchar(20) NOT NULL,
`user_id` int(100) NOT NULL,
PRIMARY KEY (`movie_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;
INSERT INTO `films` (`movie_id`, `movie_title`, `actor`, `rating`, `user_id`) VALUES
(1, 'batman', 'christian bale', 'Excellent', 3),
(2, 'Bne', 'reee', 'Ok', 3),
(3, 'Today', 'dd', 'Fair', 3);
Table structure for table reviewed
CREATE TABLE IF NOT EXISTS `reviewed` (
`review_id` int(4) NOT NULL AUTO_INCREMENT,
`review` mediumtext NOT NULL,
`movie_id` int(4) NOT NULL,
PRIMARY KEY (`review_id`),
KEY `movie_id` (`movie_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;
INSERT INTO `reviewed` (`review_id`, `review`, `movie_id`) VALUES
(1, 'Wicked film', 1),
(2, 'gedtg', 2),
(3, 'dddd', 3);
Table structure for table users
CREATE TABLE IF NOT EXISTS `users` (
`user_id` int(4) NOT NULL AUTO_INCREMENT,
`email` varchar(40) NOT NULL,
`password` varchar(40) NOT NULL,
`name` varchar(30) NOT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=30 ;
INSERT INTO `users` (`user_id`, `email`, `password`, `name`) VALUES
(1, 'ben#talktalk.net', 'password', 'Ben'),
(2, 'richard#talk.net', '1', 'richard'),
Try this:
SELECT films.movie_title, films.rating, films.actor, reviewed.review, users.name
FROM films
LEFT JOIN reviewed ON films.movie_id=reviewed.movie_id
LEFT JOIN users ON films.user_id=users.user_id
Is it possible that you want to have the user_id in your reviews table?
That way you'd have the following:
table of movies; only one per movie
table of users; only one per user
table of reviews; one per review linked to a user and to a movie
The reviews table would now have the rating, the review itself, a user id, a movie id and a unique review id.
That way a Batman could be given an Excellent rating by me and an Average rating by you without duplicating the movie row.
To just fix your above query, you can use the following:
SELECT films.movie_title, films.rating, films.actor, reviewed.review, users.name FROM films, reviewed, users WHERE films.movie_id = reviewed.movie_id AND films.user_id = users.user_id;
If you want to print only films with reviews, you don't need OUTER JOIN.
SELECT films.movie_title, films.rating, films.actor, reviewed.review users.name
FROM films JOIN reviewed on films.movie_id=reviewed.movie_id
JOIN users ON films.user_id=users.user_id;
If you want to print all films, even those with 0 reviews, you have to use LEFT JOIN (MySQL doesn't have FULL OUTER JOIN).
SELECT films.movie_title, films.rating, films.actor, reviewed.review users.name
FROM films LEFT JOIN reviewed on films.movie_id=reviewed.movie_id
LEFT JOIN users ON films.user_id=users.user_id;

MYSQL - One Column Referenced to Multiple Table

Can a single column in a table can be referenced to multiple tables?
A very late answer, but for whoever is wondering & googeling.
YES this can be done, but it is NOT good practice and even though it is quite simple, it will probably blow up in your face if you're not very aware of what you are doing. Not recommended.
However, I can see uses. For instance, you have a large table of millions of records, and you want in exceptional cases link to unknown or multiple tables (in which case it better be many). With multiple tables, if you would make a foreign key for all of them, that would be a huge bloat in your database size. An unknown table would be possible for instance in a technical support system, where you want to link to record in a table where there might be a problem, and this could be (almost) all tables in the database, including future ones.
Of course you will need two fields to link with: a foreign key field and the name of the table it is linking to. Lets call them foreignId and linkedTable
linkedTable could be an enum or a string, preferrably enum (less space), but that's only possible if the different tables you want to link to, are fixed.
Let's give an extremely silly example. You have an enormous user table users of which some user can add exactly one personal set of data to their profile. This can be about a hobby, a pet, a sport they practice or their profession. Now this info is different in all four cases. (4 possible tables is in reality not enough to justify this structure)
Now let's say linkedTable is an enum with possible values pets, hobbies, sports and professions, which are the names of four differently structured tables. Let's say id is the pkey in all four of them.
You join for instance as follows:
SELECT * FROM users
LEFT JOIN pets ON linkedTable = 'pets' AND foreignId = pets.id
LEFT JOIN hobbies ON linkedTable = 'hobbies' AND foreignId = hobbies.id
LEFT JOIN sports ON linkedTable = 'sports' AND foreignId = sports.id
LEFT JOIN professions ON linkedTable = 'professions' AND foreignId = professions.id
This is just to give a basic jest. Since you probably only need the link in rare cases, you will more likely do the lookup in your programming language, like PHP, when you loop through the users (without join).
Want to try out? You can try it yourself with building this test database (make sure you use a test database):
CREATE TABLE IF NOT EXISTS `users` (
`id` INT NOT NULL AUTO_INCREMENT ,
`name` VARCHAR(100) NOT NULL ,
`linkedTable` ENUM('pets','hobbies','sports','professions') NULL DEFAULT NULL ,
`foreignId` INT NULL DEFAULT NULL ,
PRIMARY KEY (`id`), INDEX (`linkedTable`)
) ;
CREATE TABLE IF NOT EXISTS `pets` (
`id` INT NOT NULL AUTO_INCREMENT ,
`animalTypeId` INT NOT NULL ,
`name` VARCHAR(100) NOT NULL ,
`colorId` INT NOT NULL ,
PRIMARY KEY (`id`), INDEX (`animalTypeId`), INDEX (`colorId`)
) ;
CREATE TABLE IF NOT EXISTS `hobbies` (
`id` INT NOT NULL AUTO_INCREMENT ,
`hobbyTypeId` INT NOT NULL ,
`hoursPerWeekSpend` INT NOT NULL ,
`websiteUrl` VARCHAR(300) NULL ,
PRIMARY KEY (`id`), INDEX (`hobbyTypeId`)
) ;
CREATE TABLE IF NOT EXISTS `sports` (
`id` INT NOT NULL AUTO_INCREMENT ,
`sportTypeId` INT NOT NULL ,
`hoursPerWeekSpend` INT NOT NULL ,
`nameClub` VARCHAR(100) NULL ,
`professional` TINYINT NOT NULL DEFAULT 0,
PRIMARY KEY (`id`), INDEX (`sportTypeId`)
) ;
CREATE TABLE IF NOT EXISTS `professions` (
`id` INT NOT NULL AUTO_INCREMENT ,
`professionId` INT NOT NULL ,
`hoursPerWeek` INT NOT NULL ,
`nameCompany` VARCHAR(100) NULL ,
`jobDescription` VARCHAR(400) NULL,
PRIMARY KEY (`id`), INDEX (`professionId`)
) ;
INSERT INTO `users` (`id`, `name`, `linkedTable`, `foreignId`)
VALUES
(NULL, 'Hank', 'pets', '1'),
(NULL, 'Peter', 'hobbies', '2'),
(NULL, 'Muhammed', 'professions', '1'),
(NULL, 'Clarice', NULL, NULL),
(NULL, 'Miryam', 'professions', '2'),
(NULL, 'Ming-Lee', 'hobbies', '1'),
(NULL, 'Drakan', NULL, NULL),
(NULL, 'Gertrude', 'sports', '2'),
(NULL, 'Mbase', NULL, NULL);
INSERT INTO `pets` (`id`, `animalTypeId`, `name`, `colorId`)
VALUES (NULL, '1', 'Mimi', '3'), (NULL, '2', 'Tiger', '8');
INSERT INTO `hobbies` (`id`, `hobbyTypeId`, `hoursPerWeekSpend`, `websiteUrl`)
VALUES (NULL, '123', '21', NULL), (NULL, '2', '1', 'http://www.freesoup.org');
INSERT INTO `sports` (`id`, `sportTypeId`, `hoursPerWeekSpend`, `nameClub`, `professional`)
VALUES (NULL, '2', '3', 'Racket to Racket', '0'), (NULL, '12', '34', NULL, '1');
INSERT INTO `professions` (`id`, `professionId`, `hoursPerWeek`, `nameCompany`, `jobDescription`)
VALUES (NULL, '275', '40', 'Ben & Jerry\'s', 'Ice cream designer'), (NULL, '21', '24', 'City of Dublin', 'Garbage collector');
Then run the first query.
Fun note for discussion: How would you index this?
If you mean "can a column in one table be used as a foreign key in multiple tables", then the answer is YES. This is the whole point of a relational database
Yes, you can do that so. here is a sample on how to do it:
Here is the table that has a column(CountryID) that will be referenced by multiple tables:
CREATE TABLE DLAccountingSystem.tblCountry
(
CountryID INT AUTO_INCREMENT NOT NULL PRIMARY KEY,
CountryName VARCHAR(128) NOT NULL,
LastEditUser VARCHAR(128) NOT NULL,
LastEditDate DATETIME NOT NULL
) ENGINE=INNODB;
Here are the tables that is going to reference the column(CountryID):
CREATE TABLE DLAccountingSystem.tblCity
(
CityID INT AUTO_INCREMENT NOT NULL PRIMARY KEY,
CountryID INT NOT NULL,
CityName VARCHAR(128) NOT NULL,
LastEditUser VARCHAR(128) NOT NULL,
LastEditDate DATETIME NOT NULL
) ENGINE=INNODB;
CREATE TABLE DLAccountingSystem.tblProvince
(
ProvinceID INT AUTO_INCREMENT NOT NULL PRIMARY KEY,
CountryID INT NOT NULL,
ProvinceName VARCHAR(128) NOT NULL,
LastEditUser VARCHAR(128) NOT NULL,
LastEditDate DATETIME NOT NULL
) ENGINE=INNODB;
Here is how you create a reference to the column:
ALTER TABLE DLAccountingSystem.tblCity
ADD CONSTRAINT fk_tblcitycountryid FOREIGN KEY CountryID (CountryID)
REFERENCES DLAccountingSystem.tblCountry (CountryID)
ON DELETE NO ACTION
ON UPDATE NO ACTION
ALTER TABLE DLAccountingSystem.tblProvince
ADD CONSTRAINT fk_tblprovincecountryid FOREIGN KEY CountryID (CountryID)
REFERENCES DLAccountingSystem.tblCountry (CountryID)
ON DELETE NO ACTION
ON UPDATE NO ACTION
here is a table that has column that references different columns from (CountryID, ProvinceID, CityID) multiple tables(I Don't personally advice this way of table structuring. Just my opinion no offense ;) )
CREATE TABLE DLAccountingSystem.tblPersons
(
PersonID INT AUTO_INCREMENT NOT NULL PRIMARY KEY,
PlaceID INT NOT NULL,
PlaceTypeID INT NOT NULL, -- this property refers to what table are you referencing.
//Other properties here.....
) ENGINE=INNODB;
you should also have a lookup table that would contain the PlaceType:
CREATE TABLE DLAccountingSystem.tblPlaceType
(
PlaceTypeID INT AUTO_INCREMENT NOT NULL PRIMARY KEY,
PlaceTypeName INT NOT NULL
//Other properties here.....
) ENGINE=INNODB;
here is how you fetch it:
SELECT p1.PersonID,
tcity.CityName,
tprov.ProvinceName,
tcoun.CountryName
FROM DLAccountingSystem.tblPersons p1
LEFT JOIN (SELECT p2.PersonID, p2.PlaceTypeID, c.CityName FROM DLAccountingSystem.tblPersons p2 INNER JOIN DLAccountingSystem.tblCity c ON p2.ObjectID = c.CityID WHERE PlaceTypeID = #CityTypeID) tcity ON p1.PersonID = tcity.PersonID
LEFT JOIN (SELECT p2.PersonID, p2.PlaceTypeID, c.ProvinceName FROM DLAccountingSystem.tblPersons p2 INNER JOIN DLAccountingSystem.tblProvince c ON p2.ObjectID = c.ProvinceID WHERE PlaceTypeID = #ProvinceTypeID) tprov ON p1.PersonID = tprov.PersonID
LEFT JOIN (SELECT p2.PersonID, p2.PlaceTypeID, c.CountryName FROM DLAccountingSystem.tblPersons p2 INNER JOIN DLAccountingSystem.tblCountry c ON p2.ObjectID = c.CountryID WHERE PlaceTypeID = #CountryTypeID) tcoun ON p1.PersonID = tcoun.PersonID
you can select from other tables like
A same column or set of columns can act as a parent and/or as a child endpoint of a foreign key or foreign keys.

How to refact UPDATE via SELECT

I have this structure of my db:
CREATE TABLE IF NOT EXISTS `peoples` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ;
For customers.
CREATE TABLE IF NOT EXISTS `peoplesaddresses` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`people_id` int(10) unsigned NOT NULL,
`phone` varchar(20) COLLATE utf8_unicode_ci NOT NULL,
`address` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ;
For their addresses.
CREATE TABLE IF NOT EXISTS `peoplesphones` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`people_id` int(10) unsigned NOT NULL,
`phone` varchar(20) COLLATE utf8_unicode_ci NOT NULL,
`address` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ;
For their phones.
UPD4
ALTER TABLE peoplesaddresses DISABLE KEYS;
ALTER TABLE peoplesphones DISABLE KEYS;
ALTER TABLE peoplesaddresses ADD INDEX i_phone (phone);
ALTER TABLE peoplesphones ADD INDEX i_phone (phone);
ALTER TABLE peoplesaddresses ADD INDEX i_address (address);
ALTER TABLE peoplesphones ADD INDEX i_address (address);
ALTER TABLE peoplesaddresses ENABLE KEYS;
ALTER TABLE peoplesphones ENABLE KEYS;
END UPD4
CREATE TABLE IF NOT EXISTS `order` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`people_id` int(10) unsigned NOT NULL,
`name` varchar(255) CHARACTER SET utf8 NOT NULL,
`phone` varchar(255) CHARACTER SET utf8 NOT NULL,
`adress` varchar(255) CHARACTER SET utf8 NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=12 ;
INSERT INTO `order` (`id`, `people_id`, `name`, `phone`, `adress`) VALUES
(1, 0, 'name1', 'phone1', 'address1'),
(2, 0, 'name1_1', 'phone1', 'address1_1'),
(3, 0, 'name1_1', 'phone1', 'address1_2'),
(4, 0, 'name2', 'phone2', 'address2'),
(5, 0, 'name2_1', 'phone2', 'address2_1'),
(6, 0, 'name3', 'phone3', 'address3'),
(7, 0, 'name4', 'phone4', 'address4'),
(8, 0, 'name1_1', 'phone5', 'address1_1'),
(9, 0, 'name1_1', 'phone5', 'address1_2'),
(11, 0, 'name1', 'phone1', 'address1'),
(10, 0, 'name1', 'phone1', 'address1');
Production base have over 9000 records. Is there way to execute this 3 update query's little more faster, than now (~50 min on dev machine).
INSERT INTO peoplesphones( phone, address )
SELECT DISTINCT `order`.phone, `order`.adress
FROM `order`
GROUP BY `order`.phone;
Fill peoplesphones table with unique phones
INSERT INTO peoplesaddresses( phone, address )
SELECT DISTINCT `order`.phone, `order`.adress
FROM `order`
GROUP BY `order`.adress;
Fill peoplesaddresses table with unique adress.
The next three querys are very slow:
UPDATE peoplesaddresses, peoplesphones SET peoplesaddresses.people_id = peoplesphones.id WHERE peoplesaddresses.phone = peoplesphones.phone;
UPDATE peoplesaddresses, peoplesphones SET peoplesphones.people_id = peoplesaddresses.people_id WHERE peoplesaddresses.address = peoplesphones.address;
UPDATE `order`, `peoplesphones` SET `order`.people_id = `peoplesphones`.people_id where `order`.phone = `peoplesphones`.phone;
Finally fill people table, and clear uneccessary fields.
INSERT INTO peoples( id, name )
SELECT DISTINCT `order`.people_id, `order`.name
FROM `order`
GROUP BY `order`.people_id;
ALTER TABLE `peoplesphones`
DROP `address`;
ALTER TABLE `peoplesaddresses`
DROP `phone`;
So, again: How can I make those UPDATE query's a little more faster? THX.
UPD: I forgott to say: I need to do it at once, just for migrate phones and adresses into other tables since one people can have more than one phone, and can order pizza not only at home.
UPD2:
UPD3:
Replace slow update querys on this (without with) get nothing.
UPDATE peoplesaddresses
LEFT JOIN
peoplesphones
ON peoplesaddresses.phone = peoplesphones.phone
SET peoplesaddresses.people_id = peoplesphones.id;
UPDATE peoplesphones
LEFT JOIN
`peoplesaddresses`
ON `peoplesaddresses`.address = `peoplesphones`.address
SET `peoplesphones`.people_id = `peoplesaddresses`.people_id;
UPDATE `order`
LEFT JOIN
`peoplesphones`
ON `order`.phone = `peoplesphones`.phone
SET `order`.people_id = `peoplesphones`.people_id;
UPD4 After adding code at the top (upd4), script takes a few seconds for execute. But on ~6.5k query it terminate with text: "The system cannot find the Drive specified".
Thanks to All. Especially to xQbert and Brent Baisley.
50 minutes for 9000 records is a bit ridiculous, event without indexes. You might as well put the 9000 records in Excel and do what you need to do. I think there is something else going on with your dev machine. Perhaps you have mysql configured to use very little memory? Maybe you can post the results of this "query":
show variables like "%size%";
Just this morning I did an insert(ignore)/select on 2 tables (one into another), both with over 400,000 records. 126,000 records were inserted into the second table, it took a total of 2 minutes 13 seconds.
I would say put indexes on any of the fields you are joining or grouping on, but this seems like a one time job. I don't think the lack of indexes is your problem.
All write operations are slow in relational databases. Especially indexes make them slow, since they have to be recalculated.
If you're using a WHERE in your statements, you should place an index on the fields referenced.
GROUP BY is always very slow, and so is DISTINCT, since they have to do a lot of checks that don't scale linearly. Always avoid them.
You may like to choose a different database engine for what you're doing. 9000 records in 50 minutes is very slow. Experiment with a few different engines, such as MyISAM and InnoDB. If you're using temporary tables a lot, MEMORY is really fast for those.
Update: Also, updating multiple tables in one statement probably shouldn't be done.