sql: join two tables and display records even if they are in first table and not in the second table - sql

I don't know if the title justifies the question but
i have 2 tables table A a and table B b. I am inner joining these two tables with
on (a.msisdn = b.frmsisdn or a.msisdn = b.tomsisdn)
in table A i have all the details of the users like names, msisdn, regdate, status, address etc
in table B i have user's transaction reports like timestamp, frmsisdn, tomsisdn, reference, amounts etc
Both tables have MSISDN as common but in Table A it is named as MSISDN and in Table B this can either be in FRMSISDN or TOMSISDN or will not be in any of the fields(usually users with no transactions).
But when i try to do like this, the report i am getting is not displaying the users who are in Table A with no entries in Table B, basically registered in the database but did not do any transactions as all.
Can someone tell me how do i include the MSISDN from Table A in the report even if they dont have an entry in Table B
Here is the query i am trying
select mai.msisdn, mai.firstname, mai.lastname, type as account_type,
alias as nickname, mai.regdate, mai.status,
amount as balance, count(trai.referenceid) as number_of_transactions,
sum(trai.amount) as sum_of_transactions from tableA mai
inner join tableC stk
on mai.msisdn = stk.msisdn
inner join tableB trai
on (mai.msisdn = trai.tomsisdn or mai.msisdn = trai.frmsisdn)
where trai.status = 0
and stk.walletid = 0
group by mai.msisdn, mai.firstname, mai.lastname, mai.type,
mai.alias, mai.regdate, mai.status,
stk.amount;

If you need all the entry of table A also when there si no enty in table B the use LEFT join for table B
select
mai.msisdn
, mai.firstname
, mai.lastname
, type as account_type
, alias as nickname
, mai.regdate
, mai.status
, amount as balance
, count(trai.referenceid) as number_of_transactions
, sum(trai.amount) as sum_of_transactions
from tableA mai
left join tableB trai on (mai.msisdn = trai.tomsisdn or mai.msisdn = trai.frmsisdn)
inner join tableC stk on mai.msisdn = stk.msisdn
where trai.status = 0
and stk.walletid = 0
group by
mai.msisdn
, mai.firstname
, mai.lastname
, mai.type
, mai.alias
, mai.regdate
, mai.status
, stk.amount;
or if you problem is limited but the inner join for table C
try using inner join for this table too
select
mai.msisdn
, mai.firstname
, mai.lastname
, type as account_type
, alias as nickname
, mai.regdate
, mai.status
, amount as balance
, count(trai.referenceid) as number_of_transactions
, sum(trai.amount) as sum_of_transactions
from tableA mai
left join tableB trai on (mai.msisdn = trai.tomsisdn or mai.msisdn = trai.frmsisdn)
left join tableC stk on mai.msisdn = stk.msisdn
where trai.status = 0
and stk.walletid = 0
group by
mai.msisdn
, mai.firstname
, mai.lastname
, mai.type
, mai.alias
, mai.regdate
, mai.status
, stk.amount;

Try
select mai.msisdn, mai.firstname, mai.lastname, type as account_type,
alias as nickname, mai.regdate, mai.status,
amount as balance, count(trai.referenceid) as number_of_transactions,
sum(trai.amount) as sum_of_transactions
from tableA mai
inner join tableC stk
on mai.msisdn = stk.msisdn
and stk.walletid = 0
left join tableB trai
on (mai.msisdn = trai.tomsisdn or mai.msisdn = trai.frmsisdn)
and trai.status = 0
group by mai.msisdn, mai.firstname, mai.lastname, mai.type,
mai.alias, mai.regdate, mai.status,
stk.amount;
See the difference how (at what stage) predicate is applied in LEFT JOIN .. ON and in WHERE.

Related

Select an ID where there is only one row and that row is a specific value

I have this query. There's a lot of joins because I am checking if an ID is linked to any of those tables.
Currently, this query shows me any ID's that are not linked to any of those tables. I would like to add to it so that it also shows any IDs that are linked to the d table, but only if there is only 1 row in the D table and the type in the D field is 'member'.
SELECT
c.ID,
c.location,
c.pb,
c.name,
c.surname
FROM c
LEFT JOIN l on c.rowno = l.rowno
LEFT JOIN d on c.rowno = d.rowno
LEFT JOIN t on c.rowno = t.rowno
LEFT JOIN cj ON (c.rowno = cj.rowno OR c.rowno = cj.rowno2)
LEFT JOIN dj ON c.rowno = d.rowno
LEFT JOIN lg ON c.rowno = lg.rowno
LEFT JOIN tj ON c.rowno = tj.rowno
WHERE
c.status != 'closed'
AND l.rowno IS NULL
AND d.rowno IS NULL
AND t.rowno IS NULL
AND cj.rowno IS NULL
AND dj.rowno IS NULL
AND lg.rowno IS NULL
AND tj.rowno IS NULL
My first thought is to just add
WHERE D.type = 'member'
But that gives me all IDs that have a row with D.type = member (they could have 10 rows with all different types, but as long as 1 of those has type = member it shows up). I want to see ID's that ONLY have d.type = member
I'm sorry if I'm wording this badly, I'm having trouble getting this straight in my head. Any help is appreciated!
I would use exists for all conditions except the one on the D table:
SELECT c.*
FROM c JOIN
(SELECT d.rownum, COUNT(*) as cnt,
SUM(CASE WHEN d.type = 'Member' THEN 1 ELSE 0 END) as num_members
FROM t
GROUP BY d.rownum
) d
ON c.rownum = d.rownum
WHERE c.status <> 'closed' AND
NOT EXISTS (SELECT 1 FROM t WHERE c.rowno = t.rowno) AND
NOT EXISTS (SELECT 1 FROM l WHERE c.rowno = l.rowno) AND
. . .
I find NOT EXISTS is easier to follow logically. I don't think there is a big performance difference between the two methods in SQL Server.

avoid repeating condition in select query

I have the following query to be executed on postgresql
SELECT COUNT(DISTINCT id_client) FROM contract c
INNER JOIN bundle b ON c.bundle_id = b.id
INNER JOIN payment_method pm ON pm.id = c.payment_method_id
WHERE country_id=1 AND b.platform_id=1 AND pm.name <> 'RIB'
AND CONDITION_1
AND id_client NOT IN (
SELECT id_client FROM contract c1
INNER JOIN bundle b1 ON (c1.bundle_id = b1.id)
INNER JOIN payment_method pm1 ON pm1.id = c1.payment_method_id
WHERE c1.country_id=1 AND b1.platform_id=1 AND pm1.name <> 'RIB'
AND CONDITION_2);
I don't like it because it's the same query repeated twice except of CONDITION_1 and CONDITION_2 (and I have another example where it's repeated 3 times).
It's also very slow as well.
I tried to rewrite it as the following:
WITH
filter_cpm AS (
SELECT * FROM contract c
INNER JOIN bundle b ON b.id = c.bundle_id
INNER JOIN payment_method pm ON pm.id = c.payment_method_id
WHERE c.country_id = 1 AND b.platform_id = 1 AND pm.name <> 'RIB'
)
SELECT COUNT(DISTINCT id_client) FROM filter_cpm
WHERE CONDITION_1
AND id_client NOT IN (
SELECT id_client FROM filter_cpm
WHERE CONDITION_2);
Now it's DRY but it's two times slower.
How can I re-write the query to have the same (or better) performance?
EDIT: I cannot join two conditions with AND. For example if CONDITION_1 and CONDITION_2 are VIP, then I want to select clients who were re-qualified from NOT VIP to VIP.
You can select from the common table expression twice, using an outer join:
WITH filter_cpm AS (SELECT *
FROM CONTRACT c
INNER JOIN BUNDLE b
ON b.ID = c.BUNDLE_ID
INNER JOIN PAYMENT_METHOD pm
ON pm.ID = c.PAYMENT_METHOD_ID
WHERE c.COUNTRY_ID = 1 AND
b.PLATFORM_ID = 1 AND
pm.NAME <> 'RIB')
SELECT COUNT(DISTINCT fc1.ID_CLIENT)
FROM filter_cpm fc1
LEFT OUTER JOIN filter_cpm fc2
ON fc2.ID_CLIENT = fc1.ID_CLIENT AND
CONDITION_2
WHERE fc1.CONDITION_1 AND
fc2.ID_CLIENT IS NULL
Best of luck.

SQL Left Outer Join acting like Inner Join

I am trying to do a left outer join on two tables (well, an inline view and a table).
What I want to happen is to list all the grads (I know there are 3815 DISTINCT Grads) with any of their enrolments (there could be 0 or n enrolments). What I'm getting is only a list of the grads that have enrolments (3649 DISTINCT students). I'm not sure where I'm going wrong with not getting all the rows from the grad 'view' (I don't have create view privs so this is my workaround).
This is my code:
SELECT C.*, D.FREEZE_EVENT, D.ACADEMIC_PERIOD, D.CAMPUS, D.COLLEGE, D.COLLEGE_DESC,D.MAJOR, D.MAJOR_DESC , D.STUDENT_RATE
FROM
(SELECT A.STUDENT_LEVEL_DESC, A.CAMPUS, A.CAMPUS_DESC, A.COLLEGE, A.COLLEGE_DESC, A.MAJOR_DESC, A.MAJOR, A.DEGREE_DESC, A.PERSON_UID, A.ID, A.NAME,
A.OUTCOME_GRADUATION_DATE, A.STATUS, A.GRAD_YEAR, A.TRAINING_LOCATION, B.CITIZENSHIP_TYPE
FROM ACAD_OUTOCME A, PERSON_DETAIL B
WHERE A.STUDENT_LEVEL IN ('02','03') AND A.GRAD_YEAR = '2015' AND A.FREEZE_EVENT = '10TH_SEP2016' AND B.FREEZE_EVENT = '10TH_SEP2016'
AND A.ID = B.ID) C
LEFT OUTER JOIN ACAD_STUDY D ON
C.CAMPUS = D.CAMPUS
AND C.COLLEGE = D.COLLEGE
AND C.MAJOR = D.MAJOR
AND C.PERSON_UID = D.PERSON_UID
WHERE D.FREEZE_EVENT = '10TH_SEP2016'
ORDER BY C.NAME
Any suggestions? I'm using Toad Data Point. I'm also the loan developer at work, so I don't have anyone I can ask to help out with this, and google has failed me.
Thanks!
Move your WHERE condition to the ON condition:
Select C.*
, D.FREEZE_EVENT
, D.ACADEMIC_PERIOD
, D.CAMPUS
, D.COLLEGE
, D.COLLEGE_DESC
, D.MAJOR
, D.MAJOR_DESC
, D.STUDENT_RATE
From (Select A.STUDENT_LEVEL_DESC
, A.CAMPUS
, A.CAMPUS_DESC
, A.COLLEGE
, A.COLLEGE_DESC
, A.MAJOR_DESC
, A.MAJOR
, A.DEGREE_DESC
, A.PERSON_UID
, A.ID
, A.NAME
, A.OUTCOME_GRADUATION_DATE
, A.STATUS
, A.GRAD_YEAR
, A.TRAINING_LOCATION
, B.CITIZENSHIP_TYPE
From ACAD_OUTOCME A
Join PERSON_DETAIL B On A.ID = B.ID
Where A.STUDENT_LEVEL In ('02', '03')
And A.GRAD_YEAR = '2015'
And A.FREEZE_EVENT = '10TH_SEP2016'
And B.FREEZE_EVENT = '10TH_SEP2016'
) C
Left Outer Join ACAD_STUDY D
On C.CAMPUS = D.CAMPUS
And C.COLLEGE = D.COLLEGE
And C.MAJOR = D.MAJOR
And C.PERSON_UID = D.PERSON_UID
And D.FREEZE_EVENT = '10TH_SEP2016'
Order By C.NAME;
The WHERE clause is evaluated after the OUTER JOIN, which would cause it to filter out the NULL records from the LEFT JOIN. So, having the right-hand table of a LEFT JOIN in the WHERE clause will effectively transform the OUTER JOIN into an INNER JOIN.

Join max date from a related table

I have the following queries:
select AccountId
into #liveCustomers
from AccountExtensionBase where New_duos_group not in ('T053','T054')
and New_AccountStage = 7
select AccountId
into #customerWhoLeft
from New_marketmessagein as a
inner join AccountExtensionBase as b on a.new_accountmminid = b.AccountId
where New_MessageTypeCode = '105L'
and a.New_EffectiveFromDate > '30 jun 2016'
and b.New_duos_group not in ('T053','T054')
select
accountid
, New_MPRNNumber
, New_duos_group
, New_CommercialAgreementDayRate
, New_CommercialAgreementNightRate
, New_CommercialAgreementHeatRate
, New_Tariffpriceagreedatsignup
, New_Tariffname
into
#monthCustomers
from
AccountExtensionBase
where
AccountId in (select * from #customerWhoLeft)
or
AccountId in (select * from #liveCustomers)
I now wish to join a table called usagefactorExtensionBase and join only the row containing the most recent read date but when I try to join this to my table of 4985 monthly customers I get like 106,813 rows using this code so I think my join or methodology has gone awry, can someone please help me correct the error so I display the list of monthCustomers plus the read details of their most recent read.
Attempting:
select
accountid
, New_MPRNNumber
, New_duos_group
, New_CommercialAgreementDayRate
, New_CommercialAgreementNightRate
, New_CommercialAgreementHeatRate
, New_Tariffpriceagreedatsignup
, New_Tariffname
, max(b.New_EffectiveFromDate)
, b.New_ActualUsageFactor
, b.New_EstimatedUseage
from
#monthCustomers as a
left join
New_marketmessageinusagefactorExtensionBase as b
on a.AccountId = b.new_accountmmusagefactorid
group by
accountid
, New_MPRNNumber
, New_duos_group
, New_CommercialAgreementDayRate
, New_CommercialAgreementNightRate
, New_CommercialAgreementHeatRate
, New_Tariffpriceagreedatsignup
, New_Tariffname
, b.New_ActualUsageFactor
, b.New_EstimatedUseage
try this,
SELECT
accountid,
New_MPRNNumber,
New_duos_group,
New_CommercialAgreementDayRate,
New_CommercialAgreementNightRate,
New_CommercialAgreementHeatRate,
New_Tariffpriceagreedatsignup,
New_Tariffname,
b.New_EffectiveFromDate,
b.New_ActualUsageFactor,
b.New_EstimatedUseage
FROM #monthCustomers AS a
-- Get only max date rows for each AccountID
LEFT JOIN( SELECT t1.*
FROM New_marketmessageinusagefactorExtensionBase AS t1
INNER JOIN ( SELECT new_accountmmusagefactorid, MAX(New_EffectiveFromDate) AS New_EffectiveFromDate_Max
FROM New_marketmessageinusagefactorExtensionBase
GROUP BY new_accountmmusagefactorid
) AS t2 ON t2.new_accountmmusagefactorid = t1.new_accountmmusagefactorid
AND t2.New_EffectiveFromDate_Max = t1.New_EffectiveFromDate
)AS b
ON a.AccountId = b.new_accountmmusagefactorid
there might be rows with same date, try below if is works,
SELECT
accountid,
New_MPRNNumber,
New_duos_group,
New_CommercialAgreementDayRate,
New_CommercialAgreementNightRate,
New_CommercialAgreementHeatRate,
New_Tariffpriceagreedatsignup,
New_Tariffname,
b.New_EffectiveFromDate,
b.New_ActualUsageFactor,
b.New_EstimatedUseage
FROM #monthCustomers AS a
-- Get only max date rows for each AccountID
LEFT JOIN( SELECT New_MPRNNumber,
New_duos_group,
New_CommercialAgreementDayRate,
New_CommercialAgreementNightRate,
New_CommercialAgreementHeatRate,
New_Tariffpriceagreedatsignup,
New_Tariffname,
MAX(New_EffectiveFromDate) AS New_EffectiveFromDate,
New_ActualUsageFactor,
New_EstimatedUseage
FROM New_marketmessageinusagefactorExtensionBase AS t1
GROUP BY
New_MPRNNumber,
New_duos_group,
New_CommercialAgreementDayRate,
New_CommercialAgreementNightRate,
New_CommercialAgreementHeatRate,
New_Tariffpriceagreedatsignup,
New_Tariffname,
New_ActualUsageFactor,
New_EstimatedUseage
)AS b
ON a.AccountId = b.new_accountmmusagefactorid

Get DISTINCT record using INNER JOIN

I have the follwong Query on multi tables
SELECT DISTINCT b.BoxBarcode as [Box Barcode], (select case when b.ImagesCount IS NULL
then 0
else b.ImagesCount end) as [Total Images], s.StageName as [Current Stage] ,d.DocuementTypeName as [Document Type],
u.UserName as [Start User],uu.UserName as [Finished User]
FROM [dbo].[Operations] o
inner join dbo.LKUP_Stages s on
o.stageid=s.id
inner join dbo.LKUP_Users u on
u.id=o.startuserid
left join dbo.LKUP_Users uu on
uu.id=o.FinishedUserID
inner join boxes b on
b.id=o.boxid
inner join LKUP_DocumentTypes d on
d.ID = b.DocTypeID
where b.IsExportFinished = 0
when i select count from the Boxes table where IsExportFinished = 0
i got the Count 42 records, when i run the above qoury i got 71 records,
i want just the 42 records in Boxes table to retrived.
You are doing a one-to-many join, i.e. at least one of the tables have multiple rows that match the join criteria.
Step one is to find which table(s) that give the "duplicates".
Once you have done that you may be able to fix the problem by adding additional criteria to the join. I'm taking a guess that the same boxid occurs several times in the Operations table. If that is the case you need to decide which Operation row you want to select and then update the SQL accordingly.
Try this one -
SELECT
Box_Barcode = b.BoxBarcode
, Total_Images = ISNULL(b.ImagesCount, 0)
, Current_Stage = s.StageName
, Document_Type = d.DocuementTypeName
, Start_User = u.UserName
, Finished_User = uu.UserName
FROM (
SELECT DISTINCT
o.stageid
, o.boxid
, o.startuserid
, o.FinishedUserID
FROM dbo.[Operations]
) o
JOIN dbo.LKUP_Stages s ON o.stageid = s.id
JOIN dbo.boxes b ON b.id = o.boxid
JOIN dbo.LKUP_DocumentTypes d ON d.id = b.DocTypeID
JOIN dbo.LKUP_Users u ON u.id = o.startuserid
LEFT JOIN dbo.LKUP_Users uu ON uu.id = o.FinishedUserID
WHERE b.IsExportFinished = 0
I guess that if you change your LEFT JOIN into INNER JOIN you will get 42 records as requested.