Vous êtes sur la page 1sur 26

Simular TRUNC en SQL Server

TRUNC es una funcion de PL/SQL muy popular en el mundo ORACLE, recibe una fecha con hora
y la devuelve truncada a las 00:00:00.
Es decir si se pasa como parametro 25/08/2009 19:45:23 devuelve 25/08/2009 00:00:00.
Personalmente me parece una funcin extremadamente util - por ejemplo para obtener los
registros correspondientes a un dia concreto o rango de fechas- , pero que
inexplicablemente SQL Server no incorpora de forma nativa esta funcin, aunque el problema se
mitiga gracias a los nuevos tipos de datos de SQL Server 2008 para fechas.
Por supuesto no es dificil obtener realizar este tipo de consultas. Normalmente los
programadores recurren a las funciones CAST y CONVERT para realizar este tipo de consultas.
Esta forma de trabajar es propensa a errores, sobre todo de localizacion, por no hablar de
aquellos que confian en la coversion implicita ... Yo siempre recomiendo trabajar de forma
tipada - es decir con variables datetime.
Para conseguir el mismo resultado que nos da la funcion TRUNC podemos utilizar este simple
script, donde convertimos la fecha - en este caso getdate() - al tipo varchar(10) para truncar el
resto de caracteres (el tiempo), posteriormente volvemos a convertir a fecha para obtener una
nueva fecha.

DECLARE @fecha datetime,
@fechastring varchar(10)
SET @fechastring = CONVERT(varchar(10),getdate(), 103)
SET @fecha = CONVERT(datetime, @fechastring, 103)

SELECT @fecha

SELECT [<campos>]
FROM [<tabla>]
WHERE [<campo_fecha>] > @fecha

Es importante que el formato sea el mismo es la dos conversiones, a varchar y a datetime.
Pero como hemos dicho la recomendacion es trabajar siempre de forma tipada, por lo que
podemes crear una funcion UDF.

CREATE FUNCTION dbo.trunc (@input datetime)
RETURNS datetime
AS
BEGIN
DECLARE @fecha datetime,
@fechastring varchar(10)
SET @fechastring = CONVERT(varchar(10),@input, 103)
SET @fecha = CONVERT(datetime, @fechastring, 103)
RETURN @fecha
END
Este sencillo ejemplo muestra como podramos utilizar la funcion.

CREATE TABLE FechaSample
(
id int IDENTITY PRIMARY KEY,
fecha_alta datetime
)

go

DECLARE @i int,
@j int ,
@f datetime
SET @j = 0
WHILE (@j < 10)
BEGIN
SET @f = getdate() + @j
SET @i = 0
WHILE (@i < 10)
BEGIN
INSERT INTO Fechasample
( fecha_alta)
VALUES
(@f)
SET @i = @i + 1
END
SET @j = @j + 1
END
go

SELECT * FROM fechaSample
WHERE
fecha_alta between dbo.trunc(getdate())
AND dbo.trunc(getdate() + 1)

Obtendriamos el siguiente resultado:
id fecha_alta
--- -----------------------
1 2009-10-06 12:21:45.587
2 2009-10-06 12:21:45.587
3 2009-10-06 12:21:45.587
4 2009-10-06 12:21:45.587
5 2009-10-06 12:21:45.587
6 2009-10-06 12:21:45.587
7 2009-10-06 12:21:45.587
8 2009-10-06 12:21:45.587
9 2009-10-06 12:21:45.587
10 2009-10-06 12:21:45.587

(10 row(s) affected)
Como siempre, cuando realizamos conversiones en una base de datos hay que tener en cuenta
que si convertimos una columna, no solo tendremos un coste adicional de procesamiento sino
que ademas dejaremos de utilizar los indices que pudieran estar definidos para el campo.
Tenedlo en cuenta.
Cmo insertar un fichero .xml en un campo de tipo XML
de SQL server 2005 o 2008
A peticin de unos alumnos aqui teneis el cdido de cmo insertar directamente un fichero
XML en un campo de tipo XML con .net aunque sea con un poco de retraso:
En C#

using (SqlConnection Conn = new SqlConnection(ConnectionString))
{
Conn.Open();
using (SqlCommand Cmd = Conn.CreateCommand())
{
Cmd.CommandText = @"INSERT INTO TablaConXML
VALUES
(4,'CampoNoXML',@punteroaCampoXML)";
SqlXml ParametroSQLXML
= new SqlXml(new XmlTextReader("Fichero.Xml"));
Cmd.Parameters.AddWithValue("@punteroaCampoXML"
, ParametroSQLXML);
Cmd.ExecuteNonQuery();
}
}
En VB.Net

Using Conn As SqlConnection = New SqlConnection(ConnectionString)
Conn.Open()
Using Cmd As SqlCommand = Conn.CreateCommand()
Cmd.CommandText = "INSERT INTO TablaConXML
VALUES
(4,'CampoNoXML',@punteroaCampoXML)"
Dim ParametroSQLXML As SqlXml
= New SqlXml(New XmlTextReader("Fichero.Xml"))
Cmd.Parameters.AddWithValue("@punteroaCampoXML", ParametroSQLXML)
Cmd.ExecuteNonQuery()
End Using
End Using
SET IDENTITY_INSERT ON y LinqToSQL
Recientemente he tenido que realizar un pequeo programa para migrar datos. Leia de una
base de datos y grababa en otra, ambas SQL Server 2000.
Me decid a usar VS 2008 y LinqToSQL por la rapidez en el desarrollo. No poda usar Integration
Services ni DTS (o al menos no me parecio practico), ya que entre otras cosas tenia que crear y
tratar documentos.
El caso es que a la hora de realizar inserciones en la tabla de destino, la clave primaria estaba
definida como identity - pero necesitaba insertar los mismos valores en ambas bases de datos -
y me encontraba con el siguiente error.
Explicit value must be specified for identity column in table 'DatosIdentity' when
IDENTITY_INSERT is set to ON.
La solucion es sencilla, pero seguro que a mas de uno le ahorro un dolor de
cabeza. Sencillamente debemos habilitar la insercin en los campos de identidad a travs de la
instruccion SET IDENTITY_INSERT. Pero al estar utilizando Linq To SQL la cosa cambia entre
SQL Server 2005 y SQL 2000. Mientras que SQL Server 2005 todo funciona bien a la primera,
en SQL Server 2000 se produce el error anterior.
Vamos a crear un tabla con un campo identity y un proyecto con Visual Studio 2008 en el que
grabamos datos en dicha tabla.
Primero creamos la tabla sobre SQL Server 2005.

CREATE TABLE DatosIdentity
(
Id int IDENTITY NOT NULL,
Dato varchar(50) NULL,
Fecha datetime NULL
CONSTRAINT PK_DatosIdentity PRIMARY KEY (Id)
)

En nuestro proyecto de VS 2008 aadimos un formulario y modelo de datos de
LinqToSQLClasses (ver como) conectado a SQL Server 2005.
En el formulario programos un botn de la siguiente manera:

using (DataClasses1DataContext ctx = new DataClasses1DataContext())
{
ctx.ExecuteCommand("SET IDENTITY_INSERT DatosIdentity ON");
DatosIdentity datos = new DatosIdentity();
datos.Id = 1;
datos.Dato = "Devjoker.com";
datos.Fecha = DateTime.Now;
ctx.DatosIdentities.InsertOnSubmit(datos);
ctx.SubmitChanges();
ctx.ExecuteCommand("SET IDENTITY_INSERT DatosIdentity OFF");
}
En SQL Server 2005 funcionara bien, pero si cambiamos la cadena de conexion y empezamos a
trabajar con con SQL Server 2000 obtendremos el sguiente error:
Explicit value must be specified for identity column in table 'DatosIdentity' when
IDENTITY_INSERT is set to ON.
La solucion es cambiar los decoradores de la propieda identity y cambiar el
atributo IsDbGenerated=false.

[Column(Storage="_Id", AutoSync=AutoSync.OnInsert,
DbType="Int NOT NULL IDENTITY",
IsPrimaryKey=true,
IsDbGenerated=false)]
public int Id
{
get
{
return this._Id;
}
set
{
if ((this._Id != value))
{
this.OnIdChanging(value);
this.SendPropertyChanging();
this._Id = value;
this.SendPropertyChanged("Id");
this.OnIdChanged();
}
}
}
Con esto funcionar.
El porque no lo s, cosas que tienen las nuevas tecnologas.
LinQ To SQL - Un ejemplo sencillo
En este articulo vamos a intentar explicar como funciona Linq To SQL. Para empezar
diremos que Linq To Sql es un ORM ligero para bases de datos, con soporte para
Linq integrado, disponible en la nueva versin de .NET framework.
Para quienes no sepan que es un ORM diremos que es un mapeo entre objetos de bases de
datos y objetos de un lengunaje de programacin, en nuestro caso sern clases de C#. Es
decir, a cada objeto de la base de datos le corresponde un objeto (Clase) de C#.
Lo primero que vamos a necesitar para desarrollar nuestro ejemplo es una pequea base de
datos de SQL Server 2005, que crearemos ejecutando el siguiente script.

CREATE DATABASE LINQDB
GO
USE LINQDB
GO

if exists (
select 1
from sysobjects
where id = object_id('Perfiles')
and type = 'U'
)
drop table Perfiles
go

if exists (
select 1
from sysobjects
where id = object_id('Usuarios')
and type = 'U'
)
drop table Usuarios
go

if exists (
select 1
from sysobjects
where id = object_id('UsuariosPerfiles')
and type = 'U'
)
drop table UsuariosPerfiles
go
/*==============================================================*/
/* Table: Perfiles */
/*==============================================================*/
create table Perfiles
(
CodPerfil int identity,
Perfil varchar(100) not null,
Activo char(1) not null
constraint CKC_ACTIVO_PERFILES
check (Activo in ('S','N')),
constraint PK_PERFILES
primary key (CodPerfil)
)
go
/*==============================================================*/
/* Table: Usuarios */
/*==============================================================*/
create table Usuarios
(
CodUsuario int identity,
Usuario varchar(255) not null,
Clave varchar(255) not null,
FxAlta datetime not null,
Activo char(1) not null
constraint CKC_ACTIVO_USUARIOS
check (Activo in ('S','N')),
constraint PK_USUARIOS
primary key (CodUsuario)
)
go
/*==============================================================*/
/* Table: UsuariosPerfiles */
/*==============================================================*/
create table UsuariosPerfiles
(
CodUsuario int not null,
CodPerfil int not null,
constraint PK_USUARIOSPERFILES
primary key (CodUsuario, CodPerfil)
)
go
alter table UsuariosPerfiles
add constraint FK_USUARIOS_REFERENCE_USUARIOS
foreign key (CodUsuario)
references Usuarios (CodUsuario)
go
alter table UsuariosPerfiles
add constraint FK_USUARIOS_REFERENCE_PERFILES
foreign key (CodPerfil)
references Perfiles (CodPerfil)
go

La base de datos es muy sencilla y contiene solo tres tablas:
Usuarios - Que contiene la lista de usuarios.
Perfiles - Que contiene la lista de perfiles.
UsuariosPerfiles - Que define los perfiles asignados a cada usuario.
En realidad en el articulo solo utilizaremos la tabla de usuarios, pero incluimos tres para
demostrar como el asistente de Visual Studio genera multiples clases con Linq To Sql.
Abrimos nuestro Visual Studio 2008 y creamos un nuevo proyecto de tipo Win Forms - es
solo la eleccin que yo he hecho, pero podemos usar Linq To Sql en cualquier proyecto!.
En primer lugar, seleccionamos aadir nuevo elemento y seleccionamos Linq To Sql Classes,
llamaremos al archivo LinqDb.dbml.


[ Ampliar Imagen]
Con esta accin debemos tener tres nuevos archivos en el proyecto.
LinqDb.dbml - es archivo xml que define el mapeo.
LinqDb.dbml.layout - es un archivo xml que utiliza el diseador.
LinqDb.designer.cs. - archivo de C# donde se definen las clases y el contexto (la conexin).

[ Ampliar Imagen]
Lo siguiente que debemos hacer es definir una conexin a nuestra base de datos. Abrimos el
explorador de servidores y aadimos una nueva conexin.

[ Ampliar Imagen]
Probamos la conexin y guardamos.
Lo siguiente que vamos a hacer es generar las clases de mapeo con la base de datos. En el
explorador del proyecto hacemos doble click en el archivo Linqdb.dbml - con lo que Visual
Studio abrir el diseador grfico.
En la ventana del explorador de servidores, seleccionamos la base de datos que hemos
creado al inicio del articulo y desplegamos las tablas.

[ Ampliar Imagen]
Seleccionamos las tablas que queramos mapear a C# y arrastramos sobre el diseador. Al
arrastrar generamos la clases de mapeo - clases que representan los objetos de base de datos.

[ Ampliar Imagen]
Visula Studio nos preguntar si queremos almacenar la informacin de la conexin a la base
de datos en el archivo de configuracin de la aplicacin.
Una vez hecho esto, podemos examinar el visor de clases, y veremos que se han aadido las
siguientes clases a nuestro proyecto:
Una clase DataContext - Que representa el contexto de la conexin.
Una clase por cada tabla que hayamos seleccionado. Cada clase tendr una propiedad por cada
campo de la tabla a la que representa.

[ Ampliar Imagen]
En nuestro proyecto se han creado las siguientes clases:
LinqDbDataContext
Usuarios
Perfiles
UsuariosPerfiles
La explicacin de estas clases la abordaremos es futuros articulos, pero es importante
destacar que si queremos hacer algn cambio en las clase generadas debemos hacerlo en un
archivo aparte, aprobechando que son clases parciales. De esta forma podremos regenerar el
ORM en cualquier momento sin afectar a nuestro cdigo.
De momento, Visual Studio ha realizado todo el trabajo por nosostros.
Con todo esto aadimos un formulario a nuestro proyecto, en el colocamos tres campos para
grabar datos en la tabla de usuarios - el nombre de usuario, la clave de acceso y la fecha de
alta.

[ Ampliar Imagen]
Escribimos la siguiente clase LinqTest, que dar de alta y buscar usuarios. Tiene los
siguientes mtodos:
AddUsuario - que dar de alta un usuario en la base de datos
QueryUsuarioByCodigo - que utilizar Linq para recuperar los datos de un usuario.

partial class LinqTest
{
LinqDbDataContext dc = new LinqDbDataContext();
const char SI = 'S';
internal void AddUsuario(string nombre, string clave,
DateTime fecha)
{
Usuarios usuario = new Usuarios();
usuario.Usuario1 = nombre;
usuario.Clave = clave;
usuario.Activo = SI;
usuario.FxAlta = fecha;
dc.Usuarios.InsertOnSubmit(usuario);
dc.SubmitChanges();
//Probamos si recupera bien los identitys
MessageBox.Show(
String.Format("Se ha dado de alta el usuario {0}
con el cdigo {1}",
nombre,
usuario.CodUsuario.ToString())
);
}

internal Usuarios QueryUsuarioByCodigo(int codigo)
{
var usu = from u in dc.Usuarios
where u.CodUsuario == codigo
select u;
if (usu.Count() > 0)
return usu.First();
else
return null;
}
}

Y ahora el cdigo del formulario para consumir la clase, tambin muy sencillo:

public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private void cmdAlta_Click(object sender, EventArgs e)
{
// Damos de alta el registro e inicializamos
LinqTest obj = new LinqTest();
obj.AddUsuario( txtUsuario.Text ,
txtClave.Text,
dtpFxAlta.Value);
PonerDatosEnPantalla(null);
}

private void cmdBuscar_Click(object sender, EventArgs e)
{
// Buscamos y ponemos los datos en pantalla.
LinqTest obj = new LinqTest();
frmBuscar f = new frmBuscar();
f.ShowDialog();
int codUsuario = f.ReturnValue;
Usuarios u = obj.QueryUsuarioByCodigo(codUsuario);
PonerDatosEnPantalla(u);
}

void PonerDatosEnPantalla(Usuarios u)
{
if (u != null)
{ // Ponemos los datos
txtUsuario.Text = u.Usuario1;
txtClave.Text = u.Clave;
dtpFxAlta.Value = u.FxAlta;
}
else
{ // Inicializamos el formulario
txtUsuario.Text = String.Empty ;
txtClave.Text = String.Empty;
dtpFxAlta.Value = DateTime.Now;
}
}
}
El cdigo es muy sencillo, pero lo explicamos brevemente:
Boton de alta (cmdAlta_Click) - crea una instancia de LinqTest y le pasa los valores del
formulario para que d de alta un registro.
Botn de bsqueda (cmdBuscar_Click) - pide al usuario un cdigo y utilizando una instancia de
LinqTest consulta los datos para mostrarlos en pantalla.
Como podemos ver el cdigo se ayuda un formulario de busqueda, tambien muy sencillo.

[ Ampliar Imagen]
Por si alguien quiere el cdigo del formulario de busqueda ...

public partial class frmBuscar : Form
{
public frmBuscar()
{
InitializeComponent();
}

private void cmdBuscar_Click(object sender, EventArgs e)
{
this.Close();
}

public int ReturnValue
{
get { int i = 0;
int.TryParse(txtCodigo.Text,out i);
return i;
}
}
}
Pues bien, con esto terminamos. Saludos y hasta la proxima. DJK.
Formatear nmeros en SQL Server
Una duda que suele tener la gente es como formatear los datos numrico es SQL Server - Es
bastante comn tener que formatear un nmero completando con ceros hasta un nmero
determinado de posiciones.
Podemos hacer un bucle para concatenar la expresion resultante, pero no parece ni mucho
menos la opcin mas adecuada.
El siguiente script ilustra una forma ms eficiente de conseguir el mismo resultado.

DECLARE @num int,
@formato varchar(10),
@expresion varchar(10)

SET @formato = '0000000000'
SET @num = 2002
SET @expresion = RIGHT( @formato + cast(@num AS varchar), 10)
SELECT @expresion

La idea es concatenar una expresion de formato fijo - los ceros - con el nmero a formatear.
Posteriormente utilizamos la funcin RIGHT para recortar los n elementos de la derecha.
La salida de la ejecucion es la siguiente:

DATO_FORMATEADO
---------------
0000002002
Como podemos ver, hemos conseguido nuestro objetivo. Por supuesto, este truco es vlido
tambin para completar con espacios en blanco - muy util para obtener ficheros de ancho fijo -
o cualquier otro tipo de caracter.
Podemos hacer las cosas un poco mejor, y creamos una funcion a la que le pasaremos el
nmero a formatear y el formato de salida.

CREATE FUNCTION dbo.FormatNum
(
@number int,
@format varchar(50)
)
RETURNS varchar(50)
AS
BEGIN
DECLARE @len int
SET @len = len(@format)
RETURN RIGHT( @format + cast(@number AS varchar), @len)
END
Sobre una supuesta tabla Pais, que hemos cargado previamente con cinco registros - con el
cdigo autonumrico - ejecutamos la siguiente consulta:

SELECT
dbo.FormatNum(CodPais,'000000000000') AS CodPais,
NombrePais,
Activo
FROM
Pais
El resultado de la consulta es el siguiente:

CodPais NombrePais Activo
----------------- -------------- ------
000000000001 Pais 0 0
000000000002 Pais 1 0
000000000003 Pais 2 0
000000000004 Pais 3 0
000000000005 Pais 4 0
Como leer un campo XML de SQL Server 2005 con C#
El soporte nativo a XML de SQL Server 2005 es una de las caracteristicas ms interesantes
de la verisn. Cuando empezamos a trabajar con Xml nos encontramos con el problema de
como leer los datos guardados en formato Xml de la base de datos, es decir, como obtener una
instancia de XmlDocument a partir de una consulta a la base de datos.
Para este ejemplo, lo primero que vamos a necesitar es una tabla con un campo de
tipo Xml, para ello ejecutamos el siguiente script en nuestra base de datos.

CREATE TABLE TABLA_CON_XML
(
Id int identity not null,
CampoXml Xml,
CONSTRAINT PK_TABLA_CON_XML PRIMARY KEY (Id)
)
Para grabar datos en la tabla anterior necesitamos una segunda tabla con el objetivo de
realizar una consulta posterior con la clausula FOR XML de SQL Server 2005. La creamos con
el siguiente script:

CREATE TABLE INFO
(
Id int identity not null,
DbName varchar(100),
UserName varchar(100),
FxAlta datetime,
CONSTRAINT PK_INFO PRIMARY KEY (Id)
)

GO

INSERT INTO INFO
SELECT DB_NAME(), USER_NAME(), GETDATE()
Con esto, grabamos un registro en nuestra tabla utilizando para ello una consulta con la
clausula FOR XML.


DECLARE @Xml Xml
set @Xml = (SELECT * FROM INFO
FOR XML AUTO, elements)
INSERT INTO TABLA_CON_XML
(CampoXml) VALUES (@Xml)

GO

SELECT * FROM TABLA_CON_XML
La consulta debe haber generado un Xml como el que se muestra a continuacin.

<info>
<Id>1</Id>
<DbName>PRUEBAS</DbName>
<UserName>dbo</UserName>
<FxAlta>2007-08-13T11:07:11.077</FxAlta>
</info>
Ahora ya podemos escribir el cdigo C# necesario para acceder a la base de datos y
recuperar el Xml.
Lo primero que debemos hacer es importar los namespaces necesarios para trabajar
con SQL Server y Xml.

using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Xml;
Para el ejemplo, hemos realizado una sencilla aplicacin Windows en la que a travs de una
botn leemos el Xml de la base de datos y lo guardamos en disco.

private void button1_Click(object sender, EventArgs e)
{
XmlDocument doc = new XmlDocument();
string qs = @"Data Source=10.74.4.100;
Initial Catalog=PRUEBAS;
Persist Security Info=True;
User ID=sa;Password=xxxxxxxx";
SqlConnection conexion = new SqlConnection(qs);
conexion.Open();
LoadXml(conexion, doc);
conexion.Close();
doc.Save("C:\\OutputXml.xml");
}
El cdigo anterior, nicamente crea una instancia de XmlDocument y una conexion a
nuestra base de datos. Es el mtodo LoadXml el que realmente accede a la base de datos y
obtiene el Xml.
A continuacin se muestra el mtodo LoadXml:

protected bool LoadXml(SqlConnection cn, XmlDocument doc)
{
//Leemos el Xml de la base de datos.
string sql = @"SELECT Id, CampoXml
FROM TABLA_CON_XML
WHERE Id = @Id";
SqlCommand cm = new SqlCommand(sql,cn);
cm.Parameters.Add(new SqlParameter("@Id",1));
using (SqlDataReader dr = cm.ExecuteReader())
{
if (dr.Read())
{
SqlXml mixml = dr.GetSqlXml(dr.GetOrdinal("CampoXml"));
doc.LoadXml( mixml.Value);
return true;
}
else
{
return false;
}
}
}
Este mtodo consulta la base de datos con un objeto SqlDataReader y almacena el
resultado en una variable del tipo SqlXml(perteneciente al
namespace System.Data.SqlTypes). Con este tan solo queda cargar nuestra instancia
de XmlDocument con el valor del Xml leido de la base de datos a travs de LoadXml.
Como podemos ver, trabajar con C#, Xml y SQL Server 2005 es bastante sencillo.

Vous aimerez peut-être aussi