views:

97

answers:

3

This is how a table is presented

SELECT RequestsID, Country, Activity,
[People needed (each day)], [Start date], [End date]
FROM dbo.Requests

There will be a lot of requests, and I would like to sum up the "People needed" per day (!), not as Between Start- and End date.

Also I would like to group by country, and have the possibility to set between which dates I want to get data.

Some days might be empty regarding needed people (0), but the Date should be presented anyway. Note that there can be several requests pointing out the same Dates, and the same Country - but the Activity is then different.

The query should be like (well, it´s not SQL as you can see, just trying to show the logic)

From Requests,
show Country and SUM 'People needed'
where (column not in Requests table-Date) is a Date (will be
a number of dates, I want to set the scope by a Start and End date)
and Requests.Country is @Country 
(and the Date(s) above of course is between the Requests Start date and End date...)
And from (a non existing table...?) show Date
Group by Country

I would like to see something like this:

Date            Country         People needed

06/01/2010      Nigeria          34 // this might be from three different Requests, all pointing out Nigeria. People needed might be (30+1+3 = 34)
06/02/2010      Nigeria          10
06/03/2010      Nigeria           0
06/04/2010      Nigeria           1
06/05/2010      Nigeria         134

06/01/2010      China             2
06/02/2010      China             0
06/03/2010      China            14
06/04/2010      China            23
06/05/2010      China            33

06/01/2010      Chile             3
06/02/2010      Chile             4
06/03/2010      Chile             0
06/04/2010      Chile             0
06/05/2010      Chile            19

How would you do it?

NOTE: I would like to see some kind of example code, to get started :-)

+2  A: 

Typically I would use a tally or pivot table of all dates and then join based on that date being between the range.

A technique similar to that discussed here.

Cade Roux
+2  A: 

something like this?

select d.Date, c.Country, sum(People) as PeopleNeeded 
from Dates d left join Requests r on d.Date between r.Start and r.End
group by d.Date, c.Country

where Dates contains an appropriate range of dates, as in Cade Roux's answer

Yellowfog
Looks interesting, tested it in my visual studio query builder - though, when executing, Error message: Invalid object names 'Date'Any thoughts?
Jack Johnstone
Some of the answer is left as an exercise for the reader....
Yellowfog
That´s the kind of response I wasn´t looking for :-)But thanks for the original answer, it seems to be a way to go forward.However, I would like a real answer to my comment-question...
Jack Johnstone
Sorry, it's probably my fault for choosing 'Date' as the name of a column. This will be a reserved word, which you can reference using square brackets thus: [Date]. However, it would be easier just to change it to 'ActivityDate' or some such.
Yellowfog
Thanks, will try it out.
Jack Johnstone
Actually, no matter what I call Date, the same error message appears - if I for example call it d.Datovitje it will complain about invalid object names 'Datovitje'... Do I have to define the "virtual" column somewhere else?
Jack Johnstone
+2  A: 

Normally, I'd suggest having a static calendar table which contains a sequential list of dates. However, using Cade Roux's clever approach of generating a calendar table, you would have something like:

;With Calendar As
    (
     Select Cast(Floor(Cast(@StartDate As float)) As datetime) As [Date]
     Union All
     Select DateAdd(d, 1, [Date])
     From Calendar
     Where DateAdd(d, 1, [Date]) < @EndDate
    )
Select C.[Date], R.Country, Sum(R.PeopleNeeded)
From Calendar As C
    Left Join Requests As R
        On C.[Date] Between R.[Start Date] And R.[End Date]
            And ( @Country Is Null Or R.Country = @Country )
Group By C.[Date], R.Country    
Option (MAXRECURSION 0); 

Now, if it is the case that you want to filter on country such that the only days returned are those for the given country that have data, then you would simply need to change the Left Join to an Inner Join.

ADDITION

From the comments, it was requested to show all countries whether they have a Request or not. To do that, you need to cross join to the Countries table:

With Calendar As
    (
     Select Cast(Floor(Cast(@StartDate As float)) As datetime) As [Date]
     Union All
     Select DateAdd(d, 1, [Date])
     From Calendar
     Where DateAdd(d, 1, [Date]) < @EndDate
    )
Select C.[Date], C2.Country, Sum(R.PeopleNeeded)
From Calendar As C
    Cross Join Countries As C2
    Left Join Requests As R
        On C.[Date] Between R.[Start Date] And R.[End Date]
            And R.CountryId = C2.CountryId
Group By C.[Date], C2.Country    
Option (MAXRECURSION 0); 
Thomas
This too looks interesting, tested it in my visual studio query builder - though, when executing, Error message: Must declare scalar variable @StartDate, @EndDate, @Country. Any thoughts?
Jack Johnstone
@Jack Johnstone - Those represent the parameters you wanted to filter on. You would need to add those as parameters in your query or stored procedure or simply add `Declare` statements with `Set` statements if you are going to drop the code into SQL Server Management Studio in order to execute it.
Thomas
Yes, I understand that they are parameters :-) - I actually use parameters now and then. But when using them I don´t get that error message, that´s what confused me... I will try to fix this in management studio, but anyhow, I will access it through a dataset in visual studio, so it has to work there in the end. Or??? I´m not extremely stupid, but quite new to databases and datasets.
Jack Johnstone
@Jack Johnstone - If you are trying to test it out, simply replace @StartDate and @EndDate with actual dates surrounded by single quotes (e.g. '2010-06-24') and replace @Country with the name of a country in your database (surrounded by single quotes).
Thomas
OK, will do that!
Jack Johnstone
The problem was the ";" in the beginning and end of your code - however, it doesn´t work out yet, but I will try to fix it some way. One thing that isn´t clear to me is why you are converting the start date as float? When using the @StartDate parameter it doesn´t accept date as input...
Jack Johnstone
@Jack Johnstone - The semicolon was simply to ensure that the statement before the CTE is ended. If you execute the query as the first or only statement of the batch, then the semicolon isn't needed. The reason I cast start date to a float and then call Floor on it is to strip the time portion from the value.
Thomas
@Jack Johnstone - I'm not sure what you mean by "When using the @StartDate parameter it doesn´t accept date as input". If you call `Set @StartDate = '2010-06-26 13:54:00'` for example, it should work fine. If you are going to replace @StartDate with a static value which only includes the date, then you can skip casting it to a float.
Thomas
OK. Another thing, in the Requests table, column 'Country' is an int, but when executing the query (a dialog will pop up where I can state the country), it only accepts DateTime values - where did that come from?Apart from this (that is, using Country), I´m getting it to work. But Country is quite important...
Jack Johnstone
@Jack Johnstone - It sounds like the query designer you are using is not interpreting that @Country parameter as an integer. It is one of the many reasons I never use a query designer. Instead, in Mgmt Studio, open a blank query (File, New, Database Engine Query) which should give you a blank document where you can paste the above query, change to right db, replace the parameters and execute it to see the results.
Thomas
Fine, thanks for your answers!Now, I´ve got one problem left - Dates where "Sum(R.PeopleNeeded)" == 0 won´t show up - but I want them to. I could buy a workaround: is it possible to fill a column with "dummy-data" like "1" and let the Dates take that into concern? Later on, I´ll just hide the dummy-data column, and change R.PeopleNeeded == NULL to "0".In that case, how should I create such a dummy-column?Or is there a simpler way to show "empty" dates?
Jack Johnstone
@Jack Johnstone - There is nothing in my query that filters on Sum(R.PeopleNeeded) so they should show up. At worse, they should show as null. You can change that column in the query to `Coalesce(Sum(R.PeopleNeeded),0)` so that you always get a value.
Thomas
@ Thomas - You´re right, that wasn´t the problem. The problem is that I´m not able to set the @Country parameter to something useful for some reason - but, actually, no need for that as I would like to iterate through ALL countries, like in my original question (after "I would like to see something like this:") - then I would see what I want, that is, no need to specify a special country! So, how to do that, mr Magic :-)
Jack Johnstone
@Jack Johnstone - Just remove this line from my query: `And ( @Country Is Null Or R.Country = @Country )`.
Thomas
@Thomas - I´ve done that, but the problem is that Rows won´t show up for Dates where no Country is allocated. Let´s say I´ve got 7 countries, and 5 of them have no people allocated a certain day; that date will show the 2 countries with people allocated (two rows), but not the other 5 countries. And if none of the 7 countries have "allocations", the date won´t show up at all.
Jack Johnstone
@Jack Johnstone - I've updated my post. In short, you need to cross join to the Countries table so that you have every combination of dates and countries and then left join that to the Requests table to get your totals if they exist.
Thomas
@Thomas - sorry, dates will show up, but just in single instances with Country as NULL and R.PeopleNeeded as 0...So well I fooled you a bit! It´s like this then: When no people allocated for a date, I get the output "Date: (a date); Country: NULL, PeopleNeeded: 0", instead of seven rows like "Date: (a date); Country: CHILE, PeopleNeeded: 0", "Date: (a date); Country: CHINA, PeopleNeeded: 0", and so on. Again, sorry!
Jack Johnstone
@Thomas, OK, I will check your update!
Jack Johnstone
@Thomas, I admire your patience with all my questions, and also - you are a genious! You solved it! :-)Also, I learned a lot along the way by sitting here trying to test out different things!
Jack Johnstone