Turn several queries into one in SQL Server - sql

I have a table in SQL Server called schedule that has the following columns (and others not listed):
scheduleId
roomId
dateRegistered
dateFreed
4564
2
2022-12-25
2022-12-26
4565
3
2022-12-25
2022-12-27
4566
15
2022-12-26
2022-12-27
4567
2
2022-12-28
2022-12-31
4568
3
2022-12-28
2022-12-30
In some part of my app I need to show all the rooms occupied at a certain date.
Currently I run a query like this:
SELECT TOP (1) *
FROM schedule
WHERE roomId = [theNeededRoom] AND dateFreed < [providedDate]
ORDER BY dateFreed DESC
The thing is that I have to run that query in a for loop so that I get the information for every room.
I'm sure there has to be a better way to do this in a single query that returns a row for each of the different roomIds possible, how can I go about this?
Also, when the room is first registered, the dateFreed column has a null value, if I wanted to take this into account, how can I make the query so that, in the case the dateFreed value is null, that is the row that gets chosen?

You can use TOP(1) WITH TIES, while ordering on the last "dateFreed" date.
In order to have a "tied" value to match on, instead of ordering on "dateFreed DESC" we can use the ROW_NUMBER window function to generate a ranking on the same field (which will store 1 for each most recent "dateFreed" value, per "roomId").
SELECT TOP (1) WITH TIES *
FROM schedule
WHERE dateFreed < [providedDate]
ORDER BY ROW_NUMBER() OVER(PARTITION BY roomId ORDER BY dateFreed DESC)

SELECT
t.*
FROM
(
SELECT
roomId AS rId,
max(dateFreed) AS dateFreedMax
FROM
schedule s
GROUP BY
s.roomId
) AS t
WHERE
t.dateFreedMax < [providedDate]
OR t.dateFreedMax IS NULL
Or
SELECT roomId
FROM
schedule s
GROUP BY s.roomId, dateFreed
HAVING
max(dateFreed)<[providedDate] OR dateFreed IS NULL

Related

SQL aggregate using DISTINCT on ID by latest date

Request
I have a section of data below and my goal is to limit the agent column to be distinct only containing unique values, where the unique value selected is the latest date it was modified.
Existing Data
modified agent rank
2016-10-18 346502 0
2013-06-04 346502 41
2011-10-31 346503 0
2012-08-13 346505 0
2016-04-18 346506 66
2015-01-27 346506 1
2016-01-21 346507 103
2015-01-27 346507 130
2012-01-30 346508 0
Trying to use this answer https://stackoverflow.com/a/29912858/461887 as a basis but cannot get where to aggregate it properly.
SQL not working
SELECT DISTINCT
FLiex.agtprof.modify_date_time
,FLiex.agtprof.agent_id
,FLiex.agtprof.rank
,FLiex.agtprof.external_id
WHERE
FLiex.agtprof.modify_date_time = MAX( FLiex.agtprof.modify_date_time)
FROM
FLiex.agtprof
Desired Output
modify agent rank
18/10/2016 346502 0
18/04/2016 346506 66
21/01/2016 346507 103
13/08/2012 346505 0
30/01/2012 346508 0
31/10/2011 346503 0
You're attempting to get single row data, but based on the other rows. While this may be possible with aggregate functions, it's much easier to do with window (analytic) functions:
SELECT [modified], [agent], [rank], [id]
FROM (SELECT [modified], [agent], [rank], [id],
ROW_NUMBER() OVER (PARTITION BY [agent]
ORDER BY [modified] DESC) AS rn
FROM [agtprof]) t
WHERE rn = 1
SELECT DISTINCT max(id_date), agent, rank, id
FROM fliex.agtprof
GROUP BY 2,3,4;
Try this. I think if you chose the max id_date and then group by the rest, you should get the results you're looking for.
Try this:
SELECT
FLiex.agtprof.modify_date_time
,FLiex.agtprof.agent_id
,FLiex.agtprof.rank
,FLiex.agtprof.external_id
FROM
FLiex.agtprof
INNER JOIN (
SELECT
Max(FLiex.agtprof.modify_date_time) as max_mod_date_time
,FLiex.agtprof.agent_id as agent_id
FROM
FLiex.agtprof
GROUP BY FLiex.agtprof.agent_id
) Filter
ON FLiex.agtprof.agentID = Filter.agent_id
AND FLiex.agtprof.modify_date_time = Filter.max_mod_date_time

SQL Sever Dates in strange format in SELECT

Not sure what's going on here, hoping someone can shed some light on this.
Two queries, pretty much the same except the first only selects a date column whilst the second selects an additional column:
SELECT TOP 1 CreationDate
FROM Receipts
WHERE CreationDate IS NOT NULL
ORDER BY 1
Returns
CreationDate
1802-11-01 00:00:00.000
Second query
SELECT TOP 1 Rct_Id, CreationDate
FROM Receipts
WHERE CreationDate IS NOT NULL
ORDER BY 1
Returns
CreationDate
1994-02-14 00:00:00.000
The second one is reasonable and expected, more or less dating back to when the dataset was created. I ran a second set, changing the ORDER BY 1 DESC and got
CreationDate
5202-11-01 00:00:00.000
Rct_Id CreationDate
714350 2015-02-27 00:00:00.000
I'm just really confused as to why the date would change like this when Selected on it's own. Any ideas as to what's going on?
ORDER BY 1 is order by ordinal number, which is first column in SELECT
SELECT TOP 1 CreationDate
FROM Receipts
WHERE CreationDate IS NOT NULL
ORDER BY 1
Means take one record from table receipts ordered by CreationDate ascending.
SELECT TOP 1 Rct_Id, CreationDate
FROM Receipts
WHERE CreationDate IS NOT NULL
ORDER BY 1
Means take one record from table receipts ordered by Rct_id ascending
To get same result as first query use ORDER BY 2 or specify column name explicitly (good practice). Same applies to DESC.
SELECT TOP 1 Rct_Id, CreationDate
FROM Receipts
WHERE CreationDate IS NOT NULL
ORDER BY CreationDate /* Or ORDER BY 2 */

Select most recent InstanceID base on max end date

I am trying to pull the memberinstance from a table based on the max DateEnd. If it is Null I want to pull that as it would be still ongoing. I am using sql server.
select memberinstanceid
from table
group by memberid
having MAX(ISNULL(date_end, '2099-12-31'))
This query above doesnt work for me. I have tried different ones and have gotten it to return the separate instances, but not just the one with the max date.
Below is what my table looks like.
MemberID MemberInstanceID DateStart DateEnd
2 abc12 2013-01-01 2013-12-31
4 abc21 2010-01-01 2013-12-31
2 abc10 2015-01-01 NULL
4 abc19 2014-01-01 2014-10-31
I would expect my results to look like this
MemberInstanceID
abc10
abc19
I have been trying to figure out how to do this but have not had much luck. Any help would be much appreciated. Thanks
I think you need something like the following:
select MemberID, MemberInstanceID
from table t
where (
-- DateEnd is null...
DateEnd is null
or (
-- ...or pick the latest DateEnd for this member...
DateEnd = (
select max(DateEnd)
from table
where MemberID = t.MemberID
)
-- ... and check there's not a NULL entry for DateEnd for this member
and not exists (
select 1
from table
where MemberID = t.MemberID
and DateEnd is null
)
)
)
The problem with this approach would be if there are multiple rows that match for each member, i.e. multiple NULL rows with the same MemberID, or multiple rows with the same DateEnd for the same MemberID.
SELECT TOP 1 memberinstanceid
from table
ORDER BY (CASE WHEN [DateEnd] IS NULL THEN 1 ELSE 0 END) DESC,
[DateEnd] DESC
The ORDER BY is essentially creating a "column" to sort the NULL values to the top, then doing a secondary sort on the dates that are not null.
You have a good start but you don't need to perform any explicit grouping. What you want is the row where the EndDate is null or is the largest value (latest date) of all the records with the same MemberID. You also realized that the Max couldn't return the latest non-null date because the null, if one exists, must be the latest date.
select m.*
from Members m
where m.DateEnd is null
or m.DateEnd =(
select Max( IsNull( DateEnd, '9999-12-31' ))
from Members
where MemberID = m.MemberID );

Detect Anomaly Intervals with SQL

My problem is simple: I have a table with a series of statuses and timestamps (for the sake of curiosity, these statuses indicate alarm levels) and I would like to query this table in order to get duration between two statuses.
Seems simple, but here comes the tricky part: I canĀ“t create look-up tables, procedures and it should be as fast as possible as this table is a little monster holding over 1 billion records (no kidding!)...
The schema is drop dead simple:
[pk] Time
Value
(actualy, there is a second pk but it is useless for this)
And below a real world example:
Timestamp Status
2013-1-1 00:00:00 1
2013-1-1 00:00:05 2
2013-1-1 00:00:10 2
2013-1-1 00:00:15 2
2013-1-1 00:00:20 0
2013-1-1 00:00:25 1
2013-1-1 00:00:30 2
2013-1-1 00:00:35 2
2013-1-1 00:00:40 0
The output, considering only a level 2 alarm, should be as follow should report the begin of a level 2 alarm an its end (when reach 0):
StartTime EndTime Interval
2013-1-1 00:00:05 2013-1-1 00:00:20 15
2013-1-1 00:00:30 2013-1-1 00:00:40 10
I have been trying all sorts of inner joins, but all of them lead me to an amazing Cartesian explosion. Can you guys help me figure out a way to accomplish this?
Thanks!
This has to be one of the harder questions I've seen today - thanks! I assume you can use CTEs? If so, try something like this:
;WITH Filtered
AS
(
SELECT ROW_NUMBER() OVER (ORDER BY dateField) RN, dateField, Status
FROM Test
)
SELECT F1.RN, F3.MinRN,
F1.dateField StartDate,
F2.dateField Enddate
FROM Filtered F1, Filtered F2, (
SELECT F1a.RN, MIN(F3a.RN) as MinRN
FROM Filtered F1a
JOIN Filtered F2a ON F1a.RN = F2a.RN+1 AND F1a.Status = 2 AND F2a.Status <> 2
JOIN Filtered F3a ON F1a.RN < F3a.RN AND F3a.Status <> 2
GROUP BY F1a.RN ) F3
WHERE F1.RN = F3.RN AND F2.RN = F3.MinRN
And the Fiddle. I didn't add the intervals, but I imagine you can handle that part from here.
Good luck.
Finally figured out a version I was happy with. It took me remembering an answer from another question (can't remember which one though) where it was pointed out that the difference between two (increasing) sequences was always a constant.
WITH Ordered (occurredAt, status, row, grp)
as (SELECT occurredAt, status,
ROW_NUMBER() OVER (ORDER BY occurredat),
ROW_NUMBER() OVER (PARTITION BY status
ORDER BY occurredat)
FROM Alert)
SELECT Event.startDate, Ending.occurredAt as endDate,
DATEDIFF(second, Event.startDate, Ending.occurredAt) as interval
FROM (SELECT MIN(occurredAt) as startDate, MAX(row) as ending
FROM Ordered
WHERE status = 2
GROUP BY row - grp) Event
LEFT JOIN (SELECT occurredAt, row
FROM Ordered
WHERE status != 2) Ending
ON Event.ending + 1 = Ending.row
(working SQL Fiddle example, with some additional data rows for work checking).
This unfortunately doesn't correctly deal with level-2 statuses that are end rows (behavior unspecified), although it does list them.
Just for the sake of having an alternative. Tried to do some test on performance, but did not finish.
SELECT
MIN([main].[Start]) AS [Start],
[main].[End],
DATEDIFF(s, MIN([main].[Start]), [main].[End]) AS [Seconds]
FROM
(
SELECT
[sub].[Start],
MIN([sub].[End]) AS [End]
FROM
(
SELECT
[start].[Timestamp] AS [Start],
[start].[Status] AS [StartingStatus],
[end].[Timestamp] AS [End],
[end].[Status] AS [EndingStatus]
FROM [Alerts] [start], [Alerts] [end]
WHERE [start].[Status] = 2
AND [start].[Timestamp] < [end].[Timestamp]
AND [start].[Status] <> [end].[Status]
) AS [sub]
GROUP BY
[sub].[Start],
[sub].[StartingStatus]
) AS [main]
GROUP BY
[main].[End]
And here is a Fiddle.
I do something similar by using id that is an identity to the table.
create table test(id int primary key identity(1,1),timstamp datetime,val int)
insert into test(timstamp,val) Values('1/1/2013 00:00:00',1)
insert into test(timstamp,val) Values('1/1/2013 00:00:05',2)
insert into test(timstamp,val) Values('1/1/2013 00:00:25',1)
insert into test(timstamp,val) Values('1/1/2013 00:00:30',2)
insert into test(timstamp,val) Values('1/1/2013 00:00:35',1)
select t1.timstamp,t1.val,DATEDIFF(s,t1.timstamp,t2.timstamp)
from test t1 left join test t2 on t1.id=t2.id-1
drop table test
I would also make the timestamps be seconds since 1980 or 2000 or whatever. But then you might not want to do the reverse conversion all the time and so it depends on how often you use the actual time stamp.

Recursive CTE - consolidate start and end dates

I have the following table:
row_num customer_status effective_from_datetime
------- ------------------ -----------------------
1 Active 2011-01-01
2 Active 2011-01-02
3 Active 2011-01-03
4 Suspended 2011-01-04
5 Suspended 2011-01-05
6 Active 2011-01-06
And am trying to achieve the following result whereby consecutive rows with the same status are merged into one row with an effective from and to date range:
customer_status effective_from_datetime effective_to_datetime
--------------- ----------------------- ---------------------
Active 2011-01-01 2011-01-04
Suspended 2011-01-04 2011-01-06
Active 2011-01-06 NULL
I can get a recursive CTE to output the correct effective_to_datetime based on the next row, but am having trouble merging the ranges.
Code to generate sample data:
CREATE TABLE #temp
(
row_num INT IDENTITY(1,1),
customer_status VARCHAR(10),
effective_from_datetime DATE
)
INSERT INTO #temp
VALUES
('Active','2011-01-01')
,('Active','2011-01-02')
,('Active','2011-01-03')
,('Suspended','2011-01-04')
,('Suspended','2011-01-05')
,('Active','2011-01-06')
EDIT SQL updated as per comment.
WITH
group_assigned_data AS
(
SELECT
ROW_NUMBER() OVER (PARTITION BY customer_status ORDER BY effective_from_date) AS status_sequence_id,
ROW_NUMBER() OVER ( ORDER BY effective_from_date) AS sequence_id,
customer_status,
effective_from_date
FROM
your_table
)
,
grouped_data AS
(
SELECT
customer_status,
MIN(effective_from_date) AS min_effective_from_date,
MAX(effective_from_date) AS max_effective_from_date
FROM
group_assigned_data
GROUP BY
customer_status,
sequence_id - status_sequence_id
)
SELECT
[current].customer_status,
[current].min_effective_from_date AS effective_from,
[next].min_effective_from_date AS effective_to
FROM
grouped_data AS [current]
LEFT JOIN
grouped_data AS [next]
ON [current].max_effective_from_date = [next].min_effective_from_date + 1
ORDER BY
[current].min_effective_from_date
This isn't recursive, but that's possibly a good thing.
It doesn't deal with gaps in your data. To deal with that you could create a calendar table, with every relevant date, and join on that to fill missing dates with 'unknown' status, and then run the query against that. (Infact you cate do it it a CTE that is used by the CTE above).
At present...
- If row 2 was missing, it would not change the result
- If row 3 was missing, the end_date of the first row would change
Different behaviour can be determined by preparing your data, or other methods. We'd need to know the business logic you need though.
If any one date can have multiple status entries, you need to define what logic you want it to follow. At present the behaviour is undefined, but you could correct that as simply as adding customer_status to the ORDER BY portions of ROW_NUMBER().