Get index of certain result in query results list? - mysql

I've been trying to even write this title in a way that makes most sense, but through much googling I have not found anything to match what I am looking.
Basically, I have a database that stores players and their different levels, and I already have a working query that fetches a list of all players and ranks them in the required order (Total prestige first, then level, then experience and lastly by the oldest update timestamp)
The query I use for this is:
SELECT u.id, u.username, u.mode, u.total_prestige as prestige, u.total_level as level, u.total_xp as exp, s.created FROM hs_users u JOIN hs_userskill s ON u.id = s.userId
WHERE s.id IN (SELECT MAX(id) FROM hs_userskill WHERE userId = u.id GROUP BY userId)
ORDER BY total_prestige DESC, total_level DESC, total_xp DESC, created ASC;
But now, on a different page, I need to find the players "rank" (so basically their index in the results list)
Is there a proper sql way of doing this (probably), instead of just taking the whole results set into code and looping over it? As I am tempted at doing at this point in time.
My database structure in sql:
CREATE TABLE IF NOT EXISTS `hs_modes` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`modename` varchar(40) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
`modeicon` varchar(40) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
CREATE TABLE IF NOT EXISTS `hs_skills` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`skillname` varchar(40) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
`skillicon` varchar(40) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
CREATE TABLE IF NOT EXISTS `hs_users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(40) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL UNIQUE,
`mode` int(11) NOT NULL DEFAULT '0',
`total_prestige` int(11) NOT NULL DEFAULT '0',
`total_level` int(11) NOT NULL DEFAULT '0',
`total_xp` bigint(20) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
FOREIGN KEY (`mode`) REFERENCES `hs_modes`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
CREATE TABLE IF NOT EXISTS `hs_userskill` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`userId` int(11) NOT NULL,
`skillId` int(11) NOT NULL,
`prestige` int(11) NOT NULL,
`experience` int(11) NOT NULL,
`created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`userId`) REFERENCES `hs_users`(`id`),
FOREIGN KEY (`skillId`) REFERENCES `hs_skills`(`id`),
UNIQUE KEY `userskill` (`userId`, `skillId`, `prestige`, `experience`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

In mysql 8 you have ROW_NUMBER to signify the position in the specified order
SELECT
u.id,
u.username,
u.mode,
u.total_prestige AS prestige,
u.total_level AS level,
u.total_xp AS exp,
s.created
,ROW_NUMBER() OVER(ORDER BY total_prestige DESC , total_level DESC , total_xp DESC , created ASC) rn
FROM
hs_users u
JOIN
hs_userskill s ON u.id = s.userId
WHERE
s.id IN (SELECT
MAX(id)
FROM
hs_userskill
WHERE
userId = u.id
GROUP BY userId)
ORDER BY total_prestige DESC , total_level DESC , total_xp DESC , created ASC;
Fro Bob rank
SELECT
*
FROM
(SELECT
u.id,
u.username,
u.mode,
u.total_prestige AS prestige,
u.total_level AS level,
u.total_xp AS exp,
s.created
,ROW_NUMBER() OVER(ORDER BY total_prestige DESC , total_level DESC , total_xp DESC , created ASC) rn
FROM
hs_users u
JOIN
hs_userskill s ON u.id = s.userId
WHERE
s.id IN (SELECT
MAX(id)
FROM
hs_userskill
WHERE
userId = u.id
GROUP BY userId)
) t1
WHERE username = 'Bob'

Set the database connection cursor to fetch the result as dictionary, then you can access the data as dictionary index of it's column names

Related

MySQL returning all matches from a table and indicating if an id is on another table

How can I return, on a select, a field that indicates that an id was found?
My goal is to return all songs(song) from a specific source(source) checking if an user(user) has it or not (user_song).
The query I made almost works. If I remove 'hasSong' (which Im trying to indicate that an user has a song or not), I can see all songs.
If I keep 'hasSong', I see all songs repeating the song for each user.
QUERY:
SELECT DISTINCT(song.id) AS id_song, CONCAT(song.article, ' ', song.name) AS name
FROM `song`
LEFT JOIN `user_song` ON `song`.`id` = `user_song`.`id_song`
LEFT JOIN `user` ON `user`.`id` = `user_song`.`id_user`
JOIN `song_source` ON `song`.`id` = `song_source`.`id_song`
WHERE `song_source`.`id_source` = '1'
AND ( `user_song`.`id_user` = '3' OR song.id = song_source.id_song )
ORDER BY `song`.`name` ASC
DB:
CREATE TABLE `song` (
`id` int(11) NOT NULL,
`article` varchar(10) NOT NULL,
`name` varchar(150) NOT NULL,
`shortname` varchar(150) NOT NULL,
`year` int(11) NOT NULL,
`artist` int(11) NOT NULL,
`duration` int(11) NOT NULL,
`genre` int(11) NOT NULL,
`updated` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `song_source` (
`id_song` int(11) NOT NULL,
`id_source` int(11) NOT NULL
)
CREATE TABLE `source` (
`id` int(11) NOT NULL,
`article` varchar(10) NOT NULL,
`name` varchar(150) NOT NULL,
`updated` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `user` (
`id` int(11) NOT NULL,
`email` varchar(100) NOT NULL,
`password` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `user_song` (
`id_user` int(11) NOT NULL,
`id_song` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
The specification isn't entirely clear, ...
To return all songs (with no repeated values of song.id) that are from a particular source (id_source='1'),
along with an indicator, a value of 0 or 1, that tells us if there's a row in user_song that matches on id_song and is related to a particular user,(id_user = '3')
something like this:
SELECT s.id AS id_song
, MAX( CONCAT(s.article,' ',s.name) ) AS name
, MAX( IF(us.id_user = '3' ,1,0) ) AS has_song
FROM `song` s
JOIN `song_source` ss
ON ss.id_song = s.id
AND ss.id_source = '1'
LEFT
JOIN `user_song` us
ON us.id_song = s.id
AND us.id_user = '3'
GROUP BY s.id
ORDER BY MAX(s.name)
There are a couple of other query patterns that will return an equivalent result. For example, we could use a correlated subquery in the SELECT list.
SELECT s.id AS id_song
, MAX( CONCAT(s.article,' ',s.name) ) AS name
, ( SELECT IF( COUNT(us.id_user) >0,1,0)
FROM `user_song` us
WHERE us.id_song = s.id
AND us.id_user = '3'
) AS has_song
FROM `song` s
JOIN `song_source` ss
ON ss.id_song = s.id
AND ss.id_source = '1'
GROUP BY s.id
ORDER BY MAX(s.name)
These queries are complicated by the fact that there are no guarantees of uniqueness in any of the tables. If we had guarantees, we could eliminate the need for a GROUP BY and aggregate functions.
Please consider adding PRIMARY and/or UNIQUE KEY constraints on the tables, to prevent duplication. The way the tables are defined, we could add multiple rows to song with the same id value. (And those could have different name values.)
(And the queries would be much simpler if we had some guarantees of uniqueness.)

mysql query takes 4 min to execute

Table 1.
CREATE TABLE `admin_users` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` int(11) DEFAULT '0',
`landingpage` int(11) DEFAULT '8',
`user_role_id` int(11) DEFAULT '0',
`user_parent_role` int(11) DEFAULT '0',
`bank_branch_id` int(11) DEFAULT '0',
`status` enum('1','0') COLLATE utf8_unicode_ci DEFAULT '1',
`firstname` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`lastname` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`), KEY `user_role_id` (`user_role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=281 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci CHECKSUM=1 DELAY_KEY_WRITE=1 ROW_FORMAT=DYNAMIC COMMENT='الموظفين';
Table 2.
CREATE TABLE `application_activity` (
`activityid` bigint(11) NOT NULL AUTO_INCREMENT,
`dataid` text,
`datatable` varchar(255) DEFAULT NULL,
`userid` int(11) DEFAULT '0',
`activitytype` char(1) DEFAULT 'I',
`activitytime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`activityip` varchar(15) DEFAULT NULL,
`activitydevice` text,
PRIMARY KEY (`activityid`)) ENGINE=MyISAM AUTO_INCREMENT=152862 DEFAULT CHARSET=utf8 COMMENT='نشاط التطبيق';
Query I am executing.
SELECT admin_users.`firstname`,admin_users.`lastname`,admin_users.`id`, branches.`branch_name`,
(SELECT COUNT(activityid) FROM application_activity WHERE userid=admin_users.`id`) AS cnt,
(SELECT activitytype FROM application_activity WHERE userid=admin_users.`id` ORDER BY activityid DESC LIMIT 0,1) AS activitytype,
(SELECT datatable FROM application_activity WHERE userid=admin_users.`id` ORDER BY activityid DESC LIMIT 0,1) AS datatable,
(SELECT activitytime FROM application_activity WHERE userid=admin_users.`id` ORDER BY activityid DESC LIMIT 0,1) AS activitytime
FROM admin_users
JOIN branches ON branches.`branch_id`=admin_users.`branch_id`
HAVING cnt > 0
ORDER BY cnt DESC
Table 1 have 100 records of users and Table 2 have more then 100k records of application activity.
when i execute query it will take 4min for giving result.
For this query:
This is your query:
SELECT au.`firstname`, au.`lastname`, au.`id`, b.`branch_name`,
(SELECT COUNT(*) FROM application_activity aa WHERE aa.userid = admin_users.`id`) AS cnt,
(SELECT aa.activitytype FROM application_activity aa WHERE aa.userid = au.`id` ORDER BY aa.activityid DESC LIMIT 0,1) AS activitytype,
(SELECT aa.datatable FROM application_activity aa WHERE aa.userid = au.`id` ORDER BY aa.activityid DESC LIMIT 0,1) AS datatable,
(SELECT aa.activitytime FROM application_activity aa WHERE aa.userid = au.id` ORDER BY aa.activityid DESC LIMIT 0,1) AS activitytime
FROM admin_users au JOIN
branches b
ON b.`branch_id` = au.`branch_id`
HAVING cnt > 0
ORDER BY cnt DESC;
You need appropriate indexes. One is on branches(branch_id) -- but you might have this already.
The second is application_activity(userid, activityid).
I strongly recommend that when all columns in correlated subqueries be qualified with the table alias.

View help. Pulling data from 3 tables

Okay, I need some major help with this subject. This is what I need the view to do. It needs to take Sum of the DKP_Change Column in the Attendance table
SELECT SUM(a.DKP_Change) FROM Attendance AS a GROUP BY Name
add the value of the initial DKP from the characters table
SELECT b.Inital_DKP FROM Characters AS b GROUP BY Name
Subtract the sum of the raid drops tabe cost
SELECT SUM(c.Cost) FROM Raid_Drops AS c GROUP BY Name
I'm entirely new to the idea of VIEWS and i'm not sure where to begin with, the name of the view should be DKP, the columns should be Name and Total_DKP, where total dkp is calculated from teh above select statements.
Here are the creates for all 3 tables.
CREATE TABLE `Attendance` (
`Date` date NOT NULL,
`Name` varchar(20) NOT NULL,
`Hours` int(11) NOT NULL,
`Penalty` float NOT NULL,
`Rank` set('Raider','Core','Elite') NOT NULL,
`Rate` int(11) NOT NULL,
`DKP_Change` float NOT NULL,
`RecordNumber` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`RecordNumber`)
) ENGINE=MyISAM AUTO_INCREMENT=15 DEFAULT CHARSET=latin1
CREATE TABLE `Characters` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`Name` varchar(25) NOT NULL,
`Class` varchar(25) NOT NULL,
`Spec` varchar(25) NOT NULL,
`Position` set('Healer','Tank','DPS') NOT NULL COMMENT 'Healer, Tank, or DPS',
`Usable` set('Cloth','Mail','Plate') NOT NULL COMMENT 'Type of Usable Armor? Cloth, Mail, Or Plate',
`Primary Stat` set('Agility','Strength','Intellect','Healer','Tank') NOT NULL COMMENT 'Used for Sorting Only(ie dps trinket with agility strength dps not eligible)',
`Initial_DKP` int(11) NOT NULL COMMENT 'DKP given at the start of current tier.',
`Total_DKP` int(11) NOT NULL COMMENT 'Huge Complicated Mess.',
PRIMARY KEY (`ID`)
) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=latin1
CREATE TABLE `Raid_Drops` (
`Record Number` int(11) NOT NULL,
`Date` date NOT NULL,
`Name of Item` varchar(25) NOT NULL,
`Item Slot` enum('Main Hand','Off Hand','Head','Neck','Shoulder','Back','Chest','Wrist','Hands','Waist','Legs','Feet','Ring 1','Ring 2','Trinket 1','Trinket 2') NOT NULL,
`Player_Name` varchar(25) NOT NULL,
`Cost` float NOT NULL,
PRIMARY KEY (`Record Number`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1
You could
Join the three tables in a subselect, grouping on Name
Perform your calculations on the results of each subselect
The only part that's not entirely clear to me is if the names in Characters are unique or not. If they are, you can drop the group by. If they are not, the AVG might give you unexpected results.
SQL Statement
SELECT sumA
, initialB
, sumC
, sumA + initialB - sumC
, a.Name
FROM (
SELECT Name, SUM(DKP_Change) AS sumA
FROM Attendance
GROUP BY Name
) AS a
INNER JOIN (
SELECT Name, Inital_DKP AS initialB
FROM Characters
) AS b ON a.Name = b.Name
INNER JOIN (
SELECT Name, SUM(Cost) AS sumC
FROM Raid_Drops
GROUP BY Name
) AS c ON c.Name = b.Name

GROUP_CONCAT with ORDER BY , but results are not orderd

SELECT
*,
(SELECT
GROUP_CONCAT(url SEPARATOR '$$' )
FROM project_photos
WHERE project_id = projects.id
ORDER BY priority) AS images
FROM projects
WHERE catID = 2
LIMIT 0,5
above query works fine but the images coloumn are not ordered as priority , unable to understand why it is happening
// Structure for table project is
CREATE TABLE `projects` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`catID` int(11) NOT NULL,
`title` varchar(255) NOT NULL,
`description` varchar(400) NOT NULL,
`url` varchar(255) DEFAULT NULL,
`tags` varchar(255) DEFAULT NULL,
`featured` varchar(3) NOT NULL DEFAULT 'No',
`featured_url` varchar(255) DEFAULT NULL,
`order` int(11) DEFAULT NULL,
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `catID` (`catID`),
CONSTRAINT `FK_catID` FOREIGN KEY (`catID`) REFERENCES `category` (`catID`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=48 DEFAULT CHARSET=latin1;
// Structure for table project_photos is
CREATE TABLE `project_photos` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`url` varchar(250) DEFAULT NULL,
`project_id` int(11) DEFAULT NULL,
`priority` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=347 DEFAULT CHARSET=utf8;
Ordering using ORDER BY is done after applying aggregate functions like GROUP_CONCAT(). To sort the result of GROUP_CONCAT() put it inside the argument:
SELECT
GROUP_CONCAT(url ORDER BY priority SEPARATOR '$$')
FROM project_photos
WHERE project_id = projects.id
Try use ORDER BY inside the GROUP_CONCAT
SELECT
*,
(SELECT
GROUP_CONCAT(url ORDER BY priority SEPARATOR '$$' )
FROM project_photos
WHERE project_id = projects.id
) AS images
FROM projects
WHERE catID = 2
LIMIT 0,5
You should put the order by clause inside the group concat :
GROUP_CONCAT(url SEPARATOR '$$' ORDER BY priority)
The order by outside is ordering the different rows. Since you have only one, it does nothing. Inside the group concat, it will order the elements of the group created by the group by clause.

Doing a COUNT in MySQL

I have the following two tables -- userprofile and videoinfo. videoInfo has a FK to userprofile.
CREATE TABLE `userprofile_userprofile` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`full_name` varchar(100) NOT NULL,
...
)
CREATE TABLE `userprofile_videoinfo` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(256) NOT NULL,
`uploaded_by_id` int(11) NOT NULL,
...
KEY `userprofile_videoinfo_e43a31e7` (`uploaded_by_id`),
CONSTRAINT `uploaded_by_id_refs_id_492ba9396be0968c` FOREIGN KEY (`uploaded_by_id`) REFERENCES `userprofile_userprofile` (`id`)
)
What is the SQL statement I would use to show the count of videos each userprofile has and order by COUNT?
SELECT u.id as userId, count(v.uploaded_by_id) as videosCount
FROM userprofile_userprofile u
LEFT OUTER JOIN userprofile_videoinfo v
ON v.uploaded_by_id = u.id
GROUP BY u.id
ORDER BY count(v.uploaded_by_id) DESC
SELECT uploaded_by_id, COUNT(ID) as cnt
FROM userprofile_videoinfo
GROUP BY uploaded_by_id
ORDER BY cnt DESC;