{"id":454,"date":"2017-11-03T08:13:05","date_gmt":"2017-11-03T08:13:05","guid":{"rendered":"https:\/\/sqldoubleg.live-website.com\/?p=454"},"modified":"2017-11-09T22:42:56","modified_gmt":"2017-11-09T22:42:56","slug":"using-tokens-in-sql-agent-jobs","status":"publish","type":"post","link":"https:\/\/www.sqldoubleg.com\/es\/2017\/11\/03\/using-tokens-in-sql-agent-jobs\/","title":{"rendered":"Uso de tokens en SQL Agent Jobs para renombrar los archivos de salida"},"content":{"rendered":"<p>En esta publicacion, quiero contaros acerca de una caracteristica bastante util que se puede usar dentro de los trabajos del Agente SQL en SQL Server&nbsp;En esta publicaci\u00f3n, quiero contarte sobre un error que me acaba de ocurrir recientemente. Puede que sea un poco tonto, pero a\u00fan as\u00ed puede hacerte perder un rato tratando de descubrir por qu\u00e9.<\/p>\n<p><strong>Antecedentes<\/strong><\/p>\n<p>SQL Server viene con su propio planificador de tareas para todas las versiones de pago (lo siento, usuarios Express) que es el Agente SQL. Obviamente, esto es muy \u00fatil para llevar a cabo las diferentes tareas de mantenimiento que normalmente realizamos en nuestras bases de datos, como copias de seguridad, mantenimiento de \u00edndices y estad\u00edsticas, comprobaciones de coherencia y otras que pueden aplicarse a vuestro entorno.<\/p>\n<p>Dentro de un paso de trabajo de SQL Agent, podemos usar algo llamado Tokens, y como podemos ver en <a href=\"https:\/\/docs.microsoft.com\/en-us\/sql\/ssms\/agent\/use-tokens-in-job-steps\" rel=\"noopener\" target=\"_blank\">BOL<\/a>.<\/p>\n<blockquote><p><strong>El uso de tokens cuando escribe los pasos de su trabajo le da la misma flexibilidad que proporcionan las variables cuando escribe programas de software. Despu\u00e9s de insertar un token en un script de paso de trabajo, el Agente SQL Server reemplaza el token en tiempo de ejecuci\u00f3n, antes de que el subsistema Transact-SQL ejecute el paso de trabajo.<\/strong><\/p><\/blockquote>\n<p>Entonces, de una manera simplificada, son un tipo de variables de entorno que se utilizar\u00e1n en un paso de trabajo.<\/p>\n<p>No tengo muchos casos de uso para esto, pero al menos tengo uno \ud83d\ude42 <\/p>\n<p>&nbsp;<br \/>\n<strong>Caso de uso<\/strong><\/p>\n<p>Por lo general, es una buena idea escribir la salida de los trabajos de su Agente SQL en un archivo, para poder investigar si ocurre alg\u00fan problema.<\/p>\n<p>Pero cuando definimos el archivo de salida, necesitamos elegir entre Anexar el resultado al mismo archivo una y otra vez, o sobrescribirlo, pero tanto una cosa como la otra frustra el prop\u00f3sito en mi humilde opini\u00f3n.<\/p>\n<p>Por otro lado, si te olvidas de rotar los archivos, pueden crecer bastante y luego encontrar cualquier error puede convertirse en una pesadilla. Si lo sobreescribimos, y no analizamos antes de que ocurra, el error se pierde.<\/p>\n<p>Entonces, hace alg\u00fan tiempo, escrib\u00ed un procedimiento almacenado que rota los archivos por m\u00ed y los ordena para que sea f\u00e1cil encontrar una fecha en particular.<\/p>\n<p>&nbsp;<br \/>\n<strong>El truco<\/strong><\/p>\n<p>El siguiente procedimiento almacenado har\u00e1 el truco, busca los archivos de salida para cada uno de los pasos, cambia el nombre y los mueve a una subcarpeta para se puedan encontrar f\u00e1cilmente.<\/p>\n<p>Obviamente, la estructura de las carpetas es como a mi me gusta .\/Jobs\/aaaa\/mm\/jobname\/, pero puedes modificarla para adaptarla a tus necesidades, igual prefieres .\/Jobs\/jobname\/aaaa\/mm, as\u00ed que eso depende de ti.<\/p>\n<p>Si lo dejas como est\u00e1, y tienes un trabajo como este<\/p>\n<p><a href=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2017\/10\/02_sample_job.png\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2017\/10\/02_sample_job.png\" alt=\"\" width=\"690\" height=\"626\" class=\"aligncenter size-full wp-image-457\" srcset=\"https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2017\/10\/02_sample_job.png 690w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2017\/10\/02_sample_job-300x272.png 300w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2017\/10\/02_sample_job-150x136.png 150w\" sizes=\"(max-width: 690px) 100vw, 690px\" \/><\/a><\/p>\n<p>&nbsp;<br \/>\nEntonces obtendr\u00e1s algo como esto.<\/p>\n<p><a href=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2017\/10\/01_folder_structure.png\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2017\/10\/01_folder_structure.png\" alt=\"\" width=\"950\" height=\"566\" class=\"aligncenter size-full wp-image-455\" srcset=\"https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2017\/10\/01_folder_structure.png 950w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2017\/10\/01_folder_structure-300x179.png 300w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2017\/10\/01_folder_structure-768x458.png 768w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2017\/10\/01_folder_structure-150x89.png 150w\" sizes=\"(max-width: 950px) 100vw, 950px\" \/><\/a><\/p>\n<p>&nbsp;<br \/>\nSi te gusta, aqu\u00ed est\u00e1 el Procedimiento almacenado, para que puedas copiar pegar y que empiece la diversi\u00f3n.<\/p>\n<p>&nbsp;<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\nUSE [master]\r\nGO\r\nIF OBJECT_ID('dbo.sqlg_renameJobLogFiles') IS NULL EXECUTE sp_executesql N'CREATE PROCEDURE dbo.sqlg_renameJobLogFiles AS RETURN';\r\nGO\r\nSET ANSI_NULLS ON\r\nGO\r\nSET QUOTED_IDENTIFIER ON\r\nGO\r\n-- =============================================\r\n-- Author:      Raul Gonzalez @SQLDoubleG\r\n-- Create date: 17\/09\/2013\r\n-- Description: Rename Log Files for the specified job and place them into a folder \r\n--              with the following pattern\r\n--                  [Drive:\\Current Path]\\Jobs\\yyyy\\mm\\Step-N-StepName_yyyymmdd[_hhmiss].log\r\n--\r\n-- Usage:\r\n--              Include the following call as the last step of a job\r\n--\r\n--              USE [master]\r\n--              GO\r\n--              -- tokens can only be used within job steps\r\n--              DECLARE @job_id uniqueidentifier = CONVERT(uniqueidentifier, $(ESCAPE_NONE(JOBID)))\r\n--              DECLARE @includeTime bit = 1\r\n--              DECLARE @debugging bit = 0\r\n \r\n--              EXECUTE [dbo].[sqlg_renameJobLogFiles] \r\n--                  @job_id         = @job_id\r\n--                  , @includeTime  = @includeTime\r\n--                  , @debugging    = @debugging\r\n--              GO\r\n--\r\n-- Asumptions:  Each step outputs into a different file\r\n--\r\n-- Change Log:  30\/03\/2016  RAG - Added parameter @includeTime to add _hhmmss to the output filename.\r\n--\r\n-- Copyright:   (C) 2017 Raul Gonzalez (@SQLDoubleG https:\/\/sqldoubleg.live-website.com)\r\n--\r\n--              THIS CODE AND INFORMATION ARE PROVIDED &quot;AS IS&quot; WITHOUT WARRANTY OF \r\n--              ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED \r\n--              TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND\/OR FITNESS FOR A\r\n--              PARTICULAR PURPOSE.\r\n--\r\n--              THE AUTHOR SHALL NOT BE LIABLE TO YOU OR ANY THIRD PARTY FOR ANY INDIRECT, \r\n--              SPECIAL, INCIDENTAL, PUNITIVE, COVER, OR CONSEQUENTIAL DAMAGES OF ANY KIND\r\n--\r\n--              YOU MAY ALTER THIS CODE FOR YOUR OWN *NON-COMMERCIAL* PURPOSES. YOU MAY\r\n--              REPUBLISH ALTERED CODE AS LONG AS YOU INCLUDE THIS COPYRIGHT AND GIVE DUE CREDIT. \r\n--\r\n-- =============================================\r\nALTER PROCEDURE [dbo].[sqlg_renameJobLogFiles] \r\n    @job_id         UNIQUEIDENTIFIER\r\n    , @includeTime  BIT = 0\r\n    , @debugging    BIT = 0\r\nAS\r\nBEGIN\r\n \r\n    SET NOCOUNT ON\r\n \r\n    DECLARE @outputFiles TABLE (ID                  INT IDENTITY\r\n                                , job_name          SYSNAME\r\n                                , step_no           INT\r\n                                , step_name         SYSNAME\r\n                                , output_file_name  NVARCHAR(512) NOT NULL\r\n                                , only_file_name\tNVARCHAR(512) NOT NULL)\r\n \r\n    DECLARE @numFiles           INT\r\n            , @countFiles       INT = 1\r\n            , @job_name         SYSNAME\r\n            , @output_file_name NVARCHAR(512)\r\n            , @new_path         NVARCHAR(512)\r\n            , @new_file_name    NVARCHAR(512)\r\n            , @year             VARCHAR(4) = DATEPART(YEAR,GETDATE())\r\n            , @month            VARCHAR(2) = RIGHT('00' + CONVERT(VARCHAR,DATEPART(MONTH,GETDATE())), 2)\r\n            , @day              VARCHAR(2) = RIGHT('00' + CONVERT(VARCHAR,DATEPART(DAY,GETDATE())), 2)\r\n            , @time             VARCHAR(6) = REPLACE(CONVERT(VARCHAR,GETDATE(), 108), ':', '')\r\n            , @dirCmd           NVARCHAR(2000)\r\n            , @mvCmd            NVARCHAR(2000)\r\n \r\n    INSERT INTO @outputFiles\r\n    SELECT TOP 100 PERCENT\r\n            j.name\r\n            , js.step_id\r\n            , js.step_name\r\n            , output_file_name \r\n  \t\t\t, RIGHT( output_file_name, ( CASE WHEN CHARINDEX( CHAR(92), REVERSE(output_file_name) ) &gt; 0 \r\n\t\t\t\t\t\t\t\t\t\t\tTHEN CHARINDEX( CHAR(92), REVERSE(output_file_name) ) - 1 \r\n\t\t\t\t\t\t\t\t\t\t\tELSE LEN(output_file_name) \r\n\t\t\t\t\t\t\t\t\t\tEND ) ) AS only_file_name\r\n\r\n        FROM msdb.dbo.sysjobs AS j\r\n            INNER JOIN msdb.dbo.sysjobsteps AS js\r\n                ON js.job_id = j.job_id\r\n        WHERE j.job_id = @job_id\r\n            AND output_file_name IS NOT NULL\r\n        ORDER BY js.step_id\r\n \r\n    SET @numFiles = @@ROWCOUNT\r\n \r\n    WHILE @countFiles &lt;= @numFiles BEGIN\r\n     \r\n        SELECT @output_file_name = output_file_name\r\n                , @new_file_name = 'Step-' + CONVERT(VARCHAR,step_no) + '-' + step_name\r\n                , @new_path = REPLACE(output_file_name, only_file_name, '')\r\n                                    + 'Jobs' + CHAR(92) + @year + CHAR(92) + @month + CHAR(92) + job_name + CHAR(92)\r\n                , @job_name = job_name\r\n            FROM @outputFiles\r\n            WHERE ID = @countFiles\r\n \r\n        -- Create folder if does not exist\r\n        SET @dirCmd = 'EXEC master..xp_cmdshell ''if not exist &quot;' + @new_path + '&quot;. md &quot;' + @new_path + '&quot;.'''\r\n        SET @mvCmd  = 'EXEC master..xp_cmdshell ''move &quot;' + @output_file_name + '&quot; &quot;'\r\n                            + @new_path + @new_file_name + '_' +  @year + @month + @day\r\n                            + CASE WHEN @includeTime = 1 THEN '_' + @time ELSE '' END + '.log&quot;'''\r\n     \r\n        PRINT @dirCmd\r\n        PRINT @mvCmd\r\n \r\n        IF ISNULL(@debugging, 0) = 0 BEGIN \r\n            EXEC sp_executesql @dirCmd\r\n            EXEC sp_executesql @mvCmd\r\n        END\r\n \r\n        SET @countFiles += 1\r\n    END\r\nEND\r\nGO\r\n\r\n<\/pre>\n<p>&nbsp;<br \/>\nLo \u00faltimo es agregar una llamada a este procedimiento almacenado en sus diferentes trabajos como paso final; la llamada se encuentra en la secci\u00f3n del encabezado, pero ser\u00eda una cosa as\u00ed.<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\n\r\nUSE [master]\r\nGO\r\n-- tokens can only be used within job steps\r\nDECLARE @job_id uniqueidentifier = CONVERT(uniqueidentifier, $(ESCAPE_NONE(JOBID)))\r\nDECLARE @includeTime bit = 1\r\nDECLARE @debugging bit = 0\r\n\r\nEXECUTE [dbo].[sqlg_renameJobLogFiles] \r\n\t@job_id\t\t\t= @job_id\r\n\t, @includeTime\t= @includeTime\r\n\t, @debugging\t= @debugging\r\nGO\r\n<\/pre>\n<p>&nbsp;<br \/>\n<strong>El gotcha!<\/strong><\/p>\n<p>\u00a1Casi me olvido! Todo esto vino por una raz\u00f3n, el uso de tokens. S\u00ed, no hay mucha chicha usando tokens, puedes consultar libros online para ver cuales est\u00e1n disponibles, yo solo uso JOBID en mi \u00faltimo paso de trabajo.<\/p>\n<p>La historia es que tengo scripts para crear trabajos para nuevos servidores y trato de usar SQLCMD para hacerlo m\u00e1s versatil, as\u00ed no tengo que molestarme en conectarme al servidor correcto, el script lo har\u00e1 por m\u00ed.<\/p>\n<p>Pero al crear un trabajo, me salt\u00e9 este error.<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\n\/*\r\nA fatal scripting error occurred.\r\nVariable JOBID is not defined.\r\n*\/\r\n<\/pre>\n<p>Este error es bastante molesto, porque no pod\u00eda entender por qu\u00e9, pero parece que a SQLCMD no le gustan mucho los tokens, ya que usan la misma estructura que las variables en SQLCMD $(variable).<\/p>\n<p>No puedo encontrar ninguna referencia en BOL, pero solo en caso de que os surja el mismo error, simplemente ejecutar ese script localmente, \u00bfo quiz\u00e1s PowerShell funcionar\u00eda? Ya me dir\u00e9is si lo podeis hacer.<\/p>\n<p>&nbsp;<br \/>\n\u00a1Gracias por leer!<br \/>\n<\/p>","protected":false},"excerpt":{"rendered":"<p>En esta publicacion, quiero contaros acerca de una caracteristica bastante util que se puede usar dentro de los trabajos del Agente SQL en SQL Server&nbsp;En esta publicaci\u00f3n, quiero contarte sobre un error&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4,19],"tags":[81],"_links":{"self":[{"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/posts\/454"}],"collection":[{"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/comments?post=454"}],"version-history":[{"count":0,"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/posts\/454\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/media?parent=454"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/categories?post=454"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/tags?post=454"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}