MSSQL Recursive dateadd - sql

Hello Iv got big trouble with one query whith next DateTo is counted from previous record.
I have one table
create table #t1 (
M_ID int, --ID of group
STEP int, -- Step number
DateTo Datetime, --DateTo
AddDays int) --Number of days to add to NEXT record
--TestData
INSERT INTO #t1
select 1, 1, GETDATE(), 0 union
select 1, 2, null, 1 union
select 1, 3, null, 0 union
select 1, 4, null, 0 union
select 2, 1, GETDATE(), 0 union
select 2, 2, NULL, 1 union
select 2, 3, NULL, 0
How table looks.
Logic:
If step = 1 then DateTo = GETDATE()
At step 2 of M_ID 1 previous row had 0 days to add so it should copy DateTo from previous row
At step 3 of M_ID 1 previous row has 1 day to add to previous DateTo
Im on the end of my rope...
FAQ
Have to be done in T-SQL (8KK records+)

;with cte as (
select M_ID, STEP, DateTo, AddDays
from #t1
where STEP = 1
union all
select t.M_ID, t.STEP, dateadd(dd, c.AddDays, c.DateTo), t.AddDays
from #t1 t
inner join cte c on t.M_ID = c.M_ID and t.STEP = c.STEP + 1
)
select *
from cte

Related

Having Trouble Summing distinct values

I have a fairly poorly designed DB that I'm trying to pull reports from. I'm attempting to sum the value on the column GuestCount, however with the structure of the joins, i'm getting a cartesian situation that's making the sum inaccurate. I can't use Sum(Distinct) because I'm not trying to sum the distinct values in GuestCount, but rather the sum of distinct rows.
Here's the SQL to set up the Tables:
CREATE TABLE TesttblTransactions (
ID int,
[sysdate] date,
TxnHour tinyint,
Facility nvarchar(50),
TableID int,
[Check] int,
Item int,
Parent int
)
Create Table TesttblTablesGuests (
ID int,
Facility nvarchar(50),
TableID int,
GuestCount tinyint,
TableDate Date
)
Create Table TesttblFacilities (
ID int,
ClientKey nvarchar(50),
Brand nvarchar(50),
OrgFacilityID nvarchar(50),
UnitID smallint
)
INSERT INTO testtbltransactions (
ID,
[Sysdate],
TxnHour,
Facility,
TableID,
[Check],
Item,
Parent
)
VALUES
(
1,
'20221201',
7,
'JOES',
1001,
12345,
8898989,
0
),
(
2,
'20221201',
7,
'JOES',
1001,
12345,
8776767,
1
),
(
3,
'20221201',
7,
'JOES',
1001,
12345,
856643,
0
),
(
4,
'20221201',
7,
'THE DIVE',
1001,
67890,
662342,
0
),
(
5,
'20221201',
7,
'THE DIVE',
1001,
67890,
244234,
0
),
(
6,
'20221201',
7,
'JOES',
1002,
12344,
873323,
0
);
INSERT INTO testtblTablesGuests (
ID,
Facility,
TableID,
GuestCount,
TableDate
)
VALUES
(
1,
'JOES',
1001,
4,
'20221201'
),
(
2,
'THE DIVE',
1001,
1,
'20221201'
),
(
3,
'JOES',
1002,
1,
'20221201'
);
INSERT INTO testtblFacilities (
ID,
ClientKey,
Brand,
OrgFacilityID,
UnitID
)
VALUES
(
1,
'JOES',
'Joes Hospitality Group LLC',
'Joes Bar',
987
),
(
2,
'THE DIVE',
'The Dive Restaurant Group',
'The Dive',
565
);
--Here's the SQL that I need for reporting but can't seem to get working:
Declare #StartDate as Date = '12-1-2022'
Declare #EndDate as Date = '12-1-2022'
--The query we want to work
SELECT
TesttblFacilities.ClientKey,
TesttblFacilities.Brand,
format(testtbltransactions.sysdate,'yyyy-MM-dd') AS [Date],
'H' AS Freqency,
Testtbltransactions.[TxnHour] AS [Hour],
TesttblFacilities.UnitID AS [UnitID],
'Dine In Guest Count' as Metric,
Sum(TesttblTablesGuests.GuestCount) AS [Value]
FROM ((Testtbltransactions
JOIN Testtbltablesguests ON (Testtbltablesguests.TableDate = Testtbltransactions.sysdate) AND (Testtbltransactions.FACILITY = Testtbltablesguests.facility) AND (Testtbltransactions.tableid = Testtbltablesguests.tableid))
JOIN TesttblFacilities ON Testtbltransactions.FACILITY = TesttblFacilities.ClientKey)
Where (((Testtbltransactions.parent)=0))
and Testtbltransactions.sysdate >= #StartDate
and Testtbltransactions.sysdate <= #EndDate
GROUP BY TesttblFacilities.ClientKey, Testtblfacilities.UnitID,TesttblFacilities.Brand, Testtbltransactions.facility, Testtbltransactions.sysdate, Testtbltransactions.TxnHour`
I'm getting 9 and 2, instead of 5 and 1.
In the comments NBK suggested doing a subquery - and it took me a while but I think i found something that works.. . .
Declare #StartDate as Date = '12-1-2022'
Declare #EndDate as Date = '12-1-2022'
Select
t1.txnhour,
t1.facility,
SUM(t1.guestcount) from
(
Select Distinct
TesttblTransactions.TableID as TableID,
testtbltransactions.[txnHour] as txnhour,
testtbltransactions.Facility as Facility,
testtbltablesguests.GuestCount as guestcount,
testtbltransactions.Parent as parent
From TesttblTransactions
Join TesttblTablesGuests on TesttblTablesGuests.TableID = TesttblTransactions.TableID and testtbltablesguests.Facility = TesttblTransactions.Facility
Where (((Testtbltransactions.parent)=0))
and Testtbltransactions.sysdate >= #StartDate
and Testtbltransactions.sysdate <= #EndDate
) T1
Group by t1.Facility, t1.txnhour, t1.Facility
I'm going to continue to refine this, but I think I should be able to move forward with this.

Choose a record from the table where two of the default values are 0 or 1, a bit tricky

Looking for a more elegant and most logical solution for:
The table:
index
id
by_default
text
1
1
0
AAA
2
1
1
ABA
3
1
0
ABC
4
2
0
BCA
5
2
0
BCB
The task is to find the minimum index value with defaults set to 1 and/or defaults set to 0.
I have the following code (not very elegant, but it works, also very slow):
declare #byd_1 as int=
(select min(t.index) idx from Table t where t.[id]=1 and t.by_default=1)
declare #byd_2 as int=
(select min(t.index) idx from Table t where t.[id]=1 and t.by_default=0)
select (case when #byd_1 is null then #byd_2 else #byd_1 end)
The tricky part is: sometimes the by_default column is always 0 (for example: id:2 may have no by_default values set) and as mentioned earlier the task is: need to get the minimum value of the index column.
What is the most elegant (one-line) code possible?
Using MSSQL
The expected results, according to the sample table, should be the following:
index
id
by_default
text
2
1
1
ABA
4
2
0
BCA
Edited to add text also.
A bit ugly but:
drop table #t
select *
into #t
from (
VALUES (1, 1, 0, N'AAA')
, (2, 1, 1, N'ABA')
, (3, 1, 0, N'ABC')
, (4, 2, 0, N'BCA')
, (5, 2, 0, N'BCB')
) t (index_,id,by_default,text)
select index_, id, by_default, text
from (
select min(index_) OVER(PARTITION BY id) AS minIndex
, MIN(case when by_default = 1 then index_ end) over(partition by id) AS minIndexDefault
, *
from #t
) t
where isnull(minIndexDefault, minIndex) = index_

Need a SQL Server query that gets sets of 3 rows based on a date given

I need to get 3 rows per set based on a date given (must be this date) but I want the rows to be based on this date as:
1 ( row where the date from the date column is the next date after given date )
0 ( row where the date is the closest date prior to the date given )
-1 ( prior to the date at 0 )
And add a column with the relative number.
** The dates for the same name and item will never repeat.
For example, a set of rows:
Row ID, Name, Item, Number, Date
1 Andy, Item1, 12030, 2014-06-30
2 Andy, Item1, 62030, 2014-03-31
3 Andy, Item1, 30300, 2013-12-31
4 Andy, Item1, 40030, 2013-10-31
5 Andy, Item1, 50030, 2013-08-30
6 John, Item2, 50240, 2014-04-30
7 John, Item2, 41400, 2014-03-31
8 John, Item2, 40509, 2014-01-31
9 Andy, Item2, 24004, 2014-03-31
10 Andy, Item2, 20144, 2013-12-31
11 Andy, Item2, 20450, 2013-09-30
12 Andy, Item2, 25515, 2013-06-30
If I have 2014-03-15 as the date and search for 'Andy', I expect:
Row ID, Item, Date, Relative Date
2, Item1, 2014-03-31, 1
3, Item1, 2013-12-31, 0
4, Item1, 2013-10-31, -1
9, Item2, 2014-03-31, 1
10, Item2, 2013-12-31, 0
11, Item2, 2013-09-30, -1
This is what I'm using which I have no issues switching if necessary:
DATEDIFF( quarter, 2014-03-31, date )
date BETWEEN DATEADD( quarter, -1, '20140315' ) AND
DATEADD( day, 1 ( DATEADD ( quarter, 2, '20140315' ) )
which returns:
Row ID, Item, Date, Relative Date
2, Item1, 2014-06-30, 1
3, Item1, 2014-03-31, 0
4, Item1, 2013-12-31, -1
9, Item2, 2014-03-31, 0
10, Item2, 2013-12-31, -1
Is there a better way of doing this without date math? I don't think I can accomplish exactly what I want with date math because it's hard to capture the exact 3 rows I need.
Perhaps something with row_number()?
Something like...
CREATE TABLE #TEMP
(
RowID int
, Name varchar(25)
, Item varchar(25)
, Number int
, [Date] datetime
)
INSERT INTO #TEMP
VALUES (1, 'Andy', 'Item1', 12030, '2014-06-30T00:00:00')
, (2, 'Andy', 'Item1', 62030, '2014-03-31T00:00:00')
, (3, 'Andy', 'Item1', 30300, '2013-12-31T00:00:00')
, (4, 'Andy', 'Item1', 40030, '2013-10-31T00:00:00')
, (5, 'Andy', 'Item1', 50030, '2013-08-30T00:00:00')
DECLARE #Date datetime
SET #Date = '2014-03-15T00:00:00'
CREATE TABLE #NameItem
(
ID int identity(1,1)
, Name varchar(25)
, Item varchar(25)
)
CREATE TABLE #Results
(
NIID int
, RowID int
, [Date] datetime
, RelativeDate int
)
INSERT INTO #NameItem
(Name, Item)
SELECT DISTINCT Name, Item
FROM #TEMP a
INSERT INTO #Results
(NIID, RowID, [Date], RelativeDate)
SELECT a.ID, b.RowID, b.[Date], b.RelativeDate
FROM #NameItem a
CROSS APPLY (
SELECT TOP 1 z.RowID, z.[Date], 1 AS RelativeDate
FROM #TEMP z
WHERE z.Name = a.Name
AND z.Item = a.Item
AND [Date] > #Date
ORDER BY [Date]
) b
INSERT INTO #Results
(NIID, RowID, [Date], RelativeDate)
SELECT a.ID, b.RowID, b.[Date], b.RelativeDate
FROM #NameItem a
CROSS APPLY (
SELECT TOP 1 z.RowID, z.[Date], 0 AS RelativeDate
FROM #TEMP z
WHERE z.Name = a.Name
AND z.Item = a.Item
AND [Date] < #Date
ORDER BY [Date] DESC
) b
; with cte_0 as
(
SELECT a.NIID, a.[Date]
FROM #Results a
WHERE a.RelativeDate = 0
)
INSERT INTO #Results
(NIID, RowID, [Date], RelativeDate)
SELECT b.ID, c.RowID, c.[Date], c.RelativeDate
FROM cte_0 a
INNER JOIN #NameItem b
ON a.NIID = b.ID
CROSS APPLY (
SELECT TOP 1 z.RowID, z.[Date], -1 AS RelativeDate
FROM #TEMP z
WHERE z.Name = b.Name
AND z.Item = b.Item
AND z.[Date] < a.[Date]
ORDER BY [Date] DESC
) c
SELECT a.Name, a.Item, b.RowID, b.[Date], b.RelativeDate
FROM #NameItem a
INNER JOIN #Results b
ON a.ID = b.NIID
ORDER BY a.ID, b.RelativeDate DESC
DROP TABLE #NameItem
DROP TABLE #Results
DROP TABLE #TEMP

SQL Update question

I am wondering how this can be achieved.
Let's say I have a table with two columns (IU(uniqueidentifier),(ID(int), SEL(char(1))
ID column has the following values in each row(ordered by IU):
0, 1, 2, 2, 0, 0, 1, 2, 2, 2, 0, 0, 4, 2, 2, 0, 0, 1, 2, 0, 0
I need to update column SEL with 'Y' for rows which are part of the group:
1, 2, 2, 2 ...
(Starts With 1 and in the next rows thare are 2's. (Group 4, 2, 2 is not correct).
So in this example column: SEL should be:
null, Y, Y, Y, null, null, Y, Y, Y, Y, null, null, 4, 2, 2, null, null, Y, Y, null, null
Thanks!
Here's a set-based approach.
DDL & sample data:
DECLARE #atable TABLE (
UI uniqueidentifier DEFAULT NEWSEQUENTIALID(),
ID int,
SEL char(1)
);
INSERT INTO #atable (ID)
SELECT 0 UNION ALL
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 2 UNION ALL
SELECT 0 UNION ALL
SELECT 0 UNION ALL
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 2 UNION ALL
SELECT 2 UNION ALL
SELECT 0 UNION ALL
SELECT 0 UNION ALL
SELECT 4 UNION ALL
SELECT 2 UNION ALL
SELECT 2 UNION ALL
SELECT 0 UNION ALL
SELECT 0 UNION ALL
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 0 UNION ALL
SELECT 0;
The UPDATE statement:
WITH marked AS (
SELECT
*,
grp = CASE ID WHEN 0 THEN 0 ELSE 1 END
FROM #atable
),
grouped AS (
SELECT
*,
grpID = ROW_NUMBER() OVER (ORDER BY UI)
- ROW_NUMBER() OVER (PARTITION BY grp ORDER BY UI)
FROM marked
),
ranked AS (
SELECT
*,
rnk = ROW_NUMBER() OVER (PARTITION BY grp, grpID ORDER BY UI)
FROM grouped
)
UPDATE g
SET SEL = CASE r.ID
WHEN 0 THEN NULL
WHEN 1 THEN 'Y'
ELSE CAST(g.ID AS varchar)
END
FROM grouped g
INNER JOIN ranked r ON g.grp = r.grp AND g.grpID = r.grpID
WHERE r.rnk = 1;
The result of SELECT * FROM #atable after the update:
UI ID SEL
------------------------------------ ----------- ----
A4095E70-A0CC-E011-813B-20CF30905E89 0 NULL
A5095E70-A0CC-E011-813B-20CF30905E89 1 Y
A6095E70-A0CC-E011-813B-20CF30905E89 2 Y
A7095E70-A0CC-E011-813B-20CF30905E89 2 Y
A8095E70-A0CC-E011-813B-20CF30905E89 0 NULL
A9095E70-A0CC-E011-813B-20CF30905E89 0 NULL
AA095E70-A0CC-E011-813B-20CF30905E89 1 Y
AB095E70-A0CC-E011-813B-20CF30905E89 2 Y
AC095E70-A0CC-E011-813B-20CF30905E89 2 Y
AD095E70-A0CC-E011-813B-20CF30905E89 2 Y
AE095E70-A0CC-E011-813B-20CF30905E89 0 NULL
AF095E70-A0CC-E011-813B-20CF30905E89 0 NULL
B0095E70-A0CC-E011-813B-20CF30905E89 4 4
B1095E70-A0CC-E011-813B-20CF30905E89 2 2
B2095E70-A0CC-E011-813B-20CF30905E89 2 2
B3095E70-A0CC-E011-813B-20CF30905E89 0 NULL
B4095E70-A0CC-E011-813B-20CF30905E89 0 NULL
B5095E70-A0CC-E011-813B-20CF30905E89 1 Y
B6095E70-A0CC-E011-813B-20CF30905E89 2 Y
B7095E70-A0CC-E011-813B-20CF30905E89 0 NULL
B8095E70-A0CC-E011-813B-20CF30905E89 0 NULL
Rows in a table have no inherent order, so your grouping (1,2,2,2) is completely arbitrary. It is not guaranteed that your id's will always come in this order:
0, 1, 2, 2, 0, 0, 1, 2, 2, 2, 0, 0, 4, 2, 2, 0, 0, 1, 2, 0, 0
It could be that they come in a completely other order. So you need to specify a ORDER BY clause to get your order. As you have no other fields in your table but SEL and ID, I suppose this is not possible.
I really hope someone comes up with something better than this, because I hate this answer.
create table #test (
IU int identity primary key,
id int,
sel varchar(1)
)
insert into #test(id)
values (0), (1), (2), (2), (0), (0), (1), (2), (2), (2), (0), (0), (4), (2), (2), (0), (0), (1), (2), (0), (0)
DECLARE myCur CURSOR FORWARD_ONLY
FOR
select t.ID
from #test t
order by t.IU
FOR UPDATE OF t.sel
DECLARE #ID int, #lagSel varchar(1)
OPEN myCur
FETCH myCur INTO #ID
WHILE (##FETCH_STATUS = 0) BEGIN
SET #lagSel = CASE
WHEN #lagSel = 'Y' AND #ID in (1,2) THEN 'Y'
WHEN #ID = 1 THEN 'Y'
ELSE NULL
END
UPDATE #test
SET sel = #lagSel
WHERE CURRENT OF myCur
FETCH myCur INTO #ID
END
CLOSE myCur
DEALLOCATE myCur
A couple of things to note:
We're manually managing the value of #lagSel within the cursor so we can carry a value from one row to the next.
In order to be able to use the cursor FOR UPDATE, the table has to have a primary key.
In the UPDATE statement, the WHERE CURRENT OF myCur gives (at least in theory) a big performance gain over any other where clause.
I first tried doing this with lagged joins, but couldn't quite get it there. Here's my work in case someone else can do better:
select main.IU, main.id,
CASE
WHEN main.id = 1 THEN 'Y'
WHEN main.id = 2 AND lag.id in (1, 2) THEN 'Y'
ELSE NULL
END as new_sel
from #test main left outer join
#test lag on main.IU = lag.IU + 1
I think you have the design error here. MS SQL Server does not knows nothing about "next" and "previous" rows. if you try to select the records, the order of the records can be changed from time to time, unless you specify the ordering using ORDER BY statement.
I think you need to change the structure of the tables first.
EDIT: As I see, you have the field and can order your records. Now you can achive your goal using CURSOR.
Briefly, you can create the CURSOR FOR SELECT IU, ID ORDER BY IU ASC.
looping through the cursor records you can check the sequence of the values of ID field, and when sequence will be fully equivalent, you can update the corresponding record.

How to delete when the parameter varies by group without looping? (T-SQL)

Imagine I have these columns in a table:
id int NOT NULL IDENTITY PRIMARY KEY,
instant datetime NOT NULL,
foreignId bigint NOT NULL
For each group (grouped by foreignId) I want to delete all the rows which are 1 hour older than the max(instant). Thus, for each group the parameter is different.
Is it possible without looping?
Yep, it's pretty straightforward. Try this:
DELETE mt
FROM MyTable AS mt
WHERE mt.instant <= DATEADD(hh, -1, (SELECT MAX(instant)
FROM MyTable
WHERE ForeignID = mt.ForeignID))
Or this:
;WITH MostRecentKeys
AS
(SELECT ForeignID, MAX(instant) AS LatestInstant
FROM MyTable)
DELETE mt
FROM MyTable AS mt
JOIN MostRecentKeys mrk ON mt.ForeignID = mrt.ForeignID
AND mt.Instant <= DATEADD(hh, -1, mrk.LatestInstant)
DELETE
FROM mytable
FROM mytable mto
WHERE instant <
(
SELECT DATEADD(hour, -1, MAX(instant))
FROM mytable mti
WHERE mti.foreignid = mto.foreignid
)
Note double FROM clause, it's on purpose, otherwise you won't be able to alias the table you're deleting from.
The sample data to check:
DECLARE #mytable TABLE
(
id INT NOT NULL PRIMARY KEY,
instant DATETIME NOT NULL,
foreignID INT NOT NULL
)
INSERT
INTO #mytable
SELECT 1, '2009-22-07 10:00:00', 1
UNION ALL
SELECT 2, '2009-22-07 09:30:00', 1
UNION ALL
SELECT 3, '2009-22-07 08:00:00', 1
UNION ALL
SELECT 4, '2009-22-07 10:00:00', 2
UNION ALL
SELECT 5, '2009-22-07 08:00:00', 2
UNION ALL
SELECT 6, '2009-22-07 07:30:00', 2
DELETE
FROM #mytable
FROM #mytable mto
WHERE instant <
(
SELECT DATEADD(hour, -1, MAX(instant))
FROM #mytable mti
WHERE mti.foreignid = mto.foreignid
)
SELECT *
FROM #mytable
1 2009-07-22 10:00:00.000 1
2 2009-07-22 09:30:00.000 1
4 2009-07-22 10:00:00.000 2
I'm going to assume when you say '1 hour older than the max(instant)' you mean '1 hour older than the max(instant) for that foreignId'.
Given that, there's almost certainly a more succinct way than this, but it will work:
DELETE
TableName
WHERE
DATEADD(hh, 1, instant) < (SELECT MAX(instant)
FROM TableName T2
WHERE T2.foreignId = TableName.foreignId)
The inner subquery is called a 'correlated subquery', if you want to look for more info. The way it works is that for each row under consideration by the outer query, it is the foreignId of that row that gets referenced by the subquery.