So I am running into an issue and for the life of me i can't figure out how to get the data I am looking for.
What I am trying to do is get a collection of data between a certain Date/Time, the way the database was set up the date and time is in different columns and the way its formatted is yyyyMMDD for the date and hhmmss for the time. (Not sure if this would matter but i just want to make sure i explain everything) The Data is located in an old DB2 server, I am using a linked server in SSMS to pull the data over. The part of the query I am having trouble with is
SELECT *
FROM WM370BASD.PRTRAN00 A
WHERE
(
A.PRTXTP = '300'
AND A.PRTXCD = '003'
AND A.PREDDT >= 20210901 --BEGIN DATE
AND A.PREDDT <= 20210901 --END DATE
AND A.PREDTI >= 060000 --BEGIN TIME
AND A.PREDTI <= 140000 --END TIME
)
OR
( A.PRTXTP = '200'
AND A.PRTXCD = '001'
AND A.PREDDT >= 20210901 --BEGIN DATE
AND A.PREDDT <= 20210901 --END DATE
AND A.PREDTI >= 060000 --BEGIN TIME
AND A.PREDTI <= 140000 --END TIME
AND A.PRMNOP = 'Dir Putaway'
)
OR
( A.PRTXTP = '200'
AND A.PRTXCD = '001'
AND A.PREDDT >= 20210901 --BEGIN DATE
AND A.PREDDT <= 20210901 --END DATE
AND A.PREDTI >= 060000 --BEGIN TIME
AND A.PREDTI <= 140000 --END TIME
AND A.PRMNOP = 'Locate Cases'
)
OR
( A.PRTXTP = '200'
AND A.PRTXCD = '001'
AND A.PREDDT >= 20210901 --BEGIN DATE
AND A.PREDDT <= 20210901 --END DATE
AND A.PREDTI >= 060000 --BEGIN TIME
AND A.PREDTI <= 140000 --END TIME
AND A.PRMNOP = 'SKU Putaway'
)
OR
( A.PRTXTP = '200'
AND A.PRTXCD = '002'
AND A.PREDDT >= 20210901 --BEGIN DATE
AND A.PREDDT <= 20210901 --END DATE
AND A.PREDTI >= 060000 --BEGIN TIME
AND A.PREDTI <= 140000 --END TIME
AND A.PRMNOP = 'Pull Cases-Bulk'
)
EDIT --Added result set
PREDTI PREDDT
60001 20210901
60005 20210901
60010 20210901
60012 20210901
60012 20210901
60013 20210901
60024 20210901
60025 20210901
60032 20210901
60042 20210901
135950 20210901
135947 20210901
135938 20210901
135937 20210901
135934 20210901
135928 20210901
135923 20210901
135923 20210901
135921 20210901
135918 20210901
It works fine as long as the day is the same day, However if I would span it across a day, it returns nothing....
AND A.PREDDT >= 20210821 --BEGIN DATE
AND A.PREDDT <= 20210901 --END DATE
AND A.PREDTI >= 140000 --BEGIN TIME
AND A.PREDTI <= 030000 --END TIME
I figure whatever the issue is is small and stupid and i am just overlooking it, i would appreciate a fresh set of eyes!
The following condition will ALWAYS be false because a number cannot be less than 30000 and greater than 140000 at the same time.
AND A.PREDTI >= 140000 --BEGIN TIME
AND A.PREDTI <= 030000 --END TIME
Making it as follows:
AND ( A.PREDTI >= 140000 --BEGIN TIME
OR A.PREDTI <= 030000 ) --END TIME
Using parenthesis because AND takes precedence over OR and using OR instead of AND. Now it should return results sometimes, but without understanding your logic or intent, I can't say if it is correct or not.
I recommend using DateTime type instead of integers as it is created specifically for that purpose.
You would run into other issues if you use integers the way you do.
Edit:
If I understand what you want correctly, you want from 21/8 to 1/9 and the time be between 14:00 and 3:00 (the next day).
So on 21/8 the time must be after 14:00
On 22/8 to 1/9 the time must be before 3:00 or after 14:00
On 2/9 (next day) the time must be before 3:00
This can be expressed with the following expression:
AND (
A.PREDDT >= 20210822 AND A.PREDDT <= 20210901
AND (A.PREDTI >= 140000 OR A.PREDTI <= 030000)
OR A.PREDDT = 20210902 AND A.PREDTI <= 030000
OR A.PREDDT = 20210821 AND A.PREDTI >= 140000
)
Related
I have a query below to query max and min of day interval in a range of time ( current_date - 2 to current_date - 1). Now, I need to query dayshift and extra shift separately ( dayshift from 5am to 3pm, extra shift will be the remains).
select sum(gap) from (
select to_char(time_stamp, 'yyyy/mm/dd') as day,
EXTRACT(EPOCH FROM (max(time_stamp) - min(time_stamp))) /3600 as gap
from group_table_debarker
where time_stamp >= (current_date - 2)
and time_stamp <= (current_date - 1)
and to_char(time_stamp, 'hh:mi') > '03:00' and to_char(time_stamp, 'hh:mi') < '15:00'
group by to_char(time_stamp, 'yyyy/mm/dd')
) as xxx
select sum(gap) from (
select to_char(time_stamp, 'yyyy/mm/dd') as day,
EXTRACT(EPOCH FROM (max(time_stamp) - min(time_stamp))) /3600 as gap
from group_table_debarker
where time_stamp >= (current_date - 2)
and time_stamp <= (current_date - 1)
and to_char(time_stamp, 'hh:mi') > '03:00' and to_char(time_stamp, 'hh:mi') < '15:00'
group by to_char(time_stamp, 'yyyy/mm/dd')
) as xxx
I've tried this but result wasn't expected
This is my current implementation
SELECT
date_trunc('month', do_date::date)::date as starting_of_the_month,
(date_trunc('month', do_date::date) + interval '1 month' - interval '1 day')::date as ending_of_the_month,
case when 1 + FLOOR((EXTRACT(DAY FROM do_date) - 1) / 7) = 1
THEN date_trunc('week', do_date)::date || ' - ' ||
(date_trunc('week', do_date) + '6 days') ::date end as week1,
case when 1 + FLOOR((EXTRACT(DAY FROM do_date) - 1) / 7) = 2
THEN date_trunc('week', do_date)::date || ' - ' ||
(date_trunc('week', do_date) + '6 days') ::date end as week2,
case when 1 + FLOOR((EXTRACT(DAY FROM do_date) - 1) / 7) = 3
THEN date_trunc('week', do_date)::date || ' - ' ||
(date_trunc('week', do_date) + '6 days') ::date end as week3,
case when 1 + FLOOR((EXTRACT(DAY FROM do_date) - 1) / 7) = 4
THEN date_trunc('week', do_date)::date || ' - ' ||
(date_trunc('week', do_date) + '6 days') ::date end as week4,
case when 1 + FLOOR((EXTRACT(DAY FROM do_date) - 1) / 7) = 5
THEN date_trunc('week', do_date)::date || ' - ' ||
(date_trunc('week', do_date) + '6 days') ::date end as week5
FROM sales_dos
WHERE date_trunc('month', do_date::date)::date >= '2021-02-01' AND date_trunc('month', do_date::date)::date < '2021-02-28'
This is my output for now :
I want the output to display as below :
Week 1 : 2021-02-01 - 2021-02-07
Week 2 : 2021-02-08 - 2021-02-14
Week 3 : 2021-02-15 - 2021-02-21
Week 4 : 2021-02-22 - 2021-02-28
Week 5 : -
Here is another way to do it (example for January 2021).
with
t as (select date_trunc('month', '2021-03-11'::date) as aday), -- any date in Jan-2021
s as
(
select d::date, d::date + 6 ed, extract('isodow' from d) wd
from t, generate_series (aday, aday + interval '1 month - 1 day', interval '1 day') d
)
select format ('Week %s', extract(day from d)::integer / 7 + 1) as weekname, d, ed
from s
where wd = 1;
So what you are looking for is a hybrid ISO with standard Calendar. You are taking the ISO week starting and ending period, but instead of all weeks being exactly 7 days you potentially truncate the 1st and/or last weeks.
The change to need for this is not actually extensive. For initial query returns the in the ISO week begin date instead of the 1st of the month. Then the main query then checks for week 1 and if so produces the 1st of the month. The only twist is determining the ISO week begin date. For this I've just included a function I have had for some time specifically for that. The change to the week_days function are marked --<<<.
create or replace function iso_first_of_week(date_in date)
returns date
language sql
immutable strict
/*
Given a date return the 1st day of the week according to ISO-8601.
I.e. Return the Date if it is Monday otherwise return the preceding Monday
*/
AS $$
with wk_adj(l_days) as (values (array[0,1,2,3,4,5,6]))
select date_in - l_days[ extract (isodow from date_in)::integer ]
from wk_adj;
$$;
create or replace
function week_dates( do_date_in date)
returns table (week_num integer, first_date date, last_date date)
language sql
immutable strict
as $$
with recursive date_list(week_num,first_date,terminate_date) as
( select 1
, iso_first_of_week(do_date_in)::timestamp --<<<
, (date_trunc('month', do_date_in) + interval '1 month' - interval '1 day')::timestamp
union all
select week_num+1, (first_date+interval '7 day'), terminate_date
from date_list
where first_date+interval '6 day' < terminate_date::timestamp
)
select week_num
, case when week_num = 1 --<<<
then date_trunc('month', do_date_in)::date --<<<
else first_date::date --<<<
end --<<<
, case when (first_date+interval '6 day')::date > terminate_date
then terminate_date::date
else (first_date+interval '6 day')::date
end last_date
from date_list;
$$;
---------- Original Reply
You can use a recursive query CTE to get the week number and first date for each week of the month specified. The main query calculates the ending date, shorting the last if necessary. Then wrap that into a SQL function to return the week number and date range for each week. See example.
create or replace
function week_dates( do_date_in date)
returns table (ween_num integer, first_date date, last_date date)
language sql
immutable strict
as $$
with recursive date_list(week_num,first_date,terminate_date) as
( select 1
, date_trunc('month', do_date_in)::timestamp
, (date_trunc('month', do_date_in) + interval '1 month' - interval '1 day')::timestamp
union all
select week_num+1, (first_date+interval '7 day'), terminate_date
from date_list
where first_date+interval '6 day' < terminate_date::timestamp
)
select week_num
, first_date::date
, case when (first_date+interval '6 day')::date > terminate_date
then terminate_date::date
else (first_date+interval '6 day')::date
end last_date
from date_list;
$$;
Response to: "How can i put the output in a single row with week1, week2, week3, week4 and week5". This is essentially the initial output that did not satisfy what you wanted. The term for this type action is PIVOT and is generally understood. It stems from transforming row orientation to column orientation. It is not overly difficult but it is messy.
IMHO this is something that belongs in the presentation layer and is not suitable for SQL. After all you are rearranging the data structure for presentation purposes. Let the database server use its natural format, use the presentation layer to reformat. This allows reuse of the queries instead of rewriting when the presentation is changed or another view of the same data is required.
If you actually want this then just use your initial query, or see the answer from
#Bohemian. However the below shows how this issue can be handled with just SQL (assuming the function week_dates was created).
select week1s
, case when week5e is null
then week4e
else week5e
end "end of month"
, week1s || ' - ' || week1e
, week2s || ' - ' || week2e
, week3s || ' - ' || week3e
, week4s || ' - ' || week4e
, week5s || ' - ' || week5e
from ( select max(case when (week_num=1) then first_date else NULL end) as week1s
, max(case when (week_num=1) then last_date else NULL end) as week1e
, max(case when (week_num=2) then first_date else NULL end) as week2s
, max(case when (week_num=2) then last_date else NULL end) as week2e
, max(case when (week_num=3) then first_date else NULL end) as week3s
, max(case when (week_num=3) then last_date else NULL end) as week3e
, max(case when (week_num=4) then first_date else NULL end) as week4s
, max(case when (week_num=4) then last_date else NULL end) as week4e
, max(case when (week_num=5) then first_date else NULL end) as week5s
, max(case when (week_num=5) then last_date else NULL end) as week5e
from week_dates(current_date)
) w ;
As before I have wrapped the above in a SQL function and provide an example here.
I would first simplify to:
extract(day from do_date)::int / 7 + 1 as week_in_month
then pivot on that using crosstab().
In my query i have some case condition to look over hours in lastoccurrence column then as shown in specific cases it should print in aging_range column either 6 hrs, 12 hrs, 24 hrs, 48 hrs or else > 1 week. Problem is even if as in picture hours = 1 i receive > 1 week. What is wrong with my query?
Query:
CREATE OR REPLACE VIEW schemaA.test
AS SELECT tableA.lastoccurrence,
date_part('hours'::text, timezone('GMT'::text, now()) - tableA.lastoccurrence) AS hours,
date_part('days'::text, timezone('GMT'::text, now()) - tableA.lastoccurrence) AS days,
age(timezone('GMT'::text, now()), tableA.lastoccurrence) AS age,
CASE
WHEN date_part('hours'::text, timezone('GMT'::text, now()) - tableA.lastoccurrence) >= 1::double precision AND date_part('hours'::text, timezone('GMT'::text, now()) - tableA.lastoccurrence) <= 6::double precision AND date_part('days'::text, timezone('GMT'::text, now()) - tableA.lastoccurrence) = 0::double precision THEN '6 hrs'::text
WHEN date_part('hours'::text, timezone('GMT'::text, now()) - tableA.lastoccurrence) >= 7::double precision AND date_part('hours'::text, timezone('GMT'::text, now()) - tableA.lastoccurrence) <= 12::double precision AND date_part('days'::text, timezone('GMT'::text, now()) - tableA.lastoccurrence) = 0::double precision THEN '12 hrs'::text
WHEN date_part('hours'::text, timezone('GMT'::text, now()) - tableA.lastoccurrence) >= 13::double precision AND date_part('hours'::text, timezone('GMT'::text, now()) - tableA.lastoccurrence) <= 24::double precision AND date_part('days'::text, timezone('GMT'::text, now()) - tableA.lastoccurrence) = 0::double precision THEN '24 hrs'::text
WHEN date_part('hours'::text, timezone('GMT'::text, now()) - tableA.lastoccurrence) >= 25::double precision AND date_part('hours'::text, timezone('GMT'::text, now()) - tableA.lastoccurrence) <= 48::double precision AND date_part('days'::text, timezone('GMT'::text, now()) - tableA.lastoccurrence) = 0::double precision THEN '48 hrs'::text
ELSE '> 1 week'::text
END AS aging_range
FROM tmp.tableA;
Result:
The problem is that the timestamps were less than an hour in the past when you ran the query, and your CASE statement doesn't contain a case for “hours = 0”, so the ELSE branch was taken.
The CASE expression is overly complicated. I suggest something like:
SELECT CASE
WHEN age(lastoccurrence AT TIME ZONE 'UTC') > '1 week'
THEN '> 1 week'
WHEN age(lastoccurrence AT TIME ZONE 'UTC') > '2 days'
THEN '1 week'
WHEN age(lastoccurrence AT TIME ZONE 'UTC') > '1 day'
THEN '2 days'
WHEN age(lastoccurrence AT TIME ZONE 'UTC') > '12 hours'
THEN '1 day'
WHEN age(lastoccurrence AT TIME ZONE 'UTC') > '6 hours'
THEN '12 hours'
ELSE '6 hours'
END
FROM tmp.tablea;
I'm not sure where to begin on solving this problem. I need to update a record that is only every 3rd Monday of the month. In Postgres can I query every 2nd or 3rd Monday, or to be a little more abstract every nth day of nth week?
I'm looking for an elegant answer with Postgresql. Right now I have something crude like this:
select d from generate_series(date_trunc('week',timestamp '2015-02-01' + interval '13 days'), timestamp '2015-02-01' + interval '1 month -1 day', interval '14 days') d;
I like to use a calendar table for queries like this one.
To select the third Monday of every month in 2015, I can query a calendar table like this.
select cal_date
from calendar
where year_of_date = 2015
and day_of_week = 'Mon'
and day_of_week_ordinal = 3
order by cal_date;
cal_date
--
2015-01-19
2015-02-16
2015-03-16
2015-04-20
2015-05-18
2015-06-15
2015-07-20
2015-08-17
2015-09-21
2015-10-19
2015-11-16
2015-12-21
Code to create a calendar table. (This is how pgAdminIII presents it through its CREATE SCRIPT menu selection.)
CREATE TABLE calendar
(
cal_date date NOT NULL,
year_of_date integer NOT NULL,
month_of_year integer NOT NULL,
day_of_month integer NOT NULL,
day_of_week character(3) NOT NULL,
day_of_week_ordinal integer NOT NULL,
iso_year integer NOT NULL,
iso_week integer NOT NULL,
cal_quarter integer,
CONSTRAINT calendar_pkey PRIMARY KEY (cal_date),
CONSTRAINT cal_quarter_check CHECK (cal_quarter =
CASE
WHEN date_part('month'::text, cal_date) >= 1::double precision AND date_part('month'::text, cal_date) <= 3::double precision THEN 1
WHEN date_part('month'::text, cal_date) >= 4::double precision AND date_part('month'::text, cal_date) <= 6::double precision THEN 2
WHEN date_part('month'::text, cal_date) >= 7::double precision AND date_part('month'::text, cal_date) <= 9::double precision THEN 3
WHEN date_part('month'::text, cal_date) >= 10::double precision AND date_part('month'::text, cal_date) <= 12::double precision THEN 4
ELSE NULL::integer
END),
CONSTRAINT cal_quarter_range CHECK (cal_quarter >= 1 AND cal_quarter <= 4),
CONSTRAINT calendar_check CHECK (year_of_date::double precision = date_part('year'::text, cal_date)),
CONSTRAINT calendar_check1 CHECK (month_of_year::double precision = date_part('month'::text, cal_date)),
CONSTRAINT calendar_check2 CHECK (day_of_month::double precision = date_part('day'::text, cal_date)),
CONSTRAINT calendar_check3 CHECK (day_of_week::text =
CASE
WHEN date_part('dow'::text, cal_date) = 0::double precision THEN 'Sun'::text
WHEN date_part('dow'::text, cal_date) = 1::double precision THEN 'Mon'::text
WHEN date_part('dow'::text, cal_date) = 2::double precision THEN 'Tue'::text
WHEN date_part('dow'::text, cal_date) = 3::double precision THEN 'Wed'::text
WHEN date_part('dow'::text, cal_date) = 4::double precision THEN 'Thu'::text
WHEN date_part('dow'::text, cal_date) = 5::double precision THEN 'Fri'::text
WHEN date_part('dow'::text, cal_date) = 6::double precision THEN 'Sat'::text
ELSE NULL::text
END),
CONSTRAINT calendar_check4 CHECK (day_of_week_ordinal =
CASE
WHEN day_of_month >= 1 AND day_of_month <= 7 THEN 1
WHEN day_of_month >= 8 AND day_of_month <= 14 THEN 2
WHEN day_of_month >= 15 AND day_of_month <= 21 THEN 3
WHEN day_of_month >= 22 AND day_of_month <= 28 THEN 4
ELSE 5
END),
CONSTRAINT calendar_check5 CHECK (iso_year::double precision = date_part('isoyear'::text, cal_date)),
CONSTRAINT calendar_check6 CHECK (iso_week::double precision = date_part('week'::text, cal_date))
)
WITH (
OIDS=FALSE
);
You also need
GRANT and REVOKE statments--very few people should be allowed to change the content of this kind of table, and
suitable CREATE INDEX statements.
Maybe try this:
SELECT *
, EXTRACT(DAY FROM gen)::int as dom -- DayOfMonth
, CEIL(EXTRACT(DAY FROM gen) / 7)::int as mow -- MonthOfWeek
from (
select generate_series(date_trunc('year', now()), date_trunc('year', now() + interval '1 year'), interval '1 day' )::date as gen
) as src
WHERE extract ('dow' from gen) = 1
AND CEIL(EXTRACT(DAY FROM gen) / 7)::int in (2,3)
Does anyone know of a simple method for solving this?
I have a table which consists of start times for events and the associated durations. I need to be able to split the event durations into thirty minute intervals. So for example if an event starts at 10:45:00 and the duration is 00:17:00 then the returned set should allocate 15 minutes to the 10:30:00 interval and 00:02:00 minutes to the 11:00:00 interval.
I'm sure I can figure out a clumsy approach but would like something a little simpler. This must come up quite often I'd imagine but Google is being unhelpful today.
Thanks,
Steve
You could create a lookup table with just the times (over 24 hours), and join to that table. You would need to rebase the date to that used in the lookup. Then perform a datediff on the upper and lower intervals to work out their durations. Each middle interval would be 30 minutes.
create table #interval_lookup (
from_date datetime,
to_date datetime
)
declare #time datetime
set #time = '00:00:00'
while #time < '2 Jan 1900'
begin
insert into #interval_lookup values (#time, dateadd(minute, 30, #time))
set #time = dateadd(minute, 30, #time)
end
declare #search_from datetime
declare #search_to datetime
set #search_from = '10:45:00'
set #search_to = dateadd(minute, 17, #search_from)
select
from_date as interval,
case
when from_date <= #search_from and
#search_from < to_date and
from_date <= #search_to and
#search_to < to_date
then datediff(minute, #search_from, #search_to)
when from_date <= #search_from and
#search_from < to_date
then datediff(minute, #search_from, to_date)
when from_date <= #search_to and
#search_to < to_date then
datediff(minute, from_date, #search_to)
else 30
end as duration
from
#interval_lookup
where
to_date > #search_from
and from_date <= #search_to
Create TVF that splits single event:
ALTER FUNCTION dbo.TVF_TimeRange_Split_To_Grid
(
#eventStartTime datetime
, #eventDurationMins float
, #intervalMins int
)
RETURNS #retTable table
(
intervalStartTime datetime
,intervalEndTime datetime
,eventDurationInIntervalMins float
)
AS
BEGIN
declare #eventMinuteOfDay int
set #eventMinuteOfDay = datepart(hour,#eventStartTime)*60+datepart(minute,#eventStartTime)
declare #intervalStartMinute int
set #intervalStartMinute = #eventMinuteOfDay - #eventMinuteOfDay % #intervalMins
declare #intervalStartTime datetime
set #intervalStartTime = dateadd(minute,#intervalStartMinute,cast(floor(cast(#eventStartTime as float)) as datetime))
declare #intervalEndTime datetime
set #intervalEndTime = dateadd(minute,#intervalMins,#intervalStartTime)
declare #eventDurationInIntervalMins float
while (#eventDurationMins>0)
begin
set #eventDurationInIntervalMins = cast(#intervalEndTime-#eventStartTime as float)*24*60
if #eventDurationMins<#eventDurationInIntervalMins
set #eventDurationInIntervalMins = #eventDurationMins
insert into #retTable
select #intervalStartTime,#intervalEndTime,#eventDurationInIntervalMins
set #eventDurationMins = #eventDurationMins - #eventDurationInIntervalMins
set #eventStartTime = #intervalEndTime
set #intervalStartTime = #intervalEndTime
set #intervalEndTime = dateadd(minute,#intervalMins,#intervalEndTime)
end
RETURN
END
GO
Test:
select getdate()
select * from dbo.TVF_TimeRange_Split_To_Grid(getdate(),23,30)
Test Result:
2008-10-31 11:28:12.377
intervalStartTime intervalEndTime eventDurationInIntervalMins
----------------------- ----------------------- ---------------------------
2008-10-31 11:00:00.000 2008-10-31 11:30:00.000 1,79372222222222
2008-10-31 11:30:00.000 2008-10-31 12:00:00.000 21,2062777777778
Sample usage:
select input.eventName, result.* from
(
select
'first' as eventName
,cast('2008-10-03 10:45' as datetime) as startTime
,17 as durationMins
union all
select
'second' as eventName
,cast('2008-10-05 11:00' as datetime) as startTime
,17 as durationMins
union all
select
'third' as eventName
,cast('2008-10-05 12:00' as datetime) as startTime
,100 as durationMins
) input
cross apply dbo.TVF_TimeRange_Split_To_Grid(input.startTime,input.durationMins,30) result
Sample usage result:
eventName intervalStartTime intervalEndTime eventDurationInIntervalMins
--------- ----------------------- ----------------------- ---------------------------
first 2008-10-03 10:30:00.000 2008-10-03 11:00:00.000 15
first 2008-10-03 11:00:00.000 2008-10-03 11:30:00.000 2
second 2008-10-05 11:00:00.000 2008-10-05 11:30:00.000 17
third 2008-10-05 12:00:00.000 2008-10-05 12:30:00.000 30
third 2008-10-05 12:30:00.000 2008-10-05 13:00:00.000 30
third 2008-10-05 13:00:00.000 2008-10-05 13:30:00.000 30
third 2008-10-05 13:30:00.000 2008-10-05 14:00:00.000 10
(7 row(s) affected)