Why does MySQL count from 1 and not 0? - php

The first element of arrays (in most programming languages) has an id (index) of 0. The first element (row) of MySQL tables has an (auto incremented) id of 1. The latter seems to be the exception.

The better question to ask is "why are arrays zero-indexed?" The reason has to do with pointer arithmetic. The index of an array is an offset relative to the pointer address. In C++, given array char x[5], the expressions x[1] and *(x + 1) are equivalent, given that sizeof(char) == 1.
So auto increment fields starting at 1 make sense. There is no real correlation between arrays and these fields.

You can set the start of the auto increment column to any number, like so:
ALTER TABLE tbl AUTO_INCREMENT = 1;
ALTER TABLE tbl AUTO_INCREMENT = 10;
ALTER TABLE tbl AUTO_INCREMENT = 100;
The default is 1.
See also:
http://dev.mysql.com/doc/refman/5.1/en/example-auto-increment.html

The main reason I suppose is that a row in a database isnt an array and the autoincrement value isnt an index in the sense that an array index is. The primary key id can be any value and to a great extent it is simply essential it is unique and is not guaranteed to be anything else (for example you can delete a row and it won't renumber).
This is a little like comparing apples and oranges!
Array start at 0 because that's the first number. Autoinc fields start at whatever number you want them too, and in that case we would all rather it was 1.

Related

Fastest query to insert multiple pairs of rows with cross-reference in PSQL?

So, there is this situation when you have a table in which you want to insert rows in pairs with a reference to each other. Just like in double-entry accounting, every item has it's opposite as pair of it. There is this table:
CREATE SEQUENCE tbl_item_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
CREATE TABLE tbl_item (
id integer NOT NULL PRIMARY KEY DEFAULT nextval('tbl_item_id_seq'),
pair_id integer,
label character varying(50) NOT NULL,
FOREIGN KEY (pair_id) REFERENCES tbl_item (id)
);
ALTER SEQUENCE tbl_item_id_seq OWNED BY tbl_item.id;
The items are generated procedurally. Usually, there are multiple pairs generated at once, and the ultimate goal would be to insert all the pairs with one query. I have solved this with PHP where I inserted a row, returned it's id, inserted the other row with pair_id filled and updated the first row with the id of the second. This means 3 db query started from PHP, and since there are multiple pairs generated, it means number_of_pairs * 3 queries. When I have about 100 pairs, it means 300 queries and gives a nice overhead in processing time what I would like to minimize.
So, the question is given, what's the fastest way to insert pairs of rows with a reference to each other's id into a single table in PSQL?
You could reserve some ids :
select nextval('tbl_item_id_seq') from generate_series(1,200)
then manually assign the id/pair_id. This way, the inserts could even be a single COPY statement(if your Postgres driver supports it).

Unexpected cardinality violation (Subquery returns more than 1 row)

Consider the following example:
A MySQL table (table) with structure id (Primary Key), value, unique_id exists.
Whenever a user clicks on Button A, PHP executes the following query:
SELECT `id` FROM `table` WHERE `unique_id` = x; //where x is a `unique_id`
If the query returns nothing (I.E. x does not yet exist in the unique_id column), the new row is inserted into table (with x in the unique_id column).
... Continuing with this logic:
A query is executed when viewing Page A:
SELECT `id` FROM `table` WHERE `unique_id` = x;
Today I received the following error:
SQLSTATE[21000]: Cardinality violation: 1242 Subquery returns more than 1 row
This is the first time I have experienced this issue (with a table in excess of 20k rows).
Is is possible that if two separate users both clicked on Button A at precisely the same moment in time (down to the millisecond), that two rows could be written with duplicate values in the unique_id column?
If so, how can I avoid this happening again in future? (Am I taking the wrong approach here?).
With the approach you are using, the answer is most likely that two records can be created. The first thing to do is to actually create UNIQUE INDEX on your unique_id column. According to your question it does not seem to have one.
That raises another question. Do you really need both an id and unique_id in your table? It may be possible to use only one or the other.
If you dropped the unique_id and relied merely on the primary key and converted that to an auto increment field, this problem would not exist.
If you want to continue with your current approach, add the unique index and then do the INSERT first.

How to update the id column starting from 1 again

I am having problem to update the list of id number again starting from 1,2,3,4,5. Since I have deleted few records as I was testing the sql commands. Can you please help on how to make this id column again starting from 1.
I could just the name of the id number however if I do that then when I input new record, it will again start from the previous number which was 66.
ID Name
1 A
32 B
34 C
35 D
55 E
66 F
Truncate your table first and then execute this
ALTER TABLE tablename AUTO_INCREMENT = 1
You should truncate the table to reseed it properly and not just use alter table
(tldr; it's usually better not to worry about the density or sequential order an auto-increment column.)
It is not possible1 to use an AUTO_INCREMENT to automatically fill in values less than MAX(ID).
However, the auto increment ID can be reset if existing IDs are updated. The compacting phase is required because MySQL does not allow "filling in gaps" via an auto-increment column.
Compact the existing IDs, like so:
SET #i := 0;
UPDATE t id = #i := (#i+1)
Important: Make sure that all relational usage is identified in the form of Foreign Key relations with CASCADE ON UPDATE before this is done or the data may become irreversibly corrupted.
Assign the auto-ID see to the maximum1 ID value after compacting:
ALTER TABLE t AUTO_INCREMENT = (SELECT MAX(id) FROM t)
1 Per the AUTO_INCREMENT documentation in ALTER TABLE:
You cannot reset the counter to a value less than or equal to the value that is currently in use .. if the value is less than or equal to the maximum value currently in the AUTO_INCREMENT column, the value is reset to the current maximum AUTO_INCREMENT column value plus one.
The rule means that it is not possible to set the increment ID lower than an already used ID; in addition, manually assigning a value higher will automatically raise the AUTO_INCREMENT value.
The easiest (and sometimest fastest) way is to remove column and add it back. Updating column may screw up indexes or make a mess with values. Droping whole table got no sense. But remember that if other columns refer to that ids you can damage your app.

Auto increment with my own number with alphabet

I want to do an auto increment id at my mySQL as my id. And this id will saved in mySQL. Sorry for my poor English; maybe most of you will not understand what im talking about. Let me show an example.
like this:
abc/def/001(001)until abc/def/001(100)
The auto increment id will auto change to
abc/def/002(001).
Is this possible to do it?
Can anyone help me?
Thanks a lot.
(i would like to update my question)
can this possible write with php code or Generator with php code and save into mysql?
You could use 2 columns to achieve that. one of them a normal auto increment integer value, the second one, which is updated whenever a new row is inserted either with a trigger or manually using CONCAT():
$query = "INSERT INTO table1 (column1,colum2,column3) VALUES (1,2,3)";
mysqli_query($link,$query);
$last_insert_id = mysqli_insert_id($link);
$query = "UPDATE table1 SET my_auto_increment = CONCAT('abc/def/', id) WHERE id = '$last_inserted_id'" // where id is an integer auto increment field
mysqli_query($link,$query);
This cannot be done using the MySQL AUTO_INCREMENT. At least, not directly as you describe. But it's possible to emulate that behavior.
But first, as an aside, I question the need for this type of concatenated identifier in the database. It's problematic, and is going to cause more problems and grief down the road, more than you anticipate right now. If I had to include a column like this, I wouldn't make it a primary key.
I strongly advocate surrogate integer keys, because they meet all the desirable aspects of the ideal primary key: simple, unique, anonymous, immutable, et al. There are two schools of thought on surrogate keys: (1) those that think surrogate keys are a really good thing, and (2) those who haven't been burned by choosing a natural key, yet.
I don't know what problem you are trying to solve, or know what your requirements are. But if you do have an option that avoids storing this type of identifier in the database, I strongly recommend you seriously consider availing yourself of that option.
There's good reason that MySQL doesn't natively support generating identifiers like yours. They are trouble prone, cause grief, and are a bad idea.
With that advertisement aside...
In order for the database to "automatically" generate the values for that column (that is, if the value for that column is not being provided as a value in a SQL insert statement), then you could define a BEFORE INSERT trigger to generate it for you.
The trigger could make use of an integer AUTO_INCREMENT column to generate unique values, and then use that integer value as part of an expression to generate the character string you need.
This demonstration query shows an expression that will generate the string in the specified format... i is the unique integer value, a is an expression to generate the first integer, demonstrates how to get the first integer component, b is the second integer component, and val concatenates that together in the specified format.
SELECT t.i
, (t.i DIV 100 - IF(t.i MOD 100,0,1)) a
, (t.i MOD 100 + IF(t.i MOD 100,0,100)) b
, CONCAT('abc/def/'
,IF(t.i>100000
,(t.i DIV 100 - IF(t.i MOD 100,0,1))
,RIGHT(CONCAT('00',(t.i DIV 100 - IF(t.i MOD 100,0,1))),3)
)
,'('
,RIGHT(CONCAT('00',(t.i MOD 100 + IF(t.i MOD 100,0,100))),3)
,')')
AS val
FROM ( SELECT 101 AS i
UNION ALL SELECT 199
UNION ALL SELECT 200
UNION ALL SELECT 201
UNION ALL SELECT 299
UNION ALL SELECT 300
UNION ALL SELECT 301
UNION ALL SELECT 100000
UNION ALL SELECT 100001
) t
Note: to avoid that first integer component from doing a "wrap" for values of i greater than 99900, that first component will need to be more than three places.
To put that expression into a trigger to automatically generate the bizarre identifier value, you could do something like this:
CREATE TABLE mytable
( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY
, val VARCHAR(80)
);
Note that the AUTO_INCREMENT column in the table is defined to be UNSIGNED, so we won't allow negative values. (Our expression above isn't designed to handle values less than 100, since the starting point was specified as "001(001)", we're going to have our algorithm have that correspond to an integer value of 1.
DELIMITER $$
CREATE TRIGGER trg_mytable_bi
BEFORE INSERT ON mytable
BEGIN
DECLARE i BIGINT;
SET i = 100 + NEW.id;
SET NEW.val = CONCAT('abc/def/'
,IF(i > 100000
,((i DIV 100) - IF(i MOD 100,0,1))
,RIGHT(CONCAT('00',((i DIV 100) - IF((i MOD 100),0,1))),3)
)
,'('
,RIGHT(CONCAT('00',(i MOD 100 + IF(i MOD 100,0,100))),3)
,')');
END;
$$
DELIMITER ;
It's not clear where the 'abc/def' component is coming from. So, I've just hard coded it in the trigger.
If you want to run separate AUTO_INCREMENT integer values for different values of the 'abc/def/', such that you could have 'abc/def/001(001)' and 'ccc/ddd/001(001)'...
the MyISAM engine has a peculiar feature: if the AUTO_INCREMENT column is a secondary column in an index and is not the leading column in any other index, then MySQL will generate auto increment sequences for each distinct value in the leading portion of the index. (I've tested that, but never really used it, because that feature isn't available in the InnoDB engine.)

incremental counter mysql

My question is pretty simple but answer might be tricky.
I'm in PHP and I want to manage manually a unique ID for my objects.
What is tricky is to manage atomicity. I dont want that 2 elements get the same ID.
"Elements" are grouped in "Groups". In each group I want elements ID starting from 1 and grow incrementally for each insert in that group.
My first solution is to have a "lastID" column in the table "Groups" :
CREATE TABLE groups ( id INT AUTO_INCREMENT, lastId INT )
CREATE TABLE elements ( myId INT, multiple values ...)
In order to avoid many elements with the same ID, I have to update lastId and select it in an atomic SQL Query.
After that, one retrieved, I have a unique ID that can't be picked again and I can insert my element.
My question is how to solve the bold part ? My database is MySQL with MyISAM engine so there is no transaction support.
UPDATE groups
SET lastId = lastId + 1
WHERE id = 42
SELECT lastId
FROM groups
WHERE id = 42
Is there something more atomic than these 2 requests ?
Thanks
UPDATE groups SET lastId = last_insert_id(lastId + 1)
and then you can get your new id with
SELECT last_insert_id()
Using last_insert_id with a parameter will store the value and return it when you call it later.
This method of generating autonumbers works best with MyISAM tables having only a few rows (MyISAM always locks the entire table). It also has the benefit of not locking the table for the duration of the transaction (which will happen if it is an InnoDB table).
This is from the MySQL manual:
If expr is given as an argument to LAST_INSERT_ID(), the value of the
argument is returned by the function and is remembered as the next
value to be returned by LAST_INSERT_ID(). This can be used to simulate
sequences:
Create a table to hold the sequence counter and initialize it:
CREATE TABLE sequence (id INT NOT NULL);
INSERT INTO sequence VALUES (0);
Use the table to generate sequence numbers like this:
UPDATE sequence SET id=LAST_INSERT_ID(id+1);
SELECT LAST_INSERT_ID();
The UPDATE statement increments the sequence counter
and causes the next call to LAST_INSERT_ID() to return the updated
value. The SELECT statement retrieves that value. The
mysql_insert_id() C API function can also be used to get the value.
See Section 21.8.3.37, “mysql_insert_id()”.
You can generate sequences without calling LAST_INSERT_ID(), but the
utility of using the function this way is that the ID value is
maintained in the server as the last automatically generated value. It
is multi-user safe because multiple clients can issue the UPDATE
statement and get their own sequence value with the SELECT statement
(or mysql_insert_id()), without affecting or being affected by other
clients that generate their own sequence values.
One option is for you to use the nifty MyISAM feature that let's auto_increment values be incremented for each group.
CREATE UNIQUE INDEX elements_ix1 ON elements (groupId, myID)
myID INT NOT NULL AUTO_INCREMENT
That's more "atomic" than anything that involves updating a separate table. Note that this only works for MyISAM, not InnoDB.
excerpt from http://dev.mysql.com/doc/refman/5.1/en/example-auto-increment.html
MyISAM Notes
For MyISAM tables, you can specify AUTO_INCREMENT on a secondary column in a multiple-column index. In this case, the generated value for the AUTO_INCREMENT column is calculated as MAX(auto_increment_column) + 1 WHERE prefix=given-prefix. This is useful when you want to put data into ordered groups.
I would assume your MySQL installation also has InnoDB engine which does support transactions. You just need to change the engine type of you tables.

Categories