25 oct 2014

WHERE condicional

Las consultas a las base de datos son bastante poderosas, pero en algunos casos nos pueden parecer incomodas e incluso frustrantes. Uno de los casos  más comunes es donde necesitamos hacer una consulta donde dado un parámetro si este viene nulo devolver todos los registros pero si trae algún valor hay que devolver los registros filtrados por ese valor, básicamente un filtro condicional.

Luego de varias aproximaciones más o menos eficaces, encontré la que, posiblemente, sea la forma más eficiente de hacer esto sin dividir la consulta en dos (o en 'n' dependiendo de las condiciones) consultas.

Suponiendo que tengo una tabla de productos y que el store procedure recibe un código de categoría para filtrar los resultados, pero si este viene nulo retorna el listado completo de todos los productos, el resultado, usando SQL SERVER 2014,  seria el siguiente:
SELECT P.(...) FROM Producto P
WHERE (1 =  IIF(@pcocCategoria IS NULL, 1, 0) 
OR P.cocCategoria = @pcocCategoria)
En caso de no contar con la instrucción IIF de SQL Server 2014, entonces nos las arreglamos con un CASE:
SELECT P.(...)  FROM Producto P
WHERE (1 = (CASE WHEN @pcocCategoria IS NULL THEN 1 ELSE 0 END) 
OR P.cocCategoria = @pcocCategoria)
En otros caso podría ser dadas una fecha desde y fecha hasta como parámetros necesitemos retornar los registros en ese  rango pero si vienen nulas retornar todos los registros. Entonces utilizaríamos algo similar a esto:
SELECT P.(...)  FROM Producto P
WHERE (1=IIF(@fecDesde IS NULL, 1, 0) OR (P.fecIngreso >= @fecDesde))
AND (1=IIF(@fecHasta IS NULL, 1, 0) OR (P.fecIngreso <= @fecHasta))
Es siempre importante dejar claro que todo este tipo de "workarounds" tiene sus penalizaciones de rendimiento y se debería utilizar únicamente si no existiese alternativas. Siempre es mejor buscar consultas directas, procurando usar los campos índices y en el orden correcto, pero si no queda alternativa aquí queda este tip.

25 sept 2014

Insertando, Actualizando y ¿el histórico?

En el post anterior traté de explicar como utilizar la instrucción MERGE para realizar tanto la inserción como la modificación e incluso la eliminación en una sola instrucción/estructura relacional.

Ahora, continuando con el mismo ejemplo, que sucedería si nos piden registrar un histórico de los cambios de existencias…

Pues bien la misma estructura MERGE nos proporciona un mecanismo mediante el cual tomar la salida de la misma, con los registros insertados y modificados, e insertarlos en otra tabla.

Comencemos con crear una estructura mediante la cual guardar nuestro histórico de existencias:
CREATE TABLE ProductoHistorico
(
    conProductoHistorico INT NOT NULL IDENTITY (1,1), 
    conProducto INT NOT NULL 
        CONSTRAINT ProductoHsitorico_Producto_FK FOREIGN KEY
        REFERENCES Producto(conProducto),
    numCantidadAnterior INT NULL,
    numCantidadNueva INT NULL
        CONSTRAINT ProductoHistorico_numCantidadNueva_DF DEFAULT (0),
    fecModificacion DATETIME NOT NULL 
        CONSTRAINT ProductoHistorico_fecModificacion DEFAULT (GETDATE()),
    usrModifico VARCHAR(10) NOT NULL
    CONSTRAINT ProductoHistorico_PK
        PRIMARY KEY CLUSTERED (conProductoHistorico)
        WITH (IGNORE_DUP_KEY = OFF)
);
Es simple, tenemos un consecutivo, una llave foránea con la tabla de productos, la cantidad anterior, la cantidad nueva, la fecha y el usuario que realizó la modificación.
Intentamos modificar el procedimiento almacenado de esta manera:

CREATE PROCEDURE spCargaProductosHistorico(@pusrModifico varchar(10))
AS
  SET NOCOUNT ON;
  BEGIN TRANSACTION
  BEGIN TRY     

    INSERT INTO ProductoHistorico
    (conProducto,numCantidadAnterior, numCantidadNueva,fecModificacion, usrModifico)
    SELECT conProducto,numCantidadAnterior, numCantidad,fecModificacion, usrModifico 
    FROM
        (MERGE Producto WITH (HOLDLOCK) AS TGT
            USING (SELECT PC.cocProducto, desProducto, numCantidad
                FROM productoCarga PC WITH (HOLDLOCK)) 
                AS SRC (cocProducto, desProducto, numCantidad)
            ON (TGT.cocProducto = SRC.cocProducto)
            WHEN MATCHED THEN 
                UPDATE SET TGT.desProducto = src.desProducto,
                    TGT.numCantidad = (TGT.numCantidad + SRC.numCantidad)
            WHEN NOT MATCHED THEN
                INSERT (cocProducto, desProducto, numCantidad, fecIngreso)
                VALUES (SRC.cocProducto, SRC.desProducto, SRC.numCantidad, GETDATE())
            OUTPUT INSERTED.conProducto, COALESCE(DELETED.numCantidad,0) AS numCantidadAnterior, 
            INSERTED.numCantidad, GETDATE() as fecModificacion, @pusrModifico AS usrModifico)
            AS HIST (conProducto, numCantidadAnterior,numCantidad,fecModificacion, usrModifico);

        TRUNCATE TABLE productoCarga
  END TRY
  BEGIN CATCH
  SELECT 
        ERROR_NUMBER() AS ErrorNumber
        ,ERROR_SEVERITY() AS ErrorSeverity
        ,ERROR_STATE() AS ErrorState
        ,ERROR_PROCEDURE() AS ErrorProcedure
        ,ERROR_LINE() AS ErrorLine
        ,ERROR_MESSAGE() AS ErrorMessage;

  IF @@TRANCOUNT > 0
    ROLLBACK TRANSACTION;
  END CATCH;
IF @@TRANCOUNT > 0
  COMMIT TRANSACTION;
GO
Expliquemos un poco: la cláusula OUTPUT nos devuelve dos tablas virtuales: INSERTED y DELETED, las mismas que están disponibles en los triggers, la primera corresponde con las filas insertadas (o a las modificadas con los nuevos valores) y la segunda a los valores eliminados o reemplazados. Además, no lo utilizamos en este caso, pero también devuelve un valor especial en la variable $Action que puede contener uno de los siguientes valores INSERT, UPDATE, DELETE, en función de la acción realizada en cada fila.  Ahora bien, utilizamos estas salidas de la cláusula OUTPUT para insertarlas en la tabla ProductoHistorico por medio de un INSERT SELECT FROM.

Pero hay un problema, si tratamos de compilar nuestro procedimiento almacenado nos cuenta que se nos presenta un error.

The target table 'ProductoHistorico' of the INSERT statement cannot be on either side of a (primary key, foreign key) relationship when the FROM clause contains a nested INSERT, UPDATE, DELETE, or MERGE statement. Found reference constraint 'ProductoHsitorico_Producto_FK'.

El mensaje es fácil de entender: no podemos poner una tabla como receptora de los datos de un MERGE si tiene una Foreign Key (FK, llave Foránea) con la tabla destino del MERGE. El hecho de que sea fácil de entender no significa que nos hayamos percatado del problema antes.

Ahora tenemos varias opciones: una sería eliminar la FK con la tabla de productos, pero perderíamos la integridad referencial, que puede que no ser malo, de hecho puede ser necesario si queremos guardar las eliminaciones físicas de los productos. Otra sería, aparte de la eliminación  de la COSNTRAINT de la FK, eliminar el campo conProducto propiamente de la tabla de histórico y añadir el código de producto sin FK, que al fin y al cabo es el que verdaderamente importaría en una tabla de históricos; esta parece ser la más acertada y conveniente en un escenario real. Sin embargo hay otra opción, que para fines didácticos, es la que voy implementar: guardar el resultado del MERGE en una variable tipo tabla y luego insertarla finalmente en la histórica.

Veamos como quería esta segunda aproximación de nuestro procedimiento almacenado
CREATE PROCEDURE spCargaProductosHistorico(@pusrModifico varchar(10))
AS
  SET NOCOUNT ON;
  BEGIN TRANSACTION
  BEGIN TRY    

  DECLARE @TablaTemp Table (
    conProducto INT NOT NULL,
    numCantidadAnterior INT NULL,
    numCantidadNueva INT NULL DEFAULT (0),
    fecModificacion DATETIME NOT NULL DEFAULT (GETDATE()),
    usrModifico VARCHAR(10) NOT NULL
  )

    INSERT INTO @TablaTemp
    (conProducto,numCantidadAnterior, numCantidadNueva,fecModificacion, usrModifico)
    SELECT conProducto,numCantidadAnterior, numCantidad,fecModificacion, usrModifico 
    FROM
        (MERGE Producto WITH (HOLDLOCK) AS TGT
            USING (SELECT PC.cocProducto, desProducto, numCantidad
                FROM productoCarga PC WITH (HOLDLOCK)) 
                AS SRC (cocProducto, desProducto, numCantidad)
            ON (TGT.cocProducto = SRC.cocProducto)
            WHEN MATCHED THEN 
                UPDATE SET TGT.desProducto = src.desProducto,
                    TGT.numCantidad = (TGT.numCantidad + SRC.numCantidad)
            WHEN NOT MATCHED THEN
                INSERT (cocProducto, desProducto, numCantidad, fecIngreso)
                VALUES (SRC.cocProducto, SRC.desProducto, SRC.numCantidad, GETDATE() )
            OUTPUT INSERTED.conProducto, COALESCE(DELETED.numCantidad,0) AS numCantidadAnterior, 
            INSERTED.numCantidad, GETDATE() as fecModificacion, @pusrModifico AS usrModifico)
            AS HIST (conProducto, numCantidadAnterior,numCantidad,fecModificacion, usrModifico);

            INSERT INTO ProductoHistorico
            (conProducto,numCantidadAnterior, numCantidadNueva, fecModificacion, usrModifico)
            SELECT conProducto,numCantidadAnterior, numCantidadNueva,fecModificacion, usrModifico
            FROM @TablaTemp

        TRUNCATE TABLE productoCarga
  END TRY
  BEGIN CATCH
  SELECT 
        ERROR_NUMBER() AS ErrorNumber
        ,ERROR_SEVERITY() AS ErrorSeverity
        ,ERROR_STATE() AS ErrorState
        ,ERROR_PROCEDURE() AS ErrorProcedure
        ,ERROR_LINE() AS ErrorLine
        ,ERROR_MESSAGE() AS ErrorMessage;

  IF @@TRANCOUNT > 0
    ROLLBACK TRANSACTION;
  END CATCH;
IF @@TRANCOUNT > 0
  COMMIT TRANSACTION;
GO
Simplemente “vaciamos” el resultado del MERGE en la variable tabla y luego está en la tabla ProductoHistorico. Ahora si nos compila.

Y para probarlo
INSERT INTO ProductoCarga
([cocProducto], [desProducto],[numCantidad])
VALUES ('AA001','Produto de Prueba 1',33),
('AA002','Produto de Prueba 2',17),
('AA003','Produto de Prueba 3',21),
('AA004','Produto de Prueba 4',33),
('AA005','Produto de Prueba 5',44),
('AA006','Produto de Prueba 6',20),
('AA007','Produto de Prueba 7',20),
('AA008','Produto de Prueba 8',60),
('AA009','Produto de Prueba 9',80)

SELECT * FROM ProductoCarga
SELECT  * FROM Producto

--EXECUTE spCargaProductos
DECLARE @pusrModifico VARCHAR(10)='FOY'
EXECUTE spCargaProductosHistorico @pusrModifico

SELECT * FROM ProductoCarga
SELECT * FROM Producto
SELECT * FROM ProductoHistorico

Todo funciona de manera adecuada
Igual que en el post anterior señalar que la cláusula MERGE no es la panacea ni la pomada canaria, recalcar que es necesario probar nuestros escenarios para no tener sobresaltos en nuestras puestas en producción, principalmente cuando haya triggers de pormedio.

Clausula MERGE

Problemas de la clausula MERGE

20 sept 2014

¿Insertando o Actualizando? ¡Las dos cosas a la vez!

A nivel de base de datos, (SQL Server específicamente en este caso), muchas veces necesitamos hacer una u otra operación, dependiendo de algún criterio. Uno de los casos más comunes es determinar si un registro existe para actualizarlo o en caso de que no exista insertarlo. Lo cual normalmente lo hacemos con instrucciones de control tipo IF…ELSE:
IF EXISTS (SELECT 1 FROM miTabla WHERE llave = ...)
BEGIN
    UPDATE miTabla SET ...
END
ELSE BEGIN
    INSERT INTO miTabla ...
END
Todo esto es muy de programación estructurada, y recordemos que la base de datos, SQL Server, es relacional, son paradigmas diferentes. Ahora, ¿Habrá una instrucción o estructura en T-SQL que ayude con este problema? Sí, la hay: MERGE.

La estructura MERGE, introducida en el estándar de SQL:2003 (el estándar, no el producto de MS), y por Microsoft en SQL Server 2008 (aunque con varios problemas) nos ayuda precisamente a atacar relacionalmente este tipo de problemas. 

Planteemos un caso sencillo. Primero aclarar que estoy trabajando con SQL SERVER 2014. Supongamos que tenemos una sencilla tabla de productos, y que cada cierto tiempo se reabastecen las existencias, pero en este proceso de carga de reabastecimiento pueden venir tanto productos existentes como totalmente nuevos. El requisito a cumplir es que si al cargar el producto éste ya existe se actualice la descripción del mismo así como la cantidad existente, sumando a la cantidad en el producto la cantidad a reabastecer y, si no existe, se inserte con la cantidad indicada en la carga. La forma para saber si el producto existe es por comparación del código del mismo.  Simple.

Atacamos el problema, en primera instancia, cargando los reabastecimientos en una tabla de carga. Esto se puede realizar por medio de un bulk copy explicado en este post.
Las dos tablas necesarias las definimos de la siguiente manera:

CREATE TABLE Producto
(
    conProducto INT NOT NULL IDENTITY (1,1), 
    cocProducto VARCHAR(5) NOT NULL 
        CONSTRAINT Producto_cocProducto_UK UNIQUE,
    desProducto VARCHAR(150),
    numCantidad INT NOT NULL 
        CONSTRAINT Producto_numCantidad_DF DEFAULT (0),
    fecIngreso DATETIME NOT NULL 
        CONSTRAINT Producto_fecingreso_DF DEFAULT (getdate()),
    CONSTRAINT Producto_PK
        PRIMARY KEY CLUSTERED (conProducto)
        WITH (IGNORE_DUP_KEY = OFF)
);

CREATE TABLE ProductoCarga
(
    cocProducto VARCHAR(5) NOT NULL,
    desProducto VARCHAR(150),
    numCantidad INT NOT NULL 
        CONSTRAINT ProductoCarga_numCantidad_DF DEFAULT (0)
);
Así tenemos nuestra tabla Producto donde guardamos la información de existencias, la tabla ProductoCarga donde guardamos de forma temporal nuestros reabastecimientos hasta que sean procesados, esta última tiene las restricciones mínimas para que el bulk copy sea lo más rápido posible.

 Ahora necesitamos un procedimiento almacenado que tome los datos de ProductoCarga y le aplique la lógica requerida para pasarlos a Producto y que además limpie la tabla de carga para dejarla lista para el próximo reabastecimiento. Sería algo similar a esto:

CREATE PROCEDURE spCargaProductos
AS
    SET NOCOUNT ON;
    BEGIN TRANSACTION
    BEGIN TRY
       MERGE Producto WITH (HOLDLOCK) AS TGT
           USING (SELECT PC.cocProducto, desProducto, numCantidad
              FROM productoCarga PC WITH (HOLDLOCK)) 
              AS SRC (cocProducto, desProducto, numCantidad)
           ON (TGT.cocProducto = SRC.cocProducto)
           WHEN MATCHED THEN 
              UPDATE SET TGT.desProducto = SRC.desProducto,
                  TGT.numCantidad = (TGT.numCantidad + SRC.numCantidad)
           WHEN NOT MATCHED THEN
              INSERT (cocProducto, desProducto, numCantidad, fecIngreso)
              VALUES (SRC.cocProducto, SRC.desProducto, SRC.numCantidad, 
              getdate());
           TRUNCATE TABLE productoCarga
    END TRY
    BEGIN CATCH
    SELECT 
        ERROR_NUMBER() AS ErrorNumber
        ,ERROR_SEVERITY() AS ErrorSeverity
        ,ERROR_STATE() AS ErrorState
        ,ERROR_PROCEDURE() AS ErrorProcedure
        ,ERROR_LINE() AS ErrorLine
        ,ERROR_MESSAGE() AS ErrorMessage;

    IF @@TRANCOUNT > 0
        ROLLBACK TRANSACTION;
    END CATCH;
IF @@TRANCOUNT > 0
    COMMIT TRANSACTION;
GO    
Analicemos un poco la estructura. 

Primero usamos la palabra reservada MERGE para indicar que vamos a hacer varias operaciones, a continuación viene nuestra tabla destino, o sea, donde vamos a realizar las inserciones o actualizaciones (como es en este ejemplo, también se puede eliminar si fuese necesario) aquí ponemos nuestra tabla Producto y lo decoramos con WITH (HOLDLOCK) para asegurar el lock sobre la tabla mientras estemos trabajando sobre la misma, esto es un seguro en caso de concurrencia, y le asignamos un alias (yo elegí TGT de “Target”). 

Seguidamente usamos USING para indicar de donde tomaremos los datos, cuál es su origen. Esto es muy similar a una CTE (Common Table Expression) por lo que una vez definido nuestra consulta sobre la tabla de ProductoCarga le asignamos un alias (yo elegí SRC de “Source”) que debe incluir los nombres de las columnas tal y como las vamos a referenciar en lo subsiguiente.

En la sentencia ON, debemos especificar el criterio con el que determinamos si los registros del origen corresponden con los del destino, en nuestro caso si el código del producto de ambas tablas es el mismo.

La magia viene a continuación. Con WHEN MATCHED THEN, decimos cuando el criterio especificado anteriormente se cumpla realice la operación siguiente y WHEN NOT MACHED THEN, como ya habremos deducido, significa que si el  criterio no se cumplió realice lo que sigue a continuación. En el primer caso aplicamos el UPDATE y en el segundo el INSERT, en sendos, como se habrá notado, no se incluye la tabla porque para eso la indicamos con la cláusula MERGE. La parte MATCHED es bastante  flexible y puede contener mas lógica, por ejemplo se puede indicar que además de cumplir el criterio del la sentencia ON si la cantidad es mayor a tal número actualice de una manera y otra sentencia MATCHED para que en caso de que sea menor o igual realice la actualización de otra manera

Finalmente la instrucción debe terminar con un punto y coma (;) o  nos dará el error 10713. Ademas no se debe olvidar limpiar la tabla de carga, lo cual con un TRUNCATE es mas rápido.

Aunque parezca obvio es necesario tomar en cuenta que el usuario que realice la operación debe tener permisos de consulta sobre la tabla o las tablas de origen y de UPDATE, INSERT o DELETE sobre la tabla destino según se necesite.

Realicemos un par de pruebas, primero insertamos unos registros en la tabla de carga, luego seleccionemos la misma tabla y la tabla de productos, seguido ejecutemos el procedimiento almacenado y a continuación de nuevo las dos consultas para verificar que todo se haya llevado a cabo.

INSERT INTO ProductoCarga
(cocProducto, desProducto,numCantidad)
VALUES ('AA001','Produto de Prueba 1',100),
('AA002','Produto de Prueba 2', 50),
('AA003','Produto de Prueba 3', 10)

SELECT * FROM ProductoCarga
SELECT  * FROM Producto

EXECUTE spCargaProductos

SELECT * FROM ProductoCarga
SELECT * FROM Producto


Bien, tenemos tres productos, ahora añadiremos dos a la carga. Esta vez se debería modificar los primeros tres productos e insertar los últimos.

INSERT INTO ProductoCarga
(cocProducto, desProducto,numCantidad)
VALUES ('AA001','Producto de Prueba 1',15),
('AA002','Producto de Prueba 2',8),
('AA003','Producto de Prueba 3',5),
('AA004','Producto de Prueba 4',10),
('AA005','Producto de Prueba 5',40)

SELECT * FROM ProductoCarga
SELECT  * FROM Producto

EXECUTE spCargaProductos

SELECT * FROM ProductoCarga
SELECT * FROM Producto

¡Correcto! Todo funciona tal cual debe. Sin embargo es importante señalar un par de cosas antes de saltar haciendo piruetas gritando ¡esto era lo que necesitaba! En las versiones de sql server 2008, principalmente, y 2012 la instrucción MERGE tiene algunos problemas e incluso se recomendó en su momento mejor no usarla y mantener la versión estructurada. Estos problemas tenían que ver con deadlocks, concurrencia y triggers. En la versión 2014 se estabilizó, pero aun así es necesario probar diferentes escenarios principalmente los que tienen que ver con los triggers, para asegurarnos de no toparnos con comportamientos indeseados y/o tener que reversar nuestra implementación debido a inconvenientes insalvables.

Finalmente dejo algos recursos de interés

Documentación

https://msdn.microsoft.com/es-es/library/bb510625(v=sql.120).aspx

Problemas

https://www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement/


3 ago 2014

SOLID: El desarrollo de Software no es juego de Jenga

Dentro de nuestro afán, tesonero y, ¿por que no?, ambicioso de mejorar lo que hacemos, lo que nos gusta hacer, debemos buscar constantemente herramientas, formas, maneras, guías que nos muestren caminos nuevos; incluso, que nos muestren la formas correctas de denominar los caminos que ya transitamos pero que, quizás, no sabemos precisar adecuadamente. Fue de esta manera que de pronto me hallé frente a los principios SOLID.

SOLID, es un acrónimo introducido por Robert Cecil Martin (conocido como “uncle Bob” firmante del Agile Manifiesto) refiriéndose a cinco principios básicos del Diseño Orientado a Objetos (OOD por sus siglas en inglés). Su objetivo es que, al aplicarse combinada y adecuadamente, estos cinco principios permitan la creación de sistemas robustos, de fácil mantenimiento y extensibles. Proveen una guía para que los desarrolladores eviten problemas en su código, refactorizando el mismo, y permitiendo una mejor integración dentro desarrollos ágiles de software.

De una manera muy básica paso a definir el significado de cada letra dentro del acrónimo.

Principios SOLID:

SRP -Single Resposability Principle-:
Una clase debe ser responsable de una única funcionalidad. “Divide y vencerás” de manera que la funcionalidad debe estar divida en las más pequeñas responsabilidades que podamos implementar.

Open/Close Principle:
Las entidades del software deben ser abiertas a la extensibilidad pero cerradas a las modificaciones.

LSP -Liskov Substitution Principle-:
Objetos deben poder ser reemplazables con instancias de su SubTipo sin afectar la exactitud del programa. Cada clase que hereda de otra puede usarse como su padre sin necesidad de conocer las diferencias entre ellas.

ISP -Interface Segregation Principle-:
Muchas interfaces específicas son mejores que una grande de propósito general. Esto por cuando necesitemos clases que implemente interfaces, lo deseable es que el  propósito de los mismos sea claro, definido y acotado, lo cual no es posible con mega interfaces, si no, con pequeñas interfaces.

DIP -Dependecy Inversion Principle-:
Se debe depender de abstracciones no de concreciones. Es el principio básico tras la inyección de dependencias en los desarrollados "modernos".

Un muy buen ejemplo del uso de los principios SOLID  se encuentra en el post en inclés S.O.L.I.D: The First 5 Principles of Object Oriented Design

La idea es que la utilización de estos principios se interioricen dentro de nuestra cultura de desarrollo de software, formando parte de nuestra filosofía al enfrentar nuevas implementaciones. Muy especialmente puede ir acompañado de una estrategia de desarrollo dirigido por pruebas (TDD Test Driven Development).



27 jul 2014

Todas las FK de una Tabla (Versión Actualizada)

En un post ya viejo habíamos señalado como listar las llaves foráneas de una tabla. En esta nueva versión, igualmente cortesía del Maese Norberto Mesén listamos las tablas hija, las columna hija, las tablas referenciada y las columnas referenciada más el nombre del constraint de la FK y el id de cada columna en su respectiva tabla.
USE MiBaseDeDatos;

SELECT
    fk.name 'FK',
    tp.name 'Tabla Hija',
    cp.name AS 'Columna hija', 
    cp.column_id,
    tr.name 'Tabla refenciada',
    cr.name 'Columna referenciada', 
    cr.column_id
FROM sys.foreign_keys fk
INNER JOIN sys.tables tp 
    ON fk.parent_object_id = tp.object_id
INNER JOIN sys.tables tr 
    ON fk.referenced_object_id = tr.object_id
INNER JOIN sys.foreign_key_columns fkc 
    ON fkc.constraint_object_id = fk.object_id
INNER JOIN sys.columns cp 
    ON fkc.parent_column_id = cp.column_id 
    AND fkc.parent_object_id = cp.object_id
INNER JOIN sys.columns cr 
    ON fkc.referenced_column_id = cr.column_id 
    AND fkc.referenced_object_id = cr.object_id
ORDER BY tp.name, cp.column_id
Muy útil!

29 jun 2014

Ubicando y Matando Procesos en SQL Server

Una consulta para saber los procesos que están ejecutando ejecutando en nuestra base de datos:


DECLARE @BaseDeDatos Varchar(30) = 'Nombredenuestrabasededatos'
SELECT spid, kpid, blocked,status, db_name(dbid) AS DatabaseName, 
	loginame, hostname, program_name, cmd, waittime
FROM sys.sysprocesses
WHERE db_name(dbid) = @BaseDeDatos
ORDER BY hostname
LA información que nos da son el id de la sesión de SQL Server, el id del hilo de ejecución de windows, id de la sesión que esta bloqueando la consulta, el estado actual de nuestra consulta, el nombre de la base de datos sobre la que opera nuestra consulta, el usuario(el login de base de datos) que ejecuta la consulta, el nombre de la máquina desde donde se realiza la consulta, programa o servicio desde de donde se realizó la consulta, tipo de consulta o comando que se esta ejecutando (AWAITING COMMAND significa que no esta ejecutando ninguna consulta) y el tiempo que lleva la instrucción en ejecutándose o esperando ejecutarse (en milisegundos) Una vez identificado el id de la session que esta bloqueando los demás se puede eliminar (matar) el proceso mediante el comando Kill y el numero de sesión por ejemplo:
Kill 66
Muy útil cuando alguna transacción queda abierta y nuestra base de datos que bloqueada.

8 jun 2014

PDFs y Kindle

Hace muy poco adquirí una Kindle Paperwhite. Cuento con una pequeña colección de ebooks que deseaba leer de forma más cómoda. Sin embargo mis ebooks se encuentran en formato pdf.

Supuse, en primera instancia, que sería tan simple como conectar la kindle a la pc, pasar los pdf y recostarme cómodamente a leerlos. Estaba algo errado, no es tan simple. La kindle pierde gran parte de sus atractivas funcionalidades al utilizarse con pdfs, el cambio de tamaño de la fuente, la búsqueda de palabras, etc... básicamente trata cada página del archivo como si fuese una imagen, Esto no es del todo malo, en algunos casos incluso puede ser conveniente, sobretodo cuando se incluye imágenes o gráficas de algún tipo, pero cuando se trata de únicamente texto es muy poco cómodo.

Mi segunda aproximación, luego de una breve investigación en la red, fue con Calibre, Este software se encarga de manejar nuestros ebooks y la conversión entre distintos formatos e incluso el pasarlos a la kindle, todo de manera muy intuitiva y cómoda.

Sin embargo, luego de algunas pruebas noté que los renglones o los párrafos se partían, saltando a la siguiente línea, no se acomodaban de forma correcta. No impide la lectura, pero definitivamente incomoda bastante, por lo que aun quedaba margen para mejorar el resultado final.

Fue aquí donde entró en escena mi muy apreciado compañero de trabajo y (ex)amigo Norbert. Él, dueño de una kindle de la generación anterior, ya había superado estos inconvenientes por medio de un procedimiento con más pasos, ninguno complicado, pero cuyo resultado es altamente satisfactorio.

A continuación procedo a replicarlos:

Paso 1: Abrir el archivo pdf en Word.
Localizamos el archivo y hacemos clic derecho sobre el mismo, "Open with" y elegimos "Word (desktop)". Estoy utilizando Office 2013.


 Word procederá informarnos que tratará de mantener, en la medida de lo posible, el "como se ve" el pdf.


Paso 2: Edición en Word
Word nos abrirá el archivo y procederemos a ponerlo en modo edición:

Aquí realizamos cualquier ajuste que queramos realizar y que nos lo permita la herramienta: corrección ortográfica, acomodo de espaciado, saltos de página entre capítulos, etc, A Norbert y a mi, particularmente nos gusta añadir una tabla de contenidos, si es que no existe, basado en los headings que igualmente añado o formateo si no lo estuviesen.


Paso 3: Guardar como Web Page Filtered
Una vez finalizados los cambios que queremos,  podemos salvar el documento como un archivo docx por si luego queremos realizar algún ajuste, Pero para lograr mejores resultados los salvamos como Web page Filtered.


Paso 4 Usando Calibre
Lo siguiente es que utilizamos calibre para abrir el archivo que recién hemos creado. Hacemos clic en el icono de Add Books y seleccionamos nuestro archivo:

Paso 5 Editando la Metadata
Una vez con nuestro archivo cargado en Calibre damos clic derecho sobre el mismo y elegimos Edit Metadata -> Edit Metadata Individually

Por medio de esta pantalla podemos corregir el título y autor de nuestro libro, así como buscarle una portada adecuada y, si nos apetece, cambiar alguna otras cosas. Por medio del botón Download metadata nos conectamos a algunas fuentes de Internet de la cual bajar la metadata de nuestro ebook

Paso 6 Convertir en Formato MOBI
Ya con nuestra metadata actualizada y con una bonita portada, procedemos a hacer clic derecho: Convert books -> Convert individually para convertirlo a formato MOBI que es el formato que mejor me ha funcionado en la kindle paperwhite,

Normalmente no realizo cambio alguno en la configuración, pero se puede navegar por las opciones que son bastante amplias y hacer algunas pruebas para ver las diferentes efectos sobre el el ebook.

Paso 7 Pasar el ebook a la Kindle
Finalmente llegamos al paso último. Conectamos nuestra Kindle y a continuación Calibre la reconocerá de forma automática apareciéndonos un icono más llamado Device. Cuando esto se de, damos clic derecho sobre nuestro libro y elegimos Send to device y Send to main memory

Desconectamos nuestra kindle  y ¡listo! podemos comenzar a disfrutar nuestro ebook

Agradecimientos a Norbert, una vez más, por proporcionarme este procedimiento especialmente útil cuando el pdf que queremos ver en nuestra kindle no tiene muchas gráficas o es únicamente texto. Finalmente recordar que solo se debe realizar sobre libros cuyos derechos hayan caducado o sean de libre reproducción.

24 may 2014

Certificaciones y Certificaciones

No se si les ha pasado, pero con este cambiante mundo de la tecnología nacen, mueren y resucitan tendencias en lo que uno tarda en escribir una nueva entrada en un blog (si es como mi caso, más que justificado, ya que tardo meses entre una entrada y otra...) y de un día para otro te das cuenta que las certificaciones que con tanto esfuerzo lograste ya están prácticamente obsoletas...

Sin embargo esto no debe ser excusa para no mantenernos actualizados.

El blog de Empleos TI Latinoamérica publicó recientemente una entrada con las certificaciones mejor pagadas

El articulo enumera muy brevemente las certificaciones y sus características. Esto se queda muy corto en información, pero gracias al blog de mi amigo Rodrigo Zumbado podemos tener un panorama más claro de la evolución de las certificaciones de Microsoft (que son las que particularmente me interesan) y de los actuales entresijos de las vertientes de dichas certificaciones

Ahora bien, depende de nosotros decidir que hacer con esta información, procurando sacar nuestro mejor provecho, así que por qué no vamos eligiendo en qué queremos certificarnos. Podemos empezar por el sitio del Microsoft Learning.