How would you create an installation setup that runs against multiple schemas taking into consideration the latest version of the database updates? Ideally: update a single file with a new version number, then send the DBAs an archive containing everything needed to perform the database update.
Here is the directory structure:
| install.sql
| install.bat
|
\---DATABASE_1.3.4.0
| README.txt
|
\---SCHEMA_01
| install.sql
| SCM1_VIEW_NAME_01_VW.vw
| SCM1_VIEW_NAME_02_VW.vw
| SCM1_PACKAGE_01_PKG.pkb
| SCM1_PACKAGE_01_PKG.pks
|
\---SCHEMA_02
install.sql
SCM2_VIEW_NAME_01_VW.vw
SCM2_VIEW_NAME_02_VW.vw
SCM2_PACKAGE_01_PKG.pkb
SCM2_PACKAGE_01_PKG.pks
The following code (sanitized and trimmed for brevity and security) is in install.sql:
ACCEPT tns
ACCEPT schemaUsername
ACCEPT schemaPassword
CONNECT &&schemaUsername/&&schemaPassword#&&tns
##install.sql
/
The following code is in install.bat:
#echo off
sqlplus /nolog #install.sql
pause
There are several schemas, not all of which need updates each time. Those that do not need updates will not have directories created.
What I would like to do is create two files:
version.txt
schemas.txt
These two (hand-crafted) files would be used by install.sql to determine which version of scripts to run.
For example:
version.txt
1.3.4.0
schemas.txt
SCHEMA_01
SCHEMA_02
What I really would like to know is how would you read those text files from install.sql to run the corresponding install scripts? (Without PL/SQL; other Oracle-specific conventions are acceptable.)
All ideas welcome; many thanks in advance.
Here is a solution.
install.bat
#echo off
REM *************************************************************************
REM
REM This script performs a database upgrade for the application suite.
REM
REM *************************************************************************
setLocal EnableDelayedExpansion
REM *************************************************************************
REM
REM Read the version from the file.
REM
REM *************************************************************************
set /p VERSION=<version.txt
set DB=DB_%VERSION%
set SCHEMAS=%DB%\schema-order.txt
REM *************************************************************************
REM
REM Each line in the schema-order.txt file contains the name of a schema.
REM Blank lines are ignored.
REM
REM *************************************************************************
for /f "tokens=* delims= " %%a in (%SCHEMAS%) do (
if not "%%a" == "" sqlplus /nolog #install.sql %VERSION% %%a
)
Primary install.sql
ACCEPT schemaUsername CHAR DEFAULT &2 PROMPT 'Schema Owner [&2]: '
ACCEPT schemaPassword CHAR PROMPT 'Password: ' HIDE
PROMPT Verifying Database Connection
CONNECT &&schemaUsername/&&schemaPassword#&&tns
DEFINE INSTALL_PATH = DB_&1&&ds^&2&&ds
##&&INSTALL_PATH^install.sql
This uses a batch file to parse the files, then passes the parameters to the SQL script on the command-line.
Secondary install.sql
Each line in the file executed by the first installation script can then use the INSTALL_PATH variable to reference a file containing actual SQL to run. This secondary script is responsible for running the individual SQL files that actually exact a change in the database.
##&&INSTALL_PATH^DIR&&ds^SCM1_VIEW_OBJECT_VW.vw
This solution could be modified to automatically run all files in a specific order through clever use of sorting and naming of directories (i.e., the SQL files listed in a table directory run before the SQL files in a view directory).
Related
I have a scheduled task with a .bat file that downloads some files from a web server every day by the morning then process the data and UPDATES a database. Then it triggers another .bat file to SELECT data and EXPORT to a .xls file.
The second .bat file is like this:
set a=%date:/=-%
del /q F:\file_path\file1_%a%.xls
del /q F:\file_path\file2_%a%.xls
echo %time%_%date%
cd /D D:\oracle\product\10.2.0\db_1\BIN
sqlplus usrname/psswd#ORCL #F:\select_path\select1.sql
timeout /t 30 /nobreak > nul
ren F:\file_path\file1.xls file1_%a%.xls
sqlplus usrname/psswd#ORCL #F:\select_path\select2.sql
timeout /t 30 /nobreak > nul
ren F:\file_path\file2.xls file2_%a%.xls
cd /D F:\KMB-SP\TI\Scripts\script_select
::Command to send file1 and file2 via e-mail.
But when I arrive at the office and check the progress, only the first .xls is done. So I have to run the second .bat manually and it runs perfectly.
What could be causing this?
Notes:
I put the timeout between the two SELECTs because, in the past, the code was stopping after the INSERT and didn't trigger the second .bat . My colleague said it could be execution exception. Puting a timeout would give time to end the INSERT properly.
Before, it used to make both SELECTs and then rename both files. Doing so, sometimes it worked, sometimes not, then I tried to change the order: select1, rename1, select2, rename2.
As we download files everyday, we concatenate the data on a single file called DT-date. The first code goes like this:
rem The data is downloaded and the files are organized in their files
if exist F:\path\DT-date (
Data_consolidation.exe
timeout /t 300 /nobreak > nul
F:\path\second_bat.bat
) else (exit)
As #William Robertson said, I tried echo exit right after the first SELECT, but again, it only extracted the first file and not the second one.
As #WilliamRobertson suggested, writing echo exit | before the sqlplus commands solved the problem.
I have this really nice line in my batch file that tells me how many lines are in a file:
find /v /c "" C:\Users\c1921\mypath\myfolder\!$Unit!.txt
This is nice and gives me 31 for the particular file I'm working with. My problem is the file looks something like this:
Microsoft (R) Windows Script Host Version 5.8
Copyright (C) Microsoft Corporation. All rights reserved.
my_handled
219278
check
219276
control
219274
I want to be able to skip the first three lines entirely and then save the first value and then use the second value in my next command etc.
How do I save the number (e.g. 31) into a variable in my batch file?
On a hunch I tried setting a variable like so but it wasn't effective:
set "$testVar="""
echo !$testVar!
This command allows you to "save the number (e.g. 31) into a variable in my batch file":
for /F %%a in ('find /v /c "" ^< C:\Users\c1921\mypath\myfolder\!$Unit!.txt') do set numLines=%%a
This command allows you "to skip the first three lines entirely" and process the rest:
for /F "skip=3 delims=" %%a in (C:\Users\c1921\mypath\myfolder\!$Unit!.txt) do echo Processing: "%%a"
However, in my opinion this problem could be entirely avoided if the three first lines in the text file are supressed from the very beginning. I think this file is generated via a VBScript of JScript program that is executed this way:
cscript progname.vbs > C:\Users\c1921\mypath\myfolder\!$Unit!.txt
The first three lines in the text file may be avoided adding //nologo switch this way:
cscript //nologo progname.vbs > C:\Users\c1921\mypath\myfolder\!$Unit!.txt
#ECHO OFF
SETLOCAL
SET /a count=3
SET "first="
FOR /f "skip=3delims=" %%a IN (q25089468.txt) DO (
IF DEFINED first (CALL :show %%a) ELSE (SET "first=%%a")
)
ECHO count=%count%
GOTO :EOF
:show
ECHO first=%first% second=%1
SET /a count+=2
SET "first="
GOTO :eof
I used a file named q25089468.txt containing your data for my testing.
You appear to be asking two entirely different things - how to count the lines and how to skip the first 3, then deliver each succeeding pair to another process.
im having difficulties trying to set the output of the hostname command as a variable. what I want to do is to have the text file that is outputted to have the name of the computer so computer1.txt, computer2.txt etc, but i want to do it without making a temp file for example
set HNAME =`hostname`
this is what i have currently but the script i am using is being run on several computers at the same and i believe that the temp file that i create is causing issues with the names of the .txt files.
hostname >> hostname.txt
set /p HNAME=<hostname.txt
pause
echo hello > %HNAME%.txt
pause
You have to use the for command, something like this:
for /f "usebackq" %i in ( `somecommand` ) do set envar=%i
It's very painful. for /? at the command line for more information.
These are not working for me.
Any help to definitelly corret the four examples below ?
The EXAMPLE01 just echoes "continue", even if I have three CMD.exe opened.
---------- EXAMPLE 01 ------------
#echo off
wmic process where name="cmd.exe" | find "cmd.exe" /c
SET ERRORLEVEL=value if "%value%" GTR 1 (
ECHO This batch is not first
ECHO quitting ...
)
if "%value%" LSS 2 ECHO continue
I am getting the "unexpected i error" message in the EXAMPLE 02!
----------- EXAMPLE 02 -------
#echo off
FOR /F "usebackq tokens=2" %i IN (`tasklist ^| findstr /r /b "cmd.exe"`)
DO taskkill /pid %%i
I am getting the "is first" message in the EXAMPLE 03, even with three CMD.exe opened!
----------- EXAMPLE 03 -------
#echo off
wmic process where name="cmd.exe" | find "cmd.exe" /c
if "%errorlevel%" LEQ 1 echo CMD is first
if "%errorlevel%" GTR 1 echo CMD is already running
It is also possible that I will not have access to the Wmic command at work, so, another possibility is found in the EXAMPLE 04 ... but to no avail.
----------- EXAMPLE 04 -------
#echo off
Tasklist /FI "IMAGENAME eq cmd.exe" 2>NUL | find /I /N "cmd.exe">NUL
if "%ERRORLEVEL%"==0 do (goto Use) else (goto Cont)
:Cont
ECHO Only one instance running
pause
:Use
echo Application running already. Close this window
Kind regards,
Maleck
wmz identified a number of errors in the OP's code, and also has an excellent suggestion to use a lock file for concurrency control.
Below is a robust batch solution that uses a lock file to prevent multiple instances of the batch file from running at the same time. It uses a temporary lock file for the concurrency control. The mere presence of the lock file does NOT stop the script from running. The script will only fail if another process has an exclusive lock on the lock file. This is important in case the script should crash or be killed without deleting the lock file. The next process to run the script will still succeed because the file is no longer locked.
This script assumes the script is installed on a local drive. It allows only one instance for the entire machine. There are lots of variations to control the amount of concurrency allowed. For example, incorporating the %USERNAME% into the lock file name would allow one instance per user in a network environment. Incorporating %COMPUTERNAME% in the name would allow one instance per machine in a network environment.
#echo off
setlocal
:: save parameters to variables here as needed
set "param1=%~1"
:: etc.
:: Redirect an unused file handle for an entire code block to a lock file.
:: The batch file will maintain a lock on the file until the code block
:: ends or until the process is killed. The main code is called from
:: within the block. The first process to run this script will succeed.
:: Subsequent attempts will fail as long as the original is still running.
set "started="
9>"%~f0.lock" (
set "started=1"
call :start
)
if defined started (
del "%~f0.lock" >nul 2>nul
) else (
echo Process aborted: "%~f0" is already running
)
exit /b
:start
:: The main program appears below
:: All this script does is PAUSE so we can see what happens if
:: the script is run multiple times simultaneously.
pause
exit /b
EDIT
The error message "The process cannot access the file because it is being used by another process." can be suppressed by redirecting stderr output to nul in an outer block.
2>nul (
9>"%~f0.lock" (
set "started=1"
call :start
)
)
The problem with the above is that all error messages for the main program will also be suppressed. That can be fixed by 1st saving the current definition of stderr to another unused file handle, and then adding yet another inner block that redirects stderr back to the saved file handle.
8>&2 2>nul (
9>"%~f0.lock" (
2>&8 (
set "started=1"
call :start
)
)
)
You do not set value anywhere. Even if you did it would not work as variables are expanded on parse. You would need to use delayed expansion if you need to both set and test variable against what was set. Quotes on comparison are not required (see 3). help set will show you info delayed expansion.
%i should be %%i. DO taskkill /pid %%i must be on same line as rest of for command. You also use findstr in regex mode, which means it will search for cmd[any character]exe
You use string (lexical) comparison: "1" leq 1 is true (as "4" leq 1 also is...). Use errorlevel 1 which is equivalent to errorlevel equal or greater than 1. help if shows syntax
Same as 3 plus do in do (goto Use) should be removed. %errorlevel%==0 would work, but it's normally advised to use errorlevel number.
How to check if there is only 1 cmd running:
#echo off
for /f %%i in ('Tasklist /FI "IMAGENAME eq cmd.exe" 2^>NUL' ^| find /I /c "cmd.exe"') do (
if %%i leq 2 (echo only one instance) else (echo More than one instance)
)
Note: it's just an example. I do not recommend this as a real method of concurrency control. I would rather use lock file for that.
I have written a batch file that is launched as a post processing utility by a program. The batch file reads ~24 parameters supplied by the calling program, stores them into variables, and then writes them to various text files.
Since the max input variable in CMD is %9, it's necessary to use the 'shift' command to repeatedly read and store these individually to named variables. Because the program outputs several similar batch files, the result is opening several CMD windows sequentially, assigning variables and writing data files. This ties up the calling program for too long.
It occurs to me that I could free up the calling program much faster if maybe there's a way to write a very simple batch file that can write all the command parameters to a text file, where I can process them later. Basically, just grab the parameter list, write it and done.
Q: Is there some way to treat an entire series of parameter data as one big text string and write it to one big variable... and then echo the whole big thing to one text file? Then later read the string into %n variables when there's no program waiting to resume?
Parameter list is something like 25 - 30 words, less than 200 characters.
Sample parameter list:
"First Name" "Lastname" "123 Steet Name Way" "Cityname" ST 12345 1004968 06/01/2010 "Firstname+Lastname" 101738 "On Account" 20.67 xy-1z 1 8.95 3.00 1.39 0 0 239 8.95
Items in quotes are processed as string variables. List is space delimited.
Any suggestions?
echo %* 1>args.txt
%* references all arguments: %1 %2 %3...
It also works with subroutines.
call :test 1 2 3
goto :eof
:test
echo 1: %1
echo 2: %2
echo 3: %3
echo *: %*
exit /b
output:
1: 1
2: 2
3: 3
*: 1 2 3
See the following website for more information:
http://ss64.com/nt/syntax-args.html
Interesting Post. It sparked my interest.
I too am needing something that could accept parameters and although this probably isn't useful to you now I thought it might be useful at some later date.
My solution is less simple - because there just isn't an elegant way to do it.
Basically, in this example the "-" can be used to identify a parameter, and the next space is assumed to be set to a value.
Legal Stuff:
So this is all my code and I don't really care how or where you choose to use it. No need to cite me it's just an example anyway.
Like this:
Microsoft Batch:Begin Copy below and save as filename.bat
#ECHO OFF
REM USAGE: this-batch-name.bat -BUILD "1.2.3 build 405" -JOB "Running This Job" -run RUN_FUNCTION
SET __CURRENT_WORKING_DIRECTORY__=%~dp1
ECHO.__CURRENT_WORKING_DIRECTORY__=%__CURRENT_WORKING_DIRECTORY__%
REM # Clear Previous Variables
SET PACKAGING_BUILD_NUMBER=
SET PACKAGING_JOB_NAME=
SET GO_DEEPER=
SET RUN_COMMAND=
REM ## In order to read variables set while in a "FOR" loop
REM ## you have to set the 'ENABLEDELAYEDEXPANSION' with 'SETLOCAL'.
SETLOCAL ENABLEEXTENSIONS
SETLOCAL ENABLEDELAYEDEXPANSION
REM ## Capture Command line parameters here with a %*
FOR %%A IN (%*) DO (
REM ## If we found something with a '-' in previous pass run GO_DEEPER will be defined and thus set to the command line argument.
IF DEFINED GO_DEEPER (
REM ## When ENABLEDELAYEDEXPANSION is Set with setlocal command you have to use exclamation: i.e. '^!'
IF /I "-BUILD"=="!GO_DEEPER!" SET PACKAGING_BUILD_NUMBER=%%A
IF /I "-JOB"=="!GO_DEEPER!" SET PACKAGING_JOB_NAME=%%A
IF /I "-RUN"=="!GO_DEEPER!" SET RUN_COMMAND=%%A
SET SET GO_DEEPER=
)
IF /I "%%A" GEQ "-" (
REM ## Wow we found your command line argument that started with a '-' so set the GO_DEEPER Var
SET GO_DEEPER=%%A
) ELSE (
SET SET GO_DEEPER=
)
)
REM ## Time to grab the variables set while in delayed expansion mode
ENDLOCAL && SET PACKAGING_BUILD_NUMBER=%PACKAGING_BUILD_NUMBER% && SET PACKAGING_JOB_NAME=%PACKAGING_JOB_NAME% && SET RUN_COMMAND=%RUN_COMMAND%
REM ## Sucks, but you have to clear the '"' and "'" if it exists.
IF DEFINED RUN_COMMAND (
SET RUN_COMMAND=%RUN_COMMAND:"=%
SET RUN_COMMAND=%RUN_COMMAND:'=%
)
IF DEFINED PACKAGING_JOB_NAME (
SET PACKAGING_JOB_NAME=%PACKAGING_JOB_NAME:"=%
SET PACKAGING_JOB_NAME=%PACKAGING_JOB_NAME:'=%
)
IF DEFINED PACKAGING_BUILD_NUMBER (
SET PACKAGING_BUILD_NUMBER=%PACKAGING_BUILD_NUMBER:"=%
SET PACKAGING_BUILD_NUMBER=%PACKAGING_BUILD_NUMBER:'=%
)
REM ## Now we can try to run the command function if the -run was used...
IF DEFINED RUN_COMMAND (
CALL:--%RUN_COMMAND% "'%PACKAGING_JOB_NAME%'","'%PACKAGING_BUILD_NUMBER%'"
) ELSE (
ECHO Try running:
ECHO %0 -BUILD "1.2.3 build 405" -JOB "Running This Job" -run RUN_FUNCTION
)
GOTO DONE
:--RUN_FUNCTION
ECHO running... %~0
SET VARPASSED1=%~1
SET VARPASSED2=%~2
IF DEFINED VARPASSED1 ECHO VARPASSED1 was %VARPASSED1%
IF DEFINED VARPASSED2 ECHO VARPASSED2 was %VARPASSED2%
ECHO Add your code to process here...
GOTO:EOF
:DONE
ECHO We got the following results...
IF DEFINED PACKAGING_JOB_NAME ECHO PACKAGING_JOB_NAME=%PACKAGING_JOB_NAME%
IF DEFINED PACKAGING_BUILD_NUMBER ECHO PACKAGING_BUILD_NUMBER=%PACKAGING_BUILD_NUMBER%
IF DEFINED RUN_COMMAND ECHO RUN_COMMAND=%RUN_COMMAND%
</pre> </code>
Microsoft Batch END Copy
RESULTS:
__CURRENT_WORKING_DIRECTORY__=C:\dev\a\win\sysprep\
running... :--RUN_FUNCTION
VARPASSED1 was "'Running...'"
VARPASSED2 was "'This...'"
We got the following results...
PACKAGING_JOB_NAME="Running This Job"
PACKAGING_BUILD_NUMBER="1.2.3 build 405"
RUN_COMMAND=RUN_FUNCTION