select
(age('2012-11-30 00:00:00'::timestamp, '2012-10-31 00:00:00'::timestamp)),
(age('2012-12-31 00:00:00'::timestamp, '2012-10-31 00:00:00'::timestamp)),
(age('2013-01-31 00:00:00'::timestamp, '2012-10-31 00:00:00'::timestamp)),
(age('2013-02-28 00:00:00'::timestamp, '2012-10-31 00:00:00'::timestamp))
which gives the followings:
0 years 0 mons 30 days 0 hours 0 mins 0.00 secs
0 years 2 mons 0 days 0 hours 0 mins 0.00 secs
0 years 3 mons 0 days 0 hours 0 mins 0.00 secs
0 years 3 mons 28 days 0 hours 0 mins 0.00 secs
But I want to have the following month definition , how can I do it?
0 years 1 mons 0 days 0 hours 0 mins 0.00 secs
0 years 2 mons 0 days 0 hours 0 mins 0.00 secs
0 years 3 mons 0 days 0 hours 0 mins 0.00 secs
0 years 4 mons 0 days 0 hours 0 mins 0.00 secs
The expression
age('2012-11-30 00:00:00'::timestamp, '2012-10-31 00:00:00'::timestamp)
gives 30 days. We are expecting 1 month as both values point to last days of month. If we add 1 day to the values we shall get first days of next month and
age('2012-12-01 00:00:00'::timestamp, '2012-11-01 00:00:00'::timestamp)
will give us 1 month as expected. So let us check if we have two last days of month and in this case return age interval of the next days. In other cases we shall return age interval of original values:
create or replace function age_m (t1 timestamp, t2 timestamp)
returns interval language plpgsql immutable
as $$
declare
_t1 timestamp = t1+ interval '1 day';
_t2 timestamp = t2+ interval '1 day';
begin
if extract(day from _t1) = 1 and extract(day from _t2) = 1 then
return age(_t1, _t2);
else
return age(t1, t2);
end if;
end $$;
Some examples:
with my_table(date1, date2) as (
values
('2012-11-30 00:00:00'::timestamp, '2012-10-31 00:00:00'::timestamp),
('2012-12-31 00:00:00'::timestamp, '2012-10-31 00:00:00'::timestamp),
('2013-01-31 00:00:00'::timestamp, '2012-10-31 00:00:00'::timestamp),
('2013-02-28 00:00:00'::timestamp, '2012-10-31 00:00:00'::timestamp)
)
select *, age(date1, date2), age_m(date1, date2)
from my_table
date1 | date2 | age | age_m
---------------------+---------------------+----------------+--------
2012-11-30 00:00:00 | 2012-10-31 00:00:00 | 30 days | 1 mon
2012-12-31 00:00:00 | 2012-10-31 00:00:00 | 2 mons | 2 mons
2013-01-31 00:00:00 | 2012-10-31 00:00:00 | 3 mons | 3 mons
2013-02-28 00:00:00 | 2012-10-31 00:00:00 | 3 mons 28 days | 4 mons
(4 rows)
It seems like you always use the last day of the month. What you are trying to do works flawlessly with the first day of the month. So use that instead. You can always subtract a single day to get the last day of the previous month.
#klin's function is based on that. For dates (instead of timestamps), simplify:
_t1 date = t1 + 1;
_t2 date = t2 + 1;
One can just add / subtract integer values from dates (but not timestamps).
If you want to add "a month", don't just increase the month field, since this can fail like you have experienced. And there is also the wrap around at the end of the year. Add an interval '1 month' instead.
SELECT (mydate + interval '1 month')::date AS mydate_next_month;
I cast back to date because the result of date + interval is a timestamp.
This "rounds down" automatically, if the last day of the next month is before the day in the original date. Note that it does not "round up" in the opposite case. If you want that, operate with the first of the month instead as explained above.
SQL Fiddle.
This is a modified version of the time rounding function located on PostgreSQL's official wiki:
CREATE OR REPLACE FUNCTION interval_round(base_interval INTERVAL, round_interval INTERVAL) RETURNS INTERVAL AS $BODY$
SELECT justify_interval((EXTRACT(epoch FROM $1)::INTEGER + EXTRACT(epoch FROM $2)::INTEGER / 2)
/ EXTRACT(epoch FROM $2)::INTEGER * EXTRACT(epoch FROM $2)::INTEGER * INTERVAL '1 second');
$BODY$ LANGUAGE SQL STABLE;
You can call it with another interval to round to, f.ex.
SELECT interval_round(age('2013-02-28 00:00:00'::timestamp,
'2012-10-31 00:00:00'::timestamp), '1 month')
will return 4 mons.
From http://www.postgresql.org/docs/8.4/static/functions-datetime.html, can you swap the order of the timestamps?
"Note there can be ambiguity in the months returned by age because different months have a different number of days. PostgreSQL's approach uses the month from the earlier of the two dates when calculating partial months. For example, age('2004-06-01', '2004-04-30') uses April to yield 1 mon 1 day, while using May would yield 1 mon 2 days because May has 31 days, while April has only 30."
Related
I need some help regarding sum of production count for overnight shifts.
The table just contains a timestamp (that is automaticaly generated by SQL server during INSERT), the number of OK produced pieces and the number of NOT OK produced pieces in that given timestamp.
CREATE TABLE [machine1](
[timestamp] [datetime] NOT NULL,
[OK] [int] NOT NULL,
[NOK] [int] NOT NULL
)
ALTER TABLE [machine1] ADD DEFAULT (getdate()) FOR [timestamp]
The table holds values like these (just an example, there are hundreds of lines each day and the time stamps are not fixed like each hour or each 30mins):
timestamp
OK
NOK
2022-08-01 05:30:00.000
15
1
2022-08-01 06:30:00.000
18
3
...
...
...
2022-08-01 21:30:00.000
10
12
2022-08-01 22:30:00.000
0
3
...
...
...
2022-08-01 23:59:00.000
1
2
2022-08-02 00:01:00.000
7
0
...
...
...
2022-08-02 05:30:00.000
12
4
2022-08-02 06:30:00.000
9
3
The production works in shifts like so:
morning shift: 6:00 -> 14:00
afternoon shift: 14:00 -> 22:00
night shift: 22:00 -> 6:00 the next day
I have managed to get sums for the morning and afternoon shifts without issues but I can't figure out how to do the sum for the night shift (I have these SELECTs for each shift stored as a VIEW for easy access).
For the morning shift:
SELECT CAST(timestamp AS date) AS Morning,
SUM(OK) AS SUM_OK,
SUM(NOK) AS SUM_NOK
FROM [machine1]
WHERE DATEPART(hh,timestamp) >= 6 AND DATEPART(hh,timestamp) < 14
GROUP BY CAST(timestamp AS date)
ORDER BY Morning ASC
For the afternoon shift:
SELECT CAST(timestamp AS date) AS Afternoon,
SUM(OK) AS SUM_OK,
SUM(NOK) AS SUM_NOK
FROM [machine1]
WHERE DATEPART(hh,timestamp) >= 14 AND DATEPART(hh,timestamp) < 22
GROUP BY CAST(timestamp AS date)
ORDER BY Afternoon ASC
Since we identify the date of each shift by its start, my idea would be that the result for such SUM of night shift would be
Night
SUM_OK
SUM_NOK
2022-08-01
xxx
xxx
for interval 2022-08-01 22:00:00.000 -> 2022-08-02 05:59:59.999
2022-08-02
xxx
xxx
for interval 2022-08-02 22:00:00.000 -> 2022-08-03 05:59:59.999
2022-08-03
xxx
xxx
for interval 2022-08-03 22:00:00.000 -> 2022-08-04 05:59:59.999
2022-08-04
xxx
xxx
for interval 2022-08-04 22:00:00.000 -> 2022-08-05 05:59:59.999
...
...
...
After few days of trial and error I have probably managed to find the needed solution. Using a subquery I shift all the times in range 00:00:00 -> 05:59:59 to the previous day and then I use that result in same approach as for morning and afternon shift (because now all the production data from night shift are in the same date between 22:00:00 and 23:59:59).
In case anyone needs it in future:
SELECT
CAST(nightShift.shiftedTime AS date) AS Night,
SUM(nightShift.OK) AS SUM_OK,
SUM(nightShift.NOK) AS SUM_NOK
FROM
(SELECT
CASE WHEN (DATEPART(hh, timestamp) < 6 AND DATEPART(hh, timestamp) >= 4) THEN DATEADD(HOUR, -6, timestamp)
WHEN (DATEPART(hh, timestamp) < 4 AND DATEPART(hh, timestamp) >= 2) THEN DATEADD(HOUR, -4, timestamp)
WHEN (DATEPART(hh, timestamp) < 2 AND DATEPART(hh, timestamp) >= 0) THEN DATEADD(HOUR, -2, timestamp)
END AS shiftedTime,
[OK],
[NOK]
FROM [machine1]
WHERE (DATEPART(hh, cas) >= 0 AND DATEPART(hh, cas) < 6)) nightShift
WHERE DATEPART(hh,nightShift.shiftedTime) >= 22
GROUP BY CAST(nightShift.shiftedTime AS date)
ORDER BY Night ASC
PS: If there is anything wrong with this approach, please feel free to correct me as I'm just newbie in SQL. So far this seems to do exactly what I needed.
I have power meter data stored in table MeterData:
create table MeterData (
MeterID VARCHAR2(10), DownloadCycle VARCHAR2(6), DateHour Date,
KWH Number(22,6), KW Number(22,6), KVA Number(22,6), KVAR Number(22,6),
CONSTRAINT UniqueDownload UNIQUE(MeterID, DownloadCycle, DateHour))
The data looks like this:
MeterID
DownloadCycle
DateHour
KWH
KW
KVA
KVAR
2319927
202206
13/06/2022 00:00
0.138
0.552
0.552
0
2500350
202206
13/06/2022 00:15
0.612
2.448
2.916
1.584
2500351
202206
13/06/2022 01:30
0.8
3.2
3.2358
0.48
2500352
202206
13/06/2022 04:00
0.288
1.152
1.44
0.864
2500353
202206
13/06/2022 05:30
0.90808
3.63232
4.32456
0
2500396
202206
13/06/2022 12:00
68.09
272.36
277.101157
51.04
2500446
202206
13/06/2022 18:15
0
0
0
0
2500453
202206
13/06/2022 21:00
2.772
11.088
11.088
0
2500472
202206
13/06/2022 23:30
64.8
259.2
305.788256
162.24
2500490
202206
14/06/2022 00:30
2.4
9.6
9.6
0
2501352
202206
14/06/2022 01:45
11.64
46.56
46.56
0
5187222
202206
14/06/2022 06:30
1.452
5.808
7.392
0
5284288
202206
14/06/2022 11:00
66.792
267.168
267.447334
149.336
5516997
202206
14/06/2022 18:30
0.384
1.536
8.112
0
I need to assign every record in table MeterData to a range of hours stored in table HourlyBlocks, in which I'm using intervals as the starting and ending hours:
create table HourlyBlocks (
HourlyBlock VARCHAR2(6) UNIQUE, BlockStart INTERVAL DAY(1) TO SECOND(0),
BlockEnd INTERVAL DAY(1) TO SECOND(0));
insert into HourlyBlocks values (
'Rest', interval '0 05:00:00' day to second, interval '0 18:00:00' day to second);
insert into HourlyBlocks values (
'Peak', interval '0 18:00:00' day to second, interval '0 23:00:00' day to second);
insert into HourlyBlocks values (
'Valley', interval '0 23:00:00' day to second, interval '1 05:00:00' day to second);
(HourlyBlock 'Valley' begins at 23:00:00 and ends at 05:00:00 of the following day).
To test to which HourlyBlock every record in MeterData belongs, I extract the record's hour, minute and second information as an interval with the following, adding 1 day to the interval if it is less than 05:00:00 and thus belongs to HourlyBlock 'Valley':
select distinct m.MeterID, m.DateHour, m.kwh,
NUMTODSINTERVAL(m.DateHour - trunc(m.DateHour), 'DAY') + case when
NUMTODSINTERVAL(m.DateHour - trunc(m.DateHour), 'DAY') <= interval '00 05:00:00'
day to second then interval '1' day else interval '0' day end as intval,
h.HourlyBlock
from MeterData m, HourlyBlocks h
where (NUMTODSINTERVAL(m.DateHour - trunc(m.DateHour ), 'DAY') > h.BlockStart
and NUMTODSINTERVAL(m.DateHour - trunc(m.DateHour ), 'DAY') + case when
NUMTODSINTERVAL(m.DateHour - trunc(m.DateHour ), 'DAY') <= interval '00 05:00:00'
day to second then interval '1' day else interval '0' day end <= h.BlockEnd)
The HourlyBlock are correctly assigned, except for records where DateHour is between 00:00:00 and 05:00:00!
What am I doing wrong?
The expected output for the sample data provided would be:
|MeterID|DateHour |KWH |intval |HourlyBlock|
|-------|----------------|------|-------------------|-----------|
|2319927|13/06/2022 00:00|0.138 |+00 00:00:00.000000|Valley |
|2500350|13/06/2022 00:15|0.612 |+01 00:15:00.000000|Valley |
|2500351|13/06/2022 01:30|0.8 |+01 01:30:00.000000|Valley |
|2500352|13/06/2022 04:00|0.288 |+01 04:00:00.000000|Valley |
|2500353|13/06/2022 05:30|0.908 |+00 05:30:00.000000|Rest |
|2500396|13/06/2022 12:00|68.09 |+00 12:00:00.000000|Rest |
|2500446|13/06/2022 18:15|0 |+00 18:15:00.000000|Peak |
|2500453|13/06/2022 21:00|2.772 |+00 21:00:00.000000|Peak |
|2500472|13/06/2022 23:30|64.8 |+00 23:30:00.000000|Valley |
|2500490|14/06/2022 00:30|2.4 |+01 00:30:00.000000|Valley |
|2501352|14/06/2022 01:45|11.64 |+01 01:45:00.000000|Valley |
|5187222|14/06/2022 06:30|1.452 |+00 06:30:00.000000|Rest |
|5284288|14/06/2022 11:00|66.792|+00 11:00:00.000000|Rest |
|5516997|14/06/2022 18:30|0.384 |+00 18:30:00.000000|Peak |
(I'm sorry I had to format the output as code. It was the only way around that pesky "Your post appears to contain code that is not properly formatted as code" error.)
I found the fix, and simplified my WHERE clause:
select distinct m.MeterID, m.DateHour, m.kwh,
NUMTODSINTERVAL(m.DateHour - trunc(m.DateHour), 'DAY') + case when
NUMTODSINTERVAL(m.DateHour - trunc(m.DateHour), 'DAY') <= interval '00 05:00:00'
day to second then interval '1' day else interval '0' day end as intval, h.HourlyBlock
from MeterData m, HourlyBlocks h
where NUMTODSINTERVAL(m.DateHour - trunc(m.DateHour), 'DAY') +
case when NUMTODSINTERVAL(m.DateHour - trunc(m.DateHour), 'DAY')
<= interval '00 05:00:00' day to second then interval '1' day else
interval '0' day end between h.BlockStart + interval '1' second and h.BlockStart
So my data looks like this:
DATE TEMPERATURE
2012-01-13 23:15:00 UTC 0
2012-01-14 01:35:00 UTC 5
2012-01-14 02:15:00 UTC 6
2012-01-14 03:15:00 UTC 8
2012-01-14 04:15:00 UTC 0
2012-01-14 04:55:00 UTC 0
2012-01-14 05:15:00 UTC -2
2012-01-14 05:35:00 UTC 0
I am trying to calculate the amount of time a zip code temperature will drop to 0 or below on any given day. On the 13th, it only happens for a very short amount of time so we don't really care. I want to know how to calculate the number of minutes this happens on the 14th, since it looks like a significantly (and consistently) cold day.
I want the query to add two more columns.
The first column added would be the time difference between the rows on a given date. So row 3- row 2=40 mins and row 4-row3=60 mins.
The second column would total the amount of minutes for a whole day the minutes the temperature has dropped to 0 or below. Here row 2-4 would be ignored. From row 5-8, total time that the temperature was 0 or below would be about 90 mins
It should end up looking like this:
DATE TEMPERATURE MINUTES_DIFFERENCE TOTAL_MINUTES
2012-01-13 23:15:00 UTC 0 0 0
2012-01-14 01:35:00 UTC 5 140 0
2012-01-14 02:15:00 UTC 6 40 0
2012-01-14 03:15:00 UTC 8 60 0
2012-01-14 04:15:00 UTC 0 60 60
2012-01-14 04:55:00 UTC 0 30 90
2012-01-14 05:15:00 UTC-2 20 110
2012-01-14 05:35:00 UTC 0 20 130
Use below
select *,
sum(minutes_difference) over(order by date) total_minutes
from (
select *,
ifnull(timestamp_diff(timestamp(date), lag(timestamp(date)) over(order by date), minute), 0) as minutes_difference
from your_table
)
if applied to sample data in your question - output is
Update to answer updated question
select * except(new_grp, grp),
sum(if(temperature > 0, 0, minutes_difference)) over(partition by grp order by date) total_minutes
from (
select *, countif(new_grp) over(order by date) as grp
from (
select *,
ifnull(timestamp_diff(timestamp(date), lag(timestamp(date)) over(order by date), minute), 0) as minutes_difference,
ifnull(((temperature <= 0) and (lag(temperature) over(order by date) > 0)) or
((temperature > 0) and (lag(temperature) over(order by date) <= 0)), true) as new_grp
from your_table
)
)
with output
I want to take the value that falls between the current day (today) and yesterday but only when after 9 am yesterday and before 9 am today. The current day must be only on Tuesday - Friday. But, if the current day is Monday, it will take value from Friday after 9 am to Monday before 9 am.
Samples
+---------+------------------------------+
| ID | registration_started_at |
+---------+------------------------------+
| 1 | 2021-05-13 07:00:00.000 |
| 2 | 2021-05-13 11:00:00.000 |
| 3 | 2021-05-14 08:00:00.000 |
| 4 | 2021-05-14 10:00:00.000 |
| 5 | 2021-05-15 12:00:00.000 |
| 6 | 2021-05-16 13:00:00.000 |
| 7 | 2021-05-17 08:00:00.000 |
| 8 | 2021-05-17 10:00:00.000 |
+---------+------------------------------+
So let say when the current_day (today) is Friday (14 May 2021),
When I run the query it must return
Desired Result 1
+---------+------------------------------+
| ID | registration_started_at |
+---------+------------------------------+
| 2 | 2021-05-13 11:00:00.000 |
| 3 | 2021-05-14 08:00:00.000 |
+---------+------------------------------+
But when current_day (today) is Monday (17 May 2021) it should return
Desired Result 2
+---------+------------------------------+
| ID | registration_started_at |
+---------+------------------------------+
| 4 | 2021-05-14 10:00:00.000 |
| 5 | 2021-05-15 12:00:00.000 |
| 6 | 2021-05-16 13:00:00.000 |
| 7 | 2021-05-17 08:00:00.000 |
+---------+------------------------------+
I only manage to get the desired result 1 with this query and I think this still not correct tho for desired result 2
SELECT ID,
DATETIME(registration_started_at, 'Asia/Jakarta') as registration_started_at
FROM `table`
WHERE
DATETIME_DIFF(CURRENT_DATETIME('Asia/Jakarta'), DATETIME(registration_started_at, 'Asia/Jakarta'), week) = 0
AND DATE(DATETIME_ADD(DATETIME(registration_started_at, 'Asia/Jakarta'), INTERVAL -9 HOUR)) = CURRENT_DATE('Asia/Jakarta') - 1
---------edit 1
Using the Mr. Caius Jard answer
WHERE
(DATETIME(registration_started_at, 'Asia/Jakarta') BETWEEN (
(CASE
WHEN EXTRACT(
DAYOFWEEK
FROM CURRENT_DATE('Asia/Jakarta')
) = 2 -- if Monday
THEN DATETIME_ADD(
CURRENT_DATETIME('Asia/Jakarta'),
INTERVAL -63 HOUR
) -- then 63 hours back from midnight today
ELSE DATETIME_ADD(
CURRENT_DATETIME('Asia/Jakarta'),
INTERVAL -15 HOUR
)
END)
) -- else 15 hours back from midnight today
AND DATETIME_ADD(
CURRENT_DATETIME('Asia/Jakarta'),
INTERVAL 9 HOUR
)) -- 9am today
It returns the 63 hours before today's time for Monday or 15 hours before today's time if not Monday, which is incorrect because if I run the query on 15.00 it only returns value from 00.00 today
I think this captures the logic you want:
WHERE DATETIME(registration_started_at, 'Asia/Jakarta') < DATETIME_ADD(DATETIME(DATE(CURRENT_DATETIME('Asia/Jakarta'))), INTERVAL 9 HOUR) AND
(EXTRACT(DAYOFWEEK, DATE(CURRENT_DATETIME('Asia/Jakarta'))) = 2 AND
DATETIME(registration_started_at, 'Asia/Jakarta') > DATETIME_ADD(DATETIME_ADD(DATETIME(DATE(CURRENT_DATETIME('Asia/Jakarta'))), INTERVAL 9 HOUR), INTERVAL -3 DAY) OR
DATETIME(registration_started_at, 'Asia/Jakarta') > DATETIME_ADD(DATETIME_ADD(DATETIME(DATE(CURRENT_DATETIME('Asia/Jakarta'))), INTERVAL 9 HOUR), INTERVAL -1 DAY)
)
What are the important components of this?
This expressoin:
DATETIME_ADD(DATETIME(DATE(CURRENT_DATETIME('Asia/Jakarta'))), INTERVAL 9 HOUR)
Returns 9:00 on the current date in Jakarata. No matter what, you want registration_started_at before that date/time.
This expression
EXTRACT(DAYOFWEEK, DATE(CURRENT_DATETIME('Asia/Jakarta')))
Returns the current day of the week, with 2 for Monday.
These expressions:
DATETIME_ADD(DATETIME_ADD(DATETIME(DATE(CURRENT_DATETIME('Asia/Jakarta'))), INTERVAL 9 HOUR), INTERVAL -3 DAY)
DATETIME_ADD(DATETIME_ADD(DATETIME(DATE(CURRENT_DATETIME('Asia/Jakarta'))), INTERVAL 9 HOUR), INTERVAL -1 DAY)
Just subtract 1 or 3 days from the current 9:00 datetime.
Perhaps something like:
WHERE
registration_started_at
BETWEEN
CASE WHEN EXTRACT(DAYOFWEEK FROM CURRENT_DATE()) = 2 -- if Monday
THEN DATETIME_ADD(CURRENT_DATE(), INTERVAL -63 HOUR)) -- then 63 hours back from midnight today
ELSE DATETIME_ADD(CURRENT_DATE(), INTERVAL -15 HOUR)) END -- else 15 hours back from midnight today
AND
DATETIME_ADD(CURRENT_DATE(), INTERVAL 9 HOUR)) -- 9am today
Never used bigquery, so it might need some fiddling, but the basic idea is that we ask via case when what the current day is and use it to change how much we go back in time
Using Mr. Caius Jard idea, I am able to make it works. I just need to cast CURRENT_DATE to DATETIME() to take the today's 00:00
WHERE
(DATETIME(registration_started_at, 'Asia/Jakarta') BETWEEN (
(CASE
WHEN EXTRACT(
DAYOFWEEK
FROM CURRENT_DATE('Asia/Jakarta')
) = 2 -- if Monday
THEN DATETIME_ADD(
DATETIME(CURRENT_DATE('Asia/Jakarta')),
INTERVAL -63 HOUR
) -- then 63 hours back from midnight today
ELSE DATETIME_ADD(
DATETIME(CURRENT_DATE('Asia/Jakarta')),
INTERVAL -15 HOUR
)
END)
) -- else 15 hours back from midnight today
AND DATETIME_ADD(
DATETIME(CURRENT_DATE('Asia/Jakarta')),
INTERVAL 9 HOUR
)) -- 9am today
I have the below data.
0:00:00
0:30:00
1:00:00
1:30:00
2:00:00
2:30:00
3:00:00
3:30:00
4:00:00
4:30:00
5:00:00
5:30:00
6:00:00
6:30:00
I can extract the hour the using EXTRACT(HOUR FROM TIMESTAMP) but this will give me 24 hours.
But now I need to some different calculation where I can get numbers from 1-48 based on the time given.
Something like this:
0:00:00 1
0:30:00 2
1:00:00 3
1:30:00 4
2:00:00 5
2:30:00 6
3:00:00 7
3:30:00 8
4:00:00 9
4:30:00 10
6:00:00 13
6:30:00 14
Note the skipped 11 and 12, for the absent values 5:00 and 5:30.
Is there any possibilities that I can get that result in PostgreSQL?
Simply use formula 1 + extract(hour from 2 * tm) - it gives your expected result exactly - obligatory SQLFiddle.
This will give you a double precision result, that you can round to whatever you want:
2 * (EXTRACT(HOUR FROM t) + EXTRACT(MINUTE FROM t) / 60) + 1
EDIT:
Or, as #CraigRinger suggested:
EXTRACT(EPOCH FROM t) / 1800 + 1
For the later, t needs to be TIME, not TIMESTAMP. Use cast if needed.
UPDATE: This will work with INTERVALs too.
SELECT 2 * (EXTRACT(HOUR FROM t) + EXTRACT(MINUTE FROM t) / 60) + 1,
EXTRACT(EPOCH FROM t) / 1800 + 1
FROM (VALUES (time '4:30:00'), (time '7:24:31'), (time '8:15:00')) as foo(t)
-- results:
?column? | ?column?
---------+---------
10 | 10
15.8 | 15.8172222222222
17.5 | 17.5
But as you wrote, there will be no edge cases (when the time cannot be divided with 30 minutes).
select
case
when date_time_field_of_interest::time >= '00:00:00' and date_time_field_of_interest::time < '00:30:00' then 1
when date_time_field_of_interest::time >= '00:30:00' and date_time_field_of_interest::time < '01:00:00' then 2
....
end
from your_table;