Left join without using left join with 3 tables - sql

I have to get all data from 3 tables with left join. I did that with and without using left join. But my teacher ask another solution that uses just inner join. I just couldn't find the solution please give some advice. Here are the tables:
And the results should look like this:
With left join solution:
SELECT O.*,C.CUST_CODE,C.CUST_NAME,P.PART_CODE,P.PART_NAME
FROM ORDERS O
LEFT OUTER JOIN PART P ON P.PART_ID = O.PART_ID
LEFT OUTER JOIN CUSTOMER C ON C.CUST_ID = O.CUST_ID
Without left join solution:
SELECT O.*,
(SELECT C.CUST_CODE FROM CUSTOMER C WHERE C.CUST_ID=O.CUST_ID) AS CUST_CODE,
(SELECT C.CUST_NAME FROM CUSTOMER C WHERE C.CUST_ID=O.CUST_ID) AS CUST_NAME,
(SELECT P.PART_CODE FROM PART P WHERE P.PART_ID = O.PART_ID ) AS PART_CODE,
(SELECT P.PART_NAME FROM PART P WHERE P.PART_ID = O.PART_ID ) AS PART_NAME
FROM ORDERS O

Here is one approach which uses only INNER JOIN and does not use LEFT JOIN anywhere. It begins with your original query using INNER JOIN instead of LEFT JOIN, and then adds to that result set the pieces which are missing from taking the inner product of all the tables.
SELECT t.* FROM
(
SELECT O.ORDER_ID, O.ORDER_DATE, C.CUST_CODE, C.CUST_NAME, P.PART_CODE, P.PART_NAME
FROM ORDERS O
INNER JOIN PART P
ON P.PART_ID = O.PART_ID
INNER JOIN CUSTOMER C
ON C.CUST_ID = O.CUST_ID
UNION
SELECT O.ORDER_ID, O.ORDER_DATE, NULL AS CUST_CODE, NULL AS CUST_NAME, P.PART_CODE,
P.PART_NAME
FROM ORDERS O
INNER JOIN PART P
ON P.PART_ID = O.PART_ID
WHERE O.CUST_ID NOT IN (SELECT C.CUST_ID FROM CUSTOMER C)
OR O.CUST_ID IS NULL
UNION
SELECT O.ORDER_ID, O.ORDER_DATE, C.CUST_CODE, C.CUST_NAME, NULL AS PART_CODE,
NULL AS PART_NAME
FROM ORDERS O
INNER JOIN CUSTOMER C
ON C.CUST_ID = O.CUST_ID
WHERE O.PART_ID NOT IN (SELECT P.PART_ID FROM PART P)
OR O.PART_ID IS NULL
UNION
SELECT O.ORDER_ID, O.ORDER_DATE, NULL AS CUST_CODE, NULL AS CUST_NAME,
NULL AS PART_CODE, NULL AS PART_NAME
FROM ORDERS O
WHERE (O.CUST_ID NOT IN (SELECT C.CUST_ID FROM CUSTOMER C) AND
O.PART_ID NOT IN (SELECT P.PART_ID FROM PART P)) OR
(O.CUST_ID IS NULL AND O.PART_ID IS NULL)
) t
ORDER BY t.ORDER_ID ASC
Follow the link below for a working demo:
SQLFiddle

This makes no sense in "real life", but I understand it's meaningfulness in a SQL course. You have a good teacher.
Try like this
SELECT * FROM
(
SELECT O.*,C.CUST_CODE,C.CUST_NAME,P.PART_CODE,P.PART_NAME
FROM ORDERS O
INNER JOIN PART P ON P.PART_ID = O.PART_ID
INNER JOIN CUSTOMER C ON C.CUST_ID = O.CUST_ID
UNION
SELECT O.*,NULL,NULL,P.PART_CODE,P.PART_NAME
FROM ORDERS O
INNER JOIN PART P ON P.PART_ID = O.PART_ID
WHERE O.CUST_ID IS NULL
UNION
SELECT O.*,C.CUST_CODE,C.CUST_NAME,NULL, NULL
FROM ORDERS O
INNER JOIN CUSTOMER C ON C.CUST_ID = O.CUST_ID
WHERE O.PART_ID IS NULL
UNION
SELECT O.*,NULL,NULL,NULL, NULL
FROM ORDERS O
WHERE O.PART_ID IS NULL AND O.CUST_ID IS NULL
) T
ORDER BY ORDER_ID

Related

Gross sales of top ten customers that made at least 5 purchases of some Category in a specified year

I need to get (sales.customers) | year | gross_sales of top ten customers that made at least 5 purchases of Category "Beverages" in the year 2014. I have already written these SELECT queries, but since I am new to SQL, I think I am very inefficient in writing code. This does not work properly and there is probably a simpler way of doing it. I have also pinned a picture of an ER diagram .
SELECT
T6.COMPANYNAME, YEAR, GROSS_SALES
FROM
(SELECT T1.CUSTID
FROM
(SELECT R.CUSTID, COUNT(R.CUSTID) AS NUMBEROFSALES
FROM SALES.ORDERDETAILS O
RIGHT JOIN PRODUCTION.PRODUCTS P ON P.PRODUCTID = O.PRODUCTID
RIGHT JOIN SALES.ORDERS R ON R.ORDERID = O.ORDERID
INNER JOIN SALES.CUSTOMERS C1 ON R.CUSTID = C1.CUSTID
INNER JOIN PRODUCTION.CATEGORIES C2 ON P.CATEGORYID = C2.CATEGORYID
WHERE C2.CATEGORYNAME = 'Beverages' AND YEAR(R.ORDERDATE) = 2014
GROUP BY R.CUSTID
ORDER BY SUM(R.CUSTID) DESC) T1
--HAVING COUNT(R.CUSTID) > 5
RIGHT JOIN
(SELECT R.CUSTID, SUM(O.UNITPRICE) AS MONEYSPENT
FROM SALES.ORDERDETAILS O
RIGHT JOIN PRODUCTION.PRODUCTS P ON P.PRODUCTID = O.PRODUCTID
RIGHT JOIN SALES.ORDERS R ON R.ORDERID = O.ORDERID
INNER JOIN SALES.CUSTOMERS C1 ON R.CUSTID = C1.CUSTID
INNER JOIN PRODUCTION.CATEGORIES C2 ON P.CATEGORYID = C2.CATEGORYID
WHERE C2.CATEGORYNAME = 'Beverages' AND YEAR(R.ORDERDATE) = 2014
GROUP BY R.CUSTID
ORDER BY SUM(O.UNITPRICE) DESC) T2 ON T1.CUSTID = T2.CUSTID
ORDER BY T1.NUMBEROFSALES DESC
LIMIT 10) T5
INNER JOIN
(SELECT DISTINCT(T4.COMPANYNAME), T4.CUSTID, YEAR, GROSS_SALES
FROM
(SELECT R.CUSTID AS CUSTID, YEAR(R.ORDERDATE) AS YEAR, SUM(O.UNITPRICE * O.QTY * (1 - O.DISCOUNT)) AS GROSS_SALES
FROM SALES.ORDERDETAILS O
RIGHT JOIN SALES.ORDERS R ON R.ORDERID = O.ORDERID
INNER JOIN SALES.CUSTOMERS C1 ON R.CUSTID = C1.CUSTID
GROUP BY R.CUSTID, YEAR(R.ORDERDATE)
ORDER BY YEAR(R.ORDERDATE)) T3
INNER JOIN
(SELECT C.COMPANYNAME, C.CUSTID
FROM SALES.ORDERS R
INNER JOIN SALES.CUSTOMERS C ON R.CUSTID = C.CUSTID) T4 ON T3.CUSTID = T4.CUSTID) T6 ON T5.CUSTID = T6.CUSTID
I'm not going to try and fix your code or explain the misakes there, as there are many of them. Instead based on your requirements I wrote a query that solves the problem. I show it in steps below which should make clear the process I used to solve the problem.
First how do we find the top ten customers that made 5 purchases of Beverages?
Take customer table and join to orders with beverages (inner join will exclude customers that don't meet criteria)
SELECT CUSTOMERID
FROM CUSTOMERS C
JOIN ORDERS O ON C.CUSTOMERID = O.CUSTOMERID
JOIN ORDER_DETAILS OD ON O.ORDERID = OD.ORDERID
JOIN PRODUCTS P ON OD.PRODUCTID = P.PRODUCTID
JOIN CATEGORIES C ON P.CATEGORYID = C.CATEGORYID AND C. CATEGORY_NAME = 'Beverages'
WHERE YEAR(ORDER_DATE) = 2014
GROUP BY CUSTOMERID
HAVING COUNT(ORDER_DETAILS) >= 5
Now we need the sum of order (for 2014) by customers which looks like this:
SELECT CUSTOMERID, YEAR(ORDER_DATE) AS YEAR, SUM(OD.UNIT_PRICE*OD.QUANTITY) AS TOTAL_SPEND
FROM CUSTOMERS C
JOIN ORDERS O ON C.CUSTOMERID = O.CUSTOMERID
JOIN ORDER_DETAILS OD ON O.ORDERID = OD.ORDERID
WHERE YEAR(ORDER_DATE) = 2014
GROUP BY CUSTOMERID, YEAR(ORDER_DATE)
Now we just combine these two queries like this:
SELECT CUSTOMERID, YEAR(ORDER_DATE) AS YEAR, SUM(OD.UNIT_PRICE*OD.QUANTITY) AS TOTAL_SPEND
FROM CUSTOMERS C
JOIN ORDERS O ON C.CUSTOMERID = O.CUSTOMERID
JOIN ORDER_DETAILS OD ON O.ORDERID = OD.ORDERID
JOIN (
SELECT CUSTOMERID
FROM CUSTOMERS C
JOIN ORDERS O ON C.CUSTOMERID = O.CUSTOMERID
JOIN ORDER_DETAILS OD ON O.ORDERID = OD.ORDERID
JOIN PRODUCTS P ON OD.PRODUCTID = P.PRODUCTID
JOIN CATEGORIES C ON P.CATEGORYID = C.CATEGORYID AND C. CATEGORY_NAME = 'Beverages'
WHERE YEAR(ORDER_DATE) = 2014
GROUP BY CUSTOMERID
HAVING COUNT(ORDER_DETAILS) >= 5
) as SUB ON SUB.CUSTOMERID = C.CUSTOMERID
WHERE YEAR(ORDER_DATE) = 2014
GROUP BY CUSTOMERID, YEAR(ORDER_DATE)
ORDER BY SUM(OD.UNIT_PRICE*OD.QUANTITY)
LIMIT 10
Note I did not test this but just wrote the SQL since I don't have a db to test against so there might be typos
Also Note: I'm expect it is possible to remove the sub query as it is doing a lot of the same joins the outer query is-- but we want to make sure we get the correct result and it is easier to see it is correct this way. You can also test the sub-query by itself to make sure it returns expected results.

How connect two select?

I have a problem with select from two table.
I use filtr from date, when I used NOT IN but didn't work, because data to result not in second table. I want display outfits which is free in these date and outfits which not in table rent.
Structure of base in the picture:
SELECT o.Id, o.Name, c.Name, o.Description,o.Price, r.Date1, r.Date2, r.Return
FROM Outfit o INNER JOIN
Category c
ON o.Category = c.Id INNER JOIN
Rent r
ON o.Id = r.OutFit
WHERE (myfiltrdate NOT BETWEEN r.Date1 AND r.Date2) OR
r.Return IS NOT NULL
Now my result are only record/outfit which are Rent table, but I want this result and Outfit which are not in table Rent
You may try a left join -
SELECT o.Id, o.Name, c.Name, o.Description,o.Price, r.Date1, r.Date2, r.Return
FROM Outfit o LEFT JOIN
Category c
ON o.Category = c.Id INNER JOIN
Rent r
ON o.Id = r.OutFit
WHERE (myfiltrdate NOT BETWEEN r.Date1 AND r.Date2) OR
r.Return IS NULL
I think you want LEFT JOINs and filtering. Something like this:
SELECT o.Id, o.Name, c.Name, o.Description, o.Price,
r.Date1, r.Date2, r.Return
FROM Outfit o LEFT JOIN
Category c
ON o.Category = c.Id LEFT JOIN
Rent r
ON o.Id = r.OutFit AND
(myfiltrdate BETWEEN r.Date1 AND r.Date2)
WHERE r.Return IS NULL;
Change your query to left join and filter the dates like this:
SELECT o.Id, o.Name, c.Name, o.Description,o.Price, r.Date1, r.Date2, r.Return
FROM Outfit o
INNER JOIN Category c ON o.Category = c.Id
LEFT JOIN Rent r ON o.Id = r.OutFit
WHERE (myfiltrdate < r.Date1 AND myfiltrdate > r.Date2) OR
r.Return IS NOT NULL
I don't have clear if Return is a date which the outfit is returned or is a boolean but I suspect that the condition must be
And r.Return IS NOT NULL

How to obtain two values from a sub-query to be used in the where clause of sql

I have to use two values in the where clause to be tested for equality of two values obtained from a subquery. Since I am working on an existing application, I want to keep it as a subquery. The following is my query.
SELECT
o.EMAIL_ADDRESS, c.FIRST_NAME, p.PARTY_ID
FROM
ORDER o WITH (NOLOCK)
INNER JOIN
PARTY p WITH (NOLOCK) ON o.ORDER_ID = p.PARTY_ID
INNER JOIN
CUSTOMER c WITH (NOLOCK) ON p.PARTY_ID = c.CUSTOMER_ID
WHERE
(o.EMAIL_ADDRESS, c.CUSTOMER_ID) IN (SELECT EMAIL_ADDRESS, CUSTOMER_ID
FROM CUSTOMER_MASTER
WHERE insert_date > '01/02/2019')
The problem I am facing is that the first value within the where clause, o.EMAIL_ADDRESS, throws the following error:
An expression of non-boolean type specified in a context where a condition is expected
When I use a single value within the where clause it works fine.
One method is with EXISTS and a correlated subquery.
SELECT o.EMAIL_ADDRESS, c.FIRST_NAME, p.PARTY_ID
FROM ORDER_HEADER oh WITH (NOLOCK)
INNER JOIN PARTY p WITH (NOLOCK) ON o.ORDER_ID = p.PARTY_ID
INNER JOIN CUSTOMER c WITH (NOLOCK) ON p.PARTY_ID = c.CUSTOMER_ID
WHERE EXISTS(
SELECT 1
FROM CUSTOMER_MASTER AS cm
WHERE cm.insert_date > '01/02/2019'
AND o.EMAIL_ADDRESS = cm.EMAIL_ADDRESS
AND c.CUSTOMER_ID = cm.c.CUSTOMER_ID
);
in allows single column list only. So create single column.
SELECT o.EMAIL_ADDRESS, c.FIRST_NAME, p.PARTY_ID
FROM ORDER o WITH (NOLOCK)
INNER JOIN PARTY p WITH (NOLOCK) ON o.ORDER_ID = p.PARTY_ID
INNER JOIN CUSTOMER c WITH (NOLOCK) ON p.PARTY_ID = c.CUSTOMER_ID
WHERE o.EMAIL_ADDRESS + cast(c.CUSTOMER_ID as varchar)
IN (select EMAIL_ADDRESS + cast(CUSTOMER_ID as varchar)
from CUSTOMER_MASTER where insert_date > '01/02/2019')
Use EXISTS. I would also advise fixing the date format:
SELECT o.EMAIL_ADDRESS, c.FIRST_NAME, p.PARTY_ID
FROM ORDER o JOIN
PARTY p
ON o.ORDER_ID = p.PARTY_ID JOIN
CUSTOMER c
ON p.PARTY_ID = c.CUSTOMER_ID
WHERE EXISTS (SELECT 1
FROM CUSTOMER_MASTER cm
WHERE cm.EMAIL_ADDRESS = o.EMAIL_ADDRESS AND
cm.CUSTOMER_ID = c.CUSTOMER_ID AND
cm.insert_date > '2019-02-01'
);
Your date constants should be in the YYYYMMDD format.
Based on your logic, though, I don't think you need all the JOINs:
SELECT o.EMAIL_ADDRESS, c.FIRST_NAME, o.ORDER_ID
FROM ORDER o JOIN
CUSTOMER c
ON o.ORDER_ID = c.CUSTOMER_ID
WHERE EXISTS (SELECT 1
FROM CUSTOMER_MASTER cm
WHERE cm.EMAIL_ADDRESS = o.EMAIL_ADDRESS AND
cm.CUSTOMER_ID = c.CUSTOMER_ID AND
cm.insert_date > '2019-02-01'
);
The table PARTY doesn't seem necessary, because the orders can align directly to the customers.

Missing Right Parenthesis issue

This is the Query I have written
Select C.CUST_NAME,P.PROD_DESCRIP from Customer C
JOIN (Ord O,OrderItem OT, Product P) ON (C.CUST_ID = O.CUST_ID AND O.ORD_ID = OT.ORD_ID AND OT.PROD_ID = P.PROD_ID) GROUP BY C.CUST_NAME ORDER BY OT.ORDITEM_QTY DESC
But the issue is it giving me Right Parenthesis Missing issue
Although that join syntax is allowed in some databases, it is really much clearer to split out the joins:
Select C.CUST_NAME, P.PROD_DESCRIP
from Customer C JOIN
Ord O
on C.CUST_ID = O.CUST_ID JOIN
OrderItem OT
on O.ORD_ID = OT.ORD_ID JOIN
Product P
ON OT.PROD_ID = P.PROD_ID
GROUP BY C.CUST_NAME
ORDER BY OT.ORDITEM_QTY DESC;
By the way, this probably isn't doing what you think it does. It is returning a customer name along with an arbitrary prod_descrip. It is then ordering this result by an arbitrary quantity -- perhaps from the same or a different row.
If you want to get the customer name along with the product with the maximum quantity for that customer, you can do this:
Select C.CUST_NAME,
substring_index(group_concat(P.PROD_DESCRIP order by OT.ORDITEM_QTY desc), ',', 1) as PROD_DESCRIP
from Customer C JOIN
Ord O
on C.CUST_ID = O.CUST_ID JOIN
OrderItem OT
on O.ORD_ID = OT.ORD_ID JOIN
Product P
ON OT.PROD_ID = P.PROD_ID
GROUP BY C.CUST_NAME;
Note: If PROD_DESCRIP could have a comma then you will want to use a different separator character.
EDIT:
The above is the MySQL solution. In Oracle, you would do:
select CUST_NAME, PROD_DESCRIP
from (Select C.CUST_NAME, P.PROD_DESCRIP,
row_number() over (partition by C.CUST_NAME order by OT.ORDITEM_QTY desc) as seqnum
from Customer C JOIN
Ord O
on C.CUST_ID = O.CUST_ID JOIN
OrderItem OT
on O.ORD_ID = OT.ORD_ID JOIN
Product P
ON OT.PROD_ID = P.PROD_ID
) t
where seqnum = 1;
This is actually the preferred standard SQL solution. It will work in most databases (SQL Server, Oracle, Postgres, DB2, and Teradata).
SELECT C.CUST_NAME, P.PROD_DESCRIP
FROM Customer C
INNER JOIN Ord O ON C.CUST_ID = O.CUST_ID
INNER JOIN OrderItem OT ON O.ORD_ID = OT.ORD_ID
INNER JOIN Product P ON OT.PROD_ID = P.PROD_ID
GROUP BY C.CUST_NAME
ORDER BY OT.ORDITEM_QTY DESC
SELECT C.CUST_NAME,P.PROD_DESCRIP
FROM Customer C
JOIN Ord O
ON C.CUST_ID = O.CUST_ID
JOIN OrderItem OT
ON O.ORD_ID = OT.ORD_ID
JOIN Product P
ON OT.PROD_ID = P.PROD_ID
GROUP BY C.CUST_NAME
ORDER BY OT.ORDITEM_QTY DESC

LEFT OUTER JOIN EQUIVALENT

I have a tables contains null values. In ORDER table i have 2 null in PART_ID section and 2 null values in CUSTOMER_ID.
And i have that kind of query:
SELECT O.ORDER_ID , O.ORDER_DATE , O.CUST_ID, O.QUANTITY ,O.PART_ID ,
C.CUST_NAME, C.CUST_CODE, P.PART_NAME, P.PART_CODE
FROM [ORDER] O
LEFT OUTER JOIN PART P ON P.PART_ID = O.PART_ID
LEFT OUTER JOIN CUSTOMER C ON C.CUST_ID = O.CUST_ID
So here is my question. How can i do it without using outer join ?
I tried too many things including where not exists or this ;
SELECT *
FROM [ORDER] O ,CUSTOMER C, PART P
WHERE C.CUST_ID = (
SELECT CUST_ID FROM CUSTOMER C WHERE O.CUST_ID = C.CUST_ID
) AND P.PART_ID = (SELECT PART_ID FROM PART P WHERE O.PART_ID = P.PART_ID)
but i couldn't find solution. If there is a solution how it will be ?
(Note: this is homework.)
I have that kind of table :
and left outer join gives that :
the hw said do it without using outer join and get same table as left outer join gives. But like a said i coulnd't. I'm also using MSSQL.
Outer join produces super-set over inner join. Indeed, from Wikipedia: A left outer join returns all the values from an inner join plus all values in the left table that do not match to the right table.
So to model left outer join using inner join one could use UNION of inner join SELECT between same tables with same join condition and another SELECT from 1st table that returns all rows without a match from the right table (I reduced your case to a single left join):
SELECT O.ORDER_ID , O.ORDER_DATE , O.CUST_ID, O.QUANTITY ,O.PART_ID ,
P.PART_NAME, P.PART_CODE
FROM [ORDER] O JOIN PART P ON P.PART_ID = O.PART_ID
UNION
SELECT O.ORDER_ID , O.ORDER_DATE , O.CUST_ID, O.QUANTITY ,O.PART_ID ,
NULL, NULL
FROM [ORDER] O
WHERE NOT EXISTS (SELECT 'found' FROM PART P WHERE P.PART_ID = O.PART_ID)
Presumably, you want to get matches to the columns with NULL values, instead of having them fail. If so, just modify the join conditions:
FROM [ORDER] O
LEFT OUTER JOIN PART P
ON P.PART_ID = O.PART_ID or (p.Part_id is NULL and o.Part_id is null)
LEFT OUTER JOIN CUSTOMER C
ON C.CUST_ID = O.CUST_ID or (c.cust_id is null and o.cust_id is null)
The major issue with this approach is that many (most?) SQL engines will not use indexes for the join.
Is there a specific reason why you don't want to use outer join? Isn't this the result you want? :
FROM [ORDER] O
LEFT JOIN PART P
ON P.PART_ID = O.PART_ID and P.PARTID is not null
LEFT JOIN CUSTOMER C
ON C.CUST_ID = O.CUST_ID and C.CUSTID is not null
So complete answer should be like this (after my teacher gave results) :
SELECT O.ORDER_ID,O.ORDER_DATE,O.CUST_ID,
(SELECT C.CUST_CODE FROM CUSTOMER C WHERE C.CUST_ID=O.CUST_ID) AS CUST_CODE,
(SELECT C.CUST_NAME FROM CUSTOMER C WHERE C.CUST_ID=O.CUST_ID) AS CUST_NAME,
O.PART_ID,
(SELECT P.PART_CODE FROM PART P WHERE P.PART_ID = O.PART_ID ) AS PART_CODE,
(SELECT P.PART_NAME FROM PART P WHERE P.PART_ID = O.PART_ID ) AS PART_NAME,
O.QUANTITY
FROM [ORDER] O