Ir al contenido

Guías de ingeniería

Cómo verificar una importación de CSV en PostgreSQL

Comprueba más que el número de filas después de una importación de datos de un cliente. Compara las claves aceptadas, los valores transformados, el alcance del tenant y los resultados sin resolver mediante SQL de PostgreSQL.

Mohamed Atef · ImportFlow
Última revisión

Compara los registros de origen aceptados con los registros de destino confirmados mediante claves estables y comprueba después los valores importantes y los invariantes de negocio. Mantén separadas las exclusiones, las escrituras fallidas y los resultados sin resolver. Un recuento coincidente es una primera comprobación útil, pero no permite saber si llegaron los registros correctos.

Una forma abreviada de expresarlo es: la inserción demuestra que se insertaron datos; la conciliación demuestra que son correctos. La segunda parte es condicional: la conciliación solo establece la corrección respecto a las comparaciones y los invariantes que hayas elegido realmente. No puede demostrar una regla de negocio que no se haya especificado.

Los siguientes ejemplos muestran una importación incorrecta que supera una comprobación del número de filas y explican después cómo detectarla.

Alcance y supuestos. PostgreSQL 17. Las tablas temporales con datos sintéticos demuestran la conciliación de un conjunto aceptado con alcance de tenant; no se conectan a datos de clientes.

La validación, la inserción y la conciliación responden a preguntas distintas

EtapaPreguntaLímite
Validación previa¿Estos datos propuestos cumplen las reglas que hemos comprobado?Puede no observar el estado real, los cambios concurrentes o el comportamiento efectivo de los disparadores
Pruebas de inserción / commit¿Se confirmaron estas escrituras en la base de datos?No establece la identidad del origen ni el significado en el dominio
Conciliación¿El resultado observado concuerda con el origen aceptado y los invariantes elegidos?Solo cubre el alcance, la instantánea y las comparaciones realizadas

Conserva esas distinciones en la interfaz y en la entrega. «Se analizó el CSV» no significa «la importación superó la validación». «El servidor lo recibió» no significa «la base de datos lo confirmó». «200 insertados» no significa «todos los datos del cliente son correctos».

Un comprobador de DDL debe usarse antes de estas comprobaciones de resultados reales. PG Import Check puede mostrar los requisitos declarados y las lagunas del análisis en el SQL facilitado. No valida registros CSV, consulta el destino ni realiza la conciliación.

Fija lo que esperas encontrar

Antes de ejecutar, conserva un conjunto aceptado y revisado. Debe identificar los registros de origen que se escribirán, sus valores transformados aprobados, el tenant y el espacio de nombres pertinente del sistema de origen. Conserva con él el mapeo y la versión de las reglas.

Registra las exclusiones de forma explícita. Si un archivo de 205 registros tiene cinco contactos excluidos de forma intencional, el conjunto esperado de inserción es de 200. Si esos cinco se perdieron porque un join no encontró un registro padre, son pérdidas sin explicación hasta que alguien apruebe cómo tratarlas.

Elige una clave estable. Puede ser una referencia externa de contacto con alcance de tenant, un mapeo de IDs generados en el destino o un registro de importación que asocie registros de origen con identidades de destino. Una dirección de correo electrónico no es automáticamente una buena clave: las direcciones pueden cambiar, compartirse o faltar.

Si el destino no tiene una identidad estable para buscar coincidencias o una atribución fiable a la importación, reconoce esa limitación antes de escribir. Una ventana de marcas de tiempo o una diferencia de recuentos puede incluir actividad concurrente de la aplicación. Añade un método revisado para identificar las filas de esta importación o limita lo que afirmas haber verificado.

Demostración: recuentos iguales, registros distintos

Ejecuta esta preparación en una sesión desechable de PostgreSQL 17. Ambas tablas representan únicamente al tenant que se está revisando; sus claves primarias imponen la unicidad dentro de este ejemplo.

SQL
CREATE TEMP TABLE import_expected_demo (
  tenant_id integer NOT NULL,
  external_ref text NOT NULL,
  display_name text,
  PRIMARY KEY (tenant_id, external_ref)
);
CREATE TEMP TABLE import_actual_demo (
  tenant_id integer NOT NULL,
  external_ref text NOT NULL,
  display_name text,
  PRIMARY KEY (tenant_id, external_ref)
);

INSERT INTO import_expected_demo VALUES
  (10, 'contact-a', 'Ada'),
  (10, 'contact-b', 'Bela'),
  (10, 'contact-c', NULL);

INSERT INTO import_actual_demo VALUES
  (10, 'contact-a', 'Ada'),
  (10, 'contact-c', ''),
  (10, 'contact-d', 'Dara');

SELECT
  (SELECT count(*) FROM import_expected_demo WHERE tenant_id = 10)
    AS expected_count,
  (SELECT count(*) FROM import_actual_demo WHERE tenant_id = 10)
    AS actual_count;

Ambos recuentos son tres. Sin embargo, falta el contacto B, el contacto D no se esperaba y el contacto C tiene un nombre vacío en lugar de NULL. Por eso, la igualdad de recuentos es necesaria para esta expectativa concreta de correspondencia uno a uno, pero no basta para garantizar la corrección.

Compara la pertenencia a los conjuntos en ambas direcciones

Usa las claves de negocio aceptadas para encontrar registros ausentes:

SQL
SELECT external_ref
FROM import_expected_demo WHERE tenant_id = 10
EXCEPT
SELECT external_ref
FROM import_actual_demo WHERE tenant_id = 10;

Esto devuelve contact-b. Invierte la comparación para encontrar registros inesperados:

SQL
SELECT external_ref
FROM import_actual_demo WHERE tenant_id = 10
EXCEPT
SELECT external_ref
FROM import_expected_demo WHERE tenant_id = 10;

Esto devuelve contact-d. En una tabla de producción con contactos ya existentes, no compares todo el tenant con el conjunto aceptado de una nueva importación para después etiquetar todos los contactos antiguos como inesperados. Acota las filas reales mediante una atribución fiable a la importación o compara con una expectativa explícitamente construida del estado completo tras la importación.

EXCEPT elimina las filas duplicadas de la salida. Aquí las claves primarias garantizan la unicidad. Sin esa garantía, la igualdad de conjuntos puede ocultar errores de multiplicidad. Comprueba los recuentos agrupados por clave o usa una comparación adecuada de multiconjuntos, como EXCEPT ALL, con una comprensión clara de la semántica de NULL y de la normalización.

Compara los valores con una semántica que trate NULL correctamente

Una vez comprobada la pertenencia a los conjuntos, compara los campos que importan. Usa la representación transformada esperada en lugar de una cadena sin procesar que se normalizó de forma intencional.

SQL
SELECT expected.external_ref,
       expected.display_name AS expected_name,
       actual.display_name AS actual_name
FROM import_expected_demo AS expected
JOIN import_actual_demo AS actual
  ON actual.tenant_id = expected.tenant_id
 AND actual.external_ref = expected.external_ref
WHERE expected.tenant_id = 10
  AND expected.display_name IS DISTINCT FROM actual.display_name;

Esto devuelve el contacto C. Una comparación ordinaria <> en la que interviene NULL produce un resultado desconocido, por lo que puede pasar por alto la discrepancia en una cláusula WHERE. IS DISTINCT FROM proporciona la comparación que tiene en cuenta NULL usada aquí. El join trata deliberadamente las claves coincidentes; no sustituye las comprobaciones de claves ausentes.

Elige los invariantes del dominio con el responsable de los datos. Para una importación de contactos, podrían ser el tenant y la relación con la empresa aprobados, los estados permitidos del ciclo de vida y la conservación de las referencias externas. Para una importación de facturas, podrían ser los totales por moneda y estado del documento. Una sola suma global puede ocultar errores que se compensen entre sí.

Compara los valores generados por la base de datos según sus propiedades previstas. No esperes que un ID recién generado sea igual a un ID del sistema antiguo. Para un total calculado, prueba la aritmética y el redondeo acordados. Para una marca de tiempo de auditoría, prueba la regla temporal documentada. Consulta valores generados y predeterminados.

Contabiliza cada registro de origen una sola vez

Define estados mutuamente excluyentes para tu flujo de trabajo. Para una importación que solo inserta, una contabilización final útil separa las exclusiones aprobadas, los registros cuya confirmación está acreditada, los registros cuya no inserción está acreditada y los registros sin resolver. El trabajo aún no intentado pertenece a la categoría de no insertados con un motivo separado; no des a entender que no superó una comprobación de la base de datos.

Contabilización ilustrativa
205 parsed source records
  5 approved exclusions
196 confirmed committed
  2 confirmed non-inserted (rejected batch)
  2 unresolved (commit evidence unavailable)
---
205 accounted for; import is not yet complete

Esta es una convención de contabilización de ejemplo, no un esquema universal. Un flujo de upsert también necesita distinguir los registros insertados, actualizados y dejados sin cambios de forma intencional. Un origen deduplicado puede tener varios registros de origen que representen una sola entidad aceptada; registra esa relación en lugar de forzar una ecuación incorrecta de una fila a una fila.

Un tiempo de espera de red agotado no demuestra que no se haya realizado ninguna escritura. Consulta un comprobante persistente o pruebas de la base de datos que identifiquen el intento. Si el resultado sigue siendo desconocido, mantenlo como desconocido y detén los reintentos indiscriminados de toda la importación. La guía de diseño del importador explica un patrón atómico de escritura y comprobante.

Usa una instantánea coherente y prueba después la aplicación

Varias consultas ejecutadas en momentos distintos pueden describir estados diferentes de la base de datos. Para una aplicación en funcionamiento, decide si la verificación usa una ventana de escritura controlada, una instantánea coherente de la base de datos o una vista inmutable específica de la importación. Ensaya el coste de esa elección; una transacción larga tiene consecuencias operativas.

Con el aislamiento Read Committed predeterminado de PostgreSQL, las sentencias separadas pueden ver instantáneas confirmadas distintas. Repeatable Read ofrece una instantánea coherente de la transacción, pero no congela la actividad del negocio ni incluye transacciones que se confirmen después de esa instantánea. Consulta la documentación de aislamiento.

Después de comparar la base de datos, prueba la aplicación con el usuario previsto. Una fila puede existir y, aun así, quedar oculta por RLS, estar vinculada a otro tenant, quedar excluida por un filtro de estado o carecer de una relación obligatoria. Revisa también los efectos posteriores: un correo electrónico o una llamada a una API externa pueden necesitar sus propias pruebas, más allá de la fila insertada.

El muestreo puede ayudar a encontrar problemas de formato o de experiencia de usuario; no demuestra que todas las filas coincidan. Indica qué comprobaciones cubrieron todo el conjunto aceptado y cuáles fueron muestras.

Haz que el registro de aceptación sea útil más adelante

Conserva un informe de entrega breve con la identidad del origen aceptado, la versión del mapeo y de las reglas, el destino y el tenant, las pruebas de ejecución, las exclusiones, los resultados de las comparaciones y las comprobaciones sin resolver. Incluye quién revisó el significado de negocio y cuándo se realizó la verificación.

No incluyas registros de clientes sin procesar en logs habituales ni en tickets públicos. Conserva los datos de diagnóstico solo donde esté autorizado, restringe el acceso y acuerda su eliminación. Una referencia externa puede ser sensible por sí misma aunque no sea un nombre ni una dirección de correo electrónico.

Si falla una comprobación, investiga antes de cambiar producción. Corregir un valor de origen, reparar un mapeo y compensar una escritura confirmada son acciones distintas. Conserva las pruebas del resultado original para que una reparación no borre lo que hay que explicar.

Fuentes y lecturas adicionales