« 2026年4月 | トップページ | 2026年6月 »

2026年5月

STMicroElectronicsのSTM32 DFU USBドライバーとThrustMaster/Guillemotの競合問題

1. 前置き

NEXUS-XRのFCのFirmwareをINAVに書き換える時にWindowsが適切なDFUのドライバーを使用してくれず、PCがNEXUS-XRとDFUモードで通信できない問題が発生しました。色々試行錯誤したら、DFUで通信できていたTX16S Mark IIのEdgeTXも通信エラーになりました。BetaFlight、RotorFlight、ExpressLRSなども同様だと思います。但し、EdgeTXは、本記事の方法でもDFUモードをWindowsが認識しませんでした。

現象

  1. デバイスマネージャーの「ユニバーサル シリアル バス デバイス」にASUSのマザーボード用の「AURA LED Controller」のみが表示され、「STM32 BOOTLOADER」が表示されない。
    260529stm32ng1
  2. INAV Configuratorでは、DFUと表示されるものの、通信エラーが発生しFirmwareの更新ができない。
    EdgeTX ConpanionからのFirmware更新でも通信エラーになる。
  3. USBDeviewでは、次のものが表示される。
Description Service Description Driver Filename Device Mfg Friendly Name
Guillemot STM DFU Device @oem37.inf,%SvcDesc%;Guillemot DFU Driver for STM DFU Class Devices GuiSTDFUDev.sys Guillemot Corporation  
WinUsb Device WinUSB - Kernel Driver 06/02/2012 6.1.7600.16385 WinUsb.sys WinUsb Device AURA LED Controller

当初"AURA LED Controller"が"STM32のDFUドライバ"と競合して"STM32 BOOTLOADER"が表示されないのだと想像していました。

原因の1つは、"Guillemot STM DFU Device"でした。これは"ThrustMaster"の"USBコントローラー/ジョイスティック"の"Conrol Panel"をインストールする時に、一緒にインストールされるドライバーです。ThrustMaster製品のFirmwareのUpdateに使用されるものです。

ZadagやImpulseRC Driver Fixerでドライバーをインストールすると、インストールの途中で一瞬デバイスマネージャーの"ユニバーサルシリアルバスデバイス"に"STM32 BOOTLOADER"が表示されますが、直ぐに消えてしまいます。

2. 対策

試行錯誤の過程でZadigやImpulseRC Driver Fixerを実行したことで、ドライバー情報が追加されました。これらは使えない無駄な情報です。
対策としては、これらの無駄なドライバー情報とGuillemot STM DFU Deviceを含む全てのSTM32 DFU関連のドライバー情報を削除し、STMicroElectronicsの最新ドライバーを入れます。
しかし、STM32 BOOTLOADERがデバイスマネージャーに表示されないので、デバイスマネージャーからアンインストールができません。
そこで、次の手順で情報の削除とアンインストールをします。

1) USBDeviewでアンインストール

  1. ThrustMaster/Guillemotの製品を取り外します。(念のため、ドライバーを削除したときに、自動的に再インストールされないようにする。)
  2. USBDeviewをダウンロードし、実行します。
    https://www.nirsoft.net/utils/usb_devices_view.html#DownloadLinks
    [Download USBDeview for x64 systems]をクリックし、ダウンロードしたファイルを展開して、USBDeview.exeを実行します。
    (USBDeviewはインストールして使用するソフトではありません。)
  3. 一覧の中のSTM32 DFU関連のドライバー(Product IDがdf11)のDriver InfPath(例:oem37.inf)をメモします。
    アンインストール対象例 : Guillemot STM DFUと書かれているもの
  4. 当該USBドライバーを右クリックし[Uninstall]します。これで、レジストリ-が削除されます。

[*1] プロバイダー名がSTMicroelectronicsとなっているものも古い可能性があり、後で入れ直すので消して良いです。

参考

削除されるレジストリ : コンピューター\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB\VID_0483&PID_DF11\

参考 : USBDeviewでのGuillemot STM DFU Deviceの情報
Device Name : Port_#0001.Hub_#0003
Description : Guillemot STM DFU Device
Device Type : Application Specific
Connected : No
Safe To Unplug : Yes
Disabled : No
USB Hub : No
Drive Letter :
Serial Number : 1d3t15
Registry Time 1 : 2026/04/28 火 11:44:31
Registry Time 2 : 2026/04/12 日 14:03:58
VendorID : 0483
ProductID : df11
Firmware Revision : 22.00
WCID :
USB Class : fe
USB SubClass : 01
USB Protocol : 02
Hub / Port :
Computer Name : Z690
Vendor Name :
Product Name :
ParentId Prefix :
Service Name : GuiSTDFUDev
Service Description: @oem37.inf,%SvcDesc%;Guillemot DFU Driver for STM DFU Class Devices
Driver Filename : GuiSTDFUDev.sys
Device Class :
Device Mfg : Guillemot Corporation
Friendly Name :
Power :
USB Version :
Driver Description: Guillemot STM DFU Device
Driver Version : 7.0.3.0
Driver InfSection : _Install.NTamd64
Driver InfPath : oem37.inf
Instance ID : USB\VID_0483&PID_DF11\316433743135
Capabilities : Removable, UniqueID, SurpriseRemovalOK
Install Time :
First Install Time:
Connect Time :
Disconnect Time :

2) コマンドプロンプトで削除

ドライバーパッケージツールで、不要なドライバをドライバーストアから削除し、ドライバーパッケージをアンインストールします。

  1. [Start]-[Windowsツール]-[コマンドプロンプト]を右クリックし[管理者として実行]。
  2. 次のコマンドを実行して、ドライバーの一覧を表示します。
pnputil /enum-drivers
  1. 表示されたドライバーの中で、次のようなstm32 bootloader/dfu/VID_0483&PID_DF11関連の公開名のファイル名(oem*.inf)をメモします。1)でメモしたファイル名があれば、それも含めてください。
元の名前: guistdfudev.inf
プロバイダー名: Guillemot Corporation
署名者名: Microsoft Windows Hardware Compatibility Publisher
元の名前: stm32__bootloader.inf
プロバーダー名: libwdi
署名者名: USB\VID_0483&PID_DF11 (libwdi autogenerated)
元の名前: usb_device.inf
プロバイダー名: libusb.info
署名者名: USB\VID_0483&PID_DF11 (libwdi autogenerated)
元の名前:      stm32bootloader.inf
プロバイダー名: STMicroelectronics
クラス名: USBDevice
クラス GUID: {88bae032-5a81-49f0-bc3d-a4ff138216d6}
ドライバー バージョン: 05/07/2018 1.1.0.0
署名者名: Microsoft Windows Hardware Compatibility Publisher
属性: Declarative
WHCP バージョン: 不明
参考 : pnputil /enum-drivers の表示例
公開名:     oem81.inf
元の名前: guistdfudev.inf
プロバイダー名: Guillemot Corporation
クラス名: MEDIA
クラス GUID: {4d36e96c-e325-11ce-bfc1-08002be10318}
ドライバー バージョン: 08/15/2023 7.0.3.0
署名者名: Microsoft Windows Hardware Compatibility Publisher
属性: Declarative
Attested
WHCP バージョン: 不明

公開名: oem132.inf
元の名前: guistdfudev.inf
プロバイダー名: Guillemot Corporation
クラス名: MEDIA
クラス GUID: {4d36e96c-e325-11ce-bfc1-08002be10318}
ドライバー バージョン: 04/24/2026 7.4.9.0
署名者名: Microsoft Windows Hardware Compatibility Publisher
属性: Universal
Attested
WHCP バージョン: 不明
公開名:     oem60.inf
元の名前: stm32__bootloader.inf
プロバイダー名: libwdi
クラス名: USBDevice
クラス GUID: {88bae032-5a81-49f0-bc3d-a4ff138216d6}
ドライバー バージョン: 06/02/2012 6.1.7600.16385
署名者名: USB\VID_0483&PID_DF11 (libwdi autogenerated)
属性: Legacy
WHCP バージョン: 不明
公開名:     oem110.inf
元の名前: stm32bootloader.inf
プロバイダー名: STMicroelectronics
クラス名: USBDevice
クラス GUID: {88bae032-5a81-49f0-bc3d-a4ff138216d6}
ドライバー バージョン: 05/07/2018 1.1.0.0
署名者名: Microsoft Windows Hardware Compatibility Publisher
属性: Declarative
WHCP バージョン: 不明
  1. 次のコマンドで、メモしたoem*.infファイルのドライバーをアンインストールします。
pnputil /delete-driver oem*.inf /uninstall /force

*にはメモしたファイル名の番号を入れます。

3) Guillemot STM DFU Device のドライバーファイルの削除

次のフォルダーを削除するか、別の場所に移動します。これにより、DFUモードでUSB接続した時に再インストールされることを阻止します。

C:\Program Files (x86)\Guillemot\DFU_ST

もし、次のフォルダの下にDFU_STフォルダがあれば、それも削除するか移動します。

C:\Program Files\Guillemot

もし、次のファイルがあれば、それも削除するか移動したほうが良いかも知れません?

C:\Program Files\Thrustmaster\TM Flight Series\drivers\amd64\GuiSTDFUDevUpdate64.dll
C:\Program Files\Thrustmaster\TM Flight Series\drivers\x86\GuiSTDFUDevUpdate.dll

次のものがあれば、残します。これが使用したいSTMicroElectronicsのドライバーです。

C:\Program Files\STMicroelectronics\STM32Cube\STM32CubeProgrammer\Drivers

4) STM32CubeProgrammerのインストール

STMicroElectronicsのドライバーをインストールするために、STM32CubeProgrammerをインストールします。

  1. STM32CubeProgrammer software for all STM32 のインストール
    次のサイトから STM32CubeProgrammer software for all STM32 をダウンロードし、インストールします。GuestでOKです。入力したメールアドレスにダウンロードリンクが送られてきます。Win64でOKです。
    https://www.st.com/en/development-tools/stm32cubeprog.html

  2. 次の設定でインストールします。

  • STM32CubeProgrammer : 🗹
  • STM32TrustedPackageCreator : ☐
  • STMicroelectronicsユニバーサル シリアル バス デバイス : インストール
    260529stm32cube1 260529stm32cube2 260529stm32cube4 260529stm32ube5

以上でDFUモードで接続するための準備が完了します。

参考 : ThrustMaster製品の利用

Guillemot STM DFU Deviceを削除しても、ThrustMasterのUSB JoyStickは使用できます。
この状態でThrustMasterの製品のFirmware updateができるかは未確認です。できない場合は、削除したフォルダを元に戻すか、TrustMasterのソフトを再インストールすればよいと思われます。ThrustMesterのUSB JoyStickは、Firmware単体では提供されていないので、アプリごと再インストールしかないと思われます。そうすると、再びGuillemot STM DFU Deviceが入るので、削除作業が必要になります。Guillemotには、競合問題を改善してほしいものです。

参考 : USBDeviewの情報(非接続時)
Device Name : Port_#0001.Hub_#0003
Description : STM32 Bootloader
Device Type : Application Specific
Connected : No
Safe To Unplug : No
Disabled : No
USB Hub : No
Drive Letter :
Serial Number : STM32FxSTM32
Registry Time 1 : 2026/05/27 水 16:34:06
Registry Time 2 : 2026/05/27 水 16:34:06
VendorID : 0483
ProductID : df11
Firmware Revision : 22.00
WCID :
USB Class : fe
USB SubClass : 01
USB Protocol : 02
Hub / Port :
Computer Name : Z690
Vendor Name :
Product Name :
ParentId Prefix :
Service Name : WINUSB
Service Description: @winusb.inf,%WINUSB_SvcName%;WinUsb ドライバー
Driver Filename : WinUsb.sys
Device Class :
Device Mfg : STMicroelectronics
Friendly Name : @oem60.inf,%DeviceName%;STM32 Bootloader
Power :
USB Version :
Driver Description: STM32 Bootloader
Driver Version : 1.3.0.0
Driver InfSection : USB_Install
Driver InfPath : oem60.inf
Instance ID : USB\VID_0483&PID_DF11\STM32FxSTM32
Capabilities : Removable, UniqueID
Install Time :
First Install Time:
Connect Time :
Disconnect Time :
参考 : pnputil /enum-drivers の情報
公開名: oem60.inf
元の名前: stm32bootloader.inf
プロバイダー名: STMicroelectronics
クラス名: USBDevice
クラス GUID: {88bae032-5a81-49f0-bc3d-a4ff138216d6}
ドライバー バージョン: 11/28/2025 1.3.0.0
署名者名: STMicroelectronics International N.V.
属性: Legacy
WHCP バージョン: 不明

3. NEXUS-XRのFCのFirmwareをINAVにする例

以上でWindowsがSTMicroElectronicsのドライバーを使用する環境になったので、INAVを例に実際のFirmware Update手順を紹介します。
ここでは、NEXUS-XRのFCをINAV ConfiguratorでINAVにします。

  1. NEXUX-XRのFCボタンを押したまま、PCにUSB接続します。すると、デバイスマネージャーの「ユニバーサル シリアル バス デバイス」に「STM32 BOOTLOADER」が表示されます。不明なデバイスになった時は、もう一度行ってください。
    260529stm32ok1 260529stm32ok2 260529stm32ok3
  2. INAV Configuratorを起動すると、上部にDFUと表示されます。
  3. [ファームウェアフラッシャー]からファームウェアを書き込みます。
  • FCボード : NEXUSX
  • ファームウェアバージョン : (stable)の最新バージョンを選ぶ
  • 再起動シーケンスなし : Off
  • チップメモリの完全消去 : On(RotorFlightから書き換える時や、INAVのメジャーバージョンを変える時などは、On)
  • 伝送速度の手動選択 : Off
  • 未検証版リリースを表示 : Off
  1. [ファームウェアを選択[Online]]-[ファームウェアの更新]。
    (予めファームウェアをダウンロードしている場合は、[ファームウェアを選択]でファイルを選択する。)

 

4. EdgeTXの更新例

1) PCにUSB接続して更新

  1. EdgeTX Conpanionのインストール
    次のサイトの右のReleaseから最新版のedgetx-cpn-win64-vx.xx.x.zipをダウンロードし、インストールします。

EdgeTX Conpanionは上書きインストールされず、古いバージョンも並行して使用できるので、不要であれば古いバージョンをアンインストールします。その時、Settingを残しておくと良いです。Microsoft Visual C++ 2015-2022 Redistributableは、恐らく新しいバージョンでも使用すると思うので、残しておきます。これはアンインストールしても、新しいバージョンをインストールするときに、インストールできます。

  1. Firmwareの書き込み
    1. 送信機の電源を入れずにPCにUSB接続します。すると、6POSスイッチの1が点灯した状態になります。
    2. EdgeTX Conpanion の[Tool]-[Update conponents...]で、Updateしたいものが選ばれているかを確認し、[OK]すると、必要なUpdateされたファイルがダウンロードされます。
    3. [Radio]-[Write Firmware to Radio]で書き込みます。

注意 : 送信機のトリムSWのT4とT1を内側にしながら電源を入れても、PCにはDFUモードで認識されません。

  1. SDカードは、EdgeTX Conpanionから次の操作で更新します。
    [Tools]-[Synchronize SD card...]

2) 送信機単体で更新

接続に失敗した場合は、送信機本体で更新します。

  1. edgetx-firmware-vx.xx.x.zipをダウンロードして、適切な.binファイル(TX16S Mark II なら、tx16s-で始まるファイル)をSDカードのFIRMWAREフォルダにコピーします。

  2. T4とT1を中央にしながら電源を入れ[Write Firmware]-[tx16s-xxxxxxx.bin]で[ENT]を長押しで、アップデートします。

参考 : 通常起動から[SYS]-[Storage]-[FIRMWARE]-[tx16s-xxxxxxx.bin]-[Flash bootloader]では、アップデートしませんでした。

  1. SDカードは、EdgeTX Conpanionから次の操作で更新します。
    [Tools]-[Synchronize SD card...]

3) 送信機単体でBoot Loaderを更新

Bootloaderは、送信機本体で更新します。

  1. edgetx-firmware-vx.xx.x.zipをダウンロードして、適切な.binファイル(TX16S Mark II なら、tx16s-で始まるファイル)をSDカードのFIRMWAREフォルダにコピーします。このファイルは、Firmwareのファイルと同じです。

  2. 通常起動から[SYS]-[Storage]-[FIRMWARE]-[tx16s-xxxxxxx.bin]-[Flash bootloader]で、アップデートします。
  3. T4とT1を中央にしながら電源を入れ、Bootloaderのバージョンが更新されたことを確認します。

5. 参考

Guillemot STM DFU Device Driver は、あちこちで迷惑を掛けているようです。Guillemotさんは、他人に迷惑を掛けないように改善できないのでしょうか? MicrosoftもSTSTMicroelectronics のドライバを優先するようにできないものなのでしょうか?

| | コメント (0)

入庫単価が変動する物の在庫金額と回転率 Excel Power Pivot

1. 前置き

Yahoo!知恵袋に、入庫単価が変動する物の在庫管理の質問がありました。先入先出(FIFO:First-In First-Out)が前提です。
https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q12328004942

在庫数は、「入庫累計 - 出庫累計」や「前日の在庫 + 当日の入庫 - 当日の出庫」で求めます。
一方、出庫金額や在庫金額は、複数の入庫単価が含まれているケースを考慮する必要があり、式を考えるのが面倒です。
そこで、Claudeを利用してExcelのメジャー(DAX式)を作りました。更に金額ベースの回転率や在庫日数を求めるメジャーもClaudeを利用して作りました。出庫金額は、原価計算などにも利用できると思われます。

生産管理や調達業務の経験が無いので、もっと良い方法があるのかも知れません。
メジャーが長いので、計算に時間が掛かるかも知れません。遅いようであれば、ピボットテーブルに表示する範囲を絞ってください。例えば、日毎のピボットテーブルは1ヶ月のみ表示、月毎のピボットテーブルは日付を項目に設定しないなどです。ピボットテーブルは、コピーして異なる表示にすることができます。

(トレーサビリティが可能なシステムであれば、ロット番号と単価の紐づけにより単価が特定できすので、本記事より処理が簡単になります。)

検証をあまりしていないので、使用される場合は以下を読んで式を理解し、検証をした上で自己責任でお願いいたします。間違いがあれば、コメントをください。

参考:メジャーとは

Power Pivotはセルアドレスを使用しないので、ある程度セルアドレス間違いを軽減することができます。

2. 入力

テーブル名:入庫

日付 品名 入庫単価 入庫数
26/04/01 品A 1 50
26/04/01 品B 10 5
26/04/15 品A 2 10
26/05/01 品B 20 5
26/05/05 品A 3 50

260515zaikokin1 

実務では入庫表と単価表が分離していて、単価表は適用開始日のみの場合もあると思います。その場合は、Power Queryで入庫表に入庫単価をマージ、ソート、フィルするか、メジャーで単価表から単価を拾うようにすればよいです。前者の方が簡単ですが、後者の方がファイルが小さくなります。後者のDAX式もそれほど難しくないです。「6. 単価表が独立しているケース」を見てください。

テーブル名:出庫

日付 品名 出庫数
26/04/05 品A 40
26/04/05 品B 2
26/05/10 品B 6
26/05/20 品A 40

260515zaikokin2 

テーブル名:カレンダ

Power Pivotで時間軸の集計を行うので、日付テーブルを使用します。これは他の集計でも共通で使用できるので、共有フォルダに入れておくと良いです。これにより、四半期集計、前年同期差、等も出せるようになります。
【注意】必ず入庫・出庫の全日付を含むようにしてください。入庫・出庫の全日付を含む日付テーブルを自動生成する場合は、下記の参考を参照してください。

日付 年月 四半期 半期 年度 年度. 四半期. 期 月 年
26/04/01 26/04 26/1Q 26/1H 26年度 2026 1Q 上期 4月 2026
26/04/02 26/04 26/1Q 26/1H 26年度 2026 1Q 上期 4月 2026
省略                  
28/03/31 28/03 27/4Q 27/2H 27年度 2027 4Q 下期 3月 2027

260515zaikokin3 

  1. Excelで項目行と空の1行の表を作成し、その範囲を全選択し[挿入]-[テーブル]でテーブル化し、[テーブルデザイン]でテーブル名を`カレンダ'に変更します。
  2. 日付セルをA2とした場合の数式を次に示します。この式を必要な日付範囲まで下にコピーします。
A3 = 最初の日付(シリアル値)
A4 = A3 + 1
B3 = TEXT([@日付], "yy/mm")
C3 = LET(x, EDATE([@日付], -3), (YEAR(x)-2000) & "/" & (INT((MONTH(x) -1) / 3) + 1) & "Q")
D3 = LET(x, EDATE([@日付], -3), (YEAR(x) - 2000) & "/" & (INT((MONTH(x) - 1) / 6) + 1) & "H")
E3 = YEAR(EDATE([@日付], -3)) - 2000 & "年度"
F3 = YEAR(EDATE([@日付], -3))
G3 = INT((MONTH(EDATE([@日付], -3)) - 1) / 3) + 1 & "Q"
H3 = IF(MONTH(EDATE([@日付], -3)) < 7, "上期", "下期")
I3 = TEXT([@日付], "m月")
J3 = YEAR([@日付])

参考

自動的にPower Queryで、元データの範囲の日付テーブルを生成することもできます。自動的に生成すると、元データの日付が日付テーブルの範囲を超えて、集計漏れが発生することを防止することができます。次の記事に、日付テーブルのPower Query Mのコードがあります。

3. 出力

ピボットテーブルを出力します。

  品A
年月 日付 M入庫単価 合計 / 入庫数 合計 / 出庫数 M在庫数 M入庫金額 M出庫金額 M在庫金額 M回転率A
26/04 26/04/01 ¥1.0 50   50 ¥50   ¥50  
26/04/05     40 10   ¥40 ¥10  
26/04/15 ¥2.0 10   20 ¥20   ¥30  
26/04 集計 ¥1.2 60 40 20 ¥70 ¥40 ¥30 1.5
26/05 26/05/01       20     ¥30  
26/05/05 ¥3.0 50   70 ¥150   ¥180  
26/05/10       70     ¥180  
26/05/20     40 30   ¥90 ¥90  
26/05 集計 ¥3.0 50 40 30 ¥150 ¥90 ¥90 0.7

260515zaikokin4

4. 作成手順概要

1) Excelの表をデータモデルに追加

  1. 入庫表を全選択し、[挿入]-[テーブル]でテーブル化し、[テーブルデザイン]で名前を"入庫"に変更する。
    (テーブル化は必須では無いですが、後の処理を簡略化するために、本例ではテーブルにしています。)
  2. テーブルを選択し、[データ]-[テーブルまたは範囲から]で、Power Queryエディタに取込む。
  3. "日付"の左側の型アイコンをクリックし、[日付]。 ⇒ 型が日付になる。
  4. [ホーム]-[閉じて読み込むv]-[閉じて次に読み込む]で、"接続の作成のみ"と"このモデルをデータモデルに追加する"を選択し[OK]。

同様にして'出庫'、'カレンダ'をデータモデルに追加します。

2) '品目'テーブルの作成

  1. [データ]-[クエリと接続]で右のクエリの接続に表示されるクエリを右クリックし、[編集]。 ⇒ Power Queryエディタが開く。
  2. 左の'入庫'クエリを右クリックし、[参照]、右のプロパティの名前を"品名"に変更。 ⇒ "品名"クエリができる。
  3. "品名"列を右クリックし、[他の列の削除]。 ⇒ "品名"列のみになる。
  4. "品名"列を右クリックし、[重複の削除]。⇒ 品名の重複が無くなる。(ディメンジョンテーブルは、重複があるとリレーションできない。)
  5. [ホーム]-[閉じて読み込むv]-[閉じて次に読み込む]で、"接続の作成のみ"と"このモデルをデータモデルに追加する"を選択し[OK]。

260515zaikokin5 

3) リレーション

[データ]-[データモデル(緑アイコン)v]-[データモデルの管理]-[ホーム]-[ダイアグラムビュー]で、次のように項目をドラッグし、リレーションを作成します。ドラッグする方向は、ファクトテーブル(*)からディメンジョンテーブル(1)です。

'入庫'[日付] *:1 'カレンダ'[日付]
'出庫'[日付] *:1 'カレンダ'[日付]
'入庫'[品名] *:1 '品名'[品名]
'出庫'[品名] *:1 '品名'[品名]

260515zaikokin6 

4) ピボットテーブルの作成

  1. [挿入]-[ピボットテーブルv]-[データモデルから]。⇒ ピボットテーブルが作られます。
  2. [ピボットテーブル分析]-[ピボットテーブルv]-[オプション]-[レイアウトと書式]で、"更新時に列幅を自動調整する"のチェックを外す。
  3. [デザイン]-[レポートのレイアウト]-[表形式で表示]。

7) メジャー(DAX式)

ピボットテーブルのフィールドの'注文'または'完成'を右クリックし、[メジャーの追加]で次のメジャーを作成します。
以下、:=の左がメジャーの名前で、:=の右以降がメジャーの式です。

260515zaikokin7 

参考 : 別のメジャーの編集画面

Power Pivot for Excelの数式バーでも、メジャーを編集することができます。数式バーは、編集領域の拡大ができ、コードの要素別に色分けされるので、長い式を編集しやすいです。

操作手順:[Power Pivot]-[管理]-[ホーム]-[データビュー]の画面で編集します。下部のタブでメジャーを書くデータモデルを選択し、タブの上のセルでメジャーを書くセルを選択し、数式バーにメジャー名と式を入力します。先頭は、「メジャー名:=」で書き始めます。

メジャーは、どのデータモデルに書いても同じ動作をします。書くデータモデルは、ピボットテーブルのフィールドの表示場所を決めるだけのものです。
メジャーを書いたセルの移動はできません。

260515zaikokin8 

参考

「5. サンプル」には、本記事の式とは別に、作業用の中間メジャーを作成して分割して式を短くした「260515zaikoR2.xlsx」もあります。

'入庫'に作成するメジャー

M入庫累計

'入庫'または'出庫'データがある日の入庫数の累計です。データが無い日はBLANKです。ピボットテーブルで表示しないか、フィルターやスライサーで表示日の範囲を絞るなら、IFを削除し累計式だけでOKです。

M入庫累計:=IF(
CALCULATE(COUNTROWS('入庫'), ALL('品名'[品名])) > 0
|| CALCULATE(COUNTROWS('出庫'), ALL('品名'[品名])) > 0,
CALCULATE(
SUM('入庫'[入庫数]),
FILTER(
ALL('カレンダ'),
'カレンダ'[日付] <= MAX('カレンダ'[日付])
)
)
)

参考:メジャーで累計を求める仕組みは次の記事を見てください。

M入庫単価

入庫単価です。直接入庫単価を表示するのではなく、「単価 = 金額 / 数量」で逆算することにより、合計の平均単価を表示できるようにします。

M入庫単価:=DIVIDE([M入庫金額], SUM('入庫'[入庫数]))

M入庫金額

入庫金額 = 単価 * 数量 です。合計の場合は、複数のレコードを含むので、イテレータ関数SUMXで行毎に金額を計算したものを合計します。

M入庫金額:=SUMX('入庫', '入庫'[入庫単価] * '入庫'[入庫数])

参考

イテレータ関数とは、第1引数のテーブルに対し1行ずつ第2引数以降の処理をする関数です。

'出庫'に作成するメジャー

M出庫累計

'入庫'または'出庫'データがある日の出庫数の累計です。データが無い日はBLANKです。ピボットテーブルで表示しないか、フィルターやスライサーで表示日の範囲を絞るなら、IFを削除し累計式だけでOKです。

M出庫累計:=IF(
CALCULATE(COUNTROWS('入庫'), ALL('品名'[品名])) > 0
|| CALCULATE(COUNTROWS('出庫'), ALL('品名'[品名])) > 0,
CALCULATE(
SUM('出庫'[出庫数]),
FILTER(
ALL('カレンダ'),
'カレンダ'[日付] <= MAX('カレンダ'[日付])
)
)
)

M在庫数

在庫数 = 入庫累計 - 出庫累計 です。

M在庫数:=[M入庫累計] - [M出庫累計]

M出庫金額

FIFOで出庫対象範囲と重なる入庫データの範囲を抽出し、その「単価*数量」の合計をSUMXで求めます。仕組みは、コード中のコメント・後述の解説・サンプルファイルの中の図を見てください。
'入庫'または'出庫'データがある日のみ表示します。データが無い日はBLANKです。ピボットテーブルで表示しないか、フィルターやスライサーで表示日の範囲を絞るなら、最後のIFは不要です。

M出庫金額:=VAR vHinmei = IF(HASONEVALUE('品名'[品名]), VALUES('品名'[品名]), "ALL")
VAR vHinmeiList = ALL('品名'[品名])
// =============================================
// 入庫ロットテーブルの構築
// 各ロット(日付×品名×単価)に対して
// 品名内での入庫累計下限・上限を付与する
// =============================================
VAR vNyukoBase =
ADDCOLUMNS(
// 全期間・全品名の入庫レコードを取得
FILTER(ALL('入庫'), NOT(ISBLANK('入庫'[入庫数]))),
        // 入庫累計上限 = 同品名・同日付以前の入庫数合計
"@入庫累計上限",
VAR vThisHinmei = '入庫'[品名]
VAR vThisDate = '入庫'[日付]
RETURN
CALCULATE(
SUM('入庫'[入庫数]),
FILTER(
ALL('入庫'),
'入庫'[品名] = vThisHinmei && '入庫'[日付] <= vThisDate
)
),
        // 入庫累計下限 = 同品名・同日付より前の入庫数合計(なければ0)
"@入庫累計下限",
VAR vThisHinmei = '入庫'[品名]
VAR vThisDate = '入庫'[日付]
RETURN
CALCULATE(
SUM('入庫'[入庫数]),
FILTER(
ALL('入庫'),
'入庫'[品名] = vThisHinmei && '入庫'[日付] < vThisDate
)
) + 0
)
// =============================================
// 出庫トランザクションテーブルの構築
// 各出庫レコードに対して
// 品名内での出庫累計下限・上限を付与する
// (月集計・総計対応のため全出庫をループする)
// =============================================
VAR vSoukoTrans =
ADDCOLUMNS(
// 全期間・全品名の出庫レコードを取得
FILTER(ALL('出庫'), NOT(ISBLANK('出庫'[出庫数]))),
        // 出庫累計上限 = 同品名・同日付以前の出庫数合計
"@出庫累計上限",
VAR vThisHinmei = '出庫'[品名]
VAR vThisDate = '出庫'[日付]
RETURN
CALCULATE(
SUM('出庫'[出庫数]),
FILTER(
ALL('出庫'),
'出庫'[品名] = vThisHinmei && '出庫'[日付] <= vThisDate
)
),
        // 出庫累計下限 = 同品名・同日付より前の出庫数合計(なければ0)
"@出庫累計下限",
VAR vThisHinmei = '出庫'[品名]
VAR vThisDate = '出庫'[日付]
RETURN
CALCULATE(
SUM('出庫'[出庫数]),
FILTER(
ALL('出庫'),
'出庫'[品名] = vThisHinmei && '出庫'[日付] < vThisDate
)
) + 0
)
// ピボットのコンテキストの日付範囲を取得
// 日付1行・月集計・総計のいずれにも対応
VAR vDateMin = MIN('カレンダ'[日付])
VAR vDateMax = MAX('カレンダ'[日付])
// =============================================
// FIFO按分計算
// コンテキストの日付範囲内の出庫トランザクションを
// 1件ずつループし、各件の出庫累計範囲(下限〜上限)に
// 重なる入庫ロットを特定して単価×数量を合算する
// =============================================
VAR vResult =
SUMX(
// コンテキストの日付範囲内かつ対象品名の出庫トランザクションに絞る
FILTER(
vSoukoTrans,
'出庫'[日付] >= vDateMin && '出庫'[日付] <= vDateMax
&& IF(
vHinmei = "ALL",
TRUE(),
'出庫'[品名] = vHinmei
)
),
// 当該出庫トランザクションの品名・累計範囲を取得
VAR vH = '出庫'[品名]
VAR vSouko_Upper = [@出庫累計上限] // 今回までの出庫累計
VAR vSouko_Lower = [@出庫累計下限] // 前回までの出庫累計
RETURN
// 出庫累計範囲に重なる入庫ロットをFIFOで按分
SUMX(
FILTER(
// 対象品名の入庫ロットに絞る
FILTER(vNyukoBase, '入庫'[品名] = vH),
// 出庫累計範囲と入庫累計範囲が重なるロットのみ
[@入庫累計上限] > vSouko_Lower
&& [@入庫累計下限] < vSouko_Upper
),
// 重複数量 = 両範囲の重なり部分
// MIN(当日出庫累計, @当日入庫累計) - MAX(前日出庫累計, @前日入庫累計)
VAR vOverlapQty =
MIN(vSouko_Upper, [@入庫累計上限])
- MAX(vSouko_Lower, [@入庫累計下限])
RETURN
// 重複数量 × 入庫単価
IF(vOverlapQty > 0, vOverlapQty * '入庫'[入庫単価], 0)
)
)
RETURN
IF( // 出庫が無い行はBLANKを返して非表示にする
CALCULATE(COUNTROWS('入庫'), ALL('品名'[品名])) > 0
|| CALCULATE(COUNTROWS('出庫'), ALL('品名'[品名])) > 0,
vResult
)

式の解説

vNyukoBaseは、'入庫'テーブルに該当行の前の日までの品名別の累計(下限)と該当日までの累計(上限)列を追加したものです。
vSoukoTransは、'出庫'テーブルに該当行の前の日までの品名別の累計(下限)と該当日までの累計(上限)列を追加したものです。
vResultでSoukoTranceの1行毎にvNyukoBaseの該当する部分の単価と数量を掛け算したものをSUMXで合計し、更にコンテキストの範囲の合計をSUMXで求めています。

補足

Claudeが提案したコードは、最後のIFを先頭に配置するものでした。これは、計算不要な判断を先にして、パフォーマンスを上げるためです。しかし、IFの真の場合のコードが非常に長く可読性が悪いので、上記のようにIFを最後のRETURNの後に配置しました。パフォーマンスは不明ですが、DAXエンジンの最適化で、評価しない変数は計算されないと思われ、パフォーマンスの低下は少しだけかも知れません?

 

M在庫金額

FIFOで在庫対象範囲と重なる入庫データの範囲を抽出し、その単価*数量の合計を求めます。仕組みは、コード中のコメント・後述の解説・サンプルファイルの中の図を見てください。
'入庫'または'出庫'データがある日のみ表示します。データが無い日はBLANKです。ピボットテーブルで表示しないか、フィルターやスライサーで表示日の範囲を絞るなら、最後のIFは不要です。

M在庫金額:=VAR vMaxDate = MAX('カレンダ'[日付])
// ピボットの品名フィルタを取得。複数品名(集計行)は"ALL"
VAR vHinmei = IF(HASONEVALUE('品名'[品名]), VALUES('品名'[品名]), "ALL")
// =============================================
// 入庫ロットテーブルの構築
// 各ロット(日付×品名×単価)に対して
// 品名内での入庫累計下限・上限を付与する
// =============================================
VAR vNyukoBase =
ADDCOLUMNS(
// 全期間・全品名の入庫レコードを取得
FILTER(ALL('入庫'), NOT(ISBLANK('入庫'[入庫数]))),
        // 入庫累計上限 = 同品名・同日付以前の入庫数合計
"@入庫累計上限",
VAR vThisHinmei = '入庫'[品名]
VAR vThisDate = '入庫'[日付]
RETURN
CALCULATE(
SUM('入庫'[入庫数]),
FILTER(
ALL('入庫'),
'入庫'[品名] = vThisHinmei
&& '入庫'[日付] <= vThisDate
)
),
        // 入庫累計下限 = 同品名・同日付より前の入庫数合計(なければ0)
"@入庫累計下限",
VAR vThisHinmei = '入庫'[品名]
VAR vThisDate = '入庫'[日付]
RETURN
CALCULATE(
SUM('入庫'[入庫数]),
FILTER(
ALL('入庫'),
'入庫'[品名] = vThisHinmei
&& '入庫'[日付] < vThisDate
)
) + 0
)
// =============================================
// FIFO按分計算
// vHinmei="ALL"(集計行)のとき品名ごとにループして合算
// 品名フィルタあり のとき直接計算
// =============================================
VAR vResult =
IF(
vHinmei = "ALL",
        // 全体・総計: 品名ごとにループして在庫金額を合算
SUMX(
ALL('品名'[品名]),
VAR vH = '品名'[品名]
            // 対象品名の入庫累計上限(コンテキスト日付まで)
VAR vNyuko_Upper =
CALCULATE(
SUM('入庫'[入庫数]),
FILTER(ALL('カレンダ'), 'カレンダ'[日付] <= vMaxDate),
'入庫'[品名] = vH
)
            // 対象品名の出庫累計上限(コンテキスト日付まで)
// = FIFOで払い出し済みの数量
VAR vSouko_Upper =
CALCULATE(
SUM('出庫'[出庫数]),
FILTER(ALL('カレンダ'), 'カレンダ'[日付] <= vMaxDate),
'出庫'[品名] = vH
)
            RETURN
// 在庫残ロット = 入庫累計範囲のうち出庫済み範囲より後ろの部分
SUMX(
FILTER(
// 処理対象品名の入庫ロットに絞る
FILTER(vNyukoBase, '入庫'[品名] = vH),
// 出庫済み範囲より後ろかつ入庫済み範囲内のロット
[@入庫累計上限] > vSouko_Upper
&& [@入庫累計下限] < vNyuko_Upper
),
// 重複数量 × 入庫単価
// MIN(当日入庫累計, @当日入庫累計) - MAX(当日出庫累計, @前日入庫累計)
VAR vOverlapQty =
MIN(vNyuko_Upper, [@入庫累計上限])
- MAX(vSouko_Upper, [@入庫累計下限])
RETURN
IF(vOverlapQty > 0, vOverlapQty * '入庫'[入庫単価], 0)
)
),
        // 品名フィルタあり: 対象品名のみ直接計算
VAR vNyuko_Upper = [M入庫累計]
VAR vSouko_Upper = [M出庫累計]
RETURN
SUMX(
FILTER(
// 対象品名の入庫ロットに絞る
FILTER(vNyukoBase, '入庫'[品名] = vHinmei),
// 在庫残ロット = 出庫済み範囲より後ろかつ入庫済み範囲内
[@入庫累計上限] > vSouko_Upper
&& [@入庫累計下限] < vNyuko_Upper
),
// 重複数量 = 両範囲の重なり部分
// MIN(当日入庫累計, @当日入庫累計)-MAX(当日出庫累計, @前日入庫累計)
VAR vOverlapQty =
MIN(vNyuko_Upper, [@入庫累計上限])
- MAX(vSouko_Upper, [@入庫累計下限])
RETURN
// 重複数量 × 入庫単価
IF(vOverlapQty > 0, vOverlapQty * '入庫'[入庫単価], 0)
)
)
RETURN
IF(
CALCULATE(COUNTROWS('入庫'), ALL('品名'[品名])) > 0
|| CALCULATE(COUNTROWS('出庫'), ALL('品名'[品名])) > 0,
vResult
)

式の解説

vNyukoBaseは、'入庫'テーブルに該当行の前の日までの品名別の累計(下限)と該当日までの累計(上限)列を追加したものです。
在庫範囲は出庫累計~入庫累計なので、vResultの中で、在庫下限を出庫累計、在庫上限を入庫累計に設定しています。これを後述の「FIFO按分数量計算の基本的な考え方」の仕組みでvNyukoBaseの範囲を抽出し、該当部分の単価と数量から金額を計算しています。

M在庫金額_指定日

回転数計算用に使用する中間メジャーです。
FIFOで在庫対象範囲と重なる入庫データの範囲を抽出し、その単価*数量の合計を求めます。仕組みは、コード中のコメント・後述の解説・サンプルファイルの中の図を見てください。
回転数を求めるために必要な、データが無い日の在庫金額も計算します。

M在庫金額_指定日:=VAR vHinmei =
IF(HASONEVALUE('品名'[品名]), VALUES('品名'[品名]), "ALL")
// ピボットの品名フィルタを取得。複数品名(集計行)は"ALL"
// =============================================
// M在庫金額_指定日 : コンテキストの在庫金額を求めるメジャー。
// M在庫金額の'入庫'、'出庫'レコード[日付]の限定を解除し、入庫、出庫が無い日も算出。
// ピボットテーブルに表示しない。M在庫金額平均、M回転率算出用。
// =============================================
VAR vThisDate = MAX('カレンダ'[日付])
// =============================================
// 入庫ロットテーブルの構築
// 各ロット(日付×品名×単価)に対して
// 品名内での入庫累計下限・上限を付与する
// =============================================
VAR vNyukoBase =
ADDCOLUMNS(
// 全期間・全品名の入庫レコードを取得
FILTER(ALL('入庫'), NOT(ISBLANK('入庫'[入庫数]))),
        // 入庫累計上限 = 同品名・同日付以前の入庫数合計
"@入庫累計上限",
VAR vThisHinmei = '入庫'[品名]
VAR vD = '入庫'[日付]
RETURN
CALCULATE(
SUM('入庫'[入庫数]),
FILTER(ALL('入庫'),
'入庫'[品名] = vThisHinmei
&& '入庫'[日付] <= vD
)
),
        // 入庫累計下限 = 同品名・同日付より前の入庫数合計(なければ0)
"@入庫累計下限",
VAR vThisHinmei = '入庫'[品名]
VAR vD = '入庫'[日付]
RETURN
CALCULATE(
SUM('入庫'[入庫数]),
FILTER(ALL('入庫'),
'入庫'[品名] = vThisHinmei
&& '入庫'[日付] < vD
)
) + 0
)
RETURN
// =============================================
// FIFO按分計算
// vHinmei="ALL"(集計行)のとき品名ごとにループして合算
// 品名フィルタあり のとき直接計算
// =============================================
IF(
vHinmei = "ALL",
// 全体・総計: 品名ごとにループして在庫金額を合算
SUMX(
ALL('品名'[品名]),
VAR vH = '品名'[品名]
       // 対象品名の入庫累計上限(コンテキスト日付まで)
VAR vNyuko_Upper =
CALCULATE(
SUM('入庫'[入庫数]),
FILTER(ALL('入庫'),
'入庫'[品名] = vH
&& '入庫'[日付] <= vThisDate
)
)
        // 対象品名の出庫累計上限(コンテキスト日付まで)
// = FIFOで払い出し済みの数量
VAR vSouko_Upper =
CALCULATE(
SUM('出庫'[出庫数]),
FILTER(ALL('出庫'),
'出庫'[品名] = vH
&& '出庫'[日付] <= vThisDate
)
) + 0
        RETURN
// 在庫残ロット = 入庫累計範囲のうち出庫済み範囲より後ろの部分
SUMX(
FILTER(
// 処理対象品名の入庫ロットに絞る
FILTER(vNyukoBase, '入庫'[品名] = vH),
// 出庫済み範囲より後ろかつ入庫済み範囲内のロット
[@入庫累計上限] > vSouko_Upper
&& [@入庫累計下限] < vNyuko_Upper
),
// 重複数量 = 両範囲の重なり部分
// MIN(当日入庫累計, @当日入庫累計) - MAX(当日出庫累計, @前日入庫累計)
VAR vOverlapQty =
MIN(vNyuko_Upper, [@入庫累計上限])
- MAX(vSouko_Upper, [@入庫累計下限])
RETURN
// 重複数量 × 入庫単価
IF(vOverlapQty > 0, vOverlapQty * '入庫'[入庫単価], 0)
)
),
    // 品名フィルタあり: 対象品名のみ直接計算
// 入庫累計
VAR vNyuko_Upper =
CALCULATE(
SUM('入庫'[入庫数]),
FILTER(ALL('入庫'),
'入庫'[品名] = vHinmei
&& '入庫'[日付] <= vThisDate
)
)
// 出庫累計
VAR vSouko_Upper =
CALCULATE(
SUM('出庫'[出庫数]),
FILTER(ALL('出庫'),
'出庫'[品名] = vHinmei
&& '出庫'[日付] <= vThisDate
)
) + 0
RETURN
SUMX(
FILTER(
// 対象品名の入庫ロットに絞る
FILTER(vNyukoBase, '入庫'[品名] = vHinmei),
// 在庫残ロット = 出庫済み範囲より後ろかつ入庫済み範囲内
[@入庫累計上限] > vSouko_Upper
&& [@入庫累計下限] < vNyuko_Upper
),
// 重複数量 = 両範囲の重なり部分
// MIN(当日入庫累計, @当日入庫累計)-MAX(当日出庫累計, @前日入庫累計)
VAR vOverlapQty =
MIN(vNyuko_Upper, [@入庫累計上限])
- MAX(vSouko_Upper, [@入庫累計下限])
RETURN
// 重複数量 × 入庫単価
IF(vOverlapQty > 0, vOverlapQty * '入庫'[入庫単価], 0)
)
)


式の解説

M在庫金額とほぼ同じです。主な違いは次の2点です。これにより、全日の在庫金額を算出できるようにし、後述のM在庫金額をで利用できるようにしています。

  • 品名が1つの場合に、在庫範囲の上下限を用いる式に[M入庫累計]と[M出庫累計]を使用せず、累計計算式を記述している。(理由 : [M入庫累計]と[M出庫累計]は入出庫が無い日の値がBLANKなので、後の[M在庫金額平均]に使えないため。)
  • '入庫'と'出庫'のレコードに限定するIF文が無い。(補足:VAR vResutを使用せずに、直接RETURNの下にvResultにあったコードを書いています。)

M在庫金額平均A

回転数計算用に使用する中間メジャーです。
計算対象範囲の全ての日の在庫金額の平均値を求めます。
単日では計算しません。(単日の回転率が必要であれば、IFを削除してください。)

M在庫金額平均A:=IF(
NOT(HASONEVALUE('カレンダ'[日付])),
AVERAGEX(
'カレンダ',
VAR vThisDate = 'カレンダ'[日付]
RETURN
CALCULATE(
[M在庫金額_指定日],
FILTER(ALL('カレンダ'), 'カレンダ'[日付] = vThisDate)
)
)
)
式の解説

コンテキストの範囲の'カレンダ'のM在庫金額_指定日をAVERAGEXで平均しています。CALCULATEのFILTERでAVRAGEXの行コンテキストである日付をvThisDateで参照して、日付毎のM在庫金額_指定日を計算しています。
ピボットテーブルに表示しないのであれば、IFは不要です。

M在庫金額平均B

回転率計算用に使用する中間メジャーです。
M在庫金額平均A = (計算対象範囲の初日の在庫 + 計算対象範囲の最終日の在庫) / 2
この式は、回転率の計算に一般的に使われているようです。但し、入庫、出庫の時期が偏っている場合は、誤差が大きくなります。
単日では計算しません。(単日の回転率が必要であれば、IFを削除してください。)

M在庫金額平均B:=IF(
NOT(HASONEVALUE('カレンダ'[日付])),
VAR vDateMin = MIN('カレンダ'[日付])
VAR vDateMax = MAX('カレンダ'[日付])
    // 期首時点の在庫金額
VAR vStart =
CALCULATE(
[M在庫金額_指定日],
FILTER(ALL('カレンダ'), 'カレンダ'[日付] = vDateMin)
)
// 期末時点の在庫金額
VAR vEnd =[M在庫金額平均B]
CALCULATE(
[M在庫金額_指定日],
FILTER(ALL('カレンダ'), 'カレンダ'[日付] = vDateMax)
)
RETURN
DIVIDE(vStart + vEnd, 2)
)

M回転率A

対象期間の全日の在庫平均を用いた回転率です。M回転率A = 対象期間の出庫金額合計 / 対象期間の在庫金額の平均
M回転率はコンテキストの対象期間の回転率で、財務管理で使われるようです。

M回転率A:=DIVIDE([M出庫金額], [M在庫金額平均A])

M回転率B

初日と最終日の在庫だけを用いた回転率です。M回転率B = 対象期間の出庫金額合計 / (対象期間の初日在庫金額 + 最終日在庫金額) / 2

M回転率B:=DIVIDE([M出庫金額], [M在庫金額平均B])

 

M月回転率A

M月回転率は月当たりの回転率で、生産管理で使われるようです。式は次の通りです。
M月回転率A = M回転率A / コンテキストの月数

M月回転率A :=
IF(
NOT(HASONEVALUE('カレンダ'[日付])),
// コンテキストの月数を取得
VAR vMonthCount =
DISTINCTCOUNT('カレンダ'[年月])
RETURN
DIVIDE([M回転率A], vMonthCount)
)

M在庫日数A

式は、「在庫日数 = 月の日数 / 月回転率 = (日数 / 月数) / 月回転率」です。

M在庫日数A :=
IF(
NOT(HASONEVALUE('カレンダ'[日付])),
// コンテキストの月数を取得
VAR vMonthCount =
DISTINCTCOUNT('カレンダ'[年月])
// コンテキストの総日数を取得
VAR vDayCount =
DISTINCTCOUNT('カレンダ'[日付])
// 月当たりの平均日数
VAR vDaysPerMonth =
DIVIDE(vDayCount, vMonthCount)
RETURN
// 在庫日数 = 月当たり日数 / 月回転率
DIVIDE(vDaysPerMonth, [M月回転率A])
)


区間の重なり判定と重複数量の算出方法の解説

在庫や出庫に対応する入庫情報(単価と数量)の取得の仕組みを解説します。
M出庫金額メジャーのVAR vResultの次のコードの動作を例として解説します。次のコードは日付と品名のフィルタのコードを省略しています。

VAR vResult =
SUMX(
vSoukoTrans,
VAR vSouko_Upper = [@出庫累計上限] // 今回までの出庫累計
VAR vSouko_Lower = [@出庫累計下限] // 前回までの出庫累計
RETURN
// 出庫累計範囲に重なる入庫ロットをFIFOで按分
SUMX(
FILTER( // 対象品名の入庫ロットに絞る
vNyukoBase,
// 出庫累計範囲と入庫累計範囲が重なるロットのみ
[@入庫累計上限] > vSouko_Lower && [@入庫累計下限] < vSouko_Upper
),
// 重複数量 = 両範囲の重なり部分
// MIN(当日出庫累計, @当日入庫累計) - MAX(前日出庫累計, @前日入庫累計)
VAR vOverlapQty =
MIN(vSouko_Upper, [@入庫累計上限]) - MAX(vSouko_Lower, [@入庫累計下限])
RETURN
// 重複数量 × 入庫単価
IF(vOverlapQty > 0, vOverlapQty * '入庫'[入庫単価], 0)
)
)

vSoukoTrans

日付 品名 出庫数 @出庫累計上限 @出庫累計下限
04/05 品A 40 40 0
04/05 品B 2 2 0
05/10 品B 6 2+6=8 2
05/20 品A 40 40+40=80 40

vNyukoBase

日付 品名 入庫単価 入庫数 @入庫累計上限 @入庫累計下限
04/01 品A 1 50 50 0
04/01 品B 10 5 5 0
04/15 品A 2 10 50+10=60 50
05/01 品B 20 5 5+5=10 5
05/05 品A 3 50 50+10+50=110 50+10=60

FIFO按分数量計算の基本的な考え方

入庫ロットと出庫範囲を数直線上の区間として考えます。

0────────────────▶ 累計数量
[入庫ロットの区間]
[ 出庫範囲 ]

2つの区間が重なる部分が、そのロットから出庫される数量です。

2つの区間 [A下限, A上限] と [B下限, B上限] が重なる条件は以下です。但し、下限は対象区間の直前の値とします。なので、実際の対象期間は下限+1日から始まります。以下の図では"|"で示す位置になります。

A上限 > B下限  かつ  A下限 < B上限

重なる部分の長さは、次の式で求まります。

MIN(A上限, B上限) - MAX(A下限, B下限)
A上限 > B下限
0 1 2 3 4 5 6 7 8 9 10 11 12
al--|───────-AU A上限
bl--|───────────-bu B下限 : ✓ MIN(AU,bu) - MAX(al,bl) = 5
BL--|───────-bu B下限 : ✓ MIN(AU,bu) - MAX(al,BL) = 5
BL--|───────-bu B下限 : ✓ MIN(AU,bu) - MAX(al,BL) = 2
BL--|───────-bu B下限 : ✓ MIN(AU,bu) - MAX(al,BL) = 1
BL--|───────-bu B下限 : ❌ A上限AU > B下限BL = False
A下限 < B上限
0 1 2 3 4 5 6 7 8 9 10 11 12
AL--|───────-au A下限
bl--|───────────-bu B上限 : ✓ MIN(au,bu) - MAX(AL,bl) = 5
bl--|───────-BU B上限 : ✓ MIN(au,BU) - MAX(AL,bl) = 5
bl--|───────-BU B上限 : ✓ MIN(au,BU) - MAX(AL,bl) = 2
bl--|───────-BU B上限 : ✓ MIN(au,BU) - MAX(AL,bl) = 1
bl--|───────-BU B上限 : ❌ A下限AL < B上限BU = False

これをFILTERとvOverlapQtyに当てはめると以下になります。

入庫ロット区間  : [@入庫累計下限, @入庫累計上限]
出庫範囲区間 : [vSouko_Lower, vSouko_Upper ]

重なり条件:

[@入庫累計上限] > vSouko_Lower (入庫上限が出庫下限より大きい)
[@入庫累計下限] < vSouko_Upper (入庫下限が出庫上限より小さい)

重複数量:

MIN(vSouko_Upper, [@入庫累計上限]) - MAX(vSouko_Lower, [@入庫累計下限])

例1で視覚化(品B 05/10出庫5)

vSouko_Lower=5, vSouko_Upper=10

0   1   2   3   4   5   6   7   8   9   10
al--|───────-AU 04/01ロット[0-5]
AL--|───────-AU 05/01ロット[5-10]
BL--|───────-BU 出庫範囲[5-10]

04/01ロット[0-5] vs 出庫範囲[5-10]

重なり条件:
[@入庫累計上限]5 > vSouko_Lower5 -> False ❌
→ 重なりなし(上限と下限がちょうど接しているだけ)

05/01ロット[5-10] vs 出庫範囲[5-10]

重なり条件:
[@入庫累計上限]10 > vSouko_Lower5 -> True ✓
[@入庫累計下限]5 < vSouko_Upper10 -> True ✓
→ 重複数量 = MIN(10,10) - MAX(5,5) = 10-5 = 5個 × ¥20 = ¥100

例2で視覚化(品B 05/10出庫6)

vSouko_Lower=2, vSouko_Upper=8

0   1   2   3   4   5   6   7   8   9   10
AL--|───────-AU 04/01ロット[0-5]
AL--|───────-AU 05/01ロット[5-10]
BL--|─────────-BU 出庫範囲[2-8]

04/01ロット[0-5] vs 出庫範囲[2-8]

重なり条件:
[@入庫累計上限]5 > vSouko_Lower2 -> True ✓
[@入庫累計下限]0 < vSouko_Upper8 -> True ✓
→ 重複数量 = MIN(8,5) - MAX(2,0) = 5-2 = 3個 × ¥10 = ¥30

05/01ロット[5-10] vs 出庫範囲[2-8]

重なり条件:
[@入庫累計上限]10 > vSouko_Lower2 -> True ✓
[@入庫累計下限]5 < vSouko_Upper8 -> True ✓
→ 重複数量 = MIN(8,10) - MAX(2,5) = 8-5 = 3個 × ¥20 = ¥60

合計出庫金額 = ¥30 + ¥60 = ¥90

例3で視覚化(品B 05/10出庫3)

vSouko_Lower=2, vSouko_Upper=5

0   1   2   3   4   5   6   7   8   9   10
AL--|───────-AU 04/01ロット[0-5]
AL--|───────-AU 05/01ロット[5-10]
BL--|───-| 出庫範囲[2-5]

04/01ロット[0-5] vs 出庫範囲[2-5]

重なり条件:
[@入庫累計上限]5 > vSouko_Lower2 -> True ✓
[@入庫累計下限]0 < vSouko_Upper5 -> True ✓
→ 重複数量 = MIN(5,5) - MAX(2,0) = 5-2 = 3個 × ¥10 = ¥30

05/01ロット[5-10] vs 出庫範囲[2-5]

重なり条件:
[@入庫累計上限]10 > vSouko_Lower2 -> True ✓
[@入庫累計下限]5 < vSouko_Upper5 -> False ❌
→ 重なりなし(出庫上限と入庫下限がちょうど接しているだけ)

合計出庫金額 = ¥30

まとめ:境界値の扱いがポイント

区間の下限は、対象区間の直前の値としています。
なので、区間の端がちょうど接している場合は重なりなしとして扱います。

条件 意味
上限 > 下限(厳密な不等号) 接しているだけでは重なりとみなさない
MIN - MAX > 0のときのみ計上 重複数量が正のときのみ金額計算
これにより同じ個体が二重にカウントされることを防いでいます。  

「区間の重なり判定と重複数量の算出方法の解説」は、ここまでです。


8) ピボットテーブルの設定

ピボットテーブルのフィールドで、必要な項目を設定します。
列 : '品名'[品名]、Σ値。
行 : 'カレンダ'[年月]、'カレンダ'[日付]。
Σ値 : ['M入庫単価]、'入庫'[入庫数]、'出庫'[出庫数]、[M在庫数]、[M入庫金額]、[M出庫金額]、[M在庫金額]、[回転数A]。

 

5. サンプル

「2. 入力」に示すように、'入庫'表に単価があるケースのサンプルファイルです。

ダウンロード - 260515zaikokin.xlsx

「6. 単価表が独立しているケース」です[Rev.1]。更に、作業用の中間メジャーを使用して、式を短くしています[Rev.2]。メジャーの式と動作説明は、サンプルファイルのコード中のコメントと図のシートを見てください。

ダウンロード - 260515zaikokinr2.xlsx

 

6. 単価表が独立しているケース

1) 入力表

テーブル名:入庫

日付 品名 入庫数
26/04/01 品A 50
26/04/01 品B 5
26/04/15 品A 10
26/05/01 品B 5
26/05/05 品A 50

テーブル名:単価

適用開始日 品名 単価
26/04/01 品A 1
26/04/01 品B 10
26/04/15 品A 2
26/05/01 品B 20
26/05/05 品A 3

その他は、「2. 入力」と同じです。

2) リレーション

'単価'[品名] *:1 '品名'[品名]

参考 : [適用開始日]は、それ以降の複数の[日付]に対応するので、リレーションを設定しません。メジャーで検索します。

3) メジャー

詳細は、「5.サンプル」の「260515zaikokinR2.xlsx」を見てください。

M入庫単価_ロット

'入庫'に適用するイテレータ関数から呼び出されるメジャーです。'入庫'の行コンテキストに対応する'単価'[単価]を返します。

M入庫単価_ロット:=VAR vDate = MAX('入庫'[日付]) // 呼出し元は'入庫'を処理するイテレータの中
// 入庫日・品名に対応する単価を単価テーブルから取得[Rev.1]
RETURN
CALCULATE(
MAX('単価'[単価]),
FILTER( // 入庫日以前の最大の適用開始日でフィルタ
ALL('単価'[適用開始日]),
'単価'[適用開始日] = CALCULATE(
MAX('単価'[適用開始日]), // 入庫日以前の最大の適用開始日を取得
FILTER(
ALL('単価'[適用開始日]),
'単価'[適用開始日] <= vDate // 入庫日以前でフィルタ
)
)
)
)

M入庫金額

M入庫金額:=SUMX(
'入庫',
[M入庫単価_ロット] * '入庫'[入庫数]
)

式の解説

SUMXやADDCOLUMNSなどのイテレータは、各行を処理するとき行コンテキストを生成します。その行コンテキスト上でメジャーを呼び出すと、メジャー内のCALCULATEが暗黙のコンテキスト遷移を起こし、行コンテキストをフィルタコンテキストに変換します。

'入庫'[日付]=26/04/01, '入庫'[品名]=品A の入庫金額の例

SUMX('入庫', [M入庫単価_ロット] * '入庫'[入庫数])
       ↓
各行で '入庫'[日付]=26/04/01, '入庫'[品名]=品A の行コンテキストが生成
       ↓
[M入庫単価_ロット]内のCALCULATEがコンテキスト遷移
       ↓
'入庫'[日付]=26/04/01, '入庫'[品名]=品A のフィルタコンテキストに変換
       ↓
VAR vDate = MAX('入庫'[日付]) → 26/04/01 が取得される
'品名'[品名]コンテキスト → 品A でフィルタされる

M出庫金額、M在庫金額、M在庫金額_指定日

式の中の「VAR vNyukoBase =」 に"@単価"を追加します。そして、'入庫'[入庫単価]を[@単価]に書き換えます。

// =============================================
// 入庫ロットテーブルの構築
// 各ロット(日付×品名×単価)に対して
// 品名内での入庫累計下限・上限を付与する
// =============================================
VAR vNyukoBase =
ADDCOLUMNS(
FILTER(ALL('入庫'), NOT(ISBLANK('入庫'[入庫数]))),
// 入庫累計上限
"@入庫累計上限",
VAR vThisHinmei = '入庫'[品名]
VAR vThisDate = '入庫'[日付]
RETURN
CALCULATE(
SUM('入庫'[入庫数]),
FILTER(ALL('入庫'),
'入庫'[品名] = vThisHinmei
&& '入庫'[日付] <= vThisDate
)
),
// 入庫累計下限
"@入庫累計下限",
VAR vThisHinmei = '入庫'[品名]
VAR vThisDate = '入庫'[日付]
RETURN
CALCULATE(
SUM('入庫'[入庫数]),
FILTER(ALL('入庫'),
'入庫'[品名] = vThisHinmei
&& '入庫'[日付] < vThisDate
)
) + 0,
// 入庫日・品名に対応する単価を単価テーブルから取得[Rev.1]
"@単価",
[M入庫単価_ロット]
)

// 重複数量 × 入庫単価
IF(vOverlapQty > 0, vOverlapQty * '入庫'[入庫単価], 0)

↓ 次のように変更します。

// 重複数量 × 入庫単価
IF(vOverlapQty > 0, vOverlapQty * [@単価],0) // '単価'[単価]から[Rev.1]


7. 関連動画

8. 余談

1) AI/LLMの利用

FIFOの重なり算出の方法は、Claudeが出したものです。
しかし、Claudeが生成したコードは、エラーや間違った値を算出するので、そのままでは使えません。でも、考え方の参考になります。そこで、回答されたコードを調べ、間違いを探し、正しい方向へ修正指示をすることで正解を得ます。
Claudeは、色々なケースを考慮し制限を掛ける条件式を入れる印象です。この条件が重複することがあり、コードが長く冗長になります。Claudeに不要なコードとその理由を知らせることで、コードを短くします。

一方、ChatGPT、Copilot、Geminiは、最適化で必要な条件まで外してしまい、間違ったコードになる印象です。LLMは、アルゴリズムで答えを出しているわけでは無く、似たような事例から解を導きだすので、間違いが多いようです。
Claudeは間違いを指摘すると、問題の切り分けをするためのデバッグコードを提示し、その結果を受けてアルゴリズムを修正していく印象です。

しかし、Claudeが優秀ということも無く、Geminiなどの方が適切なコードを得られることもあるので、気に入らない回答であれば、同じ質問を他のAI/LLMに入力するのが良いです。

AI/LLMの回答は、信頼性の検証が必要です。メジャーなどのコードや数式であれば、正解が分かっているサンプルデータでのテストと、コードレビューをします。テストでは見つからないバグが、コードレビューで見つかることがあります。本記事のコードも、Claudeのコードの動作を理解したうえで修正して掲載しています。但し、ごく簡単なテストしかしていません。

2) ワークシート式/VBA/Power Pivot

ワークシート式が得意な方は、それでも良いと思いますが、可読性が悪く、間違いを起こしやすいです。構造化参照を使い、+N()でコメントを入れれば、可読性が上がります。

VBAやPythonが得意な方は、それが良いと思います。VBAやPythonとDAXの可読性は、似たようなものだと思います。

Power Pivotの利点は、ピボットテーブルなので、コードや式を変更せずに項目の変更や選択フィルター処理ができる事です。但し、メジャーは、コンテキストやイテレータ関数の理解が必要です。また、Excel/Power BIにはメジャーのデバッグ機能が無いので、部分的なテストコードを書いてデバッグします。

このメジャーはPower BIでも動作するので、Power BIのドリルスルーや分析機能で回転率の悪化要因などの調査に活用することもできると思います。

| | コメント (0)

完成数から納期を満たす納期と注残を計算 Excel Power Pivot

1. 前置き

Yahoo!知恵袋に、品名と完成数から引当を開始できる納期と注残を算出する質問がありました。目的は、注文の消化進捗を確認するためです。
https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q13327903548

ワークシート式の回答が付いています。
単純なメジャーでも求めることができます。そのメジャーに多少追加をして、推移と遅延日数を出すようにしました。

なお、あまりテストをしていません。もし、使用される場合は、検証を行い自己責任でお願いいたします。メジャーの動作を理解できない方は、使用を避けてください。

2. 入力

テーブル名 : 注文

品名 注文数 納期
BASE 2 26/05/11
RING 2 26/05/12
BASE 2 26/05/15
RING 1 26/05/31
BASE 3 26/06/02
RING 1 26/06/07
BASE 4 26/06/09
RING 5 26/06/20

260512order1 

テーブル名:完成

日付 BASE RING
26/05/01 2  
26/05/15   1
26/05/15 2  
26/06/01   2
26/06/01 2  
26/06/05   1
26/06/10 2  

260512order2 

ここではクロス表にしましたが、注文と同じようなリストにできるなら処理が簡単になります。

テーブル名:カレンダ

日付 年月 四半期 半期 年度 年度. 四半期. 期 月 年
26/04/01 26/04 26/1Q 26/1H 26年度 2026 1Q 上期 4月 2026
26/04/02 26/04 26/1Q 26/1H 26年度 2026 1Q 上期 4月 2026
省略                  
28/03/31 28/03 27/4Q 27/2H 27年度 2027 4Q 下期 3月 2027

260512order3 

  1. Excelで項目行と空の1行の表を作成し、その範囲を全選択し[挿入]-[テーブル]でテーブル化し、[テーブルデザイン]でテーブル名を`カレンダ'に変更します。
  2. 日付セルをA2とした場合の数式を次に示します。この式を必要な日付範囲まで下にコピーします。
A3 = 最初の日付(シリアル値)
A4 = A3 + 1
B3 = TEXT([@日付], "yy/mm")
C3 = LET(x, EDATE([@日付], -3), (YEAR(x)-2000) & "/" & (INT((MONTH(x) -1) / 3) + 1) & "Q")
D3 = LET(x, EDATE([@日付], -3), (YEAR(x) - 2000) & "/" & (INT((MONTH(x) - 1) / 6) + 1) & "H")
E3 = YEAR(EDATE([@日付], -3)) - 2000 & "年度"
F3 = YEAR(EDATE([@日付], -3))
G3 = INT((MONTH(EDATE([@日付], -3)) - 1) / 3) + 1 & "Q"
H3 = IF(MONTH(EDATE([@日付], -3)) < 7, "上期", "下期")
I3 = TEXT([@日付], "m月")
J3 = YEAR([@日付])

3. 出力

最新

品名 合計 / 注文数 合計 / 完成数 M引当開始_完成 M引当完了_完成 M注残
BASE 11 8 26/06/09 26/06/02 3
RING 9 4 26/06/07 26/06/07 5

260512order4 

Yahoo!知恵袋の仕様は、引き当てが始まった納期ですが、引き当てが完了した日も表示しています。

推移

  BASE RING
年月 日付 合計/注文数 合計/完成数 M注文累計 M完成累計 M注残 M遅延日数_納期 M引当完了_納期 合計/注文数 合計/完成数 M注文累計 M完成累計 M注残 M遅延日数_納期 M引当完了_納期
26/05 26/05/01   2   2 -2                  
26/05/11 2   2 2 0 -10 26/05/01              
26/05/12     2 2 0     2   2   2 +20 26/06/01
26/05/15 2 2 4 4 0 ±0 26/05/15   1 2 1 1    
26/05/31     4 4 0     1   3 1 2 +1 26/06/01
26/05 集計 4 4 4 4 0     3 1 3 1 2    
26/06 26/06/01   2 4 6 -2       2 3 3 0    
26/06/02 3   7 6 1 +8 26/06/10     3 3 0    
26/06/05     7 6 1       1 3 4 -1    
26/06/07     7 6 1     1   4 4 0 -2 26/06/05
26/06/09 4   11 6 5 +2 未完了     4 4 0    
26/06/10   2 11 8 3         4 4 0    
26/06/20     11 8 3     5   9 4 5    
26/06 集計 7 4 11 8 3     6 3 9 4 5    
総計 11 8 11 8 3     9 4 9 4 5    

260512order5 
引当が完了していない納期の遅延日数は最後の完成日の1日後からの差分で求めています。プラスが遅延です。

4. 作成手順概要

1) '注文'をデータモデルに追加

  1. 注文表を全選択し、[挿入]-[テーブル]でテーブル化し、[テーブルデザイン]で名前を"注文"に変更する。
  2. テーブルを選択し、[データ]-[テーブルまたは範囲から]で、Power Queryエディタに取込む。
  3. "納期"の左側の型アイコンをクリックし、[日付]。 ⇒ 型が日付になる。
  4. [ホーム]-[閉じて読み込むv]-[閉じて次に読み込む]で、"接続の作成のみ"と"このモデルをデータモデルに追加する"を選択し[OK]。

2) '完成'をリスト形式にしデータモデルに追加

  1. 注文表を全選択し、[挿入]-[テーブル]でテーブル化し、[テーブルデザイン]で名前を"完成"に変更する。
  2. テーブルを選択し、[データ]-[テーブルまたは範囲から]で、Power Queryエディタに取込む。
  3. "日付"の左側の型アイコンをクリックし、[日付]。 ⇒ 型が日付になる。
  4. "日付"列を選択し、右クリックで[その他の列のピボット解除]。 ⇒ リスト形式になる。
  5. 数式バーの数式を次のように修正する。
= Table.UnpivotOtherColumns(変更された型, {"日付"}, "属性", "値")
↓
= Table.UnpivotOtherColumns(変更された型, {"日付"}, "品名", "完成数")
  1. [ホーム]-[閉じて読み込むv]-[閉じて次に読み込む]で、"接続の作成のみ"と"このモデルをデータモデルに追加する"を選択し[OK]。

3) 'カレンダ'をデータモデルに追加

  1. 'カレンダ'テーブルを選択し、[データ]-[テーブルまたは範囲から]で、Power Queryエディタに取込む。
  2. "日付"の左側の型アイコンをクリックし[日付]。 ⇒ 型が日付になる。
  3. [ホーム]-[閉じて読み込むv]-[閉じて次に読み込む]で、"接続の作成のみ"と"このモデルをデータモデルに追加する"を選択し[OK]。

4) '品名'を生成し、データモデルに追加

  1. 左の'注文'クエリを右クリックし、[参照]で、右の名前を"品名"に変更する。 ⇒ '品名'クエリができる。
  2. "品名"列を右クリックし、[他の列の削除]。
  3. "品名"列を右クリックし、[重複の削除]。
  4. [ホーム]-[閉じて読み込むv]-[閉じて次に読み込む]で、"接続の作成のみ"と"このモデルをデータモデルに追加する"を選択し[OK]。

5) リレーション

[データ]-[データモデル(緑アイコン)v]-[データモデルの管理]-[ホーム]-[ダイアグラムビュー]で、次のように項目をドラッグし、リレーションを取る。
'注文'[納期] *:1 'カレンダ'[日付]
'完成'[日付] *:1 'カレンダ'[日付]
'注文'[品名] *:1 '品名'[品名]
'完成'[品名] *:1 '品名'[品名]
260512order6

6) ピボットテーブルの作成

  1. [挿入]-[ピボットテーブルv]-[データモデルから]。
  2. [ピボットテーブル分析]-[ピボットテーブルv]-[オプション]-[レイアウトと書式]で、"更新時に列幅を自動調整する"のチェックを外す。
  3. [デザイン]-[レポートのレイアウト]-[表形式で表示]。

7) メジャー(DAX式)

ピボットテーブルのフィールドの'注文'または'完成'を右クリックし、[メジャーの追加]で次のメジャーを作成する。
以下、:=の左がメジャーの名前で、:=の右以降がメジャーの式です。

注文数の累計

M注文累計:=VAR ExistReqDay = NOT(ISBLANK(CALCULATE(MAX('注文'[納期]), ALL('品名'[品名]))))    // ピボットテーブルに他の品名も含め'注文'[納期]があるか
VAR ExistCompDay = NOT(ISBLANK(CALCULATE(MAX('完成'[日付]), ALL('品名'[品名])))) // ピボットテーブルに他の品名も含め'完成'[日付]があるか
// ↑推移表に表示する日を制限する。全ての日に累計を表示するならこのコードと次のIFの条件は不要。
RETURN
IF(
ExistReqDay || ExistCompDay, // '注文'[納期]または'完成'[日付]があるか
CALCULATE(
SUM('注文'[注文数]), // FILTERされた[注文数]を合計=注文数の累計
FILTER(
ALL('カレンダ'[日付]), // 累計を計算するためピボットテーブルの日付コンテキストフィルタを解除
'カレンダ'[日付] <= MAX('カレンダ'[日付]) // ピボットテーブルの日付以前の日付を抽出
)
)
)

完成数の累計

M完成累計:=VAR ExistReqDay = NOT(ISBLANK(CALCULATE(MAX('注文'[納期]), ALL('品名'[品名]))))    // ピボットテーブルに他の品名も含め'注文'[納期]があるか
VAR ExistCompDay = NOT(ISBLANK(CALCULATE(MAX('完成'[日付]), ALL('品名'[品名])))) // ピボットテーブルに他の品名も含め'完成'[日付]があるか
// ↑推移表に表示する日を制限する。全ての日に累計を表示するならこのコードと次のIFの条件は不要。
RETURN
IF(
ExistReqDay || ExistCompDay, // '注文'[納期]または'完成'[日付]があるか
CALCULATE(
SUM('完成'[完成数]), // FILTERされた[完成数]を合計=完成数の累計
FILTER(
ALL('カレンダ'[日付]), // 累計を計算するためピボットテーブルの日付コンテキストフィルタを解除
'カレンダ'[日付] <= MAX('カレンダ'[日付]) // ピボットテーブルの日付以前の日付を抽出
)
)
)

注残

= [M注文累計] - [M完成累計]

完成日に引当開始日を表示するメジャー

M引当開始_完成:=VAR c=[M完成累計]    // ピボットテーブルの日の完成累計
RETURN
CALCULATE(
MIN('注文'[納期]), // FILTER条件を満たす最初の日
FILTER(
ALL('カレンダ'[日付]), // 全カレンダを対象に次の条件を日付を抽出
// 前日注文累計 < ピボットテーブルの日の完成累計 && 当日注文数 >= ピボットテーブルの日の完成累計
[M注文累計] - '注文'[合計 / 注文数] < c && [M注文累計] >= c
)
)

完成日に引当完了日を表示するメジャー

M引当完了_完成:=VAR c=[M完成累計]    // ピボットテーブルの日の完成累計
RETURN
CALCULATE(
MAX('注文'[納期]), // FILTER条件を満たす最後の日
FILTER(
ALL('カレンダ'[日付]), // 全カレンダを対象に次の条件を日付を抽出
[M注文累計] <= c // 当日注文数 <= ピボットテーブルの日の完成累計
)
)

参考

以上、5つのメジャーのIFは、Yahoo!知恵袋の最新だけを表示するのであれば、不要です。なので、ワークシート式より分かりやすいと思います。IFは、ピボットテーブルに[日付]を表示して集計行を表示する場合に、データが無い日付を表示しないようにするために付けています。

納期行に表示する遅延日数

M遅延日数_納期:=VAR CurrentNoki = MAX('注文'[納期])    // ピボットテーブルの[納期]
// '完成'を[品名]でフィルタし[日付]でグループ化し[完成数]の合計表を作る
VAR CompListAll =
SUMMARIZE(
FILTER(ALL('完成'), '完成'[品名] = MAX('品名'[品名])),
'完成'[日付],
"@完成数", SUM('完成'[完成数])
)
// 上記表に[完成数]の累計列を追加する
VAR CompTableAll =
ADDCOLUMNS(
CompListAll,
"@完成累計",
VAR ThisDate = '完成'[日付]
RETURN
SUMX(FILTER(CompListAll, '完成'[日付] <= ThisDate), [@完成数])
)
// 全品名の完成最大日(品名フィルタなし)未完成の遅延日数はこれとの差分+1日とする
VAR MaxCompDate =
CALCULATE(MAX('完成'[日付]), ALL('完成'))
// 当該納期を消化した完成日を特定
VAR CompDate =
MINX(
FILTER(
CompTableAll,
[@完成累計] >= [M注文累計]
),
'完成'[日付]
)
// 遅延日数計算
VAR DelayDays =
IF(
NOT(ISBLANK(CompDate)),
// 完成済み:納期より前完成はマイナス、後完成はプラス
IF(
CurrentNoki > CompDate,
-DATEDIFF(CompDate, CurrentNoki, DAY),
DATEDIFF(CurrentNoki, CompDate, DAY)
),
// 未完成かつ納期が全体完成最大日以前
IF(
NOT(ISBLANK(MaxCompDate)) && CurrentNoki <= MaxCompDate,
DATEDIFF(CurrentNoki, MaxCompDate, DAY) + 1
)
)
RETURN
IF( // 単日の納期があるところのみに表示
NOT(ISBLANK(CurrentNoki)) && HASONEVALUE('カレンダ'[日付]),
DelayDays
)

260512order7

納期行に表示する引当完了日

M引当完了_納期:=VAR CurrentNoki = MAX('注文'[納期])    // ピボットテーブルの[納期]
// '完成'を[品名]でフィルタし[日付]でグループ化し[完成数]の合計表を作る
VAR CompListAll =
SUMMARIZE(
FILTER(ALL('完成'), '完成'[品名] = MAX('品名'[品名])),
'完成'[日付],
"@完成数", SUM('完成'[完成数])
)
// 上記表に[完成数]の累計列を追加する
VAR CompTableAll =
ADDCOLUMNS(
CompListAll,
"@完成累計",
VAR ThisDate = '完成'[日付]
RETURN
SUMX(FILTER(CompListAll, '完成'[日付] <= ThisDate), [@完成数])
)
// 全品名の完成最大日(品名フィルタなし)未完成の遅延日数はこれとの差分+1日とする
VAR MaxCompDate =
CALCULATE(MAX('完成'[日付]), ALL('完成'))
// 当該納期を消化した完成日を特定
VAR CompDate =
MINX(
FILTER(
CompTableAll,
[@完成累計] >= [M注文累計]
),
'完成'[日付]
)
RETURN
IF( // 単日の納期があるところのみに表示
NOT(ISBLANK(CurrentNoki)) && HASONEVALUE('カレンダ'[日付]),
IF(
NOT(ISBLANK(CompDate)),
// 完成済み:完成日を表示
FORMAT(CompDate, "yy/mm/dd"),
// 未完成かつ納期が全体完成最大日以前:未完了と表示
IF(
NOT(ISBLANK(MaxCompDate)) && CurrentNoki <= MaxCompDate,
"未完了"
)
)
)

260512order8

別のメジャーの作成編集画面

[Power Pivot]-[管理]のデータビューでメジャーを作成するデータを表示し、下部のメジャーを作成する任意のセルを選択し、数式バーにメジャーを入力、編集する。
この画面でのメジャーは、「メジャー名:=数式」という書式で記述する。
Excelの数式バーと同様に右のアイコンと境界線のドラッグで、表示行を拡大縮小することができる。

8) ピボットテーブルの設定

ピボットテーブルのフィールドで、次のように項目を設定します。

最新

列 : Σ値。
行 : '品名'[品名]。
Σ値 : '注文'[注文数]、'完成'[完成数]、[M引当開始]、[M引当完了]、[M注残]。

推移

列 : '品名'[品名]、Σ値。
行 : 'カレンダ'[年月]、'カレンダ'[日付]。
Σ値 : '注文'[注文数]、'完成'[完成数]、[M注文累計]、[M完成累計]、[M注残]、[M遅延日数_納期]、[M引当完了_納期]。

5. サンプル

ダウンロード - 260512order.xlsx

6. 関連動画

入庫単価が変動する物の在庫金額と回転率 Excel Power Pivot : 当ブログ : 2026/05/15
https://bigarrow.tea-nifty.com/blog/2026/05/post-c320e1.html

Excelで在庫管理/生産管理(メジャーで期間末在庫を算出) : 当ブログ : 2025/06/17
https://bigarrow.tea-nifty.com/blog/2025/06/post-3f6a32.html

(Excel/Power BI)メジャーが予期しない値を返す原因とDAXコンテキストの理解 : 当ブログ : 2026/03/18
https://bigarrow.tea-nifty.com/blog/2025/03/post-8025c7.html

7. 余談

M遅延日数_納期とM遅延日数_納期は、Claudeとのやり取りでコードを得ました。
コードを読み解いたところ冗長な部分がり、手作業でシンプルにしました。
Claudeの回答をシンプルにすることをChatGPT、Copilot、Geminiに依頼しましたが、正解が得られなくなる回答しか得られませんでした。
この程度のDAX式になると、AI(LLM)で正解を得るのは難しいです。

Claudeは間違っていると伝えると、式を分解してデバックコードを送ってきます。デバックコードのやり取りを繰り返して、式の何処が悪いのかを探り、式を修正してきます。これは、人がデバックをする方法と同じです。その結果、式が冗長になりますが、正解に辿り着きました。

一方、ChatGPT、Copilot、Geminiは、単純に回答とその結果のエラーのやり取りの繰り返しで、正解に辿り着きませんでした。

DAX式が長いですが、ワークシート式やVBAで行っても似たようなロジックになると思われます。その場合、ワークシート式が一番可読性が悪い気がします。
VBAとDAXの可読性は、同程度ではないかと思われます。

ExcelやPower BIには、DAXのデバッグ機能が付いていません。なので、長いDAX式は、部分的に切り出したDAX式を作成して確認します。Tabular Editorのようなものが使えると嬉しいのですが。また、ExcelのDAXが古い、ExcelのDAXの公式仕様が無い、という欠点があります。

IATF16949では不良のみならず納期遅延も重要な要求事項に含まれるので、KPIで必ず確認し再発防止をしていることが必要です。

| | コメント (0)

4IN1 Multiprotocol-TX-module Futaba / SFHSS PWM Pulse Inverted ?

1. 仕様

4IN1 Multi-moduleのFutaba/SFHSS Protocolは、送信機のOutputとは異なるPWMのパスス幅値を受信機に送信します。

2. PWMパルス幅の関係 (Unit:µs)

EdgeTX Channel Monitor Output SFHSS Receiver PWM Output
988 2020
1500 1520
2012 1020

3. 変換式 (Unit:µs)

EdgeTX = (-1024 / 1000) * SFHSS + (1024 / 1000 * 1020 + 2012)
SFHSS = (-1000 / 1024) * EdgeTX + (1000 / 1024 * 988 + 2020)

4. コード

次のコードによって変換されているようですが、当方はコードを追えていません。
https://github.com/pascallanger/DIY-Multiprotocol-TX-Module/tree/master/Multiprotocol

Futaba_cc2500.ino
Tx_def.h
Convert.ino

5. 変換表

SFHSS Receiverを接続したFlight ControllerやServoへのPWM値を一般的な1000-1500-2000µsにする場合は、EdgeTXのOutputのSubtrimとMIN/MAXとInvertedを調整して、次の表の値にします。
Unit:µs

SFHSS EdgeTX
2000 1008.48
1750 1264.48
1500 1520.48
1250 1776.48
1000 2032.48

6. 送信機のスティックとパルス幅の方向

EdgeTX/RadioMaster (TX16S)
Vertical Stick Lower: -100 [towards 1000µs]
Vertical Stick Upper: +100 [towards 2000µs]
Horizontal Stick Left: -100 [towards 1000µs]
Horizontal Stick Right: +100 [towards 2000µs]

Futaba (T10J)
Vertical Stick Lower: ↓100 [towards 2020µs]
Vertical Stick Upper: ↑100 [towards 1020µs]
Horizontal Stick Left: ←100 [towards 1020µs]
Horizontal Stick Right: →100 [towards 2020µs]

JR (X2610)
Vertical Stick Lower: H/U100 [towards 1000µs]
Vertical Stick Upper: L/D100 [towards 2000µs]
Horizontal Stick Left: L100 [towards 2000µs]
Horizontal Stick Right: R100 [towards 1000µs]

※ パスス幅の値は方向を示すだけであり、実際の値ではありません。

7. 余談

4IN1 Multiprotocol-TX-module のS-FHSSをこのような仕様にした理由が分かりません。

ジンバルとサーボの極性や中点の機種間の相違は、送信機の設定で調整し、PWMのパルス幅は絶対値で共通にした方が理解しやすいと思います。
例えば、使用するサーボやフライトコントローラーが双葉仕様であれば、TX Moduleでこのような変換をせずに、EdgeTXのOutputのSubtrimとMIN/MAXとInvertedで双葉仕様の1020-1520-2020に設定する方が分かりやすいです。
SFHSSの受信機で、双葉以外のサーボやフライトコントローラーを使用する機会も多いです。

例えば、BetaFlightを使用する場合、SFHSSでも1000-1500-2000にする必要があります。しかし現在の仕様では、送信機の出力を2032-1520-1008に設定します。これは、分かりにくい仕様です。

なので、RadioMasterの受信機のようにS-FHSS以外にも対応しているなら、S-FHSS以外を選んだほうが良いと思います。

8. 参考

| | コメント (0)

Excelで在庫管理を自動化|発注判断までできるテンプレ を Power Pivot にするとどうなる?

1. 前置き

次の動画のExcelブックに、Power Pivotを使用することを試して見ました。

結論から言いますと、このブックの出力は最後のダッシュボードを除きテーブルなので、ピボットテーブルを出力するPower Pivotでは実現できない機能がありました。なので、このテンプレートを使用するのであれば、この動画の通りワークシート式で行うのが良いです。

なので、以下の本記事は、VLOOKUP/XLOOKUP/SUMIFS等のワークシート式を、Power Pivotのリレーションとメジャーに置き換える参考程度に考えて下さい。

このテンプレートは今日現在の集計をするためのものですが、もし月別推移などの時間軸を集計したい場合は、Power Pivotが便利です。

検証をしていないので、もし利用される場合は、自己責任でお願いいたします。

2. 手順

1) 品目マスタ

動画の通りに作成します。
260508stockpm

2) 補助マスタ

動画の通りに作成します。

動画では、発注管理シートの入力規制リストに使用するリストが説明されていないで、ここでそのリストを追加します。
テーブル名:発注状況区分

発注状況
入荷済
発注済
未発注

選択肢の範囲に"発注状況"という名前を付けます。
260508stockam

3) 入出庫データ

動画の通りに作成します。
260508stockio

4) 今日の日付テーブル

DAXやPower Query Mで今日の日付を取得することもできますが、テストをするときに式を書き換えるのが不便なので、今日の日付をPower Pivotで取得するためのテーブルを作成し、これをメジャーで参照するようにします。テスト用に、サンプルは=TODAY()の代わりに直接日付を入力しています。
テーブル名:今日

日付
=TODAY()

5) 日付テーブル

本記事では不要ですが、Power Pivotで時間軸の集計を行うなら日付テーブルが必要です。
日付テーブルは他の集計でも利用できるので、独立したファイルに作成し、共有フォルダに保存しておくと良いです。

テーブル名:カレンダ

日付 年月 四半期 半期 年度 年度. 四半期. 期 月 年
26/04/01 26/04 26/1Q 26/1H 26年度 2026 1Q 上期 4月 2026
26/04/02 26/04 26/1Q 26/1H 26年度 2026 1Q 上期 4月 2026
26/04/03 26/04 26/1Q 26/1H 26年度 2026 1Q 上期 4月 2026

日付セルをA2とした場合の数式を次に示します。この式を必要な日付範囲まで下にコピーします。

A3=最初の日付(シリアル値)
A4=A3+1
B3=TEXT([@日付], "yy/mm")
C3=LET(x, EDATE([@日付], -3), (YEAR(x)-2000) & "/" & (INT((MONTH(x) -1) / 3) + 1) & "Q")
D3=LET(x, EDATE([@日付], -3), (YEAR(x) - 2000) & "/" & (INT((MONTH(x) - 1) / 6) + 1) & "H")
E3=YEAR(EDATE([@日付], -3)) - 2000 & "年度"
F3=YEAR(EDATE([@日付], -3))
G3=INT((MONTH(EDATE([@日付], -3)) - 1) / 3) + 1 & "Q"
H3=IF(MONTH(EDATE([@日付], -3)) < 7, "上期", "下期")
I3=TEXT([@日付], "m月")
J3=YEAR([@日付])
260508stockca

6) マスターテーブルをデータモデルに追加

  1. "品目マスタテーブル"の適当なセルを選択し、[データ]-[テーブルまたは範囲から]でPower Queryエディタに取込む。
  2. "日付"列項目の左端の型のアイコンをクリックし[日付]をクリックして、時刻が無い日付にする。その他、適切な型で無い項目があれば、同様に型を修正する。ABC123と表示されるany型が残らないようにする。
  3. [ホーム]-[閉じて読み込むv]-[閉じて次に読み込む]で"接続の作成のみ"と"このモデルをデータモデルに追加する"を選択し[OK]でExcelに戻る。

同様の方法で"場所マスタ"、"仕入先マスタ"、"担当者マスタ"、"今日"、"カレンダ"をデータモデルに追加する。

7) 入出庫データの整形とデータモデルの追加

入出庫データには、場所列が2列あります。これだと場所マスターとリレーションが取れないので、Power Pivotを使えません。そこで、Power Queryで場所列を1列に統合します。
入出庫データの数量は入も出も+符号です。この為、動画では、在庫の計算は入庫と出庫で符号を変えて計算しています。本記事では単純なSUM関数で在庫が計算できるように、出庫は数量をマイナス値に変換します。(これは、動画と同じように符号を変換しないで、メジャーで符号を変えても良いです。)

  1. "入出庫データ"の適当なセルを選択し、[データ]-[テーブルまたは範囲から]でPower Queryエディタに取込む。
  2. "日付"列項目の左端の型のアイコンをクリックし[日付]をクリックして、時刻が無い日付にする。その他、適切な型で無い項目があれば、同様に型を修正する。ABC123と表示されるany型が残らないようにする。
  3. "カテゴリ", "品目名", "移動元名称", "移動先名称", "区分"列を選択し、右クリックして[列の削除]。 ⇒ ファクトテーブルに不要な列を削除する。(削除しなくても良いが、ワークシート式で作られた値を参照するより元データを使用したほうが、ミスが少なくなりデータモデルも小さくなる。)
  4. [列の追加]-[カスタム列]で次のように入力し[OK]。 ⇒ 区分列が追加される。
    新しい列名 : 区分
    カスタム列の式 :
if [移動元コード] = null or [移動元コード] = "" then
if [移動先コード] = null or [移動先コード] = "" then

""

else

"入庫"

else

if [移動先コード] = null or [移動先コード] = "" then

"出庫"

else

"移動"

この式は、動画の区分のワークシート式をPower Query Mで記述したものです。

  1. "移動元コード"と"移動先コード"を選択し、右クリックして[列のピボット解除]。 ⇒ "移動元コード"と"移動先コード"が1列に並ぶ。

  2. [列の追加]-[カスタム列]で次のように入力し[OK]。 ⇒ 出庫がマイナスの列が追加される。
    新しい列名 : 数
    カスタム列の式 : = if [属性]="移動元コード" then -[数量] else [数量]

  3. "数量", "属性"列を選択し[列の削除]。 ⇒ 不要になった列が削除される。

  4. 不適切な型の列の型アイコンをクリックし、適切な型をクリックする。ABC123を残さないようにする。 ⇒ 適切な型が設定される。

  5. [ホーム]-[閉じて読み込むv]-[閉じて次に読み込む]で"接続の作成のみ"と"このモデルをデータモデルに追加する"を選択し[OK]でExcelに戻る。
    260508stockpq

8) リレーションの作成

  1. [データ]-[データモデル(緑アイコン)v]-[データモデルの管理]-[ホーム]-[ダイアグラムビュー]で次のように項目をドラッグする。
  • '入庫データ'[場所コード]を'場所マスタ'[場所コード]へ
  • '入庫データ'[品目コード]を'品目マスタ'[品目コード]へ
  • '品目マスタ'[担当者]を'担当者マスタ'[担当者]へ
  • '入庫データ'[日付]を'カレンダ'[日付]へ

'今日'[日付]は、リレーションしない。
260508stockre

9) 在庫一覧(品目別)

  1. [挿入]-[ピボットテーブルv]-[データモデルから]で新規ワークシートを選択し[OK]。
  2. シート名を"在庫一覧(品目別)"に変更。
  3. [ピボットテーブル分析]-[ピボットテーブルv]-[オプション]-[レイアウトと書式]タブで"更新時に列幅を自動調整する"のチェックを外し[OK]。
  4. [デザイン]-[レポートのレイアウトv]-[表形式で表示]。
  5. ピボットテーブルのフィールドの'入出庫データ'を右クリックし[メジャーの追加]で次のメジャーを追加する。
    :=の前がメジャーの名前で=からがメジャーの式です。
現在庫数:=SUM('入出庫データ'[数])

参考:もし動画のように出庫も+符号なら、次のようなイメージの式になります。本例では、Power Queryで符号処理しているので、この式は正常な値になりません。

現在庫数_参考:=CALCULATE(SUM('入出庫データ'[数]), '入出庫データ'[区分] = "入庫")
- CALCULATE(SUM('入出庫データ'[数]) ,'入出庫データ'[区分] = "出庫")
平均出庫数/日:= - CALCULATE(	// 出庫の数は-なので符号を反転
SUM('入出庫データ'[数]), // 30日間の出庫数の合計
'入出庫データ'[区分] = "出庫", // 出庫だけを抽出
DATESINPERIOD( // 開始日からの期間を抽出
'カレンダ'[日付], // 抽出元
MAX('今日'[日付]), // 開始日
-30, // 抽出期間 30日前
DAY // 期間の単位
)
) / 30 // 1日当たりの出庫数
在庫日数:=DIVIDE([現在庫数], [平均出庫数/日])
安全在庫日数:=IF(
// ピボットテーブルの品目コードが絞り込まれ、入出庫データの品目コードがある場合に計算
HASONEFILTER('品目マスタ'[品目コード]) && NOT(ISBLANK(MAX('入出庫データ'[品目コード]))),
MAX('品目マスタ'[安全在庫日数])
)
安全在庫数:=IF(
// ピボットテーブルの品目コードが絞り込まれ、計算元データがある場合に計算
HASONEFILTER('品目マスタ'[品目コード]) && NOT(ISBLANK([平均出庫数/日])) && NOT(ISBLANK([安全在庫日数])),
ROUNDUP([平均出庫数/日] * [安全在庫日数], 0) // 安全在庫数 = 1日当たりの出庫数 * 安全在庫日数
)
L/T日数:=IF(
// ピボットテーブルの品目コードが絞り込まれ、入出庫データの品目コードがある場合に計算
HASONEFILTER('品目マスタ'[品目コード]) && NOT(ISBLANK(MAX('入出庫データ'[品目コード]))),
MAX('品目マスタ'[L/T日数])
)
発注点:=IF(
// ピボットテーブルの品目コードが絞り込まれ、計算元データがある場合に計算
HASONEFILTER('品目マスタ'[品目コード]) && NOT(ISBLANK([平均出庫数/日])) && NOT(ISBLANK([平均出庫数/日])) && NOT(ISBLANK([L/T日数])),
ROUNDUP([平均出庫数/日] * [L/T日数] + [安全在庫数], 0) // 発注点 = 1日あたりの出庫数 * L/T日数 + 安全在庫数 = 必要な在庫数
)
推奨発注数:=IF(
NOT(ISBLANK([発注点])) && NOT(ISBLANK([現在庫数])), // 計算元データがあれば計算
IF(
[現在庫数] >= [発注点], // 在庫が十分なら0
0,
CEILING( // 不足数をロットの区切りで切り上げ
[発注点] - [現在庫数], // 不足数を計算
IFERROR(MAX('品目マスタ'[最低ロット]), 1) // 最低ロットデータが無ければ1個単位とする
)
)
)
在庫金額:=IF(
NOT(ISBLANK(MAX('品目マスタ'[単価]))), // 単価データがあれば計算
SUMX('入出庫データ', [現在庫数] * RELATED('品目マスタ'[単価])), // 金額 = 在庫数 * 単価
"単価未登録" // 単価登録が無い場合"単価未登録"を表示
)

RELATEDの要否

  • MAXの中はフィルターコンテキストにより品目コードで絞られた単価が参照できるので、RELATEDが不要です。
  • SUMXの中は、計算対象の第1引数'入出庫データ'に単価が無いので、RELATEDで単価を参照しています。

M担当者:=IF(
    // ピボットテーブルの品目コードが絞り込まれ、入出庫データの品目コードがある場合に表示
    HASONEFILTER('品目マスタ'[品目コード]) && NOT(ISBLANK(MAX('入出庫データ'[品目コード]))),
    MAX('品目マスタ'[担当者])
)

メジャーの名前

メジャーの名前はデータモデルの項目と同じ名前を付けることはできません。ここでは"担当者"が重複するので"M担当者"としています。

発注要否:=IF(
// ピボットテーブルの品目コードが絞り込まれ、入出庫データの品目コードがある場合に表示
HASONEFILTER('品目マスタ'[品目コード]) && NOT(ISBLANK(MAX('入出庫データ'[品目コード]))),
IF(
ISNUMBER([推奨発注数]) && [推奨発注数]>0, // 推奨発注数が0より大きい数字が要発注
"要発注",
"正常"
)
)

参考

動画では発注要否をスライサーに設定していますが、本例では発注要否がΣ値なのでスライサーに設定できません。
動画では手動で重複の無い品目をテーブルに入力しているので、スライサーにできます。
本例はピボットテーブルで自動的に重複の無い品目にしているので、発注要否がΣ値になりスライサーにできません。品目を発注要否でフィルターすることはできます。Power Queryでグループ化すれば、スライサーにできますが、そうするとデータモデルの分解能が下がり、時間軸集計ができなくなります。

式の内容

  • 動画のワークシート式とほぼ同じです。
  • メジャーはピボットテーブルのフィルターコンテキストが掛かるので、そのフィルタ関数は不要です。
  • ここではピボットテーブルで総計も出せるように、総計の場合は表示しないIF条件を追加しています。総計を表示しないのであれば、この条件は不要です。
  1. ピボットテーブルのフィールドの設定
    次の位置に項目をドラッグして設定します。
    列 : Σ値
    行 : '品目マスタ'[品目コード]、'品目マスタ'[品目名]、'品目マスタ'[カテゴリ]
    Σ値 : [現在庫数]、[平均出庫数/日]、[在庫日数]、[安全在庫日数]、[安全在庫数]、[L/T日数]、[発注点]、[推奨発注数]、[在庫金額]、[M担当者]

  2. スライサー
    次の項目を右クリックしてスライサーに設定します。
    '品目マスター'[品目名]、'品目マスター'[カテゴリ]、'担当者マスタ'[担当者]
    260508stocpa

10) 在庫一覧(場所別)

Power Pivotでは同じデータモデル、同じメジャーで異なるピボットテーブルを複数作ることができます。

  1. '在庫一覧(品目別)'シートをコピーし、シート名を"在庫一覧(場所別)"に変更。

  2. ピボットテーブルのフィールドの設定
    次のように項目を設定します。

列 : Σ値
行 : '品目マスタ'[品目コード]、'品目マスタ'[品目名]、'品目マスタ'[カテゴリ]、'場所マスタ'[場所コード]、'場所マスタ'[場所名]
Σ値 : [現在庫数]、[平均出庫数/日]、[在庫日数]、[安全在庫日数]、[安全在庫数]、[L/T日数]、[発注点]、[推奨発注数]、[在庫金額]、[M担当者]

  1. スライサー
    次の項目を右クリックしてスライサーに設定します。
    '場所マスター'[場所名]、'品目マスター'[品目名]、'品目マスター'[カテゴリ]、'担当者マスタ'[担当者]
    260508stockpl

11) 発注管理

これはファクトテーブルなので、Power Pivotでは作成できません。動画の通りに作成します。
260508stockor

12) ダッシュボードのピボットテーブル

  1. '在庫一覧(品目別)'シートをコピーし、シート名を"ダッシュボード"に変更。

  2. ピボットテーブルのフィールドの設定
    次のように項目を設定します。

列 : Σ値
行 : '品目マスタ'[品目コード]、'品目マスタ'[品目名]、'品目マスタ'[カテゴリ]
Σ値 : [現在庫数]、[平均出庫数/日]、[在庫日数]、[安全在庫日数]、[安全在庫数]、[L/T日数]、[在庫金額]

  1. スライサー
    次の項目を右クリックしてスライサーに設定します。
    '品目マスター'[カテゴリ]、'場所マスター'[場所名]、'担当者マスタ'[担当者]
    260508stockda

13) ダッシュボードのテキストボックス、グラフ用のピボットテーブル

  1. 'pivot'シートを追加

全体集計

  1. [挿入]-[ピボットテーブルv]-[データモデルから]でピボットテーブルを作成する'pivot'シート上のセルを選択し[OK]。
  2. ピボットテーブルのフィールドの設定
    次のように項目を設定します。
    列 : Σ値
    行 : なし
    Σ値 : [現在庫数]、[在庫金額]

同様に"在庫日数"、"場所別ピボット"、"カテゴリ別ピボット"、"トップテンピボット"を作成します。

在庫日数

フィルター : '品目マスタ'[品目コード]
列 : Σ値
行 : なし
Σ値 : [現在庫数]、[平均出庫数/日]、[在庫金額]

場所別ピボット

列 : なし
行 : '場所マスター'[場所名]
Σ値 : [在庫金額]

カテゴリ別ピボット

フィルター : '品目マスタ'[品目コード]
列 : Σ値
行 : '品目マスタ'[カテゴリ]
Σ値 : [現在庫数]、[在庫日数]

トップテンピボット

フィルター : '品目マスタ'[品目コード]
列 : なし
行 : '品目マスタ'[品目名]
Σ値 : [在庫金額]

品目名の▼-[値フィルタ]-[トップテン]で[在庫金額]の[上位]"10"[項目]-[OK]。
260508stockpi

  1. スライサーの接続
    ダッシュボードのスライサーを選択し、[スライサー]-[レポートの接続]で'pivot'シートのピボットテーブルをチェックし[OK]。

  2. テキストボックスの配置
    ダッシュボード上にテキストボックスを追加し、数式バーに=を入力し、参照する'pivot'シートのセルアドレスを入力する。この時、GETPIVOTDATAが入る場合は、ピボットテーブル外の近隣のセルを選択してEnterを押した後、セルアドレスを手修正する。

  3. ピボットグラフ
    'pivot'シートのピボットテーブルを選択し、[ピボットテーブル分析]-[ピボットテーブル]でグラフを作成し、それをCtrl+xで切り取り、'ダッシュボード'シートにCtrl+cで貼付け、表示を整える。

3. サンプル

ダウンロード - 260508stock.xlsx

4. 関連記事

5. 余談

動画のテンプレートは、過去の在庫状況から需要を予測し、発注をする仕組みになっています。
実務でこのような発注をするのは、事務用品や保守部品などのオフライン業務に限られると思います。
ライン業務は、別の需要予測、受注システム(注残管理含む)、手配システムにより発注管理をするのが一般的だと思います。
また、品目や場所を手入力するなら、コードでは無く、名称にしないと分かりにくいなどの問題もあります。
なので、Excelの使い方の参考程度にするのが良いでしょう。その際は、Power Pivotも選択肢に入れると、数式を削減できるので良いです。

SharePointのListとPower AppsとPower BIという手も考えられますが、SharePointはコンテンツ数の制約が厳しいので、部門の事務用消耗品など小規模なものに限られます。SharePointのListより、Excel Power Query / Power Pivotの方がレコード数の制約が少ないです。ライン業務なら専用システムかデータベースソフトを使うべきでしょう。

| | コメント (0)

表の外に共通項目があり、縦横に複数の項目を持つクロス表をExcel Power Queryでリストに整形

1. 前置き

Yahoo!知恵袋に、複数のシートにある表の外に共通項目があり、縦横に複数の項目を持つクロス表を1つに纏める質問がありました。
https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q13327723258

マクロを希望されているので、VBAの回答が付いています。この表は、VBAが良いと思うので、Yahoo!知恵袋の回答を見てください。
VBAのコードが理解できないのであれば、Excel Power Queryも考えられます。

Power Queryのクリック操作は、Power Query Mのコードを直接書くよりも複雑になります。でも、VBAのコードを理解できないなら、Power Query Mのコードも理解できないと思うので、クリック操作での整形を考えてみました。

Power Queryエディタで処理の過程を目で確認しやすい点は、Power Queryのクリック操作の利点です。

2. 入力

Power Queryで1つのシートを読み込むと次のようになります。

Column1 Column2 Column3 Column4 Column5 Column6 Column7 Column8 Column9
              《今野物産 ピッキングリスト》  
受信日:2026/04/28   部門:11            
出荷日:2026/04/30   出庫元:0006781 佐川営業所            
納品日:2026/05/01   売上部署:0006781 佐川営       検索対象:手入力情報 + EDI情報(本発注)    
                 
品名/規格   品目コード 単位 相手先店コード   160016 160014 160012
相手先品目コード     入数 自社店コード   S001 S002 S003
        店名 合計 東京店 大阪店 北海道店
りんご/生     P 数量 100 100    
B001   12345 10 ケース数 10 10    
ぶどう/生     P 数量 70   40 30
B002   11111 10 ケース数 7   4 3

特徴

  1. 表全体に共通する部門、受信日、出荷日、納品日の情報が表の外にある。
  2. 行項目に品名/規格、相手先品目コード、品目コード、単位、入数があり、それらが2段の同一行の上下や下段のみにある。
  3. 列項目が相手先店コートと自社店コードと店名に分かれており、3行表示になっている。

260507pq1

3. 出力

Yahoo!知恵袋の要求は、同じ形の表へまとめることですが、ここでは再利用しやすいデータベース形式に整形し、それをピボットテーブルでクロス表にすることを想定します。整形後のデータベース形式は次の通りです。

品名/規格 相手先品目コード 品目コード 相手先店コード 自社店コード 店名 数量 ケース数 部門 受信日 出荷日 納品日
りんご/生 B001 12345 160016 S001 東京店 100 10 11 2026/04/28 火 2026/04/30 木 2026/05/01 金
ぶどう/生 B002 11111 160012 S003 北海道店 30 3 11 2026/04/28 火 2026/04/30 木 2026/05/01 金
ぶどう/生 B002 11111 160014 S002 大阪店 40 4 11 2026/04/28 火 2026/04/30 木 2026/05/01 金

本記事では、単位と入数を省いていますが、品名/規格と相手先品目コードと同じ方法で追加することもできます。
同様に出庫元、売上部署も省いていますが、部門と同じ方法で追加することができます。

260507pq2

ピボットテーブルは次の通りです。

  S001 S002 S003 総計
納品日 品名/規格 相手先品目コード 品目コード 値 東京店 大阪店 北海道店  
2026/5/1 りんご/生 B001 12345 合計 / 数量 100     100
合計 / ケース数 10     10
ぶどう/生 B002 11111 合計 / 数量   40 30 70
合計 / ケース数   4 3 7
2026/5/1 合計 / 数量 100 40 30 170
2026/5/1 合計 / ケース数 10 4 3 17
全体の 合計 / 数量 100 40 30 170
全体の 合計 / ケース数 10 4 3 17

260507pq3

4. 表の整形の考え方

  1. 表全体に共通する部門、受信日、出荷日、納品日の情報が表の外にある。
    整形の最終段階でカスタム列の追加をして、絶対アドレスの参照で値をリストに追加します。
    ソースのステップで取得するセルを右クリックして、[新しいクエリとして追加]をすると新しいクエリができるので、生成された式(絶対アドレスの数式)をカスタム列の式にコピーします。
    (キーになる値を条件列で判別して必要な値を取得し、フィルで埋めて、更に条件列で項目名と値を取得する方法もあります。この方法は、行が変わっても取得できますが、ステップ数が多くなります。)

  2. 行項目に品名/規格、相手先品目コード、品目コード、単位、入数があり、それらが1レコード2段の同一列の上下や下段のみにある。
    列の追加の条件列で、行の位置を識別できる固有値がある列を条件にして行を特定し、必要な値のみを取得します。次にフィルで間のnullを埋めることで、1レコードに共通の値を埋め込みます。次に2段の行を区別できる列をピボットすることで、1レコード2段を1行のレコードに変換します。

  3. 列項目が相手先店コートと自社店コードと店名に分かれており、3行表示になっている。
    転置をすると複数行が複数列になるので、複数列を結合し再度転置をすると3行が1行になります。これをヘッダーにしてピボット解除すると、結合した値が1列になるので、分割して戻します。
    この処理は重いので、Power Query Mのコードが分かるなら、転置をしないコードを直接書いた方が良いでしょう。

260507pq4

5. 手順概要

1ファイル複数シートでも、複数ファイル複数シートでも、シートの整形手順は同じです。なので、本記事では、シートの整形手順を説明します。
シートの整形クエリを作成したら、それをヘルパークエリにして、シートの整形関数を生成し、集計クエリで使います。

1) 前処理

1ファイル複数シート

次の記事の「4. 手順」「3) パラメータクエリの作成」まで行ってください。

  • 1レコードが1シート複数行複数列から成る「複数シート」を1つのデータベース形式にする : 2025/09/26
    https://bigarrow.tea-nifty.com/blog/2025/09/post-4beb66.html 「4) サンプルシートの変換クエリを作成する」から「7) 関数の作成」の部分は、この後の本記事の手順になります。

複数ファイル複数シート

次の記事の「4. 手順例」「§2 パラメータの作成」まで行ってください。

2) シートの整形ヘルパークエリの作成

  1. [ホーム]-[新しいソースv]-[その他のソース>]-[空のクエリ]で右の名前を"サンプルシートの変換"に変更。

  2. ソース : 数式バーに次の様に入力し、✓をクリックする。 ⇒ サンプルシートが表示される。
    = パラメータ1

  3. 右の"変更された型"の左の×をクリックして削除。昇格されたヘッダーがあればそれも削除して、ソースだけにする。

  4. "部門:xx"のセルを右クリックし、[新しいクエリとして追加]する。同様に"受信日"、"出荷日"、"納品日"のセルのクエリも追加する。

参考

各クエリは、「= ソース{行番号}[Column列番号]」という行番号と列名でセルの値を取得するコードになる。これを後のカスタム列の数式にコピーして、各セルの値の列を作成する。

Column1 Column2 Column3 Column4 Column5 Column6 Column7 Column8 Column9
null null null null null null null 《今野物産 ピッキングリスト》 null
受信日:2026/04/28 null 部門:11 null null null null null null
出荷日:2026/04/30 null 出庫元:0006781 佐川営業所 null null null null null null
納品日:2026/05/01 null 売上部署:0006781 佐川営 null null null 検索対象:手入力情報 + EDI情報(本発注) null null
null null null null null null null null null
品名/規格 null 品目コード 単位 相手先店コード null 160016 160014 160012
相手先品目コード null null 入数 自社店コード null S001 S002 S003
null null null null 店名 合計 東京店 大阪店 北海道店
りんご/生 null null P 数量 100 100 null null
B001 null 12345 10 ケース数 10 10 null null
ぶどう/生 null null P 数量 70 null 40 30
B002 null 11111 10 ケース数 7 null 4 3
  1. 削除された最初の行 : [ホーム]-[行の削除v]-[上位の行の削除]で5を入力し[OK]。 ⇒ 列項目が2行なので、それより上の行は不要なので削除する。(実際はここに行項目の項目名があるが、これを項目として取得するのは面倒なので、後で手入力する。)

  2. 削除された列 : "Column2", "Column4", "Column6"列を選択し、右クリックで[列の削除]。 ⇒ データが無い列を削除する。合計はピボットテーブルで求めるので削除する。(本記事では削除していますが、単位/入数列を残すこともできます。)

Column1 Column3 Column5 Column7 Column8 Column9
品名/規格 品目コード 相手先店コード 160016 160014 160012
相手先品目コード null 自社店コード S001 S002 S003
null null 店名 東京店 大阪店 北海道店
りんご/生 null 数量 100 null null
B001 12345 ケース数 10 null null
ぶどう/生 null 数量 null 40 30
B002 11111 ケース数 null 4 3
  1. 追加された条件列 : [列の追加]-[条件列]で次のように入力し[OK]。 ⇒ "品名/規格"の項目名と値だけを持つ列が生成される。
    新しい列名 : カスタム
    列名 : 演算子 : 値 : 出力
    [Column5] : 指定の値に等しい : 相手先店コード : [Column1[]
    [Column5] : 指定の値に等しい : 数量 : [Column1]
    それ以外の場合 : null

  2. 追加された条件列1 : [列の追加]-[条件列]で次のように入力し[OK]。 ⇒ "相手先品目コード"の項目名と値だけを持つ列が生成される。
    新しい列名 : カスタム.2
    列名 : 演算子 : 値 : 出力
    [Column5] : 指定の値に等しい : 店名 : 相手先品目コード
    [Column5] : 指定の値に等しい : ケース数 : [Column1]
    それ以外の場合 : null

  3. 追加された条件列2 : [列の追加]-[条件列]で次のように入力し[OK]。 ⇒ "相手先品目コード"の項目名と値だけを持つ列が生成される。
    新しい列名 : カスタム.1
    列名 : 演算子 : 値 : 出力
    [Column5] : 指定の値に等しい : 店名 : 品目コード
    [Column5] : 指定の値に等しい : ケース数 : [Column3]
    それ以外の場合 : null

Column1 Column3 Column5 Column7 Column8 Column9 カスタム カスタム.1 カスタム.2
品名/規格 品目コード 相手先店コード 160016 160014 160012 品名/規格 null null
相手先品目コード null 自社店コード S001 S002 S003 null null null
null null 店名 東京店 大阪店 北海道店 null 相手先品目コード 品目コード
りんご/生 null 数量 100 null null りんご/生 null null
B001 12345 ケース数 10 null null null B001 12345
ぶどう/生 null 数量 null 40 30 ぶどう/生 null null
B002 11111 ケース数 null 4 3 null B002 11111
  1. 削除された列1 : "Column1", "Column3"列を選択し、右クリックで[列の削除]。 ⇒ 列が追加されて不要になった列が削除される。

  2. 下方向へコピー済み : "カスタム"列を選択し、[変換]-[フィルv]-[下へ]。 ⇒ "品名/規格"値のnullが埋まる。

  3. 上方向へコピー済み : "カスタム.1"と"カスタム.2"列を選択し、[変換]-[フィルv]-[上へ]。 ⇒ "最手先品目コード"と"品目コード"値のnullが埋まる。

Column5 Column7 Column8 Column9 カスタム カスタム.1 カスタム.2
相手先店コード 160016 160014 160012 品名/規格 相手先品目コード 品目コード
自社店コード S001 S002 S003 品名/規格 相手先品目コード 品目コード
店名 東京店 大阪店 北海道店 品名/規格 相手先品目コード 品目コード
数量 100 null null りんご/生 B001 12345
ケース数 10 null null りんご/生 B001 12345
数量 null 40 30 ぶどう/生 B002 11111
ケース数 null 4 3 ぶどう/生 B002 11111
  1. 転置されたテーブル : [変換]-[転置]。 ⇒3行の列項目を1行にするため、行と列を入れ替える。
Column1 Column2 Column3 Column4 Column5 Column6 Column7
相手先店コード 自社店コード 店名 数量 ケース数 数量 ケース数
160016 S001 東京店 100 10 null null
160014 S002 大阪店 null null 40 4
160012 S003 北海道店 null null 30 3
品名/規格 品名/規格 品名/規格 りんご/生 りんご/生 ぶどう/生 ぶどう/生
相手先品目コード 相手先品目コード 相手先品目コード B001 B001 B002 B002
品目コード 品目コード 品目コード 12345 12345 11111 11111
  1. 結合された列 : "Column1"と"Column2"列を選択し、右クリックで[列のマージ]で区切り記号に[セミコロン]で[OK]。 ⇒ 列項目の複数行を転置した複数列が1列になる。(Power Queryは、複数行のヘッダーを持つことはできないので、ここで1つに結合する。)
結合済み Column4 Column5 Column6 Column7
相手先店コード;自社店コード;店名 数量 ケース数 数量 ケース数
>160016;S001;東京店 100 10 null null
>160014;S002;大阪店 null null 40 4
>160012;S003;北海道店 null null 30 3
品名/規格;品名/規格;品名/規格 りんご/生 りんご/生 ぶどう/生 ぶどう/生
相手先品目コード;相手先品目コード;相手先品目コード B001 B001 B002 B002
品目コード;品目コード;品目コード 12345 12345 11111 11111
  1. 転置されたテーブル1 : [変換]-[転置]。 ⇒ 元の行列配置に戻る。これで、列項目が1行になる。これで、後のステップでヘッダー化とピボット解除ができる。
Column1 Column2 Column3 Column4 Column5 Column6 Column7
相手先店コード;自社店コード;店名 160016;S001;東京店 160014;S002;大阪店 160012;S003;北海道店 品名/規格;品名/規格;品名/規格 相手先品目コード;相手先品目コード;相手先品目コード 品目コード;品目コード;品目コード
数量 100 null null りんご/生 B001 12345
ケース数 10 null null りんご/生 B001 12345
数量 null 40 30 ぶどう/生 B002 11111
ケース数 null 4   ぶどう/生 B002 11111
  1. 昇格されたヘッダー数 : [変換]-[1行目をヘッダーとして使用v]-[1行目をヘッダーとして使用]。 ⇒ 1行に結合した項目がヘッダーになる。

  2. 追加されたインデックス : [列の追加]-[インデックス列v]-[0から]。 ⇒ 元表に同じ品目が複数行ある場合の識別用に番号を付ける。

相手先店コード;自社店コード;店名 160016;S001;東京店 160014;S002;大阪店 160012;S003;北海道店 品名/規格;品名/規格;品名/規格 相手先品目コード;相手先品目コード;相手先品目コード 品目コード;品目コード;品目コード インデックス
数量 100 null null りんご/生 B001 12345 0
ケース数 10 null null りんご/生 B001 12345 1
数量 null 40 30 ぶどう/生   11111 2
ケース数 null 4 3 ぶどう/生 B002 11111 3
  1. 整数除算済みの列 : "インデックス"列を選択し、[変換]-[標準v]-[整数除算]。 ⇒ 2行のペアが同じ行項目のレコードであることを識別するためと、2行毎に同じ番号を付ける。(これにより、後の"列のピボット"の"集計しない"でのエラーを防止する。エラーが無くなるのは、このインデックスにより重複が無くなり、集計が必要なレコードが無くなるためである。)
相手先店コード;自社店コード;店名 160016;S001;東京店 160014;S002;大阪店 160012;S003;北海道店 品名/規格;品名/規格;品名/規格 相手先品目コード;相手先品目コード;相手先品目コード 品目コード;品目コード;品目コード インデックス
数量 100 null null りんご/生 B001 12345 0
ケース数 10 null null りんご/生 B001 12345 0
数量 null 40 30 ぶどう/生 B002 11111 1
ケース数 null 4 3 ぶどう/生 B002 11111 1
  1. ピボット解除された他の列 :"相手先店コード;自社店コード;店名", "品名/規格;品名/規格;品名/規格", "相手先品目コード;相手先品目コード;相手先品目コード", "品目コード;品目コード;品目コード", "インデックス"列を選択し、右クリックで[その他の列のピボット解除]。 ⇒ 行項目列を選択し、その他の列項目が縦に並ぶリスト形式になる。
相手先店コード;自社店コード;店名 品名/規格;品名/規格;品名/規格 相手先品目コード;相手先品目コード;相手先品目コード 品目コード;品目コード;品目コード インデックス 属性 値
数量 りんご/生 B001 12345 0 160016;S001;東京店 100
ケース数 りんご/生 B001 12345 0 160016;S001;東京店 10
数量 ぶどう/生 B002 11111 1 160014;S002;大阪店 40
数量 ぶどう/生 B002 11111 1 160012;S003;北海道店 30
ケース数 ぶどう/生 B002 11111 1 160014;S002;大阪店 4
ケース数 ぶどう/生 B002 11111 1 160012;S003;北海道店 3
  1. ピボットされた列 : "自社店コード;店名"列を選択し、[変換]-[列のピボット]で値列に"値"を選択し、詳細設定オプションで[集計しない]-[OK]。 ⇒ 2行表示の"数量"と"ケース数"が2列表示になる。
品名/規格;品名/規格;品名/規格 相手先品目コード;相手先品目コード;相手先品目コード 品目コード;品目コード;品目コード インデックス 属性 数量 ケース数
ぶどう/生 B002 11111 1 160012;S003;北海道店 30 3
ぶどう/生 B002 11111 1 160014;S002;大阪店 40 4
りんご/生 B001 12345 0 160016;S001;東京店 100 10
  1. 並べ替えられた行 : "インデックス"列の▼-[昇順で並び替え] ⇒ ピボットで行の順番が乱れるので、インデックスでソートして元の順番に戻す。

  2. 削除された列3 : "インデックス"列を選択し、右クリックして[削除]。 ⇒ 不要になったインデックス列を削除。

  3. 区切り記号による列の分割 : "属性"列を選択し、右クリックして[列の分割>]-[区切り記号による分割]で[セミコロン]を選択し、引用符文字を[なし]にして[OK]。 ⇒ 元々2行になっていた列項目が分離される。
    数式バーのコードの"属性.1", "属性.2"を"自社店コード", "店名"に書き換える。

= Table.SplitColumn(削除された列3, "属性", Splitter.SplitTextByDelimiter(";", QuoteStyle.None), {"属性.1", "属性.2"})
↓
= Table.SplitColumn(削除された列3, "属性", Splitter.SplitTextByDelimiter(";", QuoteStyle.None), {"自社店コード", "店名"})
品名/規格;品名/規格;品名/規格 相手先品目コード;相手先品目コード;相手先品目コード 品目コード;品目コード;品目コード 相手先店コード 自社店コード 店名 数量 ケース数
りんご/生 B001 12345 160016 S001 東京店 100 10
ぶどう/生 B002 11111 160012 S003 北海道店 30 3
ぶどう/生 B002 11111 160014 S002 大阪店 40 4
  1. 名前が変更された列 : "品名/規格;品名/規格;品名/規格","相手先品目コード;相手先品目コード;相手先品目コード","品目コード;品目コード;品目コード"の列名をWクリックして"品名/規格","相手先品目コード","品目コード"に変更する。

※ 元データから項目名を自動取得することもできるが、クリック操作ではできず、Power Query Mのコードを直接記述する必要があるので、項目名を手修正する方式にしている。

  1. 追加されたカスタム : [列の追加]-[カスタム列の追加]で次のように入力し[OK]。
    新しい列名:部門
    カスタム列の式 : = ソース{1}[Column3] // ソースのステップで[新しいクエリとして追加]したクエリの数式バーの式をコピーして貼り付ける。Column3の2行目。
品名/規格 相手先品目コード 品目コード 相手先店コード 自社店コード 店名 数量 ケース数 部門
りんご/生 B001 12345 160016 S001 東京店 100 10 部門:11
ぶどう/生 B002 11111 160012 S003 北海道店 30 3 部門:11
ぶどう/生 B002 11111 160014 S002 大阪店 40 4 部門:11
  1. 追加されたカスタム1 : [列の追加]-[カスタム列の追加]で次のように入力し[OK]。
    新しい列名 : 受信日
    カスタム列の式 : = ソース{1}[Column1] // ソースのステップで[新しいクエリとして追加]したクエリの数式バーの式をコピーして貼り付ける。Column1の2行目。

  2. 追加されたカスタム2 : [列の追加]-[カスタム列の追加]で次のように入力し[OK]。
    新しい列名 : 出荷日
    カスタム列の式 : = ソース{2}[Column1] // ソースのステップで[新しいクエリとして追加]したクエリの数式バーの式をコピーして貼り付ける。Column1の3行目。

  3. 追加されたカスタム3 : [列の追加]-[カスタム列の追加]で次のように入力し[OK]。
    新しい列名 : 納品日
    カスタム列の式 : = ソース{3}[Column1] // ソースのステップで[新しいクエリとして追加]したクエリの数式バーの式をコピーして貼り付ける。Column1の4行目。

28.区切り記号の後に検出されたテキスト : "部門"、"受信日"、"出荷日"、"納品日"列を選択し、[変換]-[抽出]-[区切り記号後のテキスト]で区切り記号に":"を入力し[OK]。 ⇒ ":"の後の値が抽出される。

品名/規格 相手先品目コード 品目コード 相手先店コード 自社店コード 店名 数量 ケース数 部門 受信日 出荷日 納品日
りんご/生 B001 12345 160016 S001 東京店 100 10 11 2026/04/28 2026/04/30 2026/05/01
ぶどう/生 B002 11111 160012 S003 北海道店 30 3 11 2026/04/28 2026/04/30 2026/05/01
ぶどう/生 B002 11111 160014 S002 大阪店 40 4 11 2026/04/28 2026/04/30 2026/05/01
  1. 変更された型 : Ctrl+aで全列選択し、[変換]-[データ型の検出]。不適切な型の列を右クリックし[型の変更]で適切な型を選び[現在のものを置換]。

  2. 左の"サンプルシートの変換"を右クリックし[関数の作成...]で関数名に"シートの変換"と入力し[OK]。 ⇒ "シートの変換"関数クエリが完成。

  3. 左のシートの変換関係のクエリをシートの変換グループの下に移動する。

  4. ソースのステップで[新しいクエリとして追加]したクエリ(ABCアイコンのクエリ)を削除する。

3) 後処理処理

1ファイル複数シート

次の記事の「4. 手順」「7) シートを縦結合するクエリを作る」以降を参考にしてください。

複数ファイル複数シート

次の記事の「4. 手順例」「§4 集計クエリの作成」以降を参考にしてください。

6. サンプル

ダウンロード - 260507pq.zip

C:\Users\Public\Documents\260507pq に各ファイルを置いてください。

7. 関連記事

Power Queryで複数の表を結合する方法(まとめ):2025/11/06 : 当ブログ
https://bigarrow.tea-nifty.com/blog/2025/11/post-341d5f.html

Power Queryで表をリスト形式に整形する方法(まとめ): 2025/11/16 : 当ブログ
https://bigarrow.tea-nifty.com/blog/2025/11/post-4925ca.html

8. 余談

Yahoo!知恵袋の質問が訂正されました。1シートに複数の表があります。
https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q10327826315

Power Queryだと、1シートに複数の表がある場合は、シートの変換クエリで子表のリストに整形し、子表の変換クエリでリストに整形することが考えられます。これは、更に複雑なので、VBAが得意ならVBAの方が良いです。

AI(LLM)や質問サイトやネット検索で得たコードは、必ずコードを理解して下さい。理解できないコードは、そのコードが想定していないデータが来ると、間違った結果を出します。コードを理解していれば、どのデータを使用しているのかを理解できるので、想定していないデータが来たときに間違いに気づき、修正することができます。

ブラックボックスを検証せずに使うのは駄目です。しかし、AIの普及で、AIのコードを検証しないで使う人が増えそうで怖いです。コーディング能力も必要ですが、テスト能力の方が重要な気がします。その為には、要件定義、仕様書作成能力が重要ですね。

AI/LLM(ChatGPT、Copilot、Gemini、Claude)は、Power Queryの整形問題が不得意です。特にクリック操作は苦手なようです。

| | コメント (0)

INAV / OpenTX-Telemetry-Widget の設定

1. 前置き

RC飛行機の機体位置を送信機に表示するために、NEXUS-XRをINAVにしたついでに、INAV/OpenTX-Telemetry-Widgetを入れてみました。色々な音声警告機能が追加されました。音声警告は画面を非表示にしていても有効です。画面はあまり有用では無いと思いますが、バッテリ電圧などの音声や振動警告だけは実用性があります。しかし、初期値では不要な警告が多く出ます。また、表示も一部正しくありません。そこで設定一覧を作成しました。予想した項目もあるので、間違っている部分もあるかも知れません。

警告音声目的であれば、これを使用せずにEdgeTX側で各種警告を出すようにした方が良いと思われます。

2. 環境

  • INAV / OpenTX-Telemetry-Widget : 2.3.0 2025/01/19
  • Transmitter : RadioMaster TX16S Mark II 4IN1 (4IN1は使用しない)
  • Transmitter Firmware : EdgeTX 2.12.0
  • External TX Module : BETAFPV ELRS Nano TX V2 Module 2.4G
  • TX Module Firmware : ELRS 4.0.0
  • Flight Controller & Receiver : RadioMaster NEXUS-XR
  • Receiver Firmware : ELRS 4.0.0
  • Flight Controller Firmware : INAV 9.0.1 for NEXUS-X

3. OpenTX-Telemetry-Widget

1) 設定画面の表示

  1. TXにINAV Widgetを表示
  2. [Enter]を長押し、水色枠が出たら離す。-->これでウィンドウの選択モードに入ります。
  3. [Enter]を押す。-->これでウィンドウの編集メニューが出ます。
  4. ローラーで[Full screen]を選択し[Enter]。-->これでINAV Widgetの画面編集モードに入ります。
  5. [SYS]を押す。-->これでINAV Widgetの設定メニューに入ります。

設定画面から出る方法:1)[RTN]で設定画面を出る。 --> 2)[RTN]を長押しで編集モードから出る。

2) 設定

Item Function default Example NEXUS-XR
Battery View Total:総電圧 / Cell:1セルあたりの電圧 Total Cell  
Battery Alert All:全ての電圧警告 / Critical:危険のみ / Off:電圧警告しない All critical  
Cell Low 1セルあたりの警告電圧(IVAVと合わせる) 3.5V 3.5V  
Cell Critical 1セルあたりの危険電圧(INAVのMinimum cell voltage"と合わせる) 3.4V 3.3V  
Cell Calculation セル数計算に用いる1セルの電圧, 4.3V for LiPo, 4.4V for LiHV 4.3V 4.3V  
Fuel Unit percent/mAh(INAV CLI smartport_fuel_unitに合わせる)
電流センサが無い場合は容量計算ができないので、PercentにしてFuel機能を使用することはできない。なので、mAhに設定する。
Percent mAh  
Battery Capacity バッテリ容量 for Crossfire
これは送信機側で残容量を計算するための値だが、電流センサが無ければ意味がない。
1500mAh --  
Fuel Low 電池残容量警告閾値(INAVの"Warning capacity"に合わせる) 30% --  
Fuel Critical 電池残容量危険閾値 20% --  
Altitude Alert 高度警告。On/Off On On  
Max Altitude 高度警告の開始高度 400' or 120m 150m  
Timer 表示するTimerを選択します。Autoは、使われているTimerを自動的に選択して表示します。Auto/1/2/Off Auto Auto  
Tx Voltage 送信機の電圧表示。Both/Graph/Number Both Both  
Rx Voltage 受信機の電圧表示。On/Off On - N/Aだが画面表示される
Variometer 上昇下降速度。Both/Graph/Voice/Off Off Off  
Vario Steps 上昇下降速度を発声する高度変化の値 10' or 10m -- N/A
Altitude Graph 高度推移グラフの表示なし、横軸の時間を設定するようです? Color TXでは無効? Off Off  
Voice Alerts 音声警告。All/Critical/Off All All  
Feedback 低電圧・高度制限・RSSIなどの警告方法。All/Beeper(ビープ音)/Haptic(振動)/Off All Beeper  
RTH Feedback Return to home 動作時の設定。On/Off On Off  
HeadFree Feedback ヘッドレス(Head free)モード中の設定。On/Off On -- N/A
RSSI Feedback RSSI警告の設定。On/Off On On  
AltHold Center FB 高度維持(Alt Hold)モード中の設定。On/Off Off Off  
Speed Sensor 使用する速度センサ。GPS or (if available) Pitot air speed GPS -- N/Aだが表示される
View Mode 白黒送信機の初期画面。Classic, pilot, radar or altitude graph view modes Classic -- N/A
Roll Indicator ロール角度メモリの表示。On/Off Off Off  
Aircraft Symbol 機体の表示シンボル。0 - 5 0 0  
HUD Home Icon Homeアイコン表示/方向表示。Select HUD or simple orientation for home icon on Horus Off On  
Center Map Home レーダー画面でHomeを中心にするか機体を中心にするか Off Off  
Orientation レーター画面の方向を離陸方向にするか北にするか。Launch/compass Launch Launch  
GPS HDOP View GPS精度(HDOP)の表示。Graph/Decimal Graph -- N/A
GPS Warning GPS精度(HDOP)の警告閾値。> 3.5 HDOP [at least 1 bar] 3.5 -- N/A
GPS GPS座標の表示形式。0.000000/0°0'00.00”N 0°0'00.00”E 0.000000 0.000000  
Playback Log Playback telemetry log files (latest 5 logs from the last 2 weeks) -- -- N/A
Greyscale Gfx   -- -- N/A

4. INAV Configuratorの設定

1) 基本設定 - 電圧 & 電流センサー のキャリブレーション

INAVで電圧や電流のTelemetryを使用する場合は、キャリブレーションを行います。PWM端子に電源供給が必要です。電圧については、キャリブレーションをしなくても0.1V程度の精度はあるようです。測定器自体の精度が悪いと逆効果です。

  1. 電圧のキャリブレーション
    測定器で測定した電圧とINAVの表示電圧値を(1)式に代入し、補正値を計算して設定する。
    Gr = Vr / Vc * Gc --(1)

Vc = INAVに表示されたバッテリー電圧
Vr = 測定器で測ったバッテリー電圧
Gc= 現在の電圧補正値
Gr = 校正された電圧補正値

例
Vc = 16.37, Vr = 16.24, Gc =2474(NEXUX-Xの初期値), G = 16.24 / 16.37 * 2474 = 2454

計算式

Vc = Vi * Gc / 100 --> Vi = Vc * 100 / Gc
Vr = Vi * Gr / 100
Gr = Vr / Vi * 100 = Vr / (Vc * 100 / Gc) * 100 = Vr / Vc * Gc --(1)

  1. 電流のキャリブレーション
    測定器で電流0と任意の電流を流した時のINAVの表示電流値を(1)と(2)式に代入して、補正値とオフセット利を計算して設定する。
    Vr = Vc + Gc * Io / 10 --(1)
    Gr = Gc * (Ic - Io) / Ir --(2)

Io = 電流0の時にINAVに表示される電流値[A]
Ir = 実際の電流値[A]
Ic = INAVに表示される電流値[A]
Vc = 現在のオフセット電圧[mV]
Vr = 校正されたオフセット電圧[mV]
Gc = 現在の補正値[mv/A10]
Gr = 校正された補正値[mv/A
10]
Vi = 電流センサの出力電圧値
Gc' = Gc / 10

計算式

Ic = (Vi - Vc) / Gc'

Ir = 0 の時
Io = (Vi - Vc) / Gc' --> vi - Vc = Gc' * Io --> Vi = Gc' * Io + Vc

校正後は Ir = 0 の時に Ic = 0 になるので、
Ir = (Vi - Vr) / Gr' --> 0 = (Vi - Vr) / Gr' --> Vi = Vr
Vr = Gc' * Io + Vc --(1)

Irが流れている時
Vi = Gc' * Ir + Vr
Ic = (Vi - Vc) / Gc' --> vi - Vc = Gc' * Ic --> Vi = Gc' * Ic + Vc
Gr' = (Vi - Vr) / Ir
Gr' = ((Gc' * Ic + Vc) - (Vc + Gc' * Io)) / Ir = (Gc' * Ic - Gc' * Io) / Ir = Gc' * (Ic -Io) / Ir --(2)

余談

RadioMaster ERS-CU01 Current Sensor をINAVで使えるようにして欲しいという要望が出てますね。

RotorFlightの方には、そのような要望は出ていないようです。

どちらもNEXUS-XRで使えるようになってくれると、バッテリ残容量をTelemetryで確認できるようになるので嬉しいのですが。

2) 基本設定 - 電圧 & 電流センサー / バッテリー設定

Item Value Note
Voltage source to use for alarms and telemetry Raw  
Number of cells 0 0=auto, set if you always use the same cell count
Maximum cell voltage for cell count detection 4.25  
Minimum cell voltage 3.3 OpenTX-Telemetry-Widgetの"Cell Critical"に合わせる
Maximum cell voltage 4.2  
Warning cell voltage 3.5 OpenTX-Telemetry-Widgetの"Cell Low"に合わせる
バッテリ容量ユニット mAh/mWh  
Set capacity in mAh to battery capacity 1800 電流センサが無い場合は意味が無いので0にする。警告限界容量は無効になる。
Warning capacity 30% OpenTX-Telemetry-Widgetの"Fuel Low"に合わせる
Critical capacity 10% OpenTX-Telemetry-Widgetの"Fuel Critical"に合わせる

3) CLIでの設定 (ignore for TBS Crossfire)

CLIタブで次の設定をします。電流センサが無い場合は、smartport_fuel_unit を"MAH"にします。

set smartport_fuel_unit = PERCENT / MAH / MWH
set report_cell_voltage = OFF (ONだとLua Telemetryに総電圧を表示できません。)

5. 制限事項

  1. 画面のFuelに電池容量が表示されますが、電流センサが無ければ、意味のない数字が表示されます。
  2. EdgeTX Firmware Simulatorのログ再生で画面のDistanceが0mから変化しません。原因不明です。
  3. EdgeTX Firmware Simulatorのログ再生でレーダー画面にHomeアイコンが表示されません。原因不明です。
  4. EdgeTX Firmware Simulatorで画面のタイマーが動作しません。

6. 関連記事

7. 参考

| | コメント (0)

« 2026年4月 | トップページ | 2026年6月 »