increase the value of only one element in a set - mysql

I have a column with sets of numbers like 21,8,0,345,... if I wanted to +1 to only one element in the set, for example element 3, would I be able to do that with a mysql query?
I know I can do this with php code (explode the string to an array then update it) but I'm afraid that with multiple updates simultaneously on the same row the values will be rewritten.
First query will set 21,8,1,345 and the second will rewrite it with 21,9,0,345
Replacing the element in question might also not work because some rows have multiple elements with the same value like 2,40,40,41

MySQL supports "SELECT ... FOR UPDATE" specifically for this situation to make sure the row isn't overwritten while you're processing the the row contents.
https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html
The above link even gives a very similar example (except for exploding the elements, increasing the one you want, and imploding them back together).
SELECT counter_field FROM child_codes FOR UPDATE;
UPDATE child_codes SET counter_field = counter_field + 1;
The better answer, as Tim suggested, is to store this data in a separate table, especially since you have a variable number of items for each row. I don't know how you currently know that you want to update, say, the 3rd item but I'll assume that's known.
Let's say these numbers are temperature readings from various sensors at a "location" and they gradually go up and down. Your main table is "locations" with with fields:
id (int, auto-increment), location_name (varchar), ...
You're then going to create a new table called "readings" with fields:
id (int, auto-increment), location_id (int), temperature (smallint)
The "id" from the first table is going to match up to the "location_id" of many records in "readings".
When you want to add a new temperature reading to a location (I'm assuming you'll have a $location_id and $new_reading variables in PHP):
INSERT INTO readings (location_id, temperature)
VALUES ( $location_id, $new_reading )
(NOTE: You should be properly sanitizing your inputs, using PDO, or other library, but that's out of scope for this answer or I'm going to be here all night. :-) )
Let's say you want to update the 3rd reading for this location, that would mean the "offset" is 2 and you only want to update 1 record so that's what "LIMIT 2, 1" means below. (I tried and failed to find a way to do this in only 1 query; UPDATE does not seem to support offsets, at least not in my version of MySQL.)
SELECT id FROM readings WHERE location_id = 1 ORDER BY id LIMIT 2, 1;
/* Let's say you stored the above result in $reading_id */
UPDATE readings SET temperature = temperature + 1 WHERE id = $reading_id;

Related

update sql table current row

Complete noob alert! I need to store a largish set of data fields (480) for each of many devices i am measuring. Each field is a Decimal(8,5). First, is this an unreasonably large table? I have no experience really, so if it is unmanageable, I might start thinking of an alternative storage method.
Right now, I am creating a new row using INSERT, then trying to put the 480 data values in to the new row using UPDATE (in a loop). Currently each UPDATE is overwriting the entire column. How do I specify only to modify the last row? For example, with a table ("magnitude") having columns "id", "field1", "field2",...:
sql UPDATE magnitude SET field1 = 3.14; this modifies the entire "field1" column.
Was trying to do something like:
sql UPDATE magnitude SET field1 = 3.14 WHERE id = MAX(id)
Obviously I am a complete noob. Just trying to get this one thing working and move on... Did look around a lot but can't find a solution. Any help appreciated.
Instead of inserting a row and then updating it with values, you should insert an entire row, with populated values, at once, using the insert command.
I.e.
insert into tTable (column1, column2, ..., column n) values (datum1, datum2, ..., datum n)
Your table's definition should have the ID column with property identity, which means that it will autofill it for you when you insert, i.e. you don't need to specify it.
Re: appropriateness of the schema, I think 480 is a large number of columns. However, this is a straightforward enough example that you could try it and determine empirically if your system is able to give you the performance you need.
If I were doing this myself, I would go for a different solution that has many rows instead of many columns:
Create a table tDevice (ID int, Name nvarchar)
Create a table tData (ID int, Device_ID int, Value decimal(8,5))
-- With a foreign key on Device_ID back to tDevice.ID
Then, to populate:
Insert all your devices in tDevice
Insert one row into tData for every Device / Data combination
-- i.e. 480 x n rows, n being the number of devices
Then, you can query the data you want like so:
select * from tData join tDevice on tDevice.ID = tData.Device_ID

Maintaining order of elements in MySQL database tables OR inserting new rows in specific positions for MySQL

I have a database table that maintains some information and is required to preserve order. Essentially if I have elements 1 through 5 listed and I want to add a new element, then it could be inserted anywhere in the existing row, either at the last, after 5, the beginning before 1 or somewhere in the middle such as after 3. Is there a way to do this using MySQL INSERT statements and specifying after which row we should insert the index?
I presume not. So my strategy to go about doing this is to create another column 'order_number' that basically records the order of the elements.
For instance, if the record table has primary key (record_id) and the order_number listed side by side, it would look like this:
record_id order_number
1 1
2 2
3 3
4 4
5 5
TO add a new element to this row after row 3, the resulting end table will look like this:
record_id order_number
1 1
2 2
3 3
**6** **4** <------ added row
4 **5** <-- changed order_number
5 **6** <-- changed order_number
In such a situation, I can clearly achieve the order that I want by simply selecting the data that i want and providing an Order By order_number asc clause.
However, as you can see, to do a simple Insert, it requires me to update every other row's order_number
that appears after it. The table is expected to have an extensive amount of rows to it (magnitude of 100,000) at minimum and simply updating every other row (hence locking the table) at every single insert operation is not at all feasible.
What is a better recommended strategy in this case ?
If the order_number is not to be shown but only used for ordering, I suggest you use a decimal datatype instead of integer. This way, when you have to insert a row "between" two existing rows, you can set as order_number, the average of the two existing order numbers.
In your example:
record_id order_number
1 1.0
2 2.0
3 3.0
**6** 3.5 <---- added row
4 4.0 <-- no change
5 5.0 <-- no change
There is a problem though that if you keep inserting numbers in the same area, some order numbers may result to be too close for the precision of the datatype you have chosen, close enough as to not be distinguished form one another.
To avoid this, your insert procedure will have to examine whether the two existing order number are too close. In that case, it could reassign some order numbers of other nearby rows, "stretching" the order numbers above and below to "make space" for a new value.
You could also have a "cleanup" procedure that runs periodically and does this "stretching" in the whole or large parts of the table.
I found this answer for a similar question: https://stackoverflow.com/a/6333717/1010050
In summary, it increments all of the record IDs below the one you will be adding, to maintain consistency. That still requires you to update all of the record IDs, so it isn't the most efficient. It does have the benefit, compared to your method, of maintaining the physical order in the database, rather than just a virtual order like you have.
Another way I can think of would be to record the child and parent record IDs for each record, rather than an order number, similar to a Doubly Linked List. Inserting an element in the middle would then only require updating two other records regardless of table size. This has the same disadvantage as your solution where the physical ordering would be wrong, so reading from the table in an ordered fashion would be more costly.
For example:
record_id parent_id child_id
0 NULL 1
1 0 2
2 1 NULL
When we insert a record after record_id = 1, the table becomes:
record_id parent_id child_id
0 NULL 1
1 0 3
2 3 NULL
3 1 2
Note how only the parent_id and child_id for IDs 1 and 2 had to change.
I think between these two solutions, the biggest thing to consider is what is your most common operation: reading out the values in order, or writing a new value in the middle somewhere. If it's reading, then updating the record IDs would be your best option in order to maintain the physical order of the database. If writing, then you can optimize for that by using the method I suggested similar to a doubly linked list, or your own order method.
Summary after question update:
Seeing that updating most of the records is not feasible, then the other answer I found is definitely not valid. The solution of treating it similar to a doubly linked list is still plausible, however.

Increment an SQL table key in one for stored data

I'm trying to build an script for a database migration and I have a doubt. I have a table which has lots of registers and an integer type key. This key is auto incremental and now beginning with the '1' index.
The problem is that this index has to be occupied by a default value in the new database. So I want to loop database rows to get each index incremented in one, to leave the first place in blank and insert my value into it. I have tried with this statement:
UPDATE `tapp`, (
SELECT #loop := id_App
FROM
tapp
) o
SET id_App = id_App + 1;
However, is trying to update each index starting from the beginning, so when it tries converting the first one to '2' it finds out the second one is already taken and can't make it.
It's important to make the increase in one, because it's a MyIsam database and I also have to update each foreign key one by one. I'm using MySQL.
Please give me a hand!
The easiest way is twofold: After inserting everything into the target table, you make a 2-pass update.
First, you check what the highest number is in the table, then increment it by one and remember it as topnumber.
Then, you update everything to incremental numbers, starting with topnumber. Finally, you update everything, starting with whatever your initial seed is and increment by one each record.
number data
1 "foo"
3 "bar"
10 "snafu"
topnumber becomes 11
After the first pass, the data looks like this:
number data
11 "foo"
12 "bar"
13 "snafu"
After the second pass (and assuming your initial number is 7), the data looks like this:
number data
7 "foo"
8 "bar"
9 "snafu"
UPDATE
Alternatively, instead of updating the numbers to incremental values, you could add the remembered top number to every initial value at the first pass (and so the above sample table would look like this after the first update:
number data
11 "foo"
13 "bar"
23 "snafu"
), and at the second pass, you would decrement all the numbers by the previously stored top number and increment them by 1, which, for our example, would result in the following:
number data
2 "foo"
4 "bar"
11 "snafu"
Using the names in your code snippet, the entire script might look something like this:
/* remember the top ID */
SET #max_id = (SELECT MAX(ID) FROM tapp);
/* increment by the top ID */
UPDATE tapp SET id_App = id_App + #max_id;
/* decrement by the top ID and increment by 1 */
UPDATE tapp SET id_App = id_App - #max_id + 1;

How do I reset sequence numbers to become consecutive?

I've got a mysql table where each row has its own sequence number in a "sequence" column. However, when a row gets deleted, it leaves a gap. So...
1
2
3
4
...becomes...
1
2
4
Is there a neat way to "reset" the sequencing, so it becomes consecutive again in one SQL query?
Incidentally, I'm sure there is a technical term for this process. Anyone?
UPDATED: The "sequence" column is not a primary key. It is only used for determining the order that records are displayed within the app.
If the field is your primary key...
...then, as stated elsewhere on this question, you shouldn't be changing IDs. The IDs are already unique and you neither need nor want to re-use them.
Now, that said...
Otherwise...
It's quite possible that you have a different field (that is, as well as the PK) for some application-defined ordering. As long as this ordering isn't inherent in some other field (e.g. if it's user-defined), then there is nothing wrong with this.
You could recreate the table using a (temporary) auto_increment field and then remove the auto_increment afterwards.
I'd be tempted to UPDATE in ascending order and apply an incrementing variable.
SET #i = 0;
UPDATE `table`
SET `myOrderCol` = #i:=#i+1
ORDER BY `myOrderCol` ASC;
(Query not tested.)
It does seem quite wasteful to do this every time you delete items, but unfortunately with this manual ordering approach there's not a whole lot you can do about that if you want to maintain the integrity of the column.
You could possibly reduce the load, such that after deleting the entry with myOrderCol equal to, say, 5:
SET #i = 5;
UPDATE `table`
SET `myOrderCol` = #i:=#i+1
WHERE `myOrderCol` > 5
ORDER BY `myOrderCol` ASC;
(Query not tested.)
This will "shuffle" all the following values down by one.
I'd say don't bother. Reassigning sequential values is a relatively expensive operation and if the column value is for ordering purpose only there is no good reason to do that. The only concern you might have is if for example your column is UNSIGNED INT and you suspect that in the lifetime of your application you might have more than 4,294,967,296 rows (including deleted rows) and go out of range, even if that is your concern you can do the reassigning as a one time task 10 years later when that happens.
This is a question that often I read here and in other forums. As already written by zerkms this is a false problem. Moreover if your table is related with other ones you'll lose relations.
Just for learning purpose a simple way is to store your data in a temporary table, truncate the original one (this reset auto_increment) and than repopulate it.
Silly example:
create table seq (
id int not null auto_increment primary key,
col char(1)
) engine = myisam;
insert into seq (col) values ('a'),('b'),('c'),('d');
delete from seq where id = 3;
create temporary table tmp select col from seq order by id;
truncate seq;
insert into seq (col) select * from tmp;
but it's totally useless. ;)
If this is your PK then you shouldn't change it. PKs should be (mostly) unchanging columns. If you were to change them then not only would you need to change it in that table but also in any foreign keys where is exists.
If you do need a sequential sequence then ask yourself why. In a table there is no inherent or guaranteed order (even in the PK, although it may turn out that way because of how most RDBMSs store and retrieve the data). That's why we have the ORDER BY clause in SQL. If you want to be able to generate sequential numbers based on something else (time added into the database, etc.) then consider generating that either in your query or with your front end.
Assuming that this is an ID field, you can do this when you insert:
INSERT INTO yourTable (ID)
SELECT MIN(ID)
FROM yourTable
WHERE ID > 1
As others have mentioned I don't recommend doing this. It will hold a table lock while the next ID is evaluated.

Index counter shared by multiple tables in mysql

I have two tables, each one has a primary ID column as key. I want the two tables to share one increasing key counter.
For example, when the two tables are empty, and counter = 1. When record A is about to be inserted to table 1, its ID will be 1 and the counter will be increased to 2. When record B is about to be inserted to table 2, its ID will be 2 and the counter will be increased to 3. When record C is about to be inserted to table 1 again, its ID will be 3 and so on.
I am using PHP as the outside language. Now I have two options:
Keep the counter in the database as a single-row-single-column table. But every time I add things to table A or B, I need to update this counter table.
I can keep the counter as a global variable in PHP. But then I need to initialize the counter from the maximum key of the two tables at the start of apache, which I have no idea how to do.
Any suggestion for this?
The background is, I want to display a mix of records from the two tables in either ASC or DESC order of the creation time of the records. Furthermore, the records will be displayed in page-style, say, 50 records per page. Records are only added to the database rather than being removed. Following my above implementation, I can just perform a "select ... where key between 1 and 50" from two tables and merge the select datasets together, sort the 50 records according to IDs and display them.
Is there any other idea of implementing this requirement?
Thank you very much
Well, you will gain next to nothing with this setup; if you just keep the datetime of the insert you can easily do
SELECT * FROM
(
SELECT columnA, columnB, inserttime
FROM table1
UNION ALL
SELECT columnA, columnB, inserttime
FROM table2
)
ORDER BY inserttime
LIMIT 1, 50
And it will perform decently.
Alternatively (if chasing last drop of preformance), if you are merging the results it can be an indicator to merge the tables (why have two tables anyway if you are merging the results).
Or do it as SQL subclass (then you can have one table maintain IDs and other common attributes, and the other two reference the common ID sequence as foreign key).
if you need creatin time wont it be easier to add a timestamp field to your db and sort them according to that field?
i believe using ids as a refrence of creation is bad practice.
If you really must do this, there is a way. Create a one-row, one-column table to hold the last-used row number, and set it to zero. On each of your two data tables, create an AFTER INSERT trigger to read that table, increment it, and set the newly-inserted row number to that value. I can't remember the exact syntax because I haven't created a trigger for years; see here http://dev.mysql.com/doc/refman/5.0/en/triggers.html