BCP update database table base on output from powershell - sql

I have 4 files with the same csv header as following
Column1,Column2,Column3,Column4
But I only required data from Column2,Column3,Column4 for import the data into SQL database using BCP . I am using the PowerShell to select the columns that I want and import the required data using BCP but my powershell executed with no error and there are not data updated in my database table. May I know how to set the BCP to import the output from Powershell to database table. Here are my powershell script
$filePath = Get-ChildItem -Path 'D:\test\*' -Include $filename
$desiredColumn = 'Column2','Column3','Column4'
foreach($file in $filePath)
{
write-host $file
$test = import-csv $file | select $desiredColumn
write-host $test
$action = bcp <myDatabaseTableName> in $test -T -c -t";" -r"\n" -F2 -S <MyDatabase>
}
These are the output from the powershell script
D:\test\sample1.csv
#{column2=111;column3=222;column4=333} #{column2=444;column3=555;column4=666}
D:\test\sample2.csv
#{column2=777;column3=888;column4=999} #{column2=aaa;column3=bbb;column4=ccc}

First off, you can't update a table with bcp. It is used to bulk load data. That is, it will either insert new rows or export existing data into a flat file. Changing existing rows, usually called as updating, is out of scope for bcp. If that's what you need, you need to use another a tool. Sqlcmd works fine, and Powershell's got Invoke-Sqlcmd for running arbitary TSQL statements.
Anyway, the BCP utility has notoriously tricky syntax. As far as I know, one cannot bulk load data by passing the data as parameter to bcp, a source file must be used. Thus you need to save the filtered file and pass its name to bcp.
Exporting a filtered CSV is easy enough, just remember to use -NoTypeInformation switch, lest you'll get #TYPE Selected.System.Management.Automation.PSCustomObject as your first row of data. Assuming the bcp arguments are well and good (why -F2 though? And Unix newlines?).
Stripping double quotes requires another an edit to the file. Scrpting Guy has a solution.
foreach($file in $filePath){
write-host $file
$test = import-csv $file | select $desiredColumn
# Overwrite filtereddata.csv, should one exist, with filtered data
$test | export-csv -path .\filtereddata.csv -NoTypeInformation
# Remove doulbe quotes
(gc filtereddata.csv) | % {$_ -replace '"', ''} | out-file filtereddata.csv -Fo -En ascii
$action = bcp <myDatabaseTableName> in filtereddata.csv -T -c -t";" -r"\n" -F2 -S <MyDatabase>
}
Depending on your locale, column separator might be semicolon, colon or something else. Use -Delimiter '<character>' switch to pass whatever you need or change bcp's argument.
Erland's got a helpful page about bulk operations. Also, see Redgate's advice.

Without need to modify the file first, there is an answer here about how bcp can handle quoted data.
BCP in with quoted fields in source file
Essentially, you need to use the -f option and create/use a format file to tell SQL your custom field delimiter (in short, it is no longer a lone comma (,) but it is now (",")... comma with two double quotes. Need to escape the dblquotes and a small trick to handle the first doulbe quote on a line. But it works like a charm.
Also, need the format file to ignore column(s)... just set the destination column number to zero. All with no need to modify the file before load. Good luck!

Related

Powershell - Comparing LineNumbers of lines that match

I'm having trouble to come up with solution that would compare LineNumbers of matching pairs from two lists. I will show you what I mean on example.
I have one SQL script, where I am inserting some data into existing tables. For ensuring repeatability of the script, before every insert into I am deleting the previous content of the table with "delete" statement. I am able to parse the file and check If every "insert into database1.table1" also have "delete from database1.table1" in the file. But i don't know how to check if the delete statement of the particular table is before the insert into statement (you need to delete the content of the table before you load new data into it). I figured I would need to use the LineNumber property, but I really don't know how to combine it with the database.table check.
This is what i got into first variable with this command:
$insertinto = Get-ChildItem "$packagepath\Init\" -Include 03_Init_*.txt -Recurse | Select-String -Pattern "insert into "
#content of variable
C:\Users\hanus\Documents\sql_init.txt:42:insert into database1.table1
C:\Users\hanus\Documents\sql_init.txt:130:insert into database1.table2
C:\Users\hanus\Documents\sql_init.txt:282:insert into database2.table3
Here is what I got into second variable with this command:
$deletefrom = Get-ChildItem "$packagepath\Init\" -Include 03_Init_*.txt -Recurse | Select-String -Pattern "delete from "
#content of the variable
C:\Users\hanus\Documents\sql_init.txt:40:delete from database1.table1;
C:\Users\hanus\Documents\sql_init.txt:128:delete from database1.table2;
C:\Users\hanus\Documents\sql_init.txt:280:delete from database2.table3;
The expected output would be something like: This"delete from" statement is not before "insert into" statement, even though it's in the file.
I hope I described the problem well. I am new to Powershell and scripting so be please patient with me. Thank you for any help in advance!
You're already using Select-String, so this should be pretty simple. The content of those variables is far more than you're seeing there. Run this:
$deletefrom | Format-List * -Force
You'll see that each match contains an object with properties for what file the match is from, what line number the match was found on, and more. I think if you capture the table that is being modified in your Select-String with a look behind of what you're searching on now you could group on that, and then alert on times where the delete happens after the insert.
Get-ChildItem "$packagepath\Init\*" -Include 03_Init_*.txt -Recurse |
Select-String "(?<=delete from |insert into )([^;]+)" |
Group-Object {$_.Matches[0].value} |
ForEach-Object {
if($_.group[0] -notmatch 'delete from'){Write-Warning "Inserting into $($_.Name) before deleting"}
}

How to remove the double quotes and skip last 3 lines in bcp loading into SQL Server?

I am loading data into SQL Server using bcp command line utility.
The problem is data is coming is like below. Every field in double quotes and I have to skip last three rows because it has some trailers. How can i solve this?
Thanks in Advance. I appreciate if you could help in this.
bcp database.schema.tablename in Filename.text -T -c -t"|" -r"0x0a" -F 3 -m 2
UHDR 20211110
"DATE","CUSIP","ISIN","SEDOL","TICKER","DESCRIPTION","QUANTITY","RATE","COMMENT","MARKET","FEE"
11/10/2021|""|"CA45826T3010"|"BMVXZT5"|"ITR"|"INTEGRA RESOURCES CORP REGISTERED SHS"|"28712"|"0.0000"|"HTB"|"CA"|"11.5000"
If you want to:
unconditionally remove all " chars.
unconditionally skip the last 3 lines:
(Get-Content -ReadCount 0 Filename.text) -replace '"' |
Select-Object -SkipLast 3 |
Set-Content Filename_CleanedUp.text
Note: -ReadCount 0 is a performance optimization that makes Get-Content read all lines into a single array instead of streaming the lines one by one.
Then pass Filename_CleanedUp.text to your bcp command.

PowerShell - Create Text File from SQL

I'm creating a PowerShell script to generate .sql files based on some stored procedures in a SQL Server database.
On the advice of this article, I'm using .NET's System.Data.SqlClient.SqlConnection class to create my connection and run stored procedures. Per the article, I can store that data in a System.Data.DataTable object.
The trouble I'm running into is how to best build the file. I'm new to PowerShell, so I've been getting away with something like this:
$SqlFile = "$CurrentDirectory\$CurrentDate`_$Library`_$File`_Table_CREATE.sql$ReviewFlag"
"USE [$TargetDB]`n`nCREATE TABLE [$Library].[$File] (`n" | Out-File $SqlFile
$dt.DataTypeString | Out-File -Append $SqlFile
")`n" | Out-File -Append $SqlFile
But the trouble now is that I need to generate the file extension based on another query, which will also be a part of the file, but I can't do that in sequence as per above; I would need to somehow save that output and write it to the file.
Do I want to keep invoking Out-File, or would it be better to somehow convert the DataTable object to a string or list of strings that I could then concatenate into one large string to be written to the file all at once? This doesn't have to be perfect, but I would like to write something reasonable and maintainable.
Edit: I decided to go route of using an ArrayList to store the values from the DataTable, and from there it's simple string concatenation:
$ColumnStringList = New-Object -TypeName 'System.Collections.ArrayList'
foreach($val in $dt.DataTypeString) {$ColumnStringList.Add("`n`t"+$val)}
And then
$SqlFileString = $($SqlFileHeader+$ColumnStringList+$RRNCol+$SqlFileFooter)
$SqlFileString | Out-File $SqlFile

Invoke-Sqlcmd : Could not find stored procedure

I have a dump of a SQL database table which contains only data. It is one long list of INSERT statements. The file is about 10GB and when I try to import with Invoke-Sqlcmd or the SQL Server management studio it fails with the message "Not enough memory". Therefore I split the file into several smaller files of 250MB. All the lines are complete, so no half INSERT statements at the end or beginning of each file because of splitting the files.
When I use Powershell to import the data the first file imports without problems.
Invoke-Sqlcmd -ServerInstance myserver\instance -Database mydatabase -InputFile "C:\temp\files\dbo.Data.00.sql"
Whenever I try to import the next file I get the following error message.
Invoke-Sqlcmd : Could not find stored procedure 'I'.
At line:1 char:1
+ Invoke-Sqlcmd -ServerInstance myserver\instance -Database mydatabase -I ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Invoke-Sqlcmd], SqlPowerShellSqlExecutionExceptio
+ FullyQualifiedErrorId : SqlError,Microsoft.SqlServer.Management.PowerShell.GetScriptCommand
It mentions the stored procedure could not be found but it are only INSERT statements. I also tried to specify the database name before the first INSERT statement but that does not change the result.
USE [mydatabase]
Any ideas what is going wrong here.
I managed to work around this issue by copying the dump file to a Linux host and use the following command to split the file into files of 250.000 lines each.
split -l 250000 dbo.Data.sql
There still was a problem with the split files. All files except for the first one contained NUL characters between each character.
I used the following solution to remove the NUL characters.
Removing "NUL" characters
By executing the following command for all split files except the first one.
tr < xab -d '\000' > xab.dbo.Data.sql
tr < xac -d '\000' > xac.dbo.Data.sql
etc...

mysqldump accents

in my database, the tables are collation = latin1
in mysqldump command i put --default-character-set=latin1
BUT, in sql ouput file, the accents, like á, appears ├í,
i appears í
I don't have idea and no find solution for this :x
after searching lot, i found the solution in this question
MysqlDump from Powershell and Windows encoding
To resolve, insert
[System.Console]::OutputEncoding = [System.Text.Encoding]::UTF8
in top of your PowerShell command, after, in invoke-expression, insert this -Encoding UTF8
is this the full command
invoke-expression $cmd | Out-File $backuppathandfile -Encoding UTF8