• ベストアンサー

請求書等のシリアル番号生成方法?

請求書(E-mai本文用)等に使用するシリアル番号の生成の仕方を知りたいのですが、どうすればよいのでしょうか? 「マクロ秒とかを使用するのかな?」とも思いますが。 できれば、きっかけとなる関数や知識などをお教え下さい。 簡単なコードなどでも結構です。 シリアル番号を連番で出す場合とランダム出す場合では、方法が違うように思いますが。一様、両方の方法を試してみたいです。 よろしくお願いします。

質問者が選んだベストアンサー

  • ベストアンサー
  • galluda
  • ベストアンサー率35% (440/1242)
回答No.5

がるです。 んと…大して高度な事をやっているわけでもないのですが(苦笑 文字列が長くなってもよいのであれば、例えば、こんなコードがありえます。 function tokenizer() { // 秒&マイクロ秒を取得 list($usec, $sec) = explode(" ", microtime()); // ここはデフォルト // エポック秒:まぁ全部はいらんのでちょっと小さめに $tokens[] = $sec - 1079340000; // マイクロ秒 $tokens[] = (int)($usec * 1000 * 1000); // プロセスID $tokens[] = getmypid(); // 乱数 $tokens[] = mt_rand(); // return implode('-', $tokens); } 参考にでもなれば幸いです。

kitty1000
質問者

補足

galludaさん ご回答、ありがとうございました。 >大して高度な事をやっているわけでもないのですが 高度でなくて結構です。高度だとこちらが理解できません。 >文字列が長くなってもよいのであれば、例えば、こんなコードがあり >えます。 了解しました。ただし、こちらの必要な文字列は数字6桁です。 上記のgalludaさんのコードを改良しようとも思いましたが、番号の衝突する確率を上げずに出力する乱数の桁数を6桁にするためには、どこをいじればよいのか分かりません。どこをいじれば、番号の衝突する確率を上げずに出力する乱数の桁数を数字6桁に整えられるのでしょうか? やはり、6桁にするには他のコードが必要ですかね? よろしくお願いします。

その他の回答 (5)

  • sak39
  • ベストアンサー率0% (0/1)
回答No.6

6桁の数字と言う制限がどこからやってくる物なのか分かりませんが、エポック秒+マイクロ秒を5年間の間を概算すると5*365.25*24*60*60*1,000,000≒1.5×10の14乗となり、10進数で15桁(例えばミリ秒単位まで切下げても それでも12桁)くらい必要になってしまいます。これは、5年間でまた似たような値にサイクリックして来る(5年で一周してまた元の値に戻って来る)場合まで値を切り捨てた場合で、時間的には最小限度の必要な値に桁数を抑制したケースでの桁数です。(具体的には、エポック秒を5年間相当の秒数で割った時の余りを値として使用する) 仮に6桁の数字(10進数)と言う制限を 数字だけからアルファベットA~Zの26文字も含んで良い事としても、6桁の制限でアルファベット数字交じりとしても、36の6乗=2,176,782,336 (10桁)となり、15桁の値には遠く及びません。(つまり エポック秒+マイクロ秒をアルファベット数字交じり6桁に変換しようと思っても、桁数が少なすぎて1対1変換ができない) 時間的桁数だけで15桁(もしくは12桁)も要する手法なため、これは不可能と言う事になります。 (これは前の回答にも示した内容では有りますが…、)ご質問にある乱数の手法でも一回一回の値の振り出し時に常に一定の直前との値の重複(衝突)の確立を持ち、且つ 長期に渡って使用し続ければその衝突の確立は結果的に無視できない大きさになって来ると思われます。 時間的要素として、エポック秒+マイクロ秒に加え、衝突を防ぐ為の組み合わせを行う他の数字(つまり接続されコールされる事で発生するプロセスIDとか 接続元IPアドレスの様なもの)も、たまたま時間的に同時に重なって別端末(PC)からコールされた物でも違う物として判別できるようにする工夫として使えるのですが、更に桁数を必要とする事になります。 また、ご質問のケースの必要性から単純に同時接続している端末の端末接続順番の番号の様な単純で小さい値が実際 欲しいだけなのですが、この端末のIDの様な値は、端末の固有IDの様なもので、値の桁数は必要以上に大きい物です。 回答側で出している時間的な違いを利用して一意の請求番号を振り出す手法(エポック秒などを使う手法)は、簡単にIDを振り出す効果的な手法では有ります。 しかし、残念ながら、振り出し番号の衝突をDBで確認して別番号を振らないのであれば、衝突の危険性が全くない様にして、5年間の長期に渡って6桁に圧縮する術は無いと思われます。

kitty1000
質問者

補足

sak39さん ご回答、ありがとうございました。 了解しました。

  • sak39
  • ベストアンサー率0% (0/1)
回答No.4

ANo.3 galludaさんの回答が、妥当だと思います。 乱数での解法が妥当かどうかのご質問が有ったので意見を述べさせていただきます。 PHPのプログラマでないので、合っているか分かりませんのでご参考までですが、 mt_srand((double)microtime()*1000000); とやるとmicrotime()が返すスペース文字以降の秒数部分を無視し、マイクロタイムのみを演算対象として乱数の初期化をする事になると思います。 mt_randの取得乱数列でぶつからないとしても、同じマイクロ秒になって乱数の初期化自体が同じになってしまう5年間の間の確立は、m枚/月の請求書発行時で、平均0.00006×m回程度(5年*12ヶ月*月m回/1,000,000)となり、運が悪ければ衝突が発生する事になります。 確実に重ならないようにしたいのであれば、もう少し複雑な方法が必要だと思われます。 マイクロ秒単体では、重ならない保障は出来ません。また、日時だけでも、もちろん駄目ですし、接続により発生するIDの様な物についても同様にそれ単体では、同じIDがまた何時発生するか分かりません。それらを組み合わせた時点で初めて一意の値を作り出せます。 「いや、衝突すればエラーになるよ。 再度やり直せばOK。 また衝突発生率は、非常に低い。」等と言う事で十分であれば、その発生頻度の許容範囲を考える必要性があると思います。 請求書発行済み番号がDBに登録されているなら、それを検索して発行済みで有るかどうか確認でき、振り出し請求書番号の衝突時に 再度乱数を取得するのであれば、おっしゃる乱数を単純に使う方法も利用できると思いますが、ご質問の意図を察するに、これは違うとは思います。 また、そもそもDBを使えるなら、排他ロックして番号をシーケンシャルに振る事も容易なのですが…。

kitty1000
質問者

補足

sak39さん ご回答、ありがとうございました。 了解しました。 >確実に重ならないようにしたいのであれば、もう少し複雑な方法が必 >要だと思われます。 galludaさんのご説明はなんとなく分かるのですが、わたしには難しすぎてコーディングができません。 galludaさんのご説明されている方法ほど完璧でなくとも、私のコードよりは格段に衝突の起こりにくいコーディングを教えていただけるでしょうか(私のを改良する形でも結構です。)? galludaさんへの補足にも書きましたが、今回はデータベースは使用しません。フォームの入力内容に基づきPHPで生成した請求書メール(請求書番号付き)を、フォーム入力者へ送信するのみです。 よろしくお願いします。

  • galluda
  • ベストアンサー率35% (440/1242)
回答No.3

がると申します。 連番の場合、適切にファイルロックなりトランザクションなりを組み合わせていただくとして。 単純に「ユニークな文字列」を作成する場合、私が使ってるロジックは ・エポック秒 ・ミリ秒またはマイクロ秒 ・プロセスID ・(スレッドID ・(localなIPアドレス ・(乱数 をつなげた文字列を使っています(厳密には、上述の数字を、62進数という文字列に変換して使用)。括弧は「必要に応じて」という感じです。 とりあえずこのやり方で、特殊な状態を除いて「確実にユニークな文字列」が取得できます。 なお、ハッシュ関数(MD5など)は、厳密には「コリジョン(衝突)」の可能性があるので、私は勧めません。…まぁ「滅多にない」のですが。 以上私見ですが参考になれば幸いです。

kitty1000
質問者

お礼

galludaさんのご説明はなんとなく分かるのですが、わたしには難しすぎてコーディングができません。 galludaさんのご説明されている方法ほど完璧でなくとも、私のコードよりは格段に衝突の起こりにくいコーディングを教えていただけるでしょうか(私のを改良する形でも結構です。)? 上記補足にも書きましたが、今回はデータベースは使用しません。フォームの入力内容に基づきPHPで生成した請求書メール(請求書番号付き)を、フォーム入力者へ送信するのみです。 よろしくお願いします。

kitty1000
質問者

補足

galludaさん ご回答、ありがとうございました。 理論的にはなんとなく分かるのですが、どのようにコーディングするのか不明です。また、今回はデータベースは使用しません。

  • sak39
  • ベストアンサー率0% (0/1)
回答No.2

書類は5年保存などが基本なので、その範囲で重ならない事を考えると、西暦年の下1桁、月(2桁)、日(2桁)、時(24時制 2桁)、分(2桁)、秒(2桁)、ミリ秒(3桁)を算術演算で組み合わせる方法が考えられると思います。そのまま全部組み合わせれば、14桁になりますが、桁数を抑えるのであれば、(((((年(1桁)×12+月(2桁)-1)×31+日(2桁)-1)×24+時(24時制 2桁))×60+分(2桁))×60+秒(2桁))×1000+ミリ秒(3桁)などで、計算するのも良いかと思います。 今回のご質問では関係ないかもしれませんが、人間の手で番号を割り振る場合は、年月日に001(一日発生する請求件数を下回らない桁数)などその日の連番を加えて割り振る場合が有りますが、請求番号だけで日付が分かり、検索しやすいなどのメリットが有ります。 Windows APIを使えるなら、GetLocalTime関数でSYSTEMTIME構造体に現在日時をゲットしてミリセカンドを取得すれば最後のミリ秒3桁に使えると思います。 ミリ秒でも重なりの可能性が有るなら、乱数を更に計算桁に加える方法も有ると思います。

kitty1000
質問者

お礼

下記の乱数で重なりの可能性は、あるのでしょうか? mt_srand((double)microtime()*1000000); $example = mt_rand(100000, 999999); ある場合は、連番でもランダムでも結構ですから、数字が重ならないようにするには、どうすればよいでしょう。 できるだけ単純な方法をお願いします。

kitty1000
質問者

補足

sak39さん ご回答、ありがとうございました。 下記のような単純なものを考えていたのですが、問題ありますか? mt_srand((double)microtime()*1000000); $example = mt_rand(100000, 999999); 数字が6桁で出てきます。 よろしくお願いします。

  • yambejp
  • ベストアンサー率51% (3827/7415)
回答No.1

適当なキーワード+連番でidつくり、MD5などでハッシュするとか いろいろあるでしょうけど、履歴性などをかんがえるなら あらかじめRDBに連番とシリアル対比表になるデータをつくっておいて 必要に応じて払い出してもらうのが妥当かと。

kitty1000
質問者

補足

yambejpさん ご回答、ありがとうございました。 今回はデータベースを使用しない単純な方法を模索しています。 連番でなくて、ランダムな値で結構ですが、一つ一つの番号が違うようにしたいです。 よろしくお願いします。

関連するQ&A