Update the next row of the target row in MySQL - mysql

Suppose I have a table that tracks if a payment is missed like this:
+----+---------+------------+------------+---------+--------+
| id | loan_id | amount_due | due_at | paid_at | missed |
+----+---------+------------+------------+---------+--------+
| 1 | 1 | 100 | 2013-08-17 | NULL | NULL |
| 5 | 1 | 100 | 2013-09-17 | NULL | NULL |
| 7 | 1 | 100 | 2013-10-17 | NULL | NULL |
+----+---------+------------+------------+---------+--------+
And, for example, I ran a query that checks if a payment is missed like this:
UPDATE loan_payments
SET missed = 1
WHERE DATEDIFF(NOW(), due_at) >= 10
AND paid_at IS NULL
Then suppose that the row with id = 1 gets affected. I want the amount_due of row with id = 1 be added to the amount_due of the next row so the table would look like this:
+----+---------+------------+------------+---------+--------+
| id | loan_id | amount_due | due_at | paid_at | missed |
+----+---------+------------+------------+---------+--------+
| 1 | 1 | 100 | 2013-08-17 | NULL | 1 |
| 5 | 1 | 200 | 2013-09-17 | NULL | NULL |
| 7 | 1 | 100 | 2013-10-17 | NULL | NULL |
+----+---------+------------+------------+---------+--------+
Any advice on how to do it?
Thanks

Take a look at this :
SQL Fiddle
MySQL 5.5.32 Schema Setup:
CREATE TABLE loan_payments
(`id` int, `loan_id` int, `amount_due` int,
`due_at` varchar(10), `paid_at` varchar(4), `missed` varchar(4))
;
INSERT INTO loan_payments
(`id`, `loan_id`, `amount_due`, `due_at`, `paid_at`, `missed`)
VALUES
(1, 1, 100, '2013-09-17', NULL, NULL),
(3, 2, 100, '2013-09-17', NULL, NULL),
(5, 1, 100, '2013-10-17', NULL, NULL),
(7, 1, 100, '2013-11-17', NULL, NULL)
;
UPDATE loan_payments AS l
LEFT OUTER JOIN (SELECT loan_id, MIN(ID) AS ID
FROM loan_payments
WHERE DATEDIFF(NOW(), due_at) < 0
GROUP BY loan_id) AS l2 ON l.loan_id = l2.loan_id
LEFT OUTER JOIN loan_payments AS l3 ON l2.id = l3.id
SET l.missed = 1, l3.amount_due = l3.amount_due + l.amount_due
WHERE DATEDIFF(NOW(), l.due_at) >= 10
AND l.paid_at IS NULL
;
Query 1:
SELECT *
FROM loan_payments
Results:
| ID | LOAN_ID | AMOUNT_DUE | DUE_AT | PAID_AT | MISSED |
|----|---------|------------|------------|---------|--------|
| 1 | 1 | 100 | 2013-09-17 | (null) | 1 |
| 3 | 2 | 100 | 2013-09-17 | (null) | 1 |
| 5 | 1 | 200 | 2013-10-17 | (null) | (null) |
| 7 | 1 | 100 | 2013-11-17 | (null) | (null) |

Unfortunately I don't have time at the moment to write out full-blown SQL, but here's the psuedocode I think you need to implement:
select all DISTINCT loan_id from table loan_payments
for each loan_id:
set missed = 1 for all outstanding payments for loan_id (as determined by date)
select the sum of all outstanding payments for loan_id
add this sum to the amount_due for the loan's next due date after today
Refer to this for how to loop using pure MySQL: http://dev.mysql.com/doc/refman/5.7/en/cursors.html

I fixed my own problem by adding a missed_at field. I put the current timestamp ($now) in a variable before I update the first row to missed = 1 and missed_at = $now then I ran this query to update the next row's amount_due:
UPDATE loan_payments lp1 JOIN loan_payments lp2 ON lp1.due_at > lp2.due_at
SET lp1.amount_due = lp2.amount_due + lp1.amount_due
WHERE lp2.missed_at = $now AND DATEDIFF(lp1.due_at, lp2.due_at) <= DAYOFMONTH(LAST_DAY(lp1.due_at))
I wish I could use just use LIMIT 1 to that query but it turns out that it's not possible for an UPDATE query with a JOIN.
So all in all, I used two queries to achieve what I want. It did the trick.
Please advise if you have better solutions.
Thanks!

Related

Update first occurrence of value in a time interval

I'm trying to set the value of another column on the first occurrence of any value in a username column in monthly intervals, if there's another column with an specific value.
create table table1
(
username varchar(30) not null,
`date` date not null,
eventid int not null,
firstFlag int null
);
insert table1 (username,`date`, eventid) values
('john','2015-01-01', 1)
, ('kim','2015-01-01', 1)
, ('john','2015-01-01', 1)
, ('john','2015-01-01', 1)
, ('john','2015-03-01', 2)
, ('john','2015-03-01', 1)
, ('kim','2015-01-01', 1)
, ('kim','2015-02-01', 1);
This should result in:
| username | date | eventid | firstFlag |
|----------|------------|---------|-----------|
| john | 2015-01-01 | 1 | 1 |
| kim | 2015-01-01 | 1 | 1 |
| john | 2015-01-01 | 1 | (null) |
| john | 2015-01-01 | 1 | (null) |
| john | 2015-03-01 | 2 | 1 |
| john | 2015-03-01 | 1 | (null) |
| kim | 2015-01-01 | 1 | (null) |
| kim | 2015-02-01 | 1 | 1 |
I've tried using joins as described here, but it updates all rows:
update table1 t1
inner join
( select username,min(`date`) as minForGroup
from table1
group by username,`date`
) inr
on inr.username=t1.username and inr.minForGroup=t1.`date`
set firstFlag=1;
As a1ex07 points out, it would need another per row unique constrain to update the rows I need to:
update table1 t1
inner join
( select id, username,min(`date`) as minForGroup
from table1
where eventid = 1
group by username,month(`date`)
) inr
on inr.id=t1.id and inr.username=t1.username and inr.minForGroup=t1.`date`
set firstFlag=1;
Add an Id column, and use it on the join on constrains.
To allow only those that satisfies a specific condition on another column you need the where clause inside the subquery, otherwise it would try to match different rows as the subquery would return rows with eventid=2 while the update query would return only those with eventid=1.
To use yearly intervals instead of monthly, change the group by statement to use years.

How to get partial sum of column and finds which rows has partial sum in mysql

<!-- language: lang-none -->
+----------------------------------------+
| Here is my sample table |
+----------------------------------------+
| Date Person Column_1 Column_2 |
| 15-03-13 A 100 NULL |
| 15-03-13 B NULL 100 |
| 16-03-13 A 100 50 |
| 16-03-13 B NULL NULL |
| 17-03-13 A 100 50 |
| 17-03-13 B 20 30 |
+----------------------------------------+
Now i wanted to do sum(column_1) by date. But i also need to which sum of row includes NULL data.
here is the output result I wanted to achieve
+---------------------------------+
| Date SUM includesNullval |
+---------------------------------+
| 15-03-13 100 true |
| 16-03-13 100 true |
| 17-03-13 120 false |
+---------------------------------+
I don't know to achieve above output. Can anybody give me any idea or solution of this problem?
Note both the form and content of the answer.
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(date DATE NOT NULL
,person CHAR(1) NOT NULL
,column_1 INT NULL
,column_2 INT NULL
,PRIMARY KEY(date,person)
);
INSERT INTO my_table VALUES
('2013-03-15','A', 100, NULL),
('2013-03-15','B',NULL, 100),
('2013-03-16','A', 100, 50),
('2013-03-16','B',NULL, NULL),
('2013-03-17','A', 100, 50),
('2013-03-17','B', 20, 30);
SELECT date, SUM(column_1), MAX(column_2 IS NULL) incnull FROM my_table GROUP BY date;
+------------+---------------+---------+
| date | SUM(column_1) | incnull |
+------------+---------------+---------+
| 2013-03-15 | 100 | 1 |
| 2013-03-16 | 100 | 1 |
| 2013-03-17 | 120 | 0 |
+------------+---------------+---------+
Using a case statement will help you here:
Select Date_Column, sum(column_A),
MAX(CASE when column_A is NULL then 'True' else 'False' end) "Includes_NULL_Value"
from table_x
group by Date_column;
REX TESTER

SQL select query with multiple conditions issue

I have a problem with a SQL select query, I can't figure out what it needs to be.
This is what my items table look like:
| id | i_id | last_seen | spot |
----------------------------------------------------
| 1 | ls100 | 2017-03-10 15:30:40 | spot800 |
| 2 | ls100 | 2017-03-10 16:20:15 | spot753 |
| 3 | ls200 | 2017-03-10 16:33:10 | spot800 |
| 4 | ls300 | 2017-03-10 15:30:40 | spot800 |
| 5 | ls300 | 2017-03-10 12:10:30 | spot800 |
| 6 | ls400 | 2017-03-10 10:30:10 | spot800 |
This is what I'm trying to obtain:
| id | i_id | last_seen | spot |
----------------------------------------------------
| 3 | ls200 | 2017-03-10 16:33:10 | spot800 |
| 5 | ls300 | 2017-03-10 12:10:30 | spot800 |
So I need to have the rows where spot= 'spot800', last_seen = MAX(but only if the DateTime is the newest compared to all spots with the samei_id`), and at last the DateTime must be bigger than '2017-03-10 11:00:00'.
This is what I have so far:
SELECT *
FROM items
WHERE spot = 'spot800'
HAVING MAX(`last_seen`)
AND `last_seen` > '2017-03-10 11:00:00'
E.g.:
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,i_id INT NOT NULL
,last_seen DATETIME NOT NULL
,spot INT NOT NULL
);
INSERT INTO my_table VALUES
(1,100,'2017-03-10 15:30:40',800),
(2,100,'2017-03-10 14:20:15',753),
(3,200,'2017-03-10 16:33:10',800),
(4,300,'2017-03-10 15:30:40',800),
(5,300,'2017-03-10 12:10:30',800),
(6,400,'2017-03-10 10:30:10',800);
SELECT [DISTINCT] x.*
FROM my_table x
LEFT
JOIN my_table y
ON y.i_id = x.i_id
AND y.last_seen < x.last_seen
WHERE x.last_seen > '2017-03-10 11:00:00'
AND x.spot = 800
AND y.id IS NULL;
----+------+---------------------+------+
| id | i_id | last_seen | spot |
+----+------+---------------------+------+
| 3 | 200 | 2017-03-10 16:33:10 | 800 |
| 5 | 300 | 2017-03-10 12:10:30 | 800 |
+----+------+---------------------+------+
2 rows in set (0.00 sec)
Use MAX and GROUP BY.
SELECT id, i_id, MAX(last_seen), spot
FROM items
WHERE spot = 'spot800'
AND last_seen > '2017-03-10 11:00:00'
GROUP BY id, i_id, spot
There is several things wrng with your statement.
Firstly, HAVING must be accompanied with a GROUP BY clause, so it's not what you are looking for.
Also, MAX is an aggregate, not a boolean, function. That is, it cannot be used in filters, such as a where clause or a having clause. Also, if it did work, MAX would only return the entry that contains the time as '2017-03-10 16:33:10'. Not what you expected.
Try this instead:
SELECT * FROM items WHERE (spot='spot800' AND last_seen > '2017-03-10 11:00:00');

Find and Delete Duplicate rows in MySQL

I'm having trouble finding duplicates in a database table with the following setup:
==========================================================================
| stock_id | product_id | store_id | stock_qty | updated_at |
==========================================================================
| 9990 | 51 | 1 | 13 | 2014-10-25 16:30:01 |
| 9991 | 90 | 2 | 5 | 2014-10-25 16:30:01 |
| 9992 | 161 | 1 | 3 | 2014-10-25 16:30:01 |
| 9993 | 254 | 1 | 18 | 2014-10-25 16:30:01 |
| 9994 | 284 | 2 | 12 | 2014-10-25 16:30:01 |
| 9995 | 51 | 1 | 11 | 2014-10-25 17:30:02 |
| 9996 | 90 | 2 | 5 | 2014-10-25 17:30:02 |
| 9997 | 161 | 1 | 3 | 2014-10-25 17:30:02 |
| 9998 | 254 | 1 | 16 | 2014-10-25 17:30:02 |
| 9999 | 284 | 2 | 12 | 2014-10-25 17:30:02 |
==========================================================================
Stock updates are imported into this table every hour, I'm trying to find duplicate stock entries (any rows which have a matching product id and store id) so I can delete the oldest. The query below is my attempt, by comparing product ids and store ids on a join like this I can find one set of duplicates:
SELECT s.`stock_id`, s.`product_id`, s.`store_id`, s.`stock_qty`, s.`updated_at`
FROM `stock` s
INNER JOIN `stock` j ON s.`product_id`=j.`product_id` AND s.`store_id`=j.`store_id`
GROUP BY `stock_id`
HAVING COUNT(*) > 1
ORDER BY s.updated_at DESC, s.product_id ASC, s.store_id ASC, s.stock_id ASC;
While this query will work, it doesn't find ALL duplicates, only 1 set, which means if an import goes awry and isn't noticed until the morning, there's a possibility that we'll be left with tons of duplicate stock entries. My MySQL skills are sadly lacking and I'm at a complete loss about how to find and delete all duplicates in a fast, reliable manner.
Any help or ideas are welcome. Thanks
You can use this query:
DELETE st FROM stock st, stock st2
WHERE st.stock_id < st2.stock_id AND st.product_id = st2.product_id AND
st.store_id = st2.store_id;
This query will delete older record having same product_id and store_id and will keep latest record.
A self join on store_id, product_id and 'is older' in combination with DISTINCT should give you all rows where also a newer version exists:
> SHOW CREATE TABLE stock;
CREATE TABLE `stock` (
`stock_id` int(11) NOT NULL,
`product_id` int(11) DEFAULT NULL,
`store_id` int(11) DEFAULT NULL,
`stock_qty` int(11) DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
PRIMARY KEY (`stock_id`)
> select * from stock;
+----------+------------+----------+-----------+---------------------+
| stock_id | product_id | store_id | stock_qty | updated_at |
+----------+------------+----------+-----------+---------------------+
| 1 | 1 | 1 | 1 | 2001-01-01 12:00:00 |
| 2 | 2 | 2 | 1 | 2001-01-01 12:00:00 |
| 3 | 2 | 2 | 1 | 2002-01-01 12:00:00 |
+----------+------------+----------+-----------+---------------------+
> SELECT DISTINCT s1.stock_id, s1.store_id, s1.product_id, s1.updated_at
FROM stock s1 JOIN stock s2
ON s1.store_id = s2.store_id
AND s1.product_id = s2.product_id
AND s1.updated_at < s2.updated_at;
+----------+----------+------------+---------------------+
| stock_id | store_id | product_id | updated_at |
+----------+----------+------------+---------------------+
| 2 | 2 | 2 | 2001-01-01 12:00:00 |
+----------+----------+------------+---------------------+
> DELETE stock FROM stock
JOIN stock s2 ON stock.store_id = s2.store_id
AND stock.product_id = s2.product_id
AND stock.updated_at < s2.updated_at;
Query OK, 1 row affected (0.02 sec)
> select * from stock;
+----------+------------+----------+-----------+---------------------+
| stock_id | product_id | store_id | stock_qty | updated_at |
+----------+------------+----------+-----------+---------------------+
| 1 | 1 | 1 | 1 | 2001-01-01 12:00:00 |
| 3 | 2 | 2 | 1 | 2002-01-01 12:00:00 |
+----------+------------+----------+-----------+---------------------+
Or you can use a stored Procedure:
DELIMITER //
DROP PROCEDURE IF EXISTS removeDuplicates;
CREATE PROCEDURE removeDuplicates(
stockID INT
)
BEGIN
DECLARE stockToKeep INT;
DECLARE storeID INT;
DECLARE productID INT;
-- gets the store and product value
SELECT DISTINCT store_id, product_id
FROM stock
WHERE stock_id = stockID
LIMIT 1
INTO
storeID, productID;
SELECT stock_id
FROM stock
WHERE product_id = productID AND store_id = storeID
ORDER BY updated_at DESC
LIMIT 1
INTO
stockToKeep;
DELETE FROM stock
WHERE product_id = productID AND store_id = storeID
AND stock_id != stockToKeep;
END //
DELIMITER ;
And afterwards call it for every pair of the product id and store id via a cursor procedure:
DELIMITER //
CREATE PROCEDURE updateTable() BEGIN
DECLARE done BOOLEAN DEFAULT FALSE;
DECLARE stockID INT UNSIGNED;
DECLARE cur CURSOR FOR SELECT DISTINCT stock_id FROM stock;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done := TRUE;
OPEN cur;
testLoop: LOOP
FETCH cur INTO stockID;
IF done THEN
LEAVE testLoop;
END IF;
CALL removeDuplicates(stockID);
END LOOP testLoop;
CLOSE cur;
END//
DELIMITER ;
And then just call the second procedure
CALL updateTable();

Update table records satisfying a condition related to the same table

I'm trying to update a date column of records satisfying following condition:
Column STATION has to be the same as STATION from record with FILTER_NR = x
AND STATUS IN (11, 12, 13)
AND FILTER_NR != x
If x = 3 the UPDATE Statement, I'm looking for, should change the Table FILTER from:
+-----------+------------+---------+---------+
| FILTER_NR | PROBEDATE | STATION | STATUS |
+-----------+------------+---------+---------+
| 1 | 2011-06-01 | 1 | 10 |
| 2 | 2011-06-02 | 1 | 11 |
| 3 | 2011-06-03 | 1 | 12 |
| 4 | 2011-06-04 | 2 | 13 |
+-----------+------------+---------+---------+
to:
+-----------+------------+---------+----------+
| FILTER_NR | PROBEDATE | STATION | STATUS |
+-----------+------------+---------+----------+
| 1 | 2011-06-01 | 1 | 10 | -> not changed
| 2 | 2011-06-01 | 1 | 11 | -> changed
| 3 | 2011-06-03 | 1 | 12 | -> not changed
| 4 | 2011-06-04 | 2 | 13 | -> not changed
+-----------+------------+---------+----------+
I began with following SQL Statement, do you know how I can complete it?
UPDATE FILTER SET PROBEDATE = ADDDATE(PROBEDATE, -1)
WHERE FILTER_NR IN (...);
This should work for you:
UPDATE `FILTER` `F`
INNER JOIN `FILTER` `F1` ON `F1`.`FILTER_NR` = 3 AND `F1`.`STATION` = `F`.`STATION`
SET `F`.`PROBEDATE` = CURDATE()
WHERE `F`.`FILTER_NR` != 3
AND `F`.`STATUS` IN (11, 12, 13);
In my example, I've set PROBEDATE to the current date but please feel free to set it to what you might like.
Hope this helps!
You could ty something like this:
UPDATE FILTER
SET PROBEDATE = PROBEDATE - inteval 1 day
WHERE STATUS IN (11,12,13)
AND FILTER_NR != 3
AND STATION IN
(
SELECT STATION
FROM (
SELECT *
FROM FILTER
) as SubQueryAlias
WHERE FILTER_NR = 3
)