| NULL 特性 | MySQL | Oracle | SQL Server | PostgreSQL | SQLite |
|---|---|---|---|---|---|
| 三值逻辑 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| 空值比较 | IS [NOT] NULL、expr <=> NULL | IS [NOT] NULL、'' IS NULL |
IS [NOT] NULL | IS [NOT] NULL、IS [NOT] DISTINCT FROM NULL | IS [NOT] NULL |
| NOT IN (NULL) | 不返回结果 | 不返回结果 | 不返回结果 | 不返回结果 | 不返回结果 |
| 函数/表达式 NULL 参数 | 结果为 NULL | 结果为 NULL、CONCAT 函数和 || 例外 | 结果为 NULL、CONCAT 函数例外 | 结果为 NULL、CONCAT 函数例外 | 结果为 NULL |
| 聚合函数 | 忽略 NULL 数据、COUNT(*) 除外 | 忽略 NULL 数据、COUNT(*) 除外 | 忽略 NULL 数据、COUNT(*) 除外 | 忽略 NULL 数据、COUNT(*) 除外 | 忽略 NULL 数据、COUNT(*) 除外 |
| DISTINCT、GROUP BY、PARTITION BY、UNION | 所有空值分为一组 | 所有空值分为一组 | 所有空值分为一组 | 所有空值分为一组 | 所有空值分为一组 |
| ORDER BY | 默认空值最小 | 默认空值最大、支持 NULLS FIRST | LAST | 默认空值最小 | 默认空值最大、支持 NULLS FIRST | LAST | 默认空值最小、支持 NULLS FIRST | LAST |
| COALESCE 函数、NULLIF 函数 | ✔️、IFNULL(expr1, expr2)、IF(expr1, expr2, expr3) | ✔️、NVL(expr1, expr2)、NVL2(expr1, expr2, expr3) | ✔️、ISNULL(expr1, expr2) | ✔️ | ✔️ |
| 唯一约束允许多个空值 | ✔️ | ✔️ | ❌ | ✔️ | ✔️ |
| 检查约束允许插入空值 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
'')也不相同,原因和上面类似。但是 Oracle 是一个例外,我们会在下文具体讨论。
在大多数编程语言中,访问 null 值通常会导致错误;但是 SQL 不会出错,只是会影响到运算的结果而已。
引入三值逻辑主要是为了支持 NULL,因为 NULL 代表的是未知数据。因此,SQL 中的逻辑运算与(AND)、或(OR)以及非(NOT)的结果如下:
| AND | 真 | 假 | 未知 |
|---|---|---|---|
| 真 | 真 | 假 | 未知 |
| 假 | 假 | 假 | 假 |
| 未知 | 未知 | 假 | 未知 |
| OR | 真 | 假 | 未知 |
|---|---|---|---|
| 真 | 真 | 真 | 真 |
| 假 | 真 | 假 | 未知 |
| 未知 | 真 | 未知 | 未知 |
| NOT | 结果 |
|---|---|
| 真 | 假 |
| 假 | 真 |
| 未知 | 未知 |
?SQL 中的 WHERE、HAVING 以及 CASE WHEN 子句只返回逻辑运算结果为真的数据,不返回结果为假或未知的数据。
NULL = 0
NULL <> 0
NULL <= 0
NULL = NULL
NULL != NULL
NULL 与任何值都不相等,甚至两个 NULL 也不想等;因为我们不能说两个未知的值相同,也不能说它们不相同。
⚠️对于比较运算而言,NULL 和 NULL 不相同;但是某些 SQL 子句中的 NULL 值被看作相同的值,例如 GROUP BY。具体参考下文。那么,如何判断一个值是否是 NULL 呢?为此,SQL 引入了两个谓词(WHERE 子句):
IS NULL和IS NOT NULL。以下示例用于查找 manager 为空的员工:
-- 使用比较运算符判断空值
SELECT employee_id, first_name, last_name, manager_id
FROM employees
WHERE manager_id = NULL;
employee_id|first_name|last_name|manager_id|
-----------|----------|---------|----------|
-- 使用 IS NULL 判断空值
SELECT employee_id, first_name, last_name, manager_id
FROM employees
WHERE manager_id IS NULL;
employee_id|first_name|last_name|manager_id|
-----------|----------|---------|----------|
100|Steven |King | |
其中,第一个查询使用比较运算符判断空值,不会返回任何结果;第二个查询使用 IS NULL 判断空值,返回了正确的结果。
除了标准的IS [NOT] NULL之外,还有一些数据库扩展的运算符可以用于空值比较:
-- MySQL
SELECT employee_id, first_name, last_name, manager_id
FROM employees
WHERE manager_id <=> NULL;
employee_id|first_name|last_name|manager_id|
-----------|----------|---------|----------|
100|Steven |King | |
-- PostgreSQL
SELECT employee_id, first_name, last_name, manager_id
FROM employees
WHERE manager_id IS NOT DISTINCT FROM NULL;
employee_id|first_name|last_name|manager_id|
-----------|----------|---------|----------|
100|Steven |King | |
MySQL 中的<=>可以用于等值比较,支持两个 NULL 值;PostgreSQL 中的IS [NOT] DISTINCT FROM可以用于等值比较,支持两个 NULL 值。
以下查询的结果也不会返回任何结果:
SELECT employee_id, first_name, last_name, manager_id
FROM employees
WHERE (1 = NULL) OR (1 != NULL);
因为根据上面的三值逻辑,两个未知结果的 OR 运算最终还是未知。
前文我们说过,空字符串不是 NULL;但是 Oracle 中的空字符串被看作 NULL。例如:
-- Oracle
SELECT 1
FROM dual
WHERE '' IS NULL;
VAL|
---|
1|
-- 其他数据库
SELECT 1 AS val
WHERE '' IS NULL;
val|
---|
当然,我们如果使用等值(=)运算符判断空字符串与 NULL,结果仍然为空。
SELECT employee_id, first_name, last_name, manager_id
FROM employees
WHERE 1 NOT IN (NULL, 2);
因为上面的条件实际上等价于:
SELECT employee_id, first_name, last_name, manager_id
FROM employees
WHERE 1 != NULL AND 1 != 2;
1 不等于 NULL 的结果是未知,1 不等于 2 的结果是真,未知和真的 AND 运算结果还是未知。
⚠️如果使用 NOT IN,一定要确保括号中的值不会出现 NULL;或者尽量使用 NOT EXISTS。
SELECT ABS(NULL), 1 + NULL
FROM employees
WHERE employee_id = 100;
ABS(NULL)|1 + NULL|
---------|--------|
[NULL]| [NULL]|
一个未知值的绝对值仍然未知,1 加上一个未知值结果还是未知。
但是一个常见的例外是字符串与 NULL 的连接:
-- Oracle、SQL Server、PostgreSQL
SELECT CONCAT('Hello', NULL)
FROM employees
WHERE employee_id = 100;
CONCAT('HELLO',NULL)|
--------------------|
Hello |
-- MySQL
SELECT CONCAT('Hello', NULL)
FROM employees
WHERE employee_id = 100;
CONCAT('Hello', NULL)|
---------------------|
[NULL]|
Oracle 将 NULL 看作空字符串,所以查询结果为“Hello”;SQL Server 和 PostgreSQL 虽然区分了 NULL 和空字符串,但是 CONCAT 函数中这两者等价;MySQL 中 NULL 参数导致 CONCAT 函数结果为 NULL;SQLite 没有提供 CONCAT 函数。
另外,Oracle 中的 || 也将 NULL 看作空字符串;其他数据库 || 中的 NULL 将参数会产生 NULL 结果;SQL Server 中使用 + 连接字符串,NULL 参数将会产生 NULL 结果。
聚合函数(SUM、COUNT、AVG 等)通常会在进行计算之前删除 NULL 数据:
SELECT SUM(salary + commission_pct) sum1,
SUM(salary) + SUM(commission_pct) sum2,
COUNT(salary),
COUNT(commission_pct)
FROM employees;
SUM1 |SUM2 |COUNT(SALARY)|COUNT(COMMISSION_PCT)|
--------|--------|-------------|---------------------|
311507.8|691423.8| 107| 35|
第一个 SUM 函数返回的是 salary 和 commission_pct 都不为空的数据总和;第而个 SUM 函数返回的是 salary 不为空的数据总和加上 commission_pct 不为空的数据总和,所以比第一个数据大;COUNT 函数结果显示 salary 有 107 条记录不为空,commission_pct 只有 35 条记录不为空。
如果输入数据都是 NULL 值,除了 COUNT 函数之外的其他聚合函数返回 NULL:
SELECT COUNT(*), COUNT(commission_pct), AVG(commission_pct), SUM(commission_pct)
FROM employees
WHERE commission_pct IS NULL;
COUNT(*)|COUNT(COMMISSION_PCT)|AVG(COMMISSION_PCT)|SUM(COMMISSION_PCT)|
--------|---------------------|-------------------|-------------------|
72| 0| [NULL]| [NULL]|
COUNT(*) 总是返回数据的行数,不受空值的影响;COUNT(commission_pct) 返回了零;AVG 和 SUM 返回了 NULL。
SELECT DISTINCT commission_pct
FROM employees;
commission_pct|
--------------|
[NULL]|
0.40|
0.30|
0.20|
0.25|
0.15|
0.35|
0.10|
SELECT commission_pct
FROM employees
GROUP BY commission_pct;
commission_pct|
--------------|
[NULL]|
0.40|
0.30|
0.20|
0.25|
0.15|
0.35|
0.10|
从上面的示例可以看出,commission_pct 为空的数据有 72 条,但是分组之后只有一个 NULL 组。
除此之外,UNION 操作符也将所有的 NULL 看作相同值:
SELECT manager_id
FROM employees
WHERE manager_id IS NULL
UNION
SELECT manager_id
FROM employees
WHERE manager_id IS NULL;
manager_id|
----------|
[NULL]|
如果将 UNION 换成 UNION ALL,查询结果将会保留 2 个 NULL 值。
SELECT employee_id, manager_id
FROM employees
WHERE employee_id IN (100, 101, 102)
ORDER BY manager_id;
-- Oracle、PostgreSQL
EMPLOYEE_ID|MANAGER_ID|
-----------|----------|
101| 100|
102| 100|
100| [NULL]|
-- MySQL、SQL Server、SQLite
employee_id|manager_id|
-----------|----------|
100| [NULL]|
101| 100|
102| 100|
其中,Oracle 和 PostgreSQL 默认将 NULL 作为最大值,升序时排在最后;MySQL、SQL Server 和 SQLite 默认将 NULL 作为最小值,升序时排在最前。
另外,Oracle、PostgreSQL 和 SQLite 提供了扩展的 NULLS FIRST 和 NULLS LAST 选项:
-- Oracle、PostgreSQL 和 SQLite
SELECT employee_id, manager_id
FROM employees
WHERE employee_id IN (100, 101, 102)
ORDER BY manager_id NULLS FIRST;
employee_id|manager_id|
-----------|----------|
100| [NULL]|
101| 100|
102| 100|
我们也可以使用 CASE 表达式实现类似的效果。以下示例与 NULLS LAST 作用相同,而且所有数据库都可以使用:
SELECT employee_id, manager_id
FROM employees
WHERE employee_id IN (100, 101, 102)
ORDER BY CASE WHEN manager_id IS NULL THEN 1
ELSE 0
END,
manager_id;
employee_id|manager_id|
-----------|----------|
101| 100|
102| 100|
100| [NULL]|
首先,CASE 表达式将 manager_id 为空的数据转换为 1,非空的数据转换为 0,所以空值排在其他数据之后;第二个排序字段 manager_id 确保了非空的数据从小到大排序。
SELECT COALESCE(NULL, NULL, 3)
FROM employees
WHERE employee_id = 100;
COALESCE(NULL, NULL, 3)|
-----------------------|
3|
由于前面两个参数都是 NULL,COALESCE 最终返回了 3。
COALESCE 函数也可以使用 CASE 表达式改写如下:
CASE WHEN exp1 IS NOT NULL THEN exp1
WHEN exp2 IS NOT NULL THEN exp2
...
ELSE expN
END
NULLIF(exp1, exp2) 函数用于将指定值转换为 NULL。当 exp1 等于 exp2 时,返回 NULL;否则,返回 exp1 。NULLIF 最常见的用途是防止除零错误,例如:
SELECT 1 / NULLIF(0, 0) -- 1 / 0
FROM employees
WHERE employee_id = 100;
示例中的 NULLIF 将第一个零转换为 NULL,因此查询结果返回 NULL;如果直接使用 1 / 0,查询将会返回除零错误。MySQL 中的除零错误由 sql_mode 变量控制。
NULLIF 函数同样可以使用 CASE 表达式改写如下:
CASE WHEN exp1 = exp2 THEN NULL
ELSE exp1
END
利用 CASE 表达式,我们还可以轻松实现多个值到 NULL 的转换:
CASE WHEN expr IN (value1, value2, …)
THEN NULL
ELSE expr
END
?COALESCE 和 NULLIF 实际上是 CASE 表达式的两种缩写形式。除了标准 SQL 函数之外,数据库还提供了一些专用的函数:
CREATE TABLE t_unique(id INT UNIQUE);
INSERT INTO t_unique VALUES(1);
INSERT INTO t_unique VALUES(NULL);
INSERT INTO t_unique VALUES(NULL); -- SQL Server 产生唯一键冲突错误
-- SQL Server 除外
SELECT * FROM t_unique;
id|
------|
[NULL]|
[NULL]|
1|
-- SQL Server 除外
SELECT * FROM t_unique;
id|
------|
[NULL]|
1|
对于 SQL Server 而言,唯一约束中只允许存在一个 NULL 数据;所以第 3 个 INSERT 语句执行出错,最终只有两条记录。
如果是复合索引,情况略有不同:
CREATE TABLE t_unique2(c1 INT, c2 INT, UNIQUE(c1,c2));
INSERT INTO t_unique2 VALUES(1, 1);
INSERT INTO t_unique2 VALUES(NULL, NULL);
INSERT INTO t_unique2 VALUES(NULL, NULL); -- SQL Server 产生唯一键冲突错误
INSERT INTO t_unique2 VALUES(1, NULL);
INSERT INTO t_unique2 VALUES(1, NULL); -- Oracle 和 SQL Server 产生唯一键冲突错误
其中,SQL Server 只允许有一个记录的全部索引字段为空;如果某个字段不为空,Oracle 和 SQL Server 只允许有一个记录的其他索引字段为空。
另外,检查约束(CHECK)对于 NULL 的处理与 WHERE 条件正好相反:只要数据的检查结果不是假都可以插入成功。例如:
CREATE TABLE t_check (
c1 INT CHECK (c1 >= 0),
c2 INT CHECK (c2 >= 0),
CHECK (c1 + c2 <= 100)
);
INSERT INTO t_check VALUES (5, 5);
INSERT INTO t_check VALUES (NULL, NULL);
INSERT INTO t_check VALUES (200, NULL);
SELECT * FROM t_check;
c1 |c2 |
------|------|
5| 5|
[NULL]|[NULL]|
200|[NULL]|
如果 c1 和 c2 都有值的话,都必须大于等于零并且和值小于等于 100;c1 和 c2 都可以为空;如果其中之一为空,另一个字段的值可以大于 100。
欢迎关注❤️、点赞?、转发?!
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时
作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代
Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题
我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何
我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer
刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr
我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢
我注意到像bundler这样的项目在每个specfile中执行requirespec_helper我还注意到rspec使用选项--require,它允许您在引导rspec时要求一个文件。您还可以将其添加到.rspec文件中,因此只要您运行不带参数的rspec就会添加它。使用上述方法有什么缺点可以解释为什么像bundler这样的项目选择在每个规范文件中都需要spec_helper吗? 最佳答案 我不在Bundler上工作,所以我不能直接谈论他们的做法。并非所有项目都checkin.rspec文件。原因是这个文件,通常按照当前的惯例,只
我正在使用active_admin,我在Rails3应用程序的应用程序中有一个目录管理,其中包含模型和页面的声明。时不时地我也有一个类,当那个类有一个常量时,就像这样:classFooBAR="bar"end然后,我在每个必须在我的Rails应用程序中重新加载一些代码的请求中收到此警告:/Users/pupeno/helloworld/app/admin/billing.rb:12:warning:alreadyinitializedconstantBAR知道发生了什么以及如何避免这些警告吗? 最佳答案 在纯Ruby中:classA