开发者

Accruing over time (non-overlapping) - technique?

I'm trying to find a better way of doing a Crystal Report (Someone Else's)... Add up non-overlapping time in groups. This is evidently an age-old problem... Is there a technique of getting

  • Adjusted (start/end) times, per record, to remove common/over-lap time,
  • within subgroups
  • --using straight SQL (although I find I can do CTEs)

Assume initial order-by for Start Time (and/or Group, SubGroup) and Start and End are separate fields.

A kind-a graphic example:

Group 1
  SubGroup A
    Tkt 1    |--start&end---|                      "16"
    Tkt 2        |----tart&end----|                "18"
    Tkt 3          |--art&end---|                  "14"
    Tkt 4                            |--S & E -|   "11"

  SubGroup B
    Tkt 5  |-S&E-|                                  "7" 
    Tkt 6          |-S&E-|                          "7" 
    Tkt 7            |-S&E-|                        "7"  
    ...

(equiv adjusted start/end w/in sub-group):
Group 1
  SubGroup A                         (  w/ "elapsed time" of "33"   )
    Tkt 1    |--start&end---|         <- Regular             "16"
    Tkt 2                   |-----|   <- Adjusted "start"     "6"
    Tkt 3                         |   <- Adjusted "start" & "end"   "0"
    Tkt 4                            |--S & E -|  <- Regular "11"

  SubGroup B                         ( w/ "elapsed time"  of "17"   )
    Tkt 5  |-S&E-|                    <- Regular              "7"  
    Tkt 6          |-S&E-|            <- Regular (no overlap) "7"
    Tkt 7                |-|          <- Adjusted "Start"     "3"
    ...

I'm not talking about getting sums at this point, just illustrating adjusting start/end based on the previous record.

In the report, they are doing multiple formulas for each record, against two variables which are set on first record in group, then setting/updating values for AdjustedStart, AdjustedEnd based on current record, and passing the AdjustedStart开发者_开发问答, AdjustedEnd out to another formula to calculate the time difference, to be summed later. The current technique is slow, and I can't do a nifty cross-tab that is desired.

I am thinking/hoping that this has been solved in SQL-only, as we can't put any proc's, temp tables, etc. on the DB server. I'm trying to figure out a way w/ CTE and (re-)cursing, using the Group/SubGroup as parentage, and CASE to compare current values w/ last-parent values. Does this ring a bell or sound plausible?

The abilities of Crystal are many, but this seems like one that it's not cut out for.

Marc


I'm doing this off the top of my head...

SELECT
    CUR.subgroup,
    CUR.ticket,
    COALESCE(MAX(PARENT.end_time), CUR.start_time) AS start_time,
    CASE
        WHEN CUR.end_time < MAX(PARENT.end_time) THEN MAX(PARENT.end_time)
        ELSE CUR.end_time
    END
FROM
    My_Table CUR
LEFT OUTER JOIN My_Table PARENT ON
    PARENT.start_time <= CUR.start_time AND
    PARENT.end_time > CUR.start_time AND
    PARENT.ticket <> CUR.ticket AND
    PARENT.subgroup = CUR.subgroup
GROUP BY
    CUR.subgroup,
    CUR.ticket,
    CUR.start_time,
    CUR.end_time


I appreciate you asked this question a long time ago but it interested me, so I did some research and found a solution by Jeff Moden; he wrote an article about grouping islands of dates which you can find here: Group Islands of Contiguous Dates (login required but free to register).

I have assumed you have a table with rows for each day in a sub group, so there are 16 rows for "Tkt1", 18 for "Tkt2" etc. If that is not the case, and you only have start and end dates for each "Tkt", you will have to use a Calendar table to extrapolate the rows for each range.

Jeff's solution uses a ROW_NUMBER and DATEDIFF trick to group islands of dates.

WITH Grouped_Dates AS
( -- Find the unique dates and assign them to a group.
  -- The group looks like a date but the date means nothing except that adjacent
  -- dates will be a part of the same group.
 SELECT group_name,
        unique_date = tkt_date,
        date_group  = DATEADD(dd, -ROW_NUMBER() OVER (PARTITION BY group_name ORDER BY group_name, tkt_date), tkt_date)
  FROM t
  GROUP BY group_name, tkt_date
)
-- Now, if we find the MIN and MAX date for each date_group, we'll have the
-- Start and End dates of each group of contiguous daes.  While we're at it,
-- we can also figure out how many days are in each range of days.
SELECT group_name,
       start_date = MIN(unique_date),
       end_date   = MAX(unique_date),
       days       = DATEDIFF(dd,MIN(unique_date),MAX(unique_date))+1
FROM Grouped_Dates
GROUP BY group_name, date_group
ORDER BY group_name, start_date

The result of that query is

group_name  start_date  end_date    days
----------  ----------  ----------  ----
Group1      2012-01-01  2012-01-22    22
Group1      2012-01-24  2012-02-03    11
Group2      2012-01-09  2012-01-15     7
Group2      2012-01-18  2012-01-27    10

I've created a SQL Fiddle with sample data based on your question.

You can then sum each Group to give the grand total time spent.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜