How to find next (n) open days from SQL opening Hours Database - c#

I am using a SQL database schema similar to the one found on this link.
Best way to store working hours and query it efficiently
I am storing the Opening hours for a location using this basic schema
Shop - INTEGER
DayOfWeek - INTEGER (0-6)
OpenTime - TIME
CloseTime - TIME
What i am trying to do however is for the current DateTime (i.e. today) get the NEXT (n) number of days that the shop is open. So for example if i wasnted to find the next three days that the shop was open and configured in the opening hours the shop is closed on a Sunday and todays date is 21/02/2015 (Saturday) I would like to return the days (21/02/2015)Saturday, (23/02/2015)Monday and (23/02/2015)Tuesday.
If it was Sunday i would return (23/02/2015)Monday, (24/02/2015)Tuesday and (25/02/2015)Wednesday (as its closed on sunday) and finally if it was (20/02/2015)Friday it would return (20/02/2015)Friday, (21/02/2015)Saturday, (23/02/2015)Monday.
I dont know if this is easier to do in SQL or C# but i am mentally struggling in if figuring out how to calculate this.
Any pointers, guidance would be great.
Thank you

This will give you up to 10 days ahead in a fairly efficient way. First test data:
DECLARE #DaysAhead TABLE (
Delta INT
)
INSERT INTO #DaysAhead (Delta)
SELECT 0
UNION ALL SELECT 1
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4
UNION ALL SELECT 5
UNION ALL SELECT 6
UNION ALL SELECT 7
UNION ALL SELECT 8
UNION ALL SELECT 9
UNION ALL SELECT 10
DECLARE #Opening TABLE (
Shop INT,
DayOfWk INT,
DayNm varchar(10),
OpenTime TIME,
CloseTime TIME
)
INSERT INTO #Opening (Shop, DayOfWk, DayNm, OpenTime, CloseTime)
SELECT 1, 5, 'Fri', '09:00', '17:00' --
UNION ALL SELECT 1, 6, 'Sat' ,'09:00', '17:00'
--UNION ALL SELECT 0, 'Sun', '09:00', '17:00' -- Not open on Sunday
UNION ALL SELECT 1, 1, 'Mon', '09:00', '17:00'
UNION ALL SELECT 1, 2, 'Tue', '09:00', '17:00'
UNION ALL SELECT 1, 3, 'Wed', '09:00', '17:00'
Which can be queried like this:
DECLARE #dt datetime='21-Feb-2015'
DECLARE #dow int=datepart(dw, #dt)-1
SELECT TOP 3 o.Shop, o.DayOfWk, o.DayNm, o.OpenTime, o.CloseTime FROM (
SELECT Delta, ((#dow+Delta)%7) as DayOfWk
FROM #DaysAhead
) daysAhead
INNER JOIN #Opening o on o.DayOfWk=daysAhead.DayOfWk
ORDER BY daysAhead.Delta
Results:
DECLARE #dt datetime='20-Feb-2015' -- Fri
1 5 Fri 09:00:00.0000000 17:00:00.0000000
1 6 Sat 09:00:00.0000000 17:00:00.0000000
1 1 Mon 09:00:00.0000000 17:00:00.0000000
DECLARE #dt datetime='21-Feb-2015' -- Sat
1 6 Sat 09:00:00.0000000 17:00:00.0000000
1 1 Mon 09:00:00.0000000 17:00:00.0000000
1 2 Tue 09:00:00.0000000 17:00:00.0000000
DECLARE #dt datetime='22-Feb-2015' -- Sun
1 1 Mon 09:00:00.0000000 17:00:00.0000000
1 2 Tue 09:00:00.0000000 17:00:00.0000000
1 3 Wed 09:00:00.0000000 17:00:00.0000000

First you can use a simple query like the following to get the days of the week that the shop is open
Select DayOfWeek
From OpenHours
Where ShopId = #ShopID
This assumes that there will not be entries for days that are not open. Adjust this query if instead the open hour column is null, or less than or equal to the close time for days that are not open.
After you run that query and get the results back and preferably translate them into a List<DayOfWeek> you can do the following in your code.
List<Day0fWeek> openDays = GetOpenDaysFromDB();
DateTime start = DateToStartFrom;
int n = numberOfDays;
List<DateTime> nextNOpenDays = new List<DateTime>();
while(nextNOpenDays.Count < n)
{
if(openDays.Contains(start.DayOfWeek))
nextNOpenDays.Add(start);
start = start.AddDays(1);
}

You can use a case to make a day earlier in this week look like that day next week. Here's an example to look up the next open day:
select top 1 dateadd(day, day_diff, #dt) as dt
from (
select case
when dayofweek <= datepart(dw, #dt) then dayofweek + 7
else dayofweek
end - datepart(dw, #dt) as day_diff
, *
from dbo.OpeningHours
) sub1
order by
day_diff
You can then recurse to find more than one day. If we store the above snippet in a function called get_next_open_day, the recursive common table expression could look like:
; with cte as
(
select dbo.get_next_open_day(#dt) as open_day
, 1 as day_number
union all
select dbo.get_next_open_day(prev_day.open_day)
, prev_day.day_number + 1
from cte as prev_day
where prev_day.day_number < #number_of_days
)
select cte.open_day
, datename(dw, cte.open_day)
from cte
option (maxrecursion 100)
;
Here's a full working example:
use Test
if object_id('OpeningHours') is not null
drop table OpeningHours;
if object_id('dbo.get_next_open_day') is not null
drop function dbo.get_next_open_day;
create table OpeningHours (dayofweek int, opentime time, closetime time);
insert dbo.OpeningHours values
(2, '9:00', '17:00'),
(3, '9:00', '17:00'),
(4, '9:00', '17:00'),
(5, '9:00', '17:00'),
(6, '9:00', '21:00'),
(7, '10:00', '17:00')
;
go
create function dbo.get_next_open_day(
#dt date)
returns date
as begin return
(
select top 1 dateadd(day, day_diff, #dt) as dt
from (
select case
when dayofweek <= datepart(dw, #dt) then dayofweek + 7
else dayofweek
end - datepart(dw, #dt) as day_diff
, *
from dbo.OpeningHours
) sub1
order by
day_diff
)
end
go
--declare #dt date = '2015-02-18' -- Wed
--declare #dt date = '2015-02-20' -- Fri
declare #dt date = '2015-02-22' -- Sun
declare #number_of_days int = 10
; with cte as
(
select dbo.get_next_open_day(#dt) as open_day
, 1 as day_number
union all
select dbo.get_next_open_day(prev_day.open_day)
, prev_day.day_number + 1
from cte as prev_day
where prev_day.day_number < #number_of_days
)
select cte.open_day
, datename(dw, cte.open_day)
from cte
option (maxrecursion 100)
;
The implementation of multiple shops is left as an exercise for the reader.

Try this:
DECLARE #t TABLE(WeekID INT, OpenTime time)
DECLARE #c INT = 10
INSERT INTO #t VALUES
(1, '10:00'),--sunday
(2, '10:00'),--monday
(4, '10:00'),--wednsday
(5, '10:00')--thursday
;WITH Tally (n) AS
(
-- 1000 rows
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) a(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) c(n)
)
SELECT TOP (#c) DATEADD(dd, t.n, GETDATE())
FROM Tally t
JOIN #t s ON DATEPART(w, DATEADD(dd, t.n, GETDATE())) = s.WeekID
Output:
Date
2015-02-22 --sunday
2015-02-23 --monday
2015-02-25 --wednsday
2015-02-26 --thursday
2015-03-01 --sunday
2015-03-02 --monday
2015-03-04 --wednsday
2015-03-05 --thursday
2015-03-08 --sunday
2015-03-09 --monday
PS: You can replace GETDATE() with any date to look from.

I managed to find a solution:
public List<DateTime> getDaysOpen(int numberOfDays, DateTime start)
{
List<byte> openDays = this.getOpeningHoursDays();
List<DateTime> nextNOpenDays = new List<DateTime>();
while (nextNOpenDays.Count < numberOfDays)
{
if (openDays.Contains(Convert.ToByte(start.DayOfWeek)))
nextNOpenDays.Add(start);
start = start.AddDays(1);
}
return nextNOpenDays;
}
public List<byte> getOpeningHoursDays()
{
return db.OpeningHours.Where(oh => oh.LocationId == this.Id).Select(oh => oh.DateOfWeek).ToList();
}
This was in my opinion the easiest method to find a solution. Thank you for all your help.

Related

Creating an Oracle view that takes a parameter

I have a very long query:
SELECT TO_CHAR(tsc.id) AS status,
CASE WHEN tsc.description IS NULL THEN CAST('' as NVARCHAR2(50)) ELSE tsc.description END AS description,
SUM(CASE WHEN tr.USER_TYPE = 1 THEN 1 ELSE 0 END) AS "1",
SUM(CASE WHEN tr.USER_TYPE = 2 THEN 1 ELSE 0 END) AS "2",
SUM(CASE WHEN tr.USER_TYPE = 3 THEN 1 ELSE 0 END) AS "3",
SUM(CASE WHEN tr.USER_TYPE = 5 THEN 1 ELSE 0 END) AS "5",
SUM(CASE WHEN tr.USER_TYPE IS NOT NULL THEN 1 ELSE 0 END) AS total
FROM TRANSACTION_STATUS_CODES tsc
LEFT JOIN TRANSACTIONS tr ON tsc.id = tr.status AND tr.User_Type BETWEEN 1 AND 5 AND tr.status != 1 AND tr.update_date BETWEEN TO_DATE('2022-01-01', 'yyyy-mm-dd HH24:MI:SS') AND TO_DATE('2023-01-04', 'yyyy-mm-dd HH24:MI:SS')
LEFT JOIN TRANSACTION_USER_TYPES ut ON ut.id = tr.user_type
WHERE tsc.id != 1
GROUP BY tsc.id, tsc.description
UNION ALL
SELECT 'TOTAL 2,4,5' AS status,
NULL AS description,
SUM(CASE WHEN tr.USER_TYPE = 1 THEN 1 ELSE 0 END),
SUM(CASE WHEN tr.USER_TYPE = 2 THEN 1 ELSE 0 END),
SUM(CASE WHEN tr.USER_TYPE = 3 THEN 1 ELSE 0 END),
SUM(CASE WHEN tr.USER_TYPE = 5 THEN 1 ELSE 0 END),
SUM(CASE WHEN tr.USER_TYPE IS NOT NULL THEN 1 ELSE 0 END) AS total
FROM TRANSACTION_STATUS_CODES tsc
LEFT JOIN TRANSACTIONS tr ON tsc.id = tr.status AND tr.User_Type BETWEEN 1 AND 5 AND tr.status != 1 AND tr.update_date BETWEEN TO_DATE('2022-01-01', 'yyyy-mm-dd HH24:MI:SS') AND TO_DATE('2023-01-04', 'yyyy-mm-dd HH24:MI:SS')
LEFT JOIN TRANSACTION_USER_TYPES ut ON ut.id = tr.user_type
WHERE tsc.id != 1 AND tsc.id IN (2, 4, 5)
UNION ALL
SELECT 'Total for All' AS status,
NULL AS description,
SUM(CASE WHEN tr.USER_TYPE = 1 THEN 1 ELSE 0 END),
SUM(CASE WHEN tr.USER_TYPE = 2 THEN 1 ELSE 0 END),
SUM(CASE WHEN tr.USER_TYPE = 3 THEN 1 ELSE 0 END),
SUM(CASE WHEN tr.USER_TYPE = 5 THEN 1 ELSE 0 END),
SUM(CASE WHEN tr.USER_TYPE IS NOT NULL THEN 1 ELSE 0 END) AS total
FROM TRANSACTION_STATUS_CODES tsc
LEFT JOIN TRANSACTIONS tr ON tsc.id = tr.status AND tr.User_Type BETWEEN 1 AND 5 AND tr.status != 1 AND tr.update_date BETWEEN TO_DATE('2022-01-01', 'yyyy-mm-dd HH24:MI:SS') AND TO_DATE('2023-01-04', 'yyyy-mm-dd HH24:MI:SS')
LEFT JOIN TRANSACTION_USER_TYPES ut ON ut.id = tr.user_type
WHERE tsc.id != 1
That does what it does.
I've been asked to save it as view and just "Select * from view" which is nice...
but as you can see I run
AND tr.update_date BETWEEN TO_DATE('2022-01-01', 'yyyy-mm-dd HH24:MI:SS') AND TO_DATE('2023-01-04', 'yyyy-mm-dd HH24:MI:SS')
This line of code few times there. now. If I save it as view it will just be same result over and over.
I have this csharp code:
requestedDateTable = LocalGeneralDbExecuterService1.call_TransactionsReport_StoredProcedure(fromDateStr, toDateStr);
which is a function that stores the query above in a datatable variable with two dates I'm capturing from two different labels and gives me a modified result set based on those dates.
I'm trying to achieve the same kind of workflow but without having to write dozens lines of query code in my program.
Is that possible? If so, how? I've been trying procedures, views... and my SQL knowledge isn't WOW at all.
From 19.6 you can create SQL table macros. In effect these enable you to create parameterized views.
Here's an example based on the standard HR schema:
create or replace function filter_emps (
start_date date, end_date date
)
return clob sql_macro as
begin
return '
select * from hr.employees
where hire_date >= start_date and hire_date < end_date ';
end filter_emps;
/
select employee_id, hire_date
from filter_emps ( date'2003-01-01', date'2003-06-01' );
/*
EMPLOYEE_ID HIRE_DATE
----------- -----------------
115 18-MAY-2003 00:00
122 01-MAY-2003 00:00
*/
var start_date varchar2(10);
var end_date varchar2(10);
exec :start_date := '2005-01-01'
exec :end_date := '2005-03-01';
select employee_id, hire_date
from filter_emps (
to_date ( :start_date, 'yyyy-mm-dd' ), to_date ( :end_date, 'yyyy-mm-dd' )
);
/*
EMPLOYEE_ID HIRE_DATE
----------- -----------------
131 16-FEB-2005 00:00
142 29-JAN-2005 00:00
146 05-JAN-2005 00:00
150 30-JAN-2005 00:00
185 20-FEB-2005 00:00
*/
Views cannot take parameters.
Instead, you can write a stored procedure that takes the start and end dates as parameters and returns a cursor.
CREATE PROCEDURE procedure_name (
i_start_date IN TRANSACTIONS.UPDATE_DATE%TYPE,
i_end_date IN TRANSACTIONS.UPDATE_DATE%TYPE,
o_cursor OUT SYS_REFCURSOR
)
IS
BEGIN
OPEN o_cursor FOR
<your_query>;
END procedure_name;
/
and replace the hard-coded dates with:
AND tr.update_date BETWEEN i_start_date AND i_end_date
Then you can call the procedure from C# and pass the parameters.
As already explained, views cannot take parameters.
Another approach suiting your requirements could be a table function:
-- define a type fitting your output
create type t_my_row as object (
id number,
description varchar2(50)
);
create type t_my_tab is table of t_my_row;
-- write a procedure with the required parameters
create function get_my_tab(i_start_date in date, i_end_date in date) return t_my_tab as
l_tab t_my_tab := t_my_tab();
begin
-- your selection here: (select ... union all select...)
for l_rec in (select id, descr from mytable where update_date between i_start_date and i_end_date) loop
l_tab.extend;
l_tab(l_tab.last) := t_my_row(l_rec.id, l_rec.description);
end loop;
return l_tab;
end;
-- call it:
select * from table(get_my_tab(sysdate-1, sysdate));

Query for match all records in list SQL Server

I have a table bawe_services. i want to fetch all data that match with given keys
like i have fields
id | Service_id |bawe_id
1 2 2
2 3 3
3 2 3
if i pass service =2 i need all record of service_id=2 if i pass service=1,2,3 than i want 0 rows because 1 service is not given by any bawe so. i got 0 rows.
I use this query
select * from aspnet_bawe_services where ser_id in(1,2,3)
Thanx in advance
The count of the parameters in the "in" statement must match the having equal number.
select bawe_id from [dbo].[aspnet_bawe_services]
where Service_id in (2)
group by bawe_id
having count(Service_id)=1;
bawe_id
-----------
2
3
select bawe_id from [dbo].[aspnet_bawe_services]
where Service_id in (2,3)
group by bawe_id
having count(Service_id)=2;
bawe_id
-----------
3
select bawe_id from [dbo].[aspnet_bawe_services]
where Service_id in (1,2,3)
group by bawe_id
having count(Service_id)=3;
bawe_id
-----------
(0 row(s) affected)
TRY THIS: It's really tedious but unique requirement and I think to accomplish this, we have to use function
1-Function returns distinct count of service_id
2-Function to split comma separated value and return in table format
--Function returns distinct count of service_id
CREATE FUNCTION [dbo].[getCount](#service_id varchar(500))
RETURNS INT
AS
BEGIN
DECLARE #count int
SELECT #count = COUNT(DISTINCT(t.service_id))
FROM tmptos t
INNER JOIN [dbo].[SplitValue](#service_id, ',') tt on t.service_id = tt.items
RETURN #count
END;
--Function to split comma separated value and return in table format
--Function copied from
--separate comma separated values and store in table in sql server
CREATE FUNCTION [dbo].[SplitValue](#String varchar(MAX), #Delimiter char(1))
RETURNS #temptable TABLE (items VARCHAR(MAX))
AS
BEGIN
DECLARE #idx int
DECLARE #slice varchar(8000)
SELECT #idx = 1
if len(#String)<1 or #String is null return
WHILE #idx!= 0
BEGIN
set #idx = charindex(#Delimiter,#String)
IF #idx!=0
set #slice = left(#String,#idx - 1)
else
set #slice = #String
IF(LEN(#slice)>0)
INSERT INTO #temptable(Items) values(#slice)
SET #String = right(#String,len(#String) - #idx)
IF LEN(#String) = 0 break
END
RETURN
END;
--Table with Sample Data
create table tmptos(id int, Service_id int, bawe_id int)
insert into tmptos values
(1, 2, 2),
(2, 3, 3),
(3, 2, 3)
declare #service_id varchar(50) = '2,3'
select *
from tmptos t
inner join [dbo].[SplitValue](#service_id, ',') tt on t.Service_id = tt.items
where [dbo].[getCount](#service_id) = (select count(distinct(items)) from [dbo].[SplitValue](#service_id, ','))
OUTPUT:
id Service_id bawe_id items
1 2 2 2
2 3 3 3
3 2 3 2
It's bit lengthy but works perfectly.
select * from aspnet_bawe_services
where Service_id in (1,2,3)
and
( select count(distinct Service_id) from aspnet_bawe_services where Service_id in (1,2,3) ) = 3
last number in query (in this case "3") is elements count, which you have in IN list.
You can get the service ids that you want using group by and having:
select service_id
from t
where bawe_id in (1, 2, 3)
group by service_id
having count(distinct bawe_id) = 3;
The "= 3" is the number of ids in the IN list.
You can then use in or join or exists to get the full records:
select t.*
from t
where t.service_id in (select service_id
from t
where bawe_id in (1, 2, 3)
group by service_id
having count(distinct bawe_id) = 3
);

How to get monthly report from two different month?

I have a table named Leave it contains 2 rows
no=1 Leave=CL,Lnumber=2,FromDate='2015-01-10',ToDate=2015-01-11'
no=2 Leave=CL,Lnumber=2,FromDate='2015-01-12',ToDate=2015-01-13'
no=3 Leave=CL,Lnumber=2,FromDate='2015-01-15',ToDate='2015-02-16'
no=4 Leave=CL,Lnumber=2,FromDate='2015-01-31',ToDate='2015-02-01'
Here I want to get january month leave report(leave and Lnumer). How can I fetch this considering this FromDate and ToDate
The answer should be like this
Leave=CL,Lnumber=7 (for jan month)
Leave=Cl,Lnumber=1 (for feb month)
I am using MSSQL 2008. You can add extra fields if it is necessory. Thank You
I'd recommend building a calendar table (basically a tally table, but with dates). Then join your leave table to the calendar table between the from and to dates, and count the days that fall in the interval.
;with t as
(
select no=1, Leave='CL',Lnumber=2,FromDate=cast('2015-01-10' as date),ToDate=cast('2015-01-11' as date) union all
select no=2, Leave='CL',Lnumber=2,FromDate=cast('2015-01-31' as date),ToDate=cast('2015-02-01' as date)
), cal as
(
select top 10000 _date = cast(cast(row_number() over (order by (select null)) + 41000 as datetime) as date)
from sys.objects a, sys.objects b
)
select
t.Leave,
_year = datepart(year, c._date),
_month = datepart(month, c._date),
count(_date)
from t
inner join cal c
on c._date between t.FromDate and t.ToDate
group by t.Leave, datepart(year, c._date), datepart(month, c._date)

How do I find week number of a date according to DATEFIRST

How to find week number of a date if start day is Tuesday and end day is Monday?
Here is the example criteria
Date Output
...... ..........
01-Dec-2014 (Monday) - 2014_DECEMBER_WEEK_NO_1
04-Dec-2014 (Thursday) - 2014_DECEMBER_WEEK_NO_1
29-Dec-2014 (Monday) - 2014_DECEMBER_WEEK_NO_4
30-Dec-2014 (Tuesday) - 2014_DECEMBER_WEEK_NO_5
31-Dec-2014 (Tuesday) - 2014_DECEMBER_WEEK_NO_5
I tried following query:
select CAST(UPPER(DATENAME(YEAR, #FROMDATE)) AS VARCHAR(20))
+'_'+CAST(UPPER(DATENAME(MONTH, #FROMDATE)) AS VARCHAR(20))
+'_WEEK_NO_'+CAST((DAY(#FROMDATE)
+ (DATEPART(DW, DATEADD (MONTH, DATEDIFF (MONTH, 0, #FROMDATE), 0))-1) -1)/7 + 1 AS VARCHAR(10))
This working fine if start day is Sunday and end day is Saturday.
Set your first day to Tuesday
SET DATEFIRST 2
Execute the following query
DECLARE #FROMDATE DATE='31-Dec-2014'
SELECT CAST(UPPER(DATENAME(YEAR, #FROMDATE)) AS VARCHAR(20))
+'_'+CAST(UPPER(DATENAME(MONTH, #FROMDATE)) AS VARCHAR(20))
+'_WEEK_NO_'+REPLACE(CAST((DAY(#FROMDATE)
+ (DATEPART(DW, DATEADD (MONTH, DATEDIFF (MONTH, 0, #FROMDATE), 0))-1) -1)/7 AS VARCHAR(10)),'0','1')
UPDATE
SET DATEFIRST 2
DECLARE #FROMDATE DATE='12-JAN-2015'
DECLARE #ALLDATE DATE=DATEADD(month, DATEDIFF(month, 0, #FROMDATE), 0)
DECLARE #FIRSTDATE DATE
; WITH CTE as
(
SELECT 1 RNO,CAST(#ALLDATE AS DATE) as DATES
UNION ALL
SELECT RNO+1, DATEADD(DAY,1,DATES )
FROM CTE
WHERE DATES < DATEADD(MONTH,1,#ALLDATE)
)
SELECT TOP 1 #FIRSTDATE = DATES
FROM CTE
WHERE DATEPART(W,DATES)=1
SELECT CAST(UPPER(DATENAME(YEAR, #FROMDATE)) AS VARCHAR(20))
+'_'+CAST(UPPER(DATENAME(MONTH, #FROMDATE)) AS VARCHAR(20))
+'_WEEK_NO_'+CAST((DATEDIFF(DAY,#FIRSTDATE,#FROMDATE)/7)+1 AS VARCHAR(10))
set datefirst 2
DECLARE #StartDate DATE = '2014-12-01',
#EndDate DATE = '2014-12-31',
#DayCount int
SELECT #DayCount = count(* )
FROM (SELECT TOP ( datediff(DAY,#StartDate,#EndDate) + 1 )
[Date] = dateadd(DAY,ROW_NUMBER()
OVER(ORDER BY c1.name),
DATEADD(DD,-1,#StartDate))
FROM [master].[dbo].[spt_values] c1 ) x
WHERE datepart(dw,[Date]) = 1;
select CAST(UPPER(DATENAME(YEAR, #EndDate)) AS VARCHAR(20))
+'_'+CAST(UPPER(DATENAME(MONTH, #EndDate)) AS VARCHAR(20))
+'_WEEK_NO_'+ Cast(#DayCount as varchar)

merging records for sql table based on column data

i have some dirty resource usage records in t_resourcetable which looks like this
resNo subres startdate enddate
1 2 2012-01-02 22:03:00.000 2012-01-03 00:00:00.000
1 2 2012-01-03 00:00:00.000 2012-01-04 00:00:00.000
1 2 2012-01-04 00:00:00.000 2012-01-04 16:23:00.000
1 3 2012-01-06 16:23:00.000 2012-01-06 22:23:00.000
2 2 2012-01-04 05:23:00.000 2012-01-06 16:23:00.000
i need those dirty rows to be merged in such way
resNo subres startdate enddate
1 2 2012-01-02 22:03:00.000 2012-01-04 16:23:00.000
1 3 2012-01-06 16:23:00.000 2012-01-06 22:23:00.000
2 2 2012-01-04 05:23:00.000 2012-01-06 16:23:00.000
this should get updated to the same table, i have more than 40k rows so can not use cursor please help me cleaning up this kind of data trough some optimized sql statements.
solution provided with temptable and group does not encounter the scenario like.
i am looking for without cursor based solution to this problem
resNo subres startdate enddate
1 2 2012-01-02 22:03:00.000 2012-01-03 00:00:00.000
1 2 2012-01-03 00:00:00.000 2012-01-04 00:00:00.000
1 2 2012-01-04 00:00:00.000 2012-01-04 16:23:00.000
1 2 2012-01-14 10:09:00.000 2012-01-15 00:00:00.000
1 2 2012-01-15 00:00:00.000 2012-01-16 00:00:00.000
1 2 2012-01-16 00:00:00.000 2012-01-16 03:00:00.000
1 3 2012-01-06 16:23:00.000 2012-01-06 22:23:00.000
2 2 2012-01-04 05:23:00.000 2012-01-06 16:23:00.000
i need those dirty rows to be merged in such way
resNo subres startdate enddate
1 2 2012-01-02 22:03:00.000 2012-01-04 16:23:00.000
1 2 2012-01-14 10:09:00.000 2012-01-16 03:00:00.000
1 3 2012-01-06 16:23:00.000 2012-01-06 22:23:00.000
2 2 2012-01-04 05:23:00.000 2012-01-06 16:23:00.000
plesae take me out of this dirty data problem
you need to group your data by resNo and subRes like this:
select resNo, subRes, min(startdate), max(enddate)
from t_resourcetable
group by resNo, subRes
and insert the result on a temporary table.
Then you can truncate t_resourcetable and insert the reult from the temp tample into it
First step is to create backup:
select *
into t_resourcetable_backup20120327
from t_resourcetable
Then update enddate for first records grouped by resNo and subRes:
update t_resourcetable
set enddate = (select max (enddate)
from t_resourcetable t1
where t1.resNo = t_resourcetable.resNo
and t1.subRes = t_resourcetable.subRes)
where not exists (select null
from t_resourcetable t1
where t1.resNo = t_resourcetable.resNo
and t1.subRes = t_resourcetable.subRes
and t1.startdate < t_resourcetable.startdate)
And finally remove extra records:
delete t_resourcetable
where exists (select null
from t_resourcetable t1
where t1.resNo = t_resourcetable.resNo
and t1.subRes = t_resourcetable.subRes
and t1.startdate < t_resourcetable.startdate)
This will leave duplicates if you have duplicate startdates for unique combination of resNo and subRes. You should also check if enddates always have corresponding startdates because you will lose gaps - but this might just be the thing you want.
In addition to creating backup you might wrap update/delete in transaction, do a select after delete and rollback, then check the data in Excel and, if everything seems fine, repeat the transaction but commit it this time.
UPDATE: this query identifies gaps. If you are on Sql Server 2000, convert CTEs to derived tables. First returns list of resources not having predecessors, last does the same for successors. Both count gaps. Then lists are joined by resNo, subRes and gap number.
;with first as (
select resNo, subres, startdate,
row_number() over (partition by resNo, subres order by startdate) rowNumber
from t_resourcetable
where not exists (select null
from t_resourcetable t1
where t1.resNo = t_resourcetable.resNo
and t1.subres = t_resourcetable.subres
and t1.enddate = t_resourcetable.startdate)
),
last as (
select resNo, subres, enddate,
row_number () over (partition by resNo, subres order by enddate) rowNumber
from t_resourcetable
where not exists (select null
from t_resourcetable t1
where t1.resNo = t_resourcetable.resNo
and t1.subres = t_resourcetable.subres
and t1.startdate = t_resourcetable.enddate)
)
select first.resno, first.subres, first.startdate, last.enddate
from first
inner join last
on first.resNo = last.resNo
and first.subres = last.subres
and first.rowNumber = last.rowNumber
You can try this ?
SELECT resno,
subres,
startdate,
MIN(enddate) AS enddate
FROM (SELECT t1.resno,
t1.subres,
t1.startdate,
t2.enddate
FROM t_resourcetable t1,
t_resourcetable t2
WHERE t1.enddate <= t2.enddate
AND NOT EXISTS (SELECT *
FROM t_resourcetable t3
WHERE ( t1.resno = t3.resno
AND t1.subres = t3.subres
AND t1.startdate > t3.startdate
AND t1.startdate <= t3.enddate )
OR ( t2.resno = t3.resno
AND t2.subres = t3.subres
AND t2.enddate >= t3.startdate
AND t2.enddate < t3.enddate )))t
GROUP BY resno,
subres,
startdate
The image is like

Categories

Resources