MySQL query: Updating foreign key with unique Id - mysql

I need to link two tables 1-to-1, but the values that are to be compared and linked upon, are not unique.
I cannot find a way. As an example, I added a very simple version.
CREATE TABLE `T1` (
`id` int(6) unsigned NOT NULL,
`cmp` int(3) NOT NULL,
`uniqueT2Id` int(3) unsigned,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `T2` (
`id` int(6) unsigned NOT NULL,
`cmp` int(3) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
INSERT INTO `T1` (`id`, `cmp`, `uniqueT2Id`) VALUES
('1', '1', NULL),
('2', '1', NULL),
('3', '2', NULL),
('4', '3', NULL),
('5', '1', NULL);
INSERT INTO `T2` (`id`, `cmp`) VALUES
('1', '1'),
('2', '1'),
('3', '1'),
('4', '2'),
('5', '3');
UPDATE T1 SET uniqueT2Id=
(SELECT id FROM T2 WHERE T2.cmp=T1.cmp AND
id NOT IN (SELECT * FROM
(SELECT uniqueT2Id FROM T1 WHERE uniqueT2Id IS NOT NULL) X)
ORDER BY id ASC LIMIT 1);
SELECT * FROM T1;
http://sqlfiddle.com/#!9/3bab7c/2/0
The result is
id cmp uniqueT2Id
1 1 1
2 1 1
3 2 4
4 3 5
5 1 1
I want it to be
id rev uniqueT2Id
1 1 1
2 1 2
3 2 4
4 3 5
5 1 3
In the UPDATE I try to pick an Id that is not already used, but this obviously does not work. Does anyone know a way to do this in MySQL, preferrably without PHP?

I found an answer myself, with variables. It is horrible and requires a dummy field in the table, but it works. I am open for improvements.
CREATE TABLE `T1` (
`id` int(6) unsigned NOT NULL,
`cmp` int(3) NOT NULL,
`uniqueT2Id` int(3) NULL,
`dummy` varchar(200) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `T2` (
`id` int(6) unsigned NOT NULL,
`cmp` int(3) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
INSERT INTO `T1` (`id`, `cmp`, `uniqueT2Id`) VALUES
('1', '1', NULL),
('2', '1', NULL),
('3', '2', NULL),
('4', '5', NULL),
('5', '3', NULL),
('6', '1', NULL);
INSERT INTO `T2` (`id`, `cmp`) VALUES
('1', '1'),
('2', '1'),
('3', '1'),
('4', '2'),
('5', '3');
SET #taken = '/' ;
UPDATE T1
SET uniqueT2Id= #new:=
(SELECT id FROM T2 WHERE T2.cmp=T1.cmp AND
INSTR(#taken, CONCAT('/',id,'/')) = 0
ORDER BY id ASC LIMIT 1),
dummy=IF(#new IS NOT NULL,#taken:=CONCAT(#taken, #new, "/"),NULL);
http://sqlfiddle.com/#!9/4a61d/1/0

Related

Convert MariaDb query into MySQL

I have query in mariaDB, this query is used to (get ancestors of a child (dog) upto Level 5)
WITH RECURSIVE
cte AS (
SELECT *, 0 level, ' ' relation
FROM dogs
WHERE dog_id = 7
UNION ALL
SELECT dogs.*, level + 1, 'father'
FROM dogs
JOIN cte ON cte.father_id = dogs.dog_id
WHERE level < 5
UNION ALL
SELECT dogs.*, level + 1, 'mother'
FROM dogs
JOIN cte ON cte.mother_id = dogs.dog_id
WHERE level < 5
)
SELECT *
FROM cte
ORDER BY level, relation;
I want this query in mysql version: 5.6.45
Here is Table Script:
CREATE TABLE `dogs` (
`dog_id` int(11) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`father_id` int(11) DEFAULT NULL,
`mother_id` int(11) DEFAULT NULL,
PRIMARY KEY (`dog_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `dogs` VALUES ('0', null, null, null);
INSERT INTO `dogs` VALUES ('1', 'Father', null, null);
INSERT INTO `dogs` VALUES ('2', 'Mother', null, null);
INSERT INTO `dogs` VALUES ('3', 'Father1', null, null);
INSERT INTO `dogs` VALUES ('4', 'Mother2', null, null);
INSERT INTO `dogs` VALUES ('5', 'Son', '1', '2');
INSERT INTO `dogs` VALUES ('6', 'Daughter', '3', '4');
INSERT INTO `dogs` VALUES ('7', 'GrandSon', '5', '6');

How to get rows from table doc_val with minimum "val" from table doc_val for each of the doc_id where criteria = 'L'

You have two tables:
1. docs
2. doc_val
Point of focus is table : doc_val , it has doc_id FK from table docs , field critera which will
be our condition.
Mysql schema:
CREATE TABLE IF NOT EXISTS `docs` (
`id` int(6) unsigned NOT NULL,
`rev` int(3) unsigned NOT NULL,
`content` varchar(200) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `doc_val` (
`id` int(6) unsigned NOT NULL,
`doc_id` int(6) unsigned NOT NULL,
`val` int(3) unsigned NOT NULL,
`type` varchar(2) NOT NULL,
`criteria` varchar(2) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
INSERT INTO `docs` (`id`, `rev`, `content`) VALUES
('1', '1', 'The earth is flat'),
('2', '1', 'One hundred angels can dance on the head of a pin'),
('3', '1', 'The earth is flat and rests on a bull\'s horn'),
('4', '4', 'The earth is like a ball.');
INSERT INTO `doc_val` (`id`, `doc_id`, `val`, `type`, `criteria`) VALUES
('1', '1', 100, 'D', 'L'),
('2', '1', 101, 'D', 'L'),
('3', '1', 80, 'H', 'L'),
('4', '2', 10, 'H', 'S'),
('5', '2', 90, 'H', 'L'),
('6', '3', 100, 'D', 'L'),
('7', '3', 100, 'D', 'L');
expected output:
DECLARE curIds CURSOR FOR SELECT DISTINCT doc_id FROM doc_val;
DECLARE id INT;
CREATE TEMPORARY TABLE temp(id int, doc_id int, val int, type char(1), criteria char(1));
OPEN curIds;
read_loop: LOOP
FETCH curIds INTO id;
INSERT INTO temp
(Select * from doc_val
where val = (select min(val)
from doc_val
where doc_id = id)) AND criteria = 'L'
END LOOP;
SELECT * FROM temp;
Probably there is a syntax error but i hope you caught the idea.

Get most recent record from one-to-many relationship

As I say in the title, having this schema:
CREATE TABLE IF NOT EXISTS `services` (
`id` int(6) unsigned NOT NULL,
`description` varchar(200) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
INSERT INTO `services` (`id`, `description`) VALUES
('1', 'Water flood from kitchen'),
('2', 'Light switch burnt');
CREATE TABLE IF NOT EXISTS `visits` (
`id` int(6) unsigned NOT NULL,
`date` DATETIME NOT NULL,
`description` varchar(200) NOT NULL,
`worker` varchar(200) NOT NULL,
`services_id` int(6) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
INSERT INTO `visits` (`id`, `date`, `description`, `worker`, `services_id`) VALUES
('1', '2018-12-10 16:00:00', 'Find and stop leak', 'Thomas', '1'),
('2', '2018-12-11 09:00:00', 'Change broken pipe', 'Bob', '1'),
('3', '2018-12-10 19:00:00', 'Change light switch', 'Alfred', '2'),
('4', '2018-12-11 10:00:00', 'Paint wall blackened by shortcircuit', 'Ryan', '2');
I need to get the most recent visit, date-wise, for each service.
In this example, I would get:
'1', '2018-12-10 16:00:00', 'Find and stop leak', 'Thomas', '1'
'3', '2018-12-10 19:00:00', 'Change light switch', 'Alfred', '2'
How would you do it? I'm struggling to get a solution.
Here's the SQLFiddle:
http://sqlfiddle.com/#!9/3ca219
I would suggest a correlated subquery:
select v.*
from visits v
where v.date = (select max(v2.date)
from visits v2
where v2.services_id = v.services_id
);
With an index on visits(services_id, date), this should be as fast or faster than other approaches.

Having problems with getting a count in a select statement

I have 5 tables as follows:
files
tags
profiles
j_file_tags
j_profile_tags
So files can have tags, profiles give access to certain tags.
I put together two queries that do the following:
get a list of files that a specific profile has access to (the profile must have access to all tags a file might have)
get a list of tags that the profile has access to and where there is at least a file in that tag.
What I need for query 2 is a count of how many files are in a tag.
Here's the table structure and sample data:
CREATE TABLE `files` (
`id` int(4) NOT NULL AUTO_INCREMENT ,
`fileName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
`empID` int(4) NOT NULL ,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
ROW_FORMAT=Dynamic;
CREATE TABLE `j_file_tags` (
`id` int(4) NOT NULL AUTO_INCREMENT ,
`fileID` int(4) NULL DEFAULT NULL ,
`tagID` int(4) NULL DEFAULT NULL ,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
ROW_FORMAT=Dynamic;
CREATE TABLE `j_profile_tags` (
`id` int(4) NOT NULL AUTO_INCREMENT ,
`profileID` int(4) NULL DEFAULT NULL ,
`tagID` int(4) NULL DEFAULT NULL ,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
ROW_FORMAT=Dynamic;
CREATE TABLE `profiles` (
`id` int(4) NOT NULL AUTO_INCREMENT ,
`profileName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
ROW_FORMAT=Dynamic;
CREATE TABLE `tags` (
`id` int(4) NOT NULL AUTO_INCREMENT ,
`tagName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
ROW_FORMAT=Dynamic;
INSERT INTO `files` VALUES ('1', 'FileOne', '1');
INSERT INTO `files` VALUES ('2', 'FileTwo', '1');
INSERT INTO `files` VALUES ('3', 'FileThree', '1');
INSERT INTO `files` VALUES ('4', 'FileFour', '2');
INSERT INTO `files` VALUES ('5', 'FileFive', '2');
INSERT INTO `files` VALUES ('6', 'FileSix', '2');
INSERT INTO `files` VALUES ('7', 'FileSeven', '2');
INSERT INTO `profiles` VALUES ('1', 'ProfileOne');
INSERT INTO `profiles` VALUES ('2', 'ProfileTwo');
INSERT INTO `profiles` VALUES ('3', 'ProfileThree');
INSERT INTO `tags` VALUES ('1', 'TagOne');
INSERT INTO `tags` VALUES ('2', 'TagTwo');
INSERT INTO `tags` VALUES ('3', 'TagThree');
INSERT INTO `tags` VALUES ('4', 'TagFour');
INSERT INTO `tags` VALUES ('5', 'TagFive');
INSERT INTO `j_file_tags` VALUES ('1', '1', '1');
INSERT INTO `j_file_tags` VALUES ('2', '1', '3');
INSERT INTO `j_file_tags` VALUES ('3', '2', '1');
INSERT INTO `j_file_tags` VALUES ('4', '2', '5');
INSERT INTO `j_file_tags` VALUES ('5', '3', '1');
INSERT INTO `j_file_tags` VALUES ('6', '3', '3');
INSERT INTO `j_file_tags` VALUES ('7', '3', '6');
INSERT INTO `j_file_tags` VALUES ('8', '2', '3');
INSERT INTO `j_file_tags` VALUES ('9', '4', '1');
INSERT INTO `j_file_tags` VALUES ('10', '4', '2');
INSERT INTO `j_file_tags` VALUES ('11', '5', '1');
INSERT INTO `j_file_tags` VALUES ('12', '5', '6');
INSERT INTO `j_profile_tags` VALUES ('1', '1', '2');
INSERT INTO `j_profile_tags` VALUES ('2', '1', '3');
INSERT INTO `j_profile_tags` VALUES ('3', '1', '4');
INSERT INTO `j_profile_tags` VALUES ('4', '2', '1');
INSERT INTO `j_profile_tags` VALUES ('5', '2', '2');
INSERT INTO `j_profile_tags` VALUES ('6', '2', '3');
INSERT INTO `j_profile_tags` VALUES ('7', '2', '4');
INSERT INTO `j_profile_tags` VALUES ('8', '2', '5');
INSERT INTO `j_profile_tags` VALUES ('9', '1', '1');
INSERT INTO `j_profile_tags` VALUES ('10', '1', '5');
Here are my 2 queries:
/* Get list of files: limit by specific employee AND by tags the use has access to */
SELECT
`files`.`id`,
`files`.`fileName`,
`files`.`empID`,
GROUP_CONCAT(CONCAT(`tags`.`id`) SEPARATOR ', ') as `FileTags`
FROM `files`
LEFT JOIN `j_file_tags` ON `j_file_tags`.`fileID` = `files`.`id`
LEFT JOIN `tags` ON `tags`.`id` = `j_file_tags`.`tagID`
WHERE
`files`.`empID` = 1
AND
`j_file_tags`.`tagID` IN (1,2,3,4,5)
GROUP BY
`files`.`id`
HAVING
COUNT(`j_file_tags`.`id`) = (SELECT COUNT(`j_file_tags`.`id`) FROM `j_file_tags` WHERE `j_file_tags`.`fileID` = `files`.`id` );
/* SECOND QUERY where i need help */
SELECT
`tags`.`id`,
`tags`.`tagName`,
'1' as `fileCount` /* i need this to be an actual count */
FROM
`tags`
LEFT JOIN `j_file_tags` ON `j_file_tags`.`tagID` = `tags`.`id`
LEFT JOIN `files` ON `files`.`id` = `j_file_tags`.`fileID`
LEFT JOIN `j_profile_tags` ON `j_profile_tags`.`tagID` = `tags`.`id`
WHERE
`j_profile_tags`.`profileID` = 1
AND
`files`.`empID` = 1
GROUP BY
`tags`.`id`;
/* the fileCount column would need to say for TagOne - 2, for TagThree - 2 and for TagFive - 1 */
In the sample data the first query returns:
| 1 | FileOne | 1 | 1, 3
| 2 | FileTwo | 1 | 3, 1, 5
The second query returns:
| 1 | TagOne | should return 2
| 3 | TagThree | should return 2
| 5 | TagFive| | should return 1
The answer is simple the HAVING clause that you have in the first query is throwing off your results because there are truly 3 files with tags 1 and 3. I would suggest removing it all together or figuring out another way to do it. I rewrote your query below to display your desired result, however it is not a fix all.
SELECT
tags.id,
tags.tagName,
count(fileID) as 'fileCount'
FROM files
LEFT JOIN j_file_tags ON j_file_tags.fileID = files.id
LEFT JOIN tags ON tags.id = j_file_tags.tagID
WHERE
files.empID = 1
AND
j_file_tags.tagID IN (1,2,3,4,5)
AND
fileID IN (1,2)
GROUP BY
tags.id

Why do these queries yield different results in MySQL 5.5 vs 5.6?

Can someone explain why these two queries (one using IN and one using EXISTS) return different results in MySQL 5.6 but not in MySQL 5.5?
Using EXPLAIN, I can see different execution plans for each, but I need help understanding what's going on, and why would this IN logic be broken in 5.6 but not 5.5?
Fiddle illustrating the problem: http://sqlfiddle.com/#!9/da52b/95
Members can have two addresses: a home address and a firm address. The desired result is to provide a region X and get a list of all members with a mailing address in that region. The mailing address is the firm address if it exists, otherwise it is the home address. Cities can belong to one or more regions.
Simplified database structure and data:
CREATE TABLE `city` (
`c_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`c_name` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`c_id`)
);
INSERT INTO `city`
VALUES
('1', 'Hillsdale'),
('2', 'Smallville'),
('3', 'Oakside'),
('4', 'Lakeview');
CREATE TABLE `city_region` (
`cr_city` int(11) unsigned NOT NULL,
`cr_region` int(11) NOT NULL,
PRIMARY KEY (`cr_city`,`cr_region`)
);
INSERT INTO `city_region`
VALUES
('1', '3'),
('2', '1'),
('3', '1'),
('3', '2'),
('4', '1'),
('4', '3');
CREATE TABLE `firm_address` (
`fa_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`fa_member` int(11) NOT NULL,
`fa_city` int(11) NOT NULL,
PRIMARY KEY (`fa_id`)
);
INSERT INTO `firm_address`
VALUES
('1', '1', '3'),
('2', '2', '2'),
('3', '3', '1'),
('4', '6', '2'),
('5', '7', '1');
CREATE TABLE `home_address` (
`ha_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`ha_member` int(11) NOT NULL,
`ha_city` int(11) NOT NULL,
PRIMARY KEY (`ha_id`)
);
INSERT INTO `home_address`
VALUES
('1', '1', '2'),
('2', '2', '3'),
('3', '3', '1'),
('4', '4', '1'),
('5', '5', '2'),
('6', '6', '2');
CREATE TABLE `member` (
`m_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`m_name` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`m_id`)
);
INSERT INTO `member`
VALUES
('1', 'John'),
('2', 'Bob'),
('3', 'Dave'),
('4', 'Jane'),
('5', 'Mary'),
('6', 'Karen'),
('7', 'Christie');
CREATE TABLE `region` (
`r_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`r_name` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`r_id`)
);
INSERT INTO `region`
VALUES
('1', 'Central'),
('2', 'Lake District'),
('3', 'Westside');
Query 1 (wrong, missing a member):
SELECT * FROM member
LEFT OUTER JOIN home_address ON m_id = ha_member
LEFT OUTER JOIN city home_city ON ha_city = home_city.c_id
LEFT OUTER JOIN firm_address ON m_id = fa_member
LEFT OUTER JOIN city firm_city ON fa_city = firm_city.c_id
WHERE 1 IN (
SELECT r_id
FROM region
INNER JOIN city_region ON r_id = cr_region
WHERE cr_city = IF(fa_city IS NULL, ha_city, fa_city)
)
Query 2 (returning the correct results):
SELECT * FROM member
LEFT OUTER JOIN home_address ON m_id = ha_member
LEFT OUTER JOIN city home_city ON ha_city = home_city.c_id
LEFT OUTER JOIN firm_address ON m_id = fa_member
LEFT OUTER JOIN city firm_city ON fa_city = firm_city.c_id
WHERE EXISTS (
SELECT r_id
FROM region
INNER JOIN city_region ON r_id = cr_region
WHERE cr_city = IF(fa_city IS NULL, ha_city, fa_city)
AND r_id = 1
)
Any help understanding this inconsistency would be appreciated.
Thank you.
I spent some time looking at this today, and it appears to be a bug in MySQL 5.6. (I also tested MySQL 5.6.15 and got the same result.)
MySQL 5.6 uses some new optimizations in executing this query, but they do not seem to be responsible for the difference, as it does not help to set e.g.:
set session optimizer_switch="block_nested_loop=off";
Using IFNULL(fa_city, ha_city) instead of IF(fa_city IS NULL, ha_city, fa_city) does yield a correct result, so the bug appears to be somewhere in the processing of IF().