Answer to the most recent T-SQL challenge
The simplest rule-compliant solution to the employee-mentor challenge I recently posted is to change the loop such that it iterates through the relationships at more than one speed. Making no other changes to clean up the code, something like this would do:
CREATE PROC ListMentors(@LastEmpIdAdded int) as
DECLARE @i int
DECLARE @j int
SELECT @i = @LastEmpIdAdded -- ID of last employee added (known in advance)
PRINT 'Employee: '+CAST(@i AS varchar(10))
SELECT @i = dbo.Mentor(@i)
PRINT 'Mentor: '+CAST(@i AS varchar(10))
SELECT @j = dbo.Mentor(@i)
WHILE (@i IS NOT NULL)
BEGIN
PRINT 'Employee: '+CAST(@i AS varchar(10))
SELECT @i = dbo.Mentor(@i)
PRINT 'Mentor: '+CAST(@i AS varchar(10))
SELECT @j = dbo.Mentor(dbo.Mentor(@j))
IF (@i=@j)
BEGIN
PRINT 'List is corrupt'
BREAK
END
END
The type of list corruption the challenge presented was a loop. Due to a bug in the app that updated the list when an employee left the company, a circular relationship was being introduced between an employee and his mentors. This caused ListMentors to find itself trapped in an infinite loop.
If there is indeed a circular relationship in the list, having two iterators that walk it at different speeds will cause them to collide at some point. Thus, the code prints a message and exits the loop when @i and @j equal one another.
You’ll recall that I made the challenge off-limits to MS people. In addition to the reason I stated, this is also because the puzzle is a variation on a question that’s commonly used in Microsoft interviews. If you search the net for “Microsoft interview questions” and “circular linked list,” you’ll find it. I explicitly avoided referring to the relationships as a “list” and the corruption as “circular” in the original post because I wanted to see whether anyone would figure out that the data structure being described was really a singly-linked list, and I wanted people to think a bit rather than merely grabbing an answer off the net.
Of course, the challenge for me in porting this puzzle to T-SQL is that SQL has the ability to “walk” the list without needing the references between the nodes. That’s not true of a linked list. Good ol’ SELECT, coupled with an aggregate or two, makes short work out of problems like this. That's why I made set-oriented approaches and variations on COUNT() off-limits.
As for who was first with a solution that met all the criteria, that would be Oleg K. Scan the comments for the challenge, and you’ll find his solution.
This is a good example of where knowledge of other languages and the common techniques used to solve problems with them comes in handy with T-SQL.
Exercise for the curious: If I do away with rules 3-5, what’s the shortest solution to the problem you can come up with? (Remember that you can use techniques specific to SQL Server 2005.)
Comments
- Anonymous
February 12, 2006
That's why I hate simple solutions :-)
They are so obvious that you miss them. - Anonymous
February 13, 2006
Out of curiosity, and while is wasn't the best solution, on what criteria did my solution fail on? It was recorded before Oleg K's solution. - Anonymous
February 13, 2006
The comment has been removed - Anonymous
February 13, 2006
Good puzzle. Funny how difficult it was to leave a set-based frame of mind.
And I learned an algorithm, thank you.
Appreciate your efforts!
rockmoose - Anonymous
February 13, 2006
Ouch, I didn't get that subtle hint :-( - Anonymous
February 15, 2006
The crow is crunchy, thank you very much. Dangit.
Now, being the sore loser that I am, let me tell you why I didn't see it: I took your "Your task is to change the proc's singleton SELECT code" too literally (key word: "change", not "add"). Of course, had I considered adding a second select, I would have come up with this solution in a heartbeat. cough cough No, really. cough :-)
Interview question you say? Hah, good thing I didn't waste anyone's time by applying for a job at Microsoft then. I'll just go back to my nice, pedestrian research of a nice, obscure SQL Server 2005 bug (and how to avoid it). Just in case someone else has run into it and has a workaround: how do you prevent SQL Server from hard dumps (which seem to hang themselves, leaving 10MB of dead SQLDumper and DW20 processes to bleed your server to death) on Updates that do not affect any records on certain tables (with computed columns, but that in and of itself does not seem to suffice) with a trigger on them that does a Cross Join between Inserted and Deleted? I'm finally getting around to it now that I fixed CHECKDB giving undocumented 3853 sql_dependencies errors (manually looking up the objects and recreating them), fun new parameter sniffing foibles, tightened rules on Order By clauses (okay, that's a good thing, but still, thanks for the warning)... and rewriting our automatic e-mail system to CDO (whose bright idea was it to allow installation and activation of SQL Mail on 64-bit SQL Server and then only when actually used finally taunt with a "SQL Mail doesn't work on 64-bit SQL Server"? Very cute.)
Sorry about the rant. I guess I'm just a bit grumpy. I did enjoy the puzzle; keep 'em coming. I'll get you next time. I'll even save you some crow! - Anonymous
February 15, 2006
The comment has been removed - Anonymous
February 16, 2006
Okay, here's a rough idea:
With Mentor(EmpID, Mentor)
As (Select E.EmpID, E.Mentor
From Employees As E
Where E.Mentor Is Null
Union All
Select E.EmpID, E.Mentor
From Employees E
Inner Join Mentor M
On E.Mentor = M.EmpID)
Select EmpID, Mentor
From Mentor
Only works up to 100 deep, and does not give a message on a loop -- it just stops.
Was that what you meant? - Anonymous
November 11, 2006
I wrote a circular reference prevention trigger that will check all the rows being inserted or updated to see if the Parent / Child relations do not loop or go deeper than 10 parent/children.
CREATE Trigger triu_Department_PreventCircularTree ON dbo.Department For Insert, Update As Declare @Count int Declare @ParentDepartmentID int Declare curUpdates Cursor For SELECT ParentDepartmentID FROM Department WHERE DepartmentID IN (SELECT DepartmentID FROM Inserted) For READ ONLY Open curUpdates Fetch Next From curUpdates INTO @ParentDepartmentID While @@Fetch_Status = 0 Begin SET @Count = 0 -- Go up through the Tree for a MAX of 10 deep ... if we exceed 10 then too much While (@ParentDepartmentID Is Not Null) AND (@Count < 10) Begin SET @Count = @Count + 1 SELECT @ParentDepartmentID = ParentDepartmentID FROM Department WHERE DepartmentID = @ParentDepartmentID End -- If we exceeded the limit then there is probably a circular reference If (@Count >= 10) Begin Raiserror('A Group can NOT be a Sub-Group of a Child.', 16, 1) Goto Cleanup End -- Get the Next Department record that was updated within this INSERT or UPDATE trigger Fetch Next From curUpdates INTO @ParentDepartmentID End Cleanup: Close curUpdates Deallocate curUpdates
Anonymous
May 30, 2008
The simplest rule-compliant solution to the employee-mentor challenge I recently posted is to change the loop such that it iterates through the relationships at more than one speed. Making no other changes to clean up the code, something like this woulAnonymous
June 05, 2008
The simplest rule-compliant solution to the employee-mentor challenge I recently posted is to change the loop such that it iterates through the relationships at more than one speed. Making no other changes to clean up the code, something like this woulAnonymous
June 01, 2009
PingBack from http://paidsurveyshub.info/story.php?id=77513Anonymous
June 09, 2009
PingBack from http://quickdietsite.info/story.php?id=4699Anonymous
June 16, 2009
PingBack from http://topalternativedating.info/story.php?id=4067