Mysql update all rows value with count of same table column - mysql

I have following table with data:
| predp_id | strp_ID | predp_nas |
| -------- | ------- | --------- |
| 1 | 1 | null |
| 2 | 1 | null |
| 3 | 1 | null |
| 4 | 2 | null |
| 5 | 2 | null |
| 6 | 3 | null |
predp_nas column should be count of strp_ID column + 1 for same strp_ID on every row.
I am currently using next query to achieve this on every new insert:
INSERT INTO PREDMETIP
(`strp_ID`, `predp_nas`)
VALUES(
1,
(SELECT counter + 1 FROM (SELECT COUNT(strp_ID) counter FROM PREDMETIP WHERE strp_ID = '1') t)
);
This gives me:
| predp_id | strp_ID | predp_nas |
| -------- | ------- | --------- |
| 1 | 1 | null |
| 2 | 1 | null |
| 3 | 1 | null |
| 4 | 2 | null |
| 5 | 2 | null |
| 6 | 3 | null |
| 7 | 1 | 4 |
But now I have imported large amount of data and I need to update all predp_nas fields at once to give me result:
| predp_id | strp_ID | predp_nas |
| -------- | ------- | --------- |
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 3 |
| 4 | 2 | 1 |
| 5 | 2 | 2 |
| 6 | 3 | 1 |
| 7 | 1 | 4 |
I have DB fiddle with insert query View on DB Fiddle , I am having trouble understanding how to write query for same thing but to update all fields at once. Any help is appreciated.

What you're looking for is ROW_NUMBER() (if you're using MySQL 8+), but since your fiddle is on MySQL 5.7 I'm assuming that's your version and so you can emulate it by counting the number of rows for a given strp_ID that have a lower predp_id and using that to update the table:
UPDATE PREDMETIP p1
JOIN (
SELECT p1.predp_id,
COUNT(p2.predp_id) + 1 AS rn
FROM PREDMETIP p1
LEFT JOIN PREDMETIP p2 ON p2.strp_ID = p1.strp_ID AND p2.predp_id < p1.predp_id
GROUP BY p1.predp_id
) p2 ON p1.predp_id = p2.predp_id
SET p1.predp_nas = p2.rn
;
SELECT *
FROM PREDMETIP
Output after update:
predp_id strp_ID predp_nas
1 1 1
2 1 2
3 1 3
4 2 1
5 2 2
6 3 1
7 1 4

You seeem to be looking for an update query. If you are running MySQL 8.0, you can do this with row_number():
update predmetip p
inner join (
select p.*, row_number() over(partition by predp_id order by strp_id) rn
from predmetip p
) p1 on p1.predp_id = p.predp_id and p1.strp_id = p.strp_id
set p.predp_nas = p1.rn
On the other hand, if you are running a MySQL 5.x version, then one option is to use correlated subqueries, as demonstrated in Nick's answer. This works fine - and I upvoted Nick's answer - but the performance tends to quickly degrade when the volume of data gets larger, because you need to scan the table for each and every row in the resultset.
You can do this with user variables, but it's is tricky: since, as explained in the documentation, the order of evaluation of expressions in the select clause is undefined, we need to evaluate and assign in the same expression ; case comes handy for this. Another important thing is that we need to order the rows in a subquery before variables come into play.
You would write the select statement as follows:
set #rn := 0, #strp_id = '';
select
predp_id,
strp_id,
#rn := case
when #strp_id = strp_id then #rn + 1 -- read
when #strp_id := strp_id then 1 -- assign
end as predp_nas
from (
select *
from predmetip
order by strp_id, predp_id
) t
You can then turn it to an update:
set #rn := 0, #strp_id = '';
update predmetip p
inner join (
select
predp_id,
strp_id,
#rn := case
when #strp_id = strp_id then #rn + 1
when #strp_id := strp_id then 1
end as predp_nas
from (
select *
from predmetip
order by strp_id, predp_id
) t
) p1 on p1.predp_id = p.predp_id and p1.strp_id = p.strp_id
set p.predp_nas = p1.predp_nas;
Demo on DB Fiddle (with credits to Nick for creating it in the first place).
To read more about user variables and their tricks, I recommend this excellent answer by Madhur Bhaiya, which also contains another interesting blog link.

Related

Is there a way to UPDATE column values based another column's value?

Part 1 of my SQL task involves restructuring data. The jist of my task is as follows: Based on the event_type, if it is "begin" I am trying to use that "time" to find it's stopping time (in another row) and add it to a column (event_end) on the same row as the start time so that all the data for an event sits nicely in one row.
pID customerID locationID event_type time event_end (new row)
1 1 a begin 12.45
2 2 a begin 11.10
3 1 a stop 1.30
4 2 b begin 9.45
5 3 b stop 8.78
I would like to add another column (event_end), and have event_end = the minimum value of event_start IF event_start = 'stop', IF locationID = locationID, and IF customerID = customerID. The final step would be to delete all event_start 'begin' rows.
I have tried UPDATE SET WHERE sequences, and a little bit of CASE, but my issue is that I cannot wrap my head around how to perform this without a loop like VBA. The following is my best stab at it:
UPDATE table
SET event_end = MIN(time)
WHERE event_type = 'stop'
WHERE customerid = customerid
WHERE locationid = locationid
WHERE time > time
SELECT *
FROM table
I'm hoping to have a table with all event data in one row, not spread out over multiple rows. If this is a handful, I appologize but am thankful in advance.
Thanks
Problem Statement:
Add event_end as an extra attribute to the existing row, data will be populated based on customer_id, location_id.
We will populate data in event_end to all events which have event type as begin
Data would be picked from rows which have the same customer_id, location_id but event type as stop.
Finally, we will remove all events with type stop.
Solution: Consider your table name is customer_events and will use self join concept for the same.
First, identify which records needs to be updated. We can use a SELECT query to identify such records.
c1 table will represent rows with begin event type.
c2 table will represent rows with stop event type.
SELECT *
FROM customer_events c1
LEFT JOIN customer_events c2 ON c1.customerID = c2.customerID AND c1.locationID = c2.locationID AND c1.event_type = 'begin' AND c2.event_type = 'stop'
WHERE c1.event_type = 'begin'; -- As we want to populate data in events with value as `begin`
Write a query to update the records.
UPDATE customer_events c1
LEFT JOIN customer_events c2 ON c1.customerID = c2.customerID AND c1.locationID = c2.locationID AND c1.event_type = 'begin' AND c2.event_type = 'stop'
SET c1.event_end = c2.`time`
WHERE c1.event_type = 'begin';
Now every record with event type as begin has either value in event_end column or it would be null if no records match as stop event.
For rows with event type as stop, either they are mapped with some row with event type as begin or some are not mapped. In both cases, we don't want to keep them. To remove all records with event type as stop.
DELETE FROM customer_events
WHERE event_type = 'stop';
Note: Don't run DELETE statement unless you are sure that this solution will work for you.
Updated: We can have multiple records of begin & stop events for single customer & location.
Sample Input:
| pID | customerID* | *locationID* | *event_type* | *time* | *event_end* |
| 1 | 1 | a | begin | 02:45:00 | |
| 2 | 2 | a | begin | 03:10:00 | |
| 3 | 1 | b | begin | 04:30:00 | |
| 4 | 2 | b | begin | 05:45:00 | |
| 5 | 2 | a | stop | 06:49:59 | |
| 6 | 1 | a | begin | 07:38:00 | |
| 7 | 3 | b | begin | 08:57:19 | |
| 8 | 2 | b | stop | 09:57:43 | |
| 9 | 3 | b | stop | 10:58:03 | |
| 10 | 4 | a | begin | 11:58:34 | |
| 11 | 1 | a | stop | 12:09:36 | |
| 12 | 1 | b | stop | 13:09:50 | |
| 13 | 1 | a | stop | 14:10:02 | |
Query:
SELECT *
FROM (
SELECT
ce.*,
IF(#c_id <> ce.customerId OR #l_id <> ce.locationID, #rank:= 1, #rank:= #rank + 1 ) as rank,
#c_id:= ce.customerId,
#l_id:= ce.locationID
FROM customer_events ce,
(SELECT #c_id:= 0 c, #l_id:= '' l, #rank:= 0 r) AS t
WHERE event_type = 'begin'
ORDER BY customerId, locationID, `time`) AS c1
LEFT JOIN (
SELECT
ce.*,
IF(#c_id <> ce.customerId OR #l_id <> ce.locationID, #rank:= 1, #rank:= #rank + 1 ) as rank,
#c_id:= ce.customerId,
#l_id:= ce.locationID
FROM customer_events ce,
(SELECT #c_id:= 0 c, #l_id:= '' l, #rank:= 0 r) AS t
WHERE event_type = 'stop'
ORDER BY customerId, locationID, `time`
) AS c2 ON c1.customerID = c2.customerID AND c1.locationID = c2.locationID AND c1.rank = c2.rank;
Output:
| pId | customerID| locationId| event_type| Start_Time|End_Id| End_Time |
| 1 | 1 | a | begin | 02:45:00 | 11 | 12:09:36 |
| 6 | 1 | a | begin | 07:38:00 | 13 | 14:10:02 |
| 3 | 1 | b | begin | 04:30:00 | 12 | 13:09:50 |
| 2 | 2 | a | begin | 03:10:00 | 5 | 06:49:59 |
| 4 | 2 | b | begin | 05:45:00 | 8 | 09:57:43 |
| 7 | 3 | b | begin | 08:57:19 | 9 | 10:58:03 |
| 10 | 4 | a | begin | 11:58:34 | | |
Update Statement: Create two columns end_pID and event_end for migration.
UPDATE customer_events
INNER JOIN (
SELECT c1.pId, c2.pID End_Id, c2.time AS End_Time
FROM (
SELECT
ce.*,
IF(#c_id <> ce.customerId OR #l_id <> ce.locationID, #rank:= 1, #rank:= #rank + 1 ) as rank,
#c_id:= ce.customerId,
#l_id:= ce.locationID
FROM customer_events ce,
(SELECT #c_id:= 0 c, #l_id:= '' l, #rank:= 0 r) AS t
WHERE event_type = 'begin'
ORDER BY customerId, locationID, `time`) AS c1
LEFT JOIN (
SELECT
ce.*,
IF(#c_id <> ce.customerId OR #l_id <> ce.locationID, #rank:= 1, #rank:= #rank + 1 ) as rank,
#c_id:= ce.customerId,
#l_id:= ce.locationID
FROM customer_events ce,
(SELECT #c_id:= 0 c, #l_id:= '' l, #rank:= 0 r) AS t
WHERE event_type = 'stop'
ORDER BY customerId, locationID, `time`
) AS c2 ON c1.customerID = c2.customerID AND c1.locationID = c2.locationID AND c1.rank = c2.rank) AS tt ON customer_events.pID = tt.pId
SET customer_events.end_pID = t.End_Id, customer_events.event_end = t.End_Time;
Finally, remove all events with event_type = 'stop'

cumulative product over a big MySQL table

I have a big MySQL table on which I'd like to calculate a cumulative product. This product has to be calculated for each group, a group is defined by the value of the first column.
For example :
name | number | cumul | order
-----------------------------
a | 1 | 1 | 1
a | 2 | 2 | 2
a | 1 | 2 | 3
a | 4 | 8 | 4
b | 1 | 1 | 1
b | 1 | 1 | 2
b | 2 | 2 | 3
b | 1 | 2 | 4
I've seen this solution but don't think it would be efficient to join or subselect in my case.
I've seen this solution which is what I want except it does not partition by name.
This is similar to a cumulative sum:
select t.*,
(#p := if(#n = name, #p * number,
if(#n := name, number, number)
)
) as cumul
from t cross join
(select #n := '', #p := 1) params
order by name, `order`;

Mysql - Check if VARCHAR column has a missing value on its incrementation

I'm trying to find out if my values inserted are auto-incrementing correctly or if for any reason one has failed to be inserted, deleted or gone "missing". I've tried several answers from Stackoverflow but they were mainly pointing out autoincrementable int values so they did not help since mine is a VARCHAR value that follows the following sequence:
AA000001
AA000002
...
AA000100
...
AA213978
and so on...
Thanks for your time.
You can declare SQL Vars in Query and calculate the difference in each iteration, as shown in the example below:
Schema
create table MyTable
( ai int auto_increment primary key,
id varchar(100) not null
);
insert MyTable (id) values
('AA000001'),
('AA000002'),
('AA000005'),
('AA000008'),
('AA000009'),
('AA000010');
Query
select id
FROM
(
select
t.id,
SUBSTRING(t.id,3) as s,
CAST(SUBSTRING(t.id,3) AS UNSIGNED) - #lastId as diff,
if( #lastId = 0, 0, CAST(SUBSTRING(t.id,3) AS UNSIGNED) - #lastId) as Difference,
#lastId := CAST(SUBSTRING(t.id,3) AS UNSIGNED) as dummy
from
`MyTable` t,
( select #lastId := 0) SQLVars
order by
t.id
) d
WHERE diff>1;
This is the inside query (not the final result set of the above)
+----------+--------+------+------------+-------+
| id | s | diff | Difference | dummy |
+----------+--------+------+------------+-------+
| AA000001 | 000001 | 1 | 0 | 1 |
| AA000002 | 000002 | 1 | 1 | 2 |
| AA000005 | 000005 | 3 | 3 | 5 |
| AA000008 | 000008 | 3 | 3 | 8 |
| AA000009 | 000009 | 1 | 1 | 9 |
| AA000010 | 000010 | 1 | 1 | 10 |
+----------+--------+------+------------+-------+
Actual Results of Above Query:
+----------+
| id |
+----------+
| AA000005 |
| AA000008 |
+----------+
Here's the SQL Fiddle.
To simply test if there are missing values,
select count(*) <> max(right(col, 6))-min(right(col, 6))+1 || count(*) <> count(distinct col)

I need to get the average for every 3 records in one table and update column in separate table

Table Mytable1
Id | Actual
1 ! 10020
2 | 12203
3 | 12312
4 | 12453
5 | 13211
6 | 12838
7 | 10l29
Using the following syntax:
SELECT AVG(Actual), CEIL((#rank:=#rank+1)/3) AS rank FROM mytable1 Group BY rank;
Produces the following type of result:
| AVG(Actual) | rank |
+-------------+------+
| 12835.5455 | 1 |
| 12523.1818 | 2 |
| 12343.3636 | 3 |
I would like to take AVG(Actual) column and UPDATE a second existing table Mytable2
Id | Predict |
1 | 11133
2 | 12312
3 | 13221
I would like to get the following where the Actual value matches the ID as RANK
Id | Predict | Actual
1 | 11133 | 12835.5455
2 | 12312 | 12523.1818
3 | 13221 | 12343.3636
IMPORTANT REQUIREMENT
I need to set an offset much like the following syntax:
SELECT #rank := #rank + 1 AS Id , Mytable2.Actual FROM Mytable LIMIT 3 OFFSET 4);
PLEASE NOTE THE AVERAGE NUMBER ARE MADE UP IN EXAMPLES
you can join your existing query in the UPDATE statement
UPDATE Table2 T2
JOIN (
SELECT AVG(Actual) as AverageValue,
CEIL((#rank:=#rank+1)/3) AS rank
FROM Table1, (select #rank:=0) t
Group BY rank )T1
on T2.id = T1.rank
SET Actual = T1.AverageValue

MySQL sort differently in "batch"

Below is the standard query that sort weight in descending order.
SELECT * FROM article ORDER BY weight DESC LIMIT 0, 4
+-------------+--------+
| title | weight |
+-------------+--------+
| B | 2 |
| E | 2 |
| Y | 2 |
| A | 1 |
| C | 1 |
| D | 1 |
| F | 1 |
| G | 1 |
| X | 1 |
| Z | 1 |
| I | 1 |
| G | 1 |
+-------------+--------+
However, I wish to sort it differently as following based on the weight value.
+-------------+--------+
| title | weight |
+-------------+--------+
| B | 2 |
| A | 1 |
| C | 1 |
| D | 1 |
| E | 2 |
| F | 1 |
| G | 1 |
| X | 1 |
| Y | 2 |
| Z | 1 |
| I | 1 |
| G | 1 |
+-------------+--------+
The record with weight value 2 only selected once and sorted at the top. Then followed by records with weight value 1.
Using the approach like in these answers:
ROW_NUMBER() in MySQL
MSSQL Row_Number() over(order by) in MySql
you could obtain row numbers, separately, for rows with weight value of 2 and for rows with any other weight value, then use the resulting numbers for sorting.
Before I continue, please note that, even though the official documentation admits that
You might get the results you expect,
it also advises that,
As a general rule, you should never assign a value to a user variable and read the value within the same statement.
(By the same statement it means a statement other than SET.)
Below is a way of getting the expected order if the results are as they are expected.
SET #row2 = -1;
SET #row_other = -1;
SELECT
title, weight
FROM (
SELECT
title, weight,
#row2 := #row2 + CASE weight WHEN 2 THEN 1 ELSE 0 END AS weight2_row,
#row_other := #row_other + CASE weight WHEN 2 THEN 0 ELSE 1 END AS other_weight_row
FROM article
) s
ORDER BY
CASE weight WHEN 2 THEN #row2 ELSE #row_other DIV 3 END,
weight = 2 DESC
The specific order for non-Weight=2 rows is undefined, just like it is in your question.
Taking the same precautions that Andriy states, you can also use this:
SELECT title, weight
FROM
( SELECT title, weight
, #rownumber2 := #rownumber2 + 3 AS rn
FROM article
, ( SELECT #rownumber2 := 1 ) AS dummy
WHERE weight = 2
ORDER BY title --- optional, configure it for the
--- ordering of rows with weight = 2
UNION ALL
SELECT title, weight
, #rownumber1 := #rownumber1 + 1 AS rn
FROM article
, ( SELECT #rownumber1 := 3 ) AS dummy
WHERE weight = 1
ORDER BY title DESC --- optional, configure it for the
--- ordering of rows with weight = 1
) AS insaneOrdering
ORDER BY rn
, weight DESC ;