【问题标题】:SQL Merging 4 Queries to oneSQL 将 4 个查询合并为一个
【发布时间】:2014-06-27 00:17:47
【问题描述】:

我在合并以下语句时遇到了一个小问题

    declare @From DATE
SET @From = '01/01/2014'


declare @To DATE
SET @To = '31/01/2014'

--ISSUED SB 

SELECT
      COUNT(pm.DateAppIssued) AS Issued, 
      pm.Lender,
      pm.AmountRequested,
      p.CaseTypeID
FROM  BPS.dbo.tbl_Profile_Mortgage AS pm
        INNER JOIN BPS.dbo.tbl_Profile AS p
            ON pm.FK_ProfileId =  p.Id 
WHERE CaseTypeID = 2
AND (CONVERT(DATE,DateAppIssued, 103) 
Between  CONVERT(DATE,@From,103) and CONVERT(DATE,@To,103)) 
And Lender >  ''
GROUP BY  pm.Lender,p.CaseTypeID,pm.AmountRequested;


--Paased

SELECT
      COUNT(pm.DatePassed) AS Passed, 
      pm.Lender,
      pm.AmountRequested,
      p.CaseTypeID
FROM  BPS.dbo.tbl_Profile_Mortgage AS pm
        INNER JOIN BPS.dbo.tbl_Profile AS p
            ON pm.FK_ProfileId =  p.Id 
WHERE CaseTypeID = 2
AND (CONVERT(DATE,DatePassed, 103) 
Between  CONVERT(DATE,@From,103) and CONVERT(DATE,@To,103)) 
And Lender >  ''
GROUP BY  pm.Lender,p.CaseTypeID,pm.AmountRequested;


--Received 

SELECT
      COUNT(pm.DateAppRcvd) AS Received, 
      pm.Lender,
      pm.AmountRequested,
      p.CaseTypeID
FROM  BPS.dbo.tbl_Profile_Mortgage AS pm
        INNER JOIN BPS.dbo.tbl_Profile AS p
            ON pm.FK_ProfileId =  p.Id 
WHERE CaseTypeID = 2
AND (CONVERT(DATE,DateAppRcvd, 103) 
Between  CONVERT(DATE,@From,103) and CONVERT(DATE,@To,103)) 
And Lender >  ''
GROUP BY  pm.Lender,p.CaseTypeID,pm.AmountRequested;



--Offered

SELECT
      COUNT(pm.DateOffered) AS Offered, 
      pm.Lender,
      pm.AmountRequested,
      p.CaseTypeID
FROM  BPS.dbo.tbl_Profile_Mortgage AS pm
        INNER JOIN BPS.dbo.tbl_Profile AS p
            ON pm.FK_ProfileId =  p.Id 
WHERE CaseTypeID = 2
AND (CONVERT(DATE,DateOffered, 103) 
Between  CONVERT(DATE,@From,103) and CONVERT(DATE,@To,103)) 
And Lender >  ''
GROUP BY  pm.Lender,p.CaseTypeID,pm.AmountRequested;

理想情况下,我希望这些查询的结果如下所示

已发布、已通过、已提供、已接收、 一张桌子

对此的任何帮助将不胜感激 谢谢 生锈

【问题讨论】:

  • 联合查询有用吗?
  • Iv 尝试了一个联合,但它只返回 4 个单独的表,我需要将结果返回到 1 个表上。
  • 您通常不想使用BETWEEN with date/time/timestamp types,尤其是在 SQL Server 上。目前,以这种方式转换日期会导致它忽略索引(使查询变慢)。

标签: sql sql-server merge


【解决方案1】:

我相当肯定在这种情况下,可以在不使用任何CASE 语句的情况下编写查询,实际上:

DECLARE @From DATE = '20140101'
declare @To DATE = '20140201'

SELECT Mortgage.lender, Mortgage.amountRequested, Profile.caseTypeId,
       COUNT(Issue.issued) as issued,
       COUNT(Pass.passed) as passed,
       COUNT(Receive.received) as received,
       COUNT(Offer.offered) as offered
FROM BPS.dbo.tbl_Profile_Mortgage as Mortgage
JOIN BPS.dbo.tbl_Profile as Profile
  ON Mortgage.fk_profileId = Profile.id
     AND Profile.caseTypeId = 2
LEFT JOIN (VALUES (1, @From, @To)) Issue(issued, rangeFrom, rangeTo)
       ON Mortgage.DateAppIssued >= Issue.rangeFrom
          AND Mortgage.DateAppIssued < Issue.rangeTo
LEFT JOIN (VALUES (2, @From, @To)) Pass(passed, rangeFrom, rangeTo)
       ON Mortgage.DatePassed >= Pass.rangeFrom
          AND Mortgage.DatePassed < Pass.rangeTo
LEFT JOIN (VALUES (3, @From, @To)) Receive(received, rangeFrom, rangeTo)
       ON Mortgage.DateAppRcvd >= Receive.rangeFrom
          AND Mortgage.DateAppRcvd < Receive.rangeTo
LEFT JOIN (VALUES (4, @From, @To)) Offer(offered, rangeFrom, rangeTo)
       ON Mortgage.DateOffered >= Offer.rangeFrom
          AND Mortgage.DateOffered < Offer.rangeTo
WHERE Mortgage.lender > ''
      AND (Issue.issued IS NOT NULL 
           OR Pass.passed IS NOT NULL
           OR Receive.received IS NOT NULL
           OR Offer.offered IS NOT NULL)
GROUP BY Mortgage.lender, Mortgage.amountRequested, Profile.caseTypeId

(未测试,因为我缺少提供的数据集)。

...好的,一些解释是有序的,因为其中一些有点不直观。

首先,read this blog entry 获取有关处理日期/时间/时间戳范围的提示(有趣的是,这也适用于所有其他非整数类型)。这就是我修改@To 日期的原因——因此可以安全地查询范围而无需转换类型(因此忽略索引)。我还确保选择一种安全的格式 - 根据您调用此查询的方式,这不是问题(即,采用实际 Date 类型的参数化查询基本上是无格式的)。

       ......
       COUNT(Issue.issued) as issued,
    ......
LEFT JOIN (VALUES (1, @From, @To)) Issue(issued, rangeFrom, rangeTo)
       ON Mortgage.DateAppIssued >= Issue.rangeFrom
          AND Mortgage.DateAppIssued < Issue.rangeTo
.......

COUNT(*)COUNT(&lt;expression&gt;) 有什么区别?如果 &lt;expression&gt; 的计算结果为 null,则会被忽略。因此LEFT JOINs;如果抵押条目不在列的给定日期范围内,则不附加虚拟表,并且没有要计算的列。不幸的是,我不确定虚拟表 LEFT JOINCOUNT() 之间的相互作用如何出现在优化器中 - 连接应该能够使用索引,但我不知道它是否足够聪明也可以在这里为COUNT() 使用它....

          (Issue.issued IS NOT NULL 
           OR Pass.passed IS NOT NULL
           OR Receive.received IS NOT NULL
           OR Offer.offered IS NOT NULL)

这本质上是告诉它忽略至少没有一个列的行。在任何情况下,它们都不会被“计算”(嗯,它们可能有 0) - 没有可供函数考虑的数据 - 但它们显示在结果中,这可能不是你想要的。我不确定优化器是否足够聪明,可以使用它来限制它对哪些行进行操作——也就是说,将JOIN 条件变成一种限制各种日期列的方法,就好像它们在WHERE 中一样条款也。如果查询运行缓慢,请尝试在WHERE 子句中添加日期限制,看看是否有帮助。

【讨论】:

  • 非常感谢,我认为创建一个临时表来处理 4 select 语句将是最好的选择,我不知道我可以这样做。使用此查询似乎更容易实现我的最终结果 感谢您花时间解释它是如何工作的。
  • @RustyHamster - 实际上,我很好奇 Gunnar 的查询或我的查询对于您的数据集是否更快。对于习惯使用 SUM(CASE...) 风格的人来说,这个查询可能看起来有点不寻常。
  • 我已经测试了您的两个查询,它们都运行良好,但是 Clockwork-Muse 查询似乎使用我的数据集更快地执行分配。
【解决方案2】:

您可以像 Dan Bracuk 州一样使用工会,也可以使用案例陈述。

declare @From DATE = '01/01/2014'
declare @To DATE = '31/01/2014'

select
  sum(case when (CONVERT(DATE,DateAppIssued, 103) Between  CONVERT(DATE,@From,103) and CONVERT(DATE,@To,103)) then 1 else 0 end) as Issued
, sum(case when (CONVERT(DATE,DatePassed, 103)    Between  CONVERT(DATE,@From,103) and CONVERT(DATE,@To,103)) then 1 else 0 end) as Passed
, sum(case when (CONVERT(DATE,DateAppRcvd, 103)   Between  CONVERT(DATE,@From,103) and CONVERT(DATE,@To,103)) then 1 else 0 end) as Received
, sum(case when (CONVERT(DATE,DateOffered, 103)   Between  CONVERT(DATE,@From,103) and CONVERT(DATE,@To,103)) then 1 else 0 end) as Offered
, pm.Lender
, pm.AmountRequested
, p.CaseTypeID
FROM  BPS.dbo.tbl_Profile_Mortgage AS pm
        INNER JOIN BPS.dbo.tbl_Profile AS p
            ON pm.FK_ProfileId =  p.Id 
WHERE CaseTypeID = 2
And Lender >  ''
GROUP BY  pm.Lender,p.CaseTypeID,pm.AmountRequested;

编辑: 我所做的是查看您的查询。

所有四个查询都有相同的 Where 子句,日期比较除外。因此,我创建了一个新查询,它会选择您所有可能用于四种计数之一的数据。 最后一个子句;数据比较被移动到 case 语句中,如果行在选定的日期范围之间,则返回 1,否则返回 0。这基本上表明该行是否会在您之前的查询中返回。 因此,此列的总和将返回相当于 count(*) 的值,此日期比较在 where 子句中。

编辑 2(在 Clockwork-muse 的 cmets 之后):

关于性能的一些说明,(在 MS-SQL 2012 上测试):

在 case-statement 中将 BETWEEN 更改为 ">=" 和 "

根据表的大小,可以通过在 where 子句中添加日期对查询进行很多优化。 在我的示例数据中(约 20.000.000 行,从 2001 年到今天),通过添加,我的速度提高了 48%。

or (DateAppIssued BETWEEN @From and @to )
or (DatePassed    BETWEEN @From and @to )
or (DateAppRcvd   BETWEEN @From and @to )
or (DateOffered   BETWEEN @From and @to )

(使用 BETWEEN 和 ">=" 和 "

当将@From = '01/01/2014' 更改为 @From '2014-01-01' 并因此省略了 convert() 时,我得到了 6% 的增长,这也是毫无价值的。

例如。优化的查询可能是:

declare @From DATE = '2014-01-01'
declare @To DATE = '2014-01-31'

select
  sum(case when (DateAppIssued >= @From and DateAppIssued < @To) then 1 else 0 end) as Issued
, sum(case when (DatePassed    >= @From and DatePassed    < @To) then 1 else 0 end) as Passed
, sum(case when (DateAppRcvd   >= @From and DateAppRcvd   < @To) then 1 else 0 end) as Received
, sum(case when (DateOffered   >= @From and DateOffered   < @To) then 1 else 0 end) as Offered
, pm.Lender
, pm.AmountRequested
, p.CaseTypeID
FROM  BPS.dbo.tbl_Profile_Mortgage AS pm
        INNER JOIN BPS.dbo.tbl_Profile AS p
            ON pm.FK_ProfileId =  p.Id 
WHERE 1=1
and CaseTypeID = 2
and Lender >  ''
and (
      (DateAppIssued >= @From and DateAppIssued < @To)
   or (DatePassed    >= @From and DatePassed    < @To)
   or (DateAppRcvd   >= @From and DateAppRcvd   < @To)
   or (DateOffered   >= @From and DateOffered   < @To)
)
GROUP BY  pm.Lender,p.CaseTypeID,pm.AmountRequested;

不过,我真的很喜欢 Clockwork-muse 的回答,因为我更喜欢在可能的情况下加入案例陈述:)

【讨论】:

  • 您通常不希望使用 BETWEEN with date/time/timestamp types,尤其是在 SQL Server 上。在CASE 语句中使用它时,目前这并没有真正减慢查询速度 - 但是,您在WHERE 子句中没有相关条件的事实将使它查看更多的行而不是必要的。不过,这似乎是一个可行的解决方案。
  • 不知道 BETWEEN 的贬值 - 我猜你每天都会学到一些新东西 :) 我试图将帖子编辑为使用“=”而不是介于两者之间,但是对于可读性我决定保留 BETWEEN。我同意关于读取太多行的问题,但替代方法是检查是否有任何日期在给定范围之间,这将使用多个或,这取决于数据量,通常会进一步减慢查询.
  • 再次阅读博客 - 问题不完全是 BETWEEN,而是它使用了包容性上限:&lt;=。您可以通过使用独占上限 (&lt;) 和相关条目(在本例中为 2 月 1 日)来避免转换,这也启用了索引的使用。我同意ORs 偶尔会给优化器带来问题,但这可能是对日期范围的相当有选择性的查询,因此惩罚可能比遍历每一行所花费的要少。
  • 不,不要转换数据库中的日期,这会减慢您的速度,请记住根据您的测试,%6)。在您使用独占上限时,您不需要对其进行转换。 (...)BETWEEN 和独占上限之间的区别不在于速度(因为它在很大程度上是两个简单条件的语法糖),而在于如何比较范围的顶部。是的,一般JOINs 是首选(你可以用虚拟表做一些令人惊讶的事情),但更复杂的条件可能需要SUM(CASE...)(可读)。
  • 编辑了一次到多次:是的,我的粘贴速度很快 - 意味着粘贴查询省略转换,以显示优化的查询。正在编辑中...
【解决方案3】:

其他答案中的一体式查询当然很优雅,但是如果您急于使某些东西一次性工作,或者如果您同意以下方法在您使用时易于阅读和维护必须在一段时间后重新审视它(或者其他不太熟练的人必须弄清楚发生了什么) - 这是一个通用表表达式替代方案的骨架,我认为它很清楚阅读:

WITH Unioned_Four AS
( SELECT .. -- first select : Issued
    UNION ALL
  SELECT .. -- second : Passed
    UNION ALL
  SELECT .. -- Received
    UNION ALL
  SELECT .. -- Offered
)
SELECT
  -- group fields
  -- SUMs of the count fields
FROM Unioned_Four
GROUP BY .. -- etc 

显然,UNION 的 4 个部分中的字段必须匹配,要求虚拟字段在每个部分中返回零。

因此,您可以保留一开始的简单方法,但使用 CTE 语法将其包装为派生表,以允许您将四个计数全部放在每个 GROUPing 的一行上。此外,如果您必须为这四个特定查询添加额外的过滤,那么干预单个 SELECT 会更容易 - 另一方面是(当然)需要复制所有四个的进一步要求!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-07-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-26
    • 2016-01-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多