如果未找到单行,则返回默认值
Posted
技术标签:
【中文标题】如果未找到单行,则返回默认值【英文标题】:Return a default value if single row is not found 【发布时间】:2013-02-25 11:44:14 【问题描述】:我有以下选择语句来获取流的下一个预定项目。如果没有匹配的行,我希望它返回一个默认值。 这是我正在使用的 SQL:
SELECT `file`
FROM `show`, `schedule`
WHERE `channel` = 1
AND `start_time` <= UNIX_TIMESTAMP()
AND `start_time` > UNIX_TIMESTAMP()-1800
AND `show`.`id` = `schedule`.`file`
ORDER BY `start_time`
DESC LIMIT 1
这应该会抓取最近安排的项目,但如果它在查询前 30 分钟之前就不会了。
但是,如果用户没有安排任何内容,我想要一个默认值,以便在流中实际播放某些内容。我尝试了以下方法:
SELECT COALESCE(`file`, 'default.webm')
FROM `show`, `schedule`...
和
SELECT IFNULL(`file`, 'default.webm')
FROM `show`, `schedule`
但是,如果没有找到任何行,它总是返回一个空结果。如何改为返回默认值?
【问题讨论】:
【参考方案1】:一种方法
SELECT IFNULL(MIN(`file`), 'default.webm') `file`
FROM `show`, `schedule`
WHERE `channel` = 1 AND `start_time` <= UNIX_TIMESTAMP()
AND `start_time` > UNIX_TIMESTAMP()-1800 AND `show`.`id` = `schedule`.`file`
ORDER BY `start_time` DESC LIMIT 1
由于您只返回一行,因此您可以使用聚合函数,在这种情况下为 MIN()
,以确保在未选择任何记录时您将获得 NULL
。然后IFNULL()
或COALESCE()
将完成它的工作。
【讨论】:
好东西!但是如何将这个概念应用于返回多行而不是像我们的例子中的一行的查询呢? 这不是一个真正好的解决方案,正如 Tomas 下面解释的那样(除非您查询主键值)。【参考方案2】:@peterm 的答案和这个答案旨在适应 SQL 逻辑,该逻辑将在结果集中返回最多一行。
他的答案旨在返回单行单列。
我的答案旨在返回包含一列或多列的单行。
基本上,您可以在与第一个 SELECT 具有相同列数的第二个 SELECT
子句中使用带有硬编码值的 UNION
。
对于 OP:
(
SELECT `file`
FROM `show`,
`schedule`
WHERE `channel` = 1
AND `start_time` BETWEEN UNIX_TIMESTAMP()-1799 AND UNIX_TIMESTAMP()
AND `show`.`id` = `schedule`.`file`
ORDER BY `start_time` DESC
LIMIT 1
) UNION (
SELECT 'default.webm'
)
ORDER BY file = 'default.webm'
LIMIT 1;
除非出现任何语法错误,否则结果集将提供一行,其中一列的键为 file
。
举个更一般的例子:(DB-Fiddle Demo)
(
SELECT Col1,Col2,Col3
FROM ExampleTable
WHERE ID='1234'
) UNION (
SELECT 'Def Val','none',''
)
ORDER BY Col1 = 'Def Val'
LIMIT 1;
结果:
如果在第一个 SELECT 中没有找到任何行,则结果集将填充第二个 SELECT 中的值。作为数组的结果集将是:
[['Col1' => 'Def Val', 'Col2' => 'none', 'Col3' => '']]
如果在第一个 SELECT 中找到一行,则在结果集中提供第一个 SELECT 值,而省略第二个 SELECT 值。结果集将是:(请参阅我的演示链接)
[['Col1' => 'A', 'Col2' => 'B', 'Col3' => 'C']]
*结果集中的关联键将由第一个 SELECT 查询中的列名/别名决定。
*后续的 SELECT 查询不需要指定列别名。
*UNION 不要求来自两个联合查询的列名相同。事实上,后续 SELECT 查询中的列名或数据源可能是anything(不同的列、函数调用等)。
(
SELECT Col1,Col2,Col3
FROM ExampleTable
WHERE ID='1234'
) UNION (
SELECT 'Def Val' AS `Fallback1`,
'none' AS `Fallback2`,
'' AS `Fallback3`
)
ORDER BY Col1 = 'Def Val'
LIMIT 1;
我的看法是,这很容易阅读,而且看起来不像是一个繁琐的查询。
感谢@LudovicKuty 发现了一个关于 UNION 生成的行顺序的潜在错误。 https://dev.mysql.com/doc/refman/8.0/en/union.html#union-order-by-limit 为了消除默认行在 found 行之前排序的可能性,请编写一个具有足够逻辑的 ORDER BY 子句,以确保默认行总是在结果集中的后面排序。将有不同的 sql-dialect-specific 语法可用于识别默认行。有的ORDER BY columnName = 'default value'
就够了,有的可能要求IF
或IIF
或CASE
等。只要你构建的逻辑默认返回一个真实的结果,那么true将被视为@ 987654337@ 和 false 将被视为 0
-- 当然,1
在升序排序时位于 0
之后。
【讨论】:
这对我来说非常有效。我需要使用谓词进行聚合 (MAX
),如果没有与谓词匹配的行,我将不会返回任何行。现在我恢复了默认值。
如果第一个查询返回“1”而第二个(默认)选择返回“0”和“1”,这不起作用。最终结果将是“0”而不是“1”。
@mickmackusa 见db-fiddle.com/f/9jZiSLa5L9tCD7pmXwFiYW/0
@Fir 该查询完全符合我的意思。因为第一个子查询有一个符合条件的行,所以 UNION 之后的子查询将被忽略。这只是一种误解。老实说,我在(VALUES (0),(1)) t (c1)
之前从未见过这种语法我知道t
是派生表的别名,但我不知道c1
在做什么。
除非有order by
,否则不能保证查询返回的行的顺序(SQL 标准)。请参阅ORDER BY and LIMIT in Unions:“(...)因为默认情况下 UNION 会生成一组无序的行”以及“要使 UNION 结果中的行由每个 SELECT 一个接一个地检索到的行集组成,请选择一个每个 SELECT 中的附加列用作排序列,并添加一个 ORDER BY,在最后一个 SELECT 之后对该列进行排序。【参考方案3】:
要处理更广泛的案例,您需要一些条件逻辑。这仅在 MySQL 的存储过程中可用,因此您需要将此代码包装在一个过程中并调用它:
if exists (
SELECT `file` FROM `show`, `schedule`
WHERE `channel` = 1 AND `start_time` <= UNIX_TIMESTAMP()
AND `start_time` > UNIX_TIMESTAMP()-1800 AND `show`.`id` = `schedule`.`file`
) then
SELECT `file` FROM `show`, `schedule`
WHERE `channel` = 1 AND `start_time` <= UNIX_TIMESTAMP()
AND `start_time` > UNIX_TIMESTAMP()-1800 AND `show`.`id` = `schedule`.`file`
ORDER BY `start_time` DESC LIMIT 1
; else
select `DefaultValue` as `file`
; end if
【讨论】:
以上是关于如果未找到单行,则返回默认值的主要内容,如果未能解决你的问题,请参考以下文章
FirstOrDefaultAsync()SingleOrDefaultAsync()方法的区别