简体   繁体   中英

Complex Iterating Query in SSRS 2008

After originally composing several reports by hand as part of an MVC 3 (EF4) project for a customer, I have decided it is far easier to compose reports in Microsoft reporting services (SSRS 2008 on SQL server 2008). For most of my reports this has proven to be a breeze, not even having to write SQL procs for most of my reports (semantic query in BIDS based on model).

However one report remains daunting due to the nature of its input parameters and originally being computed in a function returning a list, not a LINQ query. The goal is to return all people in the people table who are less than x percent busy on any day in the range between date a and date b (variables input by report requester). This busy level information only exists in the Job table which has information on which person it is assigned to, the start and end dates of the job, and its task level. As a result, my original C# function does pretty much the following (psuedo-code):

given startDateRange a, stopDateRange b, busyLevel x
{
for each person in people:
   List returnPeople = new List()
   create array consisting of days from a to b
   for each job in jobs:
      for each intersecting date of DateRange and job dateRange:
         add task level of job to array
   for each day in array:
      if array[day] < x
         returnPeople.Add(person)
         break;
return returnPeople
}

Table structure for relevant pieces:

People Table:
-PersonID (PK)
-other stuff...

Jobs Table:
-JobID (PK)
-StartDate
-StopDate
-Allocation
-AssignedPersonID (FK to People Table)

As you can see, a job is on someones schedule if that job's AssignedPersonID is = that someone

So... since I'm now working in SSRS 2008, I am looking for guidance on how to get these same results in SSRS from the same input parameters. Some trick to semantic queries that I am currently unaware of? SQL stored proc with temp table or table variable? Some sort of data processing extension? Any information on what the best approach to this type of problem is would be much appreciated.

Let me know if additional information/code is required.

EDIT: Currently working on a solution using cursors and temp tables that pretty much copies the structure of my original function. This is DEFINITELY not ideal however due to the complexity and inefficient nature of cursors, so advice/alternatives would be helpful.

EDIT: Found a solution, definitely not the most efficient. Will change accepted answer if someone has a significantly faster method.

Sorry that it took a while to get back to you. I don't think there's a way to get around having to loop through the days with a WHILE loop, since that gives you individual days needed to compare to your users' input range. I think this query should help somewhat for speed though, since it doesn't use any cursors. I added the logic within the WHILE loop, so you only loop one time through, and you have your temp table as a result. The variable names might be a little different, and you might switch a <= to < or something, but the main logic is there.

The query includes the start date and end date, and is set to include anyone with an allocation less than or equal to the users' input. I hope it helps you:

DECLARE @startDate datetime, @endDate datetime, @maxAllocation float, @dayCounter datetime

DECLARE @PeopleList TABLE (PersonId int, PersonName varchar(50))

SET @dayCounter = @startDate
WHILE @dayCounter <= @endDate
BEGIN
    INSERT INTO @PeopleList SELECT P.PersonId, P.PersonName FROM People P INNER JOIN Jobs J ON P.PersonId = J.AssignedPersonId
       WHERE J.StartDate <= @dayCounter AND J.StopDate >= @dayCounter
       GROUP BY P.PersonId, P.PersonName
       HAVING SUM(J.Allocation) <= @maxAllocation
    SET @dayCounter = DATEADD(DAY, 1, @dayCounter)
END

SELECT DISTINCT PersonId, PersonName FROM @PeopleList

your problem is not related to SSRS. You just need to build the query to feed the data set on SSRS. I doesnt mattet how the data is reaching the data source, query, procedure, temp table, magic, as far as the report is concern, it just care about field names.

So if you cant transform your linq query to a T-SQL query, I suggest that you run a trace on your database, execute the linq query from your application (you then will see the T-SQL on the trace), copy it and paste on the datasource on your report.

Ended up using a TSQL stored proc, with a combination of temp tables and nested cursors. Most likely not the most efficient solution, but hopefully it will help others with the same problem. I will change the accepted answer if someone posts a more efficient solution, as that is what I was originally asking for.

 DECLARE @PeopleList TABLE(
                                          PersonID CHAR(15)
                                          )
      DECLARE @PersonValue CHAR(10)
      --Local busyList table with all dates in desired range
            DECLARE @DateValue DATETIME
            DECLARE @TotalAllocation FLOAT
            DECLARE @BusyList TABLE(
                                                DateInRange DATETIME,
                                                AllocationForDate FLOAT
                                                )
            DECLARE @dayCounter DATETIME
            SET @dayCounter = @startDate
            INSERT INTO @BusyList VALUES (@startDate, 0)
            WHILE @dayCounter<@endDate
            BEGIN
                  SET @dayCounter = @dayCounter + 1
                  INSERT INTO @BusyList VALUES (@dayCounter, 0)
            END

      DECLARE currentUser CURSOR LOCAL FAST_FORWARD FOR
      SELECT PersonID FROM [People]

      OPEN currentPerson
      FETCH NEXT FROM currentPerson INTO @PersonValue
      WHILE (@@FETCH_STATUS = 0)
            BEGIN
            DECLARE currentDay CURSOR LOCAL FAST_FORWARD FOR
            SELECT DateInRange FROM @BusyList

            OPEN currentDay
            SET @TotalAllocation = NULL
            UPDATE @BusyList SET AllocationForDate = 0
            FETCH NEXT FROM currentDay INTO @DateValue
            WHILE (@@FETCH_STATUS = 0)
                  BEGIN
                        SET @TotalAllocation = (SELECT SUM(TaskLevel) FROM Job WHERE AssignedPersonID = @PersonValue AND @DateValue BETWEEN Opportunity.StartDate AND Job.EndDate)
                        UPDATE @BusyList
                        SET AllocationForDate = @TotalAllocation WHERE (DateInRange = @DateValue)
                        FETCH NEXT FROM currentDay INTO @DateValue
                  END
            CLOSE currentDay
            DEALLOCATE currentDay
            IF EXISTS(SELECT * FROM @BusyList WHERE AllocationForDate <= @allocation OR AllocationForDate IS NULL)
                BEGIN
                INSERT INTO @PersonList VALUES (@PersonValue)
                END
            PRINT @PersonValue
            FETCH NEXT FROM currentUser INTO @PersonValue
            END

      SELECT * FROM [People] WHERE EXISTS (SELECT * FROM @PersonList Where [@PersonList].PersonID = [Person].PersonID)

      CLOSE currentPerson
      DEALLOCATE currentPerson

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM