Группировка между двумя датами - PullRequest
0 голосов
/ 26 мая 2020

У меня есть несколько производственных заказов, и я пытаюсь сгруппировать их в пределах диапазона дат и времени, а затем подсчитать количество в этом диапазоне. Например, я хочу группироваться с 22:30 до 22:30 каждый день. PT.ActualFini sh - это datetime (например, если PT.ActualFini sh - 2020-05-25 23:52:30, то он будет засчитан 26 мая, а не 25-го)

В настоящее время он сгруппирован по дате (от полуночи до полуночи), а не по желаемому с 22:30 до 22:30.

GROUP BY CAST(PT.ActualFinish AS DATE)

Я безуспешно пытался согласовать DATEADD с GROUP. Возможно ли это?

Ответы [ 2 ]

1 голос
/ 26 мая 2020

Для такого рода вещей вы можете использовать созданную мной функцию NGroupRangeAB (код ниже), которую можно использовать для создания групп по значениям с верхней и нижней границей.

Обратите внимание, что это:

SELECT   f.*
FROM     core.NGroupRangeAB(0,1440,12) AS f
ORDER BY f.RN;

Возвращает:

RN  GroupNumber  Low    High
--- ------------ ------ -------
0   1            0      120
1   2            121    240
2   3            241    360
3   4            361    480
4   5            481    600
5   6            601    720
6   7            721    840
7   8            841    960
8   9            961    1080
9   10           1081   1200
10  11           1201   1320
11  12           1321   1440

Это:

SELECT   
  f.GroupNumber,
  L = DATEADD(MINUTE,f.[Low]-SIGN(f.[Low]),CAST('00:00:00.0000000' AS TIME)),
  H = DATEADD(MINUTE,f.[High]-1,CAST('00:00:00.0000000' AS TIME))
FROM     core.NGroupRangeAB(0,1440,12) AS f
ORDER BY f.RN;

Возвраты:

GroupNumber   L                H
------------- ---------------- ----------------
1             00:00:00.0000000 01:59:00.0000000
2             02:00:00.0000000 03:59:00.0000000
3             04:00:00.0000000 05:59:00.0000000
4             06:00:00.0000000 07:59:00.0000000
5             08:00:00.0000000 09:59:00.0000000
6             10:00:00.0000000 11:59:00.0000000
7             12:00:00.0000000 13:59:00.0000000
8             14:00:00.0000000 15:59:00.0000000
9             16:00:00.0000000 17:59:00.0000000
10            18:00:00.0000000 19:59:00.0000000
11            20:00:00.0000000 21:59:00.0000000
12            22:00:00.0000000 23:59:00.0000000

Теперь пример из реальной жизни, который может вам помочь:

-- Sample Date
DECLARE @table TABLE (tm TIME);
INSERT @table VALUES ('00:15'),('11:20'),('21:44'),('09:50'),('02:15'),('02:25'),
                     ('02:31'),('23:31'),('23:54');
-- Solution:
SELECT
  GroupNbr = f.GroupNumber,
  TimeLow  = f2.L,
  TimeHigh = f2.H,
  Total    = COUNT(t.tm)
FROM        core.NGroupRangeAB(0,1440,12) AS f
CROSS APPLY (VALUES(
  DATEADD(MINUTE,f.[Low]-SIGN(f.[Low]),CAST('00:00:00.0000000' AS TIME)),
  DATEADD(MINUTE,f.[High]-1,CAST('00:00:00.0000000' AS TIME)))) AS f2(L,H)
LEFT JOIN   @table AS t
  ON        t.tm BETWEEN f2.L AND f2.H
GROUP BY    f.GroupNumber, f2.L, f2.H;

Возвраты:

GroupNbr             TimeLow          TimeHigh         Total
-------------------- ---------------- ---------------- -----------
1                    00:00:00.0000000 01:59:00.0000000 1
2                    02:00:00.0000000 03:59:00.0000000 3
3                    04:00:00.0000000 05:59:00.0000000 0
4                    06:00:00.0000000 07:59:00.0000000 0
5                    08:00:00.0000000 09:59:00.0000000 1
6                    10:00:00.0000000 11:59:00.0000000 1
7                    12:00:00.0000000 13:59:00.0000000 0
8                    14:00:00.0000000 15:59:00.0000000 0
9                    16:00:00.0000000 17:59:00.0000000 0
10                   18:00:00.0000000 19:59:00.0000000 0
11                   20:00:00.0000000 21:59:00.0000000 1
12                   22:00:00.0000000 23:59:00.0000000 2

Обратите внимание, что внутреннее соединение удалит строки с нулевым счетчиком.

CREATE FUNCTION core.NGroupRangeAB
(
  @min    BIGINT, -- Group Number Lower boundary
  @max    BIGINT, -- Group Number Upper boundary
  @groups BIGINT  -- Number of groups required
)
/*****************************************************************************************
[Purpose]:
 Creates an auxilliary table that allows for grouping based on a given set of rows (@rows)
 and requested number of "row groups" (@groups). core.NGroupRangeAB can be thought of as a
 set-based, T-SQL version of Oracle's WIDTH_BUCKET, which:

 "...lets you construct equiwidth histograms, in which the histogram range is divided into
  intervals that have identical size. (Compare with NTILE, which creates equiheight
  histograms.)" https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions214.htm
  See usage examples for more details. 

[Author]:
 Alan Burstein

[Compatibility]:
 SQL Server 2008+

[Syntax]:
 --===== Autonomous
  SELECT ng.*
  FROM   dbo.NGroupRangeAB(@rows,@groups) AS ng;

[Parameters]:
 @rows   = BIGINT; the number of rows to be "tiled" (have group number assigned to it)
 @groups = BIGINT; requested number of tile groups (same as the parameter passed to NTILE)

[Returns]:
 Inline Table Valued Function returns:
 GroupNumber = BIGINT; a row number beginning with 1 and ending with @rows
 Members     = BIGINT; Number of possible distinct members in the group
 Low         = BIGINT; the lower-bound range
 High        = BIGINT; the Upper-bound range

[Dependencies]:
 core.rangeAB (iTVF)

[Developer Notes]:
 1. An inline derived tally table using a CTE or subquery WILL NOT WORK. NTally requires 
    a correctly indexed tally table named dbo.tally; if you have or choose to use a
    permanent tally table with a different name or in a different schema make sure to 
    change the DDL for this function accordingly. The recomended number of rows is 
    1,000,000; below is the recomended DDL for dbo.tally. Note the "Beginning" and "End"
    of tally code.To learn more about tally tables see:
    http://www.sqlservercentral.com/articles/T-SQL/62867/

 2. For best results a P.O.C. index should exists on the table that you are "tiling". For 
    more information about P.O.C. indexes see:
    http://sqlmag.com/sql-server-2012/sql-server-2012-how-write-t-sql-window-functions-part-3

 3. NGroupRangeAB is deterministic; for more about deterministic and nondeterministic functions
    see https://msdn.microsoft.com/en-us/library/ms178091.aspx

[Examples]:
-----------------------------------------------------------------------------------------
--===== 1. Basic illustration of the relationship between core.NGroupRangeAB and NTILE.
     --    Consider this query which assigns 3 "tile groups" to 10 rows:

   DECLARE @rows BIGINT = 7, @tiles BIGINT = 3;

   SELECT t.N, t.TileGroup
   FROM ( SELECT r.RN, NTILE(@tiles) OVER (ORDER BY r.RN)
          FROM   core.rangeAB(1,@rows,1,1) AS r) AS t(N,TileGroup);

  Results:
    N   TileGroup
    --- ----------
    1   1
    2   1
    3   1
    4   2
    5   2
    6   3
    7   3   

 To pivot these "equiheight histograms" into "equiwidth histograms" we could do this:
   DECLARE @rows BIGINT = 7, @tiles BIGINT = 3;

   SELECT TileGroup = t.TileGroup,
          [Low]     = MIN(t.N),
          [High]    = MAX(t.N),
          Members   = COUNT(*)
   FROM ( SELECT r.RN, NTILE(@tiles) OVER (ORDER BY r.RN)
          FROM   core.rangeAB(1,@rows,1,1) AS r) AS t(N,TileGroup);
   GROUP BY t.TileGroup;

 Results:
    TileGroup  Low  High  Members
    ---------- ---- ----- -----------
    1          1    3     3
    2          4    5     2
    3          6    7     2

 This will return the same thing at a tiny fraction of the cost:
   SELECT TileGroup = ng.GroupNumber,
          [Low]     = ng.[Low],
          [High]    = ng.[High],
          Members   = ng.Members
   FROM core.NGroupRangeAB(1,@rows,@tiles) AS ng;

--===== 2.1. Divide 25 Rows into 3 groups
 DECLARE @min BIGINT = 1, @max BIGINT = 25, @groups BIGINT = 4;

 SELECT   ng.GroupNumber, ng.Members, ng.low, ng.high
 FROM     core.NGroupRangeAB(@min,@max,@groups) AS ng;

--===== 2.2. Assign group membership to another table
 DECLARE @min BIGINT = 1, @max BIGINT = 25, @groups BIGINT = 4;

 SELECT 
   ng.GroupNumber, ng.low, ng.high, s.WidgetId, s.Price
 FROM     (VALUES('a',$12),('b',$22),('c',$9),('d',$2)) AS s(WidgetId,Price)
 JOIN     core.NGroupRangeAB(@min,@max,@groups) AS ng
   ON     s.Price BETWEEN ng.[Low] AND ng.[High]
 ORDER BY ng.RN;

 Results:
   GroupNumber  low  high  WidgetId  Price
   ------------ ---- ----- --------- ---------------------
   1            1    7     d         2.00
   2            8    13    a         12.00
   2            8    13    c         9.00
   4            20   25    b         22.00

-----------------------------------------------------------------------------------------
[Revision History]:
 Rev 00 - 20190128 - Initial Creation; Final Tuning - Alan Burstein
****************************************************************************************/
RETURNS TABLE WITH SCHEMABINDING AS RETURN
SELECT
  RN          = r.RN,                -- Sort Key
  GroupNumber = r.N2,                -- Bucket (group) number
  Members     = g.S-ur.N+1,          -- Count of members in this group
  [Low]       = r.RN*g.S+rc.N+ur.N,  -- Lower boundary for the group (inclusive)
  [High]      = r.N2*g.S+rc.N        -- Upper boundary for the group (inclusive)
FROM         core.rangeAB(0,@groups-1,1,0)                    AS r      -- Range Function
CROSS APPLY (VALUES((@max-@min)/@groups,(@max-@min)%@groups)) AS g(S,U) -- Size, Underflow
CROSS APPLY (VALUES(SIGN(SIGN(r.RN-g.U)-1)+1))                AS ur(N)  -- get Underflow
CROSS APPLY (VALUES(@min+r.RN-(ur.N*(r.RN-g.U))))             AS rc(N); -- Running Count
GO
1 голос
/ 26 мая 2020

Просто добавьте 1,5 часа (90 минут) и извлеките дату:

group by convert(date, dateadd(minute, 90, pt.acctualfinish))
...