MENU

DB設計 データ型の選定基準と基本知識

背景

DB設計を担当する事が増えてきていて、 基本的なデータ型を考える基本すらないので調べてみました

理解したいこと

・データ型の基本情報 ・よく使う、テキストと数字はどちらを使えば良いのか?を基本に考える

よく利用するデータ型と制約に絞る

テキストと数字、制約ではインデックスを見ていく

・数字について

ChatGptにIntとBigintの使い分けについて質問し、 回答から判断すると、【2億以上か以下か】で判断して良さそう なので、大体のWebサービスでは、intで良いのではないかと思った

INT(整数型)

INTは整数を保存するためのデータ型です。
通常、4バイト(32ビット)のストレージサイズを持ち、範囲は-2,147,483,648から2,147,483,647(または0から4,294,967,295)です。
BIGINT(大きな整数型)

BIGINTはより大きな整数を保存するためのデータ型です。
通常、8バイト(64ビット)のストレージサイズを持ち、範囲は-9,223,372,036,854,775,808から9,223,372,036,854,775,807(または0から18,446,744,073,709,551,615)です。

・VarcharとText型について

ChatGptに質問してみた結果、 【文字数が予測できそうなもの】はVARCHAR 【予測できないもの】はTEXTが良いのではと 要するに、名称系は、VARCHARで、入力系はTEXTにするで良いのではないかと思いました笑

で、文字数の制限などをつけられるので、 VARCHAR(20) で、20文字の制限など

これはタイトルなどの文字数が100文字などつけると、 UIに大きく影響してくる可能性がると思うので要注意 文字数多くても、タイトル....などで実装しても良いかもね

VARCHAR(可変長文字列型)

VARCHARは可変長の文字列を保存するためのデータ型です。
VARCHAR(n)の形式で定義され、nは文字列が保持できる最大文字数を指定します。
文字列はその長さに応じてストレージを消費しますが、最大n文字まで保存できます。
TEXT(テキスト型)

TEXTは長い文字列を保存するためのデータ型です。
VARCHARよりもはるかに大きなサイズのテキストを保存することができます。
長さの制限がほとんどないため、非常に大きなテキストデータ(例:記事、書籍の内容)に適しています。
使い分ける基準

文字数が比較的少なく、最大長が予測可能な場合(例:名前、住所、メールアドレス)にはVARCHARを使用します。これにより、データベースのパフォーマンスを最適化し、ストレージスペースを節約できます。
文字数が非常に多く、予測が難しい場合(例:ユーザーが入力する大量のテキスト、ブログ投稿、コメントなど)にはTEXTを使用します。これにより、大量のテキストを柔軟に扱うことができます。
これらのデータ型の選択は、アプリケーションの要件、パフォーマンスの考慮、およびストレージの制約に基づいて行う必要があります。また、使用するデータベース管理システムによっても、これらのデータ型の具体的な特性や制限が異なる場合があるため、そのドキュメントを参照することも重要です。

・制約について

制約で一番わかりづらいのは、インデックスの利用だと思います

そこで、利用するかの判断は「カーディナリティ」らしい 理解に関しては下記の記事を参考にすると良いです

zenn.dev

要は、インデックスは検索速度を上げるけど、 対象のデータによって効きが変わる では、どんなデータが良いのか?

それは「カーディナリティが高い」データ 例えば、日付など詳細は記事を見てほしい 図解してくれててわかりやすい

データ型の基本情報

下記のように種類があるらしい。ChatGptに聞いたら下記の様に回答がきた

  1. 数値型(Numeric Types)

  2. 文字型(Character Types)

    • CHAR(n):固定長文字列
    • VARCHAR(n):可変長文字列
    • TEXT:長いテキストデータ
  3. 日付・時間型(Date and Time Types)

    • DATE:日付
    • TIME:時間
    • TIMESTAMP:日付と時間
    • DATETIME:日付と時間(一部のDBMSで使用)
  4. バイナリ型(Binary Types)

    • BINARY(n):固定長バイナリデータ
    • VARBINARY(n):可変長バイナリデータ
    • BLOB:バイナリ大オブジェクト
  5. 論理型(Boolean Type)

    • BOOLEAN:真偽値(TRUE/FALSE)
  6. その他のデータ型

    • ENUM:列挙型(指定された値のリストから選択)
    • SET:値のセット

メインの制約

  1. NOT NULL

    • このフィールドは空(NULL)にできないことを示します。
  2. UNIQUE

    • このフィールドのすべての値はユニーク(重複しない)でなければなりません。
  3. PRIMARY KEY

    • 各行を一意に識別するためのフィールド。通常、NOT NULLとUNIQUEの制約が自動的に適用されます。
  4. FOREIGN KEY

    • 他のテーブルの主キーを参照するフィールド。外部キー制約を使ってデータの整合性を保ちます。
  5. CHECK

    • 指定された条件を満たす必要があるフィールド(例:年齢は18歳以上)。
  6. DEFAULT

    • フィールドに値が指定されなかった場合に使用されるデフォルト値。
  7. INDEX

    • データベースの検索速度を向上させるために設定される。制約ではないが、データベース設計において重要。

これらのデータ型と制約は、データベース管理システム(DBMS)によって異なる場合があります。したがって、使用しているDBMSのドキュメントを参照して、利用可能なデータ型と制約の詳細を確認することが重要です。

まとめ

基本的なデータ型は何か?と、テキストと数字、基本的な制約を利用する判断軸を一様持てたと思う 後は現場で、挑戦して質問していけば、知識が上がってくるのではないかな?と思っています

ここまで読んでくれた方、ありがとうございます この記事を読んでいる方は、DB設計の経験が浅く、データ型をつける判断軸が全くなく四苦八苦している方なのではないかな? と思っています

DB設計ができるエンジニアは市場価値が高いと思うので一緒に頑張っていきましょう

スプレッド演算子

目的

スプレッド演算子はNext jsでデータを更新する時によく利用するので使い方まとめ

オブジェクトを更新する際のポイント

  1. 元のデータをスプレッド演算子でコピー
  2. 更新したいキーを指定して、新しい、リテラル or 数字 or オブジェクト or 配列をスプレッド演算子で更新

オブジェクトの更新

bのオブジェクトが新しいオブジェクトに

const obj = {
  a: 'aa',
  b: { aa: "aa", b: "bb"},
  c: [{ id:1, name: "name1"}, { id:2, name: "name2"}]
}

const newB = { aa: "ccc", b: "ee" }

console.log({...obj, b: {...newB}})

=> [object Object] {
  a: "aa",
  b: [object Object] {
    aa: "ccc",
    b: "ee"
  },
  c: [[object Object] {
  id: 1,
  name: "name1"
}, [object Object] {
  id: 2,
  name: "name2"
}]
}

配列の更新

const obj = {
  a: 'aa',
  b: { aa: "aa", b: "bb"},
  c: [{ id:1, name: "name1"}, { id:2, name: "name2"}]
}

const newC = [{ id: 1, name: "name3"}, { id:2, name: "name4"}]

console.log({...obj, c: [...newC] })

=> [object Object] {
  a: "aa",
  b: [object Object] {
    aa: "aa",
    b: "bb"
  },
  c: [[object Object] {
  id: 1,
  name: "name3"
}, [object Object] {
  id: 2,
  name: "name4"
}]
}

express の基本を理解する

目次

  • expressとは何か?ポイントだけ抑える
  • DB接続 with Prisma
  • その他 expressのコード読むときに知っておきたいこと

expressとは何か?

expressjs.com

「Express は、それ自体では最小限の機能を備えたルーティングとミドルウェアの Web フレームワークです。」

とドキュメントに書いてあり、実際に触ってみて、ルーティングと、ミドルウェアの理解ができれば、実務でのコードもある程度読めるようになる気がしました。

ミドルウェアの理解

できることは下記らしい

  • 任意のコードを実行する。
  • リクエストオブジェクトとレスポンスオブジェクトを変更する。
  • リクエストレスポンスサイクルを終了する。
  • スタック内の次のミドルウェア関数を呼び出す。

任意のコードを実行する

app.useを利用して、下記のコードをリクエストがあるたびに実行する use() 関数と next() が大事だったはず

  • 下記は全体に適応させるケース
const app = express()

app.use((req, res, next) => {
  console.log('Time:', Date.now())
  next()
})
  • パス指定して適応させるケース

user/:id のリクエストのケースに限定させる時に

app.use('/user/:id', (req, res, next) => {
  console.log('Request Type:', req.method)
  next()
})

認証情報をチェックしたい時は下記のようなコードにする感じなのかも reqのヘッダー情報に headersでアクセスできる様子

const app = express()
const router = express.Router()

// predicate the router with a check and bail out when needed
router.use((req, res, next) => {
  if (!req.headers['x-auth']) return next('router')
  next()
})

router.get('/', (req, res) => {
  res.send('hello, user!')
})

// use the router and 401 anything falling through
app.use('/admin', router, (req, res) => {
  res.sendStatus(401)
})

クッキーパーサーは下記のように使ったことがあったな

const express = require('express')
const app = express()
const cookieParser = require('cookie-parser')

// load the cookie-parsing middleware
app.use(cookieParser())

DB接続をPrismaで行う方法

### ライブラリを入れる
yarn add prisma prisma/clientrst

### 初期ファイル作成
yarn prisma init

### .envファイルに DB接続先をかく

DATABASE_URL="postgresql://postgres:password@localhost:5433/todo-db?schema=public"

### schema.prisma にモデルをかく

model Todo {
  id              Int       @id @default(autoincrement())
  title           String?   @db.VarChar
  created_at      DateTime  @default(now())
  updated_at      DateTime  @default(now())
  deleted_at      DateTime?
}

### migrateをする

npx prisma migrate dev --name create_todo_table


### 接続

const { PrismaClient } = require('@prisma/client');

module.exports = (router) => {
  router.get('/hello', async (req, res) => {
    const prisma = new PrismaClient();
    const todos = await prisma.todo.findMany();
    console.log(`partners ${JSON.stringify(todos)}`)
    res.send('hello world')
  })
}

その他 理解

  • common jsの書き方

require (importではなく)

### export 元

module.exports = (router) => {
  router.get('/hoge', (req, res) => {
    res.send('hoge world')
  })
}

### import 先

const HelloRoute = require('./hello') 

SQL メモ

基本的なDB構造のSQLまとめ

1対多の場合

user.todosみたいにデータを取りたい場合

  • schema
model User {
  name: string;
}

model Todo {
  title: string;
  user_id: int;
}

ChatGptに質問したら、 下記のSQLを教えてくれたでも FROMに Todoを持ってくるのは理由がわからないな

SELECT
  t.*,
  u.name AS user_name
FROM
  Todo t
LEFT JOIN
  User u ON t.user_id = u.id;

調べてたらやはり起点が異なると意味合いが違うようで、 AはTodoが紐づいていないuserを全て持ってくる BはTodoい紐づいたUserしか持ってこない

### A

SELECT
  t.*,
  u.name AS user_name
FROM
  User u
LEFT JOIN
  Todo t ON t.user_id = u.id;


### B
SELECT
  t.*,
  u.name AS user_name
FROM
  Todo t
LEFT JOIN
  User u ON t.user_id = u.id;

多対多の場合

Order Item OrderItemみたいな場合は下記

流れ Orderのテーブルを持ってくる OederItemをJOINする OrderItemとItemをJOINする where で検索するらしい

### 期待
order.items => item[] がほしい

### SQL

SELECT
  i.*
FROM
  "Order" o
LEFT JOIN 
  "OrderItem" oi ON o.id = oi.order_id
LEFT JOIN
  "Item" i ON oi.item_id = i.id
WHERE
  o.id = {特定の注文ID};

MaterialUI + useForm

背景

MaterialUiのinputのvalueと useFormの値の管理でかなり詰まったのでまとめ

まとめ

  • MaterialUiの値の操作は、基本的にvalueプロパティで変更可能 valueプロパティの値を stateで保持して、setStateで値を更新すれば操作可能

  • useFormとMateriUiのコンポーネント操作の違い

コンポーネントの値の更新は、自身でstateを作成し、その値を更新することで変更可能 useFormの値は useFormが setValue メソッドを持っているので、setValueで値を更新する

  • 便利メソッド

setValueは useFormの値を更新出来る

getValues はuseFormの値を取得出来る 引数ありの場合は指定した引数にキーの文字列を入れれば値取得可能

コード

  • 初期設定
  const {
    control,
    register,
    setValue,
    handleSubmit,
    getValues,
    formState: { errors },
  } = useForm<RequestInputProps>(formOptions);
  • Autocompleteの管理

Autocompleteの値は valueで管理 useFormはsetValueで更新 onChangeイベントで値を検知

                      <Controller
                        control={control}
                        name='partner_id'
                        render={({ field, fieldState }) => (
                          <Autocomplete
                            freeSolo
                            {...field}
                            value={partnerName}. // { name: string, id: number}
                            options={partnerNames}
                            disableCloseOnSelect
                            getOptionLabel={(option: any) => option.name}
                            onChange={(e, newValue) => {
                              setValue('partner_id', `${newValue?.id}` || '');
                            }}
                            onInputChange={(e, newValue) => {
                              setValue('partner_id', `input-${newValue}`);
                            }}
                            renderOption={(props, option, { selected }) => (
                              <li {...props}>
                                <Checkbox style={{ marginRight: 8 }} checked={selected} />
                                {option.name}
                              </li>
                            )}
                            renderInput={(params) => (
                              <TextField
                                {...params}
                                label='取引先'
                                error={fieldState.error ? true : false}
                                helperText={fieldState.error?.message}
                                style={{ width: 418 }}
                                InputLabelProps={{ shrink: true }}
                              />
                            )}
                          />
                        )}

useForm の利用する目的や使い方の英文で理解

目的

基本的な useFormと material UIの利用がまとっまっているので、 都度確認しやすくする

なんで useForm利用するのか?

・シンプルにformの値が管理しやすい ・material ui などのUIライブラリや Typescriptなどの型利用が一般的になったので、 独自開発するより良い

この記事でわかること

  • Material UI datepickers, radio buttons, dropdowns, and slidersと useFormの統合が分かる

useForm基本

  • useFormはいろんなメソッドがある
const { register, handleSubmit, reset, control, setValue } = useForm();
  • MUI などを利用しない場合は下記のようなシンプルな利用
<input type="text" ref={register} name="firstName" />
  • TextInputの場合
import { Controller } from "react-hook-form";
import TextField from "@mui/material/TextField";
import { FormInputProps } from "./FormInputProps";
export const FormInputText = ({ name, control, label }: FormInputProps) => {
  return (
    <Controller
      name={name}
      control={control}
      render={({
        field: { onChange, value },
        fieldState: { error },
        formState,
      }) => (
        <TextField
          helperText={error ? error.message : null}
          size="small"
          error={!!error}
          onChange={onChange}
          value={value}
          fullWidth
          label={label}
          variant="outlined"
        />
      )}
    />
  );
};
// src/form-component/FormInputRadio.tsx
import {
  FormControl,
  FormControlLabel,
  FormLabel,
  Radio,
  RadioGroup,
} from "@mui/material";
import { Controller } from "react-hook-form";
import { FormInputProps } from "./FormInputProps";
const options = [
  {
    label: "Radio Option 1",
    value: "1",
  },
  {
    label: "Radio Option 2",
    value: "2",
  },
];
export const FormInputRadio: React.FC<FormInputProps> = ({
  name,
  control,
  label,
}) => {
  const generateRadioOptions = () => {
    return options.map((singleOption) => (
      <FormControlLabel
        value={singleOption.value}
        label={singleOption.label}
        control={<Radio />}
      />
    ));
  };
  return (
    <FormControl component="fieldset">
      <FormLabel component="legend">{label}</FormLabel>
      <Controller
        name={name}
        control={control}
        render={({
          field: { onChange, value },
          fieldState: { error },
          formState,
        }) => (
          <RadioGroup value={value} onChange={onChange}>
            {generateRadioOptions()}
          </RadioGroup>
        )}
      />
    </FormControl>
  • dropwodn
// src/form-component/FormInputDropdown.tsx

import { FormControl, InputLabel, MenuItem, Select } from "@mui/material";
import { Controller } from "react-hook-form";
import { FormInputProps } from "./FormInputProps";
const options = [
  {
    label: "Dropdown Option 1",
    value: "1",
  },
  {
    label: "Dropdown Option 2",
    value: "2",
  },
];
export const FormInputDropdown: React.FC<FormInputProps> = ({
  name,
  control,
  label,
}) => {
  const generateSingleOptions = () => {
    return options.map((option: any) => {
      return (
        <MenuItem key={option.value} value={option.value}>
          {option.label}
        </MenuItem>
      );
    });
  };
  return (
    <FormControl size={"small"}>
      <InputLabel>{label}</InputLabel>
      <Controller
        render={({ field: { onChange, value } }) => (
          <Select onChange={onChange} value={value}>
            {generateSingleOptions()}
          </Select>
        )}
        control={control}
        name={name}
      />
    </FormControl>
  );
};
  • checknbox
// src/form-component/FormInputMultiCheckbox.tsx
import React, { useEffect, useState } from "react";
import {
  Checkbox,
  FormControl,
  FormControlLabel,
  FormLabel,
} from "@mui/material";
import { Controller } from "react-hook-form";
import { FormInputProps } from "./FormInputProps";
const options = [
  {
    label: "Checkbox Option 1",
    value: "1",
  },
  {
    label: "Checkbox Option 2",
    value: "2",
  },
];
export const FormInputMultiCheckbox: React.FC<FormInputProps> = ({
  name,
  control,
  setValue,
  label,
}) => {
  const [selectedItems, setSelectedItems] = useState<any>([]);
  // we are handling the selection manually here
  const handleSelect = (value: any) => {
    const isPresent = selectedItems.indexOf(value);
    if (isPresent !== -1) {
      const remaining = selectedItems.filter((item: any) => item !== value);
      setSelectedItems(remaining);
    } else {
      setSelectedItems((prevItems: any) => [...prevItems, value]);
    }
  };
  // we are setting form value manually here
  useEffect(() => {
    setValue(name, selectedItems);
  }, [name, selectedItems, setValue]);
  return (
    <FormControl size={"small"} variant={"outlined"}>
      <FormLabel component="legend">{label}</FormLabel>
      <div>
        {options.map((option: any) => {
          return (
            <FormControlLabel
              control={
                <Controller
                  name={name}
                  render={({ field }) => {
                    return (
                      <Checkbox
                        checked={selectedItems.includes(option.value)}
                        onChange={() => handleSelect(option.value)}
                      />
                    );
                  }}
                  control={control}
                />
              }
              label={option.label}
              key={option.value}
            />
          );
        })}
      </div>
    </FormControl>
  );
};
  • date with datepicker
/ src/form-component/FormInputDate.tsx

import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
import { Controller } from "react-hook-form";
import { FormInputProps } from "./FormInputProps";
export const FormInputDate = ({ name, control, label }: FormInputProps) => {
  return (
    <LocalizationProvider dateAdapter={AdapterDateFns}>
      <Controller
        name={name}
        control={control}
        render={({ field: { onChange, value } }) => (
          <DatePicker value={value} onChange={onChange} />
        )}
      />
    </LocalizationProvider>
  );
};

参考記事

blog.logrocket.com

useForm まとめ

stateで独自管理と useFormを利用した場合の比較記事

reffect.co.jp

独自state

  • カラム単位で、useState管理 onSubmitプロパティでhandleSubmitをする => handleSubmitは input type submitのクリックに反応する

  • オブジェクトで stateを管理する場合

import './App.css';
import { useState } from 'react';

function App() {
  const [formData, setFormData] = useState({ email: '', password: '' });

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log(formData);
  };

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData({ ...formData, [name]: value });
  };
  return (
    <div className="App">
      <h1>ログイン</h1>
      <form onSubmit={handleSubmit}>
        <div>
          <label htmlFor="email">Email</label>
          <input
            id="email"
            name="email"
            value={formData.email}
            onChange={handleChange}
          />
        </div>
        <div>
          <label htmlFor="password">パスワード</label>
          <input
            id="password"
            name="password"
            value={formData.password}
            onChange={handleChange}
            type="password"
          />
        </div>
        <div>
          <button type="submit">ログイン</button>
        </div>
      </form>
    </div>
  );
}

export default App;

useRefを利用した場合

  • refプロパティを利用して管理できるらしい
  • react から useRef があるらしい。「onChangeで値の監視いらないらしい」
import './App.css';
import { useRef } from 'react';

function App() {
  const emailRef = useRef(null);
  const passwordRef = useRef(null);

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log({
      emai: emailRef.current.value,
      password: passwordRef.current.value,
    });
  };

  return (
    <div className="App">
      <h1>ログイン</h1>
      <form onSubmit={handleSubmit}>
        <div>
          <label htmlFor="email">Email</label>
          <input id="email" name="email" ref={emailRef} />
        </div>
        <div>
          <label htmlFor="password">パスワード</label>
          <input
            id="password"
            name="password"
            ref={passwordRef}
            type="password"
          />
        </div>

        <div>
          <button type="submit">ログイン</button>
        </div>
      </form>
    </div>
  );
}

export default App;

useFormを利用

useForm registerで値管理

  • register

register 関数の引数にフィールド名を指定することで name(属性), ref, onBlur, onChange が戻される

import './App.css';
import { useForm } from 'react-hook-form';

function App() {
  const { register, handleSubmit } = useForm();

  const onSubmit = (data) => console.log(data);

  return (
    <div className="App">
      <h1>ログイン</h1>
      <form onSubmit={handleSubmit(onSubmit)}>
        <div>
          <label htmlFor="email">Email</label>
          <input id="email" {...register('email')} />
        </div>
        <div>
          <label htmlFor="password">Password</label>
          <input id="password" {...register('password')} type="password" />
        </div>
        <button type="submit">ログイン</button>
      </form>
    </div>
  );
}

export default App;
  • 基本的なバリデーション
<input
  id="password"
  {...register('password', {
    required: {
      value: true,
      message: '入力が必須の項目です。',
    },
    pattern: {
      value: /^[A-Za-z]+$/,
      message: 'アルファベットのみ入力してください。',
    },
    minLength: {
      value: 8,
      message: '8文字以上入力してください。',
    },
  })}
  type="password"
/>

registerとcontrollerの違いについて

scrapbox.io

  • コントーラブルとアンコトーラブルの概念の違いがある
  • レンダリングタイミングが異なるのでバフォーマンスに差が出るらしい

Controllerの使い方

react-hook-form.com

react-hook-form.com

This object contains methods for registering components into React Hook Form.

とドキュメントにはあるので、外部のコンポーネントを管理できるようにするやつか?

import React from "react"
import { useForm, Controller } from "react-hook-form"
import { TextField } from "@material-ui/core"

type FormInputs = {
  firstName: string
}

function App() {
  const { control, handleSubmit } = useForm<FormInputs>()
  const onSubmit = (data: FormInputs) => console.log(data)

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Controller
        as={TextField}
        name="firstName"
        control={control}
        defaultValue=""
      />

      <input type="submit" />
    </form>
  )
}

参考記事

scrapbox.io