Migrar usuarios y esquemas de Oracle® Database a Cloud SQL para PostgreSQL

Este documento forma parte de una serie que proporciona información y directrices clave relacionadas con la planificación y la realización de migraciones de bases de datos Oracle® 11g/12c a la versión 12 de Cloud SQL para PostgreSQL. En este documento se explican las diferencias básicas entre Oracle® Database y Cloud SQL para PostgreSQL en lo que respecta a la creación de usuarios, esquemas, tablas, índices y vistas.

Además de la parte de configuración inicial, la serie incluye las siguientes partes:

Diferencias de terminología entre Oracle y Cloud SQL para PostgreSQL

Oracle y Cloud SQL para PostgreSQL tienen arquitecturas y terminología diferentes para las instancias, las bases de datos, los usuarios y los esquemas. Para ver un resumen de estas diferencias, consulta la parte de terminología de esta serie.

Exportar configuraciones de Oracle

Uno de los primeros pasos a la hora de planificar una migración a Cloud SQL para PostgreSQL es revisar los ajustes de los parámetros de la base de datos de Oracle de origen. Los ajustes relacionados con la asignación de memoria, el conjunto de caracteres y los parámetros de almacenamiento son especialmente útiles porque pueden influir en la configuración inicial y el tamaño del entorno de destino de Cloud SQL para PostgreSQL. Hay varios métodos para extraer la configuración de los parámetros de Oracle. Estos son algunos de los más habituales:

  • Los informes de Automatic Workload Repository (AWR) contienen datos de asignación de recursos (CPU, RAM), configuración de parámetros de instancia y sesiones activas máximas.
  • DBA_HIST, V$OSSTAT y V$LICENSE para ver los detalles del uso de la CPU.
  • V$PARAMETER para ver los parámetros de configuración de la base de datos.
  • V$NLS_PARAMETERS para ver los parámetros de idioma de la base de datos.
  • DBA_DATA_FILES para calcular el tamaño de almacenamiento de la base de datos.
  • Oracle SPFILE para las configuraciones de instancias de bases de datos.
  • Herramientas de programación de trabajos (por ejemplo, crontab) para identificar copias de seguridad rutinarias o ventanas de mantenimiento que se deban tener en cuenta.

Importar y configurar usuarios en Cloud SQL para PostgreSQL

A grandes rasgos, cada esquema de Oracle debe crearse como un esquema independiente en PostgreSQL. En una base de datos de Oracle, user es sinónimo de schema. Esto significa que se crea un esquema al crear un usuario. Siempre hay una relación de 1:1 entre los usuarios y los esquemas. En PostgreSQL, los usuarios y los esquemas se crean por separado. Se puede crear un usuario sin crear un esquema correspondiente. Para mantener la misma estructura de usuario o esquema de Oracle en PostgreSQL, puedes crear un esquema para cada usuario.

En la siguiente tabla se muestran ejemplos de conversiones:

Tipo de acción Tipo de base de datos Comparación de comandos
Crear usuario y esquema Oracle CREATE USER username IDENTIFIED BY password;
PostgreSQL Los usuarios y los esquemas son conceptos distintos en PostgreSQL, por lo que requieren dos CREATEinstrucciones

CREATE USER username WITH PASSWORD 'password';
CREATE SCHEMA schema_name;
independientes.
Asignar funciones Oracle GRANT CONNECT TO username;
PostgreSQL GRANT pg_monitor TO username;
Concesión de privilegios Oracle GRANT SELECT, INSERT, UPDATE ON HR.EMPLOYEES TO username;
PostgreSQL GRANT SELECT, INSERT, UPDATE ON HR.EMPLOYEES TO username;
Revocar privilegios Oracle REVOKE UPDATE ON HR.EMPLOYEES FROM username;
PostgreSQL REVOKE UPDATE ON HR.EMPLOYEES FROM username;
Conceder DBA o superusuario Oracle GRANT DBA TO username;
PostgreSQL GRANT cloudsqlsuperuser TO username;
Eliminar usuario Oracle DROP USER username CASCADE;
PostgreSQL Los usuarios y los esquemas son conceptos distintos en PostgreSQL, por lo que requieren dos DROPinstrucciones

DROP USER username;
DROP SCHEMA schema_name CASCADE;
independientes.
Metadatos de usuarios Oracle DBA_USERS
PostgreSQL pg_catalog.pg_user
Metadatos de permisos Oracle DBA_SYS_PRIVS
DBA_ROLE_PRIVS
SESSION_PRIVS
PostgreSQL pg_catalog.pg_roles
Cadena de conexión de la CLI Oracle sqlplus username/password@host/tns_alias
Sqlplus username/password@host:IP/sid
PostgreSQL Sin petición de contraseña:

PGPASSWORD=password psql -h hostname -U username -d database_name

Con petición de contraseña:

psql -h hostname -U username -W -d database_name

Usuarios de bases de datos Oracle 12c:

En Oracle 12c hay dos tipos de usuarios: usuarios comunes y usuarios locales. Los usuarios comunes se crean en el CDB raíz, incluidas las PDBs. Se identifican por el prefijo C## en su nombre de usuario. Los usuarios locales solo se crean en una PDB específica. Se pueden crear diferentes usuarios de la base de datos con nombres de usuario idénticos en varias PDBs. Cuando migres de Oracle 12c a PostgreSQL, modifica los usuarios y los permisos para adaptarlos a la arquitectura de PostgreSQL. A continuación, te mostramos dos ejemplos habituales para ilustrar estas diferencias:

# Oracle local user
SQL> ALTER SESSION SET CONTAINER=pdb;
SQL> CREATE USER username IDENTIFIED BY password QUOTA 50M ON USERS;

# PostgreSQL user for a single database and schema
postgres=> CREATE USER username WITH PASSWORD 'password';
postgres=> GRANT CONNECT TO DATABASE database_name TO username;
postgres=> GRANT USAGE ON SCHEMA schema_name TO username;
postgres=> -- Optionally, grant object privileges in the schema
postgres=> GRANT ALL ON ALL TABLES IN SCHEMA schema_name TO username;
postgres=> GRANT ALL ON ALL SEQUENCES IN SCHEMA schema_name TO username;
postgres=> GRANT ALL ON ALL FUNCTIONS IN SCHEMA schema_name TO username;
postgres=> GRANT ALL ON ALL PROCEDURES IN SCHEMA schema_name TO username;
postgres=> GRANT ALL ON ALL ROUTINES IN SCHEMA schema_name TO username;

# Oracle common user
SQL> CREATE USER c##username IDENTIFIED BY password CONTAINER=ALL;

# PostgreSQL user with permissions for all database (use the local user script above and repeat it for each database and schema)

Gestionar usuarios a través de la consola Google Cloud

Para ver los usuarios configurados actualmente en Cloud SQL para PostgreSQL, ve a la página siguiente de la Google Cloud consola:

Google Cloud > Storage > SQL > Instance > Users

Captura de pantalla de la página Usuarios.

Importar definiciones de tablas y vistas

Oracle y PostgreSQL se diferencian en cuanto a la distinción entre mayúsculas y minúsculas. En los nombres de Oracle no se distingue entre mayúsculas y minúsculas. En PostgreSQL no se distingue entre mayúsculas y minúsculas, excepto cuando los nombres están entre comillas dobles. Muchas herramientas de exportación de esquemas y generación de SQL para Oracle, como DBMS_METADATA.GET_DDL, añaden automáticamente comillas dobles a los nombres de los objetos. Estas comillas pueden provocar todo tipo de problemas después de la migración. Le recomendamos que quite todas las comillas que rodean los nombres de los objetos de las instrucciones del lenguaje de definición de datos (DDL) antes de crear los objetos en PostgreSQL.

Sintaxis para crear tablas

Cuando se convierten tablas de Oracle a tipos de datos de PostgreSQL, el primer paso es extraer las instrucciones de creación de tablas de Oracle de la base de datos de origen. La siguiente consulta de ejemplo extrae el DDL de la tabla de ubicaciones del esquema HR:

SQL> SELECT DBMS_METADATA.GET_DDL('TABLE', 'LOCATIONS') FROM DUAL;

CREATE TABLE "HR"."LOCATIONS"
   (  "LOCATION_ID" NUMBER(4,0),
  "STREET_ADDRESS" VARCHAR2(40),
  "POSTAL_CODE" VARCHAR2(12),
  "CITY" VARCHAR2(30) CONSTRAINT "LOC_CITY_NN" NOT NULL ENABLE,
  "STATE_PROVINCE" VARCHAR2(25),
  "COUNTRY_ID" CHAR(2),
  CONSTRAINT "LOC_ID_PK" PRIMARY KEY ("LOCATION_ID")
...
      CONSTRAINT "LOC_C_ID_FK" FOREIGN KEY ("COUNTRY_ID")
          REFERENCES "HR"."COUNTRIES" ("COUNTRY_ID") ENABLE

La salida completa incluye elementos de almacenamiento, índices e información de espacio de tabla, que se han omitido porque estos elementos adicionales no son compatibles con la instrucción CREATE TABLE de PostgreSQL.

Una vez que se haya extraído el DDL, quita las comillas que rodean los nombres y realiza la conversión de la tabla según la tabla de conversión de tipos de datos de Oracle a PostgreSQL. Comprueba el tipo de datos de cada columna para ver si se puede convertir tal cual o, si no es compatible, elige otro tipo de datos según la tabla de conversión. Por ejemplo, a continuación se muestra el DDL convertido de la tabla locations.

CREATE TABLE HR.LOCATIONS (
  LOCATION_ID NUMERIC(4,0),
  STREET_ADDRESS VARCHAR(40),
  POSTAL_CODE VARCHAR(12),
  CITY VARCHAR(30) CONSTRAINT LOC_CITY_NN NOT NULL,
  STATE_PROVINCE VARCHAR(25),
  COUNTRY_ID CHAR(2),
  CONSTRAINT LOC_ID_PK PRIMARY KEY (LOCATION_ID),
  CONSTRAINT LOC_C_ID_FK FOREIGN KEY (COUNTRY_ID)
REFERENCES HR.COUNTRIES (COUNTRY_ID)
)

Crear tabla como selección (CTAS)

La instrucción CREATE TABLE AS SELECT (CTAS) se usa para crear una tabla a partir de otra. Ten en cuenta que solo se copian los nombres y los tipos de datos de las columnas, pero no las restricciones ni los índices. PostgreSQL admite el estándar ANSI SQL para la funcionalidad CTAS y es compatible con la instrucción CTAS de Oracle.

Columnas invisibles de Oracle 12c

PostgreSQL no admite columnas invisibles. Para solucionar este problema, crea una vista que contenga solo las columnas visibles.

Restricciones de tabla

Oracle ofrece seis tipos de restricciones de tabla que se pueden definir al crear una tabla o después de crearla mediante el comando ALTER TABLE. Los tipos de restricciones de Oracle son PRIMARY KEY, FOREIGN KEY, UNIQUE, CHECK, NOT NULL y REF. Además, Oracle permite al usuario controlar el estado de una restricción mediante las siguientes opciones:

  • INITIALLY IMMEDIATE: comprueba la restricción al final de cada instrucción SQL posterior (el estado predeterminado).
  • DEFERRABLE/NOT DEFERRABLE: permite usar la cláusula SET CONSTRAINT en las transacciones posteriores hasta que se envíe una instrucción COMMIT
  • INITIALLY DEFERRED: comprueba la restricción al final de las transacciones posteriores.
  • VALIDATE/NO VALIDATE: comprueba (o no comprueba deliberadamente) si hay errores en las filas nuevas o modificadas. Estos parámetros dependen de si la restricción es ENABLED o DISABLED.
  • ENABLED/DISABLED: especifica si la restricción debe aplicarse después de la creación (ENABLED de forma predeterminada).

PostgreSQL también admite seis tipos de restricciones de tabla: PRIMARY KEY, FOREIGN KEY, UNIQUE, CHECK, NOT NULL y EXCLUDE. Sin embargo, hay algunas diferencias notables entre los tipos de restricciones de Oracle y PostgreSQL, entre las que se incluyen las siguientes:

  • PostgreSQL no admite la restricción REF de Oracle.
  • PostgreSQL no crea automáticamente un índice en las columnas de referencia de una restricción de clave externa. Si se necesita un índice, se debe incluir una instrucción CREATE INDEX independiente en las columnas de referencia.
  • PostgreSQL no admite la cláusula ON DELETE SET NULL de Oracle. Esta cláusula indica a Oracle que asigne el valor NULL a los valores dependientes de las tablas secundarias cuando se elimine el registro de la tabla principal.
  • No se admiten restricciones en VIEWS, excepto CHECK OPTION.
  • PostgreSQL no admite la inhabilitación de restricciones. PostgreSQL admite la opción NOT VALID cuando se añade una clave externa o una restricción de comprobación con una instrucción ALTER TABLE. Esta opción indica a PostgreSQL que omita las comprobaciones de integridad referencial en los registros de la tabla secundaria.

En la siguiente tabla se resumen las principales diferencias entre los tipos de restricciones de Oracle y PostgreSQL:

Tipo de restricción de Oracle Compatibilidad con Cloud SQL para PostgreSQL Equivalente de Cloud SQL para PostgreSQL
PRIMARY KEY PRIMARY KEY
FOREIGN KEY Usa la misma sintaxis ANSI SQL que Oracle.

Usa la cláusula ON DELETE para gestionar los casos de eliminación de registros principales FOREIGN KEY. PostgreSQL ofrece tres opciones para gestionar los casos en los que se eliminan datos de la tabla principal y una tabla secundaria hace referencia a una restricción FOREIGN KEY:

  • ON DELETE CASCADE
  • ON DELETE RESTRICT
  • ON DELETE NO ACTION

PostgreSQL no admite la cláusula ON DELETE SET NULL de Oracle.

Usa la cláusula ON UPDATE para gestionar los casos de actualizaciones de registros principales de FOREIGN KEY.
PostgreSQL ofrece tres opciones para gestionar los FOREIGN KEY eventos de actualización de restricciones:

  • ON UPDATE CASCADE
  • ON UPDATE RESTRICT
  • ON UPDATE NO ACTION

PostgreSQL no crea automáticamente un índice en las columnas de referencia de una restricción de clave externa.
UNIQUE Crea un índice UNIQUE de forma predeterminada.
CHECK CHECK
NOT NULL NOT NULL
REF No No es compatible.
DEFERRABLE/NOT DEFERRABLE DEFERRABLE/NOT DEFERRABLE
INITIALLY IMMEDIATE INITIALLY IMMEDIATE
INITIALLY DEFERRED INITIALLY DEFERRED
VALIDATE/NO VALIDATE No No es compatible.
ENABLE/DISABLE No Esta opción está habilitada de forma predeterminada. Usa la opción NOT VALID cuando se añada una nueva clave externa o una restricción CHECK a la tabla mediante una instrucción ALTER TABLE para omitir las comprobaciones de integridad referencial en los registros.
Restricción en las VISTAS No No se admite, excepto VIEW WITH CHECK OPTION.
Metadatos de restricciones Oracle DBA_CONSTRAINTS
PostgreSQL INFORMATION_SCHEMA.TABLE_CONSTRAINTS

Columnas virtuales y generadas

Las columnas virtuales de Oracle se basan en los resultados de los cálculos de otras columnas. Aparecen como columnas normales, pero sus valores se derivan de un cálculo que realiza sobre la marcha el motor de la base de datos Oracle y no se almacenan en la base de datos. Las columnas virtuales se pueden usar con restricciones, índices, particiones de tablas y claves externas, pero no se pueden manipular mediante operaciones del lenguaje de manipulación de datos (DML).

Las columnas generadas de PostgreSQL son comparables a las columnas virtuales de Oracle en cuanto a funcionalidad. Sin embargo, a diferencia de Oracle, las columnas generadas en PostgreSQL se almacenan y debe especificar un tipo de datos para cada columna generada, lo que significa que ocupan espacio de almacenamiento como si fueran columnas normales.

Ejemplo de columna virtual en Oracle:

SQL> CREATE TABLE PRODUCTS (
        PRODUCT_ID     INT PRIMARY KEY,
        PRODUCT_TYPE   VARCHAR2(100) NOT NULL,
        PRODUCT_PRICE  NUMBER(6,2) NOT NULL,
        PRICE_WITH_TAX AS (ROUND(PRODUCT_PRICE * 1.01, 2))
);

SQL> INSERT INTO PRODUCTS(PRODUCT_ID, PRODUCT_TYPE, PRODUCT_PRICE)
     VALUES(1, 'A', 99.99);

SQL> SELECT * FROM PRODUCTS;
PRODUCT_ID PRODUCT_TYPE         PRODUCT_PRICE PRICE_WITH_TAX
---------- -------------------- ------------- --------------
         1 A                            99.99         100.99

Ejemplo equivalente en PostgreSQL:

postgres=> CREATE TABLE PRODUCTS (
postgres(>         PRODUCT_ID     INT PRIMARY KEY,
postgres(>         PRODUCT_TYPE   VARCHAR(100) NOT NULL,
postgres(>         PRODUCT_PRICE  NUMERIC(6,2) NOT NULL,
postgres(>         PRICE_WITH_TAX NUMERIC GENERATED ALWAYS AS (ROUND(PRODUCT_PRICE * 1.01, 2)) STORED
postgres(> );

postgres=> INSERT INTO PRODUCTS(PRODUCT_ID, PRODUCT_TYPE, PRODUCT_PRICE) VALUES(1, 'A', 99.99);

postgres=> SELECT * FROM PRODUCTS;
 product_id | product_type | product_price | price_with_tax
------------+--------------+---------------+----------------
          1 | A            |         99.99 |         100.99
(1 row)

Índices de tablas

Oracle y PostgreSQL ofrecen varios algoritmos de indexación y tipos de índices que se pueden usar en diversas aplicaciones. A continuación, se muestra una lista de los algoritmos de indexación disponibles en PostgreSQL:

Algoritmo de índice Descripción
Árbol B
  • Tipo de índice predeterminado de PostgreSQL, que se usa para acelerar las consultas de igualdad y de intervalo.
  • Admite todos los tipos de datos primitivos y se puede usar para recuperar valores NULL.
  • Los valores de índice se ordenan de forma predeterminada en orden ascendente, pero también se pueden configurar en orden descendente.
Hash
  • Se usa para acelerar las búsquedas de igualdad.
  • Más eficiente que el índice de árbol B, pero solo puede gestionar búsquedas de igualdad.
GIN
  • Índices de árbol invertido
  • Más eficiente que el índice de árbol B cuando se trata de columnas que contienen varios valores de componentes, como arrays y texto
GiST
  • No es un solo tipo de índice, sino una infraestructura para definir índices que puedan admitir más operadores de comparación que un índice B-tree normal.
  • Útil para datos geométricos cuando se necesita optimizar las búsquedas del elemento más cercano.
SP-GiST
  • Al igual que GiST, SP-GiST es una infraestructura para estrategias de indexación definidas por el usuario.
  • Permite una amplia gama de estructuras de datos no equilibradas, como los cuadrantes
  • No disponible en Cloud SQL para PostgreSQL
BRIN
  • Índices de intervalo de bloques
  • Almacena resúmenes de los intervalos de bloques físicos de una tabla.
  • Para las columnas con un orden de clasificación lineal
  • Útil para buscar intervalos en tablas enormes

En la siguiente tabla se comparan los tipos de índice de Oracle y PostgreSQL:

Índice de Oracle Descripción Compatible con PostgreSQL Equivalente de PostgreSQL
Índice de mapa de bits Almacena un mapa de bits por cada clave de índice, lo que resulta ideal para proporcionar una recuperación de datos rápida para cargas de trabajo de OLAP. No N/A
Índice de árbol B Es el tipo de índice más habitual, se adapta bien a diversas cargas de trabajo y se puede configurar en orden ASC|DESC. Índice de árbol B
Índice compuesto Se crea en dos o más columnas para mejorar el rendimiento de la recuperación de datos. El orden de las columnas dentro del índice determina la ruta de acceso. Índices de varias columnas
Se pueden especificar hasta 32 columnas al crear un índice de varias columnas.
Índice basado en funciones Almacena el resultado de una función aplicada a los valores de una columna de una tabla. Índices en expresiones
Índice único Un índice de árbol B que aplica una restricción UNIQUE a los valores indexados de cada columna. Índice único
Índice de dominio de la aplicación Adecuado para indexar datos no relacionales, como datos de audio o vídeo, datos LOB y otros tipos no textuales. No N/D
Índice invisible Función de Oracle que te permite gestionar, mantener y probar índices sin afectar a la toma de decisiones del optimizador. No Como solución alternativa, puedes crear un índice adicional en una réplica de lectura para hacer pruebas sin que afecte a la actividad en curso.
Tabla organizada por índice Tipo de índice que controla cómo se almacenan los datos a nivel de tabla e índice. No PostgreSQL no admite tablas organizadas por índices. La instrucción CLUSTER indica a PostgreSQL que organice el almacenamiento de la tabla según un índice especificado. Tiene un propósito similar al de la tabla organizada por índice de Oracle. Sin embargo, la agrupación en clústeres es una operación que se realiza una sola vez y PostgreSQL no mantiene la estructura de la tabla en las actualizaciones posteriores. Se necesita un clúster manual y periódico.
Índice local y global Se usa para indexar tablas con particiones en una base de datos de Oracle. Cada índice se define como LOCAL o GLOBAL. No Los índices de trabajo de las particiones de PostgreSQL tienen la misma función que los índices locales de Oracle (es decir, el índice se define a nivel de partición y no se admite el nivel global).
Índices parciales de tablas con particiones (Oracle 12c) Crea un índice en un subconjunto de las particiones de una tabla. Admite LOCAL y GLOBAL. La partición en PostgreSQL funciona adjuntando tablas secundarias a una tabla principal. Solo se pueden crear índices en un subconjunto de tablas secundarias.
CREATE/DROP INDEX Comando usado para crear y eliminar índices. PostgreSQL admite el comando CREATE INDEX. También admite ALTER TABLE tableName ADD INDEX indexName columnName
ALTER INDEX ... REBUILD Recompila el índice, lo que puede provocar un bloqueo exclusivo en la tabla indexada. Requiere una sintaxis diferente PostgreSQL admite la reconstrucción de índices mediante la instrucción REINDEX. La tabla se bloquea para las escrituras durante esta operación y solo se permiten lecturas.
ALTER INDEX ... REBUILD ONLINE Recompila un índice sin crear un bloqueo exclusivo en la tabla. Requiere una sintaxis diferente PostgreSQL admite la reconstrucción simultánea de índices mediante la instrucción REINDEX TABLE CONCURRENTLY. En este modo, PostgreSQL intenta volver a crear los índices con un bloqueo mínimo, pero puede tardar más tiempo y consumir más recursos en completar la recompilación.
Compresión de índices Una función para reducir el tamaño físico del índice. No N/A
Asignar
índice a un espacio de tabla
Crea un tablespace de índice que se puede almacenar en un disco independiente de los datos de la tabla para reducir los cuellos de botella de E/S de disco. No Aunque PostgreSQL permite crear un índice en un espacio de tablas definido por el usuario, no puedes crear espacios de tablas en Cloud SQL para PostgreSQL, y el índice se debe crear en el espacio de tablas predeterminado.
Indexa metadatos (tablas o vistas) Oracle DBA_INDEXES
DBA_PART_INDEXES
DBA_IND_COLUMNS
PostgreSQL pg_catalog.pg_index
pg_catalog.pg_attribute
pg_catalog.pg_class

Consideraciones sobre la conversión de índices

En la mayoría de los casos, los índices de Oracle se pueden convertir en índices de árbol B de PostgreSQL, ya que este tipo de índice es el más utilizado. Al igual que en una base de datos Oracle, se crea automáticamente un índice en los PRIMARY KEYcampos de una tabla. Del mismo modo, se crea automáticamente un índice UNIQUE en los campos que tienen una restricción UNIQUE. Además, los índices secundarios se crean mediante la instrucción CREATE INDEX estándar.

En el siguiente ejemplo se muestra cómo se puede convertir una tabla de Oracle con varios campos indexados a PostgreSQL:

SQL> CREATE TABLE ORA_IDX_TO_PG (
        col1 INT PRIMARY KEY,
        col2 VARCHAR2(60),
        col3 DATE,
        col4 CLOB,
        col5 VARCHAR2(20)
      );

-- Single-field index
SQL> CREATE INDEX idx_col2 ON ora_idx_to_pg(col2);

-- Composite index
SQL> CREATE INDEX idx_cols3_2 ON ora_idx_to_pg(col3 DESC, col2);

-- Unique index
SQL> CREATE UNIQUE INDEX idx_col3_uni ON ora_idx_to_pg(col3);

-- Function-based index
SQL> CREATE INDEX idx_func_col3 ON
        ora_idx_to_pg(EXTRACT(MONTH FROM col3));

-- CLOB index
SQL> CREATE INDEX idx_col4 ON
       ora_idx_to_pg(col4) INDEXTYPE IS CTXSYS.CONTEXT;

-- Invisible index
SQL> CREATE INDEX idx_col5_inv ON
        ora_idx_to_pg(col5) INVISIBLE;

-- Drop index
SQL> DROP INDEX idx_col5_inv;

postgres=> CREATE TABLE ORA_IDX_TO_PG (
postgres(> col1 INT PRIMARY KEY,
postgres(> col2 VARCHAR(60),
postgres(> col3 DATE,
postgres(> col4 TEXT,
postgres(> col5 VARCHAR(20)
postgres(> );

-- Single index (supported)
postgres=> CREATE INDEX idx_col2 ON ora_idx_to_pg(col2);

-- Composite index (supported)
postgres=> CREATE INDEX idx_cols3_2 ON ora_idx_to_pg(col3 DESC, col2);

-- Unique index (supported)
postgres=> CREATE UNIQUE INDEX idx_col3_uni ON ora_idx_to_pg(COL3);

-- Function-based index (supported)
postgres=> CREATE INDEX idx_func_col3 ON
postgres->         ora_idx_to_pg(EXTRACT(MONTH FROM col3));

-- CLOB (Supported, but requires different syntax. See Full Text Search for details)
postgres=> CREATE INDEX idx_col4 ON ora_idx_to_pg
postgres->         USING GIN (to_tsvector('english', col4));

-- Invisible index (not supported)
-- Optional - create the index as a B-tree index
postgres=> CREATE INDEX idx_col5 ON ora_idx_to_pg(col5);

-- Drop index
postgres=> DROP INDEX idx_col2;

SQL> SELECT ui.table_name,
            ui.index_name,
            ui.index_type,
            ic.column_name
     FROM user_indexes ui JOIN user_ind_columns ic
     ON ui.index_name = ic.index_name
     WHERE ui.table_name = 'ORA_IDX_TO_PG'
     ORDER BY 4;

postgres=> select distinct
postgres->     t.relname as table_name,
postgres->     i.relname as index_name,
postgres-> pg_get_indexdef(ix.indexrelid) index_definition
postgres-> from
postgres->     pg_class t,
postgres->     pg_class i,
postgres->     pg_index ix
postgres-> where
postgres->     t.oid = ix.indrelid
postgres->     and i.oid = ix.indexrelid
postgres->     and t.relname = 'ora_idx_to_pg'
postgres-> order by
postgres->     t.relname,
postgres->     i.relname;

-- OR Use psql \d command:
postgres=> \d ora_idx_to_pg
                  Table "public.ora_idx_to_pg"
 Column |         Type          | Collation | Nullable | Default
--------+-----------------------+-----------+----------+---------
 col1   | integer               |           | not null |
 col2   | character varying(60) |           |          |
 col3   | date                  |           |          |
 col4   | text                  |           |          |
 col5   | character varying(20) |           |          |
Indexes:
    "ora_idx_to_pg_pkey" PRIMARY KEY, btree (col1)
    "idx_col2" btree (col2)
    "idx_col4" gin (to_tsvector('english'::regconfig, col4))
    "idx_col5" btree (col5)
    "idx_cols3_2" btree (col3 DESC, col2)
    "idx_func_col3" btree (date_part('month'::text, col3))

postgres=>

Particiones de tablas

Tanto Oracle como PostgreSQL ofrecen funciones de partición para dividir tablas grandes. Para ello, se segmenta físicamente una tabla en partes más pequeñas, donde cada parte contiene un subconjunto horizontal de las filas. La tabla con particiones se denomina tabla principal y sus filas se almacenan físicamente en sus particiones. Aunque no todos los tipos de partición de Oracle son compatibles con PostgreSQL, PostgreSQL sí admite los más comunes.

En las siguientes secciones se describen los tipos de particiones admitidos por PostgreSQL y se ilustra cada uno con un ejemplo de cómo crear las particiones que corresponden a ese tipo.

Partición por RANGE

Este tipo de partición asigna filas a particiones en función de los valores de las columnas que se encuentran dentro de un intervalo determinado. Cada partición contiene filas en las que el valor de la expresión de partición se encuentra dentro de un intervalo determinado. Es importante tener en cuenta que los intervalos no se solapan entre particiones.

Ejemplo

CREATE TABLE employees (
 empid     INT,
 fname     VARCHAR(30),
 lname     VARCHAR(30),
 hired     DATE,
 separated DATE,
 job_code  INT,
 store_id  INT)
 PARTITION BY RANGE (store_id);

CREATE TABLE employees_p0 PARTITION OF employees
 FOR VALUES FROM (MINVALUE) TO (6);
CREATE TABLE employees_p1 PARTITION OF employees
 FOR VALUES FROM (6) TO (11);
CREATE TABLE employees_p2 PARTITION OF employees
 FOR VALUES FROM (11) TO (16);
CREATE TABLE employees_p3 PARTITION OF employees
 FOR VALUES FROM (16) TO (21);

Particiones LIST

Al igual que la partición RANGE, la partición LIST asigna filas a particiones en función de los valores de las columnas que se encuentren dentro de un conjunto de valores predefinido. Los valores de clave que aparecen en cada partición se enumeran explícitamente para las particiones LIST.

Ejemplo

CREATE TABLE employees (
 empid     INT,
 fname     VARCHAR(30),
 lname     VARCHAR(30),
 hired     DATE,
 separated DATE,
 job_code  INT,
 store_id  INT)
 PARTITION BY LIST (store_id);

CREATE TABLE employees_pNorth PARTITION OF employees
 FOR VALUES IN (3,5,6);
CREATE TABLE employees_pEast PARTITION OF employees
 FOR VALUES IN (1,2,10);
CREATE TABLE employees_pWest PARTITION OF employees
 FOR VALUES IN (4,12,13);
CREATE TABLE employees_pCnrl PARTITION OF employees
 FOR VALUES IN (7,8,15);

Particiones HASH

La partición HASH es la más adecuada cuando el objetivo es conseguir una distribución uniforme de los datos entre todas las particiones. Se asignan un valor de columna (o una expresión basada en un valor de columna que se va a cifrar con hash) y el valor de la fila a la partición que corresponde a ese valor de hash. Los valores hash deben asignarse de forma única a las particiones y todos los valores insertados deben asignarse a exactamente una partición.

Ejemplo

CREATE TABLE employees (
 empid     INT,
 fname     VARCHAR(30),
 lname     VARCHAR(30),
 hired     DATE,
 separated DATE,
 job_code  INT,
 store_id  INT)
 PARTITION BY HASH (date_part('year', hired));

CREATE TABLE employees_p0 PARTITION OF employees
 FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE employees_p1 PARTITION OF employees
 FOR VALUES WITH (MODULUS 4, REMAINDER 1);
CREATE TABLE employees_p2 PARTITION OF employees
 FOR VALUES WITH (MODULUS 4, REMAINDER 2);
CREATE TABLE employees_p3 PARTITION OF employees
 FOR VALUES WITH (MODULUS 4, REMAINDER 3);

Particiones multinivel

La creación de particiones de varios niveles es un método para crear una jerarquía de particiones de una sola tabla. Cada partición se divide en varias particiones diferentes. El número de subparticiones puede variar de una partición a otra.

Ejemplo

CREATE TABLE sales (
 Saleid    INT,
 sale_date DATE,
 cust_code VARCHAR(15),
 income    DECIMAL(8,2))
PARTITION BY RANGE(date_part('year', sale_date));

CREATE TABLE sales_2019 PARTITION OF sales
 FOR VALUES FROM (2019) TO (2020)
 PARTITION BY RANGE(date_part('month', sale_date));

CREATE TABLE sales_2019_q1 PARTITION OF sales_2019
 FOR VALUES FROM (1) TO (4);
CREATE TABLE sales_2019_q2 PARTITION OF sales_2019
 FOR VALUES FROM (4) TO (7);
CREATE TABLE sales_2019_q3 PARTITION OF sales_2019
 FOR VALUES FROM (7) TO (10);
CREATE TABLE sales_2019_q4 PARTITION OF sales_2019
 FOR VALUES FROM (10) TO (13);

CREATE TABLE sales_2020 PARTITION OF sales
 FOR VALUES FROM (2020) TO (2021)
 PARTITION BY RANGE(date_part('month', sale_date));

CREATE TABLE sales_2020_q1 PARTITION OF sales_2020
 FOR VALUES FROM (1) TO (4);
CREATE TABLE sales_2020_q2 PARTITION OF sales_2020
 FOR VALUES FROM (4) TO (7);
CREATE TABLE sales_2020_h2 PARTITION OF sales_2020
 FOR VALUES FROM (7) TO (13);

Adjuntar o separar particiones

En PostgreSQL, las particiones se pueden añadir o quitar de la tabla principal. Una partición que se ha separado se puede volver a adjuntar a la misma tabla más adelante. Además, se pueden especificar nuevas condiciones de partición al volver a adjuntar la partición, lo que permite ajustar los límites de la partición.

Ejemplo

CREATE TABLE employees (
 empid     INT,
 fname     VARCHAR(30),
 lname     VARCHAR(30),
 hired     DATE,
 separated DATE,
 job_code  INT,
 store_id  INT)
 PARTITION BY RANGE (date_part('year', hired));

CREATE TABLE employees_p0 PARTITION OF employees
 FOR VALUES FROM (2010) TO (2015);
CREATE TABLE employees_p1 PARTITION OF employees
 FOR VALUES FROM (2015) TO (2020);

-- changing partition boundaries
BEGIN TRANSACTION;
ALTER TABLE employees DETACH PARTITION employees_p1;
ALTER TABLE employees ATTACH PARTITION employees_p1 FOR VALUES FROM (2015) TO (2022);
COMMIT TRANSACTION;

En la siguiente tabla se describe en qué casos los tipos de partición de Oracle y Cloud SQL para PostgreSQL son equivalentes y en qué casos se recomienda una conversión:

Tipo de partición de Oracle Compatible con PostgreSQL Implementación de PostgreSQL
RANGE particiones PARTITION BY RANGE
LIST particiones PARTITION BY LIST
HASH particiones PARTITION BY HASH
SUB-PARTITIONING Particiones multinivel
Particiones de intervalos No No compatible
Asesor de particiones No No compatible
Partición de preferencias No No compatible
Partición virtual basada en columnas No Para solucionar este problema, puedes crear particiones directamente con la expresión de columna virtual:

CREATE TABLE users (
id INT,
username VARCHAR(20),
first_letter VARCHAR(1)
GENERATED ALWAYS AS
(
UPPER(SUBSTR(TRIM(username), 1, 1))
) STORED
)
PARTITION BY LIST (UPPER(SUBSTR(TRIM(username), 1, 1)));

Partición automática de listas No No compatible
Particiones
No Para solucionar este problema, puedes separar o adjuntar particiones de la tabla para ajustar los límites de las particiones.
Intercambiar particiones DETACH / ATTACH PARTITION
Particiones de varios tipos (particiones compuestas) Particiones multinivel
Metadatos de particiones Oracle DBA_TAB_PARTITIONS
DBA_TAB_SUBPARTITIONS
PostgreSQL pg_catalog.pg_class
pg_catalog.pg_partitioned_table

En el siguiente ejemplo se muestra una comparación de la creación de particiones de tabla en ambas plataformas. Ten en cuenta que PostgreSQL no admite hacer referencia a un tablespace en la cláusula PARTITIONS del comando CREATE TABLE.

Implementación de Oracle

CREATE TABLE employees (
empid NUMBER,
fname VARCHAR2(30),
lname VARCHAR2(30),
hired DATE,
separated DATE,
job_code NUMBER,
store_id NUMBER)
PARTITION BY LIST (store_id) (
PARTITION employees_pNorth VALUES (3,5,6) TABLESPACE users,
PARTITION employees_pEast VALUES (1,2,10) TABLESPACE users,
PARTITION employees_pWest VALUES (4,12,13) TABLESPACE users,
PARTITION employees_pCnrl VALUES (7,8,15) TABLESPACE users
);

Implementación de PostgreSQL

CREATE TABLE employees (
empid INT,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE,
separated DATE,
job_code INT,
store_id INT)
PARTITION BY LIST (store_id);

CREATE TABLE employees_pNorth PARTITION OF employees
FOR VALUES IN (3,5,6);
CREATE TABLE employees_pEast PARTITION OF employees
FOR VALUES IN (1,2,10);
CREATE TABLE employees_pWest PARTITION OF employees
FOR VALUES IN (4,12,13);
CREATE TABLE employees_pCnrl PARTITION OF employees
FOR VALUES IN (7,8,15);

Tablas temporales

En una base de datos de Oracle, las tablas temporales se denominan GLOBAL TEMPORARY TABLES, mientras que en PostgreSQL se conocen simplemente como tablas temporales. La funcionalidad básica de una tabla temporal es idéntica en ambas plataformas. Sin embargo, hay algunas diferencias notables:

  • Oracle almacena la estructura de la tabla temporal para que se pueda usar repetidamente incluso después de reiniciar la base de datos, mientras que PostgreSQL almacena la tabla temporal solo durante la sesión.
  • Diferentes usuarios pueden acceder a una tabla temporal de una base de datos Oracle si tienen los permisos adecuados. Por el contrario, solo se puede acceder a una tabla temporal en PostgreSQL durante la sesión en la que se creó, a menos que se haga referencia a la tabla temporal con nombres calificados por el esquema.
  • En una base de datos Oracle, se distingue entre las tablas GLOBAL y LOCAL temporales que especifican si el contenido de la tabla es global o específico de la sesión. En PostgreSQL, se admiten las palabras clave GLOBAL y LOCAL por motivos de compatibilidad, pero no tienen ningún efecto en la visibilidad de los datos.
  • Si se omite la cláusula ON COMMIT al crear una tabla temporal, el comportamiento predeterminado en Oracle Database es ON COMMIT DELETE ROWS, lo que significa que Oracle trunca la tabla temporal después de cada confirmación. Por el contrario, en PostgreSQL, el comportamiento predeterminado es conservar las filas de la tabla temporal después de cada confirmación.

En la siguiente tabla se destacan las diferencias entre las tablas temporales de Oracle y Cloud SQL para PostgreSQL.

Función de tabla temporal Implementación de Oracle Implementación de PostgreSQL
Sintaxis CREATE GLOBAL TEMPORARY TABLE CREATE TEMPORARY TABLE
Funciones de accesibilidad Se puede acceder desde varias sesiones Solo se puede acceder desde la sesión del creador, a menos que se haga referencia a ellos con nombres cualificados por el esquema.
Compatibilidad con índices
Compatibilidad con claves externas
Conservar DDL No
Acción predeterminada de ON COMMIT Se eliminan los registros Los registros se conservan
ON COMMIT PRESERVE ROWS
ON COMMIT DELETE ROWS
ON COMMIT DROP No
ALTER TABLE Asistencia
Recogida de estadísticas DBMS_STATS.GATHER_TABLE_STATS ANALYZE
Oracle 12c GLOBAL_TEMP_

TABLE_STATS
DBMS_STATS.SET_TABLE_PREFS ANALYZE

Columnas no utilizadas

La función de Oracle que permite marcar columnas específicas como UNUSED se suele usar para eliminar columnas de tablas sin eliminar físicamente los datos de las columnas. Esto se hace para evitar las posibles cargas elevadas que se producen al eliminar columnas de tablas grandes.

En PostgreSQL, al eliminar una columna grande, no se quitan los datos de la columna del almacenamiento físico, por lo que es una operación rápida incluso en tablas grandes. No es necesario marcar una columna como UNUSED, como en una base de datos Oracle. El espacio ocupado por la columna eliminada se recupera mediante nuevas instrucciones DML o durante una operación posterior de VACUUM.

Tablas de solo lectura

Las tablas de solo lectura son una función de Oracle que marca las tablas como de solo lectura mediante el comando ALTER TABLE. En Oracle 12c R2, esta función también está disponible para tablas con particiones y subparticiones. PostgreSQL no ofrece una función equivalente, pero hay dos posibles soluciones alternativas:

  • Concede el permiso SELECT en las tablas a usuarios específicos. Ten en cuenta que esto no impide que el propietario de la tabla realice operaciones de DML en sus tablas.
  • Crea una réplica de lectura de Cloud SQL para PostgreSQL y dirige a los usuarios a las tablas de réplica, que son de solo lectura. Esta solución requiere añadir una instancia de réplica de lectura a una instancia de Cloud SQL para PostgreSQL.
  • Crea un activador de base de datos que genere excepciones en las instrucciones DML. Por ejemplo:

    -- Define trigger function
    CREATE OR REPLACE FUNCTION raise_readonly_exception() RETURNS TRIGGER AS $$
    BEGIN
      RAISE EXCEPTION 'Table is readonly!';
      RETURN NULL;
    END;
    $$ LANGUAGE 'plpgsql';
    
    -- Fire trigger when DML statements is executed on read only table
    CREATE TRIGGER myTable_readonly_trigger
    BEFORE INSERT OR UPDATE OR DELETE OR TRUNCATE ON myTable FOR EACH STATEMENT
    EXECUTE PROCEDURE raise_readonly_exception();
    
    -- Testing the trigger
    postgres=> INSERT INTO myTable (id) VALUES (1);
    ERROR:  Table is readonly!
    CONTEXT:  PL/pgSQL function raise_readonly_exception() line 3 at RAISE
    postgres=>
    

Conjuntos de caracteres

Tanto Oracle como PostgreSQL admiten una amplia variedad de conjuntos de caracteres, ordenaciones y Unicode, incluida la compatibilidad con idiomas de un solo byte y de varios bytes. Además, las bases de datos PostgreSQL que residen en la misma instancia se pueden configurar con conjuntos de caracteres distintos. Consulta la lista de conjuntos de caracteres admitidos en PostgreSQL.

En Oracle Database, los conjuntos de caracteres se especifican a nivel de base de datos (Oracle 12g R1 o versiones anteriores) o a nivel de base de datos conectable (Oracle 12g R2 o versiones posteriores). En PostgreSQL, se especifica un conjunto de caracteres predeterminado cuando se crea una instancia de Cloud SQL para PostgreSQL. Cada base de datos creada en esa instancia se puede crear con un conjunto de caracteres diferente. El orden y la clasificación de los caracteres se pueden especificar por columna de la tabla.

Ejemplo

-- Create a database using UTF-8 character set and ja_JP.UTF collation
postgres=> CREATE DATABASE jpdb WITH ENCODING 'UTF8' LC_COLLATE='ja_JP.UTF8' LC_CTYPE='ja_JP.UTF8' TEMPLATE=template0;

-- Query the character set and collation settings of all databases
postgres=> SELECT datname AS DATABASE_NAME, datcollate AS LC_COLLATE, datctype AS LC_CTYPE from pg_database;
 database_name | lc_collate |  lc_ctype
---------------+------------+------------
 cloudsqladmin | en_US.UTF8 | en_US.UTF8
 template0     | en_US.UTF8 | en_US.UTF8
 template1     | en_US.UTF8 | en_US.UTF8
 postgres      | en_US.UTF8 | en_US.UTF8
 jpdb          | ja_JP.UTF8 | ja_JP.UTF8
(5 rows)

-- Alternatively, use psql \l command to query the database settings
postgres=> \l
                                                List of databases
     Name      |       Owner       | Encoding |  Collate   |   Ctype    |            Access privileges
---------------+-------------------+----------+------------+------------+-----------------------------------------
 cloudsqladmin | cloudsqladmin     | UTF8     | en_US.UTF8 | en_US.UTF8 |
 postgres      | cloudsqlsuperuser | UTF8     | en_US.UTF8 | en_US.UTF8 | =Tc/cloudsqlsuperuser                  +
               |                   |          |            |            | cloudsqlsuperuser=CTc/cloudsqlsuperuser+
               |                   |          |            |            | testuser=CTc/cloudsqlsuperuser
 template0     | cloudsqladmin     | UTF8     | en_US.UTF8 | en_US.UTF8 | =c/cloudsqladmin                       +
               |                   |          |            |            | cloudsqladmin=CTc/cloudsqladmin
 template1     | cloudsqlsuperuser | UTF8     | en_US.UTF8 | en_US.UTF8 | =c/cloudsqlsuperuser                   +
               |                   |          |            |            | cloudsqlsuperuser=CTc/cloudsqlsuperuser
-- Specifying column level collation
postgres=> CREATE TABLE test1 (
postgres(>     a text COLLATE "de_DE",
postgres(>     b text COLLATE "es_ES"
postgres(> );

Vistas

PostgreSQL admite vistas simples y complejas. En cuanto a las opciones de creación de vistas, hay algunas diferencias entre Oracle y PostgreSQL. En la siguiente tabla se destacan estas diferencias.

Función de vista de Oracle Descripción Compatibilidad con Cloud SQL para PostgreSQL Consideraciones sobre las conversiones
FORCE Crea una vista sin verificar si existen las tablas o vistas de origen. No No hay ninguna opción equivalente disponible.
CREATE OR REPLACE Crea una vista que no exista o sobrescribe una que ya tengas. PostgreSQL admite el comando CREATE OR REPLACE para las vistas.
WITH CHECK OPTION Especifica el nivel de cumplimiento al realizar operaciones de DML en la vista. El valor predeterminado es CASCADED, lo que provoca que las vistas a las que se hace referencia también se evalúen.

La palabra clave LOCAL hace que solo se evalúe la vista actual.
WITH READ-ONLY Permite solo operaciones de lectura en la vista. Las operaciones de DML están prohibidas. No Una solución alternativa es conceder privilegios SELECT en la vista a todos los usuarios.
VISIBLE | INVISIBLE (Oracle 12c) Especifica si una columna basada en la vista es visible o invisible para el usuario. No Crea el VIEW con las columnas obligatorias.

En el siguiente ejemplo de conversión se muestra cómo convertir de Oracle a Cloud SQL PostgreSQL para vistas.

-- Create view to retrieve employees from department 100 using the WITH CHECK -- OPTION option
SQL> CREATE OR REPLACE FORCE VIEW vw_emp_dept100
AS
SELECT EMPLOYEE_ID,
       FIRST_NAME,
       LAST_NAME,
       SALARY,
       DEPARTMENT_ID
FROM EMPLOYEES
WHERE DEPARTMENT_ID=100
WITH CHECK OPTION;

-- Perform an UPDATE operation on the VIEW
SQL> UPDATE vw_emp_dept100
     SET salary=salary+1000;

postgres=> CREATE OR REPLACE VIEW vw_emp_dept100
postgres-> AS
postgres-> SELECT EMPLOYEE_ID,
postgres->        FIRST_NAME,
postgres->        LAST_NAME,
postgres->        SALARY,
postgres->        DEPARTMENT_ID
postgres-> FROM EMPLOYEES
postgres-> WHERE DEPARTMENT_ID=100
postgres-> WITH CHECK OPTION;

-- Perform an UPDATE operation on the VIEW
postgres=> UPDATE vw_emp_dept100
postgres-> SET salary=salary+1000;

-- Update one employee department id to 60
postgres=> UPDATE vw_emp_dept100
postgres-> SET DEPARTMENT_ID=60
postgres-> WHERE EMPLOYEE_ID=110;

ERROR:  new row violates check option for view "vw_emp_dept100"
DETAIL:  Failing row contains (110, John, Chen, JCHEN, 515.124.4269, 1997-09-28, FI_ACCOUNT, 9200.00, null, 108, 60).

Gestión de accesos a la vista:

Los propietarios de una vista deben tener privilegios en las tablas base para crear la vista. El usuario de una vista necesita los permisos SELECT adecuados en la vista. También necesitan los permisos INSERT, UPDATE y DELETE adecuados en la vista al realizar operaciones de DML a través de ella. En ambos casos, los usuarios no necesitan permisos en las tablas subyacentes.

Siguientes pasos

  • Consulta más información sobre las cuentas de usuario de PostgreSQL.
  • Consulta arquitecturas de referencia, diagramas y prácticas recomendadas sobre Google Cloud. Consulta nuestro Centro de arquitectura de Cloud.