单行查询没有值时返回默认值的几种处理方式

单行查询没有值时返回默认值的几种处理方式

在业务开发中,经常会遇到一种查询场景:

系统需要读取某个配置项、状态值、开关值或参数值,如果数据库中存在对应记录,则返回数据库中的值;如果不存在,则返回一个默认值。

例如:

sql 复制代码
SELECT data_value AS data
FROM bsyc_config_data
WHERE id = 'enablePay';

这个 SQL 的问题是:

如果 bsyc_config_data 表中不存在 id = 'enablePay' 的记录,那么查询结果是 空结果集 ,而不是返回 NULL

很多开发人员容易误以为可以直接使用:

sql 复制代码
SELECT COALESCE(data_value, '1') AS data
FROM bsyc_config_data
WHERE id = 'enablePay';

但这只能处理 字段值为 NULL 的情况,不能处理 整行数据不存在 的情况。


一、问题本质:NULL 和无记录不是一回事

在 SQL 中,下面两种情况完全不同:

1. 有记录,但字段值为 NULL

text 复制代码
id         data_value
---------  ----------
enablePay  NULL

这种情况下,下面的 SQL 可以生效:

sql 复制代码
SELECT COALESCE(data_value, '1') AS data
FROM bsyc_config_data
WHERE id = 'enablePay';

结果:

text 复制代码
data
----
1

2. 根本没有记录

text 复制代码
没有 id = 'enablePay' 的数据

此时 SQL:

sql 复制代码
SELECT COALESCE(data_value, '1') AS data
FROM bsyc_config_data
WHERE id = 'enablePay';

不会返回任何行。

也就是说,COALESCE 只能处理列值为 NULL,不能凭空生成一行数据。


二、方式一:使用标量子查询 + COALESCE

这是最简洁的一种方式,适合查询单个配置项。

sql 复制代码
SELECT COALESCE(
    (
        SELECT data_value
        FROM bsyc_config_data
        WHERE id = 'enablePay'
    ),
    '1'
) AS data;

含义是:

  1. 先通过子查询读取 enablePay 的配置值。
  2. 如果子查询有值,则返回 data_value
  3. 如果子查询没有记录,标量子查询结果会被视为 NULL
  4. COALESCENULL 替换成默认值 '1'

返回结果始终是一行:

text 复制代码
data
----
1

适用场景

这种方式适合:

  • 查询单个系统配置
  • 查询单个开关值
  • 查询单个参数值
  • 业务上确定只会返回一条记录

例如:

sql 复制代码
SELECT COALESCE(
    (
        SELECT data_value
        FROM bsyc_config_data
        WHERE id = 'enablePay'
    ),
    '1'
) AS enablePay;

注意事项

如果 id 不是唯一的,子查询可能返回多行,部分数据库会报错。

例如 SQL Server 会报:

text 复制代码
Subquery returned more than 1 value

因此,如果不能保证唯一性,需要限制只取一条。

MySQL:

sql 复制代码
SELECT COALESCE(
    (
        SELECT data_value
        FROM bsyc_config_data
        WHERE id = 'enablePay'
        LIMIT 1
    ),
    '1'
) AS data;

SQL Server:

sql 复制代码
SELECT COALESCE(
    (
        SELECT TOP 1 data_value
        FROM bsyc_config_data
        WHERE id = 'enablePay'
    ),
    '1'
) AS data;

三、方式二:使用 UNION ALL + NOT EXISTS

如果希望逻辑表达得更清楚,也可以使用 UNION ALL

sql 复制代码
SELECT data_value AS data
FROM bsyc_config_data
WHERE id = 'enablePay'

UNION ALL

SELECT '1' AS data
WHERE NOT EXISTS (
    SELECT 1
    FROM bsyc_config_data
    WHERE id = 'enablePay'
);

这个 SQL 的逻辑非常直观:

第一段:

sql 复制代码
SELECT data_value AS data
FROM bsyc_config_data
WHERE id = 'enablePay'

表示:如果配置存在,则返回配置值。

第二段:

sql 复制代码
SELECT '1' AS data
WHERE NOT EXISTS (
    SELECT 1
    FROM bsyc_config_data
    WHERE id = 'enablePay'
)

表示:如果配置不存在,则返回默认值 '1'

返回效果

如果表中存在配置:

text 复制代码
id         data_value
---------  ----------
enablePay  0

返回:

text 复制代码
data
----
0

如果表中不存在配置:

text 复制代码
没有 enablePay 配置

返回:

text 复制代码
data
----
1

四、UNION ALL 方式的优点

UNION ALL + NOT EXISTS 的优点是语义非常明确。

它把逻辑分成了两段:

  1. 有值时返回实际值
  2. 没值时返回默认值

这种写法特别适合动态 SQL、模板 SQL、报表 SQL、配置查询 SQL,因为阅读成本低,后期维护也比较直观。

相比 COALESCE + 子查询,它的业务意图更明显。


五、UNION 和 UNION ALL 的区别

这里建议使用 UNION ALL,不要使用 UNION

UNION

UNION 会自动去重。

sql 复制代码
SELECT '1'
UNION
SELECT '1';

结果只返回一行。

UNION ALL

UNION ALL 不会去重,性能更好。

sql 复制代码
SELECT '1'
UNION ALL
SELECT '1';

会保留所有结果。

在当前场景中,第二段 SQL 只有在第一段没有数据时才会返回默认值,因此不会出现重复结果。

所以使用 UNION ALL 更合适。


六、如果 data_value 可能为 NULL 怎么办?

前面的 UNION ALL 写法只能处理 没有记录 的情况。

如果记录存在,但是 data_valueNULL,那么返回的仍然是 NULL

例如:

text 复制代码
id         data_value
---------  ----------
enablePay  NULL

下面 SQL 会返回 NULL

sql 复制代码
SELECT data_value AS data
FROM bsyc_config_data
WHERE id = 'enablePay'

UNION ALL

SELECT '1' AS data
WHERE NOT EXISTS (
    SELECT 1
    FROM bsyc_config_data
    WHERE id = 'enablePay'
);

如果希望同时处理:

  • 没有记录,返回默认值
  • 有记录但字段为 NULL,也返回默认值

可以这样写:

sql 复制代码
SELECT COALESCE(data_value, '1') AS data
FROM bsyc_config_data
WHERE id = 'enablePay'

UNION ALL

SELECT '1' AS data
WHERE NOT EXISTS (
    SELECT 1
    FROM bsyc_config_data
    WHERE id = 'enablePay'
);

这样就更加完整。


七、推荐写法

如果是系统配置项查询,并且希望 SQL 逻辑清晰,推荐使用:

sql 复制代码
SELECT COALESCE(data_value, '1') AS data
FROM bsyc_config_data
WHERE id = 'enablePay'

UNION ALL

SELECT '1' AS data
WHERE NOT EXISTS (
    SELECT 1
    FROM bsyc_config_data
    WHERE id = 'enablePay'
);

这个写法同时解决了两个问题:

  1. 数据不存在时,返回默认值 '1'
  2. 数据存在但 data_valueNULL 时,也返回默认值 '1'

八、如果只允许返回一行

如果 bsyc_config_data 中可能存在多条 enablePay 配置,需要额外控制只返回一行。

MySQL 写法:

sql 复制代码
SELECT data
FROM (
    SELECT COALESCE(data_value, '1') AS data
    FROM bsyc_config_data
    WHERE id = 'enablePay'
    LIMIT 1
) t

UNION ALL

SELECT '1' AS data
WHERE NOT EXISTS (
    SELECT 1
    FROM bsyc_config_data
    WHERE id = 'enablePay'
);

SQL Server 写法:

sql 复制代码
SELECT data
FROM (
    SELECT TOP 1 COALESCE(data_value, '1') AS data
    FROM bsyc_config_data
    WHERE id = 'enablePay'
) t

UNION ALL

SELECT '1' AS data
WHERE NOT EXISTS (
    SELECT 1
    FROM bsyc_config_data
    WHERE id = 'enablePay'
);

不过从设计角度看,配置表中的配置项 id 应该保持唯一。

更推荐在表结构上增加唯一约束,避免后续出现脏数据。

例如:

sql 复制代码
ALTER TABLE bsyc_config_data
ADD CONSTRAINT uk_bsyc_config_data_id UNIQUE (id);

九、实际业务建议

对于配置项表,建议遵守以下设计原则:

1. 配置项必须唯一

例如:

text 复制代码
enablePay
enableSms
enableMemberCard
defaultStoreId

这类配置项不应该出现多条。

2. 默认值可以写在 SQL 层,也可以写在业务层

如果默认值属于系统级规则,可以写在 SQL 中:

sql 复制代码
SELECT COALESCE(
    (
        SELECT data_value
        FROM bsyc_config_data
        WHERE id = 'enablePay'
    ),
    '1'
) AS data;

如果默认值会随业务场景变化,建议在 Java 或服务层处理。

3. 配置表适合做缓存

配置项通常变化频率低,读取频率高。

可以在服务启动时加载到缓存,也可以结合 Redis、本地缓存或配置刷新机制来减少数据库访问。


十、总结

单行查询没有值时返回默认值,是一个非常常见的 SQL 场景。

需要注意的是:

sql 复制代码
COALESCE(data_value, '1')

只能处理字段为 NULL 的情况,不能处理没有记录的情况。

如果要在没有记录时也返回默认值,可以使用两种方式:

方式一:标量子查询 + COALESCE

sql 复制代码
SELECT COALESCE(
    (
        SELECT data_value
        FROM bsyc_config_data
        WHERE id = 'enablePay'
    ),
    '1'
) AS data;

优点是简洁,适合单值查询。

方式二:UNION ALL + NOT EXISTS

sql 复制代码
SELECT COALESCE(data_value, '1') AS data
FROM bsyc_config_data
WHERE id = 'enablePay'

UNION ALL

SELECT '1' AS data
WHERE NOT EXISTS (
    SELECT 1
    FROM bsyc_config_data
    WHERE id = 'enablePay'
);

优点是逻辑清晰,适合动态 SQL、报表 SQL 和模板 SQL。

在实际项目中,如果是配置项查询,我更推荐使用 UNION ALL + NOT EXISTS,因为它表达的业务含义更直接:

有配置用配置,没有配置用默认值。