Reputation: 23
For example I have a table with orders. The table has a key (Order.No), a date where the order was opened (Order.Open) and a date where the order was closed (Order.Close).
No Open Close --------- ---------- ---------- 2013-1208 2013-03-11 2013-03-26 2013-1272 2013-03-11 2013-03-11 2013-1273 2013-03-11 2013-03-11 2013-1274 2013-03-11 2013-03-11 2013-1275 2013-03-11 2013-03-11 2013-1280 2013-03-11 2013-06-26 2013-1281 2013-03-11 2013-04-18 2013-1282 2013-03-11 2013-03-14 2013-1287 2013-03-12 2013-04-18 2013-1291 2013-03-12 2013-03-12
Now I want to make a query, where I can find out for each month how many orders were still open on the last day of the month.
For example I want to find out how many orders were still open on the last day of January:
The order was closed on or after the first day of February and the order was opened before the first day of February:
SELECT COUNT(Order.No) 'Open', '1' 'Month', '2013' 'Year' FROM Orders
WHERE (Orders.Open < '2013-02-01') AND (Orders.Close >= '2013-02-01')
Now if I want to get this information for each month I would have to do:
SELECT COUNT(Order.No) 'Open', '1' 'Month', '2013' 'Year' FROM Orders
WHERE (Orders.Open < '2013-02-01') AND (Orders.Close >= '2013-02-01')
UNION
SELECT COUNT(Order.No) Open, '2' Month, '2013' 'Year' FROM Orders
WHERE (Orders.Open < '2013-03-01') AND (Orders.Close >= '2013-03-01')
UNION
SELECT COUNT(Order.No) 'Open', '3' 'Month', '2013' 'Year' FROM Orders
WHERE (Orders.Open < '2013-04-01') AND (Orders.Close >= '2013-04-01')
UNION
SELECT COUNT(Order.No) 'Open', '4' Month, '2013' Year FROM Orders
WHERE (Orders.Open < '2013-05-01') AND (Orders.Close >= '2013-05-01')
Can I somehow simplify this query so I don't have to write this for every month and year?
The required output would be something like:
Open Month Year ---- ----- ---- 684 1 2013 683 2 2013 760 3 2013 659 4 2013
Upvotes: 2
Views: 192
Reputation: 6073
Jt, Please see, whether this is working.
Let @Order
be the table.
DECLARE @Order TABLE (No VARCHAR(20),[OPEN] Date,[Close] Date)
SELECT YEAR([OPEN]),
MONTH([OPEN]),
SUM(IIF([Close] > DATEADD(DAY, 1,EOMONTH([OPEN])),1,0))
FROM @Order
GROUP BY YEAR([OPEN]),MONTH([OPEN])
Upvotes: 0
Reputation: 94914
First generate all months you want to see in your results. Then join each month with the open orders and count per month.
with months as
(
select
datefromparts(year(min(opened)), month(min(opened)), 1) as startdate
from orders
union all
select
dateadd(month, 1, startdate)
from months
where dateadd(month, 1, startdate) <= cast(getdate() as date)
)
select
year(months.startdate) as yr, month(months.startdate) as mon, count(orders.opened) as cnt
from months
left join orders
on orders.opened <= eomonth(months.startdate)
and coalesce(orders.closed, '2999-12-31') > eomonth(months.startdate)
group by year(months.startdate), month(months.startdate)
order by year(months.startdate), month(months.startdate);
Here is the SQL fiddle: http://sqlfiddle.com/#!6/12ee7/3.
In SQL Server 2008 the functions DATEFROMPARTS and EOMONTH are missing. Replace them by
cast( cast(year(min(opened)) as varchar) + '-' + cast(month(min(opened)) as varchar) + '-01' as date) as startdate
and
dateadd(day, -1, dateadd(month, 1, months.startdate))
Here is the SQL fiddle for SQL Server 2008: http://sqlfiddle.com/#!3/12ee7/5.
Upvotes: 3
Reputation: 69769
You can just enter the list of months/years you require and join to them, e.g.
SELECT [Open] = COUNT(o.No),
[Month] = DATEPART(MONTH, d.[Date]),
[Year] = DATEPART(YEAR, d.[Date])
FROM (VALUES
('2013-01-01'),
('2013-02-01'),
('2013-03-01'),
('2013-04-01'),
('2013-05-01'),
('2013-06-01')
) d (Date)
LEFT JOIN Orders AS o
ON o.[Open] < d.[Date]
AND o.[Close] >= d.[Date]
GROUP BY d.Date;
If you have a calendar table then you can use this instead of a list of hard coded dates.
If you don't want to hardcode the dates you want then you can fairly easily generate a list on the fly, first generate a list of numbers:
WITH E1 AS -- 10 ROWS
( SELECT N = 1
FROM (VALUES (1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) n (N)
), E2 AS -- 10 X 10 = 100 ROWS
( SELECT N = 1
FROM E1 CROSS JOIN E1 AS E2
), E3 AS -- 100 x 100 = 10,000 ROWS
( SELECT N = 1
FROM E2 CROSS JOIN E2 AS E3
)
SELECT N = ROW_NUMBER() OVER(ORDER BY N)
FROM E3;
This is just an example, but will generate 10,000 sequential numbers, realistically you probably don't need to report on 10,000 months, but it doesn't hurt to demonstrate. Then you can turn this list of numbers into a list of dates:
WITH E1 (N) AS
( SELECT 1
FROM (VALUES (1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) n (N)
),
E2 (N) AS (SELECT 1 FROM E1 CROSS JOIN E1 AS E2),
E3 (N) AS (SELECT 1 FROM E2 CROSS JOIN E2 AS E3)
SELECT [Date] = DATEADD(MONTH, ROW_NUMBER() OVER(ORDER BY N) - 1, '19000101')
FROM E3;
Then you can use this as your main table and LEFT JOIN to Orders:
WITH E1 (N) AS
( SELECT 1
FROM (VALUES (1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) n (N)
),
E2 (N) AS (SELECT 1 FROM E1 CROSS JOIN E1 AS E2),
E3 (N) AS (SELECT 1 FROM E2 CROSS JOIN E2 AS E3),
Dates AS
( SELECT [Date] = DATEADD(MONTH, ROW_NUMBER() OVER(ORDER BY N) - 1, '19000101')
FROM E3
)
SELECT [Open] = COUNT(o.No),
[Month] = DATEPART(MONTH, d.[Date]),
[Year] = DATEPART(YEAR, d.[Date])
FROM Dates AS d
LEFT JOIN Orders AS o
ON o.[Open] < d.[Date]
AND o.[Close] >= d.[Date]
WHERE d.Date >= '20130101' -- OR WHATEVER DATE YOU LIKE
AND d.Date < GETDATE();
For some further reading on generating and using a numbers/dates table, both static and on the fly, take a look at this series:
Upvotes: 2
Reputation: 670
Try this
SELECT DISTINCT COUNT(no) OVER (PARTITION BY DATEPART(mm,Open)) AS [Open]
DATEPART(mm,Open) AS [Month],
DATEPART(mm,Open) AS [Year]
FROM dbo.tble
Upvotes: 1
Reputation: 1007
First, create a month series so you don't have any gaps in your result set. Then join this to your Order table on the conditions needed.
Group the results by the month series and done.
;with months as (
select cast('2013-01-01' as datetime) as dt union all
select cast('2013-02-01' as datetime) union all
select cast('2013-03-01' as datetime) union all
select cast('2013-04-01' as datetime) union all
select cast('2013-05-01' as datetime) union all
select cast('2013-06-01' as datetime) union all
select cast('2013-07-01' as datetime) union all
select cast('2013-08-01' as datetime) union all
select cast('2013-09-01' as datetime) union all
select cast('2013-10-01' as datetime) union all
select cast('2013-11-01' as datetime) union all
select cast('2013-12-01' as datetime)
)
select count(o.No), datepart(month, m.dt) month, datepart(year, m.dt) year
from months m
left join Orders o
on o.Open < dateadd(month, m.dt, 1)
and o.Close >= dateadd(month, m.dt, 1)
group by m.dt
Upvotes: 1
Reputation: 1142
Also try this. You can change condition to get better results
SELECT COUNT(`no`) Open,
DATE_FORMAT(`open`,'%Y-%m') as period,
month(`open`) Month,
year(`open`) Year
FROM `orders`
WHERE DATE_FORMAT(`close`, '%Y-%m') <> period
GROUP BY period
Upvotes: 2
Reputation: 293
SELECT COUNT(Order.No) Open, '1' Month, '2013' Year FROM Orders
group by Orders.Open, Orders.Close
Upvotes: 1