【问题标题】:Sql query to return a result set for a TimelineSql 查询以返回时间轴的结果集
【发布时间】:2014-05-14 22:34:31
【问题描述】:

我认为描述我要查找的内容的最佳方式是显示数据表以及我希望从查询中返回的内容。这是 SQL Server 中的一个简单数据表:

JobNumber TimeOfWeigh 
100       01/01/2014 08:00 
100       01/01/2014 09:00 
100       01/01/2014 10:00 
200       01/01/2014 12:00 
200       01/01/2014 13:00 
300       01/01/2014 15:00 
300       01/01/2014 16:00 
100       02/01/2014 08:00 
100       02/01/2014 09:00 
100       03/01/2014 10:00 

我想要一个查询,它将作业分组并从每个组返回第一个和最后一个 DateTime。但是,正如您在此处看到的,有 2 组 100 作业编号。我不希望第二组与第一组结合。

相反,我想要这样:

JobNumber   First Weigh         Last Weigh
100         01/01/2014 08:00    01/01/2014 10:00
200         01/01/2014 12:00    01/01/2014 13:00
300         01/01/2014 15:00    01/01/2014 16:00
100         02/01/2014 08:00    03/01/2014 10:00

我已经为此苦苦挣扎了好几个小时。任何帮助将不胜感激。

已编辑

日期和时间都是虚拟的随机数据。实际数据在一天内有数千个重量。我希望每个工作的第一个和最后一个权重来确定工作的持续时间,以便我可以在时间线上表示持续时间。但我想显示 Job 100 两次,表示它在 200 和 300 完成后暂停并恢复

【问题讨论】:

  • 左边的#字段是表格中的实际列吗?
  • 为什么最后一行将两条记录分开一个月,而第一行按天分组?
  • 所有日期之间的时间实际上是无关紧要的。我基本上表明它们可以是任何值,但它们是按时间顺序排列的。我正在使用时间轴控件,时间轴上有一个矩形,表示每个作业和持续时间(在第一个和最后一个权重之间)。但我想在时间轴上显示 Job 100 两次...表示 Job 在 200 和 300 完成后暂停和恢复。
  • 左边的字段不是列。好点的大脑,我会从原帖中删除它

标签: sql sql-server group-by


【解决方案1】:

这是我的尝试,将 row_number() 与分区一起使用。我已经把它分解成几个步骤,希望它很容易理解。如果您的表中已经有一个带有整数标识符的列,那么您可以省略第一个 CTE。即使在那之后,您也许可以进一步简化它,但它似乎确实有效。

(根据评论中的要求添加了一个标志,指示具有多个范围的作业。)

declare @sampleData table (JobNumber int, TimeOfWeigh datetime);
insert into @sampleData values
    (100, '01/01/2014 08:00'),
    (100, '01/01/2014 09:00'), 
    (100, '01/01/2014 10:00'),
    (200, '01/01/2014 12:00'),
    (200, '01/01/2014 13:00'),
    (300, '01/01/2014 15:00'),
    (300, '01/01/2014 16:00'),
    (100, '02/01/2014 08:00'),
    (100, '02/01/2014 09:00'),
    (100, '03/01/2014 10:00');

-- The first CTE assigns an ordering to the records according to TimeOfWeigh,
-- producing the row numbers you gave in your example.
with JobsCTE as
(    
    select 
        row_number() over (order by TimeOfWeigh) as RowNumber, 
        JobNumber,
        TimeOfWeigh
    from @sampleData
),

-- The second CTE orders by the RowNumber we created above, but restarts the
-- ordering every time the JobNumber changes. The difference between RowNumber
-- and this new ordering will be constant within each group.
GroupsCTE as
(
    select
        RowNumber - row_number() over (partition by JobNumber order by RowNumber) as GroupNumber,
        JobNumber,
        TimeOfWeigh
    from JobsCTE
),

-- Join by JobNumber alone to determine which jobs appear multiple times.
DuplicatedJobsCTE as
(
    select JobNumber 
    from GroupsCTE 
    group by JobNumber 
    having count(distinct GroupNumber) > 1
)

-- Finally, we use GroupNumber to get the mins and maxes from contiguous ranges.
select
    G.JobNumber,
    min(G.TimeOfWeigh) as [First Weigh],
    max(G.TimeOfWeigh) as [Last Weigh],
    case when D.JobNumber is null then 0 else 1 end as [Multiple Ranges]
from
    GroupsCTE G
    left join DuplicatedJobsCTE D on G.JobNumber = D.JobNumber
group by
    G.JobNumber,
    G.GroupNumber,
    D.JobNumber
order by
    [First Weigh];

【讨论】:

  • 耸人听闻的乔!发挥了魅力。不敢相信我得到这个答案的速度有多快。奇妙的头脑在那里!为我节省了很多时间
  • 乔。我可能在这里很贪心...无论如何我可以在结果集中添加另一列,它是一个标志,指示作业是否多次出现。我的 SQL 技能是基本的。谢谢
  • 您的意思是该标志应指示单行是否描述多个作业(意味着该标志将在结果集中的所有四个记录中设置),还是应指示是否出现作业在多个非连续范围内(意味着该标志只会在作业 100 的两条记录中设置)?
  • 仅适用于非连续范围 Joe。因此,将在新列中返回一个 True 值,指示 Job 100 在结果集中多次出现。 (在这种情况下两次)
  • 在编辑后的答案中查看DuplicatedJobsCTE。这是一个简短的附加步骤,可以选择具有多个不同组号的作业。
【解决方案2】:

您必须使用自连接来创建伪表,其中包含每组中的第一行和最后一行。

Select F.JobNumber, 
   f.TimeOfWeigh FirstWeigh, 
   l.TimeOfWeigh LastWeigh
From table f -- for first record
   join table l -- for last record
       on l.JobNumber = f.JobNumber 
          And Not exists
              (Select * from table
               Where JobNumber = f.JobNumber 
                  And id = f.id-1)
          And Not exists
              (Select * from table
               Where JobNumber = f.JobNumber 
                  And id = l.id+1)
          And Not Exists
              (Select * from table
               Where JobNumber <> f.JobNumber 
                  And id Between f.Id and l.Id)

【讨论】:

  • 不得不是一个很大的承诺,它可以在没有加入的情况下完成。现在要睡觉了。明天给你看
【解决方案3】:

当我看到它时,它让我着迷,我想知道我将如何解决它。我太忙了,无法先得到答案,后来我得到了它的工作,但从那以后我就坐了几天!几天后我仍然明白我的设计,这是一个好兆头:)

我在最后添加了一些额外的数据来证明这适用于单行 JobNumber 条目,而不是假设称重总是分批进行,但结果中的第一行与原始解决方案匹配。

这种方法还使用级联 CTE(比这里接受的答案多一个,但我不会让这让我气馁!)首先是测试数据设置:

With Weighs AS   -- sample data
(
SELECT 100 AS JobNumber, '01/01/2014 08:00' AS TimeOfWeigh UNION ALL 
SELECT 100 AS JobNumber, '01/01/2014 09:00' AS TimeOfWeigh UNION ALL 
SELECT 100 AS JobNumber, '01/01/2014 10:00' AS TimeOfWeigh UNION ALL 
SELECT 200 AS JobNumber, '01/01/2014 12:00' AS TimeOfWeigh UNION ALL 
SELECT 200 AS JobNumber, '01/01/2014 13:00' AS TimeOfWeigh UNION ALL 
SELECT 300 AS JobNumber, '01/01/2014 15:00' AS TimeOfWeigh UNION ALL 
SELECT 300 AS JobNumber, '01/01/2014 16:00' AS TimeOfWeigh UNION ALL 
SELECT 100 AS JobNumber, '02/01/2014 08:00' AS TimeOfWeigh UNION ALL 
SELECT 100 AS JobNumber, '02/01/2014 09:00' AS TimeOfWeigh UNION ALL 
SELECT 100 AS JobNumber, '03/01/2014 10:00' AS TimeOfWeigh UNION ALL
SELECT 400 AS JobNumber, '04/01/2014 14:00' AS TimeOfWeigh UNION ALL
SELECT 300 AS JobNumber, '04/01/2014 14:30' AS TimeOfWeigh
)
,
Numbered AS  -- add on a unique consecutive row number
( SELECT *, ROW_NUMBER() OVER (ORDER BY TimeOfWeigh) AS ID FROM Weighs )
, 
GroupEnds AS  -- add on a 1/0 flag for whether it's the first or last in a run
( SELECT *,
    CASE WHEN -- next row is different JobNumber?
      (SELECT ID FROM Numbered n2 WHERE n2.ID=n1.ID+1 AND n2.JobNumber=n1.JobNumber) IS NULL
    THEN 1 ELSE 0 END AS GroupEnd,
    CASE WHEN -- previous row is different JobNumber?
      (SELECT ID FROM Numbered n2 WHERE n2.ID=n1.ID-1 AND n2.JobNumber=n1.JobNumber) IS NULL
    THEN 1 ELSE 0 END AS GroupBegin
  FROM Numbered n1 
)
,
Begins_and_Ends AS  -- make sure there are always matching pairs
( SELECT * FROM GroupEnds WHERE GroupBegin=1
    UNION ALL
  SELECT * FROM GroupEnds WHERE GroupEnd=1
)
,
Pairs AS  -- give matching pairs the same ID number for GROUPing next..
( SELECT *, (1+Row_Number() OVER (ORDER BY ID))/2 AS PairID
  FROM Begins_and_Ends
)
SELECT
  Min(JobNumber) AS JobNumber,
  Min(TimeOfWeigh) as [First Weigh],
  Max(TimeOfWeigh) as [Last Weigh]
FROM Pairs
GROUP BY PairID
ORDER BY PairID

Numbered CTE 相当明显,为每一行提供一个有序的 ID 号。

CTE GroupEnds 添加一对布尔值 - 如果该行是 JobNumbers 运行中的第一行或最后一行,则为 1 或 0 - 通过尝试查看下一行或上一行是否是相同的 JobNumber。

从那里我只需要一种方法来配对相邻的 GroupBegins 和 GroupEnds。我使用 N-tile 排名函数 NTILE() 通过计算 GroupEnds 并将结果选择为 NTILE() 的参数将行数除以 2 来生成这些数字 - 但是当有奇数行到期时这会中断到单行批次,其中同一行是批次的开始和结束。

我通过保证相同数量的 Begin 和 End 行来解决这个问题:Begin 行和 End 行的 UNION,即使有些是相同的行。我是 CTE Begins_and_Ends

Pairs CTE 使用 Row_Number() 除以 2 来添加对数 - 整数结果 PairID 对于行对是相同的。

这为我们提供了以下信息 - JobNumber 批次中间的所有行现在都已被过滤掉:

JOBNUMBER  TIMEOFWEIGH     ID  End? Begin PairID
100     01/01/2014 08:00    1   0   1     1
100     01/01/2014 10:00    3   1   0     1
200     01/01/2014 12:00    4   0   1     2
200     01/01/2014 13:00    5   1   0     2
300     01/01/2014 15:00    6   0   1     3
300     01/01/2014 16:00    7   1   0     3
100     02/01/2014 08:00    8   0   1     4
100     03/01/2014 10:00    10  1   0     4
400     04/01/2014 14:00    11  1   1     5
400     04/01/2014 14:00    11  1   1     5
300     04/01/2014 14:30    12  1   1     6
300     04/01/2014 14:30    12  1   1     6

现在是最后一块蛋糕,可以通过PairID 分组并获取第一次和最后一次称重时间。我喜欢这个挑战,我想知道是否有其他人发现它在任何重量中都有用!
http://sqlfiddle.com/#!3/b4f39/48

【讨论】:

  • 注意——这不包括后面关于多次出现的作业的要求。我现在已经玩够了:)
【解决方案4】:

是的,这是一个迷人的智力谜题。感谢您的分享。我想提出不涉及 EXISTS 或 JOINS 的解决方案

首先我创建了一个表,其中包含 job_id (j_id) 和用于排序 (j_v) 的整数值。 Ints 只是更容易输入,而逻辑与日期时间完全相同。

     select * from j order by j_v;
 j_id | j_v 
------+-----
  100 |   1
  100 |   2
  100 |   2
  100 |   2
  100 |   2
  100 |   3
  200 |   4
  200 |   5
  300 |   6
  300 |   6
  300 |   6
  300 |   7
  300 |   7
  100 |   8
  100 |   9
(15 rows)

我使用了 windows 函数和 3 个 CTE:

  • 第一个添加表中的领先和滞后
  • 第二个过滤器只留下作业开始或结束的那些行
  • 第三个引入 row_number 用于删除所有偶数行。

给你:

with X AS (
select j_id, j_v,
       coalesce ( lag(j_id,1) OVER (MY_W), -1)  as j_id_lag,
       lag(j_v,1) over (MY_W) as j_v_lag,
       coalesce ( lead(j_id,1) OVER (MY_W), -1)  as j_id_lead,
       lead(j_v,1) over (MY_W) as j_v_lead
from j
WINDOW MY_W as ( ORDER BY j_v)
order by j_v 
),
Y AS ( 
select *
from X
where j_id_lag != j_id_lead
),
Z AS ( 
select * ,
      lead(j_v) OVER () AS L2,
      row_number() OVER () as my_row
from Y
) 
SELECT j_id, j_v as job_start ,l2 as job_end
from Z
where my_row %2 = 1
;
 j_id | job_start | job_end
------+-----+----
  100 |   1 |  3
  200 |   4 |  5
  300 |   6 |  7
  100 |   8 |  9
(4 rows)

查询计划来了:

                                                    QUERY PLAN                                                     
--------------------------------------------------------------------------------------------------------------------
 CTE Scan on z  (cost=325.94..379.17 rows=11 width=12) (actual time=0.047..0.071 rows=4 loops=1)
   Filter: ((my_row % 2::bigint) = 1)
   Rows Removed by Filter: 4
   CTE x
     ->  WindowAgg  (cost=149.78..203.28 rows=2140 width=8) (actual time=0.027..0.039 rows=15 loops=1)
           ->  Sort  (cost=149.78..155.13 rows=2140 width=8) (actual time=0.019..0.019 rows=15 loops=1)
                 Sort Key: j.j_v
                 Sort Method: quicksort  Memory: 25kB
                 ->  Seq Scan on j  (cost=0.00..31.40 rows=2140 width=8) (actual time=0.004..0.006 rows=15 loops=1)
   CTE y
     ->  CTE Scan on x  (cost=0.00..48.15 rows=2129 width=24) (actual time=0.031..0.050 rows=8 loops=1)
           Filter: (j_id_lag <> j_id_lead)
           Rows Removed by Filter: 7
   CTE z
     ->  WindowAgg  (cost=0.00..74.51 rows=2129 width=24) (actual time=0.042..0.062 rows=8 loops=1)
           ->  CTE Scan on y  (cost=0.00..42.58 rows=2129 width=24) (actual time=0.031..0.052 rows=8 loops=1)
 Total runtime: 0.122 ms
(17 rows)

如您所见,有一种排序(按序列值或原始问题中的时间对数据进行排序)和多次 CTE 扫描,但没有连接。复杂性 - NlogN 用于排序,正是我正在寻找的。​​p>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-05-08
    • 1970-01-01
    • 1970-01-01
    • 2018-07-25
    • 1970-01-01
    • 2021-09-04
    • 2017-12-19
    • 2012-03-30
    相关资源
    最近更新 更多