Domanda

Im having a slight issue merging the following statements

    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;

Ideally I would like the result of theses query's to show as follows

Issued, Passed , Offered, Received, All in one table

Any Help on this would be greatly appreciated Thanks Rusty

È stato utile?

Soluzione

I'm fairly certain in this case the query can be written without the use of any CASE statements, actually:

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

(not tested, as I lack a provided data set).

... Okay, some explanations are in order, because some of this is slightly non-intuitive.

First off, read this blog entry for tips about dealing with date/time/timestamp ranges (interestingly, this also applies to all other non-integral types). This is why I modified the @To date - so the range could be safely queried without needing to convert types (and thus ignore indices). I've also made sure to choose a safe format - depending on how you're calling this query, this is a non issue (ie, parameterized queries taking an actual Date type are essentially format-less).

       ......
       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
.......

What's the difference between COUNT(*) and COUNT(<expression>)? If <expression> evaluates to null, it's ignored. Hence the LEFT JOINs; if the entry for the mortgage isn't in the given date range for the column, the dummy table doesn't attach, and there's no column to count. Unfortunately, I'm not sure how the interplay between the dummy table, LEFT JOIN, and COUNT() here will appear to the optimizer - the joins should be able to use indices, but I don't know if it's smart enough to be able to use that for the COUNT() here too....

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

This is essentially telling it to ignore rows that don't have at least one of the columns. They wouldn't be "counted" in any case (well, they'd likely have 0) - there's no data for the function to consider - but they would show up in the results, which probably isn't what you want. I'm not sure if the optimizer is smart enough to use this to restrict which rows it operates over - that is, turn the JOIN conditions into a way to restrict the various date columns, as if they were in the WHERE clause too. If the query runs slow, try adding the date restrictions to the WHERE clause and see if it helps.

Altri suggerimenti

You could either as Dan Bracuk states use a union, or you could use a case-statement.

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;

Edit: What I've done is looked at your queries.

All four queries have identical Where Clause, with the exception of the date comparison. Therefore I've created a new query, which selects all your data which might be used in one of the four counts. The last clause; the data-comparison, is moved into a case statement, returning 1 if the row is between the selected date-range, and 0 otherwise. This basically indicates whether the row would be returned in your previous queries. Therefore a sum of this column would return the equivalent of a count(*), with this date-comparison in the where-clause.

Edit 2 (After comments by Clockwork-muse):

Some notes on performance, (tested on MS-SQL 2012):

Changing BETWEEN to ">=" and "<" inside a case-statement does not affect the cost of the query.

Depending on the size of the table, the query might be optimized quite a lot, by adding the dates in the where clause. In my sample data (~20.000.000 rows, spanning from 2001 to today), i got a 48% increase in speed by adding.

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

(There were no difference using BETWEEN and ">=" and "<".)

It is also worth nothing that i got a 6% increase when changing the @From = '01/01/2014' to @From '2014-01-01' and thus omitting the convert().

Eg. an optimized query could be:

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;

I do however really like Clockwork-muse's answer, as I prefer joins to case-statements, where posible :)

The all-in-one queries here in other answers are certainly elegant, but if you are in a rush to get something working as a one-off, or if you agree the following approach is easy to read and maintain when you have to revisit it some time down the road (or someone else less skilled has to work out what's going on) - here's a skeleton of a Common Table Expression alternative which I believe is quite clear to read :

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 

Obviously the fields have to match in the 4 parts of the UNION, requiring dummy fields returning zero in each one.

So you could have kept the simple approach that you started with, but wrapped it up as a derived table using the CTE syntax to allow you to have the four counts all on one row per GROUPing. Also if you have to add extra filtering to specific queries of the four, then it's easier to meddle with the individual SELECTs - the flipside being (of course) that further requirements for all four would need to be duplicated!

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top