views:

204

answers:

2

This is a follow-up to #1644748 where I successfully answered my own question, but Quassnoi helped me to realize that it was the wrong question. He gave me a solution that worked for my sample data, but I couldn't plug it back into the parent stored procedure because I fail at SQL 2005 syntax. So here is an attempt to paint the broader picture and ask what I actually need.

This is part of a stored procedure that returns a list of items in a bug tracking application I've inherited. There are are over 100 fields and 26 joins so I'm pulling out only the mostly relevant bits.

SELECT
 tickets.ticketid, 
 tickets.tickettype, 
 tickets_tickettype_lu.tickettypedesc,
 tickets.stage, 
 tickets.position,
 tickets.sponsor,
 tickets.dev,
 tickets.qa,
 DATEDIFF(DAY, ticket_history_assignment.savedate, GETDATE()) as 'daysinqueue'
FROM
 dbo.tickets WITH (NOLOCK)
 LEFT OUTER JOIN dbo.tickets_tickettype_lu WITH (NOLOCK) ON tickets.tickettype = tickets_tickettype_lu.tickettypeid
 LEFT OUTER JOIN dbo.tickets_history_assignment WITH (NOLOCK) ON tickets_history_assignment.ticketid = tickets.ticketid
 AND tickets_history_assignment.historyid = (
 SELECT
  MAX(historyid)
 FROM
  dbo.tickets_history_assignment WITH (NOLOCK)
 WHERE
  tickets_history_assignment.ticketid = tickets.ticketid
 GROUP BY
  tickets_history_assignment.ticketid
 )
WHERE
 tickets.sponsor = @sponsor

The area of interest is the daysinqueue subquery mess. The tickets_history_assignment table looks roughly as follows

declare @tickets_history_assignment table (
 historyid int,
 ticketid int,
 sponsor int,
 dev int,
 qa int,
 savedate datetime
)

insert into @tickets_history_assignment values (1521402, 92774,20,14, 20, '2009-10-27 09:17:59.527')
insert into @tickets_history_assignment values (1521399, 92774,20,14, 42, '2009-08-31 12:07:52.917')
insert into @tickets_history_assignment values (1521311, 92774,100,14, 42, '2008-12-08 16:15:49.887')
insert into @tickets_history_assignment values (1521336, 92774,100,14, 42, '2009-01-16 14:27:43.577')

Whenever a ticket is saved, the current values for sponsor, dev and qa are stored in the tickets_history_assignment table with the ticketid and a timestamp. So it is possible for someone to change the value for qa, but leave sponsor alone.

What I want to know, based on all of these conditions, is the historyid of the record in the tickets_history_assignment table where the sponsor value was last changed so that I can calculate the value for daysinqueue. If a record is inserted into the history table, and only the qa value has changed, I don't want that record. So simply relying on MAX(historyid) won't work for me.

Quassnoi came up with the following which seemed to work with my sample data, but I can't plug it into the larger query, SQL Manager bitches about the WITH statement.

;WITH    rows AS
        (
        SELECT  *, ROW_NUMBER() OVER (PARTITION BY ticketid ORDER BY savedate DESC) AS rn
        FROM    @Table
        )
SELECT  rl.sponsor, ro.savedate
FROM    rows rl
CROSS APPLY
        (
        SELECT  TOP 1 rc.savedate
        FROM    rows rc
        JOIN    rows rn
        ON      rn.ticketid = rc.ticketid
                AND rn.rn = rc.rn + 1
                AND rn.sponsor <> rc.sponsor
        WHERE   rc.ticketid = rl.ticketid
        ORDER BY
                rc.rn
        ) ro
WHERE   rl.rn = 1

I played with it yesterday afternoon and got nowhere because I don't fundamentally understand what is going on here and how it should fit into the larger context.

So, any takers?

UPDATE

Ok, here's the whole thing. I've been switching some of the table and column names in an attempt to simplify things so here's the full unedited mess.

snip - old bad code

Here are the errors:

Msg 102, Level 15, State 1, Procedure usp_GetProjectRecordsByAssignment, Line 159
Incorrect syntax near ';'.
Msg 102, Level 15, State 1, Procedure usp_GetProjectRecordsByAssignment, Line 179
Incorrect syntax near ')'.

Line numbers are of course not correct but refer to

;WITH    rows AS

And the ')' char after the WHERE rl.rn = 1

)

Respectively

Is there a tag for extra super long question?

UPDATE #2

Here is the finished query for anyone who may need this:

CREATE PROCEDURE [dbo].[usp_GetProjectRecordsByAssignment]
(
    @assigned numeric(18,0),
    @assignedtype numeric(18,0)
)
AS

SET NOCOUNT ON

WITH    rows AS
        (
        SELECT  *, ROW_NUMBER() OVER (PARTITION BY recordid ORDER BY savedate DESC) AS rn
        FROM    projects_history_assignment
        )
SELECT  projects_records.recordid, 
        projects_records.recordtype, 
        projects_recordtype_lu.recordtypedesc,
     projects_records.stage, 
     projects_stage_lu.stagedesc,
     projects_records.position, 
     projects_position_lu.positiondesc, 
     CASE projects_records.clientrequested
      WHEN '1' THEN 'Yes'
      WHEN '0' THEN 'No'
     END AS clientrequested, 
     projects_records.reportingmethod, 
     projects_reportingmethod_lu.reportingmethoddesc, 
     projects_records.clientaccess,  
     projects_clientaccess_lu.clientaccessdesc,
     projects_records.clientnumber,  
     projects_records.project,  
     projects_lu.projectdesc,  
     projects_records.version,  
     projects_version_lu.versiondesc,
     projects_records.projectedversion,
     projects_version_lu_projected.versiondesc AS projectedversiondesc,
     projects_records.sitetype,  
     projects_sitetype_lu.sitetypedesc,  
     projects_records.title,  
     projects_records.module,  
     projects_module_lu.moduledesc,  
     projects_records.component,  
     projects_component_lu.componentdesc,  
     projects_records.loginusername,  
     projects_records.loginpassword,  
     projects_records.assistedusername,  
     projects_records.browsername,  
     projects_browsername_lu.browsernamedesc,  
     projects_records.browserversion,  
     projects_records.osname,  
     projects_osname_lu.osnamedesc,  
     projects_records.osversion,  
     projects_records.errortype,  
     projects_errortype_lu.errortypedesc,  
     projects_records.gsipriority,  
     projects_gsipriority_lu.gsiprioritydesc,  
     projects_records.clientpriority,  
     projects_clientpriority_lu.clientprioritydesc,  
     projects_records.scheduledstartdate,
     projects_records.scheduledcompletiondate,  
     projects_records.projectedhours,  
     projects_records.actualstartdate,
     projects_records.actualcompletiondate,  
     projects_records.actualhours,  
     CASE projects_records.billclient
      WHEN '1' THEN 'Yes'
      WHEN '0' THEN 'No'
     END AS billclient,  
     projects_records.billamount,  
     projects_records.status,  
     projects_status_lu.statusdesc,
     CASE CAST(projects_records.assigned AS VARCHAR(5))
      WHEN '0' THEN 'N/A'
      WHEN '10000' THEN 'Unassigned'
      WHEN '20000' THEN 'Client'
      WHEN '30000' THEN 'Tech Support'
      WHEN '40000' THEN 'LMI Tech Support'
      WHEN '50000' THEN 'Upload'
      WHEN '60000' THEN 'Spider'
      WHEN '70000' THEN 'DB Admin'
      ELSE rtrim(users_assigned.nickname) + ' ' + rtrim(users_assigned.lastname)
     END AS assigned,
     CASE CAST(projects_records.assigneddev AS VARCHAR(5))
      WHEN '0' THEN 'N/A'
      WHEN '10000' THEN 'Unassigned'
      ELSE rtrim(users_assigneddev.nickname) + ' ' + rtrim(users_assigneddev.lastname)
     END AS assigneddev,
     CASE CAST(projects_records.assignedqa AS VARCHAR(5))
      WHEN '0' THEN 'N/A'
      WHEN '10000' THEN 'Unassigned'
      ELSE rtrim(users_assignedqa.nickname) + ' ' + rtrim(users_assignedqa.lastname)
     END AS assignedqa,
     CASE CAST(projects_records.assignedsponsor AS VARCHAR(5))
      WHEN '0' THEN 'N/A'
      WHEN '10000' THEN 'Unassigned'
      ELSE rtrim(users_assignedsponsor.nickname) + ' ' + rtrim(users_assignedsponsor.lastname)
     END AS assignedsponsor,
     projects_records.clientcreated,  
     CASE projects_records.clientcreated
      WHEN '1' THEN 'Yes'
      WHEN '0' THEN 'No'
     END AS clientcreateddesc,
     CASE projects_records.clientcreated
      WHEN '1' THEN rtrim(clientusers_createuser.firstname) + ' ' + rtrim(clientusers_createuser.lastname) + ' (Client)'
      ELSE rtrim(users_createuser.nickname) + ' ' + rtrim(users_createuser.lastname)
     END AS createuser,
     projects_records.createdate,  
     projects_records.savedate,
     projects_resolution.sitesaffected,  
     projects_sitesaffected_lu.sitesaffecteddesc,
     DATEDIFF(DAY, projects_history_assignment.savedate, GETDATE()) as 'daysinqueue',
     projects_records.iOnHitList,
     projects_records.changetype
FROM
    dbo.projects_records WITH (NOLOCK)
    LEFT OUTER JOIN dbo.projects_recordtype_lu WITH (NOLOCK) ON projects_records.recordtype = projects_recordtype_lu.recordtypeid
    LEFT OUTER JOIN dbo.projects_stage_lu WITH (NOLOCK) ON projects_records.stage = projects_stage_lu.stageid
    LEFT OUTER JOIN dbo.projects_position_lu WITH (NOLOCK) ON projects_records.position = projects_position_lu.positionid
    LEFT OUTER JOIN dbo.projects_reportingmethod_lu WITH (NOLOCK) ON projects_records.reportingmethod = projects_reportingmethod_lu.reportingmethodid
    LEFT OUTER JOIN dbo.projects_lu WITH (NOLOCK) ON projects_records.project = projects_lu.projectid
    LEFT OUTER JOIN dbo.projects_version_lu WITH (NOLOCK) ON projects_records.version = projects_version_lu.versionid
    LEFT OUTER JOIN dbo.projects_version_lu projects_version_lu_projected WITH (NOLOCK) ON projects_records.projectedversion = projects_version_lu_projected.versionid
    LEFT OUTER JOIN dbo.projects_sitetype_lu WITH (NOLOCK) ON projects_records.sitetype = projects_sitetype_lu.sitetypeid
    LEFT OUTER JOIN dbo.projects_module_lu WITH (NOLOCK) ON projects_records.module = projects_module_lu.moduleid
    LEFT OUTER JOIN dbo.projects_component_lu WITH (NOLOCK) ON projects_records.component = projects_component_lu.componentid
    LEFT OUTER JOIN dbo.projects_browsername_lu WITH (NOLOCK) ON projects_records.browsername = projects_browsername_lu.browsernameid
    LEFT OUTER JOIN dbo.projects_osname_lu WITH (NOLOCK) ON projects_records.osname = projects_osname_lu.osnameid
    LEFT OUTER JOIN dbo.projects_errortype_lu WITH (NOLOCK) ON projects_records.errortype = projects_errortype_lu.errortypeid
    LEFT OUTER JOIN dbo.projects_resolution WITH (NOLOCK) ON projects_records.recordid = projects_resolution.recordid
    LEFT OUTER JOIN dbo.projects_sitesaffected_lu WITH (NOLOCK) ON projects_resolution.sitesaffected = projects_sitesaffected_lu.sitesaffectedid
    LEFT OUTER JOIN dbo.projects_gsipriority_lu WITH (NOLOCK) ON projects_records.gsipriority = projects_gsipriority_lu.gsipriorityid
    LEFT OUTER JOIN dbo.projects_clientpriority_lu WITH (NOLOCK) ON projects_records.clientpriority = projects_clientpriority_lu.clientpriorityid
    LEFT OUTER JOIN dbo.projects_status_lu WITH (NOLOCK) ON projects_records.status = projects_status_lu.statusid
    LEFT OUTER JOIN dbo.projects_clientaccess_lu WITH (NOLOCK) ON projects_records.clientaccess = projects_clientaccess_lu.clientaccessid
    LEFT OUTER JOIN dbo.users users_assigned WITH (NOLOCK) ON projects_records.assigned = users_assigned.userid
    LEFT OUTER JOIN dbo.users users_assigneddev WITH (NOLOCK) ON projects_records.assigneddev = users_assigneddev.userid
    LEFT OUTER JOIN dbo.users users_assignedqa WITH (NOLOCK) ON projects_records.assignedqa = users_assignedqa.userid
    LEFT OUTER JOIN dbo.users users_assignedsponsor WITH (NOLOCK) ON projects_records.assignedsponsor = users_assignedsponsor.userid
    LEFT OUTER JOIN dbo.users users_createuser WITH (NOLOCK) ON projects_records.createuser = users_createuser.userid
    LEFT OUTER JOIN dbo.clientusers clientusers_createuser WITH (NOLOCK) ON projects_records.createuser = clientusers_createuser.userid
    LEFT OUTER JOIN dbo.projects_history_assignment WITH (NOLOCK) ON projects_history_assignment.recordid = projects_records.recordid
    AND projects_history_assignment.historyid = (
     SELECT  ro.historyid
     FROM    rows rl
     CROSS APPLY
       (
       SELECT  TOP 1 rc.historyid
       FROM    rows rc
       JOIN    rows rn
       ON      rn.recordid = rc.recordid
         AND rn.rn = rc.rn + 1
         AND rn.assigned <> rc.assigned
       WHERE   rc.recordid = rl.recordid
       ORDER BY
         rc.rn
       ) ro
     WHERE   rl.rn = 1
       AND rl.recordid = projects_records.recordid
    )   

WHERE
    (@assignedtype='0' and projects_records.assigned = @assigned)
OR  (@assignedtype='1' and projects_records.assigneddev = @assigned)
OR  (@assignedtype='2' and projects_records.assignedqa = @assigned)
OR  (@assignedtype='3' and projects_records.assignedsponsor = @assigned)
OR  (@assignedtype='4' and projects_records.createuser = @assigned)
A: 

Well, if that query does not work out -- you could always consider a trigger on the tickets_history_assignment table. You could use INSTEAD OF INSERT, UPDATE which fires before any changes are made, so you would have full control of what is about to change and what to do.

Damir Sudarevic
A: 
WITH    rows AS
        (
        SELECT  *, ROW_NUMBER() OVER (PARTITION BY ticketid ORDER BY savedate DESC) AS rn
        FROM    @Table
        )
SELECT  projects_records.recordid, 
        projects_records.recordtype, 
        /* skipped */ 
        AND projects_history_assignment.historyid = (
        SELECT  ro.historyid
        FROM    rows rl
        CROSS APPLY
                (
                SELECT  TOP 1 rc.savedate
                FROM    rows rc
                JOIN    rows rn
                ON      rn.recordid = rc.recordid
                        AND rn.rn = rc.rn + 1
                        AND rn.assigned <> rc.assigned
                WHERE   rc.recordid = rl.recordid
                ORDER BY
                        rc.rn
                ) ro
        WHERE   rl.rn = 1
                AND rl.recordid = projects_records.recordid
        )
Quassnoi
Ok, got it to work, but it now takes 25 secs to run the query as opposed to .2 before. I guess I'll try to think of another way of storing this data. Maybe I'll add another bit column for whether not the assigned user is getting changed or something. Thanks for all of your help and sorry for your trouble.
Dzejms
`@Dzejms`: The subquery here is correlated and is reevaluated for each row, that's why it takes so long. You need to replace it with a JOIN.
Quassnoi
I don't know what which subquery to replace with a join or how I would accomplish this. Are you referring to teh `CROSS APPLY` section?
Dzejms
Followup time! Since I couldn't figure out how to this via SQL alone, I stopped looking at the problem with a hammer and grabbed a wrench. I decided to fire off separate queries per recordid for the assigned history. I enumerate through the recordset in the application layer and get the appropriate date difference. I was looking at using a cursor in SQL then I realized that I could do the same thing in the app code. I'm pulling more data over the wire, but it's an Intranet app so I have that luxury.Stepping away for a few days to get fresh perspective FTW!
Dzejms