Search code examples
sqlfirebird

Auto-incrementing a Firebird field value when using UPDATE OR INSERT INTO


I have been a Delphi programmer for 25 years, but managed to avoid SQL until now. I was a dBase expert back in the day. I am using Firebird 3.0 SuperServer as a service on a Windows server 2012 box. I run a UDP listener service written in Delphi 2007 to receive status info from a software product we publish.

The FB database is fairly simple. I use the user's IP address as the primary key and record reports as they come in. I am currently getting about 150,000 reports a day and they are logged in a text file.

Rather than insert every report into a table, I would like to increment an integer value in a single record with a "running total" of reports received from each IP address. It would save a LOT of data.

The table has fields for IP address (Primary Key), LastSeen (timestamp), and Hits (integer). There are a few other fields but they aren't important.

I use UPDATE OR INSERT INTO when the report is received. If the IP address does not exist, a new row is inserted. If it does exist, then the record is updated.

I would like it to increment the "Hits" field by +1 every time I receive a report. In other words, if "Hits" already = 1, then I want to inc(Hits) on UPDATE to 2. And so on. Basically, the "Hits" field would be a running total of the number of times an IP address sends a report.

Adding 3 million rows a month just so I can get a COUNT for a specific IP address does not seem efficient at all!

Is there a way to do this?


Solution

  • The UPDATE OR INSERT statement is not suitable for this, as you need to specify the values to update or insert, so you will end up with the same value for both the insert and the update. You could address this by creating a before insert trigger that will always assign 1 to the field that holds the count (ignoring the value provided by the statement for the insert), but it is probably better to use MERGE, as it gives you more control about the resulting action.

    For example:

    merge into user_stats
      using (
        select '127.0.0.1' as ipaddress, timestamp '2021-05-30 17:38' as lastseen 
        from rdb$database
      ) as src
      on user_stats.ipaddress = src.ipaddress
    when matched then 
      update set 
        user_stats.hits = user_stats.hits + 1, 
        user_stats.lastseen = max_value(user_stats.lastseen , src.lastseen)
    when not matched then
      insert (ipaddress, hits, lastseen) values (src.ipaddress, 1, src.lastseen)
    

    However, if you get a lot of updates for the same IP address, and those updates are processed concurrently, this can be rather error-prone due to update conflicts. You can address this by inserting individual hits, and then have a background process to summarize those records into a single record (e.g. daily).

    Also keep in mind that having a single record removes the possibility to perform more analysis (e.g. distribution of hits, number of hits on day X or a time HH:mm, etc).