I'm trying to write a SQL Query that has to return how many transactions were made in the last 10 minutes for each minute. Like:
1 minute ago : 2 transactions
2 minutes ago : 1 transaction
...
9 minutes ago : 3 transactions
10 minutes ago : 4 transactions
I was trying to do this query:
DECLARE #N int = 10;
DECLARE #NOW DATETIME = GETDATE();
WITH numbers( num ) AS (
SELECT 1 UNION ALL
SELECT 1 + num FROM numbers WHERE num < #N )
SELECT num AS minute,
(
SELECT COUNT(*) AS RESULTS
FROM [ApiTransactions]
WHERE [DateUtc] > DATEADD(year, -1, #NOW)
GROUP BY DATEPART(minute, DATEADD(minute, -num, #NOW))
)
FROM numbers;
I still don't know if the logic is right. What I know is that I'm receiving the error:
Each GROUP BY expression must contain at least one column that is not an outer reference.
Why I'm having this error? Is there a better way to do the query?
You don't need a numbers table for this, unless you need to fill in times with no transactions. I would start with this:
SELECT DATEADD(minute, DATEDIFF(minute, 0, DateUtc), 0) as the_minute, COUNT(*)
FROM ApiTransactions
WHERE DateUtc > DATEADD(minute, -10, DateUtc)
GROUP BY DATEADD(minute, DATEDIFF(minute, 0, DateUtc), 0)
ORDER BY the_minute;
You can use this one:
SELECT DATEDIFF(minute,[DateUtc],GETDATE()) as minutes_ago,
COUNT(*) tran_count
FROM ApiTransactions
WHERE DATEDIFF(second,[DateUtc],GETDATE()) <= 600 -- 10 minutes ago
GROUP BY DATEDIFF(minute,[DateUtc],GETDATE())
Output:
minutes_ago tran_count
0 2
1 3
2 10
3 15
4 4
5 9
6 12
7 6
8 13
9 4
10 2
I managed to solve my problem in two different ways:
DECLARE #N int = 10;
DECLARE #NOW DATETIME = GETDATE();
WITH numbers( num ) AS (
SELECT 1 UNION ALL
SELECT 1 + num FROM numbers WHERE num < #N )
SELECT num-1 AS minute,
(
SELECT COUNT(*) AS RESULTS
FROM [ApiTransactions]
WHERE [DateUtc] > DATEADD(minute, -num, #NOW) AND [DateUtc] < DATEADD(minute, -num+1, #NOW)
)
FROM numbers;
that has as output:
minutes_ago amount
0 4 (most recents. still being updated)
1 0
2 2
3 3
4 1
5 2
6 1
7 2
8 1
9 3
and with:
DECLARE #NOW DATETIME = GETDATE();
SELECT CONVERT(int, DATEDIFF(second, [DateUtc], #NOW)/60) as TimePassed, COUNT([TypeCode]) as Amount
FROM [ApiTransactions]
WHERE [DateUtc] >= DATEADD (minute, -10 , #NOW)
GROUP BY CONVERT(int, DATEDIFF(second, [DateUtc], #NOW)/60)
ORDER BY CONVERT(int, DATEDIFF(second, [DateUtc], #NOW)/60)
that has as output:
minutes_ago amount
0 4 (most recents. still being updated)
2 2
3 3
4 1
5 2
6 1
7 2
8 1
9 3
Related
There are two columns, XCHG_DATE and USD_KRW, and the table contains daily data.
What I am trying to do is to select weekly data from the daily data.
E.g) (2022-03-01, value), (2022-03-08, value), (2022-03-15, value), (2022-03-22, value) and so one...
The current SQL I have is:
SELECT CE.XCHG_DATE xchageDate
, CE.USD_KRW usdKrw
FROM(
SELECT DATEADD(WEEK, DATEDIFF(WEEK, 1, XCHG_DATE), 4) xchageDate
FROM CWL_EXCHANGE
WHERE XCHG_DATE BETWEEN '20220301' AND '20220523'
GROUP BY DATEADD(WEEK, DATEDIFF(WEEK, 1, XCHG_DATE),4)
) AS RESULT
LEFT JOIN CWL_EXCHANGE CE
ON CE.XCHG_DATE = RESULT.xchageDate
WHERE RESULT.xchageDate = CE.XCHG_DATE
ORDER BY CE.XCHG_DATE;
This query gives me weekly data from 20220304 to 20220520, but I need the data from 2022-03 to 2022-05-23(today's date).
Can anyone please help me of how to solve this problem?
Thanks in advance!
Sample Data:
COLUMNS = XCHG_DATE USD_KRW
2022-05-23 1
2022-05-22 2
2022-05-21 3
2022-05-20 4
2022-05-19 5
2022-05-18 6
2022-05-17 7
2022-05-16 8
2022-05-15 9
2022-05-14 10
2022-05-13 11
2022-05-12 12
2022-05-11 13
2022-05-10 14
2022-05-09 15
2022-05-08 16
2022-05-07 17
2022-05-06 18
Current Output :
20220506 18
20220513 11
20220520 4
Expected Output :
20220509 15
20220516 8
20220523 1
You will need a calendar table with Weekdaynumber to arrive at the earlier weekdays corresponding to Today's date(23 May 2022). This will make the calculation easier.
DECLARE #StartDate DATE = '2022-05-01'
DECLARE #EndDate DATE = '2022-05-31'
declare #table table (XCHG_DATE date, USD_KRW int);
insert into #table
values ('2022-05-23', 1 )
,('2022-05-22', 2 )
,('2022-05-21', 3 )
,('2022-05-20', 4 )
,('2022-05-19', 5 )
,('2022-05-18', 6 )
,('2022-05-17', 7 )
,('2022-05-16', 8 )
,('2022-05-15', 9 )
,('2022-05-14', 10 )
,('2022-05-13', 11 )
,('2022-05-12', 12 )
,('2022-05-11', 13 )
,('2022-05-10', 14 )
,('2022-05-09', 15 )
,('2022-05-08', 16 )
,('2022-05-07', 17 )
,('2022-05-06', 18 );
;WITH Cal(n) AS
(
SELECT 0 UNION ALL SELECT n + 1 FROM Cal
WHERE n < DATEDIFF(DAY, #StartDate, #EndDate)
),
FnlDt(d,weeknum) AS
(
SELECT DATEADD(DAY, n, #StartDate),datepart(dw, DATEADD(DAY, n, #StartDate)) as weeknum FROM Cal
)
SELECT t.XCHG_DATE,t.USD_KRW
from FnlDt as c
INNER JOIN #table as t
on t.XCHG_DATE = c.d
where c.weeknum = datepart(dw, getdate()) -- Weekdaynumber today
XCHG_DATE
USD_KRW
2022-05-23
1
2022-05-16
8
2022-05-09
15
Sub in GETDATE() for the hardcoded value if you always want todays date
SELECT *
FROM CWL_EXCHANGE
WHERE DATEPART(dw, XCHG_DATE) = DATEPART(dw, '20220523')
I'm having trouble sorting this out. I want to see the fiscal quarters for Date_Received. When the #ReviewPeriodQuarter = 1 then I want the Date_Received months 10,11,12. If #ReviewPeriodQuarter = 2 then I want the Date_Received months 1,2,3 etc. SQL Server doesn't like the BETWEEN part of this. Thanks
DECLARE #ReviewPeriodQuarter Int
SELECT * FROM Table
WHERE
MONTH(Date_Received) =
CASE
WHEN #ReviewPeriodQuarter = 1 THEN BETWEEN 10 AND 12
WHEN #ReviewPeriodQuarter = 2 THEN BETWEEN 1 AND 3
WHEN #ReviewPeriodQuarter = 3 THEN BETWEEN 4 AND 6
WHEN #ReviewPeriodQuarter = 4 THEN BETWEEN 7 AND 9
END
I would, pesonally, move the ranges to outside the SELECT entirely, and then just use a simple WHERE:
DECLARE #MonthStart int,
#MonthEnd int;
SELECT #MonthStart = CASE #ReviewPeriodQuarter WHEN 1 THEN 10,
WHEN 2 THEN 1
WHEN 3 THEN 4
WHEN 4 THEN 7
END,
#MonthEnd = CASE #ReviewPeriodQuarter WHEN 1 THEN 12,
WHEN 2 THEN 3
WHEN 3 THEN 6
WHEN 4 THEN 9
END;
SELECT *
FROM dbo.[Table]
WHERE MONTH(Date_Received) BETWEEN #MonthStart AND #MonthEnd;
Note that this still won't be SARGable though, due to the use of MONTH(Date_Received) in the WHERE. I must admit, needing rows from a table for specific months, regardless of year, is a little odd. If that is your true requirement you might be better off "investing" in a Calendar Table you can JOIN to, and then just having a WHERE on the calendar table's CalendarMonth column; which would be SARGable.
You can either do it with more parameters like Larnu or you can use your original method but tweaked
DECLARE #ReviewPeriodQuarter INT
SELECT *
FROM Table
WHERE MONTH(Date_Received) BETWEEN
CASE
WHEN #ReviewPeriodQuarter = 1 THEN 10
WHEN #ReviewPeriodQuarter = 2 THEN 1
WHEN #ReviewPeriodQuarter = 3 THEN 4
WHEN #ReviewPeriodQuarter = 4 THEN 7
END
AND
CASE
WHEN #ReviewPeriodQuarter = 1 THEN 12
WHEN #ReviewPeriodQuarter = 2 THEN 3
WHEN #ReviewPeriodQuarter = 3 THEN 6
WHEN #ReviewPeriodQuarter = 4 THEN 9
END
How about using values()?
SELECT *
FROM Table t JOIN
(VALUES (1, 10, 12),
(2, 1, 3),
(3, 4, 6),
(4, 7, 9)
) v(ReviewPeriodQuarter, lo, hi)
ON MONTH(t.Date_Received) BETWEEN v.lo AND v.hi AND
v.ReviewPeriodQuarter = #ReviewPeriodQuarter;
Or even more simply:
WHERE DATEPART(quarter, DATEADD(MONTH, -3, t.Date_Received)) = #ReviewPeriodQuarter
In other words, you don't need conditional logic to specify each quarter. You can just use date arithmetic.
I need some help working out how many people were on site for each hour.
The data looks like this
Id Roomid, NumPeople, Starttime, Closetime.
1 1 4 2018/10/03 09:06 2018/10/03 12:43
2 2 8 2018/10/03 10:16 2018/10/03 13:12
3 1 6 2018/10/03 13:02 2018/10/03 15:01
What I need out is the max count of people during the hour, each hour
Time | PeoplePresent
9 4
10 12
11 12
12 12
13 14
14 6
15 6
Getting the count of people as the arrived is simple enough, but I can’t think where to start to get the presence for each hour. Can anyone suggest a strategy for this. I ok with the simple SQL stuff but I’m certain this requires some advanced SQL functions.
Tested the following in SQL Server 2008 R2:
You can use a recursive CTE to build the list of hours, including the row id and NumPeople values. Then you can sum them together to get your final output. I put together the following test data based on the question.
CREATE TABLE #times
(
Id int
, Roomid INT
, NumPeople INT
, Starttime DATETIME
, Closetime DATETIME
)
INSERT INTO #times
(
Id
,Roomid
,NumPeople
,Starttime
,Closetime
)
VALUES
(1, 1, 4 , '2018/10/03 09:06', '2018/10/03 12:43')
,(2, 2, 8, '2018/10/03 10:16', '2018/10/03 13:12')
,(3, 1, 6, '2018/10/03 13:02', '2018/10/03 15:01')
;WITH recursive_CTE (id, startHour, currentHour, diff, NumPeople) AS
(
SELECT
Id
,startHour = DATEPART(HOUR, t.Starttime)
,currentHour = DATEPART(HOUR, t.Starttime)
,diff = DATEDIFF(HOUR, Starttime, Closetime)
,t.NumPeople
FROM #times t
UNION ALL
SELECT
r.id
,r.startHour
,r.currentHour + 1
,r.diff
,r.NumPeople
FROM recursive_CTE r
WHERE r.currentHour < startHour + diff
)
SELECT
Time = currentHour
,PeoplePresent = SUM(NumPeople)
FROM recursive_CTE
GROUP BY currentHour
DROP TABLE #times
Query results:
Time PeoplePresent
9 4
10 12
11 12
12 12
13 14
14 6
15 6
We are running reports for a seasonal business, with expected lulls during the summer months. For some metrics, we'd essentially like to pretend that those months don't even exist.
Thus consider the default behavior of:
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-06-01') -- answer = 1
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-07-01') -- 2
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-08-01') -- 3
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-09-01') -- 4
We want to ignore June and July, so we would like those answers to look like this:
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-06-01') -- answer = 1
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-07-01') -- 1
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-08-01') -- 1
SELECT DATEDIFF(MONTH, '2015-05-01', '2015-09-01') -- 2
What is the easiest way to accomplish this? I'd like a pure SQL solution, rather than something using TSQL, but writing a custom function such as NOSUMMER_DATEDIFF could also work.
Also, keep in mind the reports will span multiple years, so the solution should be able to handle that.
If you are only interested month differences, then I would suggest a trick here. Count the number of months since some date 0, but ignore the summer months. For example:
'2015-05-01' --> 2015 * 10 + 5 = 20155
'2015-06-01' --> 2015 * 10 + 6 = 20156
'2015-07-01' --> 2015 * 10 + 6 = 20156
'2015-08-01' --> 2015 * 10 + 6 = 20156
'2015-09-01' --> 2015 * 10 + 7 = 20157
This is a fairly easy calculation:
select (case when month(date2) <= 6 then year(date2) * 10 + month(date2)
when month(date2) in (7, 8) then year(date2) * 10 + 6
else year(date2) * 10 + (month(date2) - 2)
end)
For the difference:
select ((case when month(date2) <= 6 then year(date2) * 10 + month(date2)
when month(date2) in (7, 8) then year(date2) * 10 + 6
else year(date2) * 10 + (month(date2) - 2)
end) -
(case when month(date1) <= 6 then year(date1) * 10 + month(date1)
when month(date1) in (7, 8) then year(date1) * 10 + 6
else year(date1) * 10 + (month(date1) - 2)
end)
)
To able to achieve that, you have to "split" dates ranges to an "array" of dates for every single range of dates. CTE might be helpful in this case.
See:
--your table which holds dates ranges
DECLARE #dates TABLE(id INT IDENTITY(1,1), dFrom DATE, dTo DATE)
INSERT INTO #dates (dFrom, dTo)
VALUES('2015-05-01', '2015-06-01'),
('2015-05-01', '2015-07-01'),
('2015-05-01', '2015-08-01'),
('2015-05-01', '2015-09-01')
--summer month table
DECLARE #summermonths TABLE(summMonth INT)
INSERT INTO #summermonths(summMonth)
VALUES(6), (7)
--here Common Table Expressions is in action to "split" dates ranges to an array of dates for every single date range
;WITH CTE AS
(
SELECT id, DATEADD(MM, 0, dFrom) AS ndFrom, dTo, CASE WHEN MONTH(DATEADD(MM, 0, dFrom)) = 6 OR MONTH(DATEADD(MM, 0, dFrom)) = 7 THEN 0 ELSE 1 END AS COfMonth
FROM #dates
WHERE DATEADD(MM, 1, dFrom)<=dTo
UNION ALL
SELECT id, DATEADD(MM, 1, ndFrom) AS ndFrom, dTo, CASE WHEN MONTH(DATEADD(MM, 1, ndFrom)) = 6 OR MONTH(DATEADD(MM, 1, ndFrom)) = 7 THEN 0 ELSE 1 END AS COfMonth
FROM CTE
WHERE DATEADD(MM, 1, ndFrom)<=dTo
)
SELECT t1.id, t2.dFrom, t2.dTo, SUM(t1.COfMonth) AS MyDateDiff
FROM CTE AS t1 INNER JOIN #dates AS t2 ON t1.id = t2.id
GROUP BY t1.id, t2.dFrom , t2.dTo
Result:
id dFrom dTo MyDateDiff
1 2015-05-01 2015-06-01 1
2 2015-05-01 2015-07-01 1
3 2015-05-01 2015-08-01 2
4 2015-05-01 2015-09-01 3 --not 2, because of 5, 8, 9
Got it?
Note: a solution might be differ in case of dFrom and dTo is not the first date of month.
Here is my query:
DECLARE #MM INT -- Current month
DECLARE #DD INT -- Current date
SET #MM = 1 -- For testing, set it to January
SET #DD = 1 -- For testing, set it to 01
SELECT xxxID, xxxFK, StartMonth, StartDate, StopMonth, StopDate, NULL AS OKorNOT
FROM xxxTable
ORDER BY xxxFK
And here is the data:
xxxID xxxFK StartMonth StartDate StopMonth StopDate OKorNOT
---------------- ----------- ----------- ----------- ----------- ----------- -----------
8 2287 11 15 1 2 NULL
4 2290 2 1 2 21 NULL
2 2306 9 15 10 31 NULL
3 2306 1 3 1 20 NULL
9 2661 11 15 1 3 NULL
10 2661 5 5 5 31 NULL
5 3778 6 2 9 5 NULL
6 3778 1 1 3 31 NULL
7 3778 5 10 5 31 NULL
1 3778 12 10 12 31 NULL
I need to populate OKorNot column with 1/0 depending on whether the given month-date lies between StartMonth-StartDate and StopMonth-StopDate. This is SQL Server 2000 by the way.
EDIT
The thing here to note that is that there are no years stored in the data and the months-dates may start in, say Nov-15 and end in Jan-15 so on Dec-31 and Jan-1 this case should return true.
Using integer operations only and an imaginary 384-days calendar
Since your dates are combinations of month and day, I tried to create an integer for every such combination, an integer that is unique and also preserves order. To have calculations as simple as possible, we invent a new calendar where all months have exactly 32 days and we act as if our dates are from this calendar. Then to get how many days have past since 1st of January, we have the formula:
DaysPast = 32 * month + day
(OK, it should be 32 * (month-1) + (day-1) but this way it's simpler and we only want to compare dates relatively to one another, not to January 1st. And the result is still unique for every date).
Therefore, we first calculate the DaysPast for our check date:
SET #CHECK = 32 * #MM + #DD
Then, we calculate the DaysPast for all dates (both start and stop ones) in our table:
( SELECT *
, (32 * StartMonth + StartDate) AS Start
, (32 * StopMonth + StopDate ) AS Stop
FROM xxxTable
) AS temp
Then, we have two cases.
First case, when Start = (8-Feb) and Stop = (23-Nov).
Then, the first condition #CHECK BETWEEN Start AND Stop will be true and the dates between Start and Stop will be OK.
The second condition will be False, so no more dates will be OK.
Second case, when Start = (23-Nov) and Stop = (8-Feb). :
Then, the first condition #CHECK BETWEEN Start AND Stop will be false because Start is bigger than Stop so no dates can match this condition.
The second condition Stop < Start will be true, so we also test if
#CHECK is NOT BETWEEN (9-Feb) AND (22-Nov)
to match the dates that are before (9-Feb) or after (22-Nov).
DECLARE #CHECK INT
SET #CHECK = 32 * #MM + #DD
SELECT *
, CASE WHEN
#CHECK BETWEEN Start AND Stop
OR ( Stop < Start
AND #CHECK NOT BETWEEN Stop+1 AND Start-1
)
THEN 1
ELSE 0
END
AS OKorNOT
FROM
( SELECT *
, (32 * StartMonth + StartDate) AS Start
, (32 * StopMonth + StopDate ) AS Stop
FROM xxxTable
) AS temp
ORDER BY xxxFK
It would be easier if you'd stored dates as, well, dates...
Anyway, something like this. I haven't tested. And you need to deal with year boundary which I've done
SELECT
xxxID, xxxFK, StartMonth, StartDate, StopMonth, StopDate,
CASE
WHEN
FullStart <= FullStop AND
DATEADD(month, #MM-1, DATEADD(day, #DD-1, 0)) BETWEEN FullStart AND FullStop
THEN 1
WHEN
FullStart > FullStop AND
DATEADD(month, #MM-1, DATEADD(day, #DD-1, 0)) BETWEEN
FullStart AND DATEADD(year, 1, FullStop)
THEN 1
ELSE 0
END AS OKOrNot
FROM
(
SELECT
xxxID, xxxFK, StartMonth, StartDate, StopMonth, StopDate,
DATEADD(month, StartMonth-1, DATEADD(day, StartDate-1, 0)) AS FullStart,
DATEADD(month, StopMonth-1, DATEADD(day, StopDate-1, 0)) AS FullStop
FROM xxxTable
) foo
ORDER BY xxxFK
edit: added "-1" to all values: if we're already Jan don't add another month...
SELECT *
FROM xxxTable
WHERE (StartMonth < StopMonth OR StartMonth = StopMonth AND StartDate<=StopDate)
AND (#MM > StartMonth OR #MM = StartMonth AND #DD >= StartDate)
AND (#MM < StopMonth OR #MM = StopMonth AND #DD <= StopDate)
OR (StartMonth > StopMonth OR StartMonth = StopMonth AND StartDate>StopDate)
AND ((#MM > StartMonth OR #MM = StartMonth AND #DD >= StartDate)
OR (#MM < StopMonth OR #MM = StopMonth AND #DD <= StopDate))