MySQL中有没有复制SQL Server函数的好方法ROW_NUMBER()
?
例如:
SELECT
col1, col2,
ROW_NUMBER() OVER (PARTITION BY col1, col2 ORDER BY col3 DESC) AS intRow
FROM Table1
然后,例如,我可以添加一个条件以将限制限制intRow
为1,以获得col3
每(col1, col2)
对最高的一行。
MySQL中有没有复制SQL Server函数的好方法ROW_NUMBER()
?
例如:
SELECT
col1, col2,
ROW_NUMBER() OVER (PARTITION BY col1, col2 ORDER BY col3 DESC) AS intRow
FROM Table1
然后,例如,我可以添加一个条件以将限制限制intRow
为1,以获得col3
每(col1, col2)
对最高的一行。
Answers:
我想要每对(col1,col2)对具有最高col3的行。
这是一个按组的最大值,这是最常提出的SQL问题之一(因为它看起来应该很容易,但实际上并非如此)。
我经常为null-self-join充实:
SELECT t0.col3
FROM table AS t0
LEFT JOIN table AS t1 ON t0.col1=t1.col1 AND t0.col2=t1.col2 AND t1.col3>t0.col3
WHERE t1.col1 IS NULL;
“获取表中没有其他匹配col1,col2的行具有更高col3的行。” (您会注意到这一点,并且如果多行具有相同的col1,col2,col3,则大多数其他按组最大的解决方案将返回多行。如果这是一个问题,则可能需要一些后处理。)
SELECT t1.id FROM test t1 LEFT JOIN test t2 ON t1.id>t2.id WHERE t2.id IS NULL;
它是否不需要n*n/2 + n/2
IS NULL比较才能找到单行?我没有看到任何优化吗?我试图在另一个话题中问比尔类似的问题,但他似乎忽略了它。
SELECT t0.col3 FROM table AS t0 WHERE NOT EXISTS (select 1 from table AS t1 ON t0.col1=t1.col1 AND t0.col2=t1.col2 AND t1.col3>t0.col3)
MySQL中没有排名功能。您可以获得的最接近的结果是使用一个变量:
SELECT t.*,
@rownum := @rownum + 1 AS rank
FROM YOUR_TABLE t,
(SELECT @rownum := 0) r
那么在我的情况下该如何工作?我需要两个变量,col1和col2分别一个?当col1更改时,col2需要以某种方式重置。
是。如果是Oracle,则可以使用LEAD函数在下一个值达到峰值。幸运的是,Quassnoi涵盖了您需要在MySQL中实现的逻辑。
SELECT @row_num:=@row_num+1 AS row_number, t.id FROM (SELECT * FROM table1 WHERE col = 264 ORDER BY id) t, (SELECT @row_num:=0) var;
我总是最终遵循这种模式。给定此表:
+------+------+
| i | j |
+------+------+
| 1 | 11 |
| 1 | 12 |
| 1 | 13 |
| 2 | 21 |
| 2 | 22 |
| 2 | 23 |
| 3 | 31 |
| 3 | 32 |
| 3 | 33 |
| 4 | 14 |
+------+------+
您可以得到以下结果:
+------+------+------------+
| i | j | row_number |
+------+------+------------+
| 1 | 11 | 1 |
| 1 | 12 | 2 |
| 1 | 13 | 3 |
| 2 | 21 | 1 |
| 2 | 22 | 2 |
| 2 | 23 | 3 |
| 3 | 31 | 1 |
| 3 | 32 | 2 |
| 3 | 33 | 3 |
| 4 | 14 | 1 |
+------+------+------------+
通过运行此查询,不需要定义任何变量:
SELECT a.i, a.j, count(*) as row_number FROM test a
JOIN test b ON a.i = b.i AND a.j >= b.j
GROUP BY a.i, a.j
希望有帮助!
<
,>
,<=
,>=
手柄CHAR和字母顺序VARCHAR数据类型; 我希望,这正是您想要的。
row_numbers <= 2
。非常感谢您的回答Mosty,非常好!
SELECT
@i:=@i+1 AS iterator,
t.*
FROM
tablename AS t,
(SELECT @i:=0) AS foo
查阅本文,它展示了如何在MySQL中通过分区模拟SQL ROW_NUMBER()。我在WordPress实现中遇到了这种情况。我需要ROW_NUMBER(),但它不在那儿。
http://www.explodybits.com/2011/11/mysql-row-number/
本文中的示例使用按字段划分单个分区。要按其他字段进行分区,您可以执行以下操作:
SELECT @row_num := IF(@prev_value=concat_ws('',t.col1,t.col2),@row_num+1,1) AS RowNumber
,t.col1
,t.col2
,t.Col3
,t.col4
,@prev_value := concat_ws('',t.col1,t.col2)
FROM table1 t,
(SELECT @row_num := 1) x,
(SELECT @prev_value := '') y
ORDER BY t.col1,t.col2,t.col3,t.col4
使用concat_ws可处理null。我使用int,date和varchar针对3个字段对此进行了测试。希望这可以帮助。请查看该文章,因为它可以分解此查询并进行解释。
limit 18446744073709551615
到force order by
子句。
concat_ws
带空字符串''
是危险的:concat_ws('',12,3) = concat_ws('',1,23)
。最好使用一些分隔符'_'
或使用@Kenneth Xu解决方案。
从头至尾MySQL 8.0.0
,您可以本机使用窗口函数。
窗口功能。
MySQL现在支持窗口函数,对于查询的每一行,它使用与该行相关的行来执行计算。这些包括诸如RANK(),LAG()和NTILE()之类的函数。此外,现在可以将几个现有的聚合函数用作窗口函数;例如SUM()和AVG()。
返回其分区内当前行的编号。行数范围从1到分区行数。
ORDER BY影响行编号的顺序。如果没有ORDER BY,行号是不确定的。
演示:
CREATE TABLE Table1(
id INT AUTO_INCREMENT PRIMARY KEY, col1 INT,col2 INT, col3 TEXT);
INSERT INTO Table1(col1, col2, col3)
VALUES (1,1,'a'),(1,1,'b'),(1,1,'c'),
(2,1,'x'),(2,1,'y'),(2,2,'z');
SELECT
col1, col2,col3,
ROW_NUMBER() OVER (PARTITION BY col1, col2 ORDER BY col3 DESC) AS intRow
FROM Table1;
我也将对Mosty Mostacho的解决方案投一票,对他的查询代码进行较小的修改:
SELECT a.i, a.j, (
SELECT count(*) from test b where a.j >= b.j AND a.i = b.i
) AS row_number FROM test a
将会得到相同的结果:
+------+------+------------+
| i | j | row_number |
+------+------+------------+
| 1 | 11 | 1 |
| 1 | 12 | 2 |
| 1 | 13 | 3 |
| 2 | 21 | 1 |
| 2 | 22 | 2 |
| 2 | 23 | 3 |
| 3 | 31 | 1 |
| 3 | 32 | 2 |
| 3 | 33 | 3 |
| 4 | 14 | 1 |
+------+------+------------+
对于表:
+------+------+
| i | j |
+------+------+
| 1 | 11 |
| 1 | 12 |
| 1 | 13 |
| 2 | 21 |
| 2 | 22 |
| 2 | 23 |
| 3 | 31 |
| 3 | 32 |
| 3 | 33 |
| 4 | 14 |
+------+------+
唯一的区别是查询不使用JOIN和GROUP BY,而是依赖嵌套的select。
我将定义一个函数:
delimiter $$
DROP FUNCTION IF EXISTS `getFakeId`$$
CREATE FUNCTION `getFakeId`() RETURNS int(11)
DETERMINISTIC
begin
return if(@fakeId, @fakeId:=@fakeId+1, @fakeId:=1);
end$$
然后我可以做:
select getFakeId() as id, t.* from table t, (select @fakeId:=0) as t2;
现在您没有子查询,而视图中没有该子查询。
行号功能不能被模仿。您可能会得到预期的结果,但是在某个阶段您很可能会感到失望。这是mysql文档说的:
对于其他语句,例如SELECT,您可能会得到期望的结果,但这不能保证。在下面的语句中,您可能认为MySQL将首先评估@a,然后再进行赋值:SELECT @a,@a:= @ a + 1,...; 但是,涉及用户变量的表达式的求值顺序是不确定的。
问候,乔治。
MariaDB 10.2正在实现“窗口函数”,包括RANK(),ROW_NUMBER()和其他一些功能:
https://mariadb.com/kb/zh-CN/mariadb/window-functions/
根据本月在Percona Live上的一次演讲,对它们进行了合理的优化。
语法与问题中的代码相同。
我看不到涵盖“ PARTITION BY”部分的任何简单答案,所以这是我的:
SELECT
*
FROM (
select
CASE WHEN @partitionBy_1 = l THEN @row_number:=@row_number+1 ELSE @row_number:=1 END AS i
, @partitionBy_1:=l AS p
, t.*
from (
select @row_number:=0,@partitionBy_1:=null
) as x
cross join (
select 1 as n, 'a' as l
union all
select 1 as n, 'b' as l
union all
select 2 as n, 'b' as l
union all
select 2 as n, 'a' as l
union all
select 3 as n, 'a' as l
union all
select 3 as n, 'b' as l
) as t
ORDER BY l, n
) AS X
where i > 1
在这个简单的示例中,我只放置了一个,但是您可以包含多个“ PARTITION BY”部分
CASE WHEN @partitionBy_1 = part1 AND @partitionBy_2 = part2 [...] THEN @row_number:=@row_number+1 ELSE @row_number:=1 END AS i
, @partitionBy_1:=part1 AS P1
, @partitionBy_2:=part2 AS P2
[...]
FROM (
SELECT @row_number:=0,@partitionBy_1:=null,@partitionBy_2:=null[...]
) as x
有点晚了,但也可能对寻求答案的人有所帮助...
在行/行数之间的示例-可以在任何SQL中使用的递归查询:
WITH data(row_num, some_val) AS
(
SELECT 1 row_num, 1 some_val FROM any_table --dual in Oracle
UNION ALL
SELECT row_num+1, some_val+row_num FROM data WHERE row_num < 20 -- any number
)
SELECT * FROM data
WHERE row_num BETWEEN 5 AND 10
/
ROW_NUM SOME_VAL
-------------------
5 11
6 16
7 22
8 29
9 37
10 46
也有点晚,但今天我有同样的需求,所以我在Google上进行搜索,最后在Pinal Dave的文章http://blog.sqlauthority.com/2014/03/09/mysql-reset-row中找到了一种简单的通用方法每个组分区的编号按行号/
我想关注Paul最初的问题(这也是我的问题),因此我将解决方案作为一个工作示例进行了总结。
因为我们要划分两列,所以我将在迭代期间创建一个SET变量,以识别是否启动了新的组。
SELECT col1, col2, col3 FROM (
SELECT col1, col2, col3,
@n := CASE WHEN @v = MAKE_SET(3, col1, col2)
THEN @n + 1 -- if we are in the same group
ELSE 1 -- next group starts so we reset the counter
END AS row_number,
@v := MAKE_SET(3, col1, col2) -- we store the current value for next iteration
FROM Table1, (SELECT @n := 0, @v := NULL) r -- helper table for iteration with startup values
ORDER BY col1, col2, col3 DESC -- because we want the row with maximum value
) x WHERE row_number = 1 -- and here we select exactly the wanted row from each group
3表示在MAKE_SET的第一个参数上,我希望两个值都在SET中(3 = 1 | 2)。当然,如果我们没有两个或更多个构成组的列,则可以消除MAKE_SET操作。构造完全相同。这对我来说是必需的。非常感谢Pinal Dave的清晰示范。
ORDER BY
在子查询中可以忽略(请参阅mariadb.com/kb/en/mariadb/…)。建议的解决方案是添加LIMIT 18446744073709551615
到子查询中,这将强制排序。但是,这可能会导致性能问题,并且对于真正
这也可以是一个解决方案:
SET @row_number = 0;
SELECT
(@row_number:=@row_number + 1) AS num, firstName, lastName
FROM
employees
从8.0+版本开始,MySQL支持ROW_NUMBER()。
如果使用MySQL 8.0或更高版本,请检查ROW_NUMBER()函数。否则,您将模拟ROW_NUMBER()函数。
row_number()是一种排序函数,它返回一行的序号,第一行从1开始。
对于旧版本,
SELECT t.*,
@rowid := @rowid + 1 AS ROWID
FROM TABLE t,
(SELECT @rowid := 0) dummy;
重要提示:请考虑升级到MySQL 8+,并使用已定义和记录的ROW_NUMBER()函数,并抛弃与功能受限的旧版MySQL相关的旧代码
现在是这些黑客之一:
此处大部分/全部使用查询中变量的答案似乎忽略了文档所说的事实(表述):
不要依赖SELECT列表中从上到下顺序评估的项目。不要在一个SELECT项中分配变量,而在另一项中使用它们
因此,他们有可能会得出错误的答案,因为他们通常会做
select
(row number variable that uses partition variable),
(assign partition variable)
如果对这些进行了自下而上的评估,则行号将停止工作(无分区)
因此,我们需要使用具有一定执行顺序的东西。在以下情况下输入案例:
SELECT
t.*,
@r := CASE
WHEN col = @prevcol THEN @r + 1
WHEN (@prevcol := col) = null THEN null
ELSE 1 END AS rn
FROM
t,
(SELECT @r := 0, @prevcol := null) x
ORDER BY col
如大纲ld所示,prevcol的分配顺序很重要-必须先将prevcol与当前行的值进行比较,然后才能从当前行为其分配值(否则它将是当前行的col值,而不是前一行的col值) 。
这是如何组合在一起的:
评估第一个WHEN。如果此行的col与上一行的col相同,则@r递增并从CASE返回。此返回的led值存储在@r中。MySQL的一个功能是赋值将分配给@r的新值返回到结果行中。
对于结果集的第一行,@prevcol为null(在子查询中被初始化为null),因此该谓词为false。每次col更改时,该第一个谓词也返回false(当前行与上一行不同)。这将导致对第二个WHEN进行评估。
第二个WHEN谓词始终为false,它纯粹是为@prevcol分配一个新值而存在。由于此行的col与上一行的col不同(我们知道这是因为如果相同,则将使用第一个WHEN),因此我们必须分配新值以使其下次进行测试。因为进行了赋值,然后将赋值的结果与null进行比较,并且等于null的任何内容均为false,所以该谓词始终为false。但是至少要评估它的工作是保留此行中col的值,因此可以根据下一行的col值对其进行评估
因为第二个WHEN为假,这意味着在我们要按(col)划分的列已更改的情况下,正是ELSE为@r提供了新值,从而从1重新开始编号
我们将遇到以下情况:
SELECT
t.*,
ROW_NUMBER() OVER(PARTITION BY pcol1, pcol2, ... pcolX ORDER BY ocol1, ocol2, ... ocolX) rn
FROM
t
具有以下一般形式:
SELECT
t.*,
@r := CASE
WHEN col1 = @pcol1 AND col2 = @pcol2 AND ... AND colX = @pcolX THEN @r + 1
WHEN (@pcol1 := pcol1) = null OR (@pcol2 := col2) = null OR ... OR (@pcolX := colX) = null THEN null
ELSE 1
END AS rn
FROM
t,
(SELECT @r := 0, @pcol1 := null, @pcol2 := null, ..., @pcolX := null) x
ORDER BY pcol1, pcol2, ..., pcolX, ocol1, ocol2, ..., ocolX
脚注:
pcol中的p表示“分区”,ocol中的o表示“顺序”-在一般形式中,我从变量名中删除了“ prev”以减少视觉混乱
周围的括号(@pcolX := colX) = null
很重要。没有它们,您将为@pcolX分配null,事情将停止工作
折衷的是,结果集也必须按分区列排序,以便比较前一列。因此,您不能将行号按一列排序,而将结果集按另一列排序。您也许可以使用子查询来解决此问题,但是我相信文档还指出,除非使用LIMIT,否则子查询的排序可能会被忽略,这可能会影响性能
除了测试该方法是否有效外,我还没有深入研究它,但是如果存在第二个WHEN中的谓词将被优化的风险(与null相比的任何事物都是null / false,那么为什么要麻烦运行赋值)而不执行,它也会停止。根据我的经验,这似乎没有发生,但我很乐意接受评论并提出解决方案(如果可能的话)
在创建@pcolX变量的子查询中,将创建@pcolX的空值强制转换为列的实际类型可能是明智的: select @pcol1 := CAST(null as INT), @pcol2 := CAST(null as DATE)
这不是最可靠的解决方案-但是,如果您只是想在仅包含几个不同值的字段上创建分区等级,那么当逻辑具有所需数量的变量时,在某些情况下使用它可能会很不明智。
过去,这样的事情对我有用:
SELECT t.*,
CASE WHEN <partition_field> = @rownum1 := @rownum1 + 1
WHEN <partition_field> = @rownum2 := @rownum2 + 1
...
END AS rank
FROM YOUR_TABLE t,
(SELECT @rownum1 := 0) r1, (SELECT @rownum2 := 0) r2
ORDER BY <rank_order_by_field>
;
希望有道理/有帮助!
当我们有多于一列时,这对我来说非常适合创建RowNumber。在这种情况下为两列。
SELECT @row_num := IF(@prev_value= concat(`Fk_Business_Unit_Code`,`NetIQ_Job_Code`), @row_num+1, 1) AS RowNumber,
`Fk_Business_Unit_Code`,
`NetIQ_Job_Code`,
`Supervisor_Name`,
@prev_value := concat(`Fk_Business_Unit_Code`,`NetIQ_Job_Code`)
FROM (SELECT DISTINCT `Fk_Business_Unit_Code`,`NetIQ_Job_Code`,`Supervisor_Name`
FROM Employee
ORDER BY `Fk_Business_Unit_Code`, `NetIQ_Job_Code`, `Supervisor_Name` DESC) z,
(SELECT @row_num := 1) x,
(SELECT @prev_value := '') y
ORDER BY `Fk_Business_Unit_Code`, `NetIQ_Job_Code`,`Supervisor_Name` DESC
set @i = 1;
INSERT INTO ARG_VALUE_LOOKUP(ARG_VALUE_LOOKUP_ID,ARGUMENT_NAME,VALUE,DESCRIPTION,UPDATE_TIMESTAMP,UPDATE_USER,VER_NBR,OBJ_ID)
select @i:= @i+1 as ARG_VALUE_LOOKUP_ID,ARGUMENT_NAME,VALUE,DESCRIPTION,CURRENT_TIMESTAMP,'admin',1,UUID()
FROM TEMP_ARG_VALUE_LOOKUP
order by ARGUMENT_NAME;
SELECT
col1, col2,
count(*) as intRow
FROM Table1
GROUP BY col1,col2
ORDER BY col3 desc
greatest-n-per-group
以指导您解决类似的问题。