Select first `n` rows of a grouped query - sql

I am using PostgreSQL with SQLAlchemy
I have a table of GPS metrics in the form:
SELECT * FROM user_gps_location;
My Output:
| id | user_id | entry_time | lat | lng | accuracy | altitude | speed |
| 1 | 54 | 2020-07-24 14:08:30.000000 | 54.42184220 | -110.21029370 | 41.42 | 512.40 | 0.07 |
| 2 | 54 | 2020-07-24 22:20:12.000000 | 54.42189750 | -110.21038070 | 13.00 | 512.60 | 0.00 |
| 3 | 26 | 2020-07-27 13:51:11.000000 | 54.41453910 | -110.20775990 | 1300.00 | 0.00 | 0.00 |
| 4 | 26 | 2020-07-27 22:59:00.000000 | 54.42122590 | -110.20959960 | 257.52 | 509.10 | 0.00 |
| 5 | 26 | 2020-07-28 13:54:12.000000 | 54.42185280 | -110.21025010 | 81.45 | 510.20 | 0.00 |
...
I need to be able to answer the question "What are the latest 5 entries for each user since "", sorted by entry_time
Right now I only have a basic query:
select *
from user_gps_location
where user_id in (select distinct user_id
from user_gps_location
where entry_time > '2020-09-01')
and entry_time > '2020-09-01';
Applying a limit will not do what I want. I assume I need to use a grouping and window functions (?), but I do not understand them.

The row_number function is exactly what you're looking for:
SELECT *
FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY entry_time DESC) AS rn
FROM user_gps_location
WHERE entry_time > '2020-09-01') t
WHERE rn <= 5

you can use FETCH FIRST N ROWS ONLY
select * from user_gps_location
where entry_time > '2020-09-01'
order by entry_time desc
fetch first 5 rows only

Related

SQL Server - Counting total number of days user had active contracts

I want to count the number of days while user had active contract based on table with start and end dates for each service contract. I want to count the time of any activity, no matter if the customer had 1 or 5 contracts active at same time.
+---------+-------------+------------+------------+
| USER_ID | CONTRACT_ID | START_DATE | END_DATE |
+---------+-------------+------------+------------+
| 1 | 14 | 18.02.2021 | 18.04.2022 |
| 1 | 13 | 02.01.2019 | 02.01.2020 |
| 1 | 12 | 01.01.2018 | 01.01.2019 |
| 1 | 11 | 13.02.2017 | 13.02.2019 |
| 2 | 23 | 19.06.2021 | 18.04.2022 |
| 2 | 22 | 01.07.2019 | 01.07.2020 |
| 2 | 21 | 19.01.2019 | 19.01.2020 |
+---------+-------------+------------+------------+
In result I want a table:
+---------+--------------------+
| USER_ID | DAYS_BEEING_ACTIVE |
+---------+--------------------+
| 1 | 1477 |
| 2 | 832 |
+---------+--------------------+
Where
1477 stands by 1053 (days from 13.02.2017 to 02.01.2020 - user had active contracts during this time) + 424 (days from 18.02.2021 to 18.04.2022)
832 stands by 529 (days from 19.01.2019 to 01.07.2020) + 303 (days from 19.06.2021 to 18.04.2022).
I tried some queries with joins, datediff's, case when conditions but nothing worked. I'll be grateful for any help.
If you don't have a Tally/Numbers table (highly recommended), you can use an ad-hoc tally/numbers table
Example or dbFiddle
Select User_ID
,Days = count(DISTINCT dateadd(DAY,N,Start_Date))
from YourTable A
Join ( Select Top 10000 N=Row_Number() Over (Order By (Select NULL))
From master..spt_values n1, master..spt_values n2
) B
On N<=DateDiff(DAY,Start_Date,End_Date)
Group By User_ID
Results
User_ID Days
1 1477
2 832

Subtracting previous row value from current row

I'm doing an aggregation like this:
select
date,
product,
count(*) as cnt
from
t1
where
yyyy_mm_dd in ('2020-03-31', '2020-07-31', '2020-09-30', '2020-12-31')
group by
1,2
order by
product asc, date asc
This produces data which looks like this:
| date | product | cnt | difference |
|------------|---------|------|------------|
| 2020-03-31 | p1 | 100 | null |
| 2020-07-31 | p1 | 1000 | 900 |
| 2020-09-30 | p1 | 900 | -100 |
| 2020-12-31 | p1 | 1100 | 200 |
| 2020-03-31 | p2 | 200 | null |
| 2020-07-31 | p2 | 210 | 10 |
| ... | ... | ... | x |
But without the difference column. How could I make such a calculation? I could pivot the date column and subtract that way but maybe there's a better way
Was able to use lag with partition by and order by to get this to work:
select
date,
product,
count,
count - lag(count) over (partition by product order by date, product) as difference
from(
select
date,
product,
count(*) as count
from
t1
where
yyyy_mm_dd in ('2020-03-31', '2020-07-31', '2020-09-30', '2020-12-31')
group by
1,2
) t

Combine PARTITION BY and GROUP BY

I have a (mssql) table like this:
+----+----------+---------+--------+--------+
| id | username | date | scoreA | scoreB |
+----+----------+---------+--------+--------+
| 1 | jim | 01/2020 | 100 | 0 |
| 2 | max | 01/2020 | 0 | 200 |
| 3 | jim | 01/2020 | 0 | 150 |
| 4 | max | 02/2020 | 150 | 0 |
| 5 | jim | 02/2020 | 0 | 300 |
| 6 | lee | 02/2020 | 100 | 0 |
| 7 | max | 02/2020 | 0 | 200 |
+----+----------+---------+--------+--------+
What I need is to get the best "combined" score per date. (With "combined" score I mean the best scores per user and per date summarized)
The result should look like this:
+----------+---------+--------------------------------------------+
| username | date | combined_score (max(scoreA) + max(scoreB)) |
+----------+---------+--------------------------------------------+
| jim | 01/2020 | 250 |
| max | 02/2020 | 350 |
+----------+---------+--------------------------------------------+
I came this far:
I can group the scores by user like this:
SELECT
username, (max(scoreA) + max(scoreB)) AS combined_score,
FROM score_table
GROUP BY username
ORDER BY combined_score DESC
And I can get the best score per date with PARTITION BY like this:
SELECT *
FROM
(SELECT t.*, row_number() OVER (PARTITION BY date ORDER BY scoreA DESC) rn
FROM score_table t) as tmp
WHERE tmp.rn = 1
ORDER BY date
Is there a proper way to combine these statements and get the result I need? Thank you!
Btw. Don't care about possible ties!
You can combine window functions and aggregation functions like this:
SELECT s.*
FROM (SELECT username, date, (max(scoreA) + max(scoreB)) AS combined_score,
ROW_NUMBER() OVER (PARTITION BY date ORDER BY max(scoreA) + max(scoreB) DESC) as seqnum
FROM score_table
GROUP BY username, date
) s
ORDER BY combined_score DESC;
Note that date needs to be part of the aggregation.

Find the period of occurence of a value in table

I have a table with the following data.
+------------+---------+
| Date | Version |
+------------+---------+
| 1/10/2019 | 1 |
| .... | |
| 15/10/2019 | 1 |
| 16/10/2019 | 2 |
| .... | |
| 26/10/2019 | 2 |
| 27/10/2019 | 1 |
| .... | |
| 30/10/2019 | 1 |
+------------+---------+
I need to find the period of occurrence for version in the table.
Eg:Suppose I need to get Version 1 occurence details which is present from 1/10/2019 to 15/10/2019 and from 27/10/2019 to 30/10/2019. How can i query the database for such a result?
I have tried many ways but not able to produce the desired result .I even doubt this is possible using a query!
Any inputs are highly appreciated.
Expected output:
+---------+-------------+-------------+
| Version | Period from | Period To |
+---------+-------------+-------------+
| 1 | 1/10/2019 | 15/10/2019 |
| 2 | 16/10/2019 | 26/10/2019 |
| 1 | 27/10/2019 | 30/10/2019 |
+---------+-------------+-------------+
This is gaps and Islands question.
Try this
DECLARE #SampleData TABLE ( [Date] DATE, [Version] INT)
INSERT INTO #SampleData ([Date], [Version])
VALUES
('01-10-2019', 1), ('02-10-2019', 1), ('15-10-2019', 1),
('16-10-2019', 2), ('17-10-2019', 2),('26-10-2019', 2),
('27-10-2019', 1), ('28-10-2019', 1), ('30-10-2019', 1)
SELECT
Y.[Version]
,PeriodFrom = MIN(Y.[Date])
,PeriodTo = MAX(Y.[Date])
FROM(
SELECT
X.[Version]
,X.[Date]
,ISLAND = RN-ROW_NUMBER()OVER( PARTITION BY X.[Version] ORDER BY X.[Date])
FROM(
SELECT
RN=ROW_NUMBER()OVER( ORDER BY S.[Date])
,S.[Date]
,S.[Version]
FROM
#SampleData S
) X
) Y
GROUP BY
Y.[Version], Y.ISLAND
ORDER BY
PeriodFrom
Output
Version PeriodFrom PeriodTo
1 2019-10-01 2019-10-15
2 2019-10-16 2019-10-26
1 2019-10-27 2019-10-30

Multiply with Previous Value in Oracle SQL

Its easy to multiply (or sum/divide/etc.) with previous row in Excel spreadsheet, however, I could not do it so far in Oracle SQL.
A B C
199901 3.81 51905
199902 -6.09 48743.9855
199903 4.75 51059.32481
199904 6.39 54322.01567
199905 -2.35 53045.4483
199906 2.65 54451.15268
199907 1.1 55050.11536
199908 -1.45 54251.88869
199909 0 54251.88869
199910 4.37 56622.69622
Above, column B is static and column C has the formula as:
((B2/100)+1)*C1
((B3/100)+1)*C2
((B4/100)+1)*C3
Example: 51905 from row 1 multiplied with -6.09 from row 2:
((-6.09/100)+1)*51905
I have been trying analytical and window functions, but not succeeded yet. LAG function can give previous row value in current row, but cannot give calculated previous value.
This can be done with a help of MODEL clause
select *
FROM (
SELECT t.*,
row_number() over (order by a) as rn
from table1 t
)
MODEL
DIMENSION BY (rn)
MEASURES ( A, B, 0 c )
RULES (
c[rn=1] = 51905, -- value in a first row
c[rn>1] = round( c[cv()-1] * (b[cv()]/100 +1), 6 )
)
;
Demo: http://sqlfiddle.com/#!4/9756ed/11
| RN | A | B | C |
|----|--------|-------|--------------|
| 1 | 199901 | 3.81 | 51905 |
| 2 | 199902 | -6.09 | 48743.9855 |
| 3 | 199903 | 4.75 | 51059.324811 |
| 4 | 199904 | 6.39 | 54322.015666 |
| 5 | 199905 | -2.35 | 53045.448298 |
| 6 | 199906 | 2.65 | 54451.152678 |
| 7 | 199907 | 1.1 | 55050.115357 |
| 8 | 199908 | -1.45 | 54251.888684 |
| 9 | 199909 | 0 | 54251.888684 |
| 10 | 199910 | 4.37 | 56622.696219 |