虽然重新构建了一个问题查询,但我偶然发现一个明显多余的连接修复它的事实。我不懂为什么。 (我难以简洁地解释;请耐心等待。业务背景 - 报告及其参数 - 并不重要。)
我有三个表来保存对各种报告的调用历史记录,包括传递给报告的参数。报表参数值始终存储为字符串,如下所示:
CREATE TABLE [dbo].[ImportReportParameter](
[ImportReportParameterID] [int] IDENTITY(1,1) NOT NULL,
[ImportReportHistoryID] [int] NOT NULL,
[ParameterName] [nvarchar](100) NOT NULL,
[ParameterValue] [nvarchar](200) NULL,
…
所有报告都接受日期范围,因此我知道每个ImportReportHistoryID都有一个ImportReportParameter行,其ParamaterName为“RangeStart”,其一个ParameterName为“RangeEnd”。我知道这些行中的ParameterValue字符串可以安全地转换为DateTime。 有争议的查询将数据展平为每个报表实例产生一行,其中包含日期范围作为一对DateTimes:
select top (@numberToRetrieve) ir.*,
(select cast(ParameterValue as datetime) from ImportReportParameter iparm where iparm.ImportReportHistoryID = ir.ImportReportHistoryID and ParameterName = 'RangeStart') RangeStart,
(select cast(ParameterValue as datetime) from ImportReportParameter iparm where iparm.ImportReportHistoryID = ir.ImportReportHistoryID and ParameterName = 'RangeEnd') RangeEnd
from ImportReportHistory ir
order by Requested desc
这一切都运作良好了一段时间。但随后查询开始失败:
Msg 241,Level 16,State 1,Line 2
从字符串转换日期和/或时间时转换失败。
我假设坏数据已进入ParameterValue字段,但确认成功执行并非如此:
select ImportReportHistoryID, cast(ParameterValue as datetime) RangeStart
from ImportReportParameter
where ParameterName = 'RangeStart'
,同样地:
select ImportReportHistoryID, cast(ParameterValue as datetime) RangeEnd
from ImportReportParameter
where ParameterName = 'RangeEnd'
然后我开始改变@numberToRetrieve,事情变得奇怪了。对于最多46的值,查询可以工作,但是我看不到第47个实例有什么问题...我试过只运行第47个实例,它很好。然后我从订单中删除了“desc”,这也是有效的,直到第46个。 但是我的测试ImportReportHistory数据集只有80行。所以我可以使用此查询安全地检索所有记录:isn'其中任何数据都存在问题。我检索哪些并不重要,只要我只检索其中的46个。我认为这很奇怪。
无法弄明白我决定重新配置使用连接到参数表,并意识到只是添加明显多余的连接修复了一些事情:
…
from ImportReportHistory ir
left join ImportReportParameter pSuperfluous on ir.ImportReportHistoryID = pSuperfluous.ImportReportHistoryID
所以我的问题是:为什么多余的联接“修复”一个查询,据我所知,该查询应该有效?
编辑:实际上它并没有修复它,因为我现在每个ImportReportHistoryID获得多行 - 我现在正在排序 - 但它解决了投射问题。
编辑(2):多余的连接并没有真正解决它,它只是让它发生频率降低。我现在重新制定了查询:
select top (@numberToRetrieve) ir.*, t.DisplayName,
p1.RangeStart,
p2.RangeEnd,
from ImportReportHistory ir
left join ImportReportTemplate t on ir.ImportReportTemplateID = t.ImportReportTemplateID
left join (select ImportReportHistoryID, cast(ParameterValue as datetime) RangeStart from ImportReportParameter where ParameterName = 'RangeStart') p1 on p1.ImportReportHistoryID = ir.ImportReportHistoryID
left join (select ImportReportHistoryID, cast(ParameterValue as datetime) RangeEnd from ImportReportParameter where ParameterName = 'RangeEnd') p2 on p2.ImportReportHistoryID = ir.ImportReportHistoryID
order by Requested desc
现在有效,但是:
我原来问题的答案似乎在于Martin在评论中指出的关于SELECT和WHERE处理顺序的讨论。据我所知,select 可以在where之前处理,这就是为什么有时试图将ParameterValue作为DateTime转换为ParameterName不是RangeStart或RangeEnd的行的原因。这会产生不可预测的结果,并且似乎违反了Microsoft记录的逻辑处理顺序。这也意味着我重新制定的查询(以及我过去撰写过的其他查询)有一天会失败,所以它还不是一个令人满意的解决方案。