Selecting data from three tables - mysql

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;

Related

get data and count from table with join from two different tables

I have three tables activity_log, user, staff. I have to select data from activity_log to identify which user has done which activity.
This is structure of activity_log table
CREATE TABLE `activity_log` (
`id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`os` varchar(20) NOT NULL,
`api` varchar(100) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Dumping data for table activity_log
INSERT INTO `activity_log` (`id`, `user_id`, `os`, `api`) VALUES
(1, 1, 'web', 'user/login'),
(2, 2, 'web', 'user/report'),
(3, 1, 'android', 'user/login'),
(4, 2, 'ios', 'user/data'),
(5, 3, 'android', 'user/category'),
(6, 3, 'web', 'user/result'),
(7, 3, 'ios', 'user/send_sms');
This is structure of user table
CREATE TABLE `user` (
`id` int(11) NOT NULL,
`first_name` varchar(100) NOT NULL,
`last_name` varchar(100) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Dumping data for table user
INSERT INTO `user` (`id`, `first_name`, `last_name`) VALUES
(1, 'Yogesh', 'Kale'),
(2, 'Sunit', 'Desai'),
(3, 'Paresh', 'Godambe');
This is my staff table
CREATE TABLE `staff` (
`id` int(11) NOT NULL,
`first_name` varchar(100) NOT NULL,
`last_name` varchar(100) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Dumping data for table staff
INSERT INTO `staff` (`id`, `first_name`, `last_name`) VALUES
(1, 'abcd', 'asas'),
(2, 'ajay', 'shinde'),
(3, 'kapil', 'parab');
But I have to join activity log on one condition. That condition is based on os column in activity_log table. That condition is if os column contain value as web then I have to join user_id of activity_log with id column of staff table and if os column contain value as ios or android then I have to join user_id of activity_log with id column of user table.
I need two different queries one for getting data with columns id and api from activity_log, first_name, last_name from either staff or user and another for getting total count for same above condition. I have given my table schema for reference.
I have tried following query for getting data
SELECT
al.id as id,
CONCAT(u.first_name, ' ', u.last_name) as user_name,
al.api
FROM
activity_log al
JOIN
user u
ON
u.id = al.user_id
WHERE
al.os IN('android','os')
UNION ALL
SELECT
al.id as id,
CONCAT(st.first_name, ' ', st.last_name) as user_name,
al.api
FROM
activity_log al
JOIN
staff st
ON
st.id = al.user_id
WHERE
al.os = 'web'
This above query returns me right data. But I dont know how to get count with above condition. Thats where I stuck in this. If possible please give me alternate queries for getting data and count.
Please help me in this. I spent whole day to figure out this. Thanks in advance
You can try using UNION ALL to get the required data.
Query 1:
select al.id, al.api,s.first_name, s.last_name from activity_log al inner join staff s on al.user_id=s.id where al.os='web'
UNION ALL
select al.id, al.api,u.first_name, u.last_name from activity_log al inner join user u on al.user_id=u.id where al.os!='web'
Query 2:
select
(select count(*) from activity_log al, staff s where al.user_id=s.id and al.os='web') as webcount,
(select count(*) from activity_log al, user u where al.user_id=u.id and al.os!='web') as othercount
I hope this gives you a direction for your question.
Try with
SELECT ...,count(*) FROM table1
INNER JOIN table2 ON ... WHERE ...

JOIN LEFT with multiple conditions

I'm having following tables structure
CREATE TABLE IF NOT EXISTS `review_author` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`email` varchar(255) NOT NULL,
`client_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `IDX_37D99F0819EB6921` (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=2110 ;
AND
CREATE TABLE IF NOT EXISTS `brokers_comments` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`hb_broker_id` int(11) NOT NULL,
`client_id` int(11) DEFAULT NULL,
`user_name` varchar(100) NOT NULL,
`user_email` varchar(100) NOT NULL,
`state` int(11) NOT NULL DEFAULT '0',
`text` varchar(3000) NOT NULL,
PRIMARY KEY (`id`),
KEY `IDX_5365DFFB9FE55EF7` (`hb_broker_id`),
KEY `IDX_5365DFFB19EB6921` (`client_id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1583 ;
Before extracting value i did following query:
INSERT INTO review_author (
name,
email,
client_id
)
SELECT
brokers_comments.user_name,
brokers_comments.user_email,
brokers_comments.client_id
FROM brokers_comments
LEFT JOIN review_author
ON brokers_comments.user_name=review_author.name AND
brokers_comments.user_email=review_author.email AND
brokers_comments.client_id=review_author.client_id
WHERE review_author.id IS NULL
Not in review_author should be all author from table brokers_comments and now i'm trying to get authors id using following query:
SELECT
review_author.id
FROM brokers_comments
LEFT JOIN review_author
ON brokers_comments.user_name=review_author.name AND
brokers_comments.user_email=review_author.email AND
brokers_comments.client_id=review_author.client_id
WHERE review_author.id IS NOT NULL
but i'm getting about 110 results from total 1531 records from table brokers_comments.
UPDATE
I couldn't manage to insert data in http://sqlfiddle.com/ so following link are dump for two tables review_author and brokers_comments.
Again my issue is to transfer distinct columns(user_name, user_email, client_id) from table brokers_comments to table review_author and then select review_author.id based on relation name/email/client_id from both tables.
http://wrttn.in/7ca325
http://wrttn.in/3a7885
Insert new author was wrong and made duplication. Below is new correct form.
INSERT INTO review_author (
name,
email,
client_id
)
SELECT user_name, user_email, client_id
FROM brokers_comments AS broker
WHERE NOT EXISTS
(
SELECT 1
FROM review_author AS author
WHERE author.email = broker.user_email
)
GROUP BY broker.user_email
P.S. I somebody will make a working online mysql database please put in comments so i could put it there.
Resolved
Only now i realised that user_email must be unique. Based on this i made following select statement:
SELECT
author.id
FROM brokers_comments AS broker
LEFT JOIN review_author AS author
ON broker.user_email = author.email
It seems you use excess fields in JOIN clause since client_id is a key, you need to join tables only on this field. Possible cause of that you getting not same number of records is different name/email for same client_id in those two tables. So, your two queries should be like this:
INSERT INTO review_author (
name,
email,
client_id
)
SELECT
brokers_comments.user_name,
brokers_comments.user_email,
brokers_comments.client_id
FROM brokers_comments
LEFT JOIN review_author
ON brokers_comments.client_id=review_author.client_id
WHERE review_author.id IS NULL
and
SELECT
review_author.id
FROM brokers_comments
LEFT JOIN review_author
ON brokers_comments.client_id=review_author.client_id
WHERE review_author.id IS NOT NULL

EXPLAIN SELECT.., why TYPE = ALL?

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.

Mysql query to get detail of comma-separated ids data

I have 2 tables, items and members :
CREATE TABLE IF NOT EXISTS `items` (
`id` int(5) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`member` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE IF NOT EXISTS `members` (
`id` int(5) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
What if, for example I have a record inside items, such as
INSERT INTO `test`.`items` (
`id` ,
`name` ,
`member`
)
VALUES (
NULL , 'xxxx', '1, 2, 3'
);
in members :
INSERT INTO `members` (`id`, `name`) VALUES
(1, 'asdf'),
(2, 'qwert'),
(3, 'uiop'),
(4, 'jkl;');
and I'd like to display items.member data with members.name, something like 1#asdf, 2#qwert, 3#uiop??
I've tried the following query,
SELECT items.id, items.name, GROUP_CONCAT(CONCAT_WS('#', members.id, members.name) ) as member
FROM `items`
LEFT JOIN members AS members on (members.id = items.member)
WHERE items.id = 1
But the result is not like I expected. Is there any other way to display the data via one call query? Because I'm using PHP, right now, i'm explode items.member and loop it one by one, to display the members.name.
You could look into using FIND_IN_SET() in your join criteria:
FROM items JOIN members ON FIND_IN_SET(members.id, items.member)
However, note from the definition of FIND_IN_SET():
A string list is a string composed of substrings separated by “,” characters.
Therefore the items.member column should not contain any spaces (I suppose you could use FIND_IN_SET(members.id, REPLACE(items.member, ' ', '')) - but this is going to be extremely costly as your database grows).
Really, you should normalise your schema:
CREATE TABLE memberItems (
item_id INT(5) NOT NULL,
member_id INT(5) NOT NULL,
FOREIGN KEY item_id REFERENCES items (id),
FOREIGN KEY member_id REFERENCES members (id)
);
INSERT INTO memberItems
(item_id, member_id)
SELECT items.id, members.id
FROM items
JOIN members ON FIND_IN_SET(members.id, REPLACE(items.member,' ',''))
;
ALTER TABLE items DROP member;
This is both index-friendly (and therefore can be queried very efficiently) and has the database enforce referential integrity.
Then you can do:
FROM items JOIN memberItems ON memberItems.item_id = items.id
JOIN members ON members.id = memberItems.member_id
Note also that it's generally unwise to use GROUP_CONCAT() to combine separate records into a string in this fashion: your application should instead be prepared to loop over the resultset to fetch each member.
Please take a look at this sample:
SQLFIDDLE
Your query seems to work for what you have mentioned in the question... :)
SELECT I.ID, I.ITEM,
GROUP_CONCAT(CONCAT("#",M.ID,
M.NAME, " ")) AS MEMB
FROM ITEMS AS I
LEFT JOIN MEMBERS AS M
ON M.ID = I.MID
WHERE i.id = 1
;
EDITTED ANSWER
This query will not work for you¬ as your schema doesn't seem to have any integrity... or proper references. Plus your memeber IDs are delimtted by a comma, which has been neglected in this answer.

Get all items from table while joining with a second table in a single query

I got two tables:
CREATE TABLE IF NOT EXISTS `groups2rights` (
`groups2rights_group_id` int(11) NOT NULL default '0',
`groups2rights_right` int(11) NOT NULL default '0',
PRIMARY KEY (`groups2rights_group_id`,`groups2rights_right`),
KEY `groups2rights_right` (`groups2rights_right`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `groups2rights` (`groups2rights_group_id`, `groups2rights_right`) VALUES (1, 35);
CREATE TABLE IF NOT EXISTS `rights` (
`right` int(11) NOT NULL auto_increment,
`right_name` varchar(255) default NULL,
`description` text NOT NULL,
`category` int(11) NOT NULL default '0',
PRIMARY KEY (`right`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=36 ;
INSERT INTO `rights` (`right`, `right_name`, `description`, `category`) VALUES
(33, 'admin_right_group_add', '', 100),
(34, 'admin_right_group_edit', '', 0),
(35, 'admin_right_group_delete', '', 0);
ALTER TABLE `groups2rights` ADD CONSTRAINT `groups2rights_ibfk_4` FOREIGN KEY (`groups2rights_right`) REFERENCES `rights` (`right`) ON DELETE CASCADE;
Now I tried to select all available Rights and also get if the group has it assigned, but somehow I'm missing some of the rights. Query:
SELECT r.*,g2r.groups2rights_group_id
FROM rights AS r
LEFT JOIN groups2rights AS g2r ON (g2r.groups2rights_right=r.right)
WHERE g2r.groups2rights_group_id=<<ID>> OR g2r.groups2rights_group_id IS NULL
ORDER BY r.category,r.right_name ASC
Any ideas?
Edit:
Updated the Code.
Expected Result be 3 Rows with 2 of them Havin a Null field and one having a value set.
If you do
SELECT r.*,g2r.group_id
FROM rights AS r
LEFT JOIN groups2rights AS g2r ON (g2r.right=r.right)
WHERE g2r.group_id=<<#id>> OR g2r.group_id IS NULL
ORDER BY r.category,r.right_name ASC
You will not gets rows where g2r.group_id <> null and also g2r.group_id <> <<#id>>
If you want to get all rows in rights and some of the rows in groups2rights you should do:
SELECT r.*,g2r.group_id
FROM rights AS r
LEFT JOIN (SELECT * FROM groups2rights WHERE group_id=<<#id>>) AS g2r
ON (g2r.right=r.right)
ORDER BY r.category,r.right_name ASC
This should work.
So you want to return all results found in the right table? In this case you should be using a RIGHT JOIN. This will return all results from the right table regardless of it matching the left table.
http://www.w3schools.com/sql/sql_join_right.asp