停止尝试使用内部DB框架模拟SQL OFFSET分页!

我敢肯定,到目前为止,您已经以多种方式弄错了。 而且您可能很快将无法正确处理。 那么,当您可以实施业务逻辑时,为什么还要在SQL调整上浪费您的宝贵时间呢?

让我解释…

直到最近的SQL:2008标准 ,MySQL用户才知道的LIMIT .. OFFSET被标准化为以下简单语句:

SELECT * 
FROM BOOK 
OFFSET 2 ROWS 
FETCH NEXT 1 ROWS ONLY

是。 关键字太多了。

Nv9Oixa

SQL确实是一种非常冗长的语言。 就个人而言,我们真的很喜欢MySQL / PostgreSQL的LIMIT .. 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

( 请注意,您可以在DB2中启用各种替代语法 )

当您使用google搜索时,您会发现数百万种方法可以在那些较旧的数据库中模拟OFFSET .. FETCH 。 最佳解决方案始终涉及:

  • 在Oracle中使用带有ROWNUM筛选的双嵌套派生表
  • 在SQL Server和DB2中使用带有ROW_NUMBER()筛选的单嵌套派生表格

因此,您正在模仿它。

xqBjBrO

您认为您会做对吗?

让我们来解决一些您可能没有想到的问题。

首先,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 rnFROM (SELECT ID, TITLEFROM BOOK) bWHERE ROWNUM <= 3 -- (1 + 2)
)
WHERE rn > 2

那真的是等价的吗?

当然不是。 您正在选择其他列,即rn列。 在大多数情况下,您可能并不在意,但是如果您想进行有限的子查询以与IN谓词一起使用怎么办?

-- PostgreSQL syntax:
SELECT *
FROM BOOK
WHERE AUTHOR_ID IN (SELECT IDFROM AUTHORLIMIT 1 OFFSET 2
)-- Oracle equivalent:
SELECT *
FROM BOOK
WHERE AUTHOR_ID IN (SELECT * -- Ouch. These are two columns!FROM (SELECT b.*, ROWNUM rnFROM (SELECT IDFROM AUTHOR) bWHERE ROWNUM <= 3)WHERE rn > 2
)

因此,如您所见,您将不得不执行一些更复杂的SQL转换。 如果您要手动模拟LIMIT .. OFFSET ,则可以将ID列修补到子查询中:

SELECT *
FROM BOOK
WHERE AUTHOR_ID IN (SELECT ID -- betterFROM (SELECT b.ID, ROWNUM rn -- betterFROM (SELECT IDFROM AUTHOR) bWHERE ROWNUM <= 3)WHERE rn > 2
)

所以,更像是吧? 但是由于您并不是每次都手动编写此代码,因此您将开始创建自己的漂亮的内部SQL框架,该框架涵盖到目前为止遇到的2-3个用例,对吗?

你能行的。 因此,您将自动regex-search-replace列名以产生上述内容。

所以现在,对吗?

当然不是! 因为您可以在顶级SELECT包含不明确的列名,但不能在嵌套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 rnFROM (-- Ouch! ORA-00918: column ambiguously definedSELECT BOOK.ID, AUTHOR.IDFROM BOOKJOIN AUTHORON BOOK.AUTHOR_ID = AUTHOR.ID) bWHERE 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 rnFROM (-- synthetic column names hereSELECT BOOK.ID c1, AUTHOR.ID c2FROM BOOKJOIN AUTHORON BOOK.AUTHOR_ID = AUTHOR.ID) bWHERE 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) rnFROM 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) rnFROM 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) rnFROM BOOKORDER 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 PERCENTID c1, TITLE c2,ROW_NUMBER() OVER (ORDER BY ID) rnFROM BOOKORDER 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 PERCENTID c1, TITLE c2,SOME_COLUMN c99,ROW_NUMBER() OVER (ORDER BY ID) rnFROM 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) rnFROM BOOK
) b
WHERE rn > 2 AND rn <= 3

现在,如果一位作家写了几本书,会发生什么? 是的, DISTINCT关键字应该删除这些重复项,并且有效地,PostgreSQL查询将首先正确删除重复项,然后应用LIMITOFFSET

但是, ROW_NUMBER()谓词 DISTINCT可以再次删除它们之前总是生成不同的行号。 换句话说, DISTINCT无效。

幸运的是,我们可以使用以下巧妙的小技巧再次调整此SQL :

-- Better SQL Server equivalent:
SELECT b.c1 AUTHOR_ID
FROM (SELECT DISTINCT AUTHOR_ID c1,DENSE_RANK() OVER (ORDER BY AUTHOR_ID) rnFROM 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) rnFROM BOOK
) b
WHERE rn > 2 AND rn <= 3

是的,您需要使用一个变量,因为在SQL Server中的那些ORDER BY子句中不允许使用常量。 痛苦,我知道。

在此处阅读有关此@@ version技巧的更多信息 。

我们完成了吗?

可能不是! 但是,我们可能已经涵盖了大约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。

jooq在Java中写SQL的最佳方法

键集分页

当然,现在,如果您一直在阅读我们的博客或我们的合作伙伴博客SQL Performance Explained ,那么现在您应该知道,首先, OFFSET分页通常是一个错误的选择。 您应该知道,键集分页几乎总是优于OFFSET分页。

在此处,了解jOOQ如何使用SEEK子句原生支持键集分页 。

翻译自: https://www.javacodegeeks.com/2014/06/stop-trying-to-emulate-sql-offset-pagination-with-your-in-house-db-framework.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/363530.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

和朱晔一起复习Java并发(五):并发容器和同步器

和朱晔一起复习Java并发&#xff08;五&#xff09;&#xff1a;并发容器和同步器 本节我们先会来复习一下java.util.concurrent下面的一些并发容器&#xff0c;然后再会来简单看一下各种同步器。 ConcurrentHashMap和ConcurrentSkipListMap的性能 首先&#xff0c;我们来测试一…

Hive:使用Apache Hive查询客户最喜欢的搜索查询和产品视图计数

这篇文章涵盖了使用Apache Hive查询存储在Hadoop下的搜索点击数据。 我们将以示例的方式生成有关总产品浏览量的客户排名靠前的搜索查询和统计信息。 继续之前的文章 使用大数据分析客户产品搜索点击次数 &#xff0c; Flume&#xff1a;使用Apache Flume收集客户产品搜索点…

expdp错误案例

转自:https://www.cnblogs.com/kerrycode/p/3960328.html Oracle数据泵(Data Dump)使用过程当中经常会遇到一些奇奇怪怪的错误案例&#xff0c;下面总结一些自己使用数据泵(Data Dump)过程当中遇到的问题以及解决方法。都是在使用过程中遇到的问题&#xff0c;以后陆续遇到数据…

HashSet源码分析:JDK源码系列

1.简介 继续分析源码&#xff0c;上一篇文章把HashMap的分析完毕。本文开始分析HashSet简单的介绍一下。 HashSet是一个无重复元素集合&#xff0c;内部使用HashMap实现&#xff0c;所以HashMap的特征耶继承了下来。存储的元素是无序的并且HashSet允许使用空的元素。 HashSet是…

修改左侧导航显示样式(转载自Sunmoonfire's artistic matrix)

这是一片非常好的文章&#xff0c;修改下CSS就可以改变左侧导航栏的样式&#xff0c;在网上找了一些都是要写代码的。怕连接失效&#xff0c;所以直接将文章考了过来&#xff0c;希望作者原谅&#xff0c;如有不妥&#xff0c;请通知一声&#xff0c;我会将文章删掉&#xff01…

tf.argmax()以及axis

tf.argmax()表示返回最大值的索引号&#xff0c;axis为0 &#xff0c;表示返回每列最大值索引号。axis为1 &#xff0c;表示返回每行最大值索引号 结果为 转载于:https://www.cnblogs.com/san333/p/10507402.html

jquery ajax 上传文件 demo,Jquery+AJAX上传文件,无刷新上传并重命名文件

index.htmlAjax上传图片Ajax上传图片function upload(){var form new FormData(document.getElementById("form"));$.ajax({url:"upload.php",type:"post",data:form,cache: false,processData: false,contentType: false,success:function(dat…

Meet Fabric8:基于Camel和ActiveMQ的开源集成平台

面料8 Fabric8是Red Hat的JBoss Fuse产品的Apache 2.0许可上游社区。 这是一个基于Apache ActiveMQ &#xff0c; Camel &#xff0c; CXF &#xff0c; Karaf &#xff0c; HawtIO等的集成平台。 它提供了自动化的配置和部署管理&#xff0c;以帮助使部署变得容易&#xff0…

Django之web框架的本质

web框架的本质及自定义web框架 我们可以这样理解&#xff1a;所有的Web应用本质上就是一个socket服务端&#xff0c;而用户的浏览器就是一个socket客户端&#xff0c;基于请求做出响应&#xff0c;客户都先请求&#xff0c;服务端做出对应的响应&#xff0c;按照http协议的请求…

Springboot 系列(十三)使用邮件服务

在我们这个时代&#xff0c;邮件服务不管是对于工作上的交流&#xff0c;还是平时的各种邮件通知&#xff0c;都是一个十分重要的存在。Java 从很早时候就可以通过 Java mail 支持邮件服务。Spring 更是对 Java mail 进行了进一步的封装&#xff0c;抽象出了 JavaMailSender. 后…

服务器能否只做c盘系统,我的云服务器只有一个c盘

我的云服务器只有一个c盘 内容精选换一换检查Pkey是否一致。查看弹性云服务器内部分配到的Pkey&#xff1a;cat /sys/class/infiniband/mlx5_0/ports/1/pkeys/* | grep -v "0x0000"检查Pkey是否一致如果环境中查出来的Pkey只有一个&#xff0c;请联系技术支持人员。如…

单例模式(C++实现)

RAII运用 只能在栈上创建对象 只能在堆上创建的对象 单例模式 设计模式 懒汉模式 解决线程安全 优化 饿汉模式 饿汉和懒汉的区别

Flume:使用Apache Flume收集客户产品搜索点击数据

这篇文章涵盖了使用Apache flume收集客户产品搜索点击并使用hadoop和elasticsearch接收器存储信息。 数据可能包含不同的产品搜索事件&#xff0c;例如基于不同方面的过滤&#xff0c;排序信息&#xff0c;分页信息&#xff0c;以及进一步查看的产品以及某些被客户标记为喜欢的…

vue-cli使用swiper4在ie以及safari报错

vue-cli项目中&#xff0c;通过npm run swiper --save-dev安装的是swiper4版本的插件&#xff0c;这样安装以后在谷歌火狐等浏览器都可以正常运行&#xff0c;但是在safari浏览器&#xff08;可能是版本太低&#xff09;还有ie&#xff08;9,10,11&#xff09;打开会报错&#…

电脑内部,小贴士:电脑内部连接标准

小贴士&#xff1a;电脑内部连接标准在介绍电脑内部连接标准之前&#xff0c;首先应该了解一下电脑内部接线的种类&#xff0c;以便分类处置。电脑内部尽管五颜六色的导线&#xff0c;其中导线的种类可以分为3 类&#xff0c;即电源线、信号线和控制线&#xff0c;而控制线又常…

太快了,太变态了:什么会影响Java中的方法调用性能?

那么这是怎么回事&#xff1f; 让我们从一个简短的故事开始。 几周前&#xff0c;我提议对Java核心libs邮件列表进行更改 &#xff0c;以覆盖当前final一些方法。 这刺激了一些讨论主题-其中之一是其中一个性能回归通过采取这是一个方法被引入的程度final免遭停止它final 。 我…

1、dubbo的概念

Dubbo是什么&#xff1f; Dubbo是阿里巴巴SOA服务化治理方案的核心框架&#xff0c;每天为2,000个服务提供3,000,000,000次访问量支持&#xff0c;并被广泛应用于阿里巴巴集团的各成员站点。Dubbo[]是一个分布式服务框架&#xff0c;致力于提供高性能和透明化的RPC远程服务调用…

轻云服务器的性能,腾讯云轻量应用服务器性能评测(以香港地域为例)

腾讯云轻量应用服务器香港节点24元/月&#xff0c;价格很不错&#xff0c;ForeignServer来说说腾讯云轻量服务器香港地域性能评测&#xff0c;包括腾讯云轻量应用服务器CPU型号配置、网络延迟速度测试&#xff1a;腾讯云香港轻量应用服务器性能评测腾讯云轻量应用服务器地域可选…

vue2.5.2版本 :MAC设置应用在127.0.0.1:80端口访问; 并将127.0.0.1指向www.yours.com ;问题“ Invalid Host header”

0.设置自己的host文件&#xff0c;将127.0.0.1指向自己想要访问的域名 127.0.0.1 www.yours.com 1.MAC设置应用在127.0.0.1&#xff1a;80端口访问&#xff1a; config/index.js目录下修改host和port 然后sudo运行npm run dev:(mac的80端口是被自身分享应用占用的&#xff0c…

Google Android 平台正式开源

Google 推出移动设备软件平台 Android 之时&#xff0c;曾向开发者开放 SDK 包&#xff0c;并许诺将在开源许可模式下开放其全部代码&#xff0c;今天&#xff0c;Google 与其合作伙伴&#xff0c;在 Open Handset Alliance 兑现了其承诺&#xff0c;用户现在可以正式下载 Andr…