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: Tablas importantes y ficheros

1. FIRMADOC

Esquema DBO

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.FIRM  Guarda las firmas del documento
  • FIR_IDX: id del documento = BIB_AY.IDX_IMP.IM_IDX  
  • 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.IDX (Nombre del documento +  relaciones

  • IDX_ID: Id PK = BIB_AY.IDX_IMP.IM_IDX  
  • 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.SIG (Firma binaria)

  • SIG_ID: Id PK = BIB_AY.FIR.FIR_SIG
  • SIG_CNT Firma binaria

  • 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
    


    2. FACE

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

    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;