SQL selecting from multiple tables to show even null values [duplicate] - sql

This question already has answers here:
How to join 2 queries with different number of records and columns in oracle sql?
(2 answers)
Closed 5 years ago.
I have 3 tables which have foreign key ACCOUNT_NO, first table is CREDIT_LIST which holds all the credits taken from bank, second table is CUSTOMERS which holds customer info, and the last is ACCOUNTS which holds all the info about account itself. When I try to select
SELECT
B.CUSTOMER_NO AS CUSTOMER_NO,
B.CREDIT_TYPE AS CREDIT_TYPE,
B.ACCOUNT_NO AS CREDIT_ACCOUNT_NUMBER,
A.BRANCH_CODE AS BRANCH_CODE,
C.EXTERNAL_ACCOUNT_NO AS EXTERNAL_ACCOUNT_NUMBER
FROM
CREDIT_LIST B,
CUSTOMERS A,
ACCOUNTS C
WHERE
B.STATUS = 'A' -- ACTIVE
AND A.CUSTOMER_NO = B.CUSTOMER_NO
AND C.ACCOUNT_NO = B.ACCOUNT_NO
;
I get zero results because there is no EXTERNAL_ACCOUNT_NO in ACCOUNTS which has the c.account_no = b.account_no. The problem is that I want to show the info even if there is no EXTERNAL_ACCOUNT_NO and fill it with null for example:
| CUSTOMER_NO | CREDIT_TYPE | CREDIT_ACCOUNT_NUMBER | BRANCH_CODE | EXTERNAL_ACCOUNT_NUMBER
+-------------+-------------+-----------------------+-------------+------------------------
| 1 | some_type | 123456 | 01 |
| 2 | some_type | 654321 | 01 | 111111111111
I feel like this is extremely stupid but can't figure out what exactly I am doing wrong

I think, this is what you need
SELECT
B.CUSTOMER_NO AS CUSTOMER_NO,
B.CREDIT_TYPE AS CREDIT_TYPE,
B.ACCOUNT_NO AS CREDIT_ACCOUNT_NUMBER,
A.BRANCH_CODE AS BRANCH_CODE,
C.EXTERNAL_ACCOUNT_NO AS EXTERNAL_ACCOUNT_NUMBER
FROM
CREDIT_LIST B
JOIN CUSTOMERS A on A.CUSTOMER_NO = B.CUSTOMER_NO
LEFT JOIN ACCOUNTS C on C.ACCOUNT_NO = B.ACCOUNT_NO
WHERE
B.STATUS = 'A' -- ACTIVE
;
The left join will make sure, you get details from A and B, even when there is no data available in C.
Also the AS is redundant, not really required, you can just write B.ACCOUNT_NO CREDIT_ACCOUNT_NUMBER in line 4 of the query.
Left Outer Join - Reference
Not Suggested, however, if there is a reason, you need to use the old syntax, below is what you need
SELECT
B.CUSTOMER_NO AS CUSTOMER_NO,
B.CREDIT_TYPE AS CREDIT_TYPE,
B.ACCOUNT_NO AS CREDIT_ACCOUNT_NUMBER,
A.BRANCH_CODE AS BRANCH_CODE,
C.EXTERNAL_ACCOUNT_NO AS EXTERNAL_ACCOUNT_NUMBER
FROM
CREDIT_LIST B,
CUSTOMERS A,
ACCOUNTS C
WHERE
B.STATUS = 'A' -- ACTIVE
AND A.CUSTOMER_NO = B.CUSTOMER_NO
AND C.ACCOUNT_NO(+) = B.ACCOUNT_NO -- (+) will do a outer join for you
;

Related

SQL - FILTER condition in JOIN clause

I am inserting data to an already existing TableA and the following query is a part of a stored procedure. This part of the query inserts values into some columns of TableA. The stored procedure is very lengthy which has several insert statements to fill out the different columns in TableA.
INSERT INTO TableA (ID, Event, Date, Amount, Status_, Country)
(SELECT DISTINCT ID, Event, Date, Amount, c.Status, b.Country
FROM TableA1 a
JOIN TableB1 b ON b.employeeID = a.ID
JOIN TableC1 c ON c.Status = ‘Active’)
Usually, a join condition consists of two columns, for example (a.Status_ = c.Status). But here, it's replaced with a filter condition (JOIN TableC1 c ON c.Status = ‘Active’).
The select query alone executes well and returns results. I'm trying to understand the effect made by this filter condition.
Can you explain, please?
Thanks
It's the same as this...
SELECT DISTINCT
ID, Event, Date, Amount, c.Status, b.Country
FROM
(
TableA1 a
INNER JOIN
TableB1 b
ON b.employeeID = a.ID
)
CROSS JOIN
(
SELECT * FROM TableC1 WHERE Status = 'Active'
)
c
In effect, the INNER JOIN is resolved, and then each row from that is joined to every row from TableC1 WHERE Status = 'Active'
For example
TableA1
ID
Event
Date
Amount
1
e1
2022-01-01
11
2
e2
2022-02-02
22
TableB1
EmployeeID
Country
1
c1
2
c2
TableC1
some_id
Status
1
Sleeping
2
Active
3
Active
4
Sleeping
Would yield...
ID
Event
Date
Amount
Country
Status
(some_id, added by me)
1
e1
2022-01-01
11
c1
Active
2
1
e1
2022-01-01
11
c1
Active
3
2
e2
2022-02-02
22
c2
Active
2
2
e2
2022-02-02
22
c2
Active
3
You probably should have...
INNER JOIN
TableC1 c
ON c.some_id = b.some_other_id
AND c.Status = 'Active'

Write Sql script to fetch data in one to many relationship

I want to write a sql script which fetches data from 2 tables one is parent and second is child table
Bid
BidId | Code | Date
1 | H | 2022-05-12
2 | B | 2022-05-13
BidResult
BidResultId | BidId | Emi
12 | 1 | 50
13 | 1 | 20
14 | 2 | 30
Bid and BidResult
i want fetch column Date,Code,BidId from Bid Table
and BidResultId, EMI from BidResult but want a another column of Minimum EMI
from above example i want to fetch for bidId 1
Code H,date 2022-05-12,BidResultId 12, Emi 50 and Min Emi between 50 and 20 which is 20
so i have written following query
Select B.BidId,BR.EMI As Installment,
(Select Min(BR.EMI ) from BidResult BR
Inner Join Bid B on B.BidId = BR.BidResultId
where B.BidId = 5) As MinInstallment,
B.Code,
BR.BidResultId,
CONVERT(DATE, B.Date) As BidDate
from Bid B
Inner Join BidResult BR On B.BidId = BR.BidId
where B.BidId= 5 and B.TypeId = 1
All Field are starightforward except the minimum EMI value from BidResult for that BidId
please check the query and suggest if any modifications
Solution 1: With group by
select BID, min(EMI) EMI
into #temp
from BIDResult
group by BID
select b.Date, b.Code, b.BidId, br.BidResultId, br.EMI
from BID b
inner join #temp on t.BID = b.BID
inner join BIDResult br on br.BID = t.BID and br.EMI = t.EMI
Solution 2: Without group by
select *, ROW_Number() over(PARTITION by BID order by EMI) RowNumber
into #temp
from BIDResult
select b.Date, b.Code, b.BidId, br.BidResultId, br.EMI
from BID b
inner join #temp t on t.BID = b.BID and t.RowNumber = 1

Joining on multiple tables causing incorrect results

I am trying to extract some data grouped by the markets we operate in. The table structure looks like this:
bks:
opportunity_id
bks_opps:
opportunity_id | trip_start | state
bts:
boat_id | package_id
pckgs:
package_id | boat_id
addresses:
addressable_id | district_id
districts:
district_id
What I wanted to do is to count the number of won, lost and total and percentage won for each district.
SELECT d.name AS "District",
SUM(CASE WHEN bo.state IN ('won') THEN 1 ELSE 0 END) AS "Won",
SUM(CASE WHEN bo.state IN ('lost') THEN 1 ELSE 0 END) AS "Lost",
Count(bo.state) AS "Total",
Round(100 * SUM(CASE WHEN bo.state IN ('won') THEN 1 ELSE 0 END) / Count(bo.state)) AS "% Won"
FROM bks b
INNER JOIN bks_opps bo ON bo.id = b.opportunity_id
INNER JOIN pckgs p ON p.id = b.package_id
INNER JOIN bts bt ON bt.id = p.boat_id
INNER JOIN addresses a ON a.addressable_type = 'Boat' AND a.addressable_id = bt.id
INNER JOIN districts d ON d.id = a.district_id
WHERE bo.trip_start BETWEEN '2016-05-12' AND '2016-06-12'
GROUP BY d.name;
This returns incorrect data (The values are way higher than expected). However, when I get rid of all the joins and stop grouping by district - the numbers are correct (Counting the toal # of opportunities). Anybody that can spot what I am doing wrong? The most related question on here is this one.
Example data:
District | won | lost | total
----+---------+---------+------
1 | 42 | 212 | 254
Expected data:
District | won | lost | total |
----+---------+---------+--
1 | 22 | 155 | 177
Formatted comment here:
I would venture a guess that one of your join conditions is at fault here, but with the provided structure it is impossible to say.
For instance, you have this join INNER JOIN pckgs p ON p.id = b.package_id, but package_id is not listed as a column in bks.
And these joins look especially suspect:
INNER JOIN pckgs p ON p.id = b.package_id
INNER JOIN bts bt ON bt.id = p.boat_id
If a boat can exist in multiple packages, it will be an issue.
To troubleshoot, start with the simplest query you can:
SELECT b.opportunity_id
FROM bks b
Then leave the select alone, and proceed to add in each join:
SELECT b.opportunity_id
FROM bks b
INNER JOIN pckgs p ON p.id = b.package_id
At some point you'll likely see a jump in the number of rows returned. Whichever JOIN you added last is your issue.

Join one row only over three tables

(My question has been asked a lot of times with two tables involved, and has been answered here, here and here. But I can't figure out how to do the same with three tables involved.)
I have three tables, A, B and C, where A has many B and B has many C. I want to join these tables and select zero or one rows per A, which one should be based on a condition in C.
Example, assume that:
SELECT
a.aId
,b.bId
,c.cId
FROM
a
INNER JOIN b ON b.aId=a.aId
INNER JOIN c ON c.bId=b.bId
WHERE
c.someColumn='foo'
...yields the following result:
aId bId cId
=== === ===
1 11 101
1 12 102
1 12 103
2 21 201
2 21 203
2 22 202
...then I would like to, for instance, retrieve two distinct A-rows, the ones with highest cId.
aId bId cId
=== === ===
1 12 103
2 21 203
You can use ROW_NUMBER:
WITH Cte AS (
SELECT
a.aId,
b.bId,
c.cId,
rn = ROW_NUMBER() OVER (PARTITION BY a.aId ORDER BY c.cId DESC)
FROM a
INNER JOIN b
ON b.aId = a.aId
INNER JOIN c
ON c.bId = b.bId
WHERE c.someColumn = 'foo'
)
SELECT
aId, bId, cId
FROM Cte
WHERE rn = 1

SQL Server: Subquery on a join

I have two tables with schema and data as below. Table A has an id and an associated name. Table B associates the id from Table A with a price and otherAttr. For each entry in Table A, there may be multiple entries with different prices in Table B, but otherAttr is the same for each entry.
Given an id for Table A, I would like to select the name, otherAttr, and the minimum price.
The below query returns multiple results, I need to write a query that will return a single result with the minimum price.
SELECT a.name, b.price, b.otherAttr
FROM A a
LEFT Join B b on b.idFromA = 1
WHERE a.id = 1
Table A Table B
id | name idFromA | price | otherAttr
-------- ---------------------------
1 | A 1 | 200 | abc
2 | B 1 | 300 | abc
1 | 400 | abc
2 | 20 | def
2 | 30 | def
2 | 40 | ef
I massively oversimplified my example. In addition to selecting the min price and otherAttr from Table B, I also have to select a bunch of other attributes from joins on other tables. Which is why I was thinking the Group By and Min should be a subquery of the join on Table B, as a way to avoid Grouping By all the attributes I am selecting (because the attributes being selected for vary programmatically).
The Actual query looks more like:
SELECT a.name, b.price, b.otherAttr, c.x, c.y, d.e, d.f, g.h....
FROM A a
LEFT Join B b on b.idFromA = 1
LEFT Join C c on something...
LEFT Join D d on something...
LEFT Join G g on something...
WHERE a.id = 1
To get this, you could use GROUP BY in an INNER query:
SELECT gd.name, gd.price,gd.otherAttr, c.x, c.y, d.e, d.f, g.h....
FROM
(SELECT a.id,a.name, MIN(b.price) as price, b.otherAttr
FROM A a
LEFT Join B b on b.idFromA = 1
WHERE a.id = 1
GROUP BY a.id,a.name,b.otherAttr) gd
LEFT Join B b on b.idFromA = 1
LEFT Join C c on something...
LEFT Join D d on something...
LEFT Join G g on something...
Try:-
SELECT a.name, MIN(b.price) minprice, b.otherAttr
FROM A a
LEFT Join B b ON a.Id = b.idFromA
GROUP BY a.name, b.otherAttr
HAVING a.id = 1
You could just do this instead:
SELECT a.name, MIN(b.price), MIN(b.otherAttr)
FROM TableA a
LEFT JOIN TableB b on b.idFromA = a.id
GROUP BY a.name
HAVING a.id = 1;
You need to inner join on price as well in addition to id on the subquery to intersect the right record(s) with the lowest price(s). Then TOP(1) will return only one of those records. You can avoid using TOP(1) if you can expand the conditions and group by fields in the subquery so you schema can assure only a single record is returned for that combination of attributes. Lastly, avoid left joins when intersecting sets.
SELECT TOP(1) p.id, p.price, b.OtherAttr
FROM B as b
INNER JOIN
(SELECT A.id, min(B.price) as price
FROM B
INNER JOIN A on A.id=B.idFromA and A.ID=1
GROUP BY A.id) as p on b.idFromA=p.id and b.price=p.price