Mysql : select for update got result from 'not match criteria' - mysql

I have table v and t with relation
v (one) -> t (many)
CREATE TABLE `v` (
`i` int NOT NULL,
`status` varchar(255) DEFAULT NULL,
PRIMARY KEY (`i`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `t` (
`i` int NOT NULL,
`v_id` varchar(255) DEFAULT NULL,
`t_status` varchar(255) DEFAULT NULL,
PRIMARY KEY (`i`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO v (i,status) VALUES(1,'default'),(2,'default'),(3,'default');
INSERT INTO t (i,v_id,t_status) VALUES(1,1,'ON'),(2,1,'ON'),(3,1,'OFF')(4,2,'ON'),(5,2,'ON'),(6,3,'ON');
I need to select record from table v if t of v is have status = 'ON' and lock for update status
select * from v where v.i in (select v.i from v join t on v.i = t.v_id where v.status = 'default' group by v.i) limit 1 for update skip locked;
PS.
I need to select from selected due to I don't want to lock table T
Step 1 open session 1
start transaction;
Step 2 select first record for update
mysql> select * from v where v.i in (select v.i from v join t on v.i = t.v_id where v.status = 'default' group by v.i) limit 1 for update skip locked;
+---+---------+
| i | status |
+---+---------+
| 1 | default |
+---+---------+
1 row in set (0.00 sec)
Step 3 create new session (try to be new application instance)
start transaction;
Step 4 update record session one and commit it
update v set status = 'updated' where v.i = 1;
commit;
Step 5 try to select same query with session 2
mysql> select * from v where v.i in (select v.i from v join t on v.i = t.v_id where v.status =
'default' group by v.i) limit 1 for update skip locked;
+---+---------+
| i | status |
+---+---------+
| 1 | updated |
+---+---------+
1 row in set (0.00 sec)
I so confusing why the result is not match the criteria because I need status = default from table v
OR any suggestion for the query, I'm stuck for a while
Thank you for replied.

Related

MySQL Query Needs Optimization?

I have a couple of queries that serve their purposes but since I'm not an expert and just dabble here and there, I'm wondering if these queries can be optimized.
I'm asking because it seems that as more records are added, the time it takes for the queries to complete also seem to increase.
SELECT u.`UserId`, u.`GroupId`, u.`MemberType`, g.`GroupLevel`, g.`U`, g.`D`,
(SELECT COUNT(u2.`UserId`) FROM `users` u2
INNER JOIN `groups` g2 ON g2.`Active` = 1 AND u2.`UserId` = g2.`UserId`
WHERE u2.`Level` = '$L_MEMBER'
AND u2.`MemberType` = '$M_Type'
AND u2.`CounUserId` = u.`UserId`
AND u2.`Active` = 1
AND g2.`U` > g.`U`
AND g2.`D` < g.`D`) as UsersGroup
FROM `users` u
INNER JOIN `groups` g ON g.`UserId` = u.`UserId` AND g.`Active`
WHERE u.`Level` = '$L_MEMBER' AND u.`DateCreated` < '$subdate' AND u.`Active` = 1
ORDER BY u.`UserId`
SELECT g.`UserId` FROM `groups` g
WHERE g.`U` BETWEEN '$U' AND '$D'
AND g.`UserId` !=0
AND g.`UserId` NOT IN (SELECT da.`TaggedUserId` as UserId FROM `dateawarded` da WHERE da.`UserId` = '$userid' AND `DateTagged` != '$datetagged')
AND g.`UserId` NOT IN (SELECT u.`UserId` FROM `users` u WHERE u.`Membership` <= '1')
AND g.`UserId` NOT IN (SELECT d.`DemotedUserId` FROM `demoted` d WHERE d.`UserId` = '$userid' AND d.`DateDemoted` < '$datetagged 00:00:00')
AND g.`DateModified` < '$thedate'
EXPLAIN Results:
Query 1:
1 PRIMARY g ALL NULL NULL NULL NULL 18747 Using where; Using temporary; Using filesort
1 PRIMARY u eq_ref PRIMARY PRIMARY 4 user_db.g.UserId 1 Using where
2 DEPENDENT SUBQUERY g2 ALL NULL NULL NULL NULL 18747 Using where
2 DEPENDENT SUBQUERY u2 eq_ref PRIMARY PRIMARY 4 user_db.g2.UserId 1 Using where
Query 2:
1 PRIMARY g ALL NULL NULL NULL NULL 18747 Using where
4 SUBQUERY d ALL NULL NULL NULL NULL 6895 Using where
3 SUBQUERY u ALL PRIMARY NULL NULL NULL 9354 Using where
2 SUBQUERY da ALL NULL NULL NULL NULL 39260 Using where
Any help would be appreciated.
Thanks!
To optimize the first query, add these indexes:
ALTER TABLE `groups` ADD INDEX `groups_idx_userid_grouple_u_d` (`UserId`, `GroupLevel`, `U`, `D`);
ALTER TABLE `groups` ADD INDEX `groups_idx_active_userid_u` (`Active`, `UserId`, `U`);
ALTER TABLE `users` ADD INDEX `users_idx_level_activ_useri_datec_group_membe` ( `Level`, `Active`, `UserId`, `DateCreated`, `GroupId`, `MemberType` );
ALTER TABLE `users` ADD INDEX `users_idx_level_member_active_userid_counus` ( `Level`, `MemberType`, `Active`, `UserId`, `CounUserId` );
To optimize the second query, add these indexes:
ALTER TABLE `dateawarded` ADD INDEX `dateawarded_idx_userid_datetagged_taggeduser` (`UserId`, `DateTagged`, `TaggedUserId`);
ALTER TABLE `demoted` ADD INDEX `demoted_idx_userid_datedemote_demoteduse` (`UserId`, `DateDemoted`, `DemotedUserId`);
ALTER TABLE `groups` ADD INDEX `groups_idx_u_userid` (`U`, `UserId`);
ALTER TABLE `users` ADD INDEX `users_idx_membership_userid` (`Membership`, `UserId`);
Did you profile these queries, to see how long they take to run?
Then add more (dummy test) data & see how much longer they take & decide if that is acceptable?
See https://dev.mysql.com/doc/refman/5.5/en/show-profile.html for details.
You will get output something like this
mysql> SHOW PROFILE;
+----------------------+----------+
| Status | Duration |
+----------------------+----------+
| checking permissions | 0.000040 |
| creating table | 0.000056 |
| After create | 0.011363 |
| query end | 0.000375 |
| freeing items | 0.000089 |
| logging slow query | 0.000019 |
| cleaning up | 0.000005 |
+----------------------+----------+
7 rows in set (0.00 sec)
Only you can decide what is an acceptable time for the query to run.
[Udpdate] I only posted an answer as this was too large for a comment. #Raymond is correct to say that EXPLAIN [query] will be very helpful

How to copy data from 1 table to another with different structure

I have a table with the structure
CREATE TABLE `old_reminder` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`applicant_id` int(11) NOT NULL,
`date` datetime NOT NULL,
`type` enum('payment_15_min','payment_1_day','payment_3_day') NOT NULL DEFAULT 'payment_15_min',
PRIMARY KEY (`id`)
)
and I want to migrate its data to another table with the structure
CREATE TABLE `new_reminders` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`payment_reminder_1_count` int(11) DEFAULT NULL,
`payment_reminder_1_date` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
)
basically multiple rows for each user_id will be converted into 1 row with all the enum values as columns.
I have tried the following but it is updating only 1 row
UPDATE reminders
INNER JOIN old_reminder AS `old`
ON user_id = old.applicant_id
SET new_reminder_1_date = IF(old.type = 'payment_15_min' OR old.type = 'payment_1_day' OR old.type = 'payment_3_day', old.date, '2018-01-01 00:00:00'),
payment_reminder_1_count = IF(old.type = 'payment_15_min' OR old.type = 'payment_1_day' OR old.type = 'payment_3_day',
CASE
WHEN old.type = 'payment_15_min' THEN 1
WHEN old.type = 'payment_1_day' THEN payment_reminder_1_count + 1
WHEN old.type = 'payment_3_day' THEN payment_reminder_1_count + 1 END, 0)
WHERE applicant_id = 123;
If you model looks like this
truncate table old_reminder;
insert into old_reminder (applicant_id,date,type) values (123,'2018-01-01','payment_15_min');
insert into old_reminder (applicant_id,date,type) values (123,'2018-01-02','payment_1_day');
truncate table new_reminders;
insert into new_reminders(user_id,payment_reminder_1_date) values (123,'2016-01-01');
Then a simplified version of your query
update new_reminders n join (select * from old_reminder order by applicant_id,`date`) old on old.applicant_id = n.user_id
SET
enumstr = old.type,
payment_reminder_1_date =
case
when old.type in ('payment_15_min','payment_1_day','payment_3_day') then old.date
else '2017-01-01'
end
WHERE applicant_id = 123;
Produces this result which I think is what you mean by it is updating only 1 row
+----+---------+--------------------------+-------------------------+----------------+
| id | user_id | payment_reminder_1_count | payment_reminder_1_date | enumstr |
+----+---------+--------------------------+-------------------------+----------------+
| 1 | 123 | NULL | 2018-01-01 00:00:00 | payment_15_min |
+----+---------+--------------------------+-------------------------+----------------+
1 row in set (0.00 sec)
The where clause is evaluated last in this query and is free to choose which one it wants from the result set - in this case always the first.
if the query is changed to test test the payment_reminder_1_date
update new_reminders n join (select * from old_reminder order by applicant_id,`date`) old on old.applicant_id = n.user_id
SET
enumstr = old.type,
payment_reminder_1_date =
case
when old.type in ('payment_15_min','payment_1_day','payment_3_day') then old.date
else '2017-01-01'
end
WHERE applicant_id = 123 and n.payment_reminder_1_date <> old.`date`
Then you get something more like what you hope for
+----+---------+--------------------------+-------------------------+---------------+
| id | user_id | payment_reminder_1_count | payment_reminder_1_date | enumstr |
+----+---------+--------------------------+-------------------------+---------------+
| 1 | 123 | NULL | 2018-01-02 00:00:00 | payment_1_day |
+----+---------+--------------------------+-------------------------+---------------+
1 row in set (0.00 sec)
But I don't think this is actually what you are looking for and I would suggest a cursor might be the way forward.

MYSQL delete - Table 'USER_TABLE' is specified twice, both as a target for 'DELETE' and as a separate source for data

I am new to MySql large queries, and trying to find some solution for my problem,
I looking for delete duplicate values based on "ID_object" column in my USER_TABLE.
Here is my USER_TABLE description,
`USER_TABLE` (
`ID` varchar(256) NOT NULL,
`ID_OBJECT` varchar(256) DEFAULT NULL,
`INSERTION_TIME` date DEFAULT NULL,
KEY `USER_TABLE_inx01` (`ID`(255)),
KEY `user_inx02` (`ID_OBJECT`(255))
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
I tried the following query to remove the duplicate ID_OBJECTs,
delete from USER_TABLE where id in (
select ID from USER_TABLE,
(select ID_OBJECT, min(INSERTION_TIME) as fecha from USER_TABLE group by ID_OBJECT having count(ID_OBJECT)>1) tbpr
where USER_TABLE.ID_OBJECT = tbpr.ID_OBJECT and USER_TABLE.INSERTION_TIME=tbpr.fecha);
But it says,
SQL Error (1093): Table 'USER_TABLE' is specified twice, both as a target for 'DELETE' and as a separate source for data
Can anyone assist me in this?
This will do it. I haven't attempted to check whether your actual business logic for removing duplicates is correct, since your stated requirement isn't 100% clear anyway, but this is one way you can overcome the error message:
CREATE TEMPORARY TABLE IF NOT EXISTS duplicates AS (
SELECT UT.id
FROM `USER_TABLE` AS UT
INNER JOIN
(SELECT
ID_OBJECT,
MIN(INSERTION_TIME) AS fecha
FROM `USER_TABLE`
GROUP BY ID_OBJECT
HAVING COUNT(ID_OBJECT)>1) AS tbpr
ON
UT.ID_OBJECT = tbpr.ID_OBJECT AND UT.INSERTION_TIME = tbpr.fecha
);
DELETE FROM `USER_TABLE`
WHERE id IN (SELECT id FROM duplicates);
DROP TABLE IF EXISTS duplicates;
You can see a working demo here: https://www.db-fiddle.com/f/amnAPUftLD1SmW67fjVSEv/0
You could change your query slightly
delete from USER_TABLE
where concat(id_object,insertion_time) in
(
select concat(ID_object,fecha) from
(
select ID_OBJECT, min(INSERTION_TIME) as fecha
from USER_TABLE
group by ID_OBJECT
having count(ID_OBJECT)>1
) tbpr
)
But this would not cope with triplicates, quadruplets etc. so maybe you need to reverse the logic and keep only the max where there are multiples
delete from USER_TABLE
where concat(id_object,insertion_time) not in
(
select concat(ID_object,fecha) from
(
select ID_OBJECT, max(INSERTION_TIME) as fecha
from USER_TABLE
group by ID_OBJECT
having count(ID_OBJECT)>1
) tbpr
)
and
id_object not in
(
select ID_object from
(
select ID_OBJECT, count(*) as fecha
from USER_TABLE
group by ID_OBJECT
having count(ID_OBJECT) = 1
) tbpr2
)
;
create table `USER_TABLE` (
`ID` varchar(256) NOT NULL,
`ID_OBJECT` varchar(256) DEFAULT NULL,
`INSERTION_TIME` date DEFAULT NULL,
KEY `USER_TABLE_inx01` (`ID`(255)),
KEY `user_inx02` (`ID_OBJECT`(255))
) ;
truncate table user_table;
insert into user_table values
(1,1,'2017-01-01'),(2,1,'2017-01-02'),(3,1,'2017-01-03'),
(4,2,'2017-01-01');
Result of first query
MariaDB [sandbox]> select * from user_table;
+----+-----------+----------------+
| ID | ID_OBJECT | INSERTION_TIME |
+----+-----------+----------------+
| 2 | 1 | 2017-01-02 |
| 3 | 1 | 2017-01-03 |
| 4 | 2 | 2017-01-01 |
+----+-----------+----------------+
3 rows in set (0.00 sec)
Result of second query
MariaDB [sandbox]> select * from user_table;
+----+-----------+----------------+
| ID | ID_OBJECT | INSERTION_TIME |
+----+-----------+----------------+
| 3 | 1 | 2017-01-03 |
| 4 | 2 | 2017-01-01 |
+----+-----------+----------------+
2 rows in set (0.00 sec)

MySQL complex semi-join without group by

Summary
I am looking for a semi-join(ish) query that selects a number of customers and joins their most recent data from other tables.
At a later time, I wish to directly append conditions to the end of the query: WHERE c.id IN (1,2,3)
Problem
As far as I am aware, my requirement rules out GROUP BY:
SELECT * FROM customer c
LEFT JOIN customer_address ca ON ca.customer_id = c.id
GROUP BY c.id
# PROBLEM: Cannot append conditions *after* GROUP BY!
With most subquery-based attempts, my problem is the same.
As an additional challenge, I cannot strictly use a semi-join, because I allow at least two types of phone numbers (mobile and landline), which come from the same table. As such, from the phone table I may be joining multiple records per customer, i.e. this is no longer a semi-join. My current solution below illustrates this.
Questions
The EXPLAIN result at the bottom looks performant to me. Am I correct? Are each of the subqueries executed only once? Update: It appears that DEPENDENT SUBQUERY is executed once for each row in the outer query. It would be great if we could avoid this.
Is there a better solution to what I am doing?
DDLs
DROP TABLE IF EXISTS customer;
CREATE TABLE `customer` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
);
DROP TABLE IF EXISTS customer_address;
CREATE TABLE `customer_address` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`customer_id` bigint(20) unsigned NOT NULL,
`street` varchar(85) DEFAULT NULL,
`house_number` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`)
);
DROP TABLE IF EXISTS customer_phone;
CREATE TABLE `customer_phone` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`customer_id` bigint(20) unsigned NOT NULL,
`phone` varchar(32) DEFAULT NULL,
`type` tinyint(3) unsigned NOT NULL COMMENT '1=mobile,2=landline',
PRIMARY KEY (`id`)
);
insert ignore customer values (1);
insert ignore customer_address values (1, 1, "OldStreet", 1),(2, 1, "NewStreet", 1);
insert ignore customer_phone values (1, 1, "12345-M", 1),(2, 1, "12345-L-Old", 2),(3, 1, "12345-L-New", 2);
SELECT * FROM customer;
+----+
| id |
+----+
| 1 |
+----+
SELECT * FROM customer_address;
+----+-------------+-----------+--------------+
| id | customer_id | street | house_number |
+----+-------------+-----------+--------------+
| 1 | 1 | OldStreet | 1 |
| 2 | 1 | NewStreet | 1 |
+----+-------------+-----------+--------------+
SELECT * FROM customer_phone;
+----+-------------+-------------+------+
| id | customer_id | phone | type |
+----+-------------+-------------+------+
| 1 | 1 | 12345-M | 1 |
| 2 | 1 | 12345-L-Old | 2 |
| 3 | 1 | 12345-L-New | 2 |
+----+-------------+-------------+------+
Solution so far
SELECT *
FROM customer c
# Join the most recent address
LEFT JOIN customer_address ca ON ca.id = (SELECT MAX(ca.id) FROM customer_address ca WHERE ca.customer_id = c.id)
# Join the most recent mobile phone number
LEFT JOIN customer_phone cphm ON cphm.id = (SELECT MAX(cphm.id) FROM customer_phone cphm WHERE cphm.customer_id = c.id AND cphm.`type` = 1)
# Join the most recent landline phone number
LEFT JOIN customer_phone cphl ON cphl.id = (SELECT MAX(cphl.id) FROM customer_phone cphl WHERE cphl.customer_id = c.id AND cphl.`type` = 2)
# Yay conditions appended at the end
WHERE c.id IN (1,2,3)
Fiddle
This fiddle gives the appropriate result set using the given solution. See my questions above.
http://sqlfiddle.com/#!9/98c57/3
I would avoid those dependent subqueries, instead try this:
SELECT
*
FROM customer c
LEFT JOIN (
SELECT
customer_id
, MAX(id) AS currid
FROM customer_phone
WHERE type = 1
GROUP BY
customer_id
) gm ON c.id = gm.customer_id
LEFT JOIN customer_phone mobis ON gm.currid = mobis.id
LEFT JOIN (
SELECT
customer_id
, MAX(id) AS currid
FROM customer_phone
WHERE type = 2
GROUP BY
customer_id
) gl ON c.id = gl.customer_id
LEFT JOIN customer_phone lands ON gl.currid = lands.id
WHERE c.id IN (1, 2, 3)
;
or, perhaps:
SELECT
*
FROM customer c
LEFT JOIN (
SELECT
customer_id
, MAX(case when type = 1 then id end) AS mobid
, MAX(case when type = 2 then id end) AS lndid
FROM customer_phone
GROUP BY
customer_id
) gp ON c.id = gp.customer_id
LEFT JOIN customer_phone mobis ON gp.mobid = mobis.id
LEFT JOIN customer_phone lands ON gp.lndid = lands.id
WHERE c.id IN (1, 2, 3)
;
see: http://sqlfiddle.com/#!9/ef983/1/

mysql update multiple tables with single sql to get sum(qty)

I have two tables with data
CREATE TABLE `MASTER` (
`NAME` VARCHAR(10) NOT NULL,
`QTY` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`NAME`)
);
NAME | QTY
----------
'ABC' | 0
'XYZ' | 0
CREATE TABLE `DETAIL` (
`NAME` VARCHAR(10) NOT NULL,
`QTY` INT(10) UNSIGNED NOT NULL,
`FLAG` TINYINT(1) UNSIGNED NOT NULL
);
NAME | QTY| FLAG
--------------------
'ABC' | 10 | 0
'ABC' | 20 | 0
'PQR' | 15 | 0
'PQR' | 25 | 0
i want to update sum(detail.qty) to master and set its flag to 1
so i have written query
UPDATE MASTER M, DETAIL D
SET M.QTY = M.QTY + D.QTY,
D.FLAG =1
WHERE M.NAME = D.NAME;
i have guesed MASTER.QTY should be 30 (10 + 20) from detail table.
but it only updates the first value
actual value is MASTER.QTY =10 (only updtaed first value from table)
How can i get MASTER.QTY =30?
Try this query:
update `MASTER` m,`DETAIL` d,
(
SELECT `NAME`, SUM( `QTY` ) as `QTY`
FROM `DETAIL`
GROUP BY `NAME`
) s
SET m.QTY = s.QTY,
d.FLAG = 1
WHERE
m.NAME = s.NAME
AND m.NAME = d.NAME
;
SQLFiddle demo --> http://www.sqlfiddle.com/#!2/ab355/1
IMO, your Master table is unnecessary. You don't need it if the amount of rows ain't in a > 5-digit range.
This equals the MASTER table:
SELECT NAME, SUM(QTY), FLAG FROM DETAIL GROUP BY NAME;
You can create a view from that easily.
Your answer anyways:
UPDATE MASTER m
JOIN DETAIL d ON m.NAME = d.NAME
SET
d.FLAG = 1,
m.QTY = (SELECT SUM(QTY) FROM DETAIL WHERE NAME = d.NAME GROUP BY NAME)
WHERE m.NAME = d.NAME
Also, always follow normalization rules: https://en.wikipedia.org/wiki/Database_normalization