Categorías
Enlaces de interes
Donaciones

Todo el contenido es gratuito y en beneficio de la comunidad. Puedes reconocer el esfuerzo con una donación si lo deseas.

Patrocinadores
Inserte aquí su publicidad

A raíz de un post en el foro de SQL Server (link), en el que se preguntaba cómo realizar la tabla de clasificación de una competición deportiva con t-SQL, he preparado este mecanismo para llevarlo a cabo. No va mucho más allá de ser un pasatiempo, pero ha sido un pasatiempo divertido.

Se partía de una tabla, proporcionada por el usuario, con los resultados de la temporada de fútbol 2000-2001 de 1ª división de España. Para elaborar la clasificación, hacen falta unos criterios de ordenación en caso de empates, que están en la normativa del organismo que la organiza. En este caso era una dada por el propio usuario (ver el hilo en el foro). Se añaden unas tablas para obtener un modelo que sea más fácilmente explotable y que se carga partiendo de ese origen único, para finalmente preparar un procedimiento almacenado que actualiza la clasificación.

(

CREATE TABLE [dbo].[Resumen 2001][ID] [int] NOT NULL,
[TEMPORADA] [varchar](50) NULL,
[DIVISION] [int] NULL,
[JORNADA] [int] NULL,
[EQUIPO_LOCAL] [varchar](50) NULL,
[EQUIPO_VISITANTE] [varchar](50) NULL,
[GOLES_LOCAL] [int] NULL,
[GOLES_VISITANTE] [int] NULL,
CONSTRAINT [PK_Resumen 2001] PRIMARY KEY CLUSTERED
(
[ID] ASC
)
GO

--Tabla para los equipos
create table Equipos (
  IdEquipo tinyint not null primary key clustered identity (1,1),
  Nombre varchar(20) not null ,
  Pos_Juego_Limpio tinyint not null)
go
-- Tabla para guardar los partidos que cada equipo disputa
-- Cada partido figura dos veces, una vez por cada equipo que lo juega
create table Partidos_Equipo (
  IdPartido smallint not null,
  IdEquipo tinyint not null,
  IdEquipo_Contrario tinyint not null,
  Es_Local bit not null, 
  Goles_A_Favor tinyint not null,
  Goles_En_Contra tinyint not null,
  Puntos tinyint,
  primary key clustered (IdPartido, IdEquipo))
go
-- Tabla para persistir la clasificación
create table Clasificacion (
  IdEquipo tinyint not null primary key clustered,
  PJ tinyint not null,  --Partidos jugados
  PG tinyint not null, --Partidos ganados
  PE tinyint not null, --Partidos empatados
  PP tinyint not null, --Partidos perdidos
  GF tinyint not null, --Goles a favor,
  GC tinyint not null, --Goles en contra,
  DG smallint not null, --Diferencia de goles
  Puntos tinyint not null, --Puntos
  Desempate tinyint not null) --Desempate
go
--El campo desempate es el que permite que, a igualdad de puntos,
--haciendo uso de las reglas de la competición, se ubiquen los equipos

--Datos con los resultados
INSERT INTO [dbo].[Resumen 2001] VALUES  (1,'00/01',1,1,'Osasuna','Celta',0,2),
  (2,'00/01',1,1,'Zaragoza','Espanyol',1,2),
  (3,'00/01',1,1,'Villarreal','Rayo Vallecano',1,5),
  (4,'00/01',1,1,'Las Palmas','Alavés',0,3),
  (5,'00/01',1,1,'Numancia','Oviedo',1,0),
  (6,'00/01',1,1,'Mallorca','Valladolid',1,1),
  (7,'00/01',1,1,'Real Madrid','Valencia',2,1),
  (8,'00/01',1,1,'Barcelona','Málaga',2,1),
  (9,'00/01',1,1,'Dptivo. Coruña','Ath. Bilbao',2,0),
  (10,'00/01',1,1,'Real Sociedad','Rac. Santander',2,2),
  (11,'00/01',1,2,'Celta','Real Sociedad',4,1),
  (12,'00/01',1,2,'Espanyol','Osasuna',1,2),
  (13,'00/01',1,2,'Rayo Vallecano','Zaragoza',0,0),
  (14,'00/01',1,2,'Alavés','Villarreal',0,1),
  (15,'00/01',1,2,'Oviedo','Las Palmas',2,2),
  (16,'00/01',1,2,'Valladolid','Numancia',2,0),
  (17,'00/01',1,2,'Valencia','Mallorca',4,0),
  (18,'00/01',1,2,'Málaga','Real Madrid',3,3),
  (19,'00/01',1,2,'Ath. Bilbao','Barcelona',3,1),
  (20,'00/01',1,2,'Rac. Santander','Dptivo. Coruña',0,3),
  (21,'00/01',1,3,'Celta','Espanyol',1,0),
  (22,'00/01',1,3,'Osasuna','Rayo Vallecano',2,2),
  (23,'00/01',1,3,'Zaragoza','Alavés',2,2),
  (24,'00/01',1,3,'Villarreal','Oviedo',1,0),
  (25,'00/01',1,3,'Las Palmas','Valladolid',1,1),
  (26,'00/01',1,3,'Numancia','Valencia',0,3),
  (27,'00/01',1,3,'Mallorca','Málaga',0,1),
  (28,'00/01',1,3,'Real Madrid','Ath. Bilbao',4,1),
  (29,'00/01',1,3,'Barcelona','Rac. Santander',3,1),
  (30,'00/01',1,3,'Real Sociedad','Dptivo. Coruña',1,1),
  (31,'00/01',1,4,'Espanyol','Real Sociedad',1,2),
  (32,'00/01',1,4,'Rayo Vallecano','Celta',3,0),
  (33,'00/01',1,4,'Alavés','Osasuna',2,0),
  (34,'00/01',1,4,'Oviedo','Zaragoza',2,1),
  (35,'00/01',1,4,'Valladolid','Villarreal',0,0),
  (36,'00/01',1,4,'Valencia','Las Palmas',5,1),
  (37,'00/01',1,4,'Málaga','Numancia',1,3),
  (38,'00/01',1,4,'Ath. Bilbao','Mallorca',2,1),
  (39,'00/01',1,4,'Rac. Santander','Real Madrid',0,0),
  (40,'00/01',1,4,'Dptivo. Coruña','Barcelona',2,0),
  (41,'00/01',1,5,'Espanyol','Rayo Vallecano',0,0),
  (42,'00/01',1,5,'Celta','Alavés',1,1),
  (43,'00/01',1,5,'Osasuna','Oviedo',0,0),
  (44,'00/01',1,5,'Zaragoza','Valladolid',0,0),
  (45,'00/01',1,5,'Villarreal','Valencia',1,1),
  (46,'00/01',1,5,'Las Palmas','Málaga',2,1),
  (47,'00/01',1,5,'Numancia','Ath. Bilbao',0,0),
  (48,'00/01',1,5,'Mallorca','Rac. Santander',2,1),
  (49,'00/01',1,5,'Real Madrid','Dptivo. Coruña',3,0),
  (50,'00/01',1,5,'Real Sociedad','Barcelona',0,6),
  (51,'00/01',1,6,'Rayo Vallecano','Real Sociedad',4,1),
  (52,'00/01',1,6,'Alavés','Espanyol',1,0),
  (53,'00/01',1,6,'Oviedo','Celta',3,1),
  (54,'00/01',1,6,'Valladolid','Osasuna',1,1),
  (55,'00/01',1,6,'Valencia','Zaragoza',1,0),
  (56,'00/01',1,6,'Málaga','Villarreal',2,1),
  (57,'00/01',1,6,'Ath. Bilbao','Las Palmas',0,3),
  (58,'00/01',1,6,'Rac. Santander','Numancia',4,2),
  (59,'00/01',1,6,'Dptivo. Coruña','Mallorca',1,1),
  (60,'00/01',1,6,'Barcelona','Real Madrid',2,0),
  (61,'00/01',1,7,'Rayo Vallecano','Alavés',0,1),
  (62,'00/01',1,7,'Espanyol','Oviedo',2,0),
  (63,'00/01',1,7,'Celta','Valladolid',2,1),
  (64,'00/01',1,7,'Osasuna','Valencia',1,2),
  (65,'00/01',1,7,'Zaragoza','Málaga',1,1),
  (66,'00/01',1,7,'Villarreal','Ath. Bilbao',0,0),
  (67,'00/01',1,7,'Las Palmas','Rac. Santander',2,1),
  (68,'00/01',1,7,'Numancia','Dptivo. Coruña',1,2),
  (69,'00/01',1,7,'Mallorca','Barcelona',2,0),
  (70,'00/01',1,7,'Real Sociedad','Real Madrid',1,4),
  (71,'00/01',1,8,'Alavés','Real Sociedad',0,1),
  (72,'00/01',1,8,'Oviedo','Rayo Vallecano',4,1),
  (73,'00/01',1,8,'Valladolid','Espanyol',1,1),
  (74,'00/01',1,8,'Valencia','Celta',1,0),
  (75,'00/01',1,8,'Málaga','Osasuna',3,1),
  (76,'00/01',1,8,'Ath. Bilbao','Zaragoza',1,2),
  (77,'00/01',1,8,'Rac. Santander','Villarreal',3,1),
  (78,'00/01',1,8,'Dptivo. Coruña','Las Palmas',4,0),
  (79,'00/01',1,8,'Barcelona','Numancia',1,1),
  (80,'00/01',1,8,'Real Madrid','Mallorca',0,2),
  (81,'00/01',1,9,'Alavés','Oviedo',4,0),
  (82,'00/01',1,9,'Rayo Vallecano','Valladolid',2,1),
  (83,'00/01',1,9,'Espanyol','Valencia',1,0),
  (84,'00/01',1,9,'Celta','Málaga',1,0),
  (85,'00/01',1,9,'Osasuna','Ath. Bilbao',1,1),
  (86,'00/01',1,9,'Zaragoza','Rac. Santander',2,0),
  (87,'00/01',1,9,'Villarreal','Dptivo. Coruña',3,2),
  (88,'00/01',1,9,'Las Palmas','Barcelona',0,1),
  (89,'00/01',1,9,'Numancia','Real Madrid',3,1),
  (90,'00/01',1,9,'Real Sociedad','Mallorca',0,1),
  (91,'00/01',1,10,'Oviedo','Real Sociedad',1,0),
  (92,'00/01',1,10,'Valladolid','Alavés',2,1),
  (93,'00/01',1,10,'Valencia','Rayo Vallecano',2,2),
  (94,'00/01',1,10,'Málaga','Espanyol',0,0),
  (95,'00/01',1,10,'Ath. Bilbao','Celta',2,1),
  (96,'00/01',1,10,'Rac. Santander','Osasuna',0,0),
  (97,'00/01',1,10,'Dptivo. Coruña','Zaragoza',2,0),
  (98,'00/01',1,10,'Barcelona','Villarreal',1,2),
  (99,'00/01',1,10,'Real Madrid','Las Palmas',5,1),
  (100,'00/01',1,10,'Mallorca','Numancia',2,1),
  (101,'00/01',1,11,'Oviedo','Valladolid',4,1),
  (102,'00/01',1,11,'Alavés','Valencia',1,1),
  (103,'00/01',1,11,'Rayo Vallecano','Málaga',4,2),
  (104,'00/01',1,11,'Espanyol','Ath. Bilbao',2,1),
  (105,'00/01',1,11,'Celta','Rac. Santander',1,1),
  (106,'00/01',1,11,'Osasuna','Dptivo. Coruña',1,1),
  (107,'00/01',1,11,'Zaragoza','Barcelona',3,1),
  (108,'00/01',1,11,'Villarreal','Real Madrid',0,1),
  (109,'00/01',1,11,'Las Palmas','Mallorca',1,0),
  (110,'00/01',1,11,'Real Sociedad','Numancia',4,1),
  (111,'00/01',1,12,'Valladolid','Real Sociedad',2,1),
  (112,'00/01',1,12,'Valencia','Oviedo',2,0),
  (113,'00/01',1,12,'Málaga','Alavés',3,1),
  (114,'00/01',1,12,'Ath. Bilbao','Rayo Vallecano',4,2),
  (115,'00/01',1,12,'Rac. Santander','Espanyol',1,2),
  (116,'00/01',1,12,'Dptivo. Coruña','Celta',1,0),
  (117,'00/01',1,12,'Barcelona','Osasuna',2,0),
  (118,'00/01',1,12,'Real Madrid','Zaragoza',3,0),
  (119,'00/01',1,12,'Mallorca','Villarreal',2,1),
  (120,'00/01',1,12,'Numancia','Las Palmas',0,1),
  (121,'00/01',1,13,'Valladolid','Valencia',0,0),
  (122,'00/01',1,13,'Oviedo','Málaga',3,2),
  (123,'00/01',1,13,'Alavés','Ath. Bilbao',2,1),
  (124,'00/01',1,13,'Rayo Vallecano','Rac. Santander',4,1),
  (125,'00/01',1,13,'Espanyol','Dptivo. Coruña',0,2),
  (126,'00/01',1,13,'Celta','Barcelona',3,3),
  (127,'00/01',1,13,'Osasuna','Real Madrid',2,3),
  (128,'00/01',1,13,'Zaragoza','Mallorca',1,1),
  (129,'00/01',1,13,'Villarreal','Numancia',0,0),
  (130,'00/01',1,13,'Real Sociedad','Las Palmas',1,1),
  (131,'00/01',1,14,'Valencia','Real Sociedad',2,0),
  (132,'00/01',1,14,'Málaga','Valladolid',3,1),
  (133,'00/01',1,14,'Ath. Bilbao','Oviedo',4,0),
  (134,'00/01',1,14,'Rac. Santander','Alavés',2,1),
  (135,'00/01',1,14,'Dptivo. Coruña','Rayo Vallecano',1,1),
  (136,'00/01',1,14,'Barcelona','Espanyol',4,2),
  (137,'00/01',1,14,'Real Madrid','Celta',3,0),
  (138,'00/01',1,14,'Mallorca','Osasuna',1,1),
  (139,'00/01',1,14,'Numancia','Zaragoza',1,1),
  (140,'00/01',1,14,'Las Palmas','Villarreal',1,5),
  (141,'00/01',1,15,'Valencia','Málaga',2,0),
  (142,'00/01',1,15,'Valladolid','Ath. Bilbao',0,0),
  (143,'00/01',1,15,'Oviedo','Rac. Santander',1,0),
  (144,'00/01',1,15,'Alavés','Dptivo. Coruña',3,1),
  (145,'00/01',1,15,'Rayo Vallecano','Barcelona',2,2),
  (146,'00/01',1,15,'Espanyol','Real Madrid',1,2),
  (147,'00/01',1,15,'Celta','Mallorca',2,2),
  (148,'00/01',1,15,'Osasuna','Numancia',2,0),
  (149,'00/01',1,15,'Zaragoza','Las Palmas',3,1),
  (150,'00/01',1,15,'Real Sociedad','Villarreal',0,2),
  (151,'00/01',1,16,'Málaga','Real Sociedad',3,0),
  (152,'00/01',1,16,'Ath. Bilbao','Valencia',1,1),
  (153,'00/01',1,16,'Rac. Santander','Valladolid',2,2),
  (154,'00/01',1,16,'Dptivo. Coruña','Oviedo',3,0),
  (155,'00/01',1,16,'Barcelona','Alavés',3,2),
  (156,'00/01',1,16,'Real Madrid','Rayo Vallecano',3,1),
  (157,'00/01',1,16,'Mallorca','Espanyol',3,2),
  (158,'00/01',1,16,'Numancia','Celta',4,2),
  (159,'00/01',1,16,'Las Palmas','Osasuna',3,2),
  (160,'00/01',1,16,'Villarreal','Zaragoza',1,1),
  (161,'00/01',1,17,'Málaga','Ath. Bilbao',2,1),
  (162,'00/01',1,17,'Valencia','Rac. Santander',1,0),
  (163,'00/01',1,17,'Valladolid','Dptivo. Coruña',3,1),
  (164,'00/01',1,17,'Oviedo','Barcelona',2,3),
  (165,'00/01',1,17,'Alavés','Real Madrid',1,3),
  (166,'00/01',1,17,'Rayo Vallecano','Mallorca',2,2),
  (167,'00/01',1,17,'Espanyol','Numancia',2,0),
  (168,'00/01',1,17,'Celta','Las Palmas',0,1),
  (169,'00/01',1,17,'Osasuna','Villarreal',1,0),
  (170,'00/01',1,17,'Real Sociedad','Zaragoza',1,1),
  (171,'00/01',1,18,'Real Sociedad','Ath. Bilbao',0,2),
  (172,'00/01',1,18,'Rac. Santander','Málaga',0,1),
  (173,'00/01',1,18,'Dptivo. Coruña','Valencia',2,0),
  (174,'00/01',1,18,'Barcelona','Valladolid',3,1),
  (175,'00/01',1,18,'Real Madrid','Oviedo',4,0),
  (176,'00/01',1,18,'Mallorca','Alavés',4,3),
  (177,'00/01',1,18,'Numancia','Rayo Vallecano',0,2),
  (178,'00/01',1,18,'Las Palmas','Espanyol',1,0),
  (179,'00/01',1,18,'Villarreal','Celta',2,0),
  (180,'00/01',1,18,'Zaragoza','Osasuna',4,2),
  (181,'00/01',1,19,'Ath. Bilbao','Rac. Santander',3,1),
  (182,'00/01',1,19,'Málaga','Dptivo. Coruña',1,3),
  (183,'00/01',1,19,'Valencia','Barcelona',0,1),
  (184,'00/01',1,19,'Valladolid','Real Madrid',2,2),
  (185,'00/01',1,19,'Oviedo','Mallorca',1,1),
  (186,'00/01',1,19,'Alavés','Numancia',0,2),
  (187,'00/01',1,19,'Rayo Vallecano','Las Palmas',1,1),
  (188,'00/01',1,19,'Espanyol','Villarreal',2,1),
  (189,'00/01',1,19,'Celta','Zaragoza',1,1),
  (190,'00/01',1,19,'Osasuna','Real Sociedad',1,1),
  (191,'00/01',1,20,'Celta','Osasuna',1,0),
  (192,'00/01',1,20,'Espanyol','Zaragoza',5,0),
  (193,'00/01',1,20,'Rayo Vallecano','Villarreal',0,1),
  (194,'00/01',1,20,'Alavés','Las Palmas',1,0),
  (195,'00/01',1,20,'Oviedo','Numancia',3,0),
  (196,'00/01',1,20,'Valladolid','Mallorca',2,0),
  (197,'00/01',1,20,'Valencia','Real Madrid',0,1),
  (198,'00/01',1,20,'Málaga','Barcelona',0,0),
  (199,'00/01',1,20,'Ath. Bilbao','Dptivo. Coruña',2,2),
  (200,'00/01',1,20,'Rac. Santander','Real Sociedad',1,4),
  (201,'00/01',1,21,'Real Sociedad','Celta',2,2),
  (202,'00/01',1,21,'Osasuna','Espanyol',1,3),
  (203,'00/01',1,21,'Zaragoza','Rayo Vallecano',6,1),
  (204,'00/01',1,21,'Villarreal','Alavés',2,0),
  (205,'00/01',1,21,'Las Palmas','Oviedo',1,0),
  (206,'00/01',1,21,'Numancia','Valladolid',0,0),
  (207,'00/01',1,21,'Mallorca','Valencia',2,2),
  (208,'00/01',1,21,'Real Madrid','Málaga',4,3),
  (209,'00/01',1,21,'Barcelona','Ath. Bilbao',7,0),
  (210,'00/01',1,21,'Dptivo. Coruña','Rac. Santander',2,1),
  (211,'00/01',1,22,'Espanyol','Celta',0,1),
  (212,'00/01',1,22,'Rayo Vallecano','Osasuna',2,1),
  (213,'00/01',1,22,'Alavés','Zaragoza',1,0),
  (214,'00/01',1,22,'Oviedo','Villarreal',1,3),
  (215,'00/01',1,22,'Valladolid','Las Palmas',1,0),
  (216,'00/01',1,22,'Valencia','Numancia',3,0),
  (217,'00/01',1,22,'Málaga','Mallorca',0,1),
  (218,'00/01',1,22,'Ath. Bilbao','Real Madrid',1,0),
  (219,'00/01',1,22,'Rac. Santander','Barcelona',4,0),
  (220,'00/01',1,22,'Dptivo. Coruña','Real Sociedad',4,1),
  (221,'00/01',1,23,'Real Sociedad','Espanyol',2,1),
  (222,'00/01',1,23,'Celta','Rayo Vallecano',1,1),
  (223,'00/01',1,23,'Osasuna','Alavés',0,1),
  (224,'00/01',1,23,'Zaragoza','Oviedo',5,2),
  (225,'00/01',1,23,'Villarreal','Valladolid',2,1),
  (226,'00/01',1,23,'Las Palmas','Valencia',0,2),
  (227,'00/01',1,23,'Numancia','Málaga',3,2),
  (228,'00/01',1,23,'Mallorca','Ath. Bilbao',1,0),
  (229,'00/01',1,23,'Real Madrid','Rac. Santander',1,0),
  (230,'00/01',1,23,'Barcelona','Dptivo. Coruña',2,3),
  (231,'00/01',1,24,'Rayo Vallecano','Espanyol',1,1),
  (232,'00/01',1,24,'Alavés','Celta',2,2),
  (233,'00/01',1,24,'Oviedo','Osasuna',2,3),
  (234,'00/01',1,24,'Valladolid','Zaragoza',2,0),
  (235,'00/01',1,24,'Valencia','Villarreal',3,1),
  (236,'00/01',1,24,'Málaga','Las Palmas',2,1),
  (237,'00/01',1,24,'Ath. Bilbao','Numancia',3,1),
  (238,'00/01',1,24,'Rac. Santander','Mallorca',2,1),
  (239,'00/01',1,24,'Dptivo. Coruña','Real Madrid',2,2),
  (240,'00/01',1,24,'Barcelona','Real Sociedad',3,0),
  (241,'00/01',1,25,'Real Sociedad','Rayo Vallecano',2,0),
  (242,'00/01',1,25,'Espanyol','Alavés',0,0),
  (243,'00/01',1,25,'Celta','Oviedo',1,0),
  (244,'00/01',1,25,'Osasuna','Valladolid',2,1),
  (245,'00/01',1,25,'Zaragoza','Valencia',1,1),
  (246,'00/01',1,25,'Villarreal','Málaga',1,2),
  (247,'00/01',1,25,'Las Palmas','Ath. Bilbao',0,0),
  (248,'00/01',1,25,'Numancia','Rac. Santander',1,0),
  (249,'00/01',1,25,'Mallorca','Dptivo. Coruña',2,1),
  (250,'00/01',1,25,'Real Madrid','Barcelona',2,2),
  (251,'00/01',1,26,'Alavés','Rayo Vallecano',4,2),
  (252,'00/01',1,26,'Oviedo','Espanyol',2,2),
  (253,'00/01',1,26,'Valladolid','Celta',1,2),
  (254,'00/01',1,26,'Valencia','Osasuna',1,0),
  (255,'00/01',1,26,'Málaga','Zaragoza',2,0),
  (256,'00/01',1,26,'Ath. Bilbao','Villarreal',1,1),
  (257,'00/01',1,26,'Rac. Santander','Las Palmas',2,1),
  (258,'00/01',1,26,'Dptivo. Coruña','Numancia',4,1),
  (259,'00/01',1,26,'Barcelona','Mallorca',1,1),
  (260,'00/01',1,26,'Real Madrid','Real Sociedad',4,0),
  (261,'00/01',1,27,'Real Sociedad','Alavés',1,1),
  (262,'00/01',1,27,'Rayo Vallecano','Oviedo',0,2),
  (263,'00/01',1,27,'Espanyol','Valladolid',1,0),
  (264,'00/01',1,27,'Celta','Valencia',3,2),
  (265,'00/01',1,27,'Osasuna','Málaga',3,3),
  (266,'00/01',1,27,'Zaragoza','Ath. Bilbao',2,2),
  (267,'00/01',1,27,'Villarreal','Rac. Santander',4,2),
  (268,'00/01',1,27,'Las Palmas','Dptivo. Coruña',2,0),
  (269,'00/01',1,27,'Numancia','Barcelona',1,1),
  (270,'00/01',1,27,'Mallorca','Real Madrid',1,0),
  (271,'00/01',1,28,'Oviedo','Alavés',3,3),
  (272,'00/01',1,28,'Valladolid','Rayo Vallecano',1,3),
  (273,'00/01',1,28,'Valencia','Espanyol',0,1),
  (274,'00/01',1,28,'Málaga','Celta',1,4),
  (275,'00/01',1,28,'Ath. Bilbao','Osasuna',0,1),
  (276,'00/01',1,28,'Rac. Santander','Zaragoza',2,1),
  (277,'00/01',1,28,'Dptivo. Coruña','Villarreal',4,2),
  (278,'00/01',1,28,'Barcelona','Las Palmas',4,1),
  (279,'00/01',1,28,'Real Madrid','Numancia',1,0),
  (280,'00/01',1,28,'Mallorca','Real Sociedad',1,1),
  (281,'00/01',1,29,'Real Sociedad','Oviedo',3,0),
  (282,'00/01',1,29,'Alavés','Valladolid',4,2),
  (283,'00/01',1,29,'Rayo Vallecano','Valencia',1,4),
  (284,'00/01',1,29,'Espanyol','Málaga',1,2),
  (285,'00/01',1,29,'Celta','Ath. Bilbao',2,1),
  (286,'00/01',1,29,'Osasuna','Rac. Santander',1,1),
  (287,'00/01',1,29,'Zaragoza','Dptivo. Coruña',2,1),
  (288,'00/01',1,29,'Villarreal','Barcelona',4,4),
  (289,'00/01',1,29,'Las Palmas','Real Madrid',0,1),
  (290,'00/01',1,29,'Numancia','Mallorca',0,2),
  (291,'00/01',1,30,'Valladolid','Oviedo',1,0),
  (292,'00/01',1,30,'Valencia','Alavés',1,2),
  (293,'00/01',1,30,'Málaga','Rayo Vallecano',1,1),
  (294,'00/01',1,30,'Ath. Bilbao','Espanyol',0,1),
  (295,'00/01',1,30,'Rac. Santander','Celta',3,0),
  (296,'00/01',1,30,'Dptivo. Coruña','Osasuna',2,1),
  (297,'00/01',1,30,'Barcelona','Zaragoza',4,4),
  (298,'00/01',1,30,'Real Madrid','Villarreal',4,0),
  (299,'00/01',1,30,'Mallorca','Las Palmas',2,1),
  (300,'00/01',1,30,'Numancia','Real Sociedad',3,3),
  (301,'00/01',1,31,'Real Sociedad','Valladolid',3,1),
  (302,'00/01',1,31,'Oviedo','Valencia',0,0),
  (303,'00/01',1,31,'Alavés','Málaga',1,2),
  (304,'00/01',1,31,'Rayo Vallecano','Ath. Bilbao',1,2),
  (305,'00/01',1,31,'Espanyol','Rac. Santander',3,0),
  (306,'00/01',1,31,'Celta','Dptivo. Coruña',2,1),
  (307,'00/01',1,31,'Osasuna','Barcelona',3,1),
  (308,'00/01',1,31,'Zaragoza','Real Madrid',2,3),
  (309,'00/01',1,31,'Villarreal','Mallorca',2,2),
  (310,'00/01',1,31,'Las Palmas','Numancia',1,1),
  (311,'00/01',1,32,'Valencia','Valladolid',1,0),
  (312,'00/01',1,32,'Málaga','Oviedo',2,2),
  (313,'00/01',1,32,'Ath. Bilbao','Alavés',2,0),
  (314,'00/01',1,32,'Rac. Santander','Rayo Vallecano',1,1),
  (315,'00/01',1,32,'Dptivo. Coruña','Espanyol',1,0),
  (316,'00/01',1,32,'Barcelona','Celta',1,1),
  (317,'00/01',1,32,'Real Madrid','Osasuna',1,1),
  (318,'00/01',1,32,'Mallorca','Zaragoza',2,1),
  (319,'00/01',1,32,'Numancia','Villarreal',1,3),
  (320,'00/01',1,32,'Las Palmas','Real Sociedad',2,1),
  (321,'00/01',1,33,'Real Sociedad','Valencia',1,2),
  (322,'00/01',1,33,'Valladolid','Málaga',0,0),
  (323,'00/01',1,33,'Oviedo','Ath. Bilbao',5,0),
  (324,'00/01',1,33,'Alavés','Rac. Santander',5,1),
  (325,'00/01',1,33,'Rayo Vallecano','Dptivo. Coruña',1,1),
  (326,'00/01',1,33,'Espanyol','Barcelona',0,0),
  (327,'00/01',1,33,'Celta','Real Madrid',3,0),
  (328,'00/01',1,33,'Osasuna','Mallorca',1,0),
  (329,'00/01',1,33,'Zaragoza','Numancia',3,1),
  (330,'00/01',1,33,'Villarreal','Las Palmas',2,1),
  (331,'00/01',1,34,'Málaga','Valencia',3,0),
  (332,'00/01',1,34,'Ath. Bilbao','Valladolid',1,1),
  (333,'00/01',1,34,'Rac. Santander','Oviedo',2,0),
  (334,'00/01',1,34,'Dptivo. Coruña','Alavés',2,1),
  (335,'00/01',1,34,'Barcelona','Rayo Vallecano',5,1),
  (336,'00/01',1,34,'Real Madrid','Espanyol',2,2),
  (337,'00/01',1,34,'Mallorca','Celta',2,0),
  (338,'00/01',1,34,'Numancia','Osasuna',1,0),
  (339,'00/01',1,34,'Las Palmas','Zaragoza',2,1),
  (340,'00/01',1,34,'Villarreal','Real Sociedad',1,3),
  (341,'00/01',1,35,'Real Sociedad','Málaga',4,0),
  (342,'00/01',1,35,'Valencia','Ath. Bilbao',1,0),
  (343,'00/01',1,35,'Valladolid','Rac. Santander',1,1),
  (344,'00/01',1,35,'Oviedo','Dptivo. Coruña',2,3),
  (345,'00/01',1,35,'Alavés','Barcelona',0,1),
  (346,'00/01',1,35,'Rayo Vallecano','Real Madrid',0,1),
  (347,'00/01',1,35,'Espanyol','Mallorca',0,1),
  (348,'00/01',1,35,'Celta','Numancia',1,1),
  (349,'00/01',1,35,'Osasuna','Las Palmas',3,3),
  (350,'00/01',1,35,'Zaragoza','Villarreal',0,0),
  (351,'00/01',1,36,'Ath. Bilbao','Málaga',1,3),
  (352,'00/01',1,36,'Rac. Santander','Valencia',1,1),
  (353,'00/01',1,36,'Dptivo. Coruña','Valladolid',1,2),
  (354,'00/01',1,36,'Barcelona','Oviedo',0,1),
  (355,'00/01',1,36,'Real Madrid','Alavés',5,0),
  (356,'00/01',1,36,'Mallorca','Rayo Vallecano',4,0),
  (357,'00/01',1,36,'Numancia','Espanyol',2,1),
  (358,'00/01',1,36,'Las Palmas','Celta',0,1),
  (359,'00/01',1,36,'Villarreal','Osasuna',2,0),
  (360,'00/01',1,36,'Zaragoza','Real Sociedad',1,1),
  (361,'00/01',1,37,'Ath. Bilbao','Real Sociedad',1,3),
  (362,'00/01',1,37,'Málaga','Rac. Santander',2,1),
  (363,'00/01',1,37,'Valencia','Dptivo. Coruña',0,1),
  (364,'00/01',1,37,'Valladolid','Barcelona',2,2),
  (365,'00/01',1,37,'Oviedo','Real Madrid',1,1),
  (366,'00/01',1,37,'Alavés','Mallorca',2,4),
  (367,'00/01',1,37,'Rayo Vallecano','Numancia',2,1),
  (368,'00/01',1,37,'Espanyol','Las Palmas',3,2),
  (369,'00/01',1,37,'Celta','Villarreal',1,0),
  (370,'00/01',1,37,'Osasuna','Zaragoza',1,0),
  (371,'00/01',1,38,'Rac. Santander','Ath. Bilbao',3,0),
  (372,'00/01',1,38,'Dptivo. Coruña','Málaga',4,0),
  (373,'00/01',1,38,'Barcelona','Valencia',3,2),
  (374,'00/01',1,38,'Real Madrid','Valladolid',2,1),
  (375,'00/01',1,38,'Mallorca','Oviedo',4,2),
  (376,'00/01',1,38,'Numancia','Alavés',2,1),
  (377,'00/01',1,38,'Las Palmas','Rayo Vallecano',1,0),
  (378,'00/01',1,38,'Villarreal','Espanyol',4,0),
  (379,'00/01',1,38,'Zaragoza','Celta',1,1),
  (380,'00/01',1,38,'Real Sociedad','Osasuna',0,1);
go
--insertamos los equipos
insert Equipos (Nombre, Pos_Juego_Limpio)
select  distinct EQUIPO_LOCAL, 0 from [Resumen 2001] order by 1
go
--La clasificación del juego limpio, es ficticia, le asigno el id
update Equipos set Pos_Juego_Limpio = IdEquipo
go

--Consolidación de los partidos
insert Partidos_Equipo
select IdPartido = ID, IdEquipo = E.IdEquipo, IdEquipo_Contrario = V.IdEquipo, Es_Local = 1,
  Goles_A_Favor = R.GOLES_LOCAL, Goles_En_Contra = R.GOLES_VISITANTE,
  Puntos = case
     when R.GOLES_LOCAL = R.GOLES_VISITANTE then 1
     when R.GOLES_LOCAL > R.GOLES_VISITANTE then 3
     when R.GOLES_LOCAL < R.GOLES_VISITANTE then 0 
 end
from
  [Resumen 2001]  R inner join
  Equipos E on R.EQUIPO_LOCAL = E.Nombre inner join
  Equipos V on R.EQUIPO_VISITANTE = V.Nombre
union all
select IdPartido = ID, IdEquipo = V.IdEquipo, IdEquipo_Contrario = E.IdEquipo, Es_Local = 0,
  Goles_A_Favor = R.GOLES_VISITANTE, Goles_En_Contra = R.GOLES_LOCAL,
  Puntos = case
     when R.GOLES_LOCAL = R.GOLES_VISITANTE then 1
     when R.GOLES_LOCAL > R.GOLES_VISITANTE then 0
     when R.GOLES_LOCAL < R.GOLES_VISITANTE then 3 
 end
from
  [Resumen 2001]  R inner join
  Equipos E on R.EQUIPO_LOCAL = E.Nombre inner join
  Equipos V on R.EQUIPO_VISITANTE = V.Nombre
Order by ID

go

--Procedimiento almacenado para actualizar la clasificación
alter proc p_Refrescar_Clasificacion as

set nocount on
--Construir la clasificación
truncate table Clasificacion

insert Clasificacion (IdEquipo, PJ, PG, PE, PP, GF, GC, DG, Puntos, Desempate)
select E.IdEquipo,
  PJ = count(IdPartido),
  PG = sum(case when Puntos = 3 then 1 else 0 end),
  PE = sum(case when Puntos = 1 then 1 else 0 end),
  PP = sum(case when Puntos = 0 then 1 else 0 end),
  GF = SUM(P.Goles_A_Favor),
  GC = sum(P.Goles_En_Contra),
  DG = SUM(P.Goles_A_Favor) - sum(P.Goles_En_Contra),
  Puntos = SUM(P.Puntos),
  Desempate = 0
from Equipos E inner join Partidos_Equipo P on E.IdEquipo = P.IdEquipo
group by E.IdEquipo

--Empates
while exists (select top 1 Puntos from Clasificacion where Desempate = 0 group by Puntos having COUNT(IdEquipo) > 1)
  begin
    begin tran
    ;with Clas as (
      select
        C.IdEquipo, DG, GF, E.Pos_Juego_Limpio
      from Clasificacion C inner join Equipos E on C.IdEquipo = E.IdEquipo
      where Puntos in (select top 1 Puntos from Clasificacion where Desempate = 0 group by Puntos having COUNT(IdEquipo) > 1))
      , Criterios as (
      select
        P.IdEquipo,
        Puntos = SUM(P.Puntos), --1er criterio
        DG = SUM(P.Goles_A_Favor) - SUM(Goles_En_Contra), --2º criterio
        DG_General = C.DG, --3er criterio
        GF_General = C.GF, --4º criterio
        C.Pos_Juego_Limpio --5º criterio
      from
        Partidos_Equipo P inner join
        Clas C on P.IdEquipo = C.IdEquipo
      where P.IdEquipo_Contrario in (select IdEquipo from Clas)
      group by P.IdEquipo, C.DG, C.GF, C.Pos_Juego_Limpio)
      , Desempate as (
      select IdEquipo,
        Desempate = ROW_NUMBER() over (order by Puntos desc, DG desc, DG_General desc, GF_General, Pos_Juego_Limpio asc)
      from Criterios)
      update C set Desempate = D.Desempate
      from Clasificacion C inner join Desempate D on C.IdEquipo = D.IdEquipo

    commit
  end
return 0
go

--Actualización de la clasificación
exec p_Refrescar_Clasificacion

--Obtención de la misma
select E.Nombre, PJ, PG, PE, PP, GF, GC, DG, Puntos
from
   Clasificacion C inner join Equipos E on C.IdEquipo = E.IdEquipo
order by Puntos desc, Desempate asc


No me prodigo mucho en Reporting Services, así que cada vez que me toca pasar por allí aprendo una cuantas cosas. En mi último viaje, me pidieron algo trivial, que se enviara un listado en formato Excel por mail.

Hasta ahí, pues muy sencillo. La complicación vino cuando me pidieron que en el mail se adecuaran de forma dinámica algunas cosas, como incluir el rango de fechas del informe en el asunto y en el cuerpo del mensaje y dotar al fichero de un nombre que incluyera esas características.

Después de ver hasta dónde se podía llegar con una Data-driven subscription, comprobé que se pueden hacer cosas, pero no demasiadas, y desde luego no las que yo necesitaba. Así que hice lo que todos, porque quizá no sepa mucho de SSRS, pero sí sé buscar en Google. Además, esta necesidad es bastante simple a mi entender, con lo que contaba con que no era el primer caso en la historia del mundo mundial en el que a alguien se le ocurría que el fichero a adjuntar a un mail tuviera un nombre que no fuera el del informe.

Y me sorprendió ver que, si bien el problema era común, no había una solución simple. Me costó encontrarlo, y la alternativa no es muy ortodoxa que digamos (ni soportada) así que me pareció interesante compartirlo. Hay varias entradas similares en la web, yo partí de la siguiente: http://www.sqlservercentral.com/scripts/Reporting+Services+(SSRS)/70387/.

Esa es la pista. Hay que crear una suscripción normal y corriente. Luego encontrar el job que la lanza y modificarlo. Antes de lanzar la suscripción, se personaliza (ojo, conservando una referencia de lo que se ha cambiado):

declare @reportID uniqueidentifier, @subscriptionID uniqueidentifier, @path nvarchar(255), @body nvarchar(max), @asunto nvarchar(max)

SELECT
  @reportID  = rs.reportID
, @path = '/Listados/Listado_Semana_' + convert(char(8), dateadd(dd, -7, getdate()), 112)+ '_' + convert(char(8), dateadd(dd, -1, getdate()), 112)
, @subscriptionID = rs.SubscriptionId
,  @body = 'Se adjunta listado con todos los datos de la última semana (del ' +
  substring(convert(char(8), dateadd(dd, -7, getdate()), 112), 7, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -7, getdate()), 112), 5, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -7, getdate()), 112), 1, 4) + ' al ' +
  substring(convert(char(8), dateadd(dd, -1, getdate()), 112), 7, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -1, getdate()), 112), 5, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -1, getdate()), 112), 1, 4) + ').',
  @asunto = 'Listado de datos de la semana del ' +
  substring(convert(char(8), dateadd(dd, -7, getdate()), 112), 7, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -7, getdate()), 112), 5, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -7, getdate()), 112), 1, 4) + ' al ' +
  substring(convert(char(8), dateadd(dd, -1, getdate()), 112), 7, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -1, getdate()), 112), 5, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -1, getdate()), 112), 1, 4)
FROM
        ReportSchedule rs
            INNER JOIN subscriptions s
                ON rs.subscriptionID = s.subscriptionID
            INNER JOIN dbo.[catalog] c
                ON rs.reportID = c.itemID
WHERE
  c.[path] = '/Listados/Listado Última Semana'

update dbo.[Catalog] set [path] = @path where itemID = @reportID

update subscriptions set
   ExtensionSettings = 
      replace(replace(cast(ExtensionSettings as nvarchar(max)), 'Se adjunta listado con todos los datos de la última semana.', @body), '@ReportName', @asunto)
where SubscriptionID = @subscriptionID

exec dbo.AddEvent @EventType='TimedSubscription', @EventData=@subscriptionID

Luego añadimos un segundo paso al job para dejar las cosas como estaban (yo le he añadido un delay de 1 minuto, que seguramente se pueda quitar, es para asegurarse de que el informe sale antes de volver a dejar cambiados los datos otra vez). Así, el segundo paso de ese job sería más o menos así:

waitfor delay '00:01'

--Ahora lo dejo como estaba
declare @reportID uniqueidentifier, @subscriptionID uniqueidentifier, @path nvarchar(255), @body nvarchar(max), @asunto nvarchar(max)

SELECT
  @reportID  = rs.reportID
, @path = '/Listados/Listado Última Semana'
, @subscriptionID = rs.SubscriptionId
,  @body = 'Se adjunta listado con todos los datos de la última semana (del ' +
  substring(convert(char(8), dateadd(dd, -7, getdate()), 112), 7, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -7, getdate()), 112), 5, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -7, getdate()), 112), 1, 4) + ' al ' +
  substring(convert(char(8), dateadd(dd, -1, getdate()), 112), 7, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -1, getdate()), 112), 5, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -1, getdate()), 112), 1, 4) + ').',
  @asunto = 'Listado de datos de la semana del ' +
  substring(convert(char(8), dateadd(dd, -7, getdate()), 112), 7, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -7, getdate()), 112), 5, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -7, getdate()), 112), 1, 4) + ' al ' +
  substring(convert(char(8), dateadd(dd, -1, getdate()), 112), 7, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -1, getdate()), 112), 5, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -1, getdate()), 112), 1, 4)
FROM
        ReportSchedule rs
            INNER JOIN subscriptions s
                ON rs.subscriptionID = s.subscriptionID
            INNER JOIN dbo.[catalog] c
                ON rs.reportID = c.itemID
WHERE
  c.[path] = '/Listado/Listado_Semana_' + convert(char(8), dateadd(dd, -7, getdate()), 112)+ '_' + convert(char(8), dateadd(dd, -1, getdate()), 112)

update dbo.[Catalog] set [path] = @path where itemID = @reportID

update subscriptions set
   ExtensionSettings = 
      replace(replace(cast(ExtensionSettings as nvarchar(max)), @body, 'Se adjunta listado con todos los datos de la última semana.'), @asunto, '@ReportName')
where SubscriptionID = @subscriptionID

Y ya está, hemos personalizado el asunto, el cuerpo del mensaje y el nombre del fichero. Pero con la misma táctica puede modificarse lo que se precise.

Continuando con esta serie de post (ver 1 y 2), quisiera tratar aquí un punto que a mí personalmente me echa bastante para atrás en general, pero que no quita para que sea una cuestión de enorme importancia para la calidad de datos, y estos son la definición y mantenimiento de diccionarios de datos.

Puestos a diseñar un proyecto de calidad de datos, la definición de los metadatos serían eso, los planos sobre los que luego se edificará lo demás. Lo que pasa es que es una tarea árdua y notablemente desagradecida, ya que unificar tipos de datos entre modelos que serán heterogéneos, que llevan años de una determinada forma y que hay que cambiar, que requieren de la intervención de otros equipos no directamente implicados en el proyecto (pero que mantienen esta o aquella aplicación donde se emplean o recogen los datos) para que, después de todo el esfuerzo, el entregable sea ninguno, pues oye, no es algo como para entusiasmarse si a uno le encomiendan pilotar este cometido. Por el contrario, una vez que existe, es algo de un gran valor que facilita y agiliza mucho todos los desarrollos posteriores, donde ya no habrá que pensar tanto a la hora de modelar, y además es muy posible que por el camino se haya dotado a la organización de un conjunto de servicios que se ocupen de unificar, por ejemplo, el alta de un usuario, y que ya contendrán todas las verificaciones de calidad de datos que queremos imponer. Y eso por mencionar aspectos ajenos a la propia calidad de la información en sí. Sin un diccionario de datos, la definición, implantación y seguimiento del éxito de las políticas que se van implantando es mucho más complicado.

El problema es, por tanto, quién le pone el cascabel al gato. Si te ha tocado en suerte, bueno, herramientas hay. Tenemos Master Data Services, que es la que viene de serie con SQL Server desde hace un par de versiones, pero igual que esa otro montón de ellas, ya que es un nicho de negocio que viene de antaño y que ha dado lugar a que numerosas compañías traten de cubrirlo. Pero no te engañes, son herramientas, no resolverán ninguno de los problemas importantes que deben afrontarse, que son, por citar algunos:

  1. Definir las características de cada atributo de cada entidad existente.
  2. Asignar cada campo de cada tabla a uno de esos atributos definidos.
  3. Implantar el diccionario en los nuevos desarrollos (fácil) y en los existentes (muuuuuy difícil).
  4. Mantener actualizado el diccionario.

Las herramientas te dan las vías de actualización, mantenimiento, incluso te pueden ayudar a propagar los cambios. Y eso sí, una vez que existe, todo es mucho más llevadero.

Como referencia final, dejo un par de enlaces más sobre MDS:

  1. Blog del equipo de desarrollo: http://blogs.msdn.com/b/mds/
  2. Zona de aprendizaje: http://msdn.microsoft.com/en-us/sqlserver/ff943581.aspx

La gente de Lepide Software me ha invitado a probar su herramienta de auditado para SQL Server, LepideAuditor for SQL Server. No es sólo un profiler, llega bastante más lejos, sobre todo cuando se está bajo determinadas condiciones especiales (empresa cotizadas en bolsa, o bajo SOX en general) y que es tanto más de utilidad cuantas más instancias hay que vigilar y menos brazos se disponen para hacerlo.

Así, te permite tener bajo control los cambios estructurales en todas las instancias de todas las bases de datos de tu organización de una forma desatendida, ya que además de ligero, el agente que se ocupa del rastreo conserva de forma indefinida cada cambio que se produce, para una posterior auditoría si esta se produce.

Panel de tareas

Panel de tareas de LepideAuditor, desde donde ejecutar la instalación del agente para auditar nuevas instancias, además de acceso a la ayuda.

Llevar un registro tan exhaustivo de cualquier modificación que suceda sin esta ayuda se puede lograr, pero ¿a qué precio?  Nos vemos sometidos muchas veces a un más que estricto control del gasto en el que una de las variables es olvidada de forma tan habitual como incomprensible: mi tiempo cuesta dinero, y mucho. Tal y como está el patio, hacer uso de un software que nos ahorre horas de trabajo administrativo de escaso valor añadido es rápidamente amortizado. Dicho de otro modo, haz el cálculo de la última vez que tuviste que preparar un informe sobre los cambios en una instancia o una labor pseudo-policial para determinar quién y cuándo se hizo aquello que nos dejó sin servicio aquel día. Esta herramienta te permite no sólo llevar esa auditoría, sino también programar la obtención de informes entregados vía mail de forma periódica. Con su pantalla de filtros podrás encontrar rápidamente aquello que vas buscando (filtro por fechas, por tipo de objeto y por acciones).

LepideAuditor Reports Scheduler

LepideAuditor permite agendar la obtención de informes entregados por email con la selección de eventos que se precisen

La he probado (algo que podemos hacer todos, hay una versión trial totalmente funcional que puedes obtener aquí) y he podido comprobar por mí mismo que la instalación es muy sencilla, tanto el cliente como los agentes para cada servidor, que no hace falta un curso de formación para utilizarla, y que cumple fielmente con su cometido: ser nuestros ojos allí donde los nuestros no llegan.

Imagen de la interfaz de LepideAuditor

Esta es la interfaz de Lepide Auditor. Algunos datos han sido ocultados.

Es tan simple como puede observarse en la imagen. Seleccionas la instancia, seleccionas la base de datos y ya está. Por cierto, todo ello se almacena en una base de datos SQL Server, lo que facilita bastante el mantenimiento (no hay logs externos, ni nada que quede fuera del servidor). Si seleccionas la instancia, entonces podrás ver aquellos eventos que han tenido lugar a nivel de instancia.

Adicionalmente, cuenta con la posibilidad de enviar alertas por mail en tiempo real que te avisen de hechos que tengan lugar a nivel de instancia (fallo en un login, borrado de una base de datos, etc) o a nivel de base de datos (cualquier cambio en la estructura de un objeto), lo que viene a completar las capacidades de auditado con funcionalidades de monitorización.

Creación de nueva alerta

Creación de nueva alerta, estos son los eventos que se pueden capturar

En resumen, LepideAuditor for SQL Server es una utilidad para el administrador de bases de datos que nos ayuda a cubrir de forma eficaz y con un esfuerzo mínimo los cambios estructurales de nuestras instancias y bases de datos, nos ayuda en la trazabilidad para auditorías especiales y nos permite dedicar nuestro tiempo a aquellas tareas en las que sí es necesaria una intervención de calidad, haciéndonos más productivos. Y con soporte completo para cualquier versión de SQL Server, desde SQL Server 2000 a 2012.

[Este es un post patrocinado por Lepide Software]

Proseguimos esta serie de apuntes, o reflexiones más bien, acerca de este tema tan amplio. Me centraré en esta entrada en una circunstancia bastante curiosa por la que estamos atravesando en el proyecto de estas características que ahora mismo tengo entre manos, y que entiendo que es un punto común a cualquier proyecto similar.

Lo habitual cuando se inician estos trabajos, como ya indicaba en la anterior entrada, es hacerse una composición de lugar. Eso consiste en hacer unos perfilados, unos simples recuentos que nos permitan saber en qué punto nos encontramos. Eso conduce a sentenciar “Tenemos el teléfono del 75% de nuestros clientes”. Pero vaya, eso no es del todo correcto. Dentro de esos datos de contacto habrá un gran cúmulo de datos que no sean válidos, sino que se rellenaron sin cabeza ninguna para que el formulario dejara grabar el resto de las informaciones. A lo mejor, alguno habrá que piense que colocando un asterisco junto al nombre de un datos en una pantalla ya sólo con eso conseguimos que el usuario introduzca ahí datos fiables y de garantías. Pero no, nada más lejos de la realidad. Con eso sólo consigues que ponga algo ahí, no que eso sea correcto. Y es más, prueba a pedirlos dos veces, verás qué grandes resultados obtienes.

Me estoy refiriendo a que antes de nada, hay que sacar la basura. Para ello, triste es decirlo, hay que limpiar de datos “bote” nuestras bases de datos. Su detección es bastante empírica, pero bastante simple, incluso con T-SQL (yo recomendaría el uso de Data Profiling Task and Tools, de SSIS http://msdn.microsoft.com/es-es/library/bb895310.aspx), para ver si tenemos teléfonos que aparecen en una docena de clientes distintos y sin relación aparente. Pero ha de dar lugar a algo que será muy importante a lo largo de la vida del proyecto: el catálogo de datos no válidos, listas negras o como queramos llamarlo. Son teléfonos, emails, DNIs, e incluso nombres y apellidos que tenemos que almacenar para no tratarlos. Así, teléfonos como el “666 666 666″, emails como “a@a.es” y nombres como “Pepito Pérez” aparecerán con frecuencia referenciando muchas entradas. Hay que limpiarlos y anotarlos en la lista. Con esa lista luego se pueden hacer cosas como una base de conocimiento para DQS, eso está claro, pero también, comprobar que el dato no esté ahí en el proceso de alta de un nuevo cliente, y que si alguno quiere registrarse en tu web como “Pepito Pérez” se lo tenga que currar un pelín más.

Lo malo de esto es que el ratio de datos bajará, lo que quizá no agrade al jefe, que fácilmente podría argumentar, vaya, teníamos el 75% de teléfonos, y ahora tenemos sólo el 60%”. La cuestión es que nunca se tuvo ese porcentaje. De hecho, si se hizo el estudio con habilidad, ya debieron filtrarse esos casos, lo que pasa es que no siempre es tan sencillo percatarse.

Además de una limpieza que mejora la calidad, un efecto también muy deseable es que consigues huecos para completar con datos de verdad, y que los datos dummy estaban ocupando sin sentido. Si bien, insisto, esto a barridos no se arregla, una de las mayores dificultades para la depuración es lo que obliga a discernir, dados dos datos, elegir uno de ellos como el bueno. Si podemos facilitar la tarea haciendo unas discriminaciones por mera frecuencia, ya solo con eso habremos ahorrado la revisión de muchos registros y reducido el coste de la tarea de depuración manual en la que siempre han de acabar estos procesos de calidad.

Pero a eso ya llegaremos.