INNER and LEFT JOIN on mysql - mysql

I have the needing to chain some joins queries, and I am not able to get the result that I want.
I have many tables to save some product info:
-Products: Name and description in many languages.
-Products_attributes: Some common attributes of the products like the provider or the buying price.
-Products_locations: Info concerning the locations that sell a certain product, like stock, or the selling price.
-Other important table is the Companies one: some of this companies could be a provider.
Well, this is my query:
SELECT p.id
, p.name nameProduct
, p.reference refProduct
, a.buy_price priceProduct
, l.active
, l.sell_price
, l.stock stockProduct
, c.title p_name
FROM products p
LEFT JOIN products_location l
ON l.product_reference = p.reference
AND l.active = 1
AND l.location_id = 4
JOIN products_attributes a
ON a.product_reference = p.reference
AND p.lang = 'es'
AND a.provider = 6
JOIN companies c
ON a.provider = c.id
AND c.id = 6
What I want to do is get all products of a certain provider, if the location from where is executing the query has this product, the row of the result must return too the concerning at this product<->location, in contrary it must return NULL in the column related to this relation.
At the moment, with this query, all what I am getting is the products of a provider where has a relation between the product<->location (through products_location table).
Any way to do that??
Thank you.
EDIT
An example about what I trying to get could be something like this:
TABLE: Companies
id | Title
1 | SomeName
6 | ProviderName
TABLE: Products
id | reference | name | lang
1 | 11111 | 1_es | es
2 | 11111 | 1_en | en
3 | 22222 | 2nam | es
4 | 33333 | 3nam | es
5 | 44444 | 4nam | es
6 | 55555 | 5nam | es
TABLE: Products_atributte
id | product_reference | buy_price | provider
1 | 11111 | 10 | 6
1 | 22222 | 15 | 6
1 | 33333 | 20 | 6
1 | 44444 | 12 | 1
1 | 55555 | 13 | 1
TABLE: Products_locations
id | product_reference | location_id | sell_price | stock | active
1 | 11111 | 4 | 26 | 10 | 1
1 | 11111 | 5 | 25 | 13 | 1
1 | 22222 | 5 | 20 | 13 | 1
1 | 44444 | 5 | 21 | 1 | 1
1 | 55555 | 5 | 22 | 2 | 1
AND THE RESULT MUST BE SOMETHING LIKE THIS:
nameProduct | refProduct | priceProduct | active | sell_price | stockProduct | p_name
1_es | 11111 | 10 | 1 | 26 | 10 | ProviderName
2nam | 22222 | 15 | NULL | NULL | NULL | ProviderName
3nam | 33333 | 20 | NULL | NULL | NULL | ProviderName
If I use a LEFT JOIN only in the products_locations table, I donĀ“t get the two last rows, and if I use LEFT JOIN with all the tables I get duplicates product references, also I get products provided by other providers (in the example 1-> SomeName).

You were correct to LEFT JOIN the products and products_location tables. However, you used INNER JOIN for the other two tables in the query and I believe that this may be the reason why you are only seeing records which have a relation between product and location. The logic would be that a product which does not have a location also does not have an entry in, for example, the products_attributes table. Hence, the non matching records you want to retain would be filtered off downstream by an INNER JOIN. To remedy this, use LEFT JOIN everywhere:
SELECT products.id,
products.name AS nameProduct,
products.reference AS refProduct,
products_attributesbuy_price AS priceProduct,
products_location.active,
products_location.sell_price,
products_location.stock AS stockProduct,
provider.title AS p_name
FROM products
LEFT JOIN products_location
ON products_location.product_reference = products.reference AND
products_location.active = 1 AND
products_location.location_id = 4
LEFT JOIN products_attributes
ON products_attributes.product_reference = products.reference AND
products.lang = 'es' AND
products_attributes.provider = 6
LEFT JOIN companies AS provider
ON products_attributes.provider = provider.id AND
provider.id = 6

Related

How to check for duplicates in a 1 to many table

I have a system which stores peoples outfits. Each outfit contains 1 to many items.
Thus my tables are like this
outfits
+-----------+--------+
| outfit_id | cus_id |
+-----------+--------+
| 1 | 5 |
| 2 | 92 |
+-----------+--------+
items
+---------+-------+-------+
| item_id | name | sku |
+---------+-------+-------+
| 1 | hat | 1111 |
| 2 | pants | 2222 |
| 3 | shirt | 3333 |
| 4 | shoes | 4444 |
+---------+-------+-------+
items_in_outfit
+--------+-----------+---------+
| ino_id | outfit_id | item_id |
+--------+-----------+---------+
| 1 | 1 | 3 |
| 2 | 1 | 2 |
| 3 | 1 | 4 |
| 4 | 2 | 1 |
| 4 | 2 | 3 |
+--------+-----------+---------+
I am given this information:
cus_id=92 wants to create another outfit with the sku's 1111 and 3333
However, this is a duplicate of outfit_id=2. So I want mysql to return me the outfit_id of 2. If its not a duplicate, return nothing. It only counts as a duplicate if the same cus_id is trying to make an outfit which has the exact same sku's already.
If cus_id=5 wants to create another outfit with the sku's 1111 and 3333. It should return nothing (as its not a duplicate) because cus_id=5 doesn't have an outfit with just 1111 and 3333
SELECT o.outfit_id FROM
outfits o JOIN
items_in_outfit iio
ON o.outfit_id = iio.outfit_id JOIN
items i
ON iio.item_id = i.item_id
WHERE cus_id = 92 AND ...
This may help you achieve what you are looking for :
select
cus_id
from(
select
cus_id,
group_concat(sku order by sku asc) as items
from outfits o
join items_in_outfit iio on o.outfit_id = iio.outfit_id
join items i on iio.item_id = i.item_id
group by cus_id
) T
where items = '1111,3333'
SEE DEMO HERE
EDIT : As an outfit can't contain more than 1 instance of the same item, this solution may be better for you as you don't have to order the sku id in your php before :
select
o.outfit_id
from
outfits o
join items_in_outfit iio on o.outfit_id = iio.outfit_id
join items i on iio.item_id = i.item_id
where sku IN (1111, 3333)
group by o.outfit_id having COUNT(*) = 2
SEE SECOND DEMO HERE

How to join has many relation table and fetch result by type

I have a few tables which I am trying to join and fetch the results for a list
Interviews Table
+--------------+-----------+
| interview_id | Candidate |
+--------------+-----------+
| 1 | Ram |
| 2 | Rahim |
| 3 | Joseph |
+--------------+-----------+
Participant Ratings Table
+--------------+-----------+-------+
| interview_id | Rater Type|Rating |
+--------------+-----------+-------+
| 1 | Candidate | 4 |
| 2 | Candidate | 4 |
| 1 | Recruiter | 5 |
+--------------+-----------+-------+
System Ratings Table
+--------------+------------+-------+
| interview_id | Rating Type|Rating |
+--------------+------------+-------+
| 1 | Quality | 4 |
| 1 | Depth | 4 |
| 1 | Accuracy | 5 |
| 2 | Quality | 4 |
| 2 | Depth | 3 |
| 2 | Accuracy | 5 |
| 3 | Quality | 4 |
| 3 | Depth | 5 |
| 3 | Accuracy | 5 |
+--------------+------------+-------+
I need to fetch the result of average ratings for each interview given in the following manner.
+--------------+--------------+-----------------+-----------------+
| interview_id | System Rating|Recruiter Rating |Candidate Rating |
+--------------+--------------+-----------------+-----------------+
| 1 | 4.3 | 5 | 4 |
| 2 | 4.0 | 0 | 4 |
| 3 | 4.6 | 0 | 0 |
+--------------+--------------+-----------------+-----------------+
Each interview can will have one 1 candidate rating and 1 recruiter rating but that is optional. If given a record is created in participant rating with rating and type.
Need to get the average of system ratings of all the types and get one value as system rating and if rating provided by participants then display else display as 0 if any or both the participants not provided any rating.
Please ignore the values, if there is a mistake.
The SQL which I tried to get the result.
SELECT i.candidate, i.id AS interview_id,
AVG(sr.rating) AS system_rating,
AVG(CASE WHEN pr.rater_type = 'Candidate' THEN pr.rating END) AS candidate_rating,
AVG(CASE WHEN pr.rater_type = 'Recruiter' THEN pr.rating END) AS recruiter_rating
FROM system_ratings sr, participant_ratings pr, interviews i
WHERE sr.interview_id = i.id AND i.id = 2497 AND pr.interview_id = i.interview_id
The problem is whenever participant ratings are not present then results are missing as there is join.
Use LEFT JOIN to make sure if relation tables do not have any data, still we can have records from the main table.
Reference: Understanding MySQL LEFT JOIN
Issue(s):
Wrong field name: pr.interview_id = i.interview_id, it should be pr.interview_id = i.id as we don't have any interview_id field in interviews table, it would be id field - based on your query.
pr.interview_id = i.id in where clause: If participant_rating table does not have any records for a given interview, this will cause the removal of that interview from the result set. Use LEFT JOIN for participant_rating table.
sr.interview_id = i.id in where clause: If system_rating table does not have any records for a given interview, this will cause the removal of that interview from the result set. Use LEFT JOIN for system_rating table too.
Usage of AVG works but won't work for other aggregates functions like SUM, COUNT.. because if we have one to many relationships then join will make there will be multiple records for the same row.
Solution:
SELECT
i.id AS interview_id,
i.candidate,
AVG(sr.rating) AS system_rating,
AVG(CASE WHEN pr.rater_type = 'Candidate' THEN pr.rating END) AS candidate_rating,
AVG(CASE WHEN pr.rater_type = 'Recruiter' THEN pr.rating END) AS recruiter_rating
FROM interviews i
LEFT JOIN system_rating sr ON sr.interview_id = i.id
LEFT JOIN participant_rating pr ON pr.interview_id = i.id
-- WHERE i.id IN (1, 2, 3) -- use whenever required
GROUP BY i.id

MYSQL - Find records from one table which don't exist in another

I've got the following two SQL tables (in MySQL):
Users
| id | name |
|----|------|
| 1 | Luke |
| 2 | Mark |
| 3 | Lucy |
| 4 | Biff |
User category
| user_id | category_id |
|---------|-------------|
| 1 | 5 |
| 1 | 6 |
| 2 | 5 |
| 2 | 7 |
| 3 | 5 |
I want users that are in User category but not if category id is 6.
In this case Mark and Lucy because Luke is in category 6 too and Biff has no category.
There is a way to do it without subquery and only in one query?
You can group by user_id and eliminate those rows where there is atleast one category_id of 6.
select uc.user_id,u.name
from user_category uc
join users u on uc.user_id = u.id
group by uc.user_id,u.name
having sum(case when category_id = 6 then 1 else 0 end) = 0
Join them and check for difference :
SELECT * FROM users
INNER JOIN user_category ON (user_category.user_id = users.id)
WHERE user_category.category_id <> 6
p.s. using group by is not effective, cuz it says to DB engine to do additional group by operation after gathering data.

MySQL LEFT Join with multiple possibilities and Inner Join

I don't know if a similar question have been asked, but I looked fow more than hour on mysql in stackoverflow
My problem is, i have multiple tables and I need to join them with both left join and inner join in mysql
Entity table :
id (key) | entityName
1 | john
2 | harris
3 | henry
4 | mark
5 | dom
Activity table
id (key) | entityID | status
1 | 1 | 1
2 | 2 | 0
3 | 4 | 1
Geodata table
id (key) | entityID | moment (timestamps when the entry was done)
1 | 1 | 1429542320 (smaller)
2 | 1 | 1429542331 (bigger)
3 | 2 | 1429542320 (smaller)
4 | 2 | 1429542331 (biger)
5 | 4 | 1429542331 (bigger)
Info table
id (key) | entityID | infos | date
1 | 1 | xxx | today
2 | 1 | xxx | yesterday
3 | 2 | xxx | today
4 | 2 | xxx | yesterday
5 | 3 | xxx | yesterday
6 | 5 | xxx | today
7 | 5 | xxx | yesterday
8 | 5 | xxx | tomorrow
So basically, I need every Entities that has an info for today
Moreover, if their status is true (or 1) (from activity table), show me their date in geodata table.
So this is what i've got :
SELECT e.id,
e.entityName,
i.infos,
a.status,
MAX(g.moment) -- but the max only if status =1
FROM entities AS e
LEFT JOIN activity AS a ON a.entityID = e.id
LEFT JOIN geodata AS g ON g.entityID = e.id
INNER JOIN infos AS i ON e.id = i.entityID
WHERE i.date = 'today'
GROUP BY e.id
I want every entities that has an info about today, but some of them have activity too, so i want to show it (if it doesn't just let the left join put NULL) If the status is 0, I don't need the moment, but if its true, I only need the bigger one (its numbers, so Max() should do it but it breaks)
The expected results is :
id (key) | entityName | infos | status | MAX(moment) | ..other columns
1 | john | xxx | 1 | 1429542331 (the bigger one)
2 | harris | xxx | 0 | NULL
5 | dom | xxx | NULL | NULL
If someone can help me, I'll be very thankful :)
PS.: Sorry for my english, it isn't my first language
You could change the
MAX(g.moment)
to
IF(a.status<>1, NULL, MAX(g.moment))
or alternately change LEFT JOIN geodata AS g ON g.entityID = e.id to LEFT JOIN geodata AS g ON a.entityID = e.id AND a.status = 1
Which one is faster will probably depend on your actual data; the second may be faster as less records are joined, but the more complicated join condition it uses might slow down the joining.

select query to calculate number of occurrence as well as total cost

I have one report page which displays summarized data of other report.I have used php and mysqli. Let me explain you in deep.
I have a web application of store, where you can add product details. Using these product details you can generate packaging list report of products. And based on the generated packaging list report I need to generate one other report which contains summarized data of the packaging list.
below are my tables:
product table:
id | name | desc_id | purity | style_no | type | duty
1 | ABC | 1 | 18 | TEST123 | R | 100
2 | XYZ | 2 | 14 | TEST456 | B | 80
3 | DEF | 1 | 14 | TEST122 | R | 80
4 | PQR | 1 | 18 | TEST124 | R | 120
5 | HJK | 3 | 18 | TEST134 | B | 300
Description table:
id | descrip
1 | Gold Diamond Ring
2 | Gold Diamond Pendant
3 | Gold Diamond Earring
packaging_master table
id | name
1 | pkg_1
2 | pkg_2
packging_details table
id | pkg_id | prod_id
1 | 1 | 1
2 | 1 | 2
3 | 1 | 3
4 | 1 | 4
5 | 1 | 5
I have used below query to generate the packaging list report for specific id, which works correctly.
SELECT id, (SELECT descrip FROM description WHERE id = desc_id ) AS descrip,
style_no, type , purity, duty FROM product WHERE id IN ( SELECT prod_id FROM packaging_list_details WHERE pkg_id =1 ) ORDER BY descrip ASC , purity ASC
which displays below result:
id | descrip | style_no | type | purity | duty
1 |Gold Diamond Ring | TEST123 | R | 18 | 100
4 |Gold Diamond Ring | TEST124 | R | 18 | 120
3 |Gold Diamond Ring | TEST122 | R | 14 | 80
2 |Gold Diamond Pendant| TEST456 | B | 14 | 80
5 |Gold Diamond Earring| TEST134 | B | 18 | 300
Now I want summarized data of above result using query.
Like:
id | descrip | purity | qty | duty
1 |Gold Diamond Ring | 18 | 2 | 220
2 |Gold Diamond Ring | 14 | 1 | 80
3 |Gold Diamond Pendant| 14 | 1 | 80
4 |Gold Diamond Earring| 18 | 1 | 300
How can I achieve this?
You need to use the GROUP_BY statement - See MySql docs for more info.
This will translate the query to such
SELECT d.descrip, p.purity, count(p.purity) as qty, sum(p.duty)
FROM product p
INNER JOIN Description d ON p.desc_id = d.id
LEFT OUTER JOIN packaging_details pg on pg.prod_id = p.id
GROUP BY d.descrip, p.purity
ORDER BY d.descrip desc, p.purity desc
You can also use the sub select methodology you were using, but I prefer using joins. INNER JOIN will link both tables so that all their records are returned. OUTER JOIN will return all rows from the tables on the LEFT of the statement and matches them to values from the tables on the RIGHT.
See a full SQL Fiddle sample.
NOTE: I am not sure where you are getting the values for Id in your sample - Are they simply row numbers?
I think you should rewrite your query using JOINs:
SELECT
P.id
,D.descrip
,P.style_no
,P.type
,P.purity
,P.duty
FROM
packaging_list_details PLD
JOIN
product P ON
(P.id = PLD.prod_id)
LEFT JOIN
description D on
(D.desc_id = P.id)
WHERE
(PLID.pkg_id = 1)
That should give you the same result you already have. To get the totals, you can write a new query, similar to the above:
SELECT
P.id
,D.descrip
,P.type
,P.purity
,COUNT(p.id) as total_products
,SUM(P.duty) as total_duty
FROM
packaging_list_details PLD
JOIN
product P ON
(P.id = PLD.prod_id)
LEFT JOIN
description D on
(D.desc_id = P.id)
WHERE
(PLID.pkg_id = 1)
GROUP BY
P.id
,D.descrip
,P.type
,P.purity
The second query gives you the totals you are looking for.