I have a data set with the following sample information:
ID DTE CNTR
1 20110102.0 2
1 20110204.0 3
1 20110103.0 5
2 20110205.0 6
2 20110301.0 7
2 20110302.0 3
If I want to group the information by month and sum the counter, the code I'm guessing would be this:
SELECT t.ID,
,SUM CASE(WHEN t.DTE between 20110101 and 20110131 then t.CNTR else 0) as Jan
,SUM CASE(WHEN t.DTE between 20110201 and 20110228 then t.CNTR else 0) as Feb
,SUM CASE(WHEN t.DTE between 20110301 and 20110331 then t.CNTR else 0) as Mar
FROM table t
GROUP BY t.ID
But, is there a way to aggregate that information into another two columns called "month" and "year" and group it that way, leaving me flexibility to preform select queries over many different time periods?
Edit, since your datatype is a decimal, you can use the following:
select ID,
left(dte, 4) year,
SUBSTRING(cast(dte as varchar(8)), 5, 2) mth,
sum(CNTR) Total
from yt
group by id, left(dte, 4), SUBSTRING(cast(dte as varchar(8)), 5, 2)
My suggestion would be to use the correct datatype for this which would be datetime. Then if you want the data for each month in columns, then you can use:
SELECT t.ID
, SUM(CASE WHEN t.DTE between '20110101' and '20110131' then t.CNTR else 0 end) as Jan
, SUM(CASE WHEN t.DTE between '20110201' and '20110228' then t.CNTR else 0 end) as Feb
, SUM(CASE WHEN t.DTE between '20110301' and '20110331' then t.CNTR else 0 end) as Mar
, year(dte)
FROM yt t
GROUP BY t.ID, year(dte);
This query includes a column to get the year for each DTE value.
If you want the result in multiple rows instead of columns, then you can use:
select ID,
YEAR(dte) year,
datename(m, dte) month,
sum(CNTR) Total
from yt
group by id, year(dte), datename(m, dte);
Note, this assumes that the DTE is a date datatype.
Another idea - using div/modulo operators:
select (dte / 10000) dte_year, ((dte % 10000) / 100) dte_month,sum(cntr)
from tablename
group by (dte / 10000) , ((dte % 10000) / 100)
select id,
datepart(yy, convert(datetime, dte, 112)),
datepart(mm, convert(datetime, dte, 112)),
sum(cntr)
from table
group by id,
datepart(yy, convert(datetime, dte, 112)),
datepart(mm, convert(datetime, dte, 112))
Related
How do I change the below query to pull by day and not month?
select CONVERT(CHAR(4), dCompletedDate, 100) + CONVERT(CHAR(4), dCompletedDate, 120) as MonthYear,
count(case when dCompletedDate is not null then 1 else 0 end) as 'Total Completes'
from TProfile a
left join TStudyTable b
on a.lhouseholdid=b.lhouseholdid
where
dcompleteddate >= '1/1/1990' and dcompleteddate < '1/1/2050'
group by CONVERT(CHAR(4), dCompletedDate, 100) + CONVERT(CHAR(4), dCompletedDate, 120)
order by MonthYear
Assuming that dcompleteddate is of datetime datatype, you can just cast it to a date:
select
cast(dcompleteddate as date) as dcompletedday,
count(*) as total_completes
from tprofile
left join tstudytable s on p.lhouseholdid = s.lhouseholdid
where dcompleteddate >= '1990-01-01' and dcompleteddate < '2050-01-01'
group by cast(dcompleteddate as date)
order by dcompletedday
Side notes:
I changed the conditional count() to just count(*); you are filtering on dcompleteddate so it is guaranteed to be not null (and even if it was, you current expression would still count null values)
I also changed the where clause to use literal dates that are more standard
I have an SQL query that I use to show the # of work orders created by day within a designated date range. It works well when I query a single date range, such as 3/1/2016 to 3/31/2016.
SELECT DATEADD(dd, DATEDIFF(dd, 0, dateCreated), 0) the_date,
COUNT(*) work_order_count
FROM WorkOrder
where active = 1 and
dateCreated >= '3/1/2016' and
dateCreated <= '3/31/2016 23:59:59'
GROUP BY DATEADD(dd, DATEDIFF(dd, 0, dateCreated), 0)
order by 1;
I want to take this same query a step further so that I can query the same data but show results for multiple date ranges. My specific purpose is to show data side-by-side for previous year comparison. Ideally I want to show the # of work orders created for 3/1-3/31 in 2014, 2015 and 2016, but all within the same view/result.
Is this possible? I've looked into joins but they appear to be when you are using different tables, not the same one.
Just use conditional aggregation (or a pivot):
SELECT CAST(dateCreated as DATE) as the_date,
SUM(CASE WHEN YEAR(dateCreated) = 2014 THEN 1 ELSE 0 END) as cnt_2014,
SUM(CASE WHEN YEAR(dateCreated) = 2015 THEN 1 ELSE 0 END) as cnt_2015,
SUM(CASE WHEN YEAR(dateCreated) = 2016 THEN 1 ELSE 0 END) as cnt_2016
FROM WorkOrder
WHERE active = 1 and
MONTH(dateCreated) = 3 and
YEAR(dateCreated) in (2014, 2015, 2016)
GROUP BY CAST(dateCreated as DATE)
ORDER BY the_date;
Use pivot table:
;with cte1 as (
select dateCreated, datepart(year, dateCreated) as Y
from WorkOrder
where datepart(month, dateCreated) = 3 and datepart(day, dateCreated) between 5 and 25
)
SELECT *
FROM cte1
PIVOT
(
count(dateCreated)
FOR Y in ([2014], [2015], [2016])
) as pv
How about something like:
SELECT
SUM(
CASE
WHEN dateCreated BETWEEN '3/1/2014' AND '3/31/2014 23:59:59'
THEN 1
ELSE 0
END) AS March2014,
SUM(
CASE
WHEN dateCreated BETWEEN '3/1/2015' AND '3/31/2015 23:59:59'
THEN 1
ELSE 0
END) AS March2015,
SUM(
CASE
WHEN dateCreated BETWEEN '3/1/2016' AND '3/31/2016 23:59:59'
THEN 1
ELSE 0
END) AS March2016
FROM WorkOrder
WHERE active = 1;
SELECT datepart(dd, 'yyyy'), datepart(dd, 'MM'), datepart(dd, 'dd'),
COUNT(*) work_order_count
FROM WorkOrder
where active = 1
and dateCreated >= '3/1/2016'
and dateCreated < '4/1/2020'
GROUP BY datepart(dd, 'yyyy'), datepart(dd, 'MM'), datepart(dd, 'dd')
order by datepart(dd, 'yyyy'), datepart(dd, 'MM'), datepart(dd, 'dd');
I need to write an SQL query to produce the following result set. What's the best way to achieve this?
Time Range Qty Amount
===============================================
Last 24 Hours 56 $2000
Last 7 Days 359 $3900
Last 14 Days 2321 $22,888
select 'Last 24 hours'
, sum(Qty) as Qty
, sum(Amount) as Amount
from YourTable
where TradeDt > dateadd(hour, -24, getdate())
union all
select 'Last 7 days'
, sum(Qty)
, sum(Amount)
from YourTable
where TradeDt > dateadd(day, -7, getdate())
union all
select 'Last 14 days'
, sum(Qty)
, sum(Amount)
from YourTable
where TradeDt > dateadd(day, -14, getdate())
My first guess would be to use UNION, if you absolutely need a table as a result (otherwise, you could just fetch the data row by row).
I don't think that there is any nicer way to do this in SQL.
SELECT 'Last 24 hours', SUM(qty), SUM(amount)
FROM table
WHERE datediff(day, getdate(), date) = 1
UNION
SELECT 'Last 7 days', SUM(qty), SUM(amount)
FROM table
WHERE datediff(getdate(), date, 'day') < 7
UNION
SELECT 'Last 14 days', SUM(qty), SUM(amount)
FROM table
WHERE datediff(getdate(), date, 'day') < 14
you can use a where clause on dateAdd like
select * from table
where datefield > DateAdd(d,-1,getdate())
select * from table
where datefield > DateAdd(d,-7,getdate())
select * from table
where datefield > DateAdd(d,-14,getdate())
for day and so on
Given a From Date, To Date and a Fiscal Year system, I want to get all the split-up duration within the
given From & To Date based on the Fiscal Year system. Explained below with examples:
Example 1:
Fiscal Year system: Apr to Mar
From Date: Jan-05-2008
To Date: May-15-2008
Based on Fiscal Year system, duration should be splitted into:
Jan-05-2008 to Mar-31-2008
Apr-01-2008 to May-15-2008
Example 2:
Fiscal Year system: Apr to Mar
From Date: Jan-17-2008
To Date: May-20-2009
Based on Fiscal Year system, duration should be splitted into:
Jan-17-2008 to Mar-31-2008
Apr-01-2008 to Mar-31-2009
Apr-01-2009 to May-20-2009
Am looking for approach/algorithm to solve this in PostgreSQL 8.2.
Regards,
Gnanam
I actually favor Andomar's solution (with the addition of a processes that automatically fills the Periods table), but for fun here's a solution that doesn't require it.
CREATE TABLE your_table (start_date date, end_date date);
INSERT INTO your_table VALUES ('Jan-17-2008', 'May-20-2009');
SELECT
GREATEST(start_date, ('04-01-'||series.year)::date) AS year_start,
LEAST(end_date, ('03-31-'||series.year + 1)::date) AS year_end
FROM
(SELECT
start_date,
end_date,
generate_series(
date_part('year', your_table.start_date - INTERVAL '3 months')::int,
date_part('year', your_table.end_date - INTERVAL '3 months')::int)
FROM your_table) AS series(start_date, end_date, year)
ORDER BY
start_date;
You could create a table containing the start and end of all fiscal years, f.e.
Periods (PeriodStartDt, PeriodEndDt)
Then you can join the tables together if they at least partly overlap. Use a case statement to select the end of the period or the end of the row, depending on which is later. For example (not tested):
select case when yt.StartDt < p.PeriodStartDt then p.PeriodStartDt
else yt.StartDt
end as SplitStart
, case when yt.EndDt > p.PeriodEndDt then p.PeriodEndDt
else yt.EndDt
end as SplitEnd
, yt.*
from YourTable yt
inner join Periods p
on yt.StartDt < p.PeriodEndDate
and yt.EndDt >= p.PeriodStartDate
CREATE TABLE your_table (start_date date, end_date date);
INSERT INTO your_table VALUES (CONVERT (date, GETDATE()),CONVERT (date, DATEADD(year, -1, GETDATE())) );
WITH mycte AS
(
SELECT 1 as id
UNION ALL
SELECT id + 1
FROM mycte
WHERE id + 1 < = 12
),
cte_distribution as
(
SELECT *,
DATEPART (month,DATEADD(month, mycte.id - 1, your_table.start_date)) as month_number ,
DATEPART (YEAR,DATEADD(month, mycte.id - 1, your_table.start_date)) as cal_year,
12000/12 as cash
FROM your_table
CROSS JOIN mycte
)
select
*,
(CASE WHEN month_number between 1 and 3 THEN '1st quarter' WHEN month_number between 4 and 6 THEN '2nd quarter' WHEN month_number between 7 and 9 THEN '3rd quarter' WHEN month_number between 9 and 12 THEN '4th quarter' END) as Quarter,
CASE WHEN month_number between 1 and 6 THEN CAST(CAST((cal_year - 1) as CHAR(4)) + '-' + CAST(cal_year as CHAR(4)) AS CHAR(9)) WHEN month_number between 6 and 12 THEN CAST(CAST((cal_year) as CHAR(4)) + '-' + CAST((cal_year + 1) as CHAR(4)) AS CHAR(9)) ELSE NULL END as fin_year
from cte_distribution;
Given a table:
create table #orders (
orderid int,
orderdatetime datetime
)
What is the best way to write sql to output a report containing the count of orders from the current and previous 24 hours, total orders for the current day and previous 7 days, total orders for the current week and previous 4 weeks, and total orders for the month and previous 6 months?
I'm wondering if this can be efficiently rolled up into a single sql using analytical functions, or if 4 sql statements generating the 4 groups of data is the only (or best) way.
Also, given the hourly/day/week grouping, how would one do that in sql server? Datetimes seem to be a pain in the ass everytime I have to do something like this with them...
Ideas? Put into a SSAS cube and do it from there maybe?
SELECT DATEPART(month, orderdatetime), DATEPART(week, orderdatetime), DATEPART(day, orderdatetime), COUNT(*)
FROM #orders
GROUP BY
DATEPART(month, orderdatetime), DATEPART(week, orderdatetime), DATEPART(day, orderdatetime) WITH ROLLUP
This will group the COUNT's by day, week and month in a single query.
The week rollups will have a NULL in DATEPART(day, orderdatetime) column, the month rollups will have a NULL in both DATEPART(day, orderdatetime) and DATEPART(week, orderdatetime) columns.
To make it for every hour, day, week or month from the current without gaps, use CTE's:
WITH q_hours AS
(
SELECT 0 AS col_hour
UNION ALL
SELECT col_hour + 1
FROM q_hours
WHERE col_hour < 22
),
q_days AS
(
SELECT 0 AS col_day
UNION ALL
SELECT col_day + 1
FROM q_days
WHERE col_day < 31
),
q_months AS
(
SELECT 0 AS col_month
UNION ALL
SELECT col_month + 1
FROM q_months
WHERE col_month < 12
)
SELECT col_month, col_day, col_hour, COUNT(orderid)
FROM q_hours
CROSS JOIN
q_days
CROSS JOIN
q_months
LEFT JOIN
#orders
ON DATEDIFF(month, orderdatetime, GETDATE()) = col_month
AND DATEDIFF(day, orderdatetime, GETDATE()) % 31 = col_day
AND DATEDIFF(hour, orderdatetime, GETDATE()) % 24 = col_hour
GROUP BY
col_month, col_day, col_hour WITH ROLLUP
HAVING (
col_month = 0
AND col_day = 0
AND col_hour IS NOT NULL
) -- all hours within 24 hours from now
OR
(
col_month = 0
AND col_day <= 7
AND col_hour IS NULL
) -- all days within 7 days from now
OR
(
col_month <= 6
AND col_day IS NULL
AND col_hour IS NULL
) -- all months within 6 months from now
You could run the four selects from a "dummy table" or an "identity" table that consists of a single row.
You could have:
SELECT
(<query count of orders current/prev 24 hours>) as <name1>,
(<total orders current + 7 days>) as <name2>,
(<total orders current week + 4 weeks>) as <name3>,
(<total orders month + 6 months>) as <name4>
FROM
<IDENTITY table>;
Because you want different timeframes for each datepart type, using a single query with rollup probably won't give you what you want. I'd consider just unioning them all together similar to something like this...
SELECT DatePartValue = DATEPART(HH, orderdatetime),
Type = 'Hourly',
COUNT(*)
FROM #orders
WHERE orderdatetime > DATEADD(HH, -25, GETDATE())
GROUP BY DATEPART(HH, orderdatetime)
UNION
SELECT DATEPART(DD, orderdatetime),
Type = 'Daily',
COUNT(*)
FROM #orders
WHERE orderdatetime > DATEADD(DD, -8, GETDATE())
GROUP BY DATEPART(DD, orderdatetime)
UNION
SELECT DATEPART(WEEK, orderdatetime),
Type = 'Weekly',
COUNT(*)
FROM #orders
WHERE orderdatetime > DATEADD(WEEK, -5, GETDATE())
GROUP BY DATEPART(WEEK, orderdatetime)
ORDER BY Type, DatePartValue
UNION
SELECT DATEPART(MM, orderdatetime),
Type = 'Monthly',
COUNT(*)
FROM #orders
WHERE orderdatetime > DATEADD(MM, -7, GETDATE())
GROUP BY DATEPART(MM, orderdatetime)
ORDER BY Type, DatePartValue
For results in one row, something like this:
select
orders_day = sum(case when datediff(hour,orderdatetime,getdate()) < 24 then 1 else 0 end)
, orders_week = sum(case when datediff(day,orderdatetime,getdate()) < 7 then 1 else 0 end)
, orders_month = sum(case when datediff(week,orderdatetime,getdate()) < 4 then 1 else 0 end)
, orders_half = sum(case when datediff(month,orderdatetime,getdate()) < 6 then 1 else 0 end)
from #orders
You may want to fine tune the date criteria to get appropriate behavior.
For multiple rows, take the results above and transpose it with UNPIVOT or CASE .. CROSS JOIN.
SELECT
sum(case when orderdatetime between GetDate() - 1 and GetDate() then 1 else 0 end) as Current24Hours,
sum(case when orderdatetime between GetDate() - 2 and GetDate() - 1 then 1 else 0 end) as Previous24Hours,
sum(case when orderdatetime between GetDate() - 7 and GetDate() then 1 else 0 end) as Current7Days,
sum(case when orderdatetime between GetDate() - 14 and GetDate() - 7 then 1 else 0 end) as Previous7Days,
sum(case when DATEDIFF (m, OrderDate, #now) <= 1 then 1 else 0 end) as PreviousMonth,
sum(case when DATEDIFF (m, OrderDate, #now) <= 6 then 1 else 0 end) as PreviousSixMonths
FROM orders
I think you want grouping sets. I've understand that sql server supports grouping sets.
EDIT1: I've read that sql server 2005 doesn't support grouping sets but sql server 2008 does. Here an interesting read on a presumed but not existing difference between mapreduce and an rdbms like Oracle and Sql Server. Please read the comments too!! http://www.data-miners.com/blog/2008/01/mapreduce-and-sql-aggregations.html