¿Cómo puedo asignar una variable con una declaración preparada en un procedimiento almacenado?

He creado un procedimiento almacenado simple en el que se pasan dos parámetros para hacerlo más dinámico. Hice esto con una declaración preparada en la sección "Primeros dos dígitos y recuento de registros".

De lo que no estoy seguro es si puedo hacer el SET vTotalFT sección dinámica con una declaración preparada también.

Por el momento tengo que codificar los nombres y campos de las tablas. Quiero mi vTotalFT variable que se asignará en función de una instrucción SQL dinámica preparada, pero no estoy seguro de la sintaxis. La idea es que cuando llame a mi procedimiento, podría decirle qué tabla y qué campo usar para el análisis.

CREATE PROCEDURE `sp_benfords_ft_digits_analysis`(vTable varchar(255), vField varchar(255))
    SQL SECURITY INVOKER
BEGIN

    -- Variables
    DECLARE vTotalFT int(11);

    -- Removes existing table
    DROP TABLE IF EXISTS analysis_benfords_ft_digits;

    -- Builds base analysis table
    CREATE TABLE analysis_benfords_ft_digits
    (
        ID int(11) NOT NULL AUTO_INCREMENT,
        FT_Digits int(11),
        Count_of_Records int(11),
        Actual decimal(18,3),
        Benfords    decimal(18,3),
        Difference Decimal(18,3),
        AbsDiff decimal(18,3),
        Zstat decimal(18,3),
        PRIMARY KEY (ID),
        KEY id_id (ID)
    );

    -- First Two Digits and Count of Records
    SET @s = concat('INSERT INTO analysis_benfords_ft_digits
                        (FT_Digits,Count_of_Records)
                            select substring(cast(',vField,' as char(50)),1,2) as FT_Digits, count(*) as Count_of_Records
                            from ',vTable,'
                            where ',vField,' >= 10
                            group by 1');

    prepare stmt from @s;
    execute stmt;
    deallocate prepare stmt;

    SET vTotalFT = (select sum(Count_of_Records) from
                            (select substring(cast(Gross_Amount as char(50)),1,2) as FT_Digits, count(*) as Count_of_Records
                                from supplier_invoice_headers
                                where Gross_Amount >= 10
                                group by 1) a);


    -- Actual
    UPDATE analysis_benfords_ft_digits
    SET Actual = Count_of_Records / vTotalFT;

    -- Benfords
    UPDATE analysis_benfords_ft_digits
    SET Benfords = Log(1 + (1 / FT_Digits)) / Log(10);

    -- Difference
    UPDATE analysis_benfords_ft_digits
    SET Difference = Actual - Benfords;

    -- AbsDiff
    UPDATE analysis_benfords_ft_digits
    SET AbsDiff = abs(Difference);

    -- ZStat
    UPDATE analysis_benfords_ft_digits
    SET ZStat = cast((ABS(Actual-Benfords)-IF((1/(2*vTotalFT))<ABS(Actual-Benfords),(1/(2*vTotalFT)),0))/(SQRT(Benfords*(1-Benfords)/vTotalFT)) as decimal(18,3));

preguntado el 27 de julio de 12 a las 17:07

2 Respuestas

Primero, para usar nombres dinámicos de tablas/columnas, deberá usar una cadena/Declaración preparada como tu primera consulta para @s. A continuación, para obtener el valor de retorno de COUNT() dentro de la consulta necesitarás usar SELECT .. INTO @vTotalFT.

Lo siguiente debería ser todo lo que necesita:

SET @vTotalFTquery = CONCAT('(select sum(Count_of_Records) INTO @vTotalFT from
                        (select substring(cast(', vField, ' as char(50)),1,2) as FT_Digits, count(*) as Count_of_Records
                            from ', vTable, '
                            where ', vField, ' >= 10
                            group by 1) a);');
PREPARE stmt FROM @vTotalFTquery;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

Tenga en cuenta: el nombre de la variable ha cambiado de vTotalFT a @vTotalFT. No parece funcionar sin el @. Y también la variable @vTotalFT no funcionará cuando se declare fuera de/antes de la consulta, por lo que si encuentra un error o resultados vacíos, esa podría ser la causa.

Respondido 27 Jul 12, 18:07

Gracias por esto. Probaré esto el lunes. Salud - user1236443

Hay un problema grave aquí, el objetivo de usar una declaración preparada es desinfectar la entrada del usuario, pero está concatenando ciegamente vTable y vField en la declaración de selección. Esta respuesta está abierta a la inyección de SQL. - codificador

@Coderer Si bien estoy de acuerdo con su declaración en general, creo que ha leído mal la intención de la pregunta/respuesta. La pregunta es cómo insertar dinámicamente nombres de tablas y columnas, los cuales no se pueden reemplazar a través de declaraciones preparadas. Dicho esto, mi respuesta está dirigida al "cómo hacerlo" del OP, no a una respuesta secundaria de "esta es una lista de pasos para desinfectar adecuadamente los nombres de tablas/columnas directamente en MySQL". Si cree que mi respuesta sigue siendo incorrecta, siéntase libre de editarla para hacerla más apropiada o agregue su propia respuesta para ilustrar su punto =] - muebles nuevos

Ese es un buen punto; desafortunadamente, no sé cómo podrías hacer esto de manera segura. Lo más cercano que se me ocurre es limitar los parámetros de entrada a la menor cantidad de caracteres posible, para reducir la cantidad de código que un atacante podría inyectar. Si todas las columnas y los nombres de las tablas tienen, digamos, 10 caracteres, por ejemplo, eso es probablemente (bastante) "seguro". - codificador

@Coderer No es quisquilloso, pero le ha dado a mi respuesta un -1 porque cree que "no es útil" responder la pregunta que se le hizo. Su razonamiento para esto se basa en un tema secundario que, usted mismo, no puede responder de inmediato. Cuando vote a favor o en contra de las respuestas en el futuro, considere la pregunta real que se le hace en su razonamiento, no solo sus requisitos personales. En la misma nota, si cree que se requiere información adicional para una respuesta (como la mía arriba), siempre siéntase libre de dejar un comentario solicitándolo. - muebles nuevos

SELECT CONCAT (
        'SELECT DATE(PunchDateTime) as day , '
        ,GROUP_CONCAT('GROUP_CONCAT(IF(PunchEvent=', QUOTE(PunchEvent), ',PunchDateTime,NULL))
       AS `', REPLACE(PunchEvent, '`', '``'), '`')
        ,'
         FROM     tbl_punch
         GROUP BY DATE(PunchDateTime) 
         ORDER BY PunchDateTime ASC
       '
        )
INTO @sql
FROM (
    SELECT DISTINCT PunchEvent
    FROM tbl_punch
    ) t;

PREPARE stmt
FROM @sql;

EXECUTE stmt;

DEALLOCATE PREPARE stmt;

Respondido 27 ago 16, 11:08

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.