mysql query takes 4 min to execute - mysql

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.

Related

Get index of certain result in query results list?

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

Aggregating existing query to work for multiple rows

I have a ledger table, and right now I have the ability to find the date or NULL if someone is delinquent based on their payment history. I need a query that allows me to find all delinquent members instead of just a specific one.
I need the ability to run a query that gets any member that is delinquent and return to me the member_id and the date of delinquency.
Basically what the original query to find delinquency for a specific member does, just doing every member instead of a specific one.
I have tried:
SELECT DISTINCT member_id, created_at FROM member_ledger_items WHERE
balance > 0 and id > (
IFNULL(
(SELECT id from member_ledger_items WHERE balance <= 0 and member_ledger_items.deleted_at is NULL GROUP BY member_id ORDER BY created_at, id desc LIMIT 1),
0
)
) and `member_ledger_items`.`deleted_at` is null GROUP BY member_id order by created_at asc, id asc;
This is the query to find if a specific member is delinquent:
select `created_at` from `member_ledger_items` where `member_id` = ? and `balance` > 0 and `id` >
(
IFNULL(
(select `id` from `member_ledger_items` where `member_id` = ? and `balance` <= 0 and `member_ledger_items`.`deleted_at` is null order by `created_at` desc, `id` desc limit 1)
, 0)
)
and `member_ledger_items`.`deleted_at` is null order by `created_at` asc, `id` asc limit 1;
Here is the create syntax of the member_ledger_items table:
CREATE TABLE `member_ledger_items` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`member_id` int(10) unsigned NOT NULL,
`status` varchar(20) COLLATE utf8_unicode_ci NOT NULL,
`type` enum('credit','debit') COLLATE utf8_unicode_ci NOT NULL,
`category` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`memo` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`amount` decimal(13,3) DEFAULT NULL,
`autopay` tinyint(1) DEFAULT NULL,
`late` tinyint(1) DEFAULT NULL,
`date` date NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
`deleted_at` timestamp NULL DEFAULT NULL,
`balance` decimal(13,3) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=53596 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
I need rows with member_id and date of starting delinquency.
Is this possible?
Any help would be appreciated!
SELECT `member_id`,
(SELECT `created_at`
FROM `member_ledger_items` AS MLI2
WHERE `balance` > 0
AND MLI2.`member_id` = MLI.`member_id`
AND `id` > ( Ifnull((SELECT `id`
FROM `member_ledger_items` AS MLI3
WHERE `balance` <= 0
AND MLI3.`member_id` =
MLI2.`member_id`
AND MLI3.`deleted_at` IS NULL
ORDER BY `created_at` DESC,
`id` DESC
LIMIT 1), 0) )
AND MLI2.`deleted_at` IS NULL
ORDER BY `created_at` ASC,
`id` ASC
LIMIT 1) AS created_date
FROM `member_ledger_items` AS MLI
GROUP BY `member_id`;
Ended up being the solution

mysql order rows by a record with two different values

i have records that have value equal to 1 or 2 i want to order rows by this record like this
1
2
1
2
1
2
1
how can i do this order by only orders by DESC or ASC ?
note : there is a primary key id, and there is a created date
CREATE TABLE `users` (
`uid` INT(11) NOT NULL AUTO_INCREMENT,
`phone_number` VARCHAR(16) NOT NULL,
`password` VARCHAR(100) NOT NULL,
`first_name` VARCHAR(100) NOT NULL,
`last_name` VARCHAR(100) NOT NULL,
`sex` TINYINT(4) NOT NULL, << this will have only 1 or 2
`created_date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`account_status` TINYINT(1) NOT NULL,
PRIMARY KEY (`uid`)
);
Try this query:
SELECT t.*
FROM (
SELECT STRAIGHT_JOIN uid, first_name ,
#y:=IF(#sex=sex,#y+1,1) AS rnk, #sex:=sex AS sex
FROM (SELECT #sex:=NULL) AS x
JOIN users
ORDER BY sex, uid DESC
) AS t
ORDER BY rnk ASC, sex ASC;

MySQL Order by subquery column

I have problem with sql query. The idea is to select all loans that are after payment (status 1/2/3) between 8 and 21 days with calculated value from payment_day til now.
I have already done some query but can't use columns days_after_payment and days_after_part_payment in WHERE section. I would like to have one column like days_after_payment based on loan type.
SELECT l.*,
(SELECT SUM(`value`) FROM `loan_part` WHERE `loan_id` = l.id AND `paid`=0) AS left_to_pay,
-(DATEDIFF((SELECT date FROM `loan_part` WHERE `loan_id` = l.id AND `paid`=0 AND `date`<CURDATE() ORDER BY `date` LIMIT 1), NOW())) AS days_after_part_payment,
-(DATEDIFF(l.payment_date, NOW())) AS days_after_payment
FROM loan l
WHERE (l.type=1 or l.type=2) AND (l.status=1 OR l.status=2 OR l.status=3)
GROUP BY l.client_id
ORDER BY
CASE l.type
WHEN 1 THEN days_after_payment
WHEN 2 THEN days_after_part_payment
ELSE 1 END
ASC
CREATE TABLE IF NOT EXISTS `loan` (
`id` int(11) NOT NULL,
`value` int(11) NOT NULL,
`client_id` int(11) NOT NULL,
`status` int(11) NOT NULL,
`type` int(11) NOT NULL,
`payment_date` date DEFAULT NULL
) ENGINE=MyISAM AUTO_INCREMENT=2068 DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `loan_part` (
`id` int(10) unsigned NOT NULL,
`loan_id` int(11) NOT NULL,
`value` float NOT NULL,
`date` date DEFAULT NULL,
`paid` tinyint(1) NOT NULL DEFAULT '0'
) ENGINE=MyISAM AUTO_INCREMENT=1751 DEFAULT CHARSET=utf8;
Update1 : I had to cut unnecessary columns and rewrite it into English from my native language.
ORDER BY 7
"7" means the 7th field in the SELECT. That works for GROUP BY also. I had to see the table definition to count how many in l.*.
How come id is not declared AUTO_INCREMENT?

MySQL Need advice on Query

I want to fetch latest 3 news from each news type.
CREATE TABLE IF NOT EXISTS `news` (
`news_id` int(8) NOT NULL AUTO_INCREMENT,
`news_heading` tinytext NOT NULL,
`news_description` text NOT NULL,
`news_date` date DEFAULT NOT NULL,
`news_type` tinyint(1) NOT NULL COMMENT '0- PEP|1 - MEDIA|2 - CONSULTING',
`created_date` datetime NOT NULL,
`modified_date` datetime NULL,
`display` tinyint(1) NOT NULL COMMENT '0- ON | 1 -OFF',
PRIMARY KEY (`news_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=2 ;
Below Query will give me only 1 latest news from all type. Suggest me how we can achieve for top 3 from each type
SELECT * FROM (
SELECT * FROM `news`
ORDER BY `created_date` DESC
) AS TBL
GROUP BY `news_type`
Try this:
SELECT news_id, news_heading, news_description, news_date,
news_type, created_date, modified_date, display
FROM (SELECT news_id, news_heading, news_description, news_date,
news_type, created_date, modified_date, display,
IF(#news_type = #news_type:=news_type, #id:=#id+1, #id:=1) AS id
FROM news, (SELECT #id:=1, #news_type:=0) A
ORDER BY news_type, created_date DESC
) AS A
WHERE id <= 3;