Postgres: how to join closest value from the same table - sql

I have a the following table
CREATE TABLE temp (
id SERIAL,
other_id INTEGER NOT NULL, -- some ForeignKey
date DATE NOT NULL
)
I want to join this table to itself by previous (closest) date item with the same other_id. Something like
SELECT count(*)
FROM temp AS t1
JOIN temp AS t2 ON (t2.other_id = t1.other_id AND t2.date < t1.date)
But t2.date must be closest to t1.date (not any lower date).
Is that possible at all?

You can use a query like the following:
WITH temp_rn AS (
SELECT id, other_id, date,
ROW_NUMBER() OVER (PARTITION BY other_id
ORDER BY date) AS rn
FROM temp
)
SELECT t1.*
FROM temp_rn AS t1
LEFT JOIN temp_rn AS t2 ON t1.other_id = t2.other_id AND t1.rn = t2.rn + 1
The query uses ROW_NUMBER in order to detect the 'previous' row: it is the one having the previous row number within the same other_id slice.

It's not entirely clear what you are after, but something like this might do it:
select count(*)
from temp as t1
join lateral (
select t.other_id, max(t.date)
from temp as t
where t.date < t1.date
and t.other_id = t1.other_id
group by t2.other_id
) as t2 on t2.other_id = t1.other_id;

Related

How do I join a table with timestamps to table of status change

I've seen answers to how to join to the nearest timestamp, but am puzzled with how to do what should be simple logic with sql.
I have a 2 tables, table 1 with [timestamp], and table 2 with [timestamp, status_change].
How would I obtain a table with [timestamp, status]?
I could do this with a program by getting a list of start and end time for each status from table 2, then for each timestamp in table 1 loop through the list and assign a status.
What would be the sql equivalent of this?
If I understand correctly, you want the timestamps from t1 and the statuses from t2. A correlated subquery (or lateral join) does this:
select t1.*,
(select top (1) t2.status
from t t2
where t2.timestamp <= t1.timestamp
order by t2.timestamp desc
) as status
from t1;
An alternative method uses lead() with left join:
select t1.*, t2.status
from t1 left join
(select t2.*,
lead(timestamp) over (order by timestamp) as next_timestamp
from t2
) t2
on t1.timestamp >= t2.timestamp and
(t1.timestamp < t2.next_timestamp or t2.next_timestamp is null);
My answer is pretty much the same as Gordon Linoff's, but with a CTE and a coalesce instead:
with T2Range as (
select status
, Timestamp as From_Timestamp
, lead(Timestamp) over (order by Timestamp) as To_Timestamp
from T2
)
select T1.myValue
, T2Range.Status as T2Status
from T1
left join T2Range
on T1.Timestamp between T2Range.From_Timestamp and coalesce(T2Range.To_Timestamp,T1.Timestamp);

where column in from another select results with limit (mysql/mariadb)

when i run this query returns all rows that their id exist in select from table2
SELECT * FROM table1 WHERE id in (
SELECT id FROM table2 where name ='aaa'
)
but when i add limit or between to second select :
SELECT * FROM table1 WHERE id in (
SELECT id FROM table2 where name ='aaa' limit 4
)
returns this error :
This version of MariaDB doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'
You are using LIMIT without an ORDER BY. This is generally not recommended because that returns an arbitrary set of rows -- and those can change from one execution to another.
You can convert this to a JOIN -- fortunately. If id is not duplicated in table2:
SELECT t1.*
FROM table1 t1 JOIN
(SELECT t2.id
FROM table2 t2
WHERE t2.name = 'aaa'
LIMIT 4
) t2
USING (id);
If id can be duplicated in table2, then:
SELECT t1.*
FROM table1 t1 JOIN
(SELECT DISTINCT t2.id
FROM table2 t2
WHERE t2.name = 'aaa'
LIMIT 4
) t2
USING (id);
Another fun way uses LIMIT:
SELECT t1.*
FROM table1 t1
WHERE id <= ANY (SELECT t2.id
FROM table2
WHERE t2.name = 'aaa'
ORDER BY t2.id
LIMIT 1 OFFSET 3
);
LIMIT is allowed in a scalar subquery.
You can use an analytic function such as ROW_NUMBER() in order to return one row from the subquery. I suppose, this way no problem would occur like raising too many rows issue :
SELECT * FROM
(
SELECT t1.*,
ROW_NUMBER() OVER (ORDER BY t2.id DESC) AS rn
FROM table1 t1
JOIN table2 t2 ON t2.id = t1.id
WHERE t2.name ='aaa'
) t
WHERE rn = 1
P.S.: Btw, id columns are expected to be primary keys of your tables, aren't they ?
Update ( depending on your need in the comment ) Consider using :
SELECT * FROM
(
SELECT j.*,
ROW_NUMBER() OVER (ORDER BY j.id DESC) AS rn2
FROM job_forum j
CROSS JOIN
( SELECT t.*,
ROW_NUMBER() OVER (PARTITION BY t2.id ORDER BY t2.id DESC) AS rn1
FROM table2 t2
WHERE t2.name ='aaa'
AND t2.id = j.id ) t2
WHERE rn1 = 1
) jj
WHERE rn2 <= 10

#1093 Table 'table' is specified twice, both as a target for 'DELETE' and as a separate source for data

not getting desire result in mysql query
i have searched a lot but didn't find solution
DELETE FROM table1 WHERE username NOT IN (select t1.id from table1 as t1
inner join table1 as t2 on t1.username = t2.username and t1.id <= t2.id
group by t1.username , t1.id having count(*) <= 5 order by t1.username , t1.id desc);
Output is as follows:-
should work for you
delete from
table1
where
id in ( select
id
from
( select
*,
row_number() over( partition by
username
order by
id desc) as rn
from
table1)
where
rn > 5)
You seem to want to keep the most recent five ids for each user name. I think the simplest method uses window functions:
delete t1
from table1 t1 join
(select t1.*, row_number() over (partition by username order by id desc) as seqnum
from table1 t1
) tt1
on t1.username = tt1.username and
t1.id = tt1.id
where tt1.seqnum > 5;

SQL Server : Query To Retrieve a Row based on Value in a Column

I have two databases one database is recording tank levels every 1 minute. We have about 30 tanks. The other database contains lookup tables that contain the number of gallons or each tank for a given level.
I have manged to use an INNER JOIN to get data from the lookup table in SQL Server 2012
WITH T1 As
(
SELECT
DateTime, TagName, Value
FROM
INSQL.Runtime.dbo.History
WHERE
DateTime = (SELECT Max(DateTime)
FROM INSQL.Runtime.dbo.History
WHERE TagName LIKE 'LT%')
AND TagName LIKE 'LT%' AND Value <> '0'
),
T2 AS
(
SELECT *
FROM TankSTrappingDB.dbo.StrappingTable
)
SELECT *
FROM T1
LEFT JOIN T2 ON T1.TagName = T2.TagName
WHERE T2.LIT_PV <= T1.Value
However this returns all of the rows from table 2, where the value of the actual level in the tank is less than the value recorded in table 1. I just need the single row from table 2 that is the closest match to the value recorded in table with with the corresponding timestamp.
Instead of joining T2 to T2, you could lookup the MAX(LIT_PV) in T2, where the TAGname matches? But, if you also want other values from the T2 table, you can add another test to the last bit in your SQL
... ...
SELECT *
FROM T1
LEFT JOIN T2 ON T1.TagName = T2.TagName
WHERE T2.LIT_PV <= T1.Value
and T2.LIT_PV in
(select max(T2A.LIT_PV) from T2 T2A
where T1.TagName = T2A.TagName and T2A.LIT_PV <= T1.Value
)
Please try below query. As the sample data is not available I could not test this query.
WITH T1 As
(
SELECT
DateTime, TagName, Value,
ROW_NUMBER() OVER (PARTITION BY TagName ORDER BY DateTime DESC) RN
FROM
INSQL.Runtime.dbo.History
WHERE
TagName LIKE 'LT%' AND Value <> '0'
),
T2 AS
(
SELECT *,
ROW_NUMBER() OVER(ORDER BY T.LIT_PV DESC) RN
FROM TankSTrappingDB.dbo.StrappingTable T
INNER JOIN T1 ON T1.TagName = T.TagName
AND T.LIT_PV <= T1.Value
WHERE T1.RN = 1
)
SELECT *
FROM T1
INNER JOIN T2 ON T1.TagName = T2.TagName
AND T1.RN = T2.RN
WHERE T2.RN = 1

SQL Select row from table with some minimum value from another table

I suppose this is not so hard but I can not get it.
For example I have table T1:
ID
-----
1000
1001
And I have table T2:
ID GROUP DATE
--------------------------
1000 ADSL 2.2.2012
1000 null 3.2.2012
1000 NOC 4.2.2012
1001 NOC 5.2.2012
1001 null 6.2.2012
1001 TV 7.2.2012
I want to select from T1 only the row that has as GROUP value NOC from T2 but only if NOC group is for the minimum DATE value in T2.
So my result in this case would be only 1001 because for its minimum DATE 5.2.2012 Group is NOC!
I do not want any joins and I can not use default values for IDs (where id=1000 or id=1001) because this is just example of some big table.
Important also is that I can not use t1.id = t2.id because in some application where I am using this I can not write the whole SQL expression but only partial. I can only use id.
I tried something like:
select id
from t1
where
id in (select id from t2
where group = 'NOC'
and date in (select min(date) from t2
where id in (select id from t1)
)
)
But this does not work.
I know it seems little confusing but I really can't use where t1.id = t2.id
Thanks
If T2.ID is a foreign key referencing T1.ID, you don't really need the T1 table, because all the IDs could be obtained from T2 only:
SELECT o.ID
FROM T2 AS o
WHERE EXISTS (
SELECT MIN(i.DATE)
FROM T2 AS i
WHERE i.ID = o.ID
HAVING MIN(i.DATE) = o.DATE
)
WHERE o."GROUP" = 'NOC'
But if you insist on involving T1, you just need to modify the above like this:
SELECT *
FROM T1
WHERE ID IN (
SELECT o.ID
FROM T2 AS o
WHERE o."GROUP" = 'NOC'
AND EXISTS (
SELECT MIN(i.DATE)
FROM T2 AS i
WHERE i.ID = o.ID
HAVING MIN(i.DATE) = o.DATE
)
)
Can you do this in multiple steps?
First of all, to get the minimum date per id, you would need:
select id, peoplegroup, min(date)
from t2
group by id
That will give you
1000 ADSL 2.2.2012
1001 NOC 5.2.2012
Call this table t3.
Then do
select id
from t3
where id in (
select id from t1
)
Try this:
select id from t1 where id in
(select id from t2 where group = 'NOC' and date =
(select min(date) from t2 where id = t1.id))