DATEPART fix days - sql

CASE WHEN DAY % 2 = 0 AND POL = 'SUUA'
THEN CONVERT(VARCHAR(15),DATEPART(DAY,5)) + ' TO ' + CONVERT(VARCHAR(15),DATEPART(DAY,3),103)
I set datefirst 1 but i can't how to improve this.
I'm sorry for less information. I'm using SQL Server.
I uploaded the .img to exemplify.
CASE WHEN DAY % 2 = 0 AND POL = 'SUUA'
THEN CONVERT(VARCHAR(15),DATEPART(DAY,5)) + ' TO ' + CONVERT(VARCHAR(15),DATEPART(DAY,3),103)
I want to show somwthing like this
PORT COLLECT DEPARTURE
MANAUS 07/12 TO 12/12 15/12/2018(ODD DAY)

I highly recommend using a Calendar Table for this. A calendar table holds date values with additional information, so it's easier to find particular days (like business days or weekdays, as for this example).
The following solution uses a calendar table and 2 CROSS APPLY operators to get the previous collection dates.
This is how you can create a calendar table (recursive CTE):
SET DATEFIRST 1 -- 1: Monday, 7: Sunday
-- Create a Calendar Table
IF OBJECT_ID('tempdb..#CalendarTable') IS NOT NULL
DROP TABLE #CalendarTable
;WITH CalendarTable AS
(
SELECT
Date = CONVERT(DATE, '2016-01-01'),
Weekday = DATEPART(WEEKDAY, '2016-01-01')
UNION ALL
SELECT
Date = DATEADD(DAY, 1, C.Date),
Weekday = DATEPART(WEEKDAY, DATEADD(DAY, 1, C.Date))
FROM
CalendarTable AS C
WHERE
C.Date <= '2020-01-01'
)
SELECT
C.Date,
C.Weekday
INTO
#CalendarTable
FROM
CalendarTable AS C
OPTION
(MAXRECURSION 0)
The table is like the following:
SELECT * FROM #CalendarTable ORDER BY Date DESC
Date Weekday
2020-01-02 4
2020-01-01 3
2019-12-31 2
2019-12-30 1
2019-12-29 7
2019-12-28 6
2019-12-27 5
2019-12-26 4
2019-12-25 3
2019-12-24 2
2019-12-23 1
2019-12-22 7
2019-12-21 6
2019-12-20 5
2019-12-19 4
2019-12-18 3
2019-12-17 2
2019-12-16 1
2019-12-15 7
2019-12-14 6
2019-12-13 5
2019-12-12 4
2019-12-11 3
We will use this to find the closest Wednesday and Monday just before a particular Departure date. We find this using a CROSS APPLY having the DepartureDate as a higher limit, then searching for the particular weekday (1 for monday, 3 for wednesday). Then use TOP 1 with ORDER BY Date DESC to get the highest Monday/Wednesday just before that departure date.
-- Build your Collect periods
;WITH SampleData AS
(
SELECT
V.Departure
FROM
(VALUES
('2018-12-01'),
('2018-12-09'),
('2018-12-25'),
('2018-12-29'),
('2019-01-02'),
('2019-01-07'),
('2019-01-10')) AS V(Departure)
)
SELECT
V.Departure,
-- Friday to Wednesday
ClosestWednesdayBeforeDeparture = W.Date,
PreviousFridayOfThatWednesday = DATEADD(DAY, -5, W.Date),
-- Wednesday to Monday
ClosestMondayBeforeDeparture = M.Date,
PreviousWednesdayOfThatMonday = DATEADD(DAY, -5, M.Date),
-- Check for odd/even
IsOdd = CASE WHEN DATEPART(DAY, V.Departure) % 2 = 1 THEN 1 ELSE 0 END,
-- Use previous expressions to build your collect periods
Collect = CASE
WHEN
DATEPART(DAY, V.Departure) % 2 = 1 -- IsOdd
THEN
CONVERT(VARCHAR(100), DATEADD(DAY, -5, W.Date), 120) -- PreviousFridayOfThatWednesday
+ ' TO '
+ CONVERT(VARCHAR(100), W.Date, 120) -- ClosestWednesdayBeforeDeparture
ELSE -- IsEven
CONVERT(VARCHAR(100), DATEADD(DAY, -5, M.Date), 120) -- PreviousWednesdayOfThatMonday
+ ' TO '
+ CONVERT(VARCHAR(100), M.Date, 120) -- ClosestMondayBeforeDeparture
END
FROM
SampleData AS V
CROSS APPLY (
SELECT TOP 1
C.Date
FROM
#CalendarTable AS C
WHERE
C.Date < V.Departure AND
C.Weekday = 3 -- 3: Wednesday
ORDER BY
C.Date DESC) AS W
CROSS APPLY (
SELECT TOP 1
C.Date
FROM
#CalendarTable AS C
WHERE
C.Date < V.Departure AND
C.Weekday = 1 -- 1: Monday
ORDER BY
C.Date DESC) AS M
ORDER BY
V.Departure
Finding the previous Friday from a Wednesday is as simple as moving backwards 5 days, the same goes from Monday to Wednesday.
Results:
Departure IsOdd Collect ClosestWednesdayBeforeDeparture PreviousFridayOfThatWednesday ClosestMondayBeforeDeparture PreviousWednesdayOfThatMonday
2018-12-01 1 2018-11-23 TO 2018-11-28 2018-11-28 2018-11-23 2018-11-26 2018-11-21
2018-12-09 1 2018-11-30 TO 2018-12-05 2018-12-05 2018-11-30 2018-12-03 2018-11-28
2018-12-25 1 2018-12-14 TO 2018-12-19 2018-12-19 2018-12-14 2018-12-24 2018-12-19
2018-12-29 1 2018-12-21 TO 2018-12-26 2018-12-26 2018-12-21 2018-12-24 2018-12-19
2019-01-02 0 2018-12-26 TO 2018-12-31 2018-12-26 2018-12-21 2018-12-31 2018-12-26
2019-01-07 1 2018-12-28 TO 2019-01-02 2019-01-02 2018-12-28 2018-12-31 2018-12-26
2019-01-10 0 2019-01-02 TO 2019-01-07 2019-01-09 2019-01-04 2019-01-07 2019-01-02
This was a good SQL exercise.

Really thanks #Ezlo, thats was excellent. It's a good SQL exercise. I think this will suit me well on my job. Sorry to look boring, I've some questions:
Edit:. I noticed that the dates of V(Departure) is fix. I want to make the dates dinamic. Because when I set the days on
CROSS APPLY(SELECT TOP 1
C.DATE
FROM
#CALENDARTABLE C, SAMPLEDATA V
WHERE
C.DATE < V.DEPARTURE AND
C.WEEKDAY = 1
ORDER BY
C.DATE DESC) AS W
CROSS APPLY (SELECT TOP 1
C.DATE
FROM
#CALENDARTABLE C,SAMPLEDATA V
WHERE
C.DATE < V.DEPARTURE AND
C.WEEKDAY = 7
ORDER BY
C.DATE DESC) AS M
The dates of collect as fix for all Ports.
Sorry if i boring. You guys help a lot.

Related

SSMS 2018 - Find Gaps in Dates and Flag the Gaps

I have reviewed many posts about how to find gaps in dates and believe that I am close to figuring it out but need just a little extra help. Per my query I am pulling distinct days with a record count for each distinct day. I have added a "Gap_Days" column which should return a zero if no gap from previous date OR the number of days since the previous date. As you can see all of my Gap_Days are zero when in fact I am missing 10/24 and 10/25. Therefore on 10/26 there should be a gap of 2 since the previous date is 10/23.
Thanks in advance for pointing out what I am probably looking right at.
SELECT DISTINCT Run_Date, COUNT(Run_Date) AS Daily_Count,
Gap_Days = Coalesce(DateDiff(Day,Lag(Run_Date) Over (partition by Run_Date order by Run_Date DESC), Run_Date)-1,0)
FROM tblUnitsOfWork
WHERE (Run_Date >= '2022-10-01')
GROUP BY Run_Date
ORDER BY Run_Date DESC;
Run_Date Daily_Count Gap_Days
2022-10-29 00:00:00.000 8431 0
2022-10-28 00:00:00.000 8204 0
2022-10-27 00:00:00.000 8705 0
2022-10-26 00:00:00.000 7885 0
2022-10-23 00:00:00.000 7485 0
2022-10-22 00:00:00.000 8699 0
2022-10-21 00:00:00.000 9212 0
2022-10-20 00:00:00.000 9220 0
First let's set up some demo data:
DECLARE #table TABLE (ID INT IDENTITY, date DATE)
DECLARE #dt DATE
WHILE (SELECT COUNT(*) FROM #table) < 30
BEGIN
SET #dt = DATEADD(DAY,(ROUND(((50 - 1 -1) * RAND() + 1), 0) - 1)-25,CURRENT_TIMESTAMP)
IF NOT EXISTS (SELECT 1 FROM #table WHERE date = #dt) INSERT INTO #table (date) SELECT #dt
END
ID date
--------
1 2022-11-10
2 2022-11-15
3 2022-10-20
...
28 2022-10-14
29 2022-11-13
30 2022-11-21
This gives us a table variable with 30 random dates in a 50 day window. Now let's look for missing dates:
SELECT *, CASE WHEN ROW_NUMBER() OVER (ORDER BY date) > 1 AND LAG(date,1) OVER (ORDER BY date) <> DATEADD(DAY,-1,date) THEN 'GAP! ' + CAST(DATEDIFF(DAY,LAG(date,1) OVER (ORDER BY date),date)-1 AS NVARCHAR) + ' DAYS MISSING!' END
FROM #table
ORDER BY date
All we're doing here is ignoring the first date (since it's expected there wouldn't be one before then) and from then on comparing the last date (using lag ordered by date) to the current date. If it is not a day before the case statement will produce a message with how many days were missing.
ID date MissingDatesFlag
----------------------------
1 2022-10-08 NULL
4 2022-10-09 NULL
25 2022-10-10 NULL
28 2022-10-11 NULL
22 2022-10-15 GAP! 4 DAYS MISSING!
2 2022-10-18 GAP! 3 DAYS MISSING!
12 2022-10-19 NULL
24 2022-10-20 NULL
....
15 2022-11-18 GAP! 3 DAYS MISSING!
29 2022-11-21 GAP! 3 DAYS MISSING!
20 2022-11-22 NULL
Since the demo data is randomly selected your results may vary, but they should be similar.

How to select weekly data from daily data

There are two columns, XCHG_DATE and USD_KRW, and the table contains daily data.
What I am trying to do is to select weekly data from the daily data.
E.g) (2022-03-01, value), (2022-03-08, value), (2022-03-15, value), (2022-03-22, value) and so one...
The current SQL I have is:
SELECT CE.XCHG_DATE xchageDate
, CE.USD_KRW usdKrw
FROM(
SELECT DATEADD(WEEK, DATEDIFF(WEEK, 1, XCHG_DATE), 4) xchageDate
FROM CWL_EXCHANGE
WHERE XCHG_DATE BETWEEN '20220301' AND '20220523'
GROUP BY DATEADD(WEEK, DATEDIFF(WEEK, 1, XCHG_DATE),4)
) AS RESULT
LEFT JOIN CWL_EXCHANGE CE
ON CE.XCHG_DATE = RESULT.xchageDate
WHERE RESULT.xchageDate = CE.XCHG_DATE
ORDER BY CE.XCHG_DATE;
This query gives me weekly data from 20220304 to 20220520, but I need the data from 2022-03 to 2022-05-23(today's date).
Can anyone please help me of how to solve this problem?
Thanks in advance!
Sample Data:
COLUMNS = XCHG_DATE USD_KRW
2022-05-23 1
2022-05-22 2
2022-05-21 3
2022-05-20 4
2022-05-19 5
2022-05-18 6
2022-05-17 7
2022-05-16 8
2022-05-15 9
2022-05-14 10
2022-05-13 11
2022-05-12 12
2022-05-11 13
2022-05-10 14
2022-05-09 15
2022-05-08 16
2022-05-07 17
2022-05-06 18
Current Output :
20220506 18
20220513 11
20220520 4
Expected Output :
20220509 15
20220516 8
20220523 1
You will need a calendar table with Weekdaynumber to arrive at the earlier weekdays corresponding to Today's date(23 May 2022). This will make the calculation easier.
DECLARE #StartDate DATE = '2022-05-01'
DECLARE #EndDate DATE = '2022-05-31'
declare #table table (XCHG_DATE date, USD_KRW int);
insert into #table
values ('2022-05-23', 1 )
,('2022-05-22', 2 )
,('2022-05-21', 3 )
,('2022-05-20', 4 )
,('2022-05-19', 5 )
,('2022-05-18', 6 )
,('2022-05-17', 7 )
,('2022-05-16', 8 )
,('2022-05-15', 9 )
,('2022-05-14', 10 )
,('2022-05-13', 11 )
,('2022-05-12', 12 )
,('2022-05-11', 13 )
,('2022-05-10', 14 )
,('2022-05-09', 15 )
,('2022-05-08', 16 )
,('2022-05-07', 17 )
,('2022-05-06', 18 );
;WITH Cal(n) AS
(
SELECT 0 UNION ALL SELECT n + 1 FROM Cal
WHERE n < DATEDIFF(DAY, #StartDate, #EndDate)
),
FnlDt(d,weeknum) AS
(
SELECT DATEADD(DAY, n, #StartDate),datepart(dw, DATEADD(DAY, n, #StartDate)) as weeknum FROM Cal
)
SELECT t.XCHG_DATE,t.USD_KRW
from FnlDt as c
INNER JOIN #table as t
on t.XCHG_DATE = c.d
where c.weeknum = datepart(dw, getdate()) -- Weekdaynumber today
XCHG_DATE
USD_KRW
2022-05-23
1
2022-05-16
8
2022-05-09
15
Sub in GETDATE() for the hardcoded value if you always want todays date
SELECT *
FROM CWL_EXCHANGE
WHERE DATEPART(dw, XCHG_DATE) = DATEPART(dw, '20220523')

How to populate missing date row with next date row in sql server?

date value
01-01-2020 2
04-01-2020 3
05-01-2020 5
06-01-2020 7
05-01-2020 9
I want output:
date value
01-01-2020 2
02-01-2020 3
03-01-2020 3
04-01-2020 3
05-01-2020 5
06-01-2020 7
07-01-2020 9
08-01-2020 9
09-01-2020 9
10-01-2020 9
WITH cte1 AS
(
SELECT *,
lead(date) OVER (ORDER BY date) AS NextDate FROM Screen1
)
SELECT
c.Date,
t.value
FROM Calender1 c
JOIN cte1 t ON c.Date
BETWEEN t.date AND ISNULL(DATEADD(day, -1, t.NextDate), t.date)
I have tried this but its duplicating the previous date value.In this query i have used a calender table and joined that table with main table which has missing dates.
First you need to have calendar table. Then you can find the next available value using CROSS APPLY and fill that value.
declare #table table(dateval date, val int)
insert into #table
values
('2020-01-01', 2),
('2020-01-04', 3 ),
('2020-01-05', 5 ),
('2020-01-06', 7 ),
('2020-01-10', 9)
DECLARE #StartDate DATE = '2020-01-01'
DECLARE #EndDate DATE = '2020-01-31'
;WITH Cal(n) AS
(
SELECT 0 UNION ALL SELECT n + 1 FROM Cal
WHERE n < DATEDIFF(DAY, #StartDate, #EndDate)
),
FnlDt(d) AS
(
SELECT DATEADD(DAY, n, #StartDate) FROM Cal
)
SELECT f.d, t.val
FROM FnlDt as f
CROSS APPLY
(select top 1 val from
#table as t
where t.dateval >= f.d
order by t.dateval) as t(val)
d
val
2020-01-01
2
2020-01-02
3
2020-01-03
3
2020-01-04
3
2020-01-05
5
2020-01-06
7
2020-01-07
9
2020-01-08
9
2020-01-09
9
2020-01-10
9

SQL Server : Gap / Island, datetime, contiguous block 365 day block

I have a table that looks like this:-
tblMeterReadings
id meter period_start period_end amount
1 1 2014-01-01 00:00 2014-01-01 00:29:59 100.3
2 1 2014-01-01 00:30 2014-01-01 00:59:59 50.5
3 1 2014-01-01 01:00 2014-01-01 01:29:59 70.7
4 1 2014-01-01 01:30 2014-01-01 01:59:59 900.1
5 1 2014-01-01 02:00 2014-01-01 02:29:59 400.0
6 1 2014-01-01 02:30 2014-01-01 02:59:59 200.3
7 1 2014-01-01 03:00 2014-01-01 03:29:59 100.8
8 1 2014-01-01 03:30 2014-01-01 03:59:59 140.3
This is a tiny "contiguous block" from '2014-01-01 00:00' to '2014-01-01 3:59:59'.
In the real table there are "contiguous blocks" of years in length.
I need to find the the period_start and period_end of the most recent CONTINUOUS 365 COMPLETE DAYs (fileterd by meter column).
When I say COMPLETE DAYs I mean a day that has entries spanning 00:00 to 23:59.
When I say CONTINUOUS I mean there must be no days missing.
I would like to select all the rows that make up this block of CONTINUOUS COMPLETE DAYs.
I also need an output like:
block_start block_end total_amount_for_block
2013-02-26 00:00 2014-02-26 23:59:59 1034234.5
This is beyond me, so if someone can solve... I will be very impressed.
Since your granularity is 1 second, you need to expand your periods into all the date/times between the start and end at 1 second intervals. To do this you need to cross join with a numbers table (The numbers table is generated on the fly by ranking object ids from an arbitrary system view, I have limited it to TOP 86400 since this is the number of seconds in a day, and you have stated your time periods never span more than one day):
WITH Numbers AS
( SELECT TOP (86400)
Number = ROW_NUMBER() OVER(ORDER BY a.object_id) - 1
FROM sys.all_objects a
CROSS JOIN sys.all_objects b
ORDER BY a.object_id
)
SELECT r.ID, r.meter, dt.[DateTime]
FROM tblMeterReadings r
CROSS JOIN Numbers n
OUTER APPLY
( SELECT [DateTime] = DATEADD(SECOND, n.Number, r.period_start)
) dt
WHERE dt.[DateTime] <= r.Period_End;
You then have your continuous range in which to perform the normal gaps and islands grouping:
WITH Numbers AS
( SELECT TOP (86400)
Number = ROW_NUMBER() OVER(ORDER BY a.object_id) - 1
FROM sys.all_objects a
CROSS JOIN sys.all_objects b
ORDER BY a.object_id
), Grouped AS
( SELECT r.meter,
Amount = CASE WHEN Number = 1 THEN r.Amount ELSE 0 END,
dt.[DateTime],
GroupingSet = DATEADD(SECOND,
-DENSE_RANK() OVER(PARTITION BY r.Meter
ORDER BY dt.[DateTime]),
dt.[DateTime])
FROM tblMeterReadings r
CROSS JOIN Numbers n
OUTER APPLY
( SELECT [DateTime] = DATEADD(SECOND, n.Number, r.period_start)
) dt
WHERE dt.[DateTime] <= r.Period_End
)
SELECT meter,
PeriodStart = MIN([DateTime]),
PeriodEnd = MAX([DateTime]),
Amount = SUM(Amount)
FROM Grouped
GROUP BY meter, GroupingSet
HAVING DATEADD(YEAR, 1, MIN([DateTime])) < MAX([DateTime]);
N.B. Since the join to Number causes amounts to be duplicated, it is necessary to set all duplicates to 0 using CASE WHEN Number = 1 THEN r.Amount ELSE 0 END, i.e only include the amount for the first row for each ID
Removing the Having clause for your sample data will give:
meter | PeriodStart | PeriodEnd | Amount
------+---------------------+---------------------+----------
1 | 2014-01-01 00:00:00 | 2014-01-01 03:59:59 | 1963
Example on SQL Fiddle
You could try this:
Select MIN(period_start) as "block start"
, MAX(period_end) as "block end"
, SUM(amount) as "total amount"
FROM YourTable
GROUP BY datepart(year, period_start)
, datepart(month, period_start)
, datepart(day, period_start)
, datepart(year, period_end)
, datepart(month, period_end)
, datepart(day, period_end)
Having datepart(year, period_start) = datepart(year, period_end)
AND datepart(month, period_start) = datepart(month, period_end)
AND datepart(day, period_start) = datepart(day, period_end)
AND datepart(hour, MIN(period_start)) = 0
AND datepart(minute,MIN(period_start)) = 0
AND datepart(hour, MAX(period_end)) = 23
AND datepart(minute,MIN(period_end)) = 59

Grouping the results of a query with CTEs

I have a CTE based query into which I pass about 2600 4-tuple latitude/longitude values - that have been ID tagged and held in a second table called coordinates. These top left and bottom right latitude / longitude values are passed into the CTE in order to display the amount of requests (hourly) made within those coordinates for given two timestamps).
However, I would like to get the total requests per day within the timestamps given. That is, I want to get the total count of user requests on every specified day. E.g. user opts to see every Wednesday or Wednesday AND Thursday etc. - between 11:55 and 22:04 between dates January 1 and 16, 2012 for every latitude/longitude 4-tuples I pass. The output would basically be like:
coordinates_id | stamp | zcount
1 Jan 4 2012 200 (total requests on Wednesday Jan 4 between 11:55 and 22:04)
1 Jan 11 2012 121 (total requests on Wednesday Jan 11 between 11:55 and 22:04)
2 Jan 4 2012 255 (total requests on Wednesday Jan 4 between 11:55 and 22:04)
2 Jan 11 2012 211 (total requests on Wednesday Jan 11 between 11:55 and 22:04)
.
.
.
How would I do that? My query is as below:
WITH v AS (
SELECT '2012-01-1 11:55:11'::timestamp AS _from -- provide times once
,'2012-01-16 22:02:21'::timestamp AS _to
)
, q AS (
SELECT c.coordinates_id
, date_trunc('hour', t.calltime) AS stamp
, count(*) AS zcount
FROM v
JOIN mytable t ON t.calltime BETWEEN v._from AND v._to
AND (t.calltime::time >= v._from::time AND
t.calltime::time <= v._to::time) AND
(extract(DOW from t.calltime) = 3)
JOIN coordinates c ON (t.lat, t.lon)
BETWEEN (c.bottomrightlat, c.topleftlon)
AND (c.topleftlat, c.bottomrightlon)
GROUP BY c.coordinates_id, date_trunc('hour', t.calltime)
)
, cal AS (
SELECT generate_series('2011-2-2 00:00:00'::timestamp
, '2012-4-1 05:00:00'::timestamp
, '1 hour'::interval) AS stamp
FROM v
)
SELECT q.coordinates_id, cal.stamp, COALESCE (q.zcount, 0) AS zcount
FROM v, cal
LEFT JOIN q USING (stamp)
WHERE (extract(hour from cal.stamp) >= extract(hour from v._from) AND
extract(hour from cal.stamp) <= extract(hour from v._to)) AND
(extract(DOW from cal.stamp) = 3)
AND cal.stamp >= v._from AND cal.stamp <= v._to
GROUP BY q.coordinates_id, cal.stamp, q.zcount
ORDER BY q.coordinates_id ASC, stamp ASC;
And the sample result it yields is like this:
coordinates_id | stamp | zcount
1 2012-01-04 16:00:00 1
1 2012-01-04 19:00:00 1
1 2012-01-11 14:00:00 1
1 2012-01-11 17:00:00 1
1 2012-01-11 19:00:00 1
2 2012-01-04 16:00:00 1
So, as I mentioned above, I would like to see this as
coordinates_id | stamp | zcount
1 2012-01-04 2
1 2012-01-11 3
2 2012-01-04 1
Change your final SELECT to:
SELECT q.coordinates_id, cal.stamp::date, sum(q.zcount) AS zcount
FROM v, cal
LEFT JOIN q USING (stamp)
WHERE extract(hour from cal.stamp) BETWEEN extract(hour from v._from)
AND extract(hour from v._to)
AND extract(DOW from cal.stamp) = 3
AND cal.stamp >= v._from
AND cal.stamp <= v._to
GROUP BY 1,2
ORDER BY 1,2;
The crucial part it to cast cal.stamp to date: cal.stamp::date.
That, and sum(q.zcount).