MySQL multiple joins with Many-To-Many relations - mysql

I have three tables that matter to me: customer, client_assignment and customer_products. The last two are assignment tables for Many-To-Many-relations.
In client_assignment a customer of the type client gets associated with another customer (where customer_id is the parent and client_id the child).
In customer_product I associate customers with products.
A client can not be associated with a product, he inherits it from his parent-customer. In the example below this means, that Foo-1 also has product 3, because his father (Foo) has it.
customer (Customers):
+-------------+-------+----------+
| customer_id | name | type |
+-------------+-------+----------+
| 1 | Foo | customer |
| 2 | Foo-1 | client |
| 3 | Foo-2 | client |
| 4 | Bar | customer |
| 5 | Foob | customer |
+-------------+-------+----------+
client_assignment (Customer/Client-Assignment):
+-------------+-----------+
| customer_id | client_id |
+-------------+-----------+
| 1 | 2 |
| 1 | 3 |
+-------------+-----------+
customer_product (Customer/Product-Assignment):
+-------------+------------+
| customer_id | product_id |
+-------------+------------+
| 1 | 3 |
| 1 | 4 |
| 1 | 5 |
| 4 | 3 |
| 5 | 7 |
+-------------+------------+
I want to accomplish the following: Select all customers and their respective clients that are associated with product X.
My desired result for product 3 is something like this:
+-------------+-------+--------+
| customer_id | name | parent |
+-------------+-------+--------+
| 1 | Foo | null |
| 2 | Foo-1 | 1 |
| 3 | Foo-2 | 1 |
| 4 | Bar | null |
+-------------+-------+--------+
I've been thinking about this for a bit and it seems fairly complicated. I've tried joining them like the following:
SELECT c2.customer_id, c2.name, c1.customer_id as parent
FROM customer_product p, customer c1, customer c2, client_assignment a
WHERE
c1.customer_id = p.customer_id
AND c2.customer_id = a.client_id
AND a.customer_id = c1.customer_id
AND p.product_id = 3
I know, that this query will not give me the exactly desired result, but I've created it to start with. The main problem about it is, that it does only select the clients, and not the customers themselves. Therefore I only get Foo-1 and Foo-2 as a result, but not Bar or Foo.
The question is: Is this reasonably easily achievable, and how?

You can write another SELECT that gets the customers themselves, and combine the two with UNION:
SELECT c.customer_id, c.name, NULL AS parent
FROM customer AS c
JOIN customer_product AS p ON c.customer_id = p.customer_id
WHERE c.type = 'customer'
AND p.product_id = 3
UNION
SELECT c2.customer_id, c2.name, c1.customer_id AS parent
FROM customer_product AS p
JOIN customer AS c1 ON c1.customer_id = p.customer_id
JOIN client_assignment AS a ON a.customer_id = c1.customer_id
JOIN customer AS c2 ON c2.customer_id = a.client_id
WHERE c2.type = 'client'
AND p.product_id = 3

Because you have to get names of both clients and parents of the client, a way of doing it is by using UNION.
with customer_names as (select customer_id from s_customer_product where product_id =3),
client_names as (select client_id , customer_id as Parent_id from s_client_assignment join customer_names using(customer_id))
select customer_id , name , null as Parent from s_customers join customer_names using(customer_id)
union
select a.client_id , b.name , a.parent_id as Parent from client_names a, s_customers b where b.customer_id = a.client_id;

Related

Mysql Join get multiple rows even if one of them not meet search condition

I am trying to get multiple rows with mysql join but if one of the rows doesn't meet the search condition didn't get it.
example :
I have a products table and orders table and order_products table
when someone order like 2 products I insert the order information in the order table and insert every product with its quantity in order_products table with order id
this is the schema I have
products table
| id | title |
---------------
| 1 | p1 |
| 2 | p2 |
| 3 | p3 |
| 4 | p4 |
Orders table
| id | client |
---------------
| 1 | c1 |
| 2 | c2 |
| 3 | c3 |
order_products table
| id | order_id| product_id |
-----------------------------
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 4 |
| 4 | 3 | 3 |
my query is
SELECT `orders`.*, GROUP_CONCAT(`products`.`title` SEPARATOR ' ') AS products FROM `orders` LEFT JOIN `order_products` ON `order_products`.`order_id` = `orders`.`id` LEFT JOIN `products` ON `products`.`id` = `order_products`.`product_id` GROUP BY `orders`.`id`
this query is working fine and as I intended to but the problem is when I search by product title example p1
the result I got
| id | client | info |
-------------------------
| 1 | c1 | p1 |
| 2 | c2 | p1 |
the result I want to get is like this even if the product p2 doesn't appear in search but it's in the order and I am selecting orders not products
| id | client | products|
-------------------------
| 1 | c1 | p1 p2 |
| 2 | c2 | p1 |
You want to use a having clause, not a where clause:
SELECT o.*, GROUP_CONCAT(p.title SEPARATOR ' ') AS products
FROM orders o JOIN
order_products op
ON op.order_id = o.id JOIN
products p
ON p.id = op.product_id
GROUP BY o.id
HAVING SUM( p.title LIKE ? ) > 0;
The ? is a parameter placeholder for what you are looking for.
This returns all products for orders that contain the product you are interested in.
Note the other changes I made to the query:
I introduced table aliases, so the query is easier to write and to read.
I removed the backticks, for the same reason.
I changed the LEFT JOIN to JOIN because an outer join is not necessary. Your logic requires at least one match to a product.

Join Table B to Table A only if entry in Table B equals entry in Table C

I have 3 tables. clients, sales and potential_sales.
The basic structure is as follows:
Clients Table:
+-----------+-------+----------------+
| client_id | name | address |
+-----------+-------+----------------+
| 1 | john | 12 blue ave |
| 2 | paul | 34 green lane |
| 3 | peter | 69 yellow road |
+-----------+-------+----------------+
Potential Sales Table:
+----------+------------+---------------------+
|product_id | client_id | received_free_promo |
+-----------+------------+---------------------+
| 3 | 1 | 1 |
| 4 | 2 | 0 |
| 5 | 2 | 1 |
+-----------+------------+---------------------+
Sales:
+----------+-----------+-----------+
| sales_id | client_id | product_id |
+----------+-----------+------------+
| 1 | 2 | 4 |
| 2 | 43 | 4 |
| 3 | 2 | 5 |
| 4 | 18 | 93 |
+----------+-----------+------------+
I want to join clients and potential_sales tables ONLY IF
1) received_promo equals 1 AND
2) they actually bought the promo package (i.e. the product_id for the potential sale has an entry into the sales table ). If they didn't eventually buy the free_promo product then I do not want to join the clients and potential_sales table at all. This is important - I can't simply JOIN to figure it out because this is only a small part of a bigger query and I can't afford to JOIN for no reason.
(Here is how I would like it to work. It's mainly pseudo-code to describe what I want to happen)
SELECT
c.*
FROM
clients c
LEFT JOIN potential_sales ps ON ps.client_id=c.id
LEFT JOIN sales ps ON s.product_id=ps.product_id
IF(s.sales_id) JOIN potential_sales ps ON ps.client_id=c.id
How do I do this in MySQL? I haven't come close to a solution. Please help!
Try this:
SELECT A.*, B.product_id, B.received_free_promo
FROM Clients A JOIN
(SELECT * FROM PotentialSales
WHERE received_free_promo=1) B
ON A.client_id=B.client_id
WHERE EXISTS (SELECT 1 FROM Sales C
WHERE A.client_id=C.client_id
AND B.product_id=C.product_id);
See Demo on SQL Fiddle.
What you are missing is the EXISTS clause:
SELECT
C.*,
P.*
FROM
Clients AS C
INNER JOIN PotentialSales AS P ON C.client_id = P.client_id
WHERE
P.received_free_promo = 1 AND
EXISTS (
SELECT
'the client already sold that product'
FROM
Sales AS S
WHERE
S.client_id = C.client_id AND
S.product_id = P.product_id)
Try this..." select * from client as c natural join potential as p join sales as s on p.product_id = s.product_id where received_promo = 1". select * will mention everything from all the 3 tables. You can choose what you want as the result.

Get default value from JOIN query if join field value doesn't exist in another table

I have two tables customers and reviews. The structure is like this:
customers:-
|---------------------------------|
| id | name |
|---------------------------------|
| 1 | Thutmekri |
|---------------------------------|
| 3 | Conan |
|---------------------------------|
reviews:-
|-------------------------------------------|
| id | business_id | customer_id |
|-------------------------------------------|
| 1 | 1 | 1 |
|-------------------------------------------|
| 2 | 1 | 2 |
|-------------------------------------------|
| 3 | 1 | 3 |
|-------------------------------------------|
customer_id of reviews is id of customer.
The join query,
SELECT customers.name, reviews.id as review_id
FROM customers, reviews
WHERE customers.id = reviews.customer_id
returns the dataset like this:-
|----------------------------------|
| review_id | name |
|----------------------------------|
| 1 | Thutmekri |
|----------------------------------|
| 3 | Conan |
|----------------------------------|
But I want it to return:-
|----------------------------------|
| review_id | name |
|----------------------------------|
| 1 | Thutmekri |
|----------------------------------|
| 2 | N/A |
|----------------------------------|
| 3 | Conan |
|----------------------------------|
For customer_id in reviews, which doesn't have any data in customers table, I want 'N/A' to be displayed. How can I do it?
The following query also didn't help
SELECT reviews.id as review_id, COALESCE( name, 'N/A' )
FROM customers, reviews
WHERE customers.id = reviews.customer_id
Use a left join and switch the order of the tables in the join:
SELECT
r.id AS review_id,
COALESCE(c.name, 'N/A') AS name
FROM reviews r
LEFT JOIN customers c
ON c.id = r.customer_id;
SQLFiddle
Try this :
SELECT r.id as review_id, COALESCE( c.name, 'N/A' ) FROM reviews r
LEFT JOIN customers c ON c.id = r.customer_id
Use the Right outer join to achieve this:
SELECT
reviews.id as review_id,
COALESCE(name, 'N/A' )
FROM customers
RIGHT JOIN reviews
ON customers.id = reviews.customer_id;
You can use below query
SELECT
R.id AS review_id,
COALESCE(C.name, 'N/A') AS name FROM reviews R LEFT JOIN customers C ON R.customer_id = C.id ;

Limit this JOIN query so one result for each left-side table is returned?

I have these two tables:
Person:
+----+-------+
| ID | name |
+----+-------+
| 1 | John |
| 2 | Frank |
+----+-------+
Position:
+----+---------+----------+------------+
| ID | name | personID | startDate |
+----+---------+----------+------------+
| 1 | Cashier | 1 | 2013-01-01 |
| 2 | Manager | 1 | 2013-04-23 |
| 3 | Cashier | 2 | 2014-02-01 |
+----+---------+----------+------------+
The Position table tracks various positions that a person has held.
How can I create a listing that shows each person and their current position (which would be whatever position has the latest start date)? Essentially I need to limit the JOIN of the Position table to only return one result.
I tried the following code.
SELECT p.id, h.positionID FROM person p JOIN position h ON p.id = h.personID
Try the following :-
select p.ID, pp.name PersonName, p.name PositionName ,p.startDate
from Position p
inner join
(select personID, max(startDate) sdate from Position group by personID) as a
on p.personID = a.personID and p.startDate = a.sdate
left join Person pp
on pp.ID = p.personID
Yet, it is highly advised that you post the code that you tried.
SQL Fiddle

Third join to get first_Name and Last_Name of account

I have the following query which matches Account_ID's on Accounts table with the AccountID's on project assigned table and displays the accounts assigned to a project:
SELECT proj.ProjectID, A.Project_Title, B.Account_ID, B.Username, B.Access_Type
FROM Project_Assigned proj
INNER JOIN Account B
ON proj.AccountID = B.Account_ID
INNER JOIN Project A
ON proj.ProjectID = A.Project_ID
WHERE proj.ProjectID = 1;
What I want to do now is get the First_Name, Last_Name from Client table and Agency_Employee table and display the information matched against the Account_ID's. Both Client_ID and Employee_ID are foreign keys of Account_ID. How would I add this information into the join above?
I have attempted to add an additional join but I always get a result set match 0 which I know shouldn't be the case.
Client Table:
+-----------+------------+-----------+
| Client_ID | First_Name | Last_Name |
+-----------+------------+-----------+
| 4 | Phil | Jones |
+-----------+------------+-----------+
Employee Table:
+-------------+------------+-----------+
| Employee_ID | First_Name | Last_Name |
+-------------+------------+-----------+
| 2 | John | Smith |
| 5 | Bob | Jones |
| 6 | Fred | Tucker |
+-------------+------------+-----------+
Account Table:
+------------+----------+
| Account_ID | Username |
+------------+----------+
| 1 | Dan |
| 2 | rjm |
| 3 | pw |
| 4 | Philly |
| 5 | bob |
| 6 | fred |
+------------+----------+
Project Assigned Table:
+-----------+-----------+
| ProjectID | AccountID |
+-----------+-----------+
| 1 | 1 |
| 1 | 2 |
| 1 | 4 |
+-----------+-----------+
I'm not sure, but you seem to indicate that for an Account with ID 5, the Client will have ID 5 and the Employee will have ID 5? Slightly odd, and you probably want to read up on normalisation, but shouldn't this work?
SELECT
proj.ProjectID,
Project.Project_Title,
Account.Account_ID, Account.Username,
Client.First_Name AS Client_First_Name, Client.Last_Name AS Client_Last_Name,
Employee.First_Name AS Employee_First_Name, Employee.Last_Name AS Employee_Last_Name
FROM `Project_Assigned` proj
INNER JOIN `Account` ON (proj.AccountID = Account.Account_ID)
INNER JOIN `Project` ON (proj.ProjectID = Project.Project_ID)
LEFT JOIN `Client` ON (Account.Account_ID = Client.Client_ID)
LEFT JOIN `Employee` ON (Account.Account_ID = Employee.Employee_ID)
WHERE proj.ProjectID = 1;
edit
Okay, I've updated my answer.
You'll have two first names and two lastnames, one of each will always be null.
If you only want one contact name, try this:
SELECT
COALESCE(Client.First_Name, Employee.First_Name) FirstName,
COALESCE(Client.Last_Name, Employee.Last_Name) LastName
SELECT c.First_Name, c.Last_Name, e.First_Name, e.Last_Name
FROM client c, employee e, account a
WHERE c.Client_ID = a.Account_ID OR e.Employee_ID = a.Account_ID
OR
SELECT c.First_Name, c.Last_Name
FROM client c, account a
WHERE c.Client_ID = a.Account_ID
UNION
SELECT e.First_Name, e.Last_Name
FROM employee e, account a
WHERE e.Employee_ID = a.Account_ID