I have a history table ('property_histories') that logs events in our property management system. These events can be used to determine whether a given property was available to rent and I am trying to build a (weekly) summary of 'live' properties.
The 4 events in question are 'published', 'unpublished', 'hidden_from_search' and 'unhidden_from_search.
For a property to be live it must have been:
Published.
If it has ever been unpublished a subsequent published event mush be the most recent.
If it has ever been hidden_from_search a subsequent 'unhidden_from_search' event must have taken place more recently.
Most properties will have a simple history that most likely consists of a single 'Published' event but some are more complicated an example is here:
property_histories
----------------------------
id | property_id | City | status | date
1 | 325407 | Paris | published | 2014-01-01
2 | 325407 | Paris | hidden_from_search | 2014-01-24
3 | 325407 | Paris | unhidden_from_search | 2014-02-05
4 | 325407 | Paris | unpublished | 2014-02-15
5 | 410008 | London | published | 2014-01-01
6 | 410008 | London | unpublished | 2014-01-10
7 | 410008 | London | published | 2014-01-18
My aim is to be able to count 'live' properties by week:
weekly_count
----------------------------
Year | Week | City | Live_Count
2014 | 1 | Paris | 0
2014 | 1 | London | 0
2014 | 2 | Paris | 1
2014 | 2 | London | 1
2014 | 3 | Paris | 1
2014 | 3 | London | 0
2014 | 4 | Paris | 1
2014 | 4 | London | 1
2014 | 5 | Paris | 0
2014 | 5 | London | 1
2014 | 6 | Paris | 0
2014 | 6 | London | 1
2014 | 7 | Paris | 1
2014 | 7 | London | 0
2014 | 8 | Paris | 0
2014 | 8 | London | 1
2014 | 9 | Paris | 0
2014 | 9 | London | 1
----------------------------
Help appreciated!!
Your own test results don't match what you're asking for. You state the live count is by week, which means London should be live in week #1 as it was published in week #1 and then unpublished in week #2.
Assuming week starts on a Sunday (sql default) then this will work. Just put in your own date range, and replace my numbers table with yours.
If you need Monday to be your start date, use this at the top of your query
SET DATEFIRST 1
Emulating your test:
-- Create dummy data
CREATE TABLE #property_histories
(
id int, property_id int, City varchar(50), status varchar(50), date date
)
INSERT INTO #property_histories
SELECT 1 , 325407 , 'Paris' , 'published' , '2014-01-01' UNION ALL
SELECT 2 , 325407 , 'Paris' , 'hidden_from_search' , '2014-01-24' UNION ALL
SELECT 3 , 325407 , 'Paris' , 'unhidden_from_search' , '2014-02-05' UNION ALL
SELECT 4 , 325407 , 'Paris' , 'unpublished' , '2014-02-15' UNION ALL
SELECT 5 , 410008 , 'London' , 'published' , '2014-01-01' UNION ALL
SELECT 6 , 410008 , 'London' , 'unpublished' , '2014-01-10' UNION ALL
SELECT 7 , 410008 , 'London' , 'published' , '2014-01-18'
Now the code:
-- TODO: Set your date range
DECLARE #SD Datetime = '2014-01-01'
DECLARE #ED Datetime = '2014-12-31'
DECLARE #Wks INT = Datediff(week,#SD,#ED) -- Don't change this
-- Generate dates table
SELECT NumberID as 'Week',
DATEADD(DAY, 1-DATEPART(WEEKDAY, DateAdd(week,NumberID-1,#SD)), DateAdd(week,NumberID-1,#SD)) as 'WeekStart',
DATEADD(DAY, 7-DATEPART(WEEKDAY, DateAdd(week,NumberID-1,#SD)), DateAdd(week,NumberID-1,#SD)) as 'WeekEnd'
INTO #Dates
FROM Generic.tblNumbers -- TODO: use your own Numbers table here
WHERE NumberID BETWEEN 1 AND #Wks
-- Now generate report
SELECT T.Year, T.Week, T.City,
SUM(CASE WHEN PH1.status = 'published' THEN 1
WHEN PH1.status = 'unhidden_from_search' THEN 1
ELSE 0 END) as 'Live_Count'
FROM #Dates D1
LEFT JOIN
-- Get latest date per week
(SELECT YEAR(D.WeekStart) as 'Year',
D.Week,
PH.City,
PH.property_ID,
MAX(PH.date) as MaxDate
FROM #Dates D
LEFT JOIN #property_histories PH
ON PH.date BETWEEN #SD AND D.WeekEnd
GROUP BY D.WeekStart, D.Week, D.WeekStart, D.WeekEnd, PH.City, PH.property_id
) T
ON T.Week = D1.Week
LEFT JOIN #property_histories PH1
ON PH1.City = T.City AND PH1.property_id = T.property_id AND PH1.date = T.MaxDate
GROUP BY T.Year, T.Week, T.City
To break down the logic: Firstly I'm creating a helper table with week number, week start and week end dates. Week start is largely redundant but might come in handy for reporting.
I then subquery to get the latest date relevant for each week / city / property. For this "max" date, city and property I get the status, and if it's live, I sum it. So in layman terms ; get the latest status per city per property per week and SUM(if live).
Unlike the other answers posted, this solution caters for gaps in data. If the latest status recorded for a city and property was actually all the way back to week 1, it still works in any subsequent week.
I have a feeling I have missed a simpler way to do this.
However the following query uses 2 sub queries. The first gets all the published / unpublished ranges for a property (ie, the smallest unpublished date following a published date), while the 2nd does the same for properties being hidden from search.
These are then joined to properties on the property id, where the current date is within the range returned by the sub queries. The WHERE clause then checks that a record is matched for published and not found for the hidden sub queries
Had to use DISTINCT as otherwise the multiple published dates for a single unpublish would trigger duplicate property rows being returned.
SELECT DISTINCT properties.*
FROM properties
INNER JOIN
(
SELECT a.property_id, a.created_at AS start_date, IFNULL(MIN(b.created_at), NOW()) AS end_date
FROM property_histories a
LEFT OUTER JOIN property_histories b
ON a.property_id = b.propert_id
AND a.created_at < b.created_at
WHERE a.status = 'published'
AND b.status = 'unpublished'
GROUP BY a.property_id, a.created_at
) published
ON properties.property_id = published.property_id
AND NOW() BETWEEN published.start_date AND published.end_date
LEFT OUTER JOIN
(
SELECT a.property_id, a.created_at AS start_date, MIN(b.created_at) AS end_date
FROM property_histories a
LEFT OUTER JOIN property_histories b
ON a.property_id = b.propert_id
AND a.created_at < b.created_at
WHERE a.status = 'hidden_from_search'
AND b.status = 'unhidden_from_search'
GROUP BY a.property_id, a.created_at
) hidden
ON properties.property_id = hidden.property_id
AND NOW() BETWEEN hidden.start_date AND hidden.end_date
WHERE published.property_id IS NOT NULL
AND hidden.property_id IS NULL
I used a numbers table as a handy shortcut. Essentially, your question revolved around wanting to know a running sum of published or unhidden versus unpublished or hidden. At this point, the paper IDs become a moot point in the view (provided their uniqueness is properly constrained elsewhere), and all we need is a custom sum. I have the example on SQLFiddle. Here's the query:
select years.n + 2013 as year, weeks.n as week
, c.City
,
(select
sum(case
when status in ('published','unhidden_from_research') then 1
when status in ('unpublished','hidden_from_research') then -1
else 0
end)
from property_histories p2
where weekofyear(p2.date) <= weeks.n
and p2.city=c.city
) AS Live_Count
from numbers weeks
inner join numbers years on weeks.n <= 52
cross join (select City from property_histories group by city) c
where years.n + 2013 <= (select max(year(date)) from property_histories)
group by years.n + 2013, weeks.n
, c.City
;
Related
I'm aware that there are several answers on SO about cumulative totals. I have experimented and have not found a solution to my problem.
Here is a sqlfiddle.
We have a contacts table with two fields, eid and create_time:
eid create_time
991772 April, 21 2016 11:34:21
989628 April, 17 2016 02:19:57
985557 April, 04 2016 09:56:39
981920 March, 30 2016 11:03:12
981111 March, 30 2016 09:36:48
I would like to select the number of new contacts in each month along with the size of our contacts database at the end of each month. New contacts by year and month is simple enough. For the size of the contacts table at the end of each month I did some research and found what looked to be a straight forwards method:
set #csum = 0;
select
year(c.create_time) as yr,
month(c.create_time) as mth,
count(c.eid) as new_contacts,
(#csum + count(c.eid)) as cumulative_contacts
from
contacts c
group by
yr,
mth
That runs but gives me unexpected results.
If I run:
select count(*) from contacts where date(create_time) < current_date
I get the total number of records in the table 146.
I therefore expected the final row in my query using #csum to have 146 for April 2016. It has only 3?
What my goal is for field cumulative_contacts:
For the record with e.g. January 2016.
select count(*) from contacts where date(create_time) < '2016-02-01';
And the record for February would have:
select count(*) from contacts where date(create_time) < '2016-03-01';
And so on
Try this, a bit of modification from your sql;)
CREATE TABLE IF NOT EXISTS `contacts` (
`eid` char(50) DEFAULT NULL,
`create_time` timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
INSERT INTO `contacts` (`eid`, `create_time`) VALUES
('991772', '2016-04-21 11:34:21'),
('989628', '2016-04-17 02:19:57'),
('985557', '2016-04-04 09:56:39'),
('981920', '2016-03-30 11:03:12'),
('981111', '2016-03-30 09:36:48');
SET #csum = 0;
SELECT t.*, #csum:=(#csum + new_contacts) AS cumulative_contacts
FROM (
SELECT YEAR(c.create_time) AS yr, MONTH(c.create_time) AS mth, COUNT(c.eid) AS new_contacts
FROM contacts c
GROUP BY yr, mth) t
Output results is
| yr | mth | new_contacts | cumulative_contacts |
------ ----- -------------- ---------------------
| 2016 | 3 | 2 | 2 |
| 2016 | 4 | 3 | 5 |
This sql will get the cumulative sum and is pretty efficient. It numbers each row first and then uses that as the cumulative sum.
SELECT s1.yr, s1.mth, s1.new_contacts, s2.cummulative_contacts
FROM
(SELECT
YEAR(create_time) AS yr,
MONTH(create_time) AS mth,
COUNT(eid) AS new_contacts,
MAX(eid) AS max_eid
FROM
contacts
GROUP BY
yr,
mth
ORDER BY create_time) s1 INNER JOIN
(SELECT eid, (#sum:=#sum+1) AS cummulative_contacts
FROM
contacts INNER JOIN
(SELECT #sum := 0) r
ORDER BY create_time) s2 ON max_eid=s2.eid;
--Result sample--
| yr | mth | new_contacts | cumulative_contacts |
|------|-----|--------------|---------------------|
| 2016 | 1 | 4 | 132 |
| 2016 | 2 | 4 | 136 |
| 2016 | 3 | 7 | 143 |
| 2016 | 4 | 3 | 146 |
Try this: fiddele
Here you have a "greater than or equal" join, so each group "contains" all previous values. Times 12 part, converts the hole comparation to months. I did offer this solution as it is not MySql dependant. (can be implemented on many other DBs with minimun or no changes)
select dates.yr, dates.mth, dates.new_contacts, sum(NC.new_contacts) as cumulative_new_contacts
from (
select
year(c.create_time) as yr,
month(c.create_time) as mth,
count(c.eid) as new_contacts
from
contacts c
group by
year(c.create_time),
month(c.create_time)
) as dates
left join
(
select
year(c.create_time) as yr,
month(c.create_time) as mth,
count(c.eid) as new_contacts
from
contacts c
group by
year(c.create_time),
month(c.create_time)
) as NC
on dates.yr*12+dates.mth >= NC.yr*12+NC.mth
group by
dates.yr,
dates.mth,
dates.new_contacts -- not needed by MySql, present here for other DBs compatibility
order by 1,2
There is a table Remark that contains data as shown below:
SerialNo | RemarkNo | Desp
=============================================
10 | 1 | rainy
10 | 2 | sunny
11 | 1 | sunny
11 | 2 | rainy
11 | 3 | cloudy
11 | 4 | sunny
12 | 1 | rainy
What query will return the following result:
10 | 1 | rainy
11 | 3 | cloudy
12 | null | null
That is, the second last record in each group should be returned?
Assuming all the RemarkNo for a SerialNo are continuous. The larger the remark number, the later the remark was made. Hence, the second last RemarkNo for SerialNo 10 is 1 with Desp 'rainy'.
Try:
select s.SerialNo, r.RemarkNo, r.Desp
from (select SerialNo, max(RemarkNo) maxRemark from Remark group by SerialNo) s
left join Remark r on s.SerialNo = r.SerialNo and s.maxRemark-1 = r.RemarkNo
(SQLFiddle here.)
Here is some sql pseudo-code to get you started:
select
penultimate.*
from data as penultimate
left join (
/* query on data to return last row frome each group */
) as ultimate
on /* sql to join 2nd last record on each group to last group */
where not ultimate.SerialNo is null
Completely ineffective solution, but works ...
SELECT
SerialNo,
RemarkNo,
(SELECT Desp
FROM Remarks
WHERE SerialNo = r3.SerialNo AND RemarkNo = r3.RemarkNo) Desp
FROM (
SELECT
r.SerialNo,
(SELECT r2.RemarkNo
FROM Remarks r2
WHERE r.SerialNo = r2.SerialNo
ORDER BY r2.RemarkNo DESC
LIMIT 1, 1) RemarkNo
FROM (SELECT DISTINCT SerialNo FROM Remarks) r) r3
Working example: http://sqlfiddle.com/#!2/a1f89/22
I'm in trouble with a mysql statement counting appointments for one day within a given time period. I've got a calendar table including starting and finishing column (type = DateTime). The following statement should count all appointments for November including overall appointments:
SELECT
COUNT('APPOINTMENTS') AS Count,
DATE(c.StartingDate) AS Datum
FROM t_calendar c
WHERE
c.GUID = 'blalblabla' AND
((DATE(c.StartingDate) <= DATE('2012-11-01 00:00:00')) AND (DATE(c.EndingDate) >= DATE('2012-11-30 23:59:59'))) OR
((DATE(c.StartingDate) >= DATE('2012-11-01 00:00:00')) AND (DATE(c.EndingDate) <= DATE('2012-11-30 23:59:59')))
GROUP BY DATE(c.StartingDate)
HAVING Count > 1
But how to include appointments that starts before a StartingDate and ends on the StartingDate?
e.g.
StartingDate = 2012-11-14 17:00:00, EndingDate = 2012-11-15 08:00:00
StartingDate = 2012-11-15 09:00:00, EndingDate = 2012-11-15 10:00:00
StartingDate = 2012-11-15 11:00:00, EndingDate = 2012-11-15 12:00:00
My statement returns a count of 2 for 15th of November. But that's wrong because the first appointment is missing. How to include these appointments? What I am missing, UNION SELECT, JOIN, sub selection?
A possible solution?
SELECT
c1.GUID, COUNT('APPOINTMENTS') + COUNT(DISTINCT c2.ANYFIELD) AS Count,
DATE(c1.StartingDate) AS Datum,
COUNT(DISTINCT c2.ANYFIELD)
FROM
t_calendar c1
LEFT JOIN
t_calendar c2
ON
c2.ResourceGUID = c1.ResourceGUID AND
(DATE(c2.EndingDate) = DATE(c1.StartingDate)) AND
(DATE(c2.StartingDate) < DATE(c1.StartingDate))
WHERE
((DATE(c1.StartingDate) <= DATE('2012-11-01 00:00:00')) AND (DATE(c1.EndingDate) >= DATE('2012-11-30 23:59:59'))) OR
((DATE(c1.StartingDate) >= DATE('2012-11-01 00:00:00')) AND (DATE(c1.EndingDate) <= DATE('2012-11-30 23:59:59')))
GROUP BY
c1.ResourceGUID,
DATE(c1.StartingDate)
First: Consolidate range checking
First of all your two range where conditions can be replaced by a single one. And it also seems that you're only counting appointments that either completely overlap target date range or are completely contained within. Partially overlapping ones aren't included. Hence your question about appointments that end right on the range starting date.
To make where clause easily understandable I'll simplify it by using:
two variables to define target range:
rangeStart (in your case 1st Nov 2012)
rangeEnd (I'll rather assume to 1st Dec 2012 00:00:00.00000)
won't be converting datetime to dates only (using date function) the way that you did, but you can easily do that.
With these in mind your where clause can be greatly simplified and covers all appointments for given range:
...
where (c.StartingDate < rangeEnd) and (c.EndingDate >= rangeStart)
...
This will search for all appointments that fall in target range and will cover all these appointment cases:
start end
target range |==============|
partial front |---------|
partial back |---------|
total overlap |---------------------|
total containment |-----|
Partial front/back may also barely touch your target range (what you've been after).
Second: Resolving the problem
Why you're missing the first record? Simply because of your having clause that only collects those groups that have more than 1 appointment starting on a given day: 15th Nov has two, but 14th has only one and is therefore excluded because Count = 1 and is not > 1.
To answer your second question what am I missing is: you're not missing anything, actually you have too much in your statement and needs to simplified.
Try this statement instead that should return exactly what you're after:
select count(c.GUID) as Count,
date(c.StartingDate) as Datum
from t_calendar c
where (c.GUID = 'blabla') and
(c.StartingDate < str_to_date('2012-12-01', '%Y-%m-%d') and
(c.EndingDate >= str_to_date('2012-11-01', '%Y-%m-%d'))
group by date(c.StartingDate)
I used str_to_date function to make string to date conversion more safe.
I'm not really sure why you included having in your statement, because it's not really needed. Unless your actual statement is more complex and you only included part that's most relevant. In that case you'll likely have to change it to:
having Count > 0
Getting appointment count per day in any given date range
There are likely other ways as well but the most common way would be using a numbers or ?calendar* table that gives you the ability to break a range into individual points - days. They you have to join your appointments to this numbers table and provide results.
I've created a SQLFiddle that does the trick. Here's what it does...
Suppose you have numbers table Num with numbers from 0 to x. And appointments table Cal with your records. Following script created these two tables and populates some data. Numbers are only up to 100 which is enough for 3 months worth of data.
-- appointments
create table Cal (
Id int not null auto_increment primary key,
StartDate datetime not null,
EndDate datetime not null
);
-- create appointments
insert Cal (StartDate, EndDate)
values
('2012-10-15 08:00:00', '2012-10-20 16:00:00'),
('2012-10-25 08:00:00', '2012-11-01 03:00:00'),
('2012-11-01 12:00:00', '2012-11-01 15:00:00'),
('2012-11-15 10:00:00', '2012-11-16 10:00:00'),
('2012-11-20 08:00:00', '2012-11-30 08:00:00'),
('2012-11-30 22:00:00', '2012-12-05 00:00:00'),
('2012-12-01 05:00:00', '2012-12-10 12:00:00');
-- numbers table
create table Nums (
Id int not null primary key
);
-- add 100 numbers
insert into Nums
select a.a + (10 * b.a)
from (select 0 as a union all
select 1 union all
select 2 union all
select 3 union all
select 4 union all
select 5 union all
select 6 union all
select 7 union all
select 8 union all
select 9) as a,
(select 0 as a union all
select 1 union all
select 2 union all
select 3 union all
select 4 union all
select 5 union all
select 6 union all
select 7 union all
select 8 union all
select 9) as b
Now what you have to do now is
Select a range of days which you do by selecting numbers from Num table and convert them to dates.
Then join your appointments to those dates so that those appointments that fall on particular day are joined to that particular day
Then just group all these appointments per each day and get results
Here's the code that does this:
-- just in case so comparisons don't trip over
set names 'latin1' collate latin1_general_ci;
-- start and end target date range
set #s := str_to_date('2012-11-01', '%Y-%m-%d');
set #e := str_to_date('2012-12-01', '%Y-%m-%d');
-- get appointment count per day within target range of days
select adddate(#s, n.Id) as Day, count(c.Id) as Appointments
from Nums n
left join Cal c
on ((date(c.StartDate) <= adddate(#s, n.Id)) and (date(c.EndDate) >= adddate(#s, n.Id)))
where adddate(#s, n.Id) < #e
group by Day;
And this is the result of this rather simple select statement:
| DAY | APPOINTMENTS |
-----------------------------
| 2012-11-01 | 2 |
| 2012-11-02 | 0 |
| 2012-11-03 | 0 |
| 2012-11-04 | 0 |
| 2012-11-05 | 0 |
| 2012-11-06 | 0 |
| 2012-11-07 | 0 |
| 2012-11-08 | 0 |
| 2012-11-09 | 0 |
| 2012-11-10 | 0 |
| 2012-11-11 | 0 |
| 2012-11-12 | 0 |
| 2012-11-13 | 0 |
| 2012-11-14 | 0 |
| 2012-11-15 | 1 |
| 2012-11-16 | 1 |
| 2012-11-17 | 0 |
| 2012-11-18 | 0 |
| 2012-11-19 | 0 |
| 2012-11-20 | 1 |
| 2012-11-21 | 1 |
| 2012-11-22 | 1 |
| 2012-11-23 | 1 |
| 2012-11-24 | 1 |
| 2012-11-25 | 1 |
| 2012-11-26 | 1 |
| 2012-11-27 | 1 |
| 2012-11-28 | 1 |
| 2012-11-29 | 1 |
| 2012-11-30 | 2 |
Update #1: query gives me syntax error on Left Join line (running the query within the left join independently works perfectly though)
SELECT b1.company_id, ((sum(b1.credit)-sum(b1.debit)) as 'Balance'
FROM MyTable b1
JOIN CustomerInfoTable c on c.id = b1.company_id
#Filter for Clients of particular brand, package and active status
where c.brand_id = 2 and c.status = 2 and c.package_id = 3
LEFT JOIN
(
SELECT b2.company_id, sum(b2.debit) as 'Current_Usage'
FROM MyTable b2
WHERE year(b2.timestamp) = '2012' and month(b2.timestamp) = '06'
GROUP BY b2.company_id
)
b3 on b3.company_id = b1.company_id
group by b1.company_id;
Original Post:
I keep track of debits and credits in the same table. The table has the following schema:
| company_id | timestamp | credit | debit |
| 10 | MAY-25 | 100 | 000 |
| 11 | MAY-25 | 000 | 054 |
| 10 | MAY-28 | 000 | 040 |
| 12 | JUN-01 | 100 | 000 |
| 10 | JUN-25 | 150 | 000 |
| 10 | JUN-25 | 000 | 025 |
As my result, I want to to see:
| Grouped by: company_id | Balance* | Current_Usage (in June) |
| 10 | 185 | 25 |
| 12 | 100 | 0 |
| 11 | -54 | 0 |
Balance: Calculated by (sum(credit) - sum(debits))* - timestamp does not matter
Current_Usage: Calculated by sum(debits) - but only for debits in JUN.
The problem: If I filter by JUN timestamp right away, it does not calculate the balance of all time but only the balance of any transactions in June.
How can I calculate the current usage by month but the balance on all transactions in the table. I have everything working, except that it filters only the JUN results into the current usage calculation in my code:
SELECT b.company_id, ((sum(b.credit)-sum(b.debit))/1024/1024/1024/1024) as 'BW_remaining', sum(b.debit/1024/1024/1024/1024/28*30) as 'Usage_per_month'
FROM mytable b
#How to filter this only for the current_usage calculation?
WHERE month(a.timestamp) = 'JUN' and a.credit = 0
#Group by company in order to sum all entries for balance
group by b.company_id
order by b.balance desc;
what you will need here is a join with sub query which will filter based on month.
SELECT T1.company_id,
((sum(T1.credit)-sum(T1.debit))/1024/1024/1024/1024) as 'BW_remaining',
MAX(T3.DEBIT_PER_MONTH)
FROM MYTABLE T1
LEFT JOIN
(
SELECT T2.company_id, SUM(T2.debit) T3.DEBIT_PER_MONTH
FROM MYTABLE T2
WHERE month(T2.timestamp) = 'JUN'
GROUP BY T2.company_id
)
T3 ON T1.company_id-T3.company_id
GROUP BY T1.company_id
I havn't tested the query. The point here i am trying to make is how you can join your existing query to get usage per month.
alright, thanks to #Kshitij I got it working. In case somebody else is running into the same issue, this is how I solved it:
SELECT b1.company_id, ((sum(b1.credit)-sum(b1.debit)) as 'Balance',
(
SELECT sum(b2.debit)
FROM MYTABLE b2
WHERE b2.company_id = b1.company_id and year(b2.timestamp) = '2012' and month(b2.timestamp) = '06'
GROUP BY b2.company_id
) AS 'Usage_June'
FROM MYTABLE b1
#Group by company in order to add sum of all zones the company is using
group by b1.company_id
order by Usage_June desc;
I'm doing an inner join where i select between a date range (say, BETWEEN '2011-01-01' AND '2011-02-01'), and grouping by an enumerated value. is there a way to do this for each month as a column for a range of months? I'm currently doing this by hand for each month.
Example:
vehicle_type | January | February | March
----------------------------------------------
sedan | 12 | 10 | 4
coupe | 5 | 7 | 23
truck | 0 | 0 | 9
electric | 22 | 10 | 13
hybrid | 0 | 12 | 0
You could create a calendar table...
CREATE TABLE calendar
(
description VARCHAR2(100 BYTE),
when_start DATE,
when_end DATE
)
then use a pivot query
e.g.
SELECT
vehicle_type,
SUM(jan),SUM(feb),
--add the other months here
SUM(nov),SUM(dece)
FROM
(
SELECT v.vehicle_type,
CASE WHEN c.description='Jan' THEN
count(*)
END AS jan,
case when c.description='Feb' THEN
count(*)
END AS feb,
-- Add the rest of the months here too
CASE WHEN c.description='Nov' THEN
COUNT(*)
END AS nov,
CASE WHEN c.description='Dec' THEN
COUNT(*)
END AS dece
FROM calendar c
INNER JOIN vehicles v ON v.when >= c.when_start AND v.when <= c.when_end
GROUP BY v.vehicle_type
)
GROUP BY vehicle_type
ORDER BY vehicle_type