Oracle的“Partition By”和“Row_Number”关键字

55

我有一条SQL查询语句是别人写的,我正在尝试弄清楚它的作用。请问有人能够解释一下这里的Partition ByRow_Number关键字是什么意思,并且给出一个简单的实例说明它们的作用以及为什么要使用它们吗?

Partition By的一个示例:

(SELECT cdt.*,
        ROW_NUMBER ()
        OVER (PARTITION BY cdt.country_code, cdt.account, cdt.currency
              ORDER BY cdt.country_code, cdt.account, cdt.currency)
           seq_no
   FROM CUSTOMER_DETAILS cdt);
4个回答

120

PARTITION BY用于分隔数据集,这使得您能够独立地处理与之相关的行集(如ROW_NUMBER()、COUNT()、SUM()等)。在您的查询中,相关的数据集由具有相似cdt.country_code、cdt.account和cdt.currency的行组成。当您在这些列上进行分区并对它们应用ROW_NUMBER时,这些组合/集上的其他列将从ROW_NUMBER接收连续编号。

但是,如果您按一些唯一的数据进行分区,并在其上放置一个row_number,那么查询就会很有趣,因为它将只产生相同的数字。这就像您在一个保证唯一的分区上进行ORDER BY。例如,将GUID视为cdt.country_code、cdt.account、cdt.currency的唯一组合。

newid()生成GUID,那么您对此表达式有什么期望呢?

select
   hi,ho,
   row_number() over(partition by newid() order by hi,ho)
from tbl;

……所有已分区的行(没有进行分区,每行都在其自己的行中分区)的行号都被设为 1。

基本上,你应该在非唯一列上进行分区。在 OVER 子句上使用 ORDER BY 时,PARTITION BY 需要有一个非唯一组合,否则所有行号将变为 1。

举个例子,这是你的数据:

create table tbl(hi varchar, ho varchar);

insert into tbl values
('A','X'),
('A','Y'),
('A','Z'),
('B','W'),
('B','W'),
('C','L'),
('C','L');

那么这类似于您的查询:

select
   hi,ho,
   row_number() over(partition by hi,ho order by hi,ho)
from tbl;

那会是什么输出?

HI  HO  COLUMN_2
A   X   1
A   Y   1
A   Z   1
B   W   1
B   W   2
C   L   1
C   L   2

你看到了HI HO的组合吗?前三行有独特的组合,因此它们被设置为1,B行具有相同的W,因此具有不同的ROW_NUMBERS,HI C行也是如此。

那么,为什么需要在那里使用ORDER BY呢?如果之前的开发人员只想在类似数据上放置一行号(例如,HI B所有数据都是B-W,B-W),他可以这样做:

select
   hi,ho,
   row_number() over(partition by hi,ho)
from tbl;

然而,遗憾的是,Oracle(以及Sql Server)不允许没有ORDER BY的分区;而在PostgreSQL中,PARTITION上的ORDER BY是可选的:http://www.sqlfiddle.com/#!1/27821/1

select
   hi,ho,
   row_number() over(partition by hi,ho)
from tbl;

你的分区上的 ORDER BY 看起来有点多余,这不是之前开发人员的错,有些数据库不允许没有 ORDER BYPARTITION,他可能找不到一个好的候选列进行排序。如果 PARTITION BY 列和 ORDER BY 列都相同,只需移除 ORDER BY,但由于某些数据库不允许这样做,你可以这样做:

SELECT cdt.*,
        ROW_NUMBER ()
        OVER (PARTITION BY cdt.country_code, cdt.account, cdt.currency
              ORDER BY newid())
           seq_no
   FROM CUSTOMER_DETAILS cdt
你找不到一个好的列来对类似的数据进行排序?那么干脆乱序排列吧,划分后的数据值相同。例如,你可以使用GUID(在SQL Server中使用newid())。这样就能产生与之前开发人员生成的相同输出,遗憾的是有些数据库不允许在没有ORDER BY的情况下使用PARTITION

但实际上,我不明白为什么要在相同的组合上加上数字(例如上面的B-W,B-W),这给数据库带来了冗余数据的印象。这让我想起了这个问题:如何从表格的相同记录列表中获取一条唯一记录?表格中没有唯一约束条件

看到PARTITION BY和ORDER BY使用相同的列时,真的很玄妙,不容易推断代码的意图。

在线测试:http://www.sqlfiddle.com/#!3/27821/6


但正如dbaseman也注意到的那样,按相同的列进行分区和排序是无用的。

你有一组像这样的数据:

create table tbl(hi varchar, ho varchar);

insert into tbl values
('A','X'),
('A','X'),
('A','X'),
('B','Y'),
('B','Y'),
('C','Z'),
('C','Z');

然后你按 hi,ho 进行分区,接着再按 hi,ho 进行排序。对于相似的数据没有标号的意义 :-) http://www.sqlfiddle.com/#!3/29ab8/3

select
   hi,ho,
   row_number() over(partition by hi,ho order by hi,ho) as nr
from tbl;

输出:

HI  HO  ROW_QUERY_A
A   X   1
A   X   2
A   X   3
B   Y   1
B   Y   2
C   Z   1
C   Z   2

看到了吗?为什么需要在同一组合上放置行号?你将分析三个A、X,两个B、Y和两个C、Z上的什么? :-)


你只需要在非唯一列上使用“PARTITION”,然后按非唯一列的“唯一”列进行排序。例子会让它更清晰:

create table tbl(hi varchar, ho varchar);

insert into tbl values
('A','D'),
('A','E'),
('A','F'),
('B','F'),
('B','E'),
('C','E'),
('C','D');

select
   hi,ho,
   row_number() over(partition by hi order by ho) as nr
from tbl;

PARTITION BY hi 作用于非唯一列,在每个分区内,根据其唯一列(ho)进行排序,ORDER BY ho

输出:

HI  HO  NR
A   D   1
A   E   2
A   F   3
B   E   1
B   F   2
C   D   1
C   E   2

那个数据集更有意义。

实时测试:http://www.sqlfiddle.com/#!3/d0b44/1

这与您的查询非常相似,两者在PARTITION BY和ORDER BY上使用了相同的列:

select
   hi,ho,
   row_number() over(partition by hi,ho order by hi,ho) as nr
from tbl;

这是输出结果:

HI  HO  NR
A   D   1
A   E   1
A   F   1
B   E   1
B   F   1
C   D   1
C   E   1

看到了吗?一点感觉都没有吗?

在线测试:http://www.sqlfiddle.com/#!3/d0b44/3


最后这可能是正确的查询:

SELECT cdt.*,
     ROW_NUMBER ()
     OVER (PARTITION BY cdt.country_code, cdt.account -- removed: cdt.currency
           ORDER BY 
               -- removed: cdt.country_code, cdt.account, 
               cdt.currency) -- keep
        seq_no
FROM CUSTOMER_DETAILS cdt

13

我经常使用row_number()作为从查询语句中快速丢弃重复记录的方法。只需添加一个where子句。像这样...

select a,b,rn 
  from (select a, b, row_number() over (partition by a,b order by a,b) as rn           
          from table) 
 where rn=1;

这正是我所见过的用法。 - MarvinM

7
该查询根据国家代码、账户和货币选择每个行号。因此,具有国家代码“US”,账户“XYZ”和货币“$USD”的行将分别被分配从1-n的行号;对于结果集中这些列的每种组合都是如此。
这个查询有点有趣,因为“order by”子句根本没有起作用。每个分区中的所有行都具有相同的国家代码、账户和货币,因此按这些列排序没有意义。因此,在这个特定的查询中分配的最终行号将是不可预测的。
希望这能帮到你...

4

我知道这是一个旧的线程,但PARTITION相当于GROUP BY而不是ORDER BY。在这个函数中,ORDER BY只是一种通过添加序列号将冗余转化为唯一性的方法。或者您可以在引用该函数的别名列时通过WHERE子句消除其他冗余记录。然而,在SELECT语句中使用DISTINCT可能会在这方面实现相同的效果。


有一些特殊情况下,您不能使用DISTINCT关键字。比如当您的列中有Blob类型时。https://stackoverflow.com/questions/26721525/oracle-how-can-i-perform-a-select-distinct-on-all-fields-except-a-blob - Nora Na

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接