2016-01-13 23 views
5

Sto cercando di eseguire alcuni sqlcmd tramite T-SQL su SQL Server 2008. C'è una parte del mio codice in cui sto controllando le dimensioni di un file di dati e se la dimensione del file di dati non è uguale a 0, quindi inizia a eliminare la tabella specifica in modo che possa BCP nei nuovi dati.Combina riga di comando e T-SQL - SQL Server

Qui di seguito è il mio codice che non è in esecuzione:

SET @myVariable = ' 
SETLOCAL 
FOR %%R IN (X:\Main Folder\Data\'[email protected]+'_'[email protected]+'.dat) DO SET size=%%~zR 
IF %size% NEQ 0 (
     SQLCMD -E -S my-server-name -Q "DELETE FROM '[email protected]+'.'[email protected]+'.'[email protected]+';" >> X:\Main Folder\Log\Log.txt 
)' 

EXEC master..xp_cmdshell @myVariable 

Per qualche ragione quando eseguo il mio stored procedure, il codice di cui sopra sembra essere saltato perché non spara indietro eventuali messaggi di errore.

MODIFICA: Dopo aver riaggiustato la spaziatura e il mio codice, @myVariable, viene eseguito ora. Tuttavia, non funziona ancora per quanto riguarda il fatto che cancella ancora la tabella anche se la dimensione del file di dati è uguale a 0. Tuttavia, quando la codifico in un file batch, funziona perfettamente. Qualche idea?

+0

Perché non si fa ad aggiungere una stored procedure per il database che fa il tuo controllo ed eliminare o troncare. Quindi eseguirlo dalla riga di comando? –

+0

Citazione attorno al nome del file? Entrambi in 'for' e l'output reindirizzato. – shawnt00

+0

A seconda della configurazione e della versione di OS a/i mancante dopo IF. Prova IF/i. – Christian4145

risposta

1

ho capito che posso controllare il conteggio tabella anziché una dimensione di file di dati utilizzando questo metodo:

SET @sqlCheck = 'SQLCMD -E -S ServerA -Q "IF (SELECT COUNT(*) FROM '[email protected]+'.'[email protected]+'.'[email protected]+') > 0 BEGIN DELETE FROM ServerB.'[email protected]+'.'[email protected]+'.'[email protected]+' END;"' 
       EXEC MASTER..xp_cmdshell @sqlcheck 
+1

È meglio mettere questo codice in una stored procedure come menzionato nel mio commento alla tua domanda Se il tuo codice prende qualsiasi tipo di input da parte dell'utente, puoi aprirti agli attacchi di SQL injection. –

+0

Il codice si trova in una stored procedure. Grazie – AznDevil92

+0

Perché dovresti controllare se il tavolo è vuoto o meno prima di cancellarlo? Fare il conteggio richiederà un po 'di tempo e non ha alcun vantaggio. Basta eseguire il comando DELETE; indipendentemente dal numero di record con cui hai iniziato (zero o migliaia), il risultato finale è identico: una tabella vuota. – deroby

1

Si sta utilizzando X:\ nel codice. Ma il codice è in esecuzione con l'account di servizio per SQL Server. Questo account potrebbe non avere x: disponibile.

Suggerirei di utilizzare un UNC invece di un'unità mappata. Inoltre, assicurarsi che il servizio sia in esecuzione con un account di dominio e che l'account di dominio disponga di tutte le autorizzazioni necessarie per UNC.

+0

Ho appena provato ad utilizzare UNC come \\ servername \ X $ \ .... ma non ha funzionato.E non credo che sia un problema di autorizzazione dato che tutti i miei altri comandi xp_cmdshell sono stati eseguiti. – AznDevil92

2

Non riesco a parlare con il comando DOS. Posso comunque suggerire di utilizzare Ole Automation Procedures per ottenere le dimensioni del file. In questo modo non dovrai fare affidamento sull'esecuzione di comandi batch.

Per prima cosa è necessario abilitare Ole Automation Procedures sulla istanza di SQL Server, come segue:

sp_configure 'show advanced options', 1; 
GO 
RECONFIGURE; 
GO 
sp_configure 'Ole Automation Procedures', 1; 
GO 
RECONFIGURE; 
GO 

Hai solo bisogno di fare questo una volta.


Avanti è uno script che ottiene la dimensione del file. Nell'esempio si presuppone che esista un file denominato C:\Temp\testfile.txt. Lo script seleziona la dimensione se il file esiste o seleziona 0 se non lo è. Puoi prendere questo script come esempio per fare ciò che vuoi in base alle dimensioni.

Qui va:

DECLARE @hr INT; 
DECLARE @size INT; 
DECLARE @obj_file INT; 
DECLARE @obj_file_system INT; 
DECLARE @file_name VARCHAR(100)='C:\Temp\testfile.txt'; 

-- Create a FileSystemObject. Create this once for all subsequent file manipulation. Don't forget to destroy this object once you're done with file manipulation (cf cleanup) 
EXEC @hr = sp_OACreate 'Scripting.FileSystemObject', @obj_file_system OUT; 
IF @hr<>0 GOTO __cleanup; 

-- Get a handle for the file. Don't forget to release the handle for each file you get a handle for (see cleanup). The return will be different from 0 if the file doesn't exist 
EXEC @hr = sp_OAMethod @obj_file_system, 'GetFile', @obj_file out, @file_name; 
IF @hr<>0 GOTO __print_file_size; 

-- Retrieve the file size. 
EXEC sp_OAGetProperty @obj_file, 'size', @size OUT; 

__print_file_size: 
SELECT ISNULL(@size,0) AS file_size; 

__cleanup: 
EXEC sp_OADestroy @obj_file_system; 
EXEC sp_OADestroy @obj_file; 
3

è necessario utilizzare un'unica % nella vostra for ciclo come non si sta eseguendo il codice in un file batch (che richiede %%), vedere questo post per alcuni ulteriori chiarimenti. Quindi il tuo ciclo for dovrebbe essere:

FOR %R IN (X:\Main Folder\Data\'[email protected]+'_'[email protected]+'.dat) DO SET size=%~zR 
2

Penso che il problema sia che non stai usando virgolette intorno ai nomi dei file. Quella directory di primo livello ha uno spazio al suo interno.

La risposta di Jaco sembra corretta e sono sicuro che fosse parte del problema. Probabilmente si dovrebbe inizializzare size solo per essere troppo sicuri:

SET @myVariable = ' 
SETLOCAL 
SET size=0 
FOR %R IN ("X:\Main Folder\Data\'[email protected]+'_'[email protected]+'.dat") DO SET size=%~zR 
IF %size% NEQ 0 (
     SQLCMD -E -S my-server-name -Q "DELETE FROM '[email protected]+'.'[email protected]+'.'[email protected]+';" >> "X:\Main Folder\Log\Log.txt" 
)' 

EXEC master..xp_cmdshell @myVariable 

senza le virgolette il ciclo for sta andando per il trattamento che il suo "set" (la terminologia utilizzata in for /?) in due voci separate dallo spazio.Se la directory corrente è X:\Main Folder\Data\, funzionerebbe comunque, dal momento che vede l'ultimo come percorso relativo al file .dat e quindi ancora set s il valore corretto sull'ultimo passaggio.

+0

Ho aggiunto citazioni e doppie virgolette singole e rimosso la seconda percentuale. Ancora senza fortuna – AznDevil92

+0

Hai già una variabile 'size' nel tuo ambiente? Hai confermato che sta effettivamente facendo il ciclo e acquisendo il valore? – shawnt00

+0

Un altro pensiero: è possibile che non siano attivate estensioni di comando? Credo che potrebbe causare il fallimento su "if ... neq" e quindi eseguire incondizionatamente l'operazione di cancellazione. – shawnt00

2

Non sono sicuro se questa è un'opzione per voi, ma dal momento che siete su SQL 2008 si dovrebbe essere in grado di utilizzare il comando PowerShell invece:

DECLARE @File varchar(100), @outputFile varchar(100) 
DECLARE @cmd varchar(1000) 
SELECT @File = 'path_to_file' 
SELECT @outputFile = 'path_to_output_file' 

SELECT @cmd = 'powershell.exe -command "if((Get-Item '''[email protected]+''').length -gt 0) {&sqlcmd.exe -E -S SERVERNAME -Q ''SELECT name FROM master.sys.databases ;'' -o '[email protected]+'}"' 


SELECT @cmd 

exec master..xp_cmdshell @cmd 

Ho controllato e sembra funzionare a seconda del dimensione del file.

2

Perché si dovrebbe andare 'down' alla riga di comando a tutti? (Non ci sono ragioni per cui xp_cmdshell è disabilitato per default)

Potresti non semplicemente un ciclo sulle tabelle (sys.tables WHERE name LIKE ...) e poi

  • creare una shadow-copia della tabella (SELECT INTO)
  • BULK INSERT da il file (previsto) in shadow-table (in un TRY..CATCH per gestire situazioni in cui il file non esiste, o è vuoto, o è corrotto, ..
  • se ci sono dati nella tabella shadow, quindi DELETE l'effettivo tabella registra e sposta i dati oltre
  • se non ci sono dati nella tabella shadow, quindi DELETE i record della tabella effettiva (o lasciarli in se si presume che un file bcp mancante o vuoto significa che arriverà più tardi e si è bloccato con la versione corrente per ora)
  • DROP tavolo ombra di nuovo
1

te sembra di conoscere i nomi dei database e le tabelle già, in modo da poter utilizzare il seguente, che fa fondamentalmente un DIR per il file che state cercando e controlla se è '0 bytes', se è così fa allora quello che vuoi. Cose da notare:

  • MODELLI STRING - Quando si costruisce corde, mi piace costruire un 'modello' e quindi sostituire all'interno della stringa. Questo è un buon modo per essere sicuro di avere il giusto numero di virgolette, parentesi, ecc. L'ho fatto due volte qui, una volta per costruire il comando DIR e poi di nuovo per costruire il comando TRUNCATE.

  • TRUNCATE - sebbene non faccia parte della domanda, è possibile utilizzare uno TRUNCATE anziché DELETE FROM. Se nella tabella è presente un milione di righe, il tempo di esecuzione di DELETE FROM potrebbe richiedere 2 minuti, mentre come TRUNCATE occorreranno sempre 0 secondi.

tua risposta:

SET NOCOUNT ON; 

DECLARE @DatabaseName VARCHAR(50) = 'database1' 
DECLARE @TableName VARCHAR(50) = 'table1' 

DECLARE @PathTemplate VARCHAR(50) = 'dir c:\temp\{@DatabaseName}_{@TableName}.txt' 

SET @PathTemplate = REPLACE(@PathTemplate, '{@DatabaseName}', @DatabaseName); 
SET @PathTemplate = REPLACE(@PathTemplate, '{@TableName}', @TableName); 

DECLARE @FileNames AS TABLE (FileNames VARCHAR(100)) 

INSERT @FileNames (FileNames) 
exec xp_cmdshell @PathTemplate 

IF EXISTS (SELECT 1 FROM @FileNames WHERE FileNames LIKE '%0 bytes') 
BEGIN 
    PRINT 'No Content/Missing File' 
END 
ELSE BEGIN 

    DECLARE @SqlExc VARCHAR(500) = 'TRUNCATE TABLE [{@DatabaseName}].[dbo].[{@TableName}]' 
    SET @SqlExc = REPLACE(@SqlExc, '{@DatabaseName}', @DatabaseName); 
    SET @SqlExc = REPLACE(@SqlExc, '{@TableName}', @TableName); 

    PRINT @SqlExc 
    -- sp_executesql @SqlExc <-- Do this in production 

END