From 7c7ae75f1b384d1379b92699de46a96717292f6b Mon Sep 17 00:00:00 2001 From: openkmj Date: Thu, 9 Jan 2025 17:09:51 +0900 Subject: [PATCH 1/4] week1 --- .gitignore | 11 + missions/W1/M1.ipynb | 542 +++++ missions/W1/M2.ipynb | 2156 +++++++++++++++++ missions/W1/M3/README.md | 250 ++ missions/W1/M3/etl_project_gdp.py | 62 + missions/W1/M3/etl_project_gdp_from_csv.py | 119 + missions/W1/M3/etl_project_gdp_parallel.py | 236 ++ missions/W1/M3/etl_project_gdp_with_sql.py | 83 + missions/W1/M3/modules/exporter.py | 50 + missions/W1/M3/modules/importer.py | 180 ++ missions/W1/M3/modules/logger.py | 38 + .../M3/utils/create_country_region_table.py | 48 + missions/W1/M3/utils/create_large_data_csv.py | 72 + missions/W1/create.sql | 1035 ++++++++ missions/W1/mtcars.csv | 66 +- 15 files changed, 4915 insertions(+), 33 deletions(-) create mode 100644 missions/W1/M1.ipynb create mode 100644 missions/W1/M2.ipynb create mode 100644 missions/W1/M3/README.md create mode 100644 missions/W1/M3/etl_project_gdp.py create mode 100644 missions/W1/M3/etl_project_gdp_from_csv.py create mode 100644 missions/W1/M3/etl_project_gdp_parallel.py create mode 100644 missions/W1/M3/etl_project_gdp_with_sql.py create mode 100644 missions/W1/M3/modules/exporter.py create mode 100644 missions/W1/M3/modules/importer.py create mode 100644 missions/W1/M3/modules/logger.py create mode 100644 missions/W1/M3/utils/create_country_region_table.py create mode 100644 missions/W1/M3/utils/create_large_data_csv.py create mode 100644 missions/W1/create.sql diff --git a/.gitignore b/.gitignore index 68bc17f..3baab37 100644 --- a/.gitignore +++ b/.gitignore @@ -158,3 +158,14 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + +*.db +large_data*.csv +.DS_Store +.venv +.ipynb_checkpoints +large_data*.csv +__pycache__ +*.db +*.json +*.log \ No newline at end of file diff --git a/missions/W1/M1.ipynb b/missions/W1/M1.ipynb new file mode 100644 index 0000000..3a24589 --- /dev/null +++ b/missions/W1/M1.ipynb @@ -0,0 +1,542 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "id": "a1297356-f73a-484b-b466-ae99843f51f1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "head:\n", + " Unnamed: 0 mpg cyl disp hp drat wt qsec vs am gear \\\n", + "0 Mazda RX4 21.0 6 160.0 110 3.9 2.620 16.46 0 1 4 \n", + "1 Mazda RX4 Wag 21.0 6 160.0 110 3.9 2.875 17.02 0 1 4 \n", + "\n", + " carb \n", + "0 4 \n", + "1 4 \n", + "tail:\n", + " Unnamed: 0 mpg cyl disp hp drat wt qsec vs am gear carb\n", + "30 Maserati Bora 15.0 8 301.0 335 3.54 3.57 14.6 0 1 5 8\n", + "31 Volvo 142E 21.4 4 121.0 109 4.11 2.78 18.6 1 1 4 2\n" + ] + } + ], + "source": [ + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "\n", + "\n", + "df = pd.read_csv('./mtcars.csv')\n", + "\n", + "# print head: 상단 n개 출력\n", + "print('head:\\n', df.head(2))\n", + "\n", + "# print tail: 하단 n개 출력\n", + "print('tail:\\n', df.tail(2))" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "bdf05680", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "shape:\n", + " (32, 12)\n", + "row count: 32\n" + ] + } + ], + "source": [ + "\n", + "# print shape:\n", + "print('shape:\\n', df.shape)\n", + "print('row count:', df.shape[0])\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "58fcc7b9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "columns:\n", + " Index(['Unnamed: 0', 'mpg', 'cyl', 'disp', 'hp', 'drat', 'wt', 'qsec', 'vs',\n", + " 'am', 'gear', 'carb'],\n", + " dtype='object')\n", + "Unnamed: 0\n", + "columns:\n", + " Index(['name', 'mpg', 'cyl', 'disp', 'hp', 'drat', 'wt', 'qsec', 'vs', 'am',\n", + " 'gear', 'carb'],\n", + " dtype='object')\n" + ] + } + ], + "source": [ + "print('columns:\\n', df.columns)\n", + "\n", + "# set first column nas as 'name'\n", + "print(df.columns[0])\n", + "df.rename(columns={df.columns[0]: 'name'}, inplace=True)\n", + "\n", + "print('columns:\\n', df.columns)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "7c3c4cdc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "RangeIndex: 32 entries, 0 to 31\n", + "Data columns (total 12 columns):\n", + " # Column Non-Null Count Dtype \n", + "--- ------ -------------- ----- \n", + " 0 name 32 non-null object \n", + " 1 mpg 32 non-null float64\n", + " 2 cyl 32 non-null int64 \n", + " 3 disp 32 non-null float64\n", + " 4 hp 32 non-null int64 \n", + " 5 drat 32 non-null float64\n", + " 6 wt 32 non-null float64\n", + " 7 qsec 32 non-null float64\n", + " 8 vs 32 non-null int64 \n", + " 9 am 32 non-null int64 \n", + " 10 gear 32 non-null int64 \n", + " 11 carb 32 non-null int64 \n", + "dtypes: float64(5), int64(6), object(1)\n", + "memory usage: 3.1+ KB\n", + "dtypes:\n", + " name object\n", + "mpg float64\n", + "cyl int64\n", + "disp float64\n", + "hp int64\n", + "drat float64\n", + "wt float64\n", + "qsec float64\n", + "vs int64\n", + "am int64\n", + "gear int64\n", + "carb int64\n", + "dtype: object\n" + ] + } + ], + "source": [ + "\n", + "# print info:\n", + "df.info()\n", + "\n", + "print('dtypes:\\n', df.dtypes)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "e1912a1a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "describe:\n", + " mpg cyl disp hp drat wt \\\n", + "count 32.000000 32.000000 32.000000 32.000000 32.000000 32.000000 \n", + "mean 20.090625 6.187500 230.721875 146.687500 3.596563 3.217250 \n", + "std 6.026948 1.785922 123.938694 68.562868 0.534679 0.978457 \n", + "min 10.400000 4.000000 71.100000 52.000000 2.760000 1.513000 \n", + "25% 15.425000 4.000000 120.825000 96.500000 3.080000 2.581250 \n", + "50% 19.200000 6.000000 196.300000 123.000000 3.695000 3.325000 \n", + "75% 22.800000 8.000000 326.000000 180.000000 3.920000 3.610000 \n", + "max 33.900000 8.000000 472.000000 335.000000 4.930000 5.424000 \n", + "\n", + " qsec vs am gear carb \n", + "count 32.000000 32.000000 32.000000 32.000000 32.0000 \n", + "mean 17.848750 0.437500 0.406250 3.687500 2.8125 \n", + "std 1.786943 0.504016 0.498991 0.737804 1.6152 \n", + "min 14.500000 0.000000 0.000000 3.000000 1.0000 \n", + "25% 16.892500 0.000000 0.000000 3.000000 2.0000 \n", + "50% 17.710000 0.000000 0.000000 4.000000 2.0000 \n", + "75% 18.900000 1.000000 1.000000 4.000000 4.0000 \n", + "max 22.900000 1.000000 1.000000 5.000000 8.0000 \n" + ] + } + ], + "source": [ + "\n", + "# print describe:\n", + "print('describe:\\n', df.describe())\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "976cd107", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gear unique values: 3 [4 3 5]\n", + "transmission unique values: 2 [1 0]\n" + ] + } + ], + "source": [ + "print('gear unique values:', df['gear'].nunique(), df['gear'].unique())\n", + "\n", + "print('transmission unique values:', df['am'].nunique(), df['am'].unique())\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "86e8f8b6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "am 0 1\n", + "gear \n", + "3 15 0\n", + "4 4 8\n", + "5 0 5\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjIAAAHHCAYAAACle7JuAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAPcxJREFUeJzt3Wd0FeX+9vFrk0B6QkshlNBbaEqTIkUiEBEBRbAAAVQ4hw5S/0ekqARQOahwECwEPSCoB7BSIkUQ6RCVDhogiAEpJtQAyf28cGU/blJIMGHvId/PWrNW5p72mzsD+8q0bTPGGAEAAFhQIWcXAAAAcLsIMgAAwLIIMgAAwLIIMgAAwLIIMgAAwLIIMgAAwLIIMgAAwLIIMgAAwLIIMgAAwLIIMkAmTp06pa5du6pEiRKy2WyaOXOms0v623r37i1fX19nlwGLO3r0qGw2m2JiYvJ83TabTRMnTszz9eLuRpCBpfzwww+y2Ww6ePCgJOnf//63ypcvn+fbGT58uFatWqVx48bpww8/VPv27bOd/+rVq/r3v/+txo0bKyAgQJ6enqpataoGDRqkQ4cO5Xl9ri4tLU0ffPCBHnzwQZUsWVKFCxdWUFCQ2rZtq3nz5iklJcXZJeYJm82Wo2H9+vXOLhW4a7k7uwAgN7Zu3arixYuratWqkqTNmzfrvvvuy/PtrF27Vp06ddLIkSNvOe+ZM2fUvn177dy5Uw8//LCeeuop+fr66uDBg1q8eLHmzZuna9eu5XmNrurKlSvq0qWLVq1apaZNm2rkyJEKDg7WuXPn9O2332rAgAHaunWr3nvvPWeX+rd9+OGHDuMffPCBYmNjM7TXqFHjTpaVr8LCwnTlyhUVLlw4z9d95coVubvzsYTc4YiBpWzbtk2NGjWSzWaT9GeQGTFiRJ5v5/Tp0ypatGiO5u3du7d2796tTz/9VI899pjDtJdeekn/+te/8qSmS5cuycfHJ0/WlZ/Sz2bNnDlTQ4cOdZj2/PPP6/Dhw4qNjb3jdV2+fFne3t55us4ePXo4jG/ZskWxsbEZ2u9ELXeKzWaTp6dnvqw7v9aLuxuXluDyzp8/rzNnzujMmTPaunWratWqpTNnzmjv3r06ceKEqlSpojNnzujixYu3XNcvv/yixx9/XMWLF5e3t7fuu+8+ffXVV/bpMTExstlsMsZo9uzZ9ksDWdm6dau++uorPfPMMxlCjCR5eHjotddes4//+OOP6t27typWrChPT0+FhISob9++Onv2rMNyEydOlM1m0759+/TUU0+pWLFiat68uSQpMTFRffr0UZkyZeTh4aFSpUqpU6dOOnr06C33P70P2rVrJx8fH4WGhmry5MkyxkiSjDEqX768OnXqlGG5q1evKiAgQP37989y3QkJCXr33XfVvn37DCEmXZUqVTRgwACHtrS0NM2cOVPh4eHy9PRUcHCw+vfvr/PnzzvM99lnn6lDhw4KDQ2Vh4eHKlWqpJdeekmpqakO87Vq1Uq1atXSzp071aJFC3l7e+v//u//Mq3n9OnTCgwMVKtWrez9IElHjhyRj4+PunfvnuX+5kR2teR2f/bt26fWrVvL29tbpUuX1vTp0zNs76233lJ4eLi8vb1VrFgxNWjQQIsWLbJPTz+2Dh06pB49eiggIECBgYEaP368jDFKSEhQp06d5O/vr5CQEL3++usO68/sHpmcHJM7duxQu3btVLJkSXl5ealChQrq27evw7ozu0dm9+7dioyMlL+/v3x9fdWmTRtt2bLFYZ70f7ebNm3SiBEjFBgYKB8fH3Xp0kW///77LX9HsDbOyMDl3XPPPTp27Jh9fM+ePQ7hoGPHjpKkqKiobG9APHXqlJo2barLly9ryJAhKlGihBYsWKBHHnlEn376qbp06aIWLVroww8/VM+ePfXggw+qV69e2db2+eefS5J69uyZo32JjY3VL7/8oj59+igkJER79+7VvHnztHfvXm3ZsiVDaHr88cdVpUoVTZkyxf4h+9hjj2nv3r0aPHiwypcvr9OnTys2NlbHjx+/5f1Cqampat++ve677z5Nnz5dK1eu1IQJE3Tjxg1NnjxZNptNPXr00PTp03Xu3DkVL17cvuwXX3yh5OTkbM82rFixQqmpqbc8I3Gz/v37KyYmRn369NGQIUMUHx+vWbNmaffu3dq0aZP9MkZMTIx8fX01YsQI+fr6au3atXrxxReVnJysV1991WGdZ8+eVWRkpJ544gn16NFDwcHBmW47KChIc+bM0eOPP6633npLQ4YMUVpamnr37i0/Pz/95z//ydW+ZCarWnKzP+fPn1f79u316KOPqlu3bvr00081ZswY1a5dW5GRkZKkd955R0OGDFHXrl01dOhQXb16VT/++KO2bt2qp556ymF93bt3V40aNTR16lR99dVXevnll1W8eHHNnTtXDzzwgKZNm6aFCxdq5MiRatiwoVq0aJHl/t3qmDx9+rTatm2rwMBAjR07VkWLFtXRo0e1dOnSbPtt7969uv/+++Xv76/Ro0ercOHCmjt3rlq1aqVvv/1WjRs3dph/8ODBKlasmCZMmKCjR49q5syZGjRokJYsWZLj3xUsyAAu7rvvvjOxsbFm/Pjxxt3d3axYscLExsaayMhI06BBAxMbG2tiY2PN3r17s13PsGHDjCSzceNGe9uFCxdMhQoVTPny5U1qaqq9XZIZOHDgLWvr0qWLkWTOnz+fo325fPlyhraPPvrISDIbNmywt02YMMFIMk8++aTDvOfPnzeSzKuvvpqj7f1VVFSUkWQGDx5sb0tLSzMdOnQwRYoUMb///rsxxpiDBw8aSWbOnDkOyz/yyCOmfPnyJi0tLcttDB8+3EgycXFxDu0pKSnm999/tw9nzpyxT9u4caORZBYuXOiwzMqVKzO0Z9Z//fv3N97e3ubq1av2tpYtWxpJ5u23386uSxw8+eSTxtvb2xw6dMi8+uqrRpJZvnx5jpc3xpiBAweam/9bza6W3O7PBx98YG9LSUkxISEh5rHHHrO3derUyYSHh2dbY/qx1a9fP3vbjRs3TJkyZYzNZjNTp061t58/f954eXmZqKgoe1t8fLyRZObPn2+f51bH5LJly4wks3379mxrk2QmTJhgH+/cubMpUqSI+fnnn+1tJ0+eNH5+fqZFixb2tvnz5xtJJiIiwuH4HD58uHFzczN//PFHttuFtXFpCS6vWbNmioiI0MWLF9WwYUO1b99eEREROn78uB5++GFFREQoIiJCNWvWzHY9X3/9tRo1amS/RCNJvr6+6tevn44ePap9+/blurbk5GRJkp+fX47m9/Lysv989epVnTlzxn6z8q5duzLM/49//CPD8kWKFNH69eszXHbJqUGDBtl/ttlsGjRokK5du6ZvvvlGklS1alU1btxYCxcutM937tw5rVixQk8//XS2l9rS++Pmx7y//vprBQYG2oewsDD7tE8++UQBAQF68MEH7ZcQz5w5o/r168vX11fr1q1z2P90Fy5c0JkzZ3T//ffr8uXLOnDggMM2PTw81KdPnxz3y6xZsxQQEKCuXbtq/Pjx6tmzZ6aX2G5HVrXkZn98fX0dznQVKVJEjRo10i+//GJvK1q0qE6cOKHt27ffsqZnn33W/rObm5saNGggY4yeeeYZh/VVq1bNYRuZ7cOtjsn0+82+/PJLXb9+/Za1SX+ePVy9erU6d+6sihUr2ttLlSqlp556St999539eEvXr18/h+Pz/vvvV2pqqsMZXdx9CDJwaUlJSfYPtjVr1qhx48Y6c+aMDh06pL1796pu3bo6c+aMkpKSbrmuY8eOqVq1ahna058ouZ3/7Pz9/SX9+SGUE+fOndPQoUMVHBwsLy8vBQYGqkKFCpKU6T6kT0vn4eGhadOmacWKFQoODlaLFi00ffp0JSYm5mj7hQoVcvhQkGR/Auyv9zP06tVLmzZtsvfJJ598ouvXr9/yElp6oLv5fqVmzZopNjZWsbGxatu2rcO0w4cPKykpSUFBQQ5hJzAwUBcvXtTp06ft8+7du1ddunRRQECA/P39FRgYaP9wv7n/SpcurSJFityqS+yKFy+uN998Uz/++KMCAgL05ptv5njZW8mqltzsT5kyZTKEyGLFijmEhzFjxsjX11eNGjVSlSpVNHDgQG3atCnTmsqVK+cwnv7agJIlS2Zozy405+SYbNmypR577DFNmjRJJUuWVKdOnTR//vxsH8P//fffdfny5Sz/zaalpSkhISHbfSpWrJgk3XbohzUQZODSOnXqZP9Q+/HHHzVz5kwFBgba/3Pr0qWLAgMD8+wv59yqXr26JOmnn37K0fzdunXTO++8o3/84x9aunSpVq9erZUrV0r684bXm/31L/Z0w4YN06FDhxQdHS1PT0+NHz9eNWrU0O7du//Gnjh64oknVLhwYftZmf/+979q0KBBph8qf5XeH3v27HFoDwwMtJ85K1WqlMO0tLQ0BQUF2YPOzcPkyZMlSX/88YdatmypH374QZMnT9YXX3yh2NhYTZs2zb6ev8qs725l1apVkv784Dtx4kSul89KZrXkdn/c3NwyXbf5yw3KNWrUsD/237x5c/3vf/9T8+bNNWHChAzLZba+nGwjM7c6Jm02mz799FNt3rxZgwYN0q+//qq+ffuqfv36ObpJP6dut35YG0EGLu3111+3f5i5ublpxYoVio2N1SOPPKL69evbP+xufrIiM2FhYfYX6f1V+in8v17uyKn0G43/+9//3nLe8+fPa82aNRo7dqwmTZqkLl266MEHH8xwhiQnKlWqpOef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SwSZ69evq1u3bjLGaM6cOdnOO27cOCUlJdmHhISEO1QlAAC401z+0lJ6iDl27JjWrl2b7dkYSfLw8JCHh8cdqg4AADiTSweZ9BBz+PBhrVu3TiVKlHB2SQAAwIU4NchcvHhRR44csY/Hx8crLi5OxYsXV6lSpdS1a1ft2rVLX375pVJTU5WYmChJKl68uIoUKeKssgEAgItwapDZsWOHWrdubR8fMWKEJCkqKkoTJ07U559/LkmqV6+ew3Lr1q1Tq1at7lSZAADARTk1yLRq1UrGmCynZzcNAADAEk8tAQAAZIYgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALIsgAwAALMupQWbDhg3q2LGjQkNDZbPZtHz5cofpxhi9+OKLKlWqlLy8vBQREaHDhw87p1gAAOBynBpkLl26pLp162r27NmZTp8+fbrefPNNvf3229q6dat8fHzUrl07Xb169Q5XCgAAXJG7MzceGRmpyMjITKcZYzRz5ky98MIL6tSpkyTpgw8+UHBwsJYvX64nnnjiTpYKAABckMveIxMfH6/ExERFRETY2wICAtS4cWNt3rw5y+VSUlKUnJzsMAAAgLuTywaZxMRESVJwcLBDe3BwsH1aZqKjoxUQEGAfypYtm691AgAA53HZIHO7xo0bp6SkJPuQkJDg7JIAAEA+cdkgExISIkk6deqUQ/upU6fs0zLj4eEhf39/hwEAANydXDbIVKhQQSEhIVqzZo29LTk5WVu3blWTJk2cWBkAAHAVTn1q6eLFizpy5Ih9PD4+XnFxcSpevLjKlSunYcOG6eWXX1aVKlVUoUIFjR8/XqGhoercubPzigYAAC7DqUFmx44dat26tX18xIgRkqSoqCjFxMRo9OjRunTpkvr166c//vhDzZs318qVK+Xp6emskgEAgAuxGWOMs4vIT8nJyQoICFBSUlKe3y9TfuxXebo+Kzk6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Ji0tLdP1STIvvPCCfXzz5s1GknnvvffsbR999JHx9PS0j/81yJw9e9ZIMuvXr890/X5+fiYmJibTaTf3Q9OmTc1zzz3nMM/jjz9uHnrooSzrvXjxopFkVqxYkek2AFfGpSXARWzbtk1xcXEKDw9XSkqKpD8vKVy8eFElSpSw31Ph6+ur+Ph4+2WKixcvauTIkapRo4aKFi0qX19f7d+/X8ePH3dYf4MGDW6rrvfff189evSwj/fo0UOffPKJLly4cJt7+qfk5GSdPHlSzZo1c2hv1qyZ9u/ff9vrvXk/c9o/derUsf+cfsnq9OnTkqTevXsrLi5O1apV05AhQ7R69eoM2/3r8sHBwZKk2rVrO7RdvXpVycnJGZYtXry4evfurXbt2qljx4564403HO4HGjFihJ599llFRERo6tSpDpeobrZ///4c9elf6/Xx8ZG/v799fwErIcgAd1jlypVls9kcblKVpIoVK6py5coON25evHhRpUqVUlxcnMNw8OBBjRo1StKfT78sW7ZMU6ZM0caNGxUXF6fatWvr2rVrDuv38fHJda379u3Tli1bNHr0aLm7u8vd3V333XefLl++rMWLF9vns9lsMjd9/+z169dzvb28cPN+5rR/Chcu7DBus9mUlpYmSbr33nsVHx+vl156SVeuXFG3bt3UtWvXLJe32WxZtqWv82bz58/X5s2b1bRpUy1ZskRVq1bVli1bJP15P83evXvVoUMHrV27VjVr1tSyZcty3CeZyW5/ASshyAB3WIkSJfTggw9q1qxZunTpUrbz3nvvvUpMTJS7u7sqV67sMJQsWVKStGnTJvXu3VtdunRR7dq1FRISoqNHj+ZJre+9955atGihH374wSFIjRgxQu+99559vsDAQIczCIcPH3Z4hDz9CanU1FR7m7+/v0JDQ7Vp0yaHbW7atEk1a9bMk/rT15cX/ePv76/u3bvrnXfe0ZIlS/S///1P586dy7M6pT9vhh43bpy+//571apVS4sWLbJPq1q1qoYPH67Vq1fr0Ucf1fz58zNdR40aNfK9TwFX4prPJQJ3uf/85z9q1qyZGjRooIkTJ6pOnToqVKiQtm/frgMHDqh+/fqSpIiICDVp0kSdO3fW9OnTVbVqVZ08eVJfffWVunTpogYNGqhKlSpaunSpOnbsKJvNpvHjx+fJX9bXr1/Xhx9+qMmTJ6tWrVoO05599lnNmDFDe/fuVXh4uB544AHNmjVLTZo0UWpqqsaMGePwF39QUJC8vLy0cuVKlSlTRp6engoICNCoUaM0YcIEVapUSfXq1dP8+fMVFxenhQsX/u360+VF/8yYMUOlSpXSPffco0KFCumTTz5RSEhInr2ILj4+XvPmzdMjjzyi0NBQHTx4UIcPH1avXr105coVjRo1Sl27dlWFChV04sQJbd++XY899lim6xo1apS6deume+65RxEREfriiy+0dOnSDE9qAXcLggzgBJUqVdLu3bs1ZcoUjRs3TidOnJCHh4dq1qypkSNHasCAAZL+PN3/9ddf61//+pf69Omj33//XSEhIWrRooX9PowZM2aob9++atq0qUqWLKkxY8Zkeh9GZlq1aqXy5cvbH5P+q88//1xnz55Vly5dMkyrUaOGatSooffee08zZszQ66+/rj59+uj+++9XaGio3njjDe3cudM+v7u7u958801NnjxZL774ou6//36tX79eQ4YMUVJSkp5//nmdPn1aNWvW1Oeff64qVarcRq9m7u/0Tzo/Pz9Nnz5dhw8flpubmxo2bKivv/5ahQrlzUltb29vHThwQAsWLNDZs2dVqlQpDRw4UP3799eNGzd09uxZ9erVS6dOnVLJkiX16KOPatKkSZmuq3PnznrjjTf02muvaejQoapQoYLmz59vf+QduNvYzM0XtgEUGGFhYZo0aZJ69+7t7FIA4LZwjwxQQO3du1cBAQHq1auXs0sBgNvGGRkAAGBZnJEBAACWRZABAACWRZABAACWRZABAACWRZABAACWRZABAACWRZABAACWRZABAACWRZABAACWRZABAACW9f8AIeAiG75zYIYAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# with pandas cross tab\n", + "result = pd.crosstab(df['gear'], df['am'])\n", + "print(result)\n", + "\n", + "x = [f\"{gear}, {am}\" for gear in result.index for am in result.columns]\n", + "y = result.values.flatten()\n", + "\n", + "plt.bar(x,y)\n", + "\n", + "plt.title('# of Cars by Gear x Transmission')\n", + "plt.xlabel('Gear, Auto Transmission')\n", + "plt.ylabel('# of Cars')\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "302ffcd4", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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nWSJIHP9+nX17NlTxhjNmzcvx3knTJig5ORk+5CQkHCHqgQAAHeay99ayggxx48f17p163K8GiNJHh4e8vDwuEPVAQAAZ3LpIJMRYo4cOaL169crICDA2SUBAAAX4tQgc/HiRR09etQ+Hh8fr7i4OJUqVUplypRRjx49tHv3bn3++edKS0tTYmKiJKlUqVIqVqyYs8oGAAAuwqlBZufOnWrXrp19fNSoUZKkqKgoTZ48WZ9++qkkqUGDBg7LrV+/Xm3btr1TZQIAABfl1CDTtm1bGWOynZ7TNAAAAEs8tQQAAJAVggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAsggwAALAspwaZjRs3qkuXLgoNDZXNZtPKlSsdphtj9Pzzz6tMmTIqXry4IiIidOTIEecUCwAAXI5Tg8ylS5dUv359zZ07N8vpM2fO1Ouvv64333xT27Ztk7e3tzp27KirV6/e4UoBAIArcnfmxiMjIxUZGZnlNGOMZs+ereeee05du3aVJL333nsKDg7WypUr9dhjj93JUgEAgAty2T4y8fHxSkxMVEREhL3N399fTZs21ZYtW7JdLjU1VSkpKQ4DAAAonFw2yCQmJkqSgoODHdqDg4Pt07ISHR0tf39/+1C+fPkCrRMAADiPywaZ2zVhwgQlJyfbh4SEBGeXBAAACojLBpmQkBBJ0unTpx3aT58+bZ+WFQ8PD/n5+TkMAACgcHLZIFOpUiWFhIRo7dq19raUlBRt27ZNzZo1c2JlAADAVTj1qaWLFy/q6NGj9vH4+HjFxcWpVKlSqlChgkaMGKEXX3xR1apVU6VKlTRx4kSFhoaqW7duzisaAAC4DKcGmZ07d6pdu3b28VGjRkmSoqKiFBMTo7Fjx+rSpUsaMGCAfv/9d7Vs2VKrV6+Wp6ens0oGAAAuxGaMMc4uoiClpKTI399fycnJ+d5fpuL4L/J1fci9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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# without crosstab\n", + "groupby = df.groupby(['gear', 'am']).size().reset_index(name='count')\n", + "\n", + "x = [f\"{gear}, {am}\" for gear, am, _ in groupby.values]\n", + "y = [count for _, _, count in groupby.values]\n", + "\n", + "\n", + "plt.bar(x,y)\n", + "\n", + "# make graph with above data\n", + "plt.title('# of Cars by Gear x Transmission')\n", + "plt.xlabel('Gear, Auto Transmission')\n", + "plt.ylabel('# of Cars')\n", + "plt.show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "e9ca6925", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAxgAAAMVCAYAAAD041K6AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAraBJREFUeJzs3Xl8TOf+B/DPZJskZLFEIhKRWmKJKEIae4uEpirVa7v29baNomm1aEtCK6iWthRdJG1R261wFUksoSqU4BLUUktUE1pERIhJ5vn94Ze5RtaZOTNnTnzer1dezJnnnPN9njPznPOdc85zVEIIASIiIiIiIgnYyB0AERERERFVHUwwiIiIiIhIMkwwiIiIiIhIMkwwiIiIiIhIMkwwiIiIiIhIMkwwiIiIiIhIMkwwiIiIiIhIMkwwiIiIiIhIMkwwiIiIiIhIMkwwiKjKadCgAUaOHCl3GE+8vLw8jB07Fl5eXlCpVJg8ebLcIQEAunXrhm7duhk836VLl6BSqbBgwYIKy8bExEClUhkRHRGR8jHBICKrlpCQAJVKhcOHD5f6frdu3RAYGGjyerZu3YqYmBiTl0P/M2fOHCQkJODVV1/F999/j2HDhpUoc+TIEahUKrz33ntlLufcuXNQqVSIjo42Z7hERCQRO7kDICKS2pkzZ2BjY9jvJ1u3bsWSJUuYZEho165deOaZZzBz5swyy7Rp0wZNmzbFDz/8gA8++KDUMqtXrwYADB06VJK4kpOTJVkOERGVjmcwiKjKUavVsLe3lzsMg9y9e1fuECR3/fp1uLu7V1huyJAhuHDhAg4cOFDq+z/88AOaNm2KNm3amBRPfn4+AMDBwQEODg4mLYuIiMrGBIOIqpzH78HQaDSIjY1F48aN4ejoiFq1aqFTp05ISUkBAIwcORJLliwBAKhUKt1fsbt37+LNN9+Er68v1Go1AgICsGDBAggh9NZ77949TJw4EbVr14aLiwtefPFFXL16FSqVSu/MSPH1+adOncI///lP1KhRA506dQIAHD9+HCNHjsRTTz0FR0dHeHl5YfTo0bhx44beuoqXcfbsWQwdOhRubm7w8PDA+++/DyEErly5gr59+8LV1RVeXl74+OOPS7TT559/jhYtWsDZ2Rk1atRAcHCw7mxBea5fv44xY8bA09MTjo6OaNWqFb799lvd+6mpqVCpVLh48SJ++uknXXteunSp1OUNGTIEAEpdd3p6Os6cOaMrs2nTJkRERMDb2xtqtRoNGzbE7NmzUVRUpDdf8aVz6enp6NKlC5ydnTF9+nTde4/eg/HgwQPMmDEDbdu2hZubG6pVq4bOnTtj9+7dZbbBwoUL4efnBycnJ3Tt2hUZGRkVthsArFy5Em3btoWTkxNq1qyJQYMG4cqVK3plzp07h5dffhleXl5wdHSEj48PBg0ahNu3b1dqHUREcuMlUkSkCLdv38bff/9dYrpGo6lw3piYGMTFxWHs2LFo3749cnNzcfjwYRw5cgQ9e/bEv/71L/z5559ISUnB999/rzevEAIvvvgidu/ejTFjxuDpp59GUlISpkyZgqtXr2LhwoW6siNHjsS6deswbNgwPPPMM9izZw8iIiLKjKt///5o3Lgx5syZo0tWUlJScOHCBYwaNQpeXl44efIkvvzyS5w8eRIHDhwocePwwIED0axZM8ydOxc//fQTPvjgA9SsWRPLly/Hc889h3nz5mHVqlV466230K5dO3Tp0gUA8NVXX2HixIn4xz/+gUmTJuH+/fs4fvw4Dh48iH/+859lxnzv3j1069YN58+fx4QJE+Dv74/169dj5MiRyMnJwaRJk9CsWTN8//33eOONN+Dj44M333wTAODh4VHqMv39/dGhQwesW7cOCxcuhK2tre694qSjOKaEhARUr14d0dHRqF69Onbt2oUZM2YgNzcXH330kd5yb9y4gd69e2PQoEEYOnQoPD09S11/bm4uvv76awwePBjjxo3DnTt38M033yA8PBy//vornn76ab3y3333He7cuYOoqCjcv38fn376KZ577jmcOHGizHUAwIcffoj3338fAwYMwNixY/HXX3/h888/R5cuXXD06FG4u7vjwYMHCA8PR0FBAV5//XV4eXnh6tWr2LJlC3JycuDm5lbm8omIrIYgIrJi8fHxAkC5fy1atNCbx8/PT4wYMUL3ulWrViIiIqLc9URFRYnSusTExEQBQHzwwQd60//xj38IlUolzp8/L4QQIj09XQAQkydP1is3cuRIAUDMnDlTN23mzJkCgBg8eHCJ9eXn55eY9sMPPwgAYu/evSWWMX78eN20wsJC4ePjI1QqlZg7d65u+q1bt4STk5Nem/Tt27dEu1XGokWLBACxcuVK3bQHDx6I0NBQUb16dZGbm6ub7ufnV2G7F1uyZIkAIJKSknTTioqKRL169URoaKhuWmnt869//Us4OzuL+/fv66Z17dpVABDLli0rUb5r166ia9euuteFhYWioKBAr8ytW7eEp6enGD16tG7axYsXBQDh5OQk/vjjD930gwcPCgDijTfe0E0r3j7FLl26JGxtbcWHH36ot54TJ04IOzs73fSjR48KAGL9+vUlG4mISCF4iRQRKcKSJUuQkpJS4i8oKKjCed3d3XHy5EmcO3fO4PVu3boVtra2mDhxot70N998E0IIbNu2DQCwfft2AMBrr72mV+71118vc9mvvPJKiWlOTk66/9+/fx9///03nnnmGQAPR1x63NixY3X/t7W1RXBwMIQQGDNmjG66u7s7AgICcOHCBb1pf/zxBw4dOlRmfKXZunUrvLy8MHjwYN00e3t7TJw4EXl5edizZ49Byys2cOBA2Nvb610mtWfPHly9elV3eRSg3z537tzB33//jc6dOyM/Px+//fab3jLVajVGjRpV4bptbW1192RotVrcvHkThYWFCA4OLrXNIyMjUa9ePd3r9u3bIyQkBFu3bi1zHT/++CO0Wi0GDBiAv//+W/fn5eWFxo0b6y7HKj5DkZSUpLtnhIhIaZhgEJEitG/fHj169CjxV6NGjQrnnTVrFnJyctCkSRO0bNkSU6ZMwfHjxyu13suXL8Pb2xsuLi5605s1a6Z7v/hfGxsb+Pv765Vr1KhRmct+vCwA3Lx5E5MmTYKnpyecnJzg4eGhK1faNfj169fXe+3m5gZHR0fUrl27xPRbt27pXr/zzjuoXr062rdvj8aNGyMqKgq//PJLmbEWu3z5Mho3blxilK7H28NQtWrVQnh4ODZu3Ij79+8DeHh5lJ2dHQYMGKArd/LkSbz00ktwc3ODq6srPDw8dKNLPd4+9erVq/TN3N9++y2CgoJ09+h4eHjgp59+KrXNGzduXGJakyZNyrzHBHh4X4UQAo0bN4aHh4fe3+nTp3H9+nUADz8T0dHR+Prrr1G7dm2Eh4djyZIlvP+CiBSFCQYRVXldunTB77//jhUrViAwMBBff/012rRpg6+//lrWuB79Nb7YgAED8NVXX+GVV17Bjz/+iOTkZN3ZEa1WW6L8o/crlDcNgN5N6c2aNcOZM2ewZs0adOrUCf/+97/RqVOncoeUNbehQ4ciNzcXW7ZswYMHD/Dvf/8bYWFhuns3cnJy0LVrV/z3v//FrFmz8J///AcpKSmYN28egJLtU1r7lmblypUYOXIkGjZsiG+++Qbbt29HSkoKnnvuuVLb3BharRYqlUq37Mf/li9friv78ccf4/jx45g+fbpu4IAWLVrgjz/+kCQWIiJz403eRPREqFmzJkaNGoVRo0YhLy8PXbp0QUxMjO4So7Keuuzn54cdO3bgzp07emcxii/H8fPz0/2r1Wpx8eJFvV+4z58/X+kYb926hZ07dyI2NhYzZszQTTfm0q7KqFatGgYOHIiBAwfiwYMH6NevHz788ENMmzYNjo6Opc7j5+eH48ePQ6vV6p3FeLw9jPHiiy/CxcUFq1evhr29PW7duqV3eVRqaipu3LiBH3/8UXezOgBcvHjR6HUCwIYNG/DUU0/hxx9/1PsclJVslbY9zp49iwYNGpS5joYNG0IIAX9/fzRp0qTCmFq2bImWLVvivffew/79+9GxY0csW7aszGeFEBFZE57BIKIq7/EhXqtXr45GjRqhoKBAN61atWoAHv5K/qjnn38eRUVFWLx4sd70hQsXQqVSoXfv3gCA8PBwAMAXX3yhV+7zzz+vdJzFZx7EY8PfLlq0qNLLqKzH28TBwQHNmzeHEKLckbmef/55ZGdnY+3atbpphYWF+Pzzz1G9enV07drV6JicnJzw0ksvYevWrVi6dCmqVauGvn376t4vrX0ePHhQos0NVdpyDx48iLS0tFLLJyYm4urVq7rXv/76Kw4ePKj7LJSmX79+sLW1RWxsbIntK4TQbY/c3FwUFhbqvd+yZUvY2NjofV6JiKwZz2AQUZXXvHlzdOvWDW3btkXNmjVx+PBhbNiwARMmTNCVadu2LQBg4sSJCA8Ph62tLQYNGoQ+ffrg2WefxbvvvotLly6hVatWSE5OxqZNmzB58mQ0bNhQN//LL7+MRYsW4caNG7phas+ePQug7DMkj3J1dUWXLl0wf/58aDQa1KtXD8nJySb/Ql+asLAweHl5oWPHjvD09MTp06exePFiRERElLjf5FHjx4/H8uXLMXLkSKSnp6NBgwbYsGEDfvnlFyxatKjceStj6NCh+O6775CUlIQhQ4boEj8A6NChA2rUqIERI0Zg4sSJUKlU+P7770scsBvqhRdewI8//oiXXnoJERERuHjxIpYtW4bmzZsjLy+vRPlGjRqhU6dOePXVV1FQUIBFixahVq1aePvtt8tcR8OGDfHBBx9g2rRpuHTpEiIjI+Hi4oKLFy9i48aNGD9+PN566y3s2rULEyZMQP/+/dGkSRMUFhbi+++/h62tLV5++WWT6klEZClMMIioyps4cSI2b96M5ORkFBQUwM/PDx988AGmTJmiK9OvXz+8/vrrWLNmDVauXAkhBAYNGgQbGxts3rwZM2bMwNq1axEfH48GDRrgo48+0j3fodh3330HLy8v/PDDD9i4cSN69OiBtWvXIiAgoMxLjh63evVqvP7661iyZAmEEAgLC8O2bdvg7e0taZv861//wqpVq/DJJ58gLy8PPj4+mDhxIt57771y53NyckJqaiqmTp2Kb7/9Frm5uQgICEB8fLzeww2N9dxzz6Fu3brIysrSuzwKeHgj+JYtW/Dmm2/ivffeQ40aNTB06FB0795ddwbJGCNHjkR2djaWL1+OpKQkNG/eHCtXrsT69euRmppaovzw4cNhY2ODRYsW4fr162jfvj0WL16MunXrlrueqVOnokmTJli4cCFiY2MBAL6+vggLC8OLL74IAGjVqhXCw8Pxn//8B1evXoWzszNatWqFbdu26UYTIyKydiph6k8/RERUpmPHjqF169ZYuXJliQNmIiKiqoj3YBARSeTevXslpi1atAg2NjZ6NyUTERFVZbxEiohIIvPnz0d6ejqeffZZ2NnZYdu2bdi2bRvGjx8PX19fucMjIiKyCF4iRUQkkZSUFMTGxuLUqVPIy8tD/fr1MWzYMLz77ruws+PvOURE9GRggkFERERERJLhPRhERERERCQZJhhERERERCQZJhhERERERCQZJhhERERERCQZJhhERERERCQZJhhERERERCQZJhhERERERCQZJhhERERERCQZJhhERERERCQZJhhERERERCQZJhhERERERCQZJhhERERERCQZJhhERERERCQZJhhERCSrhIQEqFQqXLp0Se5QiMgEMTExUKlUutcNGjTAyJEj5QuIZMMEg4iIiIiIJGMndwBEREREVPWcOXMGNjb8LftJxASDiIiIiCSnVqvlDoFkwrSSdIqvnTx79iyGDh0KNzc3eHh44P3334cQAleuXEHfvn3h6uoKLy8vfPzxx7p5U1NToVKpsHbtWkyfPh1eXl6oVq0aXnzxRVy5cqXEupYsWYKn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x2obgdnBWrx/2AYFWvPdCJ8RE26W5Spl31QWpccPKK8Oph4nmD3BqFu3Lpo3b643rVmzZvj3v/9d5jxqtRpqtbrEdHt7+1I3SlnTrUlBkfWO1lPcdkpoR6Ww9ra05tiI6MliieOER5myPy7Qqsy6Pzd332zt+6aKKD1+QDl1MDVGsz+us2PHjjhz5ozetLNnz8LPz8/cqyYiIiIrx+MEoqrH7AnGG2+8gQMHDmDOnDk4f/48Vq9ejS+//BJRUVHmXjURERFZOR4nEFU9Zk8w2rVrh40bN+KHH35AYGAgZs+ejUWLFmHIkCHmXjURERFZOR4nEFU9FnmS9wsvvIAXXnjBEqsiIiIiheFxAlHVYvYzGERERERE9ORggkFERERERJJhgkFERERERJJhgkFERERERJJhgkFERERERJJhgkFERERERJJhgkFERERERJJhgkFERERERJJhgkFERERERJJhgkFERERERJJhgkFERERERJJhgkFERERERJJhgkFERERERJJhgkFERERERJKxkzsAkl9gTBLmt3/4b0GRyqRlXZobIVFUREREVBU1mPqTQeXVtqLM4xQed1gnnsEgIiIiIiLJMMEgIiIiIiLJMMEgIiIiIiLJMMEgIiIiIiLJ8CZvIqrypBjAAODNhERERJXBMxhERERERCQZJhhERERERCQZJhhERERERCQZJhhEJKm9e/eiT58+8Pb2hkqlQmJiot77QgjMmDEDdevWhZOTE3r06IFz587JEywRERFJjgkGEUnq7t27aNWqFZYsWVLq+/Pnz8dnn32GZcuW4eDBg6hWrRrCw8Nx//59C0dKRERE5sBRpIhIUr1790bv3r1LfU8IgUWLFuG9995D3759AQDfffcdPD09kZiYiEGDBlkyVCIiIjIDnsEgIou5ePEisrOz0aNHD900Nzc3hISEIC0tTcbIiIiISCo8g0FEFpOdnQ0A8PT01Jvu6empe680BQUFKCgo0L3Ozc0FAGg0Gmg0mjLnK35PbSOMjrm05VlK8fosvV6pKD1+QL46qG2l+cwWf/Yril/J24iIrA8TDCKyenFxcYiNjS0xPTk5Gc7OzhXOPztYK0kcW7dulWQ5hkpJSZFlvVJRevyA5eswv720y6so/vz8fGlXSERPNIsnGHPnzsW0adMwadIkLFq0yNKrJyIZeXl5AQCuXbuGunXr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BQB069ZNb3p8fDxGjhwJAMjMzISNzf9OjNy6dQvjxo1DdnY2atSogbZt22L//v1o3ry5aZETkSL17t1b9/+goCCEhITAz88P69atw5gxY0qdx9SBH6QauECugQLkGAhASkqPH7B8HUwdZORxFcUv58APxYrPbDo6OiI0NBRxcXGoX79+qWX5o4P8CaGxlF4Hpf/oAFTuhwdT62dQgiFExZVLTU3Ve71w4UIsXLjQoKCI6Mnh7u6OJk2alHtflrEDPxTfSC/VwAWWHihAzoEApKD0+AH56mDsICOPKx4Eo6L45R74wdAzm/zRgUm73JT+owNQfh1M/dGBQ7cQkazy8vLw+++/Y9iwYWWWMXXgB6kGLpDrIFmOgQCkpPT4AeUNMvK4iuKXe/sYemaTPzowaZeL0n90ACr3w4OpPzpUqQSjwdSf5A6BiCrw1ltvoU+fPvDz88Off/6JmTNnwtbWFoMHD5Y7NCKyEhWd2eSPDkza5ab0Hx2A8utgat0MGkWKiMhUf/zxBwYPHoyAgAAMGDAAtWrVwoEDB+Dh4SF3aERkJYrPbHLESSJlqlJnMIjI+q1Zs0buEIjIyvDMJlHVwgSDiIiIZFV8ZvPGjRvw8PBAp06deGaTSMGYYBAREZGseGaTqGrhPRhERERERCQZJhhERERERCQZJhhERERERCQZJhhERERERCQZJhhERERERCQZJhhERERERCQZJhhERERERCQZJhhERERERCQZJhhERERERCQZJhhERERERCQZO7kDICKgwdSfJFvWpbkRki2LiIiIyFA8g0FERERERJJhgkFERERERJJhgkFERERERJJhgkFERERERJJhgkFERERERJJhgkFERERERJJhgkFERERERJJhgkFERERERJJhgkFERERERJJhgkFERERERJIxKsFYsmQJGjRoAEdHR4SEhODXX38tt/z69evRtGlTODo6omXLlti6datRwRJR1WFoP0JEVRv7BKKqw+AEY+3atYiOjsbMmTNx5MgRtGrVCuHh4bh+/Xqp5ffv34/BgwdjzJgxOHr0KCIjIxEZGYmMjAyTgyciZTK0HyGiqo19AlHVYnCC8cknn2DcuHEYNWoUmjdvjmXLlsHZ2RkrVqwotfynn36KXr16YcqUKWjWrBlmz56NNm3aYPHixSYHT0TKZGg/QkRVG/sEoqrFzpDCDx48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MjLSjBkzxhQVFfkt5kp1xV7T+ZBkCgsL/Rp7oFm8eLG59NJLTbNmzcy1115rtm3b5l43dOhQj+eeMT9/3W/37t1Ns2bNTK9evczbb7/tsd7lcpn09HQTExNj7Ha7GTFihPniiy8CJodOnTpV+7iYP3++McaY8vJyk5SUZKKjo014eLjp1KmTufPOO736wrAh8U+fPt3dNyYmxtx4441m165dHvvz9Tlo6GPo888/N5JMbm5ulX354/jXNX+npqaaoUOHVtnmyiuvNM2aNTOdO3f2mDcr1XZcAhlzAnOCr3MwJrDmhUCdE2zGGFP/6x0AAAAAUDPuwQAAAABgGQoMAAAAAJahwAAAAABgGQoMAAAAAJahwAAAAABgGQoMAAAAAJahwAAAAABgGQoMAAAAAJahwAAAAABgGQoMAAAAAJahwAAAAABgGQoMAAAAAJahwAAAAABgGQoMAAAAAJahwAAAAABgGQoMAAAAAJahwAAAAABgGQoMAIBfLV26VDabTTt27PB3KAAAC1BgAAAAALAMBQYAAAAAy1BgICCVlZX5OwQAXvbDDz/I5XL5OwwAgMUoMOBh06ZNGjBggCIiItSlSxctWbJEGRkZstlsHv1efPFF9e/fX82bN1fbtm01fvx4HTlyxKPPf//7X91666269NJLZbfbFR8frz/+8Y/6/vvvPfpNnjxZLVq00IEDB3TjjTeqZcuWmjhxotdzBVA/x44d05QpUxQXFye73a6EhARNmzZNP/74o7799lvNnDlTffr0UYsWLRQVFaXk5GR9/PHHHvvYtGmTbDabVqxYoblz56pjx46KjIxUaWmpu095ebnuuusutWvXTlFRUZo0aZK+++47X6cLAGikMH8HgMCxe/du3XDDDerQoYMyMzNVUVGhrKwsRUdHe/R74IEHlJ6ernHjxukPf/iDvv76ay1evFi/+tWvtHv3brVu3VqStGrVKpWXl2vatGlq166dPvroIy1evFhHjx7VqlWrPPb5008/afTo0RoyZIgeeeQRRUZG+iptALU4fvy4rr32Wp06dUpTp05Vz549dezYMb366qsqLy/XV199pTVr1ujWW29VQkKCSkpKtGTJEg0dOlSffvqp4uLiPPa3YMECNWvWTDNnzpTD4VCzZs3c6+699161bt1aGRkZ+uKLL5STk6NDhw65ixMAQJAwwP+5+eab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vws9N65cALCK12/ybtasmfr376+NGze621wulzZu3KiBAwd6e3gAAYY5AQCAps0nH5GaMWOGUlNTNWDAAF177bV67LHHVFZWpttvv90XwwMIMMwJAAA0XT4pMH73u9/p66+/1rx581RcXKwrr7xS69evr3KTZ2PY7XbNnz+/yscogkmw5xDs8UvBn0OwxO+LOcHXguXYn6+mnB+5AYC1bIbvpAMAAABgEZ/80B4AAACACwMFBgAAAADLUGAAAAAAsAwFBgAAAADLBEWBkZOTo759+7p/KGjgwIFat25drdusWrVKPXv2VEREhPr06aO1a9f6KNrqNTSHpUuXymazefxFRET4MOLaPfTQQ7LZbJo+fXqt/QLtPFSqT/yBdg4yMjKqxNOzZ89atwnU49+UZGdn65prrlHLli118cUXKyUlRV988YW/w/KK+j7vg8mxY8f0+9//Xu3atVPz5s3Vp08f7dixw99hNVpFRYXS09OVkJCg5s2bq0uXLlqwYIH4XhcAvhAUBcYll1yihx56SDt37tSOHTt0/fXX65ZbbtG+ffuq7f/BBx9owoQJmjJlinbv3q2UlBSlpKQoPz/fx5H/T0NzkH7+5dWioiL336FDh3wYcc22b9+uJUuWqG/fvrX2C8TzINU/finwzkGvXr084nnvvfdq7Buox7+p2bx5s+655x5t27ZNeXl5cjqdSkpKUllZmb9Ds1RDnjfB4rvvvtPgwYMVHh6udevW6dNPP9Wjjz6qNm3a+Du0Rnv44YeVk5OjJ598Up999pkefvhhLVq0SIsXL/Z3aAAuBCZItWnTxvzzn/+sdt24cePMTTfd5NGWmJho7rrrLl+EVm+15fD888+bVq1a+Tagejhz5ozp1q2bycvLM0OHDjX3339/jX0D8Tw0JP5AOwfz5883/fr1q3f/QDz+F4ITJ04YSWbz5s3+DsUyDXneBJO//OUvZsiQIf4Owytuuukmc8cdd3i0jR071kycONFPEQG4kATFFYxfqqio0IoVK1RWVqaBAwdW22fr1q0aOXKkR9vo0aO1detWX4RYp/rkIElnz55Vp06dFB8fX+fVDl+55557dNNNN1U5vtUJxPPQkPilwDsHBQUFiouLU+fOnTVx4kQdPny4xr6BePwvBKdPn5YktW3b1s+RWKehz5tg8cYbb2jAgAG69dZbdfHFF+uqq67Ss88+6++wLDFo0CBt3LhRX375pSTp448/1nvvvafk5GQ/RwbgQuCTX/K2wt69ezVw4ED98MMPatGihVavXq0rrrii2r7FxcVVfhE4JiZGxcXFvgi1Rg3JoUePHnruuefUt29fnT59Wo888ogGDRqkffv26ZJLLvFx5D9bsWKFdu3ape3bt9erf6Cdh4bGH2jnIDExUUuXLlWPHj1UVFSkzMxMXXfddcrPz1fLli2r9A+0438hcLlcmj59ugYPHqzevXv7OxxLNPR5E0y++uor5eTkaMaMGZozZ462b9+u++67T82aNVNqaqq/w2uUtLQ0lZaWqmfPngoNDVVFRYUeeOABTZw40d+hAbgABE2B0aNHD+3Zs0enT5/Wq6++qtTUVG3evLnGF+iBqCE5DBw40OPqxqBBg3T55ZdryZIlWrBggS/DliQdOXJE999/v/Ly8gLqZvP6Op/4A+0c/PKdx759+yoxMVGdOnXSypUrNWXKFJ/Hg6ruuece5efn13pvTDAJ9ud9XVwulwYMGKAHH3xQknTVVVcpPz9fzzzzTNAXGCtXrtTy5cv10ksvqVevXtqzZ4+mT5+uuLi4oM8NQOALmgKjWbNm6tq1qySpf//+2r59ux5//HEtWbKkSt/Y2FiVlJR4tJWUlCg2NtYnsdakITmcKzw8XFdddZX279/v7TCrtXPnTp04cUJXX321u62iokJbtmzRk08+KYfDodDQUI9tAuk8nE/85/L3OThX69at1b179xrjCaTjfyG499579dZbb2nLli1+u8poNSueN4GsQ4cOVd7gufzyy/Xaa6/5KSLr/PnPf1ZaWprGjx8vSerTp48OHTqk7OxsCgwAXhd092BUcrlccjgc1a4bOHCgNm7c6NGWl5dX6/0O/lBbDueqqKjQ3r171aFDBy9HVb0RI0Zo79692rNnj/tvwIABmjhxovbs2VPti4xAOg/nE/+5/H0OznX27FkdOHCgxngC6fg3ZcYY3XvvvVq9erX+85//KCEhwd8hWcaK500gGzx4cJWvFP7yyy/VqVMnP0VknfLycoWEeP4THxoaKpfL5aeIAFxQ/H2XeX2kpaWZzZs3m8LCQvPJJ5+YtLQ0Y7PZTG5urjHGmNtuu82kpaW5+7///vsmLCzMPPLII+azzz4z8+fPN+Hh4Wbv3r3+SqHBOWRmZpoNGzaYAwcOmJ07d5rx48ebiIgIs2/fPn+lUMW53yYTDOfhl+qKP9DOwZ/+9CezadMmU1hYaN5//30zcuRI0759e3PixIlq4w/0499UTJs2zbRq1cps2rTJFBUVuf/Ky8v9HZpXNKVvkfroo49MWFiYeeCBB0xBQYFZvny5iYyMNC+++KK/Q2u01NRU07FjR/PWW2+ZwsJC8/rrr5v27dubWbNm+Ts0ABeAoPiI1IkTJzRp0iQVFRWpVatW6tu3rzZs2KBRo0ZJkg4fPuzxTs2gQYP00ksvae7cuZozZ466deumNWvW+PWmy4bm8N133+nOO+9UcXGx2rRpo/79++uDDz4I6HtOguE81CbQz8HRo0c1YcIEnTx5UtHR0RoyZIi2bdum6OjoauMPtuMfrHJyciRJw4YN82h//vnnNXnyZN8HhHq75pprtHr1as2ePVtZWVlKSEjQY4891iRuhF68eLHS09N1991368SJE4qLi9Ndd92lefPm+Ts0ABcAmzH8rCcAAAAAawTtPRgAAAAAAg8FBgAAAADLUGAAAAAAsAwFBgAAAADLUGAAAAAAsAwFBgAAAADLUGAAAAAAsAwFBgAAAADLUGAAAAAAsAwFBgAAAADLUGAAAAAAsAwFBgAAAADL/H+A1C0S+18r1QAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, axs = plt.subplots(4, 3, figsize=(8, 8))\n", + "\n", + "for i, column in enumerate(df.columns[1:]):\n", + " ax = axs[i//3, i%3]\n", + " ax.hist(df[column])\n", + " ax.set_title(column)\n", + " ax.grid(True)\n", + "\n", + "fig.delaxes(axs[3, 2])\n", + "\n", + "\n", + "plt.suptitle('Histograms of Variables')\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "ce93de2e", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjsAAAHHCAYAAABZbpmkAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAASdRJREFUeJzt3XlclPX+///nAAKKMoSK4I5oKm5lpaKSLSauZVppx/VkZoqZ5rH0nDxmnTRbvp3qlO1qmmXlydJcPuS+UJjmirmFYQliEpuGKHP9/vDHHEdAWWaBy8f9dpvbzXlf77nm9WaYeHZd7+t9WQzDMAQAAGBSXp4uAAAAwJUIOwAAwNQIOwAAwNQIOwAAwNQIOwAAwNQIOwAAwNQIOwAAwNQIOwAAwNQIOwAAwNQIO0AlYrFY9Mwzz9ifz58/XxaLRceOHXPae7hinwDgSYQdwIWOHj2qMWPGqEmTJvL391dgYKC6dOmi1157TX/++aenyzO9Z555RhaLRb///nuR2xs3bqy+ffu6uaryu+2222SxWOyP4OBg3XLLLfrwww9ls9k8XR5Q4fh4ugDArL755hvdf//98vPz0/Dhw9W6dWvl5eVpy5YtmjJlivbv36933323XO8xbNgwDR48WH5+fk6qGpVF/fr1NXv2bEnSqVOn9NFHH2nUqFE6dOiQXnjhBQ9XB1QshB3ABZKSkjR48GA1atRI69atU1hYmH1bbGysjhw5om+++abc7+Pt7S1vb+9y78eVzpw5o4CAAE+X4XI2m015eXny9/d3y/tZrVYNHTrU/nzMmDFq3ry5/vOf/+i5555TlSpV3FJHWVwrvxOoODiNBbjAiy++qJycHH3wwQcOQadA06ZN9fjjj0uSunXrpnbt2hW5n+bNmysmJqbY9ylqfk3BqZktW7aoQ4cO8vf3V5MmTfTRRx8Vev3+/ft1xx13qGrVqqpfv77+9a9/FXsaZNWqVYqOjlZAQIBq1KihPn36aP/+/Q59Ro4cqerVq+vo0aPq3bu3atSooSFDhhS5vy+++EIWi0UbN24stO2dd96RxWLRvn37JEmpqan661//qvr168vPz09hYWG65557XDKv6MyZM5o8ebIaNGggPz8/NW/eXC+//LIMw3DoZ7FYNH78eH388cdq1aqV/Pz8tHr1aknSp59+qptuukk1atRQYGCg2rRpo9dee83h9RkZGZo4caL9fZo2bao5c+aU+TRUtWrV1KlTJ505c0anTp2SJP3888+6//77FRwcbN9+acg2DEO1atXSE088YW+z2WwKCgqSt7e3MjIy7O1z5syRj4+PcnJy7G0//fST7rvvPgUHB8vf318333yzvv76a4e6Cn5HN27cqHHjxikkJET169cv0xiBsuLIDuACy5cvV5MmTdS5c+er9h02bJhGjx6tffv2qXXr1vb27du369ChQ3r66adL/f5HjhzRfffdp1GjRmnEiBH68MMPNXLkSN10001q1aqVpIsB4vbbb9eFCxc0depUBQQE6N1331XVqlUL7W/hwoUaMWKEYmJiNGfOHJ09e1Zz585V165d9eOPP6px48b2vhcuXFBMTIy6du2ql19+WdWqVSuyxj59+qh69er67LPP1K1bN4dtS5YsUatWrew/j4EDB2r//v167LHH1LhxY6WlpSkuLk7JyckO712c9PT0ItsvDxaGYejuu+/W+vXrNWrUKN1www1as2aNpkyZot9++02vvvqqQ/9169bps88+0/jx41WrVi01btxYcXFxevDBB3XnnXdqzpw5kqQDBw5o69at9oB79uxZdevWTb/99pvGjBmjhg0batu2bZo2bZpSUlL073//+6pjKsrPP/8sb29vBQUF6eTJk+rcubPOnj2rCRMmqGbNmlqwYIHuvvtuffHFF7r33ntlsVjUpUsXbdq0yb6PPXv2KDMzU15eXtq6dav69OkjSdq8ebNuvPFGVa9eXdLFoNylSxfVq1fP/vvz2WefqX///lq6dKnuvfdeh9rGjRun2rVr65///KfOnDlTpvEBZWYAcKrMzExDknHPPfeUqH9GRobh7+9vPPXUUw7tEyZMMAICAoycnBx7myRjxowZ9ufz5s0zJBlJSUn2tkaNGhmSjE2bNtnb0tLSDD8/P2Py5Mn2tokTJxqSjO+//96hn9Vqddhndna2ERQUZIwePdqhvtTUVMNqtTq0jxgxwpBkTJ06tURjf/DBB42QkBDjwoUL9raUlBTDy8vLePbZZw3DMIw//vjDkGS89NJLJdrnpWbMmGFIuuKjT58+9v7Lli0zJBn/+te/HPZz3333GRaLxThy5Ii9TZLh5eVl7N+/36Hv448/bgQGBjqM6XLPPfecERAQYBw6dMihferUqYa3t7eRnJx8xXF169bN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Qmr4q0N4sLy93H/DbcIOAABwutX7UjRzeaJSMnPtbWFWf83oF6mercPcWgunsQAAgFOt3peisYt2OgQdSUrNzNXYRTu1el+KW+sh7AAAAKfJtxmauTxRRhHbCtpmLk9Uvq2oHq5B2AEAAE6TkJRe6IjOpQxJKZm5SkhKd1tNhB0AAOA0adnFB52y9HMGwg4AAHCakBr+Tu3nDIQdAADgNB3CgxVm9VdxF5hbdPGqrA7hwW6ribADAACcxtvLohn9IiWpUOApeD6jX6Rb19sh7AAAAKfq2TpMc4e2V6jV8VRVqNVfc4e2d/s6OywqCAAAnK5n6zDdFRnKCsoAAMC8vL0sioqo6ekyOI0FAADMjbADAABMjbADAABMjbADAABMjbADAABMjbADAABMjbADAABMjbADAABMjbADAABMjbADAABMjbADAABMjbADAABMjbADAABMjbADAABMjbADAABMjbADAABMjbADAABMjbADAABMjbADAABMjbADAABMjbADAABMjbADAABMjbADAABMjbADAABMjbADAABMjbADAABMjbADAABMjbADAABMjbADAABMjbADAABMzaNhZ+7cuWrbtq0CAwMVGBioqKgorVq1yr49NzdXsbGxqlmzpqpXr66BAwfq5MmTDvtITk5Wnz59VK1aNYWEhGjKlCm6cOGCu4cCAAAqKI+Gnfr16+uFF17Qjh079MMPP+iOO+7QPffco/3790uSJk2apOXLl+vzzz/Xxo0bdeLECQ0YMMD++vz8fPXp00d5eXnatm2bFixYoPnz5+uf//ynp4YEAAAqGIthGIani7hUcHCwXnrpJd13332qXbu2Fi9erPvuu0+S9NNPP6lly5aKj49Xp06dtGrVKvXt21cnTpxQnTp1JElvv/22nnrqKZ06dUq+vr4les+srCxZrVZlZmYqMDDQZWMDAADOU9K/3xVmzk5+fr4+/fRTnTlzRlFRUdqxY4fOnz+v7t272/u0aNFCDRs2VHx8vCQpPj5ebdq0sQcdSYqJiVFWVpb96FBRzp07p6ysLIcHAAAwJ4+Hnb1796p69ery8/PTo48+qi+//FKRkZFKTU2Vr6+vgoKCHPrXqVNHqampkqTU1FSHoFOwvWBbcWbPni2r1Wp/NGjQwLmDAgAAFYbHw07z5s21a9cuff/99xo7dqxGjBihxMREl77ntGnTlJmZaX8cP37cpe8HAAA8x8fTBfj6+qpp06aSpJtuuknbt2/Xa6+9pkGDBikvL08ZGRkOR3dOnjyp0NBQSVJoaKgSEhIc9ldwtVZBn6L4+fnJz8/PySMBAAAVkceP7FzOZrPp3Llzuummm1SlShWtXbvWvu3gwYNKTk5WVFSUJCkqKkp79+5VWlqavU9cXJwCAwMVGRnp9toBAEDF49EjO9OmTVOvXr3UsGFDZWdna/HixdqwYYPWrFkjq9WqUaNG6YknnlBwcLACAwP12GOPKSoqSp06dZIk9ejRQ5GRkRo2bJhefPFFpaam6umnn1ZsbCxHbgAAgCQPh520tDQNHz5cKSkpslqtatu2rdasWaO77rpLkvTqq6/Ky8tLAwcO1Llz5xQTE6O33nrL/npvb2+tWLFCY8eOVVRUlAICAjRixAg9++yznhoSAACoYCrcOjuewDo7AABUPpVunR0AAABXIOwAAABTI+wAAABTI+wAAABTI+wAAABTI+wAAABTI+wAAABTI+wAAABTI+wAAABTI+wAAABTI+wAAABTI+wAAABTI+wAAABTI+wAAABTI+wAAABTI+wAAABTI+wAAABTI+wAAABTI+wAAABTI+wAAABTI+wAAABTI+wAAABTI+wAAABTI+wAAABTI+wAAABTI+wAAABTI+wAAABTI+wAAABTI+wAAABTI+wAAABTI+wAAABT8/F0AQAAwJzybYYSktKVlp2rkBr+6hAeLG8vi9vrIOwAAACnW70vRTOXJyolM9feFmb114x+kerZOsyttXAaCwAAONXqfSkau2inQ9CRpNTMXI1dtFOr96W4tR7CDgAAcJp8m6GZyxNlFLGtoG3m8kTl24rq4RqEHQAA4DQJSemFjuhcypCUkpmrhKR0t9VE2AEAAE6Tll180ClLP2cg7AAAAKcJqeHv1H7OQNgBAABO0yE8WGFWfxV3gblFF6/K6hAe7LaaCDsAAMBpvL0smtEvUpIKBZ6C5zP6Rbp1vR3CDgAAcKqercM0d2h7hVodT1WFWv01d2h7t6+zw6KCAADA6Xq2DtNdkaGsoAwAAMzL28uiqIiani6D01gAAMDcCDsAAMDUCDsAAMDUCDsAAMDUCDsAAMDUCDsAAMDUPBp2Zs+erVtuuUU1atRQSEiI+vfvr4MHDzr0ue2222SxWBwejz76qEOf5ORk9enTR9WqVVNISIimTJmiCxcuuHMoAACggvLoOjsbN25UbGysbrnlFl24cEF///vf1aNHDyUmJiogIMDeb/To0Xr22Wftz6tVq2b/d35+vvr06aPQ0FBt27ZNKSkpGj58uKpUqaJZs2a5dTwAAKDisRiGYXi6iAKnTp1SSEiINm7cqFtvvVXSxSM7N9xwg/79738X+ZpVq1apb9++OnHihOrUqSNJevvtt/XUU0/p1KlT8vX1ver7ZmVlyWq1KjMzU4GBgU4bDwAAcJ2S/v2uUHN2MjMzJUnBwY53Qv34449Vq1YttW7dWtOmTdPZs2ft2+Lj49WmTRt70JGkmJgYZWVlaf/+/UW+z7lz55SVleXwAAAA5lRhbhdhs9k0ceJEdenSRa1bt7a3/+Uvf1GjRo1Ut25d7dmzR0899ZQOHjyo//73v5Kk1NRUh6Ajyf48NTW1yPeaPXu2Zs6c6aKRAACAiqTChJ3Y2Fjt27dPW7ZscWh/5JFH7P9u06aNwsLCdOedd+ro0aOKiIgo03tNmzZNTzzxhP15VlaWGjRoULbCAQBAkfIu2LQw/ph+ST+rRsHVNCyqsXx93H9SqUKEnfHjx2vFihXatGmT6tevf8W+HTt2lCQdOXJEERERCg0NVUJCgkOfkydPSpJCQ0OL3Iefn5/8/PycUDkAACjK7JWJem9zkmyXzAx+fuUBjY4O17TekW6txaNzdgzD0Pjx4/Xll19q3bp1Cg8Pv+prdu3aJUkKCwuTJEVFRWnv3r1KS0uz94mLi1NgYKAiI937wwQAABeDzjubHIOOJNkM6Z1NSZq9MtGt9Xg07MTGxmrRokVavHixatSoodTUVKWmpurPP/+UJB09elTPPfecduzYoWPHjunrr7/W8OHDdeutt6pt27aSpB49eigyMlLDhg3T7t27tWbNGj399NOKjY3l6A0AAG6Wd8Gm9zYnXbHPe5uTlHfB5qaKPBx25s6dq8zMTN12220KCwuzP5YsWSJJ8vX11bfffqsePXqoRYsWmjx5sgYOHKjly5fb9+Ht7a0VK1bI29tbUVFRGjp0qIYPH+6wLg8AAHCPhfHHCh3RuZzNuNjPXTw6Z+dqS/w0aNBAGzduvOp+GjVqpJUrVzqrLAAAUEa/pJ+9eqdS9HOGCrXODgAAqNwaBVe7eqdS9HMGwg4AAHCaYVGN5WW5ch8vy8V+7kLYAQAATuPr46XR0Ve+unp0dLhb19upEOvsAAAA8yhYR+fydXa8LPLIOjsV6kagnsKNQAEAcD5Xr6Bc0r/fHNkBAAAu4evjpVHRTTxdBnN2AACAuRF2AACAqRF2AACAqRF2AACAqRF2AACAqRF2AACAqXHpOQAAcIl8m6GEpHSlZecqpIa/OoQHy/tq95JwAcIOAABwutX7UjRzeaJSMnPtbWFWf83oF6mercPcWgunsQAAgFOt3peisYt2OgQdSUrNzNXYRTu1el+KW+sh7AAAAKfJtxmauTxRRd2LqqBt5vJE5dvcd7cqwg4AAHCahKT0Qkd0LmVISsnMVUJSuttqKlXYOX/+vB566CElJSW5qh4AAFCJpWUXH3TK0s8ZShV2qlSpoqVLl7qqFgAAUMmF1PB3aj9nKPVprP79+2vZsmUuKAUAAFR2HcKDFWb1V3EXmFt08aqsDuHBbqup1JeeN2vWTM8++6y2bt2qm266SQEBAQ7bJ0yY4LTiAABA5eLtZdGMfpEau2inLJLDROWCADSjX6Rb19uxGIZRqunQ4eHhxe/MYtHPP/9c7qLcLSsrS1arVZmZmQoMDPR0OQAAVHruWGenpH+/Sx12zIiwAwCA87l6BeWS/v0u8wrKeXl5SkpKUkREhHx8WIgZAAA48vayKCqipqfLKP0E5bNnz2rUqFGqVq2aWrVqpeTkZEnSY489phdeeMHpBQIAAJRHqcPOtGnTtHv3bm3YsEH+/v+7bKx79+5asmSJU4sDAAAor1Kff1q2bJmWLFmiTp06yWL533m3Vq1a6ejRo04tDgAAoLxKfWTn1KlTCgkJKdR+5swZh/ADAABQEZQ67Nx888365ptv7M8LAs7777+vqKgo51UGAADgBKU+jTVr1iz16tVLiYmJunDhgl577TUlJiZq27Zt2rhxoytqBAAAlZCrLz0vqVKHna5du2rXrl164YUX1KZNG/3f//2f2rdvr/j4eLVp08YVNQIAgErGHYsKlhSLCopFBQEAcKbV+1I0dtFOXR4wCo7pzB3a3imBp6R/v0s9Z2f48OGaN29epbwtBAAAcK18m6GZyxMLBR3pf/fJmrk8Ufk29x1rKXXY8fX11ezZs9W0aVM1aNBAQ4cO1fvvv6/Dhw+7oj4AAFCJJCSlO5y6upwhKSUzVwlJ6W6rqdRh5/3339ehQ4d0/Phxvfjii6pevbpeeeUVtWjRQvXr13dFjQAAoJJIyy4+6JSlnzOUOuwUuO6661SzZk1dd911CgoKko+Pj2rXru3M2gAAQCUTUsP/6p1K0c8ZSh12/v73v6tz586qWbOmpk6dqtzcXE2dOlWpqan68ccfXVEjAACoJDqEByvM6q/iLjC36OJVWR3Cg91WU6mvxvLy8lLt2rU1adIkDRgwQNdff72ranMbrsYCAMB5Cq7GkuQwUbnSXI31448/6h//+IcSEhLUpUsX1atXT3/5y1/07rvv6tChQ+UqGgAAVH49W4dp7tD2CrU6nqoKtfo7LeiURrnX2dm9e7deffVVffzxx7LZbMrPz3dWbW7DkR0AAJzP1Ssol/Tvd6lXUDYMQz/++KM2bNigDRs2aMuWLcrKylLbtm3VrVu3chUNAADMw9vLoqiImp4uo/RhJzg4WDk5OWrXrp26deum0aNHKzo6WkFBQS4oDwAAVFaV9t5YixYtUnR0NKd7AABAsUxzb6xff/1Vkir9YoLM2QEAwHkq/b2xbDabnn32WVmtVjVq1EiNGjVSUFCQnnvuOdlstnIVDQAAKreKeG+sUp/G+sc//qEPPvhAL7zwgrp06SJJ2rJli5555hnl5ubq+eefd3qRAACgcijNvbHcNXm51GFnwYIFev/993X33Xfb29q2bat69epp3LhxhB0AAK5hprg3Vnp6ulq0aFGovUWLFkpPd98dTAEAQMVjintjtWvXTv/5z38Ktf/nP/9Ru3btSrWv2bNn65ZbblGNGjUUEhKi/v376+DBgw59cnNzFRsbq5o1a6p69eoaOHCgTp486dAnOTlZffr0UbVq1RQSEqIpU6bowoULpR0aAAAop4p4b6xSn8Z68cUX1adPH3377beKioqSJMXHx+v48eNauXJlqfa1ceNGxcbG6pZbbtGFCxf097//XT169FBiYqICAgIkSZMmTdI333yjzz//XFarVePHj9eAAQO0detWSVJ+fr769Omj0NBQbdu2TSkpKRo+fLiqVKmiWbNmlXZ4AACgHLy9LJrRL1JjF+2URUXfG2tGv0i3rrdTpkvPT5w4obfeeksHDhyQJLVs2VLjxo1T3bp1y1XMqVOnFBISoo0bN+rWW29VZmamateurcWLF+u+++6TJP30009q2bKl4uPj1alTJ61atUp9+/bViRMnVKdOHUnS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XevQFhcXp6ioKHeVXWJlGd/l8vPztXfvXoWFhbmh4vLLycnR0aNHi623Mn1+Rbna+C5XGT6/Ll26OFy1IkmHDh1So0aNin1NZfocyzK+ouzatatCf46SNG/ePIWEhKhPnz5X7FeZPr9LlXR8l6vo38OzZ8/Ky8sxSnh7e8tmsxX7Go98hi45XnSNuvw0z7Bhw4ypU6fan2/dutXw8fExXn75ZePAgQPGjBkzjCpVqhh79+71QLWld7XxzZw501izZo1x9OhRY8eOHcbgwYMNf39/Y//+/R6o9uomT55sbNiwwUhKSjK2bt1qdO/e3ahVq5aRlpZmGEbl//xKO77K9vkZhmEkJCQYPj4+xvPPP28cPnzY+Pjjj41q1aoZixYtsveZOnWqMWzYMPvzn3/+2ahWrZoxZcoU48CBA8abb75peHt7G6tXr/bEEK6oLON79dVXjWXLlhmHDx829u7dazz++OOGl5eX8e2333piCCWSn59vNGzY0HjqqacKbavs30PDKN34Ktv3cMSIEUa9evWMFStWGElJScZ///tfo1atWsaTTz5p71MRvoOEHSe6PAx069bNGDFihEOfzz77zLj++usNX19fo1WrVsY333zj3iLL4WrjmzhxotGwYUPD19fXqFOnjtG7d29j586d7i+0hAYNGmSEhYUZvr6+Rr169YxBgwYZR44csW+v7J9facdX2T6/AsuXLzdat25t+Pn5GS1atDDeffddh+0jRowwunXr5tC2fv1644YbbjB8fX2NJk2aGPPmzXNfwaVU2vHNmTPHiIiIMPz9/Y3g4GDjtttuM9atW+fmqktnzZo1hiTj4MGDhbZV9u+hYZRufJXte5iVlWU8/vjjRsOGDQ1/f3+jSZMmxj/+8Q/j3Llz9j4V4TtoMYyrLHMIAABQiTFnBwAAmBphBwAAmBphBwAAmBphBwAAmBphBwAAmBphBwAAmBphBwAAmBphB0Clddttt2nixIn2540bN9a///3vcu1zw4YNslgsysjIKNd+AFQchB0AHpOamqrHHntMTZo0kZ+fnxo0aKB+/foVum9OSW3fvl2PPPKIk6sEUNn5eLoAANemY8eOqUuXLgoKCtJLL72kNm3a6Pz581qzZo1iY2P1008/lXqftWvXdkGlpZeXlydfX19PlwHg/8eRHQAeMW7cOFksFiUkJGjgwIG6/vrr1apVKz3xxBP67rvv9NBDD6lv374Orzl//rxCQkL0wQcfFLnPy09jWSwWvf/++7r33ntVrVo1NWvWTF9//bXDa1auXKnrr79eVatW1e23365jx44V2u+WLVsUHR2tqlWrqkGDBpowYYLOnDnj8L7PPfechg8frsDAQI4uARUMYQeA26Wnp2v16tWKjY1VQEBAoe1BQUF6+OGHtXr1aqWkpNjbV6xYobNnz2rQoEElfq+ZM2fqgQce0J49e9S7d28NGTJE6enpkqTjx49rwIAB6tevn3bt2qWHH35YU6dOdXj90aNH1bNnTw0cOFB79uzRkiVLtGXLFo0fP96h38svv6x27drpxx9/1PTp00vz4wDgYoQdAG535MgRGYahFi1aFNunc+fOat68uRYuXGhvmzdvnu6//35Vr169xO81cuRIPfjgg2ratKlmzZqlnJwcJSQkSJLmzp2riIgIvfLKK2revLmGDBmikSNHOrx+9uzZGjJkiCZOnKhmzZqpc+fOev311/XRRx8pNzfX3u+OO+7Q5MmTFRERoYiIiBLXB8D1CDsA3M4wjBL1e/jhhzVv3jxJ0smTJ7Vq1So99NBDpXqvtm3b2v8dEBCgwMBApaWlSZIOHDigjh07OvSPiopyeL57927Nnz9f1atXtz9iYmJks9mUlJRk73fzzTeXqi4A7sMEZQBu16xZM1kslqtOQh4+fLimTp2q+Ph4bdu2TeHh4YqOji7Ve1WpUsXhucVikc1mK/Hrc3JyNGbMGE2YMKHQtoYNG9r/XdTpOAAVA2EHgNsFBwcrJiZGb775piZMmFAoKGRkZCgoKEg1a9ZU//79NW/ePMXHx+uvf/2rU+to2bJloQnL3333ncPz9u3bKzExUU2bNnXqewNwH05jAfCIN998U/n5+erQoYOWLl2qw4cP68CBA3r99dcdTiU9/PDDWrBggQ4cOKARI0Y4tYZHH31Uhw8f1pQpU3Tw4EEtXrxY8+fPd+jz1FNPadu2bRo/frx27dqlw4cP66uvvio0QRlAxUXYAeARTZo00c6dO3X77bdr8uTJat26te666y6tXbtWc+fOtffr3r27wsLCFBMTo7p16zq1hoYNG2rp0qVatmyZ2rVrp7fffluzZs1y6NO2bVtt3LhRhw4dUnR0tG688Ub985//dHotAFzHYpR0piAAeEBOTo7q1aunefPmacCAAZ4uB0AlxJwdABWSzWbT77//rldeeUVBQUG6++67PV0SgEqKsAOgQkpOTlZ4eLjq16+v+fPny8eH/1wBKBtOYwEAAFNjgjIAADA1wg4AADA1wg4AADA1wg4AADA1wg4AADA1wg4AADA1wg4AADA1wg4AADA1wg4AADC1/w+99nWR45ctHwAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "\n", + "plt.scatter(df['cyl'], df['hp'])\n", + "plt.title('Cylinder vs Horse Power')\n", + "plt.xlabel('Cylinder')\n", + "plt.ylabel('Horse Power')\n", + "plt.show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "c9ac6ed4", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkIAAAHHCAYAAABTMjf2AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAARGRJREFUeJzt3XtUVPX+//HXgAIiMISKAylGaireSislr+UNVMxLFzNTy6+VYueoXalThqey7HRdpZ7ztVPnpJZ5UktLzcxLGmppWoaZcTAtQUy+Dl4Cldm/P/wxNQLKZYaZYT8fa81azd6f2fPee4bm5d6fz2dbDMMwBAAAYEIB3i4AAADAWwhCAADAtAhCAADAtAhCAADAtAhCAADAtAhCAADAtAhCAADAtAhCAADAtAhCAADAtAhCAADAtAhCgI956623ZLFYZLFYtGnTplLrDcNQ06ZNZbFYNHjwYJd1Ja+zWCwKCAhQbGys+vfvr/Xr15fajsPh0L///W/169dPDRs2VN26dRUdHa3+/fvrH//4h4qKijy1ixV22WWXldrHEuvXr5fFYtF//vOfGq6qevbv3+/yOQUGBiouLk7Dhg3Tzp07vV0eYDp1vF0AgLKFhIRo4cKF6t69u8vyDRs26Oeff1ZwcHCZr+vXr5/GjBkjwzCUnZ2t2bNn64YbbtBHH32k5ORkSdJvv/2mYcOGafXq1bruuuv0wAMPqHHjxsrPz9eGDRs0adIkbd26VW+88YbH99OsbrvtNg0cOFDFxcXas2eP5syZo5UrV2rLli268sorvV0eYBoEIcBHDRw4UIsXL9arr76qOnV+/1NduHChOnfurF9//bXM111xxRUaPXq08/mwYcPUoUMHvfzyy84gNHXqVK1evVovv/yy/vznP7u8/v7779e+ffu0Zs0aD+yV7zp58qTq169fY+/XqVMnl8+pW7duGjJkiObMmaO///3vNVZHVZw6dUqhoaHeLgNwCy6NAT7qtttu09GjR10CyenTp/Wf//xHo0aNqvB22rdvr4YNGyo7O1uSdPDgQc2bN09JSUmlQlCJli1batKkSRfc7uDBg3X55ZeXuS4xMVFXX3218/maNWvUvXt3RUZGKiwsTK1atdKjjz5a4X2ojK+//lrJycmKiIhQWFiY+vTpoy1btri0Kbn8WHL2Kzo6Wk2aNJEkHT9+XFOmTNFll12m4OBgRUdHq1+/ftqxY4fLNrZu3aqkpCRZrVaFhoaqV69e2rx5c5XrvuGGGyTJ+TlJ0uLFi9W5c2fVq1dPDRs21OjRo/XLL78413/44YeyWCz65ptvnMvef/99WSwWDR8+3GX7bdq00a233uqybP78+c7tR0VFaeTIkTp48KBLm969e6tdu3bavn27evbsqdDQUI99doA3EIQAH3XZZZcpMTFR77zzjnPZypUrZbfbNXLkyApv5//+7//0f//3f2rQoIFzG8XFxS5nI6ri1ltvVXZ2tr788kuX5T/99JO2bNnirPG7777T4MGDVVRUpBkzZuiFF17QkCFDKhwazpw5o19//bXUw263l2r73XffqUePHtq1a5ceeughPf7448rOzlbv3r21devWUu0nTZqkzMxMPfHEE3rkkUckSffee6/mzJmjESNGaPbs2XrggQdUr1497dmzx/m6zz77TD179lRBQYGmT5+uZ555RseOHdMNN9ygbdu2VfgY/lFWVpYkOT+nt956S7fccosCAwM1c+ZMTZgwQUuWLFH37t117NgxSVL37t1lsVi0ceNG53Y+//xzBQQEuPQvO3LkiL7//nv17NnTuezpp5/WmDFj1LJlS7344ouaMmWK1q5dq549ezq3X+Lo0aNKTk7WlVdeqZdfflnXX399lfYR8EkGAJ/y5ptvGpKML7/80njttdeM8PBw49SpU4ZhGMbNN99sXH/99YZhGEazZs2MQYMGubxWkjF+/HjjyJEjRl5enrF161ajT58+hiTjhRdeMAzDMKZOnWpIMnbu3Ony2qKiIuPIkSPOx6+//nrBOu12uxEcHGzcf//9LstnzZplWCwW46effjIMwzBeeuklQ5Jx5MiRSh+LZs2aGZIu+Fi8eLGz/dChQ42goCAjKyvLuezQoUNGeHi40bNnT+eykmPcvXt34+zZsy7vabVajdTU1HJrcjgcRsuWLY0BAwYYDofDufzUqVNGfHy80a9fvwvuU3Z2tiHJSE9PN44cOWLk5uYa69evN6666ipDkvH+++8bp0+fNqKjo4127doZv/32m/O1K1asMCQZTzzxhHNZ27ZtjVtuucX5vFOnTsbNN99sSDL27NljGIZhLFmyxJBk7Nq1yzAMw9i/f78RGBhoPP300y61ffvtt0adOnVclvfq1cuQZMydO/eC+wX4K84IAT7slltu0W+//aYVK1bo+PHjWrFixUUvi73xxhtq1KiRoqOj1aVLF23evFnTpk3TlClTJEkFBQWSpLCwMJfXffzxx2rUqJHz0axZswu+T0REhJKTk/Xee+/JMAzn8kWLFqlr166Ki4uTJEVGRkqSPvjgAzkcjsrsviSpS5cuWrNmTanH3/72N5d2xcXF+uSTTzR06FCXS3YxMTEaNWqUNm3a5Nz3EhMmTFBgYKDLssjISG3dulWHDh0qs56dO3dq3759GjVqlI4ePeo8Q3Xy5En16dNHGzdurNB+Tp8+XY0aNZLNZlPv3r2VlZWl5557TsOHD9dXX32lvLw8TZo0SSEhIc7XDBo0SK1bt9ZHH33kXNajRw99/vnnks5d1tu1a5fuvvtuNWzY0Ln8888/V2RkpNq1aydJWrJkiRwOh2655RaXs2w2m00tW7bUunXrXGoNDg7WnXfeedF9AvwRnaUBH9aoUSP17dtXCxcu1KlTp1RcXKybbrrpgq+58cYbNXnyZFksFoWHh6tt27YunYDDw8MlSSdOnHB5Xbdu3Zz9kZ5//vkKXbq69dZbtWzZMmVkZOi6665TVlaWtm/frpdfftmlzbx58/Q///M/euSRR9SnTx8NHz5cN910kwICLv5vsYYNG6pv376llv+xA7l07vLPqVOn1KpVq1Jt27RpI4fDoYMHD6pt27bO5fHx8aXazpo1S2PHjlXTpk3VuXNnDRw4UGPGjHGGq3379kmSxo4dW27Ndrtdl1xyyQX36+6779bNN9+sgIAARUZGqm3bts6RgD/99JMklbkvrVu3drns1aNHD82dO1c//vijsrKyZLFYlJiY6AxIEyZM0Oeff65u3bo5j/e+fftkGIZatmxZZm1169Z1eX7ppZcqKCjogvsD+CuCEODjRo0apQkTJig3N1fJycnOMyzladKkSZnBoUTr1q0lSbt371bHjh2dy0tCl3SuE21FpKSkKDQ0VO+9956uu+46vffeewoICNDNN9/sbFOvXj1t3LhR69at00cffaRVq1Zp0aJFuuGGG/TJJ5+UOiNTk+rVq1dq2S233KIePXpo6dKl+uSTT/T888/rueee05IlS5ScnOw82/P888+XO8z9/LNtZWnZsuUFP6eKKpleYePGjfrvf/+rTp06qX79+urRo4deffVVnThxQl9//bWefvpp52scDocsFotWrlxZ5vE/v/6yjhNQWxCEAB83bNgw3XPPPdqyZYsWLVpU7e0lJycrMDBQCxYs0O23316tbdWvX1+DBw/W4sWL9eKLL2rRokXq0aOHYmNjXdoFBASoT58+6tOnj1588UU988wzeuyxx7Ru3Tq3hAHpXJALDQ3V3r17S637/vvvFRAQoKZNm1ZoWzExMZo0aZImTZqkvLw8derUSU8//bSSk5PVvHlzSecuDbqr9vOVXJbcu3evczRZib1797pctoyLi1NcXJw+//xz/fe//1WPHj0kST179tS0adO0ePFiFRcXu3SUbt68uQzDUHx8vK644gqP7APgL+gjBPi4sLAwzZkzR08++aRSUlKqvb24uDjdddddWrlypV577bUy2/yxz8/F3HrrrTp06JDmzZunXbt2lRqinZ+fX+o1JWdS3Dl7dWBgoPr3768PPvhA+/fvdy4/fPiwc2LKiIiIC26juLi41Gi06OhoxcbGOmvt3Lmzmjdvrr/97W+lLi9K5y7RVdfVV1+t6OhozZ071+UYrVy5Unv27NGgQYNc2vfo0UOfffaZtm3b5gxCV155pcLDw/Xss8+qXr166ty5s7P98OHDFRgYqPT09FKftWEYOnr0aLX3AfAXnBEC/MCF+qNUxcsvv6zs7Gzdd999evfdd5WSkqLo6Gj9+uuv2rx5s5YvX15m/5SyDBw4UOHh4XrggQcUGBioESNGuKyfMWOGNm7cqEGDBqlZs2bKy8vT7Nmz1aRJk1KzZlfXU0895ZyzaNKkSapTp47+/ve/q6ioSLNmzbro648fP64mTZropptuUseOHRUWFqZPP/1UX375pV544QVJ585uzZs3T8nJyWrbtq3uvPNOXXrppfrll1+0bt06RUREaPny5dXaj7p16+q5557TnXfeqV69eum2227T4cOH9corr+iyyy7T1KlTXdr36NFDCxYskMVicR7TwMBAXXfddVq9erV69+7t0senefPmeuqpp5SWlqb9+/dr6NChCg8PV3Z2tpYuXaq7775bDzzwQLX2AfAXBCHAhEJDQ7Vq1Sq9/fbbevvttzVr1iwVFBQoMjJSHTt21OzZsyscvkJCQjRkyBAtWLBAffv2VXR0tMv6IUOGaP/+/frnP/+pX3/9VQ0bNlSvXr2Unp4uq9Xq1v1q27atPv/8c6WlpWnmzJlyOBzq0qWL5s+fry5dulz09aGhoZo0aZI++eQT58iqFi1aaPbs2Zo4caKzXe/evZWRkaG//vWveu2113TixAnZbDZ16dJF99xzj1v2Zdy4cQoNDdWzzz6rhx9+WPXr19ewYcP03HPPleonVnIWqHXr1s55iEqWr1692rn+jx555BFdccUVeumll5Seni5Jatq0qfr3768hQ4a4ZR8Af2AxKnMOHAAAoBahjxAAADAtghAAADAtghAAADAtghAAADAtghAAADAtghAAADAt5hHSufvuHDp0SOHh4bJYLN4uBwAAVIBhGDp+/LhiY2MrdBPnshCEJB06dKjC9yACAAC+5eDBg2rSpEmVXksQkhQeHi7p3IG82L2IAACAbygoKFDTpk2dv+NVQRCSnJfDIiIiCEIAAPiZ6nRrobM0AAAwLYIQAAAwLYIQAAAwLYIQAAAwLYIQAAAwLYIQAAAwLYIQAAAwLYIQAAAwLYIQAAAwLWaWhl8qdhjalp2vvOOFig4P0bXxUQoM4Ia5AIDKIQjB76zanaP05ZnKsRc6l8VYQzQ9JUFJ7WK8WBkAwN9waQx+ZdXuHE2cv8MlBElSrr1QE+fv0KrdOV6qDADgjwhC8BvFDkPpyzNllLGuZFn68kwVO8pqAQBAaQQh+I1t2fmlzgT9kSEpx16obdn5NVcUAMCvEYTgN/KOlx+CqtIOAACCEPxGdHiIW9sBAEAQgt+4Nj5KMdYQlTdI3qJzo8eujY+qybIAAH6MIAS/ERhg0fSUBEkqFYZKnk9PSWA+IQBAhRGE4FeS2sVozuhOslldL3/ZrCGaM7oT8wgBACqFCRXhd5Laxahfgo2ZpQEA1UYQgl8KDLAosXkDb5cBAPBzXBoDAACmRRACAACmRRACAACmRRACAACmRRACAACmRRACAACmRRACAACmRRACAACmRRACAACmRRACAACmRRACAACmRRACAACmRRACAACmRRACAACmRRACAACmRRACAACmRRACAACmRRACAACmRRACAACmRRACAACmRRACAACmRRACAACmRRACAACmRRACAACmRRACAACmRRACAACmRRACAACmRRACAACmRRACAACmRRACAACm5dUgNGfOHHXo0EERERGKiIhQYmKiVq5c6VxfWFio1NRUNWjQQGFhYRoxYoQOHz7sso0DBw5o0KBBCg0NVXR0tB588EGdPXu2pncFAAD4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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "\n", + "plt.scatter(df['mpg'], df['hp'])\n", + "plt.title('MPG vs Horse Power')\n", + "plt.xlabel('MPG')\n", + "plt.ylabel('Horse Power')\n", + "plt.show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "c1b3c30b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " mpg cyl disp hp drat wt qsec \\\n", + "mpg 1.000000 -0.852162 -0.847551 -0.776168 0.681172 -0.867659 0.418684 \n", + "cyl -0.852162 1.000000 0.902033 0.832447 -0.699938 0.782496 -0.591242 \n", + "disp -0.847551 0.902033 1.000000 0.790949 -0.710214 0.887980 -0.433698 \n", + "hp -0.776168 0.832447 0.790949 1.000000 -0.448759 0.658748 -0.708223 \n", + "drat 0.681172 -0.699938 -0.710214 -0.448759 1.000000 -0.712441 0.091205 \n", + "wt -0.867659 0.782496 0.887980 0.658748 -0.712441 1.000000 -0.174716 \n", + "qsec 0.418684 -0.591242 -0.433698 -0.708223 0.091205 -0.174716 1.000000 \n", + "vs 0.664039 -0.810812 -0.710416 -0.723097 0.440278 -0.554916 0.744535 \n", + "am 0.599832 -0.522607 -0.591227 -0.243204 0.712711 -0.692495 -0.229861 \n", + "gear 0.480285 -0.492687 -0.555569 -0.125704 0.699610 -0.583287 -0.212682 \n", + "carb -0.550925 0.526988 0.394977 0.749812 -0.090790 0.427606 -0.656249 \n", + "\n", + " vs am gear carb \n", + "mpg 0.664039 0.599832 0.480285 -0.550925 \n", + "cyl -0.810812 -0.522607 -0.492687 0.526988 \n", + "disp -0.710416 -0.591227 -0.555569 0.394977 \n", + "hp -0.723097 -0.243204 -0.125704 0.749812 \n", + "drat 0.440278 0.712711 0.699610 -0.090790 \n", + "wt -0.554916 -0.692495 -0.583287 0.427606 \n", + "qsec 0.744535 -0.229861 -0.212682 -0.656249 \n", + "vs 1.000000 0.168345 0.206023 -0.569607 \n", + "am 0.168345 1.000000 0.794059 0.057534 \n", + "gear 0.206023 0.794059 1.000000 0.274073 \n", + "carb -0.569607 0.057534 0.274073 1.000000 \n" + ] + } + ], + "source": [ + "# 각 변수들 간의 상관 관계를 알아 보기 위해 상관계수를 구하는 표를 출력하세요.\n", + "print(df.drop(columns=['name']).corr())\n" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "22172777", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjIAAAHHCAYAAACle7JuAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAPK1JREFUeJzt3Xl4VPXd///XJEIGJRkMECaRAGE3RKhQsQHLooStdyqIFbEoLrU3EbgES1vobRvTljsuVbQVotXegCK4AjZYQhEIFGSHFCItP+AOgjIBFZiEQCJmzvcPfpmbyQIzYbYzeT6ua/6Ycz5z8j5zWufF+SzHYhiGIQAAABOKCnUBAAAAjUWQAQAApkWQAQAApkWQAQAApkWQAQAApkWQAQAApkWQAQAApkWQAQAApkWQAQAApkWQAeCzBx98UJ06dWr0Z1u2bOnfggA0WQQZIEK8++67slgsWr58eZ19ffr0kcVi0fr16+vs69ChgwYMGBCMEn1y7tw5PfXUUyosLAx1KZKkhQsXymKxyGKxaNOmTXX2G4ah5ORkWSwW/cd//IfHvprPWSwWRUVFKSkpScOHD6/33Fwul9544w1lZGSoTZs2atasmRISEjR8+HD9+c9/VlVVVaBOETAlggwQIW677TZJqvMjW1ZWpuLiYl1zzTXavHmzx75jx47p2LFj7s9667XXXtOBAweuruArOHfunHJycsImyNSwWq1asmRJne0bNmzQ559/rpiYmHo/l5GRoTfffFOLFi3S5MmTtXfvXt1+++1atWqVu8358+c1evRoTZo0SefOndPMmTP15z//Wb/85S9ltVr12GOP6bHHHgvYuQFmdE2oCwDgH0lJSUpJSakTZLZs2SLDMPSjH/2ozr6a974GmWbNml1dsSY2evRovffee/rjH/+oa675v/+ELlmyRP369dNXX31V7+e6d++uiRMnut+PHTtWvXv31osvvqhRo0ZJkmbMmKHVq1frxRdf1OOPP+7x+Z/97Gc6ePCg1qxZE4CzAsyLOzJABLntttu0Z88enT9/3r1t8+bN6tWrl0aNGqWtW7fK5XJ57LNYLBo4cKB72+LFi9WvXz+1aNFC8fHxuvfee3Xs2DGPv1PfGJmvv/5a999/v+Li4tSqVStNmjRJ//znP2WxWLRw4cI6tX7xxRcaM2aMWrZsqbZt22rmzJmqrq6WJB05ckRt27aVJOXk5Li7ZZ566ql6z3vnzp2yWCxatGhRnX2rV6+WxWLRypUrJUnl5eWaPn26OnXqpJiYGCUkJCgjI0O7d+9u+Iu9xIQJE/T11197BIpvvvlG77//vu677z6vjiFJN910k9q0aaOSkhJJF++Ovf766xo5cmSdEFOjW7du3JEBaiHIABHktttu04ULF7Rt2zb3ts2bN2vAgAEaMGCAnE6niouLPfb17NlTrVu3liTNmTNHDzzwgLp166YXXnhB06dP19q1azVo0CCdOXOmwb/rcrmUmZmppUuXatKkSZozZ44cDocmTZpUb/vq6mqNGDFCrVu31h/+8AcNHjxYzz//vP785z9Lktq2bau8vDxJF+9cvPnmm3rzzTd111131Xu87373u+rcubPefffdOvveeecdXX/99RoxYoQkafLkycrLy9O4ceM0f/58zZw5Uy1atNC//vWvy3yz/6dTp05KT0/X0qVL3dtWrVolp9Ope++916tjSNLp06d1+vRp93e/atUqVVdXe9y1AeAFA0DE+PTTTw1Jxu9+9zvDMAzjwoULxnXXXWcsWrTIMAzDaNeunTFv3jzDMAyjrKzMiI6ONh599FHDMAzjyJEjRnR0tDFnzhyPY+7bt8+45pprPLZPmjTJ6Nixo/v9Bx98YEgyXnzxRfe26upq4/bbbzckGQsWLPD4rCTjt7/9rcffufnmm41+/fq533/55ZeGJCM7O9urc589e7bRrFkz49SpU+5tVVVVRqtWrYyHH37Yvc1msxlTpkzx6piXWrBggSHJ2LFjh/Hyyy8bsbGxxrlz5wzDMIwf/ehHxtChQw3DMIyOHTsaP/jBDzw+K8l45JFHjC+//NI4efKksW3bNuOOO+4wJBnPP/+8YRiGMWPGDEOSUVRU5PHZqqoq48svv3S/vvrqK59rByIZd2SACHLjjTeqdevW7rEv//znP1VRUeGelTRgwAD3gN8tW7aourraPT5m2bJlcrlcuueee/TVV1+5X3a7Xd26dat3xlONgoICNWvWTI8++qh7W1RUlKZMmdLgZyZPnuzx/vvf/77+93//t3EnLmn8+PG6cOGCli1b5t7297//XWfOnNH48ePd21q1aqVt27bp+PHjjf5b99xzj86fP6+VK1eqvLxcK1euvGK30l/+8he1bdtWCQkJuvXWW7V582Y98cQTmj59uqSLg7Il1Zma/re//U1t27Z1vzp27NjouoFIxGBfIIJYLBYNGDBAGzdulMvl0ubNm5WQkKCuXbtKuhhkXn75ZUlyB5qaIHPw4EEZhqFu3brVe+zLDfD97LPPlJiYqGuvvdZje83frc1qtbrHwNS4/vrrdfr0aS/Osn59+vRRz5499c477+iRRx6RdLFbqU2bNrr99tvd7Z599llNmjRJycnJ6tevn0aPHq0HHnhAnTt39vpvtW3bVsOGDdOSJUt07tw5VVdX6+67777sZ+68805NnTpVFotFsbGx6tWrl6677jr3/tjYWEnS2bNnPT43cOBA93ic5557rs7MM6CpI8gAEea2225Tfn6+9u3b5x4fU2PAgAH6+c9/ri+++EKbNm1SUlKS+wfc5XLJYrFo1apVio6OrnNcfy5iV9/x/WH8+PGaM2eOvvrqK8XGxuqvf/2rJkyY4DG76J577tH3v/99LV++XH//+9/13HPP6ZlnntGyZcvcs4e8cd999+nRRx9VaWmpRo0apVatWl22ffv27TVs2LAG9/fs2VOSVFxcrD59+ri314Qm6eJAbACe6FoCIsyl68ls3rzZY0ZSv379FBMTo8LCQm3bts1jX5cuXWQYhlJSUjRs2LA6r+9973sN/s2OHTvK4XDo3LlzHtsPHTrU6POwWCw+f2b8+PH69ttv9cEHH2jVqlUqKyurdwBuYmKiHnvsMa1YsUIlJSVq3bq15syZ49PfGjt2rKKiorR161afZis1ZNSoUYqOjtZbb7111ccCmhKCDBBhvvvd78pqteqtt97SF1984XFHJiYmRn379tW8efNUUVHhsX7MXXfdpejoaOXk5MgwDI9jGoahr7/+usG/OWLECF24cEGvvfaae5vL5dK8efMafR413VSXmy1V24033qibbrpJ77zzjt555x0lJiZq0KBB7v3V1dVyOp0en0lISFBSUpLPK+a2bNlSeXl5euqpp5SZmenTZ+vToUMHPfzww1q1apW7+6+22tcFAF1LQMRp3ry5brnlFv3jH/9QTEyM+vXr57F/wIABev755yV5LoTXpUsX/f73v9fs2bN15MgRjRkzRrGxsSopKdHy5cv105/+VDNnzqz3b44ZM0b9+/fXz372Mx06dEg9e/bUX//6V506dUpS4+6utGjRQqmpqXrnnXfUvXt3xcfHKy0tTWlp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cvL3y+GsdG9LtFmVnZmqkWmJV90eAAB4j64lHxQUO5S1eLdHKJGkUmelshbvVkGx46raAwAA3xBkvFTtMpSTv19GPftqtuXk71e1y2hUewAA4DuCjJe2l5yqc2flUoYkh7NS20tONao9AADwHUHGSyfLGw4l9bXztT0AAPBdSINMXl6eevfurbi4OMXFxSk9PV2rVq1y76+srNSUKVPUunVrtWzZUuPGjdOJEydCUmtCrNWndr62BwAAvgtpkGnfvr2efvpp7dq1Szt37tTtt9+uO++8U59++qkkacaMGcrPz9d7772nDRs26Pjx47rrrrtCUmv/lHgl2qxqaNK0RRdnI/VPiW9UewAA4DuLYRhhNdo0Pj5ezz33nO6++261bdtWS5Ys0d133y1J+ve//60bb7xRW7Zs0fe+9z2vjldWViabzSan06m4uLirqq1mFpIkj0G8NWElb2JfjynVvrYHAAAXefv7HTZjZKqrq/X222+roqJC6enp2rVrly5cuKBhw4a52/Ts2VMdOnTQli1bQlLjyLRE5U3sK7vNszvIbrPWG0p8bQ8AAHwT8gXx9u3bp/T0dFVWVqply5Zavny5UlNTVVRUpObNm6tVq1Ye7du1a6fS0tIGj1dVVaWqqir3+7KyMr/WOzItURmpdq9X6vW1PQAA8F7Ig0yPHj1UVFQkp9Op999/X5MmTdKGDRsafbzc3Fzl5OT4scK6oqMsSu/SOmDtAQCAd0LetdS8eXN17dpV/fr1U25urvr06aOXXnpJdrtd33zzjc6cOePR/sSJE7Lb7Q0eb/bs2XI6ne7XsWPHAnwGAAAgVEIeZGpzuVyqqqpSv3791KxZM61du9a978CBAzp69KjS09Mb/HxMTIx7OnfNCwAARKaQdi3Nnj1bo0aNUocOHVReXq4lS5aosLBQq1evls1m0yOPPKInnnhC8fHxiouL07Rp05Senu71jCUAABDZQhpkTp48qQceeEAOh0M2m029e/fW6tWrlZGRIUmaO3euoqKiNG7cOFVVVWnEiBGaP39+KEsGAABhJOzWkfE3f64jAwAAgsN068gAAAD4iiADAABMiyADAABMiyADAABMiyADAABMiyADAABMiyADAABMiyADAABMiyADAABMiyADAABMiyADAABMiyADAABMiyADAABMiyADAABMiyADAABMiyADAABM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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-0.8676593765172281\n" + ] + } + ], + "source": [ + "plt.scatter(df['wt'], df['mpg'])\n", + "plt.title('Weight vs MPG')\n", + "plt.xlabel('Weight')\n", + "plt.ylabel('MPG')\n", + "plt.show()\n", + "\n", + "print(df['wt'].corr(df['mpg']))" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "3e4168b2", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjMAAAHHCAYAAABKudlQAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAOTNJREFUeJzt3X98VNWd//H3JJAEIRmMEpJIgADyI6IUECwBwg/lh3WxYLuALS6o1SWCKyruarsWotJIq7hdF8GHfShW1hVbfii4RlQSMBBABZSIImSjUEgKBZyEH4mYOd8/+GbqJBPyazIzZ/J6Ph55+MidO5PPuTdy37nn3HMcxhgjAAAAS0UEuwAAAIDmIMwAAACrEWYAAIDVCDMAAMBqhBkAAGA1wgwAALAaYQYAAFiNMAMAAKxGmAEAAFYjzABolFmzZql79+5Nfm+HDh38WxCAVo8wA4SB119/XQ6HQ2vXrq312oABA+RwOJSbm1vrta5duyo9PT0QJTbK2bNntXDhQuXl5QW7FAAWIMwAYWDEiBGSpPz8fK/tZWVlKiwsVJs2bbR161av1w4fPqzDhw973ttQL7zwgvbv39+8gutx9uxZZWVlEWYANEibYBcAoPmSk5OVmppaK8wUFBTIGKN//Md/rPVa9feNDTNt27ZtXrEA4GfcmQHCxIgRI7R7926dO3fOs23r1q266qqrdOONN2r79u1yu91erzkcDg0fPtyzbeXKlRo8eLDatWun+Ph4TZ8+XYcPH/b6Ob7GzJw4cUK33Xab4uLi1LFjR82cOVOffPKJHA6HVqxYUavWI0eOaPLkyerQoYM6deqk+fPnq6qqSpL01VdfqVOnTpKkrKwsORwOORwOLVy40Ge7P/roIzkcDr388su1XnvnnXfkcDi0YcMGSVJ5ebnmzZun7t27Kzo6WgkJCRo3bpx27dpV94H9//Lz8zVkyBDFxMSoZ8+eev7557Vw4UI5HA6v/d59912NGDFCHTt2VIcOHdSnTx/98pe/9NqnsrJSCxYsUK9evRQdHa2UlBT967/+qyorK2v93JUrV2ro0KG65JJLdOmllyojI0MbN26st16gNeHODBAmRowYoVdeeUU7duzQ6NGjJV0ILOnp6UpPT5fL5VJhYaGuueYaz2t9+/bVZZddJklatGiRHn30UU2dOlW/+MUvdPz4cT377LPKyMjQ7t271bFjR58/1+12a9KkSdq5c6cyMzPVt29fvfHGG5o5c6bP/auqqjRhwgRdd911euqpp/Tee+/p6aefVs+ePZWZmalOnTpp2bJlyszM1JQpU3TLLbdIkqfumq699lr16NFDr7/+eq2fuWrVKl166aWaMGGCJGn27Nn685//rLlz5yotLU0nTpxQfn6+Pv/8cw0aNKjOY7t3716NHz9enTp10sKFC/Xdd99pwYIF6ty5s9d+n332mf7hH/5B11xzjR577DFFR0fr4MGDXl18brdbN998s/Lz83X33XerX79+2rt3r5555hl9+eWXWrdunWffrKwsLVy4UOnp6XrssccUFRWlHTt2aNOmTRo/fnyd9QKtjgEQFj777DMjyTz++OPGGGPOnz9v2rdvb15++WVjjDGdO3c2S5cuNcYYU1ZWZiIjI81dd91ljDHmq6++MpGRkWbRokVen7l3717Tpk0br+0zZ8403bp183y/evVqI8n8x3/8h2dbVVWVGTt2rJFkXnrpJa/3SjKPPfaY188ZOHCgGTx4sOf748ePG0lmwYIFDWr7I488Ytq2bWtOnjzp2VZZWWk6duxo7rjjDs82p9Np5syZ06DP/L7JkyebmJgY8/XXX3u27du3z0RGRprv/zP6zDPPGEnm+PHjdX7WK6+8YiIiIswHH3zgtX358uVGktm6dasxxpgDBw6YiIgIM2XKFFNVVeW1r9vtbnQbgHBGNxMQJvr166fLLrvMMxbmk08+0ZkzZzxPK6Wnp3vuEBQUFKiqqsozXmbNmjVyu92aOnWq/va3v3m+EhMTdeWVV/p8EqpaTk6O2rZtq7vuusuzLSIiQnPmzKnzPbNnz/b6fuTIkfq///u/pjVc0rRp03T+/HmtWbPGs23jxo365ptvNG3aNM+2jh07aseOHTp69GiDP7uqqkrvvPOOJk+erK5du3q29+vXz3PH5/ufL0lvvPGGV5fe9/3pT39Sv3791LdvX69jPXbsWEnyHOt169bJ7Xbr17/+tSIivP+prtm1BbR2hBkgTDgcDqWnp3vGxmzdulUJCQnq1auXJO8wU/3f6jBz4MABGWN05ZVXqlOnTl5fn3/+uY4dO1bnz/3666+VlJSkSy65xGt79c+tKSYmxjMmptqll16qU6dONa3huvD4ed++fbVq1SrPtlWrVunyyy/3hARJ+u1vf6vCwkKlpKRo6NChWrhwYb0h6vjx4zp37pyuvPLKWq/16dPH6/tp06Zp+PDh+sUvfqHOnTtr+vTpev31172CzYEDB/TZZ5/VOs69e/eWJM+xLioqUkREhNLS0hp/QIBWhjEzQBgZMWKE1q9fr71793rGy1RLT0/XQw89pCNHjig/P1/Jycnq0aOHpAvjOBwOh95++21FRkbW+lx/TnTn6/P9Ydq0aVq0aJH+9re/KTY2Vm+++aZuvfVWtWnz93/mpk6dqpEjR2rt2rXauHGjfve732nx4sVas2aNbrzxxmbX0K5dO23ZskW5ubl66623lJOTo1WrVmns2LHauHGjIiMj5Xa7dfXVV2vJkiU+PyMlJaXZdQCtDWEGCCPfn29m69atmjdvnue1wYMHKzo6Wnl5edqxY4d+9KMfeV7r2bOnjDFKTU313CFoqG7duik3N1dnz571ujtz8ODBJrejKd0o06ZNU1ZWllavXq3OnTurrKxM06dPr7VfUlKS7rnnHt1zzz06duyYBg0apEWLFtUZZjp16qR27drpwIEDtV7zNd9ORESErr/+el1//fVasmSJfvOb3+hXv/qVcnNzdcMNN6hnz5765JNPdP3111+0nT179pTb7da+ffv0gx/8oOEHAmiF6GYCwsi1116rmJgY/fd//7eOHDnidWcmOjpagwYN0tKlS3XmzBmv+WVuueUWRUZGKisrS8YYr880xujEiRN1/swJEybo/PnzeuGFFzzb3G63li5d2uR2VIeib775psHv6devn66++mqtWrVKq1atUlJSkjIyMjyvV1VVyeVyeb0nISFBycnJPh+JrhYZGakJEyZo3bp1OnTokGf7559/rnfeecdr35MnT9Z6f3UQqf4ZU6dO1ZEjR7yOV7Vz587pzJkzkqTJkycrIiJCjz32WK3xNzXPEdDacWcGCCNRUVEaMmSIPvjgA0VHR2vw4MFer6enp+vpp5+W5D1ZXs+ePfXEE0/okUce0VdffaXJkycrNjZWxcXFWrt2re6++27Nnz/f58+cPHmyhg4dqgcffFAHDx5U37599eabb3ou7E25y9KuXTulpaVp1apV6t27t+Lj49W/f3/179//ou+bNm2afv3rXysmJkZ33nmn18DZ8vJydenSRT/96U81YMAAdejQQe+9954+/PBDzzGpS1ZWlnJycjRy5Ejdc889+u677/Tss8/qqquu0qeffurZ77HHHtOWLVt00003qVu3bjp27Jiee+45denSxXO8b7vtNr3++uuaPXu2cnNzNXz4cFVVVemLL77Q66+/rnfeeUfXXnutevXqpV/96ld6/PHHNXLkSN1yyy2Kjo7Whx9+qOTkZGVnZzf6uAJhK6jPUgHwu0ceecRIMunp6bVeW7NmjZFkYmNjzXfffVfr9dWrV5sRI0aY9u3bm/bt25u+ffuaOXPmmP3793v2qflotjEXHqX+2c9+ZmJjY43T6TSzZs0yW7duNZLMa6+95vXe9u3b1/q5CxYsMDX/Odq2bZsZPHiwiYqKavBj2gcOHDCSjCSTn5/v9VplZaV56KGHzIABA0xsbKxp3769GTBggHnuuefq/VxjjNm8ebOnnh49epjly5fXqvv99983P/7xj01ycrKJiooyycnJ5tZbbzVffvml12d9++23ZvHixeaqq64y0dHR5tJLLzWDBw82WVlZxuVyee374osvmoEDB3r2GzVqlHn33XcbVDPQWjiM4X4lAP9bt26dpkyZovz8fK9ZhsPJwoULfXbNAQgsxswAaLbvL6EgXRif8uyzzyouLu6iM+sCgD8wZgZAs9177706d+6chg0bpsrKSq1Zs0bbtm3Tb37zG7Vr1y7Y5QEIc4QZAM02duxYPf3009qwYYMqKirUq1cvPfvss5o7d26wSwPQCjBmBgAAWI0xMwAAwGqEGQAAYLWwHzPjdrt19OhRxcbGstIsAACWMMaovLxcycnJtVaOrynsw8zRo0dZuA0AAEsdPnxYXbp0ueg+YR9mYmNjJV04GHFxcUGuBgAANERZWZlSUlI81/GLCfswU921FBcXR5gBAMAyDRkiwgBgAABgNcIMAACwGmEGAABYjTADAACsRpgBAABWI8wAAACrEWYAAIDVCDMAAMBqhBkAAGC1sJ8BGEDzVbmNdhaf1LHyCiXExmhoarwiI1i4FUBoIMwAuKicwhJlrd+nEleFZ1uSM0YLJqVpYv+kIFYGABfQzQSgTjmFJcpcucsryEhSqatCmSt3KaewJEiVAcDfEWYA+FTlNspav0/Gx2vV27LW71OV29ceABA4hBkAPu0sPlnrjsz3GUklrgrtLD4ZuKIAwAfCDACfjpXXHWSash8AtBTCDACfEmJj/LofALQUwgwAn4amxivJGaO6HsB26MJTTUNT4wNZFgDUQpgB4FNkhEMLJqVJUq1AU/39gklpzDcDIOgIMwDqNLF/kpbNGKREp3dXUqIzRstmDGKeGQAhgUnzAFzUxP5JGpeWyAzAAEIWYQZAvSIjHBrW87JglwEAPtHNBAAArEaYAQAAViPMAAAAqxFmAACA1QgzAADAaoQZAABgNcIMAACwGmEGAABYjTADAACsRpgBAABWI8wAAACrEWYAAIDVCDMAAMBqhBkAAGA1wgwAALAaYQYAAFiNMAMAAKxGmAEAAFYjzAAAAKsRZgAAgNUIMwAAwGqEGQAAYDXCDAAAsBphBgAAWI0wAwAArBbUMJOdna0hQ4YoNjZWCQkJmjx5svbv3+95/eTJk7r33nvVp08ftWvXTl27dtW//Mu/yOVyBbFqAAAQSoIaZjZv3qw5c+Zo+/btevfdd3X+/HmNHz9eZ86ckSQdPXpUR48e1VNPPaXCwkKtWLFCOTk5uvPOO4NZNgAACCEOY4wJdhHVjh8/roSEBG3evFkZGRk+9/nTn/6kGTNm6MyZM2rTpk29n1lWVian0ymXy6W4uDh/lwwAAFpAY67f9aeBAKruPoqPj7/oPnFxcXUGmcrKSlVWVnq+Lysr82+RAAAgpITMAGC326158+Zp+PDh6t+/v899/va3v+nxxx/X3XffXefnZGdny+l0er5SUlJaqmQAABACQqabKTMzU2+//bby8/PVpUuXWq+XlZVp3Lhxio+P15tvvqm2bdv6/Bxfd2ZSUlLoZgIAwCLWdTPNnTtXGzZs0JYtW3wGmfLyck2cOFGxsbFau3ZtnUFGkqKjoxUdHd2S5QIAgBAS1G4mY4zmzp2rtWvXatOmTUpNTa21T1lZmcaPH6+oqCi9+eabiomJCUKlAAAgVAX1zsycOXP06quv6o033lBsbKxKS0slSU6nU+3atfMEmbNnz2rlypUqKyvzDOjt1KmTIiMjg1k+AAAIAUEdM+NwOHxuf+mllzRr1izl5eVpzJgxPvcpLi5W9+7d6/0ZPJoNAIB9rBkzU1+OGj16dL37AACA1i1kHs0GAABoCsIMAACwGmEGAABYjTADAACsRpgBAABWI8wAAACrEWYAAIDVCDMAAMBqhBkAAGA1wgwAALAaYQYAAFiNMAMAAKxGmAEAAFYjzAAAAKsRZgAAgNUIMwAAwGqEGQAAYDXCDAAAsBphBgAAWI0wAwAArEaYAQAAViPMAAAAqxFmAACA1QgzAADAaoQZAABgNcIMAACwGmEGAABYjTADAACsRpgBAABWI8wAAACrEWYAAIDVCDMAAMBqhBkAAGA1wgwAALAaYQYAAFiNMAMAAKxGmAEAAFYjzAAAAKsRZgAAgNUIMwAAwGqEGQAAYDXCDAAAsBphBgAAWI0wAwAArEaYAQAAViPMAAAAqxFmAACA1QgzAADAaoQZAABgNcIMAACwGmEGAABYjTADAACsRpgBAABWI8wAAACrEWYAAIDVCDMAAMBqhBkAAGA1wgwAALAaYQYAAFitTbALAIDGqnIb7Sw+qWPlFUqIjdHQ1HhFRjiCXRaAICHMALBKTmGJstbvU4mrwrMtyRmjBZPSNLF/UhArAxAsdDMBsEZOYYkyV+7yCjKSVOqqUObKXcopLAlSZQCCiTADwApVbqOs9ftkfLxWvS1r/T5VuX3tASCcEWYAWGFn8clad2S+z0gqcVVoZ/HJwBUFICQQZgBY4Vh53UGmKfsBCB+EGQBWSIiN8et+AMIHYQaAFYamxivJGaO6HsB26MJTTUNT4wNZFoAQQJgBYIXICIcWTEqTpFqBpvr7BZPSmG8GaIUIM2g1qtxGBUUn9MaeIyooOsFTL80UjOM5sX+Sls0YpESnd1dSojNGy2YMYp4ZoJUK6qR52dnZWrNmjb744gu1a9dO6enpWrx4sfr06ePZp6KiQg8++KBee+01VVZWasKECXruuefUuXPnIFYO2zDRmn8F83hO7J+kcWmJzAAMwMNhjAnan6cTJ07U9OnTNWTIEH333Xf65S9/qcLCQu3bt0/t27eXJGVmZuqtt97SihUr5HQ6NXfuXEVERGjr1q0N+hllZWVyOp1yuVyKi4tryeYgRFVPtFbzF7360sdf9I3D8QQQCI25fgc1zNR0/PhxJSQkaPPmzcrIyJDL5VKnTp306quv6qc//akk6YsvvlC/fv1UUFCgH/7wh/V+JmGmdatyG41YvKnO+UkcutBFkf9vY/nLvgE4ngACpTHX75AaM+NyuSRJ8fEXnkb4+OOPdf78ed1www2effr27auuXbuqoKDA52dUVlaqrKzM66u1YExIbUy05l8cTwChKGQWmnS73Zo3b56GDx+u/v37S5JKS0sVFRWljh07eu3buXNnlZaW+vyc7OxsZWVltXS5IYcxIb4x0Zp/cTwBhKKQuTMzZ84cFRYW6rXXXmvW5zzyyCNyuVyer8OHD/upwtDF4nt1Y6I1/+J4AghFIRFm5s6dqw0bNig3N1ddunTxbE9MTNS3336rb775xmv/v/71r0pMTPT5WdHR0YqLi/P6CmcsvndxTLTmXxxPAKEoqGHGGKO5c+dq7dq12rRpk1JTU71eHzx4sNq2bav333/fs23//v06dOiQhg0bFuhyQxJjGC6Oidb8i+MJIBQFNczMmTNHK1eu1KuvvqrY2FiVlpaqtLRU586dkyQ5nU7deeedeuCBB5Sbm6uPP/5Yt99+u4YNG9agJ5laA8Yw1I+J1vyL4wkg1AR1APCyZcskSaNHj/ba/tJLL2nWrFmSpGeeeUYRERH6yU9+4jVpHi5gDEPDMNGaf3E8AYSSkJpnpiWE+zwz1fN+lLoqfI6bYd4PAICNrJ1nBo3HGAYAQGtHmAkDjGEAALRmITNpHpqHMQwAgNaKMBNGIiMcGtbzsmCXAQBAQNHNBAAArEaYAQAAViPMAAAAqxFmAACA1QgzAADAajzNhICrchseIQcA+A1hBgGVU1iirPX7vFb6TnLGaMGkNCb3sxgBFUAwEWYQMDmFJcpcuavWGlKlrgplrtzFbMWWIqACCDbGzCAgqtxGWev3+VwMs3pb1vp9qnKH9bqnYac6oH4/yEh/D6g5hSVBqgxAa0KYQUDsLD5Z64L3fUZSiatCO4tPBq4oNAsBFUCoIMwgII6V1x1kmrIfgo+ACiBUEGYQEAmxMfXv1Ij9EHwEVAChgjCDgBiaGq8kZ4zqer7FoQuDRoemxgeyLDQDARVAqCDMICAiIxxaMClNkmoFmurvF0xK43FeixBQAYQKwgwCZmL/JC2bMUiJTu+/1BOdMTyWbSECKoBQ4TDGhPWjBmVlZXI6nXK5XIqLiwt2ORATrIUb5pkB0BIac/0mzABoNgIqAH9rzPWbGYABNFtkhEPDel4W7DIAtFKMmQEAAFYjzAAAAKsRZgAAgNUIMwAAwGqEGQAAYDXCDAAAsBphBgAAWI0wAwAArEaYAQAAVmtSmCkuLtaBAwdqbT9w4IC++uqr5tYEAADQYE0KM7NmzdK2bdtqbd+xY4dmzZrV3JoQAFVuo4KiE3pjzxEVFJ1QlTusl+hCmOD3FoAvTVqbaffu3Ro+fHit7T/84Q81d+7cZheFlsUqx7ARv7cA6tKkOzMOh0Pl5eW1trtcLlVVVTW7KLScnMISZa7c5XVBkKRSV4UyV+5STmFJkCoD6sbvLYCLaVKYycjIUHZ2tldwqaqqUnZ2tkaMGOG34uBfVW6jrPX75OvGfPW2rPX7uHWPkMLvLYD6NKmbafHixcrIyFCfPn00cuRISdIHH3ygsrIybdq0ya8Fwn92Fp+s9Zft9xlJJa4K7Sw+qWE9LwtcYcBF8HsLoD5NujOTlpamTz/9VFOnTtWxY8dUXl6uf/qnf9IXX3yh/v37+7tG+Mmx8rovCE3ZDwgEfm8B1KdJd2YkKTk5Wb/5zW/8WQtaWEJsjF/3AwKB31sA9WnypHkffPCBZsyYofT0dB05ckSS9Morryg/P99vxcG/hqbGK8kZI0cdrzt04emQoanxgSwLuCh+bwHUp0lhZvXq1ZowYYLatWunXbt2qbKyUtKFp5m4WxO6IiMcWjApTZJqXRiqv18wKU2REXVdNoDA4/cWQH2aFGaeeOIJLV++XC+88ILatm3r2T58+HDt2rXLb8XB/yb2T9KyGYOU6PS+JZ/ojNGyGYOYrwMhid9bABfTpDEz+/fvV0ZGRq3tTqdT33zzTXNrQgub2D9J49IStbP4pI6VVygh9sItev6yRSjj9xZAXZoUZhITE3Xw4EF1797da3t+fr569Ojhj7rQwiIjHDzGCuvwewvAlyaFmbvuukv33XefXnzxRTkcDh09elQFBQWaP3++Hn30UX/XCEi6MHlauP1VHo5tAoBAa1KYefjhh+V2u3X99dfr7NmzysjIUHR0tObPn697773X3zUCYbkuTzi2CQCCwWGMafIc4N9++60OHjyo06dPKy0tTR06dPBnbX5RVlYmp9Mpl8uluLi4YJeDJqhel6fmL2r1/QsbB4CGY5sAwJ8ac/1u8jwzkhQVFaW0tDT17dtX7733nj7//PPmfFzYqnIbFRSd0Bt7jqig6ARryDRCOK7LE45tAoBgalI309SpU5WRkaG5c+fq3LlzGjJkiIqLi2WM0Wuvvaaf/OQn/q7TWnQlNE84rssTjm0CgGBq0p2ZLVu2eBaYXLt2rdxut7755hv953/+p5544gm/Fmiz6q6EmheuUleFMlfuUk5hSZAqs0c4rssTjm0CgGBqUphxuVyKj78wdXhOTo5+8pOf6JJLLtFNN92kAwcO+LVAW9GV4B/huC5POLYJAIKpSWEmJSVFBQUFOnPmjHJycjR+/HhJ0qlTpxQTwz/AUuO6ElC3cFyXJxzbBADB1KQwM2/ePP385z9Xly5dlJSUpNGjR0u60P109dVX+7M+a9GV4B/huC5POLYJAIKpSWHmnnvu0fbt2/Xiiy9q27Ztioi48DE9evTQokWL/FqgrehK8J9wXJcnHNsEAMHSpHlmHnjggQbvu2TJksZ+vF8Fa56ZKrfRiMWbVOqq8DluxqELF678fxvLX+ANFI6z5YZjmwDAHxpz/W7So9m7d+/W7t27df78efXp00eS9OWXXyoyMlKDBg3y7OdwtN5/lKu7EjJX7pJD8go0dCU0TTiuyxOObQKAQGtSmJk0aZJiY2P18ssv69JLL5V0YfDv7bffrpEjR+rBBx/0a5G2qu5KqDnPTCLzzABNwp0sAL40qZvpiiuu0MaNG3XVVVd5bS8sLNT48eN19OhRvxXYXKGwnAH/AAPNxwSUQOvS4t1MZWVlOn78eK3tx48fV3l5eVM+MqzRlQA0T11rWVVPQMmgaaB1a9LTTFOmTNHtt9+uNWvW6C9/+Yv+8pe/aPXq1brzzjt1yy23+LtGAK0YE1ACqE+T7swsX75c8+fP189+9jOdP3/+wge1aaM777xTv/vd7/xaIIDWjbWsANSnSWHmkksu0XPPPaff/e53KioqkiT17NlT7du392txAMAElADq06QwU619+/a65ppr/FULANTCBJQA6tOkMTMAECisZQWgPoQZACGNtawA1IcwAyDksZYVgItp1pgZAAiUif2TNC4tkQkoAdRCmAFgDSagBOALYQYA/IBlS4DgCeqYmS1btmjSpElKTk6Ww+HQunXrvF4/ffq05s6dqy5duqhdu3ZKS0vT8uXLg1MsANQhp7BEIxZv0q0vbNd9r+3RrS9s14jFm5RTWBLs0gCPKrdRQdEJvbHniAqKToTVrNlBvTNz5swZDRgwQHfccYfPZRAeeOABbdq0SStXrlT37t21ceNG3XPPPUpOTtbNN98chIoBwBvrRsEG4b5Qa1DvzNx444164oknNGXKFJ+vb9u2TTNnztTo0aPVvXt33X333RowYIB27twZ4EoBoDbWjYINqgN3zWVBqgN3ONxBDOlHs9PT0/Xmm2/qyJEjMsYoNzdXX375pcaPH1/neyorK1VWVub1BQAtoTHrRgHB0FoCd0iHmWeffVZpaWnq0qWLoqKiNHHiRC1dulQZGRl1vic7O1tOp9PzlZKSEsCKAbQmrBuFUNdaAnfIh5nt27frzTff1Mcff6ynn35ac+bM0XvvvVfnex555BG5XC7P1+HDhwNYMYDWhHWjEOpaS+AO2Uezz507p1/+8pdau3atbrrpJknSNddcoz179uipp57SDTfc4PN90dHRio6ODmSpAFqp6nWjSl0VPm/jO3RhlmLWjUKwtGTgDqXpCEI2zJw/f17nz59XRIT3zaPIyEi53e4gVQUAf1e9blTmyl1ySF6BhnWjEApaKnCH2tNRQe1mOn36tPbs2aM9e/ZIkoqLi7Vnzx4dOnRIcXFxGjVqlB566CHl5eWpuLhYK1as0B//+Mc6n34CgEBj3SiEspZYqDUUn45yGGOCNoQ5Ly9PY8aMqbV95syZWrFihUpLS/XII49o48aNOnnypLp166a7775b999/vxyOhh34srIyOZ1OuVwuxcXF+bsJACAptG65AzX5605KldtoxOJNdQ4qrr7Tk/9vY5v9+9+Y63dQw0wgEGYAAPBP4C4oOqFbX9he737/c9cPm72OWmOu3yE7ZgYAAPiPPxZqDdWno0L60WwAABA6QnU6AsIMAABokOqno+rqnHLowlicQE9HQJgBAAAN0hJPR/kDYQYAGqHKbVRQdEJv7DmigqIT1q9pAzRWKE5HwABgAGigUJsoDAiWif2TNC4tMWSmI+DRbABogOqJwmr+g1n9TzcT5AH+1ZjrN91MAFCPKrdR1vp9PqeDr96WtX4fXU5AkBBmAKAeO4tP1jnjqXQh0JS4KrSz+GTgigLgQZgBgHqE6kRhAC4gzABAPUJ1ojAAF/A0EwDUo3qisFJXhc9xM9WL6wV6ojCgpdi2cCphBgDqUT1RWObKXXJIXoEmmBOF1WTbBQihycYpCHg0GwAaKJT/kQ/l2mCPUJqCoDHXb8IMADRCKN79CKULEOxV5TYasXhTnU/uVXen5v/b2ID8zjfm+k03EwA0QmSEQ8N6XhbsMjzqmwPHoQtz4IxLSwx66EJoa8wUBKH0/4DE00wALMGaSL4xBw78xeYpCLgzAyDkMR6kbjZfgBBabJ6CgDszAEJa9XiQmncfSl0Vyly5SzmFJUGqLDTYfAFCaKmegqCuzkiHLvwREYpTEBBmAIQs1kSqn80XIISW6ikIJNX6fQqlKQh8IcwACFmMB6mfzRcghJ6J/ZO0bMYgJTq97+QlOmNC+qk4xswACFmMB2mY6gtQzXFFiYwrQhNM7J+kcWmJITcFwcUQZgCELMaDNJyNFyCErlCbgqA+hBkAIYs1kRrHtgsQ4C+MmQEQshgPAqAhCDMAQpqtAxIBBA7dTABCnu3jQUJxPScgnBBmAEu09gtiZIRDQ1PjPcdgZ/FJK44BsxcDLY8wA1iAC6Kdx6Cu1ayrZy+mmwzwD8bMACGO6fztPAbMXgwEDmEGCGFcEO09BsxeDAQOYQYIYVwQ7T0GzF4MBA5jZpqotQ/GRGBwQbT3GDB7MRA4hJkmsHEgIuzEBbHxxyBU/tBg9mIgcAgzjcTTCQgkLoiNOwah9IdG9ezFmSt3ySF51c7sxYB/MWamEWwdiAh7MZ1/w4/Bu/tKQ+6JJ2YvBgLDYYwJ6ytvWVmZnE6nXC6X4uLimvVZBUUndOsL2+vd73/u+iGLvcGvQumOQ7Bc7BiMS0vUiMWb6hwoXH33Jv/fxgYl+IVK1xdgk8Zcv+lmagRbByLCfrZP5+8PFzsGBUUnGvzEUzD+0GA1a6BlEWYagcGYCCYuiHUfA/7QAFo3xsw0QvVAxLr+Fnbowm3vcB6MCYQi/tAAWjfCTCMwGLPhqtxGBUUn9MaeIyooOsGgaLQo/tAAWje6mRqp+umEmgMRE1vZYMyLYbAqAo3HoIHWjaeZmoinE3yrax6e6iPD46hoSQRpIHw05vpNmIHfVLlNSD8ei9aBPzSA8MCj2QiKxiwI2NqfykHL4akvoPVhADD8hsdjAQDBQJiB3/B4LAAgGAgz8BsejwUABANhBn7DPDwAgGAgzMCvWCUYABBoPM0Evwv3RRF59BcAQgthBi0iXB+PZVI2AAg9dDMBDVQ9u3HNuXRKXRXKXLlLOYUlQaoMAFo3wgzQAFVuo6z1+2ot0yD9fR2grPX7WFATAIKAMAM0QGNmNwYABBZhBmgAZjcGgNBFmAEagNmNASB0EWaABmB2YwAIXYQZoAGY3RgAQhdhBmggZjcGgNDEpHlAI4T77MYAYCPCDNBI4Tq7MQDYim4mAABgNcIMAACwGmEGAABYjTADAACsRpgBAABWI8wAAACrEWYAAIDVghpmtmzZokmTJik5OVkOh0Pr1q2rtc/nn3+um2++WU6nU+3bt9eQIUN06NChwBcLAABCUlDDzJkzZzRgwAAtXbrU5+tFRUUaMWKE+vbtq7y8PH366ad69NFHFRPDysQAAOAChzHGBLsISXI4HFq7dq0mT57s2TZ9+nS1bdtWr7zySpM/t6ysTE6nUy6XS3FxcX6oFAAAtLTGXL9DdsyM2+3WW2+9pd69e2vChAlKSEjQdddd57Mr6vsqKytVVlbm9QUAAMJXyIaZY8eO6fTp03ryySc1ceJEbdy4UVOmTNEtt9yizZs31/m+7OxsOZ1Oz1dKSkoAqwYAAIEWst1MR48e1RVXXKFbb71Vr776qme/m2++We3bt9f//M//+PycyspKVVZWer4vKytTSkoK3UxAK1flNqx2DlikMd1MIbtq9uWXX642bdooLS3Na3u/fv2Un59f5/uio6MVHR3d0uUBsEhOYYmy1u9TiavCsy3JGaMFk9I0sX9SECsD4A8h280UFRWlIUOGaP/+/V7bv/zyS3Xr1i1IVQGwTU5hiTJX7vIKMpJU6qpQ5spdyiksCVJlAPwlqHdmTp8+rYMHD3q+Ly4u1p49exQfH6+uXbvqoYce0rRp05SRkaExY8YoJydH69evV15eXvCKBmCNKrdR1vp98tWXbiQ5JGWt36dxaYl0OQEWC+qdmY8++kgDBw7UwIEDJUkPPPCABg4cqF//+teSpClTpmj58uX67W9/q6uvvlp/+MMftHr1ao0YMSKYZQOwxM7ik7XuyHyfkVTiqtDO4pOBKwqA3wX1zszo0aNV3/jjO+64Q3fccUeAKgIQTo6V1x1kmrIfgNAUsmNmAKC5EmIbNlt4Q/cDEJoIMwDC1tDUeCU5Y1TXaBiHLjzVNDQ1PpBlAfAzwgyAsBUZ4dCCSRemd6gZaKq/XzApjcG/gOUIMwDC2sT+SVo2Y5ASnd5dSYnOGC2bMYh5ZoAwELKT5gGAv0zsn6RxaYnMAAyEKcIMgFYhMsKhYT0vC3YZAFoA3UwAAMBqhBkAAGA1upmAIGAFZwDwH8IMEGCs4AwA/kU3ExBArOAMAP5HmAECpL4VnKULKzhXuS++XhkAwBthBggQVnBuvarcRgVFJ/TGniMqKDpBYAX8jDEzQICwgnPrxBgpoOVxZwYIEFZwbn0YIwUEBmEGCBBWcG5dGCMFBA5hBggQVnBuXRgjBQQOYQYIIFZwbj0YIwUEDgOAgQBjBefWgTFSQOAQZoAgYAXn8Fc9RqrUVeFz3IxDF+7IMUYKaD66mQCgBTBGCggcwgwAtBDGSAGBQTcTALQgxkgBLY8wAwAtjDFSQMuimwkAAFiNMAMAAKxGmAEAAFYjzAAAAKsRZgAAgNUIMwAAwGqEGQAAYDXCDAAAsBphBgAAWI0wAwAArEaYAQAAViPMAAAAqxFmAACA1QgzAADAaoQZAABgNcIMAACwGmEGAABYjTADAACsRpgBAABWI8wAAACrtQl2AQAQCFVuo53FJ3WsvEIJsTEamhqvyAhHsMsC4AeEGQBhL6ewRFnr96nEVeHZluSM0YJJaZrYPymIlQHwB7qZAIS1nMISZa7c5RVkJKnUVaHMlbuUU1gSpMoA+AthBkDYqnIbZa3fJ+PjteptWev3qcrtaw8AtiDMAGiUKrdRQdEJvbHniAqKToR0ENhZfLLWHZnvM5JKXBXaWXwycEUB8DvGzABoMNvGnhwrrzvINGU/AKGJOzMAGsTGsScJsTF+3Q9AaCLMAKiXrWNPhqbGK8kZo7oewHbowp2loanxgSwLgJ8RZgDUy9axJ5ERDi2YlCZJtQJN9fcLJqUx3wxgOcIMgHrZPPZkYv8kLZsxSIlO766kRGeMls0YFJJjfQA0DgOAAdTL9rEnE/snaVxaIjMAA2GKMAOgXtVjT0pdFT7HzTh04U5HKI89iYxwaFjPy4JdBoAWQDcTgHox9gRAKCPMAGgQxp4ACFV0MwFoMMaeAAhFhBkAjcLYEwChhm4mAABgNcIMAACwGmEGAABYjTADAACsRpgBAABWI8wAAACrEWYAAIDVCDMAAMBqhBkAAGC1sJ8B2JgLa/yWlZUFuRIAANBQ1dft6uv4xYR9mCkvL5ckpaSkBLkSAADQWOXl5XI6nRfdx2EaEnks5na7dfToUcXGxsrh8M9ieGVlZUpJSdHhw4cVFxfnl88MRa2hna2hjRLtDCetoY0S7QwnTW2jMUbl5eVKTk5WRMTFR8WE/Z2ZiIgIdenSpUU+Oy4uLmx/+b6vNbSzNbRRop3hpDW0UaKd4aQpbazvjkw1BgADAACrEWYAAIDVCDNNEB0drQULFig6OjrYpbSo1tDO1tBGiXaGk9bQRol2hpNAtDHsBwADAIDwxp0ZAABgNcIMAACwGmEGAABYjTADAACsRpjxYcuWLZo0aZKSk5PlcDi0bt26i+6fl5cnh8NR66u0tDQwBTdBdna2hgwZotjYWCUkJGjy5Mnav39/ve/705/+pL59+yomJkZXX321/vd//zcA1TZNU9q4YsWKWucxJiYmQBU3zbJly3TNNdd4JqQaNmyY3n777Yu+x6bzWK2x7bTxXNb05JNPyuFwaN68eRfdz8bz+X0NaaeN53PhwoW1au7bt+9F32PbuWxsG1vqPBJmfDhz5owGDBigpUuXNup9+/fvV0lJiecrISGhhSpsvs2bN2vOnDnavn273n33XZ0/f17jx4/XmTNn6nzPtm3bdOutt+rOO+/U7t27NXnyZE2ePFmFhYUBrLzhmtJG6cIsld8/j19//XWAKm6aLl266Mknn9THH3+sjz76SGPHjtWPf/xjffbZZz73t+08VmtsOyX7zuX3ffjhh3r++ed1zTXXXHQ/W89ntYa2U7LzfF511VVeNefn59e5r63nsjFtlFroPBpclCSzdu3ai+6Tm5trJJlTp04FpKaWcOzYMSPJbN68uc59pk6dam666Savbdddd53553/+55Yuzy8a0saXXnrJOJ3OwBXVQi699FLzhz/8wedrtp/H77tYO20+l+Xl5ebKK6807777rhk1apS577776tzX5vPZmHbaeD4XLFhgBgwY0OD9bTyXjW1jS51H7sz40Q9+8AMlJSVp3Lhx2rp1a7DLaRSXyyVJio+Pr3OfgoIC3XDDDV7bJkyYoIKCghatzV8a0kZJOn36tLp166aUlJR6//IPNVVVVXrttdd05swZDRs2zOc+tp9HqWHtlOw9l3PmzNFNN91U6zz5YvP5bEw7JTvP54EDB5ScnKwePXro5z//uQ4dOlTnvraey8a0UWqZ80iY8YOkpCQtX75cq1ev1urVq5WSkqLRo0dr165dwS6tQdxut+bNm6fhw4erf//+de5XWlqqzp07e23r3LlzSI8NqtbQNvbp00cvvvii3njjDa1cuVJut1vp6en6y1/+EsBqG2/v3r3q0KGDoqOjNXv2bK1du1ZpaWk+97X5PDamnbaey9dee027du1SdnZ2g/a39Xw2tp02ns/rrrtOK1asUE5OjpYtW6bi4mKNHDlS5eXlPve38Vw2to0tdh79fq8nzKgB3Uy+ZGRkmBkzZvi/oBYwe/Zs061bN3P48OGL7te2bVvz6quvem1bunSpSUhIaMny/KKhbazp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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-0.1747158787134049\n" + ] + } + ], + "source": [ + "plt.scatter(df['wt'], df['qsec'])\n", + "plt.title('Weight vs qsec')\n", + "plt.xlabel('Weight')\n", + "plt.ylabel('qsec')\n", + "plt.show()\n", + "\n", + "print(df['wt'].corr(df['qsec']))" + ] + }, + { + "cell_type": "markdown", + "id": "b4f1fabe", + "metadata": {}, + "source": [ + "- 이런 데이터셋을 분석해서 얻을 수 있는 경제적 가치가 무엇일까?, 어떤 비즈니스 상황에서 어떤 경제적 가치를 얻을 수 있을까?\n", + " - 나의 생각\n", + " - M1 활동에서는 지표들간의 상관관계를 찾는 작업을 진행했다.\n", + " - 데이터 분석의 목적이 무엇일까?\n", + " - 좋은 의사결정을 할 수 있는 근거를 마련하기.\n", + " - 좋은 의사결정이란?\n", + " - 의도와 근거가 명확한 결정..? -> 좀 더 생각해보기.\n", + " - 의사결정의 의도: 판매량 증가, 비용 절감, 효율 향상 등등 -> 이것들이 결국엔 경제적 가치로 이어진다.\n", + " - 근거가 있으면 설득할 수 있다.\n", + " - 지표들간의 상관관계를 찾으면 뭐가 좋으냐?\n", + " - 통제가능한 지표를 조절하여 통제불가능한 지표를 조절할 수 있다.\n", + " - 상관관계를 통해 부족한 데이터를 예측할 수 있다.\n", + " - 데이터를 분석하기 전에 목적(=경제적 가치)을 명확히 하는 것이 중요하다.\n", + " - 내가 비즈니스맨이라면 `1. 연도별로 선호하는 자동차 특성`, `2. 나이대별 선호하는 자동차 특성`이 궁금할 것 같다.\n", + " 1. 연도별로 선호하는 자동차 특성 \n", + " - 목적: 시장이 변화하는 추세를 파악하고 싶다 -> 이를 바탕으로 선호되는 특성에 맞는 신규 자동차 개발(경제적 가치)\n", + " - 방법: 연도별 판매량 column을 추가로 수집하고 다른 칼럼과 상관관계를 찾아본다.\n", + " 2. 나이대별 선호하는 자동차 특성\n", + " - 목적: 나이대별 선호하는 자동차 특성을 파악하고 싶다 -> 나이대별 맞춤 마케팅 전략 수립(경제적 가치)\n", + " - 방법: 나이대/성별 당 판매량 column을 추가로 수집하고 다른 칼럼과 상관관계를 찾아본다.\n", + " - 팀의 생각\n", + " - groupby했던 것 처럼 차량을 여러 카테고리로 분류하여 클러스터링 할 수 있다.\n", + " - 연비, 무게, 마력 등 지표간의 상관관계를 통해 최적화된 차량 설계가 가능하다.\n", + " - 소비자 선호 데이터를 추가하여 마케팅 전략 수립에 도움이 될 수 있다.\n", + " - 종합\n", + " - 팀원들은 현재 데이터를 기준으로, 나는 좀 더 일반적인 차원에서 생각해보았다.\n", + " - 공통적으로 데이터는 현재 상황을 해석하고 미래를 예측하는 근거라고 생각했다." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/missions/W1/M2.ipynb b/missions/W1/M2.ipynb new file mode 100644 index 0000000..775bfa1 --- /dev/null +++ b/missions/W1/M2.ipynb @@ -0,0 +1,2156 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 163, + "metadata": {}, + "outputs": [], + "source": [ + "import sqlite3\n", + "\n", + "conn = sqlite3.connect('./data.db')\n", + "\n", + "cursor = conn.cursor()\n", + "\n", + "sql = open('./create.sql', 'r').read()\n", + "\n", + "def init_db():\n", + " cursor.executescript(sql)\n", + "\n", + "def execute_and_print_query(query, limit=5):\n", + " cursor.execute(query)\n", + " print(query)\n", + " i = 0\n", + " for row in cursor:\n", + " if i >= limit:\n", + " print('...')\n", + " break\n", + " print(row)\n", + " i += 1\n", + " print('-' * 50)\n", + "\n", + "init_db()" + ] + }, + { + "cell_type": "code", + "execution_count": 164, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT * FROM Customers\n", + "(1, 'Alfreds Futterkiste', 'Maria Anders', 'Obere Str. 57', 'Berlin', '12209', 'Germany')\n", + "(2, 'Ana Trujillo Emparedados y helados', 'Ana Trujillo', 'Avda. de la Constitución 2222', 'México D.F.', '5021', 'Mexico')\n", + "(3, 'Antonio Moreno Taquería', 'Antonio Moreno', 'Mataderos 2312', 'México D.F.', '5023', 'Mexico')\n", + "(4, 'Around the Horn', 'Thomas Hardy', '120 Hanover Sq.', 'London', 'WA1 1DP', 'UK')\n", + "(5, 'Berglunds snabbköp', 'Christina Berglund', 'Berguvsvägen 8', 'Luleå', 'S-958 22', 'Sweden')\n", + "...\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Syntax\n", + "execute_and_print_query('SELECT * FROM Customers')" + ] + }, + { + "cell_type": "code", + "execution_count": 165, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT CustomerName, City FROM Customers\n", + "('Alfreds Futterkiste', 'Berlin')\n", + "('Ana Trujillo Emparedados y helados', 'México D.F.')\n", + "('Antonio Moreno Taquería', 'México D.F.')\n", + "('Around the Horn', 'London')\n", + "('Berglunds snabbköp', 'Luleå')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers\n", + "(1, 'Alfreds Futterkiste', 'Maria Anders', 'Obere Str. 57', 'Berlin', '12209', 'Germany')\n", + "(2, 'Ana Trujillo Emparedados y helados', 'Ana Trujillo', 'Avda. de la Constitución 2222', 'México D.F.', '5021', 'Mexico')\n", + "(3, 'Antonio Moreno Taquería', 'Antonio Moreno', 'Mataderos 2312', 'México D.F.', '5023', 'Mexico')\n", + "(4, 'Around the Horn', 'Thomas Hardy', '120 Hanover Sq.', 'London', 'WA1 1DP', 'UK')\n", + "(5, 'Berglunds snabbköp', 'Christina Berglund', 'Berguvsvägen 8', 'Luleå', 'S-958 22', 'Sweden')\n", + "...\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Select\n", + "execute_and_print_query('SELECT CustomerName, City FROM Customers')\n", + "execute_and_print_query('SELECT * FROM Customers')" + ] + }, + { + "cell_type": "code", + "execution_count": 166, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT DISTINCT Country FROM Customers\n", + "('Germany',)\n", + "('Mexico',)\n", + "('UK',)\n", + "('Sweden',)\n", + "('France',)\n", + "...\n", + "--------------------------------------------------\n", + "SELECT Country FROM Customers\n", + "('Germany',)\n", + "('Mexico',)\n", + "('Mexico',)\n", + "('UK',)\n", + "('Sweden',)\n", + "...\n", + "--------------------------------------------------\n", + "SELECT COUNT(DISTINCT Country) FROM Customers\n", + "(21,)\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Select Distinct\n", + "execute_and_print_query('SELECT DISTINCT Country FROM Customers')\n", + "execute_and_print_query('SELECT Country FROM Customers')\n", + "execute_and_print_query('SELECT COUNT(DISTINCT Country) FROM Customers')" + ] + }, + { + "cell_type": "code", + "execution_count": 167, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT * FROM Customers WHERE CustomerID=1\n", + "(1, 'Alfreds Futterkiste', 'Maria Anders', 'Obere Str. 57', 'Berlin', '12209', 'Germany')\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE CustomerID > 80\n", + "(81, 'Tradição Hipermercados', 'Anabela Domingues', 'Av. Inês de Castro, 414', 'São Paulo', '05634-030', 'Brazil')\n", + "(82, \"Trail''s Head Gourmet Provisioners\", 'Helvetius Nagy', '722 DaVinci Blvd.', 'Kirkland', '98034', 'USA')\n", + "(83, 'Vaffeljernet', 'Palle Ibsen', 'Smagsløget 45', 'Århus', '8200', 'Denmark')\n", + "(84, 'Victuailles en stock', 'Mary Saveley', '2, rue du Commerce', 'Lyon', '69004', 'France')\n", + "(85, 'Vins et alcools Chevalier', 'Paul Henriot', \"59 rue de l''Abbaye\", 'Reims', '51100', 'France')\n", + "...\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Where\n", + "execute_and_print_query('SELECT * FROM Customers WHERE CustomerID=1')\n", + "execute_and_print_query('SELECT * FROM Customers WHERE CustomerID > 80')" + ] + }, + { + "cell_type": "code", + "execution_count": 168, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT * FROM Products ORDER BY Price\n", + "(33, 'Geitost', 15, 4, '500 g', 2.5)\n", + "(24, 'Guaraná Fantástica', 10, 1, '12 - 355 ml cans', 4.5)\n", + "(13, 'Konbu', 6, 8, '2 kg box', 6)\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Products ORDER BY Price DESC\n", + "(38, 'Côte de Blaye', 18, 1, '12 - 75 cl bottles', 263.5)\n", + "(29, 'Thüringer Rostbratwurst', 12, 6, '50 bags x 30 sausgs.', 123.79)\n", + "(9, 'Mishi Kobe Niku', 4, 6, '18 - 500 g pkgs.', 97)\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Products ORDER BY ProductName\n", + "(17, 'Alice Mutton', 7, 6, '20 - 1 kg tins', 39)\n", + "(3, 'Aniseed Syrup', 1, 2, '12 - 550 ml bottles', 10)\n", + "(40, 'Boston Crab Meat', 19, 8, '24 - 4 oz tins', 18.4)\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Products ORDER BY ProductName DESC\n", + "(47, 'Zaanse koeken', 22, 3, '10 - 4 oz boxes', 9.5)\n", + "(64, 'Wimmers gute Semmelknödel', 12, 5, '20 bags x 4 pieces', 33.25)\n", + "(63, 'Vegie-spread', 7, 2, '15 - 625 g jars', 43.9)\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers ORDER BY Country, CustomerName\n", + "(12, 'Cactus Comidas para llevar', 'Patricio Simpson', 'Cerrito 333', 'Buenos Aires', '1010', 'Argentina')\n", + "(54, 'Océano Atlántico Ltda.', 'Yvonne Moncada', 'Ing. Gustavo Moncada 8585 Piso 20-A', 'Buenos Aires', '1010', 'Argentina')\n", + "(64, 'Rancho grande', 'Sergio Gutiérrez', 'Av. del Libertador 900', 'Buenos Aires', '1010', 'Argentina')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers ORDER BY Country ASC, CustomerName DESC\n", + "(64, 'Rancho grande', 'Sergio Gutiérrez', 'Av. del Libertador 900', 'Buenos Aires', '1010', 'Argentina')\n", + "(54, 'Océano Atlántico Ltda.', 'Yvonne Moncada', 'Ing. Gustavo Moncada 8585 Piso 20-A', 'Buenos Aires', '1010', 'Argentina')\n", + "(12, 'Cactus Comidas para llevar', 'Patricio Simpson', 'Cerrito 333', 'Buenos Aires', '1010', 'Argentina')\n", + "...\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Order By\n", + "execute_and_print_query('SELECT * FROM Products ORDER BY Price', limit=3)\n", + "execute_and_print_query('SELECT * FROM Products ORDER BY Price DESC', limit=3)\n", + "execute_and_print_query('SELECT * FROM Products ORDER BY ProductName', limit=3)\n", + "execute_and_print_query('SELECT * FROM Products ORDER BY ProductName DESC', limit=3)\n", + "execute_and_print_query('SELECT * FROM Customers ORDER BY Country, CustomerName', limit=3)\n", + "execute_and_print_query('SELECT * FROM Customers ORDER BY Country ASC, CustomerName DESC', limit=3)" + ] + }, + { + "cell_type": "code", + "execution_count": 169, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT * FROM Customers WHERE Country = 'Spain' AND CustomerName LIKE 'G%'\n", + "(29, 'Galería del gastrónomo', 'Eduardo Saavedra', 'Rambla de Cataluña, 23', 'Barcelona', '8022', 'Spain')\n", + "(30, 'Godos Cocina Típica', 'José Pedro Freyre', 'C/ Romero, 33', 'Sevilla', '41101', 'Spain')\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE Country = 'Germany' AND City = 'Berlin' AND PostalCode > 12000\n", + "(1, 'Alfreds Futterkiste', 'Maria Anders', 'Obere Str. 57', 'Berlin', '12209', 'Germany')\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE Country = 'Spain' AND (CustomerName LIKE 'G%' OR CustomerName LIKE 'R%')\n", + "(29, 'Galería del gastrónomo', 'Eduardo Saavedra', 'Rambla de Cataluña, 23', 'Barcelona', '8022', 'Spain')\n", + "(30, 'Godos Cocina Típica', 'José Pedro Freyre', 'C/ Romero, 33', 'Sevilla', '41101', 'Spain')\n", + "(69, 'Romero y tomillo', 'Alejandra Camino', 'Gran Vía, 1', 'Madrid', '28001', 'Spain')\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE Country = 'Spain' AND CustomerName LIKE 'G%' OR CustomerName LIKE 'R%'\n", + "(29, 'Galería del gastrónomo', 'Eduardo Saavedra', 'Rambla de Cataluña, 23', 'Barcelona', '8022', 'Spain')\n", + "(30, 'Godos Cocina Típica', 'José Pedro Freyre', 'C/ Romero, 33', 'Sevilla', '41101', 'Spain')\n", + "(64, 'Rancho grande', 'Sergio Gutiérrez', 'Av. del Libertador 900', 'Buenos Aires', '1010', 'Argentina')\n", + "(65, 'Rattlesnake Canyon Grocery', 'Paula Wilson', '2817 Milton Dr.', 'Albuquerque', '87110', 'USA')\n", + "(66, 'Reggiani Caseifici', 'Maurizio Moroni', 'Strada Provinciale 124', 'Reggio Emilia', '42100', 'Italy')\n", + "...\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL And\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE Country = 'Spain' AND CustomerName LIKE 'G%'\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE Country = 'Germany' AND City = 'Berlin' AND PostalCode > 12000\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE Country = 'Spain' AND (CustomerName LIKE 'G%' OR CustomerName LIKE 'R%')\")\n", + "# (spain and g) or r\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE Country = 'Spain' AND CustomerName LIKE 'G%' OR CustomerName LIKE 'R%'\")" + ] + }, + { + "cell_type": "code", + "execution_count": 170, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT * FROM Customers WHERE Country = 'Germany' OR Country = 'Spain'\n", + "(1, 'Alfreds Futterkiste', 'Maria Anders', 'Obere Str. 57', 'Berlin', '12209', 'Germany')\n", + "(6, 'Blauer See Delikatessen', 'Hanna Moos', 'Forsterstr. 57', 'Mannheim', '68306', 'Germany')\n", + "(8, 'Bólido Comidas preparadas', 'Martín Sommer', 'C/ Araquil, 67', 'Madrid', '28023', 'Spain')\n", + "(17, 'Drachenblut Delikatessend', 'Sven Ottlieb', 'Walserweg 21', 'Aachen', '52066', 'Germany')\n", + "(22, 'FISSA Fabrica Inter. Salchichas S.A.', 'Diego Roel', 'C/ Moralzarzal, 86', 'Madrid', '28034', 'Spain')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE City = 'Berlin' OR CustomerName LIKE 'G%' OR Country = 'Norway'\n", + "(1, 'Alfreds Futterkiste', 'Maria Anders', 'Obere Str. 57', 'Berlin', '12209', 'Germany')\n", + "(29, 'Galería del gastrónomo', 'Eduardo Saavedra', 'Rambla de Cataluña, 23', 'Barcelona', '8022', 'Spain')\n", + "(30, 'Godos Cocina Típica', 'José Pedro Freyre', 'C/ Romero, 33', 'Sevilla', '41101', 'Spain')\n", + "(31, 'Gourmet Lanchonetes', 'André Fonseca', 'Av. Brasil, 442', 'Campinas', '04876-786', 'Brazil')\n", + "(32, 'Great Lakes Food Market', 'Howard Snyder', '2732 Baker Blvd.', 'Eugene', '97403', 'USA')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE Country = 'Spain' AND (CustomerName LIKE 'G%' OR CustomerName LIKE 'R%')\n", + "(29, 'Galería del gastrónomo', 'Eduardo Saavedra', 'Rambla de Cataluña, 23', 'Barcelona', '8022', 'Spain')\n", + "(30, 'Godos Cocina Típica', 'José Pedro Freyre', 'C/ Romero, 33', 'Sevilla', '41101', 'Spain')\n", + "(69, 'Romero y tomillo', 'Alejandra Camino', 'Gran Vía, 1', 'Madrid', '28001', 'Spain')\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE Country = 'Spain' AND CustomerName LIKE 'G%' OR CustomerName LIKE 'R%'\n", + "(29, 'Galería del gastrónomo', 'Eduardo Saavedra', 'Rambla de Cataluña, 23', 'Barcelona', '8022', 'Spain')\n", + "(30, 'Godos Cocina Típica', 'José Pedro Freyre', 'C/ Romero, 33', 'Sevilla', '41101', 'Spain')\n", + "(64, 'Rancho grande', 'Sergio Gutiérrez', 'Av. del Libertador 900', 'Buenos Aires', '1010', 'Argentina')\n", + "(65, 'Rattlesnake Canyon Grocery', 'Paula Wilson', '2817 Milton Dr.', 'Albuquerque', '87110', 'USA')\n", + "(66, 'Reggiani Caseifici', 'Maurizio Moroni', 'Strada Provinciale 124', 'Reggio Emilia', '42100', 'Italy')\n", + "...\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL OR\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE Country = 'Germany' OR Country = 'Spain'\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE City = 'Berlin' OR CustomerName LIKE 'G%' OR Country = 'Norway'\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE Country = 'Spain' AND (CustomerName LIKE 'G%' OR CustomerName LIKE 'R%')\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE Country = 'Spain' AND CustomerName LIKE 'G%' OR CustomerName LIKE 'R%'\")" + ] + }, + { + "cell_type": "code", + "execution_count": 171, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT * FROM Customers WHERE NOT Country = 'Spain'\n", + "(1, 'Alfreds Futterkiste', 'Maria Anders', 'Obere Str. 57', 'Berlin', '12209', 'Germany')\n", + "(2, 'Ana Trujillo Emparedados y helados', 'Ana Trujillo', 'Avda. de la Constitución 2222', 'México D.F.', '5021', 'Mexico')\n", + "(3, 'Antonio Moreno Taquería', 'Antonio Moreno', 'Mataderos 2312', 'México D.F.', '5023', 'Mexico')\n", + "(4, 'Around the Horn', 'Thomas Hardy', '120 Hanover Sq.', 'London', 'WA1 1DP', 'UK')\n", + "(5, 'Berglunds snabbköp', 'Christina Berglund', 'Berguvsvägen 8', 'Luleå', 'S-958 22', 'Sweden')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE CustomerName NOT LIKE 'A%'\n", + "(5, 'Berglunds snabbköp', 'Christina Berglund', 'Berguvsvägen 8', 'Luleå', 'S-958 22', 'Sweden')\n", + "(6, 'Blauer See Delikatessen', 'Hanna Moos', 'Forsterstr. 57', 'Mannheim', '68306', 'Germany')\n", + "(7, 'Blondel père et fils', 'Frédérique Citeaux', '24, place Kléber', 'Strasbourg', '67000', 'France')\n", + "(8, 'Bólido Comidas preparadas', 'Martín Sommer', 'C/ Araquil, 67', 'Madrid', '28023', 'Spain')\n", + "(9, \"Bon app''\", 'Laurence Lebihans', '12, rue des Bouchers', 'Marseille', '13008', 'France')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE CustomerID NOT BETWEEN 10 AND 60\n", + "(1, 'Alfreds Futterkiste', 'Maria Anders', 'Obere Str. 57', 'Berlin', '12209', 'Germany')\n", + "(2, 'Ana Trujillo Emparedados y helados', 'Ana Trujillo', 'Avda. de la Constitución 2222', 'México D.F.', '5021', 'Mexico')\n", + "(3, 'Antonio Moreno Taquería', 'Antonio Moreno', 'Mataderos 2312', 'México D.F.', '5023', 'Mexico')\n", + "(4, 'Around the Horn', 'Thomas Hardy', '120 Hanover Sq.', 'London', 'WA1 1DP', 'UK')\n", + "(5, 'Berglunds snabbköp', 'Christina Berglund', 'Berguvsvägen 8', 'Luleå', 'S-958 22', 'Sweden')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE City NOT IN ('Paris', 'London')\n", + "(1, 'Alfreds Futterkiste', 'Maria Anders', 'Obere Str. 57', 'Berlin', '12209', 'Germany')\n", + "(2, 'Ana Trujillo Emparedados y helados', 'Ana Trujillo', 'Avda. de la Constitución 2222', 'México D.F.', '5021', 'Mexico')\n", + "(3, 'Antonio Moreno Taquería', 'Antonio Moreno', 'Mataderos 2312', 'México D.F.', '5023', 'Mexico')\n", + "(5, 'Berglunds snabbköp', 'Christina Berglund', 'Berguvsvägen 8', 'Luleå', 'S-958 22', 'Sweden')\n", + "(6, 'Blauer See Delikatessen', 'Hanna Moos', 'Forsterstr. 57', 'Mannheim', '68306', 'Germany')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE NOT CustomerID > 50\n", + "(1, 'Alfreds Futterkiste', 'Maria Anders', 'Obere Str. 57', 'Berlin', '12209', 'Germany')\n", + "(2, 'Ana Trujillo Emparedados y helados', 'Ana Trujillo', 'Avda. de la Constitución 2222', 'México D.F.', '5021', 'Mexico')\n", + "(3, 'Antonio Moreno Taquería', 'Antonio Moreno', 'Mataderos 2312', 'México D.F.', '5023', 'Mexico')\n", + "(4, 'Around the Horn', 'Thomas Hardy', '120 Hanover Sq.', 'London', 'WA1 1DP', 'UK')\n", + "(5, 'Berglunds snabbköp', 'Christina Berglund', 'Berguvsvägen 8', 'Luleå', 'S-958 22', 'Sweden')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE NOT CustomerId < 50\n", + "(50, 'Maison Dewey', 'Catherine Dewey', 'Rue Joseph-Bens 532', 'Bruxelles', 'B-1180', 'Belgium')\n", + "(51, 'Mère Paillarde', 'Jean Fresnière', '43 rue St. Laurent', 'Montréal', 'H1J 1C3', 'Canada')\n", + "(52, 'Morgenstern Gesundkost', 'Alexander Feuer', 'Heerstr. 22', 'Leipzig', '4179', 'Germany')\n", + "(53, 'North/South', 'Simon Crowther', 'South House 300 Queensbridge', 'London', 'SW7 1RZ', 'UK')\n", + "(54, 'Océano Atlántico Ltda.', 'Yvonne Moncada', 'Ing. Gustavo Moncada 8585 Piso 20-A', 'Buenos Aires', '1010', 'Argentina')\n", + "...\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Not\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE NOT Country = 'Spain'\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE CustomerName NOT LIKE 'A%'\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE CustomerID NOT BETWEEN 10 AND 60\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE City NOT IN ('Paris', 'London')\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE NOT CustomerID > 50\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE NOT CustomerId < 50\")" + ] + }, + { + "cell_type": "code", + "execution_count": 172, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "INSERT INTO Customers (CustomerName, ContactName, Address, City, PostalCode, Country) \n", + " VALUES ('Cardinal', 'Tom B. Erichsen', 'Skagen 21', 'Stavanger', '4006', 'Norway')\n", + "--------------------------------------------------\n", + "INSERT INTO Customers (CustomerName, City, Country)\n", + " VALUES ('Cardinal', 'Stavanger', 'Norway')\n", + "--------------------------------------------------\n", + "INSERT INTO Customers (CustomerName, ContactName, Address, City, PostalCode, Country)\n", + " VALUES\n", + " ('Cardinal', 'Tom B. Erichsen', 'Skagen 21', 'Stavanger', '4006', 'Norway'),\n", + " ('Greasy Burger', 'Per Olsen', 'Gateveien 15', 'Sandnes', '4306', 'Norway'),\n", + " ('Tasty Tee', 'Finn Egan', 'Streetroad 19B', 'Liverpool', 'L1 0AA', 'UK')\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Insert Into\n", + "execute_and_print_query(\"\"\"INSERT INTO Customers (CustomerName, ContactName, Address, City, PostalCode, Country) \n", + " VALUES ('Cardinal', 'Tom B. Erichsen', 'Skagen 21', 'Stavanger', '4006', 'Norway')\"\"\")\n", + "execute_and_print_query(\"\"\"INSERT INTO Customers (CustomerName, City, Country)\n", + " VALUES ('Cardinal', 'Stavanger', 'Norway')\"\"\")\n", + "execute_and_print_query(\"\"\"INSERT INTO Customers (CustomerName, ContactName, Address, City, PostalCode, Country)\n", + " VALUES\n", + " ('Cardinal', 'Tom B. Erichsen', 'Skagen 21', 'Stavanger', '4006', 'Norway'),\n", + " ('Greasy Burger', 'Per Olsen', 'Gateveien 15', 'Sandnes', '4306', 'Norway'),\n", + " ('Tasty Tee', 'Finn Egan', 'Streetroad 19B', 'Liverpool', 'L1 0AA', 'UK')\"\"\")" + ] + }, + { + "cell_type": "code", + "execution_count": 173, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT CustomerName, ContactName, Address FROM Customers WHERE Address IS NULL\n", + "('Cardinal', None, None)\n", + "--------------------------------------------------\n", + "SELECT CustomerName, ContactName, Address FROM Customers WHERE Address IS NOT NULL\n", + "('Alfreds Futterkiste', 'Maria Anders', 'Obere Str. 57')\n", + "('Ana Trujillo Emparedados y helados', 'Ana Trujillo', 'Avda. de la Constitución 2222')\n", + "('Antonio Moreno Taquería', 'Antonio Moreno', 'Mataderos 2312')\n", + "('Around the Horn', 'Thomas Hardy', '120 Hanover Sq.')\n", + "('Berglunds snabbköp', 'Christina Berglund', 'Berguvsvägen 8')\n", + "...\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Null Values\n", + "execute_and_print_query(\"SELECT CustomerName, ContactName, Address FROM Customers WHERE Address IS NULL\")\n", + "execute_and_print_query(\"SELECT CustomerName, ContactName, Address FROM Customers WHERE Address IS NOT NULL\")" + ] + }, + { + "cell_type": "code", + "execution_count": 174, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "UPDATE Customers\n", + " SET ContactName = 'Alfred Schmidt', City= 'Frankfurt'\n", + " WHERE CustomerID = 1\n", + "--------------------------------------------------\n", + "UPDATE Customers\n", + " SET ContactName='Juan'\n", + " WHERE Country='Mexico'\n", + "--------------------------------------------------\n", + "UPDATE Customers SET ContactName='Juan'\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Update\n", + "execute_and_print_query(\"\"\"UPDATE Customers\n", + " SET ContactName = 'Alfred Schmidt', City= 'Frankfurt'\n", + " WHERE CustomerID = 1\"\"\")\n", + "execute_and_print_query(\"\"\"UPDATE Customers\n", + " SET ContactName='Juan'\n", + " WHERE Country='Mexico'\"\"\")\n", + "# WARN! Where가 없으면 모든 로우가 업데이트\n", + "execute_and_print_query(\"\"\"UPDATE Customers SET ContactName='Juan'\"\"\")" + ] + }, + { + "cell_type": "code", + "execution_count": 175, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DELETE FROM Customers WHERE CustomerName='Alfreds Futterkiste'\n", + "--------------------------------------------------\n", + "DELETE FROM Customers\n", + "--------------------------------------------------\n", + "DROP TABLE Customers\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Delete\n", + "execute_and_print_query(\"DELETE FROM Customers WHERE CustomerName='Alfreds Futterkiste'\")\n", + "# 테이블의 모든 로우 삭제\n", + "execute_and_print_query(\"DELETE FROM Customers\")\n", + "# 테이블 자체까지 삭제\n", + "execute_and_print_query(\"DROP TABLE Customers\")\n", + "\n", + "init_db()" + ] + }, + { + "cell_type": "code", + "execution_count": 176, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT * FROM Customers LIMIT 3\n", + "(1, 'Alfreds Futterkiste', 'Maria Anders', 'Obere Str. 57', 'Berlin', '12209', 'Germany')\n", + "(2, 'Ana Trujillo Emparedados y helados', 'Ana Trujillo', 'Avda. de la Constitución 2222', 'México D.F.', '5021', 'Mexico')\n", + "(3, 'Antonio Moreno Taquería', 'Antonio Moreno', 'Mataderos 2312', 'México D.F.', '5023', 'Mexico')\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE Country = 'Germany' LIMIT 3\n", + "(1, 'Alfreds Futterkiste', 'Maria Anders', 'Obere Str. 57', 'Berlin', '12209', 'Germany')\n", + "(6, 'Blauer See Delikatessen', 'Hanna Moos', 'Forsterstr. 57', 'Mannheim', '68306', 'Germany')\n", + "(17, 'Drachenblut Delikatessend', 'Sven Ottlieb', 'Walserweg 21', 'Aachen', '52066', 'Germany')\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers ORDER BY CustomerName DESC LIMIT 3\n", + "(91, 'Wolski', 'Zbyszek', 'ul. Filtrowa 68', 'Walla', '01-012', 'Poland')\n", + "(90, 'Wilman Kala', 'Matti Karttunen', 'Keskuskatu 45', 'Helsinki', '21240', 'Finland')\n", + "(89, 'White Clover Markets', 'Karl Jablonski', '305 - 14th Ave. S. Suite 3B', 'Seattle', '98128', 'USA')\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Select Top\n", + "# Top은 SQLite에서 지원되지 않음\n", + "# execute_and_print_query(\"SELECT TOP 3 * FROM Customers\")\n", + "# 대신 LIMIT 사용\n", + "execute_and_print_query(\"SELECT * FROM Customers LIMIT 3\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE Country = 'Germany' LIMIT 3\")\n", + "execute_and_print_query(\"SELECT * FROM Customers ORDER BY CustomerName DESC LIMIT 3\")" + ] + }, + { + "cell_type": "code", + "execution_count": 177, + "metadata": {}, + "outputs": [], + "source": [ + "# SQL Aggregate Functions\n", + "# MIN, MAX, COUNT, SUM, AVG\n", + "# 보통 Group By와 함께 쓰인다" + ] + }, + { + "cell_type": "code", + "execution_count": 178, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT MIN(Price) FROM Products\n", + "(2.5,)\n", + "--------------------------------------------------\n", + "SELECT MAX(Price) FROM Products\n", + "(263.5,)\n", + "--------------------------------------------------\n", + "SELECT MIN(Price) AS SmallestPrice FROM Products\n", + "(2.5,)\n", + "--------------------------------------------------\n", + "SELECT MIN(Price) AS SmallestPrice, CategoryID FROM Products GROUP BY CategoryID\n", + "(4.5, 1)\n", + "(10, 2)\n", + "(9.2, 3)\n", + "(2.5, 4)\n", + "(7, 5)\n", + "...\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Min and Max\n", + "execute_and_print_query(\"SELECT MIN(Price) FROM Products\")\n", + "execute_and_print_query(\"SELECT MAX(Price) FROM Products\")\n", + "execute_and_print_query(\"SELECT MIN(Price) AS SmallestPrice FROM Products\")\n", + "execute_and_print_query(\"SELECT MIN(Price) AS SmallestPrice, CategoryID FROM Products GROUP BY CategoryID\")" + ] + }, + { + "cell_type": "code", + "execution_count": 179, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT COUNT(*) FROM Products\n", + "(77,)\n", + "--------------------------------------------------\n", + "SELECT COUNT(ProductName) FROM Products\n", + "(77,)\n", + "--------------------------------------------------\n", + "SELECT COUNT(ProductID) FROM Products WHERE Price > 20\n", + "(37,)\n", + "--------------------------------------------------\n", + "SELECT COUNT(DISTINCT Price) FROM Products\n", + "(62,)\n", + "--------------------------------------------------\n", + "SELECT COUNT(*) AS [Number of records] FROM Products\n", + "(77,)\n", + "--------------------------------------------------\n", + "SELECT COUNT(*) AS [Number of records], CategoryID FROM Products GROUP BY CategoryID\n", + "(12, 1)\n", + "(12, 2)\n", + "(13, 3)\n", + "(10, 4)\n", + "(7, 5)\n", + "...\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Count\n", + "execute_and_print_query(\"SELECT COUNT(*) FROM Products\")\n", + "execute_and_print_query(\"SELECT COUNT(ProductName) FROM Products\")\n", + "execute_and_print_query(\"SELECT COUNT(ProductID) FROM Products WHERE Price > 20\")\n", + "execute_and_print_query(\"SELECT COUNT(DISTINCT Price) FROM Products\")\n", + "# []를 쓰면 \"\" 없이도 칼럼명에 공백이 가능!\n", + "execute_and_print_query(\"SELECT COUNT(*) AS [Number of records] FROM Products\")\n", + "execute_and_print_query(\"SELECT COUNT(*) AS [Number of records], CategoryID FROM Products GROUP BY CategoryID\")" + ] + }, + { + "cell_type": "code", + "execution_count": 180, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT SUM(Quantity) FROM OrderDetails\n", + "(12743,)\n", + "--------------------------------------------------\n", + "SELECT SUM(Quantity) FROM OrderDetails WHERE ProductId = 11\n", + "(182,)\n", + "--------------------------------------------------\n", + "SELECT SUM(Quantity) AS total FROM OrderDetails\n", + "(12743,)\n", + "--------------------------------------------------\n", + "SELECT OrderID, SUM(Quantity) AS [Total Quantity] FROM OrderDetails GROUP BY OrderID\n", + "(10248, 27)\n", + "(10249, 49)\n", + "(10250, 60)\n", + "(10251, 41)\n", + "(10252, 105)\n", + "...\n", + "--------------------------------------------------\n", + "SELECT SUM(Quantity * 10) FROM OrderDetails\n", + "(127430,)\n", + "--------------------------------------------------\n", + "SELECT SUM(Price * Quantity) FROM OrderDetails LEFT JOIN Products ON OrderDetails.ProductID = Products.ProductID\n", + "(386424.23,)\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Sum\n", + "execute_and_print_query(\"SELECT SUM(Quantity) FROM OrderDetails\")\n", + "execute_and_print_query(\"SELECT SUM(Quantity) FROM OrderDetails WHERE ProductId = 11\")\n", + "execute_and_print_query(\"SELECT SUM(Quantity) AS total FROM OrderDetails\")\n", + "execute_and_print_query(\"SELECT OrderID, SUM(Quantity) AS [Total Quantity] FROM OrderDetails GROUP BY OrderID\")\n", + "execute_and_print_query(\"SELECT SUM(Quantity * 10) FROM OrderDetails\")\n", + "execute_and_print_query(\"SELECT SUM(Price * Quantity) FROM OrderDetails LEFT JOIN Products ON OrderDetails.ProductID = Products.ProductID\")" + ] + }, + { + "cell_type": "code", + "execution_count": 181, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT AVG(Price) FROM Products\n", + "(28.866363636363637,)\n", + "--------------------------------------------------\n", + "SELECT AVG(Price) FROM Products WHERE CategoryID = 1\n", + "(37.979166666666664,)\n", + "--------------------------------------------------\n", + "SELECT AVG(Price) AS [average price] FROM Products\n", + "(28.866363636363637,)\n", + "--------------------------------------------------\n", + "SELECT * FROM Products WHERE price > (SELECT AVG(price) FROM Products)\n", + "(7, \"Uncle Bob's Organic Dried Pears\", 3, 7, '12 - 1 lb pkgs.', 30)\n", + "(8, 'Northwoods Cranberry Sauce', 3, 2, '12 - 12 oz jars', 40)\n", + "(9, 'Mishi Kobe Niku', 4, 6, '18 - 500 g pkgs.', 97)\n", + "(10, 'Ikura', 4, 8, '12 - 200 ml jars', 31)\n", + "(12, 'Queso Manchego La Pastora', 5, 4, '10 - 500 g pkgs.', 38)\n", + "...\n", + "--------------------------------------------------\n", + "SELECT AVG(Price) AS AveragePrice, CategoryID FROM Products GROUP BY CategoryID\n", + "(37.979166666666664, 1)\n", + "(23.0625, 2)\n", + "(25.16, 3)\n", + "(28.73, 4)\n", + "(20.25, 5)\n", + "...\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Avg\n", + "execute_and_print_query(\"SELECT AVG(Price) FROM Products\")\n", + "execute_and_print_query(\"SELECT AVG(Price) FROM Products WHERE CategoryID = 1\")\n", + "execute_and_print_query(\"SELECT AVG(Price) AS [average price] FROM Products\")\n", + "execute_and_print_query(\"SELECT * FROM Products WHERE price > (SELECT AVG(price) FROM Products)\")\n", + "execute_and_print_query(\"SELECT AVG(Price) AS AveragePrice, CategoryID FROM Products GROUP BY CategoryID\")" + ] + }, + { + "cell_type": "code", + "execution_count": 182, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT * FROM Customers WHERE CustomerName LIKE 'a%'\n", + "(1, 'Alfreds Futterkiste', 'Maria Anders', 'Obere Str. 57', 'Berlin', '12209', 'Germany')\n", + "(2, 'Ana Trujillo Emparedados y helados', 'Ana Trujillo', 'Avda. de la Constitución 2222', 'México D.F.', '5021', 'Mexico')\n", + "(3, 'Antonio Moreno Taquería', 'Antonio Moreno', 'Mataderos 2312', 'México D.F.', '5023', 'Mexico')\n", + "(4, 'Around the Horn', 'Thomas Hardy', '120 Hanover Sq.', 'London', 'WA1 1DP', 'UK')\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE city LIKE 'L_nd__'\n", + "(4, 'Around the Horn', 'Thomas Hardy', '120 Hanover Sq.', 'London', 'WA1 1DP', 'UK')\n", + "(11, \"B''s Beverages\", 'Victoria Ashworth', 'Fauntleroy Circus', 'London', 'EC2 5NT', 'UK')\n", + "(16, 'Consolidated Holdings', 'Elizabeth Brown', 'Berkeley Gardens 12 Brewery', 'London', 'WX1 6LT', 'UK')\n", + "(19, 'Eastern Connection', 'Ann Devon', '35 King George', 'London', 'WX3 6FW', 'UK')\n", + "(53, 'North/South', 'Simon Crowther', 'South House 300 Queensbridge', 'London', 'SW7 1RZ', 'UK')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE city LIKE '%L%'\n", + "(1, 'Alfreds Futterkiste', 'Maria Anders', 'Obere Str. 57', 'Berlin', '12209', 'Germany')\n", + "(4, 'Around the Horn', 'Thomas Hardy', '120 Hanover Sq.', 'London', 'WA1 1DP', 'UK')\n", + "(5, 'Berglunds snabbköp', 'Christina Berglund', 'Berguvsvägen 8', 'Luleå', 'S-958 22', 'Sweden')\n", + "(9, \"Bon app''\", 'Laurence Lebihans', '12, rue des Bouchers', 'Marseille', '13008', 'France')\n", + "(11, \"B''s Beverages\", 'Victoria Ashworth', 'Fauntleroy Circus', 'London', 'EC2 5NT', 'UK')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE CustomerName LIKE 'La%'\n", + "(40, \"La corne d''abondance\", 'Daniel Tonini', \"67, avenue de l''Europe\", 'Versailles', '78000', 'France')\n", + "(41, \"La maison d''Asie\", 'Annette Roulet', '1 rue Alsace-Lorraine', 'Toulouse', '31000', 'France')\n", + "(42, 'Laughing Bacchus Wine Cellars', 'Yoshi Tannamuri', '1900 Oak St.', 'Vancouver', 'V3F 2K1', 'Canada')\n", + "(43, 'Lazy K Kountry Store', 'John Steel', '12 Orchestra Terrace', 'Walla Walla', '99362', 'USA')\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE CustomerName LIKE 'a%' OR CustomerName LIKE 'b%'\n", + "(1, 'Alfreds Futterkiste', 'Maria Anders', 'Obere Str. 57', 'Berlin', '12209', 'Germany')\n", + "(2, 'Ana Trujillo Emparedados y helados', 'Ana Trujillo', 'Avda. de la Constitución 2222', 'México D.F.', '5021', 'Mexico')\n", + "(3, 'Antonio Moreno Taquería', 'Antonio Moreno', 'Mataderos 2312', 'México D.F.', '5023', 'Mexico')\n", + "(4, 'Around the Horn', 'Thomas Hardy', '120 Hanover Sq.', 'London', 'WA1 1DP', 'UK')\n", + "(5, 'Berglunds snabbköp', 'Christina Berglund', 'Berguvsvägen 8', 'Luleå', 'S-958 22', 'Sweden')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE CustomerName LIKE '%a'\n", + "(3, 'Antonio Moreno Taquería', 'Antonio Moreno', 'Mataderos 2312', 'México D.F.', '5023', 'Mexico')\n", + "(13, 'Centro comercial Moctezuma', 'Francisco Chang', 'Sierras de Granada 9993', 'México D.F.', '5022', 'Mexico')\n", + "(30, 'Godos Cocina Típica', 'José Pedro Freyre', 'C/ Romero, 33', 'Sevilla', '41101', 'Spain')\n", + "(61, 'Que Delícia', 'Bernardo Batista', 'Rua da Panificadora, 12', 'Rio de Janeiro', '02389-673', 'Brazil')\n", + "(62, 'Queen Cozinha', 'Lúcia Carvalho', 'Alameda dos Canàrios, 891', 'São Paulo', '05487-020', 'Brazil')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE CustomerName LIKE 'b%s'\n", + "(7, 'Blondel père et fils', 'Frédérique Citeaux', '24, place Kléber', 'Strasbourg', '67000', 'France')\n", + "(8, 'Bólido Comidas preparadas', 'Martín Sommer', 'C/ Araquil, 67', 'Madrid', '28023', 'Spain')\n", + "(11, \"B''s Beverages\", 'Victoria Ashworth', 'Fauntleroy Circus', 'London', 'EC2 5NT', 'UK')\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE CustomerName LIKE '%or%'\n", + "(3, 'Antonio Moreno Taquería', 'Antonio Moreno', 'Mataderos 2312', 'México D.F.', '5023', 'Mexico')\n", + "(4, 'Around the Horn', 'Thomas Hardy', '120 Hanover Sq.', 'London', 'WA1 1DP', 'UK')\n", + "(36, 'Hungry Coyote Import Store', 'Yoshi Latimer', 'City Center Plaza 516 Main St.', 'Elgin', '97827', 'USA')\n", + "(40, \"La corne d''abondance\", 'Daniel Tonini', \"67, avenue de l''Europe\", 'Versailles', '78000', 'France')\n", + "(43, 'Lazy K Kountry Store', 'John Steel', '12 Orchestra Terrace', 'Walla Walla', '99362', 'USA')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE CustomerName LIKE 'a__%'\n", + "(1, 'Alfreds Futterkiste', 'Maria Anders', 'Obere Str. 57', 'Berlin', '12209', 'Germany')\n", + "(2, 'Ana Trujillo Emparedados y helados', 'Ana Trujillo', 'Avda. de la Constitución 2222', 'México D.F.', '5021', 'Mexico')\n", + "(3, 'Antonio Moreno Taquería', 'Antonio Moreno', 'Mataderos 2312', 'México D.F.', '5023', 'Mexico')\n", + "(4, 'Around the Horn', 'Thomas Hardy', '120 Hanover Sq.', 'London', 'WA1 1DP', 'UK')\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE CustomerName LIKE '_r%'\n", + "(4, 'Around the Horn', 'Thomas Hardy', '120 Hanover Sq.', 'London', 'WA1 1DP', 'UK')\n", + "(17, 'Drachenblut Delikatessend', 'Sven Ottlieb', 'Walserweg 21', 'Aachen', '52066', 'Germany')\n", + "(20, 'Ernst Handel', 'Roland Mendel', 'Kirchgasse 6', 'Graz', '8010', 'Austria')\n", + "(25, 'Frankenversand', 'Peter Franken', 'Berliner Platz 43', 'München', '80805', 'Germany')\n", + "(26, 'France restauration', 'Carine Schmitt', '54, rue Royale', 'Nantes', '44000', 'France')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE Country LIKE 'Spain'\n", + "(8, 'Bólido Comidas preparadas', 'Martín Sommer', 'C/ Araquil, 67', 'Madrid', '28023', 'Spain')\n", + "(22, 'FISSA Fabrica Inter. Salchichas S.A.', 'Diego Roel', 'C/ Moralzarzal, 86', 'Madrid', '28034', 'Spain')\n", + "(29, 'Galería del gastrónomo', 'Eduardo Saavedra', 'Rambla de Cataluña, 23', 'Barcelona', '8022', 'Spain')\n", + "(30, 'Godos Cocina Típica', 'José Pedro Freyre', 'C/ Romero, 33', 'Sevilla', '41101', 'Spain')\n", + "(69, 'Romero y tomillo', 'Alejandra Camino', 'Gran Vía, 1', 'Madrid', '28001', 'Spain')\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Like\n", + "# %: 0개 이상의 캐릭터\n", + "# _: 정확히 1개 캐릭터\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE CustomerName LIKE 'a%'\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE city LIKE 'L_nd__'\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE city LIKE '%L%'\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE CustomerName LIKE 'La%'\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE CustomerName LIKE 'a%' OR CustomerName LIKE 'b%'\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE CustomerName LIKE '%a'\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE CustomerName LIKE 'b%s'\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE CustomerName LIKE '%or%'\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE CustomerName LIKE 'a__%'\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE CustomerName LIKE '_r%'\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE Country LIKE 'Spain'\")" + ] + }, + { + "cell_type": "code", + "execution_count": 183, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT * FROM Customers WHERE CustomerName LIKE 'a%'\n", + "(1, 'Alfreds Futterkiste', 'Maria Anders', 'Obere Str. 57', 'Berlin', '12209', 'Germany')\n", + "(2, 'Ana Trujillo Emparedados y helados', 'Ana Trujillo', 'Avda. de la Constitución 2222', 'México D.F.', '5021', 'Mexico')\n", + "(3, 'Antonio Moreno Taquería', 'Antonio Moreno', 'Mataderos 2312', 'México D.F.', '5023', 'Mexico')\n", + "(4, 'Around the Horn', 'Thomas Hardy', '120 Hanover Sq.', 'London', 'WA1 1DP', 'UK')\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE CustomerName LIKE '%es'\n", + "(11, \"B''s Beverages\", 'Victoria Ashworth', 'Fauntleroy Circus', 'London', 'EC2 5NT', 'UK')\n", + "(23, 'Folies gourmandes', 'Martine Rancé', '184, chaussée de Tournai', 'Lille', '59000', 'France')\n", + "(31, 'Gourmet Lanchonetes', 'André Fonseca', 'Av. Brasil, 442', 'Campinas', '04876-786', 'Brazil')\n", + "(34, 'Hanari Carnes', 'Mario Pontes', 'Rua do Paço, 67', 'Rio de Janeiro', '05454-876', 'Brazil')\n", + "(47, 'LINO-Delicateses', 'Felipe Izquierdo', 'Ave. 5 de Mayo Porlamar', 'I. de Margarita', '4980', 'Venezuela')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE CustomerName LIKE '%mer%'\n", + "(13, 'Centro comercial Moctezuma', 'Francisco Chang', 'Sierras de Granada 9993', 'México D.F.', '5022', 'Mexico')\n", + "(46, 'LILA-Supermercado', 'Carlos González', 'Carrera 52 con Ave. Bolívar #65-98 Llano Largo', 'Barquisimeto', '3508', 'Venezuela')\n", + "(69, 'Romero y tomillo', 'Alejandra Camino', 'Gran Vía, 1', 'Madrid', '28001', 'Spain')\n", + "(81, 'Tradição Hipermercados', 'Anabela Domingues', 'Av. Inês de Castro, 414', 'São Paulo', '05634-030', 'Brazil')\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE City LIKE '_ondon'\n", + "(4, 'Around the Horn', 'Thomas Hardy', '120 Hanover Sq.', 'London', 'WA1 1DP', 'UK')\n", + "(11, \"B''s Beverages\", 'Victoria Ashworth', 'Fauntleroy Circus', 'London', 'EC2 5NT', 'UK')\n", + "(16, 'Consolidated Holdings', 'Elizabeth Brown', 'Berkeley Gardens 12 Brewery', 'London', 'WX1 6LT', 'UK')\n", + "(19, 'Eastern Connection', 'Ann Devon', '35 King George', 'London', 'WX3 6FW', 'UK')\n", + "(53, 'North/South', 'Simon Crowther', 'South House 300 Queensbridge', 'London', 'SW7 1RZ', 'UK')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE City LIKE 'L___on'\n", + "(4, 'Around the Horn', 'Thomas Hardy', '120 Hanover Sq.', 'London', 'WA1 1DP', 'UK')\n", + "(11, \"B''s Beverages\", 'Victoria Ashworth', 'Fauntleroy Circus', 'London', 'EC2 5NT', 'UK')\n", + "(16, 'Consolidated Holdings', 'Elizabeth Brown', 'Berkeley Gardens 12 Brewery', 'London', 'WX1 6LT', 'UK')\n", + "(19, 'Eastern Connection', 'Ann Devon', '35 King George', 'London', 'WX3 6FW', 'UK')\n", + "(53, 'North/South', 'Simon Crowther', 'South House 300 Queensbridge', 'London', 'SW7 1RZ', 'UK')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE CustomerName LIKE 'a__%'\n", + "(1, 'Alfreds Futterkiste', 'Maria Anders', 'Obere Str. 57', 'Berlin', '12209', 'Germany')\n", + "(2, 'Ana Trujillo Emparedados y helados', 'Ana Trujillo', 'Avda. de la Constitución 2222', 'México D.F.', '5021', 'Mexico')\n", + "(3, 'Antonio Moreno Taquería', 'Antonio Moreno', 'Mataderos 2312', 'México D.F.', '5023', 'Mexico')\n", + "(4, 'Around the Horn', 'Thomas Hardy', '120 Hanover Sq.', 'London', 'WA1 1DP', 'UK')\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE CustomerName LIKE '_r%'\n", + "(4, 'Around the Horn', 'Thomas Hardy', '120 Hanover Sq.', 'London', 'WA1 1DP', 'UK')\n", + "(17, 'Drachenblut Delikatessend', 'Sven Ottlieb', 'Walserweg 21', 'Aachen', '52066', 'Germany')\n", + "(20, 'Ernst Handel', 'Roland Mendel', 'Kirchgasse 6', 'Graz', '8010', 'Austria')\n", + "(25, 'Frankenversand', 'Peter Franken', 'Berliner Platz 43', 'München', '80805', 'Germany')\n", + "(26, 'France restauration', 'Carine Schmitt', '54, rue Royale', 'Nantes', '44000', 'France')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE Country LIKE 'Spain'\n", + "(8, 'Bólido Comidas preparadas', 'Martín Sommer', 'C/ Araquil, 67', 'Madrid', '28023', 'Spain')\n", + "(22, 'FISSA Fabrica Inter. Salchichas S.A.', 'Diego Roel', 'C/ Moralzarzal, 86', 'Madrid', '28034', 'Spain')\n", + "(29, 'Galería del gastrónomo', 'Eduardo Saavedra', 'Rambla de Cataluña, 23', 'Barcelona', '8022', 'Spain')\n", + "(30, 'Godos Cocina Típica', 'José Pedro Freyre', 'C/ Romero, 33', 'Sevilla', '41101', 'Spain')\n", + "(69, 'Romero y tomillo', 'Alejandra Camino', 'Gran Vía, 1', 'Madrid', '28001', 'Spain')\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Wildcards\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE CustomerName LIKE 'a%'\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE CustomerName LIKE '%es'\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE CustomerName LIKE '%mer%'\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE City LIKE '_ondon'\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE City LIKE 'L___on'\")\n", + "# not supported in mysql, postgres\n", + "# execute_and_print_query(\"SELECT * FROM Customers WHERE CustomerName LIKE '[bsp]%'\")\n", + "# execute_and_print_query(\"SELECT * FROM Customers WHERE CustomerName LIKE '[a-f]%'\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE CustomerName LIKE 'a__%'\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE CustomerName LIKE '_r%'\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE Country LIKE 'Spain'\")" + ] + }, + { + "cell_type": "code", + "execution_count": 184, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT * FROM Customers WHERE Country IN ('Germany', 'France', 'UK')\n", + "(1, 'Alfreds Futterkiste', 'Maria Anders', 'Obere Str. 57', 'Berlin', '12209', 'Germany')\n", + "(4, 'Around the Horn', 'Thomas Hardy', '120 Hanover Sq.', 'London', 'WA1 1DP', 'UK')\n", + "(6, 'Blauer See Delikatessen', 'Hanna Moos', 'Forsterstr. 57', 'Mannheim', '68306', 'Germany')\n", + "(7, 'Blondel père et fils', 'Frédérique Citeaux', '24, place Kléber', 'Strasbourg', '67000', 'France')\n", + "(9, \"Bon app''\", 'Laurence Lebihans', '12, rue des Bouchers', 'Marseille', '13008', 'France')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE Country NOT IN ('Germany', 'France', 'UK')\n", + "(2, 'Ana Trujillo Emparedados y helados', 'Ana Trujillo', 'Avda. de la Constitución 2222', 'México D.F.', '5021', 'Mexico')\n", + "(3, 'Antonio Moreno Taquería', 'Antonio Moreno', 'Mataderos 2312', 'México D.F.', '5023', 'Mexico')\n", + "(5, 'Berglunds snabbköp', 'Christina Berglund', 'Berguvsvägen 8', 'Luleå', 'S-958 22', 'Sweden')\n", + "(8, 'Bólido Comidas preparadas', 'Martín Sommer', 'C/ Araquil, 67', 'Madrid', '28023', 'Spain')\n", + "(10, 'Bottom-Dollar Marketse', 'Elizabeth Lincoln', '23 Tsawassen Blvd.', 'Tsawassen', 'T2F 8M4', 'Canada')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE CustomerID IN (SELECT CustomerID FROM Orders)\n", + "(2, 'Ana Trujillo Emparedados y helados', 'Ana Trujillo', 'Avda. de la Constitución 2222', 'México D.F.', '5021', 'Mexico')\n", + "(3, 'Antonio Moreno Taquería', 'Antonio Moreno', 'Mataderos 2312', 'México D.F.', '5023', 'Mexico')\n", + "(4, 'Around the Horn', 'Thomas Hardy', '120 Hanover Sq.', 'London', 'WA1 1DP', 'UK')\n", + "(5, 'Berglunds snabbköp', 'Christina Berglund', 'Berguvsvägen 8', 'Luleå', 'S-958 22', 'Sweden')\n", + "(7, 'Blondel père et fils', 'Frédérique Citeaux', '24, place Kléber', 'Strasbourg', '67000', 'France')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE CustomerID NOT IN (SELECT CustomerID FROM Orders)\n", + "(1, 'Alfreds Futterkiste', 'Maria Anders', 'Obere Str. 57', 'Berlin', '12209', 'Germany')\n", + "(6, 'Blauer See Delikatessen', 'Hanna Moos', 'Forsterstr. 57', 'Mannheim', '68306', 'Germany')\n", + "(12, 'Cactus Comidas para llevar', 'Patricio Simpson', 'Cerrito 333', 'Buenos Aires', '1010', 'Argentina')\n", + "(22, 'FISSA Fabrica Inter. Salchichas S.A.', 'Diego Roel', 'C/ Moralzarzal, 86', 'Madrid', '28034', 'Spain')\n", + "(26, 'France restauration', 'Carine Schmitt', '54, rue Royale', 'Nantes', '44000', 'France')\n", + "...\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL In\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE Country IN ('Germany', 'France', 'UK')\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE Country NOT IN ('Germany', 'France', 'UK')\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE CustomerID IN (SELECT CustomerID FROM Orders)\")\n", + "execute_and_print_query(\"SELECT * FROM Customers WHERE CustomerID NOT IN (SELECT CustomerID FROM Orders)\")" + ] + }, + { + "cell_type": "code", + "execution_count": 185, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT * FROM Products WHERE Price BETWEEN 10 AND 20\n", + "(1, 'Chais', 1, 1, '10 boxes x 20 bags', 18)\n", + "(2, 'Chang', 1, 1, '24 - 12 oz bottles', 19)\n", + "(3, 'Aniseed Syrup', 1, 2, '12 - 550 ml bottles', 10)\n", + "(15, 'Genen Shouyu', 6, 2, '24 - 250 ml bottles', 15.5)\n", + "(16, 'Pavlova', 7, 3, '32 - 500 g boxes', 17.45)\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Products WHERE Price NOT BETWEEN 10 AND 20\n", + "(4, \"Chef Anton's Cajun Seasoning\", 2, 2, '48 - 6 oz jars', 22)\n", + "(5, \"Chef Anton's Gumbo Mix\", 2, 2, '36 boxes', 21.35)\n", + "(6, \"Grandma's Boysenberry Spread\", 3, 2, '12 - 8 oz jars', 25)\n", + "(7, \"Uncle Bob's Organic Dried Pears\", 3, 7, '12 - 1 lb pkgs.', 30)\n", + "(8, 'Northwoods Cranberry Sauce', 3, 2, '12 - 12 oz jars', 40)\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Products WHERE Price BETWEEN 10 AND 20 AND CategoryID IN (1,2,3)\n", + "(1, 'Chais', 1, 1, '10 boxes x 20 bags', 18)\n", + "(2, 'Chang', 1, 1, '24 - 12 oz bottles', 19)\n", + "(3, 'Aniseed Syrup', 1, 2, '12 - 550 ml bottles', 10)\n", + "(15, 'Genen Shouyu', 6, 2, '24 - 250 ml bottles', 15.5)\n", + "(16, 'Pavlova', 7, 3, '32 - 500 g boxes', 17.45)\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Products WHERE ProductName BETWEEN 'Carnarvon Tigers' AND 'Mozzarella di Giovanni' ORDER BY ProductName\n", + "(18, 'Carnarvon Tigers', 7, 8, '16 kg pkg.', 62.5)\n", + "(1, 'Chais', 1, 1, '10 boxes x 20 bags', 18)\n", + "(2, 'Chang', 1, 1, '24 - 12 oz bottles', 19)\n", + "(39, 'Chartreuse verte', 18, 1, '750 cc per bottle', 18)\n", + "(4, \"Chef Anton's Cajun Seasoning\", 2, 2, '48 - 6 oz jars', 22)\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Products WHERE ProductName BETWEEN \"Carnarvon Tigers\" AND \"Chef Anton's Cajun Seasoning\" ORDER BY ProductName\n", + "(18, 'Carnarvon Tigers', 7, 8, '16 kg pkg.', 62.5)\n", + "(1, 'Chais', 1, 1, '10 boxes x 20 bags', 18)\n", + "(2, 'Chang', 1, 1, '24 - 12 oz bottles', 19)\n", + "(39, 'Chartreuse verte', 18, 1, '750 cc per bottle', 18)\n", + "(4, \"Chef Anton's Cajun Seasoning\", 2, 2, '48 - 6 oz jars', 22)\n", + "--------------------------------------------------\n", + "SELECT * FROM Products WHERE ProductName NOT BETWEEN 'Carnarvon Tigers' AND 'Mozzarella di Giovanni' ORDER BY ProductName\n", + "(17, 'Alice Mutton', 7, 6, '20 - 1 kg tins', 39)\n", + "(3, 'Aniseed Syrup', 1, 2, '12 - 550 ml bottles', 10)\n", + "(40, 'Boston Crab Meat', 19, 8, '24 - 4 oz tins', 18.4)\n", + "(60, 'Camembert Pierrot', 28, 4, '15 - 300 g rounds', 34)\n", + "(30, 'Nord-Ost Matjeshering', 13, 8, '10 - 200 g glasses', 25.89)\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Orders WHERE OrderDate BETWEEN '1996-07-01' AND '1996-07-31'\n", + "(10248, 90, 5, '1996-07-04', 3)\n", + "(10249, 81, 6, '1996-07-05', 1)\n", + "(10250, 34, 4, '1996-07-08', 2)\n", + "(10251, 84, 3, '1996-07-08', 1)\n", + "(10252, 76, 4, '1996-07-09', 2)\n", + "...\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Between\n", + "execute_and_print_query(\"SELECT * FROM Products WHERE Price BETWEEN 10 AND 20\")\n", + "execute_and_print_query(\"SELECT * FROM Products WHERE Price NOT BETWEEN 10 AND 20\")\n", + "execute_and_print_query(\"SELECT * FROM Products WHERE Price BETWEEN 10 AND 20 AND CategoryID IN (1,2,3)\")\n", + "execute_and_print_query(\"SELECT * FROM Products WHERE ProductName BETWEEN 'Carnarvon Tigers' AND 'Mozzarella di Giovanni' ORDER BY ProductName\")\n", + "execute_and_print_query(\"\"\"SELECT * FROM Products WHERE ProductName BETWEEN \"Carnarvon Tigers\" AND \"Chef Anton's Cajun Seasoning\" ORDER BY ProductName\"\"\")\n", + "execute_and_print_query(\"SELECT * FROM Products WHERE ProductName NOT BETWEEN 'Carnarvon Tigers' AND 'Mozzarella di Giovanni' ORDER BY ProductName\")\n", + "# Not supported in mysql\n", + "# execute_and_print_query(\"SELECT * FROM Orders WHERE OrderDate BETWEEN #07/01/1996# AND #07/31/1996#\")\n", + "execute_and_print_query(\"SELECT * FROM Orders WHERE OrderDate BETWEEN '1996-07-01' AND '1996-07-31'\")" + ] + }, + { + "cell_type": "code", + "execution_count": 186, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT CustomerID AS ID FROM Customers\n", + "(1,)\n", + "(2,)\n", + "(3,)\n", + "(4,)\n", + "(5,)\n", + "...\n", + "--------------------------------------------------\n", + "SELECT CustomerID ID FROM Customers\n", + "(1,)\n", + "(2,)\n", + "(3,)\n", + "(4,)\n", + "(5,)\n", + "...\n", + "--------------------------------------------------\n", + "SELECT CustomerID AS ID, CustomerName AS Customer FROM Customers\n", + "(1, 'Alfreds Futterkiste')\n", + "(2, 'Ana Trujillo Emparedados y helados')\n", + "(3, 'Antonio Moreno Taquería')\n", + "(4, 'Around the Horn')\n", + "(5, 'Berglunds snabbköp')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT ProductName AS [My Great Products] FROM Products\n", + "('Chais',)\n", + "('Chang',)\n", + "('Aniseed Syrup',)\n", + "(\"Chef Anton's Cajun Seasoning\",)\n", + "(\"Chef Anton's Gumbo Mix\",)\n", + "...\n", + "--------------------------------------------------\n", + "SELECT ProductName AS \"My Great Products\" FROM Products\n", + "('Chais',)\n", + "('Chang',)\n", + "('Aniseed Syrup',)\n", + "(\"Chef Anton's Cajun Seasoning\",)\n", + "(\"Chef Anton's Gumbo Mix\",)\n", + "...\n", + "--------------------------------------------------\n", + "SELECT CustomerName, CONCAT(Address,', ',PostalCode,', ',City,', ',Country) AS Address FROM Customers\n", + "('Alfreds Futterkiste', 'Obere Str. 57, 12209, Berlin, Germany')\n", + "('Ana Trujillo Emparedados y helados', 'Avda. de la Constitución 2222, 5021, México D.F., Mexico')\n", + "('Antonio Moreno Taquería', 'Mataderos 2312, 5023, México D.F., Mexico')\n", + "('Around the Horn', '120 Hanover Sq., WA1 1DP, London, UK')\n", + "('Berglunds snabbköp', 'Berguvsvägen 8, S-958 22, Luleå, Sweden')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers AS Persons\n", + "(1, 'Alfreds Futterkiste', 'Maria Anders', 'Obere Str. 57', 'Berlin', '12209', 'Germany')\n", + "(2, 'Ana Trujillo Emparedados y helados', 'Ana Trujillo', 'Avda. de la Constitución 2222', 'México D.F.', '5021', 'Mexico')\n", + "(3, 'Antonio Moreno Taquería', 'Antonio Moreno', 'Mataderos 2312', 'México D.F.', '5023', 'Mexico')\n", + "(4, 'Around the Horn', 'Thomas Hardy', '120 Hanover Sq.', 'London', 'WA1 1DP', 'UK')\n", + "(5, 'Berglunds snabbköp', 'Christina Berglund', 'Berguvsvägen 8', 'Luleå', 'S-958 22', 'Sweden')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT o.OrderID, o.OrderDate, c.CustomerName FROM Customers AS c, Orders AS o WHERE c.CustomerName='Around the Horn' AND c.CustomerID=o.CustomerID\n", + "(10355, '1996-11-15', 'Around the Horn')\n", + "(10383, '1996-12-16', 'Around the Horn')\n", + "--------------------------------------------------\n", + "SELECT Orders.OrderID, Orders.OrderDate, Customers.CustomerName FROM Customers, Orders WHERE Customers.CustomerName='Around the Horn' AND Customers.CustomerID=Orders.CustomerID\n", + "(10355, '1996-11-15', 'Around the Horn')\n", + "(10383, '1996-12-16', 'Around the Horn')\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Aliases\n", + "execute_and_print_query(\"SELECT CustomerID AS ID FROM Customers\")\n", + "execute_and_print_query(\"SELECT CustomerID ID FROM Customers\")\n", + "execute_and_print_query(\"SELECT CustomerID AS ID, CustomerName AS Customer FROM Customers\")\n", + "execute_and_print_query(\"SELECT ProductName AS [My Great Products] FROM Products\")\n", + "execute_and_print_query('SELECT ProductName AS \"My Great Products\" FROM Products')\n", + "execute_and_print_query(\"SELECT CustomerName, CONCAT(Address,', ',PostalCode,', ',City,', ',Country) AS Address FROM Customers\")\n", + "execute_and_print_query(\"SELECT * FROM Customers AS Persons\")\n", + "execute_and_print_query(\"SELECT o.OrderID, o.OrderDate, c.CustomerName FROM Customers AS c, Orders AS o WHERE c.CustomerName='Around the Horn' AND c.CustomerID=o.CustomerID\")\n", + "execute_and_print_query(\"SELECT Orders.OrderID, Orders.OrderDate, Customers.CustomerName FROM Customers, Orders WHERE Customers.CustomerName='Around the Horn' AND Customers.CustomerID=Orders.CustomerID\")" + ] + }, + { + "cell_type": "code", + "execution_count": 187, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT Orders.OrderID, Customers.CustomerName, Orders.OrderDate\n", + "FROM Orders\n", + "INNER JOIN Customers ON Orders.CustomerID=Customers.CustomerID\n", + "(10248, 'Wilman Kala', '1996-07-04')\n", + "(10249, 'Tradição Hipermercados', '1996-07-05')\n", + "(10250, 'Hanari Carnes', '1996-07-08')\n", + "(10251, 'Victuailles en stock', '1996-07-08')\n", + "(10252, 'Suprêmes délices', '1996-07-09')\n", + "...\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Joins\n", + "execute_and_print_query(\"\"\"SELECT Orders.OrderID, Customers.CustomerName, Orders.OrderDate\n", + "FROM Orders\n", + "INNER JOIN Customers ON Orders.CustomerID=Customers.CustomerID\"\"\")" + ] + }, + { + "cell_type": "code", + "execution_count": 188, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT ProductID, ProductName, CategoryName\n", + "FROM Products\n", + "INNER JOIN Categories ON Products.CategoryID = Categories.CategoryID\n", + "(1, 'Chais', 'Beverages')\n", + "(2, 'Chang', 'Beverages')\n", + "(3, 'Aniseed Syrup', 'Condiments')\n", + "(4, \"Chef Anton's Cajun Seasoning\", 'Condiments')\n", + "(5, \"Chef Anton's Gumbo Mix\", 'Condiments')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT Products.ProductID, Products.ProductName, Categories.CategoryName\n", + "FROM Products\n", + "INNER JOIN Categories ON Products.CategoryID = Categories.CategoryID\n", + "(1, 'Chais', 'Beverages')\n", + "(2, 'Chang', 'Beverages')\n", + "(3, 'Aniseed Syrup', 'Condiments')\n", + "(4, \"Chef Anton's Cajun Seasoning\", 'Condiments')\n", + "(5, \"Chef Anton's Gumbo Mix\", 'Condiments')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT Products.ProductID, Products.ProductName, Categories.CategoryName\n", + "FROM Products\n", + "JOIN Categories ON Products.CategoryID = Categories.CategoryID\n", + "(1, 'Chais', 'Beverages')\n", + "(2, 'Chang', 'Beverages')\n", + "(3, 'Aniseed Syrup', 'Condiments')\n", + "(4, \"Chef Anton's Cajun Seasoning\", 'Condiments')\n", + "(5, \"Chef Anton's Gumbo Mix\", 'Condiments')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT Orders.OrderID, Customers.CustomerName, Shippers.ShipperName\n", + "FROM ((Orders\n", + "INNER JOIN Customers ON Orders.CustomerID = Customers.CustomerID)\n", + "INNER JOIN Shippers ON Orders.ShipperID = Shippers.ShipperID)\n", + "(10248, 'Wilman Kala', 'Federal Shipping')\n", + "(10249, 'Tradição Hipermercados', 'Speedy Express')\n", + "(10250, 'Hanari Carnes', 'United Package')\n", + "(10251, 'Victuailles en stock', 'Speedy Express')\n", + "(10252, 'Suprêmes délices', 'United Package')\n", + "...\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Inner Join\n", + "# 그냥 join이랑 inner join이랑 같다.\n", + "execute_and_print_query(\"\"\"SELECT ProductID, ProductName, CategoryName\n", + "FROM Products\n", + "INNER JOIN Categories ON Products.CategoryID = Categories.CategoryID\"\"\")\n", + "execute_and_print_query(\"\"\"SELECT Products.ProductID, Products.ProductName, Categories.CategoryName\n", + "FROM Products\n", + "INNER JOIN Categories ON Products.CategoryID = Categories.CategoryID\"\"\")\n", + "execute_and_print_query(\"\"\"SELECT Products.ProductID, Products.ProductName, Categories.CategoryName\n", + "FROM Products\n", + "JOIN Categories ON Products.CategoryID = Categories.CategoryID\"\"\")\n", + "execute_and_print_query(\"\"\"SELECT Orders.OrderID, Customers.CustomerName, Shippers.ShipperName\n", + "FROM ((Orders\n", + "INNER JOIN Customers ON Orders.CustomerID = Customers.CustomerID)\n", + "INNER JOIN Shippers ON Orders.ShipperID = Shippers.ShipperID)\"\"\")" + ] + }, + { + "cell_type": "code", + "execution_count": 189, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT Customers.CustomerName, Orders.OrderID\n", + "FROM Customers\n", + "LEFT JOIN Orders ON Customers.CustomerID = Orders.CustomerID\n", + "ORDER BY Customers.CustomerName\n", + "('Alfreds Futterkiste', None)\n", + "('Ana Trujillo Emparedados y helados', 10308)\n", + "('Antonio Moreno Taquería', 10365)\n", + "('Around the Horn', 10355)\n", + "('Around the Horn', 10383)\n", + "(\"B''s Beverages\", 10289)\n", + "('Berglunds snabbköp', 10278)\n", + "('Berglunds snabbköp', 10280)\n", + "('Berglunds snabbköp', 10384)\n", + "('Blauer See Delikatessen', None)\n", + "...\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Left Join\n", + "execute_and_print_query(\"\"\"SELECT Customers.CustomerName, Orders.OrderID\n", + "FROM Customers\n", + "LEFT JOIN Orders ON Customers.CustomerID = Orders.CustomerID\n", + "ORDER BY Customers.CustomerName\"\"\", limit=10)" + ] + }, + { + "cell_type": "code", + "execution_count": 190, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT Orders.OrderID, Employees.LastName, Employees.FirstName\n", + "FROM Orders\n", + "RIGHT JOIN Employees ON Orders.EmployeeID = Employees.EmployeeID\n", + "ORDER BY Orders.OrderID\n", + "(None, 'West', 'Adam')\n", + "(10248, 'Buchanan', 'Steven')\n", + "(10249, 'Suyama', 'Michael')\n", + "(10250, 'Peacock', 'Margaret')\n", + "(10251, 'Leverling', 'Janet')\n", + "(10252, 'Peacock', 'Margaret')\n", + "(10253, 'Leverling', 'Janet')\n", + "(10254, 'Buchanan', 'Steven')\n", + "(10255, 'Dodsworth', 'Anne')\n", + "(10256, 'Leverling', 'Janet')\n", + "(10257, 'Peacock', 'Margaret')\n", + "(10258, 'Davolio', 'Nancy')\n", + "(10259, 'Peacock', 'Margaret')\n", + "(10260, 'Peacock', 'Margaret')\n", + "(10261, 'Peacock', 'Margaret')\n", + "(10262, 'Callahan', 'Laura')\n", + "(10263, 'Dodsworth', 'Anne')\n", + "(10264, 'Suyama', 'Michael')\n", + "(10265, 'Fuller', 'Andrew')\n", + "(10266, 'Leverling', 'Janet')\n", + "(10267, 'Peacock', 'Margaret')\n", + "(10268, 'Callahan', 'Laura')\n", + "(10269, 'Buchanan', 'Steven')\n", + "(10270, 'Davolio', 'Nancy')\n", + "(10271, 'Suyama', 'Michael')\n", + "(10272, 'Suyama', 'Michael')\n", + "(10273, 'Leverling', 'Janet')\n", + "(10274, 'Suyama', 'Michael')\n", + "(10275, 'Davolio', 'Nancy')\n", + "(10276, 'Callahan', 'Laura')\n", + "(10277, 'Fuller', 'Andrew')\n", + "(10278, 'Callahan', 'Laura')\n", + "(10279, 'Callahan', 'Laura')\n", + "(10280, 'Fuller', 'Andrew')\n", + "(10281, 'Peacock', 'Margaret')\n", + "(10282, 'Peacock', 'Margaret')\n", + "(10283, 'Leverling', 'Janet')\n", + "(10284, 'Peacock', 'Margaret')\n", + "(10285, 'Davolio', 'Nancy')\n", + "(10286, 'Callahan', 'Laura')\n", + "(10287, 'Callahan', 'Laura')\n", + "(10288, 'Peacock', 'Margaret')\n", + "(10289, 'King', 'Robert')\n", + "(10290, 'Callahan', 'Laura')\n", + "(10291, 'Suyama', 'Michael')\n", + "(10292, 'Davolio', 'Nancy')\n", + "(10293, 'Davolio', 'Nancy')\n", + "(10294, 'Peacock', 'Margaret')\n", + "(10295, 'Fuller', 'Andrew')\n", + "(10296, 'Suyama', 'Michael')\n", + "(10297, 'Buchanan', 'Steven')\n", + "(10298, 'Suyama', 'Michael')\n", + "(10299, 'Peacock', 'Margaret')\n", + "(10300, 'Fuller', 'Andrew')\n", + "(10301, 'Callahan', 'Laura')\n", + "(10302, 'Peacock', 'Margaret')\n", + "(10303, 'King', 'Robert')\n", + "(10304, 'Davolio', 'Nancy')\n", + "(10305, 'Callahan', 'Laura')\n", + "(10306, 'Davolio', 'Nancy')\n", + "(10307, 'Fuller', 'Andrew')\n", + "(10308, 'King', 'Robert')\n", + "(10309, 'Leverling', 'Janet')\n", + "(10310, 'Callahan', 'Laura')\n", + "(10311, 'Davolio', 'Nancy')\n", + "(10312, 'Fuller', 'Andrew')\n", + "(10313, 'Fuller', 'Andrew')\n", + "(10314, 'Davolio', 'Nancy')\n", + "(10315, 'Peacock', 'Margaret')\n", + "(10316, 'Davolio', 'Nancy')\n", + "(10317, 'Suyama', 'Michael')\n", + "(10318, 'Callahan', 'Laura')\n", + "(10319, 'King', 'Robert')\n", + "(10320, 'Buchanan', 'Steven')\n", + "(10321, 'Leverling', 'Janet')\n", + "(10322, 'King', 'Robert')\n", + "(10323, 'Peacock', 'Margaret')\n", + "(10324, 'Dodsworth', 'Anne')\n", + "(10325, 'Davolio', 'Nancy')\n", + "(10326, 'Peacock', 'Margaret')\n", + "(10327, 'Fuller', 'Andrew')\n", + "(10328, 'Peacock', 'Margaret')\n", + "(10329, 'Peacock', 'Margaret')\n", + "(10330, 'Leverling', 'Janet')\n", + "(10331, 'Dodsworth', 'Anne')\n", + "(10332, 'Leverling', 'Janet')\n", + "(10333, 'Buchanan', 'Steven')\n", + "(10334, 'Callahan', 'Laura')\n", + "(10335, 'King', 'Robert')\n", + "(10336, 'King', 'Robert')\n", + "(10337, 'Peacock', 'Margaret')\n", + "(10338, 'Peacock', 'Margaret')\n", + "(10339, 'Fuller', 'Andrew')\n", + "(10340, 'Davolio', 'Nancy')\n", + "(10341, 'King', 'Robert')\n", + "(10342, 'Peacock', 'Margaret')\n", + "(10343, 'Peacock', 'Margaret')\n", + "(10344, 'Peacock', 'Margaret')\n", + "(10345, 'Fuller', 'Andrew')\n", + "(10346, 'Leverling', 'Janet')\n", + "...\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Right Join\n", + "execute_and_print_query(\"\"\"SELECT Orders.OrderID, Employees.LastName, Employees.FirstName\n", + "FROM Orders\n", + "RIGHT JOIN Employees ON Orders.EmployeeID = Employees.EmployeeID\n", + "ORDER BY Orders.OrderID\"\"\", limit=100)" + ] + }, + { + "cell_type": "code", + "execution_count": 191, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT Customers.CustomerName, Orders.OrderID\n", + "FROM Customers\n", + "FULL OUTER JOIN Orders ON Customers.CustomerID=Orders.CustomerID\n", + "ORDER BY Customers.CustomerName\n", + "('Alfreds Futterkiste', None)\n", + "('Ana Trujillo Emparedados y helados', 10308)\n", + "('Antonio Moreno Taquería', 10365)\n", + "('Around the Horn', 10355)\n", + "('Around the Horn', 10383)\n", + "(\"B''s Beverages\", 10289)\n", + "('Berglunds snabbköp', 10278)\n", + "('Berglunds snabbköp', 10280)\n", + "('Berglunds snabbköp', 10384)\n", + "('Blauer See Delikatessen', None)\n", + "('Blondel père et fils', 10265)\n", + "('Blondel père et fils', 10297)\n", + "('Blondel père et fils', 10360)\n", + "('Blondel père et fils', 10436)\n", + "(\"Bon app''\", 10331)\n", + "(\"Bon app''\", 10340)\n", + "(\"Bon app''\", 10362)\n", + "('Bottom-Dollar Marketse', 10389)\n", + "('Bottom-Dollar Marketse', 10410)\n", + "('Bottom-Dollar Marketse', 10411)\n", + "('Bottom-Dollar Marketse', 10431)\n", + "('Bólido Comidas preparadas', 10326)\n", + "('Cactus Comidas para llevar', None)\n", + "('Centro comercial Moctezuma', 10259)\n", + "('Chop-suey Chinese', 10254)\n", + "('Chop-suey Chinese', 10370)\n", + "('Comércio Mineiro', 10290)\n", + "('Consolidated Holdings', 10435)\n", + "('Die Wandernde Kuh', 10301)\n", + "('Die Wandernde Kuh', 10312)\n", + "('Die Wandernde Kuh', 10348)\n", + "('Die Wandernde Kuh', 10356)\n", + "('Drachenblut Delikatessend', 10363)\n", + "('Drachenblut Delikatessend', 10391)\n", + "('Du monde entier', 10311)\n", + "('Eastern Connection', 10364)\n", + "('Eastern Connection', 10400)\n", + "('Ernst Handel', 10258)\n", + "('Ernst Handel', 10263)\n", + "('Ernst Handel', 10351)\n", + "('Ernst Handel', 10368)\n", + "('Ernst Handel', 10382)\n", + "('Ernst Handel', 10390)\n", + "('Ernst Handel', 10402)\n", + "('Ernst Handel', 10403)\n", + "('Ernst Handel', 10430)\n", + "('Ernst Handel', 10442)\n", + "('FISSA Fabrica Inter. Salchichas S.A.', None)\n", + "('Familia Arquibaldo', 10347)\n", + "('Familia Arquibaldo', 10386)\n", + "('Familia Arquibaldo', 10414)\n", + "('Folies gourmandes', 10408)\n", + "('Folk och fä HB', 10264)\n", + "('Folk och fä HB', 10327)\n", + "('Folk och fä HB', 10378)\n", + "('Folk och fä HB', 10434)\n", + "('France restauration', None)\n", + "('Franchi S.p.A.', 10422)\n", + "('Frankenversand', 10267)\n", + "('Frankenversand', 10337)\n", + "('Frankenversand', 10342)\n", + "('Frankenversand', 10396)\n", + "('Furia Bacalhau e Frutos do Mar', 10328)\n", + "('Furia Bacalhau e Frutos do Mar', 10352)\n", + "('GROSELLA-Restaurante', 10268)\n", + "('Galería del gastrónomo', 10366)\n", + "('Galería del gastrónomo', 10426)\n", + "('Godos Cocina Típica', 10303)\n", + "('Gourmet Lanchonetes', 10423)\n", + "('Great Lakes Food Market', None)\n", + "('HILARIÓN-Abastos', 10257)\n", + "('HILARIÓN-Abastos', 10395)\n", + "('Hanari Carnes', 10250)\n", + "('Hanari Carnes', 10253)\n", + "('Hungry Coyote Import Store', 10375)\n", + "('Hungry Coyote Import Store', 10394)\n", + "('Hungry Coyote Import Store', 10415)\n", + "('Hungry Owl All-Night Grocers', 10298)\n", + "('Hungry Owl All-Night Grocers', 10309)\n", + "('Hungry Owl All-Night Grocers', 10335)\n", + "('Hungry Owl All-Night Grocers', 10373)\n", + "('Hungry Owl All-Night Grocers', 10380)\n", + "('Hungry Owl All-Night Grocers', 10429)\n", + "('Island Trading', 10315)\n", + "('Island Trading', 10318)\n", + "('Island Trading', 10321)\n", + "('Königlich Essen', 10323)\n", + "('Königlich Essen', 10325)\n", + "('LILA-Supermercado', 10283)\n", + "('LILA-Supermercado', 10296)\n", + "('LILA-Supermercado', 10330)\n", + "('LILA-Supermercado', 10357)\n", + "('LILA-Supermercado', 10381)\n", + "('LINO-Delicateses', 10405)\n", + "(\"La corne d''abondance\", None)\n", + "(\"La maison d''Asie\", 10350)\n", + "(\"La maison d''Asie\", 10358)\n", + "(\"La maison d''Asie\", 10371)\n", + "(\"La maison d''Asie\", 10413)\n", + "(\"La maison d''Asie\", 10425)\n", + "...\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Full Join\n", + "execute_and_print_query(\"\"\"SELECT Customers.CustomerName, Orders.OrderID\n", + "FROM Customers\n", + "FULL OUTER JOIN Orders ON Customers.CustomerID=Orders.CustomerID\n", + "ORDER BY Customers.CustomerName\"\"\", limit=100)" + ] + }, + { + "cell_type": "code", + "execution_count": 192, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT A.CustomerName AS CustomerName1, B.CustomerName AS CustomerName2, A.City\n", + "FROM Customers A, Customers B\n", + "WHERE A.CustomerID <> B.CustomerID\n", + "AND A.City = B.City\n", + "ORDER BY A.City\n", + "('Cactus Comidas para llevar', 'Océano Atlántico Ltda.', 'Buenos Aires')\n", + "('Cactus Comidas para llevar', 'Rancho grande', 'Buenos Aires')\n", + "('Océano Atlántico Ltda.', 'Cactus Comidas para llevar', 'Buenos Aires')\n", + "('Océano Atlántico Ltda.', 'Rancho grande', 'Buenos Aires')\n", + "('Rancho grande', 'Cactus Comidas para llevar', 'Buenos Aires')\n", + "...\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Self Join\n", + "execute_and_print_query(\"\"\"SELECT A.CustomerName AS CustomerName1, B.CustomerName AS CustomerName2, A.City\n", + "FROM Customers A, Customers B\n", + "WHERE A.CustomerID <> B.CustomerID\n", + "AND A.City = B.City\n", + "ORDER BY A.City\"\"\")" + ] + }, + { + "cell_type": "code", + "execution_count": 193, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT City FROM Customers\n", + "UNION\n", + "SELECT City FROM Suppliers\n", + "ORDER BY City\n", + "\n", + "('Aachen',)\n", + "('Albuquerque',)\n", + "('Anchorage',)\n", + "('Ann Arbor',)\n", + "('Annecy',)\n", + "...\n", + "--------------------------------------------------\n", + "SELECT City FROM Customers\n", + "UNION ALL\n", + "SELECT City FROM Suppliers\n", + "ORDER BY City\n", + "\n", + "('Aachen',)\n", + "('Albuquerque',)\n", + "('Anchorage',)\n", + "('Ann Arbor',)\n", + "('Annecy',)\n", + "...\n", + "--------------------------------------------------\n", + "SELECT City, Country FROM Customers\n", + "WHERE Country='Germany'\n", + "UNION\n", + "SELECT City, Country FROM Suppliers\n", + "WHERE Country='Germany'\n", + "ORDER BY City\n", + "\n", + "('Aachen', 'Germany')\n", + "('Berlin', 'Germany')\n", + "('Brandenburg', 'Germany')\n", + "('Cunewalde', 'Germany')\n", + "('Cuxhaven', 'Germany')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT City, Country FROM Customers\n", + "WHERE Country='Germany'\n", + "UNION ALL\n", + "SELECT City, Country FROM Suppliers\n", + "WHERE Country='Germany'\n", + "ORDER BY City\n", + "('Aachen', 'Germany')\n", + "('Berlin', 'Germany')\n", + "('Berlin', 'Germany')\n", + "('Brandenburg', 'Germany')\n", + "('Cunewalde', 'Germany')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT 'Customer' AS Type, ContactName, City, Country\n", + "FROM Customers\n", + "UNION\n", + "SELECT 'Supplier', ContactName, City, Country\n", + "FROM Suppliers\n", + "('Customer', 'Alejandra Camino', 'Madrid', 'Spain')\n", + "('Customer', 'Alexander Feuer', 'Leipzig', 'Germany')\n", + "('Customer', 'Ana Trujillo', 'México D.F.', 'Mexico')\n", + "('Customer', 'Anabela Domingues', 'São Paulo', 'Brazil')\n", + "('Customer', 'André Fonseca', 'Campinas', 'Brazil')\n", + "...\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Union\n", + "execute_and_print_query(\"\"\"SELECT City FROM Customers\n", + "UNION\n", + "SELECT City FROM Suppliers\n", + "ORDER BY City\n", + "\"\"\")\n", + "execute_and_print_query(\"\"\"SELECT City FROM Customers\n", + "UNION ALL\n", + "SELECT City FROM Suppliers\n", + "ORDER BY City\n", + "\"\"\")\n", + "execute_and_print_query(\"\"\"SELECT City, Country FROM Customers\n", + "WHERE Country='Germany'\n", + "UNION\n", + "SELECT City, Country FROM Suppliers\n", + "WHERE Country='Germany'\n", + "ORDER BY City\n", + "\"\"\")\n", + "execute_and_print_query(\"\"\"SELECT City, Country FROM Customers\n", + "WHERE Country='Germany'\n", + "UNION ALL\n", + "SELECT City, Country FROM Suppliers\n", + "WHERE Country='Germany'\n", + "ORDER BY City\"\"\")\n", + "execute_and_print_query(\"\"\"SELECT 'Customer' AS Type, ContactName, City, Country\n", + "FROM Customers\n", + "UNION\n", + "SELECT 'Supplier', ContactName, City, Country\n", + "FROM Suppliers\"\"\")" + ] + }, + { + "cell_type": "code", + "execution_count": 194, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT COUNT(CustomerID), Country\n", + "FROM Customers\n", + "GROUP BY Country\n", + "(3, 'Argentina')\n", + "(2, 'Austria')\n", + "(2, 'Belgium')\n", + "(9, 'Brazil')\n", + "(3, 'Canada')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT COUNT(CustomerID), Country\n", + "FROM Customers\n", + "GROUP BY Country\n", + "ORDER BY COUNT(CustomerID) DESC\n", + "(13, 'USA')\n", + "(11, 'Germany')\n", + "(11, 'France')\n", + "(9, 'Brazil')\n", + "(7, 'UK')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT Shippers.ShipperName, COUNT(Orders.OrderID) AS NumberOfOrders FROM Orders\n", + "LEFT JOIN Shippers ON Orders.ShipperID = Shippers.ShipperID\n", + "GROUP BY ShipperName\n", + "('Federal Shipping', 68)\n", + "('Speedy Express', 54)\n", + "('United Package', 74)\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Group By\n", + "execute_and_print_query(\"\"\"SELECT COUNT(CustomerID), Country\n", + "FROM Customers\n", + "GROUP BY Country\"\"\")\n", + "execute_and_print_query(\"\"\"SELECT COUNT(CustomerID), Country\n", + "FROM Customers\n", + "GROUP BY Country\n", + "ORDER BY COUNT(CustomerID) DESC\"\"\")\n", + "execute_and_print_query(\"\"\"SELECT Shippers.ShipperName, COUNT(Orders.OrderID) AS NumberOfOrders FROM Orders\n", + "LEFT JOIN Shippers ON Orders.ShipperID = Shippers.ShipperID\n", + "GROUP BY ShipperName\"\"\")" + ] + }, + { + "cell_type": "code", + "execution_count": 195, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT COUNT(CustomerID), Country\n", + "FROM Customers\n", + "GROUP BY Country\n", + "HAVING COUNT(CustomerID) > 5\n", + "(9, 'Brazil')\n", + "(11, 'France')\n", + "(11, 'Germany')\n", + "(7, 'UK')\n", + "(13, 'USA')\n", + "--------------------------------------------------\n", + "SELECT COUNT(CustomerID), Country\n", + "FROM Customers\n", + "GROUP BY Country\n", + "HAVING COUNT(CustomerID) > 5\n", + "ORDER BY COUNT(CustomerID) DESC\n", + "(13, 'USA')\n", + "(11, 'Germany')\n", + "(11, 'France')\n", + "(9, 'Brazil')\n", + "(7, 'UK')\n", + "--------------------------------------------------\n", + "SELECT Employees.LastName, COUNT(Orders.OrderID) AS NumberOfOrders\n", + "FROM (Orders\n", + "INNER JOIN Employees ON Orders.EmployeeID = Employees.EmployeeID)\n", + "GROUP BY LastName\n", + "HAVING COUNT(Orders.OrderID) > 10\n", + "('Buchanan', 11)\n", + "('Callahan', 27)\n", + "('Davolio', 29)\n", + "('Fuller', 20)\n", + "('King', 14)\n", + "...\n", + "--------------------------------------------------\n", + "SELECT Employees.LastName, COUNT(Orders.OrderID) AS NumberOfOrders\n", + "FROM Orders\n", + "INNER JOIN Employees ON Orders.EmployeeID = Employees.EmployeeID\n", + "WHERE LastName = 'Davolio' OR LastName = 'Fuller'\n", + "GROUP BY LastName\n", + "HAVING COUNT(Orders.OrderID) > 25\n", + "('Davolio', 29)\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Having\n", + "execute_and_print_query(\"\"\"SELECT COUNT(CustomerID), Country\n", + "FROM Customers\n", + "GROUP BY Country\n", + "HAVING COUNT(CustomerID) > 5\"\"\")\n", + "execute_and_print_query(\"\"\"SELECT COUNT(CustomerID), Country\n", + "FROM Customers\n", + "GROUP BY Country\n", + "HAVING COUNT(CustomerID) > 5\n", + "ORDER BY COUNT(CustomerID) DESC\"\"\")\n", + "execute_and_print_query(\"\"\"SELECT Employees.LastName, COUNT(Orders.OrderID) AS NumberOfOrders\n", + "FROM (Orders\n", + "INNER JOIN Employees ON Orders.EmployeeID = Employees.EmployeeID)\n", + "GROUP BY LastName\n", + "HAVING COUNT(Orders.OrderID) > 10\"\"\")\n", + "execute_and_print_query(\"\"\"SELECT Employees.LastName, COUNT(Orders.OrderID) AS NumberOfOrders\n", + "FROM Orders\n", + "INNER JOIN Employees ON Orders.EmployeeID = Employees.EmployeeID\n", + "WHERE LastName = 'Davolio' OR LastName = 'Fuller'\n", + "GROUP BY LastName\n", + "HAVING COUNT(Orders.OrderID) > 25\"\"\")" + ] + }, + { + "cell_type": "code", + "execution_count": 196, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT SupplierName\n", + "FROM Suppliers\n", + "WHERE EXISTS (SELECT ProductName FROM Products WHERE Products.SupplierID = Suppliers.supplierID AND Price < 20)\n", + "('Exotic Liquid',)\n", + "('New Orleans Cajun Delights',)\n", + "('Tokyo Traders',)\n", + "(\"Mayumi's\",)\n", + "('Pavlova, Ltd.',)\n", + "...\n", + "--------------------------------------------------\n", + "SELECT SupplierName\n", + "FROM Suppliers\n", + "WHERE EXISTS (SELECT ProductName FROM Products WHERE Products.SupplierID = Suppliers.supplierID AND Price = 22)\n", + "('New Orleans Cajun Delights',)\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Exists\n", + "execute_and_print_query(\"\"\"SELECT SupplierName\n", + "FROM Suppliers\n", + "WHERE EXISTS (SELECT ProductName FROM Products WHERE Products.SupplierID = Suppliers.supplierID AND Price < 20)\"\"\")\n", + "execute_and_print_query(\"\"\"SELECT SupplierName\n", + "FROM Suppliers\n", + "WHERE EXISTS (SELECT ProductName FROM Products WHERE Products.SupplierID = Suppliers.supplierID AND Price = 22)\"\"\")" + ] + }, + { + "cell_type": "code", + "execution_count": 204, + "metadata": {}, + "outputs": [], + "source": [ + "# SQL Any, All\n", + "# not supported in sqlite\n", + "# execute_and_print_query(\"\"\"SELECT ProductName\n", + "# FROM Products\n", + "# WHERE ProductID = ANY(SELECT ProductID\n", + "# FROM OrderDetails\n", + "# WHERE Quantity = 10)\"\"\")\n", + "# execute_and_print_query(\"\"\"SELECT ProductName\n", + "# FROM Products\n", + "# WHERE ProductID = ANY(SELECT ProductID\n", + "# FROM OrderDetails\n", + "# WHERE Quantity > 99)\"\"\")\n", + "# execute_and_print_query(\"\"\"SELECT ProductName\n", + "# FROM Products\n", + "# WHERE ProductID = ANY(SELECT ProductID\n", + "# FROM OrderDetails\n", + "# WHERE Quantity > 1000)\"\"\")\n", + "# execute_and_print_query(\"\"\"SELECT ALL ProductName\n", + "# FROM Products\n", + "# WHERE TRUE\"\"\")\n", + "# execute_and_print_query(\"\"\"SELECT ProductName\n", + "# FROM Products\n", + "# WHERE ProductID = ALL\n", + "# (SELECT ProductID\n", + "# FROM OrderDetails\n", + "# WHERE Quantity = 10)\"\"\")" + ] + }, + { + "cell_type": "code", + "execution_count": 206, + "metadata": {}, + "outputs": [], + "source": [ + "# SQL Select Into\n", + "# not supported in sqlite\n", + "# execute_and_print_query(\"SELECT * INTO CustomersBackup2017 FROM Customers\")\n", + "# execute_and_print_query(\"SELECT * INTO CustomersBackup2017 IN 'Backup.mdb' FROM Customers\")\n", + "# execute_and_print_query(\"SELECT CustomerName, ContactName INTO CustomersBackup2017 FROM Customers\")\n", + "# execute_and_print_query(\"SELECT * INTO CustomersGermany FROM Customers WHERE Country = 'Germany'\")\n", + "# execute_and_print_query(\"\"\"SELECT Customers.CustomerName, Orders.OrderID\n", + "# INTO CustomersOrderBackup2017\n", + "# FROM Customers\n", + "# LEFT JOIN Orders ON Customers.CustomerID = Orders.CustomerID\"\"\")\n", + "# execute_and_print_query(\"\"\"SELECT * INTO newtable\n", + "# FROM oldtable\n", + "# WHERE 1 = 0\"\"\")" + ] + }, + { + "cell_type": "code", + "execution_count": 208, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "INSERT INTO Customers (CustomerName, City, Country)\n", + "SELECT SupplierName, City, Country FROM Suppliers\n", + "--------------------------------------------------\n", + "INSERT INTO Customers (CustomerName, ContactName, Address, City, PostalCode, Country)\n", + "SELECT SupplierName, ContactName, Address, City, PostalCode, Country FROM Suppliers\n", + "--------------------------------------------------\n", + "INSERT INTO Customers (CustomerName, City, Country)\n", + "SELECT SupplierName, City, Country FROM Suppliers\n", + "WHERE Country='Germany'\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Insert Into Select\n", + "execute_and_print_query(\"\"\"INSERT INTO Customers (CustomerName, City, Country)\n", + "SELECT SupplierName, City, Country FROM Suppliers\"\"\")\n", + "execute_and_print_query(\"\"\"INSERT INTO Customers (CustomerName, ContactName, Address, City, PostalCode, Country)\n", + "SELECT SupplierName, ContactName, Address, City, PostalCode, Country FROM Suppliers\"\"\")\n", + "execute_and_print_query(\"\"\"INSERT INTO Customers (CustomerName, City, Country)\n", + "SELECT SupplierName, City, Country FROM Suppliers\n", + "WHERE Country='Germany'\"\"\")" + ] + }, + { + "cell_type": "code", + "execution_count": 209, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SELECT OrderID, Quantity,\n", + "CASE\n", + " WHEN Quantity > 30 THEN 'The quantity is greater than 30'\n", + " WHEN Quantity = 30 THEN 'The quantity is 30'\n", + " ELSE 'The quantity is under 30'\n", + "END AS QuantityText\n", + "FROM OrderDetails\n", + "(10248, 12, 'The quantity is under 30')\n", + "(10248, 10, 'The quantity is under 30')\n", + "(10248, 5, 'The quantity is under 30')\n", + "(10249, 9, 'The quantity is under 30')\n", + "(10249, 40, 'The quantity is greater than 30')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT CustomerName, City, Country\n", + "FROM Customers\n", + "ORDER BY\n", + "(CASE\n", + " WHEN City IS NULL THEN Country\n", + " ELSE City\n", + "END)\n", + "('Drachenblut Delikatessend', 'Aachen', 'Germany')\n", + "('Rattlesnake Canyon Grocery', 'Albuquerque', 'USA')\n", + "('Old World Delicatessen', 'Anchorage', 'USA')\n", + "(\"Grandma Kelly's Homestead\", 'Ann Arbor', 'USA')\n", + "(\"Grandma Kelly's Homestead\", 'Ann Arbor', 'USA')\n", + "...\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Case\n", + "execute_and_print_query(\"\"\"SELECT OrderID, Quantity,\n", + "CASE\n", + " WHEN Quantity > 30 THEN 'The quantity is greater than 30'\n", + " WHEN Quantity = 30 THEN 'The quantity is 30'\n", + " ELSE 'The quantity is under 30'\n", + "END AS QuantityText\n", + "FROM OrderDetails\"\"\")\n", + "execute_and_print_query(\"\"\"SELECT CustomerName, City, Country\n", + "FROM Customers\n", + "ORDER BY\n", + "(CASE\n", + " WHEN City IS NULL THEN Country\n", + " ELSE City\n", + "END)\"\"\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 212, + "metadata": {}, + "outputs": [], + "source": [ + "# SQL Null Functions\n", + "# execute_and_print_query(\"SELECT ProductName, UnitPrice * (UnitsInStock + IFNULL(UnitsOnOrder, 0)) FROM Products\")\n", + "# execute_and_print_query(\"SELECT ProductName, UnitPrice * (UnitsInStock + COALESCE(UnitsOnOrder, 0)) FROM Products\")" + ] + }, + { + "cell_type": "code", + "execution_count": 214, + "metadata": {}, + "outputs": [], + "source": [ + "# SQL Stored Procedures\n", + "# not supported in sqlite\n", + "# execute_and_print_query(\"\"\"CREATE PROCEDURE SelectAllCustomers\n", + "# AS\n", + "# SELECT * FROM Customers\n", + "# GO\"\"\")\n", + "# execute_and_print_query(\"EXEC SelectAllCustomers\")\n", + "# execute_and_print_query(\"\"\"CREATE PROCEDURE SelectAllCustomers @City nvarchar(30)\n", + "# AS\n", + "# SELECT * FROM Customers WHERE City = @City\n", + "# GO\"\"\")\n", + "# execute_and_print_query(\"EXEC SelectAllCustomers @City = 'London'\")\n", + "# execute_and_print_query(\"\"\"CREATE PROCEDURE SelectAllCustomers @City nvarchar(30), @PostalCode nvarchar(10)\n", + "# AS\n", + "# SELECT * FROM Customers WHERE City = @City AND PostalCode = @PostalCode\n", + "# GO;\n", + "# \"\"\")\n", + "# execute_and_print_query(\"EXEC SelectAllCustomers @City = 'London', @PostalCode = 'WA1 1DP'\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 217, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-- Select all:\n", + "SELECT * FROM Customers\n", + "(1, 'Alfreds Futterkiste', 'Maria Anders', 'Obere Str. 57', 'Berlin', '12209', 'Germany')\n", + "(2, 'Ana Trujillo Emparedados y helados', 'Ana Trujillo', 'Avda. de la Constitución 2222', 'México D.F.', '5021', 'Mexico')\n", + "(3, 'Antonio Moreno Taquería', 'Antonio Moreno', 'Mataderos 2312', 'México D.F.', '5023', 'Mexico')\n", + "(4, 'Around the Horn', 'Thomas Hardy', '120 Hanover Sq.', 'London', 'WA1 1DP', 'UK')\n", + "(5, 'Berglunds snabbköp', 'Christina Berglund', 'Berguvsvägen 8', 'Luleå', 'S-958 22', 'Sweden')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers -- WHERE City='Berlin'\n", + "(1, 'Alfreds Futterkiste', 'Maria Anders', 'Obere Str. 57', 'Berlin', '12209', 'Germany')\n", + "(2, 'Ana Trujillo Emparedados y helados', 'Ana Trujillo', 'Avda. de la Constitución 2222', 'México D.F.', '5021', 'Mexico')\n", + "(3, 'Antonio Moreno Taquería', 'Antonio Moreno', 'Mataderos 2312', 'México D.F.', '5023', 'Mexico')\n", + "(4, 'Around the Horn', 'Thomas Hardy', '120 Hanover Sq.', 'London', 'WA1 1DP', 'UK')\n", + "(5, 'Berglunds snabbköp', 'Christina Berglund', 'Berguvsvägen 8', 'Luleå', 'S-958 22', 'Sweden')\n", + "...\n", + "--------------------------------------------------\n", + "-- SELECT * FROM Customers;\n", + "SELECT * FROM Products\n", + "(1, 'Chais', 1, 1, '10 boxes x 20 bags', 18)\n", + "(2, 'Chang', 1, 1, '24 - 12 oz bottles', 19)\n", + "(3, 'Aniseed Syrup', 1, 2, '12 - 550 ml bottles', 10)\n", + "(4, \"Chef Anton's Cajun Seasoning\", 2, 2, '48 - 6 oz jars', 22)\n", + "(5, \"Chef Anton's Gumbo Mix\", 2, 2, '36 boxes', 21.35)\n", + "...\n", + "--------------------------------------------------\n", + "/*Select all the columns\n", + "of all the records\n", + "in the Customers table:*/\n", + "SELECT * FROM Customers\n", + "(1, 'Alfreds Futterkiste', 'Maria Anders', 'Obere Str. 57', 'Berlin', '12209', 'Germany')\n", + "(2, 'Ana Trujillo Emparedados y helados', 'Ana Trujillo', 'Avda. de la Constitución 2222', 'México D.F.', '5021', 'Mexico')\n", + "(3, 'Antonio Moreno Taquería', 'Antonio Moreno', 'Mataderos 2312', 'México D.F.', '5023', 'Mexico')\n", + "(4, 'Around the Horn', 'Thomas Hardy', '120 Hanover Sq.', 'London', 'WA1 1DP', 'UK')\n", + "(5, 'Berglunds snabbköp', 'Christina Berglund', 'Berguvsvägen 8', 'Luleå', 'S-958 22', 'Sweden')\n", + "...\n", + "--------------------------------------------------\n", + "/*SELECT * FROM Customers;\n", + "SELECT * FROM Products;\n", + "SELECT * FROM Orders;\n", + "SELECT * FROM Categories;*/\n", + "SELECT * FROM Suppliers\n", + "(1, 'Exotic Liquid', 'Charlotte Cooper', '49 Gilbert St.', 'Londona', 'EC1 4SD', 'UK', '(171) 555-2222')\n", + "(2, 'New Orleans Cajun Delights', 'Shelley Burke', 'P.O. Box 78934', 'New Orleans', '70117', 'USA', '(100) 555-4822')\n", + "(3, \"Grandma Kelly's Homestead\", 'Regina Murphy', '707 Oxford Rd.', 'Ann Arbor', '48104', 'USA', '(313) 555-5735')\n", + "(4, 'Tokyo Traders', 'Yoshi Nagase', '9-8 Sekimai Musashino-shi', 'Tokyo', '100', 'Japan', '(03) 3555-5011')\n", + "(5, \"Cooperativa de Quesos 'Las Cabras'\", 'Antonio del Valle Saavedra', 'Calle del Rosal 4', 'Oviedo', '33007', 'Spain', '(98) 598 76 54')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT CustomerName, /*City,*/ Country FROM Customers\n", + "('Alfreds Futterkiste', 'Germany')\n", + "('Ana Trujillo Emparedados y helados', 'Mexico')\n", + "('Antonio Moreno Taquería', 'Mexico')\n", + "('Around the Horn', 'UK')\n", + "('Berglunds snabbköp', 'Sweden')\n", + "...\n", + "--------------------------------------------------\n", + "SELECT * FROM Customers WHERE (CustomerName LIKE 'L%'\n", + "OR CustomerName LIKE 'R%' /*OR CustomerName LIKE 'S%'\n", + "OR CustomerName LIKE 'T%'*/ OR CustomerName LIKE 'W%')\n", + "AND Country='USA'\n", + "ORDER BY CustomerName\n", + "(43, 'Lazy K Kountry Store', 'John Steel', '12 Orchestra Terrace', 'Walla Walla', '99362', 'USA')\n", + "(45, \"Let''s Stop N Shop\", 'Jaime Yorres', '87 Polk St. Suite 5', 'San Francisco', '94117', 'USA')\n", + "(48, 'Lonesome Pine Restaurant', 'Fran Wilson', '89 Chiaroscuro Rd.', 'Portland', '97219', 'USA')\n", + "(65, 'Rattlesnake Canyon Grocery', 'Paula Wilson', '2817 Milton Dr.', 'Albuquerque', '87110', 'USA')\n", + "(89, 'White Clover Markets', 'Karl Jablonski', '305 - 14th Ave. S. Suite 3B', 'Seattle', '98128', 'USA')\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "# SQL Comments\n", + "execute_and_print_query(\"\"\"-- Select all:\n", + "SELECT * FROM Customers\"\"\")\n", + "execute_and_print_query(\"\"\"SELECT * FROM Customers -- WHERE City='Berlin'\"\"\")\n", + "execute_and_print_query(\"\"\"-- SELECT * FROM Customers;\n", + "SELECT * FROM Products\"\"\")\n", + "execute_and_print_query(\"\"\"/*Select all the columns\n", + "of all the records\n", + "in the Customers table:*/\n", + "SELECT * FROM Customers\"\"\")\n", + "execute_and_print_query(\"\"\"/*SELECT * FROM Customers;\n", + "SELECT * FROM Products;\n", + "SELECT * FROM Orders;\n", + "SELECT * FROM Categories;*/\n", + "SELECT * FROM Suppliers\"\"\")\n", + "execute_and_print_query(\"SELECT CustomerName, /*City,*/ Country FROM Customers\")\n", + "execute_and_print_query(\"\"\"SELECT * FROM Customers WHERE (CustomerName LIKE 'L%'\n", + "OR CustomerName LIKE 'R%' /*OR CustomerName LIKE 'S%'\n", + "OR CustomerName LIKE 'T%'*/ OR CustomerName LIKE 'W%')\n", + "AND Country='USA'\n", + "ORDER BY CustomerName\"\"\")" + ] + }, + { + "cell_type": "code", + "execution_count": 128, + "metadata": {}, + "outputs": [], + "source": [ + "# SQL Operators" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/missions/W1/M3/README.md b/missions/W1/M3/README.md new file mode 100644 index 0000000..d0113ef --- /dev/null +++ b/missions/W1/M3/README.md @@ -0,0 +1,250 @@ +# GDP ETL Project + +The goal of this project is to implement ETL process for GDP data. + +## Business Requirements +The main purpose of this project is to collect GDP data from IMF website and transform it to a format that can be used for analysis. + +Common analysis use cases are as follows: +1. Filter countries by GDP +2. Top N countries by GDP +3. Group by region + + +### Contents +--- +- [GDP ETL Project](#gdp-etl-project) + - [Business Requirements](#business-requirements) + - [Contents](#contents) + - [Definition of ETL Process](#definition-of-etl-process) + - [1. Extract](#1-extract) + - [2. Transform](#2-transform) + - [3. Load](#3-load) + - [Implementation](#implementation) + - [ETL Process](#etl-process) + - [Modules](#modules) + - [**`importer.py`**](#importerpy) + - [**`exporter.py`**](#exporterpy) + - [**`logger.py`**](#loggerpy) + - [Utils](#utils) + - [**`create_country_region_table.py`**](#create_country_region_tablepy) + - [**`create_large_data_csv.py`**](#create_large_data_csvpy) + - [Performance Experiment](#performance-experiment) + - [Read CSV](#read-csv) + - [Pandas DataFrame](#pandas-dataframe) + - [Parallel/Distributed Processing](#paralleldistributed-processing) + - [Steps](#steps) + +## Definition of ETL Process + +### 1. Extract +- Parse html or read csv file. +- After extraction, the data should follow the format: + ```json + [ + { + "Country": "United States", + "GDP": "30,337,162", + "Region": "North America" + }, + ... + ] + ``` + +### 2. Transform +- Transform GDP value + 1. Convert GDP value string to float + 2. Convert GDP value to billion +- Sort data by GDP +- After transformation, the data should follow the format: + ```json + [ + { + "Country":"United States", + "GDP":30337.16, + "Region":"North America" + }, + ... + ] + ``` + +### 3. Load +- Export the data to a JSON file or sqlite database. +- For optimizing query performance, store the data in GDP order. + +--- + +## Implementation + +### ETL Process +1. **`etl_project_gdp.py`**: ETL process wiki web -> json +2. **`etl_project_gdp_with_sql.py`**: ETL process wiki web -> sqlite +3. **`etl_project_gdp_from_csv.py`**: ETL process csv -> sqlite +4. **`etl_project_gdp_parallel.py`**: ETL process with Parallel/Distributed Design + +### Modules + +#### **`importer.py`** +Extracts data from Wikipedia and saves it to a JSON file. + +Supported Data Source: +- Wikipedia +- CSV File + +Importer Class Hierarchy: +Seperate Interface and Implementation to support multiple data source. +- `ImporterInterface` + - `WebImporterInterface` + - `WikiWebImporter` + - `FileImporterInterface` + - `CsvFileImporter` + +--- + +#### **`exporter.py`** +Exports the data to a JSON file. + +Supported Export Target: +- JSON File +- SQLite Database(.db file) + +Exporter Class Hierarchy: +- `ExporterInterface` + - `JsonFileExporter` + - `SqliteExporter` + +--- + +#### **`logger.py`** +Logs the data to a file. + +Supported Log Level: +- info +- error + +--- + +### Utils + +#### **`create_country_region_table.py`** +Extracts country and region data from Wikipedia and saves it to a JSON file. + +Format: `{country: region}` + +#### **`create_large_data_csv.py`** +Generates a large CSV file for testing. + +--- +## Performance Experiment + +Test Data(Generated by `create_large_data_csv.py`): +- 10M row(260MB) +- 100M row(2.6GB) + +Environment: +- 32GB RAM +- CPU 10 core + +--- + +### Read CSV + +```python +# 10M: 4.51s +# 100M: 48.77s +df = pd.read_csv("large_data.csv") +``` + +If the file is too large to fit in memory, we should use `chunksize` parameter to read the file in chunks. + +```python +chunks = pd.read_csv( + "large_data.csv", + dtype=schema, + header=None, + names=schema.keys(), + chunksize=CHUNKSIZE, +) +df = pd.concat(chunks) +``` + +**Estimated Result:** + +More chunks(smaller chunksize), Slower +- Overhead of creating new dataframe for each chunk +- Overhead of concatenating all chunks + + +**Actual Result:** + +| | chunksize 10K | chunksize 100K | chunksize 1M | none | +| ------------- | ------------- | -------------- | ------------ | ------ | +| datasize 10M | 4.82s | 3.92s | 4.3s | 4.51s | +| datasize 100M | 46.85s | 40.25s | 44.38s | 48.77s | + +Regardless of data size, chunksize 100K is the fastest. + +**Why?** + +Pandas buffer realloc problem? + +--- + +### Pandas DataFrame + +```python +# 10M: 4.70s +# 100M: 50.19s +df["GDP"] = df["GDP"].apply(lambda x: x.replace(",", "")) +df["GDP"] = df["GDP"].apply(lambda x: round(float(x) / 1000, 2)) +``` + +```python +# 10M: 4.14s +# 100M: 42.46s +df["GDP"] = df["GDP"].apply(lambda x: round(float(x.replace(",", "")) / 1000, 2)) +``` + +```python +# 10M: 3.98s +# 100M: 39.55s +df["GDP"] = ( + pd.to_numeric(df["GDP"].str.replace(",", ""), errors="coerce") + .div(1000) + .round(2) +) +``` + +```python +# 10M: 3.19s +# 100M: 32.64s +df["GDP"] = (df["GDP"].replace(",", "", regex=True).astype(float) / 1000).round(2) +``` + +```python +# 10M: 1.66s +# 100M: 17.05s +df["GDP"] = (df["GDP"].str.replace(",", "").astype(float) / 1000).round(2) +``` + +--- + +## Parallel/Distributed Processing + +See detail in `etl_project_gdp_parallel.py`. + +### Steps + +1. Split one big file to small files + ex) data.csv -> data_0.csv, data_1.csv +2. Preprocess each file + ex) data_0.csv -> data_0_preprocessed.csv +3. Map each file to region + ex) data_0_preprocessed.csv -> data_0_asia.csv, data_0_europe.csv +4. Reduce by region + ex) data_0_asia.csv, data_1_asia.csv -> data_asia.csv +5. Sort by GDP + ex) data_asia.csv -> data_asia_sorted.csv +6. Load to sqlite + ex) data_asia_sorted.csv -> data_asia_sorted.db +7. Query by region diff --git a/missions/W1/M3/etl_project_gdp.py b/missions/W1/M3/etl_project_gdp.py new file mode 100644 index 0000000..dd33214 --- /dev/null +++ b/missions/W1/M3/etl_project_gdp.py @@ -0,0 +1,62 @@ +from modules.logger import logger, init_logger +from modules.importer import WikiWebImporter +from modules.exporter import JsonFileExporter +from pathlib import Path + +HOME_DIR = Path(__file__).resolve().parent +LOG_FILE_PATH = HOME_DIR / "log/etl_project_log.txt" +RAW_DATA_FILE_PATH = HOME_DIR / "data/Countries_by_GDP.json" +OUTPUT_FILE_PATH = HOME_DIR / "data/Countries_by_GDP_Transformed.json" + + +def transform_df(df): + """ + Transformation function + """ + # Million -> Billion + df["GDP"] = df["GDP"].apply(lambda x: x.replace(",", "")) + df["GDP"] = df["GDP"].apply(lambda x: round(float(x) / 1000, 2)) + + # Sort by GDP + df = df.sort_values(by="GDP", ascending=False) + + return df + + +def main(): + init_logger(LOG_FILE_PATH) + logger.print_separator() + logger.info("Starting the ETL process") + + # Extract + # parsing html and store to raw_data_file_path + wiki_importer = WikiWebImporter(raw_data_file_path=RAW_DATA_FILE_PATH) + df = wiki_importer.import_data() + + # Transform + # transform GDP to billion and sort by GDP + logger.info("Transforming data...") + df = transform_df(df) + + # Load + # export to output_file_path + exporter = JsonFileExporter(OUTPUT_FILE_PATH) + exporter.export_data(df) + + logger.info("ETL process completed successfully") + + # Query + df_over_100 = df[df["GDP"] > 100] + print("Countries with GDP > 100B:") + for _, row in df_over_100.iterrows(): + print(f"{row['Country']:<20} {row['GDP']}") + + df_groupby_top5 = df.groupby("Region").head(5) + avg_gdp = df_groupby_top5.groupby("Region")["GDP"].mean() + print("Top 5 Average GDP by Region:") + for region, gdp in avg_gdp.items(): + print(f"{region:<15} {gdp:.2f}") + + +if __name__ == "__main__": + main() diff --git a/missions/W1/M3/etl_project_gdp_from_csv.py b/missions/W1/M3/etl_project_gdp_from_csv.py new file mode 100644 index 0000000..71d5a3c --- /dev/null +++ b/missions/W1/M3/etl_project_gdp_from_csv.py @@ -0,0 +1,119 @@ +import sqlite3 +import time +import pandas as pd +from modules.logger import logger, init_logger +from modules.importer import CsvFileImporter +from modules.exporter import SqliteExporter + +LOG_FILE_PATH = "etl_project_log.txt" +DB_PATH = "World_Economies.db" +TABLE_NAME = "Countries_by_GDP" +INPUT_FILE_PATH = "large_data_10M.csv" +# INPUT_FILE_PATH = "large_data.csv" + +QUERY_1 = """ +SELECT Country, GDP_USD_billion +FROM Countries_by_GDP +WHERE GDP_USD_billion > 100 +ORDER BY GDP_USD_billion DESC +""" +QUERY_2 = """ +SELECT Region, AVG(GDP_USD_billion) FROM +( + SELECT + Country, + GDP_USD_billion, + Region, + ROW_NUMBER() OVER (PARTITION BY Region ORDER BY GDP_USD_billion DESC) AS row_num + FROM Countries_by_GDP +) +WHERE row_num <= 5 +GROUP BY Region +""" + + +def transfrom_df(df: pd.DataFrame) -> pd.DataFrame: + """ + Transformation function + """ + time_start = time.time() + # Million -> Billion + # df["GDP"] = df["GDP"].apply(lambda x: x.replace(",", "")) + # df["GDP"] = df["GDP"].apply(lambda x: round(float(x) / 1000, 2)) + + # df["GDP"] = df["GDP"].apply(lambda x: round(float(x.replace(",", "")) / 1000, 2)) + # df["GDP"] = (df["GDP"].replace(",", "", regex=True).astype(float) / 1000).round(2) + df["GDP"] = (df["GDP"].str.replace(",", "").astype(float) / 1000).round(2) + # df["GDP"] = ( + # pd.to_numeric(df["GDP"].str.replace(",", ""), errors="coerce") + # .div(1000) + # .round(2) + # ) + time_end = time.time() + logger.info(f"Transform GDP: {time_end - time_start:.2f} seconds") + + # Sort by GDP + time_start = time.time() + df = df.sort_values(by="GDP", ascending=False) + time_end = time.time() + logger.info(f"Sort by GDP: {time_end - time_start:.2f} seconds") + + # Rename GDP column to GDP_USD_billion + df.rename(columns={"GDP": "GDP_USD_billion"}, inplace=True) + + return df + + +def main(): + init_logger(LOG_FILE_PATH) + logger.print_separator() + logger.info("Starting the ETL process") + + # Extract + time_start = time.time() + csv_importer = CsvFileImporter(INPUT_FILE_PATH) + df = csv_importer.import_data() + time_end = time.time() + logger.info(f"Extract Time taken: {time_end - time_start:.2f} seconds") + + # Transform + time_start = time.time() + logger.info("Transforming data...") + df = transfrom_df(df) + time_end = time.time() + logger.info(f"Transform Time taken: {time_end - time_start:.2f} seconds") + + print(df.head(3)) + + # Load + time_start = time.time() + sqlite_exporter = SqliteExporter(DB_PATH, table_name=TABLE_NAME) + sqlite_exporter.export_data(df) + time_end = time.time() + logger.info(f"Load Time taken: {time_end - time_start:.2f} seconds") + + logger.info("ETL process completed successfully") + + print("Top 5 Average GDP by Region:") + + time_start = time.time() + df_groupby_top5 = df.groupby("Region").head(5) + avg_gdp = df_groupby_top5.groupby("Region")["GDP_USD_billion"].mean() + for region, gdp in avg_gdp.items(): + print(f"{region:<15} {gdp:.2f}") + time_end = time.time() + logger.info(f"Query with Dataframe : {time_end - time_start:.2f} seconds") + + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + time_start = time.time() + cursor.execute(QUERY_2) + for row in cursor: + print(f"{row[0]:<15} {row[1]:.2f}") + time_end = time.time() + logger.info(f"Query with SQLITE: {time_end - time_start:.2f} seconds") + conn.close() + + +if __name__ == "__main__": + main() diff --git a/missions/W1/M3/etl_project_gdp_parallel.py b/missions/W1/M3/etl_project_gdp_parallel.py new file mode 100644 index 0000000..ae218fd --- /dev/null +++ b/missions/W1/M3/etl_project_gdp_parallel.py @@ -0,0 +1,236 @@ +import sqlite3 +import time +import pandas as pd +from modules.logger import logger, init_logger +from multiprocessing import Pool +from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor + +LOG_FILE_PATH = "etl_project_log.txt" +# DB_PATH = "World_Economies_1B.db" +DB_NAME = "World_Economies_10M" +TABLE_NAME = "Countries_by_GDP" +# INPUT_FILE_PATH = "large_data_1B.csv" +INPUT_FILE_PATH = "large_data_10M.csv" +DATA_SIZE = 10_000_000 # 10M rows +CHUNK_SIZE = 1_000_000 # 100K rows per chunk +NUM_CHUNKS = DATA_SIZE // CHUNK_SIZE # 100 chunks + +QUERY_1 = """ +SELECT Country, GDP_USD_billion +FROM Countries_by_GDP +WHERE GDP_USD_billion > 100 +ORDER BY GDP_USD_billion DESC +""" +QUERY_2 = """ +SELECT Region, AVG(GDP_USD_billion) FROM +( + SELECT + Country, + GDP_USD_billion, + Region, + ROW_NUMBER() OVER (PARTITION BY Region ORDER BY GDP_USD_billion DESC) AS row_num + FROM Countries_by_GDP +) +WHERE row_num <= 5 +GROUP BY Region +""" + + +def transfrom_df(df: pd.DataFrame) -> pd.DataFrame: + """ + Transformation function + """ + + df["GDP"] = (df["GDP"].str.replace(",", "").astype(float) / 1000).round(2) + + # Sort by GDP + # df = df.sort_values(by="GDP", ascending=False) + + # Rename GDP column to GDP_USD_billion + df.rename(columns={"GDP": "GDP_USD_billion"}, inplace=True) + + return df + + +schema = { + "Country": str, + "GDP": str, + "Region": str, +} + + +def process_chunk(index: int): + df = pd.read_csv( + INPUT_FILE_PATH, + dtype=schema, + header=None, + names=schema.keys(), + skiprows=index * CHUNK_SIZE, + nrows=CHUNK_SIZE, + ) + df.to_csv(f"data/large_data_10M_{index}.csv", index=False) + + +def process_chunk2(index: int, chunk: pd.DataFrame): + print(f"Processing chunk {index}") + chunk.to_csv(f"data/large_data_10M_{index}.csv", index=False) + + +def extract_data_from_source(): + """ + (Extract) Seperate one big file into multiple small files. + + large_data_1B.csv -> large_data_1B_0.csv, large_data_1B_1.csv, ... + """ + + with Pool() as pool: + with pd.read_csv( + INPUT_FILE_PATH, + dtype=schema, + header=None, + names=schema.keys(), + chunksize=CHUNK_SIZE, + ) as reader: + results = [] + for i, chunk in enumerate(reader): + results.append(pool.apply_async(process_chunk2, args=(i, chunk))) + + pool.close() + pool.join() + + +def transform_chunk(index: int): + """ + (Transform - Preprocess) Transform each small file + """ + df = pd.read_csv(f"data/large_data_10M_{index}.csv", dtype=schema) + df = transfrom_df(df) # 기존 transform 함수 사용 + df.to_csv(f"data/large_data_10M_{index}_transformed.csv", index=False) + + +def map_by_region(index: int): + """ + (Transform - Map) Separate each small file by region + """ + df = pd.read_csv(f"data/large_data_10M_{index}_transformed.csv") + regions = ["Asia", "Europe", "Africa", "North America", "South America", "Oceania"] + + for region in regions: + region_df = df[df["Region"] == region] + region_df.to_csv(f"data/large_data_10M_{index}_{region}.csv", index=False) + + +def reduce_by_region(region: str): + """ + (Transform - Reduce) Merge all files for each region + """ + all_files = [f"data/large_data_10M_{i}_{region}.csv" for i in range(NUM_CHUNKS)] + dfs = [] + + for file in all_files: + try: + df = pd.read_csv(file) + dfs.append(df) + except FileNotFoundError: + continue + + if dfs: + combined_df = pd.concat(dfs, ignore_index=True) + combined_df.to_csv(f"data/large_data_10M_{region}.csv", index=False) + + +def sort_by_gdp(region: str): + """ + (Transform - Sort) Sort each region file by GDP + """ + df = pd.read_csv(f"data/large_data_10M_{region}.csv") + df = df.sort_values(by="GDP_USD_billion", ascending=False) + df.to_csv(f"data/large_data_10M_{region}_sorted.csv", index=False) + + +def load_to_database(region: str): + """ + (Load) Export each region file to sqlite + """ + conn = sqlite3.connect(f"data/{DB_NAME}_{region}.db") + df = pd.read_csv(f"data/large_data_10M_{region}_sorted.csv") + df.to_sql(TABLE_NAME, conn, if_exists="append", index=False) + conn.close() + + +def query_by_region(region: str): + with sqlite3.connect(f"data/{DB_NAME}_{region}.db") as conn: + cursor = conn.cursor() + cursor.execute( + "SELECT AVG(GDP_USD_billion) FROM (SELECT * FROM Countries_by_GDP ORDER BY GDP_USD_billion DESC LIMIT 5)" + ) + result = cursor.fetchall() + return region, result[0][0] + + +def main(): + init_logger(LOG_FILE_PATH) + logger.print_separator() + logger.info("Starting the Parallel ETL process") + + # 1. Extract + time_start = time.time() + # extract_data_from_source() + with Pool() as pool: + pool.map(process_chunk, range(NUM_CHUNKS)) + # with ThreadPoolExecutor() as executor: + # executor.map(process_chunk, range(NUM_CHUNKS)) + # with ProcessPoolExecutor() as executor: + # executor.map(process_chunk, range(NUM_CHUNKS)) + time_end = time.time() + logger.info(f"Extract data: {time_end - time_start:.2f} seconds") + + # 2. Transform - Preprocess + time_start = time.time() + with Pool() as pool: + pool.map(transform_chunk, range(NUM_CHUNKS)) + time_end = time.time() + logger.info(f"Transform chunks: {time_end - time_start:.2f} seconds") + + # 3. Transform - Map + time_start = time.time() + with Pool() as pool: + pool.map(map_by_region, range(NUM_CHUNKS)) + time_end = time.time() + logger.info(f"Map by region: {time_end - time_start:.2f} seconds") + + # 4. Transform - Reduce + time_start = time.time() + regions = ["Asia", "Europe", "Africa", "North America", "South America", "Oceania"] + with Pool() as pool: + pool.map(reduce_by_region, regions) + time_end = time.time() + logger.info(f"Reduce by region: {time_end - time_start:.2f} seconds") + + # 5. Sort + time_start = time.time() + with Pool() as pool: + pool.map(sort_by_gdp, regions) + time_end = time.time() + logger.info(f"Sort by GDP: {time_end - time_start:.2f} seconds") + + # 6. Load + time_start = time.time() + with Pool() as pool: + pool.map(load_to_database, regions) + time_end = time.time() + logger.info(f"Load to database: {time_end - time_start:.2f} seconds") + + # 7. Query + time_start = time.time() + regions = ["Asia", "Europe", "Africa", "North America", "South America", "Oceania"] + with Pool() as pool: + results = pool.map(query_by_region, regions) + print(results) + + time_end = time.time() + logger.info(f"Query: {time_end - time_start:.2f} seconds") + + +if __name__ == "__main__": + main() diff --git a/missions/W1/M3/etl_project_gdp_with_sql.py b/missions/W1/M3/etl_project_gdp_with_sql.py new file mode 100644 index 0000000..6106224 --- /dev/null +++ b/missions/W1/M3/etl_project_gdp_with_sql.py @@ -0,0 +1,83 @@ +import sqlite3 + +from modules.logger import logger, init_logger +from modules.importer import WikiWebImporter +from modules.exporter import SqliteExporter + +LOG_FILE_PATH = "etl_project_log.txt" +DB_PATH = "World_Economies.db" +TABLE_NAME = "Countries_by_GDP" + +QUERY_1 = """ +SELECT Country, GDP_USD_billion +FROM Countries_by_GDP +WHERE GDP_USD_billion > 100 +ORDER BY GDP_USD_billion DESC +""" +QUERY_2 = """ +SELECT Region, AVG(GDP_USD_billion) FROM +( + SELECT + Country, + GDP_USD_billion, + Region, + ROW_NUMBER() OVER (PARTITION BY Region ORDER BY GDP_USD_billion DESC) AS row_num + FROM Countries_by_GDP +) +WHERE row_num <= 5 +GROUP BY Region +""" + + +def transfrom_df(df): + """ + Transformation function + """ + # Million -> Billion + df["GDP"] = df["GDP"].apply(lambda x: x.replace(",", "")) + df["GDP"] = df["GDP"].apply(lambda x: round(float(x) / 1000, 2)) + + # Sort by GDP + df = df.sort_values(by="GDP", ascending=False) + + # Rename GDP column to GDP_USD_billion + df.rename(columns={"GDP": "GDP_USD_billion"}, inplace=True) + + return df + + +def main(): + init_logger(LOG_FILE_PATH) + logger.print_separator() + logger.info("Starting the ETL process") + + # Extract + wiki_importer = WikiWebImporter() + df = wiki_importer.import_data() + + # Transform + logger.info("Transforming data...") + df = transfrom_df(df) + + # Load + sqlite_exporter = SqliteExporter(DB_PATH, table_name=TABLE_NAME) + sqlite_exporter.export_data(df) + + logger.info("ETL process completed successfully") + + # Query + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + cursor.execute(QUERY_1) + print("Countries with GDP > 100B:") + for row in cursor: + print(f"{row[0]:<20} {row[1]}") + cursor.execute(QUERY_2) + print("Top 5 Average GDP by Region:") + for row in cursor: + print(f"{row[0]:<15} {row[1]:.2f}") + conn.close() + + +if __name__ == "__main__": + main() diff --git a/missions/W1/M3/modules/exporter.py b/missions/W1/M3/modules/exporter.py new file mode 100644 index 0000000..69984d6 --- /dev/null +++ b/missions/W1/M3/modules/exporter.py @@ -0,0 +1,50 @@ +from abc import ABC, abstractmethod +import pandas as pd +import sqlite3 + +from modules.logger import logger + + +class ExporterInterface(ABC): + """ + Data exporter interface + """ + + def __init__(self, target: str, **options): + self.target = target + self.options = options + + @abstractmethod + def export_data(self, df: pd.DataFrame): + pass + + +class JsonFileExporter(ExporterInterface): + """ + Json file data exporter + """ + + def export_data(self, df: pd.DataFrame): + logger.info(f"Exporting data to {self.target}...") + df.to_json(self.target, orient="records", indent=2) + logger.info("Data exported successfully") + + +class SqliteExporter(ExporterInterface): + """ + Sqlite data exporter + """ + + def __init__(self, target: str, table_name: str): + if not table_name: + logger.error("table_name is required for SqliteExporter") + raise ValueError("table_name is required for SqliteExporter") + super().__init__(target, table_name=table_name) + + # sqlite connection만 받아오는게 좋을까? -> 굳이? 전부 이 안으로 추상화하자. + def export_data(self, df: pd.DataFrame): + logger.info(f"Exporting data to {self.target}...") + conn = sqlite3.connect(self.target) + df.to_sql(self.options["table_name"], conn, if_exists="replace", index=False) + conn.close() + logger.info("Data exported successfully") diff --git a/missions/W1/M3/modules/importer.py b/missions/W1/M3/modules/importer.py new file mode 100644 index 0000000..4c81118 --- /dev/null +++ b/missions/W1/M3/modules/importer.py @@ -0,0 +1,180 @@ +from abc import ABC, abstractmethod +import pandas as pd +import json +from bs4 import BeautifulSoup +import requests +from pathlib import Path +from modules.logger import logger + + +class ImporterInterface(ABC): + """ + General Data importer interface. + Importer Rule: import data from source and return dataframe. The dataframe should have the following columns: + - Country + - GDP + - Region + """ + + def __init__(self, source: str): + self.source = source + + @abstractmethod + def import_data(self) -> pd.DataFrame: + """ + Import raw data from the source + """ + pass + + +class WebImporterInterface(ImporterInterface): + """ + Web Crawler interface. request -> parse -> return + Subclass should implement _parse_html method. + Web importer는 HTML을 파싱하여 중간 데이터를 만들기 때문에(일종의 Tranform 작업) 중간 데이터를 저장할 수 있는 옵션을 둔다. + """ + + def __init__(self, source: str, raw_data_file_path: str = None): + super().__init__(source) + self.raw_data_file_path = raw_data_file_path + + def import_data(self) -> pd.DataFrame: + logger.info(f"Importing data from {self.source}...") + html = self._get_html() + df = self._parse_html(html) + if self.raw_data_file_path: + self._store_raw_data(self.raw_data_file_path, df) + logger.info(f"Data imported successfully") + return df + + def _get_html(self) -> str: + """ + Fetch HTML from the given URL + """ + try: + response = requests.get(self.source) + response.raise_for_status() + return response.text + except requests.exceptions.RequestException as e: + logger.error(f"ERROR: Failed to fetch HTML from {self.source}") + logger.error(f"ERROR: {e}") + raise e + + @abstractmethod + def _parse_html(self, html: str) -> pd.DataFrame: + """ + Parse HTML to dataframe + """ + pass + + def _store_raw_data(self, path: str, df: pd.DataFrame): + """ + Store raw data to the given file + """ + df.to_json(path, orient="records", indent=2) + + +class WikiWebImporter(WebImporterInterface): + """ + Wiki Web Data Importer class. + """ + + IMF_WIKI_URL = ( + "https://en.wikipedia.org/wiki/List_of_countries_by_GDP_%28nominal%29" + ) + COUNTRY_REGION_TABLE_PATH = ( + Path(__file__).resolve().parent / "../data/country_region_table.json" + ) + + def __init__(self, raw_data_file_path: str = None): + super().__init__(self.IMF_WIKI_URL, raw_data_file_path) + + def import_data(self) -> pd.DataFrame: + return super().import_data() + + # 여기서 region까지 매핑하는 것이 좋을까? .. yes + # why? data importer가 가져오는 데이터의 포맷을 통일하고 싶다. + def _parse_html(self, html: str) -> pd.DataFrame: + data = self._parse_wiki_table_to_df(html) + data = self._map_region(data) + return data + + def _parse_wiki_table_to_df(self, html: str) -> pd.DataFrame: + """ + Parse wikitable to dataframe + """ + soup = BeautifulSoup(html, "html.parser") + table = soup.find("table", class_="wikitable") + data = [] + try: + if not table: + raise Exception("HTML Parsing Error. Wikitable not found") + rows = table.find_all("tr") + for row in rows[3:]: + columns = row.find_all("td") + country = columns[0].text.strip() + gdp = columns[1].text.strip() + if not gdp or gdp == "—": + continue + data.append({"Country": country, "GDP": gdp}) + # TODO: dataframe 만들기 최적화 + return pd.DataFrame(data) + except Exception as e: + logger.error(f"ERROR: Error parsing HTML: {e}") + raise e + + def _map_region(self, df: pd.DataFrame) -> pd.DataFrame: + """ + Map region to the given country + """ + + def parse_json(file_path): + """ + Read JSON file + """ + with open(file_path, "r") as file: + data = json.load(file) + return data + + country_region_table = parse_json(self.COUNTRY_REGION_TABLE_PATH) + df["Region"] = df["Country"].map(country_region_table) + return df + + +class FileImporter(ImporterInterface): + """ + File data importer + File importer는 별도의 trasnform 작업이 없으니 중간 데이터가 없다. + """ + + def import_data(self) -> pd.DataFrame: + logger.info(f"Importing data from {self.source}...") + df = self._parse_file(self.source) + logger.info("Data imported successfully") + return df + + @abstractmethod + def _parse_file(self, file: str) -> pd.DataFrame: + """ + Parse file to dataframe + """ + pass + + +class CsvFileImporter(FileImporter): + """ + Csv file importer + """ + + def _parse_file(self, file: str) -> pd.DataFrame: + schema = { + "Country": str, + "GDP": str, + "Region": str, + } + # df = pd.read_csv(file, dtype=schema, header=None, names=schema.keys()) + chunks = pd.read_csv( + file, dtype=schema, header=None, names=schema.keys(), chunksize=100_000 + ) + df = pd.concat(chunks) + return df diff --git a/missions/W1/M3/modules/logger.py b/missions/W1/M3/modules/logger.py new file mode 100644 index 0000000..a8f1151 --- /dev/null +++ b/missions/W1/M3/modules/logger.py @@ -0,0 +1,38 @@ +import datetime +from pathlib import Path + +DEFAULT_LOG_FILE_PATH = Path(__file__).resolve().parent / "../log/log.txt" + + +class Logger: + """ + Logger class + """ + + def __init__(self, log_file_path: str = DEFAULT_LOG_FILE_PATH): + self.log_file_path = log_file_path + + def info(self, message: str): + self._log("INFO", message) + + def error(self, message: str): + self._log("ERROR", message) + + def print_separator(self): + with open(self.log_file_path, "a") as log_file: + log_file.write("-" * 30 + "\n") + print("-" * 30) + + def _log(self, type: str, message: str): + with open(self.log_file_path, "a") as log_file: + timestamp = datetime.datetime.now() + log_data = f'{timestamp.strftime("%Y-%b-%d-%H-%M-%S")}, {type}: {message}' + log_file.write(log_data + "\n") + print(log_data) + + +logger = Logger() + + +def init_logger(log_file_path: str): + logger.log_file_path = log_file_path diff --git a/missions/W1/M3/utils/create_country_region_table.py b/missions/W1/M3/utils/create_country_region_table.py new file mode 100644 index 0000000..5751562 --- /dev/null +++ b/missions/W1/M3/utils/create_country_region_table.py @@ -0,0 +1,48 @@ +import json +import requests +from bs4 import BeautifulSoup +from pathlib import Path + +TARGET_URL = "https://en.wikipedia.org/wiki/List_of_sovereign_states_and_dependent_territories_by_continent" +OUTPUT_FILE_PATH = "../data/country_region_table.json" + + +def save_to_json(data, file_path): + home_dir = Path(__file__).resolve().parent + out_dir = home_dir / file_path + + with open(out_dir, "w") as file: + json.dump(data, file, indent=2) + + +def main(): + """ + Extract country and region from wikipedia + """ + response = requests.get(TARGET_URL) + soup = BeautifulSoup(response.text, "html.parser") + tables = soup.findAll("table", class_="wikitable")[:6] + regions = ["Africa", "Asia", "Europe", "North America", "Oceania", "South America"] + country_region_table = {} + for table, region in zip(tables, regions): + rows = table.findAll("tr") + for row in rows: + td = row.find("td") + if not td: + continue + b = td.find("b") + if not b: + continue + a = b.find("a") + if not a: + continue + countryName = a["title"] + countryNameAlias = a.text.strip() + country_region_table[countryName] = region + country_region_table[countryNameAlias] = region + + save_to_json(country_region_table, OUTPUT_FILE_PATH) + + +if __name__ == "__main__": + main() diff --git a/missions/W1/M3/utils/create_large_data_csv.py b/missions/W1/M3/utils/create_large_data_csv.py new file mode 100644 index 0000000..bd4035b --- /dev/null +++ b/missions/W1/M3/utils/create_large_data_csv.py @@ -0,0 +1,72 @@ +import random +import string +import sys +import time +from multiprocessing import Pool +from pathlib import Path + +OUTPUT_FILE_PATH = "../data/large_data.csv" + + +def generate_random_name(): + """ + name format: 3~10 characters + first letter should be uppercase and the rest should be lowercase + """ + return random.choice(string.ascii_uppercase) + "".join( + random.choices(string.ascii_lowercase, k=random.randint(2, 9)) + ) + + +def generate_random_gdp(): + """ + gdp format: 1,000,000,000 + max value: 10,000,000 + less number, more posibility + """ + max_value = 10000000 + k = random.choice([max_value, max_value * 0.1, max_value * 0.01]) + random_value = int(k * random.random() ** 1.8) + return f'"{random_value:,}"' + + +def generate_random_region(): + return random.choice( + ["Asia", "Europe", "Africa", "North America", "South America", "Oceania"] + ) + + +def generate_data(): + return ( + f"{generate_random_name()},{generate_random_gdp()},{generate_random_region()}\n" + ) + + +def generate_data_chunk(chunk_size: int): + return [generate_data() for _ in range(chunk_size)] + + +def main(): + CHUNK_SIZE = 10000 + row_number = 10000 + if len(sys.argv) > 1: + row_number = int(sys.argv[1]) + + num_chunks = row_number // CHUNK_SIZE + + home_dir = Path(__file__).resolve().parent + out_dir = home_dir / OUTPUT_FILE_PATH + + with open(out_dir, "w") as f: + with Pool() as pool: + for data_chunk in pool.imap_unordered( + generate_data_chunk, [CHUNK_SIZE] * num_chunks + ): + f.writelines(data_chunk) + + +if __name__ == "__main__": + time_start = time.time() + main() + time_end = time.time() + print(f"Time taken: {time_end - time_start:.2f} seconds") diff --git a/missions/W1/create.sql b/missions/W1/create.sql new file mode 100644 index 0000000..c0b3d95 --- /dev/null +++ b/missions/W1/create.sql @@ -0,0 +1,1035 @@ +-- Either: +-- Open DB Browser for SQLite. +-- Create a new database named Northwind.db. +-- Select Execute SQL. +-- Copy and paste the SQL below into the Execute SQL window. +-- Run the script to populate the Northwind database. +-- Or: +-- Run the following command in a terminal window. +-- sqlite3 Northwind.db +-- Copy and paste the SQL below into the terminal window to populate the Northwind database. + +DROP TABLE IF EXISTS OrderDetails; +DROP TABLE IF EXISTS Orders; +DROP TABLE IF EXISTS Products; +DROP TABLE IF EXISTS Categories; +DROP TABLE IF EXISTS Customers; +DROP TABLE IF EXISTS Employees; +DROP TABLE IF EXISTS Shippers; +DROP TABLE IF EXISTS Suppliers; + +CREATE TABLE Categories +( + CategoryID INTEGER PRIMARY KEY AUTOINCREMENT, + CategoryName TEXT, + Description TEXT +); + +CREATE TABLE Customers +( + CustomerID INTEGER PRIMARY KEY AUTOINCREMENT, + CustomerName TEXT, + ContactName TEXT, + Address TEXT, + City TEXT, + PostalCode TEXT, + Country TEXT +); + +CREATE TABLE Employees +( + EmployeeID INTEGER PRIMARY KEY AUTOINCREMENT, + LastName TEXT, + FirstName TEXT, + BirthDate DATE, + Photo TEXT, + Notes TEXT +); + +CREATE TABLE Shippers( + ShipperID INTEGER PRIMARY KEY AUTOINCREMENT, + ShipperName TEXT, + Phone TEXT +); + +CREATE TABLE Suppliers( + SupplierID INTEGER PRIMARY KEY AUTOINCREMENT, + SupplierName TEXT, + ContactName TEXT, + Address TEXT, + City TEXT, + PostalCode TEXT, + Country TEXT, + Phone TEXT +); + +CREATE TABLE Products( + ProductID INTEGER PRIMARY KEY AUTOINCREMENT, + ProductName TEXT, + SupplierID INTEGER, + CategoryID INTEGER, + Unit TEXT, + Price NUMERIC DEFAULT 0, + FOREIGN KEY (CategoryID) REFERENCES Categories (CategoryID), + FOREIGN KEY (SupplierID) REFERENCES Suppliers (SupplierID) +); + +CREATE TABLE Orders( + OrderID INTEGER PRIMARY KEY AUTOINCREMENT, + CustomerID INTEGER, + EmployeeID INTEGER, + OrderDate DATETIME, + ShipperID INTEGER, + FOREIGN KEY (EmployeeID) REFERENCES Employees (EmployeeID), + FOREIGN KEY (CustomerID) REFERENCES Customers (CustomerID), + FOREIGN KEY (ShipperID) REFERENCES Shippers (ShipperID) +); + +CREATE TABLE OrderDetails( + OrderDetailID INTEGER PRIMARY KEY AUTOINCREMENT, + OrderID INTEGER, + ProductID INTEGER, + Quantity INTEGER, + FOREIGN KEY (OrderID) REFERENCES Orders (OrderID), + FOREIGN KEY (ProductID) REFERENCES Products (ProductID) +); + +INSERT INTO Categories VALUES(1,'Beverages','Soft drinks, coffees, teas, beers, and ales'); +INSERT INTO Categories VALUES(2,'Condiments','Sweet and savory sauces, relishes, spreads, and seasonings'); +INSERT INTO Categories VALUES(3,'Confections','Desserts, candies, and sweet breads'); +INSERT INTO Categories VALUES(4,'Dairy Products','Cheeses'); +INSERT INTO Categories VALUES(5,'Grains/Cereals','Breads, crackers, pasta, and cereal'); +INSERT INTO Categories VALUES(6,'Meat/Poultry','Prepared meats'); +INSERT INTO Categories VALUES(7,'Produce','Dried fruit and bean curd'); +INSERT INTO Categories VALUES(8,'Seafood','Seaweed and fish'); + +INSERT INTO Customers VALUES(1,'Alfreds Futterkiste','Maria Anders','Obere Str. 57','Berlin','12209','Germany'); +INSERT INTO Customers VALUES(2,'Ana Trujillo Emparedados y helados','Ana Trujillo','Avda. de la Constitución 2222','México D.F.','5021','Mexico'); +INSERT INTO Customers VALUES(3,'Antonio Moreno Taquería','Antonio Moreno','Mataderos 2312','México D.F.','5023','Mexico'); +INSERT INTO Customers VALUES(4,'Around the Horn','Thomas Hardy','120 Hanover Sq.','London','WA1 1DP','UK'); +INSERT INTO Customers VALUES(5,'Berglunds snabbköp','Christina Berglund','Berguvsvägen 8','Luleå','S-958 22','Sweden'); +INSERT INTO Customers VALUES(6,'Blauer See Delikatessen','Hanna Moos','Forsterstr. 57','Mannheim','68306','Germany'); +INSERT INTO Customers VALUES(7,'Blondel père et fils','Frédérique Citeaux','24, place Kléber','Strasbourg','67000','France'); +INSERT INTO Customers VALUES(8,'Bólido Comidas preparadas','Martín Sommer','C/ Araquil, 67','Madrid','28023','Spain'); +INSERT INTO Customers VALUES(9,'Bon app''''','Laurence Lebihans','12, rue des Bouchers','Marseille','13008','France'); +INSERT INTO Customers VALUES(10,'Bottom-Dollar Marketse','Elizabeth Lincoln','23 Tsawassen Blvd.','Tsawassen','T2F 8M4','Canada'); +INSERT INTO Customers VALUES(11,'B''''s Beverages','Victoria Ashworth','Fauntleroy Circus','London','EC2 5NT','UK'); +INSERT INTO Customers VALUES(12,'Cactus Comidas para llevar','Patricio Simpson','Cerrito 333','Buenos Aires','1010','Argentina'); +INSERT INTO Customers VALUES(13,'Centro comercial Moctezuma','Francisco Chang','Sierras de Granada 9993','México D.F.','5022','Mexico'); +INSERT INTO Customers VALUES(14,'Chop-suey Chinese','Yang Wang','Hauptstr. 29','Bern','3012','Switzerland'); +INSERT INTO Customers VALUES(15,'Comércio Mineiro','Pedro Afonso','Av. dos Lusíadas, 23','São Paulo','05432-043','Brazil'); +INSERT INTO Customers VALUES(16,'Consolidated Holdings','Elizabeth Brown','Berkeley Gardens 12 Brewery','London','WX1 6LT','UK'); +INSERT INTO Customers VALUES(17,'Drachenblut Delikatessend','Sven Ottlieb','Walserweg 21','Aachen','52066','Germany'); +INSERT INTO Customers VALUES(18,'Du monde entier','Janine Labrune','67, rue des Cinquante Otages','Nantes','44000','France'); +INSERT INTO Customers VALUES(19,'Eastern Connection','Ann Devon','35 King George','London','WX3 6FW','UK'); +INSERT INTO Customers VALUES(20,'Ernst Handel','Roland Mendel','Kirchgasse 6','Graz','8010','Austria'); +INSERT INTO Customers VALUES(21,'Familia Arquibaldo','Aria Cruz','Rua Orós, 92','São Paulo','05442-030','Brazil'); +INSERT INTO Customers VALUES(22,'FISSA Fabrica Inter. Salchichas S.A.','Diego Roel','C/ Moralzarzal, 86','Madrid','28034','Spain'); +INSERT INTO Customers VALUES(23,'Folies gourmandes','Martine Rancé','184, chaussée de Tournai','Lille','59000','France'); +INSERT INTO Customers VALUES(24,'Folk och fä HB','Maria Larsson','Åkergatan 24','Bräcke','S-844 67','Sweden'); +INSERT INTO Customers VALUES(25,'Frankenversand','Peter Franken','Berliner Platz 43','München','80805','Germany'); +INSERT INTO Customers VALUES(26,'France restauration','Carine Schmitt','54, rue Royale','Nantes','44000','France'); +INSERT INTO Customers VALUES(27,'Franchi S.p.A.','Paolo Accorti','Via Monte Bianco 34','Torino','10100','Italy'); +INSERT INTO Customers VALUES(28,'Furia Bacalhau e Frutos do Mar','Lino Rodriguez','Jardim das rosas n. 32','Lisboa','1675','Portugal'); +INSERT INTO Customers VALUES(29,'Galería del gastrónomo','Eduardo Saavedra','Rambla de Cataluña, 23','Barcelona','8022','Spain'); +INSERT INTO Customers VALUES(30,'Godos Cocina Típica','José Pedro Freyre','C/ Romero, 33','Sevilla','41101','Spain'); +INSERT INTO Customers VALUES(31,'Gourmet Lanchonetes','André Fonseca','Av. Brasil, 442','Campinas','04876-786','Brazil'); +INSERT INTO Customers VALUES(32,'Great Lakes Food Market','Howard Snyder','2732 Baker Blvd.','Eugene','97403','USA'); +INSERT INTO Customers VALUES(33,'GROSELLA-Restaurante','Manuel Pereira','5ª Ave. Los Palos Grandes','Caracas','1081','Venezuela'); +INSERT INTO Customers VALUES(34,'Hanari Carnes','Mario Pontes','Rua do Paço, 67','Rio de Janeiro','05454-876','Brazil'); +INSERT INTO Customers VALUES(35,'HILARIÓN-Abastos','Carlos Hernández','Carrera 22 con Ave. Carlos Soublette #8-35','San Cristóbal','5022','Venezuela'); +INSERT INTO Customers VALUES(36,'Hungry Coyote Import Store','Yoshi Latimer','City Center Plaza 516 Main St.','Elgin','97827','USA'); +INSERT INTO Customers VALUES(37,'Hungry Owl All-Night Grocers','Patricia McKenna','8 Johnstown Road','Cork','','Ireland'); +INSERT INTO Customers VALUES(38,'Island Trading','Helen Bennett','Garden House Crowther Way','Cowes','PO31 7PJ','UK'); +INSERT INTO Customers VALUES(39,'Königlich Essen','Philip Cramer','Maubelstr. 90','Brandenburg','14776','Germany'); +INSERT INTO Customers VALUES(40,'La corne d''''abondance','Daniel Tonini','67, avenue de l''''Europe','Versailles','78000','France'); +INSERT INTO Customers VALUES(41,'La maison d''''Asie','Annette Roulet','1 rue Alsace-Lorraine','Toulouse','31000','France'); +INSERT INTO Customers VALUES(42,'Laughing Bacchus Wine Cellars','Yoshi Tannamuri','1900 Oak St.','Vancouver','V3F 2K1','Canada'); +INSERT INTO Customers VALUES(43,'Lazy K Kountry Store','John Steel','12 Orchestra Terrace','Walla Walla','99362','USA'); +INSERT INTO Customers VALUES(44,'Lehmanns Marktstand','Renate Messner','Magazinweg 7','Frankfurt a.M.','60528','Germany'); +INSERT INTO Customers VALUES(45,'Let''''s Stop N Shop','Jaime Yorres','87 Polk St. Suite 5','San Francisco','94117','USA'); +INSERT INTO Customers VALUES(46,'LILA-Supermercado','Carlos González','Carrera 52 con Ave. Bolívar #65-98 Llano Largo','Barquisimeto','3508','Venezuela'); +INSERT INTO Customers VALUES(47,'LINO-Delicateses','Felipe Izquierdo','Ave. 5 de Mayo Porlamar','I. de Margarita','4980','Venezuela'); +INSERT INTO Customers VALUES(48,'Lonesome Pine Restaurant','Fran Wilson','89 Chiaroscuro Rd.','Portland','97219','USA'); +INSERT INTO Customers VALUES(49,'Magazzini Alimentari Riuniti','Giovanni Rovelli','Via Ludovico il Moro 22','Bergamo','24100','Italy'); +INSERT INTO Customers VALUES(50,'Maison Dewey','Catherine Dewey','Rue Joseph-Bens 532','Bruxelles','B-1180','Belgium'); +INSERT INTO Customers VALUES(51,'Mère Paillarde','Jean Fresnière','43 rue St. Laurent','Montréal','H1J 1C3','Canada'); +INSERT INTO Customers VALUES(52,'Morgenstern Gesundkost','Alexander Feuer','Heerstr. 22','Leipzig','4179','Germany'); +INSERT INTO Customers VALUES(53,'North/South','Simon Crowther','South House 300 Queensbridge','London','SW7 1RZ','UK'); +INSERT INTO Customers VALUES(54,'Océano Atlántico Ltda.','Yvonne Moncada','Ing. Gustavo Moncada 8585 Piso 20-A','Buenos Aires','1010','Argentina'); +INSERT INTO Customers VALUES(55,'Old World Delicatessen','Rene Phillips','2743 Bering St.','Anchorage','99508','USA'); +INSERT INTO Customers VALUES(56,'Ottilies Käseladen','Henriette Pfalzheim','Mehrheimerstr. 369','Köln','50739','Germany'); +INSERT INTO Customers VALUES(57,'Paris spécialités','Marie Bertrand','265, boulevard Charonne','Paris','75012','France'); +INSERT INTO Customers VALUES(58,'Pericles Comidas clásicas','Guillermo Fernández','Calle Dr. Jorge Cash 321','México D.F.','5033','Mexico'); +INSERT INTO Customers VALUES(59,'Piccolo und mehr','Georg Pipps','Geislweg 14','Salzburg','5020','Austria'); +INSERT INTO Customers VALUES(60,'Princesa Isabel Vinhoss','Isabel de Castro','Estrada da saúde n. 58','Lisboa','1756','Portugal'); +INSERT INTO Customers VALUES(61,'Que Delícia','Bernardo Batista','Rua da Panificadora, 12','Rio de Janeiro','02389-673','Brazil'); +INSERT INTO Customers VALUES(62,'Queen Cozinha','Lúcia Carvalho','Alameda dos Canàrios, 891','São Paulo','05487-020','Brazil'); +INSERT INTO Customers VALUES(63,'QUICK-Stop','Horst Kloss','Taucherstraße 10','Cunewalde','1307','Germany'); +INSERT INTO Customers VALUES(64,'Rancho grande','Sergio Gutiérrez','Av. del Libertador 900','Buenos Aires','1010','Argentina'); +INSERT INTO Customers VALUES(65,'Rattlesnake Canyon Grocery','Paula Wilson','2817 Milton Dr.','Albuquerque','87110','USA'); +INSERT INTO Customers VALUES(66,'Reggiani Caseifici','Maurizio Moroni','Strada Provinciale 124','Reggio Emilia','42100','Italy'); +INSERT INTO Customers VALUES(67,'Ricardo Adocicados','Janete Limeira','Av. Copacabana, 267','Rio de Janeiro','02389-890','Brazil'); +INSERT INTO Customers VALUES(68,'Richter Supermarkt','Michael Holz','Grenzacherweg 237','Genève','1203','Switzerland'); +INSERT INTO Customers VALUES(69,'Romero y tomillo','Alejandra Camino','Gran Vía, 1','Madrid','28001','Spain'); +INSERT INTO Customers VALUES(70,'Santé Gourmet','Jonas Bergulfsen','Erling Skakkes gate 78','Stavern','4110','Norway'); +INSERT INTO Customers VALUES(71,'Save-a-lot Markets','Jose Pavarotti','187 Suffolk Ln.','Boise','83720','USA'); +INSERT INTO Customers VALUES(72,'Seven Seas Imports','Hari Kumar','90 Wadhurst Rd.','London','OX15 4NB','UK'); +INSERT INTO Customers VALUES(73,'Simons bistro','Jytte Petersen','Vinbæltet 34','København','1734','Denmark'); +INSERT INTO Customers VALUES(74,'Spécialités du monde','Dominique Perrier','25, rue Lauriston','Paris','75016','France'); +INSERT INTO Customers VALUES(75,'Split Rail Beer & Ale','Art Braunschweiger','P.O. Box 555','Lander','82520','USA'); +INSERT INTO Customers VALUES(76,'Suprêmes délices','Pascale Cartrain','Boulevard Tirou, 255','Charleroi','B-6000','Belgium'); +INSERT INTO Customers VALUES(77,'The Big Cheese','Liz Nixon','89 Jefferson Way Suite 2','Portland','97201','USA'); +INSERT INTO Customers VALUES(78,'The Cracker Box','Liu Wong','55 Grizzly Peak Rd.','Butte','59801','USA'); +INSERT INTO Customers VALUES(79,'Toms Spezialitäten','Karin Josephs','Luisenstr. 48','Münster','44087','Germany'); +INSERT INTO Customers VALUES(80,'Tortuga Restaurante','Miguel Angel Paolino','Avda. Azteca 123','México D.F.','5033','Mexico'); +INSERT INTO Customers VALUES(81,'Tradição Hipermercados','Anabela Domingues','Av. Inês de Castro, 414','São Paulo','05634-030','Brazil'); +INSERT INTO Customers VALUES(82,'Trail''''s Head Gourmet Provisioners','Helvetius Nagy','722 DaVinci Blvd.','Kirkland','98034','USA'); +INSERT INTO Customers VALUES(83,'Vaffeljernet','Palle Ibsen','Smagsløget 45','Århus','8200','Denmark'); +INSERT INTO Customers VALUES(84,'Victuailles en stock','Mary Saveley','2, rue du Commerce','Lyon','69004','France'); +INSERT INTO Customers VALUES(85,'Vins et alcools Chevalier','Paul Henriot','59 rue de l''''Abbaye','Reims','51100','France'); +INSERT INTO Customers VALUES(86,'Die Wandernde Kuh','Rita Müller','Adenauerallee 900','Stuttgart','70563','Germany'); +INSERT INTO Customers VALUES(87,'Wartian Herkku','Pirkko Koskitalo','Torikatu 38','Oulu','90110','Finland'); +INSERT INTO Customers VALUES(88,'Wellington Importadora','Paula Parente','Rua do Mercado, 12','Resende','08737-363','Brazil'); +INSERT INTO Customers VALUES(89,'White Clover Markets','Karl Jablonski','305 - 14th Ave. S. Suite 3B','Seattle','98128','USA'); +INSERT INTO Customers VALUES(90,'Wilman Kala','Matti Karttunen','Keskuskatu 45','Helsinki','21240','Finland'); +INSERT INTO Customers VALUES(91,'Wolski','Zbyszek','ul. Filtrowa 68','Walla','01-012','Poland'); + +INSERT INTO Employees VALUES(1,'Davolio','Nancy','1968-12-08','EmpID1.pic','Education includes a BA in psychology from Colorado State University. She also completed (The Art of the Cold Call). Nancy is a member of ''Toastmasters International''.'); +INSERT INTO Employees VALUES(2,'Fuller','Andrew','1952-02-19','EmpID2.pic','Andrew received his BTS commercial and a Ph.D. in international marketing from the University of Dallas. He is fluent in French and Italian and reads German. He joined the company as a sales representative, was promoted to sales manager and was then named vice president of sales. Andrew is a member of the Sales Management Roundtable, the Seattle Chamber of Commerce, and the Pacific Rim Importers Association.'); +INSERT INTO Employees VALUES(3,'Leverling','Janet','1963-08-30','EmpID3.pic','Janet has a BS degree in chemistry from Boston College). She has also completed a certificate program in food retailing management. Janet was hired as a sales associate and was promoted to sales representative.'); +INSERT INTO Employees VALUES(4,'Peacock','Margaret','1958-09-19','EmpID4.pic','Margaret holds a BA in English literature from Concordia College and an MA from the American Institute of Culinary Arts. She was temporarily assigned to the London office before returning to her permanent post in Seattle.'); +INSERT INTO Employees VALUES(5,'Buchanan','Steven','1955-03-04','EmpID5.pic','Steven Buchanan graduated from St. Andrews University, Scotland, with a BSC degree. Upon joining the company as a sales representative, he spent 6 months in an orientation program at the Seattle office and then returned to his permanent post in London, where he was promoted to sales manager. Mr. Buchanan has completed the courses ''Successful Telemarketing'' and ''International Sales Management''. He is fluent in French.'); +INSERT INTO Employees VALUES(6,'Suyama','Michael','1963-07-02','EmpID6.pic','Michael is a graduate of Sussex University (MA, economics) and the University of California at Los Angeles (MBA, marketing). He has also taken the courses ''Multi-Cultural Selling'' and ''Time Management for the Sales Professional''. He is fluent in Japanese and can read and write French, Portuguese, and Spanish.'); +INSERT INTO Employees VALUES(7,'King','Robert','1960-05-29','EmpID7.pic','Robert King served in the Peace Corps and traveled extensively before completing his degree in English at the University of Michigan and then joining the company. After completing a course entitled ''Selling in Europe'', he was transferred to the London office.'); +INSERT INTO Employees VALUES(8,'Callahan','Laura','1958-01-09','EmpID8.pic','Laura received a BA in psychology from the University of Washington. She has also completed a course in business French. She reads and writes French.'); +INSERT INTO Employees VALUES(9,'Dodsworth','Anne','1969-07-02','EmpID9.pic','Anne has a BA degree in English from St. Lawrence College. She is fluent in French and German.'); +INSERT INTO Employees VALUES(10,'West','Adam','1928-09-19','EmpID10.pic','An old chum.'); + +INSERT INTO Shippers VALUES(1, 'Speedy Express', '(503) 555-9831'); +INSERT INTO Shippers VALUES(2, 'United Package', '(503) 555-3199'); +INSERT INTO Shippers VALUES(3, 'Federal Shipping', '(503) 555-9931'); + +INSERT INTO Suppliers VALUES(1,'Exotic Liquid','Charlotte Cooper','49 Gilbert St.','Londona','EC1 4SD','UK','(171) 555-2222'); +INSERT INTO Suppliers VALUES(2,'New Orleans Cajun Delights','Shelley Burke','P.O. Box 78934','New Orleans','70117','USA','(100) 555-4822'); +INSERT INTO Suppliers VALUES(3,'Grandma Kelly''s Homestead','Regina Murphy','707 Oxford Rd.','Ann Arbor','48104','USA','(313) 555-5735'); +INSERT INTO Suppliers VALUES(4,'Tokyo Traders','Yoshi Nagase','9-8 Sekimai Musashino-shi','Tokyo','100','Japan','(03) 3555-5011'); +INSERT INTO Suppliers VALUES(5,'Cooperativa de Quesos ''Las Cabras''','Antonio del Valle Saavedra','Calle del Rosal 4','Oviedo','33007','Spain','(98) 598 76 54'); +INSERT INTO Suppliers VALUES(6,'Mayumi''s','Mayumi Ohno','92 Setsuko Chuo-ku','Osaka','545','Japan','(06) 431-7877'); +INSERT INTO Suppliers VALUES(7,'Pavlova, Ltd.','Ian Devling','74 Rose St. Moonie Ponds','Melbourne','3058','Australia','(03) 444-2343'); +INSERT INTO Suppliers VALUES(8,'Specialty Biscuits, Ltd.','Peter Wilson','29 King''s Way','Manchester','M14 GSD','UK','(161) 555-4448'); +INSERT INTO Suppliers VALUES(9,'PB Knäckebröd AB','Lars Peterson','Kaloadagatan 13','Göteborg','S-345 67','Sweden','031-987 65 43'); +INSERT INTO Suppliers VALUES(10,'Refrescos Americanas LTDA','Carlos Diaz','Av. das Americanas 12.890','São Paulo','5442','Brazil','(11) 555 4640'); +INSERT INTO Suppliers VALUES(11,'Heli Süßwaren GmbH & Co. KG','Petra Winkler','Tiergartenstraße 5','Berlin','10785','Germany','(010) 9984510'); +INSERT INTO Suppliers VALUES(12,'Plutzer Lebensmittelgroßmärkte AG','Martin Bein','Bogenallee 51','Frankfurt','60439','Germany','(069) 992755'); +INSERT INTO Suppliers VALUES(13,'Nord-Ost-Fisch Handelsgesellschaft mbH','Sven Petersen','Frahmredder 112a','Cuxhaven','27478','Germany','(04721) 8713'); +INSERT INTO Suppliers VALUES(14,'Formaggi Fortini s.r.l.','Elio Rossi','Viale Dante, 75','Ravenna','48100','Italy','(0544) 60323'); +INSERT INTO Suppliers VALUES(15,'Norske Meierier','Beate Vileid','Hatlevegen 5','Sandvika','1320','Norway','(0)2-953010'); +INSERT INTO Suppliers VALUES(16,'Bigfoot Breweries','Cheryl Saylor','3400 - 8th Avenue Suite 210','Bend','97101','USA','(503) 555-9931'); +INSERT INTO Suppliers VALUES(17,'Svensk Sjöföda AB','Michael Björn','Brovallavägen 231','Stockholm','S-123 45','Sweden','08-123 45 67'); +INSERT INTO Suppliers VALUES(18,'Aux joyeux ecclésiastiques','Guylène Nodier','203, Rue des Francs-Bourgeois','Paris','75004','France','(1) 03.83.00.68'); +INSERT INTO Suppliers VALUES(19,'New England Seafood Cannery','Robb Merchant','Order Processing Dept. 2100 Paul Revere Blvd.','Boston','2134','USA','(617) 555-3267'); +INSERT INTO Suppliers VALUES(20,'Leka Trading','Chandra Leka','471 Serangoon Loop, Suite #402','Singapore','512','Singapore','555-8787'); +INSERT INTO Suppliers VALUES(21,'Lyngbysild','Niels Petersen','Lyngbysild Fiskebakken 10','Lyngby','2800','Denmark','43844108'); +INSERT INTO Suppliers VALUES(22,'Zaanse Snoepfabriek','Dirk Luchte','Verkoop Rijnweg 22','Zaandam','9999 ZZ','Netherlands','(12345) 1212'); +INSERT INTO Suppliers VALUES(23,'Karkki Oy','Anne Heikkonen','Valtakatu 12','Lappeenranta','53120','Finland','(953) 10956'); +INSERT INTO Suppliers VALUES(24,'G''day, Mate','Wendy Mackenzie','170 Prince Edward Parade Hunter''s Hill','Sydney','2042','Australia','(02) 555-5914'); +INSERT INTO Suppliers VALUES(25,'Ma Maison','Jean-Guy Lauzon','2960 Rue St. Laurent','Montréal','H1J 1C3','Canada','(514) 555-9022'); +INSERT INTO Suppliers VALUES(26,'Pasta Buttini s.r.l.','Giovanni Giudici','Via dei Gelsomini, 153','Salerno','84100','Italy','(089) 6547665'); +INSERT INTO Suppliers VALUES(27,'Escargots Nouveaux','Marie Delamare','22, rue H. Voiron','Montceau','71300','France','85.57.00.07'); +INSERT INTO Suppliers VALUES(28,'Gai pâturage','Eliane Noz','Bat. B 3, rue des Alpes','Annecy','74000','France','38.76.98.06'); +INSERT INTO Suppliers VALUES(29,'Forêts d''érables','Chantal Goulet','148 rue Chasseur','Ste-Hyacinthe','J2S 7S8','Canada','(514) 555-2955'); + +INSERT INTO Products VALUES(1,'Chais',1,1,'10 boxes x 20 bags',18.00); +INSERT INTO Products VALUES(2,'Chang',1,1,'24 - 12 oz bottles',19.00); +INSERT INTO Products VALUES(3,'Aniseed Syrup',1,2,'12 - 550 ml bottles',10.00); +INSERT INTO Products VALUES(4,'Chef Anton''s Cajun Seasoning',2,2,'48 - 6 oz jars',22.00); +INSERT INTO Products VALUES(5,'Chef Anton''s Gumbo Mix',2,2,'36 boxes',21.35); +INSERT INTO Products VALUES(6,'Grandma''s Boysenberry Spread',3,2,'12 - 8 oz jars',25.00); +INSERT INTO Products VALUES(7,'Uncle Bob''s Organic Dried Pears',3,7,'12 - 1 lb pkgs.',30.00); +INSERT INTO Products VALUES(8,'Northwoods Cranberry Sauce',3,2,'12 - 12 oz jars',40.00); +INSERT INTO Products VALUES(9,'Mishi Kobe Niku',4,6,'18 - 500 g pkgs.',97.00); +INSERT INTO Products VALUES(10,'Ikura',4,8,'12 - 200 ml jars',31.00); +INSERT INTO Products VALUES(11,'Queso Cabrales',5,4,'1 kg pkg.',21.00); +INSERT INTO Products VALUES(12,'Queso Manchego La Pastora',5,4,'10 - 500 g pkgs.',38.00); +INSERT INTO Products VALUES(13,'Konbu',6,8,'2 kg box',6.00); +INSERT INTO Products VALUES(14,'Tofu',6,7,'40 - 100 g pkgs.',23.25); +INSERT INTO Products VALUES(15,'Genen Shouyu',6,2,'24 - 250 ml bottles',15.50); +INSERT INTO Products VALUES(16,'Pavlova',7,3,'32 - 500 g boxes',17.45); +INSERT INTO Products VALUES(17,'Alice Mutton',7,6,'20 - 1 kg tins',39); +INSERT INTO Products VALUES(18,'Carnarvon Tigers',7,8,'16 kg pkg.',62.50); +INSERT INTO Products VALUES(19,'Teatime Chocolate Biscuits',8,3,'10 boxes x 12 pieces',9.20); +INSERT INTO Products VALUES(20,'Sir Rodney''s Marmalade',8,3,'30 gift boxes',81.00); +INSERT INTO Products VALUES(21,'Sir Rodney''s Scones',8,3,'24 pkgs. x 4 pieces',10.00); +INSERT INTO Products VALUES(22,'Gustaf''s Knäckebröd',9,5,'24 - 500 g pkgs.',21.00); +INSERT INTO Products VALUES(23,'Tunnbröd',9,5,'12 - 250 g pkgs.',9.00); +INSERT INTO Products VALUES(24,'Guaraná Fantástica',10,1,'12 - 355 ml cans',4.50); +INSERT INTO Products VALUES(25,'NuNuCa Nuß-Nougat-Creme',11,3,'20 - 450 g glasses',14.00); +INSERT INTO Products VALUES(26,'Gumbär Gummibärchen',11,3,'100 - 250 g bags',31.23); +INSERT INTO Products VALUES(27,'Schoggi Schokolade',11,3,'100 - 100 g pieces',43.90); +INSERT INTO Products VALUES(28,'Rössle Sauerkraut',12,7,'25 - 825 g cans',45.60); +INSERT INTO Products VALUES(29,'Thüringer Rostbratwurst',12,6,'50 bags x 30 sausgs.',123.79); +INSERT INTO Products VALUES(30,'Nord-Ost Matjeshering',13,8,'10 - 200 g glasses',25.89); +INSERT INTO Products VALUES(31,'Gorgonzola Telino',14,4,'12 - 100 g pkgs',12.50); +INSERT INTO Products VALUES(32,'Mascarpone Fabioli',14,4,'24 - 200 g pkgs.',32.00); +INSERT INTO Products VALUES(33,'Geitost',15,4,'500 g',2.50); +INSERT INTO Products VALUES(34,'Sasquatch Ale',16,1,'24 - 12 oz bottles',14.00); +INSERT INTO Products VALUES(35,'Steeleye Stout',16,1,'24 - 12 oz bottles',18.00); +INSERT INTO Products VALUES(36,'Inlagd Sill',17,8,'24 - 250 g jars',19.00); +INSERT INTO Products VALUES(37,'Gravad lax',17,8,'12 - 500 g pkgs.',26.00); +INSERT INTO Products VALUES(38,'Côte de Blaye',18,1,'12 - 75 cl bottles',263.50); +INSERT INTO Products VALUES(39,'Chartreuse verte',18,1,'750 cc per bottle',18.00); +INSERT INTO Products VALUES(40,'Boston Crab Meat',19,8,'24 - 4 oz tins',18.40); +INSERT INTO Products VALUES(41,'Jack''s New England Clam Chowder',19,8,'12 - 12 oz cans',9.65); +INSERT INTO Products VALUES(42,'Singaporean Hokkien Fried Mee',20,5,'32 - 1 kg pkgs.',14.00); +INSERT INTO Products VALUES(43,'Ipoh Coffee',20,1,'16 - 500 g tins',46.00); +INSERT INTO Products VALUES(44,'Gula Malacca',20,2,'20 - 2 kg bags',19.45); +INSERT INTO Products VALUES(45,'Røgede sild',21,8,'1k pkg.',9.50); +INSERT INTO Products VALUES(46,'Spegesild',21,8,'4 - 450 g glasses',12.00); +INSERT INTO Products VALUES(47,'Zaanse koeken',22,3,'10 - 4 oz boxes',9.50); +INSERT INTO Products VALUES(48,'Chocolade',22,3,'10 pkgs.',12.75); +INSERT INTO Products VALUES(49,'Maxilaku',23,3,'24 - 50 g pkgs.',20.00); +INSERT INTO Products VALUES(50,'Valkoinen suklaa',23,3,'12 - 100 g bars',16.25); +INSERT INTO Products VALUES(51,'Manjimup Dried Apples',24,7,'50 - 300 g pkgs.',53.00); +INSERT INTO Products VALUES(52,'Filo Mix',24,5,'16 - 2 kg boxes',7.00); +INSERT INTO Products VALUES(53,'Perth Pasties',24,6,'48 pieces',32.80); +INSERT INTO Products VALUES(54,'Tourtière',25,6,'16 pies',7.45); +INSERT INTO Products VALUES(55,'Pâté chinois',25,6,'24 boxes x 2 pies',24.00); +INSERT INTO Products VALUES(56,'Gnocchi di nonna Alice',26,5,'24 - 250 g pkgs.',38.00); +INSERT INTO Products VALUES(57,'Ravioli Angelo',26,5,'24 - 250 g pkgs.',19.50); +INSERT INTO Products VALUES(58,'Escargots de Bourgogne',27,8,'24 pieces',13.25); +INSERT INTO Products VALUES(59,'Raclette Courdavault',28,4,'5 kg pkg.',55.00); +INSERT INTO Products VALUES(60,'Camembert Pierrot',28,4,'15 - 300 g rounds',34.00); +INSERT INTO Products VALUES(61,'Sirop d''érable',29,2,'24 - 500 ml bottles',28.50); +INSERT INTO Products VALUES(62,'Tarte au sucre',29,3,'48 pies',49.30); +INSERT INTO Products VALUES(63,'Vegie-spread',7,2,'15 - 625 g jars',43.90); +INSERT INTO Products VALUES(64,'Wimmers gute Semmelknödel',12,5,'20 bags x 4 pieces',33.25); +INSERT INTO Products VALUES(65,'Louisiana Fiery Hot Pepper Sauce',2,2,'32 - 8 oz bottles',21.05); +INSERT INTO Products VALUES(66,'Louisiana Hot Spiced Okra',2,2,'24 - 8 oz jars',17.00); +INSERT INTO Products VALUES(67,'Laughing Lumberjack Lager',16,1,'24 - 12 oz bottles',14.00); +INSERT INTO Products VALUES(68,'Scottish Longbreads',8,3,'10 boxes x 8 pieces',12.50); +INSERT INTO Products VALUES(69,'Gudbrandsdalsost',15,4,'10 kg pkg.',36.00); +INSERT INTO Products VALUES(70,'Outback Lager',7,1,'24 - 355 ml bottles',15.00); +INSERT INTO Products VALUES(71,'Fløtemysost',15,4,'10 - 500 g pkgs.',21.50); +INSERT INTO Products VALUES(72,'Mozzarella di Giovanni',14,4,'24 - 200 g pkgs.',34.80); +INSERT INTO Products VALUES(73,'Röd Kaviar',17,8,'24 - 150 g jars',15.00); +INSERT INTO Products VALUES(74,'Longlife Tofu',4,7,'5 kg pkg.',10.00); +INSERT INTO Products VALUES(75,'Rhönbräu Klosterbier',12,1,'24 - 0.5 l bottles',7.75); +INSERT INTO Products VALUES(76,'Lakkalikööri',23,1,'500 ml',18.00); +INSERT INTO Products VALUES(77,'Original Frankfurter grüne Soße',12,2,'12 boxes',13.00); + +INSERT INTO Orders VALUES(10248,90,5,'1996-07-04',3); +INSERT INTO Orders VALUES(10249,81,6,'1996-07-05',1); +INSERT INTO Orders VALUES(10250,34,4,'1996-07-08',2); +INSERT INTO Orders VALUES(10251,84,3,'1996-07-08',1); +INSERT INTO Orders VALUES(10252,76,4,'1996-07-09',2); +INSERT INTO Orders VALUES(10253,34,3,'1996-07-10',2); +INSERT INTO Orders VALUES(10254,14,5,'1996-07-11',2); +INSERT INTO Orders VALUES(10255,68,9,'1996-07-12',3); +INSERT INTO Orders VALUES(10256,88,3,'1996-07-15',2); +INSERT INTO Orders VALUES(10257,35,4,'1996-07-16',3); +INSERT INTO Orders VALUES(10258,20,1,'1996-07-17',1); +INSERT INTO Orders VALUES(10259,13,4,'1996-07-18',3); +INSERT INTO Orders VALUES(10260,55,4,'1996-07-19',1); +INSERT INTO Orders VALUES(10261,61,4,'1996-07-19',2); +INSERT INTO Orders VALUES(10262,65,8,'1996-07-22',3); +INSERT INTO Orders VALUES(10263,20,9,'1996-07-23',3); +INSERT INTO Orders VALUES(10264,24,6,'1996-07-24',3); +INSERT INTO Orders VALUES(10265,7,2,'1996-07-25',1); +INSERT INTO Orders VALUES(10266,87,3,'1996-07-26',3); +INSERT INTO Orders VALUES(10267,25,4,'1996-07-29',1); +INSERT INTO Orders VALUES(10268,33,8,'1996-07-30',3); +INSERT INTO Orders VALUES(10269,89,5,'1996-07-31',1); +INSERT INTO Orders VALUES(10270,87,1,'1996-08-01',1); +INSERT INTO Orders VALUES(10271,75,6,'1996-08-01',2); +INSERT INTO Orders VALUES(10272,65,6,'1996-08-02',2); +INSERT INTO Orders VALUES(10273,63,3,'1996-08-05',3); +INSERT INTO Orders VALUES(10274,85,6,'1996-08-06',1); +INSERT INTO Orders VALUES(10275,49,1,'1996-08-07',1); +INSERT INTO Orders VALUES(10276,80,8,'1996-08-08',3); +INSERT INTO Orders VALUES(10277,52,2,'1996-08-09',3); +INSERT INTO Orders VALUES(10278,5,8,'1996-08-12',2); +INSERT INTO Orders VALUES(10279,44,8,'1996-08-13',2); +INSERT INTO Orders VALUES(10280,5,2,'1996-08-14',1); +INSERT INTO Orders VALUES(10281,69,4,'1996-08-14',1); +INSERT INTO Orders VALUES(10282,69,4,'1996-08-15',1); +INSERT INTO Orders VALUES(10283,46,3,'1996-08-16',3); +INSERT INTO Orders VALUES(10284,44,4,'1996-08-19',1); +INSERT INTO Orders VALUES(10285,63,1,'1996-08-20',2); +INSERT INTO Orders VALUES(10286,63,8,'1996-08-21',3); +INSERT INTO Orders VALUES(10287,67,8,'1996-08-22',3); +INSERT INTO Orders VALUES(10288,66,4,'1996-08-23',1); +INSERT INTO Orders VALUES(10289,11,7,'1996-08-26',3); +INSERT INTO Orders VALUES(10290,15,8,'1996-08-27',1); +INSERT INTO Orders VALUES(10291,61,6,'1996-08-27',2); +INSERT INTO Orders VALUES(10292,81,1,'1996-08-28',2); +INSERT INTO Orders VALUES(10293,80,1,'1996-08-29',3); +INSERT INTO Orders VALUES(10294,65,4,'1996-08-30',2); +INSERT INTO Orders VALUES(10295,85,2,'1996-09-02',2); +INSERT INTO Orders VALUES(10296,46,6,'1996-09-03',1); +INSERT INTO Orders VALUES(10297,7,5,'1996-09-04',2); +INSERT INTO Orders VALUES(10298,37,6,'1996-09-05',2); +INSERT INTO Orders VALUES(10299,67,4,'1996-09-06',2); +INSERT INTO Orders VALUES(10300,49,2,'1996-09-09',2); +INSERT INTO Orders VALUES(10301,86,8,'1996-09-09',2); +INSERT INTO Orders VALUES(10302,76,4,'1996-09-10',2); +INSERT INTO Orders VALUES(10303,30,7,'1996-09-11',2); +INSERT INTO Orders VALUES(10304,80,1,'1996-09-12',2); +INSERT INTO Orders VALUES(10305,55,8,'1996-09-13',3); +INSERT INTO Orders VALUES(10306,69,1,'1996-09-16',3); +INSERT INTO Orders VALUES(10307,48,2,'1996-09-17',2); +INSERT INTO Orders VALUES(10308,2,7,'1996-09-18',3); +INSERT INTO Orders VALUES(10309,37,3,'1996-09-19',1); +INSERT INTO Orders VALUES(10310,77,8,'1996-09-20',2); +INSERT INTO Orders VALUES(10311,18,1,'1996-09-20',3); +INSERT INTO Orders VALUES(10312,86,2,'1996-09-23',2); +INSERT INTO Orders VALUES(10313,63,2,'1996-09-24',2); +INSERT INTO Orders VALUES(10314,65,1,'1996-09-25',2); +INSERT INTO Orders VALUES(10315,38,4,'1996-09-26',2); +INSERT INTO Orders VALUES(10316,65,1,'1996-09-27',3); +INSERT INTO Orders VALUES(10317,48,6,'1996-09-30',1); +INSERT INTO Orders VALUES(10318,38,8,'1996-10-01',2); +INSERT INTO Orders VALUES(10319,80,7,'1996-10-02',3); +INSERT INTO Orders VALUES(10320,87,5,'1996-10-03',3); +INSERT INTO Orders VALUES(10321,38,3,'1996-10-03',2); +INSERT INTO Orders VALUES(10322,58,7,'1996-10-04',3); +INSERT INTO Orders VALUES(10323,39,4,'1996-10-07',1); +INSERT INTO Orders VALUES(10324,71,9,'1996-10-08',1); +INSERT INTO Orders VALUES(10325,39,1,'1996-10-09',3); +INSERT INTO Orders VALUES(10326,8,4,'1996-10-10',2); +INSERT INTO Orders VALUES(10327,24,2,'1996-10-11',1); +INSERT INTO Orders VALUES(10328,28,4,'1996-10-14',3); +INSERT INTO Orders VALUES(10329,75,4,'1996-10-15',2); +INSERT INTO Orders VALUES(10330,46,3,'1996-10-16',1); +INSERT INTO Orders VALUES(10331,9,9,'1996-10-16',1); +INSERT INTO Orders VALUES(10332,51,3,'1996-10-17',2); +INSERT INTO Orders VALUES(10333,87,5,'1996-10-18',3); +INSERT INTO Orders VALUES(10334,84,8,'1996-10-21',2); +INSERT INTO Orders VALUES(10335,37,7,'1996-10-22',2); +INSERT INTO Orders VALUES(10336,60,7,'1996-10-23',2); +INSERT INTO Orders VALUES(10337,25,4,'1996-10-24',3); +INSERT INTO Orders VALUES(10338,55,4,'1996-10-25',3); +INSERT INTO Orders VALUES(10339,51,2,'1996-10-28',2); +INSERT INTO Orders VALUES(10340,9,1,'1996-10-29',3); +INSERT INTO Orders VALUES(10341,73,7,'1996-10-29',3); +INSERT INTO Orders VALUES(10342,25,4,'1996-10-30',2); +INSERT INTO Orders VALUES(10343,44,4,'1996-10-31',1); +INSERT INTO Orders VALUES(10344,89,4,'1996-11-01',2); +INSERT INTO Orders VALUES(10345,63,2,'1996-11-04',2); +INSERT INTO Orders VALUES(10346,65,3,'1996-11-05',3); +INSERT INTO Orders VALUES(10347,21,4,'1996-11-06',3); +INSERT INTO Orders VALUES(10348,86,4,'1996-11-07',2); +INSERT INTO Orders VALUES(10349,75,7,'1996-11-08',1); +INSERT INTO Orders VALUES(10350,41,6,'1996-11-11',2); +INSERT INTO Orders VALUES(10351,20,1,'1996-11-11',1); +INSERT INTO Orders VALUES(10352,28,3,'1996-11-12',3); +INSERT INTO Orders VALUES(10353,59,7,'1996-11-13',3); +INSERT INTO Orders VALUES(10354,58,8,'1996-11-14',3); +INSERT INTO Orders VALUES(10355,4,6,'1996-11-15',1); +INSERT INTO Orders VALUES(10356,86,6,'1996-11-18',2); +INSERT INTO Orders VALUES(10357,46,1,'1996-11-19',3); +INSERT INTO Orders VALUES(10358,41,5,'1996-11-20',1); +INSERT INTO Orders VALUES(10359,72,5,'1996-11-21',3); +INSERT INTO Orders VALUES(10360,7,4,'1996-11-22',3); +INSERT INTO Orders VALUES(10361,63,1,'1996-11-22',2); +INSERT INTO Orders VALUES(10362,9,3,'1996-11-25',1); +INSERT INTO Orders VALUES(10363,17,4,'1996-11-26',3); +INSERT INTO Orders VALUES(10364,19,1,'1996-11-26',1); +INSERT INTO Orders VALUES(10365,3,3,'1996-11-27',2); +INSERT INTO Orders VALUES(10366,29,8,'1996-11-28',2); +INSERT INTO Orders VALUES(10367,83,7,'1996-11-28',3); +INSERT INTO Orders VALUES(10368,20,2,'1996-11-29',2); +INSERT INTO Orders VALUES(10369,75,8,'1996-12-02',2); +INSERT INTO Orders VALUES(10370,14,6,'1996-12-03',2); +INSERT INTO Orders VALUES(10371,41,1,'1996-12-03',1); +INSERT INTO Orders VALUES(10372,62,5,'1996-12-04',2); +INSERT INTO Orders VALUES(10373,37,4,'1996-12-05',3); +INSERT INTO Orders VALUES(10374,91,1,'1996-12-05',3); +INSERT INTO Orders VALUES(10375,36,3,'1996-12-06',2); +INSERT INTO Orders VALUES(10376,51,1,'1996-12-09',2); +INSERT INTO Orders VALUES(10377,72,1,'1996-12-09',3); +INSERT INTO Orders VALUES(10378,24,5,'1996-12-10',3); +INSERT INTO Orders VALUES(10379,61,2,'1996-12-11',1); +INSERT INTO Orders VALUES(10380,37,8,'1996-12-12',3); +INSERT INTO Orders VALUES(10381,46,3,'1996-12-12',3); +INSERT INTO Orders VALUES(10382,20,4,'1996-12-13',1); +INSERT INTO Orders VALUES(10383,4,8,'1996-12-16',3); +INSERT INTO Orders VALUES(10384,5,3,'1996-12-16',3); +INSERT INTO Orders VALUES(10385,75,1,'1996-12-17',2); +INSERT INTO Orders VALUES(10386,21,9,'1996-12-18',3); +INSERT INTO Orders VALUES(10387,70,1,'1996-12-18',2); +INSERT INTO Orders VALUES(10388,72,2,'1996-12-19',1); +INSERT INTO Orders VALUES(10389,10,4,'1996-12-20',2); +INSERT INTO Orders VALUES(10390,20,6,'1996-12-23',1); +INSERT INTO Orders VALUES(10391,17,3,'1996-12-23',3); +INSERT INTO Orders VALUES(10392,59,2,'1996-12-24',3); +INSERT INTO Orders VALUES(10393,71,1,'1996-12-25',3); +INSERT INTO Orders VALUES(10394,36,1,'1996-12-25',3); +INSERT INTO Orders VALUES(10395,35,6,'1996-12-26',1); +INSERT INTO Orders VALUES(10396,25,1,'1996-12-27',3); +INSERT INTO Orders VALUES(10397,60,5,'1996-12-27',1); +INSERT INTO Orders VALUES(10398,71,2,'1996-12-30',3); +INSERT INTO Orders VALUES(10399,83,8,'1996-12-31',3); +INSERT INTO Orders VALUES(10400,19,1,'1997-01-01',3); +INSERT INTO Orders VALUES(10401,65,1,'1997-01-01',1); +INSERT INTO Orders VALUES(10402,20,8,'1997-01-02',2); +INSERT INTO Orders VALUES(10403,20,4,'1997-01-03',3); +INSERT INTO Orders VALUES(10404,49,2,'1997-01-03',1); +INSERT INTO Orders VALUES(10405,47,1,'1997-01-06',1); +INSERT INTO Orders VALUES(10406,62,7,'1997-01-07',1); +INSERT INTO Orders VALUES(10407,56,2,'1997-01-07',2); +INSERT INTO Orders VALUES(10408,23,8,'1997-01-08',1); +INSERT INTO Orders VALUES(10409,54,3,'1997-01-09',1); +INSERT INTO Orders VALUES(10410,10,3,'1997-01-10',3); +INSERT INTO Orders VALUES(10411,10,9,'1997-01-10',3); +INSERT INTO Orders VALUES(10412,87,8,'1997-01-13',2); +INSERT INTO Orders VALUES(10413,41,3,'1997-01-14',2); +INSERT INTO Orders VALUES(10414,21,2,'1997-01-14',3); +INSERT INTO Orders VALUES(10415,36,3,'1997-01-15',1); +INSERT INTO Orders VALUES(10416,87,8,'1997-01-16',3); +INSERT INTO Orders VALUES(10417,73,4,'1997-01-16',3); +INSERT INTO Orders VALUES(10418,63,4,'1997-01-17',1); +INSERT INTO Orders VALUES(10419,68,4,'1997-01-20',2); +INSERT INTO Orders VALUES(10420,88,3,'1997-01-21',1); +INSERT INTO Orders VALUES(10421,61,8,'1997-01-21',1); +INSERT INTO Orders VALUES(10422,27,2,'1997-01-22',1); +INSERT INTO Orders VALUES(10423,31,6,'1997-01-23',3); +INSERT INTO Orders VALUES(10424,51,7,'1997-01-23',2); +INSERT INTO Orders VALUES(10425,41,6,'1997-01-24',2); +INSERT INTO Orders VALUES(10426,29,4,'1997-01-27',1); +INSERT INTO Orders VALUES(10427,59,4,'1997-01-27',2); +INSERT INTO Orders VALUES(10428,66,7,'1997-01-28',1); +INSERT INTO Orders VALUES(10429,37,3,'1997-01-29',2); +INSERT INTO Orders VALUES(10430,20,4,'1997-01-30',1); +INSERT INTO Orders VALUES(10431,10,4,'1997-01-30',2); +INSERT INTO Orders VALUES(10432,75,3,'1997-01-31',2); +INSERT INTO Orders VALUES(10433,60,3,'1997-02-03',3); +INSERT INTO Orders VALUES(10434,24,3,'1997-02-03',2); +INSERT INTO Orders VALUES(10435,16,8,'1997-02-04',2); +INSERT INTO Orders VALUES(10436,7,3,'1997-02-05',2); +INSERT INTO Orders VALUES(10437,87,8,'1997-02-05',1); +INSERT INTO Orders VALUES(10438,79,3,'1997-02-06',2); +INSERT INTO Orders VALUES(10439,51,6,'1997-02-07',3); +INSERT INTO Orders VALUES(10440,71,4,'1997-02-10',2); +INSERT INTO Orders VALUES(10441,55,3,'1997-02-10',2); +INSERT INTO Orders VALUES(10442,20,3,'1997-02-11',2); +INSERT INTO Orders VALUES(10443,66,8,'1997-02-12',1); + +INSERT INTO OrderDetails VALUES(1,10248,11,12); +INSERT INTO OrderDetails VALUES(2,10248,42,10); +INSERT INTO OrderDetails VALUES(3,10248,72,5); +INSERT INTO OrderDetails VALUES(4,10249,14,9); +INSERT INTO OrderDetails VALUES(5,10249,51,40); +INSERT INTO OrderDetails VALUES(6,10250,41,10); +INSERT INTO OrderDetails VALUES(7,10250,51,35); +INSERT INTO OrderDetails VALUES(8,10250,65,15); +INSERT INTO OrderDetails VALUES(9,10251,22,6); +INSERT INTO OrderDetails VALUES(10,10251,57,15); +INSERT INTO OrderDetails VALUES(11,10251,65,20); +INSERT INTO OrderDetails VALUES(12,10252,20,40); +INSERT INTO OrderDetails VALUES(13,10252,33,25); +INSERT INTO OrderDetails VALUES(14,10252,60,40); +INSERT INTO OrderDetails VALUES(15,10253,31,20); +INSERT INTO OrderDetails VALUES(16,10253,39,42); +INSERT INTO OrderDetails VALUES(17,10253,49,40); +INSERT INTO OrderDetails VALUES(18,10254,24,15); +INSERT INTO OrderDetails VALUES(19,10254,55,21); +INSERT INTO OrderDetails VALUES(20,10254,74,21); +INSERT INTO OrderDetails VALUES(21,10255,2,20); +INSERT INTO OrderDetails VALUES(22,10255,16,35); +INSERT INTO OrderDetails VALUES(23,10255,36,25); +INSERT INTO OrderDetails VALUES(24,10255,59,30); +INSERT INTO OrderDetails VALUES(25,10256,53,15); +INSERT INTO OrderDetails VALUES(26,10256,77,12); +INSERT INTO OrderDetails VALUES(27,10257,27,25); +INSERT INTO OrderDetails VALUES(28,10257,39,6); +INSERT INTO OrderDetails VALUES(29,10257,77,15); +INSERT INTO OrderDetails VALUES(30,10258,2,50); +INSERT INTO OrderDetails VALUES(31,10258,5,65); +INSERT INTO OrderDetails VALUES(32,10258,32,6); +INSERT INTO OrderDetails VALUES(33,10259,21,10); +INSERT INTO OrderDetails VALUES(34,10259,37,1); +INSERT INTO OrderDetails VALUES(35,10260,41,16); +INSERT INTO OrderDetails VALUES(36,10260,57,50); +INSERT INTO OrderDetails VALUES(37,10260,62,15); +INSERT INTO OrderDetails VALUES(38,10260,70,21); +INSERT INTO OrderDetails VALUES(39,10261,21,20); +INSERT INTO OrderDetails VALUES(40,10261,35,20); +INSERT INTO OrderDetails VALUES(41,10262,5,12); +INSERT INTO OrderDetails VALUES(42,10262,7,15); +INSERT INTO OrderDetails VALUES(43,10262,56,2); +INSERT INTO OrderDetails VALUES(44,10263,16,60); +INSERT INTO OrderDetails VALUES(45,10263,24,28); +INSERT INTO OrderDetails VALUES(46,10263,30,60); +INSERT INTO OrderDetails VALUES(47,10263,74,36); +INSERT INTO OrderDetails VALUES(48,10264,2,35); +INSERT INTO OrderDetails VALUES(49,10264,41,25); +INSERT INTO OrderDetails VALUES(50,10265,17,30); +INSERT INTO OrderDetails VALUES(51,10265,70,20); +INSERT INTO OrderDetails VALUES(52,10266,12,12); +INSERT INTO OrderDetails VALUES(53,10267,40,50); +INSERT INTO OrderDetails VALUES(54,10267,59,70); +INSERT INTO OrderDetails VALUES(55,10267,76,15); +INSERT INTO OrderDetails VALUES(56,10268,29,10); +INSERT INTO OrderDetails VALUES(57,10268,72,4); +INSERT INTO OrderDetails VALUES(58,10269,33,60); +INSERT INTO OrderDetails VALUES(59,10269,72,20); +INSERT INTO OrderDetails VALUES(60,10270,36,30); +INSERT INTO OrderDetails VALUES(61,10270,43,25); +INSERT INTO OrderDetails VALUES(62,10271,33,24); +INSERT INTO OrderDetails VALUES(63,10272,20,6); +INSERT INTO OrderDetails VALUES(64,10272,31,40); +INSERT INTO OrderDetails VALUES(65,10272,72,24); +INSERT INTO OrderDetails VALUES(66,10273,10,24); +INSERT INTO OrderDetails VALUES(67,10273,31,15); +INSERT INTO OrderDetails VALUES(68,10273,33,20); +INSERT INTO OrderDetails VALUES(69,10273,40,60); +INSERT INTO OrderDetails VALUES(70,10273,76,33); +INSERT INTO OrderDetails VALUES(71,10274,71,20); +INSERT INTO OrderDetails VALUES(72,10274,72,7); +INSERT INTO OrderDetails VALUES(73,10275,24,12); +INSERT INTO OrderDetails VALUES(74,10275,59,6); +INSERT INTO OrderDetails VALUES(75,10276,10,15); +INSERT INTO OrderDetails VALUES(76,10276,13,10); +INSERT INTO OrderDetails VALUES(77,10277,28,20); +INSERT INTO OrderDetails VALUES(78,10277,62,12); +INSERT INTO OrderDetails VALUES(79,10278,44,16); +INSERT INTO OrderDetails VALUES(80,10278,59,15); +INSERT INTO OrderDetails VALUES(81,10278,63,8); +INSERT INTO OrderDetails VALUES(82,10278,73,25); +INSERT INTO OrderDetails VALUES(83,10279,17,15); +INSERT INTO OrderDetails VALUES(84,10280,24,12); +INSERT INTO OrderDetails VALUES(85,10280,55,20); +INSERT INTO OrderDetails VALUES(86,10280,75,30); +INSERT INTO OrderDetails VALUES(87,10281,19,1); +INSERT INTO OrderDetails VALUES(88,10281,24,6); +INSERT INTO OrderDetails VALUES(89,10281,35,4); +INSERT INTO OrderDetails VALUES(90,10282,30,6); +INSERT INTO OrderDetails VALUES(91,10282,57,2); +INSERT INTO OrderDetails VALUES(92,10283,15,20); +INSERT INTO OrderDetails VALUES(93,10283,19,18); +INSERT INTO OrderDetails VALUES(94,10283,60,35); +INSERT INTO OrderDetails VALUES(95,10283,72,3); +INSERT INTO OrderDetails VALUES(96,10284,27,15); +INSERT INTO OrderDetails VALUES(97,10284,44,21); +INSERT INTO OrderDetails VALUES(98,10284,60,20); +INSERT INTO OrderDetails VALUES(99,10284,67,5); +INSERT INTO OrderDetails VALUES(100,10285,1,45); +INSERT INTO OrderDetails VALUES(101,10285,40,40); +INSERT INTO OrderDetails VALUES(102,10285,53,36); +INSERT INTO OrderDetails VALUES(103,10286,35,100); +INSERT INTO OrderDetails VALUES(104,10286,62,40); +INSERT INTO OrderDetails VALUES(105,10287,16,40); +INSERT INTO OrderDetails VALUES(106,10287,34,20); +INSERT INTO OrderDetails VALUES(107,10287,46,15); +INSERT INTO OrderDetails VALUES(108,10288,54,10); +INSERT INTO OrderDetails VALUES(109,10288,68,3); +INSERT INTO OrderDetails VALUES(110,10289,3,30); +INSERT INTO OrderDetails VALUES(111,10289,64,9); +INSERT INTO OrderDetails VALUES(112,10290,5,20); +INSERT INTO OrderDetails VALUES(113,10290,29,15); +INSERT INTO OrderDetails VALUES(114,10290,49,15); +INSERT INTO OrderDetails VALUES(115,10290,77,10); +INSERT INTO OrderDetails VALUES(116,10291,13,20); +INSERT INTO OrderDetails VALUES(117,10291,44,24); +INSERT INTO OrderDetails VALUES(118,10291,51,2); +INSERT INTO OrderDetails VALUES(119,10292,20,20); +INSERT INTO OrderDetails VALUES(120,10293,18,12); +INSERT INTO OrderDetails VALUES(121,10293,24,10); +INSERT INTO OrderDetails VALUES(122,10293,63,5); +INSERT INTO OrderDetails VALUES(123,10293,75,6); +INSERT INTO OrderDetails VALUES(124,10294,1,18); +INSERT INTO OrderDetails VALUES(125,10294,17,15); +INSERT INTO OrderDetails VALUES(126,10294,43,15); +INSERT INTO OrderDetails VALUES(127,10294,60,21); +INSERT INTO OrderDetails VALUES(128,10294,75,6); +INSERT INTO OrderDetails VALUES(129,10295,56,4); +INSERT INTO OrderDetails VALUES(130,10296,11,12); +INSERT INTO OrderDetails VALUES(131,10296,16,30); +INSERT INTO OrderDetails VALUES(132,10296,69,15); +INSERT INTO OrderDetails VALUES(133,10297,39,60); +INSERT INTO OrderDetails VALUES(134,10297,72,20); +INSERT INTO OrderDetails VALUES(135,10298,2,40); +INSERT INTO OrderDetails VALUES(136,10298,36,40); +INSERT INTO OrderDetails VALUES(137,10298,59,30); +INSERT INTO OrderDetails VALUES(138,10298,62,15); +INSERT INTO OrderDetails VALUES(139,10299,19,15); +INSERT INTO OrderDetails VALUES(140,10299,70,20); +INSERT INTO OrderDetails VALUES(141,10300,66,30); +INSERT INTO OrderDetails VALUES(142,10300,68,20); +INSERT INTO OrderDetails VALUES(143,10301,40,10); +INSERT INTO OrderDetails VALUES(144,10301,56,20); +INSERT INTO OrderDetails VALUES(145,10302,17,40); +INSERT INTO OrderDetails VALUES(146,10302,28,28); +INSERT INTO OrderDetails VALUES(147,10302,43,12); +INSERT INTO OrderDetails VALUES(148,10303,40,40); +INSERT INTO OrderDetails VALUES(149,10303,65,30); +INSERT INTO OrderDetails VALUES(150,10303,68,15); +INSERT INTO OrderDetails VALUES(151,10304,49,30); +INSERT INTO OrderDetails VALUES(152,10304,59,10); +INSERT INTO OrderDetails VALUES(153,10304,71,2); +INSERT INTO OrderDetails VALUES(154,10305,18,25); +INSERT INTO OrderDetails VALUES(155,10305,29,25); +INSERT INTO OrderDetails VALUES(156,10305,39,30); +INSERT INTO OrderDetails VALUES(157,10306,30,10); +INSERT INTO OrderDetails VALUES(158,10306,53,10); +INSERT INTO OrderDetails VALUES(159,10306,54,5); +INSERT INTO OrderDetails VALUES(160,10307,62,10); +INSERT INTO OrderDetails VALUES(161,10307,68,3); +INSERT INTO OrderDetails VALUES(162,10308,69,1); +INSERT INTO OrderDetails VALUES(163,10308,70,5); +INSERT INTO OrderDetails VALUES(164,10309,4,20); +INSERT INTO OrderDetails VALUES(165,10309,6,30); +INSERT INTO OrderDetails VALUES(166,10309,42,2); +INSERT INTO OrderDetails VALUES(167,10309,43,20); +INSERT INTO OrderDetails VALUES(168,10309,71,3); +INSERT INTO OrderDetails VALUES(169,10310,16,10); +INSERT INTO OrderDetails VALUES(170,10310,62,5); +INSERT INTO OrderDetails VALUES(171,10311,42,6); +INSERT INTO OrderDetails VALUES(172,10311,69,7); +INSERT INTO OrderDetails VALUES(173,10312,28,4); +INSERT INTO OrderDetails VALUES(174,10312,43,24); +INSERT INTO OrderDetails VALUES(175,10312,53,20); +INSERT INTO OrderDetails VALUES(176,10312,75,10); +INSERT INTO OrderDetails VALUES(177,10313,36,12); +INSERT INTO OrderDetails VALUES(178,10314,32,40); +INSERT INTO OrderDetails VALUES(179,10314,58,30); +INSERT INTO OrderDetails VALUES(180,10314,62,25); +INSERT INTO OrderDetails VALUES(181,10315,34,14); +INSERT INTO OrderDetails VALUES(182,10315,70,30); +INSERT INTO OrderDetails VALUES(183,10316,41,10); +INSERT INTO OrderDetails VALUES(184,10316,62,70); +INSERT INTO OrderDetails VALUES(185,10317,1,20); +INSERT INTO OrderDetails VALUES(186,10318,41,20); +INSERT INTO OrderDetails VALUES(187,10318,76,6); +INSERT INTO OrderDetails VALUES(188,10319,17,8); +INSERT INTO OrderDetails VALUES(189,10319,28,14); +INSERT INTO OrderDetails VALUES(190,10319,76,30); +INSERT INTO OrderDetails VALUES(191,10320,71,30); +INSERT INTO OrderDetails VALUES(192,10321,35,10); +INSERT INTO OrderDetails VALUES(193,10322,52,20); +INSERT INTO OrderDetails VALUES(194,10323,15,5); +INSERT INTO OrderDetails VALUES(195,10323,25,4); +INSERT INTO OrderDetails VALUES(196,10323,39,4); +INSERT INTO OrderDetails VALUES(197,10324,16,21); +INSERT INTO OrderDetails VALUES(198,10324,35,70); +INSERT INTO OrderDetails VALUES(199,10324,46,30); +INSERT INTO OrderDetails VALUES(200,10324,59,40); +INSERT INTO OrderDetails VALUES(201,10324,63,80); +INSERT INTO OrderDetails VALUES(202,10325,6,6); +INSERT INTO OrderDetails VALUES(203,10325,13,12); +INSERT INTO OrderDetails VALUES(204,10325,14,9); +INSERT INTO OrderDetails VALUES(205,10325,31,4); +INSERT INTO OrderDetails VALUES(206,10325,72,40); +INSERT INTO OrderDetails VALUES(207,10326,4,24); +INSERT INTO OrderDetails VALUES(208,10326,57,16); +INSERT INTO OrderDetails VALUES(209,10326,75,50); +INSERT INTO OrderDetails VALUES(210,10327,2,25); +INSERT INTO OrderDetails VALUES(211,10327,11,50); +INSERT INTO OrderDetails VALUES(212,10327,30,35); +INSERT INTO OrderDetails VALUES(213,10327,58,30); +INSERT INTO OrderDetails VALUES(214,10328,59,9); +INSERT INTO OrderDetails VALUES(215,10328,65,40); +INSERT INTO OrderDetails VALUES(216,10328,68,10); +INSERT INTO OrderDetails VALUES(217,10329,19,10); +INSERT INTO OrderDetails VALUES(218,10329,30,8); +INSERT INTO OrderDetails VALUES(219,10329,38,20); +INSERT INTO OrderDetails VALUES(220,10329,56,12); +INSERT INTO OrderDetails VALUES(221,10330,26,50); +INSERT INTO OrderDetails VALUES(222,10330,72,25); +INSERT INTO OrderDetails VALUES(223,10331,54,15); +INSERT INTO OrderDetails VALUES(224,10332,18,40); +INSERT INTO OrderDetails VALUES(225,10332,42,10); +INSERT INTO OrderDetails VALUES(226,10332,47,16); +INSERT INTO OrderDetails VALUES(227,10333,14,10); +INSERT INTO OrderDetails VALUES(228,10333,21,10); +INSERT INTO OrderDetails VALUES(229,10333,71,40); +INSERT INTO OrderDetails VALUES(230,10334,52,8); +INSERT INTO OrderDetails VALUES(231,10334,68,10); +INSERT INTO OrderDetails VALUES(232,10335,2,7); +INSERT INTO OrderDetails VALUES(233,10335,31,25); +INSERT INTO OrderDetails VALUES(234,10335,32,6); +INSERT INTO OrderDetails VALUES(235,10335,51,48); +INSERT INTO OrderDetails VALUES(236,10336,4,18); +INSERT INTO OrderDetails VALUES(237,10337,23,40); +INSERT INTO OrderDetails VALUES(238,10337,26,24); +INSERT INTO OrderDetails VALUES(239,10337,36,20); +INSERT INTO OrderDetails VALUES(240,10337,37,28); +INSERT INTO OrderDetails VALUES(241,10337,72,25); +INSERT INTO OrderDetails VALUES(242,10338,17,20); +INSERT INTO OrderDetails VALUES(243,10338,30,15); +INSERT INTO OrderDetails VALUES(244,10339,4,10); +INSERT INTO OrderDetails VALUES(245,10339,17,70); +INSERT INTO OrderDetails VALUES(246,10339,62,28); +INSERT INTO OrderDetails VALUES(247,10340,18,20); +INSERT INTO OrderDetails VALUES(248,10340,41,12); +INSERT INTO OrderDetails VALUES(249,10340,43,40); +INSERT INTO OrderDetails VALUES(250,10341,33,8); +INSERT INTO OrderDetails VALUES(251,10341,59,9); +INSERT INTO OrderDetails VALUES(252,10342,2,24); +INSERT INTO OrderDetails VALUES(253,10342,31,56); +INSERT INTO OrderDetails VALUES(254,10342,36,40); +INSERT INTO OrderDetails VALUES(255,10342,55,40); +INSERT INTO OrderDetails VALUES(256,10343,64,50); +INSERT INTO OrderDetails VALUES(257,10343,68,4); +INSERT INTO OrderDetails VALUES(258,10343,76,15); +INSERT INTO OrderDetails VALUES(259,10344,4,35); +INSERT INTO OrderDetails VALUES(260,10344,8,70); +INSERT INTO OrderDetails VALUES(261,10345,8,70); +INSERT INTO OrderDetails VALUES(262,10345,19,80); +INSERT INTO OrderDetails VALUES(263,10345,42,9); +INSERT INTO OrderDetails VALUES(264,10346,17,36); +INSERT INTO OrderDetails VALUES(265,10346,56,20); +INSERT INTO OrderDetails VALUES(266,10347,25,10); +INSERT INTO OrderDetails VALUES(267,10347,39,50); +INSERT INTO OrderDetails VALUES(268,10347,40,4); +INSERT INTO OrderDetails VALUES(269,10347,75,6); +INSERT INTO OrderDetails VALUES(270,10348,1,15); +INSERT INTO OrderDetails VALUES(271,10348,23,25); +INSERT INTO OrderDetails VALUES(272,10349,54,24); +INSERT INTO OrderDetails VALUES(273,10350,50,15); +INSERT INTO OrderDetails VALUES(274,10350,69,18); +INSERT INTO OrderDetails VALUES(275,10351,38,20); +INSERT INTO OrderDetails VALUES(276,10351,41,13); +INSERT INTO OrderDetails VALUES(277,10351,44,77); +INSERT INTO OrderDetails VALUES(278,10351,65,10); +INSERT INTO OrderDetails VALUES(279,10352,24,10); +INSERT INTO OrderDetails VALUES(280,10352,54,20); +INSERT INTO OrderDetails VALUES(281,10353,11,12); +INSERT INTO OrderDetails VALUES(282,10353,38,50); +INSERT INTO OrderDetails VALUES(283,10354,1,12); +INSERT INTO OrderDetails VALUES(284,10354,29,4); +INSERT INTO OrderDetails VALUES(285,10355,24,25); +INSERT INTO OrderDetails VALUES(286,10355,57,25); +INSERT INTO OrderDetails VALUES(287,10356,31,30); +INSERT INTO OrderDetails VALUES(288,10356,55,12); +INSERT INTO OrderDetails VALUES(289,10356,69,20); +INSERT INTO OrderDetails VALUES(290,10357,10,30); +INSERT INTO OrderDetails VALUES(291,10357,26,16); +INSERT INTO OrderDetails VALUES(292,10357,60,8); +INSERT INTO OrderDetails VALUES(293,10358,24,10); +INSERT INTO OrderDetails VALUES(294,10358,34,10); +INSERT INTO OrderDetails VALUES(295,10358,36,20); +INSERT INTO OrderDetails VALUES(296,10359,16,56); +INSERT INTO OrderDetails VALUES(297,10359,31,70); +INSERT INTO OrderDetails VALUES(298,10359,60,80); +INSERT INTO OrderDetails VALUES(299,10360,28,30); +INSERT INTO OrderDetails VALUES(300,10360,29,35); +INSERT INTO OrderDetails VALUES(301,10360,38,10); +INSERT INTO OrderDetails VALUES(302,10360,49,35); +INSERT INTO OrderDetails VALUES(303,10360,54,28); +INSERT INTO OrderDetails VALUES(304,10361,39,54); +INSERT INTO OrderDetails VALUES(305,10361,60,55); +INSERT INTO OrderDetails VALUES(306,10362,25,50); +INSERT INTO OrderDetails VALUES(307,10362,51,20); +INSERT INTO OrderDetails VALUES(308,10362,54,24); +INSERT INTO OrderDetails VALUES(309,10363,31,20); +INSERT INTO OrderDetails VALUES(310,10363,75,12); +INSERT INTO OrderDetails VALUES(311,10363,76,12); +INSERT INTO OrderDetails VALUES(312,10364,69,30); +INSERT INTO OrderDetails VALUES(313,10364,71,5); +INSERT INTO OrderDetails VALUES(314,10365,11,24); +INSERT INTO OrderDetails VALUES(315,10366,65,5); +INSERT INTO OrderDetails VALUES(316,10366,77,5); +INSERT INTO OrderDetails VALUES(317,10367,34,36); +INSERT INTO OrderDetails VALUES(318,10367,54,18); +INSERT INTO OrderDetails VALUES(319,10367,65,15); +INSERT INTO OrderDetails VALUES(320,10367,77,7); +INSERT INTO OrderDetails VALUES(321,10368,21,5); +INSERT INTO OrderDetails VALUES(322,10368,28,13); +INSERT INTO OrderDetails VALUES(323,10368,57,25); +INSERT INTO OrderDetails VALUES(324,10368,64,35); +INSERT INTO OrderDetails VALUES(325,10369,29,20); +INSERT INTO OrderDetails VALUES(326,10369,56,18); +INSERT INTO OrderDetails VALUES(327,10370,1,15); +INSERT INTO OrderDetails VALUES(328,10370,64,30); +INSERT INTO OrderDetails VALUES(329,10370,74,20); +INSERT INTO OrderDetails VALUES(330,10371,36,6); +INSERT INTO OrderDetails VALUES(331,10372,20,12); +INSERT INTO OrderDetails VALUES(332,10372,38,40); +INSERT INTO OrderDetails VALUES(333,10372,60,70); +INSERT INTO OrderDetails VALUES(334,10372,72,42); +INSERT INTO OrderDetails VALUES(335,10373,58,80); +INSERT INTO OrderDetails VALUES(336,10373,71,50); +INSERT INTO OrderDetails VALUES(337,10374,31,30); +INSERT INTO OrderDetails VALUES(338,10374,58,15); +INSERT INTO OrderDetails VALUES(339,10375,14,15); +INSERT INTO OrderDetails VALUES(340,10375,54,10); +INSERT INTO OrderDetails VALUES(341,10376,31,42); +INSERT INTO OrderDetails VALUES(342,10377,28,20); +INSERT INTO OrderDetails VALUES(343,10377,39,20); +INSERT INTO OrderDetails VALUES(344,10378,71,6); +INSERT INTO OrderDetails VALUES(345,10379,41,8); +INSERT INTO OrderDetails VALUES(346,10379,63,16); +INSERT INTO OrderDetails VALUES(347,10379,65,20); +INSERT INTO OrderDetails VALUES(348,10380,30,18); +INSERT INTO OrderDetails VALUES(349,10380,53,20); +INSERT INTO OrderDetails VALUES(350,10380,60,6); +INSERT INTO OrderDetails VALUES(351,10380,70,30); +INSERT INTO OrderDetails VALUES(352,10381,74,14); +INSERT INTO OrderDetails VALUES(353,10382,5,32); +INSERT INTO OrderDetails VALUES(354,10382,18,9); +INSERT INTO OrderDetails VALUES(355,10382,29,14); +INSERT INTO OrderDetails VALUES(356,10382,33,60); +INSERT INTO OrderDetails VALUES(357,10382,74,50); +INSERT INTO OrderDetails VALUES(358,10383,13,20); +INSERT INTO OrderDetails VALUES(359,10383,50,15); +INSERT INTO OrderDetails VALUES(360,10383,56,20); +INSERT INTO OrderDetails VALUES(361,10384,20,28); +INSERT INTO OrderDetails VALUES(362,10384,60,15); +INSERT INTO OrderDetails VALUES(363,10385,7,10); +INSERT INTO OrderDetails VALUES(364,10385,60,20); +INSERT INTO OrderDetails VALUES(365,10385,68,8); +INSERT INTO OrderDetails VALUES(366,10386,24,15); +INSERT INTO OrderDetails VALUES(367,10386,34,10); +INSERT INTO OrderDetails VALUES(368,10387,24,15); +INSERT INTO OrderDetails VALUES(369,10387,28,6); +INSERT INTO OrderDetails VALUES(370,10387,59,12); +INSERT INTO OrderDetails VALUES(371,10387,71,15); +INSERT INTO OrderDetails VALUES(372,10388,45,15); +INSERT INTO OrderDetails VALUES(373,10388,52,20); +INSERT INTO OrderDetails VALUES(374,10388,53,40); +INSERT INTO OrderDetails VALUES(375,10389,10,16); +INSERT INTO OrderDetails VALUES(376,10389,55,15); +INSERT INTO OrderDetails VALUES(377,10389,62,20); +INSERT INTO OrderDetails VALUES(378,10389,70,30); +INSERT INTO OrderDetails VALUES(379,10390,31,60); +INSERT INTO OrderDetails VALUES(380,10390,35,40); +INSERT INTO OrderDetails VALUES(381,10390,46,45); +INSERT INTO OrderDetails VALUES(382,10390,72,24); +INSERT INTO OrderDetails VALUES(383,10391,13,18); +INSERT INTO OrderDetails VALUES(384,10392,69,50); +INSERT INTO OrderDetails VALUES(385,10393,2,25); +INSERT INTO OrderDetails VALUES(386,10393,14,42); +INSERT INTO OrderDetails VALUES(387,10393,25,7); +INSERT INTO OrderDetails VALUES(388,10393,26,70); +INSERT INTO OrderDetails VALUES(389,10393,31,32); +INSERT INTO OrderDetails VALUES(390,10394,13,10); +INSERT INTO OrderDetails VALUES(391,10394,62,10); +INSERT INTO OrderDetails VALUES(392,10395,46,28); +INSERT INTO OrderDetails VALUES(393,10395,53,70); +INSERT INTO OrderDetails VALUES(394,10395,69,8); +INSERT INTO OrderDetails VALUES(395,10396,23,40); +INSERT INTO OrderDetails VALUES(396,10396,71,60); +INSERT INTO OrderDetails VALUES(397,10396,72,21); +INSERT INTO OrderDetails VALUES(398,10397,21,10); +INSERT INTO OrderDetails VALUES(399,10397,51,18); +INSERT INTO OrderDetails VALUES(400,10398,35,30); +INSERT INTO OrderDetails VALUES(401,10398,55,120); +INSERT INTO OrderDetails VALUES(402,10399,68,60); +INSERT INTO OrderDetails VALUES(403,10399,71,30); +INSERT INTO OrderDetails VALUES(404,10399,76,35); +INSERT INTO OrderDetails VALUES(405,10399,77,14); +INSERT INTO OrderDetails VALUES(406,10400,29,21); +INSERT INTO OrderDetails VALUES(407,10400,35,35); +INSERT INTO OrderDetails VALUES(408,10400,49,30); +INSERT INTO OrderDetails VALUES(409,10401,30,18); +INSERT INTO OrderDetails VALUES(410,10401,56,70); +INSERT INTO OrderDetails VALUES(411,10401,65,20); +INSERT INTO OrderDetails VALUES(412,10401,71,60); +INSERT INTO OrderDetails VALUES(413,10402,23,60); +INSERT INTO OrderDetails VALUES(414,10402,63,65); +INSERT INTO OrderDetails VALUES(415,10403,16,21); +INSERT INTO OrderDetails VALUES(416,10403,48,70); +INSERT INTO OrderDetails VALUES(417,10404,26,30); +INSERT INTO OrderDetails VALUES(418,10404,42,40); +INSERT INTO OrderDetails VALUES(419,10404,49,30); +INSERT INTO OrderDetails VALUES(420,10405,3,50); +INSERT INTO OrderDetails VALUES(421,10406,1,10); +INSERT INTO OrderDetails VALUES(422,10406,21,30); +INSERT INTO OrderDetails VALUES(423,10406,28,42); +INSERT INTO OrderDetails VALUES(424,10406,36,5); +INSERT INTO OrderDetails VALUES(425,10406,40,2); +INSERT INTO OrderDetails VALUES(426,10407,11,30); +INSERT INTO OrderDetails VALUES(427,10407,69,15); +INSERT INTO OrderDetails VALUES(428,10407,71,15); +INSERT INTO OrderDetails VALUES(429,10408,37,10); +INSERT INTO OrderDetails VALUES(430,10408,54,6); +INSERT INTO OrderDetails VALUES(431,10408,62,35); +INSERT INTO OrderDetails VALUES(432,10409,14,12); +INSERT INTO OrderDetails VALUES(433,10409,21,12); +INSERT INTO OrderDetails VALUES(434,10410,33,49); +INSERT INTO OrderDetails VALUES(435,10410,59,16); +INSERT INTO OrderDetails VALUES(436,10411,41,25); +INSERT INTO OrderDetails VALUES(437,10411,44,40); +INSERT INTO OrderDetails VALUES(438,10411,59,9); +INSERT INTO OrderDetails VALUES(439,10412,14,20); +INSERT INTO OrderDetails VALUES(440,10413,1,24); +INSERT INTO OrderDetails VALUES(441,10413,62,40); +INSERT INTO OrderDetails VALUES(442,10413,76,14); +INSERT INTO OrderDetails VALUES(443,10414,19,18); +INSERT INTO OrderDetails VALUES(444,10414,33,50); +INSERT INTO OrderDetails VALUES(445,10415,17,2); +INSERT INTO OrderDetails VALUES(446,10415,33,20); +INSERT INTO OrderDetails VALUES(447,10416,19,20); +INSERT INTO OrderDetails VALUES(448,10416,53,10); +INSERT INTO OrderDetails VALUES(449,10416,57,20); +INSERT INTO OrderDetails VALUES(450,10417,38,50); +INSERT INTO OrderDetails VALUES(451,10417,46,2); +INSERT INTO OrderDetails VALUES(452,10417,68,36); +INSERT INTO OrderDetails VALUES(453,10417,77,35); +INSERT INTO OrderDetails VALUES(454,10418,2,60); +INSERT INTO OrderDetails VALUES(455,10418,47,55); +INSERT INTO OrderDetails VALUES(456,10418,61,16); +INSERT INTO OrderDetails VALUES(457,10418,74,15); +INSERT INTO OrderDetails VALUES(458,10419,60,60); +INSERT INTO OrderDetails VALUES(459,10419,69,20); +INSERT INTO OrderDetails VALUES(460,10420,9,20); +INSERT INTO OrderDetails VALUES(461,10420,13,2); +INSERT INTO OrderDetails VALUES(462,10420,70,8); +INSERT INTO OrderDetails VALUES(463,10420,73,20); +INSERT INTO OrderDetails VALUES(464,10421,19,4); +INSERT INTO OrderDetails VALUES(465,10421,26,30); +INSERT INTO OrderDetails VALUES(466,10421,53,15); +INSERT INTO OrderDetails VALUES(467,10421,77,10); +INSERT INTO OrderDetails VALUES(468,10422,26,2); +INSERT INTO OrderDetails VALUES(469,10423,31,14); +INSERT INTO OrderDetails VALUES(470,10423,59,20); +INSERT INTO OrderDetails VALUES(471,10424,35,60); +INSERT INTO OrderDetails VALUES(472,10424,38,49); +INSERT INTO OrderDetails VALUES(473,10424,68,30); +INSERT INTO OrderDetails VALUES(474,10425,55,10); +INSERT INTO OrderDetails VALUES(475,10425,76,20); +INSERT INTO OrderDetails VALUES(476,10426,56,5); +INSERT INTO OrderDetails VALUES(477,10426,64,7); +INSERT INTO OrderDetails VALUES(478,10427,14,35); +INSERT INTO OrderDetails VALUES(479,10428,46,20); +INSERT INTO OrderDetails VALUES(480,10429,50,40); +INSERT INTO OrderDetails VALUES(481,10429,63,35); +INSERT INTO OrderDetails VALUES(482,10430,17,45); +INSERT INTO OrderDetails VALUES(483,10430,21,50); +INSERT INTO OrderDetails VALUES(484,10430,56,30); +INSERT INTO OrderDetails VALUES(485,10430,59,70); +INSERT INTO OrderDetails VALUES(486,10431,17,50); +INSERT INTO OrderDetails VALUES(487,10431,40,50); +INSERT INTO OrderDetails VALUES(488,10431,47,30); +INSERT INTO OrderDetails VALUES(489,10432,26,10); +INSERT INTO OrderDetails VALUES(490,10432,54,40); +INSERT INTO OrderDetails VALUES(491,10433,56,28); +INSERT INTO OrderDetails VALUES(492,10434,11,6); +INSERT INTO OrderDetails VALUES(493,10434,76,18); +INSERT INTO OrderDetails VALUES(494,10435,2,10); +INSERT INTO OrderDetails VALUES(495,10435,22,12); +INSERT INTO OrderDetails VALUES(496,10435,72,10); +INSERT INTO OrderDetails VALUES(497,10436,46,5); +INSERT INTO OrderDetails VALUES(498,10436,56,40); +INSERT INTO OrderDetails VALUES(499,10436,64,30); +INSERT INTO OrderDetails VALUES(500,10436,75,24); +INSERT INTO OrderDetails VALUES(501,10437,53,15); +INSERT INTO OrderDetails VALUES(502,10438,19,15); +INSERT INTO OrderDetails VALUES(503,10438,34,20); +INSERT INTO OrderDetails VALUES(504,10438,57,15); +INSERT INTO OrderDetails VALUES(505,10439,12,15); +INSERT INTO OrderDetails VALUES(506,10439,16,16); +INSERT INTO OrderDetails VALUES(507,10439,64,6); +INSERT INTO OrderDetails VALUES(508,10439,74,30); +INSERT INTO OrderDetails VALUES(509,10440,2,45); +INSERT INTO OrderDetails VALUES(510,10440,16,49); +INSERT INTO OrderDetails VALUES(511,10440,29,24); +INSERT INTO OrderDetails VALUES(512,10440,61,90); +INSERT INTO OrderDetails VALUES(513,10441,27,50); +INSERT INTO OrderDetails VALUES(514,10442,11,30); +INSERT INTO OrderDetails VALUES(515,10442,54,80); +INSERT INTO OrderDetails VALUES(516,10442,66,60); +INSERT INTO OrderDetails VALUES(517,10443,11,6); +INSERT INTO OrderDetails VALUES(518,10443,28,12); \ No newline at end of file diff --git a/missions/W1/mtcars.csv b/missions/W1/mtcars.csv index 08529b5..a22b9c2 100644 --- a/missions/W1/mtcars.csv +++ b/missions/W1/mtcars.csv @@ -1,33 +1,33 @@ -"","mpg","cyl","disp","hp","drat","wt","qsec","vs","am","gear","carb" -"Mazda RX4",21,6,160,110,3.9,2.62,16.46,0,1,4,4 -"Mazda RX4 Wag",21,6,160,110,3.9,2.875,17.02,0,1,4,4 -"Datsun 710",22.8,4,108,93,3.85,2.32,18.61,1,1,4,1 -"Hornet 4 Drive",21.4,6,258,110,3.08,3.215,19.44,1,0,3,1 -"Hornet Sportabout",18.7,8,360,175,3.15,3.44,17.02,0,0,3,2 -"Valiant",18.1,6,225,105,2.76,3.46,20.22,1,0,3,1 -"Duster 360",14.3,8,360,245,3.21,3.57,15.84,0,0,3,4 -"Merc 240D",24.4,4,146.7,62,3.69,3.19,20,1,0,4,2 -"Merc 230",22.8,4,140.8,95,3.92,3.15,22.9,1,0,4,2 -"Merc 280",19.2,6,167.6,123,3.92,3.44,18.3,1,0,4,4 -"Merc 280C",17.8,6,167.6,123,3.92,3.44,18.9,1,0,4,4 -"Merc 450SE",16.4,8,275.8,180,3.07,4.07,17.4,0,0,3,3 -"Merc 450SL",17.3,8,275.8,180,3.07,3.73,17.6,0,0,3,3 -"Merc 450SLC",15.2,8,275.8,180,3.07,3.78,18,0,0,3,3 -"Cadillac Fleetwood",10.4,8,472,205,2.93,5.25,17.98,0,0,3,4 -"Lincoln Continental",10.4,8,460,215,3,5.424,17.82,0,0,3,4 -"Chrysler Imperial",14.7,8,440,230,3.23,5.345,17.42,0,0,3,4 -"Fiat 128",32.4,4,78.7,66,4.08,2.2,19.47,1,1,4,1 -"Honda Civic",30.4,4,75.7,52,4.93,1.615,18.52,1,1,4,2 -"Toyota Corolla",33.9,4,71.1,65,4.22,1.835,19.9,1,1,4,1 -"Toyota Corona",21.5,4,120.1,97,3.7,2.465,20.01,1,0,3,1 -"Dodge Challenger",15.5,8,318,150,2.76,3.52,16.87,0,0,3,2 -"AMC Javelin",15.2,8,304,150,3.15,3.435,17.3,0,0,3,2 -"Camaro Z28",13.3,8,350,245,3.73,3.84,15.41,0,0,3,4 -"Pontiac Firebird",19.2,8,400,175,3.08,3.845,17.05,0,0,3,2 -"Fiat X1-9",27.3,4,79,66,4.08,1.935,18.9,1,1,4,1 -"Porsche 914-2",26,4,120.3,91,4.43,2.14,16.7,0,1,5,2 -"Lotus Europa",30.4,4,95.1,113,3.77,1.513,16.9,1,1,5,2 -"Ford Pantera L",15.8,8,351,264,4.22,3.17,14.5,0,1,5,4 -"Ferrari Dino",19.7,6,145,175,3.62,2.77,15.5,0,1,5,6 -"Maserati Bora",15,8,301,335,3.54,3.57,14.6,0,1,5,8 -"Volvo 142E",21.4,4,121,109,4.11,2.78,18.6,1,1,4,2 +"","mpg","cyl","disp","hp","drat","wt","qsec","vs","am","gear","carb" +"Mazda RX4",21,6,160,110,3.9,2.62,16.46,0,1,4,4 +"Mazda RX4 Wag",21,6,160,110,3.9,2.875,17.02,0,1,4,4 +"Datsun 710",22.8,4,108,93,3.85,2.32,18.61,1,1,4,1 +"Hornet 4 Drive",21.4,6,258,110,3.08,3.215,19.44,1,0,3,1 +"Hornet Sportabout",18.7,8,360,175,3.15,3.44,17.02,0,0,3,2 +"Valiant",18.1,6,225,105,2.76,3.46,20.22,1,0,3,1 +"Duster 360",14.3,8,360,245,3.21,3.57,15.84,0,0,3,4 +"Merc 240D",24.4,4,146.7,62,3.69,3.19,20,1,0,4,2 +"Merc 230",22.8,4,140.8,95,3.92,3.15,22.9,1,0,4,2 +"Merc 280",19.2,6,167.6,123,3.92,3.44,18.3,1,0,4,4 +"Merc 280C",17.8,6,167.6,123,3.92,3.44,18.9,1,0,4,4 +"Merc 450SE",16.4,8,275.8,180,3.07,4.07,17.4,0,0,3,3 +"Merc 450SL",17.3,8,275.8,180,3.07,3.73,17.6,0,0,3,3 +"Merc 450SLC",15.2,8,275.8,180,3.07,3.78,18,0,0,3,3 +"Cadillac Fleetwood",10.4,8,472,205,2.93,5.25,17.98,0,0,3,4 +"Lincoln Continental",10.4,8,460,215,3,5.424,17.82,0,0,3,4 +"Chrysler Imperial",14.7,8,440,230,3.23,5.345,17.42,0,0,3,4 +"Fiat 128",32.4,4,78.7,66,4.08,2.2,19.47,1,1,4,1 +"Honda Civic",30.4,4,75.7,52,4.93,1.615,18.52,1,1,4,2 +"Toyota Corolla",33.9,4,71.1,65,4.22,1.835,19.9,1,1,4,1 +"Toyota Corona",21.5,4,120.1,97,3.7,2.465,20.01,1,0,3,1 +"Dodge Challenger",15.5,8,318,150,2.76,3.52,16.87,0,0,3,2 +"AMC Javelin",15.2,8,304,150,3.15,3.435,17.3,0,0,3,2 +"Camaro Z28",13.3,8,350,245,3.73,3.84,15.41,0,0,3,4 +"Pontiac Firebird",19.2,8,400,175,3.08,3.845,17.05,0,0,3,2 +"Fiat X1-9",27.3,4,79,66,4.08,1.935,18.9,1,1,4,1 +"Porsche 914-2",26,4,120.3,91,4.43,2.14,16.7,0,1,5,2 +"Lotus Europa",30.4,4,95.1,113,3.77,1.513,16.9,1,1,5,2 +"Ford Pantera L",15.8,8,351,264,4.22,3.17,14.5,0,1,5,4 +"Ferrari Dino",19.7,6,145,175,3.62,2.77,15.5,0,1,5,6 +"Maserati Bora",15,8,301,335,3.54,3.57,14.6,0,1,5,8 +"Volvo 142E",21.4,4,121,109,4.11,2.78,18.6,1,1,4,2 From 6292397ce9aa954eac88694914769396ada5024d Mon Sep 17 00:00:00 2001 From: openkmj Date: Thu, 9 Jan 2025 18:45:43 +0900 Subject: [PATCH 2/4] fix --- .gitignore | 2 +- missions/W1/M3/README.md | 4 + missions/W1/M3/etl_project_gdp.py | 3 +- missions/W1/M3/etl_project_gdp_from_csv.py | 49 +++----- missions/W1/M3/etl_project_gdp_parallel.py | 117 ++++++++---------- missions/W1/M3/etl_project_gdp_with_sql.py | 10 +- missions/W1/M3/modules/logger.py | 19 +++ .../M3/utils/create_country_region_table.py | 6 + 8 files changed, 104 insertions(+), 106 deletions(-) diff --git a/.gitignore b/.gitignore index 3baab37..c0b44ba 100644 --- a/.gitignore +++ b/.gitignore @@ -168,4 +168,4 @@ large_data*.csv __pycache__ *.db *.json -*.log \ No newline at end of file +*log.txt \ No newline at end of file diff --git a/missions/W1/M3/README.md b/missions/W1/M3/README.md index d0113ef..5316ce6 100644 --- a/missions/W1/M3/README.md +++ b/missions/W1/M3/README.md @@ -231,6 +231,10 @@ df["GDP"] = (df["GDP"].str.replace(",", "").astype(float) / 1000).round(2) ## Parallel/Distributed Processing +Main idea: +- Split file and process each file in parallel +- Store data seperately by region + See detail in `etl_project_gdp_parallel.py`. ### Steps diff --git a/missions/W1/M3/etl_project_gdp.py b/missions/W1/M3/etl_project_gdp.py index dd33214..863b6f9 100644 --- a/missions/W1/M3/etl_project_gdp.py +++ b/missions/W1/M3/etl_project_gdp.py @@ -14,8 +14,7 @@ def transform_df(df): Transformation function """ # Million -> Billion - df["GDP"] = df["GDP"].apply(lambda x: x.replace(",", "")) - df["GDP"] = df["GDP"].apply(lambda x: round(float(x) / 1000, 2)) + df["GDP"] = (df["GDP"].str.replace(",", "").astype(float) / 1000).round(2) # Sort by GDP df = df.sort_values(by="GDP", ascending=False) diff --git a/missions/W1/M3/etl_project_gdp_from_csv.py b/missions/W1/M3/etl_project_gdp_from_csv.py index 71d5a3c..9ae3090 100644 --- a/missions/W1/M3/etl_project_gdp_from_csv.py +++ b/missions/W1/M3/etl_project_gdp_from_csv.py @@ -1,15 +1,16 @@ import sqlite3 import time import pandas as pd -from modules.logger import logger, init_logger +from modules.logger import logger, init_logger, LogExecutionTime from modules.importer import CsvFileImporter from modules.exporter import SqliteExporter +from pathlib import Path -LOG_FILE_PATH = "etl_project_log.txt" -DB_PATH = "World_Economies.db" +HOME_DIR = Path(__file__).resolve().parent +LOG_FILE_PATH = HOME_DIR / "log/etl_project_log.txt" +DB_PATH = HOME_DIR / "data/World_Economies.db" +INPUT_FILE_PATH = HOME_DIR / "data/large_data.csv" TABLE_NAME = "Countries_by_GDP" -INPUT_FILE_PATH = "large_data_10M.csv" -# INPUT_FILE_PATH = "large_data.csv" QUERY_1 = """ SELECT Country, GDP_USD_billion @@ -36,7 +37,6 @@ def transfrom_df(df: pd.DataFrame) -> pd.DataFrame: """ Transformation function """ - time_start = time.time() # Million -> Billion # df["GDP"] = df["GDP"].apply(lambda x: x.replace(",", "")) # df["GDP"] = df["GDP"].apply(lambda x: round(float(x) / 1000, 2)) @@ -49,14 +49,9 @@ def transfrom_df(df: pd.DataFrame) -> pd.DataFrame: # .div(1000) # .round(2) # ) - time_end = time.time() - logger.info(f"Transform GDP: {time_end - time_start:.2f} seconds") # Sort by GDP - time_start = time.time() df = df.sort_values(by="GDP", ascending=False) - time_end = time.time() - logger.info(f"Sort by GDP: {time_end - time_start:.2f} seconds") # Rename GDP column to GDP_USD_billion df.rename(columns={"GDP": "GDP_USD_billion"}, inplace=True) @@ -70,48 +65,34 @@ def main(): logger.info("Starting the ETL process") # Extract - time_start = time.time() - csv_importer = CsvFileImporter(INPUT_FILE_PATH) - df = csv_importer.import_data() - time_end = time.time() - logger.info(f"Extract Time taken: {time_end - time_start:.2f} seconds") + with LogExecutionTime("Extract"): + csv_importer = CsvFileImporter(INPUT_FILE_PATH) + df = csv_importer.import_data() # Transform - time_start = time.time() - logger.info("Transforming data...") - df = transfrom_df(df) - time_end = time.time() - logger.info(f"Transform Time taken: {time_end - time_start:.2f} seconds") - - print(df.head(3)) + with LogExecutionTime("Transform"): + df = transfrom_df(df) # Load - time_start = time.time() - sqlite_exporter = SqliteExporter(DB_PATH, table_name=TABLE_NAME) - sqlite_exporter.export_data(df) - time_end = time.time() - logger.info(f"Load Time taken: {time_end - time_start:.2f} seconds") + with LogExecutionTime("Load"): + sqlite_exporter = SqliteExporter(DB_PATH, table_name=TABLE_NAME) + sqlite_exporter.export_data(df) logger.info("ETL process completed successfully") + # Query print("Top 5 Average GDP by Region:") - time_start = time.time() df_groupby_top5 = df.groupby("Region").head(5) avg_gdp = df_groupby_top5.groupby("Region")["GDP_USD_billion"].mean() for region, gdp in avg_gdp.items(): print(f"{region:<15} {gdp:.2f}") - time_end = time.time() - logger.info(f"Query with Dataframe : {time_end - time_start:.2f} seconds") conn = sqlite3.connect(DB_PATH) cursor = conn.cursor() - time_start = time.time() cursor.execute(QUERY_2) for row in cursor: print(f"{row[0]:<15} {row[1]:.2f}") - time_end = time.time() - logger.info(f"Query with SQLITE: {time_end - time_start:.2f} seconds") conn.close() diff --git a/missions/W1/M3/etl_project_gdp_parallel.py b/missions/W1/M3/etl_project_gdp_parallel.py index ae218fd..4aafcac 100644 --- a/missions/W1/M3/etl_project_gdp_parallel.py +++ b/missions/W1/M3/etl_project_gdp_parallel.py @@ -1,16 +1,16 @@ import sqlite3 import time import pandas as pd -from modules.logger import logger, init_logger +from pathlib import Path +from modules.logger import logger, init_logger, LogExecutionTime from multiprocessing import Pool from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor -LOG_FILE_PATH = "etl_project_log.txt" -# DB_PATH = "World_Economies_1B.db" -DB_NAME = "World_Economies_10M" +HOME_DIR = Path(__file__).resolve().parent +LOG_FILE_PATH = HOME_DIR / "log/etl_project_log.txt" +DB_NAME = "World_Economies" TABLE_NAME = "Countries_by_GDP" -# INPUT_FILE_PATH = "large_data_1B.csv" -INPUT_FILE_PATH = "large_data_10M.csv" +INPUT_FILE_PATH = HOME_DIR / "data/large_data.csv" DATA_SIZE = 10_000_000 # 10M rows CHUNK_SIZE = 1_000_000 # 100K rows per chunk NUM_CHUNKS = DATA_SIZE // CHUNK_SIZE # 100 chunks @@ -43,9 +43,6 @@ def transfrom_df(df: pd.DataFrame) -> pd.DataFrame: df["GDP"] = (df["GDP"].str.replace(",", "").astype(float) / 1000).round(2) - # Sort by GDP - # df = df.sort_values(by="GDP", ascending=False) - # Rename GDP column to GDP_USD_billion df.rename(columns={"GDP": "GDP_USD_billion"}, inplace=True) @@ -68,12 +65,12 @@ def process_chunk(index: int): skiprows=index * CHUNK_SIZE, nrows=CHUNK_SIZE, ) - df.to_csv(f"data/large_data_10M_{index}.csv", index=False) + df.to_csv(f"data/large_data_{index}.csv", index=False) def process_chunk2(index: int, chunk: pd.DataFrame): print(f"Processing chunk {index}") - chunk.to_csv(f"data/large_data_10M_{index}.csv", index=False) + chunk.to_csv(f"data/large_data_{index}.csv", index=False) def extract_data_from_source(): @@ -103,28 +100,28 @@ def transform_chunk(index: int): """ (Transform - Preprocess) Transform each small file """ - df = pd.read_csv(f"data/large_data_10M_{index}.csv", dtype=schema) - df = transfrom_df(df) # 기존 transform 함수 사용 - df.to_csv(f"data/large_data_10M_{index}_transformed.csv", index=False) + df = pd.read_csv(f"data/large_data_{index}.csv", dtype=schema) + df = transfrom_df(df) + df.to_csv(f"data/large_data_{index}_transformed.csv", index=False) def map_by_region(index: int): """ (Transform - Map) Separate each small file by region """ - df = pd.read_csv(f"data/large_data_10M_{index}_transformed.csv") + df = pd.read_csv(f"data/large_data_{index}_transformed.csv") regions = ["Asia", "Europe", "Africa", "North America", "South America", "Oceania"] for region in regions: region_df = df[df["Region"] == region] - region_df.to_csv(f"data/large_data_10M_{index}_{region}.csv", index=False) + region_df.to_csv(f"data/large_data_{index}_{region}.csv", index=False) def reduce_by_region(region: str): """ (Transform - Reduce) Merge all files for each region """ - all_files = [f"data/large_data_10M_{i}_{region}.csv" for i in range(NUM_CHUNKS)] + all_files = [f"data/large_data_{i}_{region}.csv" for i in range(NUM_CHUNKS)] dfs = [] for file in all_files: @@ -136,16 +133,16 @@ def reduce_by_region(region: str): if dfs: combined_df = pd.concat(dfs, ignore_index=True) - combined_df.to_csv(f"data/large_data_10M_{region}.csv", index=False) + combined_df.to_csv(f"data/large_data_{region}.csv", index=False) def sort_by_gdp(region: str): """ (Transform - Sort) Sort each region file by GDP """ - df = pd.read_csv(f"data/large_data_10M_{region}.csv") + df = pd.read_csv(f"data/large_data_{region}.csv") df = df.sort_values(by="GDP_USD_billion", ascending=False) - df.to_csv(f"data/large_data_10M_{region}_sorted.csv", index=False) + df.to_csv(f"data/large_data_{region}_sorted.csv", index=False) def load_to_database(region: str): @@ -153,7 +150,7 @@ def load_to_database(region: str): (Load) Export each region file to sqlite """ conn = sqlite3.connect(f"data/{DB_NAME}_{region}.db") - df = pd.read_csv(f"data/large_data_10M_{region}_sorted.csv") + df = pd.read_csv(f"data/large_data_{region}_sorted.csv") df.to_sql(TABLE_NAME, conn, if_exists="append", index=False) conn.close() @@ -174,62 +171,54 @@ def main(): logger.info("Starting the Parallel ETL process") # 1. Extract - time_start = time.time() - # extract_data_from_source() - with Pool() as pool: - pool.map(process_chunk, range(NUM_CHUNKS)) - # with ThreadPoolExecutor() as executor: - # executor.map(process_chunk, range(NUM_CHUNKS)) - # with ProcessPoolExecutor() as executor: - # executor.map(process_chunk, range(NUM_CHUNKS)) - time_end = time.time() - logger.info(f"Extract data: {time_end - time_start:.2f} seconds") + with LogExecutionTime("Extract data"): + # extract_data_from_source() + with Pool() as pool: + pool.map(process_chunk, range(NUM_CHUNKS)) + # with ThreadPoolExecutor() as executor: + # executor.map(process_chunk, range(NUM_CHUNKS)) + # with ProcessPoolExecutor() as executor: + # executor.map(process_chunk, range(NUM_CHUNKS)) # 2. Transform - Preprocess - time_start = time.time() - with Pool() as pool: - pool.map(transform_chunk, range(NUM_CHUNKS)) - time_end = time.time() - logger.info(f"Transform chunks: {time_end - time_start:.2f} seconds") + with LogExecutionTime("Transform chunks"): + with Pool() as pool: + pool.map(transform_chunk, range(NUM_CHUNKS)) # 3. Transform - Map - time_start = time.time() - with Pool() as pool: - pool.map(map_by_region, range(NUM_CHUNKS)) - time_end = time.time() - logger.info(f"Map by region: {time_end - time_start:.2f} seconds") + with LogExecutionTime("Map by region"): + with Pool() as pool: + pool.map(map_by_region, range(NUM_CHUNKS)) # 4. Transform - Reduce - time_start = time.time() - regions = ["Asia", "Europe", "Africa", "North America", "South America", "Oceania"] - with Pool() as pool: - pool.map(reduce_by_region, regions) - time_end = time.time() - logger.info(f"Reduce by region: {time_end - time_start:.2f} seconds") + with LogExecutionTime("Reduce by region"): + regions = [ + "Asia", + "Europe", + "Africa", + "North America", + "South America", + "Oceania", + ] + with Pool() as pool: + pool.map(reduce_by_region, regions) # 5. Sort - time_start = time.time() - with Pool() as pool: - pool.map(sort_by_gdp, regions) - time_end = time.time() - logger.info(f"Sort by GDP: {time_end - time_start:.2f} seconds") + with LogExecutionTime("Sort by GDP"): + with Pool() as pool: + pool.map(sort_by_gdp, regions) # 6. Load - time_start = time.time() - with Pool() as pool: - pool.map(load_to_database, regions) - time_end = time.time() - logger.info(f"Load to database: {time_end - time_start:.2f} seconds") + with LogExecutionTime("Load to database"): + with Pool() as pool: + pool.map(load_to_database, regions) # 7. Query - time_start = time.time() regions = ["Asia", "Europe", "Africa", "North America", "South America", "Oceania"] - with Pool() as pool: - results = pool.map(query_by_region, regions) - print(results) - - time_end = time.time() - logger.info(f"Query: {time_end - time_start:.2f} seconds") + with LogExecutionTime("Query by region"): + with Pool() as pool: + results = pool.map(query_by_region, regions) + print(results) if __name__ == "__main__": diff --git a/missions/W1/M3/etl_project_gdp_with_sql.py b/missions/W1/M3/etl_project_gdp_with_sql.py index 6106224..98f1642 100644 --- a/missions/W1/M3/etl_project_gdp_with_sql.py +++ b/missions/W1/M3/etl_project_gdp_with_sql.py @@ -1,11 +1,12 @@ import sqlite3 - +from pathlib import Path from modules.logger import logger, init_logger from modules.importer import WikiWebImporter from modules.exporter import SqliteExporter -LOG_FILE_PATH = "etl_project_log.txt" -DB_PATH = "World_Economies.db" +HOME_DIR = Path(__file__).resolve().parent +LOG_FILE_PATH = HOME_DIR / "log/etl_project_log.txt" +DB_PATH = HOME_DIR / "data/World_Economies.db" TABLE_NAME = "Countries_by_GDP" QUERY_1 = """ @@ -34,8 +35,7 @@ def transfrom_df(df): Transformation function """ # Million -> Billion - df["GDP"] = df["GDP"].apply(lambda x: x.replace(",", "")) - df["GDP"] = df["GDP"].apply(lambda x: round(float(x) / 1000, 2)) + df["GDP"] = (df["GDP"].str.replace(",", "").astype(float) / 1000).round(2) # Sort by GDP df = df.sort_values(by="GDP", ascending=False) diff --git a/missions/W1/M3/modules/logger.py b/missions/W1/M3/modules/logger.py index a8f1151..f3acffe 100644 --- a/missions/W1/M3/modules/logger.py +++ b/missions/W1/M3/modules/logger.py @@ -1,4 +1,5 @@ import datetime +import time from pathlib import Path DEFAULT_LOG_FILE_PATH = Path(__file__).resolve().parent / "../log/log.txt" @@ -36,3 +37,21 @@ def _log(self, type: str, message: str): def init_logger(log_file_path: str): logger.log_file_path = log_file_path + + +class LogExecutionTime: + """ + Execution time logger + """ + + def __init__(self, description: str): + self.description = description + + def __enter__(self): + self.start_time = time.time() + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.end_time = time.time() + self.execution_time = self.end_time - self.start_time + logger.info(f"{self.description} took {self.execution_time:.2f} seconds") diff --git a/missions/W1/M3/utils/create_country_region_table.py b/missions/W1/M3/utils/create_country_region_table.py index 5751562..1369871 100644 --- a/missions/W1/M3/utils/create_country_region_table.py +++ b/missions/W1/M3/utils/create_country_region_table.py @@ -41,6 +41,12 @@ def main(): country_region_table[countryName] = region country_region_table[countryNameAlias] = region + # manually add + country_region_table["DR Congo"] = "Africa" + country_region_table["Congo"] = "Africa" + country_region_table["Bahamas"] = "North America" + country_region_table["Gambia"] = "Africa" + save_to_json(country_region_table, OUTPUT_FILE_PATH) From ba9f7859e8a68f06ae93ce644a10242ec0d9d252 Mon Sep 17 00:00:00 2001 From: openkmj Date: Fri, 10 Jan 2025 18:52:19 +0900 Subject: [PATCH 3/4] fix --- missions/W1/M3/README.md | 37 +++++-- missions/W1/M3/config.py | 11 ++ missions/W1/M3/etl_project_gdp.py | 51 ++-------- missions/W1/M3/etl_project_gdp_from_csv.py | 88 ++-------------- missions/W1/M3/etl_project_gdp_parallel.py | 111 ++++++++------------- missions/W1/M3/etl_project_gdp_with_sql.py | 76 +++----------- missions/W1/M3/modules/importer.py | 12 ++- missions/W1/M3/modules/query_helper.py | 72 +++++++++++++ missions/W1/M3/modules/transformer.py | 20 ++++ 9 files changed, 216 insertions(+), 262 deletions(-) create mode 100644 missions/W1/M3/config.py create mode 100644 missions/W1/M3/modules/query_helper.py create mode 100644 missions/W1/M3/modules/transformer.py diff --git a/missions/W1/M3/README.md b/missions/W1/M3/README.md index 5316ce6..b742608 100644 --- a/missions/W1/M3/README.md +++ b/missions/W1/M3/README.md @@ -16,7 +16,7 @@ Common analysis use cases are as follows: - [GDP ETL Project](#gdp-etl-project) - [Business Requirements](#business-requirements) - [Contents](#contents) - - [Definition of ETL Process](#definition-of-etl-process) + - [ETL Process Overview](#etl-process-overview) - [1. Extract](#1-extract) - [2. Transform](#2-transform) - [3. Load](#3-load) @@ -24,8 +24,10 @@ Common analysis use cases are as follows: - [ETL Process](#etl-process) - [Modules](#modules) - [**`importer.py`**](#importerpy) + - [**`transformer.py`**](#transformerpy) - [**`exporter.py`**](#exporterpy) - [**`logger.py`**](#loggerpy) + - [**`query_helper.py`**](#query_helperpy) - [Utils](#utils) - [**`create_country_region_table.py`**](#create_country_region_tablepy) - [**`create_large_data_csv.py`**](#create_large_data_csvpy) @@ -35,11 +37,15 @@ Common analysis use cases are as follows: - [Parallel/Distributed Processing](#paralleldistributed-processing) - [Steps](#steps) -## Definition of ETL Process +## ETL Process Overview ### 1. Extract -- Parse html or read csv file. -- After extraction, the data should follow the format: +- Move data from external system to workspace +- This process will be abstracted in `importer.py` along with parsing process. + +### 2. Transform +- Prasing raw data to structured format + - After parsing, the data should follow the format: ```json [ { @@ -47,11 +53,10 @@ Common analysis use cases are as follows: "GDP": "30,337,162", "Region": "North America" }, - ... + // ... ] ``` - -### 2. Transform + - This process will be abstracted in `importer.py` along with extracting process. - Transform GDP value 1. Convert GDP value string to float 2. Convert GDP value to billion @@ -64,7 +69,7 @@ Common analysis use cases are as follows: "GDP":30337.16, "Region":"North America" }, - ... + // ... ] ``` @@ -85,7 +90,7 @@ Common analysis use cases are as follows: ### Modules #### **`importer.py`** -Extracts data from Wikipedia and saves it to a JSON file. +Extracts data from data source and parse it to structured format. Supported Data Source: - Wikipedia @@ -99,6 +104,15 @@ Seperate Interface and Implementation to support multiple data source. - `FileImporterInterface` - `CsvFileImporter` +`ImporterInterface` defines the interface for all importers. +`WebImporterInterface` and `FileImporterInterface` defines how to extract data from data source. +`WikiWebImporter` and `CsvFileImporter` defines how to parse data from data source. + +--- + +#### **`transformer.py`** +Functions for transforming data. + --- #### **`exporter.py`** @@ -124,6 +138,11 @@ Supported Log Level: --- +#### **`query_helper.py`** +Functions for querying data. + +--- + ### Utils #### **`create_country_region_table.py`** diff --git a/missions/W1/M3/config.py b/missions/W1/M3/config.py new file mode 100644 index 0000000..c367da9 --- /dev/null +++ b/missions/W1/M3/config.py @@ -0,0 +1,11 @@ +from pathlib import Path + +HOME_DIR = Path(__file__).resolve().parent +LOG_FILE_PATH = HOME_DIR / "log/etl_project_log.txt" +RAW_DATA_FILE_PATH = HOME_DIR / "data/Countries_by_GDP.json" +OUTPUT_FILE_PATH = HOME_DIR / "data/Countries_by_GDP_Transformed.json" +DB_NAME = "World_Economies" +TABLE_NAME = "Countries_by_GDP" +DB_PATH = HOME_DIR / f"data/{DB_NAME}.db" +CSV_FILE_NAME = "large_data" +CSV_INPUT_FILE_PATH = HOME_DIR / f"data/{CSV_FILE_NAME}.csv" diff --git a/missions/W1/M3/etl_project_gdp.py b/missions/W1/M3/etl_project_gdp.py index 863b6f9..67d7958 100644 --- a/missions/W1/M3/etl_project_gdp.py +++ b/missions/W1/M3/etl_project_gdp.py @@ -1,25 +1,12 @@ +from config import LOG_FILE_PATH, RAW_DATA_FILE_PATH, OUTPUT_FILE_PATH from modules.logger import logger, init_logger from modules.importer import WikiWebImporter +from modules.transformer import transform_gdp from modules.exporter import JsonFileExporter -from pathlib import Path - -HOME_DIR = Path(__file__).resolve().parent -LOG_FILE_PATH = HOME_DIR / "log/etl_project_log.txt" -RAW_DATA_FILE_PATH = HOME_DIR / "data/Countries_by_GDP.json" -OUTPUT_FILE_PATH = HOME_DIR / "data/Countries_by_GDP_Transformed.json" - - -def transform_df(df): - """ - Transformation function - """ - # Million -> Billion - df["GDP"] = (df["GDP"].str.replace(",", "").astype(float) / 1000).round(2) - - # Sort by GDP - df = df.sort_values(by="GDP", ascending=False) - - return df +from modules.query_helper import ( + print_gdp_over_100_countries_df, + print_top5_avg_gdp_by_region_df, +) def main(): @@ -27,34 +14,18 @@ def main(): logger.print_separator() logger.info("Starting the ETL process") - # Extract - # parsing html and store to raw_data_file_path - wiki_importer = WikiWebImporter(raw_data_file_path=RAW_DATA_FILE_PATH) - df = wiki_importer.import_data() + importer = WikiWebImporter(raw_data_file_path=RAW_DATA_FILE_PATH) + df = importer.import_data() - # Transform - # transform GDP to billion and sort by GDP - logger.info("Transforming data...") - df = transform_df(df) + df = transform_gdp(df) - # Load - # export to output_file_path exporter = JsonFileExporter(OUTPUT_FILE_PATH) exporter.export_data(df) logger.info("ETL process completed successfully") - # Query - df_over_100 = df[df["GDP"] > 100] - print("Countries with GDP > 100B:") - for _, row in df_over_100.iterrows(): - print(f"{row['Country']:<20} {row['GDP']}") - - df_groupby_top5 = df.groupby("Region").head(5) - avg_gdp = df_groupby_top5.groupby("Region")["GDP"].mean() - print("Top 5 Average GDP by Region:") - for region, gdp in avg_gdp.items(): - print(f"{region:<15} {gdp:.2f}") + print_gdp_over_100_countries_df(df) + print_top5_avg_gdp_by_region_df(df) if __name__ == "__main__": diff --git a/missions/W1/M3/etl_project_gdp_from_csv.py b/missions/W1/M3/etl_project_gdp_from_csv.py index 9ae3090..ec8539d 100644 --- a/missions/W1/M3/etl_project_gdp_from_csv.py +++ b/missions/W1/M3/etl_project_gdp_from_csv.py @@ -1,62 +1,9 @@ -import sqlite3 -import time -import pandas as pd +from config import LOG_FILE_PATH, DB_PATH, TABLE_NAME, CSV_INPUT_FILE_PATH from modules.logger import logger, init_logger, LogExecutionTime from modules.importer import CsvFileImporter +from modules.transformer import transform_gdp, rename_columns from modules.exporter import SqliteExporter -from pathlib import Path - -HOME_DIR = Path(__file__).resolve().parent -LOG_FILE_PATH = HOME_DIR / "log/etl_project_log.txt" -DB_PATH = HOME_DIR / "data/World_Economies.db" -INPUT_FILE_PATH = HOME_DIR / "data/large_data.csv" -TABLE_NAME = "Countries_by_GDP" - -QUERY_1 = """ -SELECT Country, GDP_USD_billion -FROM Countries_by_GDP -WHERE GDP_USD_billion > 100 -ORDER BY GDP_USD_billion DESC -""" -QUERY_2 = """ -SELECT Region, AVG(GDP_USD_billion) FROM -( - SELECT - Country, - GDP_USD_billion, - Region, - ROW_NUMBER() OVER (PARTITION BY Region ORDER BY GDP_USD_billion DESC) AS row_num - FROM Countries_by_GDP -) -WHERE row_num <= 5 -GROUP BY Region -""" - - -def transfrom_df(df: pd.DataFrame) -> pd.DataFrame: - """ - Transformation function - """ - # Million -> Billion - # df["GDP"] = df["GDP"].apply(lambda x: x.replace(",", "")) - # df["GDP"] = df["GDP"].apply(lambda x: round(float(x) / 1000, 2)) - - # df["GDP"] = df["GDP"].apply(lambda x: round(float(x.replace(",", "")) / 1000, 2)) - # df["GDP"] = (df["GDP"].replace(",", "", regex=True).astype(float) / 1000).round(2) - df["GDP"] = (df["GDP"].str.replace(",", "").astype(float) / 1000).round(2) - # df["GDP"] = ( - # pd.to_numeric(df["GDP"].str.replace(",", ""), errors="coerce") - # .div(1000) - # .round(2) - # ) - - # Sort by GDP - df = df.sort_values(by="GDP", ascending=False) - - # Rename GDP column to GDP_USD_billion - df.rename(columns={"GDP": "GDP_USD_billion"}, inplace=True) - - return df +from modules.query_helper import print_top5_avg_gdp_by_region_sql def main(): @@ -64,36 +11,21 @@ def main(): logger.print_separator() logger.info("Starting the ETL process") - # Extract with LogExecutionTime("Extract"): - csv_importer = CsvFileImporter(INPUT_FILE_PATH) - df = csv_importer.import_data() + importer = CsvFileImporter(CSV_INPUT_FILE_PATH) + df = importer.import_data() - # Transform with LogExecutionTime("Transform"): - df = transfrom_df(df) + df = transform_gdp(df) + df = rename_columns(df, "GDP", "GDP_USD_billion") - # Load with LogExecutionTime("Load"): - sqlite_exporter = SqliteExporter(DB_PATH, table_name=TABLE_NAME) - sqlite_exporter.export_data(df) + exporter = SqliteExporter(DB_PATH, table_name=TABLE_NAME) + exporter.export_data(df) logger.info("ETL process completed successfully") - # Query - print("Top 5 Average GDP by Region:") - - df_groupby_top5 = df.groupby("Region").head(5) - avg_gdp = df_groupby_top5.groupby("Region")["GDP_USD_billion"].mean() - for region, gdp in avg_gdp.items(): - print(f"{region:<15} {gdp:.2f}") - - conn = sqlite3.connect(DB_PATH) - cursor = conn.cursor() - cursor.execute(QUERY_2) - for row in cursor: - print(f"{row[0]:<15} {row[1]:.2f}") - conn.close() + print_top5_avg_gdp_by_region_sql(DB_PATH, TABLE_NAME) if __name__ == "__main__": diff --git a/missions/W1/M3/etl_project_gdp_parallel.py b/missions/W1/M3/etl_project_gdp_parallel.py index 4aafcac..b4bffb0 100644 --- a/missions/W1/M3/etl_project_gdp_parallel.py +++ b/missions/W1/M3/etl_project_gdp_parallel.py @@ -1,39 +1,24 @@ +from multiprocessing import Pool +from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor import sqlite3 -import time import pandas as pd -from pathlib import Path from modules.logger import logger, init_logger, LogExecutionTime -from multiprocessing import Pool -from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor +from config import ( + LOG_FILE_PATH, + DB_NAME, + TABLE_NAME, + CSV_FILE_NAME, + CSV_INPUT_FILE_PATH, +) -HOME_DIR = Path(__file__).resolve().parent -LOG_FILE_PATH = HOME_DIR / "log/etl_project_log.txt" -DB_NAME = "World_Economies" -TABLE_NAME = "Countries_by_GDP" -INPUT_FILE_PATH = HOME_DIR / "data/large_data.csv" DATA_SIZE = 10_000_000 # 10M rows CHUNK_SIZE = 1_000_000 # 100K rows per chunk NUM_CHUNKS = DATA_SIZE // CHUNK_SIZE # 100 chunks - -QUERY_1 = """ -SELECT Country, GDP_USD_billion -FROM Countries_by_GDP -WHERE GDP_USD_billion > 100 -ORDER BY GDP_USD_billion DESC -""" -QUERY_2 = """ -SELECT Region, AVG(GDP_USD_billion) FROM -( - SELECT - Country, - GDP_USD_billion, - Region, - ROW_NUMBER() OVER (PARTITION BY Region ORDER BY GDP_USD_billion DESC) AS row_num - FROM Countries_by_GDP -) -WHERE row_num <= 5 -GROUP BY Region -""" +SCHEMA = { + "Country": str, + "GDP": str, + "Region": str, +} def transfrom_df(df: pd.DataFrame) -> pd.DataFrame: @@ -43,37 +28,29 @@ def transfrom_df(df: pd.DataFrame) -> pd.DataFrame: df["GDP"] = (df["GDP"].str.replace(",", "").astype(float) / 1000).round(2) - # Rename GDP column to GDP_USD_billion df.rename(columns={"GDP": "GDP_USD_billion"}, inplace=True) return df -schema = { - "Country": str, - "GDP": str, - "Region": str, -} - - -def process_chunk(index: int): +def read_and_save_chunk(index: int): df = pd.read_csv( - INPUT_FILE_PATH, - dtype=schema, + CSV_INPUT_FILE_PATH, + dtype=SCHEMA, header=None, - names=schema.keys(), + names=SCHEMA.keys(), skiprows=index * CHUNK_SIZE, nrows=CHUNK_SIZE, ) - df.to_csv(f"data/large_data_{index}.csv", index=False) + df.to_csv(f"data/{CSV_FILE_NAME}_{index}.csv", index=False) -def process_chunk2(index: int, chunk: pd.DataFrame): +def save_chunk(index: int, chunk: pd.DataFrame): print(f"Processing chunk {index}") - chunk.to_csv(f"data/large_data_{index}.csv", index=False) + chunk.to_csv(f"data/{CSV_FILE_NAME}_{index}.csv", index=False) -def extract_data_from_source(): +def sequential_split(): """ (Extract) Seperate one big file into multiple small files. @@ -82,15 +59,15 @@ def extract_data_from_source(): with Pool() as pool: with pd.read_csv( - INPUT_FILE_PATH, - dtype=schema, + CSV_INPUT_FILE_PATH, + dtype=SCHEMA, header=None, - names=schema.keys(), + names=SCHEMA.keys(), chunksize=CHUNK_SIZE, ) as reader: results = [] for i, chunk in enumerate(reader): - results.append(pool.apply_async(process_chunk2, args=(i, chunk))) + results.append(pool.apply_async(save_chunk, args=(i, chunk))) pool.close() pool.join() @@ -100,28 +77,28 @@ def transform_chunk(index: int): """ (Transform - Preprocess) Transform each small file """ - df = pd.read_csv(f"data/large_data_{index}.csv", dtype=schema) + df = pd.read_csv(f"data/{CSV_FILE_NAME}_{index}.csv", dtype=SCHEMA) df = transfrom_df(df) - df.to_csv(f"data/large_data_{index}_transformed.csv", index=False) + df.to_csv(f"data/{CSV_FILE_NAME}_{index}_transformed.csv", index=False) def map_by_region(index: int): """ (Transform - Map) Separate each small file by region """ - df = pd.read_csv(f"data/large_data_{index}_transformed.csv") + df = pd.read_csv(f"data/{CSV_FILE_NAME}_{index}_transformed.csv") regions = ["Asia", "Europe", "Africa", "North America", "South America", "Oceania"] for region in regions: region_df = df[df["Region"] == region] - region_df.to_csv(f"data/large_data_{index}_{region}.csv", index=False) + region_df.to_csv(f"data/{CSV_FILE_NAME}_{index}_{region}.csv", index=False) def reduce_by_region(region: str): """ (Transform - Reduce) Merge all files for each region """ - all_files = [f"data/large_data_{i}_{region}.csv" for i in range(NUM_CHUNKS)] + all_files = [f"data/{CSV_FILE_NAME}_{i}_{region}.csv" for i in range(NUM_CHUNKS)] dfs = [] for file in all_files: @@ -133,16 +110,16 @@ def reduce_by_region(region: str): if dfs: combined_df = pd.concat(dfs, ignore_index=True) - combined_df.to_csv(f"data/large_data_{region}.csv", index=False) + combined_df.to_csv(f"data/{CSV_FILE_NAME}_{region}.csv", index=False) def sort_by_gdp(region: str): """ (Transform - Sort) Sort each region file by GDP """ - df = pd.read_csv(f"data/large_data_{region}.csv") + df = pd.read_csv(f"data/{CSV_FILE_NAME}_{region}.csv") df = df.sort_values(by="GDP_USD_billion", ascending=False) - df.to_csv(f"data/large_data_{region}_sorted.csv", index=False) + df.to_csv(f"data/{CSV_FILE_NAME}_{region}_sorted.csv", index=False) def load_to_database(region: str): @@ -150,7 +127,7 @@ def load_to_database(region: str): (Load) Export each region file to sqlite """ conn = sqlite3.connect(f"data/{DB_NAME}_{region}.db") - df = pd.read_csv(f"data/large_data_{region}_sorted.csv") + df = pd.read_csv(f"data/{CSV_FILE_NAME}_{region}_sorted.csv") df.to_sql(TABLE_NAME, conn, if_exists="append", index=False) conn.close() @@ -170,27 +147,27 @@ def main(): logger.print_separator() logger.info("Starting the Parallel ETL process") - # 1. Extract - with LogExecutionTime("Extract data"): - # extract_data_from_source() + # 1. Split + with LogExecutionTime("Split data"): + # sequential_split() with Pool() as pool: - pool.map(process_chunk, range(NUM_CHUNKS)) + pool.map(read_and_save_chunk, range(NUM_CHUNKS)) # with ThreadPoolExecutor() as executor: - # executor.map(process_chunk, range(NUM_CHUNKS)) + # executor.map(read_and_save_chunk, range(NUM_CHUNKS)) # with ProcessPoolExecutor() as executor: - # executor.map(process_chunk, range(NUM_CHUNKS)) + # executor.map(read_and_save_chunk, range(NUM_CHUNKS)) - # 2. Transform - Preprocess + # 2. Preprocess with LogExecutionTime("Transform chunks"): with Pool() as pool: pool.map(transform_chunk, range(NUM_CHUNKS)) - # 3. Transform - Map + # 3. Map with LogExecutionTime("Map by region"): with Pool() as pool: pool.map(map_by_region, range(NUM_CHUNKS)) - # 4. Transform - Reduce + # 4. Reduce with LogExecutionTime("Reduce by region"): regions = [ "Asia", diff --git a/missions/W1/M3/etl_project_gdp_with_sql.py b/missions/W1/M3/etl_project_gdp_with_sql.py index 98f1642..30faeb2 100644 --- a/missions/W1/M3/etl_project_gdp_with_sql.py +++ b/missions/W1/M3/etl_project_gdp_with_sql.py @@ -1,49 +1,12 @@ -import sqlite3 -from pathlib import Path +from config import LOG_FILE_PATH, DB_PATH, TABLE_NAME from modules.logger import logger, init_logger from modules.importer import WikiWebImporter +from modules.transformer import transform_gdp, rename_columns from modules.exporter import SqliteExporter - -HOME_DIR = Path(__file__).resolve().parent -LOG_FILE_PATH = HOME_DIR / "log/etl_project_log.txt" -DB_PATH = HOME_DIR / "data/World_Economies.db" -TABLE_NAME = "Countries_by_GDP" - -QUERY_1 = """ -SELECT Country, GDP_USD_billion -FROM Countries_by_GDP -WHERE GDP_USD_billion > 100 -ORDER BY GDP_USD_billion DESC -""" -QUERY_2 = """ -SELECT Region, AVG(GDP_USD_billion) FROM -( - SELECT - Country, - GDP_USD_billion, - Region, - ROW_NUMBER() OVER (PARTITION BY Region ORDER BY GDP_USD_billion DESC) AS row_num - FROM Countries_by_GDP +from modules.query_helper import ( + print_gdp_over_100_countries_sql, + print_top5_avg_gdp_by_region_sql, ) -WHERE row_num <= 5 -GROUP BY Region -""" - - -def transfrom_df(df): - """ - Transformation function - """ - # Million -> Billion - df["GDP"] = (df["GDP"].str.replace(",", "").astype(float) / 1000).round(2) - - # Sort by GDP - df = df.sort_values(by="GDP", ascending=False) - - # Rename GDP column to GDP_USD_billion - df.rename(columns={"GDP": "GDP_USD_billion"}, inplace=True) - - return df def main(): @@ -51,32 +14,19 @@ def main(): logger.print_separator() logger.info("Starting the ETL process") - # Extract - wiki_importer = WikiWebImporter() - df = wiki_importer.import_data() + importer = WikiWebImporter() + df = importer.import_data() - # Transform - logger.info("Transforming data...") - df = transfrom_df(df) + df = transform_gdp(df) + df = rename_columns(df, "GDP", "GDP_USD_billion") - # Load - sqlite_exporter = SqliteExporter(DB_PATH, table_name=TABLE_NAME) - sqlite_exporter.export_data(df) + exporter = SqliteExporter(DB_PATH, table_name=TABLE_NAME) + exporter.export_data(df) logger.info("ETL process completed successfully") - # Query - conn = sqlite3.connect(DB_PATH) - cursor = conn.cursor() - cursor.execute(QUERY_1) - print("Countries with GDP > 100B:") - for row in cursor: - print(f"{row[0]:<20} {row[1]}") - cursor.execute(QUERY_2) - print("Top 5 Average GDP by Region:") - for row in cursor: - print(f"{row[0]:<15} {row[1]:.2f}") - conn.close() + print_gdp_over_100_countries_sql(DB_PATH, TABLE_NAME) + print_top5_avg_gdp_by_region_sql(DB_PATH, TABLE_NAME) if __name__ == "__main__": diff --git a/missions/W1/M3/modules/importer.py b/missions/W1/M3/modules/importer.py index 4c81118..69fe21b 100644 --- a/missions/W1/M3/modules/importer.py +++ b/missions/W1/M3/modules/importer.py @@ -10,7 +10,9 @@ class ImporterInterface(ABC): """ General Data importer interface. - Importer Rule: import data from source and return dataframe. The dataframe should have the following columns: + Importer Rule: import data from source and parse it to dataframe. + + The dataframe should have the following columns: - Country - GDP - Region @@ -22,16 +24,15 @@ def __init__(self, source: str): @abstractmethod def import_data(self) -> pd.DataFrame: """ - Import raw data from the source + Import raw data from the source and parse it to dataframe. """ pass class WebImporterInterface(ImporterInterface): """ - Web Crawler interface. request -> parse -> return + Web Crawler interface. request(extract) -> parse(transform) -> return Subclass should implement _parse_html method. - Web importer는 HTML을 파싱하여 중간 데이터를 만들기 때문에(일종의 Tranform 작업) 중간 데이터를 저장할 수 있는 옵션을 둔다. """ def __init__(self, source: str, raw_data_file_path: str = None): @@ -144,7 +145,8 @@ def parse_json(file_path): class FileImporter(ImporterInterface): """ File data importer - File importer는 별도의 trasnform 작업이 없으니 중간 데이터가 없다. + + Read file and parse it to dataframe. """ def import_data(self) -> pd.DataFrame: diff --git a/missions/W1/M3/modules/query_helper.py b/missions/W1/M3/modules/query_helper.py new file mode 100644 index 0000000..b5f758e --- /dev/null +++ b/missions/W1/M3/modules/query_helper.py @@ -0,0 +1,72 @@ +import pandas as pd +import sqlite3 + + +def print_gdp_over_100_countries_df(df: pd.DataFrame) -> pd.DataFrame: + """ + (Pandas) Print countries with GDP > 100B. + """ + df_over_100 = df[df["GDP"] > 100] + print("Countries with GDP > 100B:") + for _, row in df_over_100.iterrows(): + print(f"{row['Country']:<20} {row['GDP']}") + + +def print_gdp_over_100_countries_sql(db_path: str, table_name: str): + """ + (SQLite) Print countries with GDP > 100B. + """ + + QUERY_1 = f""" +SELECT Country, GDP_USD_billion +FROM {table_name} +WHERE GDP_USD_billion > 100 +ORDER BY GDP_USD_billion DESC +""" + + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + cursor.execute(QUERY_1) + print("Countries with GDP > 100B:") + for row in cursor: + print(f"{row[0]:<20} {row[1]}") + conn.close() + + +def print_top5_avg_gdp_by_region_df(df: pd.DataFrame) -> pd.DataFrame: + """ + Print top 5 average GDP by region. + """ + df_groupby_top5 = df.groupby("Region").head(5) + avg_gdp = df_groupby_top5.groupby("Region")["GDP"].mean() + print("Top 5 Average GDP by Region:") + for region, gdp in avg_gdp.items(): + print(f"{region:<15} {gdp:.2f}") + + +def print_top5_avg_gdp_by_region_sql(db_path: str, table_name: str): + """ + (SQLite) Print top 5 average GDP by region. + """ + + QUERY_2 = f""" +SELECT Region, AVG(GDP_USD_billion) FROM +( + SELECT + Country, + GDP_USD_billion, + Region, + ROW_NUMBER() OVER (PARTITION BY Region ORDER BY GDP_USD_billion DESC) AS row_num + FROM {table_name} +) +WHERE row_num <= 5 +GROUP BY Region +""" + + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + cursor.execute(QUERY_2) + print("Top 5 Average GDP by Region:") + for row in cursor: + print(f"{row[0]:<15} {row[1]:.2f}") + conn.close() diff --git a/missions/W1/M3/modules/transformer.py b/missions/W1/M3/modules/transformer.py new file mode 100644 index 0000000..9684661 --- /dev/null +++ b/missions/W1/M3/modules/transformer.py @@ -0,0 +1,20 @@ +import pandas as pd + + +def transform_gdp(df: pd.DataFrame) -> pd.DataFrame: + """ + Convert GDP to billion and sort by GDP. + """ + + df["GDP"] = (df["GDP"].str.replace(",", "").astype(float) / 1000).round(2) + df = df.sort_values(by="GDP", ascending=False) + + return df + + +def rename_columns(df: pd.DataFrame, from_column: str, to_column: str) -> pd.DataFrame: + """ + Rename columns. + """ + df.rename(columns={from_column: to_column}, inplace=True) + return df From 6f47f94183f7dc548a06ac32562ee6273726d52b Mon Sep 17 00:00:00 2001 From: openkmj Date: Fri, 17 Jan 2025 18:57:43 +0900 Subject: [PATCH 4/4] fix --- .gitignore | 9 +++++---- missions/W1/M3/config.py | 3 ++- missions/W1/M3/etl_project_gdp_from_csv.py | 3 +-- missions/W1/M3/etl_project_gdp_parallel.py | 1 + missions/W1/M3/etl_project_gdp_with_sql.py | 3 +-- missions/W1/M3/modules/importer.py | 23 ++++++++++------------ missions/W1/M3/modules/query_helper.py | 6 +++--- missions/W1/M3/modules/transformer.py | 9 +-------- 8 files changed, 24 insertions(+), 33 deletions(-) diff --git a/.gitignore b/.gitignore index c0b44ba..c594f41 100644 --- a/.gitignore +++ b/.gitignore @@ -159,13 +159,14 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ -*.db -large_data*.csv .DS_Store .venv .ipynb_checkpoints -large_data*.csv __pycache__ *.db *.json -*log.txt \ No newline at end of file +*.txt +*.html +*.csv +*.json +*.sql \ No newline at end of file diff --git a/missions/W1/M3/config.py b/missions/W1/M3/config.py index c367da9..e46a048 100644 --- a/missions/W1/M3/config.py +++ b/missions/W1/M3/config.py @@ -2,7 +2,8 @@ HOME_DIR = Path(__file__).resolve().parent LOG_FILE_PATH = HOME_DIR / "log/etl_project_log.txt" -RAW_DATA_FILE_PATH = HOME_DIR / "data/Countries_by_GDP.json" +# RAW_DATA_FILE_PATH = HOME_DIR / "data/Countries_by_GDP.json" +RAW_DATA_FILE_PATH = HOME_DIR / "data/Countries_by_GDP.html" OUTPUT_FILE_PATH = HOME_DIR / "data/Countries_by_GDP_Transformed.json" DB_NAME = "World_Economies" TABLE_NAME = "Countries_by_GDP" diff --git a/missions/W1/M3/etl_project_gdp_from_csv.py b/missions/W1/M3/etl_project_gdp_from_csv.py index ec8539d..5d9a7d5 100644 --- a/missions/W1/M3/etl_project_gdp_from_csv.py +++ b/missions/W1/M3/etl_project_gdp_from_csv.py @@ -1,7 +1,7 @@ from config import LOG_FILE_PATH, DB_PATH, TABLE_NAME, CSV_INPUT_FILE_PATH from modules.logger import logger, init_logger, LogExecutionTime from modules.importer import CsvFileImporter -from modules.transformer import transform_gdp, rename_columns +from modules.transformer import transform_gdp from modules.exporter import SqliteExporter from modules.query_helper import print_top5_avg_gdp_by_region_sql @@ -17,7 +17,6 @@ def main(): with LogExecutionTime("Transform"): df = transform_gdp(df) - df = rename_columns(df, "GDP", "GDP_USD_billion") with LogExecutionTime("Load"): exporter = SqliteExporter(DB_PATH, table_name=TABLE_NAME) diff --git a/missions/W1/M3/etl_project_gdp_parallel.py b/missions/W1/M3/etl_project_gdp_parallel.py index b4bffb0..46659eb 100644 --- a/missions/W1/M3/etl_project_gdp_parallel.py +++ b/missions/W1/M3/etl_project_gdp_parallel.py @@ -11,6 +11,7 @@ CSV_INPUT_FILE_PATH, ) +# TODO: 동적으로 가져와야한다. DATA_SIZE = 10_000_000 # 10M rows CHUNK_SIZE = 1_000_000 # 100K rows per chunk NUM_CHUNKS = DATA_SIZE // CHUNK_SIZE # 100 chunks diff --git a/missions/W1/M3/etl_project_gdp_with_sql.py b/missions/W1/M3/etl_project_gdp_with_sql.py index 30faeb2..1b67903 100644 --- a/missions/W1/M3/etl_project_gdp_with_sql.py +++ b/missions/W1/M3/etl_project_gdp_with_sql.py @@ -1,7 +1,7 @@ from config import LOG_FILE_PATH, DB_PATH, TABLE_NAME from modules.logger import logger, init_logger from modules.importer import WikiWebImporter -from modules.transformer import transform_gdp, rename_columns +from modules.transformer import transform_gdp from modules.exporter import SqliteExporter from modules.query_helper import ( print_gdp_over_100_countries_sql, @@ -18,7 +18,6 @@ def main(): df = importer.import_data() df = transform_gdp(df) - df = rename_columns(df, "GDP", "GDP_USD_billion") exporter = SqliteExporter(DB_PATH, table_name=TABLE_NAME) exporter.export_data(df) diff --git a/missions/W1/M3/modules/importer.py b/missions/W1/M3/modules/importer.py index 69fe21b..2b53684 100644 --- a/missions/W1/M3/modules/importer.py +++ b/missions/W1/M3/modules/importer.py @@ -42,9 +42,9 @@ def __init__(self, source: str, raw_data_file_path: str = None): def import_data(self) -> pd.DataFrame: logger.info(f"Importing data from {self.source}...") html = self._get_html() - df = self._parse_html(html) if self.raw_data_file_path: - self._store_raw_data(self.raw_data_file_path, df) + self._store_raw_data(self.raw_data_file_path, html) + df = self._parse_html(html) logger.info(f"Data imported successfully") return df @@ -68,11 +68,12 @@ def _parse_html(self, html: str) -> pd.DataFrame: """ pass - def _store_raw_data(self, path: str, df: pd.DataFrame): + def _store_raw_data(self, path: str, data: str): """ Store raw data to the given file """ - df.to_json(path, orient="records", indent=2) + with open(path, "w") as file: + file.write(data) class WikiWebImporter(WebImporterInterface): @@ -129,16 +130,12 @@ def _map_region(self, df: pd.DataFrame) -> pd.DataFrame: Map region to the given country """ - def parse_json(file_path): - """ - Read JSON file - """ - with open(file_path, "r") as file: - data = json.load(file) - return data + country_region_df = pd.read_json(self.COUNTRY_REGION_TABLE_PATH, orient="index") + country_region_df = country_region_df.reset_index() + country_region_df.columns = ["Country", "Region"] + + df = df.merge(country_region_df, on="Country", how="left") - country_region_table = parse_json(self.COUNTRY_REGION_TABLE_PATH) - df["Region"] = df["Country"].map(country_region_table) return df diff --git a/missions/W1/M3/modules/query_helper.py b/missions/W1/M3/modules/query_helper.py index b5f758e..2904e55 100644 --- a/missions/W1/M3/modules/query_helper.py +++ b/missions/W1/M3/modules/query_helper.py @@ -6,10 +6,10 @@ def print_gdp_over_100_countries_df(df: pd.DataFrame) -> pd.DataFrame: """ (Pandas) Print countries with GDP > 100B. """ - df_over_100 = df[df["GDP"] > 100] + df_over_100 = df[df["GDP_USD_billion"] > 100] print("Countries with GDP > 100B:") for _, row in df_over_100.iterrows(): - print(f"{row['Country']:<20} {row['GDP']}") + print(f"{row['Country']:<20} {row['GDP_USD_billion']}") def print_gdp_over_100_countries_sql(db_path: str, table_name: str): @@ -38,7 +38,7 @@ def print_top5_avg_gdp_by_region_df(df: pd.DataFrame) -> pd.DataFrame: Print top 5 average GDP by region. """ df_groupby_top5 = df.groupby("Region").head(5) - avg_gdp = df_groupby_top5.groupby("Region")["GDP"].mean() + avg_gdp = df_groupby_top5.groupby("Region")["GDP_USD_billion"].mean() print("Top 5 Average GDP by Region:") for region, gdp in avg_gdp.items(): print(f"{region:<15} {gdp:.2f}") diff --git a/missions/W1/M3/modules/transformer.py b/missions/W1/M3/modules/transformer.py index 9684661..94edcc4 100644 --- a/missions/W1/M3/modules/transformer.py +++ b/missions/W1/M3/modules/transformer.py @@ -8,13 +8,6 @@ def transform_gdp(df: pd.DataFrame) -> pd.DataFrame: df["GDP"] = (df["GDP"].str.replace(",", "").astype(float) / 1000).round(2) df = df.sort_values(by="GDP", ascending=False) + df.rename(columns={"GDP": "GDP_USD_billion"}, inplace=True) return df - - -def rename_columns(df: pd.DataFrame, from_column: str, to_column: str) -> pd.DataFrame: - """ - Rename columns. - """ - df.rename(columns={from_column: to_column}, inplace=True) - return df