Pregunta

Una de las "mejores prácticas" está accediendo a los datos a través de procedimientos almacenados. Entiendo por qué este escenario es bueno. Mi motivación es la base de datos dividida y la lógica de la aplicación (las tablas pueden cambiar, si el comportamiento de los procedimientos almacenados es el mismo), la defensa para la inyección de SQL (los usuarios no pueden ejecutar " seleccionar * de some_tables " ;, solo pueden llamar a los procedimientos almacenados), y seguridad (en el procedimiento almacenado puede ser " cualquier cosa " que sea segura, ese usuario no puede seleccionar / insertar / actualizar / eliminar datos, que no es para ellos).

Lo que no sé es cómo acceder a los datos con filtros dinámicos.

Estoy usando MSSQL 2005.

Si tengo mesa:

CREATE TABLE tblProduct (
   ProductID uniqueidentifier -- PK
   , IDProductType uniqueidentifier -- FK to another table
   , ProductName nvarchar(255) -- name of product
   , ProductCode nvarchar(50) -- code of product for quick search
   , Weight decimal(18,4)
   , Volume decimal(18,4)
)

luego debería crear 4 procedimientos almacenados (crear / leer / actualizar / eliminar).

El procedimiento almacenado para " crear " es fácil.

CREATE PROC Insert_Product ( @ProductID uniqueidentifier, @IDProductType uniqueidentifier, ... etc ... ) AS BEGIN
   INSERT INTO tblProduct ( ProductID, IDProductType, ... etc .. ) VALUES ( @ProductID, @IDProductType, ... etc ... )
END

El procedimiento almacenado para " eliminar " También es fácil.

CREATE PROC Delete_Product ( @ProductID uniqueidentifier, @IDProductType uniqueidentifier, ... etc ... ) AS BEGIN
    DELETE tblProduct WHERE ProductID = @ProductID AND IDProductType = @IDProductType AND ... etc ...
END

El procedimiento almacenado para " actualizar " es similar al de " eliminar " ;, pero no estoy seguro de que esta sea la forma correcta de hacerlo. Creo que actualizar todas las columnas no es eficiente.

CREATE PROC Update_Product( @ProductID uniqueidentifier, @Original_ProductID uniqueidentifier, @IDProductType uniqueidentifier, @Original_IDProductType uniqueidentifier, ... etc ... ) AS BEGIN
   UPDATE tblProduct SET ProductID = @ProductID, IDProductType = @IDProductType, ... etc ...
      WHERE ProductID = @Original_ProductID AND IDProductType = @Original_IDProductType AND ... etc ...
END

Y el último procedimiento almacenado para " leído " Es pequeño misterio para mí. ¿Cómo pasar valores de filtro para condiciones complejas? Tengo una sugerencia:

Usando el parámetro XML para pasar la condición:

CREATE PROC Read_Product ( @WhereCondition XML ) AS BEGIN
    DECLARE @SELECT nvarchar(4000)
    SET @SELECT = 'SELECT ProductID, IDProductType, ProductName, ProductCode, Weight, Volume FROM tblProduct'

    DECLARE @WHERE nvarchar(4000)
    SET @WHERE = dbo.CreateSqlWherecondition( @WhereCondition ) --dbo.CreateSqlWherecondition is some function which returns text with WHERE condition from passed XML

    DECLARE @LEN_SELECT int
    SET @LEN_SELECT = LEN( @SELECT )
    DECLARE @LEN_WHERE int
    SET @LEN_WHERE = LEN( @WHERE )
    DECLARE @LEN_TOTAL int
    SET @LEN_TOTAL = @LEN_SELECT + @LEN_WHERE
    IF @LEN_TOTAL > 4000 BEGIN
        -- RAISE SOME CONCRETE ERROR, BECAUSE DYNAMIC SQL ACCEPTS MAX 4000 chars
    END

    DECLARE @SQL nvarchar(4000)
    SET @SQL = @SELECT + @WHERE

    EXEC sp_execsql @SQL
END

Pero, creo que la limitación de " 4000 " los caracteres para una consulta son feos.

La siguiente sugerencia es usar tablas de filtro para cada columna. Inserte los valores del filtro en la tabla de filtros y luego llame al procedimiento almacenado con el ID de los filtros:

CREATE TABLE tblFilter (
   PKID uniqueidentifier -- PK
   , IDFilter uniqueidentifier -- identification of filter
   , FilterType tinyint -- 0 = ignore, 1 = equals, 2 = not equals, 3 = greater than, etc ...
   , BitValue bit , TinyIntValue tinyint , SmallIntValue smallint, IntValue int
   , BigIntValue bigint, DecimalValue decimal(19,4), NVarCharValue nvarchar(4000)
   , GuidValue uniqueidentifier, etc ... )

CREATE TABLE Read_Product ( @Filter_ProductID uniqueidentifier, @Filter_IDProductType uniqueidentifier, @Filter_ProductName uniqueidentifier, ... etc ... ) AS BEGIN
   SELECT ProductID, IDProductType, ProductName, ProductCode, Weight, Volume
   FROM tblProduct
   WHERE ( @Filter_ProductID IS NULL
            OR ( ( ProductID IN ( SELECT GuidValue FROM tblFilter WHERE IDFilter = @Filter_ProductID AND FilterType = 1 ) AND NOT ( ProductID IN ( SELECT GuidValue FROM tblFilter WHERE IDFilter = @Filter_ProductID AND FilterType = 2 ) )
      AND ( @Filter_IDProductType IS NULL
            OR ( ( IDProductType IN ( SELECT GuidValue FROM tblFilter WHERE IDFilter = @Filter_IDProductType AND FilterType = 1 ) AND NOT ( IDProductType IN ( SELECT GuidValue FROM tblFilter WHERE IDFilter = @Filter_IDProductType AND FilterType = 2 ) )
      AND ( @Filter_ProductName IS NULL OR ( ... etc ... ) ) 
END

Pero esta sugerencia es un poco complicada, creo.

¿Existe alguna " mejor práctica " ¿Para hacer este tipo de procedimientos almacenados?

¿Fue útil?

Solución

Primero: para su rutina de eliminación, su cláusula where solo debe incluir la clave principal.

Segundo: para su rutina de actualización, no intente optimizar antes de tener un código de trabajo. De hecho, no intente optimizar hasta que pueda crear un perfil de su aplicación y ver dónde están los cuellos de botella. Puedo asegurarle que la actualización de una columna de una fila y la actualización de todas las columnas de una fila tienen una velocidad casi idéntica. Lo que lleva tiempo en un DBMS es (1) encontrar el bloque de disco donde escribirá los datos y (2) bloquear a otros escritores para que su escritura sea coherente. Finalmente, escribir el código necesario para actualizar solo las columnas que necesitan cambiar será generalmente más difícil de hacer y más difícil de mantener. Si realmente quisieras ser exigente, tendrías que comparar la velocidad de averiguar qué columnas cambiaron en comparación con solo actualizar cada columna. Si los actualiza a todos, no tiene que leer ninguno de ellos.

Tercero: tiendo a escribir un procedimiento almacenado para cada ruta de recuperación. En su ejemplo, haría una por clave principal, una por cada clave externa y luego agregaría una para cada nueva ruta de acceso a medida que las necesitaba en la aplicación. Se ágil No escriba el código que no necesita. También estoy de acuerdo con el uso de vistas en lugar de procedimientos almacenados, sin embargo, puede usar un procedimiento almacenado para devolver varios conjuntos de resultados (en algunas versiones de MSSQL) o para cambiar filas en columnas, lo que puede ser útil.

Si necesita obtener, por ejemplo, 7 filas por clave principal, tiene algunas opciones. Puede llamar al procedimiento almacenado que obtiene una fila por clave principal siete veces. Esto puede ser lo suficientemente rápido si mantiene abierta la conexión entre todas las llamadas. Si sabe que nunca necesita más de un cierto número (por ejemplo, 10) de ID a la vez, puede escribir un procedimiento almacenado que incluya una cláusula donde como "e ID" (arg1, arg2, arg3 ...) " y asegúrese de que los argumentos no utilizados se establecen en NULL. Si decide que necesita generar SQL dinámico, no me molestaría con un procedimiento almacenado porque TSQL es tan fácil de cometer un error como cualquier otro idioma. Además, no obtiene ningún beneficio al utilizar la base de datos para realizar la manipulación de cadenas: casi siempre es su cuello de botella, por lo que no tiene sentido darle a la base de datos más trabajo del necesario.

Otros consejos

Para leer datos, no necesita un procedimiento almacenado por seguridad o para separar la lógica, puede usar vistas.

Solo otorga solo seleccionar en la vista.

Puede limitar los registros que se muestran, cambiar los nombres de los campos, unir muchas tablas en una lógica "tabla de cotización", etc.

No estoy de acuerdo con que la creación de Insertar / Actualizar / Seleccionar procedimientos almacenados sea una "mejor práctica". A menos que su aplicación completa esté escrita en SP, use una capa de base de datos en su aplicación para manejar estas actividades CRUD. Mejor aún, use una tecnología ORM para manejarlos por usted.

Mi sugerencia es que no intentes crear un procedimiento almacenado que haga todo lo que podrías o deberías hacer ahora. Si necesita recuperar una fila basada en la clave principal de la tabla, escriba un procedimiento almacenado para hacerlo. Si necesita buscar todas las filas que cumplan con un conjunto de criterios, averigüe cuáles podrían ser esos criterios y escriba un procedimiento almacenado para hacerlo.

Si intenta escribir un software que resuelva todos los problemas posibles en lugar de un conjunto específico de problemas, generalmente fallará al proporcionar algo útil.

su procedimiento almacenado de selección se puede hacer de la siguiente manera para requerir solo un proceso almacenado pero cualquier número de elementos diferentes en la cláusula where. Pase cualquiera o una combinación de los parámetros y obtendrá TODOS los elementos que coincidan, por lo que solo necesita un procedimiento almacenado.

Create sp_ProductSelect
(
 @ProductID int = null,
 @IDProductType int = null,
 @ProductName varchar(50) = null,
 @ProductCode varchar(10) = null,
 ...
 @Volume int = null
)
AS
SELECT ProductID, IDProductType, ProductName, ProductCode, Weight, Volume FROM tblProduct'  
Where
  ((@ProductID is null) or (ProductID = @ProductID)) AND
  ((@ProductName is null) or (ProductName = @ProductName)) AND
  ...
  ((@Volume is null) or (Volume= @Volume))

En SQL 2005, admite nvarchar (max), que tiene un límite de 2G, pero acepta prácticamente todas las operaciones de cadena sobre nvarchar normal. Es posible que desee probar si esto puede encajar en lo que necesita en el primer enfoque.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top