SQL query of multiple joins - mysql

I have a problem dealing with joins
This is my first table:
CREATE TABLE IF NOT EXISTS `form` (
`id_form` int(20) NOT NULL AUTO_INCREMENT,
`nameform` varchar(50) NOT NULL,
PRIMARY KEY (`id_form`)
)
The data in the table
INSERT INTO `form` (`id_form`, `nameform`) VALUES
(1, 'Formulaire commun'),
(2, 'Formulaire FCPR'),
(3, 'Formulaire fonds d''amorçage'),
(4, 'Formulaire FOPRODI'),
(5, 'Formulaire ITP'),
(6, 'Formulaire PASRI'),
(7, 'Formulaire PCAM'),
(8, 'Formulaire PIRD'),
(9, 'Formulaire PMN'),
(10, 'Formulaire PNRI'),
(11, 'Formulaire PRF'),
(12, 'Formulaire RIICTIC'),
(13, 'Formulaire VRR');
My second table userdata:
CREATE TABLE IF NOT EXISTS `donnée_utilisateur` (
`id_d` int(20) NOT NULL AUTO_INCREMENT,
`id_form` int(20) NOT NULL,
`id_us` int(20) NOT NULL,
PRIMARY KEY (`id_d`),
KEY `id-form` (`id_form`),
KEY `id-us` (`id_us`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=407 ;
ALTER TABLE `donnée_utilisateur`
ADD CONSTRAINT `fvdsvsd` FOREIGN KEY (`id_us`) REFERENCES `utilisateur` (`id_us`),
ADD CONSTRAINT `ssssssssssss` FOREIGN KEY (`id_form`) REFERENCES `form` (`id_form`);
The data in it:
INSERT INTO `donnée_utilisateur` (`id_d`, `id_form`, `id_us`) VALUES
(380, 2, 6),
(381, 2, 6),
(382, 3, 6),
(383, 3, 6),
(384, 4, 6),
(385, 5, 6);
And finally the user table :
CREATE TABLE IF NOT EXISTS `utilisateur` (
`id_us` int(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id_us`),
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=8 ;
The data :
INSERT INTO `utilisateur` (`id_us`) VALUES
(3),
(6),
(7);
What I want to do is to get the id_form which doesn't exist in userdata table for a specific user.
I've tried to do it like this:
SELECT f.id_form
FROM `donnée_utilisateur` d
RIGHT JOIN `form` f ON f.id_form=d.id_form Where d.id_d IS NULL
This query leads to this result if we have that kind of data :
id_form
1
6
7
8
9
10
11
12
13
This is the expected result and it's correct. If I want this result for a specific user, I change it like this :
SELECT f.id_form
FROM `donnée_utilisateur` d
RIGHT JOIN `form` f ON f.id_form=d.id_form
INNER JOIN `utilisateur` u ON u.id_us=d.id_us Where d.id_d IS NULL AND id_us=6
I'm getting nothing or it should be like the result that I just wrote.
Let's take another example for id_us=7
SELECT f.id_form
FROM `donnée_utilisateur` d
RIGHT JOIN `form` f ON f.id_form=d.id_form
INNER JOIN `utilisateur` u ON u.id_us=d.id_us Where d.id_d IS NULL AND u. id_us=7
This should result in all id_form from 1 to 12 because the user didn't insert any data.

Right joins are very hard to read and thus prone to errors. Usually you'd start with the table you must get data from and then left outer join tables you might get data from.
Let's look at your query:
You right join after table donnée_utilisateur, so donnée_utilisateur gets outer joined to the other tables.
The other tables are form and utilisateur. You have no join criteria combining the two, so you cross join them, i.e. combine every form with every utilisateur.
So to this cross join product you outer join donnée_utilisateur.
Where d.id_d IS NULL makes this an anti join. A trick used to replace a mere NOT EXISTS or NOT IN in DBMS that have weaknesses with these straight-forward methods. You use it to get all form / utilisateur combinations for which there is no entry in donnée_utilisateur. Probably many.
Where id_us=6 further narrows the results. Unfortunately you forgot to use a qualifier. Is it u.id_us or d.id_us? The DBMS cannot know. Let's say it decides you mean d.id_us. That field is always null, because you just dismissed all matches. d.id_us = 6 is never true, so all rows get discarded. Your result is empty. If the DBMS decided you mean u.id_us, you'd prabably get results, particularly the same id_form over and over.
You may want to add the qualifier u, but I suggest you rather re-write the whole query and use NOT IN or NOT EXISTS.
And what has utilisateur to do with your query anyway? I thought you where looking for forms for which not exists user 6 in donnée_utilisateur. Why join utilisateur at all? (And if you join it, you should probably outer join it to donnée_utilisateur.)

You can do with a subselect
select id_form from form where
id_form not in (select distinct id_form from donnée_utilisateur where id_us=6 )
Or RDBMS engine correctly
select id_form from form where
id_form not in (select id_form from donnée_utilisateur where id_us=6 )

Thorsten was very good in his clarification, but did not provide the completed query to help you. Your original right-join query was VERY close. However, I have switched to a left-join as follows:
SELECT
f.id_form,
f.nameform
from
form f
left join donnée_utilisateur d
ON f.id_form = d.id_form
AND d.id_us = 6
where
d.id_d IS NULL
So, I am starting with the FORM table to get the ID and the name. No problem. Now, your consideration that the form is not found within the secondary table, so that is a left-join on the form ID and looking for NULL in the where clause. But this, by itself is qualifying a form for ANY user. To finalize your need for a specific user, just add the AND clause to the secondary table so THAT portion remains as a left-join for the FORM AND specific user resulting in NULL for the d.id_d column

Related

Can I control which JOINed row gets used in an UPDATE?

Once upon a time, I had a table like this:
CREATE TABLE `Events` (
`EvtId` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`AlarmId` INT UNSIGNED,
-- Other fields omitted for brevity
PRIMARY KEY (`EvtId`)
);
AlarmId was permitted to be NULL.
Now, because I want to expand from zero-or-one alarm per event to zero-or-more alarms per event, in a software update I'm changing instances of my database to have this instead:
CREATE TABLE `Events` (
`EvtId` INT UNSIGNED NOT NULL AUTO_INCREMENT,
-- Other fields omitted for brevity
PRIMARY KEY (`EvtId`)
);
CREATE TABLE `EventAlarms` (
`EvtId` INT UNSIGNED NOT NULL,
`AlarmId` INT UNSIGNED NOT NULL,
PRIMARY KEY (`EvtId`, `AlarmId`),
CONSTRAINT `fk_evt` FOREIGN KEY (`EvtId`) REFERENCES `Events` (`EvtId`)
ON DELETE CASCADE ON UPDATE CASCADE
);
So far so good.
The data is easy to migrate, too:
INSERT INTO `EventAlarms`
SELECT `EvtId`, `AlarmId` FROM `Events` WHERE `AlarmId` IS NOT NULL;
ALTER TABLE `Events` DROP COLUMN `AlarmId`;
Thing is, my system requires that a downgrade also be possible. I accept that downgrades will sometimes be lossy in terms of data, and that's okay. However, they do need to work where possible, and result in the older database structure while making a best effort to keep as much original data as is reasonably possible.
In this case, that means going from zero-or-more alarms per event, to zero-or-one alarm per event. I could do it like this:
ALTER TABLE `Events` ADD COLUMN `AlarmId` INT UNSIGNED;
UPDATE `Events`
LEFT JOIN `EventAlarms` USING(`EvtId`)
SET `Events`.`AlarmId` = `EventAlarms`.`AlarmId`;
DROP TABLE `EventAlarms`;
… which is kind of fine, since I don't really care which one gets kept (it's best-effort, remember). However, as warned, this is not good for replication as the result may be unpredictable:
> SHOW WARNINGS;
Unsafe statement written to the binary log using statement format since
BINLOG_FORMAT = STATEMENT. Statements writing to a table with an auto-
increment column after selecting from another table are unsafe because the
order in which rows are retrieved determines what (if any) rows will be
written. This order cannot be predicted and may differ on master and the
slave.
Is there a way to somehow "order" or "limit" the join in the update, or shall I just skip this whole enterprise and stop trying to be clever? If the latter, how can I leave the downgraded AlarmId as NULL iff there were multiple rows in the new table between which we cannot safely distinguish? I do want to migrate the AlarmId if there is only one.
As a downgrade is a "one-time" maintenance operation, it doesn't have to be exactly real-time, but speed would be nice. Both tables could potentially have thousands of rows.
(MariaDB 5.5.56 on CentOS 7, but must also work on whatever ships with CentOS 6.)
First, we can perform a bit of analysis, with a self-join:
SELECT `A`.`EvtId`, COUNT(`B`.`EvtId`) AS `N`
FROM `EventAlarms` AS `A`
LEFT JOIN `EventAlarms` AS `B` ON (`A`.`EvtId` = `B`.`EvtId`)
GROUP BY `B`.`EvtId`
The result will look something like this:
EvtId N
--------------
370 1
371 1
372 4
379 1
380 1
382 16
383 1
384 1
Now you can, if you like, drop all the rows representing events that map to more than one alarm (which you suggest as a fallback solution; I think this makes sense, though you could modify the below to leave one of them in place if you really wanted).
Instead of actually DELETEing anything, though, it's easier to introduce a new table, populated using the self-joining query shown above:
CREATE TEMPORARY TABLE `_migrate` (
`EvtId` INT UNSIGNED,
`n` INT UNSIGNED,
PRIMARY KEY (`EvtId`),
KEY `idx_n` (`n`)
);
INSERT INTO `_migrate`
SELECT `A`.`EvtId`, COUNT(`B`.`EvtId`) AS `n`
FROM `EventAlarms` AS `A`
LEFT JOIN `EventAlarms` AS `B` ON(`A`.`EvtId` = `B`.`EvtId`)
GROUP BY `B`.`EvtId`;
Then your update becomes:
UPDATE `Events`
LEFT JOIN `_migrate` ON (`Events`.`EvtId` = `_migrate`.`EvtId` AND `_migrate`.`n` = 1)
LEFT JOIN `EventAlarms` ON (`_migrate`.`EvtId` = `EventAlarms`.`EvtId`)
SET `Events`.`AlarmId` = `EventAlarms`.`AlarmId`
WHERE `EventAlarms`.`AlarmId` IS NOT NULL
And, finally, clean up after yourself:
DROP TABLE `_migrate`;
DROP TABLE `EventAlarms`;
MySQL still kicks out the same warning as before, but since know that at most one value will be pulled from the source tables, we can basically just ignore it.
It should even be reasonably efficient, as we can tell from the equivalent EXPLAIN SELECT:
EXPLAIN SELECT `Events`.`EvtId` FROM `Events`
LEFT JOIN `_migrate` ON (`Events`.`EvtId` = `_migrate`.`EvtId` AND `_migrate`.`n` = 1)
LEFT JOIN `EventAlarms` ON (`_migrate`.`EvtId` = `EventAlarms`.`EvtId`)
WHERE `EventAlarms`.`AlarmId` IS NOT NULL
id select_type table type possible_keys key key_len ref rows Extra
---------------------------------------------------------------------------------------------------------------------
1 SIMPLE _migrate ref PRIMARY,idx_n idx_n 5 const 6 Using index
1 SIMPLE EventAlarms ref PRIMARY,fk_AlarmId PRIMARY 8 db._migrate.EvtId 1 Using where; Using index
1 SIMPLE Events eq_ref PRIMARY PRIMARY 8 db._migrate.EvtId 1 Using where; Using index
Use a subquery and user variables to select just one EventAlarms
In your update instead of EventAlarms use
( SELECT `EvtId`, `AlarmId`
FROM ( SELECT `EvtId`, `AlarmId`,
#rn := if ( #EvtId = `EvtId`
#rn + 1,
if ( #EvtId := `EvtId` , 1, 1)
) as rn
FROM `EventAlarms`
CROSS JOIN ( SELECT #EvtId := 0, #rn := 0) as vars
ORDER BY EvtId, AlarmId
) as t
WHERE rn = 1
) as SingleEventAlarms

ORDER a table from an column which is in another table in Mysql

SQL Fiddle And Here is my whole code:
DROP TABLE tmdb_movies;
CREATE TABLE tmdb_movies (
tmdb_id INTEGER NOT NULL PRIMARY KEY,
movie_title TEXT NOT NULL,
popularity INTEGER NOT NULL
);
INSERT INTO tmdb_movies (tmdb_id, movie_title, popularity) VALUES
(1, 'Logan', '88.4'),
(2, 'Iron Man', '74.3'),
(3, 'SuperMan', '102.56');
DROP TABLE genres;
CREATE TABLE genres (
tmdb_id INTEGER NOT NULL,
genres_name TEXT NOT NULL
);
INSERT INTO genres (tmdb_id, genres_name) VALUES
(1, 'Crime'),
(1, 'Comedy'),
(1, 'Drama'),
(2, 'Action'),
(2, 'Horror'),
(2, 'Documentary'),
(3, 'Music');
SELECT distinct genres.tmdb_id
FROM genres
JOIN tmdb_movies USING (tmdb_id)
ORDER BY tmdb_movies.popularity DESC
What, i am doing here is: I want to sort table by popularity column in tmdb_movies table.
The SQL query is working perfectly fine in the [SQL Fiddle][1]
But, i am getting this error, when i run it in my MySQL
3065 - Expression #1 of ORDER BY clause is not in SELECT list, references column 'DB.tmdb_movies.popularity' which is not in SELECT list; this is incompatible with DISTINCT
This is probably because of my default SQL mode. Do i need to change the mode? Anyway to do this, without changing the mode, because i think default mode will be better?
Is there a specific reason you don't want to add the popularity to the select?
SELECT distinct genres.tmdb_id, tmdb_movies.popularity
FROM genres
JOIN tmdb_movies USING (tmdb_id)
ORDER BY tmdb_movies.popularity DESC

Relational database JOIN return excessive values

Using relational database usually i am joing the table using INNER JOIN. When querying out the data from the joined table it returns excessive amount of value. As an illustration i have created a dummy data table as below.
The table sequence are Operation can have many daily and daily can have many activity and each activity is unique by its activity UID.
Usually i will INNER JOIN for example to join Operation table with Activity table to query out each breakdown of class, phase, ops, root per operation. However it returns excessive amount of Durationhrs. The worst case if joined all the 3 tables (Operation, Daily, Activity) it will return massive hours value beyond believe.
My questions are
Where did i went wrong?
What kind of join to make it right?
If this cannot be done what is the best method to join?
My database like this
CREATE TABLE Operation
(`Operationuid` varchar(3), `operationname` varchar(10), `owner` varchar(55))
;
INSERT INTO operation
(`Operationuid`, `operationname`, `owner`)
VALUES
('AA1', 'Cow', 'Jon Letoy'),
('AA2', 'Chicken', 'Ridikill' ),
('AA3', 'Snake', 'Mighty'),
('AA4', 'Sheep', 'The great'),
('AA5', 'Pig', 'Peon');
CREATE TABLE Activity
(`Operationuid` varchar(3), `DailyUID` varchar(10), `ActivityUID` varchar(55), `Class` varchar(3), `Phase` varchar(3), `Ops` varchar(3), `Root` varchar(3), Duration int);
INSERT INTO Activity
(`Operationuid`, `DailyUID`, `ActivityUID`, `Class`, `Phase`, `Ops`, `Root`, `Duration`)
VALUES
('AA1', 'DD1', 'AC1', 'AB1', 'PH1', 'OP1', null, 12),
('AA1', 'DD1', 'AC2', 'AB1', 'PH2', 'OP1', null, 2),
('AA1', 'DD1', 'AC3', 'AB2', 'PH2', 'OP2', 'RR1', 3),
('AA1', 'DD1', 'AC4', 'AB3', 'PH3', 'OP3', null, 5),
('AA1', 'DD1', 'AC5', 'AB4', 'PH4', 'OP4', 'RR2', 1);
CREATE TABLE Daily
(`Operationuid` varchar(3), `DailyUID` varchar(10), `Dayno` varchar(55), `Daycost` decimal);
INSERT INTO Daily
(`Operationuid`, `DailyUID`, `Dayno`, `Daycost`)
VALUES
('AA1', 'DD1', 1, 1000),
('AA1', 'DD2', 2, 2000),
('AA1', 'DD3', 3, 3000),
('AA1', 'DD4', 4, 4000),
('AA1', 'DD5', 5, 5000);
Select operation.*, daily.*, activity.* from Operation
INNER JOIN daily on daily.operationUID=operatin.operationUID
INNER JOIN activity on activity.operationUID=operation.operationUID
This will return 25 result instead of only 5 that i need why?
You have a little problem on your JOIN. You joined the Activity table with itself here: INNER JOIN activity on activity.operationUID=activity.operationUID
You may want to correct that as:
Select operation.*, daily.*, activity.* from Operation
INNER JOIN daily on daily.operationUID=operation.operationUID
INNER JOIN activity on operation.operationUID=activity.operationUID
See it working here: http://sqlfiddle.com/#!9/1a4ae9/8
On that result you will see 25 rows as result. That's because when you query many tables without JOIN operations what happens is a cartesian plane between the involved tables which will result in the multiplication of numbers of rows in all tables, on your case it WOULD be 5*5*5. But since we have the JOIN operation you receive the equivalence between operation and daily tables therefore 5 registries than you add another join with activity table which has 5 more registries to the Operationuid equals to AA1 so it will result in a each registry of the first join (operation with daily) with all registries on the activity table.

Insert into master table when detail records present but missing master

I have two tables - one master, one detail (i.e. a one-to-many pair of tables). I'm importing data from a horrible schema and one feature of the data is that often I have some detail records but no master.
How would go about inserting master records in these cases? I can locate the missing masters easily enough with this query:
select * from p_ltx_surgical_comp as c -- detail
left join p_ltx_surgical as s -- master
on c.fk_oid = s.fk_oid -- this is the key
where s.oid is null -- primary key, so null means no record exists
group by c.fk_oid; -- only show one value even if there are multiple detail records
Oh, and as an extra wrinkle, I only want to insert a single master even if there a are multiple detail records.
You can start with this INSERT query:
INSERT INTO p_ltx_surgical (fk_oid)
SELECT DISTINCT c.fk_oid
FROM
p_ltx_surgical_comp AS c
LEFT JOIN p_ltx_surgical AS s
ON c.fk_oid = s.fk_oid
WHERE
s.oid IS NULL
and you can add more details to your table, for example:
INSERT INTO p_ltx_surgical (fk_oid, description, ...)
SELECT DISTINCT c.fk_oid, 'missing record', ...
FROM
...
Ah, I was so close... this seems to have worked:
insert into p_ltx_surgical (oid, fk_oid, ltx_surg_date)
select sp_getvdtablekey('p_ltx_surgical', 0), c.fk_oid, '1900-01-01' from
p_ltx_surgical_comp as c -- detail
left join p_ltx_surgical as s -- master
on c.fk_oid = s.fk_oid -- this is the key
where s.oid is null
group by c.fk_oid; -- primary key, so null means no record exists

Aggregate joined table results in an SQL SELECT on MySQL 5

I have the following schema, and would like to do a query that returns one row for each entry in the articles table, with it's corresponding content column from the content table, and a column with each of that articles tags, such as you might get by using concat.
The query should SELECT only rows that match a certain tag. So if the tag atdi was provided, the result set would look something like:
id content tags
1 on my way nails broke and fell song,atdi,invalid
3 im all alone so far up here and my oxygen is all gone song,atdi,hourglass
4 you know your insides true better than i do song,atdi,starslight
I've tried a few different ways with subqueries, but keep getting errors - it's quite frustrating.
Here's the schema:
CREATE TABLE articles (
id int not null default 0,
published datetime,
author int not null default 0,
primary key (id)
);
INSERT INTO articles
(id, published, author)
VALUES
(1, CURRENT_TIMESTAMP, 1),
(2, CURRENT_TIMESTAMP, 1),
(3, CURRENT_TIMESTAMP, 1),
(4, CURRENT_TIMESTAMP, 1);
CREATE TABLE content (
id int not null default 0,
content varchar(250) not null default '',
primary key (id)
);
INSERT INTO content
(id,content)
VALUES
(1,'on my way nails broke and fell'),
(2,'exo skeleton junction at the railroad delayed'),
(3,'im all alone so far up here and my oxygen is all gone'),
(4,'you know your insides true better than i do');
CREATE TABLE tags (
id int not null default 0,
tag varchar(100) not null default '',
primary key (id,tag)
);
INSERT INTO tags
(id,tag)
VALUES
(1,"song"),
(2,"song"),
(3,"song"),
(4,"song"),
(1,"atdi"),
(2,"mars"),
(3,"atdi"),
(4,"atdi"),
(1,"invalid"),
(2,"roulette"),
(3,"hourglass"),
(4,"starslight");
Try something like this one
select a.id, a.content, b.tags_1
from content as a inner join (
select id, GROUP_CONCAT(tag SEPARATOR ',') as tags_1 FROM tags group by id
) as b on a.id = b.id
INNER JOIN tags AS c ON a.id = c.id
WHERE c.tag = 'atdi'
Using the GROUP_CONCAT() method