views:

140

answers:

5

I'm looking up access logs for specific courses. I need to show all the courses even if they don't exist in the logs table. Hence the outer join.... but after trying (presumably) all of the variations of LEFT OUTER, RIGHT OUTER, INNER and placement of the tables within the SQL code, I couldn't get my result.

Here's what I am running:

SELECT   (a.first_name+' '+a.last_name) instructor,
            c.course_id,
            COUNT(l.access_date) course_logins,
            a.logins system_logins,
            MAX(l.access_date) last_course_login,
            a.last_login last_system_login
FROM       lsn_logs l RIGHT OUTER JOIN courses c ON l.course_id = c.course_id,
            accounts a
WHERE     l.object_id = 'LOGIN' 
            AND c.course_type = 'COURSE' 
            AND c.course_id NOT LIKE '%TEST%' 
            AND a.account_rights > 2
            AND l.user_id = a.username
            AND ((a.first_name+' '+a.last_name) = c.instructor)
GROUP BY    c.course_id, 
            a.first_name, 
            a.last_name,
            a.last_login,
            a.logins,
            c.instructor
ORDER BY    a.last_name,
            a.first_name,
            c.course_id,
            course_logins DESC

Is it something in the WHERE clause that's preventing me from getting course_id's that don't exist in lsn_logs? Is it the way I'm joining the tables?

Again, in short, I want all course_id's regardless of their existence in lsn_logs.

+6  A: 

Is it something in the WHERE clause that's preventing me from getting course_id's that don't exist in lsn_logs?

Yes, it is.

You use equality conditions in your WHERE clause that effectively filter out the NULL rows generated by the OUTER JOIN.

Update:

SELECT  c.instructor,
        c.course_id,
        l.course_logins,
        a.logins system_logins,
        l.last_course_login,
        a.last_login last_system_login
FROM    courses с
JOIN    accounts a
ON      a.first_name + ' ' + a.last_name = c.instructor
CROSS APPLY
        (
        SELECT  COALESCE(COUNT(access_date), 0) course_logins,
                MAX(access_date) last_course_login
        FROM    lsn_logs l
        WHERE   l.object_id = 'LOGIN'
                AND l.course_id = c.course_id
                AND l.user_id = a.username
        ) l
WHERE   c.course_type = 'COURSE' 
        AND c.course_id NOT LIKE '%TEST%' 
        AND a.account_rights > 2
ORDER BY
        a.last_name,
        a.first_name,
        c.course_id,
        course_logins DESC
Quassnoi
thats what i figured... i think joe r's and/or andomars answer might work, but thanks for the clarification
CheeseConQueso
@cheese: could you please answer the question in my comment to your post?
Quassnoi
+2  A: 

To expand on Quassnoi's good answer, for the outer join to work you could change:

AND l.user_id = a.username

To:

AND (l.user_id is null OR l.user_id = a.username)
Andomar
the accounts and courses tables will always have records that match, but the lsn_logs will be empty for some courses and/or accounts
CheeseConQueso
the null/or combos on the lsn_logs table needed attention
CheeseConQueso
@CheeseConQueso: Answer edited so it just checks for null on the lsn_logs table
Andomar
also this - (l.object_id IS NULL OR l.object_id = 'LOGIN')... mention something about null checks in the answer
CheeseConQueso
+1  A: 

The SQL Server 2005 RIGHT OUTER JOIN certainly works! :)

What about the following (I've made some assumptions)?:

remove

AND l.user_id = a.username 

and change the joins to

dbo.courses c
LEFT OUTER JOIN lsn_logs l
ON c.course_id = l.course_id
LEFT OUTER JOIN accounts a
ON l.user_id = a.username 

and add some null checks for lsn_logs and accounts fields, such as:

(l.object_id IS NULL OR l.object_id = 'LOGIN')
Joe R
By the way, you have a very disturbing photo! :)
Joe R
its disturbing in a good way though... thanks for the suggestion, i think your double outer join might work
CheeseConQueso
still same result set... i see where you were going with this though
CheeseConQueso
You'd also need null checks on, e.g. - (l.object_id is null OR l.object_id = 'LOGIN')
Joe R
thats what im trying now, thanks
CheeseConQueso
Null checks on any of the lsn_logs or account fields
Joe R
ya i didn't think of that, and thats what it was
CheeseConQueso
add something about null checks to your answer
CheeseConQueso
A: 

This ended up working

The null checks that Joe R suggested took care of the problem that Quassnoi mentioned

SELECT   (a.first_name+' '+a.last_name) instructor,
            c.course_id,
            COUNT(l.access_date) course_logins,
            a.logins system_logins,
            MAX(l.access_date) last_course_login,
            a.last_login last_system_login
FROM       lsn_logs l RIGHT OUTER JOIN courses c ON l.course_id = c.course_id,
            accounts a
WHERE     (l.object_id IS NULL OR l.object_id = 'LOGIN') 
            AND c.course_type = 'COURSE' 
            AND c.course_id NOT LIKE '%TEST%' 
            AND a.account_rights > 2
            AND (l.user_id = a.username OR l.user_id IS NULL)
            AND ((a.first_name+' '+a.last_name) = c.instructor)
            AND a.logins > 0
GROUP BY    c.course_id, 
            a.first_name, 
            a.last_name,
            a.last_login,
            a.logins,
            c.instructor
ORDER BY    a.last_name,
            a.first_name,
            c.course_id,
            course_logins DESC
CheeseConQueso
btw - there is probably a better way to do this all within the `FROM` section.... please post any modifications
CheeseConQueso
this throws and error "sqlclient data provider error number 8153" but still returns the results.... gotta look into that
CheeseConQueso
+1  A: 
SELECT...    
FROM courses c
     INNER JOIN accounts a
        ON (a.first_name+' '+a.last_name) = c.instructor
     LEFT OUTER JOIN lsn_logs l
        ON l.course_id = c.course_id
        AND l.user_id = a.username
        AND l.object_id = 'LOGIN' 
WHERE c.course_type = 'COURSE'  
    AND c.course_id NOT LIKE '%TEST%'
    AND a.account_rights > 2 
    AND a.logins > 0 
GROUP BY...
ORDER BY...
Anthony Faull
nice - this works the same as mine, but eliminates all the null checking
CheeseConQueso
your solution returns 1386 and mine returns 1358... i gotta investigate that
CheeseConQueso