Creating a numeric sequence based on the date of a corresponding transaction - a step-by-step guide

INTRO

I built an e-commerce app with TypeScript and Sequelize ORM. In the app, I have a table that generates sequential invoice numbers based on the current day.

CREATE TABLE `dm_generate_trx` (
  `id` int NOT NULL AUTO_INCREMENT,
  `date` date NOT NULL,
  `increment_value` int NOT NULL DEFAULT '0',
  `flag_hit` varchar(1) CHARACTER SET utf8 DEFAULT NULL,
  `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
)

This is how the table looks like:

|id |date      |increment_value|flag_hit|created_at         |updated_at         |
|---|----------|---------------|--------|-------------------|-------------------|
|118|2022-05-19|1              |N       |2022-05-19 10:10:21|2022-05-19 10:10:21|
|126|2022-05-19|2              |N       |2022-05-19 10:10:54|2022-05-19 10:10:54|
|134|2022-05-19|3              |N       |2022-05-19 10:11:37|2022-05-19 10:11:37|
|142|2022-05-20|1              |N       |2022-05-20 08:12:14|2022-05-20 08:12:14|
|150|2022-05-20|2              |N       |2022-05-20 08:12:34|2022-05-20 08:12:34|

In my TypeScript code, I have a query to generate the invoice number daily starting from 1 each day.

const count = await dm_generate_trx.count({
        where: {
          createdAt: {
            [Op.gt]: todayStart, // will be "DATE NOW 00:00:00"
            [Op.lt]: now,
          },
        },
        transaction,
      });
      const currentCount = count + 1;

The SQL equivalent would be:

SELECT count(*) FROM dm_generate_trx 
WHERE createdAt > 'DATE NOW 00:00:00' AND createdAt < 'DATE NOW HH:MM:SS'

I then add 1 to this count and insert it into the database.

ISSUE

The problem arises when multiple transactions run almost simultaneously, resulting in duplicates because the script doesn't capture the initially inserted value.

I expect the data to display as below:

...
id date increment_value flag_hit created_at updated_at
118 2022-05-19 1 N 2022-05-19 10:10:21 2022-05-19 10:10:21

However, the actual data shows duplicates.

...
id date increment_value flag_hit created_at updated_at
118 2022-05-19 1 N 2022-05-19 10:10:21 2022-05-19 10:10:21

How can I address this issue? I've tried using

ISOLATION_LEVELS.READ_UNCOMMITTED
without success. Should I resolve this at the code level or within the database?

Answer №1

It appears that you are dealing with a race condition in your current setup.

Simply using the READ_UNCOMMITTED option will not eliminate this issue, as there is still a small window of time between reading the current value, incrementing it in Typescript, and then inserting it into the table. This brief moment may seem insignificant, but trust me — problems can arise.

There's a saying that goes, "One in a million is next Tuesday." Even if an event seems highly unlikely, when certain conditions are repeated many times, it becomes more probable. You may want to explore the concept of the Birthday problem.

To prevent concurrent transactions from encountering a race condition, some form of locking mechanism is necessary. One transaction must wait until another completes before proceeding.

In MySQL, the AUTO_INCREMENT feature is commonly used to address this issue. It allows multiple sessions to insert into the same table simultaneously by keeping track of the latest auto-incremented ID. However, this feature does not support resetting an incrementing number daily. In such cases, each session would need to lock the table, retrieve the current maximum value, increment it, and then insert the updated value. The LOCK TABLES statement can be utilized for this purpose.

Unfortunately, there is no straightforward transactional solution to this problem, as a race condition would remain a concern.


* I have yet to encounter a valid use case for the READ_UNCOMMITTED setting.

Answer №2

In my opinion, there are a couple of routes you could take:

  1. Determine the updated increment value and perform the insertion in a single transaction on the client side.
  2. Develop a stored procedure and execute it within a single database transaction.

Utilizing the READ_UNCOMMITTED isolation level may not be ideal here. It's crucial to avoid uncommitted data when dealing with count information. The chosen isolation level should reflect the data that has been officially committed.

Answer №3

Option A:

Create a database table to store the sequential numbers with proper transaction handling to prevent concurrency issues. The table should include columns for the year and the last used sequence number.

If it's crucial to avoid missing invoice numbers for accounting purposes, consider implementing a system to assign a "VOIDed" label to any lost invoice numbers.

Option B:

Explore alternatives to using the year + sequence number combination for generating invoice numbers.

(Disclaimer: I lack an Accounting background, so I may not be aware of all the specific requirements.)

Answer №4

If you are working with MY SQL, consider utilizing a secondary auto increment feature specifically designed for it. In this scenario, focus on the date and increment value.

Explore the implementation of AUTO_INCREMENT

Additionally, if the id column is also necessary, you can create a trigger to handle that aspect.

Your queries could resemble something similar to the following:

CREATE TABLE `dm_generate_trx` (
  `id` int,
  `date` date NOT NULL,
  `increment_value` int NOT NULL AUTO_INCREMENT,
  `flag_hit` varchar(1) CHARACTER SET utf8 DEFAULT NULL,
  `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`date`, `increment_value`)
)

CREATE TRIGGER dm_generate_trx_id
BEFORE INSERT ON dm_generate_trx
FOR EACH ROW
BEGIN
      DECLARE nid INT;
      SELECT  COALESCE(MAX(id), 0) + 1
      INTO    nid
      FROM    dm_generate_trx

      
      SET NEW.id = nid;
END;

I hope this information proves to be helpful.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Angular2 Interactive Modal Pop Up

Here is an example of a modal in HTML code: <app-modal #modal1> <div class="app-modal-header"> header </div> <div class="app-modal-body"> You c ...

Utilizing a single mysql connection across client and server JavaScript files within a node.js environment

I have come across similar questions on this platform, but none exactly like mine, so I hope I am not repeating something that has already been asked before. As a newcomer to node.js & socket.io, I ask for your patience as I attempt to build a multi-c ...

Insert data into Typeorm even if it already exists

Currently, I am employing a node.js backend in conjunction with nest.js and typeorm for my database operations. My goal is to retrieve a JSON containing a list of objects that I intend to store in a mySQL database. This database undergoes daily updates, bu ...

having difficulty incorporating if/else statements in a SQL query

I'm having trouble running this query: $sql = "update customers set activation_code='$reg' WHERE c_number='$users1' and password='$id'"; $result = mysql_query($sql); if (mysql_error()) { ?> <script type=tex ...

Efficient File Upload Feature Compatible with all Browsers, Including Internet Explorer versions 7, 8, and 9

Can someone assist me with a problem I'm facing? I need a code script that enables multiple file uploading on all browsers. While HTML5 supports Chrome and Mozilla Firefox, it does not support IE. I am looking for a script/jquery solution that works ...

Typescript - neglecting a package that lacks typings

I am considering using an open source package that does not have TypeScript bindings. After checking the linked resource, I was unable to find a solution. Although I attempted to use @ts-ignore, it did not function as expected. Could someone please prov ...

Retrieve the Ionic storage item as a string

My issue is related to the code snippet below: this.storage.get('user') Upon execution, it returns the following object: t {__zone_symbol__state: null, __zone_symbol__value: Array(0)} I am uncertain about how to handle this object. Is there ...

Arranging a Multidimensional Array Based on Array Values

Currently, I am in the process of developing a message system for a project I am working on. My main objective is to organize an array of conversations based on the datetime of the latest message. Below is a snippet of the array: Array( [0] = Array ( ...

The parameters provided for ionic2 do not align with any acceptable signature for the call target

Currently, I have 3 pages named adopt, adopt-design, and adopt-invite. To navigate between these pages, I am using navCtrl.push() to move forward and to go back to the previous page. Everything works smoothly on the browser, but when I try to build it for ...

What is the way to send custom properties to TypeScript in combination with StyledComponents?

Encountering an error while attempting to implement Styled Components 3 with TypeScript: TS2365: Operator '<' cannot be applied to types 'ThemedStyledFunction<{}, any, DetailedHTMLProps<TableHTMLAttributes<HTMLTableElement>, ...

Even when there is a change in value within the beforeEach hook, the original value remains unchanged and is used for dynamic tests

My current project setup: I am currently conducting dynamic tests on cypress where I receive a list of names from environment variables. The number of tests I run depends on the number of names in this list. What I aim to achieve: My main goal is to manip ...

Inject Angular 2 component into designated space

I am working on a website that requires a settings dialog to be loaded in a designated area upon clicking a button. The settings dialog is a component that retrieves data from REST endpoints. I am hesitant to simply insert the component and hide it as I ...

What is the method for retrieving the second type of property from an array of objects?

I have a collection of objects that map other objects with unique identifiers (id) and names (name). My goal is to retrieve the name corresponding to a specific id. Here is my initial approach: const obj = { foo: { id: 1, name: 'one' }, ...

Creating a TypeScript class with methods to export as an object

Just dipping my toes into Typescript and I've encountered a bit of a challenge. I have a generic class that looks like this: export class Sample { a: number; b: number; doSomething(): any { // return something } } My issue ari ...

Uploading selected radio buttons to the database

As I work on creating a form with radio buttons, the structure looks something like this: <dl> <dt><label for="a1">Meatballs?</label></dt> <dd> <input type="radio" value="yes&quo ...

I am having trouble getting MySQL to accept Excel's date format when inserting

When trying to insert data from an Excel sheet into MySQL, I encountered an issue with inserting date format data. Below is my code for reference. The 'dob' and 'join_date' fields are in date form. <?php session_start(); if(!isset($ ...

Make an indirect mention of a distant JavaScript web address

Our company is looking to incorporate Rollup with Angular 4/Typescript and NPM, and we have a specific set of requirements: Various teams develop JS libraries that need to be centralized, similar to a CDN These libraries are hosted at remote URLs and sho ...

Tips for integrating tsconfig with webpack's provide plugin

In my project, I have a simple component that utilizes styled-components and references theme colors from my utils.tsx file. To avoid including React and styled-components in every component file, I load them through WebpackProvidePlugin. Everything works ...

closing custom components in Ag-Grid React columns

I am currently utilizing version "27.1.0" of "ag-grid-react". In order to display a custom column component that presents a set of options and closes when the user makes a selection, I need it to trigger an API call. Since this component does not re-render ...

Indexing a JSON array column in MySQL with the possibility of non-JSON data present

In my MySQL 8 database, I have a multi-tenant setup. The primary table is called entity, and each tenant can configure additional fields such as field1, field2, and so on. These fields can be text fields, booleans, JSON arrays, and more. The JSON array con ...