There are 2 MariaDB (Ver 15.1 Distrib 5.5.64-MariaDB, for Linux (x86_64)) tables:
CREATE TABLE Table1
(`phone` int, `calldate` datetime)
;
INSERT INTO Table1
(`phone`, `calldate`)
VALUES
(123, '2020-01-01 10:00:00'),
(123, '2020-01-01 11:00:00'),
(123, '2020-01-01 12:00:00')
;
CREATE TABLE Table2
(`phone` int, `calldate` datetime)
;
INSERT INTO Table2
(`phone`, `calldate`)
VALUES
( 123, '2020-01-01 09:01:00'),
( 123, '2020-01-01 09:02:00'),
( 123, '2020-01-01 10:15:00'),
( 123, '2020-01-01 10:20:00'),
( 123, '2020-01-01 10:23:00'),
( 123, '2020-01-01 11:05:00'),
( 123, '2020-01-01 11:12:00'),
( 123, '2020-01-01 11:25:00')
;
How to get result as :
The calldate of first record from table1 (2020-01-01 10:00:00) is more later than calldate of two records from table2.
Similarly for second one - the count is 5 (from 09:01:00 to 10:23:00)
But two records from table2 with calldate 09:01:00 and 09:02:00 are already "overlapped" by first record from table1, so result should be 3 instead of 5.
|------+----------------------+-------+
| phone | calldate | count |
|-------+---------------------+-------+
| 123 | 2020-01-01 09:02:00 | 2 |
| 123 | 2020-01-01 10:23:00 | 3 |
| 123 | 2020-01-01 11:25:00 | 3 |
|------+---------------------+|------+|
Also, the calldate in result set should be most last calldate from "overlapped" subset.
You can do this using window functions:
select t1.phone, t1.calldate, count(t2.phone)
from (select t1.*,
lead(calldate) over (partition by phone order by calldate) as next_calldate
from table1 t1
) t1 left join
table2 t2
on t2.phone = t1.phone and
t2.calldate >= t1.calldate and
(t2.calldate < t1.next_calldate or t1.next_calldate is null)
group by t1.phone, t1.calldate;
EDIT:
You can follow the same idea with a correlated subquery:
select t1.phone, t1.calldate, count(t2.phone)
from (select t1.*,
(select min(tt1.calldate)
from table1 tt1
where tt1.calldate > t1.calldate
) as next_calldate
from table1 t1
) t1 left join
table2 t2
on t2.phone = t1.phone and
t2.calldate >= t1.calldate and
(t2.calldate < t1.next_calldate or t1.next_calldate is null)
group by t1.phone, t1.calldate;
This will be even less efficient than the window functions version.
Join the tables and use NOT EXISTS in the ON clause like this:
select t1.phone, t1.calldate, count(t2.calldate) count
from Table1 t1 left join Table2 t2
on t2.phone = t1.phone and t2.calldate < t1.calldate
and not exists (
select 1 from Table1
where calldate < t1.calldate and t2.calldate < calldate
)
group by t1.phone, t1.calldate
See the demo.
Results:
| phone | calldate | count |
| ----- | ------------------- | ----- |
| 123 | 2020-01-01 10:00:00 | 2 |
| 123 | 2020-01-01 11:00:00 | 3 |
| 123 | 2020-01-01 12:00:00 | 3 |
Related
I got two tables with identical structure. From those tables I need to get rows with highest value on rate column where fix_id is the same.
Table1
fix_id | rate | proc | unique_id
2 | 72 | 50 | 23_tab1
3 | 98 | 70 | 24_tab1
4 | 78 | 80 | 25_tab1
table2
fix_id | rate | proc | unique_id
2 | 75 | 999 | 23_tab2
3 | 80 | 179 | 24_tab2
4 | 82 | 898 | 25_tab2
Expected result
fix_id | rate | proc | unique_id
2 | 75 | 999 | 23_tab2
3 | 98 | 70 | 24_tab1
4 | 82 | 898 | 25_tab2
I've tried this...
Select fix_id,proc,unique_id,MAX(rate) rate from
(Select fix_id,proc,unique_id,MAX(rate) rate from table1 group by fix_id
UNION ALL SELECT fix_id,proc,unique_id,MAX(rate) rate from table2 group by fix_id ) group by fix_id
I get the highest values from rate column but the values from other columns are incorrect.
It can be done using CASE statement.
Try this query
select
(case
when T1.rate > T2.rate then T1.fix_id else T2.fix_id
end) as fix_id,
(case
when T1.rate > T2.rate then T1.rate else T2.rate
end) as rate,
(case
when T1.rate > T2.rate then T1.proc else T2.proc
end) as proc,
(case
when T1.rate > T2.rate then T1.unique_id else T2.unique_id
end) as unique_id
from table1 as T1, table2 as T2 where T1.id = T2.id
You can use row_number():
select t.*
from (select fix_id, proc, unique_id, rate,
row_number() over (partition by fix_id order by rate desc) as seqnum
from ((select fix_id, proc, unique_id, rate from table1
) union all
(select fix_id, proc, unique_id, rate from table2
)
) t
) t
where seqnum = 1;
As fix_id is unique in both tables, the answer with CASE statements (https://stackoverflow.com/a/65609931/53341) is likely the fastest (so, I've upvoted that)...
Join once
Compare rates, on each row
Pick which table to read from, on each row
For large numbers of columns, however, it's unwieldy to type all the CASE statements. So, here is a shorter version, though it probably takes twice as long to run...
SELECT t1.*
FROM table1 AS t1 INNER JOIN table2 AS t2 ON t1.fix_id = t2.fix_id
WHERE t1.rate >= t2.rate
UNION ALL
SELECT t2.*
FROM table1 AS t1 INNER JOIN table2 AS t2 ON t1.fix_id = t2.fix_id
WHERE t1.rate < t2.rate
Lets say I have 2 tables like so:
MyTable1:
Name ID Timestamp TestNum Grade
Alex 1101 2020-10-01 12:00:00 1 85
Alex 1101 2020-10-02 13:00:00 2 90
Alex 1101 2020-10-03 8:00:00 3 95
Alex 1101 2020-10-04 10:00:00 4 90
MyTable2:
ID Avg StDev
1101 90 4.08
I am trying to get the row of the first (Timestamp) instance where the grade was X standard deviations away.
ExpectedResults:
Name ID Timestamp TestNum StDevsAway
Alex 1101 2020-10-01 12:00:00 1 -1.23
Alex 1101 2020-10-02 13:00:00 2 0
Alex 1101 2020-10-03 8:00:00 3 1.23
The 4th row should not be returned as its Standard Deviations Away was already found at a previous Timestamp.
I'm still fairly new to MySQL, but this is where I'm at so far:
select a.Name
, a.ID
, a.Timestamp
, a.TestNum
, round( ( a.Grade - b.Avg ) / b.StDev, 2 ) as StDevsAway
from MyTable1 as a
join MyTable2 as b
on a.ID = b.ID
group
by round( ( a.Grade - b.Avg ) / b.StDev, 2 );
I think the question is just about finding "first" row for each id/grade tupe. So (asssuming MySQL 8.0):
select t1.*
from (
select t1.*, row_number() over(partition by id, grade order by timestamp) rn
from mytable1 t1
) t1
where rn = 1
Then, you can bring the second table with a join if you like:
select t1.*, round(t1.grade - t2.avg) / t2.stdev, 2) stdevsaway
from (
select t1.*, row_number() over(partition by id, grade order by timestamp) rn
from mytable1 t1
) t1
inner join mytable2 t2 on t2.id = t1.id
where rn = 1
In earlier versions, you can filter with a subquery:
select t1.*, round(t1.grade - t2.avg) / t2.stdev, 2) stdevsaway
from mytable1 t1
inner join mytable2 t2 on t2.id = t1.id
where t1.timestamp = (
select min(t11.timestamp) from mytable1 t11 where t11.id = t1.id and t11.grade = t1.grade
)
In previous Versin and of Course in mysql8 as well you can do this.
this will exclude every testnum, that gas standard deviation,except the first one, for that user
Schema (MySQL v5.5)
CREATE TABLE MyTable1 (
`Name` VARCHAR(4),
`ID` INTEGER,
`Timestamp` DATETIME,
`TestNum` VARCHAR(7),
`Grade` INTEGER
);
INSERT INTO MyTable1
(`Name`, `ID`, `Timestamp`, `TestNum`, `Grade`)
VALUES
('Alex', '1101', '2020-10-01 12:00:00', '1', '85'),
('Alex', '1101', '2020-10-02 13:00:00', '2', '90'),
('Alex', '1101', '2020-10-03 08:00:00', '3','95'),
('Alex', '1101', '2020-10-04 10:00:00', '4', '90');
CREATE TABLE MyTable2 (
`ID` INTEGER,
`Avg` INTEGER,
`StDev` FLOAT
);
INSERT INTO MyTable2
(`ID`, `Avg`, `StDev`)
VALUES
('1101', '90', '4.08');
Query #1
select
a.Name
, a.ID
, a.Timestamp
, a.TestNum
, round( ( a.Grade - b.Avg ) / b.StDev, 2 ) as StDevsAway
from MyTable1 as a join MyTable2 as b on a.ID = b.ID
WHERE
TestNum NOT IN (SELECT TestNum
FROM MyTable1 c
WHERE c.`ID` = a.`ID`
AND c.`Grade` = b.Avg
AND c.`TestNum`<> (SELECT MIN(TestNum)
FROM MyTable1 d
WHERE d.`ID` = a.`ID`
AND d.`Grade` = b.Avg)
);
| Name | ID | Timestamp | TestNum | StDevsAway |
| ---- | ---- | ------------------- | ------- | ---------- |
| Alex | 1101 | 2020-10-01 12:00:00 | 1 | -1.23 |
| Alex | 1101 | 2020-10-02 13:00:00 | 2 | 0 |
| Alex | 1101 | 2020-10-03 08:00:00 | 3 | 1.23 |
View on DB Fiddle
create table dt
(
id varchar(20),
user_id int,
name varchar(20),
td DATE,
amount float
);
INSERT INTO dt VALUES('blah',1, 'Rodeo', '2018-01-20', 10.12);
INSERT INTO dt VALUES('blahblah',1, 'Rodeo', '2019-01-01', 40.44);
INSERT INTO dt VALUES('sas',2, 'Janice', '2018-02-05', 18.18);
INSERT INTO dt VALUES('dsdcd',3, 'Sam', '2019-01-26', 16.13);
INSERT INTO dt VALUES('sdc',2, 'Janice', '2018-02-01', 12.19);
INSERT INTO dt VALUES('scsc',2, 'Janice', '2017-12-06', 5.10);
+----------+---------+--------+------------+--------+
| id | user_id | name | td | amount |
+----------+---------+--------+------------+--------+
| blah | 1 | Rodeo | 2018-01-20 | 10.12 |
| blahblah | 1 | Rodeo | 2019-01-01 | 40.44 |
| sas | 2 | Janice | 2018-02-05 | 18.18 |
| dsdcd | 3 | Sam | 2019-01-26 | 16.13 |
| sdc | 2 | Janice | 2018-02-01 | 12.19 |
| scsc | 2 | Janice | 2017-12-06 | 5.1 |
+----------+---------+--------+------------+--------+
For the above table how i can get this output. I can achieve this by windowing function but not sure how to do this by correlated subquery. Appreciate any help!
Output
Basically difference of users first transaction amount from their latest transaction amount. If the user has only one transaction then the difference is 0
User_id name amount
1 Rodeo 30.32 [40.44(latest trans) - 10.12 (min trans)]
3 Sam 0
2 Janice 13.08 [18.18 (latest trans) - 5.1 (min trans)]
With 2 subqueries to get the latest and earliest amounts:
select distinct t.user_id, t.name,
(select amount from dt
where user_id = t.user_id
order by td desc limit 1
)
-
(select amount from dt
where user_id = t.user_id
order by td limit 1
) amount
from dt t
See the demo.
Or:
select t.user_id, t.name,
max(t.latest * t.amount) - max(t.earliest * t.amount) amount
from (
select d.user_id, d.name, d.amount,
d.td = g.earliestdate earliest, d.td = g.latestdate latest
from dt d inner join (
select user_id, min(td) earliestdate, max(td) latestdate
from dt
group by user_id
) g on d.user_id = g.user_id and d.td in (earliestdate, latestdate)
) t
group by t.user_id, t.name
See the demo.
Results:
| user_id | name | amount |
| ------- | ------ | ------ |
| 1 | Rodeo | 30.32 |
| 2 | Janice | 13.08 |
| 3 | Sam | 0 |
This is similar to SQL select only rows with max value on a column, but you need to do it twice: once for the earliest row, again for the latest row.
SELECT t1.user_id, t1.name, t1.amount - t2.amount ASA amount
FROM (
SELECT dt1.user_id, dt1.name, dt1.amount
FROM dt AS dt1
JOIN (
SELECT user_id, name, MAX(td) AS maxdate
FROM dt
GROUP BY user_id, name) AS dt2
ON dt1.user_id = dt2.user_id AND dt1.name = dt2.name AND dt1.td = dt2.maxdate
) AS t1
JOIN (
SELECT dt1.user_id, dt1.name, dt1.amount
FROM dt AS dt1
JOIN (
SELECT user_id, name, MIN(td) AS mindate
FROM dt
GROUP BY user_id, name) AS dt2
ON dt1.user_id = dt2.user_id AND dt1.name = dt2.name AND dt1.td = dt2.mindate
) AS t2
ON t1.user_id = t2.user_id AND t1.name = t2.name
Approach using Correlated Subquery:
Query
SELECT user_id,
name,
Round(Coalesce ((SELECT t1.amount
FROM dt t1
WHERE t1.user_id = dt.user_id
ORDER BY t1.td DESC
LIMIT 1) - (SELECT t2.amount
FROM dt t2
WHERE t2.user_id = dt.user_id
ORDER BY t2.td ASC
LIMIT 1), 0), 2) AS amount
FROM dt
GROUP BY user_id,
name;
| user_id | name | amount |
| ------- | ------ | ------ |
| 1 | Rodeo | 30.32 |
| 2 | Janice | 13.08 |
| 3 | Sam | 0 |
View on DB Fiddle
You can try this as well
Select t3.user_id, t3.name, max(t3.new_amount) FROM (
Select t1.user_id, t2.name, (t1.amount - t2.amount) as new_amount
FROM dt t1
INNER JOIN dt t2
ON t1.user_id=t2.user_id
Order by t1.user_id ASC, t1.td DESC, t2.user_id ASC, t2.td ASC
) as t3
group by t3.user_id,t3.name;
Demo
I have table with 3 columns like
NAME DATE_TIME AMOUNT
T1 2012:10:01 12:15:00 200
T1 2012:10:01 12:30:00 300
T1 2012:10:02 01:00:00 400
T1 2012:10:02 01:15:00 500
T1 2012:10:03 04:00:00 600
T1 2012:10:03 05:00:00 700
T2 2013:12:31 22:30:00 3838
T2 2013:12:31 23:00:00 7272
T2 2013:12:31 23:30:00 891
T2 2013:12:31 23:45:00 288
In the table T1 value the maximum interval is 15 mins and for T2 the maximun intervals is 30 Mins ,the output would be
NAME Granularity/max.interval
T1 15 (mins)
T2 30 (mins)
Thanks in advance
Here is a solution with a full demo.
-- SQL needed
SELECT
NAME, Granularity, cnt
FROM
(
SELECT
NAME, Granularity, COUNT(*) cnt
FROM
(
SELECT
*,
IF(#last_name IS NULL OR NAME != #last_name OR DATE(#last_dt) != DATE(DATE_TIME),
0, TIMESTAMPDIFF(MINUTE, #last_dt, DATE_TIME))AS Granularity,
#last_name:=NAME,
#last_dt:=DATE_TIME
FROM t1 JOIN (SELECT #last_dt:=NULL, #last_name:=NULL) v
) t
WHERE t.Granularity > 0
GROUP BY NAME, Granularity
) tt
WHERE
(NAME, cnt) IN
(
SELECT NAME, MAX(cnt) max_cnt
FROM
(
SELECT
NAME, Granularity, COUNT(*) cnt
FROM
(
SELECT
*,
IF(#last_name IS NULL OR NAME != #last_name OR DATE(#last_dt) != DATE(DATE_TIME),
0, TIMESTAMPDIFF(MINUTE, #last_dt, DATE_TIME))AS Granularity,
#last_name:=NAME,
#last_dt:=DATE_TIME
FROM t1 JOIN (SELECT #last_dt:=NULL, #last_name:=NULL) v
) t
WHERE t.Granularity > 0
GROUP BY NAME, Granularity
) ttt
GROUP BY NAME
);
The following is a full demo using the data you provided.
SQL:
-- data
create table t1(NAME char(20), DATE_TIME datetime, AMOUNT int);
insert into t1 values
('T1', '2012:10:01 12:15:00', 200),
('T1', '2012:10:01 12:30:00', 300),
('T1', '2012:10:02 01:00:00', 400),
('T1', '2012:10:02 01:15:00', 500),
('T1', '2012:10:03 04:00:00', 600),
('T1', '2012:10:03 05:00:00', 700),
('T2', '2013:12:31 22:30:00', 3838),
('T2', '2013:12:31 23:00:00', 7272),
('T2', '2013:12:31 23:30:00', 891),
('T2', '2013:12:31 23:45:00', 288);
SELECT * FROM t1;
-- SQL needed
SELECT
NAME, Granularity, cnt
FROM
(
SELECT
NAME, Granularity, COUNT(*) cnt
FROM
(
SELECT
*,
IF(#last_name IS NULL OR NAME != #last_name OR DATE(#last_dt) != DATE(DATE_TIME),
0, TIMESTAMPDIFF(MINUTE, #last_dt, DATE_TIME))AS Granularity,
#last_name:=NAME,
#last_dt:=DATE_TIME
FROM t1 JOIN (SELECT #last_dt:=NULL, #last_name:=NULL) v
) t
WHERE t.Granularity > 0
GROUP BY NAME, Granularity
) tt
WHERE
(NAME, cnt) IN
(
SELECT NAME, MAX(cnt) max_cnt
FROM
(
SELECT
NAME, Granularity, COUNT(*) cnt
FROM
(
SELECT
*,
IF(#last_name IS NULL OR NAME != #last_name OR DATE(#last_dt) != DATE(DATE_TIME),
0, TIMESTAMPDIFF(MINUTE, #last_dt, DATE_TIME))AS Granularity,
#last_name:=NAME,
#last_dt:=DATE_TIME
FROM t1 JOIN (SELECT #last_dt:=NULL, #last_name:=NULL) v
) t
WHERE t.Granularity > 0
GROUP BY NAME, Granularity
) ttt
GROUP BY NAME
);
Output:
mysql> SELECT * FROM t1;
+------+---------------------+--------+
| NAME | DATE_TIME | AMOUNT |
+------+---------------------+--------+
| T1 | 2012-10-01 12:15:00 | 200 |
| T1 | 2012-10-01 12:30:00 | 300 |
| T1 | 2012-10-02 01:00:00 | 400 |
| T1 | 2012-10-02 01:15:00 | 500 |
| T1 | 2012-10-03 04:00:00 | 600 |
| T1 | 2012-10-03 05:00:00 | 700 |
| T2 | 2013-12-31 22:30:00 | 3838 |
| T2 | 2013-12-31 23:00:00 | 7272 |
| T2 | 2013-12-31 23:30:00 | 891 |
| T2 | 2013-12-31 23:45:00 | 288 |
+------+---------------------+--------+
10 rows in set (0.00 sec)
...
+------+-------------+-----+
| NAME | Granularity | cnt |
+------+-------------+-----+
| T1 | 15 | 2 |
| T2 | 30 | 2 |
+------+-------------+-----+
2 rows in set (0.00 sec)
I have the following table:
+----+---------------------+---------------+
| id | created_at | deklaracja_id |
+----+---------------------+---------------+
| 1 | 2015-01-09 12:14:00 | 1 |/*deklaracja*/
| 2 | 2015-02-09 12:14:00 | 1 |/*korekta for 1*/
| 3 | 2015-03-09 12:14:00 | 3 |/*deklaracja/
| 4 | 2015-01-09 12:14:00 | 3 |/*korekta for 3*/
| 5 | 2015-10-09 12:14:00 | 3 |/*korekta for 3*/
| 6 | 2015-10-09 12:14:00 | 6 |/*deklaracja*/
+----+---------------------+---------------+
Cond:
id = deklaracja_id is "deklaracja"
id <> deklaracja_id is "korekta"
I need a query to show all "deklaracja" and count of their "korekty" later than 2015-01-09.
Ex.
+----+---------------------+---------------+
| id | created_at | korekty_count |
+----+---------------------+---------------+
| 1 | 2015-01-09 12:14:00 | 1 |
| 3 | 2015-03-09 12:14:00 | 2 |
| 6 | 2015-10-09 12:14:00 | 0 |
+----+---------------------+---------------+
I've tried something like:
SELECT *,
SUM(CASE WHEN (id <> deklaracja_id)THEN 1 ELSE 0 END )
AS korekty_count
FROM Deklaracja
WHERE created >= '2015-09-01 00:00:00'
but it's not working and now I'm totally stuck :/
You can use a correlated sub-query:
SELECT id, created_at,
(SELECT COUNT(*)
FROM Deklaracja AS t2
WHERE t1.id = t2.deklaracja_id AND
t2.id <> t2.deklaracja_id) AS AS korekty_count
FROM Deklaracja AS t1
WHERE id = deklaracja_id
Demo here
Add a GROUP BY clause to your query.
SELECT *,
SUM(CASE WHEN (id <> deklaracja_id)THEN 1 ELSE 0 END )
AS korekty_count
FROM Deklaracja
WHERE created_at >= '2015-01-09 00:00:00' GROUP BY deklaracja_id
SELECT created_at, count(*)-1
from Deklaracje d
where id in (select id from Deklaracje e
where e.deklaracja_id=d.deklaracja_id)
group by deklaracja_id
SQL Fiddle link
I would use a subquery to get the records you're interested in before grouping:
SELECT
id,
created_at,
COUNT(deklaracja_id) AS korekty_count
FROM (
SELECT id, deklaracja_id, created_at
FROM Deklaracja
WHERE created_at >= '2015-09-01 00:00:00'
AND id <> deklaracja_id
) tmp
GROUP BY id;
See demo.
Something like this:
select id, created_at,
( select count(*) from deklaracja dt
where dt.deklaracja_id <> dt.id
and dt.deklaracja_id = d.deklaracja_id
and dt.created_at >= '2015-09-01 00:00:00' ) as korekty_count
from deklaracja d
where id = deklaracja_id
The answer by Giorgos Betsos is a good one. However, if you want to achieve the same results without a sub-query like you tried in your query, then try this one using joins:
SELECT t1.id, t1.created_at, COUNT(t2.id) AS korekty_count
FROM Deklaracja AS t1
LEFT JOIN Deklaracja AS t2 ON t1.id = t2.deklaracja_id
AND t2.id <> t2.deklaracja_id
WHERE t1.id = t1.deklaracja_id
AND t1.created_at >= '2015-09-01 00:00:00'
GROUP BY t1.id
Here is a fiddle