Storing Item Relationships in MySQL - mysql

Background
I want to be able to store complex relationships between products in a MySQL database.
I currently have two tables. The first is a list of products:
ID | sku | division | retired
The second table is a relationship table
ID | prod1 | relation | prod2
With the idea being that prod2 has relation to prod.
Example Rows
Product Table:
1 | lamp | household | false
2 | lamp-short | household | false
3 | chair| household | false
4 | desk | household | false
5 | desk-realwood | household | false
6 | desk-extratall | household | false
6 | couch | household | false
Relations Table:
1 | 1 | child | 2 <- short lamp is child of lamp
2 | 4 | child | 5 <- real wood desk is child of desk
2 | 4 | child | 6 <- extra tall desk is child of desk
Then on a single product page for lamp, I'd like to have a "Related Products" box, so I need to pull in all related products. So I need an SQL query, that given the table names of products and product_relations and the ID of the current product, can pull in all rows in the products table that are related to the current product.
IE, if I'm looking at lamp, I need to pull in lamp-short.
I think I need to use a JOIN, but when I've tried it, I can't get it to work.
SELECT *
FROM `products`
INNER JOIN `product_relations` on products.id = ( product_relations.prod2 WHERE product_relations.prod1 = 1 )
5 being provided to the query.
Question
How would I write my query if I knew the ID of the current product, to get all related products?

If you wanted to get the related product sku and it's relationship to the original product searching by it's sku, you could use:
SELECT rp.sku, r.relation
FROM product p
INNER JOIN relations r ON p.id = r.prod1
INNER JOIN product rp on r.prod2 = rp.id
WHERE p.sku = '<original sku>'
This can be shortened, if you are searching by the original product's id, since then you don't really need the p aliased table above:
SELECT rp.sku, r.relation
FROM relations r
INNER JOIN product rp on r.prod2 = rp.id
WHERE r.prod1 = '<original product id>';
And you can add to that WHERE clause to filter by specific r.relation types, like 'Child' or what have you.

If they are children:
SELECT *
FROM products a
JOIN product_relations b
ON a.id = b.prod1
WHERE a.id = product_id_to_find
If you want all related:
SELECT *
FROM products a
JOIN product_relations b
ON a.id = b.prod1 OR a.id = b.prod2
WHERE a.id = product_id_to_find

These are entirely based on JNevill's answer. I'm adding them here and a more detailed reference for myself and others now that I understand how it works.
/***********************************************************************/
/******** GET ALL PRODUCTS "related" TO PRODUCT 1 (AND REVERSE) ********/
/***********************************************************************/
SELECT *
FROM products
JOIN product_relations
ON products.id = product_relations.prod2
WHERE product_relations.prod1 = 1 OR product_relations.prod2 = 1 AND product_relations.relation = "related"
/***********************************************************************/
/**************** GET THE CHILD PRODUCTS OF PRODUCT 1 ******************/
/***********************************************************************/
SELECT *
FROM products
JOIN product_relations
ON products.id = product_relations.prod2
WHERE product_relations.prod1 = 1 AND product_relations.relation = "child"
/***********************************************************************/
/**************** GET THE PARENT PRODUCT OF PRODUCT 1 ******************/
/***********************************************************************/
SELECT *
FROM products
JOIN product_relations
ON products.id = product_relations.prod1
WHERE product_relations.prod2 = 1 AND product_relations.relation = "child"
By flipping the lookup mechanics on the above, we don't have to have a child and a parent row in the product_relations table. The child row works for both.
/***********************************************************************/
/******** GET THE SUCCESSOR (NEW VERSION) PRODUCT OF PRODUCT 1 *********/
/***********************************************************************/
SELECT *
FROM products
JOIN product_relations
ON products.id = product_relations.prod2
WHERE product_relations.prod1 = 1 AND product_relations.relation = "successor"
/***********************************************************************/
/******* GET THE PREDECESSOR (OLD VERSION) PRODUCT OF PRODUCT 1 ********/
/***********************************************************************/
SELECT *
FROM products
JOIN product_relations
ON products.id = product_relations.prod2
WHERE product_relations.prod1 = 1 AND product_relations.relation = "predecessor"

Related

Mysql count the different id's of the foreign key

I have the following tables:
jobs:
-------------------------------------------------------
| id | title | slug |
-------------------------------------------------------
employments:
-------------------------------------------------------
| id | job_type|
-------------------------------------------------------
applications:
-------------------------------------------------------
| id | job_opening_id| application_state_id|
-------------------------------------------------------
application_states
-------------------------------------------------------
| id | name|
-------------------------------------------------------
I want to create a query that counts the different application_state_id's
----------------------------------------------------------------------------
| j.title| j.slug| e.job_type | candidates | hired
----------------------------------------------------------------------------
This is the query that i have at the moment:
SELECT
j.title,
j.slug,
e.job_type,
count(a1.application_state_id) as candidates,
count(a2.application_state_id) as hired
FROM
jobs AS j
INNER JOIN employments AS e ON j.employment_id = e.id
LEFT JOIN applications AS a1 ON a1.job_opening_id = job_openings.id
LEFT JOIN application_states AS as ON as.id = a1.application_state_id
LEFT JOIN applications AS a2 ON a2.job_opening_id = j.id AND a2.application_state_id = 1
GROUP BY
a1.application_state_id,
a2.application_state_id,
j.id,
j.title,
j.slug
I thought i could create 2 joins and set the application_state_id, but all that does is count records double.
What do i need to change in this query? I hope someone can help me.
You did not provide sample data, but as I see from your code
you are joining the table applications twice,
so by the 1st to get the total number of candidates
and by the 2nd to get the total number of hired candidates.
I think you can drop the 2nd join and do conditional counting to get the total number of hired candidates.
Also:
the select statement must include the columns that you group by and any aggregated columns
and I don't see why you need to join to the application_states table.
Try this:
SELECT
j.title,
j.slug,
e.job_type,
count(a.application_state_id) as candidates,
sum(case when a.application_state_id = 1 then 1 else 0 end) as hired
FROM
jobs AS j INNER JOIN employments AS e ON j.employment_id = e.id
LEFT JOIN applications AS a ON a.job_opening_id = job_openings.id
GROUP BY
j.title,
j.slug,
e.job_type

MySQL - return single record by joining multiple tables with multiple WHERE/OR clauses

Did many searches and were able to write all the JOIN queries except this one. I have 3 tables, which look like:
TABLE - accounts
account_id | account_email
1 | aa#bb.com
2 | cc#dd.com
TABLE - products
product_id | product_name
1 | name1
2 | name2
TABLE - licenses
license_id | account_id | product_id | license_code
1 | 1 | 1 |
2 | 1 | 2 |
3 | 0 | 1 | abc123
I know account_email, product_id and license_code (this one can be empty) variables, and need to check if client has license for selected product (searching by account_email or license_code).
The problem is that account_id sometimes can be 0 (in other words, client has license, but client's profile is not stored in the accounts table).
Trying to use this one, but it returns wrong (duplicated) results:
SELECT * FROM licenses
INNER JOIN products ON licenses.product_id=products.product_id AND products.product_id='X'
INNER JOIN accounts ON licenses.account_id=accounts.account_id AND accounts.account_email='XYZ' OR licenses.license_code='ZZZ'
The question: how do I rewrite query, so I can find a record by account_email or license_code field? Put simply, if account_id is not 0 (profile exists), I should see data from 3 tables (accounts, products, licenses). If account_id is 0, I should see data from 2 tables (values from accounts table should be displayed as empty/null).
Needless to say, account_email and license_code fields are unique.
You just need to adjust the parentheses. However, I would use a separate WHERE clause:
SELECT *
FROM licenses l INNER JOIN
products p
ON l.product_id = p.product_id INNER JOIN
accounts a
ON l.account_id = a.account_id
WHERE p.product_id = 'X' AND
(a.account_email='XYZ' OR l.license_code = 'ZZZ')
You can keep the filters in the ON clauses -- there is nothing wrong with that. I just prefer to separate join conditions from filtering conditions. The important part are the parentheses, so the ON clause is not interpreted as:
ON (l.account_id = a.account_id AND a.account_email = 'XYZ') OR
licenses.license_code = 'ZZZ'
This is the interpretation without parens.
After some more code modification and testing, here's the code which works perfectly. It's a modified Gordon Linoff's code (original code didn't work when only license_code was known, and email address wasn't known). So most of credits go to Gordon Linoff for his efforts.
SELECT *
FROM licenses l JOIN
products p
ON l.product_id = p.product_id LEFT JOIN
accounts a
ON l.account_id = a.account_id
WHERE p.product_id = 'X' AND
(a.account_email='XYZ' OR l.license_code = 'ZZZ')

MySQL Many to Many query confusion

I've looked at a bunch of questions and solutions regarding many to many queries. I just can't seem to wrap my head around it. Maybe I'm not completely understanding the keywords in MySQL. But...
I have 3 tables. The first table is a list of peoples contact information. The second table is a list of mailing list categories. The third table is an associative table that holds the id's from the first and second table. How would I write a MySQL query to get all the contacts from the contact table that match the VIP list id (which I already have)?
Table 1 (contacts)
id | name | email
-----------------------------
1 | John | john#gmail.com
-----------------------------
2 | Jane | jane#gmail.com
-----------------------------
Table 2 (list_type)
id | list_name |
-----------------
1 | VIP's |
-----------------
2 | Generic |
-----------------
Table 3 (list_contact_joiner)
contact_id | list_type_id |
----------------------------
1 | 2 |
----------------------------
2 | 1 |
----------------------------
This is what I tried but get a syntax error
$listID = 1;
SELECT list_contact_joiner.contact_id
FROM list_contact_joiner
WHERE list_id = $listID AS lcj
INNER JOIN contact_lists AS cl
ON cl.id = lcj.contact_id
SELECT c.*
FROM contacts c
JOIN list_contact_joiner j on j.contact_id = c.id
JOIN list_type t on j.list_type_id = t.id
WHERE t.list_name = 'VIP''s'
If you already have the id of VIP's then you need to join only 2 tables
SELECT c.*
FROM contacts c
JOIN list_contact_joiner j on j.contact_id = c.id
WHERE j.list_type_id = 1
Yes the join statement is not correct. It should be something as
select
c.name,
c.email,
lt.list_type
from list_contact_joiner lcj
join contacts c on c.id = lcj.contact_id
join list_type lt on lt.id = lcj.list_type_id
where
lt.id = ?
If you are looking for data with $listID = 1; then the place holder is
lt.id = ?
Your syntax error is because all the table specification (FROM and JOIN) has to occur before the WHERE and you got your alias syntax a little messed up - this should work better:
$listID = 1;
SELECT lcj.contact_id
FROM list_contact_joiner AS lcj
INNER JOIN contact_lists AS cl
ON cl.id = lcj.contact_id
WHERE lcj.list_id = $listID
But if all you are trying to do is get the contact_id then you don't need to do any join at all...
SELECT contact_id
FROM list_contact_joiner
WHERE list_id = $listID
If you need the rest of the contact information then you might try this:
SELECT contact.*
FROM list_contact_joiner
JOIN contacts ON contacts.id = list_contact_joiner.contact_id
WHERE list_id = $listID
Note that you still don't need to get the list_type table at all.

mysql error in my query

i have to check in my products i am selling (mostly gaming consoles and games)
i want to see which products has which categories and this is my query:
select * From products left join products_categories on (product_id=id) ;
+------+------+------------+-------------+----------
| id | name | product_id | category_id | and more
+------+------+------------+-------------+----------
| 4 | Xbox | 4 | 2 |
| 5 | PS3 | 5 | 2 |
| 7 | BAD | NULL | NULL |
etc...
+------+------+------------+-------------+---------
here i have a product (#7 - BAD) that i don'T want to see since i removed the category,
I don't want to see the product without categories?
The LEFT JOIN command is used to combines null matching rows which are
stored in related tables In order to join these tables, the join table
require a common field (commonly called foreign key) from the left
table. This type of join requires keywords ON or USING.
Example:
SELECT *
From products
LEFT JOIN products_categories ON (product_id=id)
WHERE product_id IS NOT NULL;
Or you can use the INNER JOIN:
The JOIN or INNER JOIN command is used to combines non-null matching
rows which are stored in related tables In order to join these tables,
the join table require a common field (commonly called foreign key)
from the left table. This type of join requires keywords ON or USING.
Example:
SELECT * From products INNER JOIN products_categories ON (product_id=id);
Now, I would recommend to add a flag for inactive or active product, this way you don't need to remove the categories for a product if it's inactive. This way, if you want to re-activate it, simply turn the flag back to 1 or whatever flag you use.
Example:
SELECT *
FROM products
INNER JOIN products_categories ON (product_id=id)
WHERE products.is_active = 1;

joining tables in mysql query?

I would like to run a query on two table "products" and "category", There are 30 record in my product table. product table has a column name category_ids varchar(255),which storing the ids of category in the format like(10,11,12,130,..) for each record of products table. In sort a product can be many categories. category table having a column name parent_id which is the parent category of that category.
I want to list the record with all category of that product.
For example look at one record of the products table having id = 7.
product_Id = 7,
category_ids = '213,215,216',
product_name = 'Ponds',
.....
Means product ponds has three category = category.id = 213, category.id = 215 and category.id = 216.
I want to list here all three records of ponds like in this format :=
product_Id | product_name | category_name | parent_category_name
7 ponds cream chemical
7 ponds medicine chemical
7 ponds powder Ayurvedic
I am trying with this query :-
select
p.id as product_id,
p.product_name,
child.name as category_name,
parent.name as parent_category_name
from category child
left join products p on child.id in(p.category_ids)
left join category parent on parent.id = child.parentid and parent.parentid = 0
where p.id = 7
The above query getting only one record not all three records as above.
What condition and joining in this query will be applied to get result as above described.
Sorry for spending your valuable time.
Any suggestions and ideas would be greatly appreciated.
Thanks a lot.
Try to change ON condition -
LEFT JOIN products p ON child.id IN(p.category_ids)
->
LEFT JOIN products p ON FIND_IN_SET(child.id, p.category_ids)
...because:
SELECT 1 IN ('1,2,3') find_1, 2 IN ('1,2,3') find_2;
+--------+--------+
| find_1 | find_2 |
+--------+--------+
| 1 | 0 | -- 0 !
+--------+--------+
SELECT FIND_IN_SET(1, '1,2,3') find_1, FIND_IN_SET(2, '1,2,3') find_2;
+--------+--------+
| find_1 | find_2 |
+--------+--------+
| 1 | 2 |
+--------+--------+
GROUP_CONCAT function.
example:
SELECT p.product_Id, p.product_name, GROUP_CONCAT(c.category) FROM products p
JOIN category c
ON FIND_IN_SET(child.id, p.product_Id)
GROUP BY p.product_Id;