Left Join on foreign keys - mysql

Perhaps my failing is not knowing the terminology, but I am looking to do perform a JOIN on two tables with another table as the foreign keys.
So the id of an item on table A is used to get the foreign key from table B, which is used to get the textual representation from table C.
TABLE A
+----+------------+
| id | name |
+----+------------+
| 1 | comment |
+----+------------+
TABLE B
+-----------+------------------+
| object_id | cat_id_ref |
+-----------+------------------+
| 1 | 2 |
+-----------+------------------+
| 1 | 3 |
+-----------+------------------+
TABLE C
+---------+----------+
| cat_id | cat_type |
+---------+----------+
| 1 | Mean |
| 2 | Nice |
| 3 | Rude |
+---------+----------+
So the question is 2 part. what is this 1 step away foreign key lookup called, terminology, and for MySQL is this sub query? Best practice for this type of JOIN
The desired result would be:
+----+------------+------------+
| id | name | cat_type |
+----+------------+------------+
| 1 | veg 1 | Nice |
+----+------------+------------+
| 1 | veg 1 | Rude |
+----+------------+------------+

This looks like a many-to-many relationship. I refer to TABLE B as an "association" table (or, sometimes a "relationship" table, in some cases, this can be called a "membership" table.)
From a database design standpoint, we do "Entity Relationship Modeling". An "entity" is a person, place, thing, concept, or event that can be uniquely identified, is of interest to the business, and we can store information about. And we have "relationships" between those entities. When we have a relationship identified, we ask the right questions, to find out how many of one entity are related to another.
In your example, it looks like B implements an association, between entities A and C.
An "A" can be related to zero, one or more "C".
A "C" can be related to zero, one or more "A".
(This is where having a suitable name to identify an entity can make the model more understandable.)
To resolve the many-to-many, we implement a third table, which we could name "A_C" or "C_A", or it could have a totally different name. (For example, the association between "COMMITTEE" and "PERSON" might be called "MEMBERSHIP").
The new table, in your example, named "B"
A "B" is related to exactly one "A"
A "B" is related to exactly one "C"
An "A" is related to zero, one or more "B"
A "C" is related to zero, one or more "B"
The introduction of the "B" table resolves the many-to-many relationship into two "one-to-many" relationships.
(It looks like you already have an understanding of how a foreign key goes on the table on the "many" side of the relationship, and how that refers to the primary key (or unique key) of the entity table on "one" side of the relationship: the value of the foreign key is a copy of the primary key value from the other table.)
As to the second part of your question, here's an example of a query that will return the specified resultset:
SELECT a.id
, a.name
, c.veg_type
FROM A a
LEFT
JOIN B b
ON b.object_id = a.id
LEFT
JOIN C c
ON c.veg_id = b.veg_type_ref
(There are different queries that will return the same resultset; the big differences will be in the handling of rows that are "missing" (for example, a row in table A that has no "matching" row in table B. The other differences are performance, depending on cardinality, selectivity, available indexes, etc.)
These are all JOINs; there's no need to introduce a subquery to get the specified resultset.

It sounds like you're looking for something like this.
select A.*, C.veg_type
from A
inner join B
on A.id = B.object_id
inner join C
on C.veg_id = B.veg_id_ref

Basically, just a one - to - many relationship
Can a veg have more than 1 type? This example seems too simple to require the middle table.
select id,name, veg_type
from TableA
inner join TableB on Tablea.id = tableb.object_id
inner join TableC on tableb.veg_id_ref = tablec.id

Related

get data from junction table as null if no relation

I have 3 tables inputs, posts, and posts_values:
I need to get all inputs with there values, and if value doesn't exist get null instead
like (input, value)
name: David, job:Developer, city:NULL
You need to LEFT JOIN the inputs table like this:
SELECT i.input_name, pv.value
FROM inputs as i
LEFT JOIN posts AS p ON i.form_id = p.id
LEFT JOIN post_values AS pv ON pv.b_id = p.id
AND pv.a_id = i.id;
This will give you:
| input_name | value |
|------------|-----------|
| name | David |
| job | Developer |
| city | (null) |
sql fiddle demo
Note that:, I assumed that a_id on the third table post_values is a foreign key for the column id in inputs table, so that each input name is matched with a value. Otherwise, you might need to make this a_id a surrogate key, and add an additional foreign key input_id.
You need to read about SQL joins and the difference between them, check this for example:
A Visual Explanation of SQL Joins

How do I turn a list of interconnected pairs of ids into a cluster of ids?

I have a table with pairs (and sometimes triples) of ids, which act as sort of links in a chain
+------+-----+
| from | to |
+------+-----+
| id1 | id2 |
| id2 | id3 |
| id4 | id5 |
+------+-----+
I want to create a new table where all the links are clustered into chains/families:
+-----+----------+
| id | familyid |
+-----+----------+
| id1 | 1 |
| id2 | 1 |
| id3 | 1 |
| id4 | 2 |
| id5 | 2 |
+-----+----------+
i.e. add up all chains in a link into a single family, and give it an id.
in the example above, the first 2 rows of the first table create one family, and the last row creates another family.
Solution
I will use node.js to query big batches of rows (a few thousands every batch), process them, and insert them into my own table with a family id.
The issue
The problem is I have a few tens of thousands of id pairs, and I will also need to add new ids over time after the initial creation of the families table, and i will need to add ids to existing families
Are there good algorithms for clustering pairs of data into families/clusters, keeping my issue in mind?
Not sure if it's an answer as more some ideas...
I created two tables similar to the ones you have, the first one I populated with the same data as you have.
Table Base, fromID, toID
Table chain, fromID, chainID (numeric, null allowed)
I then inserted all unique values from Base into chain with a null value for chainID. The idea being these are the rows as yet unprocessed.
It was then a case of repeatedly running a couple of statements...
update chain c
set chainID = n
where chainid is null and exists ( select 1 from base b where b.fromID = c.fromID )
order by fromID
limit 1
This would allocate the next chain ID to the first row without one (n needs to be generated from somewhere and incremented each time you run this)
Then the one that relates all of the records...
update chain c
join base b on b.toID = c.fromID
join chain c1 on b.fromID = c1.fromID
set c.chainID = c1.chainID
where c.chainID is null and c1.chainID is not null
This is run repeatedly until it affects 0 rows (i.e. it's nothing more to do).
Then run the first update to create the next chain etc. Again if you run the first update till it affects 0 rows, this shows that they are all linked.
Would be interested if you want to try this and see if it stands up with more complex scenarios.
This looks a lot like clustering over graph dataset where 'familyid' is the cluster center number.
Here is a question I think is relevant.
Here is the algorithm description. You will need to implement under the conditions you described.

Select column by value in another table column

I have two tables:
Table A:
id | name
Table B:
id | hash | owners_id
owners_id contains the ids from table A.
Example:
Table A:
id | name
1 | James
2 | Jonas
Table B:
id | hash | owners_id
1 | j28sj | 1,2
Expect Result:
James | j28sj
Jonas | j28sj
Because both contain the ownerds_id
I'm trying to make a query that selects all the names from table A associates with table B owners_id column.
SELECT
A.name,
B.hash
FROM
A
left JOIN B ON
B.owners_id LIKE CONCAT('%', A.id, '%')
Note: The database you designed is poorly designed and it may not work it you have owners_id like 1,11,111 .so either you need to make seperate table with many to many relation or put leading zeros like 001,011,111
There are a couple ways to do this. If you want to keep owners_id as a comma-separated string, it's a bit messy. You need to first parse the string into a list of integers to form the join condition:
SELECT A.name, B.hash FROM A
LEFT JOIN B
ON find_in_set(A.id,B.owners_id) <> 0;
You may want to consider letting owners_id be an integer foreign key to Table A, if you can change your schema.
Here's a working SQL fiddle:
http://sqlfiddle.com/#!9/320477/4

MySQL query get column value similar to given

Sorry if my question seems unclear, I'll try to explain.
I have a column in a row, for example /1/3/5/8/42/239/, let's say I would like to find a similar one where there is as many corresponding "ids" as possible.
Example:
| My Column |
#1 | /1/3/7/2/4/ |
#2 | /1/5/7/2/4/ |
#3 | /1/3/6/8/4/ |
Now, by running the query on #1 I would like to get row #2 as it's the most similar. Is there any way to do it or it's just my fantasy? Thanks for your time.
EDIT:
As suggested I'm expanding my question. This column represents favourite artist of an user from a music site. I'm searching them like thisMyColumn LIKE '%/ID/%' and remove by replacing /ID/ with /
Since you did not provice really much info about your data I have to fill the gaps with my guesses.
So you have a users table
users table
-----------
id
name
other_stuff
And you like to store which artists are favorites of a user. So you must have an artists table
artists table
-------------
id
name
other_stuff
And to relate you can add another table called favorites
favorites table
---------------
user_id
artist_id
In that table you add a record for every artist that a user likes.
Example data
users
id | name
1 | tom
2 | john
artists
id | name
1 | michael jackson
2 | madonna
3 | deep purple
favorites
user_id | artist_id
1 | 1
1 | 3
2 | 2
To select the favorites of user tom for instance you can do
select a.name
from artists a
join favorites f on f.artist_id = a.id
join users u on f.user_id = u.id
where u.name = 'tom'
And if you add proper indexing to your table then this is really fast!
Problem is you're storing this in a really, really awkward way.
I'm guessing you have to deal with an arbitrary number of values. You have two options:
Store the multiple ID's in a blob object in JSON format. While MySQL doesn't have JSON functions built in, there are user defined functions that will extract values for you, etc.
See: http://blog.ulf-wendel.de/2013/mysql-5-7-sql-functions-for-json-udf/
Alternatively, switch to PostGres
Add as many columns to your table as the maximum number of ID's you expect to have. So if /1/3/7/2/4/8/ is the longest entry, have 6 columns in your table. Reason this is bad: you'll have sparse columns that'll unnecessarily slow your tables.
I'm sure you could write some horrific regex to accomplish the task, but I caution on using complex regex's on enormous tables.

What type of Join to use?

I've got a core table and and 3 tables that extend the 'core' table in different ways.
I'm working with MLS data and I have a 'common' table that contains information common to all mls listings and then a table that has specifically "residential" information, one for "commercial",etc... I have been using mls number to join a single table when I know a listing when the property type is known, but for searching I want to join all of them and have the special fields available for search criteria (not simply searching the common table).
What type of join will give me a dataset that will contain all listings (including the extended fields in the idx tables) ?
For each Common table record there is a single corresponding record in ONLY ONE of the idx tables.
___________
| |
| COMMON |
| |
|___________|
_|_
|
___________________|_____________________
_|_ _|_ _|_
_____|_____ _____|______ ____|______
| | | | | |
| IDX1 | | IDX2 | | IDX3 |
| | | | | |
|___________| |____________| |___________|
If you want everything in one row, you can use something like this format. Basically it gives you all the "Common" fields, then the other fields if there is a match otherwise NULL:
SELECT Common.*,
Idx1.*,
Idx2.*,
Idx3.*
FROM Common
LEFT JOIN Idx1
ON Idx1.MLSKey = Common.MLSKey
LEFT JOIN Idx2
ON Idx2.MLSKey = Common.MLSKey
LEFT JOIN Idx3
ON Idx3.MLSKey = Common.MLSKey
Bear in mind it's better to list out fields than to use the SELECT * whenever possible...
Also I'm assuming MySQL syntax is the same as SQL Server, which is what I use.
I have a similar set up of tables where the table 'jobs' is the core table.
I have this query that selects certain elements from each of the other 2 tables:
SELECT jobs.frequency, twitterdetails.accountname, feeds.feed
FROM jobs
JOIN twitterdetails ON twitterdetails.ID = jobs.accountID
JOIN feeds ON jobs.FeedID = feeds.FeedID
WHERE jobs.username ='".$currentuser."';");
So, as you can see, no specific JOIN, but the linking fields defined. You'd probably just need an extra JOIN line for your set up.
Ugly solution / poor attempt / may have misunderstood question:
SELECT common.*,IDX1.field,NULL,NULL FROM COMMON
LEFT JOIN IDX1 ON COMMON.ID = IDX1.ID
WHERE TYPE="RESIDENTIAL"
UNION ALL
SELECT common.*,NULL,IDX2.field,NULL FROM COMMON
LEFT JOIN IDX2 ON COMMON.ID = IDX2.ID
WHERE TYPE="RESIDENTIAL"
UNION ALL
SELECT common.*,NULL,NULL,IDX3.field FROM COMMON
LEFT JOIN IDX3 ON COMMON.ID = IDX3.ID
WHERE TYPE="INDUSTRIAL"
Orbit is close. Use inner join, not left join. You don't want common to show up in the join if it does not have a row in idx.
You MUST union 3 queries to get the proper results assuming each record in common can only have 1 idx table. Plug in "NULL" to fill in the columns that each idx table is missing so they can be unioned.
BTW your table design is good.