web-dev-qa-db-ja.com

PowershellスクリプトのExecuteReader()

PowerShellスクリプトでSQLテーブルからデータを読み取ろうとしています。リーダーオブジェクトでデータを確認できますが、While (readerobject.read()){}を使用してデータを読み取ると、ループの内側に移動しません。

Powershell

 $cmd = $sqlConn.CreateCommand()
 $cmd.CommandText ="SELECT * from user"
 $movedUserDetails = $cmd.ExecuteReader()
 while ($movedUserDetails.Read())
   {
      "[0] : " + $movedUserDetails.GetValue(0)
   }
 $movedUserDetails.Close() 
9
Geeth

構文は正しいですが、ループ内で値を使用して何もしていません。どういうわけかそれを永続化したいと思うでしょう。 Powershell内でいくつかの基本的なSQLを実行する例を以下に示します。2種類のコマンド(Text/SP)と2種類の実行方法(DataAdapter/DataReader)があります。どちらでも問題なく機能するはずです。

# config
$svr = "serverName"
$db = "databaseName"

# connection
$sqlConnection = New-Object System.Data.SqlClient.SqlConnection
$sqlConnection.ConnectionString = "Server=$svr;Database=$db;Integrated Security=True"
$sqlConnection.Open()

# command A - text
$sqlCmd = New-Object System.Data.SqlClient.SqlCommand
$sqlCmd.Connection = $sqlConnection
$sqlCmd.CommandText = "SELECT name AS TABLE_NAME FROM sys.tables"

# command B - stored procedure
$sqlCmd = New-Object System.Data.SqlClient.SqlCommand
$sqlCmd.Connection = $sqlConnection
$sqlCmd.CommandText = "sys.sp_tables"
$sqlCmd.CommandType = [System.Data.CommandType]::StoredProcedure
$sqlCmd.Parameters.Add("@table_owner", "dbo")

# execute A - data reader
$reader = $sqlCmd.ExecuteReader()
$tables = @()
while ($reader.Read()) {
    $tables += $reader["TABLE_NAME"]
}
$reader.Close()

# execute B - data adapter
$dataTable = New-Object System.Data.DataTable
$sqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$sqlAdapter.SelectCommand = $sqlCmd
$sqlAdapter.Fill($dataTable)
$sqlConnection.Close()
7
KyleMit

私はまったく同じ問題を抱えていましたが、その理由は次のとおりだと思います(私のために働いた):

エラーの場合など、データベース接続は常に正しく閉じられているとは限りません。閉じていない場合は、whileループをスキップします。コードを次のように変更します。

     $sqlConn.Open()
     $cmd = $sqlConn.CreateCommand()
     $cmd.CommandText ="SELECT * from user"
     $movedUserDetails = $cmd.ExecuteReader()
     try
     {
         while ($movedUserDetails.Read())
         {
           "[0] : " + $movedUserDetails.GetValue(0)
         }
     }
     catch
     {
       #log error
     }
     finally
     {
       $sqlConn.Close() 
     }

Finallyステートメントは常に実行され、接続が適切に閉じられていることを保証します。

1
Naha

私はあなたのコードを試してみましたが、うまくいきました。おそらく、SqlDataAdapterで試すことができます。 SQLでレコードをフェッチするためにこのPowershellモジュールを作成しました。それは私を失敗したことはありません

function Invoke-SqlSelect
{
    [CmdletBinding()]
    Param
    ( 
        [ValidateNotNullOrEmpty()] 
        [Parameter(ValueFromPipeline=$True,Mandatory=$True)] 
        [string] $SqlServer,
        [Parameter(ValueFromPipeline=$True,Mandatory=$False)] 
        [string] $Database = "master",
        [ValidateNotNullOrEmpty()] 
        [Parameter(ValueFromPipeline=$True,Mandatory=$True)] 
        [string] $SqlStatement
    )
    $ErrorActionPreference = "Stop"

    $sqlConnection = New-Object System.Data.SqlClient.SqlConnection
    $sqlConnection.ConnectionString = "Server=$SqlServer;Database=$Database;Integrated Security=True"

    $sqlCmd = New-Object System.Data.SqlClient.SqlCommand
    $sqlCmd.CommandText = $SqlStatement
    $sqlCmd.Connection = $sqlConnection

    $sqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
    $sqlAdapter.SelectCommand = $sqlCmd
    $dataTable = New-Object System.Data.DataTable
    try
    {
        $sqlConnection.Open()
        $sqlOutput = $sqlAdapter.Fill($dataTable)
        Write-Output -Verbose $sqlOutput
        $sqlConnection.Close()
        $sqlConnection.Dispose()
    }
    catch
    {
        Write-Output -Verbose "Error executing SQL on database [$Database] on server [$SqlServer]. Statement: `r`n$SqlStatement"
        return $null
    }


    if ($dataTable) { return ,$dataTable } else { return $null }
}
1
Rubanov

まず、SQL Serverですばやく簡単な作業をしている場合や、ファイルベースのスクリプトを実行している場合は、手間を省いて Invoke-Sqlcmd を使用します。それは本当に賢い人によって書かれ、維持されているので、あなたに役立つでしょう。

短期間に多くのクエリを実行する必要があり、接続を再利用するメリットがある場合。または、パラメータ化されたクエリの安全性/完全性が必要な場合は、SqlConnectionSqlCommandおよびSqlDataReaderの方が意味があります。

PowerShellはパイプライン指向の構造であることを念頭に置いて、パイプラインの観点から考え、それを効果的に活用する必要があります。つまり、すべてのレコードをDataTableにダンプして、下流で再び繰り返すだけでなく、動的な性質のPowerShellを利用して「コールバック」(つまり[ScriptBlock])を渡して操作を実行してみませんかIDataRecordを反復するときに、各IDataReaderで。

次の関数Invoke-SqlCommandには、接続文字列、クエリ、およびコールバックが必要です。これらは、行の投影/分析などに使用できます。

注:永続化されたSqlConnectionが必要な場合は、$ConnectionStringパラメータを$Connectionに置き換えるだけです。

function Invoke-SqlCommand {
  param(
    [Parameter(Mandatory=$True,
               ValueFromPipeline=$True,
               ValueFromPipelineByPropertyName=$True,
               HelpMessage="The connection string.")]
    [string] $ConnectionString,

    [Parameter(Mandatory=$True,
               HelpMessage="The query to run.")]
    [string] $Query,

    [Parameter(Mandatory=$True,
               HelpMessage="The work to perform against each IDataRecord.")]
    [scriptblock] $ScriptBlock
  )

  $conn = New-Object System.Data.SqlClient.SqlConnection
  $conn.ConnectionString = $ConnectionString

  $cmd = $conn.CreateCommand()
  $cmd.CommandText = $Query

  try {
    $conn.Open()
    $rd = $cmd.ExecuteReader()

    while($rd.Read()){
        Write-Output (Invoke-Command $ScriptBlock -ArgumentList $rd)
    }  
  } 
  finally {
    $conn.Close()
  }
}

catch {...}を指定せずに本番環境で使用しないでください。簡潔にするためにここでは省略しています。

この形式では、各IDataRecordに対していくつかの操作と投影を実行し、それをダウンストリーム処理のためにパイプラインに渡すことができます。

$connectionString = "your connection string"
$query = "SELECT * FROM users"
Invoke-SqlCommand $connectionString $query {
    param(
        [Parameter(Mandatory=$True)]
        [System.Data.SqlClient.SqlDataReader]$rd)

    $obj = New-Object -TypeName PSObject -Property @{ user_id = $rd.GetValue($rd.GetOrdinal("geoname_id"))}
    $obj.psobject.typenames.insert(0,'MyAwesome.Object')

    Write-Output $obj
}

ここでのNew-Objectの使用は、順序付けられたハッシュテーブルに依存することなく、一貫したフィールドの順序付けを提供するだけであり、Get-Memberのようなものを実行するときにカスタムPSObjectをより簡単に識別するのに役立ちます。

1
pimbrouwers