Accessレポートで#Type!/#Size!を解消:Nzとサブレポート参照の正しい計算式

Access のレポートで Nz() を使った引き算をすると、結果欄が「#Type!(型不一致)」や「#Size!」になってしまうことがあります。多くは Nz の戻り値が数値になっていない、サブレポート参照が誤っている、サブレポートが空/未読込のタイミングで参照している――という複合要因です。エラーを消しつつ正しく差分を出す式と、実務での確認ポイントをまとめます。

目次

現象:レポートの計算式が #Type! / #Size! になる

Microsoft Access のレポートで、テキストボックス(計算コントロール)の「コントロールソース」に次のような式を入れたとき、結果が数値ではなく #Type! や #Size! と表示されることがあります。

=Nz([asep]) - Nz((Reports![Rep16_loc_reg]!Report12csub!col1))

やりたいことは単純で、[asep] とサブレポート側の col1 の差分を出したい、かつ Null は 0 として扱いたい、というケースです。しかし Access のレポートは「式の評価タイミング」「型の推論」「サブレポートの参照方法」が絡むため、見た目が簡単でもエラーになりやすいポイントがあります。

#Type! と #Size! の意味を押さえる

まずはエラー表示の意味をざっくり整理します。原因を1つに決め打ちすると遠回りになりやすいので、症状から当たりを付けるのが近道です。

表示意味(要約)このテーマで多い原因最初に見るべき点
#Type!型が合わず演算できない(例:文字列 − 数値)Nz() が ""(空文字)を返している/文字列の数値が混在しているNz の第2引数、Val/CDbl 等の数値化
#Size!結果が表示・格納できる範囲/サイズを超える、または型の混在で内部判定が崩れるサブレポート参照の失敗で想定外の値になる/IIf で返す型が混ざるサブレポート参照を .Report 付きで正す、返り値の型を統一する

特に #Type! は「数値のつもりが文字列扱い」になっている時に出やすく、#Size! は「参照の崩れ・型の混在・想定外の巨大値」などで出ることがあります。

原因になりやすいポイント:Nz とサブレポート参照の3つの罠

  • Nz() の戻り値が数値になっていない:[asep] や col1 がテキスト型(文字列)だと、Null を Nz すると "" になりやすく、引き算で #Type! になります。
  • サブレポート参照の書き方が不正:サブレポートは「レポートそのもの」ではなく「サブレポートコントロール」です。内部のレポートにアクセスするには .Report が必要です。
  • サブレポートが空、または読み込み前のタイミングで参照している:サブレポートにレコードが無い、あるいはまだロードされていない状態でコントロール値を参照すると、条件によって #Type! / #Size! の原因になります。

まずはこれ:Nz の第2引数を 0 にして「必ず数値で返す」

Null を 0 にしたいなら、Nz の第2引数を省略しないのが鉄則です。さらにサブレポート参照は .Report を付けます。

=Nz([asep], 0) - Nz(Reports![Rep16_loc_reg]!Report12csub.Report!col1, 0)

この1行だけで直るケースが最も多いです。ポイントは次の2つです。

  • Nz(値, 0) の形にすることで、Null のときも 数値の 0 を返し、引き算が成立しやすくなります。
  • Report12csub.Report!col1 の .Report が重要です(これが無いと、サブレポート「コントロール」を直接参照した扱いになり、値の取得が不安定になります)。

サブレポート参照の正しい書式を理解する

サブレポート参照が崩れると、Nz を直しても #Type!/#Size! が残ることがあります。Access のレポートでは、参照の「部品」が揃っていないと値に届きません。

部品例意味よくある勘違い
親レポートReports![Rep16_loc_reg]開いている親レポートのオブジェクトフォーム参照(Forms!)と混同する
サブレポートコントロール名!Report12csub親レポート上に配置したサブレポート枠の「名前」「ソースオブジェクト(表示しているレポート名)」と混同する
サブレポート(内部レポート).Reportサブレポートコントロール内のレポートオブジェクトにアクセスするためのプロパティ.Report を付けずに中のコントロールを参照しようとする
サブレポート内のコントロール!col1サブレポート上のテキストボックス等のコントロール名フィールド名を入れれば取れると思い込む(コントロール名が別だと取れない)

特に重要なのが、サブレポートは「コントロール名」で参照する点です。デザインビューでサブレポート枠をクリックし、プロパティシートの 「名前」 が Report12csub になっているか確認してください。ここがズレていると、式が正しくても参照エラーになります。

文字列っぽい数値が混ざるなら「数値化」してから引き算する

テーブル設計や途中のクエリの都合で、数字がテキストとして入っていることは現場では珍しくありません(例:"123"、空文字 ""、前後にスペース、通貨記号付きなど)。この場合は Nz だけでは足りず、数値化してから引き算します。

=Val(Nz([asep], 0)) - Val(Nz(Reports![Rep16_loc_reg]!Report12csub.Report!col1, 0))

数値化の関数は複数あります。状況で使い分けると安定します。

関数特徴向いているケース注意点
Val()文字列の先頭から数値として読める部分を数値化「テキストに数字が入る」「空文字が混ざる」などの現場データ先頭が数字でないと 0 になりやすい(例:"¥100" は 0 になりがち)
CDbl()より厳密に Double に変換する小数を扱う/形式が揃っているデータ変換できない文字列だとエラーになるため、Nz/IsNumeric 等と組み合わせる
CLng()整数(Long)に変換する金額の「円」など、整数で扱える値小数があると丸めが発生/範囲外はオーバーフローの原因
CCur()通貨型に変換する金額計算で誤差を避けたい範囲外・文字列混在だとエラーになるため前処理が必要

「とにかく表示エラーを止めたい」だけでなく、どの型で計算したいか(小数/金額/整数)を決めると、後工程(集計・CSV出力・他システム連携)まで含めて事故が減ります。

サブレポートが空でも落ちない:HasData で分岐する

サブレポートが条件で空になる場合、サブレポート内のコントロールを参照した瞬間に崩れることがあります。そこで HasData を使い、「レコードがある時だけ参照する」形にします。

=Nz([asep], 0) -
 IIf(Reports![Rep16_loc_reg]!Report12csub.Report.HasData,
     Nz(Reports![Rep16_loc_reg]!Report12csub.Report!col1, 0),
     0)

この形にしておくと、

  • サブレポートにデータがあるとき:col1 を参照して差分
  • サブレポートが空のとき:サブレポート側を 0 とみなして差分

という動きになり、運用上の「条件によってだけ壊れる」状態を回避しやすくなります。

補足:Access の IIf() は、式の内容や評価タイミングによっては「真/偽どちらの式も評価される」挙動になることがあります。もしサブレポート参照そのものが実行時エラーになる状況(例:コントロール名の不一致、サブレポートが未ロードで .Report に到達できない等)では、HasData 分岐だけでは守り切れない場合があります。そのときは参照するセクションをサブレポートの後ろ(フッター側)に移す、または後述のようにVBA でエラートラップして数値を返す方法が確実です。

最後の保険:IsError で 0 に落とす(使いどころに注意)

現場では「エラー表示が1件でも残ると印刷物として出せない」ことがあります。その場合の最後の保険が IsError() です。式全体を評価してエラーなら 0(または空欄)に落とします。

=IIf(IsError(
      Nz([asep],0) -
      IIf(Reports![Rep16_loc_reg]!Report12csub.Report.HasData,
          Nz(Reports![Rep16_loc_reg]!Report12csub.Report!col1,0),
          0)
    ),
    0,
    Nz([asep],0) -
    IIf(Reports![Rep16_loc_reg]!Report12csub.Report.HasData,
        Nz(Reports![Rep16_loc_reg]!Report12csub.Report!col1,0),
        0)
 )

ただし IsError() は「本当は参照が間違っている」などの問題も 0 で隠してしまいます。開発・検証段階では安易に入れず、原因が特定できた後に「運用保険」として使うのがおすすめです。

また、IsError() は「式の結果がエラー値になる」ケースには有効ですが、式の途中で発生する実行時エラーを完全に抑えられるとは限りません。参照が不正で落ちるタイプの問題は、まず参照名・配置セクション・データ有無を正してから、仕上げとして IsError() を使うと安全です。

実務でハマりやすいチェック項目

式を直してもまだ #Type!/#Size! が出る場合は、だいたい「名前」「型」「タイミング」「集計意図」のどれかがズレています。以下の表で一気に潰すと早いです。

チェック項目具体的な確認方法よくある症状推奨対処
[asep] の型元テーブル/クエリでフィールド型を確認、またはレポートのコントロールソースを確認Null のときだけ #Type!、空文字が混ざるNz([asep],0) +必要なら Val()/CDbl()
col1 の型サブレポート側のコントロールのコントロールソース、元データ型を確認レコードによってだけ #Type!、印刷時だけ崩れるサブ側も Nz(…,0)、文字列なら数値化
サブレポートコントロール名親レポートのデザインビューでサブレポート枠を選択→「名前」参照式が常にエラー、または別のサブレポートを見に行く式内の Report12csub を「名前」に合わせる
col1 はコントロール名かサブレポートのデザインビューで対象のテキストボックスを選択→「名前」フィールド名は合っているのに参照できないコントロール名を col1 に揃えるか、式側を正しい名前に変更
参照タイミング計算式を置いたセクション(詳細/ヘッダー/フッター)を確認画面表示はOKだが印刷で崩れる、またはグループでだけ崩れるサブレポートと同じ/後のセクションで計算、HasData を併用
フォーマット設定結果表示テキストボックスの「書式」「小数点以下表示」「入力規則」など#Size! や表示崩れ、桁あふれ型を統一(CDbl/CCur 等)、必要なら書式を見直す

「col1」が明細なのか合計なのかを整理する

サブレポート内の col1 が明細行の値なのか、合計値なのかで、参照すべきコントロールが変わります。明細のまま参照すると「どの行の col1 を引くの?」という状態になり、意図しない値を引いてしまうことがあります。

合計を引きたい場合は、サブレポート側に「合計用テキストボックス」を用意してから参照すると安定します。

  • サブレポートのレポートフッター(またはグループフッター)にテキストボックスを配置
  • コントロールソース:=Sum(Nz([col1],0))
  • 名前:例 txtCol1Total

親レポートからは、その合計コントロールを参照します。

=Nz([asep], 0) - Nz(Reports![Rep16_loc_reg]!Report12csub.Report!txtCol1Total, 0)

この作りにしておくと、

  • 「明細行のコントロール」を参照してしまって値が変動する
  • グループごとにサブレポートが再生成されて参照が不安定になる

といったトラブルを避けやすくなります。

より安定する設計:計算をクエリ側に寄せる

レポート間参照は便利ですが、読み込み順やセクションの評価順に影響されがちです。差分計算が帳票の中心なら、クエリで差分を作ってからレポートで表示する構成のほうが、長期運用で安定します。

例えば、サブレポート側で集計している値が「キー(例:ID)」ごとに1つ決まるなら、集計サブクエリを作って親データに結合し、差分を計算します。

SELECT
    M.ID,
    Nz(M.asep,0) AS asep0,
    Nz(S.col1_sum,0) AS col1_sum0,
    Nz(M.asep,0) - Nz(S.col1_sum,0) AS diff_value
FROM
    MainTable AS M
    LEFT JOIN
    (
        SELECT
            ID,
            Sum(Nz(col1,0)) AS col1_sum
        FROM SubTable
        GROUP BY ID
    ) AS S
    ON M.ID = S.ID;

このクエリをレポートのレコードソースにすれば、レポート上の計算式は単に =diff_value を表示するだけになります。サブレポート参照が不要になるので、#Type!/#Size! の「再発余地」を大きく減らせます。

VBA で安全な差分関数を用意する方法(任意)

式が長くなりすぎる、複数箇所で同じ差分計算を使う、という場合は VBA 関数に逃がすのも有効です。レポートの式は短く保ち、共通処理にまとめると保守が楽になります。

Public Function SafeDiff(ByVal v1 As Variant, ByVal v2 As Variant) As Double
    On Error GoTo EH
    SafeDiff = CDbl(Val(Nz(v1, 0))) - CDbl(Val(Nz(v2, 0)))
    Exit Function
EH:
    SafeDiff = 0
End Function

レポート上では次のように書けます。

=SafeDiff([asep], Reports![Rep16_loc_reg]!Report12csub.Report!col1)

VBA に寄せると IsError() のような式の二重評価も避けられ、読みやすさも上がります(ただし、VBA を許容できる運用かどうかは要件次第です)。

切り分けの手順:原因を最短で特定する

最後に、現場で効く切り分け手順をまとめます。#Type!/#Size! は複合要因になりやすいので、「一度に全部直す」より1項目ずつ確認するのが結果的に早いです。

  1. 親側だけ表示できるか:まず結果用テキストボックスに =Nz([asep],0) だけを入れ、#Type! が出ないか確認します。
  2. サブ側だけ表示できるか:別のテキストボックスに =Nz(Reports![Rep16_loc_reg]!Report12csub.Report!col1,0) を入れて参照が通るか確認します。
  3. HasData の値を見える化:=Reports![Rep16_loc_reg]!Report12csub.Report.HasData を一時的に表示し、空データ時の挙動を把握します。
  4. 数値化の必要性を判定:どちらかが文字列の可能性があるなら Val() を追加し、差分が安定するか確認します。
  5. コントロール名の最終確認:サブレポート枠の「名前」、サブレポート内の対象テキストボックスの「名前」が式と一致しているか再確認します。

この順で確認すると、「式は合っているのに参照先が違う」「Null の扱いが 0 ではなく空文字になっている」など、よくある落とし穴を短時間で潰せます。

まとめ

  • Nz を使うなら Nz(値, 0) で数値を返すのが基本。
  • サブレポート参照は サブレポートコントロール名.Report!コントロール名 の形にする。
  • 文字列数値が混ざるなら Val/CDbl/CCur などで型を揃える。
  • サブレポートが空になる可能性があるなら HasData で分岐。
  • 安定運用を重視するなら、差分計算を クエリ側に寄せると再発しにくい。

ここまでの対処を入れても解決しない場合は、ほとんどが「参照名のズレ」か「明細/合計の取り違え」です。表のチェック項目をもう一度なぞると、原因に行き当たるはずです。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次