【问题标题】:Getting data from first and last row of each group从每组的第一行和最后一行获取数据
【发布时间】:2014-11-10 06:44:32
【问题描述】:

我发现了许多与此类似的主题,但没有一个我能很好地理解以解决我的具体案例。

A 有一个具有以下基本结构的表:

+------------------------+
| id | session ID | bal  |
+------------------------+
| 0  | 00000002 | 100    |
| 1  | 00000002 | 120    |
| 2  | 00000002 | 140    |
| 3  | 00000001 | 900    |
| 4  | 00000001 | 800    |
| 5  | 00000001 | 500    |
+------------------------+ 

我需要创建一个 (Microsoft SQL) 查询,它根据 ID 列的顺序值返回每个唯一的 sessionID 以及第一个(“开始”)和最后一个(“结束”)bal 条目。结果如下所示:

+---------------------------+
| session ID | start | end  |
+---------------------------+
| 00000002   | 100   | 140  |
| 00000001   | 900   | 500  |
+---------------------------+

我怎样才能做到这一点?

【问题讨论】:

  • 你用的是什么数据库?
  • 你能创建一个 sqlfiddle 吗?
  • OP 在帖子中提到 SQL Server...您是在寻找 min()max(),还是根据 id 字段要求第一个和最后一个?
  • 我认为您可能会从研究一些基础知识中受益:GROUP BY 以及相关的 MAXMIN 聚合函数将解决您的问题
  • 我根据 ID 字段询问第一个和最后一个,如果您查看我想要的结果表,您会发现 start 可能高于 end,反之亦然。所以下面给出的 min() 和 max() 答案不起作用..

标签: sql sql-server group-by


【解决方案1】:

编辑 在回复您的评论时,SQL Server 支持窗口函数。根据Session ID 查找第一个和最后一个bal 值的一种方法是:

select  distinct [Session ID]
,       first_value(bal) over (partition by [Session ID] order by id) as [start]
,       first_value(bal) over (partition by [Session ID] order by id desc) as [end]
from    Table1

Example at SQL Fiddle.

另一种方式(有很多)是增加和减少行号:

select  [Session ID]
,       max(case when rn1 = 1 then bal end) as [start]
,       max(case when rn2 = 1 then bal end) as [end]
from    (
        select  row_number() over (partition by [Session ID] order by id) as rn1
        ,       row_number() over (partition by [Session ID] order by id desc) as rn2
        ,       *
        from    Table1
        ) as SubQueryAlias
group by
        [Session ID]

Example at SQL Fiddle.

【讨论】:

  • 看起来它们是按 id 列排序的,有时从高到低。
  • 正确.. 如果您查看我想要的结果表,则最小值和最大值在这里不相关。我需要根据 ID 获取第一个和最后一个 bal 值。
  • 第一个解决方案实际上是正确的,尽管恕我直言,第三列使用 last_value (和 ID ASC)会更清楚。这是一个适用于 2005 - 2008R2 sqlfiddle.com/#!3/8fe4a/10/0 的解决方案
  • @Andomar last_value() 实际上是 first_value() 相反。函数的默认“窗口框架”('UNBOUNDED PRECEDING AND CURRENT ROW')导致它们的行为不同。如果您指定等效的窗口框架('UNBOUNDED PRECEDING 和 UNBOUNDED FOLLOWING '),则函数的行为与预期的一样。话虽如此,当我发表我的原始即兴声明时,我并不知道这一点。感谢您指出差异。 sqlfiddle.com/#!6/6c244/31/0
  • @JimV.: Asked on DBA,他们得出了相同的结论
【解决方案2】:

在MySQL中可能是这样的:

SELECT `session ID`, MIN(bal) AS start, MAX(bal) AS end FROM `yourtable` WHERE `session ID` IN (
    SELECT DISTINCT(`session ID`) FROM `yourtable`
);

【讨论】:

  • in 子句应该完成什么?问题是关于 Microsoft SQL(或 SQL Server)btw
  • 是的,我在 MySQL 中问过,因为这是我主宰的,现在你必须“翻译”到 SQL Server。
【解决方案3】:

我假设 bal 是数字(尽管在这种情况下不必是数字,因为所有条目的长度都为 3)

select sessionID
     , min(bal) as start
     , max(bal) as end
from table_name
group by sessionID

其中“table_name”是您的表的名称

【讨论】:

  • 余额的最小值和最大值不是正确的结果。
  • 你是对的 - 我读错了问题。第三个条目(以“with CTE as”开头的条目)是我将如何做的——至少,如果我不知道 SQL 现在具有分析功能(这就是我在 Oracle 中的做法)。跨度>
【解决方案4】:

您可以使用JOINCommon Table Expression 来提高可读性:

with CTE as
(
    select 
        sessionId, min(id) as firstId, max(id) as lastId
    from 
        log
    group by sessionId
)
select
    CTE.sessionId, Log1.bal as start, Log2.bal as [end]
from
    CTE
    join Log as Log1 on Log1.id = CTE.firstId
    join Log as Log2 on Log2.id = CTE.lastId

请参阅SQL Fiddle

【讨论】:

  • 您不会多次引用派生表,因此 CTE 是多余的。 Example here.
  • @Andomar 你是对的。我刚开始写它时考虑了多个派生表引用。无论如何,它很容易理解和有用的技术,所以我会保留它。
  • @Andomar 我认为您可以在回答中添加您对我的查询所做的更改,因为在我看来它比您建议的更简单。
  • 窗口函数通常可以只使用一次表扫描。所以我更喜欢带有窗口函数的变体,即使它不太清楚
  • @Andomar 我明白了。无论如何,SQL Fiddle 表明,在这种情况下,这种方法又做了两次排序,超过了两次表扫描的总成本。所以我更喜欢先把精力放在可读性上,并在必要时进行调整(只有真正的数据库才能知道何时)。
【解决方案5】:

SELECT FIRST(column_name), LAST(column_name) FROM table_name; http://forums.mysql.com/read.php?65,363723,363723

【讨论】:

    猜你喜欢
    • 2014-09-29
    • 2021-04-06
    • 2019-05-24
    • 1970-01-01
    • 1970-01-01
    • 2016-12-12
    • 1970-01-01
    • 2011-03-27
    • 1970-01-01
    相关资源
    最近更新 更多