我有一个 SQL 查询,用于在表中查找某个日期范围内的值。
如果没有找到记录,我想生成具有默认值的行。
表现有记录之一的示例:
设备ID | 时间 | 语境 | 价值 |
---|---|---|---|
1 | 2022-02-10 | 连接的 | 错误的 |
因此,限制时间列在 2022-02-07 和 2022-02-10 之间的查询必须为 2 月 7 日、8 日和 9 日创建假行,但不能为 10 日创建假行,因为该行已经存在。
预期结果:
设备ID | 时间 | 语境 | 价值 |
---|---|---|---|
1 | 2022-02-7 | 伪造的 | 错误的 |
1 | 2022-02-8 | 伪造的 | 错误的 |
1 | 2022-02-9 | 伪造的 | 错误的 |
1 | 2022-02-10 | 连接的 | 错误的 |
我怎样才能做到这一点?使用递归 CTE?
AMt*_*two 10
当我想到你想要完成什么时,我会用“简单的英语”这样描述它:
我的思维过程立即跳跃到“如果我总是包含默认值,然后在存在实际结果时以某种方式过滤掉它们会怎么样?
在思考了我早上喝的咖啡后,我意识到使用 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;
Run Code Online (Sandbox Code Playgroud)
我这里也做同样的治疗。只需制作一些默认占位符,并将它们抽象到我可以轻松引用的 CTE 中。也许您的默认值存储在某个表中的某个位置,或者您可能更喜欢将它们填充到#temp
table 或 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;
Run Code Online (Sandbox Code Playgroud)
现在,通过 CTE 中的真实查询和另一个 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);
Run Code Online (Sandbox Code Playgroud)
如果您将第一行更改为不存在的值,那么sys.objects
您将获得占位符默认结果:
DECLARE @ObjectName nvarchar(128) = N'AMtwo';
Run Code Online (Sandbox Code Playgroud)
我的解决方案有效,但可能并不理想。例如,如果您查看执行计划,您将看到它运行“真实”查询两次。对于这个简单的查询来说,这完全没问题,但对于其他情况可能不太适用。
您最好简单地运行“真实”查询来插入#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;
Run Code Online (Sandbox Code Playgroud)
可以使用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 行(包括第 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;
Run Code Online (Sandbox Code Playgroud)
枢轴部分将数据转换为:
设备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;
Run Code Online (Sandbox Code Playgroud)
关于这个问题的答案很酷,但是为了填写缺失的日期范围,我更喜欢使用表格来经典地解决这个问题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'
Run Code Online (Sandbox Code Playgroud)
对于非常大的日期范围和数据集,这可能不是提高性能的第一方法,但我发现它是最简单的,通常足以让您跨越终点线,特别是在正确索引的情况下。
归档时间: |
|
查看次数: |
3179 次 |
最近记录: |