none
SQLでレコードを追加する際に連番を振りたい。 RRS feed

  • 質問

  • SQLでレコードを追加するために、下記のような記述を行いましたが、エラーが発生いたしました。

    【VBA記述内容】

    const ExportTbl  as string = "T_Export"

            strSQL = "INSERT INTO " & ExportTbl _
                    & " ( ID, ・・・以下略) " _
                    & " SELECT DCOUNT('ID'," & "'" & ExportTbl & "'" & ",'ID <= ' & [ID]),  ・・・・以下略

                    & "FROM " & ExportTbl         

           dbs.Execute strSQL, dbSeeChanges Or dbFailOnError

    上記内容を実行しますと、次のようなエラーが発生します。

    「実行時エラー 3061:パラメータが少なすぎます。1を指定してください。」

    『以下略』の部分だけで実行すると問題なく処理されますので、

    やはり、このDCOUNT部分に誤りがあるようです。

    SQL文内の条件の設定をよくわからず、フィールドは”[”を使うべきなのか、 " ' "(シングルクォーテーション)なのか、

    フィールド名はテーブル名から記述すべきか、など、あらゆるパターンで試しましたがエラーがでてしまいます。

    そもそもDCOUNTの使用方法が間違っているのかどうかも謎で、八方ふさがっております。

    なにとぞ皆様からのご教示いただけますよう、よろしくお願いいたします。

    2015年1月30日 0:47

回答

  • こんにちは

    wSQL = "INSERT INTO T1 ( [ID], [TEXT] ) SELECT DCount('ID','T2','ID <=' & [ID]), Text1 FROM T2;"
    dbs.Execute wSQL, dbSeeChanges Or dbFailOnError

    Access2013ではこれで問題ないですね。Executeを実行する直前のSQL文をまず確認して、それをクエリーとして実行してみて動くかどうか確認してみましょう。
    入力フィールドと、出力フィールドを一つずつ減らしてみて、どこで動くようになるかで、あたりはつくかと思います。

    また、フィールドで [ が必要なのは、予約語などAccessが間違えそうなフィールド名の時ですね。
    クエリービルダでSQLを作ってみるとわかると思います。

    それと連番を振る方法ですが、この方法だとデータ量が大きいときに時間がかかりますよね。
    Accessでは、ユーザー関数をSQL文の中に入れられますので、私はよく次のようにやります。

    -----SQL文-----

    Cntr=0
    strSQL="SELECT GetSEQ([Text1]) AS ID, T2.Text1 FROM T2;"

    -----モジュール-----

    Option Compare Database
    Dim Cntr As Long
    Function GetSEQ(DummyFld) As Long
        Cntr = Cntr + 1
        GetSEQ = Cntr
    End Function

    ※ Cntr=0 を入れることでCntrを初期化します。
    ※ DummyFldに、そのレコードのなんでもいいからフィールドを渡します。
     これがないと、番号のカウントアップがされません。キャッシュが働いてしまうからですね。

    いかがでしょう?


    Shigeru Murai / SIMOZ

    • 回答としてマーク しんとら 2015年1月30日 5:44
    2015年1月30日 1:55

すべての返信

  • こんにちは

    wSQL = "INSERT INTO T1 ( [ID], [TEXT] ) SELECT DCount('ID','T2','ID <=' & [ID]), Text1 FROM T2;"
    dbs.Execute wSQL, dbSeeChanges Or dbFailOnError

    Access2013ではこれで問題ないですね。Executeを実行する直前のSQL文をまず確認して、それをクエリーとして実行してみて動くかどうか確認してみましょう。
    入力フィールドと、出力フィールドを一つずつ減らしてみて、どこで動くようになるかで、あたりはつくかと思います。

    また、フィールドで [ が必要なのは、予約語などAccessが間違えそうなフィールド名の時ですね。
    クエリービルダでSQLを作ってみるとわかると思います。

    それと連番を振る方法ですが、この方法だとデータ量が大きいときに時間がかかりますよね。
    Accessでは、ユーザー関数をSQL文の中に入れられますので、私はよく次のようにやります。

    -----SQL文-----

    Cntr=0
    strSQL="SELECT GetSEQ([Text1]) AS ID, T2.Text1 FROM T2;"

    -----モジュール-----

    Option Compare Database
    Dim Cntr As Long
    Function GetSEQ(DummyFld) As Long
        Cntr = Cntr + 1
        GetSEQ = Cntr
    End Function

    ※ Cntr=0 を入れることでCntrを初期化します。
    ※ DummyFldに、そのレコードのなんでもいいからフィールドを渡します。
     これがないと、番号のカウントアップがされません。キャッシュが働いてしまうからですね。

    いかがでしょう?


    Shigeru Murai / SIMOZ

    • 回答としてマーク しんとら 2015年1月30日 5:44
    2015年1月30日 1:55
  • こんにちは。

    返信が遅くなり、申し訳ありません。とても丁寧で要点がわかりやすいご回答をいただき、大変助かりました。ありがとうございました。

    ご指導いただきました、SQL文をクエリにしてフィールドを1つずつ実行する検証をいたしました。

    おかげさまで、希望通りの処理ができるようになったのですが、また新たな疑問が出てまいりました。

    >wSQL = "INSERT INTO T1 ( [ID], [TEXT] ) SELECT DCount('ID','T2','ID <=' & [ID]), Text1 FROM T2;"
    dbs.Execute wSQL, dbSeeChanges Or dbFailOnError

    ご提示いただいた内容を参考に修正してみたのですが、なぜかカウントが1つ足りず、+1ででっちあげて作成した下記内容に最終的に落ち着きました。

    >strT_SQL = "SELECT DCOUNT('No'," & "'" & ExportTbl & "'" & ",'No <= [No]')+1,・・・・以下略

    どの解説でも+1せずに実行できるようですが、条件部分の[no]がうまく追加レコードのNOを見にいけてないのでしょうか・・

    そして、想像をはるか上をいくすばらしい提案内容をありがとうございます。とても使いやすそうで、ぜひ取得してみたいと思います。

    ただ、知識の足りないところがございまして、何点かお伺いしたいことがございます。

    ①GetSEQの引数ですが、こちらはSQLで実行する際にモジュールでカウントを実行するためのトリガーのようなもの、という認識ではおかしいでしょうか。また、引数の型がstringで問題はないでしょうか。

    ②>これがないと、番号のカウントアップがされません。キャッシュが働いてしまうからですね。とご説明いただいているのですが、なぜカウントアップされないのか、キャッシュが働いてしまうとはどういう状態なのかがわからないのです。

    基本的な知識がなく、お手数をおかけしますが、なにとぞ再度ご指導のほどよろしくお願いいたします。

    2015年1月30日 2:52
  • T_Exportテーブルからデータを抜いて、同じT_Exportテーブルにレコードを追加されたいように見えますが、違いますか?
    テーブル名からすると違うテーブルからレコードを抜いて、T_Exportテーブルにレコードを追加されたいように思えます。
    ただ、その際に、T_Exportテーブルには既にレコードがあり、T_Exportテーブルの中でその続きで連番を振りながらレコードを追加したいということでしょうか?

    #DCOUNTを使われていますからAccessだと思いますが、その辺りの環境も書くようにして下さい。データベースやそのバージョンによってSQLが変わってきます。

    #追記
    やはり違うテーブルから抜いて、T_Exportテーブルに書き出すということだったようですね。ただ、依然として+1しなければならないというところが謎です。空のT_Exportテーブルに書き出すの出れば、+1は必要ないことなんですけどね・・・。


    ★良い回答には回答済みマークを付けよう! MVP - .NET  http://d.hatena.ne.jp/trapemiya/

    2015年1月30日 2:59
    モデレータ
  • 大変失礼いたしました。「ACCESS2003」を使用しております。

    おっしゃるとおり、他のテーブルのレコードを、T_Exportテーブルに既にあるレコードと同じでなければ追加したいのです。

    ですので、T_Exportテーブルに既にあるレコード数にプラスして番号を振りたいと考えています。

    何度申し訳ありません。どうぞよろしくお願いいたします。

    2015年1月30日 3:13
  • こちらでは異なる現象になりますね。

    まず、
    SELECT DCOUNT('No'," & "'" & ExportTbl & "'" & ",'No <= [No]')+1

    SELECT DCOUNT('[No]'," & "'" & ExportTbl & "'" & ",'[No] <= [No]')+1

    にしないとすべてのレコードに同じ値が返ってきてしまいます。
    そして、[]をつけると正常になります(+1入れずに)
    ちょっと確認してみてください。
    []は、Noには必須ですね。いい例になったですね。

    >>  ①GetSEQの引数ですが、こちらはSQLで実行する際にモジュールでカウントを実行するためのトリガーのようなもの、という認識ではおかしいでしょうか。また、引数の型がstringで問題はないでしょうか。

    ”トリガー”とはちょっと違いますね。単純にSQL文に関数を組み込んだだけです。(これも一種のトリガーなのかな?)
    引数をStringとするならば、渡すパラメータは文字列型にしておいたほうがいいですね。たぶん。

    >> ②なぜカウントアップされないのか、キャッシュが働いてしまうとはどういう状態なのかがわからないのです。

    これはAccess(DBエンジン)側で高速に処理するための仕様(工夫)だと思います。
    関数に渡されるパラメータがいつも同じならば、最初に返した値を2回目以降も無条件で返すことで高速化しようとしているのでしょう。何度も関数を実行する必要はないだろうという判断ですね。キャッシュ(メモリ)に最初に返した値を保存しておき、それをそのまま2回目以降は渡せば速いですから。
    なので、わざとレコードの情報を渡すことで、関数を強制的に実行させるわけです。そこでカウンタ変数が1つずつアップするということですね。


    Shigeru Murai / SIMOZ

    2015年1月30日 3:17
  • Noを見逃していました。
    "["は、名前の間に空白がある場合や、名前が予約語だった場合に予約語として扱いたくない場合に使用します。
    ここで、Noは予約語ですので、[]で括る必要があります。
    フィールド名に予約語と同じ名前を使うのは、できれば避けた方が無難です。

    ちなみに、T_ExportのNoはオートナンバー型ではまずいのでしょうか? オートナンバー型であれば勝手に連番を振ってくれます。それとも、T_Exportでは既存のレコードの削除も発生するために、オートナンバー型ではまずいのでしょうか? しかし、この場合でもNoは先頭レコードからの順位を表さなくなってしまいます。ただ、削除が直近の追加のやり直しの場合にだけ発生するのであれば、オートナンバー型ではなく、現在されているような連番の振り方は有効ですが・・・

    (参考)
    Access 2002 およびそれ以降のバージョンの Access の予約語の一覧
    http://support.microsoft.com/kb/286335/ja


    ★良い回答には回答済みマークを付けよう! MVP - .NET  http://d.hatena.ne.jp/trapemiya/

    2015年1月30日 4:29
    モデレータ
  • いろいろと調べていただき、ありがとうございます。

    予約語への配慮が足りず、危ないワードもフィールドに使っておりました。

    以後、もう少し安全な設定をこころがけるようにいたします。

    GetSEQについての解説を丁寧にしていただき、ありがとうございました。

    参照させることでキャッシュへの読み書きの回数を減らすことができる、というイメージで受け取りましたが。。理解が怪しくてすみません。

    また、オートナンバーへのご提案につきましては、おっしゃるとおり、有効だと思います。

    そこまでこだわる必要のないところだったのですが、今後のバリエーションを考えて連番を振る方法をがんばってしまいました。

    大変お手数をおかけいたしました。おかげさまでGetSEQでの連番も試してみたところうまく処理できましたし、バリエーションがまた増えました。

    いろいろと当初の質問以外の部分まで教えていただき、ありがとうございました。

    2015年1月30日 5:49
  • 解決されたようですが、少し気になりましたので確認させて下さい。

    連番を振る元になっているのは、挿入元のテーブルのレコードですが、この方法だと挿入先のテーブルに既にあるレコードの番号と被ることがありませんか? 特にアプリケーションを再起動すると連番がまた1から始まりますが、その辺りは大丈夫でしょうか?それとも1から始まらないように挿入先のテーブルの情報を読んで連番の開始値をセットするロジックを入れられていますか?
    一般的には挿入先のテーブルのレコードの情報を元に連番を決定しなければなりません。なぜなら、挿入先のテーブルにおいて連番になっている必要があるからです。

    >おっしゃるとおり、他のテーブルのレコードを、T_Exportテーブルに既にあるレコードと同じでなければ追加したいのです。

    と書かれていたので、今、現在どのようなSQLを書かれているのか気になったのですが、とりあえずざくっとですが、以下のSQLを書いてみました。動作を確認しています。

    INSERT INTO テーブル2 (ID, 名前)
    SELECT
        (select nz(max(ID),0) from テーブル2) +
            (select count(*)
             from (select max(t1.id) as id, t1.名前
                   from テーブル1 AS t1 left join テーブル2 as t2 on t1.名前 = t2.名前
                   where t2.ID is null group by t1.名前) t0
             where t0.id <= t.id ) as 連番,
         t.名前
    FROM (SELECT max(t1.id) as id, t1.名前
         FROM テーブル1 AS t1 LEFT JOIN テーブル2 AS t2 ON t1.名前 = t2.名前
         WHERE t2.ID is null group by t1.名前)  AS t;
    

    #SQL Serverとかですともっと簡潔にSQLが書けるのですが、Accessだとこれが限界かもしれません。


    ★良い回答には回答済みマークを付けよう! MVP - .NET  http://d.hatena.ne.jp/trapemiya/

    2015年1月30日 8:08
    モデレータ