拉出按索引排序的每个 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 - 如何按每个列表中的第四个元素对列表列表进行排序? [复制]