Sql Automatic Result Issue - sql

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

Related

Is there a way to have SQL SELECT a column with a different date?

I'm trying to pull a pretty simple report that reviews the last day's data. It looks similar to this:
SELECT Name, Date, Count
FROM dbo.X
WHERE Date BETWEEN DATEADD(DD, -1, GETDATE()) AND DATEADD(DD, 0, GETDATE())
This works fine, but I've been asked to have a column present that shows day before count as well for comparison. I'm not really sure how to do this since the datetime condition is already specifying the last day. Is there a way to make this work so the select list would look like this for example?
SELECT Name, Date, YesterdaysCount, Count
Edit: Sample Data:
Name Date Count
a 6/22/2020 1
b 6/22/2020 2
c 6/22/2020 3
d 6/22/2020 4
e 6/22/2020 5
a 6/21/2020 2
b 6/21/2020 4
c 6/21/2020 6
d 6/21/2020 8
e 6/21/2020 10
Desired Results:
Name Date YesterdayCount Count
a 6/22/2020 2 1
b 6/22/2020 4 2
c 6/22/2020 6 3
d 6/22/2020 8 4
e 6/22/2020 10 5
This should work if you don't have a time component. If you do, just change the "Y.[Date] =" to a BETWEEN as with the first part.
SELECT [Name], [Date], [Count],
(SELECT SUM(Y.[Count]) FROM dbo.X AS Y WHERE Y.[Name] = X.[Name] AND Y.[Date] = DATEADD(DAY, -1, X.[DATE]) AS YesterdaysCount
FROM dbo.X
WHERE [Date] BETWEEN DATEADD(DD,-1,GETDATE()) AND DATEADD(DD, 0, GETDATE())
There are many ways to do this, these are two ways.
I would suggest use variables for each day
if you only care about the day, i would recommend using date instead of datetime that way searching is faster (both variables, and casting your date field)
declare #table1 table (name varchar(10), YourDate datetime, YourCount int )
insert into #table1 values ('name1', '2020-06-19', 1)
--insert into #table1 values ('name1', '2020-06-20', 1)
insert into #table1 values ('name1', '2020-06-21', 2)
insert into #table1 values ('name1', '2020-06-22', 3)
insert into #table1 values ('name1', '2020-06-23', 4)
insert into #table1 values ('name1', '2020-06-24', 5)
--use date fiels for faster comparisson
declare #Today date=getdate(), #Yesterday date=dateadd(day,-1,getdate())
--using a left join to the same table for yesterdays (check for nulls)
SELECT t1.Name, t1.YourDate,
sum(isnull(t2.YourCount,0)) as YesterdayCount,
sum(isnull(t1.YourCount,0)) as TodayCount
From #table1 t1
Left Join #table1 t2 on t1.name=t2.name and t2.YourDate=cast(dateadd(day,-1,t1.YourDate) as date)
Where cast(t1.YourDate as date) BETWEEN #Yesterday AND #Today
Group By t1.name, t1.YourDate
--using outer apply
SELECT t1.Name, t1.YourDate,
sum(isnull(t2.YourCount,0)) as YesterdayCount,
sum(isnull(t1.YourCount,0)) as TodayCount
From #table1 t1
outer apply(
Select t3.YourCount From #table1 t3 where t3.YourDate=cast(dateadd(day,-1,t1.YourDate) as date)
) as t2
Where cast(t1.YourDate as date) BETWEEN #Yesterday AND #Today
Group By t1.name, t1.YourDate
SELECT
Name, Date,
(SELECT Count
WHERE Date BETWEEN DATEADD(DD, -2, GETDATE()) AND DATEADD(DD, -1, GETDATE())) AS YesterdaysCount, Count
FROM
dbo.X
WHERE
Date BETWEEN DATEADD(DD, -1, GETDATE()) AND DATEADD(DD, 0, GETDATE())

Coldfusion query output order totals by day [duplicate]

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.

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.

UPDATE field based on another row filed value

I have a table stored on a SQL Server 2008, that associate a value to a date range.
DateFrom DateTo Value
2012-01-01 2012-02-01 10
2012-02-02 2012-02-15 15
The application that deal with this table, can insert a new range between the existings.
For example, If i insert
DateFrom DateTo Value
2012-02-07 2012-02-10 12
The result must be
DateFrom DateTo Value
2012-01-01 2012-02-01 10
2012-02-02 2012-02-06 15
2012-02-07 2012-02-10 12
2012-02-11 2012-02-15 15
I can do that programmatically from the application, but I wonder if there is some fast SQL statement that make me able to set the data values by referencing other row's field and performing data operation on it.
A MUST requirement is that the date range must represent a time sequence, two range cannot span each other.
I've written an example based on the example I gave you in a comment, it may do what you want. Since, in general terms, there might be multiple rows to insert/delete, it's best to define them all separately, then use a MERGE to perform the overall change.
I've also assumed that it's okay to delete/insert to achieve the splitting - you can't update and produce 2 rows from 1, so you'd always have to do an insert, and the symmetry is cleaner if I do both:
declare #T table (DateFrom datetime2, DateTo datetime2,Value int)
insert into #T(DateFrom , DateTo , Value) VALUES
('20120101', '20120201', 10),
('20120202', '20120206', 15),
('20120207', '20120210', 12),
('20120211', '20120215', 15)
select * from #t order by DateFrom
declare #NewFrom datetime2 = '20120205'
declare #NewTo datetime2 = '20120208'
declare #NewValue int = 8
--We need to identify a) rows to delete, b) new sliced rows to create, and c) the new row itself
;With AlteredRows as (
select #NewFrom as DateFrom,#NewTo as DateTo,#NewValue as Value,1 as toInsert
union all
select DateFrom,DATEADD(day,-1,#NewFrom),Value,1 from #t where #NewFrom between DATEADD(day,1,DateFrom) and DateTo
union all
select DATEADD(day,1,#NewTo),DateTo,Value,1 from #t where #NewTo between DateFrom and DATEADD(day,-1,DateTo)
union all
select DateFrom,DateTo,0,0 from #t where DateTo > #NewFrom and DateFrom < #NewTo
)
merge into #t t using AlteredRows ar on t.DateFrom = ar.DateFrom and t.DateTo = ar.DateTo
when matched and toInsert=0 then delete
when not matched then insert (DateFrom,DateTo,Value) values (ar.DateFrom,ar.DateTo,ar.Value);
select * from #t order by DateFrom
It may be possible to re-write the CTE so that it's a single scan of #t - but I only think it's worth doing that if performance is critical.
I've had similar problems in the past, and found that if the range needs to be continuous the best approach is to do away with the End Date of the range, and calculate this as the Next start date. Then if needs be create a view as follows:
SELECT FromDate,
( SELECT DATEADD(DAY, -1, MIN(DateFrom))
FROM YourTable b
WHERE b.FromDate > a.FromDate
) [ToDate],
Value
FROM YourTable a
This ensures that 2 ranges can never cross, however does not necessarily ensure no work is required upon insert to get the desired result, but it should be more maintainable and have less scope for error than storing both the start and end date.
ADDENDUM
Once I had written out all of the below I realised it does not improve maintainability that much to do away with the DateTo Field, it still requires a fair amount of code for the validation, but here's how I would do it anyway.
DECLARE #T table (DateFrom DATE, Value INT)
INSERT INTO #T VALUES ('20120101', 10), ('20120202', 15), ('20120207', 12), ('20120211', 15)
DECLARE #NewFrom DATE = '20120209',
#NewTo DATE = '20120210',
#NewValue INT = 8
-- SHOW INITIAL VALUES FOR DEMONSTATIVE PURPOSES --
SELECT DateFrom,
ISNULL(( SELECT DATEADD(DAY, -1, MIN(DateFrom))
FROM #t b
WHERE b.DateFrom > a.DateFrom
), CAST(GETDATE() AS DATE)) [DateTo],
Value
FROM #t a
ORDER BY DateFrom
;WITH CTE AS
( SELECT DateFrom,
( SELECT DATEADD(DAY, -1, MIN(DateFrom))
FROM #t b
WHERE b.DateFrom > a.DateFrom
) [DateTo],
Value
FROM #t a
),
MergeCTE AS
( SELECT #NewFrom [DateFrom], #NewValue [Value], 'INSERT' [RowAction]
WHERE #NewFrom < #NewTo -- ENSURE A VALID RANGE IS ENTERED
UNION ALL
-- INSERT A ROW WHERE THE NEW DATE TO SLICES AN EXISTING PERIOD
SELECT DATEADD(DAY, 1, #NewTo), Value, 'INSERT'
FROM CTE
WHERE #NewTo BETWEEN DateFrom AND DateTo
UNION ALL
-- DELETE ALL ENTRIES STARTING WITHIN THE DEFINED PERIOD
SELECT DateFrom, Value, 'DELETE'
FROM CTE
WHERE DateFrom BETWEEN #NewFrom AND #NewTo
)
MERGE INTO #t t USING MergeCTE c ON t.DateFrom = c.DateFrom AND t.Value = c.Value
WHEN MATCHED AND RowAction = 'DELETE' THEN DELETE
WHEN NOT MATCHED THEN INSERT VALUES (c.DateFrom, c.Value);
SELECT DateFrom,
ISNULL(( SELECT DATEADD(DAY, -1, MIN(DateFrom))
FROM #t b
WHERE b.DateFrom > a.DateFrom
), CAST(GETDATE() AS DATE)) [DateTo],
Value
FROM #t a
ORDER BY DateFrom
You can use a cursor to get each row from the table at a time and aftwerwards do the necessary calculations.
If NewDateFrom >= RowDateFrom and NewDateFrom <= RowDateTo ...
Check this article to see how to make a cursor.

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