Buscar un valor en columnas seleccionadas

Conocemos un nombre, correo o referencia, pero no recordamos en cuál de varias columnas de texto se guardó. Generar una búsqueda sobre toda la base sería costoso y podría leer información que no necesitamos.

Qué necesitamos distinguir

Una lista autorizada limita de antemano qué columnas pueden consultarse. QUOTENAME delimita un identificador de forma válida; un parámetro envía el valor separado del SQL dinámico, que es código construido durante la ejecución.

Definiremos primero una lista explícita de columnas autorizadas. Los identificadores se delimitarán con QUOTENAME y el valor se enviará como parámetro; así evitamos concatenar el dato buscado dentro del SQL dinámico.

Consulta

/*
    Antes de ejecutar:
        Revisa la base activa, los nombres de objetos, los parámetros y los permisos.
        Adapta el ejemplo a tu modelo y pruébalo primero en un ambiente controlado.
*/

SET NOCOUNT ON;
GO
-- Consulta
DECLARE @Valor NVARCHAR(200) = N'Pedido 1';
DECLARE @CoincidenciaExacta BIT = 0;
DECLARE @Patron NVARCHAR(450);
DECLARE @Sql NVARCHAR(MAX);

DECLARE @Columnas TABLE
(
    Esquema SYSNAME,
    Tabla SYSNAME,
    Columna SYSNAME,
    PRIMARY KEY (Esquema, Tabla, Columna)
);

INSERT INTO @Columnas (Esquema, Tabla, Columna)
VALUES
    (N'dbo', N'Cliente', N'Nombre'),
    (N'dbo', N'Cliente', N'Correo'),
    (N'dbo', N'MovimientoInventario', N'Referencia');

IF EXISTS
(
    SELECT 1
    FROM @Columnas AS x
    WHERE NOT EXISTS
    (
        SELECT 1
        FROM sys.tables AS t
        INNER JOIN sys.schemas AS s
            ON s.schema_id = t.schema_id
        INNER JOIN sys.columns AS c
            ON c.object_id = t.object_id
        WHERE s.name = x.Esquema
          AND t.name = x.Tabla
          AND c.name = x.Columna
          AND c.system_type_id IN
              (TYPE_ID(N'char'), TYPE_ID(N'varchar'),
               TYPE_ID(N'nchar'), TYPE_ID(N'nvarchar'))
    )
)
    THROW 50020, N'La lista contiene una columna inexistente o no textual.', 1;

SET @Patron = CASE
    WHEN @CoincidenciaExacta = 1 THEN @Valor
    ELSE N'%' +
         REPLACE(REPLACE(REPLACE(REPLACE
         (@Valor, N'\', N'\\'), N'%', N'\%'), N'_', N'\_'), N'[', N'\[') +
         N'%'
END;

SELECT @Sql = STRING_AGG
(
    CAST
    (
        N'SELECT N' + QUOTENAME(x.Esquema, '''') + N' AS Esquema, ' +
        N'N' + QUOTENAME(x.Tabla, '''') + N' AS Tabla, ' +
        N'N' + QUOTENAME(x.Columna, '''') + N' AS Columna, ' +
        N'CONVERT(nvarchar(4000), ' + QUOTENAME(x.Columna) +
        N') AS ValorEncontrado FROM ' + QUOTENAME(x.Esquema) + N'.' +
        QUOTENAME(x.Tabla) + N' WHERE CONVERT(nvarchar(4000), ' +
        QUOTENAME(x.Columna) + N') ' +
        CASE WHEN @CoincidenciaExacta = 1
             THEN N'= @Patron'
             ELSE N'LIKE @Patron ESCAPE N''\'''
        END
        AS NVARCHAR(MAX)
    ),
    N' UNION ALL '
)
FROM @Columnas AS x;

EXEC sys.sp_executesql
    @Sql,
    N'@Patron nvarchar(450)',
    @Patron = @Patron;
GO

Parámetros o permisos

Cambia @Valor y las filas de @Columnas. La búsqueda se limita a tipos de texto y a los primeros 4,000 caracteres convertidos; para columnas max con documentos extensos conviene diseñar otra estrategia. QUOTENAME protege nombres de esquema, tabla y columna; no sustituye la parametrización de valores. sp_executesql recibe @Patron por separado. Se requieren permisos de lectura sobre todas las columnas seleccionadas y visibilidad de sus metadatos.

Cómo leer el resultado

Cada coincidencia indica esquema, tabla, columna y valor encontrado. En modo parcial escapamos , %, _ y [ para tratarlos como caracteres del texto y no como comodines de LIKE. STRING_AGG construye una consulta por columna y las une con UNION ALL. Aunque los identificadores estén delimitados, cada columna seleccionada puede requerir un escaneo si no existe una estrategia de búsqueda adecuada. La comparación utiliza la intercalación de cada columna. Por eso sensibilidad a mayúsculas, acentos y otros caracteres puede variar entre tablas.

Qué conclusión sí permite obtener

Permite localizar un valor en un conjunto explícito y revisable de columnas textuales sin insertar el valor directamente en el SQL dinámico.

Qué conclusión todavía no permite obtener

No busca automáticamente en toda la base, no cubre binarios ni compara números o fechas con su tipo original. Tampoco garantiza buen rendimiento sobre tablas grandes o búsquedas parciales con comodín inicial.

Siguiente comprobación razonable

Si una columna concentra búsquedas recurrentes, revisa su modelo de acceso y evita convertir esta consulta de investigación en una operación habitual de la aplicación.

Índice del recorrido

Desarrollo y exploración

22 artículos de Desarrollo y exploración
  1. 01Buscar una tabla o vista por nombre
  2. 02Buscar una columna en toda la base
  3. 03Buscar texto dentro de procedimientos, vistas y funciones
  4. 04Consultar dependencias de un objeto
  5. 05Listar relaciones y llaves foráneas
  6. 06Consultar llaves primarias, restricciones únicas e índices
  7. 07Listar columnas con tipo, longitud, nulabilidad y valor predeterminado
  8. 08Encontrar triggers y sus tablas asociadas
  9. 09Encontrar objetos modificados recientemente
  10. 10Obtener conteos aproximados de todas las tablas
  11. 11Medir el espacio utilizado por cada tabla
  12. 12Revisar columnas IDENTITY y su valor actual
  13. 13Detectar registros duplicados por una clave de negocio
  14. 14Detectar registros huérfanos
  15. 15Comparar dos conjuntos de datos
  16. 16Validar conversiones antes de modificar una columna
  17. 17Ejecutar un UPDATE con validación y reversión controlada
  18. 18Ejecutar un DELETE con respaldo lógico y reversión controlada
  19. 19Comparar la estructura de dos bases de datos
  20. 20Describir el resultado de una consulta o procedimiento
  21. 21Encontrar tablas sin llave primaria
  22. 22Buscar un valor en columnas seleccionadas