My system has a ledger. I set up 33 account records for various transaction types. Some of these account types have never been used -- the accounts are there provisionally, but they have never been used. In the ledger, only 25 accounts have actually been used. The ledger records the account id of the transaction.
An INNER JOIN (or "Right Join") comes in 2 forms
Implied:
SELECT account.id, account.name, COUNT( * )
FROM account, ledger
WHERE ledger.accountid = account.id
GROUP BY account.id
Explicit:
SELECT account.id, account.name, COUNT( * )
FROM account
INNER JOIN ledger ON ledger.accountid = account.id
GROUP BY 1
Both these INNER JOINS return the same results:
25 rows only -- the rows where the 25 ledger accountids match up with account records
A LEFT JOIN (or "Outer Join") is in this form:
SELECT account.id, account.name, COUNT( * )
FROM account
LEFT JOIN ledger ON ledger.accountid = account.id
GROUP BY 1
This returns 33 rows: 1 row for ever account record. Where the ledger has any matching transactions, the number of records matches the INNER JOIN. For the 8 unmatched account records, the query returns "1"
If I explicitly request information from the related ledger rows:
SELECT account.id,
account.name,
COUNT( * ) ,
MAX( ledger.id )
FROM account
LEFT JOIN ledger ON accountid = account.id
GROUP BY 1
The field "MAX(ledger.id)" will be blank if there are no corresponding ledger records.
I can use this 'blank' to enhance the report:
SELECT account.id,
account.name,
IF( MAX( ledger.id ) IS NULL,
'NO LEDGER RECORDS',
COUNT( * ) )
FROM account
LEFT JOIN ledger ON accountid = account.id
GROUP BY 1
The query above checks to see if there are corresponding ledger records by testing the value of the MAX(ledger.id). If that value IS NULL (no ledger record), it returns the string "NO LEDGER RECORDS", otherwise, it returns the count of the ledger records.
The same sort of Outer Join logic could be applied to your problem.