mysql - how to join table twice with the same table - mysql

I have this two tables:
Main:
id | name | hair_color | eye_color
1 | a | 1 | 2
2 | b | 1 | 3
3 | c | 4 | 3
Items:
id | name
1 | black
2 | blue
3 | green
4 | blonde
I want to select one row from the Main table but replace the hair_color and eye_color ids by theirs name that fits by the Items table.
I mean, for row number 1 in Main table, I want to get this details:
$res = array(
id=>1,
name=>'a',
hair_color=>'black',
eye_color=>'blue');
I tried this incorrect mysql query:
SELECT `main`.`id`, `main`.`name`, `items`.`name` as `hair_color`, `items`.`name` as `eye_color`
FROM `main`
LEFT JOIN `items` ON `main`.`hair_color` = `items`.`id
LEFT JOIN `items` ON `main`.`eye_color` = `items`.`id
WHERE `main`.`id` = 1
I know that this query is incorrect, but I don't know how to do this right.
Any idea?
Thanks
EDIT:
Thanks all!

You were close, but you needed aliases.
Something like
SELECT `main`.`id`,
`main`.`name`,
h.`name` as `hair_color`,
e.`name` as `eye_color`
FROM `main`
LEFT JOIN `items` h ON `main`.`hair_color` = h.id
LEFT JOIN `items` e ON `main`.`eye_color` = e.id
WHERE `main`.`id` = 1
SQL Fiddle DEMO

You just need to give aliases to your items tables:
SELECT `main`.`id`, `main`.`name`,
hi.`name` as `hair_color`, ei.`name` as `eye_color`
FROM `main`
LEFT JOIN `items` hi ON `main`.`hair_color` = hi.`id`
LEFT JOIN `items` ei ON `main`.`eye_color` = ei.`id`

Related

SQL Order by parent and child

Basically I need help in my query here. I want to be in right order which is child must be under parents name and in A-Z order. But if I add a subChild under child (Split 1) seem the order is wrong. It should be under Room Rose.
p/s : A subChild also can create another subChild
HERE I PROVIDE A DEMO
Appreciate your help me get this ordered correctly?
SELECT A.venueID
, B.mainVenueID
, A.venueName
FROM tblAdmVenue A
LEFT
JOIN tblAdmVenueLink B
ON A.venueID = B.subVenueID
ORDER
BY COALESCE(B.mainVenueID, A.venueID)
, B.mainVenueID IS NOT NULL
, A.venueID
I want it return an order something like this.
venueName
--------------
Banquet
Big Room
-Room Daisy
-Room Rose
-Split 1
Hall
-Meeting Room WP
Seem this recursive approach also in not working
WITH venue_ctg AS (
SELECT A.venueID, A.venueName, B.mainVenueID
FROM tblAdmVenue A LEFT JOIN tblAdmVenueLink B
ON A.venueID = B.subVenueID
WHERE B.mainVenueID IS NULL
UNION ALL
SELECT A.venueID, A.venueName, B.mainVenueID
FROM tblAdmVenue A LEFT JOIN tblAdmVenueLink B
ON A.venueID = B.subVenueID
WHERE B.mainVenueID IS NOT NULL
)
SELECT *
FROM venue_ctg ORDER BY venueName
output given
For your data you can use this:
To display this correctly, you can use a SEPARATPR like comma, and split the returned data, and check the hirarchy
-- schema
CREATE TABLE tblAdmVenue (
venueID VARCHAR(225) NOT NULL,
venueName VARCHAR(225) NOT NULL,
PRIMARY KEY(venueID)
);
CREATE TABLE tblAdmVenueLink (
venueLinkID VARCHAR(225) NOT NULL,
mainVenueID VARCHAR(225) NOT NULL,
subVenueID VARCHAR(225) NOT NULL,
PRIMARY KEY(venueLinkID)
-- FOREIGN KEY (DepartmentId) REFERENCES Departments(Id)
);
-- data
INSERT INTO tblAdmVenue (venueID, venueName)
VALUES ('LA43', 'Big Room'), ('LA44', 'Hall'),
('LA45', 'Room Daisy'), ('LA46', 'Room Rose'),
('LA47', 'Banquet'), ('LA48', 'Split 1'),
('LA49', 'Meeting Room WP');
INSERT INTO tblAdmVenueLink (venueLinkID, mainVenueID, subVenueID)
VALUES ('1', 'LA43', 'LA45'), ('2', 'LA43', 'LA46'),
('3', 'LA46', 'LA48'), ('4', 'LA44', 'LA49');
✓
✓
✓
✓
with recursive cte (subVenueID, mainVenueID,level) as (
select subVenueID,
mainVenueID, 1 as level
from tblAdmVenueLink
union
select p.subVenueID,
cte.mainVenueID,
cte.level+1
from tblAdmVenueLink p
inner join cte
on p.mainVenueID = cte.subVenueID
)
select
CONCAT(GROUP_CONCAT(b.venueName ORDER BY level DESC SEPARATOR '-->') ,'-->',a.venueName)
from cte c
LEFT JOIN tblAdmVenue a ON a.venueID = c.subVenueID
LEFT JOIN tblAdmVenue b ON b.venueID = c.mainVenueID
GROUP BY subVenueID;
| CONCAT(GROUP_CONCAT(b.venueName ORDER BY level DESC SEPARATOR '-->') ,'-->',a.venueName) |
| :----------------------------------------------------------------------------------------- |
| Big Room-->Room Daisy |
| Big Room-->Room Rose |
| Big Room-->Room Rose-->Split 1 |
| Hall-->Meeting Room WP |
db<>fiddle here
You want your data ordered in alphabetical order and depth first.
A common solution for this is to traverse the structure from the top element, concatenating the path to each item as you go. You can then directly use the path for ordering.
Here is how to do it in MySQL 8.0 with a recursive query
with recursive cte(venueID, venueName, mainVenueID, path, depth) as (
select v.venueID, v.venueName, cast(null as char(100)), venueName, 0
from tblAdmVenue v
where not exists (select 1 from tblAdmVenueLink l where l.subVenueID = v.venueID)
union all
select v.venueID, v.venueName, c.venueID, concat(c.path, '/', v.venueName), c.depth + 1
from cte c
inner join tblAdmVenueLink l on l.mainVenueID = c.venueID
inner join tblAdmVenue v on v.venueID = l.subVenueID
)
select * from cte order by path
The anchor of the recursive query selects top nodes (ie rows whose ids do not exist in column subVenueID of the link table). Then, the recursive part follows the relations.
As a bonus, I added a level column that represents the depth of each node, starting at 0 for top nodes.
Demo on DB Fiddle:
venueID | venueName | mainVenueID | path | depth
:------ | :-------------- | :---------- | :------------------------- | ----:
LA47 | Banquet | null | Banquet | 0
LA43 | Big Room | null | Big Room | 0
LA45 | Room Daisy | LA43 | Big Room/Room Daisy | 1
LA46 | Room Rose | LA43 | Big Room/Room Rose | 1
LA48 | Split 1 | LA46 | Big Room/Room Rose/Split 1 | 2
LA44 | Hall | null | Hall | 0
LA49 | Meeting Room WP | LA44 | Hall/Meeting Room WP | 1
Use only one table, not two. The first table has all the info needed.
Then start the CTE with the rows WHERE mainVenueID IS NULL, no JOIN needed.
This may be a good tutorial: https://stackoverflow.com/a/18660789/1766831
Its 'forest' is close to what you want.
I suppose you have:
table tblAdmVenue A is the venue list; and
table tblAdmVenueLink B is the tree relation table for parent-child
For your question on how to get a correct sorting order, I think one of the trick is to concatenate the parent venue names.
with q0(venueID, venueName, mainVenueID, venuePath) as (
select
A.venueID,
A.venueName,
null,
A.venueName
from tblAdmVenue A
left join tblAdmVenue B on A.venueID = B.subVenueID
where B.mainVenueID is null
union all
select
A.venueID,
A.venueName,
q0.venueID,
q0.venuePath + char(9) + A.venueName
from q0
inner join tblAdmVenue B on q0.venueID = B.mainVenueID
inner join tblAdmVenue A on A.venueID = B.subVenueID
)
select venueID, venueName, mainVenueID
from q0
order by venuePath

How to find all the opposite combinations between two columns in SQL

I am making a web dating app that needs to match users and let them chat with each other.
I want to figure out how to find all the matches for a particular user.
Right now I have a table called follows that has 2 columns.
UserID | MatchUserID
--------------------
1 | 2
2 | 1
1 | 3
1 | 4
1 | 5
4 | 1
5 | 4
The idea is that for two users to match they need to follow one another. The table above shows which user follows which.
Assuming that the user who is currently logged on is UserID = 1.
I need a query that will return from the MatchUserID table the following results:
2, 4
In a way, I am looking to find all the opposite combinations between the two columns.
This is the code I use to create the table.
CREATE TABLE Match
(
UserID INT NOT NULL,
MatchUserID INT NOT NULL,
PRIMARY KEY (UserID, MatchUserID)
);
You can do it with a self join:
select m.MatchUserID
from `Match` m inner join `Match` mm
on mm.MatchUserID = m.UserId
where
m.UserId = 1
and
m.MatchUserID = mm.UserId
See the demo.
Results:
| MatchUserID |
| ----------- |
| 2 |
| 4 |
The simplest way possibly is to use EXISTS and a correlated subquery that searches for the other match.
SELECT t1.matchuserid
FROM elbat t1
WHERE t1.userid = 1
AND EXISTS (SELECT *
FROM elbat t2
WHERE t2.matchuserid = t1.userid
AND t2.userid = t1.matchuserid);

Return 2 rows from 2nd table INNER JOIN

I've 2 tables, crm_lead and statussen.
This is (part of) content table statussen
stat_id | stat_type | stat_naam
1 | lead_status | Ingevoerd CJV
2 | lead_status | Ingevoerd website
... | ... | ...
11 | lead_bron | Zelf gegenereerd
12 | lead_bron | Bestaande klant
In lead i've 1 column "lead_status" and "lead_bron". Both have the id of the corresponding row from table statussen.
When I run following query:
SELECT * FROM crm_lead
INNER JOIN statussen on crm_lead.lead_status=statussen.stat_id
WHERE lead_id=31
The result is only showing one row of table statussen (lead_status) and not lead_bron...
I get the single row from table crm_lead with lead_status = 1 and lead_bron = 11. But I also get only 1 row from table statussen: stat_id = 1. But I also need the row with stat_id = 11 from that table..
It's because you are searching for only lead_status and not lead_bron (in your join).
This is one way to get what you're looking for:
SELECT *
FROM
crm_lead
INNER JOIN statussen ON crm_lead.lead_status=statussen.stat_id
WHERE lead_id=31
UNION ALL
SELECT *
FROM
crm_lead
INNER JOIN statussen ON crm_lead.lead_bron=statussen.stat_id
WHERE lead_id=31

Select from one table but filtering other two

Let's say i've got this database:
book
| idBook | name |
|--------|----------|
| 1 |Book#1 |
category
| idCateg| category |
|--------|----------|
| 1 |Adventures|
| 2 |Science F.|
book_categ
| id | idBook | idCateg | DATA |
|--------|--------|----------|--------|
| 1 | 1 | 1 | (null) |
| 2 | 1 | 2 | (null) |
I'm trying to select only the books which are in category 1 AND category 2 something like this
SELECT book.* FROM book,book_categ
WHERE book_categ.idCateg = 1 AND book_categ.idCateg = 2
Obviously, this giving 0 results becouse each row has only one idCateg it does work width OR but the results are not what I need. I've also tried to use a join, but I just can't get the results I expect.
Here it's the SQLFiddle of my current project, with my current DB, the data at the begining is just a sample. SQLFiddle
Any help will be really appreciated.
Solution using EXISTS:
select *
from book b
where exists (select 'x'
from book_categ x
where x.idbook = b.idbook
and x.idcateg = 1)
and exists (select 'x'
from book_categ x
where x.idbook = b.idbook
and x.idcateg = 2)
Solution using join with an inline view:
select *
from book b
join (select idbook
from book_categ
where idcateg in (1, 2)
group by idbook
having count(*) = 2) x
on b.idbook = x.idbook
You could try using ALL instead of IN (if you only want values that match all criteria to be returned):
SELECT book.*
FROM book, book_categ
WHERE book_categ.idCateg = ALL(1 , 2)
One way to get the result is to do join to the book_categ table twice, something like
SELECT b.*
FROM book b
JOIN book_categ c1
ON c1.book_id = b.id
AND c1.idCateg = 1
JOIN book_categ c2
ON c2.book_id = b.id
AND c2.idCateg = 2
This assumes that (book_id, idCateg) is constrained to be unique in the book_categ table. If it isn't unique, then this query can return duplicate rows. Adding a GROUP BY clause or the DISTINCT keyword will eliminate any generated duplicates.
There are several other queries that can get generate the same result.
For example, another approach to finding book_id that are in two categories is to get all the rows with idCateg values of 1 or 2, and then GROUP BY book_id and get a count of DISTINCT values...
SELECT b.*
FROM book b
JOIN ( SELECT d.book_id
FROM book_categ d
WHERE d.idCateg IN (1,2)
GROUP BY d.book_id
HAVING COUNT(DISTINCT d.idCateg) = 2
) c
ON c.book_id = b.id

Mysql JOIN query MAX value

This is example of the problem i am having. The query should return rows paul and rick because they have the highest rating of the child rows. Instead the query is returning dave and owen, my guess is because they are the first child rows. I am grouping by position and using MAX(child.rating) but the query isn't working like i want it to be. In the real table i have alot of columns that is why i use child.* in the select clause.
mytable
id | name | parentid| position| rating |
1 | mike | 1 | 1 | 6 |
2 | dave | 1 | 2 | 5 |
3 | paul | 1 | 2 | 7 |
4 | john | 1 | 2 | 3 |
5 | mike | 5 | 1 | 8 |
6 | owen | 5 | 2 | 2 |
7 | rick | 5 | 2 | 9 |
8 | jaye | 5 | 2 | 3 |
$getquery = mysql_query("SELECT MAX(child.rating),child.* FROM mytable child
LEFT JOIN mytable parent on parent.parentid=child.parentid
WHERE parent.name LIKE '%mike%' GROUP BY child.position,child.parentid");
while($row=mysql_fetch_assoc($getquery)) {
$id = $row['id'];
$name = $row['name'];
$parentid = $row['parentid'];
if($id==$parentid) {
continue;
}
echo "<p>Name: $name </p>";
}
You can use a subquery in from clause to first figure out what is the maximum rating for each parent and then get the children with that rating:
select *
from mytable c
join
(select parentid, max(rating) as 'maxrating'
from mytable m
group by parentid) as q on c.parentid=q.parentid and c.rating = q.maxrating;
Funny thing, I've just realized what you're looking for. Here is the final query:
select t1.* from mytable t1
left join mytable t2
on t1.parentid = t2.parentid and t1.rating < t2.rating
join mytable parents
on parents.id = t1.parentid
where t2.rating is null and parents.name like '%mike%'
And here is a working example
This is the way mysql's group by works, and is actually working correctly.
There are two way around it, either a subquery or joins that get the top most child, and you probably want to reorder the way your tables are
Here's the join method (if i'm understanding your data correctly):
SELECT child.*
FROM mytable parent
LEFT JOIN mytable child ON parent.parentid=child.parentid
LEFT JOIN mytable child2 ON child.parentid=child2.parentid AND child2.rating > child.rating AND child2.parentid IS NULL
WHERE parent.name LIKE '%mike%' AND parent.position = 1 AND child.position <> 1
This makes the assumption that parants always have a position of 1, and children do not. You may need to also add another bit to the child2 join to remove the possibility of parents having a higher rating than the children?
The 2nd join makes sure there are no other children with a higher rating for each parent.
This must be what you're trying to do (although I'm unsure if're really comparing child's parentid with parent's parentid):
SELECT child.* FROM mytable child
INNER JOIN mytable parent on parent.parentid=child.parentid
LEFT JOIN mytable child2 ON (child2.parentid = parent.parentid AND child2.position = child.position AND child2.rating > child.rating)
WHERE parent.name LIKE '%mike%' AND child2.parentid IS NULL
GROUP BY child.position, child.parentid
HAVING `better` = 0;
Another option would be to use a subquery, but you should check which works faster:
SELECT child.*
FROM (
SELECT MAX(child.rating) maxrating, child.parentid, child.position FROM mytable child
INNER JOIN mytable parent on parent.parentid=child.parentid
WHERE parent.name LIKE '%mike%'
GROUP BY child.position,child.parentid
) h
INNER JOIN mytable child ON (child.parentid = h.parentid AND child.position = h.position AND child.rating = h.maxrating)
performance may be very different on tables of different sizes.
If I haven't got your point right, I still suggest you use INNER JOINs instead of OUTERs if you don't need anything for which there's nothing to join. INNER JOINs are usually way faster.
I actually think second one will work faster on larger tables.
add:
ORDER BY child.rating DESC