mysql left join question - mysql

I want make query select all names from table 'a' where from table 'b' i have id_one='3'. id_two is id record from table
'a', two records have relation from id_one='3'. How i can make query ?
CREATE TABLE IF NOT EXISTS `a` (
`id` int(11) NOT NULL,
`name` varchar(11) NOT NULL,
`value` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Zrzut danych tabeli `a`
--
INSERT INTO `a` (`id`, `name`, `value`) VALUES
(1, 'lalala', 0),
(2, 'allalala', 0);
CREATE TABLE IF NOT EXISTS `b` (
`id_one` int(11) NOT NULL,
`id_two` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Zrzut danych tabeli `b`
--
INSERT INTO `b` (`id_one`, `id_two`) VALUES
(3, 1),
(3, 2);

This is what you want:
select Name
from a inner join b on a.id = b.id_two
where b.id_one = 3

sorry I'm not fully understanding your schemas or your question, but I think what you're trying to ask for is:
SELECT * FROM a
JOIN b on a.id = b.id_two;
Try that.

Your question title mentions left joins, but you don't need a left join to make the query you described.
Left joins are good for finding things that don't match up the way you'd expect. So, using a left join in this case depends on what you are looking for. If you are looking for b.id_two entries that don't have corresponding table a entries,
select Name, b.* from b left join a on a.id = b.id_two
This will give you a table that lists every row in table b, with NULLs in place of the names for table a where there is no match.
Likewise, if you are looking for names that don't have entries in b.id_two, you would use
select Name, b.* from a left join b on a.id = b.id_two
If you want to enforce that there is always a correspondence, you can define a foreign key constraint between the parent and child table.

select Name
from a join b on a.id = b.id_two
where b.id_one = 3;
Will also work to get your answer. Might I also suggest you significantly improve your create table statements to include indexing. E.G.
CREATE TABLE IF NOT EXISTS `a` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(11) NOT NULL,
`value` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE IF NOT EXISTS `b` (
`id_one` bigint(20) NOT NULL,
`id_two` bigint(20) NOT NULL,
KEY `FKCAFBB09382DEAC` (`id_one`),
CONSTRAINT `b_a_1` FOREIGN KEY (`id_two`) REFERENCES `a` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
I would also use bigint for primary keys and use charset=utf8
these days it is just to common to want to migrate application to multi-lingual, lay the ground work now. IMHO

Related

3 tables joins on One to many

Im having some troubles learning about joins, im working with 2 One-to-many relation ships:
In this case, i have novels with many chapters and many ratings
I need to get the novels information plus a count of chapters associated to each novel and an avarage of the ratings of each novel and im trying this:
SELECT n.id
, n.nvl_title
, COUNT(c.id) AS nvl_chapters
, AVG(nr.rate_value) as nvl_rating
, MAX(c.createdAt) AS nvl_last_update
FROM novels n
left
JOIN novels_ratings nr
ON nr.novel_id = n.id
left
JOIN chapters c
ON c.nvl_id = n.id
AND c.chp_status = 'Active'
WHERE n.nvl_status IN ("Active", "Finished")
GROUP
BY n.id;
Working only with the chapters the query seems to work very fine but if I add the line "left JOIN novels_ratings nr ON nr.novel_id = n.id" the chapters count increment to many ratings the novel have.
For example: A novel with 2 chapters and 2 rating returns 4 chapters in total.
Any help will be fully apreciated.
If there is something I miss to explain, please, let me know and i will try to clarify.
I'veen working with some ugly querys that do the job but as soon as the chapters table begin to have MANY registers I have been forced to learn more optical querys
EDIT
I have create a small database, enough to make some tests on the query:
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET AUTOCOMMIT = 0;
START TRANSACTION;
SET time_zone = "+00:00";
CREATE TABLE `chapters` (
`id` int(11) NOT NULL,
`nvl_id` int(20) DEFAULT NULL,
`chp_title` varchar(250) DEFAULT NULL,
`chp_status` varchar(8) NOT NULL DEFAULT 'Active',
`createdAt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO `chapters` (`id`, `nvl_id`, `chp_title`, `chp_status`, `createdAt`) VALUES
(1, 1, 'generic chapter 1', 'Active', '0000-00-00 00:00:00'),
(2, 1, 'generic chapter 2', 'Active', '0000-00-00 00:00:00');
CREATE TABLE `novels` (
`id` int(20) NOT NULL,
`nvl_title` varchar(250) DEFAULT NULL,
`nvl_status` varchar(8) NOT NULL DEFAULT 'Active'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO `novels` (`id`, `nvl_title`, `nvl_status`) VALUES
(1, 'generic novel', 'Active');
CREATE TABLE `novels_ratings` (
`id` int(20) NOT NULL,
`novel_id` int(20) DEFAULT NULL,
`rate_value` int(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO `novels_ratings` (`id`, `novel_id`, `rate_value`) VALUES
(1, 1, 3),
(2, 1, 4);
ALTER TABLE `chapters`
ADD PRIMARY KEY (`id`);
ALTER TABLE `novels`
ADD PRIMARY KEY (`id`);
ALTER TABLE `novels_ratings`
ADD PRIMARY KEY (`id`);
ALTER TABLE `chapters`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;
ALTER TABLE `novels`
MODIFY `id` int(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;
ALTER TABLE `novels_ratings`
MODIFY `id` int(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;
COMMIT;
Thank you very much!
I think the simplest way to do this is here:
SELECT n.id, n.nvl_title,
COUNT(c.id) AS nvl_chapters,
(select AVG(nr.rate_value) from novels_ratings nr where nr.novel_id = n.id) as nvl_rating,
MAX(c.createdAt) AS nvl_last_update
FROM novels n
left JOIN chapters c ON c.nvl_id = n.id AND c.chp_status = 'Active'
WHERE n.nvl_status IN ("Active", "Finished")
GROUP BY n.id;
Very straightforward, and it should perform well too.
This is a complete solution (finally). Since MySQL does not implement FULL JOIN the solution uses a LEFT JOIN paired with a RIGHT JOIN instead.
You can do:
with
r as (
select n.id, avg(nr.rate_value) as nvl_rating
from novels n
join novels_ratings nr on nr.novel_id = n.id
group by n.id
),
c as (
select n.id, count(c.id) as nvl_chapters, max(c.createdAt) as nvl_last_update
from novels n
join chapters c on c.nvl_id = n.id and c.chp_status = 'Active'
group by n.id
)
select r.id, r.nvl_rating, c.*
from r
left join c on c.id = r.id
UNION ALL
select c.id, r.nvl_rating, c.*
from r
right join c on c.id = r.id
where r.id 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;

JOIN databases from SELECT

I need a joined (Union like) result from multiple databases on the same databaseserver in one query. Every customer database contains a location table and all customers are listed in the core database.
I don't need a simple join between to different databases. I need the actual joined database name to come from the same query.
I figure something like this.
SELECT customerlist.dbname,customerlist.realname,location.address
FROM core.customerlist
INNER JOIN `customer.dbname`.location
ORDER BY customerlist.realname
I know this won't work, I'm just trying to pseudo code what I'm searching for. I hope someone can help.
Database structure:
-- Database: `core`
CREATE TABLE IF NOT EXISTS `customerlist` (
`realname` varchar(20) NOT NULL,
`dbname` varchar(16) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `customerlist` (`realname`, `dbname`) VALUES
('Johnny', 'johnny'),
('Alfred', 'alfred');
-- --------------------------------------------------------
-- Database: `alfred`
CREATE TABLE IF NOT EXISTS `location` (
`address` varchar(20) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `location` (`address`) VALUES
('House Three'),
('Car 1');
-- --------------------------------------------------------
-- Database: `johnny`
CREATE TABLE IF NOT EXISTS `location` (
`address` varchar(20) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `location` (`address`) VALUES
('House One'),
('House Two');
Desired result;
johnny,Johnny,House One
johnny,Johnny,House Two
alfred,Alfred,House Three
alfred,Alfred,Car 1
You'll have to be careful of the two table identical tables...joining them in a union if fine since they are identical in structure:
SELECT customerlist.dbname,customerlist.realname,location.address
FROM core.customerlist
LEFT JOIN (
SELECT * FROM (johnny.location UNION alfred.location
)) AS T2 ON customerlist.dbname = T2.dbname
ORDER BY customerlist.realname

Mysql how to join tables more than two

I have problem with my query,
I have tables below:
CREATE TABLE IF NOT EXISTS `klik_zona` (
`kode_zona` int(10) unsigned NOT NULL,
`klik` int(10) unsigned NOT NULL,
PRIMARY KEY (`kode_zona`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `klik_zona` (`kode_zona`, `klik`) VALUES
(1, 45);
CREATE TABLE IF NOT EXISTS `tampil_zona` (
`kode_zona` int(10) unsigned NOT NULL,
`tanggal` date NOT NULL,
`tampil` int(10) unsigned NOT NULL,
PRIMARY KEY (`kode_zona`,`tanggal`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `tampil_zona` (`kode_zona`, `tanggal`, `tampil`) VALUES
(1, '2014-03-16', 100),
(1, '2014-03-17', 23);
CREATE TABLE IF NOT EXISTS `zona_iklan` (
`kode_zona` int(10) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`kode_zona`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ;
INSERT INTO `zona_iklan` (`kode_zona`) VALUES
(1),
(2),
(3);
I have query:
SELECT z.kode_zona, SUM( tz.tampil ) , SUM( kz.klik )
FROM zona_iklan z
LEFT JOIN tampil_zona tz ON tz.kode_zona = z.kode_zona
LEFT JOIN klik_zona kz ON kz.kode_zona = z.kode_zona
GROUP BY z.kode_zona
but it give result:
kode_zona SUM(tz.tampil) SUM(kz.klik)
1 123 90
2 NULL NULL
3 NULL NULL
I want get result:
kode_zona SUM(tz.tampil) SUM(kz.klik)
1 123 45
2 NULL NULL
3 NULL NULL
please help me.. how to make query so that I get result that I hope it..
thanks,
In your example you join two records from tampil_zona on to one record from zona_iklan, which essentially causes that one record to duplicate. Then you are joining one record in klik_zona on to both of those duplicated records, causing the doubling of results that you want to avoid.
Instead, you need to aggregate the records before you join them, to ensure that you are always joining the records 1-to-1.
SELECT
z.kode_zona, tz.tampil, kz.klik
FROM
zona_iklan AS z
LEFT JOIN
(SELECT kode_zona, SUM(tampil) AS tampil FROM tampil_zona GROUP BY kode_zona) AS tz
ON tz.kode_zona = z.kode_zona
LEFT JOIN
(SELECT kode_zona, SUM(klik) AS klik FROM klik_zona GROUP BY kode_zona) AS kz
ON kz.kode_zona = z.kode_zona
Try removing the GROUP BY and look at the result. You will see that there are two records with kode_zona = 1. This because there are two records in tampil_zona matching that id. You could divide by count(*) but that seems futile. You probably want to think about how to modify the join.

Update statement causes fields to be updated with NULL or maximum value

If you had to pick one of the two following queries, which would you choose and why:
UPDATE `table1` AS e
SET e.points = e.points+(
SELECT points FROM `table2` AS ep WHERE e.cardnbr=ep.cardnbr);
or:
UPDATE `table1` AS e
INNER JOIN
(
SELECT points, cardnbr
FROM `table2`
) AS ep ON (e.cardnbr=ep.cardnbr)
SET e.points = e.points+ep.points;
Tables' definitions:
CREATE TABLE `table1` (
`cardnbr` int(10) DEFAULT NULL,
`name` varchar(50) DEFAULT NULL,
`points` decimal(7,3) DEFAULT '0.000',
`email` varchar(50) NOT NULL DEFAULT 'user#company.com',
`id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=25205 DEFAULT CHARSET=latin1$$
CREATE TABLE `table2` (
`cardnbr` int(10) DEFAULT NULL,
`id` int(11) NOT NULL AUTO_INCREMENT,
`points` decimal(7,3) DEFAULT '0.000',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci$$
UPDATE: BOTH are causing problems the first is causing non matched rows to update into NULL.
The second is causing them to update into the max value 999.9999 (decimal 7,3).
PS the cardnbr field is NOT a key
I prefer the second one..reason for that is
When using JOIN the databse can create an execution plan that is better for your query and save time whereas subqueries (like your first one ) will run all the queries and load all the datas which may take time.
i think subqueries is easy to read but performance wise JOIN is faster...
First, the two statements are not equivalent, as you found out yourself. The first one will update all rows of table1, putting NULL values for those rows that have no related rows in table2.
So the second query looks better because it doesn't update all rows of table1. It could be written in a more simpel way, like this though:
UPDATE table1 AS e
INNER JOIN table2 AS ep
ON e.cardnbr = ep.cardnbr
SET e.points = e.points + ep.points ;
So, the 2nd query would be the best to use, if cardnbr was the primary key of table2. Is it?
If it isn't, then which values from table2 should be used for the update of table1 (added to points)? All of them? You could use this:
UPDATE table1 AS e
INNER JOIN
( SELECT SUM(points) AS points, cardnbr
FROM table2
GROUP BY cardnbr
) AS ep ON e.cardnbr = ep.cardnbr
SET
e.points = e.points + ep.points ;
Just one of them? That would require some other derived table, depending on what you want.