Given the following table in PostgreSQL
CREATE TABLE my_table (
time TIMESTAMP NOT NULL,
minutes INTEGER NOT NULL);
I am forming a query to detect when the accumulated value of 'minutes' crosses an hour boundary. For example with the following data in the table:
time | minutes
-------------------------
<some timestamp> | 55
<some timestamp> | 4
I want to know how many minutes remain before we reach 60 (one hour). In the example the answer would be 1 since 55 + 4 + 1 = 60.
Further, I would like to know this at insert time, so if my last insert made the accumulated number of minutes cross an "hour boundary" I would like it to return boolean true.
My naive attempt, without the insert part, looks like this:
SELECT
make_timestamptz(
date_part('year', (SELECT current_timestamp))::int,
date_part('month', (SELECT current_timestamp))::int,
date_part('day', (SELECT current_timestamp))::int,
date_part('hour', (SELECT current_timestamp))::int,
0,
0
) AS current_hour,
SUM(minutes) as sum_minutes
FROM
my_table
WHERE
sum_minutes >= 60
I would then take a row count above 0 to mean we crossed the boundary. But it is hopelessly inelegant, and does not work. Is this even possible? Would be possible to make it somewhat performant?
I am using Timescaledb/PostgreSQL on linux.
Hmmm . . . insert doesn't really return values. But you can use a CTE to do the insert and then sum the values after the insert:
with i as (
insert into my_table ( . . . )
values ( . . . )
returning *
)
select ( coalesce(i.minutes, 0) + coalesce(t.minutes, 0) ) > 60
from (select sum(minutes) as minutes from i) i cross join
(select sum(minutes) as minutes from my_table) t
The INSERT could look like this:
WITH cur_sum AS (
SELECT coalesce(sum(minutes), 0) AS minutes
FROM my_table
WHERE date_trunc('hour', current_timestamp) = date_trunc('hour', time)
)
INSERT INTO my_table (time, minutes)
SELECT current_timestamp, 12 FROM cur_sum
RETURNING cur_sum.minutes + 12 > 60;
This example inserts 12 minutes at the current time.
Related
I have a table in DB2 which contains data such as the following:
completed_timestamp
details
2021-12-19-15.38.10
abcd
2021-12-19-15.39.10
efgh
2021-12-19-15.48.10
ijkl
2021-12-19-15.49.10
mnop
2021-12-19-15.54.10
qrst
I want to be able to count the number of rows in the table for every 10 minutes e.g.
Time
count
2021-12-19-15.40
2
2021-12-19-15.50
2
2021-12-19-16.00
1
completed_timestamp = Timestamp, details = varchar
I've seen this done in other SQL languages but so far have not figured out how to do it with DB2. How would I do that?
You can get the minute from the timestamp, divide it by ten, get the next integer, multiply by ten and add it to the hour as minutes :
WITH TABLE1 (completed_timestamp, details) AS (
VALUES
(timestamp '2021-12-19-15.38.10', 'abcd'),
(timestamp '2021-12-19-15.39.10', 'efgh'),
(timestamp '2021-12-19-15.48.10', 'ijkl'),
(timestamp '2021-12-19-15.49.10', 'mnop'),
(timestamp '2021-12-19-15.54.10', 'qrst')
),
trunc_timestamps (time, details) AS (
SELECT trunc(completed_timestamp, 'HH24') + (ceiling(minute(completed_timestamp) / 10.0) * 10) MINUTES, details FROM table1
)
SELECT trunc_timestamps.time, count(*) FROM trunc_timestamps GROUP BY trunc_timestamps.time
I have a table organized as follows:
id lateAt
1231235 2019/09/14
1242123 2019/09/13
3465345 NULL
5676548 2019/09/28
8986475 2019/09/23
Where lateAt is a timestamp of when a certain loan's payment became late. So, for each current date - I need to look at these numbers daily - there's a certain amount of entries which are late for 0-15, 15-30, 30-45, 45-60, 60-90 and 90+ days.
This is my desired output:
lateGroup Count
0-15 20
15-30 22
30-45 25
45-60 32
60-90 47
90+ 57
This is something I can easily calculate in R, but to get the results back to my BI dashboard I'd have to create a new table in my database, which I don't think is a good practice. What is the SQL-native approach to this problem?
I would define the "late groups" using a range, the join against the number of days:
with groups (grp) as (
values
(int4range(0,15, '[)')),
(int4range(15,30, '[)')),
(int4range(30,45, '[)')),
(int4range(45,60, '[)')),
(int4range(60,90, '[)')),
(int4range(90,null, '[)'))
)
select grp, count(t.user_id)
from groups g
left join the_table t on g.grp #> current_date - t.late_at
group by grp
order by grp;
int4range(0,15, '[)') creates a range from 0 (inclusive) and 15 (exclusive)
Online example: https://rextester.com/QJSN89445
The quick and dirty way to do this in SQL is:
SELECT '0-15' AS lateGroup,
COUNT(*) AS lateGroupCount
FROM my_table t
WHERE (CURRENT_DATE - t.lateAt) >= 0
AND (CURRENT_DATE - t.lateAt) < 15
UNION
SELECT '15-30' AS lateGroup,
COUNT(*) AS lateGroupCount
FROM my_table t
WHERE (CURRENT_DATE - t.lateAt) >= 15
AND (CURRENT_DATE - t.lateAt) < 30
UNION
SELECT '30-45' AS lateGroup,
COUNT(*) AS lateGroupCount
FROM my_table t
WHERE (CURRENT_DATE - t.lateAt) >= 30
AND (CURRENT_DATE - t.lateAt) < 45
-- Etc...
For production code, you would want to do something more like Ross' answer.
You didn't mention which DBMS you're using, but nearly all of them will have a construct known as a "value constructor" like this:
select bins.lateGroup, bins.minVal, bins.maxVal FROM
(VALUES
('0-15',0,15),
('15-30',15.0001,30), -- increase by a small fraction so bins don't overlap
('30-45',30.0001,45),
('45-60',45.0001,60),
('60-90',60.0001,90),
('90-99999',90.0001,99999)
) AS bins(lateGroup,minVal,maxVal)
If your DBMS doesn't have it, then you can probably use UNION ALL:
SELECT '0-15' as lateGroup, 0 as minVal, 15 as maxVal
union all SELECT '15-30',15,30
union all SELECT '30-45',30,45
Then your complete query, with the sample data you provided, would look like this:
--- example from SQL Server 2012 SP1
--- first let's set up some sample data
create table #temp (id int, lateAt datetime);
INSERT #temp (id, lateAt) values
(1231235,'2019-09-14'),
(1242123,'2019-09-13'),
(3465345,NULL),
(5676548,'2019-09-28'),
(8986475,'2019-09-23');
--- here's the actual query
select lateGroup, count(*) as Count
from #temp as T,
(VALUES
('0-15',0,15),
('15-30',15.0001,30), -- increase by a small fraction so bins don't overlap
('30-45',30.0001,45),
('45-60',45.0001,60),
('60-90',60.0001,90),
('90-99999',90.0001,99999)
) AS bins(lateGroup,minVal,maxVal)
) AS bins(lateGroup,minVal,maxVal)
where datediff(day,lateAt,getdate()) between minVal and maxVal
group by lateGroup
order by lateGroup
--- remove our sample data
drop table #temp;
Here's the output:
lateGroup Count
15-30 2
30-45 2
Note: rows with null lateAt are not counted.
I think you can do it all in one clear query :
with cte_lategroup as
(
select *
from (values(0,15,'0-15'),(15,30,'15-30'),(30,45,'30-45')) as t (mini, maxi, designation)
)
select
t2.designation
, count(*)
from test t
left outer join cte_lategroup t2
on current_date - t.lateat >= t2.mini
and current_date - lateat < t2.maxi
group by t2.designation;
With a preset like yours :
create table test
(
id int
, lateAt date
);
insert into test
values (1231235, to_date('2019/09/14', 'yyyy/mm/dd'))
,(1242123, to_date('2019/09/13', 'yyyy/mm/dd'))
,(3465345, null)
,(5676548, to_date('2019/09/28', 'yyyy/mm/dd'))
,(8986475, to_date('2019/09/23', 'yyyy/mm/dd'));
I'm pretty new to SQL and have this problem:
I have a filled table with a date column and other not interesting columns.
date | name | name2
2015-03-20 | peter | pan
2015-03-20 | john | wick
2015-03-18 | harry | potter
What im doing right now is counting everything for a date
select date, count(*)
from testtable
where date >= current date - 10 days
group by date
what i want to do now is counting the resulting lines and only returning them if there are less then 10 resulting lines.
What i tried so far is surrounding the whole query with a temp table and the counting everything which gives me the number of resulting lines (yeah)
with temp_count (date, counter) as
(
select date, count(*)
from testtable
where date >= current date - 10 days
group by date
)
select count(*)
from temp_count
What is still missing the check if the number is smaller then 10.
I was searching in this Forum and came across some "having" structs to use, but that forced me to use a "group by", which i can't.
I was thinking about something like this :
with temp_count (date, counter) as
(
select date, count(*)
from testtable
where date >= current date - 10 days
group by date
)
select *
from temp_count
having count(*) < 10
maybe im too tired to think of an easy solution, but i can't solve this so far
Edit: A picture for clarification since my english is horrible
http://imgur.com/1O6zwoh
I want to see the 2 columned results ONLY IF there are less then 10 rows overall
I think you just need to move your having clause to the inner query so that it is paired with the GROUP BY:
with temp_count (date, counter) as
(
select date, count(*)
from testtable
where date >= current date - 10 days
group by date
having count(*) < 10
)
select *
from temp_count
If what you want is to know whether the total # of records (after grouping), are returned, then you could do this:
with temp_count (date, counter) as
(
select date, counter=count(*)
from testtable
where date >= current date - 10 days
group by date
)
select date, counter
from (
select date, counter, rseq=row_number() over (order by date)
from temp_count
) x
group by date, counter
having max(rseq) >= 10
This will return 0 rows if there are less than 10 total, and will deliver ALL the results if there are 10 or more (you can just get the first 10 rows if needed with this also).
In your temp_count table, you can filter results with the WHERE clause:
with temp_count (date, counter) as
(
select date, count(distinct date)
from testtable
where date >= current date - 10 days
group by date
)
select *
from temp_count
where counter < 10
Something like:
with t(dt, rn, cnt) as (
select dt, row_number() over (order by dt) as rn
, count(1) as cnt
from testtable
where dt >= current date - 10 days
group by dt
)
select dt, cnt
from t where 10 >= (select max(rn) from t);
will do what you want (I think)
I have one Change Report Table which has two columns ChangedTime,FileName
Please consider this table has over 1000 records
Here I need to query all the changes based on following factors
i) Interval (i.e-1mins )
ii) No of files
It means when we have given Interval 1 min and No Of files 10.
If the the no of changed files more than 10 in any of the 1 minute interval, we need to get all the changed files exists in that 1 minute interval
Example:
i) Consider we have 15 changes in the interval 11:52 to 11:53
ii)And consider we have 20 changes in the interval 12:58 to 12:59
Now my expected results would be 35 records.
Thanks in advance.
You need to aggregate by the interval and then do the count. Assuming that an interval starting at time 0 is ok, the following should work:
declare #interval int = 1;
declare #limit int = 10;
select sum(cnt)
from (select count(*) as cnt
from t
group by DATEDIFF(minute, 0, ChangedTime)/#interval
) t
where cnt >= #limit;
If you have another time in mind for when intervals should start, then substitute that for 0.
EDIT:
For your particular query:
select sum(ChangedTime)
from (select count(*) as ChangedTime
from [MyDB].[dbo].[Log_Table.in_PC]
group by DATEDIFF(minute, 0, ChangedTime)/#interval
) t
where ChangedTime >= #limit;
You can't have a three part alias name on a subquery. t will do.
Something like this should work:
You count the number of records using the COUNT() function.
Then you limit the selection with the WHERE clause:
SELECT COUNT(FileName)
FROM "YourTable"
WHERE ChangedTime >= "StartInteval"
AND ChangedTime <= "EndInterval";
Another method that is useful in a where clause is BETWEEN : http://msdn.microsoft.com/en-us/library/ms187922.aspx.
You didn't state which SQL DB you are using so I assume its MSSQL.
select count(*) from (select a.FileName,
b.ChangedTime startTime,
a.ChangedTime endTime,
DATEDIFF ( minute , a.ChangedTime , b.ChangedTime ) timeInterval
from yourtable a, yourtable b
where a.FileName = b.FileName
and a.ChangedTime > b.ChangedTime
and DATEDIFF ( minute , a.ChangedTime , b.ChangedTime ) = 1) temp
group by temp.FileName
Table I'm trying to get analytical result from is voice calls record. Each call (one row) has duration in seconds (just int value, not datetime). I'm trying to get number of records grouped by 15 seconds spans like this:
+-------------------+
|Period | Count |
+-------------------+
| 0-15 | 213421 |
|15-30 | 231123 |
|30-45 | 1234 |
+-------------------+
Starts of period is 0, end is 86400.
I've tried some combinations with setting variables for start and end and then incrementing them by 15, but i'm not very successful, and i can't understand what is going wrong.
Please help out. Thank you!
The following query fits your needs:
SELECT
(duration - duration % 15),
COUNT(*)
FROM
call
GROUP BY
(duration - duration % 15)
ORDER BY
1;
You can also add some string formatting, in case you need the output exactly as you described it:
SELECT
(duration - duration % 15)::text || '-' || (duration - duration % 15 + 15)::text,
COUNT(*)
FROM
call
GROUP BY
(duration - duration % 15)
ORDER BY
1;
In MySQL:
SELECT CONCAT(span * 15, '-', span * 15 + 15), COUNT(*) AS cnt
FROM (
SELECT v.*, FLOOR(period / 15) AS span
FROM voice_calls v
) q
GROUP BY
span
UPDATE:
The solution you posted will work, assuming there will always be more than 5760 rows.
But you better create a dummy rowset of 5760 rows and use it in OUTER JOIN:
CREATE TABLE spans (span INT NOT NULL PRIMARY KEY);
INSERT
INTO spans
VALUES (0),
(1),
...
(5759)
SELECT span * 15, COUNT(*)
FROM spans
LEFT JOIN
calls
ON call.duration >= span * 15
AND call.duration < span * 15 + 15
GROUP BY
span
It will be more efficient and sane, as it can neither underflow (if less than 5760 rows in calls), nor take to much time if there are millions of rows there.
I think a query like this should work; You'll have to pivot the results yourself on the display though (eg, this query gets the results horizontally, you'll have to display them vertically):
SELECT
SUM(CASE WHEN CallLength BETWEEN 0 AND 15 THEN CallLength ELSE 0 END) AS ZeroToFifteen,
SUM(CASE WHEN CallLength BETWEEN 16 AND 30 THEN CallLength ELSE 0 END) AS FifteenToThirty
FROM CallTable
But after re-reading the question, putting case statements up to 86400 is probably out of the question... Oh well :)
This worked at last:
SET #a:=-15, #b:=0;
SELECT t.start_time, t.end_time, count(c.duration)
FROM calls c,
(
SELECT (#a:=#a+15) as start_time, (#b:=#b+15) as end_time
FROM `calls`
GROUP BY
cdr_id
) as t
WHERE c.duration BETWEEN t.start_time and t.end_time
GROUP BY
t.start_time, t.end_time