LEFT JOIN table to find non matching rows, same table - mysql

I have a table that looks like this:
id int primary key
uniqueID string --not uniquely indexed
foreignKeyID int --foreignKey to another table
I want to find all the uniqueIds in this table that exist for foreign key 1 that do not exist for foreign key 2
I thought I could do something like this:
SELECT * FROM table t1
LEFT JOIN table t2
ON t1.uniqueID = t2.uniqueID
WHERE
t1.foreignKeyID = 1
AND t2.uniqueID IS NULL
However this is never giving me results. I can make it work with a NOT IN subquery but this is a very large table so I suspect a solution using joins will be faster.
Looking for the best way to structure this query.
Here's an sample data set and SQL Fiddle with an example of the working NOT IN query I am trying to convert to a LEFT JOIN:
CREATE TABLE `table` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`uniqueID` varchar(255),
`foreignKeyID` int(5) unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
INSERT INTO `table` (uniqueID, foreignKeyID) VALUES ('aaa', 1), ('bbb', 1);
http://sqlfiddle.com/#!9/48a3f3/4 and a non-working LEFT JOIN I thought would be equivalent.
Thanks!

Try this, seems to be working if understood the question properly:
SELECT *
FROM `table` t
LEFT JOIN `table` tt ON tt.uniqueID = t.uniqueID AND tt.foreignKeyID <> 1
WHERE t.foreignKeyID = 1 AND tt.id IS NULL;

Related

SQL - Left Join and Group By causes row data from second table to get mixed up

I have two tables, the first table has a reference to the id from the second table, I want to make a query involving a left join with fields from the second table as well as with a COUNT function in the select, because of the COUNT function, I am using an GROUP BY clause.
So my query looks something like:
SELECT t1.id, t1.txt, t2.id, t2.txt, COUNT(t2.id)
FROM test_data1 t1
LEFT JOIN test_data2 t2 ON (t1.ref_col = t2.id)
GROUP BY t1.id
In my tables, only the second row of test_data1 has an entry in ref_col, so I would expect that for the first row in the results, the value for t2.id would be NULL, however that is not the case (in my example I see the value 2, but I'm not sure if there might be an element of randomness here).
If I use
SELECT MAX(t1.id), MAX(t1.txt), MAX(t2.id), MAX(t2.txt), COUNT(t2.id)
FROM test_data1 t1
LEFT JOIN test_data2 t2 ON (t1.ref_col = t2.id)
GROUP BY t1.id
I get my expected results, however I am surprised this is necessary given that there will at most only be one entry in test_data2 matching ref_col in test_data1.
Does anyone know why LEFT JOIN + GROUP BY is behaving this way? This is using MySQL version 8 on Linux.
If you want to reproduce this here are the table definitions:
CREATE TABLE test_data1 (id int unsigned NOT NULL AUTO_INCREMENT,
txt VARCHAR(45) DEFAULT NULL,
ref_col int unsigned DEFAULT NULL, PRIMARY KEY (id));
CREATE TABLE test_data1 (id int unsigned NOT NULL AUTO_INCREMENT,
txt VARCHAR(45) DEFAULT NULL,
ref_col int unsigned DEFAULT NULL, PRIMARY KEY (id));
INSERT INTO test_data1 (id, txt, ref_col)
VALUES
(1,'zz',NULL),
(2,'yy',2),
(3,'xx',NULL);
INSERT INTO test_data2 (id, txt)
VALUES
(1,'aa'),
(2,'bb'),
(3,'cc'),
(4,'dd');

Is there anyway to insert only unique entries into a pivot table in MYSQL?

I have tried to use INSERT IGNORE but still I can see that when I runs the query again. It still inserts the records. I don't want to insert duplicate records in there.
Can anyone help me out?
INSERT INTO enquiry_location_status
(enquiry_id,
location_id,
enquiry_status)
SELECT we.id AS enquiry_id,
wl.location_id AS location_id,
we.status AS enquiry_status
FROM enquiry we
LEFT JOIN wishlist w
ON w.enquiry_id = we.id
LEFT JOIN wishlist_location wl
ON wl.wishlist_id = w.id
WHERE wl.wishlist_id IS NOT NULL;
My table definition:
CREATE TABLE `enquiry_location_status` (
`id` bigint(20) NOT NULL,
`enquiry_id` int(10) NOT NULL,
`location_id` int(11) NOT NULL,
`enquiry_status` varchar(30) NOT NULL
);
ALTER TABLE `enquiry_location_status`
ADD PRIMARY KEY (`id`);
ALTER TABLE `enquiry_location_status`
MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT;
Here is a screenshot how my pivot table data looks like:
I have tried this, it seems to be working but it's very slow. It's taking 80 seconds for 40 K+ records just.
INSERT INTO enquiry_location_status
(enquiry_id,
location_id,
enquiry_status)
SELECT we.id AS enquiry_id,
wl.location_id AS location_id,
we.status AS enquiry_status
FROM enquiry we
LEFT JOIN wishlist w
ON w.enquiry_id = we.id
LEFT JOIN wishlist_location wl
ON wl.wishlist_id = w.id
WHERE wl.wishlist_id IS NOT NULL
AND NOT EXISTS (SELECT els.enquiry_id,
els.location_id,
els.enquiry_status
FROM enquiry_location_status els
WHERE els.enquiry_id = we.id
AND els.location_id = wl.location_id
AND els.enquiry_status = we.status)
Thanks

In MySQL check if set of values is equal to the values of another set; don't care about order

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.

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.

Complicated SELECT query

I have a table which defines what things another table can have, for example:
CREATE TABLE `objects` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(50) NOT NULL
);
INSERT INTO `objects` (`name`) VALUES ('Test');
INSERT INTO `objects` (`name`) VALUES ('Test 2');
CREATE TABLE `properties` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(50) NOT NULL
);
INSERT INTO `properties` (`name`) VALUES ('colour');
INSERT INTO `properties` (`name`) VALUES ('size');
CREATE TABLE `objects_properties` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`object_id` INT UNSIGNED NOT NULL,
`property_id` INT UNSIGNED NOT NULL,
`value` VARCHAR(50) NOT NULL,
FOREIGN KEY (`object_id`)
REFERENCES `objects` (`id`),
FOREIGN KEY (`property_id`)
REFERENCES `properties` (`id`)
);
INSERT INTO `objects_properties` (`object_id`, `property_id`, `value`) VALUES 1, 1, 'red');
INSERT INTO `objects_properties` (`object_id`, `property_id`, `value`) VALUES 1, 2, 'small');
INSERT INTO `objects_properties` (`object_id`, `property_id`, `value`) VALUES 2, 1, 'blue');
INSERT INTO `objects_properties` (`object_id`, `property_id`, `value`) VALUES 2, 2, 'large');
Hopefully this makes sense. Basically instead of having columns for colour, size etc. in the objects table, I have two other tables, one that defines the properties any object can have, and another that links objects to some or all of these properties.
My question is if there's some way to retrieve this information like this:
+--------+------------+------------+
| object | colour | size |
+--------+------------+------------+
| Test | red | small |
| Test 2 | blue | large |
+--------+------------+------------+
So you can see the column headings are actually row values. I'm not sure if it's possible or how costly it would be compared to doing a few separate queries and putting everything together in PHP.
SELECT o.name, c.colour, s.size
FROM objects o
LEFT JOIN (SELECT op.object_id, op.value colour
FROM objects_properties op
join properties p on op.property_id = p.id and p.name = 'colour') c
ON o.id = c.object_id
LEFT JOIN (SELECT op.object_id, op.value size
FROM objects_properties op
join properties p on op.property_id = p.id and p.name = 'size') s
ON o.id = s.object_id
The keyword here is "pivot table" "crosstab" (but a "pivot table" lies also in that direction) and no, MySQL cannot do this directly. You can create a query that will select this, but you will have to explicitly define the columns yourself in the query. No fetching of columns from another table. Other RDBMS may have capabilities for this.
pivot (or something like that) could be useful. In MS SQL Server you can use it BUT the values to pivot the table must be constant or you can use a stored procedure to calculate it.
Here you can find more info.
Have a nice day!
SELECT o.*,
(
SELECT *
FROM object_properties op
WHERE op.object_id = o.object_id
AND op.property_id = $prop_color_id
) AS color,
(
SELECT *
FROM object_properties op
WHERE op.object_id = o.object_id
AND op.property_id = $prop_size_id
) AS size
FROM objects o
Substitute the $prop_color_id and $prop_size_id with the color and size property id's.
For this query to be efficient, make (object_id, property_id) a PRIMARY KEY in the object_properties and get rid of the surrogate key.