Can this while be converted to a set based query? - sql

I have this query:
if OBJECT_ID('tempdb..#tempA') is not null drop table #tempA
create table #tempA
(
tempid varchar(5),
tempdate smalldatetime
)
declare #loopdate smalldatetime
set #loopdate = '4/2/2013'
while (#loopdate <= '4/28/2013')
begin
--Purpose is to get IDs not in TableB for each date period
insert into #tempA (tempid, tempdate)
select storeid, #loopdate
from
(
select tableAid
from tableA
except
select tableBid
from tableB
where tableBdate = #loopdate
) as idget
set #loopdate = DATEADD(day, 1, #loopdate)
end
Is there a way to make the while loop set-based or is this best that could be done?
EDIT: made changes for correctness
EDIT: end result
ID1 4/2/2014
ID2 4/2/2014
ID4 4/2/2014
ID2 4/3/2014
ID1 4/4/2014
ID5 4/4/2014
ID3 4/5/2014

Still a loop but maybe a little more efficient
while (#loopdate <= '4/28/2013')
begin
--Purpose is to get IDs not in TableB for each date period
insert into #tempA (tempid, tempdate)
select storeid, #loopdate
from
(
select tableAid
from tableA
left join tableB
on tableB.tableBid = tableA.tableAid
and tableB.tableBdate = #loopdate
where tableB.tableBid is null
) as idget
set #loopdate = DATEADD(day, 1, #loopdate)
end
This needs some work but may get you all the way with a set
;WITH Days
as
(
SELECT cast('4/2/2013' AS datetime ) as 'Day'
UNION ALL
SELECT DATEADD(DAY, +1, Day) as 'Day'
FROM Days
where [DAY] <= '4/28/2013'
)
SELECT tableA.tableAid, Days.[Day]
from Days
left join tableB
on tableB.tableBdate = Days.[Day]
full join tableA
on tableB.tableBid = tableA.tableAid
where tableB.tableBid is null

it depends on whether not tableA has a date on it, if not then:
WITH DateList(DateDay) AS
(
SELECT CAST('2013-04-28' AS DATETIME)
UNION ALL
SELECT DATEADD(DAY, DATEDIFF(DAY,0,DATEADD(DAY, -1, DateDay)),0)
FROM DateList
WHERE DateDay between '2013-04-03' and '2013-04-28'
)
SELECT DISTINCT
tableAid
, DateDay
FROM DateList
cross join #tableA a
left join #tableB b
on tableAid = b.tableBid
and b.tableBdate = DateDay
where
b.tableBid is null
ORDER BY
DateDay ASC

insert into #tempA (tempid, tempdate)
select tableAid, tableAdate
from tableA
except
select tableBid,tableBdate
from tableB
where tableBdate >= '4/2/2013' and tableBdate <= '4/28/2013';

You can include the range of dates in a conditional clause, as below:
insert into #tempA (tempid, tempdate)
select tableAid
from tableA
except
select tableBid
from tableB
where tableBdate >= '4/2/2013' and tableBdate <= '4/28/2013';

Related

Double record when both conditions are met SQL

I have a record in my table:
What I need is to create a column with order state: '1' if order was created, '0' if order was cancelled.
So for this example, when there was both creation and cancellation I need two states. The final table should be:
How can I do this?
I think you can simply do a UNION like this:
select OrderCreateDate, OrderCancelDate, ReportDate, 1 as OrderState
from your_table
where orderCreateDate is not null
union all
select OrderCreateDate, OrderCancelDate, ReportDate, 0 as OrderState
from your_table
where orderCancelDate is not null
One way to do this is to join your table multiple times with a constraint on the join to limit your result set; this is an easy way to pivot your data, but it can affect performance.
DECLARE #a TABLE (id INT, createdate date,canceldate date,reportdate DATE)
INSERT INTO #a (id, createdate, canceldate, reportdate)
VALUES (
1, -- id - int
GETDATE(), -- createdate - date
GETDATE(), -- canceldate - date
GETDATE() -- reportdate - date
)
INSERT INTO #a (id, createdate, canceldate, reportdate)
VALUES (
2, -- id - int
GETDATE(), -- createdate - date
null, -- canceldate - date
GETDATE() -- reportdate - date
)
SELECT a.id,a.createdate,a.canceldate,a.reportdate,CASE WHEN a1.id IS NOT NULL THEN '1' ELSE 0 END AS 'createdInd'
,CASE WHEN a2.id IS NOT NULL THEN '1' ELSE 0 END AS 'CancelledInd'
FROM #a a
LEFT JOIN #a a1 ON a.id = a1.id AND a1.createdate IS NOT NULL
LEFT JOIN #a a2 ON a.id = a2.id AND a2.canceldate IS NOT NULL
id createdate canceldate reportdate createdInd CancelledInd
1 2021-04-07 2021-04-07 2021-04-07 1 1
2 2021-04-07 NULL 2021-04-07 1 0
Join to the table a query that returns the values 1 and 0:
SELECT t.*, s.OrderState
FROM tablename AS t
INNER JOIN (SELECT 1 AS OrderState UNION ALL SELECT 0) AS s
ON (s.OrderState = 1 AND t.OrderCreateDate IS NOT NULL)
OR (s.OrderState = 0 AND t.OrderCancelDate IS NOT NULL)

SQL to display pivoted data

I have the following table:
and i want the following output displayed:
the above is basically the (quan*cst) for each day. Now I can acieve this by the following sql:
select t1.pid, isnull(b.m1,0) as day1sale, isnull(a.m2,0) as day2sale
from dbo.test1 t1
left join(select pid, sum(quan*cst) m1
from dbo.test1 where date='2017-05-01' group by pid) b on b.pid=t1.pid
left join (select pid, sum(quan*cst) m2
from dbo.test1 where date='2017-05-02' group by pid) a on a.pid=t1.pid
group by t1.pid,m2 ,m1
order by t1.pid
But i was wondering if there is a simpler way to do it without actually having to hard code the dates?
thanks in advance for the help!
Using a self join to get a list of distinct dates, the result set can be pivoted and the desired aggregates applied to each PID.
CREATE TABLE #TEMP_EXAMPLE
(
[DATE] DATE,
[ID] INT,
[PID] INT,
[QUAN] INT,
[CST] INT
)
INSERT INTO #TEMP_EXAMPLE VALUES('05/01/2017','1','1','2','3')
INSERT INTO #TEMP_EXAMPLE VALUES('05/01/2017','2','2','6','2')
INSERT INTO #TEMP_EXAMPLE VALUES('05/01/2017','3','3','5','1')
INSERT INTO #TEMP_EXAMPLE VALUES('05/01/2017','4','1','1','3')
INSERT INTO #TEMP_EXAMPLE VALUES('05/02/2017','5','3','3','1')
INSERT INTO #TEMP_EXAMPLE VALUES('05/02/2017','6','4','4','7')
INSERT INTO #TEMP_EXAMPLE VALUES('05/02/2017','7','1','7','3')
INSERT INTO #TEMP_EXAMPLE VALUES('05/02/2017','8','5','2','8')
INSERT INTO #TEMP_EXAMPLE VALUES('05/02/2017','9','6','5','6')
INSERT INTO #TEMP_EXAMPLE VALUES('05/02/2017','10','2','8','2')
SELECT ROW_NUMBER() OVER(PARTITION BY 1 ORDER BY [DATE]) AS ID,*
INTO #TEMP_DYNAMIC_DATES
FROM (SELECT DISTINCT [DATE] FROM #TEMP_EXAMPLE ) AS X
SELECT * FROM #TEMP_DYNAMIC_DATES
SELECT PID,ISNULL(DAY1SALE,0) AS DAY1SALE,ISNULL(DAY2SALE,0) AS DAY2SALE FROM (
SELECT PID,SUM([QUAN] * [CST]) AS X, INDIC FROM (
SELECT A.*,
CASE WHEN B.ID = 1 THEN 'DAY1SALE'
WHEN B.ID = 2 THEN 'DAY2SALE'
END AS INDIC
FROM #TEMP_EXAMPLE AS A
JOIN #TEMP_DYNAMIC_DATES AS B
ON A.DATE = B.DATE
) AS X
GROUP BY PID,INDIC) AS O
PIVOT(SUM(X) FOR INDIC IN([DAY1SALE],[DAY2SALE])) AS PT

SQL - Total Time on day

My table looks a lot like the table shown in the following StackOverflow URL:
Calculating total time excluding overlapped time & breaks in SQLServer
My table also includes an OwnerID. Each person has an unique OwnerID, and I could easily join in the person name belonging to that ID.
The result requested should be just like in the linked URL, but per Owner. I tried modifying the selected answer for his URL but that gives me the following error:
The statement terminated. The maximum recursion 100 has been exhausted before statement completion.
This is the query I try to run...
;WITH addNR AS ( -- Add row numbers
SELECT StartDate, EndDate, ROW_NUMBER() OVER (ORDER BY StartDate, EndDate) AS RowID
FROM dbo.FollowUp AS T
WHERE StartDate > '2017-10-02 08:30:00.000'
), createNewTable AS ( -- Recreate table according overlap time
SELECT StartDate, EndDate, RowID
FROM addNR
WHERE RowID = 1
UNION ALL
SELECT
CASE
WHEN a.StartDate <= AN.StartDate AND AN.StartDate <= a.EndDate THEN a.StartDate
ELSE AN.StartDate END AS StartTime,
CASE WHEN a.StartDate <= AN.EndDate AND AN.EndDate <= a.EndDate THEN a.EndDate
ELSE AN.EndDate END AS EndTime,
AN.RowID
FROM addNR AS AN
INNER JOIN createNewTable AS a
ON a.RowID + 1 = AN.RowID
), getMinutes AS ( -- Get difference in minutes
SELECT DATEDIFF(MINUTE,StartDate,MAX(EndDate)) AS diffMinutes
FROM createNewTable
GROUP BY StartDate
)
SELECT SUM(diffMinutes) AS Result
FROM getMinutes
Where I replaced StartTime=StartDate and EndTime=EndDate since my columns are named so..
Sample Data
Coincidence #vitalygolub .
Try my script with various sample data.Also Time Calendar table should be permanent table so it is only time creation.
It is not Recursive so it should perform better.If output is thrown then distinct can be avoided.
create table #tbl (ownerid int,StartTime datetime,enddate datetime);
insert into #tbl values
(1,'2014-10-01 10:30:00.000','2014-10-01 12:00:00.000') -- 90 mins
,(1,'2014-10-01 10:40:00.000','2014-10-01 12:00:00.000') -- 0 since its overlapped with previous
,(1,'2014-10-01 10:42:00.000','2014-10-01 12:20:00.000') -- 20 mins excluding overlapped time
,(1,'2014-10-01 10:40:00.000','2014-10-01 13:00:00.000') -- 40 mins
,(1,'2014-10-01 10:44:00.000','2014-10-01 12:21:00.000') -- 0 previous ones have already covered this time range
,(1,'2014-10-13 15:50:00.000','2014-10-13 16:00:00.000') -- 10 mins
create table #Timetable(timecol time primary key )
insert into #Timetable
select dateadd(minute,(c.rn-1),'00:00')
from(
select top (24*60) row_number()over(order by number)rn from
master..spt_values order by number)c
SELECT c.ownerid
,cast(c.StartTime AS DATE)
,count(DISTINCT timecol) TimeMin
FROM #Timetable t
CROSS APPLY (
SELECT *
FROM #tbl c
WHERE timecol >= cast(c.StartTime AS TIME)
AND timecol < cast(c.enddate AS TIME)
) c
GROUP BY c.ownerid
,cast(c.StartTime AS DATE)
drop table #Timetable
drop table #tbl
Ok, here is working code, am not sure about performance. The idea: create "calendar" with 1 minute precision, fill it for every OwnerId and calculate number of records
DECLARE #table TABLE (OwnerId int,StartTime DateTime2, EndTime DateTime2)
INSERT INTO #table SELECT 1,'2014-10-01 10:30:00.000', '2014-10-01 12:00:00.000'
INSERT INTO #table SELECT 1,'2014-10-01 10:40:00.000', '2014-10-01 12:00:00.000'
INSERT INTO #table SELECT 1,'2014-10-01 10:42:00.000', '2014-10-01 12:20:00.000'
INSERT INTO #table SELECT 1,'2014-10-01 10:40:00.000', '2014-10-01 13:00:00.000'
INSERT INTO #table SELECT 1,'2014-10-01 10:44:00.000', '2014-10-01 12:21:00.000'
INSERT INTO #table SELECT 1,'2014-10-13 15:50:00.000', '2014-10-13 16:00:00.000'
----------------------------------------------------------------------------
INSERT INTO #table SELECT 2,'2014-10-01 10:30:00.000', '2014-10-01 12:00:00.000'
INSERT INTO #table SELECT 2,'2014-10-01 10:40:00.000', '2014-10-01 12:00:00.000'
INSERT INTO #table SELECT 2,'2014-10-01 10:42:00.000', '2014-10-01 12:20:00.000'
declare #period int, #start datetime;;
select #period=datediff(mi, MIN(starttime),MAX(endtime)),#start =MIN(StartTime) from #table;
declare #seconds table(num int identity(0,1),garbage bit not null);
insert into #seconds(garbage) values(0);
while( select COUNT(*) from #seconds) < #period
insert into #seconds(garbage ) select garbage from #seconds;
with a(ownerId, usedminute ) as
(
select distinct t.ownerID,s.num from #seconds s join #table t on
dateadd(mi,s.num, #start) between t.StartTime and dateadd(s,-1,t.EndTime)
)
select ownerId, count(*) time_in_minutes from a group by ownerID;
You can do this without while loops using a derived tally table and regular set based joins, which as a result will perform very efficiently:
-- Define test data
declare #table table (ownerid int,starttime datetime2, endtime datetime2);
insert into #table select 1,'2014-10-01 10:30:00.000', '2014-10-01 12:00:00.000';
insert into #table select 1,'2014-10-01 10:40:00.000', '2014-10-01 12:00:00.000';
insert into #table select 1,'2014-10-01 10:42:00.000', '2014-10-01 12:20:00.000';
insert into #table select 1,'2014-10-01 10:40:00.000', '2014-10-01 13:00:00.000';
insert into #table select 1,'2014-10-01 10:44:00.000', '2014-10-01 12:21:00.000';
insert into #table select 1,'2014-10-13 15:50:00.000', '2014-10-13 16:00:00.000';
----------------------------------------------------------------------------
insert into #table select 2,'2014-10-01 10:30:00.000', '2014-10-01 12:00:00.000';
insert into #table select 2,'2014-10-01 10:40:00.000', '2014-10-01 12:00:00.000';
insert into #table select 2,'2014-10-01 10:42:00.000', '2014-10-01 12:20:00.000';
-- Query
declare #MinStartTime datetime;
declare #Minutes int;
-- Define data boundaries
select #MinStartTime = min(starttime)
,#Minutes = datediff(minute,min(starttime), max(endtime))+1
from #table;
-- Initial Numbers Table - 10 rows
with t(t) as (select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1)
-- Create tally table of minutes by cross joining numbers table many times to generate 1m rows
,n(n) as (select top(#Minutes) dateadd(minute,row_number() over (order by (select null))-1,#MinStartTime) from t t1, t t2, t t3, t t4, t t5, t t6)
-- Define largest possible range for each OwnerID
,o(i,s,e) as (select ownerid, min(starttime), max(endtime) from #table group by ownerid)
select o.i as OwnerID
,cast(n.n as date) as DateValue
,count(n.n) as TotalMinutes
from o
join n -- Return minutes for each OwnerID range,
on n.n between o.s and o.e
where exists(select null -- where that minute should be included.
from #table as t
where n.n >= t.starttime
and n.n < t.endtime)
group by o.i
,cast(n.n as date)
order by o.i
,DateValue
Output:
+---------+------------+--------------+
| OwnerID | DateValue | TotalMinutes |
+---------+------------+--------------+
| 1 | 2014-10-01 | 150 |
| 1 | 2014-10-13 | 10 |
| 2 | 2014-10-01 | 111 |
+---------+------------+--------------+

Set column values in TableA to corresponding values from TableB if some value exist in TableC

I have a TableA that I just added two columns to, StartDate and StopDate. It also contain the column UserName.
I want to fill it with values from TableB that also has StartDate and StopDate. TableB has another column called UserId.
TableC has two columns, Id and Name.
This is what I want to do, explained with some kind of pseudocode:
for each row in TableB, where TableB.UserId exists in TableC.Id, take the corresponding row in TableA where TableA.UserName = TableC.Name and set the TableA.StartDate = TableB.StartDate & TableA.StopDate = TableB.StopDate.
I tried to create a join statement that would do it but the result was kind of embarrassing.
Help would be much appreciated.
Edit: What I have tried:
UPDATE TableA
SET STARTDATE = (
SELECT TableB.StartDate
FROM TableB
WHERE TableB.UserId = (??? what should I write here)?
And then same for StopDate
You can achieve this using the "update-from" syntax.
*I see you've already solved your question, but I'll post this as a possible alternative.
declare #TableA table (id int identity(1, 1), startDate datetime, stopDate datetime, userName varchar(20))
declare #TableB table (id int identity(1, 1), startDate datetime, stopDate datetime, userId int)
declare #TableC table (userId int, userName varchar(20))
insert into #TableA (userName)
select 'A' union all select 'B'
insert into #TableB (userId, startDate, stopDate)
select 1, '2015-01-01', '2015-01-31' union all select 2, '2015-12-01', '2015-12-31'
insert into #TableC
select 1, 'A' union all select 2, 'B'
update
A
set
A.startDate = B.startDate,
A.stopDate = B.stopDate
from
#TableA A
inner join #TableC C on C.userName = A.userName
inner join #TableB B on B.userId = C.userId
select
*
from
#TableA
I solved it, here is my solution
UPDATE TableA
SET StartDate = up.StartDate, EndDate = up.EndDate
FROM TableB up WHERE up.UserID = (SELECT Id from TableC u where u.Name = TableA.UserName)

Query to merge continuous temporal records

I have a table like this:
id START_DATE end_date
1 01/01/2011 01/10/2011
2 01/11/2011 01/20/2011
3 01/25/2011 02/01/2011
4 02/10/2011 02/15/2011
5 02/16/2011 02/27/2011
I want to merge the records where the start_date is just next day of end_date of another record: So the end record should be something like this:
new_id START_DATE end_date
1 01/01/2011 01/20/2011
2 01/25/2011 02/01/2011
3 02/10/2011 02/27/2011
One way that I know to do this will be to create a row based temp table with various rows as dates (each record for one date, between the total range of days) and thus making the table flat.
But there has to be a cleaner way to do this in a single query... e.g. something using row_num?
Thanks guys.
declare #T table
(
id int,
start_date datetime,
end_date datetime
)
insert into #T values
(1, '01/01/2011', '01/10/2011'),
(2, '01/11/2011', '01/20/2011'),
(3, '01/25/2011', '02/01/2011'),
(4, '02/10/2011', '02/15/2011'),
(5, '02/16/2011', '02/27/2011')
select row_number() over(order by min(dt)) as new_id,
min(dt) as start_date,
max(dt) as end_date
from (
select dateadd(day, N.Number, start_date) as dt,
dateadd(day, N.Number - row_number() over(order by dateadd(day, N.Number, start_date)), start_date) as grp
from #T
inner join master..spt_values as N
on N.number between 0 and datediff(day, start_date, end_date) and
N.type = 'P'
) as T
group by grp
order by new_id
You can use a numbers table instead of using master..spt_values.
Try This
Declare #chgRecs Table
(updId int primary key not null,
delId int not null,
endt datetime not null)
While Exists (Select * from Table a
Where Exists
(Select * from table
Where start_date =
DateAdd(day, 1, a.End_Date)))
Begin
Insert #chgRecs (updId, delId , endt)
Select a.id, b.id, b.End_Date,
From table a
Where Exists
(Select * from table
Where start_date =
DateAdd(day, 1, a.End_Date)))
And Not Exists
(Select * from table
Where end_Date =
DateAdd(day, -1, a.Start_Date)))
Delete table Where id In (Select delId from #chgRecs )
Update table set
End_Date = u.endt
From table t join #chgRecs u
On u.updId = t.Id
Delete #delRecs
End
No, was not looking for a loop...
I guess this is a good solution:
taking all the data in a #temp table
SELECT * FROM #temp
SELECT t2.start_date , t1.end_date FROM #temp t1 JOIN #temp t2 ON t1.start_date = DATEADD(DAY,1,t2.end_date)
UNION
SELECT START_DATE,end_date FROM #temp WHERE start_date NOT IN (SELECT t2.START_DATE FROM #temp t1 JOIN #temp t2 ON t1.start_date = DATEADD(DAY,1,t2.end_date))
AND end_date NOT IN (SELECT t1.end_Date FROM #temp t1 JOIN #temp t2 ON t1.start_date = DATEADD(DAY,1,t2.end_date))
DROP TABLE #temp
Please let me know if there is anything better than this.
Thanks guys.
A recursive solution:
CREATE TABLE TestData
(
Id INT PRIMARY KEY,
StartDate DATETIME NOT NULL,
EndDate DATETIME NOT NULL
);
SET DATEFORMAT MDY;
INSERT TestData
SELECT 1, '01/01/2011', '01/10/2011'
UNION ALL
SELECT 2, '01/11/2011', '01/20/2011'
UNION ALL
SELECT 3, '01/25/2011', '02/01/2011'
UNION ALL
SELECT 4, '02/10/2011', '02/15/2011'
UNION ALL
SELECT 5, '02/16/2011', '02/27/2011'
UNION ALL
SELECT 6, '02/28/2011', '03/06/2011'
UNION ALL
SELECT 7, '02/28/2011', '03/03/2011'
UNION ALL
SELECT 8, '03/10/2011', '03/18/2011'
UNION ALL
SELECT 9, '03/19/2011', '03/25/2011';
WITH RecursiveCTE
AS
(
SELECT t.Id, t.StartDate, t.EndDate
,1 AS GroupID
FROM TestData t
WHERE t.Id=1
UNION ALL
SELECT crt.Id, crt.StartDate, crt.EndDate
,CASE WHEN DATEDIFF(DAY,prev.EndDate,crt.StartDate)=1 THEN prev.GroupID ELSE prev.GroupID+1 END
FROM TestData crt
JOIN RecursiveCTE prev ON crt.Id-1=prev.Id
--WHERE crt.Id > 1
)
SELECT cte.GroupID, MIN(cte.StartDate) AS StartDate, MAX(cte.EndDate) AS EndDate
FROM RecursiveCTE cte
GROUP BY cte.GroupID
ORDER BY cte.GroupID;
DROP TABLE TestData;