Highest per each group - sql

It's hard to show my actual table and data here so I'll describe my problem with a sample table and data:
create table foo(id int,x_part int,y_part int,out_id int,out_idx text);
insert into foo values (1,2,3,55,'BAK'),(2,3,4,77,'ZAK'),(3,4,8,55,'RGT'),(9,10,15,77,'UIT'),
(3,4,8,11,'UTL'),(3,4,8,65,'MAQ'),(3,4,8,77,'YTU');
Following is the table foo:
id x_part y_part out_id out_idx
-- ------ ------ ------ -------
3 4 8 11 UTL
3 4 8 55 RGT
1 2 3 55 BAK
3 4 8 65 MAQ
9 10 15 77 UIT
2 3 4 77 ZAK
3 4 8 77 YTU
I need to select all fields by sorting the highest id of each out_id.
Expected output:
id x_part y_part out_id out_idx
-- ------ ------ ------ -------
3 4 8 11 UTL
3 4 8 55 RGT
3 4 8 65 MAQ
9 10 15 77 UIT
Using PostgreSQL.

Postgres specific (and fastest) solution:
select distinct on (out_id) *
from foo
order by out_id, id desc;
Standard SQL solution using a window function (second fastest)
select id, x_part, y_part, out_id, out_idx
from (
select id, x_part, y_part, out_id, out_idx,
row_number() over (partition by out_id order by id desc) as rn
from foo
) t
where rn = 1
order by id;
Note that both solutions will only return each id once, even if there are multiple out_id values that are the same. If you want them all returned, use dense_rank() instead of row_number()

select *
from foo
where (id,out_id) in (
select max(id),out_id from foo group by out_id
) order by out_id

Finding max(val) := finding the record for which no larger val exists:
SELECT *
FROM foo f
WHERE NOT EXISTS (
SELECT 317
FROM foo nx
WHERE nx.out_id = f.out_id
AND nx.id > f.id
);

Related

Running total of a calculated item in sqlite

I need the running sum of the following in a query in sqlite.
Sum(of a few items)-Sum(of a few items) As rn
E.g.:
``accno qtydel qtyrec rn rnt 001 5 1 4 4 001 2 0 6 10 001 5 3 8 18 004 6 3 9 9 004 1 2 8 17
If using Sqlite 3.25 or newer, window functions make this trivial:
sqlite> WITH mytable(id, n) AS (VALUES (1,8), (2,2), (3,3))
...> SELECT n, sum(n) OVER (ORDER BY id) AS rnt FROM mytable ORDER BY id;
n rnt
---------- ----------
8 8
2 10
3 13
Straight from the SQLite tutorial on window frames ...
Assuming the two columns are labeled a and b in table t:
SELECT
a,
b,
SUM(a) OVER (
ORDER BY rowid
) RunningTotalA,
SUM(b) OVER (
ORDER BY rowid
) RunningTotalB
FROM t;
Output, with headers:
a|b|RunningTotalA|RunningTotalB
8|8|8|8
2|10|10|18
3|13|13|31

Oracle: Get the smaller values and the first greater value

I have a table like this;
ID Name Value
1 Sample1 10
2 Sample2 20
3 Sample3 30
4 Sample4 40
And I would like to get all of the rows that contain smaller values and the first row that contains greater value.
For example when I send '25' as a parameter to Value column, I want to have following table;
ID Name Value
1 Sample1 10
2 Sample2 20
3 Sample3 30
I'm stuck at this point, thanks in advance.
Analytic functions to the rescue!
create table your_table (
id number,
value number)
insert into your_table
select level, level * 10
from dual
connect by level <= 5
select * from your_table
id | value
----+------
1 | 10
2 | 20
3 | 30
4 | 40
5 | 50
Ok, now we use lag(). Specify field, offset and the default value (for the first row that has no previous one).
select id, value, lag(value, 1, value) over (order by value) previous_value
from your_table
id | value | previous_value
---+-------+---------------
1 | 10 | 10
2 | 20 | 10
3 | 30 | 20
4 | 40 | 30
5 | 50 | 40
Now apply where.
select id, value
from (
select id, value, lag(value, 1, value) over (order by value) previous_value
from your_table)
where previous_value < 25
Works for me.
id | value
----+------
1 | 10
2 | 20
3 | 30
Of course you have to have some policy on ties. For example, what happens if two rows have the same value and they are both first — do you want to keep both or only one of them. Or maybe you have some other criterion for breaking the tie (say, sort by id). But the idea is fairly simple.
you can try a query like this :
SELECT * FROM YourTableName WHERE Value < 25 OR ID IN (SELECT TOP 1 ID FROM YourTableName WHERE Value >= 25 ORDER BY Value)
in Oracle, you can try this (but see "That Young Man" answer, I think it's better than mine):
SELECT * FROM (
SELECT ID, NAME, VALUE, 1 AS RN
FROM YT
WHERE VALUE < 25
UNION ALL
SELECT ID, NAME, VALUE, ROW_NUMBER()OVER (ORDER BY VALUE) AS RN
FROM YT
WHERE VALUE > 25
) A
WHERE RN=1;

Updating column based on another column's value

How do i update table structured like this:
id[pkey] | parent_id | position
1 1
2 1
3 1
4 1
5 1
6 2
7 2
8 2
9 2
10 3
11 3
12 3
...and so on
to achieve this result:
id[pkey] | parent_id | position
1 1 1
2 1 2
3 1 3
4 1 4
5 1 5
6 2 1
7 2 2
8 2 3
9 2 4
10 3 1
11 3 2
12 3 3
...and so on
I was thinking about somehow mixing
SELECT DISTINCT parent_id FROM cats AS t;
with
CREATE SEQUENCE dpos;
UPDATE cats t1 SET position = nextval('dpos') WHERE t.parent_id = t1.parent_id;
DROP SEQUENCE dpos;
although im not really experienced with postgres, and not sure how to use some kind of FOREACH. I appreciate any help
You can get the incremental number using row_number(). The question is how to assign it to a particular row. Here is one method using a join:
update cats
set position = c2.newpos
from (select c2.*, c2.ctid as c_ctid,
row_number() over (partition by c2.parent_id order by NULL) as seqnum
from cats c2
) c2
where cats.parent_id = c2.parent_id and cats.ctid = c2.c_ctid;
Use row_number function
select parent_id,
row_number() over (partition by parent_id order by parent_id) as position_id from table
Try this:
UPDATE table_name set table_name.dataID = v_table_name.rn
FROM
(
SELECT row_number() over (partition by your_primaryKey order by your_primaryKey) AS rn, id
FROM table_name
) AS v_table_name
WHERE v_table_name.your_primaryKey = v_table_name.your_primaryKey;

SQL Server GROUP BY COUNT Consecutive Rows Only

I have a table called DATA on Microsoft SQL Server 2008 R2 with three non-nullable integer fields: ID, Sequence, and Value. Sequence values with the same ID will be consecutive, but can start with any value. I need a query that will return a count of consecutive rows with the same ID and Value.
For example, let's say I have the following data:
ID Sequence Value
-- -------- -----
1 1 1
5 1 100
5 2 200
5 3 200
5 4 100
10 10 10
I want the following result:
ID Start Value Count
-- ----- ----- -----
1 1 1 1
5 1 100 1
5 2 200 2
5 4 100 1
10 10 10 1
I tried
SELECT ID, MIN([Sequence]) AS Start, Value, COUNT(*) AS [Count]
FROM DATA
GROUP BY ID, Value
ORDER BY ID, Start
but that gives
ID Start Value Count
-- ----- ----- -----
1 1 1 1
5 1 100 2
5 2 200 2
10 10 10 1
which groups all rows with the same values, not just consecutive rows.
Any ideas? From what I've seen, I believe I have to left join the table with itself on consecutive rows using ROW_NUMBER(), but I am not sure exactly how to get counts from that.
Thanks in advance.
You can use Sequence - ROW_NUMBER() OVER (ORDER BY ID, Val, Sequence) AS g to create a group:
SELECT
ID,
MIN(Sequence) AS Sequence,
Val,
COUNT(*) AS cnt
FROM
(
SELECT
ID,
Sequence,
Sequence - ROW_NUMBER() OVER (ORDER BY ID, Val, Sequence) AS g,
Val
FROM
yourtable
) AS s
GROUP BY
ID, Val, g
Please see a fiddle here.

Select all items with sum of fields in specified range

I have simple table:
file_size file_id file_time
1 1 19
2 2 20
3 3 21
4 4 22
5 5 23
I want to find such item that all items with less file_time has the sum of file_size in predefined range.
I written next query:
SELECT * FROM test_table AS D0 WHERE
(SELECT TOTAL(file_size) FROM test_table AS D1 WHERE
D1.file_time <= D0.file_time ORDER BY file_id)
BETWEEN 1 AND 9
This query get correct results:
1 1 19
2 2 20
3 3 21
But this query does not work if needed items has the same file_time field:
file_size file_id file_time
1 1 20
2 2 20
3 3 20
4 4 20
5 5 20
The desired result for this data is:
1 1 20
2 2 20
3 3 20
The file_id field is unique.
What is wrong in my SQL-query?
The code to create test table:
CREATE TABLE test_table (file_size INT, file_id INT, file_time INT)
INSERT INTO test_table VALUES(1,1,20)
INSERT INTO test_table VALUES(2,2,20)
INSERT INTO test_table VALUES(3,3,20)
INSERT INTO test_table VALUES(4,4,20)
INSERT INTO test_table VALUES(5,5,20)
You shouldn't consider file_time as a single column in your query, since you want to consider the column file_id either. You should use the pairs of file_time and file_id and you should compare them lexicographically as follows:
SELECT *
FROM test_table AS D0
WHERE (
SELECT TOTAL( file_size )
FROM test_table AS D1
WHERE D1.file_time < D0.file_time
OR (
D1.file_time = D0.file_time
AND D1.file_id <= D0.file_id
)
ORDER BY file_time, file_id DESC
)
BETWEEN 1
AND 9
Not sure if I understood but I think
-- sum of file sizes between 1 and 7 with the lowest time
SELECT SUM(test.file_size) AS sum_file_size, test.file_time
FROM test
WHERE (test.file_time = (SELECT TOP 1 test.file_time
FROM test
ORDER BY file_time))
AND (test.file_size BETWEEN 1 AND 9)
GROUP BY test.file_time;
-- sum of file sizes per time `group`
SELECT SUM(test.file_size) AS sum_file_size, test.file_time,
FROM test
WHERE (test.file_size BETWEEN 1 AND 7)
GROUP BY test.file_time
ORDER BY test.file_time;