MySQL - Using column value for joining in the same query - mysql

I have three tables that looks something like this:
Table joins
|ID|JOIN_NAME|
1 persons
2 companies
Table information
|ID|JOIN_ID|
1 1
2 2
Table information_extra_persons
|ID|INFORMATION_ID|NAME|
1 1 John
Table information_extra_companies
|ID|INFORMATION_ID|NAME|
1 2 IBM
How can i join together these tables in one SQL? I've tried something like:
SELECT * FROM `information`
INNER JOIN `information_extra_(SELECT `name` FROM `joins` WHERE `id` = `join_id`)`
ON `information_extra_(SELECT `name` FROM `joins` WHERE `id` = `join_id`)`.`information_id` = `information`.`id`
but I can't get it to work. Of course this isn't my actual table setup, but it's the same principle. Does anyone know how to get all the info in just one SQL?

That's actually four tables, not three. This isn't just a nitpick - it looks as though the substance of your question is "how can I use the name of the table as part of the join criteria?" (ie. how can the information_extra_ tables be treated as a single table?)
To which the answer is: you can't. (Outside of dynamic SQL.)
In this specific case, the following should return what I think you are looking for:
select j.join_name joined_entity,
case when j.join_name = 'persons' then p.name
else c.name
end joined_entity_name
from information i
inner join joins j on i.join_id = j.id
left join information_extra_persons p on i.id = p.information_id
left join information_extra_companies c on i.id = c.information_id
Alternatively, a less efficient (but more general) approach might be:
select j.join_name joined_entity,
v.name joined_entity_name
from information i
inner join joins j on i.join_id = j.id
inner join (select 'persons' entity, information_id, name from information_extra_persons
union all
select 'companies' entity, information_id, name from information_extra_companies) v
on i.id = v.information_id and j.join_name = v.entity

Related

Mysql Left Join of 3 tables and a one being joined twice

I need to do a join of 3 tables.
They all have an ID value, the issue is:
Table 1
Is the main one, name : cnpj_cnae and the ID is CNPJ_CNAE
Table 2
I need to get four columns from this one (Up to here I already got it).
The ID here is CNPJ_CNAE
Table 3
Here's the issue. For each ID value on table 1 I can have more than 1 even 10 on this table. I need to get 4 columns from ONLY THE FIRST TWO of this table.
The ID here is CNPJ_SOCIO
The finishing table would need to look like
ID INFOTABLE1 INFOTABLE2 INFOTABLE3ROW1 INFOTABLE3ROW2
I have tried some joins, but being a relative newbie with mysql I am suffering >.<
I have tried to do this:
CREATE TABLE cnpj_cnae_emp_test3 AS (
SELECT
`cnpj_cnae_test2`.*,
`cnpj_soci`.NOME_SOCIO,`cnpj_soci`.CNPJ_CPF_SOCI,`cnpj_soci`.ID_QUALIFICACAO_REP,`cnpj_soci`.DESC_QUALIFICACAO_REP,
`cnpj_emp_02`.MUNICIPIO,`cnpj_emp_02`.BAIRRO,`cnpj_emp_02`.TIPO_LOGRADOURO,`cnpj_emp_02`.LOGRADOURO,`cnpj_emp_02`.NUMERO,`cnpj_emp_02`.COMPLEMENTO
FROM `cnpj_cnae_test2`, `cnpj_soci`, `cnpj_emp_02`
WHERE `cnpj_cnae_test2`.CNPJ_CNAE = `cnpj_soci`.CNPJ_SOCIO AND `cnpj_cnae_test2`.CNPJ_CNAE = `cnpj_emp_02`.CNPJ
);
The issue is this will generate extra rows for every SOCI where instead of 1 soci on each one I need 2 socis on each line.
Table 1:
Layout of CNAE table
Table 2:
Layout of EMP table
Table 3:
Layout of Socio table
The resulting table would look like this:
More detailed end result Final layout with descriptions
Layout of result
In the end this by Owl was exactly what I needed:
WITH soci_partition AS (
SELECT CNPJ_SOCIO
,NOME_SOCIO
,CNPJ_CPF_SOCIO
,ID_QUALIFICACAO
,DESC_QUALIFICACAO_SOCIO
,row_number() OVER (PARTITION BY CNPJ_SOCIO ORDER BY IDX ASC) AS rownum
FROM cnpj_soci
)
SELECT * -- add actual columns
FROM cnpj_cnae_test2 AS cnae
INNER JOIN cnpj_emp_02 AS emp -- left join instead? depends on table structure
ON cnae.ID_CNAE = emp.IDX
LEFT JOIN soci_partition AS soci1
ON soci1.CNPJ_SOCIO = cnae.CNPJ_CNAE and soci1.rownum = 1
LEFT JOIN soci_partition AS soci2
ON soci2.CNPJ_SOCIO = cnae.CNPJ_CNAE and soci2.rownum = 2
Not sure if I got the joins/partition right, the foreign keys weren't clear.
Even if it's not perfect, hopefully this explains the general theory. Please edit in your correct answer if you end up debugging.
Also don't use old-style joins. You'll immediately fail interviews on that alone; they were deprecated 25 years ago.
WITH soci_partition AS (
SELECT CNPJ_SOCIO
,NOME_SOCIO
,CNPJ_CPF_SOCIO
,ID_QUALIFICACAO
,DESC_QUALIFICACAO_SOCIO
,row_number() OVER (PARTITION BY CNPJ_SOCIO ORDER BY IDX ASC) AS rownum
FROM cnpj_soci
)
SELECT * -- add actual columns
FROM cnpj_cnae_test2 AS cnae
INNER JOIN cnpj_emp_02 AS emp -- left join instead? depends on table structure
ON cnae.ID_CNAE = emp.IDX
LEFT JOIN soci_partition AS soci1
ON soci1.CNPJ_SOCIO = cnae.CNPJ_CNAE and soci1.rownum = 1
LEFT JOIN soci_partition AS soci2
ON soci2.CNPJ_SOCIO = cnae.CNPJ_CNAE and soci2.rownum = 2
Give a try to below query
select * from cnae t1
left join emp t2 on t1.cnpj_cnae = t2.cnpj
left join socio t3 on t1.cnpj_cnae = t2.cnpj_socio
group by idx_cnae ;

MySQL - unique rows, corresponding to one of 3 tables only

The following query pulls data correctly as expected, however the left join with lnk_cat_isrc table and through that to catalogue table, brings back repeated data if there is more than one item in catalogue which has the same isrcs from isrc table:
SELECT
isrc.ISRC,
isrc.Track_Name,
isrc.ArtistName,
isrc.TitleVersion,
isrc.Track_Time,
`isrc_performer`.`PerformerName` ,
`performer_category`.`PerformerCategory` ,
`isrc_performer`.`PerformerRole` ,
`isrc`.`isrc_ID`,
`isrc_performer`.`Perf_ID`
FROM `isrc`
LEFT JOIN `isrc_performer` ON (isrc.isrc_ID = isrc_performer.isrc_ID)
LEFT JOIN `performer_category` ON (performer_category.PerfCat_ID = isrc_performer.PerfCat_ID)
LEFT JOIN `lnk_cat_isrc` ON (lnk_cat_isrc.isrc_ID = isrc.isrc_ID)
LEFT JOIN `catalogue` ON (catalogue.ID = lnk_cat_isrc.cat_id)
ORDER BY isrc_ID desc LIMIT 0 , 10
";
I cannot use group by on isrc, because the isrc_performer table can have more than one performer to an isrc.
So the relations are like this:
Few items from catalogue table can have several identical items from isrc table. In turn, each isrc can have more than one entry in isrc_performer table.
What I want is to display all corresponding data from isrc_performer in relation to each isrc, but not repeating it for each item from catalogue table.
I also want to display all the rest "empty" isrcs (those which don't have any data in isrc_performer table)
Can you give me any ideas?
P.S. despite I'm not pulling any data from catalogue table itself, I'm using it to search by a catalogue number, when user defines search criteria for $where_condition variable, hence I need to keep it in the query.
i.e. $where_condition = "catalogue.Catalogue LIKE '%test%' OR ISRC LIKE '%test%' OR Track_Name LIKE '%test%' OR ArtistName LIKE '%test%' OR TitleVersion LIKE '%test%' OR PerformerName LIKE '%test%' OR PerformerCategory LIKE '%test%' OR PerformerRole LIKE '%test%'";
------UPD:
trying to graphically represent possible variation in these 3 tables relations:
cat1 - isrc1 - performer1
isrc2 - performer1
- performer2
- performer3
cat2 - isrc2 - performer1
- performer2
- performer3
- isrc3 - performer2
- performer4
cat3 - isrc4
- isrc1 - performer1
UPD (pics added)
Here are screen prints. As you can see on picture 1 there are 9 rows with same isrc number, however there are 3 repeated performers Jason, David, Paul.
This is because 3 different catalogue items have this exact isrc with 3 different performers as per pic 2
= 1(isrc) * 3(catalogue) * 3(performers) = 9 row on output
All I want is that Performers grid would only display 3 rows of this isrc for each performer.
---Rearrange the answer to put the "best" option up top.. .but is all of this for naught.. w/o any data from lnk_cat_isrc or catalogue being returned, why does filtering on catalog make a difference? we're returning all isrc regardless of any filtering because it's a left join...
So this brings into question given sample data what are the expected results.
Possibly more elegant... (but not sure if it would be faster) moving away from exists and simply using a distinct in a subquery so catalog queries always return 1 row per isrc; solving the 1-M problem keeping the left join thereby keeping the isrc records not in the catalog limits.
Return all isrc information performer information if it exists, performer category info if it exists and catalogue information If, and only if it matches the catalog filters.
SELECT isrc.ISRC
, isrc.Track_Name
, isrc.ArtistName
, isrc.TitleVersion
, isrc.Track_Time
,`isrc_performer`.`PerformerName`
,`performer_category`.`PerformerCategory`
,`isrc_performer`.`PerformerRole`
,`isrc`.`isrc_ID`
,`isrc_performer`.`Perf_ID`
FROM `isrc`
LEFT JOIN `isrc_performer`
ON isrc.isrc_ID = isrc_performer.isrc_ID
LEFT JOIN `performer_category`
ON performer_category.PerfCat_ID = isrc_performer.PerfCat_ID
LEFT JOIN (SELECT distinct lnk_cat_isrc.isrc_ID
FROM `lnk_cat_isrc`
INNER JOIN `catalogue`
ON catalogue.ID = lnk_cat_isrc.cat_id
WHERE...) DCat
ON Dcat.isrc_ID = isrc.isrc_ID
ORDER BY isrc_ID desc
LIMIT 0 , 10;
As you pointed out the join is causing the problem. So eliminate the join and use the exists notation. Distinct would also work since you're not selecting any values from catalog; though exists should be faster.
Fast but doesn't include all isrc records... (not sure why the or not exists should bring them back in...)
SELECT isrc.ISRC
, isrc.Track_Name
,isrc.ArtistName
,isrc.TitleVersion
,isrc.Track_Time
,`isrc_performer`.`PerformerName`
,`performer_category`.`PerformerCategory`
,`isrc_performer`.`PerformerRole`
,`isrc`.`isrc_ID`
,`isrc_performer`.`Perf_ID`
FROM `isrc`
LEFT JOIN `isrc_performer`
ON (isrc.isrc_ID = isrc_performer.isrc_ID)
LEFT JOIN `performer_category`
ON (performer_category.PerfCat_ID = isrc_performer.PerfCat_ID)
WHERE EXISTS (SELECT *
FROM `lnk_cat_isrc`
INNER JOIN `catalogue`
ON catalogue.ID = lnk_cat_isrc.cat_id
--and your other criteria
WHERE (lnk_cat_isrc.isrc_ID = isrc.isrc_ID)
)
OR NOT EXISTS (SELECT *
FROM `lnk_cat_isrc`
WHERE lnk_cat_isrc.isrc_ID = isrc.isrc_ID
ORDER BY isrc_ID desc
LIMIT 0 , 10
Or using select distinct simple straight forward; but slow
SELECT isrc.ISRC
, isrc.Track_Name
,isrc.ArtistName
,isrc.TitleVersion
,isrc.Track_Time
,`isrc_performer`.`PerformerName`
,`performer_category`.`PerformerCategory`
,`isrc_performer`.`PerformerRole`
,`isrc`.`isrc_ID`
,`isrc_performer`.`Perf_ID`
FROM `isrc`
LEFT JOIN `isrc_performer`
ON (isrc.isrc_ID = isrc_performer.isrc_ID)
LEFT JOIN `performer_category`
ON (performer_category.PerfCat_ID = isrc_performer.PerfCat_ID)
LEFT JOIN `lnk_cat_isrc`
ON (lnk_cat_isrc.isrc_ID = isrc.isrc_ID)
LEFT JOIN `catalogue`
ON (catalogue.ID = lnk_cat_isrc.cat_id)
--AND (other criteria on catalog here, cause in a where clause you left joins will behave like inner joins)
ORDER BY isrc_ID desc
LIMIT 0 , 10;

Querying Many to Many When 3 Tables Connected to A junction

DB MODEL
I have Model Which is Connected in above Order Now I want to Select Style Names From Style Table.
Case 1 : I want the Style Names which are both intersecting Category and Gender
Case 2 : I want the Style Names which are intersecting Gender only regardless of category.
I am newbie to SQL is there any efficient way to Get the desired result using SQL JOINS . Any Help in such case will be appreciated or is there any modelling solutions to deal with such type of situations.
Here is how you would join your tables:
SELECT *
FROM `style`
INNER JOIN `StyleCategoryGender` ON `StyleCategoryGender`.`SID` = `style`.`Code`
INNER JOIN `StyleCategoryGender` ON `Category`.`Code` = `StyleCategoryGender`.`GID`
INNER JOIN `StyleCategoryGender` ON `Gender`.`Code` = `StyleCategoryGender`.`CID`
then you can add WHERE clauses as needed
SELECT *
FROM `style`
INNER JOIN `StyleCategoryGender` ON `StyleCategoryGender`.`SID` = `style`.`CODE`
INNER JOIN `StyleCategoryGender` ON `Category`.`Code` = `StyleCategoryGender`.`GID`
INNER JOIN `StyleCategoryGender` ON `Gender`.`Code` = `StyleCategoryGender`.`CID`
WHERE `Gender`.`Name` = 'Female'

MySQL join & search

I have a problem with joining some tables, heres my structure:
tbl_imdb:
fldID fldTitle fldImdbID
1 Moviename 0000001
tbl_genres:
fldID fldGenre
1 Action
2 Drama
tbl_genres_rel:
fldID fldMovieID fldGenreID
1 1 1
2 1 2
What I’m trying to do is a query that will find all movies that is both an action movie and drama, is this possible to do without a subquery, if so, how?
What I'm trying right now is:
SELECT tbl_imdb.*
FROM tbl_imdb
LEFT JOIN tbl_imdb_genres_rel ON ( tbl_imdb.fldID = tbl_imdb_genres_rel.fldMovieID )
LEFT JOIN tbl_imdb_genres ON ( tbl_imdb_genres_rel.fldGenreID = tbl_imdb_genres.fldID )
WHERE tbl_imdb_genres.fldGenre = 'Drama'
AND tbl_imdb_genres.fldGenre = 'Action';
But this dosnt work, however it does work if I only keep one of the two WHERE's, but thats not what I want.
Two ways to do it:
1
SELECT tbl_imdb.*
FROM tbl_imdb
INNER JOIN tbl_genres_rel rel_action
ON tbl_imdb.fldID = rel_action.fldMovieID
INNER JOIN tbl_genres genre_action
ON rel_action.fldGenreId = genre_action.fldID
AND 'Action' = genre_action.fldGenre
INNER JOIN tbl_genres_rel rel_drama
ON tbl_imdb.fldID = rel_drama.fldMovieID
INNER JOIN tbl_genres genre_drama
ON rel_drama.fldGenreId = genre_drama.fldID
AND 'Drama' = genre_drama.fldGenre
This method is on the same path as your original solution. 2 differences:
The join should be inner, not left because you're trying to get movies that certainly have the corresponding genre entry
Since you want to find 2 different generes, you'll have to do the join with tbl_genres_rel and tbl_genres twice, once for each particular genre you're interested in.
2
SELECT tbl_imdb.*
FROM tbl_imdb
INNER JOIN tbl_genres_rel
ON tbl_imdb.fldID = tbl_genres_rel.fldMovieID
INNER JOIN tbl_genres
ON tbl_genres_rel.fldGenreId = tbl_genres.fldID
AND tbl_genres.fldGenre IN ('Action', 'Drama')
GROUP BY tbl_imdb.fldID
HAVING COUNT(*) = 2
Again, the basic join plan is the same. Difference here is that we join to the tbl_genres_rel and tbl_genres path just once. This on itself fetches all genres for one film, and then filters for the one's you're interested in. The ones that qualify will now have 2 rows for each distinct value of tbl_imdb.fldId. The GROUP BY aggregates on that, flattening that into one row. By asserting in the HAVING clause that we have exactly 2 rows, we ensure that we keep only those rows that have both the genres.
(Note that this assumes that there is a unique constraint on tbl_genres_rel over {fldMovieID, fldGenreID}. If such a constraint is not present, you should consider adding it.)
LEFT JOIN is not applicable in your case because records should exist on both tables. And you need to count the instances of the movie
SELECT *
FROM tbl_imdb a
INNER JOIN tbl_genres_rel b
on a.fldID = fldMovieID
INNER JOIN tbl_genres c
on c.fldGenreID = b.fldID
WHERE c.fldGenre IN ('Drama', 'Action')
GROUP BY a.Moviename
HAVING COUNT(*) > 1

SQL: Get latest entries from history table

I have 3 tables
person (id, name)
area (id, number)
history (id, person_id, area_id, type, datetime)
In this tables I store the info which person had which area at a specific time. It is like a salesman travels in an area for a while and then he gets another area. He can also have multiple areas at a time.
history type = 'I' for CheckIn or 'O' for Checkout.
Example:
id person_id area_id type datetime
1 2 5 'O' '2011-12-01'
2 2 5 'I' '2011-12-31'
A person started traveling in area 5 at 2011-12-01 and gave it back on 2011-12-31.
Now I want to have a list of all the areas all persons have right now.
person1.name, area1.number, area2.number, area6.name
person2.name, area5.number, area9.number
....
The output could be like this too (it doesn't matter):
person1.name, area1.number
person1.name, area2.number
person1.name, area6.number
person2.name, area5.number
....
How can I do that?
This question is, indeed, quite tricky. You need a list of the entries in history where, for a given user and area, there is an 'O' record with no subsequent 'I' record. Working with just the history table, that translates to:
SELECT ho.person_id, ho.area_id, ho.type, MAX(ho.datetime)
FROM History AS ho
WHERE ho.type = 'O'
AND NOT EXISTS(SELECT *
FROM History AS hi
WHERE hi.person_id = ho.person_id
AND hi.area_id = ho.area_id
AND hi.type = 'I'
AND hi.datetime > ho.datetime
)
GROUP BY ho.person_id, ho.area_id, ho.type;
Then, since you're really only after the person's name and the area's number (though why the area number can't be the same as its ID I am not sure), you need to adapt slightly, joining with the extra two tables:
SELECT p.name, a.number
FROM History AS ho
JOIN Person AS p ON ho.person_id = p.id
JOIN Area AS a ON ho.area_id = a.id
WHERE ho.type = 'O'
AND NOT EXISTS(SELECT *
FROM History AS hi
WHERE hi.person_id = ho.person_id
AND hi.area_id = ho.area_id
AND hi.type = 'I'
AND hi.datetime > ho.datetime
);
The NOT EXISTS clause is a correlated sub-query; that tends to be inefficient. You might be able to recast it as a LEFT OUTER JOIN with appropriate join and filter conditions:
SELECT p.name, a.number
FROM History AS ho
JOIN Person AS p ON ho.person_id = p.id
JOIN Area AS a ON ho.area_id = a.id
LEFT OUTER JOIN History AS hi
ON hi.person_id = ho.person_id
AND hi.area_id = ho.area_id
AND hi.type = 'I'
AND hi.datetime > ho.datetime
WHERE ho.type = 'O'
AND hi.person_id IS NULL;
All SQL unverified.
You're looking for results where each row may have a different number of columns? I think you may want to look into GROUP_CONCAT()
SELECT p.`id`, GROUP_CONCAT(a.`number`, ',') AS `areas` FROM `person` a LEFT JOIN `history` h ON h.`person_id` = p.`id` LEFT JOIN `area` a ON a.`id` = h.`area_id`
I haven't tested this query, but I have used group concat in similar ways before. Naturally, you will want to tailor this to fit your needs. Of course, group concat will return a string so it will require post processing to use the data.
EDIT I thikn your question has been edited since I began responding. My query does not really fit your request anymore...
Try this:
select *
from person p
inner join history h on h.person_id = p.id
left outer join history h2 on h2.person_id = p.id and h2.area_id = h.area_id and h2.type = 'O'
inner join areas on a.id = h.area_id
where h2.person_id is null and h.type = 'I'