A UUID is a Universally Unique Identifier, meaning that these identifiers are unique over your whole DB; even better unique" across space and time". UUID's are stored in 128bit format and follow a standarized format (rfc4122) This makes the type a great alternative to the ID as identity column, I would dare say a superior alternative.
Advantages
-
Easy replications
-
Generated randomly, impossible to guess the previous/next value
-
Very important in the context of API's since most API protocols expose their unqiue keys in the url.
Developers don't have to worry about encrypting/hashing their keys. (Imagine click & print coupons where you could guess the next value...)
Supported by almost every respected ORM (sometimes even the default over ID)
Add the advantages of regular ID's to this list...
What would the UUID implementation look like? (Pseudocode)
Solution similar to the id:
CREATE OR REPLACE TABLE USERS (
uuid uuid not null generated always as identity,
firstname varchar(50) not null,
lastname varchar(50) not null
primary key (uuid)
);
An alternative would be to combine GENERATED ALWAYS with DEFAULT GENERATE_UUID():
CREATE OR REPLACE TABLE USERS (
uuid varchar(50) generated always default GENERATE_UUID(),
firstname varchar(50) not null,
lastname varchar(50) not null
primary key (uuid)
);
My least favorite alternative is provide a default so applications don't have to pass the value every time. But it allows invalid userinput:
CREATE OR REPLACE TABLE USERS (
uuid varchar(50) not null default GENERATE_UUID(),
firstname varchar(50) not null,
lastname varchar(50) not null
primary key (uuid)
);
Note: It seems like we can't use scalar functions like GENERATE_UNIQUE() as default value today. Is this material for a separate ticket?
Workarounds/Alternatives (See attached SQL files to play with it yourself)
Rowid
GUID/UUID identity support
Support for UUID columns
CREATE OR REPLACE TABLE USERS (
rid rowid generated always,
firstname varchar(50) not null,
lastname varchar(50) not null
);
insert into users (firstname, lastname)
values ('Mario', 'Aquino'), ('Dimi', 'VLH');
-- Does NOT work => only one rowid/table allowed
CREATE OR REPLACE TABLE ADDRESSES (
rid rowid generated always,
street varchar(50),
city varchar(50),
user_id rowid,
foreign key (user_id) REFERENCES users(rid)
);
-- Does not work because the types are not compatible?
CREATE OR REPLACE TABLE ADDRESSES (
rid rowid generated always,
street varchar(50),
city varchar(50),
user_id varchar(26),
foreign key (user_id) REFERENCES users(id)
);
Triggers
CREATE OR REPLACE TABLE USERS (
uuid varchar(13) not null,
firstname varchar(50) not null,
lastname varchar(50) not null,
primary key (uuid)
);
CREATE OR REPLACE TRIGGER generate_uuid AFTER INSERT ON USERS FOR EACH ROW MODE DB2SQL UPDATE USERS SET UUID = GENERATE_UNIQUE();
insert into users (uuid, firstname, lastname)
values ('tempstring', 'Mario', 'Aquino'), ('tempstring2', 'Dimi', 'VLH');
select *
from users;
SQL adapters in Open Source Languages will have to check if the trigger exists before every insert operation.
On insert we have to send dummydata in the table as a placeholder before the trigger is executed. For an identity column it becomas a daunting task to provide unique dummydata.
Alternative with SQL Sequence Object
CREATE OR REPLACE TABLE USERS (
uuid integer not null,
firstname varchar(50) not null,
lastname varchar(50) not null,
primary key (uuid)
);
CREATE OR REPLACE SEQUENCE unique_identifiers AS INTEGER
START WITH 1
INCREMENT BY 1
NO MAXVALUE
NO CYCLE
CACHE 20;
-- Does not work, because the uuid is not generated
insert into users (firstname, lastname)
values ('Mario', 'Aquino'), ('Dimi', 'VLH');
insert into users (uuid, firstname, lastname)
values (next value for unique_identifiers, 'Mario', 'Aquino'), (next value for unique_identifiers,'Dimi', 'VLH');
select *
from users;
What about other databases?
Postgresql has great support for this column type.
MySQL and SQLite stumble behind, but their connector-packages provide support from within the OpenSource languages. I'm not a fan of this approach since you have to translate the logic to every language...
Recap
I know there have been tickets opened about this before. But today we still don't have proper working workarounds, are they are too much work to implement and maintain.
UUID's appear more and more into modern implementations because it guaranties easier integration in future systems because you can use the existing identifiers. No need to create mapping between identifiers from system A to system B!
UUID version 4 is great, but see also version 7 and version 8, both recently introduced but perhaps not yet fully finalized. They have the advantage of better access path paging as newly generated values will fall on the same or nearby access path pages as valued just previously generated.
The CAAC has reviewed this IBM Idea and recommends that IBM view this as a medium priority Idea that should be addressed.
We recommend that the version 4 be adopted if this is implemented. We believe making this an SQL function is advantageous.
Background: The COMMON Americas Advisory Council (CAAC) members have a broad range of experience in working with small and medium-sized IBM i customers. CAAC has a key role in working with IBM i development to help assess the value and impact of individual IBM Ideas on the broader IBM i community and has therefore reviewed your Idea.
For more information about CAAC, see www.common.org/caac
Carmelita Ruvalcaba - CAAC Program Manager
Thank you so much for this! You have clearly invested some time going through my examples and also in creating this workaround.
In my opinion, a function like this should be added by default in DB2 SQL. Like this you don't have to repeat this for every client/library.
Something similar concerning the trigger. It is a great touch to my post to add the trigger before insert instead of after! But I don't get why there does not exist something like:
The documentation for GENERATE_UNIQUE contains an example for a BEFORE INSERT trigger that doesn't require a dummy value. Whatever the GENERATE_UNIQUE returns doesn't seem to be an UUID considering that it only returns 13 instead of 16 bytes.
I've put together a short example with some UUID functions that might be helpful.
Sorry about the spaces in the SQL examples. No idea why this happened. Previous time when I fixed it in the edit, my ticket was gone. So I'm leaving it like this.