My question in a SQL Fiddle.
How can I join all elements in table A with all elements in table B via a many-to-many mapping table, even if no relationship exists? My LEFT JOIN solution isn't giving the results I expect.
Details:
Given these tables:
CREATE TABLE `user` (
id INT PRIMARY KEY NOT NULL,
email VARCHAR(100) NOT NULL
);
CREATE TABLE `event` (
id INT NOT NULL,
start_time DATETIME NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE `event_response` (
id INT PRIMARY KEY NOT NULL,
user_id INT NOT NULL,
event_id INT NOT NULL,
response VARCHAR(5) NOT NULL,
FOREIGN KEY (user_id)
REFERENCES `user`(id)
ON DELETE CASCADE,
FOREIGN KEY (event_id)
REFERENCES `event`(id)
ON DELETE CASCADE
);
And this seed data:
-- create some users
INSERT INTO `user`(`id`, email)
VALUES
(1, 'abc1#gmail.com'),
(2, 'abc2#gmail.com'),
(3, 'abc3#gmail.com');
-- create two events
INSERT INTO `event`(`id`, start_time)
VALUES
(1, '2020-09-01'),
(2, '2020-10-01');
-- Only 3 users have responded to the events
INSERT INTO `event_response`(`id`, user_id, event_id, response)
VALUES
(1, 1, 1, 'yes'),
(2, 2, 1, 'no'),
(3, 3, 2, 'yes');
I need a report like this:
start_time, email, response
"2020-09-01", abc1#gmail.com, yes
"2020-09-01", abc2#gmail.com, no
"2020-09-01", abc3#gmail.com, <NO RESPONSE>
"2020-10-01", abc1#gmail.com, <NO RESPONSE>
"2020-10-01", abc2#gmail.com, <NO RESPONSE>
"2020-10-01", abc3#gmail.com, yes
The query I have tried (but doesn't give satisfactory results):
SELECT
e.start_time,
u.email,
COALESCE(er.response, '<NO RESPONSE>') AS response
FROM `user` AS u
LEFT JOIN event_response AS er ON u.id = er.user_id
LEFT JOIN `event` AS e ON er.event_id = e.id
ORDER BY e.start_time ASC;
Use a cross join to generate the rows and left join to bring in the values:
select e.*, u.*, er.response
from event e cross join
user u left join
event_response er
on er.user_id = u.id and er.event_id = e.id;
Related
I would like to fetch all users.myKey from the table committee if the id is in the table.
CREATE TABLE users(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255),
myKey VARCHAR(100)
);
INSERT INTO users (name, myKey)
VALUES ("Gregor", "kx4ht"),
("Liza", "1lPxk"),
("Matt", "mP3fd"),
("Bob", "zStr5");
CREATE TABLE committee(
user_id INT,
friend_id INT,
member_id INT,
PRIMARY KEY (`user_id`, `friend_id`, `member_id`),
FOREIGN KEY (`user_id`) REFERENCES `users` (`id`),
FOREIGN KEY (`friend_id`) REFERENCES `users` (`id`),
FOREIGN KEY (`member_id`) REFERENCES `users` (`id`)
);
INSERT INTO committee (user_id, friend_id, member_id)
VALUES (4, 1, 3),
(1, 2, 3);
What i got now:
SELECT u.myKey FROM users u INNER JOIN committee c ON (c.user_id = u.id || c.friend_id = u.id || c.member_id = u.id) WHERE u.id = 2 GROUP BY u.id;
Result now:
I get only my own myKey user_id2
What i expect:
I want only get the myKeys for the others inside committee. E.g if i user 2 want get the myKeys from table committee where i can find my user id 2
In this case it should return the myKey for user1 and user3
As far as I understand your problem, you want to find a specific id in the "committee" table, find the id's that are next to the specified id and then find those neighboring ids in the "users" table and show their keys.
This is what I have come up with:
SELECT u.id, u.myKey
FROM users u
LEFT JOIN (SELECT IF (tmp.user_id = 2, NULL, tmp.user_id) AS user_id,
IF(tmp.friend_id = 2, NULL, tmp.friend_id) AS friend_id,
IF(tmp.member_id = 2, NULL, tmp.member_id) AS member_id
FROM (SELECT *
FROM committee
WHERE user_id = 2 OR friend_id = 2 OR member_id = 2) AS tmp) AS id_table
ON u.id = id_table.user_id OR u.id = id_table.friend_id OR u.id = id_table.member_id
WHERE user_id IS NOT NULL OR friend_id IS NOT NULL OR member_id IS NOT NULL;
Note that I am searching for the user with the id 2, as you specified.
The result of this query, as you said you would have expected it:
id | myKey
1 kx4ht
3 mP3fd
I'm checking if 2 foreign ids of a table are equal to 2 foreign keys in another table to the same table, but I don't care about the order of the ids, just that they have the same values.
i.e.
SELECT (1, 2, 3) = (1, 2, 3);
> 1
SELECT (1, 2, 3) = (2, 1, 3);
> 0
I'd like a way so that (1,2,3) matches (2,1,3) as well as (1,3,2) and (2,3,1).
Unfortunately searching for information on this has proved difficult, most advice is "Lists don't exist in MySQL" and searches for sorting, or unordered checking result in various SQL calls that aren't relevant.
Fiddle: https://www.db-fiddle.com/f/eqz27tR9uDMQriDhkwBo2a/0
I deliberately put an event in the table with the participants ordered differently to the participants in not_event, and it's that join,
SELECT * FROM event
JOIN not_event ON (
(event.participant_1_id, event.participant_2_id) =
(not_event.participant_1_id, not_event.participant_2_id));
That's the issue. I don't care what order participant_1_id and participant_2_id are, in either table, so long as they're the same 2.
Rest of code from fiddle,
CREATE TABLE `participant` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`key` varchar(15) NOT NULL,
PRIMARY KEY (`id`));
CREATE TABLE `event` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`participant_1_id` int(11) NOT NULL,
`participant_2_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `event_ibfk_1` FOREIGN KEY (`participant_1_id`) REFERENCES `participant` (`id`),
CONSTRAINT `event_ibfk_2` FOREIGN KEY (`participant_2_id`) REFERENCES `participant` (`id`)
);
CREATE TABLE `not_event` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`participant_1_id` int(11) NOT NULL,
`participant_2_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `not_event_ibfk_1` FOREIGN KEY (`participant_1_id`) REFERENCES `participant` (`id`),
CONSTRAINT `not_event_ibfk_2` FOREIGN KEY (`participant_2_id`) REFERENCES `participant` (`id`)
);
INSERT INTO `participant` VALUES (1, 'Team1');
INSERT INTO `participant` VALUES (2, 'Team2');
INSERT INTO `event` VALUES (NULL, 1, 2);
INSERT INTO `not_event` VALUES (NULL, 2, 1);
SELECT (1, 2, 3) = (1, 2, 3);
SELECT (1, 2, 3) = (2, 1, 3);
SELECT * FROM event
JOIN not_event ON (
(event.participant_1_id, event.participant_2_id) =
(not_event.participant_1_id, not_event.participant_2_id));
SELECT * FROM event
JOIN not_event ON (
(event.participant_1_id, event.participant_2_id) =
(not_event.participant_2_id, not_event.participant_1_id));
A few options, none I'm really happy with,
For binary joins, with only 2 fields, using LEAST and GREATEST works, but on more than 2 fields, it wouldn't work,
SELECT * FROM event
JOIN not_event ON (
LEAST(event.participant_1_id, event.participant_2_id) =
LEAST(not_event.participant_1_id, not_event.participant_2_id)
AND
GREATEST(event.participant_1_id, event.participant_2_id) =
GREATEST(not_event.participant_1_id, not_event.participant_2_id));
After that, there seems to be a dreadfully inefficient LENGTH check, with multiple REPLACEs with CONCAT_WS,
SELECT * FROM event
JOIN not_event ON (
1 = LENGTH(REPLACE(REPLACE(
CONCAT_WS(
',', event.participant_1_id, event.participant_2_id),
not_event.participant_1_id, ''), not_event.participant_2_id, ''))
);
But this one sucks, and is unreliable, because "1" would replace "11" with "", "2" replaces "222" with "", etc.
Updated fiddle (with these solutions): https://www.db-fiddle.com/f/eqz27tR9uDMQriDhkwBo2a/1
I've found you can achieve this via 3 different methods. 1 is querying the event and not_event tables, and joining the participant table twice, grouping them together, and running a GROUP BY with HAVING comparing GROUP_CONCATs,
SELECT event.*, not_event.*
FROM event
JOIN participant p1 ON p1.id IN (event.participant_1_id, event.participant_2_id),
not_event
JOIN participant p2 ON p2.id IN (not_event.participant_1_id, not_event.participant_2_id)
GROUP BY event.id, not_event.id
HAVING
GROUP_CONCAT(p1.key ORDER BY p1.key) =
GROUP_CONCAT(p2.key ORDER BY p2.key)
Or by running 2 subqueries that do GROUP_CONCAT on the field that you're interested in joining them on, and then join them afterwards,
SELECT *
FROM (
SELECT GROUP_CONCAT(participant.id ORDER BY participant.id) `key`, event.*
FROM event
JOIN participant ON (participant.id IN (event.participant_1_id, event.participant_2_id))
GROUP BY event.id) _event
JOIN (
SELECT GROUP_CONCAT(participant.id ORDER BY participant.id) `key`, not_event.*
FROM not_event
JOIN participant ON (participant.id IN (not_event.participant_1_id, not_event.participant_2_id))
GROUP BY not_event.id) _not_event
ON _event.key = _not_event.key;
And then the super "direct" or manual way,
SELECT event.*, not_event.*
FROM event, not_event
WHERE
event.participant_1_id IN (not_event.participant_1_id, not_event.participant_2_id) AND
event.participant_2_id IN (not_event.participant_1_id, not_event.participant_2_id) AND
not_event.participant_1_id IN (event.participant_1_id, event.participant_2_id) AND
not_event.participant_2_id IN (event.participant_1_id, event.participant_2_id)
These all join them correctly as wanted.
Updated fiddle:
https://www.db-fiddle.com/f/eqz27tR9uDMQriDhkwBo2a/5
With both solutions, Event(1, 2) correctly joins with NotEvent(2, 1) and nothing else, and Event(2, 3) correctly joins with NotEvent(3, 2) and nothing else.
I still think it's crazy that you have to join a table like this, in this way, rather than just comparing the keys within the table directly, but these work, one way or another.
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 ...
I'm guessing this is too local but I can't figure out a way to make it more general (which might be why I'm not able to find my answer on Google).
We have an application that tracks contacts for our business. These contacts (Contact table) are either contacted through the phone (Contact_Phone table) or through email (Contact_Email). If the user is contacted through the phone an agent keeps track of the total number of seconds (Contact_Phone.totalSeconds). Through a piece of business logic that I have no control over email contacts are treated as one second. A user might be contact through just email, just phone, or both.
I'm trying to generate a report on how long we've spent contacting each user but I can't get the results I expect.
Tables:
CREATE TABLE IF NOT EXISTS `Contact` (
`id` INT NOT NULL AUTO_INCREMENT ,
`name` VARCHAR(45) NULL ,
PRIMARY KEY (`id`) )
ENGINE = InnoDB;
CREATE TABLE IF NOT EXISTS `Contact_Email` (
`id` INT NOT NULL AUTO_INCREMENT ,
`ContactId` INT NULL ,
PRIMARY KEY (`id`) ,
INDEX `contact_email_contact_idx` (`ContactId` ASC) ,
CONSTRAINT `contact_email_contact`
FOREIGN KEY (`ContactId` )
REFERENCES `Contact` (`id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
CREATE TABLE IF NOT EXISTS `Contact_Phone` (
`id` INT NOT NULL AUTO_INCREMENT ,
`totalSeconds` INT NULL ,
`ContactId` INT NULL ,
PRIMARY KEY (`id`) ,
INDEX `Contact_Phone_contact_idx` (`ContactId` ASC) ,
CONSTRAINT `Contact_Phone_contact`
FOREIGN KEY (`ContactId` )
REFERENCES `Contact` (`id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
Test Data:
insert into Contact (id, name) values (1, 'Scott');
insert into Contact (id, name) values (2, 'Joe');
insert into Contact_Phone (totalSeconds, ContactId) values (10, 2);
insert into Contact_Phone (totalSeconds, ContactId) values (100, 2);
insert into Contact_Email (ContactId) values (1);
insert into Contact_Email (ContactId) values (1);
insert into Contact_Email (ContactId) values (2);
Query:
select
name,
(select sum(totalSeconds) from Contact_Phone where Contact_Phone.ContactId = Contact.id)
+
(select count(*) from Contact_Email where Contact_Email.ContactId = Contact.id)
from Contact;
Expected Results:
Joe 111
Scott 2
Actual Results:
Joe 111
Scott null
Thanks
How about using summaries and LEFT JOIN operations, like so?
SELECT Contact.name,
COALESCE(p.seconds,0) + COALESCE(e.seconds,0) seconds
FROM Contact.Name
LEFT JOIN (
SELECT ContactID AS id,
SUM(totalSeconds) AS seconds
FROM ContactPhone
GROUP BY ContactID
) p ON Contact.id = p.id
LEFT JOIN (
SELECT ContactID AS id,
COUNT(*) AS seconds
FROM ContactEmail
GROUP BY ContactID
) e ON Contact.id = e.id
The LEFT JOIN operations will preserve your result rows where one or the other of your "seconds" computations is NULL. And, the COALESCE operations will prevent your query from attempting arithmetic on NULL values, which yields NULL.
I have the following MySQL structure (minimized a lot):
CREATE TABLE `site_movies` (
`id` int(10),
`title` varchar(90),
PRIMARY KEY (`id`)
) ENGINE=MyISAM;
INSERT INTO `site_movies` VALUES(1, 'Borrowers, The');
INSERT INTO `site_movies` VALUES(2, 'Louis C.K.: Chewed Up');
INSERT INTO `site_movies` VALUES(3, 'Louis C.K.: Shameless');
INSERT INTO `site_movies` VALUES(4, 'Vinni-Pukh');
CREATE TABLE `site_movies_directors` (
`id` mediumint(8),
`name` varchar(255),
PRIMARY KEY (`id`)
) ENGINE=MyISAM;
CREATE TABLE `site_movies_directors_connections` (
`movie_id` mediumint(8),
`director_id` mediumint(8)
) ENGINE=MyISAM;
CREATE TABLE `site_movies_seen` (
`object_id` int(10),
`date` varchar(10),
`rating` tinyint(2)
) ENGINE=MyISAM;
INSERT INTO `site_movies_seen` VALUES(1, '0', 4);
INSERT INTO `site_movies_seen` VALUES(2, '1293821757', 5);
INSERT INTO `site_movies_seen` VALUES(3, '1293821758', 7);
INSERT INTO `site_movies_seen` VALUES(4, '0', 6);
And then the following query (also minimized a lot):
SELECT m.title, s.date
FROM site_movies_seen s
INNER JOIN site_movies m ON s.object_id = m.id
LEFT JOIN site_movies_directors_connections AS mdc ON ( m.id = mdc.movie_id )
GROUP BY mdc.movie_id, s.date
ORDER BY s.date ASC
Prints:
title date
Borrowers, The 0
Louis C.K.: Chewed Up 1293821757
Louis C.K.: Shameless 1293821758
Notice that "Vinni-Pukh" is missing because it is the second entry in the _seen table with date = 0. How can I include all entires, even when several entires have the same timestamp?
Change your group by statement to this:
GROUP BY m.id, s.date
Your join condition says m.id = mdc.movie_id, so you might think that these two fields are always equal and therefore it doesn't matter whether you write m.id or mdc.movie_id in your GROUP BY. This is not true because you are using a LEFT JOIN, not an INNER JOIN. This means that mdc.movie_id can be NULL, and all NULL entries go into the same group.
Also, since you aren't selecting any columns from the site_movies_directors_connections you should omit it from the query completely.
SELECT m.title, s.date
FROM site_movies_seen s
INNER JOIN site_movies m ON s.object_id = m.id
GROUP BY m.id, s.date
ORDER BY s.date