Related
It is a bit difficult to explain but basically I need to create a temporary table (date datetime, #customers int) where #customers is the number of weekly customers pulled from another table. Here's my code.
declare #date datetime
declare #temptable table (date datetime not null,#customers int)
set #date='2018-02-13'
while #date<getdate()
begin
insert into #temptable values
(#date,
(select count(*) from in_ft_conversion
where u4='cfa' and sales_date between #date and #date-7))
set #date=#date+7
end
The result is a table with all the correct date entries but 0 in the customer column... Does anybody know what I'm doing wrong? Thanks!
Your date range is wrong , swap the date values in the BETWEEN so you have BETWEEN <earlier date> AND <later date>
where u4='cfa' and sales_date between #date-7 and #date))
Why would you use a while loop for this? I think you want something like this:
insert into #temptable (date, num_customers)
select dateadd(day, '2018-02-08', weekno * 7)
count(*)
from in_ft_conversion cross apply
(values (datediff(day, '2018-02-08', sales_date) / 7
) v(weekno)
where u4 = 'cfa' and sales_date >= '2018-02-08'
group by v.weekno;
No loop is necessary.
Your problem is specifically the between comparison:
sales_date between #date and #date-7
The dates are backwards -- the lower bound needs to go first.
But, I also doubt that you want to count weeks with 8 days and have one day overlap on each week. I think the above logic does what you want, but you can adjust the date arithmetic to get the exact dates you want.
I have data of the following format:
Date Value
08/28 100
09/01 1
09/01 5
09/10 2
I would like my output to be:
Date Value
08/28 100
08/29 100
08/30 100
08/31 100
09/01 106
09/02 106
.
.
.
09/10 108
I'm just getting started with SQL, so any help would be appreciated. What I have right now is below, but that's not really close to what I seek:
SELECT Date, COUNT(DISTINCT(Service)) AS Value
FROM [Directory]
WHERE Date <= #myDate
GROUP BY Date ORDER BY Date
First, you can use a sub query to get the aggregate values
SELECT Date, (SELECT SUM(Value) FROM Directory d WHERE d.Date <= Directory.Date)
FROM [Directory]
WHERE Date <= #myDate
ORDER BY Date
Which would give you something that looks like this:
Date Value
08/28 100
09/01 101
09/01 106
09/10 108
Then you can add a Date table as sgeddes suggested. This article explains if fairly well: http://michaelmorley.name/how-to/create-date-dimension-table-in-sql-server
Then you can modify your query like so
SELECT DateTable.Date, (SELECT SUM(Value) FROM Directory d WHERE d.Date <= Directory.Date)
FROM [Directory] LEFT OUTER JOIN DateTable on Directory.Date = DateTable.Date
WHERE DateTable.Date <= #myDate
ORDER BY DateTable.Date
To get the data format you're looking for.
Based on sgeddes suggestion:
SELECT a.Date, COUNT(DISTINCT(d.Service)) AS Value
FROM [Directory] d
LEFT OUTER JOIN [Date Table] a on d.Date = a.Date
WHERE Date <= #myDate
GROUP BY Date
ORDER BY Date
Use following script in sqlserver :
BEGIN
--If exist then drop temp tables
DROP TABLE #YOURTABLE;
DROP TABLE #TEST1;
DECLARE #MINDATE DATETIME;
DECLARE #MAXDATE DATETIME;
CREATE TABLE #YOURTABLE(
CDATE DATE,
VALUE INT
);
INSERT INTO #YOURTABLE VALUES ('08/28/2014',100),('09/01/2014',1),('09/01/2014',5),('09/10/2014',100);
--select start date and end date from your table
SELECT #MINDATE=MIN(CDATE),#MAXDATE=MAX(CDATE) FROM #YOURTABLE;
CREATE TABLE #TEST1(
CDATE DATE,
VALUE INT
);
;WITH CALENDAR
AS (
SELECT #MINDATE CDATE
UNION ALL
SELECT CDATE + 1
FROM CALENDAR
WHERE CDATE + 1 <= #MAXDATE
)
-- insert all dates with 0 value in temp table
INSERT INTO #TEST1 SELECT CDATE,0 FROM CALENDAR;
--delete dates which are already there in your table
DELETE FROM #TEST1 WHERE CDATE IN (SELECT CDATE FROM #YOURTABLE)
-- insert all dates with values from your table to temporary table which holds dates which are not in your table
INSERT INTO #TEST1 SELECT * FROM #YOURTABLE;
SELECT T1.CDATE,(SELECT SUM(VALUE) FROM #TEST1 T2 WHERE T2.CDATE<=T1.CDATE) FROM #TEST1 T1
END
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.
We're looking to populate a number of tables for testing. The data can all be the same except for a timestamp column. Other columns in the row contain complicated data, like objects in xml format and other messy stuff, so we don't want to have to remake it. What would be an easy way to expand a single entry to thousands, by incrementing a timestamp by a constant interval, through a sql query.
Our current idea (in a C-ish pseudocode) is to:
Get the latest (likely only) row and store it in a variable "thisRow"
While(thisRow->time < endTime)
{
Increment thisRow->time by a constant variable
Insert thisRow
}
We are using postgres
I like to keep a Numbers table in my databases for certain queries. If you had one then you could use the single statement below. If you don't have one, it's easy enough to generate one as a temporary table or permanent table.
INSERT INTO Test_Table
(
col1,
col2,
...,
my_timestamp
)
SELECT
ST.col1,
ST.col2,
...,
DATEADD(mi, N.number, ST.my_timestamp)
FROM
Source_Table ST
INNER JOIN Numbers N ON
N.number BETWEEN 1 AND 1000 -- Change this to what you want
This is for SQL Server. The DATEADD function may be different in your RDBMS. You can also add in a random function if you don't want the intervals to be exactly one minute and you could of course change the intervals to be hours, days, or whatever. You could also use a simple equation if you wanted say two hours per interval.
Using SQL SERVER 2005+ you can make use of CTE statements
Have a look at this
DECLARE #StartTime DATETIME,
#EndTime DATETIME
SELECT #StartTime = '01 Jan 2009',
#EndTime = '31 Jan 2009'
DECLARE #Table TABLE(
DateVal DATETIME,
Col1 INT
)
INSERT INTO #Table SELECT #StartTime, 1
SELECT *
FROM #Table
;WITH CTE AS (
SELECT #StartTime StartTime
UNION ALL
SELECT DATEADD(dd, 1, StartTime) StartTime
FROM CTE
WHERE StartTime < #EndTime
)
SELECT CTE.*,
t.Col1
FROM CTE,
#Table t
If you are using a database that supports recursion, for example Microsoft SQL Server, you can use something like:
WITH T AS (
SELECT 1 AS r
UNION ALL
SELECT r + 1 AS r
FROM T
WHERE r < 100 -- Or however many rows you need
)
INSERT INTO TestTable (Col1, Col2,...)
SELECT s.Col1, s.Col2,...
FROM SourceTable s
CROSS JOIN T
I have a set of transactions occurring at specific points in time:
CREATE TABLE Transactions (
TransactionDate Date NOT NULL,
TransactionValue Integer NOT NULL
)
The data might be:
INSERT INTO Transactions (TransactionDate, TransactionValue)
VALUES ('1/1/2009', 1)
INSERT INTO Transactions (TransactionDate, TransactionValue)
VALUES ('3/1/2009', 2)
INSERT INTO Transactions (TransactionDate, TransactionValue)
VALUES ('6/1/2009', 3)
Assuming that the TransactionValue sets some kind of level, I need to know what the level was between the transactions. I need this in the context of a set of T-SQL queries, so it would be best if I could get a result set like this:
Month Value
1/2009 1
2/2009 1
3/2009 2
4/2009 2
5/2009 2
6/2009 3
Note how, for each month, we either get the value specified in the transaction, or we get the most recent non-null value.
My problem is that I have little idea how to do this! I'm only an "intermediate" level SQL Developer, and I don't remember ever seeing anything like this before. Naturally, I could create the data I want in a program, or using cursors, but I'd like to know if there's a better, set-oriented way to do this.
I'm using SQL Server 2008, so if any of the new features will help, I'd like to hear about it.
P.S. If anyone can think of a better way to state this question, or even a better subject line, I'd greatly appreciate it. It took me quite a while to decide that "spread", while lame, was the best I could come up with. "Smear" sounded worse.
I'd start by building a Numbers table holding sequential integers from 1 to a million or so. They come in really handy once you get the hang of it.
For example, here is how to get the 1st of every month in 2008:
select firstOfMonth = dateadd( month, n - 1, '1/1/2008')
from Numbers
where n <= 12;
Now, you can put that together using OUTER APPLY to find the most recent transaction for each date like so:
with Dates as (
select firstOfMonth = dateadd( month, n - 1, '1/1/2008')
from Numbers
where n <= 12
)
select d.firstOfMonth, t.TransactionValue
from Dates d
outer apply (
select top 1 TransactionValue
from Transactions
where TransactionDate <= d.firstOfMonth
order by TransactionDate desc
) t;
This should give you what you're looking for, but you might have to Google around a little to find the best way to create the Numbers table.
here's what i came up with
declare #Transactions table (TransactionDate datetime, TransactionValue int)
declare #MinDate datetime
declare #MaxDate datetime
declare #iDate datetime
declare #Month int
declare #count int
declare #i int
declare #PrevLvl int
insert into #Transactions (TransactionDate, TransactionValue)
select '1/1/09',1
insert into #Transactions (TransactionDate, TransactionValue)
select '3/1/09',2
insert into #Transactions (TransactionDate, TransactionValue)
select '5/1/09',3
select #MinDate = min(TransactionDate) from #Transactions
select #MaxDate = max(TransactionDate) from #Transactions
set #count=datediff(mm,#MinDate,#MaxDate)
set #i=1
set #iDate=#MinDate
while (#i<=#count)
begin
set #iDate=dateadd(mm,1,#iDate)
if (select count(*) from #Transactions where TransactionDate=#iDate) < 1
begin
select #PrevLvl = TransactionValue from #Transactions where TransactionDate=dateadd(mm,-1,#iDate)
insert into #Transactions (TransactionDate, TransactionValue)
select #iDate, #prevLvl
end
set #i=#i+1
end
select *
from #Transactions
order by TransactionDate
To do it in a set-based way, you need sets for all of your data or information. In this case there's the overlooked data of "What months are there?" It's very useful to have a "Calendar" table as well as a "Number" table in databases as utility tables.
Here's a solution using one of these methods. The first bit of code sets up your calendar table. You can fill it using a cursor or manually or whatever and you can limit it to whatever date range is needed for your business (back to 1900-01-01 or just back to 1970-01-01 and as far into the future as you want). You can also add any other columns that are useful for your business.
CREATE TABLE dbo.Calendar
(
date DATETIME NOT NULL,
is_holiday BIT NOT NULL,
CONSTRAINT PK_Calendar PRIMARY KEY CLUSTERED (date)
)
INSERT INTO dbo.Calendar (date, is_holiday) VALUES ('2009-01-01', 1) -- New Year
INSERT INTO dbo.Calendar (date, is_holiday) VALUES ('2009-01-02', 1)
...
Now, using this table your question becomes trivial:
SELECT
CAST(MONTH(date) AS VARCHAR) + '/' + CAST(YEAR(date) AS VARCHAR) AS [Month],
T1.TransactionValue AS [Value]
FROM
dbo.Calendar C
LEFT OUTER JOIN dbo.Transactions T1 ON
T1.TransactionDate <= C.date
LEFT OUTER JOIN dbo.Transactions T2 ON
T2.TransactionDate > T1.TransactionDate AND
T2.TransactionDate <= C.date
WHERE
DAY(C.date) = 1 AND
T2.TransactionDate IS NULL AND
C.date BETWEEN '2009-01-01' AND '2009-12-31' -- You can use whatever range you want
John Gibb posted a fine answer, already accepted, but I wanted to expand on it a bit to:
eliminate the one year limitation,
expose the date range in a more
explicit manner, and
eliminate the need for a separate
numbers table.
This slight variation uses a recursive common table expression to establish the set of Dates representing the first of each month on or after from and to dates defined in DateRange. Note the use of the MAXRECURSION option to prevent a stack overflow (!); adjust as necessary to accommodate the maximum number of months expected. Also, consider adding alternative Dates assembly logic to support weeks, quarters, even day-to-day.
with
DateRange(FromDate, ToDate) as (
select
Cast('11/1/2008' as DateTime),
Cast('2/15/2010' as DateTime)
),
Dates(Date) as (
select
Case Day(FromDate)
When 1 Then FromDate
Else DateAdd(month, 1, DateAdd(month, ((Year(FromDate)-1900)*12)+Month(FromDate)-1, 0))
End
from DateRange
union all
select DateAdd(month, 1, Date)
from Dates
where Date < (select ToDate from DateRange)
)
select
d.Date, t.TransactionValue
from Dates d
outer apply (
select top 1 TransactionValue
from Transactions
where TransactionDate <= d.Date
order by TransactionDate desc
) t
option (maxrecursion 120);
If you do this type of analysis often, you might be interested in this SQL Server function I put together for exactly this purpose:
if exists (select * from dbo.sysobjects where name = 'fn_daterange') drop function fn_daterange;
go
create function fn_daterange
(
#MinDate as datetime,
#MaxDate as datetime,
#intval as datetime
)
returns table
--**************************************************************************
-- Procedure: fn_daterange()
-- Author: Ron Savage
-- Date: 12/16/2008
--
-- Description:
-- This function takes a starting and ending date and an interval, then
-- returns a table of all the dates in that range at the specified interval.
--
-- Change History:
-- Date Init. Description
-- 12/16/2008 RS Created.
-- **************************************************************************
as
return
WITH times (startdate, enddate, intervl) AS
(
SELECT #MinDate as startdate, #MinDate + #intval - .0000001 as enddate, #intval as intervl
UNION ALL
SELECT startdate + intervl as startdate, enddate + intervl as enddate, intervl as intervl
FROM times
WHERE startdate + intervl <= #MaxDate
)
select startdate, enddate from times;
go
it was an answer to this question, which also has some sample output from it.
I don't have access to BOL from my phone so this is a rough guide...
First, you need to generate the missing rows for the months you have no data. You can either use a OUTER join to a fixed table or temp table with the timespan you want or from a programmatically created dataset (stored proc or suchlike)
Second, you should look at the new SQL 2008 'analytic' functions, like MAX(value) OVER ( partition clause ) to get the previous value.
(I KNOW Oracle can do this 'cause I needed it to calculate compounded interest calcs between transaction dates - same problem really)
Hope this points you in the right direction...
(Avoid throwing it into a temp table and cursoring over it. Too crude!!!)
-----Alternative way------
select
d.firstOfMonth,
MONTH(d.firstOfMonth) as Mon,
YEAR(d.firstOfMonth) as Yr,
t.TransactionValue
from (
select
dateadd( month, inMonths - 1, '1/1/2009') as firstOfMonth
from (
values (1), (2), (3), (4), (5), (7), (8), (9), (10), (11), (12)
) Dates(inMonths)
) d
outer apply (
select top 1 TransactionValue
from Transactions
where TransactionDate <= d.firstOfMonth
order by TransactionDate desc
) t