Hybrid join in DB2 z/os sql - 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.

Related

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.

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

SQL Query to find MAX Date

I have some software that uses dBase4 for its database. I am attempting to construct a report using fields from 3 tables (Customer, Service & History).
In all of the tables the ACCOUNT field is the same. The 'Customer' and the 'Service' table only have one one record for each Customer. The 'History' table has multiple records for each Customer.
I need to write a query so that only the record with the MAX date in 'History.BILLTHRU' is returned for each Customer. The code below returns all of the records for each Customer in the History table:
SELECT Customer.ACCOUNT,
Customer.FIRSTNAME,
(more fields...),
History.ACCOUNT,
History.BILLTHRU,
Service.ACCOUNT,
Service.OFFERCODE
FROM "C:\Customer.dbf" Customer
INNER JOIN "C:\History.dbf" History
ON (Customer.ACCOUNT = History.ACCOUNT)
INNER JOIN "C:\Service.dbf" Service
ON (Customer.ACCOUNT = Service.ACCOUNT)
WHERE Customer.STATUS = "A"
ORDER BY Customer.LAST_BUS_NAME
Use a sub-query and a group by:
SELECT Customer.ACCOUNT,
Customer.FIRSTNAME,
(more fields...),
History.ACCOUNT,
History.BILLTHRU,
Service.ACCOUNT,
Service.OFFERCODE
FROM "C:\Customer.dbf" Customer
INNER JOIN (SELECT ACCOUNT, MAX(BILLTHRU) AS BILLTHRU
FROM "C:\History.dbf"
GROUP BY ACCOUNT) History
ON (Customer.ACCOUNT = History.ACCOUNT)
INNER JOIN "C:\Service.dbf" Service
ON (Customer.ACCOUNT = Service.ACCOUNT)
WHERE Customer.STATUS = "A"
ORDER BY Customer.LAST_BUS_NAME
I like to use common table expressions (CTEs). Subqueries are good, but breaking it out like this sometimes makes it easier to keep separate.
with GetMaxDate as (
select account, max(billthru) as MaxBillThru
from "C:\History.dbf"
group by account
)
SELECT Customer.ACCOUNT,
Customer.FIRSTNAME,
(more fields...),
GetMaxDate.ACCOUNT,
GetMaxDate.MaxBillThru,
Service.ACCOUNT,
Service.OFFERCODE
.....
from FROM "C:\Customer.dbf" Customer
INNER JOIN GetMaxDate on customer.ACCOUNT = GetMaxDate.Account
INNER JOIN "C:\Service.dbf" Service
ON (Customer.ACCOUNT = Service.ACCOUNT)
WHERE Customer.STATUS = "A"
ORDER BY Customer.LAST_BUS_NAME
EDIT: This is a SQL Server function. I'm leaving it in case it can help you or someone else. I'll delete it if it just clouds the issue.

T-SQL Query Join on different column depending on case

I have a legacy system that has a sales table and a customer table, CMS and CUST respectively. I need to query for the shipped to address based on different criteria. The customer table treats each address as its own customer. So if I have a billing address, then a shipping address, those will both be different CUSTNUM's. The CMS table has columns CUSTNUM and SHIPNUM. If the sales order uses the billing address as the shipping address, SHIPNUM = 0. If those 2 address are different, SHIPNUM = a different customer number than CUSTNUM. I'm trying to write a query that joins CUST to CMS based on the case of SHIPNUM being > 0 or not. My original query just used CUSTNUM, and ignored the SHIPNUM. My new query is syntactically correct and executes, but the row count returned is 2860 vs 3590 for the old query. The old join statement is just the commented out line :ON CMS.CUSTNUM = CUST.CUSTNUM.
from
KGI_LOTNOS as LOT
INNER JOIN CMS
ON LOT.ORDERNO = CMS.ORDERNO
JOIN CUST
ON CUST.CUSTNUM =
CASE
WHEN CMS.SHIPNUM > 0
THEN CMS.SHIPNUM
Else CMS.CUSTNUM
END
-- ON CMS.CUSTNUM = CUST.CUSTNUM
INNER JOIN COUNTRY as C
ON CUST.COUNTRY = C.COUNTRY
Here is an example from the CMS table;
CUSTNUM SHIPNUM ORDERNO
41863 77394 828509 <--Different billing and shipping address
43242 69291 776888 <--Different billing and shipping address
2356 0 765022 <--Same billing and shipping address
Any thoughts on how to make this work?
PS Here is the original query in its entirety.
select
CUST.CUSTNUM as Customer,
CMS.CUSTNUM,
CMS.SHIPNUM,
CUST.CTYPE,
CMS.ORDERNO,
CMS.ODR_DATE,
LTRIM(RTRIM(CUST.FIRSTNAME)) as First,
LTRIM(RTRIM(CUST.LASTNAME)) as Last,
LTRIM(RTRIM(CUST.COMPANY)) as Company,
LTRIM(RTRIM(CUST.PHONE)) as Phone,
LTRIM(RTRIM(CUST.EMAIL)) as Email,
LTRIM(RTRIM(CUST.ADDR)) as ADDR1,
LTRIM(RTRIM(CUST.ADDR2)) as ADDR2,
LTRIM(RTRIM(CUST.ADDR3)) as ADDR3,
LTRIM(RTRIM(CUST.CITY)) as City,
LTRIM(RTRIM(CUST.State)) as State,
LTRIM(RTRIM(CUST.ZIPCODE)) as Zip,
LTRIM(RTRIM(C.NAME)) as Country,
LOT.ITEMNO,
LOT.LOTNO,
COUNT(LOT.ITEMNO) as Quantity
from
KGI_LOTNOS as LOT
INNER JOIN CMS
ON LOT.ORDERNO = CMS.ORDERNO
LEFT JOIN CUST
ON CMS.CUSTNUM = CUST.CUSTNUM
INNER JOIN COUNTRY as C
ON CUST.COUNTRY = C.COUNTRY
where
(
CUST.CTYPE IN ('P','W','Z')
)
AND
(
LOT.LOTNO IN ('1000001','20001','300001')
)
GROUP BY
CMS.ORDERNO,
CUST.CUSTNUM,
CMS.CUSTNUM,
CMS.SHIPNUM,
CUST.CTYPE,
CUST.FIRSTNAME,
CMS.ODR_DATE,
CUST.LASTNAME,
CUST.COMPANY,
CUST.PHONE,
CUST.EMAIL,
CUST.ADDR,
CUST.ADDR2,
CUST.ADDR3,
LOT.ITEMNO,
CUST.CITY,
CUST.STATE,
CUST.ZIPCODE,
C.NAME,
LOT.LOTNO
ORDER BY
Customer,
CMS.ORDERNO,
LOT.ITEMNO,
LOT.LOTNO
If you use INNER JOIN you have risk to exclude raws which have no reference in another table. This could be caused by any of 2 another joins in your expression - comment them and try again. If you still receive less records you should check consistency of your data - one table has values which not correspond to values in another table.
BTW, I don't like CASE in JOIN expression simply because it looks ugly. What do you thinK about this expression which seemed to do the job too:
LEFT JOIN CUST
ON CUST.CUSTNUM = COALESCE(NULLIF(CMS.SHIPNUM, 0), CMS.CUSTNUM)
You could use a CTE like this.
WITH cte (ORDERNO, SHIPNUM) AS
(
SELECT ORDERNUM, SHIPNUM = CASE
WHEN CMS.SHIPNUM > 0
FROM CMS
Fewer records join using your altered criteria, there are some CMS.SHIPNUM values that don't have matching CUSTNUM in the CUST table.
To find the problematic entries change from INNER to OUTER join and add WHERE criteria, something like:
LEFT JOIN CUST
ON CUST.CUSTNUM = CASE WHEN CMS.SHIPNUM > 0 THEN CMS.SHIPNUM
ELSE CMS.CUSTNUM
END
WHERE CUST.CUSTNUM IS NULL
AND CMS.SHIPNUM > 0
Edit: You'll have to remove the INNER JOIN to COUNTRY to see the unmatched from your updated JOIN since it joins on a field from the customer table, and make sure to have the SHIPNUM field in your SELECT.
your query looks correct but not sure why it is not working, try left joinCUST table twiceone on shipping and the other on billing and then write the case statement for each customer column.
select
LTRIM(RTRIM(case when CMS.SHIPNUM > 0 THEN CUST.FIRSTNAME else CUST_BILL.FIRSTNAME end )) as First,
from
KGI_LOTNOS as LOT
INNER JOIN CMS
ON LOT.ORDERNO = CMS.ORDERNO
left JOIN CUST CUST
ON CUST.CUSTNUM = CMS.SHIPNUM
left JOIN CUST CUST_BILL
ON CMS.CUSTNUM = CUST_BILL.CUSTNUM
INNER JOIN COUNTRY as C
ON CUST.COUNTRY = C.COUNTRY
if it still outputs less rows then something else is wrong

Filtering query with join statement

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