├── CSV-Column-To-Row ├── CSV-Column-To-Row.sql └── README.md ├── Fizz-Buzz-SQL ├── Fizz-Buzz-SQL.sql └── README.md ├── Order-By-Rand ├── Order-By-Rand.sql └── README.md ├── RDBMS-Nnormalization ├── README.md └── django_project │ ├── .gitignore │ ├── README.md │ ├── django_project │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py │ ├── manage.py │ └── normalization │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ ├── 0001_initial.py │ └── __init__.py │ ├── models.py │ ├── templates │ └── normalization │ │ ├── base.html │ │ └── order_details_list.html │ ├── tests.py │ ├── urls.py │ └── views.py ├── README.md ├── performance-tuning ├── README.md ├── images │ ├── arc.png │ ├── btree.png │ ├── multi-column.png │ ├── nested.png │ ├── partition.png │ ├── replication.png │ └── sharding.png ├── part0.md ├── part1.md ├── part2.md └── part3.md └── sql-training ├── basic.md ├── sql-training.md ├── step-1-answer.md ├── step-1-question.md ├── step-2-answer.md └── step-2-question.md /CSV-Column-To-Row/CSV-Column-To-Row.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE csv ( 2 | id int NOT NULL AUTO_INCREMENT, 3 | col varchar(10) DEFAULT NULL, 4 | PRIMARY KEY (id) 5 | ); 6 | 7 | insert into csv(col) values ("a,b,c"), ("x,y,z"), ("1,3"); 8 | 9 | select SUBSTRING_INDEX(SUBSTRING_INDEX(col, ',', n), ',', -1) cut_col 10 | from csv, 11 | (select d1.n + d2.n * 10 + 1 n from 12 | (select 1 as n 13 | union select 2 as n 14 | union select 3 as n 15 | union select 4 as n 16 | union select 5 as n 17 | union select 6 as n 18 | union select 7 as n 19 | union select 8 as n 20 | union select 9 as n 21 | union select 0 as n) d1, 22 | (select 1 as n 23 | union select 2 as n 24 | union select 3 as n 25 | union select 4 as n 26 | union select 5 as n 27 | union select 6 as n 28 | union select 7 as n 29 | union select 8 as n 30 | union select 9 as n 31 | union select 0 as n) d2) d 32 | where n <= (select length(col) - length(replace(col,',','')) + 1 as a ) 33 | order by id,n; 34 | 35 | select id,n,SUBSTRING_INDEX(SUBSTRING_INDEX(col, ',', n), ',', -1) cut_col 36 | from csv, 37 | (select d1.n + d2.n * 10 + 1 n from 38 | (select 1 as n 39 | union select 2 as n 40 | union select 3 as n 41 | union select 4 as n 42 | union select 5 as n 43 | union select 6 as n 44 | union select 7 as n 45 | union select 8 as n 46 | union select 9 as n 47 | union select 0 as n) d1, 48 | (select 1 as n 49 | union select 2 as n 50 | union select 3 as n 51 | union select 4 as n 52 | union select 5 as n 53 | union select 6 as n 54 | union select 7 as n 55 | union select 8 as n 56 | union select 9 as n 57 | union select 0 as n) d2) d 58 | where n <= (select length(col) - length(replace(col,',','')) + 1 as a ) 59 | order by id,n; 60 | -------------------------------------------------------------------------------- /CSV-Column-To-Row/README.md: -------------------------------------------------------------------------------- 1 | # CSV-Column-To-Row 2 | 3 | MySQL で CSV(カンマ区切り)のカラムを行に変換する 4 | 5 | ## 例 6 | 7 | CSV カラムのテーブル 8 | 9 | ``` 10 | mysql> select * from csv; 11 | +----+-------+ 12 | | id | col | 13 | +----+-------+ 14 | | 1 | a,b,c | 15 | | 2 | x,y,z | 16 | | 3 | 1,3 | 17 | +----+-------+ 18 | 3 rows in set (0.00 sec) 19 | ``` 20 | 21 | 変換 22 | 23 | ``` 24 | +---------+ 25 | | cut_col | 26 | +---------+ 27 | | a | 28 | | b | 29 | | c | 30 | | x | 31 | | y | 32 | | z | 33 | | 1 | 34 | | 3 | 35 | +---------+ 36 | 8 rows in set (0.01 sec) 37 | 38 | +----+---+---------+ 39 | | id | n | cut_col | 40 | +----+---+---------+ 41 | | 1 | 1 | a | 42 | | 1 | 2 | b | 43 | | 1 | 3 | c | 44 | | 2 | 1 | x | 45 | | 2 | 2 | y | 46 | | 2 | 3 | z | 47 | | 3 | 1 | 1 | 48 | | 3 | 2 | 3 | 49 | +----+---+---------+ 50 | 8 rows in set (0.01 sec) 51 | ``` 52 | -------------------------------------------------------------------------------- /Fizz-Buzz-SQL/Fizz-Buzz-SQL.sql: -------------------------------------------------------------------------------- 1 | create table digit(n int); 2 | 3 | insert into digit value(0),(1),(2),(3),(4),(5),(6),(7),(8),(9); 4 | 5 | set @var:=0, @var3:=3 ,@var5:=5; 6 | 7 | select if(fizz is null,if(buzz is null,var,buzz),if(buzz is null,fizz,concat(fizz,buzz))) as '?' from ( 8 | select @var3:=@var3-1 , @var5:=@var5-1 , @var:=@var+1 as var, 9 | @fizz:=if (@var3=0,"fizz",null) as fizz, 10 | @buzz:=if (@var5=0,"buzz",null) as buzz, 11 | if (@var3=0,@var3:=3,null), 12 | if (@var5=0,@var5:=5,null) 13 | from( 14 | select d1.n + d2.n * 10 + 1 n 15 | from digit d1, digit d2 16 | ) d ) a 17 | ; -------------------------------------------------------------------------------- /Fizz-Buzz-SQL/README.md: -------------------------------------------------------------------------------- 1 | # Fizz-Buzz-SQL 2 | [エントリーblog](http://hironomiu.hatenablog.com/entry/2012/04/14/155758) -------------------------------------------------------------------------------- /Order-By-Rand/Order-By-Rand.sql: -------------------------------------------------------------------------------- 1 | set @rownum=0; 2 | 3 | drop table teams; 4 | 5 | create table teams (team varchar(30)); 6 | 7 | insert into teams values 8 | ; 9 | 10 | select (SELECT @rownum:=@rownum+1) as "登壇順", team as "登壇チーム" from 11 | (select team from teams order by rand()) c; 12 | -------------------------------------------------------------------------------- /Order-By-Rand/README.md: -------------------------------------------------------------------------------- 1 | # Order by rand 2 | MySQLのrand()とユーザー定義変数を使ったサンプルコード 3 | -------------------------------------------------------------------------------- /RDBMS-Nnormalization/README.md: -------------------------------------------------------------------------------- 1 | # RDBMS-Normalization 2 | -------------------------------------------------------------------------------- /RDBMS-Nnormalization/django_project/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/macos,django,python 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=macos,django,python 3 | 4 | ### Django ### 5 | *.log 6 | *.pot 7 | *.pyc 8 | __pycache__/ 9 | local_settings.py 10 | db.sqlite3 11 | db.sqlite3-journal 12 | media 13 | 14 | # If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/ 15 | # in your Git repository. Update and uncomment the following line accordingly. 16 | # /staticfiles/ 17 | 18 | ### Django.Python Stack ### 19 | # Byte-compiled / optimized / DLL files 20 | *.py[cod] 21 | *$py.class 22 | 23 | # C extensions 24 | *.so 25 | 26 | # Distribution / packaging 27 | .Python 28 | build/ 29 | develop-eggs/ 30 | dist/ 31 | downloads/ 32 | eggs/ 33 | .eggs/ 34 | parts/ 35 | sdist/ 36 | var/ 37 | wheels/ 38 | pip-wheel-metadata/ 39 | share/python-wheels/ 40 | *.egg-info/ 41 | .installed.cfg 42 | *.egg 43 | MANIFEST 44 | 45 | # PyInstaller 46 | # Usually these files are written by a python script from a template 47 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 48 | *.manifest 49 | *.spec 50 | 51 | # Installer logs 52 | pip-log.txt 53 | pip-delete-this-directory.txt 54 | 55 | # Unit test / coverage reports 56 | htmlcov/ 57 | .tox/ 58 | .nox/ 59 | .coverage 60 | .coverage.* 61 | .cache 62 | nosetests.xml 63 | coverage.xml 64 | *.cover 65 | *.py,cover 66 | .hypothesis/ 67 | .pytest_cache/ 68 | pytestdebug.log 69 | 70 | # Translations 71 | *.mo 72 | 73 | # Django stuff: 74 | 75 | # Flask stuff: 76 | instance/ 77 | .webassets-cache 78 | 79 | # Scrapy stuff: 80 | .scrapy 81 | 82 | # Sphinx documentation 83 | docs/_build/ 84 | doc/_build/ 85 | 86 | # PyBuilder 87 | target/ 88 | 89 | # Jupyter Notebook 90 | .ipynb_checkpoints 91 | 92 | # IPython 93 | profile_default/ 94 | ipython_config.py 95 | 96 | # pyenv 97 | .python-version 98 | 99 | # pipenv 100 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 101 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 102 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 103 | # install all needed dependencies. 104 | #Pipfile.lock 105 | 106 | # poetry 107 | #poetry.lock 108 | 109 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 110 | __pypackages__/ 111 | 112 | # Celery stuff 113 | celerybeat-schedule 114 | celerybeat.pid 115 | 116 | # SageMath parsed files 117 | *.sage.py 118 | 119 | # Environments 120 | # .env 121 | .env/ 122 | .venv/ 123 | env/ 124 | venv/ 125 | ENV/ 126 | env.bak/ 127 | venv.bak/ 128 | pythonenv* 129 | 130 | # Spyder project settings 131 | .spyderproject 132 | .spyproject 133 | 134 | # Rope project settings 135 | .ropeproject 136 | 137 | # mkdocs documentation 138 | /site 139 | 140 | # mypy 141 | .mypy_cache/ 142 | .dmypy.json 143 | dmypy.json 144 | 145 | # Pyre type checker 146 | .pyre/ 147 | 148 | # pytype static type analyzer 149 | .pytype/ 150 | 151 | # operating system-related files 152 | # file properties cache/storage on macOS 153 | *.DS_Store 154 | # thumbnail cache on Windows 155 | Thumbs.db 156 | 157 | # profiling data 158 | .prof 159 | 160 | 161 | ### macOS ### 162 | # General 163 | .DS_Store 164 | .AppleDouble 165 | .LSOverride 166 | 167 | # Icon must end with two \r 168 | Icon 169 | 170 | 171 | # Thumbnails 172 | ._* 173 | 174 | # Files that might appear in the root of a volume 175 | .DocumentRevisions-V100 176 | .fseventsd 177 | .Spotlight-V100 178 | .TemporaryItems 179 | .Trashes 180 | .VolumeIcon.icns 181 | .com.apple.timemachine.donotpresent 182 | 183 | # Directories potentially created on remote AFP share 184 | .AppleDB 185 | .AppleDesktop 186 | Network Trash Folder 187 | Temporary Items 188 | .apdisk 189 | 190 | ### Python ### 191 | # Byte-compiled / optimized / DLL files 192 | 193 | # C extensions 194 | 195 | # Distribution / packaging 196 | 197 | # PyInstaller 198 | # Usually these files are written by a python script from a template 199 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 200 | 201 | # Installer logs 202 | 203 | # Unit test / coverage reports 204 | 205 | # Translations 206 | 207 | # Django stuff: 208 | 209 | # Flask stuff: 210 | 211 | # Scrapy stuff: 212 | 213 | # Sphinx documentation 214 | 215 | # PyBuilder 216 | 217 | # Jupyter Notebook 218 | 219 | # IPython 220 | 221 | # pyenv 222 | 223 | # pipenv 224 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 225 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 226 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 227 | # install all needed dependencies. 228 | 229 | # poetry 230 | 231 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 232 | 233 | # Celery stuff 234 | 235 | # SageMath parsed files 236 | 237 | # Environments 238 | # .env 239 | 240 | # Spyder project settings 241 | 242 | # Rope project settings 243 | 244 | # mkdocs documentation 245 | 246 | # mypy 247 | 248 | # Pyre type checker 249 | 250 | # pytype static type analyzer 251 | 252 | # operating system-related files 253 | # file properties cache/storage on macOS 254 | # thumbnail cache on Windows 255 | 256 | # profiling data 257 | 258 | 259 | # End of https://www.toptal.com/developers/gitignore/api/macos,django,python -------------------------------------------------------------------------------- /RDBMS-Nnormalization/django_project/README.md: -------------------------------------------------------------------------------- 1 | # django_project_normalization 2 | 3 | 正規化ハンズオンで作成したテーブル定義を Gjango で実装したサンプルコード 4 | 5 | ## 動作環境 6 | 7 | Python 8 | 9 | ``` 10 | $ python3 --version 11 | Python 3.9.0 12 | ``` 13 | 14 | Django 15 | 16 | ``` 17 | $ python3 -m django --version 18 | 3.1.7 19 | ``` 20 | 21 | ## 作成メモ 22 | 23 | ### Create Project 24 | 25 | ``` 26 | $ django-admin startproject djang_project 27 | ``` 28 | 29 | ### Create App 30 | 31 | ``` 32 | $ python3 manage.py startapp normalization 33 | ``` 34 | 35 | ### Initial Migration 36 | 37 | 管理画面用のマイグレーション 38 | 39 | ``` 40 | $ python3 manage.py migrate 41 | ``` 42 | 43 | ### Create SuperUser 44 | 45 | 管理画面のユーザ作成。パスワードは`hogehoge`で作成 46 | 47 | ``` 48 | $ python3 manage.py createsuperuser 49 | Username (leave blank to use 'h-miura'): admin 50 | Email address: hoge@hoge.com 51 | Password: 52 | Password (again): 53 | The password is too similar to the email address. 54 | Bypass password validation and create user anyway? [y/N]: y 55 | Superuser created successfully. 56 | ``` 57 | 58 | ## run server 59 | 60 | ビルトインサーバの起動。PORT は任意で指定(例は`9999`を指定) 61 | 62 | ``` 63 | $ python3 manage.py runserver 9999 64 | ``` 65 | 66 | ## マイグレーション 67 | 68 | 今回のテーブルのマイグレーション。`models.py`に対してて定義したら都度行う 69 | 70 | 作成 71 | 72 | ``` 73 | $ python3 manage.py makemigrations normalization 74 | Migrations for 'normalization': 75 | normalization/migrations/0001_initial.py 76 | - Create model Customers 77 | - Create model Items 78 | - Create model Orders 79 | - Create model Order_details 80 | - Create constraint order_id_item_id_unique on model order_details 81 | ``` 82 | 83 | 適用 84 | 85 | ``` 86 | $ python3 manage.py migrate 87 | Operations to perform: 88 | Apply all migrations: admin, auth, contenttypes, normalization, sessions 89 | Running migrations: 90 | Applying normalization.0001_initial... OK 91 | ``` 92 | 93 | ## 管理画面に追加 94 | 95 | 管理画面からテーブル操作を行えるよう`admin.py`に都度追加(以下例) 96 | 97 | ``` 98 | from django.contrib import admin 99 | from .models import Customers, Items, Orders, Order_details 100 | 101 | admin.site.register(Customers) 102 | admin.site.register(Items) 103 | admin.site.register(Orders) 104 | admin.site.register(Order_details) 105 | ``` 106 | -------------------------------------------------------------------------------- /RDBMS-Nnormalization/django_project/django_project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/RDBMS-Tips/44565285e84cfa867c1b3add142508beba3f400e/RDBMS-Nnormalization/django_project/django_project/__init__.py -------------------------------------------------------------------------------- /RDBMS-Nnormalization/django_project/django_project/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for django_project project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_project.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /RDBMS-Nnormalization/django_project/django_project/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for django_project project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.1.7. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.1/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | 15 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 16 | BASE_DIR = Path(__file__).resolve().parent.parent 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 't@2vb!bz!592hv2(4*=9t+9!m7iitawrr0umqu&s9azbnqzl#l' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'normalization.apps.NormalizationConfig', 35 | 'django.contrib.admin', 36 | 'django.contrib.auth', 37 | 'django.contrib.contenttypes', 38 | 'django.contrib.sessions', 39 | 'django.contrib.messages', 40 | 'django.contrib.staticfiles', 41 | ] 42 | 43 | MIDDLEWARE = [ 44 | 'django.middleware.security.SecurityMiddleware', 45 | 'django.contrib.sessions.middleware.SessionMiddleware', 46 | 'django.middleware.common.CommonMiddleware', 47 | 'django.middleware.csrf.CsrfViewMiddleware', 48 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 | 'django.contrib.messages.middleware.MessageMiddleware', 50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 51 | ] 52 | 53 | ROOT_URLCONF = 'django_project.urls' 54 | 55 | TEMPLATES = [ 56 | { 57 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 58 | 'DIRS': [], 59 | 'APP_DIRS': True, 60 | 'OPTIONS': { 61 | 'context_processors': [ 62 | 'django.template.context_processors.debug', 63 | 'django.template.context_processors.request', 64 | 'django.contrib.auth.context_processors.auth', 65 | 'django.contrib.messages.context_processors.messages', 66 | ], 67 | }, 68 | }, 69 | ] 70 | 71 | WSGI_APPLICATION = 'django_project.wsgi.application' 72 | 73 | 74 | # Database 75 | # https://docs.djangoproject.com/en/3.1/ref/settings/#databases 76 | 77 | DATABASES = { 78 | 'default': { 79 | 'ENGINE': 'django.db.backends.sqlite3', 80 | 'NAME': BASE_DIR / 'db.sqlite3', 81 | } 82 | } 83 | 84 | 85 | # Password validation 86 | # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators 87 | 88 | AUTH_PASSWORD_VALIDATORS = [ 89 | { 90 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 91 | }, 92 | { 93 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 94 | }, 95 | { 96 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 97 | }, 98 | { 99 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 100 | }, 101 | ] 102 | 103 | 104 | # Internationalization 105 | # https://docs.djangoproject.com/en/3.1/topics/i18n/ 106 | 107 | LANGUAGE_CODE = 'ja' 108 | 109 | TIME_ZONE = 'Asia/Tokyo' 110 | 111 | USE_I18N = True 112 | 113 | USE_L10N = True 114 | 115 | USE_TZ = True 116 | 117 | 118 | # Static files (CSS, JavaScript, Images) 119 | # https://docs.djangoproject.com/en/3.1/howto/static-files/ 120 | 121 | STATIC_URL = '/static/' 122 | -------------------------------------------------------------------------------- /RDBMS-Nnormalization/django_project/django_project/urls.py: -------------------------------------------------------------------------------- 1 | """django_project URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.1/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path, include 18 | 19 | urlpatterns = [ 20 | path('admin/', admin.site.urls), 21 | path("", include('normalization.urls')), 22 | ] 23 | -------------------------------------------------------------------------------- /RDBMS-Nnormalization/django_project/django_project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for django_project project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_project.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /RDBMS-Nnormalization/django_project/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_project.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /RDBMS-Nnormalization/django_project/normalization/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/RDBMS-Tips/44565285e84cfa867c1b3add142508beba3f400e/RDBMS-Nnormalization/django_project/normalization/__init__.py -------------------------------------------------------------------------------- /RDBMS-Nnormalization/django_project/normalization/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Customers, Items, Orders, Order_details 3 | 4 | admin.site.register(Customers) 5 | admin.site.register(Items) 6 | admin.site.register(Orders) 7 | admin.site.register(Order_details) 8 | -------------------------------------------------------------------------------- /RDBMS-Nnormalization/django_project/normalization/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class NormalizationConfig(AppConfig): 5 | name = 'normalization' 6 | -------------------------------------------------------------------------------- /RDBMS-Nnormalization/django_project/normalization/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2021-04-07 01:10 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | import django.utils.timezone 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='Customers', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('name', models.CharField(max_length=100, verbose_name='受注先')), 21 | ('address', models.CharField(max_length=100, verbose_name='受注先住所')), 22 | ('created_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='作成日時')), 23 | ('updated_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='更新日時')), 24 | ], 25 | options={ 26 | 'db_table': 'customers', 27 | }, 28 | ), 29 | migrations.CreateModel( 30 | name='Items', 31 | fields=[ 32 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 33 | ('name', models.CharField(max_length=100, verbose_name='商品')), 34 | ('price', models.IntegerField(verbose_name='単価')), 35 | ('created_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='作成日時')), 36 | ('updated_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='更新日時')), 37 | ], 38 | options={ 39 | 'db_table': 'items', 40 | }, 41 | ), 42 | migrations.CreateModel( 43 | name='Orders', 44 | fields=[ 45 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 46 | ('order_date', models.DateField(default=django.utils.timezone.now, verbose_name='受注日')), 47 | ('created_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='作成日時')), 48 | ('updated_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='更新日時')), 49 | ('customer', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='normalization.customers', verbose_name='受注先')), 50 | ], 51 | options={ 52 | 'db_table': 'orders', 53 | }, 54 | ), 55 | migrations.CreateModel( 56 | name='Order_details', 57 | fields=[ 58 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 59 | ('item_quantity', models.IntegerField(verbose_name='数量')), 60 | ('created_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='作成日時')), 61 | ('updated_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='更新日時')), 62 | ('item', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='normalization.items', verbose_name='商品ID')), 63 | ('order', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='normalization.orders', verbose_name='受注ID')), 64 | ], 65 | options={ 66 | 'db_table': 'order_details', 67 | }, 68 | ), 69 | migrations.AddConstraint( 70 | model_name='order_details', 71 | constraint=models.UniqueConstraint(fields=('order_id', 'item_id'), name='order_id_item_id_unique'), 72 | ), 73 | ] 74 | -------------------------------------------------------------------------------- /RDBMS-Nnormalization/django_project/normalization/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/RDBMS-Tips/44565285e84cfa867c1b3add142508beba3f400e/RDBMS-Nnormalization/django_project/normalization/migrations/__init__.py -------------------------------------------------------------------------------- /RDBMS-Nnormalization/django_project/normalization/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils import timezone 3 | 4 | 5 | class Customers(models.Model): 6 | name = models.CharField('受注先', max_length=100) 7 | address = models.CharField('受注先住所', max_length=100) 8 | created_at = models.DateTimeField('作成日時', default=timezone.now) 9 | updated_at = models.DateTimeField('更新日時', default=timezone.now) 10 | 11 | class Meta: 12 | db_table = "customers" 13 | 14 | def __str__(self): 15 | return self.name 16 | 17 | 18 | class Items(models.Model): 19 | name = models.CharField('商品', max_length=100) 20 | price = models.IntegerField('単価') 21 | created_at = models.DateTimeField('作成日時', default=timezone.now) 22 | updated_at = models.DateTimeField('更新日時', default=timezone.now) 23 | 24 | class Meta: 25 | db_table = "items" 26 | 27 | def __str__(self): 28 | return self.name 29 | 30 | 31 | class Orders(models.Model): 32 | order_date = models.DateField('受注日', default=timezone.now) 33 | customer = models.ForeignKey( 34 | Customers, verbose_name='受注先', on_delete=models.PROTECT, 35 | ) 36 | created_at = models.DateTimeField('作成日時', default=timezone.now) 37 | updated_at = models.DateTimeField('更新日時', default=timezone.now) 38 | 39 | class Meta: 40 | db_table = "orders" 41 | 42 | def __str__(self): 43 | return f'受注番号:{self.id}-受注日:{self.order_date}' 44 | 45 | 46 | class Order_details(models.Model): 47 | order = models.ForeignKey( 48 | Orders, verbose_name='受注ID', on_delete=models.PROTECT, 49 | ) 50 | item = models.ForeignKey( 51 | Items, verbose_name='商品ID', on_delete=models.PROTECT, 52 | ) 53 | item_quantity = models.IntegerField('数量') 54 | created_at = models.DateTimeField('作成日時', default=timezone.now) 55 | updated_at = models.DateTimeField('更新日時', default=timezone.now) 56 | 57 | class Meta: 58 | db_table = "order_details" 59 | constraints = [ 60 | models.UniqueConstraint( 61 | fields=["order_id", "item_id"], 62 | name="order_id_item_id_unique" 63 | ), 64 | ] 65 | 66 | def __str__(self): 67 | return f'id:{self.id}-受注番号:{self.order_id}-商品番号:{self.item_id}' 68 | -------------------------------------------------------------------------------- /RDBMS-Nnormalization/django_project/normalization/templates/normalization/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | {% block content %} 12 | {% endblock %} 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /RDBMS-Nnormalization/django_project/normalization/templates/normalization/order_details_list.html: -------------------------------------------------------------------------------- 1 | {% extends 'normalization/base.html' %} 2 | {% block content %} 3 |

RDBMS-Normalization

4 |

受注情報

5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {% for order_detail in order_details_list %} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {% endfor %} 31 | 32 |
ID受注番号受注日受注先受注先住所商品数量単価
{{order_detail.id}}{{order_detail.order_id}}{{order_detail.order.order_date}}{{order_detail.order.customer}}{{order_detail.order.customer.address}}{{order_detail.item}}{{order_detail.item_quantity}}{{order_detail.item.price}}
33 | 34 |

商品DDL

35 |

36 | CREATE TABLE "items" (
37 | "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
38 | "name" varchar(100) NOT NULL,
39 | "price" integer NOT NULL,
40 | "created_at" datetime NOT NULL,
41 | "updated_at" datetime NOT NULL) 42 |

43 |

受注先DDL

44 |

45 | CREATE TABLE "customers" (
46 | "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
47 | "name" varchar(100) NOT NULL,
48 | "address" varchar(100) NOT NULL,
49 | "created_at" datetime NOT NULL,
50 | "updated_at" datetime NOT NULL) 51 |

52 |

受注

53 |

54 | CREATE TABLE "orders" (
55 | "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
56 | "order_date" date NOT NULL,
57 | "created_at" datetime NOT NULL,
58 | "updated_at" datetime NOT NULL,
59 | "customer_id" integer NOT NULL REFERENCES "customers" ("id") DEFERRABLE INITIALLY DEFERRED) 60 |

61 |

受注明細

62 |

CREATE TABLE "order_details" (
63 | "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
64 | "item_quantity" integer NOT NULL,
65 | "created_at" datetime NOT NULL,
66 | "updated_at" datetime NOT NULL,
67 | "item_id" integer NOT NULL REFERENCES "items" ("id") DEFERRABLE INITIALLY DEFERRED,
68 | "order_id" integer NOT NULL REFERENCES "orders" ("id") DEFERRABLE INITIALLY DEFERRED,
69 | CONSTRAINT "order_id_item_id_unique" UNIQUE ("order_id", "item_id")) 70 |

71 | {% endblock %} -------------------------------------------------------------------------------- /RDBMS-Nnormalization/django_project/normalization/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /RDBMS-Nnormalization/django_project/normalization/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | app_name = "normalization" 5 | 6 | urlpatterns = [ 7 | path("", views.IndexView.as_view(), name="index") 8 | ] 9 | -------------------------------------------------------------------------------- /RDBMS-Nnormalization/django_project/normalization/views.py: -------------------------------------------------------------------------------- 1 | from django.views import generic 2 | from .models import Order_details 3 | 4 | 5 | class IndexView(generic.ListView): 6 | model = Order_details 7 | 8 | def get_queryset(self): 9 | result = self.model.objects.all() 10 | order_id = self.request.GET.get('order_id') 11 | if order_id != '' and isinstance(order_id, str) == True: 12 | result = self.model.objects.filter(order=order_id) 13 | 14 | return result 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RDBMS Tips 2 | 3 | ## sql-training 4 | 5 | 実務で良く使われる SQL 文の学習 6 | 7 | ## performance-tuning 8 | 9 | RDBMS パフォーマンスチューニングについて 10 | 11 | ## Fizz-Buzz-SQL 12 | 13 | MySQL のユーザ定義関数を使った FizzBuzz 14 | 15 | ## Order-By-Rand 16 | 17 | MySQL の rand()とユーザー定義変数を使ったサンプルコード 18 | 19 | ## RDBMS-Normalization 20 | 21 | ### django_project 22 | 23 | 正規化ハンズオンで作成したテーブル定義を Django で作成したサンプルコード 24 | 25 | ## CSV-Column-To-Row 26 | 27 | MySQL で CSV(カンマ区切り)のカラムを行に変換する 28 | 29 | ## SQL-Tree-Structure 30 | 31 | [SQL-Tree-Structure(別リポジトリ)](https://github.com/hironomiu/SQL-Tree-Structure) 32 | 33 | 階層構造データ(隣接リスト、経路列挙、入れ子集合、閉包テーブル)のサンプルコード -------------------------------------------------------------------------------- /performance-tuning/README.md: -------------------------------------------------------------------------------- 1 | # performance-tuning 2 | 3 | [part0](./part0.md) 演習環境の準備 4 | 5 | [part1](./part1.md) パフォーマンスチューニングその1 6 | 7 | [part2](./part2.md) パフォーマンスチューニングその2 8 | -------------------------------------------------------------------------------- /performance-tuning/images/arc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/RDBMS-Tips/44565285e84cfa867c1b3add142508beba3f400e/performance-tuning/images/arc.png -------------------------------------------------------------------------------- /performance-tuning/images/btree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/RDBMS-Tips/44565285e84cfa867c1b3add142508beba3f400e/performance-tuning/images/btree.png -------------------------------------------------------------------------------- /performance-tuning/images/multi-column.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/RDBMS-Tips/44565285e84cfa867c1b3add142508beba3f400e/performance-tuning/images/multi-column.png -------------------------------------------------------------------------------- /performance-tuning/images/nested.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/RDBMS-Tips/44565285e84cfa867c1b3add142508beba3f400e/performance-tuning/images/nested.png -------------------------------------------------------------------------------- /performance-tuning/images/partition.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/RDBMS-Tips/44565285e84cfa867c1b3add142508beba3f400e/performance-tuning/images/partition.png -------------------------------------------------------------------------------- /performance-tuning/images/replication.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/RDBMS-Tips/44565285e84cfa867c1b3add142508beba3f400e/performance-tuning/images/replication.png -------------------------------------------------------------------------------- /performance-tuning/images/sharding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hironomiu/RDBMS-Tips/44565285e84cfa867c1b3add142508beba3f400e/performance-tuning/images/sharding.png -------------------------------------------------------------------------------- /performance-tuning/part0.md: -------------------------------------------------------------------------------- 1 | # RDBMS パフォーマンスチューニング入門 Part0 2 | 3 | ## 準備 4 | 5 | ### データの準備 6 | 7 | [part1,part2 で利用するテーブルデータ:GitHub/SQL-DATA](https://github.com/hironomiu/SQL-DATA)を clone する 8 | 9 | ### Docker 10 | 11 | Dockerfile を作成し構築する場合はこちら[MySQL:GitHub/Docker-DockerCompose-Training/recipe-mysql-dockerfile](https://github.com/hironomiu/Docker-DockerCompose-Training/tree/main/recipe-mysql-dockerfile) 12 | 13 | コマンドで構築する場合はこちら[MySQL:Docker-DockerCompose-Training/recipe-mysql-no-dockerfile](https://github.com/hironomiu/Docker-DockerCompose-Training/tree/main/recipe-mysql-no-dockerfile) 14 | 15 | my.cnf を編集するため vim のインストール 16 | 17 | ``` 18 | docker container exec -it mysqld bash 19 | 20 | apt update 21 | apt install -y vim 22 | 23 | exit 24 | ``` 25 | 26 | ### DB の作成 27 | 28 | bash モードで接続 29 | 30 | ``` 31 | docker container exec -it mysqld bash 32 | ``` 33 | 34 | パスワードについて適時変更(値、直接入力せずなど)すること 35 | 36 | ``` 37 | mysql -u root -pmysql -e "create database part1;" 38 | ``` 39 | 40 | bash モードから exit 41 | 42 | ``` 43 | exit 44 | ``` 45 | 46 | ### データの投入 47 | 48 | ※ 以下実行の際に MySQL Client が必要 49 | 50 | ※ [part1 で利用するテーブルデータ:GitHub/SQL-DATA](https://github.com/hironomiu/SQL-DATA)を clone したディレクトリ直下に移動し以下を実行 51 | 52 | ※ データの投入はマシンによって 30 分以上掛かるため注意 53 | 54 | linux 55 | 56 | パスワードについて適時変更(値、直接入力せずなど)すること 57 | 58 | ``` 59 | zcat users.dump.gz | mysql -u root -pmysql -h127.0.0.1 part1 60 | zcat messages.dump.gz | mysql -u root -pmysql -h127.0.0.1 part1 61 | ``` 62 | 63 | mac 64 | 65 | パスワードについて適時変更(値、直接入力せずなど)すること 66 | 67 | ``` 68 | gzcat users.dump.gz | mysql -u root -pmysql -h127.0.0.1 part1 69 | gzcat messages.dump.gz | mysql -u root -pmysql -h127.0.0.1 part1 70 | ``` 71 | -------------------------------------------------------------------------------- /performance-tuning/part1.md: -------------------------------------------------------------------------------- 1 | # RDBMS パフォーマンスチューニング入門 Part1 2 | 3 | ## 準備 4 | 5 | 実演を手元で動かしたい場合は[part0](./part0.md)を行うこと 6 | 7 | ## 広義のパフォーマンスチューニングとは 8 | 9 | - 確保したリソース(CPU、メモリ、Disk、NW など)でシステムを安定稼働させる、期待する時間で処理を行うための改善活動 10 | 11 | - 改善活動を行うためにどのように稼働、処理を確認するか? 12 | - 各種メトリクス、ミドルウェアの実行状況をモニタリング 13 | - 監視に落とし込む 14 | - サービスレベルでの死活監視など 15 | - 今回はミドルウェアの実行状況の文脈から SQL の実行状況をモニタリングする「スローログ」について触れます 16 | - スローログで問題の可能性がある SQL を抽出 17 | - 対象の SQL を explain にて評価 18 | - 対策の実施(今回は INDEX の作成) 19 | - 対処の SQL を再評価 20 | - パフォーマンス改善されていることを確認 21 | 22 | ## (今回のお題の)RDBMS パフォーマンスチューニングとは 23 | 24 | 大きくわけると 2 つのチューニングがあります 25 | 26 | - SQL チューニング 27 | 28 | - SQL 文を意図した実行計画(アクセスパス)になるよう SQL 文の修正や誘導(ヒント句 インデックスの指定、駆動表の指定、探索アルゴリズムの指定など)、`または`インデックスを貼り走査のパフォーマンスチューニング(性能改善、性能最適)をはかるアプローチ 29 | - `または`と記載したがこれは`or`の関係ではなくどちらも用いることが多く`and`の関係 30 | - **今回はここを中心に入門します** 31 | 32 | - システムチューニング 33 | 34 | - RDBMS のパラメータ(メモリ(共有、セッション固有など)、ファイルパス、何かしらの上限値(セッション数、プロセス数、スレッド数など)、アルゴリズム(LRU など)、その他)、下位レイヤのパラメータ(カーネルパラメータ(ファイルディスクリプタ、I/O スケジューラ、その他)、など)、ハードウェアの追加(CPU、メモリなど)や上位機種に変更などでパフォーマンスチューニング(性能改善、性能最適)をはかるアプローチ 35 | 36 | - なぜ改善だけではなく最適と言う言葉を使ったか 37 | - SQL 文をチューニングしたとしても実際には様々な処理が同時実行されているなかで動作するため適切(最適)なリソースを用いて動作することが結果として重要になる。 38 | - Bad チューニングした SQL 文のせいで他の SQL 文が遅くなる 39 | 40 | ## 頭の片隅においておく観点 41 | 42 | 今回のパフォーマンスチューニングにて頭の片隅に置いておく観点 43 | 44 | - レスポンスタイム 45 | 46 | - 入力を与えてから、応答が返るまでにかかるの時間のこと 47 | - OLTP(オンライン処理)、ユニークなデータを取り出す処理向き 48 | - INDEX を用いた探索 49 | - 今回の話はここを意識する話が中心です 50 | - elapsed time = cpu time + α(ロック待ち、I/O待ちなど) 51 | 52 | - スループット 53 | 54 | - 単位時間あたりに処理できる量のこと 55 | - バッチ処理向きな観点 56 | 57 | - I/O バウンド 58 | 59 | - 高ワークロード時に「DiskI/O」が性能の頭打ちになるケース 60 | - RDBMS は I/O バウンドに陥りやすい 61 | - 今回の話はここを意識する話となります 62 | 63 | - CPU バウンド 64 | 65 | - 高ワークロード時に「CPU」が性能の頭打ちになるケース 66 | - Web サーバ、アプリケーションサーバなどは CPU バウンドに陥りやすい 67 | 68 | - キャッシュヒット率 69 | 70 | - データをメモリからアクセスするコストを 1、ストレージからアクセスするコストを 100 と想定し、メモリにデータが存在しない場合ストレージにアクセスし得るものとする 71 | - ヒット率 100%の場合のコストは? -> 100 72 | - ヒット率 99%の場合のコストは? -> 99 + 100 -> 199 73 | - 必要なデータがメモリにあるのが好ましいイメージを持つ 74 | - ここで指すメモリとは? 75 | - RDBMS のバッファキャッシュ機能を指す。MySQL では`innodb_buffer_pool_size`など 76 | - メタな用語で`共有メモリ`と呼ぶこともある 77 | 78 | - 計算量 79 | - 線形探索 最良`0(1)`、最悪`O(N)`、平均`O(N/2)` 80 | - テーブルのフルスキャンは`O(N)`に近い 81 | - 計算量は 1 件のデータを突き止めるがフルスキャンは N 件のデータから M 件突き止めるので厳密には違う 82 | - N=M の検索結果を返すこともある 83 | - 二分木 `O(log2N)` 84 | - 今回学ぶ B+tree のベース 85 | 86 | ## MySQL アーキテクチャ 87 | 88 | 主に認証、認証後のクライアントからの SQL の要求、サーバ側の受け付け、レスポンスまでの基本的な動作について 89 | 90 | ![arc](./images/arc.png) 91 | 92 | 今回主にチューニングで行う箇所は ④ ~ ⑧ での箇所で実行される SQL についてです。 93 | 94 | - オプティマイザ 95 | - ④ ~ ⑧ ではオプティマイザと呼ばれる存在が SQL 文を解析後、最適なデータ走査方法の策定 = 実行計画の策定を行います。この実行計画を元にデータ走査が行われ ⑧ で結果(SELECT なら結果セットと実行結果、その他は実行結果)が返されます。 96 | - 今回の`最適`はレスポンスタイムが早くなることを前提に進めます 97 | 98 | ## 実演 99 | 100 | これからする一連の流れを一旦実演 101 | 102 | - スローログの設定確認(※設定後MySQLの再起動が必要) 103 | 104 | ``` 105 | cat /etc/mysql/my.cnf 106 | 107 | slow_query_log=ON 108 | slow_query_log_file=/var/lib/mysql/slow.log 109 | long_query_time=0.1 110 | ``` 111 | 112 | `my.cnf`の配置パスは以下で確認可能(今回は MySQL 作成時に存在した`/etc/mysql/my.cnf`に記述) 113 | 114 | ``` 115 | # mysql --help | grep my.cnf 116 | order of preference, my.cnf, $MYSQL_TCP_PORT, 117 | /etc/my.cnf /etc/mysql/my.cnf ~/.my.cnf 118 | ``` 119 | 120 | - スローログの出力内容を`tail`で確認 121 | 122 | ``` 123 | tail -f /var/lib/mysql/slow.log 124 | ``` 125 | 126 | - テーブル構成 127 | 128 | ``` 129 | show create table users; 130 | 131 | CREATE TABLE `users` ( 132 | `id` int NOT NULL AUTO_INCREMENT, 133 | `name` varchar(50) NOT NULL, 134 | `email` varchar(100) NOT NULL, 135 | `password` varchar(255) NOT NULL, 136 | `birthday` datetime NOT NULL, 137 | `profile1` text, 138 | `profile2` text, 139 | `created_at` datetime NOT NULL, 140 | `updated_at` datetime NOT NULL, 141 | PRIMARY KEY (`id`) 142 | ) ENGINE=InnoDB AUTO_INCREMENT=1000008 DEFAULT CHARSET=utf8mb3 143 | 144 | 1 row in set (0.01 sec) 145 | ``` 146 | 147 | - レコード数の確認 148 | 149 | ``` 150 | mysql> select count(*) from users; 151 | +----------+ 152 | | count(*) | 153 | +----------+ 154 | | 1000006 | 155 | +----------+ 156 | 1 row in set (12.05 sec) 157 | ``` 158 | 159 | - 今回チューニングする SQL の実行 160 | 161 | ``` 162 | mysql> select name from users where email = "o3xE22lXIlWJCdd@example.com"; 163 | 164 | +-----------------+ 165 | | name | 166 | +-----------------+ 167 | | o3xE22lXIlWJCdd | 168 | +-----------------+ 169 | 1 row in set (16.25 sec) 170 | ``` 171 | 172 | - スローログの出力内容を確認 173 | 174 | - 2 件出力されていること 175 | 176 | ``` 177 | # tail -f /var/lib/mysql/slow.log 178 | # User@Host: root[root] @ [172.17.0.1] Id: 8 179 | # Query_time: 12.034466 Lock_time: 0.000113 Rows_sent: 1 Rows_examined: 0 180 | use part1; 181 | SET timestamp=1625534799; 182 | select count(*) from users; 183 | # Time: 2021-07-06T01:35:24.616980Z 184 | # User@Host: root[root] @ [172.17.0.1] Id: 8 185 | # Query_time: 16.227654 Lock_time: 0.003036 Rows_sent: 1 Rows_examined: 1000006 186 | SET timestamp=1625535308; 187 | select name from users where email = "o3xE22lXIlWJCdd@example.com"; 188 | ``` 189 | 190 | - explain の実行 191 | 192 | ``` 193 | mysql> explain select name from users where email = "o3xE22lXIlWJCdd@example.com"\G 194 | *************************** 1. row *************************** 195 | id: 1 196 | select_type: SIMPLE 197 | table: users 198 | partitions: NULL 199 | type: ALL 200 | possible_keys: NULL 201 | key: NULL 202 | key_len: NULL 203 | ref: NULL 204 | rows: 750105 205 | filtered: 10.00 206 | Extra: Using where 207 | 1 row in set, 1 warning (0.01 sec) 208 | ``` 209 | 210 | 以降のチューニングはスローログ、Explain の章を学び再度実演にて解決します 211 | 212 | ## スローログ 213 | 214 | RDBMS(MySQL)の性能改善に向けたロギング戦略、日々ロギングし性能改善の材料とする。スローログが起点となるケースもあるが、他のメトリクスで閾値を超えた時を起点として、その時刻前後にスローログにロギングされた SQL を確認することなどが多い 215 | 216 | - スローログの設定方法 217 | - 出力可否、出力先(ファイルパス)、閾値(秒)の設定 218 | 219 | ``` 220 | /etc/mysql/my.cnf 221 | 222 | [mysqld] 223 | slow_query_log=ON 224 | slow_query_log_file=/var/lib/mysql/slow.log 225 | long_query_time=0.1 226 | ``` 227 | 228 | ``` 229 | mysql> show variables like 'slow_query%'; 230 | +---------------------+-------------------------+ 231 | | Variable_name | Value | 232 | +---------------------+-------------------------+ 233 | | slow_query_log | ON | 234 | | slow_query_log_file | /var/lib/mysql/slow.log | 235 | +---------------------+-------------------------+ 236 | 2 rows in set (0.00 sec) 237 | 238 | mysql> show variables like 'long%'; 239 | +-----------------+----------+ 240 | | Variable_name | Value | 241 | +-----------------+----------+ 242 | | long_query_time | 0.100000 | 243 | +-----------------+----------+ 244 | 1 row in set (0.00 sec) 245 | ``` 246 | 247 | - 出力例 248 | ターミナル 1(ターミナル  2 で実行した SQL が閾値を超えた場合出力される。ここでは`select count(*) from users;`が実行された SQL が出力されている) 249 | 250 | ``` 251 | $ tail -f /var/lib/mysql/slow.log 252 | 253 | # Time: 2021-07-06T01:26:51.852561Z 254 | # User@Host: root[root] @ [172.17.0.1] Id: 8 255 | # Query_time: 12.034466 Lock_time: 0.000113 Rows_sent: 1 Rows_examined: 0 256 | use part1; 257 | SET timestamp=1625534799; 258 | select count(*) from users; 259 | ``` 260 | 261 | - 実行 SQL 262 | ターミナル 2 263 | 264 | ``` 265 | mysql> show create table users; 266 | mysql> select count(*) from users; 267 | mysql> select name from users where email = "o3xE22lXIlWJCdd@example.com"; 268 | ``` 269 | 270 | - 出力例 271 | - ターミナル 1 の出力結果より`# Query_time: 14.961383 `、`Rows_sent: 1 Rows_examined: 1000006`から実行時間約 15 秒、返したレコード 1 件、読み込んだレコード 1000006 件 272 | - `読み込んだレコード1000006件`を返したレコード`Rows_sent: 1`に近づければ`# Query_time: 14.961383 `が縮小できると考える 273 | 274 | ``` 275 | # Time: 2021-07-06T01:56:32.193708Z 276 | # User@Host: root[root] @ [172.17.0.1] Id: 8 277 | # Query_time: 14.961383 Lock_time: 0.000140 Rows_sent: 1 Rows_examined: 1000006 278 | use part1; 279 | SET timestamp=1625536577; 280 | select name from users where email = "o3xE22lXIlWJCdd@example.com"; 281 | ``` 282 | 283 | ## Explain 284 | 285 | Explain は SQL 文の実行計画計画に関する情報を出力します。この出力結果から、ここでは基本的な実行計画の改善戦略について学んでいきます 286 | 287 | - Explaine 出力例 288 | 289 | ``` 290 | mysql> explain select name from users where email = "o3xE22lXIlWJCdd@example.com"\G 291 | *************************** 1. row *************************** 292 | id: 1 293 | select_type: SIMPLE 294 | table: users 295 | partitions: NULL 296 | type: ALL 297 | possible_keys: NULL 298 | key: NULL 299 | key_len: NULL 300 | ref: NULL 301 | rows: 750105 302 | filtered: 10.00 303 | Extra: Using where 304 | 1 row in set, 1 warning (0.01 sec) 305 | ``` 306 | 307 | - この出力結果からは、users テーブルから`table: users`、候補となる INDEX`possible_keys: NULL`が存在せず、実際に適用された INDEX もなく`key: NULL`、717622 件のレコードを走査`rows: 750105`したことがわかります 308 | - 注意 `rows: 750105`は統計値で実際には 1000006 レコード 309 | - スローログで触れたように返すレコード 1 件に`rows: 750105`を近づけることを考える 310 | 311 | ## B+tree INDEX アーキテクチャ 312 | 313 | RDBMS ではテーブル探索の機能として B+tree(B\*tree)INDEX は(ほぼ)必ず実装されています。INDEX は RDBMS でのテーブル探索を行う上で、とても重要な機能です。 314 | 315 | - どのように重要か? 316 | - 探索の際に`O(N)` -> `O(logFN*log2F)`に計算量を落とす 317 | - 大量のデータから少量のデータを探索する場合に有効 318 | - スローログ、Explain で解決したい課題`返すレコード`と`読み込むレコード`を近づけることができる 319 | 320 | B+treeINDEX 図 321 | 322 | ![B+tree](./images/btree.png) 323 | 324 | B+treeINDEX の構造は簡単にあらわすと、このような図になります。二分木と違い、ブロックデバイスを前提に、格納効率の最大化を行い、探索時の計算量は`O(logFN*log2F)`となります。 325 | 326 | - (注意)MySQL の PK は ClusteredINDEX のため leaf に Row(行)も格納される 327 | 328 | - Unique、Non Unique の違い Next Look Up) 329 | - この図で示した B+treeINDEX では Unique 探索、Non Unique 探索のどちらも実現できます 330 | - Unique では対象にヒットしたら次を look up しません 331 | - Non Unique では対象にヒットしたあとも次を look up します。look up 時に値が違う場合、そこで look up を終了します 332 | 333 | 図中では INDEX から探索しデータにアクセス(ルックアップ)されるるまでに 5 ページ(ブロック)のアクセスで実現されています。 334 | 335 | 例として、テーブルレコードが 100 万件、テーブル容量が 1Gbyte のテーブルがあるとします。 336 | 全レコードから特定の 1 件のレコードを要求した場合、計算量は O(n)になります。 337 | この場合 100 万件中の 1(もしくは n)件のデータを突き止めるために 1Gbyte の容量全てを走査して導き出します。 338 | これはいわゆる Full Scan の状態です。これを b+treeINDEX でこのテーブルを探索した場合、root,branch,leaf を探索し leaf から紐づくデータのアクセスで完了します。図中では 5 ページ(ブロック)のアクセスで導き出せています。この場合、root,branch,leaf,データ部のページサイズを 8Kbyte とした場合、40Kbyte のアクセスで探索が完了します。 339 | 340 | この例で、100 万レコードから 1 件(もしくは n 件)のデータが導きだされるケースで Full Scan と INDEX 探索では`1Gbyte対40byte`のデータ容量の走査の差が性能差となって現れます。 341 | 342 | - SQL でどのカラムを INDEX として貼るべきか? 343 | - 今回のスローログで見つかった SQL ならば`Where句`で指定されたカラムに設定し性能改善したか確認 344 | - カバリングインデックスと言う戦略も選択肢としてある(`select句`で指定されたカラムまで含める) 345 | 346 | ## SQL パフォーマンスチューニング実演 347 | 348 | ここからは FullScan の SQL(select 文)を explain で確認し確認する勘所、確認後の対策として B+treeINDEX を貼り、explain の内容の違い、検索時間の違いを見ていきます 349 | 350 | - 確認 テーブルサイズ 351 | - ディクショナリを用いた算出方法もあるが今回は FS 上のファイルサイズで確認する 352 | 353 | ``` 354 | # ll /var/lib/mysql/part1/users.ibd 355 | -rw-r----- 1 mysql mysql 4743757824 Jul 6 01:04 /var/lib/mysql/part1/users.ib 356 | ``` 357 | 358 | - 確認 テーブル構成 359 | 360 | ``` 361 | mysql> show create table users; 362 | 363 | CREATE TABLE `users` ( 364 | `id` int NOT NULL AUTO_INCREMENT, 365 | `name` varchar(50) NOT NULL, 366 | `email` varchar(100) NOT NULL, 367 | `password` varchar(255) NOT NULL, 368 | `birthday` datetime NOT NULL, 369 | `profile1` text, 370 | `profile2` text, 371 | `created_at` datetime NOT NULL, 372 | `updated_at` datetime NOT NULL, 373 | PRIMARY KEY (`id`) 374 | ) ENGINE=InnoDB AUTO_INCREMENT=1000008 DEFAULT CHARSET=utf8mb3 375 | 376 | 1 row in set (0.01 sec) 377 | ``` 378 | 379 | - 確認 レコード数(1000006 件) 380 | 381 | ``` 382 | mysql> select count(*) from users; 383 | +----------+ 384 | | count(*) | 385 | +----------+ 386 | | 1000006 | 387 | +----------+ 388 | 1 row in set (12.29 sec) 389 | ``` 390 | 391 | - パフォーマンスに問題がある SQL を実行し確認 392 | - `1 row in set (16.08 sec)`に注目 393 | 394 | ``` 395 | mysql> select name from users where email = "o3xE22lXIlWJCdd@example.com"; 396 | +-----------------+ 397 | | name | 398 | +-----------------+ 399 | | o3xE22lXIlWJCdd | 400 | +-----------------+ 401 | 1 row in set (16.08 sec) 402 | 403 | ``` 404 | 405 | - explain`rows`に注目しましょう(`rows: 750105`) 406 | 407 | ``` 408 | mysql> explain select name from users where email = "o3xE22lXIlWJCdd@example.com"\G 409 | *************************** 1. row *************************** 410 | id: 1 411 | select_type: SIMPLE 412 | table: users 413 | partitions: NULL 414 | type: ALL 415 | possible_keys: NULL 416 | key: NULL 417 | key_len: NULL 418 | ref: NULL 419 | rows: 750105 420 | filtered: 10.00 421 | Extra: Using where 422 | 1 row in set, 1 warning (0.01 sec) 423 | ``` 424 | 425 | - パフォーマンスに問題がある SQL の対策で INDEX の作成 426 | - 今回の話とはずれるが INDEX の作成時間は覚えておくことは大事 427 | - `Query OK, 0 rows affected (23.89 sec)` 428 | 429 | ``` 430 | mysql> alter table users add index email(`email`); 431 | Query OK, 0 rows affected (23.89 sec) 432 | Records: 0 Duplicates: 0 Warnings: 0 433 | ``` 434 | 435 | - 注意 今回 mail に INDEX を貼ったが Unique、Non Unique については検討していない 436 | - Unique INDEX の場合は以下 437 | 438 | ``` 439 | mysql> alter table users drop index email; 440 | mysql> alter table users add unique index email(`email`); 441 | Query OK, 0 rows affected (31.78 sec) 442 | Records: 0 Duplicates: 0 Warnings: 0 443 | ``` 444 | 445 | - explain`rows`に注目しましょう(`rows: 1`)、`possible_keys`に候補となるインデックス、`key`で実際に利用したインデックスが表示される(今回は`email`が利用された) 446 | 447 | ``` 448 | mysql> explain select name from users where email = "o3xE22lXIlWJCdd@example.com"\G 449 | *************************** 1. row *************************** 450 | id: 1 451 | select_type: SIMPLE 452 | table: users 453 | partitions: NULL 454 | type: ref 455 | possible_keys: email 456 | key: email 457 | key_len: 302 458 | ref: const 459 | rows: 1 460 | filtered: 100.00 461 | Extra: NULL 462 | 1 row in set, 1 warning (0.00 sec) 463 | ``` 464 | 465 | - 余談(`select句`のカラムを`name`->`email`にし SQL の動作の違いについて確認) 466 | - Extra の違いに注目 467 | - 古い Version だと`Using index,Using where`だったこともあり 468 | - カバリングインデックスなどと呼ばれる 469 | - これを応用すると今回の INDEX は`mail`のみで良いか? 470 | - ケースバイケースで`mail,name`が良い場合もある 471 | 472 | ``` 473 | mysql> explain select email from users where email = "o3xE22lXIlWJCdd@example.com"\G 474 | *************************** 1. row *************************** 475 | id: 1 476 | select_type: SIMPLE 477 | table: users 478 | partitions: NULL 479 | type: ref 480 | possible_keys: email 481 | key: email 482 | key_len: 302 483 | ref: const 484 | rows: 1 485 | filtered: 100.00 486 | Extra: Using index 487 | 1 row in set, 1 warning (0.00 sec) 488 | ``` 489 | 490 | - 問題の SQL を実行し実行時間から改善したことを確認 491 | - 1 row in set (0.00 sec) 492 | 493 | ``` 494 | mysql> select name from users where email = "o3xE22lXIlWJCdd@example.com"; 495 | +-----------------+ 496 | | name | 497 | +-----------------+ 498 | | o3xE22lXIlWJCdd | 499 | +-----------------+ 500 | 1 row in set (0.00 sec) 501 | ``` 502 | 503 | - I/O が 717622 から 1 に減少(実際には 1000006 から 1) 504 | - ページの走査も全ページから INDEX(root,branch,leaf の 3 ~ 4 ページ) + レコードのページの 4 ~ 5 ページになった 505 | - 4261412864byte = 約4Gbyte から 8kbyte * 4 ~ 5 = 32k ~ 40kbyte 506 | 507 | ``` 508 | mysql> select count(*) from users; 509 | +----------+ 510 | | count(*) | 511 | +----------+ 512 | | 1000006 | 513 | +----------+ 514 | 1 row in set (26.05 sec) 515 | ``` 516 | 517 | ## 余談 518 | 519 | 今回の SQL チューニングは INDEX を用いることで、以下を達成しレスポンスタイムを上げることができました 520 | 521 | - 計算量を減らす 522 | - 実際に走査するレコードを減らす 523 | - 走査する容量を減らす 524 | 525 | 冒頭で`最適`と言う言葉に触れましたが`走査する容量を減らす`と言うのは共有メモリを最適な利用に近づけることに寄与していることに注目できると SQL チューニングとシステムチューニングを相互に俯瞰でき RDBMS チューニングの精度が上がっていきます。 526 | -------------------------------------------------------------------------------- /performance-tuning/part2.md: -------------------------------------------------------------------------------- 1 | # RDBMS パフォーマンスチューニング入門 Part2 2 | 3 | ## 準備 4 | 5 | 実演を手元で動かしたい場合は[part0](./part0.md)を行うこと 6 | 7 | ## 今回利用するテーブル 8 | 9 | 親:users(id) - 子:messages(user_id)の関係(FK なし) 10 | 11 | ### テーブル定義 12 | 13 | `users`(PK のみ) 14 | 15 | ``` 16 | mysql> show create table users; 17 | 18 | CREATE TABLE `users` ( 19 | `id` int NOT NULL AUTO_INCREMENT, 20 | `name` varchar(50) NOT NULL, 21 | `email` varchar(100) NOT NULL, 22 | `password` varchar(255) NOT NULL, 23 | `birthday` datetime NOT NULL, 24 | `profile1` text, 25 | `profile2` text, 26 | `created_at` datetime NOT NULL, 27 | `updated_at` datetime NOT NULL, 28 | PRIMARY KEY (`id`) 29 | ) ENGINE=InnoDB AUTO_INCREMENT=1000008 DEFAULT CHARSET=utf8mb3 30 | 31 | ``` 32 | 33 | `messages`(PK のみ) 34 | 35 | ``` 36 | mysql> show create table messages; 37 | 38 | CREATE TABLE `messages` ( 39 | `id` int NOT NULL AUTO_INCREMENT, 40 | `user_id` int NOT NULL, 41 | `title` varchar(100) NOT NULL, 42 | `message` text NOT NULL, 43 | `created_at` datetime NOT NULL, 44 | `updated_at` datetime NOT NULL, 45 | PRIMARY KEY (`id`) 46 | ) ENGINE=InnoDB AUTO_INCREMENT=1754074 DEFAULT CHARSET=utf8mb3 47 | ``` 48 | 49 | ### 件数 50 | 51 | `users` 52 | 53 | ``` 54 | mysql> select count(*) from users; 55 | +----------+ 56 | | count(*) | 57 | +----------+ 58 | | 1000006 | 59 | +----------+ 60 | 1 row in set (12.39 sec) 61 | 62 | ``` 63 | 64 | `messages` 65 | 66 | ``` 67 | mysql> select count(*) from messages; 68 | +----------+ 69 | | count(*) | 70 | +----------+ 71 | | 1754073 | 72 | +----------+ 73 | 1 row in set (0.87 sec) 74 | ``` 75 | 76 | ## SQL テクニック&Tips 77 | 78 | ### Nested Loop Join の理解と基本的なSQLチューニング 79 | 80 | ![nested](./images/nested.png) 81 | 82 | 上絵では FullScan に見えるが実際は Index などで駆動表のレコードを特定、内部表への探索も Index で特定することが望ましい 83 | Nested Loop Joinはクロス結合、内部結合、外部結合などの振る舞いがあるが内部結合で済むテーブル関係と構造にし内部結合では実現できないケースでクロス結合や外部結合を用いる、まずは内部結合の動作の理解を深めましょう(上は内部結合の前提の絵) 84 | 85 | Nested Loop Join について以下の SQL を例に解説 86 | 87 | #### 実行時間の確認 88 | 89 | `messages b`と`users a`の内部結合、実行時間は`8.92 sec` 90 | 91 | ``` 92 | mysql> select a.name ,b.message from messages b inner join users a on a.id = b.user_id and a.id = 1000001; 93 | +---------+---------------------------+ 94 | | name | message | 95 | +---------+---------------------------+ 96 | | sunrise | Sunriseへようこそ! | 97 | +---------+---------------------------+ 98 | 1 row in set (8.92 sec) 99 | ``` 100 | 101 | #### 実行計画の確認(explain) 102 | 103 | `users a`から走査(駆動表)し、次に`messages b`を走査している(Nested Loop Join) 104 | 105 | ※SQLの記述では`messages b`から`users a`を走査するよう記述されているがオプティマイザが決定したアクセスパスは異なっている 106 | 107 | explainから`users a`はPKでアクセスし、`messages b`はINDEXは存在せずFull Scanとなっている 108 | 109 | ``` 110 | mysql> explain select a.name ,b.message from messages b inner join users a on a.id = b.user_id and a.id = 1000001\G 111 | *************************** 1. row *************************** 112 | id: 1 113 | select_type: SIMPLE 114 | table: a 115 | partitions: NULL 116 | type: const 117 | possible_keys: PRIMARY 118 | key: PRIMARY 119 | key_len: 4 120 | ref: const 121 | rows: 1 122 | filtered: 100.00 123 | Extra: NULL 124 | *************************** 2. row *************************** 125 | id: 1 126 | select_type: SIMPLE 127 | table: b 128 | partitions: NULL 129 | type: ALL 130 | possible_keys: NULL 131 | key: NULL 132 | key_len: NULL 133 | ref: NULL 134 | rows: 1676140 135 | filtered: 10.00 136 | Extra: Using where 137 | 2 rows in set, 1 warning (0.00 sec) 138 | ``` 139 | 140 | #### SQLチューニング 141 | 今回は上のexplainで`key`にINDEXが指定され、`rows`を取得するレコード数(今回は1レコード)に近づけるよう`messages b`にINDEXを作成する。 142 | 143 | ``` 144 | mysql> alter table messages add index user_id(user_id); 145 | Query OK, 0 rows affected (4.85 sec) 146 | Records: 0 Duplicates: 0 Warnings: 0 147 | ``` 148 | 149 | #### 実行計画の確認(explain) 150 | explainから`key: user_id`,`rows: 1`と意図した実行計画になっている 151 | 152 | ``` 153 | mysql> explain select a.name ,b.message from messages b inner join users a on a.id = b.user_id and a.id = 1000001\G 154 | *************************** 1. row *************************** 155 | id: 1 156 | select_type: SIMPLE 157 | table: a 158 | partitions: NULL 159 | type: const 160 | possible_keys: PRIMARY 161 | key: PRIMARY 162 | key_len: 4 163 | ref: const 164 | rows: 1 165 | filtered: 100.00 166 | Extra: NULL 167 | *************************** 2. row *************************** 168 | id: 1 169 | select_type: SIMPLE 170 | table: b 171 | partitions: NULL 172 | type: ref 173 | possible_keys: user_id 174 | key: user_id 175 | key_len: 4 176 | ref: const 177 | rows: 1 178 | filtered: 100.00 179 | Extra: NULL 180 | 2 rows in set, 1 warning (0.00 sec) 181 | ``` 182 | 183 | #### 実行時間の確認 184 | `0.01 sec`とパフォーマンスが改善していることがわかる 185 | 186 | ``` 187 | mysql> select a.name ,b.message from messages b inner join users a on a.id = b.user_id and a.id = 1000001; 188 | +---------+---------------------------+ 189 | | name | message | 190 | +---------+---------------------------+ 191 | | sunrise | Sunriseへようこそ! | 192 | +---------+---------------------------+ 193 | 1 row in set (0.01 sec) 194 | ``` 195 | 196 | ### Multi Column Index 197 | 198 | Multi Column Index(複数カラムで構成する INDEX)のカラムの列挙順について 199 | 200 | #### 例 201 | 202 | select 句は全てのカラム、検索条件は`birthday`と`name`での絞り込み 203 | 204 | ``` 205 | select * from users where birthday = "1988-04-23 00:00:00" and name = "o3xE22lXIlWJCdd"; 206 | ``` 207 | 208 | #### 実行時間の確認 209 | 210 | SQLでは条件句(where)に`birthday`と`name`の絞り込みが行われている 211 | 212 | 実行時間は`1 row in set (18.53 sec)` 213 | 214 | ``` 215 | mysql> select * from users where birthday = "1988-04-23 00:00:00" and name = "o3xE22lXIlWJCdd"; 216 | 217 | ... 割愛 218 | 219 | 1 row in set (18.53 sec) 220 | ``` 221 | 222 | #### 実行計画の確認(explain) 223 | 224 | `possible_keys: NULL`,`key: NULL`とINDEXの候補もなく使用されていない。1件のレコードを取得するのに`rows: 703878`(統計値)件読まれている。 225 | 226 | ``` 227 | mysql> explain select * from users where birthday = "1988-04-23 00:00:00" and name = "o3xE22lXIlWJCdd"\G 228 | 229 | *************************** 1. row *************************** 230 | id: 1 231 | select_type: SIMPLE 232 | table: users 233 | partitions: NULL 234 | type: ALL 235 | possible_keys: NULL 236 | key: NULL 237 | key_len: NULL 238 | ref: NULL 239 | rows: 703878 240 | filtered: 1.00 241 | Extra: Using where 242 | 1 row in set, 1 warning (0.02 sec) 243 | 244 | ``` 245 | 246 | #### データ分布の確認 247 | 248 | 条件句の`birthday`の分布を目視で確認。今回はバランス良くバラけているようである 249 | 250 | ``` 251 | mysql> select birthday , count(*) from users group by birthday; 252 | +---------------------+----------+ 253 | | birthday | count(*) | 254 | +---------------------+----------+ 255 | | 1993-11-26 00:00:00 | 37 | 256 | | 1968-08-14 00:00:00 | 43 | 257 | | 1986-08-26 00:00:00 | 53 | 258 | | 1957-06-07 00:00:00 | 43 | 259 | 260 | ..... 261 | 262 | | 1944-09-30 00:00:00 | 24 | 263 | | 1952-05-07 00:00:00 | 38 | 264 | +---------------------+----------+ 265 | 23726 rows in set (14.44 sec) 266 | 267 | ``` 268 | 269 | 条件句で指定した`birthday = "1988-04-23 00:00:00"`でレコード数の確認(51件) 270 | 271 | ``` 272 | mysql> select count(*) from users where birthday = "1988-04-23 00:00:00"; 273 | +----------+ 274 | | count(*) | 275 | +----------+ 276 | | 51 | 277 | +----------+ 278 | 1 row in set (11.68 sec) 279 | ``` 280 | 281 | 条件句で指定した`name = "o3xE22lXIlWJCdd"`でレコード数の確認(1件) 282 | 283 | ``` 284 | mysql> select count(*) from users where name = "o3xE22lXIlWJCdd"; 285 | +----------+ 286 | | count(*) | 287 | +----------+ 288 | | 1 | 289 | +----------+ 290 | 1 row in set (12.53 sec) 291 | ``` 292 | 293 | #### チューニング 294 | 295 | INDEX は絞り込みが効くカラムから指定し`name,birthday`で(今回だと name の絞り込みが効く可能性が高いため先に指定)作成 296 | 297 | ``` 298 | alter table users add index name_birthday(name,birthday); 299 | ``` 300 | 301 | #### 実行計画の確認(explain) 302 | 303 | ``` 304 | mysql> explain select * from users where birthday = "1988-04-23 00:00:00" and name = "o3xE22lXIlWJCdd"\G 305 | *************************** 1. row *************************** 306 | id: 1 307 | select_type: SIMPLE 308 | table: users 309 | partitions: NULL 310 | type: ref 311 | possible_keys: name_birthday 312 | key: name_birthday 313 | key_len: 157 314 | ref: const,const 315 | rows: 1 316 | filtered: 100.00 317 | Extra: NULL 318 | 1 row in set, 1 warning (0.01 sec) 319 | ``` 320 | 321 | #### 実行時間の確認 322 | 323 | `1 row in set (0.00 sec)`とチューニングが成功している 324 | 325 | ``` 326 | mysql> select * from users where birthday = "1988-04-23 00:00:00" and name = "o3xE22lXIlWJCdd"; 327 | 328 | 省略... 329 | 330 | 1 row in set (0.00 sec) 331 | ``` 332 | 333 | #### 別のINDEXの作成 334 | 335 | `name,birthday`の逆で`birthday,name `で作成し確認 336 | 337 | ``` 338 | alter table users add index birthday_name(birthday,name); 339 | ``` 340 | 341 | #### 実行計画の確認(explain) 342 | 343 | `name_birthday`が選択され`rows: 1`で意図した通りの実行計画になる 344 | 345 | ``` 346 | mysql> explain select * from users where birthday = "1988-04-23 00:00:00" and name = "o3xE22lXIlWJCdd"\G 347 | *************************** 1. row *************************** 348 | id: 1 349 | select_type: SIMPLE 350 | table: users 351 | partitions: NULL 352 | type: ref 353 | possible_keys: name_birthday,birthday_name 354 | key: name_birthday 355 | key_len: 157 356 | ref: const,const 357 | rows: 1 358 | filtered: 100.00 359 | Extra: NULL 360 | 1 row in set, 1 warning (0.01 sec) 361 | ``` 362 | 363 | #### ヒント句 364 | 365 | ヒント句を用いて選択されなかった`birthday_name`を指定し実行計画ので確認(use index)(※ヒント句については後で改めて触れる) 366 | 367 | ``` 368 | mysql> explain select * from users use index(birthday_name) where birthday = "1988-04-23 00:00:00" and name = "o3xE22lXIlWJCdd"\G 369 | *************************** 1. row *************************** 370 | id: 1 371 | select_type: SIMPLE 372 | table: users 373 | partitions: NULL 374 | type: ref 375 | possible_keys: birthday_name 376 | key: birthday_name 377 | key_len: 157 378 | ref: const,const 379 | rows: 1 380 | filtered: 100.00 381 | Extra: NULL 382 | 1 row in set, 1 warning (0.00 sec) 383 | ``` 384 | 385 | #### 実行結果 386 | 387 | `birthday_name`でもパフォーマンスは良好である 388 | 389 | ``` 390 | mysql> select * from users use index(birthday_name) where birthday = "1988-04-23 00:00:00" and name = "o3xE22lXIlWJCdd"; 391 | 392 | 省略... 393 | 394 | 1 row in set (0.00 sec) 395 | ``` 396 | 397 | #### 参考:Multi Column Indexでのデータの並び 398 | 399 | ![multi-column](./images/multi-column.png) 400 | 401 | ### カバリングインデックス 402 | 403 | `Multi Column Index`の派生。INDEX で SELECT 句、条件句などをカバーしレコードまで探索をしないことでパフォーマンス向上を狙う 404 | 405 | #### 例 406 | 407 | 条件句(`where`)に`email`が指定され、結果セット(`select`句)に`name`が返されている。 408 | 実行時間は`1 row in set (11.60 sec)`掛かっている。 409 | 410 | ``` 411 | 412 | mysql> select name from users where email = "POCqOOm8flPwKGm@example.com"; 413 | +-----------------+ 414 | | name | 415 | +-----------------+ 416 | | POCqOOm8flPwKGm | 417 | +-----------------+ 418 | 1 row in set (11.60 sec) 419 | 420 | ``` 421 | 422 | #### チューニング(カバリングインデックスの作成) 423 | 424 | 今回のケースでは `name`,`email` の 2 カラムでINDEXを作成することでINDEXだけで探索を完了できる。この手法をカバリングインデックスと言う 425 | 426 | ``` 427 | mysql> alter table users add index email_name(email,name); 428 | ``` 429 | 430 | #### 実行計画(explain) 431 | 432 | `Extra: Using index`に注目 433 | 434 | ``` 435 | mysql> explain select name from users where email = "POCqOOm8flPwKGm@example.com"\G 436 | *************************** 1. row *************************** 437 | id: 1 438 | select_type: SIMPLE 439 | table: users 440 | partitions: NULL 441 | type: ref 442 | possible_keys: email_name 443 | key: email_name 444 | key_len: 302 445 | ref: const 446 | rows: 1 447 | filtered: 100.00 448 | Extra: Using index 449 | 1 row in set, 1 warning (0.01 sec) 450 | ``` 451 | 452 | #### 実行結果 453 | 454 | `1 row in set (0.01 sec)`に注目 455 | 456 | ``` 457 | mysql> select name from users where email = "POCqOOm8flPwKGm@example.com"; 458 | +-----------------+ 459 | | name | 460 | +-----------------+ 461 | | POCqOOm8flPwKGm | 462 | +-----------------+ 463 | 1 row in set (0.01 sec) 464 | ``` 465 | 466 | 467 | 468 | #### 一旦インデックスの削除 469 | 470 | 別のINDEXを評価するため`email_name`の削除 471 | 472 | ``` 473 | mysql> alter table users drop index email_name; 474 | Query OK, 0 rows affected (0.03 sec) 475 | Records: 0 Duplicates: 0 Warnings: 0 476 | ``` 477 | 478 | #### インデックスの作成 479 | 480 | 条件句(`where`)で指定されている`email`のみでINDEXの作成 481 | 482 | ``` 483 | mysql> alter table users add index email(email); 484 | ``` 485 | 486 | #### 実行計画(explain) 487 | 488 | `Extra: NULL`に注目 489 | 490 | ``` 491 | mysql> explain select name from users where email = "POCqOOm8flPwKGm@example.com"\G 492 | *************************** 1. row *************************** 493 | id: 1 494 | select_type: SIMPLE 495 | table: users 496 | partitions: NULL 497 | type: ref 498 | possible_keys: email 499 | key: email 500 | key_len: 302 501 | ref: const 502 | rows: 1 503 | filtered: 100.00 504 | Extra: NULL 505 | 1 row in set, 1 warning (0.00 sec) 506 | ``` 507 | 508 | #### 実行結果 509 | 510 | `1 row in set (0.01 sec)`に注目 511 | 512 | ``` 513 | mysql> select name from users where email = "POCqOOm8flPwKGm@example.com"; 514 | +-----------------+ 515 | | name | 516 | +-----------------+ 517 | | POCqOOm8flPwKGm | 518 | +-----------------+ 519 | 1 row in set (0.01 sec) 520 | ``` 521 | 522 | #### カバリングインデックスの作成 523 | 524 | 改めてカバリングインデックスを作成する 525 | 526 | ``` 527 | mysql> alter table users add index email_name(email,name); 528 | ``` 529 | 530 | #### 実行計画(explain) 531 | 532 | `key: email`に注目(`email`が選択されている) 533 | 534 | ``` 535 | mysql> explain select name from users where email = "POCqOOm8flPwKGm@example.com"\G 536 | *************************** 1. row *************************** 537 | id: 1 538 | select_type: SIMPLE 539 | table: users 540 | partitions: NULL 541 | type: ref 542 | possible_keys: email,email_name 543 | key: email 544 | key_len: 302 545 | ref: const 546 | rows: 1 547 | filtered: 100.00 548 | Extra: NULL 549 | 1 row in set, 1 warning (0.00 sec) 550 | ``` 551 | 552 | #### 実行計画(explain)その2 553 | 554 | ヒント句を用い`email_name`を選択させる 555 | 556 | ``` 557 | mysql> explain select name from users use index(email_name) where email = "POCqOOm8flPwKGm@example.com"\G 558 | *************************** 1. row *************************** 559 | id: 1 560 | select_type: SIMPLE 561 | table: users 562 | partitions: NULL 563 | type: ref 564 | possible_keys: email_name 565 | key: email_name 566 | key_len: 302 567 | ref: const 568 | rows: 1 569 | filtered: 100.00 570 | Extra: Using index 571 | 1 row in set, 1 warning (0.00 sec) 572 | ``` 573 | 574 | #### どちらが良いか? 575 | 576 | ケースバイケース。このケースでは`explain`だけでの評価ではなく、システムの稼働状況やメモリのキャッシュ状況なども加え総合的に評価することが望ましい(一般論としては`where句だけのINDEX` -> `カバリングインデックス`) 577 | 578 | ### INDEX Sort 579 | 580 | B+tree インデックスはソートされ格納される仕様を利用したチューニング手法 581 | 582 | #### INDEXの削除 583 | 584 | 前のチューニングで作成した`name_birthday`,`birthday_name`を一旦削除 585 | 586 | ``` 587 | alter table users drop index name_birthday; 588 | alter table users drop index birthday_name; 589 | ``` 590 | 591 | #### 例(昇順(asc)) 592 | 593 | ``` 594 | mysql> select birthday,count(*) from users group by birthday order by birthday asc limit 10; 595 | ``` 596 | 597 | #### 実行計画(explain) 598 | 599 | `key: NULL`とFull Scanで統計値`rows: 703878`のレコードにアクセスしている 600 | 601 | 602 | ``` 603 | mysql> explain select birthday,count(*) from users group by birthday order by birthday asc limit 10\G 604 | *************************** 1. row *************************** 605 | id: 1 606 | select_type: SIMPLE 607 | table: users 608 | partitions: NULL 609 | type: ALL 610 | possible_keys: NULL 611 | key: NULL 612 | key_len: NULL 613 | ref: NULL 614 | rows: 703878 615 | filtered: 100.00 616 | Extra: Using temporary; Using filesort 617 | 1 row in set, 1 warning (0.01 sec) 618 | ``` 619 | 620 | #### 実行結果 621 | 622 | `limit 10`でN件から10件のレコードを取得に`10 rows in set (9.53 sec)`掛かっている 623 | 624 | ``` 625 | mysql> select birthday,count(*) from users group by birthday order by birthday asc limit 10; 626 | +---------------------+----------+ 627 | | birthday | count(*) | 628 | +---------------------+----------+ 629 | | 1934-11-17 00:00:00 | 48 | 630 | | 1934-11-18 00:00:00 | 46 | 631 | | 1934-11-19 00:00:00 | 53 | 632 | | 1934-11-20 00:00:00 | 57 | 633 | | 1934-11-21 00:00:00 | 39 | 634 | | 1934-11-22 00:00:00 | 28 | 635 | | 1934-11-23 00:00:00 | 31 | 636 | | 1934-11-24 00:00:00 | 51 | 637 | | 1934-11-25 00:00:00 | 44 | 638 | | 1934-11-26 00:00:00 | 49 | 639 | +---------------------+----------+ 640 | 10 rows in set (9.53 sec) 641 | ``` 642 | 643 | #### 例(降順(desc)) 644 | 645 | ``` 646 | mysql> select birthday,count(*) from users group by birthday order by birthday desc limit 10; 647 | ``` 648 | 649 | #### explain 650 | 651 | `key: NULL`とFull Scanで統計値`rows: 703878`のレコードにアクセスしている 652 | 653 | ``` 654 | mysql> explain select birthday,count(*) from users group by birthday order by birthday desc limit 10\G 655 | *************************** 1. row *************************** 656 | id: 1 657 | select_type: SIMPLE 658 | table: users 659 | partitions: NULL 660 | type: ALL 661 | possible_keys: NULL 662 | key: NULL 663 | key_len: NULL 664 | ref: NULL 665 | rows: 703878 666 | filtered: 100.00 667 | Extra: Using temporary; Using filesort 668 | 1 row in set, 1 warning (0.01 sec) 669 | ``` 670 | 671 | #### 実行結果 672 | 673 | `limit 10`でN件から10件のレコードを取得に`10 rows in set (9.19 sec)`掛かっている 674 | 675 | ``` 676 | mysql> select birthday,count(*) from users group by birthday order by birthday desc limit 10; 677 | +---------------------+----------+ 678 | | birthday | count(*) | 679 | +---------------------+----------+ 680 | | 1999-11-01 00:00:00 | 40 | 681 | | 1999-10-31 00:00:00 | 47 | 682 | | 1999-10-30 00:00:00 | 39 | 683 | | 1999-10-29 00:00:00 | 44 | 684 | | 1999-10-28 00:00:00 | 51 | 685 | | 1999-10-27 00:00:00 | 32 | 686 | | 1999-10-26 00:00:00 | 28 | 687 | | 1999-10-25 00:00:00 | 38 | 688 | | 1999-10-24 00:00:00 | 35 | 689 | | 1999-10-23 00:00:00 | 37 | 690 | +---------------------+----------+ 691 | 10 rows in set (9.19 sec) 692 | ``` 693 | 694 | #### チューニング 695 | 696 | `birthday`にINDEXを作成する 697 | 698 | ``` 699 | mysql> alter table users add index birthday(birthday); 700 | ``` 701 | 702 | #### 昇順の実行計画(explain) 703 | 704 | `key: birthday` とINDEXを利用してテーブル走査している。`rows: 10`と必要最低限のアクセスに留めている。 705 | 706 | ``` 707 | mysql> explain select birthday,count(*) from users group by birthday order by birthday asc limit 10\G 708 | *************************** 1. row *************************** 709 | id: 1 710 | select_type: SIMPLE 711 | table: users 712 | partitions: NULL 713 | type: index 714 | possible_keys: birthday 715 | key: birthday 716 | key_len: 5 717 | ref: NULL 718 | rows: 10 719 | filtered: 100.00 720 | Extra: Using index 721 | 1 row in set, 1 warning (0.00 sec) 722 | ``` 723 | 724 | #### 実行結果 725 | 726 | `10 rows in set (0.01 sec)`でレコードを返している 727 | 728 | ``` 729 | mysql> select birthday,count(*) from users group by birthday order by birthday asc limit 10; 730 | +---------------------+----------+ 731 | | birthday | count(*) | 732 | +---------------------+----------+ 733 | | 1934-11-17 00:00:00 | 48 | 734 | | 1934-11-18 00:00:00 | 46 | 735 | | 1934-11-19 00:00:00 | 53 | 736 | | 1934-11-20 00:00:00 | 57 | 737 | | 1934-11-21 00:00:00 | 39 | 738 | | 1934-11-22 00:00:00 | 28 | 739 | | 1934-11-23 00:00:00 | 31 | 740 | | 1934-11-24 00:00:00 | 51 | 741 | | 1934-11-25 00:00:00 | 44 | 742 | | 1934-11-26 00:00:00 | 49 | 743 | +---------------------+----------+ 744 | 10 rows in set (0.01 sec) 745 | ``` 746 | 747 | #### 降順の実行計画(explain) 748 | 749 | 8.0 から`Backward index scan`が使える 750 | 751 | `key: birthday`とINDEXを利用してテーブル走査している。`rows: 10`と必要最低限のアクセスに留めている。 752 | 753 | ``` 754 | mysql> explain select birthday,count(*) from users group by birthday order by birthday desc limit 10\G 755 | *************************** 1. row *************************** 756 | id: 1 757 | select_type: SIMPLE 758 | table: users 759 | partitions: NULL 760 | type: index 761 | possible_keys: birthday 762 | key: birthday 763 | key_len: 5 764 | ref: NULL 765 | rows: 10 766 | filtered: 100.00 767 | Extra: Backward index scan; Using index 768 | 1 row in set, 1 warning (0.01 sec) 769 | ``` 770 | 771 | #### 実行結果 772 | 773 | `10 rows in set (0.01 sec)`でレコードを返している 774 | 775 | ``` 776 | mysql> select birthday,count(*) from users group by birthday order by birthday desc limit 10; 777 | +---------------------+----------+ 778 | | birthday | count(*) | 779 | +---------------------+----------+ 780 | | 1999-11-01 00:00:00 | 40 | 781 | | 1999-10-31 00:00:00 | 47 | 782 | | 1999-10-30 00:00:00 | 39 | 783 | | 1999-10-29 00:00:00 | 44 | 784 | | 1999-10-28 00:00:00 | 51 | 785 | | 1999-10-27 00:00:00 | 32 | 786 | | 1999-10-26 00:00:00 | 28 | 787 | | 1999-10-25 00:00:00 | 38 | 788 | | 1999-10-24 00:00:00 | 35 | 789 | | 1999-10-23 00:00:00 | 37 | 790 | +---------------------+----------+ 791 | 10 rows in set (0.01 sec) 792 | ``` 793 | 794 | #### INDEXの作成 795 | 796 | 一旦削除したINDEXを再作成 797 | ``` 798 | alter table users add index name_birthday(name,birthday); 799 | alter table users add index birthday_name(birthday,name); 800 | ``` 801 | 802 | #### 昇順の実行計画(explain) 803 | 804 | 念のため確認 805 | 806 | ``` 807 | mysql> explain select birthday,count(*) from users group by birthday order by birthday asc limit 10\G 808 | *************************** 1. row *************************** 809 | id: 1 810 | select_type: SIMPLE 811 | table: users 812 | partitions: NULL 813 | type: index 814 | possible_keys: birthday,name_birthday,birthday_name 815 | key: birthday 816 | key_len: 5 817 | ref: NULL 818 | rows: 10 819 | filtered: 100.00 820 | Extra: Using index 821 | 1 row in set, 1 warning (0.00 sec) 822 | ``` 823 | 824 | #### 降順の実行計画(explain) 825 | 826 | 念のため確認 827 | 828 | ``` 829 | mysql> explain select birthday,count(*) from users group by birthday order by birthday desc limit 10\G 830 | *************************** 1. row *************************** 831 | id: 1 832 | select_type: SIMPLE 833 | table: users 834 | partitions: NULL 835 | type: index 836 | possible_keys: birthday,name_birthday,birthday_name 837 | key: birthday 838 | key_len: 5 839 | ref: NULL 840 | rows: 10 841 | filtered: 100.00 842 | Extra: Backward index scan; Using index 843 | 1 row in set, 1 warning (0.00 sec) 844 | ``` 845 | 846 | ### ヒント句 847 | 848 | オプティマイザに対して SQL 文のアクセス(パス|順序)やインデックスの指定などの振る舞いの誘導ができる 849 | 850 | #### USE INDEX その1 851 | 852 | `use index(birthday_name)`で`birthday_name`を指定しテーブル走査させる 853 | 854 | ``` 855 | mysql> select * from users use index(birthday_name) where birthday = "1988-04-23 00:00:00" and name = "o3xE22lXIlWJCdd"; 856 | 857 | ...省略 858 | 859 | 1 row in set (0.01 sec) 860 | ``` 861 | 862 | #### 実行計画 863 | 864 | `key: birthday_name`で選択されている 865 | 866 | ``` 867 | mysql> explain select * from users use index(birthday_name) where birthday = "1988-04-23 00:00:00" and name = "o3xE22lXIlWJCdd"\G 868 | *************************** 1. row *************************** 869 | id: 1 870 | select_type: SIMPLE 871 | table: users 872 | partitions: NULL 873 | type: ref 874 | possible_keys: birthday_name 875 | key: birthday_name 876 | key_len: 157 877 | ref: const,const 878 | rows: 1 879 | filtered: 100.00 880 | Extra: NULL 881 | 1 row in set, 1 warning (0.01 sec) 882 | ``` 883 | 884 | #### USE INDEX その2 885 | 886 | `use index(email_name)`で`email_name`を指定しテーブル走査させる 887 | 888 | ``` 889 | mysql> select name from users use index(email_name) where email = "POCqOOm8flPwKGm@example.com"; 890 | +-----------------+ 891 | | name | 892 | +-----------------+ 893 | | POCqOOm8flPwKGm | 894 | +-----------------+ 895 | 1 row in set (0.00 sec) 896 | ``` 897 | 898 | #### 実行計画 899 | 900 | `key: email_name`で選択されている 901 | 902 | ``` 903 | mysql> explain select name from users use index(email_name) where email = "POCqOOm8flPwKGm@example.com"\G 904 | *************************** 1. row *************************** 905 | id: 1 906 | select_type: SIMPLE 907 | table: users 908 | partitions: NULL 909 | type: ref 910 | possible_keys: email_name 911 | key: email_name 912 | key_len: 302 913 | ref: const 914 | rows: 1 915 | filtered: 100.00 916 | Extra: Using index 917 | 1 row in set, 1 warning (0.00 sec) 918 | ``` 919 | 920 | #### 実行計画(ヒント句なし) 921 | 922 | `possible_keys: email,email_name`の選択肢から`key: email`と`email`をオプティマイザは選択 923 | 924 | ``` 925 | mysql> explain select name from users where email = "POCqOOm8flPwKGm@example.com"\G 926 | *************************** 1. row *************************** 927 | id: 1 928 | select_type: SIMPLE 929 | table: users 930 | partitions: NULL 931 | type: ref 932 | possible_keys: email,email_name 933 | key: email 934 | key_len: 302 935 | ref: const 936 | rows: 1 937 | filtered: 100.00 938 | Extra: NULL 939 | 1 row in set, 1 warning (0.00 sec) 940 | ``` 941 | 942 | #### STRAIGHT_JOIN 943 | 944 | 駆動表を指定できる 945 | 946 | ``` 947 | mysql> select STRAIGHT_JOIN a.name ,b.message from messages b inner join users a on a.id = b.user_id and a.id = 1000001; 948 | +---------+---------------------------+ 949 | | name | message | 950 | +---------+---------------------------+ 951 | | sunrise | Sunriseへようこそ! | 952 | +---------+---------------------------+ 953 | 1 row in set (0.00 sec) 954 | ``` 955 | 956 | #### 実行計画(explain) 957 | 958 | `1. row`で`table: b`と`messages b`を駆動表として走査 959 | 960 | ``` 961 | mysql> explain select STRAIGHT_JOIN a.name ,b.message from messages b inner join users a on a.id = b.user_id and a.id = 1000001\G 962 | *************************** 1. row *************************** 963 | id: 1 964 | select_type: SIMPLE 965 | table: b 966 | partitions: NULL 967 | type: ref 968 | possible_keys: user_id 969 | key: user_id 970 | key_len: 4 971 | ref: const 972 | rows: 1 973 | filtered: 100.00 974 | Extra: NULL 975 | *************************** 2. row *************************** 976 | id: 1 977 | select_type: SIMPLE 978 | table: a 979 | partitions: NULL 980 | type: const 981 | possible_keys: PRIMARY 982 | key: PRIMARY 983 | key_len: 4 984 | ref: const 985 | rows: 1 986 | filtered: 100.00 987 | Extra: NULL 988 | 2 rows in set, 1 warning (0.00 sec) 989 | ``` 990 | 991 | ### union による複数 INDEX 992 | 993 | 条件 A or 条件 B を A、B 各々に INDEX を貼り union で各々 INDEX SCAN を行う手法 994 | 995 | #### 例 996 | 997 | ``` 998 | mysql> select * from users where email = "POCqOOm8flPwKGm@example.com" or name = "sunrise"; 999 | 1000 | ...省略 1001 | 1002 | 2 rows in set (0.01 sec) 1003 | ``` 1004 | 1005 | #### 実行計画(explain) 1006 | 1007 | `key: email_name,name_birthday`が選択され`Extra: Using sort_union(email_name,name_birthday); Using where`の`Using sort_union(email_name,name_birthday)`からインデックスマージが行われている(※これ自体は問題のあるアクセスパスではない) 1008 | 1009 | ``` 1010 | mysql> explain select * from users where email = "POCqOOm8flPwKGm@example.com" or name = "sunrise"\G 1011 | *************************** 1. row *************************** 1012 | id: 1 1013 | select_type: SIMPLE 1014 | table: users 1015 | partitions: NULL 1016 | type: index_merge 1017 | possible_keys: email_name,name_birthday,email 1018 | key: email_name,name_birthday 1019 | key_len: 302,152 1020 | ref: NULL 1021 | rows: 2 1022 | filtered: 100.00 1023 | Extra: Using sort_union(email_name,name_birthday); Using where 1024 | 1 row in set, 1 warning (0.00 sec) 1025 | ``` 1026 | 1027 | #### union で分割 1028 | 1029 | ``` 1030 | mysql> select * from users where email = "POCqOOm8flPwKGm@example.com" union select * from users where name = "sunrise"; 1031 | 1032 | 1033 | ...省略 1034 | 1035 | 2 rows in set (0.00 sec) 1036 | ``` 1037 | 1038 | #### 実行計画(explain) 1039 | 1040 | `1. row`で`key: email_name`で走査(`email`を選択することもある)、`2. row`で`key: name_birthday`で走査、`3. row`で``1. row`&`2. row`をunion 1041 | 1042 | ``` 1043 | mysql> explain select * from users where email = "POCqOOm8flPwKGm@example.com" union select * from users where name = "sunrise"\G 1044 | *************************** 1. row *************************** 1045 | id: 1 1046 | select_type: PRIMARY 1047 | table: users 1048 | partitions: NULL 1049 | type: ref 1050 | possible_keys: email_name,email 1051 | key: email_name 1052 | key_len: 302 1053 | ref: const 1054 | rows: 1 1055 | filtered: 100.00 1056 | Extra: NULL 1057 | *************************** 2. row *************************** 1058 | id: 2 1059 | select_type: UNION 1060 | table: users 1061 | partitions: NULL 1062 | type: ref 1063 | possible_keys: name_birthday 1064 | key: name_birthday 1065 | key_len: 152 1066 | ref: const 1067 | rows: 1 1068 | filtered: 100.00 1069 | Extra: NULL 1070 | *************************** 3. row *************************** 1071 | id: NULL 1072 | select_type: UNION RESULT 1073 | table: 1074 | partitions: NULL 1075 | type: ALL 1076 | possible_keys: NULL 1077 | key: NULL 1078 | key_len: NULL 1079 | ref: NULL 1080 | rows: NULL 1081 | filtered: NULL 1082 | Extra: Using temporary 1083 | 3 rows in set, 1 warning (0.01 sec) 1084 | ``` 1085 | 1086 | #### チューニング 1087 | 1088 | `2. row`で`key: name_birthday`でもパフォーマンスは出ているがより最適なINDEXとして`name`のみでINDEXを作成 1089 | 1090 | ``` 1091 | alter table users add index name(name); 1092 | ``` 1093 | 1094 | #### 実行計画(explain) 1095 | 1096 | `possible_keys: name_birthday,name`からオプティマイザは`key: name_birthday`を選択(今回は意図した通りにならなかったためヒント句`use index`などで誘導することで意図したアクセスパスにできる) 1097 | 1098 | ``` 1099 | mysql> explain select * from users where email = "POCqOOm8flPwKGm@example.com" union select * from users where name = "sunrise"\G 1100 | *************************** 1. row *************************** 1101 | id: 1 1102 | select_type: PRIMARY 1103 | table: users 1104 | partitions: NULL 1105 | type: ref 1106 | possible_keys: email,email_name 1107 | key: email 1108 | key_len: 302 1109 | ref: const 1110 | rows: 1 1111 | filtered: 100.00 1112 | Extra: NULL 1113 | *************************** 2. row *************************** 1114 | id: 2 1115 | select_type: UNION 1116 | table: users 1117 | partitions: NULL 1118 | type: ref 1119 | possible_keys: name_birthday,name 1120 | key: name_birthday 1121 | key_len: 152 1122 | ref: const 1123 | rows: 1 1124 | filtered: 100.00 1125 | Extra: NULL 1126 | *************************** 3. row *************************** 1127 | id: NULL 1128 | select_type: UNION RESULT 1129 | table: 1130 | partitions: NULL 1131 | type: ALL 1132 | possible_keys: NULL 1133 | key: NULL 1134 | key_len: NULL 1135 | ref: NULL 1136 | rows: NULL 1137 | filtered: NULL 1138 | Extra: Using temporary 1139 | 3 rows in set, 1 warning (0.01 sec) 1140 | ``` 1141 | -------------------------------------------------------------------------------- /performance-tuning/part3.md: -------------------------------------------------------------------------------- 1 | # RDBMS パフォーマンスチューニング入門 part3 2 | 3 | ## Insert 時のボトルネック 4 | 5 | ### PK の衝突 6 | 7 | ## レプリケーション 8 | 9 | ![replication](./images/replication.png) 10 | 11 | ## パーティショニング 12 | 13 | ![parition](./images/partition.png) 14 | 15 | ## シャーディング 16 | 17 | ![sharding](./images/sharding.png) 18 | -------------------------------------------------------------------------------- /sql-training/basic.md: -------------------------------------------------------------------------------- 1 | # sql-training-basic 2 | 3 | ## DDL 4 | 5 | ### create database 6 | 7 | ### create table 8 | 9 | ### drop table 10 | 11 | ## DML 12 | 13 | ### select 14 | 15 | ### insert 16 | 17 | ### update 18 | 19 | ### delete 20 | -------------------------------------------------------------------------------- /sql-training/sql-training.md: -------------------------------------------------------------------------------- 1 | # SQL 実力アップセミナー 2 | 3 | このセミナーは「リレーショナルデータベースの必須技術「正規化」を学ぼう」で学んだテーブル定義を用いて SQL の実践的な書き方を学びます。「リレーショナルデータベースの必須技術「正規化」を学ぼう」を視聴している方がテーブル定義の背景などを理解して臨めますのでベストですが視聴していなくても問題なく SQL 文の勉強ができるセミナーになっています。 4 | 5 | ## 学習対象 6 | 7 | SQL 初心者から中級者 8 | 9 | ### 初心者の定義 10 | 11 | 単一テーブルに対して SELECT 文が書ける、条件句で絞り込みができる。UPDATE、INSERT、DELETE 文が書ける、条件句で絞り込みができる。 12 | 13 | ### 中級者の定義 14 | 15 | 複数テーブルを結合して SELECT 文が書ける。同じ結果となる SQL 文を複数書き分けることができる。 16 | 17 | ### 上級者の定義 18 | 19 | SQL 文をパフォーマンス視点で最適な SQL 文を導き出せる 20 | 21 | ## 対象データベース 22 | 23 | このセミナーでは MySQL をベースに SQL 文について解説しています。 24 | 25 | ## 進め方 26 | 27 | このセミナーはライブコーディング形式で進めます。セミナー中は自身で SQL を書く時間はありませんので事前に回答を元に SQL を書いてみるか、ライブコーディング後に書いてみることをおすすめします。 28 | 29 | [Step1 回答](./step-1-answer.md) 30 | 31 | [Step2 回答](./step-2-answer.md) 32 | 33 | ## 事前準備 34 | 35 | MySQL が動作し、SQL が発行できる環境(ライブコーディングの視聴のみでも学べる構成にしてありますが実際に自分で SQL を書く方が学びが多いため推奨) 36 | 37 | [参考:Docker 用の MySQL 環境レシピ](https://github.com/hironomiu/Docker-DockerCompose-Training/blob/main/recipe-mysql-dockerfile/README.md) 38 | 39 | ## 概要 40 | 41 | 今回のサンプル概要 42 | 43 | ## テーブル定義 44 | 45 | 概要に基づいて制約など盛り込んだテーブル定義が以下です。以下の CREATE TABLE 文を任意の MySQL DATABASE で実行し作成しましょう。([参考:Docker 用の MySQL 環境レシピ](https://github.com/hironomiu/Docker-DockerCompose-Training/blob/main/recipe-mysql-dockerfile/README.md)の場合なら`test`に作成) 46 | 47 | ``` 48 | DROP TABLE IF EXISTS order_details; 49 | DROP TABLE IF EXISTS orders; 50 | DROP TABLE IF EXISTS items; 51 | DROP TABLE IF EXISTS customers; 52 | 53 | CREATE TABLE customers ( 54 | id INT UNSIGNED NOT NULL AUTO_INCREMENT, 55 | name VARCHAR(100) NOT NULL, 56 | address VARCHAR(100), 57 | created_at DATETIME NOT NULL, 58 | updated_at DATETIME NOT NULL, 59 | PRIMARY KEY(id) 60 | ); 61 | 62 | CREATE TABLE items ( 63 | id INT UNSIGNED NOT NULL AUTO_INCREMENT, 64 | name VARCHAR(100) NOT NULL, 65 | price INT UNSIGNED NOT NULL, 66 | created_at DATETIME NOT NULL, 67 | updated_at DATETIME NOT NULL, 68 | PRIMARY KEY(id) 69 | ); 70 | 71 | CREATE TABLE orders ( 72 | id INT UNSIGNED NOT NULL AUTO_INCREMENT, 73 | order_date DATE NOT NULL, 74 | customer_id INT UNSIGNED NOT NULL, 75 | created_at DATETIME NOT NULL, 76 | updated_at DATETIME NOT NULL, 77 | PRIMARY KEY(id), 78 | FOREIGN KEY (customer_id) REFERENCES customers(id) 79 | ); 80 | 81 | CREATE TABLE order_details ( 82 | order_id INT UNSIGNED NOT NULL, 83 | item_id INT UNSIGNED NOT NULL, 84 | item_quantity INT UNSIGNED NOT NULL, 85 | created_at DATETIME NOT NULL, 86 | updated_at DATETIME NOT NULL, 87 | PRIMARY KEY(order_id,item_id), 88 | FOREIGN KEY (order_id) REFERENCES orders(id), 89 | FOREIGN KEY (item_id) REFERENCES items(id) 90 | ); 91 | ``` 92 | 93 | ### Q0 テーブルの削除順序、作成順序 94 | 上のDDLのテーブルの削除順序、作成順序はなぜこの順序か考えてみましょう 95 | 96 | ## サンプルデータ 97 | 98 | ``` 99 | insert into customers(id,name,address,created_at,updated_at) values(1,'A商事','東京都',now(),now()),(2,'B商会','埼玉県',now(),now()),(3,'C商店','神奈川県',now(),now()); 100 | 101 | insert into items(id,name,price,created_at,updated_at) values(1,'シャツ',1000,now(),now()),(2,'パンツ',950,now(),now()),(3,'マフラー',1200,now(),now()),(4,'ブルゾン',1800,now(),now()); 102 | 103 | insert into orders(id,order_date,customer_id,created_at,updated_at) values(1 , '2013-10-01',1,now(),now()),(2 , '2013-10-01',2,now(),now()),(3 , '2013-10-02',2,now(),now()),(4 , '2013-10-02',3,now(),now()); 104 | 105 | insert into order_details(order_id,item_id,item_quantity,created_at,updated_at) values(1 , 1 ,3,now(),now()),(1 , 2 ,2,now(),now()),(2 , 1 ,1,now(),now()),(2 , 3 ,10,now(),now()),(2 , 4 ,5,now(),now()),(3 , 2 ,80,now(),now()),(4 , 3 ,25,now(),now()); 106 | ``` 107 | 108 | ## Step1 Question 109 | 110 | ### Q0 テーブル確認をしましょう 111 | 112 | 抽出結果 113 | 114 | ``` 115 | +----------------+ 116 | | Tables_in_test | 117 | +----------------+ 118 | | customers | 119 | | items | 120 | | order_details | 121 | | orders | 122 | +----------------+ 123 | 4 rows in set (0.00 sec) 124 | 125 | +----+---------+--------------+---------------------+---------------------+ 126 | | id | name | address | created_at | updated_at | 127 | +----+---------+--------------+---------------------+---------------------+ 128 | | 1 | A商事 | 東京都 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 129 | | 2 | B商会 | 埼玉県 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 130 | | 3 | C商店 | 神奈川県 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 131 | +----+---------+--------------+---------------------+---------------------+ 132 | 3 rows in set (0.00 sec) 133 | 134 | +----+--------------+-------+---------------------+---------------------+ 135 | | id | name | price | created_at | updated_at | 136 | +----+--------------+-------+---------------------+---------------------+ 137 | | 1 | シャツ | 1000 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 138 | | 2 | パンツ | 950 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 139 | | 3 | マフラー | 1200 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 140 | | 4 | ブルゾン | 1800 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 141 | +----+--------------+-------+---------------------+---------------------+ 142 | 4 rows in set (0.00 sec) 143 | 144 | +----------+---------+---------------+---------------------+---------------------+ 145 | | order_id | item_id | item_quantity | created_at | updated_at | 146 | +----------+---------+---------------+---------------------+---------------------+ 147 | | 1 | 1 | 3 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 148 | | 1 | 2 | 2 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 149 | | 2 | 1 | 1 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 150 | | 2 | 3 | 10 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 151 | | 2 | 4 | 5 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 152 | | 3 | 2 | 80 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 153 | | 4 | 3 | 25 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 154 | +----------+---------+---------------+---------------------+---------------------+ 155 | 7 rows in set (0.00 sec) 156 | 157 | +----+------------+-------------+---------------------+---------------------+ 158 | | id | order_date | customer_id | created_at | updated_at | 159 | +----+------------+-------------+---------------------+---------------------+ 160 | | 1 | 2013-10-01 | 1 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 161 | | 2 | 2013-10-01 | 2 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 162 | | 3 | 2013-10-02 | 2 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 163 | | 4 | 2013-10-02 | 3 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 164 | +----+------------+-------------+---------------------+---------------------+ 165 | 4 rows in set (0.00 sec) 166 | ``` 167 | 168 | ### Q1 商品シャツの売り上げ合計金額を算出しましょう 169 | 170 | 抽出結果 171 | 172 | ``` 173 | +----------+ 174 | | proceeds | 175 | +----------+ 176 | | 4000 | 177 | +----------+ 178 | 1 row in set (0.01 sec) 179 | ``` 180 | 181 | ### Q1 解答後補足問題 182 | 183 | 結合順序を変えてみましょう 184 | 185 | ### Q2 商品をでランダムに 1 行求めましょう 186 | 187 | 抽出結果(ランダムに抽出するため同じになるとは限りません) 188 | 189 | ``` 190 | +----+-----------+-------+---------------------+------------+ 191 | | id | name | price | created_at | updated_at | 192 | +----+-----------+-------+---------------------+------------+ 193 | | 2 | パンツ | 950 | 2017-09-08 16:47:09 | NULL | 194 | +----+-----------+-------+---------------------+------------+ 195 | 1 row in set (0.00 sec) 196 | ``` 197 | 198 | ### Q2 解答後補足問題1 199 | 200 | ランダム抽出の内部の動作について調べましょう。調査を踏まえ注意すべき点などないかなど考えてみましょう 201 | 202 | ### Q2 解答後補足問題2 203 | 204 | SELECT 文での「`*`(アスタリスク」についてどのようなケースで利用して良いか考えてみましょう 205 | 206 | ### Q3 商品「シャツ」「パンツ」を受注した受注 id を求めましょう。受注 id は新しい(大きい)順に並べましょう 207 | 208 | 抽出結果 209 | 210 | ``` 211 | +----------+ 212 | | order_id | 213 | +----------+ 214 | | 3 | 215 | | 2 | 216 | | 1 | 217 | +----------+ 218 | 3 rows in set (0.00 sec) 219 | ``` 220 | 221 | ### Q3 解答後補足問題 1 222 | 223 | Q3 の SQL 文を union 句を用いて同じ結果になる SELECT 文を構築しましょう 224 | 抽出結果 225 | 226 | ``` 227 | +----------+ 228 | | order_id | 229 | +----------+ 230 | | 3 | 231 | | 2 | 232 | | 1 | 233 | +----------+ 234 | 3 rows in set (0.00 sec) 235 | ``` 236 | 237 | ### Q3 解答後補足問題 2 238 | 239 | Q3 の SQL 文を group by 句を用いて同じ結果になる SELECT 文を構築しましょう 240 | 抽出結果 241 | 242 | ``` 243 | +----------+ 244 | | order_id | 245 | +----------+ 246 | | 3 | 247 | | 2 | 248 | | 1 | 249 | +----------+ 250 | 3 rows in set (0.00 sec) 251 | ``` 252 | 253 | ### Q4 受注全体から受注金額の平均を算出しましょう 254 | 255 | 抽出結果 256 | 257 | ``` 258 | +-----------------+ 259 | | avg_order_price | 260 | +-----------------+ 261 | | 33225.0000 | 262 | +-----------------+ 263 | 1 row in set (0.00 sec) 264 | ``` 265 | 266 | ### Q4 解答後補足問題 267 | 268 | 受注の件数も一緒に取得しましょう 269 | 270 | 抽出結果 271 | 272 | ``` 273 | +-----------------+-------------+ 274 | | avg_order_price | order_count | 275 | +-----------------+-------------+ 276 | | 33225.0000 | 4 | 277 | +-----------------+-------------+ 278 | 1 row in set (0.00 sec) 279 | ``` 280 | 281 | ### Q5 受注金額が一番大きい受注の受注 id と受注金額を求めましょう 282 | 283 | 抽出結果 284 | 285 | ``` 286 | +----+-------------+ 287 | | id | order_price | 288 | +----+-------------+ 289 | | 3 | 76000 | 290 | +----+-------------+ 291 | 1 row in set (0.00 sec) 292 | ``` 293 | 294 | ### Q5 解答後補足問題 295 | 296 | 今回の回答が正しいか考えてみましょう 297 | 298 | --- 299 | 300 | ## Step2 Question 301 | 302 | ここからは冒頭の` テーブル定義``サンプルデータ `を再度実行しデータを最初の状態に戻したところから行います 303 | 304 | ### Q6 新しい商品を 1 回の insert で登録しましょう。 305 | 306 | | 商品 | 単価 | 307 | | :----------- | :--- | 308 | | タンクトップ | 1300 | 309 | | ジャンパー | 2500 | 310 | | ソックス | 600 | 311 | 312 | ヒント バルクインサートを検索しましょう 313 | 314 | ``` 315 | +----+--------------------+-------+---------------------+---------------------+ 316 | | id | name | price | created_at | updated_at | 317 | +----+--------------------+-------+---------------------+---------------------+ 318 | | 1 | シャツ | 1000 | 2021-06-02 01:00:43 | 2021-06-02 01:00:43 | 319 | | 2 | パンツ | 950 | 2021-06-02 01:00:43 | 2021-06-02 01:00:43 | 320 | | 3 | マフラー | 1200 | 2021-06-02 01:00:43 | 2021-06-02 01:00:43 | 321 | | 4 | ブルゾン | 1800 | 2021-06-02 01:00:43 | 2021-06-02 01:00:43 | 322 | | 5 | タンクトップ | 1300 | 2021-06-02 01:07:17 | 2021-06-02 01:07:17 | 323 | | 6 | ジャンパー | 2500 | 2021-06-02 01:07:17 | 2021-06-02 01:07:17 | 324 | | 7 | ソックス | 600 | 2021-06-02 01:07:17 | 2021-06-02 01:07:17 | 325 | +----+--------------------+-------+---------------------+---------------------+ 326 | 7 rows in set (0.00 sec) 327 | ``` 328 | 329 | ### Q6 解答後補足問題 330 | 331 | 今回の回答に至った insert 文のメリットを考えてみましょう 332 | 333 | ### Q6 解答後補足問題 334 | 335 | 時刻カラムの設定をDB側に任せるメリットを考えてみましょう 336 | 337 | ### Q7 B 商会の受注金額の合計を算出しましょう 338 | 339 | 抽出例 340 | 341 | ``` 342 | +-----------+ 343 | | sum_price | 344 | +-----------+ 345 | | 98000 | 346 | +-----------+ 347 | 1 row in set (0.00 sec) 348 | ``` 349 | 350 | ### Q8 商品(itemss)から受注明細(order_details)で使われている商品(items.id,items.name)を求めましょう、3 種類の SQL を作成しましょう 351 | 352 | 抽出例 353 | 354 | ``` 355 | +---------+--------------+ 356 | | item_id | name | 357 | +---------+--------------+ 358 | | 1 | シャツ | 359 | | 2 | パンツ | 360 | | 3 | マフラー | 361 | | 4 | ブルゾン | 362 | +---------+--------------+ 363 | 4 rows in set (0.00 sec) 364 | ``` 365 | 366 | ヒント:「inner join」「exists」「in」で一つずつ構築しましょう 367 | 368 | ### Q9 受注明細(order_details)で使われていない商品(items.id,items.name)を求めましょう、3 種類の SQL を作成しましょう 369 | 370 | 抽出例 371 | 372 | ``` 373 | +----+--------------------+ 374 | | id | name | 375 | +----+--------------------+ 376 | | 5 | タンクトップ | 377 | | 6 | ジャンパー | 378 | | 7 | ソックス | 379 | +----+--------------------+ 380 | 3 rows in set (0.00 sec) 381 | ``` 382 | 383 | ### Q10 全アイテム(items)に存在する商品を受注明細(order_details)から各商品ごとの注文回数(抽出例は item_id が 1~7 固定のものとする)を求めよ、全注文数(all_order)も求めよ 384 | 385 | 抽出例 386 | 387 | ``` 388 | +-----------+------+------+------+------+------+------+------+ 389 | | all_order | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 390 | +-----------+------+------+------+------+------+------+------+ 391 | | 7 | 2 | 2 | 2 | 1 | 0 | 0 | 0 | 392 | +-----------+------+------+------+------+------+------+------+ 393 | 1 row in set (0.00 sec) 394 | ``` 395 | 396 | ### Q11 全アイテム(items)に存在する商品を受注明細(order_details)から各商品ごとの注文回数を求めよ(5 分) 397 | 398 | 抽出例 399 | 400 | ``` 401 | +----+-------+ 402 | | id | count | 403 | +----+-------+ 404 | | 1 | 2 | 405 | | 2 | 2 | 406 | | 3 | 2 | 407 | | 4 | 1 | 408 | | 5 | 0 | 409 | | 6 | 0 | 410 | | 7 | 0 | 411 | +----+-------+ 412 | 7 rows in set (0.00 sec) 413 | ``` 414 | 415 | ### Q12 全アイテム(items)に存在する商品を受注明細(order_details)から各商品ごとの注文回数を求めよ、全注文数(all_order)も求めよ(5 分) 抽出例 416 | 417 | 抽出例 418 | 419 | ``` 420 | +-----------+-------+ 421 | | all_order | count | 422 | +-----------+-------+ 423 | | all_order | 7 | 424 | | 1 | 2 | 425 | | 2 | 2 | 426 | | 3 | 2 | 427 | | 4 | 1 | 428 | | 5 | 0 | 429 | | 6 | 0 | 430 | | 7 | 0 | 431 | +-----------+-------+ 432 | 8 rows in set (0.00 sec) 433 | ``` 434 | 435 | ### Q13 Q11,Q12 で求めた全アイテム(items)に存在する商品を受注明細(order_details)から各商品ごとの注文(order_id)を求めよ、(5 分) 抽出例 436 | 437 | 抽出例 438 | 439 | ``` 440 | +----+----------+ 441 | | id | order_id | 442 | +----+----------+ 443 | | 1 | 1,2 | 444 | | 2 | 1,3 | 445 | | 3 | 2,4 | 446 | | 4 | 2 | 447 | | 5 | NULL | 448 | | 6 | NULL | 449 | | 7 | NULL | 450 | +----+----------+ 451 | 7 rows in set (0.00 sec) 452 | ``` 453 | 454 | ### Q13 解答後補足問題 455 | 456 | エラーとなる理由を考えてみましょう 457 | 458 | ``` 459 | mysql> select b.id ,group_concat(a.order_id) as order from order_details a right outer join items b on a.item_id = b.id group by b.id; 460 | ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'order from order_details a right outer join items b on a.item_id = b.id group by' at line 1 461 | ``` 462 | 463 | --- 464 | -------------------------------------------------------------------------------- /sql-training/step-1-answer.md: -------------------------------------------------------------------------------- 1 | # SQL実力アップセミナー(Step1 回答) 2 | 3 | ## Answer 4 | ### A0 テーブルの削除順序、作成順序 5 | 親テーブルから作成し子テーブルから削除する必要があるため 6 | 7 | order_details は orders, itemsに依存している 8 | 9 | ``` 10 | FOREIGN KEY (order_id) REFERENCES orders(id), 11 | FOREIGN KEY (item_id) REFERENCES items(id) 12 | ``` 13 | 14 | orders は customers に依存している 15 | 16 | ``` 17 | FOREIGN KEY (customer_id) REFERENCES customers(id) 18 | ``` 19 | 20 | ### A0 テーブル確認をしましょう 21 | ``` 22 | show tables; 23 | 24 | select * from customers; 25 | 26 | select * from items; 27 | 28 | select * from order_details; 29 | 30 | select * from orders; 31 | ``` 32 | 33 | ### A1 商品シャツの売り上げ合計金額を算出しましょう 34 | ``` 35 | select sum(a.price * b.item_quantity) proceeds from items a inner join order_details b on (a.id = b.item_id) where a.name = 'シャツ'; 36 | ``` 37 | 38 | ### A1 解答後補足問題 39 | 結合順序を変えてみましょう 40 | ``` 41 | select sum(a.price * b.item_quantity) proceeds from order_details b inner join items a on (a.id = b.item_id and a.name = 'シャツ'); 42 | ``` 43 | 44 | ### A2 商品をでランダムに1行求めましょう 45 | ``` 46 | select * from items order by rand() limit 1; 47 | ``` 48 | 49 | ### A2 解答後補足問題1 50 | 見た目上は1レコードを取得しているが、全レコードに乱数値を設定し全件ソートし1レコードを取得していることに注意しましょう 51 | 52 | ### A2 解答後補足問題2 53 | 前提として「`*`」はレコードを示すワイルドカードと認識しましょう。プログラムコード内にSQLを記述する際にレコード内のカラムデータを取得する場合は「`*`」は使用せず、必要なカラム名を全て記載しましょう。レコードを意識するケース、例えばレーコード件数などを取得する場合「`SELECT COUNT(*)`」などは「`*`」が推奨です 54 | 55 | ### A3 商品「シャツ」「パンツ」を受注した受注idを求めましょう。受注idは新しい(大きい)順に並べましょう 56 | ``` 57 | select distinct b.order_id from items a inner join order_details b on a.id = b.item_id where name in ('シャツ','パンツ') order by b.order_id desc; 58 | ``` 59 | 60 | ### A3 解答後補足問題1 61 | Q3のSQL文をunion句を用いて同じ結果になるSELECT文を構築しましょう 62 | ``` 63 | select order_id from (select b.order_id from items a inner join order_details b on a.id=b.item_id where name = 'シャツ' union select b.order_id from items a inner join order_details b on a.id=b.item_id where name = 'パンツ') c order by order_id desc; 64 | ``` 65 | 66 | ### A3 解答後補足問題2 67 | Q3のSQL文をgroup by句を用いて同じ結果になるSELECT文を構築しましょう 68 | ``` 69 | select b.order_id from items a inner join order_details b on a.id = b.item_id where name in ('シャツ','パンツ') group by b.order_id order by b.order_id desc; 70 | ``` 71 | 72 | ### A4 受注全体から受注金額の平均を算出しましょう 73 | ``` 74 | select avg(sum_price) from (select a.order_id,sum(b.price * a.item_quantity) sum_price from order_details a inner join items b on b.id = a.item_id group by order_id) c; 75 | ``` 76 | 77 | ### A4 解答後補足問題 78 | 受注の件数も一緒に取得しましょう 79 | ``` 80 | select avg(sum_price),count(*) order_couunt from (select a.order_id,sum(b.price * a.item_quantity) sum_price from order_details a inner join items b on b.id = a.item_id group by order_id) c; 81 | ``` 82 | 83 | ### A5 受注金額が一番大きい受注の受注idと受注金額を求めましょう 84 | ``` 85 | select a.order_id,sum(b.price * a.item_quantity) order_price from order_details a inner join items b on a.item_id = b.id group by a.order_id order by order_price desc limit 1; 86 | ``` 87 | 88 | ### A5 解答後補足問題 89 | 今回の回答が正しいか考えてみましょう 90 | 91 | 追加データ 92 | ``` 93 | insert into orders(id,order_date,customer_id,created_at,updated_at) values(5 , '2013-10-02',2,now(),now()); 94 | insert into order_details(order_id,item_id,item_quantity,created_at,updated_at) values(5 , 2 ,80,now(),now()); 95 | ``` 96 | 97 | limit句で1件しか取得していないため、受注金額の一番大きい受注が複数の場合問題となる可能性がある(問題にするかは要件次第) 98 | ``` 99 | select a.order_id,sum(b.price * a.item_quantity) order_price from order_details a inner join items b on a.item_id = b.id group by a.order_id having order_price = (select max(order_price) from (select a.order_id,sum(b.price * a.item_quantity) order_price from order_details a inner join items b on a.item_id = b.id group by a.order_id) c); 100 | ``` -------------------------------------------------------------------------------- /sql-training/step-1-question.md: -------------------------------------------------------------------------------- 1 | # SQL 実力アップセミナー 2 | 3 | このセミナーは「リレーショナルデータベースの必須技術「正規化」を学ぼう」で学んだテーブル定義を用いて SQL の実践的な書き方を学びます。「リレーショナルデータベースの必須技術「正規化」を学ぼう」を視聴している方がテーブル定義の背景などを理解して臨めますのでベストですが視聴していなくても問題なく SQL 文の勉強ができるセミナーになっています。 4 | 5 | ## 学習対象 6 | 7 | SQL 初心者から中級者 8 | 9 | ### 初心者の定義 10 | 11 | 単一テーブルに対して SELECT 文が書ける、条件句で絞り込みができる。UPDATE、INSERT、DELETE 文が書ける、条件句で絞り込みができる。 12 | 13 | ### 中級者の定義 14 | 15 | 複数テーブルを結合して SELECT 文が書ける。同じ結果となる SQL 文を複数書き分けることができる。 16 | 17 | ### 上級者の定義 18 | 19 | SQL 文をパフォーマンス視点で最適な SQL 文を導き出せる 20 | 21 | ## 対象データベース 22 | 23 | このセミナーでは MySQL をベースに SQL 文について解説しています。 24 | 25 | ## 進め方 26 | 27 | このセミナーはライブコーディング形式で進めます。セミナー中は自身で SQL を書く時間はありませんので事前に回答を元に SQL を書いてみるか、ライブコーディング後に書いてみることをおすすめします。 28 | 29 | [Step1 回答](./step-1-answer.md) 30 | 31 | [Step2 回答](./step-2-answer.md) 32 | 33 | ## 事前準備 34 | 35 | MySQL が動作し、SQL が発行できる環境(ライブコーディングの視聴のみでも学べる構成にしてありますが実際に自分で SQL を書く方が学びが多いため推奨) 36 | 37 | [参考:Docker 用の MySQL 環境レシピ](https://github.com/hironomiu/Docker-DockerCompose-Training/blob/main/recipe-mysql-dockerfile/README.md) 38 | 39 | ## 概要 40 | 41 | 今回のサンプル概要 42 | 43 | ## テーブル定義 44 | 45 | 概要に基づいて制約など盛り込んだテーブル定義が以下です。以下の CREATE TABLE 文を任意の MySQL DATABASE で実行し作成しましょう。([参考:Docker 用の MySQL 環境レシピ](https://github.com/hironomiu/Docker-DockerCompose-Training/blob/main/recipe-mysql-dockerfile/README.md)の場合なら`test`に作成) 46 | 47 | ``` 48 | DROP TABLE IF EXISTS order_details; 49 | DROP TABLE IF EXISTS orders; 50 | DROP TABLE IF EXISTS items; 51 | DROP TABLE IF EXISTS customers; 52 | 53 | CREATE TABLE customers ( 54 | id INT UNSIGNED NOT NULL AUTO_INCREMENT, 55 | name VARCHAR(100) NOT NULL, 56 | address VARCHAR(100), 57 | created_at DATETIME NOT NULL, 58 | updated_at DATETIME NOT NULL, 59 | PRIMARY KEY(id) 60 | ); 61 | 62 | CREATE TABLE items ( 63 | id INT UNSIGNED NOT NULL AUTO_INCREMENT, 64 | name VARCHAR(100) NOT NULL, 65 | price INT UNSIGNED NOT NULL, 66 | created_at DATETIME NOT NULL, 67 | updated_at DATETIME NOT NULL, 68 | PRIMARY KEY(id) 69 | ); 70 | 71 | CREATE TABLE orders ( 72 | id INT UNSIGNED NOT NULL AUTO_INCREMENT, 73 | order_date DATE NOT NULL, 74 | customer_id INT UNSIGNED NOT NULL, 75 | created_at DATETIME NOT NULL, 76 | updated_at DATETIME NOT NULL, 77 | PRIMARY KEY(id), 78 | FOREIGN KEY (customer_id) REFERENCES customers(id) 79 | ); 80 | 81 | CREATE TABLE order_details ( 82 | order_id INT UNSIGNED NOT NULL, 83 | item_id INT UNSIGNED NOT NULL, 84 | item_quantity INT UNSIGNED NOT NULL, 85 | created_at DATETIME NOT NULL, 86 | updated_at DATETIME NOT NULL, 87 | PRIMARY KEY(order_id,item_id), 88 | FOREIGN KEY (order_id) REFERENCES orders(id), 89 | FOREIGN KEY (item_id) REFERENCES items(id) 90 | ); 91 | ``` 92 | 93 | ### Q0 テーブルの削除順序、作成順序 94 | 上のDDLのテーブルの削除順序、作成順序はなぜこの順序か考えてみましょう 95 | 96 | ## サンプルデータ 97 | 98 | ``` 99 | insert into customers(id,name,address,created_at,updated_at) values(1,'A商事','東京都',now(),now()),(2,'B商会','埼玉県',now(),now()),(3,'C商店','神奈川県',now(),now()); 100 | 101 | insert into items(id,name,price,created_at,updated_at) values(1,'シャツ',1000,now(),now()),(2,'パンツ',950,now(),now()),(3,'マフラー',1200,now(),now()),(4,'ブルゾン',1800,now(),now()); 102 | 103 | insert into orders(id,order_date,customer_id,created_at,updated_at) values(1 , '2013-10-01',1,now(),now()),(2 , '2013-10-01',2,now(),now()),(3 , '2013-10-02',2,now(),now()),(4 , '2013-10-02',3,now(),now()); 104 | 105 | insert into order_details(order_id,item_id,item_quantity,created_at,updated_at) values(1 , 1 ,3,now(),now()),(1 , 2 ,2,now(),now()),(2 , 1 ,1,now(),now()),(2 , 3 ,10,now(),now()),(2 , 4 ,5,now(),now()),(3 , 2 ,80,now(),now()),(4 , 3 ,25,now(),now()); 106 | ``` 107 | 108 | ## Step1 Question 109 | 110 | ### Q0 テーブル確認をしましょう 111 | 112 | 抽出結果 113 | 114 | ``` 115 | +----------------+ 116 | | Tables_in_test | 117 | +----------------+ 118 | | customers | 119 | | items | 120 | | order_details | 121 | | orders | 122 | +----------------+ 123 | 4 rows in set (0.00 sec) 124 | 125 | +----+---------+--------------+---------------------+---------------------+ 126 | | id | name | address | created_at | updated_at | 127 | +----+---------+--------------+---------------------+---------------------+ 128 | | 1 | A商事 | 東京都 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 129 | | 2 | B商会 | 埼玉県 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 130 | | 3 | C商店 | 神奈川県 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 131 | +----+---------+--------------+---------------------+---------------------+ 132 | 3 rows in set (0.00 sec) 133 | 134 | +----+--------------+-------+---------------------+---------------------+ 135 | | id | name | price | created_at | updated_at | 136 | +----+--------------+-------+---------------------+---------------------+ 137 | | 1 | シャツ | 1000 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 138 | | 2 | パンツ | 950 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 139 | | 3 | マフラー | 1200 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 140 | | 4 | ブルゾン | 1800 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 141 | +----+--------------+-------+---------------------+---------------------+ 142 | 4 rows in set (0.00 sec) 143 | 144 | +----------+---------+---------------+---------------------+---------------------+ 145 | | order_id | item_id | item_quantity | created_at | updated_at | 146 | +----------+---------+---------------+---------------------+---------------------+ 147 | | 1 | 1 | 3 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 148 | | 1 | 2 | 2 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 149 | | 2 | 1 | 1 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 150 | | 2 | 3 | 10 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 151 | | 2 | 4 | 5 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 152 | | 3 | 2 | 80 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 153 | | 4 | 3 | 25 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 154 | +----------+---------+---------------+---------------------+---------------------+ 155 | 7 rows in set (0.00 sec) 156 | 157 | +----+------------+-------------+---------------------+---------------------+ 158 | | id | order_date | customer_id | created_at | updated_at | 159 | +----+------------+-------------+---------------------+---------------------+ 160 | | 1 | 2013-10-01 | 1 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 161 | | 2 | 2013-10-01 | 2 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 162 | | 3 | 2013-10-02 | 2 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 163 | | 4 | 2013-10-02 | 3 | 2020-04-14 08:30:09 | 2020-04-14 08:30:09 | 164 | +----+------------+-------------+---------------------+---------------------+ 165 | 4 rows in set (0.00 sec) 166 | ``` 167 | 168 | ### Q1 商品シャツの売り上げ合計金額を算出しましょう 169 | 170 | 抽出結果 171 | 172 | ``` 173 | +----------+ 174 | | proceeds | 175 | +----------+ 176 | | 4000 | 177 | +----------+ 178 | 1 row in set (0.01 sec) 179 | ``` 180 | 181 | ### Q1 解答後補足問題 182 | 183 | 結合順序を変えてみましょう 184 | 185 | ### Q2 商品をでランダムに 1 行求めましょう 186 | 187 | 抽出結果(ランダムに抽出するため同じになるとは限りません) 188 | 189 | ``` 190 | +----+-----------+-------+---------------------+------------+ 191 | | id | name | price | created_at | updated_at | 192 | +----+-----------+-------+---------------------+------------+ 193 | | 2 | パンツ | 950 | 2017-09-08 16:47:09 | NULL | 194 | +----+-----------+-------+---------------------+------------+ 195 | 1 row in set (0.00 sec) 196 | ``` 197 | 198 | ### Q2 解答後補足問題1 199 | 200 | ランダム抽出の内部の動作について調べましょう。調査を踏まえ注意すべき点などないかなど考えてみましょう 201 | 202 | ### Q2 解答後補足問題2 203 | 204 | SELECT 文での「`*`(アスタリスク」についてどのようなケースで利用して良いか考えてみましょう 205 | 206 | ### Q3 商品「シャツ」「パンツ」を受注した受注 id を求めましょう。受注 id は新しい(大きい)順に並べましょう 207 | 208 | 抽出結果 209 | 210 | ``` 211 | +----------+ 212 | | order_id | 213 | +----------+ 214 | | 3 | 215 | | 2 | 216 | | 1 | 217 | +----------+ 218 | 3 rows in set (0.00 sec) 219 | ``` 220 | 221 | ### Q3 解答後補足問題 1 222 | 223 | Q3 の SQL 文を union 句を用いて同じ結果になる SELECT 文を構築しましょう 224 | 抽出結果 225 | 226 | ``` 227 | +----------+ 228 | | order_id | 229 | +----------+ 230 | | 3 | 231 | | 2 | 232 | | 1 | 233 | +----------+ 234 | 3 rows in set (0.00 sec) 235 | ``` 236 | 237 | ### Q3 解答後補足問題 2 238 | 239 | Q3 の SQL 文を group by 句を用いて同じ結果になる SELECT 文を構築しましょう 240 | 抽出結果 241 | 242 | ``` 243 | +----------+ 244 | | order_id | 245 | +----------+ 246 | | 3 | 247 | | 2 | 248 | | 1 | 249 | +----------+ 250 | 3 rows in set (0.00 sec) 251 | ``` 252 | 253 | ### Q4 受注全体から受注金額の平均を算出しましょう 254 | 255 | 抽出結果 256 | 257 | ``` 258 | +-----------------+ 259 | | avg_order_price | 260 | +-----------------+ 261 | | 33225.0000 | 262 | +-----------------+ 263 | 1 row in set (0.00 sec) 264 | ``` 265 | 266 | ### Q4 解答後補足問題 267 | 268 | 受注の件数も一緒に取得しましょう 269 | 270 | 抽出結果 271 | 272 | ``` 273 | +-----------------+-------------+ 274 | | avg_order_price | order_count | 275 | +-----------------+-------------+ 276 | | 33225.0000 | 4 | 277 | +-----------------+-------------+ 278 | 1 row in set (0.00 sec) 279 | ``` 280 | 281 | ### Q5 受注金額が一番大きい受注の受注 id と受注金額を求めましょう 282 | 283 | 抽出結果 284 | 285 | ``` 286 | +----+-------------+ 287 | | id | order_price | 288 | +----+-------------+ 289 | | 3 | 76000 | 290 | +----+-------------+ 291 | 1 row in set (0.00 sec) 292 | ``` 293 | 294 | ### Q5 解答後補足問題 295 | 296 | 今回の回答が正しいか考えてみましょう 297 | -------------------------------------------------------------------------------- /sql-training/step-2-answer.md: -------------------------------------------------------------------------------- 1 | # SQL 実力アップセミナー(Step2 回答) 2 | 3 | ## Answer 4 | 5 | ### A6 6 | 7 | [MySQL Bulk Data Loading](https://dev.mysql.com/doc/refman/8.0/en/optimizing-innodb-bulk-data-loading.html) 8 | ``` 9 | insert into items values 10 | (null,"タンクトップ",1300,now(),now()), 11 | (null,"ジャンパー",2500,now(),now()), 12 | (null,"ソックス",600,now(),now()); 13 | ``` 14 | 15 | 参考(DEFAULT CURRENT_TIMESTAMP の例) 16 | 17 | ``` 18 | CREATE TABLE `items2` ( 19 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 20 | `name` varchar(100) NOT NULL, 21 | `price` int(10) unsigned NOT NULL, 22 | `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 23 | `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 24 | PRIMARY KEY (`id`) 25 | ); 26 | 27 | insert into items2(name,price) values 28 | ("タンクトップ",1300), 29 | ("ジャンパー",2500), 30 | ("ソックス",600); 31 | ``` 32 | 33 | 問い合わせ結果 34 | 35 | ``` 36 | mysql> select * from items2; 37 | +----+--------------------+-------+---------------------+---------------------+ 38 | | id | name | price | created_at | updated_at | 39 | +----+--------------------+-------+---------------------+---------------------+ 40 | | 1 | タンクトップ | 1300 | 2020-05-06 01:31:30 | 2020-05-06 01:31:30 | 41 | | 2 | ジャンパー | 2500 | 2020-05-06 01:31:30 | 2020-05-06 01:31:30 | 42 | | 3 | ソックス | 600 | 2020-05-06 01:31:30 | 2020-05-06 01:31:30 | 43 | +----+--------------------+-------+---------------------+---------------------+ 44 | 3 rows in set (0.00 sec) 45 | ``` 46 | 47 | ### A6 解答後補足問題 48 | 49 | 今回の回答に至った insert 文のメリットを考えてみましょう 50 | 51 | 通常の insert 文の場合 1 レコード insert するたびに client から mysqld が作成したスレッドに要求がされるためレコード数分 insert 文が発行されるがこの insert では 1 回の insert 文で複数行の挿入が行える。 52 | 53 | ### A6 解答後補足問題 54 | 55 | 時刻カラムの設定をDB側(`now()` or `DEFAULT CURRENT_TIMESTAMP`)に任せるメリットを考えてみましょう 56 | 57 | アプリケーション側で時刻を設定した場合、複数のアプリケーションサーバで運営していた場合、サーバ間でのがズレが生じる可能性がある。 58 | 59 | `now()` or `DEFAULT CURRENT_TIMESTAMP`はSQL文(ステートメント)単位で発効される 60 | 61 | 62 | ### A7 63 | 64 | [MySQL Join](https://dev.mysql.com/doc/refman/8.0/en/join.html) 65 | 66 | ``` 67 | select sum(d.price * c.item_quantity) as sum_price from customers a 68 | inner join orders b on a.id = b.customer_id and name = "B商会" 69 | inner join order_details c on b.id = c.order_id 70 | inner join items d on d.id = c.item_id; 71 | ``` 72 | 73 | ``` 74 | select sum(order_details.item_quantity * items.price) as sum_price from orders 75 | inner join order_details on orders.id = order_details.order_id 76 | inner join items on order_details.item_id = items.id 77 | inner join customers on customers.id = orders.customer_id where customers.name = "B商会"; 78 | ``` 79 | 80 | ### A8 81 | 82 | #### 駆動票 items 83 | 84 | exists 85 | 86 | [MySQL EXISTS](https://dev.mysql.com/doc/refman/8.0/en/exists-and-not-exists-subqueries.html) 87 | 88 | ``` 89 | select a.id,a.name from items a where exists (select * from order_details b where a.id = b.item_id); 90 | ``` 91 | 92 | inner join(group by) 93 | 94 | ``` 95 | select a.id,a.name from items a inner join order_details b on a.id = b.item_id group by a.id ,a.name; 96 | ``` 97 | 98 | inner join(distinct) 99 | 100 | ``` 101 | select distinct a.id,a.name from items a inner join order_details b on a.id = b.item_id; 102 | ``` 103 | 104 | in 105 | 106 | ``` 107 | select a.id,a.name from items a where a.id in ( select b.item_id from order_details b ); 108 | ``` 109 | 110 | #### 駆動表 order_details 111 | 112 | exists 113 | 114 | ``` 115 | select distinct(item_id),(select name from items where items.id = item_id) as name from order_details 116 | where exists(select * from items where item_id = items.id); 117 | ``` 118 | 119 | inner join(distinct) 120 | 121 | ``` 122 | select distinct(item_id),items.name from order_details inner join items on item_id = items.id; 123 | ``` 124 | 125 | inner join(group by) 126 | 127 | ``` 128 | select a.item_id,b.name from order_details a inner join items b on a.item_id = b.id group by a.item_id,b.name; 129 | ``` 130 | 131 | in 132 | 133 | ``` 134 | select distinct(item_id),(select name from items 135 | where items.id = item_id) as name from order_details 136 | where item_id in (select id from items); 137 | ``` 138 | 139 | ### A9 140 | 141 | #### 駆動票 items 142 | 143 | not exists 144 | 145 | ``` 146 | select a.id ,a.name from items a where not exists (select * from order_details b where a.id = b.item_id); 147 | ``` 148 | 149 | left join 150 | 151 | ``` 152 | select a.id ,a.name from items a left outer join order_details b on a.id = b.item_id where b.order_id is null; 153 | ``` 154 | 155 | not in 156 | 157 | ``` 158 | select a.id ,a.name from items a where a.id not in (select b.item_id from order_details b); 159 | ``` 160 | 161 | #### 駆動票 order_details 162 | 163 | 例 outer join にて order_details からは満たせない 164 | 165 | ``` 166 | mysql> select a.item_id ,(select name from items where id = a.item_id) from order_details a right outer join items b on a.item_id = b.id; 167 | +---------+-----------------------------------------------+ 168 | | item_id | (select name from items where id = a.item_id) | 169 | +---------+-----------------------------------------------+ 170 | | 1 | シャツ | 171 | | 1 | シャツ | 172 | | 2 | パンツ | 173 | | 2 | パンツ | 174 | | 3 | マフラー | 175 | | 3 | マフラー | 176 | | 4 | ブルゾン | 177 | | NULL | NULL | 178 | | NULL | NULL | 179 | | NULL | NULL | 180 | +---------+-----------------------------------------------+ 181 | 10 rows in set (0.00 sec) 182 | 183 | mysql> select a.item_id ,(select name from items where id = a.item_id) from order_details a left outer join items b on a.item_id = b.id; 184 | +---------+-----------------------------------------------+ 185 | | item_id | (select name from items where id = a.item_id) | 186 | +---------+-----------------------------------------------+ 187 | | 1 | シャツ | 188 | | 1 | シャツ | 189 | | 2 | パンツ | 190 | | 2 | パンツ | 191 | | 3 | マフラー | 192 | | 3 | マフラー | 193 | | 4 | ブルゾン | 194 | +---------+-----------------------------------------------+ 195 | 7 rows in set (0.00 sec) 196 | ``` 197 | 198 | 例 outer join にて order_details からは満たせる(上との違いを確認してみましょう) 199 | 200 | ``` 201 | mysql> select b.id ,(select name from items where id = b.id) from order_details a right outer join items b on a.item_id = b.id where a.order_id is null; 202 | +----+------------------------------------------+ 203 | | id | (select name from items where id = b.id) | 204 | +----+------------------------------------------+ 205 | | 5 | タンクトップ | 206 | | 6 | ジャンパー | 207 | | 7 | ソックス | 208 | +----+------------------------------------------+ 209 | 3 rows in set (0.00 sec) 210 | ``` 211 | 212 | ### A10 213 | 214 | [MySQL CASEステートメント](https://dev.mysql.com/doc/refman/8.0/en/case.html) 215 | 216 | ``` 217 | select count(*) as "all_order", 218 | sum(case when item_id = 1 then 1 else 0 end) as "1", 219 | sum(case when item_id = 2 then 1 else 0 end) as "2", 220 | sum(case when item_id = 3 then 1 else 0 end) as "3", 221 | sum(case when item_id = 4 then 1 else 0 end) as "4", 222 | sum(case when item_id = 5 then 1 else 0 end) as "5", 223 | sum(case when item_id = 6 then 1 else 0 end) as "6", 224 | sum(case when item_id = 6 then 1 else 0 end) as "7" 225 | from order_details; 226 | ``` 227 | 228 | ### A11 229 | 230 | ``` 231 | select b.id , count(a.item_id) count 232 | from order_details a right outer join items b on a.item_id = b.id group by b.id; 233 | ``` 234 | 235 | `count(item_id) count`にしている理由を考えてみましょう 236 | 237 | ### A12 238 | 239 | ``` 240 | select "all_order" ,count(*) as count from order_details 241 | union 242 | select b.id , count(a.item_id) count 243 | from order_details a right outer join items b on a.item_id = b.id group by b.id; 244 | ``` 245 | 246 | ### A13 247 | 248 | [MySQL GROUP_CONCAT](https://dev.mysql.com/doc/refman/8.0/en/aggregate-functions.html#function_group-concat) 249 | ``` 250 | select b.id , group_concat(a.order_id) order_id from order_details a right outer join items b on a.item_id = b.id group by b.id; 251 | ``` 252 | 253 | ### A13 解答後補足問題 254 | 255 | `group_concat(a.order_id) as order`の`order`が予約語なため 256 | 257 | ``` 258 | mysql> select b.id ,group_concat(a.order_id) as order from order_details a right outer join items b on a.item_id = b.id group by b.id; 259 | ``` 260 | 261 | 元の回答のように`order_id`など予約語以外にするとエラーとならない 262 | 263 | ``` 264 | mysql> select b.id ,group_concat(a.order_id) as order_id from order_details a right outer join items b on a.item_id = b.id group by b.id; 265 | +----+----------+ 266 | | id | order_id | 267 | +----+----------+ 268 | | 1 | 1,2 | 269 | | 2 | 1,3 | 270 | | 3 | 2,4 | 271 | | 4 | 2 | 272 | | 5 | NULL | 273 | | 6 | NULL | 274 | | 7 | NULL | 275 | +----+----------+ 276 | 7 rows in set (0.01 sec) 277 | ``` 278 | -------------------------------------------------------------------------------- /sql-training/step-2-question.md: -------------------------------------------------------------------------------- 1 | ## Step2 Question 2 | 3 | ここからは冒頭の` テーブル定義``サンプルデータ `を再度実行しデータを最初の状態に戻したところから行います 4 | 5 | ### Q6 新しい商品を 1 回の insert で登録しましょう。 6 | 7 | | 商品 | 単価 | 8 | | :----------- | :--- | 9 | | タンクトップ | 1300 | 10 | | ジャンパー | 2500 | 11 | | ソックス | 600 | 12 | 13 | ヒント バルクインサートを検索しましょう 14 | 15 | ``` 16 | +----+--------------------+-------+---------------------+---------------------+ 17 | | id | name | price | created_at | updated_at | 18 | +----+--------------------+-------+---------------------+---------------------+ 19 | | 1 | シャツ | 1000 | 2021-06-02 01:00:43 | 2021-06-02 01:00:43 | 20 | | 2 | パンツ | 950 | 2021-06-02 01:00:43 | 2021-06-02 01:00:43 | 21 | | 3 | マフラー | 1200 | 2021-06-02 01:00:43 | 2021-06-02 01:00:43 | 22 | | 4 | ブルゾン | 1800 | 2021-06-02 01:00:43 | 2021-06-02 01:00:43 | 23 | | 5 | タンクトップ | 1300 | 2021-06-02 01:07:17 | 2021-06-02 01:07:17 | 24 | | 6 | ジャンパー | 2500 | 2021-06-02 01:07:17 | 2021-06-02 01:07:17 | 25 | | 7 | ソックス | 600 | 2021-06-02 01:07:17 | 2021-06-02 01:07:17 | 26 | +----+--------------------+-------+---------------------+---------------------+ 27 | 7 rows in set (0.00 sec) 28 | ``` 29 | 30 | ### Q6 解答後補足問題 31 | 32 | 今回の回答に至った insert 文のメリットを考えてみましょう 33 | 34 | ### Q6 解答後補足問題 35 | 36 | 時刻カラムの設定をDB側に任せるメリットを考えてみましょう 37 | 38 | ### Q7 B 商会の受注金額の合計を算出しましょう 39 | 40 | 抽出例 41 | 42 | ``` 43 | +-----------+ 44 | | sum_price | 45 | +-----------+ 46 | | 98000 | 47 | +-----------+ 48 | 1 row in set (0.00 sec) 49 | ``` 50 | 51 | ### Q8 商品(itemss)から受注明細(order_details)で使われている商品(items.id,items.name)を求めましょう、3 種類の SQL を作成しましょう 52 | 53 | 抽出例 54 | 55 | ``` 56 | +---------+--------------+ 57 | | item_id | name | 58 | +---------+--------------+ 59 | | 1 | シャツ | 60 | | 2 | パンツ | 61 | | 3 | マフラー | 62 | | 4 | ブルゾン | 63 | +---------+--------------+ 64 | 4 rows in set (0.00 sec) 65 | ``` 66 | 67 | ヒント:「inner join」「exists」「in」で一つずつ構築しましょう 68 | 69 | ### Q9 受注明細(order_details)で使われていない商品(items.id,items.name)を求めましょう、3 種類の SQL を作成しましょう 70 | 71 | 抽出例 72 | 73 | ``` 74 | +----+--------------------+ 75 | | id | name | 76 | +----+--------------------+ 77 | | 5 | タンクトップ | 78 | | 6 | ジャンパー | 79 | | 7 | ソックス | 80 | +----+--------------------+ 81 | 3 rows in set (0.00 sec) 82 | ``` 83 | 84 | ### Q10 全アイテム(items)に存在する商品を受注明細(order_details)から各商品ごとの注文回数(抽出例は item_id が 1~7 固定のものとする)を求めよ、全注文数(all_order)も求めよ 85 | 86 | 抽出例 87 | 88 | ``` 89 | +-----------+------+------+------+------+------+------+------+ 90 | | all_order | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 91 | +-----------+------+------+------+------+------+------+------+ 92 | | 7 | 2 | 2 | 2 | 1 | 0 | 0 | 0 | 93 | +-----------+------+------+------+------+------+------+------+ 94 | 1 row in set (0.00 sec) 95 | ``` 96 | 97 | ### Q11 全アイテム(items)に存在する商品を受注明細(order_details)から各商品ごとの注文回数を求めよ(5 分) 98 | 99 | 抽出例 100 | 101 | ``` 102 | +----+-------+ 103 | | id | count | 104 | +----+-------+ 105 | | 1 | 2 | 106 | | 2 | 2 | 107 | | 3 | 2 | 108 | | 4 | 1 | 109 | | 5 | 0 | 110 | | 6 | 0 | 111 | | 7 | 0 | 112 | +----+-------+ 113 | 7 rows in set (0.00 sec) 114 | ``` 115 | 116 | ### Q12 全アイテム(items)に存在する商品を受注明細(order_details)から各商品ごとの注文回数を求めよ、全注文数(all_order)も求めよ(5 分) 抽出例 117 | 118 | 抽出例 119 | 120 | ``` 121 | +-----------+-------+ 122 | | all_order | count | 123 | +-----------+-------+ 124 | | all_order | 7 | 125 | | 1 | 2 | 126 | | 2 | 2 | 127 | | 3 | 2 | 128 | | 4 | 1 | 129 | | 5 | 0 | 130 | | 6 | 0 | 131 | | 7 | 0 | 132 | +-----------+-------+ 133 | 8 rows in set (0.00 sec) 134 | ``` 135 | 136 | ### Q13 Q11,Q12 で求めた全アイテム(items)に存在する商品を受注明細(order_details)から各商品ごとの注文(order_id)を求めよ、(5 分) 抽出例 137 | 138 | 抽出例 139 | 140 | ``` 141 | +----+----------+ 142 | | id | order_id | 143 | +----+----------+ 144 | | 1 | 1,2 | 145 | | 2 | 1,3 | 146 | | 3 | 2,4 | 147 | | 4 | 2 | 148 | | 5 | NULL | 149 | | 6 | NULL | 150 | | 7 | NULL | 151 | +----+----------+ 152 | 7 rows in set (0.00 sec) 153 | ``` 154 | 155 | ### Q13 解答後補足問題 156 | 157 | エラーとなる理由を考えてみましょう 158 | 159 | ``` 160 | mysql> select b.id ,group_concat(a.order_id) as order from order_details a right outer join items b on a.item_id = b.id group by b.id; 161 | ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'order from order_details a right outer join items b on a.item_id = b.id group by' at line 1 162 | ``` 163 | 164 | --- 165 | --------------------------------------------------------------------------------