db2分页sql
我敢肯定,到目前为止,您已经以多种方式弄错了。 而且您可能很快将无法正确处理。 那么,当您可以实施业务逻辑时,为什么还要在SQL调整上浪费您的宝贵时间呢?
让我解释…
直到最近的SQL:2008标准 ,MySQL用户才知道的LIMIT .. OFFSET
被标准化为以下简单语句:
SELECT *
FROM BOOK
OFFSET 2 ROWS
FETCH NEXT 1 ROWS ONLY
是。 关键字太多了。
SQL确实是一种非常冗长的语言。 就个人而言,我们真的很喜欢MySQL / PostgreSQLLIMIT .. OFFSET
子句的简洁性,这就是为什么我们为jOOQ DSL API选择它的原因 。
在SQL中:
SELECT * FROM BOOK LIMIT 1 OFFSET 2
在jOOQ中:
select().from(BOOK).limit(1).offset(2);
现在,当您是SQL框架供应商时,或者在滚动自己的内部SQL抽象时,您可能会考虑标准化此简洁的小子句。 这是数据库中固有支持偏移分页的两种口味:
-- MySQL, H2, HSQLDB, Postgres, and SQLite
SELECT * FROM BOOK LIMIT 1 OFFSET 2
-- CUBRID supports a MySQL variant of the
-- LIMIT .. OFFSET clause
SELECT * FROM BOOK LIMIT 2, 1
-- Derby, SQL Server 2012, Oracle 12, SQL:2008
SELECT * FROM BOOK
OFFSET 2 ROWS FETCH NEXT 1 ROWS ONLY
-- Ingres. Eek, almost the standard. Almost!
SELECT * FROM BOOK
OFFSET 2 FETCH FIRST 1 ROWS ONLY
-- Firebird
SELECT * FROM BOOK ROWS 2 TO 3
-- Sybase SQL Anywhere
SELECT TOP 1 ROWS START AT 3 * FROM BOOK
-- DB2 (without OFFSET)
SELECT * FROM BOOK FETCH FIRST 1 ROWS ONLY
-- Sybase ASE, SQL Server 2008 (without OFFSET)
SELECT TOP 1 * FROM BOOK
到目前为止,一切都很好。 这些都可以处理。 一些数据库将偏移量放在限制之前,另一些数据库则将限制放在偏移量之前,并且T-SQL系列将整个TOP
子句放在SELECT
列表之前。 这很容易模仿。 现在呢:
- Oracle 11g及以下
- SQL Server 2008及更低版本
- 具有偏移量的DB2
当您使用google搜索时,您会发现数百万种方法可以在那些较旧的数据库中模拟OFFSET .. FETCH
。 最佳解决方案始终涉及:
- 在Oracle中使用带有
ROWNUM
筛选的双嵌套派生表 - 在SQL Server和DB2中使用带有
ROW_NUMBER()
筛选的单嵌套派生表格
因此,您正在模仿它。
您认为您会做对吗?
让我们来解决一些您可能没有想到的问题。
首先,Oracle。 Oracle显然想创建一个最大的供应商锁定,只有苹果最近推出了Swift才超过了。 这就是为什么ROWNUM
解决方案的性能最佳,甚至优于基于SQL:2003标准窗口函数的解决方案的原因。 不相信吗? 阅读有关Oracle偏移分页性能的这篇非常有趣的文章 。
因此,Oracle中的最佳解决方案是:
-- PostgreSQL syntax:
SELECT ID, TITLE
FROM BOOK
LIMIT 1 OFFSET 2
-- Oracle equivalent:
SELECT *
FROM (
SELECT b.*, ROWNUM rn
FROM (
SELECT ID, TITLE
FROM BOOK
) b
WHERE ROWNUM <= 3 -- (1 + 2)
)
WHERE rn > 2
那真的等效吗?
当然不是。 您正在选择其他列,即rn
列。 在大多数情况下,您可能并不在意,但是如果您想进行有限的子查询以与IN
谓词一起使用怎么办?
-- PostgreSQL syntax:
SELECT *
FROM BOOK
WHERE AUTHOR_ID IN (
SELECT ID
FROM AUTHOR
LIMIT 1 OFFSET 2
)
-- Oracle equivalent:
SELECT *
FROM BOOK
WHERE AUTHOR_ID IN (
SELECT * -- Ouch. These are two columns!
FROM (
SELECT b.*, ROWNUM rn
FROM (
SELECT ID
FROM AUTHOR
) b
WHERE ROWNUM <= 3
)
WHERE rn > 2
)
因此,如您所见,您将不得不执行一些更复杂SQL转换。 如果您要手动模拟LIMIT .. OFFSET
,则可以将ID
列修补到子查询中:
SELECT *
FROM BOOK
WHERE AUTHOR_ID IN (
SELECT ID -- better
FROM (
SELECT b.ID, ROWNUM rn -- better
FROM (
SELECT ID
FROM AUTHOR
) b
WHERE ROWNUM <= 3
)
WHERE rn > 2
)
所以,更像是吧? 但是,由于您不是每次都手动编写此代码,因此您将开始创建自己的漂亮的内部SQL框架,该框架涵盖到目前为止所遇到的2-3个用例,对吗?
你能行的。 因此,您将自动regex-search-replace列名以产生上述内容。
所以现在,对吗?
当然不是! 因为您可以在顶级SELECT
包含不明确的列名,但不能在嵌套选择中包含。 如果要这样做:
-- PostgreSQL syntax:
-- Perfectly valid repetition of two ID columns
SELECT BOOK.ID, AUTHOR.ID
FROM BOOK
JOIN AUTHOR
ON BOOK.AUTHOR_ID = AUTHOR.ID
LIMIT 1 OFFSET 2
-- Oracle equivalent:
SELECT *
FROM (
SELECT b.*, ROWNUM rn
FROM (
-- Ouch! ORA-00918: column ambiguously defined
SELECT BOOK.ID, AUTHOR.ID
FROM BOOK
JOIN AUTHOR
ON BOOK.AUTHOR_ID = AUTHOR.ID
) b
WHERE ROWNUM <= 3
)
WHERE rn > 2
不。 而且,由于您有多个ID
实例,因此手动修补前面示例中的ID列的技巧不起作用。 并且将列重命名为随机值是很麻烦的,因为您自己的内部数据库框架的用户希望接收定义良好的列名称。 即ID
和… ID
。
因此,解决方案是将列重命名两次。 在每个派生表中一次:
-- Oracle equivalent:
-- Rename synthetic column names back to original
SELECT c1 ID, c2 ID
FROM (
SELECT b.c1, b.c2, ROWNUM rn
FROM (
-- synthetic column names here
SELECT BOOK.ID c1, AUTHOR.ID c2
FROM BOOK
JOIN AUTHOR
ON BOOK.AUTHOR_ID = AUTHOR.ID
) b
WHERE ROWNUM <= 3
)
WHERE rn > 2
但是现在,我们完成了吗?
当然不是! 如果您将这样的查询加倍嵌套怎么办? 您是否考虑将ID
列重命名为合成名称,然后再重新命名? ……让我们留在这里,谈论完全不同的事情:
SQL Server 2008是否可以使用相同的功能?
当然不是! 在SQL Server 2008中,最流行的方法是使用窗口函数。 即ROW_NUMBER()
。 因此,让我们考虑:
-- PostgreSQL syntax:
SELECT ID, TITLE
FROM BOOK
LIMIT 1 OFFSET 2
-- SQL Server equivalent:
SELECT b.*
FROM (
SELECT ID, TITLE,
ROW_NUMBER() OVER (ORDER BY ID) rn
FROM BOOK
) b
WHERE rn > 2 AND rn <= 3
就这样吧?
当然不是!
好的,我们已经遇到了这个问题。 我们不应该选择*
,因为在我们将其用作IN
谓词的子查询的情况下,这会生成过多的列。 因此,让我们考虑使用综合列名称的正确解决方案:
-- SQL Server equivalent:
SELECT b.c1 ID, b.c2 TITLE
FROM (
SELECT ID c1, TITLE c2,
ROW_NUMBER() OVER (ORDER BY ID) rn
FROM BOOK
) b
WHERE rn > 2 AND rn <= 3
但是现在我们明白了,对不对?
做出有根据的猜测: 不!
如果您在原始查询中添加ORDER BY
子句,会发生什么情况?
-- PostgreSQL syntax:
SELECT ID, TITLE
FROM BOOK
ORDER BY SOME_COLUMN
LIMIT 1 OFFSET 2
-- Naive SQL Server equivalent:
SELECT b.c1 ID, b.c2 TITLE
FROM (
SELECT ID c1, TITLE c2,
ROW_NUMBER() OVER (ORDER BY ID) rn
FROM BOOK
ORDER BY SOME_COLUMN
) b
WHERE rn > 2 AND rn <= 3
现在,这在SQL Server中不起作用。 子查询不允许具有ORDER BY
子句,除非它们也具有TOP
子句(或SQL Server 2012中的OFFSET .. FETCH
子句)。
好的,我们可以使用TOP 100 PERCENT
进行调整,以使SQL Server满意。
-- Better SQL Server equivalent:
SELECT b.c1 ID, b.c2 TITLE
FROM (
SELECT TOP 100 PERCENT
ID c1, TITLE c2,
ROW_NUMBER() OVER (ORDER BY ID) rn
FROM BOOK
ORDER BY SOME_COLUMN
) b
WHERE rn > 2 AND rn <= 3
现在,根据SQL Server,这是正确SQL,尽管您不能保证在查询执行后派生表的顺序将继续存在。 很可能是由于某种影响再次改变了顺序。
如果要在外部查询中按SOME_COLUMN
进行排序, SOME_COLUMN
必须再次转换SQL语句以添加另一个综合列:
-- Better SQL Server equivalent:
SELECT b.c1 ID, b.c2 TITLE
FROM (
SELECT TOP 100 PERCENT
ID c1, TITLE c2,
SOME_COLUMN c99,
ROW_NUMBER() OVER (ORDER BY ID) rn
FROM BOOK
) b
WHERE rn > 2 AND rn <= 3
ORDER BY b.c99
确实开始变得有点讨厌。 让我们猜一下是否:
这是正确的解决方案!
当然不是! 如果原始查询中包含DISTINCT
怎么办?
-- PostgreSQL syntax:
SELECT DISTINCT AUTHOR_ID
FROM BOOK
LIMIT 1 OFFSET 2
-- Naive SQL Server equivalent:
SELECT b.c1 AUTHOR_ID
FROM (
SELECT DISTINCT AUTHOR_ID c1,
ROW_NUMBER() OVER (ORDER BY AUTHOR_ID) rn
FROM BOOK
) b
WHERE rn > 2 AND rn <= 3
现在,如果一位作家写了几本书怎么办? 是的, DISTINCT
关键字应该删除这些重复项,并且有效地,PostgreSQL查询将首先正确删除重复项,然后应用LIMIT
和OFFSET
。
但是, ROW_NUMBER()
谓词在 DISTINCT
可以再次删除它们之前总是生成不同的行号。 换句话说, DISTINCT
无效。
-- Better SQL Server equivalent:
SELECT b.c1 AUTHOR_ID
FROM (
SELECT DISTINCT AUTHOR_ID c1,
DENSE_RANK() OVER (ORDER BY AUTHOR_ID) rn
FROM BOOK
) b
WHERE rn > 2 AND rn <= 3
在此处阅读有关此技巧的更多信息:
SQL技巧:row_number()是SELECT,而density_rank()是SELECT DISTINCT 。
请注意, ORDER BY
子句必须包含SELECT
字段列表中的所有列。 显然,这会将SELECT DISTINCT
字段列表中可接受的列限制为窗口函数的ORDER BY
子句中允许的列(例如,没有其他窗口函数)。
我们当然也可以尝试使用通用表表达式来解决此问题,或者我们考虑
另一个问题?
当然是!
您甚至不知道窗口函数的ORDER BY
子句中的列应该是什么? 您是否刚刚随机选择了任何一栏? 如果该列上没有索引该怎么办,您的窗口函数仍会执行吗?
当原始的SELECT
语句还具有ORDER BY
子句时,答案很容易,那么您应该采用该子句(如果适用,还要加上SELECT DISTINCT
子句中的所有列)。
但是,如果您没有任何ORDER BY
子句怎么办?
还有另一把戏! 使用“常量”变量:
-- Better SQL Server equivalent:
SELECT b.c1 AUTHOR_ID
FROM (
SELECT AUTHOR_ID c1,
ROW_NUMBER() OVER (ORDER BY @@version) rn
FROM BOOK
) b
WHERE rn > 2 AND rn <= 3
是的,您需要使用一个变量,因为在SQL Server的那些ORDER BY
子句中不允许使用常量。 痛苦的,我知道。
我们完成了吗?
可能不是! 但是,我们可能已经涵盖了大约99%的常见案例和边缘案例。 现在,我们可以睡个好觉了。
注意,所有这些SQL转换都是在jOOQ中实现的。 jOOQ是唯一认真对待SQL(带有所有缺点和警告)SQL抽象框架,它对所有这些疯狂行为进行了标准化。
如开头所述,使用jOOQ,您只需编写:
// Don't worry about general emulation
select().from(BOOK).limit(1).offset(2);
// Don't worry about duplicate column names
// in subselects
select(BOOK.ID, AUTHOR.ID)
.from(BOOK)
.join(AUTHOR)
.on(BOOK.AUTHOR_ID.eq(AUTHOR.ID))
.limit(1).offset(2);
// Don't worry about invalid IN predicates
select()
.from(BOOK)
.where(BOOK.AUTHOR_ID).in(
select(AUTHOR.ID)
.from(AUTHOR)
.limit(1).offset(2)
);
// Don't worry about the ROW_NUMBER() vs.
// DENSE_RANK() distinction
selectDistinct(AUTHOR_ID)
.from(BOOK).limit(1).offset(2);
使用jOOQ,您可以像编写PostgreSQL一样出色地编写Oracle SQL或Transact SQL! …不必完全跳起SQL船 ,而是继续使用JPA。
键集分页
现在,当然,如果您正在阅读我们的博客或我们的合作伙伴博客SQL Performance Explained ,那么现在您应该知道, OFFSET
分页通常首先是一个不好的选择。 您应该知道,键集分页几乎总是优于OFFSET
分页。
在此处,了解jOOQ如何使用SEEK子句原生支持键集分页 。
db2分页sql