Here is my table
`id` int(11) NOT NULL,
`notifyroles` varchar(50) NULL,
PRIMARY KEY (`id`)
ENGINE=MyISAM DEFAULT CHARSET=utf8
I use it to store a single set of dynamic values of an array that is imploded to string such as item1,item2,item3 and when I pull the data from the db I will explode those values again.
When I initialize my software I insert row id 1 and then leave the notifyroles element as NULL until I used it.
It will and should never have any other rows but row 1 and so I chose not to use the auto increment feature. I never use INSERT I always just use UPDATE for id 1.
Since i dont want to have to write a bunch of code to check for more rows and truncate it and reset it if there is and all of that stuff my question is:
Is there a way to lock the table so that it cannot have more than 1 row? And if someone tried to INSERT another row it would fail.
ps. I am hoping that with the evolution of MySQL that maybe after all this time there is such a way.
Simplest is to manage the rights so the user your software uses has no insert rights but does have update rights on that table.
See: http://dev.mysql.com/doc/refman/5.7/en/grant.html
There's not really a way to lock a table, but you can take advantage of MySQL triggers. As the name suggest, they are activated immediately at the time the specified action is performed, in this case, an insert. Maybe try this:
CREATE TRIGGER locktable
AFTER INSERT ON mytable
FOR EACH ROW
BEGIN
IF (NEW.id != --id of the row you want protected--) THEN
DELETE FROM mytable WHERE id = NEW.id;
END IF;
END;
Why not BEFORE INSERT? some strategies suggest causing the query to fail, but I'm really not comfortable with that approach.
I hope it helps.
Related
I'm using MySQL's AUTO_INCREMENT field and InnoDB to support transactions. I noticed when I rollback the transaction, the AUTO_INCREMENT field is not rollbacked? I found out that it was designed this way but are there any workarounds to this?
It can't work that way. Consider:
program one, you open a transaction and insert into a table FOO which has an autoinc primary key (arbitrarily, we say it gets 557 for its key value).
Program two starts, it opens a transaction and inserts into table FOO getting 558.
Program two inserts into table BAR which has a column which is a foreign key to FOO. So now the 558 is located in both FOO and BAR.
Program two now commits.
Program three starts and generates a report from table FOO. The 558 record is printed.
After that, program one rolls back.
How does the database reclaim the 557 value? Does it go into FOO and decrement all the other primary keys greater than 557? How does it fix BAR? How does it erase the 558 printed on the report program three output?
Oracle's sequence numbers are also independent of transactions for the same reason.
If you can solve this problem in constant time, I'm sure you can make a lot of money in the database field.
Now, if you have a requirement that your auto increment field never have gaps (for auditing purposes, say). Then you cannot rollback your transactions. Instead you need to have a status flag on your records. On first insert, the record's status is "Incomplete" then you start the transaction, do your work and update the status to "compete" (or whatever you need). Then when you commit, the record is live. If the transaction rollsback, the incomplete record is still there for auditing. This will cause you many other headaches but is one way to deal with audit trails.
Let me point out something very important:
You should never depend on the numeric features of autogenerated keys.
That is, other than comparing them for equality (=) or unequality (<>), you should not do anything else. No relational operators (<, >), no sorting by indexes, etc. If you need to sort by "date added", have a "date added" column.
Treat them as apples and oranges: Does it make sense to ask if an apple is the same as an orange? Yes. Does it make sense to ask if an apple is larger than an orange? No. (Actually, it does, but you get my point.)
If you stick to this rule, gaps in the continuity of autogenerated indexes will not cause problems.
I had a client needed the ID to rollback on a table of invoices, where the order must be consecutive
My solution in MySQL was to remove the AUTO-INCREMENT and pull the latest Id from the table, add one (+1) and then insert it manually.
If the table is named "TableA" and the Auto-increment column is "Id"
INSERT INTO TableA (Id, Col2, Col3, Col4, ...)
VALUES (
(SELECT Id FROM TableA t ORDER BY t.Id DESC LIMIT 1)+1,
Col2_Val, Col3_Val, Col4_Val, ...)
Why do you care if it is rolled back? AUTO_INCREMENT key fields are not supposed to have any meaning so you really shouldn't care what value is used.
If you have information you're trying to preserve, perhaps another non-key column is needed.
I do not know of any way to do that. According to the MySQL Documentation, this is expected behavior and will happen with all innodb_autoinc_lock_mode lock modes. The specific text is:
In all lock modes (0, 1, and 2), if a
transaction that generated
auto-increment values rolls back,
those auto-increment values are
“lost.” Once a value is generated for
an auto-increment column, it cannot be
rolled back, whether or not the
“INSERT-like” statement is completed,
and whether or not the containing
transaction is rolled back. Such lost
values are not reused. Thus, there may
be gaps in the values stored in an
AUTO_INCREMENT column of a table.
If you set auto_increment to 1 after a rollback or deletion, on the next insert, MySQL will see that 1 is already used and will instead get the MAX() value and add 1 to it.
This will ensure that if the row with the last value is deleted (or the insert is rolled back), it will be reused.
To set the auto_increment to 1, do something like this:
ALTER TABLE tbl auto_increment = 1
This is not as efficient as simply continuing on with the next number because MAX() can be expensive, but if you delete/rollback infrequently and are obsessed with reusing the highest value, then this is a realistic approach.
Be aware that this does not prevent gaps from records deleted in the middle or if another insert should occur prior to you setting auto_increment back to 1.
INSERT INTO prueba(id)
VALUES (
(SELECT IFNULL( MAX( id ) , 0 )+1 FROM prueba target))
If the table doesn't contain values or zero rows
add target for error mysql type update FROM on SELECT
If you need to have the ids assigned in numerical order with no gaps, then you can't use an autoincrement column. You'll need to define a standard integer column and use a stored procedure that calculates the next number in the insert sequence and inserts the record within a transaction. If the insert fails, then the next time the procedure is called it will recalculate the next id.
Having said that, it is a bad idea to rely on ids being in some particular order with no gaps. If you need to preserve ordering, you should probably timestamp the row on insert (and potentially on update).
Concrete answer to this specific dilemma (which I also had) is the following:
1) Create a table that holds different counters for different documents (invoices, receipts, RMA's, etc..); Insert a record for each of your documents and add the initial counter to 0.
2) Before creating a new document, do the following (for invoices, for example):
UPDATE document_counters SET counter = LAST_INSERT_ID(counter + 1) where type = 'invoice'
3) Get the last value that you just updated to, like so:
SELECT LAST_INSERT_ID()
or just use your PHP (or whatever) mysql_insert_id() function to get the same thing
4) Insert your new record along with the primary ID that you just got back from the DB. This will override the current auto increment index, and make sure you have no ID gaps between you records.
This whole thing needs to be wrapped inside a transaction, of course. The beauty of this method is that, when you rollback a transaction, your UPDATE statement from Step 2 will be rolled back, and the counter will not change anymore. Other concurrent transactions will block until the first transaction is either committed or rolled back so they will not have access to either the old counter OR a new one, until all other transactions are finished first.
SOLUTION:
Let's use 'tbl_test' as an example table, and suppose the field 'Id' has AUTO_INCREMENT attribute
CREATE TABLE tbl_test (
Id int NOT NULL AUTO_INCREMENT ,
Name varchar(255) NULL ,
PRIMARY KEY (`Id`)
)
;
Let's suppose that table has houndred or thousand rows already inserted and you don't want to use AUTO_INCREMENT anymore; because when you rollback a transaction the field 'Id' is always adding +1 to AUTO_INCREMENT value.
So to avoid that you might make this:
Let's remove AUTO_INCREMENT value from column 'Id' (this won't delete your inserted rows):
ALTER TABLE tbl_test MODIFY COLUMN Id int(11) NOT NULL FIRST;
Finally, we create a BEFORE INSERT Trigger to generate an 'Id' value automatically. But using this way won't affect your Id value even if you rollback any transaction.
CREATE TRIGGER trg_tbl_test_1
BEFORE INSERT ON tbl_test
FOR EACH ROW
BEGIN
SET NEW.Id= COALESCE((SELECT MAX(Id) FROM tbl_test),0) + 1;
END;
That's it! You're done!
You're welcome.
$masterConn = mysql_connect("localhost", "root", '');
mysql_select_db("sample", $masterConn);
for($i=1; $i<=10; $i++) {
mysql_query("START TRANSACTION",$masterConn);
$qry_insert = "INSERT INTO `customer` (id, `a`, `b`) VALUES (NULL, '$i', 'a')";
mysql_query($qry_insert,$masterConn);
if($i%2==1) mysql_query("COMMIT",$masterConn);
else mysql_query("ROLLBACK",$masterConn);
mysql_query("ALTER TABLE customer auto_increment = 1",$masterConn);
}
echo "Done";
Okay, I am currently developing a website that is supposed to have a searchable database of pool pumps. As part of this system, to prevent people from reading hidden data, I had the primary key of the pool pump stock randomly generated. Here's the code I wrote for the MariaDB backend:
DELIMITER $$
CREATE TRIGGER random_pump_id BEFORE INSERT ON tbl_stock FOR EACH ROW
BEGIN
DECLARE temp_id MEDIUMINT;
REPEAT
SET temp_id = FLOOR(RAND() * 16777216);
UNTIL (SELECT COUNT(*) FROM tbl_stock WHERE pump_id = temp_id) <= 0 END REPEAT;
SET NEW.pump_id = temp_id;
END
$$
But now I've run into a dilemma. Every time I want to insert a row, I need a way to retrieve the primary key I just generated. I know if I used AUTO_INCREMENT I could use the LAST_INSERT_ID function, or lastInsertId in PDO. But since I am not using AUTO_INCREMENT, and instead am using a separate trigger, these functions will only return a 0. I know I can do it in PostgreSQL by using the RETURNING clause, but I can't find a way to accomplish this in MariaDB.
Does anyone know of any solution? Perhaps some obscure trigger I don't know about? Please?
Presumably you have some other way to locate the record? Probably via a UNIQUE key? If so, the fetch the row after adding the random id.
Don't use a trigger, instead write application code. (Triggers can't solve all problems.)
I have a MySQL table for storing details of uploaded files, like this:
CREATE TABLE files (
file_id bigint not null primary key auto_increment,
file_name text,
file_path text,
file_extension varchar(15),
file_link text);
The actual name of the file on the disk is set to the file_id generated by the table when I insert the record - so although I can record all sorts of details, the database doesn't actually know what the file is called at this point.
Sure it's easy to just run another UPDATE statement shortly after to set the file_link column, but I wondered if there was a way to do something a bit more elegant here?
What I would like to do is have the value of file_link be automatically set on INSERT to this: CONCAT(file_path, file_id, file_extension) without needing to run another UPDATE statement immediately after.
Does anyone know how I can do this - and if it's a bad idea can you explain why?
P.S I'm using PHP to run these statements, if that makes a difference.
You could do this with the following trigger:
DELIMITER $$
CREATE TRIGGER makelink BEFORE INSERT ON files
FOR EACH ROW
BEGIN
SET NEW.file_link = CONCAT(NEW.file_path, NEW.file_id, NEW.file_extension);
END;
$$
DELIMITER ;
...although I have to say, I personally would go with creating this with the SELECT statement instead of having the database do things like this on insert - sure it's slightly more efficient to store static data, but it makes the database schema less transparent.
Typically you don't want to store derived data. Do some reading on db normalization when you have some spare cycles.
I have a mysql database and some php that allows you to create an entry in the database, update an entry, and view the entries as a web page or xml. What I want to do is add a function to move an entry in the database up or down by one row, or, send to the top of the database or bottom.
I've seen some online comments about doing this type of thing that suggested doing a dynamic sort when displaying the page, but I'm looking for a persistent resort. I've seen one approach suggested that would be to have a separate "sort" field in the database that is agnostic of the actual database sort key, but I'm not sure why that would be better than actually re-ordering the database
Here is a dump of the table structure:
SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
--
-- Database: `hlnManager`
--
-- --------------------------------------------------------
--
-- Table structure for table `hln_stations`
--
CREATE TABLE IF NOT EXISTS `hln_stations` (
`id` int(6) NOT NULL auto_increment,
`station_title` varchar(60) NOT NULL default '',
`station_display_name` varchar(60) NOT NULL default '',
`station_subtitle` varchar(60) NOT NULL default '',
`station_detailed_description` text NOT NULL,
`stream_url_or_playlist_url` text NOT NULL,
`link_type` varchar(25) NOT NULL default '',
`small_thumbnail_graphic_url` text NOT NULL,
`large_thumbnail_graphic_url` text NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=21 ;
Not sure what you mean by "Reordering" the database... SQL Databases typically do not make any guarantees on what order (if any) they will return records in short of an ORDER BY clause.
You need a "SortOrder" type column. I suggest you make it an int with a unique key.
You need a way to update this "SortOrder" column via the UI
Easy to program, easy to use: Implement a simple drag+drop interface in HTML using jQuery or whatever javascript library works for you. In the on-complete method (or in response to a save button), trigger an ajax call which will simply send an array of ids in the correct order. On the database side, loop over it and update the SortOrder accordingly, starting at 1, then 2, etc...
Harder to program, hard to use: Implement a classical move-up and move-down buttons. When clicked, send the id and action (eg, up, down) to the server. There are several strategies to handle this update, but I will outline a couple:
Assuming the user clicked "move up", you can swap IDs with the previous record.
Find the previous record: SELECT id FROM hln_stations WHERE SortOrder < (SELECT SortOrder FROM hln_stations WHERE id = ...) ORDER BY SortOrder DESC LIMIT 1
Run two update statements, swapping the SortOrder. Reverse for moving down. Add special code to detect top or bottom.
etc...
There are other ways, but for a web interface, I suggest you do Drag+Drop, as the users will love it.
Databases are not "stored" in any order. They are stored in whatever way is convenient for the storage subsystem. If you delete a record, a new record may use the space of the old record "inserting" itself into the database. While it may seem like the database always returns records in a particular order, you can't rely on it.
The ONLY way to assure a sort order is to have a field to sort on.
Dont know where you can find example to find example. but you can look the following code it is very basic:
Let id is your primary key and there is a column sort_order. You want to store primary keys in the following order: 5,4,3,6,8,7,9,10,2,1.
then you store them in an array:
$my_sorted = array(5,4,3,6,8,7,9,10,2,1);
then you update your table:
update `mytable` set `sort_order` = (index of $my_sorted) WHERE `id`=(array value of that index).
Instead of doing many queries you can do it in one query like:
$query = "UPDATE `mytable` SET sort_order= CASE id ";
foreach($my_sorted as $key=>$val){
$query .= " WHEN '$val' THEN $key ";
}
$query .="END";
Then you run $query in mysql.
After updating table you can select from mytable with order by sort_order asc or desc.
hope this helps.
"re-ordering" the database would require two records swapping primary keys, or most likely they would need to have all data except the primary keys be swapped. this would most likely be undesireable, since the primary key should be the one way you can consistently refer to a particular record.
The separate order field would be the way to go. Just make sure that you put an index on the order field so that things stay speedy.
There is no way to find out in which order databases stores data. When we query to database, we specify the field name that we want our data to be sorted by.
In your case, I would add a new column: sequence int(10). and write php function to change/update sequence number. when i will use select query, I will order by sequence number.
I am working with an old MySQL table, which serves as a log of sorts. It looks like
CREATE TABLE `queries` (
`Email` char(32) NOT NULL DEFAULT '',
`Query` blob,
`NumRecords` int(5) unsigned DEFAULT NULL,
`Date` date DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
Now, I need to be able to UPDATE the records in this table (don't ask why, I don't know). Normally, I would just do
UPDATE table SET ... WHERE unique_column = value
But in this case, I don't have a unique column to work from.
Is there a workaround for this, or am I just going to have to push to put in a nice, standard INT NOT NULL AUTO_INCREMENT?
UPDATE queries
SET ...
WHERE Email = value1
AND Query = value2
AND NumRecords = value3
AND Date = value4
LIMIT 1;
A unique identifier is the only reliable way of doing this. Just add an auto_increment column and be done with it.
For exhaustive info including some workaround approaches (none of them perfect though!) check this question, where the OP had a table without a unique identifier and no way to change it.
Update: As Doug Currie points out, this is not entirely true: A unique ID is not necessary as such here. I still strongly recommend the practice of always using one. If two users decide to update two different rows that are exact duplicates of each other at the exact same time (e.g. by selecting a row in a GUI), there could be collisions because it's not possible to define which row is targeted by which operation. It's a microscopic possibility and in the case at hand probably totally negligeable, but it's not good design.
There are two different issues here. First, is de-duping the table. That is an entirely different question and solution which might involve adding a auto_increment column. However, if you are not going to de-dup the table, then by definition, two rows with the same data represent the same instance of information and both ought to be updated if they match the filtering criteria. So, either add a unique key, de-dup the table (in which case uniqueness is based on the combination of all columns) or update all matching rows.
In case you didn't know this, it will affect performance, but you don't need to use a primary key in your WHERE clause when updating a record. You can single out a row by specifying the existing values:
UPDATE queries
SET Query = 'whatever'
WHERE Email = 'whatever#whatever.com' AND
Query = 'whatever' AND
NumRecords = 42 AND
Date = '1969-01-01'
If there are duplicate rows, why not update them all, since you can't differentiate anyway?
You just can't do it with a GUI interface in MySQL Query Browser.
If you need to start differentiating the rows, then add an autoincrement integer field, and you'll be able to edit them in MySQL Query Browser too.
Delete the duplicates first. What's the point of having duplicate rows in the table (or any table for that matter)?
Once you've deleted the duplicates you can implement the key and they your problem is solved.