sql with group by and range of dates - sql

I have a query which uses max aggregate function to calculate reset date for a particular selection date.There is a table vp_Accrual which have effectivedate field.selection date is passed to effectivedate field and based on that using Max function, reset date is calculated.This Query gives result only for a particular selection date.It calculate a reset date based on selection date passed.
I want to modify it so that it will give me reset date for a range of dates
Below Query gives Output for one person for the date 1st Jan as follow.
We need to modify Query so that it will give result for multiple selection date instead of only one selection date.
I tried by adding addition #enddate field as effectivedate between #selectiondate and #enddate.
But I guess tha approach is not correct.
I want to pass range of date, such as 1st jan to 4th Jan, and it should give Output for each date as below
[Image is added as Link as i dont have enough Repuation Point][https://i.stack.imgur.com/0HSX1.jpg]
Below is the Query used.
DECLARE #selectionDate datetime
SET #selectionDate = CONVERT(nvarchar(10), DATEADD(dd, 0, CAST('01/01/2017' AS datetime)), 101)
SELECT
e.personnum,
ac.name as Accrul_Code,
#selectionDate as Selection_DATE,
CASE
WHEN MAX(va.effectivedate) IS NULL THEN CAST('1/1/1753' AS datetime)
ELSE MAX(va.effectivedate)
END AS resetdate
FROM vp_employeev42 e WITH (NOLOCK)
INNER JOIN accrualprofile ap
ON e.accrualprflname = ap.name
INNER JOIN accrualprofilemm apm
ON apm.accrualprofileid = ap.accrualprofileid
INNER JOIN accrualrule ar
ON apm.accrualruleid = ar.accrualruleid
INNER JOIN accrualcode ac
ON ac.accrualcodeid = ar.accrualcodeid
LEFT OUTER JOIN vp_accrual va
ON va.accrualcodeid = ar.accrualcodeid
AND e.personid = va.personid
AND accrualtrantype IN (3, 11)
AND va.effectivedate <= #selectionDate
WHERE
ac.NAME IN ('FT PTO','LOC','EPT - 5','PT PTO','EPT')
AND va.DISQUALIFIEDSW != 1
and e.PERSONNUM='00152'
GROUP BY e.personnum,
ac.name
Please help me with this Query

Best I can make out, you want a Cartesian join on a fake date table that will cause your results to repeat themselves over and over, but for a different set of dates each time
Something like this:
DECLARE #selectionDate datetime
DECLARE #endDate datetime
SET #selectionDate = DATEFROMPARTS(2017,1,1)
SET #endDate = DATEFROMPARTS(2017,5,1)
WITH dates ( IncDate ) AS (
SELECT #selectionDate UNION ALL
SELECT DATEADD(month,1,IncDate) FROM dates WHERE Incdate <= #endDate )
SELECT
e.personnum,
ac.name as Accrul_Code,
IncDate as Selection_DATE,
MAX(COALESCE(effectivedate, CAST('1/1/1753' AS datetime)) AS resetdate
FROM vp_employeev42 e WITH (NOLOCK)
INNER JOIN accrualprofile ap
ON e.accrualprflname = ap.name
INNER JOIN accrualprofilemm apm
ON apm.accrualprofileid = ap.accrualprofileid
INNER JOIN accrualrule ar
ON apm.accrualruleid = ar.accrualruleid
INNER JOIN accrualcode ac
ON ac.accrualcodeid = ar.accrualcodeid
CROSS JOIN dates
LEFT OUTER JOIN vp_accrual va
ON va.accrualcodeid = ar.accrualcodeid
AND e.personid = va.personid
AND accrualtrantype IN (3, 11)
AND effectivedate <= IncDate
WHERE
ac.NAME IN ('FT PTO','LOC','EPT - 5','PT PTO','EPT')
AND va.DISQUALIFIEDSW != 1
and e.PERSONNUM='00152'
GROUP BY e.personnum,
ac.name,
IncDate
A few cautions though:
This query cannot generate the results you posted, but neither can yours. There's no logic at all to your example results, where your reset date is the max date that is less than the selection date but your first of February selection date somehow chooses the tenth of Jan, yet your first of march selection date chooses the eleventh of Jan.
This is totally untested; you provided no sample data, only an image of the results, and I post from an iPad, so generating test data would be incredibly laborious, typing it all in
You mix up your dates as varchar and datetime. Please be more diligent in keeping these data types separate. Always work with dates as dates, not strings. Never store dates as string. Never rely on implicit conversions between date and string
DATEFROMPARTS came along in sqlserver 2012 I think, if your sql is older than this, use convert with a format string to turn your string into a date.. do not declare selectiondate as a varchar!

Related

Creating a new table from existing tables

I am trying to create a new table combining columns from multiple tables into one - simple enough. I wrote this query and when I went to retrieve some data there is NOTHING in the table but the column names. Am I missing a step?
Select E.PCR_ID as PreHospital_Report
, E.eTimes_03 as DateofCall
, R.eResponse_03 as IncidentID
, R.eResponse_05 as TypeofService
, GMR.dAgency_02 as GMR_AGENCY_NAME
, S.eSituation_11 as Primary_Impression
, S.eSituation_12 as Secondary_Impression
, D.eDisposition_12 as Incident_Patient_Disposition
, P.ePatient_15 as Age
, P.ePatient_16 as Age_Units
, V.EVitals_01 as Time_Vitals_Taken
, V.EVitals_18 as Blood_Gluse_Level
, V.eVitals_23 as Total_Glasgow_Coma_Scale
, V.eVitals_26 as Level_of_Responsiveness
INTO GMR_FactTable_Call
FROM eTimes E
LEFT JOIN eResponse R ON E.PCR_ID = R.PCR_ID
LEFT JOIN ePatient P ON E.PCR_ID = P.PCR_ID
LEFT JOIN eVitals V ON E.PCR_ID = V.PCR_ID
LEFT JOIN eSituation S ON E.PCR_ID = S.PCR_ID
LEFT JOIN dAgency GMR ON E.PCR_ID = GMR.PCR_ID
LEFT JOIN eDisposition D ON E.PCR_ID = D.PCR_ID
WHERE E.eTimes_03 >= '01/01/2019'
Your current WHERE clause may not be behaving as you expect. Change this:
WHERE E.eTimes_03 >= '01/01/2019'
to this:
WHERE E.eTimes_03 >= '20190101'
If this fixes the problem, then it means that your current WHERE clause isn't matching any data. This borders on being a typo, but it's important to always remember to use proper date literals in your query. The corrected version above uses an unambiguous format which should work regardless of your server settings.
May be you don't have data for condition E.eTimes_03 >= '01/01/2019'
Also, always ensure that you are having datetime defined properly as per your language settings.see what is your language settings. Is the dateformat set as dmy or mdy ?
DBCC USEROPTIONS
You will get response as given below:
Set Option
Value
textsize
2147483647
language
us_english
dateformat
mdy
...
...
Now, you need to define your datetime value accordingly.
E.eTimes_03 >= '18/12/2019' -- dmy
E.eTimes_03 >= '12/18/2019' -- mdy
Or you can go for ISO8601 format, which is agnostic of language settings.
E.eTimes_03 >= '2019-12-18' -- YYYY-MM-DD format

Teradata spool space issue on running a sub query with Count

I am using below query to calculate business days between two dates for all the order numbers. Business days are already available in the teradata table Common_WorkingCalendar. But, i'm also facing spool space issue while i execute the query. I have ample space available in my data lab. Need to optimize the query. Appreciate any inputs.
SELECT
tx."OrderNumber",
(SELECT COUNT(1) FROM Common_WorkingCalendar
WHERE CalDate between Cast(tx."TimeStamp" as date) and Cast(mf.ShipDate as date)) as BusDays
from StoreFulfillment ff
inner join StoreTransmission tx
on tx.OrderNumber = ff.OrderNumber
inner join StoreMerchandiseFulfillment mf
on mf.OrderNumber = ff.OrderNumber
This is a very inefficient way to get this count which results in a product join.
The recommended approach is adding a sequential number to your calendar which increases only on business days (calculated using SUM(CASE WHEN businessDay THEN 1 ELSE 0 END) OVER (ORDER BY CalDate ROWS UNBOUNDED PRECEDING)), then it's two joins, for the start date and the end date.
If this calculation is needed a lot you better add a new column, otherwise you can do it on the fly:
WITH cte AS
(
SELECT CalDate,
-- as this table only contains business days you can use this instead
row_number(*) Over (ORDER BY CalDate) AS DayNo
FROM Common_WorkingCalendar
)
SELECT
tx."OrderNumber",
to_dt.DayNo - from_dt.DayNo AS BusDays
FROM StoreFulfillment ff
INNER JOIN StoreTransmission tx
ON tx.OrderNumber = ff.OrderNumber
INNER JOIN StoreMerchandiseFulfillment mf
ON mf.OrderNumber = ff.OrderNumber
JOIN cte AS from_dt
ON from_dt.CalDate = Cast(tx."TimeStamp" AS DATE)
JOIN cte AS to_dt
ON to_dt.CalDate = Cast(mf.ShipDate AS DATE)

SQL Server DATE conversion error

Here is my query:
SELECT
*
FROM
(SELECT
A.Name, AP.PropertyName, APV.Value AS [PropertyValue],
CONVERT(DATETIME, APV.VALUE, 101) AS [DateValue]
FROM dbo.Account AS A
JOIN dbo.AccountProperty AS AP ON AP.AccountTypeId = A.AccountTypeId
JOIN dbo.AccountPropertyValue AS APV ON APV.AccountPropertyId = APV.AccountPropertyId
AND APV.AccountId = A.AccountId
WHERE
A.AccountTypeId = '19602AEF-27B2-46E6-A068-7E8C18B0DD75' --VENDOR
AND AP.PropertyName LIKE '%DATE%'
AND ISDATE(APV.Value) = 1
AND LEN(SUBSTRING( REVERSE(APV.Value), 0 , CHARINDEX( '/', REVERSE(APV.Value)))) = 4 --ENSURE 4 digit year
) AS APV
WHERE
APV.DateValue < GETDATE()
It results in the following error:
Conversion failed when converting date and/or time from character string.
If you comment out the WHERE APV.DateValue < GETDATE() clause then there is no error and I get the 300+ rows. When I enable the WHERE clause I get the error.
So you are going to tell me my data is jacked up right? Well that's what I thought, so I tried to figure out where the problem in the data was, so I started using TOP() to isolate the location. Problem was once I use the TOP() function the error went away, I only have 2000 rows of data to begin with. So I put a ridiculous TOP(99999999) on the inner SELECT and now the entire query works.
The inner SELECT returns the same number of rows with or without the TOP().
WHY???
FYI, this is SQL that works:
SELECT
*
FROM
(SELECT TOP(99999999)
A.Name, AP.PropertyName, APV.Value AS [PropertyValue],
CONVERT(DATETIME, APV.VALUE, 101) AS [DateValue]
FROM dbo.Account AS A
JOIN dbo.AccountProperty AS AP ON AP.AccountTypeId = A.AccountTypeId
JOIN dbo.AccountPropertyValue AS APV ON APV.AccountPropertyId = APV.AccountPropertyId
AND APV.AccountId = A.AccountId
WHERE
A.AccountTypeId = '19602AEF-27B2-46E6-A068-7E8C18B0DD75' --VENDOR
AND AP.PropertyName LIKE '%DATE%'
AND ISDATE(APV.Value) = 1
AND LEN(SUBSTRING(REVERSE(APV.Value), 0 , CHARINDEX( '/', REVERSE(APV.Value)))) = 4
) AS APV
WHERE
APV.DateValue < GETDATE()
The problem that you are facing is that SQL Server can evaluate the expressions at any time during the query processing -- even before the WHERE clause gets evaluated. This can be a big benefit for performance. But, the consequence is that errors can be generated by rows not in the final result set. (This is true of divide-by-zero as well as conversion errors.)
Fortunately, SQL Server has a work-around for the conversion problem. Use try_convert():
TRY_CONVERT( DATETIME, APV.VALUE, 101) AS [DateValue]
This returns NULL rather than an error if there is a problem.
The reason why some versions work and others don't is because of the order of execution. There really isn't a way to predict what does and does not work -- and it could change if the execution plan for the query changes for other reasons (such as table statistics). Hence, use try_convert().
My guess is that your date is such that APV.VALUE contains also data that cannot be converted into a date, and should be filtered out using the other criteria?
Since SQL Server can decide to limit the data first using the criteria you have given:
APV.DateValue < CONVERT( DATETIME, GETDATE(),101)
And if there is data that cannot be converted into the date, then you will get the error.
To make it more clear, this is what is being filtered:
CONVERT( DATETIME, APV.VALUE, 101) AS [DateValue]
And if there is any data that cannot be converted into a date using 101 format, the filter using getdate() will fail, even if the row would not be included in the final result set for example because AP.PropertyName does not contain DATE.
Since you're using SQL Server 2012, using try_convert instead of convert should fix your problem
And why it works with top? In that case SQL Server cannot use the criteria from the outer query, because then the result might change, because it might affect the number of rows returned by top
Because number of records in the table < 999..99. And regarding the error it seems like SQL engine evaluates the WHERE clause after converting to date so you can try this:
SELECT *
FROM (
SELECT A.Name
, AP.PropertyName
, APV.Value AS [PropertyValue]
,
CASE
WHEN SDATE(APV.Value) = 1
THEN CONVERT( DATETIME, APV.VALUE, 101)
ELSE NULL
END AS [DateValue]
FROM dbo.Account AS A
JOIN dbo.AccountProperty AS AP
ON AP.AccountTypeId = A.AccountTypeId
JOIN dbo.AccountPropertyValue AS APV
ON APV.AccountPropertyId = APV.AccountPropertyId
AND APV.AccountId = A.AccountId
WHERE A.AccountTypeId = '19602AEF-27B2-46E6-A068-7E8C18B0DD75' --VENDOR
AND AP.PropertyName LIKE '%DATE%'
AND LEN( SUBSTRING( REVERSE(APV.Value), 0 , CHARINDEX( '/', REVERSE(APV.Value)))) = 4 --ENSURE 4 digit year
) AS APV
WHERE APV.DateValue IS NOT NULL AND APV.DateValue < GETDATE()

How to implement this logic?

I have designed a script to get the inspector performance score which is based on different factors. Inspector is awarded grades based on their performance score. Script runs over night as a SQL job and updates all the inspectors (over 6500 inspectors) grades.
We are checking last 90 days progress but many inspectors who have done no work in last 90 days are getting full marks. To avoid this situation we have decided to look at last 90 days and if the number of reports is zero go back another 90 days for that inspector.
i.e. If out 6500 inspectors lets say 250 has done no job then script needs to go back another 90 days for those 250 inspectors and see if they have any work.
This could have been implemented in cursors very easily but i can't use cursor as it is taking too long as discussed here select query in Cursor taking too long
What are the other option? Should i write a function which will first check if there is any work been done in last 90 days for one inspector if not then go back another 90 days. But for doing this i would till need cursor?
ADDED
I have tried setting dates in temp table as mentioned by #Raj but it is taking too much time. This is a same query which took so long while using cursor. Other stats are running fine and i think something to do with query.
Requirements:
Number of visits for each inspectors where visits has uploaded document (1 or 2 or 13)
Tables:
Inspectors: InspectorID
InspectionScope: ScopeID, InspectorID (FK)
Visits: VisitID, VisitDate ScopeID (FK)
VisitsDoc: DocID, DocType, VisitID (FK)
DECLARE
#DateFrom90 date, #DateTo date, #DateFrom180 date, #DateFrom date;
SELECT #DateTo = CAST(GETDATE() AS DATE)
,#DateFrom90 = CAST(GETDATE() - 90 AS DATE)
,#DateFrom180 = CAST(GETDATE() - 180 AS DATE)
DECLARE #Inspectors TABLE (
InspectorID int,
InspectorGrade int,
DateFrom date,
DateTo date
);
insert into #inspectors (
InspectorID ,
InspectorGrade,
DateFrom ,
DateTo
)
select
tmp.InspectorID , tmp.InspectorGrade
,case when tmp.VisitWithReport = 0 then #DateFrom180 else #DateFrom90 end StartDate
,#DateTo EndDate
from
(
select
i.InspectorID , i.InspectorGrade
,VisitWithReport = (select COUNT(v.visitid) from visits v
inner join InspectionScope s on s.ScopeID = v.ScopeID
where v.ReportStandard not in (0,9) and v.VisitType = 1
and v.VisitDate BETWEEN #DateFrom90 and #DateTo
and s.InspectorID = i.InspectorID)
from inspectors i
)tmp;
--select * from #Inspectors
SELECT i.InspectorID , i.InspectorGrade
,TotalVisitsWithAtLeastOneReport = (select COUNT(distinct v.visitID) from Visits v
inner join InspectionScope s on s.ScopeID = v.ScopeID
inner join VisitDocs vd on vd.VisitID = v.VisitID
where vd.DocType IN (1,2,13) and s.InspectorID = i.InspectorID
and v.VisitDate BETWEEN i.DateFrom and i.DateTo
)
from #Inspectors i
You can identify the last job/work date first before applying any logic. Like, you can store InspectorID and LastWorkDay in a temp table (assuming LastWorkDay will be available in some table). Then based on LastWorkDay you can decide how many days you have to go back - 90 or 180. This will be another field (StartDate) in temp table which can be derived based on LastWorkDay column.

SQL Merging 4 Queries to one

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