From 4ff019885a288574d6c2082b718c1432ae280840 Mon Sep 17 00:00:00 2001 From: Andrew Pruski Date: Mon, 27 Feb 2023 10:10:42 +0000 Subject: [PATCH] Day 64 - Querying data in databases --- 2023/day64.md | 290 ++++++++++++++++++++++++++++++++++++++++ 2023/images/day64-1.png | Bin 0 -> 19071 bytes 2 files changed, 290 insertions(+) create mode 100644 2023/images/day64-1.png diff --git a/2023/day64.md b/2023/day64.md index e69de29..3bbf028 100644 --- a/2023/day64.md +++ b/2023/day64.md @@ -0,0 +1,290 @@ +# Querying data in databases + +Hello and welcome to the second post in the database part of the 90 Days of DevOps blog series! + +In this post we will be going through spinning up an instance of PostgreSQL in a docker container, retrieving data, and then updating that data. + +So let’s get started! + +
+ +# Software needed + +To follow along with the scripts in this blog post, you will need docker installed and pgAdmin. + +Both are completely free and can be downloaded here: - + +Docker - https://www.docker.com/products/docker-desktop/
+pgAdmin - https://www.pgadmin.org/ + +
+ +# Running PostgreSQL + +We have created a custom PostgreSQL docker image which has a demo database ready to go. + +In order to run the container, open a terminal and execute: - + + docker run -d \ + --publish 5432:5432 \ + --env POSTGRES_PASSWORD=Testing1122 \ + --name demo-container \ + ghcr.io/dbafromthecold/demo-postgres:latest + +This will pull the image down from our github repository and spin up an instance of PostgreSQL with a database, dvdrental, ready to go. + +Note - the image size is 437MB which may or may not be an issue depending on your internet connection + +Confirm the container is up and running with: - + + docker container ls + +Then open pgAdmin and connect with the server name as *localhost* and the password as *Testing1122* + +
+ +# Selecting data + +Once you’ve connected to the instance of PostgreSQL running in the container, let’s look at the staff table in the dvdrental database. Right click on the dvdrental database in the left-hand menu and select Query Tool. + +To retrieve data from a table we use a SELECT statement. The structure of a SELECT statement is this: - + + + SELECT data_we_want_to_retrieve + FROM table + WHERE some_condition + + +So to retrieve all the data from the staff table, we would run: - + + SELECT * + FROM staff + +The * indicates we want to retrieve all the columns from the table. + +If we wanted to only retrieve staff members called “Mike” we would run: - + + SELECT * + FROM staff + WHERE first_name = ‘Mike’ + +OK, now let’s look at joining two tables together in the SELECT statement. + +Here is the relationship between the staff and address tables: - + +![](images/day64-1.png) + +From the Entity Relational Diagram (ERD), which is a method of displaying tables in a relational database, we can see that the tables are joined on the address_id column. + +The address_id column is a primary key in the address table and a foreign key in the staff table. + +We can also see (by looking at the join) that this is a many-to-one relationship…aka rows in the address table can be linked to more than one row in the staff table. + +Makes sense as more than one member of staff could have the same address. + +Ok, in order to retrieve data from both the staff and address tables we join them in our SELECT statement: - + + SELECT * + FROM staff s + INNER JOIN address a ON s.address_id = a.address_id + +That will retrieve all the rows from the staff table and also all the corresponding rows from the address table…aka we have retrieved all staff members and their addresses. + +Let’s limit the query a little. Let’s just retrieve some data from the staff table and some from the address table for one staff member + + SELECT s.first_name, s.last_name, a.address, a.district, a.phone + FROM staff s + INNER JOIN address a ON s.address_id = a.address_id + WHERE first_name = ‘Mike’ + +Here we have only retrieved the name of any staff member called Mike and their address. + +You may have noticed that when joining the address table to the staff table we used an INNER JOIN. + +This is a type of join that specifies only to retrieve rows in the staff table that has a corresponding row in the address table. + +The other types of joins are: - + +LEFT OUTER JOIN - this would retrieve data in the staff table even if there was no row in the address table + +RIGHT OUTER JOIN - this would retrieve data in the address table even if there was no row in the staff table + +FULL OUTER JOIN - this would retrieve all data from the tables even if there was no corresponding matching row in the other table + +If we run: - + + SELECT * + FROM staff s + RIGHT OUTER JOIN address a ON s.address_id = a.address_id + +We will get all the rows in the address table that do not have a corresponding row in the staff table. + +But, if we run: - + + SELECT * + FROM staff s + LEFT OUTER JOIN address a ON s.address_id = a.address_id + +We will still only get records in the staff table that have a record in the address table. + +
+ +# Inserting data + +This is due to the FOREIGN KEY constraint linking the two tables, if we tried to insert a row into the staff table and the address_id we specified did not exist in the address table we would get an error: - + + ERROR: insert or update on table "staff" violates foreign key constraint "staff_address_id_fkey" + +This is because the foreign key is saying that a record in the staff table must reference a valid row in the address table. + +Enforcing this relationship is enforcing the referential integrity of the database…i.e. - maintaining consistent and valid relationships between the data in the tables. + +So we have to add a row to the staff table that references an existing row in the address table. + +So an example of this would be: - + + INSERT INTO staff( + staff_id, first_name, last_name, address_id, + email, store_id, active, username, password, last_update, picture) + VALUES + (999, 'Andrew', 'Pruski', 1, 'andrew.pruski@90daysofdevops.com', + '2', 'T', 'apruski', 'Testing1122', CURRENT_DATE, ''); + +Notice that we specify all the columns in the table and then the corresponding values. + +To verify that the row has been inserted: - + + SELECT s.first_name, s.last_name, a.address, a.district, a.phone + FROM staff s + INNER JOIN address a ON s.address_id = a.address_id + WHERE first_name = 'Andrew' + +And there is our inserted row! + +
+ +# Updating data + +To update a row in a table we use a statement in the format: - + + UPDATE table + SET column = new_value + WHERE some_condition + +OK, now let’s update the row that we inserted previously. Say the staff member’s email address has changed. To view the current email address: - + + SELECT s.first_name, s.last_name, s.email + FROM staff s + WHERE first_name = 'Andrew' + +And we want to change that email value to ‘andrewxpruski@outlook.com’. To update that value: - + + UPDATE staff + SET email = 'apruski@90daysofdevops.com' + WHERE first_name = 'Andrew' + +You should see that one row has been updated in the output. To confirm run the SELECT statement again: - + + SELECT s.first_name, s.last_name, s.email + FROM staff s + WHERE first_name = 'Andrew' + +
+ +# Deleting data + +To delete a row from a table we use a statement in the format: - + + DELETE FROM table + WHERE some_condition + +So to delete the row we inserted and updated previously, we can run: - + + DELETE FROM staff + WHERE first_name = ‘Andrew’ + +You should see that one row was deleted in the output. To confirm: - + + SELECT s.first_name, s.last_name, s.email + FROM staff s + WHERE first_name = 'Andrew' + +No rows should be returned. + +
+ +# Creating tables + +Let’s have a look at the definition of the staff table. This can be scripted out by right-clicking on the table, then Scripts > CREATE Script + +This will open a new query window and show the statement to create the table. + +Each column will be listed with the following properties: - + + Name - Data type - Constraints + +If we look at the address_id column we can see: - + + address_id smallint NOT NULL + +So we have the column name, that it is a smallint data type (https://www.postgresql.org/docs/9.1/datatype-numeric.html), and that it cannot be null. It cannot be null as a FOREIGN KEY constraint is going to be created to link to the address table. + +The columns that are a character datatype also have a COLLATE property. Collations specify case, sorting rules, and accent sensitivity properties. Each character datatype here is using the default setting, more information on collations can be found here: - +https://www.postgresql.org/docs/current/collation.html + +Other columns also have default values specified…such as the last_update column: - + + last_update timestamp without time zone NOT NULL DEFAULT 'now()' + +This says that if no value is set for the column when a row is inserted, the current time will be used. + +Then we have a couple of constraints defined on the table. Firstly a primary key: - + + CONSTRAINT staff_pkey PRIMARY KEY (staff_id) + +A primary key is a unique identifier in a table, aka this row can be used to identify individual rows in the table. The primary key on the address table, address_id, is used as a foreign key in the staff table to link a staff member to an address. + +The foreign key is also defined in the CREATE TABLE statement: - + + CONSTRAINT staff_address_id_fkey FOREIGN KEY (address_id) + REFERENCES public.address (address_id) MATCH SIMPLE + ON UPDATE CASCADE + ON DELETE RESTRICT + +We can see here that the address_id column in the staff table references the address_id column in the address table. + +The ON UPDATE CASCADE means that if the address_id in the address table is updated, any rows in the staff table referencing it will also be updated. (Note - it’s very rare that you would update a primary key value in a table, I’m including this here as it’s in the CREATE TABLE statement). + +The ON DELETE RESTRICT prevents the deletion of any rows in the address table that are referenced in the staff table. This prevents rows in the staff table having references to the rows in the address table that are no longer there…protecting the integrity of the data. + +OK so let’s create our own table and import some data into it: - + + CREATE TABLE test_table ( + id smallint, + first_name VARCHAR(50), + last_name VARCHAR(50), + dob DATE, + email VARCHAR(255), + CONSTRAINT test_table_pkey PRIMARY KEY (id) + ) + +NOTE - VARCHAR is an alias for CHARACTER VARYING which we saw when we scripted out the staff table + +Ok, so we have a test_table with 6 columns and a primary key (the id column). + +Let’s go and import some data into it. The docker image that we are using has a test_data.csv file in the /dvdrental directory and we can import that data with: - + + COPY test_table(id,first_name, last_name, dob, email) + FROM '/dvdrental/test_data.csv' + DELIMITER ',' + CSV HEADER; + +To verify: - + + SELECT * FROM test_table + +So that’s how to retrieve, update, and delete data from a database. We also looked at creating tables and importing data. + +Join us tommorrow where we will be looking at backing up and restoring databases. + +Thank you for reading! diff --git a/2023/images/day64-1.png b/2023/images/day64-1.png new file mode 100644 index 0000000000000000000000000000000000000000..dde8230e080c46a7051f0d0bf6e310834f9ff982 GIT binary patch literal 19071 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 literal 0 HcmV?d00001