diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000..d7308f6
Binary files /dev/null and b/.DS_Store differ
diff --git a/_build/.DS_Store b/_build/.DS_Store
new file mode 100644
index 0000000..3c08347
Binary files /dev/null and b/_build/.DS_Store differ
diff --git a/_build/.doctrees/environment.pickle b/_build/.doctrees/environment.pickle
new file mode 100644
index 0000000..6f17f01
Binary files /dev/null and b/_build/.doctrees/environment.pickle differ
diff --git a/_build/.doctrees/glue_cache.json b/_build/.doctrees/glue_cache.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/_build/.doctrees/glue_cache.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/_build/.doctrees/intro.doctree b/_build/.doctrees/intro.doctree
new file mode 100644
index 0000000..ebb89c4
Binary files /dev/null and b/_build/.doctrees/intro.doctree differ
diff --git a/_build/.doctrees/lectures/lecture2.doctree b/_build/.doctrees/lectures/lecture2.doctree
new file mode 100644
index 0000000..395fa06
Binary files /dev/null and b/_build/.doctrees/lectures/lecture2.doctree differ
diff --git a/_build/html/.buildinfo b/_build/html/.buildinfo
new file mode 100644
index 0000000..97e578c
--- /dev/null
+++ b/_build/html/.buildinfo
@@ -0,0 +1,4 @@
+# Sphinx build info version 1
+# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
+config: bc1b45f9d49732f4fe4d166dd42f1706
+tags: 645f666f9bcd5a90fca523b33c5a78b7
diff --git a/_build/html/_images/color.png b/_build/html/_images/color.png
new file mode 100644
index 0000000..d29eed6
Binary files /dev/null and b/_build/html/_images/color.png differ
diff --git a/_build/html/_images/construction.png b/_build/html/_images/construction.png
new file mode 100644
index 0000000..e1b2f7e
Binary files /dev/null and b/_build/html/_images/construction.png differ
diff --git a/_build/html/_images/full.png b/_build/html/_images/full.png
new file mode 100644
index 0000000..cacbf2b
Binary files /dev/null and b/_build/html/_images/full.png differ
diff --git a/_build/html/_images/left.png b/_build/html/_images/left.png
new file mode 100644
index 0000000..4c6cee2
Binary files /dev/null and b/_build/html/_images/left.png differ
diff --git a/_build/html/_images/right.png b/_build/html/_images/right.png
new file mode 100644
index 0000000..4a8b30f
Binary files /dev/null and b/_build/html/_images/right.png differ
diff --git a/_build/html/_panels_static/panels-main.c949a650a448cc0ae9fd3441c0e17fb0.css b/_build/html/_panels_static/panels-main.c949a650a448cc0ae9fd3441c0e17fb0.css
new file mode 100644
index 0000000..fc14abc
--- /dev/null
+++ b/_build/html/_panels_static/panels-main.c949a650a448cc0ae9fd3441c0e17fb0.css
@@ -0,0 +1 @@
+details.dropdown .summary-title{padding-right:3em !important;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;user-select:none}details.dropdown:hover{cursor:pointer}details.dropdown .summary-content{cursor:default}details.dropdown summary{list-style:none;padding:1em}details.dropdown summary .octicon.no-title{vertical-align:middle}details.dropdown[open] summary .octicon.no-title{visibility:hidden}details.dropdown summary::-webkit-details-marker{display:none}details.dropdown summary:focus{outline:none}details.dropdown summary:hover .summary-up svg,details.dropdown summary:hover .summary-down svg{opacity:1}details.dropdown .summary-up svg,details.dropdown .summary-down svg{display:block;opacity:.6}details.dropdown .summary-up,details.dropdown .summary-down{pointer-events:none;position:absolute;right:1em;top:.75em}details.dropdown[open] .summary-down{visibility:hidden}details.dropdown:not([open]) .summary-up{visibility:hidden}details.dropdown.fade-in[open] summary~*{-moz-animation:panels-fade-in .5s ease-in-out;-webkit-animation:panels-fade-in .5s ease-in-out;animation:panels-fade-in .5s ease-in-out}details.dropdown.fade-in-slide-down[open] summary~*{-moz-animation:panels-fade-in .5s ease-in-out, panels-slide-down .5s ease-in-out;-webkit-animation:panels-fade-in .5s ease-in-out, panels-slide-down .5s ease-in-out;animation:panels-fade-in .5s ease-in-out, panels-slide-down .5s ease-in-out}@keyframes panels-fade-in{0%{opacity:0}100%{opacity:1}}@keyframes panels-slide-down{0%{transform:translate(0, -10px)}100%{transform:translate(0, 0)}}.octicon{display:inline-block;fill:currentColor;vertical-align:text-top}.tabbed-content{box-shadow:0 -.0625rem var(--tabs-color-overline),0 .0625rem var(--tabs-color-underline);display:none;order:99;padding-bottom:.75rem;padding-top:.75rem;width:100%}.tabbed-content>:first-child{margin-top:0 !important}.tabbed-content>:last-child{margin-bottom:0 !important}.tabbed-content>.tabbed-set{margin:0}.tabbed-set{border-radius:.125rem;display:flex;flex-wrap:wrap;margin:1em 0;position:relative}.tabbed-set>input{opacity:0;position:absolute}.tabbed-set>input:checked+label{border-color:var(--tabs-color-label-active);color:var(--tabs-color-label-active)}.tabbed-set>input:checked+label+.tabbed-content{display:block}.tabbed-set>input:focus+label{outline-style:auto}.tabbed-set>input:not(.focus-visible)+label{outline:none;-webkit-tap-highlight-color:transparent}.tabbed-set>label{border-bottom:.125rem solid transparent;color:var(--tabs-color-label-inactive);cursor:pointer;font-size:var(--tabs-size-label);font-weight:700;padding:1em 1.25em .5em;transition:color 250ms;width:auto;z-index:1}html .tabbed-set>label:hover{color:var(--tabs-color-label-active)}
diff --git a/_build/html/_panels_static/panels-variables.06eb56fa6e07937060861dad626602ad.css b/_build/html/_panels_static/panels-variables.06eb56fa6e07937060861dad626602ad.css
new file mode 100644
index 0000000..adc6166
--- /dev/null
+++ b/_build/html/_panels_static/panels-variables.06eb56fa6e07937060861dad626602ad.css
@@ -0,0 +1,7 @@
+:root {
+--tabs-color-label-active: hsla(231, 99%, 66%, 1);
+--tabs-color-label-inactive: rgba(178, 206, 245, 0.62);
+--tabs-color-overline: rgb(207, 236, 238);
+--tabs-color-underline: rgb(207, 236, 238);
+--tabs-size-label: 1rem;
+}
\ No newline at end of file
diff --git a/_build/html/_sources/intro.md b/_build/html/_sources/intro.md
new file mode 100644
index 0000000..9d8469a
--- /dev/null
+++ b/_build/html/_sources/intro.md
@@ -0,0 +1,6 @@
+# CPSC 304 Introduction to Relational Databases
+
+Under construction
+
+
+
diff --git a/_build/html/_sources/lectures/lecture2.ipynb b/_build/html/_sources/lectures/lecture2.ipynb
new file mode 100644
index 0000000..27c4f4a
--- /dev/null
+++ b/_build/html/_sources/lectures/lecture2.ipynb
@@ -0,0 +1,4857 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "3867df60",
+ "metadata": {},
+ "source": [
+ "# Lecture 2: SQL - Class 2\n",
+ "Gittu George, February 24 2022"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0c890d68",
+ "metadata": {},
+ "source": [
+ "## Today's Agenda\n",
+ "\n",
+ "- Refresher (Warm-up)\n",
+ "- Refresher exercise\n",
+ "- Learning objectives\n",
+ "- More SQL commands (lap 1)\n",
+ "- Aggregations (lap 2)\n",
+ "- Grouping (lap 2)\n",
+ "- Joins (lap 3)\n",
+ "- Summary (finish line)\n",
+ "\n",
+ "## Warm-up ☕️\n",
+ "### Refresher!! Take some time and think if you know...\n",
+ "\n",
+ "- What are databases?\n",
+ "- Where is it commonly used?\n",
+ "- How to set up a database?\n",
+ "- General structure of a database\n",
+ "- Various ways of interacting with the database, using\n",
+ " - command-line interface\n",
+ " - ODBC drivers via developer interfaces like pgadmin and toad.\n",
+ " - jupyter notebook ( which we will be using throughout the course)\n",
+ "- To create a table and various data types \n",
+ "- Integrity constraints such as primary key and foreign key\n",
+ "- Basic SQL commands like SELECT, FROM, and WHERE\n",
+ "\n",
+ "In this class, we will be using the tables that we created in lecture 1. Here are the scripts if you want to recreate the classroom exercise table.\n",
+ "\n",
+ "```{toggle}\n",
+ "\n",
+ "```sql\n",
+ "DROP TABLE IF EXISTS students,courses;\n",
+ "\n",
+ "CREATE TABLE IF NOT EXISTS students(\n",
+ "student_no integer PRIMARY KEY, \n",
+ "stud_name text,\n",
+ "age integer,\n",
+ "major text);\n",
+ "\n",
+ "CREATE TABLE IF NOT EXISTS courses(\n",
+ "id SERIAL PRIMARY KEY,\n",
+ "student_no integer, \n",
+ "course_name text,\n",
+ "course_year integer,\n",
+ "course_percentage float);\n",
+ "\n",
+ "INSERT INTO students(student_no,stud_name,age,major) VALUES (111,'Catherine',23,'MBAN'),\n",
+ "(222,'Tiff',28,'MDS'),\n",
+ "(333,'John',23,'MBAN'),\n",
+ "(444,'Amir',28,'MBAN'),\n",
+ "(555,'Gittu',20,'MDS'),\n",
+ "(666,'Isha',30,'MBAN'),\n",
+ "(777,'Heidy',30,'MBAN'),\n",
+ "(888,'Angela',27,'MBAN'),\n",
+ "(999,'Jason',30,'MBAN');\n",
+ "\n",
+ "INSERT INTO courses(student_no,course_name,course_year,course_percentage) VALUES (111,'TPCS INFO TECH',2019,88),\n",
+ "(111,'Data Visualization',2019,88),\n",
+ "(222,'Health and Technology',2020,80),\n",
+ "(222,'Web and Cloud Computing',2019,91),\n",
+ "(222,'Spark Programming',2019,90),\n",
+ "(333,'Parallel Computing',2019,90),\n",
+ "(444,'Large Scale Infrastructures',2019,83),\n",
+ "(444,'TPCS INFO TECH',2019,98),\n",
+ "(555,'TPCS INFO TECH',2020,78),\n",
+ "(555,'Health and Technology',2020,81),\n",
+ "(666,'Data Visualization',2021,85),\n",
+ "(666,'Parallel Computing',2019,87),\n",
+ "(888,'Spark Programming',2019,93),\n",
+ "(999,'TPCS INFO TECH',2019,98),\n",
+ "(999,'Data Visualization',2019,87),\n",
+ "(1000,'C programming',2019,87),\n",
+ "(1111,'Introduction to Genomics',2019,87);\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a754bc90-1a0a-4b5e-8063-78c16951e14d",
+ "metadata": {},
+ "source": [
+ "First of all let's load SQL to work on this notebook."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "id": "5b537cb8-53cc-4bf2-82a3-560c697eef4a",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import os\n",
+ "import psycopg2\n",
+ "from dotenv import load_dotenv\n",
+ "load_dotenv()\n",
+ "host = os.environ.get('DB_HOST')\n",
+ "user = os.environ.get('DB_USER')\n",
+ "password = os.environ.get('DB_PASS')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "50f1cc3c-aaec-499f-883f-390832ddc3d8",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%load_ext sql"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "id": "fedffc66-2974-4c48-b61a-8d38d74e5596",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'Connected: postgres@postgres'"
+ ]
+ },
+ "execution_count": 18,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%sql postgresql://{user}:{password}@{host}:5432/postgres"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "350fda14-b35c-40f8-9a7d-5b8c90032c4a",
+ "metadata": {
+ "tags": []
+ },
+ "source": [
+ "### Refresher exercise \n",
+ "\n",
+ "***Question 1:*** In this table `eg`\n",
+ "\n",
+ "| firstname | countryfrom | continent | gender | age |\n",
+ "|-----------|-------------|---------------|--------|-----|\n",
+ "| matt | usa | north america | M | 23 |\n",
+ "| jenn | uk | europe | F | 35 |\n",
+ "| guy | france | europe | M | 25 |\n",
+ "| james | china | asia | M | 29 |\n",
+ "| lida | india | asia | F | 56 |\n",
+ "| linda | canada | north america | F | 18 |\n",
+ "| sofia | germany | europe | F | 22 |\n",
+ "| george | india | asia | M | 29 |\n",
+ "\n",
+ "What will be returned from following SQL \n",
+ "```sql\n",
+ "select firstname,gender from eg where continent ='asia' AND age =29\n",
+ "```\n",
+ "A) george,M\n",
+ "\n",
+ "B) george,M,asia,29\n",
+ "\n",
+ "C) george,india,asia, M,29 \n",
+ "\n",
+ "D) M,george\n",
+ "\n",
+ "E) M,james\n",
+ "\n",
+ "```{toggle}\n",
+ "\n",
+ "***Answer: D***\n",
+ "\n",
+ "```{important}\n",
+ "Remember the order of the columns returned will be based on the order of columns that we specify within the select statement.\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "69eba2fb-8158-44e0-ae46-64410b0be50a",
+ "metadata": {},
+ "source": [
+ "***Question 2:*** Consider a scenario where you want to perform analysis on a table (peopletable) with 100 columns (including personname,age,gender,origin...etc.) that define a person. You are interested in seeing the name of individuals older than 90. Which SQL query is more appropriate in this situation?\n",
+ "\n",
+ "A) ***select * from peopletable where age > 90;***\n",
+ "\n",
+ "B) ***select personname,age from peopletable where age > 90;***\n",
+ "\n",
+ "B) ***select personname,age,gender,origin from peopletable where age < 90;***\n",
+ "\n",
+ "D) ***select personname from peopletable where age > 90;***\n",
+ "\n",
+ "```{toggle}\n",
+ "\n",
+ "***Answer: D***\n",
+ "\n",
+ "```{important}\n",
+ "Just bring the columns and rows that are needed. Even though `SELECT` and `WHERE` are very basic SQL commands, it's crucial when you are dealing with a large table\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9a7e0bbd-ae06-4b1b-b934-cdf23e081828",
+ "metadata": {},
+ "source": [
+ "## Learning objectives\n",
+ "- You will be able to create SQL queries using (Lap 1 & Lap 2)\n",
+ " - Distinct, \n",
+ " - ORDER BY, \n",
+ " - LIMIT, \n",
+ " - GROUP BY, \n",
+ " - alias AS\n",
+ "- You will learn about different kinds of joins and be able to create SQL queries that perform `JOINS`. (Lap 3)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a033eb62-9c25-4b69-8cb2-14a36cd9272b",
+ "metadata": {},
+ "source": [
+ "## Lap 1 🥛\n",
+ "### DISTINCT\n",
+ "\n",
+ "The DISTINCT statement is used only to return distinct elements from a table.\n",
+ "\n",
+ "***Syntax:***\n",
+ "\n",
+ "```sql\n",
+ "SELECT DISTINCT column1, column2, ...columnN\n",
+ "FROM tablename;\n",
+ "```\n",
+ "\n",
+ "`DISTINCT` is applied to all columns that follows the `DISTINCT` keyword. Say for eg if we give `DISTINCT column1, column2` then the combination of values in both `column1` and `column2` columns will be used for returning the unique combination (or removing the duplicate elements)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "id": "d92b4b94-d2cd-4f44-86de-baf632b91ba6",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "7 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ " \n",
+ " \n",
+ " course_name \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " Large Scale Infrastructures \n",
+ " \n",
+ " \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " Web and Cloud Computing \n",
+ " \n",
+ " \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[('Data Visualization',),\n",
+ " ('Large Scale Infrastructures',),\n",
+ " ('Spark Programming',),\n",
+ " ('Web and Cloud Computing',),\n",
+ " ('Health and Technology',),\n",
+ " ('TPCS INFO TECH',),\n",
+ " ('Parallel Computing',)]"
+ ]
+ },
+ "execution_count": 17,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT DISTINCT course_name FROM courses;"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 94,
+ "id": "1dc8e64b-e555-4580-9b95-1ea9af3d0273",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "11 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " course_name \n",
+ " course_year \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " C programming \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " \n",
+ " \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[('Parallel Computing', 2019),\n",
+ " ('C programming', 2019),\n",
+ " ('TPCS INFO TECH', 2019),\n",
+ " ('Spark Programming', 2019),\n",
+ " ('Web and Cloud Computing', 2019),\n",
+ " ('Health and Technology', 2020),\n",
+ " ('Introduction to Genomics', 2019),\n",
+ " ('Large Scale Infrastructures', 2019),\n",
+ " ('TPCS INFO TECH', 2020),\n",
+ " ('Data Visualization', 2021),\n",
+ " ('Data Visualization', 2019)]"
+ ]
+ },
+ "execution_count": 94,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT DISTINCT course_name, course_year FROM courses;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "904d711d-aad3-4c57-8291-86b1ea1c8604",
+ "metadata": {},
+ "source": [
+ "### ORDER BY\n",
+ "\n",
+ "`ORDER BY` statement sorts the results returned by `SELECT` based on a sort expression.\n",
+ "\n",
+ "Syntax\n",
+ "```sql\n",
+ "SELECT column1, column2 ...columnN\n",
+ "FROM table_name\n",
+ "ORDER BY column1 [ASC | DESC], column2 [ASC | DESC] ....columnN [ASC|DESC];\n",
+ "```\n",
+ "\n",
+ "```{note}\n",
+ "By default, it will sort in ASC. So you can choose not to give ASC.\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 95,
+ "id": "922e1099-7d1b-41ff-aa18-21492c59b20a",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "17 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " id \n",
+ " student_no \n",
+ " course_name \n",
+ " course_year \n",
+ " course_percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 16 \n",
+ " 1000 \n",
+ " C programming \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 17 \n",
+ " 1111 \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 91.0 \n",
+ " \n",
+ " \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 12 \n",
+ " 666 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 93.0 \n",
+ " \n",
+ " \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.0 \n",
+ " \n",
+ " \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 81.0 \n",
+ " \n",
+ " \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (15, 999, 'Data Visualization', 2019, 87.0),\n",
+ " (16, 1000, 'C programming', 2019, 87.0),\n",
+ " (17, 1111, 'Introduction to Genomics', 2019, 87.0),\n",
+ " (4, 222, 'Web and Cloud Computing', 2019, 91.0),\n",
+ " (5, 222, 'Spark Programming', 2019, 90.0),\n",
+ " (6, 333, 'Parallel Computing', 2019, 90.0),\n",
+ " (7, 444, 'Large Scale Infrastructures', 2019, 83.0),\n",
+ " (8, 444, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (12, 666, 'Parallel Computing', 2019, 87.0),\n",
+ " (13, 888, 'Spark Programming', 2019, 93.0),\n",
+ " (3, 222, 'Health and Technology', 2020, 80.0),\n",
+ " (10, 555, 'Health and Technology', 2020, 81.0),\n",
+ " (9, 555, 'TPCS INFO TECH', 2020, 78.0),\n",
+ " (11, 666, 'Data Visualization', 2021, 85.0)]"
+ ]
+ },
+ "execution_count": 95,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT * \n",
+ "FROM courses\n",
+ "ORDER BY course_year;"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 96,
+ "id": "57c57348-9def-4d46-8b9e-eac8d509cf60",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "17 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " id \n",
+ " student_no \n",
+ " course_name \n",
+ " course_year \n",
+ " course_percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 81.0 \n",
+ " \n",
+ " \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.0 \n",
+ " \n",
+ " \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 12 \n",
+ " 666 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 93.0 \n",
+ " \n",
+ " \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 16 \n",
+ " 1000 \n",
+ " C programming \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 17 \n",
+ " 1111 \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 91.0 \n",
+ " \n",
+ " \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(11, 666, 'Data Visualization', 2021, 85.0),\n",
+ " (10, 555, 'Health and Technology', 2020, 81.0),\n",
+ " (3, 222, 'Health and Technology', 2020, 80.0),\n",
+ " (9, 555, 'TPCS INFO TECH', 2020, 78.0),\n",
+ " (6, 333, 'Parallel Computing', 2019, 90.0),\n",
+ " (7, 444, 'Large Scale Infrastructures', 2019, 83.0),\n",
+ " (8, 444, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (12, 666, 'Parallel Computing', 2019, 87.0),\n",
+ " (13, 888, 'Spark Programming', 2019, 93.0),\n",
+ " (14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (15, 999, 'Data Visualization', 2019, 87.0),\n",
+ " (16, 1000, 'C programming', 2019, 87.0),\n",
+ " (1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (17, 1111, 'Introduction to Genomics', 2019, 87.0),\n",
+ " (2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (4, 222, 'Web and Cloud Computing', 2019, 91.0),\n",
+ " (5, 222, 'Spark Programming', 2019, 90.0)]"
+ ]
+ },
+ "execution_count": 96,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT * \n",
+ "FROM courses\n",
+ "ORDER BY course_year DESC;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "25e1020a-93a1-48e9-a763-80674b98b46c",
+ "metadata": {},
+ "source": [
+ "### LIMIT\n",
+ "\n",
+ "Until now, we were returning everything that our SQL query returns. `LIMIT` statement is used to limit the number of rows that are returned. \n",
+ "\n",
+ "syntax:\n",
+ "\n",
+ "```sql\n",
+ "SELECT column1, column2, ...columnN\n",
+ "FROM tablename\n",
+ "LIMIT numberofrows;\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 97,
+ "id": "ef67fccb-2300-4109-b626-8bc7580b29c2",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "2 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " id \n",
+ " student_no \n",
+ " course_name \n",
+ " course_year \n",
+ " course_percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (2, 111, 'Data Visualization', 2019, 88.0)]"
+ ]
+ },
+ "execution_count": 97,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT * \n",
+ "FROM courses\n",
+ "LIMIT 2;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "74228e90-a013-4c9b-b34b-c7cc8fdea343",
+ "metadata": {},
+ "source": [
+ "`LIMIT` keyword is used in a variety of situations. Here are a few cases\n",
+ "\n",
+ "- ***Memory Management:*** Say you just want to look at the output your query returns. If you are dealing with lots and lots of rows, returning the entire rows can slow down the query, cause memory issues, and finally crash your jupyter. In these cases, you can use `LIMIT`. \n",
+ "\n",
+ "- ***Interest in the first few rows:*** If we are interested in just the first N rows, we can achieve that using `LIMIT`. People tend to use `LIMIT` a lot when they want to return the top 10 rows after performing an `ORDER BY`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "dac09f4e-ebbf-4bf1-9c13-d45c3249cf30",
+ "metadata": {},
+ "source": [
+ "Let's apply all the statements that we learned in one statement.\n",
+ "\n",
+ "***Question:*** List out the row that got the highest `course_percentage` for the `Data Visualization` course."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 98,
+ "id": "368c7bdf-7c5c-4e8c-81ae-02de2044b7d8",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "1 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " id \n",
+ " student_no \n",
+ " course_name \n",
+ " course_year \n",
+ " course_percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(2, 111, 'Data Visualization', 2019, 88.0)]"
+ ]
+ },
+ "execution_count": 98,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT * \n",
+ "FROM courses\n",
+ "WHERE course_name = 'Data Visualization'\n",
+ "ORDER BY course_percentage DESC\n",
+ "LIMIT 1;"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 124,
+ "id": "81a06062-3b03-4c69-ac1a-d592e043b437",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "8 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " age \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 18 \n",
+ " \n",
+ " \n",
+ " 22 \n",
+ " \n",
+ " \n",
+ " 23 \n",
+ " \n",
+ " \n",
+ " 25 \n",
+ " \n",
+ " \n",
+ " 29 \n",
+ " \n",
+ " \n",
+ " 29 \n",
+ " \n",
+ " \n",
+ " 35 \n",
+ " \n",
+ " \n",
+ " 56 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(18,), (22,), (23,), (25,), (29,), (29,), (35,), (56,)]"
+ ]
+ },
+ "execution_count": 124,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%sql select age from eg order by age"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ca99d216-561f-4cc2-ad74-9b6dc6174a76",
+ "metadata": {},
+ "source": [
+ "### Checkpoint 1 !! Take some time and think if you can...\n",
+ "\n",
+ "- Select columns and bring in rows just what we need (using `SELECT` & `WHERE`)\n",
+ "- Return `DISTINCT` elements\n",
+ "- `ORDER BY` rows returned based on column(s)\n",
+ "- `LIMIT` the number of rows returned\n",
+ "\n",
+ "Now we will learn some more advanced SQL operations to gain more insight into the data. But, before that, let's do some exercise. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9fd2624e-57a1-4250-9833-a52db657be86",
+ "metadata": {},
+ "source": [
+ "### Check point 1 exercise.\n",
+ "\n",
+ "#### iclicker questions\n",
+ "\n",
+ "Answer the following questions using the table `eg`\n",
+ "\n",
+ "| firstname | countryfrom | continent | gender | age |\n",
+ "|-----------|-------------|---------------|--------|-----|\n",
+ "| matt | usa | north america | M | 23 |\n",
+ "| jenn | uk | europe | F | 35 |\n",
+ "| guy | france | europe | M | 25 |\n",
+ "| james | china | asia | M | 29 |\n",
+ "| lida | india | asia | F | 56 |\n",
+ "| linda | canada | north america | F | 18 |\n",
+ "| sofia | germany | europe | F | 22 |\n",
+ "| george | india | asia | M | 29 |\n",
+ "\n",
+ "\n",
+ "***Question 1:*** How many elements will be returned from this SQL \n",
+ "\n",
+ "```sql\n",
+ "select DISTINCT gender,firstname from eg\n",
+ "```\n",
+ "\n",
+ "A) 2\n",
+ "\n",
+ "B) 8\n",
+ "\n",
+ "C) 4 \n",
+ "\n",
+ "```{toggle}\n",
+ "***Answer: B***\n",
+ "```\n",
+ "***Question 2:*** What will be the first value returned from this SQL \n",
+ "\n",
+ "```sql\n",
+ "select age from eg order by age\n",
+ "```\n",
+ "A) 23\n",
+ "\n",
+ "B) 18\n",
+ "\n",
+ "C) 56\n",
+ "\n",
+ "D) 35\n",
+ "\n",
+ "```{toggle}\n",
+ "***Answer: B***\n",
+ "```\n",
+ "\n",
+ "#### Reasoning Question\n",
+ "\n",
+ "***Question 1:*** Write a SQL query to list the row that got the highest `course_percentage` for the `TPCS INFO TECH'` course. Can you spot any issues by examining the original table?\n",
+ "\n",
+ "```{toggle}\n",
+ "\n",
+ "You probably might have got this SQL query by changing the SQL query that we discussed for the \"Data Visualization\" course. \n",
+ "\n",
+ "***SELECT * FROM courses\n",
+ "WHERE course_name = 'TPCS INFO TECH'\n",
+ "ORDER BY course_percentage DESC\n",
+ "LIMIT 1;***\n",
+ "\n",
+ "This gives you \n",
+ "\n",
+ "| id | student_no | course_name | course_year | course_percentage |\n",
+ "|---:|-----------:|---------------:|------------:|------------------:|\n",
+ "| 8 | 444 | TPCS INFO TECH | 2019 | 98.0 |\n",
+ "\n",
+ "What are possible issues? Take out the `LIMIT`, and you might notice there is a tie, and 2 students, 444 and 999 scored the highest.\n",
+ "\n",
+ "\n",
+ "***SELECT * FROM courses\n",
+ "WHERE course_name = 'TPCS INFO TECH'\n",
+ "ORDER BY course_percentage DESC***\n",
+ "\n",
+ "\n",
+ "| id | student_no | course_name | course_year | course_percentage |\n",
+ "|---:|-----------:|---------------:|------------:|------------------:|\n",
+ "| 8 | 444 | TPCS INFO TECH | 2019 | 98.0 |\n",
+ "| 14 | 999 | TPCS INFO TECH | 2019 | 98.0 |\n",
+ "| 1 | 111 | TPCS INFO TECH | 2019 | 88.0 |\n",
+ "| 9 | 555 | TPCS INFO TECH | 2020 | 78.0 |\n",
+ "\n",
+ "So even though many of them use a combination of `ORDER BY` and `LIMIT` for these scenarios we might run into situations like this. There are a couple of ways to deal with these kinds of scenarios, and we will learn about `subquery` in our next class, which will help you capture ties."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "91ab04a8-4456-4653-9348-4ad153493bb8",
+ "metadata": {},
+ "source": [
+ "## Lap 2 🧋\n",
+ "### Aggregations\n",
+ "\n",
+ "So far, we have returned columns in our select statement. We can also use aggregation functions that operate on rows to summarize the data in the form of a single value. Here is a list of aggregation functions in SQL:\n",
+ "\n",
+ "| Function | Description |\n",
+ "|----------|------------------|\n",
+ "| MAX() | maximum value |\n",
+ "| MIN() | minimum value |\n",
+ "| AVG() | average value |\n",
+ "| SUM() | sum of values |\n",
+ "| COUNT() | number of values |"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 35,
+ "id": "66c023e1-c594-4c0c-8809-7dc54ee6ca4b",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "1 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " count \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 15 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(15,)]"
+ ]
+ },
+ "execution_count": 35,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT COUNT(course_name) FROM courses;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7d0df81a-5c7c-48e3-b05a-4410be8f4840",
+ "metadata": {},
+ "source": [
+ "The above query is counting number of values in the column `course_name`. it's also sort of like counting the number of rows. We can also pass the `DISTINCT` columns into these operations. For example, the below query will find the number of courses available in the university."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 36,
+ "id": "ca947761-3728-4594-83d4-d6484927b7cf",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "1 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " count \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 7 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(7,)]"
+ ]
+ },
+ "execution_count": 36,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT COUNT(DISTINCT course_name) FROM courses;"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 126,
+ "id": "e505b586-f4ea-436e-8ade-abb9e5892638",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "1 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " avg \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 87.70588235294117 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(87.70588235294117,)]"
+ ]
+ },
+ "execution_count": 126,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "select AVG(course_percentage) \n",
+ "FROM courses"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c28c9c2e-6275-4e8c-bee9-36f5795f62eb",
+ "metadata": {},
+ "source": [
+ "Few things to keep in mind when dealing with the aggregation function\n",
+ "\n",
+ "- You are not restricted in using just one aggregation function in the SELECT statement.✅\n",
+ "\n",
+ "```sql\n",
+ "SELECT COUNT(DISTINCT course_name),max(course_percentage) FROM courses;\n",
+ "```\n",
+ "\n",
+ "- You CAN'T use aggregations and regular columns in a single query. You can use only when you have a `GROUP BY` clause. (will discuss soon)❌\n",
+ "\n",
+ "```sql\n",
+ "SELECT COUNT(DISTINCT course_name),course_percentage FROM courses;\n",
+ "```\n",
+ "\n",
+ "- You CAN'T use aggregation function in a where clause. The following query is wrong ❌\n",
+ "\n",
+ "```sql\n",
+ "SELECT * FROM courses WHERE course_percentage < AVG(course_percentage);\n",
+ "```\n",
+ "\n",
+ "You can answer this question when we discuss subqueries in the next class (another reason to learn subqueries :) )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2164f169-3e19-4804-9985-4a2f392cbd13",
+ "metadata": {},
+ "source": [
+ "### Grouping\n",
+ "\n",
+ "The aggregations we learned in our previous session also become useful when using the `GROUP BY` statement. `GROUP BY` statement divides a table into groups of rows based on the values of one or more columns. Once this grouping is done, you can apply your aggregation to these groups.\n",
+ "\n",
+ "Syntax:\n",
+ "```sql\n",
+ "SELECT\n",
+ " grouping_columns, aggregated_columns\n",
+ "FROM\n",
+ " table1\n",
+ "GROUP BY\n",
+ " grouping_columns\n",
+ "```\n",
+ "\n",
+ "Example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 42,
+ "id": "7ac98229-6261-48fb-ac85-20435c5d325c",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "15 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " id \n",
+ " student_no \n",
+ " course_name \n",
+ " course_year \n",
+ " course_percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.0 \n",
+ " \n",
+ " \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 91.0 \n",
+ " \n",
+ " \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 81.0 \n",
+ " \n",
+ " \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 93.0 \n",
+ " \n",
+ " \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (3, 222, 'Health and Technology', 2020, 80.0),\n",
+ " (4, 222, 'Web and Cloud Computing', 2019, 91.0),\n",
+ " (5, 222, 'Spark Programming', 2019, 90.0),\n",
+ " (6, 333, 'Parallel Computing', 2019, 90.0),\n",
+ " (7, 444, 'Large Scale Infrastructures', 2019, 83.0),\n",
+ " (8, 444, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (9, 555, 'TPCS INFO TECH', 2020, 78.0),\n",
+ " (10, 555, 'Health and Technology', 2020, 81.0),\n",
+ " (11, 666, 'Data Visualization', 2021, 85.0),\n",
+ " (12, 777, 'Parallel Computing', 2019, 87.0),\n",
+ " (13, 888, 'Spark Programming', 2019, 93.0),\n",
+ " (14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (15, 999, 'Data Visualization', 2019, 87.0)]"
+ ]
+ },
+ "execution_count": 42,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "select * from courses;"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 43,
+ "id": "dc492d9a-f26d-494b-97c0-ef208c6c3187",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "7 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " course_name \n",
+ " avg \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 86.66666666666667 \n",
+ " \n",
+ " \n",
+ " Large Scale Infrastructures \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " Spark Programming \n",
+ " 91.5 \n",
+ " \n",
+ " \n",
+ " Web and Cloud Computing \n",
+ " 91.0 \n",
+ " \n",
+ " \n",
+ " Health and Technology \n",
+ " 80.5 \n",
+ " \n",
+ " \n",
+ " TPCS INFO TECH \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " Parallel Computing \n",
+ " 88.5 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[('Data Visualization', 86.66666666666667),\n",
+ " ('Large Scale Infrastructures', 83.0),\n",
+ " ('Spark Programming', 91.5),\n",
+ " ('Web and Cloud Computing', 91.0),\n",
+ " ('Health and Technology', 80.5),\n",
+ " ('TPCS INFO TECH', 88.0),\n",
+ " ('Parallel Computing', 88.5)]"
+ ]
+ },
+ "execution_count": 43,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "select course_name, AVG(course_percentage) \n",
+ "FROM courses\n",
+ "group by course_name;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d3e6fb09-9bf3-4c40-9933-158edd938eb6",
+ "metadata": {},
+ "source": [
+ "We can also perform a multi level grouping;"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 51,
+ "id": "a9edaba0-6db9-4c80-927c-2df18798d0f3",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "6 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " course_name \n",
+ " course_year \n",
+ " avg \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.5 \n",
+ " \n",
+ " \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.5 \n",
+ " \n",
+ " \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.5 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[('Parallel Computing', 2019, 88.5),\n",
+ " ('Health and Technology', 2020, 80.5),\n",
+ " ('Large Scale Infrastructures', 2019, 83.0),\n",
+ " ('TPCS INFO TECH', 2020, 78.0),\n",
+ " ('Data Visualization', 2021, 85.0),\n",
+ " ('Data Visualization', 2019, 87.5)]"
+ ]
+ },
+ "execution_count": 51,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "select course_name, course_year,AVG(course_percentage) \n",
+ "FROM courses\n",
+ "group by course_name, course_year\n",
+ "having AVG(course_percentage) <90 ;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "59b16985-fefc-4326-bfde-b7f55dcf9610",
+ "metadata": {},
+ "source": [
+ "Now, what if I want to see only the courses with an average of less than 90 %? We mentioned before that this kind of filtering (filtering on the aggregation function) is not possible using `WHERE` statement, and that's why we want the `HAVING` statement to do filtering using these aggregated values.\n",
+ "\n",
+ "### HAVING\n",
+ "\n",
+ "Syntax:\n",
+ "```sql\n",
+ "SELECT\n",
+ " grouping_columns, aggregated_columns\n",
+ "FROM\n",
+ " table1\n",
+ "GROUP BY\n",
+ " grouping_columns\n",
+ "HAVING\n",
+ " group_condition\n",
+ "\n",
+ "```\n",
+ "\n",
+ "```{important}\n",
+ "To summarize:\n",
+ "\n",
+ "- WHERE filters rows before grouping. It filters records in a table level\n",
+ "- HAVING filters groups after grouping. It filters records in a group level\n",
+ "```\n",
+ "\n",
+ "For example, let's get the question we raised at the end of the grouping section. I want to see the courses with a course average of less than 90 %? "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 49,
+ "id": "8210644e-b6bd-4106-aa3b-85520b30380a",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "5 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " course_name \n",
+ " avg \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 86.66666666666667 \n",
+ " \n",
+ " \n",
+ " Large Scale Infrastructures \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " Health and Technology \n",
+ " 80.5 \n",
+ " \n",
+ " \n",
+ " TPCS INFO TECH \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " Parallel Computing \n",
+ " 88.5 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[('Data Visualization', 86.66666666666667),\n",
+ " ('Large Scale Infrastructures', 83.0),\n",
+ " ('Health and Technology', 80.5),\n",
+ " ('TPCS INFO TECH', 88.0),\n",
+ " ('Parallel Computing', 88.5)]"
+ ]
+ },
+ "execution_count": 49,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "select course_name, AVG(course_percentage) \n",
+ "FROM courses\n",
+ "group by course_name\n",
+ "having AVG(course_percentage) <90 ;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ac4b81f6-37f8-4e28-914d-9d73aa271733",
+ "metadata": {},
+ "source": [
+ "### Using alias (AS)\n",
+ "\n",
+ "Until now, we have been referring tables as table names and columns as column names from the schema. But when writing SQL, we are not required to use the same column and table names as in the schema. Instead, we can create aliases for a column or a table using the keyword `AS`.\n",
+ "\n",
+ "Syntax:\n",
+ "```sql\n",
+ "SELECT\n",
+ " column1 [AS] c1,\n",
+ " column2 [AS] c2\n",
+ "FROM\n",
+ " table1 [AS] t1;\n",
+ "```\n",
+ "\n",
+ "It's entirely optional to use AS, but WHY do we want it? \n",
+ "- This makes code more readable\n",
+ "- You can return the columns with a more meaningful name \n",
+ "- Helps a lot when we do JOINS ( wait for the next topic)\n",
+ "\n",
+ "Let's rewrite our previous query using AS"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 129,
+ "id": "4ff59e7a-50e7-4c4a-8469-35ee6481ee29",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "8 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " course_name \n",
+ " course_year \n",
+ " average course percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.5 \n",
+ " \n",
+ " \n",
+ " C programming \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.5 \n",
+ " \n",
+ " \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.5 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[('Parallel Computing', 2019, 88.5),\n",
+ " ('C programming', 2019, 87.0),\n",
+ " ('Health and Technology', 2020, 80.5),\n",
+ " ('Introduction to Genomics', 2019, 87.0),\n",
+ " ('Large Scale Infrastructures', 2019, 83.0),\n",
+ " ('TPCS INFO TECH', 2020, 78.0),\n",
+ " ('Data Visualization', 2021, 85.0),\n",
+ " ('Data Visualization', 2019, 87.5)]"
+ ]
+ },
+ "execution_count": 129,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "select course_name, course_year,AVG(course_percentage) AS \"average course percentage\"\n",
+ "FROM courses AS c\n",
+ "group by course_name, course_year\n",
+ "having AVG(course_percentage) <90 ;"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 137,
+ "id": "d66a7187-7ea7-42d5-a90d-76e584456600",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "17 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " id \n",
+ " student_no \n",
+ " course_name \n",
+ " course_year \n",
+ " course_percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.0 \n",
+ " \n",
+ " \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 91.0 \n",
+ " \n",
+ " \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 81.0 \n",
+ " \n",
+ " \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ " 12 \n",
+ " 666 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 93.0 \n",
+ " \n",
+ " \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 16 \n",
+ " 1000 \n",
+ " C programming \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 17 \n",
+ " 1111 \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (3, 222, 'Health and Technology', 2020, 80.0),\n",
+ " (4, 222, 'Web and Cloud Computing', 2019, 91.0),\n",
+ " (5, 222, 'Spark Programming', 2019, 90.0),\n",
+ " (6, 333, 'Parallel Computing', 2019, 90.0),\n",
+ " (7, 444, 'Large Scale Infrastructures', 2019, 83.0),\n",
+ " (8, 444, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (9, 555, 'TPCS INFO TECH', 2020, 78.0),\n",
+ " (10, 555, 'Health and Technology', 2020, 81.0),\n",
+ " (11, 666, 'Data Visualization', 2021, 85.0),\n",
+ " (12, 666, 'Parallel Computing', 2019, 87.0),\n",
+ " (13, 888, 'Spark Programming', 2019, 93.0),\n",
+ " (14, 999, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (15, 999, 'Data Visualization', 2019, 87.0),\n",
+ " (16, 1000, 'C programming', 2019, 87.0),\n",
+ " (17, 1111, 'Introduction to Genomics', 2019, 87.0)]"
+ ]
+ },
+ "execution_count": 137,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT *\n",
+ "FROM courses;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a81789ef-1835-4a47-bc68-d172531eefbc",
+ "metadata": {},
+ "source": [
+ "### Checkpoint 2 !! Take some time and think if you can...\n",
+ "\n",
+ "- Do some aggregations and grouping queries using SQL.\n",
+ " - GROUP BY\n",
+ " - aggregation functions\n",
+ " - HAVING\n",
+ "\n",
+ "Until now, you deal with queries on a single table. What if we are interested in data from another table as well? For example, I am interested in seeing the course details (from the `courses` table) and the students (from the `students` table) related to those courses. In these situations, we use joins. Let's learn how to stitch tables together. Before that, let's do some exercise..."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f99b3fa8-0acd-4c08-891c-0c3dfc8865c5",
+ "metadata": {},
+ "source": [
+ "### Check point 2 exercise.\n",
+ "\n",
+ "#### iclicker questions\n",
+ "\n",
+ "***Question 1:*** Spot the issue, if any, in this SQL query \n",
+ "\n",
+ "```sql\n",
+ "SELECT COUNT(DISTINCT course_name),max(course_percentage),course_percentage \n",
+ "FROM courses ;\n",
+ "```\n",
+ "\n",
+ "A: Multiple aggregation functions in the SELECT statement\n",
+ "\n",
+ "B: No issues\n",
+ "\n",
+ "C: Can't use DISTINCT inside an aggregation function\n",
+ "\n",
+ "D: Can't use aggregations and regular columns in a single query\n",
+ "\n",
+ "```{toggle}\n",
+ "***Answer: D***\n",
+ "```\n",
+ "\n",
+ "***Question 2:*** Spot the issue, if any, in this SQL query \n",
+ "\n",
+ "```sql\n",
+ "SELECT COUNT(DISTINCT course_name),max(course_percentage),course_year\n",
+ "FROM courses \n",
+ "GROUP BY course_year;\n",
+ "```\n",
+ "\n",
+ "A: Multiple aggregation functions in the SELECT statement\n",
+ "\n",
+ "B: No issues\n",
+ "\n",
+ "C: Can't use DISTINCT inside an aggregation function\n",
+ "\n",
+ "D: Can't use aggregations and regular columns in a single query\n",
+ "\n",
+ "```{toggle}\n",
+ "***Answer: B***\n",
+ "```\n",
+ "\n",
+ "***Question 3:*** Spot the issue, if any, in this SQL query \n",
+ "\n",
+ "```sql\n",
+ "SELECT COUNT(DISTINCT course_name),max(course_percentage),course_year\n",
+ "FROM courses \n",
+ "WHERE course_name != 'TPCS INFO TECH'\n",
+ "GROUP BY course_year\n",
+ "having course_percentage < 90;\n",
+ "```\n",
+ "\n",
+ "A: No issues\n",
+ "\n",
+ "B: Can't use column `course_year` in `SELECT`\n",
+ "\n",
+ "C: Can't use WHERE when using aggregation functions\n",
+ "\n",
+ "D: Can't use course_percentage in a `HAVING` statement\n",
+ "\n",
+ "```{toggle}\n",
+ "***Answer: D***\n",
+ "```\n",
+ "\n",
+ "#### Reasoning Question\n",
+ "\n",
+ "***Question 1:*** We learned the `GROUP BY` clause, and we used it with aggregate functions. Using table `courses`, write a SQL query using `GROUP BY` without any aggregation function. Write your findings and learnings.\n",
+ "\n",
+ "```{toggle}\n",
+ "You must have tried a variety of SQL queries and got into error;\n",
+ "\n",
+ "***select * FROM courses\n",
+ "group by course_name;***\n",
+ "\n",
+ "***select course_year,course_name\n",
+ "FROM courses\n",
+ "group by course_name;***\n",
+ "\n",
+ "Below is one query that runs, and this query can be considered equivalent to using `DISTINCT` if no aggregate functions are used. \n",
+ "\n",
+ "***select course_name\n",
+ "FROM courses\n",
+ "group by course_name;***\n",
+ "\n",
+ "```{important}\n",
+ "In an aggregation query, the unaggregated expressions need to be consistent with the `GROUP BY` expressions. And all other expressions need to use aggregation functions\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e4010eaa-4240-4a85-bfe0-9dad81b29a74",
+ "metadata": {},
+ "source": [
+ "## Lap 3 🧃\n",
+ "### Join\n",
+ "\n",
+ "Syntax:\n",
+ "```sql\n",
+ "SELECT\n",
+ " columns\n",
+ "FROM\n",
+ " left_table\n",
+ "join_type\n",
+ " right_table\n",
+ "ON\n",
+ " join_condition\n",
+ ";\n",
+ "```\n",
+ "\n",
+ "Following are the types of joins\n",
+ "### Cross join\n",
+ "This is the simplest way of performing a join by cross joining 2 tables (like the cartesian product from your relational algebra classes), in our case, table `students` and `courses`. This kind of join returns all possible combinations of rows from `students` and `courses`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "3a8aa89a-bb69-46f2-af7e-a8f7d53171ac",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "135 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " student_no \n",
+ " stud_name \n",
+ " age \n",
+ " major \n",
+ " id \n",
+ " student_no_1 \n",
+ " course_name \n",
+ " course_year \n",
+ " course_percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(111, 'Catherine', 23, 'MBAN', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 3, 222, 'Health and Technology', 2020, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 3, 222, 'Health and Technology', 2020, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 3, 222, 'Health and Technology', 2020, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 3, 222, 'Health and Technology', 2020, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 3, 222, 'Health and Technology', 2020, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 3, 222, 'Health and Technology', 2020, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 3, 222, 'Health and Technology', 2020, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 3, 222, 'Health and Technology', 2020, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 3, 222, 'Health and Technology', 2020, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 4, 222, 'Web and Cloud Computing', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 4, 222, 'Web and Cloud Computing', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 4, 222, 'Web and Cloud Computing', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 4, 222, 'Web and Cloud Computing', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 4, 222, 'Web and Cloud Computing', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 4, 222, 'Web and Cloud Computing', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 4, 222, 'Web and Cloud Computing', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 4, 222, 'Web and Cloud Computing', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 4, 222, 'Web and Cloud Computing', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 5, 222, 'Spark Programming', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 5, 222, 'Spark Programming', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 5, 222, 'Spark Programming', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 5, 222, 'Spark Programming', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 5, 222, 'Spark Programming', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 5, 222, 'Spark Programming', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 5, 222, 'Spark Programming', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 5, 222, 'Spark Programming', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 5, 222, 'Spark Programming', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 6, 333, 'Parallel Computing', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 6, 333, 'Parallel Computing', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 6, 333, 'Parallel Computing', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 6, 333, 'Parallel Computing', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 6, 333, 'Parallel Computing', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 6, 333, 'Parallel Computing', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 6, 333, 'Parallel Computing', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 6, 333, 'Parallel Computing', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 6, 333, 'Parallel Computing', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 7, 444, 'Large Scale Infrastructures', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 7, 444, 'Large Scale Infrastructures', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 7, 444, 'Large Scale Infrastructures', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 7, 444, 'Large Scale Infrastructures', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 7, 444, 'Large Scale Infrastructures', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 7, 444, 'Large Scale Infrastructures', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 7, 444, 'Large Scale Infrastructures', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 7, 444, 'Large Scale Infrastructures', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 7, 444, 'Large Scale Infrastructures', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 8, 444, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 8, 444, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 8, 444, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 8, 444, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 8, 444, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 8, 444, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 8, 444, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 8, 444, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 8, 444, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 9, 555, 'TPCS INFO TECH', 2020, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 9, 555, 'TPCS INFO TECH', 2020, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 9, 555, 'TPCS INFO TECH', 2020, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 9, 555, 'TPCS INFO TECH', 2020, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 9, 555, 'TPCS INFO TECH', 2020, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 9, 555, 'TPCS INFO TECH', 2020, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 9, 555, 'TPCS INFO TECH', 2020, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 9, 555, 'TPCS INFO TECH', 2020, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 9, 555, 'TPCS INFO TECH', 2020, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 10, 555, 'Health and Technology', 2020, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 10, 555, 'Health and Technology', 2020, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 10, 555, 'Health and Technology', 2020, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 10, 555, 'Health and Technology', 2020, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 10, 555, 'Health and Technology', 2020, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 10, 555, 'Health and Technology', 2020, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 10, 555, 'Health and Technology', 2020, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 10, 555, 'Health and Technology', 2020, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 10, 555, 'Health and Technology', 2020, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 11, 666, 'Data Visualization', 2021, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 11, 666, 'Data Visualization', 2021, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 11, 666, 'Data Visualization', 2021, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 11, 666, 'Data Visualization', 2021, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 11, 666, 'Data Visualization', 2021, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 11, 666, 'Data Visualization', 2021, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 11, 666, 'Data Visualization', 2021, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 11, 666, 'Data Visualization', 2021, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 11, 666, 'Data Visualization', 2021, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 12, 777, 'Parallel Computing', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 12, 777, 'Parallel Computing', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 12, 777, 'Parallel Computing', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 12, 777, 'Parallel Computing', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 12, 777, 'Parallel Computing', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 12, 777, 'Parallel Computing', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 12, 777, 'Parallel Computing', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 12, 777, 'Parallel Computing', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 12, 777, 'Parallel Computing', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 13, 888, 'Spark Programming', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 13, 888, 'Spark Programming', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 13, 888, 'Spark Programming', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 13, 888, 'Spark Programming', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 13, 888, 'Spark Programming', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 13, 888, 'Spark Programming', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 13, 888, 'Spark Programming', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 13, 888, 'Spark Programming', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 13, 888, 'Spark Programming', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 15, 999, 'Data Visualization', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 15, 999, 'Data Visualization', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 15, 999, 'Data Visualization', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 15, 999, 'Data Visualization', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 15, 999, 'Data Visualization', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 15, 999, 'Data Visualization', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 15, 999, 'Data Visualization', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 15, 999, 'Data Visualization', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 15, 999, 'Data Visualization', 2019, 88.0)]"
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "\n",
+ "SELECT\n",
+ " *\n",
+ "FROM\n",
+ " students\n",
+ "CROSS JOIN\n",
+ " courses\n",
+ ";"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "57ec5f08-452f-4376-a71c-c1d27552a712",
+ "metadata": {},
+ "source": [
+ "```{note}\n",
+ "But in real life, we usually perform joins on a column, and we will discuss some types of joins on columns in the following sections. Since we are performing joins on a column, we need to pass that information using the `ON` keyword to give which columns are used to stitch the tables together\n",
+ "```\n",
+ "### Inner join\n",
+ "\n",
+ "Inner join only returns the matching rows from the left and right tables."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 65,
+ "id": "c4959539-2048-404b-a5ff-2d5cbf146f72",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "15 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " student_no \n",
+ " stud_name \n",
+ " age \n",
+ " course_name \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Web and Cloud Computing \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " Large Scale Infrastructures \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(111, 'Catherine', 23, 'TPCS INFO TECH'),\n",
+ " (111, 'Catherine', 23, 'Data Visualization'),\n",
+ " (222, 'Tiff', 28, 'Health and Technology'),\n",
+ " (222, 'Tiff', 28, 'Web and Cloud Computing'),\n",
+ " (222, 'Tiff', 28, 'Spark Programming'),\n",
+ " (333, 'John', 23, 'Parallel Computing'),\n",
+ " (444, 'Amir', 28, 'Large Scale Infrastructures'),\n",
+ " (444, 'Amir', 28, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'Health and Technology'),\n",
+ " (666, 'Isha', 30, 'Data Visualization'),\n",
+ " (666, 'Isha', 30, 'Parallel Computing'),\n",
+ " (888, 'Angela', 27, 'Spark Programming'),\n",
+ " (999, 'Jason', 30, 'TPCS INFO TECH'),\n",
+ " (999, 'Jason', 30, 'Data Visualization')]"
+ ]
+ },
+ "execution_count": 65,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "\n",
+ "SELECT s.student_no,s.stud_name,s.age,c.course_name\n",
+ "FROM\n",
+ " students AS s\n",
+ "INNER JOIN\n",
+ " courses AS c\n",
+ "ON\n",
+ " s.student_no = c.student_no;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7aff1260-a2c7-42f2-a92a-92ba317d64c8",
+ "metadata": {},
+ "source": [
+ "```{margin}\n",
+ "Check how we are using the alias `AS` we learned in the previous session.\n",
+ "```\n",
+ "\n",
+ "```{note}\n",
+ "In the returned table, student “Heidy” is missing as that student is not taking any courses and is not mentioned in the `courses`. Also, the courses \"C programming\" and \"Introduction to Genomics\" are missing since no students from our `student` table are taking these courses.\n",
+ "```\n",
+ "### Outer join \n",
+ "\n",
+ "An outer join returns all the rows from one or both of the tables that join. There are 3 variations of it. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6140faf0-24d5-49ed-937b-4fdc45447206",
+ "metadata": {},
+ "source": [
+ "#### Left outer\n",
+ "The first table that appears in the query is the left table, and the one appearing after the `LEFT OUTER JOIN` keyword is the right table.\n",
+ "\n",
+ "A left outer join returns all rows from the left table (matching or not), in addition to the matching rows from both tables. So the non-matching rows from the left table are assigned null values in the columns that belong to the right table.\n",
+ "\n",
+ "If you think about it from a Venn diagram perspective, it will look like...\n",
+ "\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 66,
+ "id": "c2b3c698-c279-47e5-9646-6857bd3b2ede",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "16 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " student_no \n",
+ " stud_name \n",
+ " age \n",
+ " course_name \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Web and Cloud Computing \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " Large Scale Infrastructures \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Heidy \n",
+ " 30 \n",
+ " None \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(111, 'Catherine', 23, 'TPCS INFO TECH'),\n",
+ " (111, 'Catherine', 23, 'Data Visualization'),\n",
+ " (222, 'Tiff', 28, 'Health and Technology'),\n",
+ " (222, 'Tiff', 28, 'Web and Cloud Computing'),\n",
+ " (222, 'Tiff', 28, 'Spark Programming'),\n",
+ " (333, 'John', 23, 'Parallel Computing'),\n",
+ " (444, 'Amir', 28, 'Large Scale Infrastructures'),\n",
+ " (444, 'Amir', 28, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'Health and Technology'),\n",
+ " (666, 'Isha', 30, 'Data Visualization'),\n",
+ " (666, 'Isha', 30, 'Parallel Computing'),\n",
+ " (888, 'Angela', 27, 'Spark Programming'),\n",
+ " (999, 'Jason', 30, 'TPCS INFO TECH'),\n",
+ " (999, 'Jason', 30, 'Data Visualization'),\n",
+ " (777, 'Heidy', 30, None)]"
+ ]
+ },
+ "execution_count": 66,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "\n",
+ "SELECT s.student_no,s.stud_name,s.age,c.course_name\n",
+ "FROM\n",
+ " students AS s\n",
+ "LEFT OUTER JOIN\n",
+ " courses AS c\n",
+ "ON\n",
+ " s.student_no = c.student_no;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "da7f9856-53c3-4f3c-b505-fd3cf39d5744",
+ "metadata": {},
+ "source": [
+ "#### Right outer \n",
+ "It behaves exactly in the same way as a left join, except that it keeps all rows from the right table and only the matching ones from the left table.\n",
+ "\n",
+ "If you think about it from a Venn diagram perspective, it will look like.\n",
+ "\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 67,
+ "id": "c715fc93-110c-4781-8fa6-632e403e1c89",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "17 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " student_no \n",
+ " stud_name \n",
+ " age \n",
+ " course_name \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Web and Cloud Computing \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " Large Scale Infrastructures \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " None \n",
+ " None \n",
+ " None \n",
+ " C programming \n",
+ " \n",
+ " \n",
+ " None \n",
+ " None \n",
+ " None \n",
+ " Introduction to Genomics \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(111, 'Catherine', 23, 'TPCS INFO TECH'),\n",
+ " (111, 'Catherine', 23, 'Data Visualization'),\n",
+ " (222, 'Tiff', 28, 'Health and Technology'),\n",
+ " (222, 'Tiff', 28, 'Web and Cloud Computing'),\n",
+ " (222, 'Tiff', 28, 'Spark Programming'),\n",
+ " (333, 'John', 23, 'Parallel Computing'),\n",
+ " (444, 'Amir', 28, 'Large Scale Infrastructures'),\n",
+ " (444, 'Amir', 28, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'Health and Technology'),\n",
+ " (666, 'Isha', 30, 'Data Visualization'),\n",
+ " (666, 'Isha', 30, 'Parallel Computing'),\n",
+ " (888, 'Angela', 27, 'Spark Programming'),\n",
+ " (999, 'Jason', 30, 'TPCS INFO TECH'),\n",
+ " (999, 'Jason', 30, 'Data Visualization'),\n",
+ " (None, None, None, 'C programming'),\n",
+ " (None, None, None, 'Introduction to Genomics')]"
+ ]
+ },
+ "execution_count": 67,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "\n",
+ "SELECT s.student_no,s.stud_name,s.age,c.course_name\n",
+ "FROM\n",
+ " students AS s\n",
+ "RIGHT OUTER JOIN\n",
+ " courses AS c\n",
+ "ON\n",
+ " s.student_no = c.student_no;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "474d5994-7832-4586-b2cc-327ef5385ce3",
+ "metadata": {},
+ "source": [
+ "#### Full outer\n",
+ "\n",
+ "left join + right join = full outer join.\n",
+ "\n",
+ "It retrieves matching and non-matching rows from both tables. \n",
+ "\n",
+ "If you think about it from a Venn diagram perspective, it will look like.\n",
+ "\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 68,
+ "id": "34a25ff1-6f68-43e7-8a00-67a213795a3c",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "18 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " student_no \n",
+ " stud_name \n",
+ " age \n",
+ " course_name \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Web and Cloud Computing \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " Large Scale Infrastructures \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " None \n",
+ " None \n",
+ " None \n",
+ " C programming \n",
+ " \n",
+ " \n",
+ " None \n",
+ " None \n",
+ " None \n",
+ " Introduction to Genomics \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Heidy \n",
+ " 30 \n",
+ " None \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(111, 'Catherine', 23, 'TPCS INFO TECH'),\n",
+ " (111, 'Catherine', 23, 'Data Visualization'),\n",
+ " (222, 'Tiff', 28, 'Health and Technology'),\n",
+ " (222, 'Tiff', 28, 'Web and Cloud Computing'),\n",
+ " (222, 'Tiff', 28, 'Spark Programming'),\n",
+ " (333, 'John', 23, 'Parallel Computing'),\n",
+ " (444, 'Amir', 28, 'Large Scale Infrastructures'),\n",
+ " (444, 'Amir', 28, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'Health and Technology'),\n",
+ " (666, 'Isha', 30, 'Data Visualization'),\n",
+ " (666, 'Isha', 30, 'Parallel Computing'),\n",
+ " (888, 'Angela', 27, 'Spark Programming'),\n",
+ " (999, 'Jason', 30, 'TPCS INFO TECH'),\n",
+ " (999, 'Jason', 30, 'Data Visualization'),\n",
+ " (None, None, None, 'C programming'),\n",
+ " (None, None, None, 'Introduction to Genomics'),\n",
+ " (777, 'Heidy', 30, None)]"
+ ]
+ },
+ "execution_count": 68,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "\n",
+ "SELECT s.student_no,s.stud_name,s.age,c.course_name\n",
+ "FROM\n",
+ " students AS s\n",
+ "FULL OUTER JOIN\n",
+ " courses AS c\n",
+ "ON\n",
+ " s.student_no = c.student_no;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1b9ac233-6f00-4276-8e4c-16e7cfbfbbe4",
+ "metadata": {},
+ "source": [
+ "### Summarize:\n",
+ "\n",
+ "***CARTESIAN JOIN:*** returns the Cartesian product of the sets of records from the two or more joined tables.\n",
+ "\n",
+ "***INNER JOIN:*** returns rows when there is a match in both tables.\n",
+ "\n",
+ "***LEFT JOIN:*** returns all rows from the left table, even if there are no matches in the right table.\n",
+ "\n",
+ "***RIGHT JOIN:*** returns all rows from the right table, even if there are no matches in the left table.\n",
+ "\n",
+ "***FULL JOIN:*** combines the results of both left and right outer joins."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "de8a2d6d-3a7a-4985-ac24-140316fbd60e",
+ "metadata": {},
+ "source": [
+ "We learned now about the joins. You know now how to join 2 tables. Once you joined 2 tables its sort of behave like another table that you apply the operations what we learned."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 87,
+ "id": "d33b019c-4093-4605-937b-a2f4b13ff778",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "3 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " stud_name \n",
+ " course_name \n",
+ " course_year \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Isha \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " \n",
+ " \n",
+ " Catherine \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " Jason \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[('Isha', 'Data Visualization', 2021),\n",
+ " ('Catherine', 'Data Visualization', 2019),\n",
+ " ('Jason', 'Data Visualization', 2019)]"
+ ]
+ },
+ "execution_count": 87,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "select s.stud_name,c.course_name, c.course_year\n",
+ "FROM students AS s\n",
+ "INNER JOIN\n",
+ "courses AS c\n",
+ "ON s.student_no = c.student_no\n",
+ "WHERE course_name = 'Data Visualization'\n",
+ "ORDER BY course_year DESC;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d9b1dc63-2d19-4c0e-8a35-2f6a57a59a51",
+ "metadata": {},
+ "source": [
+ "```{important}\n",
+ "We can have all the keywords we learned in a single SQL query, and we have come across some in previous examples. `BUT` the order of SQL keywords `DOES` matter: SELECT, FROM, JOIN, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT. \n",
+ "```\n",
+ "```{seealso}\n",
+ "We experienced performing joins on 2 tables, but joins can also be performed on multiple tables. Multi joins in SQL work by progressively creating derived tables one after the other. Here is the link that explains this [process](https://www.interfacett.com/blogs/multiple-joins-work-just-like-single-joins/)\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d04adf0d-6a40-45d8-922f-84855e75db71",
+ "metadata": {},
+ "source": [
+ "### Checkpoint 3 !! Take some time and think if you can...\n",
+ "- Understand different kinds of joins\n",
+ "- When to use joins\n",
+ "- Write SQL queries to join tables"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "731106d9-ba08-4692-bac2-8463d1518c87",
+ "metadata": {},
+ "source": [
+ "### Check point 3 exercise.\n",
+ "#### iclicker questions\n",
+ "\n",
+ "***Question 1:*** Spot the issue, if any, in this SQL query \n",
+ "\n",
+ "```sql\n",
+ "select s.stud_name,c.course_name, c.course_year\n",
+ "FROM students AS s\n",
+ "INNER JOIN\n",
+ "courses AS c\n",
+ "ON s.student_no = c.student_no\n",
+ "ORDER BY course_year DESC\n",
+ "WHERE course_name = 'Data Visualization';\n",
+ "```\n",
+ "\n",
+ "A: There are some issues with the join key\n",
+ "\n",
+ "B: No issues\n",
+ "\n",
+ "C: Can't specify alias when performing a join\n",
+ "\n",
+ "D: ORDER BY need to come after the WHERE clause\n",
+ "\n",
+ "```{toggle}\n",
+ "***Answer: D***\n",
+ "```\n",
+ "\n",
+ "***Question 2:*** \"In this Venn diagram green color indicate left outer join\" - is this statement TRUE/FALSE?\n",
+ "\n",
+ " \n",
+ "\n",
+ "A: TRUE\n",
+ "\n",
+ "B: FALSE\n",
+ "\n",
+ "```{toggle}\n",
+ "***Answer: FALSE***\n",
+ "\n",
+ "The following figure is what indicates the left outer join.\n",
+ "\n",
+ " \n",
+ "\n",
+ "But what is given here is a special scenario where we apply left join to be useful. For example, what if we want to find all students who are not taking any courses?\n",
+ "\n",
+ "\n",
+ "SELECT s.student_no,s.stud_name,s.age,c.course_name\n",
+ "FROM\n",
+ " students AS s\n",
+ "LEFT OUTER JOIN\n",
+ " courses AS c\n",
+ "ON\n",
+ " s.student_no = c.student_no\n",
+ "WHERE c.student_no is NULL\n",
+ "\n",
+ "\n",
+ "| student_no | stud_name | age | course_name |\n",
+ "|-----------:|----------:|----:|------------:|\n",
+ "| 777 | Heidy | 30 | None |\n",
+ "\n",
+ "Ahaa..! looks like \"heidy\" is not taking any courses; we need to check with her to see why she is not taking any courses :)\n",
+ "\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "12fa7b6e-fb81-41e9-8fa7-982fcee3dba8",
+ "metadata": {},
+ "source": [
+ "## 🏁 Finish line 🏁 🍺\n",
+ "\n",
+ "Are you able to recollect our 3 checkpoints?\n",
+ "\n",
+ "- ***Checkpoint 1:*** Take some time and think if you can...\n",
+ " - Select columns and bring in rows just what we needed (using SELECT & WHERE)\n",
+ " - Return DISTINCT elements\n",
+ " - ORDER BY rows returned based on column(s)\n",
+ " - LIMIT the number of rows returned\n",
+ "- ***Checkpoint 2:*** Take some time and think if you can...\n",
+ " - Do some aggregations and grouping queries using SQL.\n",
+ " - GROUP BY\n",
+ " - aggregation functions\n",
+ " - HAVING\n",
+ "- ***Checkpoint 3:*** Take some time and think if you can...\n",
+ " - Understand different kinds of joins\n",
+ " - When to use joins\n",
+ " - Write SQL queries to join tables"
+ ]
+ }
+ ],
+ "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.8.12"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/_build/html/_static/__init__.py b/_build/html/_static/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/_build/html/_static/__pycache__/__init__.cpython-38.pyc b/_build/html/_static/__pycache__/__init__.cpython-38.pyc
new file mode 100644
index 0000000..161e836
Binary files /dev/null and b/_build/html/_static/__pycache__/__init__.cpython-38.pyc differ
diff --git a/_build/html/_static/basic.css b/_build/html/_static/basic.css
new file mode 100644
index 0000000..18d05ab
--- /dev/null
+++ b/_build/html/_static/basic.css
@@ -0,0 +1,905 @@
+/*
+ * basic.css
+ * ~~~~~~~~~
+ *
+ * Sphinx stylesheet -- basic theme.
+ *
+ * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+
+/* -- main layout ----------------------------------------------------------- */
+
+div.clearer {
+ clear: both;
+}
+
+div.section::after {
+ display: block;
+ content: '';
+ clear: left;
+}
+
+/* -- relbar ---------------------------------------------------------------- */
+
+div.related {
+ width: 100%;
+ font-size: 90%;
+}
+
+div.related h3 {
+ display: none;
+}
+
+div.related ul {
+ margin: 0;
+ padding: 0 0 0 10px;
+ list-style: none;
+}
+
+div.related li {
+ display: inline;
+}
+
+div.related li.right {
+ float: right;
+ margin-right: 5px;
+}
+
+/* -- sidebar --------------------------------------------------------------- */
+
+div.sphinxsidebarwrapper {
+ padding: 10px 5px 0 10px;
+}
+
+div.sphinxsidebar {
+ float: left;
+ width: 270px;
+ margin-left: -100%;
+ font-size: 90%;
+ word-wrap: break-word;
+ overflow-wrap : break-word;
+}
+
+div.sphinxsidebar ul {
+ list-style: none;
+}
+
+div.sphinxsidebar ul ul,
+div.sphinxsidebar ul.want-points {
+ margin-left: 20px;
+ list-style: square;
+}
+
+div.sphinxsidebar ul ul {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+div.sphinxsidebar form {
+ margin-top: 10px;
+}
+
+div.sphinxsidebar input {
+ border: 1px solid #98dbcc;
+ font-family: sans-serif;
+ font-size: 1em;
+}
+
+div.sphinxsidebar #searchbox form.search {
+ overflow: hidden;
+}
+
+div.sphinxsidebar #searchbox input[type="text"] {
+ float: left;
+ width: 80%;
+ padding: 0.25em;
+ box-sizing: border-box;
+}
+
+div.sphinxsidebar #searchbox input[type="submit"] {
+ float: left;
+ width: 20%;
+ border-left: none;
+ padding: 0.25em;
+ box-sizing: border-box;
+}
+
+
+img {
+ border: 0;
+ max-width: 100%;
+}
+
+/* -- search page ----------------------------------------------------------- */
+
+ul.search {
+ margin: 10px 0 0 20px;
+ padding: 0;
+}
+
+ul.search li {
+ padding: 5px 0 5px 20px;
+ background-image: url(file.png);
+ background-repeat: no-repeat;
+ background-position: 0 7px;
+}
+
+ul.search li a {
+ font-weight: bold;
+}
+
+ul.search li p.context {
+ color: #888;
+ margin: 2px 0 0 30px;
+ text-align: left;
+}
+
+ul.keywordmatches li.goodmatch a {
+ font-weight: bold;
+}
+
+/* -- index page ------------------------------------------------------------ */
+
+table.contentstable {
+ width: 90%;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+table.contentstable p.biglink {
+ line-height: 150%;
+}
+
+a.biglink {
+ font-size: 1.3em;
+}
+
+span.linkdescr {
+ font-style: italic;
+ padding-top: 5px;
+ font-size: 90%;
+}
+
+/* -- general index --------------------------------------------------------- */
+
+table.indextable {
+ width: 100%;
+}
+
+table.indextable td {
+ text-align: left;
+ vertical-align: top;
+}
+
+table.indextable ul {
+ margin-top: 0;
+ margin-bottom: 0;
+ list-style-type: none;
+}
+
+table.indextable > tbody > tr > td > ul {
+ padding-left: 0em;
+}
+
+table.indextable tr.pcap {
+ height: 10px;
+}
+
+table.indextable tr.cap {
+ margin-top: 10px;
+ background-color: #f2f2f2;
+}
+
+img.toggler {
+ margin-right: 3px;
+ margin-top: 3px;
+ cursor: pointer;
+}
+
+div.modindex-jumpbox {
+ border-top: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+ margin: 1em 0 1em 0;
+ padding: 0.4em;
+}
+
+div.genindex-jumpbox {
+ border-top: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+ margin: 1em 0 1em 0;
+ padding: 0.4em;
+}
+
+/* -- domain module index --------------------------------------------------- */
+
+table.modindextable td {
+ padding: 2px;
+ border-collapse: collapse;
+}
+
+/* -- general body styles --------------------------------------------------- */
+
+div.body {
+ min-width: 450px;
+ max-width: 800px;
+}
+
+div.body p, div.body dd, div.body li, div.body blockquote {
+ -moz-hyphens: auto;
+ -ms-hyphens: auto;
+ -webkit-hyphens: auto;
+ hyphens: auto;
+}
+
+a.headerlink {
+ visibility: hidden;
+}
+
+a.brackets:before,
+span.brackets > a:before{
+ content: "[";
+}
+
+a.brackets:after,
+span.brackets > a:after {
+ content: "]";
+}
+
+h1:hover > a.headerlink,
+h2:hover > a.headerlink,
+h3:hover > a.headerlink,
+h4:hover > a.headerlink,
+h5:hover > a.headerlink,
+h6:hover > a.headerlink,
+dt:hover > a.headerlink,
+caption:hover > a.headerlink,
+p.caption:hover > a.headerlink,
+div.code-block-caption:hover > a.headerlink {
+ visibility: visible;
+}
+
+div.body p.caption {
+ text-align: inherit;
+}
+
+div.body td {
+ text-align: left;
+}
+
+.first {
+ margin-top: 0 !important;
+}
+
+p.rubric {
+ margin-top: 30px;
+ font-weight: bold;
+}
+
+img.align-left, figure.align-left, .figure.align-left, object.align-left {
+ clear: left;
+ float: left;
+ margin-right: 1em;
+}
+
+img.align-right, figure.align-right, .figure.align-right, object.align-right {
+ clear: right;
+ float: right;
+ margin-left: 1em;
+}
+
+img.align-center, figure.align-center, .figure.align-center, object.align-center {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+img.align-default, figure.align-default, .figure.align-default {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.align-left {
+ text-align: left;
+}
+
+.align-center {
+ text-align: center;
+}
+
+.align-default {
+ text-align: center;
+}
+
+.align-right {
+ text-align: right;
+}
+
+/* -- sidebars -------------------------------------------------------------- */
+
+div.sidebar,
+aside.sidebar {
+ margin: 0 0 0.5em 1em;
+ border: 1px solid #ddb;
+ padding: 7px;
+ background-color: #ffe;
+ width: 40%;
+ float: right;
+ clear: right;
+ overflow-x: auto;
+}
+
+p.sidebar-title {
+ font-weight: bold;
+}
+
+div.admonition, div.topic, blockquote {
+ clear: left;
+}
+
+/* -- topics ---------------------------------------------------------------- */
+
+div.topic {
+ border: 1px solid #ccc;
+ padding: 7px;
+ margin: 10px 0 10px 0;
+}
+
+p.topic-title {
+ font-size: 1.1em;
+ font-weight: bold;
+ margin-top: 10px;
+}
+
+/* -- admonitions ----------------------------------------------------------- */
+
+div.admonition {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ padding: 7px;
+}
+
+div.admonition dt {
+ font-weight: bold;
+}
+
+p.admonition-title {
+ margin: 0px 10px 5px 0px;
+ font-weight: bold;
+}
+
+div.body p.centered {
+ text-align: center;
+ margin-top: 25px;
+}
+
+/* -- content of sidebars/topics/admonitions -------------------------------- */
+
+div.sidebar > :last-child,
+aside.sidebar > :last-child,
+div.topic > :last-child,
+div.admonition > :last-child {
+ margin-bottom: 0;
+}
+
+div.sidebar::after,
+aside.sidebar::after,
+div.topic::after,
+div.admonition::after,
+blockquote::after {
+ display: block;
+ content: '';
+ clear: both;
+}
+
+/* -- tables ---------------------------------------------------------------- */
+
+table.docutils {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ border: 0;
+ border-collapse: collapse;
+}
+
+table.align-center {
+ margin-left: auto;
+ margin-right: auto;
+}
+
+table.align-default {
+ margin-left: auto;
+ margin-right: auto;
+}
+
+table caption span.caption-number {
+ font-style: italic;
+}
+
+table caption span.caption-text {
+}
+
+table.docutils td, table.docutils th {
+ padding: 1px 8px 1px 5px;
+ border-top: 0;
+ border-left: 0;
+ border-right: 0;
+ border-bottom: 1px solid #aaa;
+}
+
+table.footnote td, table.footnote th {
+ border: 0 !important;
+}
+
+th {
+ text-align: left;
+ padding-right: 5px;
+}
+
+table.citation {
+ border-left: solid 1px gray;
+ margin-left: 1px;
+}
+
+table.citation td {
+ border-bottom: none;
+}
+
+th > :first-child,
+td > :first-child {
+ margin-top: 0px;
+}
+
+th > :last-child,
+td > :last-child {
+ margin-bottom: 0px;
+}
+
+/* -- figures --------------------------------------------------------------- */
+
+div.figure, figure {
+ margin: 0.5em;
+ padding: 0.5em;
+}
+
+div.figure p.caption, figcaption {
+ padding: 0.3em;
+}
+
+div.figure p.caption span.caption-number,
+figcaption span.caption-number {
+ font-style: italic;
+}
+
+div.figure p.caption span.caption-text,
+figcaption span.caption-text {
+}
+
+/* -- field list styles ----------------------------------------------------- */
+
+table.field-list td, table.field-list th {
+ border: 0 !important;
+}
+
+.field-list ul {
+ margin: 0;
+ padding-left: 1em;
+}
+
+.field-list p {
+ margin: 0;
+}
+
+.field-name {
+ -moz-hyphens: manual;
+ -ms-hyphens: manual;
+ -webkit-hyphens: manual;
+ hyphens: manual;
+}
+
+/* -- hlist styles ---------------------------------------------------------- */
+
+table.hlist {
+ margin: 1em 0;
+}
+
+table.hlist td {
+ vertical-align: top;
+}
+
+/* -- object description styles --------------------------------------------- */
+
+.sig {
+ font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
+}
+
+.sig-name, code.descname {
+ background-color: transparent;
+ font-weight: bold;
+}
+
+.sig-name {
+ font-size: 1.1em;
+}
+
+code.descname {
+ font-size: 1.2em;
+}
+
+.sig-prename, code.descclassname {
+ background-color: transparent;
+}
+
+.optional {
+ font-size: 1.3em;
+}
+
+.sig-paren {
+ font-size: larger;
+}
+
+.sig-param.n {
+ font-style: italic;
+}
+
+/* C++ specific styling */
+
+.sig-inline.c-texpr,
+.sig-inline.cpp-texpr {
+ font-family: unset;
+}
+
+.sig.c .k, .sig.c .kt,
+.sig.cpp .k, .sig.cpp .kt {
+ color: #0033B3;
+}
+
+.sig.c .m,
+.sig.cpp .m {
+ color: #1750EB;
+}
+
+.sig.c .s, .sig.c .sc,
+.sig.cpp .s, .sig.cpp .sc {
+ color: #067D17;
+}
+
+
+/* -- other body styles ----------------------------------------------------- */
+
+ol.arabic {
+ list-style: decimal;
+}
+
+ol.loweralpha {
+ list-style: lower-alpha;
+}
+
+ol.upperalpha {
+ list-style: upper-alpha;
+}
+
+ol.lowerroman {
+ list-style: lower-roman;
+}
+
+ol.upperroman {
+ list-style: upper-roman;
+}
+
+:not(li) > ol > li:first-child > :first-child,
+:not(li) > ul > li:first-child > :first-child {
+ margin-top: 0px;
+}
+
+:not(li) > ol > li:last-child > :last-child,
+:not(li) > ul > li:last-child > :last-child {
+ margin-bottom: 0px;
+}
+
+ol.simple ol p,
+ol.simple ul p,
+ul.simple ol p,
+ul.simple ul p {
+ margin-top: 0;
+}
+
+ol.simple > li:not(:first-child) > p,
+ul.simple > li:not(:first-child) > p {
+ margin-top: 0;
+}
+
+ol.simple p,
+ul.simple p {
+ margin-bottom: 0;
+}
+
+dl.footnote > dt,
+dl.citation > dt {
+ float: left;
+ margin-right: 0.5em;
+}
+
+dl.footnote > dd,
+dl.citation > dd {
+ margin-bottom: 0em;
+}
+
+dl.footnote > dd:after,
+dl.citation > dd:after {
+ content: "";
+ clear: both;
+}
+
+dl.field-list {
+ display: grid;
+ grid-template-columns: fit-content(30%) auto;
+}
+
+dl.field-list > dt {
+ font-weight: bold;
+ word-break: break-word;
+ padding-left: 0.5em;
+ padding-right: 5px;
+}
+
+dl.field-list > dt:after {
+ content: ":";
+}
+
+dl.field-list > dd {
+ padding-left: 0.5em;
+ margin-top: 0em;
+ margin-left: 0em;
+ margin-bottom: 0em;
+}
+
+dl {
+ margin-bottom: 15px;
+}
+
+dd > :first-child {
+ margin-top: 0px;
+}
+
+dd ul, dd table {
+ margin-bottom: 10px;
+}
+
+dd {
+ margin-top: 3px;
+ margin-bottom: 10px;
+ margin-left: 30px;
+}
+
+dl > dd:last-child,
+dl > dd:last-child > :last-child {
+ margin-bottom: 0;
+}
+
+dt:target, span.highlighted {
+ background-color: #fbe54e;
+}
+
+rect.highlighted {
+ fill: #fbe54e;
+}
+
+dl.glossary dt {
+ font-weight: bold;
+ font-size: 1.1em;
+}
+
+.versionmodified {
+ font-style: italic;
+}
+
+.system-message {
+ background-color: #fda;
+ padding: 5px;
+ border: 3px solid red;
+}
+
+.footnote:target {
+ background-color: #ffa;
+}
+
+.line-block {
+ display: block;
+ margin-top: 1em;
+ margin-bottom: 1em;
+}
+
+.line-block .line-block {
+ margin-top: 0;
+ margin-bottom: 0;
+ margin-left: 1.5em;
+}
+
+.guilabel, .menuselection {
+ font-family: sans-serif;
+}
+
+.accelerator {
+ text-decoration: underline;
+}
+
+.classifier {
+ font-style: oblique;
+}
+
+.classifier:before {
+ font-style: normal;
+ margin: 0 0.5em;
+ content: ":";
+ display: inline-block;
+}
+
+abbr, acronym {
+ border-bottom: dotted 1px;
+ cursor: help;
+}
+
+/* -- code displays --------------------------------------------------------- */
+
+pre {
+ overflow: auto;
+ overflow-y: hidden; /* fixes display issues on Chrome browsers */
+}
+
+pre, div[class*="highlight-"] {
+ clear: both;
+}
+
+span.pre {
+ -moz-hyphens: none;
+ -ms-hyphens: none;
+ -webkit-hyphens: none;
+ hyphens: none;
+}
+
+div[class*="highlight-"] {
+ margin: 1em 0;
+}
+
+td.linenos pre {
+ border: 0;
+ background-color: transparent;
+ color: #aaa;
+}
+
+table.highlighttable {
+ display: block;
+}
+
+table.highlighttable tbody {
+ display: block;
+}
+
+table.highlighttable tr {
+ display: flex;
+}
+
+table.highlighttable td {
+ margin: 0;
+ padding: 0;
+}
+
+table.highlighttable td.linenos {
+ padding-right: 0.5em;
+}
+
+table.highlighttable td.code {
+ flex: 1;
+ overflow: hidden;
+}
+
+.highlight .hll {
+ display: block;
+}
+
+div.highlight pre,
+table.highlighttable pre {
+ margin: 0;
+}
+
+div.code-block-caption + div {
+ margin-top: 0;
+}
+
+div.code-block-caption {
+ margin-top: 1em;
+ padding: 2px 5px;
+ font-size: small;
+}
+
+div.code-block-caption code {
+ background-color: transparent;
+}
+
+table.highlighttable td.linenos,
+span.linenos,
+div.highlight span.gp { /* gp: Generic.Prompt */
+ user-select: none;
+ -webkit-user-select: text; /* Safari fallback only */
+ -webkit-user-select: none; /* Chrome/Safari */
+ -moz-user-select: none; /* Firefox */
+ -ms-user-select: none; /* IE10+ */
+}
+
+div.code-block-caption span.caption-number {
+ padding: 0.1em 0.3em;
+ font-style: italic;
+}
+
+div.code-block-caption span.caption-text {
+}
+
+div.literal-block-wrapper {
+ margin: 1em 0;
+}
+
+code.xref, a code {
+ background-color: transparent;
+ font-weight: bold;
+}
+
+h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
+ background-color: transparent;
+}
+
+.viewcode-link {
+ float: right;
+}
+
+.viewcode-back {
+ float: right;
+ font-family: sans-serif;
+}
+
+div.viewcode-block:target {
+ margin: -1px -10px;
+ padding: 0 10px;
+}
+
+/* -- math display ---------------------------------------------------------- */
+
+img.math {
+ vertical-align: middle;
+}
+
+div.body div.math p {
+ text-align: center;
+}
+
+span.eqno {
+ float: right;
+}
+
+span.eqno a.headerlink {
+ position: absolute;
+ z-index: 1;
+}
+
+div.math:hover a.headerlink {
+ visibility: visible;
+}
+
+/* -- printout stylesheet --------------------------------------------------- */
+
+@media print {
+ div.document,
+ div.documentwrapper,
+ div.bodywrapper {
+ margin: 0 !important;
+ width: 100%;
+ }
+
+ div.sphinxsidebar,
+ div.related,
+ div.footer,
+ #top-link {
+ display: none;
+ }
+}
\ No newline at end of file
diff --git a/_build/html/_static/check-solid.svg b/_build/html/_static/check-solid.svg
new file mode 100644
index 0000000..92fad4b
--- /dev/null
+++ b/_build/html/_static/check-solid.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/_build/html/_static/clipboard.min.js b/_build/html/_static/clipboard.min.js
new file mode 100644
index 0000000..54b3c46
--- /dev/null
+++ b/_build/html/_static/clipboard.min.js
@@ -0,0 +1,7 @@
+/*!
+ * clipboard.js v2.0.8
+ * https://clipboardjs.com/
+ *
+ * Licensed MIT © Zeno Rocha
+ */
+!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return o}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),c=n.n(e);function a(t){try{return document.execCommand(t)}catch(t){return}}var f=function(t){t=c()(t);return a("cut"),t};var l=function(t){var e,n,o,r=1
+
+
+
+
diff --git a/_build/html/_static/copybutton.css b/_build/html/_static/copybutton.css
new file mode 100644
index 0000000..5d29149
--- /dev/null
+++ b/_build/html/_static/copybutton.css
@@ -0,0 +1,81 @@
+/* Copy buttons */
+button.copybtn {
+ position: absolute;
+ display: flex;
+ top: .3em;
+ right: .5em;
+ width: 1.7em;
+ height: 1.7em;
+ opacity: 0;
+ transition: opacity 0.3s, border .3s, background-color .3s;
+ user-select: none;
+ padding: 0;
+ border: none;
+ outline: none;
+ border-radius: 0.4em;
+ border: #e1e1e1 1px solid;
+ background-color: rgb(245, 245, 245);
+}
+
+button.copybtn.success {
+ border-color: #22863a;
+}
+
+button.copybtn img {
+ width: 100%;
+ padding: .2em;
+}
+
+div.highlight {
+ position: relative;
+}
+
+.highlight:hover button.copybtn {
+ opacity: 1;
+}
+
+.highlight button.copybtn:hover {
+ background-color: rgb(235, 235, 235);
+}
+
+.highlight button.copybtn:active {
+ background-color: rgb(187, 187, 187);
+}
+
+/**
+ * A minimal CSS-only tooltip copied from:
+ * https://codepen.io/mildrenben/pen/rVBrpK
+ *
+ * To use, write HTML like the following:
+ *
+ * Short
+ */
+ .o-tooltip--left {
+ position: relative;
+ }
+
+ .o-tooltip--left:after {
+ opacity: 0;
+ visibility: hidden;
+ position: absolute;
+ content: attr(data-tooltip);
+ padding: .2em;
+ font-size: .8em;
+ left: -.2em;
+ background: grey;
+ color: white;
+ white-space: nowrap;
+ z-index: 2;
+ border-radius: 2px;
+ transform: translateX(-102%) translateY(0);
+ transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1);
+}
+
+.o-tooltip--left:hover:after {
+ display: block;
+ opacity: 1;
+ visibility: visible;
+ transform: translateX(-100%) translateY(0);
+ transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1);
+ transition-delay: .5s;
+}
diff --git a/_build/html/_static/copybutton.js b/_build/html/_static/copybutton.js
new file mode 100644
index 0000000..482bda0
--- /dev/null
+++ b/_build/html/_static/copybutton.js
@@ -0,0 +1,197 @@
+// Localization support
+const messages = {
+ 'en': {
+ 'copy': 'Copy',
+ 'copy_to_clipboard': 'Copy to clipboard',
+ 'copy_success': 'Copied!',
+ 'copy_failure': 'Failed to copy',
+ },
+ 'es' : {
+ 'copy': 'Copiar',
+ 'copy_to_clipboard': 'Copiar al portapapeles',
+ 'copy_success': '¡Copiado!',
+ 'copy_failure': 'Error al copiar',
+ },
+ 'de' : {
+ 'copy': 'Kopieren',
+ 'copy_to_clipboard': 'In die Zwischenablage kopieren',
+ 'copy_success': 'Kopiert!',
+ 'copy_failure': 'Fehler beim Kopieren',
+ },
+ 'fr' : {
+ 'copy': 'Copier',
+ 'copy_to_clipboard': 'Copié dans le presse-papier',
+ 'copy_success': 'Copié !',
+ 'copy_failure': 'Échec de la copie',
+ },
+ 'ru': {
+ 'copy': 'Скопировать',
+ 'copy_to_clipboard': 'Скопировать в буфер',
+ 'copy_success': 'Скопировано!',
+ 'copy_failure': 'Не удалось скопировать',
+ },
+ 'zh-CN': {
+ 'copy': '复制',
+ 'copy_to_clipboard': '复制到剪贴板',
+ 'copy_success': '复制成功!',
+ 'copy_failure': '复制失败',
+ }
+}
+
+let locale = 'en'
+if( document.documentElement.lang !== undefined
+ && messages[document.documentElement.lang] !== undefined ) {
+ locale = document.documentElement.lang
+}
+
+let doc_url_root = DOCUMENTATION_OPTIONS.URL_ROOT;
+if (doc_url_root == '#') {
+ doc_url_root = '';
+}
+
+const path_static = `${doc_url_root}_static/`;
+
+/**
+ * Set up copy/paste for code blocks
+ */
+
+const runWhenDOMLoaded = cb => {
+ if (document.readyState != 'loading') {
+ cb()
+ } else if (document.addEventListener) {
+ document.addEventListener('DOMContentLoaded', cb)
+ } else {
+ document.attachEvent('onreadystatechange', function() {
+ if (document.readyState == 'complete') cb()
+ })
+ }
+}
+
+const codeCellId = index => `codecell${index}`
+
+// Clears selected text since ClipboardJS will select the text when copying
+const clearSelection = () => {
+ if (window.getSelection) {
+ window.getSelection().removeAllRanges()
+ } else if (document.selection) {
+ document.selection.empty()
+ }
+}
+
+// Changes tooltip text for two seconds, then changes it back
+const temporarilyChangeTooltip = (el, oldText, newText) => {
+ el.setAttribute('data-tooltip', newText)
+ el.classList.add('success')
+ setTimeout(() => el.setAttribute('data-tooltip', oldText), 2000)
+ setTimeout(() => el.classList.remove('success'), 2000)
+}
+
+// Changes the copy button icon for two seconds, then changes it back
+const temporarilyChangeIcon = (el) => {
+ img = el.querySelector("img");
+ img.setAttribute('src', `${path_static}check-solid.svg`)
+ setTimeout(() => img.setAttribute('src', `${path_static}copy-button.svg`), 2000)
+}
+
+const addCopyButtonToCodeCells = () => {
+ // If ClipboardJS hasn't loaded, wait a bit and try again. This
+ // happens because we load ClipboardJS asynchronously.
+ if (window.ClipboardJS === undefined) {
+ setTimeout(addCopyButtonToCodeCells, 250)
+ return
+ }
+
+ // Add copybuttons to all of our code cells
+ const codeCells = document.querySelectorAll('div.highlight pre')
+ codeCells.forEach((codeCell, index) => {
+ const id = codeCellId(index)
+ codeCell.setAttribute('id', id)
+
+ const clipboardButton = id =>
+ `
+
+ `
+ codeCell.insertAdjacentHTML('afterend', clipboardButton(id))
+ })
+
+function escapeRegExp(string) {
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
+}
+
+// Callback when a copy button is clicked. Will be passed the node that was clicked
+// should then grab the text and replace pieces of text that shouldn't be used in output
+function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") {
+
+ var regexp;
+ var match;
+
+ // Do we check for line continuation characters and "HERE-documents"?
+ var useLineCont = !!lineContinuationChar
+ var useHereDoc = !!hereDocDelim
+
+ // create regexp to capture prompt and remaining line
+ if (isRegexp) {
+ regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)')
+ } else {
+ regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)')
+ }
+
+ const outputLines = [];
+ var promptFound = false;
+ var gotLineCont = false;
+ var gotHereDoc = false;
+ const lineGotPrompt = [];
+ for (const line of textContent.split('\n')) {
+ match = line.match(regexp)
+ if (match || gotLineCont || gotHereDoc) {
+ promptFound = regexp.test(line)
+ lineGotPrompt.push(promptFound)
+ if (removePrompts && promptFound) {
+ outputLines.push(match[2])
+ } else {
+ outputLines.push(line)
+ }
+ gotLineCont = line.endsWith(lineContinuationChar) & useLineCont
+ if (line.includes(hereDocDelim) & useHereDoc)
+ gotHereDoc = !gotHereDoc
+ } else if (!onlyCopyPromptLines) {
+ outputLines.push(line)
+ } else if (copyEmptyLines && line.trim() === '') {
+ outputLines.push(line)
+ }
+ }
+
+ // If no lines with the prompt were found then just use original lines
+ if (lineGotPrompt.some(v => v === true)) {
+ textContent = outputLines.join('\n');
+ }
+
+ // Remove a trailing newline to avoid auto-running when pasting
+ if (textContent.endsWith("\n")) {
+ textContent = textContent.slice(0, -1)
+ }
+ return textContent
+}
+
+
+var copyTargetText = (trigger) => {
+ var target = document.querySelector(trigger.attributes['data-clipboard-target'].value);
+ return formatCopyText(target.innerText, '', false, true, true, true, '', '')
+}
+
+ // Initialize with a callback so we can modify the text before copy
+ const clipboard = new ClipboardJS('.copybtn', {text: copyTargetText})
+
+ // Update UI with error/success messages
+ clipboard.on('success', event => {
+ clearSelection()
+ temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_success'])
+ temporarilyChangeIcon(event.trigger)
+ })
+
+ clipboard.on('error', event => {
+ temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_failure'])
+ })
+}
+
+runWhenDOMLoaded(addCopyButtonToCodeCells)
\ No newline at end of file
diff --git a/_build/html/_static/copybutton_funcs.js b/_build/html/_static/copybutton_funcs.js
new file mode 100644
index 0000000..b9168c5
--- /dev/null
+++ b/_build/html/_static/copybutton_funcs.js
@@ -0,0 +1,58 @@
+function escapeRegExp(string) {
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
+}
+
+// Callback when a copy button is clicked. Will be passed the node that was clicked
+// should then grab the text and replace pieces of text that shouldn't be used in output
+export function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") {
+
+ var regexp;
+ var match;
+
+ // Do we check for line continuation characters and "HERE-documents"?
+ var useLineCont = !!lineContinuationChar
+ var useHereDoc = !!hereDocDelim
+
+ // create regexp to capture prompt and remaining line
+ if (isRegexp) {
+ regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)')
+ } else {
+ regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)')
+ }
+
+ const outputLines = [];
+ var promptFound = false;
+ var gotLineCont = false;
+ var gotHereDoc = false;
+ const lineGotPrompt = [];
+ for (const line of textContent.split('\n')) {
+ match = line.match(regexp)
+ if (match || gotLineCont || gotHereDoc) {
+ promptFound = regexp.test(line)
+ lineGotPrompt.push(promptFound)
+ if (removePrompts && promptFound) {
+ outputLines.push(match[2])
+ } else {
+ outputLines.push(line)
+ }
+ gotLineCont = line.endsWith(lineContinuationChar) & useLineCont
+ if (line.includes(hereDocDelim) & useHereDoc)
+ gotHereDoc = !gotHereDoc
+ } else if (!onlyCopyPromptLines) {
+ outputLines.push(line)
+ } else if (copyEmptyLines && line.trim() === '') {
+ outputLines.push(line)
+ }
+ }
+
+ // If no lines with the prompt were found then just use original lines
+ if (lineGotPrompt.some(v => v === true)) {
+ textContent = outputLines.join('\n');
+ }
+
+ // Remove a trailing newline to avoid auto-running when pasting
+ if (textContent.endsWith("\n")) {
+ textContent = textContent.slice(0, -1)
+ }
+ return textContent
+}
diff --git a/_build/html/_static/css/blank.css b/_build/html/_static/css/blank.css
new file mode 100644
index 0000000..8a686ec
--- /dev/null
+++ b/_build/html/_static/css/blank.css
@@ -0,0 +1,2 @@
+/* This file is intentionally left blank to override the stylesheet of the
+parent theme via theme.conf. The parent style we import directly in theme.css */
\ No newline at end of file
diff --git a/_build/html/_static/css/index.ff1ffe594081f20da1ef19478df9384b.css b/_build/html/_static/css/index.ff1ffe594081f20da1ef19478df9384b.css
new file mode 100644
index 0000000..9b1c5d7
--- /dev/null
+++ b/_build/html/_static/css/index.ff1ffe594081f20da1ef19478df9384b.css
@@ -0,0 +1,6 @@
+/*!
+ * Bootstrap v4.5.0 (https://getbootstrap.com/)
+ * Copyright 2011-2020 The Bootstrap Authors
+ * Copyright 2011-2020 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:540px;--breakpoint-md:720px;--breakpoint-lg:960px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,:after,:before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-size:1rem;line-height:1.5;color:#212529;text-align:left}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;text-decoration:underline dotted;cursor:help;border-bottom:0;text-decoration-skip-ink:none}address{font-style:normal;line-height:inherit}address,dl,ol,ul{margin-bottom:1rem}dl,ol,ul{margin-top:0}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;background-color:transparent}a:hover{color:#0056b3}a:not([href]),a:not([href]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{border-style:none}img,svg{vertical-align:middle}svg{overflow:hidden}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem}.display-1,.display-2{font-weight:300;line-height:1.2}.display-2{font-size:5.5rem}.display-3{font-size:4.5rem}.display-3,.display-4{font-weight:300;line-height:1.2}.display-4{font-size:3.5rem}hr{margin-top:1rem;margin-bottom:1rem;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-inline,.list-unstyled{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer:before{content:"\2014\00A0"}.img-fluid,.img-thumbnail{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:540px){.container{max-width:540px}}@media (min-width:720px){.container{max-width:720px}}@media (min-width:960px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1400px}}.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:540px){.container,.container-sm{max-width:540px}}@media (min-width:720px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:960px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1400px}}.row{display:flex;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-10,.col-11,.col-12,.col-auto,.col-lg,.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-auto,.col-md,.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12,.col-md-auto,.col-sm,.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{flex-basis:0;flex-grow:1;min-width:0;max-width:100%}.row-cols-1>*{flex:0 0 100%;max-width:100%}.row-cols-2>*{flex:0 0 50%;max-width:50%}.row-cols-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-4>*{flex:0 0 25%;max-width:25%}.row-cols-5>*{flex:0 0 20%;max-width:20%}.row-cols-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-auto{flex:0 0 auto;width:auto;max-width:100%}.col-1{flex:0 0 8.33333%;max-width:8.33333%}.col-2{flex:0 0 16.66667%;max-width:16.66667%}.col-3{flex:0 0 25%;max-width:25%}.col-4{flex:0 0 33.33333%;max-width:33.33333%}.col-5{flex:0 0 41.66667%;max-width:41.66667%}.col-6{flex:0 0 50%;max-width:50%}.col-7{flex:0 0 58.33333%;max-width:58.33333%}.col-8{flex:0 0 66.66667%;max-width:66.66667%}.col-9{flex:0 0 75%;max-width:75%}.col-10{flex:0 0 83.33333%;max-width:83.33333%}.col-11{flex:0 0 91.66667%;max-width:91.66667%}.col-12{flex:0 0 100%;max-width:100%}.order-first{order:-1}.order-last{order:13}.order-0{order:0}.order-1{order:1}.order-2{order:2}.order-3{order:3}.order-4{order:4}.order-5{order:5}.order-6{order:6}.order-7{order:7}.order-8{order:8}.order-9{order:9}.order-10{order:10}.order-11{order:11}.order-12{order:12}.offset-1{margin-left:8.33333%}.offset-2{margin-left:16.66667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333%}.offset-5{margin-left:41.66667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333%}.offset-8{margin-left:66.66667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333%}.offset-11{margin-left:91.66667%}@media (min-width:540px){.col-sm{flex-basis:0;flex-grow:1;min-width:0;max-width:100%}.row-cols-sm-1>*{flex:0 0 100%;max-width:100%}.row-cols-sm-2>*{flex:0 0 50%;max-width:50%}.row-cols-sm-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-sm-4>*{flex:0 0 25%;max-width:25%}.row-cols-sm-5>*{flex:0 0 20%;max-width:20%}.row-cols-sm-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-sm-auto{flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{flex:0 0 8.33333%;max-width:8.33333%}.col-sm-2{flex:0 0 16.66667%;max-width:16.66667%}.col-sm-3{flex:0 0 25%;max-width:25%}.col-sm-4{flex:0 0 33.33333%;max-width:33.33333%}.col-sm-5{flex:0 0 41.66667%;max-width:41.66667%}.col-sm-6{flex:0 0 50%;max-width:50%}.col-sm-7{flex:0 0 58.33333%;max-width:58.33333%}.col-sm-8{flex:0 0 66.66667%;max-width:66.66667%}.col-sm-9{flex:0 0 75%;max-width:75%}.col-sm-10{flex:0 0 83.33333%;max-width:83.33333%}.col-sm-11{flex:0 0 91.66667%;max-width:91.66667%}.col-sm-12{flex:0 0 100%;max-width:100%}.order-sm-first{order:-1}.order-sm-last{order:13}.order-sm-0{order:0}.order-sm-1{order:1}.order-sm-2{order:2}.order-sm-3{order:3}.order-sm-4{order:4}.order-sm-5{order:5}.order-sm-6{order:6}.order-sm-7{order:7}.order-sm-8{order:8}.order-sm-9{order:9}.order-sm-10{order:10}.order-sm-11{order:11}.order-sm-12{order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333%}.offset-sm-2{margin-left:16.66667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333%}.offset-sm-5{margin-left:41.66667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333%}.offset-sm-8{margin-left:66.66667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333%}.offset-sm-11{margin-left:91.66667%}}@media (min-width:720px){.col-md{flex-basis:0;flex-grow:1;min-width:0;max-width:100%}.row-cols-md-1>*{flex:0 0 100%;max-width:100%}.row-cols-md-2>*{flex:0 0 50%;max-width:50%}.row-cols-md-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-md-4>*{flex:0 0 25%;max-width:25%}.row-cols-md-5>*{flex:0 0 20%;max-width:20%}.row-cols-md-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-md-auto{flex:0 0 auto;width:auto;max-width:100%}.col-md-1{flex:0 0 8.33333%;max-width:8.33333%}.col-md-2{flex:0 0 16.66667%;max-width:16.66667%}.col-md-3{flex:0 0 25%;max-width:25%}.col-md-4{flex:0 0 33.33333%;max-width:33.33333%}.col-md-5{flex:0 0 41.66667%;max-width:41.66667%}.col-md-6{flex:0 0 50%;max-width:50%}.col-md-7{flex:0 0 58.33333%;max-width:58.33333%}.col-md-8{flex:0 0 66.66667%;max-width:66.66667%}.col-md-9{flex:0 0 75%;max-width:75%}.col-md-10{flex:0 0 83.33333%;max-width:83.33333%}.col-md-11{flex:0 0 91.66667%;max-width:91.66667%}.col-md-12{flex:0 0 100%;max-width:100%}.order-md-first{order:-1}.order-md-last{order:13}.order-md-0{order:0}.order-md-1{order:1}.order-md-2{order:2}.order-md-3{order:3}.order-md-4{order:4}.order-md-5{order:5}.order-md-6{order:6}.order-md-7{order:7}.order-md-8{order:8}.order-md-9{order:9}.order-md-10{order:10}.order-md-11{order:11}.order-md-12{order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333%}.offset-md-2{margin-left:16.66667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333%}.offset-md-5{margin-left:41.66667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333%}.offset-md-8{margin-left:66.66667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333%}.offset-md-11{margin-left:91.66667%}}@media (min-width:960px){.col-lg{flex-basis:0;flex-grow:1;min-width:0;max-width:100%}.row-cols-lg-1>*{flex:0 0 100%;max-width:100%}.row-cols-lg-2>*{flex:0 0 50%;max-width:50%}.row-cols-lg-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-lg-4>*{flex:0 0 25%;max-width:25%}.row-cols-lg-5>*{flex:0 0 20%;max-width:20%}.row-cols-lg-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-lg-auto{flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{flex:0 0 8.33333%;max-width:8.33333%}.col-lg-2{flex:0 0 16.66667%;max-width:16.66667%}.col-lg-3{flex:0 0 25%;max-width:25%}.col-lg-4{flex:0 0 33.33333%;max-width:33.33333%}.col-lg-5{flex:0 0 41.66667%;max-width:41.66667%}.col-lg-6{flex:0 0 50%;max-width:50%}.col-lg-7{flex:0 0 58.33333%;max-width:58.33333%}.col-lg-8{flex:0 0 66.66667%;max-width:66.66667%}.col-lg-9{flex:0 0 75%;max-width:75%}.col-lg-10{flex:0 0 83.33333%;max-width:83.33333%}.col-lg-11{flex:0 0 91.66667%;max-width:91.66667%}.col-lg-12{flex:0 0 100%;max-width:100%}.order-lg-first{order:-1}.order-lg-last{order:13}.order-lg-0{order:0}.order-lg-1{order:1}.order-lg-2{order:2}.order-lg-3{order:3}.order-lg-4{order:4}.order-lg-5{order:5}.order-lg-6{order:6}.order-lg-7{order:7}.order-lg-8{order:8}.order-lg-9{order:9}.order-lg-10{order:10}.order-lg-11{order:11}.order-lg-12{order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333%}.offset-lg-2{margin-left:16.66667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333%}.offset-lg-5{margin-left:41.66667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333%}.offset-lg-8{margin-left:66.66667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333%}.offset-lg-11{margin-left:91.66667%}}@media (min-width:1200px){.col-xl{flex-basis:0;flex-grow:1;min-width:0;max-width:100%}.row-cols-xl-1>*{flex:0 0 100%;max-width:100%}.row-cols-xl-2>*{flex:0 0 50%;max-width:50%}.row-cols-xl-3>*{flex:0 0 33.33333%;max-width:33.33333%}.row-cols-xl-4>*{flex:0 0 25%;max-width:25%}.row-cols-xl-5>*{flex:0 0 20%;max-width:20%}.row-cols-xl-6>*{flex:0 0 16.66667%;max-width:16.66667%}.col-xl-auto{flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{flex:0 0 8.33333%;max-width:8.33333%}.col-xl-2{flex:0 0 16.66667%;max-width:16.66667%}.col-xl-3{flex:0 0 25%;max-width:25%}.col-xl-4{flex:0 0 33.33333%;max-width:33.33333%}.col-xl-5{flex:0 0 41.66667%;max-width:41.66667%}.col-xl-6{flex:0 0 50%;max-width:50%}.col-xl-7{flex:0 0 58.33333%;max-width:58.33333%}.col-xl-8{flex:0 0 66.66667%;max-width:66.66667%}.col-xl-9{flex:0 0 75%;max-width:75%}.col-xl-10{flex:0 0 83.33333%;max-width:83.33333%}.col-xl-11{flex:0 0 91.66667%;max-width:91.66667%}.col-xl-12{flex:0 0 100%;max-width:100%}.order-xl-first{order:-1}.order-xl-last{order:13}.order-xl-0{order:0}.order-xl-1{order:1}.order-xl-2{order:2}.order-xl-3{order:3}.order-xl-4{order:4}.order-xl-5{order:5}.order-xl-6{order:6}.order-xl-7{order:7}.order-xl-8{order:8}.order-xl-9{order:9}.order-xl-10{order:10}.order-xl-11{order:11}.order-xl-12{order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333%}.offset-xl-2{margin-left:16.66667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333%}.offset-xl-5{margin-left:41.66667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333%}.offset-xl-8{margin-left:66.66667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333%}.offset-xl-11{margin-left:91.66667%}}.table{width:100%;margin-bottom:1rem;color:#212529}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm td,.table-sm th{padding:.3rem}.table-bordered,.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{color:#212529;background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#7abaff}.table-hover .table-primary:hover,.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#b3b7bb}.table-hover .table-secondary:hover,.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#8fd19e}.table-hover .table-success:hover,.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#86cfda}.table-hover .table-info:hover,.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-hover .table-warning:hover,.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#ed969e}.table-hover .table-danger:hover,.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfc}.table-hover .table-light:hover,.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover,.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th,.table-hover .table-active:hover,.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#343a40}.table-dark td,.table-dark th,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:hsla(0,0%,100%,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:hsla(0,0%,100%,.075)}@media (max-width:539.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:719.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:959.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}input[type=date].form-control,input[type=datetime-local].form-control,input[type=month].form-control,input[type=time].form-control{appearance:none}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;font-size:1rem;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size],textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:flex;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:inline-flex;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(40,167,69,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#28a745;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#28a745;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3E%3C/svg%3E") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label:before,.was-validated .custom-control-input:valid~.custom-control-label:before{border-color:#28a745}.custom-control-input.is-valid:checked~.custom-control-label:before,.was-validated .custom-control-input:valid:checked~.custom-control-label:before{border-color:#34ce57;background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label:before,.was-validated .custom-control-input:valid:focus~.custom-control-label:before{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label:before,.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label:before,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545'%3E%3Ccircle cx='6' cy='6' r='4.5'/%3E%3Cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3E%3Ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#dc3545;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545'%3E%3Ccircle cx='6' cy='6' r='4.5'/%3E%3Cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3E%3Ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3E%3C/svg%3E") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label:before,.was-validated .custom-control-input:invalid~.custom-control-label:before{border-color:#dc3545}.custom-control-input.is-invalid:checked~.custom-control-label:before,.was-validated .custom-control-input:invalid:checked~.custom-control-label:before{border-color:#e4606d;background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label:before,.was-validated .custom-control-input:invalid:focus~.custom-control-label:before{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label:before,.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label:before,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:flex;flex-flow:row wrap;align-items:center}.form-inline .form-check{width:100%}@media (min-width:540px){.form-inline label{justify-content:center}.form-inline .form-group,.form-inline label{display:flex;align-items:center;margin-bottom:0}.form-inline .form-group{flex:0 0 auto;flex-flow:row wrap}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:flex;align-items:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{align-items:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary.focus,.btn-primary:focus,.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary.focus,.btn-secondary:focus,.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success.focus,.btn-success:focus,.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info.focus,.btn-info:focus,.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning.focus,.btn-warning:focus,.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger.focus,.btn-danger:focus,.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light.focus,.btn-light:focus,.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark.focus,.btn-dark:focus,.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-outline-primary{color:#007bff;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;text-decoration:none}.btn-link:hover{color:#0056b3}.btn-link.focus,.btn-link:focus,.btn-link:hover{text-decoration:underline}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle:after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty:after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:540px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:720px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:960px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle:after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty:after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle:after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty:after{margin-left:0}.dropright .dropdown-toggle:after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle:after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";display:none}.dropleft .dropdown-toggle:before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty:after{margin-left:0}.dropleft .dropdown-toggle:before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split:after,.dropright .dropdown-toggle-split:after,.dropup .dropdown-toggle-split:after{margin-left:0}.dropleft .dropdown-toggle-split:before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio],.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;flex:1 1 auto;width:1%;min-width:0;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:flex;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label:after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;left:0;z-index:-1;width:1rem;height:1.25rem;opacity:0}.custom-control-input:checked~.custom-control-label:before{color:#fff;border-color:#007bff;background-color:#007bff}.custom-control-input:focus~.custom-control-label:before{box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label:before{border-color:#80bdff}.custom-control-input:not(:disabled):active~.custom-control-label:before{color:#fff;background-color:#b3d7ff;border-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label,.custom-control-input[disabled]~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label:before,.custom-control-input[disabled]~.custom-control-label:before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label:before{pointer-events:none;background-color:#fff;border:1px solid #adb5bd}.custom-control-label:after,.custom-control-label:before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:""}.custom-control-label:after{background:no-repeat 50%/50% 50%}.custom-checkbox .custom-control-label:before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label:before{border-color:#007bff;background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label:before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label:before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label:before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label:before{background-color:rgba(0,123,255,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label:before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label:after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label:after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label:after{background-color:#fff;transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label:before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:#fff url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") no-repeat right .75rem center/8px 10px;border:1px solid #ced4da;border-radius:.25rem;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{display:inline-block;margin-bottom:0}.custom-file,.custom-file-input{position:relative;width:100%;height:calc(1.5em + .75rem + 2px)}.custom-file-input{z-index:2;margin:0;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:disabled~.custom-file-label,.custom-file-input[disabled]~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label:after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]:after{content:attr(data-browse)}.custom-file-label{left:0;z-index:1;height:calc(1.5em + .75rem + 2px);font-weight:400;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label,.custom-file-label:after{position:absolute;top:0;right:0;padding:.375rem .75rem;line-height:1.5;color:#495057}.custom-file-label:after{bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);content:"Browse";background-color:#e9ecef;border-left:inherit;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;height:1.4rem;padding:0;background-color:transparent;appearance:none}.custom-range:focus{outline:none}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#b3d7ff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{transition:none}}.custom-range::-moz-range-thumb:active{background-color:#b3d7ff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{transition:none}}.custom-range::-ms-thumb:active{background-color:#b3d7ff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower,.custom-range::-ms-fill-upper{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label:before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label:before,.custom-file-label,.custom-select{transition:none}}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item{flex:1 1 auto;text-align:center}.nav-justified .nav-item{flex-basis:0;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;padding:.5rem 1rem}.navbar,.navbar .container,.navbar .container-fluid,.navbar .container-lg,.navbar .container-md,.navbar .container-sm,.navbar .container-xl{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat 50%;background-size:100% 100%}@media (max-width:539.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{padding-right:0;padding-left:0}}@media (min-width:540px){.navbar-expand-sm{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:719.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{padding-right:0;padding-left:0}}@media (min-width:720px){.navbar-expand-md{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:959.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{padding-right:0;padding-left:0}}@media (min-width:960px){.navbar-expand-lg{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand,.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30'%3E%3Cpath stroke='rgba(0,0,0,0.5)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand,.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:hsla(0,0%,100%,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:hsla(0,0%,100%,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:hsla(0,0%,100%,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:hsla(0,0%,100%,.5);border-color:hsla(0,0%,100%,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30'%3E%3Cpath stroke='rgba(255,255,255,0.5)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-dark .navbar-text{color:hsla(0,0%,100%,.5)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-body{flex:1 1 auto;min-height:1px;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem}.card-subtitle,.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-bottom:-.75rem;border-bottom:0}.card-header-pills,.card-header-tabs{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img,.card-img-bottom,.card-img-top{flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck .card{margin-bottom:15px}@media (min-width:540px){.card-deck{display:flex;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{flex:1 0 0%;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group>.card{margin-bottom:15px}@media (min-width:540px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:540px){.card-columns{column-count:3;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion>.card{overflow:hidden}.accordion>.card:not(:last-of-type){border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:not(:first-of-type){border-top-left-radius:0;border-top-right-radius:0}.accordion>.card>.card-header{border-radius:0;margin-bottom:-1px}.breadcrumb{flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb,.breadcrumb-item{display:flex}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item:before{display:inline-block;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover:before{text-decoration:underline;text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:3;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}a.badge-primary:focus,a.badge-primary:hover{color:#fff;background-color:#0062cc}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.badge-secondary{color:#fff;background-color:#6c757d}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#545b62}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.badge-success{color:#fff;background-color:#28a745}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#1e7e34}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.badge-info{color:#fff;background-color:#17a2b8}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#117a8b}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.badge-warning{color:#212529;background-color:#ffc107}a.badge-warning:focus,a.badge-warning:hover{color:#212529;background-color:#d39e00}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.badge-danger{color:#fff;background-color:#dc3545}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#bd2130}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.badge-light{color:#212529;background-color:#f8f9fa}a.badge-light:focus,a.badge-light:hover{color:#212529;background-color:#dae0e5}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#1d2124}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:540px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@keyframes progress-bar-stripes{0%{background-position:1rem 0}to{background-position:0 0}}.progress{height:1rem;line-height:0;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress,.progress-bar{display:flex;overflow:hidden}.progress-bar{flex-direction:column;justify-content:center;color:#fff;text-align:center;white-space:nowrap;background-color:#007bff;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,hsla(0,0%,100%,.15) 25%,transparent 0,transparent 50%,hsla(0,0%,100%,.15) 0,hsla(0,0%,100%,.15) 75%,transparent 0,transparent);background-size:1rem 1rem}.progress-bar-animated{animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion:reduce){.progress-bar-animated{animation:none}}.media{display:flex;align-items:flex-start}.media-body{flex:1}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:540px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:720px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:960px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0}a.close.disabled{pointer-events:none}.toast{max-width:350px;overflow:hidden;font-size:.875rem;background-color:hsla(0,0%,100%,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(0,0,0,.1);backdrop-filter:blur(10px);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:flex;align-items:center;padding:.25rem .75rem;color:#6c757d;background-color:hsla(0,0%,100%,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translateY(-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered:before{display:block;height:calc(100vh - 1rem);height:min-content;content:""}.modal-dialog-centered.modal-dialog-scrollable{flex-direction:column;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable:before{content:none}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;align-items:flex-start;justify-content:space-between;padding:1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .close{padding:1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:540px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered:before{height:calc(100vh - 3.5rem);height:min-content}.modal-sm{max-width:300px}}@media (min-width:960px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow:before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow:before,.bs-tooltip-top .arrow:before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow:before,.bs-tooltip-right .arrow:before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow:before,.bs-tooltip-bottom .arrow:before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow:before,.bs-tooltip-left .arrow:before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{top:0;left:0;z-index:1060;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover,.popover .arrow{position:absolute;display:block}.popover .arrow{width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow:after,.popover .arrow:before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=top]>.arrow:before,.bs-popover-top>.arrow:before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top]>.arrow:after,.bs-popover-top>.arrow:after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right]>.arrow:before,.bs-popover-right>.arrow:before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right]>.arrow:after,.bs-popover-right>.arrow:after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=bottom]>.arrow:before,.bs-popover-bottom>.arrow:before{top:0;border-width:0 .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow:after,.bs-popover-bottom>.arrow:after{top:1px;border-width:0 .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header:before,.bs-popover-bottom .popover-header:before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left]>.arrow:before,.bs-popover-left>.arrow:before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left]>.arrow:after,.bs-popover-left>.arrow:after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner:after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:no-repeat 50%/100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3E%3C/svg%3E")}.carousel-control-next-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8'%3E%3Cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3E%3C/svg%3E")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:flex;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@keyframes spinner-border{to{transform:rotate(1turn)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:.25em solid;border-right:.25em solid transparent;border-radius:50%;animation:spinner-border .75s linear infinite}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;animation:spinner-grow .75s linear infinite}.spinner-grow-sm{width:1rem;height:1rem}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important}.rounded-right,.rounded-top{border-top-right-radius:.25rem!important}.rounded-bottom,.rounded-right{border-bottom-right-radius:.25rem!important}.rounded-bottom,.rounded-left{border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix:after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}@media (min-width:540px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}}@media (min-width:720px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}}@media (min-width:960px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive:before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9:before{padding-top:42.85714%}.embed-responsive-16by9:before{padding-top:56.25%}.embed-responsive-4by3:before{padding-top:75%}.embed-responsive-1by1:before{padding-top:100%}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-fill{flex:1 1 auto!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}@media (min-width:540px){.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}}@media (min-width:720px){.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}}@media (min-width:960px){.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:540px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:720px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:960px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.user-select-all{user-select:all!important}.user-select-auto{user-select:auto!important}.user-select-none{user-select:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:sticky!important}.fixed-top{top:0}.fixed-bottom,.fixed-top{position:fixed;right:0;left:0;z-index:1030}.fixed-bottom{bottom:0}@supports (position:sticky){.sticky-top{position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:540px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:720px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:960px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.stretched-link:after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:transparent}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:540px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:720px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:960px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0056b3!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#494f54!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#19692c!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#0f6674!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#ba8b00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#a71d2a!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#cbd3da!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#121416!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:hsla(0,0%,100%,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,:after,:before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]:after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}.container,body{min-width:960px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}}html{font-size:var(--pst-font-size-base);scroll-padding-top:calc(var(--pst-header-height) + 12px)}body{padding-top:calc(var(--pst-header-height) + 20px);background-color:#fff;font-family:var(--pst-font-family-base);font-weight:400;line-height:1.65;color:rgba(var(--pst-color-text-base),1)}p{margin-bottom:1.15rem;font-size:1em;color:rgba(var(--pst-color-paragraph),1)}p.rubric{border-bottom:1px solid #c9c9c9}a{color:rgba(var(--pst-color-link),1);text-decoration:none}a:hover{color:rgba(var(--pst-color-link-hover),1);text-decoration:underline}a.headerlink{color:rgba(var(--pst-color-headerlink),1);font-size:.8em;padding:0 4px;text-decoration:none}a.headerlink:hover{background-color:rgba(var(--pst-color-headerlink),1);color:rgba(var(--pst-color-headerlink-hover),1)}.heading-style,h1,h2,h3,h4,h5,h6{margin:2.75rem 0 1.05rem;font-family:var(--pst-font-family-heading);font-weight:400;line-height:1.15}h1{margin-top:0;font-size:var(--pst-font-size-h1);color:rgba(var(--pst-color-h1),1)}h2{font-size:var(--pst-font-size-h2);color:rgba(var(--pst-color-h2),1)}h3{font-size:var(--pst-font-size-h3);color:rgba(var(--pst-color-h3),1)}h4{font-size:var(--pst-font-size-h4);color:rgba(var(--pst-color-h4),1)}h5{font-size:var(--pst-font-size-h5);color:rgba(var(--pst-color-h5),1)}h6{font-size:var(--pst-font-size-h6);color:rgba(var(--pst-color-h6),1)}.text_small,small{font-size:var(--pst-font-size-milli)}hr{border:0;border-top:1px solid #e5e5e5}code,kbd,pre,samp{font-family:var(--pst-font-family-monospace)}code{color:rgba(var(--pst-color-inline-code),1)}pre{margin:1.5em 0;padding:10px;background-color:rgba(var(--pst-color-preformatted-background),1);color:rgba(var(--pst-color-preformatted-text),1);line-height:1.2em;border:1px solid #c9c9c9;border-radius:.2rem;box-shadow:1px 1px 1px #d8d8d8}dd{margin-top:3px;margin-bottom:10px;margin-left:30px}.navbar{position:fixed;min-height:var(--pst-header-height);width:100%;padding:0}.navbar .container-xl{height:100%}@media (min-width:960px){.navbar #navbar-end>.navbar-end-item{display:inline-block}}.navbar-brand{position:relative;height:var(--pst-header-height);width:auto;padding:.5rem 0}.navbar-brand img{max-width:100%;height:100%;width:auto}.navbar-light{background:#fff!important;box-shadow:0 .125rem .25rem 0 rgba(0,0,0,.11)}.navbar-light .navbar-nav li a.nav-link{padding:0 .5rem;color:rgba(var(--pst-color-navbar-link),1)}.navbar-light .navbar-nav li a.nav-link:hover{color:rgba(var(--pst-color-navbar-link-hover),1)}.navbar-light .navbar-nav>.active>.nav-link{font-weight:600;color:rgba(var(--pst-color-navbar-link-active),1)}.navbar-header a{padding:0 15px}.admonition,div.admonition{margin:1.5625em auto;padding:0 .6rem .8rem;overflow:hidden;page-break-inside:avoid;border-left:.2rem solid;border-left-color:rgba(var(--pst-color-admonition-default),1);border-bottom-color:rgba(var(--pst-color-admonition-default),1);border-right-color:rgba(var(--pst-color-admonition-default),1);border-top-color:rgba(var(--pst-color-admonition-default),1);border-radius:.2rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 .0625rem rgba(0,0,0,.1);transition:color .25s,background-color .25s,border-color .25s}.admonition :last-child,div.admonition :last-child{margin-bottom:0}.admonition p.admonition-title~*,div.admonition p.admonition-title~*{padding:0 1.4rem}.admonition>ol,.admonition>ul,div.admonition>ol,div.admonition>ul{margin-left:1em}.admonition>.admonition-title,div.admonition>.admonition-title{position:relative;margin:0 -.6rem;padding:.4rem .6rem .4rem 2rem;font-weight:700;background-color:rgba(var(--pst-color-admonition-default),.1)}.admonition>.admonition-title:before,div.admonition>.admonition-title:before{position:absolute;left:.6rem;width:1rem;height:1rem;color:rgba(var(--pst-color-admonition-default),1);font-family:Font Awesome\ 5 Free;font-weight:900;content:var(--pst-icon-admonition-default)}.admonition>.admonition-title+*,div.admonition>.admonition-title+*{margin-top:.4em}.admonition.attention,div.admonition.attention{border-color:rgba(var(--pst-color-admonition-attention),1)}.admonition.attention>.admonition-title,div.admonition.attention>.admonition-title{background-color:rgba(var(--pst-color-admonition-attention),.1)}.admonition.attention>.admonition-title:before,div.admonition.attention>.admonition-title:before{color:rgba(var(--pst-color-admonition-attention),1);content:var(--pst-icon-admonition-attention)}.admonition.caution,div.admonition.caution{border-color:rgba(var(--pst-color-admonition-caution),1)}.admonition.caution>.admonition-title,div.admonition.caution>.admonition-title{background-color:rgba(var(--pst-color-admonition-caution),.1)}.admonition.caution>.admonition-title:before,div.admonition.caution>.admonition-title:before{color:rgba(var(--pst-color-admonition-caution),1);content:var(--pst-icon-admonition-caution)}.admonition.warning,div.admonition.warning{border-color:rgba(var(--pst-color-admonition-warning),1)}.admonition.warning>.admonition-title,div.admonition.warning>.admonition-title{background-color:rgba(var(--pst-color-admonition-warning),.1)}.admonition.warning>.admonition-title:before,div.admonition.warning>.admonition-title:before{color:rgba(var(--pst-color-admonition-warning),1);content:var(--pst-icon-admonition-warning)}.admonition.danger,div.admonition.danger{border-color:rgba(var(--pst-color-admonition-danger),1)}.admonition.danger>.admonition-title,div.admonition.danger>.admonition-title{background-color:rgba(var(--pst-color-admonition-danger),.1)}.admonition.danger>.admonition-title:before,div.admonition.danger>.admonition-title:before{color:rgba(var(--pst-color-admonition-danger),1);content:var(--pst-icon-admonition-danger)}.admonition.error,div.admonition.error{border-color:rgba(var(--pst-color-admonition-error),1)}.admonition.error>.admonition-title,div.admonition.error>.admonition-title{background-color:rgba(var(--pst-color-admonition-error),.1)}.admonition.error>.admonition-title:before,div.admonition.error>.admonition-title:before{color:rgba(var(--pst-color-admonition-error),1);content:var(--pst-icon-admonition-error)}.admonition.hint,div.admonition.hint{border-color:rgba(var(--pst-color-admonition-hint),1)}.admonition.hint>.admonition-title,div.admonition.hint>.admonition-title{background-color:rgba(var(--pst-color-admonition-hint),.1)}.admonition.hint>.admonition-title:before,div.admonition.hint>.admonition-title:before{color:rgba(var(--pst-color-admonition-hint),1);content:var(--pst-icon-admonition-hint)}.admonition.tip,div.admonition.tip{border-color:rgba(var(--pst-color-admonition-tip),1)}.admonition.tip>.admonition-title,div.admonition.tip>.admonition-title{background-color:rgba(var(--pst-color-admonition-tip),.1)}.admonition.tip>.admonition-title:before,div.admonition.tip>.admonition-title:before{color:rgba(var(--pst-color-admonition-tip),1);content:var(--pst-icon-admonition-tip)}.admonition.important,div.admonition.important{border-color:rgba(var(--pst-color-admonition-important),1)}.admonition.important>.admonition-title,div.admonition.important>.admonition-title{background-color:rgba(var(--pst-color-admonition-important),.1)}.admonition.important>.admonition-title:before,div.admonition.important>.admonition-title:before{color:rgba(var(--pst-color-admonition-important),1);content:var(--pst-icon-admonition-important)}.admonition.note,div.admonition.note{border-color:rgba(var(--pst-color-admonition-note),1)}.admonition.note>.admonition-title,div.admonition.note>.admonition-title{background-color:rgba(var(--pst-color-admonition-note),.1)}.admonition.note>.admonition-title:before,div.admonition.note>.admonition-title:before{color:rgba(var(--pst-color-admonition-note),1);content:var(--pst-icon-admonition-note)}table.field-list{border-collapse:separate;border-spacing:10px;margin-left:1px}table.field-list th.field-name{padding:1px 8px 1px 5px;white-space:nowrap;background-color:#eee}table.field-list td.field-body p{font-style:italic}table.field-list td.field-body p>strong{font-style:normal}table.field-list td.field-body blockquote{border-left:none;margin:0 0 .3em;padding-left:30px}.table.autosummary td:first-child{white-space:nowrap}.sig{font-family:var(--pst-font-family-monospace)}.sig-inline.c-texpr,.sig-inline.cpp-texpr{font-family:unset}.sig.c .k,.sig.c .kt,.sig.c .m,.sig.c .s,.sig.c .sc,.sig.cpp .k,.sig.cpp .kt,.sig.cpp .m,.sig.cpp .s,.sig.cpp .sc{color:rgba(var(--pst-color-text-base),1)}.sig-name{color:rgba(var(--pst-color-inline-code),1)}blockquote{padding:0 1em;color:#6a737d;border-left:.25em solid #dfe2e5}dt.label>span.brackets:not(:only-child):before{content:"["}dt.label>span.brackets:not(:only-child):after{content:"]"}a.footnote-reference{vertical-align:super;font-size:small}div.deprecated{margin-bottom:10px;margin-top:10px;padding:7px;background-color:#f3e5e5;border:1px solid #eed3d7;border-radius:.5rem}div.deprecated p{color:#b94a48;display:inline}.topic{background-color:#eee}.seealso dd{margin-top:0;margin-bottom:0}.viewcode-back{font-family:var(--pst-font-family-base)}.viewcode-block:target{background-color:#f4debf;border-top:1px solid #ac9;border-bottom:1px solid #ac9}span.guilabel{border:1px solid #7fbbe3;background:#e7f2fa;font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}footer{width:100%;border-top:1px solid #ccc;padding:10px}footer .footer-item p{margin-bottom:0}.bd-search{position:relative;padding:1rem 15px;margin-right:-15px;margin-left:-15px}.bd-search .icon{position:absolute;color:#a4a6a7;left:25px;top:25px}.bd-search input{border-radius:0;border:0;border-bottom:1px solid #e5e5e5;padding-left:35px}.bd-toc{-ms-flex-order:2;order:2;height:calc(100vh - 2rem);overflow-y:auto}@supports (position:-webkit-sticky) or (position:sticky){.bd-toc{position:-webkit-sticky;position:sticky;top:calc(var(--pst-header-height) + 20px);height:calc(100vh - 5rem);overflow-y:auto}}.bd-toc .onthispage{color:#a4a6a7}.section-nav{padding-left:0;border-left:1px solid #eee;border-bottom:none}.section-nav ul{padding-left:1rem}.toc-entry,.toc-entry a{display:block}.toc-entry a{padding:.125rem 1.5rem;color:rgba(var(--pst-color-toc-link),1)}@media (min-width:1200px){.toc-entry a{padding-right:0}}.toc-entry a:hover{color:rgba(var(--pst-color-toc-link-hover),1);text-decoration:none}.bd-sidebar{padding-top:1em}@media (min-width:720px){.bd-sidebar{border-right:1px solid rgba(0,0,0,.1)}@supports (position:-webkit-sticky) or (position:sticky){.bd-sidebar{position:-webkit-sticky;position:sticky;top:calc(var(--pst-header-height) + 20px);z-index:1000;height:calc(100vh - var(--pst-header-height) - 20px)}}}.bd-sidebar.no-sidebar{border-right:0}.bd-links{padding-top:1rem;padding-bottom:1rem;margin-right:-15px;margin-left:-15px}@media (min-width:720px){.bd-links{display:block}@supports (position:-webkit-sticky) or (position:sticky){.bd-links{max-height:calc(100vh - 11rem);overflow-y:auto}}}.bd-sidenav{display:none}.bd-content{padding-top:20px}.bd-content .section{max-width:100%}.bd-content .section table{display:block;overflow:auto}.bd-toc-link{display:block;padding:.25rem 1.5rem;font-weight:600;color:rgba(0,0,0,.65)}.bd-toc-link:hover{color:rgba(0,0,0,.85);text-decoration:none}.bd-toc-item.active{margin-bottom:1rem}.bd-toc-item.active:not(:first-child){margin-top:1rem}.bd-toc-item.active>.bd-toc-link{color:rgba(0,0,0,.85)}.bd-toc-item.active>.bd-toc-link:hover{background-color:transparent}.bd-toc-item.active>.bd-sidenav{display:block}nav.bd-links p.caption{font-size:var(--pst-sidebar-caption-font-size);text-transform:uppercase;font-weight:700;position:relative;margin-top:1.25em;margin-bottom:.5em;padding:0 1.5rem;color:rgba(var(--pst-color-sidebar-caption),1)}nav.bd-links p.caption:first-child{margin-top:0}.bd-sidebar .nav{font-size:var(--pst-sidebar-font-size)}.bd-sidebar .nav ul{list-style:none;padding:0 0 0 1.5rem}.bd-sidebar .nav li>a{display:block;padding:.25rem 1.5rem;color:rgba(var(--pst-color-sidebar-link),1)}.bd-sidebar .nav li>a:hover{color:rgba(var(--pst-color-sidebar-link-hover),1);text-decoration:none;background-color:transparent}.bd-sidebar .nav li>a.reference.external:after{font-family:Font Awesome\ 5 Free;font-weight:900;content:"\f35d";font-size:.75em;margin-left:.3em}.bd-sidebar .nav .active:hover>a,.bd-sidebar .nav .active>a{font-weight:600;color:rgba(var(--pst-color-sidebar-link-active),1)}.toc-h2{font-size:.85rem}.toc-h3{font-size:.75rem}.toc-h4{font-size:.65rem}.toc-entry>.nav-link.active{font-weight:600;color:#130654;color:rgba(var(--pst-color-toc-link-active),1);background-color:transparent;border-left:2px solid rgba(var(--pst-color-toc-link-active),1)}.nav-link:hover{border-style:none}#navbar-main-elements li.nav-item i{font-size:.7rem;padding-left:2px;vertical-align:middle}.bd-toc .nav .nav{display:none}.bd-toc .nav .nav.visible,.bd-toc .nav>.active>ul{display:block}.prev-next-area{margin:20px 0}.prev-next-area p{margin:0 .3em;line-height:1.3em}.prev-next-area i{font-size:1.2em}.prev-next-area a{display:flex;align-items:center;border:none;padding:10px;max-width:45%;overflow-x:hidden;color:rgba(0,0,0,.65);text-decoration:none}.prev-next-area a p.prev-next-title{color:rgba(var(--pst-color-link),1);font-weight:600;font-size:1.1em}.prev-next-area a:hover p.prev-next-title{text-decoration:underline}.prev-next-area a .prev-next-info{flex-direction:column;margin:0 .5em}.prev-next-area a .prev-next-info .prev-next-subtitle{text-transform:capitalize}.prev-next-area a.left-prev{float:left}.prev-next-area a.right-next{float:right}.prev-next-area a.right-next div.prev-next-info{text-align:right}.alert{padding-bottom:0}.alert-info a{color:#e83e8c}#navbar-icon-links i.fa,#navbar-icon-links i.fab,#navbar-icon-links i.far,#navbar-icon-links i.fas{vertical-align:middle;font-style:normal;font-size:1.5rem;line-height:1.25}#navbar-icon-links i.fa-github-square:before{color:#333}#navbar-icon-links i.fa-twitter-square:before{color:#55acee}#navbar-icon-links i.fa-gitlab:before{color:#548}#navbar-icon-links i.fa-bitbucket:before{color:#0052cc}.tocsection{border-left:1px solid #eee;padding:.3rem 1.5rem}.tocsection i{padding-right:.5rem}.editthispage{padding-top:2rem}.editthispage a{color:var(--pst-color-sidebar-link-active)}.xr-wrap[hidden]{display:block!important}.toctree-checkbox{position:absolute;display:none}.toctree-checkbox~ul{display:none}.toctree-checkbox~label i{transform:rotate(0deg)}.toctree-checkbox:checked~ul{display:block}.toctree-checkbox:checked~label i{transform:rotate(180deg)}.bd-sidebar li{position:relative}.bd-sidebar label{position:absolute;top:0;right:0;height:30px;width:30px;cursor:pointer;display:flex;justify-content:center;align-items:center}.bd-sidebar label:hover{background:rgba(var(--pst-color-sidebar-expander-background-hover),1)}.bd-sidebar label i{display:inline-block;font-size:.75rem;text-align:center}.bd-sidebar label i:hover{color:rgba(var(--pst-color-sidebar-link-hover),1)}.bd-sidebar li.has-children>.reference{padding-right:30px}div.doctest>div.highlight span.gp,span.linenos,table.highlighttable td.linenos{user-select:none;-webkit-user-select:text;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.docutils.container{padding-left:unset;padding-right:unset}
\ No newline at end of file
diff --git a/_build/html/_static/css/theme.css b/_build/html/_static/css/theme.css
new file mode 100644
index 0000000..2e03fe3
--- /dev/null
+++ b/_build/html/_static/css/theme.css
@@ -0,0 +1,120 @@
+/* Provided by the Sphinx base theme template at build time */
+@import "../basic.css";
+
+:root {
+ /*****************************************************************************
+ * Theme config
+ **/
+ --pst-header-height: 60px;
+
+ /*****************************************************************************
+ * Font size
+ **/
+ --pst-font-size-base: 15px; /* base font size - applied at body / html level */
+
+ /* heading font sizes */
+ --pst-font-size-h1: 36px;
+ --pst-font-size-h2: 32px;
+ --pst-font-size-h3: 26px;
+ --pst-font-size-h4: 21px;
+ --pst-font-size-h5: 18px;
+ --pst-font-size-h6: 16px;
+
+ /* smaller then heading font sizes*/
+ --pst-font-size-milli: 12px;
+
+ --pst-sidebar-font-size: .9em;
+ --pst-sidebar-caption-font-size: .9em;
+
+ /*****************************************************************************
+ * Font family
+ **/
+ /* These are adapted from https://systemfontstack.com/ */
+ --pst-font-family-base-system: -apple-system, BlinkMacSystemFont, Segoe UI, "Helvetica Neue",
+ Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
+ --pst-font-family-monospace-system: "SFMono-Regular", Menlo, Consolas, Monaco,
+ Liberation Mono, Lucida Console, monospace;
+
+ --pst-font-family-base: var(--pst-font-family-base-system);
+ --pst-font-family-heading: var(--pst-font-family-base);
+ --pst-font-family-monospace: var(--pst-font-family-monospace-system);
+
+ /*****************************************************************************
+ * Color
+ *
+ * Colors are defined in rgb string way, "red, green, blue"
+ **/
+ --pst-color-primary: 19, 6, 84;
+ --pst-color-success: 40, 167, 69;
+ --pst-color-info: 0, 123, 255; /*23, 162, 184;*/
+ --pst-color-warning: 255, 193, 7;
+ --pst-color-danger: 220, 53, 69;
+ --pst-color-text-base: 51, 51, 51;
+
+ --pst-color-h1: var(--pst-color-primary);
+ --pst-color-h2: var(--pst-color-primary);
+ --pst-color-h3: var(--pst-color-text-base);
+ --pst-color-h4: var(--pst-color-text-base);
+ --pst-color-h5: var(--pst-color-text-base);
+ --pst-color-h6: var(--pst-color-text-base);
+ --pst-color-paragraph: var(--pst-color-text-base);
+ --pst-color-link: 0, 91, 129;
+ --pst-color-link-hover: 227, 46, 0;
+ --pst-color-headerlink: 198, 15, 15;
+ --pst-color-headerlink-hover: 255, 255, 255;
+ --pst-color-preformatted-text: 34, 34, 34;
+ --pst-color-preformatted-background: 250, 250, 250;
+ --pst-color-inline-code: 232, 62, 140;
+
+ --pst-color-active-navigation: 19, 6, 84;
+ --pst-color-navbar-link: 77, 77, 77;
+ --pst-color-navbar-link-hover: var(--pst-color-active-navigation);
+ --pst-color-navbar-link-active: var(--pst-color-active-navigation);
+ --pst-color-sidebar-link: 77, 77, 77;
+ --pst-color-sidebar-link-hover: var(--pst-color-active-navigation);
+ --pst-color-sidebar-link-active: var(--pst-color-active-navigation);
+ --pst-color-sidebar-expander-background-hover: 244, 244, 244;
+ --pst-color-sidebar-caption: 77, 77, 77;
+ --pst-color-toc-link: 119, 117, 122;
+ --pst-color-toc-link-hover: var(--pst-color-active-navigation);
+ --pst-color-toc-link-active: var(--pst-color-active-navigation);
+
+ /*****************************************************************************
+ * Icon
+ **/
+
+ /* font awesome icons*/
+ --pst-icon-check-circle: '\f058';
+ --pst-icon-info-circle: '\f05a';
+ --pst-icon-exclamation-triangle: '\f071';
+ --pst-icon-exclamation-circle: '\f06a';
+ --pst-icon-times-circle: '\f057';
+ --pst-icon-lightbulb: '\f0eb';
+
+ /*****************************************************************************
+ * Admonitions
+ **/
+
+ --pst-color-admonition-default: var(--pst-color-info);
+ --pst-color-admonition-note: var(--pst-color-info);
+ --pst-color-admonition-attention: var(--pst-color-warning);
+ --pst-color-admonition-caution: var(--pst-color-warning);
+ --pst-color-admonition-warning: var(--pst-color-warning);
+ --pst-color-admonition-danger: var(--pst-color-danger);
+ --pst-color-admonition-error: var(--pst-color-danger);
+ --pst-color-admonition-hint: var(--pst-color-success);
+ --pst-color-admonition-tip: var(--pst-color-success);
+ --pst-color-admonition-important: var(--pst-color-success);
+
+ --pst-icon-admonition-default: var(--pst-icon-info-circle);
+ --pst-icon-admonition-note: var(--pst-icon-info-circle);
+ --pst-icon-admonition-attention: var(--pst-icon-exclamation-circle);
+ --pst-icon-admonition-caution: var(--pst-icon-exclamation-triangle);
+ --pst-icon-admonition-warning: var(--pst-icon-exclamation-triangle);
+ --pst-icon-admonition-danger: var(--pst-icon-exclamation-triangle);
+ --pst-icon-admonition-error: var(--pst-icon-times-circle);
+ --pst-icon-admonition-hint: var(--pst-icon-lightbulb);
+ --pst-icon-admonition-tip: var(--pst-icon-lightbulb);
+ --pst-icon-admonition-important: var(--pst-icon-exclamation-circle);
+
+}
diff --git a/_build/html/_static/demo.png b/_build/html/_static/demo.png
new file mode 100644
index 0000000..5341055
Binary files /dev/null and b/_build/html/_static/demo.png differ
diff --git a/_build/html/_static/doctools.js b/_build/html/_static/doctools.js
new file mode 100644
index 0000000..8cbf1b1
--- /dev/null
+++ b/_build/html/_static/doctools.js
@@ -0,0 +1,323 @@
+/*
+ * doctools.js
+ * ~~~~~~~~~~~
+ *
+ * Sphinx JavaScript utilities for all documentation.
+ *
+ * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+
+/**
+ * select a different prefix for underscore
+ */
+$u = _.noConflict();
+
+/**
+ * make the code below compatible with browsers without
+ * an installed firebug like debugger
+if (!window.console || !console.firebug) {
+ var names = ["log", "debug", "info", "warn", "error", "assert", "dir",
+ "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace",
+ "profile", "profileEnd"];
+ window.console = {};
+ for (var i = 0; i < names.length; ++i)
+ window.console[names[i]] = function() {};
+}
+ */
+
+/**
+ * small helper function to urldecode strings
+ *
+ * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL
+ */
+jQuery.urldecode = function(x) {
+ if (!x) {
+ return x
+ }
+ return decodeURIComponent(x.replace(/\+/g, ' '));
+};
+
+/**
+ * small helper function to urlencode strings
+ */
+jQuery.urlencode = encodeURIComponent;
+
+/**
+ * This function returns the parsed url parameters of the
+ * current request. Multiple values per key are supported,
+ * it will always return arrays of strings for the value parts.
+ */
+jQuery.getQueryParameters = function(s) {
+ if (typeof s === 'undefined')
+ s = document.location.search;
+ var parts = s.substr(s.indexOf('?') + 1).split('&');
+ var result = {};
+ for (var i = 0; i < parts.length; i++) {
+ var tmp = parts[i].split('=', 2);
+ var key = jQuery.urldecode(tmp[0]);
+ var value = jQuery.urldecode(tmp[1]);
+ if (key in result)
+ result[key].push(value);
+ else
+ result[key] = [value];
+ }
+ return result;
+};
+
+/**
+ * highlight a given string on a jquery object by wrapping it in
+ * span elements with the given class name.
+ */
+jQuery.fn.highlightText = function(text, className) {
+ function highlight(node, addItems) {
+ if (node.nodeType === 3) {
+ var val = node.nodeValue;
+ var pos = val.toLowerCase().indexOf(text);
+ if (pos >= 0 &&
+ !jQuery(node.parentNode).hasClass(className) &&
+ !jQuery(node.parentNode).hasClass("nohighlight")) {
+ var span;
+ var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg");
+ if (isInSVG) {
+ span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
+ } else {
+ span = document.createElement("span");
+ span.className = className;
+ }
+ span.appendChild(document.createTextNode(val.substr(pos, text.length)));
+ node.parentNode.insertBefore(span, node.parentNode.insertBefore(
+ document.createTextNode(val.substr(pos + text.length)),
+ node.nextSibling));
+ node.nodeValue = val.substr(0, pos);
+ if (isInSVG) {
+ var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
+ var bbox = node.parentElement.getBBox();
+ rect.x.baseVal.value = bbox.x;
+ rect.y.baseVal.value = bbox.y;
+ rect.width.baseVal.value = bbox.width;
+ rect.height.baseVal.value = bbox.height;
+ rect.setAttribute('class', className);
+ addItems.push({
+ "parent": node.parentNode,
+ "target": rect});
+ }
+ }
+ }
+ else if (!jQuery(node).is("button, select, textarea")) {
+ jQuery.each(node.childNodes, function() {
+ highlight(this, addItems);
+ });
+ }
+ }
+ var addItems = [];
+ var result = this.each(function() {
+ highlight(this, addItems);
+ });
+ for (var i = 0; i < addItems.length; ++i) {
+ jQuery(addItems[i].parent).before(addItems[i].target);
+ }
+ return result;
+};
+
+/*
+ * backward compatibility for jQuery.browser
+ * This will be supported until firefox bug is fixed.
+ */
+if (!jQuery.browser) {
+ jQuery.uaMatch = function(ua) {
+ ua = ua.toLowerCase();
+
+ var match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
+ /(webkit)[ \/]([\w.]+)/.exec(ua) ||
+ /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
+ /(msie) ([\w.]+)/.exec(ua) ||
+ ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
+ [];
+
+ return {
+ browser: match[ 1 ] || "",
+ version: match[ 2 ] || "0"
+ };
+ };
+ jQuery.browser = {};
+ jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true;
+}
+
+/**
+ * Small JavaScript module for the documentation.
+ */
+var Documentation = {
+
+ init : function() {
+ this.fixFirefoxAnchorBug();
+ this.highlightSearchWords();
+ this.initIndexTable();
+ if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) {
+ this.initOnKeyListeners();
+ }
+ },
+
+ /**
+ * i18n support
+ */
+ TRANSLATIONS : {},
+ PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; },
+ LOCALE : 'unknown',
+
+ // gettext and ngettext don't access this so that the functions
+ // can safely bound to a different name (_ = Documentation.gettext)
+ gettext : function(string) {
+ var translated = Documentation.TRANSLATIONS[string];
+ if (typeof translated === 'undefined')
+ return string;
+ return (typeof translated === 'string') ? translated : translated[0];
+ },
+
+ ngettext : function(singular, plural, n) {
+ var translated = Documentation.TRANSLATIONS[singular];
+ if (typeof translated === 'undefined')
+ return (n == 1) ? singular : plural;
+ return translated[Documentation.PLURALEXPR(n)];
+ },
+
+ addTranslations : function(catalog) {
+ for (var key in catalog.messages)
+ this.TRANSLATIONS[key] = catalog.messages[key];
+ this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')');
+ this.LOCALE = catalog.locale;
+ },
+
+ /**
+ * add context elements like header anchor links
+ */
+ addContextElements : function() {
+ $('div[id] > :header:first').each(function() {
+ $('').
+ attr('href', '#' + this.id).
+ attr('title', _('Permalink to this headline')).
+ appendTo(this);
+ });
+ $('dt[id]').each(function() {
+ $('').
+ attr('href', '#' + this.id).
+ attr('title', _('Permalink to this definition')).
+ appendTo(this);
+ });
+ },
+
+ /**
+ * workaround a firefox stupidity
+ * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075
+ */
+ fixFirefoxAnchorBug : function() {
+ if (document.location.hash && $.browser.mozilla)
+ window.setTimeout(function() {
+ document.location.href += '';
+ }, 10);
+ },
+
+ /**
+ * highlight the search words provided in the url in the text
+ */
+ highlightSearchWords : function() {
+ var params = $.getQueryParameters();
+ var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : [];
+ if (terms.length) {
+ var body = $('div.body');
+ if (!body.length) {
+ body = $('body');
+ }
+ window.setTimeout(function() {
+ $.each(terms, function() {
+ body.highlightText(this.toLowerCase(), 'highlighted');
+ });
+ }, 10);
+ $('' + _('Hide Search Matches') + '
')
+ .appendTo($('#searchbox'));
+ }
+ },
+
+ /**
+ * init the domain index toggle buttons
+ */
+ initIndexTable : function() {
+ var togglers = $('img.toggler').click(function() {
+ var src = $(this).attr('src');
+ var idnum = $(this).attr('id').substr(7);
+ $('tr.cg-' + idnum).toggle();
+ if (src.substr(-9) === 'minus.png')
+ $(this).attr('src', src.substr(0, src.length-9) + 'plus.png');
+ else
+ $(this).attr('src', src.substr(0, src.length-8) + 'minus.png');
+ }).css('display', '');
+ if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) {
+ togglers.click();
+ }
+ },
+
+ /**
+ * helper function to hide the search marks again
+ */
+ hideSearchWords : function() {
+ $('#searchbox .highlight-link').fadeOut(300);
+ $('span.highlighted').removeClass('highlighted');
+ },
+
+ /**
+ * make the url absolute
+ */
+ makeURL : function(relativeURL) {
+ return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL;
+ },
+
+ /**
+ * get the current relative url
+ */
+ getCurrentURL : function() {
+ var path = document.location.pathname;
+ var parts = path.split(/\//);
+ $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() {
+ if (this === '..')
+ parts.pop();
+ });
+ var url = parts.join('/');
+ return path.substring(url.lastIndexOf('/') + 1, path.length - 1);
+ },
+
+ initOnKeyListeners: function() {
+ $(document).keydown(function(event) {
+ var activeElementType = document.activeElement.tagName;
+ // don't navigate when in search box, textarea, dropdown or button
+ if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT'
+ && activeElementType !== 'BUTTON' && !event.altKey && !event.ctrlKey && !event.metaKey
+ && !event.shiftKey) {
+ switch (event.keyCode) {
+ case 37: // left
+ var prevHref = $('link[rel="prev"]').prop('href');
+ if (prevHref) {
+ window.location.href = prevHref;
+ return false;
+ }
+ break;
+ case 39: // right
+ var nextHref = $('link[rel="next"]').prop('href');
+ if (nextHref) {
+ window.location.href = nextHref;
+ return false;
+ }
+ break;
+ }
+ }
+ });
+ }
+};
+
+// quick alias for translations
+_ = Documentation.gettext;
+
+$(document).ready(function() {
+ Documentation.init();
+});
diff --git a/_build/html/_static/documentation_options.js b/_build/html/_static/documentation_options.js
new file mode 100644
index 0000000..93b7c24
--- /dev/null
+++ b/_build/html/_static/documentation_options.js
@@ -0,0 +1,12 @@
+var DOCUMENTATION_OPTIONS = {
+ URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
+ VERSION: '',
+ LANGUAGE: 'None',
+ COLLAPSE_INDEX: false,
+ BUILDER: 'html',
+ FILE_SUFFIX: '.html',
+ LINK_SUFFIX: '.html',
+ HAS_SOURCE: true,
+ SOURCELINK_SUFFIX: '',
+ NAVIGATION_WITH_KEYS: true
+};
\ No newline at end of file
diff --git a/_build/html/_static/file.png b/_build/html/_static/file.png
new file mode 100644
index 0000000..a858a41
Binary files /dev/null and b/_build/html/_static/file.png differ
diff --git a/_build/html/_static/images/logo_binder.svg b/_build/html/_static/images/logo_binder.svg
new file mode 100644
index 0000000..45fecf7
--- /dev/null
+++ b/_build/html/_static/images/logo_binder.svg
@@ -0,0 +1,19 @@
+
+
+
+
+logo
+
+
+
+
+
+
+
+
diff --git a/_build/html/_static/images/logo_colab.png b/_build/html/_static/images/logo_colab.png
new file mode 100644
index 0000000..b7560ec
Binary files /dev/null and b/_build/html/_static/images/logo_colab.png differ
diff --git a/_build/html/_static/images/logo_jupyterhub.svg b/_build/html/_static/images/logo_jupyterhub.svg
new file mode 100644
index 0000000..60cfe9f
--- /dev/null
+++ b/_build/html/_static/images/logo_jupyterhub.svg
@@ -0,0 +1 @@
+logo_jupyterhub Hub
diff --git a/_build/html/_static/jquery-3.5.1.js b/_build/html/_static/jquery-3.5.1.js
new file mode 100644
index 0000000..5093733
--- /dev/null
+++ b/_build/html/_static/jquery-3.5.1.js
@@ -0,0 +1,10872 @@
+/*!
+ * jQuery JavaScript Library v3.5.1
+ * https://jquery.com/
+ *
+ * Includes Sizzle.js
+ * https://sizzlejs.com/
+ *
+ * Copyright JS Foundation and other contributors
+ * Released under the MIT license
+ * https://jquery.org/license
+ *
+ * Date: 2020-05-04T22:49Z
+ */
+( function( global, factory ) {
+
+ "use strict";
+
+ if ( typeof module === "object" && typeof module.exports === "object" ) {
+
+ // For CommonJS and CommonJS-like environments where a proper `window`
+ // is present, execute the factory and get jQuery.
+ // For environments that do not have a `window` with a `document`
+ // (such as Node.js), expose a factory as module.exports.
+ // This accentuates the need for the creation of a real `window`.
+ // e.g. var jQuery = require("jquery")(window);
+ // See ticket #14549 for more info.
+ module.exports = global.document ?
+ factory( global, true ) :
+ function( w ) {
+ if ( !w.document ) {
+ throw new Error( "jQuery requires a window with a document" );
+ }
+ return factory( w );
+ };
+ } else {
+ factory( global );
+ }
+
+// Pass this if window is not defined yet
+} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
+
+// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1
+// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode
+// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common
+// enough that all such attempts are guarded in a try block.
+"use strict";
+
+var arr = [];
+
+var getProto = Object.getPrototypeOf;
+
+var slice = arr.slice;
+
+var flat = arr.flat ? function( array ) {
+ return arr.flat.call( array );
+} : function( array ) {
+ return arr.concat.apply( [], array );
+};
+
+
+var push = arr.push;
+
+var indexOf = arr.indexOf;
+
+var class2type = {};
+
+var toString = class2type.toString;
+
+var hasOwn = class2type.hasOwnProperty;
+
+var fnToString = hasOwn.toString;
+
+var ObjectFunctionString = fnToString.call( Object );
+
+var support = {};
+
+var isFunction = function isFunction( obj ) {
+
+ // Support: Chrome <=57, Firefox <=52
+ // In some browsers, typeof returns "function" for HTML elements
+ // (i.e., `typeof document.createElement( "object" ) === "function"`).
+ // We don't want to classify *any* DOM node as a function.
+ return typeof obj === "function" && typeof obj.nodeType !== "number";
+ };
+
+
+var isWindow = function isWindow( obj ) {
+ return obj != null && obj === obj.window;
+ };
+
+
+var document = window.document;
+
+
+
+ var preservedScriptAttributes = {
+ type: true,
+ src: true,
+ nonce: true,
+ noModule: true
+ };
+
+ function DOMEval( code, node, doc ) {
+ doc = doc || document;
+
+ var i, val,
+ script = doc.createElement( "script" );
+
+ script.text = code;
+ if ( node ) {
+ for ( i in preservedScriptAttributes ) {
+
+ // Support: Firefox 64+, Edge 18+
+ // Some browsers don't support the "nonce" property on scripts.
+ // On the other hand, just using `getAttribute` is not enough as
+ // the `nonce` attribute is reset to an empty string whenever it
+ // becomes browsing-context connected.
+ // See https://github.com/whatwg/html/issues/2369
+ // See https://html.spec.whatwg.org/#nonce-attributes
+ // The `node.getAttribute` check was added for the sake of
+ // `jQuery.globalEval` so that it can fake a nonce-containing node
+ // via an object.
+ val = node[ i ] || node.getAttribute && node.getAttribute( i );
+ if ( val ) {
+ script.setAttribute( i, val );
+ }
+ }
+ }
+ doc.head.appendChild( script ).parentNode.removeChild( script );
+ }
+
+
+function toType( obj ) {
+ if ( obj == null ) {
+ return obj + "";
+ }
+
+ // Support: Android <=2.3 only (functionish RegExp)
+ return typeof obj === "object" || typeof obj === "function" ?
+ class2type[ toString.call( obj ) ] || "object" :
+ typeof obj;
+}
+/* global Symbol */
+// Defining this global in .eslintrc.json would create a danger of using the global
+// unguarded in another place, it seems safer to define global only for this module
+
+
+
+var
+ version = "3.5.1",
+
+ // Define a local copy of jQuery
+ jQuery = function( selector, context ) {
+
+ // The jQuery object is actually just the init constructor 'enhanced'
+ // Need init if jQuery is called (just allow error to be thrown if not included)
+ return new jQuery.fn.init( selector, context );
+ };
+
+jQuery.fn = jQuery.prototype = {
+
+ // The current version of jQuery being used
+ jquery: version,
+
+ constructor: jQuery,
+
+ // The default length of a jQuery object is 0
+ length: 0,
+
+ toArray: function() {
+ return slice.call( this );
+ },
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+
+ // Return all the elements in a clean array
+ if ( num == null ) {
+ return slice.call( this );
+ }
+
+ // Return just the one element from the set
+ return num < 0 ? this[ num + this.length ] : this[ num ];
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems ) {
+
+ // Build a new jQuery matched element set
+ var ret = jQuery.merge( this.constructor(), elems );
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Execute a callback for every element in the matched set.
+ each: function( callback ) {
+ return jQuery.each( this, callback );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map( this, function( elem, i ) {
+ return callback.call( elem, i, elem );
+ } ) );
+ },
+
+ slice: function() {
+ return this.pushStack( slice.apply( this, arguments ) );
+ },
+
+ first: function() {
+ return this.eq( 0 );
+ },
+
+ last: function() {
+ return this.eq( -1 );
+ },
+
+ even: function() {
+ return this.pushStack( jQuery.grep( this, function( _elem, i ) {
+ return ( i + 1 ) % 2;
+ } ) );
+ },
+
+ odd: function() {
+ return this.pushStack( jQuery.grep( this, function( _elem, i ) {
+ return i % 2;
+ } ) );
+ },
+
+ eq: function( i ) {
+ var len = this.length,
+ j = +i + ( i < 0 ? len : 0 );
+ return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
+ },
+
+ end: function() {
+ return this.prevObject || this.constructor();
+ },
+
+ // For internal use only.
+ // Behaves like an Array's method, not like a jQuery method.
+ push: push,
+ sort: arr.sort,
+ splice: arr.splice
+};
+
+jQuery.extend = jQuery.fn.extend = function() {
+ var options, name, src, copy, copyIsArray, clone,
+ target = arguments[ 0 ] || {},
+ i = 1,
+ length = arguments.length,
+ deep = false;
+
+ // Handle a deep copy situation
+ if ( typeof target === "boolean" ) {
+ deep = target;
+
+ // Skip the boolean and the target
+ target = arguments[ i ] || {};
+ i++;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target !== "object" && !isFunction( target ) ) {
+ target = {};
+ }
+
+ // Extend jQuery itself if only one argument is passed
+ if ( i === length ) {
+ target = this;
+ i--;
+ }
+
+ for ( ; i < length; i++ ) {
+
+ // Only deal with non-null/undefined values
+ if ( ( options = arguments[ i ] ) != null ) {
+
+ // Extend the base object
+ for ( name in options ) {
+ copy = options[ name ];
+
+ // Prevent Object.prototype pollution
+ // Prevent never-ending loop
+ if ( name === "__proto__" || target === copy ) {
+ continue;
+ }
+
+ // Recurse if we're merging plain objects or arrays
+ if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
+ ( copyIsArray = Array.isArray( copy ) ) ) ) {
+ src = target[ name ];
+
+ // Ensure proper type for the source value
+ if ( copyIsArray && !Array.isArray( src ) ) {
+ clone = [];
+ } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) {
+ clone = {};
+ } else {
+ clone = src;
+ }
+ copyIsArray = false;
+
+ // Never move original objects, clone them
+ target[ name ] = jQuery.extend( deep, clone, copy );
+
+ // Don't bring in undefined values
+ } else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+};
+
+jQuery.extend( {
+
+ // Unique for each copy of jQuery on the page
+ expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
+
+ // Assume jQuery is ready without the ready module
+ isReady: true,
+
+ error: function( msg ) {
+ throw new Error( msg );
+ },
+
+ noop: function() {},
+
+ isPlainObject: function( obj ) {
+ var proto, Ctor;
+
+ // Detect obvious negatives
+ // Use toString instead of jQuery.type to catch host objects
+ if ( !obj || toString.call( obj ) !== "[object Object]" ) {
+ return false;
+ }
+
+ proto = getProto( obj );
+
+ // Objects with no prototype (e.g., `Object.create( null )`) are plain
+ if ( !proto ) {
+ return true;
+ }
+
+ // Objects with prototype are plain iff they were constructed by a global Object function
+ Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;
+ return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
+ },
+
+ isEmptyObject: function( obj ) {
+ var name;
+
+ for ( name in obj ) {
+ return false;
+ }
+ return true;
+ },
+
+ // Evaluates a script in a provided context; falls back to the global one
+ // if not specified.
+ globalEval: function( code, options, doc ) {
+ DOMEval( code, { nonce: options && options.nonce }, doc );
+ },
+
+ each: function( obj, callback ) {
+ var length, i = 0;
+
+ if ( isArrayLike( obj ) ) {
+ length = obj.length;
+ for ( ; i < length; i++ ) {
+ if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( i in obj ) {
+ if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
+ break;
+ }
+ }
+ }
+
+ return obj;
+ },
+
+ // results is for internal usage only
+ makeArray: function( arr, results ) {
+ var ret = results || [];
+
+ if ( arr != null ) {
+ if ( isArrayLike( Object( arr ) ) ) {
+ jQuery.merge( ret,
+ typeof arr === "string" ?
+ [ arr ] : arr
+ );
+ } else {
+ push.call( ret, arr );
+ }
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, arr, i ) {
+ return arr == null ? -1 : indexOf.call( arr, elem, i );
+ },
+
+ // Support: Android <=4.0 only, PhantomJS 1 only
+ // push.apply(_, arraylike) throws on ancient WebKit
+ merge: function( first, second ) {
+ var len = +second.length,
+ j = 0,
+ i = first.length;
+
+ for ( ; j < len; j++ ) {
+ first[ i++ ] = second[ j ];
+ }
+
+ first.length = i;
+
+ return first;
+ },
+
+ grep: function( elems, callback, invert ) {
+ var callbackInverse,
+ matches = [],
+ i = 0,
+ length = elems.length,
+ callbackExpect = !invert;
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( ; i < length; i++ ) {
+ callbackInverse = !callback( elems[ i ], i );
+ if ( callbackInverse !== callbackExpect ) {
+ matches.push( elems[ i ] );
+ }
+ }
+
+ return matches;
+ },
+
+ // arg is for internal usage only
+ map: function( elems, callback, arg ) {
+ var length, value,
+ i = 0,
+ ret = [];
+
+ // Go through the array, translating each of the items to their new values
+ if ( isArrayLike( elems ) ) {
+ length = elems.length;
+ for ( ; i < length; i++ ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret.push( value );
+ }
+ }
+
+ // Go through every key on the object,
+ } else {
+ for ( i in elems ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret.push( value );
+ }
+ }
+ }
+
+ // Flatten any nested arrays
+ return flat( ret );
+ },
+
+ // A global GUID counter for objects
+ guid: 1,
+
+ // jQuery.support is not used in Core but other projects attach their
+ // properties to it so it needs to exist.
+ support: support
+} );
+
+if ( typeof Symbol === "function" ) {
+ jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ];
+}
+
+// Populate the class2type map
+jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
+function( _i, name ) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase();
+} );
+
+function isArrayLike( obj ) {
+
+ // Support: real iOS 8.2 only (not reproducible in simulator)
+ // `in` check used to prevent JIT error (gh-2145)
+ // hasOwn isn't used here due to false negatives
+ // regarding Nodelist length in IE
+ var length = !!obj && "length" in obj && obj.length,
+ type = toType( obj );
+
+ if ( isFunction( obj ) || isWindow( obj ) ) {
+ return false;
+ }
+
+ return type === "array" || length === 0 ||
+ typeof length === "number" && length > 0 && ( length - 1 ) in obj;
+}
+var Sizzle =
+/*!
+ * Sizzle CSS Selector Engine v2.3.5
+ * https://sizzlejs.com/
+ *
+ * Copyright JS Foundation and other contributors
+ * Released under the MIT license
+ * https://js.foundation/
+ *
+ * Date: 2020-03-14
+ */
+( function( window ) {
+var i,
+ support,
+ Expr,
+ getText,
+ isXML,
+ tokenize,
+ compile,
+ select,
+ outermostContext,
+ sortInput,
+ hasDuplicate,
+
+ // Local document vars
+ setDocument,
+ document,
+ docElem,
+ documentIsHTML,
+ rbuggyQSA,
+ rbuggyMatches,
+ matches,
+ contains,
+
+ // Instance-specific data
+ expando = "sizzle" + 1 * new Date(),
+ preferredDoc = window.document,
+ dirruns = 0,
+ done = 0,
+ classCache = createCache(),
+ tokenCache = createCache(),
+ compilerCache = createCache(),
+ nonnativeSelectorCache = createCache(),
+ sortOrder = function( a, b ) {
+ if ( a === b ) {
+ hasDuplicate = true;
+ }
+ return 0;
+ },
+
+ // Instance methods
+ hasOwn = ( {} ).hasOwnProperty,
+ arr = [],
+ pop = arr.pop,
+ pushNative = arr.push,
+ push = arr.push,
+ slice = arr.slice,
+
+ // Use a stripped-down indexOf as it's faster than native
+ // https://jsperf.com/thor-indexof-vs-for/5
+ indexOf = function( list, elem ) {
+ var i = 0,
+ len = list.length;
+ for ( ; i < len; i++ ) {
+ if ( list[ i ] === elem ) {
+ return i;
+ }
+ }
+ return -1;
+ },
+
+ booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" +
+ "ismap|loop|multiple|open|readonly|required|scoped",
+
+ // Regular expressions
+
+ // http://www.w3.org/TR/css3-selectors/#whitespace
+ whitespace = "[\\x20\\t\\r\\n\\f]",
+
+ // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram
+ identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace +
+ "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+",
+
+ // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
+ attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
+
+ // Operator (capture 2)
+ "*([*^$|!~]?=)" + whitespace +
+
+ // "Attribute values must be CSS identifiers [capture 5]
+ // or strings [capture 3 or capture 4]"
+ "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" +
+ whitespace + "*\\]",
+
+ pseudos = ":(" + identifier + ")(?:\\((" +
+
+ // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
+ // 1. quoted (capture 3; capture 4 or capture 5)
+ "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
+
+ // 2. simple (capture 6)
+ "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
+
+ // 3. anything else (capture 2)
+ ".*" +
+ ")\\)|)",
+
+ // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
+ rwhitespace = new RegExp( whitespace + "+", "g" ),
+ rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" +
+ whitespace + "+$", "g" ),
+
+ rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
+ rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace +
+ "*" ),
+ rdescend = new RegExp( whitespace + "|>" ),
+
+ rpseudo = new RegExp( pseudos ),
+ ridentifier = new RegExp( "^" + identifier + "$" ),
+
+ matchExpr = {
+ "ID": new RegExp( "^#(" + identifier + ")" ),
+ "CLASS": new RegExp( "^\\.(" + identifier + ")" ),
+ "TAG": new RegExp( "^(" + identifier + "|[*])" ),
+ "ATTR": new RegExp( "^" + attributes ),
+ "PSEUDO": new RegExp( "^" + pseudos ),
+ "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" +
+ whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" +
+ whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
+ "bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
+
+ // For use in libraries implementing .is()
+ // We use this for POS matching in `select`
+ "needsContext": new RegExp( "^" + whitespace +
+ "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace +
+ "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
+ },
+
+ rhtml = /HTML$/i,
+ rinputs = /^(?:input|select|textarea|button)$/i,
+ rheader = /^h\d$/i,
+
+ rnative = /^[^{]+\{\s*\[native \w/,
+
+ // Easily-parseable/retrievable ID or TAG or CLASS selectors
+ rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
+
+ rsibling = /[+~]/,
+
+ // CSS escapes
+ // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
+ runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ),
+ funescape = function( escape, nonHex ) {
+ var high = "0x" + escape.slice( 1 ) - 0x10000;
+
+ return nonHex ?
+
+ // Strip the backslash prefix from a non-hex escape sequence
+ nonHex :
+
+ // Replace a hexadecimal escape sequence with the encoded Unicode code point
+ // Support: IE <=11+
+ // For values outside the Basic Multilingual Plane (BMP), manually construct a
+ // surrogate pair
+ high < 0 ?
+ String.fromCharCode( high + 0x10000 ) :
+ String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
+ },
+
+ // CSS string/identifier serialization
+ // https://drafts.csswg.org/cssom/#common-serializing-idioms
+ rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,
+ fcssescape = function( ch, asCodePoint ) {
+ if ( asCodePoint ) {
+
+ // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER
+ if ( ch === "\0" ) {
+ return "\uFFFD";
+ }
+
+ // Control characters and (dependent upon position) numbers get escaped as code points
+ return ch.slice( 0, -1 ) + "\\" +
+ ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " ";
+ }
+
+ // Other potentially-special ASCII characters get backslash-escaped
+ return "\\" + ch;
+ },
+
+ // Used for iframes
+ // See setDocument()
+ // Removing the function wrapper causes a "Permission Denied"
+ // error in IE
+ unloadHandler = function() {
+ setDocument();
+ },
+
+ inDisabledFieldset = addCombinator(
+ function( elem ) {
+ return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset";
+ },
+ { dir: "parentNode", next: "legend" }
+ );
+
+// Optimize for push.apply( _, NodeList )
+try {
+ push.apply(
+ ( arr = slice.call( preferredDoc.childNodes ) ),
+ preferredDoc.childNodes
+ );
+
+ // Support: Android<4.0
+ // Detect silently failing push.apply
+ // eslint-disable-next-line no-unused-expressions
+ arr[ preferredDoc.childNodes.length ].nodeType;
+} catch ( e ) {
+ push = { apply: arr.length ?
+
+ // Leverage slice if possible
+ function( target, els ) {
+ pushNative.apply( target, slice.call( els ) );
+ } :
+
+ // Support: IE<9
+ // Otherwise append directly
+ function( target, els ) {
+ var j = target.length,
+ i = 0;
+
+ // Can't trust NodeList.length
+ while ( ( target[ j++ ] = els[ i++ ] ) ) {}
+ target.length = j - 1;
+ }
+ };
+}
+
+function Sizzle( selector, context, results, seed ) {
+ var m, i, elem, nid, match, groups, newSelector,
+ newContext = context && context.ownerDocument,
+
+ // nodeType defaults to 9, since context defaults to document
+ nodeType = context ? context.nodeType : 9;
+
+ results = results || [];
+
+ // Return early from calls with invalid selector or context
+ if ( typeof selector !== "string" || !selector ||
+ nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
+
+ return results;
+ }
+
+ // Try to shortcut find operations (as opposed to filters) in HTML documents
+ if ( !seed ) {
+ setDocument( context );
+ context = context || document;
+
+ if ( documentIsHTML ) {
+
+ // If the selector is sufficiently simple, try using a "get*By*" DOM method
+ // (excepting DocumentFragment context, where the methods don't exist)
+ if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) {
+
+ // ID selector
+ if ( ( m = match[ 1 ] ) ) {
+
+ // Document context
+ if ( nodeType === 9 ) {
+ if ( ( elem = context.getElementById( m ) ) ) {
+
+ // Support: IE, Opera, Webkit
+ // TODO: identify versions
+ // getElementById can match elements by name instead of ID
+ if ( elem.id === m ) {
+ results.push( elem );
+ return results;
+ }
+ } else {
+ return results;
+ }
+
+ // Element context
+ } else {
+
+ // Support: IE, Opera, Webkit
+ // TODO: identify versions
+ // getElementById can match elements by name instead of ID
+ if ( newContext && ( elem = newContext.getElementById( m ) ) &&
+ contains( context, elem ) &&
+ elem.id === m ) {
+
+ results.push( elem );
+ return results;
+ }
+ }
+
+ // Type selector
+ } else if ( match[ 2 ] ) {
+ push.apply( results, context.getElementsByTagName( selector ) );
+ return results;
+
+ // Class selector
+ } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName &&
+ context.getElementsByClassName ) {
+
+ push.apply( results, context.getElementsByClassName( m ) );
+ return results;
+ }
+ }
+
+ // Take advantage of querySelectorAll
+ if ( support.qsa &&
+ !nonnativeSelectorCache[ selector + " " ] &&
+ ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) &&
+
+ // Support: IE 8 only
+ // Exclude object elements
+ ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) {
+
+ newSelector = selector;
+ newContext = context;
+
+ // qSA considers elements outside a scoping root when evaluating child or
+ // descendant combinators, which is not what we want.
+ // In such cases, we work around the behavior by prefixing every selector in the
+ // list with an ID selector referencing the scope context.
+ // The technique has to be used as well when a leading combinator is used
+ // as such selectors are not recognized by querySelectorAll.
+ // Thanks to Andrew Dupont for this technique.
+ if ( nodeType === 1 &&
+ ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) {
+
+ // Expand context for sibling selectors
+ newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
+ context;
+
+ // We can use :scope instead of the ID hack if the browser
+ // supports it & if we're not changing the context.
+ if ( newContext !== context || !support.scope ) {
+
+ // Capture the context ID, setting it first if necessary
+ if ( ( nid = context.getAttribute( "id" ) ) ) {
+ nid = nid.replace( rcssescape, fcssescape );
+ } else {
+ context.setAttribute( "id", ( nid = expando ) );
+ }
+ }
+
+ // Prefix every selector in the list
+ groups = tokenize( selector );
+ i = groups.length;
+ while ( i-- ) {
+ groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " +
+ toSelector( groups[ i ] );
+ }
+ newSelector = groups.join( "," );
+ }
+
+ try {
+ push.apply( results,
+ newContext.querySelectorAll( newSelector )
+ );
+ return results;
+ } catch ( qsaError ) {
+ nonnativeSelectorCache( selector, true );
+ } finally {
+ if ( nid === expando ) {
+ context.removeAttribute( "id" );
+ }
+ }
+ }
+ }
+ }
+
+ // All others
+ return select( selector.replace( rtrim, "$1" ), context, results, seed );
+}
+
+/**
+ * Create key-value caches of limited size
+ * @returns {function(string, object)} Returns the Object data after storing it on itself with
+ * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
+ * deleting the oldest entry
+ */
+function createCache() {
+ var keys = [];
+
+ function cache( key, value ) {
+
+ // Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
+ if ( keys.push( key + " " ) > Expr.cacheLength ) {
+
+ // Only keep the most recent entries
+ delete cache[ keys.shift() ];
+ }
+ return ( cache[ key + " " ] = value );
+ }
+ return cache;
+}
+
+/**
+ * Mark a function for special use by Sizzle
+ * @param {Function} fn The function to mark
+ */
+function markFunction( fn ) {
+ fn[ expando ] = true;
+ return fn;
+}
+
+/**
+ * Support testing using an element
+ * @param {Function} fn Passed the created element and returns a boolean result
+ */
+function assert( fn ) {
+ var el = document.createElement( "fieldset" );
+
+ try {
+ return !!fn( el );
+ } catch ( e ) {
+ return false;
+ } finally {
+
+ // Remove from its parent by default
+ if ( el.parentNode ) {
+ el.parentNode.removeChild( el );
+ }
+
+ // release memory in IE
+ el = null;
+ }
+}
+
+/**
+ * Adds the same handler for all of the specified attrs
+ * @param {String} attrs Pipe-separated list of attributes
+ * @param {Function} handler The method that will be applied
+ */
+function addHandle( attrs, handler ) {
+ var arr = attrs.split( "|" ),
+ i = arr.length;
+
+ while ( i-- ) {
+ Expr.attrHandle[ arr[ i ] ] = handler;
+ }
+}
+
+/**
+ * Checks document order of two siblings
+ * @param {Element} a
+ * @param {Element} b
+ * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
+ */
+function siblingCheck( a, b ) {
+ var cur = b && a,
+ diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
+ a.sourceIndex - b.sourceIndex;
+
+ // Use IE sourceIndex if available on both nodes
+ if ( diff ) {
+ return diff;
+ }
+
+ // Check if b follows a
+ if ( cur ) {
+ while ( ( cur = cur.nextSibling ) ) {
+ if ( cur === b ) {
+ return -1;
+ }
+ }
+ }
+
+ return a ? 1 : -1;
+}
+
+/**
+ * Returns a function to use in pseudos for input types
+ * @param {String} type
+ */
+function createInputPseudo( type ) {
+ return function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === type;
+ };
+}
+
+/**
+ * Returns a function to use in pseudos for buttons
+ * @param {String} type
+ */
+function createButtonPseudo( type ) {
+ return function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return ( name === "input" || name === "button" ) && elem.type === type;
+ };
+}
+
+/**
+ * Returns a function to use in pseudos for :enabled/:disabled
+ * @param {Boolean} disabled true for :disabled; false for :enabled
+ */
+function createDisabledPseudo( disabled ) {
+
+ // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable
+ return function( elem ) {
+
+ // Only certain elements can match :enabled or :disabled
+ // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled
+ // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled
+ if ( "form" in elem ) {
+
+ // Check for inherited disabledness on relevant non-disabled elements:
+ // * listed form-associated elements in a disabled fieldset
+ // https://html.spec.whatwg.org/multipage/forms.html#category-listed
+ // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled
+ // * option elements in a disabled optgroup
+ // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled
+ // All such elements have a "form" property.
+ if ( elem.parentNode && elem.disabled === false ) {
+
+ // Option elements defer to a parent optgroup if present
+ if ( "label" in elem ) {
+ if ( "label" in elem.parentNode ) {
+ return elem.parentNode.disabled === disabled;
+ } else {
+ return elem.disabled === disabled;
+ }
+ }
+
+ // Support: IE 6 - 11
+ // Use the isDisabled shortcut property to check for disabled fieldset ancestors
+ return elem.isDisabled === disabled ||
+
+ // Where there is no isDisabled, check manually
+ /* jshint -W018 */
+ elem.isDisabled !== !disabled &&
+ inDisabledFieldset( elem ) === disabled;
+ }
+
+ return elem.disabled === disabled;
+
+ // Try to winnow out elements that can't be disabled before trusting the disabled property.
+ // Some victims get caught in our net (label, legend, menu, track), but it shouldn't
+ // even exist on them, let alone have a boolean value.
+ } else if ( "label" in elem ) {
+ return elem.disabled === disabled;
+ }
+
+ // Remaining elements are neither :enabled nor :disabled
+ return false;
+ };
+}
+
+/**
+ * Returns a function to use in pseudos for positionals
+ * @param {Function} fn
+ */
+function createPositionalPseudo( fn ) {
+ return markFunction( function( argument ) {
+ argument = +argument;
+ return markFunction( function( seed, matches ) {
+ var j,
+ matchIndexes = fn( [], seed.length, argument ),
+ i = matchIndexes.length;
+
+ // Match elements found at the specified indexes
+ while ( i-- ) {
+ if ( seed[ ( j = matchIndexes[ i ] ) ] ) {
+ seed[ j ] = !( matches[ j ] = seed[ j ] );
+ }
+ }
+ } );
+ } );
+}
+
+/**
+ * Checks a node for validity as a Sizzle context
+ * @param {Element|Object=} context
+ * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
+ */
+function testContext( context ) {
+ return context && typeof context.getElementsByTagName !== "undefined" && context;
+}
+
+// Expose support vars for convenience
+support = Sizzle.support = {};
+
+/**
+ * Detects XML nodes
+ * @param {Element|Object} elem An element or a document
+ * @returns {Boolean} True iff elem is a non-HTML XML node
+ */
+isXML = Sizzle.isXML = function( elem ) {
+ var namespace = elem.namespaceURI,
+ docElem = ( elem.ownerDocument || elem ).documentElement;
+
+ // Support: IE <=8
+ // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes
+ // https://bugs.jquery.com/ticket/4833
+ return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" );
+};
+
+/**
+ * Sets document-related variables once based on the current document
+ * @param {Element|Object} [doc] An element or document object to use to set the document
+ * @returns {Object} Returns the current document
+ */
+setDocument = Sizzle.setDocument = function( node ) {
+ var hasCompare, subWindow,
+ doc = node ? node.ownerDocument || node : preferredDoc;
+
+ // Return early if doc is invalid or already selected
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) {
+ return document;
+ }
+
+ // Update global variables
+ document = doc;
+ docElem = document.documentElement;
+ documentIsHTML = !isXML( document );
+
+ // Support: IE 9 - 11+, Edge 12 - 18+
+ // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936)
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ if ( preferredDoc != document &&
+ ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) {
+
+ // Support: IE 11, Edge
+ if ( subWindow.addEventListener ) {
+ subWindow.addEventListener( "unload", unloadHandler, false );
+
+ // Support: IE 9 - 10 only
+ } else if ( subWindow.attachEvent ) {
+ subWindow.attachEvent( "onunload", unloadHandler );
+ }
+ }
+
+ // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only,
+ // Safari 4 - 5 only, Opera <=11.6 - 12.x only
+ // IE/Edge & older browsers don't support the :scope pseudo-class.
+ // Support: Safari 6.0 only
+ // Safari 6.0 supports :scope but it's an alias of :root there.
+ support.scope = assert( function( el ) {
+ docElem.appendChild( el ).appendChild( document.createElement( "div" ) );
+ return typeof el.querySelectorAll !== "undefined" &&
+ !el.querySelectorAll( ":scope fieldset div" ).length;
+ } );
+
+ /* Attributes
+ ---------------------------------------------------------------------- */
+
+ // Support: IE<8
+ // Verify that getAttribute really returns attributes and not properties
+ // (excepting IE8 booleans)
+ support.attributes = assert( function( el ) {
+ el.className = "i";
+ return !el.getAttribute( "className" );
+ } );
+
+ /* getElement(s)By*
+ ---------------------------------------------------------------------- */
+
+ // Check if getElementsByTagName("*") returns only elements
+ support.getElementsByTagName = assert( function( el ) {
+ el.appendChild( document.createComment( "" ) );
+ return !el.getElementsByTagName( "*" ).length;
+ } );
+
+ // Support: IE<9
+ support.getElementsByClassName = rnative.test( document.getElementsByClassName );
+
+ // Support: IE<10
+ // Check if getElementById returns elements by name
+ // The broken getElementById methods don't pick up programmatically-set names,
+ // so use a roundabout getElementsByName test
+ support.getById = assert( function( el ) {
+ docElem.appendChild( el ).id = expando;
+ return !document.getElementsByName || !document.getElementsByName( expando ).length;
+ } );
+
+ // ID filter and find
+ if ( support.getById ) {
+ Expr.filter[ "ID" ] = function( id ) {
+ var attrId = id.replace( runescape, funescape );
+ return function( elem ) {
+ return elem.getAttribute( "id" ) === attrId;
+ };
+ };
+ Expr.find[ "ID" ] = function( id, context ) {
+ if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
+ var elem = context.getElementById( id );
+ return elem ? [ elem ] : [];
+ }
+ };
+ } else {
+ Expr.filter[ "ID" ] = function( id ) {
+ var attrId = id.replace( runescape, funescape );
+ return function( elem ) {
+ var node = typeof elem.getAttributeNode !== "undefined" &&
+ elem.getAttributeNode( "id" );
+ return node && node.value === attrId;
+ };
+ };
+
+ // Support: IE 6 - 7 only
+ // getElementById is not reliable as a find shortcut
+ Expr.find[ "ID" ] = function( id, context ) {
+ if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
+ var node, i, elems,
+ elem = context.getElementById( id );
+
+ if ( elem ) {
+
+ // Verify the id attribute
+ node = elem.getAttributeNode( "id" );
+ if ( node && node.value === id ) {
+ return [ elem ];
+ }
+
+ // Fall back on getElementsByName
+ elems = context.getElementsByName( id );
+ i = 0;
+ while ( ( elem = elems[ i++ ] ) ) {
+ node = elem.getAttributeNode( "id" );
+ if ( node && node.value === id ) {
+ return [ elem ];
+ }
+ }
+ }
+
+ return [];
+ }
+ };
+ }
+
+ // Tag
+ Expr.find[ "TAG" ] = support.getElementsByTagName ?
+ function( tag, context ) {
+ if ( typeof context.getElementsByTagName !== "undefined" ) {
+ return context.getElementsByTagName( tag );
+
+ // DocumentFragment nodes don't have gEBTN
+ } else if ( support.qsa ) {
+ return context.querySelectorAll( tag );
+ }
+ } :
+
+ function( tag, context ) {
+ var elem,
+ tmp = [],
+ i = 0,
+
+ // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too
+ results = context.getElementsByTagName( tag );
+
+ // Filter out possible comments
+ if ( tag === "*" ) {
+ while ( ( elem = results[ i++ ] ) ) {
+ if ( elem.nodeType === 1 ) {
+ tmp.push( elem );
+ }
+ }
+
+ return tmp;
+ }
+ return results;
+ };
+
+ // Class
+ Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) {
+ if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) {
+ return context.getElementsByClassName( className );
+ }
+ };
+
+ /* QSA/matchesSelector
+ ---------------------------------------------------------------------- */
+
+ // QSA and matchesSelector support
+
+ // matchesSelector(:active) reports false when true (IE9/Opera 11.5)
+ rbuggyMatches = [];
+
+ // qSa(:focus) reports false when true (Chrome 21)
+ // We allow this because of a bug in IE8/9 that throws an error
+ // whenever `document.activeElement` is accessed on an iframe
+ // So, we allow :focus to pass through QSA all the time to avoid the IE error
+ // See https://bugs.jquery.com/ticket/13378
+ rbuggyQSA = [];
+
+ if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) {
+
+ // Build QSA regex
+ // Regex strategy adopted from Diego Perini
+ assert( function( el ) {
+
+ var input;
+
+ // Select is set to empty string on purpose
+ // This is to test IE's treatment of not explicitly
+ // setting a boolean content attribute,
+ // since its presence should be enough
+ // https://bugs.jquery.com/ticket/12359
+ docElem.appendChild( el ).innerHTML = " " +
+ "" +
+ " ";
+
+ // Support: IE8, Opera 11-12.16
+ // Nothing should be selected when empty strings follow ^= or $= or *=
+ // The test attribute must be unknown in Opera but "safe" for WinRT
+ // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
+ if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) {
+ rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
+ }
+
+ // Support: IE8
+ // Boolean attributes and "value" are not treated correctly
+ if ( !el.querySelectorAll( "[selected]" ).length ) {
+ rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
+ }
+
+ // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+
+ if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
+ rbuggyQSA.push( "~=" );
+ }
+
+ // Support: IE 11+, Edge 15 - 18+
+ // IE 11/Edge don't find elements on a `[name='']` query in some cases.
+ // Adding a temporary attribute to the document before the selection works
+ // around the issue.
+ // Interestingly, IE 10 & older don't seem to have the issue.
+ input = document.createElement( "input" );
+ input.setAttribute( "name", "" );
+ el.appendChild( input );
+ if ( !el.querySelectorAll( "[name='']" ).length ) {
+ rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" +
+ whitespace + "*(?:''|\"\")" );
+ }
+
+ // Webkit/Opera - :checked should return selected option elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ // IE8 throws error here and will not see later tests
+ if ( !el.querySelectorAll( ":checked" ).length ) {
+ rbuggyQSA.push( ":checked" );
+ }
+
+ // Support: Safari 8+, iOS 8+
+ // https://bugs.webkit.org/show_bug.cgi?id=136851
+ // In-page `selector#id sibling-combinator selector` fails
+ if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) {
+ rbuggyQSA.push( ".#.+[+~]" );
+ }
+
+ // Support: Firefox <=3.6 - 5 only
+ // Old Firefox doesn't throw on a badly-escaped identifier.
+ el.querySelectorAll( "\\\f" );
+ rbuggyQSA.push( "[\\r\\n\\f]" );
+ } );
+
+ assert( function( el ) {
+ el.innerHTML = " " +
+ " ";
+
+ // Support: Windows 8 Native Apps
+ // The type and name attributes are restricted during .innerHTML assignment
+ var input = document.createElement( "input" );
+ input.setAttribute( "type", "hidden" );
+ el.appendChild( input ).setAttribute( "name", "D" );
+
+ // Support: IE8
+ // Enforce case-sensitivity of name attribute
+ if ( el.querySelectorAll( "[name=d]" ).length ) {
+ rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
+ }
+
+ // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
+ // IE8 throws error here and will not see later tests
+ if ( el.querySelectorAll( ":enabled" ).length !== 2 ) {
+ rbuggyQSA.push( ":enabled", ":disabled" );
+ }
+
+ // Support: IE9-11+
+ // IE's :disabled selector does not pick up the children of disabled fieldsets
+ docElem.appendChild( el ).disabled = true;
+ if ( el.querySelectorAll( ":disabled" ).length !== 2 ) {
+ rbuggyQSA.push( ":enabled", ":disabled" );
+ }
+
+ // Support: Opera 10 - 11 only
+ // Opera 10-11 does not throw on post-comma invalid pseudos
+ el.querySelectorAll( "*,:x" );
+ rbuggyQSA.push( ",.*:" );
+ } );
+ }
+
+ if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches ||
+ docElem.webkitMatchesSelector ||
+ docElem.mozMatchesSelector ||
+ docElem.oMatchesSelector ||
+ docElem.msMatchesSelector ) ) ) ) {
+
+ assert( function( el ) {
+
+ // Check to see if it's possible to do matchesSelector
+ // on a disconnected node (IE 9)
+ support.disconnectedMatch = matches.call( el, "*" );
+
+ // This should fail with an exception
+ // Gecko does not error, returns false instead
+ matches.call( el, "[s!='']:x" );
+ rbuggyMatches.push( "!=", pseudos );
+ } );
+ }
+
+ rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) );
+ rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) );
+
+ /* Contains
+ ---------------------------------------------------------------------- */
+ hasCompare = rnative.test( docElem.compareDocumentPosition );
+
+ // Element contains another
+ // Purposefully self-exclusive
+ // As in, an element does not contain itself
+ contains = hasCompare || rnative.test( docElem.contains ) ?
+ function( a, b ) {
+ var adown = a.nodeType === 9 ? a.documentElement : a,
+ bup = b && b.parentNode;
+ return a === bup || !!( bup && bup.nodeType === 1 && (
+ adown.contains ?
+ adown.contains( bup ) :
+ a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
+ ) );
+ } :
+ function( a, b ) {
+ if ( b ) {
+ while ( ( b = b.parentNode ) ) {
+ if ( b === a ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+
+ /* Sorting
+ ---------------------------------------------------------------------- */
+
+ // Document order sorting
+ sortOrder = hasCompare ?
+ function( a, b ) {
+
+ // Flag for duplicate removal
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ // Sort on method existence if only one input has compareDocumentPosition
+ var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
+ if ( compare ) {
+ return compare;
+ }
+
+ // Calculate position if both inputs belong to the same document
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ?
+ a.compareDocumentPosition( b ) :
+
+ // Otherwise we know they are disconnected
+ 1;
+
+ // Disconnected nodes
+ if ( compare & 1 ||
+ ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) {
+
+ // Choose the first element that is related to our preferred document
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ if ( a == document || a.ownerDocument == preferredDoc &&
+ contains( preferredDoc, a ) ) {
+ return -1;
+ }
+
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ if ( b == document || b.ownerDocument == preferredDoc &&
+ contains( preferredDoc, b ) ) {
+ return 1;
+ }
+
+ // Maintain original order
+ return sortInput ?
+ ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
+ 0;
+ }
+
+ return compare & 4 ? -1 : 1;
+ } :
+ function( a, b ) {
+
+ // Exit early if the nodes are identical
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ var cur,
+ i = 0,
+ aup = a.parentNode,
+ bup = b.parentNode,
+ ap = [ a ],
+ bp = [ b ];
+
+ // Parentless nodes are either documents or disconnected
+ if ( !aup || !bup ) {
+
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ /* eslint-disable eqeqeq */
+ return a == document ? -1 :
+ b == document ? 1 :
+ /* eslint-enable eqeqeq */
+ aup ? -1 :
+ bup ? 1 :
+ sortInput ?
+ ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
+ 0;
+
+ // If the nodes are siblings, we can do a quick check
+ } else if ( aup === bup ) {
+ return siblingCheck( a, b );
+ }
+
+ // Otherwise we need full lists of their ancestors for comparison
+ cur = a;
+ while ( ( cur = cur.parentNode ) ) {
+ ap.unshift( cur );
+ }
+ cur = b;
+ while ( ( cur = cur.parentNode ) ) {
+ bp.unshift( cur );
+ }
+
+ // Walk down the tree looking for a discrepancy
+ while ( ap[ i ] === bp[ i ] ) {
+ i++;
+ }
+
+ return i ?
+
+ // Do a sibling check if the nodes have a common ancestor
+ siblingCheck( ap[ i ], bp[ i ] ) :
+
+ // Otherwise nodes in our document sort first
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ /* eslint-disable eqeqeq */
+ ap[ i ] == preferredDoc ? -1 :
+ bp[ i ] == preferredDoc ? 1 :
+ /* eslint-enable eqeqeq */
+ 0;
+ };
+
+ return document;
+};
+
+Sizzle.matches = function( expr, elements ) {
+ return Sizzle( expr, null, null, elements );
+};
+
+Sizzle.matchesSelector = function( elem, expr ) {
+ setDocument( elem );
+
+ if ( support.matchesSelector && documentIsHTML &&
+ !nonnativeSelectorCache[ expr + " " ] &&
+ ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
+ ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) {
+
+ try {
+ var ret = matches.call( elem, expr );
+
+ // IE 9's matchesSelector returns false on disconnected nodes
+ if ( ret || support.disconnectedMatch ||
+
+ // As well, disconnected nodes are said to be in a document
+ // fragment in IE 9
+ elem.document && elem.document.nodeType !== 11 ) {
+ return ret;
+ }
+ } catch ( e ) {
+ nonnativeSelectorCache( expr, true );
+ }
+ }
+
+ return Sizzle( expr, document, null, [ elem ] ).length > 0;
+};
+
+Sizzle.contains = function( context, elem ) {
+
+ // Set document vars if needed
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ if ( ( context.ownerDocument || context ) != document ) {
+ setDocument( context );
+ }
+ return contains( context, elem );
+};
+
+Sizzle.attr = function( elem, name ) {
+
+ // Set document vars if needed
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ if ( ( elem.ownerDocument || elem ) != document ) {
+ setDocument( elem );
+ }
+
+ var fn = Expr.attrHandle[ name.toLowerCase() ],
+
+ // Don't get fooled by Object.prototype properties (jQuery #13807)
+ val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
+ fn( elem, name, !documentIsHTML ) :
+ undefined;
+
+ return val !== undefined ?
+ val :
+ support.attributes || !documentIsHTML ?
+ elem.getAttribute( name ) :
+ ( val = elem.getAttributeNode( name ) ) && val.specified ?
+ val.value :
+ null;
+};
+
+Sizzle.escape = function( sel ) {
+ return ( sel + "" ).replace( rcssescape, fcssescape );
+};
+
+Sizzle.error = function( msg ) {
+ throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
+
+/**
+ * Document sorting and removing duplicates
+ * @param {ArrayLike} results
+ */
+Sizzle.uniqueSort = function( results ) {
+ var elem,
+ duplicates = [],
+ j = 0,
+ i = 0;
+
+ // Unless we *know* we can detect duplicates, assume their presence
+ hasDuplicate = !support.detectDuplicates;
+ sortInput = !support.sortStable && results.slice( 0 );
+ results.sort( sortOrder );
+
+ if ( hasDuplicate ) {
+ while ( ( elem = results[ i++ ] ) ) {
+ if ( elem === results[ i ] ) {
+ j = duplicates.push( i );
+ }
+ }
+ while ( j-- ) {
+ results.splice( duplicates[ j ], 1 );
+ }
+ }
+
+ // Clear input after sorting to release objects
+ // See https://github.com/jquery/sizzle/pull/225
+ sortInput = null;
+
+ return results;
+};
+
+/**
+ * Utility function for retrieving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+getText = Sizzle.getText = function( elem ) {
+ var node,
+ ret = "",
+ i = 0,
+ nodeType = elem.nodeType;
+
+ if ( !nodeType ) {
+
+ // If no nodeType, this is expected to be an array
+ while ( ( node = elem[ i++ ] ) ) {
+
+ // Do not traverse comment nodes
+ ret += getText( node );
+ }
+ } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
+
+ // Use textContent for elements
+ // innerText usage removed for consistency of new lines (jQuery #11153)
+ if ( typeof elem.textContent === "string" ) {
+ return elem.textContent;
+ } else {
+
+ // Traverse its children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+ ret += getText( elem );
+ }
+ }
+ } else if ( nodeType === 3 || nodeType === 4 ) {
+ return elem.nodeValue;
+ }
+
+ // Do not include comment or processing instruction nodes
+
+ return ret;
+};
+
+Expr = Sizzle.selectors = {
+
+ // Can be adjusted by the user
+ cacheLength: 50,
+
+ createPseudo: markFunction,
+
+ match: matchExpr,
+
+ attrHandle: {},
+
+ find: {},
+
+ relative: {
+ ">": { dir: "parentNode", first: true },
+ " ": { dir: "parentNode" },
+ "+": { dir: "previousSibling", first: true },
+ "~": { dir: "previousSibling" }
+ },
+
+ preFilter: {
+ "ATTR": function( match ) {
+ match[ 1 ] = match[ 1 ].replace( runescape, funescape );
+
+ // Move the given value to match[3] whether quoted or unquoted
+ match[ 3 ] = ( match[ 3 ] || match[ 4 ] ||
+ match[ 5 ] || "" ).replace( runescape, funescape );
+
+ if ( match[ 2 ] === "~=" ) {
+ match[ 3 ] = " " + match[ 3 ] + " ";
+ }
+
+ return match.slice( 0, 4 );
+ },
+
+ "CHILD": function( match ) {
+
+ /* matches from matchExpr["CHILD"]
+ 1 type (only|nth|...)
+ 2 what (child|of-type)
+ 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
+ 4 xn-component of xn+y argument ([+-]?\d*n|)
+ 5 sign of xn-component
+ 6 x of xn-component
+ 7 sign of y-component
+ 8 y of y-component
+ */
+ match[ 1 ] = match[ 1 ].toLowerCase();
+
+ if ( match[ 1 ].slice( 0, 3 ) === "nth" ) {
+
+ // nth-* requires argument
+ if ( !match[ 3 ] ) {
+ Sizzle.error( match[ 0 ] );
+ }
+
+ // numeric x and y parameters for Expr.filter.CHILD
+ // remember that false/true cast respectively to 0/1
+ match[ 4 ] = +( match[ 4 ] ?
+ match[ 5 ] + ( match[ 6 ] || 1 ) :
+ 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) );
+ match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" );
+
+ // other types prohibit arguments
+ } else if ( match[ 3 ] ) {
+ Sizzle.error( match[ 0 ] );
+ }
+
+ return match;
+ },
+
+ "PSEUDO": function( match ) {
+ var excess,
+ unquoted = !match[ 6 ] && match[ 2 ];
+
+ if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) {
+ return null;
+ }
+
+ // Accept quoted arguments as-is
+ if ( match[ 3 ] ) {
+ match[ 2 ] = match[ 4 ] || match[ 5 ] || "";
+
+ // Strip excess characters from unquoted arguments
+ } else if ( unquoted && rpseudo.test( unquoted ) &&
+
+ // Get excess from tokenize (recursively)
+ ( excess = tokenize( unquoted, true ) ) &&
+
+ // advance to the next closing parenthesis
+ ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) {
+
+ // excess is a negative index
+ match[ 0 ] = match[ 0 ].slice( 0, excess );
+ match[ 2 ] = unquoted.slice( 0, excess );
+ }
+
+ // Return only captures needed by the pseudo filter method (type and argument)
+ return match.slice( 0, 3 );
+ }
+ },
+
+ filter: {
+
+ "TAG": function( nodeNameSelector ) {
+ var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
+ return nodeNameSelector === "*" ?
+ function() {
+ return true;
+ } :
+ function( elem ) {
+ return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
+ };
+ },
+
+ "CLASS": function( className ) {
+ var pattern = classCache[ className + " " ];
+
+ return pattern ||
+ ( pattern = new RegExp( "(^|" + whitespace +
+ ")" + className + "(" + whitespace + "|$)" ) ) && classCache(
+ className, function( elem ) {
+ return pattern.test(
+ typeof elem.className === "string" && elem.className ||
+ typeof elem.getAttribute !== "undefined" &&
+ elem.getAttribute( "class" ) ||
+ ""
+ );
+ } );
+ },
+
+ "ATTR": function( name, operator, check ) {
+ return function( elem ) {
+ var result = Sizzle.attr( elem, name );
+
+ if ( result == null ) {
+ return operator === "!=";
+ }
+ if ( !operator ) {
+ return true;
+ }
+
+ result += "";
+
+ /* eslint-disable max-len */
+
+ return operator === "=" ? result === check :
+ operator === "!=" ? result !== check :
+ operator === "^=" ? check && result.indexOf( check ) === 0 :
+ operator === "*=" ? check && result.indexOf( check ) > -1 :
+ operator === "$=" ? check && result.slice( -check.length ) === check :
+ operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 :
+ operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
+ false;
+ /* eslint-enable max-len */
+
+ };
+ },
+
+ "CHILD": function( type, what, _argument, first, last ) {
+ var simple = type.slice( 0, 3 ) !== "nth",
+ forward = type.slice( -4 ) !== "last",
+ ofType = what === "of-type";
+
+ return first === 1 && last === 0 ?
+
+ // Shortcut for :nth-*(n)
+ function( elem ) {
+ return !!elem.parentNode;
+ } :
+
+ function( elem, _context, xml ) {
+ var cache, uniqueCache, outerCache, node, nodeIndex, start,
+ dir = simple !== forward ? "nextSibling" : "previousSibling",
+ parent = elem.parentNode,
+ name = ofType && elem.nodeName.toLowerCase(),
+ useCache = !xml && !ofType,
+ diff = false;
+
+ if ( parent ) {
+
+ // :(first|last|only)-(child|of-type)
+ if ( simple ) {
+ while ( dir ) {
+ node = elem;
+ while ( ( node = node[ dir ] ) ) {
+ if ( ofType ?
+ node.nodeName.toLowerCase() === name :
+ node.nodeType === 1 ) {
+
+ return false;
+ }
+ }
+
+ // Reverse direction for :only-* (if we haven't yet done so)
+ start = dir = type === "only" && !start && "nextSibling";
+ }
+ return true;
+ }
+
+ start = [ forward ? parent.firstChild : parent.lastChild ];
+
+ // non-xml :nth-child(...) stores cache data on `parent`
+ if ( forward && useCache ) {
+
+ // Seek `elem` from a previously-cached index
+
+ // ...in a gzip-friendly way
+ node = parent;
+ outerCache = node[ expando ] || ( node[ expando ] = {} );
+
+ // Support: IE <9 only
+ // Defend against cloned attroperties (jQuery gh-1709)
+ uniqueCache = outerCache[ node.uniqueID ] ||
+ ( outerCache[ node.uniqueID ] = {} );
+
+ cache = uniqueCache[ type ] || [];
+ nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
+ diff = nodeIndex && cache[ 2 ];
+ node = nodeIndex && parent.childNodes[ nodeIndex ];
+
+ while ( ( node = ++nodeIndex && node && node[ dir ] ||
+
+ // Fallback to seeking `elem` from the start
+ ( diff = nodeIndex = 0 ) || start.pop() ) ) {
+
+ // When found, cache indexes on `parent` and break
+ if ( node.nodeType === 1 && ++diff && node === elem ) {
+ uniqueCache[ type ] = [ dirruns, nodeIndex, diff ];
+ break;
+ }
+ }
+
+ } else {
+
+ // Use previously-cached element index if available
+ if ( useCache ) {
+
+ // ...in a gzip-friendly way
+ node = elem;
+ outerCache = node[ expando ] || ( node[ expando ] = {} );
+
+ // Support: IE <9 only
+ // Defend against cloned attroperties (jQuery gh-1709)
+ uniqueCache = outerCache[ node.uniqueID ] ||
+ ( outerCache[ node.uniqueID ] = {} );
+
+ cache = uniqueCache[ type ] || [];
+ nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
+ diff = nodeIndex;
+ }
+
+ // xml :nth-child(...)
+ // or :nth-last-child(...) or :nth(-last)?-of-type(...)
+ if ( diff === false ) {
+
+ // Use the same loop as above to seek `elem` from the start
+ while ( ( node = ++nodeIndex && node && node[ dir ] ||
+ ( diff = nodeIndex = 0 ) || start.pop() ) ) {
+
+ if ( ( ofType ?
+ node.nodeName.toLowerCase() === name :
+ node.nodeType === 1 ) &&
+ ++diff ) {
+
+ // Cache the index of each encountered element
+ if ( useCache ) {
+ outerCache = node[ expando ] ||
+ ( node[ expando ] = {} );
+
+ // Support: IE <9 only
+ // Defend against cloned attroperties (jQuery gh-1709)
+ uniqueCache = outerCache[ node.uniqueID ] ||
+ ( outerCache[ node.uniqueID ] = {} );
+
+ uniqueCache[ type ] = [ dirruns, diff ];
+ }
+
+ if ( node === elem ) {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Incorporate the offset, then check against cycle size
+ diff -= last;
+ return diff === first || ( diff % first === 0 && diff / first >= 0 );
+ }
+ };
+ },
+
+ "PSEUDO": function( pseudo, argument ) {
+
+ // pseudo-class names are case-insensitive
+ // http://www.w3.org/TR/selectors/#pseudo-classes
+ // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
+ // Remember that setFilters inherits from pseudos
+ var args,
+ fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
+ Sizzle.error( "unsupported pseudo: " + pseudo );
+
+ // The user may use createPseudo to indicate that
+ // arguments are needed to create the filter function
+ // just as Sizzle does
+ if ( fn[ expando ] ) {
+ return fn( argument );
+ }
+
+ // But maintain support for old signatures
+ if ( fn.length > 1 ) {
+ args = [ pseudo, pseudo, "", argument ];
+ return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
+ markFunction( function( seed, matches ) {
+ var idx,
+ matched = fn( seed, argument ),
+ i = matched.length;
+ while ( i-- ) {
+ idx = indexOf( seed, matched[ i ] );
+ seed[ idx ] = !( matches[ idx ] = matched[ i ] );
+ }
+ } ) :
+ function( elem ) {
+ return fn( elem, 0, args );
+ };
+ }
+
+ return fn;
+ }
+ },
+
+ pseudos: {
+
+ // Potentially complex pseudos
+ "not": markFunction( function( selector ) {
+
+ // Trim the selector passed to compile
+ // to avoid treating leading and trailing
+ // spaces as combinators
+ var input = [],
+ results = [],
+ matcher = compile( selector.replace( rtrim, "$1" ) );
+
+ return matcher[ expando ] ?
+ markFunction( function( seed, matches, _context, xml ) {
+ var elem,
+ unmatched = matcher( seed, null, xml, [] ),
+ i = seed.length;
+
+ // Match elements unmatched by `matcher`
+ while ( i-- ) {
+ if ( ( elem = unmatched[ i ] ) ) {
+ seed[ i ] = !( matches[ i ] = elem );
+ }
+ }
+ } ) :
+ function( elem, _context, xml ) {
+ input[ 0 ] = elem;
+ matcher( input, null, xml, results );
+
+ // Don't keep the element (issue #299)
+ input[ 0 ] = null;
+ return !results.pop();
+ };
+ } ),
+
+ "has": markFunction( function( selector ) {
+ return function( elem ) {
+ return Sizzle( selector, elem ).length > 0;
+ };
+ } ),
+
+ "contains": markFunction( function( text ) {
+ text = text.replace( runescape, funescape );
+ return function( elem ) {
+ return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1;
+ };
+ } ),
+
+ // "Whether an element is represented by a :lang() selector
+ // is based solely on the element's language value
+ // being equal to the identifier C,
+ // or beginning with the identifier C immediately followed by "-".
+ // The matching of C against the element's language value is performed case-insensitively.
+ // The identifier C does not have to be a valid language name."
+ // http://www.w3.org/TR/selectors/#lang-pseudo
+ "lang": markFunction( function( lang ) {
+
+ // lang value must be a valid identifier
+ if ( !ridentifier.test( lang || "" ) ) {
+ Sizzle.error( "unsupported lang: " + lang );
+ }
+ lang = lang.replace( runescape, funescape ).toLowerCase();
+ return function( elem ) {
+ var elemLang;
+ do {
+ if ( ( elemLang = documentIsHTML ?
+ elem.lang :
+ elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) {
+
+ elemLang = elemLang.toLowerCase();
+ return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
+ }
+ } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 );
+ return false;
+ };
+ } ),
+
+ // Miscellaneous
+ "target": function( elem ) {
+ var hash = window.location && window.location.hash;
+ return hash && hash.slice( 1 ) === elem.id;
+ },
+
+ "root": function( elem ) {
+ return elem === docElem;
+ },
+
+ "focus": function( elem ) {
+ return elem === document.activeElement &&
+ ( !document.hasFocus || document.hasFocus() ) &&
+ !!( elem.type || elem.href || ~elem.tabIndex );
+ },
+
+ // Boolean properties
+ "enabled": createDisabledPseudo( false ),
+ "disabled": createDisabledPseudo( true ),
+
+ "checked": function( elem ) {
+
+ // In CSS3, :checked should return both checked and selected elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ var nodeName = elem.nodeName.toLowerCase();
+ return ( nodeName === "input" && !!elem.checked ) ||
+ ( nodeName === "option" && !!elem.selected );
+ },
+
+ "selected": function( elem ) {
+
+ // Accessing this property makes selected-by-default
+ // options in Safari work properly
+ if ( elem.parentNode ) {
+ // eslint-disable-next-line no-unused-expressions
+ elem.parentNode.selectedIndex;
+ }
+
+ return elem.selected === true;
+ },
+
+ // Contents
+ "empty": function( elem ) {
+
+ // http://www.w3.org/TR/selectors/#empty-pseudo
+ // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
+ // but not by others (comment: 8; processing instruction: 7; etc.)
+ // nodeType < 6 works because attributes (2) do not appear as children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+ if ( elem.nodeType < 6 ) {
+ return false;
+ }
+ }
+ return true;
+ },
+
+ "parent": function( elem ) {
+ return !Expr.pseudos[ "empty" ]( elem );
+ },
+
+ // Element/input types
+ "header": function( elem ) {
+ return rheader.test( elem.nodeName );
+ },
+
+ "input": function( elem ) {
+ return rinputs.test( elem.nodeName );
+ },
+
+ "button": function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === "button" || name === "button";
+ },
+
+ "text": function( elem ) {
+ var attr;
+ return elem.nodeName.toLowerCase() === "input" &&
+ elem.type === "text" &&
+
+ // Support: IE<8
+ // New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
+ ( ( attr = elem.getAttribute( "type" ) ) == null ||
+ attr.toLowerCase() === "text" );
+ },
+
+ // Position-in-collection
+ "first": createPositionalPseudo( function() {
+ return [ 0 ];
+ } ),
+
+ "last": createPositionalPseudo( function( _matchIndexes, length ) {
+ return [ length - 1 ];
+ } ),
+
+ "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) {
+ return [ argument < 0 ? argument + length : argument ];
+ } ),
+
+ "even": createPositionalPseudo( function( matchIndexes, length ) {
+ var i = 0;
+ for ( ; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ } ),
+
+ "odd": createPositionalPseudo( function( matchIndexes, length ) {
+ var i = 1;
+ for ( ; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ } ),
+
+ "lt": createPositionalPseudo( function( matchIndexes, length, argument ) {
+ var i = argument < 0 ?
+ argument + length :
+ argument > length ?
+ length :
+ argument;
+ for ( ; --i >= 0; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ } ),
+
+ "gt": createPositionalPseudo( function( matchIndexes, length, argument ) {
+ var i = argument < 0 ? argument + length : argument;
+ for ( ; ++i < length; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ } )
+ }
+};
+
+Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ];
+
+// Add button/input type pseudos
+for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
+ Expr.pseudos[ i ] = createInputPseudo( i );
+}
+for ( i in { submit: true, reset: true } ) {
+ Expr.pseudos[ i ] = createButtonPseudo( i );
+}
+
+// Easy API for creating new setFilters
+function setFilters() {}
+setFilters.prototype = Expr.filters = Expr.pseudos;
+Expr.setFilters = new setFilters();
+
+tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
+ var matched, match, tokens, type,
+ soFar, groups, preFilters,
+ cached = tokenCache[ selector + " " ];
+
+ if ( cached ) {
+ return parseOnly ? 0 : cached.slice( 0 );
+ }
+
+ soFar = selector;
+ groups = [];
+ preFilters = Expr.preFilter;
+
+ while ( soFar ) {
+
+ // Comma and first run
+ if ( !matched || ( match = rcomma.exec( soFar ) ) ) {
+ if ( match ) {
+
+ // Don't consume trailing commas as valid
+ soFar = soFar.slice( match[ 0 ].length ) || soFar;
+ }
+ groups.push( ( tokens = [] ) );
+ }
+
+ matched = false;
+
+ // Combinators
+ if ( ( match = rcombinators.exec( soFar ) ) ) {
+ matched = match.shift();
+ tokens.push( {
+ value: matched,
+
+ // Cast descendant combinators to space
+ type: match[ 0 ].replace( rtrim, " " )
+ } );
+ soFar = soFar.slice( matched.length );
+ }
+
+ // Filters
+ for ( type in Expr.filter ) {
+ if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] ||
+ ( match = preFilters[ type ]( match ) ) ) ) {
+ matched = match.shift();
+ tokens.push( {
+ value: matched,
+ type: type,
+ matches: match
+ } );
+ soFar = soFar.slice( matched.length );
+ }
+ }
+
+ if ( !matched ) {
+ break;
+ }
+ }
+
+ // Return the length of the invalid excess
+ // if we're just parsing
+ // Otherwise, throw an error or return tokens
+ return parseOnly ?
+ soFar.length :
+ soFar ?
+ Sizzle.error( selector ) :
+
+ // Cache the tokens
+ tokenCache( selector, groups ).slice( 0 );
+};
+
+function toSelector( tokens ) {
+ var i = 0,
+ len = tokens.length,
+ selector = "";
+ for ( ; i < len; i++ ) {
+ selector += tokens[ i ].value;
+ }
+ return selector;
+}
+
+function addCombinator( matcher, combinator, base ) {
+ var dir = combinator.dir,
+ skip = combinator.next,
+ key = skip || dir,
+ checkNonElements = base && key === "parentNode",
+ doneName = done++;
+
+ return combinator.first ?
+
+ // Check against closest ancestor/preceding element
+ function( elem, context, xml ) {
+ while ( ( elem = elem[ dir ] ) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ return matcher( elem, context, xml );
+ }
+ }
+ return false;
+ } :
+
+ // Check against all ancestor/preceding elements
+ function( elem, context, xml ) {
+ var oldCache, uniqueCache, outerCache,
+ newCache = [ dirruns, doneName ];
+
+ // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching
+ if ( xml ) {
+ while ( ( elem = elem[ dir ] ) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ if ( matcher( elem, context, xml ) ) {
+ return true;
+ }
+ }
+ }
+ } else {
+ while ( ( elem = elem[ dir ] ) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ outerCache = elem[ expando ] || ( elem[ expando ] = {} );
+
+ // Support: IE <9 only
+ // Defend against cloned attroperties (jQuery gh-1709)
+ uniqueCache = outerCache[ elem.uniqueID ] ||
+ ( outerCache[ elem.uniqueID ] = {} );
+
+ if ( skip && skip === elem.nodeName.toLowerCase() ) {
+ elem = elem[ dir ] || elem;
+ } else if ( ( oldCache = uniqueCache[ key ] ) &&
+ oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
+
+ // Assign to newCache so results back-propagate to previous elements
+ return ( newCache[ 2 ] = oldCache[ 2 ] );
+ } else {
+
+ // Reuse newcache so results back-propagate to previous elements
+ uniqueCache[ key ] = newCache;
+
+ // A match means we're done; a fail means we have to keep checking
+ if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ };
+}
+
+function elementMatcher( matchers ) {
+ return matchers.length > 1 ?
+ function( elem, context, xml ) {
+ var i = matchers.length;
+ while ( i-- ) {
+ if ( !matchers[ i ]( elem, context, xml ) ) {
+ return false;
+ }
+ }
+ return true;
+ } :
+ matchers[ 0 ];
+}
+
+function multipleContexts( selector, contexts, results ) {
+ var i = 0,
+ len = contexts.length;
+ for ( ; i < len; i++ ) {
+ Sizzle( selector, contexts[ i ], results );
+ }
+ return results;
+}
+
+function condense( unmatched, map, filter, context, xml ) {
+ var elem,
+ newUnmatched = [],
+ i = 0,
+ len = unmatched.length,
+ mapped = map != null;
+
+ for ( ; i < len; i++ ) {
+ if ( ( elem = unmatched[ i ] ) ) {
+ if ( !filter || filter( elem, context, xml ) ) {
+ newUnmatched.push( elem );
+ if ( mapped ) {
+ map.push( i );
+ }
+ }
+ }
+ }
+
+ return newUnmatched;
+}
+
+function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
+ if ( postFilter && !postFilter[ expando ] ) {
+ postFilter = setMatcher( postFilter );
+ }
+ if ( postFinder && !postFinder[ expando ] ) {
+ postFinder = setMatcher( postFinder, postSelector );
+ }
+ return markFunction( function( seed, results, context, xml ) {
+ var temp, i, elem,
+ preMap = [],
+ postMap = [],
+ preexisting = results.length,
+
+ // Get initial elements from seed or context
+ elems = seed || multipleContexts(
+ selector || "*",
+ context.nodeType ? [ context ] : context,
+ []
+ ),
+
+ // Prefilter to get matcher input, preserving a map for seed-results synchronization
+ matcherIn = preFilter && ( seed || !selector ) ?
+ condense( elems, preMap, preFilter, context, xml ) :
+ elems,
+
+ matcherOut = matcher ?
+
+ // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
+ postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
+
+ // ...intermediate processing is necessary
+ [] :
+
+ // ...otherwise use results directly
+ results :
+ matcherIn;
+
+ // Find primary matches
+ if ( matcher ) {
+ matcher( matcherIn, matcherOut, context, xml );
+ }
+
+ // Apply postFilter
+ if ( postFilter ) {
+ temp = condense( matcherOut, postMap );
+ postFilter( temp, [], context, xml );
+
+ // Un-match failing elements by moving them back to matcherIn
+ i = temp.length;
+ while ( i-- ) {
+ if ( ( elem = temp[ i ] ) ) {
+ matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem );
+ }
+ }
+ }
+
+ if ( seed ) {
+ if ( postFinder || preFilter ) {
+ if ( postFinder ) {
+
+ // Get the final matcherOut by condensing this intermediate into postFinder contexts
+ temp = [];
+ i = matcherOut.length;
+ while ( i-- ) {
+ if ( ( elem = matcherOut[ i ] ) ) {
+
+ // Restore matcherIn since elem is not yet a final match
+ temp.push( ( matcherIn[ i ] = elem ) );
+ }
+ }
+ postFinder( null, ( matcherOut = [] ), temp, xml );
+ }
+
+ // Move matched elements from seed to results to keep them synchronized
+ i = matcherOut.length;
+ while ( i-- ) {
+ if ( ( elem = matcherOut[ i ] ) &&
+ ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) {
+
+ seed[ temp ] = !( results[ temp ] = elem );
+ }
+ }
+ }
+
+ // Add elements to results, through postFinder if defined
+ } else {
+ matcherOut = condense(
+ matcherOut === results ?
+ matcherOut.splice( preexisting, matcherOut.length ) :
+ matcherOut
+ );
+ if ( postFinder ) {
+ postFinder( null, results, matcherOut, xml );
+ } else {
+ push.apply( results, matcherOut );
+ }
+ }
+ } );
+}
+
+function matcherFromTokens( tokens ) {
+ var checkContext, matcher, j,
+ len = tokens.length,
+ leadingRelative = Expr.relative[ tokens[ 0 ].type ],
+ implicitRelative = leadingRelative || Expr.relative[ " " ],
+ i = leadingRelative ? 1 : 0,
+
+ // The foundational matcher ensures that elements are reachable from top-level context(s)
+ matchContext = addCombinator( function( elem ) {
+ return elem === checkContext;
+ }, implicitRelative, true ),
+ matchAnyContext = addCombinator( function( elem ) {
+ return indexOf( checkContext, elem ) > -1;
+ }, implicitRelative, true ),
+ matchers = [ function( elem, context, xml ) {
+ var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
+ ( checkContext = context ).nodeType ?
+ matchContext( elem, context, xml ) :
+ matchAnyContext( elem, context, xml ) );
+
+ // Avoid hanging onto element (issue #299)
+ checkContext = null;
+ return ret;
+ } ];
+
+ for ( ; i < len; i++ ) {
+ if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) {
+ matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ];
+ } else {
+ matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches );
+
+ // Return special upon seeing a positional matcher
+ if ( matcher[ expando ] ) {
+
+ // Find the next relative operator (if any) for proper handling
+ j = ++i;
+ for ( ; j < len; j++ ) {
+ if ( Expr.relative[ tokens[ j ].type ] ) {
+ break;
+ }
+ }
+ return setMatcher(
+ i > 1 && elementMatcher( matchers ),
+ i > 1 && toSelector(
+
+ // If the preceding token was a descendant combinator, insert an implicit any-element `*`
+ tokens
+ .slice( 0, i - 1 )
+ .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } )
+ ).replace( rtrim, "$1" ),
+ matcher,
+ i < j && matcherFromTokens( tokens.slice( i, j ) ),
+ j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ),
+ j < len && toSelector( tokens )
+ );
+ }
+ matchers.push( matcher );
+ }
+ }
+
+ return elementMatcher( matchers );
+}
+
+function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
+ var bySet = setMatchers.length > 0,
+ byElement = elementMatchers.length > 0,
+ superMatcher = function( seed, context, xml, results, outermost ) {
+ var elem, j, matcher,
+ matchedCount = 0,
+ i = "0",
+ unmatched = seed && [],
+ setMatched = [],
+ contextBackup = outermostContext,
+
+ // We must always have either seed elements or outermost context
+ elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ),
+
+ // Use integer dirruns iff this is the outermost matcher
+ dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ),
+ len = elems.length;
+
+ if ( outermost ) {
+
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ outermostContext = context == document || context || outermost;
+ }
+
+ // Add elements passing elementMatchers directly to results
+ // Support: IE<9, Safari
+ // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id
+ for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) {
+ if ( byElement && elem ) {
+ j = 0;
+
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ if ( !context && elem.ownerDocument != document ) {
+ setDocument( elem );
+ xml = !documentIsHTML;
+ }
+ while ( ( matcher = elementMatchers[ j++ ] ) ) {
+ if ( matcher( elem, context || document, xml ) ) {
+ results.push( elem );
+ break;
+ }
+ }
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ }
+ }
+
+ // Track unmatched elements for set filters
+ if ( bySet ) {
+
+ // They will have gone through all possible matchers
+ if ( ( elem = !matcher && elem ) ) {
+ matchedCount--;
+ }
+
+ // Lengthen the array for every element, matched or not
+ if ( seed ) {
+ unmatched.push( elem );
+ }
+ }
+ }
+
+ // `i` is now the count of elements visited above, and adding it to `matchedCount`
+ // makes the latter nonnegative.
+ matchedCount += i;
+
+ // Apply set filters to unmatched elements
+ // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`
+ // equals `i`), unless we didn't visit _any_ elements in the above loop because we have
+ // no element matchers and no seed.
+ // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that
+ // case, which will result in a "00" `matchedCount` that differs from `i` but is also
+ // numerically zero.
+ if ( bySet && i !== matchedCount ) {
+ j = 0;
+ while ( ( matcher = setMatchers[ j++ ] ) ) {
+ matcher( unmatched, setMatched, context, xml );
+ }
+
+ if ( seed ) {
+
+ // Reintegrate element matches to eliminate the need for sorting
+ if ( matchedCount > 0 ) {
+ while ( i-- ) {
+ if ( !( unmatched[ i ] || setMatched[ i ] ) ) {
+ setMatched[ i ] = pop.call( results );
+ }
+ }
+ }
+
+ // Discard index placeholder values to get only actual matches
+ setMatched = condense( setMatched );
+ }
+
+ // Add matches to results
+ push.apply( results, setMatched );
+
+ // Seedless set matches succeeding multiple successful matchers stipulate sorting
+ if ( outermost && !seed && setMatched.length > 0 &&
+ ( matchedCount + setMatchers.length ) > 1 ) {
+
+ Sizzle.uniqueSort( results );
+ }
+ }
+
+ // Override manipulation of globals by nested matchers
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ outermostContext = contextBackup;
+ }
+
+ return unmatched;
+ };
+
+ return bySet ?
+ markFunction( superMatcher ) :
+ superMatcher;
+}
+
+compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {
+ var i,
+ setMatchers = [],
+ elementMatchers = [],
+ cached = compilerCache[ selector + " " ];
+
+ if ( !cached ) {
+
+ // Generate a function of recursive functions that can be used to check each element
+ if ( !match ) {
+ match = tokenize( selector );
+ }
+ i = match.length;
+ while ( i-- ) {
+ cached = matcherFromTokens( match[ i ] );
+ if ( cached[ expando ] ) {
+ setMatchers.push( cached );
+ } else {
+ elementMatchers.push( cached );
+ }
+ }
+
+ // Cache the compiled function
+ cached = compilerCache(
+ selector,
+ matcherFromGroupMatchers( elementMatchers, setMatchers )
+ );
+
+ // Save selector and tokenization
+ cached.selector = selector;
+ }
+ return cached;
+};
+
+/**
+ * A low-level selection function that works with Sizzle's compiled
+ * selector functions
+ * @param {String|Function} selector A selector or a pre-compiled
+ * selector function built with Sizzle.compile
+ * @param {Element} context
+ * @param {Array} [results]
+ * @param {Array} [seed] A set of elements to match against
+ */
+select = Sizzle.select = function( selector, context, results, seed ) {
+ var i, tokens, token, type, find,
+ compiled = typeof selector === "function" && selector,
+ match = !seed && tokenize( ( selector = compiled.selector || selector ) );
+
+ results = results || [];
+
+ // Try to minimize operations if there is only one selector in the list and no seed
+ // (the latter of which guarantees us context)
+ if ( match.length === 1 ) {
+
+ // Reduce context if the leading compound selector is an ID
+ tokens = match[ 0 ] = match[ 0 ].slice( 0 );
+ if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" &&
+ context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) {
+
+ context = ( Expr.find[ "ID" ]( token.matches[ 0 ]
+ .replace( runescape, funescape ), context ) || [] )[ 0 ];
+ if ( !context ) {
+ return results;
+
+ // Precompiled matchers will still verify ancestry, so step up a level
+ } else if ( compiled ) {
+ context = context.parentNode;
+ }
+
+ selector = selector.slice( tokens.shift().value.length );
+ }
+
+ // Fetch a seed set for right-to-left matching
+ i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length;
+ while ( i-- ) {
+ token = tokens[ i ];
+
+ // Abort if we hit a combinator
+ if ( Expr.relative[ ( type = token.type ) ] ) {
+ break;
+ }
+ if ( ( find = Expr.find[ type ] ) ) {
+
+ // Search, expanding context for leading sibling combinators
+ if ( ( seed = find(
+ token.matches[ 0 ].replace( runescape, funescape ),
+ rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) ||
+ context
+ ) ) ) {
+
+ // If seed is empty or no tokens remain, we can return early
+ tokens.splice( i, 1 );
+ selector = seed.length && toSelector( tokens );
+ if ( !selector ) {
+ push.apply( results, seed );
+ return results;
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ // Compile and execute a filtering function if one is not provided
+ // Provide `match` to avoid retokenization if we modified the selector above
+ ( compiled || compile( selector, match ) )(
+ seed,
+ context,
+ !documentIsHTML,
+ results,
+ !context || rsibling.test( selector ) && testContext( context.parentNode ) || context
+ );
+ return results;
+};
+
+// One-time assignments
+
+// Sort stability
+support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando;
+
+// Support: Chrome 14-35+
+// Always assume duplicates if they aren't passed to the comparison function
+support.detectDuplicates = !!hasDuplicate;
+
+// Initialize against the default document
+setDocument();
+
+// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
+// Detached nodes confoundingly follow *each other*
+support.sortDetached = assert( function( el ) {
+
+ // Should return 1, but returns 4 (following)
+ return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1;
+} );
+
+// Support: IE<8
+// Prevent attribute/property "interpolation"
+// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
+if ( !assert( function( el ) {
+ el.innerHTML = " ";
+ return el.firstChild.getAttribute( "href" ) === "#";
+} ) ) {
+ addHandle( "type|href|height|width", function( elem, name, isXML ) {
+ if ( !isXML ) {
+ return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
+ }
+ } );
+}
+
+// Support: IE<9
+// Use defaultValue in place of getAttribute("value")
+if ( !support.attributes || !assert( function( el ) {
+ el.innerHTML = " ";
+ el.firstChild.setAttribute( "value", "" );
+ return el.firstChild.getAttribute( "value" ) === "";
+} ) ) {
+ addHandle( "value", function( elem, _name, isXML ) {
+ if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
+ return elem.defaultValue;
+ }
+ } );
+}
+
+// Support: IE<9
+// Use getAttributeNode to fetch booleans when getAttribute lies
+if ( !assert( function( el ) {
+ return el.getAttribute( "disabled" ) == null;
+} ) ) {
+ addHandle( booleans, function( elem, name, isXML ) {
+ var val;
+ if ( !isXML ) {
+ return elem[ name ] === true ? name.toLowerCase() :
+ ( val = elem.getAttributeNode( name ) ) && val.specified ?
+ val.value :
+ null;
+ }
+ } );
+}
+
+return Sizzle;
+
+} )( window );
+
+
+
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+
+// Deprecated
+jQuery.expr[ ":" ] = jQuery.expr.pseudos;
+jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+jQuery.escapeSelector = Sizzle.escape;
+
+
+
+
+var dir = function( elem, dir, until ) {
+ var matched = [],
+ truncate = until !== undefined;
+
+ while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) {
+ if ( elem.nodeType === 1 ) {
+ if ( truncate && jQuery( elem ).is( until ) ) {
+ break;
+ }
+ matched.push( elem );
+ }
+ }
+ return matched;
+};
+
+
+var siblings = function( n, elem ) {
+ var matched = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType === 1 && n !== elem ) {
+ matched.push( n );
+ }
+ }
+
+ return matched;
+};
+
+
+var rneedsContext = jQuery.expr.match.needsContext;
+
+
+
+function nodeName( elem, name ) {
+
+ return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
+
+};
+var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i );
+
+
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, not ) {
+ if ( isFunction( qualifier ) ) {
+ return jQuery.grep( elements, function( elem, i ) {
+ return !!qualifier.call( elem, i, elem ) !== not;
+ } );
+ }
+
+ // Single element
+ if ( qualifier.nodeType ) {
+ return jQuery.grep( elements, function( elem ) {
+ return ( elem === qualifier ) !== not;
+ } );
+ }
+
+ // Arraylike of elements (jQuery, arguments, Array)
+ if ( typeof qualifier !== "string" ) {
+ return jQuery.grep( elements, function( elem ) {
+ return ( indexOf.call( qualifier, elem ) > -1 ) !== not;
+ } );
+ }
+
+ // Filtered directly for both simple and complex selectors
+ return jQuery.filter( qualifier, elements, not );
+}
+
+jQuery.filter = function( expr, elems, not ) {
+ var elem = elems[ 0 ];
+
+ if ( not ) {
+ expr = ":not(" + expr + ")";
+ }
+
+ if ( elems.length === 1 && elem.nodeType === 1 ) {
+ return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [];
+ }
+
+ return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
+ return elem.nodeType === 1;
+ } ) );
+};
+
+jQuery.fn.extend( {
+ find: function( selector ) {
+ var i, ret,
+ len = this.length,
+ self = this;
+
+ if ( typeof selector !== "string" ) {
+ return this.pushStack( jQuery( selector ).filter( function() {
+ for ( i = 0; i < len; i++ ) {
+ if ( jQuery.contains( self[ i ], this ) ) {
+ return true;
+ }
+ }
+ } ) );
+ }
+
+ ret = this.pushStack( [] );
+
+ for ( i = 0; i < len; i++ ) {
+ jQuery.find( selector, self[ i ], ret );
+ }
+
+ return len > 1 ? jQuery.uniqueSort( ret ) : ret;
+ },
+ filter: function( selector ) {
+ return this.pushStack( winnow( this, selector || [], false ) );
+ },
+ not: function( selector ) {
+ return this.pushStack( winnow( this, selector || [], true ) );
+ },
+ is: function( selector ) {
+ return !!winnow(
+ this,
+
+ // If this is a positional/relative selector, check membership in the returned set
+ // so $("p:first").is("p:last") won't return true for a doc with two "p".
+ typeof selector === "string" && rneedsContext.test( selector ) ?
+ jQuery( selector ) :
+ selector || [],
+ false
+ ).length;
+ }
+} );
+
+
+// Initialize a jQuery object
+
+
+// A central reference to the root jQuery(document)
+var rootjQuery,
+
+ // A simple way to check for HTML strings
+ // Prioritize #id over to avoid XSS via location.hash (#9521)
+ // Strict HTML recognition (#11290: must start with <)
+ // Shortcut simple #id case for speed
+ rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
+
+ init = jQuery.fn.init = function( selector, context, root ) {
+ var match, elem;
+
+ // HANDLE: $(""), $(null), $(undefined), $(false)
+ if ( !selector ) {
+ return this;
+ }
+
+ // Method init() accepts an alternate rootjQuery
+ // so migrate can support jQuery.sub (gh-2101)
+ root = root || rootjQuery;
+
+ // Handle HTML strings
+ if ( typeof selector === "string" ) {
+ if ( selector[ 0 ] === "<" &&
+ selector[ selector.length - 1 ] === ">" &&
+ selector.length >= 3 ) {
+
+ // Assume that strings that start and end with <> are HTML and skip the regex check
+ match = [ null, selector, null ];
+
+ } else {
+ match = rquickExpr.exec( selector );
+ }
+
+ // Match html or make sure no context is specified for #id
+ if ( match && ( match[ 1 ] || !context ) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[ 1 ] ) {
+ context = context instanceof jQuery ? context[ 0 ] : context;
+
+ // Option to run scripts is true for back-compat
+ // Intentionally let the error be thrown if parseHTML is not present
+ jQuery.merge( this, jQuery.parseHTML(
+ match[ 1 ],
+ context && context.nodeType ? context.ownerDocument || context : document,
+ true
+ ) );
+
+ // HANDLE: $(html, props)
+ if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {
+ for ( match in context ) {
+
+ // Properties of context are called as methods if possible
+ if ( isFunction( this[ match ] ) ) {
+ this[ match ]( context[ match ] );
+
+ // ...and otherwise set as attributes
+ } else {
+ this.attr( match, context[ match ] );
+ }
+ }
+ }
+
+ return this;
+
+ // HANDLE: $(#id)
+ } else {
+ elem = document.getElementById( match[ 2 ] );
+
+ if ( elem ) {
+
+ // Inject the element directly into the jQuery object
+ this[ 0 ] = elem;
+ this.length = 1;
+ }
+ return this;
+ }
+
+ // HANDLE: $(expr, $(...))
+ } else if ( !context || context.jquery ) {
+ return ( context || root ).find( selector );
+
+ // HANDLE: $(expr, context)
+ // (which is just equivalent to: $(context).find(expr)
+ } else {
+ return this.constructor( context ).find( selector );
+ }
+
+ // HANDLE: $(DOMElement)
+ } else if ( selector.nodeType ) {
+ this[ 0 ] = selector;
+ this.length = 1;
+ return this;
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( isFunction( selector ) ) {
+ return root.ready !== undefined ?
+ root.ready( selector ) :
+
+ // Execute immediately if ready is not present
+ selector( jQuery );
+ }
+
+ return jQuery.makeArray( selector, this );
+ };
+
+// Give the init function the jQuery prototype for later instantiation
+init.prototype = jQuery.fn;
+
+// Initialize central reference
+rootjQuery = jQuery( document );
+
+
+var rparentsprev = /^(?:parents|prev(?:Until|All))/,
+
+ // Methods guaranteed to produce a unique set when starting from a unique set
+ guaranteedUnique = {
+ children: true,
+ contents: true,
+ next: true,
+ prev: true
+ };
+
+jQuery.fn.extend( {
+ has: function( target ) {
+ var targets = jQuery( target, this ),
+ l = targets.length;
+
+ return this.filter( function() {
+ var i = 0;
+ for ( ; i < l; i++ ) {
+ if ( jQuery.contains( this, targets[ i ] ) ) {
+ return true;
+ }
+ }
+ } );
+ },
+
+ closest: function( selectors, context ) {
+ var cur,
+ i = 0,
+ l = this.length,
+ matched = [],
+ targets = typeof selectors !== "string" && jQuery( selectors );
+
+ // Positional selectors never match, since there's no _selection_ context
+ if ( !rneedsContext.test( selectors ) ) {
+ for ( ; i < l; i++ ) {
+ for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) {
+
+ // Always skip document fragments
+ if ( cur.nodeType < 11 && ( targets ?
+ targets.index( cur ) > -1 :
+
+ // Don't pass non-elements to Sizzle
+ cur.nodeType === 1 &&
+ jQuery.find.matchesSelector( cur, selectors ) ) ) {
+
+ matched.push( cur );
+ break;
+ }
+ }
+ }
+ }
+
+ return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched );
+ },
+
+ // Determine the position of an element within the set
+ index: function( elem ) {
+
+ // No argument, return index in parent
+ if ( !elem ) {
+ return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;
+ }
+
+ // Index in selector
+ if ( typeof elem === "string" ) {
+ return indexOf.call( jQuery( elem ), this[ 0 ] );
+ }
+
+ // Locate the position of the desired element
+ return indexOf.call( this,
+
+ // If it receives a jQuery object, the first element is used
+ elem.jquery ? elem[ 0 ] : elem
+ );
+ },
+
+ add: function( selector, context ) {
+ return this.pushStack(
+ jQuery.uniqueSort(
+ jQuery.merge( this.get(), jQuery( selector, context ) )
+ )
+ );
+ },
+
+ addBack: function( selector ) {
+ return this.add( selector == null ?
+ this.prevObject : this.prevObject.filter( selector )
+ );
+ }
+} );
+
+function sibling( cur, dir ) {
+ while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {}
+ return cur;
+}
+
+jQuery.each( {
+ parent: function( elem ) {
+ var parent = elem.parentNode;
+ return parent && parent.nodeType !== 11 ? parent : null;
+ },
+ parents: function( elem ) {
+ return dir( elem, "parentNode" );
+ },
+ parentsUntil: function( elem, _i, until ) {
+ return dir( elem, "parentNode", until );
+ },
+ next: function( elem ) {
+ return sibling( elem, "nextSibling" );
+ },
+ prev: function( elem ) {
+ return sibling( elem, "previousSibling" );
+ },
+ nextAll: function( elem ) {
+ return dir( elem, "nextSibling" );
+ },
+ prevAll: function( elem ) {
+ return dir( elem, "previousSibling" );
+ },
+ nextUntil: function( elem, _i, until ) {
+ return dir( elem, "nextSibling", until );
+ },
+ prevUntil: function( elem, _i, until ) {
+ return dir( elem, "previousSibling", until );
+ },
+ siblings: function( elem ) {
+ return siblings( ( elem.parentNode || {} ).firstChild, elem );
+ },
+ children: function( elem ) {
+ return siblings( elem.firstChild );
+ },
+ contents: function( elem ) {
+ if ( elem.contentDocument != null &&
+
+ // Support: IE 11+
+ // elements with no `data` attribute has an object
+ // `contentDocument` with a `null` prototype.
+ getProto( elem.contentDocument ) ) {
+
+ return elem.contentDocument;
+ }
+
+ // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only
+ // Treat the template element as a regular one in browsers that
+ // don't support it.
+ if ( nodeName( elem, "template" ) ) {
+ elem = elem.content || elem;
+ }
+
+ return jQuery.merge( [], elem.childNodes );
+ }
+}, function( name, fn ) {
+ jQuery.fn[ name ] = function( until, selector ) {
+ var matched = jQuery.map( this, fn, until );
+
+ if ( name.slice( -5 ) !== "Until" ) {
+ selector = until;
+ }
+
+ if ( selector && typeof selector === "string" ) {
+ matched = jQuery.filter( selector, matched );
+ }
+
+ if ( this.length > 1 ) {
+
+ // Remove duplicates
+ if ( !guaranteedUnique[ name ] ) {
+ jQuery.uniqueSort( matched );
+ }
+
+ // Reverse order for parents* and prev-derivatives
+ if ( rparentsprev.test( name ) ) {
+ matched.reverse();
+ }
+ }
+
+ return this.pushStack( matched );
+ };
+} );
+var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g );
+
+
+
+// Convert String-formatted options into Object-formatted ones
+function createOptions( options ) {
+ var object = {};
+ jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) {
+ object[ flag ] = true;
+ } );
+ return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ * options: an optional list of space-separated options that will change how
+ * the callback list behaves or a more traditional option object
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible options:
+ *
+ * once: will ensure the callback list can only be fired once (like a Deferred)
+ *
+ * memory: will keep track of previous values and will call any callback added
+ * after the list has been fired right away with the latest "memorized"
+ * values (like a Deferred)
+ *
+ * unique: will ensure a callback can only be added once (no duplicate in the list)
+ *
+ * stopOnFalse: interrupt callings when a callback returns false
+ *
+ */
+jQuery.Callbacks = function( options ) {
+
+ // Convert options from String-formatted to Object-formatted if needed
+ // (we check in cache first)
+ options = typeof options === "string" ?
+ createOptions( options ) :
+ jQuery.extend( {}, options );
+
+ var // Flag to know if list is currently firing
+ firing,
+
+ // Last fire value for non-forgettable lists
+ memory,
+
+ // Flag to know if list was already fired
+ fired,
+
+ // Flag to prevent firing
+ locked,
+
+ // Actual callback list
+ list = [],
+
+ // Queue of execution data for repeatable lists
+ queue = [],
+
+ // Index of currently firing callback (modified by add/remove as needed)
+ firingIndex = -1,
+
+ // Fire callbacks
+ fire = function() {
+
+ // Enforce single-firing
+ locked = locked || options.once;
+
+ // Execute callbacks for all pending executions,
+ // respecting firingIndex overrides and runtime changes
+ fired = firing = true;
+ for ( ; queue.length; firingIndex = -1 ) {
+ memory = queue.shift();
+ while ( ++firingIndex < list.length ) {
+
+ // Run callback and check for early termination
+ if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
+ options.stopOnFalse ) {
+
+ // Jump to end and forget the data so .add doesn't re-fire
+ firingIndex = list.length;
+ memory = false;
+ }
+ }
+ }
+
+ // Forget the data if we're done with it
+ if ( !options.memory ) {
+ memory = false;
+ }
+
+ firing = false;
+
+ // Clean up if we're done firing for good
+ if ( locked ) {
+
+ // Keep an empty list if we have data for future add calls
+ if ( memory ) {
+ list = [];
+
+ // Otherwise, this object is spent
+ } else {
+ list = "";
+ }
+ }
+ },
+
+ // Actual Callbacks object
+ self = {
+
+ // Add a callback or a collection of callbacks to the list
+ add: function() {
+ if ( list ) {
+
+ // If we have memory from a past run, we should fire after adding
+ if ( memory && !firing ) {
+ firingIndex = list.length - 1;
+ queue.push( memory );
+ }
+
+ ( function add( args ) {
+ jQuery.each( args, function( _, arg ) {
+ if ( isFunction( arg ) ) {
+ if ( !options.unique || !self.has( arg ) ) {
+ list.push( arg );
+ }
+ } else if ( arg && arg.length && toType( arg ) !== "string" ) {
+
+ // Inspect recursively
+ add( arg );
+ }
+ } );
+ } )( arguments );
+
+ if ( memory && !firing ) {
+ fire();
+ }
+ }
+ return this;
+ },
+
+ // Remove a callback from the list
+ remove: function() {
+ jQuery.each( arguments, function( _, arg ) {
+ var index;
+ while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
+ list.splice( index, 1 );
+
+ // Handle firing indexes
+ if ( index <= firingIndex ) {
+ firingIndex--;
+ }
+ }
+ } );
+ return this;
+ },
+
+ // Check if a given callback is in the list.
+ // If no argument is given, return whether or not list has callbacks attached.
+ has: function( fn ) {
+ return fn ?
+ jQuery.inArray( fn, list ) > -1 :
+ list.length > 0;
+ },
+
+ // Remove all callbacks from the list
+ empty: function() {
+ if ( list ) {
+ list = [];
+ }
+ return this;
+ },
+
+ // Disable .fire and .add
+ // Abort any current/pending executions
+ // Clear all callbacks and values
+ disable: function() {
+ locked = queue = [];
+ list = memory = "";
+ return this;
+ },
+ disabled: function() {
+ return !list;
+ },
+
+ // Disable .fire
+ // Also disable .add unless we have memory (since it would have no effect)
+ // Abort any pending executions
+ lock: function() {
+ locked = queue = [];
+ if ( !memory && !firing ) {
+ list = memory = "";
+ }
+ return this;
+ },
+ locked: function() {
+ return !!locked;
+ },
+
+ // Call all callbacks with the given context and arguments
+ fireWith: function( context, args ) {
+ if ( !locked ) {
+ args = args || [];
+ args = [ context, args.slice ? args.slice() : args ];
+ queue.push( args );
+ if ( !firing ) {
+ fire();
+ }
+ }
+ return this;
+ },
+
+ // Call all the callbacks with the given arguments
+ fire: function() {
+ self.fireWith( this, arguments );
+ return this;
+ },
+
+ // To know if the callbacks have already been called at least once
+ fired: function() {
+ return !!fired;
+ }
+ };
+
+ return self;
+};
+
+
+function Identity( v ) {
+ return v;
+}
+function Thrower( ex ) {
+ throw ex;
+}
+
+function adoptValue( value, resolve, reject, noValue ) {
+ var method;
+
+ try {
+
+ // Check for promise aspect first to privilege synchronous behavior
+ if ( value && isFunction( ( method = value.promise ) ) ) {
+ method.call( value ).done( resolve ).fail( reject );
+
+ // Other thenables
+ } else if ( value && isFunction( ( method = value.then ) ) ) {
+ method.call( value, resolve, reject );
+
+ // Other non-thenables
+ } else {
+
+ // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer:
+ // * false: [ value ].slice( 0 ) => resolve( value )
+ // * true: [ value ].slice( 1 ) => resolve()
+ resolve.apply( undefined, [ value ].slice( noValue ) );
+ }
+
+ // For Promises/A+, convert exceptions into rejections
+ // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in
+ // Deferred#then to conditionally suppress rejection.
+ } catch ( value ) {
+
+ // Support: Android 4.0 only
+ // Strict mode functions invoked without .call/.apply get global-object context
+ reject.apply( undefined, [ value ] );
+ }
+}
+
+jQuery.extend( {
+
+ Deferred: function( func ) {
+ var tuples = [
+
+ // action, add listener, callbacks,
+ // ... .then handlers, argument index, [final state]
+ [ "notify", "progress", jQuery.Callbacks( "memory" ),
+ jQuery.Callbacks( "memory" ), 2 ],
+ [ "resolve", "done", jQuery.Callbacks( "once memory" ),
+ jQuery.Callbacks( "once memory" ), 0, "resolved" ],
+ [ "reject", "fail", jQuery.Callbacks( "once memory" ),
+ jQuery.Callbacks( "once memory" ), 1, "rejected" ]
+ ],
+ state = "pending",
+ promise = {
+ state: function() {
+ return state;
+ },
+ always: function() {
+ deferred.done( arguments ).fail( arguments );
+ return this;
+ },
+ "catch": function( fn ) {
+ return promise.then( null, fn );
+ },
+
+ // Keep pipe for back-compat
+ pipe: function( /* fnDone, fnFail, fnProgress */ ) {
+ var fns = arguments;
+
+ return jQuery.Deferred( function( newDefer ) {
+ jQuery.each( tuples, function( _i, tuple ) {
+
+ // Map tuples (progress, done, fail) to arguments (done, fail, progress)
+ var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ];
+
+ // deferred.progress(function() { bind to newDefer or newDefer.notify })
+ // deferred.done(function() { bind to newDefer or newDefer.resolve })
+ // deferred.fail(function() { bind to newDefer or newDefer.reject })
+ deferred[ tuple[ 1 ] ]( function() {
+ var returned = fn && fn.apply( this, arguments );
+ if ( returned && isFunction( returned.promise ) ) {
+ returned.promise()
+ .progress( newDefer.notify )
+ .done( newDefer.resolve )
+ .fail( newDefer.reject );
+ } else {
+ newDefer[ tuple[ 0 ] + "With" ](
+ this,
+ fn ? [ returned ] : arguments
+ );
+ }
+ } );
+ } );
+ fns = null;
+ } ).promise();
+ },
+ then: function( onFulfilled, onRejected, onProgress ) {
+ var maxDepth = 0;
+ function resolve( depth, deferred, handler, special ) {
+ return function() {
+ var that = this,
+ args = arguments,
+ mightThrow = function() {
+ var returned, then;
+
+ // Support: Promises/A+ section 2.3.3.3.3
+ // https://promisesaplus.com/#point-59
+ // Ignore double-resolution attempts
+ if ( depth < maxDepth ) {
+ return;
+ }
+
+ returned = handler.apply( that, args );
+
+ // Support: Promises/A+ section 2.3.1
+ // https://promisesaplus.com/#point-48
+ if ( returned === deferred.promise() ) {
+ throw new TypeError( "Thenable self-resolution" );
+ }
+
+ // Support: Promises/A+ sections 2.3.3.1, 3.5
+ // https://promisesaplus.com/#point-54
+ // https://promisesaplus.com/#point-75
+ // Retrieve `then` only once
+ then = returned &&
+
+ // Support: Promises/A+ section 2.3.4
+ // https://promisesaplus.com/#point-64
+ // Only check objects and functions for thenability
+ ( typeof returned === "object" ||
+ typeof returned === "function" ) &&
+ returned.then;
+
+ // Handle a returned thenable
+ if ( isFunction( then ) ) {
+
+ // Special processors (notify) just wait for resolution
+ if ( special ) {
+ then.call(
+ returned,
+ resolve( maxDepth, deferred, Identity, special ),
+ resolve( maxDepth, deferred, Thrower, special )
+ );
+
+ // Normal processors (resolve) also hook into progress
+ } else {
+
+ // ...and disregard older resolution values
+ maxDepth++;
+
+ then.call(
+ returned,
+ resolve( maxDepth, deferred, Identity, special ),
+ resolve( maxDepth, deferred, Thrower, special ),
+ resolve( maxDepth, deferred, Identity,
+ deferred.notifyWith )
+ );
+ }
+
+ // Handle all other returned values
+ } else {
+
+ // Only substitute handlers pass on context
+ // and multiple values (non-spec behavior)
+ if ( handler !== Identity ) {
+ that = undefined;
+ args = [ returned ];
+ }
+
+ // Process the value(s)
+ // Default process is resolve
+ ( special || deferred.resolveWith )( that, args );
+ }
+ },
+
+ // Only normal processors (resolve) catch and reject exceptions
+ process = special ?
+ mightThrow :
+ function() {
+ try {
+ mightThrow();
+ } catch ( e ) {
+
+ if ( jQuery.Deferred.exceptionHook ) {
+ jQuery.Deferred.exceptionHook( e,
+ process.stackTrace );
+ }
+
+ // Support: Promises/A+ section 2.3.3.3.4.1
+ // https://promisesaplus.com/#point-61
+ // Ignore post-resolution exceptions
+ if ( depth + 1 >= maxDepth ) {
+
+ // Only substitute handlers pass on context
+ // and multiple values (non-spec behavior)
+ if ( handler !== Thrower ) {
+ that = undefined;
+ args = [ e ];
+ }
+
+ deferred.rejectWith( that, args );
+ }
+ }
+ };
+
+ // Support: Promises/A+ section 2.3.3.3.1
+ // https://promisesaplus.com/#point-57
+ // Re-resolve promises immediately to dodge false rejection from
+ // subsequent errors
+ if ( depth ) {
+ process();
+ } else {
+
+ // Call an optional hook to record the stack, in case of exception
+ // since it's otherwise lost when execution goes async
+ if ( jQuery.Deferred.getStackHook ) {
+ process.stackTrace = jQuery.Deferred.getStackHook();
+ }
+ window.setTimeout( process );
+ }
+ };
+ }
+
+ return jQuery.Deferred( function( newDefer ) {
+
+ // progress_handlers.add( ... )
+ tuples[ 0 ][ 3 ].add(
+ resolve(
+ 0,
+ newDefer,
+ isFunction( onProgress ) ?
+ onProgress :
+ Identity,
+ newDefer.notifyWith
+ )
+ );
+
+ // fulfilled_handlers.add( ... )
+ tuples[ 1 ][ 3 ].add(
+ resolve(
+ 0,
+ newDefer,
+ isFunction( onFulfilled ) ?
+ onFulfilled :
+ Identity
+ )
+ );
+
+ // rejected_handlers.add( ... )
+ tuples[ 2 ][ 3 ].add(
+ resolve(
+ 0,
+ newDefer,
+ isFunction( onRejected ) ?
+ onRejected :
+ Thrower
+ )
+ );
+ } ).promise();
+ },
+
+ // Get a promise for this deferred
+ // If obj is provided, the promise aspect is added to the object
+ promise: function( obj ) {
+ return obj != null ? jQuery.extend( obj, promise ) : promise;
+ }
+ },
+ deferred = {};
+
+ // Add list-specific methods
+ jQuery.each( tuples, function( i, tuple ) {
+ var list = tuple[ 2 ],
+ stateString = tuple[ 5 ];
+
+ // promise.progress = list.add
+ // promise.done = list.add
+ // promise.fail = list.add
+ promise[ tuple[ 1 ] ] = list.add;
+
+ // Handle state
+ if ( stateString ) {
+ list.add(
+ function() {
+
+ // state = "resolved" (i.e., fulfilled)
+ // state = "rejected"
+ state = stateString;
+ },
+
+ // rejected_callbacks.disable
+ // fulfilled_callbacks.disable
+ tuples[ 3 - i ][ 2 ].disable,
+
+ // rejected_handlers.disable
+ // fulfilled_handlers.disable
+ tuples[ 3 - i ][ 3 ].disable,
+
+ // progress_callbacks.lock
+ tuples[ 0 ][ 2 ].lock,
+
+ // progress_handlers.lock
+ tuples[ 0 ][ 3 ].lock
+ );
+ }
+
+ // progress_handlers.fire
+ // fulfilled_handlers.fire
+ // rejected_handlers.fire
+ list.add( tuple[ 3 ].fire );
+
+ // deferred.notify = function() { deferred.notifyWith(...) }
+ // deferred.resolve = function() { deferred.resolveWith(...) }
+ // deferred.reject = function() { deferred.rejectWith(...) }
+ deferred[ tuple[ 0 ] ] = function() {
+ deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments );
+ return this;
+ };
+
+ // deferred.notifyWith = list.fireWith
+ // deferred.resolveWith = list.fireWith
+ // deferred.rejectWith = list.fireWith
+ deferred[ tuple[ 0 ] + "With" ] = list.fireWith;
+ } );
+
+ // Make the deferred a promise
+ promise.promise( deferred );
+
+ // Call given func if any
+ if ( func ) {
+ func.call( deferred, deferred );
+ }
+
+ // All done!
+ return deferred;
+ },
+
+ // Deferred helper
+ when: function( singleValue ) {
+ var
+
+ // count of uncompleted subordinates
+ remaining = arguments.length,
+
+ // count of unprocessed arguments
+ i = remaining,
+
+ // subordinate fulfillment data
+ resolveContexts = Array( i ),
+ resolveValues = slice.call( arguments ),
+
+ // the master Deferred
+ master = jQuery.Deferred(),
+
+ // subordinate callback factory
+ updateFunc = function( i ) {
+ return function( value ) {
+ resolveContexts[ i ] = this;
+ resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
+ if ( !( --remaining ) ) {
+ master.resolveWith( resolveContexts, resolveValues );
+ }
+ };
+ };
+
+ // Single- and empty arguments are adopted like Promise.resolve
+ if ( remaining <= 1 ) {
+ adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject,
+ !remaining );
+
+ // Use .then() to unwrap secondary thenables (cf. gh-3000)
+ if ( master.state() === "pending" ||
+ isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) {
+
+ return master.then();
+ }
+ }
+
+ // Multiple arguments are aggregated like Promise.all array elements
+ while ( i-- ) {
+ adoptValue( resolveValues[ i ], updateFunc( i ), master.reject );
+ }
+
+ return master.promise();
+ }
+} );
+
+
+// These usually indicate a programmer mistake during development,
+// warn about them ASAP rather than swallowing them by default.
+var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;
+
+jQuery.Deferred.exceptionHook = function( error, stack ) {
+
+ // Support: IE 8 - 9 only
+ // Console exists when dev tools are open, which can happen at any time
+ if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) {
+ window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack );
+ }
+};
+
+
+
+
+jQuery.readyException = function( error ) {
+ window.setTimeout( function() {
+ throw error;
+ } );
+};
+
+
+
+
+// The deferred used on DOM ready
+var readyList = jQuery.Deferred();
+
+jQuery.fn.ready = function( fn ) {
+
+ readyList
+ .then( fn )
+
+ // Wrap jQuery.readyException in a function so that the lookup
+ // happens at the time of error handling instead of callback
+ // registration.
+ .catch( function( error ) {
+ jQuery.readyException( error );
+ } );
+
+ return this;
+};
+
+jQuery.extend( {
+
+ // Is the DOM ready to be used? Set to true once it occurs.
+ isReady: false,
+
+ // A counter to track how many items to wait for before
+ // the ready event fires. See #6781
+ readyWait: 1,
+
+ // Handle when the DOM is ready
+ ready: function( wait ) {
+
+ // Abort if there are pending holds or we're already ready
+ if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
+ return;
+ }
+
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If a normal DOM Ready event fired, decrement, and wait if need be
+ if ( wait !== true && --jQuery.readyWait > 0 ) {
+ return;
+ }
+
+ // If there are functions bound, to execute
+ readyList.resolveWith( document, [ jQuery ] );
+ }
+} );
+
+jQuery.ready.then = readyList.then;
+
+// The ready event handler and self cleanup method
+function completed() {
+ document.removeEventListener( "DOMContentLoaded", completed );
+ window.removeEventListener( "load", completed );
+ jQuery.ready();
+}
+
+// Catch cases where $(document).ready() is called
+// after the browser event has already occurred.
+// Support: IE <=9 - 10 only
+// Older IE sometimes signals "interactive" too soon
+if ( document.readyState === "complete" ||
+ ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) {
+
+ // Handle it asynchronously to allow scripts the opportunity to delay ready
+ window.setTimeout( jQuery.ready );
+
+} else {
+
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", completed );
+
+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", completed );
+}
+
+
+
+
+// Multifunctional method to get and set values of a collection
+// The value/s can optionally be executed if it's a function
+var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
+ var i = 0,
+ len = elems.length,
+ bulk = key == null;
+
+ // Sets many values
+ if ( toType( key ) === "object" ) {
+ chainable = true;
+ for ( i in key ) {
+ access( elems, fn, i, key[ i ], true, emptyGet, raw );
+ }
+
+ // Sets one value
+ } else if ( value !== undefined ) {
+ chainable = true;
+
+ if ( !isFunction( value ) ) {
+ raw = true;
+ }
+
+ if ( bulk ) {
+
+ // Bulk operations run against the entire set
+ if ( raw ) {
+ fn.call( elems, value );
+ fn = null;
+
+ // ...except when executing function values
+ } else {
+ bulk = fn;
+ fn = function( elem, _key, value ) {
+ return bulk.call( jQuery( elem ), value );
+ };
+ }
+ }
+
+ if ( fn ) {
+ for ( ; i < len; i++ ) {
+ fn(
+ elems[ i ], key, raw ?
+ value :
+ value.call( elems[ i ], i, fn( elems[ i ], key ) )
+ );
+ }
+ }
+ }
+
+ if ( chainable ) {
+ return elems;
+ }
+
+ // Gets
+ if ( bulk ) {
+ return fn.call( elems );
+ }
+
+ return len ? fn( elems[ 0 ], key ) : emptyGet;
+};
+
+
+// Matches dashed string for camelizing
+var rmsPrefix = /^-ms-/,
+ rdashAlpha = /-([a-z])/g;
+
+// Used by camelCase as callback to replace()
+function fcamelCase( _all, letter ) {
+ return letter.toUpperCase();
+}
+
+// Convert dashed to camelCase; used by the css and data modules
+// Support: IE <=9 - 11, Edge 12 - 15
+// Microsoft forgot to hump their vendor prefix (#9572)
+function camelCase( string ) {
+ return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+}
+var acceptData = function( owner ) {
+
+ // Accepts only:
+ // - Node
+ // - Node.ELEMENT_NODE
+ // - Node.DOCUMENT_NODE
+ // - Object
+ // - Any
+ return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
+};
+
+
+
+
+function Data() {
+ this.expando = jQuery.expando + Data.uid++;
+}
+
+Data.uid = 1;
+
+Data.prototype = {
+
+ cache: function( owner ) {
+
+ // Check if the owner object already has a cache
+ var value = owner[ this.expando ];
+
+ // If not, create one
+ if ( !value ) {
+ value = {};
+
+ // We can accept data for non-element nodes in modern browsers,
+ // but we should not, see #8335.
+ // Always return an empty object.
+ if ( acceptData( owner ) ) {
+
+ // If it is a node unlikely to be stringify-ed or looped over
+ // use plain assignment
+ if ( owner.nodeType ) {
+ owner[ this.expando ] = value;
+
+ // Otherwise secure it in a non-enumerable property
+ // configurable must be true to allow the property to be
+ // deleted when data is removed
+ } else {
+ Object.defineProperty( owner, this.expando, {
+ value: value,
+ configurable: true
+ } );
+ }
+ }
+ }
+
+ return value;
+ },
+ set: function( owner, data, value ) {
+ var prop,
+ cache = this.cache( owner );
+
+ // Handle: [ owner, key, value ] args
+ // Always use camelCase key (gh-2257)
+ if ( typeof data === "string" ) {
+ cache[ camelCase( data ) ] = value;
+
+ // Handle: [ owner, { properties } ] args
+ } else {
+
+ // Copy the properties one-by-one to the cache object
+ for ( prop in data ) {
+ cache[ camelCase( prop ) ] = data[ prop ];
+ }
+ }
+ return cache;
+ },
+ get: function( owner, key ) {
+ return key === undefined ?
+ this.cache( owner ) :
+
+ // Always use camelCase key (gh-2257)
+ owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ];
+ },
+ access: function( owner, key, value ) {
+
+ // In cases where either:
+ //
+ // 1. No key was specified
+ // 2. A string key was specified, but no value provided
+ //
+ // Take the "read" path and allow the get method to determine
+ // which value to return, respectively either:
+ //
+ // 1. The entire cache object
+ // 2. The data stored at the key
+ //
+ if ( key === undefined ||
+ ( ( key && typeof key === "string" ) && value === undefined ) ) {
+
+ return this.get( owner, key );
+ }
+
+ // When the key is not a string, or both a key and value
+ // are specified, set or extend (existing objects) with either:
+ //
+ // 1. An object of properties
+ // 2. A key and value
+ //
+ this.set( owner, key, value );
+
+ // Since the "set" path can have two possible entry points
+ // return the expected data based on which path was taken[*]
+ return value !== undefined ? value : key;
+ },
+ remove: function( owner, key ) {
+ var i,
+ cache = owner[ this.expando ];
+
+ if ( cache === undefined ) {
+ return;
+ }
+
+ if ( key !== undefined ) {
+
+ // Support array or space separated string of keys
+ if ( Array.isArray( key ) ) {
+
+ // If key is an array of keys...
+ // We always set camelCase keys, so remove that.
+ key = key.map( camelCase );
+ } else {
+ key = camelCase( key );
+
+ // If a key with the spaces exists, use it.
+ // Otherwise, create an array by matching non-whitespace
+ key = key in cache ?
+ [ key ] :
+ ( key.match( rnothtmlwhite ) || [] );
+ }
+
+ i = key.length;
+
+ while ( i-- ) {
+ delete cache[ key[ i ] ];
+ }
+ }
+
+ // Remove the expando if there's no more data
+ if ( key === undefined || jQuery.isEmptyObject( cache ) ) {
+
+ // Support: Chrome <=35 - 45
+ // Webkit & Blink performance suffers when deleting properties
+ // from DOM nodes, so set to undefined instead
+ // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted)
+ if ( owner.nodeType ) {
+ owner[ this.expando ] = undefined;
+ } else {
+ delete owner[ this.expando ];
+ }
+ }
+ },
+ hasData: function( owner ) {
+ var cache = owner[ this.expando ];
+ return cache !== undefined && !jQuery.isEmptyObject( cache );
+ }
+};
+var dataPriv = new Data();
+
+var dataUser = new Data();
+
+
+
+// Implementation Summary
+//
+// 1. Enforce API surface and semantic compatibility with 1.9.x branch
+// 2. Improve the module's maintainability by reducing the storage
+// paths to a single mechanism.
+// 3. Use the same single mechanism to support "private" and "user" data.
+// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
+// 5. Avoid exposing implementation details on user objects (eg. expando properties)
+// 6. Provide a clear path for implementation upgrade to WeakMap in 2014
+
+var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
+ rmultiDash = /[A-Z]/g;
+
+function getData( data ) {
+ if ( data === "true" ) {
+ return true;
+ }
+
+ if ( data === "false" ) {
+ return false;
+ }
+
+ if ( data === "null" ) {
+ return null;
+ }
+
+ // Only convert to a number if it doesn't change the string
+ if ( data === +data + "" ) {
+ return +data;
+ }
+
+ if ( rbrace.test( data ) ) {
+ return JSON.parse( data );
+ }
+
+ return data;
+}
+
+function dataAttr( elem, key, data ) {
+ var name;
+
+ // If nothing was found internally, try to fetch any
+ // data from the HTML5 data-* attribute
+ if ( data === undefined && elem.nodeType === 1 ) {
+ name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase();
+ data = elem.getAttribute( name );
+
+ if ( typeof data === "string" ) {
+ try {
+ data = getData( data );
+ } catch ( e ) {}
+
+ // Make sure we set the data so it isn't changed later
+ dataUser.set( elem, key, data );
+ } else {
+ data = undefined;
+ }
+ }
+ return data;
+}
+
+jQuery.extend( {
+ hasData: function( elem ) {
+ return dataUser.hasData( elem ) || dataPriv.hasData( elem );
+ },
+
+ data: function( elem, name, data ) {
+ return dataUser.access( elem, name, data );
+ },
+
+ removeData: function( elem, name ) {
+ dataUser.remove( elem, name );
+ },
+
+ // TODO: Now that all calls to _data and _removeData have been replaced
+ // with direct calls to dataPriv methods, these can be deprecated.
+ _data: function( elem, name, data ) {
+ return dataPriv.access( elem, name, data );
+ },
+
+ _removeData: function( elem, name ) {
+ dataPriv.remove( elem, name );
+ }
+} );
+
+jQuery.fn.extend( {
+ data: function( key, value ) {
+ var i, name, data,
+ elem = this[ 0 ],
+ attrs = elem && elem.attributes;
+
+ // Gets all values
+ if ( key === undefined ) {
+ if ( this.length ) {
+ data = dataUser.get( elem );
+
+ if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {
+ i = attrs.length;
+ while ( i-- ) {
+
+ // Support: IE 11 only
+ // The attrs elements can be null (#14894)
+ if ( attrs[ i ] ) {
+ name = attrs[ i ].name;
+ if ( name.indexOf( "data-" ) === 0 ) {
+ name = camelCase( name.slice( 5 ) );
+ dataAttr( elem, name, data[ name ] );
+ }
+ }
+ }
+ dataPriv.set( elem, "hasDataAttrs", true );
+ }
+ }
+
+ return data;
+ }
+
+ // Sets multiple values
+ if ( typeof key === "object" ) {
+ return this.each( function() {
+ dataUser.set( this, key );
+ } );
+ }
+
+ return access( this, function( value ) {
+ var data;
+
+ // The calling jQuery object (element matches) is not empty
+ // (and therefore has an element appears at this[ 0 ]) and the
+ // `value` parameter was not undefined. An empty jQuery object
+ // will result in `undefined` for elem = this[ 0 ] which will
+ // throw an exception if an attempt to read a data cache is made.
+ if ( elem && value === undefined ) {
+
+ // Attempt to get data from the cache
+ // The key will always be camelCased in Data
+ data = dataUser.get( elem, key );
+ if ( data !== undefined ) {
+ return data;
+ }
+
+ // Attempt to "discover" the data in
+ // HTML5 custom data-* attrs
+ data = dataAttr( elem, key );
+ if ( data !== undefined ) {
+ return data;
+ }
+
+ // We tried really hard, but the data doesn't exist.
+ return;
+ }
+
+ // Set the data...
+ this.each( function() {
+
+ // We always store the camelCased key
+ dataUser.set( this, key, value );
+ } );
+ }, null, value, arguments.length > 1, null, true );
+ },
+
+ removeData: function( key ) {
+ return this.each( function() {
+ dataUser.remove( this, key );
+ } );
+ }
+} );
+
+
+jQuery.extend( {
+ queue: function( elem, type, data ) {
+ var queue;
+
+ if ( elem ) {
+ type = ( type || "fx" ) + "queue";
+ queue = dataPriv.get( elem, type );
+
+ // Speed up dequeue by getting out quickly if this is just a lookup
+ if ( data ) {
+ if ( !queue || Array.isArray( data ) ) {
+ queue = dataPriv.access( elem, type, jQuery.makeArray( data ) );
+ } else {
+ queue.push( data );
+ }
+ }
+ return queue || [];
+ }
+ },
+
+ dequeue: function( elem, type ) {
+ type = type || "fx";
+
+ var queue = jQuery.queue( elem, type ),
+ startLength = queue.length,
+ fn = queue.shift(),
+ hooks = jQuery._queueHooks( elem, type ),
+ next = function() {
+ jQuery.dequeue( elem, type );
+ };
+
+ // If the fx queue is dequeued, always remove the progress sentinel
+ if ( fn === "inprogress" ) {
+ fn = queue.shift();
+ startLength--;
+ }
+
+ if ( fn ) {
+
+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if ( type === "fx" ) {
+ queue.unshift( "inprogress" );
+ }
+
+ // Clear up the last queue stop function
+ delete hooks.stop;
+ fn.call( elem, next, hooks );
+ }
+
+ if ( !startLength && hooks ) {
+ hooks.empty.fire();
+ }
+ },
+
+ // Not public - generate a queueHooks object, or return the current one
+ _queueHooks: function( elem, type ) {
+ var key = type + "queueHooks";
+ return dataPriv.get( elem, key ) || dataPriv.access( elem, key, {
+ empty: jQuery.Callbacks( "once memory" ).add( function() {
+ dataPriv.remove( elem, [ type + "queue", key ] );
+ } )
+ } );
+ }
+} );
+
+jQuery.fn.extend( {
+ queue: function( type, data ) {
+ var setter = 2;
+
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ setter--;
+ }
+
+ if ( arguments.length < setter ) {
+ return jQuery.queue( this[ 0 ], type );
+ }
+
+ return data === undefined ?
+ this :
+ this.each( function() {
+ var queue = jQuery.queue( this, type, data );
+
+ // Ensure a hooks for this queue
+ jQuery._queueHooks( this, type );
+
+ if ( type === "fx" && queue[ 0 ] !== "inprogress" ) {
+ jQuery.dequeue( this, type );
+ }
+ } );
+ },
+ dequeue: function( type ) {
+ return this.each( function() {
+ jQuery.dequeue( this, type );
+ } );
+ },
+ clearQueue: function( type ) {
+ return this.queue( type || "fx", [] );
+ },
+
+ // Get a promise resolved when queues of a certain type
+ // are emptied (fx is the type by default)
+ promise: function( type, obj ) {
+ var tmp,
+ count = 1,
+ defer = jQuery.Deferred(),
+ elements = this,
+ i = this.length,
+ resolve = function() {
+ if ( !( --count ) ) {
+ defer.resolveWith( elements, [ elements ] );
+ }
+ };
+
+ if ( typeof type !== "string" ) {
+ obj = type;
+ type = undefined;
+ }
+ type = type || "fx";
+
+ while ( i-- ) {
+ tmp = dataPriv.get( elements[ i ], type + "queueHooks" );
+ if ( tmp && tmp.empty ) {
+ count++;
+ tmp.empty.add( resolve );
+ }
+ }
+ resolve();
+ return defer.promise( obj );
+ }
+} );
+var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source;
+
+var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" );
+
+
+var cssExpand = [ "Top", "Right", "Bottom", "Left" ];
+
+var documentElement = document.documentElement;
+
+
+
+ var isAttached = function( elem ) {
+ return jQuery.contains( elem.ownerDocument, elem );
+ },
+ composed = { composed: true };
+
+ // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only
+ // Check attachment across shadow DOM boundaries when possible (gh-3504)
+ // Support: iOS 10.0-10.2 only
+ // Early iOS 10 versions support `attachShadow` but not `getRootNode`,
+ // leading to errors. We need to check for `getRootNode`.
+ if ( documentElement.getRootNode ) {
+ isAttached = function( elem ) {
+ return jQuery.contains( elem.ownerDocument, elem ) ||
+ elem.getRootNode( composed ) === elem.ownerDocument;
+ };
+ }
+var isHiddenWithinTree = function( elem, el ) {
+
+ // isHiddenWithinTree might be called from jQuery#filter function;
+ // in that case, element will be second argument
+ elem = el || elem;
+
+ // Inline style trumps all
+ return elem.style.display === "none" ||
+ elem.style.display === "" &&
+
+ // Otherwise, check computed style
+ // Support: Firefox <=43 - 45
+ // Disconnected elements can have computed display: none, so first confirm that elem is
+ // in the document.
+ isAttached( elem ) &&
+
+ jQuery.css( elem, "display" ) === "none";
+ };
+
+
+
+function adjustCSS( elem, prop, valueParts, tween ) {
+ var adjusted, scale,
+ maxIterations = 20,
+ currentValue = tween ?
+ function() {
+ return tween.cur();
+ } :
+ function() {
+ return jQuery.css( elem, prop, "" );
+ },
+ initial = currentValue(),
+ unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
+
+ // Starting value computation is required for potential unit mismatches
+ initialInUnit = elem.nodeType &&
+ ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) &&
+ rcssNum.exec( jQuery.css( elem, prop ) );
+
+ if ( initialInUnit && initialInUnit[ 3 ] !== unit ) {
+
+ // Support: Firefox <=54
+ // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144)
+ initial = initial / 2;
+
+ // Trust units reported by jQuery.css
+ unit = unit || initialInUnit[ 3 ];
+
+ // Iteratively approximate from a nonzero starting point
+ initialInUnit = +initial || 1;
+
+ while ( maxIterations-- ) {
+
+ // Evaluate and update our best guess (doubling guesses that zero out).
+ // Finish if the scale equals or crosses 1 (making the old*new product non-positive).
+ jQuery.style( elem, prop, initialInUnit + unit );
+ if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) {
+ maxIterations = 0;
+ }
+ initialInUnit = initialInUnit / scale;
+
+ }
+
+ initialInUnit = initialInUnit * 2;
+ jQuery.style( elem, prop, initialInUnit + unit );
+
+ // Make sure we update the tween properties later on
+ valueParts = valueParts || [];
+ }
+
+ if ( valueParts ) {
+ initialInUnit = +initialInUnit || +initial || 0;
+
+ // Apply relative offset (+=/-=) if specified
+ adjusted = valueParts[ 1 ] ?
+ initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] :
+ +valueParts[ 2 ];
+ if ( tween ) {
+ tween.unit = unit;
+ tween.start = initialInUnit;
+ tween.end = adjusted;
+ }
+ }
+ return adjusted;
+}
+
+
+var defaultDisplayMap = {};
+
+function getDefaultDisplay( elem ) {
+ var temp,
+ doc = elem.ownerDocument,
+ nodeName = elem.nodeName,
+ display = defaultDisplayMap[ nodeName ];
+
+ if ( display ) {
+ return display;
+ }
+
+ temp = doc.body.appendChild( doc.createElement( nodeName ) );
+ display = jQuery.css( temp, "display" );
+
+ temp.parentNode.removeChild( temp );
+
+ if ( display === "none" ) {
+ display = "block";
+ }
+ defaultDisplayMap[ nodeName ] = display;
+
+ return display;
+}
+
+function showHide( elements, show ) {
+ var display, elem,
+ values = [],
+ index = 0,
+ length = elements.length;
+
+ // Determine new display value for elements that need to change
+ for ( ; index < length; index++ ) {
+ elem = elements[ index ];
+ if ( !elem.style ) {
+ continue;
+ }
+
+ display = elem.style.display;
+ if ( show ) {
+
+ // Since we force visibility upon cascade-hidden elements, an immediate (and slow)
+ // check is required in this first loop unless we have a nonempty display value (either
+ // inline or about-to-be-restored)
+ if ( display === "none" ) {
+ values[ index ] = dataPriv.get( elem, "display" ) || null;
+ if ( !values[ index ] ) {
+ elem.style.display = "";
+ }
+ }
+ if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) {
+ values[ index ] = getDefaultDisplay( elem );
+ }
+ } else {
+ if ( display !== "none" ) {
+ values[ index ] = "none";
+
+ // Remember what we're overwriting
+ dataPriv.set( elem, "display", display );
+ }
+ }
+ }
+
+ // Set the display of the elements in a second loop to avoid constant reflow
+ for ( index = 0; index < length; index++ ) {
+ if ( values[ index ] != null ) {
+ elements[ index ].style.display = values[ index ];
+ }
+ }
+
+ return elements;
+}
+
+jQuery.fn.extend( {
+ show: function() {
+ return showHide( this, true );
+ },
+ hide: function() {
+ return showHide( this );
+ },
+ toggle: function( state ) {
+ if ( typeof state === "boolean" ) {
+ return state ? this.show() : this.hide();
+ }
+
+ return this.each( function() {
+ if ( isHiddenWithinTree( this ) ) {
+ jQuery( this ).show();
+ } else {
+ jQuery( this ).hide();
+ }
+ } );
+ }
+} );
+var rcheckableType = ( /^(?:checkbox|radio)$/i );
+
+var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i );
+
+var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i );
+
+
+
+( function() {
+ var fragment = document.createDocumentFragment(),
+ div = fragment.appendChild( document.createElement( "div" ) ),
+ input = document.createElement( "input" );
+
+ // Support: Android 4.0 - 4.3 only
+ // Check state lost if the name is set (#11217)
+ // Support: Windows Web Apps (WWA)
+ // `name` and `type` must use .setAttribute for WWA (#14901)
+ input.setAttribute( "type", "radio" );
+ input.setAttribute( "checked", "checked" );
+ input.setAttribute( "name", "t" );
+
+ div.appendChild( input );
+
+ // Support: Android <=4.1 only
+ // Older WebKit doesn't clone checked state correctly in fragments
+ support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+ // Support: IE <=11 only
+ // Make sure textarea (and checkbox) defaultValue is properly cloned
+ div.innerHTML = "";
+ support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
+
+ // Support: IE <=9 only
+ // IE <=9 replaces tags with their contents when inserted outside of
+ // the select element.
+ div.innerHTML = " ";
+ support.option = !!div.lastChild;
+} )();
+
+
+// We have to close these tags to support XHTML (#13200)
+var wrapMap = {
+
+ // XHTML parsers do not magically insert elements in the
+ // same way that tag soup parsers do. So we cannot shorten
+ // this by omitting or other required elements.
+ thead: [ 1, "" ],
+ col: [ 2, "" ],
+ tr: [ 2, "" ],
+ td: [ 3, "" ],
+
+ _default: [ 0, "", "" ]
+};
+
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+// Support: IE <=9 only
+if ( !support.option ) {
+ wrapMap.optgroup = wrapMap.option = [ 1, "", " " ];
+}
+
+
+function getAll( context, tag ) {
+
+ // Support: IE <=9 - 11 only
+ // Use typeof to avoid zero-argument method invocation on host objects (#15151)
+ var ret;
+
+ if ( typeof context.getElementsByTagName !== "undefined" ) {
+ ret = context.getElementsByTagName( tag || "*" );
+
+ } else if ( typeof context.querySelectorAll !== "undefined" ) {
+ ret = context.querySelectorAll( tag || "*" );
+
+ } else {
+ ret = [];
+ }
+
+ if ( tag === undefined || tag && nodeName( context, tag ) ) {
+ return jQuery.merge( [ context ], ret );
+ }
+
+ return ret;
+}
+
+
+// Mark scripts as having already been evaluated
+function setGlobalEval( elems, refElements ) {
+ var i = 0,
+ l = elems.length;
+
+ for ( ; i < l; i++ ) {
+ dataPriv.set(
+ elems[ i ],
+ "globalEval",
+ !refElements || dataPriv.get( refElements[ i ], "globalEval" )
+ );
+ }
+}
+
+
+var rhtml = /<|?\w+;/;
+
+function buildFragment( elems, context, scripts, selection, ignored ) {
+ var elem, tmp, tag, wrap, attached, j,
+ fragment = context.createDocumentFragment(),
+ nodes = [],
+ i = 0,
+ l = elems.length;
+
+ for ( ; i < l; i++ ) {
+ elem = elems[ i ];
+
+ if ( elem || elem === 0 ) {
+
+ // Add nodes directly
+ if ( toType( elem ) === "object" ) {
+
+ // Support: Android <=4.0 only, PhantomJS 1 only
+ // push.apply(_, arraylike) throws on ancient WebKit
+ jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
+
+ // Convert non-html into a text node
+ } else if ( !rhtml.test( elem ) ) {
+ nodes.push( context.createTextNode( elem ) );
+
+ // Convert html into DOM nodes
+ } else {
+ tmp = tmp || fragment.appendChild( context.createElement( "div" ) );
+
+ // Deserialize a standard representation
+ tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase();
+ wrap = wrapMap[ tag ] || wrapMap._default;
+ tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ];
+
+ // Descend through wrappers to the right content
+ j = wrap[ 0 ];
+ while ( j-- ) {
+ tmp = tmp.lastChild;
+ }
+
+ // Support: Android <=4.0 only, PhantomJS 1 only
+ // push.apply(_, arraylike) throws on ancient WebKit
+ jQuery.merge( nodes, tmp.childNodes );
+
+ // Remember the top-level container
+ tmp = fragment.firstChild;
+
+ // Ensure the created nodes are orphaned (#12392)
+ tmp.textContent = "";
+ }
+ }
+ }
+
+ // Remove wrapper from fragment
+ fragment.textContent = "";
+
+ i = 0;
+ while ( ( elem = nodes[ i++ ] ) ) {
+
+ // Skip elements already in the context collection (trac-4087)
+ if ( selection && jQuery.inArray( elem, selection ) > -1 ) {
+ if ( ignored ) {
+ ignored.push( elem );
+ }
+ continue;
+ }
+
+ attached = isAttached( elem );
+
+ // Append to fragment
+ tmp = getAll( fragment.appendChild( elem ), "script" );
+
+ // Preserve script evaluation history
+ if ( attached ) {
+ setGlobalEval( tmp );
+ }
+
+ // Capture executables
+ if ( scripts ) {
+ j = 0;
+ while ( ( elem = tmp[ j++ ] ) ) {
+ if ( rscriptType.test( elem.type || "" ) ) {
+ scripts.push( elem );
+ }
+ }
+ }
+ }
+
+ return fragment;
+}
+
+
+var
+ rkeyEvent = /^key/,
+ rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/,
+ rtypenamespace = /^([^.]*)(?:\.(.+)|)/;
+
+function returnTrue() {
+ return true;
+}
+
+function returnFalse() {
+ return false;
+}
+
+// Support: IE <=9 - 11+
+// focus() and blur() are asynchronous, except when they are no-op.
+// So expect focus to be synchronous when the element is already active,
+// and blur to be synchronous when the element is not already active.
+// (focus and blur are always synchronous in other supported browsers,
+// this just defines when we can count on it).
+function expectSync( elem, type ) {
+ return ( elem === safeActiveElement() ) === ( type === "focus" );
+}
+
+// Support: IE <=9 only
+// Accessing document.activeElement can throw unexpectedly
+// https://bugs.jquery.com/ticket/13393
+function safeActiveElement() {
+ try {
+ return document.activeElement;
+ } catch ( err ) { }
+}
+
+function on( elem, types, selector, data, fn, one ) {
+ var origFn, type;
+
+ // Types can be a map of types/handlers
+ if ( typeof types === "object" ) {
+
+ // ( types-Object, selector, data )
+ if ( typeof selector !== "string" ) {
+
+ // ( types-Object, data )
+ data = data || selector;
+ selector = undefined;
+ }
+ for ( type in types ) {
+ on( elem, type, selector, data, types[ type ], one );
+ }
+ return elem;
+ }
+
+ if ( data == null && fn == null ) {
+
+ // ( types, fn )
+ fn = selector;
+ data = selector = undefined;
+ } else if ( fn == null ) {
+ if ( typeof selector === "string" ) {
+
+ // ( types, selector, fn )
+ fn = data;
+ data = undefined;
+ } else {
+
+ // ( types, data, fn )
+ fn = data;
+ data = selector;
+ selector = undefined;
+ }
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ } else if ( !fn ) {
+ return elem;
+ }
+
+ if ( one === 1 ) {
+ origFn = fn;
+ fn = function( event ) {
+
+ // Can use an empty set, since event contains the info
+ jQuery().off( event );
+ return origFn.apply( this, arguments );
+ };
+
+ // Use same guid so caller can remove using origFn
+ fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+ }
+ return elem.each( function() {
+ jQuery.event.add( this, types, fn, data, selector );
+ } );
+}
+
+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
+
+ global: {},
+
+ add: function( elem, types, handler, data, selector ) {
+
+ var handleObjIn, eventHandle, tmp,
+ events, t, handleObj,
+ special, handlers, type, namespaces, origType,
+ elemData = dataPriv.get( elem );
+
+ // Only attach events to objects that accept data
+ if ( !acceptData( elem ) ) {
+ return;
+ }
+
+ // Caller can pass in an object of custom data in lieu of the handler
+ if ( handler.handler ) {
+ handleObjIn = handler;
+ handler = handleObjIn.handler;
+ selector = handleObjIn.selector;
+ }
+
+ // Ensure that invalid selectors throw exceptions at attach time
+ // Evaluate against documentElement in case elem is a non-element node (e.g., document)
+ if ( selector ) {
+ jQuery.find.matchesSelector( documentElement, selector );
+ }
+
+ // Make sure that the handler has a unique ID, used to find/remove it later
+ if ( !handler.guid ) {
+ handler.guid = jQuery.guid++;
+ }
+
+ // Init the element's event structure and main handler, if this is the first
+ if ( !( events = elemData.events ) ) {
+ events = elemData.events = Object.create( null );
+ }
+ if ( !( eventHandle = elemData.handle ) ) {
+ eventHandle = elemData.handle = function( e ) {
+
+ // Discard the second event of a jQuery.event.trigger() and
+ // when an event is called after a page has unloaded
+ return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
+ jQuery.event.dispatch.apply( elem, arguments ) : undefined;
+ };
+ }
+
+ // Handle multiple events separated by a space
+ types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
+ t = types.length;
+ while ( t-- ) {
+ tmp = rtypenamespace.exec( types[ t ] ) || [];
+ type = origType = tmp[ 1 ];
+ namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
+
+ // There *must* be a type, no attaching namespace-only handlers
+ if ( !type ) {
+ continue;
+ }
+
+ // If event changes its type, use the special event handlers for the changed type
+ special = jQuery.event.special[ type ] || {};
+
+ // If selector defined, determine special event api type, otherwise given type
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+
+ // Update special based on newly reset type
+ special = jQuery.event.special[ type ] || {};
+
+ // handleObj is passed to all event handlers
+ handleObj = jQuery.extend( {
+ type: type,
+ origType: origType,
+ data: data,
+ handler: handler,
+ guid: handler.guid,
+ selector: selector,
+ needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
+ namespace: namespaces.join( "." )
+ }, handleObjIn );
+
+ // Init the event handler queue if we're the first
+ if ( !( handlers = events[ type ] ) ) {
+ handlers = events[ type ] = [];
+ handlers.delegateCount = 0;
+
+ // Only use addEventListener if the special events handler returns false
+ if ( !special.setup ||
+ special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, eventHandle );
+ }
+ }
+ }
+
+ if ( special.add ) {
+ special.add.call( elem, handleObj );
+
+ if ( !handleObj.handler.guid ) {
+ handleObj.handler.guid = handler.guid;
+ }
+ }
+
+ // Add to the element's handler list, delegates in front
+ if ( selector ) {
+ handlers.splice( handlers.delegateCount++, 0, handleObj );
+ } else {
+ handlers.push( handleObj );
+ }
+
+ // Keep track of which events have ever been used, for event optimization
+ jQuery.event.global[ type ] = true;
+ }
+
+ },
+
+ // Detach an event or set of events from an element
+ remove: function( elem, types, handler, selector, mappedTypes ) {
+
+ var j, origCount, tmp,
+ events, t, handleObj,
+ special, handlers, type, namespaces, origType,
+ elemData = dataPriv.hasData( elem ) && dataPriv.get( elem );
+
+ if ( !elemData || !( events = elemData.events ) ) {
+ return;
+ }
+
+ // Once for each type.namespace in types; type may be omitted
+ types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
+ t = types.length;
+ while ( t-- ) {
+ tmp = rtypenamespace.exec( types[ t ] ) || [];
+ type = origType = tmp[ 1 ];
+ namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
+
+ // Unbind all events (on this namespace, if provided) for the element
+ if ( !type ) {
+ for ( type in events ) {
+ jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+ }
+ continue;
+ }
+
+ special = jQuery.event.special[ type ] || {};
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+ handlers = events[ type ] || [];
+ tmp = tmp[ 2 ] &&
+ new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" );
+
+ // Remove matching events
+ origCount = j = handlers.length;
+ while ( j-- ) {
+ handleObj = handlers[ j ];
+
+ if ( ( mappedTypes || origType === handleObj.origType ) &&
+ ( !handler || handler.guid === handleObj.guid ) &&
+ ( !tmp || tmp.test( handleObj.namespace ) ) &&
+ ( !selector || selector === handleObj.selector ||
+ selector === "**" && handleObj.selector ) ) {
+ handlers.splice( j, 1 );
+
+ if ( handleObj.selector ) {
+ handlers.delegateCount--;
+ }
+ if ( special.remove ) {
+ special.remove.call( elem, handleObj );
+ }
+ }
+ }
+
+ // Remove generic event handler if we removed something and no more handlers exist
+ // (avoids potential for endless recursion during removal of special event handlers)
+ if ( origCount && !handlers.length ) {
+ if ( !special.teardown ||
+ special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
+
+ jQuery.removeEvent( elem, type, elemData.handle );
+ }
+
+ delete events[ type ];
+ }
+ }
+
+ // Remove data and the expando if it's no longer used
+ if ( jQuery.isEmptyObject( events ) ) {
+ dataPriv.remove( elem, "handle events" );
+ }
+ },
+
+ dispatch: function( nativeEvent ) {
+
+ var i, j, ret, matched, handleObj, handlerQueue,
+ args = new Array( arguments.length ),
+
+ // Make a writable jQuery.Event from the native event object
+ event = jQuery.event.fix( nativeEvent ),
+
+ handlers = (
+ dataPriv.get( this, "events" ) || Object.create( null )
+ )[ event.type ] || [],
+ special = jQuery.event.special[ event.type ] || {};
+
+ // Use the fix-ed jQuery.Event rather than the (read-only) native event
+ args[ 0 ] = event;
+
+ for ( i = 1; i < arguments.length; i++ ) {
+ args[ i ] = arguments[ i ];
+ }
+
+ event.delegateTarget = this;
+
+ // Call the preDispatch hook for the mapped type, and let it bail if desired
+ if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+ return;
+ }
+
+ // Determine handlers
+ handlerQueue = jQuery.event.handlers.call( this, event, handlers );
+
+ // Run delegates first; they may want to stop propagation beneath us
+ i = 0;
+ while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {
+ event.currentTarget = matched.elem;
+
+ j = 0;
+ while ( ( handleObj = matched.handlers[ j++ ] ) &&
+ !event.isImmediatePropagationStopped() ) {
+
+ // If the event is namespaced, then each handler is only invoked if it is
+ // specially universal or its namespaces are a superset of the event's.
+ if ( !event.rnamespace || handleObj.namespace === false ||
+ event.rnamespace.test( handleObj.namespace ) ) {
+
+ event.handleObj = handleObj;
+ event.data = handleObj.data;
+
+ ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||
+ handleObj.handler ).apply( matched.elem, args );
+
+ if ( ret !== undefined ) {
+ if ( ( event.result = ret ) === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+ }
+ }
+
+ // Call the postDispatch hook for the mapped type
+ if ( special.postDispatch ) {
+ special.postDispatch.call( this, event );
+ }
+
+ return event.result;
+ },
+
+ handlers: function( event, handlers ) {
+ var i, handleObj, sel, matchedHandlers, matchedSelectors,
+ handlerQueue = [],
+ delegateCount = handlers.delegateCount,
+ cur = event.target;
+
+ // Find delegate handlers
+ if ( delegateCount &&
+
+ // Support: IE <=9
+ // Black-hole SVG instance trees (trac-13180)
+ cur.nodeType &&
+
+ // Support: Firefox <=42
+ // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861)
+ // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click
+ // Support: IE 11 only
+ // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343)
+ !( event.type === "click" && event.button >= 1 ) ) {
+
+ for ( ; cur !== this; cur = cur.parentNode || this ) {
+
+ // Don't check non-elements (#13208)
+ // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
+ if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) {
+ matchedHandlers = [];
+ matchedSelectors = {};
+ for ( i = 0; i < delegateCount; i++ ) {
+ handleObj = handlers[ i ];
+
+ // Don't conflict with Object.prototype properties (#13203)
+ sel = handleObj.selector + " ";
+
+ if ( matchedSelectors[ sel ] === undefined ) {
+ matchedSelectors[ sel ] = handleObj.needsContext ?
+ jQuery( sel, this ).index( cur ) > -1 :
+ jQuery.find( sel, this, null, [ cur ] ).length;
+ }
+ if ( matchedSelectors[ sel ] ) {
+ matchedHandlers.push( handleObj );
+ }
+ }
+ if ( matchedHandlers.length ) {
+ handlerQueue.push( { elem: cur, handlers: matchedHandlers } );
+ }
+ }
+ }
+ }
+
+ // Add the remaining (directly-bound) handlers
+ cur = this;
+ if ( delegateCount < handlers.length ) {
+ handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } );
+ }
+
+ return handlerQueue;
+ },
+
+ addProp: function( name, hook ) {
+ Object.defineProperty( jQuery.Event.prototype, name, {
+ enumerable: true,
+ configurable: true,
+
+ get: isFunction( hook ) ?
+ function() {
+ if ( this.originalEvent ) {
+ return hook( this.originalEvent );
+ }
+ } :
+ function() {
+ if ( this.originalEvent ) {
+ return this.originalEvent[ name ];
+ }
+ },
+
+ set: function( value ) {
+ Object.defineProperty( this, name, {
+ enumerable: true,
+ configurable: true,
+ writable: true,
+ value: value
+ } );
+ }
+ } );
+ },
+
+ fix: function( originalEvent ) {
+ return originalEvent[ jQuery.expando ] ?
+ originalEvent :
+ new jQuery.Event( originalEvent );
+ },
+
+ special: {
+ load: {
+
+ // Prevent triggered image.load events from bubbling to window.load
+ noBubble: true
+ },
+ click: {
+
+ // Utilize native event to ensure correct state for checkable inputs
+ setup: function( data ) {
+
+ // For mutual compressibility with _default, replace `this` access with a local var.
+ // `|| data` is dead code meant only to preserve the variable through minification.
+ var el = this || data;
+
+ // Claim the first handler
+ if ( rcheckableType.test( el.type ) &&
+ el.click && nodeName( el, "input" ) ) {
+
+ // dataPriv.set( el, "click", ... )
+ leverageNative( el, "click", returnTrue );
+ }
+
+ // Return false to allow normal processing in the caller
+ return false;
+ },
+ trigger: function( data ) {
+
+ // For mutual compressibility with _default, replace `this` access with a local var.
+ // `|| data` is dead code meant only to preserve the variable through minification.
+ var el = this || data;
+
+ // Force setup before triggering a click
+ if ( rcheckableType.test( el.type ) &&
+ el.click && nodeName( el, "input" ) ) {
+
+ leverageNative( el, "click" );
+ }
+
+ // Return non-false to allow normal event-path propagation
+ return true;
+ },
+
+ // For cross-browser consistency, suppress native .click() on links
+ // Also prevent it if we're currently inside a leveraged native-event stack
+ _default: function( event ) {
+ var target = event.target;
+ return rcheckableType.test( target.type ) &&
+ target.click && nodeName( target, "input" ) &&
+ dataPriv.get( target, "click" ) ||
+ nodeName( target, "a" );
+ }
+ },
+
+ beforeunload: {
+ postDispatch: function( event ) {
+
+ // Support: Firefox 20+
+ // Firefox doesn't alert if the returnValue field is not set.
+ if ( event.result !== undefined && event.originalEvent ) {
+ event.originalEvent.returnValue = event.result;
+ }
+ }
+ }
+ }
+};
+
+// Ensure the presence of an event listener that handles manually-triggered
+// synthetic events by interrupting progress until reinvoked in response to
+// *native* events that it fires directly, ensuring that state changes have
+// already occurred before other listeners are invoked.
+function leverageNative( el, type, expectSync ) {
+
+ // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add
+ if ( !expectSync ) {
+ if ( dataPriv.get( el, type ) === undefined ) {
+ jQuery.event.add( el, type, returnTrue );
+ }
+ return;
+ }
+
+ // Register the controller as a special universal handler for all event namespaces
+ dataPriv.set( el, type, false );
+ jQuery.event.add( el, type, {
+ namespace: false,
+ handler: function( event ) {
+ var notAsync, result,
+ saved = dataPriv.get( this, type );
+
+ if ( ( event.isTrigger & 1 ) && this[ type ] ) {
+
+ // Interrupt processing of the outer synthetic .trigger()ed event
+ // Saved data should be false in such cases, but might be a leftover capture object
+ // from an async native handler (gh-4350)
+ if ( !saved.length ) {
+
+ // Store arguments for use when handling the inner native event
+ // There will always be at least one argument (an event object), so this array
+ // will not be confused with a leftover capture object.
+ saved = slice.call( arguments );
+ dataPriv.set( this, type, saved );
+
+ // Trigger the native event and capture its result
+ // Support: IE <=9 - 11+
+ // focus() and blur() are asynchronous
+ notAsync = expectSync( this, type );
+ this[ type ]();
+ result = dataPriv.get( this, type );
+ if ( saved !== result || notAsync ) {
+ dataPriv.set( this, type, false );
+ } else {
+ result = {};
+ }
+ if ( saved !== result ) {
+
+ // Cancel the outer synthetic event
+ event.stopImmediatePropagation();
+ event.preventDefault();
+ return result.value;
+ }
+
+ // If this is an inner synthetic event for an event with a bubbling surrogate
+ // (focus or blur), assume that the surrogate already propagated from triggering the
+ // native event and prevent that from happening again here.
+ // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the
+ // bubbling surrogate propagates *after* the non-bubbling base), but that seems
+ // less bad than duplication.
+ } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) {
+ event.stopPropagation();
+ }
+
+ // If this is a native event triggered above, everything is now in order
+ // Fire an inner synthetic event with the original arguments
+ } else if ( saved.length ) {
+
+ // ...and capture the result
+ dataPriv.set( this, type, {
+ value: jQuery.event.trigger(
+
+ // Support: IE <=9 - 11+
+ // Extend with the prototype to reset the above stopImmediatePropagation()
+ jQuery.extend( saved[ 0 ], jQuery.Event.prototype ),
+ saved.slice( 1 ),
+ this
+ )
+ } );
+
+ // Abort handling of the native event
+ event.stopImmediatePropagation();
+ }
+ }
+ } );
+}
+
+jQuery.removeEvent = function( elem, type, handle ) {
+
+ // This "if" is needed for plain objects
+ if ( elem.removeEventListener ) {
+ elem.removeEventListener( type, handle );
+ }
+};
+
+jQuery.Event = function( src, props ) {
+
+ // Allow instantiation without the 'new' keyword
+ if ( !( this instanceof jQuery.Event ) ) {
+ return new jQuery.Event( src, props );
+ }
+
+ // Event object
+ if ( src && src.type ) {
+ this.originalEvent = src;
+ this.type = src.type;
+
+ // Events bubbling up the document may have been marked as prevented
+ // by a handler lower down the tree; reflect the correct value.
+ this.isDefaultPrevented = src.defaultPrevented ||
+ src.defaultPrevented === undefined &&
+
+ // Support: Android <=2.3 only
+ src.returnValue === false ?
+ returnTrue :
+ returnFalse;
+
+ // Create target properties
+ // Support: Safari <=6 - 7 only
+ // Target should not be a text node (#504, #13143)
+ this.target = ( src.target && src.target.nodeType === 3 ) ?
+ src.target.parentNode :
+ src.target;
+
+ this.currentTarget = src.currentTarget;
+ this.relatedTarget = src.relatedTarget;
+
+ // Event type
+ } else {
+ this.type = src;
+ }
+
+ // Put explicitly provided properties onto the event object
+ if ( props ) {
+ jQuery.extend( this, props );
+ }
+
+ // Create a timestamp if incoming event doesn't have one
+ this.timeStamp = src && src.timeStamp || Date.now();
+
+ // Mark it as fixed
+ this[ jQuery.expando ] = true;
+};
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+ constructor: jQuery.Event,
+ isDefaultPrevented: returnFalse,
+ isPropagationStopped: returnFalse,
+ isImmediatePropagationStopped: returnFalse,
+ isSimulated: false,
+
+ preventDefault: function() {
+ var e = this.originalEvent;
+
+ this.isDefaultPrevented = returnTrue;
+
+ if ( e && !this.isSimulated ) {
+ e.preventDefault();
+ }
+ },
+ stopPropagation: function() {
+ var e = this.originalEvent;
+
+ this.isPropagationStopped = returnTrue;
+
+ if ( e && !this.isSimulated ) {
+ e.stopPropagation();
+ }
+ },
+ stopImmediatePropagation: function() {
+ var e = this.originalEvent;
+
+ this.isImmediatePropagationStopped = returnTrue;
+
+ if ( e && !this.isSimulated ) {
+ e.stopImmediatePropagation();
+ }
+
+ this.stopPropagation();
+ }
+};
+
+// Includes all common event props including KeyEvent and MouseEvent specific props
+jQuery.each( {
+ altKey: true,
+ bubbles: true,
+ cancelable: true,
+ changedTouches: true,
+ ctrlKey: true,
+ detail: true,
+ eventPhase: true,
+ metaKey: true,
+ pageX: true,
+ pageY: true,
+ shiftKey: true,
+ view: true,
+ "char": true,
+ code: true,
+ charCode: true,
+ key: true,
+ keyCode: true,
+ button: true,
+ buttons: true,
+ clientX: true,
+ clientY: true,
+ offsetX: true,
+ offsetY: true,
+ pointerId: true,
+ pointerType: true,
+ screenX: true,
+ screenY: true,
+ targetTouches: true,
+ toElement: true,
+ touches: true,
+
+ which: function( event ) {
+ var button = event.button;
+
+ // Add which for key events
+ if ( event.which == null && rkeyEvent.test( event.type ) ) {
+ return event.charCode != null ? event.charCode : event.keyCode;
+ }
+
+ // Add which for click: 1 === left; 2 === middle; 3 === right
+ if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) {
+ if ( button & 1 ) {
+ return 1;
+ }
+
+ if ( button & 2 ) {
+ return 3;
+ }
+
+ if ( button & 4 ) {
+ return 2;
+ }
+
+ return 0;
+ }
+
+ return event.which;
+ }
+}, jQuery.event.addProp );
+
+jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) {
+ jQuery.event.special[ type ] = {
+
+ // Utilize native event if possible so blur/focus sequence is correct
+ setup: function() {
+
+ // Claim the first handler
+ // dataPriv.set( this, "focus", ... )
+ // dataPriv.set( this, "blur", ... )
+ leverageNative( this, type, expectSync );
+
+ // Return false to allow normal processing in the caller
+ return false;
+ },
+ trigger: function() {
+
+ // Force setup before trigger
+ leverageNative( this, type );
+
+ // Return non-false to allow normal event-path propagation
+ return true;
+ },
+
+ delegateType: delegateType
+ };
+} );
+
+// Create mouseenter/leave events using mouseover/out and event-time checks
+// so that event delegation works in jQuery.
+// Do the same for pointerenter/pointerleave and pointerover/pointerout
+//
+// Support: Safari 7 only
+// Safari sends mouseenter too often; see:
+// https://bugs.chromium.org/p/chromium/issues/detail?id=470258
+// for the description of the bug (it existed in older Chrome versions as well).
+jQuery.each( {
+ mouseenter: "mouseover",
+ mouseleave: "mouseout",
+ pointerenter: "pointerover",
+ pointerleave: "pointerout"
+}, function( orig, fix ) {
+ jQuery.event.special[ orig ] = {
+ delegateType: fix,
+ bindType: fix,
+
+ handle: function( event ) {
+ var ret,
+ target = this,
+ related = event.relatedTarget,
+ handleObj = event.handleObj;
+
+ // For mouseenter/leave call the handler if related is outside the target.
+ // NB: No relatedTarget if the mouse left/entered the browser window
+ if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) {
+ event.type = handleObj.origType;
+ ret = handleObj.handler.apply( this, arguments );
+ event.type = fix;
+ }
+ return ret;
+ }
+ };
+} );
+
+jQuery.fn.extend( {
+
+ on: function( types, selector, data, fn ) {
+ return on( this, types, selector, data, fn );
+ },
+ one: function( types, selector, data, fn ) {
+ return on( this, types, selector, data, fn, 1 );
+ },
+ off: function( types, selector, fn ) {
+ var handleObj, type;
+ if ( types && types.preventDefault && types.handleObj ) {
+
+ // ( event ) dispatched jQuery.Event
+ handleObj = types.handleObj;
+ jQuery( types.delegateTarget ).off(
+ handleObj.namespace ?
+ handleObj.origType + "." + handleObj.namespace :
+ handleObj.origType,
+ handleObj.selector,
+ handleObj.handler
+ );
+ return this;
+ }
+ if ( typeof types === "object" ) {
+
+ // ( types-object [, selector] )
+ for ( type in types ) {
+ this.off( type, selector, types[ type ] );
+ }
+ return this;
+ }
+ if ( selector === false || typeof selector === "function" ) {
+
+ // ( types [, fn] )
+ fn = selector;
+ selector = undefined;
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ }
+ return this.each( function() {
+ jQuery.event.remove( this, types, fn, selector );
+ } );
+ }
+} );
+
+
+var
+
+ // Support: IE <=10 - 11, Edge 12 - 13 only
+ // In IE/Edge using regex groups here causes severe slowdowns.
+ // See https://connect.microsoft.com/IE/feedback/details/1736512/
+ rnoInnerhtml = /
+{% endmacro %}
\ No newline at end of file
diff --git a/_build/html/genindex.html b/_build/html/genindex.html
new file mode 100644
index 0000000..653596b
--- /dev/null
+++ b/_build/html/genindex.html
@@ -0,0 +1,201 @@
+
+
+
+
+
+
+
+ Index — CPSC 304 Introduction to Relational Databases
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/_build/html/index.html b/_build/html/index.html
new file mode 100644
index 0000000..3157386
--- /dev/null
+++ b/_build/html/index.html
@@ -0,0 +1 @@
+
diff --git a/_build/html/intro.html b/_build/html/intro.html
new file mode 100644
index 0000000..f5717fd
--- /dev/null
+++ b/_build/html/intro.html
@@ -0,0 +1,255 @@
+
+
+
+
+
+
+
+ CPSC 304 Introduction to Relational Databases — CPSC 304 Introduction to Relational Databases
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
CPSC 304 Introduction to Relational Databases
+
Under construction
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/_build/html/lectures/lecture2.html b/_build/html/lectures/lecture2.html
new file mode 100644
index 0000000..95fc6d9
--- /dev/null
+++ b/_build/html/lectures/lecture2.html
@@ -0,0 +1,4742 @@
+
+
+
+
+
+
+
+ 1. Lecture 2: SQL - Class 2 — CPSC 304 Introduction to Relational Databases
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1. Lecture 2: SQL - Class 2
+
Gittu George, February 24 2022
+
+
+
1.2. Warm-up ☕️
+
+
1.2.1. Refresher!! Take some time and think if you know…
+
+What are databases?
+Where is it commonly used?
+How to set up a database?
+General structure of a database
+Various ways of interacting with the database, using
+
+
+To create a table and various data types
+Integrity constraints such as primary key and foreign key
+Basic SQL commands like SELECT, FROM, and WHERE
+
+
In this class, we will be using the tables that we created in lecture 1. Here are the scripts if you want to recreate the classroom exercise table.
+
+
DROP TABLE IF EXISTS students , courses ;
+
+CREATE TABLE IF NOT EXISTS students (
+student_no integer PRIMARY KEY ,
+stud_name text ,
+age integer ,
+major text );
+
+CREATE TABLE IF NOT EXISTS courses (
+id SERIAL PRIMARY KEY ,
+student_no integer ,
+course_name text ,
+course_year integer ,
+course_percentage float );
+
+INSERT INTO students ( student_no , stud_name , age , major ) VALUES ( 111 , 'Catherine' , 23 , 'MBAN' ),
+( 222 , 'Tiff' , 28 , 'MDS' ),
+( 333 , 'John' , 23 , 'MBAN' ),
+( 444 , 'Amir' , 28 , 'MBAN' ),
+( 555 , 'Gittu' , 20 , 'MDS' ),
+( 666 , 'Isha' , 30 , 'MBAN' ),
+( 777 , 'Heidy' , 30 , 'MBAN' ),
+( 888 , 'Angela' , 27 , 'MBAN' ),
+( 999 , 'Jason' , 30 , 'MBAN' );
+
+INSERT INTO courses ( student_no , course_name , course_year , course_percentage ) VALUES ( 111 , 'TPCS INFO TECH' , 2019 , 88 ),
+( 111 , 'Data Visualization' , 2019 , 88 ),
+( 222 , 'Health and Technology' , 2020 , 80 ),
+( 222 , 'Web and Cloud Computing' , 2019 , 91 ),
+( 222 , 'Spark Programming' , 2019 , 90 ),
+( 333 , 'Parallel Computing' , 2019 , 90 ),
+( 444 , 'Large Scale Infrastructures' , 2019 , 83 ),
+( 444 , 'TPCS INFO TECH' , 2019 , 98 ),
+( 555 , 'TPCS INFO TECH' , 2020 , 78 ),
+( 555 , 'Health and Technology' , 2020 , 81 ),
+( 666 , 'Data Visualization' , 2021 , 85 ),
+( 666 , 'Parallel Computing' , 2019 , 87 ),
+( 888 , 'Spark Programming' , 2019 , 93 ),
+( 999 , 'TPCS INFO TECH' , 2019 , 98 ),
+( 999 , 'Data Visualization' , 2019 , 87 ),
+( 1000 , 'C programming' , 2019 , 87 ),
+( 1111 , 'Introduction to Genomics' , 2019 , 87 );
+
+
+
+
First of all let’s load SQL to work on this notebook.
+
+
+
+
+
+
'Connected: postgres@postgres'
+
+
+
+
+
+
+
1.2.2. Refresher exercise
+
Question 1: In this table eg
+
+
+firstname
+countryfrom
+continent
+gender
+age
+
+
+
+matt
+usa
+north america
+M
+23
+
+jenn
+uk
+europe
+F
+35
+
+guy
+france
+europe
+M
+25
+
+james
+china
+asia
+M
+29
+
+lida
+india
+asia
+F
+56
+
+linda
+canada
+north america
+F
+18
+
+sofia
+germany
+europe
+F
+22
+
+george
+india
+asia
+M
+29
+
+
+
+
What will be returned from following SQL
+
select firstname , gender from eg where continent = 'asia' AND age = 29
+
+
+
A) george,M
+
B) george,M,asia,29
+
C) george,india,asia, M,29
+
D) M,george
+
E) M,james
+
+
Answer: D
+
+
Important
+
Remember the order of the columns returned will be based on the order of columns that we specify within the select statement.
+
+
+
Question 2: Consider a scenario where you want to perform analysis on a table (peopletable) with 100 columns (including personname,age,gender,origin…etc.) that define a person. You are interested in seeing the name of individuals older than 90. Which SQL query is more appropriate in this situation?
+
A) select * from peopletable where age > 90;
+
B) select personname,age from peopletable where age > 90;
+
B) select personname,age,gender,origin from peopletable where age < 90;
+
D) select personname from peopletable where age > 90;
+
+
Answer: D
+
+
Important
+
Just bring the columns and rows that are needed. Even though SELECT
and WHERE
are very basic SQL commands, it’s crucial when you are dealing with a large table
+
+
+
+
+
+
1.3. Learning objectives
+
+
+
+
1.4. Lap 1 🥛
+
+
1.4.1. DISTINCT
+
The DISTINCT statement is used only to return distinct elements from a table.
+
Syntax:
+
SELECT DISTINCT column1 , column2 , ... columnN
+FROM tablename ;
+
+
+
DISTINCT
is applied to all columns that follows the DISTINCT
keyword. Say for eg if we give DISTINCT column1, column2
then the combination of values in both column1
and column2
columns will be used for returning the unique combination (or removing the duplicate elements).
+
+
+
+
* postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres
+9 rows affected.
+
+
+
+
+
+ course_name
+
+
+
+
+ Data Visualization
+
+
+ Introduction to Genomics
+
+
+ Large Scale Infrastructures
+
+
+ Spark Programming
+
+
+ Web and Cloud Computing
+
+
+ Health and Technology
+
+
+ C programming
+
+
+ TPCS INFO TECH
+
+
+ Parallel Computing
+
+
+
+
+
+
+
+
* postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres
+11 rows affected.
+
+
+
+
+
+ course_name
+ course_year
+
+
+
+
+ Parallel Computing
+ 2019
+
+
+ C programming
+ 2019
+
+
+ TPCS INFO TECH
+ 2019
+
+
+ Spark Programming
+ 2019
+
+
+ Web and Cloud Computing
+ 2019
+
+
+ Health and Technology
+ 2020
+
+
+ Introduction to Genomics
+ 2019
+
+
+ Large Scale Infrastructures
+ 2019
+
+
+ TPCS INFO TECH
+ 2020
+
+
+ Data Visualization
+ 2021
+
+
+ Data Visualization
+ 2019
+
+
+
+
+
+
+
1.4.2. ORDER BY
+
ORDER BY
statement sorts the results returned by SELECT
based on a sort expression.
+
Syntax
+
SELECT column1 , column2 ... columnN
+FROM table_name
+ORDER BY column1 [ ASC | DESC ], column2 [ ASC | DESC ] .... columnN [ ASC | DESC ];
+
+
+
+
Note
+
By default, it will sort in ASC. So you can choose not to give ASC.
+
+
+
+
+
* postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres
+
+
+
+
+
+
+ id
+ student_no
+ course_name
+ course_year
+ course_percentage
+
+
+
+
+ 1
+ 111
+ TPCS INFO TECH
+ 2019
+ 88.0
+
+
+ 2
+ 111
+ Data Visualization
+ 2019
+ 88.0
+
+
+ 14
+ 999
+ TPCS INFO TECH
+ 2019
+ 98.0
+
+
+ 15
+ 999
+ Data Visualization
+ 2019
+ 87.0
+
+
+ 16
+ 1000
+ C programming
+ 2019
+ 87.0
+
+
+ 17
+ 1111
+ Introduction to Genomics
+ 2019
+ 87.0
+
+
+ 4
+ 222
+ Web and Cloud Computing
+ 2019
+ 91.0
+
+
+ 5
+ 222
+ Spark Programming
+ 2019
+ 90.0
+
+
+ 6
+ 333
+ Parallel Computing
+ 2019
+ 90.0
+
+
+ 7
+ 444
+ Large Scale Infrastructures
+ 2019
+ 83.0
+
+
+ 8
+ 444
+ TPCS INFO TECH
+ 2019
+ 98.0
+
+
+ 12
+ 666
+ Parallel Computing
+ 2019
+ 87.0
+
+
+ 13
+ 888
+ Spark Programming
+ 2019
+ 93.0
+
+
+ 3
+ 222
+ Health and Technology
+ 2020
+ 80.0
+
+
+ 10
+ 555
+ Health and Technology
+ 2020
+ 81.0
+
+
+ 9
+ 555
+ TPCS INFO TECH
+ 2020
+ 78.0
+
+
+ 11
+ 666
+ Data Visualization
+ 2021
+ 85.0
+
+
+
+
+
+
+
+
* postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres
+
+
+
+
+
+
+ id
+ student_no
+ course_name
+ course_year
+ course_percentage
+
+
+
+
+ 11
+ 666
+ Data Visualization
+ 2021
+ 85.0
+
+
+ 10
+ 555
+ Health and Technology
+ 2020
+ 81.0
+
+
+ 3
+ 222
+ Health and Technology
+ 2020
+ 80.0
+
+
+ 9
+ 555
+ TPCS INFO TECH
+ 2020
+ 78.0
+
+
+ 6
+ 333
+ Parallel Computing
+ 2019
+ 90.0
+
+
+ 7
+ 444
+ Large Scale Infrastructures
+ 2019
+ 83.0
+
+
+ 8
+ 444
+ TPCS INFO TECH
+ 2019
+ 98.0
+
+
+ 12
+ 666
+ Parallel Computing
+ 2019
+ 87.0
+
+
+ 13
+ 888
+ Spark Programming
+ 2019
+ 93.0
+
+
+ 14
+ 999
+ TPCS INFO TECH
+ 2019
+ 98.0
+
+
+ 15
+ 999
+ Data Visualization
+ 2019
+ 87.0
+
+
+ 16
+ 1000
+ C programming
+ 2019
+ 87.0
+
+
+ 1
+ 111
+ TPCS INFO TECH
+ 2019
+ 88.0
+
+
+ 17
+ 1111
+ Introduction to Genomics
+ 2019
+ 87.0
+
+
+ 2
+ 111
+ Data Visualization
+ 2019
+ 88.0
+
+
+ 4
+ 222
+ Web and Cloud Computing
+ 2019
+ 91.0
+
+
+ 5
+ 222
+ Spark Programming
+ 2019
+ 90.0
+
+
+
+
+
+
+
1.4.3. LIMIT
+
Until now, we were returning everything that our SQL query returns. LIMIT
statement is used to limit the number of rows that are returned.
+
syntax:
+
SELECT column1 , column2 , ... columnN
+FROM tablename
+LIMIT numberofrows ;
+
+
+
+
+
+
* postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres
+2 rows affected.
+
+
+
+
+
+ id
+ student_no
+ course_name
+ course_year
+ course_percentage
+
+
+
+
+ 1
+ 111
+ TPCS INFO TECH
+ 2019
+ 88.0
+
+
+ 2
+ 111
+ Data Visualization
+ 2019
+ 88.0
+
+
+
+
+
LIMIT
keyword is used in a variety of situations. Here are a few cases
+
+Memory Management: Say you just want to look at the output your query returns. If you are dealing with lots and lots of rows, returning the entire rows can slow down the query, cause memory issues, and finally crash your jupyter. In these cases, you can use LIMIT
.
+Interest in the first few rows: If we are interested in just the first N rows, we can achieve that using LIMIT
. People tend to use LIMIT
a lot when they want to return the top 10 rows after performing an ORDER BY
+
+
Let’s apply all the statements that we learned in one statement.
+
Question: List out the row that got the highest course_percentage
for the Data Visualization
course.
+
+
+
+
* postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres
+1 rows affected.
+
+
+
+
+
+ id
+ student_no
+ course_name
+ course_year
+ course_percentage
+
+
+
+
+ 2
+ 111
+ Data Visualization
+ 2019
+ 88.0
+
+
+
+
+
+
+
+
* postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres
+
+
+
+
+
+
+ age
+
+
+
+
+ 18
+
+
+ 22
+
+
+ 23
+
+
+ 25
+
+
+ 29
+
+
+ 29
+
+
+ 35
+
+
+ 56
+
+
+
+
+
+
+
1.4.4. Checkpoint 1 !! Take some time and think if you can…
+
+Select columns and bring in rows just what we need (using SELECT
& WHERE
)
+Return DISTINCT
elements
+ORDER BY
rows returned based on column(s)
+LIMIT
the number of rows returned
+
+
Now we will learn some more advanced SQL operations to gain more insight into the data. But, before that, let’s do some exercise.
+
+
+
1.4.5. Check point 1 exercise.
+
+
1.4.5.1. iclicker questions
+
Answer the following questions using the table eg
+
+
+firstname
+countryfrom
+continent
+gender
+age
+
+
+
+matt
+usa
+north america
+M
+23
+
+jenn
+uk
+europe
+F
+35
+
+guy
+france
+europe
+M
+25
+
+james
+china
+asia
+M
+29
+
+lida
+india
+asia
+F
+56
+
+linda
+canada
+north america
+F
+18
+
+sofia
+germany
+europe
+F
+22
+
+george
+india
+asia
+M
+29
+
+
+
+
Question 1: How many elements will be returned from this SQL
+
select DISTINCT gender , firstname from eg
+
+
+
A) 2
+
B) 8
+
C) 4
+
+
Question 2: What will be the first value returned from this SQL
+
select age from eg order by age
+
+
+
A) 23
+
B) 18
+
C) 56
+
D) 35
+
+
+
+
1.4.5.2. Reasoning Question
+
Question 1: Write a SQL query to list the row that got the highest course_percentage
for the TPCS INFO TECH'
course. Can you spot any issues by examining the original table?
+
+
You probably might have got this SQL query by changing the SQL query that we discussed for the “Data Visualization” course.
+
SELECT * FROM courses
+WHERE course_name = ‘TPCS INFO TECH’
+ORDER BY course_percentage DESC
+LIMIT 1;
+
This gives you
+
+
+id
+student_no
+course_name
+course_year
+course_percentage
+
+
+
+8
+444
+TPCS INFO TECH
+2019
+98.0
+
+
+
+
What are possible issues? Take out the LIMIT
, and you might notice there is a tie, and 2 students, 444 and 999 scored the highest.
+
SELECT * FROM courses
+WHERE course_name = ‘TPCS INFO TECH’
+ORDER BY course_percentage DESC
+
+
+id
+student_no
+course_name
+course_year
+course_percentage
+
+
+
+8
+444
+TPCS INFO TECH
+2019
+98.0
+
+14
+999
+TPCS INFO TECH
+2019
+98.0
+
+1
+111
+TPCS INFO TECH
+2019
+88.0
+
+9
+555
+TPCS INFO TECH
+2020
+78.0
+
+
+
+
So even though many of them use a combination of ORDER BY
and LIMIT
for these scenarios we might run into situations like this. There are a couple of ways to deal with these kinds of scenarios, and we will learn about subquery
in our next class, which will help you capture ties.
+
+
+
+
+
+
1.5. Lap 2 🧋
+
+
1.5.1. Aggregations
+
So far, we have returned columns in our select statement. We can also use aggregation functions that operate on rows to summarize the data in the form of a single value. Here is a list of aggregation functions in SQL:
+
+
+Function
+Description
+
+
+
+MAX()
+maximum value
+
+MIN()
+minimum value
+
+AVG()
+average value
+
+SUM()
+sum of values
+
+COUNT()
+number of values
+
+
+
+
+
+
+
* postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres
+
+
+
+
+
+
+ count
+
+
+
+
+ 17
+
+
+
+
+
The above query is counting number of values in the column course_name
. it’s also sort of like counting the number of rows. We can also pass the DISTINCT
columns into these operations. For example, the below query will find the number of courses available in the university.
+
+
+
+
* postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres
+
+
+
+
+
+
+ count
+
+
+
+
+ 9
+
+
+
+
+
+
+
+
* postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres
+
+
+
+
+
+
+ avg
+
+
+
+
+ 87.70588235294117
+
+
+
+
+
Few things to keep in mind when dealing with the aggregation function
+
+
SELECT COUNT ( DISTINCT course_name ), max ( course_percentage ) FROM courses ;
+
+
+
+
SELECT COUNT ( DISTINCT course_name ), course_percentage FROM courses ;
+
+
+
+
SELECT * FROM courses WHERE course_percentage < AVG ( course_percentage );
+
+
+
You can answer this question when we discuss subqueries in the next class (another reason to learn subqueries :) )
+
+
+
1.5.2. Grouping
+
The aggregations we learned in our previous session also become useful when using the GROUP BY
statement. GROUP BY
statement divides a table into groups of rows based on the values of one or more columns. Once this grouping is done, you can apply your aggregation to these groups.
+
Syntax:
+
SELECT
+ grouping_columns , aggregated_columns
+FROM
+ table1
+GROUP BY
+ grouping_columns
+
+
+
Example:
+
+
+
+
* postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres
+
+
+
+
+
+
+ id
+ student_no
+ course_name
+ course_year
+ course_percentage
+
+
+
+
+ 1
+ 111
+ TPCS INFO TECH
+ 2019
+ 88.0
+
+
+ 2
+ 111
+ Data Visualization
+ 2019
+ 88.0
+
+
+ 3
+ 222
+ Health and Technology
+ 2020
+ 80.0
+
+
+ 4
+ 222
+ Web and Cloud Computing
+ 2019
+ 91.0
+
+
+ 5
+ 222
+ Spark Programming
+ 2019
+ 90.0
+
+
+ 6
+ 333
+ Parallel Computing
+ 2019
+ 90.0
+
+
+ 7
+ 444
+ Large Scale Infrastructures
+ 2019
+ 83.0
+
+
+ 8
+ 444
+ TPCS INFO TECH
+ 2019
+ 98.0
+
+
+ 9
+ 555
+ TPCS INFO TECH
+ 2020
+ 78.0
+
+
+ 10
+ 555
+ Health and Technology
+ 2020
+ 81.0
+
+
+ 11
+ 666
+ Data Visualization
+ 2021
+ 85.0
+
+
+ 12
+ 666
+ Parallel Computing
+ 2019
+ 87.0
+
+
+ 13
+ 888
+ Spark Programming
+ 2019
+ 93.0
+
+
+ 14
+ 999
+ TPCS INFO TECH
+ 2019
+ 98.0
+
+
+ 15
+ 999
+ Data Visualization
+ 2019
+ 87.0
+
+
+ 16
+ 1000
+ C programming
+ 2019
+ 87.0
+
+
+ 17
+ 1111
+ Introduction to Genomics
+ 2019
+ 87.0
+
+
+
+
+
+
+
+
* postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres
+
+
+
+
+
+
+ course_name
+ avg
+
+
+
+
+ Data Visualization
+ 86.66666666666667
+
+
+ Introduction to Genomics
+ 87.0
+
+
+ Large Scale Infrastructures
+ 83.0
+
+
+ Spark Programming
+ 91.5
+
+
+ Web and Cloud Computing
+ 91.0
+
+
+ Health and Technology
+ 80.5
+
+
+ C programming
+ 87.0
+
+
+ TPCS INFO TECH
+ 90.5
+
+
+ Parallel Computing
+ 88.5
+
+
+
+
+
We can also perform a multi level grouping;
+
+
+
+
* postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres
+
+
+
+
+
+
+ course_name
+ course_year
+ avg
+
+
+
+
+ Parallel Computing
+ 2019
+ 88.5
+
+
+ C programming
+ 2019
+ 87.0
+
+
+ Health and Technology
+ 2020
+ 80.5
+
+
+ Introduction to Genomics
+ 2019
+ 87.0
+
+
+ Large Scale Infrastructures
+ 2019
+ 83.0
+
+
+ TPCS INFO TECH
+ 2020
+ 78.0
+
+
+ Data Visualization
+ 2021
+ 85.0
+
+
+ Data Visualization
+ 2019
+ 87.5
+
+
+
+
+
Now, what if I want to see only the courses with an average of less than 90 %? We mentioned before that this kind of filtering (filtering on the aggregation function) is not possible using WHERE
statement, and that’s why we want the HAVING
statement to do filtering using these aggregated values.
+
+
+
1.5.3. HAVING
+
Syntax:
+
SELECT
+ grouping_columns , aggregated_columns
+FROM
+ table1
+GROUP BY
+ grouping_columns
+HAVING
+ group_condition
+
+
+
+
Important
+
To summarize:
+
+
+
For example, let’s get the question we raised at the end of the grouping section. I want to see the courses with a course average of less than 90 %?
+
+
+
+
* postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres
+
+
+
+
+
+
+ course_name
+ avg
+
+
+
+
+ Data Visualization
+ 86.66666666666667
+
+
+ Introduction to Genomics
+ 87.0
+
+
+ Large Scale Infrastructures
+ 83.0
+
+
+ Health and Technology
+ 80.5
+
+
+ C programming
+ 87.0
+
+
+ Parallel Computing
+ 88.5
+
+
+
+
+
+
+
1.5.4. Using alias (AS)
+
Until now, we have been referring tables as table names and columns as column names from the schema. But when writing SQL, we are not required to use the same column and table names as in the schema. Instead, we can create aliases for a column or a table using the keyword AS
.
+
Syntax:
+
SELECT
+ column1 [ AS ] c1 ,
+ column2 [ AS ] c2
+FROM
+ table1 [ AS ] t1 ;
+
+
+
It’s entirely optional to use AS, but WHY do we want it?
+
+This makes code more readable
+You can return the columns with a more meaningful name
+Helps a lot when we do JOINS ( wait for the next topic)
+
+
Let’s rewrite our previous query using AS
+
+
+
+
* postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres
+
+
+
+
+
+
+ course_name
+ course_year
+ average course percentage
+
+
+
+
+ Parallel Computing
+ 2019
+ 88.5
+
+
+ C programming
+ 2019
+ 87.0
+
+
+ Health and Technology
+ 2020
+ 80.5
+
+
+ Introduction to Genomics
+ 2019
+ 87.0
+
+
+ Large Scale Infrastructures
+ 2019
+ 83.0
+
+
+ TPCS INFO TECH
+ 2020
+ 78.0
+
+
+ Data Visualization
+ 2021
+ 85.0
+
+
+ Data Visualization
+ 2019
+ 87.5
+
+
+
+
+
+
+
+
* postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres
+
+
+
+
+
+
+ id
+ student_no
+ course_name
+ course_year
+ course_percentage
+
+
+
+
+ 1
+ 111
+ TPCS INFO TECH
+ 2019
+ 88.0
+
+
+ 2
+ 111
+ Data Visualization
+ 2019
+ 88.0
+
+
+ 3
+ 222
+ Health and Technology
+ 2020
+ 80.0
+
+
+ 4
+ 222
+ Web and Cloud Computing
+ 2019
+ 91.0
+
+
+ 5
+ 222
+ Spark Programming
+ 2019
+ 90.0
+
+
+ 6
+ 333
+ Parallel Computing
+ 2019
+ 90.0
+
+
+ 7
+ 444
+ Large Scale Infrastructures
+ 2019
+ 83.0
+
+
+ 8
+ 444
+ TPCS INFO TECH
+ 2019
+ 98.0
+
+
+ 9
+ 555
+ TPCS INFO TECH
+ 2020
+ 78.0
+
+
+ 10
+ 555
+ Health and Technology
+ 2020
+ 81.0
+
+
+ 11
+ 666
+ Data Visualization
+ 2021
+ 85.0
+
+
+ 12
+ 666
+ Parallel Computing
+ 2019
+ 87.0
+
+
+ 13
+ 888
+ Spark Programming
+ 2019
+ 93.0
+
+
+ 14
+ 999
+ TPCS INFO TECH
+ 2019
+ 98.0
+
+
+ 15
+ 999
+ Data Visualization
+ 2019
+ 87.0
+
+
+ 16
+ 1000
+ C programming
+ 2019
+ 87.0
+
+
+ 17
+ 1111
+ Introduction to Genomics
+ 2019
+ 87.0
+
+
+
+
+
+
+
1.5.5. Checkpoint 2 !! Take some time and think if you can…
+
+
Until now, you deal with queries on a single table. What if we are interested in data from another table as well? For example, I am interested in seeing the course details (from the courses
table) and the students (from the students
table) related to those courses. In these situations, we use joins. Let’s learn how to stitch tables together. Before that, let’s do some exercise…
+
+
+
1.5.6. Check point 2 exercise.
+
+
1.5.6.1. iclicker questions
+
Question 1: Spot the issue, if any, in this SQL query
+
SELECT COUNT ( DISTINCT course_name ), max ( course_percentage ), course_percentage
+FROM courses ;
+
+
+
A: Multiple aggregation functions in the SELECT statement
+
B: No issues
+
C: Can’t use DISTINCT inside an aggregation function
+
D: Can’t use aggregations and regular columns in a single query
+
+
Question 2: Spot the issue, if any, in this SQL query
+
SELECT COUNT ( DISTINCT course_name ), max ( course_percentage ), course_year
+FROM courses
+GROUP BY course_year ;
+
+
+
A: Multiple aggregation functions in the SELECT statement
+
B: No issues
+
C: Can’t use DISTINCT inside an aggregation function
+
D: Can’t use aggregations and regular columns in a single query
+
+
Question 3: Spot the issue, if any, in this SQL query
+
SELECT COUNT ( DISTINCT course_name ), max ( course_percentage ), course_year
+FROM courses
+WHERE course_name != 'TPCS INFO TECH'
+GROUP BY course_year
+having course_percentage < 90 ;
+
+
+
A: No issues
+
B: Can’t use column course_year
in SELECT
+
C: Can’t use WHERE when using aggregation functions
+
D: Can’t use course_percentage in a HAVING
statement
+
+
+
+
1.5.6.2. Reasoning Question
+
Question 1: We learned the GROUP BY
clause, and we used it with aggregate functions. Using table courses
, write a SQL query using GROUP BY
without any aggregation function. Write your findings and learnings.
+
+
You must have tried a variety of SQL queries and got into error;
+
select * FROM courses
+group by course_name;
+
select course_year,course_name
+FROM courses
+group by course_name;
+
Below is one query that runs, and this query can be considered equivalent to using DISTINCT
if no aggregate functions are used.
+
select course_name
+FROM courses
+group by course_name;
+
+
Important
+
In an aggregation query, the unaggregated expressions need to be consistent with the GROUP BY
expressions. And all other expressions need to use aggregation functions
+
+
+
+
+
+
+
1.6. Lap 3 🧃
+
+
1.6.1. Join
+
Syntax:
+
SELECT
+ columns
+FROM
+ left_table
+join_type
+ right_table
+ON
+ join_condition
+;
+
+
+
Following are the types of joins
+
+
+
1.6.2. Cross join
+
This is the simplest way of performing a join by cross joining 2 tables (like the cartesian product from your relational algebra classes), in our case, table students
and courses
. This kind of join returns all possible combinations of rows from students
and courses
.
+
+
+
+
* postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres
+153 rows affected.
+
+
+
+
+
+ student_no
+ stud_name
+ age
+ major
+ id
+ student_no_1
+ course_name
+ course_year
+ course_percentage
+
+
+
+
+ 111
+ Catherine
+ 23
+ MBAN
+ 1
+ 111
+ TPCS INFO TECH
+ 2019
+ 88.0
+
+
+ 222
+ Tiff
+ 28
+ MDS
+ 1
+ 111
+ TPCS INFO TECH
+ 2019
+ 88.0
+
+
+ 333
+ John
+ 23
+ MBAN
+ 1
+ 111
+ TPCS INFO TECH
+ 2019
+ 88.0
+
+
+ 444
+ Amir
+ 28
+ MBAN
+ 1
+ 111
+ TPCS INFO TECH
+ 2019
+ 88.0
+
+
+ 555
+ Gittu
+ 20
+ MDS
+ 1
+ 111
+ TPCS INFO TECH
+ 2019
+ 88.0
+
+
+ 666
+ Isha
+ 30
+ MBAN
+ 1
+ 111
+ TPCS INFO TECH
+ 2019
+ 88.0
+
+
+ 777
+ Heidy
+ 30
+ MBAN
+ 1
+ 111
+ TPCS INFO TECH
+ 2019
+ 88.0
+
+
+ 888
+ Angela
+ 27
+ MBAN
+ 1
+ 111
+ TPCS INFO TECH
+ 2019
+ 88.0
+
+
+ 999
+ Jason
+ 30
+ MBAN
+ 1
+ 111
+ TPCS INFO TECH
+ 2019
+ 88.0
+
+
+ 111
+ Catherine
+ 23
+ MBAN
+ 2
+ 111
+ Data Visualization
+ 2019
+ 88.0
+
+
+ 222
+ Tiff
+ 28
+ MDS
+ 2
+ 111
+ Data Visualization
+ 2019
+ 88.0
+
+
+ 333
+ John
+ 23
+ MBAN
+ 2
+ 111
+ Data Visualization
+ 2019
+ 88.0
+
+
+ 444
+ Amir
+ 28
+ MBAN
+ 2
+ 111
+ Data Visualization
+ 2019
+ 88.0
+
+
+ 555
+ Gittu
+ 20
+ MDS
+ 2
+ 111
+ Data Visualization
+ 2019
+ 88.0
+
+
+ 666
+ Isha
+ 30
+ MBAN
+ 2
+ 111
+ Data Visualization
+ 2019
+ 88.0
+
+
+ 777
+ Heidy
+ 30
+ MBAN
+ 2
+ 111
+ Data Visualization
+ 2019
+ 88.0
+
+
+ 888
+ Angela
+ 27
+ MBAN
+ 2
+ 111
+ Data Visualization
+ 2019
+ 88.0
+
+
+ 999
+ Jason
+ 30
+ MBAN
+ 2
+ 111
+ Data Visualization
+ 2019
+ 88.0
+
+
+ 111
+ Catherine
+ 23
+ MBAN
+ 3
+ 222
+ Health and Technology
+ 2020
+ 80.0
+
+
+ 222
+ Tiff
+ 28
+ MDS
+ 3
+ 222
+ Health and Technology
+ 2020
+ 80.0
+
+
+ 333
+ John
+ 23
+ MBAN
+ 3
+ 222
+ Health and Technology
+ 2020
+ 80.0
+
+
+ 444
+ Amir
+ 28
+ MBAN
+ 3
+ 222
+ Health and Technology
+ 2020
+ 80.0
+
+
+ 555
+ Gittu
+ 20
+ MDS
+ 3
+ 222
+ Health and Technology
+ 2020
+ 80.0
+
+
+ 666
+ Isha
+ 30
+ MBAN
+ 3
+ 222
+ Health and Technology
+ 2020
+ 80.0
+
+
+ 777
+ Heidy
+ 30
+ MBAN
+ 3
+ 222
+ Health and Technology
+ 2020
+ 80.0
+
+
+ 888
+ Angela
+ 27
+ MBAN
+ 3
+ 222
+ Health and Technology
+ 2020
+ 80.0
+
+
+ 999
+ Jason
+ 30
+ MBAN
+ 3
+ 222
+ Health and Technology
+ 2020
+ 80.0
+
+
+ 111
+ Catherine
+ 23
+ MBAN
+ 4
+ 222
+ Web and Cloud Computing
+ 2019
+ 91.0
+
+
+ 222
+ Tiff
+ 28
+ MDS
+ 4
+ 222
+ Web and Cloud Computing
+ 2019
+ 91.0
+
+
+ 333
+ John
+ 23
+ MBAN
+ 4
+ 222
+ Web and Cloud Computing
+ 2019
+ 91.0
+
+
+ 444
+ Amir
+ 28
+ MBAN
+ 4
+ 222
+ Web and Cloud Computing
+ 2019
+ 91.0
+
+
+ 555
+ Gittu
+ 20
+ MDS
+ 4
+ 222
+ Web and Cloud Computing
+ 2019
+ 91.0
+
+
+ 666
+ Isha
+ 30
+ MBAN
+ 4
+ 222
+ Web and Cloud Computing
+ 2019
+ 91.0
+
+
+ 777
+ Heidy
+ 30
+ MBAN
+ 4
+ 222
+ Web and Cloud Computing
+ 2019
+ 91.0
+
+
+ 888
+ Angela
+ 27
+ MBAN
+ 4
+ 222
+ Web and Cloud Computing
+ 2019
+ 91.0
+
+
+ 999
+ Jason
+ 30
+ MBAN
+ 4
+ 222
+ Web and Cloud Computing
+ 2019
+ 91.0
+
+
+ 111
+ Catherine
+ 23
+ MBAN
+ 5
+ 222
+ Spark Programming
+ 2019
+ 90.0
+
+
+ 222
+ Tiff
+ 28
+ MDS
+ 5
+ 222
+ Spark Programming
+ 2019
+ 90.0
+
+
+ 333
+ John
+ 23
+ MBAN
+ 5
+ 222
+ Spark Programming
+ 2019
+ 90.0
+
+
+ 444
+ Amir
+ 28
+ MBAN
+ 5
+ 222
+ Spark Programming
+ 2019
+ 90.0
+
+
+ 555
+ Gittu
+ 20
+ MDS
+ 5
+ 222
+ Spark Programming
+ 2019
+ 90.0
+
+
+ 666
+ Isha
+ 30
+ MBAN
+ 5
+ 222
+ Spark Programming
+ 2019
+ 90.0
+
+
+ 777
+ Heidy
+ 30
+ MBAN
+ 5
+ 222
+ Spark Programming
+ 2019
+ 90.0
+
+
+ 888
+ Angela
+ 27
+ MBAN
+ 5
+ 222
+ Spark Programming
+ 2019
+ 90.0
+
+
+ 999
+ Jason
+ 30
+ MBAN
+ 5
+ 222
+ Spark Programming
+ 2019
+ 90.0
+
+
+ 111
+ Catherine
+ 23
+ MBAN
+ 6
+ 333
+ Parallel Computing
+ 2019
+ 90.0
+
+
+ 222
+ Tiff
+ 28
+ MDS
+ 6
+ 333
+ Parallel Computing
+ 2019
+ 90.0
+
+
+ 333
+ John
+ 23
+ MBAN
+ 6
+ 333
+ Parallel Computing
+ 2019
+ 90.0
+
+
+ 444
+ Amir
+ 28
+ MBAN
+ 6
+ 333
+ Parallel Computing
+ 2019
+ 90.0
+
+
+ 555
+ Gittu
+ 20
+ MDS
+ 6
+ 333
+ Parallel Computing
+ 2019
+ 90.0
+
+
+ 666
+ Isha
+ 30
+ MBAN
+ 6
+ 333
+ Parallel Computing
+ 2019
+ 90.0
+
+
+ 777
+ Heidy
+ 30
+ MBAN
+ 6
+ 333
+ Parallel Computing
+ 2019
+ 90.0
+
+
+ 888
+ Angela
+ 27
+ MBAN
+ 6
+ 333
+ Parallel Computing
+ 2019
+ 90.0
+
+
+ 999
+ Jason
+ 30
+ MBAN
+ 6
+ 333
+ Parallel Computing
+ 2019
+ 90.0
+
+
+ 111
+ Catherine
+ 23
+ MBAN
+ 7
+ 444
+ Large Scale Infrastructures
+ 2019
+ 83.0
+
+
+ 222
+ Tiff
+ 28
+ MDS
+ 7
+ 444
+ Large Scale Infrastructures
+ 2019
+ 83.0
+
+
+ 333
+ John
+ 23
+ MBAN
+ 7
+ 444
+ Large Scale Infrastructures
+ 2019
+ 83.0
+
+
+ 444
+ Amir
+ 28
+ MBAN
+ 7
+ 444
+ Large Scale Infrastructures
+ 2019
+ 83.0
+
+
+ 555
+ Gittu
+ 20
+ MDS
+ 7
+ 444
+ Large Scale Infrastructures
+ 2019
+ 83.0
+
+
+ 666
+ Isha
+ 30
+ MBAN
+ 7
+ 444
+ Large Scale Infrastructures
+ 2019
+ 83.0
+
+
+ 777
+ Heidy
+ 30
+ MBAN
+ 7
+ 444
+ Large Scale Infrastructures
+ 2019
+ 83.0
+
+
+ 888
+ Angela
+ 27
+ MBAN
+ 7
+ 444
+ Large Scale Infrastructures
+ 2019
+ 83.0
+
+
+ 999
+ Jason
+ 30
+ MBAN
+ 7
+ 444
+ Large Scale Infrastructures
+ 2019
+ 83.0
+
+
+ 111
+ Catherine
+ 23
+ MBAN
+ 8
+ 444
+ TPCS INFO TECH
+ 2019
+ 98.0
+
+
+ 222
+ Tiff
+ 28
+ MDS
+ 8
+ 444
+ TPCS INFO TECH
+ 2019
+ 98.0
+
+
+ 333
+ John
+ 23
+ MBAN
+ 8
+ 444
+ TPCS INFO TECH
+ 2019
+ 98.0
+
+
+ 444
+ Amir
+ 28
+ MBAN
+ 8
+ 444
+ TPCS INFO TECH
+ 2019
+ 98.0
+
+
+ 555
+ Gittu
+ 20
+ MDS
+ 8
+ 444
+ TPCS INFO TECH
+ 2019
+ 98.0
+
+
+ 666
+ Isha
+ 30
+ MBAN
+ 8
+ 444
+ TPCS INFO TECH
+ 2019
+ 98.0
+
+
+ 777
+ Heidy
+ 30
+ MBAN
+ 8
+ 444
+ TPCS INFO TECH
+ 2019
+ 98.0
+
+
+ 888
+ Angela
+ 27
+ MBAN
+ 8
+ 444
+ TPCS INFO TECH
+ 2019
+ 98.0
+
+
+ 999
+ Jason
+ 30
+ MBAN
+ 8
+ 444
+ TPCS INFO TECH
+ 2019
+ 98.0
+
+
+ 111
+ Catherine
+ 23
+ MBAN
+ 9
+ 555
+ TPCS INFO TECH
+ 2020
+ 78.0
+
+
+ 222
+ Tiff
+ 28
+ MDS
+ 9
+ 555
+ TPCS INFO TECH
+ 2020
+ 78.0
+
+
+ 333
+ John
+ 23
+ MBAN
+ 9
+ 555
+ TPCS INFO TECH
+ 2020
+ 78.0
+
+
+ 444
+ Amir
+ 28
+ MBAN
+ 9
+ 555
+ TPCS INFO TECH
+ 2020
+ 78.0
+
+
+ 555
+ Gittu
+ 20
+ MDS
+ 9
+ 555
+ TPCS INFO TECH
+ 2020
+ 78.0
+
+
+ 666
+ Isha
+ 30
+ MBAN
+ 9
+ 555
+ TPCS INFO TECH
+ 2020
+ 78.0
+
+
+ 777
+ Heidy
+ 30
+ MBAN
+ 9
+ 555
+ TPCS INFO TECH
+ 2020
+ 78.0
+
+
+ 888
+ Angela
+ 27
+ MBAN
+ 9
+ 555
+ TPCS INFO TECH
+ 2020
+ 78.0
+
+
+ 999
+ Jason
+ 30
+ MBAN
+ 9
+ 555
+ TPCS INFO TECH
+ 2020
+ 78.0
+
+
+ 111
+ Catherine
+ 23
+ MBAN
+ 10
+ 555
+ Health and Technology
+ 2020
+ 81.0
+
+
+ 222
+ Tiff
+ 28
+ MDS
+ 10
+ 555
+ Health and Technology
+ 2020
+ 81.0
+
+
+ 333
+ John
+ 23
+ MBAN
+ 10
+ 555
+ Health and Technology
+ 2020
+ 81.0
+
+
+ 444
+ Amir
+ 28
+ MBAN
+ 10
+ 555
+ Health and Technology
+ 2020
+ 81.0
+
+
+ 555
+ Gittu
+ 20
+ MDS
+ 10
+ 555
+ Health and Technology
+ 2020
+ 81.0
+
+
+ 666
+ Isha
+ 30
+ MBAN
+ 10
+ 555
+ Health and Technology
+ 2020
+ 81.0
+
+
+ 777
+ Heidy
+ 30
+ MBAN
+ 10
+ 555
+ Health and Technology
+ 2020
+ 81.0
+
+
+ 888
+ Angela
+ 27
+ MBAN
+ 10
+ 555
+ Health and Technology
+ 2020
+ 81.0
+
+
+ 999
+ Jason
+ 30
+ MBAN
+ 10
+ 555
+ Health and Technology
+ 2020
+ 81.0
+
+
+ 111
+ Catherine
+ 23
+ MBAN
+ 11
+ 666
+ Data Visualization
+ 2021
+ 85.0
+
+
+ 222
+ Tiff
+ 28
+ MDS
+ 11
+ 666
+ Data Visualization
+ 2021
+ 85.0
+
+
+ 333
+ John
+ 23
+ MBAN
+ 11
+ 666
+ Data Visualization
+ 2021
+ 85.0
+
+
+ 444
+ Amir
+ 28
+ MBAN
+ 11
+ 666
+ Data Visualization
+ 2021
+ 85.0
+
+
+ 555
+ Gittu
+ 20
+ MDS
+ 11
+ 666
+ Data Visualization
+ 2021
+ 85.0
+
+
+ 666
+ Isha
+ 30
+ MBAN
+ 11
+ 666
+ Data Visualization
+ 2021
+ 85.0
+
+
+ 777
+ Heidy
+ 30
+ MBAN
+ 11
+ 666
+ Data Visualization
+ 2021
+ 85.0
+
+
+ 888
+ Angela
+ 27
+ MBAN
+ 11
+ 666
+ Data Visualization
+ 2021
+ 85.0
+
+
+ 999
+ Jason
+ 30
+ MBAN
+ 11
+ 666
+ Data Visualization
+ 2021
+ 85.0
+
+
+ 111
+ Catherine
+ 23
+ MBAN
+ 12
+ 666
+ Parallel Computing
+ 2019
+ 87.0
+
+
+ 222
+ Tiff
+ 28
+ MDS
+ 12
+ 666
+ Parallel Computing
+ 2019
+ 87.0
+
+
+ 333
+ John
+ 23
+ MBAN
+ 12
+ 666
+ Parallel Computing
+ 2019
+ 87.0
+
+
+ 444
+ Amir
+ 28
+ MBAN
+ 12
+ 666
+ Parallel Computing
+ 2019
+ 87.0
+
+
+ 555
+ Gittu
+ 20
+ MDS
+ 12
+ 666
+ Parallel Computing
+ 2019
+ 87.0
+
+
+ 666
+ Isha
+ 30
+ MBAN
+ 12
+ 666
+ Parallel Computing
+ 2019
+ 87.0
+
+
+ 777
+ Heidy
+ 30
+ MBAN
+ 12
+ 666
+ Parallel Computing
+ 2019
+ 87.0
+
+
+ 888
+ Angela
+ 27
+ MBAN
+ 12
+ 666
+ Parallel Computing
+ 2019
+ 87.0
+
+
+ 999
+ Jason
+ 30
+ MBAN
+ 12
+ 666
+ Parallel Computing
+ 2019
+ 87.0
+
+
+ 111
+ Catherine
+ 23
+ MBAN
+ 13
+ 888
+ Spark Programming
+ 2019
+ 93.0
+
+
+ 222
+ Tiff
+ 28
+ MDS
+ 13
+ 888
+ Spark Programming
+ 2019
+ 93.0
+
+
+ 333
+ John
+ 23
+ MBAN
+ 13
+ 888
+ Spark Programming
+ 2019
+ 93.0
+
+
+ 444
+ Amir
+ 28
+ MBAN
+ 13
+ 888
+ Spark Programming
+ 2019
+ 93.0
+
+
+ 555
+ Gittu
+ 20
+ MDS
+ 13
+ 888
+ Spark Programming
+ 2019
+ 93.0
+
+
+ 666
+ Isha
+ 30
+ MBAN
+ 13
+ 888
+ Spark Programming
+ 2019
+ 93.0
+
+
+ 777
+ Heidy
+ 30
+ MBAN
+ 13
+ 888
+ Spark Programming
+ 2019
+ 93.0
+
+
+ 888
+ Angela
+ 27
+ MBAN
+ 13
+ 888
+ Spark Programming
+ 2019
+ 93.0
+
+
+ 999
+ Jason
+ 30
+ MBAN
+ 13
+ 888
+ Spark Programming
+ 2019
+ 93.0
+
+
+ 111
+ Catherine
+ 23
+ MBAN
+ 14
+ 999
+ TPCS INFO TECH
+ 2019
+ 98.0
+
+
+ 222
+ Tiff
+ 28
+ MDS
+ 14
+ 999
+ TPCS INFO TECH
+ 2019
+ 98.0
+
+
+ 333
+ John
+ 23
+ MBAN
+ 14
+ 999
+ TPCS INFO TECH
+ 2019
+ 98.0
+
+
+ 444
+ Amir
+ 28
+ MBAN
+ 14
+ 999
+ TPCS INFO TECH
+ 2019
+ 98.0
+
+
+ 555
+ Gittu
+ 20
+ MDS
+ 14
+ 999
+ TPCS INFO TECH
+ 2019
+ 98.0
+
+
+ 666
+ Isha
+ 30
+ MBAN
+ 14
+ 999
+ TPCS INFO TECH
+ 2019
+ 98.0
+
+
+ 777
+ Heidy
+ 30
+ MBAN
+ 14
+ 999
+ TPCS INFO TECH
+ 2019
+ 98.0
+
+
+ 888
+ Angela
+ 27
+ MBAN
+ 14
+ 999
+ TPCS INFO TECH
+ 2019
+ 98.0
+
+
+ 999
+ Jason
+ 30
+ MBAN
+ 14
+ 999
+ TPCS INFO TECH
+ 2019
+ 98.0
+
+
+ 111
+ Catherine
+ 23
+ MBAN
+ 15
+ 999
+ Data Visualization
+ 2019
+ 87.0
+
+
+ 222
+ Tiff
+ 28
+ MDS
+ 15
+ 999
+ Data Visualization
+ 2019
+ 87.0
+
+
+ 333
+ John
+ 23
+ MBAN
+ 15
+ 999
+ Data Visualization
+ 2019
+ 87.0
+
+
+ 444
+ Amir
+ 28
+ MBAN
+ 15
+ 999
+ Data Visualization
+ 2019
+ 87.0
+
+
+ 555
+ Gittu
+ 20
+ MDS
+ 15
+ 999
+ Data Visualization
+ 2019
+ 87.0
+
+
+ 666
+ Isha
+ 30
+ MBAN
+ 15
+ 999
+ Data Visualization
+ 2019
+ 87.0
+
+
+ 777
+ Heidy
+ 30
+ MBAN
+ 15
+ 999
+ Data Visualization
+ 2019
+ 87.0
+
+
+ 888
+ Angela
+ 27
+ MBAN
+ 15
+ 999
+ Data Visualization
+ 2019
+ 87.0
+
+
+ 999
+ Jason
+ 30
+ MBAN
+ 15
+ 999
+ Data Visualization
+ 2019
+ 87.0
+
+
+ 111
+ Catherine
+ 23
+ MBAN
+ 16
+ 1000
+ C programming
+ 2019
+ 87.0
+
+
+ 222
+ Tiff
+ 28
+ MDS
+ 16
+ 1000
+ C programming
+ 2019
+ 87.0
+
+
+ 333
+ John
+ 23
+ MBAN
+ 16
+ 1000
+ C programming
+ 2019
+ 87.0
+
+
+ 444
+ Amir
+ 28
+ MBAN
+ 16
+ 1000
+ C programming
+ 2019
+ 87.0
+
+
+ 555
+ Gittu
+ 20
+ MDS
+ 16
+ 1000
+ C programming
+ 2019
+ 87.0
+
+
+ 666
+ Isha
+ 30
+ MBAN
+ 16
+ 1000
+ C programming
+ 2019
+ 87.0
+
+
+ 777
+ Heidy
+ 30
+ MBAN
+ 16
+ 1000
+ C programming
+ 2019
+ 87.0
+
+
+ 888
+ Angela
+ 27
+ MBAN
+ 16
+ 1000
+ C programming
+ 2019
+ 87.0
+
+
+ 999
+ Jason
+ 30
+ MBAN
+ 16
+ 1000
+ C programming
+ 2019
+ 87.0
+
+
+ 111
+ Catherine
+ 23
+ MBAN
+ 17
+ 1111
+ Introduction to Genomics
+ 2019
+ 87.0
+
+
+ 222
+ Tiff
+ 28
+ MDS
+ 17
+ 1111
+ Introduction to Genomics
+ 2019
+ 87.0
+
+
+ 333
+ John
+ 23
+ MBAN
+ 17
+ 1111
+ Introduction to Genomics
+ 2019
+ 87.0
+
+
+ 444
+ Amir
+ 28
+ MBAN
+ 17
+ 1111
+ Introduction to Genomics
+ 2019
+ 87.0
+
+
+ 555
+ Gittu
+ 20
+ MDS
+ 17
+ 1111
+ Introduction to Genomics
+ 2019
+ 87.0
+
+
+ 666
+ Isha
+ 30
+ MBAN
+ 17
+ 1111
+ Introduction to Genomics
+ 2019
+ 87.0
+
+
+ 777
+ Heidy
+ 30
+ MBAN
+ 17
+ 1111
+ Introduction to Genomics
+ 2019
+ 87.0
+
+
+ 888
+ Angela
+ 27
+ MBAN
+ 17
+ 1111
+ Introduction to Genomics
+ 2019
+ 87.0
+
+
+ 999
+ Jason
+ 30
+ MBAN
+ 17
+ 1111
+ Introduction to Genomics
+ 2019
+ 87.0
+
+
+
+
+
+
Note
+
But in real life, we usually perform joins on a column, and we will discuss some types of joins on columns in the following sections. Since we are performing joins on a column, we need to pass that information using the ON
keyword to give which columns are used to stitch the tables together
+
+
+
+
1.6.3. Inner join
+
Inner join only returns the matching rows from the left and right tables.
+
+
+
+
* postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres
+
+
+
+
+
+
+ student_no
+ stud_name
+ age
+ course_name
+
+
+
+
+ 111
+ Catherine
+ 23
+ TPCS INFO TECH
+
+
+ 111
+ Catherine
+ 23
+ Data Visualization
+
+
+ 222
+ Tiff
+ 28
+ Health and Technology
+
+
+ 222
+ Tiff
+ 28
+ Web and Cloud Computing
+
+
+ 222
+ Tiff
+ 28
+ Spark Programming
+
+
+ 333
+ John
+ 23
+ Parallel Computing
+
+
+ 444
+ Amir
+ 28
+ Large Scale Infrastructures
+
+
+ 444
+ Amir
+ 28
+ TPCS INFO TECH
+
+
+ 555
+ Gittu
+ 20
+ TPCS INFO TECH
+
+
+ 555
+ Gittu
+ 20
+ Health and Technology
+
+
+ 666
+ Isha
+ 30
+ Data Visualization
+
+
+ 666
+ Isha
+ 30
+ Parallel Computing
+
+
+ 888
+ Angela
+ 27
+ Spark Programming
+
+
+ 999
+ Jason
+ 30
+ TPCS INFO TECH
+
+
+ 999
+ Jason
+ 30
+ Data Visualization
+
+
+
+
+
+
+
Note
+
In the returned table, student “Heidy” is missing as that student is not taking any courses and is not mentioned in the courses
. Also, the courses “C programming” and “Introduction to Genomics” are missing since no students from our student
table are taking these courses.
+
+
+
+
1.6.4. Outer join
+
An outer join returns all the rows from one or both of the tables that join. There are 3 variations of it.
+
+
1.6.4.1. Left outer
+
The first table that appears in the query is the left table, and the one appearing after the LEFT OUTER JOIN
keyword is the right table.
+
A left outer join returns all rows from the left table (matching or not), in addition to the matching rows from both tables. So the non-matching rows from the left table are assigned null values in the columns that belong to the right table.
+
If you think about it from a Venn diagram perspective, it will look like…
+
+
+
+
+
* postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres
+
+
+
+
+
+
+ student_no
+ stud_name
+ age
+ course_name
+
+
+
+
+ 111
+ Catherine
+ 23
+ TPCS INFO TECH
+
+
+ 111
+ Catherine
+ 23
+ Data Visualization
+
+
+ 222
+ Tiff
+ 28
+ Health and Technology
+
+
+ 222
+ Tiff
+ 28
+ Web and Cloud Computing
+
+
+ 222
+ Tiff
+ 28
+ Spark Programming
+
+
+ 333
+ John
+ 23
+ Parallel Computing
+
+
+ 444
+ Amir
+ 28
+ Large Scale Infrastructures
+
+
+ 444
+ Amir
+ 28
+ TPCS INFO TECH
+
+
+ 555
+ Gittu
+ 20
+ TPCS INFO TECH
+
+
+ 555
+ Gittu
+ 20
+ Health and Technology
+
+
+ 666
+ Isha
+ 30
+ Data Visualization
+
+
+ 666
+ Isha
+ 30
+ Parallel Computing
+
+
+ 888
+ Angela
+ 27
+ Spark Programming
+
+
+ 999
+ Jason
+ 30
+ TPCS INFO TECH
+
+
+ 999
+ Jason
+ 30
+ Data Visualization
+
+
+ 777
+ Heidy
+ 30
+ None
+
+
+
+
+
+
+
1.6.4.2. Right outer
+
It behaves exactly in the same way as a left join, except that it keeps all rows from the right table and only the matching ones from the left table.
+
If you think about it from a Venn diagram perspective, it will look like.
+
+
+
+
+
* postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres
+
+
+
+
+
+
+ student_no
+ stud_name
+ age
+ course_name
+
+
+
+
+ 111
+ Catherine
+ 23
+ TPCS INFO TECH
+
+
+ 111
+ Catherine
+ 23
+ Data Visualization
+
+
+ 222
+ Tiff
+ 28
+ Health and Technology
+
+
+ 222
+ Tiff
+ 28
+ Web and Cloud Computing
+
+
+ 222
+ Tiff
+ 28
+ Spark Programming
+
+
+ 333
+ John
+ 23
+ Parallel Computing
+
+
+ 444
+ Amir
+ 28
+ Large Scale Infrastructures
+
+
+ 444
+ Amir
+ 28
+ TPCS INFO TECH
+
+
+ 555
+ Gittu
+ 20
+ TPCS INFO TECH
+
+
+ 555
+ Gittu
+ 20
+ Health and Technology
+
+
+ 666
+ Isha
+ 30
+ Data Visualization
+
+
+ 666
+ Isha
+ 30
+ Parallel Computing
+
+
+ 888
+ Angela
+ 27
+ Spark Programming
+
+
+ 999
+ Jason
+ 30
+ TPCS INFO TECH
+
+
+ 999
+ Jason
+ 30
+ Data Visualization
+
+
+ None
+ None
+ None
+ C programming
+
+
+ None
+ None
+ None
+ Introduction to Genomics
+
+
+
+
+
+
+
1.6.4.3. Full outer
+
left join + right join = full outer join.
+
It retrieves matching and non-matching rows from both tables.
+
If you think about it from a Venn diagram perspective, it will look like.
+
+
+
+
+
* postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres
+
+
+
+
+
+
+ student_no
+ stud_name
+ age
+ course_name
+
+
+
+
+ 111
+ Catherine
+ 23
+ TPCS INFO TECH
+
+
+ 111
+ Catherine
+ 23
+ Data Visualization
+
+
+ 222
+ Tiff
+ 28
+ Health and Technology
+
+
+ 222
+ Tiff
+ 28
+ Web and Cloud Computing
+
+
+ 222
+ Tiff
+ 28
+ Spark Programming
+
+
+ 333
+ John
+ 23
+ Parallel Computing
+
+
+ 444
+ Amir
+ 28
+ Large Scale Infrastructures
+
+
+ 444
+ Amir
+ 28
+ TPCS INFO TECH
+
+
+ 555
+ Gittu
+ 20
+ TPCS INFO TECH
+
+
+ 555
+ Gittu
+ 20
+ Health and Technology
+
+
+ 666
+ Isha
+ 30
+ Data Visualization
+
+
+ 666
+ Isha
+ 30
+ Parallel Computing
+
+
+ 888
+ Angela
+ 27
+ Spark Programming
+
+
+ 999
+ Jason
+ 30
+ TPCS INFO TECH
+
+
+ 999
+ Jason
+ 30
+ Data Visualization
+
+
+ None
+ None
+ None
+ C programming
+
+
+ None
+ None
+ None
+ Introduction to Genomics
+
+
+ 777
+ Heidy
+ 30
+ None
+
+
+
+
+
+
+
+
1.6.5. Summarize:
+
CARTESIAN JOIN: returns the Cartesian product of the sets of records from the two or more joined tables.
+
INNER JOIN: returns rows when there is a match in both tables.
+
LEFT JOIN: returns all rows from the left table, even if there are no matches in the right table.
+
RIGHT JOIN: returns all rows from the right table, even if there are no matches in the left table.
+
FULL JOIN: combines the results of both left and right outer joins.
+
We learned now about the joins. You know now how to join 2 tables. Once you joined 2 tables its sort of behave like another table that you apply the operations what we learned.
+
+
+
+
* postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres
+
+
+
+
+
+
+ stud_name
+ course_name
+ course_year
+
+
+
+
+ Isha
+ Data Visualization
+ 2021
+
+
+ Catherine
+ Data Visualization
+ 2019
+
+
+ Jason
+ Data Visualization
+ 2019
+
+
+
+
+
+
Important
+
We can have all the keywords we learned in a single SQL query, and we have come across some in previous examples. BUT
the order of SQL keywords DOES
matter: SELECT, FROM, JOIN, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT.
+
+
+
See also
+
We experienced performing joins on 2 tables, but joins can also be performed on multiple tables. Multi joins in SQL work by progressively creating derived tables one after the other. Here is the link that explains this process
+
+
+
+
1.6.6. Checkpoint 3 !! Take some time and think if you can…
+
+
+
+
1.6.7. Check point 3 exercise.
+
+
1.6.7.1. iclicker questions
+
Question 1: Spot the issue, if any, in this SQL query
+
select s . stud_name , c . course_name , c . course_year
+FROM students AS s
+INNER JOIN
+courses AS c
+ON s . student_no = c . student_no
+ORDER BY course_year DESC
+WHERE course_name = 'Data Visualization' ;
+
+
+
A: There are some issues with the join key
+
B: No issues
+
C: Can’t specify alias when performing a join
+
D: ORDER BY need to come after the WHERE clause
+
+
Question 2: “In this Venn diagram green color indicate left outer join” - is this statement TRUE/FALSE?
+
+
A: TRUE
+
B: FALSE
+
+
Answer: FALSE
+
The following figure is what indicates the left outer join.
+
+
But what is given here is a special scenario where we apply left join to be useful. For example, what if we want to find all students who are not taking any courses?
+
SELECT s.student_no,s.stud_name,s.age,c.course_name
+FROM
+students AS s
+LEFT OUTER JOIN
+courses AS c
+ON
+s.student_no = c.student_no
+WHERE c.student_no is NULL
+
+
+student_no
+stud_name
+age
+course_name
+
+
+
+777
+Heidy
+30
+None
+
+
+
+
Ahaa..! looks like “heidy” is not taking any courses; we need to check with her to see why she is not taking any courses :)
+
+
+
+
+
+
1.7. 🏁 Finish line 🏁 🍺
+
Are you able to recollect our 3 checkpoints?
+
+Checkpoint 1: Take some time and think if you can…
+
+Select columns and bring in rows just what we needed (using SELECT & WHERE)
+Return DISTINCT elements
+ORDER BY rows returned based on column(s)
+LIMIT the number of rows returned
+
+
+Checkpoint 2: Take some time and think if you can…
+
+
+Checkpoint 3: Take some time and think if you can…
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/_build/html/objects.inv b/_build/html/objects.inv
new file mode 100644
index 0000000..4f8d054
--- /dev/null
+++ b/_build/html/objects.inv
@@ -0,0 +1,6 @@
+# Sphinx inventory version 2
+# Project: Python
+# Version:
+# The remainder of this file is compressed using zlib.
+xڅ
+0y}c{j/>,*7JMfK2uMgNLXCtjqwI5cTH"3v 8id`b2~, NgAĪǀ})R%ނL+RoPz
\ No newline at end of file
diff --git a/_build/html/search.html b/_build/html/search.html
new file mode 100644
index 0000000..2baf759
--- /dev/null
+++ b/_build/html/search.html
@@ -0,0 +1,230 @@
+
+
+
+
+
+
+
+ Search — CPSC 304 Introduction to Relational Databases
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Search
+
+
+
+
+ Please activate JavaScript to enable the search
+ functionality.
+
+
+
+
+
+
+ Searching for multiple words only shows matches that contain
+ all words.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/_build/html/searchindex.js b/_build/html/searchindex.js
new file mode 100644
index 0000000..f19bda2
--- /dev/null
+++ b/_build/html/searchindex.js
@@ -0,0 +1 @@
+Search.setIndex({docnames:["intro","lectures/lecture2"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":4,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":3,"sphinx.domains.rst":2,"sphinx.domains.std":2,"sphinx.ext.intersphinx":1,"sphinxcontrib.bibtex":7,sphinx:56},filenames:["intro.md","lectures/lecture2.ipynb"],objects:{},objnames:{},objtypes:{},terms:{"0":1,"10":1,"100":1,"1000":1,"11":1,"111":1,"1111":1,"12":1,"13":1,"14":1,"15":1,"153":1,"16":1,"17":1,"18":1,"20":1,"2019":1,"2020":1,"2021":1,"2022":1,"22":1,"222":1,"23":1,"24":1,"25":1,"27":1,"28":1,"29":1,"30":1,"333":1,"35":1,"4":1,"444":1,"5":1,"5432":1,"555":1,"56":1,"6":1,"666":1,"66666666666667":1,"7":1,"70588235294117":1,"777":1,"78":1,"8":1,"80":1,"81":1,"83":1,"85":1,"86":1,"87":1,"88":1,"888":1,"9":1,"90":1,"91":1,"93":1,"98":1,"999":1,"case":1,"default":1,"do":1,"final":1,"float":1,"function":1,"import":1,"null":1,"return":1,"true":1,A:1,AND:1,And:1,BUT:1,But:1,By:1,For:1,IF:1,INTO:1,If:1,In:1,It:1,NOT:1,No:1,ON:1,The:1,There:1,To:1,abl:1,about:1,abov:1,achiev:1,across:1,addit:1,advanc:1,affect:1,after:1,ag:1,aggregated_column:1,ahaa:1,algebra:1,alias:1,all:1,also:1,am:1,amazonaw:1,america:1,amir:1,an:1,analysi:1,angela:1,ani:1,anoth:1,answer:1,appear:1,appli:1,appropri:1,ar:1,asc:1,asia:1,assign:1,avail:1,averag:1,avg:1,b:1,base:1,basic:1,becom:1,been:1,befor:1,behav:1,belong:1,below:1,both:1,bring:1,c1:1,c2:1,c:1,canada:1,captur:1,cartesian:1,catherin:1,caus:1,chang:1,china:1,choos:1,classroom:1,claus:1,cloud:1,code:1,color:1,column1:1,column2:1,column:1,columnn:1,com:1,combin:1,come:1,command:1,commonli:1,comput:1,connect:1,consid:1,consist:1,constraint:1,construct:0,contin:1,count:1,countryfrom:1,coupl:1,cours:1,course_nam:1,course_percentag:1,course_year:1,crash:1,creat:1,crucial:1,cwq2624hiuap:1,d:1,data:1,databas:1,db_host:1,db_pass:1,db_user:1,deal:1,defin:1,deriv:1,desc:1,descript:1,detail:1,develop:1,diagram:1,differ:1,discuss:1,divid:1,doe:1,done:1,dotenv:1,down:1,driver:1,drop:1,duplic:1,e:1,eg:1,element:1,end:1,entir:1,environ:1,equival:1,error:1,etc:1,europ:1,even:1,everyth:1,exactli:1,examin:1,exampl:1,except:1,exist:1,experienc:1,explain:1,express:1,f:1,fals:1,far:1,februari:1,few:1,figur:1,filter:1,find:1,first:1,firstnam:1,follow:1,foreign:1,form:1,franc:1,from:1,gain:1,gender:1,gener:1,genom:1,georg:1,germani:1,get:1,gittu:1,give:1,given:1,got:1,green:1,group_condit:1,grouping_column:1,gui:1,health:1,heidi:1,help:1,her:1,here:1,highest:1,host:1,how:1,i:1,id:1,includ:1,india:1,indic:1,individu:1,info:1,inform:1,infrastructur:1,insert:1,insid:1,insight:1,instead:1,integ:1,integr:1,interact:1,interest:1,interfac:1,introduct:1,isha:1,issu:1,its:1,jame:1,jason:1,jenn:1,john:1,join_condit:1,join_typ:1,jupyt:1,just:1,keep:1,kei:1,keyword:1,kind:1,larg:1,left_tabl:1,less:1,let:1,level:1,lida:1,life:1,like:1,linda:1,link:1,list:1,load:1,load_dotenv:1,load_ext:1,look:1,lot:1,m:1,major:1,make:1,manag:1,mani:1,match:1,matt:1,matter:1,max:1,maximum:1,mban:1,md:1,meaning:1,memori:1,mention:1,might:1,min:1,mind:1,minimum:1,miss:1,more:1,multi:1,multipl:1,must:1,n:1,name:1,need:1,next:1,non:1,none:1,north:1,notebook:1,notic:1,now:1,number:1,numberofrow:1,odbc:1,older:1,onc:1,one:1,ones:1,onli:1,oper:1,option:1,origin:1,os:1,other:1,our:1,out:1,output:1,parallel:1,pass:1,password:1,peopl:1,peoplet:1,percentag:1,perform:1,person:1,personnam:1,perspect:1,pgadmin:1,possibl:1,postgr:1,postgresql:1,previou:1,primari:1,probabl:1,process:1,product:1,program:1,progress:1,psycopg2:1,queri:1,rais:1,rd:1,readabl:1,real:1,recollect:1,record:1,recreat:1,refer:1,regular:1,relat:1,rememb:1,remov:1,requir:1,restrict:1,result:1,retriev:1,rewrit:1,right_tabl:1,row:1,run:1,sai:1,same:1,scale:1,scenario:1,schema:1,score:1,script:1,section:1,see:1,select:1,serial:1,session:1,set:1,she:1,simplest:1,sinc:1,singl:1,situat:1,slow:1,so:1,sofia:1,soon:1,sort:1,spark:1,special:1,specifi:1,spot:1,statement:1,stitch:1,structur:1,stud_nam:1,student:1,student_no:1,student_no_1:1,subqueri:1,sum:1,summari:1,syntax:1,t1:1,t:1,tabl:1,table1:1,table_nam:1,tablenam:1,tech:1,technolog:1,tend:1,text:1,than:1,thei:1,them:1,thi:1,thing:1,those:1,though:1,throughout:1,ti:1,tie:1,tiff:1,toad:1,togeth:1,top:1,topic:1,tpc:1,tri:1,two:1,type:1,uk:1,unaggreg:1,under:0,understand:1,uniqu:1,univers:1,until:1,usa:1,user:1,usual:1,valu:1,variat:1,varieti:1,variou:1,venn:1,veri:1,via:1,visual:1,wai:1,wait:1,want:1,we:1,web:1,well:1,were:1,west:1,what:1,when:1,where:1,which:1,who:1,why:1,within:1,without:1,work:1,write:1,wrong:1,your:1},titles:["CPSC 304 Introduction to Relational Databases","1. Lecture 2: SQL - Class 2"],titleterms:{"1":1,"2":1,"3":1,"304":0,"class":1,AS:1,BY:1,agenda:1,aggreg:1,alia:1,can:1,check:1,checkpoint:1,cpsc:0,cross:1,databas:0,distinct:1,exercis:1,finish:1,full:1,group:1,have:1,iclick:1,inner:1,introduct:0,join:1,know:1,lap:1,learn:1,lectur:1,left:1,limit:1,line:1,object:1,order:1,outer:1,point:1,question:1,reason:1,refresh:1,relat:0,right:1,s:1,some:1,sql:1,summar:1,take:1,think:1,time:1,todai:1,up:1,us:1,warm:1,you:1}})
\ No newline at end of file
diff --git a/_build/jupyter_execute/lectures/lecture2.ipynb b/_build/jupyter_execute/lectures/lecture2.ipynb
new file mode 100644
index 0000000..e8bf15d
--- /dev/null
+++ b/_build/jupyter_execute/lectures/lecture2.ipynb
@@ -0,0 +1,5222 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "3867df60",
+ "metadata": {},
+ "source": [
+ "# Lecture 2: SQL - Class 2\n",
+ "Gittu George, February 24 2022"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0c890d68",
+ "metadata": {},
+ "source": [
+ "## Today's Agenda\n",
+ "\n",
+ "- Refresher (Warm-up)\n",
+ "- Refresher exercise\n",
+ "- Learning objectives\n",
+ "- More SQL commands (lap 1)\n",
+ "- Aggregations (lap 2)\n",
+ "- Grouping (lap 2)\n",
+ "- Joins (lap 3)\n",
+ "- Summary (finish line)\n",
+ "\n",
+ "## Warm-up ☕️\n",
+ "### Refresher!! Take some time and think if you know...\n",
+ "\n",
+ "- What are databases?\n",
+ "- Where is it commonly used?\n",
+ "- How to set up a database?\n",
+ "- General structure of a database\n",
+ "- Various ways of interacting with the database, using\n",
+ " - command-line interface\n",
+ " - ODBC drivers via developer interfaces like pgadmin and toad.\n",
+ " - jupyter notebook ( which we will be using throughout the course)\n",
+ "- To create a table and various data types \n",
+ "- Integrity constraints such as primary key and foreign key\n",
+ "- Basic SQL commands like SELECT, FROM, and WHERE\n",
+ "\n",
+ "In this class, we will be using the tables that we created in lecture 1. Here are the scripts if you want to recreate the classroom exercise table.\n",
+ "\n",
+ "```{toggle}\n",
+ "\n",
+ "```sql\n",
+ "DROP TABLE IF EXISTS students,courses;\n",
+ "\n",
+ "CREATE TABLE IF NOT EXISTS students(\n",
+ "student_no integer PRIMARY KEY, \n",
+ "stud_name text,\n",
+ "age integer,\n",
+ "major text);\n",
+ "\n",
+ "CREATE TABLE IF NOT EXISTS courses(\n",
+ "id SERIAL PRIMARY KEY,\n",
+ "student_no integer, \n",
+ "course_name text,\n",
+ "course_year integer,\n",
+ "course_percentage float);\n",
+ "\n",
+ "INSERT INTO students(student_no,stud_name,age,major) VALUES (111,'Catherine',23,'MBAN'),\n",
+ "(222,'Tiff',28,'MDS'),\n",
+ "(333,'John',23,'MBAN'),\n",
+ "(444,'Amir',28,'MBAN'),\n",
+ "(555,'Gittu',20,'MDS'),\n",
+ "(666,'Isha',30,'MBAN'),\n",
+ "(777,'Heidy',30,'MBAN'),\n",
+ "(888,'Angela',27,'MBAN'),\n",
+ "(999,'Jason',30,'MBAN');\n",
+ "\n",
+ "INSERT INTO courses(student_no,course_name,course_year,course_percentage) VALUES (111,'TPCS INFO TECH',2019,88),\n",
+ "(111,'Data Visualization',2019,88),\n",
+ "(222,'Health and Technology',2020,80),\n",
+ "(222,'Web and Cloud Computing',2019,91),\n",
+ "(222,'Spark Programming',2019,90),\n",
+ "(333,'Parallel Computing',2019,90),\n",
+ "(444,'Large Scale Infrastructures',2019,83),\n",
+ "(444,'TPCS INFO TECH',2019,98),\n",
+ "(555,'TPCS INFO TECH',2020,78),\n",
+ "(555,'Health and Technology',2020,81),\n",
+ "(666,'Data Visualization',2021,85),\n",
+ "(666,'Parallel Computing',2019,87),\n",
+ "(888,'Spark Programming',2019,93),\n",
+ "(999,'TPCS INFO TECH',2019,98),\n",
+ "(999,'Data Visualization',2019,87),\n",
+ "(1000,'C programming',2019,87),\n",
+ "(1111,'Introduction to Genomics',2019,87);\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a754bc90-1a0a-4b5e-8063-78c16951e14d",
+ "metadata": {},
+ "source": [
+ "First of all let's load SQL to work on this notebook."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "5b537cb8-53cc-4bf2-82a3-560c697eef4a",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import os\n",
+ "import psycopg2\n",
+ "from dotenv import load_dotenv\n",
+ "load_dotenv()\n",
+ "host = os.environ.get('DB_HOST')\n",
+ "user = os.environ.get('DB_USER')\n",
+ "password = os.environ.get('DB_PASS')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "50f1cc3c-aaec-499f-883f-390832ddc3d8",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%load_ext sql"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "fedffc66-2974-4c48-b61a-8d38d74e5596",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'Connected: postgres@postgres'"
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%sql postgresql://{user}:{password}@{host}:5432/postgres"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "350fda14-b35c-40f8-9a7d-5b8c90032c4a",
+ "metadata": {
+ "tags": []
+ },
+ "source": [
+ "### Refresher exercise \n",
+ "\n",
+ "***Question 1:*** In this table `eg`\n",
+ "\n",
+ "| firstname | countryfrom | continent | gender | age |\n",
+ "|-----------|-------------|---------------|--------|-----|\n",
+ "| matt | usa | north america | M | 23 |\n",
+ "| jenn | uk | europe | F | 35 |\n",
+ "| guy | france | europe | M | 25 |\n",
+ "| james | china | asia | M | 29 |\n",
+ "| lida | india | asia | F | 56 |\n",
+ "| linda | canada | north america | F | 18 |\n",
+ "| sofia | germany | europe | F | 22 |\n",
+ "| george | india | asia | M | 29 |\n",
+ "\n",
+ "What will be returned from following SQL \n",
+ "```sql\n",
+ "select firstname,gender from eg where continent ='asia' AND age =29\n",
+ "```\n",
+ "A) george,M\n",
+ "\n",
+ "B) george,M,asia,29\n",
+ "\n",
+ "C) george,india,asia, M,29 \n",
+ "\n",
+ "D) M,george\n",
+ "\n",
+ "E) M,james\n",
+ "\n",
+ "```{toggle}\n",
+ "\n",
+ "***Answer: D***\n",
+ "\n",
+ "```{important}\n",
+ "Remember the order of the columns returned will be based on the order of columns that we specify within the select statement.\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "69eba2fb-8158-44e0-ae46-64410b0be50a",
+ "metadata": {},
+ "source": [
+ "***Question 2:*** Consider a scenario where you want to perform analysis on a table (peopletable) with 100 columns (including personname,age,gender,origin...etc.) that define a person. You are interested in seeing the name of individuals older than 90. Which SQL query is more appropriate in this situation?\n",
+ "\n",
+ "A) ***select * from peopletable where age > 90;***\n",
+ "\n",
+ "B) ***select personname,age from peopletable where age > 90;***\n",
+ "\n",
+ "B) ***select personname,age,gender,origin from peopletable where age < 90;***\n",
+ "\n",
+ "D) ***select personname from peopletable where age > 90;***\n",
+ "\n",
+ "```{toggle}\n",
+ "\n",
+ "***Answer: D***\n",
+ "\n",
+ "```{important}\n",
+ "Just bring the columns and rows that are needed. Even though `SELECT` and `WHERE` are very basic SQL commands, it's crucial when you are dealing with a large table\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9a7e0bbd-ae06-4b1b-b934-cdf23e081828",
+ "metadata": {},
+ "source": [
+ "## Learning objectives\n",
+ "- You will be able to create SQL queries using (Lap 1 & Lap 2)\n",
+ " - Distinct, \n",
+ " - ORDER BY, \n",
+ " - LIMIT, \n",
+ " - GROUP BY, \n",
+ " - alias AS\n",
+ "- You will learn about different kinds of joins and be able to create SQL queries that perform `JOINS`. (Lap 3)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a033eb62-9c25-4b69-8cb2-14a36cd9272b",
+ "metadata": {},
+ "source": [
+ "## Lap 1 🥛\n",
+ "### DISTINCT\n",
+ "\n",
+ "The DISTINCT statement is used only to return distinct elements from a table.\n",
+ "\n",
+ "***Syntax:***\n",
+ "\n",
+ "```sql\n",
+ "SELECT DISTINCT column1, column2, ...columnN\n",
+ "FROM tablename;\n",
+ "```\n",
+ "\n",
+ "`DISTINCT` is applied to all columns that follows the `DISTINCT` keyword. Say for eg if we give `DISTINCT column1, column2` then the combination of values in both `column1` and `column2` columns will be used for returning the unique combination (or removing the duplicate elements)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "d92b4b94-d2cd-4f44-86de-baf632b91ba6",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "9 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " course_name \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " Introduction to Genomics \n",
+ " \n",
+ " \n",
+ " Large Scale Infrastructures \n",
+ " \n",
+ " \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " Web and Cloud Computing \n",
+ " \n",
+ " \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " C programming \n",
+ " \n",
+ " \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[('Data Visualization',),\n",
+ " ('Introduction to Genomics',),\n",
+ " ('Large Scale Infrastructures',),\n",
+ " ('Spark Programming',),\n",
+ " ('Web and Cloud Computing',),\n",
+ " ('Health and Technology',),\n",
+ " ('C programming',),\n",
+ " ('TPCS INFO TECH',),\n",
+ " ('Parallel Computing',)]"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT DISTINCT course_name FROM courses;"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "1dc8e64b-e555-4580-9b95-1ea9af3d0273",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "11 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " course_name \n",
+ " course_year \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " C programming \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " \n",
+ " \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[('Parallel Computing', 2019),\n",
+ " ('C programming', 2019),\n",
+ " ('TPCS INFO TECH', 2019),\n",
+ " ('Spark Programming', 2019),\n",
+ " ('Web and Cloud Computing', 2019),\n",
+ " ('Health and Technology', 2020),\n",
+ " ('Introduction to Genomics', 2019),\n",
+ " ('Large Scale Infrastructures', 2019),\n",
+ " ('TPCS INFO TECH', 2020),\n",
+ " ('Data Visualization', 2021),\n",
+ " ('Data Visualization', 2019)]"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT DISTINCT course_name, course_year FROM courses;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "904d711d-aad3-4c57-8291-86b1ea1c8604",
+ "metadata": {},
+ "source": [
+ "### ORDER BY\n",
+ "\n",
+ "`ORDER BY` statement sorts the results returned by `SELECT` based on a sort expression.\n",
+ "\n",
+ "Syntax\n",
+ "```sql\n",
+ "SELECT column1, column2 ...columnN\n",
+ "FROM table_name\n",
+ "ORDER BY column1 [ASC | DESC], column2 [ASC | DESC] ....columnN [ASC|DESC];\n",
+ "```\n",
+ "\n",
+ "```{note}\n",
+ "By default, it will sort in ASC. So you can choose not to give ASC.\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "922e1099-7d1b-41ff-aa18-21492c59b20a",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "17 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " id \n",
+ " student_no \n",
+ " course_name \n",
+ " course_year \n",
+ " course_percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 16 \n",
+ " 1000 \n",
+ " C programming \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 17 \n",
+ " 1111 \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 91.0 \n",
+ " \n",
+ " \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 12 \n",
+ " 666 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 93.0 \n",
+ " \n",
+ " \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.0 \n",
+ " \n",
+ " \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 81.0 \n",
+ " \n",
+ " \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (14, 999, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (15, 999, 'Data Visualization', 2019, 87.0),\n",
+ " (16, 1000, 'C programming', 2019, 87.0),\n",
+ " (17, 1111, 'Introduction to Genomics', 2019, 87.0),\n",
+ " (4, 222, 'Web and Cloud Computing', 2019, 91.0),\n",
+ " (5, 222, 'Spark Programming', 2019, 90.0),\n",
+ " (6, 333, 'Parallel Computing', 2019, 90.0),\n",
+ " (7, 444, 'Large Scale Infrastructures', 2019, 83.0),\n",
+ " (8, 444, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (12, 666, 'Parallel Computing', 2019, 87.0),\n",
+ " (13, 888, 'Spark Programming', 2019, 93.0),\n",
+ " (3, 222, 'Health and Technology', 2020, 80.0),\n",
+ " (10, 555, 'Health and Technology', 2020, 81.0),\n",
+ " (9, 555, 'TPCS INFO TECH', 2020, 78.0),\n",
+ " (11, 666, 'Data Visualization', 2021, 85.0)]"
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT * \n",
+ "FROM courses\n",
+ "ORDER BY course_year;"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "57c57348-9def-4d46-8b9e-eac8d509cf60",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "17 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " id \n",
+ " student_no \n",
+ " course_name \n",
+ " course_year \n",
+ " course_percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 81.0 \n",
+ " \n",
+ " \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.0 \n",
+ " \n",
+ " \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 12 \n",
+ " 666 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 93.0 \n",
+ " \n",
+ " \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 16 \n",
+ " 1000 \n",
+ " C programming \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 17 \n",
+ " 1111 \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 91.0 \n",
+ " \n",
+ " \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(11, 666, 'Data Visualization', 2021, 85.0),\n",
+ " (10, 555, 'Health and Technology', 2020, 81.0),\n",
+ " (3, 222, 'Health and Technology', 2020, 80.0),\n",
+ " (9, 555, 'TPCS INFO TECH', 2020, 78.0),\n",
+ " (6, 333, 'Parallel Computing', 2019, 90.0),\n",
+ " (7, 444, 'Large Scale Infrastructures', 2019, 83.0),\n",
+ " (8, 444, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (12, 666, 'Parallel Computing', 2019, 87.0),\n",
+ " (13, 888, 'Spark Programming', 2019, 93.0),\n",
+ " (14, 999, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (15, 999, 'Data Visualization', 2019, 87.0),\n",
+ " (16, 1000, 'C programming', 2019, 87.0),\n",
+ " (1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (17, 1111, 'Introduction to Genomics', 2019, 87.0),\n",
+ " (2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (4, 222, 'Web and Cloud Computing', 2019, 91.0),\n",
+ " (5, 222, 'Spark Programming', 2019, 90.0)]"
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT * \n",
+ "FROM courses\n",
+ "ORDER BY course_year DESC;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "25e1020a-93a1-48e9-a763-80674b98b46c",
+ "metadata": {},
+ "source": [
+ "### LIMIT\n",
+ "\n",
+ "Until now, we were returning everything that our SQL query returns. `LIMIT` statement is used to limit the number of rows that are returned. \n",
+ "\n",
+ "syntax:\n",
+ "\n",
+ "```sql\n",
+ "SELECT column1, column2, ...columnN\n",
+ "FROM tablename\n",
+ "LIMIT numberofrows;\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "ef67fccb-2300-4109-b626-8bc7580b29c2",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "2 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " id \n",
+ " student_no \n",
+ " course_name \n",
+ " course_year \n",
+ " course_percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (2, 111, 'Data Visualization', 2019, 88.0)]"
+ ]
+ },
+ "execution_count": 8,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT * \n",
+ "FROM courses\n",
+ "LIMIT 2;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "74228e90-a013-4c9b-b34b-c7cc8fdea343",
+ "metadata": {},
+ "source": [
+ "`LIMIT` keyword is used in a variety of situations. Here are a few cases\n",
+ "\n",
+ "- ***Memory Management:*** Say you just want to look at the output your query returns. If you are dealing with lots and lots of rows, returning the entire rows can slow down the query, cause memory issues, and finally crash your jupyter. In these cases, you can use `LIMIT`. \n",
+ "\n",
+ "- ***Interest in the first few rows:*** If we are interested in just the first N rows, we can achieve that using `LIMIT`. People tend to use `LIMIT` a lot when they want to return the top 10 rows after performing an `ORDER BY`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "dac09f4e-ebbf-4bf1-9c13-d45c3249cf30",
+ "metadata": {},
+ "source": [
+ "Let's apply all the statements that we learned in one statement.\n",
+ "\n",
+ "***Question:*** List out the row that got the highest `course_percentage` for the `Data Visualization` course."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "id": "368c7bdf-7c5c-4e8c-81ae-02de2044b7d8",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "1 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " id \n",
+ " student_no \n",
+ " course_name \n",
+ " course_year \n",
+ " course_percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(2, 111, 'Data Visualization', 2019, 88.0)]"
+ ]
+ },
+ "execution_count": 9,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT * \n",
+ "FROM courses\n",
+ "WHERE course_name = 'Data Visualization'\n",
+ "ORDER BY course_percentage DESC\n",
+ "LIMIT 1;"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "81a06062-3b03-4c69-ac1a-d592e043b437",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "8 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " age \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 18 \n",
+ " \n",
+ " \n",
+ " 22 \n",
+ " \n",
+ " \n",
+ " 23 \n",
+ " \n",
+ " \n",
+ " 25 \n",
+ " \n",
+ " \n",
+ " 29 \n",
+ " \n",
+ " \n",
+ " 29 \n",
+ " \n",
+ " \n",
+ " 35 \n",
+ " \n",
+ " \n",
+ " 56 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(18,), (22,), (23,), (25,), (29,), (29,), (35,), (56,)]"
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%sql select age from eg order by age"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ca99d216-561f-4cc2-ad74-9b6dc6174a76",
+ "metadata": {},
+ "source": [
+ "### Checkpoint 1 !! Take some time and think if you can...\n",
+ "\n",
+ "- Select columns and bring in rows just what we need (using `SELECT` & `WHERE`)\n",
+ "- Return `DISTINCT` elements\n",
+ "- `ORDER BY` rows returned based on column(s)\n",
+ "- `LIMIT` the number of rows returned\n",
+ "\n",
+ "Now we will learn some more advanced SQL operations to gain more insight into the data. But, before that, let's do some exercise. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9fd2624e-57a1-4250-9833-a52db657be86",
+ "metadata": {},
+ "source": [
+ "### Check point 1 exercise.\n",
+ "\n",
+ "#### iclicker questions\n",
+ "\n",
+ "Answer the following questions using the table `eg`\n",
+ "\n",
+ "| firstname | countryfrom | continent | gender | age |\n",
+ "|-----------|-------------|---------------|--------|-----|\n",
+ "| matt | usa | north america | M | 23 |\n",
+ "| jenn | uk | europe | F | 35 |\n",
+ "| guy | france | europe | M | 25 |\n",
+ "| james | china | asia | M | 29 |\n",
+ "| lida | india | asia | F | 56 |\n",
+ "| linda | canada | north america | F | 18 |\n",
+ "| sofia | germany | europe | F | 22 |\n",
+ "| george | india | asia | M | 29 |\n",
+ "\n",
+ "\n",
+ "***Question 1:*** How many elements will be returned from this SQL \n",
+ "\n",
+ "```sql\n",
+ "select DISTINCT gender,firstname from eg\n",
+ "```\n",
+ "\n",
+ "A) 2\n",
+ "\n",
+ "B) 8\n",
+ "\n",
+ "C) 4 \n",
+ "\n",
+ "```{toggle}\n",
+ "***Answer: B***\n",
+ "```\n",
+ "***Question 2:*** What will be the first value returned from this SQL \n",
+ "\n",
+ "```sql\n",
+ "select age from eg order by age\n",
+ "```\n",
+ "A) 23\n",
+ "\n",
+ "B) 18\n",
+ "\n",
+ "C) 56\n",
+ "\n",
+ "D) 35\n",
+ "\n",
+ "```{toggle}\n",
+ "***Answer: B***\n",
+ "```\n",
+ "\n",
+ "#### Reasoning Question\n",
+ "\n",
+ "***Question 1:*** Write a SQL query to list the row that got the highest `course_percentage` for the `TPCS INFO TECH'` course. Can you spot any issues by examining the original table?\n",
+ "\n",
+ "```{toggle}\n",
+ "\n",
+ "You probably might have got this SQL query by changing the SQL query that we discussed for the \"Data Visualization\" course. \n",
+ "\n",
+ "***SELECT * FROM courses\n",
+ "WHERE course_name = 'TPCS INFO TECH'\n",
+ "ORDER BY course_percentage DESC\n",
+ "LIMIT 1;***\n",
+ "\n",
+ "This gives you \n",
+ "\n",
+ "| id | student_no | course_name | course_year | course_percentage |\n",
+ "|---:|-----------:|---------------:|------------:|------------------:|\n",
+ "| 8 | 444 | TPCS INFO TECH | 2019 | 98.0 |\n",
+ "\n",
+ "What are possible issues? Take out the `LIMIT`, and you might notice there is a tie, and 2 students, 444 and 999 scored the highest.\n",
+ "\n",
+ "\n",
+ "***SELECT * FROM courses\n",
+ "WHERE course_name = 'TPCS INFO TECH'\n",
+ "ORDER BY course_percentage DESC***\n",
+ "\n",
+ "\n",
+ "| id | student_no | course_name | course_year | course_percentage |\n",
+ "|---:|-----------:|---------------:|------------:|------------------:|\n",
+ "| 8 | 444 | TPCS INFO TECH | 2019 | 98.0 |\n",
+ "| 14 | 999 | TPCS INFO TECH | 2019 | 98.0 |\n",
+ "| 1 | 111 | TPCS INFO TECH | 2019 | 88.0 |\n",
+ "| 9 | 555 | TPCS INFO TECH | 2020 | 78.0 |\n",
+ "\n",
+ "So even though many of them use a combination of `ORDER BY` and `LIMIT` for these scenarios we might run into situations like this. There are a couple of ways to deal with these kinds of scenarios, and we will learn about `subquery` in our next class, which will help you capture ties."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "91ab04a8-4456-4653-9348-4ad153493bb8",
+ "metadata": {},
+ "source": [
+ "## Lap 2 🧋\n",
+ "### Aggregations\n",
+ "\n",
+ "So far, we have returned columns in our select statement. We can also use aggregation functions that operate on rows to summarize the data in the form of a single value. Here is a list of aggregation functions in SQL:\n",
+ "\n",
+ "| Function | Description |\n",
+ "|----------|------------------|\n",
+ "| MAX() | maximum value |\n",
+ "| MIN() | minimum value |\n",
+ "| AVG() | average value |\n",
+ "| SUM() | sum of values |\n",
+ "| COUNT() | number of values |"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "id": "66c023e1-c594-4c0c-8809-7dc54ee6ca4b",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "1 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " count \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 17 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(17,)]"
+ ]
+ },
+ "execution_count": 11,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT COUNT(course_name) FROM courses;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7d0df81a-5c7c-48e3-b05a-4410be8f4840",
+ "metadata": {},
+ "source": [
+ "The above query is counting number of values in the column `course_name`. it's also sort of like counting the number of rows. We can also pass the `DISTINCT` columns into these operations. For example, the below query will find the number of courses available in the university."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "id": "ca947761-3728-4594-83d4-d6484927b7cf",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "1 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " count \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 9 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(9,)]"
+ ]
+ },
+ "execution_count": 12,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT COUNT(DISTINCT course_name) FROM courses;"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "id": "e505b586-f4ea-436e-8ade-abb9e5892638",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "1 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " avg \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 87.70588235294117 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(87.70588235294117,)]"
+ ]
+ },
+ "execution_count": 13,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "select AVG(course_percentage) \n",
+ "FROM courses"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c28c9c2e-6275-4e8c-bee9-36f5795f62eb",
+ "metadata": {},
+ "source": [
+ "Few things to keep in mind when dealing with the aggregation function\n",
+ "\n",
+ "- You are not restricted in using just one aggregation function in the SELECT statement.✅\n",
+ "\n",
+ "```sql\n",
+ "SELECT COUNT(DISTINCT course_name),max(course_percentage) FROM courses;\n",
+ "```\n",
+ "\n",
+ "- You CAN'T use aggregations and regular columns in a single query. You can use only when you have a `GROUP BY` clause. (will discuss soon)❌\n",
+ "\n",
+ "```sql\n",
+ "SELECT COUNT(DISTINCT course_name),course_percentage FROM courses;\n",
+ "```\n",
+ "\n",
+ "- You CAN'T use aggregation function in a where clause. The following query is wrong ❌\n",
+ "\n",
+ "```sql\n",
+ "SELECT * FROM courses WHERE course_percentage < AVG(course_percentage);\n",
+ "```\n",
+ "\n",
+ "You can answer this question when we discuss subqueries in the next class (another reason to learn subqueries :) )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2164f169-3e19-4804-9985-4a2f392cbd13",
+ "metadata": {},
+ "source": [
+ "### Grouping\n",
+ "\n",
+ "The aggregations we learned in our previous session also become useful when using the `GROUP BY` statement. `GROUP BY` statement divides a table into groups of rows based on the values of one or more columns. Once this grouping is done, you can apply your aggregation to these groups.\n",
+ "\n",
+ "Syntax:\n",
+ "```sql\n",
+ "SELECT\n",
+ " grouping_columns, aggregated_columns\n",
+ "FROM\n",
+ " table1\n",
+ "GROUP BY\n",
+ " grouping_columns\n",
+ "```\n",
+ "\n",
+ "Example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "id": "7ac98229-6261-48fb-ac85-20435c5d325c",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "17 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " id \n",
+ " student_no \n",
+ " course_name \n",
+ " course_year \n",
+ " course_percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.0 \n",
+ " \n",
+ " \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 91.0 \n",
+ " \n",
+ " \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 81.0 \n",
+ " \n",
+ " \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ " 12 \n",
+ " 666 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 93.0 \n",
+ " \n",
+ " \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 16 \n",
+ " 1000 \n",
+ " C programming \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 17 \n",
+ " 1111 \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (3, 222, 'Health and Technology', 2020, 80.0),\n",
+ " (4, 222, 'Web and Cloud Computing', 2019, 91.0),\n",
+ " (5, 222, 'Spark Programming', 2019, 90.0),\n",
+ " (6, 333, 'Parallel Computing', 2019, 90.0),\n",
+ " (7, 444, 'Large Scale Infrastructures', 2019, 83.0),\n",
+ " (8, 444, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (9, 555, 'TPCS INFO TECH', 2020, 78.0),\n",
+ " (10, 555, 'Health and Technology', 2020, 81.0),\n",
+ " (11, 666, 'Data Visualization', 2021, 85.0),\n",
+ " (12, 666, 'Parallel Computing', 2019, 87.0),\n",
+ " (13, 888, 'Spark Programming', 2019, 93.0),\n",
+ " (14, 999, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (15, 999, 'Data Visualization', 2019, 87.0),\n",
+ " (16, 1000, 'C programming', 2019, 87.0),\n",
+ " (17, 1111, 'Introduction to Genomics', 2019, 87.0)]"
+ ]
+ },
+ "execution_count": 14,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "select * from courses;"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "id": "dc492d9a-f26d-494b-97c0-ef208c6c3187",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "9 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " course_name \n",
+ " avg \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 86.66666666666667 \n",
+ " \n",
+ " \n",
+ " Introduction to Genomics \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " Large Scale Infrastructures \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " Spark Programming \n",
+ " 91.5 \n",
+ " \n",
+ " \n",
+ " Web and Cloud Computing \n",
+ " 91.0 \n",
+ " \n",
+ " \n",
+ " Health and Technology \n",
+ " 80.5 \n",
+ " \n",
+ " \n",
+ " C programming \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " TPCS INFO TECH \n",
+ " 90.5 \n",
+ " \n",
+ " \n",
+ " Parallel Computing \n",
+ " 88.5 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[('Data Visualization', 86.66666666666667),\n",
+ " ('Introduction to Genomics', 87.0),\n",
+ " ('Large Scale Infrastructures', 83.0),\n",
+ " ('Spark Programming', 91.5),\n",
+ " ('Web and Cloud Computing', 91.0),\n",
+ " ('Health and Technology', 80.5),\n",
+ " ('C programming', 87.0),\n",
+ " ('TPCS INFO TECH', 90.5),\n",
+ " ('Parallel Computing', 88.5)]"
+ ]
+ },
+ "execution_count": 15,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "select course_name, AVG(course_percentage) \n",
+ "FROM courses\n",
+ "group by course_name;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d3e6fb09-9bf3-4c40-9933-158edd938eb6",
+ "metadata": {},
+ "source": [
+ "We can also perform a multi level grouping;"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "id": "a9edaba0-6db9-4c80-927c-2df18798d0f3",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "8 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " course_name \n",
+ " course_year \n",
+ " avg \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.5 \n",
+ " \n",
+ " \n",
+ " C programming \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.5 \n",
+ " \n",
+ " \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.5 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[('Parallel Computing', 2019, 88.5),\n",
+ " ('C programming', 2019, 87.0),\n",
+ " ('Health and Technology', 2020, 80.5),\n",
+ " ('Introduction to Genomics', 2019, 87.0),\n",
+ " ('Large Scale Infrastructures', 2019, 83.0),\n",
+ " ('TPCS INFO TECH', 2020, 78.0),\n",
+ " ('Data Visualization', 2021, 85.0),\n",
+ " ('Data Visualization', 2019, 87.5)]"
+ ]
+ },
+ "execution_count": 16,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "select course_name, course_year,AVG(course_percentage) \n",
+ "FROM courses\n",
+ "group by course_name, course_year\n",
+ "having AVG(course_percentage) <90 ;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "59b16985-fefc-4326-bfde-b7f55dcf9610",
+ "metadata": {},
+ "source": [
+ "Now, what if I want to see only the courses with an average of less than 90 %? We mentioned before that this kind of filtering (filtering on the aggregation function) is not possible using `WHERE` statement, and that's why we want the `HAVING` statement to do filtering using these aggregated values.\n",
+ "\n",
+ "### HAVING\n",
+ "\n",
+ "Syntax:\n",
+ "```sql\n",
+ "SELECT\n",
+ " grouping_columns, aggregated_columns\n",
+ "FROM\n",
+ " table1\n",
+ "GROUP BY\n",
+ " grouping_columns\n",
+ "HAVING\n",
+ " group_condition\n",
+ "\n",
+ "```\n",
+ "\n",
+ "```{important}\n",
+ "To summarize:\n",
+ "\n",
+ "- WHERE filters rows before grouping. It filters records in a table level\n",
+ "- HAVING filters groups after grouping. It filters records in a group level\n",
+ "```\n",
+ "\n",
+ "For example, let's get the question we raised at the end of the grouping section. I want to see the courses with a course average of less than 90 %? "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "id": "8210644e-b6bd-4106-aa3b-85520b30380a",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "6 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " course_name \n",
+ " avg \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 86.66666666666667 \n",
+ " \n",
+ " \n",
+ " Introduction to Genomics \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " Large Scale Infrastructures \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " Health and Technology \n",
+ " 80.5 \n",
+ " \n",
+ " \n",
+ " C programming \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " Parallel Computing \n",
+ " 88.5 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[('Data Visualization', 86.66666666666667),\n",
+ " ('Introduction to Genomics', 87.0),\n",
+ " ('Large Scale Infrastructures', 83.0),\n",
+ " ('Health and Technology', 80.5),\n",
+ " ('C programming', 87.0),\n",
+ " ('Parallel Computing', 88.5)]"
+ ]
+ },
+ "execution_count": 17,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "select course_name, AVG(course_percentage) \n",
+ "FROM courses\n",
+ "group by course_name\n",
+ "having AVG(course_percentage) <90 ;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ac4b81f6-37f8-4e28-914d-9d73aa271733",
+ "metadata": {},
+ "source": [
+ "### Using alias (AS)\n",
+ "\n",
+ "Until now, we have been referring tables as table names and columns as column names from the schema. But when writing SQL, we are not required to use the same column and table names as in the schema. Instead, we can create aliases for a column or a table using the keyword `AS`.\n",
+ "\n",
+ "Syntax:\n",
+ "```sql\n",
+ "SELECT\n",
+ " column1 [AS] c1,\n",
+ " column2 [AS] c2\n",
+ "FROM\n",
+ " table1 [AS] t1;\n",
+ "```\n",
+ "\n",
+ "It's entirely optional to use AS, but WHY do we want it? \n",
+ "- This makes code more readable\n",
+ "- You can return the columns with a more meaningful name \n",
+ "- Helps a lot when we do JOINS ( wait for the next topic)\n",
+ "\n",
+ "Let's rewrite our previous query using AS"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "id": "4ff59e7a-50e7-4c4a-8469-35ee6481ee29",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "8 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " course_name \n",
+ " course_year \n",
+ " average course percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.5 \n",
+ " \n",
+ " \n",
+ " C programming \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.5 \n",
+ " \n",
+ " \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.5 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[('Parallel Computing', 2019, 88.5),\n",
+ " ('C programming', 2019, 87.0),\n",
+ " ('Health and Technology', 2020, 80.5),\n",
+ " ('Introduction to Genomics', 2019, 87.0),\n",
+ " ('Large Scale Infrastructures', 2019, 83.0),\n",
+ " ('TPCS INFO TECH', 2020, 78.0),\n",
+ " ('Data Visualization', 2021, 85.0),\n",
+ " ('Data Visualization', 2019, 87.5)]"
+ ]
+ },
+ "execution_count": 18,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "select course_name, course_year,AVG(course_percentage) AS \"average course percentage\"\n",
+ "FROM courses AS c\n",
+ "group by course_name, course_year\n",
+ "having AVG(course_percentage) <90 ;"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "id": "d66a7187-7ea7-42d5-a90d-76e584456600",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "17 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " id \n",
+ " student_no \n",
+ " course_name \n",
+ " course_year \n",
+ " course_percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.0 \n",
+ " \n",
+ " \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 91.0 \n",
+ " \n",
+ " \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 81.0 \n",
+ " \n",
+ " \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ " 12 \n",
+ " 666 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 93.0 \n",
+ " \n",
+ " \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 16 \n",
+ " 1000 \n",
+ " C programming \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 17 \n",
+ " 1111 \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (3, 222, 'Health and Technology', 2020, 80.0),\n",
+ " (4, 222, 'Web and Cloud Computing', 2019, 91.0),\n",
+ " (5, 222, 'Spark Programming', 2019, 90.0),\n",
+ " (6, 333, 'Parallel Computing', 2019, 90.0),\n",
+ " (7, 444, 'Large Scale Infrastructures', 2019, 83.0),\n",
+ " (8, 444, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (9, 555, 'TPCS INFO TECH', 2020, 78.0),\n",
+ " (10, 555, 'Health and Technology', 2020, 81.0),\n",
+ " (11, 666, 'Data Visualization', 2021, 85.0),\n",
+ " (12, 666, 'Parallel Computing', 2019, 87.0),\n",
+ " (13, 888, 'Spark Programming', 2019, 93.0),\n",
+ " (14, 999, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (15, 999, 'Data Visualization', 2019, 87.0),\n",
+ " (16, 1000, 'C programming', 2019, 87.0),\n",
+ " (17, 1111, 'Introduction to Genomics', 2019, 87.0)]"
+ ]
+ },
+ "execution_count": 19,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT *\n",
+ "FROM courses;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a81789ef-1835-4a47-bc68-d172531eefbc",
+ "metadata": {},
+ "source": [
+ "### Checkpoint 2 !! Take some time and think if you can...\n",
+ "\n",
+ "- Do some aggregations and grouping queries using SQL.\n",
+ " - GROUP BY\n",
+ " - aggregation functions\n",
+ " - HAVING\n",
+ "\n",
+ "Until now, you deal with queries on a single table. What if we are interested in data from another table as well? For example, I am interested in seeing the course details (from the `courses` table) and the students (from the `students` table) related to those courses. In these situations, we use joins. Let's learn how to stitch tables together. Before that, let's do some exercise..."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f99b3fa8-0acd-4c08-891c-0c3dfc8865c5",
+ "metadata": {},
+ "source": [
+ "### Check point 2 exercise.\n",
+ "\n",
+ "#### iclicker questions\n",
+ "\n",
+ "***Question 1:*** Spot the issue, if any, in this SQL query \n",
+ "\n",
+ "```sql\n",
+ "SELECT COUNT(DISTINCT course_name),max(course_percentage),course_percentage \n",
+ "FROM courses ;\n",
+ "```\n",
+ "\n",
+ "A: Multiple aggregation functions in the SELECT statement\n",
+ "\n",
+ "B: No issues\n",
+ "\n",
+ "C: Can't use DISTINCT inside an aggregation function\n",
+ "\n",
+ "D: Can't use aggregations and regular columns in a single query\n",
+ "\n",
+ "```{toggle}\n",
+ "***Answer: D***\n",
+ "```\n",
+ "\n",
+ "***Question 2:*** Spot the issue, if any, in this SQL query \n",
+ "\n",
+ "```sql\n",
+ "SELECT COUNT(DISTINCT course_name),max(course_percentage),course_year\n",
+ "FROM courses \n",
+ "GROUP BY course_year;\n",
+ "```\n",
+ "\n",
+ "A: Multiple aggregation functions in the SELECT statement\n",
+ "\n",
+ "B: No issues\n",
+ "\n",
+ "C: Can't use DISTINCT inside an aggregation function\n",
+ "\n",
+ "D: Can't use aggregations and regular columns in a single query\n",
+ "\n",
+ "```{toggle}\n",
+ "***Answer: B***\n",
+ "```\n",
+ "\n",
+ "***Question 3:*** Spot the issue, if any, in this SQL query \n",
+ "\n",
+ "```sql\n",
+ "SELECT COUNT(DISTINCT course_name),max(course_percentage),course_year\n",
+ "FROM courses \n",
+ "WHERE course_name != 'TPCS INFO TECH'\n",
+ "GROUP BY course_year\n",
+ "having course_percentage < 90;\n",
+ "```\n",
+ "\n",
+ "A: No issues\n",
+ "\n",
+ "B: Can't use column `course_year` in `SELECT`\n",
+ "\n",
+ "C: Can't use WHERE when using aggregation functions\n",
+ "\n",
+ "D: Can't use course_percentage in a `HAVING` statement\n",
+ "\n",
+ "```{toggle}\n",
+ "***Answer: D***\n",
+ "```\n",
+ "\n",
+ "#### Reasoning Question\n",
+ "\n",
+ "***Question 1:*** We learned the `GROUP BY` clause, and we used it with aggregate functions. Using table `courses`, write a SQL query using `GROUP BY` without any aggregation function. Write your findings and learnings.\n",
+ "\n",
+ "```{toggle}\n",
+ "You must have tried a variety of SQL queries and got into error;\n",
+ "\n",
+ "***select * FROM courses\n",
+ "group by course_name;***\n",
+ "\n",
+ "***select course_year,course_name\n",
+ "FROM courses\n",
+ "group by course_name;***\n",
+ "\n",
+ "Below is one query that runs, and this query can be considered equivalent to using `DISTINCT` if no aggregate functions are used. \n",
+ "\n",
+ "***select course_name\n",
+ "FROM courses\n",
+ "group by course_name;***\n",
+ "\n",
+ "```{important}\n",
+ "In an aggregation query, the unaggregated expressions need to be consistent with the `GROUP BY` expressions. And all other expressions need to use aggregation functions\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e4010eaa-4240-4a85-bfe0-9dad81b29a74",
+ "metadata": {},
+ "source": [
+ "## Lap 3 🧃\n",
+ "### Join\n",
+ "\n",
+ "Syntax:\n",
+ "```sql\n",
+ "SELECT\n",
+ " columns\n",
+ "FROM\n",
+ " left_table\n",
+ "join_type\n",
+ " right_table\n",
+ "ON\n",
+ " join_condition\n",
+ ";\n",
+ "```\n",
+ "\n",
+ "Following are the types of joins\n",
+ "### Cross join\n",
+ "This is the simplest way of performing a join by cross joining 2 tables (like the cartesian product from your relational algebra classes), in our case, table `students` and `courses`. This kind of join returns all possible combinations of rows from `students` and `courses`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "id": "3a8aa89a-bb69-46f2-af7e-a8f7d53171ac",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "153 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " student_no \n",
+ " stud_name \n",
+ " age \n",
+ " major \n",
+ " id \n",
+ " student_no_1 \n",
+ " course_name \n",
+ " course_year \n",
+ " course_percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Heidy \n",
+ " 30 \n",
+ " MBAN \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Heidy \n",
+ " 30 \n",
+ " MBAN \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Heidy \n",
+ " 30 \n",
+ " MBAN \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 91.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 91.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 91.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 91.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 91.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 91.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Heidy \n",
+ " 30 \n",
+ " MBAN \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 91.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 91.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 91.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Heidy \n",
+ " 30 \n",
+ " MBAN \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Heidy \n",
+ " 30 \n",
+ " MBAN \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Heidy \n",
+ " 30 \n",
+ " MBAN \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Heidy \n",
+ " 30 \n",
+ " MBAN \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Heidy \n",
+ " 30 \n",
+ " MBAN \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 81.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 81.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 81.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 81.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 81.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 81.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Heidy \n",
+ " 30 \n",
+ " MBAN \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 81.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 81.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 81.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Heidy \n",
+ " 30 \n",
+ " MBAN \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 12 \n",
+ " 666 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 12 \n",
+ " 666 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 12 \n",
+ " 666 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 12 \n",
+ " 666 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 12 \n",
+ " 666 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 12 \n",
+ " 666 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Heidy \n",
+ " 30 \n",
+ " MBAN \n",
+ " 12 \n",
+ " 666 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 12 \n",
+ " 666 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 12 \n",
+ " 666 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 93.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 93.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 93.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 93.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 93.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 93.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Heidy \n",
+ " 30 \n",
+ " MBAN \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 93.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 93.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 93.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Heidy \n",
+ " 30 \n",
+ " MBAN \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Heidy \n",
+ " 30 \n",
+ " MBAN \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 16 \n",
+ " 1000 \n",
+ " C programming \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 16 \n",
+ " 1000 \n",
+ " C programming \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 16 \n",
+ " 1000 \n",
+ " C programming \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 16 \n",
+ " 1000 \n",
+ " C programming \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 16 \n",
+ " 1000 \n",
+ " C programming \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 16 \n",
+ " 1000 \n",
+ " C programming \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Heidy \n",
+ " 30 \n",
+ " MBAN \n",
+ " 16 \n",
+ " 1000 \n",
+ " C programming \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 16 \n",
+ " 1000 \n",
+ " C programming \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 16 \n",
+ " 1000 \n",
+ " C programming \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 17 \n",
+ " 1111 \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 17 \n",
+ " 1111 \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 17 \n",
+ " 1111 \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 17 \n",
+ " 1111 \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 17 \n",
+ " 1111 \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 17 \n",
+ " 1111 \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Heidy \n",
+ " 30 \n",
+ " MBAN \n",
+ " 17 \n",
+ " 1111 \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 17 \n",
+ " 1111 \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 17 \n",
+ " 1111 \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(111, 'Catherine', 23, 'MBAN', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (777, 'Heidy', 30, 'MBAN', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (777, 'Heidy', 30, 'MBAN', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 3, 222, 'Health and Technology', 2020, 80.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 3, 222, 'Health and Technology', 2020, 80.0),\n",
+ " (333, 'John', 23, 'MBAN', 3, 222, 'Health and Technology', 2020, 80.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 3, 222, 'Health and Technology', 2020, 80.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 3, 222, 'Health and Technology', 2020, 80.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 3, 222, 'Health and Technology', 2020, 80.0),\n",
+ " (777, 'Heidy', 30, 'MBAN', 3, 222, 'Health and Technology', 2020, 80.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 3, 222, 'Health and Technology', 2020, 80.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 3, 222, 'Health and Technology', 2020, 80.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 4, 222, 'Web and Cloud Computing', 2019, 91.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 4, 222, 'Web and Cloud Computing', 2019, 91.0),\n",
+ " (333, 'John', 23, 'MBAN', 4, 222, 'Web and Cloud Computing', 2019, 91.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 4, 222, 'Web and Cloud Computing', 2019, 91.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 4, 222, 'Web and Cloud Computing', 2019, 91.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 4, 222, 'Web and Cloud Computing', 2019, 91.0),\n",
+ " (777, 'Heidy', 30, 'MBAN', 4, 222, 'Web and Cloud Computing', 2019, 91.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 4, 222, 'Web and Cloud Computing', 2019, 91.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 4, 222, 'Web and Cloud Computing', 2019, 91.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 5, 222, 'Spark Programming', 2019, 90.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 5, 222, 'Spark Programming', 2019, 90.0),\n",
+ " (333, 'John', 23, 'MBAN', 5, 222, 'Spark Programming', 2019, 90.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 5, 222, 'Spark Programming', 2019, 90.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 5, 222, 'Spark Programming', 2019, 90.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 5, 222, 'Spark Programming', 2019, 90.0),\n",
+ " (777, 'Heidy', 30, 'MBAN', 5, 222, 'Spark Programming', 2019, 90.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 5, 222, 'Spark Programming', 2019, 90.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 5, 222, 'Spark Programming', 2019, 90.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 6, 333, 'Parallel Computing', 2019, 90.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 6, 333, 'Parallel Computing', 2019, 90.0),\n",
+ " (333, 'John', 23, 'MBAN', 6, 333, 'Parallel Computing', 2019, 90.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 6, 333, 'Parallel Computing', 2019, 90.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 6, 333, 'Parallel Computing', 2019, 90.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 6, 333, 'Parallel Computing', 2019, 90.0),\n",
+ " (777, 'Heidy', 30, 'MBAN', 6, 333, 'Parallel Computing', 2019, 90.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 6, 333, 'Parallel Computing', 2019, 90.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 6, 333, 'Parallel Computing', 2019, 90.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 7, 444, 'Large Scale Infrastructures', 2019, 83.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 7, 444, 'Large Scale Infrastructures', 2019, 83.0),\n",
+ " (333, 'John', 23, 'MBAN', 7, 444, 'Large Scale Infrastructures', 2019, 83.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 7, 444, 'Large Scale Infrastructures', 2019, 83.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 7, 444, 'Large Scale Infrastructures', 2019, 83.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 7, 444, 'Large Scale Infrastructures', 2019, 83.0),\n",
+ " (777, 'Heidy', 30, 'MBAN', 7, 444, 'Large Scale Infrastructures', 2019, 83.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 7, 444, 'Large Scale Infrastructures', 2019, 83.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 7, 444, 'Large Scale Infrastructures', 2019, 83.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 8, 444, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 8, 444, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (333, 'John', 23, 'MBAN', 8, 444, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 8, 444, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 8, 444, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 8, 444, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (777, 'Heidy', 30, 'MBAN', 8, 444, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 8, 444, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 8, 444, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 9, 555, 'TPCS INFO TECH', 2020, 78.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 9, 555, 'TPCS INFO TECH', 2020, 78.0),\n",
+ " (333, 'John', 23, 'MBAN', 9, 555, 'TPCS INFO TECH', 2020, 78.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 9, 555, 'TPCS INFO TECH', 2020, 78.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 9, 555, 'TPCS INFO TECH', 2020, 78.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 9, 555, 'TPCS INFO TECH', 2020, 78.0),\n",
+ " (777, 'Heidy', 30, 'MBAN', 9, 555, 'TPCS INFO TECH', 2020, 78.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 9, 555, 'TPCS INFO TECH', 2020, 78.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 9, 555, 'TPCS INFO TECH', 2020, 78.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 10, 555, 'Health and Technology', 2020, 81.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 10, 555, 'Health and Technology', 2020, 81.0),\n",
+ " (333, 'John', 23, 'MBAN', 10, 555, 'Health and Technology', 2020, 81.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 10, 555, 'Health and Technology', 2020, 81.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 10, 555, 'Health and Technology', 2020, 81.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 10, 555, 'Health and Technology', 2020, 81.0),\n",
+ " (777, 'Heidy', 30, 'MBAN', 10, 555, 'Health and Technology', 2020, 81.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 10, 555, 'Health and Technology', 2020, 81.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 10, 555, 'Health and Technology', 2020, 81.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 11, 666, 'Data Visualization', 2021, 85.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 11, 666, 'Data Visualization', 2021, 85.0),\n",
+ " (333, 'John', 23, 'MBAN', 11, 666, 'Data Visualization', 2021, 85.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 11, 666, 'Data Visualization', 2021, 85.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 11, 666, 'Data Visualization', 2021, 85.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 11, 666, 'Data Visualization', 2021, 85.0),\n",
+ " (777, 'Heidy', 30, 'MBAN', 11, 666, 'Data Visualization', 2021, 85.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 11, 666, 'Data Visualization', 2021, 85.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 11, 666, 'Data Visualization', 2021, 85.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 12, 666, 'Parallel Computing', 2019, 87.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 12, 666, 'Parallel Computing', 2019, 87.0),\n",
+ " (333, 'John', 23, 'MBAN', 12, 666, 'Parallel Computing', 2019, 87.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 12, 666, 'Parallel Computing', 2019, 87.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 12, 666, 'Parallel Computing', 2019, 87.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 12, 666, 'Parallel Computing', 2019, 87.0),\n",
+ " (777, 'Heidy', 30, 'MBAN', 12, 666, 'Parallel Computing', 2019, 87.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 12, 666, 'Parallel Computing', 2019, 87.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 12, 666, 'Parallel Computing', 2019, 87.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 13, 888, 'Spark Programming', 2019, 93.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 13, 888, 'Spark Programming', 2019, 93.0),\n",
+ " (333, 'John', 23, 'MBAN', 13, 888, 'Spark Programming', 2019, 93.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 13, 888, 'Spark Programming', 2019, 93.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 13, 888, 'Spark Programming', 2019, 93.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 13, 888, 'Spark Programming', 2019, 93.0),\n",
+ " (777, 'Heidy', 30, 'MBAN', 13, 888, 'Spark Programming', 2019, 93.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 13, 888, 'Spark Programming', 2019, 93.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 13, 888, 'Spark Programming', 2019, 93.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 14, 999, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 14, 999, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (333, 'John', 23, 'MBAN', 14, 999, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 14, 999, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 14, 999, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 14, 999, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (777, 'Heidy', 30, 'MBAN', 14, 999, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 14, 999, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 14, 999, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 15, 999, 'Data Visualization', 2019, 87.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 15, 999, 'Data Visualization', 2019, 87.0),\n",
+ " (333, 'John', 23, 'MBAN', 15, 999, 'Data Visualization', 2019, 87.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 15, 999, 'Data Visualization', 2019, 87.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 15, 999, 'Data Visualization', 2019, 87.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 15, 999, 'Data Visualization', 2019, 87.0),\n",
+ " (777, 'Heidy', 30, 'MBAN', 15, 999, 'Data Visualization', 2019, 87.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 15, 999, 'Data Visualization', 2019, 87.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 15, 999, 'Data Visualization', 2019, 87.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 16, 1000, 'C programming', 2019, 87.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 16, 1000, 'C programming', 2019, 87.0),\n",
+ " (333, 'John', 23, 'MBAN', 16, 1000, 'C programming', 2019, 87.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 16, 1000, 'C programming', 2019, 87.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 16, 1000, 'C programming', 2019, 87.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 16, 1000, 'C programming', 2019, 87.0),\n",
+ " (777, 'Heidy', 30, 'MBAN', 16, 1000, 'C programming', 2019, 87.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 16, 1000, 'C programming', 2019, 87.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 16, 1000, 'C programming', 2019, 87.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 17, 1111, 'Introduction to Genomics', 2019, 87.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 17, 1111, 'Introduction to Genomics', 2019, 87.0),\n",
+ " (333, 'John', 23, 'MBAN', 17, 1111, 'Introduction to Genomics', 2019, 87.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 17, 1111, 'Introduction to Genomics', 2019, 87.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 17, 1111, 'Introduction to Genomics', 2019, 87.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 17, 1111, 'Introduction to Genomics', 2019, 87.0),\n",
+ " (777, 'Heidy', 30, 'MBAN', 17, 1111, 'Introduction to Genomics', 2019, 87.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 17, 1111, 'Introduction to Genomics', 2019, 87.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 17, 1111, 'Introduction to Genomics', 2019, 87.0)]"
+ ]
+ },
+ "execution_count": 20,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "\n",
+ "SELECT\n",
+ " *\n",
+ "FROM\n",
+ " students\n",
+ "CROSS JOIN\n",
+ " courses\n",
+ ";"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "57ec5f08-452f-4376-a71c-c1d27552a712",
+ "metadata": {},
+ "source": [
+ "```{note}\n",
+ "But in real life, we usually perform joins on a column, and we will discuss some types of joins on columns in the following sections. Since we are performing joins on a column, we need to pass that information using the `ON` keyword to give which columns are used to stitch the tables together\n",
+ "```\n",
+ "### Inner join\n",
+ "\n",
+ "Inner join only returns the matching rows from the left and right tables."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "id": "c4959539-2048-404b-a5ff-2d5cbf146f72",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "15 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " student_no \n",
+ " stud_name \n",
+ " age \n",
+ " course_name \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Web and Cloud Computing \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " Large Scale Infrastructures \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(111, 'Catherine', 23, 'TPCS INFO TECH'),\n",
+ " (111, 'Catherine', 23, 'Data Visualization'),\n",
+ " (222, 'Tiff', 28, 'Health and Technology'),\n",
+ " (222, 'Tiff', 28, 'Web and Cloud Computing'),\n",
+ " (222, 'Tiff', 28, 'Spark Programming'),\n",
+ " (333, 'John', 23, 'Parallel Computing'),\n",
+ " (444, 'Amir', 28, 'Large Scale Infrastructures'),\n",
+ " (444, 'Amir', 28, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'Health and Technology'),\n",
+ " (666, 'Isha', 30, 'Data Visualization'),\n",
+ " (666, 'Isha', 30, 'Parallel Computing'),\n",
+ " (888, 'Angela', 27, 'Spark Programming'),\n",
+ " (999, 'Jason', 30, 'TPCS INFO TECH'),\n",
+ " (999, 'Jason', 30, 'Data Visualization')]"
+ ]
+ },
+ "execution_count": 21,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "\n",
+ "SELECT s.student_no,s.stud_name,s.age,c.course_name\n",
+ "FROM\n",
+ " students AS s\n",
+ "INNER JOIN\n",
+ " courses AS c\n",
+ "ON\n",
+ " s.student_no = c.student_no;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7aff1260-a2c7-42f2-a92a-92ba317d64c8",
+ "metadata": {},
+ "source": [
+ "```{margin}\n",
+ "Check how we are using the alias `AS` we learned in the previous session.\n",
+ "```\n",
+ "\n",
+ "```{note}\n",
+ "In the returned table, student “Heidy” is missing as that student is not taking any courses and is not mentioned in the `courses`. Also, the courses \"C programming\" and \"Introduction to Genomics\" are missing since no students from our `student` table are taking these courses.\n",
+ "```\n",
+ "### Outer join \n",
+ "\n",
+ "An outer join returns all the rows from one or both of the tables that join. There are 3 variations of it. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6140faf0-24d5-49ed-937b-4fdc45447206",
+ "metadata": {},
+ "source": [
+ "#### Left outer\n",
+ "The first table that appears in the query is the left table, and the one appearing after the `LEFT OUTER JOIN` keyword is the right table.\n",
+ "\n",
+ "A left outer join returns all rows from the left table (matching or not), in addition to the matching rows from both tables. So the non-matching rows from the left table are assigned null values in the columns that belong to the right table.\n",
+ "\n",
+ "If you think about it from a Venn diagram perspective, it will look like...\n",
+ "\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "id": "c2b3c698-c279-47e5-9646-6857bd3b2ede",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "16 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " student_no \n",
+ " stud_name \n",
+ " age \n",
+ " course_name \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Web and Cloud Computing \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " Large Scale Infrastructures \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Heidy \n",
+ " 30 \n",
+ " None \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(111, 'Catherine', 23, 'TPCS INFO TECH'),\n",
+ " (111, 'Catherine', 23, 'Data Visualization'),\n",
+ " (222, 'Tiff', 28, 'Health and Technology'),\n",
+ " (222, 'Tiff', 28, 'Web and Cloud Computing'),\n",
+ " (222, 'Tiff', 28, 'Spark Programming'),\n",
+ " (333, 'John', 23, 'Parallel Computing'),\n",
+ " (444, 'Amir', 28, 'Large Scale Infrastructures'),\n",
+ " (444, 'Amir', 28, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'Health and Technology'),\n",
+ " (666, 'Isha', 30, 'Data Visualization'),\n",
+ " (666, 'Isha', 30, 'Parallel Computing'),\n",
+ " (888, 'Angela', 27, 'Spark Programming'),\n",
+ " (999, 'Jason', 30, 'TPCS INFO TECH'),\n",
+ " (999, 'Jason', 30, 'Data Visualization'),\n",
+ " (777, 'Heidy', 30, None)]"
+ ]
+ },
+ "execution_count": 22,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "\n",
+ "SELECT s.student_no,s.stud_name,s.age,c.course_name\n",
+ "FROM\n",
+ " students AS s\n",
+ "LEFT OUTER JOIN\n",
+ " courses AS c\n",
+ "ON\n",
+ " s.student_no = c.student_no;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "da7f9856-53c3-4f3c-b505-fd3cf39d5744",
+ "metadata": {},
+ "source": [
+ "#### Right outer \n",
+ "It behaves exactly in the same way as a left join, except that it keeps all rows from the right table and only the matching ones from the left table.\n",
+ "\n",
+ "If you think about it from a Venn diagram perspective, it will look like.\n",
+ "\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "id": "c715fc93-110c-4781-8fa6-632e403e1c89",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "17 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " student_no \n",
+ " stud_name \n",
+ " age \n",
+ " course_name \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Web and Cloud Computing \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " Large Scale Infrastructures \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " None \n",
+ " None \n",
+ " None \n",
+ " C programming \n",
+ " \n",
+ " \n",
+ " None \n",
+ " None \n",
+ " None \n",
+ " Introduction to Genomics \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(111, 'Catherine', 23, 'TPCS INFO TECH'),\n",
+ " (111, 'Catherine', 23, 'Data Visualization'),\n",
+ " (222, 'Tiff', 28, 'Health and Technology'),\n",
+ " (222, 'Tiff', 28, 'Web and Cloud Computing'),\n",
+ " (222, 'Tiff', 28, 'Spark Programming'),\n",
+ " (333, 'John', 23, 'Parallel Computing'),\n",
+ " (444, 'Amir', 28, 'Large Scale Infrastructures'),\n",
+ " (444, 'Amir', 28, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'Health and Technology'),\n",
+ " (666, 'Isha', 30, 'Data Visualization'),\n",
+ " (666, 'Isha', 30, 'Parallel Computing'),\n",
+ " (888, 'Angela', 27, 'Spark Programming'),\n",
+ " (999, 'Jason', 30, 'TPCS INFO TECH'),\n",
+ " (999, 'Jason', 30, 'Data Visualization'),\n",
+ " (None, None, None, 'C programming'),\n",
+ " (None, None, None, 'Introduction to Genomics')]"
+ ]
+ },
+ "execution_count": 23,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "\n",
+ "SELECT s.student_no,s.stud_name,s.age,c.course_name\n",
+ "FROM\n",
+ " students AS s\n",
+ "RIGHT OUTER JOIN\n",
+ " courses AS c\n",
+ "ON\n",
+ " s.student_no = c.student_no;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "474d5994-7832-4586-b2cc-327ef5385ce3",
+ "metadata": {},
+ "source": [
+ "#### Full outer\n",
+ "\n",
+ "left join + right join = full outer join.\n",
+ "\n",
+ "It retrieves matching and non-matching rows from both tables. \n",
+ "\n",
+ "If you think about it from a Venn diagram perspective, it will look like.\n",
+ "\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "id": "34a25ff1-6f68-43e7-8a00-67a213795a3c",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "18 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " student_no \n",
+ " stud_name \n",
+ " age \n",
+ " course_name \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Web and Cloud Computing \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " Large Scale Infrastructures \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " None \n",
+ " None \n",
+ " None \n",
+ " C programming \n",
+ " \n",
+ " \n",
+ " None \n",
+ " None \n",
+ " None \n",
+ " Introduction to Genomics \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Heidy \n",
+ " 30 \n",
+ " None \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(111, 'Catherine', 23, 'TPCS INFO TECH'),\n",
+ " (111, 'Catherine', 23, 'Data Visualization'),\n",
+ " (222, 'Tiff', 28, 'Health and Technology'),\n",
+ " (222, 'Tiff', 28, 'Web and Cloud Computing'),\n",
+ " (222, 'Tiff', 28, 'Spark Programming'),\n",
+ " (333, 'John', 23, 'Parallel Computing'),\n",
+ " (444, 'Amir', 28, 'Large Scale Infrastructures'),\n",
+ " (444, 'Amir', 28, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'Health and Technology'),\n",
+ " (666, 'Isha', 30, 'Data Visualization'),\n",
+ " (666, 'Isha', 30, 'Parallel Computing'),\n",
+ " (888, 'Angela', 27, 'Spark Programming'),\n",
+ " (999, 'Jason', 30, 'TPCS INFO TECH'),\n",
+ " (999, 'Jason', 30, 'Data Visualization'),\n",
+ " (None, None, None, 'C programming'),\n",
+ " (None, None, None, 'Introduction to Genomics'),\n",
+ " (777, 'Heidy', 30, None)]"
+ ]
+ },
+ "execution_count": 24,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "\n",
+ "SELECT s.student_no,s.stud_name,s.age,c.course_name\n",
+ "FROM\n",
+ " students AS s\n",
+ "FULL OUTER JOIN\n",
+ " courses AS c\n",
+ "ON\n",
+ " s.student_no = c.student_no;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1b9ac233-6f00-4276-8e4c-16e7cfbfbbe4",
+ "metadata": {},
+ "source": [
+ "### Summarize:\n",
+ "\n",
+ "***CARTESIAN JOIN:*** returns the Cartesian product of the sets of records from the two or more joined tables.\n",
+ "\n",
+ "***INNER JOIN:*** returns rows when there is a match in both tables.\n",
+ "\n",
+ "***LEFT JOIN:*** returns all rows from the left table, even if there are no matches in the right table.\n",
+ "\n",
+ "***RIGHT JOIN:*** returns all rows from the right table, even if there are no matches in the left table.\n",
+ "\n",
+ "***FULL JOIN:*** combines the results of both left and right outer joins."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "de8a2d6d-3a7a-4985-ac24-140316fbd60e",
+ "metadata": {},
+ "source": [
+ "We learned now about the joins. You know now how to join 2 tables. Once you joined 2 tables its sort of behave like another table that you apply the operations what we learned."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "id": "d33b019c-4093-4605-937b-a2f4b13ff778",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "3 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " stud_name \n",
+ " course_name \n",
+ " course_year \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Isha \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " \n",
+ " \n",
+ " Catherine \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " Jason \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[('Isha', 'Data Visualization', 2021),\n",
+ " ('Catherine', 'Data Visualization', 2019),\n",
+ " ('Jason', 'Data Visualization', 2019)]"
+ ]
+ },
+ "execution_count": 25,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "select s.stud_name,c.course_name, c.course_year\n",
+ "FROM students AS s\n",
+ "INNER JOIN\n",
+ "courses AS c\n",
+ "ON s.student_no = c.student_no\n",
+ "WHERE course_name = 'Data Visualization'\n",
+ "ORDER BY course_year DESC;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d9b1dc63-2d19-4c0e-8a35-2f6a57a59a51",
+ "metadata": {},
+ "source": [
+ "```{important}\n",
+ "We can have all the keywords we learned in a single SQL query, and we have come across some in previous examples. `BUT` the order of SQL keywords `DOES` matter: SELECT, FROM, JOIN, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT. \n",
+ "```\n",
+ "```{seealso}\n",
+ "We experienced performing joins on 2 tables, but joins can also be performed on multiple tables. Multi joins in SQL work by progressively creating derived tables one after the other. Here is the link that explains this [process](https://www.interfacett.com/blogs/multiple-joins-work-just-like-single-joins/)\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d04adf0d-6a40-45d8-922f-84855e75db71",
+ "metadata": {},
+ "source": [
+ "### Checkpoint 3 !! Take some time and think if you can...\n",
+ "- Understand different kinds of joins\n",
+ "- When to use joins\n",
+ "- Write SQL queries to join tables"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "731106d9-ba08-4692-bac2-8463d1518c87",
+ "metadata": {},
+ "source": [
+ "### Check point 3 exercise.\n",
+ "#### iclicker questions\n",
+ "\n",
+ "***Question 1:*** Spot the issue, if any, in this SQL query \n",
+ "\n",
+ "```sql\n",
+ "select s.stud_name,c.course_name, c.course_year\n",
+ "FROM students AS s\n",
+ "INNER JOIN\n",
+ "courses AS c\n",
+ "ON s.student_no = c.student_no\n",
+ "ORDER BY course_year DESC\n",
+ "WHERE course_name = 'Data Visualization';\n",
+ "```\n",
+ "\n",
+ "A: There are some issues with the join key\n",
+ "\n",
+ "B: No issues\n",
+ "\n",
+ "C: Can't specify alias when performing a join\n",
+ "\n",
+ "D: ORDER BY need to come after the WHERE clause\n",
+ "\n",
+ "```{toggle}\n",
+ "***Answer: D***\n",
+ "```\n",
+ "\n",
+ "***Question 2:*** \"In this Venn diagram green color indicate left outer join\" - is this statement TRUE/FALSE?\n",
+ "\n",
+ " \n",
+ "\n",
+ "A: TRUE\n",
+ "\n",
+ "B: FALSE\n",
+ "\n",
+ "```{toggle}\n",
+ "***Answer: FALSE***\n",
+ "\n",
+ "The following figure is what indicates the left outer join.\n",
+ "\n",
+ " \n",
+ "\n",
+ "But what is given here is a special scenario where we apply left join to be useful. For example, what if we want to find all students who are not taking any courses?\n",
+ "\n",
+ "\n",
+ "SELECT s.student_no,s.stud_name,s.age,c.course_name\n",
+ "FROM\n",
+ " students AS s\n",
+ "LEFT OUTER JOIN\n",
+ " courses AS c\n",
+ "ON\n",
+ " s.student_no = c.student_no\n",
+ "WHERE c.student_no is NULL\n",
+ "\n",
+ "\n",
+ "| student_no | stud_name | age | course_name |\n",
+ "|-----------:|----------:|----:|------------:|\n",
+ "| 777 | Heidy | 30 | None |\n",
+ "\n",
+ "Ahaa..! looks like \"heidy\" is not taking any courses; we need to check with her to see why she is not taking any courses :)\n",
+ "\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "12fa7b6e-fb81-41e9-8fa7-982fcee3dba8",
+ "metadata": {},
+ "source": [
+ "## 🏁 Finish line 🏁 🍺\n",
+ "\n",
+ "Are you able to recollect our 3 checkpoints?\n",
+ "\n",
+ "- ***Checkpoint 1:*** Take some time and think if you can...\n",
+ " - Select columns and bring in rows just what we needed (using SELECT & WHERE)\n",
+ " - Return DISTINCT elements\n",
+ " - ORDER BY rows returned based on column(s)\n",
+ " - LIMIT the number of rows returned\n",
+ "- ***Checkpoint 2:*** Take some time and think if you can...\n",
+ " - Do some aggregations and grouping queries using SQL.\n",
+ " - GROUP BY\n",
+ " - aggregation functions\n",
+ " - HAVING\n",
+ "- ***Checkpoint 3:*** Take some time and think if you can...\n",
+ " - Understand different kinds of joins\n",
+ " - When to use joins\n",
+ " - Write SQL queries to join tables"
+ ]
+ }
+ ],
+ "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.8.12"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
\ No newline at end of file
diff --git a/_build/jupyter_execute/lectures/lecture2.py b/_build/jupyter_execute/lectures/lecture2.py
new file mode 100644
index 0000000..3d66f82
--- /dev/null
+++ b/_build/jupyter_execute/lectures/lecture2.py
@@ -0,0 +1,819 @@
+#!/usr/bin/env python
+# coding: utf-8
+
+# # Lecture 2: SQL - Class 2
+# Gittu George, February 24 2022
+
+# ## Today's Agenda
+#
+# - Refresher (Warm-up)
+# - Refresher exercise
+# - Learning objectives
+# - More SQL commands (lap 1)
+# - Aggregations (lap 2)
+# - Grouping (lap 2)
+# - Joins (lap 3)
+# - Summary (finish line)
+#
+# ## Warm-up ☕️
+# ### Refresher!! Take some time and think if you know...
+#
+# - What are databases?
+# - Where is it commonly used?
+# - How to set up a database?
+# - General structure of a database
+# - Various ways of interacting with the database, using
+# - command-line interface
+# - ODBC drivers via developer interfaces like pgadmin and toad.
+# - jupyter notebook ( which we will be using throughout the course)
+# - To create a table and various data types
+# - Integrity constraints such as primary key and foreign key
+# - Basic SQL commands like SELECT, FROM, and WHERE
+#
+# In this class, we will be using the tables that we created in lecture 1. Here are the scripts if you want to recreate the classroom exercise table.
+#
+# ```{toggle}
+#
+# ```sql
+# DROP TABLE IF EXISTS students,courses;
+#
+# CREATE TABLE IF NOT EXISTS students(
+# student_no integer PRIMARY KEY,
+# stud_name text,
+# age integer,
+# major text);
+#
+# CREATE TABLE IF NOT EXISTS courses(
+# id SERIAL PRIMARY KEY,
+# student_no integer,
+# course_name text,
+# course_year integer,
+# course_percentage float);
+#
+# INSERT INTO students(student_no,stud_name,age,major) VALUES (111,'Catherine',23,'MBAN'),
+# (222,'Tiff',28,'MDS'),
+# (333,'John',23,'MBAN'),
+# (444,'Amir',28,'MBAN'),
+# (555,'Gittu',20,'MDS'),
+# (666,'Isha',30,'MBAN'),
+# (777,'Heidy',30,'MBAN'),
+# (888,'Angela',27,'MBAN'),
+# (999,'Jason',30,'MBAN');
+#
+# INSERT INTO courses(student_no,course_name,course_year,course_percentage) VALUES (111,'TPCS INFO TECH',2019,88),
+# (111,'Data Visualization',2019,88),
+# (222,'Health and Technology',2020,80),
+# (222,'Web and Cloud Computing',2019,91),
+# (222,'Spark Programming',2019,90),
+# (333,'Parallel Computing',2019,90),
+# (444,'Large Scale Infrastructures',2019,83),
+# (444,'TPCS INFO TECH',2019,98),
+# (555,'TPCS INFO TECH',2020,78),
+# (555,'Health and Technology',2020,81),
+# (666,'Data Visualization',2021,85),
+# (666,'Parallel Computing',2019,87),
+# (888,'Spark Programming',2019,93),
+# (999,'TPCS INFO TECH',2019,98),
+# (999,'Data Visualization',2019,87),
+# (1000,'C programming',2019,87),
+# (1111,'Introduction to Genomics',2019,87);
+# ```
+
+# First of all let's load SQL to work on this notebook.
+
+# In[1]:
+
+
+import os
+import psycopg2
+from dotenv import load_dotenv
+load_dotenv()
+host = os.environ.get('DB_HOST')
+user = os.environ.get('DB_USER')
+password = os.environ.get('DB_PASS')
+
+
+# In[2]:
+
+
+get_ipython().run_line_magic('load_ext', 'sql')
+
+
+# In[3]:
+
+
+get_ipython().run_line_magic('sql', 'postgresql://{user}:{password}@{host}:5432/postgres')
+
+
+# ### Refresher exercise
+#
+# ***Question 1:*** In this table `eg`
+#
+# | firstname | countryfrom | continent | gender | age |
+# |-----------|-------------|---------------|--------|-----|
+# | matt | usa | north america | M | 23 |
+# | jenn | uk | europe | F | 35 |
+# | guy | france | europe | M | 25 |
+# | james | china | asia | M | 29 |
+# | lida | india | asia | F | 56 |
+# | linda | canada | north america | F | 18 |
+# | sofia | germany | europe | F | 22 |
+# | george | india | asia | M | 29 |
+#
+# What will be returned from following SQL
+# ```sql
+# select firstname,gender from eg where continent ='asia' AND age =29
+# ```
+# A) george,M
+#
+# B) george,M,asia,29
+#
+# C) george,india,asia, M,29
+#
+# D) M,george
+#
+# E) M,james
+#
+# ```{toggle}
+#
+# ***Answer: D***
+#
+# ```{important}
+# Remember the order of the columns returned will be based on the order of columns that we specify within the select statement.
+# ```
+
+# ***Question 2:*** Consider a scenario where you want to perform analysis on a table (peopletable) with 100 columns (including personname,age,gender,origin...etc.) that define a person. You are interested in seeing the name of individuals older than 90. Which SQL query is more appropriate in this situation?
+#
+# A) ***select * from peopletable where age > 90;***
+#
+# B) ***select personname,age from peopletable where age > 90;***
+#
+# B) ***select personname,age,gender,origin from peopletable where age < 90;***
+#
+# D) ***select personname from peopletable where age > 90;***
+#
+# ```{toggle}
+#
+# ***Answer: D***
+#
+# ```{important}
+# Just bring the columns and rows that are needed. Even though `SELECT` and `WHERE` are very basic SQL commands, it's crucial when you are dealing with a large table
+# ```
+
+# ## Learning objectives
+# - You will be able to create SQL queries using (Lap 1 & Lap 2)
+# - Distinct,
+# - ORDER BY,
+# - LIMIT,
+# - GROUP BY,
+# - alias AS
+# - You will learn about different kinds of joins and be able to create SQL queries that perform `JOINS`. (Lap 3)
+
+# ## Lap 1 🥛
+# ### DISTINCT
+#
+# The DISTINCT statement is used only to return distinct elements from a table.
+#
+# ***Syntax:***
+#
+# ```sql
+# SELECT DISTINCT column1, column2, ...columnN
+# FROM tablename;
+# ```
+#
+# `DISTINCT` is applied to all columns that follows the `DISTINCT` keyword. Say for eg if we give `DISTINCT column1, column2` then the combination of values in both `column1` and `column2` columns will be used for returning the unique combination (or removing the duplicate elements).
+
+# In[4]:
+
+
+get_ipython().run_cell_magic('sql', '', 'SELECT DISTINCT course_name FROM courses;')
+
+
+# In[5]:
+
+
+get_ipython().run_cell_magic('sql', '', 'SELECT DISTINCT course_name, course_year FROM courses;')
+
+
+# ### ORDER BY
+#
+# `ORDER BY` statement sorts the results returned by `SELECT` based on a sort expression.
+#
+# Syntax
+# ```sql
+# SELECT column1, column2 ...columnN
+# FROM table_name
+# ORDER BY column1 [ASC | DESC], column2 [ASC | DESC] ....columnN [ASC|DESC];
+# ```
+#
+# ```{note}
+# By default, it will sort in ASC. So you can choose not to give ASC.
+# ```
+
+# In[6]:
+
+
+get_ipython().run_cell_magic('sql', '', 'SELECT * \nFROM courses\nORDER BY course_year;')
+
+
+# In[7]:
+
+
+get_ipython().run_cell_magic('sql', '', 'SELECT * \nFROM courses\nORDER BY course_year DESC;')
+
+
+# ### LIMIT
+#
+# Until now, we were returning everything that our SQL query returns. `LIMIT` statement is used to limit the number of rows that are returned.
+#
+# syntax:
+#
+# ```sql
+# SELECT column1, column2, ...columnN
+# FROM tablename
+# LIMIT numberofrows;
+# ```
+
+# In[8]:
+
+
+get_ipython().run_cell_magic('sql', '', 'SELECT * \nFROM courses\nLIMIT 2;')
+
+
+# `LIMIT` keyword is used in a variety of situations. Here are a few cases
+#
+# - ***Memory Management:*** Say you just want to look at the output your query returns. If you are dealing with lots and lots of rows, returning the entire rows can slow down the query, cause memory issues, and finally crash your jupyter. In these cases, you can use `LIMIT`.
+#
+# - ***Interest in the first few rows:*** If we are interested in just the first N rows, we can achieve that using `LIMIT`. People tend to use `LIMIT` a lot when they want to return the top 10 rows after performing an `ORDER BY`
+
+# Let's apply all the statements that we learned in one statement.
+#
+# ***Question:*** List out the row that got the highest `course_percentage` for the `Data Visualization` course.
+
+# In[9]:
+
+
+get_ipython().run_cell_magic('sql', '', "SELECT * \nFROM courses\nWHERE course_name = 'Data Visualization'\nORDER BY course_percentage DESC\nLIMIT 1;")
+
+
+# In[10]:
+
+
+get_ipython().run_line_magic('sql', 'select age from eg order by age')
+
+
+# ### Checkpoint 1 !! Take some time and think if you can...
+#
+# - Select columns and bring in rows just what we need (using `SELECT` & `WHERE`)
+# - Return `DISTINCT` elements
+# - `ORDER BY` rows returned based on column(s)
+# - `LIMIT` the number of rows returned
+#
+# Now we will learn some more advanced SQL operations to gain more insight into the data. But, before that, let's do some exercise.
+
+# ### Check point 1 exercise.
+#
+# #### iclicker questions
+#
+# Answer the following questions using the table `eg`
+#
+# | firstname | countryfrom | continent | gender | age |
+# |-----------|-------------|---------------|--------|-----|
+# | matt | usa | north america | M | 23 |
+# | jenn | uk | europe | F | 35 |
+# | guy | france | europe | M | 25 |
+# | james | china | asia | M | 29 |
+# | lida | india | asia | F | 56 |
+# | linda | canada | north america | F | 18 |
+# | sofia | germany | europe | F | 22 |
+# | george | india | asia | M | 29 |
+#
+#
+# ***Question 1:*** How many elements will be returned from this SQL
+#
+# ```sql
+# select DISTINCT gender,firstname from eg
+# ```
+#
+# A) 2
+#
+# B) 8
+#
+# C) 4
+#
+# ```{toggle}
+# ***Answer: B***
+# ```
+# ***Question 2:*** What will be the first value returned from this SQL
+#
+# ```sql
+# select age from eg order by age
+# ```
+# A) 23
+#
+# B) 18
+#
+# C) 56
+#
+# D) 35
+#
+# ```{toggle}
+# ***Answer: B***
+# ```
+#
+# #### Reasoning Question
+#
+# ***Question 1:*** Write a SQL query to list the row that got the highest `course_percentage` for the `TPCS INFO TECH'` course. Can you spot any issues by examining the original table?
+#
+# ```{toggle}
+#
+# You probably might have got this SQL query by changing the SQL query that we discussed for the "Data Visualization" course.
+#
+# ***SELECT * FROM courses
+# WHERE course_name = 'TPCS INFO TECH'
+# ORDER BY course_percentage DESC
+# LIMIT 1;***
+#
+# This gives you
+#
+# | id | student_no | course_name | course_year | course_percentage |
+# |---:|-----------:|---------------:|------------:|------------------:|
+# | 8 | 444 | TPCS INFO TECH | 2019 | 98.0 |
+#
+# What are possible issues? Take out the `LIMIT`, and you might notice there is a tie, and 2 students, 444 and 999 scored the highest.
+#
+#
+# ***SELECT * FROM courses
+# WHERE course_name = 'TPCS INFO TECH'
+# ORDER BY course_percentage DESC***
+#
+#
+# | id | student_no | course_name | course_year | course_percentage |
+# |---:|-----------:|---------------:|------------:|------------------:|
+# | 8 | 444 | TPCS INFO TECH | 2019 | 98.0 |
+# | 14 | 999 | TPCS INFO TECH | 2019 | 98.0 |
+# | 1 | 111 | TPCS INFO TECH | 2019 | 88.0 |
+# | 9 | 555 | TPCS INFO TECH | 2020 | 78.0 |
+#
+# So even though many of them use a combination of `ORDER BY` and `LIMIT` for these scenarios we might run into situations like this. There are a couple of ways to deal with these kinds of scenarios, and we will learn about `subquery` in our next class, which will help you capture ties.
+
+# ## Lap 2 🧋
+# ### Aggregations
+#
+# So far, we have returned columns in our select statement. We can also use aggregation functions that operate on rows to summarize the data in the form of a single value. Here is a list of aggregation functions in SQL:
+#
+# | Function | Description |
+# |----------|------------------|
+# | MAX() | maximum value |
+# | MIN() | minimum value |
+# | AVG() | average value |
+# | SUM() | sum of values |
+# | COUNT() | number of values |
+
+# In[11]:
+
+
+get_ipython().run_cell_magic('sql', '', 'SELECT COUNT(course_name) FROM courses;')
+
+
+# The above query is counting number of values in the column `course_name`. it's also sort of like counting the number of rows. We can also pass the `DISTINCT` columns into these operations. For example, the below query will find the number of courses available in the university.
+
+# In[12]:
+
+
+get_ipython().run_cell_magic('sql', '', 'SELECT COUNT(DISTINCT course_name) FROM courses;')
+
+
+# In[13]:
+
+
+get_ipython().run_cell_magic('sql', '', 'select AVG(course_percentage) \nFROM courses')
+
+
+# Few things to keep in mind when dealing with the aggregation function
+#
+# - You are not restricted in using just one aggregation function in the SELECT statement.✅
+#
+# ```sql
+# SELECT COUNT(DISTINCT course_name),max(course_percentage) FROM courses;
+# ```
+#
+# - You CAN'T use aggregations and regular columns in a single query. You can use only when you have a `GROUP BY` clause. (will discuss soon)❌
+#
+# ```sql
+# SELECT COUNT(DISTINCT course_name),course_percentage FROM courses;
+# ```
+#
+# - You CAN'T use aggregation function in a where clause. The following query is wrong ❌
+#
+# ```sql
+# SELECT * FROM courses WHERE course_percentage < AVG(course_percentage);
+# ```
+#
+# You can answer this question when we discuss subqueries in the next class (another reason to learn subqueries :) )
+
+# ### Grouping
+#
+# The aggregations we learned in our previous session also become useful when using the `GROUP BY` statement. `GROUP BY` statement divides a table into groups of rows based on the values of one or more columns. Once this grouping is done, you can apply your aggregation to these groups.
+#
+# Syntax:
+# ```sql
+# SELECT
+# grouping_columns, aggregated_columns
+# FROM
+# table1
+# GROUP BY
+# grouping_columns
+# ```
+#
+# Example:
+
+# In[14]:
+
+
+get_ipython().run_cell_magic('sql', '', 'select * from courses;')
+
+
+# In[15]:
+
+
+get_ipython().run_cell_magic('sql', '', 'select course_name, AVG(course_percentage) \nFROM courses\ngroup by course_name;')
+
+
+# We can also perform a multi level grouping;
+
+# In[16]:
+
+
+get_ipython().run_cell_magic('sql', '', 'select course_name, course_year,AVG(course_percentage) \nFROM courses\ngroup by course_name, course_year\nhaving AVG(course_percentage) <90 ;')
+
+
+# Now, what if I want to see only the courses with an average of less than 90 %? We mentioned before that this kind of filtering (filtering on the aggregation function) is not possible using `WHERE` statement, and that's why we want the `HAVING` statement to do filtering using these aggregated values.
+#
+# ### HAVING
+#
+# Syntax:
+# ```sql
+# SELECT
+# grouping_columns, aggregated_columns
+# FROM
+# table1
+# GROUP BY
+# grouping_columns
+# HAVING
+# group_condition
+#
+# ```
+#
+# ```{important}
+# To summarize:
+#
+# - WHERE filters rows before grouping. It filters records in a table level
+# - HAVING filters groups after grouping. It filters records in a group level
+# ```
+#
+# For example, let's get the question we raised at the end of the grouping section. I want to see the courses with a course average of less than 90 %?
+
+# In[17]:
+
+
+get_ipython().run_cell_magic('sql', '', 'select course_name, AVG(course_percentage) \nFROM courses\ngroup by course_name\nhaving AVG(course_percentage) <90 ;')
+
+
+# ### Using alias (AS)
+#
+# Until now, we have been referring tables as table names and columns as column names from the schema. But when writing SQL, we are not required to use the same column and table names as in the schema. Instead, we can create aliases for a column or a table using the keyword `AS`.
+#
+# Syntax:
+# ```sql
+# SELECT
+# column1 [AS] c1,
+# column2 [AS] c2
+# FROM
+# table1 [AS] t1;
+# ```
+#
+# It's entirely optional to use AS, but WHY do we want it?
+# - This makes code more readable
+# - You can return the columns with a more meaningful name
+# - Helps a lot when we do JOINS ( wait for the next topic)
+#
+# Let's rewrite our previous query using AS
+
+# In[18]:
+
+
+get_ipython().run_cell_magic('sql', '', 'select course_name, course_year,AVG(course_percentage) AS "average course percentage"\nFROM courses AS c\ngroup by course_name, course_year\nhaving AVG(course_percentage) <90 ;')
+
+
+# In[19]:
+
+
+get_ipython().run_cell_magic('sql', '', 'SELECT *\nFROM courses;')
+
+
+# ### Checkpoint 2 !! Take some time and think if you can...
+#
+# - Do some aggregations and grouping queries using SQL.
+# - GROUP BY
+# - aggregation functions
+# - HAVING
+#
+# Until now, you deal with queries on a single table. What if we are interested in data from another table as well? For example, I am interested in seeing the course details (from the `courses` table) and the students (from the `students` table) related to those courses. In these situations, we use joins. Let's learn how to stitch tables together. Before that, let's do some exercise...
+
+# ### Check point 2 exercise.
+#
+# #### iclicker questions
+#
+# ***Question 1:*** Spot the issue, if any, in this SQL query
+#
+# ```sql
+# SELECT COUNT(DISTINCT course_name),max(course_percentage),course_percentage
+# FROM courses ;
+# ```
+#
+# A: Multiple aggregation functions in the SELECT statement
+#
+# B: No issues
+#
+# C: Can't use DISTINCT inside an aggregation function
+#
+# D: Can't use aggregations and regular columns in a single query
+#
+# ```{toggle}
+# ***Answer: D***
+# ```
+#
+# ***Question 2:*** Spot the issue, if any, in this SQL query
+#
+# ```sql
+# SELECT COUNT(DISTINCT course_name),max(course_percentage),course_year
+# FROM courses
+# GROUP BY course_year;
+# ```
+#
+# A: Multiple aggregation functions in the SELECT statement
+#
+# B: No issues
+#
+# C: Can't use DISTINCT inside an aggregation function
+#
+# D: Can't use aggregations and regular columns in a single query
+#
+# ```{toggle}
+# ***Answer: B***
+# ```
+#
+# ***Question 3:*** Spot the issue, if any, in this SQL query
+#
+# ```sql
+# SELECT COUNT(DISTINCT course_name),max(course_percentage),course_year
+# FROM courses
+# WHERE course_name != 'TPCS INFO TECH'
+# GROUP BY course_year
+# having course_percentage < 90;
+# ```
+#
+# A: No issues
+#
+# B: Can't use column `course_year` in `SELECT`
+#
+# C: Can't use WHERE when using aggregation functions
+#
+# D: Can't use course_percentage in a `HAVING` statement
+#
+# ```{toggle}
+# ***Answer: D***
+# ```
+#
+# #### Reasoning Question
+#
+# ***Question 1:*** We learned the `GROUP BY` clause, and we used it with aggregate functions. Using table `courses`, write a SQL query using `GROUP BY` without any aggregation function. Write your findings and learnings.
+#
+# ```{toggle}
+# You must have tried a variety of SQL queries and got into error;
+#
+# ***select * FROM courses
+# group by course_name;***
+#
+# ***select course_year,course_name
+# FROM courses
+# group by course_name;***
+#
+# Below is one query that runs, and this query can be considered equivalent to using `DISTINCT` if no aggregate functions are used.
+#
+# ***select course_name
+# FROM courses
+# group by course_name;***
+#
+# ```{important}
+# In an aggregation query, the unaggregated expressions need to be consistent with the `GROUP BY` expressions. And all other expressions need to use aggregation functions
+# ```
+
+# ## Lap 3 🧃
+# ### Join
+#
+# Syntax:
+# ```sql
+# SELECT
+# columns
+# FROM
+# left_table
+# join_type
+# right_table
+# ON
+# join_condition
+# ;
+# ```
+#
+# Following are the types of joins
+# ### Cross join
+# This is the simplest way of performing a join by cross joining 2 tables (like the cartesian product from your relational algebra classes), in our case, table `students` and `courses`. This kind of join returns all possible combinations of rows from `students` and `courses`.
+
+# In[20]:
+
+
+get_ipython().run_cell_magic('sql', '', '\nSELECT\n *\nFROM\n students\nCROSS JOIN\n courses\n;')
+
+
+# ```{note}
+# But in real life, we usually perform joins on a column, and we will discuss some types of joins on columns in the following sections. Since we are performing joins on a column, we need to pass that information using the `ON` keyword to give which columns are used to stitch the tables together
+# ```
+# ### Inner join
+#
+# Inner join only returns the matching rows from the left and right tables.
+
+# In[21]:
+
+
+get_ipython().run_cell_magic('sql', '', '\nSELECT s.student_no,s.stud_name,s.age,c.course_name\nFROM\n students AS s\nINNER JOIN\n courses AS c\nON\n s.student_no = c.student_no;')
+
+
+# ```{margin}
+# Check how we are using the alias `AS` we learned in the previous session.
+# ```
+#
+# ```{note}
+# In the returned table, student “Heidy” is missing as that student is not taking any courses and is not mentioned in the `courses`. Also, the courses "C programming" and "Introduction to Genomics" are missing since no students from our `student` table are taking these courses.
+# ```
+# ### Outer join
+#
+# An outer join returns all the rows from one or both of the tables that join. There are 3 variations of it.
+
+# #### Left outer
+# The first table that appears in the query is the left table, and the one appearing after the `LEFT OUTER JOIN` keyword is the right table.
+#
+# A left outer join returns all rows from the left table (matching or not), in addition to the matching rows from both tables. So the non-matching rows from the left table are assigned null values in the columns that belong to the right table.
+#
+# If you think about it from a Venn diagram perspective, it will look like...
+#
+#
+
+# In[22]:
+
+
+get_ipython().run_cell_magic('sql', '', '\nSELECT s.student_no,s.stud_name,s.age,c.course_name\nFROM\n students AS s\nLEFT OUTER JOIN\n courses AS c\nON\n s.student_no = c.student_no;')
+
+
+# #### Right outer
+# It behaves exactly in the same way as a left join, except that it keeps all rows from the right table and only the matching ones from the left table.
+#
+# If you think about it from a Venn diagram perspective, it will look like.
+#
+#
+
+# In[23]:
+
+
+get_ipython().run_cell_magic('sql', '', '\nSELECT s.student_no,s.stud_name,s.age,c.course_name\nFROM\n students AS s\nRIGHT OUTER JOIN\n courses AS c\nON\n s.student_no = c.student_no;')
+
+
+# #### Full outer
+#
+# left join + right join = full outer join.
+#
+# It retrieves matching and non-matching rows from both tables.
+#
+# If you think about it from a Venn diagram perspective, it will look like.
+#
+#
+
+# In[24]:
+
+
+get_ipython().run_cell_magic('sql', '', '\nSELECT s.student_no,s.stud_name,s.age,c.course_name\nFROM\n students AS s\nFULL OUTER JOIN\n courses AS c\nON\n s.student_no = c.student_no;')
+
+
+# ### Summarize:
+#
+# ***CARTESIAN JOIN:*** returns the Cartesian product of the sets of records from the two or more joined tables.
+#
+# ***INNER JOIN:*** returns rows when there is a match in both tables.
+#
+# ***LEFT JOIN:*** returns all rows from the left table, even if there are no matches in the right table.
+#
+# ***RIGHT JOIN:*** returns all rows from the right table, even if there are no matches in the left table.
+#
+# ***FULL JOIN:*** combines the results of both left and right outer joins.
+
+# We learned now about the joins. You know now how to join 2 tables. Once you joined 2 tables its sort of behave like another table that you apply the operations what we learned.
+
+# In[25]:
+
+
+get_ipython().run_cell_magic('sql', '', "select s.stud_name,c.course_name, c.course_year\nFROM students AS s\nINNER JOIN\ncourses AS c\nON s.student_no = c.student_no\nWHERE course_name = 'Data Visualization'\nORDER BY course_year DESC;")
+
+
+# ```{important}
+# We can have all the keywords we learned in a single SQL query, and we have come across some in previous examples. `BUT` the order of SQL keywords `DOES` matter: SELECT, FROM, JOIN, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT.
+# ```
+# ```{seealso}
+# We experienced performing joins on 2 tables, but joins can also be performed on multiple tables. Multi joins in SQL work by progressively creating derived tables one after the other. Here is the link that explains this [process](https://www.interfacett.com/blogs/multiple-joins-work-just-like-single-joins/)
+# ```
+
+# ### Checkpoint 3 !! Take some time and think if you can...
+# - Understand different kinds of joins
+# - When to use joins
+# - Write SQL queries to join tables
+
+# ### Check point 3 exercise.
+# #### iclicker questions
+#
+# ***Question 1:*** Spot the issue, if any, in this SQL query
+#
+# ```sql
+# select s.stud_name,c.course_name, c.course_year
+# FROM students AS s
+# INNER JOIN
+# courses AS c
+# ON s.student_no = c.student_no
+# ORDER BY course_year DESC
+# WHERE course_name = 'Data Visualization';
+# ```
+#
+# A: There are some issues with the join key
+#
+# B: No issues
+#
+# C: Can't specify alias when performing a join
+#
+# D: ORDER BY need to come after the WHERE clause
+#
+# ```{toggle}
+# ***Answer: D***
+# ```
+#
+# ***Question 2:*** "In this Venn diagram green color indicate left outer join" - is this statement TRUE/FALSE?
+#
+#
+#
+# A: TRUE
+#
+# B: FALSE
+#
+# ```{toggle}
+# ***Answer: FALSE***
+#
+# The following figure is what indicates the left outer join.
+#
+#
+#
+# But what is given here is a special scenario where we apply left join to be useful. For example, what if we want to find all students who are not taking any courses?
+#
+#
+# SELECT s.student_no,s.stud_name,s.age,c.course_name
+# FROM
+# students AS s
+# LEFT OUTER JOIN
+# courses AS c
+# ON
+# s.student_no = c.student_no
+# WHERE c.student_no is NULL
+#
+#
+# | student_no | stud_name | age | course_name |
+# |-----------:|----------:|----:|------------:|
+# | 777 | Heidy | 30 | None |
+#
+# Ahaa..! looks like "heidy" is not taking any courses; we need to check with her to see why she is not taking any courses :)
+#
+# ```
+
+# ## 🏁 Finish line 🏁 🍺
+#
+# Are you able to recollect our 3 checkpoints?
+#
+# - ***Checkpoint 1:*** Take some time and think if you can...
+# - Select columns and bring in rows just what we needed (using SELECT & WHERE)
+# - Return DISTINCT elements
+# - ORDER BY rows returned based on column(s)
+# - LIMIT the number of rows returned
+# - ***Checkpoint 2:*** Take some time and think if you can...
+# - Do some aggregations and grouping queries using SQL.
+# - GROUP BY
+# - aggregation functions
+# - HAVING
+# - ***Checkpoint 3:*** Take some time and think if you can...
+# - Understand different kinds of joins
+# - When to use joins
+# - Write SQL queries to join tables
diff --git a/_config.yml b/_config.yml
new file mode 100644
index 0000000..274917a
--- /dev/null
+++ b/_config.yml
@@ -0,0 +1,51 @@
+# Book settings
+# Learn more at https://jupyterbook.org/customize/config.html
+
+title: CPSC 304 Introduction to Relational Databases
+author: Gittu George, PhD
+email: ggeorg02@cs.ubc.ca
+logo: demo.png
+
+# Force re-execution of notebooks on each build.
+# See https://jupyterbook.org/content/execute.html
+execute:
+ execute_notebooks: force
+
+# Define the name of the latex output file for PDF builds
+latex:
+ latex_documents:
+ targetname: book.tex
+
+# Add a bibtex file so that we can create citations
+bibtex_bibfiles:
+ - references.bib
+
+launch_buttons:
+ notebook_interface : "classic" # The interface interactive links will activate ["classic", "jupyterlab"]
+ binderhub_url : "https://mybinder.org"
+ thebe : true
+
+# Information about where the book exists on the web
+repository:
+ url: https://github.com/ggeorg02/cs # Online location of your book
+ path_to_book: "" # Optional path to your book, relative to the repository root
+ branch: master # Which branch of the repository should be used when creating links (optional)
+
+# Add GitHub buttons to your book
+# See https://jupyterbook.org/customize/config.html#add-a-link-to-your-repository
+html:
+ favicon: logo.png
+ home_page_in_navbar: false
+ use_edit_page_button: true
+ use_repository_button: false
+ use_issues_button: true
+parse:
+ myst_enable_extensions:
+ # don't forget to list any other extensions you want enabled,
+ # including those that are enabled by default!
+ - html_image
+ - dollarmath
+
+sphinx:
+ extra_extensions:
+ - sphinx_thebe
diff --git a/_toc.yml b/_toc.yml
new file mode 100644
index 0000000..f53736e
--- /dev/null
+++ b/_toc.yml
@@ -0,0 +1,7 @@
+format: jb-book
+root: intro
+parts:
+- caption: Lectures
+ numbered: True
+ chapters:
+ - file: lectures/lecture2
\ No newline at end of file
diff --git a/demo.png b/demo.png
new file mode 100644
index 0000000..5341055
Binary files /dev/null and b/demo.png differ
diff --git a/img/construction.png b/img/construction.png
new file mode 100644
index 0000000..e1b2f7e
Binary files /dev/null and b/img/construction.png differ
diff --git a/intro.md b/intro.md
new file mode 100644
index 0000000..9d8469a
--- /dev/null
+++ b/intro.md
@@ -0,0 +1,6 @@
+# CPSC 304 Introduction to Relational Databases
+
+Under construction
+
+
+
diff --git a/lectures/.DS_Store b/lectures/.DS_Store
new file mode 100644
index 0000000..e09833d
Binary files /dev/null and b/lectures/.DS_Store differ
diff --git a/lectures/.ipynb_checkpoints/lecture1-checkpoint.ipynb b/lectures/.ipynb_checkpoints/lecture1-checkpoint.ipynb
new file mode 100644
index 0000000..e3707d4
--- /dev/null
+++ b/lectures/.ipynb_checkpoints/lecture1-checkpoint.ipynb
@@ -0,0 +1,171 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "2c7298d3",
+ "metadata": {},
+ "source": [
+ "# Lecture 0: Course Introduction\n",
+ "Gittu George, January 4 2022"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "64b43b00",
+ "metadata": {},
+ "source": [
+ "## Teaching squad\n",
+ "### Instructor\n",
+ "![](img/l1.png)\n",
+ "- I am Gittu George, Ph.D\n",
+ "- I am a Postdoctoral Fellow.\n",
+ "- Email Me: ggeorg02@cs.ubc.ca\n",
+ "- Office Hours: Tue 2 -3 pm\n",
+ "- Research interests are at the intersection of computer science and genomics.\n",
+ "- I primarily teach cloud computing courses with MDS, and it is my first time teaching BAIT 580A.\n",
+ "- Simon Goring developed this course, and I made my changes/additions to the course.\n",
+ "### Teaching Assistants\n",
+ "| | |\n",
+ "| :--------------------------------------- | :----------------- | \n",
+ "| | **Colby DeLisle:** Hey There! I'm a PhD candidate in physics at UBC, and I study quantum information and quantum field theory. Originally from Missouri, USA, I got my BSc in computer science from the University of Missouri before coming to Vancouver..| \n",
+ "| | **Daniel Ramandi:** Hey There! I'm a PhD student, working in a Neuroscience lab in the department of Psychiatry, looking at Neural correlates of behavior. I use machine learning to model brain activity and behavior. I'm originally from Iran, and love swimming, cooking and Vancouver! :))|\n",
+ "| | **Elisa Hu:** Hi there, my name is Elisa, I am in my last year of undergrad majoring computer science and statistics. I had 2 years TA experience before this course. Besides that, I love eating good food and explore new places. Hope to see you all in the class!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d2905962",
+ "metadata": {},
+ "source": [
+ "## Todays Agenda\n",
+ "- Course Overview\n",
+ "- Data management in a big data environment\n",
+ " - What is big data?\n",
+ " - Which tool to use?\n",
+ " - How big is big data?\n",
+ "- How to approach big data? – Some guidelines"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9ed2fbcb",
+ "metadata": {},
+ "source": [
+ "## Course Overview\n",
+ "\n",
+ "### My Goals for the Course\n",
+ "- To think critically about databases as part of an analytics workflow\n",
+ "- Learn how to design, use and understand the inner working of the SQL based databases\n",
+ "- Taking you from level zero to intermediate with the NoSQL databases (document and graph-based databases)\n",
+ "- To work with the data to find the tools best suited to answering the questions you pose\n",
+ "- To be able to present analytic workflows and decisions clearly to stakeholders\n",
+ "\n",
+ "### What this course is not about\n",
+ "- SQL or python programming \n",
+ "- Cloud computing \n",
+ "- Visualization\n",
+ "\n",
+ "### Course plan\n",
+ "\n",
+ "| Date | Topic | Assessments due |\n",
+ "|-------------|-----------------------------------------------|--------------------------------------------------------------|\n",
+ "| January 4 | Introduction to Big Data Analytics | |\n",
+ "| January 6 | Introduction to Cloud Computing | |\n",
+ "| January 11 | Data Modeling for Business Applications | Group Assignment: Question Presentation |\n",
+ "| January 13 | SQL for Visualization, constraints & cleaning | Assign1: Setting up AWS & Linking Jupyter to RDS (Postgres)- Jan 14th |\n",
+ "| January 18 | Faster SQL for Visualization | |\n",
+ "| January 20 | (de)Normalization & Data Warehousing | Group Assignment: Data Dictionary and Jupyter Notebook Draft- Jan 21st |\n",
+ "| January 25 | Introduction to NoSQL and Graph Databases | |\n",
+ "| January 27 | Querying Graph Databases | Assign2: Data pipelines with Postgres (DDL)- Jan 28th |\n",
+ "| February 1 | Modeling Data with Graphs | |\n",
+ "| February 3 | Class Conclusions/ Special Topics | |\n",
+ "| February 7 | | Assign3: Setting up & asking questions to Graph Database |\n",
+ "| February 10 | | Group Assignment: Final Report and Presentation |\n",
+ "\n",
+ "### Course Model\n",
+ "\n",
+ " \n",
+ "\n",
+ "```{sidebar} Toolsets we will be using\n",
+ "- Data - JSON, CSV\n",
+ "- Cloud Vendor - AWS\n",
+ "- RDBMS - PostgreSQL , Neo4j, MongoDB\n",
+ "- Analysis - Python & Jupyter\n",
+ "\n",
+ "```\n",
+ "\n",
+ "### Individual Assignments (50 %)\n",
+ "\n",
+ "#### Assignment 1 (16 %)\n",
+ "\n",
+ "```{sidebar} Let's checkout some tweets!!\n",
+ "```{figure} img/l3.png\n",
+ "---\n",
+ "alt: ec2-1\n",
+ "width: 400px\n",
+ "align: center\n",
+ "---\n",
+ "```\n",
+ "\n",
+ "Introduce you to AWS, working with Postgres in a Jupyter notebook\n",
+ "- Think about data in the context of a research problem\n",
+ "- Setup your AWS account\n",
+ "- Launch your database in AWS\n",
+ "- Use Python & Jupyter\n",
+ "- Get code to download data directly from an FTP\n",
+ "\n",
+ "#### Assignment 2 (17 %)\n",
+ "\n",
+ "Data Pipelines in Jupyter/Postgres\n",
+ "- Connect to a database\n",
+ "- Generate a query to select data\n",
+ "- Produce reasonable plots to explain a feature\n",
+ "\n",
+ " \n",
+ "\n",
+ "#### Assignment 3 (17 %)\n",
+ "\n",
+ "Six Degrees of Sampling\n",
+ "- An introduction to graph databases using the initial Twitter data\n",
+ "- Using the graph to answer questions about networks of interaction\n",
+ "- Producing interactive plots to represent knowledge\n",
+ "\n",
+ "### Group Assignments (40 %)\n",
+ "\n",
+ "There will 5 students in a randomly assigned group. Checkout [this](https://canvas.ubc.ca/courses/89141/discussion_topics/1258994) to find your group.\n",
+ "- Ask the question (5 %)\n",
+ "- Find the data (Jupyter & data dictionary) (10 %)\n",
+ "- Presentation and reproducible Jupyter workbook (20 %)\n",
+ "- Team work reflection (5%)\n",
+ "\n",
+ "```{figure} img/ready.png\n",
+ "---\n",
+ "width: 250px\n",
+ "align: center\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.8.5"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/lectures/.ipynb_checkpoints/lecture2-checkpoint.ipynb b/lectures/.ipynb_checkpoints/lecture2-checkpoint.ipynb
new file mode 100644
index 0000000..27c4f4a
--- /dev/null
+++ b/lectures/.ipynb_checkpoints/lecture2-checkpoint.ipynb
@@ -0,0 +1,4857 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "3867df60",
+ "metadata": {},
+ "source": [
+ "# Lecture 2: SQL - Class 2\n",
+ "Gittu George, February 24 2022"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0c890d68",
+ "metadata": {},
+ "source": [
+ "## Today's Agenda\n",
+ "\n",
+ "- Refresher (Warm-up)\n",
+ "- Refresher exercise\n",
+ "- Learning objectives\n",
+ "- More SQL commands (lap 1)\n",
+ "- Aggregations (lap 2)\n",
+ "- Grouping (lap 2)\n",
+ "- Joins (lap 3)\n",
+ "- Summary (finish line)\n",
+ "\n",
+ "## Warm-up ☕️\n",
+ "### Refresher!! Take some time and think if you know...\n",
+ "\n",
+ "- What are databases?\n",
+ "- Where is it commonly used?\n",
+ "- How to set up a database?\n",
+ "- General structure of a database\n",
+ "- Various ways of interacting with the database, using\n",
+ " - command-line interface\n",
+ " - ODBC drivers via developer interfaces like pgadmin and toad.\n",
+ " - jupyter notebook ( which we will be using throughout the course)\n",
+ "- To create a table and various data types \n",
+ "- Integrity constraints such as primary key and foreign key\n",
+ "- Basic SQL commands like SELECT, FROM, and WHERE\n",
+ "\n",
+ "In this class, we will be using the tables that we created in lecture 1. Here are the scripts if you want to recreate the classroom exercise table.\n",
+ "\n",
+ "```{toggle}\n",
+ "\n",
+ "```sql\n",
+ "DROP TABLE IF EXISTS students,courses;\n",
+ "\n",
+ "CREATE TABLE IF NOT EXISTS students(\n",
+ "student_no integer PRIMARY KEY, \n",
+ "stud_name text,\n",
+ "age integer,\n",
+ "major text);\n",
+ "\n",
+ "CREATE TABLE IF NOT EXISTS courses(\n",
+ "id SERIAL PRIMARY KEY,\n",
+ "student_no integer, \n",
+ "course_name text,\n",
+ "course_year integer,\n",
+ "course_percentage float);\n",
+ "\n",
+ "INSERT INTO students(student_no,stud_name,age,major) VALUES (111,'Catherine',23,'MBAN'),\n",
+ "(222,'Tiff',28,'MDS'),\n",
+ "(333,'John',23,'MBAN'),\n",
+ "(444,'Amir',28,'MBAN'),\n",
+ "(555,'Gittu',20,'MDS'),\n",
+ "(666,'Isha',30,'MBAN'),\n",
+ "(777,'Heidy',30,'MBAN'),\n",
+ "(888,'Angela',27,'MBAN'),\n",
+ "(999,'Jason',30,'MBAN');\n",
+ "\n",
+ "INSERT INTO courses(student_no,course_name,course_year,course_percentage) VALUES (111,'TPCS INFO TECH',2019,88),\n",
+ "(111,'Data Visualization',2019,88),\n",
+ "(222,'Health and Technology',2020,80),\n",
+ "(222,'Web and Cloud Computing',2019,91),\n",
+ "(222,'Spark Programming',2019,90),\n",
+ "(333,'Parallel Computing',2019,90),\n",
+ "(444,'Large Scale Infrastructures',2019,83),\n",
+ "(444,'TPCS INFO TECH',2019,98),\n",
+ "(555,'TPCS INFO TECH',2020,78),\n",
+ "(555,'Health and Technology',2020,81),\n",
+ "(666,'Data Visualization',2021,85),\n",
+ "(666,'Parallel Computing',2019,87),\n",
+ "(888,'Spark Programming',2019,93),\n",
+ "(999,'TPCS INFO TECH',2019,98),\n",
+ "(999,'Data Visualization',2019,87),\n",
+ "(1000,'C programming',2019,87),\n",
+ "(1111,'Introduction to Genomics',2019,87);\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a754bc90-1a0a-4b5e-8063-78c16951e14d",
+ "metadata": {},
+ "source": [
+ "First of all let's load SQL to work on this notebook."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "id": "5b537cb8-53cc-4bf2-82a3-560c697eef4a",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import os\n",
+ "import psycopg2\n",
+ "from dotenv import load_dotenv\n",
+ "load_dotenv()\n",
+ "host = os.environ.get('DB_HOST')\n",
+ "user = os.environ.get('DB_USER')\n",
+ "password = os.environ.get('DB_PASS')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "50f1cc3c-aaec-499f-883f-390832ddc3d8",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%load_ext sql"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "id": "fedffc66-2974-4c48-b61a-8d38d74e5596",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'Connected: postgres@postgres'"
+ ]
+ },
+ "execution_count": 18,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%sql postgresql://{user}:{password}@{host}:5432/postgres"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "350fda14-b35c-40f8-9a7d-5b8c90032c4a",
+ "metadata": {
+ "tags": []
+ },
+ "source": [
+ "### Refresher exercise \n",
+ "\n",
+ "***Question 1:*** In this table `eg`\n",
+ "\n",
+ "| firstname | countryfrom | continent | gender | age |\n",
+ "|-----------|-------------|---------------|--------|-----|\n",
+ "| matt | usa | north america | M | 23 |\n",
+ "| jenn | uk | europe | F | 35 |\n",
+ "| guy | france | europe | M | 25 |\n",
+ "| james | china | asia | M | 29 |\n",
+ "| lida | india | asia | F | 56 |\n",
+ "| linda | canada | north america | F | 18 |\n",
+ "| sofia | germany | europe | F | 22 |\n",
+ "| george | india | asia | M | 29 |\n",
+ "\n",
+ "What will be returned from following SQL \n",
+ "```sql\n",
+ "select firstname,gender from eg where continent ='asia' AND age =29\n",
+ "```\n",
+ "A) george,M\n",
+ "\n",
+ "B) george,M,asia,29\n",
+ "\n",
+ "C) george,india,asia, M,29 \n",
+ "\n",
+ "D) M,george\n",
+ "\n",
+ "E) M,james\n",
+ "\n",
+ "```{toggle}\n",
+ "\n",
+ "***Answer: D***\n",
+ "\n",
+ "```{important}\n",
+ "Remember the order of the columns returned will be based on the order of columns that we specify within the select statement.\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "69eba2fb-8158-44e0-ae46-64410b0be50a",
+ "metadata": {},
+ "source": [
+ "***Question 2:*** Consider a scenario where you want to perform analysis on a table (peopletable) with 100 columns (including personname,age,gender,origin...etc.) that define a person. You are interested in seeing the name of individuals older than 90. Which SQL query is more appropriate in this situation?\n",
+ "\n",
+ "A) ***select * from peopletable where age > 90;***\n",
+ "\n",
+ "B) ***select personname,age from peopletable where age > 90;***\n",
+ "\n",
+ "B) ***select personname,age,gender,origin from peopletable where age < 90;***\n",
+ "\n",
+ "D) ***select personname from peopletable where age > 90;***\n",
+ "\n",
+ "```{toggle}\n",
+ "\n",
+ "***Answer: D***\n",
+ "\n",
+ "```{important}\n",
+ "Just bring the columns and rows that are needed. Even though `SELECT` and `WHERE` are very basic SQL commands, it's crucial when you are dealing with a large table\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9a7e0bbd-ae06-4b1b-b934-cdf23e081828",
+ "metadata": {},
+ "source": [
+ "## Learning objectives\n",
+ "- You will be able to create SQL queries using (Lap 1 & Lap 2)\n",
+ " - Distinct, \n",
+ " - ORDER BY, \n",
+ " - LIMIT, \n",
+ " - GROUP BY, \n",
+ " - alias AS\n",
+ "- You will learn about different kinds of joins and be able to create SQL queries that perform `JOINS`. (Lap 3)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a033eb62-9c25-4b69-8cb2-14a36cd9272b",
+ "metadata": {},
+ "source": [
+ "## Lap 1 🥛\n",
+ "### DISTINCT\n",
+ "\n",
+ "The DISTINCT statement is used only to return distinct elements from a table.\n",
+ "\n",
+ "***Syntax:***\n",
+ "\n",
+ "```sql\n",
+ "SELECT DISTINCT column1, column2, ...columnN\n",
+ "FROM tablename;\n",
+ "```\n",
+ "\n",
+ "`DISTINCT` is applied to all columns that follows the `DISTINCT` keyword. Say for eg if we give `DISTINCT column1, column2` then the combination of values in both `column1` and `column2` columns will be used for returning the unique combination (or removing the duplicate elements)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "id": "d92b4b94-d2cd-4f44-86de-baf632b91ba6",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "7 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " course_name \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " Large Scale Infrastructures \n",
+ " \n",
+ " \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " Web and Cloud Computing \n",
+ " \n",
+ " \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[('Data Visualization',),\n",
+ " ('Large Scale Infrastructures',),\n",
+ " ('Spark Programming',),\n",
+ " ('Web and Cloud Computing',),\n",
+ " ('Health and Technology',),\n",
+ " ('TPCS INFO TECH',),\n",
+ " ('Parallel Computing',)]"
+ ]
+ },
+ "execution_count": 17,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT DISTINCT course_name FROM courses;"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 94,
+ "id": "1dc8e64b-e555-4580-9b95-1ea9af3d0273",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "11 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " course_name \n",
+ " course_year \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " C programming \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " \n",
+ " \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[('Parallel Computing', 2019),\n",
+ " ('C programming', 2019),\n",
+ " ('TPCS INFO TECH', 2019),\n",
+ " ('Spark Programming', 2019),\n",
+ " ('Web and Cloud Computing', 2019),\n",
+ " ('Health and Technology', 2020),\n",
+ " ('Introduction to Genomics', 2019),\n",
+ " ('Large Scale Infrastructures', 2019),\n",
+ " ('TPCS INFO TECH', 2020),\n",
+ " ('Data Visualization', 2021),\n",
+ " ('Data Visualization', 2019)]"
+ ]
+ },
+ "execution_count": 94,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT DISTINCT course_name, course_year FROM courses;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "904d711d-aad3-4c57-8291-86b1ea1c8604",
+ "metadata": {},
+ "source": [
+ "### ORDER BY\n",
+ "\n",
+ "`ORDER BY` statement sorts the results returned by `SELECT` based on a sort expression.\n",
+ "\n",
+ "Syntax\n",
+ "```sql\n",
+ "SELECT column1, column2 ...columnN\n",
+ "FROM table_name\n",
+ "ORDER BY column1 [ASC | DESC], column2 [ASC | DESC] ....columnN [ASC|DESC];\n",
+ "```\n",
+ "\n",
+ "```{note}\n",
+ "By default, it will sort in ASC. So you can choose not to give ASC.\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 95,
+ "id": "922e1099-7d1b-41ff-aa18-21492c59b20a",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "17 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " id \n",
+ " student_no \n",
+ " course_name \n",
+ " course_year \n",
+ " course_percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 16 \n",
+ " 1000 \n",
+ " C programming \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 17 \n",
+ " 1111 \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 91.0 \n",
+ " \n",
+ " \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 12 \n",
+ " 666 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 93.0 \n",
+ " \n",
+ " \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.0 \n",
+ " \n",
+ " \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 81.0 \n",
+ " \n",
+ " \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (15, 999, 'Data Visualization', 2019, 87.0),\n",
+ " (16, 1000, 'C programming', 2019, 87.0),\n",
+ " (17, 1111, 'Introduction to Genomics', 2019, 87.0),\n",
+ " (4, 222, 'Web and Cloud Computing', 2019, 91.0),\n",
+ " (5, 222, 'Spark Programming', 2019, 90.0),\n",
+ " (6, 333, 'Parallel Computing', 2019, 90.0),\n",
+ " (7, 444, 'Large Scale Infrastructures', 2019, 83.0),\n",
+ " (8, 444, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (12, 666, 'Parallel Computing', 2019, 87.0),\n",
+ " (13, 888, 'Spark Programming', 2019, 93.0),\n",
+ " (3, 222, 'Health and Technology', 2020, 80.0),\n",
+ " (10, 555, 'Health and Technology', 2020, 81.0),\n",
+ " (9, 555, 'TPCS INFO TECH', 2020, 78.0),\n",
+ " (11, 666, 'Data Visualization', 2021, 85.0)]"
+ ]
+ },
+ "execution_count": 95,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT * \n",
+ "FROM courses\n",
+ "ORDER BY course_year;"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 96,
+ "id": "57c57348-9def-4d46-8b9e-eac8d509cf60",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "17 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " id \n",
+ " student_no \n",
+ " course_name \n",
+ " course_year \n",
+ " course_percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 81.0 \n",
+ " \n",
+ " \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.0 \n",
+ " \n",
+ " \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 12 \n",
+ " 666 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 93.0 \n",
+ " \n",
+ " \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 16 \n",
+ " 1000 \n",
+ " C programming \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 17 \n",
+ " 1111 \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 91.0 \n",
+ " \n",
+ " \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(11, 666, 'Data Visualization', 2021, 85.0),\n",
+ " (10, 555, 'Health and Technology', 2020, 81.0),\n",
+ " (3, 222, 'Health and Technology', 2020, 80.0),\n",
+ " (9, 555, 'TPCS INFO TECH', 2020, 78.0),\n",
+ " (6, 333, 'Parallel Computing', 2019, 90.0),\n",
+ " (7, 444, 'Large Scale Infrastructures', 2019, 83.0),\n",
+ " (8, 444, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (12, 666, 'Parallel Computing', 2019, 87.0),\n",
+ " (13, 888, 'Spark Programming', 2019, 93.0),\n",
+ " (14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (15, 999, 'Data Visualization', 2019, 87.0),\n",
+ " (16, 1000, 'C programming', 2019, 87.0),\n",
+ " (1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (17, 1111, 'Introduction to Genomics', 2019, 87.0),\n",
+ " (2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (4, 222, 'Web and Cloud Computing', 2019, 91.0),\n",
+ " (5, 222, 'Spark Programming', 2019, 90.0)]"
+ ]
+ },
+ "execution_count": 96,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT * \n",
+ "FROM courses\n",
+ "ORDER BY course_year DESC;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "25e1020a-93a1-48e9-a763-80674b98b46c",
+ "metadata": {},
+ "source": [
+ "### LIMIT\n",
+ "\n",
+ "Until now, we were returning everything that our SQL query returns. `LIMIT` statement is used to limit the number of rows that are returned. \n",
+ "\n",
+ "syntax:\n",
+ "\n",
+ "```sql\n",
+ "SELECT column1, column2, ...columnN\n",
+ "FROM tablename\n",
+ "LIMIT numberofrows;\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 97,
+ "id": "ef67fccb-2300-4109-b626-8bc7580b29c2",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "2 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " id \n",
+ " student_no \n",
+ " course_name \n",
+ " course_year \n",
+ " course_percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (2, 111, 'Data Visualization', 2019, 88.0)]"
+ ]
+ },
+ "execution_count": 97,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT * \n",
+ "FROM courses\n",
+ "LIMIT 2;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "74228e90-a013-4c9b-b34b-c7cc8fdea343",
+ "metadata": {},
+ "source": [
+ "`LIMIT` keyword is used in a variety of situations. Here are a few cases\n",
+ "\n",
+ "- ***Memory Management:*** Say you just want to look at the output your query returns. If you are dealing with lots and lots of rows, returning the entire rows can slow down the query, cause memory issues, and finally crash your jupyter. In these cases, you can use `LIMIT`. \n",
+ "\n",
+ "- ***Interest in the first few rows:*** If we are interested in just the first N rows, we can achieve that using `LIMIT`. People tend to use `LIMIT` a lot when they want to return the top 10 rows after performing an `ORDER BY`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "dac09f4e-ebbf-4bf1-9c13-d45c3249cf30",
+ "metadata": {},
+ "source": [
+ "Let's apply all the statements that we learned in one statement.\n",
+ "\n",
+ "***Question:*** List out the row that got the highest `course_percentage` for the `Data Visualization` course."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 98,
+ "id": "368c7bdf-7c5c-4e8c-81ae-02de2044b7d8",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "1 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " id \n",
+ " student_no \n",
+ " course_name \n",
+ " course_year \n",
+ " course_percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(2, 111, 'Data Visualization', 2019, 88.0)]"
+ ]
+ },
+ "execution_count": 98,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT * \n",
+ "FROM courses\n",
+ "WHERE course_name = 'Data Visualization'\n",
+ "ORDER BY course_percentage DESC\n",
+ "LIMIT 1;"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 124,
+ "id": "81a06062-3b03-4c69-ac1a-d592e043b437",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "8 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " age \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 18 \n",
+ " \n",
+ " \n",
+ " 22 \n",
+ " \n",
+ " \n",
+ " 23 \n",
+ " \n",
+ " \n",
+ " 25 \n",
+ " \n",
+ " \n",
+ " 29 \n",
+ " \n",
+ " \n",
+ " 29 \n",
+ " \n",
+ " \n",
+ " 35 \n",
+ " \n",
+ " \n",
+ " 56 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(18,), (22,), (23,), (25,), (29,), (29,), (35,), (56,)]"
+ ]
+ },
+ "execution_count": 124,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%sql select age from eg order by age"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ca99d216-561f-4cc2-ad74-9b6dc6174a76",
+ "metadata": {},
+ "source": [
+ "### Checkpoint 1 !! Take some time and think if you can...\n",
+ "\n",
+ "- Select columns and bring in rows just what we need (using `SELECT` & `WHERE`)\n",
+ "- Return `DISTINCT` elements\n",
+ "- `ORDER BY` rows returned based on column(s)\n",
+ "- `LIMIT` the number of rows returned\n",
+ "\n",
+ "Now we will learn some more advanced SQL operations to gain more insight into the data. But, before that, let's do some exercise. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9fd2624e-57a1-4250-9833-a52db657be86",
+ "metadata": {},
+ "source": [
+ "### Check point 1 exercise.\n",
+ "\n",
+ "#### iclicker questions\n",
+ "\n",
+ "Answer the following questions using the table `eg`\n",
+ "\n",
+ "| firstname | countryfrom | continent | gender | age |\n",
+ "|-----------|-------------|---------------|--------|-----|\n",
+ "| matt | usa | north america | M | 23 |\n",
+ "| jenn | uk | europe | F | 35 |\n",
+ "| guy | france | europe | M | 25 |\n",
+ "| james | china | asia | M | 29 |\n",
+ "| lida | india | asia | F | 56 |\n",
+ "| linda | canada | north america | F | 18 |\n",
+ "| sofia | germany | europe | F | 22 |\n",
+ "| george | india | asia | M | 29 |\n",
+ "\n",
+ "\n",
+ "***Question 1:*** How many elements will be returned from this SQL \n",
+ "\n",
+ "```sql\n",
+ "select DISTINCT gender,firstname from eg\n",
+ "```\n",
+ "\n",
+ "A) 2\n",
+ "\n",
+ "B) 8\n",
+ "\n",
+ "C) 4 \n",
+ "\n",
+ "```{toggle}\n",
+ "***Answer: B***\n",
+ "```\n",
+ "***Question 2:*** What will be the first value returned from this SQL \n",
+ "\n",
+ "```sql\n",
+ "select age from eg order by age\n",
+ "```\n",
+ "A) 23\n",
+ "\n",
+ "B) 18\n",
+ "\n",
+ "C) 56\n",
+ "\n",
+ "D) 35\n",
+ "\n",
+ "```{toggle}\n",
+ "***Answer: B***\n",
+ "```\n",
+ "\n",
+ "#### Reasoning Question\n",
+ "\n",
+ "***Question 1:*** Write a SQL query to list the row that got the highest `course_percentage` for the `TPCS INFO TECH'` course. Can you spot any issues by examining the original table?\n",
+ "\n",
+ "```{toggle}\n",
+ "\n",
+ "You probably might have got this SQL query by changing the SQL query that we discussed for the \"Data Visualization\" course. \n",
+ "\n",
+ "***SELECT * FROM courses\n",
+ "WHERE course_name = 'TPCS INFO TECH'\n",
+ "ORDER BY course_percentage DESC\n",
+ "LIMIT 1;***\n",
+ "\n",
+ "This gives you \n",
+ "\n",
+ "| id | student_no | course_name | course_year | course_percentage |\n",
+ "|---:|-----------:|---------------:|------------:|------------------:|\n",
+ "| 8 | 444 | TPCS INFO TECH | 2019 | 98.0 |\n",
+ "\n",
+ "What are possible issues? Take out the `LIMIT`, and you might notice there is a tie, and 2 students, 444 and 999 scored the highest.\n",
+ "\n",
+ "\n",
+ "***SELECT * FROM courses\n",
+ "WHERE course_name = 'TPCS INFO TECH'\n",
+ "ORDER BY course_percentage DESC***\n",
+ "\n",
+ "\n",
+ "| id | student_no | course_name | course_year | course_percentage |\n",
+ "|---:|-----------:|---------------:|------------:|------------------:|\n",
+ "| 8 | 444 | TPCS INFO TECH | 2019 | 98.0 |\n",
+ "| 14 | 999 | TPCS INFO TECH | 2019 | 98.0 |\n",
+ "| 1 | 111 | TPCS INFO TECH | 2019 | 88.0 |\n",
+ "| 9 | 555 | TPCS INFO TECH | 2020 | 78.0 |\n",
+ "\n",
+ "So even though many of them use a combination of `ORDER BY` and `LIMIT` for these scenarios we might run into situations like this. There are a couple of ways to deal with these kinds of scenarios, and we will learn about `subquery` in our next class, which will help you capture ties."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "91ab04a8-4456-4653-9348-4ad153493bb8",
+ "metadata": {},
+ "source": [
+ "## Lap 2 🧋\n",
+ "### Aggregations\n",
+ "\n",
+ "So far, we have returned columns in our select statement. We can also use aggregation functions that operate on rows to summarize the data in the form of a single value. Here is a list of aggregation functions in SQL:\n",
+ "\n",
+ "| Function | Description |\n",
+ "|----------|------------------|\n",
+ "| MAX() | maximum value |\n",
+ "| MIN() | minimum value |\n",
+ "| AVG() | average value |\n",
+ "| SUM() | sum of values |\n",
+ "| COUNT() | number of values |"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 35,
+ "id": "66c023e1-c594-4c0c-8809-7dc54ee6ca4b",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "1 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " count \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 15 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(15,)]"
+ ]
+ },
+ "execution_count": 35,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT COUNT(course_name) FROM courses;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7d0df81a-5c7c-48e3-b05a-4410be8f4840",
+ "metadata": {},
+ "source": [
+ "The above query is counting number of values in the column `course_name`. it's also sort of like counting the number of rows. We can also pass the `DISTINCT` columns into these operations. For example, the below query will find the number of courses available in the university."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 36,
+ "id": "ca947761-3728-4594-83d4-d6484927b7cf",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "1 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " count \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 7 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(7,)]"
+ ]
+ },
+ "execution_count": 36,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT COUNT(DISTINCT course_name) FROM courses;"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 126,
+ "id": "e505b586-f4ea-436e-8ade-abb9e5892638",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "1 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " avg \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 87.70588235294117 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(87.70588235294117,)]"
+ ]
+ },
+ "execution_count": 126,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "select AVG(course_percentage) \n",
+ "FROM courses"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c28c9c2e-6275-4e8c-bee9-36f5795f62eb",
+ "metadata": {},
+ "source": [
+ "Few things to keep in mind when dealing with the aggregation function\n",
+ "\n",
+ "- You are not restricted in using just one aggregation function in the SELECT statement.✅\n",
+ "\n",
+ "```sql\n",
+ "SELECT COUNT(DISTINCT course_name),max(course_percentage) FROM courses;\n",
+ "```\n",
+ "\n",
+ "- You CAN'T use aggregations and regular columns in a single query. You can use only when you have a `GROUP BY` clause. (will discuss soon)❌\n",
+ "\n",
+ "```sql\n",
+ "SELECT COUNT(DISTINCT course_name),course_percentage FROM courses;\n",
+ "```\n",
+ "\n",
+ "- You CAN'T use aggregation function in a where clause. The following query is wrong ❌\n",
+ "\n",
+ "```sql\n",
+ "SELECT * FROM courses WHERE course_percentage < AVG(course_percentage);\n",
+ "```\n",
+ "\n",
+ "You can answer this question when we discuss subqueries in the next class (another reason to learn subqueries :) )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2164f169-3e19-4804-9985-4a2f392cbd13",
+ "metadata": {},
+ "source": [
+ "### Grouping\n",
+ "\n",
+ "The aggregations we learned in our previous session also become useful when using the `GROUP BY` statement. `GROUP BY` statement divides a table into groups of rows based on the values of one or more columns. Once this grouping is done, you can apply your aggregation to these groups.\n",
+ "\n",
+ "Syntax:\n",
+ "```sql\n",
+ "SELECT\n",
+ " grouping_columns, aggregated_columns\n",
+ "FROM\n",
+ " table1\n",
+ "GROUP BY\n",
+ " grouping_columns\n",
+ "```\n",
+ "\n",
+ "Example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 42,
+ "id": "7ac98229-6261-48fb-ac85-20435c5d325c",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "15 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " id \n",
+ " student_no \n",
+ " course_name \n",
+ " course_year \n",
+ " course_percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.0 \n",
+ " \n",
+ " \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 91.0 \n",
+ " \n",
+ " \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 81.0 \n",
+ " \n",
+ " \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 93.0 \n",
+ " \n",
+ " \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (3, 222, 'Health and Technology', 2020, 80.0),\n",
+ " (4, 222, 'Web and Cloud Computing', 2019, 91.0),\n",
+ " (5, 222, 'Spark Programming', 2019, 90.0),\n",
+ " (6, 333, 'Parallel Computing', 2019, 90.0),\n",
+ " (7, 444, 'Large Scale Infrastructures', 2019, 83.0),\n",
+ " (8, 444, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (9, 555, 'TPCS INFO TECH', 2020, 78.0),\n",
+ " (10, 555, 'Health and Technology', 2020, 81.0),\n",
+ " (11, 666, 'Data Visualization', 2021, 85.0),\n",
+ " (12, 777, 'Parallel Computing', 2019, 87.0),\n",
+ " (13, 888, 'Spark Programming', 2019, 93.0),\n",
+ " (14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (15, 999, 'Data Visualization', 2019, 87.0)]"
+ ]
+ },
+ "execution_count": 42,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "select * from courses;"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 43,
+ "id": "dc492d9a-f26d-494b-97c0-ef208c6c3187",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "7 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " course_name \n",
+ " avg \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 86.66666666666667 \n",
+ " \n",
+ " \n",
+ " Large Scale Infrastructures \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " Spark Programming \n",
+ " 91.5 \n",
+ " \n",
+ " \n",
+ " Web and Cloud Computing \n",
+ " 91.0 \n",
+ " \n",
+ " \n",
+ " Health and Technology \n",
+ " 80.5 \n",
+ " \n",
+ " \n",
+ " TPCS INFO TECH \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " Parallel Computing \n",
+ " 88.5 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[('Data Visualization', 86.66666666666667),\n",
+ " ('Large Scale Infrastructures', 83.0),\n",
+ " ('Spark Programming', 91.5),\n",
+ " ('Web and Cloud Computing', 91.0),\n",
+ " ('Health and Technology', 80.5),\n",
+ " ('TPCS INFO TECH', 88.0),\n",
+ " ('Parallel Computing', 88.5)]"
+ ]
+ },
+ "execution_count": 43,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "select course_name, AVG(course_percentage) \n",
+ "FROM courses\n",
+ "group by course_name;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d3e6fb09-9bf3-4c40-9933-158edd938eb6",
+ "metadata": {},
+ "source": [
+ "We can also perform a multi level grouping;"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 51,
+ "id": "a9edaba0-6db9-4c80-927c-2df18798d0f3",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "6 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " course_name \n",
+ " course_year \n",
+ " avg \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.5 \n",
+ " \n",
+ " \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.5 \n",
+ " \n",
+ " \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.5 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[('Parallel Computing', 2019, 88.5),\n",
+ " ('Health and Technology', 2020, 80.5),\n",
+ " ('Large Scale Infrastructures', 2019, 83.0),\n",
+ " ('TPCS INFO TECH', 2020, 78.0),\n",
+ " ('Data Visualization', 2021, 85.0),\n",
+ " ('Data Visualization', 2019, 87.5)]"
+ ]
+ },
+ "execution_count": 51,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "select course_name, course_year,AVG(course_percentage) \n",
+ "FROM courses\n",
+ "group by course_name, course_year\n",
+ "having AVG(course_percentage) <90 ;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "59b16985-fefc-4326-bfde-b7f55dcf9610",
+ "metadata": {},
+ "source": [
+ "Now, what if I want to see only the courses with an average of less than 90 %? We mentioned before that this kind of filtering (filtering on the aggregation function) is not possible using `WHERE` statement, and that's why we want the `HAVING` statement to do filtering using these aggregated values.\n",
+ "\n",
+ "### HAVING\n",
+ "\n",
+ "Syntax:\n",
+ "```sql\n",
+ "SELECT\n",
+ " grouping_columns, aggregated_columns\n",
+ "FROM\n",
+ " table1\n",
+ "GROUP BY\n",
+ " grouping_columns\n",
+ "HAVING\n",
+ " group_condition\n",
+ "\n",
+ "```\n",
+ "\n",
+ "```{important}\n",
+ "To summarize:\n",
+ "\n",
+ "- WHERE filters rows before grouping. It filters records in a table level\n",
+ "- HAVING filters groups after grouping. It filters records in a group level\n",
+ "```\n",
+ "\n",
+ "For example, let's get the question we raised at the end of the grouping section. I want to see the courses with a course average of less than 90 %? "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 49,
+ "id": "8210644e-b6bd-4106-aa3b-85520b30380a",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "5 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " course_name \n",
+ " avg \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 86.66666666666667 \n",
+ " \n",
+ " \n",
+ " Large Scale Infrastructures \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " Health and Technology \n",
+ " 80.5 \n",
+ " \n",
+ " \n",
+ " TPCS INFO TECH \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " Parallel Computing \n",
+ " 88.5 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[('Data Visualization', 86.66666666666667),\n",
+ " ('Large Scale Infrastructures', 83.0),\n",
+ " ('Health and Technology', 80.5),\n",
+ " ('TPCS INFO TECH', 88.0),\n",
+ " ('Parallel Computing', 88.5)]"
+ ]
+ },
+ "execution_count": 49,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "select course_name, AVG(course_percentage) \n",
+ "FROM courses\n",
+ "group by course_name\n",
+ "having AVG(course_percentage) <90 ;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ac4b81f6-37f8-4e28-914d-9d73aa271733",
+ "metadata": {},
+ "source": [
+ "### Using alias (AS)\n",
+ "\n",
+ "Until now, we have been referring tables as table names and columns as column names from the schema. But when writing SQL, we are not required to use the same column and table names as in the schema. Instead, we can create aliases for a column or a table using the keyword `AS`.\n",
+ "\n",
+ "Syntax:\n",
+ "```sql\n",
+ "SELECT\n",
+ " column1 [AS] c1,\n",
+ " column2 [AS] c2\n",
+ "FROM\n",
+ " table1 [AS] t1;\n",
+ "```\n",
+ "\n",
+ "It's entirely optional to use AS, but WHY do we want it? \n",
+ "- This makes code more readable\n",
+ "- You can return the columns with a more meaningful name \n",
+ "- Helps a lot when we do JOINS ( wait for the next topic)\n",
+ "\n",
+ "Let's rewrite our previous query using AS"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 129,
+ "id": "4ff59e7a-50e7-4c4a-8469-35ee6481ee29",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "8 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " course_name \n",
+ " course_year \n",
+ " average course percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.5 \n",
+ " \n",
+ " \n",
+ " C programming \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.5 \n",
+ " \n",
+ " \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.5 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[('Parallel Computing', 2019, 88.5),\n",
+ " ('C programming', 2019, 87.0),\n",
+ " ('Health and Technology', 2020, 80.5),\n",
+ " ('Introduction to Genomics', 2019, 87.0),\n",
+ " ('Large Scale Infrastructures', 2019, 83.0),\n",
+ " ('TPCS INFO TECH', 2020, 78.0),\n",
+ " ('Data Visualization', 2021, 85.0),\n",
+ " ('Data Visualization', 2019, 87.5)]"
+ ]
+ },
+ "execution_count": 129,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "select course_name, course_year,AVG(course_percentage) AS \"average course percentage\"\n",
+ "FROM courses AS c\n",
+ "group by course_name, course_year\n",
+ "having AVG(course_percentage) <90 ;"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 137,
+ "id": "d66a7187-7ea7-42d5-a90d-76e584456600",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "17 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " id \n",
+ " student_no \n",
+ " course_name \n",
+ " course_year \n",
+ " course_percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.0 \n",
+ " \n",
+ " \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 91.0 \n",
+ " \n",
+ " \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 81.0 \n",
+ " \n",
+ " \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ " 12 \n",
+ " 666 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 93.0 \n",
+ " \n",
+ " \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 16 \n",
+ " 1000 \n",
+ " C programming \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 17 \n",
+ " 1111 \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (3, 222, 'Health and Technology', 2020, 80.0),\n",
+ " (4, 222, 'Web and Cloud Computing', 2019, 91.0),\n",
+ " (5, 222, 'Spark Programming', 2019, 90.0),\n",
+ " (6, 333, 'Parallel Computing', 2019, 90.0),\n",
+ " (7, 444, 'Large Scale Infrastructures', 2019, 83.0),\n",
+ " (8, 444, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (9, 555, 'TPCS INFO TECH', 2020, 78.0),\n",
+ " (10, 555, 'Health and Technology', 2020, 81.0),\n",
+ " (11, 666, 'Data Visualization', 2021, 85.0),\n",
+ " (12, 666, 'Parallel Computing', 2019, 87.0),\n",
+ " (13, 888, 'Spark Programming', 2019, 93.0),\n",
+ " (14, 999, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (15, 999, 'Data Visualization', 2019, 87.0),\n",
+ " (16, 1000, 'C programming', 2019, 87.0),\n",
+ " (17, 1111, 'Introduction to Genomics', 2019, 87.0)]"
+ ]
+ },
+ "execution_count": 137,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT *\n",
+ "FROM courses;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a81789ef-1835-4a47-bc68-d172531eefbc",
+ "metadata": {},
+ "source": [
+ "### Checkpoint 2 !! Take some time and think if you can...\n",
+ "\n",
+ "- Do some aggregations and grouping queries using SQL.\n",
+ " - GROUP BY\n",
+ " - aggregation functions\n",
+ " - HAVING\n",
+ "\n",
+ "Until now, you deal with queries on a single table. What if we are interested in data from another table as well? For example, I am interested in seeing the course details (from the `courses` table) and the students (from the `students` table) related to those courses. In these situations, we use joins. Let's learn how to stitch tables together. Before that, let's do some exercise..."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f99b3fa8-0acd-4c08-891c-0c3dfc8865c5",
+ "metadata": {},
+ "source": [
+ "### Check point 2 exercise.\n",
+ "\n",
+ "#### iclicker questions\n",
+ "\n",
+ "***Question 1:*** Spot the issue, if any, in this SQL query \n",
+ "\n",
+ "```sql\n",
+ "SELECT COUNT(DISTINCT course_name),max(course_percentage),course_percentage \n",
+ "FROM courses ;\n",
+ "```\n",
+ "\n",
+ "A: Multiple aggregation functions in the SELECT statement\n",
+ "\n",
+ "B: No issues\n",
+ "\n",
+ "C: Can't use DISTINCT inside an aggregation function\n",
+ "\n",
+ "D: Can't use aggregations and regular columns in a single query\n",
+ "\n",
+ "```{toggle}\n",
+ "***Answer: D***\n",
+ "```\n",
+ "\n",
+ "***Question 2:*** Spot the issue, if any, in this SQL query \n",
+ "\n",
+ "```sql\n",
+ "SELECT COUNT(DISTINCT course_name),max(course_percentage),course_year\n",
+ "FROM courses \n",
+ "GROUP BY course_year;\n",
+ "```\n",
+ "\n",
+ "A: Multiple aggregation functions in the SELECT statement\n",
+ "\n",
+ "B: No issues\n",
+ "\n",
+ "C: Can't use DISTINCT inside an aggregation function\n",
+ "\n",
+ "D: Can't use aggregations and regular columns in a single query\n",
+ "\n",
+ "```{toggle}\n",
+ "***Answer: B***\n",
+ "```\n",
+ "\n",
+ "***Question 3:*** Spot the issue, if any, in this SQL query \n",
+ "\n",
+ "```sql\n",
+ "SELECT COUNT(DISTINCT course_name),max(course_percentage),course_year\n",
+ "FROM courses \n",
+ "WHERE course_name != 'TPCS INFO TECH'\n",
+ "GROUP BY course_year\n",
+ "having course_percentage < 90;\n",
+ "```\n",
+ "\n",
+ "A: No issues\n",
+ "\n",
+ "B: Can't use column `course_year` in `SELECT`\n",
+ "\n",
+ "C: Can't use WHERE when using aggregation functions\n",
+ "\n",
+ "D: Can't use course_percentage in a `HAVING` statement\n",
+ "\n",
+ "```{toggle}\n",
+ "***Answer: D***\n",
+ "```\n",
+ "\n",
+ "#### Reasoning Question\n",
+ "\n",
+ "***Question 1:*** We learned the `GROUP BY` clause, and we used it with aggregate functions. Using table `courses`, write a SQL query using `GROUP BY` without any aggregation function. Write your findings and learnings.\n",
+ "\n",
+ "```{toggle}\n",
+ "You must have tried a variety of SQL queries and got into error;\n",
+ "\n",
+ "***select * FROM courses\n",
+ "group by course_name;***\n",
+ "\n",
+ "***select course_year,course_name\n",
+ "FROM courses\n",
+ "group by course_name;***\n",
+ "\n",
+ "Below is one query that runs, and this query can be considered equivalent to using `DISTINCT` if no aggregate functions are used. \n",
+ "\n",
+ "***select course_name\n",
+ "FROM courses\n",
+ "group by course_name;***\n",
+ "\n",
+ "```{important}\n",
+ "In an aggregation query, the unaggregated expressions need to be consistent with the `GROUP BY` expressions. And all other expressions need to use aggregation functions\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e4010eaa-4240-4a85-bfe0-9dad81b29a74",
+ "metadata": {},
+ "source": [
+ "## Lap 3 🧃\n",
+ "### Join\n",
+ "\n",
+ "Syntax:\n",
+ "```sql\n",
+ "SELECT\n",
+ " columns\n",
+ "FROM\n",
+ " left_table\n",
+ "join_type\n",
+ " right_table\n",
+ "ON\n",
+ " join_condition\n",
+ ";\n",
+ "```\n",
+ "\n",
+ "Following are the types of joins\n",
+ "### Cross join\n",
+ "This is the simplest way of performing a join by cross joining 2 tables (like the cartesian product from your relational algebra classes), in our case, table `students` and `courses`. This kind of join returns all possible combinations of rows from `students` and `courses`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "3a8aa89a-bb69-46f2-af7e-a8f7d53171ac",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "135 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " student_no \n",
+ " stud_name \n",
+ " age \n",
+ " major \n",
+ " id \n",
+ " student_no_1 \n",
+ " course_name \n",
+ " course_year \n",
+ " course_percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(111, 'Catherine', 23, 'MBAN', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 3, 222, 'Health and Technology', 2020, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 3, 222, 'Health and Technology', 2020, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 3, 222, 'Health and Technology', 2020, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 3, 222, 'Health and Technology', 2020, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 3, 222, 'Health and Technology', 2020, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 3, 222, 'Health and Technology', 2020, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 3, 222, 'Health and Technology', 2020, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 3, 222, 'Health and Technology', 2020, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 3, 222, 'Health and Technology', 2020, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 4, 222, 'Web and Cloud Computing', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 4, 222, 'Web and Cloud Computing', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 4, 222, 'Web and Cloud Computing', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 4, 222, 'Web and Cloud Computing', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 4, 222, 'Web and Cloud Computing', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 4, 222, 'Web and Cloud Computing', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 4, 222, 'Web and Cloud Computing', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 4, 222, 'Web and Cloud Computing', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 4, 222, 'Web and Cloud Computing', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 5, 222, 'Spark Programming', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 5, 222, 'Spark Programming', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 5, 222, 'Spark Programming', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 5, 222, 'Spark Programming', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 5, 222, 'Spark Programming', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 5, 222, 'Spark Programming', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 5, 222, 'Spark Programming', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 5, 222, 'Spark Programming', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 5, 222, 'Spark Programming', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 6, 333, 'Parallel Computing', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 6, 333, 'Parallel Computing', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 6, 333, 'Parallel Computing', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 6, 333, 'Parallel Computing', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 6, 333, 'Parallel Computing', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 6, 333, 'Parallel Computing', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 6, 333, 'Parallel Computing', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 6, 333, 'Parallel Computing', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 6, 333, 'Parallel Computing', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 7, 444, 'Large Scale Infrastructures', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 7, 444, 'Large Scale Infrastructures', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 7, 444, 'Large Scale Infrastructures', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 7, 444, 'Large Scale Infrastructures', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 7, 444, 'Large Scale Infrastructures', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 7, 444, 'Large Scale Infrastructures', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 7, 444, 'Large Scale Infrastructures', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 7, 444, 'Large Scale Infrastructures', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 7, 444, 'Large Scale Infrastructures', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 8, 444, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 8, 444, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 8, 444, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 8, 444, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 8, 444, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 8, 444, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 8, 444, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 8, 444, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 8, 444, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 9, 555, 'TPCS INFO TECH', 2020, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 9, 555, 'TPCS INFO TECH', 2020, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 9, 555, 'TPCS INFO TECH', 2020, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 9, 555, 'TPCS INFO TECH', 2020, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 9, 555, 'TPCS INFO TECH', 2020, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 9, 555, 'TPCS INFO TECH', 2020, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 9, 555, 'TPCS INFO TECH', 2020, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 9, 555, 'TPCS INFO TECH', 2020, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 9, 555, 'TPCS INFO TECH', 2020, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 10, 555, 'Health and Technology', 2020, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 10, 555, 'Health and Technology', 2020, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 10, 555, 'Health and Technology', 2020, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 10, 555, 'Health and Technology', 2020, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 10, 555, 'Health and Technology', 2020, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 10, 555, 'Health and Technology', 2020, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 10, 555, 'Health and Technology', 2020, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 10, 555, 'Health and Technology', 2020, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 10, 555, 'Health and Technology', 2020, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 11, 666, 'Data Visualization', 2021, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 11, 666, 'Data Visualization', 2021, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 11, 666, 'Data Visualization', 2021, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 11, 666, 'Data Visualization', 2021, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 11, 666, 'Data Visualization', 2021, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 11, 666, 'Data Visualization', 2021, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 11, 666, 'Data Visualization', 2021, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 11, 666, 'Data Visualization', 2021, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 11, 666, 'Data Visualization', 2021, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 12, 777, 'Parallel Computing', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 12, 777, 'Parallel Computing', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 12, 777, 'Parallel Computing', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 12, 777, 'Parallel Computing', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 12, 777, 'Parallel Computing', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 12, 777, 'Parallel Computing', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 12, 777, 'Parallel Computing', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 12, 777, 'Parallel Computing', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 12, 777, 'Parallel Computing', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 13, 888, 'Spark Programming', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 13, 888, 'Spark Programming', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 13, 888, 'Spark Programming', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 13, 888, 'Spark Programming', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 13, 888, 'Spark Programming', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 13, 888, 'Spark Programming', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 13, 888, 'Spark Programming', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 13, 888, 'Spark Programming', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 13, 888, 'Spark Programming', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 15, 999, 'Data Visualization', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 15, 999, 'Data Visualization', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 15, 999, 'Data Visualization', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 15, 999, 'Data Visualization', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 15, 999, 'Data Visualization', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 15, 999, 'Data Visualization', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 15, 999, 'Data Visualization', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 15, 999, 'Data Visualization', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 15, 999, 'Data Visualization', 2019, 88.0)]"
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "\n",
+ "SELECT\n",
+ " *\n",
+ "FROM\n",
+ " students\n",
+ "CROSS JOIN\n",
+ " courses\n",
+ ";"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "57ec5f08-452f-4376-a71c-c1d27552a712",
+ "metadata": {},
+ "source": [
+ "```{note}\n",
+ "But in real life, we usually perform joins on a column, and we will discuss some types of joins on columns in the following sections. Since we are performing joins on a column, we need to pass that information using the `ON` keyword to give which columns are used to stitch the tables together\n",
+ "```\n",
+ "### Inner join\n",
+ "\n",
+ "Inner join only returns the matching rows from the left and right tables."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 65,
+ "id": "c4959539-2048-404b-a5ff-2d5cbf146f72",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "15 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " student_no \n",
+ " stud_name \n",
+ " age \n",
+ " course_name \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Web and Cloud Computing \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " Large Scale Infrastructures \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(111, 'Catherine', 23, 'TPCS INFO TECH'),\n",
+ " (111, 'Catherine', 23, 'Data Visualization'),\n",
+ " (222, 'Tiff', 28, 'Health and Technology'),\n",
+ " (222, 'Tiff', 28, 'Web and Cloud Computing'),\n",
+ " (222, 'Tiff', 28, 'Spark Programming'),\n",
+ " (333, 'John', 23, 'Parallel Computing'),\n",
+ " (444, 'Amir', 28, 'Large Scale Infrastructures'),\n",
+ " (444, 'Amir', 28, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'Health and Technology'),\n",
+ " (666, 'Isha', 30, 'Data Visualization'),\n",
+ " (666, 'Isha', 30, 'Parallel Computing'),\n",
+ " (888, 'Angela', 27, 'Spark Programming'),\n",
+ " (999, 'Jason', 30, 'TPCS INFO TECH'),\n",
+ " (999, 'Jason', 30, 'Data Visualization')]"
+ ]
+ },
+ "execution_count": 65,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "\n",
+ "SELECT s.student_no,s.stud_name,s.age,c.course_name\n",
+ "FROM\n",
+ " students AS s\n",
+ "INNER JOIN\n",
+ " courses AS c\n",
+ "ON\n",
+ " s.student_no = c.student_no;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7aff1260-a2c7-42f2-a92a-92ba317d64c8",
+ "metadata": {},
+ "source": [
+ "```{margin}\n",
+ "Check how we are using the alias `AS` we learned in the previous session.\n",
+ "```\n",
+ "\n",
+ "```{note}\n",
+ "In the returned table, student “Heidy” is missing as that student is not taking any courses and is not mentioned in the `courses`. Also, the courses \"C programming\" and \"Introduction to Genomics\" are missing since no students from our `student` table are taking these courses.\n",
+ "```\n",
+ "### Outer join \n",
+ "\n",
+ "An outer join returns all the rows from one or both of the tables that join. There are 3 variations of it. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6140faf0-24d5-49ed-937b-4fdc45447206",
+ "metadata": {},
+ "source": [
+ "#### Left outer\n",
+ "The first table that appears in the query is the left table, and the one appearing after the `LEFT OUTER JOIN` keyword is the right table.\n",
+ "\n",
+ "A left outer join returns all rows from the left table (matching or not), in addition to the matching rows from both tables. So the non-matching rows from the left table are assigned null values in the columns that belong to the right table.\n",
+ "\n",
+ "If you think about it from a Venn diagram perspective, it will look like...\n",
+ "\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 66,
+ "id": "c2b3c698-c279-47e5-9646-6857bd3b2ede",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "16 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " student_no \n",
+ " stud_name \n",
+ " age \n",
+ " course_name \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Web and Cloud Computing \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " Large Scale Infrastructures \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Heidy \n",
+ " 30 \n",
+ " None \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(111, 'Catherine', 23, 'TPCS INFO TECH'),\n",
+ " (111, 'Catherine', 23, 'Data Visualization'),\n",
+ " (222, 'Tiff', 28, 'Health and Technology'),\n",
+ " (222, 'Tiff', 28, 'Web and Cloud Computing'),\n",
+ " (222, 'Tiff', 28, 'Spark Programming'),\n",
+ " (333, 'John', 23, 'Parallel Computing'),\n",
+ " (444, 'Amir', 28, 'Large Scale Infrastructures'),\n",
+ " (444, 'Amir', 28, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'Health and Technology'),\n",
+ " (666, 'Isha', 30, 'Data Visualization'),\n",
+ " (666, 'Isha', 30, 'Parallel Computing'),\n",
+ " (888, 'Angela', 27, 'Spark Programming'),\n",
+ " (999, 'Jason', 30, 'TPCS INFO TECH'),\n",
+ " (999, 'Jason', 30, 'Data Visualization'),\n",
+ " (777, 'Heidy', 30, None)]"
+ ]
+ },
+ "execution_count": 66,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "\n",
+ "SELECT s.student_no,s.stud_name,s.age,c.course_name\n",
+ "FROM\n",
+ " students AS s\n",
+ "LEFT OUTER JOIN\n",
+ " courses AS c\n",
+ "ON\n",
+ " s.student_no = c.student_no;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "da7f9856-53c3-4f3c-b505-fd3cf39d5744",
+ "metadata": {},
+ "source": [
+ "#### Right outer \n",
+ "It behaves exactly in the same way as a left join, except that it keeps all rows from the right table and only the matching ones from the left table.\n",
+ "\n",
+ "If you think about it from a Venn diagram perspective, it will look like.\n",
+ "\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 67,
+ "id": "c715fc93-110c-4781-8fa6-632e403e1c89",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "17 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " student_no \n",
+ " stud_name \n",
+ " age \n",
+ " course_name \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Web and Cloud Computing \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " Large Scale Infrastructures \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " None \n",
+ " None \n",
+ " None \n",
+ " C programming \n",
+ " \n",
+ " \n",
+ " None \n",
+ " None \n",
+ " None \n",
+ " Introduction to Genomics \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(111, 'Catherine', 23, 'TPCS INFO TECH'),\n",
+ " (111, 'Catherine', 23, 'Data Visualization'),\n",
+ " (222, 'Tiff', 28, 'Health and Technology'),\n",
+ " (222, 'Tiff', 28, 'Web and Cloud Computing'),\n",
+ " (222, 'Tiff', 28, 'Spark Programming'),\n",
+ " (333, 'John', 23, 'Parallel Computing'),\n",
+ " (444, 'Amir', 28, 'Large Scale Infrastructures'),\n",
+ " (444, 'Amir', 28, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'Health and Technology'),\n",
+ " (666, 'Isha', 30, 'Data Visualization'),\n",
+ " (666, 'Isha', 30, 'Parallel Computing'),\n",
+ " (888, 'Angela', 27, 'Spark Programming'),\n",
+ " (999, 'Jason', 30, 'TPCS INFO TECH'),\n",
+ " (999, 'Jason', 30, 'Data Visualization'),\n",
+ " (None, None, None, 'C programming'),\n",
+ " (None, None, None, 'Introduction to Genomics')]"
+ ]
+ },
+ "execution_count": 67,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "\n",
+ "SELECT s.student_no,s.stud_name,s.age,c.course_name\n",
+ "FROM\n",
+ " students AS s\n",
+ "RIGHT OUTER JOIN\n",
+ " courses AS c\n",
+ "ON\n",
+ " s.student_no = c.student_no;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "474d5994-7832-4586-b2cc-327ef5385ce3",
+ "metadata": {},
+ "source": [
+ "#### Full outer\n",
+ "\n",
+ "left join + right join = full outer join.\n",
+ "\n",
+ "It retrieves matching and non-matching rows from both tables. \n",
+ "\n",
+ "If you think about it from a Venn diagram perspective, it will look like.\n",
+ "\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 68,
+ "id": "34a25ff1-6f68-43e7-8a00-67a213795a3c",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "18 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " student_no \n",
+ " stud_name \n",
+ " age \n",
+ " course_name \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Web and Cloud Computing \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " Large Scale Infrastructures \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " None \n",
+ " None \n",
+ " None \n",
+ " C programming \n",
+ " \n",
+ " \n",
+ " None \n",
+ " None \n",
+ " None \n",
+ " Introduction to Genomics \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Heidy \n",
+ " 30 \n",
+ " None \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(111, 'Catherine', 23, 'TPCS INFO TECH'),\n",
+ " (111, 'Catherine', 23, 'Data Visualization'),\n",
+ " (222, 'Tiff', 28, 'Health and Technology'),\n",
+ " (222, 'Tiff', 28, 'Web and Cloud Computing'),\n",
+ " (222, 'Tiff', 28, 'Spark Programming'),\n",
+ " (333, 'John', 23, 'Parallel Computing'),\n",
+ " (444, 'Amir', 28, 'Large Scale Infrastructures'),\n",
+ " (444, 'Amir', 28, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'Health and Technology'),\n",
+ " (666, 'Isha', 30, 'Data Visualization'),\n",
+ " (666, 'Isha', 30, 'Parallel Computing'),\n",
+ " (888, 'Angela', 27, 'Spark Programming'),\n",
+ " (999, 'Jason', 30, 'TPCS INFO TECH'),\n",
+ " (999, 'Jason', 30, 'Data Visualization'),\n",
+ " (None, None, None, 'C programming'),\n",
+ " (None, None, None, 'Introduction to Genomics'),\n",
+ " (777, 'Heidy', 30, None)]"
+ ]
+ },
+ "execution_count": 68,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "\n",
+ "SELECT s.student_no,s.stud_name,s.age,c.course_name\n",
+ "FROM\n",
+ " students AS s\n",
+ "FULL OUTER JOIN\n",
+ " courses AS c\n",
+ "ON\n",
+ " s.student_no = c.student_no;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1b9ac233-6f00-4276-8e4c-16e7cfbfbbe4",
+ "metadata": {},
+ "source": [
+ "### Summarize:\n",
+ "\n",
+ "***CARTESIAN JOIN:*** returns the Cartesian product of the sets of records from the two or more joined tables.\n",
+ "\n",
+ "***INNER JOIN:*** returns rows when there is a match in both tables.\n",
+ "\n",
+ "***LEFT JOIN:*** returns all rows from the left table, even if there are no matches in the right table.\n",
+ "\n",
+ "***RIGHT JOIN:*** returns all rows from the right table, even if there are no matches in the left table.\n",
+ "\n",
+ "***FULL JOIN:*** combines the results of both left and right outer joins."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "de8a2d6d-3a7a-4985-ac24-140316fbd60e",
+ "metadata": {},
+ "source": [
+ "We learned now about the joins. You know now how to join 2 tables. Once you joined 2 tables its sort of behave like another table that you apply the operations what we learned."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 87,
+ "id": "d33b019c-4093-4605-937b-a2f4b13ff778",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "3 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " stud_name \n",
+ " course_name \n",
+ " course_year \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Isha \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " \n",
+ " \n",
+ " Catherine \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " Jason \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[('Isha', 'Data Visualization', 2021),\n",
+ " ('Catherine', 'Data Visualization', 2019),\n",
+ " ('Jason', 'Data Visualization', 2019)]"
+ ]
+ },
+ "execution_count": 87,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "select s.stud_name,c.course_name, c.course_year\n",
+ "FROM students AS s\n",
+ "INNER JOIN\n",
+ "courses AS c\n",
+ "ON s.student_no = c.student_no\n",
+ "WHERE course_name = 'Data Visualization'\n",
+ "ORDER BY course_year DESC;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d9b1dc63-2d19-4c0e-8a35-2f6a57a59a51",
+ "metadata": {},
+ "source": [
+ "```{important}\n",
+ "We can have all the keywords we learned in a single SQL query, and we have come across some in previous examples. `BUT` the order of SQL keywords `DOES` matter: SELECT, FROM, JOIN, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT. \n",
+ "```\n",
+ "```{seealso}\n",
+ "We experienced performing joins on 2 tables, but joins can also be performed on multiple tables. Multi joins in SQL work by progressively creating derived tables one after the other. Here is the link that explains this [process](https://www.interfacett.com/blogs/multiple-joins-work-just-like-single-joins/)\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d04adf0d-6a40-45d8-922f-84855e75db71",
+ "metadata": {},
+ "source": [
+ "### Checkpoint 3 !! Take some time and think if you can...\n",
+ "- Understand different kinds of joins\n",
+ "- When to use joins\n",
+ "- Write SQL queries to join tables"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "731106d9-ba08-4692-bac2-8463d1518c87",
+ "metadata": {},
+ "source": [
+ "### Check point 3 exercise.\n",
+ "#### iclicker questions\n",
+ "\n",
+ "***Question 1:*** Spot the issue, if any, in this SQL query \n",
+ "\n",
+ "```sql\n",
+ "select s.stud_name,c.course_name, c.course_year\n",
+ "FROM students AS s\n",
+ "INNER JOIN\n",
+ "courses AS c\n",
+ "ON s.student_no = c.student_no\n",
+ "ORDER BY course_year DESC\n",
+ "WHERE course_name = 'Data Visualization';\n",
+ "```\n",
+ "\n",
+ "A: There are some issues with the join key\n",
+ "\n",
+ "B: No issues\n",
+ "\n",
+ "C: Can't specify alias when performing a join\n",
+ "\n",
+ "D: ORDER BY need to come after the WHERE clause\n",
+ "\n",
+ "```{toggle}\n",
+ "***Answer: D***\n",
+ "```\n",
+ "\n",
+ "***Question 2:*** \"In this Venn diagram green color indicate left outer join\" - is this statement TRUE/FALSE?\n",
+ "\n",
+ " \n",
+ "\n",
+ "A: TRUE\n",
+ "\n",
+ "B: FALSE\n",
+ "\n",
+ "```{toggle}\n",
+ "***Answer: FALSE***\n",
+ "\n",
+ "The following figure is what indicates the left outer join.\n",
+ "\n",
+ " \n",
+ "\n",
+ "But what is given here is a special scenario where we apply left join to be useful. For example, what if we want to find all students who are not taking any courses?\n",
+ "\n",
+ "\n",
+ "SELECT s.student_no,s.stud_name,s.age,c.course_name\n",
+ "FROM\n",
+ " students AS s\n",
+ "LEFT OUTER JOIN\n",
+ " courses AS c\n",
+ "ON\n",
+ " s.student_no = c.student_no\n",
+ "WHERE c.student_no is NULL\n",
+ "\n",
+ "\n",
+ "| student_no | stud_name | age | course_name |\n",
+ "|-----------:|----------:|----:|------------:|\n",
+ "| 777 | Heidy | 30 | None |\n",
+ "\n",
+ "Ahaa..! looks like \"heidy\" is not taking any courses; we need to check with her to see why she is not taking any courses :)\n",
+ "\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "12fa7b6e-fb81-41e9-8fa7-982fcee3dba8",
+ "metadata": {},
+ "source": [
+ "## 🏁 Finish line 🏁 🍺\n",
+ "\n",
+ "Are you able to recollect our 3 checkpoints?\n",
+ "\n",
+ "- ***Checkpoint 1:*** Take some time and think if you can...\n",
+ " - Select columns and bring in rows just what we needed (using SELECT & WHERE)\n",
+ " - Return DISTINCT elements\n",
+ " - ORDER BY rows returned based on column(s)\n",
+ " - LIMIT the number of rows returned\n",
+ "- ***Checkpoint 2:*** Take some time and think if you can...\n",
+ " - Do some aggregations and grouping queries using SQL.\n",
+ " - GROUP BY\n",
+ " - aggregation functions\n",
+ " - HAVING\n",
+ "- ***Checkpoint 3:*** Take some time and think if you can...\n",
+ " - Understand different kinds of joins\n",
+ " - When to use joins\n",
+ " - Write SQL queries to join tables"
+ ]
+ }
+ ],
+ "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.8.12"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/lectures/.ipynb_checkpoints/lecture3-checkpoint.ipynb b/lectures/.ipynb_checkpoints/lecture3-checkpoint.ipynb
new file mode 100644
index 0000000..467cfd7
--- /dev/null
+++ b/lectures/.ipynb_checkpoints/lecture3-checkpoint.ipynb
@@ -0,0 +1,705 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "a26f0deb",
+ "metadata": {},
+ "source": [
+ "# Lecture 2: Introduction to cloud computing and connection to cloud\n",
+ "Gittu George, January 6 2022"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "01b5fc58",
+ "metadata": {},
+ "source": [
+ "## Todays Agenda\n",
+ "\n",
+ "- Intro to cloud computing – WHAT ?, WHY ?, HOW?\n",
+ "- General overview on services available in AWS (Amazon web services)\n",
+ "- Various database services available in AWS\n",
+ "- Details on RDS - Relational Database Service\n",
+ "- Connecting to RDS (Postgres)\n",
+ "\n",
+ "## Learning objectives\n",
+ "- Have a general understanding of cloud computing and help you get started.\n",
+ "- You will know how to connect to a remote database from a Jupyter notebook.\n",
+ "- Different ways in interacting with the database from jupyter notebook.\n",
+ "- Showing different ways of loading the data.\n",
+ "- You will use queries to bring data into a Jupyter notebook and provide a simple data analysis.\n",
+ "\n",
+ "\n",
+ "\n",
+ "## Intro to cloud computing\n",
+ "\n",
+ "Let me tell you a short cloud-computing tale. This story starts from a computer that you all are familiar with. By the end of this tale, you will answer WHAT, WHY & HOW cloud computing.\n",
+ "\n",
+ "Here is the computer that I am talking about:\n",
+ " \n",
+ " \n",
+ " \n",
+ "OKAY! So now we all agreed to call monitor as the client and that box as server. Now let's take this knowledge to a bigger picture or think about how this idea will be when you start working in the industry. \n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ "\n",
+ "Collectively we call these servers data centers ( you can also hear people calling some other names like on-premise infrastructure). Mostly all companies (may be used to as there is this trend of moving to cloud) have data centers, which is considered a company powerhouse for powering data needs (like storage, processing, etc.)\n",
+ "\n",
+ "If you want to check out more on those gigantic data centers. Checkout [here](https://www.youtube.com/watch?v=g7JaN3rTK2A)\n",
+ "\n",
+ "Let me pause and take a minute to answer this question:\n",
+ "\n",
+ "```{sidebar} Question is here..\n",
+ "So assume that UBC has some server, and we are using this “client-server model” to do our analysis, then what do we not care about in the laptop requirements that MBAN mandates?\n",
+ "```\n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ "Now that you understand this client-server model, let's look inside these data centers to see which parties are involved.\n",
+ "\n",
+ "\n",
+ "```{margin}\n",
+ " \n",
+ "```\n",
+ " \n",
+ "\n",
+ "\n",
+ "List down in your notes about the labor costs:\n",
+ "\n",
+ "List down costs other than labor cost:\n",
+ "\n",
+ "With cloud computing, we are bringing down most of the costs you listed now. Look how neat and clean the diagram below is as cloud providers are taking care of most of the responsibilities that we discussed and using their infrastructure servers as services.\n",
+ "\n",
+ "```{margin}\n",
+ " \n",
+ "```\n",
+ " \n",
+ "\n",
+ "\n",
+ "I hope by now you can formulate an answer for questions WHAT, WHY, and HOW cloud computing. \n",
+ "\n",
+ "- ***WHAT?***\n",
+ "Cloud Computing is when we get a server in the cloud for our compute, storage, databases, and network services provided to users via the internet.\n",
+ "\n",
+ "- ***WHY?***\n",
+ "Save lots of money that otherwise need to spend for on-premise infrastructure, and I don't want to worry about infrastructure and can focus on your analysis right from day1.\n",
+ "\n",
+ "- ***HOW?***\n",
+ "Some cloud vendors provide infrastructure as a service by taking care of all the responsibilities that otherwise need to be done on-premise.\n",
+ "\n",
+ "## Benefits of cloud computing:\n",
+ "- Trade capital expense for variable expense\n",
+ "- Massive economies of scale\n",
+ "- Stop guessing capacity\n",
+ "- Increase speed and agility\n",
+ "- Stop spending money on running and maintaining data centers\n",
+ "- Go global in minutes\n",
+ "\n",
+ "Source: aws\n",
+ "\n",
+ "## Cloud providers\n",
+ "- Amazon Web Services (AWS)\n",
+ "- Microsoft Azure\n",
+ "- Google Cloud\n",
+ "\n",
+ "## Category of services available in AWS\n",
+ "- Compute\n",
+ " - EC2 – Elastic Cloud Compute\n",
+ "- Storage\n",
+ " - EBS - Elastic Block Storage\n",
+ " - S3 - Simple Storage Service\n",
+ "- Database\n",
+ "\n",
+ " \n",
+ "\n",
+ "Source: [dbtest](https://www.dbbest.com/technologies/nosql-databases/)\n",
+ "\n",
+ "### Database (Amazon RDS)\n",
+ "- Relational database service (RDS) provides a simple way to provision, create, and scale a relational database within AWS.\n",
+ "- Managed service – AWS takes responsibility for the administrative tasks\n",
+ "- Following database engines are supported\n",
+ " - Amazon Aurora\n",
+ " - PostgreSQL\n",
+ " - MySQL\n",
+ " - MariaDB\n",
+ " - Oracle Database\n",
+ " - SQL Server\n",
+ "\n",
+ "For a list of entire services and details, check out [here](https://d1.awsstatic.com/whitepapers/aws-overview.pdf)\n",
+ "\n",
+ "## Ways to interact with AWS\n",
+ "\n",
+ "- [Web interface](https://www.awsacademy.com/LMS_Login)\n",
+ "\n",
+ " A web-based GUI provides the capability to interact with the services within AWS.\n",
+ "\n",
+ "- [AWS CLI](https://aws.amazon.com/cli/)\n",
+ "\n",
+ "- [SDK](https://aws.amazon.com/sdk-for-python/)\n",
+ "\n",
+ "A good blog [here](https://adamraffe.com/2019/02/20/aws-fundamentals-part-3-interacting-with-aws/) explains various ways of interaction.\n",
+ "\n",
+ "## Demo\n",
+ "Lets together explore AWS\n",
+ "\n",
+ "```{figure} img/explore.png\n",
+ "---\n",
+ "width: 200px\n",
+ "align: center\n",
+ "---\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "81990cd8",
+ "metadata": {},
+ "source": [
+ "## Connecting To Cloud (RDS)\n",
+ "Consider your database what you built as an independent entity. Then, follow instructions to set up your database in the cloud.\n",
+ "\n",
+ "OKAY.. so now you have your database ready! In our case, we set up our Postgres database in RDS. Since you are not using an already existing database, you created it, so it's empty now. So we need to load data into this database. After loading data to the database, we are all set with querying from the database.\n",
+ "\n",
+ "Whether it's for loading or querying the database, we need to connect to the database. You all are aware of 2 other ways that you can use to interact with the database outside of this jupyter notebook.\n",
+ "\n",
+ "- pgadmin\n",
+ "- psql\n",
+ "\n",
+ "What information do you want to know if you're going to connect to the above? We need to know the information of the database that you wanted to connect to \n",
+ "- Hostname\n",
+ "- Port number\n",
+ "- User name (need to make sure that this user have access to the database)\n",
+ "- Password (Ofcourse !! you need to know the password for this user)\n",
+ "\n",
+ "If you want to know more details refer to the installation notes for pgadmin & psql\n",
+ "\n",
+ "Now let's think about other sources you want to connect to the database. Here are a few that I thought of\n",
+ "- From a programming language (java, R, python, etc..)\n",
+ "- From your jupyter notebook\n",
+ "- From tableau \n",
+ "- From excel \n",
+ "- From R Studio\n",
+ "- OR from any other application that you are using, e.g., your banking website\n",
+ "\n",
+ "Besides knowing the hostname, port name, username, and password, we also need to install an Application programming interface (API) that helps the applications on the client-side communicate with the server-side (in our case database). Java Database Connectivity (JDBC) and Open Database Connectivity (ODBC) are 2 of such APIs that are provided by the database vendor (Postgres, Oracle, MySQL, etc..) to access their database.\n",
+ "\n",
+ "JDBC is language-specific, and its the standard interface between any java application and database. JDBC converts the java programmer's requests to the database to something that the database understands (you can think of it as a translator). Meanwhile, ODBC is not language-specific, so it can convert requests from any application/software/programming language to something the database understands. Here is a diagram that will help you understand how ODBC works.\n",
+ "\n",
+ " \n",
+ "\n",
+ "```{sidebar} How ODBC connects ?\n",
+ "```{figure} img/odbc.png\n",
+ "---\n",
+ "width: 450px\n",
+ "align: center\n",
+ "---\n",
+ "```\n",
+ "In the rest of the section, let's focus on connecting to the database from python and the jupyter notebook. We will be using specific packages to make this possible, but as we have discussed, it uses ODBC for the connection. Many packages make this possible, but we will discuss a package named `psycopg2.`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "fd85b213",
+ "metadata": {},
+ "source": [
+ "## Using psycopg2 package\n",
+ "Let first install the package using [conda](https://anaconda.org/anaconda/psycopg2)\n",
+ "\n",
+ "```bash\n",
+ "%%sh\n",
+ "conda install -c anaconda psycopg2 \n",
+ "```\n",
+ "Let's make sure that we can import it."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0dffddf8",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "import psycopg2\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6e2ef767",
+ "metadata": {},
+ "source": [
+ "Now we have `psycopg2` imported into our notebook. `psycopg2` package manages the interaction between python and our database. So the first takeaway message here is that psycopg2 has nothing to do with jupyter notebook; rather, it's tied to python. So we use jupyter notebook as an interactive programming environment to query in python. Later if time permits, we will talk about `SQL magics,` which is a specific way of interacting with databases from jupyter notebook.\n",
+ "\n",
+ "Okay! Let's now get to the steps to connect to a database using psycopg2.\n",
+ "\n",
+ "- ***Create a connection*** \n",
+ "\n",
+ " This connection allows communication with the database; It opens up a network connection.\n",
+ "\n",
+ "- ***Create a cursor***\n",
+ "\n",
+ " The cursor is an address for the memory on the database management server to say this is what we are looking at. This python object helps you execute the query and fetch the results from the database. You can read more about cursors [here](https://medium.com/dev-bits/understanding-postgresql-cursors-with-python-ebc3da591fe7).\n",
+ "\n",
+ "- ***Formulate your query***\n",
+ "\n",
+ " Formulate the SQL query that you want to execute in the database.\n",
+ "\n",
+ "- ***Execute***\n",
+ "\n",
+ " Pass your query to execute() method of cursor object, run the query in the database.\n",
+ "\n",
+ "- ***Fetch/commit/rollback***\n",
+ "\n",
+ " The query that we performed in execute doesn't return the query right away. To return it, we need to perform a fetch. If the query is to write something, then we need to commit it. If some transaction went wrong, then we need to roll back it.\n",
+ "\n",
+ "Let's now check out these by creating a ticker table, loading data to it, and doing some querying. Let's first create schema import,\n",
+ "\n",
+ "```{note}\n",
+ "Below you need to replace the `conString` values with your `host`,`dbname`,`user`, `password`, and `port` you used while creating your RDS instance.\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "df63401d",
+ "metadata": {},
+ "source": [
+ "```bash\n",
+ "# Create a connection\n",
+ "conString = {'host':'mbandtweet.xxxxx.amazonaws.com',\n",
+ " 'dbname':'postgres',\n",
+ " 'user':'postgres',\n",
+ " 'password':'password',\n",
+ " 'port':5432}\n",
+ "conn = psycopg2.connect(**conString)\n",
+ "# Create a cursor\n",
+ "cur = conn.cursor()\n",
+ "# - Formulate your query\n",
+ "query = \"\"\"CREATE SCHEMA IF NOT EXISTS classwork\"\"\"\n",
+ "# - Execute\n",
+ "cur.execute(query)\n",
+ "# - commiting.\n",
+ "conn.commit()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0d2cf03c",
+ "metadata": {},
+ "source": [
+ "In the above code we created schema import. You probably already know about it, but if you want to read more on when & why use schema, check this [out](https://www.postgresql.org/docs/8.1/ddl-schemas.html). Let's create table,"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0e4f5123",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "## Here we create the table tickers\n",
+ "cur.execute(\"\"\"CREATE TABLE IF NOT EXISTS classwork.tickers(\n",
+ " actsymbol text PRIMARY KEY,\n",
+ " securityname text,\n",
+ " exchange text,\n",
+ " cqssymbol text,\n",
+ " etf text,\n",
+ " roundlotsize text,\n",
+ " testissue text,\n",
+ " nasdaqsymbol text)\"\"\")\n",
+ "conn.commit()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "75b203f0",
+ "metadata": {},
+ "source": [
+ "### Loading the data to database\n",
+ "Now we have our table ready, let's load data into this table. Here we read line by line using python and then insert it into the table using `execute.` This took me around ~5 min.\n",
+ "\n",
+ "```bash\n",
+ "from ftplib import FTP\n",
+ "from io import StringIO\n",
+ "import csv\n",
+ "\n",
+ "session = FTP('ftp.nasdaqtrader.com')\n",
+ "session.login()\n",
+ "r = StringIO()\n",
+ "session.retrlines('RETR /SymbolDirectory/otherlisted.txt', lambda x: r.write(x+'\\n'))\n",
+ "r.seek(0)\n",
+ "csvfile = list(csv.DictReader(r, delimiter='|'))\n",
+ "\n",
+ "## Here we are reading each row and then inserting it to the table one at a time \n",
+ "for row in csvfile:\n",
+ " cur.execute(\"INSERT INTO classwork.tickers VALUES (%s, %s, %s, %s, %s, %s, %s, %s)\", list(row.values()))\n",
+ "\n",
+ "conn.commit()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ad6f3434",
+ "metadata": {},
+ "source": [
+ "There are many other ways of loading the data to the table. Few to mention\n",
+ "\n",
+ "- [copy_from](https://naysan.ca/2020/06/21/pandas-to-postgresql-using-psycopg2-copy_from/)\n",
+ "- [COPY](https://www.postgresql.org/docs/13/sql-copy.html) command.\n",
+ "\n",
+ "When using these methods, make sure your CSV file is formatted correctly, or else you will be getting errors.\n",
+ "\n",
+ "If you want to read more on other ways to load data in Postgres, check out [this](https://www.highgo.ca/2020/12/08/bulk-loading-into-postgresql-options-and-comparison/) article."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c077aa8d",
+ "metadata": {},
+ "source": [
+ "### Reading data from database\n",
+ "As discussed before, an execute doesn't return the data right away, and we need to perform a fetch. There are mainly 3 flavors of fetch \n",
+ "- fetchone\n",
+ "- fetchmany\n",
+ "- fetchall"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7b2b1440",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "query = \"\"\"SELECT * FROM classwork.tickers\"\"\"\n",
+ "cur.execute(query)\n",
+ "row = cur.fetchone()\n",
+ "print(row)\n",
+ "````"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2dd3cff8",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "query = \"\"\"SELECT * FROM classwork.tickers\"\"\"\n",
+ "cur.execute(query)\n",
+ "row = cur.fetchmany(5)\n",
+ "print(row)\n",
+ "````"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "256bf3f2",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "query = \"\"\"SELECT * FROM classwork.tickers LIMIT 5\"\"\"\n",
+ "cur.execute(query)\n",
+ "row = cur.fetchall()\n",
+ "print(row)\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "76b7369e",
+ "metadata": {},
+ "source": [
+ "Sometimes you might not need everything that is returned from a database. For example, you might want to transform each row returned from the database. In these cases fetching each row at a time will be of help."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "71c48282",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "query = \"\"\"SELECT * FROM classwork.tickers LIMIT 5\"\"\"\n",
+ "cur.execute(query)\n",
+ "for rows in cur:\n",
+ " print(rows[1])\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2b16904a",
+ "metadata": {},
+ "source": [
+ "But in general most of the cases, you can go with fetchall, provided you write an efficient SQL query to execute and get just the columns and rows that you are interested in. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ba83dae9",
+ "metadata": {},
+ "source": [
+ "Before we move to the next topic, let me show you how rollback works. Say, for instance, your query ends up failing for some reason."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5bb59c12",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "query = \"\"\"SELECT * FROM classwrk.tickers LIMIT 5\"\"\"\n",
+ "cur.execute(query)\n",
+ "row = cur.fetchall()\n",
+ "print(row)\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2e6dc43e",
+ "metadata": {},
+ "source": [
+ "You realized it and corrected it."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c96c333b",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "query = \"\"\"SELECT * FROM classwork.tickers LIMIT 5\"\"\"\n",
+ "cur.execute(query)\n",
+ "row = cur.fetchall()\n",
+ "print(row)\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0bc47450",
+ "metadata": {},
+ "source": [
+ "But this query, even though it's correct it won't end up going through and will get this error saying `your current transaction is aborted`\n",
+ "\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "277ced49",
+ "metadata": {},
+ "source": [
+ "You need to do a `rollback()` to back to the previous stable state and then execute your query."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "43def46e",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "conn.rollback()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4c99b1ad",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "query = \"\"\"SELECT * FROM classwork.tickers LIMIT 5\"\"\"\n",
+ "cur.execute(query)\n",
+ "row = cur.fetchall()\n",
+ "print(row)\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8bd8217e",
+ "metadata": {},
+ "source": [
+ "```{important}\n",
+ "Whenever you get an error, as we showed before, `InFailedSqlTransaction: current transaction is aborted, commands ignored until end of transaction block\n",
+ "`, make sure you do a `rollback`.\n",
+ "```\n",
+ "\n",
+ "```{tip}\n",
+ "It's not a bad idea to develop your SQL query in pgadmin, toad or any other GUI interface and then bring it in once you know it's ready.\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "cfa7dbb5",
+ "metadata": {},
+ "source": [
+ "## Dealing with passwords\n",
+ "```{margin}\n",
+ " \n",
+ "```\n",
+ "Say if you want to share this notebook with your colleague, or if somebody came by and looked at your notebook, they can see your database connection details, including your password. It's probably okay now as it's a toy database, but when you start working in the industry, you will be dealing with a database ( or, in other words, business), and it can cause lots of problems if the password is compromised. So now on, let's practice not doing the connection call as I did.\n",
+ "\n",
+ "We will make use of a python package dotenv. Install python package"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f3cac0fa",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "%%sh\n",
+ "conda install -c anaconda python-dotenv\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ee2ecf93",
+ "metadata": {},
+ "source": [
+ "```{seealso}\n",
+ "https://anaconda.org/conda-forge/python-dotenv\n",
+ "```\n",
+ "\n",
+ "This package looks for a .env file in the folder where your notebook resides, and it loads your database connection details to the environment variable for your session. Here is how my env file looks like\n",
+ "\n",
+ "```bash\n",
+ "(base) ggeorg02@MacBook-Pro assign1 % cat .env \n",
+ "DB_HOST=mbandtweet.xxxxx.us-west-2.rds.amazonaws.com\n",
+ "DB_NAME=postgres\n",
+ "DB_USER=postgres\n",
+ "DB_PASS=password\n",
+ "DB_PORT=5432\n",
+ "```\n",
+ "You have to change this to your connection details\n",
+ "To create a .env file, you might want to use your terminal. Check how to use [vi editor](https://www.howtogeek.com/102468/a-beginners-guide-to-editing-text-files-with-vi/)\n",
+ "\n",
+ "```bash\n",
+ "cd \n",
+ "vi .env\n",
+ "```\n",
+ "\n",
+ "```{note}\n",
+ "I purposely created this file with just an extension, not a filename. And you can usually make this kind of file only from the terminal, so I am using vi editor. You might not be able to find this file from your file explorer, and that's the reason we created it starting with a period. This means that `.iamasecretfile`. So no wonder why you can't see, right? This way, no one can accidentally look at this file when using your computer. if you want to see it, then again, you want to go to terminal and type `ls -a`\n",
+ "```\n",
+ "\n",
+ "After this, you can call all variables like this. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d2cc04c8",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "import os\n",
+ "import psycopg2\n",
+ "\n",
+ "##Make sure you import and load your .env file\n",
+ "from dotenv import load_dotenv\n",
+ "load_dotenv()\n",
+ "\n",
+ "conString = {'host':os.environ.get('DB_HOST'),\n",
+ " 'dbname':os.environ.get('DB_NAME'),\n",
+ " 'user':os.environ.get('DB_USER'),\n",
+ " 'password':os.environ.get('DB_PASS'),\n",
+ " 'port':os.environ.get('DB_PORT')}\n",
+ "print(conString[\"port\"])\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e0b20dee",
+ "metadata": {},
+ "source": [
+ "This way, you don't have to worry about exposing your password to anyone.\n",
+ "\n",
+ "```{caution}\n",
+ "Nowhere in assignments/projects should you include the hostname or password. If anything needs to be checked, then TA's will contact you.\n",
+ "```\n",
+ "\n",
+ "```{important}\n",
+ "Make sure you close the cursor and connection before quitting your jupyter notebook. This is a good practice and can protect you from memory leakage and too many open connections.\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "23a2f724",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "cur.close()\n",
+ "conn.close()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c6435b54",
+ "metadata": {},
+ "source": [
+ "## Working with dumps\n",
+ "Taking dumps comes in handy when you want to share a copy of your database with someone or if you're going to keep it as a backup. \n",
+ "\n",
+ "- How can I take a dump of the database that I already have. \n",
+ "```bash\n",
+ "pg_dump -h mbandtweet.xxxxx.ca-central-1.rds.amazonaws.com --username=postgres -n import -f import.sql\n",
+ "```\n",
+ "Read more about how to take database dumps [here](https://www.postgresql.org/docs/12/app-pgdump.html).\n",
+ "\n",
+ "- How can you load these dumps to make your schema and tables ready?\n",
+ "\n",
+ "psql -h HOST_NAME -U USER_NAME DATABASE < PATH_TO_THE_.sql_DUMP_FILE\n",
+ "\n",
+ "```bash\n",
+ "psql -h mbandtweet.xxxx.us-west-2.rds.amazonaws.com -U postgres postgres < import.sql\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ad51007f",
+ "metadata": {},
+ "source": [
+ "```{warning}\n",
+ "If you are not using your RDS instance, make sure you shut it down from your AWS console. You can always restart it when working on your assignments/projects. However, your credits are limited, and if those get exhausted, we won't provide you with more.\n",
+ "```\n",
+ "\n",
+ "```{note}\n",
+ "Closing your lab doesn't shut down the RDS instance; you need to shut it down from your AWS console explicitly.\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.8.12"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/lectures/.ipynb_checkpoints/lecture4-checkpoint.ipynb b/lectures/.ipynb_checkpoints/lecture4-checkpoint.ipynb
new file mode 100644
index 0000000..9a9e6b1
--- /dev/null
+++ b/lectures/.ipynb_checkpoints/lecture4-checkpoint.ipynb
@@ -0,0 +1,300 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "16d159b3",
+ "metadata": {},
+ "source": [
+ "# Lecture 3: Data Modeling for Business Applications\n",
+ "Gittu George, January 11 2022"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "60f5c082",
+ "metadata": {},
+ "source": [
+ "## Todays Agenda\n",
+ "\n",
+ "- Data normalization\n",
+ "- Refresher to Database concepts\n",
+ "- Group Presentation\n",
+ "\n",
+ "## Learning objectives\n",
+ "- WHAT & WHY databases? (ACID properties)\n",
+ "- WHY breaking the table is essential? \n",
+ "- Have an understanding of various normal forms in a database.\n",
+ "\n",
+ "## Introducing to some problems\n",
+ "\n",
+ "We will start with a CSV file or an excel spreadsheet. This is the most common way of storing data in various organizations. Often people organize it messy. Checkout this CSV file\n",
+ "\n",
+ " \n",
+ "\n",
+ "This looks okay now as it's just 3 rows, but think about how it will look if we are going to store entire students in a university. Here what are the problems that come to your mind? Here are a few of them\n",
+ "\n",
+ "```{margin}\n",
+ " \n",
+ "```\n",
+ "\n",
+ "- ***Does the order of courses in a course column matter?***\n",
+ "\n",
+ " E.g., For Amir Khan, the order for course number is 525,516. Does this mean that the order in which he took classes? How many courses are a student supposed to take? We see Catherine, Amir, Gittu taking 3, 2, and 1 class, respectively. Is there a minimum number of courses that a student needs to take? Is there any maximum limit?\n",
+ "\n",
+ "- ***Too many duplicates ?***\n",
+ "\n",
+ "- ***What happens if I am loading incoming students to the database ?*** \n",
+ "\n",
+ " They won't be taking any classes until they start, which can result in lots of null values.\n",
+ "\n",
+ "- ***Human errors can happen.***\n",
+ "\n",
+ " Assume what happens if I accidentally entered 525a as the course number? \n",
+ "\n",
+ "- ***Deletion anomaly***\n",
+ "\n",
+ " Deleting a student can end up deleting course information.\n",
+ " E.g., deleting Catherine can take out the course information for course numbers 580, 518. \n",
+ "\n",
+ "- ***Update anomaly***\n",
+ "\n",
+ " We plan to change the course name for 525 from \"Web and cloud computing\" to \"Cloud computing for Big Data\"! How many places need to be updated?\n",
+ "\n",
+ "- ***Insertion anomaly***\n",
+ "\n",
+ " We plan to launch a new course named 999 named \"Big Data Systems,\" and no students are currently enrolled in it. So how are we going to store this information? \n",
+ "\n",
+ "Loading this data into a database table can bring in problems mentioned above, resulting in serious data integrity issues, and hence we should do data normalization.\n",
+ "\n",
+ "## Normalization\n",
+ "\n",
+ "Normalization is the process of minimizing redundancy from a table of relations. These redundancies in relation may cause insertion, deletion, and update anomalies. Normal forms are a certain set of defined rules to \"break a table smartly\" to create normalized tables.\n",
+ "\n",
+ "Storytime:\n",
+ "\n",
+ "Ted Codd introduced the concept of normalization and first normal form in his [paper](https://www.seas.upenn.edu/~zives/03f/cis550/codd.pdf) in 1970, which provides theoretical foundation of designing a database. After a year, he defined 2nd and 3rd normal form in [this](https://forum.thethirdmanifesto.com/wp-content/uploads/asgarosforum/987737/00-efc-further-normalization.pdf) paper. Later in 1974, Codd and Raymond F. Boyce defined the Boyce–Codd normal form (BCNF). These 4 normal forms are considered the most common or important ones; Most database systems are designed to follow the 3rd normal form or BCNF. \n",
+ "\n",
+ "Later came some more restrictive normal forms (4NF,5NF,6NF), but these are mainly used for theoretical purposes and are less implemented in industries.\n",
+ "\n",
+ "Note: We explain the normal forms in an easier-to-understand fashion, and they can also be explained using theoretical formulas and derivations.\n",
+ "\n",
+ "### First normal form (1NF)\n",
+ "\n",
+ "To be in 1NF,\n",
+ "- All types must be atomic.❌\n",
+ "- No repeating groups ✅\n",
+ "\n",
+ " \n",
+ "\n",
+ "- All types must be atomic.✅\n",
+ "- No repeating groups ❌\n",
+ "\n",
+ " \n",
+ "\n",
+ "- All types must be atomic. ✅\n",
+ "- No repeating groups✅\n",
+ "\n",
+ " \n",
+ "\n",
+ "### Second normal form (2NF)\n",
+ "\n",
+ "To be in 2NF\n",
+ "- Must be in first normal form ✅\n",
+ "- Any non-key attributes fully depend on the candidate key ❌\n",
+ "\n",
+ " \n",
+ "\n",
+ "- Must be in first normal form ✅\n",
+ "- Any non-key attributes fully depend on the candidate key ✅\n",
+ "\n",
+ " \n",
+ "\n",
+ "### Third normal form\n",
+ "\n",
+ "To be in 3NF\n",
+ "- Must be in second normal form ✅\n",
+ "- There should be no transitive dependency ❌\n",
+ "\n",
+ " \n",
+ "\n",
+ "\n",
+ "- Must be in second normal form ✅\n",
+ "- There should be no transitive dependency ✅\n",
+ "\n",
+ " \n",
+ "\n",
+ "Now we have finished normalizing the table, let's create this table's in our database."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9f65d6b4",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "import psycopg2\n",
+ "# Create a connection\n",
+ "conString = {'host':'mbandtweet.xxxx.rds.amazonaws.com',\n",
+ " 'dbname':'postgres',\n",
+ " 'user':'postgres',\n",
+ " 'password':'xxxx',\n",
+ " 'port':5432}\n",
+ "conn = psycopg2.connect(**conString)\n",
+ "# Create a cursor\n",
+ "cur = conn.cursor()\n",
+ "createTables = \"\"\"\n",
+ "CREATE TABLE IF NOT EXISTS classwork.students(student_no integer PRIMARY KEY, stud_fname text, \n",
+ "stud_lname text, major text);\n",
+ "\n",
+ "CREATE TABLE IF NOT EXISTS classwork.courses(course_no integer PRIMARY KEY, course_name text);\n",
+ "\n",
+ "CREATE TABLE IF NOT EXISTS classwork.grades( grade text PRIMARY KEY, grade_type text);\n",
+ "\n",
+ "CREATE TABLE IF NOT EXISTS classwork.student_course_grade( \n",
+ "student_no integer REFERENCES classwork.students(student_no), \n",
+ "course_no integer REFERENCES classwork.courses(course_no), \n",
+ "grade text REFERENCES classwork.grades(grade), \n",
+ "PRIMARY KEY(student_no, course_no));\"\"\"\n",
+ "\n",
+ "cur.execute(createTables)\n",
+ "conn.commit()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f36057b2",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "insertinto = \"\"\"\n",
+ "INSERT INTO classwork.students VALUES (111,'Catherine','John','MBAN');\n",
+ "INSERT INTO classwork.students VALUES (222,'Amir','Khan','MDS');\n",
+ "INSERT INTO classwork.students VALUES (333,'Gittu','George','MBAN');\n",
+ "INSERT INTO classwork.courses VALUES (580,'TPCS INFO TECH');\n",
+ "INSERT INTO classwork.courses VALUES (518,'Data Visualization');\n",
+ "INSERT INTO classwork.courses VALUES (516,'Health and Technology');\n",
+ "INSERT INTO classwork.courses VALUES (525,'Web and Cloud Computing');\n",
+ "\n",
+ "INSERT INTO classwork.grades VALUES ('A','Excellent');\n",
+ "INSERT INTO classwork.grades VALUES ('B','Good');\n",
+ "INSERT INTO classwork.grades VALUES ('C','Average');\n",
+ "\n",
+ "INSERT INTO classwork.student_course_grade VALUES(111,580,'A');\n",
+ "INSERT INTO classwork.student_course_grade VALUES(111,518,'A');\n",
+ "INSERT INTO classwork.student_course_grade VALUES(111,516,'A');\n",
+ "INSERT INTO classwork.student_course_grade VALUES(222,525,'A');\n",
+ "INSERT INTO classwork.student_course_grade VALUES(222,516,'B');\n",
+ "INSERT INTO classwork.student_course_grade VALUES(333,525,'C');\n",
+ "\"\"\"\n",
+ "cur.execute(insertinto)\n",
+ "conn.commit()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2013a284",
+ "metadata": {},
+ "source": [
+ "Demo exploring it from pgadmin vizualizer.\n",
+ "\n",
+ "```{margin}\n",
+ " \n",
+ "```\n",
+ "\n",
+ " \n",
+ "\n",
+ "Revisit the problems listed at the beginning to see if we have resolved those by performing this normalization.\n",
+ "\n",
+ "Entity Relation Models are about Entities. So we can put these tables together using the primary keys and foreign keys in getting what we want."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e25b86d7",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "query = \"\"\"SELECT stu.stud_fname, stu.stud_lname,g.grade,c.course_name FROM schema.students AS stu\n",
+ "INNER JOIN schema.student_course_grade AS g ON g.student_no = stu.student_no\n",
+ "INNER JOIN schema.courses AS c ON c.course_no = g.course_no\n",
+ "WHERE g.grade='A' AND c.course_name='Health and Technology';\n",
+ "\"\"\"\n",
+ "cur.execute(insertinto)\n",
+ "row = cur.fetchone()\n",
+ "print(row)\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d3cef99c",
+ "metadata": {},
+ "source": [
+ "## Key Properties of a DBMS\n",
+ "\n",
+ "Often we refer to ACID or ACID Compliant databases. With modern No-SQL databases, we also hear about BASE databases. However, ACID was one of the fundamental properties of most early databases and is the reason for their broad adoption. It is an acronym that means:\n",
+ "\n",
+ "- ***A***tomic\n",
+ "\n",
+ "The unit of operation within an ACID database is the transaction. A transaction may involve one command or multiple commands. When multiple commands modify data, if one of them fails, the transaction is not committed (i.e., none of the operations are performed).\n",
+ "\n",
+ "E.g., We have a database with one table (Values) and two columns (a and b). We have an operation where, on a particular row, a value is subtracted from a and added to b. This might be similar to a banking transaction, where I withdraw money from my account to pay for a new pair of slippers. The operation takes place in two steps:\n",
+ "\n",
+ "- Remove value from a\n",
+ "- Place value in b\n",
+ "\n",
+ "Atomicity ensures that if a system failure occurs between steps one and two, or if either step one or step two is invalid for any other reason, the entire transaction will not be processed.\n",
+ "\n",
+ "\n",
+ "- ***C***onsistent\n",
+ "\n",
+ "All data within the database follow the defined rules for the database, the defined rules for anyone table, and the defined rules for any one field. All operations on the database will return the same value, given the same set of underlying data each time. \n",
+ "\n",
+ "E.g., We have table marks that give the breakdown of marking schemes for various courses. It is divided into quiz, participation, midterm, and final columns. All fields must be integers, and because these values make up the course marking scheme, each row must sum to 100. We can add a constraint to the table that ensures this using the CHECK operator.\n",
+ "\n",
+ "A database that ensures consistency would reject any transaction where the final result results in a marking sum <> 100.\n",
+ "\n",
+ "\n",
+ "- ***I***solation\n",
+ "\n",
+ "Any transaction occurs in isolation. One transaction cannot affect another transaction. When a transaction acts on the data, the tables or rows on which it acts are effectively locked to other transactions.\n",
+ "\n",
+ "If two transactions modify the same table, the sequence of operations will ensure that the first transaction completes before the second transaction can change the data.\n",
+ "\n",
+ " E.g., In that same marks table as before, let's assume that a department has decided that all final exam scores will be reduced by 5% and that that value will be shifted to participation. At the same time, an individual instructor in the program has decided to increase the final weighting and reduce the midterm weighting.\n",
+ "\n",
+ "If these operations are not isolated, the program-level transaction may reduce the weighting of the final exam. The instructor-level transaction\n",
+ "\n",
+ "\n",
+ "- ***D***urability\n",
+ "\n",
+ "The results of any transaction that completes successfully are permanently stored within the database.\n",
+ "\n",
+ "E.g., Data that goes in following a completed transaction doesn't come out unless another transaction operates to remove the data."
+ ]
+ }
+ ],
+ "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.8.12"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/lectures/.ipynb_checkpoints/lecture5-checkpoint.ipynb b/lectures/.ipynb_checkpoints/lecture5-checkpoint.ipynb
new file mode 100644
index 0000000..1fc1885
--- /dev/null
+++ b/lectures/.ipynb_checkpoints/lecture5-checkpoint.ipynb
@@ -0,0 +1,640 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "f0fce9da",
+ "metadata": {},
+ "source": [
+ "# Lecture 4: SQL for vizualization\n",
+ "Gittu George, January 13 2022"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "320aa509",
+ "metadata": {},
+ "source": [
+ "## Todays Agenda\n",
+ "\n",
+ "- Vizualization science\n",
+ "- Steps on vizualization \n",
+ "- SQL tricks for data prep\n",
+ "- Views\n",
+ "- Data Cleaning science\n",
+ "\n",
+ "```{margin}\n",
+ " \n",
+ "```\n",
+ "```{margin}\n",
+ " \n",
+ "```\n",
+ "\n",
+ "## Learning objectives\n",
+ "- You will understand what kinds of graphs are best applied to various “questions.”\n",
+ "- You will understand how to construct SQL queries to pre-process data for efficient plotting\n",
+ "- You will be able to work from question, to figure, to SQL query\n",
+ "- Why Data Cleaning Matters\n",
+ "- How to develop a data cleaning plan as part of a workflow\n",
+ "- How to establish and understand data standards for various data types\n",
+ "- How to encode data cleaning standards into a database structure\n",
+ "\n",
+ "\n",
+ "## Introduction to visualization science\n",
+ "You will be doing many data visualization in this course for your assignments and projects. When you think about it, what are the first things that come to your mind?\n",
+ "\n",
+ "- There are so many different graphs out there; which one should I use?\n",
+ "- There are so many different graphing tools out there; which one should I use?\n",
+ "- Should my plot be static or dynamic?\n",
+ "\n",
+ "Answers to these questions solely rely on the purpose of a graph ?? !!\n",
+ "\n",
+ "```{sidebar} “Graphics reveal data.” - Edward Tufte\n",
+ "\n",
+ "Edward Tufte is a data scientist who did a lot of groundbreaking work in the field of visualization. His [book from 1983](https://www.edwardtufte.com/tufte/books_vdqi), is still referred and sited by many recent(eg [this](https://medium.com/nightingale/improve-your-visualization-skills-using-tuftes-principles-of-graphical-design-3a0f40a53a2c) & [this](https://towardsdatascience.com/little-known-ways-to-make-your-data-visualization-awesome-890d71b6e365)) articles. From his book, we will be discussing some of the general principles that you need to keep in mind.\n",
+ "\n",
+ "```\n",
+ "\n",
+ "In general, graphs should;\n",
+ "- Make large datasets coherent\n",
+ "- Serve a reasonably clear purpose\n",
+ "- Be closely integrated with descriptions of the dataset\n",
+ "- Induce the viewer to think about substance\n",
+ "\n",
+ "\n",
+ "### General Principles for vizualization \n",
+ "\n",
+ "- Show the data (fairly)\n",
+ "\n",
+ " Ensure that the data represented does not exaggerate or obscure true patterns.\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ "\n",
+ "- Maximize data-ink ratio\n",
+ "\n",
+ " Elements in your figure should represent data, rather than adornment (grid lines &cetera)\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ "\n",
+ "- Erase non-data ink\n",
+ "\n",
+ " As much as possible, reduce clutter around the data itself.\n",
+ "\n",
+ " \n",
+ "\n",
+ " \n",
+ "\n",
+ "- Erase redundant data-ink\n",
+ "\n",
+ " Remove (or deprioritize) data that doesn’t support the statements\n",
+ "\n",
+ " \n",
+ "\n",
+ " \n",
+ "\n",
+ "- Edit your figures as you would your text\n",
+ "\n",
+ " Ask, is this legible? Does this represent what we need it to show?\n",
+ " - InkScape\n",
+ " - CorelDRAW\n",
+ " - AI\n",
+ "\n",
+ "## Steps on vizualization \n",
+ "By now, we understand the science of visualization, but you have to keep in mind that graphical display is more than graphs. Here are how the steps will look like, and here I will be showing some SQL tricks and techniques that you can use.\n",
+ "\n",
+ "### Data Preparation\n",
+ "- What are we trying to plot?\n",
+ " - More variables or facets within your data mean more visual variables.\n",
+ " - Make clear choices about categorical vs. continuous variables.\n",
+ " \n",
+ " \n",
+ " \n",
+ " Source : [Bertin. 1967. Semiology of Graphics.](https://www.historyofinformation.com/detail.php?id=3361)\n",
+ " \n",
+ "***We will be discussing later some of the SQL tricks.***\n",
+ "\n",
+ "- Are we plotting the data only once or to be redrawn frequently? Is the data preparation complex?\n",
+ " - We can manage longer processing times if things are plotted only once.\n",
+ " - A complex query only needs to be run once.\n",
+ " If data is updated often, or the plot must be regenerated often, we need other solutions.\n",
+ "\n",
+ "***And that's why we want to know about views which we will discuss in detail later.*** \n",
+ " \n",
+ "### Reproducible Workflows\n",
+ "- Considerations In Plotting Decisions\n",
+ " - How much data are you trying to plot?\n",
+ " - How many “series”?\n",
+ "- Volume of Data\n",
+ " - Static presentation\n",
+ " - Dynamic presentation\n",
+ "\n",
+ "```{important}\n",
+ "Simplify your data first. \n",
+ "- Can we aggregate by type, count?\n",
+ "\n",
+ " use GROUP BY\n",
+ "\n",
+ "- Do we need to prepare data ahead of time?\n",
+ "\n",
+ " That's WHY we need to create MATERIALIZED VIEW. \n",
+ " \n",
+ "- Is the data likely to change?\n",
+ "\n",
+ " That's WHY we need to create VIEW. \n",
+ "```\n",
+ "### Visual Representation\n",
+ "\n",
+ "What are we Representing? Here are the main categories\n",
+ "\n",
+ "- Numeric data\n",
+ " - Univariate;\n",
+ " Histogram, density plot: Count of observations.\n",
+ " - Multivariate, n = 2;\n",
+ " Scatter plots (x vs. y), bar plots (count of y at value x), boxplots (distribution of y at x)\n",
+ " - Multivariate, n = 2; one variable ordered\n",
+ " line graph (ordered x vs. y), connected scatterplot (ordered x vs. y)\n",
+ " As n increases, we can use other graphic elements (hue, symbol shape & size) to represent other dimensions of the data\n",
+ "\n",
+ "- Categorical Data\n",
+ " - Univariate (keyword list &cetera)\n",
+ " Barplot (count of terms), word cloud (ugh); pie or donut chart (ugh)\n",
+ " - Univariate, structured\n",
+ " Dendrogram (hierarchy of relationships)\n",
+ " - Categorical with Numeric\n",
+ " Variations on numeric, using an extra facet\n",
+ "\n",
+ "An amazing reference: [data-to-viz](https://www.data-to-viz.com/)\n",
+ "- Represent data SIMPLY\n",
+ "- More graphs, less clutter.\n",
+ "- Less text on the screen (significant figures!!!!)\n",
+ "- Highlight the key elements (use the alpha channel!)\n",
+ "\n",
+ " \n",
+ "\n",
+ "## SQL tricks\n",
+ "\n",
+ "These SQL tricks help with the data preparation for generating the plots.\n",
+ "\n",
+ "- Group continuous variable using [CASE](https://www.postgresql.org/docs/8.4/functions-conditional.html):\n",
+ "\n",
+ "```\n",
+ "SELECT varone,\n",
+ "\tCASE WHEN vartwo < 5 THEN ‘Small’\n",
+ " WHEN vartwo < 10 THEN ‘Medium’\n",
+ " WHEN vartwo > 10 THEN ‘Big’\n",
+ " END,\n",
+ " varthree\n",
+ "FROM table; \n",
+ "```\n",
+ "\n",
+ " \n",
+ "\n",
+ "- Dealing with missing values using [COALESCE](https://www.postgresql.org/docs/8.1/functions-conditional.html)\n",
+ "\n",
+ "```\n",
+ "SELECT varone, \n",
+ "COALESCE(vartwo, 0)\n",
+ "FROM table;\n",
+ "```\n",
+ "\n",
+ "COALESCE is used to deal with potential NULL values. It saves you from dealing with it in Python & lets you clearly define how you deal with missing values.\n",
+ "\n",
+ " \n",
+ "\n",
+ "- Changing plotting order:\n",
+ "\n",
+ "```\n",
+ "SELECT varone,\n",
+ " vartwo,\n",
+ " varthree\n",
+ "FROM table\n",
+ "ORDER BY varone;\n",
+ "```\n",
+ "\n",
+ " \n",
+ "\n",
+ "- Changing plotting order with random:\n",
+ "\n",
+ "Using random() allows us to sort by a random value that is recalculated each time.\n",
+ "\n",
+ "```\n",
+ "Changing order:\n",
+ "SELECT varone,\n",
+ " vartwo,\n",
+ " varthree\n",
+ "FROM table\n",
+ "ORDER BY random();\n",
+ "```\n",
+ "\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d1ed3dec-6b78-434e-a317-338860ac4095",
+ "metadata": {},
+ "source": [
+ "## Views\n",
+ "\n",
+ "Whether we want to use VIEWS depends on the plotting or querying frequency. For example, complex queries with changing data that will be run multiple times (or have varying “WHERE” statements) can use a VIEW.\n",
+ "\n",
+ "Check out how much time this query is taking to run. Before all that lets initiate the connection to the database."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "bc60f875-dd47-4e1a-9d7f-9da998474cc2",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "import os\n",
+ "import psycopg2\n",
+ "\n",
+ "##Make sure you import and load your .env file\n",
+ "from dotenv import load_dotenv\n",
+ "load_dotenv()\n",
+ "\n",
+ "conString = {'host':os.environ.get('DB_HOST'),\n",
+ " 'dbname':os.environ.get('DB_NAME'),\n",
+ " 'user':os.environ.get('DB_USER'),\n",
+ " 'password':os.environ.get('DB_PASS'),\n",
+ " 'port':os.environ.get('DB_PORT')}\n",
+ "print(conString[\"port\"])\n",
+ "conn = psycopg2.connect(**conString)\n",
+ "cur = conn.cursor()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "dd0c1d2b-a42d-4c13-8459-0672f891912d",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "%%time\n",
+ "query=\"\"\"SELECT tw.id, \n",
+ " unnest(regexp_matches(tw.text, '\\$[A-Z]+\\M', 'g')) AS substring\n",
+ "FROM import.tweets AS tw;\"\"\"\n",
+ "cur.execute(query)\n",
+ "cur.fetchmany(5)\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5415237e-a9da-45f4-b792-ba36c5f3d34a",
+ "metadata": {},
+ "source": [
+ "Let's create a view "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2a53161e-0968-4e28-9294-4b7d3d6ffd89",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "%%time\n",
+ "query=\"\"\"CREATE OR REPLACE VIEW import.tickertweets AS\n",
+ "SELECT tw.id, unnest(regexp_matches(tw.text, '\\$[A-Z]+\\M', 'g')) AS substring\n",
+ "FROM import.tweets AS tw;\"\"\"\n",
+ "cur.execute(query)\n",
+ "conn.commit()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1b3fedad-acaf-4927-8507-c4ef71dbe520",
+ "metadata": {},
+ "source": [
+ "Let's see the query time when we call it from the VIEW."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2a999556-cf91-4f1d-b34c-a5b9a9348a2a",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "%%time\n",
+ "query=\"\"\"SELECT * FROM import.tickertweets;\"\"\"\n",
+ "cur.execute(query)\n",
+ "cur.fetchmany(5)\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0365c2ac-30d9-4064-8e44-1aa30746ea9e",
+ "metadata": {},
+ "source": [
+ "So, a TABLE is stored data. A VIEW is a stored query. A VIEW’s query is stored to be cached (saved to memory) for faster retrieval and optimized for speed.\n",
+ "\n",
+ "Complex queries with fixed data that will be run multiple times (or have varying “WHERE” statement) can use a MATERIALIZED VIEW:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "cec2fd3d-3e1a-4cee-a6fe-d0749f7c7147",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "%%time\n",
+ "query=\"\"\"DROP MATERIALIZED VIEW import.tickertweetsmat CASCADE;\"\"\"\n",
+ "cur.execute(query)\n",
+ "conn.commit()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a1de88d3-c4b0-4ea7-8180-92f70fca918d",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "%%time\n",
+ "query=\"\"\"CREATE MATERIALIZED VIEW import.tickertweetsmat AS\n",
+ "SELECT tw.id, \n",
+ " unnest(regexp_matches(tw.text, '\\$[A-Z]+\\M', 'g')) AS substring\n",
+ "FROM import.tweets AS tw;\"\"\"\n",
+ "cur.execute(query)\n",
+ "conn.commit()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e5f5462a-bdc5-4055-8490-a3b8dd8e7e2a",
+ "metadata": {},
+ "source": [
+ "Let's see the query time when we call it from the VIEW."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5b2b3912-66db-4694-b249-66cc183bdc9a",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "%%time\n",
+ "query=\"\"\"SELECT * FROM import.tickertweetsmat;\"\"\"\n",
+ "cur.execute(query)\n",
+ "cur.fetchmany(5)\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "70a490e8-7455-4147-883b-3ba3d328f544",
+ "metadata": {},
+ "source": [
+ "- A TABLE is stored data. \n",
+ "- A VIEW is a stored query. \n",
+ "- A MATERIALIZED VIEW is a query committed to stored data.\n",
+ "\n",
+ "When to use what?\n",
+ "- Choose a table when the underlying data don’t change (except for INSERTs)\n",
+ "- Choose a MATERIALIZED VIEW when the query is slow, and you can tolerate some lag between UPDATE/INSERTs and updated values.\n",
+ "- Choose a VIEW when you reuse the query and want to simplify calling it.\n",
+ "\n",
+ "Check out these yourself?\n",
+ "\n",
+ "- How much time does it take to create a view vs. materialized view?\n",
+ "- How much time does it take to query? You can use these queries to find that out.\n",
+ "- How much space does it take? You can use these queries to find that out."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "22253f4b-b282-4464-8f88-682dba27f2b7",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "%%time\n",
+ "query=\"\"\"SELECT pg_size_pretty (pg_relation_size('import.tickertweets'));\"\"\"\n",
+ "cur.execute(query)\n",
+ "print(cur.fetchone())\n",
+ "query=\"\"\"SELECT pg_size_pretty (pg_relation_size('import.tickertweetsmat'));\"\"\"\n",
+ "cur.execute(query)\n",
+ "print(cur.fetchone())\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "67f1ef59",
+ "metadata": {},
+ "source": [
+ "### Summary\n",
+ "\n",
+ "- Views can be an important tool in managing long-running queries for visualization (and for other purposes)\n",
+ "- Tradeoffs exist with respect to storage space and the rate at which data needs to be refreshed.\n",
+ "\n",
+ "| Table | View | Materialized View | SELECT Query |\n",
+ "|------------------------------------|------------------|-------------------|------------------|\n",
+ "| High Disk Space | Low Disk Space | High Disk Space | Low Disk Space |\n",
+ "| Fast Read Time | Slower Read Time | Fast Read Time | Slow Read Time |\n",
+ "| Fixed Values (cannot be refreshed) | Can be refreshed | Can be refreshed | Always refreshed |"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c5df4ba4",
+ "metadata": {},
+ "source": [
+ "```{margin}\n",
+ " \n",
+ "```\n",
+ "## Data Cleaning\n",
+ "\n",
+ "Before we get to visualization or the table, we need to think about data cleaning. This is considered very important yet tedious, where analysts spend most of their time.\n",
+ "\n",
+ "### Why Does Clean Data Matter?\n",
+ "\n",
+ "Dirty data impacts every part of business decision-making. Outliers, data gaps, misspellings, poorly structured data, incorrectly entered, invalid values, duplication.\n",
+ "\n",
+ "```{margin}\n",
+ "“At the operational level, poor data leads directly to customer dissatisfaction, increased cost, and lowered employee job satisfaction. . . 8 - 12% of revenue may be consumed by bad data.”\n",
+ "[TC Redman](https://dl.acm.org/doi/pdf/10.1145/269012.269025)\n",
+ "```\n",
+ "\n",
+ "- Garbage in - Garbage out\n",
+ "\n",
+ "If you put dirty data in, then what you will be getting, as a result, will be dirty or of no use, or even worse, it can mislead you.\n",
+ "\n",
+ "- Time is money\n",
+ "\n",
+ "Suppose your data issues get addressed at the point when the data is initially consumed. In that case, there is a meager cost associated with it, but once data gets dispersed throughout the company with different project groups, it becomes harder to address as you want to bring this change across all departments. Then once you've started to make strategic decisions based on this data, it becomes very expensive to correct the data as you also want to deal with the consequences of bad strategy decisions.\n",
+ "\n",
+ "```{margin}\n",
+ "50% — the amount of time that knowledge workers waste in hidden data factories, hunting for data, finding and correcting errors, and searching for confirmatory sources for data they don’t trust.”\n",
+ "[Redman (2016) HBR](https://hbr.org/2016/09/bad-data-costs-the-u-s-3-trillion-per-year)\n",
+ "```\n",
+ "\n",
+ "\n",
+ "### Impacts with the dirty data \n",
+ "\n",
+ "```{margin}\n",
+ "NASA's Mars Climate Orbiter was designed to study Mars from orbit and serve as a communications relay for the Mars Polar Lander and Deep Space probes. Unfortunately, the mission was unsuccessful due to a navigation error caused by a failure to translate English units to metric.\n",
+ "[NASA](https://solarsystem.nasa.gov/missions/mars-climate-orbiter/in-depth/)\n",
+ "```\n",
+ "\n",
+ "It can be broadly categorized into\n",
+ "\n",
+ "- Operational \n",
+ "\n",
+ " - Customer service impacts (mismatched addresses)\n",
+ " - More costly to analyze data and produce insights\n",
+ "\n",
+ "- Strategic\n",
+ "\n",
+ " - More difficult to set & execute a broad strategy\n",
+ " - More challenging to align needs in large organizations\n",
+ " \n",
+ "\n",
+ "\n",
+ "### Where Does Dirty Data Come From & how to approach?\n",
+ "\n",
+ "It can come from anywhere. So don't just think that it had to do with the external data; sometimes, internal data that has been provided to you might be dirtier.\n",
+ "\n",
+ " \n",
+ "\n",
+ "```{note}\n",
+ "Data Screening deals with finding errors (generally at data input or analysis)\n",
+ "Data Cleaning fixes errors, reformats data following data acquisition.\n",
+ "```\n",
+ "Data Cleaning means having a plan\n",
+ "- Any analysis you do will use some combination of internal and external data\n",
+ "- You can expect that “finding”, “screening,” and “cleaning” data will take close to a majority of your time\n",
+ "- You can expect that to costs of poor data management and cleaning can be significant in time and value\n",
+ "- Beginning with a Data Cleaning Plan is perhaps the most critical stage of analysis.\n",
+ "\n",
+ "### Elements of a Data Cleaning Plan\n",
+ "\n",
+ "#### Dataset Provenance Metadata\n",
+ "Assume you are a consumer and producer\n",
+ "\n",
+ "```{margin}\n",
+ "Without provenance, consumers have no inherent way to trust the integrity and credibility of shared data. Data publishers, in turn, need to be aware of the needs of prospective consumer communities to know how much provenance detail is appropriate. \n",
+ "[W3C Data On The Web Best Practices](https://www.w3.org/TR/dwbp/#provenance)\n",
+ "```\n",
+ "\n",
+ "For Each Dataset (dataset level metadata):\n",
+ "- Title\n",
+ "- URI (source location\n",
+ "- Keywords (if needed)\n",
+ "- Publication Date\n",
+ "- Publisher\n",
+ "- Creator\n",
+ "- Contact Point\n",
+ "- Spatial Coverage\n",
+ "- Temporal Coverage\n",
+ "- Language\n",
+ "- Date & Time Formats\n",
+ "- Data Version\n",
+ "- Access Date\n",
+ "\n",
+ "It can be formatted as plain text, YAML, JSON, or other\n",
+ "W3C Data on the Web: [link](https://www.w3.org/TR/dwbp) \n",
+ "\n",
+ "#### Data Quality Documentation (pre-cleaning)\n",
+ "\n",
+ "- Completeness per row\n",
+ "\n",
+ "Do we have all the data we need in each table?\n",
+ "\n",
+ "- Syntactical Correctness\n",
+ "\n",
+ "Are all data in the expected & correct format?\n",
+ "\n",
+ "- Absence of Contradictions\n",
+ "\n",
+ "All data are in alignment with one another & with expectations.\n",
+ "\n",
+ "- Accuracy\n",
+ "\n",
+ "All data is up to date and accurate.\n",
+ "\n",
+ "- Absence of repetition\n",
+ "\n",
+ "Synonyms & overlapping values addressed; all entities unique\n",
+ "Overlapping values & synonyms addressed; each entity is unique\n",
+ "\n",
+ "- Referential Integrity\n",
+ "\n",
+ "Required references and entities are complete.\n",
+ "\n",
+ "- Completeness\n",
+ "\n",
+ "All cross-sum products are complete (sum of product counts == total inventory count)\n",
+ "\n",
+ "- Normative consistency\n",
+ "\n",
+ "The naming and meaning of data is the same overall systems\n",
+ "\n",
+ "#### Data Cleaning Documentation\n",
+ "- All steps (and the order of steps) should be preserved and documented.\n",
+ "- All steps should be reproducible (within reason)\n",
+ "- Reproducibility either directly (by executing a script directly) or indirectly (explaining the steps in a program)\n",
+ "\n",
+ "\n",
+ "#### Data Quality Documentation (post-cleaning)\n",
+ "#### Quality Assessment / Error Reporting Procedures\n",
+ "- Clear documentation of quality assessment metrics (as above)\n",
+ "- Clear documentation of how to report/address data issues.\n",
+ "\n",
+ "### Data Standards for Databases\n",
+ "\n",
+ "- [schema.org](https://schema.org)\n",
+ "- [Microsoft Common Data Standard](https://github.com/microsoft/CDM)\n",
+ "- [datastandards.directory](https://datastandards.directory/)\n",
+ "- [W3 standards for dates](https://www.w3.org/TR/NOTE-datetime), [Blog post](https://www.iso.org/news/2017/02/Ref2164.html) about the most recent ISO 8601 standard update\n",
+ "\n",
+ " - April 2, 1974\n",
+ " - 04-02-74\n",
+ " - 04/02/1974\n",
+ " - 4/2/74\n",
+ " - 19740402\n",
+ " - 04021974 - is this April 2 or February 4?\n",
+ " - 2 April 1974\n",
+ "\n",
+ "- [W3 standards for names](https://www.w3.org/International/questions/qa-personal-names)\n",
+ " - Don't think people have one name\n",
+ " - Don't think people’s names do not change\n",
+ " - Don't think people’s names have canonical order (First, Last)\n",
+ "\n",
+ "- Provide clear meaning for data elements\n",
+ " - Either separately or within the database (COMMENT ON ...)\n",
+ "```\n",
+ "COMMENT ON TABLE mytable IS 'This table is the table for my data.';\n",
+ "COMMENT ON COLUMN my_table.my_column IS 'Employee ID number';\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.8.12"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/lectures/.ipynb_checkpoints/lecture6-checkpoint.ipynb b/lectures/.ipynb_checkpoints/lecture6-checkpoint.ipynb
new file mode 100644
index 0000000..1db3159
--- /dev/null
+++ b/lectures/.ipynb_checkpoints/lecture6-checkpoint.ipynb
@@ -0,0 +1,676 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "2966d98b",
+ "metadata": {},
+ "source": [
+ "# Lecture 5: Disk Management and Indexes\n",
+ "Gittu George, January 18 2022"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c894801c",
+ "metadata": {},
+ "source": [
+ "## Todays Agenda\n",
+ "\n",
+ "- How do humans store and search data?\n",
+ "- How does Postgres store data? \n",
+ "- How does Postgres search data?\n",
+ "- WHY indexes?\n",
+ "- WHAT are indexes?\n",
+ "- Different kinds of indexes\n",
+ "- Do I need an index?\n",
+ "\n",
+ "## Learning objectives\n",
+ "- You will apply knowledge of disk management and query structure to optimize complex queries.\n",
+ "- You will understand the different types of INDEXes available in Postgres and the best-suited applications\n",
+ "- You will be able to profile and optimize various queries to provide the fastest response.\n",
+ "\n",
+ "I will use the `testindex` table from `fakedata` schema for demonstration. You can load this data to your database using this [dump](https://canvas.ubc.ca/files/18838846/download?download_frd=1). It's a zip file; do make sure you extract it.\n",
+ "\n",
+ "\n",
+ "## How do humans store and search data?\n",
+ "Think about how we used to store data before the era of computers? We used to store the data in books. Let's take this children's encyclopedia as an example.\n",
+ "\n",
+ " \n",
+ "\n",
+ ".\n",
+ "\n",
+ "There are around 300 `pages` in this book, and if a kid wants to know about `China`, they will go from the first `page` until she gets to 48th `page`.\n",
+ "\n",
+ "This is how a kid would `search`, but if you want to read about china, you would go to the `index` of this book, look for `China`, and go to the 48th page straight away. If you want to read some history about indexes, check out [this](http://www.elephant.org.il/indexing/a-history-of-index-creation) article.\n",
+ "\n",
+ "OKAY, now you all know how humans search for data from a book; Postgres also store and search data similarly.\n",
+ "\n",
+ "## How does Postgres store data? \n",
+ "Information within a database is also stored in pages. A database page is not structured like in a book, but it's structured like below.\n",
+ "\n",
+ " \n",
+ "\n",
+ ".\n",
+ "\n",
+ "So these pages are of 8 kB blocks; there are some meta-data as headers (green) at the front of it and item ID (yellow) that points to the tuples(rows). This ends with a special section (blue) left empty in ordinary tables, but if we have some indexes set on the tables, it will have some information to facilitate the index access methods.\n",
+ "\n",
+ "So a table within a database will have a list of pages where the data is stored, and these individual pages store multiple tuples within that table.\n",
+ "\n",
+ "Read more about it [here.](https://www.postgresql.org/docs/current/storage-page-layout.html)\n",
+ "\n",
+ "## How does Postgres search data?\n",
+ "\n",
+ "Let's consider a complicated table.\n",
+ "\n",
+ " \n",
+ "\n",
+ "For explanation, let's assume that the contents in this table are stored in 3 page files. Here we are interested in finding the rows with the unique name that starts with `y`. A database, just like a kid, needs to search through the 3 pages to find the rows that contain a name that begins with `y`. This is a sequential search, and this is how a database search by default. This process could be slow if we got millions of rows spread across multiple pages, as the computer needs to go through the entire pages to find the names that start with y. \n",
+ "\n",
+ "Also, here in this example, let's assume that Yokamino is on page 2, the database will find this on page 2, but it will still go to page 3 in search of other occurrences of names that start with y. WHY? Because it doesn't know that there is only a single occurrence of a name that starts with y. \n",
+ "\n",
+ "Let's look at how Postgres search for data using `EXPLAIN`. We will do this on table `fakedata.testindex`,"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b856196b-e6b2-46fa-a7bd-ed49fe36015e",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "import os\n",
+ "import psycopg2\n",
+ "import psycopg2.extras\n",
+ "import random\n",
+ "from dotenv import load_dotenv\n",
+ "import time\n",
+ "import datetime\n",
+ "\n",
+ "load_dotenv()\n",
+ "\n",
+ "conString = {'host':os.environ.get('DB_HOST'),\n",
+ " 'dbname':os.environ.get('DB_NAME'),\n",
+ " 'user':os.environ.get('DB_USER'),\n",
+ " 'password':os.environ.get('DB_PASS'),\n",
+ " 'port':os.environ.get('DB_PORT')}\n",
+ "\n",
+ "conn = psycopg2.connect(**conString)\n",
+ "cur = conn.cursor()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d7181c1d-227b-4dba-9431-112e7de22819",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "## i am dropping previous index \n",
+ "createindex = \"\"\"DROP INDEX if exists fakedata.hash_testindex_index;\n",
+ "DROP INDEX if exists fakedata.pgweb_idx;\n",
+ "DROP INDEX if exists default_testindex_index;\"\"\"\n",
+ "cur.execute(createindex)\n",
+ "conn.commit()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "04734f9f",
+ "metadata": {},
+ "source": [
+ "Here, we do a normal search for a product name within the column `productname`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2790abd3-93e7-42d8-a4ca-eed7c906db22",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "query = \"\"\"explain analyze select count(*) from fakedata.testindex where productname = 'flavor halibut';\"\"\"\n",
+ "cur.execute(query)\n",
+ "cur.fetchall()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4c3ca5b9",
+ "metadata": {},
+ "source": [
+ "We are doing a pattern matching search to return all the `productname` that start with `fla`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7d40b26f-c513-44ae-9c15-e3b8a806b4ca",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "query = \"\"\"EXPLAIN ANALYZE SELECT COUNT(*) FROM fakedata.testindex WHERE productname LIKE 'fla%';\"\"\"\n",
+ "cur.execute(query)\n",
+ "cur.fetchall()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "65710707",
+ "metadata": {},
+ "source": [
+ "```{note}\n",
+ "A query is turned into an internal execution plan breaking the query into specific elements that are re-ordered and optimized. Read more about EXPLAIN [here](https://www.postgresql.org/docs/9.5/using-explain.html). [This](https://thoughtbot.com/blog/reading-an-explain-analyze-query-plan) blog also explains it well.\n",
+ "```\n",
+ "\n",
+ "These are computers, so it's faster than humans to go through all the pages, but it's inefficient. Won't it be cool to tell the database that the data you are looking for is only on certain pages, so you don't want to go through all these pages !!! This is WHY we want indexes. We will see if indexing can speed up the previous query.\n",
+ "\n",
+ "## WHAT are indexes?\n",
+ "\n",
+ "Indexes make a map of each of these rows effectively as where they are put into the page files so we can do a sequential scan of an index to find the pointer to page/pages this information is stored. So the database can go straight to those pages and skip the rest of the pages.\n",
+ "\n",
+ " \n",
+ "\n",
+ "Index gets applied to a column in a table. As we were querying on the column `productname`, let's now apply an index to the column `productname` in the database and see how the query execution plan changes. Will it improve the speed? Let's see.."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "95738d29-112a-4d79-89ce-88735f6e37bd",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "%%time\n",
+ "createindex = \"\"\"CREATE INDEX if not exists default_testindex_index ON fakedata.testindex (productname varchar_pattern_ops);\"\"\"\n",
+ "cur.execute(createindex)\n",
+ "conn.commit()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1f753f0b-097b-408f-8c0e-31b7fe9459ed",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "query = \"\"\"explain analyze select count(*) from fakedata.testindex where productname = 'flavor halibut';\"\"\"\n",
+ "cur.execute(query)\n",
+ "cur.fetchall()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a8cb67fc-6021-4eef-ace2-70db1a175329",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "query = \"\"\"EXPLAIN ANALYZE \n",
+ "SELECT COUNT(*) FROM fakedata.testindex \n",
+ "WHERE productname LIKE 'fla%';\"\"\"\n",
+ "cur.execute(query)\n",
+ "cur.fetchall()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1046e917",
+ "metadata": {},
+ "source": [
+ "It increased the speed, and you see that your query planner used the index to speed up queries.\n",
+ "\n",
+ "But keep in mind if the selection criteria are too broad, or the INDEX is too imprecise, the query planner will skip it.\n",
+ "\n",
+ " \n",
+ "\n",
+ "Let's try that out."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3b49488e-55f8-4296-a937-7537d78b514e",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "query = \"\"\"EXPLAIN ANALYZE \n",
+ "SELECT COUNT(*) FROM fakedata.testindex \n",
+ "WHERE productname LIKE '%';\"\"\"\n",
+ "cur.execute(query)\n",
+ "cur.fetchall()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "db99aeba",
+ "metadata": {},
+ "source": [
+ "It's good that database engines are smart enough to identify if it's better to look at an index or perform a sequential scan. Here in this example, the database engine understands the query is too broad. But, ultimately, it's going to search through entire rows, so it's better to perform a sequential scan rather than look up at the index. \n",
+ "\n",
+ "As the index is making a map in the disk, it takes up disk storage. Let's see how much space this index is taking up"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "acf85a93-ef98-4bc9-bdb0-603ddc0582eb",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "query = \"\"\"SELECT pg_size_pretty (pg_indexes_size('fakedata.testindex'));\"\"\"\n",
+ "cur.execute(query)\n",
+ "cur.fetchall()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a1402bd9",
+ "metadata": {},
+ "source": [
+ "Now we realize indexes are great! So let's try some different queries."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7eb04468-854c-4782-a898-ee0ad52e0f55",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "query = \"\"\"EXPLAIN ANALYZE \n",
+ "SELECT COUNT(*) FROM fakedata.testindex \n",
+ "WHERE productname LIKE '%fla%';\"\"\"\n",
+ "cur.execute(query)\n",
+ "cur.fetchall()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9890adea",
+ "metadata": {},
+ "source": [
+ "This was not too broad search; we were trying to return all the elements that contains `fla`. But why didn't it speed things up? So we can't always go with default indexing, and it might not be helpful in all cases. That's why it's important to know about different indexes and a general understanding of how it works. This will help you to choose indexes properly in various situations.\n",
+ "\n",
+ "## Different kinds of indexes\n",
+ "\n",
+ "In this section, we will go through various kinds of indexes and syntax for creating them;\n",
+ "\n",
+ "- B-Tree (binary search tree - btree)\n",
+ "- Hash\n",
+ "- GIST (Generalized Search Tree)\n",
+ "- GIN (Generalized Inverted Tree)\n",
+ "- BRIN (Block Range Index)\n",
+ "\n",
+ "Each has its own set of operations, tied to Postgres functions/operators. For example, you can read about indexes [here](https://www.postgresql.org/docs/current/indexes-types.html). \n",
+ "\n",
+ "The general syntax for making an index:\n",
+ "```\n",
+ "CREATE INDEX indexname ON schema.tablename USING method (columnname opclass);\n",
+ "```\n",
+ "\n",
+ "B-Tree is the default index, and you can see we used this previously for column `productname`.\n",
+ "```\n",
+ "CREATE INDEX if not exists default_testindex_index ON fakedata.testindex (productname varchar_pattern_ops);\n",
+ "```\n",
+ "\n",
+ "### B-Tree\n",
+ "- Values are balanced across nodes to produce a minimum-spanning tree.\n",
+ "- Each node splits values, each “leaf” points to addresses.\n",
+ "- All nodes are equidistant from the root.\n",
+ "- High Key\n",
+ "\n",
+ " \n",
+ "\n",
+ "Btree is not just about dealing with numbers but also handling text columns; that's the reason why I gave `varchar_pattern_ops` as `opclass`. So in this example, finds the field that we are looking for in just 3 steps, rather than scanning 7 rows.\n",
+ "\n",
+ " \n",
+ "\n",
+ "### Hash\n",
+ "- Cell values are hashed (encoded) form and mapped to address “buckets”\n",
+ "- Hash -> bucket mappings -> disk address\n",
+ "- The hash function tries to balance the number of buckets & number of addresses within a bucket\n",
+ "- Hash only supports EQUALITY\n",
+ "\n",
+ " \n",
+ "\n",
+ "Let's create a hash index on column `productname` and execute queries that we ran before"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3e56dbef-650b-4091-924b-ad69fcbc3baa",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "%%time\n",
+ "## i am dropping previous index \n",
+ "createindex = \"\"\"DROP INDEX fakedata.default_testindex_index;\n",
+ "CREATE INDEX if not exists hash_testindex_index ON fakedata.testindex USING hash(productname);\"\"\"\n",
+ "cur.execute(createindex)\n",
+ "conn.commit()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "63fe2824-f758-491e-9ca1-62c0db195b85",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "query = \"\"\"explain analyze select count(*) from fakedata.testindex where productname = 'flavor halibut';\"\"\"\n",
+ "cur.execute(query)\n",
+ "cur.fetchall()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1d610947-208c-4444-928f-bc2103b7f34f",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "query = \"\"\"EXPLAIN ANALYZE \n",
+ "SELECT COUNT(*) FROM fakedata.testindex \n",
+ "WHERE productname LIKE 'fla%';\"\"\"\n",
+ "cur.execute(query)\n",
+ "cur.fetchall()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "40c9faa9-6ad0-49f2-a1fa-0041adb235e4",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "query = \"\"\"SELECT pg_size_pretty (pg_indexes_size('fakedata.testindex'));\"\"\"\n",
+ "cur.execute(query)\n",
+ "cur.fetchall()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b97d6cfb",
+ "metadata": {},
+ "source": [
+ "- Why do you think hash takes up more space here?\n",
+ "- When do you think it's best to you hash indexing?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6b5d6fc9",
+ "metadata": {},
+ "source": [
+ "### GIN\n",
+ "\n",
+ "Before talking about GIN indexes, let me give you a little bit of background on a full-text search. Full-text search is usually used if you have a column with sentences and you need to query rows based on the match for a particular string in that sentence. For example, say we want to get rows with `new` in the `sentence` column.\n",
+ "\n",
+ "| row number | sentence|\n",
+ "|-------------|------------------------------------------------|\n",
+ "| 1 | this column has to do some thing with new year, also has to do something with row |\n",
+ "| 2 | this column has to do some thing with colors |\n",
+ "| 3 | new year celebrated on 1st Jan |\n",
+ "| 4 | new year celebration was very great this year |\n",
+ "| 5 | This is about cars released this year |\n",
+ "\n",
+ "\n",
+ "We usually query it this way, this definitely will return our result, but it takes time.\n",
+ "```\n",
+ "SELECT * FROM fakedata.testindex \n",
+ "WHERE productname LIKE '%new%';\n",
+ "```\n",
+ "That's why we go for full-text search; I found [this](https://arctype.com/blog/postgres-full-text-search/) blog to help understand this.\n",
+ "```\n",
+ "SELECT * FROM fakedata.testindex \n",
+ "WHERE to_tsvector('english', sentence) @@ to_tsquery('english','new');';\n",
+ "```\n",
+ "Here we are converting the type of column `sentence` to `tsvector`\n",
+ "Here the first row in sentence column `this column has to do something with the new year, also has to do something with row` will be represented like this internally \n",
+ "```\n",
+ "'also':11 'column':2 'do':5,14 'has':3,12 'new':9 'row':17 'some':6 'something':15 'thing':7 'this':1 'to':4,13 'with':8,16 'year':10\n",
+ "```\n",
+ "`to_tsquery` is how you query, and here we query for `new` using `to_tsquery('english','new')`.\n",
+ "\n",
+ "Postgres does a pretty good job with the full-text search, but if we want to speed up the search, we go for GIN indexes. The column needs to be of `tsvector` type for a full-text search.\n",
+ "\n",
+ "- Indexing many values to the same row\n",
+ "- Inverse of B-tree - one value to many rows\n",
+ "e.g., “quick”, or “brown” or “the” all point to row 1\n",
+ "- Most commonly used for `full-text searching.`\n",
+ "\n",
+ " \n",
+ "\n",
+ "Let's try these on our tables;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "93f768ad-7429-45a3-870f-d7bce5779a23",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "query = \"\"\"EXPLAIN ANALYZE SELECT count(*) FROM fakedata.testindex WHERE to_tsvector('english', productname) @@ to_tsquery('english','flavor');\"\"\"\n",
+ "cur.execute(query)\n",
+ "cur.fetchall()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "553abd53-37e2-4c26-9250-fbd1bd3b8a9d",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "%%time\n",
+ "createindex = \"\"\"DROP INDEX if exists fakedata.hash_testindex_index;\n",
+ "DROP INDEX if exists fakedata.pgweb_idx;\n",
+ "CREATE INDEX if not exists pgweb_idx ON fakedata.testindex USING GIN (to_tsvector('english', productname));\n",
+ "CREATE INDEX if not exists pgweb_idx_mat ON fakedata.testindex_materialized USING GIN (productname_ts);\"\"\"\n",
+ "cur.execute(createindex)\n",
+ "conn.commit()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "27927604-29ea-4fda-917c-3686a5a81b52",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "query = \"\"\"EXPLAIN ANALYZE SELECT count(*) FROM fakedata.testindex WHERE to_tsvector('english', productname) @@ to_tsquery('english','flavor');\"\"\"\n",
+ "cur.execute(query)\n",
+ "cur.fetchall()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3d76c08e",
+ "metadata": {},
+ "source": [
+ "By creating a materialized view with a computed `tsvector` column, we can make searches even faster, since it will not be necessary to redo the to_tsvector calls to verify index matches."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "dfb70f2c-b453-4c55-b9f7-f3ae7a194de3",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "query = \"\"\"EXPLAIN ANALYZE SELECT count(*) FROM fakedata.testindex_materialized WHERE productname_ts @@ to_tsquery('english','flavor');\"\"\"\n",
+ "cur.execute(query)\n",
+ "cur.fetchall()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "af076345",
+ "metadata": {},
+ "source": [
+ "This indexing does speed up things if we want to search for a particular word from a column, like `flavor`. But if we want to search for some pattern within a sentence, then this index won't help. EG the query what we trying from beginning "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "482cd24d-2e66-4cb5-a6ec-f45802141424",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "query = \"\"\"EXPLAIN ANALYZE \n",
+ "SELECT COUNT(*) FROM fakedata.testindex \n",
+ "WHERE productname LIKE '%fla%';\"\"\"\n",
+ "cur.execute(query)\n",
+ "cur.fetchall()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "20909a67",
+ "metadata": {},
+ "source": [
+ "Hence, we want a different flavor of gin indexing, trigram index. I found [this](https://scoutapm.com/blog/how-to-make-text-searches-in-postgresql-faster-with-trigram-similarity) blog to help understand this.\n",
+ "\n",
+ "Trigrams are a special case of [N-grams](https://web.stanford.edu/~jurafsky/slp3/4.pdf). The concept relies on dividing the sentence into a sequence of three consecutive letters, and this result is considered as a `set .`Before performing this process \n",
+ "- Two blank spaces are added at the beginning. \n",
+ "- One at the end. \n",
+ "- Double ones replace single spaces. \n",
+ "\n",
+ "\n",
+ "The trigram set corresponding to \"flavor halibut\" looks like this:\n",
+ "\n",
+ "{\" f\",\" h\",\" fl\",\" ha\",ali,avo,but,fla,hal,ibu,lav,lib,\"or \",\"ut \",vor}\n",
+ "\n",
+ "These are considered words, and the rest remains the same as what we discussed before. To use this index add `gin_trgm_ops` as `operator class`. Let's do it."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "73d2c1d0-c7c4-463a-a1b8-96d6ddb42131",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "%%time\n",
+ "createindex = \"\"\"DROP INDEX if exists fakedata.pgweb_idx;\n",
+ "CREATE INDEX pgweb_idx ON fakedata.testindex USING GIN (productname gin_trgm_ops);\"\"\"\n",
+ "cur.execute(createindex)\n",
+ "conn.commit()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "02f734ff",
+ "metadata": {},
+ "source": [
+ "Let's see if it speeds up the query that we tried in many cases before."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "da3ef859-0d4b-4b51-94ea-951409a03a83",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "query = \"\"\"EXPLAIN ANALYZE \n",
+ "SELECT COUNT(*) FROM fakedata.testindex \n",
+ "WHERE productname LIKE '%fla%';\"\"\"\n",
+ "cur.execute(query)\n",
+ "cur.fetchall()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e6f61149-a203-4ff5-98b4-689d09588359",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "query = \"\"\"EXPLAIN ANALYZE \n",
+ "SELECT COUNT(*) FROM fakedata.testindex \n",
+ "WHERE productname LIKE '%flavor%';\"\"\"\n",
+ "cur.execute(query)\n",
+ "cur.fetchall()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4574f855",
+ "metadata": {},
+ "source": [
+ "Finally, we see this query is using indexes, and it did speed up the query.\n",
+ "\n",
+ "### GIST \n",
+ "\n",
+ "- Supports many search types, including spatial and full text.\n",
+ "- Can be extended to custom data types\n",
+ "- Balanced tree-based method (nodes & leaves)\n",
+ "\n",
+ " \n",
+ "\n",
+ "### BRIN\n",
+ "- Indexes block statistics for ordered data\n",
+ "- Block min & max mapped to index\n",
+ "- Search finds block first, then finds values\n",
+ "- Best for larger volumes of data\n",
+ "- This is essentially how we read a book’s index.\n",
+ "\n",
+ " \n",
+ "\n",
+ "We will use this later when we go through our Twitter example (tomorrow's lecture).\n",
+ "\n",
+ "Now we have learned about indexes, can you answer these questions?\n",
+ "- What are indexes?\n",
+ "- Different types of indexes?\n",
+ "- When to use indexes?\n",
+ "- How to use an index?\n",
+ "- Why do I need an index?\n",
+ "\n",
+ "## Compared to Other Services\n",
+ "\n",
+ "- BigQuery, AWS RedShift\n",
+ " - Don’t use Indexes, infrastructure searches whole “columns”\n",
+ " \n",
+ "- MongoDB\n",
+ " - Single & multiparameter indexes (similar to b-tree)\n",
+ " - spatial (similar to GIST)\n",
+ " - text indexes (similar to GIN)\n",
+ " - hash index\n",
+ " \n",
+ "- Neo4j (Graph Database)\n",
+ " - b-tree and full text\n",
+ "\n",
+ "## Summary\n",
+ "- Indexes are important to speed up operations\n",
+ "- Indexes are optimized to certain kinds of data\n",
+ "- Index performance can be assessed using `EXPLAIN.`\n",
+ "- Indexes can come at a cost"
+ ]
+ }
+ ],
+ "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.8.12"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/lectures/.ipynb_checkpoints/lecture7-checkpoint.ipynb b/lectures/.ipynb_checkpoints/lecture7-checkpoint.ipynb
new file mode 100644
index 0000000..2685e5b
--- /dev/null
+++ b/lectures/.ipynb_checkpoints/lecture7-checkpoint.ipynb
@@ -0,0 +1,390 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "e66709ec",
+ "metadata": {},
+ "source": [
+ "# Lecture 6: (de)Normalization & Data Warehousing\n",
+ "Gittu George, January 20 2022"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c3a48923",
+ "metadata": {},
+ "source": [
+ "## Todays Agenda\n",
+ "- Quick Recap\n",
+ "- WHAT is a Data Warehouse?\n",
+ "- WHY Data Warehouse? \n",
+ "- HOW do we build a data warehouse?\n",
+ "- Differences\n",
+ "- Wrap up\n",
+ "\n",
+ "## Learning objectives\n",
+ "- Understanding of data warehouse.\n",
+ "- Building a data warehouse.\n",
+ "- Connecting our learning from previous classes.\n",
+ "\n",
+ "## Quick Recap \n",
+ "- Building workflows to generate actionable insights using data\n",
+ "- How to take raw data from independent sources and load it to the database\n",
+ "- How to take the data and manage it within a database, using normalization, using data standards (schema.org) & referential integrity (fk/pk) to ensure data is clean\n",
+ "- How to speed up the underlying infrastructure with proper use of indexes\n",
+ "- The importance of reproducible workflows to ingest data to ensure data is clean and well organized\n",
+ "\n",
+ "This will be our last class talking about relational databases.\n",
+ "\n",
+ "## WHAT is a Data Warehouse?\n",
+ "\n",
+ "- A Data Warehouse is a Data Management System\n",
+ "- Data Analysts and BI Experts query a front end\n",
+ "- Data is ingested in the back-end from multiple sources\n",
+ "- A Data Warehouse simplifies data access and analysis.\n",
+ "- Its indeed data stored in a database but in a denormalized fashion\n",
+ "\n",
+ "It's generally a process,\n",
+ "\n",
+ " \n",
+ "\n",
+ "- Data moves from data sources to a data warehouse and specialized data marts.\n",
+ "- Users access information from the data warehouse/data mart.\n",
+ "- Users access data through applications.\n",
+ "\n",
+ "\n",
+ "When building a data warehouse, we’re taking data from multiple sources and joining it in a comprehensive way to support multiple purposes, from reports and app-based analytics to data mining and machine learning applications.\n",
+ "\n",
+ "How do you think a data warehouse may look like?\n",
+ "\n",
+ "## General structure of Data Warehouse\n",
+ "\n",
+ "### Reciept story \n",
+ " \n",
+ "\n",
+ "Image source: [wikiMedia](https://commons.wikimedia.org/w/index.php?curid=2612801)\n",
+ "\n",
+ "Fact Table: A table that contains the measurements of a business process of interest (in red).\n",
+ "Dimension Table(s): Tables that provide context for the fact table (in violet). \n",
+ "\n",
+ "Let's look at a real world example,\n",
+ "\n",
+ " \n",
+ "\n",
+ "Source: [ibm](https://www.ibm.com/docs/en/tap/3.5.3?topic=tables-example-fact-table-associated-dimensions),[oracle](https://docs.oracle.com/cd/E41507_01/epm91pbr3/eng/epm/penw/concept_MultidimensionalWarehouseMDW-9912e0.html)\n",
+ "\n",
+ "A Data Warehouse results from a process that takes data and transforms it into a “fact table” that a range of users can access. These facts are represented in a manner that is suited to analysis & use by a broad set of users. In addition, it powers up BI Applications like PowerBI or Tableau.\n",
+ "\n",
+ "## WHY \n",
+ "\n",
+ "- Data Can Be Complex\n",
+ "- Multiple Sources\n",
+ "- Multiple Uses\n",
+ "- Multiple Users\n",
+ "- Varied Skill Levels\n",
+ "- Changing Data & Data Needs\n",
+ "\n",
+ "## Goals of Data Warehousing & BI\n",
+ "\n",
+ "The Data Warehouse system must:\n",
+ "- Make information easily accessible\n",
+ "- Present information consistently\n",
+ "- Adapt to change\n",
+ "- Present information in a timely way\n",
+ "- Be secure\n",
+ "- Be authoritative & trustworthy\n",
+ "- Be accepted by end-users\n",
+ "\n",
+ "\n",
+ "### So How Do We Meet Expectations?\n",
+ "\n",
+ "We have this dimensional model influenced by the business requirements & data realities.\n",
+ "\n",
+ "The Data Warehouse Design must be collaborative for the expectations to be met. Some parties include;\n",
+ "- IT Managers\n",
+ "- Executives\n",
+ "- BI Analysts\n",
+ "- Department Members\n",
+ "- App Designers\n",
+ "\n",
+ " \n",
+ "\n",
+ "## How Do We Build a Data Warehouse?\n",
+ "\n",
+ "You are already in the process of building an enterprise data warehouse. Here are the steps that you already did;\n",
+ "- You identified the data sources\n",
+ "- You performed data cleaning and normalization to load the data properly into the database\n",
+ "- You are already doing some analysis on the data\n",
+ "\n",
+ "Even though you are already doing some joins to get to the analysis, we haven't properly identified the facts and dimensions for denormalizing the data to one flat table. So let's go through our Twitter data to see how this process looks like.\n",
+ "\n",
+ "### in our Twitter data, eg\n",
+ "- What is the question?\n",
+ "- Data we got in hand?\n",
+ "- What Facts Do We Need?\n",
+ "- What are Facts? What are the Dimensions of those facts?\n",
+ "\n",
+ "A Data Warehouse is a type of database, but it conforms to a different data model. It's focused on a Fact Table with multiple dimensions.\n",
+ "\n",
+ "A Data Warehouse is part of a workflow that gets us from raw data (directly from a business process like sales, purchases, quality assessment metrics, real estate holdings, or some other measure) to a dataset easily queried and modeled as part of an analytics team.\n",
+ "\n",
+ "Thinking of our Twitter data, we had a central question that asked if daily cashtag volume was positively correlated to stock price changes. So, we took raw tweet data, stock symbol, and price data from multiple sources (Twitter, NYSE, Yahoo! Finance), came up with a data model and undertook data cleaning.\n",
+ "\n",
+ "If we're interested in understanding relationships between the tweets and changes in price, we might want to look at some different analyses. For example:\n",
+ "\n",
+ " * Sentiment analysis on tweets\n",
+ " * Presence of key terms (\"sell\" or \"buy\")\n",
+ " * Changes in price\n",
+ " * Analysis by individual ticker\n",
+ " * Aggregation at different scales (week, month)\n",
+ "\n",
+ "If we do that, we would want to work on a single table to undertake our analysis, so we're assured that there is consistency in our analysis.\n",
+ "\n",
+ "Here is the query we might use:\n",
+ "\n",
+ "```sql\n",
+ "SELECT sy.nasdaqsymbol,\n",
+ "\t sy.securityname,\n",
+ " cl.tweet,\n",
+ "\t cl.userid,\n",
+ "\t CASE WHEN rp.replyid IS NULL THEN 'FALSE' \n",
+ "\t ELSE 'TRUE' END AS reply,\n",
+ "\t CASE WHEN rt.retweetid IS NULL THEN 'FALSE' \n",
+ "\t ELSE 'TRUE' END AS retweet,\n",
+ "\t ROUND(sv.open::numeric,2) AS open,\n",
+ "\t ROUND(sv.close::numeric, 2) AS close,\n",
+ "\t ROUND(sv.close::numeric, 2) - ROUND(sv.open::numeric,2) AS change,\n",
+ "\t date_trunc('day', cl.createdate)::date AS day\n",
+ "FROM tweets.cashtags AS ct\n",
+ "INNER JOIN tweets.cleantweets AS cl ON cl.tweetid = ct.tweetid\n",
+ "INNER JOIN tweets.symbols AS sy ON sy.symbolid = ct.symbolid\n",
+ "INNER JOIN tweets.stockvalues AS sv ON sv.symbolid = sy.symbolid AND sv.date = date_trunc('day', cl.createdate)::date\n",
+ "LEFT JOIN tweets.replies AS rp ON rp.tweetid = cl.tweetid\n",
+ "LEFT JOIN tweets.retweets AS rt ON rt.tweetid = cl.tweetid;\n",
+ "```\n",
+ "\n",
+ "We're putting back the columns together and making some decisions about how we treat the data; for example, we're just adding a boolean (`TRUE`/`FALSE`) as to whether something is tweeted or retweeted (using the `CASE WHEN` statements).\n",
+ "\n",
+ "To create the wide table, given that this is essentially a derived table, we're going to use a `MATERIALIZED VIEW`. This means that the output is committed to the disk, but it results from a query, so it's not something we're going to update directly.\n",
+ "\n",
+ "So our call would look something like this:\n",
+ "\n",
+ "```sql\n",
+ "CREATE MATERIALIZED VIEW tweets.dtwide AS\n",
+ "SELECT sy.nasdaqsymbol,\n",
+ "\t sy.securityname,\n",
+ " cl.tweet,\n",
+ "\t cl.userid,\n",
+ "\t CASE WHEN rp.replyid IS NULL THEN 'FALSE' \n",
+ "\t ELSE 'TRUE' END AS reply,\n",
+ "\t CASE WHEN rt.retweetid IS NULL THEN 'FALSE' \n",
+ "\t ELSE 'TRUE' END AS retweet,\n",
+ "\t ROUND(sv.open::numeric,2) AS open,\n",
+ "\t ROUND(sv.close::numeric, 2) AS close,\n",
+ "\t ROUND(sv.close::numeric, 2) - ROUND(sv.open::numeric,2) AS change,\n",
+ "\t date_trunc('day', cl.createdate)::date AS day\n",
+ "FROM tweets.cashtags AS ct\n",
+ "INNER JOIN tweets.cleantweets AS cl ON cl.tweetid = ct.tweetid\n",
+ "INNER JOIN tweets.symbols AS sy ON sy.symbolid = ct.symbolid\n",
+ "INNER JOIN tweets.stockvalues AS sv ON sv.symbolid = sy.symbolid AND sv.date = date_trunc('day', cl.createdate)::date\n",
+ "LEFT JOIN tweets.replies AS rp ON rp.tweetid = cl.tweetid\n",
+ "LEFT JOIN tweets.retweets AS rt ON rt.tweetid = cl.tweetid;\n",
+ "```\n",
+ "\n",
+ "I ran this already. Looking at the \"Materialized View\" tab in pgAdmin, you'll see the view there. It's a table with three million rows, so we may want to add some indices to the table to speed up our query speed.\n",
+ "\n",
+ "For students not doing this exercise with me, I am putting a snapshot of this table so that you can follow along.\n",
+ "\n",
+ " \n",
+ "\n",
+ " \n",
+ "\n",
+ "\n",
+ "The B-Tree index is most appropriate for most of the columns. We may choose some other indexes for some of the other columns. Let's take a look:\n",
+ "The `nasdaqsymbol` column is simple, and we're unlikely to be searching for anything \n",
+ "\n",
+ "\n",
+ "But let's pause for a minute and consider further what we're planning on doing with the data. This will also help us plan out what indexes we might add to help speed things up.\n",
+ "\n",
+ "The B-Tree index is the default in Postgres and may be most appropriate for most columns. We may choose other indexes for some of the other columns and might even see other transformations we should apply to the data. Let's take a look:\n",
+ "\n",
+ " * The `nasdaqsymbol` column could either use a B-TREE index or a HASH index. A HASH index is likely more appropriate if the ratio of unique symbols to total rows is very high (there are few unique symbols and many rows). However, remember that a hash index is only useful for equality. Are we likely to want to do analysis on stock symbols based on their letter configuration? (e.g., all stocks that start with *'A'*), or is it more likely that we're just looking at matches?\n",
+ "\n",
+ "```sql\n",
+ "CREATE INDEX tweets.symbolhash_idx ON tweets.dtwide USING hash(nasdaqsymbol);\n",
+ "CREATE INDEX tweets.symbolhash_idx ON tweets.dtwide USING btree(nasdaqsymbol);\n",
+ "```\n",
+ "\n",
+ " * `securityname` is something we might query, looking for things like *'Common Stock'* or *'ETF'* along with searches for the name of a company. In this case, full-text search may well be useful. If we want to use full-text search, then, consulting the Postgres full-text search documentation, we might want to add a column that uses a `tsvector()` transformation of the `securityname` column. We'll add it to our materialized view as the column security_ts and then work on an index:\n",
+ "\n",
+ "```sql\n",
+ "CREATE INDEX securitysearch_idx ON tweets.dtwide USING GIN (security_ts);\n",
+ "```\n",
+ "\n",
+ "This will allow us to use direct matching or full-text search to find the appropriate columns.\n",
+ "\n",
+ " * `tweets` would be similar to the `security` name, so we'll add the same index and transformation.\n",
+ " * `reply` and `retweet` have only two values, *'TRUE'* and *'FALSE'*, so a hash index here is also likely useful.\n",
+ " * `open`, `close` and `change` are numeric values. We'd likely be searching using statements like `open > 10`, or some sort of equality. In that case, a `btree` index is likely sufficient.\n",
+ " * `day` is interesting. If the dataset is not ordered by `day`, we could use a `btree` index. If we were only searching by exact matches, a `hash` might be useful here. **IF HOWEVER** we order our `MATERIALIZED VIEW` by `day`, then we can use a `brin` index. The `brin` index assigns values to ordered address blocks.\n",
+ " \n",
+ "So, this leaves us with some extra transformations and a set of indexes to apply:\n",
+ "\n",
+ "```sql\n",
+ "CREATE MATERIALIZED VIEW tweets.dtwide AS\n",
+ "SELECT sy.nasdaqsymbol,\n",
+ "\t sy.securityname,\n",
+ " to_tsvector(sy.securityname) AS security_ts,\n",
+ " cl.tweet,\n",
+ " to_tsvector(cl.tweet) AS tweet_ts,\n",
+ "\t cl.userid,\n",
+ "\t CASE WHEN rp.replyid IS NULL THEN 'FALSE' \n",
+ "\t ELSE 'TRUE' END AS reply,\n",
+ "\t CASE WHEN rt.retweetid IS NULL THEN 'FALSE' \n",
+ "\t ELSE 'TRUE' END AS retweet,\n",
+ "\t ROUND(sv.open::numeric,2) AS open,\n",
+ "\t ROUND(sv.close::numeric, 2) AS close,\n",
+ "\t ROUND(sv.close::numeric, 2) - ROUND(sv.open::numeric,2) AS change,\n",
+ "\t date_trunc('day', cl.createdate)::date AS day\n",
+ "FROM tweets.cashtags AS ct\n",
+ "INNER JOIN tweets.cleantweets AS cl ON cl.tweetid = ct.tweetid\n",
+ "INNER JOIN tweets.symbols AS sy ON sy.symbolid = ct.symbolid\n",
+ "INNER JOIN tweets.stockvalues AS sv ON sv.symbolid = sy.symbolid AND sv.date = date_trunc('day', cl.createdate)::date\n",
+ "LEFT JOIN tweets.replies AS rp ON rp.tweetid = cl.tweetid\n",
+ "LEFT JOIN tweets.retweets AS rt ON rt.tweetid = cl.tweetid\n",
+ "ORDER BY date_trunc('day', cl.createdate)::date ASC;\n",
+ "```\n",
+ "\n",
+ "And then add the following indices:\n",
+ "```sql\n",
+ "CREATE INDEX symbol_idx ON tweets.dtwide USING GIN(security_ts);\n",
+ "CREATE INDEX tweet_idx ON tweets.dtwide USING GIN(tweet_ts);\n",
+ "CREATE INDEX nasdaq_idx ON tweets.dtwide USING btree(securityname);\n",
+ "CREATE INDEX userid_idx ON tweets.dtwide USING hash(userid);\n",
+ "CREATE INDEX reply_idx ON tweets.dtwide USING hash(reply);\n",
+ "CREATE INDEX retweet_idx ON tweets.dtwide USING hash(retweet);\n",
+ "CREATE INDEX open_idx ON tweets.dtwide USING btree(open);\n",
+ "CREATE INDEX close_idx ON tweets.dtwide USING btree(close);\n",
+ "CREATE INDEX change_idx ON tweets.dtwide USING btree(change);\n",
+ "CREATE INDEX day_idx ON tweets.dtwide USING BRIN(day);\n",
+ "```\n",
+ "\n",
+ "We can directly query our own mini-data warehouse using simple SQL queries given these changes and new indices. For example, \n",
+ "\n",
+ "```sql\n",
+ "SELECT symbol, ROUND(change / open, 2), COUNT(*) AS tweets\n",
+ "FROM tweets.dtwide\n",
+ "GROUP BY day, symbol, change;\n",
+ "```\n",
+ "\n",
+ "It would give us the relationship between the number of tweets in a day and the proportional change in stock value. All of this work then allows us to simplify our workflow:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "42da4150-20dc-46b7-ad19-838ed4da16c5",
+ "metadata": {},
+ "source": [
+ "```\n",
+ "import os\n",
+ "import psycopg2\n",
+ "import psycopg2.extras\n",
+ "import random\n",
+ "from dotenv import load_dotenv\n",
+ "import time\n",
+ "import datetime\n",
+ "import pandas as pd\n",
+ "\n",
+ "load_dotenv()\n",
+ "\n",
+ "conString = {'host':os.environ.get('DB_HOST'),\n",
+ " 'dbname':os.environ.get('DB_NAME'),\n",
+ " 'user':os.environ.get('DB_USER'),\n",
+ " 'password':os.environ.get('DB_PASS'),\n",
+ " 'port':os.environ.get('DB_PORT')}\n",
+ "\n",
+ "conn = psycopg2.connect(**conString)\n",
+ "\n",
+ "cur = conn.cursor()\n",
+ "\n",
+ "cur.execute(\"\"\"SELECT nasdaqsymbol, ROUND(change / open, 2), COUNT(*) AS tweets\n",
+ " FROM tweets.dtwide\n",
+ " GROUP BY day, nasdaqsymbol, change, open;\"\"\")\n",
+ "\n",
+ "summarized = pd.DataFrame(cur.fetchall(), columns=['nasdaqsymbol', 'change', 'tweets'])\n",
+ "\n",
+ "import plotly.express as px\n",
+ "\n",
+ "fig = px.scatter(summarized, \n",
+ " x='tweets',\n",
+ " y='change',\n",
+ " title='Stock value change by tweet count.', \n",
+ " hover_name='nasdaqsymbol',)\n",
+ "cur.close()\n",
+ "fig.show()\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0d7b7bfe",
+ "metadata": {},
+ "source": [
+ "## Differences \n",
+ "### DW vs RMDBS\n",
+ "\n",
+ "| Database | Data Warehouse |\n",
+ "|----------------------------------------------------------------------------------------|------------------------------------------|\n",
+ "| Concerned with data security and durability | Concerned with broad access for analysis |\n",
+ "| Highly normalized to ensure referential integrity & reduce update/insertion anomalies. | Denormalized to ensure ease of access |\n",
+ "| Designed to support data ingestion. | they are only updated by the system |\n",
+ "| Data can go in and out | User data goes out, not in |\n",
+ "\n",
+ "### ETL vs ELT\n",
+ "\n",
+ "| ETL | ELT |\n",
+ "|------------------------------------------------|----------------------------------------|\n",
+ "| Extract, Transform and Load | Extract, Load and Transform |\n",
+ "| Data is loaded after performing transformation | Data first loaded and then transformed |\n",
+ "| Mostly used for structured data | Mostly used for unstructured data |\n",
+ "| SQL based databases | NoSQL based systems |\n",
+ "| For building Data Warehouses | For building data lakes |\n",
+ "\n",
+ "### OLAP vs OLTP\n",
+ "\n",
+ "| OLAP | OLTP |\n",
+ "|-----------------------------------------------|---------------------------------------|\n",
+ "| Online analytical processing | Online transactional processing |\n",
+ "| Focus on Insert, Update, Delete for databases | Focuses on extract data for analyzing |\n",
+ "| uses the data warehouse | uses the database |\n",
+ "\n",
+ "### BI vs DW\n",
+ "\n",
+ "Data warehouse powers up BI. (Business intelligence)\n",
+ "\n",
+ "## Wrap up"
+ ]
+ }
+ ],
+ "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.8.12"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/lectures/.ipynb_checkpoints/lecture8-checkpoint.ipynb b/lectures/.ipynb_checkpoints/lecture8-checkpoint.ipynb
new file mode 100644
index 0000000..9c1cbf3
--- /dev/null
+++ b/lectures/.ipynb_checkpoints/lecture8-checkpoint.ipynb
@@ -0,0 +1,468 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "62e042a2",
+ "metadata": {},
+ "source": [
+ "# Lecture 7: Beyond RDBMS\n",
+ "Gittu George, January 25 2022\n",
+ "\n",
+ "_Attribution: This notebook is developed using some materials provided by neo4j._"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b37d1586",
+ "metadata": {},
+ "source": [
+ "## Todays Agenda\n",
+ "- Where are we with RDBMS?\n",
+ "- Advantages/Disadvantages of RDBMS\n",
+ "- What is NoSQL?\n",
+ "- Different types of NoSQL?\n",
+ "- Introduction to graph database\n",
+ "- Explain the graph model\n",
+ "- Setting up and connecting to graph database\n",
+ "- Class Activity:\n",
+ "\n",
+ "## Learning objectives\n",
+ "- Understand (traditional) limitations of RDBMS\n",
+ "- Understand the main forms of NoSQL/non-relational databases\n",
+ "- Examine some potential solutions\n",
+ "- Introduce Graph Databases\n",
+ "- Understanding graph model\n",
+ "\n",
+ "## Where are we with RDBMS?\n",
+ "\n",
+ "- We talked about Big Data generally and issues with data specifically\n",
+ "- We talked about cloud computing and set up an RDS instance in the cloud.\n",
+ "- We talked about how to connect to a database through Jupyter (or essentially from anywhere using the ODBC drivers)\n",
+ "- We used the connection to query data and plot it using interactive figures\n",
+ "- We used normalization and constraints to clean our data.\n",
+ "- We added appropriate indexes to the data where necessary to (ostensibly) speed up data queries\n",
+ "- We de-normalized to support broader data access and built a mini-warehouse\n",
+ "\n",
+ "## Story Time (Justin Beiber problem)\n",
+ "\n",
+ "```{margin}\n",
+ " \n",
+ "\n",
+ "Check out [this article](https://www.wired.com/2015/11/how-instagram-solved-its-justin-bieber-problem/) to know why I am here!!\n",
+ "```\n",
+ "\n",
+ "We want the right tool for the job. So far, we’ve used RDBMS appropriately by normalizing it, using indexes wherever needed & denormalizing it for analytical and dashboard purposes. Postgres or any other database ( like MySQL, Oracle, and SQL Server) can support very large operations. Instagram, IMDB, Skype, and others use Postgres for business-critical services. But, SQL is not suited to all analyses. Here is what happened with Instagram - how Justin Beiber's popularity could destroy the entire infrastructure. \n",
+ "\n",
+ "Even around 2003, Instagram already had a pretty big infrastructure, and they used to use Postgres as their database. Everything was going well until Justin Beiber got into Instagram. He was one of the first high-profile people to get on Instagram. When he would post something, people would comment and like him so much. But people around the globe are doing it simultaneously that it is hitting servers so fast that people can't see their likes or comments, or it took a long time for that to get updated as a result of [locking tables](https://www.postgresql.org/docs/9.1/explicit-locking.html). After so much debugging to this issue, Instagram decided to move to a hybrid system with Amazon Cassandra and Postgres.\n",
+ "\n",
+ "The takeaway message from this story is that even though they had a big infrastructure on Postgres, no other techniques really worked out to solve this problem. And they want to rely on a hybrid solution ( Postgres and Cassandra) to this problem. Most data systems are hybrid in the era we are in; you will experience this when you start working in the industry.\n",
+ "\n",
+ "## Advantages of RDBMS\n",
+ "\n",
+ "- Uses a (semi) standard language (SQL) across all platforms.\n",
+ "- Uses ACID principles\n",
+ " - Atomic Transactions\n",
+ " - Consistent data representation\n",
+ " - Independent Transactions\n",
+ " - Durable Data\n",
+ "- Highly optimized query planner (EXPLAIN ANALYZE)\n",
+ "\n",
+ "## Disadvantages of RDBMS\n",
+ "\n",
+ "- Write times and table locks reduce efficiency\n",
+ " - Particularly when the upsert level is high (UPDATE & INSERT)\n",
+ "- Some data has complex models\n",
+ "- Some data may be “sparse” in a normalized format\n",
+ "- Documents & keys may not be suited to an RDBMS\n",
+ "- Scaling difficulties\n",
+ "\n",
+ "## What is NoSQL?\n",
+ "\n",
+ "```{margin}\n",
+ " \n",
+ "\n",
+ "[Here](http://www.strozzi.it/cgi-bin/CSA/tw7/I/en_US/nosql/Home%20Page) checkout the real NoSQL from 1998.\n",
+ "```\n",
+ "\n",
+ "NoSQL (Not (Only) SQL) was there for quite a while now. It was first introduced in 1998 by [Carlo Strozzi’s](http://www.strozzi.it/users/carlo/vitae.html) to call his lightweight, relational database that did not follow the standard SQL interface, and hence NoSQL. Later in 2000, this got popular as hashtag #NoSQL to discuss the advancement in the non-relational database world. It's a trendy buzzword these days.\n",
+ "\n",
+ "### Main Goals of NoSQL\n",
+ "\n",
+ "- Reduce the dependence on fixed data schema.\n",
+ "- Reduce the dependence on a single point of truth.\n",
+ "- Scaling!\n",
+ "\n",
+ "### NoSQL Strengths\n",
+ "\n",
+ "```{margin}\n",
+ "I came up with [the BASE] acronym with my students in their office earlier that year. I agree it is contrived a bit, but so is \"ACID\" -- much more than people realize, so we figured it was good enough. ***- Eric Brewer***\n",
+ "```\n",
+ "\n",
+ "- (semi) Schema-free\n",
+ " - With no referential integrity and internal consistency, we can easily split data across servers\n",
+ "- Accepting Basic Availability:\n",
+ " - You usually see all the tweets when you sort by “Latest”, but sometimes a refresh shows some messages you missed.\n",
+ " - You are usually chatting in real-time, but sometimes messages pile up.\n",
+ "- NoSQL is BASE\n",
+ " - Basically Available\n",
+ " - Soft State\n",
+ " - Eventual Consistency\n",
+ "\n",
+ "\n",
+ "### Different types of NoSQL database.\n",
+ "\n",
+ "#### Key–value store\n",
+ "\n",
+ "In these kinds of databases, data is stored as key-value pairs. You can think of it as a giant dictionary object. In these cases, the database will contain a simple, unique string as the key, and that key will point to a large data value. The data value can be stored as an integer, a string, or a complex structure. This database is based on the Amazon Dynamo [paper](https://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf).\n",
+ "\n",
+ "E.g., Amazon DynamoDB, Redis, Memcached\n",
+ "\n",
+ "#### Document store\n",
+ "\n",
+ "A type of key-value DB with internal (searchable) structure. A “document” contains all relevant data and has a unique key.\n",
+ "\n",
+ "Eg: ElasticSearch, MongoDB, AWS DocumentDB\n",
+ "\n",
+ "\n",
+ "#### Column based\n",
+ "\n",
+ "Column-based databases are built based on google's big table [paper](https://static.googleusercontent.com/media/research.google.com/en//archive/bigtable-osdi06.pdf). Our normal files and relational databases store data in the disk in a row based fashion, but here data is stored in a column-based fashion. Efficiency comes in filtering when a few columns are needed.\n",
+ "\n",
+ "E.g., Bigtable, Cassandra, Amazon Redshift.\n",
+ "\n",
+ "\n",
+ "#### Graph based\n",
+ "\n",
+ "Objects are defined with properties, focused on relationships between objects. More to follow...\n",
+ "\n",
+ "E.g., Neo4j, OrientDB, AWS Neptune."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a81d0426",
+ "metadata": {},
+ "source": [
+ "## Introduction to Graph Databases\n",
+ "\n",
+ "```{margin}\n",
+ " \n",
+ "\n",
+ "Can you cross all 7 bridges without crossing any of them more than once?\n",
+ "```\n",
+ "\n",
+ "Graph databases are based on graph theory, an area of discrete mathematics. Leonhard Euler is considered to be the father of graph theory. In 1735 he used graph theory to prove the [famous Seven Bridges of Konigsberg problem](https://www.ime.usp.br/~yw/2012/grafinhos/aulas/material-aulas/Paper-Euler-Letters.pdf) had no solution. \n",
+ "\n",
+ "```{margin}\n",
+ "\"This question is so banal, but seemed to be worthy of attention in that neither geometry, nor algebra, nor even the art of counting [ars combi- natoria] was sufficient to solve it. In view of this, it occurred to me to wonder whether it belonged to the geometry of position [geometria situs], which Leibniz had once so much longed for. \" ***- Leonhard Euler***\n",
+ "```\n",
+ "\n",
+ "You can watch [this](https://www.youtube.com/watch?v=nZwSo4vfw6c) exciting TEDEd video that explains in detail this problem and why it doesn't have a solution.\n",
+ "\n",
+ "You can think about a graph as a network of connections. You can see that in various disciplines and everywhere around us. For example, here are some networks you can see in computer science, chemistry, and biology. \n",
+ "\n",
+ " \n",
+ "\n",
+ "Source: [pnas](https://www.pnas.org/content/104/21/8685),[britannica](https://www.britannica.com/science/molecule),\n",
+ ",[metro](http://3.bp.blogspot.com/_k2eyFIEQ3fk/THzBoljqTPI/AAAAAAAAABk/IUUY1vuAnw0/s1600/metro_network.gif)\n",
+ "\n",
+ "\n",
+ "If you look closely into these networks, you can notice that all these networks got some objects linked to each other. So we can also define graphs as a collection of objects linked to each other.\n",
+ "\n",
+ "We can model many things around us using simple graphs. Let's draw out a simple example here...\n",
+ "\n",
+ " \n",
+ "\n",
+ "### Graph database history \n",
+ "\n",
+ "A graph database is a database management system that will enable you to perform Create, Read, Update, and Delete (CRUD) operations on these graph models that we drew out before.\n",
+ "\n",
+ "Graph databases were there for quite a long time. It may be that it's getting traction these days because of this big data and NoSQL era. So here, I drew out a timeline of graph databases.\n",
+ "\n",
+ " \n",
+ "\n",
+ "Graph databases are often used for OLTP; data is stored inside these databases as graphs, and relationship brings lots of value. So from a data scientist/ analyst perspective, relationships end up being the critical element for analyzing the data.\n",
+ "\n",
+ "### Use cases of graph databases\n",
+ "\n",
+ " \n",
+ "\n",
+ "You can find various use-cases from different domains. Please check [this out](https://neo4j.com/use-cases/) to see use-cases for your domain of interest.\n",
+ "\n",
+ "### How Do We Interact with a Graph Database?\n",
+ "\n",
+ "- ***AQL (ArangoDB Query Language):*** a SQL-like query language used in ArangoDB for both documents and graphs\n",
+ "- ***Cypher Query Language (Cypher):*** a graph query declarative language for Neo4j that enables ad hoc and programmatic (SQL-like) access to the graph.\n",
+ "- ***GQL:*** proposed ISO standard graph query language\n",
+ "- ***GraphQL:*** an open-source data query and manipulation language for APIs. Dgraph implements a modified GraphQL language called DQL (formerly GraphQL+-)\n",
+ "- ***Gremlin:*** a graph programming language that is a part of Apache TinkerPop open-source project[42]\n",
+ "- ***SPARQL:*** a query language for RDF databases that can retrieve and manipulate data stored in RDF format\n",
+ "\n",
+ "Source: Wikipedia"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b5ecfcec",
+ "metadata": {},
+ "source": [
+ "### Neo4j DBMS\n",
+ "\n",
+ "```{margin}\n",
+ "I recommend watching [this video](https://www.youtube.com/watch?v=GM9bB4ytGao) on Neo4j.\n",
+ "```\n",
+ "\n",
+ "In this course, we will be learning about neo4j. Neo4j is one of the first players in the graph database and is a very popular database. Neo4j DBMS mainly consists of 2 parts \n",
+ "- Neo4j database storage for storing the data and \n",
+ "- Neo4j graph engine to retrieve the data. \n",
+ "\n",
+ "***Neo4j DBMS storage:***\n",
+ "\n",
+ "Neo4j stores the data as graphs on disk. Index-free adjacency is one advantage of storing the data like this. I found a good \n",
+ "\n",
+ "```{margin}\n",
+ "Check out this article by [Dan McCreary](https://dmccreary.medium.com/how-to-explain-index-free-adjacency-to-your-manager-1a8e68ec664a). I strongly recommend reading it even though I am summarizing contents from this article.\n",
+ "```\n",
+ "\n",
+ "This analogy will help you understand this concept. Say if you want to visit your neighbor's house, what steps would you take?\n",
+ "\n",
+ " \n",
+ "\n",
+ "\n",
+ "Source: [Medium](https://dmccreary.medium.com/how-to-explain-index-free-adjacency-to-your-manager-1a8e68ec664a) \n",
+ "1. Leave my house.\n",
+ "2. Point me towards Anne’s house.\n",
+ "3. Walk to Anne’s house.\n",
+ "\n",
+ "This is exactly how graph databases are designed, and this is what I mean when I say data is stored as graphs. So when objects and connections are added to a database, these objects are added as connected, and hence we can access data using pointer navigation.\n",
+ "\n",
+ "If you wonder why I am saying this, you need to understand how this process is done in an RDBMS.\n",
+ "\n",
+ " \n",
+ "\n",
+ "Source: [Medium](https://dmccreary.medium.com/how-to-explain-index-free-adjacency-to-your-manager-1a8e68ec664a) \n",
+ "\n",
+ "In RDBMS, it needs to go to a central index where it needs to look for the address location of Anne's house and later point towards that address. So every time I need to visit Anne's house, I need to take this approach, and this can be time-consuming as I need to walk longer and wait at the central index to get Anne's address.\n",
+ "\n",
+ "Even though neo4j is in the class of NoSQL databases, its ACID complaint\n",
+ "\n",
+ "***Neo4j DBMS Graph engine:***\n",
+ "\n",
+ "As we discussed before, Neo4j uses Cypher Query Language (CQL) to retrieve data. This graph engine is used to interpret the cypher query passed by the user to retrieve the data, whether it is on disk or cached in memory. Our next 2 lectures are dedicated to CQL.\n",
+ "\n",
+ "### Graph model\n",
+ "\n",
+ "Let's understand the key elements in a graph model. What we sketched before will be modeled like this in Neo4j. Understanding this is very important for querying the graph database.\n",
+ "\n",
+ " \n",
+ "\n",
+ "- ***Nodes -*** Entities with searchable properties.\n",
+ "- ***Labels -*** To define what kind of nodes they are.\n",
+ "- ***Relationships -*** Links between entities with searchable properties and directionality.\n",
+ "- ***Relationship types -*** Entities with searchable properties\n",
+ "- ***Properties -*** Entities with searchable properties"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4cab59cf",
+ "metadata": {},
+ "source": [
+ "## Cypher Art\n",
+ "\n",
+ "- Querying a graph using Cypher is intuitive.\n",
+ "- Cypher is ASCII art\n",
+ "- Optimized for being read by humans, \n",
+ "- Self-explanatory queries\n",
+ "\n",
+ "You can think of Cypher as mapping English language sentence structure to patterns in a graph. Here nouns are nodes of the graph, the verbs are the relationships in the graph, and the adjectives and adverbs are the properties.\n",
+ "\n",
+ " \n",
+ "\n",
+ "```{Margin}\n",
+ "You can check out [this](https://neo4j.com/developer/cypher/guide-sql-to-cypher/) article to see how you can compare SQL to cypher. \n",
+ "```\n",
+ "\n",
+ "We saw our dummy example and graph model. Two key elements that make up a graph are Nodes (also known as vertices) and Relationships (also known as edges). And a graph model consists of 5 elements Nodes, Labels, Properties Value, Relationships, Relationship types. So let's see how these elements are represented in cypher language.\n",
+ "\n",
+ "### Nodes\n",
+ "\n",
+ "Cypher uses a pair of parentheses like ( ), (n) to represent a node.\n",
+ "\n",
+ "```\n",
+ "( ) – represents an anonymous node.\n",
+ "(n) – Here n represents a variable. All the nodes will be referred to this variable.\n",
+ "```\n",
+ "\n",
+ "We can specify the nodes we want based on the Label and/or properties inside ( ).\n",
+ "\n",
+ "#### Labels\n",
+ "\n",
+ "```\n",
+ "(:Label) – returns all nodes with specified Label\n",
+ "(variable:Label) – returns all nodes with specified Label to variable\n",
+ "(:Label1:Label2) – returns all nodes with Label1 and Label2\n",
+ "(variable:Label1:Label2) – returns all nodes with Label1 and Label2 to variable\n",
+ "```\n",
+ "\n",
+ "#### Property for nodes\n",
+ "\n",
+ "``` \n",
+ "(variable {propertyKey: propertyValue})\n",
+ "(variable:Label {propertyKey: propertyValue})\n",
+ "(variable {propertyKey1: propertyValue1, propertyKey2: propertyValue2})\n",
+ "(variable:Label {propertyKey: propertyValue, propertyKey2: propertyValue2})\n",
+ "``` \n",
+ "\n",
+ "#### Let's work out our nodes\n",
+ "\n",
+ "```\n",
+ "(git:PERSON {name: 'Gittu', age: 21, profession:'Postdoctoral Fellow'})\n",
+ "(mik:PERSON {name: 'Mike', age: 21,profession:'Associate Professor'})\n",
+ "(tom:PERSON {name: 'Tom', age: 24,profession:'Postdoctoral Fellow'})\n",
+ "(pan:ANIMAL {name: 'Panda',method_of_movement: 'Climb',height:20,mainly_found: 'China'})\n",
+ "(pol:ANIMAL {name: 'Polar Bear',method_of_movement: 'Walk',height:20,mainly_found: 'Canada'})\n",
+ "(kan:ANIMAL {name: 'Kangaroo',method_of_movement: 'Hop',height:30,mainly_found: 'Australia'})\n",
+ "```\n",
+ "\n",
+ "### Relationships\n",
+ "\n",
+ "Syntax: Specifying aliases for column headings\n",
+ "\n",
+ "```\n",
+ "() // a node\n",
+ "()--() // 2 nodes have some type of relationship\n",
+ "()-->() // the first node has a relationship to the second node\n",
+ "()<--() // the second node has a relationship to the first node\n",
+ "```\n",
+ "\n",
+ "```{note}\n",
+ "Comments in Cypher - //\n",
+ "Just like how # is in python & how -- in SQL\n",
+ "```\n",
+ "#### Relationship type\n",
+ "\n",
+ "```\n",
+ "(node1)-[:REL_TYPE]->(node2)\n",
+ "```\n",
+ "\n",
+ "#### Properties for relationships\n",
+ "\n",
+ "```\n",
+ "(node1)-[:REL_TYPE {propertyKey: propertyValue}]->(node2)\n",
+ "(node1)-[:REL_TYPE {propertyKey: propertyValue, propertyKey2: propertyValue2}]->(node2)\n",
+ "```\n",
+ "\n",
+ "#### Let's work out our relationships\n",
+ "\n",
+ "```\n",
+ "(git)-[:LIKE {percent: 100}]->(pan)\n",
+ "(mik)-[:LIKE {percent: 90}]->(pol)\n",
+ "(tom)-[:LIKE {percent: 100}]->(pol)\n",
+ "(tom)-[:LIKE {percent: 50}]->(kan)\n",
+ "(pol)-[:LIKE {percent: 100}]->(git)\n",
+ "```\n",
+ "\n",
+ "We modeled our nodes and relationship, and in the next class, we will create these in our graph database."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c5a2f6f2",
+ "metadata": {
+ "tags": []
+ },
+ "source": [
+ "## Setting up your graph database \n",
+ "\n",
+ "Now we are quite knowledgeable about graph databases, and in particular, with Neo4j, we will set up our graph database(neo4j).\n",
+ "\n",
+ "You can install neo4j \n",
+ "- On your laptop, \n",
+ "- On your company's on-premise server, or\n",
+ "- On the cloud. \n",
+ "\n",
+ "We will be setting up our neo4j in the cloud. There are a couple of options to do it, either we can spin up an ec2 instance in AWS and install neo4j in there, but remember doing it this way means we need to manage neo4j for all the software updates, system maintenance, and other admin tasks. Recently, in 2019, neo4j came up with its own managed cloud service called neo4j Aura. So we will be using neo4j aura for this course. Follow the instructions under installation instructions to install this.\n",
+ "\n",
+ "## How to connect?\n",
+ "How we can create a \n",
+ "\n",
+ "- Command-line\n",
+ "- Neo4j Browser\n",
+ "- Python py2neo (drivers)\n",
+ "\n",
+ "### Python py2neo (drivers)\n",
+ "\n",
+ "First, you need to install the packages; I recommend installing it using pip, as conda might not install dependencies.\n",
+ "\n",
+ "```\n",
+ "import sys\n",
+ "!{sys.executable} -m pip install py2neo\n",
+ "```\n",
+ "\n",
+ "```python\n",
+ "# This is our neo4j Connection Object:\n",
+ "import os\n",
+ "from dotenv import load_dotenv\n",
+ "from py2neo import Graph\n",
+ "## Here again our .env file\n",
+ "load_dotenv()\n",
+ "## add neo4j environment details to your .env file\n",
+ "# This is our neo4j Connection Object:\n",
+ "graph = Graph(uri = os.environ.get('NEO_URI'), \n",
+ " user = os.environ.get('NEO_USER'),\n",
+ " password = os.environ.get('NEO_PASS'))\n",
+ "\n",
+ "### Syntax\n",
+ "#query =\"\"\"Your Cypher Query\"\"\"\n",
+ "#usercount = graph.run(query).data()\n",
+ "#print(usercount)\n",
+ "\n",
+ "## Example: Below code won't run, as we don't have any data in our graph database. You can run this after loading the dump.\n",
+ "usercount = graph.run(\"\"\"\n",
+ " MATCH (u:USER)\n",
+ " RETURN COUNT(u)\"\"\").data()\n",
+ "print(usercount)\n",
+ "```\n",
+ "\n",
+ "## Can you?\n",
+ "\n",
+ "- Describe what a graph is.\n",
+ "- Describe what a graph database is.\n",
+ "- Describe some common use cases for using a graph database.\n",
+ "- Describe the Neo4j property graph model.\n",
+ "- Whiteboard a graph data model.\n",
+ "- Describe the components and benefits of the Neo4j Graph Platform."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "09e603be",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "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.8.12"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/lectures/img/.DS_Store b/lectures/img/.DS_Store
new file mode 100644
index 0000000..daf1d57
Binary files /dev/null and b/lectures/img/.DS_Store differ
diff --git a/lectures/img/color.png b/lectures/img/color.png
new file mode 100644
index 0000000..d29eed6
Binary files /dev/null and b/lectures/img/color.png differ
diff --git a/lectures/img/full.png b/lectures/img/full.png
new file mode 100644
index 0000000..cacbf2b
Binary files /dev/null and b/lectures/img/full.png differ
diff --git a/lectures/img/inner.png b/lectures/img/inner.png
new file mode 100644
index 0000000..8c3fa23
Binary files /dev/null and b/lectures/img/inner.png differ
diff --git a/lectures/img/left.png b/lectures/img/left.png
new file mode 100644
index 0000000..4c6cee2
Binary files /dev/null and b/lectures/img/left.png differ
diff --git a/lectures/img/right.png b/lectures/img/right.png
new file mode 100644
index 0000000..4a8b30f
Binary files /dev/null and b/lectures/img/right.png differ
diff --git a/lectures/lecture2.ipynb b/lectures/lecture2.ipynb
new file mode 100644
index 0000000..7060183
--- /dev/null
+++ b/lectures/lecture2.ipynb
@@ -0,0 +1,4865 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "3867df60",
+ "metadata": {},
+ "source": [
+ "# Lecture 2: SQL - Class 2\n",
+ "Gittu George, February 24 2022"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0c890d68",
+ "metadata": {},
+ "source": [
+ "## Today's Agenda\n",
+ "\n",
+ "- Refresher (Warm-up)\n",
+ "- Refresher exercise\n",
+ "- Learning objectives\n",
+ "- More SQL commands (lap 1)\n",
+ "- Aggregations (lap 2)\n",
+ "- Grouping (lap 2)\n",
+ "- Joins (lap 3)\n",
+ "- Summary (finish line)\n",
+ "\n",
+ "## Warm-up ☕️\n",
+ "### Refresher!! Take some time and think if you know...\n",
+ "\n",
+ "- What are databases?\n",
+ "- Where is it commonly used?\n",
+ "- How to set up a database?\n",
+ "- General structure of a database\n",
+ "- Various ways of interacting with the database, using\n",
+ " - command-line interface\n",
+ " - ODBC drivers via developer interfaces like pgadmin and toad.\n",
+ " - jupyter notebook ( which we will be using throughout the course)\n",
+ "- To create a table and various data types \n",
+ "- Integrity constraints such as primary key and foreign key\n",
+ "- Basic SQL commands like SELECT, FROM, and WHERE\n",
+ "\n",
+ "In this class, we will be using the tables that we created in lecture 1. Here are the scripts if you want to recreate the classroom exercise table.\n",
+ "\n",
+ "```{toggle}\n",
+ "\n",
+ "```sql\n",
+ "DROP TABLE IF EXISTS students,courses;\n",
+ "\n",
+ "CREATE TABLE IF NOT EXISTS students(\n",
+ "student_no integer PRIMARY KEY, \n",
+ "stud_name text,\n",
+ "age integer,\n",
+ "major text);\n",
+ "\n",
+ "CREATE TABLE IF NOT EXISTS courses(\n",
+ "id SERIAL PRIMARY KEY,\n",
+ "student_no integer, \n",
+ "course_name text,\n",
+ "course_year integer,\n",
+ "course_percentage float);\n",
+ "\n",
+ "INSERT INTO students(student_no,stud_name,age,major) VALUES (111,'Catherine',23,'MBAN'),\n",
+ "(222,'Tiff',28,'MDS'),\n",
+ "(333,'John',23,'MBAN'),\n",
+ "(444,'Amir',28,'MBAN'),\n",
+ "(555,'Gittu',20,'MDS'),\n",
+ "(666,'Isha',30,'MBAN'),\n",
+ "(777,'Heidy',30,'MBAN'),\n",
+ "(888,'Angela',27,'MBAN'),\n",
+ "(999,'Jason',30,'MBAN');\n",
+ "\n",
+ "INSERT INTO courses(student_no,course_name,course_year,course_percentage) VALUES (111,'TPCS INFO TECH',2019,88),\n",
+ "(111,'Data Visualization',2019,88),\n",
+ "(222,'Health and Technology',2020,80),\n",
+ "(222,'Web and Cloud Computing',2019,91),\n",
+ "(222,'Spark Programming',2019,90),\n",
+ "(333,'Parallel Computing',2019,90),\n",
+ "(444,'Large Scale Infrastructures',2019,83),\n",
+ "(444,'TPCS INFO TECH',2019,98),\n",
+ "(555,'TPCS INFO TECH',2020,78),\n",
+ "(555,'Health and Technology',2020,81),\n",
+ "(666,'Data Visualization',2021,85),\n",
+ "(666,'Parallel Computing',2019,87),\n",
+ "(888,'Spark Programming',2019,93),\n",
+ "(999,'TPCS INFO TECH',2019,98),\n",
+ "(999,'Data Visualization',2019,87),\n",
+ "(1000,'C programming',2019,87),\n",
+ "(1111,'Introduction to Genomics',2019,87);\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a754bc90-1a0a-4b5e-8063-78c16951e14d",
+ "metadata": {},
+ "source": [
+ "First of all let's load SQL to work on this notebook."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "5b537cb8-53cc-4bf2-82a3-560c697eef4a",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import os\n",
+ "import psycopg2\n",
+ "from dotenv import load_dotenv\n",
+ "load_dotenv()\n",
+ "host = os.environ.get('DB_HOST')\n",
+ "user = os.environ.get('DB_USER')\n",
+ "password = os.environ.get('DB_PASS')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "50f1cc3c-aaec-499f-883f-390832ddc3d8",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%load_ext sql"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "fedffc66-2974-4c48-b61a-8d38d74e5596",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'Connected: postgres@postgres'"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%sql postgresql://{user}:{password}@{host}:5432/postgres"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "350fda14-b35c-40f8-9a7d-5b8c90032c4a",
+ "metadata": {
+ "tags": []
+ },
+ "source": [
+ "### Refresher exercise \n",
+ "\n",
+ "***Question 1:*** In this table `eg`\n",
+ "\n",
+ "| firstname | countryfrom | continent | gender | age |\n",
+ "|-----------|-------------|---------------|--------|-----|\n",
+ "| matt | usa | north america | M | 23 |\n",
+ "| jenn | uk | europe | F | 35 |\n",
+ "| guy | france | europe | M | 25 |\n",
+ "| james | china | asia | M | 29 |\n",
+ "| lida | india | asia | F | 56 |\n",
+ "| linda | canada | north america | F | 18 |\n",
+ "| sofia | germany | europe | F | 22 |\n",
+ "| george | india | asia | M | 29 |\n",
+ "\n",
+ "What will be returned from following SQL \n",
+ "```sql\n",
+ "select firstname,gender from eg where continent ='asia' AND age =29\n",
+ "```\n",
+ "A) george,M\n",
+ "\n",
+ "B) george,M,asia,29\n",
+ "\n",
+ "C) george,india,asia, M,29 \n",
+ "\n",
+ "D) M,george\n",
+ "\n",
+ "E) M,james\n",
+ "\n",
+ "```{toggle}\n",
+ "\n",
+ "***Answer: D***\n",
+ "\n",
+ "```{important}\n",
+ "Remember the order of the columns returned will be based on the order of columns that we specify within the select statement.\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "69eba2fb-8158-44e0-ae46-64410b0be50a",
+ "metadata": {},
+ "source": [
+ "***Question 2:*** Consider a scenario where you want to perform analysis on a table (peopletable) with 100 columns (including personname,age,gender,origin...etc.) that define a person. You are interested in seeing the name of individuals older than 90. Which SQL query is more appropriate in this situation?\n",
+ "\n",
+ "A) ***select * from peopletable where age > 90;***\n",
+ "\n",
+ "B) ***select personname,age from peopletable where age > 90;***\n",
+ "\n",
+ "B) ***select personname,age,gender,origin from peopletable where age < 90;***\n",
+ "\n",
+ "D) ***select personname from peopletable where age > 90;***\n",
+ "\n",
+ "```{toggle}\n",
+ "\n",
+ "***Answer: D***\n",
+ "\n",
+ "```{important}\n",
+ "Just bring the columns and rows that are needed. Even though `SELECT` and `WHERE` are very basic SQL commands, it's crucial when you are dealing with a large table\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9a7e0bbd-ae06-4b1b-b934-cdf23e081828",
+ "metadata": {},
+ "source": [
+ "## Learning objectives\n",
+ "- You will be able to create SQL queries using (Lap 1 & Lap 2)\n",
+ " - Distinct, \n",
+ " - ORDER BY, \n",
+ " - LIMIT, \n",
+ " - GROUP BY, \n",
+ " - alias AS\n",
+ "- You will learn about different kinds of joins and be able to create SQL queries that perform `JOINS`. (Lap 3)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a033eb62-9c25-4b69-8cb2-14a36cd9272b",
+ "metadata": {},
+ "source": [
+ "## Lap 1 🥛\n",
+ "### DISTINCT\n",
+ "\n",
+ "The DISTINCT statement is used only to return distinct elements from a table.\n",
+ "\n",
+ "***Syntax:***\n",
+ "\n",
+ "```sql\n",
+ "SELECT DISTINCT column1, column2, ...columnN\n",
+ "FROM tablename;\n",
+ "```\n",
+ "\n",
+ "`DISTINCT` is applied to all columns that follows the `DISTINCT` keyword. Say for eg if we give `DISTINCT column1, column2` then the combination of values in both `column1` and `column2` columns will be used for returning the unique combination (or removing the duplicate elements)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "d92b4b94-d2cd-4f44-86de-baf632b91ba6",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "9 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " course_name \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " Introduction to Genomics \n",
+ " \n",
+ " \n",
+ " Large Scale Infrastructures \n",
+ " \n",
+ " \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " Web and Cloud Computing \n",
+ " \n",
+ " \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " C programming \n",
+ " \n",
+ " \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[('Data Visualization',),\n",
+ " ('Introduction to Genomics',),\n",
+ " ('Large Scale Infrastructures',),\n",
+ " ('Spark Programming',),\n",
+ " ('Web and Cloud Computing',),\n",
+ " ('Health and Technology',),\n",
+ " ('C programming',),\n",
+ " ('TPCS INFO TECH',),\n",
+ " ('Parallel Computing',)]"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT DISTINCT course_name FROM courses;"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 94,
+ "id": "1dc8e64b-e555-4580-9b95-1ea9af3d0273",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "11 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " course_name \n",
+ " course_year \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " C programming \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " \n",
+ " \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[('Parallel Computing', 2019),\n",
+ " ('C programming', 2019),\n",
+ " ('TPCS INFO TECH', 2019),\n",
+ " ('Spark Programming', 2019),\n",
+ " ('Web and Cloud Computing', 2019),\n",
+ " ('Health and Technology', 2020),\n",
+ " ('Introduction to Genomics', 2019),\n",
+ " ('Large Scale Infrastructures', 2019),\n",
+ " ('TPCS INFO TECH', 2020),\n",
+ " ('Data Visualization', 2021),\n",
+ " ('Data Visualization', 2019)]"
+ ]
+ },
+ "execution_count": 94,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT DISTINCT course_name, course_year FROM courses;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "904d711d-aad3-4c57-8291-86b1ea1c8604",
+ "metadata": {},
+ "source": [
+ "### ORDER BY\n",
+ "\n",
+ "`ORDER BY` statement sorts the results returned by `SELECT` based on a sort expression.\n",
+ "\n",
+ "Syntax\n",
+ "```sql\n",
+ "SELECT column1, column2 ...columnN\n",
+ "FROM table_name\n",
+ "ORDER BY column1 [ASC | DESC], column2 [ASC | DESC] ....columnN [ASC|DESC];\n",
+ "```\n",
+ "\n",
+ "```{note}\n",
+ "By default, it will sort in ASC. So you can choose not to give ASC.\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 95,
+ "id": "922e1099-7d1b-41ff-aa18-21492c59b20a",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "17 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " id \n",
+ " student_no \n",
+ " course_name \n",
+ " course_year \n",
+ " course_percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 16 \n",
+ " 1000 \n",
+ " C programming \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 17 \n",
+ " 1111 \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 91.0 \n",
+ " \n",
+ " \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 12 \n",
+ " 666 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 93.0 \n",
+ " \n",
+ " \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.0 \n",
+ " \n",
+ " \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 81.0 \n",
+ " \n",
+ " \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (15, 999, 'Data Visualization', 2019, 87.0),\n",
+ " (16, 1000, 'C programming', 2019, 87.0),\n",
+ " (17, 1111, 'Introduction to Genomics', 2019, 87.0),\n",
+ " (4, 222, 'Web and Cloud Computing', 2019, 91.0),\n",
+ " (5, 222, 'Spark Programming', 2019, 90.0),\n",
+ " (6, 333, 'Parallel Computing', 2019, 90.0),\n",
+ " (7, 444, 'Large Scale Infrastructures', 2019, 83.0),\n",
+ " (8, 444, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (12, 666, 'Parallel Computing', 2019, 87.0),\n",
+ " (13, 888, 'Spark Programming', 2019, 93.0),\n",
+ " (3, 222, 'Health and Technology', 2020, 80.0),\n",
+ " (10, 555, 'Health and Technology', 2020, 81.0),\n",
+ " (9, 555, 'TPCS INFO TECH', 2020, 78.0),\n",
+ " (11, 666, 'Data Visualization', 2021, 85.0)]"
+ ]
+ },
+ "execution_count": 95,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT * \n",
+ "FROM courses\n",
+ "ORDER BY course_year;"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 96,
+ "id": "57c57348-9def-4d46-8b9e-eac8d509cf60",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "17 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " id \n",
+ " student_no \n",
+ " course_name \n",
+ " course_year \n",
+ " course_percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 81.0 \n",
+ " \n",
+ " \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.0 \n",
+ " \n",
+ " \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 12 \n",
+ " 666 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 93.0 \n",
+ " \n",
+ " \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 16 \n",
+ " 1000 \n",
+ " C programming \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 17 \n",
+ " 1111 \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 91.0 \n",
+ " \n",
+ " \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(11, 666, 'Data Visualization', 2021, 85.0),\n",
+ " (10, 555, 'Health and Technology', 2020, 81.0),\n",
+ " (3, 222, 'Health and Technology', 2020, 80.0),\n",
+ " (9, 555, 'TPCS INFO TECH', 2020, 78.0),\n",
+ " (6, 333, 'Parallel Computing', 2019, 90.0),\n",
+ " (7, 444, 'Large Scale Infrastructures', 2019, 83.0),\n",
+ " (8, 444, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (12, 666, 'Parallel Computing', 2019, 87.0),\n",
+ " (13, 888, 'Spark Programming', 2019, 93.0),\n",
+ " (14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (15, 999, 'Data Visualization', 2019, 87.0),\n",
+ " (16, 1000, 'C programming', 2019, 87.0),\n",
+ " (1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (17, 1111, 'Introduction to Genomics', 2019, 87.0),\n",
+ " (2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (4, 222, 'Web and Cloud Computing', 2019, 91.0),\n",
+ " (5, 222, 'Spark Programming', 2019, 90.0)]"
+ ]
+ },
+ "execution_count": 96,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT * \n",
+ "FROM courses\n",
+ "ORDER BY course_year DESC;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "25e1020a-93a1-48e9-a763-80674b98b46c",
+ "metadata": {},
+ "source": [
+ "### LIMIT\n",
+ "\n",
+ "Until now, we were returning everything that our SQL query returns. `LIMIT` statement is used to limit the number of rows that are returned. \n",
+ "\n",
+ "syntax:\n",
+ "\n",
+ "```sql\n",
+ "SELECT column1, column2, ...columnN\n",
+ "FROM tablename\n",
+ "LIMIT numberofrows;\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 97,
+ "id": "ef67fccb-2300-4109-b626-8bc7580b29c2",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "2 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " id \n",
+ " student_no \n",
+ " course_name \n",
+ " course_year \n",
+ " course_percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (2, 111, 'Data Visualization', 2019, 88.0)]"
+ ]
+ },
+ "execution_count": 97,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT * \n",
+ "FROM courses\n",
+ "LIMIT 2;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "74228e90-a013-4c9b-b34b-c7cc8fdea343",
+ "metadata": {},
+ "source": [
+ "`LIMIT` keyword is used in a variety of situations. Here are a few cases\n",
+ "\n",
+ "- ***Memory Management:*** Say you just want to look at the output your query returns. If you are dealing with lots and lots of rows, returning the entire rows can slow down the query, cause memory issues, and finally crash your jupyter. In these cases, you can use `LIMIT`. \n",
+ "\n",
+ "- ***Interest in the first few rows:*** If we are interested in just the first N rows, we can achieve that using `LIMIT`. People tend to use `LIMIT` a lot when they want to return the top 10 rows after performing an `ORDER BY`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "dac09f4e-ebbf-4bf1-9c13-d45c3249cf30",
+ "metadata": {},
+ "source": [
+ "Let's apply all the statements that we learned in one statement.\n",
+ "\n",
+ "***Question:*** List out the row that got the highest `course_percentage` for the `Data Visualization` course."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 98,
+ "id": "368c7bdf-7c5c-4e8c-81ae-02de2044b7d8",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "1 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " id \n",
+ " student_no \n",
+ " course_name \n",
+ " course_year \n",
+ " course_percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(2, 111, 'Data Visualization', 2019, 88.0)]"
+ ]
+ },
+ "execution_count": 98,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT * \n",
+ "FROM courses\n",
+ "WHERE course_name = 'Data Visualization'\n",
+ "ORDER BY course_percentage DESC\n",
+ "LIMIT 1;"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 124,
+ "id": "81a06062-3b03-4c69-ac1a-d592e043b437",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "8 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " age \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 18 \n",
+ " \n",
+ " \n",
+ " 22 \n",
+ " \n",
+ " \n",
+ " 23 \n",
+ " \n",
+ " \n",
+ " 25 \n",
+ " \n",
+ " \n",
+ " 29 \n",
+ " \n",
+ " \n",
+ " 29 \n",
+ " \n",
+ " \n",
+ " 35 \n",
+ " \n",
+ " \n",
+ " 56 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(18,), (22,), (23,), (25,), (29,), (29,), (35,), (56,)]"
+ ]
+ },
+ "execution_count": 124,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%sql select age from eg order by age"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ca99d216-561f-4cc2-ad74-9b6dc6174a76",
+ "metadata": {},
+ "source": [
+ "### Checkpoint 1 !! Take some time and think if you can...\n",
+ "\n",
+ "- Select columns and bring in rows just what we need (using `SELECT` & `WHERE`)\n",
+ "- Return `DISTINCT` elements\n",
+ "- `ORDER BY` rows returned based on column(s)\n",
+ "- `LIMIT` the number of rows returned\n",
+ "\n",
+ "Now we will learn some more advanced SQL operations to gain more insight into the data. But, before that, let's do some exercise. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9fd2624e-57a1-4250-9833-a52db657be86",
+ "metadata": {},
+ "source": [
+ "### Check point 1 exercise.\n",
+ "\n",
+ "#### iclicker questions\n",
+ "\n",
+ "Answer the following questions using the table `eg`\n",
+ "\n",
+ "| firstname | countryfrom | continent | gender | age |\n",
+ "|-----------|-------------|---------------|--------|-----|\n",
+ "| matt | usa | north america | M | 23 |\n",
+ "| jenn | uk | europe | F | 35 |\n",
+ "| guy | france | europe | M | 25 |\n",
+ "| james | china | asia | M | 29 |\n",
+ "| lida | india | asia | F | 56 |\n",
+ "| linda | canada | north america | F | 18 |\n",
+ "| sofia | germany | europe | F | 22 |\n",
+ "| george | india | asia | M | 29 |\n",
+ "\n",
+ "\n",
+ "***Question 1:*** How many elements will be returned from this SQL \n",
+ "\n",
+ "```sql\n",
+ "select DISTINCT gender,firstname from eg\n",
+ "```\n",
+ "\n",
+ "A) 2\n",
+ "\n",
+ "B) 8\n",
+ "\n",
+ "C) 4 \n",
+ "\n",
+ "```{toggle}\n",
+ "***Answer: B***\n",
+ "```\n",
+ "***Question 2:*** What will be the first value returned from this SQL \n",
+ "\n",
+ "```sql\n",
+ "select age from eg order by age\n",
+ "```\n",
+ "A) 23\n",
+ "\n",
+ "B) 18\n",
+ "\n",
+ "C) 56\n",
+ "\n",
+ "D) 35\n",
+ "\n",
+ "```{toggle}\n",
+ "***Answer: B***\n",
+ "```\n",
+ "\n",
+ "#### Reasoning Question\n",
+ "\n",
+ "***Question 1:*** Write a SQL query to list the row that got the highest `course_percentage` for the `TPCS INFO TECH'` course. Can you spot any issues by examining the original table?\n",
+ "\n",
+ "```{toggle}\n",
+ "\n",
+ "You probably might have got this SQL query by changing the SQL query that we discussed for the \"Data Visualization\" course. \n",
+ "\n",
+ "***SELECT * FROM courses\n",
+ "WHERE course_name = 'TPCS INFO TECH'\n",
+ "ORDER BY course_percentage DESC\n",
+ "LIMIT 1;***\n",
+ "\n",
+ "This gives you \n",
+ "\n",
+ "| id | student_no | course_name | course_year | course_percentage |\n",
+ "|---:|-----------:|---------------:|------------:|------------------:|\n",
+ "| 8 | 444 | TPCS INFO TECH | 2019 | 98.0 |\n",
+ "\n",
+ "What are possible issues? Take out the `LIMIT`, and you might notice there is a tie, and 2 students, 444 and 999 scored the highest.\n",
+ "\n",
+ "\n",
+ "***SELECT * FROM courses\n",
+ "WHERE course_name = 'TPCS INFO TECH'\n",
+ "ORDER BY course_percentage DESC***\n",
+ "\n",
+ "\n",
+ "| id | student_no | course_name | course_year | course_percentage |\n",
+ "|---:|-----------:|---------------:|------------:|------------------:|\n",
+ "| 8 | 444 | TPCS INFO TECH | 2019 | 98.0 |\n",
+ "| 14 | 999 | TPCS INFO TECH | 2019 | 98.0 |\n",
+ "| 1 | 111 | TPCS INFO TECH | 2019 | 88.0 |\n",
+ "| 9 | 555 | TPCS INFO TECH | 2020 | 78.0 |\n",
+ "\n",
+ "So even though many of them use a combination of `ORDER BY` and `LIMIT` for these scenarios we might run into situations like this. There are a couple of ways to deal with these kinds of scenarios, and we will learn about `subquery` in our next class, which will help you capture ties."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "91ab04a8-4456-4653-9348-4ad153493bb8",
+ "metadata": {},
+ "source": [
+ "## Lap 2 🧋\n",
+ "### Aggregations\n",
+ "\n",
+ "So far, we have returned columns in our select statement. We can also use aggregation functions that operate on rows to summarize the data in the form of a single value. Here is a list of aggregation functions in SQL:\n",
+ "\n",
+ "| Function | Description |\n",
+ "|----------|------------------|\n",
+ "| MAX() | maximum value |\n",
+ "| MIN() | minimum value |\n",
+ "| AVG() | average value |\n",
+ "| SUM() | sum of values |\n",
+ "| COUNT() | number of values |"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 35,
+ "id": "66c023e1-c594-4c0c-8809-7dc54ee6ca4b",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "1 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " count \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 15 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(15,)]"
+ ]
+ },
+ "execution_count": 35,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT COUNT(course_name) FROM courses;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7d0df81a-5c7c-48e3-b05a-4410be8f4840",
+ "metadata": {},
+ "source": [
+ "The above query is counting number of values in the column `course_name`. it's also sort of like counting the number of rows. We can also pass the `DISTINCT` columns into these operations. For example, the below query will find the number of courses available in the university."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 36,
+ "id": "ca947761-3728-4594-83d4-d6484927b7cf",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "1 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " count \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 7 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(7,)]"
+ ]
+ },
+ "execution_count": 36,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT COUNT(DISTINCT course_name) FROM courses;"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 126,
+ "id": "e505b586-f4ea-436e-8ade-abb9e5892638",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "1 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " avg \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 87.70588235294117 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(87.70588235294117,)]"
+ ]
+ },
+ "execution_count": 126,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "select AVG(course_percentage) \n",
+ "FROM courses"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c28c9c2e-6275-4e8c-bee9-36f5795f62eb",
+ "metadata": {},
+ "source": [
+ "Few things to keep in mind when dealing with the aggregation function\n",
+ "\n",
+ "- You are not restricted in using just one aggregation function in the SELECT statement.✅\n",
+ "\n",
+ "```sql\n",
+ "SELECT COUNT(DISTINCT course_name),max(course_percentage) FROM courses;\n",
+ "```\n",
+ "\n",
+ "- You CAN'T use aggregations and regular columns in a single query. You can use only when you have a `GROUP BY` clause. (will discuss soon)❌\n",
+ "\n",
+ "```sql\n",
+ "SELECT COUNT(DISTINCT course_name),course_percentage FROM courses;\n",
+ "```\n",
+ "\n",
+ "- You CAN'T use aggregation function in a where clause. The following query is wrong ❌\n",
+ "\n",
+ "```sql\n",
+ "SELECT * FROM courses WHERE course_percentage < AVG(course_percentage);\n",
+ "```\n",
+ "\n",
+ "You can answer this question when we discuss subqueries in the next class (another reason to learn subqueries :) )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2164f169-3e19-4804-9985-4a2f392cbd13",
+ "metadata": {},
+ "source": [
+ "### Grouping\n",
+ "\n",
+ "The aggregations we learned in our previous session also become useful when using the `GROUP BY` statement. `GROUP BY` statement divides a table into groups of rows based on the values of one or more columns. Once this grouping is done, you can apply your aggregation to these groups.\n",
+ "\n",
+ "Syntax:\n",
+ "```sql\n",
+ "SELECT\n",
+ " grouping_columns, aggregated_columns\n",
+ "FROM\n",
+ " table1\n",
+ "GROUP BY\n",
+ " grouping_columns\n",
+ "```\n",
+ "\n",
+ "Example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 42,
+ "id": "7ac98229-6261-48fb-ac85-20435c5d325c",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "15 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " id \n",
+ " student_no \n",
+ " course_name \n",
+ " course_year \n",
+ " course_percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.0 \n",
+ " \n",
+ " \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 91.0 \n",
+ " \n",
+ " \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 81.0 \n",
+ " \n",
+ " \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 93.0 \n",
+ " \n",
+ " \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (3, 222, 'Health and Technology', 2020, 80.0),\n",
+ " (4, 222, 'Web and Cloud Computing', 2019, 91.0),\n",
+ " (5, 222, 'Spark Programming', 2019, 90.0),\n",
+ " (6, 333, 'Parallel Computing', 2019, 90.0),\n",
+ " (7, 444, 'Large Scale Infrastructures', 2019, 83.0),\n",
+ " (8, 444, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (9, 555, 'TPCS INFO TECH', 2020, 78.0),\n",
+ " (10, 555, 'Health and Technology', 2020, 81.0),\n",
+ " (11, 666, 'Data Visualization', 2021, 85.0),\n",
+ " (12, 777, 'Parallel Computing', 2019, 87.0),\n",
+ " (13, 888, 'Spark Programming', 2019, 93.0),\n",
+ " (14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (15, 999, 'Data Visualization', 2019, 87.0)]"
+ ]
+ },
+ "execution_count": 42,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "select * from courses;"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 43,
+ "id": "dc492d9a-f26d-494b-97c0-ef208c6c3187",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "7 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " course_name \n",
+ " avg \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 86.66666666666667 \n",
+ " \n",
+ " \n",
+ " Large Scale Infrastructures \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " Spark Programming \n",
+ " 91.5 \n",
+ " \n",
+ " \n",
+ " Web and Cloud Computing \n",
+ " 91.0 \n",
+ " \n",
+ " \n",
+ " Health and Technology \n",
+ " 80.5 \n",
+ " \n",
+ " \n",
+ " TPCS INFO TECH \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " Parallel Computing \n",
+ " 88.5 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[('Data Visualization', 86.66666666666667),\n",
+ " ('Large Scale Infrastructures', 83.0),\n",
+ " ('Spark Programming', 91.5),\n",
+ " ('Web and Cloud Computing', 91.0),\n",
+ " ('Health and Technology', 80.5),\n",
+ " ('TPCS INFO TECH', 88.0),\n",
+ " ('Parallel Computing', 88.5)]"
+ ]
+ },
+ "execution_count": 43,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "select course_name, AVG(course_percentage) \n",
+ "FROM courses\n",
+ "group by course_name;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d3e6fb09-9bf3-4c40-9933-158edd938eb6",
+ "metadata": {},
+ "source": [
+ "We can also perform a multi level grouping;"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 51,
+ "id": "a9edaba0-6db9-4c80-927c-2df18798d0f3",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "6 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " course_name \n",
+ " course_year \n",
+ " avg \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.5 \n",
+ " \n",
+ " \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.5 \n",
+ " \n",
+ " \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.5 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[('Parallel Computing', 2019, 88.5),\n",
+ " ('Health and Technology', 2020, 80.5),\n",
+ " ('Large Scale Infrastructures', 2019, 83.0),\n",
+ " ('TPCS INFO TECH', 2020, 78.0),\n",
+ " ('Data Visualization', 2021, 85.0),\n",
+ " ('Data Visualization', 2019, 87.5)]"
+ ]
+ },
+ "execution_count": 51,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "select course_name, course_year,AVG(course_percentage) \n",
+ "FROM courses\n",
+ "group by course_name, course_year\n",
+ "having AVG(course_percentage) <90 ;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "59b16985-fefc-4326-bfde-b7f55dcf9610",
+ "metadata": {},
+ "source": [
+ "Now, what if I want to see only the courses with an average of less than 90 %? We mentioned before that this kind of filtering (filtering on the aggregation function) is not possible using `WHERE` statement, and that's why we want the `HAVING` statement to do filtering using these aggregated values.\n",
+ "\n",
+ "### HAVING\n",
+ "\n",
+ "Syntax:\n",
+ "```sql\n",
+ "SELECT\n",
+ " grouping_columns, aggregated_columns\n",
+ "FROM\n",
+ " table1\n",
+ "GROUP BY\n",
+ " grouping_columns\n",
+ "HAVING\n",
+ " group_condition\n",
+ "\n",
+ "```\n",
+ "\n",
+ "```{important}\n",
+ "To summarize:\n",
+ "\n",
+ "- WHERE filters rows before grouping. It filters records in a table level\n",
+ "- HAVING filters groups after grouping. It filters records in a group level\n",
+ "```\n",
+ "\n",
+ "For example, let's get the question we raised at the end of the grouping section. I want to see the courses with a course average of less than 90 %? "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 49,
+ "id": "8210644e-b6bd-4106-aa3b-85520b30380a",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "5 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " course_name \n",
+ " avg \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 86.66666666666667 \n",
+ " \n",
+ " \n",
+ " Large Scale Infrastructures \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " Health and Technology \n",
+ " 80.5 \n",
+ " \n",
+ " \n",
+ " TPCS INFO TECH \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " Parallel Computing \n",
+ " 88.5 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[('Data Visualization', 86.66666666666667),\n",
+ " ('Large Scale Infrastructures', 83.0),\n",
+ " ('Health and Technology', 80.5),\n",
+ " ('TPCS INFO TECH', 88.0),\n",
+ " ('Parallel Computing', 88.5)]"
+ ]
+ },
+ "execution_count": 49,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "select course_name, AVG(course_percentage) \n",
+ "FROM courses\n",
+ "group by course_name\n",
+ "having AVG(course_percentage) <90 ;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ac4b81f6-37f8-4e28-914d-9d73aa271733",
+ "metadata": {},
+ "source": [
+ "### Using alias (AS)\n",
+ "\n",
+ "Until now, we have been referring tables as table names and columns as column names from the schema. But when writing SQL, we are not required to use the same column and table names as in the schema. Instead, we can create aliases for a column or a table using the keyword `AS`.\n",
+ "\n",
+ "Syntax:\n",
+ "```sql\n",
+ "SELECT\n",
+ " column1 [AS] c1,\n",
+ " column2 [AS] c2\n",
+ "FROM\n",
+ " table1 [AS] t1;\n",
+ "```\n",
+ "\n",
+ "It's entirely optional to use AS, but WHY do we want it? \n",
+ "- This makes code more readable\n",
+ "- You can return the columns with a more meaningful name \n",
+ "- Helps a lot when we do JOINS ( wait for the next topic)\n",
+ "\n",
+ "Let's rewrite our previous query using AS"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 129,
+ "id": "4ff59e7a-50e7-4c4a-8469-35ee6481ee29",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "8 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " course_name \n",
+ " course_year \n",
+ " average course percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.5 \n",
+ " \n",
+ " \n",
+ " C programming \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.5 \n",
+ " \n",
+ " \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.5 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[('Parallel Computing', 2019, 88.5),\n",
+ " ('C programming', 2019, 87.0),\n",
+ " ('Health and Technology', 2020, 80.5),\n",
+ " ('Introduction to Genomics', 2019, 87.0),\n",
+ " ('Large Scale Infrastructures', 2019, 83.0),\n",
+ " ('TPCS INFO TECH', 2020, 78.0),\n",
+ " ('Data Visualization', 2021, 85.0),\n",
+ " ('Data Visualization', 2019, 87.5)]"
+ ]
+ },
+ "execution_count": 129,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "select course_name, course_year,AVG(course_percentage) AS \"average course percentage\"\n",
+ "FROM courses AS c\n",
+ "group by course_name, course_year\n",
+ "having AVG(course_percentage) <90 ;"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 137,
+ "id": "d66a7187-7ea7-42d5-a90d-76e584456600",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ " * postgresql://postgres:***@postgres.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "17 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " id \n",
+ " student_no \n",
+ " course_name \n",
+ " course_year \n",
+ " course_percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 80.0 \n",
+ " \n",
+ " \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 91.0 \n",
+ " \n",
+ " \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 90.0 \n",
+ " \n",
+ " \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 83.0 \n",
+ " \n",
+ " \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 78.0 \n",
+ " \n",
+ " \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 81.0 \n",
+ " \n",
+ " \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 85.0 \n",
+ " \n",
+ " \n",
+ " 12 \n",
+ " 666 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 93.0 \n",
+ " \n",
+ " \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 98.0 \n",
+ " \n",
+ " \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 16 \n",
+ " 1000 \n",
+ " C programming \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ " 17 \n",
+ " 1111 \n",
+ " Introduction to Genomics \n",
+ " 2019 \n",
+ " 87.0 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (3, 222, 'Health and Technology', 2020, 80.0),\n",
+ " (4, 222, 'Web and Cloud Computing', 2019, 91.0),\n",
+ " (5, 222, 'Spark Programming', 2019, 90.0),\n",
+ " (6, 333, 'Parallel Computing', 2019, 90.0),\n",
+ " (7, 444, 'Large Scale Infrastructures', 2019, 83.0),\n",
+ " (8, 444, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (9, 555, 'TPCS INFO TECH', 2020, 78.0),\n",
+ " (10, 555, 'Health and Technology', 2020, 81.0),\n",
+ " (11, 666, 'Data Visualization', 2021, 85.0),\n",
+ " (12, 666, 'Parallel Computing', 2019, 87.0),\n",
+ " (13, 888, 'Spark Programming', 2019, 93.0),\n",
+ " (14, 999, 'TPCS INFO TECH', 2019, 98.0),\n",
+ " (15, 999, 'Data Visualization', 2019, 87.0),\n",
+ " (16, 1000, 'C programming', 2019, 87.0),\n",
+ " (17, 1111, 'Introduction to Genomics', 2019, 87.0)]"
+ ]
+ },
+ "execution_count": 137,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT *\n",
+ "FROM courses;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a81789ef-1835-4a47-bc68-d172531eefbc",
+ "metadata": {},
+ "source": [
+ "### Checkpoint 2 !! Take some time and think if you can...\n",
+ "\n",
+ "- Do some aggregations and grouping queries using SQL.\n",
+ " - GROUP BY\n",
+ " - aggregation functions\n",
+ " - HAVING\n",
+ "\n",
+ "Until now, you deal with queries on a single table. What if we are interested in data from another table as well? For example, I am interested in seeing the course details (from the `courses` table) and the students (from the `students` table) related to those courses. In these situations, we use joins. Let's learn how to stitch tables together. Before that, let's do some exercise..."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f99b3fa8-0acd-4c08-891c-0c3dfc8865c5",
+ "metadata": {},
+ "source": [
+ "### Check point 2 exercise.\n",
+ "\n",
+ "#### iclicker questions\n",
+ "\n",
+ "***Question 1:*** Spot the issue, if any, in this SQL query \n",
+ "\n",
+ "```sql\n",
+ "SELECT COUNT(DISTINCT course_name),max(course_percentage),course_percentage \n",
+ "FROM courses ;\n",
+ "```\n",
+ "\n",
+ "A: Multiple aggregation functions in the SELECT statement\n",
+ "\n",
+ "B: No issues\n",
+ "\n",
+ "C: Can't use DISTINCT inside an aggregation function\n",
+ "\n",
+ "D: Can't use aggregations and regular columns in a single query\n",
+ "\n",
+ "```{toggle}\n",
+ "***Answer: D***\n",
+ "```\n",
+ "\n",
+ "***Question 2:*** Spot the issue, if any, in this SQL query \n",
+ "\n",
+ "```sql\n",
+ "SELECT COUNT(DISTINCT course_name),max(course_percentage),course_year\n",
+ "FROM courses \n",
+ "GROUP BY course_year;\n",
+ "```\n",
+ "\n",
+ "A: Multiple aggregation functions in the SELECT statement\n",
+ "\n",
+ "B: No issues\n",
+ "\n",
+ "C: Can't use DISTINCT inside an aggregation function\n",
+ "\n",
+ "D: Can't use aggregations and regular columns in a single query\n",
+ "\n",
+ "```{toggle}\n",
+ "***Answer: B***\n",
+ "```\n",
+ "\n",
+ "***Question 3:*** Spot the issue, if any, in this SQL query \n",
+ "\n",
+ "```sql\n",
+ "SELECT COUNT(DISTINCT course_name),max(course_percentage),course_year\n",
+ "FROM courses \n",
+ "WHERE course_name != 'TPCS INFO TECH'\n",
+ "GROUP BY course_year\n",
+ "having course_percentage < 90;\n",
+ "```\n",
+ "\n",
+ "A: No issues\n",
+ "\n",
+ "B: Can't use column `course_year` in `SELECT`\n",
+ "\n",
+ "C: Can't use WHERE when using aggregation functions\n",
+ "\n",
+ "D: Can't use course_percentage in a `HAVING` statement\n",
+ "\n",
+ "```{toggle}\n",
+ "***Answer: D***\n",
+ "```\n",
+ "\n",
+ "#### Reasoning Question\n",
+ "\n",
+ "***Question 1:*** We learned the `GROUP BY` clause, and we used it with aggregate functions. Using table `courses`, write a SQL query using `GROUP BY` without any aggregation function. Write your findings and learnings.\n",
+ "\n",
+ "```{toggle}\n",
+ "You must have tried a variety of SQL queries and got into error;\n",
+ "\n",
+ "***select * FROM courses\n",
+ "group by course_name;***\n",
+ "\n",
+ "***select course_year,course_name\n",
+ "FROM courses\n",
+ "group by course_name;***\n",
+ "\n",
+ "Below is one query that runs, and this query can be considered equivalent to using `DISTINCT` if no aggregate functions are used. \n",
+ "\n",
+ "***select course_name\n",
+ "FROM courses\n",
+ "group by course_name;***\n",
+ "\n",
+ "```{important}\n",
+ "In an aggregation query, the unaggregated expressions need to be consistent with the `GROUP BY` expressions. And all other expressions need to use aggregation functions\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e4010eaa-4240-4a85-bfe0-9dad81b29a74",
+ "metadata": {},
+ "source": [
+ "## Lap 3 🧃\n",
+ "### Join\n",
+ "\n",
+ "Syntax:\n",
+ "```sql\n",
+ "SELECT\n",
+ " columns\n",
+ "FROM\n",
+ " left_table\n",
+ "join_type\n",
+ " right_table\n",
+ "ON\n",
+ " join_condition\n",
+ ";\n",
+ "```\n",
+ "\n",
+ "Following are the types of joins\n",
+ "### Cross join\n",
+ "This is the simplest way of performing a join by cross joining 2 tables (like the cartesian product from your relational algebra classes), in our case, table `students` and `courses`. This kind of join returns all possible combinations of rows from `students` and `courses`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "3a8aa89a-bb69-46f2-af7e-a8f7d53171ac",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "135 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " student_no \n",
+ " stud_name \n",
+ " age \n",
+ " major \n",
+ " id \n",
+ " student_no_1 \n",
+ " course_name \n",
+ " course_year \n",
+ " course_percentage \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 1 \n",
+ " 111 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 2 \n",
+ " 111 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 3 \n",
+ " 222 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 4 \n",
+ " 222 \n",
+ " Web and Cloud Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 5 \n",
+ " 222 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 6 \n",
+ " 333 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 7 \n",
+ " 444 \n",
+ " Large Scale Infrastructures \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 8 \n",
+ " 444 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 9 \n",
+ " 555 \n",
+ " TPCS INFO TECH \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 10 \n",
+ " 555 \n",
+ " Health and Technology \n",
+ " 2020 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 11 \n",
+ " 666 \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 12 \n",
+ " 777 \n",
+ " Parallel Computing \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 13 \n",
+ " 888 \n",
+ " Spark Programming \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 14 \n",
+ " 999 \n",
+ " TPCS INFO TECH \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " MBAN \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " MDS \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " MBAN \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " MBAN \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " MDS \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Isha \n",
+ " 30 \n",
+ " MBAN \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " MBAN \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " MBAN \n",
+ " 15 \n",
+ " 999 \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " 88.0 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(111, 'Catherine', 23, 'MBAN', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 1, 111, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 2, 111, 'Data Visualization', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 3, 222, 'Health and Technology', 2020, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 3, 222, 'Health and Technology', 2020, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 3, 222, 'Health and Technology', 2020, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 3, 222, 'Health and Technology', 2020, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 3, 222, 'Health and Technology', 2020, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 3, 222, 'Health and Technology', 2020, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 3, 222, 'Health and Technology', 2020, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 3, 222, 'Health and Technology', 2020, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 3, 222, 'Health and Technology', 2020, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 4, 222, 'Web and Cloud Computing', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 4, 222, 'Web and Cloud Computing', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 4, 222, 'Web and Cloud Computing', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 4, 222, 'Web and Cloud Computing', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 4, 222, 'Web and Cloud Computing', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 4, 222, 'Web and Cloud Computing', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 4, 222, 'Web and Cloud Computing', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 4, 222, 'Web and Cloud Computing', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 4, 222, 'Web and Cloud Computing', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 5, 222, 'Spark Programming', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 5, 222, 'Spark Programming', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 5, 222, 'Spark Programming', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 5, 222, 'Spark Programming', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 5, 222, 'Spark Programming', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 5, 222, 'Spark Programming', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 5, 222, 'Spark Programming', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 5, 222, 'Spark Programming', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 5, 222, 'Spark Programming', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 6, 333, 'Parallel Computing', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 6, 333, 'Parallel Computing', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 6, 333, 'Parallel Computing', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 6, 333, 'Parallel Computing', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 6, 333, 'Parallel Computing', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 6, 333, 'Parallel Computing', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 6, 333, 'Parallel Computing', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 6, 333, 'Parallel Computing', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 6, 333, 'Parallel Computing', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 7, 444, 'Large Scale Infrastructures', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 7, 444, 'Large Scale Infrastructures', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 7, 444, 'Large Scale Infrastructures', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 7, 444, 'Large Scale Infrastructures', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 7, 444, 'Large Scale Infrastructures', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 7, 444, 'Large Scale Infrastructures', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 7, 444, 'Large Scale Infrastructures', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 7, 444, 'Large Scale Infrastructures', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 7, 444, 'Large Scale Infrastructures', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 8, 444, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 8, 444, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 8, 444, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 8, 444, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 8, 444, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 8, 444, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 8, 444, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 8, 444, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 8, 444, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 9, 555, 'TPCS INFO TECH', 2020, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 9, 555, 'TPCS INFO TECH', 2020, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 9, 555, 'TPCS INFO TECH', 2020, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 9, 555, 'TPCS INFO TECH', 2020, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 9, 555, 'TPCS INFO TECH', 2020, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 9, 555, 'TPCS INFO TECH', 2020, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 9, 555, 'TPCS INFO TECH', 2020, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 9, 555, 'TPCS INFO TECH', 2020, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 9, 555, 'TPCS INFO TECH', 2020, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 10, 555, 'Health and Technology', 2020, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 10, 555, 'Health and Technology', 2020, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 10, 555, 'Health and Technology', 2020, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 10, 555, 'Health and Technology', 2020, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 10, 555, 'Health and Technology', 2020, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 10, 555, 'Health and Technology', 2020, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 10, 555, 'Health and Technology', 2020, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 10, 555, 'Health and Technology', 2020, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 10, 555, 'Health and Technology', 2020, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 11, 666, 'Data Visualization', 2021, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 11, 666, 'Data Visualization', 2021, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 11, 666, 'Data Visualization', 2021, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 11, 666, 'Data Visualization', 2021, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 11, 666, 'Data Visualization', 2021, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 11, 666, 'Data Visualization', 2021, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 11, 666, 'Data Visualization', 2021, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 11, 666, 'Data Visualization', 2021, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 11, 666, 'Data Visualization', 2021, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 12, 777, 'Parallel Computing', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 12, 777, 'Parallel Computing', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 12, 777, 'Parallel Computing', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 12, 777, 'Parallel Computing', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 12, 777, 'Parallel Computing', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 12, 777, 'Parallel Computing', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 12, 777, 'Parallel Computing', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 12, 777, 'Parallel Computing', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 12, 777, 'Parallel Computing', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 13, 888, 'Spark Programming', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 13, 888, 'Spark Programming', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 13, 888, 'Spark Programming', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 13, 888, 'Spark Programming', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 13, 888, 'Spark Programming', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 13, 888, 'Spark Programming', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 13, 888, 'Spark Programming', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 13, 888, 'Spark Programming', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 13, 888, 'Spark Programming', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 14, 999, 'TPCS INFO TECH', 2019, 88.0),\n",
+ " (111, 'Catherine', 23, 'MBAN', 15, 999, 'Data Visualization', 2019, 88.0),\n",
+ " (222, 'Tiff', 28, 'MDS', 15, 999, 'Data Visualization', 2019, 88.0),\n",
+ " (333, 'John', 23, 'MBAN', 15, 999, 'Data Visualization', 2019, 88.0),\n",
+ " (444, 'Amir', 28, 'MBAN', 15, 999, 'Data Visualization', 2019, 88.0),\n",
+ " (555, 'Gittu', 20, 'MDS', 15, 999, 'Data Visualization', 2019, 88.0),\n",
+ " (666, 'Isha', 30, 'MBAN', 15, 999, 'Data Visualization', 2019, 88.0),\n",
+ " (777, 'Isha', 30, 'MBAN', 15, 999, 'Data Visualization', 2019, 88.0),\n",
+ " (888, 'Angela', 27, 'MBAN', 15, 999, 'Data Visualization', 2019, 88.0),\n",
+ " (999, 'Jason', 30, 'MBAN', 15, 999, 'Data Visualization', 2019, 88.0)]"
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "\n",
+ "SELECT\n",
+ " *\n",
+ "FROM\n",
+ " students\n",
+ "CROSS JOIN\n",
+ " courses\n",
+ ";"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "57ec5f08-452f-4376-a71c-c1d27552a712",
+ "metadata": {},
+ "source": [
+ "```{note}\n",
+ "But in real life, we usually perform joins on a column, and we will discuss some types of joins on columns in the following sections. Since we are performing joins on a column, we need to pass that information using the `ON` keyword to give which columns are used to stitch the tables together\n",
+ "```\n",
+ "### Inner join\n",
+ "\n",
+ "Inner join only returns the matching rows from the left and right tables."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 65,
+ "id": "c4959539-2048-404b-a5ff-2d5cbf146f72",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "15 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " student_no \n",
+ " stud_name \n",
+ " age \n",
+ " course_name \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Web and Cloud Computing \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " Large Scale Infrastructures \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(111, 'Catherine', 23, 'TPCS INFO TECH'),\n",
+ " (111, 'Catherine', 23, 'Data Visualization'),\n",
+ " (222, 'Tiff', 28, 'Health and Technology'),\n",
+ " (222, 'Tiff', 28, 'Web and Cloud Computing'),\n",
+ " (222, 'Tiff', 28, 'Spark Programming'),\n",
+ " (333, 'John', 23, 'Parallel Computing'),\n",
+ " (444, 'Amir', 28, 'Large Scale Infrastructures'),\n",
+ " (444, 'Amir', 28, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'Health and Technology'),\n",
+ " (666, 'Isha', 30, 'Data Visualization'),\n",
+ " (666, 'Isha', 30, 'Parallel Computing'),\n",
+ " (888, 'Angela', 27, 'Spark Programming'),\n",
+ " (999, 'Jason', 30, 'TPCS INFO TECH'),\n",
+ " (999, 'Jason', 30, 'Data Visualization')]"
+ ]
+ },
+ "execution_count": 65,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "\n",
+ "SELECT s.student_no,s.stud_name,s.age,c.course_name\n",
+ "FROM\n",
+ " students AS s\n",
+ "INNER JOIN\n",
+ " courses AS c\n",
+ "ON\n",
+ " s.student_no = c.student_no;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7aff1260-a2c7-42f2-a92a-92ba317d64c8",
+ "metadata": {},
+ "source": [
+ "```{margin}\n",
+ "Check how we are using the alias `AS` we learned in the previous session.\n",
+ "```\n",
+ "\n",
+ "```{note}\n",
+ "In the returned table, student “Heidy” is missing as that student is not taking any courses and is not mentioned in the `courses`. Also, the courses \"C programming\" and \"Introduction to Genomics\" are missing since no students from our `student` table are taking these courses.\n",
+ "```\n",
+ "### Outer join \n",
+ "\n",
+ "An outer join returns all the rows from one or both of the tables that join. There are 3 variations of it. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6140faf0-24d5-49ed-937b-4fdc45447206",
+ "metadata": {},
+ "source": [
+ "#### Left outer\n",
+ "The first table that appears in the query is the left table, and the one appearing after the `LEFT OUTER JOIN` keyword is the right table.\n",
+ "\n",
+ "A left outer join returns all rows from the left table (matching or not), in addition to the matching rows from both tables. So the non-matching rows from the left table are assigned null values in the columns that belong to the right table.\n",
+ "\n",
+ "If you think about it from a Venn diagram perspective, it will look like...\n",
+ "\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 66,
+ "id": "c2b3c698-c279-47e5-9646-6857bd3b2ede",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "16 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " student_no \n",
+ " stud_name \n",
+ " age \n",
+ " course_name \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Web and Cloud Computing \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " Large Scale Infrastructures \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Heidy \n",
+ " 30 \n",
+ " None \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(111, 'Catherine', 23, 'TPCS INFO TECH'),\n",
+ " (111, 'Catherine', 23, 'Data Visualization'),\n",
+ " (222, 'Tiff', 28, 'Health and Technology'),\n",
+ " (222, 'Tiff', 28, 'Web and Cloud Computing'),\n",
+ " (222, 'Tiff', 28, 'Spark Programming'),\n",
+ " (333, 'John', 23, 'Parallel Computing'),\n",
+ " (444, 'Amir', 28, 'Large Scale Infrastructures'),\n",
+ " (444, 'Amir', 28, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'Health and Technology'),\n",
+ " (666, 'Isha', 30, 'Data Visualization'),\n",
+ " (666, 'Isha', 30, 'Parallel Computing'),\n",
+ " (888, 'Angela', 27, 'Spark Programming'),\n",
+ " (999, 'Jason', 30, 'TPCS INFO TECH'),\n",
+ " (999, 'Jason', 30, 'Data Visualization'),\n",
+ " (777, 'Heidy', 30, None)]"
+ ]
+ },
+ "execution_count": 66,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "\n",
+ "SELECT s.student_no,s.stud_name,s.age,c.course_name\n",
+ "FROM\n",
+ " students AS s\n",
+ "LEFT OUTER JOIN\n",
+ " courses AS c\n",
+ "ON\n",
+ " s.student_no = c.student_no;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "da7f9856-53c3-4f3c-b505-fd3cf39d5744",
+ "metadata": {},
+ "source": [
+ "#### Right outer \n",
+ "It behaves exactly in the same way as a left join, except that it keeps all rows from the right table and only the matching ones from the left table.\n",
+ "\n",
+ "If you think about it from a Venn diagram perspective, it will look like.\n",
+ "\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 67,
+ "id": "c715fc93-110c-4781-8fa6-632e403e1c89",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "17 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " student_no \n",
+ " stud_name \n",
+ " age \n",
+ " course_name \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Web and Cloud Computing \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " Large Scale Infrastructures \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " None \n",
+ " None \n",
+ " None \n",
+ " C programming \n",
+ " \n",
+ " \n",
+ " None \n",
+ " None \n",
+ " None \n",
+ " Introduction to Genomics \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(111, 'Catherine', 23, 'TPCS INFO TECH'),\n",
+ " (111, 'Catherine', 23, 'Data Visualization'),\n",
+ " (222, 'Tiff', 28, 'Health and Technology'),\n",
+ " (222, 'Tiff', 28, 'Web and Cloud Computing'),\n",
+ " (222, 'Tiff', 28, 'Spark Programming'),\n",
+ " (333, 'John', 23, 'Parallel Computing'),\n",
+ " (444, 'Amir', 28, 'Large Scale Infrastructures'),\n",
+ " (444, 'Amir', 28, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'Health and Technology'),\n",
+ " (666, 'Isha', 30, 'Data Visualization'),\n",
+ " (666, 'Isha', 30, 'Parallel Computing'),\n",
+ " (888, 'Angela', 27, 'Spark Programming'),\n",
+ " (999, 'Jason', 30, 'TPCS INFO TECH'),\n",
+ " (999, 'Jason', 30, 'Data Visualization'),\n",
+ " (None, None, None, 'C programming'),\n",
+ " (None, None, None, 'Introduction to Genomics')]"
+ ]
+ },
+ "execution_count": 67,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "\n",
+ "SELECT s.student_no,s.stud_name,s.age,c.course_name\n",
+ "FROM\n",
+ " students AS s\n",
+ "RIGHT OUTER JOIN\n",
+ " courses AS c\n",
+ "ON\n",
+ " s.student_no = c.student_no;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "474d5994-7832-4586-b2cc-327ef5385ce3",
+ "metadata": {},
+ "source": [
+ "#### Full outer\n",
+ "\n",
+ "left join + right join = full outer join.\n",
+ "\n",
+ "It retrieves matching and non-matching rows from both tables. \n",
+ "\n",
+ "If you think about it from a Venn diagram perspective, it will look like.\n",
+ "\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 68,
+ "id": "34a25ff1-6f68-43e7-8a00-67a213795a3c",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "18 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " student_no \n",
+ " stud_name \n",
+ " age \n",
+ " course_name \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 111 \n",
+ " Catherine \n",
+ " 23 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Web and Cloud Computing \n",
+ " \n",
+ " \n",
+ " 222 \n",
+ " Tiff \n",
+ " 28 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 333 \n",
+ " John \n",
+ " 23 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " Large Scale Infrastructures \n",
+ " \n",
+ " \n",
+ " 444 \n",
+ " Amir \n",
+ " 28 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 555 \n",
+ " Gittu \n",
+ " 20 \n",
+ " Health and Technology \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " 666 \n",
+ " Isha \n",
+ " 30 \n",
+ " Parallel Computing \n",
+ " \n",
+ " \n",
+ " 888 \n",
+ " Angela \n",
+ " 27 \n",
+ " Spark Programming \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " TPCS INFO TECH \n",
+ " \n",
+ " \n",
+ " 999 \n",
+ " Jason \n",
+ " 30 \n",
+ " Data Visualization \n",
+ " \n",
+ " \n",
+ " None \n",
+ " None \n",
+ " None \n",
+ " C programming \n",
+ " \n",
+ " \n",
+ " None \n",
+ " None \n",
+ " None \n",
+ " Introduction to Genomics \n",
+ " \n",
+ " \n",
+ " 777 \n",
+ " Heidy \n",
+ " 30 \n",
+ " None \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[(111, 'Catherine', 23, 'TPCS INFO TECH'),\n",
+ " (111, 'Catherine', 23, 'Data Visualization'),\n",
+ " (222, 'Tiff', 28, 'Health and Technology'),\n",
+ " (222, 'Tiff', 28, 'Web and Cloud Computing'),\n",
+ " (222, 'Tiff', 28, 'Spark Programming'),\n",
+ " (333, 'John', 23, 'Parallel Computing'),\n",
+ " (444, 'Amir', 28, 'Large Scale Infrastructures'),\n",
+ " (444, 'Amir', 28, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'TPCS INFO TECH'),\n",
+ " (555, 'Gittu', 20, 'Health and Technology'),\n",
+ " (666, 'Isha', 30, 'Data Visualization'),\n",
+ " (666, 'Isha', 30, 'Parallel Computing'),\n",
+ " (888, 'Angela', 27, 'Spark Programming'),\n",
+ " (999, 'Jason', 30, 'TPCS INFO TECH'),\n",
+ " (999, 'Jason', 30, 'Data Visualization'),\n",
+ " (None, None, None, 'C programming'),\n",
+ " (None, None, None, 'Introduction to Genomics'),\n",
+ " (777, 'Heidy', 30, None)]"
+ ]
+ },
+ "execution_count": 68,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "\n",
+ "SELECT s.student_no,s.stud_name,s.age,c.course_name\n",
+ "FROM\n",
+ " students AS s\n",
+ "FULL OUTER JOIN\n",
+ " courses AS c\n",
+ "ON\n",
+ " s.student_no = c.student_no;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1b9ac233-6f00-4276-8e4c-16e7cfbfbbe4",
+ "metadata": {},
+ "source": [
+ "### Summarize:\n",
+ "\n",
+ "***CARTESIAN JOIN:*** returns the Cartesian product of the sets of records from the two or more joined tables.\n",
+ "\n",
+ "***INNER JOIN:*** returns rows when there is a match in both tables.\n",
+ "\n",
+ "***LEFT JOIN:*** returns all rows from the left table, even if there are no matches in the right table.\n",
+ "\n",
+ "***RIGHT JOIN:*** returns all rows from the right table, even if there are no matches in the left table.\n",
+ "\n",
+ "***FULL JOIN:*** combines the results of both left and right outer joins."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "de8a2d6d-3a7a-4985-ac24-140316fbd60e",
+ "metadata": {},
+ "source": [
+ "We learned now about the joins. You know now how to join 2 tables. Once you joined 2 tables its sort of behave like another table that you apply the operations what we learned."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 87,
+ "id": "d33b019c-4093-4605-937b-a2f4b13ff778",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " * postgresql://postgres:***@cpsc.cwq2624hiuap.us-west-2.rds.amazonaws.com:5432/postgres\n",
+ "3 rows affected.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " stud_name \n",
+ " course_name \n",
+ " course_year \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Isha \n",
+ " Data Visualization \n",
+ " 2021 \n",
+ " \n",
+ " \n",
+ " Catherine \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ " Jason \n",
+ " Data Visualization \n",
+ " 2019 \n",
+ " \n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "[('Isha', 'Data Visualization', 2021),\n",
+ " ('Catherine', 'Data Visualization', 2019),\n",
+ " ('Jason', 'Data Visualization', 2019)]"
+ ]
+ },
+ "execution_count": 87,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "select s.stud_name,c.course_name, c.course_year\n",
+ "FROM students AS s\n",
+ "INNER JOIN\n",
+ "courses AS c\n",
+ "ON s.student_no = c.student_no\n",
+ "WHERE course_name = 'Data Visualization'\n",
+ "ORDER BY course_year DESC;"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d9b1dc63-2d19-4c0e-8a35-2f6a57a59a51",
+ "metadata": {},
+ "source": [
+ "```{important}\n",
+ "We can have all the keywords we learned in a single SQL query, and we have come across some in previous examples. `BUT` the order of SQL keywords `DOES` matter: SELECT, FROM, JOIN, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT. \n",
+ "```\n",
+ "```{seealso}\n",
+ "We experienced performing joins on 2 tables, but joins can also be performed on multiple tables. Multi joins in SQL work by progressively creating derived tables one after the other. Here is the link that explains this [process](https://www.interfacett.com/blogs/multiple-joins-work-just-like-single-joins/)\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d04adf0d-6a40-45d8-922f-84855e75db71",
+ "metadata": {},
+ "source": [
+ "### Checkpoint 3 !! Take some time and think if you can...\n",
+ "- Understand different kinds of joins\n",
+ "- When to use joins\n",
+ "- Write SQL queries to join tables"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "731106d9-ba08-4692-bac2-8463d1518c87",
+ "metadata": {},
+ "source": [
+ "### Check point 3 exercise.\n",
+ "#### iclicker questions\n",
+ "\n",
+ "***Question 1:*** Spot the issue, if any, in this SQL query \n",
+ "\n",
+ "```sql\n",
+ "select s.stud_name,c.course_name, c.course_year\n",
+ "FROM students AS s\n",
+ "INNER JOIN\n",
+ "courses AS c\n",
+ "ON s.student_no = c.student_no\n",
+ "ORDER BY course_year DESC\n",
+ "WHERE course_name = 'Data Visualization';\n",
+ "```\n",
+ "\n",
+ "A: There are some issues with the join key\n",
+ "\n",
+ "B: No issues\n",
+ "\n",
+ "C: Can't specify alias when performing a join\n",
+ "\n",
+ "D: ORDER BY need to come after the WHERE clause\n",
+ "\n",
+ "```{toggle}\n",
+ "***Answer: D***\n",
+ "```\n",
+ "\n",
+ "***Question 2:*** \"In this Venn diagram green color indicate left outer join\" - is this statement TRUE/FALSE?\n",
+ "\n",
+ " \n",
+ "\n",
+ "A: TRUE\n",
+ "\n",
+ "B: FALSE\n",
+ "\n",
+ "```{toggle}\n",
+ "***Answer: FALSE***\n",
+ "\n",
+ "The following figure is what indicates the left outer join.\n",
+ "\n",
+ " \n",
+ "\n",
+ "But what is given here is a special scenario where we apply left join to be useful. For example, what if we want to find all students who are not taking any courses?\n",
+ "\n",
+ "\n",
+ "SELECT s.student_no,s.stud_name,s.age,c.course_name\n",
+ "FROM\n",
+ " students AS s\n",
+ "LEFT OUTER JOIN\n",
+ " courses AS c\n",
+ "ON\n",
+ " s.student_no = c.student_no\n",
+ "WHERE c.student_no is NULL\n",
+ "\n",
+ "\n",
+ "| student_no | stud_name | age | course_name |\n",
+ "|-----------:|----------:|----:|------------:|\n",
+ "| 777 | Heidy | 30 | None |\n",
+ "\n",
+ "Ahaa..! looks like \"heidy\" is not taking any courses; we need to check with her to see why she is not taking any courses :)\n",
+ "\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "12fa7b6e-fb81-41e9-8fa7-982fcee3dba8",
+ "metadata": {},
+ "source": [
+ "## 🏁 Finish line 🏁 🍺\n",
+ "\n",
+ "Are you able to recollect our 3 checkpoints?\n",
+ "\n",
+ "- ***Checkpoint 1:*** Take some time and think if you can...\n",
+ " - Select columns and bring in rows just what we needed (using SELECT & WHERE)\n",
+ " - Return DISTINCT elements\n",
+ " - ORDER BY rows returned based on column(s)\n",
+ " - LIMIT the number of rows returned\n",
+ "- ***Checkpoint 2:*** Take some time and think if you can...\n",
+ " - Do some aggregations and grouping queries using SQL.\n",
+ " - GROUP BY\n",
+ " - aggregation functions\n",
+ " - HAVING\n",
+ "- ***Checkpoint 3:*** Take some time and think if you can...\n",
+ " - Understand different kinds of joins\n",
+ " - When to use joins\n",
+ " - Write SQL queries to join tables"
+ ]
+ }
+ ],
+ "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.8.12"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/logo.png b/logo.png
new file mode 100644
index 0000000..bb9603c
Binary files /dev/null and b/logo.png differ
diff --git a/references.bib b/references.bib
new file mode 100644
index 0000000..783ec6a
--- /dev/null
+++ b/references.bib
@@ -0,0 +1,56 @@
+---
+---
+
+@inproceedings{holdgraf_evidence_2014,
+ address = {Brisbane, Australia, Australia},
+ title = {Evidence for {Predictive} {Coding} in {Human} {Auditory} {Cortex}},
+ booktitle = {International {Conference} on {Cognitive} {Neuroscience}},
+ publisher = {Frontiers in Neuroscience},
+ author = {Holdgraf, Christopher Ramsay and de Heer, Wendy and Pasley, Brian N. and Knight, Robert T.},
+ year = {2014}
+}
+
+@article{holdgraf_rapid_2016,
+ title = {Rapid tuning shifts in human auditory cortex enhance speech intelligibility},
+ volume = {7},
+ issn = {2041-1723},
+ url = {http://www.nature.com/doifinder/10.1038/ncomms13654},
+ doi = {10.1038/ncomms13654},
+ number = {May},
+ journal = {Nature Communications},
+ author = {Holdgraf, Christopher Ramsay and de Heer, Wendy and Pasley, Brian N. and Rieger, Jochem W. and Crone, Nathan and Lin, Jack J. and Knight, Robert T. and Theunissen, Frédéric E.},
+ year = {2016},
+ pages = {13654},
+ file = {Holdgraf et al. - 2016 - Rapid tuning shifts in human auditory cortex enhance speech intelligibility.pdf:C\:\\Users\\chold\\Zotero\\storage\\MDQP3JWE\\Holdgraf et al. - 2016 - Rapid tuning shifts in human auditory cortex enhance speech intelligibility.pdf:application/pdf}
+}
+
+@inproceedings{holdgraf_portable_2017,
+ title = {Portable learning environments for hands-on computational instruction using container-and cloud-based technology to teach data science},
+ volume = {Part F1287},
+ isbn = {978-1-4503-5272-7},
+ doi = {10.1145/3093338.3093370},
+ abstract = {© 2017 ACM. There is an increasing interest in learning outside of the traditional classroom setting. This is especially true for topics covering computational tools and data science, as both are challenging to incorporate in the standard curriculum. These atypical learning environments offer new opportunities for teaching, particularly when it comes to combining conceptual knowledge with hands-on experience/expertise with methods and skills. Advances in cloud computing and containerized environments provide an attractive opportunity to improve the effciency and ease with which students can learn. This manuscript details recent advances towards using commonly-Available cloud computing services and advanced cyberinfrastructure support for improving the learning experience in bootcamp-style events. We cover the benets (and challenges) of using a server hosted remotely instead of relying on student laptops, discuss the technology that was used in order to make this possible, and give suggestions for how others could implement and improve upon this model for pedagogy and reproducibility.},
+ booktitle = {{ACM} {International} {Conference} {Proceeding} {Series}},
+ author = {Holdgraf, Christopher Ramsay and Culich, A. and Rokem, A. and Deniz, F. and Alegro, M. and Ushizima, D.},
+ year = {2017},
+ keywords = {Teaching, Bootcamps, Cloud computing, Data science, Docker, Pedagogy}
+}
+
+@article{holdgraf_encoding_2017,
+ title = {Encoding and decoding models in cognitive electrophysiology},
+ volume = {11},
+ issn = {16625137},
+ doi = {10.3389/fnsys.2017.00061},
+ abstract = {© 2017 Holdgraf, Rieger, Micheli, Martin, Knight and Theunissen. Cognitive neuroscience has seen rapid growth in the size and complexity of data recorded from the human brain as well as in the computational tools available to analyze this data. This data explosion has resulted in an increased use of multivariate, model-based methods for asking neuroscience questions, allowing scientists to investigate multiple hypotheses with a single dataset, to use complex, time-varying stimuli, and to study the human brain under more naturalistic conditions. These tools come in the form of “Encoding” models, in which stimulus features are used to model brain activity, and “Decoding” models, in which neural features are used to generated a stimulus output. Here we review the current state of encoding and decoding models in cognitive electrophysiology and provide a practical guide toward conducting experiments and analyses in this emerging field. Our examples focus on using linear models in the study of human language and audition. We show how to calculate auditory receptive fields from natural sounds as well as how to decode neural recordings to predict speech. The paper aims to be a useful tutorial to these approaches, and a practical introduction to using machine learning and applied statistics to build models of neural activity. The data analytic approaches we discuss may also be applied to other sensory modalities, motor systems, and cognitive systems, and we cover some examples in these areas. In addition, a collection of Jupyter notebooks is publicly available as a complement to the material covered in this paper, providing code examples and tutorials for predictive modeling in python. The aimis to provide a practical understanding of predictivemodeling of human brain data and to propose best-practices in conducting these analyses.},
+ journal = {Frontiers in Systems Neuroscience},
+ author = {Holdgraf, Christopher Ramsay and Rieger, J.W. and Micheli, C. and Martin, S. and Knight, R.T. and Theunissen, F.E.},
+ year = {2017},
+ keywords = {Decoding models, Encoding models, Electrocorticography (ECoG), Electrophysiology/evoked potentials, Machine learning applied to neuroscience, Natural stimuli, Predictive modeling, Tutorials}
+}
+
+@book{ruby,
+ title = {The Ruby Programming Language},
+ author = {Flanagan, David and Matsumoto, Yukihiro},
+ year = {2008},
+ publisher = {O'Reilly Media}
+}
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..0a9f3f2
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,9 @@
+jupyter-book
+ghp-import
+sphinx-thebe
+pandas
+plotly
+matplotlib
+numpy
+ipython-sql
+pgspecial
\ No newline at end of file