Please start any new threads on our new site at https://forums.sqlteam.com. We've got lots of great SQL Server experts to answer whatever question you can come up with.

 All Forums
 SQL Server 2005 Forums
 Transact-SQL (2005)
 Self-Joining a Log Table

Author  Topic 

akashenk
Posting Yak Master

111 Posts

Posted - 2007-04-23 : 18:46:36
I have a "Log" table that records when "Employees" are assigned/removed from "Clients", The table has the following structure:

Employee_ID (nchar)
Client_ID (nchar)
AssignedRemoved (bit),
LogDate (datetime)

The AssignedRemoved field is a bit that specifies if the entry is for a client Assignment (1) or Removal (0).

I need to get a resultset that contains all of the Client_IDs for a given User_ID where the Client is still assigned AS OF a certain date.

Here is some smaple data

UserA, ClientA, 1, 1/6/07
UserB, ClientA, 1, 1/8/07
UserC, ClientB, 1, 2/5/07
UserA, ClientA, 0, 1/12/07
UserA, ClientD, 1, 1/7/07
UserC, ClientA, 1, 1/14/07

So, for example, if I wanted a list of all clients assigned to UserA as of 1/8/07... the result would be "ClientA, ClientD". If I change the date input to 1/13/07, the result for UserA would be just "ClientD".

So, I'm thinking I will need to do some sort of self join here, but I am having a hard time figuring it out. Can anyone help? Thanks in advance.

dinakar
Master Smack Fu Yak Hacker

2507 Posts

Posted - 2007-04-23 : 18:50:37
SELECT <columns>
FROM Log
where employee_id = @user
and AssignedRemoved = 1
and employee_id <> Client_Id


will give you both ClientA and ClientD. Now I dont understand what is the logic for you to choose ClientD over ClientA when date is 1/13/07.



************************
Life is short. Enjoy it.
************************
http://weblogs.sqlteam.com/dinakar/
Go to Top of Page

akashenk
Posting Yak Master

111 Posts

Posted - 2007-04-23 : 19:48:43
Thanks for the reply, however I'm not sure where the employee_id <> Client_Id fits in, given that they will never be equal. Perhaps I dind't make myself clear. I need to pass an employee (userID) and a date. I am trying to find out which clients are assigned to the given employee as of the given date.

quote:
Originally posted by dinakar

SELECT <columns>
FROM Log
where employee_id = @user
and AssignedRemoved = 1
and employee_id <> Client_Id


will give you both ClientA and ClientD. Now I dont understand what is the logic for you to choose ClientD over ClientA when date is 1/13/07.



************************
Life is short. Enjoy it.
************************
http://weblogs.sqlteam.com/dinakar/

Go to Top of Page

khtan
In (Som, Ni, Yak)

17689 Posts

Posted - 2007-04-23 : 21:18:27
[code]
select *
from Log
where AssignedRemoved = 1
and Employee_ID = @UserID
and LogDate <= @Date
[/code]


KH

Go to Top of Page

Vinnie881
Master Smack Fu Yak Hacker

1231 Posts

Posted - 2007-04-23 : 21:23:46
Declare @InputDate datetime
Declare @EmployeeID varchar(20)
Select @InputDate = '04/08/2007', @EmployeeID = 'UserA'

SELECT Client_ID
FROM Table1 b
where employee_id = @Employee_ID
and AssignedRemoved = 1
and b.LogDate = ( select max(a.logdate) as logdate
from table1 a
where a.logDate <= @InputDate
and a.Employee_ID = @EmployeeID
and a.Client_ID = b.ClientID
and a.AssignedRemoved = 1
and (
( select max(a.logdate) as logdate
from table1 c
where c.logDate <= @InputDate
and a.Employee_ID = @EmployeeID
and a.Client_ID = b.ClientID
and AssignedRemoved = 0
) is null
or
( select max(a.logdate) as logdate
from table1 c
where c.logDate <= @InputDate
and a.Employee_ID = @EmployeeID
and a.Client_ID = b.ClientID
and AssignedRemoved = 0) < (select max(a.logdate) as logdate
from table1 a
where a.logDate <= @InputDate
and a.Employee_ID = @EmployeeID
and a.Client_ID = b.ClientID
and AssignedRemoved = 1
)

)
)
Go to Top of Page

akashenk
Posting Yak Master

111 Posts

Posted - 2007-04-23 : 21:27:46
Thanks for the reply, khtan. Unfortunately, its not quite so simple which is why I think I need some sort of self-join.

With your code, I will get a list of all clients which were assigned to the employee on or before the specified date. However, some of these were subsequently "unassigned". In my example, if I pass " UserA" and the date 1/13/07 to your codd, I will get two records, when I should only get one.


quote:
Originally posted by khtan


select *
from Log
where AssignedRemoved = 1
and Employee_ID = @UserID
and LogDate <= @Date



KH



Go to Top of Page

jsmith8858
Dr. Cross Join

7423 Posts

Posted - 2007-04-23 : 21:33:08
I *think* this will do the trick, simple and quick but kind of unusual:


select client_ID
from Log
where user_ID = @UserID and logDate <= @EndDate
group by client_ID
having max(case when AssignedRemoved = 1 then logDate else '1/1/1900' end) >
max(case when AssignedRemoved = 0 then logDate else '1/1/1900' end)



- Jeff
http://weblogs.sqlteam.com/JeffS
Go to Top of Page

khtan
In (Som, Ni, Yak)

17689 Posts

Posted - 2007-04-23 : 21:34:34
try this

select *
from Log l
where AssignedRemoved = 1
and Employee_ID = @UserID
and LogDate <= @Date
and not exists
(
select *
from Log x
where x.Employee_ID = l.Employee_ID
and x.Client_ID = l.Client_ID
and x.AssignedRemoved = 0
and x.LogDate <= @Date
)




KH

Go to Top of Page

jsmith8858
Dr. Cross Join

7423 Posts

Posted - 2007-04-23 : 21:40:51
mine might look funny but it will work, and it should be quite efficient (no self join needed).

- Jeff
http://weblogs.sqlteam.com/JeffS
Go to Top of Page

khtan
In (Som, Ni, Yak)

17689 Posts

Posted - 2007-04-23 : 21:42:42
quote:
Originally posted by jsmith8858

mine might look funny but it will work, and it should be quite efficient (no self join needed).

- Jeff
http://weblogs.sqlteam.com/JeffS




Yes. Your codes works well. Mine does not cater for situation where the client is assign again to the Employee.



KH

Go to Top of Page

akashenk
Posting Yak Master

111 Posts

Posted - 2007-04-23 : 21:51:29
Wow, that's a doozy. I couldn't get it to compile. I get the following error:

"An aggregate may not appear in the WHERE clause unless it is in a subquery contained in a HAVING clause or a select list, and the column being aggregated is an outer reference."

Also, and I am no expert in this, but this would seem to be a really poorly perfomring query, with so many sub-queries. Is there no way to accomplish the same thing with self-joins?


quote:
Originally posted by Vinnie881

Declare @InputDate datetime
Declare @EmployeeID varchar(20)
Select @InputDate = '04/08/2007', @EmployeeID = 'UserA'

SELECT Client_ID
FROM Table1 b
where employee_id = @Employee_ID
and AssignedRemoved = 1
and b.LogDate = ( select max(a.logdate) as logdate
from table1 a
where a.logDate <= @InputDate
and a.Employee_ID = @EmployeeID
and a.Client_ID = b.ClientID
and a.AssignedRemoved = 1
and (
( select max(a.logdate) as logdate
from table1 c
where c.logDate <= @InputDate
and a.Employee_ID = @EmployeeID
and a.Client_ID = b.ClientID
and AssignedRemoved = 0
) is null
or
( select max(a.logdate) as logdate
from table1 c
where c.logDate <= @InputDate
and a.Employee_ID = @EmployeeID
and a.Client_ID = b.ClientID
and AssignedRemoved = 0) < (select max(a.logdate) as logdate
from table1 a
where a.logDate <= @InputDate
and a.Employee_ID = @EmployeeID
and a.Client_ID = b.ClientID
and AssignedRemoved = 1
)

)
)


Go to Top of Page

jsmith8858
Dr. Cross Join

7423 Posts

Posted - 2007-04-23 : 21:53:00
akashenk -- did you see/try my code ?

- Jeff
http://weblogs.sqlteam.com/JeffS
Go to Top of Page

akashenk
Posting Yak Master

111 Posts

Posted - 2007-04-23 : 21:59:28
Thanks for your help, jsmith and kthan! JSmith's does seem to be the best as it take into account the situation where a client is reassigned to the same employee at a later time. One question, though. What does the "else '1/1/1900' end" part do. I assume that this references some system min date. Is there a way to accomplish the same thing without referenceing this date in a hard-coded fashion?


quote:
Originally posted by jsmith8858

I *think* this will do the trick, simple and quick but kind of unusual:


select client_ID
from Log
where user_ID = @UserID and logDate <= @EndDate
group by client_ID
having max(case when AssignedRemoved = 1 then logDate else '1/1/1900' end) >
max(case when AssignedRemoved = 0 then logDate else '1/1/1900' end)



- Jeff
http://weblogs.sqlteam.com/JeffS


Go to Top of Page

jsmith8858
Dr. Cross Join

7423 Posts

Posted - 2007-04-23 : 22:07:58
I like the way you are thinking (i.e., removing hard-coded dates), but in this case it is just there to ensure that the logic works and has no real effect other than to avoid dealing with NULLS. As long as you have no dates at or before 1/1/1900 in your data, you will be fine. It's essentially like using a default of 0 when doing some integer assignments or arithmetic.

- Jeff
http://weblogs.sqlteam.com/JeffS
Go to Top of Page

akashenk
Posting Yak Master

111 Posts

Posted - 2007-04-23 : 22:09:49
If I am assured to never have a null date, is this something I can remove, or is it better to keep just in case?
Go to Top of Page

jsmith8858
Dr. Cross Join

7423 Posts

Posted - 2007-04-23 : 22:36:24
No, it's not for null dates, it's for when there is no assigned or removed row in the data for a particular client within the date range.

- Jeff
http://weblogs.sqlteam.com/JeffS
Go to Top of Page

akashenk
Posting Yak Master

111 Posts

Posted - 2007-04-23 : 22:44:58
Ahh... I see. Thanks for all your help.
Go to Top of Page

Vinnie881
Master Smack Fu Yak Hacker

1231 Posts

Posted - 2007-04-24 : 00:52:41
I had the parenthesise wrong.


The reason this code is has so many subqueries is due to you nee to retrieve the last date the client was applied taking into consideration if they were ever unapplied. This query should give you exacltly what you need factoring in all scenerios

Like : A client was applied, then later unapplied on a specific date.

The other code I have seen does not factor this in.

Declare @InputDate datetime
Declare @EmployeeID varchar(20)
Select @InputDate = '04/08/2007', @EmployeeID = 'UserA'

SELECT Client_ID
FROM Table1 b
where employee_id = @EmployeeID
and AssignedRemoved = 1
and b.LogDate = ( select max(a.logdate) as logdate
from table1 a
where a.logDate <= @InputDate
and a.Employee_ID = @EmployeeID
and a.Client_ID = b.ClientID
and a.AssignedRemoved = 1
and (
(
( select max(a.logdate) as logdate
from table1 c
where c.logDate <= @InputDate
and a.Employee_ID = @EmployeeID
and a.Client_ID = b.ClientID
and AssignedRemoved = 0
) is null)
or
(
select max(a.logdate) as logdate
from table1 c
where c.logDate <= @InputDate
and a.Employee_ID = @EmployeeID
and a.Client_ID = b.ClientID
and AssignedRemoved = 0) < (select max(a.logdate) as logdate
from table1 a
where a.logDate <= @InputDate
and a.Employee_ID = @EmployeeID
and a.Client_ID = b.ClientID
and AssignedRemoved = 1
)

)
) )
Go to Top of Page

akashenk
Posting Yak Master

111 Posts

Posted - 2007-04-24 : 01:06:57
Thanks for the reply, vinnie. I see that now. However, I have been playing around with the code provided by jsmith and I think this handles all situations... such as a client being assigned, unassigned and later assigned again. Thanks again for your help, though.
Go to Top of Page

SwePeso
Patron Saint of Lost Yaks

30421 Posts

Posted - 2007-04-24 : 01:28:32
Since this is a SQL Server 2005 forum, try this approach!
-- Prepare sample data
DECLARE @Sample TABLE ([User] VARCHAR(10), Client VARCHAR(10), Assigned BIT, dt DATETIME)

INSERT @Sample
SELECT 'UserA', 'ClientA', 1, '1/6/07' UNION ALL
SELECT 'UserB', 'ClientA', 1, '1/8/07' UNION ALL
SELECT 'UserC', 'ClientB', 1, '2/5/07' UNION ALL
SELECT 'UserA', 'ClientA', 0, '1/12/07' UNION ALL
SELECT 'UserA', 'ClientD', 1, '1/7/07' UNION ALL
SELECT 'UserC', 'ClientA', 1, '1/14/07'

-- Initialize a common table expression
;WITH cteLog ([User], Client)
AS
(
SELECT y.[User],
y.Client
FROM (
SELECT [User],
Client,
Assigned,
dt,
ROW_NUMBER() OVER (PARTITION BY [User], Client ORDER BY dt DESC, Assigned) AS RecID
FROM @Sample
WHERE dt <= '1/8/07'
) AS y
WHERE y.Assigned = 1
AND y.RecID = 1
) -- change assigned [asc] to assigned desc if order of assigned is important

-- Show the expected output
SELECT DISTINCT c1.[User],
STUFF((SELECT DISTINCT TOP 100 PERCENT ', ' + c2.Client FROM cteLog AS c2 WHERE c2.[User] = c1.[User] ORDER BY ', ' + c2.Client FOR XML PATH('')), 1, 2, '') AS Clients
FROM cteLog AS c1
ORDER BY c1.[User]
This will also handle cases where a user is assigned and removed from a client the very same day.
But still the sample data can't distinguish from the case where a user has been assigned to a client, and then on the same day is removed and later same day is assigned again. My code will only handle cases where a user is assigned a client and later same day is removed from client.


Peter Larsson
Helsingborg, Sweden
Go to Top of Page

akashenk
Posting Yak Master

111 Posts

Posted - 2007-04-24 : 01:42:41
Thanks, Peso. That appears to work as well. I believe you also need to add a "and y.[user]=@userId" line at the end so that the Employee can be passed as an input parameter.

What is the advantage of this approach over JSmith's?...

select client_ID
from Log
where user_ID = @UserID and logDate <= @EndDate
group by client_ID
having max(case when AssignedRemoved = 1 then logDate else '1/1/1900' end) >
max(case when AssignedRemoved = 0 then logDate else '1/1/1900' end)
Go to Top of Page
    Next Page

- Advertisement -