Accessで検索フォームを作ったのに「なぜか一部のレコードが出ない」「クエリを開くとEnter Parameter Valueが出る」。この2つは別問題に見えて、実はLike条件と未入力の扱いが絡むことが多いです。原因の切り分けから、再発しにくい書き方、設計改善までまとめます。
症状の整理:検索フォームでは欠けるのに、テーブルには存在する
今回の構成は、Documentsテーブルを元にした検索クエリ(例:DocSearchQ)を、検索フォーム(例:DocSearchF)のレコードソースとして利用する形です。フォーム上の検索ボックスに名前や年などを入力し、結果を一覧表示する、というよくある作りです。
ところが、フォームを開いたときにDocumentsの一部レコードが表示されない(しかも規則性がないように見える)。さらにDocSearchQを単体で開くと「Enter Parameter Value(Forms!DocSearchF!FamilySearch など)」が出てしまう。ここで混乱が起きます。
結論から言うと、表示されない原因の本命は「Like の書き方」と「未入力時の扱い」です。パラメータ入力要求は「フォーム参照クエリの仕様」を理解すれば、怖い現象ではありません。
まず確認:Accessの「Null」と「空文字」は別物
Accessで検索の条件が思い通りに働かないとき、ほぼ必ず絡むのがNull(値が存在しない)と空文字(長さ0の文字列)の違いです。見た目が空欄でも、内部的には次の2パターンがあります。
- Null:未設定。比較やLikeの結果がNullになりやすい
- 空文字:””。文字列として存在し、Like “” などで一致判定される
さらに、テーブル設計の「ゼロ長文字列を許可(Allow Zero Length)」や、入力フォーム側の制御によって、同じ“空欄”でもデータが混在します。ここを吸収するのがNz()です。
原因1:Likeにワイルドカードが無く「完全一致」になっている
たとえば次の条件は、一見すると部分一致検索に見えますが、実際は完全一致です。
Like "" & Forms!DocSearchF!FName1Search & ""
ワイルドカードが無いので、入力した文字列と完全に同じ値しかヒットしません。入力が「Ken」なのにデータが「Kenji」なら落ちます。これが「一部のレコードが出ない」原因として非常に多いパターンです。
Accessのワイルドカードは「*」が基本(設定により%の場合も)
通常のAccess(ANSI-89)では、Likeのワイルドカードは*(任意の文字列)と?(任意の1文字)です。データベースの設定でANSI-92構文を有効にしている場合は、同じLikeでも%や_のワイルドカードが前提になることがあります。チーム内で設定が混在すると「同じSQLなのに動きが違う」ことが起きるため、運用ルールとして統一しておくと安全です。
| 目的 | 推奨のLike式(ANSI-89: *) | 意味 |
|---|---|---|
| 前方一致 | Like Nz([Forms]![DocSearchF]![FName1Search],"") & "*" | 入力で始まる |
| 部分一致(含む) | Like "*" & Nz([Forms]![DocSearchF]![FName1Search],"") & "*" | どこかに含む |
| 後方一致 | Like "*" & Nz([Forms]![DocSearchF]![FName1Search],"") | 入力で終わる |
| 完全一致 | = Nz([Forms]![DocSearchF]![FName1Search],"") | 完全一致(最も絞る) |
検索フォーム用途なら、まずは前方一致か部分一致が実用的です。大量データで速度が気になる場合は、前方一致(先頭に*を付けない)から検討すると、比較的インデックスが活きやすくなります。
原因2:検索欄が空だと「空文字だけ一致」になり、値があるレコードが落ちる
検索フォームでは「何も入力しない=絞り込まない(全件表示)」が自然です。しかし条件の書き方によっては、未入力時にこう解釈されます。
- 検索欄が空(Null/””)
- 条件が
Like ""相当になる - 結果:空文字のレコードしか残らない
この状態で、どこかの条件に OR [フィールド] Is Null が混ざっていると、今度はNullのレコードだけが残って「ランダムに欠けて見える」現象が起きます。実際にはランダムではなく、Null/空文字の分布に従って欠けています。
ポイント:Is Nullを見たいのは「テーブル列」ではなく「検索コントロール」
よくある失敗は、「フォームが未入力かどうか」ではなく「データがNullかどうか」をORで救ってしまうことです。検索欄が未入力なら条件そのものを無効化し、入力があるときだけ絞り込む、という形に揃えると事故が激減します。
おすすめの基本形は次です(部分一致+未入力は全件)。
(
[FName 1] Like "*" & Nz([Forms]![DocSearchF]![FName1Search],"") & "*"
OR Nz([Forms]![DocSearchF]![FName1Search],"")=""
)
この形のメリットは、検索欄が未入力(Null/””)なら右側がTrueになり、結果としてその条件は常にTrueになって絞り込みが解除される点です。テーブル側のNull/空文字に引きずられません。
原因3:Between(範囲指定)は未入力が混ざると想定外の絞り込みになりやすい
年(Year)や日付(Date)の範囲検索で、次のように書くことがあります。
Between [Forms]![DocSearchF]![YearMinSearch] And [Forms]![DocSearchF]![YearMaxSearch]
ここで片方でも未入力だと、比較結果がNullになったり、意図しない入力要求が出たりして、フォーム上では「一部が欠ける」原因になります。
安全な書き方:未入力なら自分自身の値を使って無条件化する
数値の年フィールド(例:Documents.Year)で、未入力なら絞り込まないようにするには、次の形が扱いやすいです。
Documents.[Year] Between Nz([Forms]![DocSearchF]![YearMinSearch], Documents.[Year])
And Nz([Forms]![DocSearchF]![YearMaxSearch], Documents.[Year])
これなら、最小が未入力なら「[Year]以上」が無効化され、最大が未入力なら「[Year]以下」が無効化されます。両方未入力なら [Year] Between [Year] And [Year] となり、YearがNullでないレコードは常にTrue(=全件)です。
もしYearがNullのレコードも「未入力時は表示したい」なら、次のように“未入力時は無条件”を明示すると確実です。
(
(Nz([Forms]![DocSearchF]![YearMinSearch],"")="" AND Nz([Forms]![DocSearchF]![YearMaxSearch],"")="")
OR Documents.[Year] Between Nz([Forms]![DocSearchF]![YearMinSearch], Documents.[Year])
And Nz([Forms]![DocSearchF]![YearMaxSearch], Documents.[Year])
)
| 入力状態 | YearMinSearch | YearMaxSearch | 動き |
|---|---|---|---|
| 両方入力 | 2000 | 2010 | 2000〜2010のみ |
| 最小だけ入力 | 2000 | 未入力 | 2000以上(上限なし) |
| 最大だけ入力 | 未入力 | 2010 | 2010以下(下限なし) |
| 両方未入力 | 未入力 | 未入力 | 全件(YearがNullでも表示したいなら上の“無条件化”パターンを使う) |
日付フィールドでも同様に組めますが、日付の上限は「当日を含める/含めない」で意図が分かれるため、検索要件に合わせて調整してください(例:終了日は23:59:59まで含める、など)。
原因4:クエリを単体で開くと「Enter Parameter Value」が出る理由
[Forms]![DocSearchF]![...] のようにフォームを参照するクエリは、フォームが開いていない状態でクエリだけ実行すると参照先が解決できません。そのためAccessは「値を入力して」と尋ねます。これがEnter Parameter Valueです。
ただし:フォームを開いていても出る場合は“参照ミス”の可能性が高い
フォームが開いているのに入力要求が出る場合、次のパターンが多いです。
| 原因 | 例 | チェック方法 |
|---|---|---|
| コントロール名の打ち間違い | FamilySearch のつもりが FamliySearch | フォームのプロパティでNameを確認 |
| フォーム名の参照違い | DocSearchFではなくサブフォーム名を参照している | 実際に値が入るフォーム/サブフォームを確認 |
| フィールド名の誤字・スペース | [FName 1]を[FName1]と書いた | テーブル定義とクエリのSQLを見比べる |
| 式のカッコや演算子の崩れ | AND/ORの括りが崩れ、Accessがパラメータと解釈 | SQLビューで括弧の対応を確認 |
正しいテスト手順
- まずDocSearchFを開いた状態でDocSearchQを実行する(フォーム参照が解決できる)
- クエリ単体でテストしたい場合は、検証中だけフォーム参照を定数に置き換える(例:”Ken”)
- 運用が大きい場合は、クエリ側をパラメータクエリにし、フォームから値を渡す構成にすると、テストや移行がしやすい
パラメータクエリ化の例(フォーム参照を減らして堅牢にする)
フォーム参照は便利ですが、フォームが増えると参照が複雑になりがちです。以下は、クエリにPARAMETERS句を持たせて、フォーム側から値を渡す考え方の例です。
PARAMETERS
pFName1 Text (255),
pYearMin Long,
pYearMax Long;
SELECT
Documents.*
FROM
Documents
WHERE
(Documents.[FName 1] Like "*" & Nz([pFName1],"") & "*" OR Nz([pFName1],"")="")
AND (
(Nz([pYearMin],"")="" AND Nz([pYearMax],"")="")
OR Documents.[Year] Between Nz([pYearMin], Documents.[Year]) And Nz([pYearMax], Documents.[Year])
);
この形にしておくと、フォーム参照が無いのでクエリ単体でも挙動が読みやすく、他フォームへの流用もしやすくなります。
実践:検索フォーム用クエリ(DocSearchQ)のテンプレート
検索条件が増えるほど、書き方の“揺れ”がバグになります。そこで、検索フォームでは次のルールで統一するのがおすすめです。
- 文字列検索は「Like + Nz + 未入力なら全件」を基本形にする
- 数値・日付の範囲は「Between + Nz」に加え、Null値も表示したいなら未入力時は無条件を明示する
- ORで救うのは「パラメータが未入力」だけ。テーブル列のNullをORで救わない
- 条件式は、できる限りパラメータ側だけにNzやTrimを使い、フィールド側に関数をかけない(読みやすさと速度のため)
例として、Documentsに「FName 1 / LName 1 / City / Country / Year」がある想定で、検索フォームDocSearchFの未バインドテキストボックスに入力して絞り込むクエリ例です。
SELECT
Documents.*
FROM
Documents
WHERE
(
Documents.[FName 1] Like "*" & Nz([Forms]![DocSearchF]![FName1Search],"") & "*"
OR Nz([Forms]![DocSearchF]![FName1Search],"")=""
)
AND (
Documents.[LName 1] Like "*" & Nz([Forms]![DocSearchF]![LName1Search],"") & "*"
OR Nz([Forms]![DocSearchF]![LName1Search],"")=""
)
AND (
Documents.[City] Like "*" & Nz([Forms]![DocSearchF]![CitySearch],"") & "*"
OR Nz([Forms]![DocSearchF]![CitySearch],"")=""
)
AND (
Documents.[Country] Like "*" & Nz([Forms]![DocSearchF]![CountrySearch],"") & "*"
OR Nz([Forms]![DocSearchF]![CountrySearch],"")=""
)
AND (
(Nz([Forms]![DocSearchF]![YearMinSearch],"")="" AND Nz([Forms]![DocSearchF]![YearMaxSearch],"")="")
OR Documents.[Year] Between Nz([Forms]![DocSearchF]![YearMinSearch], Documents.[Year])
And Nz([Forms]![DocSearchF]![YearMaxSearch], Documents.[Year])
);
この形にすると、検索欄が未入力の項目は自動的に無条件化され、入力した項目だけが絞り込みに効きます。「開いた瞬間に欠ける」「たまに欠ける」の多くが解消されます。
複数の名前列(FName1/FName2など)を“どれかに一致”で探したい場合
人名が複数列に分かれている設計では、「FName1にもFName2にも一致しないと表示されない」ようなAND条件を作ってしまいがちです。ユーザーが期待するのは「1人目でも2人目でもいいから見つけたい」ことが多いので、その場合はORでまとめて括弧を付けます。
(
Documents.[FName 1] Like "*" & Nz([Forms]![DocSearchF]![NameSearch],"") & "*"
OR Documents.[FName 2] Like "*" & Nz([Forms]![DocSearchF]![NameSearch],"") & "*"
OR Nz([Forms]![DocSearchF]![NameSearch],"")=""
)
ANDとORを混在させるときは、括弧の付け方が結果を大きく変えるので、SQLビューで必ず意図通りの論理になっているか確認してください。
Trimの扱い:見えないスペースで一致しない問題を潰す
完全一致検索や前方一致を使う場合、データ側に先頭末尾スペースが混入していると一致しないことがあります。入力側だけをTrimしておくと、ユーザー操作のブレを吸収できます。
Like "*" & Trim(Nz([Forms]![DocSearchF]![CitySearch],"")) & "*"
ただし、フィールド側にTrimをかける(例:Trim([City]) Like …)と、インデックスが効きにくくなる場合があるため、基本は入力側のみに留めると良いです。
フォーム側の実装で見落としがちなポイント
SQLを直しても、フォーム側の動きが不完全だと「直ったはずなのに結果が変わらない」と感じます。次の点も合わせて確認してください。
| 確認ポイント | よくある状態 | 対策 |
|---|---|---|
| 検索ボタンで再クエリしているか | 入力しても一覧が更新されない | Me.Requery または一覧サブフォームだけをRequery |
| 検索欄がバインドされていないか | 入力がDocumentsに保存され、データが汚れる | 検索欄は未バインド(ControlSource空)にする |
| Null/空文字が混在 | 一部だけ一致しない | Nzで吸収、必要ならデータクレンジングを実施 |
| データ型不一致 | 年が文字列、数値比較が壊れる | Yearは数値、Dateは日付型で統一 |
| ANSI-92設定の違い | *なのに%が必要な環境がある | 運用で統一、または環境に合わせてSQLを揃える |
トラブルシュート:それでも欠けるときの切り分け手順
原因がLikeと未入力であることが多いとはいえ、複数条件が絡むと「どの条件が落としているのか」が見えづらくなります。次の順で確認すると、短時間で原因に辿り着けます。
- 条件をすべて外したクエリ(Documents.*をそのまま出す)で、欠けているレコードが存在するか確認する
- 条件を1つずつ戻して、どの条件を戻した瞬間に欠けるかを見る
- 欠けたレコードの該当フィールドが、Nullか空文字か、スペースが入っていないかを確認する
- フォームの入力欄がNullなのか””なのかを、MsgBoxなどで確認する(例:
IsNull(Me.FName1Search))
「規則性がない」は、たいてい「規則性に気付けていない」状態です。Null/空文字/スペース/データ型のいずれかが、必ず規則性として現れます。
改善提案:テーブル設計の見直し(正規化)で検索を“壊れにくく”する
ここまでは「現状のDocumentsテーブル構造のまま、検索条件の書き方で事故を防ぐ」話でした。ですが、次のような構造になっている場合、クエリはどうしても複雑化し、将来的に保守が難しくなります。
- City / State / Country のような階層情報がDocumentsに直書きされている
- FName1/LName1/FName2/LName2… のように、人名が“列”として増えていく(繰り返し項目)
こうした設計は、検索UIを増やすほど条件が指数的に増え、LikeやNull処理の“同じ罠”を何度も踏みます。根本的に安定させたいなら、正規化(テーブル分割)を検討する価値があります。
正規化の方向性:文字列で結合しない、IDで結合する
検索の信頼性と速度を上げるコツは、可能な範囲で文字列ではなく数値キー(ID)で絞り込むことです。同名の揺れ(Ken/Kenji、Tokyo/東京)や表記ゆれ(USA/U.S.A.)に強くなり、インデックスも効かせやすくなります。
| 現状の例(非推奨になりやすい) | 改善の考え方(推奨) | 得られる効果 |
|---|---|---|
| Documents.Country に文字列 | Countriesテーブルを作りCountryIDで保持 | 表記ゆれ排除、検索が高速 |
| Documents.City / State / Country が直書き | City→State→Countryを関連テーブル化 | コンボ選択が可能、入力ミス減 |
| FName1/LName1/FName2/LName2… | Peopleテーブル+中間テーブル(DocumentPeople) | 人数が増えても列追加不要 |
具体例:人名を関連テーブル化する
Documentsに「1人目」「2人目」…と列を増やす代わりに、次のような構成にします。
- Documents(DocumentID, Title, Year, CityID, …)
- People(PersonID, FirstName, LastName, …)
- DocumentPeople(DocumentID, PersonID, Role など)
これにより、「この人物が関わる文書を探す」「著者だけで絞る」といった検索が、列の増減に左右されずに組めます。さらに、フォーム側も「人物コンボボックス(PersonID)」を使った絞り込みにできるため、Like検索の比率を減らせます。
検索UIの作り替え:コンボボックス中心にしてLikeを減らす
テキストボックスのLike検索は自由度が高い一方、未入力・表記ゆれ・スペース混入などの影響を受けやすいです。そこで検索フォームの入力は、次のような比率にするのがおすすめです。
- 都道府県・国・カテゴリ:コンボボックス(候補から選択)
- 人物:可能なら人物マスタから選択(補助的にフリーワード)
- タイトル・メモなど:フリーワード(Like)
- 年・日付:最小/最大の範囲入力
特に、City/State/Countryのような階層は、連動コンボ(カスケード)にすると、入力ミスと検索漏れが激減します(Countryを選ぶとState候補が絞られ、Stateを選ぶとCity候補が絞られる)。
正規化後のクエリは「短く」「読みやすく」「速い」
例として、CountryIDで検索する場合、Where句は次のようにシンプルになります。
WHERE
(Documents.CountryID = [Forms]![DocSearchF]![CountryID]
OR IsNull([Forms]![DocSearchF]![CountryID]) )
文字列のLike条件よりも意図が明確で、結果がぶれません。さらに、インデックスが効くため速度面でも有利です。
未入力パラメータの事故を防ぐ「統一ルール」
Accessの検索フォームを長く運用すると、担当者が増え、条件式の書き方が少しずつ変わります。その結果、「同じように見えるのに挙動が違う」条件が混ざり、いつか破綻します。そこで、プロジェクト内で次の統一ルールを決めておくと強いです。
| 項目タイプ | 統一パターン | 例 |
|---|---|---|
| 文字列(部分一致) | (Field Like "*" & Nz(Param,"") & "*" OR Nz(Param,"")="") | 氏名、都市、キーワード |
| 数値/日付(範囲) | (Param両方未入力) OR Field Between Nz(Min,Field) And Nz(Max,Field) | 年、作成日 |
| ID(完全一致) | (FieldID = ParamID OR IsNull(ParamID)) | 国ID、カテゴリID |
このルールに揃えるだけで、「未入力で欠ける」「Nullだけ残る」「たまに落ちる」といった典型的な事故の多くを未然に防げます。
まとめ:フォーム参照クエリの“つまずき”はパターン化できる
Accessの検索フォームで「一部のレコードが表示されない」問題は、データが消えているわけではなく、ほとんどの場合条件式の書き方で落ちています。Likeのワイルドカード、Nzによる未入力吸収、Betweenの安全化、そしてEnter Parameter Valueの仕様理解。この4点を押さえれば、検索フォームは安定します。
さらに長期運用を見据えるなら、Documentsテーブルの正規化と、コンボボックス中心の検索UIへの移行が効きます。検索条件が増えても壊れにくく、メンテナンスしやすい構成に育てていきましょう。

コメント