Ranking system gaps in ranking output - mysql

I have this project that it ranks different items by their scores, the ranking is okay but it shows gaps when there is a tied score.
Here is the query:
SELECT bgycode, scc_bgyscoretotal, FIND_IN_SET( scc_bgyscoretotal, (
SELECT GROUP_CONCAT(DISTINCT scc_bgyscoretotal
ORDER BY scc_bgyscoretotal DESC ) FROM bgyprofile)
) AS rank
FROM bgyprofile
and it outputs like this:
any way to fix this?
Thanks in advance sorry for the bad english

You basically need Dense_Rank() like functionality (available in MySQL version >= 8.0). In older versions of MySQL, it can be emulated using Session Variables.
In a Derived table, determine ranking of a scc_bgyscoretotal (highest value having rank 1 and so on). Firstly, get unique values of scc_bgyscoretotal, and then determine their ranking using Session Variables.
Now, simply join these Derived table to the main table bgyprofile using scc_bgyscoretotal.
Try the following:
SELECT t2.bgycode,
t2.scc_bgyscoretotal,
dt2.`rank`
FROM bgyprofile AS t2
JOIN
(
SELECT dt1.scc_bgyscoretotal,
#rank_no := #rank_no + 1 AS `rank`
FROM
(
SELECT t1.scc_bgyscoretotal
FROM bgyprofile AS t1
GROUP BY t1.scc_bgyscoretotal
ORDER BY t1.scc_bgyscoretotal DESC
) AS dt1
CROSS JOIN (SELECT #rank_no := 0) AS init1
) AS dt2 ON dt2.scc_bgyscoretotal = t2.scc_bgyscoretotal

Related

How to use MYSQL to include missing rows in final table with default 0 values for all columns?

This question is part of a bigger mySQL query I have. So I have a table of 'playerIds', 'dates', 'scores' and 'problems'. It's Table T0 in the image attached. I am running a SQL query on it to get the most-recent row for all players where the 'date' is <= (2020-08-14 - 7days). Not all players will have a row with a date that satisfies that condition, so naturally those playerId rows will not appear in the resulting table (Table T1 in the pic).
Now what I want to do is to include those missing rows with 0 values for 'score' and 'problems' in the resulting table (See Table T2 in the pic). I am totally at a loss as to how to go about it since I am very new to SQL queries.
Here's the part of the SQL query which is producing Table T1 from T0, but I want to modify it such that it produces Table T2 from T0:
select *
from (
select *, row_number() over (partition by playerId order by date desc) as ranking
from player
where date<=date_add(date('2020-08-14'),interval -7 day)
) t
where t.ranking = 1
One option uses a subquery to list all the players, and then brings your current resultset with a left join:
select p.playerId, t.date, coalesce(t.score, 0) score, coalesce(t.problem, 0) problem
from (select distinct playerId from player) p
left join (
select p.*, row_number() over (partition by playerId order by date desc) as rn
from player p
where date <= '2020-08-14' - interval 7 day
) t on t.playerId = p.playerId and t.rn = 1
If you have a referential table for all players, you can just replace the select distinct subquery with that table.

Filtering values according to a different value for each user

I am trying to understand how to do in mySQL what I usually do in python.
I have a sales table, with sale_date, user_id and price_USD as columns. Each row is an order made by the user.
I want to get a new table that has all of the orders which cost more than the last order the user made (so in this picture, just the yellow rows).
I know how to get a table with the last order for each user, but I cannot save it on the database.
How do I compare each row's price to a different value by the user_id and get just the larger ones in one 'swoop'?
Thanks
If you are running MysL 8.0, you can do this with window functions:
select t.*
from (
select
t.*,
first_value(price_usd)
over(partition by user_id order by sale_date desc) last_price_usd
from mytable t
) t
where lag_price_usd is null or price > last_price_usd
In earlier versions, you could use a correlated subquery:
select t.*
from mytable t
where t.price_usd > (
select price_usd
from mytable t1
where t1.user_id = t.user_id
order by sale_date desc
limit 1
)

How to sort teams by summarized score in SQL? (two columns sorting with SUM())

Playerbase have 3 columns: PlrName, TeamTag, Score
I need to sort players grouping them by TeamTag. And put teams with best summarized score above.
So want to figure out what query will help make this:
PlrName|TeamTag|Score PlrName|TeamTag|Score
-------------------------------------------------------
Player1|TeamThr|0 Player6|TeamThr|9 \
Player2|TeamTwo|2 Player1|TeamThr|0 > 9
Player3|TeamOne|4 Player4|TeamThr|0 /
Player4|TeamThr|0 Player5|TeamOne|4 \
Player5|TeamOne|4 became-> Player3|TeamOne|4 > 8
Player6|TeamThr|9 Player8|TeamOne|0 /
Player7|TeamTwo|2 Player2|TeamTwo|2 \
Player8|TeamOne|0 Player7|TeamTwo|2 > 6
Player9|TeamTwo|2 Player9|TeamTwo|2 /
added:
With this query i can get an array(?) of TeamTag's ordered by team score:
SELECT TeamTag FROM Playerbase GROUP BY team ORDER BY SUM(Score) DESC
..can I sort then PlayerBase using this "array"? Prefably within one query :)
Also i need to get full lines (using *), not only three fields.
If your dbms doesn't support window functions (any MySQL version below 8.0):
SELECT PlrName, TeamTag, Score FROM table a
LEFT JOIN
(SELECT PlrName, TeamTag, Score ,SUM(score) ts FROM table GROUP BY TeamTag) b
ON a.TeamTag=b.TeamTag
ORDER BY b.ts DESC, a.TeamTag, score DESC;
Try this
; with cte as (
select TeamTag, sum(Score) as Sum
from tablename)
select t.PlrName, t.teamtag, t.Score from tablename as t inner join cte as c
on t.teamtag=c.teamtag order by c.sum desc, t.score desc
Assuming your dbms supports window functions
select PlrName, TeamTag, Score
from (
select PlrName, TeamTag, Score, sum(Score) over (partition by TeamTag) ms
from tablename ) t
order by ms desc, TeamTag, Score desc
EDIT changed max() to sum() as OP refined the problem explanation.
I made it like this, but feel little uncomfortable about sql :D
SELECT * FROM `Playerbase` as T1
JOIN
(SELECT TeamTag, SUM(Score) AS Sum FROM `Playerbase` GROUP BY TeamTag ORDER BY Sum DESC) AS T2
ON T2.TeamTag = T1.TeamTag
Didn't make what i was trying to, but anyway thanks for answers. They helped me to understand a bit more :D

SQL SELECT last entry without limiting

I have a table with 3 fields:
id
note
created_at
Is there a way in the SQL language especially Postgres that I can select the value of the last note without having to LIMIT 1?
Normal query:
select note from table order by created_at desc limit 1
I'm interested in something avoiding the limit since I'll need it as a subquery.
Simple version with EXISTS semi-join:
SELECT note FROM tbl t
WHERE NOT EXISTS
(SELECT 1 FROM tbl t1 WHERE t1.created_at > t.created_at);
"Find a note where no other note was created later."
This shares the weakness of #Hogan's version that it can return multiple rows if created_at is not UNIQUE - like #Ollie already pointed out. Unlike #Hogan's query (max() is only defined for simple types) this one can be improved easily:
Compare row types
SELECT note FROM tbl t
WHERE NOT EXISTS
(SELECT 1 FROM tbl t1
WHERE (t1.created_at, t1.id) > (t.created_at, t.id));
Assuming you want the greatest id in case of a tie with created_at, and id is the primary key, therefore unique. This works in PostgreSQL and MySQL.
SQL Fiddle.
Window function
The same can be achieved with a window function in PostgreSQL:
SELECT note
FROM (
SELECT note, row_number() OVER (ORDER BY created_at DESC, id DESC) AS rn
FROM tbl t
) x
WHERE rn = 1;
MySQL lacks support for window functions. You can substitute with a variable like this:
SELECT note
FROM (
SELECT note, #rownum := #rownum + 1 AS rn
FROM tbl t
,(SELECT #rownum := 0) r
ORDER BY created_at DESC, id DESC
) x
WHERE rn = 1;
(SELECT #rownum := 0) r initializes the variable with 0 without an explicit SET command.
SQL Fiddle.
If your id column is an autoincrementing primary key field, it's pretty easy. This assumes the latest note has the highest id. (That might not be true; only you know that!)
select *
from note
where id = (select max(id) from note)
It's here: http://sqlfiddle.com/#!2/7478a/1/0 for MySQL and here http://sqlfiddle.com/#!1/6597d/1/0 for postgreSQL. Same SQL.
If your id column isn't set up so the latest note has the highest id, but still is a primary key (that is, still has unique values in each row), it's a little harder. We have to disambiguate identical dates; we'll do this by choosing, arbitrarily, the highest id.
select *
from note
where id = (
select max(id)
from note where created_at =
(select max(created_at)
from note
)
)
Here's an example: http://sqlfiddle.com/#!2/1f802/4/0 for MySQL.
Here it is for postgreSQL (the SQL is the same, yay!) http://sqlfiddle.com/#!1/bca8c/1/0
Another possibility: maybe you want both notes shown together in one row if they were both created at the same exact time. Again, only you know that.
select group_concat(note separator '; ')
from note
where created_at = (select max(created_at) from note)
In postgreSQL 9+, it's
select string_agg(note, '; ')
from note
where created_at = (select max(created_at) from note)
If you do have the possibility for duplicate created_at times and duplicate id values, and you don't want the group_concat effect, you are unfortunately stuck with LIMIT.
I'm not 100% on Postgres (actually never used it) but you can get the same effect with something like this - if the created_at is unique ... (or with any column which is unique):
SELECT note FROM table WHERE created_at = (
SELECT MAX(created_at) FROM table
)
I may not know how to answer on this platform but what I have suggested is working
SELECT * FROM table GROUP BY field ORDER BY max(field) DESC;
You can get the last value of the field without limiting, usually in JOINED query we get the last update time with no limiting of output like this way, such as last message time without limiting it.

MySQL Query with first 20 items ordered by one field and rest ordered by name ASC

I have a database table that has two fields , date and name.
I want to have my query pull the first 20 by newest date first, then the rest of the query to pull the other elements by name alphabetically.
So that way the top 20 newest products would show first, then the rest would be ordered by name.
It's a bit ugly, but you can do it in one query:
SELECT name,
`date`
FROM ( SELECT #rank := #rank + 1 AS rank,
name,
`date`
FROM (SELECT #rank := 0) dummy
JOIN products
ORDER BY `date` DESC, name) dateranked
ORDER BY IF(rank <= 20, rank, 21), name;
The innermost query, dummy, initializes our #rank variable. The next derived table, dateranked, ranks all rows by recency (breaking ties by name). The outermost query then simply re-orders the rows by our computed rank, treating ranks greater than 20 as rank #21, and then by name.
UPDATE: This query version is more compact, puts the conditional ranking logic in the outermost ORDER BY, uses IF() rather than CASE/END.
I'm afraid this has to be done by adding a special column to your table or creating a temporary table, TPup. If you let me know whether you are interested in those options, I'll tell you more.
The two queries option like the following might be a possibility, but my version of MySQL tells me LIMIT isn't available in sub-queries.
SELECT `date`, `name` from `table` ORDER BY `date` LIMIT 0, 20;
SELECT `date`, `name` from `table` WHERE `id` NOT IN (SELECT `id` from `table` ORDER BY `date` LIMIT 0, 20) ORDER BY `name`;
Use sql UNION operator to combine result of two SELECT queries.
According to MySQL docs:
use of ORDER BY for individual SELECT statements implies nothing
about the order in which the rows appear in the final result because
UNION by default produces an unordered set of rows.
...
To use an ORDER BY or LIMIT clause to sort or limit the entire UNION
result, parenthesize the individual SELECT statements and place the
ORDER BY or LIMIT after the last one. The following example uses both clauses:
(SELECT a FROM t1 WHERE a=10 AND B=1)
UNION
(SELECT a FROM t2 WHERE a=11 AND B=2)
ORDER BY a LIMIT 10;
Edit:
I missed the part that explain OP needs to sort one set of the result on the date and the other set of the result alphabetically. I think you need to create a temporary field for the sorting purpose. And SQL query would be something similar to this.
(SELECT *, 'firstset' as set_id FROM t1 ORDER BY date LIMIT 0, 20)
UNION
(SELECT *, 'secondset' as set_id FROM t1 ORDER BY date LIMIT 20, 18446744073709551615)
ORDER BY
CASE
WHEN set_id='firstset' THEN date
WHEN set_id='secondset' THEN name
END DESC ;