我正在考虑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行来获得运算符的效果,尽管语法稍微笨拙:
?> 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
类型查询).
当列具有非唯一值时,限制输出行是无用的,并且语法变得更加笨拙:
?> 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进行两次表扫描.这是可以理解的,因为单个表扫描意味着对临时结果使用未知量的存储.
假设您需要包含值及其后继值的所有对:
?> 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优化器不能没有相关的子查询,而我们人类会更清楚.也许在@...
有线操作的特殊处理的帮助下,优化器会进行单次扫描吗?
这是类似的,但是在两个表中,其中一个表具有非唯一索引:
?> 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优化器也没有优化掉子查询,尽管(可能有一点提示@<
)它可以.
(在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 | +----+------+----+------+
......这是另一个例子,这次是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 |
我知道以下警告:
该@...
运营商是"非本地",因为他们需要的操作数的所有可能值的知识.这似乎不是上述示例中所示类型的所有条件中的问题,但在其他地方可能是一个问题(尽管我还没有找到一个其他子查询无法解决的例子).
@...
与不同的运营商不同,运营商@
不具有传递性.但是,他们与<>
运营商共享这个属性.
完全利用@...
运算符可能意味着引入新的索引和表访问类型(如示例中所讨论的).
请注意,这个问题不是讨论的起点.我正在寻找@...
运算符之类的东西不在标准中,也不在我所知的任何SQL方言中的原因 - 我希望这些原因与我忽略的这些运算符的定义和/或实现的某些问题有关.
我知道其中一个原因是"Occam的剃须刀"(多余的非正式的必要条件),但是,正如我试图在上面展示的那样,这里的多元化也带来了一些优点(简洁和易于优化).我正在寻找更有力的理由.
(2014-03-31) ,,@>
和可能成为,,和或类似的(读:第一较大/更少[等于]),以便不与所述的既定用途碰撞标识符前缀.@<
@>=
@<=
|>
|<
|>=
|<=
@
我很困惑这是否是这个问题的适当论坛.但是,这些运算符不存在的原因是它们不是特别有用,而其他ANSI SQL功能取而代之.
首先,在on
我知道的每个数据库中,子句中的比较运算符都在where
和case
子句中可用.目前尚不清楚这些运营商将如何在这些背景下使用.
其次,运营商没有具体说明在关系的情况下该做什么.返回所有行?但是当这样的运算符的用户只期望一行时,这将返回多行.
第三,ANSI标准功能,例如row_number()
可以生成相同的结果.虽然它可能不是这个特定问题的最佳选择,但它更为通用.和标准.
顺便说一下,Postgres具有很好的功能distinct on()
,通常比分析功能等效.
我暗地想要一种新join
类型,lookup join
如果匹配多条记录,那将会失败.但是,我不太确定为此目的应该改变整个语言.
暂时搁置你提议的补充的优点,你实际上只有一个问题:
问题是:是否有一些很好的理由说明为什么这样的运营商不存在?
任何其他缺席功能的原因相同:因为还没有人实现它.
现在,由于以下各项的组合,没有人实现它的原因很可能是没有人觉得需要一个特殊的操作员.
它有用的情况并不常见,并且每个可以想象的任务用新的运算符污染语言都不是一个好主意.
使用现有语法实现相同任务的变通方法既简单又充足.
如果人们确实认为该功能值得实现,他们可能会使用@
与运算符一部分不同的语法.
简洁是很好的,但这似乎不是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