left Join ON condition and Select where condition difference - mysql

I have three tables {animal, food, animal_food}
DROP TABLE IF EXISTS `tbl_animal`;
CREATE TABLE `tbl_animal` (
id_animal INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(25) NOT NULL DEFAULT "no name",
sex CHAR(1) NOT NULL DEFAULT "M",
size VARCHAR(10) NOT NULL DEFAULT "Mini",
age VARCHAR(10) NOT NULL DEFAULT "born",
hair VARCHAR(5 ) NOT NULL DEFAULT "short",
color VARCHAR(25) NOT NULL DEFAULT "not defined",
FOREIGN KEY (sex) REFERENCES `tbl_sexes` (sex),
FOREIGN KEY (tamanio) REFERENCES `tbl_sizes` (size),
FOREIGN KEY (age) REFERENCES `tbl_ages` (age),
FOREIGN KEY (hair) REFERENCES `tbl_hair_length` (hair_length),
CONSTRAINT `uc_Info_Animal` UNIQUE (`id_animal`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `tbl_food`;
CREATE TABLE `tbl_food` (
id_food INTEGER NOT NULL PRIMARY KEY,
type_food VARCHAR(20) NOT NULL DEFAULT "Other",
label VARCHAR(50) NOT NULL,
CONSTRAINT `uc_Info_Food` UNIQUE (`id_food`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `animal_food`;
CREATE TABLE `animal_food` (
id_animal INTEGER NOT NULL,
food VARCHAR(50) NOT NULL DEFAULT "",
quantity VARCHAR(50) NOT NULL DEFAULT "",
times VARCHAR(50) NOT NULL DEFAULT "",
description VARCHAR(50) NOT NULL DEFAULT "",
date_last DATE DEFAULT '0000-00-00 00:00:00',
date_water DATE DEFAULT '0000-00-00 00:00:00',
CONSTRAINT fk_ID_Animal_Food FOREIGN KEY (id_animal) REFERENCES `tbl_animal`(id_animal)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
And I have a view where I select the values columns in animal and animal_food depending on ID
CREATE VIEW `CAT_animal_food` AS
SELECT a.name, a.sex,a.size,a.age,a.hair,a.color,
a_f.*
FROM `tbl_animal` a, `animal_food` a_f
WHERE a.id_animal = a_f.id_animal;
What would be better to create a view like above or, to join these animal and animal_food tables?
SELECT ...
FROM A.table t1
JOIN B.table2 t2 ON t2.column = t1.col
What is really the diference between that kind of view and a left join for example?

The only difference between the two SELECT statements is the syntax style. Both perform INNER JOINS. In other words, this style uses what is called "implicit" syntax:
SELECT ...
FROM A.table t1, B.table2 t2
WHERE t2.column = t1.col
It is "implicit" because the join condition is implied by the WHERE clause. This version uses "explicit" syntax:
SELECT ...
FROM A.table t1
JOIN B.table2 t2 ON t2.column = t1.col
Most people prefer to see "explicit" syntax because it make your code easier to follow; the join condition is explicitly understood and any WHERE clause is obvious.
None of this is related to LEFT JOINS of course. Here is a famous link with a great visual description of join types.

There is a big difference.
The old school style using a list of tables only allows inner joins.
Further, placing non-key conditions in the ON clause of a proper join allows greater performance and capability that a where clause can not deliver. The primary reason for this is that the ON clause is evaluated as the join is being made, but the WHERE clause is evaluated after all joins have been made.
The subject is too complex to do justice here.

Related

Can't query names from a table that does not exist in another table

I have two tables that are identical in fields. I want to query names in table2 that are not in table1. Both tables have name field as unique (primary key).
Here are the info. of my database design:
My query is:
SELECT `table2`.`name` FROM `mydatabase`.`table2`, `mydatabase`.`table1`
WHERE `table2`.`name` NOT IN (SELECT `table1`.`name` FROM `mydatabase`.`table1`)
AND table2`.`name` NOT LIKE 'xyz%';
The output of SHOW CREATE TABLE <table name>:
For table1:
table1, CREATE TABLE `table1` (
`name` varchar(500) NOT NULL,
`ip` varchar(500) DEFAULT NULL,
`type` varchar(500) DEFAULT NULL,
`grade` varchar(500) DEFAULT NULL,
`extended_ip` text,
PRIMARY KEY (`name`),
UNIQUE KEY `mydatabase_name_UNIQUE` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
And table2:
tabl2, CREATE TABLE `table2` (
`name` varchar(500) NOT NULL,
`ip` varchar(500) DEFAULT NULL,
`type` varchar(500) DEFAULT NULL,
`grade` varchar(500) DEFAULT NULL,
`extended_ip` text,
PRIMARY KEY (`name`),
UNIQUE KEY `mydatabase_name_UNIQUE` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
The output of EXPLAIN <my query>:
# id, select_type, table, partitions, type, possible_keys, key, key_len, ref, rows, filtered, Extra
1, PRIMARY, table1, , index, , mydatabase_name_UNIQUE, 502, , 17584, 100.00, Using index
1, PRIMARY, table2, , index, , mydatabase_name_UNIQUE, 502, , 46264, 100.00, Using where; Using index; Using join buffer (Block Nested Loop)
2, SUBQUERY, table1 , index, PRIMARY,mydatabase_name_UNIQUE, mydatabase_name_UNIQUE, 502, , 17584, 100.00, Using index
EDIT:
And I forgot to mention what happens is that the databse just crashes with my query. i am using mysql-workbench in Ubuntu 18. When I perform this query the whole workbench closes and I have to restart opening it again.
Just do a LEFT JOIN on name, with table2 as your starting table, since you want to consider all the names from table2 which do not exist in table1. Names which don't exist in table1 will have a null value post the join. Note that this join based solution will be significantly faster than any subquery based approach.
Also, you should avoid comma (,) based implicit joins. It is old syntax, and you should use explicit JOIN based syntax. Read: Explicit vs implicit SQL joins
Also, it is a good habit to use Aliasing for better readability
Try the following:
SELECT t2.name
FROM `mydatabase`.`table2` AS t2
LEFT JOIN `mydatabase`.`table1` AS t1 ON t1.name = t2.name
WHERE t1.name IS NULL
AND t2.name NOT LIKE 'xyz%';
Try a subquery:
SELECT `table2`.`name` FROM `mydatabase`.`table2` WHERE `table2`.`name` NOT IN (SELECT `table1`.`name` FROM `mydatabase`.`table1`);

How do I SELECT from a table with a JOIN with multiple matching values?

I have the following simple query that works just fine when there is one keyword to match:
SELECT gc.id, gc.name
FROM gift_card AS gc
JOIN keyword ON gc.id = keyword.gc_id
WHERE keyword = 'mini'
GROUP BY gc.id
ORDER BY id DESC
What I want to do is find the id's that match at least two of the keywords I provide. I thought just adding a simple AND would work but I get blank results.
SELECT gc.id, gc.name
FROM gift_card AS gc
JOIN keyword ON gc.id = keyword.gc_id
WHERE keyword = 'mini'
AND keyword = '2012'
GROUP BY gc.id
ORDER BY id DESC
Obviously SQL is not my strong suit so I am looking for some help one what I am doing wrong here.
Here are my table structures:
CREATE TABLE `gift_card` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=52 DEFAULT CHARSET=utf8;
CREATE TABLE `keyword` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`gc_id` int(11) NOT NULL,
`keyword` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`),
UNIQUE KEY `dupes_UNIQUE` (`gc_id`,`keyword`)
) ENGINE=InnoDB AUTO_INCREMENT=477 DEFAULT CHARSET=utf8;
No, and does not work. A column cannot have two different values in one row.
Instead, or . . . and a bit more logic:
SELECT gc.id, gc.name
FROM gift_card gc JOIN
keyword k
ON gc.id = k.gc_id
WHERE k.keyword IN ('mini', '2012')
GROUP BY gc.id
HAVING COUNT(*) = 2 -- both match
ORDER BY id DESC;
It is a good idea to qualify all column names in a query that has more than one table reference.

MySQL - Query with LEFT JOIN and group by not returning rows for count 0

I am running below mentioned query.
select c.id, c.code, c.name, count(a.iso_country) from countries c
left join airports a on c.code = a.iso_country group by a.iso_country
order by count(a.iso_country);
In my 'countries' table, I have 247 rows.
In 'airports' table, 'iso_country' column maps to 'code' column in 'countries' table.
Below are the table definitions.
Countries table -
CREATE TABLE `countries` (
`id` int(11) NOT NULL,
`code` varchar(2) NOT NULL,
`name` text,
`continent` text,
PRIMARY KEY (`id`),
UNIQUE KEY `code_UNIQUE` (`code`),
KEY `code_idx` (`code`)
)
Airports table -
CREATE TABLE `airports` (
`id` int(11) NOT NULL,
`type` text,
`name` text,
`continent` text,
`iso_country` varchar(2) DEFAULT NULL,
`iso_region` text,
PRIMARY KEY (`id`),
KEY `country_iso_code_fk_idx` (`iso_country`),
CONSTRAINT `country_fk` FOREIGN KEY (`iso_country`) REFERENCES `countries`
(`code`) )
The issue I'm facing is - the query I mentioned above returns 242 countries - 241 countries with airports and 1 with null values for 'airports', but doesn't include other 5 countries who also don't have any airports. Please guide me what am I doing wrong in this query.
PS:- I am just a novice in SQL.
I'm running on MySQL 5.7 Community Edition.
Thanks in advance.
You want a count of airports by country, including those where there are none, right?
Try this:
SELECT
c.id, c.code, c.name, count(a.iso_country) AS airport_count
FROM
countries c LEFT JOIN airports a ON c.code = a.iso_country
GROUP BY
c.id, c.code, c.name
ORDER BY
airport_count DESC;
Could it have something to do with NULL values? Your a.iso_country column is DEFAULT NULL and c.code is NOT NULL. Usually when comparing values, if either can be NULL, you want to use something like COALESCE to provide a second option in the case that the value is NULL. The reason for this being that NULL != '' and NULL != 0. What if you join on COALESCE(a.iso_country, '') = COALESCE(c.code, '')?
I'm also not sure how COUNT behaves when given a NULL. You may need to be careful there.
Can you try with checking this condition in the where clause .Implicitly checking null in table where airport are not found.
where (airports.name.is null)

Possible multiple returned matches joined to a MySQL table

I am not even sure how to ask this question but here's my situation. I use Plex to stream movies at home. I've built a database which I translate to a webpage that I use as an index. With in this database I have a few tables. The main one is called movie_list. 1 of the fields is called Rating which has an association table called assc_movie_genre which simply stores the movie id generated from the main table and a genre id which is read from another association table. There can be multiple movie Id's that are the same which match a Genre, for instance let's say The Matrix falls under the category Action and Sci Fi there will be 2 entries for MovieId each on matching the corresponding genre code. Anyways, my question is I need a query (if possible) that can join all genres to the appropriate row. Right now I have the following query
SELECT a.`Title`,a.`Year`,b.`Rating` FROM movie_list a, assc_rating b WHERE b.`Id` = a.`Rating
But would need to expand it to I guess join the multiple genres that match. I hope that all makes sense.
Thanks in advance
Update
Thanks to your help I am also there. Here is my current query
SELECT a.Title, c.Rating,
GROUP_CONCAT(DISTINCT b.GenreId ORDER BY b.GenreId)
AS Genres FROM assc_movie_genre b, movie_list a, assc_rating c
WHERE a.Id = b.MovieId AND a.Rating = c.Id group by a.Title
ORDER BY a.Title;
But the issue remains where I am just getting the GenreId instead of the genre name. I would assume I need to put a select in there somewhere so that it is pulling the name from the assc_genres tables just not 100% sure where.
Here's what the current output looks like
Title Rating Genres
28 Days Later... R 11,16,17
The concat works great and I'm so close. Thanks again
Update
Here are the queries to create my tables, you can get the structure from here (obviously)
CREATE TABLE IF NOT EXISTS `assc_genres` (
`Id` int(11) NOT NULL AUTO_INCREMENT,
`Genre` varchar(50) NOT NULL DEFAULT '0',
PRIMARY KEY (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=latin1;
CREATE TABLE IF NOT EXISTS `assc_movie_genre` (
`MovieId` int(11) NOT NULL DEFAULT '0',
`GenreId` int(11) NOT NULL DEFAULT '0',
KEY `FK_assc_movie_genre_movie_list` (`MovieId`),
KEY `FK_assc_movie_genre_assc_genres` (`GenreId`),
CONSTRAINT `FK_assc_movie_genre_movie_list` FOREIGN KEY (`MovieId`) REFERENCES `movie_list` (`Id`),
CONSTRAINT `FK_assc_movie_genre_assc_genres` FOREIGN KEY (`GenreId`) REFERENCES `assc_genres` (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE IF NOT EXISTS `assc_rating` (
`Id` int(11) NOT NULL AUTO_INCREMENT,
`Rating` char(50) NOT NULL DEFAULT '0',
PRIMARY KEY (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=latin1;
CREATE TABLE IF NOT EXISTS `assc_status` (
`Id` tinyint(4) NOT NULL,
`Status` char(50) NOT NULL,
PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE IF NOT EXISTS `movie_list` (
`Id` int(11) NOT NULL AUTO_INCREMENT,
`Title` varchar(100) NOT NULL DEFAULT '0',
`Year` year(4) NOT NULL DEFAULT '2000',
`Rating` int(11) NOT NULL DEFAULT '0',
`Folder` varchar(50) NOT NULL DEFAULT '0',
PRIMARY KEY (`Id`),
UNIQUE KEY `Title_Year` (`Title`,`Year`),
KEY `FK_movie_list_assc_rating` (`Rating`),
CONSTRAINT `FK_movie_list_assc_rating` FOREIGN KEY (`Rating`) REFERENCES `assc_rating` (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=614 DEFAULT CHARSET=latin1;
I am not sure as well if this is what you are asking, but you can join all 3 tables to get the data like
SELECT a.`Title`,
a.`Year`,
b.`Rating`
FROM movie_list a
JOIN assc_movie_genre c ON a.Id = c.movie_id
JOIN assc_rating b ON b.`Id` = c.genre_id;
Per your comment you can use GROUP_CONCAT() like
SELECT a.`Title`,
a.`Year`,
b.`Rating`,
xx.genre_list
FROM movie_list a
JOIN ( select movie_id, genre_id, group_concat(genre) as genre_list
from assc_movie_genre
group by movie_id) xx ON a.Id = xx.movie_id
JOIN assc_rating b ON b.`Id` = xx.genre_id;
You can modify your query like
SELECT a.Title, c.Rating,
GROUP_CONCAT(DISTINCT d.`Genre` ORDER BY d.`Genre`) AS Genres
FROM movie_list a
JOIN assc_movie_genre b ON a.Id = b.MovieId
JOIN assc_rating c ON a.Rating = c.Id
JOIN `assc_genres` d ON b.`GenreId` = d.Id
group by a.Title
ORDER BY a.Title;

delete rows from multiple tables

I'm trying to use SQL to delete multiple rows from multiple tables that are
joined together.
Table A is joined to Table B
Table B is joined to Table C
I want to delete all rows in table B & C that correspond to a row in Table A
CREATE TABLE `boards` (
`boardid` int(2) NOT NULL AUTO_INCREMENT,
`boardname` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`boardid`)
);
-- --------------------------------------------------------
--
-- Table structure for table `messages`
--
CREATE TABLE `messages` (
`messageid` int(6) NOT NULL AUTO_INCREMENT,
`boardid` int(2) NOT NULL DEFAULT '0',
`topicid` int(4) NOT NULL DEFAULT '0',
`message` text NOT NULL,
`author` varchar(255) NOT NULL DEFAULT '',
`date` datetime DEFAULT NULL,
PRIMARY KEY (`messageid`)
);
-- --------------------------------------------------------
--
-- Table structure for table `topics`
--
CREATE TABLE `topics` (
`topicid` int(4) NOT NULL AUTO_INCREMENT,
`boardid` int(2) NOT NULL DEFAULT '0',
`topicname` varchar(255) NOT NULL DEFAULT '',
`author` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`topicid`)
);
Well, if you had used InnoDB tables, you could set up a cascading delete with foreign keys that would do it all automatically. But if you have some reason for using MyISAM, You just use a multiple-table DELETE:
DELETE FROM boards, topics, messages
USING boards INNER JOIN topics INNER JOIN messages
WHERE boards.boardid = $boardid
AND topics.boardid = boards.boardid
AND messages.boardid = boards.boardid;
this can be done by your db-system if you are using foreign keys with "on delete cascade".
Take a look here:
http://dev.mysql.com/doc/refman/5.1/en/innodb-foreign-key-constraints.html
You could either just check for presence
delete from topics where boardid in (select boardid from boards)
delete from messages where boardid in (select boardid from boards)
but this would only make sense if this behaviour should not always apply. When the behaviour should always apply, implement foreign keys with delete on cascade
explained on a zillion sites, in your helpfiles and here
Deleting rows from multiple tables can be done in two ways :
Delete rows from one table, determining which rows to delete by referring to another
table
Delete rows from multiple tables with a single statement
Multiple-table DELETE statements can be written in two formats. The following example demonstrates one syntax, for a query that deletes rows from a table t1 where the id values match those in a table t2:
DELETE t1 FROM t1, t2 WHERE t1.id = t2.id;
The second syntax is slightly different:
DELETE FROM t1 USING t1, t2 WHERE t1.id = t2.id;
To delete the matching records from both tables, the statements are:
DELETE t1, t2 FROM t1, t2 WHERE t1.id = t2.id;
DELETE FROM t1, t2 USING t1, t2 WHERE t1.id = t2.id;
The ORDER BY and LIMIT clauses normally supported by UPDATE and DELETE aren’t allowed when these statements are used for multiple-table operations.