質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.77%

SQL テーブルを作るか?カラムに入れるか?

解決済

回答 4

投稿

  • 評価
  • クリップ 2
  • VIEW 711

知りたいこと

ユーザーの閲覧履歴をどこに保存すべきかで悩んでいます。

・履歴は10件まで
・当人だけが見れるページに表示するときにしか取得しない

という設計なので、全ユーザーの履歴を保存したテーブルを作るべきか、それとも当人のレコードの1つのカラムに全てを入れるか、どちらにすべきか決められません。

自分が考えらえるメリットとデメリットを以下に書いていきますので、あっているところや間違っているところなど、そしてどうすべきかにについてアドバイス頂けませんでしょうか。

全ユーザーの履歴を保存したテーブルを作る

テーブルを作るとしたら、すでにあるcontents(コンテンツのテーブル)に加えて、histories(閲覧履歴のテーブル)を作ることになります。

▼ contents(コンテンツのテーブル)

content_id val
1 val1
2 val2
3 val3
4 val4
5 val5
6 val6

▼ histories(閲覧履歴のテーブル)

history_id user_id target_content_id visit_date
1 3 6 2020-01-05
2 1 3 2020-01-04
3 3 2 2020-01-03
4 2 8 2020-01-02
5 2 4 2020-01-01

このメリットは、SQLでINNER JOINをかければ次のように閲覧履歴が簡単に取得できる点です。

/*(例)user_id=3の閲覧履歴はこのように簡単に取得できる*/
select
  c.content_id,
  c.val,
  h.visit_date
from contents c
inner join histories h on
h.target_content_id = c.content_id
where
  h.user_id=3

ですが、デメリットとしては、データの保存が煩雑そうに思える点です。

例えば保存する前にまず当人のレコードを数えて、それが10件以内ならそのまま保存し、10件以上なら一番古いレコードを削除してから保存する。という処理が煩雑に思えます。

当人のレコードの1つのカラムに全てを入れる

上記に比べて当人のレコードの1つのカラムに全てを入れることもできると思います。

users(ユーザーのテーブル)のhistoriesに入れるということです。

▼ users(ユーザーのテーブル)

content_id name his_histories
1 aくん "[{"target_content_id":3,"date":"2020-01-04"}]"
2 bくん "[{"target_content_id":4,"date":"2020-01-01"},{"target_content_id":8,"date":"2020-01-02"}]"
3 cくん "[{"target_content_id":6,"date":"2020-01-05"},{"target_content_id":2,"date":"2020-01-03"}]"

こちらはSQLでINNER JOINとはいかず、まずhis_historiesを取得してそのIDを配列にしてカンマ区切りの文字列(2,3,4,6,8...)にして、SQLでcontents(コンテンツのテーブル)を対象にINで検索するという流れになると思うので、取得が煩雑そうなのがデメリットかと思います。

ですが、メリットとしてはデータの保存が楽そうで、his_historiesを取得したらPHPで10件かどうか数えて処理新しい履歴と合体した結果をhis_historiesに入れなおせばいいので、SQLの実行はそのINSERTの1回で済みそうに思えます。

....

長くなってしまってすみません。
このように考えていて、全ユーザーの履歴を保存したテーブルを作るべきか、それとも当人のレコードの1つのカラムに全てを入れるか、なかなか決めることができずにいます。

こういったケースではどのようにデータを保存すべきなのでしょうか。

アドバイス宜しくお願い致します。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 4

+2

自分なら他の回答にあるように追加は無制限に行って参照時に上位10件だけ取得する方法を取るだろうなと思います。
消してしまった情報は二度と見れないので、安全側に倒すなら全部とっておくという意味で。

ただその履歴が更新される頻度やデータ量、または参照する頻度の問題で、DBへのアクセスがボトルネックになる事が事前にテストしてわかっているのであれば、参照用の別テーブルやDBを分けておくなり、割り切ってどうにか10件に調整するようにバッチやトリガで削除するなり、色んなメンテナンス方法は考えられるでしょうけど。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/03/24 14:43

    テストなどまったくしておらずとりあえず質問した状態でした。アドバイスありがとうございました。

    キャンセル

+1

メリット/デメリットについては、一度SQLアンチパターンに目を通されることをお薦めします。

敢えてアンチパターンを採用する場合も個人的にはあると思いますが、こういった質問をされるレベルであれば、正規化に則り、アンチパターンは回避された方が良いかと思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/03/24 14:44

    初心者なのでアンチパターン回避でいきます。ありがとうございます。

    キャンセル

checkベストアンサー

0

例えば保存する前にまず当人のレコードを数えて、それが10件以内ならそのまま保存し、10件以上なら一番古いレコードを削除してから保存する。という処理が煩雑に思えます。 

取得の時に上位10件だけとるという風にしているのなら、別に「削除してから」は不要かと思います。
履歴であれば何かしらの時に運営側が参照して調査に使うことがあるかもしれませんし、残しておいて差し支えはないと思います。

「どうしても削除したい」というのでしたら日次か週次か月次かのタイミングで上位10件以外を削除するようなバッチ処理を置いておけば良いと思います。
取っておきたいなら別テーブルに移動とかファイルに落とし込んでおくとか言うことも考えられますし。
あくまで「参照するのは10件だけ」という形にしておくのが無難かと思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/03/24 14:34

    たしかにわざわざ消す必要がなかったですね。貴重なアドバイスをありがとうございました。

    キャンセル

0

特定のcontent_idに対して、target_content_idやdateが不定数だったり
するならJSON型で持つメリットはありますが、見た感じそうでもなさそうです。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/03/23 22:22

    mysqlのようにカラムがJSON型で保持できるなら多少メリットは
    でてきますが、そうでなくなにか文字列型のカラムにJSONを
    流し込むだけでは検索性がよくないのでやめたほうがいいでしょう

    キャンセル

  • 2020/03/24 14:43

    いまいちJSONにすべきシーンが理解できていないので、みなさん仰るようにテーブルをつくりカラムに入れるのはやめようと思います。ありがとうございます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.77%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る