Mostrando entradas con la etiqueta trucos. Mostrar todas las entradas
Mostrando entradas con la etiqueta trucos. Mostrar todas las entradas

jueves, 4 de noviembre de 2010

Enviar ficheros por FTP usando Powershell

Con este script de Powershell podemos automatizar el envío por FTP de todos los ficheros que existan en un directorio y cumplan con un determinado filtro (por ejemplo, todos los .txt).

Me he basado en este post para hacerlo: How to upload a file, the PowerShell way

Actualización 17/11/2010: añado $R.proxy = $null para que no de problemas con proxys HTTP :)


#Directorio del que obtenemos los ficheros
$source_folder="c:\temp\"

#Filtro que deben cumplir los ficheros que se envían
$files_filter=$source_folder + "*.txt"

#Archivo donde se guardará el log del proceso
$log_file="c:\FTP.log"

#URL de nuestro servidor FTP
$FTP_url="ftp://myserver.ftp/destino/"
$FTP_user="usuario"
$FTP_password="password"

# Obtenemos todos los ficheros del directorio que cumplan con el filtro
foreach ($filename in Get-Childitem $files_filter)
{
#Path completo del fichero de origen que se enviará por FTP
$Source=$source_folder + $filename.name

#Guardamos una línea en el archivo de logs
$DATE=Get-Date
Add-Content $log_file "[INFO]: $DATE - $Source"

#Path completo del fichero de destino
$Destination=$FTP_url + $filename.name

#Aquí comienza el control de errores, se verá más abajo con el "trap"
&{
#Usamos el metodo estático Create para crear una petición web.
#Pasamos el destino como argumento y lo convertimos
#en un objeto FtpWebRequest.
$R=[System.Net.FtpWebRequest][System.Net.WebRequest]::Create($Destination)

#Especificamos a la petición cómo nos loguearemos
#(usando un objeto NetworkCredential)
$R.Credentials = New-Object System.Net.NetworkCredential($FTP_user, $FTP_password)
$R.proxy = $null
#... y qué tipo de método se usará. Un file upload.
$R.Method = "STOR"

#Aquí se usa el método más simple para obtener los bytes
#de un fichero a un array de bytes.
$FileContens = [System.IO.File]::ReadAllBytes($Source)

#Finalmente, se ponen el array de bytes en la petición web.
$S = $R.GetRequestStream()
$S.Write($FileContens, 0, $FileContens.Length)

#Se cierran el stream y se libera el objeto
$S.Close()
$S.Dispose()
}
trap [SystemException]
{
#Si se produce un error, lo guardamos en el fichero de logs
#y continuamos con el siguiente fichero
$ERROR=$_
$DATE=Get-date
Add-Content $log_file "[ERROR]: $DATE - $ERROR"
continue
}
}



Ahora lo suyo es guardarnos el script en un fichero .ps1 y crear una tarea programada que lo ejecute, por ejemplo, que cada media hora compruebe el directorio y envíe los ficheros.

Y ya que estamos, podemos crear la tarea programada con Powershell:

SCHTASKS /Create /RL HIGHEST /SC MINUTE /MO 30 /TN "Enviar ficheros por FTP" /NP /TR 'powershell.exe –Command "c:\enviar_ficheros_FTP.ps1"'

Bola extra
Tal vez os interese también obtener los ficheros de un FTP: How to download a file, the PowerShell way

Happy codding ;)

martes, 2 de marzo de 2010

T-SQL: SET ROWCOUNT vs. SELECT TOP

“Si hay 2 formas de hacer lo mismo, una seguro que da problemas.”
No sé si esta es una de las Leyes de Murphy, pero debería serlo :P

SET ROWCOUNT es uno de esos settings “con trampa” que tiene T-SQL y que si no sabes usarlos adecuadamente te puede provocar muchos quebraderos de cabeza.

Según MSDN: Hace que SQL Server detenga el procesamiento de la consulta una vez que se han devuelto las filas especificadas.

Ah, qué bonito, si sólo quiero que una consulta me devuelva 20 registros pues pongo “SET ROWCOUNT 20;” antes de ejecutarla y listo.
Vamos, lo mismo que si hago un SELECT TOP 20 …

Bueno, sabiendo esto se puede elegir entre utilizar uno u otro método para limitar los resultados. Pero SET ROWCOUNT, al tratarse de un setting afecta a TODAS las consultas que se ejecutan dentro del mismo contexto y aquí es donde viene el problema.

Por ejemplo, supongamos que tenemos 2 tablas relacionadas y una función llamada MiFuncion() que hace lo siguiente:


SELECT idPadre FROM TablaPadre WHERE campoPadre = @parametro


Ahora bien, si hacemos esto:


SET ROWCOUNT 10;
SELECT * FROM TablaHija WHERE idPadre in (select idPadre from MiFuncion(@parametro))


La llamada a MiFuncion (@parametro) devolverá únicamente 10 registros, y a su vez la SELECT inicial también devolverá 10 registros, lo que puede provocar que no obtengamos el resultado deseado :(

¿Y cómo se puede solucionar esto? Pues es relativamente sencillo, cambiamos la llamada a la función por una subconsulta:


SET ROWCOUNT 10;
SELECT * FROM TablaHija WHERE idPadre in (SELECT idPadre FROM TablaPadre WHERE campoPadre = @parametro)


Y así, la subconsulta devuelve los registros que tenga que devolver, sin tener en cuenta el número de SET ROWCOUNT y la SELECT sí que devuelve 10 registros O_o El que lo entienda que me lo explique.

Así que ya sabéis, si vais a usar SET ROWCOUNT, hacerlo con cuidado y aseguraros de desactivarla usando SET ROWCOUNT 0. En cualquier caso, es menos peligroso usar SELECT TOP, ya que solo afecta a la consulta en la que se especifica.

Tampoco es muy aconsejable mezclar SELECT TOP … con SET ROWCOUNT … porque SET ROWCOUNT anula la palabra clave TOP de la instrucción SELECT si el número de filas es inferior.

Happy codding ;)

BOLA EXTRA: a partir de la próxima versión de SQL Server (la que siga a la 2008) la utilización de SET ROWCOUNT no afectará a las instrucciones DELETE, INSERT ni UPDATE.
No me quiero ni imaginar los problemas que puede provocar SET ROWCOUNT con un UPDATE O_o

viernes, 26 de febrero de 2010

Otro test de agudeza visual: ¿qué código es peor?

Opción 1:


Dim intVariable as Integer

Try
intVariable = LlamadaaMetodoQuePuedeDevolverNothing()
Catch ex as Exception
intVariable = 0
End Try



Opción 2:


Dim intVariable as Integer = 0

Try
intVariable = LlamadaaMetodoQuePuedeDevolverNothing()
Catch ex as Exception

End Try





Respuesta: la opción 2, que provoca un precioso "agujero negro de excepciones".

PD: los 2 ejemplos están basados en hechos reales, que no tengo tanta imaginación :P

viernes, 18 de diciembre de 2009

Establecer propiedades del IDE de Visual Studio dependiendo del proyecto que se abre

En el proyecto en el que estoy trabajando tenemos en el Team Foundation un branch con la solución de desarrollo y otro con la misma solución de producción. Considero que es una buena práctica que evita subir código no probado a producción y por ahora nos está llendo bastante bien.

El "problema" es que a veces tenemos que tener ambas soluciones abiertas a la vez, y como los cambios que tienen suelen ser muy pequeños, hay que tener mucho cuidado para no meter la pata y no modificar el código en el branch que no toca.

Comentándolo con mi compañero Pere, se me ocurrió que estaría bien tener un IDE diferente para cada una de las soluciones, algo sencillo, como cambiar el color de fondo o el tipo de letra. Y nos pusimos a buscar cómo hacerlo, gracias a San Google encontramos rápido la solución:

En este post: http://geekswithblogs.net/sdorman/archive/2007/04/25/111981.aspx

se explican 2 formas de hacerlo, una con macros que a mí me pareció un poco complicada y otra bastante más sencilla ejecutando el Visual Studio desde línea de comandos.

Los pasos que hay que seguir:

1 - En el menú Tools - Options dentro Environment - Fonts and Colors, establecemos el tipo de letra y los colores que queramos para, por ejemplo, la solución de desarrollo.

2 - Exportamos los settings desde el menú Tools - Import and Export Settings...

3 - Seleccionamos la opción Export selected enviromment settings, marcamos todos los settings y seleccionamos una ruta para guardar el fichero, por ejemplo: "C:\VSSettings\Desarrollo.vssettings"

Repetimos los pasos con un fondo distinto o un tipo de letra diferente y lo guardamos en "C:\VSSettings\Produccion.vssettings"

4 - Creamos un acceso directo en el escritorio, en el destino seleccionamos el fichero devenv.exe que es el que lanza el Visual Studio (generalmente está en C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE).

5 - Abrimos las propiedades del acceso directo y cambiamos el destino por:

devenv.exe "C:\Mi_Solucion_Desarrollo\MiSolucion.sln" /resetsettings "C:\Vssettings\desarrollo.vssettings"

6 - Creamos otro acceso directo como en el paso 4.

7- Cambimos el destino por:

devenv.exe "C:\Mi_Solucion_Produccion\MiSolucion.sln" /resetsettings "C:\Vssettings\Produccion.vssettings"


Y listo, ya tenemos un acceso directo para que nos abra cada solución con una configuración del IDE de Visual Studio diferente.

Lo cierto es que tarda un poco más en iniciarse ya que tiene que cargar la configuración pero creo que nos evitará muchos dolores de cabeza :)


Happy codding ;)

martes, 10 de noviembre de 2009

Test de agudeza visual con Hashtables

¿Qué diferencia hay entre este código ...



Dim hstMiHash As New Hashtable()

If hstMiHash.ContainsKey("CLAVE") = False Then
hstMiHash.Add("CLAVE", "Valor")
Else
hstMiHash("CLAVE") = "Valor"
End If




... y este otro?



Dim hstMiHash As New Hashtable()
hstMiHash("CLAVE") = "Valor"





RESPUESTA:
ninguna :)


Hashtable.Add

viernes, 29 de mayo de 2009

Truco rápido: Histórico de cambios en una tabla usando un trigger

No me voy a enrollar explicando que es un trigger ni para que sirve, tenéis información de sobra en este link: CREATE TRIGGER (Transact-SQL)

Lo que voy a explicar aquí es como usar un trigger para que nos guarde en una tabla de históricos los cambios (INSERT y UPDATE) que se producen en una tabla. Para ello supongamos que tenemos una tabla con un identificador (campo Id) y un nombre (campo nombre), y queremos almacenar en la tabla Historico (Id,nombre_antiguo,nombre_nuevo), los cambios que se van produciendo.

Dado que se pueden ejecutar updates sobre varios registros a la vez, será necesario que nuestro trigger obtenga todos los cambios a la vez y los procese uno a uno, eso lo haremos con un cursor.

El truco está en cómo distinguiremos que se trata de un INSERT o de un UPDATE, es muy sencillo, en caso de ser un INSERT el motor de base de datos almacena un registro en la tabla temporal "INSERTED", y en el caso de un update, habrá 2 registros: uno en la tabla "DELETED" y otro en "INSERTED", es como si para actualizarlo, primero se borrase y después se volviese a insertar, cosas del SQL Server :P

Vamos con el código:


CREATE TRIGGER [dbo].[prueba_insert_update]
ON [dbo].[Pruebas]
AFTER INSERT, UPDATE
AS
BEGIN

SET NOCOUNT ON;


-- Identificador del registro
DECLARE @Id int


-- Variable para almacenar el nombre anterior
DECLARE @nombre_antiguo varchar(20)


-- Declaramos el cursor "Cursorito" para que contenga únicamente
-- los ID de la tabla temporal "INSERTED"
DECLARE Cursorito CURSOR FORWARD_ONLY
FOR
SELECT Id FROM INSERTED


-- Abrimos el cursor
OPEN Cursorito


-- Movemos el puntero al primer registro del cursor
FETCH NEXT FROM Cursorito into @Id


-- Recorremos el cursor, cuando @@Fetch_Status sea diferente de 0
-- habremos llegado al final
WHILE @@Fetch_Status = 0
BEGIN


SET @nombre_antiguo = ''


-- Buscamos en la tabla temporal "DELETED" si hay un registro con
-- el Id que toca en esta pasada, lo que significaría que
-- se ha producido un UPDATE
SELECT @nombre_antiguo = ISNULL(nombre,'')
FROM DELETED
WHERE Id = @Id


-- Si no hay ningún registro que cumpla la condición la
-- variable @nombre_antiguo contendrá '' y se tratará
-- de una inserción


-- Añadimos un registro en la tabla de históricos
INSERT INTO Historico
(Id,nombre_antiguo,nombre_nuevo)
SELECT @Id, @nombre_antiguo, nombre
FROM INSERTED
WHERE Id = @Id


-- Nos movemos al siguiente registro
FETCH NEXT FROM Cursorito into @Id

END


-- Cerramos el cursor y liberamos el recurso
CLOSE Cursorito
DEALLOCATE Cursorito

END



Happy coding ;)

viernes, 8 de mayo de 2009

Buscar en campos de texto con T-SQL: mucho más que LIKE

Una de las primeras cosas que se aprende cuando se empieza con T-SQL es a usar la cláusula LIKE para hacer comparaciones en campos de tipo texto (char, varchar o text). Pero esta cláusula está bastante limitada y no ofrece un buen rendimiento, así que ¿qué podemos utilizar en vez de LIKE?

Una muy buena opción es CONTAINS (que para ser sincero, descubrí de casualidad :P), que nos permite afinar bastante más las comparaciones y ofrece mucho mejor rendimiento.

Para poner un ejemplo del uso de CONTAINS voy a usar la base de datos PUBS (que ofrece Microsoft como ejemplo con SQL Server y se puede descargar desde aquí) y vamos a buscar registros en la tabla jobs filtrando por el campo job_desc.

Para que os hagáis una idea (y si no os apetece descargaros las bases de datos de ejemplo) la tabla jobs tiene los siguientes registros:


job_id job_desc min_lvl max_lvl
-------- -------------------------------------------------- ------- -------
1 New Hire - Job not specified 10 10
2 Chief Executive Officer 200 250
3 Business Operations Manager 175 225
4 Chief Financial Officier 175 250
5 Publisher 150 250
6 Managing Editor 140 225
7 Marketing Manager 120 200
8 Public Relations Manager 100 175
9 Acquisitions Manager 75 175
10 Productions Manager 75 165
11 Operations Manager 75 150
12 Editor 25 100
13 Sales Representative 25 100
14 Designer 25 100


Para poder usar CONTAINS primero debemos crear un índice FULLTEXT en la tabla, que se hace con las siguientes instrucciones:


CREATE FULLTEXT CATALOG ft AS DEFAULT;
CREATE FULLTEXT INDEX ON Jobs(job_desc)
KEY INDEX PK__jobs__173876EA
GO


Siendo PK__jobs__173876EA el índice principal (PRIMARY KEY) de la tabla.

Una vez hecho esto podemos empezar con los ejemplos:

Buscar palabra exacta

Obtendremos todos los registros que contengas ‘manager’ en el campo job_desc. Fijaros que no distingue mayúsculas y minúsculas:


select * from jobs where CONTAINS( job_desc, 'manager' )

job_id job_desc min_lvl max_lvl
-------- -------------------------------------------------- ------- -------
3 Business Operations Manager 175 225
7 Marketing Manager 120 200
8 Public Relations Manager 100 175
9 Acquisitions Manager 75 175
10 Productions Manager 75 165
11 Operations Manager 75 150


Además se pueden buscar frases (más de 1 palabra) y en las búsquedas se omiten los separadores.


select * from jobs where CONTAINS (job_desc, '"hire job"');
job_id job_desc min_lvl max_lvl
-------- -------------------------------------------------- ------- -------
1 New Hire - Job not specified 10 10



Buscar por aproximación con varias opciones

Buscamos los registros que contengan alguna palabra que empiece por ‘pub’ o por ‘edit’.


select * from jobs where CONTAINS(job_desc, '"pub*" OR "edit*"');

job_id job_desc min_lvl max_lvl
-------- -------------------------------------------------- ------- -------
5 Publisher 150 250
6 Managing Editor 140 225
8 Public Relations Manager 100 175
12 Editor 25 100


CONTAINS admite los operadores AND, AND NOT, OR.

Buscar palabra aproximada cerca de otra

Obtenemos registros que contengan una palabra que empiece por ‘pub’ y que esa palabra esté cerca de ‘manager’.


select * from jobs where CONTAINS (job_desc, '"pub*" NEAR manager')

job_id job_desc min_lvl max_lvl
-------- -------------------------------------------------- ------- -------
8 Public Relations Manager 100 175



CONTAINS permite además buscar por palabras derivadas y sinónimos, pero para eso es necesario usar un diccionario de sinónimos.

Happy codding ;)

Bola extra
Además de CONTAINS tenemos otras opciones para este tipo de búsquedas como FREETEXT
y CONTAINSTABLE

viernes, 27 de marzo de 2009

3 formas de obtener la versión de SQL Server desde T-SQL

En un mundo perfecto todas nuestras instalaciones tendrían las mismas versiones de las aplicaciones, motores de bases de datos y librerías auxiliares, pero como este mundo es una utopía, a veces tenemos que averiguar con qué versión del motor de base de datos está trabajando la aplicación.


En el caso de SQL Server he descubierto 3 formas de averiguar la versión desde T-SQL, con cualquiera obtendremos la versión completa pero en la mayoría de los casos únicamente nos interesará la major release para ejecutar o no características de las nuevas versiones o que están obsoletas en la última versión pero siguen funcionando en versiones antiguas.


Vamos con las 3 opciones:


@@VERSION
http://msdn.microsoft.com/es-es/library/ms177512.aspx


Como es una variable de sistema sirve para cualquier versión de SQL Server, pero a partir de la versión 2005 devuelve un nvarchar en vez del varchar de las versiones anteriores.





declare @version char(2)

select @version = substring(@@version, charindex('- ', @@version) + 2, charindex('.',@@version) - charindex('- ', @@version)-2)



SERVERPROPERTY(‘ProductVersion’)


http://msdn.microsoft.com/es-es/library/ms174396.aspx

Esta opción sólo está disponible a partir de SQL Server 2000 y devuelve un valor de tipo sqlvariant, por lo que habrá que convertirlo a varchar o nvarchar para sacar la versión principal.





declare @productversion varchar(50)

declare @version char(2)

select @productversion = CAST(SERVERPROPERTY('productversion') AS VARCHAR(50))

set @version = SUBSTRING(@productversion,1,CHARINDEX('.',@productversion)-1)




xp_msver ‘ProductVersion’

http://technet.microsoft.com/es-es/library/ms187372.aspx

Quizás sea el método más complicado porque devuleve un conjunto de resultados con la siguiente forma:

Index Name Internal_Value Character_Value
------ -------------------------------- -------------- ----------------------
2 ProductVersion 589824 9.00.1406.00

(1 filas afectadas)

Pero también se puede obtener la versión con algo como esto:





CREATE TABLE #VersionTable(
[Index] int,
Name varchar(30),
Internal_Value int,
Character_Value varchar(50))
GO

INSERT INTO #VersionTable
EXEC master..xp_msver 'ProductVersion'

DECLARE @Version char(2)

SELECT @Version = (
SELECT SUBSTRING(Character_Value,1,CHARINDEX('.', Character_Value)-1)
FROM #VersionTable)

drop table #VersionTable



Con cualquiera de las 3 opciones obtendremos lo siguiente en la variable @Version:

‘7’ -> SQL Server 7
‘8’ -> SQL Server 2000 y MSDE
‘9’ -> SQL Server 2005
‘10’ -> SQL Server 2008

Happy coding ;)

martes, 10 de marzo de 2009

Truco rápido: todos los mensajes de error de SQL Server a mano

Cuando trabajas con SQL Server estás acostumbrado a que los mensajes de error que aparecen en el fichero ERRORLOG o en el visor de sucesos sean bastante crípticos y lo normal es acabar tirando de [ponga aquí su buscador favorito] para saber de qué se trata.

Pues bien, hace tiempo que descubrí ,dentro de la enormidad que son los libros en pantalla de SQL Server, una página con los códigos de error organizados por número, y desde entonces es mi primera opción a la hora de buscar, así que aquí la tenéis:

http://technet.microsoft.com/es-es/library/cc645603.aspx (en castellano)

http://technet.microsoft.com/en-us/library/cc645603.aspx (en inglés)


Espero que os sirva de ayuda ;)