当前位置:  开发笔记 > 编程语言 > 正文

为什么SQL中没有"第一个大于/小于[或等于]"的比较运算符?

如何解决《为什么SQL中没有"第一个大于/小于[或等于]"的比较运算符?》经验,为你挑选了2个好方法。

我正在考虑SQL中4个新比较运算符的提议.这些都是类似>,<,>=<=运营商,但真正只有当每个操作数的值,满足上,一个所有值中最接近于其他操作数的值.由于一个值​​几乎处于另一个值,我得出结论(在意识到没有first关键字,并且在丢弃unique关键字之后),一个好的选择是定义这4个新运算符:

a @> b:如果a > b没有a' < a满足a' > b,没有b' > b满足a > b'

a @< b :如果是真的 b @> a

a @>= b:如果a ? b没有a' < a满足a' ? b,没有b' > b满足a ? b'

a @<= b :如果是真的 b @>= a

问题是:是否有一些很好的理由说明为什么这样的运营商不存在?


(2014-03-20)我重新提出这个问题,因为上述表述显然不够明确:

这样的运营商不应该存在的原因是什么?


以下示例旨在找出@...操作员可能遇到的问题的起点.我将使用3个MySQL表:

create table ta (id int auto_increment, ca char, primary key(id), unique index(ca));
create table tb (id int auto_increment, cb char, primary key(id), index(cb));
create table tc (id int auto_increment, cc char, primary key(id));
insert into ta (ca) values ('A'),('E'),('I'),('O'),('U');
insert into tb (cb) values ('C'),('D'),('E'),('F'),('F'),('M'),('N'),('O'),('Z');
insert into tc (cc) values ('C'),('D'),('E'),('F'),('F'),('M'),('N'),('O'),('Z');

示例#1

当列具有唯一值时,@...可以通过将查询或子查询的输出限制为1行来获得运算符的效果,尽管语法稍微笨拙:

?> select * from ta where ca @> 'B'; -- currently not valid, equivalent to:
!> select * from ta where ca > 'B' order by ca limit 1;
+----+------+
| id | ca   |
+----+------+
|  2 | E    |
+----+------+

(limit 1是特定到MySQL,MariaDB的时,PostgreSQL等,其他RDBMS有select top 1,where rownum = 1等)

在表中,ta我们在列上有一个唯一索引ca.该指数可以被利用来获取到所选择的值以相同的速度ca = 'E'.优化器可以实现这一点,但如果没有,则可以为从所选值开始的不需要的扫描设置数据结构(MySQL explain表示这是range类型查询).

例#2

当列具有非唯一值时,限制输出行是无用的,并且语法变得更加笨拙:

?> select * from tb where cb @> 'E'; -- currently not valid, equivalent to:
!> select * from tb where cb = (select min(cb) from tb where cb > 'E');
+----+------+
| id | cb   |
+----+------+
|  4 | F    |
|  5 | F    |
+----+------+

幸运的是,如果我正确读取输出explain,MySQL足够聪明,可以优化子查询,但如果不是,则索引将被使用两次而不是一次.

对于tc没有列索引的表cc,MySQL进行两次表扫描.这是可以理解的,因为单个表扫描意味着对临时结果使用未知量的存储.

例#3

假设您需要包含值及其后继值的所有对:

?> select t1.ca as c1, t2.ca as c2
   from ta t1
   join ta t2 on t1.ca @< t2.ca; -- currently not valid, equivalent to:
!> select t1.ca as c1, t2.ca as c2
   from ta t1
   join ta t2 on t2.ca = (select min(ca) from ta where ca > t1.ca);
+------+------+
| c1   | c2   |
+------+------+
| A    | E    |
| E    | I    |
| I    | O    |
| O    | U    |
+------+------+

如果我explain正确地读取输出,MySQL优化器不能没有相关的子查询,而我们人类会更清楚.也许在@...有线操作的特殊处理的帮助下,优化器会进行单次扫描吗?

例#4

这是类似的,但是在两个表中,其中一个表具有非唯一索引:

?> select * from ta join tb on ca @< cb; -- currently not valid, equivalent to:
!> select * from ta join tb on cb = (select min(cb) from tb where cb > ca);
+----+------+----+------+
| id | ca   | id | cb   |
+----+------+----+------+
|  1 | A    |  1 | C    |
|  2 | E    |  4 | F    |
|  2 | E    |  5 | F    |
|  3 | I    |  6 | M    |
|  4 | O    |  9 | Z    |
|  5 | U    |  9 | Z    |
+----+------+----+------+

在这里,MySQL优化器也没有优化掉子查询,尽管(可能有一点提示@<)它可以.

例#5

(在2014-03-20上添加.)@...无论@对手做什么,运营商似乎都有意义.这是一个where条件中表达式的人为例子:

?> select * from ta join tb
   where round((ascii(ca)+ascii(cb))/2) @> ascii('E');
   -- currently not valid, equivalent to:
!> select * from ta join tb
   where round((ascii(ca)+ascii(cb))/2) = (
      select min(round((ascii(ca)+ascii(cb))/2)) from ta, tb
      where      round((ascii(ca)+ascii(cb))/2) > ascii('E')
   );
+----+------+----+------+
| id | ca   | id | cb   |
+----+------+----+------+
|  3 | I    |  1 | C    |
|  2 | E    |  4 | F    |
|  2 | E    |  5 | F    |
+----+------+----+------+

例#6

......这是另一个例子,这次是select表达式:

?> select *, cb @< ca
   from tb, ta; -- currently not valid, equivalent to:
!> select *, ifnull(cb = (select max(cb) from tb where cb < ca), 0) as 'cb @< ca'
   from tb, ta;
+----+------+----+------+----------+
| id | cb   | id | ca   | cb @< ca |
+----+------+----+------+----------+
|  1 | C    |  1 | A    |        0 |
|  1 | C    |  2 | E    |        0 |
|  1 | C    |  3 | I    |        0 |
|  1 | C    |  4 | O    |        0 |
|  1 | C    |  5 | U    |        0 |
|  2 | D    |  1 | A    |        0 |
|  2 | D    |  2 | E    |        1 |
| -- (omitting rows with cb @< ca equal to 0 from here on)
|  4 | F    |  3 | I    |        1 |
|  5 | F    |  3 | I    |        1 |
|  7 | N    |  4 | O    |        1 |
|  8 | O    |  5 | U    |        1 |

我知道以下警告:

警告#1

@...运营商是"非本地",因为他们需要的操作数的所有可能值的知识.这似乎不是上述示例中所示类型的所有条件中的问题,但在其他地方可能是一个问题(尽管我还没有找到一个其他子查询无法解决的例子).

警告#2

@...与不同的运营商不同,运营商@不具有传递性.但是,他们与<>运营商共享这个属性.

警告#3

完全利用@...运算符可能意味着引入新的索引和表访问类型(如示例中所讨论的).


请注意,这个问题不是讨论的起点.我正在寻找@...运算符之类的东西不在标准中,也不在我所知的任何SQL方言中的原因 - 我希望这些原因与我忽略的这些运算符的定义和/或实现的某些问题有关.

我知道其中一个原因是"Occam的剃须刀"(多余的非正式的必要条件),但是,正如我试图在上面展示的那样,这里的多元化也带来了一些优点(简洁和易于优化).我正在寻找更有力的理由.


(2014-03-31) ,,@> 和可能成为,,和或类似的(读:第一较大/更少[等于]),以便不与所述的既定用途碰撞标识符前缀.@<@>=@<=|>|<|>=|<=@



1> Gordon Linof..:

我很困惑这是否是这个问题的适当论坛.但是,这些运算符不存在的原因是它们不是特别有用,而其他ANSI SQL功能取而代之.

首先,在on我知道的每个数据库中,子句中的比较运算符都在wherecase子句中可用.目前尚不清楚这些运营商将如何在这些背景下使用.

其次,运营商没有具体说明在关系的情况下该做什么.返回所有行?但是当这样的运算符的用户只期望一行时,这将返回多行.

第三,ANSI标准功能,例如row_number()可以生成相同的结果.虽然它可能不是这个特定问题的最佳选择,但它更为通用.和标准.

顺便说一下,Postgres具有很好的功能distinct on(),通常比分析功能等效.

我暗地想要一种新join类型,lookup join如果匹配多条记录,那将会失败.但是,我不太确定为此目的应该改变整个语言.



2> Bill Karwin..:

暂时搁置你提议的补充的优点,你实际上只有一个问题:

问题是:是否有一些很好的理由说明为什么这样的运营商不存在?

任何其他缺席功能的原因相同:因为还没有人实现它.

现在,由于以下各项的组合,没有人实现它的原因很可能是没有人觉得需要一个特殊的操作员.

它有用的情况并不常见,并且每个可以想象的任务用新的运算符污染语言都不是一个好主意.

使用现有语法实现相同任务的变通方法既简单又充足.

如果人们确实认为该功能值得实现,他们可能会使用@与运算符一部分不同的语法.

简洁是很好的,但这似乎不是SQL的目标(否则SQL将用关系代数表示法编写).

非标准条款(如LIMIT MySQL,PostgreSQL,SQLite)或TOP(Microsoft,Sybase),或ROWNUM(Oracle)或FIRST(Informix,InterBase)足以满足大多数用户的需求.

使用有序窗口函数的标准语法实际上是SQL:2003规范定义的解决方案.我意识到MySQL还不支持窗口功能,但几乎所有其他品牌的RDBMS都支持它们.

如果您建议专门为MySQL添加此功能,最好使用标准语法使其与其他品牌更兼容,而不是引入新的非标准语法.


现在回答你的另一个问题:

我正在寻找这些@运营商可能遇到的真正问题.

人们立即浮现在脑海中:它不是通用的.

它处理的情况是,您只需要紧跟在比较操作数之后的单行.但是它没有处理有序窗口函数可以满足的许多其他情况:

返回窗口中的下行.

返回窗口中的最后一行.

排名而不是按行号返回前三行.

等等

它还引入了一个标准SQL中没有先例的新标点符号,它可能与某些供应商的添加冲突,例如在MySQL中添加用户变量前缀或在Microsoft SQL Server中添加前缀参数.


我在PostgreSQL 8.4中测试了你的例子,它支持窗口函数.我知道这不是PostgreSQL的当前版本,但它是CentOS repo中的默认版本,并且它足以演示窗口函数.

示例#1

select * 
from (select *, row_number() over (order by ca) as rn from ta where ca > 'B') as t
where rn = 1;

 id | ca | rn 
----+----+----
  2 | E  |  1

例#2

select * 
from (select *, rank() over (order by cb) as rk from tb where cb > 'E') as t
where rk = 1;

 id | cb | rk 
----+----+----
  5 | F  |  1
  4 | F  |  1

例#3

select t1.ca as c1, t2.ca as c2
from (select ca, row_number() over (order by ca) AS rn from ta) as t1
join (select ca, row_number() over (order by ca) AS rn from ta) as t2
  on t1.rn+1 = t2.rn;

 c1 | c2 
----+----
 A  | E
 E  | I
 I  | O
 O  | U

例#4

select *
from (select ta.id, ta.ca, tb.id, tb.cb, 
    rank() over (partition by ca order by cb) AS rk 
    from ta join tb on ca < cb) as t
where rk = 1;

 id | ca | id | cb | rn 
----+----+----+----+----
  1 | A  |  1 | C  |  1
  2 | E  |  5 | F  |  1
  2 | E  |  4 | F  |  1
  3 | I  |  6 | M  |  1
  4 | O  |  9 | Z  |  1
  5 | U  |  9 | Z  |  1


优化器是否可以做任何事情来帮助取决于优化器代码,而不是语法.我的观点是,无论好坏,即使您不喜欢它们的语法,*也是*SQL:2003标准方式来执行这些查询.
推荐阅读
小白也坚强_177
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站