I have a table structure as given below and what I'd like to be able to do is retrieve the top three records with the highest value for each Company code.
I've googled and I couldn't find a better way so hopefully you guys can help me out.
By the way, I'm attempting this in MySQL and SAP HANA. But I am hoping that I can grab the "structure" if the query for HANA if I can get help for only MySQL
Thanks much!
Here's the table:
http://pastebin.com/xgzCgpKL
In MySQL you can do
To get exactly three records per group (company) no matter ties emulating ROW_NUMBER() analytic function. Records with the same value get the same rank.
SELECT company, plant, value
FROM
(
SELECT company, plant, value, #n := IF(#g = company, #n + 1, 1) rnum, #g := company
FROM table1 CROSS JOIN (SELECT #n := 0, #g := NULL) i
ORDER BY company, value DESC, plant
) q
WHERE rnum <= 3;
Output:
| COMPANY | PLANT | VALUE |
|---------|-------|-------|
| 1 | C | 5 |
| 1 | B | 4 |
| 1 | A | 3 |
| 2 | G | 6 |
| 2 | C | 5 |
| 2 | D | 3 |
| 3 | E | 8 |
| 3 | A | 7 |
| 3 | B | 3 |
Get all records per group that have a rank from 1 to 3 emulating DENSE_RANK() analytic function
SELECT company, plant, value
FROM
(
SELECT company, plant, value, #n := IF(#g = company, IF(#v = value, #n, #n + 1), 1) rnum, #g := company, #v := value
FROM table1 CROSS JOIN (SELECT #n := 0, #g := NULL, #v := NULL) i
ORDER BY company, value DESC, plant
) q
WHERE rnum <= 3;
Output:
| COMPANY | PLANT | VALUE |
|---------|-------|-------|
| 1 | C | 5 |
| 1 | B | 4 |
| 1 | A | 3 |
| 1 | E | 3 |
| 1 | G | 3 |
| 2 | G | 6 |
| 2 | C | 5 |
| 2 | D | 3 |
| 3 | E | 8 |
| 3 | A | 7 |
| 3 | B | 3 |
| 3 | G | 3 |
Here is SQLFiddle demo
UPDATE: Now it looks like HANA supports analytic functions so the queries will look like
SELECT company, plant, value
FROM
(
SELECT company, plant, value,
ROW_NUMBER() OVER (PARTITION BY company ORDER BY value DESC) rnum
FROM table1
)
WHERE rnum <= 3;
SELECT company, plant, value
FROM
(
SELECT company, plant, value,
DENSE_RANK() OVER (PARTITION BY company ORDER BY value DESC) rank
FROM table1
)
WHERE rank <= 3;
Here is SQLFiddle demo It's for Oracle but I believe it will work for HANA too
Related
I have 3 tables, 1 with prices, another with customers and the last with dependents of customers like a health insurance. When I have 1 customer and 1 dependent the value for the first dependent is one, when I have two dependents the value of second dependent is different, but the first is the same.
I need a query that shows the dependents table and gets the value of each dependent, 4 dependents are the maximum.
Dependent Table
+--------------+--------+---------+------------+
| id_dependent | name | number | primary_id |
+--------------+--------+---------+------------+
| 51 | Carlos | 956585 | 2 |
| 52 | João | 985868 | 2 |
| 53 | Jaime | 985868 | 2 |
| 54 | Evan | 985847 | 3 |
| 55 | Kaus | 584788 | 3 |
+--------------+--------+---------+------------+
Price Table
+----------+---------+-----------+-------+---------+
| price_id | Product | Dependent | Value | Plan_id |
+----------+---------+-----------+-------+---------+
| 11 | Plan1 | 1 | 15,00 | 56 |
| 12 | Plan1 | 2 | 13,50 | 56 |
| 13 | Plan1 | 3 | 11,50 | 56 |
+----------+---------+-----------+-------+---------+
What I need
+--------------+--------+--------+------------+-------+
| id_dependent | name | number | primary_id | Value |
+--------------+--------+--------+------------+-------+
| 51 | Carlos | 956585 | 2 | 15,00 |
| 52 | João | 985868 | 2 | 13,50 |
| 53 | Jaime | 985868 | 2 | 11,50 |
| 54 | Evan | 985847 | 3 | 15,00 |
| 55 | Kaus | 584788 | 3 | 13,50 |
+--------------+--------+--------+------------+-------+
How can I do this?
You can use row_number() to enumerate the dependents and then join:
select d.*, p.price
from (select d.*, row_number() over (partition by primary_id order by id_dependent) as seqnum
from dependents d
) d left join
price p
on p.dependent = d.seqnum and p.plan_id = 56;
In earlier versions of MySQL, you can use variables:
select d.*, p.price
from (select d.*,
(#rn := if(#p = d.primary_id, #rn + 1,
if(#p := d.primary_id, 1, 1)
)
) as seqnum
from (select d.* from dependents d order by primary_id, id_dependent) d cross join
(select #p := -1, #rn := 0) params
) d left join
price p
on p.dependent = d.seqnum and p.plan_id = 56;
Notes on the use of variables:
They are deprecated and may be removed in future versions of MySQL.
The order by is in a subquery; that is needed in some versions of MySQL. Variables and order by don't always play well together.
Both variables are assigned in the same expression. MySQL does not guarantee the order of evaluation of expressions, so this is very important for working code.
This works with mysql 5.6 and 5.7
Select d.`id_dependent`, d.`name`, d.`number`, d.`primary_id`, p.`Value`
From (
SELECT d.*,
if (#primid = primary_id,#curRank := #curRank + 1,#curRank := 1) AS rank,
#primid := primary_id
FROM Dependent d, (SELECT #curRank := 0) r , (SELECT #primid := 0) s
ORDER BY primary_id,id_dependent) d
left join price p on p.Dependent = d.rank
Order by d.`id_dependent`;
Which results in
id_dependent name number primary_id Value
51 Carlos 956585 2 15,00
52 João 985868 2 13,50
53 Jaime 985868 2 11,50
54 Evan 985847 3 15,00
55 Kaus 584788 3 13,50
I have tables
Student
--------------
| Name | Grade |
--------------
--------------
| John | A+ |
--------------
| Tina | B |
--------------
And Type
--------------
| Type | Class |
--------------
--------------
| Good | 12 |
--------------
| Avg | 11 |
--------------
Now I want to get the result by merging them row-wise so the result of the SQL query would be,
-----------------------------
| Name | Grade | Type | Class |
-----------------------------
| John | A+ | Good | 12 |
-----------------------------
| Tine | B | Avg | 11 |
-----------------------------
Assuming there are the same number of columns in each table, you can link them by row number. Without an ORDER BY clause though the row numbering is indeterminate and you can't guarantee results.
In MySQL 5.7 and below:
SELECT Name, Grade, Type, Class
FROM (SELECT *, #rownum := #rownum + 1 AS rownum
FROM Student
CROSS JOIN (SELECT #rownum := 0) r
) s
JOIN (SELECT *, #rownum2 := #rownum2 + 1 AS rownum
FROM Type
CROSS JOIN (SELECT #rownum2 := 0) r
) t ON t.rownum = s.rownum
In MySQL 8.0 and above:
SELECT Name, Grade, Type, Class
FROM (SELECT *, ROW_NUMBER() OVER () AS rownum
FROM Student) s
JOIN (SELECT *, ROW_NUMBER() OVER () rownum
FROM Type) t ON t.rownum = s.rownum
Output:
Name Grade Type Class
John A+ Good 12
Tina B Avg 11
Demo on dbfiddle
I have a table (called users) I need rank of users based on their score but I want rank on the bases of users max score.
+-----------+------------+
| User_id | Score |
+-----------+------------+
| 1 | 12258 |
| 1 | 112 |
| 2 | 9678 |
| 5 | 9678 |
| 3 | 689206 |
| 3 | 1868 |
Expect result
+-----------+------------+---------+
| User_id | Score | Rank |
+-----------+------------+---------+
| 3 | 689206 | 1 |
| 1 | 12258 | 2 |
| 2 | 9678 | 3 |
| 5 | 9678 | 3 |
You are looking for DENSE_RANK, But it supports mysql version higher than 8.0
use correlated-subquery to get max value by each User_id
use two variables one to store rank another to store previous value to make the DENSE_RANK number.
look like this.
CREATE TABLE T(
User_id int,
Score int
);
insert into t values (1,12258);
insert into t values (1,112);
insert into t values (2,9678);
insert into t values (5,9678);
insert into t values (3,689206);
insert into t values (3,1868);
Query 1:
SELECT User_id,Score,Rank
FROM (
SELECT User_id,
Score,
#rank :=IF(#previous = t1.score, #rank, #rank + 1) Rank,
#previous := t1.Score
FROM T t1 CROSS JOIN (SELECT #Rank := 0,#previous := 0) r
WHERE t1.Score =
(
SELECT MAX(Score)
FROM T tt
WHERE t1.User_id = tt.User_id
)
ORDER BY Score desc
) t1
Results:
| User_id | Score | Rank |
|---------|--------|------|
| 3 | 689206 | 1 |
| 1 | 12258 | 2 |
| 2 | 9678 | 3 |
| 5 | 9678 | 3 |
Another trick in MySql 5.7 to calculate a DENSE_RANK (like in MySql 8) is to use a CASE WHEN with the variable assignments in it.
SELECT User_id, MaxScore AS Score,
CASE
WHEN MaxScore = #prevScore THEN #rnk
WHEN #prevScore := MaxScore THEN #rnk := #rnk+1
ELSE #rnk := #rnk+1
END AS Rank
FROM
(
SELECT User_id, MAX(Score) AS MaxScore
FROM YourTable
GROUP BY User_id
ORDER BY MaxScore DESC, User_id
) AS q
CROSS JOIN (SELECT #rnk := 0, #prevScore := null) AS vars
You can test it here on rextester.
I have a big table where data are structured like this
My table car
id_car | Site_car | descr_car
-----------------------------------
1 | onesite | onedesc
2 | twosite | twodesc
3 | twosite | onedesc
4 | onesite | onedesc
5 | twosite | twodesc
6 | onesite | onedesc
7 | treesite | onedesc
8 | treesite | onedesc
I want to be able to display the column site_car randomly but with onesite first twosite second and threesite third each 15 time or more
what I want to display
id_car | Site_car | descr_car
-----------------------------------
4 | onesite | onedesc
3 | twosite | twodesc
7 | treesite | onedesc
1 | onesite | onedesc
2 | twosite | twodesc
6 | treesite | onedesc
Do you guys have idea?
Thx
This is tricky in MySQL. The idea is to enumerate the rows and then order by that enumeration:
select c.*
from (select c.*,
(#rn := if(#sc = site_car, #rn + 1,
if(#sc := site_car, 1, 1)
)
) as rn
from (select c.*
from car c
order by site_car, id_car
) c cross join
(select #sc := -1, #rn := 0) params
) c
order by rn, field(site_car, 'onesite', 'twosite', 'threesite');
By the way this is much simpler in MySQL 8+:
select c.*
from car c
order by row_number() over (partition by site_car order by id_car),
field(site_car, 'onesite', 'twosite', 'threesite');
you can try this
SET #Fno:= 999
SET #Sno:= 9999
SET #Tno:= 99999
SELECT id_car, Site_car , descr_car from
(SELECT
#row_number:=CASE
WHEN Site_car = 'onesite' THEN #Fno + 1
WHEN Site_car = 'twosite' THEN #Sno + 1
ELSE #Tno+1
END AS num,* from car) order by num
I have a big MySQL table on which I'd like to calculate a cumulative product. This product has to be calculated for each group, a group is defined by the value of the first column.
For example :
name | number | cumul | order
-----------------------------
a | 1 | 1 | 1
a | 2 | 2 | 2
a | 1 | 2 | 3
a | 4 | 8 | 4
b | 1 | 1 | 1
b | 1 | 1 | 2
b | 2 | 2 | 3
b | 1 | 2 | 4
I've seen this solution but don't think it would be efficient to join or subselect in my case.
I've seen this solution which is what I want except it does not partition by name.
This is similar to a cumulative sum:
select t.*,
(#p := if(#n = name, #p * number,
if(#n := name, number, number)
)
) as cumul
from t cross join
(select #n := '', #p := 1) params
order by name, `order`;