Sort by one column, but get offset by another - sql

Let's say I have a table with two columns:
| ID | A |
I want to sort by A, then get the 10 records after a given ID. What would be the best way to handle this in Postgres?
To clarify, I want to sort by A, but do my pagination by the ID. So if I had a table like:
1 | 'C'
2 | 'B'
3 | 'A'
4 | 'G'
5 | 'A'
6 | 'H'
So after sorting by A, I'd want the first three values after id=1, so:
1 | 'C'
4 | 'G'
6 | 'H'

An ordering of any column is purely dependant on the order by clause and the terms "before" or "after" come into picture only when there's a pre-determined order. So, once the records are ordered by column "A", there's no guarantee that the id's will be ordered in the sequence 1,4,6, unless you also specified that ordering of id.
So, if you
want the first three values after id=1
It means there should be a way to determine the point where the id value has become 1 and all the rows beyond are to be considered. To ensure that you have to explicitly include id in the order by. A COUNT analytic function can come to our rescue to mark the point.
SELECT id,a
FROM ( SELECT t.*,COUNT(CASE WHEN id = 1 THEN 1 END) --the :id argument
OVER( ORDER BY a,id) se
FROM t order by a,id --the rows are ordered first by a, then by id
-- same as in the above count analytic function
) s
WHERE se = 1 limit 3; -- the argument 3 or 10 that you wish to pass
-- se = 1 won't change for other ids, it's a marker
-- that id = n is reached
DEMO

I think this will do:
SELECT *
FROM (SELECT * from MyTable where ID > givenId order by A) sub
LIMIT 10;

You don't want the A columns, so:
SELECT r.*
FROM t
WHERE t.id > ANY (SELET id FROM t t2 WHERE t2.col = 'A')
ORDER BY col
LIMIT 10;
Note that this does not return any rows with A as the value. It also works when the comparison value is not sorted first.

this will work:
SELECT * from Table1 where "ID"=1
order by "A" desc limit 2;
check :http://sqlfiddle.com/#!15/5854b/3
for your query :
SELECT * from Table1 where "ID"=1
order by "A" desc limit 10;

Related

SQL query to find all UUIDs with a logical expression of flags set

I have a SQLite database with ~30 million rows with UUIDs and a flag column (among other columns) and I want to return all UUIDs that satisfy a logical expression of the flags.
UUID | flag | ...
1 | "a" |
1 | "b" |
1 | "a" |
2 | "b" |
2 | "c" |
3 | "a" |
For example I want to return all UUIDs that have flag ("a" AND ("b" or "c")) over all rows. In the above table only UUID=1 satisfies that constraint.
This is a similar question but it only asks about the case of having all 4 flags set --- there is no disjunction --- so the solutions there don't work here.
edit: #forpas HAVING SUM solution is what I was looking for but I ending up solving the problem by creating a user defined aggregate function before I saw it.
You can use aggregation with the conditions in the HAVING clause:
SELECT UUID
FROM tablename
GROUP BY UUID
HAVING SUM(flag = 'a') > 0
AND SUM(flag IN ('b', 'c')) > 0;
You can add as many conditions as you want.
See the demo.
Consider the following approach using min() and max() functions:
select UUID
from Tbl
group by UUID having min(flag)="a" and min(flag)<>max(flag)
When min() is used with strings, the strings are ordered alphabetically A-Z and the first one is returned. In your case flags with a value will be the minimum and flags with c value will be the maximum. The having clause will ensure that there's an a value within the group and that value is not equal to the maximum flag value of the group (in this case b or c).
See a demo from here.
you can use EXISTS if there are other rows with the same UUID, and the flag
SELECT
UUID
FROM
mytable m
WHERE
flag = 'a'
AND
(EXISTS (
SELECT
1
FROM
mytable
WHERE
flag = 'b'
and UUID = m.UUID
) OR
EXISTS (
SELECT
1
FROM
mytable
WHERE
flag = 'c'
and UUID = m.UUID
)
)
or you can simpler write
SELECT
UUID
FROM
mytable m
WHERE
flag = 'a'
AND
EXISTS (
SELECT
1
FROM
mytable
WHERE
flag IN ('b','c')
and UUID = m.UUID
)

How to identify rows per group before a certain value gap?

I'd like to update a certain column in a table based on the difference in a another column value between neighboring rows in PostgreSQL.
Here is a test setup:
CREATE TABLE test(
main INTEGER,
sub_id INTEGER,
value_t INTEGER);
INSERT INTO test (main, sub_id, value_t)
VALUES
(1,1,8),
(1,2,7),
(1,3,3),
(1,4,85),
(1,5,40),
(2,1,3),
(2,2,1),
(2,3,1),
(2,4,8),
(2,5,41);
My goal is to determine in each group main starting from sub_id 1 which value in diff exceeds a certain threshold (e.g. <10 or >-10) by checking in ascending order by sub_id. Until the threshold is reached I would like to flag every passed row AND the one row where the condition is FALSE by filling column newval with a value e.g. 1.
Should I use a loop or are there smarter solutions?
The task description in pseudocode:
FOR i in GROUP [PARTITION BY main ORDER BY sub_id]:
DO until diff > 10 OR diff <-10
SET newval = 1 AND LEAD(newval) = 1
Basic SELECT
As fast as possible:
SELECT *, bool_and(diff BETWEEN -10 AND 10) OVER (PARTITION BY main ORDER BY sub_id) AS flag
FROM (
SELECT *, value_t - lag(value_t, 1, value_t) OVER (PARTITION BY main ORDER BY sub_id) AS diff
FROM test
) sub;
Fine points
Your thought model evolves around the window function lead(). But its counterpart lag() is a bit more efficient for the purpose, since there is no off-by-one error when including the row before the big gap. Alternatively, use lead() with inverted sort order (ORDER BY sub_id DESC).
To avoid NULL for the first row in the partition, provide value_t as default as 3rd parameter, which makes the diff 0 instead of NULL. Both lead() and lag() have that capability.
diff BETWEEN -10 AND 10 is slightly faster than #diff < 11 (clearer and more flexible, too). (# being the "absolute value" operator, equivalent to the abs() function.)
bool_or() or bool_and() in the outer window function is probably cheapest to mark all rows up to the big gap.
Your UPDATE
Until the threshold is reached I would like to flag every passed row AND the one row where the condition is FALSE by filling column newval with a value e.g. 1.
Again, as fast as possible.
UPDATE test AS t
SET newval = 1
FROM (
SELECT main, sub_id
, bool_and(diff BETWEEN -10 AND 10) OVER (PARTITION BY main ORDER BY sub_id) AS flag
FROM (
SELECT main, sub_id
, value_t - lag(value_t, 1, value_t) OVER (PARTITION BY main ORDER BY sub_id) AS diff
FROM test
) sub
) u
WHERE (t.main, t.sub_id) = (u.main, u.sub_id)
AND u.flag;
Fine points
Computing all values in a single query is typically substantially faster than a correlated subquery.
The added WHERE condition AND u.flag makes sure we only update rows that actually need an update.
If some of the rows may already have the right value in newval, add another clause to avoid those empty updates, too: AND t.newval IS DISTINCT FROM 1
See:
How do I (or can I) SELECT DISTINCT on multiple columns?
SET newval = 1 assigns a constant (even though we could use the actually calculated value in this case), that's a bit cheaper.
db<>fiddle here
Your question was hard to comprehend, the "value_t" column was irrelevant to the question, and you forgot to define the "diff" column in your SQL.
Anyhow, here's your solution:
WITH data AS (
SELECT main, sub_id, value_t
, abs(value_t
- lead(value_t) OVER (PARTITION BY main ORDER BY sub_id)) > 10 is_evil
FROM test
)
SELECT main, sub_id, value_t
, CASE max(is_evil::int)
OVER (PARTITION BY main ORDER BY sub_id
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING)
WHEN 1 THEN NULL ELSE 1 END newval
FROM data;
I'm using a CTE to prepare the data (computing whether a row is "evil"), and then the "max" window function is used to check if there were any "evil" rows before the current one, per partition.
EXISTS on an aggregating subquery:
UPDATE test u
SET value_t = NULL
WHERE EXISTS (
SELECT * FROM (
SELECT main,sub_id
, value_t , ABS(value_t - lag(value_t)
OVER (PARTITION BY main ORDER BY sub_id) ) AS absdiff
FROM test
) x
WHERE x.main = u.main
AND x.sub_id <= u.sub_id
AND x.absdiff >= 10
)
;
SELECT * FROM test
ORDER BY main, sub_id;
Result:
UPDATE 3
main | sub_id | value_t
------+--------+---------
1 | 1 | 8
1 | 2 | 7
1 | 3 | 3
1 | 4 |
1 | 5 |
2 | 1 | 3
2 | 2 | 1
2 | 3 | 1
2 | 4 | 8
2 | 5 |
(10 rows)

Select Table records in given order - Oracle

I have table lets say - Students, with 5 records and id(s) are 1 to 5, now i want to select the records - in a way that result should come like given sorting order of id column
id column should be resulted - 5,2,1,3,4 ( order may vary each time)
is there any other way to do this in oracle sql?
In mysql we have FIELD() clause to do this. I want to achieve this in oracle.
A quick and dirty way uses instr():
order by instr(',5,2,1,3,4,', ',' || id ',')
To handle values not in the string, you can convert them to NULL and sort those accordingly:
order by nullif(instr(',5,2,1,3,4,', ',' || id ','), 0) nulls last
Oracle uses heap-oraganized table, which means by default rows are stored in no particular order. However, while selecting the rows, you could mention ORDER BY clause explicitly to return the result set in ASC(default)/DESC order.
You could use DECODE/CASE to customize the sorting, for example:
with data as(
select level num from dual connect by level <=5
)
select num
from data
order by case
when num = 5 then 1
when num = 2 then 2
when num = 1 then 3
when num = 3 then 4
when num = 4 then 5
end;
NUM
----------
5
2
1
3
4

SQL: Limit by unknown number of occurences

Having a SQL table, consistent of the columns id and type. I Want to select only the first occurences of a type without using WHERE, since i dont know which types wild occur first, and without LIMIT since i don't know how many.
id | type
---------
1 | 1
2 | 1
3 | 2
4 | 2
5 | 2
E.g.:
SELECT id FROM table ORDER BY type (+ ?) should only return id 1 and 2
SELECT id FROM table ORDER BY type DESC (+ ?) should only return id 3, 4 and 5
Can this be acheived via standard and simple SQL operators?
That's easy. You must use a where clause and evaluate the minimum type there.
SELECT *
FROM mytable
WHERE type = (select min(type) from mytable)
ORDER BY id;
EDIT: Do the same with max() if you want to get the maximum type records.
EDIT: In case the types are not ascending as in your example, you will have to get the type of the minimum/maximum id instead of getting the minimum/maximum type:
SELECT *
FROM mytable
WHERE type = (select type from mytable where id = (select min(id) from mytable))
ORDER BY id;

How to select the biggest value from a list of duplicate entries

i'm trying to query a db which contains a list of transactions. The transactions that i need must be of a specific status and if duplicate cost code are present the query should only return the ID with the highest ID number
my sample table is as follows,
Table name = foo,
status that i need is 3
ID transaction date status cost code
1 20120101 3 5
2 20120101 3 5
3 20120101 4 7
in this example what i need is ID 2
Thanks
select * from foo where status = 3 order by id desc limit 1;
You can replace the 3 with whichever status you're interested in retrieving.
The "order by id desc limit 1" phrase will satisfy the "ID with the highest ID number" constraint.
You can use MAX to get the highest ID number if the selected columns are same
SELECT transaction_date, Status, cost_code, MAX(ID) As ID
FROM foo
GROUP BY transaction_date, Status, cost_code
Use this query:
SELECT MAX(ID) AS MaxID
FROM foo
WHERE status = 3