Simple explanation of how grouping works in SQL

To get a general idea of how grouping works in SQL, let's create a small table quickly and then use group function to see what happens.


We have now created fruits table with some entries in it.

This is how this table looks like...


We have intentionally added some Null entry in the column to see how they are going to be grouped. 

Data provided to us often includes null values too and let's see how grouping affects on these values.


GROUP BY command is going to let us know how many items in the column for which groups are formed. So there are 4 groups in the column. Orange, Mango, Apple and Null

If we further add COUNT function to see how many items in each group...



Now we can clearly see how many records in each group. 

It is important to note that Null is considered as a group and it has 3 records.

How to create new table in PostGre SQL

 First, let us create a new database with the name walmart.

We are trying create a department table for walmart as it has many departments and each department is under a division.


On the next screen, we give it its name walmart and save it.


Now we can see that database got created



Next, we will create table under this database. From the dropdown menu on the top, selects tools and then select Query Tool


It will open new window where we can write SQL code. We are going to name this table as departments and we want 2 columns in it- department and division.

Another important thing to note is we want department column to be the primary key. There can be only 1 primary key and it acts as a constraints for the column. It prevents any duplicate or null values in the column as it is unique key.


The confirmation indicates that table was created successfully. Note that we want to limit the number of characters that can be entered in each entry of the table is 100 and that's why using datatype varchar, we have used 100 in the bracket. If you know that the entries are going to be really small then you can reduce it to 50 if need be.

A good practice is to keep some margin for future purpose. May be the list you have right now  is showing all small department names but in future if a new department with longer name needs to be created then you should have enough room in your column to allow it.




And we can see under walmart database schema, that the table with the name departments is there now.



At the moment we have created empty table without any entry in any of its rows. Something like this...


Next, we will add data in these columns. We have already prepared data to be entered in this table. 


We will always confirm after executing the query that it was successful.

And if we want to see how departments table looks like, we can check that now.



Using this we can create as many tables we need under this database.

This is a manual process and hence it is recommended only when table to be created is small.

Many times, we are already given .csv or excel file to upload it on SQL server. How to upload that?
We will take one example of that in the next article.





How to build a complex query in SQL

 

Dataset name: warehouse_orders

Table names: orders, warehouse

I have uploaded these tables in to bigquery and here are contents of these tables

Order table:



warehouse table:


As we can see, warehouse_id is a common column in both tables.

Orders table gives information about the orders fulfilled and warehouse table gives information about warehouse parameters.

Our goal is to calculate how much % of orders are being fulfilled by each warehouse.

Our expected end result should be something like this...

% orders fulfilled = number of orders per warehouse/total orders X 100

  • No of orders fulfilled can be obtained using COUNT function from orders table
  • Total number of orders can be obtained from orders table using SUM function
  • Warehouse name can be obtained from warehouse table (it is already available in column name warehouse_alias
  • Warehouse_id is in both tables so we can join both tables using this column

Let us join tables in the first query so we get warehouse_id and warehouse name columns for our end result

SELECT 

warehouse.warehouse_id,

warehouse.warehouse_alias

FROM 

`my-sql-project-336018.warehouse_orders.warehouse` AS warehouse

LEFT JOIN 

`my-sql-project-336018.warehouse_orders.orders` AS orders

ON 

warehouse.warehouse_id = orders.warehouse_id

I am joining tables with LEFT JOIN...why?

Because I want to ensure that all warehouse_id in the warehouse table must be there and the corresponding warehouse_id data in orders table should be there.

Which means, if there are warehouse_id in orders table but not in warehouse table, they are not required for our task.

Also always check that subquery in itself should run independently because we are going to use its result in the main query. We are getting below result when we run this subquery.



Next, let us find out number of orders by using COUNT function in orders table.

COUNT(orders.order_id) AS number_of_orders_fulfilled

And since using aggregation, add GROUP BY function too...


SELECT 
warehouse.warehouse_id,
warehouse.warehouse_alias,
COUNT(orders.order_id) AS number_of_orders_fulfilled
FROM 
`my-sql-project-336018.warehouse_orders.warehouse` AS warehouse
LEFT JOIN 
`my-sql-project-336018.warehouse_orders.orders` AS orders
ON 
warehouse.warehouse_id = orders.warehouse_id
GROUP BY warehouse.warehouse_id, warehouse.warehouse_alias

This is the result we got so far...


Some warehouse/s has zero fulfilled orders. So when we checked the details of the data, it says some warehouses are newly built and are not in operation yet. So we will have to remove those from the list.

In order to get % of orders fulfilled, we still need total orders fulfilled.
If we run an independent query for that, it would be something like this...

SELECT 
COUNT(*) as total_orders_fulfilled
FROM 
`my-sql-project-336018.warehouse_orders.orders` 
And the result of this query is...

Now adding this into our main query...

SELECT 
warehouse.warehouse_id,
warehouse.warehouse_alias,
COUNT(orders.order_id) AS number_of_orders_fulfilled,
ROUND(COUNT(orders.order_id)/(SELECT COUNT(*) FROM warehouse_orders.orders)*100,2) AS percent_order_fulfilled
FROM
 `my-sql-project-336018.warehouse_orders.warehouse` AS warehouse
LEFT JOIN 
`my-sql-project-336018.warehouse_orders.orders` AS orders
ON 
warehouse.warehouse_id = orders.warehouse_id
GROUP BY warehouse.warehouse_id, warehouse.warehouse_alias


The end result we are getting is...


If we calculate a couple of samples to make sure the result is right, out of total 9999 orders, when 548 are fulfilled, it is 5.48% of fulfillment.

This is good but we want to do some cosmetic changes to this query so it looks more presentable.

First, we want to remove warehouses with zero fulfillment which we will do using HAVING function.

Second, warehouse_alias column gives only name of the warehouse but does not indicate where this warehouse is located. So, using CONCAT function, we can have warehouse name and state name together.

Third, instead of giving exact fulfillment percentages, how about giving it in a range?
For example, we can say 0 to 20% , 20 to 60% and above 60% fulfillment
We can do this using CASE statement

Below is the CONCAT function we are going to use so we get 
State:Warehouse_alias format and we will call that column as warehouse_name

CONCAT(warehouse.state,':',warehouse.warehouse_alias) AS warehouse_name

And, to remove warehouses with zero orders,

HAVING COUNT(orders.order_id) > 0

Now result looks like this...


This looks much better. We will still try one last change here by adding fulfillment ranges from 0 to 20 to 60 using CASE statement...something like this...and we will call this column "fulfillment_summary"

CASE 
WHEN COUNT(orders.order_id)/(SELECT COUNT(*) FROM my-sql-project-336018.warehouse_orders.orders AS orders) <= 0.20
THEN "Fulfilled 0-20% of orders"
WHEN COUNT(orders.order_id)/(SELECT COUNT(*) FROM my-sql-project-336018.warehouse_orders.orders AS orders) > 0.20 AND
COUNT(orders.order_id)/(SELECT COUNT(*) FROM my-sql-project-336018.warehouse_orders.orders AS orders) <= 0.60
THEN "Fulfilled 21-60% or orders"
ELSE 
"Fulfilled more than 60% of orders"
END AS fulfillment_summary

So the full query looks like this...

SELECT 
warehouse.warehouse_id,
CONCAT(warehouse.state,':',warehouse.warehouse_alias) AS warehouse_name,
COUNT(orders.order_id) AS total_orders_fulfilled,
CASE 
WHEN COUNT(orders.order_id)/(SELECT COUNT(*) FROM my-sql-project-336018.warehouse_orders.orders AS orders) <= 0.20
THEN "Fulfilled 0-20% of orders"
WHEN COUNT(orders.order_id)/(SELECT COUNT(*) FROM my-sql-project-336018.warehouse_orders.orders AS orders) > 0.20 AND
COUNT(orders.order_id)/(SELECT COUNT(*) FROM my-sql-project-336018.warehouse_orders.orders AS orders) <= 0.60
THEN "Fulfilled 21-60% or orders"
ELSE 
"Fulfilled more than 60% of orders"
END AS fulfillment_summary
FROM 
`my-sql-project-336018.warehouse_orders.warehouse` AS warehouse
LEFT JOIN 
`my-sql-project-336018.warehouse_orders.orders` AS orders
ON 
warehouse.warehouse_id = orders.warehouse_id
GROUP BY warehouse.warehouse_id, warehouse_name
HAVING COUNT(orders.order_id) > 0
ORDER BY fulfillment_summary DESC

And our final result looks like this...





How to use SUMPRODUCT function in EXCEL

 SUMPRODUCT can save us lot of time as it does calculation in multiple ways.

We are given data for Quantity, Price and Margin and we want to find out the total revenue and total profit 


Total revenue = SUM(Quantity X Unit Price)

Company's total revenue is a sum of revenues from all products.

We can do this in EXCEL cell by cell using formula in each row. But SUMPRODUCT does it all together at once and hence saving time.


 SUMPRODUCT automatically does calculation of quantity X Price and then sum it up for all rows and gives us the answer. We just need to select range of data in each column - quantity and Unit Price

Same way we can also calculate profit in this data.


By selecting range in 3 columns- Quantity, Unit Price and Margin we can get the profit number quickly






How to use pivot table to identify insights in EXCEL

 We know that pivot tables are useful to segregate and group data in a meaningful way so we can identify insights and generate new questions and answers for the data.

Let's take an example to understand in a simple way how it is done

We are given a data for movies.


The table has various rows indicating different data for movies.

We are given some basic questions by stakeholders to start with the analysis.

  • How much revenue generated each year from movies?
  • What is the average revenue per movie each year?


That's a good direction for a start. Let's create a pivot table to find out answers.



Here is the data that we received using pivot table



We can immediately see some interesting things here.

2015 has the lowest average revenue per movie...why?

Now we will focus on 2015 data. 

It is evident that 2015 had released higher number of movies (124) than any other year. Hence an obvious thought would be that it reduced the average because of that.

But is it possible that it has higher number of movies with low box office collection?

We don't know yet so let's try to find that out.

I took $10 million as a round figure from  the box office revenue column to find out how many movies in out list had collection of less than $10 million...

Why I took $10 million limit for filtering data?

I can ask the stake holder to quantify what they consider as low revenue movie based on industry standard. Or (as I did in this case) I figured out that all movies less than $10 million revenue were breakeven for investors. 

Meaning, average budget for movies with revenue less than $10 million was approx. $10 million. So investors did not make any money if revenue was less than $10 million. 

That's why I took $10 million as a round figure to establish a movie as low revenue movie.

Using filter function in pivot table, I have filtered count of all movies with less than $10 million revenue.


Now with the support of data, I can say with confidence that 2015 had higher percentage (16.13%) of low revenue movies and that's why average revenue per movie was lowest in that year.




Example of a subquery in SQL (within FORM statement)

 There is a public dataset for New York citibike that I have used for this example.

Dataset

This dataset has 2 tables- One is named as citibike_stations and the other is named as citibike_trips

Our requirement is to get data for number of rides starting at each station so we know which is the busiest station for bike business and accordingly bike availability can be maintained.

We have something like this in mind for end result.


Station_id is a common column in both table but only citibike_trips has start_station_id column.

So here is the general idea for querying...

From citibike_trips table, get start_station_id and using COUNT function, get the number of trips for them.

Something like this...

SELECT 
    start_station_id, 
    start_station_name,
    COUNT(*) AS number_of_rides_starting_at_station
FROM 
    citibike_trips
GROUP BY start_station_id
ORDER BY number_of_rides_starting_at_station DESC

This query can also give us the result we desire.




BUT...

There are some stations mentioned on citibike_trips that are NOT
on citibike_stations and vice versa...

And, we want to take data for all stations that are common to both tables.

So to ensure we get data for all stations, we will do inner join of both tables.

Which means that the end result will have data for all stations common
in both tables.

SELECT 
station_id, 
name,
number_of_rides_starting_at_station 
FROM 
(SELECT start_station_id, COUNT(*) AS number_of_rides_starting_at_station
FROM 
citibike_trips
GROUP BY start_station_id) AS station_num_trips
INNER JOIN 
citibike_stations
ON 
station_id = start_station_id
ORDER BY 
number_of_rides_starting_at_station  DESC 

As you can see, our original query is now put within the brackets as
sub-query and given it an alias station_num_trips

And joined both tables using station_id column.

This is an example of sub-query within FROM statement.

How to write a simple SQL query

  There is  a public dataset on kaggle web site which gives you the citi bike usage in New York. Bike sharing company has 2 types of customers. One who are subscribers (most likely commuters who need to use bike everyday from one station to another) and customer(those who may rent the bike as and when needed).

Dataset

I am trying to find out what routes are most popular with different user types. I have this in mind...I would like to see end result in different columns.


This is what I have in mind to accomplish as end result



This is the query I wrote in SQL

SELECT 
        usertype,  
        CONCAT(start_station_name,"to",end_station_name) AS route,
        COUNT(*) AS num_trips,
        ROUND(AVG(CAST(tripduration AS INT64 )/60),2) AS duration       
FROM `bigquery-public-data.new_york.citibike_trips` 
WHERE tripduration IS NOT NULL
GROUP BY usertype,start_station_name,end_station_name
ORDER BY num_trips DESC
LIMIT 10



I am adding start and end station name using CONCAT function and calling the column as route

I am counting rows for all columns and calling the column as num_trips as they are showing me the frequency of trips

I found that tripduration column has numerical values but its data type is string so I am going to convert its data type from string to INT using CAST function. Also I am looking for average trip duration in minutes so using AVG function, I am dividing the result by 60 since data for trip duration is in seconds

And I am removing any rows where trip duration is not available using WHERE tripduration IS NOT NULL function

And finally I am grouping and ordering the result using GROUP BY and ORDER BY functions

This is a simple SQL query and results is below



How to clean data using SQL

 What are possible causes of bad data that we may have to take in to account so we can focus on cleaning effort to make the data sparkling clean...

  • Duplicates
  • Truncated data
  • Extra spaces and character
  • Null data/Missing data
  • Mistyped numbers
  • Inconsistent string
  • Inconsistent date format
  • Misleading column names
  • Mismatched data type
  • Misspelt words

______________________________________________

Using DISTINCT clean duplicates and inconsistent values

Let's say if a product has only 4 colours and you find out that there are 6 colours in the colour column in the product table

SELECT DISTINCT colour

FROM product

Or, let's say that if colour column has number from 0 to 9 that represents different colours of the product and you want to find out if there is no double digit colour, then use LEN function to find the length of the numbers in the column

SELECT LEN(colour)

FROM product

_____________________________________

Using IS NULL, find the null values from any column

SELECT first_name,last_name

FROM customer

WHERE last_name IS NULL

This query will give us all first names where last names are missing

______________________________________________

Using SUBSTR, find the odd value out

Using SELECT SUBSTR(city,1,3) FROM table_name we can select only first three characters from city column

______________________________________________

Using TRIM, remove unwanted spaces from a string in a column

Below action will remove all leading and trailing spaces from first_name and last_name columns in customer table

UPDATE customer

 SET

        first_name = TRIM(first_name)

        last_name = TRIM(last_name);


______________________________________________

Once you find the correct last name, you can replace it using UPDATE function

UPDATE customer

SET last_name = 'Smith'

WHERE first_name = 'Roger'

If there are more than 1 roger in the list then, you can use customer_id instead of first_name to make this correction

______________________________________________

Often, we are provided with description of the data where you can see max and min values (Range) in a column

Check by using MAX(column_name) and MIN(column_name) to ensure that data des not have incorrect values

For example, if we know that age column in the students table can have values between 10 and 12, then using MAX(age) and MIN(age) we can find if there are any incorrect values

______________________________________________

Once we know there are values less or higher than the expected range, using COUNT function, we can find out how many of these students exist in the list with incorrect age

SELECT COUNT  *  

FROM students

WHERE age = 13 AND age = 9

______________________________________________

If it appears that there is a significant number of incorrect data in a column, then consult data source for further guideline...should we correct it(find the correct data or replace incorrect values with average values in a column) or delete it from the table using DELETE function

DELETE students

WHERE age = 13 and age = 9

______________________________________________

If 'age' column has been assigned string data type but the column has numbers, then using CAST function, we can change the data type of column 'age'

SELECT CAST(age AS integer) 

FROM students

ORDER BY CAST(age AS integer)

______________________________________________

Some times we need to combine data in 2 columns in 1 column, then we can use CONCAT

For example, date and time are in separate column and we want t combine them together in 1 column. 

SELECT CONCAT (column1,column2) AS new_column_name

FROM table_name

______________________________________________

Using COALESCE, we can avoid null values in a column by returning non null values in a list

In product table, we have product code in one column and product name in another column and in one row, you have product name available but product code missing. And we have been told that product code is not available at the moment but we can't remove that row as it is an important product.




So whenever data is pulled for product code, we want to show product in the product_id column instead of null value

SELECT COALESCE(product_code,product_name) 

FROM product


______________________________________________

Using CASE statement, we can replace incorrect string with corrected string

SELECT customer_id,

CASE

WHEN first_name = 'Jonh' THEN 'John'

WHEN first_name = 'Rajeev' THEN 'Rajiv'

END AS corrected_name

FROM table_name

______________________________________________




Complex query example

Requirement:  You are given the table with titles of recipes from a cookbook and their page numbers. You are asked to represent how the reci...