Let's jump right into my problem and imagine that we have this simple table
CREATE TABLE `test` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`score` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `test` (`id`, `score`)
VALUES
(1, 10),
(2, 15),
(3, 6),
(4, 17);
What I want to do is the following. I want to sort by score and then, get the rank of the field with the ID 3
Since the entry with the ID 3 has the lowest score it would be rank 4.
I have this query, to get the ranks sorted by score
SELECT #rownum:=#rownum+1 rank, id, score from test t, (SELECT #rownum:=0) r ORDER BY score DESC;
The result is the following
rank || id || score
1 4 17
2 2 15
3 1 10
4 3 6
However, what I want to achieve is to get the rank instantly. Without having the ranks and results of the other values. I can't just add a WHERE CLAUSE with the condition that the ID should be 3, because If I do so the rank is always 1 because the query only matches one entry in the database.
How would I get the rank directly without iterating somehow over the result?
Why not just count how many are better than the id you want the rank of?
For example for id 3:
SELECT count(*)+1 FROM test where score > (SELECT score from test WHERE id = 3))
Just change the id at the end of the subquery to get the rank of another id
Add a index to score column and use below query-
select count(*) from test a join test b on a.score > b.score where a.id =3
Related
I've got a table of participant teams who're playing against each other and the table contains the overall previous score of teams.
What I want to do is fetch all the team records ordered as the highest score, lowest score, second-highest score, second-lowest score, and so on.
because I want to pair the team with the highest score with the team with the lowest score.
Here's my DB schema,
CREATE TABLE participant_teams (
team_id int(11) NOT NULL,
team_name text NOT NULL,
created_by int(11) NOT NULL,
team_profile text NOT NULL,
team_member text NOT NULL,
baller_event_id_fk int(11) NOT NULL,
pay_status text NOT NULL COMMENT 'pending, paid, cancelled',
team_score double NOT NULL,
registered_on timestamp NOT NULL DEFAULT current_timestamp(),
updated_on timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO participant_teams (team_id, team_name, created_by, team_profile, team_member, baller_event_id_fk, pay_status, team_score, registered_on, updated_on) VALUES
(1, 'Summer Havoc', 2, 'uploads/54988c5c833638d4c19546fbc8de1a35.jpg', '5,2', 1, 'paid', 25, '2022-01-04 13:12:39', '2022-01-04 13:12:39'),
(2, 'Jammers Classic', 4, 'uploads/54988c5c833638d4c19546fbc8de1a35.jpg', '4,3', 1, 'paid', 6.25, '2022-01-04 13:13:17', '2022-01-04 13:13:17'),
(3, 'Brickslayers', 7, 'uploads/54988c5c833638d4c19546fbc8de1a35.jpg', '7,10', 1, 'paid', 12.5, '2022-01-05 07:18:08', '2022-01-05 07:18:08'),
(4, 'Basket Bombers', 9, 'uploads/54988c5c833638d4c19546fbc8de1a35.jpg', '9,8', 1, 'paid', 0, '2022-01-05 07:18:45', '2022-01-05 07:18:45');
Right now I've just displayed the teams with scores in descending order as below,
select * from participant_teams order by team_score DESC;
The Records I have in my table looks quite like this,
team_id
team_name
team_score
1
Summer Havoc
25
2
Jammers Classic
6.25
3
Brickslayers
12.5
4
Basket Bombers
0
and the required output is,
team_id
team_name
team_score
1
Summer Havoc
25
4
Basket Bombers
0
3
Brickslayers
12.5
2
Jammers Classic
6.25
Please, Help me out with the query if it's possible.
Note: I am working on Mysql & PHP so any solution in PHP also might work
Thanks.
if your MySQL version support ROW_NUMBER window function you can try to use ROW_NUMBER to do a simple formula to make a group be order by number then use abs function to get absolute value
SELECT team_id,team_name,team_score
FROM (
SELECT team_id,team_name,team_score,
abs(ROW_NUMBER() OVER(ORDER BY team_score) -
ROW_NUMBER() OVER(ORDER BY team_score DESC)) rn
FROM participant_teams
) t1
ORDER BY rn desc,team_score desc
https://dbfiddle.uk/?rdbms=mariadb_10.4&fiddle=f638b2aa60649f030982bad575463796
Error Code: 1109. Unknown table 'numbers' in field list
Why my code thinks that there is no table numbers and how to fix it?
And if possible answer question why use case with triggers?
P.S Numbers table I have been using combining with sunstring_index so from tables where in some column fields have two words i could split them into two rows
Maybe there is sufficient way?
drop schema exp;
create database exp;
use exp;
create table IDVU (
`ID` int(8) unsigned not null auto_increment ,
`VU` varchar(45) not null,
PRIMARY KEY (`id`),
KEY `ix_VU` (`VU`)
)ENGINE = InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
create table sep (
ID1 int(8) unsigned NOT NULL primary key auto_increment,
ID2 int(8) unsigned not null,
V varchar(45) not null,
U varchar(45) not null,
KEY `ix_ID2` (`ID2`),
CONSTRAINT `ID_IDVU_SEP` FOREIGN KEY (`ID2`) REFERENCES `IDVU` (`ID`)
ON DELETE CASCADE ON UPDATE CASCADE
)ENGINE = InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
create table numbers select 1 n union all select 2;
delimiter $$
CREATE TRIGGER `edit` AFTER INSERT
ON `idvu`
FOR EACH ROW BEGIN
IF new.VU like '% %' THEN
SET #V = SUBSTRING_INDEX(SUBSTRING_INDEX(new.Vu, ' ', numbers.n), ' ', -1),
#U = SUBSTRING_INDEX(SUBSTRING_INDEX(new.Vu, ' ', numbers.n), ' ', -1);
else
SET #V = 'NEW',#U = 'NEW';
END IF;
INSERT INTO sep (ID2,V, U) VALUES (new.ID,#V, #U);
END$$
delimiter ;
select * from idvu order by ID;
select * from sep order by ID1;
insert into iDVU value (2,'Dd Rr');
UPDATE: OP wants to create a trigger AFTER INSERT to break up content of NEW.values inserted into table1 into different rows and insert them into table2.
Table1
Number Player Team Position
1 Jan Ho Team 1 C
2 Mike Dog Team 3 LW
4 8 Slim Dre Team 4, Team 1 G D
6 Mad Dog Team 2 D
break it up into rows and insert into table2 like below
Table2
Number Player Team Position
1 Jan Ho Team 1 C
2 Mike Dog Team 3 LW
4 Slim Dre Team 4 G
8 Slim Dre Team 1 D
6 Mad Dog Team 2 D
if you're just trying to break out the strings, you can just hardcode the 1 and 2 in there like this and there's no need to grab 1 and 2 in numbers table since that table is currently hardcoded to contain 1 and 2 anyways.
SET #V = SUBSTRING_INDEX(SUBSTRING_INDEX(new.Vu, ' ', 1), ' ', -1),
#U = SUBSTRING_INDEX(SUBSTRING_INDEX(new.Vu, ' ', 2), ' ', -1);
sqlfiddle
but then i noticed you don't even need to call SUBSTRING_INDEX() twice..this works too
SET #V = SUBSTRING_INDEX(new.Vu, ' ', 1),
#U = SUBSTRING_INDEX(new.Vu,' ', -1);
sqlfiddle
UPDATE after seeing your comment, I see why you wanted to create table numbers so your trigger would be something like this.
First you create table numbers that contains rows that has n values from 1 to 10 (possible maximum number of fields to break up into rows).
Then you select from numbers where n values are <= number of fields in your number. Then apply SUBSTRING_INDEX() functions to get the field at n position.
create table numbers
select 1 as n
union select 2
union select 3
union select 4
union select 5
union select 6
union select 7
union select 8
union select 9
union select 10;
CREATE TRIGGER `edit2` AFTER INSERT
ON `table1`
FOR EACH ROW BEGIN
INSERT INTO table2 (number,player,team,position)
SELECT
SUBSTRING_INDEX(SUBSTRING_INDEX(NEW.number,' ',n),' ',-1) as number,
NEW.player as player,
SUBSTRING_INDEX(SUBSTRING_INDEX(NEW.team,', ',n),', ',-1) as team,
SUBSTRING_INDEX(SUBSTRING_INDEX(NEW.position,' ',n),' ',-1) as position
FROM
numbers n
WHERE LENGTH(NEW.number)
- LENGTH(REPLACE(NEW.number,' ',''))
+ 1 >= n.n;
END
sqlfiddle to see trigger in action
I'm working on a PHP project with MYSQL database. I have a table of groups of students. Each group has an examiner. What i want to do is that i want to set two examiners for each group randomly. How to do it?
MySQL Code:
create table groups (
groupID int(10) not null,
nbStudents int not null,
avgGPA DOUBLE NOT NULL,
projectName varchar(50) not null,
advisorID int,
examiner1ID int,
examiner2ID int,
adminID int not null,
primary key (groupID)
);
create table faculty (
name varchar(30) not null,
facultyID int(10) not null,
email varchar(30) not null,
mobile int(15) not null,
primary key (facultyID)
);
examiner1ID and examiner2ID are foreign keys from the table faculty.
Here is a very convoluted way to do it. It uses 2 subqueries to pick faculty members, and insert .. on duplicate key to update the examiners IDs.
insert into groups
(groupID, examiner1ID, examiner2ID)
select groupID,
#x:=(select facultyID from faculty order by rand() limit 1),
(select facultyID from faculty where facultyID <> #x order by rand() limit 1)
from groups
on duplicate key update examiner1ID=values(examiner1ID), examiner2ID=values(examiner2ID);
#x is a user-defined-variable. In this case, it is used to store the first random faculty member. <> #x makes sure we don't pick the same faculty member in both slots.
Since groupID is a unique key, when we try to insert a row with an existing unique key, it will update the existing row instead of inserting it. That's what on duplicate key update clause is used for.
set different examiners for each group:
insert into groups
(groupID, examier1ID, examier2ID)
select a.groupID, max(if(b.id%2, b.facultyID, 0)), max(if(b.id%2, 0, b.facultyID))
from (
select #row:=#row+1 id, groupID
from groups a
join (select #row:=0) b) a
join (
select #row:=#row+1 id, facultyID
from (
select facultyID
from faculty a
order by rand()) a
join (select #row:=0) b) b on a.id = ceil(b.id/2)
group by a.groupID
on duplicate key update examiner1ID=values(examiner1ID), examiner2ID=values(examiner2ID);
I'm currently creating a page in PHP that will display a user and their ranks in my site's highscores.
However, I have one field that is different from the rest and isn't functioning as intended.
Here is my table:
-- ----------------------------
-- Table structure for `playerstats`
-- ----------------------------
DROP TABLE IF EXISTS `playerstats`;
CREATE TABLE `playerstats` (
`uid` int(11) NOT NULL DEFAULT '0',
`gamelevel` int(11) NOT NULL DEFAULT '0',
`overall` int(11) NOT NULL DEFAULT '0',
`overallxp` bigint(20) NOT NULL DEFAULT '0',
KEY `uid` (`uid`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
...and some example data:
INSERT INTO `playerstats` VALUES ('14950', '123', '1495', '129825211')
INSERT INTO `playerstats` VALUES ('28967', '124', '1495', '127168799')
INSERT INTO `playerstats` VALUES ('95848', '121', '1495', '108481173')
Here is my query:
SELECT count(*) + 1 FROM (SELECT uid, overall, overallxp, gamelevel FROM playerstats GROUP BY playerstats.uid) AS x WHERE overall > (SELECT overall FROM playerstats WHERE uid = ". $userid .")"
...and $userid is:
$userid = (int) $_GET['searched'];
Now, when I navigate to the personal highscores of userid14950, it displays the correct overall ranking for that user because they are the person with the highest overallxp for their overall. However, when I visit the personal highscores of userid28967 or userid95848, their overall rank is the same as userid14950 for some reason (most likely because I don't account for users with the same overall result).
My question is: how would I go about making it so if two (or more) users share the same overall, they have their correct rank displayed, and not a duplicate one?
So that's about it.
Any help is very much appreciated :)
Thanks,
Mark
try this:
SET #rank = 0;
SELECT rank FROM (
SELECT #rank:=#rank + 1 AS rank, uid FROM playerstats ORDER BY overall DESC,
overallxp DESC
) as tmp WHERE uid = 14950
the avove query will return the rank for user14950
This query will list all the users and their ranks
SET #rank=0;
SELECT rank, uid, overall, overallxp FROM (
SELECT #rank:=#rank + 1 AS rank, uid, overall, overallxp FROM playerstats ORDER BY overall DESC, overallxp DESC
) as tmp
Here is my DB Structure:
CREATE TABLE IF NOT EXISTS `UserItems` (
`id` bigint(20) unsigned NOT NULL auto_increment,
`user_id` int(10) unsigned NOT NULL,
`item_id` int(10) unsigned NOT NULL,
`qty` int(11) NOT NULL default '0'
) ;
CREATE TABLE IF NOT EXISTS `UserEquippedItems` (
`user_id` int(10) unsigned NOT NULL,
`user_item_id` bigint(20) unsigned NOT NULL
);
CREATE TABLE IF NOT EXISTS `UserFriendEquippedItems` (
`user_friend_id` int(10) unsigned NOT NULL,
`user_item_id` bigint(20) unsigned NOT NULL
);
UserItems keeps all the item inventory with quantity.
let say if I have 5 item (item id 123456). then the entry will be
(null, $userid, 123456, 5).
the qty is the quantity of all the entity with the same item_id.
However, some Users may equip the item. Some do not. If they equip it, it will be an entry in UserEquippedItems table.
Also, users' friends can equip the user's item too.
Sample Data:
UserItems:
id, user_id, item_id, qty
( 1, 4567, 123123123, 5)
( 2, 4567, 100010001, 2)
( 3, 4567, 100010099, 1)
UserEquippedItems: (user_item_id is UserItems.id)
user_id, user_item_id
( 4567, 1)
( 4567, 2)
UserFriendEquippedItems
(user_item_id is UserItems.id)
user_friend_id, user_item_id
( 4100, 1)
( 4100, 3)
So, how can I find out the quantity of items that are equipped?
and how can I find out the quantity of items that are NOT equipped ?
Side Story: Before, we have each individual UserItems as a single entry in the DB. e.g. for item_id = 123123123, if I have 5 of them, i have 5 entries in the DB. But then, our DB grows like crazy to 4 million UserItems records. that's why instead of having 5 entries, we only have one entry in the UserItems table, with the qty field for keep track how many in total. I don't know if it is the right approach, but I hope it can cut down 75% of the DB size.
Also, it was the query to get the unequipped items:
SELECT Items.id, count(UserItems.id) as numCount
FROM UserItems INNER JOIN
Items ON UserItems.active=1 AND
UserItems.item_id=Items.id AND
Items.active=1 AND
UserItems.user_id=$userId
WHERE NOT EXISTS (
SELECT UserEquippedItems.user_item_id
FROM UserEquippedItems
WHERE UserEquippedItems.user_item_id= UserItems.id
)
AND NOT EXISTS (
SELECT UserFriendsEquippedItems.user_item_id
FROM UserFriendsEquippedItems
WHERE UserFriendsEquippedItems.user_item_id= UserItems.id)
GROUP BY Items.id
Of course, this query doesn't work with the new schema. :)
This should work:
SELECT id, user_id, item_id, qty,
(SELECT COUNT(*) FROM UserEquippedItems uei
WHERE uei.user_item_id=ui.id) as qty_equipped,
(SELECT COUNT(*) FROM UserFriendEquippedItems ufei
WHERE ufei.user_item_id=ui.id) as qty_friend_equipped
FROM UserItems ui
Then to get the unequipped, on your client you can subtract the qty_equipped and qty_friend_equipped from qty.
A single query that just returns unequipped:
SELECT id, user_id, item_id, qty,
qty-
(SELECT COUNT(*) FROM UserEquippedItems uei
WHERE uei.user_item_id=ui.id)-
(SELECT COUNT(*) FROM UserFriendEquippedItems ufei
WHERE ufei.user_item_id=ui.id)
as qty_unequipped
FROM UserItems ui
You could combine those 2 queries above into one big query, but I image that would hurt performance, since it will run the sub-queries twice.
You can add a WHERE clause on the end of both of these to return results for a specific user / item.
To count the total number of items equipped:
SELECT count(*) FROM UserEquippedItems e JOIN UserItems i ON (i.id = e.user_item_id)
To find the total number of items of id $item_id equipped
SELECT count(*) FROM UserEquippedItems e WHERE e.user_item_id = $item_id
And to find the total number of items user $user_id has equipped (my first response, though probably not what you're after)
SELECT count(*) FROM UserEquippedItems WHERE user_id = $user_id