SQL rownumber partition 取范围数据进行分组

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SQL rownumber partition 取范围数据进行分组相关的知识,希望对你有一定的参考价值。

例如我有如下表:
Name Time
张三 1/1/2012
李四 1/2/2012
王五 1/3/2012
张三 1/4/2012
王五 1/5/2012
张三 1/6/2012
张三 1/13/2012
张三 1/21/2012
我要得出结果
Name Time Repeat Contacts
张三 1/1/2012 1
李四 1/2/2012 1
王五 1/3/2012 1
张三 1/4/2012 2
王五 1/5/2012 2
张三 1/6/2012 3
张三 1/13/2012 4
张三 1/21/2012 1
最后一列的数据就是根据名字,按照时间来排序,以7天为一个单位,一旦俩者的时间超过7天,那么repeat contact 数据就变成1重新排序,请问SQL 语句怎么实现

oracle中,分组后,取各组的前n条记录的sql语句:rownumber() over()

row_number() OVER (PARTITION BY COL1 ORDER BY COL2) 表示根据COL1分组,在分组内部根据 COL2排序,而此函数计算的值就表示每组内部排序后的顺序编号(组内连续的唯一的).  与rownum的区别在于:使用rownum进行排序的时候是先对结果集加入伪列rownum然后再进行排序,而此函数在包含排序从句后是先排序再计算行号码.
  row_number()和rownum差不多,功能更强一点(可以在各个分组内从1开时排序).
  rank()是跳跃排序,有两个第二名时接下来就是第四名(同样是在各个分组内).
  dense_rank()l是连续排序,有两个第二名时仍然跟着第三名。相比之下row_number是没有重复值的 .
  lag(arg1,arg2,arg3):
arg1是从其他行返回的表达式
arg2是希望检索的当前行分区的偏移量。是一个正的偏移量,时一个往回检索以前的行的数目。
arg3是在arg2表示的数目超出了分组的范围时返回的值。

看几个SQL语句:
语句一:
select row_number() over(order by sale/cnt desc) as sort, sale/cnt
from (
select -60 as sale,3 as cnt from dual union
select 24 as sale,6 as cnt from dual union
select 50 as sale,5 as cnt from dual union
select -20 as sale,2 as cnt from dual union
select 40 as sale,8 as cnt from dual);

执行结果:
SORT SALE/CNT
---------- ----------
1 10
2 5
3 4
4 -10
5 -20

语句二:查询员工的工资,按部门排序
select ename,sal,row_number() over (partition by deptno order by sal desc) as sal_order from scott.emp;
执行结果:
ENAME SAL SAL_ORDER
-------------------- ---------- ----------
KING 5000 1
CLARK 2450 2
MILLER 1300 3
SCOTT 3000 1
FORD 3000 2
JONES 2975 3
ADAMS 1100 4
SMITH 800 5
BLAKE 2850 1
ALLEN 1600 2
TURNER 1500 3
WARD 1250 4
MARTIN 1250 5
JAMES 950 6

已选择14行。
语句三:查询每个部门的最高工资
select deptno,ename,sal from
(select deptno,ename,sal,row_number() over (partition by deptno order by sal desc) as sal_order
from scott.emp) where sal_order <2;

执行结果:
DEPTNO ENAME SAL
---------- -------------------- ----------
10 KING 5000
20 SCOTT 3000
30 BLAKE 2850

已选择3行。
语句四:
select deptno,sal,rank() over (partition by deptno order by sal) as rank_order from scott.emp order by deptno;
执行结果:
DEPTNO SAL RANK_ORDER
---------- ---------- ----------
10 1300 1
10 2450 2
10 5000 3
20 800 1
20 1100 2
20 2975 3
20 3000 4
20 3000 4
30 950 1
30 1250 2
30 1250 2
30 1500 4
30 1600 5
30 2850 6

已选择14行。
语句五:
select deptno,sal,dense_rank() over(partition by deptno order by sal) as dense_rank_order from scott.emp order by deptn;执行结果:
DEPTNO SAL DENSE_RANK_ORDER
---------- ---------- ----------------
10 1300 1
10 2450 2
10 5000 3
20 800 1
20 1100 2
20 2975 3
20 3000 4
20 3000 4
30 950 1
30 1250 2
30 1250 2
30 1500 3
30 1600 4
30 2850 5

已选择14行。
语句六:
select deptno,ename,sal,lag(ename,1,null) over(partition by deptno order by ename) as lag_ from scott.emp order by deptno;
执行结果:
DEPTNO ENAME SAL LAG_
---------- -------------------- ---------- --------------------
10 CLARK 2450
10 KING 5000 CLARK
10 MILLER 1300 KING
20 ADAMS 1100
20 FORD 3000 ADAMS
20 JONES 2975 FORD
20 SCOTT 3000 JONES
20 SMITH 800 SCOTT
30 ALLEN 1600
30 BLAKE 2850 ALLEN
30 JAMES 950 BLAKE
30 MARTIN 1250 JAMES
30 TURNER 1500 MARTIN
30 WARD 1250 TURNER

已选择14行。
参考技术A sql 2005环境

select Time,name,ROW_NUMBER() over (partition by fu order by fu) as counts from
(select time,(str(DATEPART(WEEK,time)) + name) as fu,name from tableA
group by time,DATEPART(WEEK,time),name
) kk

以上能实现

--说明:以下是按周分组,
select time,(str(DATEPART(WEEK,time)) + name) as fu,name from tableA
group by time,DATEPART(WEEK,time),name

--得到
2012-01-01 1张三
2012-01-02 1李四
2012-01-03 1王五
2012-01-04 1张三
2012-01-05 1王五
2012-01-06 1张三
2012-01-13 2张三
2012-01-21 3张三
2012-01-22 4李四
2012-01-22 4张三
2012-01-23 4王五
2012-01-24 4张三
2012-01-25 4王五

--再用ROW_NUMBER()就可以了

另外,2012-1-1是周日 ,1-1到1-7,刚好week值为1,

要用的时候,要测试下跨年的情况追问

感谢回答,不过可能我表达有歧义

我需要的是 俩个相邻的日期超过7天就重新再排,可能要涉及到datediff,不知道能否实现?

追答

select Time,name,ROW_NUMBER() over (partition by 周 order by 周) as counts from
(
select str(DATEDIFF(day,(select MIN(time) from tableA),time)/7 +1) + name as 周 ,Time,Name,id
from tableA
) kk

--得到

Time name counts
2012-12-13 张三 1
2012-12-19 张三 2
2012-12-22 李四 1
2012-12-23 王五 1
2012-12-25 王五 2
2012-12-24 张三 1
2012-12-20 张三 2
2013-01-02 李四 1
2012-12-29 张三 1
2012-12-30 张三 2

-- 说明

select MIN(time) from tableA --得到最小天,做为第1天

--跟week没有关系了

追问

您好,非常感谢support
但是得出的结果还是有点问题
如你的结果
2012-12-24 张三 1
2012-12-20 张三 2

20 应该是排在24前面的
您看下是否是哪里计算错误了?

追答

order by 周
改为
order by 周,time

这样就可以了,抱歉呵

追问

根据代码

但是结果如下
2012-01-01 张三 1
2012-01-04 张三 2
2012-01-06 张三 3
2012-01-02 李四 1
2012-01-03 王五 1
2012-01-05 王五 2
2012-01-13 张三 1
2012-01-21 张三 1
2012-01-22 张三 1
理论上,01-13应该是4, 01-22应该是2,这是为什么呢

追答

假设,每周7天
2012-01-01 张三,第1周的第1天

那么,
2012-01-13 张三,第2周的第6天 第2周只有一个张三出现啊,所以是1,不是4
2012-01-21 张三,第3周的第7天
2012-01-22 张三,第4周的第1天 第4周第一个出现的张三啊,所以是1,不是2

追问

我的意思不是按照第一周或者第二周的,而是按照列最近俩个TIME 来计算时间差,超过7天就重新来排

另外,非常感谢你,我会再加悬赏,非常感谢
===========

您好,关于我的要求,可否解决?

参考技术B if OBJECT_ID('tb') is not null
drop table tb
go
create table tb(Name varchar(10),Time varchar(10))
insert into tb values('张三','1/1/2012')
insert into tb values('李四','1/2/2012')
insert into tb values('王五','1/3/2012')
insert into tb values('张三','1/4/2012')
insert into tb values('王五','1/5/2012')
insert into tb values('张三','1/6/2012')
insert into tb values('张三','1/13/2012')
insert into tb values('张三','1/21/2012')

select * ,ROW_NUMBER()over(partition by Name order by Name,Time ) as 'Repeat Contacts' from tb

/*
Name Time Repeat Contacts
---------- ---------- --------------------
李四 1/2/2012 1
王五 1/3/2012 1
王五 1/5/2012 2
张三 1/1/2012 1
张三 1/13/2012 2
张三 1/21/2012 3
张三 1/4/2012 4
张三 1/6/2012 5

(8 行受影响) */追问

这个我已经实现了
select *,
rid=ROW_NUMBER() over (partition by [Name] order by [Time] asc)
from temp.dbo.test_1

能否帮忙确认下,如何以时间段为区间,也就是每隔七天,重新进行一次rownumber 标序

参考技术C 这个本行数据, 还要依赖 本行的日期 与 上一行的日期的, 有点折腾。

数据库是 Oracle ? 还是 SQL Server 啊 ?追问

SQL,所以才来求助- - ,能否帮忙support一下?我再追加分- -,急

追答

create table tb(Name varchar(10), [Time] date)

数据和 xiaozhuimeng 的一样.

with myCte AS (
SELECT
Name,
MIN( [TIME] ) as [TIME],
1 as step
FROM
tb
GROUP BY
Name
UNION ALL

SELECT
tb.Name,
tb.[TIME],
CASE WHEN DATEDIFF ( dd, myCte.[TIME], tb.[TIME] ) > 7 THEN myCte.step + 1
ELSE myCte.step END AS step
FROM
myCte
JOIN tb
ON ( myCte.Name = tb.Name
AND myCte.[TIME] myCte.TIME
AND tb.TIME > t2.TIME
)
)
SELECT
Name, [TIME],
ROW_NUMBER()over(partition by Name, step order by Name,Time,step ) as 'Repeat Contacts'
FROM
myCte
ORDER BY
Name, [TIME];

基于 OVER PARTITION BY 子句的 SQL 计算列

【中文标题】基于 OVER PARTITION BY 子句的 SQL 计算列【英文标题】:SQL Calculated Column based on OVER PARTITION BY Clause 【发布时间】:2020-03-27 09:53:11 【问题描述】:

我有一个基于 PARTITION BY 跨某些列的 ROW NUMBER 视图。 ROW NUMBER 从 0 开始,并根据来自各种文件的 ssis 输入为每条记录递增, 对于每个具有唯一文件名和日期组合的文件,会有从 0 到 n 的行号。

现在我需要创建一个新列,使用行号对值进行计算。

RowNumber Value Filename FileDate  
0           500   datax   20200301  
1           200   datax   20200301  
2           100   datax   20200301  
0           600   datax   20200302  
1           200   datax   20200302  
2           200   datax   20200302  
3           100   datax   20200302  
4           200   datax   20200302  
0           700   datay   20200303  
1           500   datay   20200303 

我想要一个新列,将每个 RowNumber n 中的值除以 RowNumber 0 中的值,用于其唯一分区

NEWVALUE = (Value @ RowNumber N / Value at RowNumber 0)

RowNumber Value  NEWVALUE          Filename FileDate  
0           500   NULL             datax   20200301  
1           200   0.400            datax   20200301  
2           100   0.200            datax   20200301  
0           600   NULL             datax   20200302  
1           200   0.333            datax   20200302  
2           200   0.333            datax   20200302  
3           100   0.167            datax   20200302  
4           300   0.500            datax   20200302  
0           700   NULL             datay   20200303  
1           500   0.714            datay   20200303  

谁知道怎么做

【问题讨论】:

“跨一些列” - 哦,天哪,我们猜猜 - 是跨 FileDate 吗?我可以猜到ORDER BYValue 降序。尚不清楚Filename 是否相关,但如果您只是向我们展示了实际 ROW_NUMBER,这一切都会变得容易很多 看看FIRST_VALUE 作为@Larnu,建议您只需要一个窗口函数作为FIrST_VALUE。参考docs.microsoft.com/en-us/sql/t-sql/functions/… @404,OP 不需要分区中的前一个值,而是第一个值。 FIRST_VALUE 成功了,谢谢@Gordon。 【参考方案1】:

像其他人建议的那样,ROWNUM = 0 相当于 FIRST_VALUE

棘手的一点是为 NEWVALUE 添加一个 null 而不是 1,您将不得不求助于子查询。

SELECT 
RowNumber,Value,CASE WHEN RowNumber=0 THEN NULL else NEWVALUE end as NEWVALUE, FileName,FileDate
FROM (
  select ROW_NUMBER() OVER (PArtition BY FileDate ORDER BY Value DESC) -1 as RowNumber,
  Value,
  Value * 1.0 / FIRST_VALUE(Value) OVER (PArtition BY FileDate ORDER BY Value DESC) as NEWVALUE
  ,FileName
  ,FileDate
  from Data
  ) t

这里是 sqlfiddle 如果你想玩它http://sqlfiddle.com/#!18/2bdca4/1

【讨论】:

【参考方案2】:

一种方法使用条件聚合:

select ( value * 1.0 /
         max(case when rownumber = 0 then value end) over (partition by filename, filedate)
         value
       ) as ratio

我猜测分区组是基于filenamefilegroup

或者使用first_value():

select ( value * 1.0 /
         first_value(value) over (partition by filename, filedate order by rownumber)
         value
       ) as ratio

要将其中任何一个放入视图中,您需要一个子查询。

如果排序是基于最大值,那么你可以使用类似的逻辑(并且省去子查询):

select ( value * 1.0 /
         max(value) over (partition by filename, filedate)  * 1.0
         value
       ) as ratio

【讨论】:

以上是关于SQL rownumber partition 取范围数据进行分组的主要内容,如果未能解决你的问题,请参考以下文章

基于 OVER PARTITION BY 子句的 SQL 计算列

转转转---ROWNUMBER() OVER( PARTITION BY COL1 ORDER BY COL2)用法

ROWNUMBER() OVER( PARTITION BY COL1 ORDER BY COL2)用法,先分组,然后在组内排名,分组计算等

sql 找到在SQL中PARTITION的用法,分组取某一条记录

sql 分组取最新的数据sqlserver巧用row_number和partition by分组取top数据

oracle sql中根据其他表中的计数重新启动rownumber