Search code examples
sqljoinsql-server-2012birt

SQL subquery based on row values with unrelated table


I need to get a count of records in an unrelated table, based on the row values in a query with some moderately complex joins. All data is on one server in a single SQL 2012 database, on several different tables.

I am recreating ticket movement history for a single ticket at a time, from audit records and need to calculate business days for the spans in rows created by the joins. Tickets are moved around between areas (ASSIGNMENT), and there are guidelines on how long it should be at any one area. The ticket may go to the same area multiple times with each time restarting the time count.

I need to consider company holidays in the business day calculations. After looking at several solutions for business day calculations on SE I decided to go with a company calendar table (dbo.UPMCCALENDARM1) and count the dates between spans. Seemed like a great idea...

I can't figure out how to use the row values as parameters for the date count query.

The query below has working solutions with a Variable and with a Cross Join, but it only works with hard coded dates, if I try to use the field values it does not work, because they are not part of the sub query and can not be bound.

-- between DV_im_Audit_ASSIGNMENT.Time and Detail.RESOLVED_TIME

In theory I could probably get there using this full query in the sub query to get the date count, but this is as short as I can make it and still get clean data. It is a pretty heavy lift for an on demand report, that would be my last option. So I want to reach out to UPMCCALENDARM1 as each occurrence of DV_im_Audit_ASSIGNMENT.Time and Detail.RESOLVED_TIME are listed.

Can it be done? If so how?

declare @NonBus integer 
set @NonBus = '0'
set @NonBus = (select Count(UPMCCALENDARM1.DATE) as NonBus
            from dbo.UPMCCALENDARM1
            where UPMC_BUSINESS_DAY = 'f'
            and UPMCCALENDARM1.DATE 
            between '2015-08-01' and '2015-08-31'
--          between DV_im_Audit_ASSIGNMENT.Time and Detail.RESOLVED_TIME
            )

select DV_im_Audit_ASSIGNMENT.Incident_ID
, DV_im_Audit_ASSIGNMENT.Old_ASSIGNMENT
, DV_im_Audit_ASSIGNMENT.New_ASSIGNMENT
, DV_im_Audit_ASSIGNMENT.Time as Assign_Time
, B.Time as Reassign_Time
, Detail.OPEN_TIME
, Cal.NonBus
, NonBus
, Detail.RESOLVED_TIME
, A.rownumA
, B.rownumB

from dbo.DV_im_Audit_ASSIGNMENT

--Get RownumA as a select join so I can work with it here, else get an invalid column name 'rownumA' error
left join(select Incident_ID
        , Old_ASSIGNMENT
        , New_ASSIGNMENT
        , [Time]
        , rownumA = ROW_NUMBER() OVER (ORDER BY  DV_im_Audit_ASSIGNMENT.Incident_ID, DV_im_Audit_ASSIGNMENT.Time)
        from dbo.DV_im_Audit_ASSIGNMENT
        where Incident_ID = ?
        ) as A
            on DV_im_Audit_ASSIGNMENT.Incident_ID = A.Incident_ID
            and DV_im_Audit_ASSIGNMENT.New_ASSIGNMENT = A.New_ASSIGNMENT
            and DV_im_Audit_ASSIGNMENT.Time = A.Time 

--Get time assigned to next group, is problomatic when assigned to the same group multiple times.
left join(select Incident_ID
        , Old_ASSIGNMENT
        , New_ASSIGNMENT
        , [Time]
        , rownumB = ROW_NUMBER() OVER (ORDER BY  DV_im_Audit_ASSIGNMENT.Incident_ID, DV_im_Audit_ASSIGNMENT.Time)
        from dbo.DV_im_Audit_ASSIGNMENT
        where Incident_ID = ?
        ) as B
            on DV_im_Audit_ASSIGNMENT.Incident_ID = B.Incident_ID
            and DV_im_Audit_ASSIGNMENT.New_ASSIGNMENT = B.Old_ASSIGNMENT
            and DV_im_Audit_ASSIGNMENT.Time < B.Time 
        and rownumA = (B.rownumB - 1)  

--Get current ticket info
left join (select Incident_ID
        , OPEN_TIME
        , RESOLVED_TIME
        from dbo.DV_im_PROBSUMMARYM1_Detail
        where Incident_ID = ?
        ) as Detail 
    on DV_im_Audit_ASSIGNMENT.Incident_ID = Detail.Incident_ID


--Count non-bussiness days.  This section is in testing and does not use dataview as a source.
-- this gets the date count for one group of dates, need a different count for each row based on assign time. 
cross join (Select Count(UPMCCALENDARM1.DATE) as NonBus
            from dbo.UPMCCALENDARM1
            where UPMC_BUSINESS_DAY = 'f'
            and UPMCCALENDARM1.DATE 
            between '2015-08-01' and '2015-08-30'
--          between DV_im_Audit_ASSIGNMENT.Time and Detail.RESOLVED_TIME
            ) as Cal


--Get data for one ticket
where DV_im_Audit_ASSIGNMENT.Incident_ID = ?

ORDER BY  DV_im_Audit_ASSIGNMENT.Incident_ID, DV_im_Audit_ASSIGNMENT.Time

Results enter image description here

FYI - I am running this SQL through BIRT 4.2, I believe there are few SQL items that will not pass through BIRT


Solution

  • Following the suggestion by @Dominique I created a custom scalar function (using the wizard in SSMS), I used default values for the dates as I had started by playing with stored procedure and that made it easier to test. This problem requires a function as it will return a value per row, where a stored procedure will not.

    SET ANSI_NULLS ON
    GO
    SET QUOTED_IDENTIFIER ON
    GO
    -- =============================================
    -- Author:      James Jenkins
    -- Create date: September 2015
    -- Description: Counts Business Days for UPMC during a span of dates
    -- =============================================
    CREATE FUNCTION dbo.UPMCBusinessDayCount 
    (
        -- Add the parameters for the function here
        @StartDate date = '2015-08-01',
        @EndDate date = '2015-08-31'
    )
    RETURNS int
    AS
    BEGIN
        -- Declare the return variable here
        DECLARE @BusDay int
    
        -- Add the T-SQL statements to compute the return value here
        SELECT @BusDay = Count(UPMCCALENDARM1.DATE) 
            from dbo.UPMCCALENDARM1
            where UPMC_BUSINESS_DAY = 't'
            and UPMCCALENDARM1.DATE between @StartDate and @EndDate
    
        -- Return the result of the function
        RETURN @BusDay
    
    END
    GO
    

    After the function is created in the database I added these two lines to my select statement, and it works perfectly.

    --Custom function counts business days on UPMCCALENDARM1
    , dbo.UPMCBusinessDayCount(DV_im_Audit_ASSIGNMENT.Time, Detail.RESOLVED_TIME) as BusDay
    

    I can use this function for any span that has date data in this (or any query on the database). I will probably be removing the default dates as well as adding a third parameter to count non-business days (UPMC_BUSINESS_DAY = 'f'). But as it is the problem is solved.