create table invoices(
   year           int not null
  ,invoice_number int not null auto_increment
  ,primary key(year, invoice_number)
);

insert into invoices(year) values(2009);
insert into invoices(year) values(2009);
insert into invoices(year) values(2009);

insert into invoices(year) values(2010);
insert into invoices(year) values(2010);
insert into invoices(year) values(2010);

select * 
  from invoices;

+------+----------------+
| year | invoice_number |
+------+----------------+
| 2009 |              1 |
| 2009 |              2 |
| 2009 |              3 |
| 2010 |              1 |
| 2010 |              2 |
| 2010 |              3 |

Invoice should be auto generated from every year of 1 april I have created such tables but it will generate from 1 jan , I want to start from 1 april

Dani AI

Generated

Short summary and recommended approach

The goal is a per‑fiscal‑year invoice counter that resets on April 1. The thread shows two patterns: the MyISAM per‑group auto_increment trick (as demonstrated) and the InnoDB transaction + SELECT MAX approach (also shown by ). MyISAM can give per‑group auto_increment behavior but is non‑transactional and not crash‑safe. The SELECT MAX inside a transaction works for low concurrency but can become a contention/race point under heavy parallel inserts.

A robust, transactional pattern for InnoDB is a small counter table keyed by fiscal year and an atomic UPDATE that returns the new value. Example workflow:

CREATE TABLE fiscal_counters (
  fiscal CHAR(9) NOT NULL PRIMARY KEY,
  next_invoice INT UNSIGNED NOT NULL DEFAULT 1
) ENGINE=InnoDB;

-- atomic get-and-increment for fiscal '2014-2015'
START TRANSACTION;
UPDATE fiscal_counters
  SET next_invoice = LAST_INSERT_ID(next_invoice + 1)
  WHERE fiscal = '2014-2015';
SET @num = LAST_INSERT_ID();
INSERT INTO invoices(fiscal, invoice_number, ...) VALUES('2014-2015', @num, ...);
COMMIT;

Notes and caveats

  • To start at 100 (as asked), initialize next_invoice = 99; the first increment yields 100.
  • Ensure a row for the new fiscal year is created (INSERT ... ON DUPLICATE KEY UPDATE or an explicit initializer) when the April 1 rollover happens.
  • Keep fiscal labels in a small lookup table (id, start_date, end_date, label) rather than inferring from integers; compute the fiscal label in application code or a stored helper (as suggested) so invoices remain easy to query and validate.
  • For very low traffic, the SELECT MAX transaction approach is acceptable; for safety and scale prefer the counter table + LAST_INSERT_ID technique on InnoDB.

Recommended Answers

All 10 Replies

If the invoice numbering goes, for example, from 2009/04/01 to 2010/03/31 then the column year should take both years or the full date string, something like:

2009-2010

Or:

20090401-20100331

Then it can work as you desire:

create table invoices(
    years char(9) not null,
    invoice_number int unsigned not null auto_increment,
    primary key(years, invoice_number)
) engine = myisam;

insert into invoices (years) values('2009-2010'), ('2009-2010'), ('2009-2010');
insert into invoices (years) values('2010-2011'), ('2010-2011');

select * from invoices;
+-----------+----------------+
| years     | invoice_number |
+-----------+----------------+
| 2009-2010 |              1 |
| 2009-2010 |              2 |
| 2009-2010 |              3 |
| 2010-2011 |              1 |
| 2010-2011 |              2 |
+-----------+----------------+
5 rows in set (0.00 sec)

Note: this works on MyISAM engine, but it will not work on InnoDB because this engine requires the auto_increment column in the first position of the primary index key, so:

primary key(invoice_number, years)

To automatically switch the years segments you could use a function, for example this will check if the submitted date is previous of the current 1st of April:

drop function if exists currentyear;
delimiter //
CREATE FUNCTION currentyear(dt datetime)
RETURNS char(9) deterministic
BEGIN
DECLARE result CHAR(9);
CASE WHEN DATEDIFF(dt, CONCAT(YEAR(dt), '-', '04-01')) >= 0 THEN SET result := CONCAT(YEAR(dt), '-', YEAR(DATE_ADD(dt, INTERVAL 1 YEAR)));
ELSE SET result := CONCAT(YEAR(DATE_SUB(dt, INTERVAL 1 YEAR)), '-', YEAR(dt));
END CASE;
RETURN result;
END//
delimiter ;

> select currentyear(now());
+--------------------+
| currentyear(now()) |
+--------------------+
| 2014-2015          |
+--------------------+
1 row in set (0.00 sec)


> select currentyear('2014-03-31 23:59:59');
+------------------------------------+
| currentyear('2014-03-31 23:59:59') |
+------------------------------------+
| 2013-2014                          |
+------------------------------------+
1 row in set (0.00 sec)

And can be used in the inserts:

insert into invoices (years) values(currentyear(now()));

Personally, I would prefer to rely on solution that makes me able to use transactions. Bye!

ThankYou It solves my problem

how to set the auto increment invoice_number value from 100
my expected output:

create table invoices(
years char(9) not null,
invoice_number int unsigned not null auto_increment,
primary key(years, invoice_number)
)

select * from invoices;

+-----------+----------------+
| years | invoice_number |
+-----------+----------------+
| 2009-2010 | 100 |
| 2009-2010 | 101 |
| 2009-2010 | 102 |
| 2010-2011 | 100 |
| 2010-2011 | 101|
+-----------+----------------+

Thanks.

Before anything else, if you want to ask a question, create a new one. Please do not PULL an old thread of someone else to ask a question.

Now, you are talking about starting value of auto increment value? From your script, it could be...

create table invoices(
  years char(9) not null,
  invoice_number int unsigned not null auto_increment=100,
  primary key(years, invoice_number)
)
create table invoices
(
    years char(9) not null,
    invoice_number int unsigned not null auto_increment,
    primary key(years, invoice_number)
) AUTO_INCREMENT = 100

Thanks I tried your structure but it's not woking.Showing the below error

1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '=100,

Did you try Taywin's example?

Err, I didn't test it out. Sorry :P Also, I am not sure you can have multiple 'primary' for auto_increment. You can have only 1 auto_increment field... Select one...

Hi, I'm not sure this will work, because in this case the auto_increment column needs to be in the first position of the primary key, example with the auto increment is second position, as above:

create table io(
    id int unsigned not null auto_increment,
    cat tinyint unsigned not null,
    msg text,
    primary key(cat, id)
) engine = myisam auto_increment = 100;

Now, it sets the auto_increment:

show create table io;

 CREATE TABLE `io` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `cat` tinyint(3) unsigned NOT NULL,
  `msg` text,
  PRIMARY KEY (`cat`,`id`)
) ENGINE=MyISAM AUTO_INCREMENT=100;

But if we try to add something:

insert into io(cat, msg) values(1, 'hello'), (1, 'world');
insert into io(cat, msg) values(2, 'oranges'), (2, 'apples');

Will return:

+----+-----+---------+
| id | cat | msg     |
+----+-----+---------+
|  1 |   1 | hello   |
|  2 |   1 | world   |
|  1 |   2 | oranges |
|  2 |   2 | apples  |
+----+-----+---------+
4 rows in set (0.00 sec)

As you see it doesn't consider the auto_increment setting. Now, if we alter the table to reorder the index, it will start from 100 but it won't group anymore, this is the test:

alter table io modify id int unsigned not null, drop primary key;
alter table io modify id int unsigned not null auto_increment, add primary key(id, cat);

Here show create table io returns:

CREATE TABLE `io` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `cat` tinyint(3) unsigned NOT NULL,
  `msg` text,
  PRIMARY KEY (`id`,`cat`) -- inverted order
) ENGINE=MyISAM AUTO_INCREMENT=100

And inserting as before:

insert into io(cat, msg) values(1, 'hello'), (1, 'world');
insert into io(cat, msg) values(2, 'oranges'), (2, 'apples');

Returns:

+-----+-----+---------+
| id  | cat | msg     |
+-----+-----+---------+
|   1 |   1 | hello   |
|   2 |   1 | world   |
|   1 |   2 | oranges |
|   2 |   2 | apples  |
| 100 |   1 | hello   |
| 101 |   1 | world   |
| 102 |   2 | oranges |
| 103 |   2 | apples  |
+-----+-----+---------+
8 rows in set (0.00 sec)

As you see, now it starts from 100 but it doesn't group anymore.

A little update to use transactions and InnoDB tables. We could create a table without an auto_increment column and the use transactions to get the max invoice number, for example:

create table invoices(
  years char(9) not null,
  invoice_number int unsigned not null,
  primary key(years, invoice_number)
) engine = innodb;

start transaction;

set @maxid = null;
set @currentyear = currentyear(now());
select @maxid := coalesce(max(invoice_number), 99) + 1 as max from invoices where years = @currentyear;
insert into invoices(years, invoice_number) values(@currentyear, @maxid);

commit;

With coalesce we define the starting number, in case the WHERE condition years = @currentyear is false, then coalesce will return NULL and by default is applied the second argument, which in this case is 100. In case you don't want to start from 100, then replace:

coalesce(max(invoice_number), 99)

With:

coalesce(max(invoice_number), 0)

The result will look like:

 select * from invoices;
+-----------+----------------+
| years     | invoice_number |
+-----------+----------------+
| 2013-2014 |            100 |
| 2013-2014 |            101 |
| 2013-2014 |            102 |
| 2014-2015 |            100 |
| 2014-2015 |            101 |
| 2014-2015 |            102 |
+-----------+----------------+
6 rows in set (0.00 sec)

Here's the sqlfiddle: http://sqlfiddle.com/#!9/096f2/1

And this is a PHP example:

<?php

# $db
require '../connections/pdo.php';

if(array_key_exists('insert', $_GET))
{
    # when receiving the datetime from $_GET,
    # the format needs to be validated
    $date = array_key_exists('date', $_GET) ? $_GET['date'] : date('Y-m-d H:i:s');

    $db->beginTransaction();
    $db->query("SET @maxid = null");

    $stmt = $db->prepare("SET @currentyear = currentyear(?)");
    $stmt->execute(array($date));
    $stmt = null;

    $db->query("SELECT @maxid := coalesce(max(invoice_number), 99) + 1 as max FROM invoices WHERE years = @currentyear");

    $db->query("INSERT INTO invoices(years, invoice_number) VALUES(@currentyear, @maxid)");

    $db->commit();
}

$query = $db->query("SELECT * FROM invoices");
$rows = $query->fetchAll(PDO::FETCH_OBJ);

echo "
<table>
    <thead>
        <tr>
            <th>YEARS</th>
            <th>Invoice Number</th>
        </tr>
    </thead>
    <tbody>";
    foreach($rows as $row)
    {
    echo "
        <tr>
            <td>
                {$row->years}
            </td>
            <td>
                {$row->invoice_number}
            </td>
        </tr>";
    }
echo "
    </tbody>
</table>";

By using a link like:

will add rows to the invoices table. Bye.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.