Inner join with multiple tables - sql

I have these four tables:
PRODUCTS
---------
PRODUCT_ID
PRODUCT_TITLE
(other fields)
COLORS
---------
COLOR_ID
COLOR_NAME
MATERIALS
---------
MATERIAL_ID
MATERIAL_NAME
IMAGES
---------
IMAGE_ID
BIG
MED
SMALL
THUMB
SIZE
---------
SIZE_ID
SIZE_NAME
And also:
PRODUCT_COLOR
---------
PRODUCT_ID
COLOR_ID
PRODUCT_MATERIAL
---------
PRODUCT_ID
MATERIAL_ID
PRODUCT_SIZE
---------
PRODUCT_ID
SIZE_ID
PRODUCT_IMAGE
---------
PRODUCT_ID
IMAGE_ID
COLOR_ID (can be null)
MATERIAL_ID (can be null)
All the products can have a different color and/or material. E.g. I can have a product that has one or more material options but no colors associated and vice versa. The output should be something like this:
-----------------------------------------------------------------------------
| PRODUCT_ID | PRODUCT_NAME | COLOR_ID | MATERIAL_ID | IMAGE_ID | SIZE_ID |
-----------------------------------------------------------------------------
| 1 | T-SHIRT | 1 | null | 1 | 1 |
| 1 | T-SHIRT | 1 | null | 1 | 2 |
| 1 | T-SHIRT | 1 | null | 1 | 3 |
| 1 | T-SHIRT | 1 | null | 1 | 4 |
| 2 | JEANS | null | 1 | 2 | 1 |
| 2 | JEANS | null | 1 | 2 | 2 |
| 2 | JEANS | null | 1 | 2 | 3 |
| 2 | JEANS | null | 1 | 2 | 4 |
| 2 | JEANS | null | 1 | 2 | 5 |
| 3 | T-SHIRT VNECK | 2 | 2 | 3 | 1 |
| 3 | T-SHIRT VNECK | 2 | 2 | 3 | 2 |
| 3 | T-SHIRT VNECK | 3 | 2 | 4 | 1 |
| 3 | T-SHIRT VNECK | 3 | 2 | 4 | 2 |
| 3 | T-SHIRT VNECK | 4 | 3 | 5 | 1 |
| 3 | T-SHIRT VNECK | 4 | 3 | 5 | 2 |
-----------------------------------------------------------------------------
I have tried the following statement but it returns 0 rows:
SELECT PRODUCTS.PRODUCT_ID, PRODUCTS.PRODUCT_TITLE, COLORS.COLOR_ID, MATERIALS.MATERIAL_ID, IMAGES.IMAGE_ID, SIZE.SIZE_ID from PRODUCTS
INNER JOIN PRODUCT_COLOR ON (PRODUCTS.PRODUCT_ID = PRODUCT_COLOR.PRODUCT_ID)
INNER JOIN COLORS ON (COLORS.COLOR_ID = PRODUCT_COLOR.COLOR_ID)
INNER JOIN PRODUCT_MATERIAL ON (PRODUCTS.PRODUCT_ID = PRODUCT_MATERIAL.PRODUCT_ID)
INNER JOIN MATERIALS ON (MATERIALS.MATERIAL_ID = PRODUCT_MATERIAL.MATERIAL_ID)
INNER JOIN PRODUCT_IMAGE ON (PRODUCTS.PRODUCT_ID = PRODUCT_IMAGE.PRODUCT_ID)
INNER JOIN IMAGES ON (IMAGES.IMAGE_ID = PRODUCT_IMAGE.IMAGE_ID)
INNER JOIN PRODUCT_SIZE ON (PRODUCTS.PRODUCT_ID = PRODUCT_SIZE.PRODUCT_ID)
INNER JOIN SIZE ON (SIZE.SIZE_ID = PRODUCT_SIZE.SIZE_ID)
ORDER BY PRODUCTS.id_PRODUCT;
Any ideas?

You could do something like this:
select p.product_id,
p.product_name,
c.color_id,
m.material_id,
i.image_id,
s.size_id
from products p
left join product_color pc
on p.product_id = pc.product_id
left join colors c
on pc.color_id = c.colorid
left join product_material pm
on p.product_id = pm.product_id
left join materials m
on pm.material_id = m.material_id
left join product_image pi
on p.product_id = pi.product_id
left join images i
on pi.image_id = i.image_id
or c.color_id = i.color_id
or m.material_id = i.material_id
left join product_size ps
on p.product_id = ps.product_id
left join size s
on ps.size_id = s.size_id
I would advise you reviewing JOINs. There is a great visual explanation of joins online that will help you write these queries.

Well, you need to learn how to build joins, and the way I normally do it is by selecting one table and join the next one and the next one and the next one until I have the result I want.
select product_id, product_name
from products
next I join the first one I need so I go ahead and say
select p.product_id, p.product_name, pc.color_id
from products p
join product_color pc on (pc.product_id = p.product_id)
On the join it is important to figure out if I maybe have nothing to join with and I still want to see the line. So I rather use a left join
select p.product_id, p.product_name, pc.color_id
from products p
left join product_color pc on (pc.product_id = p.product_id)
That way you add each table to join. By the way. Is this homework?

If you just need IDs, keep it simple
select p.product_id,
p.product_name,
pc.color_id,
pm.material_id,
pi.image_id,
ps.size_id
from products p,
PRODUCT_COLOR pc,
product_material pm,
PRODUCT_SIZE ps,
PRODUCT_IMAGE pi
where
p.product_id = pc.product_id(+)
and p.product_id = pm.product_id(+)
and p.product_id = ps.product_id(+)
and p.product_id = pi.product_id(+);

Related

SQL INNER JOIN using CASE and 3 tables

I have the following tables and want show only the types that are available, while counting the products in that category.
Types
Type_Id
Name
1
Candy
2
Chocolate Bar
Products
Product_Id
Name
Type_Id
AvailabilityId
1
Chocolate Name 1
1
1
2
Chcoolate Name 1
2
2
3
Candy Name 1
2
2
Availability
Availability_Id
Name
1
Available
2
Reserved
3
Sol d
Desired Result
Type_Id
Name
TotalAvailable
1
Candy
1
2
Chocolate
2
Start with a basic JOIN between all three tables, on the related columns. Note use of table aliases t - Types, p - Products, a - Availability
SELECT *
FROM Types t
INNER JOIN Products p ON p.Type_Id = t.Type_Id
INNER JOIN Availability a ON a.Availability_Id = p.Availability_Id
Next add a WHERE clause to filter results by the availability status:
SELECT *
FROM Types t
INNER JOIN Products p ON p.Type_Id = t.Type_Id
INNER JOIN Availability a ON a.Availability_Id = p.Availability_Id
WHERE a.Name IN ('Available', 'Reserved')
Results:
Type_Id | Name | Product_Id | Name | Type_Id | Availability_Id | Availability_Id | Name
------: | :------------ | ---------: | :------------- | ------: | --------------: | --------------: | :--------
1 | Candy | 1 | Chocolate Name | 1 | 1 | 1 | Available
2 | Chocolate Bar | 2 | Chcoolate Name | 2 | 2 | 2 | Reserved
2 | Chocolate Bar | 3 | Candy Name | 2 | 2 | 2 | Reserved
Finally COUNT(*) the total rows matched, grouping by category (i.e. [Type].[Name])
SELECT t.Type_Id
, t.Name AS Type_Name
, COUNT(*) AS Total_Products
FROM Types t
INNER JOIN Products p ON p.Type_Id = t.Type_Id
INNER JOIN Availability a ON a.Availability_Id = p.Availability_Id
WHERE a.Name IN ('Available', 'Reserved')
GROUP BY t.Type_Id
, t.Name
Type_Id | Type_Name | Total_Products
------: | :------------ | -------------:
1 | Candy | 1
2 | Chocolate Bar | 2
SQL Fiddle
You can use an inner query with inner join to achieve this. Here's a SQL fiddle: http://sqlfiddle.com/#!9/42bd7f/2

Replace subqueries in where statement

I've built a query that intends to find products (products table) with both a 'used' offer and a 'new' offer, and get the lowest price for each. A product can have multiple offers (link_prices table). The offer's condition is determined by the name of the merchant (merchants table): a name without used and occasion is a 'new' offer, a name with used is a 'used' offer.
Here's a sample of the tables (PostgreSQL):
merchants
+----+---------------+
| id | name |
+----+---------------+
| 1 | amazon_used |
| 2 | ebay_location |
| 3 | amazon |
| 4 | target |
| 5 | target_used |
+----+---------------+
link_prices
+----+-------------+------------+-------+
| id | merchant_id | product_id | price |
+----+-------------+------------+-------+
| 1 | 1 | 1 | |
| 2 | 1 | 2 | 20 |
| 3 | 4 | 2 | 30 |
| 4 | 5 | 2 | 5 |
| 5 | 2 | 3 | 10 |
| 6 | 1 | 4 | 80 |
| 7 | 1 | 3 | 100 |
+----+-------------+------------+-------+
In this case, I'm expecting my query to return
+------------+----------------+---------------+
| product_id | min_used_price | min_new_price |
+------------+----------------+---------------+
| 2 | 5 | 30 |
+------------+----------------+---------------+
I've got the following query to work but I feel like I shouldn't need to use subqueries to achieve this. I just can't work my head around it. Any help would be appreciated to optimize this query.
SELECT products.id,
MIN(CASE WHEN merchants.name ILIKE '%used%' THEN link_prices.price END) as min_used_price,
MIN(CASE WHEN merchants.name NOT ILIKE '%used%' THEN link_prices.price END) as min_new_price
FROM products
INNER JOIN link_prices ON link_prices.product_id = products.id
INNER JOIN merchants ON merchants.id = link_prices.merchant_id
WHERE
products.id IN (
SELECT products.id
FROM products
INNER JOIN link_prices ON link_prices.product_id = products.id
INNER JOIN merchants ON merchants.id = link_prices.merchant_id
AND merchants.name ILIKE '%used%'
AND link_prices.price IS NOT NULL
AND link_prices.price <> 0
)
AND products.id IN (
SELECT products.id
FROM products
INNER JOIN link_prices ON link_prices.product_id = products.id
INNER JOIN merchants ON merchants.id = link_prices.merchant_id
AND merchants.name NOT ILIKE '%used%'
AND merchants.name NOT ILIKE '%location%'
AND link_prices.price IS NOT NULL
AND link_prices.price <> 0
)
GROUP BY products.id
Thanks a ton!
Your description makes this sound like conditional aggregation:
select lp.product_id,
min(lp.price) filter (where m.name like '%used') as min_used_price,
min(lp.price) filter (where m.name not like '%used') as min_new_price
from merchants m join
link_prices lp
on lp.merchant_id = m.id
group by lp.product_id;
You sample query is much more complicated and has conditions that are not mentioned in the text of the question. But I think this structure will work for what you want to do.

A query that counts the number of records from another table

I have tables merchants:
merchant_id | merchant_name | ... |
-------------------------------------
1 | merchant name 1 | ... |
2 | merchant name 2 | ... |
3 | merchant name 3 | ... |
Product Table products:
product_id | product_name | ... |
------------------------------------
1 | product name 1 | ... |
2 | product name 2 | ... |
3 | product name 3 | ... |
And there are table likes_products:
like_id | product_Id | user_id |
-----------------------------------
1 | 101 | 101 |
2 | 102 | 102 |
3 | 103 | 101 |
Table likes_merchants:
like_id | merchants_Id | user_id |
-----------------------------------
1 | 107 | 101 |
2 | 108 | 102 |
3 | 109 | 101 |
Table bookmarks_products:
bookmark_id | product_Id | user_id |
---------------------------------------
1 | 101 | 101 |
2 | 102 | 102 |
3 | 103 | 101 |
I make a request that displays all the products, counts their number of likes. Then he looks like the user from the user who is now authorized products, and if this user has the product tabs, and displays true or false:
SELECT P.* ,
COUNT(L.USER_ID) AS LIKES,
(B.PRODUCT_ID IS NOT NULL) AS BOOKMARKS,
(L.PRODUCT_ID IS NOT NULL) AS IS_LIKED
FROM PRODUCTS AS P
LEFT JOIN BOOKMARKS_PRODUCTS AS B ON (B.PRODUCT_ID = P.PRODUCT_ID)
LEFT JOIN LIKES_PRODUCTS AS L ON (L.PRODUCT_ID = P.PRODUCT_ID)
GROUP BY P.PRODUCT_ID, B.PRODUCT_ID, L.PRODUCT_ID
ORDER BY P.PRODUCT_ID
My problem is that there:
1) I can’t check what an authorized user likes or likes in bookmarks. If any caller likes a product or bookmarks it, he will release true.
2) I can not understand why he considers the number of likes and bookmarks together. That is: if a user has put a like and bookmarked a product, then one like is put, then another user has bookmarked this product, then the likes have increased, and if he likes this product, they will be doubled.
Help me understand my problem please
What I expect to receive:
product_id | product_name | ... | LIKES | BOOKMARKS | IS_LIKED |
-------------------------------------------------------------------
1 | product name 1 | ... | 1 | false | true |
2 | product name 2 | ... | 0 | true | true |
3 | product name 3 | ... | 5 | false | false |
But the problem is that if there are likes from users in the table, there are bookmarks from users in the table, it sums them up and outputs the correct half-half likes.
This is what happens without GROUP BY:
SELECT P.* ,
(B.PRODUCT_ID IS NOT NULL) AS BOOKMARKS,
(L.PRODUCT_ID IS NOT NULL) AS IS_LIKED
FROM PRODUCTS AS P
LEFT JOIN BOOKMARKS_PRODUCTS AS B ON (B.PRODUCT_ID = P.PRODUCT_ID)
LEFT JOIN LIKES_PRODUCTS AS L ON (L.PRODUCT_ID = P.PRODUCT_ID)
ORDER BY P.PRODUCT_ID
product_id | product_name | ... | LIKES | BOOKMARKS | IS_LIKED |
-------------------------------------------------------------------
1 | product name 1 | ... | 1 | false | true |
1 | product name 1 | ... | 1 | false | true |
3 | product name 3 | ... | 5 | false | false |
Product 1 is entered twice as it is in two tables likes_products and bookmarks_products
You can try below -
select A.product_id,likes,
case when B.PRODUCT_ID IS NOT NULL then 'true' else 'false' end AS BOOKMARKS,
case when L.PRODUCT_ID IS NOT NULL then 'true' else 'false' end AS IS_LIKED
from
(
SELECT P.PRODUCT_ID ,
COUNT(L.USER_ID) AS LIKES,
FROM PRODUCTS AS P
LEFT JOIN LIKES_PRODUCTS AS L ON L.PRODUCT_ID = P.PRODUCT_ID
GROUP BY P.PRODUCT_ID
)A
LEFT JOIN BOOKMARKS_PRODUCTS AS B ON B.PRODUCT_ID = A.PRODUCT_ID
LEFT JOIN LIKES_PRODUCTS AS L ON L.PRODUCT_ID = A.PRODUCT_ID
order by A.product_id

Joining 6 tables into single query?

Hey can anyone help me join the 5 tables below into a single query? I currently have the query below but is doesn't seem to work as if there are two products with the same ID inside the hires table all of the products are returned form the products table which is obviously wrong.
SELECT products.prod_id, products.title, products.price, product_types.name,
listagg(suppliers.name, ',') WITHIN GROUP(ORDER BY suppliers.name) suppliers
FROM products
INNER JOIN product_suppliers ON products.prod_id = product_suppluer.prod_id
INNER JOIN product_types ON product_types.type_id = products.type_id
INNER JOIN suppliers ON product_suppliers.supp_id = suppliers.supp_id
LEFT OUTER JOIN hires ON hires.prod_id = products.prod_id
WHERE (hires.hire_end < to_date('21-JAN-13') OR hires.hire_start > to_date('26-JAN-13'))
OR hires.prod_id IS NULL
GROUP BY products.prod_id, products.title, products.price, product_types.name
Table data:
PRODUCTS
--------------------------------------------
| Prod_ID | Title | Price | Type_ID |
|------------------------------------------|
| 1 | A | 5 | 1 |
| 2 | B | 7 | 1 |
| 3 | C | 3 | 2 |
| 4 | D | 3 | 3 |
|------------------------------------------|
PRODUCT_TYPES
----------------------
| Type_ID | Type |
|--------------------|
| 1 | TYPE_A |
| 2 | TYPE_B |
| 3 | TYPE_C |
| 4 | TYPE_D |
|--------------------|
PRODUCT_SUPPLIERS
-------------------------
| Prod_ID | Supp_ID |
|-----------------------|
| 1 | 1 |
| 1 | 2 |
| 2 | 2 |
| 3 | 3 |
| 4 | 4 |
|-----------------------|
SUPPLIERS
----------------------
| Supp_ID | Name |
|--------------------|
| 1 | SUPP_A |
| 2 | SUPP_B |
| 3 | SUPP_C |
| 4 | SUPP_D |
|--------------------|
HIRES
---------------------------------------------------------------
| Hire_ID | Prod_ID | Cust_ID | Hire_Start | Hire_End |
|-----------------------|------------|------------------------|
| 1 | 1 | 1 | 22-Jan-13 | 23-Jan-13 |
| 2 | 2 | 2 | 27-Jan-13 | 29-Jan-13 |
| 3 | 1 | 3 | 30-Jan-13 | 31-Jan-13 |
|-----------------------|------------|------------|-----------|
PRODUCTS
--------------------------------
| Cust_ID | Name | Phone |
|------------------------------|
| 1 | Cust_A | 555-666 |
| 2 | Cust_B | 444-234 |
| 3 | Cust_C | 319-234 |
| 4 | Cust_D | 398-092 |
|------------------------------|
The output from the query at the moment looks like this:
-------------------------------------------------------------
| Prod_ID | Title | Price | Type_ID | Suppliers |
|------------------------------------------|----------------|
| 1 | A | 5 | Type_A | SUPP_A,SUPP_B |
| 2 | B | 7 | Type_B | SUPP_B |
| 3 | C | 3 | Type_C | SUPP_C |
| 4 | D | 3 | Type_D | SUPP_D |
|------------------------------------------|----------------|
When it should look like this surely? as Prod_ID '1' is hired out between the dates in the query
-------------------------------------------------------------
| Prod_ID | Title | Price | Type_ID | Suppliers |
|------------------------------------------|----------------|
| 2 | B | 7 | Type_B | SUPP_B |
| 3 | C | 3 | Type_C | SUPP_C |
| 4 | D | 3 | Type_D | SUPP_D |
|------------------------------------------|----------------|
If anyone can help modify the query to output as suggested i would be really grateful. Because my understanding is that it should work as written?
Your issue is that Prod_Id 1 is both in and out of those date ranges. So instead, use a subquery to filter out which Prod_Id are in those ranges, and exclude those.
This is a much simplified version of your query:
SELECT P.Prod_ID
FROM Products P
LEFT JOIN (
SELECT Prod_ID
FROM Hires
WHERE hire_end >= To_Date('20130121', 'yyyymmdd') AND hire_start <= To_Date('20130126', 'yyyymmdd')
) H ON P.Prod_ID = H.Prod_ID
WHERE h.prod_id IS NULL
And the SQL Fiddle.
Assuming I copied and pasted correctly, this should be your query:
SELECT products.prod_id, products.title, products.price, product_types.name,
listagg(suppliers.name, ',') WITHIN GROUP(ORDER BY suppliers.name) suppliers
FROM products
INNER JOIN product_suppliers ON products.prod_id = product_suppluer.prod_id
INNER JOIN product_types ON product_types.type_id = products.type_id
INNER JOIN suppliers ON product_suppliers.supp_id = suppliers.supp_id
LEFT JOIN (
SELECT Prod_ID
FROM Hires
WHERE hire_end >= To_Date('20130121', 'yyyymmdd') AND hire_start <= To_Date('20130126', 'yyyymmdd')
) H ON products.Prod_ID = H.Prod_ID
WHERE H.Prod_ID IS NULL
GROUP BY products.prod_id, products.title, products.price, product_types.name
Hope this helps.
Your left outer join will return null values when there is no match, meaning you still have a row (with no HIRE table data) when the results of this join query are Null:
LEFT OUTER JOIN hires ON hires.prod_id = products.prod_id
WHERE (hires.hire_end < to_date('21-JAN-13')
OR hires.hire_start > to_date('26-JAN-13'))
OR hires.prod_id IS NULL
Try adding a select from the hires table (eg. hire.Hire_Start) to see this happening, then switch it to an inner join as well and I think your problem will be solved.
OR add a WHERE clause on the full query with something like hire.Hire_Start is not null
EDIT
If you change your original query to:
SELECT hires.Hire_Start, products.prod_id, products.title, products.price, product_types.name,
listagg(suppliers.name, ',') WITHIN GROUP(ORDER BY suppliers.name) suppliers
FROM products
INNER JOIN product_suppliers ON products.prod_id = product_suppluer.prod_id
INNER JOIN product_types ON product_types.type_id = products.type_id
INNER JOIN suppliers ON product_suppliers.supp_id = suppliers.supp_id
LEFT OUTER JOIN hires ON hires.prod_id = products.prod_id
WHERE (hires.hire_end < to_date('21-JAN-13') OR hires.hire_start > to_date('26- JAN-13'))
OR hires.prod_id IS NULL
GROUP BY products.prod_id, products.title, products.price, product_types.name
What comes back in the Hire_Start column?
Then if you add it to the where clause do you get the expected result:
SELECT hires.Hire_Start, products.prod_id, products.title, products.price, product_types.name,
listagg(suppliers.name, ',') WITHIN GROUP(ORDER BY suppliers.name) suppliers
FROM products
INNER JOIN product_suppliers ON products.prod_id = product_suppluer.prod_id
INNER JOIN product_types ON product_types.type_id = products.type_id
INNER JOIN suppliers ON product_suppliers.supp_id = suppliers.supp_id
LEFT OUTER JOIN hires ON hires.prod_id = products.prod_id
WHERE (hires.hire_end < to_date('21-JAN-13') OR hires.hire_start > to_date('26- JAN-13'))
OR hires.prod_id IS NULL
WHERE hires.Hire_Start is not null
GROUP BY products.prod_id, products.title, products.price, product_types.name
Finally, dropping the Outer Join altogether, does this work as expected?
SELECT hires.Hire_Start, products.prod_id, products.title, products.price, product_types.name,
listagg(suppliers.name, ',') WITHIN GROUP(ORDER BY suppliers.name) suppliers
FROM products
INNER JOIN product_suppliers ON products.prod_id = product_suppluer.prod_id
INNER JOIN product_types ON product_types.type_id = products.type_id
INNER JOIN suppliers ON product_suppliers.supp_id = suppliers.supp_id
INNER JOIN hires ON hires.prod_id = products.prod_id
WHERE (hires.hire_end < to_date('21-JAN-13') OR hires.hire_start > to_date('26- JAN-13'))
GROUP BY products.prod_id, products.title, products.price, product_types.name
And note: is the OR Hires.prod_ID sopposed to indicate that if the result returns no hire information it is available, in which case you need to write the query more like the other answer provided.
Here is some code that may help you:
SELECT L.V_PRODUCT_ID "PROD_ID" , L.TITLE "TITLE" , L.PRICE "PRICE" , L.TYPE "TYPE" , S.NAME "SUPPLIERS"
FROM
(SELECT V_PRODUCT_ID , TITLE , PRICE , TYPE , SUPPLIER_ID FROM
((select p.prod_id v_product_id , p.title TITLE , p.price PRICE , t.type TYPE
from products p , products_types t
where p.type_id = t_type_id) A
JOIN
(SELECT PROD_ID VV_PRODUCT_ID , SUPP_ID SUPPLIER_ID
FROM PRODUCTS_SUPPLIERS) H
ON (A.V_PRODUCT_ID = H.VV_PRODUCT_ID))) L
JOIN
SUPLLIERS S
ON (L.SUPPLIER_ID = S.SUPP_ID);
SELECT Emp.Empid, Emp.EmpFirstName, Emp.EmpLastName, Dept.DepartmentName
FROM Employee Emp
INNER JOIN Department dept
ON Emp.Departmentid=Dept.Departmenttid

Left Join on Associative Table

I have three tables
Prospect -- holds prospect information
id
name
projectID
Sample data for Prospect
id | name | projectID
1 | p1 | 1
2 | p2 | 1
3 | p3 | 1
4 | p4 | 2
5 | p5 | 2
6 | p6 | 2
Conjoint -- holds conjoint information
id
title
projectID
Sample data
id | title | projectID
1 | color | 1
2 | size | 1
3 | qual | 1
4 | color | 2
5 | price | 2
6 | weight | 2
There is an associative table that holds the conjoint values for the prospects:
ConjointProspect
id
prospectID
conjointID
value
Sample Data
id | prospectID | conjointID | value
1 | 1 | 1 | 20
2 | 1 | 2 | 30
3 | 1 | 3 | 50
4 | 2 | 1 | 10
5 | 2 | 3 | 40
There are one or more prospects and one or more conjoints in their respective tables. A prospect may or may not have a value for each conjoint.
I'd like to have an SQL statement that will extract all conjoint values for each prospect of a given project, displaying NULL where there is no value for a value that is not present in the ConjointProspect table for a given conjoint and prospect.
Something along the lines of this for projectID = 1
prospectID | conjoint ID | value
1 | 1 | 20
1 | 2 | 30
1 | 3 | 50
2 | 1 | 10
2 | 2 | NULL
2 | 3 | 40
3 | 1 | NULL
3 | 2 | NULL
3 | 3 | NULL
I've tried using an inner join on the prospect and conjoint tables and then a left join on the ConjointProspect, but somewhere I'm getting a cartesian products for prospect/conjoint pairs that don't make any sense (to me)
SELECT p.id, p.name, c.id, c.title, cp.value
FROM prospect p
INNER JOIN conjoint c ON p.projectID = c.projectid
LEFT JOIN conjointProspect cp ON cp.prospectID = p.id
WHERE p.projectID = 2
ORDER BY p.id, c.id
prospectID | conjoint ID | value
1 | 1 | 20
1 | 2 | 30
1 | 3 | 50
1 | 1 | 20
1 | 2 | 30
1 | 3 | 50
1 | 1 | 20
1 | 2 | 30
1 | 3 | 50
2 | 1 | 10
2 | 2 | 40
2 | 1 | 10
2 | 2 | 40
2 | 1 | 10
2 | 2 | 40
3 | 1 | NULL
3 | 2 | NULL
3 | 3 | NULL
Guidance is very much appreciated!!
Then this will work for you... Prejoin a Cartesian against all prospects and elements within that project via a select as your first FROM table. Then, left join to the conjoinprospect. You can obviously change / eliminate certain columns from result, but at least all is there, in the join you want with exact results you are expecting...
SELECT
PJ.*,
CJP.Value
FROM
( SELECT
P.ID ProspectID,
P.Name,
P.ProjectID,
CJ.Title,
CJ.ID ConJointID
FROM
Prospect P,
ConJoint CJ
where
P.ProjectID = 1
AND P.ProjectID = CJ.ProjectID
ORDER BY
1, 4
) PJ
LEFT JOIN conjointProspect cjp
ON PJ.ProspectID = cjp.prospectID
AND PJ.ConjointID = cjp.conjointid
ORDER BY
PJ.ProspectID,
PJ.ConJointID
Your cartesian product is a result of joining by project Id - in your sample data there are 3 prospects with a project id of 1 and 3 conjoints with a project id of 1. Joining based on project id should then result in 9 rows of data, which is what you're getting. It looks like you really need to join via the conjointprospects table as that it what holds the mapping between prospects and conjoint.
What if you try something like:
SELECT p.id, p.name, c.id, c.title, cp.value
FROM prospect p
LEFT JOIN conjointProspect cp ON cp.prospectID = p.id
RIGHT JOIN conjoint c ON cp.conjointID = c.id
WHERE p.projectID = 2
ORDER BY p.id, c.id
Not sure if that will work, but it seems like conjointprospects needs to be at the center of your join in order to correctly map prospects to conjoints.