AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / dba / 问题 / 307298
Accepted
Olivier MATROT
Olivier MATROT
Asked: 2022-02-11 05:12:23 +0800 CST2022-02-11 05:12:23 +0800 CST 2022-02-11 05:12:23 +0800 CST

如果不满足限制,则将默认行添加到查询结果

  • 772

我有一个 SQL 查询,它在一系列日期的表中查找值。

如果没有找到记录,我想生成具有默认值的行。

表中现有记录之一的示例:

设备ID 时间 语境 价值
1 2022-02-10 连接的 错误的

因此,限制 2022-02-07 和 2022-02-10 之间的 Time 列的查询必须为 2 月 7 日、8 日和 9 日创建假行,但不能为 10 日创建假行,因为那已经存在。

预期结果:

设备ID 时间 语境 价值
1 2022-02-7 伪造的 错误的
1 2022-02-8 伪造的 错误的
1 2022-02-9 伪造的 错误的
1 2022-02-10 连接的 错误的

我怎样才能做到这一点?使用递归 CTE?

sql-server date-math
  • 3 3 个回答
  • 1056 Views

3 个回答

  • Voted
  1. Best Answer
    AMtwo
    2022-02-11T06:20:11+08:002022-02-11T06:20:11+08:00

    当我想到你想要完成的事情时,我会用“简单的英语”这样描述它:

    • 返回一些查询的结果
    • 但如果不存在结果,则返回一些默认值。

    我的思考过程立即飞跃了“如果我总是包含默认值,但是当存在实际结果时以某种方式将它们过滤掉怎么办?

    在考虑了我早上喝的咖啡后,我意识到这实际上很容易用 CTE 完成。不需要递归,但我将使用两个 CTE。

    那个真正的询问

    让我们首先将您的真实查询放入 CTE。这使得从查询中多次引用结果变得很容易,非常容易。在这个例子中,我只是要查询 sys.objects,并将整个该死的东西放入 CTE:

    DECLARE @ObjectName nvarchar(128) = N'sysschobjs';
    
    RealQuery AS (
        SELECT object_id, name
        FROM sys.objects
        WHERE name = @ObjectName
        )
    SELECT *
    FROM RealQuery;
    

    现在为默认值

    我在这里做同样的处理。只需制作一些默认占位符,并将它们抽象为我可以轻松引用的 CTE。也许您的默认值存储在某个表中的某个地方,或者您可能更喜欢将它们填充到#temptable 或 table 中@variable,在这种情况下,您不需要在这里使用 CTE。

    WITH Defaults AS (
        SELECT *
        FROM (VALUES (1,N'One'),(2,N'Two'),(3,N'Three')) AS x(Id,Name)
        )
    SELECT *
    FROM Defaults;
    

    混搭的时间

    现在,在 CTE 中使用我们的真实查询,在另一个中使用我们的“默认”值,我只UNION ALL需要“真实”结果和默认占位符。“魔术”WHERE NOT EXISTS (SELECT 1 FROM RealQuery)用于控制是否包含这些默认值。

    这将返回与对象名称匹配的单行sysschobjs,并且不返回默认占位符:

    DECLARE @ObjectName nvarchar(128) = N'sysschobjs';
    
    WITH Defaults AS (
        SELECT *
        FROM (VALUES (1,N'One'),(2,N'Two'),(3,N'Three')) AS x(Id,Name)
        ),
    RealQuery AS (
        SELECT object_id, name
        FROM sys.objects
        WHERE name = @ObjectName
        )
    SELECT *
    FROM RealQuery
    UNION ALL
    SELECT *
    FROM Defaults
    WHERE NOT EXISTS (SELECT 1 FROM RealQuery);
    

    sysschobjs 的一个值的 SSMS 结果

    如果您将第一行更改为不存在的值,sys.objects那么您将获得占位符默认结果:

    DECLARE @ObjectName nvarchar(128) = N'AMtwo';
    

    三个占位符结果的图像,数字一、二、三用文字拼出

    还有其他方法。

    我的解决方案有效,但可能并不理想。例如,如果您查看执行计划,您会看到它正在运行两次“真实”查询。对于这个琐碎的查询,这完全没问题,但对于其他可能无法正常工作的情况。

    您最好简单地运行“真实”查询以插入#Results临时表,然后检查临时表中有多少行。

    IF EXISTS (SELECT 1 FROM #Results)
    BEGIN
        INSERT INTO #Results(Id,Name)
        SELECT *
        FROM (VALUES (1,N'One'),(2,N'Two'),(3,N'Three')) AS x(Id,Name);
    END;
    
    SELECT *
    FROM #Results;
    
    • 10
  2. Paul White
    2022-02-11T11:45:50+08:002022-02-11T11:45:50+08:00

    可以使用 pivot 和 unpivot 来有效地实现这一点。

    使用稍微扩展的测试数据:

    设备ID 时间 语境 价值
    1 2022-02-08 00:00:00 连接的 错误的
    1 2022-02-10 00:00:00 连接的 错误的
    2 2022-02-09 00:00:00 连接的 错误的

    要生成第 7到第11行:

    WITH TheQuery AS
    (
        SELECT V.*
        FROM 
        (
            VALUES
                (1, CONVERT(datetime2(0), '20220208', 112), 'Connected', 'False'),
                (1, CONVERT(datetime2(0), '20220210', 112), 'Connected', 'False'),
                (2, CONVERT(datetime2(0), '20220209', 112), 'Connected', 'False')
        ) AS V (DeviceId, [Time], Context, [Value])
    )
    SELECT
        U.DeviceId, 
        U.TheDate, 
        Context = IIF(U.Present = 1, U.Context, 'Fake'), 
        [Value] = IIF(U.Present = 1, U.[Value], 'False')
    FROM TheQuery AS Q
    PIVOT (COUNT([Time]) FOR [Time] IN 
        ([20220207],[20220208],[20220209],[20220210],[20220211])) AS P
    UNPIVOT (Present FOR TheDate IN 
        ([20220207],[20220208],[20220209],[20220210],[20220211])) AS U;
    

    枢轴部分将数据转换为:

    设备ID 语境 价值 20220207 20220208 20220209 20220210 20220211
    1 连接的 错误的 0 1 0 1 0
    2 连接的 错误的 0 0 1 0 0

    如果COUNT值不存在,则聚合结果为零,否则为 1。unpivot 将集合旋转回行,其中计数产生零的额外行:

    设备ID 语境 价值 当下 日期
    1 连接的 错误的 0 20220207
    1 连接的 错误的 1 20220208
    1 连接的 错误的 0 20220209
    1 连接的 错误的 1 20220210
    1 连接的 错误的 0 20220211
    2 连接的 错误的 0 20220207
    2 连接的 错误的 0 20220208
    2 连接的 错误的 1 20220209
    2 连接的 错误的 0 20220210
    2 连接的 错误的 0 20220211

    计数中的“当前”列提供了一种简单的方法来决定是否应使用默认值。

    最终结果是:

    设备ID 日期 语境 价值
    1 20220207 伪造的 错误的
    1 20220208 连接的 错误的
    1 20220209 伪造的 错误的
    1 20220210 连接的 错误的
    1 20220211 伪造的 错误的
    2 20220207 伪造的 错误的
    2 20220208 伪造的 错误的
    2 20220209 连接的 错误的
    2 20220210 伪造的 错误的
    2 20220211 伪造的 错误的

    执行计划:

    计划

    该方法适用于动态 SQL,因为数据透视表和非数据透视表都需要一串带引号的逗号分隔日期。

    可以通过多种方式创建此字符串,例如:

    DECLARE 
        @Start datetime2(0) = '20220207',
        @End datetime2(0) = '20220211';
    
    WITH R AS 
    (
        SELECT TheDate = @Start
        UNION ALL
        SELECT DATEADD(DAY, 1, R.TheDate)
        FROM R
        WHERE R.TheDate < @End
    )
    SELECT 
        STRING_AGG(QUOTENAME(R.TheDate), ',')
    FROM R;
    
    • 8
  3. J.D.
    2022-02-11T16:11:09+08:002022-02-11T16:11:09+08:00

    这个答案很酷,但为了填补缺失的日期范围,我的偏好只是通过使用DateDimensions表格将其踢出经典。这是 Aaron Bertrand 如何生成一个示例。

    当然,如果您不需要所有额外的维度,您可以只为日期生成它。DateDimensions然后,您只需要通过另一个表中的日期字段对您的表进行简单的外部连接,以填补您缺少的日期的空白,如下所示:

    SELECT 
        ISNULL(YT.DeviceId, 1) AS DeviceId, 
        DD.[Date] AS [Time], 
        ISNULL(YT.Context, 'Fake') AS Context,
        ISNULL(YT.[Value], 'False') AS [Value]
    FROM DateDimensions AS DD
    LEFT JOIN YourTable AS YT
        ON DD.[Date] = YT.[Time]
    WHERE DD.[Date] >= '2022-02-07'
        AND DD.[Date] <= '2022-02-10'
    

    对于非常大的日期范围和数据集,这可能不是性能排名第一的方法,但我发现它最简单,通常足以让你越过终点线,尤其是在正确索引的情况下。

    • 7

相关问题

  • SQL Server - 使用聚集索引时如何存储数据页

  • 我需要为每种类型的查询使用单独的索引,还是一个多列索引可以工作?

  • 什么时候应该使用唯一约束而不是唯一索引?

  • 死锁的主要原因是什么,可以预防吗?

  • 如何确定是否需要或需要索引

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    连接到 PostgreSQL 服务器:致命:主机没有 pg_hba.conf 条目

    • 12 个回答
  • Marko Smith

    如何让sqlplus的输出出现在一行中?

    • 3 个回答
  • Marko Smith

    选择具有最大日期或最晚日期的日期

    • 3 个回答
  • Marko Smith

    如何列出 PostgreSQL 中的所有模式?

    • 4 个回答
  • Marko Smith

    列出指定表的所有列

    • 5 个回答
  • Marko Smith

    如何在不修改我自己的 tnsnames.ora 的情况下使用 sqlplus 连接到位于另一台主机上的 Oracle 数据库

    • 4 个回答
  • Marko Smith

    你如何mysqldump特定的表?

    • 4 个回答
  • Marko Smith

    使用 psql 列出数据库权限

    • 10 个回答
  • Marko Smith

    如何从 PostgreSQL 中的选择查询中将值插入表中?

    • 4 个回答
  • Marko Smith

    如何使用 psql 列出所有数据库和表?

    • 7 个回答
  • Martin Hope
    Jin 连接到 PostgreSQL 服务器:致命:主机没有 pg_hba.conf 条目 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane 如何列出 PostgreSQL 中的所有模式? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh 为什么事务日志不断增长或空间不足? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland 列出指定表的所有列 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney MySQL 能否合理地对数十亿行执行查询? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx 如何监控大型 .sql 文件的导入进度? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison 你如何mysqldump特定的表? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 对 SQL 查询进行计时? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas 如何从 PostgreSQL 中的选择查询中将值插入表中? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 列出所有数据库和表? 2011-02-18 00:45:49 +0800 CST

热门标签

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve