tags:

views:

363

answers:

6

I have a table:

ComputerID, UserID, LoginDate

I need a select statement that returns:
ComputerID, UserID, LoginCount

For all computers, but for each computer, showing just the one user that logged on most often to that computer. (If there is a tie, I guess I would want to just arbitrarily pick one of the users....so this would seem to indicate I need a TOP 1 somewhere.)

(This is in ms-access, so can't use vendor specific functionality).

Solution (slight fix of JBrooks answer)

select main.*
from (select ComputerID, UserID, count(1) as cnt
    from ComputerLoginHistory
    group by ComputerID, UserID) as main
           inner join (select ComputerID, max(cnt) As  maxCnt
                            from 
                                    (select ComputerID, UserID, count(1) as cnt
                                     from ComputerLoginHistory
                                     group by ComputerID, UserID) as Counts
                            group by ComputerID)
                          as maxes
on main.ComputerID = maxes.ComputerID
and main.cnt = maxes.maxCnt

To handle the situation where >1 user may have the same loginCount for a given computer, about all I can think of to wrap another select around this, selecting the Max(UserID).....so you are basically just arbitrarily picking one of them. That's what I've done in this example, where I am pulling back the most recent user, rather than the most active user:

Select ComputerID, Max(xUserID) As UserID, MaxLoginDate
FROM
(
SELECT main.ComputerID, main.UserID as xUserID, main.MaxLoginDate
FROM [select ComputerID, UserID, Max(LoginDate) as MaxLoginDate
    from ComputerLoginHistory
    group by ComputerID, UserID]. AS main 
     INNER JOIN [select ComputerID, Max(MaxLoginDate) As MaxLogin
                            from 
                                    (select ComputerID, UserID, Max(LoginDate) as MaxLoginDate
                                     from ComputerLoginHistory
                                     group by ComputerID, UserID) as Counts
                            group by ComputerID]. AS maxes ON (main.MaxLoginDate = maxes.MaxLogin) AND (main.ComputerID = maxes.ComputerID)
)
GROUP BY ComputerID, MaxLoginDate
ORDER BY ComputerID
+5  A: 

In MS Access, I would write two three queries and call the second query from the first query. The first query should simply do the count, and the next query will find the max of the first query. The third query will reference the user ID in the first query after joining both of the first 2 queries together.

For example:

qryCountQuery:

SELECT 
LoginCount.ComputerID, 
LoginCount.UserID, 
Count(LoginCount.ComputerID) AS CountOfComputerID
FROM 
LoginCount
GROUP BY 
LoginCount.ComputerID, LoginCount.UserID;

qryMaxQuery:

SELECT 
qryCountQuery.ComputerID, 
Max(qryCountQuery.CountOfComputerID) AS MaxOfCountOfComputerID
FROM 
qryCountQuery
GROUP BY 
qryCountQuery.ComputerID;

qryCountMaxQueryCombined:

SELECT 
qryMaxQuery.ComputerID, 
qryMaxQuery.MaxOfCountOfComputerID, 
qryCountQuery.UserID
FROM 
qryCountQuery 
INNER JOIN qryMaxQuery 
    ON (qryCountQuery.CountOfComputerID = qryMaxQuery.MaxOfCountOfComputerID) AND 
       (qryCountQuery.ComputerID = qryMaxQuery.ComputerID);

NOTE: If you have users that are "tied" for the most logins to one PC, the PC will show up twice with both users. You could throw in a UNIQUE or throw a FIRST aggregate call into yet another query. It depends on if you absolutely must have only one result per computerID.

NOTE2: In another system such as MySQL I might use nested queries, but I prefer to separate it out in Access.

NOTE3: I forgot this problem was difficult in Access. I'm glad I tested my code.

Ben McCormack
+1 for a very thorogh and tested answer
Chris Ballance
Yes, good answer....went with JBrooks approach though due to the conciseness.
tbone
I completely understand. I sometimes do that in Access, but Access (version 2002/XP) likes to kill my SQL formatting and makes it exceptionally difficult for me to work with.
Ben McCormack
+2  A: 
SELECT TOP 1 * FROM YOURTABLE ORDER BY LoginCount DESC

(MS - SQL, not sure this works in Access)

Try some variation on this example:

SELECT UserId, MAX(COUNT(*)) as "HighestLogin"
FROM YOURTABLE
GROUP BY ComputerId;
Chris Ballance
This will only return one row....I need all rows (all computers)
tbone
The TOP keyword is not Standard SQL.
onedaywhen
@onedaywhen Thanks, updated accordingly
Chris Ballance
TOP is standard Jet/ACE SQL. It has been supported at least since Jet 2.x. I don't even know what the term "Standard SQL" means. All I see is a bunch of SQL dialects, slightly different for every database engine. There are SQL standards, but no single database engine fully implements any SQL standard that has ever existed.
David-W-Fenton
@David W. Fenton: Standard SQL usually means ISO/ANSI SQL Standards to which all SQL products -- even the Access Database Engine in the Jet 4.0 era -- pay a high regard to. On the other hand, "standard Jet/ACE SQL" is a phrase you just invented ;)
onedaywhen
FWIW the SQL-99 Standard has the ROW_NUMBER() OVER (ORDER BY..) syntax as an alternative for the proprietary TOP N syntax.
onedaywhen
+4  A: 

If you want a listing of each computer Id with its top user it would be something like:

select main.*
from (select ComputerID, UserID, count(1) as cnt
    from logTable
    group by ComputerID, UserID) as main
    inner join (select ComputerID, max(cnt) maxCnt
       from (select ComputerID, UserID, count(1) as cnt
        from logTable
        group by ComputerID, UserID) as Counts) 
       as maxes
on main.ComputerID = maxes.ComputerID
and main.cnt = maxes.maxCnt
JBrooks
What if two users had the same number of logins to a computer?
Paul
I posted a slight fix to this sql in the original question.
tbone
A: 

Could you check this on your Access database:

select ComputerID, UserID, count(*) as LoginCount
from t2  parent_table
group by ComputerID, UserID
having count(*) = (select max(count(*) ) from t2 
       where ComputerID = parent_table.ComputerID group by ComputerID, UserID)
order by ComputerID, UserID
skwllsp
Damn. Cannot have aggregate function in expression <expression>. (Error 3095)
tbone
A: 
create temporary table t select ComputerID, UserID, count(*) as LoginCount
from table group by ComputerID, UserID;

create temporary table m select ComputerID, max(LoginCount) as maxLoginCount
from t group by ComputerID;

select m.ComputerID, max(UserID) from m join t
on m.ComputerID = t.ComputerID and m.maxLoginCount = t.LoginCount
group by ComputerID;
Keith Randall
Did you try running this SQL in Access, i.e., against a Jet/ACE data store?
David-W-Fenton
Nope. It's pretty generic sql, though. Temporary tables should work in Access (http://office.microsoft.com/en-us/access/HP010322201033.aspx), not sure about create/select though.
Keith Randall
@David W. Fenton: the question's title states, "using standard sql." And if you believe the Access Help (which I don't), the Access Database Engine does support the CREATE TEMPORARY TABLE syntax (http://office.microsoft.com/en-gb/access/HA012314411033.aspx) ;)
onedaywhen
+2  A: 

not sure if this is available in MS ACCESS, it is for sure available in SQL Server 05 & 08 so you will have to investigate that.

you can use the handy ROW_NUMBER() function that will allow you to partition and assign a row number to a subset of the data and then you can limit the results brought back based upon that row number.

DECLARE     @MaxResultsPerComputerID    INT
SELECT      @MaxResultsPerComputerID    = 3 -- assign the maximum number of results to bring back per computer

SELECT      *
FROM        (
            SELECT      ComputerID, 
                        UserID, 
                        LoginDate,
                        NumberOfTimesLoggedIn, -- Derive this value somehow (not sure what your db schema is)
                        ROW_NUMBER() OVER (PARTITION BY ComputerID ORDER BY NumberOfTimesLoggedIn) AS RowNumber -- the magic happens here
            FROM        SomeTable
            ) a
WHERE       RowNumber <= @MaxResultsPerComputerID
Jon Erickson
This is Oracle though I think, no??
tbone
this is sql server 05+, not sure if it will work in MS Access. but I won't delete because it may be an answer that may help someone that finds this question.
Jon Erickson
`ROW_NUMBER() OVER` may be useful for the Oracle/SQL Server/DB2 crowd, but it's not useful in Access, MySQL, or PostgreSQL (source: *SQL Cookbook* , p. 330: http://www.amazon.com/Cookbook-Cookbooks-OReilly-Anthony-Molinaro/dp/0596009763/ )
Ben McCormack