Given the following table, how can I sequentially reorder position from 1 to N using a single query after one or more rows have been deleted and still preserve the order of position?
+---------+----------+-----+
| id (pk) | position | fk |
+---------+----------+-----+
| 4 | 1 | 123 |
| 2 | 2 | 123 |
| 18 | 3 | 123 |
| 5 | 4 | 123 |
| 3 | 5 | 123 |
+---------+----------+-----+
For instance, if position=1 (id=4) was deleted, the desired final records are:
+---------+----------+-----+
| id (pk) | position | fk |
+---------+----------+-----+
| 2 | 1 | 123 |
| 18 | 2 | 123 |
| 5 | 3 | 123 |
| 3 | 4 | 123 |
+---------+----------+-----+
and if position=3 (id=18) was deleted, the desired final records are:
+---------+----------+-----+
| id (pk) | position | fk |
+---------+----------+-----+
| 4 | 1 | 123 |
| 2 | 2 | 123 |
| 5 | 3 | 123 |
| 3 | 4 | 123 |
+---------+----------+-----+
I can do something like the following if only row was deleted but not for multiple rows.
DELETE FROM mytable WHERE fk=123 AND position = 4;
UPDATE mytable SET position=position-1 WHERE fk=123 AND position > 4;
User-defined variables to the rescue if you're not already using MySQL 8, which provides window functions like ROW_NUMBER():
UPDATE t
JOIN (
SELECT
t.*
, #n := #n + 1 as n
FROM t
, (SELECT #n := 0) var_init
ORDER BY position
) sq ON t.id = sq.id
SET t.position = sq.n;
see it working live in an sqlfiddle
BONUS:
It gets slightly more complicated, when you have multiple groups.
For example, for sample data like this
| id | position | fk |
|-----|----------|-----|
| 4 | 1 | 123 |
| 2 | 2 | 123 |
| 5 | 4 | 123 |
| 3 | 5 | 123 |
| 40 | 1 | 234 |
| 20 | 2 | 234 |
| 180 | 3 | 234 |
| 30 | 5 | 234 |
the query would be
UPDATE t
JOIN (
SELECT
t.*
, #n := if(#prev_fk != fk, 1, #n + 1) as n
, #prev_fk := fk
FROM t
, (SELECT #n := 0, #prev_fk := NULL) var_init
ORDER BY fk, position
) sq ON t.id = sq.id
SET t.position = sq.n;
Here you just save the current fk in another variable. When the next row is processed, the variable still holds the value of the "previous row". Then you reset the #n variable, when the value changes.
see it working live in an sqlfiddle
UPDATE:
In MySQL 8 you can use the window function row_number() like this:
update t join (
select t.*, row_number() over (partition by fk order by position) as new_pos
from t
) sq using (id) set t.position = sq.new_pos;
You can use update and the ROW_NUMBER() function. If you order by position you it should be ok.
UPDATE [1]
SET POSITION = [2].RN
FROM t [1]
JOIN (
SELECT
t.ID
, ROW_NUMBER() OVER (ORDER BY POSITION DESC) AS RN
FROM t
) [2]
ON [1].id = [2].id
As people have mentioned this is not applicable for MySql. Sorry for the missinformation as i didnt saw the tag.
Related
I have a table of sport results. When using following code, the table get places according to the times.
SET #pos := 0;
UPDATE table SET Place = ( SELECT #pos := #pos + 1 ) ORDER BY Time ASC;
In case of same times (like rows 1,3 and 4,5), it updates it according to the ID-s, so the result is following:
ID | Time | Place
1 | 00:12:14 | 1
2 | 00:12:18 | 3
3 | 00:12:14 | 2
4 | 00:12:25 | 4
5 | 00:12:25 | 5
How could I update the table so, that if there is multiple rows of same time, all rows would get the best place (like in following table)?
ID | Time | Place
1 | 00:12:14 | 1
2 | 00:12:18 | 3
3 | 00:12:14 | 1
4 | 00:12:25 | 4
5 | 00:12:25 | 4
You can do it with a join of the table to a query returning for each row the number of rows less than its Time:
update tablename t inner join (
select t.id, (
select count(*) counter from tablename
where time < t.time
) counter
from tablename t
) c on c.id = t.id
set t.place = c.counter + 1;
See the demo.
Results:
| ID | Time | Place |
| --- | -------- | ----- |
| 1 | 00:12:14 | 1 |
| 2 | 00:12:18 | 3 |
| 3 | 00:12:14 | 1 |
| 4 | 00:12:25 | 4 |
| 5 | 00:12:25 | 4 |
I'm trying to make a query setting rank column by First and Second column. Like Rank over Partition which doesn't exist MySQL
For example,
From
+----+-------+--------+------+
| id | First | Second | Rank |
+----+-------+--------+------+
| 1 | a | 10 | |
| 2 | a | 9 | |
| 3 | b | 10 | |
| 4 | b | 7 | |
| 5 | a | 1 | |
| 6 | b | 1 | |
+----+-------+--------+------+
To
+----+-------+--------+------+
| id | First | Second | Rank |
+----+-------+--------+------+
| 1 | a | 10 | 3 |
| 2 | a | 9 | 2 |
| 3 | b | 10 | 3 |
| 4 | b | 7 | 2 |
| 5 | a | 1 | 1 |
| 6 | b | 1 | 1 |
+----+-------+--------+------+
The Rank doesn't continue. It starts from 1 again when it reaches the last value of 'a' of 'First' column.
And it's gotta be SET not SELECT.
I wouldn't mind using SELECT but my point is I'm not trying to retrieve data from Database but setting values.
Cheers in advance mates.
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,first CHAR(1) NOT NULL
,second INT NOT NULL
);
INSERT INTO my_table VALUES
(1,'a',10),
(2,'a',9),
(3,'b',10),
(4,'b',7),
(5,'a',1),
(6,'b',1);
SELECT id
, first
, second
, rank
FROM
( SELECT x.*
, CASE WHEN #prev = first THEN #i:=#i+1 ELSE #i:=1 END rank
, #prev:=first
FROM my_table x
, (SELECT #prev:=null,#i:=0) vars
ORDER
BY first
, second
, id
) a
ORDER
BY id;
+----+-------+--------+------+
| id | first | second | rank |
+----+-------+--------+------+
| 1 | a | 10 | 3 |
| 2 | a | 9 | 2 |
| 3 | b | 10 | 3 |
| 4 | b | 7 | 2 |
| 5 | a | 1 | 1 |
| 6 | b | 1 | 1 |
+----+-------+--------+------+
6 rows in set (0.00 sec)
Came up with a solution which I was looking for.
I'm not sure if these queries are completely safe but so far no harms.
SET #rank = 0, #First = ''
UPDATE 'Table' SET
rank = IF(#First = First, #rank:= #rank +1, #rank := 1 AND #First := First)
ORDER BY First ASC, Second;
One method is a correlated subquery. For rank() you can do:
select t.*,
(select count(*) + 1
from t t2
where t2.first = t.first and t2.second < t.second
) as rank
from t;
Ranks are tricky to handle with variables (dense_rank() and row_number() are simpler).
EDIT:
This is easy to turn into an update:
update t join
(select t.*,
(select count(*) + 1
from t t2
where t2.first = t.first and t2.second < t.second
) as new_rank
from t
) tt
on t.id = tt.id
set t.rank = tt.new_rank;
I'm trying to limit the number of rows per field value of a given query. I've found this answered question:
here
As in the first answer of the link, I've created the following table:
create table mytab (
id int not null auto_increment primary key,
first_column int,
second_column int
) engine = myisam;
Inserted this data:
insert into mytab (first_column,second_column) values
(1,1),
(1,4),
(2,10),
(3,4),
(1,4),
(2,5),
(1,6);
And finally run this query
select
id,
first_column,
second_column,
row_num
from
(select
*,
#num := if(#first_column = first_column, #num + 1, 1) as row_num,
#first_column:=first_column as c
from mytab
order by first_column,id) as t,
(select #first_column:='',#num:=0) as r;
But instead of getting this result, where the row_num increases whenever first_column is repeated,
+----+--------------+---------------+---------+
| id | first_column | second_column | row_num |
+----+--------------+---------------+---------+
| 1 | 1 | 1 | 1 |
| 2 | 1 | 4 | 2 |
| 5 | 1 | 4 | 3 |
| 7 | 1 | 6 | 4 |
| 3 | 2 | 10 | 1 |
| 6 | 2 | 5 | 2 |
| 4 | 3 | 4 | 1 |
+----+--------------+---------------+---------+
I get this result:
+----+--------------+---------------+---------+
| id | first_column | second_column | row_num |
+----+--------------+---------------+---------+
| 1 | 1 | 1 | 1 |
| 2 | 1 | 4 | 1 |
| 5 | 1 | 4 | 1 |
| 7 | 1 | 6 | 1 |
| 3 | 2 | 10 | 1 |
| 6 | 2 | 5 | 1 |
| 4 | 3 | 4 | 1 |
+----+--------------+---------------+---------+
I literally copied the code from the link. I checked in SQL Fiddle and code works fine. I'm using XAMPP. Could that be the reason? If it is, is there any workaround to get something like the above working?
I'd really appreciate some help. Thanks in advance.
The variable assignment has to be in the sub-query.
select
id,
first_column,
second_column,
row_num
from
(select
m.*,
#num := if(#first_column = first_column, #num + 1, 1) as row_num,
#first_column:=first_column as c
from mytab m
cross join (select #first_column:='',#num:=0) r --this was in the outer query previously
order by first_column,id
) t
I have added a new column 'sums' to my table and I am trying to sum up values from 'vals' column then update 'sums' in the same table, according to the algorithm shown below in the table.
I could write a few loops in PHP but I don't think it would be nice.
Any clue how to write it nicely?
--------------------------------------------------
| id | sets | parts | vals | sums |
|-------|---------|---------|----------|---------|
| 1 | 1 | 1 | 2 | 2 |
|-------|---------|---------|----------|---------|
| 2 | 1 | 2 | 3 | 2+3=5 |
|-------|---------|---------|----------|---------|
| 3 | 1 | 3 | 5 |2+3+5=10 |
|-------|---------|---------|----------|---------|
| 4 | 2 | 1 | 4 | 4 |
|-------|---------|---------|----------|---------|
| 5 | 2 | 2 | 1 | 4+1=5 |
|-------|---------|---------|----------|---------|
| 6 | 2 | 3 | 2 | 4+1+2=7 |
|-------|---------|---------|----------|---------|
| 7 | 3 | 1 | 6 | 6 |
|-------|---------|---------|----------|---------|
This should return the value you want for sums:
SELECT t.id
, IF(t.sets=#prev_sets,#i:=#i+t.vals,#i:=t.vals) AS `sums`
, #prev_sets := t.sets AS prev_sets
FROM mytable t
JOIN (SELECT #prev_sets := NULL, #i := 0 ) i
ORDER BY t.sets, t.parts
You can use this as an inline view in an update statement
UPDATE ( SELECT t.id
, IF(t.sets=#prev_sets,#i:=#i+t.vals,#i:=t.vals) AS `sums`
, #prev_sets := t.sets AS prev_sets
FROM mytable t
JOIN (SELECT #prev_sets := NULL, #i := 0 ) i
ORDER BY t.sets, t.parts
) s
JOIN mytable r
ON r.id = s.id
SET r.sums = s.sums
From the example data, it looks like you want the "groupings" on sets, and you want to order on parts within each group.
I have got tables baskets, fruits and basket_fruits (join-table: basket_id-fruit_id).
How can I return a position of each fruit in basket so I will get something like
+---------------------------------------+
| basket_id | fruit_id | fruit_position |
|---------------------------------------|
| 1 | 2 | 1 |
| 1 | 5 | 2 |
+---------------------------------------+
Fruit position is just a number of a row in a returned joined table (it is not a column).
Schema:
baskets: id, title
fruits: id, title
basket_fruits: id, basket_id, fruit_id
MySQL does not support ranging functions so you'll have to use subqueries:
SELECT basket_id, fruit_id,
(
SELECT COUNT(*)
FROM basket_fruit bfi
WHERE bfi.basket_id = bf.basket_id
AND bfi.fruit_id <= bf.fruit_id
) AS fruit_position
FROM basket_fruit bf
WHERE basket_id = 1
or use session variables (faster but relies on implementation details which are not documented and may break in future releases):
SET #rn = 0;
SELECT basket_id, fruit_id, #rn := #rn + 1 AS fruit_position
FROM basket_fruit bf
WHERE basket_id = 1
ORDER BY
fruit_id
I do not see any column in basket_fruits table that I would consider weightable. If you simply want to add some numbers to the data in that table, you could try this (this allows each basket to have its own weights counting from 1):
SET #current_group = NULL;
SET #current_count = NULL;
SELECT
id, basket_id, fruit_id,
CASE
WHEN #current_group = basket_id THEN #current_count := #current_count + 1
WHEN #current_group := basket_id THEN #current_count := 1
END AS fruit_position
FROM basket_fruits
ORDER BY basket_id, id
Sample input:
+----+-----------+----------+
| id | basket_id | fruit_id |
+----+-----------+----------+
| 2 | 2 | 5 |
| 6 | 2 | 1 |
| 9 | 1 | 2 |
| 15 | 2 | 3 |
| 17 | 1 | 5 |
+----+-----------+----------+
Sample output:
+----+-----------+----------+----------------+
| id | basket_id | fruit_id | fruit_position |
+----+-----------+----------+----------------+
| 9 | 1 | 2 | 1 |
| 17 | 1 | 5 | 2 |
| 2 | 2 | 5 | 1 |
| 6 | 2 | 1 | 2 |
| 15 | 2 | 3 | 3 |
+----+-----------+----------+----------------+
SQL provides no guarantees on the order of the returned rows. Therefore fruit_position is likely to be different when queried from time to time. Most likely this will happen due to DML activity on your table.
If you really need some ordering, you should pick:
Use existing columns as ordering key, like fruit name (if exists)
Create a special field, like seq_nr that will specify ordering for your fruits.