Subject: SQL statement that includes LEFT & RIGHT JOIN in
Posted By: callagga Post Date: 6/3/2008 8:07:20 AM
Hi,

How an I write an SQL statement with a GROUP BY that will both (a) include the NULL value from the left hand table, but also (b) include ALL columns from the right hand table. It's like I need a LEFT JOIN and a RIGHT JOIN in the query at the same time.

Here's an example of the 2 tables I have and the result I'm after. As you can see I want the NULL's from Expenses Table summed, as well as include all categories from the right hand table (i.e. even if there are no expenses against them)

Table = Expenses
Amount Category
$10 1
$20 2
$30 1
$40 NULL {i.e. not yet categorised}

Table = Categories
ID Title
1 Food
2 Entertainment
3 Travel
4 Personal

REQUIRED RESULT
Category Total
Food 40
Entertainment 20
Travel 0
Personal 0
NULL 40


Thanks
Reply By: Old Pedant Reply Date: 6/3/2008 12:56:09 PM
Easiest thing is to this with a UNION, I think.

SELECT C.Title, SUM(ISNULL(E.Amount,0.0)) AS Total
FROM categories AS C LEFT JOIN expenses AS E
ON C.ID = E.Category
    UNION
SELECT 'not categorized yet', SUM(E.Amount)
FROM expenses WHERE category IS NULL
ORDER BY C.Title

Nothing more than that.  That's for SQL Server.  If you want it for Access:

SELECT C.Title, SUM(IIF(ISNULL(E.Amount),0.0,E.Amount)) AS Total
FROM categories AS C LEFT JOIN expenses AS E
ON C.ID = E.Category
    UNION
SELECT 'not categorized yet', SUM(E.Amount)
FROM expenses WHERE category IS NULL
ORDER BY C.Title

If you want the NULL to appear at the end of the list, just add a dummy field and change the order by:

SELECT 1 AS ordering, C.Title, SUM(ISNULL(E.Amount,0.0)) AS Total
FROM categories AS C LEFT JOIN expenses AS E
ON C.ID = E.Category
    UNION
SELECT 2, 'not categorized yet', SUM(E.Amount)
FROM expenses WHERE category IS NULL
ORDER BY ordering, C.Title
Reply By: Old Pedant Reply Date: 6/3/2008 12:57:33 PM
Sorry...should just be
    SUM(Amount)
for the second SELECT in the UNION.  I didn't bother to alias the table to E since there is only one table used.
Reply By: callagga Reply Date: 6/3/2008 3:12:23 PM
Thanks heaps.

One minor question - Is it possible to have the output such that instead of the word "NULL" appearing the text "Non Categorised Expenses" appears instead?

Thanks again
Reply By: Old Pedant Reply Date: 6/3/2008 4:10:13 PM
Ummm....you didn't read my code, did you???

I *ALREADY* did that for you!!!

SELECT 1 AS ordering, C.Title, SUM(ISNULL(E.Amount,0.0)) AS Total
FROM categories AS C LEFT JOIN expenses AS E
ON C.ID = E.Category
    UNION
SELECT 2, 'not categorized yet', SUM(E.Amount)
FROM expenses WHERE category IS NULL
ORDER BY ordering, C.Title
Reply By: callagga Reply Date: 6/3/2008 4:18:00 PM
arrr, got it sorry.  Well I guess read it quickly and shooting off a quick reply before heading out != read & understood & tried the code.  Thanks again.  Really appreciated
Reply By: callagga Reply Date: 6/4/2008 4:36:51 AM
Thanks again,

There's one additional twist this time I'm pretty sure is NOT possible in SQL, however given what you've been able to demonstrate re SQL I'd love to ask.

If I actually wanted to have the report broken down into periods (i.e. each X days) would this be possible?  That is per the below.  That is each expense has a date, and I want to get a summary per Period (e.g. X days wide)


REQUIRED OUTPUT
===============
Category      13/4/08-15/4/08     16/4/08-18/4/08      29/4/08-20/4/08      etc
-------       ---------------     ---------------      ---------------      ----
Entertainment 20.00               10                   0
Food          40.00               5                    20
Personal      0.00                0                    0
Travel        0.00                0                    10
NULL          40.00               80                   100

INPUT
=====
* Category table => per previous posts
* Expense table => per previous BUT with a new TransactionDate column.


Cheers
Greg

Reply By: Old Pedant Reply Date: 6/4/2008 5:07:18 PM
Well, you *could* do this, but it would amost surely be easier to do it what every reporting mechanism it is that you are using.  

But just to show an example (not using LEFT JOIN or UNION, just for simplicity, using name "tdate" for brevity):


SELECT 1 AS ordering, 
         C.Title, 
         SUM( CASE WHEN tdate BETWEEN '13/4/2008' AND '15/4/2008' THEN Amount ELSE 0.0 END ) AS Total1,
         SUM( CASE WHEN tdate BETWEEN '16/4/2008' AND '18/4/2008' THEN Amount ELSE 0.0 END ) AS Total2,
         SUM( CASE WHEN tdate BETWEEN '19/4/2008' AND '20/4/2008' THEN Amount ELSE 0.0 END ) AS Total3,
         ...
FROM expenses AS E INNER JOIN categories AS C
ON C.ID = E.Category
GROUP BY ordering, C.title


If you do it via reporting, then it's pretty easy and much more flexible:


SELECT 1 AS ordering, 
         C.Title, 
         DATEDIFF( day, '13/4/2008', tdate ) / 2 AS periodNumber,
         SUM( ISNULL(E.Amount,0.0) ) AS Total1
FROM expenses AS E LEFT JOIN categories AS C
ON C.ID = E.Category
GROUP BY ordering, C.title, DATEDIFF( day, '13/4/2008', tdate ) / 2
ORDER BY ordering, C.title, periodNumber


Hmmm???
Reply By: callagga Reply Date: 6/4/2008 8:15:42 PM
thanks for the "DATEDIFF" idea! - I'll look into using it & trying it out.  

I guess if such a SQL statement works this would still be better than my Ruby code issuing a SQL call for each specific period (i.e. as it works out sums for each period).
Reply By: Old Pedant Reply Date: 6/5/2008 7:00:32 PM
Haven't used Ruby, but oh yeah oh yeah...has to be *MUCH* faster!

In ASP/VBScript code, you could turn that SQL into a "report" pretty easily, thus (again, simplifying the query for brevity):

<%
...
SQL = "SELECT C.Title, " _
    &       " DATEDIFF( day, '13/4/2008', tdate ) / 2 AS periodNumber, " _
    &       " SUM( ISNULL(E.Amount,0.0) ) AS Total " _
    & " FROM expenses AS E LEFT JOIN categories AS C " _
    & " ON C.ID = E.Category " _
    & " WHERE tdate BETWEEN '13/4/2008' AND '22/4/2008' " _
    & " GROUP BY C.title, DATEDIFF( day, '13/4/2008', tdate ) / 2 " _
    & " ORDER BY C.title, periodNumber "

Set RS = connObj.Execute( SQL )
%>
<TABLE Border=1 Cellpadding=3>
<TR>
    <TH>Title</TH>
<% For Period = #2008-4-13# To #2008-4-22# Step 2 %>
    <TH><%=Period%><br/><%=(Period+1)%></TH>
<% Next %>
</TR>
<%
priorTitle = ""
Do Until RS.EOF
   curTitle = RS("Title")
   If curTitle <> priorTitle Then
       If priorTitle <> "" Then Response.Write "</TR>" & vbNewLine
%>
<TR>
    <TD><%=curTitle%></TD>
<%
       priorTitle = curTitle
   End If
%>
    <TD><%=FormatCurrency(RS("Total")%></TD>
<%
    RS.MoveNext
Loop
RS.Close
%>
</TR>
</TABLE>


That code has some flaws:  As written, if there is not data for some period for a given title, it will put the next total into that period, etc.  Can easily fix that in the code, but since there are other (purposeful) omissions (such as the UNION) I left it as is, just so the code is simple and understandable.
Reply By: callagga Reply Date: 6/8/2008 1:58:11 AM
got an idea from someone I was chatting to.

Basically pre-create a table that has every date over the years you're interested in.  Then for each date create a column for each sorting arrangement you might want, e.g. day number, week number, month number etc.  Then join the expenses table with this on date.  Pretty cool...
Reply By: Old Pedant Reply Date: 6/9/2008 1:19:14 AM
Yes, that's what I recommend for building event calendars.

http://www.ClearviewDesign.com/Newbie

See the "calendar of events" demo.

But I wasn't sure it was worth the effort here.  If you can use it for other purposes, though, it clearly simplifies lots of queries.

Reply By: Old Pedant Reply Date: 6/9/2008 1:32:43 AM
You remember who it was who suggested the trick?  Curious if he and I have "tallked".
Reply By: callagga Reply Date: 6/9/2008 2:30:02 AM
it's from Glenn, who has nothing to do with this forum, so I expect it would be extremely unlikely you know him...he does Crystal Reports

Go to topic 71962

Return to index page 1