拉出按索引排序的每个 REPEATING 组中的第一个索引记录

Posted

技术标签:

【中文标题】拉出按索引排序的每个 REPEATING 组中的第一个索引记录【英文标题】:Pull out first index record in each REPEATING group ordered by index 【发布时间】:2014-05-04 22:46:06 【问题描述】:

我有这个数据的表

DECLARE @tbl TABLE
(
    IDX INTEGER,
    VAL VARCHAR(50)
)

--Inserted values for testing
INSERT INTO @tbl(IDX, VAL) VALUES(1,'A')
INSERT INTO @tbl(IDX, VAL) VALUES(2,'A')
INSERT INTO @tbl(IDX, VAL) VALUES(3,'A')
INSERT INTO @tbl(IDX, VAL) VALUES(4,'B')
INSERT INTO @tbl(IDX, VAL) VALUES(5,'B')
INSERT INTO @tbl(IDX, VAL) VALUES(6,'B')
INSERT INTO @tbl(IDX, VAL) VALUES(7,'A')
INSERT INTO @tbl(IDX, VAL) VALUES(8,'A')
INSERT INTO @tbl(IDX, VAL) VALUES(9,'A')
INSERT INTO @tbl(IDX, VAL) VALUES(10,'C')
INSERT INTO @tbl(IDX, VAL) VALUES(11,'C')
INSERT INTO @tbl(IDX, VAL) VALUES(12,'A')
INSERT INTO @tbl(IDX, VAL) VALUES(13,'A')
--INSERT INTO @tbl(IDX, VAL) VALUES(14,'A')  -- this line has bad binary code
INSERT INTO @tbl(IDX, VAL) VALUES(14,'A')    -- replace with this line and it works
INSERT INTO @tbl(IDX, VAL) VALUES(15,'D')
INSERT INTO @tbl(IDX, VAL) VALUES(16,'D')
Select * From @tbl  -- to see what you have inserted...

我正在寻找的输出是每组 Val 对 Idx 的优先排序中的第一个和最后一个 Idx 和 Val。注意到 Val 可能会重复!!! Idx 也可能不像 imsert 语句中那样在表中按升序排列。请不要使用光标! 即

Val  First   Last
=================
A        1      3
B        4      6
A        7      9
C       10     11
A       12     14
D       15     16

【问题讨论】:

所以一个组被定义为idx值与val值相同的不间断区间?我认为很难。 按 Idx 顺序更正 val 的完整间隔。我同意,程序编程很容易,甚至可以使用光标,但是使用“简单”选择我很难过,这就是我问的原因:-) 【参考方案1】:

如果idx 值保证是连续的,那么试试这个:

Select f.val, f.idx first, l.idx last
From @tbl f
   join @tbl l
      on l.val = f.val 
          and l.idx > f.idx 
          and not exists 
              (Select * from @tbl
               Where val = f.val 
                  and idx = l.idx + 1)
          and not exists  
              (Select * from @tbl
               Where val = f.val 
                  and idx = f.idx - 1)
          and not exists
              (Select * from @tbl
               Where val <> f.val
                 and idx Between f.idx and l.idx)
order by f.idx

如果idx 的值不是连续的,那么它需要更复杂一些...

Select f.val, f.idx first, l.idx last
From @tbl f
   join @tbl l
      on l.val = f.val 
          and l.idx > f.idx 
          and not exists 
              (Select * from @tbl
               Where val = f.val 
                  and idx = (select Min(idx)
                             from @tbl 
                             where idx > l.idx))
          and not exists  
              (Select * from @tbl
               Where val = f.val 
                  and idx = (select Max(idx)
                             from @tbl 
                             where idx < f.idx))
          and not exists
              (Select * from @tbl
               Where val <> f.val
                 and idx Between f.idx and l.idx)
order by f.idx

【讨论】:

关闭,但当行以连续的 idx 但以随机顺序插入时,非连续的一项非常有效。 我认为插入顺序无关紧要。 idx 值的序列定义了组。除了那个顺序,没有顺序。 尝试运行它,然后以不同的随机顺序完成插入。我的要求是我们在 idx 订购时进行“分组”。如果我们更改插入 Idx 的顺序,那么我们仍然应该得到相同的结果。恐怕这个声明不这样做。 以不同的顺序插入完全相同的记录不会改变记录集。它们仍然是完全相同的集合。如果仅基于表中的属性(列值),任何查询都将返回相同的结果。如果所需的结果取决于插入顺序,那么您需要在包含插入顺序的表中添加另一列,并将查询编写为依赖于该新列以及当前列。 我刚刚尝试以随机顺序插入同一组记录,正如预期的那样,两个查询的结果与以 idx 顺序插入时的结果相同【参考方案2】:

SQL Server 2012

在 SQL Server 2012 中,您可以将 cte 序列与如下所示的滞后/超前分析函数一起使用 (fiddle here)。代码不假设任何关于 idx 的类型或序列,并在每个窗口中查询第一次和最后一次出现的 val。

;with cte as
(
select val, idx,  
ROW_NUMBER() over(order by (select 0)) as urn  --row_number without ordering
from @tbl),
cte1 as
(
select urn, val, idx, 
lag(val, 1) over(order by urn) as prevval, 
lead(val, 1) over(order by urn) as nextval 
from cte
),
cte2 as
(
select val, idx, ROW_NUMBER() over(order by (select 0)) as orn,
(ROW_NUMBER() over(order by (select 0))+1)/2 as prn from cte1
where (prevval <> nextval or prevval is null or nextval is null)
),
cte3 as
(
select val, FIRST_VALUE(idx) over(partition by prn order by prn) as firstidx,
LAST_VALUE(idx) over(partition by prn order by prn) as lastidx, orn
from cte2
),
cte4 as
(
select val, firstidx, lastidx, min(orn) as rn
from cte3
group by val, firstidx, lastidx
)
select val, firstidx, lastidx
from cte4
order by rn;

SQL Server 2008

在 SQL Server 2008 中,由于缺少滞后/超前分析功能,代码更加受折磨。 (小提琴here)。这里同样,代码不假设任何关于 idx 的类型或序列,并在每个窗口中查询第一次和最后一次出现的 val。

;with cte as
(
select val, idx,  ROW_NUMBER() over(order by (select 0)) as urn
from @tbl),
cte1 as
(
select m.urn, m.val, m.idx,
_lag.val as prevval, _lead.val as nextval
from cte as m
left join cte as _lag
on _lag.urn = m.urn-1
left join cte AS _lead
on _lead.urn = m.urn+1),
cte2 as
(
select val, idx, ROW_NUMBER() over(order by (select 0)) as orn,
(ROW_NUMBER() over(order by (select 0))+1)/2 as prn from cte1
where (prevval <> nextval or prevval is null or nextval is null)),
cte3 as
( select *, ROW_NUMBER() over(partition by prn order by orn) as rownum
from cte2), 
cte4 as 
(select o.val, (select i.idx from cte3 as i where i.rownum = 1 and i.prn = o.prn) 
as firstidx,
(select i.idx from cte3 as i where i.rownum = 2 and i.prn = o.prn) as lastidx, 
o.orn from cte3 as o), 
cte5 as ( 
select val, firstidx, lastidx, min(orn) as rn
from cte4
group by val, firstidx, lastidx
)
select val, firstidx, lastidx
from cte5
order by rn;

注意: 这两种解决方案都是基于数据库引擎保留插入顺序的假设,虽然关系型数据库在理论上不保证顺序。

【讨论】:

应该说我正在使用 sql server 2008(并且可能也必须在 oracle 中这样做)所以没有滞后 n 领先【参考方案3】:

一种方法——至少对于不使用特殊功能的 SQL Server 2008 来说,是引入一个辅助表和辅助变量。

现在这对您来说是否真的可行(由于许多其他要求)我不知道 - 但它可能会引导您走上解决方案的道路,但它确实可以解决您当前的无光标设置要求和也不领先/落后:

所以基本上我所做的就是制作一个辅助表和一个辅助分组变量: (对不起命名)

DECLARE @grp TABLE
    (
      idx INTEGER ,
      val VARCHAR(50) ,
      gidx INT
    )

DECLARE @gidx INT = 1

INSERT  INTO @grp
        ( idx, val, gidx )
        SELECT  idx ,
                val ,
                0
        FROM    @tbl AS t

我用你的源表@tbl 中的值填充它。

然后我做了一个更新技巧,根据 VAL 何时更改值来为 gidx 分配一个值:

UPDATE  g
SET     @gidx = gidx = CASE WHEN val <> ISNULL(( SELECT val
                                                 FROM   @grp AS g2
                                                 WHERE  g2.idx = g.idx - 1
                                               ), val) THEN @gidx + 1
                            ELSE @gidx
                       END
FROM    @grp AS g

它的作用是将值 1 分配给 gidx 直到 VAL 发生变化,然后它分配 gidx + 1 也分配给 @gixd 变量。等等。 这将为您提供以下可用结果:

idx val  gidx
1   A   1
2   A   1
3   A   1
4   B   2
5   B   2
6   B   2
7   A   3
8   A   3
9   A   3
10  C   4
11  C   4
12  A   5
13  A   5
14  A   5
15  D   6
16  D   6

请注意,现在 gidx 是一个分组因素。

那么就很简单了,用sub select提取数据:

SELECT  ( SELECT TOP 1
                    VAL
          FROM      @GRP g3
          WHERE     g2.gidx = g3.gidx
        ) AS Val ,
        MIN(idx) AS First ,
        MAX(idx) AS Last
FROM    @grp AS g2
GROUP BY gidx 

这会产生结果:

A   1   3
B   4   6
A   7   9
C   10  11
A   12  14
D   15  16

Fiddler link

【讨论】:

【参考方案4】:

我假设 IDX 值是唯一的。如果也可以假设它们从 1 开始并且没有间隙,如您的示例所示,您可以尝试以下 SQL Server 2005+ 解决方案:

WITH partitioned AS (
  SELECT
    IDX, Val,
    grp = IDX - ROW_NUMBER() OVER (PARTITION BY Val ORDER BY IDX ASC)
  FROM @tbl
)
SELECT
  Val,
  FirstIDX = MIN(IDX),
  LastIDX  = MAX(IDX)
FROM partitioned
GROUP BY
  Val, grp
ORDER BY
  FirstIDX
;

如果 IDX 的值可能有间隙和/或可能从 1 以外的值开始,您可以改用上面的以下修改:

WITH partitioned AS (
  SELECT
    IDX, Val,
    grp = ROW_NUMBER() OVER (                 ORDER BY IDX ASC)
        - ROW_NUMBER() OVER (PARTITION BY Val ORDER BY IDX ASC)
  FROM @tbl
)
SELECT
  Val,
  FirstIDX = MIN(IDX),
  LastIDX  = MAX(IDX)
FROM partitioned
GROUP BY
  Val, grp
ORDER BY
  FirstIDX
;

注意:如果您最终使用这些查询中的任何一个,请确保查询语句preceding 用分号分隔,尤其是在您使用 SQL Server 2008 或更高版本时.

【讨论】:

以上是关于拉出按索引排序的每个 REPEATING 组中的第一个索引记录的主要内容,如果未能解决你的问题,请参考以下文章

我可以按类型访问什么类型的集合,并在必要时找不到

按组中第二条记录的数据对记录组进行排序

每个匹配只显示第一行

Python - 如何按每个列表中的第四个元素对列表列表进行排序? [复制]

按一个变量排序,按另一个分组,然后在 R 中的 SQL Query 中选择第一行

按最近的时间戳对数组中的对象数组进行排序,然后使用 jq 按每个数组的第一个对象的时间戳对外部数组进行排序