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
Related
This question already has answers here:
How can I SELECT rows with MAX(Column value), PARTITION by another column in MYSQL?
(22 answers)
Closed 2 years ago.
I have three tables, e.g. fruits:
+----+--------+---------+
| id | type | variety |
+----+--------+---------+
| 1 | orange | 5 |
| 2 | orange | 7 |
| 3 | apple | 1 |
| 4 | apple | 0 |
+----+--------+---------+
containers:
+----+--------+
| id | year |
+----+--------+
| 1 | 2015 |
| 2 | 2020 |
| 3 | 2020 |
| 4 | 2018 |
+----+--------+
and inclusion:
+----+----------+---------+
| id | fruit_id | cont_id |
+----+----------+---------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 1 |
| 4 | 3 | 2 |
| 5 | 3 | 3 |
| 6 | 3 | 4 |
+----+----------+---------+
I need to select "newest" container for each fruit variety if there is any:
+----+--------+----------+------+
| id | type | variety | year |
+----+--------+----------+------+
| 1 | orange | 5 | 2020 |
| 2 | orange | 7 | 2015 |
| 3 | apple | 1 | 2020 |
| 4 | apple | 0 | NULL |
+----+--------+----------+------+
I'm trying something like
SELECT * FROM `fruits`
LEFT JOIN (SELECT * FROM `containers`
JOIN `inclusion` ON `inclusion`.`cont_id` = `containers`.`id`
WHERE `fruit_id` = `fruits`.`id`
ORDER BY `year` DESC LIMIT 1
) `tops` ON `tops`.`fruit_id` = `fruits`.`id`;
but it says
ERROR 1054 (42S22): Unknown column 'fruits.id' in 'where clause'
is there any way to get the required result?
I'm using mariadb my now, but migration to mysql could happen, so I need a solution working on both servers.
What if I also add cnt_type table:
+----+---------+
| id | type |
+----+---------+
| 1 | box |
| 2 | package |
+----+---------+
and containers would include type:
+----+--------+------+
| id | year | type |
+----+--------+------+
| 1 | 2015 | 1 |
| 2 | 2020 | 1 |
| 3 | 2020 | 2 |
| 4 | 2018 | 2 |
+----+--------+------+
so I need to extract top-year of each container type including each fruit variety?
+----+--------+----------+----------+------+
| id | type | variety | cnt_type | year |
+----+--------+----------+----------+------+
| 1 | orange | 5 | box | 2020 |
| 1 | orange | 5 | package | NULL |
| 2 | orange | 7 | box | 2015 |
| 2 | orange | 7 | package | NULL |
| 3 | apple | 1 | box | 2020 |
| 3 | apple | 1 | package | 2020 |
| 4 | apple | 0 | box | NULL |
| 4 | apple | 0 | package | NULL |
+----+--------+----------+----------+------+
In this case combination type-year for each container should be unique.
In Maria 10.2+ and MySQL 8+ window functions can solve this:
WITH x AS (
SELECT
*,
ROW_NUMBER() OVER(PARTITION BY f.type, f.variety, ct.cnt_type ORDER BY c.year DESC) rn
FROM
fruits f
INNER JOIN inclusion i on f.id = i.fruit_id
LEFT JOIN containers c on c.id = i.container_id
LEFT JOIN cnt_type ct on c.type = ct.id
)
SELECT * FROM x WHERE rn = 1
I'm not entirely sure I got your requirement on the last line, because you have multiple things called "type" and you talked about fruit types and varieties in the first instance, then you stoped talking about varieties in the second instance.. but you can change the columns in the PARTITION BY if you need. Easiest way to "debug" the requirement is to remove the WHERE rn = 1 and just run the query, then you'll see the rn column having 2, 3, 4 maybe.. If you adjust the PARTITION BY it will behave like the rows are being counted up and everything in the partition that is the same will have a counter that increments as the years descend (so for my query with a partition of f.type, f.variety, ct.cnt_type if you have 5 rows where all those rows have the same values for f.type, f.variety, ct.cnt_type, then the rn will increment from 1..5 as the years descend. If it's wrong and only f.type, f.variety should be considered "the group within which the rn counts up" then make the partition that instead
Because the most recent year is the one where rn = 1 that's what we pick to give the latest
If you end up stuck with MySQL 5.7 (and no window functions) you can use this query (which will also work on MariaDB). The problem can be resolved to a fairly simple selection of MAX(containers.year) grouped by all columns in fruit and the container type. Note a CROSS JOIN of fruits to cnt_type is required to ensure that all fruit/container combinations are included in the output:
SELECT f.id, f.type, f.variety,
ct.type AS cnt_type,
MAX(c.year) AS year
FROM fruits f
CROSS JOIN cnt_type ct
LEFT JOIN inclusion i ON i.fruit_id = f.id
LEFT JOIN containers c ON c.id = i.cont_id AND c.type = ct.id
GROUP BY f.id, f.type, f.variety, ct.type
Output:
id type variety cnt_type year
1 orange 5 box 2020
1 orange 5 package null
2 orange 7 box 2015
2 orange 7 package null
3 apple 1 box 2020
3 apple 1 package 2020
4 apple 0 box null
4 apple 0 package null
Demo on db-fiddle
You can use window functions such as DENSE_RANK() for MariaDB 10.2+ in order to pick the latest records grouped by fruit id's as follows
WITH f AS
(
SELECT f.*, c.year, DENSE_RANK() OVER (PARTITION BY f.id ORDER BY c.year DESC) AS dr
FROM fruits f
LEFT JOIN inclusion i
ON i.fruit_id = f.id
LEFT JOIN containers c
ON c.id = i.cont_id
)
SELECT id , type, variety, year
FROM f
WHERE dr = 1
Demo
Update : If you need to expand the results as adding that table(cnt_type) in the last update, then replace the query with the following one
WITH f AS
(
SELECT f.*, cn.type AS cnt_type, c.year,
DENSE_RANK() OVER (PARTITION BY f.type, f.variety, cn.type ORDER BY c.year DESC)
AS dr
FROM fruits f
LEFT JOIN inclusion i
ON i.fruit_id = f.id
CROSS JOIN cnt_type cn
LEFT JOIN containers c
ON c.id = i.cont_id AND c.type = cn.id
)
SELECT id , type, variety, cnt_type, year
FROM f
WHERE dr = 1
ORDER BY id, type, variety, cnt_type
Demo
where returning rows are multiplexed due to each rows of cnt_type table through use of
CROSS JOIN.
I'm trying to make a query in MySQL that returns me only 10 users from the table, but with a rank value that is the ORDER result of the xp column. Right now I have this:
SELECT id, xp, #curRank := #curRank + 1 AS rank
FROM usuarios, (SELECT #curRank := 0) r
ORDER BY xp DESC LIMIT 10;
It looks to be working perfectly when fetching the first 10 users.
+--------------------+------+------+
| id | xp | rank |
+--------------------+------+------+
| 373901344995803138 | 5863 | 1 |
| 701198768049225770 | 5692 | 2 |
| 239203656405221376 | 4961 | 3 |
| 692489002942726154 | 4508 | 4 |
| 416988898628206593 | 3669 | 5 |
| 312003290378534912 | 3155 | 6 |
| 608344569381126167 | 3059 | 7 |
| 671949142473310238 | 3041 | 8 |
| 549743978191519744 | 2991 | 9 |
| 592440479577145383 | 2519 | 10 |
+--------------------+------+------+
But when I try to fetch for example LIMIT 10,10 to get the users between 11 and 20, although they are ordered, their global rank is incorrect because #curRank is not increasing for all the users before the offset.
+--------------------+------+------+
| id | xp | rank |
+--------------------+------+------+
| 638196238436532234 | 1888 | 1 |
| 601269358349516833 | 1447 | 2 |
| 548357514497097743 | 1338 | 3 |
| 203591312031744000 | 1330 | 4 |
| 379034072519016469 | 1283 | 5 |
| 563804445654122497 | 1086 | 6 |
| 421296425981181952 | 1025 | 7 |
| 263816867100098560 | 850 | 8 |
| 631330775379214371 | 776 | 9 |
| 442529076511637504 | 702 | 10 |
+--------------------+------+------+
I don't know a way to make the global ranking work when using LIMIT.
In MySQL 8.0, just use window functions, as demonstrated by Gordon Linoff.
In earlier versions, you basically need a subquery to do what you want. I would recommend:
SELECT *
FROM (
SELECT id, xp, #curRank := #curRank + 1 AS rank
FROM (SELECT * FROM usuarios ORDER BY xp DESC) u
CROSS JOIN (SELECT #curRank := 0) r
ORDER BY xp DESC
) t
ORDER BY xp DESC
LIMIT 10, 10;
The subquery ranks all users first, then you can safely filter in the outer query. Note that the query pre-orders the table by xp in a subquery first: this is safer (user variables are tricky in MySQL).
Actually, you don't even needLIMIT in the outer query; you can use a WHERE clause instead:
SELECT *
FROM (
SELECT id, xp, #curRank := #curRank + 1 AS rank
FROM (SELECT * FROM usuarios ORDER BY xp DESC) u
CROSS JOIN (SELECT #curRank := 0) r
ORDER BY xp DESC
) t
WHERE rank BETWEEN 11 AND 20
ORDER BY rank
Instead, use row_number():
SELECT id, xp, row_number() over (order by cp desc) as rnk
FROM usuarios
ORDER BY xp DESC
LIMIT 10;
I'm trying to get a users ranking getting his highest performances in every beatmap.
I get the user highest performance in every beatmap (only taking the top 5 performances) and adding them together, but it fails when the highest performance in one beatmap is repeated... because it counts twice
I'm based in this solution, but it doesn't works well for me...
Using MySQL 5.7
What i'm doing wrong?
Fiddle
Using this code:
SET group_concat_max_len := 1000000;
SELECT #i:=#i+1 rank, x.userID, x.totalperformance FROM (SELECT r.userID, SUM(r.performance) as totalperformance
FROM
(SELECT Rankings.*
FROM Rankings INNER JOIN (
SELECT userID, GROUP_CONCAT(performance ORDER BY performance DESC) grouped_performance
FROM Rankings
GROUP BY userID) group_max
ON Rankings.userID = group_max.userID
AND FIND_IN_SET(performance, grouped_performance) <= 5
ORDER BY
Rankings.userID, Rankings.performance DESC) as r
GROUP BY userID) x
JOIN
(SELECT #i:=0) vars
ORDER BY x.totalperformance DESC
Expected result:
+------+--------+------------------+
| rank | userID | totalperformance |
+------+--------+------------------+
| 1 | 1 | 450 |
+------+--------+------------------+
| 2 | 2 | 250 |
+------+--------+------------------+
| 3 | 5 | 140 |
+------+--------+------------------+
| 4 | 3 | 50 |
+------+--------+------------------+
| 5 | 75 | 10 |
+------+--------+------------------+
| 6 | 45 | 0 | --
+------+--------+------------------+
| 7 | 70 | 0 | ----> This order is not relevant
+------+--------+------------------+
| 8 | 76 | 0 | --
+------+--------+------------------+
Actual Result:
+------+--------+------------------+
| rank | userID | totalperformance |
+------+--------+------------------+
| 1 | 1 | 520 |
+------+--------+------------------+
| 2 | 2 | 350 |
+------+--------+------------------+
| 3 | 5 | 220 |
+------+--------+------------------+
| 4 | 3 | 100 |
+------+--------+------------------+
| 5 | 75 | 10 |
+------+--------+------------------+
| 6 | 45 | 0 | --
+------+--------+------------------+
| 7 | 70 | 0 | ----> This order is not relevant
+------+--------+------------------+
| 8 | 76 | 0 | --
+------+--------+------------------+
As you have mentioned that you are picking only top 5 performances per user across beatmaps then you can try this way:
select #i:=#i+1, userid,performance from (
select userid,sum(performance) as performance from (
select
#row_number := CASE WHEN #last_category <> t1.userID THEN 1 ELSE #row_number + 1 END AS row_number,
#last_category :=t1.userid,
t1.userid,
t1.beatmapid,
t1.performance
from (
select
userid, beatmapid,
max(performance) as performance
from Rankings
group by userid, beatmapid
) t1
CROSS JOIN (SELECT #row_number := 0, #last_category := null) t2
ORDER BY t1.userID , t1.performance desc
) t3
where row_number<=5
group by userid
)
t4 join (SELECT #i := 0 ) t5
order by performance desc
Above query will not consider duplicate Performance Score and pick only top 5 performance values.
DEMO
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`;
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