jueves, 16 de diciembre de 2021

La API de JPA 2.1 (IX) JPA-JNDI sin persistence.xml

 0. Introducción

Continuamos del post anterior, pero ahora tenemos que utilizar JNDI en una aplicación web, en este caso Vaadin.

Veamos la estructura de ficheros ya vista en otro blog

src
   |->main
   |   |->webapp
   |   |   |->WEB-INF       Not used (can be deleted)
   |   |   |   |->beans.xml Not used 
   |   |   |->skins
   |   |   |   |->ui
   |   |   |   |->content
   |   |   |->META-INF
   |   |   |   |>context.xml (JNDI pool of connections)
   |   |   |->imgs
   |   |   |   |->enterprise (or any particular folder with images)
   |   |   |->icons
   |   |   |   |->flags (or any particular folder with icons)
   |   |->resources
   |   |   |->view (in yaml or any partiular folder with some info)
   |   |   |->reports (or any partiular folder with jasper reports)
   |   |   |->readme (or any partiular folder with some info)
   |   |   |->properties (property files)
   |   |   |   ->control_post.properties
| | |->META-INF | | | |->services | | | |->com.vaadin.flow.server.VaadinServiceInitListener | | | |->persistence.xml | | |->i18n (all teh internationalization resources) | | | |->file_en.properties ; file_es.properties | | |->docs (any doc ) | | |->simplelogger.properties | |->java | | |->ui | | |->openadmin |->test frontend |->tone-edu (test with js) | |->tone-test.js |->three-edu (test with js) | |->three-test.js |->apex-charts (test with js) | |->apex-charts-test.js |->styles (all the css defined) | |->my-defined.css.css |->src (empty) |->generated | |->vaadin.ts |->font-awesome | |->webfonts, svgs, sprites, scss, metadata, less, js, css.. gradle.properties build.gradle

En este caso com es lógico no utilizaremos el presistence.xml


1. Ficheros importantes


En src/main/webapp/META-INF tenemos context.xml. En este ejemplo solo utilizaremos solo el recurso "control_post"

 
<Context path="/openweb" docBase="openweb"
        debug="5" reloadable="true" crossContext="true">
    <Resource 
      name="control_post"
      auth="Container"
      type="javax.sql.DataSource"
      username="my_user"
      password="my_password"
      maxTotal="50"
      maxIdle="20"
      maxWait="10000"
      timeBetweenEvictionRunsMillis="180000"
      removeAbandonedOnMaintenance="true"
      removeAbandonedOnBorrow="true"
      driverClassName="org.postgresql.Driver"
      url="jdbc:postgresql://192.1.1.1:5432/openweb" 
    />
    
    <Resource 
      name="other-persistence-unit"
      auth="Container"
      type="javax.sql.DataSource"
      username="other_user"
      password="other_password"
      maxTotal="50"
      maxIdle="20"
      maxWait="10000"
      timeBetweenEvictionRunsMillis="180000"
      removeAbandonedOnMaintenance="true"
      removeAbandonedOnBorrow="true"
      driverClassName="org.postgresql.Driver"
      url="jdbc:postgresql://192.1.1.2:5432/other_db" 
    />
        
</Context>



En src/resources/properties metemos este fichero control_post.properties

#-----------------------------------------------------------------------------
#1. General Properties
persistence.unit.name=            control_post
persistence.provider.class.name=  org.hibernate.jpa.HibernatePersistenceProvider
jta.datasource.name=              control_post
#non.jta.datasource=               
#mapping.file.names=
#jar.file.urls=
#persistence.unit.root.url
managed.classes=                  openadmin.model.csv.AytosCSV;\
                                  openadmin.model.csv.CSVLog;\
                                  openadmin.model.sedipualba.Expedient;\
                                  openadmin.model.sedipualba.Document

exclude.unlisted.classes=         true	
#shared.cache.mode=
#-----------------------------------------------------------------------------
#Properties not included in datasource
hibernate.dialect=                org.hibernate.dialect.PostgreSQLDialect
#hibernate.hbm2ddl.auto=           update
hibernate.hbm2ddl.auto=           none
hibernate.show_sql=               true


En src/main/java/utils tenemos PersistenceUnitInfoEdu que es la clase que va a recoger todas las propiedades de la conexión e hibernate. Observar la línea con el fondo amarillento que es la que se encarga de recoger el recurso JNDI de Tomcat. Observar tambien la ruta en rojo para buscar el recurso que quedaría: "java:/comp/env/control_post"


package utils;

import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.persistence.SharedCacheMode;
import javax.persistence.ValidationMode;
import javax.persistence.spi.ClassTransformer;
import javax.persistence.spi.PersistenceUnitInfo;
import javax.persistence.spi.PersistenceUnitTransactionType;
import javax.sql.DataSource;

import org.apache.commons.lang3.StringUtils;

import lombok.Getter;
import lombok.Setter;
import openadmin.utils.basic.StringUtilsEdu;

public class PersistenceUnitInfoEdu implements PersistenceUnitInfo {
    
    public static String JPA_VERSION = "2.2";
    @Getter @Setter private String persistenceUnitName; //OK
    @Getter @Setter private PersistenceUnitTransactionType transactionType   = PersistenceUnitTransactionType.RESOURCE_LOCAL; //OK
    @Getter @Setter private String persistenceProviderClassName="org.hibernate.jpa.HibernatePersistenceProvider"; //OK
    @Getter @Setter private DataSource jtaDataSource=null; //OK a falta de JNI
    @Getter @Setter private DataSource nonJtaDataSource=null; //OK a falta del no JNI
    @Getter @Setter private List<String> mappingFileNames = new ArrayList<>(); //OK
    @Getter @Setter private List<URL> jarFileUrls=null; //OK
    @Getter @Setter private URL persistenceUnitRootUrl=null; //OK
    @Getter @Setter private List<String> managedClassNames = new ArrayList<>(); //OK
    @Getter @Setter private boolean excludeUnlistedClasses=false; //OK
    @Getter @Setter private SharedCacheMode sharedCacheMode=null; //OK
    @Getter @Setter private ValidationMode validationMode=ValidationMode.AUTO; //OK
    @Getter @Setter private Properties properties=null; //OK
    @Getter @Setter private String persistenceXMLSchemaVersion=JPA_VERSION; //OK
    @Getter @Setter private ClassLoader classLoader=null; //OK
    @Getter @Setter private List<ClassTransformer> transformers = new ArrayList<>(); //OK addTransformes
    @Getter @Setter private ClassTransformer transformer=null;
    @Getter @Setter private ClassLoader newTempClassLoader=null; //OK
    
    public PersistenceUnitInfoEdu(String persistenceUnitName, List<String> managedClassNames, boolean excludeUnlistedClasses, Properties properties) {
        this.persistenceUnitName = persistenceUnitName;
        this.managedClassNames = managedClassNames;
        this.excludeUnlistedClasses=excludeUnlistedClasses;
        this.properties = properties;
    }

	@Override
	public boolean excludeUnlistedClasses() {
		return excludeUnlistedClasses;
	}

	@Override
	public void addTransformer(ClassTransformer transformer) {
		// TODO Auto-generated method stub
	}

	public PersistenceUnitInfoEdu(Properties props) throws Exception {
        this.persistenceUnitName = props.getProperty("persistence.unit.name").trim();
        this.persistenceProviderClassName=props.getProperty("persistence.provider.class.name").trim();
        
        String dtSrc=props.getProperty("jta.datasource.name","").trim();
        /*
        if (dtSrc.length()>0) { 
        	Context initContext = new InitialContext();
        	Context envContext  = (Context)initContext.lookup("java:/comp/env");
        	//DataSource ds = (DataSource)envContext.lookup("jdbc/myoracle");
        	this.jtaDataSource=(DataSource)envContext.lookup(dtSrc);
        }
        */	
        if (dtSrc.length()>0) { 
        	InitialContext cxt = new InitialContext();
        	if ( cxt == null ) {
        	   throw new Exception("Uh oh -- no context!");
        	}

        	this.jtaDataSource= (DataSource) cxt.lookup( "java:/comp/env/"+dtSrc );

        	if ( this.jtaDataSource == null ) {
        	   throw new Exception("Data source not found!");
        	}
        	
        }
        
        String[] managedCls=StringUtilsEdu.splitAndTrim(props.getProperty("managed.classes"),";");
        this.managedClassNames =  Arrays.asList(managedCls);
        
        this.excludeUnlistedClasses=Boolean.parseBoolean(props.getProperty("exclude.unlisted.classes").trim());
     
        this.properties = props;
    }
	
	
}


En src/main/java/utils tenemos JPANoPersistenceUtils que permite obtener las EntitymanagerFactory


package utils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;


import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Query;
import javax.persistence.spi.PersistenceUnitInfo;

import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl;
import org.hibernate.jpa.boot.internal.PersistenceUnitInfoDescriptor;



public class JPANoPersistenceUtils {
	
	public static PersistenceUnitInfoEdu getPsUnitInfo(String persistenceUnitName, List<String> managedClassNames, boolean excludeUnlistedClasses, Properties properties) {
		return new PersistenceUnitInfoEdu(persistenceUnitName, managedClassNames, excludeUnlistedClasses, properties);
	}
	
	public static PersistenceUnitInfoEdu getPsUnitInfo (Properties properties) throws Exception {
		return new PersistenceUnitInfoEdu(properties);
	}
	
	public static EntityManagerFactory getEntityManagerFactory(PersistenceUnitInfo psUnitInfo) {
		Map<String, Object> configuration = new HashMap<>();
		return new EntityManagerFactoryBuilderImpl(
		          new PersistenceUnitInfoDescriptor(psUnitInfo), configuration)
		          .build();
	}
	
	public static EntityManagerFactory getEntityManagerFactory(String persistenceUnitName, List<String> managedClassNames, boolean excludeUnlistedClasses, Properties properties) {
		PersistenceUnitInfo persistenceUnitInfo = getPsUnitInfo(persistenceUnitName,managedClassNames,excludeUnlistedClasses,properties);
		return getEntityManagerFactory(persistenceUnitInfo);
	}
	
	public static EntityManagerFactory getEntityManagerFactory(Properties properties) throws Exception {
		PersistenceUnitInfo persistenceUnitInfo = getPsUnitInfo(properties);
		return getEntityManagerFactory(persistenceUnitInfo);
	}
	
	

	public static void main(String[] args) {
		List<String>classes=new ArrayList<String>();
		classes.add("openadmin.utils.nopersistencexml.Test");
		EntityManagerFactory emf=getEntityManagerFactory("my-h2",classes,true,MyConnectionProps.getPropsH2());
		
		
		EntityManager em=emf.createEntityManager();
		
		//Query q=em.createNativeQuery("SELECT * FROM TEST");
		Query q=em.createQuery("SELECT t FROM Test t WHERE t.id<1000");
		List<Test> ltest=q.getResultList();
		for (Test t:ltest) System.out.println(t.getId()+"-"+ t.getName());
			
		em.close();
		emf.close();
	}

}


Veamos una clase de prueba para abrir una conexión.OJO, está clase se debe de llamar dentro de una aplicacion web en marcha. Observar como se obtiene la ruta al fichero de propiedades "control_post.properties" que se encuentra dentro del directorio "properties". Solamante se crea una EntityManagerFactory y a partir de esta una EntityManager. 


 package utils;

import java.io.File;
import java.io.FileInputStream;
import java.util.Properties;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;


import openadmin.utils.nopersistencexml.JPANoPersistenceUtils;

public class Test {

  public static void main(String[] args) {
      String propsPath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
      propsPath += File.separator + "properties"+File.separator+ "control_post.properties";
      Properties controlProps=new Properties();
      try {
          controlProps.load(new FileInputStream(propsPath));
          EntityManagerFactory factory = JPANoPersistenceUtils.getEntityManagerFactory(controlProps);
          EntityManager em = factory.createEntityManager();
          em.close();
          //Do some stuff
          factory.close();
      } catch (Exception e) {
          e.printStackTrace();
      }	
    }
}


Thread fácil (1)

 Segun stackoverflow, esta es una manera fácil de hacer threads en java


new Thread(() -> {
  try {
    this.share = FirmadocUtils.getSMBShare (this.FirmadocProps);
   
    FirmadocUtils.persistAllCSVsSamba(this.share, this.FirmadocProps,this.daoCSVLog);
  } catch (Exception e) {e.printStackTrace();}
}).start();




miércoles, 15 de diciembre de 2021

AYTOS-FIRMADOC-FACE: (1)Tablas importantes y localización de los ficheros

1. FIRMADOC

Esquema DBO

BIB_AY.IDX (Nombre del documento +  relaciones

  • IDX_ID: Id PK = BIB_AY.IDX_IMP.IM_IDX  que coincide con el nombre del documento
  • IDX_EXT: Extensión del documento (PDF o la que sea)
  • IDX_FCH; Que define la carpeta donde se guarda el documento con esta expresión:
       10000*YEAR(B.IDX_FCH)+100*MONTH(B.IDX_FCH)+DAY(B.IDX_FCH) as carpeta
  • IDX_ALI: "Nombre del documento (Relaciones de transferencias)
  • IDX_TDO:  ->ADMDDA.TDO.TDO_ID (descrip=TDO_DES (Relaciones de transferencias)
  • IDX_CP1: "Identificadores:"
  • IDX_FFD: Fecha y hora de firma del documento (SI nulo, no firmado)
  • IDX_FCH: Fecha para localizar la carpeta del documento (AAAAMMDD)
  • BIB_AY.IDX_IMP  Guarda el codigo de verificacion CSV

    -> IDX_IMP.IM_IDX-> IDX.IDX_ID

    • IIM_IDX: Id (PK) y nombre del documento ejemplo 498589(.PDF)
    • IIM_CVA: Código de verificación documento (CSV)
    • IIM_CRC: Otro código complementario

    BIB_AY.FIR  Guarda los datos de las firmas del documento (pero no el binario)
    • FIR_IDX: id del documento = BIB_AY.IDX_ID  
    • FIR_FCH: Fecha de la firma 
    • FIR_UOR: Firmantes = ADMDDA.A_ORG_UOR.UOR_COD ( cargo=UOR_NOM)
    • FIR_UOR_SUS: Firmante sustituto = ADMDDA.A_ORG_UOR.UOR_COD ( cargo=UOR_NOM)
    • FIR_USU: Firmantes (Usuario) = ADMDDA.USU.USU_IDE  ( cargo=USU_NOM)

    BIB_AY.SIG (Firma binaria)

  • SIG_ID: Id PK = BIB_AY.FIR.FIR_SIG
  • SIG_CNT Firma binaria (Seguramente estará tambien comprimida por el agoritmo de Huffman)

  • BIBY_AY.DOC (Algunos documentos se guardan en esta tabla)
    > DOC.DOC_ID-> IDX.IDX_ID
    • DOC_ID:id del documento = BIB_AY.IDX_ID  
    • DOC_CNT: BLOB o contenido del documento. Ojo esta comprimido con el algoritmo de Huffman que se explica en un post posterior
    BIBY_AY.DOC_ORI (Algunos documentos (pero muy pocos!!!)se guardan en esta tabla)
    > DOC_ORI.DOC_ID-> IDX.IDX_ID
    • ORI_ID:id del documento = BIB_AY.IDX_ID  
    • ORI_CNT: BLOB o contenido del documento. Ojo esta comprimido con el algoritmo de Huffman que se explica en un post posterior

    ADMDDA.A_ORG_UOR Cargos y usuarios
    • UOR_COD (PK)
    • UOR_NON Cargo

    ADMDDA.TDO Tipos de documento
    • TDO_ID (PK)
    • TDO_DES
    • TDO_DE1
    ADMDDA.USU Usuarios
    • USU_IDE (PK)
    • USU_NOM 


    Veamos los campos que hacen falta para llenar la cabecera



    Cuando alguno de los campos falta, aparecen en blanco

    (1) ADMDDA.TDO.TDO_DES
    (2) BIBAY.IDX.IDX_ALI

    (3) BIBAY.IDX_IMP.IIM_CVA
    (4) Fecha del sistema actual

    (5) ADMDDA.TDO.TDO_DES
    (6) BIBAY.IDX.IDX_CP1

    Para cada firmante
    (7)BIBAY.FIRM.FIR_UOR          ->ADMDDA.A_ORG_UOR.UOR_COD (muestra cargo UOR_NOM)        
    (7)BIBAY.FIRM.FIR_UOR_SUS ->ADMDDA.A_ORG_UOR.UOR_COD (muestra cargo sustituto si hay UOR_NOM)        
    A veces también se ponen los nombres :
    (7)BIBAY.FIRM.FIR_USU ->  ADMDDA.USU .USU_ID (USU_NOM) 

    (9) BIBAY.IDX.IDX_FDD (Fecha de firma)



    Veamos una consulta para extraer los datos

    SELECT A.IIM_IDX, A.IIM_CVA, --Nombre doc, CSV
           B.IDX_ALI, B.IDX_CP1, B.IDX_FFD, --Documento 2 , Identificadores 2, Firmado  
           10000*YEAR(B.IDX_FCH)+100*MONTH(B.IDX_FCH)+DAY(B.IDX_FCH) AS folder,   
           C.TDO_DES, C.TDO_DE1, --Documento 1, identificadores 1
           X.FIR_FCH, -- Fecha de la firma 
           D.UOR_NOM, D1.UOR_NOM,  -- Cargos (firmante y en nombre)
           E.USU_NOM --Nombre firmante
           
    FROM BIB_AY.dbo.IDX_IMP A
    JOIN BIB_AY.dbo.IDX B ON B.IDX_ID=A.IIM_IDX
    JOIN ADMDDA.dbo.TDO C ON TDO_ID=B.IDX_TDO
    LEFT JOIN BIB_AY.dbo.FIR X ON X.FIR_IDX=A.IIM_IDX
    LEFT JOIN ADMDDA.dbo.A_ORG_UOR D ON D.UOR_COD=X.FIR_UOR
    LEFT JOIN ADMDDA.dbo.A_ORG_UOR D1 ON D1.UOR_COD=X.FIR_UOR_SUS 
    LEFT JOIN ADMDDA.dbo.USU E ON E.USU_IDE=X.FIR_USU
    

    Pero los documentos se localizan en 3 sitios distintos:

    1. Cuando no existe ningún registro de la tabla BIBY_AY.DOC donde  DOC.DOC_ID apunte a IDX.IDX_ID entonces se guarda en la carpeta FIRMADOC de un servidor destinado a ello (192.168.XXX.XXX)

    2. Si existe ningún registro de la tabla BIBY_AY.DOC donde  DOC.DOC_ID apunte a IDX.IDX_ID entonces se guarda en el campo DOC_CNT omprimido por el algoritmo Huffman

    3. Si existe ningún registro de la tabla BIBY_AY.DOC_ORI donde  DOC_ORI.ORI_ID apunte a IDX.IDX_ID entonces se guarda en el campo ORI_CNT comprimido por el algoritmo Huffman. Ojo son muy pocos los documentos guardados aquí


    2. FACE

    Se localizan los ficheros en AYTOS-FACTURA/FACe/in o AYTOS-SICALWIN/sageaytos/FACe/in

    3. 

    SQL SERVER (I) Buscar que tabla y campo contiene un valor. Buscar nombres de campo


    1. Buscar valores en una BD. VErsion Google

    Me parece muy interesantes las repuestas de stackoverflow 


    Una de las que más me ha gustado es de sbrbot  que he adaptado un poco para el caso de tipo "string"


    DECLARE @search VARCHAR(100), @table SYSNAME, @column SYSNAME, @schema SYSNAME
    DECLARE curTabCol CURSOR FOR SELECT c.TABLE_SCHEMA , c.TABLE_NAME, c.COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS c JOIN INFORMATION_SCHEMA.TABLES t ON t.TABLE_NAME=c.TABLE_NAME AND t.TABLE_TYPE='BASE TABLE' -- avoid views WHERE c.DATA_TYPE IN ('varchar','nvarchar','text','ntext','char','nchar') -- searching only in these column types --AND c.COLUMN_NAME IN ('NAME','DESCRIPTION') -- searching only in these column names ORDER BY c.TABLE_NAME, c.COLUMN_NAME SET @search='PHXN' OPEN curTabCol
    FETCH NEXT FROM curTabCol INTO @schema, @table, @column
    WHILE (@@FETCH_STATUS = 0) BEGIN EXECUTE('IF EXISTS (SELECT * FROM ' + @schema + '.[' + @table + '] WHERE [' + @column + '] LIKE ''%' + @search + '%'')
    PRINT ''' + @table + '.' + @column + '''') FETCH NEXT FROM curTabCol INTO @schema, @table, @column END CLOSE curTabCol DEALLOCATE curTabCol



    y para tipo "enteros"


    DECLARE @search int, @table SYSNAME, @column SYSNAME, @schema SYSNAME
    
    DECLARE curTabCol CURSOR FOR
        SELECT c.TABLE_SCHEMA , c.TABLE_NAME, c.COLUMN_NAME
        FROM INFORMATION_SCHEMA.COLUMNS c
        JOIN INFORMATION_SCHEMA.TABLES t 
          ON t.TABLE_NAME=c.TABLE_NAME AND t.TABLE_TYPE='BASE TABLE' -- avoid views
        WHERE c.DATA_TYPE IN ('numeric','int','bigint','decimal') -- searching only in these column types
        --AND c.COLUMN_NAME IN ('NAME','DESCRIPTION') -- searching only in these column names
        ORDER BY c.TABLE_NAME, c.COLUMN_NAME 
    
    
    --SET @search='SPHXN-YNS5H-9TV6R'
    --SET @search='10BC9C2A9F54521E4AE70402CCFC4A87F569F8A8'
    SET @search=418534
    
    OPEN curTabCol
    FETCH NEXT FROM curTabCol INTO @schema, @table, @column
    
    WHILE (@@FETCH_STATUS = 0)
    BEGIN
        --PRINT '                                  ' + @table + '.' + @column
        EXECUTE('IF EXISTS 
                 (SELECT * FROM ' + @schema + '.[' + @table + '] WHERE [' + @column + '] = ' + @search + ') 
                  PRINT ''' + @table + '.' + @column + '''')
        FETCH NEXT FROM curTabCol INTO @schema, @table, @column
    END
    
    CLOSE curTabCol
    DEALLOCATE curTabCol
    


    2. Buscar valores en una BD. Version Claude

    DECLARE @db SYSNAME = N'BIB_AY';--'ACCEDE_ADM';---BIB_AY';--'SCWAY2026';
    DECLARE @valor NVARCHAR(200) = '14-049'; --'220260004314'; --'220260007686' -- '320260001544';
    DECLARE @schema NVARCHAR(128), @tabla NVARCHAR(128);
    DECLARE @sql NVARCHAR(MAX), @sqlCount NVARCHAR(MAX);
    DECLARE @cnt INT;
    
    -- 1. Tablas de la base de datos indicada (sin depender de USE)
    IF OBJECT_ID('tempdb..#Tablas') IS NOT NULL DROP TABLE #Tablas;
    CREATE TABLE #Tablas (Esquema NVARCHAR(128), Tabla NVARCHAR(128));
    
    SET @sql = N'
    SELECT s.name, t.name
    FROM ' + QUOTENAME(@db) + N'.sys.tables t
    JOIN ' + QUOTENAME(@db) + N'.sys.schemas s ON t.schema_id = s.schema_id
    WHERE t.is_ms_shipped = 0';
    INSERT INTO #Tablas (Esquema, Tabla) EXEC sp_executesql @sql;
    
    -- 2. Columnas candidatas (texto/numéricas) de esa misma base de datos
    IF OBJECT_ID('tempdb..#Columnas') IS NOT NULL DROP TABLE #Columnas;
    CREATE TABLE #Columnas (Esquema NVARCHAR(128), Tabla NVARCHAR(128), Columna NVARCHAR(128));
    
    SET @sql = N'
    SELECT s.name, t.name, c.name
    FROM ' + QUOTENAME(@db) + N'.sys.columns c
    JOIN ' + QUOTENAME(@db) + N'.sys.tables t ON c.object_id = t.object_id
    JOIN ' + QUOTENAME(@db) + N'.sys.schemas s ON t.schema_id = s.schema_id
    JOIN ' + QUOTENAME(@db) + N'.sys.types ty ON c.user_type_id = ty.user_type_id
    WHERE t.is_ms_shipped = 0
      AND ty.name IN (''varchar'',''nvarchar'',''char'',''nchar'',''text'',''ntext'',
                      ''int'',''bigint'',''smallint'',''tinyint'',''decimal'',''numeric'')';
    INSERT INTO #Columnas (Esquema, Tabla, Columna) EXEC sp_executesql @sql;
    
    -- 3. Recorrer tabla por tabla
    DECLARE cur CURSOR FOR SELECT Esquema, Tabla FROM #Tablas ORDER BY Esquema, Tabla;
    OPEN cur;
    FETCH NEXT FROM cur INTO @schema, @tabla;
    
    WHILE @@FETCH_STATUS = 0
    BEGIN
        PRINT 'Consultando tabla: ' + @db + '.' + @schema + '.' + @tabla;
    
        SELECT @sql = STRING_AGG(CAST(
            'SELECT ''' + @db + '.' + @schema + '.' + @tabla + ''' AS Tabla, ''' + Columna + ''' AS Campo, TRY_CAST([' + Columna + '] AS NVARCHAR(4000)) AS Valor
             FROM ' + QUOTENAME(@db) + '.' + QUOTENAME(@schema) + '.' + QUOTENAME(@tabla) + '
             WHERE TRY_CAST([' + Columna + '] AS NVARCHAR(4000)) LIKE ''%' + @valor + '%'''
        AS NVARCHAR(MAX)), ' UNION ALL ')
        FROM #Columnas
        WHERE Esquema = @schema AND Tabla = @tabla;
    
        SET @cnt = 0;
    
        IF @sql IS NOT NULL
        BEGIN
            SET @sqlCount = N'SELECT @cntOut = COUNT(*) FROM (' + @sql + N') AS x';
            EXEC sp_executesql @sqlCount, N'@cntOut INT OUTPUT', @cntOut = @cnt OUTPUT;
    
            PRINT '  -> ' + CAST(@cnt AS NVARCHAR(20)) + ' registro(s) encontrado(s)';
    
            IF @cnt > 0
                EXEC sp_executesql @sql;
        END
        ELSE
        BEGIN
            PRINT '  -> Sin columnas candidatas (texto/numéricas) en esta tabla';
        END
    
        SET @sql = NULL;
        FETCH NEXT FROM cur INTO @schema, @tabla;
    END
    
    CLOSE cur;
    DEALLOCATE cur;
    DROP TABLE #Tablas;
    DROP TABLE #Columnas;
    

    3. Buscar nombres de campos que contengan un substring. Version claude

    DECLARE @nombre_bd sysname = 'BIB_AY';
    DECLARE @esquema   sysname = 'dbo';
    DECLARE @buscar    nvarchar(100) = '%EXP%';   -- <-- cambia aqui el substring a buscar
    
    DECLARE @sql nvarchar(max) = N'
    SELECT
        s.name  AS esquema,
        t.name  AS tabla,
        c.name  AS columna,
        ty.name AS tipo_dato,
        c.max_length
    FROM ' + QUOTENAME(@nombre_bd) + N'.sys.columns c
    JOIN ' + QUOTENAME(@nombre_bd) + N'.sys.tables  t  ON t.object_id = c.object_id
    JOIN ' + QUOTENAME(@nombre_bd) + N'.sys.schemas s  ON s.schema_id = t.schema_id
    JOIN ' + QUOTENAME(@nombre_bd) + N'.sys.types   ty ON ty.user_type_id = c.user_type_id
    WHERE s.name = @esquema
      AND c.name LIKE @buscar
    ORDER BY t.name, c.name;';
    
    EXEC sp_executesql @sql, N'@esquema sysname, @buscar nvarchar(100)', @esquema, @buscar;