How to get one output table from select in while-loop - sql

Edited to live up to the rules here. Sorry about my first attempt.
I got the following sample data:
CREATE TABLE SampleData(
[Time] [time](7) NOT NULL
) ON [PRIMARY]
GO
INSERT INTO SampleData([Time]) VALUES ('01:00:00')
INSERT INTO SampleData([Time]) VALUES ('02:00:00')
INSERT INTO SampleData([Time]) VALUES ('02:00:00')
INSERT INTO SampleData([Time]) VALUES ('03:00:00')
INSERT INTO SampleData([Time]) VALUES ('03:00:00')
INSERT INTO SampleData([Time]) VALUES ('03:00:00')
INSERT INTO SampleData([Time]) VALUES ('04:00:00')
INSERT INTO SampleData([Time]) VALUES ('04:00:00')
INSERT INTO SampleData([Time]) VALUES ('04:00:00')
INSERT INTO SampleData([Time]) VALUES ('04:00:00')
GO
This is my query:
DECLARE #Counter INT
SET #Counter = 1
WHILE (#Counter <= 4)
BEGIN
SELECT Count([Time]) AS OrdersAmount
FROM SampleData
WHERE DATEPART(HOUR, [Time]) = #Counter
SET #Counter = #Counter + 1
END
This is the result of the query:
OrdersAmount
1
-----
OrdersAmount
2
-----
OrdersAmount
3
-----
OrdersAmount
4
So 4 seperate tables. What I need is one table, with alle values in it, on each their own row, like this:
OrdersAmount
1
2
3
4
I tried with cte and declaring a temp table, but I just can't make it work.

I don't have the data so can't test.
But if I get your problem right, this should work for you.
select PromisedPickupDt = cast(PromisedPickupDate as date),
[Hour] = datepart(hour, PromisedPickupDate),
HourlyAmount = sum(OrdersAmount)
from [FLX].[dbo].[NDA_SAP_Bestillinger]
where cast(PromisedPickupDate as date) = cast(getdate() as date)
group by cast(PromisedPickupDate as date), datepart(hour, PromisedPickupDate)

You need an Hours table to join and group against. You can create this using a VALUES constructor:
SELECT
Count(*) AS OrdersAmount
FROM (VALUES
(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15),(16),(17),(18),(19),(20),(21),(22),(23)
) AS v(Hour)
LEFT JOIN [FLX].[dbo].[NDA_SAP_Bestillinger]
ON v.Hour = DATEPART(hour, PromisedPickupTime)
AND PromisedPickupDate >= CAST(CAST(GETDATE() AS date) AS datetime)
AND PromisedPickupDate < CAST(DATEADD(day, 1, CAST(GETDATE() AS date)) AS datetime)
GROUP BY v.Hour;
What is going on here is that we start with a filter by the current date.
Then we join a constructed Hours table against the hour-part of the date. Because it is on the left-side of a LEFT JOIN, we now have all the hours whether or not there is any value in that hour. We can now group by it.
Note the correct method to compare to a date range: a half-open interval >= AND <
Do not use YEAR\MONTH etc in join and filter predicates, as indexes cannot be used for this.

Related

Counting number of transactions within past 1 hour on a particular user

Is there any way how to (in the best case, without using cursor) count number of transactions that the same user made in previous 1 hour.
That means that for this table
CREATE TABLE #TR (PK INT, TR_DATE DATETIME, USER_PK INT)
INSERT INTO #TR VALUES (1,'2018-07-31 06:02:00.000',10)
INSERT INTO #TR VALUES (2,'2018-07-31 06:36:00.000',10)
INSERT INTO #TR VALUES (3,'2018-07-31 06:55:00.000',10)
INSERT INTO #TR VALUES (4,'2018-07-31 07:10:00.000',10)
INSERT INTO #TR VALUES (5,'2018-07-31 09:05:00.000',10)
INSERT INTO #TR VALUES (6,'2018-07-31 06:05:00.000',11)
INSERT INTO #TR VALUES (7,'2018-07-31 06:55:00.000',11)
INSERT INTO #TR VALUES (8,'2018-07-31 07:10:00.000',11)
INSERT INTO #TR VALUES (9,'2018-07-31 06:12:00.000',12)
The result should be:
The solution could be something like: COUNT(*) OVER (PARTITION BY USER_PK ORDER BY TR_DATE ROWS BETWEEN ((WHERE DATEADD(HH,-1,PRECENDING.TR_DATE) > CURRENT ROW.TR_DATE) AND CURRENT ROW ...but I know that ROWS BETWEEN can not be used like that...
I am guessing SQL Server based on the syntax. In SQL Server, you can use apply:
select t.*, tr2.result
from #tr tr outer apply
(select count(*) as result
from #tr tr2
where tr2.user_id = tr.user_id and
tr2.tr_date > dateadd(hour, -1, tr.date) and
tr2.tr_date <= tr.tr_date
) tr2;
SELECT USER_PK, COUNT(*) AS TransactionCount
FROM #TR
WHERE DATEDIFF(MINUTE, TR_DATE, GETDATE()) <= 60
AND DATEDIFF(MINUTE, TR_DATE, GETDATE()) >= 0
GROUP BY USER_PK
You can change GETDATE() with whatever you want, but they need to have the same value

How can I check to see if a data set has a grouping of 3 entries within 5 days

I am looking to see if from a specific date there are a total of 3 entries within a grouping of 5 days within +/- 5 days of the specified date. As long as the second column is false.
Here is an example data set.
Create Table #Data(entryDate date, complete bit)
Insert into #Data Values('2013-02-05', 1)
Insert into #Data Values('2013-02-06', 0)
Insert into #Data Values('2013-02-09', 0)
Insert into #Data Values('2013-02-11', 0)
Insert into #Data Values('2013-02-12', 0)
Insert into #Data Values('2013-02-14', 0)
Given a date of 2013-02-11 I want a true result given that there are 2 scenarios that satisfy my conditions.
2013-02-09, 2013-02-11, 2013-02-12
2013-02-11, 2013-02-12, 2013-02-14
Or given a date of 2013-02-09 I would get a true result but there is only 1 scenario that satisfies the conditions
2013-02-09, 2013-02-11, 2013-02-12
Note that 2013-02-05, 2013-02-06, 2013-02-09 do not satisfy the conditions due to the fact that 2013-02-05 is set to true.
How can I write a sql expression that gives me true or false as described above.
use Outer Apply and then evaluate your result.
declare #data table(entryDate date, complete bit)
Insert into #data Values('2013-02-05', 1)
Insert into #data Values('2013-02-06', 0)
Insert into #data Values('2013-02-09', 0)
Insert into #data Values('2013-02-11', 0)
Insert into #data Values('2013-02-12', 0)
Insert into #data Values('2013-02-14', 0)
select *
from #data d1
outer apply (
SELECT Count(*) AS CountMatches
FROM #data d2
where
ABS(DateDiff(d,d1.entryDate,d2.entryDate)) < 5 AND
complete = 0 AND
d1.entryDate <> d2.entryDate
) t
where
t.CountMatches >=3
I've also come up with the following which ensures that no dates after a completion are counted towards the 3 of 5 groupings
Declare #data table(Id int, entryDate date, complete bit)
Insert into #data Values(1, '2013-02-05', 1)
Insert into #data Values(2, '2013-02-06', 0)
Insert into #data Values(3, '2013-02-09', 0)
Insert into #data Values(4, '2013-02-11', 0)
Insert into #data Values(5, '2013-02-12', 0)
Insert into #data Values(6, '2013-02-14', 0)
Declare #windowMiddle date
Set #windowMiddle = '2013-02-09'
Declare #newestComplete date
select top 1 #newestComplete = entryDate from #data where complete =1 order by entryDate desc
--if(#newestComplete > #windowMiddle) --exit
;with cte as
(
select *
from #data cte
where
cte.entryDate > DATEADD(dd, -5, #windowMiddle)
and cte.entryDate >#newestComplete
and cte.entryDate < DATEADD(dd, 5, #windowMiddle)
)
select cte.entryDate, count(*)
from cte inner join cte d on cte.entryDate between DATEADD(dd, -4, d.entryDate) and d.entryDate
group by cte.entryDate
having (count(*)>=3)
Perhaps someone can comment on whether BlackjacketMack's answer is better than this.

Returning a set of the most recent rows from a table

I'm trying to retrieve the latest set of rows from a source table containing a foreign key, a date and other fields present. A sample set of data could be:
create table #tmp (primaryId int, foreignKeyId int, startDate datetime,
otherfield varchar(50))
insert into #tmp values (1, 1, '1 jan 2010', 'test 1')
insert into #tmp values (2, 1, '1 jan 2011', 'test 2')
insert into #tmp values (3, 2, '1 jan 2013', 'test 3')
insert into #tmp values (4, 2, '1 jan 2012', 'test 4')
The form of data that I'm hoping to retrieve is:
foreignKeyId maxStartDate otherfield
------------ ----------------------- -------------------------------------------
1 2011-01-01 00:00:00.000 test 2
2 2013-01-01 00:00:00.000 test 3
That is, just one row per foreignKeyId showing the latest start date and associated other fields - the primaryId is irrelevant.
I've managed to come up with:
select t.foreignKeyId, t.startDate, t.otherField from #tmp t
inner join (
select foreignKeyId, max(startDate) as maxStartDate
from #tmp
group by foreignKeyId
) s
on t.foreignKeyId = s.foreignKeyId and s.maxStartDate = t.startDate
but (a) this uses inner queries, which I suspect may lead to performance issues, and (b) it gives repeated rows if two rows in the original table have the same foreignKeyId and startDate.
Is there a query that will return just the first match for each foreign key and start date?
Depending on your sql server version, try the following:
select *
from (
select *, rnum = ROW_NUMBER() over (
partition by #tmp.foreignKeyId
order by #tmp.startDate desc)
from #tmp
) t
where t.rnum = 1
If you wanted to fix your attempt as opposed to re-engineering it then
select t.foreignKeyId, t.startDate, t.otherField from #tmp t
inner join (
select foreignKeyId, max(startDate) as maxStartDate, max(PrimaryId) as Latest
from #tmp
group by foreignKeyId
) s
on t.primaryId = s.latest
would have done the job, assuming PrimaryID increases over time.
Qualms about inner query would have been laid to rest as well assuming some indexes.

T-SQL - query for records before and after current time in same statement

For you T-SQL gurus:
I have the following table:
ID Arrival
1 06:16:00
2 06:17:00
3 07:19:00
4 08:21:00
5 10:22:00
6 13:21:00
7 20:22:00
Say the time is currently 08:00 AM and I want to select 2 records before and after record with closest time to now. Result should return records with IDs 2,3,4,5 and 6.
Getting the records before and after record with ID=4 is straight forward but so far, I cannot figure out how to return the complete set as part of the same query. I have these two select statements:
SELECT TOP(2) * FROM Schedules
where (datepart(hour, Arrival) - datepart(hour, getdate()))*60 + datepart(minute, Arrival) - datepart(minute, getdate()) < 0
order by (datepart(hour, Arrival) - datepart(hour, getdate()))*60 + datepart(minute, Arrival) - datepart(minute, getdate())
SELECT TOP(2) * FROM Schedules
where (datepart(hour, Arrival) - datepart(hour, getdate()))*60 + datepart(minute, Arrival) - datepart(minute, getdate()) >= 0
order by (datepart(hour, Arrival) - datepart(hour, getdate()))*60 + datepart(minute, Arrival) - datepart(minute, getdate()) asc
which return records before and after. I tried using a union on both statements but this requires dropping the first order by clause which invalidates my query criteria.
Any ideas will help, thanks.
We can use ROW_NUMBER to partition on if the arrival is before or after and order by the absolute value of the difference between the arrival and the input time.
DECLARE #CurrentTime as time
SET #CurrentTime = '08:00 AM'
DECLARE #Schedules table (id int, arrival time)
INSERT INTO #Schedules
VALUES (1 , '06:16:00' ),
(2, '06:17:00' ),
(3, '07:19:00'),
(4, '08:21:00'),
(5, '10:22:00'),
(6, '13:21:00'),
(7, '20:22:00')
DECLARE #closestTime as time
SELECT TOP 1 #closestTime = arrival FROM #Schedules ORDER BY ABS(DATEDIFF(mi, #CurrentTime ,arrival))
;WITH cte
AS (SELECT id,
arrival,
Row_number() OVER (PARTITION BY (CASE WHEN #closestTime > arrival THEN 1
WHEN #closestTime < arrival THEN 2 END)
ORDER BY Abs(Datediff(mi, #closestTime, arrival))) rn
FROM #Schedules)
SELECT *
FROM cte
WHERE rn < 3
OR arrival = #closestTime
ORDER BY id
Results in
id arrival rn
----------- ---------------- --------------------
2 06:17:00.0000000 2
3 07:19:00.0000000 1
4 08:21:00.0000000 1
5 10:22:00.0000000 1
6 13:21:00.0000000 2
See working example at this data.se query
Can you try the following? I have tested, got the result that u want
create table Arriavel (ID int, Arr_time time)
insert into Arriavel values (1, '06:16:00')
insert into Arriavel values (2, '06:17:00')
insert into Arriavel values (3, '07:19:00')
insert into Arriavel values (4, '08:21:00')
insert into Arriavel values (5, '10:22:00')
insert into Arriavel values (6, '13:21:00')
insert into Arriavel values (7, '20:22:00')
declare #cur_time time
set #cur_time = '08:00:00'
select max(arr_time) arr_time
from Arriavel
where arr_time < #cur_time
union all
select min(arr_time) arr_time
from Arriavel
where arr_time > #cur_time

First and Last Time of Day

I need to run a query to sort out records for the first time an event occurs during the day, and the last time an event happens during the day, and run the report to include a week of recorded history on the system. This is in a SQL2005 database, but I haven't found anything to help me narrow things down to just a first occurrance and a last occurance.
-- Test data in table #T
declare #T table(id int, dt datetime)
insert into #T values (1, '2011-01-01T10:00:00')
insert into #T values (2, '2011-01-01T11:00:00')
insert into #T values (3, '2011-01-01T12:00:00')
insert into #T values (4, '2011-01-02T20:00:00')
insert into #T values (5, '2011-01-02T21:00:00')
insert into #T values (6, '2011-01-02T22:00:00')
-- First day of interval to query
declare #FromDate datetime = '2011-01-01'
-- Add 7 days to get #ToDate
declare #ToDate datetime = dateadd(d, 7, #FromDate)
;with cte as
(
select *,
row_number() over(partition by datediff(d, T.dt, 0) order by T.dt) as rnMin,
row_number() over(partition by datediff(d, T.dt, 0) order by T.dt desc) as rnMax
from #T as T
where T.dt >= #FromDate and T.dt < #ToDate
)
select C.id, C.dt
from cte as C
where C.rnMax = 1 or C.rnMin = 1
order by C.dt