I'm trying to figure out how to use UNION with the same JOIN and not to fall into #2014 - Commands out of sync.
I have created four simple tables and wrote simplified code for them. The main idea is to get each sword, gun and hat which have 'best' in name + if the owner of these is cool.
SELECT pirate_id, sword, null AS gun, null AS hat, legs = 1 AS is_cool FROM swords as t1
LEFT JOIN
(SELECT legs, pirate_id as pid FROM body_parts) AS t2
ON t1.pirate_id = t2.pid
WHERE sword LIKE '%best%'
UNION
SELECT pirate_id, null AS sword, gun, null AS hat, legs = 1 AS is_cool FROM guns as t3
LEFT JOIN
(SELECT legs, pirate_id as pid FROM body_parts) AS t4
ON t3.pirate_id = t4.pid
WHERE gun LIKE '%best%'
UNION
SELECT pirate_id, null AS sword, null AS gun, hat, legs = 1 AS is_cool FROM hats as t5
LEFT JOIN
(SELECT legs, pirate_id as pid FROM body_parts) AS t6
ON t5.pirate_id = t6.pid
WHERE hat LIKE '%best%'
ORDER BY is_cool DESC, pirate_id ASC
Currently I have to repeat JOIN each time I have to UNION. I hope there is a better way...
TABLES CREATION:
CREATE TABLE IF NOT EXISTS `body_parts` (
`pirate_id` int(11) NOT NULL,
`legs` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `body_parts` (`pirate_id`, `legs`) VALUES
(1, 0),
(2, 1),
(3, 2);
CREATE TABLE IF NOT EXISTS `guns` (
`pirate_id` int(11) NOT NULL,
`gun` varchar(100) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `guns` (`pirate_id`, `gun`) VALUES
(1, 'best 1');
CREATE TABLE IF NOT EXISTS `hats` (
`pirate_id` int(11) NOT NULL,
`hat` varchar(100) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `hats` (`pirate_id`, `hat`) VALUES
(2, 'best 2');
CREATE TABLE IF NOT EXISTS `swords` (
`pirate_id` int(11) NOT NULL,
`sword` varchar(100) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `swords` (`pirate_id`, `sword`) VALUES
(3, 'best 3');
You could use a single left join changing the using a subquery
select t1.pirate_id, t1.sword, t1.gun, t1.hat, t2.legs=1 is_cool
from (
SELECT pirate_id, sword, null AS gun, null AS hat
FROM swords
WHERE sword LIKE '%best%'
UNION
SELECT pirate_id, null, gun, null
FROM guns
WHERE gun LIKE '%best%'
UNION
SELECT pirate_id, null, null, hat
FROM hats
WHERE hat LIKE '%best%'
) t1
LEFT JOIN (
SELECT legs, pirate_id
FROM body_parts
) t2 ON t2.pirate_id = t1.pirate_id
ORDER BY is_cool DESC, pirate_id ASC
Related
I use MySQL.
I have three tables:
Book, Publisher, Description.
In those tables I have 2 books, 3 publishers and 1 description from publisher #1
Description contains three fields book_id, publisher_id, description
I would like to select all records where description has been provided by the publisher or NULLs otherwise (if description was not provided by the publisher);
In other words I need the following output:
book_id
publisher_id
description
1
1
blah
1
2
NULL
1
3
NULL
2
1
NULL
2
2
NULL
2
3
NULL
Below is SQL I’m using:
CREATE TABLE `book` (
`id` int(11) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
CREATE TABLE `publisher` (
`id` int(11) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
CREATE TABLE `description` (
`book_id` int(11) NOT NULL,
`publisher_id` int(11) NOT NULL,
`description` varchar(256) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
INSERT INTO `book` (`id`) VALUES (1), (2);
INSERT INTO `publisher` (`id`) VALUES (1), (2), (3);
INSERT INTO `description` (`book_id`, `publisher_id`, `description`) VALUES ('1', '1', 'blah');
The SQL I'm using looks like this but it doesn't return the results I need:
SELECT b.id book_id, p.id publisher_id, d.description FROM description d
RIGHT JOIN book b ON d.book_id=b.id
RIGHT JOIN publisher p ON publisher_id=p.id
Your help will be greatly appriciated
It sounds like you want a cross join between books and publishers and then to left join the description:
SELECT b.id book_id, p.id as publisher_id, d.description
FROM book b CROSS JOIN
publisher p LEFT JOIN
description d
ON d.book_id = b.id AND d.publisher_id = p.id
ORDER BY b.id, p.id;
Here is a db<>fiddle.
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
I'm using MySQL 5.5. with two tables in it:
DROP TABLE IF EXISTS `events_dictionary`;
CREATE TABLE `events_dictionary` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(64) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `events_dictionary` VALUES (1, 'Light'),(2, 'Switch'),(3, 'on'),(4, 'off');
DROP TABLE IF EXISTS `events_log`;
CREATE TABLE `events_log` (
`log_id` bigint(20) NOT NULL AUTO_INCREMENT,
`event_name_id` int(11) NOT NULL DEFAULT '0',
`event_param1` int(11) DEFAULT NULL,
`event_value1` int(11) DEFAULT NULL,
PRIMARY KEY (`log_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `events_log` VALUES (1, 1, 2, 3),(2, 1, 2, 4);
Table events_dictionary contains names for events_log events names,params and values.
So, my question is - how could i select data from event_log table with columns event_name_id, event_param1, event_value1 mapped to name values from events_dictionary table?
I tried to do this query:
SELECT name, event_param1, event_value1
FROM events_log
JOIN events_dictionary ON events_log.event_name_id = events_dictionary.id;
But, in this case i see only event_name_id replaced with values from events_dictionary, like this:
name | event_param1 | event_value1
Light | 1 | 1
Light | 1 | 2
And i want to replace event_param1, and event_value1 with names from events_dictionary too.
Thanks in advance!
You need to join to the events_dictionary multiple times
SELECT a.name, b.name, c.name
FROM events_log
JOIN events_dictionary a ON events_log.event_name_id = a.id
JOIN events_dictionary b ON events_log.event_param1 = b.id
JOIN events_dictionary c ON events_log.event_value1 = c.id;
PS
Your example for the event_log isn't that helpful , instead insert the values (1,1,2,3),(2,1,2,4) to turn the switch on and off for the light.
DS
You can use correlated subqueries:
SELECT name,
(SELECT t.name
FROM events_dictionary AS t
WHERE t.id = event_param1) AS param_name,
(SELECT t2.name
FROM events_dictionary AS t2
WHERE t2.id = event_value1) AS event_name
FROM events_log AS el
JOIN events_dictionary AS ed ON el.event_name_id = ed.id;
Demo here
CREATE TABLE `reviews` (
`id` int(11) NOT NULL,
`average` decimal(11,2) NOT NULL,
`house_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `reviews` (`id`, `average`, `house_id`) VALUES
(1, '10.00', 1),
(2, '10.00', 1);
ALTER TABLE `reviews`
ADD PRIMARY KEY (`id`);
ALTER TABLE `reviews`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;
CREATE TABLE `dummy_reviews` (
`id` int(11) NOT NULL,
`average` decimal(11,2) NOT NULL,
`house_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `dummy_reviews` (`id`, `average`, `house_id`) VALUES
(0, '2.00', 1);
ALTER TABLE `dummy_reviews`
ADD PRIMARY KEY (`id`);
AND the query
SELECT
AVG(r.average) AS avg1,
AVG(dr.average) AS avg2
FROM
reviews r
LEFT JOIN
dummy_reviews dr ON r.house_id = dr.house_id
the result is
avg1 avg2
10.000000 2.000000
All good by now but (10 + 2) / 2 = 6 ... wrong result
I need (10+10+2) / 3 = 7,33 ... How can I get this result?
SQLFiddle
You have values joined and as such you wont have 3 rows, you will have 2. What you need is a union so you can have all rows from your average tables and do the calculation from it. Like this:
select avg(average) from
(select average from reviews
union all
select average from dummy_reviews
) queries
See it here: http://sqlfiddle.com/#!9/e0b75f/3
Jorge's answer is the simplest approach (and I duly upvoted it). In response to your comment, you can do the following:
select ( (coalesce(r.suma, 0) + coalesce(d.suma, 0)) /
(coalesce(r.cnt, 0) + coalesce(d.cnt, 0))
) as overall_average
from (select sum(average) as suma, count(*) as cnt
from reviews
) r cross join
(select sum(average) as suma, count(*) as cnt
from dummy_reviews
) d;
Actually, I suggest this not only because of your comment. Under some circumstances, this could be the better performing code.
I am returning a array of user created statuses in a feed. I have realized that looping through the mysql results in php post mysql to add on additional key/values ridiculously slows down our api response times so im trying to eliminate any foreach looping in php on this array of statuses.
here is my table setup:
USERS TABLE
UID / FIRST_NAME/ LAST_NAME/ECT
UPDATES TABLE
msg_id/message/ UID_FK
LIKED TABLE
LIKED_ID/ MSG_ID_FK/ LIKED_BY_UID
Is there a way that in mysql i can include a "isLiked" bool if the user making the api request has liked the update on each row returned in my query?
Here is my actual query im working with, it has a bit more data than i described in it but the goal is the same....find a way to add a 'isLiked' column to each row:
SELECT b.type,b.owner,b.update_img,b.ALBUM_ID,b.last_comment,a.uid, a.first_name, a.last_name, a.gender, a.thumb_img, b.msg_id, b.message, b.created,b.POST_PRIVACY
FROM users AS a, updates AS b, LIKED as c WHERE b.uid_fk = a.uid AND b.type<>'FRIEND_RELATIONSHIP'AND b.created<$time AND b.type<>'FAMILIAR_RELATIONSHIP' AND a.college='$college' AND b.POST_PRIVACY<>'4' AND b.POST_PRIVACY<>'5' AND b.created>=$tstamp ORDER BY b.created DESC
LIMIT 100
EDIT:
Here is a version of the query where I attempt to do what im aiming for ,but the problem is this ONLY returns the rows that I have liked:
SELECT b.type, b.owner, b.update_img, b.ALBUM_ID, b.last_comment, a.uid, a.first_name, a.last_name, a.gender, a.thumb_img, b.msg_id, b.message, b.created, b.POST_PRIVACY,c.LIKED
FROM users AS a, updates AS b, LIKES AS c
WHERE b.uid_fk = a.uid
AND b.POST_PRIVACY <> '4'
AND b.POST_PRIVACY <> '5'
AND c.LIKED_UID = '1'
AND b.msg_id = c.MSG_ID_FK
ORDER BY b.created DESC
LIMIT 100
If you are getting only the line with liked but want all the lines, try this:
SELECT b.type, b.owner, b.update_img, b.ALBUM_ID, b.last_comment, a.uid, a.first_name, a.last_name, a.gender, a.thumb_img, b.msg_id, b.message, b.created, b.POST_PRIVACY,c.LIKED
FROM users AS a, updates AS b, BUMPS AS c
WHERE b.uid_fk = a.uid
AND b.POST_PRIVACY <> '4'
AND b.POST_PRIVACY <> '5'
AND ((c.LIKED_UID = '1'
AND b.msg_id = c.MSG_ID_FK)
OR c.MSG_ID_FK IS NULL
)
ORDER BY b.created DESC
LIMIT 100
Added null test so that it is equivalent to a LEFT JOIN.
The column c.LIKED will be 1 when there is a liked on that message id and null when it doesn't.
-- EDIT --
As the above didn't work (may be wrong, I didn't test it), try this form of writing it (I like this way):
SELECT b.type, b.owner, b.update_img, b.ALBUM_ID, b.last_comment, a.uid, a.first_name, a.last_name, a.gender, a.thumb_img, b.msg_id, b.message, b.created, b.POST_PRIVACY,c.LIKED
FROM
users AS a
INNER JOIN updates AS b ON b.uid_fk = a.uid
LEFT JOIN BUMPS AS c ON b.msg_id = c.MSG_ID_FK
WHERE b.POST_PRIVACY <> '4'
AND b.POST_PRIVACY <> '5'
ORDER BY b.created DESC
LIMIT 100
Note that it will only select lines that are related on users and updates, and values from table BUMPS will be selected as NULL if no related line exists. This is what I recommend.
(deleted informations that could cause error and lead to confusion)
-- EDIT 2 added a test-case with simplified data and query --
I didn't see the entire problem before, then I did a test-case.
I created test tables and data:
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `test_multi_sets`()
DETERMINISTIC
begin
select user() as first_col;
select user() as first_col, now() as second_col;
select user() as first_col, now() as second_col, now() as third_col;
end$$
DELIMITER ;
CREATE TABLE IF NOT EXISTS `BUMPS` (
`id` int(11) NOT NULL,
`MSG_ID_FK` int(11) NOT NULL,
`LIKED_UID` int(11) NOT NULL,
`LIKED` tinyint(4) NOT NULL DEFAULT '0'
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;
INSERT INTO `BUMPS` (`id`, `MSG_ID_FK`, `LIKED_UID`, `LIKED`) VALUES
(1, 1, 1, 1),
(2, 3, 2, 0),
(3, 1, 2, 0);
CREATE TABLE IF NOT EXISTS `updates` (
`msg_id` int(11) NOT NULL,
`uid_fk` int(11) NOT NULL,
`text` varchar(20) NOT NULL,
`POST_PRIVACY` tinyint(4) NOT NULL DEFAULT '0'
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1;
INSERT INTO `updates` (`msg_id`, `uid_fk`, `text`, `POST_PRIVACY`) VALUES
(1, 1, 't11', 3),
(2, 1, 't12', 3),
(3, 2, 't21', 3),
(4, 2, 't22', 3);
CREATE TABLE IF NOT EXISTS `users` (
`uid` int(11) NOT NULL,
`name` varchar(20) NOT NULL,
`pass` varchar(20) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1;
INSERT INTO `users` (`uid`, `name`, `pass`) VALUES
(1, 'u1', 'p1'),
(2, 'u2', 'p2');
ALTER TABLE `BUMPS`
ADD PRIMARY KEY (`id`), ADD KEY `MSG_ID_FK` (`MSG_ID_FK`), ADD KEY `LIKED_UID` (`LIKED_UID`);
ALTER TABLE `updates`
ADD PRIMARY KEY (`msg_id`), ADD KEY `uid_fk` (`uid_fk`);
ALTER TABLE `users`
ADD PRIMARY KEY (`uid`);
ALTER TABLE `BUMPS`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=4;
ALTER TABLE `updates`
MODIFY `msg_id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=5;
ALTER TABLE `users`
MODIFY `uid` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=3;
ALTER TABLE `BUMPS`
ADD CONSTRAINT `bumps_ibfk_1` FOREIGN KEY (`LIKED_UID`) REFERENCES `users` (`uid`),
ADD CONSTRAINT `bumps_ibfk_2` FOREIGN KEY (`MSG_ID_FK`) REFERENCES `updates` (`msg_id`);
ALTER TABLE `updates`
ADD CONSTRAINT `updates_ibfk_1` FOREIGN KEY (`uid_fk`) REFERENCES `users` (`uid`);
and run this select:
SELECT a.uid, a.name, b.msg_id, b.text, b.POST_PRIVACY, c.LIKED
FROM users AS a
INNER JOIN updates AS b ON b.uid_fk = a.uid
LEFT JOIN BUMPS AS c ON (c.LIKED_UID = 1 AND b.msg_id = c.MSG_ID_FK)
WHERE NOT (b.POST_PRIVACY IN(4, 5)) LIMIT 100
with these results:
| uid | name | msg_id | text | POST_PRIVACY | LIKED
| 1 | u1 | 1 | t11 | 3 | 1
| 1 | u1 | 2 | t12 | 3 | NULL
| 2 | u2 | 3 | t21 | 3 | NULL
| 2 | u2 | 4 | t22 | 3 | NULL
Even that user 2 liked updates, it won't show on user1 query.
I hope this helps you to solve your problem. I believe that I can't go any further than this, but anyway, send a message if needed.
Caution if using this on your production server, as first scrip on the EDIT 2 will create tables.