Gestión de Nombres Únicos en Oracle: Una Solución Genérica

Recientemente me enfrenté al desafío de crear nombres de usuarios únicos de forma automática. Me propuse resolver el caso específico de generar un username único concatenando el primer nombre y apellido (first and last name), pero con la flexibilidad de que la solución fuera reutilizable para otros tipos de entidades.

Ejemplo del problema:

– Usuario 1: john.doe
– Usuario 2: john.doe → ¿Qué hacer?

La Solución

Se desarrolló una solución genérica que no solo resuelve el problema de usernames únicos, sino que puede aplicarse a cualquier entidad que requiera nombres únicos (proyectos, tenants, etc.).

Los recursos de esta solución los podemos encontrar en:

core_users.sql
core_users_bi_trg.sql
core_unique_names.sql
core_unique_name_manager.pks
core_unique_name_manager.pkb

Componentes Principales

1. Tabla Genérica: core_unique_names

create table core_unique_names (
    name_id                      number generated by default on null as identity (start with 1) primary key not null
  , entity_type                  varchar2(50 char)              not null
  , base_name                    varchar2(100 char)             not null
  , counter                      number             default 0   not null
  , last_used                    timestamp with local time zone default localtimestamp not null);

2. Package Genérico: core_unique_name_manager

create or replace package core_unique_name_manager as
    -- Función genérica para cualquier tipo de entidad
    function generate_unique_name(
        p_entity_type                    in varchar2
      , p_base_name                      in varchar2
    ) return varchar2;

    -- Wrapper específico para usernames
    function generate_unique_username(
        p_first_name                     in varchar2
      , p_last_name                      in varchar2
    ) return varchar2;
end;
function generate_unique_name(
    p_entity_type                    in varchar2
  , p_base_name                      in varchar2
  ) return varchar2
  is
    l_counter                        number;
    l_unique_name                    varchar2(100);
    l_clean_name                     varchar2(100);
  begin
    -- Clean and validate base name
    l_clean_name := clean_name(p_base_name);

    if l_clean_name is null then
      raise_application_error(-20001, 'Base name cannot be null or empty');
    end if;

    if length(l_clean_name) < 2 then
      raise_application_error(-20002, 'Generated name is too short');
    end if;

    -- Get or create counter for this base name
    begin
      select counter + 1
        into l_counter
        from core_unique_names
       where entity_type = p_entity_type
         and base_name = l_clean_name
         and active_yn = 'Y'
         for update;

      -- Update counter
      update core_unique_names
         set counter = l_counter
           , last_used = localtimestamp
       where entity_type = p_entity_type
         and base_name = l_clean_name
         and active_yn = 'Y';

    exception
      when no_data_found then
        -- First occurrence, create counter record
        l_counter := 0;
        insert into core_unique_names (
          entity_type
        , base_name
        , counter
        ) values (
          p_entity_type
        , l_clean_name
        , 1
        );
    end;

    -- Generate unique name
    if l_counter = 0 then
      l_unique_name := l_clean_name;
    else
      l_unique_name := l_clean_name || '_' || l_counter;
    end if;

    return l_unique_name;
  end generate_unique_name;

Cómo Funciona

Paso 1: Generación del Nombre Base

Para usernames, convertimos el nombre completo a un formato estándar:

– Juan Carlos Pérez → juan_carlos.perez
– María José García-López → maria_jose.garcia-lopez

Paso 2: Verificación de Unicidad

El sistema busca en la tabla core_unique_names si ya existe un registro para ese nombre base.

Paso 3: Generación del Nombre Único

Primera vez: juan_carlos.perez
Segunda vez: juan_carlos.perez_1
Tercera vez: juan_carlos.perez_2

Implementación Automática

Trigger en la Tabla de Usuarios

create or replace trigger core_users_bi_trg
before insert
on core_users
referencing old as old new as new
for each row
begin
    -- Generar username único automáticamente
    :new.username := core_unique_name_manager.generate_unique_username(
        :new.first_name
      , :new.last_name
    );
end;
/

Ventajas de esta Solución

1. Genérica: No solo para usernames, sino para cualquier entidad
2. Automática: No requiere intervención manual
3. Consistente: Mismo formato para todos los usuarios
4. Escalable: Maneja miles de usuarios sin problemas
5. Auditable: Mantiene historial de nombres utilizados

Aplicación a Otros Tipos de Entidades

Esta solución puede extenderse fácilmente a otros tipos de entidades:

-- Para proyectos
project_name := core_unique_name_manager.generate_unique_name(
    'PROJECT'
  , 'Mi Proyecto'
);
-- Resultado: 'mi_proyecto', 'mi_proyecto_1', etc.

-- Para tenants
tenant_name := core_unique_name_manager.generate_unique_name(
    'TENANT'
  , 'Empresa ABC'
);
-- Resultado: 'empresa_abc', 'empresa_abc_1', etc.