トップページ⇒tech
81コメント47KB

なぜRDBはあんなに難しいのか?

■ このスレッドは過去ログ倉庫に格納されています
0001デフォルトの名無しさんNGNG
なぜRDBはあんなに難しいのか?

RDBを使ったプログラムを開発してます。
テーブルの設計者がおかしなテーブルを作ってくれたせいで大変です。

たとえば
テーブルAに二つの外部キーがあり
それぞれさしているものはテーブルBの同じレコードです。
本人いわくどちらを使ってもいいようにと気を使ってくれたようです。
だからテーブルAのレコードを更新するときは
二つの外部キーの同期を考えてプログラムを作っています。

ほかにも
テーブルAのレコードを更新したら
テーブルBのレコードも更新しないと整合性が保たれない
というのもあります。

作り直してくれと頼んだのですが
論理的に間違いはないと一点張りです。

かく言うわたしもRDBの分析設計(正規化)は
素人同然です。(がこのテーブル設計者よりはマシです)

こいつに理にかなった説明をして作り直させるため
分析設計正規化について勉強したいのですが
よい参考書としてどのようなものがあるのでしょうか?

当方、 「なぜC++はあんなに難しいのか? 」立てたものです。
003130NGNG
>GID2 は階層的なをコードです。
すみません。この部分ですか?

よく見るとデータが
PARENT_GID:GID2=1:1
ではないんですね(汗

でも、そーするとこのGID2って...?
0032デフォルトの名無しさんNGNG
GID2ってのは要するにただのコメントなんでしょ?
プログラマはGIDこそキーだと思ってクエリー書いて一週間に一回GID2を
更新するバッチ書くってのはどう?
003330NGNG
うーーん...(藁
00341ですNGNG
1です。

GID をキーとするアプリと
GID2 をキーとするアプリがあります。
これから開発するアプリは GID2 をキーにすることを推奨しています。
GID は既存のアプリで使われているので残してあります。

GID2 は そのフィールド値だけで自分のレコードの階層位置がわかる
絶対パスみたいなものです。
テーブル設計者この仕組みを考え出したことに誇りを感じているので
ねじ伏せるのは用意ではありません。
00351ですNGNG
1です。

>これから開発するアプリは GID2 をキーにすることを推奨しています。
推奨というか GID2 をキーにしろといわれています。
GID2 は値が変動するキーです。この考え自体がへん。

ソースコードはエライ複雑になってます。
わかりにくい。
0036デフォルトの名無しさんNGNG
確かに一目で階層がわかるのはすばらしいアイデアです。
早速すべてのアプリをGID2をキーにして作り始めましょう。

ただGID2は値が変わることがあるので、その場合には既存のすべての
テーブルやBCPファイルででGID2を参照してるものを調べて更新する必要
があります。欠点と言えばそれくらいでしょうか。
003730NGNG
>>28を訂正

■ GROUP_TABLE
GID ---PK,REF
GID2
GNAME
PARENT_GID

■ USER_TABLE
UID ---PK
UNAME
GID ---FOR

GROUP_TABLE.GID2を変更してもUSER_TABLEに影響がないように。
USER_TABLE.GID2を参照してるPGMは修正が必要ですけど。
0038デフォルトの名無しさんNGNG
全部大文字なのがいやだ
0039名無しさんi486NGNG
>>1 やっぱりおかしいと思ったら全員で説得すべき。
説得できないのであれば、自分も賛同したということ。PGならPGなりに
プライドを持ってがんばれ。設計した奴がえらいとは限らん。
0040デフォルトの名無しさんNGNG
つーか、将来のことも考えて辞めちまえ
00411ですNGNG
>>40

1です。

転職2回目です。
前の会社も今の会社も結構外面はよく
確かな技術力と信頼性で評価されてきた会社です。
名前もみんなが知っています。
でも実際の仕事を見ているとどちらも丁寧ではありませんでした。
店頭なんかで見るソフトなども携わったが設計はひどかった。
簡単にシステムを停止できるようなものまでいろいろと。
0042くどいがNGNG
全部大文字なのがいやだ
0043名無しさんi486NGNG
>>42 なぜ? Cじゃないんだからさ。まぁ使い分けた方が見やすいが。
0044デフォルトの名無しさんNGNG
言葉を正しく使わないと議論が混乱するいい例。
FKがある(設定する)のはテーブルBですね。参照される側の
テーブルAの方にFKがあるとは言いません。

>二つの外部キーの同期を考えてプログラムを作っています。
同期させるのは、あるFK(外部キー)とあるFKの間ではないですね。

*

テーブル設計としては、USER_TABLEにGID2があるのが冗長という
点を除けば、全くおかしくありません。(計算値であるGID2を
GROUP_TABLEに入れるべきではない、という視点もありますが)

再帰構造の実現は、GROUP_TABLEのようなやり方が普通です。

GID2をUSER_TABLEに入れたのは、アプリケーションのアクセス
状況や、レコード数などを見なければなんとも言えませんね。
ただ、Join無しにGID2を条件として、USER_TABLEを検索できるよう
設計しているのだ、と言えば、誰もそれが誤りであるとは言えないでしょう。

*

では、USER_TABLE.GID2をどうやってGROUP_TABLE.GID2と同期
させればいいのか。
トリガーがあるRDBMSであれば、GROUP_TABLEのトリガーとして、
USER_TABLEを更新するようにします。
# これ試してないので、何かの制限にひっかかるかも

あるいは、USER_TABLEに対するアクセスをストアードプロシージャ
化します(add_user_table()とか)。

どのようなクライアントアプリケーションからのデータ変更に対しても、
データベース全体の整合性が自動的に保たれ状態に出来るという
意味では、トリガーで実装するのが一番キレイだと思います。
0045デフォルトの名無しさんNGNG
なお、データベース理論的に正しいテーブルは、30さんのフォーマット
を借りれば、以下のようになります(外部キーはFKとしてあります)。

■ GROUP_TABLE
GID ---PK,REF
GNAME
PARENT_GID

■ USER_TABLE
UID ---PK
UNAME
GID ---FK

計算値(導出項目)であるGID2がGROUP_TABLEにあってはなりません。
ただし、データベースへのアクセスを容易にするために、計算値を
テーブルの項目にキャッシュしておくのは非正規化同様良くやる
手法です。
0046デフォルトの名無しさんNGNG
うん、別に悪くないテーブルじゃない?

GID2の整合性ととるのもトリガ用意するかストアド経由で
更新するようにすれば手間じゃない気が。
004746NGNG
うへぇ、外出だ
0048いつでもどこでも名無しさんNGNG
あの直接RDBとは関係無いのですが
テーブルやフィールドに名前を付けるのを
英語で記述して行こうと思っているのですが
辞書等で調べてもなかなかマッチしません
何か良い書籍やサイトが有れば教えていただけませんか?
0049デフォルトの名無しさんNGNG
excite翻訳
0050デフォルトの名無しさんNGNG
付けた英単語を見て、万人がその内容を想像出来るかどうか、
という問題はおいときます。

私が良くやる方法は、まず対象カテゴリで頻出の英単語(その
カテゴリ名ズバリが分かればそのほうがなお良い)で、英語圏
のサイトをいくつか検索します。
で、そのカテゴリや概念の説明ページを探し、そのカテゴリに
対する正しい英単語を調べます。

ただし、最初に言ったように、正しい英単語を使ったからと言って、
それを見る人が理解できなければなにもなりません。

カッコ悪いですが、基本英単語であればそれを使い、その他の
難しい英単語や専門分野の単語は、ローマ字で命名するのが
良いと思います。

# ただこの場合も、'si'や'shi'、'cha'や'cya'など人によって違いがある
# ので、「ヘボン式で命名する」などの規約を作っておくのが吉。
0051デフォルトの名無しさんNGNG
>>49
goo( http://www.goo.ne.jp/ )のEXCEEDもある。

でも、個人的には、yield_to_maturityなんてのよりも、manki_rimawari
のほうが好き。
005224NGNG
>うん、別に悪くないテーブルじゃない?

本気でそう思うんですか? Aを親とするレコードBが、Cを親にするように変更
されたら、Bの子孫レコードすべてとそのレコードのGID2を参照するすべての
レコードが更新されるんですよ。もし階層構造の変更が頻繁に行われるので
あれば、デッドロックの嵐でしょうね。

個人的にはGID2とやらは、何の役にも立たないだろう、と思います。
0053デフォルトの名無しさんNGNG
>>50 うちも簡単な英単語と、ローマ字の組み合わせでやっていますね。
プロジェクトでルールを決めてやっています。(当り前だと思うけど)
005446NGNG
>もし階層構造の変更が頻繁に行われるので
>あれば、デッドロックの嵐でしょうね。

本当か?

>個人的にはGID2とやらは、何の役にも立たないだろう、と思います。

使わなければ役に立たない。
使おうと思えば使える。
再帰的に探すよりは多分早い。
0055デフォルトの名無しさんNGNG
>>52
>本気でそう思うんですか? Aを親とするレコードBが、Cを親にするように変更
>されたら、Bの子孫レコードすべてとそのレコードのGID2を参照するすべての
>レコードが更新されるんですよ。もし階層構造の変更が頻繁に行われるので

だから、それはアプリケーションからのアクセス要件によりますって。
頻繁に、「select * from group table wher gid2 like '01%'」みたいな
検索を必要とするなら、レコードに変更があった時に、gid2を再構築
するのはそれほど悪い戦略では無いと思いますよ。
逆にそのような検索がめったに無いのであれば、get_gid2()みたいな
ストアードファンクション(プロシージャ)を用意しておけば良いだけの
話でしょ?

第三正規系として正しいかどうか、というのであれば、件のテーブル
設計は正しくないけれども、だからといって、アプリケーションからの
アクセス要件無しに、その設計が間違っているかどうかは判断出来ない
のでは?

> あれば、デッドロックの嵐でしょうね。

デッドロックにはなりませんよ。

> 個人的にはGID2とやらは、何の役にも立たないだろう、と思います。

GID2無しに( >>22 参照)、「G95088以下に属するUSERの中から
○○という条件に合致するものを検索する」みたいな検索は簡単
には出来ませんよね?

GID2があれば、
select * from user_table where gid2 like '0101%' and more_cond
って書けるでしょ?
005655NGNG
あ、外出だった(ワラ
005755NGNG
それに、GID2の再構築はそれほど大変じゃ無いよ。

■ GROUP_TABLE
GID    GID2   GNAME PARENT_GID
---------------------------------
G94001 01     A     NULL
G95088 0101   A/A   G94001
G98001 0102   A/B   G94001
G98004 010201 A/B/A G98001
G99022 010202 A/B/B G98001
G94011 0103   A/C   G94001
G00023 02     B     NULL
G00007 0201   B/A   G00023

例えばG98001をG94001(GID2=01)の下からG00023(GID2=02)の下に移動する場合、
GID    GID2   GNAME PARENT_GID
---------------------------------
G98001 0102   A/B   G94001
G98004 010201 A/B/A G98001
G99022 010202 A/B/B G98001

のGID2の先頭2文字を'02'に変更できればOK。GNAMEはこの際置いとく。
一度のUpdate文で出来るかどうかはわかりませんが。
005824NGNG
うーむ、確かに再帰的な検索があるときには便利ですね。
それは認めます。

>デッドロックにはなりませんよ。

そうですか? 階層構造の変更が行われるときには当然1トランザク
ション内で更新される必要があるから、関連レコードすべてがロック
されますよね? で、その中の複数レコードを更新する別トランザク
ションがあったらデッドロックになりませんか?

よく考えてみると、GROUPテーブルのつくりはそのまま(もしくは
親子関係を表現するテーブルを別に作る)で、GROUPを参照する
テーブルのほうがGIDのみでアクセスすればすむんじゃないですか?
005946NGNG
>>58それはデッドロックとは言わないと思う。

>よく考えてみると、GROUPテーブルのつくりはそのまま(もしくは
>親子関係を表現するテーブルを別に作る)で、GROUPを参照する
>テーブルのほうがGIDのみでアクセスすればすむんじゃないですか?

作った人じゃないから知らんけど、joinするよりしない方が速いから
そうしたんじゃないの?
006055NGNG
>>58
> 階層構造の変更が行われるときには当然1トランザク
>ション内で更新される必要があるから、関連レコードすべてがロック
>されますよね?
それはそのとおり。

> で、その中の複数レコードを更新する別トランザク
>ションがあったらデッドロックになりませんか?
それは、更新の待ち行列に入るだけで、デッドロックとは言いません。

>よく考えてみると、GROUPテーブルのつくりはそのまま(もしくは
>親子関係を表現するテーブルを別に作る)で、

今回の例の場合は、GROUP_TABLE内においては、親が1レコード
しかないので(親:子=1:n)、複数テーブルに分ける必要は無いと思います。

>GROUPを参照する
>テーブルのほうがGIDのみでアクセスすればすむんじゃないですか?

だーかーらー、パフォーマンスの関係で、ジョイン無しに
USER_TABLEだけからツリーを引っ張り出すことをしたいし、
そのようなことが良くある、みたいな状況だと、
USER_TABLEにGID2を入れる戦略もアリだって。

そーゆーこと無しに、その設計が正しいのかどうかなんて議論は
出来ませんって。
そーゆーこと無しに判断するなら、今回のテーブル設計は、それ
ほど間違いは無い、ってことしか言えないんじゃない?
006155NGNG
なお、私個人としては、今回のGID2のような
「再帰構造を線形構造にする」みたいな考え方は、
RDBにはなじまない、と思っています。

でも、これが無いと、エラク検索が大変になりそう。
006224NGNG
>それは、更新の待ち行列に入るだけで、デッドロックとは言いません。

だから「複数レコードを更新する別トランザクション」って書いて
いるでしょう。もちろん必ずおきるわけではありませんが、1回の
トランザクションでロックするレコードの数が増えれば当然デッド
ロックの危険性は高くなりますよね?

>回の例の場合は、GROUP_TABLE内においては、親が1レコード
>かないので(親:子=1:n)、複数テーブルに分ける必要は無いと思います。

まあ確かにそれほど必要ではないですね。ただGROUPテーブルの大きさ
や更新の頻度によっては、更新時のページ分割やレコード転送などが
無視できないかな、と思ったので。

>だーかーらー、パフォーマンスの関係で、ジョイン無しに
>USER_TABLEだけからツリーを引っ張り出すことをしたいし、
>そのようなことが良くある、みたいな状況だと、
>USER_TABLEにGID2を入れる戦略もアリだって。

うーん、まあそれはそのとおりですね。よっぽどその検索のレスポンス
が要求される場合でない限り、そういう設計はしたくないですが(~~;)
0063デフォルトの名無しさんNGNG
>>62
> トランザクションでロックするレコードの数が増えれば当然デッド
> ロックの危険性は高くなりますよね?

あ、更新の順序が決まっていなくて、しかも、逐次行ロック(とかいう
言葉は多分無いけど、雰囲気分かりますよね)していく場合ですね。

それは、確かにおっしゃるとおりです。
あまり、そのようなことしないので、気づきませんでした。

# ところで、1さんのRDBMSは何を使ってるんでしょうね?
0064デフォルトの名無しさんNGNG
>>62
>うーん、まあそれはそのとおりですね。よっぽどその検索のレスポンス
>が要求される場合でない限り、そういう設計はしたくないですが(~~;)

私もあまりしたくありませんが、例えばディレクトリやファイルのモデル
をデータベース化しようとしたときに、「フルパス」という属性は入れる
と思います。get_fullpath()ってありがちでしょ?
これを、ルートディレクトリまで再帰的にたどっていかなきゃ分からない
としたら、それはそれでツライかも。
006563NGNG
やっぱり、前言撤回。
fullpathという属性は入れません。
get_fullpath()というストアードファンクションで実装かな・・・
006648NGNG


>50
>付けた英単語を見て、万人がその内容を想像出来るかどうか、
>という問題はおいときます。

>私が良くやる方法は、まず対象カテゴリで頻出の英単語(その
>カテゴリ名ズバリが分かればそのほうがなお良い)で、英語圏
>のサイトをいくつか検索します。
>で、そのカテゴリや概念の説明ページを探し、そのカテゴリに
>対する正しい英単語を調べます。

なるほど確かにプロジェクト全体が把握できるのが最優先ですね

この方法でしたら多少込み入った用語でも
英単語化できそうですね
どちらにしろ英語はできないと厳しそうですが(^^;

>49,51
まとめて
gooとexciteはしってたのですが
やはり込み入った単語が出てこなかったので
そのときほかの人はどうしてるか気になったもので
他にもこの様なサイトが有ります

http://www.alc.co.jp/
0067デフォルトの名無しさんNGNG
>>55
GID2があったって良いけど、それを外部に対するキーにするなという主張だと思うぞ。
GID2の変更をそれを参照している全てのテーブルに反映させるのはなんというか…
つーかGIDとGID2だけを持ったテーブル作れば良いじゃん。
006855NGNG
>GID2があったって良いけど、それを外部に対するキーにするなという主張だと思うぞ。
>GID2の変更をそれを参照している全てのテーブルに反映させるのはなんというか…

まぁ、確かにうざいんだけど、非正規化したことが設計ミスかどうかは、
何をトレードオフにしたかによるので、一概にどうこう言えないと思いますよ。
トリガーが使えれば、同期させるのは自動にできるわけだし。
006924NGNG
>まぁ、確かにうざいんだけど、非正規化したことが設計ミスかどうかは、
>何をトレードオフにしたかによるので、一概にどうこう言えないと思いますよ。

まあ、GID2が夜間バッチで変更されるようなものならそれほど影響は
ないでしょうね。

その辺は1を書いた人に出てきてもらわないとわからんことですが...

>トリガーが使えれば、同期させるのは自動にできるわけだし。

どっちかっていうと更新処理の「手間」よりも、同時に更新される
レコード数が爆発的に多くなる可能性があることが問題なので、これ
はあまり説得力がないと思います。
0070デフォルトの名無しさんNGNG
おーい、1さんよぉ〜
出てきとくれ
0071デフォルトの名無しさんNGNG
正規化の意味が分かってるのか?
GID2を主キーに使うというのは非正規化じゃないぞ。変更されるものが
なんで主キーなんだ?
#まあ言葉の定義なんぞ人によって違うからそれを非正規化といっても良いけど

この場合の非正規化とはGID2を別テーブルにせず、GIDとGID2が2つとも
(只の)キーとして1つのテーブルに存在することであって、主キーとしてGID2を
選択するのはただの間違い。
0072デフォルトの名無しさんNGNG
っていうかさ、こういうテクニックは効果的だけど
使用ルールを徹底してほしいよ。

007355NGNG
>>71
>GID2を主キーに使うというのは非正規化じゃないぞ。
ではなくて、GID2をGROUP_TABLEに持つことを非正規化と言っています。
多分、第2正規形に違反(というのか?)していると思います。
また、そのコピーをUSER_TABLEに持つのも、非正規化です。

「主キー」というのは、1さんが主キーというものの意味を誤解している
ために出てきた言葉だと思いますよ。
おそらく、実際のテーブルは、>>37のような構成になっていると思います。
constraint primary key pk_group_table (gid, gid2)
だったら、痛すぎですけど。
0074デフォルトの名無しさんNGNG
>>71
>#まあ言葉の定義なんぞ人によって違うからそれを非正規化といっても良いけど

正規化したものを崩すことが「非正規化」と言うのでは?
正規化する前のものを「非正規化」とは言わない。
それは、「正規化していない」と言うのが正しい。
・・・と私は思う。
0075>65NGNG
蒸し返すようだけど・・・
>get_fullpath()というストアードファンクションで実装かな・・・

DBMSによるけど、複数レコードが返ってくる事を考えるとめんどくさくない?
007675NGNG
スマn、言葉足らず

多分

select gid1 from group_table where get_fullpath(gid1) like '%01%'

とか使うつもりでget_fullpath(gid1)って使うんだろうけど、
複数レコードが欲しいときが多ければ

get_fullpath('%01%')

みたいな使い方の方が便利そうだけど・・なぁ・・・
って意見
0077デフォルトの名無しさんNGNG
      ,一-、
     / ̄ l |   / ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
    ■■-っ < んなーこたーない
    ´∀`/    \__________
   __/|Y/\.
 Ё|__ | /  |
     | У..  |

0078デフォルトの名無しさんNGNG
ある程度のシステムなら正規化原理主義でいくべきかもしれない。
最近思うな。
0079デフォルトの名無しさんNGNG
その、ある程度ってのがむずかしくない?

最大1万件って話だったのに、運用してみたら10万件になったり
とか
0080デフォルトの名無しさんNGNG
数十万件くらいなら、どうでもいいでしょ。
数百万件くらいから、真剣にテーブルを考えましょう。
0081NGNG
NG
■ このスレッドは過去ログ倉庫に格納されています