Использование Powershell для экспорта больших выходных данных из Oracle в CSV - PullRequest
0 голосов
/ 24 сентября 2019

Мне необходимо экспортировать довольно большой CSV-файл из Oracle один раз в неделю.

Я попробовал два подхода.

  1. Adapter.fill (набор данных)
  2. Цикл по столбцам и строкам для сохранения в файл CSV по одной строке за раз.

Первому не хватает памяти при работе (на сервере есть только 4 ГБ ОЗУ),второй занимает около часа, так как для экспорта нужно более 4 миллионов строк.

Вот код №1:

#Your query. It cannot contain any double quotes otherwise it will break.
$query = "SELECT manycolumns FROM somequery"

#Oracle login credentials and other variables
$username = "username"
$password = "password"
$datasource = "database address"
$output = "\\NetworkLocation\Sales.csv"

#creates a blank CSV file and make sure it's in ASCI
Out-File $output -Force ascii

#This here will look for "Oracle.ManagedDataAccess.dll" file inside "C:\Oracle" folder. We usually have two versions of Oracle installed so the adaptor can be in different locations. Needs changing if the Oracle is installed elsewhere.
$location = Get-ChildItem -Path C:\Oracle -Filter Oracle.ManagedDataAccess.dll -Recurse -ErrorAction SilentlyContinue -Force

#Establishes connection to Oracle using the DLL file
Add-Type -Path $location.FullName
$connectionString = 'User Id=' + $username + ';Password=' + $password + ';Data Source=' + $datasource
$connection = New-Object Oracle.ManagedDataAccess.Client.OracleConnection($connectionString)
$connection.open()
$command=$connection.CreateCommand()
$command.CommandText=$query

#Creates a table in memory and fills it with results from the query. Then, export the virtual table into CSV.
$DataSet = New-Object System.Data.DataSet
$Adapter = New-Object Oracle.ManagedDataAccess.Client.OracleDataAdapter($command)
$Adapter.Fill($DataSet)
$DataSet.Tables[0] | Export-Csv $output -NoTypeInformation

$connection.Close()

А вот # 2

#Your query. It cannot contain any double quotes otherwise it will break.
$query = "SELECT manycolumns FROM somequery"

#Oracle login credentials and other variables
$username = "username"
$password = "password"
$datasource = "database address"
$output = "\\NetworkLocation\Sales.csv"
$tempfile = $env:TEMP + "\Temp.csv"

#creates a blank CSV file and make sure it's in ASCI
Out-File $tempfile -Force ascii

#This here will look for "Oracle.ManagedDataAccess.dll" file inside "C:\Oracle" folder. Needs changing if the Oracle is installed elsewhere.
$location = Get-ChildItem -Path C:\Oracle -Filter Oracle.ManagedDataAccess.dll -Recurse -ErrorAction SilentlyContinue -Force

#Establishes connection to Oracle using the DLL file
Add-Type -Path $location.FullName
$connectionString = 'User Id=' + $username + ';Password=' + $password + ';Data Source=' + $datasource
$connection = New-Object Oracle.ManagedDataAccess.Client.OracleConnection($connectionString)
$connection.open()
$command=$connection.CreateCommand()
$command.CommandText=$query

#Reads results column by column. This way you don't have to specify how many columns it has.
$reader = $command.ExecuteReader()
  while($reader.Read()) {
       $props = @{}
       for($i = 0; $i -lt $reader.FieldCount; $i+=1) {
           $name = $reader.GetName($i)
           $value = $reader.item($i)
           $props.Add($name, $value)   
       }
       #Exports each line to CSV file. Works best when the file is on local drive as it saves it after each line.
       new-object PSObject -Property $props | Export-Csv $tempfile -NoTypeInformation -Append
  }

Move-Item $tempfile $output -Force

$connection.Close()

В идеалеЯ хотел бы использовать первый код, так как он намного быстрее второго, но чтобы как-то избежать нехватки памяти.

Ребята, вы знаете, есть ли способ "заполнить" первые 1 миллион?записи, добавить их в CSV, очистить таблицу «DataSet», следующие 1 миллион и т. д.?После завершения работы кода CSV весит ~ 1,3 ГБ, но при запуске даже 8 ГБ памяти ему недостаточно (на моем ноутбуке 8, но на сервере всего 4 ГБ, и он действительно сильно бьется).

Любые советы будут оценены.

Ответы [ 2 ]

1 голос
/ 24 сентября 2019

В сообществе * nix мы любим однострочники!

Вы можете установить разметку 'csv on' в sqlplus (> = 12)

Создать файл запроса

cat > query.sql <<EOF
set head off
set feed off
set timing off
set trimspool on
set term off
spool output.csv
select 
  object_id, 
  owner, 
  object_name, 
  object_type, 
  status, 
  created, 
  last_ddl_time 
from dba_objects;
spool off
exit;
EOF

Буферизация output.csv примерно так:

sqlplus -s -m "CSV ON DELIM ',' QUOTE ON" user/password@\"localhost:1521/<my_service>\" @query.sql

Другой вариант - SQLcl (инструмент CLI для SQL Developer. Двоичное имя: 'sql' переименован 'sqlcl' )

Создать файл запроса (Примечание! Срок включен | выключен)

cat > query.sql <<EOF
set head off
set feed off
set timing off
set term off
set trimspool on
set sqlformat csv
spool output.csv
select 
  object_id, 
  owner, 
  object_name, 
  object_type, 
  status, 
  created, 
  last_ddl_time 
from dba_objects 
where rownum < 5;
spool off
exit;
EOF

Буферизация output.csv вот так:

sqlcl -s system/oracle@\"localhost:1521/XEPDB1\" @query.sql

Альт!

cat output.csv 
9,"SYS","I_FILE#_BLOCK#","INDEX","VALID",18.10.2018 07:49:04,18.10.2018 07:49:04
38,"SYS","I_OBJ3","INDEX","VALID",18.10.2018 07:49:04,18.10.2018 07:49:04
45,"SYS","I_TS1","INDEX","VALID",18.10.2018 07:49:04,18.10.2018 07:49:04
51,"SYS","I_CON1","INDEX","VALID",18.10.2018 07:49:04,18.10.2018 07:49:04

И победителем является sqlplus для 77k строк!(удален фильтр rownum <5) </strong>

time sqlcl -s system/oracle@\"localhost:1521/XEPDB1\" @query.sql

real    0m23.776s
user    0m39.542s
sys     0m1.293s

time sqlplus -s -m "CSV ON DELIM ',' QUOTE ON" system/oracle@localhost/XEPDB1 @query.sql

real    0m3.066s
user    0m0.700s
sys     0m0.265s

wc -l output.csv
77480 output.csv

Вы можете экспериментировать с форматами в SQL Developer.

select /*CSV|HTML|JSON|TEXT|<TONSOFOTHERFORMATS>*/ from dba_objects;

Если вы загружаетеCSV в базу данных, этот инструмент сделает это!

https://github.com/csv2db/csv2db

Удачи!

0 голосов
/ 25 сентября 2019

Спасибо всем за ответы, я узнал о сценариях Oracle и SQL * плюс, что я никогда не знал, что существует.Возможно, я буду использовать их в будущем, но, думаю, мне придется обновить пакет Oracle Developer.

Я нашел способ отредактировать свой код для работы с использованием документации здесь: https://docs.oracle.com/database/121/ODPNT/OracleDataAdapterClass.htm#i1002865

Он не идеален, поскольку делает паузу через каждые 1 миллион строк, сохраняет выходные данные и повторно запускает запрос, который переоценивает его (для выполнения того, который я выполняю, требуется около 1-2 минут).

По сути, это то же самое, что запускать один код x раз (где x - верхний предел числа строк в миллионах), выполняя «сначала извлекать только 1 000 000 строк», затем «Смещать 1 000 000 строк» ​​Выбрать следующие 1 000 »Только 000 строк "и т. Д. И сохраняя его в CSV, добавляя внизу.

Вот код:

#Your query. It cannot contain any double quotes otherwise it will break.
$query = "SELECT
A lot of columns
FROM
a lot of tables joined together
WHERE
a lot of conditions
"

#Oracle login credentials and other variables
$username = myusername
$password = mypassword
$datasource = TNSnameofmyDatasource
$output = "$env:USERPROFILE\desktop\Sales.csv"

#creates a blank CSV file and make sure it's in ASCII as that's what the output of my query is
Out-File $output -Force ascii

#This here will look for "Oracle.ManagedDataAccess.dll" file inside "C:\Oracle" folder. Needs changing if the Oracle is installed elsewhere.
$location = Get-ChildItem -Path C:\Oracle -Filter Oracle.ManagedDataAccess.dll -Recurse -ErrorAction SilentlyContinue -Force

#Establishes connection to Oracle using the DLL file
Add-Type -Path $location.FullName
$connectionString = 'User Id=' + $username + ';Password=' + $password + ';Data Source=' + $datasource
$connection = New-Object Oracle.ManagedDataAccess.Client.OracleConnection($connectionString)
$connection.open()
$command=$connection.CreateCommand()
$command.CommandText=$query

#Creates a table in memory to be filled up with results from the query using ODAC
$DataSet = New-Object System.Data.DataSet
$Adapter = New-Object Oracle.ManagedDataAccess.Client.OracleDataAdapter($command)

#Declaring variables for the loop
$fromrecord = 0
$numberofrecords = 1000000
$timesrun = 0

#Loop as long as the number of Rows in the virtual table are equal to specified $numberofrecords
while(($timesrun -eq 0) -or ($DataSet.Tables[0].Rows.Count -eq $numberofrecords))
{
$DataSet.Clear()
$Adapter.Fill($DataSet,$fromrecord,$numberofrecords,'*') | Out-Null #Suppresses writing to console the number of rows filled
Write-progress "Saved: $fromrecord Rows"
$DataSet.Tables[0] | Export-Csv $output -Append -NoTypeInformation
$fromrecord=$fromrecord+$numberofrecords
$timesrun++
}

$connection.Close()
...