Coldfusion query output order totals by day [duplicate] - sql

I have an SQL 2005 table, let's call it Orders, in the format:
OrderID, OrderDate, OrderAmount
1, 25/11/2008, 10
2, 25/11/2008, 2
3, 30/1002008, 5
Then I need to produce a report table showing the ordered amount on each day in the last 7 days:
Day, OrderCount, OrderAmount
25/11/2008, 2, 12
26/11/2008, 0, 0
27/11/2008, 0, 0
28/11/2008, 0, 0
29/11/2008, 0, 0
30/11/2008, 1, 5
The SQL query that would normally produce this:
select count(*), sum(OrderAmount)
from Orders
where OrderDate>getdate()-7
group by datepart(day,OrderDate)
Has a problem in that it will skip the days where there are no orders:
Day, OrderCount, OrderAmount
25/11/2008, 2, 12
30/11/2008, 1, 5
Normally I would fix this using a tally table and outer join against rows there, but I'm really looking for a simpler or more efficient solution for this. It seems like such a common requirement for a report query that some elegant solution should be available for this already.
So: 1. Can this result be obtain from a simple query without using tally tables?
and 2. If no, can we create this tally table (reliably) on the fly (I can create a tally table using CTE but recursion stack limits me to 100 rows)?

SQL isn't "skipping" dates... because queries run against data that is actually in the table. So, if you don't have a DATE in the table for January 14th, then why would SQL show you a result :)
What you need to do is make a temp table, and JOIN to it.
CREATE TABLE #MyDates ( TargetDate DATETIME )
INSERT INTO #MyDates VALUES CONVERT(DATETIME, CONVERT(VARCHAR, GETDATE() - 0, 101))
INSERT INTO #MyDates VALUES CONVERT(DATETIME, CONVERT(VARCHAR, GETDATE() - 1, 101))
INSERT INTO #MyDates VALUES CONVERT(DATETIME, CONVERT(VARCHAR, GETDATE() - 2, 101))
INSERT INTO #MyDates VALUES CONVERT(DATETIME, CONVERT(VARCHAR, GETDATE() - 3, 101))
INSERT INTO #MyDates VALUES CONVERT(DATETIME, CONVERT(VARCHAR, GETDATE() - 4, 101))
INSERT INTO #MyDates VALUES CONVERT(DATETIME, CONVERT(VARCHAR, GETDATE() - 5, 101))
INSERT INTO #MyDates VALUES CONVERT(DATETIME, CONVERT(VARCHAR, GETDATE() - 6, 101))
INSERT INTO #MyDates VALUES CONVERT(DATETIME, CONVERT(VARCHAR, GETDATE() - 7, 101))
SELECT CONVERT(VARCHAR, TargetDate, 101) AS Date, COUNT(*) AS OrderCount
FROM dbo.Orders INNER JOIN #MyDates ON Orders.Date = #MyDates.TargetDate
GROUP BY blah blah blah (you know the rest)
There you go!

I had the same problem and this is how I solved it:
SELECT datename(DW,nDays) TimelineDays,
Convert(varchar(10), nDays, 101) TimelineDate,
ISNULL(SUM(Counter),0) Totals
FROM (Select GETDATE() AS nDays
union Select GETDATE()-1
union Select GETDATE()-2
union Select GETDATE()-3
union Select GETDATE()-4
union Select GETDATE()-5
union Select GETDATE()-6) AS tDays
Left Join (Select * From tHistory Where Account = 1000) AS History
on (DATEPART(year,nDays) + DATEPART(MONTH,nDays) + DATEPART(day,nDays)) =
(DATEPART(year,RecordDate) + DATEPART(MONTH,RecordDate) + DATEPART(day,RecordDate))
GROUP BY nDays
ORDER BY nDays DESC
The ouput is:
TimelineDays, TimelineDate, Totals
Tuesday 10/26/2010 0
Monday 10/25/2010 6
Sunday 10/24/2010 3
Saturday 10/23/2010 2
Friday 10/22/2010 0
Thursday 10/21/2010 0
Wednesday 10/20/2010 0

Depending on how SQL Server handles temporary tables, you can more or less easily arrange to create a temporary table and populate it with the 7 (or was that 8?) dates you are interested in. You can then use that as your tally table. There isn't a cleaner way that I know of; you can only select data that exists in a table or that can be derived from data that exists in a table or set of tables. If there are dates not represented in the Orders table, you can't select those dates from the Orders table.

CREATE PROCEDURE [dbo].[sp_Myforeach_Date]
-- Add the parameters for the stored procedure here
#SatrtDate as DateTime,
#EndDate as dateTime,
#DatePart as varchar(2),
#OutPutFormat as int
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
Declare #DateList Table
(Date varchar(50))
WHILE #SatrtDate<= #EndDate
BEGIN
INSERT #DateList (Date) values(Convert(varchar,#SatrtDate,#OutPutFormat))
IF Upper(#DatePart)='DD'
SET #SatrtDate= DateAdd(dd,1,#SatrtDate)
IF Upper(#DatePart)='MM'
SET #SatrtDate= DateAdd(mm,1,#SatrtDate)
IF Upper(#DatePart)='YY'
SET #SatrtDate= DateAdd(yy,1,#SatrtDate)
END
SELECT * FROM #DateList
END
Just put this Code and call the SP
in This way
exec sp_Myforeach_Date #SatrtDate='03 Jan 2010',#EndDate='03 Mar 2010',#DatePart='dd',#OutPutFormat=106
Thanks
*Suvabrata Roy
ICRA Online Ltd.
Kolkata*

If you want to see value zero than put the following query:
select count(*), sum(OrderAmount)
from Orders
where OrderDate>getdate()-7
and sum(OrderAmount) > 0 or sum(OrderAmount) = 0
group by datepart(day,OrderDate)

Since you will want to use this date table frequently in other queries as well, I suggest you make it a permanent table and create a job to add the new year's dates once a year.

Related

Sql Automatic Result Issue

I have a SQL Server table:
CREATE TABLE tblExample
(
ID int,
Name nvarchar(256),
Date datetime,
IsAnual bit
)
This is a simplified example.
Now I scan the next 30 days from GETDATE(). If there is result, I insert information into another table:
WHERE DATEDIFF(dd, GETDATE(), Date) <= 30
Up to now there is no problem. But
WHERE IsAnual = 1
I must take into account their continuations. How can I do this?
Suppose that GETDATE() is 2013-10-22 and the column contains 2013-10-30, there is not problem. What if GETDATE() is 2014-10-28 and column contains 2013-10-30 AND IsAnual = 1?
Updated:
I found solution. I used recursive query.
CREATE TABLE tblExample
(
ID int IDENTITY(1,1) PRIMARY KEY NOT NULL,
Name nvarchar(256),
Date datetime,
IsAnual bit
)
And inserted some rows:
INSERT INTO tblExample
(Name, Date, IsAnual)
VALUES
('A', '2012-11-01', 1),
('B', '2013-11-01', 0),
('C', '2013-01-01', 1)
And final section is properly working query:
WITH TempTable AS
(
SELECT
e.ID,
e.Name,
e.Date,
e.IsAnual
FROM tblExample AS e
UNION ALL
SELECT
e.ID,
e.Name,
DATEADD(yy, 1, t.Date),
e.IsAnual
FROM tblExample AS e
INNER JOIN TempTable AS t
ON e.ID = t.ID
WHERE e.IsAnual = 1
AND DATEDIFF(yy, t.Date, DATEADD(yy, 1, GETDATE())) > 0
)
SELECT
*
FROM TempTable
WHERE DATEDIFF(dd, GETDATE(), Date) BETWEEN 0 AND 30
Results here:
14 B 01.11.2013 False
13 A 01.11.2013 True
WHERE DATEDIFF(dd, GETDATE(),
CASE
WHEN IsAnnual = 0 THEN Date
WHEN IsAnnual = 1 THEN DATEADD(year,DATEDIFF(year,Date,GETDATE()),Date)
END
) <= 30
The expression DATEADD(year,DATEDIFF(year,Date,GETDATE()),Date) will give you the date provided in the Date column but with its year set to the current year.
I think that's what you were asking for.
It should be noted, however, that the above will not be able to leverage any indexes on Date, so may not provide the absolute best performance on a large table.
(My initial attempt had the CASE expression incorrect, but it's hopefully correct now)
WHERE DATEDIFF( DAY ,DATEADD(YEAR,(1753 -DATEPART(YEAR ,GETDATE())) *IsAnual ,GETDATE()) ,DATEADD(YEAR ,(1753 -DATEPART(YEAR ,Date)) *IsAnual ,Date)) BETWEEN 0 AND 30

SQL fill days for dates not found in Database

I'm getting data from my function as follows:
Date | Number
06-02-2012 | 2
06-05-2012 | 5
06-08-2012 | 5
If i want to include all dates that are not found in DB in the following matter how would i do it?:
Date | Number
06-02-2012 | 2
06-03-2012 | 0
06-04-2012 | 0
06-05-2012 | 5
06-06-2012 | 0
06-07-2012 | 0
06-08-2012 | 5
SELECT convert(varchar, MIN(DATEADD(wk, DATEDIFF(wk, 0, person.date), 0)), 1), Count(person.ID)
FROM [dbo].[Person] person
WHERE (DATEDIFF(D, person.date, #dateFrom) <=0 AND DATEDIFF(D, person.date, #dateTo) >=0)
GROUP BY DATEPART(WK, person.date)
You would create a temporary table, or subquery, containing all the dates in your chosen range, and use a left join against your source data
I recommend that you create a table of dates -- a one column table containing dates from, say 2000-01-01 to 2050-12-31. You can then use that table on the left hand side of a LEFT JOIN query like this:
SELECT date_table.date AS [Date], COUNT(your_table.primary_key) AS [Number]
FROM date_table
LEFT JOIN your_table ON date_table.date = your_table.date
WHERE date_table.date BETWEEN '2012-01-01' AND '2012-06-30'
Index the date table wisely and you'll end up with a very efficient query.
If you need just a small interval, you can try a function like this:
DECLARE #minDate date
DECLARE #maxDate date
SET #minDate = '2012-09-01'
SELECT #maxDate = CAST( CONVERT( CHAR(8), GetDate(), 112) AS DATETIME)
DECLARE #Numbers TABLE(
Date date,
Number int)
WHILE #minDate < #maxDate
BEGIN
INSERT INTO #Numbers
SELECT #minDate, 0
WHERE NOT EXISTS( SELECT Number FROM Numbers WHERE [Date] = #minDate )
SET #minDate = DATEADD(day, 1, #minDate)
END
SELECT n.[Date], ISNULL(n.Number, 0)
FROM #Numbers n
UNION ALL
SELECT Numbers.[Date], ISNULL(Numbers.Number, 0)
FROM Numbers
ORDER BY [Date]
If you need more month and year, then I think the best way to make a prefilled permanent helper table with the dates what you need. And make only an easy join on them like it is posted in an other answer.

How to build this sql query

i want to find the records in sql
from say 25-08-2012 to 01-09-2012
and i want to group by date
here is my query
SELECT CONVERT(VARCHAR(10), date, 105) AS dt,
COUNT(id) AS cnt
FROM tablename
WHERE date BETWEEN CONVERT(DATETIME, '21-08-2012 00:00:00:000',103)
AND CONVERT(DATETIME, '01-09-2012 23:59:00:000' ,103)
GROUP BY CONVERT(VARCHAR(10), date, 105)
i am getting result as
dt cnt
01-09-2012 48
27-08-2012 1
28-08-2012 3
29-08-2012 11
30-08-2012 3
but expect it as
dt cnt
25-08-2012 0
26-08-2012 0
01-09-2012 48
27-08-2012 1
28-08-2012 3
29-08-2012 11
30-08-2012 3
How i can modify above query
i also tried with CASE but no luck
Many Thanks..
The reason you are missing these dates in result is that there's no data for them in your table, however, the following would insure you are getting all of the dates in specified range:
CREATE TABLE #tmp_dates ([date] datetime)
DECLARE #dt_start datetime, #dt_end datetime, #dt_dif int
SET #dt_start = CONVERT(DATETIME, '21-08-2012 00:00:00:000',103)
SET #dt_end = CONVERT(DATETIME, '01-09-2012 23:59:00:000' ,103)
SET #dt_dif = datediff(day,#dt_start,#dt_end)
WHILE #dt_dif >= 0 BEGIN
INSERT INTO #tmp_dates
SELECT dateadd(day,#dt_dif,#dt_start)
SET #dt_dif = #dt_dif - 1
END
SELECT CONVERT(VARCHAR(10), t2.[date], 101) AS dt, COUNT(t1.id) AS cnt
INTO #tmp_result
FROM tablename t1
RIGHT OUTER JOIN #tmp_dates t2
ON CONVERT(VARCHAR(10), t1.[date], 101) = CONVERT(VARCHAR(10), t2.[date], 101)
GROUP BY CONVERT(VARCHAR(10), t2.[date], 101)
ORDER BY CONVERT(DATETIME,CONVERT(VARCHAR(10), t2.[date], 101)) ASC /* DESC */
SELECT convert(VARCHAR(10),CONVERT(DATETIME,dt),105) as dt,cnt FROM #tmp_result
DROP TABLE #tmp_dates
DROP TABLE #tmp_result
Your query cannot be directly modified to return the data you want. To count the dates in question, there must be records in the target table actually having those dates; in your case, however, the dates are merely parameters in the query. As a result, there is no way to incorporate them into your result set.
You must create a secondary table that includes all the dates for which you want data, and then recharacterize your query as a left outer join from that date table to your target table. This will, in turn, give you the zero counts for the dates present in the "date" table, but absent from the target table.
How about using IsNull()? Let me know if this works.
SELECT CONVERT(VARCHAR(10), date, 105) AS dt,
ISNULL(COUNT(id),0) AS cnt
FROM tablename
WHERE date BETWEEN CONVERT(DATETIME, '21-08-2012 00:00:00:000',103)
AND CONVERT(DATETIME, '01-09-2012 23:59:00:000' ,103)
GROUP BY CONVERT(VARCHAR(10), date, 105)

Create list of dates, a month apart, starting from current date

I'm looking for a simple select query (not using a table) to just return a list of dates, 1 month apart. The output should looke something like this, (assuming GetDate() = '2011-07-05 11:59:000' and I wanted between NOW() and NOW()+4 months
Date
2011-07-05 11:59:000
2011-08-05 11:59:000
2011-09-05 11:59:000
2011-10-05 11:59:000
2011-11-05 11:59:000
The part that's killing me is calculating the next year, for example if i run this query in Nov, the months should be listed as 11, 12, 1, 2. Thanks!
You can use recursive CTE and need not string UNIONs together if the requirement is not fixed as below:
;with MonthlyCalendar as (
select cast(getdate() as datetime) as dt
union all
select dateadd(mm, 1, dt)
from MonthlyCalendar
)
select top 5 dt as [Date] from MonthlyCalendar
option (maxrecursion 0)
When it comes to performance and you have the need for only 4 months above UNION is far superior than recursive option.
#JNK's answer, just reworked to give you each date in a row:
SELECT GETDATE() 'Date'
UNION
SELECT DATEADD(month, 1, GETDATE()) 'Date'
UNION
SELECT DATEADD(month, 2, GETDATE()) 'Date'
UNION
SELECT DATEADD(month, 3, GETDATE()) 'Date'
UNION
SELECT DATEADD(month, 4, GETDATE()) 'Date'
Had to do something like this just this morning!
I prefer to handle these small (one off) situations by looping through the data and building the list based on the current (or target) date:
if object_id('tempdb..#dates') is not null drop table #dates
select dateadd(MINUTE, -1, CONVERT(VARCHAR(10), dateadd(DD, 1, getdate()), 111)) result into #dates
declare #current datetime
select #current = result from #dates
while not exists (select * from #dates where result = dateadd(month, 4, #current))
begin
insert into #dates
select dateadd(month, 1, max(result)) from #dates
end
select * from #dates order by result
SELECT GETDATE(),
DATEADD(month, 1, GETDATE()),
DATEADD(month, 2, GETDATE()),
DATEADD(month, 3, GETDATE()),
DATEADD(month, 4, GETDATE())
DATEADD takes care of all that year consideration logic for you, and leap years and such too.
Obviously this returns a list of columns. See Ryan's answer for the row solution!
try this :
DECLARE #intFlag INT
declare #LastLimit as int
set #LastLimit = 4
SET #intFlag = 0
WHILE (#intFlag <#LastLimit)
BEGIN
select DATEADD(month, #intFlag, GETDATE())
SET #intFlag = #intFlag + 1
END
You can use a dynamic script to build a calendar set.
A good example can be found here:
http://blog.namwarrizvi.com/?p=139
In that example you would just replaced the DATEADD and DATEDIFF to use months instead of days.
There is a generic elegant solution on the problem here: Get usernames logged in on day by day basis from database
Of course, it will require adjustments, but the principle is great.
In SQL Oracle, you can easily create a list of dates using CONNECT BY.
For example, if you want all the months between '2000-12-31' and today:
select add_months(date '2000-12-31',level) dates
from dual
connect by level <= months_between(sysdate, date '2000-12-31');
The function used to obtain the number of months, here months_between, can change between different SQL versions (e.g. in SQL Server it should be datediff()).
It's often useful to keep a table of incrementing values, as large as you need it to be:
create table sequence ( value int not null primary key clustered )
insert sequence values(0)
insert sequence values(1)
insert sequence values(2)
insert sequence values(3)
. . .
insert sequence values(n)
With such a table, producing a list of any size is trivial. This will give you 36 date/time values a month apart, starting with the current date/time.
select top 36
dtValue = dateadd( month , sequence.value , date(current_timestamp) )
from dbo.sequence
order by sequence.value

How I can select / sort dates by period intervals?

For ex:
If we have in table records like:
25/06/2009
28/12/2009
19/02/2010
16/04/2011
20/05/2012
I want to split/select this dates according to 6 month intervals starting from current date.
result should be like:
0-6 month from now: first record
7-12 month from now: second record
...
It will be much apreciated if you make this simple as I made it very stupid and complicated like:
declare variable like t1=curdate()+6
t2=curdate()+12
...
then selected records to fit between curdate() and t1, then t1 and t2 etc.
Thanks,
r.
CORRECTION: Had it backwards, Need to use Modulus, not integer division - sorry...
If MonthCount is a calculated value which counts the number of months since a specific Dec 31, and mod is modulus division (output the remainder after dividing)
Select [Column list here]
From Table
Group By Case When MonthCount Mod 12 < 6
Then 0 Else 1 End
In SQL Server, for example, you could use the DateDiff Function
Select [Column list here]
From Table
Group By Case When DateDiff(month, myDateColumn, curdate) % 12 < 6
Then 0 Else 1 End
( in SQL Server the percent sign is the modulus operator )
This will group all the record into buckets which each contain six months of data
SELECT (DATEDIFF(MONTH, thedate, GETDATE()) / 6) AS semester,
SUM(receipt)
FROM thetable
GROUP BY semester
ORDER BY semester
the key idea is grouping and ordering by the expression that gives you the "semester".
This question really baffled me, cos I couldn't actually come up with a simple solution for it. Damn.
Best I could manage was an absolute bastardization of the following where you create a Temp Table, insert the "Periods" into it, join back to your original table, and group off that.
Assume your content table has the following
ID int
Date DateTime
Counter int
And you're trying to sum all the counter's in six month periods
DECLARE #min_date datetime
select #min_date = min(date) from test
DECLARE #max_date datetime
select #max_date = max(date) from test
DECLARE #today_a datetime
DECLARE #today_b datetime
set #today_a = getdate()
set #today_b = getdate()
CREATE TABLE #temp (startdate DateTime, enddate DateTime)
WHILE #today_a > #min_date
BEGIN
INSERT INTO #temp (startDate, endDate) VALUES (dateadd(month, -6, #today_a), #today_a)
SET #today_a = dateadd(month, -6, #today_a)
END
WHILE #today_b < #max_date
BEGIN
INSERT INTO #temp (startDate, endDate) VALUES (#today_b, dateadd(month, 6, #today_b))
SET #today_b = dateadd(month, 6, #today_b)
END
SELECT * FROM #temp
SELECT
sum(counter),
'Between ' + Convert(nvarchar(10), startdate, 121) + ' => ' + Convert(nvarchar(10), enddate, 121) as Period
FROM test t
JOIN #Temp ht
ON t.Date between ht.startDate AND ht.EndDate
GROUP BY
'Between ' + Convert(nvarchar(10), startdate, 121) + ' => ' + Convert(nvarchar(10), enddate, 121)
DROP TABLE #temp
I really hope someone can come up with a better solution my brain has obviously melted.
Not quite what you're attempting to accomplish, but you could use the DATEDIFF function to distinguish the ranging of each record:
SELECT t.MonthGroup, SUM(t.Counter) AS TotalCount
FROM (
SELECT Counter, (DATEDIFF(m, GETDATE(), Date) / 6) AS MonthGroup
FROM Table
) t
GROUP BY t.MonthGroup
This would create a sub query with an expression that expresses the date ranging group you want. It would then group the sub-query by this date ranging group and you can then do whatever you want with the results.
Edit: I modified the example based on your example.
If you're using SQL Server:
SELECT *,
(
FLOOR
(
(
DATEDIFF(month, GETDATE(), date_column)
- CASE WHEN DAY(GETDATE()) > DAY(date_column) THEN 1 ELSE 0 END
) / 6.0
) * 6
) AS SixMonthlyInterval
FROM your_table
If you're using MySQL:
SELECT *,
(
FLOOR
(
(
((YEAR(date_column) - YEAR(CURDATE())) * 12)
+ MONTH(date_column) - MONTH(CURDATE())
- CASE WHEN DAY(CURDATE()) > DAY(date_column) THEN 1 ELSE 0 END
) / 6.0
) * 6
) AS SixMonthlyInterval
FROM your_table