Group by Id with greatest value - mysql

I have a simple table where I'm calculating a "level value" dynamically via SELECT with repeated Ids in this table, what I'm trying to do is to Group the rows Ids by the greatest level value.
Fiddle: https://www.db-fiddle.com/f/2Pyfi2PMV8eaQbDt2uWQjc/2
I already tried using CASE in Group and Order but does not work well.
I already tried using MAX() but I get the score of the second value instead of the fourth. What I'm trying to get is a result like:
| id | score | level |
| -- | ------- | ----- |
| 1 | [] | 0 |
| 16 | [1,2,4] | 3 |
| 17 | [1] | 1 |
Is there any way to group the Ids but in case there's a repeated Id get the one with the greatest level?
Thanks.

You can use group by like this :
select test.id , min(test.score) as score,
(CASE
WHEN JSON_LENGTH(min(test.score)) = 3 THEN 3
WHEN JSON_LENGTH(min(test.score)) = 2 THEN 2
WHEN JSON_LENGTH(min(test.score)) = 1 THEN 1
ELSE 0
END) as level
from test group by test.id
and the result is :
|id | score | level |
|---|---------|-------|
|1 | [] | 0 |
|16 | [1,2,4] | 3 |
|17 | [1] | 1 |

Related

get empty instead of repeated value in query

I have a table like this
|num|id|name|prj|
| 1 | 1|abc | 1 |
| 2 | 1|efg | 1 |
| 3 | 1|cde | 1 |
| 4 | 2|zzz | 1 |
I want to run a query like this:
SELECT * FROM table WHERE prj=1 ORDER BY name
but printing out repeated values only once. I want to keep all the rows and I would like to do this at database level and not on the presentation layer (I know how to do it in php).
Desired result is
|num|id|name|prj|
| 1 | 1|abc | 1 |
| 3 | |cde | 1 |
| 2 | |efg | 1 |
| 4 | 2|zzz | 1 |
any hint on where to start from to build that query?
Use a session variable to test if the previous ID is the same as the current ID:
SELECT num, IF(#lastid = id, '', #lastid := id) AS id, name, prj
FROM table
CROSS JOIN (SELECT #lastid := null) x
ORDER BY table.id, name
DEMO
Note that you need to qualify table.id, because ORDER BY defaults to using the alias from the SELECT list if it's the same as a table column, and that would order the empty fields first.

SQL order by match to specific row

I have a example table below. I am trying to create a SQL query that gets all user_ids besides user_id of the current user and then orders by number of matches to the row with the current user_id
For example, if the user has a user_id of '1', I want to get all of the user_ids corresponding with the rows of id 2-8, and then order the user_ids from most matches to the row of the current user to least matches with the row of the current user
Let's say var current_user = 1
Something like this:
SELECT user_id
FROM assets
WHERE user_id <> `current_user` and
ORDER BY most matches to `current_user`"
The output should get 7,8,3,9,2
I would appreciate anyone's input on how I can effectively achieve this.
Table assets
+----------+---------+-------+--------+-------+
| id | user_id | cars | houses | boats |
+----------+---------+-------+--------+-------+
| 1 | 1 | 3 | 2 | 3 |
| 2 | 8 | 3 | 2 | 5 |
| 3 | 3 | 3 | 2 | 2 |
| 4 | 2 | 5 | 1 | 5 |
| 5 | 9 | 5 | 7 | 3 |
| 8 | 7 | 3 | 2 | 3 |
+----------+---------+-------+--------+-------+
I think you can just do this:
select a.*
from assets a cross join
assets a1
where a1.user_id = 1 and a.user_id <> a1.user_id
order by ( (a.cars = a1.cars) + (a.houses = a1.houses) + (a.boats = a1.boats) ) desc;
In MySQL, a boolean expression is treated as an integer in a numeric context, with 1 for true and 0 for false.
If you want to be fancier, you could order by the total difference:
order by ( abs(a.cars - a1.cars) + abs(a.houses - a1.houses) + abs(a.boats - a1.boats) );
This is called Manhattan distance, and you would be implementing a version of a nearest neighbor model.

MySQL getting maximum of column with fallback to second column

+------+---------+--------+---------+---------+---------+
| id | user_id | obj_id | created | applied | content |
+------+---------+--------+---------+---------+---------+
| 1 | 1 | 1 | 1 | 1 | ... |
| 2 | 1 | 2 | 1 | 1 | ... |
| 3 | 1 | 1 | 1 | 2 | ... |
| 4 | 1 | 2 | 2 | 2 | ... |
| 5 | 2 | 1 | 1 | 1 | ... |
| 6 | 2 | 2 | 1 | 1 | ... |
+------+---------+--------+---------+---------+---------+
I have a table similar to the one above. id, user_id and obj_id are foreign keys; created and applied are timestamps stored as integers. I need to get the entire row, grouped by user_id and obj_id, with the maximum value of applied. If two rows have the same applied value, I need to favour the maximum value of created. So for the above data, my desired output is:
+------+---------+--------+---------+---------+---------+
| id | user_id | obj_id | created | applied | content |
+------+---------+--------+---------+---------+---------+
| 1 | 1 | 1 | 1 | 1 | ... |
| 4 | 1 | 2 | 2 | 2 | ... |
| 5 | 2 | 1 | 1 | 1 | ... |
| 6 | 2 | 2 | 1 | 1 | ... |
+------+---------+--------+---------+---------+---------+
My current solution is to get everything ordered by applied then created:
select * from data order by applied desc created desc;
and sort things out in the code, but this table gets pretty big and I'd like an SQL solution that just gets the data I need.
select *
from my_table
where id in (
/* inner subquery b */
select max(id)
from my_table where
(user_id, obj_id, applied, created) in (
/* inner subquery A */
select user_id, obj_id, max(applied), max(created)
from my_table
group by user_id, obj_id
)
);
Then inner subquery A return the (distinct) rows having user_id, obj_id, max(applied), max(created). Using these with in clause the subquery B retrive a list of single ID each realated the a row with a proper value of user_id, obj_id, max(applied), max(created). so you have a collection of valid id for getting your result.
The main select use these ID for select the result you need.
Thanks to Mark Heintz in the comments, this answer got me to where I need to be.
SELECT
data.id,
data.user_id,
data.obj_id,
data.created,
data.applied,
data.content
FROM data
LEFT JOIN data next_max_applied ON
next_max_applied.user_id = data.user_id AND
next_max_applied.obj_id = data.obj_id AND (
next_max_applied.applied > data.applied OR (
next_max_applied.applied = data.applied AND
next_max_applied.created > data.created
)
)
WHERE next_max_applied.applied IS NULL
GROUP BY user_id, obj_id;
Go read the answer for details on how it works; the left join tries to find a more recently applied row for the same user and object. If there isn't one, it will find a row applied at the same time, but created more recently.
The above means that any row without a more recent row to replace it will have a next_max_applied.applied value of null. These rows are filtered for by the IS NULL clause.
Finally, the group by clause handles any rows that have identical user, object, applied and created columns.

MySQL Advanced Ranks Query

I have a table that looks like this:
map uid time name
'first' 1 5.0 'Jon'
'first' 3 4.9 'Robin'
'second' 1 2.0 'Jon'
'first' 2 5.3 'Max'
'second' 3 2.1 'Robin'
I am currently selecting the values using this:
SELECT records.* FROM `records` WHERE `uid` = '3' ORDER BY `records`.`time` ASC
Now obviously, I have multiple uids for different maps. How would I find the rank of every user out of total ranks? I know I can find total ranks of the map by using COUNT(DISTINCT map). However, I am having issues selecting a specific user and their rank in the map. Any help would be appreciated!
EDIT:
Desired output when selecting uid 3 is as follows:
map uid time name position totalposition (totalposition would be COUNT(DISTINCT map))
'first' 3 4.9 'Robin' 2 3
'second' 3 2.1 'Robin' 2 2
Use the following query : -
mysql> set #pos = 0; select records.*, #pos:=#pos+1 as position from records order by time desc;
Output :
+--------+------+------+-------+--------------+
| map | uid | time | name | position |
+--------+------+------+-------+--------------+
| first | 2 | 5.30 | Max | 1 |
| first | 1 | 5.00 | jon | 2 |
| first | 3 | 4.90 | Robin | 3 |
| second | 3 | 2.10 | Robin | 4 |
| second | 1 | 2.00 | Jon | 5 |
+--------+------+------+-------+--------------+
And now, to recieve position of a particular :
mysql> set #pos = 0; select * from (select records.*, #pos:=#pos+1 as position
mysql> from records order by time desc) as t where uid = 3;
Output :
+--------+------+------+-------+----------+
| map | uid | time | name | position |
+--------+------+------+-------+----------+
| first | 3 | 4.90 | Robin | 3 |
| second | 3 | 2.10 | Robin | 4 |
+--------+------+------+-------+----------+

how to write this self join based on three columns

Hello there I have a following table
------------------------------------------
| id | language | parentid | no_daughter |
------------------------------------------
| 1 | 1 | 0 | 2 |
------------------------------------------
| 1 | 1 | 0 | 2 |
------------------------------------------
| 2 | 1 | 1 | 1 |
------------------------------------------
| 2 | 2 | 1 | 1 |
------------------------------------------
| 3 | 1 | 1 | 0 |
------------------------------------------
| 3 | 2 | 1 | 0 |
------------------------------------------
| 4 | 1 | 2 | 0 |
------------------------------------------
| 4 | 2 | 2 | 0 |
------------------------------------------
| 5 | 1 | 2 | 0 |
------------------------------------------
| 5 | 2 | 2 | 1 |
-----------------------------------------
| 5 | 1 | 4 | 1 |
------------------------------------------
| 5 | 2 | 4 | 1 |
------------------------------------------
Scenario
Every record has more than one rows in table with different language ids. parentid tells who is the parent of this record. no_daughter columns tells against each record that how many child one record has. Means in Ideal scenario If no_daughter has value 2 of id = 1 , it means 1 should be parentid of 2 records in same table. But If a record has more than one exitance with respect to language, it will be considered as one record.
My Problem
I need to find out those records where no_daughter value is not correct. It means if no_daughter is 2, there must be two records whoes parentid has that id. In above case record with id = 1 is valid. But record having id = 2 is not valid because the no_daughter = 1 but actual daughter of this record is 2. Same is the case with id=4
Can any body tell me how can I find these faulty records?
Updated after answers
Ken Clark has and shola has given answer which return same result for example shola query is
SELECT DISTINCT
id
FROM
tbl_info t
INNER JOIN
(SELECT
parentid,
COUNT(DISTINCT id) AS childs
FROM
tbl_info
GROUP BY parentid) AS parentchildrelation
ON t.id = parentchildrelation.parentid
AND t.no_daughters != parentchildrelation.childs
This query is returning those ids who have been used as parentid somewhere in table but having wrong no_daughter values. But not returning ids that has value in no_daugter columns but have not been used as parentid any where in table. For exampl id = 5 has no_daughter = 1 but it is not used as parentid in table. So it is also a faulty record. But above query is not capturing such records.
Any help will be much appreciated.
Try this:
SELECT DISTINCT
id
FROM
tbl_info t
Left JOIN
(SELECT
parentid,
COUNT(DISTINCT id) AS childs
FROM
tbl_info
GROUP BY parentid) AS parentchildrelation
ON t.id = parentchildrelation.parentid
Where t.no_daughters != parentchildrelation.childs
Try this:
SELECT id FROM tinfo t inner join
(SELECT parentid, COUNT(distinct language ) as childs FROM tinfo group by parentid) as summary
on t.id=summary.parentid and t.no_daughters!= summary.childs
try this
Select Distinct * From tablename t
Left Join
(
Select COUNT(t1.Id) Doughter,t1.parentid,t1.language From tablename t1 Group By t1.parentid,t1.language
)tbl
On t.id=tbl.parentid And tbl.language=t.language And t.no_daughter<>tbl.Doughter