SQL query joining four tables - sql

Original query is joining customer table and contract table and Extra Service History, this all works.
However I'm having trouble adding 4th table which should apply some further criteria.
Current working query (no changes needed) :
select b.*
from SubscribersFIN b
inner join (select Id, Account_Number, ContractNumber, BackendId
from Contract) e on b.c_id='FI_' + e.Account_Number
left join (select Contract
from Extra_Service_History
where Service_Name='debit_plan') d on e.Id=d.Contract
where COUNTRY='fi'
and NO_SMS = 0
and d.Contract is null
Goal is to filter the set that came from the big query that only records that had Paid status in Invoice to show.
right join (select Contract
from Invoice
where Status = 'PAID') i on e.Id=i.Contract
This one does not seem to do the trick, so I'm not able to figure out what sort of a join-type or logic is required here.

You have a few options:
INNER JOIN
Depending on the particular type of outer join, they return rows where no match is found (either left, right, or both sides of the join). Based on your description this is not what you want. Simply use:
inner join (select Contract
from Invoice
where Status = 'PAID') i on e.Id=i.Contract
It shouldn't matter where this occurs in the FROM clause; provided the join between these 2 tables is INNER. The query engine is free to rearrange for performance provided it doesn't change semantics. (But personally I find it tidier to put INNER JOINs at the top.)
IN filter
What you've described is a filter.
Goal is to filter the set that came from the big query that only records that had Paid status in Invoice to show.
So it's clearer to implement this as a filter in the WHERE clause. E.g.
where e.Id in (select Contract
from Invoice
where Status = 'PAID')
and ...
EXISTS filter
Similar to the above, but using an EXISTS subquery instead.
where exists (select *
from Invoice i
where Status = 'PAID'
and i.Contract = e.Id)
and ...

Rather than mixing LEFT and RIGHT joins, just place it as an INNER join higher up in your query:
select b.*
from SubscribersFIN b
inner join (select Id, Account_Number, ContractNumber, BackendId
from Contract) e on b.c_id='FI_' + e.Account_Number
inner join (select Contract
from Invoice
where Status = 'PAID') i on e.Id=i.Contract
left join (select Contract
from Extra_Service_History
where Service_Name='debit_plan') d on e.Id=d.Contract
where COUNTRY='fi'
and NO_SMS = 0
and d.Contract is null

Based on my understanding i just re arranged the query. Try this. If your where condition columns are coming from any of the LEFT JOIN tables, join them at the on clause.
select b.* from SubscribersFIN b
inner join Contract e on b.c_id='FI_' + e.Account_Number
left join Extra_Service_History d on e.Id=d.Contract and d.Service_Name='debit_plan' and d.Contract is null
left join invoice i on e.Id=i.Contract and i.Status = 'PAID'
where COUNTRY='fi' and NO_SMS = 0

Related

LEFT JOIN with condition in WHERE clause

SELECT
supplies.id,
supplierId,
supplies.date,
supplies.commodity,
supplier_payments.date AS paymentDate,
FROM
supplies
INNER JOIN suppliers ON suppliers.id = supplies.supplierId
LEFT JOIN supplier_payments ON supplier_payments.supplyId = supplies.id
WHERE supplier_payments.isDeleted = 0 AND supplierId = 1
What I am trying is to get all records from supplies table and related records from supplier_payments but the supplier_payments.isDeleted should be equal to 0. What happens now that I only get records from supplies that have at least one supplier payment because of the condition. Is there a way to get all supply records and supply payments with condition?
Consider moving the condition on the LEFT JOINed table to the ON clause of the JOIN:
SELECT
sr.id,
se.supplierId,
se.date,
se.commodity,
sp.date AS paymentDate,
FROM supplies se
INNER JOIN suppliers sr ON sr.id = se.supplierId
LEFT JOIN supplier_payments sp
ON sp.supplyId = se.id
AND sp.isDeleted = 0
WHERE se.supplierId = 1
Side notes:
in a multi-table query, always qualify each column with the table it belongs to, to make the query easier to follow and avoid ambiguity
table aliases make the query easier to read and write

2 Tables - one customer, one transactions. How to handle a customer with no transaction?

I have 2 tables-one customers, one transactions. One customer does not have any transactions. How do I handle that? As I'm trying to join my tables, the customer with no transaction does not show up as shown in code below.
SELECT Orders.Customer_Id, Customers.AcctOpenDate, Customers.CustomerFirstName, Customers.CustomerLastName, Orders.TxnDate, Orders.Amount
FROM Orders
INNER JOIN Customers ON Orders.Customer_Id=Customers.Customer_Id;
I need to be able to account for the customer with no transaction such as querying for least transaction amount.
Use below updated query - Right Outer join is used instead of Inner join to show all customers regardless of the customer placed an order yet.
SELECT Orders.Customer_Id, Customers.AcctOpenDate,
Customers.CustomerFirstName, Customers.CustomerLastName,
Orders.TxnDate, Orders.Amount
FROM Orders
Right Outer JOIN Customers ON Orders.Customer_Id=Customers.Customer_Id;
INNER Joins show only those records that are present in BOTH tables
OUTER joins gets SQL to list all the records present in the designated table and shows NULLs for the fields in the other table that are not present
LEFT OUTER JOIN (the first table)
RIGHT OUTER JOIN (the second table)
FULL OUTER JOIN (all records for both tables)
Get up to speed on the join types and how to handle NULLS and that is 90% of writing SQL script.
Below is the same query with a left join and using ISNULL to turn the amount column into 0 if it has no records present
SELECT Orders.Customer_Id, Customers.AcctOpenDate, Customers.CustomerFirstName, Customers.CustomerLastName
, Orders.TxnDate, ISNULL(Orders.Amount,0)
FROM Customers
LEFT OUTER JOIN Orders ON Orders.Customer_Id=Customers.Customer_Id;
try this :
SELECT Orders.Customer_Id, Customers.AcctOpenDate, Customers.CustomerFirstName, Customers.CustomerLastName, Orders.TxnDate, Orders.Amount
FROM Orders
Right OUTER JOIN Customers ON Orders.Customer_Id=Customers.Customer_Id;
I strongly recommend LEFT JOIN. This keeps all rows in the first table, along with matching columns in the second. If there are no matching rows, these columns are NULL:
SELECT c.Customer_Id, c.AcctOpenDate, c.CustomerFirstName, c.CustomerLastName,
o.TxnDate, o.Amount
FROM Customers c LEFT JOIN
Orders o
ON o.Customer_Id = c.Customer_Id;
Although you could use RIGHT JOIN, I never use RIGHT JOINs, because I find them much harder to follow. The logic of "keep all rows in the first table I read" is relatively simple. The logic of "I don't know which rows I'm keeping until I read the last table" is harder to follow.
Also note that I included table aliases and change the CustomerId to come from customers -- the table where you are keeping all rows.
Using CASE will replace "null" with 0 then you can sum the values. This will count customers with no transactions.
SELECT c.Name,
SUM(CASE WHEN t.ID IS NULL THEN 0 ELSE 1 END) as TransactionsPerCustomer
FROM Customers c
LEFT JOIN Transactions t
ON c.Name = t.customerID
group by c.Name
SELECT c.Name,
SUM(CASE WHEN t.ID IS NULL THEN 0 ELSE 1 END) as numberoftransaction
FROM customers c
LEFT JOIN transactions t
ON c.Name = t.customerID
group by c.Name

LEFT JOIN help in sql

I have to make a list of customer who do not have any invoice but have paid an invoice … maybe twice.
But with my code (stated below) it contains everything from the left join. However I only need the lines highlighted with green.
How should I make a table with only the 2 highlights?
Select paymentsfrombank.invoicenumber,paymentsfrombank.customer,paymentsfrombank.value
FROM paymentsfrombank
LEFT OUTER JOIN debtors
ON debtors.value = paymentsfrombank.value
You only want to select columns from paymentsfrombank. So why do you even join?
select invoice_number, customer, value from paymentsfrombank
except
select invoice_number, customer, value from debtors;
(This requires exact matches as in your example, i.e. same amount for the invoice/customer).
There are two issues in your SQL. First, you need to join on Invoice number, not on value, as joining on value is pointless. Second, you need to only pick those payments where there are no corresponding debts, i.e. when you left-join, the table on the right has "null" in the joining column. The SQL would be something like this:
SELECT paymentsfrombank.invoicenumber,paymentsfrombank.customer,paymentsfrombank.value
FROM paymentsfrombank
LEFT OUTER JOIN debtors
ON debtors.InvoiceNumber = paymentsfrombank.InvoiceNumber
WHERE debtors.InvoiceNumber is NULL
in mysql we usually have this way to flip the relation and extract the rows that dosen't have relation.
Select paymentsfrombank.invoicenumber,paymentsfrombank.customer,paymentsfrombank.value
FROM paymentsfrombank
LEFT OUTER JOIN debtors
ON debtors.value = paymentsfrombank.value where debtors.value is null
You can use NOT EXISTS :
SELECT p.*
FROM paymentsfrombank p
WHERE NOT EXISTS (SELECT 1 FROM debtors d WHERE d.invoice_number = p.invoice_number);
However, the LEFT OUTER JOIN would also work if you add filtered with WHERE Clause to filtered out only missing customers that haven't any invoice information :
SELECT p.invoicenumber, p.customer, p.value
FROM paymentsfrombank P LEFT OUTER JOIN
debtors d
ON d.InvoiceNumber = p.InvoiceNumber
WHERE d.InvoiceNumber IS NULL;
Note : I have used table alias (p & d) that makes query to easier read & write.

SQL Get aggregate as 0 for non existing row using inner joins

I am using SQL Server to query these three tables that look like (there are some extra columns but not that relevant):
Customers -> Id, Name
Addresses -> Id, Street, StreetNo, CustomerId
Sales -> AddressId, Week, Total
And I would like to get the total sales per week and customer (showing at the same time the address details). I have come up with this query
SELECT a.Name, b.Street, b.StreetNo, c.Week, SUM (c.Total) as Total
FROM Customers a
INNER JOIN Addresses b ON a.Id = b.CustomerId
INNER JOIN Sales c ON b.Id = c.AddressId
GROUP BY a.Name, c.Week, b.Street, b.StreetNo
and even if my SQL skill are close to none it looks like it's doing its job. But now I would like to be able to show 0 whenever the one customer don't have sales for a particular week (weeks are just integers). And I wonder if somehow I should get distinct values of the weeks in the Sales table, and then loop through them (not sure how)
Any help?
Thanks
Use CROSS JOIN to generate the rows for all customers and weeks. Then use LEFT JOIN to bring in the data that is available:
SELECT c.Name, a.Street, a.StreetNo, w.Week,
COALESCE(SUM(s.Total), 0) as Total
FROM Customers c CROSS JOIN
(SELECT DISTINCT s.Week FROM sales s) w LEFT JOIN
Addresses a
ON c.CustomerId = a.CustomerId LEFT JOIN
Sales s
ON s.week = w.week AND s.AddressId = a.AddressId
GROUP BY c.Name, a.Street, a.StreetNo, w.Week;
Using table aliases is good, but the aliases should be abbreviations for the table names. So, a for Addresses not Customers.
You should generate a week numbers, rather than using DISTINCT. This is better in terms of performance and reliability. Then use a LEFT JOIN on the Sales table instead of an INNER JOIN:
SELECT a.Name
,b.Street
,b.StreetNo
,weeks.[Week]
,COALESCE(SUM(c.Total),0) as Total
FROM Customers a
INNER JOIN Addresses b ON a.Id = b.CustomerId
CROSS JOIN (
-- Generate a sequence of 52 integers (13 x 4)
SELECT ROW_NUMBER() OVER (ORDER BY a.x) AS [Week]
FROM (VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) a(x)
CROSS JOIN (SELECT x FROM (VALUES(1),(1),(1),(1)) b(x)) b
) weeks
LEFT JOIN Sales c ON b.Id = c.AddressId AND c.[Week] = weeek.[Week]
GROUP BY a.Name
,b.Street
,b.StreetNo
,weeks.[Week]
Please try the following...
SELECT Name,
Street,
StreetNo,
Week,
SUM( CASE
WHEN Total IS NULL THEN
0
ELSE
Total
END ) AS Total
FROM Customers a
JOIN Addresses b ON a.Id = b.CustomerId
RIGHT JOIN Sales c ON b.Id = c.AddressId
GROUP BY a.Name,
c.Week,
b.Street,
b.StreetNo;
I have modified your statement in three places. The first is I changed your join to Sales to a RIGHT JOIN. This will join as it would with an INNER JOIN, but it will also keep the records from the table on the right side of the JOIN that do not have a matching record or group of records on the left, placing NULL values in the resulting dataset's fields that would have come from the left of the JOIN. A LEFT JOIN works in the same way, but with any extra records in the table on the left being retained.
I have removed the word INNER from your surviving INNER JOIN. Where JOIN is not preceded by a join type, an INNER JOIN is performed. Both JOIN and INNER JOIN are considered correct, but the prevailing protocol seems to be to leave the INNER out, where the RDBMS allows it to be left out (which SQL-Server does). Which you go with is still entirely up to you - I have left it out here for illustrative purposes.
The third change is that I have added a CASE statement that tests to see if the Total field contains a NULL value, which it will if there were no sales for that Customer for that Week. If it does then SUM() would return a NULL, so the CASE statement returns a 0 instead. If Total does not contain a NULL value, then the SUM() of all values of Total for that grouping is performed.
Please note that I am assuming that Total will not have any NULL values other than from the RIGHT JOIN. Please advise me if this assumption is incorrect.
Please also note that I have assumed that either there will be no missing Weeks for a Customer in the Sales table or that you are not interested in listing them if there are. Again, please advise me if this assumption is incorrect.
If you have any questions or comments, then please feel free to post a Comment accordingly.

SQL Joins with Five tables

I have a SQL Database containing five tables.
tblExpense : Expense_ID, Expense_Catagory, Expense_Date, Expense_Particular, Expense_Amount
tblExpenseTransaction : Expense_Transaction_ID, Expense_ID, Transaction_ID
tblStudentMain : Student_Main_ID, Student_Name
tblStudentTransaction : Student_Transaction_ID, Student_Main_ID, Transaction_ID
tblTransaction : Transaction_ID, Transaction_Date, Transaction_Amount
Now tblTransaction contains transactions done by students or expenses and are connected by between tables( i.e. tblStudentTransaction and tblExpenseTransaction). The Relations are like tblStudent -> tblStudentTransactions -> tblTransactions and tblExpense -> tblExpenseTransaction -> tblTransaction.
Now My Task is to retrieve all the transactions made only by Students or Expenses.
I tried to do that using this query :
SELECT tblTransaction.Transaction_ID, tblTransaction.Transaction_Date, tblTransaction.Transaction_Amount, tblTransaction.Transaction_Particular, tblTransaction.Transaction_Mode,
tblTransaction.Is_Deposit
FROM tblTransaction INNER JOIN
tblStudentTransaction ON tblTransaction.Transaction_ID = tblStudentTransaction.Transaction_ID INNER JOIN
tblStudentMain ON tblStudentTransaction.Student_Main_ID = tblStudentMain.Student_Main_ID
which gives me result. But when i tried to do that with other set of tables with this :
SELECT tblTransaction.Transaction_ID, tblTransaction.Transaction_Date, tblTransaction.Transaction_Amount, tblTransaction.Transaction_Particular, tblTransaction.Transaction_Mode,
tblTransaction.Is_Deposit
FROM tblTransaction INNER JOIN
tblExpenseTransaction ON tblTransaction.Transaction_ID = tblExpenseTransaction.Transaction_ID RIGHT OUTER JOIN
tblExpense ON tblExpenseTransaction.Expense_ID = tblExpense.Expense_ID
query returned rows with transactions related to student also.
Now i know that, i need to use RIGHT, LEFT or either JOIN but i am confused. Please give me idea if anyone have.
Assuming you need a single query that indicates if the transaction is associated with an expense, a student or neither:
SELECT t.*, CASE
WHEN e.Transaction_ID IS NOT NULL THEN 'Expense'
WHEN s.Transaction_ID IS NOT NULL THEN 'Student'
ELSE 'Other'
END AS Type
FROM tblTransaction t
LEFT JOIN tblExpenseTransaction e ON e.Transaction_ID = t.Transaction_ID
LEFT JOIN tblStudentTransaction s ON s.Transaction_ID = t.Transaction_ID
WHERE Type = 'Expense'
Or you could specify Type = 'Student`` to get all student-related transactions, orType = 'Other'` for others, or leave off the WHERE clause off to get all transactions and their type.
As per my understanding individually you are getting the records for the student and expense using the below queries thus try adding union to the two and you should get the requisite data.
As expense and student are individual entities thus I dont think there would be any overlapping / common records between the two tables thus inner join should work fine for you.
SELECT tblTransaction.Transaction_ID, tblTransaction.Transaction_Date, tblTransaction.Transaction_Amount,
tblTransaction.Transaction_Particular, tblTransaction.Transaction_Mode,
tblTransaction.Is_Deposit
FROM tblTransaction INNER JOIN
tblStudentTransaction ON tblTransaction.Transaction_ID = tblStudentTransaction.Transaction_ID INNER JOIN
tblStudentMain ON tblStudentTransaction.Student_Main_ID = tblStudentMain.Student_Main_ID
union
SELECT tblTransaction.Transaction_ID, tblTransaction.Transaction_Date, tblTransaction.Transaction_Amount,
tblTransaction.Transaction_Particular, tblTransaction.Transaction_Mode,
tblTransaction.Is_Deposit
FROM tblTransaction INNER JOIN
tblExpenseTransaction ON tblTransaction.Transaction_ID = tblExpenseTransaction.Transaction_ID INNER JOIN
tblExpense ON tblExpenseTransaction.Expense_ID = tblExpense.Expense_ID