Summary Stats and Corresponding Dates - SQL Server - sql

I was hoping someone perhaps could help. This problem was presented to me recently and I thought it would be easy, but (personally) found it a bit of a struggle. I can do it in Excel and SSRS - but I was curious if I was able to do it in SQL Server...
I would like to create a set of summary statistics (Max, Min) for a dataset. Easy enough... But I wanted to associate the corresponding date with those values.
Here is what my data looks like:
I have yearly data (not exactly - but beside the point) and I produce a pivoted summary like this using a series of CASE WHEN statements. This is fine - the output is seen on the right (above).
Each time I output this data - I like to provide a summary of the all the historic data (I only show the most recent data for sake of brevity). So... The question is how do I take an output like the one shown below (on different dates) and provide a summary data set like the one I have on the right?
So - a little background. I have already managed to join the Min and Max values using a UNION and that bit is fine. The tricky bit (I think) is how to form an INNER JOIN, using a sub query, with the Max or Min result values to return the corresponding Max or Min date, for each Type? Now it is highly likely that I am being a bit of an idiot and missing something obvious....but... Would really appreciate any help from anyone...
Many thanks in advance

This query will do the job, and for all TYPE
SELECT
Description, [CAR], [CAT], [MAT], [EAT], [PAR], [MAR], [FAR], [MOT], [LOT], [COT], [ROT]
FROM
(SELECT
unpvt.TYPE
,unpvt.Description
,unpvt.value
FROM (
SELECT
t.TYPE
,CONVERT(sql_variant,MAX(maxResult.MAX_RESULT)) as MAX_RESULT
,CONVERT(sql_variant,MIN(minResult.MIN_RESULT)) as MIN_RESULT
,CONVERT(sql_variant,MAX(CASE WHEN maxResult.MAX_RESULT IS NOT NULL THEN t.DATE ELSE NULL END)) as MAX_DATE
,CONVERT(sql_variant,MIN(CASE WHEN minResult.MIN_RESULT IS NOT NULL THEN t.DATE ELSE NULL END)) as MIN_DATE
FROM
table_name t -- You need to set your table name
LEFT JOIN (SELECT
TYPE
,MIN(RESULT) as MIN_RESULT
FROM
table_name -- You need to set your table name
GROUP BY
TYPE) minResult
on minResult.TYPE = t.TYPE
and minResult.MIN_RESULT = t.RESULT
LEFT JOIN (SELECT
TYPE
,MAX(RESULT) as MAX_RESULT
FROM
table_name -- You need to set your table name
GROUP BY
TYPE) maxResult
on maxResult.TYPE = t.TYPE
and maxResult.MAX_RESULT = t.RESULT
GROUP BY
t.TYPE) U
unpivot (
value
for Description in (MAX_RESULT, MIN_RESULT, MAX_DATE, MIN_DATE)
) unpvt) P
PIVOT
(
MAX(value)
FOR TYPE IN ([CAR], [CAT], [MAT], [EAT], [PAR], [MAR], [FAR], [MOT], [LOT], [COT], [ROT])
)AS PVT
DEMO : SQLFIDDLE
CONVERT(sql_variant, is a cast for columns to a common data type. This is a requirement of the UNPIVOT operator when you are running with subquery FROM.

It is possible to use the PIVOT command if your SQLServer is 2005 or better, but the raw data for the pivot need to be in a specific format, and the query I came up with is ugly
WITH minmax AS (
SELECT TYPE, RESULT, [date]
, row_number() OVER (partition BY TYPE ORDER BY TYPE, RESULT) a
, row_number() OVER (partition BY TYPE ORDER BY TYPE, RESULT DESC) d
FROM t)
SELECT info
, cam = CASE charindex('date', info)
WHEN 0 THEN cast(cast(cam AS int) AS varchar(50))
ELSE cast(cam AS varchar(50))
END
, car = CASE charindex('date', info)
WHEN 0 THEN cast(cast(car AS int) AS varchar(50))
ELSE cast(cam AS varchar(50))
END
, cat = CASE charindex('date', info)
WHEN 0 THEN cast(cast(cat AS int) AS varchar(50))
ELSE cast(cam AS varchar(50))
END
FROM (SELECT TYPE, 'maxres' info, RESULT value FROM minmax WHERE 1 = d
UNION ALL
SELECT TYPE, 'minres' info, RESULT value FROM minmax WHERE 1 = a
UNION ALL
SELECT TYPE, 'maxdate' info , [date] value FROM minmax WHERE 1 = d
UNION ALL
SELECT TYPE, 'mindate' info , [date] value FROM minmax WHERE 1 = a) DATA
PIVOT
(max(value) FOR TYPE IN ([CAM], [CAR], [CAT])) pvt
It's only a proof of concept so in SQLFiddle I have used a reducet set of fake data (3 row per 3 Type)
After the data preparation
SELECT TYPE, 'maxres' info, RESULT value FROM minmax WHERE 1 = d
UNION ALL
SELECT TYPE, 'minres' info, RESULT value FROM minmax WHERE 1 = a
UNION ALL
SELECT TYPE, 'maxdate' info , [date] value FROM minmax WHERE 1 = d
UNION ALL
SELECT TYPE, 'mindate' info , [date] value FROM minmax WHERE 1 = a
the value column is implicitly casted to the more complex datatype, in this case DateTime (you cannot have different data type in the same column), to see the data in the intended way an explicit cast is in needed, and is done with the CASE and CAST in
, cam = CASE charindex('date', info)
WHEN 0 THEN cast(cast(cam AS int) AS varchar(50))
ELSE cast(cam AS varchar(50))
END
the CASE check the data type, looking for the substring 'date' in the info column, then cast the row value back to INT for the minres and maxres column and in any case cast the value to varchar(50) to have the same data type again
UPDATE
With the sql_variant the CASE CAST block is not needed, thanks Ryx5
WITH minmax AS (
SELECT TYPE, RESULT, [date]
, row_number() OVER (partition BY TYPE ORDER BY TYPE, RESULT) a
, row_number() OVER (partition BY TYPE ORDER BY TYPE, RESULT DESC) d
FROM table_name)
SELECT info
, [CAM], [CAR], [CAT]
FROM (SELECT TYPE, 'maxres' info, cast(RESULT as sql_variant) value
FROM minmax WHERE 1 = d
UNION ALL
SELECT TYPE, 'minres' info, cast(RESULT as sql_variant) value
FROM minmax WHERE 1 = a
UNION ALL
SELECT TYPE, 'maxdate' info , cast([date] as sql_variant) value
FROM minmax WHERE 1 = d
UNION ALL
SELECT TYPE, 'mindate' info , cast([date] as sql_variant) value
FROM minmax WHERE 1 = a) DATA
PIVOT
(max(value) FOR TYPE IN ([CAM], [CAR], [CAT])) pvt

Related

Why null value in the table getting error while using lead function

I am getting this error - Error converting data type nvarchar to numeric.
I have data coming from a table and I need only two values from the table where I filter only the number (no alphanumeric so used the isnumeric(covrg_cd)=1). The input data looks like the first picture. The Row 1 will always be null and in other other rows, there may or may not be data. However, because row 1 is always null, the lead function is throwing this error: Error converting data type nvarchar to numeric, but the rate column is always in nvarchar. I am using LEAD function in SQL to get the paybandfrom & paybandto using the Rate from Input table and using row_number() to get the tier value.
Input table
out put must be like this..
I have my query like this
SELECT a.payband , a.[from] as pybdnfrom, (RIGHT('00000000000000000000' + CAST(A.[TO] AS VARCHAR),20)) AS pybndto , a.tier
FROM (SELECT DISTINCT A.RATE as payband, A.RATE as [from], CASE WHEN TIER <> 4 THEN A.[TO] ELSE 100000000.000 END AS [to], ROW_NUMBER() OVER(ORDER BY RATE) AS TIER
FROM(SELECT DISTINCT A.RATE, LEAD(SUM((CONVERT(NUMERIC(20,3), (A.RATE)))-0.010)) OVER(ORDER BY A.RATE) AS [TO], ROW_NUMBER() OVER(ORDER BY A.RATE) AS TIER
FROM (SELECT DISTINCT BN_RATE_KEY02 as RATE, COVRG_CD AS COVERAGE
from #tmppsRateCost
WHERE ISNUMERIC(COVRG_CD) = 1 AND COVRG_CD = '1')A GROUP BY A.RATE)A)A
ORDER BY 1
Any help would be appreciated.
The error is because the '' cannot be parsed as a number. It's not related to the LEAD.
If you want to keep that approach you can modify your query in this way (I just commented the parts I replaced):
SELECT a.payband
,a.[from] AS pybdnfrom
--,(RIGHT('00000000000000000000' + CAST(A.[TO] AS VARCHAR), 20)) AS pybndto
,CASE WHEN payband = '' THEN '' ELSE (RIGHT('00000000000000000000' + CAST(A.[TO] AS VARCHAR), 20)) END AS pybndto
,a.tier
FROM (
SELECT DISTINCT A.RATE AS payband
,A.RATE AS [from]
,CASE
WHEN TIER <> 5
THEN A.[TO]
ELSE 100000000.000
END AS [to]
,ROW_NUMBER() OVER (
ORDER BY RATE
) AS TIER
FROM (
SELECT DISTINCT A.RATE
--,LEAD(SUM((CONVERT(NUMERIC(20, 3), (A.RATE))) - 0.010)) OVER (
,LEAD(SUM((CONVERT(NUMERIC(20, 3), (CASE WHEN A.RATE = '' THEN '0.010' ELSE A.RATE END))) - 0.010)) OVER (
ORDER BY A.RATE
) AS [TO]
,ROW_NUMBER() OVER (
ORDER BY A.RATE
) AS TIER
FROM (
SELECT DISTINCT BN_RATE_KEY02 AS RATE
,COVRG_CD AS COVERAGE
FROM #tmppsRateCost
WHERE ISNUMERIC(COVRG_CD) = 1
AND COVRG_CD = '1'
) A
GROUP BY A.RATE
) A
) A
ORDER BY 1
Anyway I guess you might have a cleaner approach just by removing the empty line in the initial table.
Get rid of ISNUMERIC() and use TRY_CONVERT() insert of CONVERT(). In this condition:
WHERE ISNUMERIC(COVRG_CD) = 1 AND COVRG_CD = '1'
The ISNUMERIC() is just unneeded because you have an exact string comparison.
SELECT a.payband , a.[from] as pybdnfrom,
(RIGHT('00000000000000000000' + CAST(A.[TO] AS VARCHAR),20)) AS pybndto ,
a.tier
FROM (SELECT DISTINCT A.RATE as payband, A.RATE as [from],
CASE WHEN TIER <> 4 THEN A.[TO] ELSE 100000000.000 END AS [to],
ROW_NUMBER() OVER (ORDER BY RATE) AS TIER
FROM (SELECT DISTINCT A.RATE,
LEAD(SUM((TRY_CONVERT(NUMERIC(20,3), (A.RATE)))-0.010)) OVER (ORDER BY A.RATE) AS [TO],
ROW_NUMBER() OVER (ORDER BY A.RATE) AS TIER
FROM (SELECT DISTINCT BN_RATE_KEY02 as RATE, COVRG_CD AS COVERAGE
FROM #tmppsRateCost
WHERE COVRG_CD = '1'
)A
GROUP BY A.RATE
) A
) A
ORDER BY 1;

Combine multiple boolean columns into a single column

I am generation reports from an ERP system where users are provided with a check box which return a boolean value for each item selected. The database is hosted on SQL Server.
However, users can select Contracts with other values as well, as shown below.
I would like to capture the Categories as a single column and I don't mind having duplicate rows in the view. I would like the first row to return Contract and the second the other value selected, for the same Reference ID.
You can use apply :
select distinct t.*, tt.category
from t cross apply
( values ('Contracts', t.Contracts),
('Tender', t.Tender),
('Waiver', t.Waiver),
('Quotation', t.Quotation)
) tt(category, flag)
where flag = 1;
I guess a straightforward way is:
select *, 'Contract' as [Category] from [TableOne] where [Contract] = 1
union all select *, 'Tender' as [Category] from [TableOne] where [Tender] = 1
union all select *, 'Waiver' as [Category] from [TableOne] where [Waiver] = 1
union all select *, 'Quotation' as [Category] from [TableOne] where [Quotation] = 1
union all select *, '(none)' as [Category] from [TableOne] where [Contract]+[Tender]+[Waiver]+[Quotation] = 0
order by [Reference ID]
Note that the last line is put there just in case you need to handle the all-zero case.

Processing Delta feed data in SQL Server 2014

I am building a data warehouse currently that processes data (for the sake of this question, let's just say one table) from a table that is updated every 15 minutes. My process stores a snapshot of the table and then compares the refreshed version with the snapshot and then stores the difference - or delta - in a separate staging table that will then be processed at the end of the day. At the end of the day I want a row describing the name of the column that has changed with a timestamp, to be then used when create snapshots at any point in time. It is worth noting that by the end of the day, there can be multiple rows for each unique identifier created i.e. a row for every change someone might have actioned during the day. So, I am stuck on the last part. I found this clever link Return column Names of Changed values with XML but the problem is this is very inefficient when processing thousands of rows. I would be grateful to anyone who has
any ideas on a more appropriate solution (excluding change Data Capture)?
Thank you.
if OBJECT_ID('tempdb..#TempHistory') is Not Null
drop table #TempHistory
-- I just made up a few column here, there are LOADS of them in the real query but for Governance of course...
SELECT
distinct
a.ClaimId,
a.FirstName,
a.Surname,
a.Incident,
a.Total,
a.extractDate -- this field is create by ETL process
into #temphistory
FROM [Data Mart Test].[Staging].[Claim] a JOIN [Data Mart Test].[dbo].[Claim] b
ON a.ClaimId = b.ClaimId
WHERE
ISNULL(a.ClaimId,0) <> ISNULL(b.ClaimId,0) OR
ISNULL(a.FirstName,'') <> ISNULL(b.FirstName,'') OR
ISNULL(a.Surname,'') <> ISNULL(b.Surname,'') OR
ISNULL(a.Incident,'') <> ISNULL(b.Incident,'') OR
ISNULL(a.Total,0.0) <> ISNULL(b.Total,0.0) OR
if OBJECT_ID('tempdb..#TempHistoryA') is Not Null
drop table #TempHistoryA
select
*
, 1 as Version
into #TempHistoryA
FROM [Data Mart Test].[dbo].[Claim] where ClaimID in (select distinct claimid FROM #TempHistory)
if OBJECT_ID('tempdb..#TempHistoryB') is Not Null
drop table #TempHistoryB
SELECT *
,(RANK() OVER(PARTITION BY [ClaimId] ORDER BY [ExtractDate])) + 1 as Version
into #TempHistoryB
FROM [Data Mart Test].[Staging].[Claim]where ClaimID in (select distinct claimid FROM #TempHistory)
if OBJECT_ID('tempdb..#TempChanges') is Not Null
drop table #TempChanges
DECLARE #x xml
SET #x = (
SELECT
t2.ClaimID AS [#key]
, t2.Version AS [#version]
, ( SELECT t1.* FOR XML PATH('t1'), TYPE ) AS [*]
, ( SELECT t2.* FOR XML PATH('t2'), TYPE ) AS [*]
FROM #TempHistoryA AS t1
INNER JOIN #TempHistoryB AS t2
ON t1.ClaimID = t2.ClaimID
AND t1.Version = t2.Version - 1
FOR XML PATH('row'), ROOT('root')
);
WITH Nodes AS (
SELECT
C.value('../../#key', 'int') AS [Key]
, C.value('../../#version', 'int') AS Version_ID
, C.value('local-name(..)', 'varchar(255)')AS Version_Alias
, C.value('local-name(.)', 'varchar(max)') AS Field
, C.value('.', 'varchar(max)') AS Val
FROM #x.nodes('/root/row/*/*') AS T(C)
)
SELECT
[Key] as ClaimID,
x.ExtractDate,
Field
, Max(CASE Version_Alias WHEN 't1' THEN Val END) AS [Initial Value]
, Max(CASE Version_Alias WHEN 't2' THEN Val END) AS [New Value]
Into #TempChanges
FROM [Nodes]v
inner join [#TempHistoryB]x on x.ClaimId = v.[Key]
and x.Version = v.Version_ID
where Field not in ( 'ExtractDate','Version')
GROUP BY
[Key],
x.ExtractDate,
Field
HAVING Max(CASE Version_Alias WHEN 't1' THEN Val END) <> Max(CASE Version_Alias WHEN 't2' THEN Val END)
--Find records in [Data Mart Test].dbo.Claim that are not in [Data Mart Test].Staging.Claim
SELECT
*
FROM [Data Mart Test].dbo.Claim
WHERE ClaimId NOT IN (SELECT b.ClaimId FROM [Data Mart Test].Staging.Claim b)
delete from [Data Mart Test].[dbo].[Claim] where ClaimID in (select distinct claimid FROM #TempHistory)
delete from [#TempHistoryB]
where not exists
(
select
*
from
(select
claimid,
max(Version) as LastVersion
FROM #TempHistoryB b
group by ClaimId
) b
where b.ClaimId = claimid
and b.LastVersion = Version
)
insert into [Data Mart Test].[dbo].[Claim]
select
[ClaimId]
--a whole lot of other columns
from #TempHistoryB

speed up SQL Query

I have a query which is taking some serious time to execute on anything older than the past, say, hours worth of data. This is going to create a view which will be used for datamining, so the expectations are that it would be able to search back weeks or months of data and return in a reasonable amount of time (even a couple minutes is fine... I ran for a date range of 10/3/2011 12:00pm to 10/3/2011 1:00pm and it took 44 minutes!)
The problem is with the two LEFT OUTER JOINs in the bottom. When I take those out, it can run in about 10 seconds. However, those are the bread and butter of this query.
This is all coming from one table. The ONLY thing this query returns differently than the original table is the column xweb_range. xweb_range is a calculated field column (range) which will only use the values from [LO,LC,RO,RC]_Avg where their corresponding [LO,LC,RO,RC]_Sensor_Alarm = 0 (do not include in range calculation if sensor alarm = 1)
WITH Alarm (sub_id,
LO_Avg, LO_Sensor_Alarm, LC_Avg, LC_Sensor_Alarm, RO_Avg, RO_Sensor_Alarm, RC_Avg, RC_Sensor_Alarm) AS (
SELECT sub_id, LO_Avg, LO_Sensor_Alarm, LC_Avg, LC_Sensor_Alarm, RO_Avg, RO_Sensor_Alarm, RC_Avg, RC_Sensor_Alarm
FROM dbo.some_table
where sub_id <> '0'
)
, AddRowNumbers AS (
SELECT rowNumber = ROW_NUMBER() OVER (ORDER BY LO_Avg)
, sub_id
, LO_Avg, LO_Sensor_Alarm
, LC_Avg, LC_Sensor_Alarm
, RO_Avg, RO_Sensor_Alarm
, RC_Avg, RC_Sensor_Alarm
FROM Alarm
)
, UnPivotColumns AS (
SELECT rowNumber, value = LO_Avg FROM AddRowNumbers WHERE LO_Sensor_Alarm = 0
UNION ALL SELECT rowNumber, LC_Avg FROM AddRowNumbers WHERE LC_Sensor_Alarm = 0
UNION ALL SELECT rowNumber, RO_Avg FROM AddRowNumbers WHERE RO_Sensor_Alarm = 0
UNION ALL SELECT rowNumber, RC_Avg FROM AddRowNumbers WHERE RC_Sensor_Alarm = 0
)
SELECT rowNumber.sub_id
, cds.equipment_id
, cds.read_time
, cds.LC_Avg
, cds.LC_Dev
, cds.LC_Ref_Gap
, cds.LC_Sensor_Alarm
, cds.LO_Avg
, cds.LO_Dev
, cds.LO_Ref_Gap
, cds.LO_Sensor_Alarm
, cds.RC_Avg
, cds.RC_Dev
, cds.RC_Ref_Gap
, cds.RC_Sensor_Alarm
, cds.RO_Avg
, cds.RO_Dev
, cds.RO_Ref_Gap
, cds.RO_Sensor_Alarm
, COALESCE(range1.range, range2.range) AS xweb_range
FROM AddRowNumbers rowNumber
LEFT OUTER JOIN (SELECT rowNumber, range = MAX(value) - MIN(value) FROM UnPivotColumns GROUP BY rowNumber HAVING COUNT(*) > 1) range1 ON range1.rowNumber = rowNumber.rowNumber
LEFT OUTER JOIN (SELECT rowNumber, range = AVG(value) FROM UnPivotColumns GROUP BY rowNumber HAVING COUNT(*) = 1) range2 ON range2.rowNumber = rowNumber.rowNumber
INNER JOIN dbo.some_table cds
ON rowNumber.sub_id = cds.sub_id
It's difficult to understand exactly what your query is trying to do without knowing the domain. However, it seems to me like your query is simply trying to find, for each row in dbo.some_table where sub_id is not 0, the range of the following columns in the record (or, if only one matches, that single value):
LO_AVG when LO_SENSOR_ALARM=0
LC_AVG when LC_SENSOR_ALARM=0
RO_AVG when RO_SENSOR_ALARM=0
RC_AVG when RC_SENSOR_ALARM=0
You constructed this query assigning each row a sequential row number, unpivoted the _AVG columns along with their row number, computed the range aggregate grouping by row number and then joining back to the original records by row number. CTEs don't materialize results (nor are they indexed, as discussed in the comments). So each reference to AddRowNumbers is expensive, because ROW_NUMBER() OVER (ORDER BY LO_Avg) is a sort.
Instead of cutting this table up just to join it back together by row number, why not do something like:
SELECT cds.sub_id
, cds.equipment_id
, cds.read_time
, cds.LC_Avg
, cds.LC_Dev
, cds.LC_Ref_Gap
, cds.LC_Sensor_Alarm
, cds.LO_Avg
, cds.LO_Dev
, cds.LO_Ref_Gap
, cds.LO_Sensor_Alarm
, cds.RC_Avg
, cds.RC_Dev
, cds.RC_Ref_Gap
, cds.RC_Sensor_Alarm
, cds.RO_Avg
, cds.RO_Dev
, cds.RO_Ref_Gap
, cds.RO_Sensor_Alarm
--if the COUNT is 0, xweb_range will be null (since MAX will be null), if it's 1, then use MAX, else use MAX - MIN (as per your example)
, (CASE WHEN stats.[Count] < 2 THEN stats.[MAX] ELSE stats.[MAX] - stats.[MIN] END) xweb_range
FROM dbo.some_table cds
--cross join on the following table derived from values in cds - it will always contain 1 record per row of cds
CROSS APPLY
(
SELECT COUNT(*), MIN(Value), MAX(Value)
FROM
(
--construct a table using the column values from cds we wish to aggregate
VALUES (LO_AVG, LO_SENSOR_ALARM),
(LC_AVG, LC_SENSOR_ALARM),
(RO_AVG, RO_SENSORALARM),
(RC_AVG, RC_SENSOR_ALARM)
) x (Value, Sensor_Alarm) --give a name to the columns for _AVG and _ALARM
WHERE Sensor_Alarm = 0 --filter our constructed table where _ALARM=0
) stats([Count], [Min], [Max]) --give our derived table and its columns some names
WHERE cds.sub_id <> '0' --this is a filter carried over from the first CTE in your example

Select Statement Return 0 if Null

I have the following query
SELECT ProgramDate, [CountVal]= COUNT(ProgramDate)
FROM ProgramsTbl
WHERE (Type = 'Type1' AND ProgramDate = '10/18/11' )
GROUP BY ProgramDate
What happens is that if there is no record that matches the Type and ProgramDate, I do not get any records returned.
What I like to have outputted in the above is something like the following if there is no values returned. Notice how for the CountVal we have 0 even if there are no records returned that fit the match condition:
ProgramDate CountVal
10/18/11 0
This is a little more complicated than you would like however, it is very possible. You will first have to create a temporary table of dates. For example, the query below creates a range of dates from 2011-10-11 to 2011-10-20
CREATE TEMPORARY TABLE date_stamps AS
SELECT (date '2011-10-10' + new_number) AS date_stamp
FROM generate_series(1, 10) AS new_number;
Using this temporary table, you can select from it and left join your table ProgramsTbl. For example
SELECT date_stamp,COUNT(ProgramDate)
FROM date_stamps
LEFT JOIN ProgramsTbl ON ProgramsTbl.ProgramDate = date_stamps.date_stamp
WHERE Type = 'Type1'
GROUP BY ProgramDate;
Select ProgramDate, [CountVal]= SUM(occur)
from
(
SELECT ProgramDate, 1 occur
FROM ProgramsTbl
WHERE (Type = 'Type1' AND ProgramDate = '10/18/11' )
UNION
SELECT '10/18/11', 0
)
GROUP BY ProgramDate
Because each SELECT statement is really building a table of records you can use a SELECT query to build a table with both the program count and a default count of zero. This would require two SELECT queries (one to get the actual count, one to get the default count) and using a UNION to combine the two SELECT results into a single table.
From there you can SELECT from the UNIONed table to sum the CountVals (if the programDate occurs in the ProgramTable the CountVal will be
CountVal of the first query if it exists(>0) + CountVal of the second query (=0)).
This way even if there are no records for the desired programDate in ProgramTable you will get a record back indicating a count of 0.
This would look like:
SELECT ProgramDate, SUM(CountVal)
FROM
(SELECT ProgramDate, COUNT(*) AS CountVal
FROM ProgramsTbl
WHERE (Type = 'Type1' AND ProgramDate = '10/18/11' )
UNION
SELECT '10/18/11' AS ProgramDate, 0 AS CountVal) T1
Here's a solution that works on SQL Server; not sure about other db platforms:
DECLARE #Type VARCHAR(5) = 'Type1'
, #ProgramDate DATE = '10/18/2011'
SELECT pt.ProgramDate
, COUNT(pt2.ProgramDate)
FROM ( SELECT #ProgramDate AS ProgramDate
, #Type AS Type
) pt
LEFT JOIN ProgramsTbl pt2 ON pt.Type = pt2.Type
AND pt.ProgramDate = pt2.ProgramDate
GROUP BY pt.ProgramDate
Grunge but simple and efficient
SELECT '10/18/11' as 'Program Date', count(*) as 'count'
FROM ProgramsTbl
WHERE Type = 'Type1' AND ProgramDate = '10/18/11'
Try something along these lines. This will establish a row with a date of 10/18/11 that will definitely return. Then you left join to your actual data to get your desired count (which can now return 0 if there are no corresponding rows).
To do this for more than 1 date, you'd want to build a Date table that holds a list of all dates you want to query (so substitute the "select '10/18/11'" with "select Date from DateTbl").
SELECT ProgDt.ProgDate, [CountVal]= COUNT(ProgramsTbl.ProgramDate)
FROM (SELECT '10/18/11' as 'ProgDate') ProgDt
LEFT JOIN ProgramsTbl
ON ProgDt.ProgDate = ProgramsTbl.ProgramDate
WHERE (Type = 'Type1')
GROUP BY ProgDt.ProgDate
To create a date table that you can use for querying, do this (assumes SQL Server 2005+):
create table Dates (MyDate datetime)
go
insert into Dates
select top 100000 row_number() over (order by s1.name)
from master..spt_values s1, master..spt_values s2
go