SQL: Ranking Sections separately of a Rollup over multiple columns - sql

I try to do a Rollup over multiple columns and then apply a ranking on each stage/section of the rollup process. The result should look somewhat like the following:
| ColA | ColB | ColC | RankingCriteria | Ranking |
|------|------|------|-----------------|---------|
| - | - | - | 10 | 1 |
|------|------|------|-----------------|---------|
| A | - | - | 10 | 1 |
| B | - | - | 8 | 2 |
|------|------|------|-----------------|---------|
| A | a | - | 9 | 1 |
| A | b | - | 7 | 2 |
| A | c | - | 5 | 3 |
| A | d | - | 2 | 4 |
|------|------|------|-----------------|---------|
| B | a | - | 8 | 1 |
| B | c | - | 7 | 2 |
| B | b | - | 2 | 3 |
|------|------|------|-----------------|---------|
| A | a | x | 7 | 1 |
| A | a | y | 5 | 2 |
| A | a | z | 4 | 3 |
|------|------|------|-----------------|---------|
| A | b | y | 6 | 1 |
|------|------|------|-----------------|---------|
| A | c | w | 10 | 1 |
| A | c | y | 10 | 1 |
| A | c | z | 8 | 2 |
| A | c | x | 6 | 3 |
|------|------|------|-----------------|---------|
| A | d | y | 4 | 1 |
|------|------|------|-----------------|---------|
| B | a | w | 10 | 1 |
| B | a | x | 8 | 2 |
|------|------|------|-----------------|---------|
| B | b | y | 6 | 1 |
| B | b | z | 5 | 2 |
| B | b | w | 4 | 3 |
|------|------|------|-----------------|---------|
| B | c | x | 6 | 1 |
|------|------|------|-----------------|---------|
So as you can see each grouping set has it's own ranking.
The basic Rollup-Query for this is simple but the ranking is giving me headaches and I am running out of ideas on how to achieve this.
Select ColA, ColB, ColC, RankingCriteria
From table
Group By Rollup(ColA, ColB, ColC)
The problem is that I cannot use a normal Rank() over (Partition by ...) because there is no partition I could use that'd work on the whole thing.

I think this will produce what you want:
SELECT r.*,
row_number() over (partition by (case when colb is null and colc is null and cola is not null
then 1 else 0 end),
(case when colb is null and colc is null and cola is not null
then NULL else A end),
(case when colb is null and colc is null and cola is not null
then NULL else B end)
order by RankingCriteria desc) as seqnum
FROM (Select ColA, ColB, ColC, RankingCriteria
From table
Group By Rollup(ColA, ColB, ColC)
) r;
The way I read the logic is that partitioning by A and B works for all but the second group. That is why this uses the three case statements.

Related

How to assign duplicate increment in SQL?

While going through SQL columns, if we find text match "NEW" in Calc column, update the incrementing a count starting with 1 in Results column.
It should look like this on the output:
The following uses an id column to resolve the order issue. Replace that with your corresponding expression. This also addresses the requirement to start the display sequence with 1 and also show 0 for the 'NEW' rows.
The SQL (updated):
SELECT logs.*
, CASE WHEN text = 'NEW' THEN 0
ELSE
COALESCE(SUM(CASE WHEN text = 'NEW' THEN 1 END) OVER (PARTITION BY xrank ORDER BY id)+1, 1)
END AS display
FROM logs
ORDER BY id
The result:
+----+-------+------+---------+
| id | xrank | text | display |
+----+-------+------+---------+
| 1 | 1 | A | 1 |
| 2 | 1 | B | 1 |
| 3 | 1 | C | 1 |
| 4 | 1 | NEW | 0 |
| 5 | 1 | D | 2 |
| 6 | 1 | Q | 2 |
| 7 | 1 | B | 2 |
| 8 | 1 | NEW | 0 |
| 9 | 1 | D | 3 |
| 10 | 1 | Z | 3 |
| 11 | 2 | A | 1 |
| 12 | 2 | B | 1 |
| 13 | 2 | C | 1 |
| 14 | 2 | NEW | 0 |
| 15 | 2 | D | 2 |
| 16 | 2 | Q | 2 |
| 17 | 2 | B | 2 |
| 18 | 2 | NEW | 0 |
| 19 | 2 | D | 3 |
| 20 | 2 | Z | 3 |
+----+-------+------+---------+
You need a column that specifies the ordering for the table. With that, just use a cumulative sum:
select t.*,
1 + sum(case when Calc = 'NEW' then 1 else 0 end) over (partition by Rank_Id order by Seq) as display
from t;

Using LAG function with higher offset

Suppose we have the following input table
cat | value | position
------------------------
1 | A | 1
1 | B | 2
1 | C | 3
1 | D | 4
2 | C | 1
2 | B | 2
2 | A | 3
2 | D | 4
As you can see, the values A,B,C,D change position in each category, I want to track this change by adding a column change in front of each value, the output should look like this:
cat | value | position | change
---------------------------------
1 | A | 1 | NULL
1 | B | 2 | NULL
1 | C | 3 | NULL
1 | D | 4 | NULL
2 | C | 1 | 2
2 | B | 2 | 0
2 | A | 3 | -2
2 | D | 4 | 0
For example C was in position 3 in category 1 and moved to position 1 in category 2 and therefore has a change of 2. I tried inmplementing this using the LAG() function with an offset of 4 but I failed, how can I write this query.
Use lag() - with the proper partition by clause:
select
t.*,
lag(position) over(partition by value order by cat) - position change
from mytable t
You can use lag and then order by to maintain original order. Here is the demo.
select
*,
lag(position) over (partition by value order by cat) - position as change
from yourTable
order by
cat, position
output:
| cat | value | position | change |
| --- | ----- | -------- | ------ |
| 1 | A | 1 | null |
| 1 | B | 2 | null |
| 1 | C | 3 | null |
| 1 | D | 4 | null |
| 2 | C | 1 | 2 |
| 2 | B | 2 | 0 |
| 2 | A | 3 | -2 |
| 2 | D | 4 | 0 |
I think you just want lag() with the right partition by:
select t.*,
(lag(position) over (partition by value order by cat) - position) as change
from t;
Here is a db<>fiddle.

Limit a sorted number of rows joined

I have two tables, A and B, and a join table M. I want to, for each A.id, get the top 2 B.id's sorting on the value in table M, producing the results below. This is running on an Azure SQL database
Table A Table M Table B
+-----+ +-----+-----+-------+ +-----+
| Id | | AId | BId | Value | | Id |
+-----+ +-----+-----+-------+ +-----+
| 1 | | 1 | 3 | 4 | | 1 |
| 2 | | 1 | 2 | 3 | | 2 |
| 3 | | 3 | 2 | 3 | | 3 |
| 4 | | 3 | 5 | 6 | | 4 |
+-----+ | 3 | 3 | 4 | | 5 |
| 4 | 1 | 2 | +-----+
| 4 | 2 | 1 |
| 4 | 4 | 3 |
+-----+-----+-------+
Result
+-----+-----+-------+
| AId | BId | Value |
+-----+-----+-------+
| 1 | 3 | 4 |
| 1 | 2 | 3 |
| 3 | 5 | 6 |
| 3 | 3 | 4 |
| 4 | 1 | 2 |
| 4 | 4 | 3 |
+-----+-----+-------+
I know that I can select all the M.AId rows where they equal 1, sort it, and limit by 2, but I need to do this for every row in Table A. I've made an attempt to use group by, but I wasn't sure how to sort and limit it. I've also tried to search for resources associated with this issue but I couldn't find any resources.
(I also wasn't sure how to word the title for this issue)
You can just use ROW_NUMBER:
SELECT
AId, BId, Value
FROM (
SELECT *,
Rn = ROW_NUMBER() OVER(PARTITION BY AId ORDER BY Value DESC)
FROM M
) t
WHERE Rn <= 2

Select 5 of each distinct value

I have the following table in PostgreSQL:
| a | b | c |
===================
| 'w' | 2 | 3 |
| 'w' | 7 | 2 |
| 'w' | 8 | 1 |
| 'w' | 3 | 6 |
| 'w' | 0 | 8 |
| 'w' | 2 | 9 |
| 'w' | 2 | 9 |
| 'z' | 4 | 9 |
| 'z' | 0 | 9 |
| 'z' | 0 | 8 |
| 'z' | 3 | 6 |
| 'z' | 2 | 7 |
| 'z' | 3 | 1 |
| 'z' | 3 | 2 |
| 'z' | 3 | 3 |
I want to select all records, but limit them to 5 records for each distinct value in column a.
So the result would look like:
| a | b | c |
===================
| 'w' | 2 | 3 |
| 'w' | 7 | 2 |
| 'w' | 8 | 1 |
| 'w' | 3 | 6 |
| 'w' | 0 | 8 |
| 'z' | 4 | 9 |
| 'z' | 0 | 9 |
| 'z' | 0 | 8 |
| 'z' | 3 | 6 |
| 'z' | 2 | 7 |
What is the most effecient way to achieve that in RoR? Thanks!
you can use row_number, but you have to specify order or you will get unpredictable resutls
with cte as (
select
*,
row_number() over(partition by a order by b, c) as row_num
from table1
)
select a, b, c
from cte
where row_num <= 5

Crosstab multi columns

Hello I have a problem with SQL in SQL Server 2005.
Suppose that I have a table called myTable with data as below:
| NAME | CREDIT | GRADE | YEAR | SEMESTER |
---------------------------------------------
| Name1 | 1 | A | 1 | 1 |
| Name2 | 4 | B | 1 | 1 |
| Name3 | 2 | E | 1 | 1 |
| Name4 | 7 | F | 1 | 1 |
| Name5 | 4 | A | 1 | 2 |
| Name6 | 3 | C | 1 | 2 |
| Name7 | 6 | D | 1 | 2 |
| Name8 | 1 | A | 1 | 2 |
| Name9 | 1 | A | 1 | 2 |
| Name10 | 1 | A | 1 | 2 |
| Name11 | 3 | C | 2 | 1 |
| Name12 | 6 | E | 2 | 1 |
| Name13 | 4 | C | 2 | 1 |
| Name14 | 2 | B | 2 | 2 |
| Name15 | 1 | A | 2 | 2 |
| Name16 | 1 | A | 2 | 2 |
| Name17 | 1 | A | 2 | 2 |
| Name18 | 5 | D | 3 | 1 |
| Name19 | 1 | A | 3 | 1 |
| Name20 | 1 | A | 3 | 1 |
| Name18 | 5 | D | 3 | 2 |
| Name19 | 1 | A | 3 | 2 |
| Name20 | 1 | A | 3 | 2 |
I want to output the result as below:
| NAM1 | CRDT1 | GRD1 | YEAR1 | SEMER1 | NAM2 | CRDT2 | GRD2 | YEAR2 | SEMES2 |
-----------------------------------------------------------------------------
| Name1| 1 | A | 1 | 1 |Name5 | 4 | A | 1 | 2 |
| Name2| 4 | B | 1 | 1 |Name6 | 3 | C | 1 | 2 |
| Name3| 2 | E | 1 | 1 |Name7 | 6 | D | 1 | 2 |
| Name4| 7 | F | 1 | 1 |Name8 | 1 | A | 1 | 2 |
|Name9 | 1 | A | 1 | 2 |
|Name10| 1 | A | 1 | 2 |
| Name11| 3 | C | 2 | 1 |Name14| 2 | B | 2 | 2 |
| Name12| 6 | E | 2 | 1 |Name15| 1 | A | 2 | 2 |
| Name13| 4 | C | 2 | 1 |Name16| 1 | A | 2 | 2 |
|Name17| 1 | A | 2 | 2 |
| Name18| 5 | D | 3 | 1 |Name18| 5 | D | 3 | 2 |
| Name19| 1 | A | 3 | 1 |Name19| 1 | A | 3 | 2 |
| Name20| 1 | A | 3 | 1 |Name20| 1 | A | 3 | 2 |
Where
- Nam1= Name in Semester 1
- CRDT1= Credit in Semester 1
- GRD1= Grade in Semester 1
- Year1= Year in Semester 1
- Semer1 = Semester in Semester 1
- Nam2= Name in Semester 2
- CRDT2= Credit in Semester 2
- GRD2= Grade in Semester 2
- Year2= Year in Semester 2
- Semer2 = Semester in Semester 2
Please go to this URL to test this SQL: http://sqlfiddle.com/#!3/196c6/1
How Can I create SQL to make output like this?
select
s1.Name as nam1, s1.credit as crdt1, s1.Year as year1, s1.semester as semer1,
s2.Name as nam2, s2.credit as crdt2, s2.Year as year2, s2.semester as semer2
from
(select *, ROW_NUMBER() over (partition by year order by name) rn from myTable where semester=1 ) s1
full outer join
(select *, ROW_NUMBER() over (partition by year order by name) rn from myTable where semester=2 ) s2
on s1.year = s2.year
and s1.rn = s2.rn
I don't like doing an outer join, when a simple group by is sufficient:
select max(case when semester = 1 then Name end) as name1,
max(case when semester = 1 then credit end) as credit1,
max(case when semester = 1 then year end) as year1,
max(case when semester = 1 then semester end) as semester1,
max(case when semester = 2 then Name end) as name2,
max(case when semester = 2 then credit end) as credit2,
max(case when semester = 2 then year end) as year2,
max(case when semester = 2 then semester end) as semester2
from (select t.*,
row_number() over (partition by semester order by name) as rownum
from t
) t2
group by rownum
order by rownum
select Name,credit, grade, year,semester from myTable
group by semester,year, Name,credit, grade;
now we have to make a dynamic query with this previous query:
create as temporary table as there are semster first
create dynamically a select query with all fields of all semester table in a loop:
foreach temporary table concat all fields of this table in select query
and add construct label field with semester value of this table
and add temporary table with union
'select' + #tbls1.fieldName + ',' + ... + + #tbls2.fieldName +