Handling non existent values in sql query expressi

2019-01-27 11:29发布

问题:

I am using the following query in an ssrs line chart. It counts how many orders are recorded each month based on each order date.

My problem is that when a month has no orders, rather than saying zero or null it removes the row for that month all together. I would prefer for it to count it as zero but null would be ok too.

Basically, I want to always have twelve rows whether they contain information or not.

How can I fix this? Is there an expression I can use or something? Or am I missing something completely obvious?

SELECT
MONTH(Ord.OrdDate) AS 'MONTH',
COUNT(CASE WHEN @Worker_ID1 IS NULL OR @Worker_ID1 = Worker.ID THEN 1 END) AS 'Worker1',
COUNT(CASE WHEN @Worker_ID2 IS NULL OR @Worker_ID2 = Worker.ID THEN 1 END) AS 'Worker2',
COUNT(CASE WHEN @Worker_ID3 IS NULL OR @Worker_ID3 = Worker.ID THEN 1 END) AS 'Worker3',
COUNT(CASE WHEN @Worker_ID4 IS NULL OR @Worker_ID4 = Worker.ID THEN 1 END) AS 'Worker4',
COUNT(CASE WHEN @Worker_ID5 IS NULL OR @Worker_ID5 = Worker.ID THEN 1 END) AS 'Worker5'

FROM Ord
JOIN Prod ON Ord.Prod_ID = Prod.ID
JOIN ProdType ON Prod.ProdType_ID = ProdType.ID
JOIN Grouping ON Ord.Grouping_ID = Grouping.ID
JOIN Worker ON Grouping.Worker_ID = Worker.ID

WHERE @Year = YEAR(Ord.OrdDate)
AND (@ProdType_ID IS NULL OR @ProdType_ID = ProdType.ID)

GROUP BY MONTH(Ord.OrdDate)

回答1:

As the above answers mentioned, you will need an outer join and some kind of calendar table. This is untested, but I think will work for you:

with dateCTE as
(
     select cast('2012-01-01' as datetime) dateValue -- start date
     union all
     select DateAdd(mm, 1, dateValue)
     from    dateCTE   
     where   dateValue < '2012-12-30' -- end date
 )
SELECT
MONTH(dateCTE.dateValue) AS 'MONTH',
COUNT(CASE WHEN @Worker_ID1 IS NULL OR @Worker_ID1 = Worker.ID THEN 1 END) AS 'Worker1',
COUNT(CASE WHEN @Worker_ID2 IS NULL OR @Worker_ID2 = Worker.ID THEN 1 END) AS 'Worker2',
COUNT(CASE WHEN @Worker_ID3 IS NULL OR @Worker_ID3 = Worker.ID THEN 1 END) AS 'Worker3',
COUNT(CASE WHEN @Worker_ID4 IS NULL OR @Worker_ID4 = Worker.ID THEN 1 END) AS 'Worker4',
COUNT(CASE WHEN @Worker_ID5 IS NULL OR @Worker_ID5 = Worker.ID THEN 1 END) AS 'Worker5'

FROM dateCTE
LEFT JOIN Ord on MONTH(dateCTE.datevalue) = MONTH(Ord.OrdDate)
JOIN Prod ON Ord.Prod_ID = Prod.ID
JOIN ProdType ON Prod.ProdType_ID = ProdType.ID
JOIN Groupord ON Ord.Groupord_ID = Groupord.ID
JOIN Worker ON Groupord.Worker_ID = Worker.ID

WHERE (@Year = YEAR(Ord.OrdDate) or ORD.prod_id is null)
AND (@DrugType_ID IS NULL OR @ProdType_ID = ProdType.ID)

GROUP BY MONTH(dateCTE.dateValue)
OPTION  (MAXRECURSION 0)


回答2:

You need a table containing months in order to make this work (or you can use a stored procedure or probably common table expression).

SELECT Months.Month, COUNT(Orders.OrderID)
FROM
 Months
LEFT OUTER JOIN
 Orders
ON
 MONTH(Orders.OrderDate) = Months.Month

Would ensure you got:

Month, Count
1, 1
2, 1
3, 2
4, NULL
etc


回答3:

;WITH m(m) AS ( SELECT TOP 12 ROW_NUMBER() OVER (ORDER BY [object_id]) FROM sys.objects )
SELECT [MONTH] = m.m,
  Worker1 = COUNT(CASE WHEN COALESCE(@Worker_ID1, w.ID) = w.ID THEN 1 END),
  Worker2 = COUNT(CASE WHEN COALESCE(@Worker_ID2, w.ID) = w.ID THEN 1 END),
  Worker3 = COUNT(CASE WHEN COALESCE(@Worker_ID3, w.ID) = w.ID THEN 1 END),
  Worker4 = COUNT(CASE WHEN COALESCE(@Worker_ID4, w.ID) = w.ID THEN 1 END),
  Worker5 = COUNT(CASE WHEN COALESCE(@Worker_ID5, w.ID) = w.ID THEN 1 END)
FROM m
LEFT OUTER JOIN dbo.Ord AS o
ON o.OrdDate >= DATEADD(MONTH, m.m-1, RTRIM(@Year)+'0101')
AND o.OrdDate < DATEADD(MONTH, m.m, RTRIM(@Year+'0101')
INNER JOIN dbo.Prod     AS p  ON o.Prod_ID     = p.ID
INNER JOIN dbo.ProdType AS pt ON p.ProdType_ID = pt.ID
INNER JOIN dbo.Grouping AS g  ON o.Grouping_ID = g.ID
INNER JOIN dbo.Worker   AS w  ON g.Worker_ID   = w.ID
WHERE (@DrugType_ID IS NULL OR pt.ID = @ProdType_ID)
GROUP BY m.m
ORDER BY m.m;


回答4:

Got it working by removing the where clause and filtering in the count aggregate.

SELECT
MONTH(Ord.OrdDate) AS 'MONTH',
COUNT(CASE WHEN @Year = YEAR(Ord.OrdDate) AND Worker.ID = @Worker_ID1 AND @ProdType_ID = ProdType.ID THEN 1 END) AS 'Worker1',
COUNT(CASE WHEN @Year = YEAR(Ord.OrdDate) AND Worker.ID = @Worker_ID2 AND @ProdType_ID = ProdType.ID THEN 1 END) AS 'Worker2',
COUNT(CASE WHEN @Year = YEAR(Ord.OrdDate) AND Worker.ID = @Worker_ID3 AND @ProdType_ID = ProdType.ID THEN 1 END) AS 'Worker3',
COUNT(CASE WHEN @Year = YEAR(Ord.OrdDate) AND Worker.ID = @Worker_ID4 AND @ProdType_ID = ProdType.ID THEN 1 END) AS 'Worker4',
COUNT(CASE WHEN @Year = YEAR(Ord.OrdDate) AND Worker.ID = @Worker_ID5 AND @ProdType_ID = ProdType.ID THEN 1 END) AS 'Worker5'

FROM Ord
JOIN Grouping ON Ord.Grouping_ID = Grouping.ID
JOIN Worker ON Grouping.Worker_ID = Worker.ID
JOIN Prod ON Ord.Prod_ID = Prod.ID
JOIN ProdType ON Prod.ProdType_ID = ProdType.ID 

GROUP BY MONTH(Ord.OrdDate)


回答5:

select a.mon,b.* from
(
select 1 as mon union select 2 as mon union select 3 as mon union select 4 as mon union 
select 5 as mon union select 6 as mon union select 7 as mon union select 8 as mon union 
select 9 as mon union select 10 as mon union select 11 as mon union  select 12 as mon 
) a
left outer join
(
   your existing query here
) b
on a.mon=b.MONTH(OrdDate)
go

Just copy and paste your query in the place holder and go. You will always get 12 rows only. And there is no need to create any tables too.



回答6:

that behavior is well defined by SQL construct "Inner join". use left join (or right join, depending on which is correct side) to retrieve null values when the join condition is not satisfied as below (not tested)

SELECT
MONTH(Ord.OrdDate) AS 'MONTH',
COUNT(CASE WHEN @Worker_ID1 IS NULL OR @Worker_ID1 = Worker.ID THEN 1 END) AS 'Worker1',
COUNT(CASE WHEN @Worker_ID2 IS NULL OR @Worker_ID2 = Worker.ID THEN 1 END) AS 'Worker2',
COUNT(CASE WHEN @Worker_ID3 IS NULL OR @Worker_ID3 = Worker.ID THEN 1 END) AS 'Worker3',
COUNT(CASE WHEN @Worker_ID4 IS NULL OR @Worker_ID4 = Worker.ID THEN 1 END) AS 'Worker4',
COUNT(CASE WHEN @Worker_ID5 IS NULL OR @Worker_ID5 = Worker.ID THEN 1 END) AS 'Worker5'

FROM Prod 
LEFT JOIN ORD ON Ord.Prod_ID = Prod.ID
JOIN ProdType ON Prod.ProdType_ID = ProdType.ID
JOIN Grouping ON Ord.Grouping_ID = Groupord.ID
JOIN Worker ON Grouping.Worker_ID = Worker.ID

WHERE ((Ord.OrdDate is not null and @Year = YEAR(Ord.OrdDate)) or ORD.prod_id is null)
AND (@ProdType_ID IS NULL OR @ProdType_ID = ProdType.ID)

GROUP BY MONTH(Ord.OrdDate)

Note - i added additional where clause conditions to check on the year function on orddate as this can be null

google for joins SQL and am sure you will find much more quality information than this

Hope this helps