SQL Order By Complex Sort - sql

I have the following table in Oracle SQL dialect (being called with some java code)
Part # | Locker # | Serial # | Description
1 1 1 Alpha
1 1 1 Beta
1 1 1 Gamma
2 1 15 Alpha
2 7 17 Gamma
2 7 21 Beta
I am looking for a way to do the following sort:
Group part, locker, serial # together and sort the descriptions in ascending or descending order within each group WHILE also making sure the first record for each group is also correctly sorted in ascending or descending order (conflicts should sort in the desired order on part, locker, serial). so for example:
Sorting DESC would yield:
Part # | Locker # | Serial # | Description
2 7 17 Gamma
1 1 1 Gamma
1 1 1 Beta
1 1 1 Alpha
2 7 21 Beta
2 1 15 Alpha
How can I achieve this complex type of sorting? Is it even possible just with a query?

Interesting challenge, needing to group by 3 fields and select the highest Description for the group, keeping that in the query for sorting.... nice!
I've had a go, in MS-SQL 2008 which can be seen at http://sqlfiddle.com/#!3/422d2/10
There may be an easier way with MS T-SQL Ranking Functions, but this Derived Table of Groups should be fairly easily implemented in other SQLs.
This appears to give the sort order you require :
SELECT
p1.*, Groups.GMaxDescr
FROM Parts p1 INNER JOIN
(SELECT
p2.Part AS GPart,
p2.Locker AS GLocker,
p2.Serial AS GSerial,
Max(p2.Descr) as GMaxDescr
FROM Parts p2
GROUP BY Part, Locker, Serial
) AS Groups -- derived table of Groups with First (Max() for DESC) Name
-- join original rows with the Groups data for sorting
ON p1.Part = Groups.GPart
AND p1.Locker=Groups.GLocker
AND p1.Serial=Groups.GSerial
ORDER BY Groups.GMaxDescr DESC,
Part DESC,
Locker DESC,
Serial DESC

Related

How to get the values for every group of the top 3 types

I've got this table ratings:
id
user_id
type
value
0
0
Rest
4
1
0
Bar
3
2
0
Cine
2
3
0
Cafe
1
4
1
Rest
4
5
1
Bar
3
6
1
Cine
2
7
1
Cafe
5
8
2
Rest
4
9
2
Bar
3
10
3
Cine
2
11
3
Cafe
5
I want to have a table with a row for every pair (user_id, type) for the top 3 rated types through all users (ranked by sum(value) across the whole table).
Desired result:
user_id
type
value
0
Rest
4
0
Cafe
1
0
Bar
3
1
Rest
4
1
Cafe
5
1
Bar
3
2
Rest
4
3
Cafe
5
2
Bar
3
I was able to do this with two queries, one to get the top 3 and then another to get the rows where the type matches the top 3 types.
Does someone know how to fit this into a single query?
Get rows per user for the 3 highest ranking types, where types are ranked by the total sum of their value across the whole table.
So it's not exactly about the top 3 types per user, but about the top 3 types overall. Not all users will have rows for the top 3 types, even if there would be 3 or more types for the user.
Strategy:
Aggregate to get summed values per type (type_rnk).
Take only the top 3. (Break ties ...)
Join back to main table, eliminating any other types.
Order result by user_id, type_rnk DESC
SELECT r.user_id, r.type, r.value
FROM ratings r
JOIN (
SELECT type, sum(value) AS type_rnk
FROM ratings
GROUP BY 1
ORDER BY type_rnk DESC, type -- tiebreaker
LIMIT 3 -- strictly the top 3
) v USING (type)
ORDER BY user_id, type_rnk DESC;
db<>fiddle here
Since multiple types can have the same ranking, I added type to the sort order to break ties alphabetically by their name (as you did not specify otherwise).
Turns out, we don't need window functions - the ones with OVER and, optionally, PARTITION for this. (Since you asked in a comment).
I think you just want row_number(). Based on your results, you seem to want three rows per type, with the highest value:
select t.*
from (select t.*,
row_number() over (partition by type order by value desc) as seqnum
from t
) t
where seqnum <= 3;
Your description suggests that you might just want this per user, which is a slight tweak:
select t.*
from (select t.*,
row_number() over (partition by user order by value desc) as seqnum
from t
) t
where seqnum <= 3;

SQL Server - logical question - Get rank from IDs

I'm trying to write a query to solve a logical problem using Redshift POSTGRES 8.
Input column is a bunch of IDs and Order IDs and desired output is basically a rank of the ID as you can see in the screenshot.
(I'm sorry I'm not allowed to embed images into my StackOverflow posts yet)
If you could help me answer this question using SQL, that would be great! Thanks
Data
order id
id
size
desired output
1
abcd
2
1
1
abcd
2
1
1
efgh
5
2
1
efgh
5
2
1
efgh
5
2
1
efgh
5
2
2
aa
2
1
2
aa
2
1
2
bb
2
2
2
bb
2
2
SELECT
*,
DENSE_RANK() OVER (PARTITION BY order_item_id ORDER BY id) AS desired_result
FROM
your_table
DENSE_RANK() creates sequences starting from 1 according to the ORDER BY.
Any rows with the same ID will get the same value, and where RANK() would skip values in the event of ties DENSE_RANK() does not.
The PARTITION BY allows new sequences to be created for each different order_item_id.

I want to update a table to number the positions of orders

I have a legacy table with orders and their (sometimes multiple) rows with varchar values:
Order Row
1 1
1 1a
1 1b
2 1
2 2
I want to introduce another integer field "ROW_NR"
Order Row ROW_NR
1 1 1
1 1a 2
1 1b 3
2 1 1
2 2 2
which will hold the number of the row in the resp. order.
How can I accomplish updating the new field using SQL?
Window-Functions can be used to achive this, like described in Firebird windowing
(using keywords as column-name is very bad practice, in example replaced by _order_ and _row_)
ROW_NUMBER () OVER (PARTITION BY _order_ order by _row_ asc) as row_num

Cumulative count of duplicates

For a table looking like
ID | Value
-------------
1 | 2
2 | 10
3 | 3
4 | 2
5 | 0
6 | 3
7 | 3
I would like to calculate the number of IDs with a higher Value, for each Value that appears in the table, i.e.
Value | Position
----------------
10 | 0
3 | 1
2 | 4
0 | 6
This equates to the offset of the Value in a ORDER BY Value ordering.
I have considered doing this by calculating the number of duplicates with something like
SELECT Value, count(*) AS ct FROM table GROUP BY Value";
And then cumulating the result, but I guess that is not the optimal way to do it (nor have I managed to combine the commands accordingly)
How would one go about calculating this efficiently (for several dozens of thousands of rows)?
This seems like a perfect opportunity for the window function rank() (not the related dense_rank()):
SELECT DISTINCT ON (value)
value, rank() OVER (ORDER BY value DESC) - 1 AS position
FROM tbl
ORDER BY value DESC;
rank() starts with 1, while your count starts with 0, so subtract 1.
Adding a DISTINCT step (DISTINCT ON is slightly cheaper here) to remove duplicate rows (after computing counting ranks). DISTINCT is applied after window functions. Details in this related answer:
Best way to get result count before LIMIT was applied
Result exactly as requested.
An index on value will help performance.
SQL Fiddle.
You might also try this if you're not comfortable with window functions:
SELECT t1.value, COUNT(DISTINCT t2.id) AS position
FROM tbl t1 LEFT OUTER JOIN tbl t2
ON t1.value < t2.value
GROUP BY t1.value
Note the self-join.

ORDER BY Within JOIN Ordering

2I'm trying to perform a kind os sub ordering within an order based on a join.
SELECT
w.*
FROM
[dbo].[Scores] AS w
JOIN #ScoresTable AS s on s.AreaId = w.AreaId
WHERE
[Id] = #Id
ORDER BY
w.Score -- this is where the problem is
ScoresTable is a table variable that has a specific order so my selected data from w is based on its AreaId order.
What I'm trying to do is then sort these results based on the w.Score column but that just seems to "override" the order I get (correctly) from the JOIN clause.
How do I add the Score order whilst still respecting the AreaId order established based on the JOIN?
I've tried using:
ORDER BY
s.AreaId, w.Score
The JOIN results in the correct ordering based on #ScoresTable.AreaId order like:
AreaId
5
3
6
Without the ORDER BY clause I get this (AreaId is ordered as required):
Id Score WheId AreaId ContextId
25 25 2 5 1
26 50 2 5 2
27 2 2 5 3
28 10 2 5 4
29 5 2 5 5
39 1 2 3 11
40 30 2 6 12
All I want to do now is order this on the Score column to get this result set (AreaId is ordered as required and sorted on Score):
Id Score WheId AreaId ContextId
27 2 2 5 3
29 5 2 5 5
28 10 2 5 4
25 25 2 5 1
26 50 2 5 2
39 1 2 3 11
40 30 2 6 12
I would add an AreaOrder column to your #ScoresTable, so it has two columns
AreaOrder, AreaId and then you can
SELECT w.*
FROM
[dbo].[Scores] AS w
JOIN #ScoresTable AS s on s.AreaId = w.AreaId
WHERE [Id] = #Id
ORDER BY
s.AreaOrder, w.Score
There is no such thing as JOIN order. The order you observe when there is no ORDER BY clause is arbitrary. It may be this with these data but once you have one or more (or less) rows or different load on the server or different distribution on the any of the joined tables or the moon gets closer to earth, the query plan may be different and the order of the result set will not be the same.
The point is that you can't and you shouldn't expect a specific ordering if you do not provide an ORDER BY clause.
And from the reuslts you have shown, we may guess that the ordering is based on Id ASC
Now from the limited data you have shown, it seems that you want to group the rows with same AreaId together and order them by Score. The ordering between the various values of AreaId though, seems strange (5 -> 3 -> 6).
If you want that to be affected by the Id column, the following may be what you are after:
ORDER BY
MIN(w.Id) OVER (PARTITION BY w.AreaId),
w.Score ;
(and note that if you replace the MIN(w.id) with MAX(w.Id), you'll get the same results with the specific data. If the rest of your data comply with that, we can't know for sure.)
I see a descending order in AreaId, so you would want to repeat that in your query. The Order By in the current query is always leading over the Order By in any subquery or view.
ORDER BY
s.AreaId desc, w.Score