Filtering query with join statement - sql

I am using ColdFusion 8 to develop my company's website and would like to return a list of records (just the clientname field) from a table (dbo.clients) that has no match in a different table (dbo.fees) for the purpose of prompting the end-user to add a fee schedule for those companies. An example:
dbo.clients
CLIENT_ID CLIENT_NAME
1 Joe's Diner
2 Save-a-Lot
3 Family Meds
4 DiFazio's
dbo.fees
CID CLIENT_NAME FEE
1 Joe's Diner 25.000
2 Save-a-Lot 35.000
4 DiFazio's 30.000
What I desire is a resultset that, in the case of the above tables/data, would return only clientid/clientname 3/Family Meds because they do not have a fee listed/record in the table dbo.fees. My DB is MSSQL 2005. My query is:
SELECT clientid
FROM clients
INNER JOIN fees
ON clients.clientid <> fees.cid;
Which returns a Cartesian product of 50,000+ results. Using LEFT/RIGHT OUTER JOIN still gives me a Cartesian product and DISTINCT simply returns every record from dbo.clients regardless of whether or not they have a dbo.fees entry or not. What am I doing wrong?
p.s. Also of note: The admin before me apparently did not set up a PK/FK relationship between the clients/fees tables and so any query syntax that might be reliant on that may not work in this situation. It would probably have to work based solely on the values of the relevant fields.

You can use a LEFT JOIN with a WHERE clause that will return only those records that do not appear in the fees table:
select c.CLIENT_ID, c.CLIENT_NAME
from clients c
left join fees f
on c.CLIENT_ID = f.CLIENT_ID
where f.CLIENT_ID is null
If you need help learning JOIN syntax, here is a great reference:
A Visual Explanation of SQL Joins
This can also be written using a NOT EXISTS:
select *
from clients c
where not exists (select CLIENT_ID
from fees f
where c.CLIENT_ID = f.CLIENT_ID)
See SQL Fiddle Demo with both queries

Simplest, you could just use a NOT IN;
SELECT clientid FROM clients WHERE clientid NOT IN
(SELECT clientid FROM fees)
...or you can use a LEFT JOIN to do the same thing a bit more verbosely; f.clientid will be NULL if a fee does not exist for the client.
SELECT c.clientid
FROM clients c
LEFT JOIN fees f
ON c.clientid = f.clientid
WHERE f.clientid IS NULL

Related

Finding multiple results in SQL Server 2012 (not duplicates)

I need some help. I've got a list of customers and services that they've used, but I need to narrow that list to customers that have used more than one service (excluding those who've only used one service). They have sometimes used the same service more than once, but I need a list of unique services.
The below brings back the main list of customers.
SELECT
DISTINCT M.CustID
,S.ServiceID
,R.ReceivedDate
,S.ServiceRequestID
FROM Customers AS M
LEFT OUTER JOIN CustomerDates AS R ON M.CustID = R.CustID
LEFT OUTER JOIN Service1 AS S ON R.ServiceRequestID = S.ServiceRequestID
WHERE S.CloseDate IS NULL
What I need is a list that excludes the first three lines as they have only used one service, whereas the next seven I need as they've used more than one service.
It is quite likely that you can just use NOT EXISTS. This is likely to do what you want:
SELECT M.CustID, S.ServiceID, R.ReceivedDate, S.ServiceRequestID
FROM Customers M JOIN
CustomerDates R
ON M.CustID = R.CustID JOIN
Service1 S
ON R.ServiceRequestID = S.ServiceRequestID
WHERE S.CloseDate IS NULL AND
EXISTS (SELECT 1
FROM CustomerDates cd2
WHERE cd2.CustId = m.custId AND cd2.ServiceRequestID <> r.ServiceRequestID
);
I'm not 100% sure this is equivalent to your query. The SELECT DISTINCT should not be needed, unless you have duplicates in your tables. Also, you seem to require matches between the tables, so LEFT JOIN is not appropriate. It it is not clear if the WHERE condition is relevant for finding duplicates.
Try the following, it should work
SELECT
CustID
,ServiceID
,ReceivedDate
,ServiceRequestID
FROM
(SELECT
DISTINCT M.CustID
,S.ServiceID
,R.ReceivedDate
,S.ServiceRequestID
,count(*) over (partition by M.CustID) as total
FROM Customers AS M
LEFT OUTER JOIN CustomerDates AS R ON M.CustID = R.CustID
LEFT OUTER JOIN Service1 AS S ON R.ServiceRequestID = S.ServiceRequestID
WHERE S.CloseDate IS NULL
) vals
where total > 1

How to put conditions on left joins

I have two tables, CustomerCost and Products that look like the following:
I am joining the two tables using the following SQL query:
SELECT custCost.ProductId,
custCost.CustomerCost
FROM CUSTOMERCOST Cost
LEFT JOIN PRODUCTS prod ON Cost.productId =prod.productId
WHERE prod.productId=4
AND (Cost.Customer_Id =2717
OR Cost.Customer_Id IS NULL)
The result of the join is:
joins result
What i want to do is when I pass customerId 2717 it should return only specific customer cost i.e. 258.93, and when customerId does not match then only it should take cost as 312.50
What am I doing wrong here?
You can get your expected output as follows:
SELECT Cost.ProductId,
Cost.CustomerCost
FROM CUSTOMERCOST Cost
INNER JOIN PRODUCTS prod ON Cost.productId = prod.productId
WHERE prod.productId=4
AND Cost.Customer_Id = 2717
However, if you want to allow customer ID to be passed as NULL, you will have to change the last line to AND Cost.Customer_Id IS NULL. To do so dynamically, you'll need to use variables and generate the query based on the input.
The problem in the original query that you have posted is that you have used an alias called custCost which is not present in the query.
EDIT: Actually, you don't even need a join. The CUSTOMERCOST table seems to have both Customer and Product IDs.
You can simply:
SELECT
Cost.ProductId, Cost.CustomerCost
FROM
CUSTOMERCOST Cost
WHERE
Cost.Customer_Id = 2717
AND Cost.productId = 4
You seem to want:
SELECT c.*
FROM CUSTOMERCOST c
WHERE c.productId = 4 AND c.Customer_Id = 2717
UNION ALL
SELECT c.*
FROM CUSTOMERCOST c
WHERE c.productId = 4 AND c.Customer_Id IS NULL AND
NOT EXISTS (SELECT 1 FROM CUSTOMERCOST c2 WHERE c2.productId = 4 AND c2.Customer_Id = 2717);
That is, take the matching cost, if it exists for the customer. Otherwise, take the default cost.
SELECT custCost.ProductId,
custCost.CustomerCost
FROM CUSTOMERCOST Cost
LEFT JOIN PRODUCTS prod
ON Cost.productId =prod.productId
AND (Cost.Customer_Id =2717 OR Cost.Customer_Id IS NULL)
WHERE prod.productId=4
WHERE applies to the joined row. ON controls the join condition.
Outer joins are why FROM and ON were added to SQL-92. The old SQL-89
syntax had no support for them, and different vendors added different,
incompatible syntax to support them.

Join multiple tables in a ternary

I have a 3 tables (customers, customerID is PK)(services, serviceID is PK)(equipment, equID is PK). The second two tables have a cost associated with them. All 3 tables connect individually to a table (Billing, transID is the PK). Another process puts transactions in for each customer which charges them for their services individually. I would like to generate a query that pulls all transactions and costs for related equipment and services. When I tried to join cus->billing->service->billing->equip it causes some weirdness. Does anyone know a better way to do this?
Select * from Customer as c
inner join Billing as b on c.customerID=b.customerID
inner join Service as s on b.ServID=s.ServID
inner join Billing on s.ServID=b.ServID
inner join Equipment as e on b.EquID=e.EquID AND b.Serial=e.Serial
where c.customerID=1
Although you have joined Billing table twice , you haven't used the second one. I think you can remove following from the query.
inner join Billing on s.ServID=b.ServID --(4th line of the query)
I got it working with some help from friends and the comments. Final code is below.
Select * from Customer as c
inner join Billing as b on c.customerID=b.customerID
left join Service as s on b.ServID=s.ServID
left join Equipment as e on b.EquID=e.EquID AND b.Serial=e.Serial
where c.customerID=1

Excluding multiple results in specific column (SQL JOIN)

I'm taking my first steps in terms of practical SQL use in real life.
I have a few tables with contractual and financial information and the query works exactly as I need - to a certain point. It looks more or less like that:
SELECT /some columns/ from CONTRACTS
Linked 3 extra tables with INNER JOIN to add things like department names, product information etc. This all works but they all have simplish one-to-one relationship (one contract related to single department in Department table, one product information entry in the corresponding table etc).
Now this is my challenge:
I also need to add contract invoicing information doing something like:
inner join INVOICES on CONTRACTS.contnoC = INVOICES.contnoI
(and selecting also the Invoice number linked to the Contract number, although that's partly optional)
The problem I'm facing is that unlike with other tables where there's always one-to-one relationship when joining tables, INVOICES table can have multiple (or none at all) entries that correspond to a single contract no. The result is that I will get multiple query results for a single contract no (with different invoice numbers presented), needlessly crowding the query results.
Essentially I'm looking to add INVOICES table to a query to just identify if the contract no is present in the INVOICES table (contract has been invoiced or not). Invoice number itself could be presented (it is with INNER JOIN), however it's not critical as long it's somehow marked. Invoice number fields remains blank in the result with the INNER JOIN function, which is also necessary (i.e. to have the row presented even if the match is not found in INVOICES table).
SELECT DISTINCT would look to do what I need, but I seemed to face the problem that I need to levy DISTINCT criteria only for column representing contract numbers, NOT any other column (there can be same values presented, but all those should be presented).
Unfortunately I'm not totally aware of what database system I am using.
Seems like the question is still getting some attention and in an effort to provide some explanation here are a few techniques.
If you just want any contract with details from the 1 to 1 tables you can do it similarily to what you have described. the key being NOT to include any column from Invoices table in the column list.
SELECT
DISTINCT Contract, Department, ProductId .....(nothing from Invoices Table!!!)
FROM
Contracts c
INNER JOIN Departments D
ON c.departmentId = d.Department
INNER JOIN Product p
ON c.ProductId = p.ProductId
INNER JOIN Invoices i
ON c.contnoC = i.contnoI
Perhaps a Little cleaner would be to use IN or EXISTS like so:
SELECT
Contract, Department, ProductId .....(nothing from Invoices Table!!!)
FROM
Contracts c
INNER JOIN Departments D
ON c.departmentId = d.Department
INNER JOIN Product p
ON c.ProductId = p.ProductId
WHERE
EXISTS (SELECT 1 FROM Invoices i WHERE i.contnoI = c.contnoC )
SELECT
Contract, Department, ProductId .....(nothing from Invoices Table!!!)
FROM
Contracts c
INNER JOIN Departments D
ON c.departmentId = d.Department
INNER JOIN Product p
ON c.ProductId = p.ProductId
WHERE
contnoC IN (SELECT contnoI FROM Invoices)
Don't use IN if the SELECT ... list can return a NULL!!!
If you Actually want all of the contracts and just know if a contract has been invoiced you can use aggregation and a case expression:
SELECT
Contract, Department, ProductId, CASE WHEN COUNT(i.contnoI) = 0 THEN 0 ELSE 1 END as Invoiced
FROM
Contracts c
INNER JOIN Departments D
ON c.departmentId = d.Department
INNER JOIN Product p
ON c.ProductId = p.ProductId
LEFT JOIN Invoices i
ON c.contnoC = i.contnoI
GROUP BY
Contract, Department, ProductId
Then if you actually want to return details about a particular invoice you can use a technique similar to that of cybercentic87 if your RDBMS supports or you could use a calculated column with TOP or LIMIT depending on your system.
SELECT
Contract, Department, ProductId, (SELECT TOP 1 InvoiceNo FROM invoices i WHERE c.contnoC = i.contnoI ORDER BY CreateDate DESC) as LastestInvoiceNo
FROM
Contracts c
INNER JOIN Departments D
ON c.departmentId = d.Department
INNER JOIN Product p
ON c.ProductId = p.ProductId
GROUP BY
Contract, Department, ProductId
I would do it this way:
with mainquery as(
<<here goes you main query>>
),
invoices_rn as(
select *,
ROW_NUMBER() OVER (PARTITION BY contnoI order by
<<some column to decide which invoice you want to take eg. date>>) as rn
)
invoices as (
select * from invoices_rn where rn = 1
)
select * from mainquery
left join invoices i on contnoC = i.contnoI
This gives you an ability to get all of the invoice details to your query, also it gives you full control of which invoice you want see in your main query. Please read more about CTEs; they are pretty handy and much easier to understand / read than nested selects.
I still don't know what database you are using. If ROW_NUMBER is not available, I will figure out something else :)
Also with a left join you should use COALESCE function for example:
COALESCE(i.invoice_number,'0')
Of course this gives you some more possibilities, you could for example in your main select do:
CASE WHEN i.invoicenumber is null then 'NOT INVOICED'
else 'INVOICED'
END as isInvoiced
You can use
SELECT ..., invoiced = 'YES' ... where exists ...
union
SELECT ..., invoiced = 'NO' ... where not exists ...
or you can use a column like "invoiced" with a subquery into invoices to set it's value depending on whether you get a hit or not

Hybrid join in DB2 z/os sql

I have been rewriting decades old DB z/OS queries and there are three tables as below:
customer
+-----------+----------+---------+
|customer_id|state_code|city_code|
+-----------+----------+---------+
customer_address
+-----------+-------------+
|customer_id|facility_name|
+-----------+-------------+
loan
+-----------+----------+---------+
|loan_code |state_code|city_code|
+-----------+----------+---------+
customer = customer_address is one-to-one and customer = loan is one-to-many.
We used to have two different queries to get list of customers by statewise and citywise, who have loans and are "active in business" (by having a record in customer_address!) as below:
SELECT CUST.STATE_CODE, CUST.CITY_CODE, CUST_ADRS.FAC_NAME
FROM CUSTOMER CUST, CUST_ADDRESS WHERE CUST_ADRS.ADR_ID <> 0 AND
CUST_ADRS.CUST_ID = CUST.CUST_ID
The result of the above query is collected and each state and city is passed to below query from PreparedStatement. If there is a loan_id exists, then we collect the state, city and facility name.
SELECT CL.LOAN_ID FROM CUSTOMER_LOAN CL WHERE
CL.LOAN_CODE IN ('A1', 'BC') AND CL.STATE_CODE = ? AND CL.CITY_CODE = ?
I have rewritten these two queries into a single query. (There are indices in place for customer.cust_id, customer_loan.loan_id). I did not include loan_id in the modified query.
SELECT DISTINCT CUST.STATE_CODE, CUST.CITY_CODE, CUST_ADRS.FAC_NAME
FROM CUSTOMER CUST INNER JOIN CUST_ADDRESS CUST_ADRS ON
CUST_ADRS.ADR_ID <> 0 AND CUST.CUST_ID = CUST_ADRS.CUST_ID
INNER JOIN CUSTOMER_LOAN CL
ON
CL.LOAN_CODE IN ('A1', 'BC') and
CL.STATE_CODE = CUST.STATE_CODE and
CL.CITY_CODE = CUST.CITY_CODE
Now I could see performance has significantly improved in the web application and query execution time takes some 700 ms. But I wonder if there is anything I can do to improve or modify this query. Any inputs or thoughts are greatly appreciated.
One option which might be faster would be to use EXISTS:
select c.state_code, c.city_code, ca.fac_name
from customer c
join customer_address ca on c.customer_id = ca.customer_id
where exists (
select 1
from loan l
where l.state_code = c.state_code and
l.city_code = c.city_code and
l.loan_code in ('A1','BC')
)
As always though, you need to test each query for yourself to see which performs the best.