前回の記事では、ABAPからSAPのODataを呼び出す記事を紹介した。
今回はExcelマクロ(VBA)からODataを呼び出す方法を、実際のVBAコードをベースに紹介したいと思う。
ODataそのものの基本は以下の記事で解説しているため、本記事では「VBAではどう書くか」に集中して話を進める。

ブラウザからOData(Web API)を呼んで確認
ブラウザからURLでODataを呼ぶ
まず、サンプルで取り上げるODataサービス(Web API)を整理しておく。
ODataは前回の ODataをABAPから呼び出す方法 でも取り上げた C_PURCHASEORDER_FS_SRV だ。
たとえば、次のURLは C_PURCHASEORDER_FS_SRV を用いて、購買伝票の
- 仕入先(Supplier)
- 発注総額(PurchaseOrderNetAmount)
- 発注日(PurchaseOrderDate)
を取得する。
また、次のURLは上記に加えて、発注明細の情報も取り出す。
このURLをブラウザで叩くと、次のようなXMLの結果が返ってくる。
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xml:base="https://S4HANA2025.COM:44300/sap/opu/odata/sap/C_PURCHASEORDER_FS_SRV/">
<id>https://S4HANA2025.COM:44300/sap/opu/odata/sap/C_PURCHASEORDER_FS_SRV/C_PurchaseOrderFs('4500000062')</id>
<title type="text">C_PurchaseOrderFs('4500000062')</title>
<updated>2026-04-06T14:25:49Z</updated>
<category term="C_PURCHASEORDER_FS_SRV.C_PurchaseOrderFsType" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/>
<link href="C_PurchaseOrderFs('4500000062')" rel="self" title="C_PurchaseOrderFsType"/>
<link href="C_PurchaseOrderFs('4500000062')/to_PurchaseOrderItem" rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/to_PurchaseOrderItem" type="application/atom+xml;type=feed" title="to_PurchaseOrderItem">
<m:inline>
<feed xml:base="https://S4HANA2025.COM:44300/sap/opu/odata/sap/C_PURCHASEORDER_FS_SRV/">
<id>https://S4HANA2025.COM:44300/sap/opu/odata/sap/C_PURCHASEORDER_FS_SRV/C_PurchaseOrderFs('4500000062')/to_PurchaseOrderItem</id>
<title type="text">C_PurOrdItemEnh</title>
<updated>2026-04-06T14:25:49Z</updated>
<author>
<name/>
</author>
<link href="C_PurchaseOrderFs('4500000062')/to_PurchaseOrderItem" rel="self" title="C_PurOrdItemEnh"/>
<entry>
<id>https://S4HANA2025.COM:44300/sap/opu/odata/sap/C_PURCHASEORDER_FS_SRV/C_PurOrdItemEnh(PurchaseOrder='4500000062',PurchaseOrderItem='00010')</id>
<title type="text">C_PurOrdItemEnh(PurchaseOrder='4500000062',PurchaseOrderItem='00010')</title>
<updated>2026-04-06T14:25:49Z</updated>
<category term="C_PURCHASEORDER_FS_SRV.C_PurOrdItemEnhType" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/>
<link href="C_PurOrdItemEnh(PurchaseOrder='4500000062',PurchaseOrderItem='00010')" rel="self" title="C_PurOrdItemEnhType"/>
<link href="C_PurOrdItemEnh(PurchaseOrder='4500000062',PurchaseOrderItem='00010')/to_PlantValHelp" rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/to_PlantValHelp" type="application/atom+xml;type=entry" title="to_PlantValHelp"/>
<link href="C_PurOrdItemEnh(PurchaseOrder='4500000062',PurchaseOrderItem='00010')/to_POAccountAssignmentFactSheet" rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/to_POAccountAssignmentFactSheet" type="application/atom+xml;type=feed" title="to_POAccountAssignmentFactSheet"/>
<link href="C_PurOrdItemEnh(PurchaseOrder='4500000062',PurchaseOrderItem='00010')/to_PODeliveryAddressFactSheet" rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/to_PODeliveryAddressFactSheet" type="application/atom+xml;type=entry" title="to_PODeliveryAddressFactSheet"/>
<link href="C_PurOrdItemEnh(PurchaseOrder='4500000062',PurchaseOrderItem='00010')/to_POScheduleLineFactSheet" rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/to_POScheduleLineFactSheet" type="application/atom+xml;type=feed" title="to_POScheduleLineFactSheet"/>
<link href="C_PurOrdItemEnh(PurchaseOrder='4500000062',PurchaseOrderItem='00010')/to_PurchaseOrderEnhanced" rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/to_PurchaseOrderEnhanced" type="application/atom+xml;type=entry" title="to_PurchaseOrderEnhanced"/>
<link href="C_PurOrdItemEnh(PurchaseOrder='4500000062',PurchaseOrderItem='00010')/to_PurOrdScheduleLine" rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/to_PurOrdScheduleLine" type="application/atom+xml;type=feed" title="to_PurOrdScheduleLine"/>
<link href="C_PurOrdItemEnh(PurchaseOrder='4500000062',PurchaseOrderItem='00010')/to_ServicePerformer" rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/to_ServicePerformer" type="application/atom+xml;type=entry" title="to_ServicePerformer"/>
<content type="application/xml">
<m:properties>
<d:PurchaseOrder>4500000062</d:PurchaseOrder>
<d:PurchaseOrderItem>00010</d:PurchaseOrderItem>
<d:PurchaseOrderItemCategory>0</d:PurchaseOrderItemCategory>
<d:PurchaseOrderItemCategory_Text>標準</d:PurchaseOrderItemCategory_Text>
<d:Material>RM233-2</d:Material>
<d:ManufacturerMaterial>RM233-2</d:ManufacturerMaterial>
<d:ManufacturerMaterial_Text>RAW233-2、ND、かんばん、分納契約</d:ManufacturerMaterial_Text>
<d:MaterialGroup>L002</d:MaterialGroup>
<d:MaterialGroup_Text>原材料</d:MaterialGroup_Text>
<d:Plant>1710</d:Plant>
<d:Plant_Text>Plant 1 US</d:Plant_Text>
<d:AccountAssignmentCategory/>
<d:AccountAssignmentCategory_Text/>
<d:PurchaseOrderItemText>RAW233-2,ND,KANBAN,sched. agreements</d:PurchaseOrderItemText>
<d:PurchaseRequisition/>
<d:PurchaseRequisitionItem>00000</d:PurchaseRequisitionItem>
<d:ProductType/>
<d:ProductType_Text>互換モード</d:ProductType_Text>
<d:FirstDeliveryDate>2017-10-31T00:00:00</d:FirstDeliveryDate>
<d:OrderQuantity>100</d:OrderQuantity>
<d:PurchaseOrderQuantityUnit>PC</d:PurchaseOrderQuantityUnit>
<d:DocumentCurrency>USD</d:DocumentCurrency>
<d:PurchaseOrderPriceUnit>PC</d:PurchaseOrderPriceUnit>
<d:NetPriceAmount>3.00</d:NetPriceAmount>
<d:PurchaseOrderNetPriceQuantity>1</d:PurchaseOrderNetPriceQuantity>
<d:NetAmount>300.00</d:NetAmount>
<d:PurchasingDocumentDeletionCode/>
<d:ServicePerformer/>
<d:PerformancePeriodStartDate m:null="true"/>
<d:PerformancePeriodEndDate m:null="true"/>
<d:PurchaseOrderItemStatus/>
<d:OverdelivTolrtdLmtRatioInPct>10.0</d:OverdelivTolrtdLmtRatioInPct>
<d:UnderdelivTolrtdLmtRatioInPct>10.0</d:UnderdelivTolrtdLmtRatioInPct>
<d:UnlimitedOverdeliveryIsAllowed>false</d:UnlimitedOverdeliveryIsAllowed>
<d:IsToBeAcceptedAtOrigin>false</d:IsToBeAcceptedAtOrigin>
<d:StorageLocation>171E</d:StorageLocation>
<d:IntrastatServiceCode/>
<d:CommodityCode/>
<d:ShippingInstruction/>
<d:IncotermsVersion/>
<d:IncotermsClassification>EXW</d:IncotermsClassification>
<d:IncotermsLocation1>VENDOR</d:IncotermsLocation1>
<d:IncotermsLocation2/>
<d:PurchasingInfoRecord>5300000154</d:PurchasingInfoRecord>
<d:PurchaseContract/>
<d:PurContractForOverallLimit/>
<d:PurchaseContractItem>00000</d:PurchaseContractItem>
<d:InfoRecordIsToBeUpdated>true</d:InfoRecordIsToBeUpdated>
<d:TaxCode>I1</d:TaxCode>
<d:TaxCode_Text>A/P 売上税、課税対象、明細に配賦</d:TaxCode_Text>
<d:TaxJurisdiction>7700000000</d:TaxJurisdiction>
<d:TaxCalculationProcedure>TAXUSJ</d:TaxCalculationProcedure>
<d:SupplierConfirmationControlKey/>
<d:PurgDocOrderAcknNumber/>
<d:IsOrderAcknRqd>false</d:IsOrderAcknRqd>
<d:ItemIsRejectedBySupplier>false</d:ItemIsRejectedBySupplier>
<d:RequisitionerName/>
</m:properties>
</content>
</entry>
</feed>
</m:inline>
</link>
<content type="application/xml">
<m:properties>
<d:PurchaseOrderDate>2017-10-26T00:00:00</d:PurchaseOrderDate>
<d:Supplier>17300002</d:Supplier>
<d:PurchaseOrderNetAmount>300.00</d:PurchaseOrderNetAmount>
</m:properties>
</content>
</entry>
Excelマクロ(VBA)でURLを送信した場合も、上記と同じXMLがレスポンスとして返ってくる。
以下では、これと同じことをVBAで実装する例を紹介する。
ExcelマクロからODataを呼び出す
作成したExcelマクロ(VBA)
今回作成したサンプルVBAコードの全容は以下である。
Option Explicit
'===========================================================
' 購買発注ヘッダと発注明細 00010 を、1本の OData URL で取得する処理
'
' 取得対象
' 【ヘッダ】
' - Supplier
' - PurchaseOrderNetAmount
' - PurchaseOrderDate
'
' 【明細 00010】
' - Material
' - OrderQuantity
' - PurchaseOrderQuantityUnit
'
' 出力先
' Main!B3 : Supplier
' Main!B4 : PurchaseOrderNetAmount
' Main!B5 : PurchaseOrderDate
' Main!B6 : Material (明細00010)
' Main!B7 : OrderQuantity (明細00010)
' Main!B8 : PurchaseOrderQuantityUnit(明細00010)
'
' 入力元
' Main!B2 : 購買発注番号
' Config!B1 : SAP のベースURL
' Config!B2 : sap-client
' Config!B3 : ユーザID
' Config!B4 : パスワード
'
' ポイント
' - URLは1本だけ呼び出す
' - 明細は to_PurchaseOrderItem で展開して全部受け取る
' - その後、VBA側で PurchaseOrderItem = "00010" を探す
'===========================================================
Public Sub GetPOHeaderAndItem00010_FromSAP()
'--- ワークシート
Dim wsMain As Worksheet
Dim wsConfig As Worksheet
'--- 入力値・接続情報
Dim purchaseOrder As String
Dim baseUrl As String
Dim sapClient As String
Dim userId As String
Dim password As String
'--- 通信用URL
Dim requestUrl As String
'--- HTTP通信用オブジェクト
Dim http As Object
'--- XML解析用オブジェクト
Dim xmlDoc As Object
'--- ヘッダ用ノード
Dim supplierNode As Object
Dim netAmountNode As Object
Dim orderDateNode As Object
'--- 明細00010の entry ノード
Dim itemEntryNode As Object
'--- 明細00010の項目ノード
Dim materialNode As Object
Dim orderQtyNode As Object
Dim orderUnitNode As Object
'--- 出力前に値をいったん文字列で保持する
Dim supplierText As String
Dim netAmountText As String
Dim orderDateText As String
Dim materialText As String
Dim orderQtyText As String
Dim orderUnitText As String
' 何かエラーが出た場合は ErrHandler に飛ぶ
On Error GoTo ErrHandler
'===========================================================
' 1. シートを取得する
'===========================================================
Set wsMain = ThisWorkbook.Worksheets("Main")
Set wsConfig = ThisWorkbook.Worksheets("Config")
'===========================================================
' 2. 画面・設定シートから必要な値を読み込む
'===========================================================
purchaseOrder = Trim(CStr(wsMain.Range("B2").Value))
baseUrl = Trim(CStr(wsConfig.Range("B1").Value))
sapClient = Trim(CStr(wsConfig.Range("B2").Value))
userId = Trim(CStr(wsConfig.Range("B3").Value))
password = Trim(CStr(wsConfig.Range("B4").Value))
'===========================================================
' 3. 必須入力をチェックする
' ここで止まる場合は、まずセルの入力値を見直す
'===========================================================
If purchaseOrder = "" Then
Err.Raise vbObjectError + 100, , "Main シート B2 の購買発注番号が空である。"
End If
If baseUrl = "" Then
Err.Raise vbObjectError + 101, , "Config シート B1 のベースURLが空である。"
End If
If sapClient = "" Then
Err.Raise vbObjectError + 102, , "Config シート B2 の sap-client が空である。"
End If
If userId = "" Then
Err.Raise vbObjectError + 103, , "Config シート B3 のユーザIDが空である。"
End If
If password = "" Then
Err.Raise vbObjectError + 104, , "Config シート B4 のパスワードが空である。"
End If
'===========================================================
' 4. 1本で取得する OData URL を組み立てる
'
' ここではヘッダ項目を $select しつつ、
' 明細を $expand=to_PurchaseOrderItem で展開する
'
' 注意:
' この環境では $expand の中に ($select=...) を付けると
' エラーになるため、to_PurchaseOrderItem はそのまま展開する
'===========================================================
requestUrl = baseUrl & _
"/sap/opu/odata/sap/C_PURCHASEORDER_FS_SRV/" & _
"C_PurchaseOrderFs('" & Replace(purchaseOrder, "'", "''") & "')" & _
"?$select=Supplier,PurchaseOrderNetAmount,PurchaseOrderDate,to_PurchaseOrderItem" & _
"&$expand=to_PurchaseOrderItem" & _
"&sap-client=" & sapClient
' Immediate ウィンドウへURLを出力する
' VBAエディタで Ctrl + G を押すと確認できる
Debug.Print "===== Request URL ====="
Debug.Print requestUrl
'===========================================================
' 5. SAP へ HTTP GET を送信する
'===========================================================
Set http = CreateObject("MSXML2.XMLHTTP.6.0")
' False は同期実行である
' 返答が返るまで処理が止まるため、初心者には追いやすい
http.Open "GET", requestUrl, False, userId, password
' XMLで返してほしいことを伝える
http.setRequestHeader "Accept", "application/atom+xml,application/xml"
' リクエスト送信
http.Send
' ステータスコードを確認する
Debug.Print "===== HTTP Status ====="
Debug.Print http.Status & " " & http.StatusText
' レスポンス全文は長いことがあるため、先頭だけ確認する
Debug.Print "===== Response (first 2000 chars) ====="
Debug.Print Left$(http.ResponseText, 2000)
If http.Status <> 200 Then
Err.Raise vbObjectError + 1, , "HTTP Error: " & http.Status & " " & http.StatusText
End If
'===========================================================
' 6. XML文字列を DOM として読み込む
'===========================================================
Set xmlDoc = CreateObject("MSXML2.DOMDocument.6.0")
xmlDoc.async = False
If Not xmlDoc.LoadXML(http.ResponseText) Then
Err.Raise vbObjectError + 105, , "XMLの解析に失敗した。ResponseText を確認すること。"
End If
' XPath を使う設定
xmlDoc.SetProperty "SelectionLanguage", "XPath"
' OData Atom XML で使う名前空間を設定する
' atom を入れないと entry / content が取れない
xmlDoc.SetProperty "SelectionNamespaces", _
"xmlns:atom='http://www.w3.org/2005/Atom' " & _
"xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata' " & _
"xmlns:d='http://schemas.microsoft.com/ado/2007/08/dataservices'"
'===========================================================
' 7. ヘッダ項目を取得する
'
' 単一エンティティのため、ルートの atom:entry 配下に
' ヘッダの m:properties がある
'===========================================================
Set supplierNode = xmlDoc.SelectSingleNode("/atom:entry/atom:content/m:properties/d:Supplier")
Set netAmountNode = xmlDoc.SelectSingleNode("/atom:entry/atom:content/m:properties/d:PurchaseOrderNetAmount")
Set orderDateNode = xmlDoc.SelectSingleNode("/atom:entry/atom:content/m:properties/d:PurchaseOrderDate")
If supplierNode Is Nothing Then
Err.Raise vbObjectError + 2, , "Supplier が見つからない。"
End If
If netAmountNode Is Nothing Then
Err.Raise vbObjectError + 3, , "PurchaseOrderNetAmount が見つからない。"
End If
If orderDateNode Is Nothing Then
Err.Raise vbObjectError + 4, , "PurchaseOrderDate が見つからない。"
End If
supplierText = supplierNode.Text
netAmountText = netAmountNode.Text
orderDateText = orderDateNode.Text
'===========================================================
' 8. 展開された明細の中から、明細 00010 の entry を探す
'
' 構造のイメージ
' entry(ヘッダ)
' └ link title="to_PurchaseOrderItem"
' └ m:inline
' └ feed
' └ entry(明細1件ずつ)
'
' その中で d:PurchaseOrderItem = "00010" の entry を探す
'===========================================================
Set itemEntryNode = xmlDoc.SelectSingleNode( _
"/atom:entry/atom:link[@title='to_PurchaseOrderItem']/m:inline/atom:feed/atom:entry" & _
"[atom:content/m:properties/d:PurchaseOrderItem='00010']")
If itemEntryNode Is Nothing Then
Err.Raise vbObjectError + 5, , "明細 00010 が見つからない。"
End If
'===========================================================
' 9. 明細 00010 から必要項目を取得する
'===========================================================
Set materialNode = itemEntryNode.SelectSingleNode("atom:content/m:properties/d:Material")
Set orderQtyNode = itemEntryNode.SelectSingleNode("atom:content/m:properties/d:OrderQuantity")
Set orderUnitNode = itemEntryNode.SelectSingleNode("atom:content/m:properties/d:PurchaseOrderQuantityUnit")
If materialNode Is Nothing Then
Err.Raise vbObjectError + 6, , "明細 00010 の Material が見つからない。"
End If
If orderQtyNode Is Nothing Then
Err.Raise vbObjectError + 7, , "明細 00010 の OrderQuantity が見つからない。"
End If
If orderUnitNode Is Nothing Then
Err.Raise vbObjectError + 8, , "明細 00010 の PurchaseOrderQuantityUnit が見つからない。"
End If
materialText = materialNode.Text
orderQtyText = orderQtyNode.Text
orderUnitText = orderUnitNode.Text
'===========================================================
' 10. 取得した値をシートへ出力する
'===========================================================
wsMain.Range("B3").Value = supplierText
wsMain.Range("B4").Value = netAmountText
wsMain.Range("B5").Value = orderDateText
wsMain.Range("B6").Value = materialText
wsMain.Range("B7").Value = orderQtyText
wsMain.Range("B8").Value = orderUnitText
'===========================================================
' 11. デバッグ確認用に取得結果を出力する
'===========================================================
Debug.Print "===== Result ====="
Debug.Print "Supplier = " & supplierText
Debug.Print "PurchaseOrderNetAmount = " & netAmountText
Debug.Print "PurchaseOrderDate = " & orderDateText
Debug.Print "Material(00010) = " & materialText
Debug.Print "OrderQuantity(00010) = " & orderQtyText
Debug.Print "PurchaseOrderQuantityUnit(00010) = " & orderUnitText
MsgBox "ヘッダと明細 00010 の取得が完了した。", vbInformation, "GetPOHeaderAndItem00010_FromSAP"
Exit Sub
'===========================================================
' 12. エラーハンドリング
' エラー時は Immediate ウィンドウとメッセージで内容を出す
'===========================================================
ErrHandler:
Debug.Print "===== Error ====="
Debug.Print "Err.Number = " & Err.Number
Debug.Print "Err.Description = " & Err.Description
MsgBox Err.Description, vbExclamation, "GetPOHeaderAndItem00010_FromSAP"
End Sub
上記マクロを実装したExcelファイルは資料室にも公開してあるので、必要に応じでダウンロードし、利用してほしい。
なお、筆者が検証に使用した環境は、以前の記事で紹介した Professor’s RSA である。

このVBAコードがやっていること
このVBAは、SAPのODataサービスに対して1回だけアクセスし、購買発注のヘッダ情報と明細00010の情報をまとめて取得する処理である。
全体的な流れは、以下の通りだ。
- Excelシートから購買発注番号や接続情報を読む
- ODataのURLを組み立てる
- SAPにHTTP GETで問い合わせる
- 返ってきたXMLを読む
- ヘッダ情報を取り出す
- 明細一覧の中から「00010」の行だけ探す
- 結果をExcelに出力
$select と $expand
今回のURLでポイントになるのが、ODataの$selectと$expandだ。
$Selectは「欲しい項目を絞る」ための指定、$expandは「関連データも一緒に広げて取る」ための指定である。
ODataの$selectと$expandを使って、購買発注ヘッダ(Supplierなど)と明細(to_PurchaseOrderItem)を1回の通信でまとめて取得している。
requestUrl = baseUrl & _
"/sap/opu/odata/sap/C_PURCHASEORDER_FS_SRV/" & _
"C_PurchaseOrderFs('" & Replace(purchaseOrder, "'", "''") & "')" & _
"?$select=Supplier,PurchaseOrderNetAmount,PurchaseOrderDate,to_PurchaseOrderItem" & _
"&$expand=to_PurchaseOrderItem" & _
"&sap-client=" & sapClient
VBAでODataのデバッグをやるときのコツ
1. まず確認すべきは「送ったURL」
ODataのデバッグでは、最初に見るべきはURLである。
URLの組み立てでミスすると、正常なレスポンスを得られない。
そのため、サンプルコードでは通信前に requestUrl を Debug.Print で出力している。
そしていきなり完成形の長いURLを使うのではなく、短いURLから段階的に確認するのがコツである。
たとえば、次のように進めるとよい。
- ODataサービスのルートにアクセスする
- エンティティ単体(キー指定のみ)で取得する
$selectを追加する$expandを追加する
このように少しずつ条件を足していくことで、どの段階でエラーになるかがはっきりする。
「サービスのルートにアクセスする」とは、ODataサービスの一番上(入口)に対して、何も条件を付けずにアクセスすることである。
たとえば今回の例なら、URLはこうなる。
https://<HOST>/sap/opu/odata/sap/C_PURCHASEORDER_FS_SRV/
この状態では、
$selectも付けない$expandも付けない- キー指定(購買発注番号)もしない
つまり、一番シンプルなURLである。
また、作成したURLはVBAの中だけで確認せず、ブラウザで直接実行するのも有効である。
ブラウザでアクセスして、
- エラーが出ないか
- HTTP 200で返るか
- 欲しい項目がちゃんと含まれているか
を確認する。
2. HTTPステータスを見て成功かエラーかを判定
httpでURLを送信するとレスポンスが返ってくるが、レスポンス本文を見るよりも先に、httpのStatus を見る。
サンプルコードでは Status を見るため、リクエスト送信後に Status と StatusText を Debug.Print で出力している。
Debug.Print http.Status Debug.Print http.StatusText
Status で成功かエラーを切り分けられる。
Statusによる成功/エラーの判定
- 200 : 通信成功
- 401 : 認証エラー
- 404 : URLやサービス名の誤り
- 500 : SAP側エラーかクエリ条件不正
3. レスポンス全文ではなく、先頭だけを見る
レスポンス文が長いと読みにくい。
そのため、最初はレスポンスの先頭だけ確認すると効率がよい。
Debug.Print Left$(http.ResponseText, 1000)
ここで見たいのは、次のような点である。
- 本当にXMLが返っているか
- HTMLのエラーページが返っていないか
- SAPのエラーメッセージが返っていないか
4. XML解析エラーと通信エラーは分けて考える
通信が成功しても、XMLとして読めるとは限らない。
そのため、LoadXML できるかは別で確認するのがよい。
If Not xmlDoc.LoadXML(http.ResponseText) Then
Debug.Print "XML parse error"
End If
ここで失敗するなら、問題はWeb通信ではなく、返却内容の形式にある可能性が高い。
この切り分けをしないと、原因を見誤りやすい。
5. ODataでは名前空間を真っ先に疑う
レスポンスに値が見えているのに SelectSingleNode で取れないときは、まず名前空間を疑うべきである。
xmlDoc.SetProperty "SelectionNamespaces", _
"xmlns:atom='http://www.w3.org/2005/Atom' " & _
"xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata' " & _
"xmlns:d='http://schemas.microsoft.com/ado/2007/08/dataservices'"
ODataのAtom XMLでは、entry や content や項目名にプレフィックスが付く。
この設定がないと、データがあっても取れない。
6. XPathは一発で決めず、短く分けて試す
xmlDoc.SetProperty "SelectionLanguage", "XPath"
はXML文書の要素検索を「XPath」で行うことを宣言するため、プロパティ SelectionLanguage にセットしている。
長いXPathをいきなり書くと、どこで失敗したか分かりにくい。
そのため、短く刻んで確認するのがコツである。
たとえば、
Debug.Print xmlDoc.SelectSingleNode("/atom:entry").XML
次に
Debug.Print xmlDoc.SelectSingleNode("/atom:entry/atom:link").XML
というように、少しずつ下へたどる。
どこまでは取れて、どこから取れないかを見つけるのが重要である。
7. $expand を使ったときは、まず構造を見る
発注明細を取得するため $expand を使っているが、$expandで取得できる子データは入れ子構造で返る。
そのため、いきなり条件付きで探すのではなく、まず明細全体の構造を見るようにする。
つまり、
- link
- m:inline
- feed
- entry
の順に存在を確認する。
明細が見つからないときは、条件式より先に構造の理解不足を疑うのが正しい。
8. .Text の前に “Is Nothing” で確認する
これは小さいが重要である。
If supplierNode Is Nothing Then
Debug.Print "Supplier not found"
Else
Debug.Print supplierNode.Text
End If
いきなり .Text を取ると、エラーは出るが原因が雑になる。Is Nothing を入れると、「取れない」のか「空文字」なのかを区別できる。
9. ログは値だけでなく、ラベル付きで出す
デバッグ出力は、値だけだと判別できない。そのため、必ずラベルを付けるのがよい。
Debug.Print "Status=" & http.Status Debug.Print "URL=" & requestUrl Debug.Print "Supplier=" & supplierText
これだけで、調査のしやすさがかなり変わる。
マクロは証明書のエラーとは無縁?
ABAPではSSL証明書のエラーでハマりやすい。
証明書が正しくインポートされていないと、実行時に SSSLERR_PEER_CERT_UNTRUSTED エラーが発生してしまう。
前回の記事でも、SSSLERR_PEER_CERT_UNTRUSTED エラーの対策、すなわち ABAPコーディング以上に、SSL証明書のインポート手順など「証明書」への対応に時間を割いた。
しかし、Excelマクロ実行時に SSSLERR_PEER_CERT_UNTRUSTED エラーは起きない。
というか、実際にマクロを動かしてみれば分かる通り、SSL証明書はほぼ意識しない。
この違いは、「誰が証明書をチェックしているか」の違いである。
ABAPの場合:SAPサーバがチェックする
ABAPでODataを呼ぶとき、通信しているのはユーザのPCではなくSAPサーバである。
つまり、
- SAPアプリケーションサーバがHTTPS通信する
- SAP自身が証明書を検証する
- SAPが「この接続は安全か?」を判断する
このように、SAP(ABAP)はWindowsの証明書ストアを使っていない。
そのため、SAP側で以下が必要になる。
- T-CD: STRUSTで証明書(CA)を登録
- SSLクライアントPSEの設定
- SM59でHTTPS接続設定
これがないと、「このサーバは信用できない!」となってエラーになる。
Excelマクロの場合:Windowsがチェックする
一方、Excelマクロ(VBA)はどうか?
内部的にはこうだ。
- Windowsの通信機能(WinHTTP / WinINet)を使う
- SSLの検証はWindowsがやる
つまり、Excel自身は証明書を意識していない ということである。
さらにWindows内部では、以下も実行される。
- 主要な認証局(CA)を最初から信頼済み
- 自動更新もされる
その結果、大抵のHTTPSサイトなら、証明書のエラーは発生することなく、だいたい通るのである。
証明書のエラーを切り分けるなら
上記の通り、ABAPと違ってExcelマクロは証明書エラーの影響を受け難い。
この性質を利用すれば、
Excelマクロで成功
という順番でデバッグすれば、失敗の原因は証明書という切り分けが可能だ。
まとめ:ABAP+VBAでデバッグの生産性を上げる
前回記事で紹介した、ABAPからODataを呼ぶコードを、Excelマクロに置き換えたVBAコードとして紹介した。
ABAPではハマりやすい証明書エラーは、Excelマクロではほとんど起きない。
この理由は、ABAPは証明書のチェックをSAPサーバ側で行うのに対し、Excelでは証明書のチェックをWindowsが一括で面倒を見てくれるからである。
このため、Web API 時代のSAPエンジニアは、ABAP/VBAの両スキルを持つことが理想だ。
「ABAPは書けるけどVBAは書いたことがない」
という人は一定数いると思う。でも、安心してほしい。
今は生成AIの時代でもある。生成AIにABAPコードを読ませ、「これと同じことをExcelマクロで書いてほしい」と伝えるだけで、AIがVBAコードを生成してくれるのだ。



コメント