ORDER BY Within JOIN Ordering - sql

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

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;

Update new foreign key column of existing table with ids from another table in SQL Server

I have an existing table to which I have added a new column which is supposed to hold the Id of a record in another (new) table.
Simplified structure is sort of like this:
Customer table
[CustomerId] [GroupId] [LicenceId] <-- new column
Licence table <-- new table
[LicenceId] [GroupId]
The Licence table has a certain number of licences per group than can be assigned to customers in that same group. There are multiple groups, and each group has a variable number of customers and licences.
So say there are 100 licences available for group 1 and there are 50 customers in group 1, so each can get a license. There are never more customers than there are licences.
Sample
Customer
[CustomerId] [GroupId] [LicenceId]
1 1 NULL
2 1 NULL
3 1 NULL
4 1 NULL
5 2 NULL
6 2 NULL
7 2 NULL
8 3 NULL
9 3 NULL
Licence
[LicenceId] [GroupId]
1 1
2 1
3 1
4 1
5 1
6 1
7 2
8 2
9 2
10 2
11 2
12 3
13 3
14 3
15 3
16 3
17 3
Desired outcome
Customer
[CustomerId] [GroupId] [LicenceId]
1 1 1
2 1 2
3 1 3
4 1 4
5 2 7
6 2 8
7 2 9
8 3 12
9 3 13
So now I have to do this one time update to give every customer a licence and I have no idea how to go about it.
I'm not allowed to use a cursor. I can't seem to do a MERGE UPDATE, because joining the Customer to the Licence table by GroupId will result in multiple hits.
How do I assign each customer the next available LicenceId within their group in one query?
Is this even possible?
You can use window functions:
with c as (
select c.*, row_number() over (partition by groupid order by newid()) as seqnum
from customers c
),
l as (
select l.*, row_number() over (partition by groupid order by newid()) as seqnum
from licenses c
)
update c
set c.licenceid = l.licenseid
from c join
l
on c.seqnum = l.seqnum and c.groupid = l.groupid;
This assigns the licenses randomly. That is really just for fun. The most efficient method is to use:
row_number() over (partition by groupid order by (select null)) as seqnum
SQL Server often avoids an additional sort operation in this case.
But you might want to order them by something else -- for instance by the ordering of the customer ids, or by some date column, or something else.
Gordon has put it very well in his answer.
Let me break it down into simpler steps for you.
Step 1. Use the ROW_NUMBER() function to assign a SeqNum to the Customers. Use PARTITION BY GroupId so that the number starts from 1 in every group. I would ORDER BY CustomerId
Step 2. Use the ROW_NUMBER() function to assign a SeqNum to the Licences. Use PARTITION BY GroupId so that the number starts from 1 in every group. ORDER BY LicenseId because your ask is to "assign each customer the next available LicenceId within their group".
Now use these 2 queries to update LicenseId in Customer table.

Delete rows, which are duplicated and follow each other consequently

It's hard to formulate, so i'll just show an example and you are welcome to edit my question and title.
Suppose, i have a table
flag id value datetime
0 b 1 343 13
1 a 1 23 12
2 b 1 21 11
3 b 1 32 10
4 c 2 43 11
5 d 2 43 10
6 d 2 32 9
7 c 2 1 8
For each id i want to squeze the table by flag columns such that all duplicate flag values that follow each other collapse to one row with sum aggregation. Desired result:
flag id value
0 b 1 343
1 a 1 23
2 b 1 53
3 c 2 75
4 d 2 32
5 c 2 1
P.S: I found functions like CONDITIONAL_CHANGE_EVENT, which seem to be able to do that, but the examples of them in docs dont work for me
Use the differnece of row number approach to assign groups based on consecutive row flags being the same. Thereafter use a running sum.
select distinct id,flag,sum(value) over(partition by id,grp) as finalvalue
from (
select t.*,row_number() over(partition by id order by datetime)-row_number() over(partition by id,flag order by datetime) as grp
from tbl t
) t
Here's an approach which uses CONDITIONAL_CHANGE_EVENT:
select
flag,
id,
sum(value) value
from (
select
conditional_change_event(flag) over (order by datetime desc) part,
flag,
id,
value
from so
) t
group by part, flag, id
order by part;
The result is different from your desired result stated in the question because of order by datetime. Adding a separate column for the row number and sorting on that gives the correct result.

SQL Order By Complex Sort

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

SQL RANDOM ORDER BY ON JOINED TABLE

I have 2 tables: Persons(idPerson INT) and Questions(idQuestion INT).
I want to insert the data into a 3rd table: OrderedQuestions(idPerson INT, idQuestion INT, questionRank INT)
I want to assign all the questions to all the persons but in a random order.
I thought of doing a CROSS JOIN but then, I get the same order of questions for every persons.
INSERT INTO OrderedQuestions
SELECT idPerson, idQuestion, questionRank FROM Persons
CROSS JOIN
(SELECT idQuestion,ROW_NUMBER() OVER (ORDER BY NEWID()) as questionRank
FROM Questions) as t
How can I achieve such a random, distinct ordering for every persons?
Obviously, I want the solution to be as fast as possible.
(It can be done using TSQL or Linq to SQL)
Desired results for 3 persons and 5 questions:
idPerson idQuestion questionRank
1. 1 18 1
2. 1 14 2
3. 1 25 3
4. 1 31 4
5. 1 2 5
6. 2 2 1
7. 2 25 2
8. 2 31 3
9. 2 18 4
10. 2 14 5
11. 3 31 1
12. 3 18 2
13. 3 14 3
14. 3 25 4
15. 3 2 5
I just edited the results (Since the IDs are autogenerated, they can't be used to order the questions).
This could probably be written more efficently, but it meets all the reqs.
SELECT
idperson,
idQuestion,
ROW_NUMBER() OVER (PARTITION BY personid ORDER BY ordering) as questionRank
FROM (
SELECT idperson, idQuestion, ordering
FROM person
CROSS JOIN
(
SELECT idQuestion, NewID() as ordering FROM Question
) as t
) as a
order by personid, QuestionRank