How to group by period of time? - sql

I have a table with messages and I need to find chats where were two or more messages in period of 10 seconds.
table
id message_id time
1 1 13:09:00
1 2 13:09:01
1 3 13:09:50
2 1 15:18:00
2 2 15:20:00
3 1 15:00:00
3 2 15:10:00
3 3 15:10:10
So the result looks like
id
1
3
I can't come up with the idea how to group by a period or maybe it can be done other way?
select id
from t
group by id, ?
having count(message_id) > 1

You can use a self-join:
with add_ymd(id, mid, dt) as (
select id, message_id, (date(now())||' '||time)::timestamp from messages
),
tm_counts as (
select t2.id, max(t2.n) + 1 tm from (
select t.id, t.mid, sum(case when extract(epoch from t.dt - t1.dt) <= 10 then 1 end) n
from add_ymd t join add_ymd t1 on t.dt > t1.dt group by t.id, t.mid) t2
where t2.n is not null group by t2.id
)
select id from tm_counts where tm > 1
Output:
id
---
1
3

Related

How to get the next non-zero value in table partitioned by id?

Here is a subset of my table:
id
date
value
1
01/01/2022
5
1
02/02/2022
0
1
03/01/2022
0
1
04/02/2022
10
2
01/04/2022
5
2
02/04/2022
3
2
03/04/2022
0
2
04/04/2022
10
Where there are 0s in the value field, i would like to replace them with the non-zero value that occurs after the sequence of 0s are over, partitioned by id.
I have tried to use LAG but im really struggling as it takes the value above the current value in the table.
Any help will be appreciated.
Transformed table to look like
id
date
value
1
01/01/2022
5
1
02/02/2022
10
1
03/01/2022
10
1
04/02/2022
10
2
01/04/2022
5
2
02/04/2022
3
2
03/04/2022
10
2
04/04/2022
10
you can use cross apply;
select T1.id, T1.date, CASE WHEN T1.value = 0 THEN X.value ELSE T1.value END value from TestTable T1
OUTER APPLY (SELECT TOP 1 * FROM TestTable T2
WHERE T1.id = T2.id AND T2.date > T1.date
AND T2.value > 0
ORDER BY T2.date) X
sqlfiddle
Assuming by replace them you mean to update the table, simplest way would be a correlated subquery:
update t set value = (
select top(1) value
from t t2
where t2.id = t.id
and t2.value > 0
and t2.date > t.date
order by t2.date
)
where t.value = 0;
We group every 0 with the first value after it that's not 0 and then we use max() over() to replace the 0s in the group.
select id
,date
,max(value) over(partition by id, grp) as value
from
(
select *
,count(case when value != 0 then 1 end) over(partition by id order by date desc) as grp
from t
) t
order by id, date
id
date
value
1
2022-01-01
5
1
2022-02-02
10
1
2022-03-01
10
1
2022-04-02
10
2
2022-01-04
5
2
2022-02-04
3
2
2022-03-04
10
2
2022-04-04
10
Fiddle
You can do it using outer apply:
select
d.id, d.date_,
case when d.value != 0 then d.value else nz.value end as value
from data d
outer apply (
select min(value) as value
from data dd
where dd.id = d.id
and dd.date_ > d.date_
and dd.value <> 0
) nz
You can test on this db<>fiddle

How to join and count with multiple condition in oracle

i have 2 tables :
table1(id, name)
1233 AAA
3424 BBB
4345 CCC
4342 DDD
1243 RRR
3453 GGG
table2(id,date,status)
1233 01/07/19 1
3424 01/07/19 1
4342 01/07/19 2
1243 01/07/19 1
4342 01/07/19 1
4345 02/07/19 2
1243 02/07/19 1
1233 02/07/19 1
4345 03/07/19 1
4342 03/07/19 2
1233 03/07/19 1
4342 04/07/19 2
4345 04/07/19 2
4342 04/07/19 1
1243 04/07/19 2
15 ROW
i have tried this code
SELECT rn.id, name, NVL(cnt, 0) jum
FROM table1 rn
LEFT JOIN (SELECT id, COUNT(id) AS cnt
FROM VIEW_AKTIFITAS
WHERE extract(year from date)=2019
AND extract(month from date)=7
GROUP BY id,extract(month from date) n ON n.id= rn.id
i want to get this result
how to count the status entry in table 2
RESULT
ID COUNT_STATUS_1 COUNT_STATUS_2 TOTAL_COUNT
1233 2 0 2
3424 2 0 2
4345 1 2 3
4342 2 3 5
1243 2 1 3
3453 0 0 0
please help me to solve this problem.. thanks
Left join table2 to table1 and then use conditional aggregation to get the different counts for the two status.
SELECT t1.id,
count(CASE
WHEN t2.status = 1 THEN
1
END) count_status_1,
count(CASE
WHEN t2.status = 2 THEN
1
END) count_status_2,
count(t2.status) total_count
FROM table1 t1
LEFT JOIN table2 t2
ON t2.id = t1.id;
In Oracle 11.1 and higher, you can use the PIVOT operator to do the aggregation. Something like this:
select id, name, count_status_1, count_status_2,
count_status_1 + count_status_2 as total_count
from (select t1.id, t1.name, t2.status from table1 t1 left join table2 t2
on t1.id = t2.id)
pivot (count(*) for status in (1 as count_status_1, 2 as count_status_2))
order by id -- if needed
;
Based on your sample code, you seem to want:
SELECT rn.id, rn.name, NVL(cnt, 0) jum,
SUM(CASE WHEN a.status = 1 THEN 1 ELSE 0 END) as status_1,
SUM(CASE WHEN a.status = 1 THEN 1 ELSE 0 END) as status_2,
COUNT(a.status) as total
FROM table1 rn LEFT JOIN
VIEW_AKTIFITAS a
ON a.id = rn.id AND
a.date >= DATE '2019-07-01' AND
a.date < DATE '2019-08-01'
GROUP BY rn.id, rn.name;
You do not need a subquery for the LEFT JOIN. The date comparisons are better done using direct date comparisons. Oracle allows you to use ISO-standard date formats with the DATE keyword.
this is my final code
SELECT t1.nip, t1.nama_pegawai,
count(CASE
WHEN t2.status = 1
and t2.tahun=2019
and t2.bulan=7
THEN
1
END) count_status_1,
count(CASE
WHEN t2.status = 2
and t2.tahun=2019
and t2.bulan=7
THEN
1
END) count_status_2,
count(CASE
WHEN t2.tahun=2019
and t2.bulan=7
THEN 1 END
) total_count
FROM VIEW_PEGAWAI t1
LEFT JOIN VIEW_AKTIFITAS t2
ON t2.nip = t1.nip
where t1.opd_id=14
and T1.JENIS_PEG<>3
group by t1.nip,t1.nama_pegawai

SQL: How do I display all records per unique id, but not the first record ever recorded in SQL

Example:
id Pricemoney time/date
1 100 01/20/2017
1 10 01/21/2017
1 1000 01/21/20147
2 10 01/23/2017
2 100 01/24/2017
3 1000 01/19/2017
3 100 01/22/2017
3 10 01/24/2017
I want to run a SQL query where I can display all the Id and it's pricemoney BUT NOT include the first record (based on time/date) per unique
Just to clarify what I do not want to be displayed
userid Pricemoney issuedate
1 100 01/20/2017 -- not included
1 10 01/21/2017
1 1000 01/21/20147
2 10 01/23/2017 --- not inlcuded
2 100 01/24/2017
3 1000 01/19/2017 -- not included
3 100 01/22/2017
3 10 01/24/2017
Expected result:
id Pricemoney time/date
1 10 01/21/2017
1 1000 01/21/20147
2 100 01/24/2017
3 100 01/22/2017
3 10 01/24/2017
You can use row_number():
select t.*
from (select t.*,
row_number() over (partition by id order by time_date asc) as seqnum
from <tablename> t
) t
where seqnum > 1;
If you want to keep single rows, you can do:
select t.*
from (select t.*,
row_number() over (partition by id order by time_date asc) as seqnum,
count(*) over (partition by id) as cnt
from <tablename> t
) t
where seqnum > 1 and cnt > 1;
You may use EXISTS
select t1.*
from data t1
where exists (
select 1
from data t2
where t1.id = t2.id and t2.time_date < t1.time_date
)
you can try this :
select data1.id,data1.Date,data1.Pricemoney from data1
left join (
select id ,min(Date) date from data1
group by id
) as t
on data1.date= t.date and t.id = data1.id
where t.id is null
group by data1.id,data1.Date,data1.Pricemoney
above query not duplicated records also ignore, if want
not duplicated records then use having count(id) > 1 in left query e,g.
select data1.id,data1.Date,data1.Pricemoney from data1
left join (
select id ,min(Date) date from data1
group by id
having COUNT(id) > 1
) as t
on data1.date= t.date and t.id = data1.id
where t.id is null
group by data1.id,data1.Date,data1.Pricemoney

SQL MIN Datetime based on first occuranceof a value in another column

This is what I have
ID Name DateTime Value Group
1 Mark 1/1/2010 0 1
2 Mark 1/2/2010 1 1
3 Mark 1/3/2010 0 1
4 Mark 1/4/2010 0 2
40 Mark 1/5/2010 1 2
5 Mark 1/9/2010 1 2
6 Mark 1/6/2010 1 2
7 Kelly 1/1/2010 0 3
8 Kelly 1/2/2010 1 3
9 Kelly 1/3/2010 1 3
10 Nancy 1/4/2010 0 4
11 Nancy 1/5/2010 0 4
12 Nancy 1/6/2010 1 5
13 Nancy 1/7/2010 0 5
What I want is to get the rows per "name" per "group" with minimum datetime after the value becomes 1. From the above example, I would need to get
3 Mark 1/3/2010 0 1
6 Mark 1/6/2010 1 2
9 Kelly 1/3/2010 1 3
13 Nancy 1/7/2010 0 5
Based on the description of your rules, I believe the output will actually be a bit different since 2010-01-05 was the first DateTime where the Value = 1 for Group 2 for Mark.
ID Name DateTime Value Group
3 Mark 2010-01-03 0 1
6 Mark 2010-01-06 1 2
9 Kelly 2010-01-03 1 3
13 Nancy 2010-01-07 0 5
The below code will work as demonstrated in this SQLFiddle.
SELECT sub.ID
, sub.Name
, sub.[DateTime]
, sub.Value
, sub.[Group]
FROM
(SELECT t.ID
, t.Name
, t.[DateTime]
, t.Value
, t.[Group]
, SequentialOrder = ROW_NUMBER() OVER
(PARTITION BY t.Name, t.[Group]
ORDER BY t.[DateTime])
FROM Test t
JOIN
(SELECT Name
, [Group]
, MinimumDateTime = MIN([DateTime])
FROM Test
WHERE Value = 1
GROUP BY Name
, [Group]) mint
ON t.Name = mint.Name
AND t.[Group] = mint.[Group]
WHERE t.[DateTime] > mint.MinimumDateTime) sub
WHERE sub.SequentialOrder = 1
ORDER BY ID;
Below is my query and it goes on assumption that records are received in order of their dates
WITH TBL_1 AS
(
SELECT A.*, ROW_NUMBER() OVER(PARTITION BY NAME, GROUP ORDER BY DATE) AS RN
FROM TABLE
WHERE (NAME, GROUP) IN
(SELECT NAME, GROUP FROM TABLE WHERE VALUE = 1)
),
TBL_2 AS
(
SELECT * FROM TBL_1 WHERE VALUE = 1
),
TBL_3 AS
(
SELECT A.*
FROM TBL_1 AS A
INNER JOIN TBL_2 AS B
ON B.NAME = A.NAME
AND B.GROUP = A.GROUP
AND A.RN > B.RN
)
SELECT *
FROM TBL_3
WHERE (NAME, GROUP, DATE) IN
(SELECT NAME, GROUP, MIN(DATE) FROM TBL_3 GROUP BY NAME, GROUP)
In SQL Server 2012 you can do this:
SELECT * FROM (
SELECT DISTINCT
ID,
Name,
DateTime,
Value,
Gr,
LAG(ID) OVER (PARTITION BY Name, Gr ORDER BY DateTime) F
FROM (
SELECT
ID,
Name,
DateTime,
Value,
Gr,
CASE WHEN LAG(Value) OVER (PARTITION BY Name, Gr ORDER BY DateTime) = 1 THEN 1 ELSE 0 END F
FROM
T
) TT
WHERE F = 1
) TT WHERE F IS NULL
ORDER BY Gr, Name, DateTime
Fiddle: http://www.sqlfiddle.com/#!6/5a0fa2/19
using window functions:
with cte as (
select
*,
row_number() over(partition by [Group], Name order by [DateTime]) as rn,
dense_rank() over(order by [Group], Name) as rnk
from Table1
)
select c1.*
from cte as c1
inner join cte as c2 on c2.rn = c1.rn - 1 and c2.rnk = c1.rnk and c2.Value = 1
where
not exists (select * from cte as c3 where c3.rn <= c1.rn - 2 and c3.rnk = c1.rnk and c3.Value = 1)
or apply:
select t1.*
from Table1 as t1
cross apply (
select top 1 t2.Value, t2.DateTime
from Table1 as t2
where
t2.[Group] = t1.[Group] and t2.Name = t1.Name and
t2.[DateTime] < t1.[DateTime]
order by t2.[Datetime] desc
) as t2
where
t2.Value = 1 and
not exists (
select *
from Table1 as t3
where
t3.[Group] = t1.[Group] and t3.Name = t1.Name and
t3.[DateTime] < t2.[DateTime] and t3.Value = 1
)
sql fiddle demo
update forgot to mention that your output seems to be incorrect - there should id = 6 instead of 5 in second row (see sql fiddle).

Select and aggregate last records base on order

I have different versions of the charges in a table. I want to grab and sum the last charge grouped by Type.
So I want to add 9.87, 9.63, 1.65.
I want the Parent ID , sum(9.87 + 9.63 + 1.65) as the results of this query.
We use MSSQL
ID ORDER CHARGES TYPE PARENT ID
1 1 6.45 1 1
2 2 1.25 1 1
3 3 9.87 1 1
4 1 6.54 2 1
5 2 5.64 2 1
6 3 0.84 2 1
7 4 9.63 2 1
8 1 7.33 3 1
9 2 5.65 3 1
10 3 8.65 3 1
11 4 5.14 3 1
12 5 1.65 3 1
WITH recordsList
AS
(
SELECT Type, Charges,
ROW_NUMBER() OVER (PArtition BY TYPE
ORDER BY [ORDER] DESC) rn
FROM tableName
)
SELECT SUM(Charges) totalCharge
FROM recordsLIst
WHERE rn = 1
SQLFiddle Demo
Use row_number() to identify the rows to be summed, and then sum them:
select SUM(charges)
from (select t.*,
ROW_NUMBER() over (PARTITION by type order by id desc) as seqnum
from t
) t
where seqnum = 1
Alternatively you could use a window aggregate MAX():
SELECT SUM(Charges)
FROM (
SELECT
[ORDER],
Charges,
MaxOrder = MAX([ORDER]) OVER (PARTITION BY [TYPE])
FROM atable
) s
WHERE [ORDER] = MaxOrder
;
SELECT t.PARENT_ID, SUM(t.CHARGES)
FROM dbo.test73 t
WHERE EXISTS (
SELECT 1
FROM dbo.test73
WHERE [TYPE] = t.[TYPE]
HAVING MAX([ORDER]) = t.[ORDER]
)
GROUP BY t.PARENT_ID
Demo on SQLFiddle