20200922のMySQLに関する記事は8件です。

Docker x Laravel phpMyAdmin を導入する

以前の記事(最強のLaravel開発環境をDockerを使って構築する【新編集版】)のコメントにてphpMyAdminを使用したいという要望がありましたので補足の記事を作成させていただきます。

手順

https://github.com/ucan-lab/docker-laravel

docker-compose.yml に phpMyAdmin のサービスを追加

docker-compose.yml
volumes:
  # 追記
  pma-session-store:

services:
  # 追記
  pma:
    image: phpmyadmin/phpmyadmin:5.0.2
    environment:
      - PMA_HOST=db
      - PMA_USER=root
      - PMA_PASSWORD=secret
    ports:
      - 8080:80
    volumes:
      - pma-session-store:/sessions

Dockerコンテナの構築

$ docker-compose up -d

phpMyAdmin へアクセス

http://127.0.0.1:8080

上記のURLへアクセスし、phpMyAdminの画面が表示されればokです。

ScreenShot 2020-09-22 19.11.42.png

ScreenShot 2020-09-22 19.11.46.png

補足

MySQL新認証プラグイン caching_sha2_password

私のdbコンテナの設定でMySQL8.0系で認証方式を新しいcaching_sha2_passwordに設定しています。phpMyAdminは5.0.1以降でないとcaching_sha2_passwordに対応していないのでタグで新しいバージョンを明示的に指定してます。latestが5系を指すようになったら外してもいいかもです。

蛇足

(MySQLのCLIやSequel Ace等のクライアントツールで特に不便がないのでがあるので個人的にはphpMyAdminは入れなくてもいいかなと思ってます)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker x Laravel phpMyAdmin のコンテナを構築する

以前の記事(最強のLaravel開発環境をDockerを使って構築する【新編集版】)のコメントにてphpMyAdminを使用したいという要望がありましたので補足の記事を作成させていただきます。

手順

https://github.com/ucan-lab/docker-laravel

docker-compose.yml に phpMyAdmin のサービスを追加

docker-compose.yml
volumes:
  # 追記
  pma-session-store:

services:
  # 追記
  pma:
    image: phpmyadmin/phpmyadmin:5.0.2
    environment:
      - PMA_HOST=db
      - PMA_USER=root
      - PMA_PASSWORD=secret
    ports:
      - 8080:80
    volumes:
      - pma-session-store:/sessions

Dockerコンテナの構築

$ docker-compose up -d

phpMyAdmin へアクセス

http://127.0.0.1:8080

上記のURLへアクセスし、phpMyAdminの画面が表示されればokです。

ScreenShot 2020-09-22 19.11.42.png

ScreenShot 2020-09-22 19.11.46.png

補足

MySQL新認証プラグイン caching_sha2_password

私のdbコンテナの設定でMySQL8.0系で認証方式を新しいcaching_sha2_passwordに設定しています。phpMyAdminは5.0.1以降でないとcaching_sha2_passwordに対応していないのでタグで新しいバージョンを明示的に指定してます。latestが5系を指すようになったら外してもいいかもです。

蛇足

(MySQLのCLIやSequel Ace等のクライアントツールで特に不便がないのでがあるので個人的にはphpMyAdminは入れなくてもいいかなと思ってます)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails6]devise + paranoia + MySQL8系で実現するユーザー論理削除と一意制約の両立

はじめに

タイトルの環境にて、ユーザーの論理削除および一意制約の両立にかなり苦戦したので
解決方法を軽くまとめます。
deviseの一意制約をオーバーライドして独自の制約を持たせる方法などが出ていましたが
どれも面倒でもっと簡単にできないかと思い調べた結果です。

やりたいこと

先の環境にて(特にMySQL)、deviseデフォルトのユーザー物理削除ではなく論理削除を行い、
かつ退会したユーザーが同じアドレスやIDで再登録できるよう一意制約を担保したい。

PostgreSQL, SQLite

こちら2つのDBでは部分indexを活用することで簡単に実装できるそうです。
・実装記事
・add_index仕様1
・add_index仕様2

この、部分indexをMySQLは採用しておらず簡単に扱えない模様。
(情報が5.7ばかりだから8系では採用しているのか?どちらにしろRailsのmigrateからは無理そう)
軽くアンチMySQLになるかも…

結論(MySQLでの実現方法)

この記事みたらいけると思います。
https://vanhuyz.com/how-to-apply-unique-restriction-with-soft-delete-in-rails/
1. deleted_atカラムを追加後、
2. deleted_atにNullではなくアクティブユーザーにも特定の値をもたせることで、
 2カラムでのUNIQUEを実現し(dleted_atがNULLだと実現しない)
3. シンプルに論理削除と一意制約を両立

最高に優良な記事をどうもありがとう!!

実装

[環境]
- Ruby:2.6.6p146
- Rails:6.0.3.3
- MySQL:8.0.21
- mysql2:0.5.3
- devise:4.7.2
- paranoia:2.4.2

準備

環境構築やdevise, paranoia導入等の基本部分の実装は他に沢山の記事が出ているので割愛します。
devise標準の会員登録・退会機能等が動作する前提です。

1. 設定ファイルparanoia.rbを作る

config/initializers/paranoia.rb
# ユーザー退会後の論理削除・一意制約を両立させるための処理
# 非削除レコードはdeleted_at = '0000-01-01 00:00:00'
# 削除済みレコードはdeleted_at != '0000-01-01 00:00:00'
Paranoia.default_sentinel_value = DateTime.new(0)

2. migrationでのindex変更

デフォルトのUsersテーブルのindexを変更するマイグレーションファイルを作成

rails g Change_Index_To_Users

作成したマイグレーションファイルを以下のように変更

一度デフォルトで作成されたemailインデックスを削除し
新たに[email, deleted_at]でのuniqueインデックスを作成

db/migrate/202009220000000.rb
class ChangeIndexUniuqueToUsers < ActiveRecord::Migration[6.0]
  def change
    remove_index :users, :email
    add_index :users, [:email,:deleted_at], unique: true
  end
end

migration

rails db:migrate

3. Validation追加

アプリケーションレベルでのバリデーションを追加

app/models/user.rb
class User < ActiveRecord::Base
  acts_as_paranoid
  validates :email, uniqueness: { scope: :deleted_at }  

4. 動作確認

railsコンソールもしくはrailsサーバーを立ち上げ実際にユーザー作成、退会、同アドレスでの再登録を実施してDBを確認します。
論理削除_一意制約.PNG

無事同じアドレスでの再登録に成功しました!!
他何かいい方法などあれば是非教えてくださいませ~!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go+MySQL+Dockerで簡単なCRUDを実装する

はじめに

先日、「業務で使う予定ないとはいえGoぐらいある程度知っておいたほうがいいよな...」と思い、Goを使って簡単なCRUDを実装してみたのでそのやり方を備忘録としてまとめておきます。

基本的には以下のサイトの内容を組み合わせて少しアレンジしたものになっています。Goの勉強をする上でこれらのサイトには非常にお世話になったのでこちらもご参考ください。
DockerでGoの開発環境を構築する
Go / Gin で超簡単なWebアプリを作る
Go言語(Golang)入門~MySQL接続編~
docker-compose MySQL8.0 のDBコンテナを作成する
docker-compose upでMySQLが起動するまで待つ方法(2種類紹介)

GoをDockerで立ち上げる

まずはGoをDockerで立ち上げていきます。
作業ディレクトリ直下に
- DockerFile
- docker-compose.yml
- main.go
を作成します

DockerFile
FROM golang:latest

RUN mkdir /app
WORKDIR /app
docker-compose.yml
version: '3'
services:
  go:
    build:
      context: .
      dockerfile: DockerFile
    command: /bin/sh -c "go run main.go"
    stdin_open: true
    tty: true
    volumes:
      - .:/app
main.go
package main

import "fmt"

func main() {
  fmt.Println("Hello, World!")
}

これでdocker-compose upを行うとコンソール上にHello, World!と出てくると思います。
これが出ればまずGoの起動は成功です。

簡単に各ファイルの解説をします。
・DockerFile
Goのコンテナ(仮想環境)を作成します。
ここでWORKDIR /appを指定していることで以降の動作をすべて/app以下で行ってくれます。

・docker-compose.yml
DockerFileで作ったコンテナを立ち上げるときの設定などを書きます。
これにより、DockerFileにあるコンテナを立ち上げてその中でgo run main.goのコマンドを叩いてmain.goを起動します

・main.go
Goに関する処理はここに書いていきます。今回はHello, World!を出力するだけで終了しています。

GoでWebページを作成する

とりあえずGoの起動ができたので次はGoを使ってWebページを作っていきましょう。
今回はGinというフレームワークを使ってみます。
- DockerFileにインストールを追加
- docker-compose.ymlにportsの記述を追加
- main.goの内容を書き換え
- templates/index.htmlを作成
を行ってください。

DockerFile
FROM golang:latest

RUN mkdir /app
WORKDIR /app

RUN go get github.com/gin-gonic/gin
docker-compose.yml
version: '3'
services:
  go:
    build:
      context: .
      dockerfile: DockerFile
    command: /bin/sh -c "go run main.go"
    stdin_open: true
    tty: true
    volumes:
      - .:/app
    ports:
      - 8080:8080
main.go
package main

import (
  "github.com/gin-gonic/gin"
)

func main() {
  router := gin.Default()
  router.LoadHTMLGlob("templates/*.html")

  router.GET("/", func(ctx *gin.Context){
    ctx.HTML(200, "index.html", gin.H{})
  })

  router.Run()
}
templates/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Sample App</title>
</head>
<body>
  <h1>Hello World!!!</h1>
</body>
</html>

これで

docker-compose build
docker-compose up -d

を行ってしばらく待ってから
http://localhost:8080にアクセスするとHello World!と表示されると思います。

今回やったことを解説します。
今回はGinというフレームワークを追加しました。
GinはDockerFileでコンテナ作成後にgo get github.com/gin-gonic/ginというコマンドでインストールされ、main.goで呼び出されます。
そしてmain.goの中でtemplatesの中身が読み取られ、

router.GET("/", func(ctx *gin.Context){
  ctx.HTML(200, "index.html", gin.H{})
})

によってroot("/")に対してtemplates/index.htmlが紐づけられることになります。
ちなみにrouter.GETの第一引数("/")を"/test"などに変えると、http://localhost:8080ではなく、http://localhost:8080/testでindex.htmlが表示されるようになります。

最後にdocker-compose.ymlにportを追加することでlocalhost:8080へのアクセスを可能にしています。

DockerでMySQLを起動する

ここまででGoでのWebページ作成はできるようになりました。しかし、実際にはWebサービスを作るときにDBとの接続は避けて通れない内容になってきます。
そこで次はDockerを使ってMySQLを立ち上げていきます。

まずはdocker-compose.ymlに
・dbコンテナについての記述
・volumeの記述
を追加してください。

docker-compose.yml
db:
  image: mysql:8.0
  environment:
    MYSQL_ROOT_PASSWORD: root
    MYSQL_DATABASE: go_database
    MYSQL_USER: go_test
    MYSQL_PASSWORD: password
    TZ: 'Asia/Tokyo'
  command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
  volumes:
    - db-data:/var/lib/mysql
    - ./db/my.cnf:/etc/mysql/conf.d/my.cnf
  ports:
    - 3306:3306

volumes:
  db-data:
    driver: local

また、dbディレクトリを作り、その中にmy.cnfファイルを作成してください。

my.cnf
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_bin

default-time-zone = SYSTEM
log_timestamps = SYSTEM

default-authentication-plugin = mysql_native_password

[mysql]
default-character-set = utf8mb4

[client]
default-character-set = utf8mb4

(この辺は参考ページそのままです。ログに関する部分だけなぜか上手くいかなかったので外しています)

ここまでやってdocker-compose up -dをやるとMySQLのコンテナも立ち上がるはずです。
設定の記述しかないのでここの説明は省略します。

GoとMySQLを接続する

MySQLが立ち上がったので早速Goにつないでみます。
今回は接続にsqlドライバーとGORMというフレームワークを使います。
- DockerFileにインストールの追加
- docker-compose.ymlに依存関係の記述を追加
- main.goにDB接続の処理を追加
を行ってください。

DockerFile
FROM golang:latest

RUN mkdir /app
WORKDIR /app

RUN go get github.com/gin-gonic/gin
RUN go get github.com/go-sql-driver/mysql
RUN go get github.com/jinzhu/gorm
docker-compose.yml
version: '3'
services:
  go:
    build:
      context: .
      dockerfile: DockerFile
    command: /bin/sh -c "go run main.go"
    stdin_open: true
    tty: true
    volumes:
      - .:/app
    ports:
      - 8080:8080
    depends_on:
      - "db"

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: go_database
      MYSQL_USER: go_test
      MYSQL_PASSWORD: password
      TZ: 'Asia/Tokyo'
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    volumes:
      - db-data:/var/lib/mysql
      - ./db/my.cnf:/etc/mysql/conf.d/my.cnf
    ports:
      - 3306:3306

volumes:
  db-data:
    driver: local
main.go
package main

import (
  "fmt"
  "time"

  "github.com/gin-gonic/gin"
  "github.com/jinzhu/gorm"
  _ "github.com/go-sql-driver/mysql"
)

func main() {
  db := sqlConnect()
  defer db.Close()

  router := gin.Default()
  router.LoadHTMLGlob("templates/*.html")

  router.GET("/", func(ctx *gin.Context){
    ctx.HTML(200, "index.html", gin.H{})
  })

  router.Run()
}

func sqlConnect() (database *gorm.DB) {
  DBMS := "mysql"
  USER := "go_test"
  PASS := "password"
  PROTOCOL := "tcp(db:3306)"
  DBNAME := "go_database"

  CONNECT := USER + ":" + PASS + "@" + PROTOCOL + "/" + DBNAME + "?charset=utf8&parseTime=true&loc=Asia%2FTokyo"

  count := 0
  db, err := gorm.Open(DBMS, CONNECT)
  if err != nil {
    for {
      if err == nil {
        fmt.Println("")
        break
      }
      fmt.Print(".")
      time.Sleep(time.Second)
      count++
      if count > 180 {
        fmt.Println("")
        fmt.Println("DB接続失敗")
        panic(err)
      }
      db, err = gorm.Open(DBMS, CONNECT)
    }
  }
  fmt.Println("DB接続成功")

  return db
}

これでdocker compose upを行い、コンソールに「DB接続成功」と出たら成功です。

追加された内容はsqlConnectがメインなのでそこを解説します。

func sqlConnect() (database *gorm.DB) {
  DBMS := "mysql"
  USER := "go_test"
  PASS := "password"
  PROTOCOL := "tcp(db:3306)"
  DBNAME := "go_database"

  CONNECT := USER + ":" + PASS + "@" + PROTOCOL + "/" + DBNAME + "?charset=utf8&parseTime=true&loc=Asia%2FTokyo"

  count := 0
  db, err := gorm.Open(DBMS, CONNECT)
  if err != nil {
    for {
      if err == nil {
        fmt.Println("")
        break
      }
      fmt.Print(".")
      time.Sleep(time.Second)
      count++
      if count > 180 {
        fmt.Println("")
        fmt.Println("DB接続失敗")
        panic(err)
      }
      db, err = gorm.Open(DBMS, CONNECT)
    }
  }
  fmt.Println("DB接続成功")

  return db
}

前半部はDBに接続するための情報を定義しています。docker-compose.ymlで設定した内容を入力してください。
その後、db, err := gorm.Open(DBMS, CONNECT)でDBに接続します。しかし、MySQLの起動時間によってはこのコマンドが実行される時点でMySQLの準備が完了していない場合があります。
そこでこのコードでは2つの対策をしています。

1つめはdocker-compose.ymlでの依存関係の設定です。
ここでdepends_onを設定したことにより、dbコンテナが立ち上がってからgoコンテナが立ち上がるようになります。

2つめはリトライ処理です。
dbコンテナが起動してからもMySQLが立ち上がるまでに時間がかかるのでもしDBにつながらなかった場合に1秒待ってからリトライするようにしています。
これだと本当にエラーのときにリトライし続けてしまうので適当な回数でエラーを返すようにします。このコードでは3分つながらなかったらエラーになるようになっています。

CRUDを実装する

ついにMySQLにもつながるようになったので最後にCRUDの処理を実装して実際の流れをみていきましょう。
あと変更するのはmain.goとindex.htmlのみです。
- Userの定義を作成
- マイグレーション
- post処理の実装
- ユーザー追加フォーム、ユーザー削除ボタンの実装
をやっていきます。

main.go
package main

import (
  "fmt"
  "strconv"
  "time"

  "github.com/gin-gonic/gin"
  "github.com/jinzhu/gorm"
  _ "github.com/go-sql-driver/mysql"
)

type User struct {
  gorm.Model
  Name string
  Email string
}

func main() {
  db := sqlConnect()
  db.AutoMigrate(&User{})
  defer db.Close()

  router := gin.Default()
  router.LoadHTMLGlob("templates/*.html")

  router.GET("/", func(ctx *gin.Context){
    db := sqlConnect()
    var users []User
    db.Order("created_at asc").Find(&users)
    defer db.Close()

    ctx.HTML(200, "index.html", gin.H{
      "users": users,
    })
  })

  router.POST("/new", func(ctx *gin.Context) {
    db := sqlConnect()
    name := ctx.PostForm("name")
    email := ctx.PostForm("email")
    fmt.Println("create user " + name + " with email " + email)
    db.Create(&User{Name: name, Email: email})
    defer db.Close()

    ctx.Redirect(302, "/")
  })

  router.POST("/delete/:id", func(ctx *gin.Context) {
    db := sqlConnect()
    n := ctx.Param("id")
    id, err := strconv.Atoi(n)
    if err != nil {
      panic("id is not a number")
    }
    var user User
    db.First(&user, id)
    db.Delete(&user)
    defer db.Close()

    ctx.Redirect(302, "/")
  })

  router.Run()
}

func sqlConnect() (database *gorm.DB) {
  DBMS := "mysql"
  USER := "go_test"
  PASS := "password"
  PROTOCOL := "tcp(db:3306)"
  DBNAME := "go_database"

  CONNECT := USER + ":" + PASS + "@" + PROTOCOL + "/" + DBNAME + "?charset=utf8&parseTime=true&loc=Asia%2FTokyo"

  count := 0
  db, err := gorm.Open(DBMS, CONNECT)
  if err != nil {
    for {
      if err == nil {
        fmt.Println("")
        break
      }
      fmt.Print(".")
      time.Sleep(time.Second)
      count++
      if count > 180 {
        fmt.Println("")
        panic(err)
      }
      db, err = gorm.Open(DBMS, CONNECT)
    }
  }

  return db
}
templates/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Sample App</title>
</head>
<body>
  <h2>ユーザー追加</h2>
  <form method="post" action="/new">
    <p>名前<input type="text" name="name" size="30" placeholder="入力してください" ></p>
    <p>メールアドレス<input type="text" name="email" size="30" placeholder="入力してください" ></p>
    <p><input type="submit" value="Send"></p>
  </form>

  <h2>ユーザー一覧</h2>
  <table>
    <tr>
      <td>名前</td>
      <td>メールアドレス</td>
    </tr>
    {{ range .users }}
      <tr>
        <td>{{ .Name }}</td>
        <td>{{ .Email }}</td>
        <td>
          <form method="post" action="/delete/{{.ID}}">
            <button type="submit">削除</button>
          </form>
        </td>
      </tr>
    {{ end }}
  </ul>
  </body>
</html>

これでdocker-compose up -dを行い、http://localhost:8080にアクセスするとユーザー登録フォームが現れ、ユーザーを登録すると下に登録したユーザーの情報が表示されるようになります。
また、コンテナを削除して上げ直しても登録されたユーザーは削除されず、ユーザー一覧に表示が残るようになります。

それでは追加部分の解説をしていきます。
まず、main.goでUserという構造体を作成しています。gorm.Modelでidなどモデルに必要な内容をUserに入れ、更にUser特有のname, emailを追加しています。
この構造はdb.AutoMigrateによってDBに反映されます。

続いて各パスでのCRUD処理を実装していきます。

rootパスではユーザー一覧を取得します。
db.Find(&users)でDB内にあるユーザー一覧をUser構造として取得します。
間にOrderを挟むことで取得時に古いユーザーが上に来るようにしています。
最後に取得したユーザーをindex.htmlに渡しています。

/newパスではフォームの内容をもとにユーザーを作成しています。
ctx.PostFormでフォームによってsubmitされた内容を取得し、その内容をdb.Createで永続化しています。
処理が終わったらrootにリダイレクトします。

/deleteパスではidを指定してユーザーを削除しています。
こちらではURLにユーザーのidを指定しているのですが、同様にctxから取得します。
そしてその内容からdb.Firstでユーザーを取得し、db.Deleteで該当のユーザーを削除します。
ここで、idはstringで渡されているのでstrconv.Atoiでint型に変換していることに注意してください。

index.htmlでは一般的なhtmlの書き方でformとtableを作成しています。
ここで、{{ range .users }}という形でmain.goから渡されたusersを受け取っています。

おわりに

今回はGoでのWebサービス開発の導入としてGo+MySQL+Dockerで簡単なCRUDを実装してみました。あくまで練習なのでバリデーションとか細かい制御などは考えていません。
今回行った内容は初歩ではありますが、この内容を広げて複雑化していくことで実際にWebサービスを作ることができると思います。
もしGoで何か作ってみたいと考えている方がいらっしゃったらぜひ参考にしてみてください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

libmysqlclient.so.18がないといわれる

対応

インストール

yum update -y
yum install -y gcc mysql-devel mysql-libs

# ↓どっちかに入る
# /lib64/mysql/libmysqlclient.so.18
# /usr/lib64/mysql/libmysqlclient.so.18

それでも動かない場合

環境変数、LD_LIBRARY_PATHが通っている場所へlibmysqlclient.so.18をコピーする。


実体はlibmysqlclient.so.18.0.0になっているので注意
libmysqlclient.so.18はシンボリックリンク)

特にLambda上で必要な場合などは注意。

# Lambda上
LD_LIBRARY_PATH=/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者向け】データベースのテーブル設計で僕が意識している6つのこと

はじめまして、himakuroです。

2017年ぐらいからQiitaに記事を投下しようと考えていたのですが、なかなか筆が乗らずようやく初投稿です:relaxed:

軽く自己紹介をしておくと、普段は社内SEとしてPHP、Ruby、Golangを書いたり、
趣味の個人ブログ方ではプログラミング初心者に向けた記事や雑記的なものを書いたりしています。

今回は記念すべき1つ目の記事と言う事で
僕が普段テーブル設計(主に命名)で気をつけている6つの事を書きました。

僕の好みも含まれていますが、初心者の方がテーブルやカラム名を決める際の参考になればなと思います。

テーブル名は必ず複数形にする

テーブルは一つしかないから単数形を使うべき!
Modelも単数形で定義するじゃん!

みたいな反論が聞こえてきそうですが、僕は複数形で定義する派です。

また複数形にする場合に、テーブル名の途中の部分を複数形にしている物をたまに見かけますが、僕は末尾を複数形で統一するのが好きです。

x user_bank
x users_bank
o user_banks

Railsで開発をしている方は、僕と同じ様に末尾が複数形の方が好きという人が多い(はず?)

カラム名のsuffixとして_idを無闇に使わない

suffix(サフィックス)とは user_idpost_id で言う _id の部分を指しています。
要は末尾に付ける物ですね。

そしてこの _id は、他テーブルの主キーを指すのに利用して行きたいので 
categoriesテーブルが存在しないのに、 category_id をpostsテーブルに追加するなんて事はしません。

また命名規則としては テーブル名(単数形)_id で統一するのがおすすめです。

x categories_id
x id_category
o category_id

同じものを指すカラムは型を統一する

※ コメントでご指摘を頂いたため2020-09-24に内容を更新しました

1つ前の内容に少し関連しているのですが
他テーブルのauto incrementなid列と関連付けるための
テーブル名(単数形)_id カラムを定義する際には型はunsignedのbigintで統一しています。

理由としてはRailsやLaravelなどでauto incrementのid列を定義すると, id はunsigned bigintになるからです。

本来ならカラムの型はどのようなデータが入るかを意識した上で選定するべきですが
フレームワークを使っていると自動で型が確定するものがあり、今回の内容がそれに該当します。

自動で型が確定するカラムは実際にどのような型になっているかを確認するのを疎かにしがちで、
今まで携わったLaravelやRailsのプロジェクトでも、 auto incrementな他テーブルのidを参照する xxx_id をbigintではなくintで定義しているのを多数見てきました。 

xxx_id に限らず同じ意味を持つカラムは同じ型で定義するように意識していきましょう。

履歴テーブルはhistory、ログテーブルはlog

履歴テーブルとログテーブルって何が違うの?

と聞かれたら僕は

履歴テーブルは画面に表示して ユーザー(利用者)に見せるもの
ログテーブルは エンジニア(開発者)などが調査に使用するもの

と答えます。

また命名に関しては、例えばゲーム開発などでユーザーがゲーム画面で閲覧する事が出来るプレゼント履歴用のテーブルを作る場合は user_present_histories と名付けます。

ユーザーからアイテム消えたんだけど?みたいなお問い合わせが来た場合に、エンジニアが調査が出来るようにユーザーのアイテム利用ログを残しておく場合は user_item_logs と名付けます。

人によっては historylog をprefix(プレフィックス)として先頭に持って来る方もいると思いますが、この辺りは好みかなと思います。

o user_item_logs
o log_user_items

booleanのカラムはひと目で分かるようにする

型がbooleanのカラムはひと目でわかるように
is_ can_ has_ の様なprefixを付けるようにしています。

例: is_active, has_owner

こうすることで、これらのカラム名を見ただけで
型がbooleanであることが推測でき、更にtrueやfalseなどの値が入っていた時に
何を意味するのかが明確になります。

ちなみに型がbooleanで check_xxx の様なカラムをたまに見かけますが
これでは true の場合にはチェックが必要なのか、それともチェックが終わっているのかが明確ではないので、booleanのカラム名として check はおすすめしません。

日付を保持するカラムの命名方法

日付を保持するカラムを命名する際に finish_at にするか finished_at にするかで迷ったことはありませんか?

これを迷わなくするためには カラムで保持するデータが未来を指すのか、過去のものを指すのか を考慮すると良いです。

過去形を使わない例

例えばゲーム開発でイベント情報を持つテーブルがあり、そのテーブルにはイベント開始時刻と、終了時刻を持つカラムがあるとします。

イベントの開催情報は事前告知するのが一般的であり、イベント開始時刻と終了時刻には未来の時間を保持することになります。

よって、カラム名も過去形にはせずに start_atfinish_at の様に命名をしていきます。

過去形を使う例

続いて過去形で命名をする場合を見ていきます。

user_quests と言うユーザーのクエスト情報を管理するテーブルがあり、そこにはユーザーがクエストを開始した時刻と終了した時刻を保持するカラムがあるとします。

これらのカラムにデータが入るのはユーザーがクエストを開始した時、終了した時なので、データとしては過去の時間を指していることになります。

よってカラム名は started_at finished_at のように過去形で命名をします。

レコードの作成時間や更新時間を保持するカラムとして created_atupdated_at などがあると思いますが、これらのカラムのデータも作成された時間、更新された時間を保持するので過去形ですね。

テーブルの設計って大変

一般的にテーブル設計って聞くと、正規化とかパフォーマンスを意識したインデックスの貼り方とか難しい事ばかりを意識しがちですが、それ以前にテーブル名やカラム名を意識するのも大切だよというのを伝えたかったのでこの記事を書きました。

これが正解と言うわけではありませんが、テーブル名やカラム名はとても重要なので
ネーミングセンスが無いから適当につけよ…とは思わずに、しっかりと考えて命名をするようにしましょう。

初投稿 完

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PythonでMySQLに接続する方法【初心者向け】

◎下記を参照してDBへのコネクト、SELECT分をためしてみる。
パッケージはmysqlclientを利用する
PythonでMySQLに接続する方法【初心者向け】

PythonでMySQLに接続する書き方は、そのままではエラーとなるので、下記を利用すること。

#mysqlclientパッケージを利用する
import MySQLdb

# 接続する
conn = MySQLdb.connect(
user='root',
passwd='root',
host='localhost',
db='mstibqym_crontest')

# カーソルを取得する
cur = conn.cursor()

# SQL(データベースを操作するコマンド)を実行する
sql = "select * from test_table"
cur.execute(sql)

# 実行結果を取得する
rows = cur.fetchall()

# 一行ずつ表示する
for row in rows:
    print(row)

cur.close

# 接続を閉じる
conn.close

◎INSERT分参考
Python3でMySQLを使う – 基本操作からエラー処理までサンプルコード付

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PythonでMySQLにINSERTする【初心者向け】

◎下記を参照してDBへのコネクト、SELECT分をためしてみる。
パッケージはmysqlclientを利用する
PythonでMySQLに接続する方法【初心者向け】

PythonでMySQLに接続する書き方は、そのままではエラーとなるので、下記を利用すること。

#mysqlclientパッケージを利用する
import MySQLdb

# 接続する
conn = MySQLdb.connect(
user='root',
passwd='root',
host='localhost',
db='mstibqym_crontest')

# カーソルを取得する
cur = conn.cursor()

# SQL(データベースを操作するコマンド)を実行する
sql = "select * from test_table"
cur.execute(sql)

# 実行結果を取得する
rows = cur.fetchall()

# 一行ずつ表示する
for row in rows:
    print(row)

cur.close

# 接続を閉じる
conn.close

◎INSERT分は下記を参考に実施
Python3でMySQLを使う – 基本操作からエラー処理までサンプルコード付

#mysqlclientパッケージを利用する
import MySQLdb

# 接続する
conn = MySQLdb.connect(
user='root',
passwd='root',
host='localhost',
db='mstibqym_crontest')

# カーソルを取得する
cur = conn.cursor()

# SQL(データベースを操作するコマンド)を実行する
sql = "INSERT INTO test_table (item) VALUES('xyzss')"
cur.execute(sql)

conn.commit()
cur.close

# 接続を閉じる
conn.close
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む