Skip to content
This repository was archived by the owner on Oct 11, 2024. It is now read-only.

Commit 1e216d3

Browse files
committed
in progress...
1 parent a898afa commit 1e216d3

19 files changed

+326
-107
lines changed

05_create_table.sql

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,33 @@ SET ROLE TO demorole;
99

1010
CREATE TABLE banking_demo.branch(
1111
branch_id int NOT NULL PRIMARY KEY, -- using just "id" for name here is not recommended, the more explicit the better for important stuff
12-
balance int NOT NULL DEFAULT 0
12+
balance numeric NOT NULL DEFAULT 0
1313
);
1414

1515
CREATE TABLE banking_demo.teller(
1616
teller_id int NOT NULL PRIMARY KEY,
1717
branch_id int NOT NULL,
18-
balance int NOT NULL DEFAULT 0
18+
balance numeric NOT NULL DEFAULT 0
1919
);
2020

2121
CREATE TABLE banking_demo.account(
2222
account_id int NOT NULL PRIMARY KEY,
2323
branch_id int NOT NULL,
2424
teller_id int NOT NULL,
25-
balance int NOT NULL DEFAULT 0
25+
balance numeric NOT NULL DEFAULT 0
2626
);
2727

2828
CREATE TABLE banking_demo.transaction_history(
2929
teller_id int NOT NULL,
3030
branch_id int NOT NULL,
3131
account_id int NOT NULL,
32-
delta int NOT NULL,
32+
delta numeric NOT NULL,
3333
created_on timestamp with time zone NOT NULL DEFAULT now()
3434
);
3535

36-
36+
-- Generally it's also a good practice to at least minimally comment the tables and columns for complex applications
37+
COMMENT ON TABLE banking_demo.transaction_history IS 'A simple banking table';
38+
COMMENT ON COLUMN banking_demo.transaction_history.delta IS 'Change in account balance for one transaction';
3739

3840
-- generate 1 branch, 10 tellers for branch, 10K accounts for each teller with random balances
3941

06_create_table_options.sql

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,19 @@
11
/*
22
Other ways of creating tables are:
3-
1) using LIKE to use an existing table as a templates and selecting (or leaving out) some constraints/checks/indexes
3+
1) using LIKE to use existing tables as a templates and selecting (or leaving out) some constraints/checks/indexes
44
2) create table as select ...
5-
*/
6-
7-
-- create a copy of
8-
CREATE TABLE temp (LIKE t_demo EXCLUDING INDEXES);
9-
10-
-- could also do:
11-
-- create table t_demo_log as select * from t_demo where false;
125
13-
14-
/*
156
Other types of tables are:
16-
1) temporary tables
17-
2) "unlogged" tables
7+
1) temporary tables - auto-removed when session ends and visible only in that session that created them
8+
2) "unlogged" tables - such tables are not WAL-logged thus a lot faster to work with. Downside is that they're emptied after a crash.
189
*/
1910

20-
-- temporary tables are not persistent and visible only in that session that created them
21-
CREATE TEMP TABLE t (LIKE t_demo);
11+
-- create a temporary copy of banking_demo.teller
12+
-- NB! Note that you cannot specify a schema for temp tables
13+
CREATE TEMP TABLE teller_temp(LIKE banking_demo.teller EXCLUDING INDEXES);
2214

23-
-- unlogged tables are not WAL-logged (emptied after a crash) thus a lot faster to work with
24-
CREATE UNLOGGED TABLE t_data_staging (LIKE t_demo);
15+
-- could also "auto create" a table from select (no indexes, FKs, checks, etc are transferred)
16+
CREATE TEMP TABLE teller_temp_2 AS SELECT * FROM banking_demo.teller WHERE false;
2517

18+
-- unlogged tables are a good option for staging tables that get a lot of updates and can be re-initialized quickly from input data
19+
CREATE UNLOGGED TABLE banking_demo.staging_data AS SELECT * FROM banking_demo.account;

07_alter_table.sql

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,35 @@
11
/*
2-
Another significant performance tweaking option for tables is the FILLFACTOR parameter.
2+
ALTER TABLE is mostly commonly used to:
3+
1) add/drop/rename columns
4+
2) add/drop constraints (FKs, checks, not null)
5+
3) set column defaults
6+
4) change column data types
7+
5) add/drop triggers
8+
6) declare inheritance (used typically for partitioning which will be built-in from Postgres 10)
9+
7) changing physical storage parameters
10+
8) a lot more, see docus...
11+
*/
12+
13+
-- adding a new column
14+
-- note that here we also demonstrate that changing the structure can be performed transactionally!! (unlike in Oracle for example)
15+
BEGIN;
16+
ALTER TABLE account_unlogged ADD COLUMN extra_info TEXT;
17+
ALTER TABLE account_unlogged ALTER COLUMN extra_info SET DEFAULT 'hello';
18+
COMMIT;
19+
-- also note that for big tables on busy DBs the above 2-step form is much preferred over below form as it won't re-write the whole table:
20+
-- ALTER TABLE account_unlogged ADD COLUMN extra_info TEXT DEFAULT 'hello';
21+
22+
-- adding a simple check constraint
23+
ALTER TABLE account_unlogged ADD CONSTRAINT CHECK my_check CHECK (account_id > 0);
24+
25+
-- change column data type. NB! mostly it means a full table re-write so be wary.
26+
ALTER TABLE account_unlogged ALTER COLUMN extra_info TYPE varchar(500);
27+
28+
29+
/*
30+
A significant performance tweaking option for tables is the FILLFACTOR parameter.
331
It tells Postgres to fill up tables only to specified percentage, so that future row updates would
4-
have a chance to be performed "in line". Some "terms and conditions" apply but for certain usecases huge boosts are possible.
32+
have a chance to be performed "in line" (called HOT-updates). Some "terms and conditions" apply but for certain usecases (a lot of
33+
updates on un-indexed columns) huge boosts are possible. Fillfactor can be also specified similarily when creating the table.
534
*/
6-
ALTER TABLE
35+
ALTER TABLE account_unlogged SET (FILLFACTOR=80);

09_data_type_showcase.sql

Lines changed: 83 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,93 @@
1+
CREATE TABLE main_datatypes (
2+
/* serials aka sequences */
3+
id bigserial PRIMARY KEY, -- serial/bigserial corresponds to int4/int8 and will just auto-attach a DEFAULT sequence
4+
5+
/* numbers */
6+
smallish_integers int, -- int4 or integer,
7+
large_integers int8, -- for big-data i.e. > 2 billion rows
8+
floating_point double precision, -- for non-exact calculations. synonymous with "float8"
9+
exact_calculations numeric, -- for scientific/financial calculations. synonymous with "decimal"
110

11+
/* character data */
12+
text_data1 text, -- for text data it's generally recommended to just use the "text" type
13+
text_data2 varchar(200), -- when limiting input needs to be enforced use varchar(X)
14+
text_fixed char(3), -- for fixed size data like currency codes
15+
json_data jsonb, -- efficiently stored and indexable JSON texts, meant for NoSQL use cases
216

17+
/* temporals */
18+
event_ts timestamptz, -- timestamptz = "timestamp with time zone" and should always be used instead of simple "timestamp"
19+
event_date date, -- '2017-08-15'
20+
event_time time, -- '17:35'
21+
event_duration interval, -- '1day', '2years 1month', '3h 0m 10s'
22+
23+
is_active boolean, -- boolean input can be specified as true/false (case insensitive), t/f, on/off
24+
25+
/* typical auditing fields to track changes of important data*/
26+
created_by text NOT NULL DEFAULT current_user,
27+
created_on timestamptz NOT NULL DEFAULT now(),
28+
last_modified_by text -- would need a trigger to ensure it's always updated when changing data
29+
last_modified_on timestamptz, -- would need a trigger to ensure it's always updated when changing data
30+
);
331

432

533

6-
CREATE TABLE t_demo (
7-
id serial PRIMARY KEY,
8-
data jsonb,
9-
department text NOT NULL,
10-
created_by text NOT NULL DEFAULT current_user,
11-
created_on timestamptz NOT NULL DEFAULT now(),
12-
last_modified_on timestamptz
13-
);
34+
/* Numerals */
35+
36+
INSERT INTO main_datatypes(large_integers)
37+
SELECT 1e11; --100 billion
38+
39+
INSERT INTO main_datatypes(floating_point)
40+
SELECT 3.14;
41+
42+
/* character data */
43+
44+
INSERT INTO main_datatypes(text_data1) -- take only first 10 chars of 300
45+
SELECT repeat('a', 300)::char(10);
46+
47+
--INSERT INTO main_datatypes(text_data2) -- this will fail as text_data2 allows max 200 chars
48+
-- SELECT repeat('a', 300);
49+
50+
INSERT INTO main_datatypes (json_data)
51+
VALUES ('{"user_id": 1, "order_items": [{"item_id":3, "code": "EAS123"}], "created_on": "2017-08-15 11:45:28.852685+03"}');
52+
53+
54+
55+
/*
56+
Temporals - postgres has excellent support for working with times. Here some most used functions.
57+
*/
58+
59+
-- current timestamptz as of beginning of transaction, thus we'll see the same values
60+
BEGIN;
61+
SELECT now();
62+
SELECT now();
63+
END;
64+
65+
-- same
66+
SELECT 'now'::timestamptz;
67+
68+
-- current timestamptz in real time, different values
69+
BEGIN;
70+
SELECT clock_timestamp();
71+
SELECT clock_timestamp();
72+
END;
1473

15-
COMMENT ON TABLE t_demo IS 'a simple table';
16-
COMMENT ON COLUMN t_demo.data IS 'JSONB is designed for NoSQL';
74+
-- current date
75+
SELECT 'today'::date;
76+
SELECT current_date;
77+
SELECT now()::date;
1778

18-
INSERT INTO t_demo (data, department)
19-
VALUES ('{"user_id": 1, "order_items": [{"item_id":3, "code": "EAS123"}]}', 'sales');
79+
-- using interval to get exact date of 90 days ago
80+
SELECT current_date - '90d'::interval;
2081

21-
-- index top level keys for a simple NoSQL use case.
22-
CREATE INDEX CONCURRENTLY ON t_demo USING gin (data);
82+
-- current UNIX epoch seconds
83+
SELECT extract(epoch FROM now());
2384

24-
-- index everything
25-
CREATE INDEX ON t_demo USING gin (data jsonb_path_ops);
85+
-- generate all dates for the last week
86+
SELECT generate_series(date_trunc('week', now() - '1 week'::interval),
87+
date_trunc('week', now()) - '1day'::interval, '1day');
2688

27-
-- Prepare for frequent changes, increase FILLFACTOR
28-
ALTER TABLE t_demo SET (fillfactor=80);
89+
-- same as above but with ORDINALITY
90+
SELECT
91+
*
92+
FROM generate_series(date_trunc('week', now() - '1 week'::interval), date_trunc('week', now()) - '1day'::interval, '1day')
93+
WITH ORDINALITY t(date, day_of_week);

10_views.sql

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
1-
CREATE VIEW v_simple AS
2-
SELECT * FROM t_demo;
1+
/*
2+
Views allow application layering and can be also used for securing data. They behave the same as tables for GRANT privileges and
3+
don't normally incur any performance hit when selecting.
4+
*/
35

4-
-- NB! simple views (selecting from one table basically) allow also inserting so "check option" might make sense,
6+
-- the simplest view possible
7+
CREATE VIEW v_account_balance AS
8+
SELECT account_id, balance FROM account;
9+
10+
GRANT SELECT ON v_account_balance TO public;
11+
12+
13+
-- NB! simple views (selecting from one table basically) allow also inserting, so "check option" might make sense,
514
-- it avoids inserting data that a user would not be able to see due to WHERE condition
6-
CREATE VIEW v_data_for_sales_dept AS
7-
SELECT * FROM t_demo
8-
WHERE department = 'sales'
15+
CREATE VIEW v_tellers_for_branch_one AS
16+
SELECT * FROM teller
17+
WHERE branch_id = 1
918
WITH CHECK OPTION;

15_materialized_views.sql

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
-- Materialized view is a "point in time" copy of the select statement and needs explicit refreshing
22
-- In other aspects the mat.view acts like a normal table - one can create indexes on it
33

4-
CREATE MATERIALIZED VIEW mv_last_month_data AS
5-
SELECT * FROM t_demo
4+
CREATE MATERIALIZED VIEW mv_last_months_transactions AS
5+
SELECT * FROM transaction_history
66
WHERE created_on > current_date - '1month'::interval;
77

8+
-- refresh the data (blocks selects)
9+
REFRESH MATERIALIZED VIEW mv_last_month_data;
10+
811
-- refresh concurrently needs at least one unique index to "merge" changes effectively
912
CREATE UNIQUE INDEX ON mv_last_month_data (id);
10-
1113
-- "concurrent" refresh allows other sessions to read the view during the update but is slower
1214
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_last_month_data;

20_stored_procedures.sql

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,63 @@
1-
-- in PL/pgSQL there's no distinction between a function (restricted to returning a single scalar value in Oracle PL/SQL) and a procedure -
2-
-- all stored procedural code are 'functions' that can return anything (scalars, multiple values, rows of multiple values or table types) or nothing.
1+
/*
2+
PL/pgSQL is a specialized programming language that allows to query data and provides common features like variables, comparisons,
3+
branching (if/else), error handling, calling other functions.
34
4-
CREATE OR REPLACE FUNCTION f1_returns_text() RETURNS text as
5+
Stored procedures in PL/pgSQL are beneficial as they provide layering for the "data logic" (one can do modifications
6+
without applications knowing about it) and usually also improve performance for multi-step data processing scenarios.
7+
8+
In PL/pgSQL there's no distinction between a function (restricted to returning a single scalar value in Oracle PL/SQL) and a procedure,
9+
all stored procedural code are 'functions' that can return anything (scalars, multiple values, rows of multiple values or table types)
10+
or nothing.
11+
12+
NB! In addition to PL/pgSQL there are dozens of other PL-languages available for installation for more complex data processing needs -
13+
plpython, plperl, pljava to name a few.
14+
*/
15+
16+
-- a simple sproc returning a scalar
17+
CREATE OR REPLACE FUNCTION say_hello() RETURNS text as
518
$SQL$
619
BEGIN
7-
RETURN 'demo';
20+
RETURN 'Hello! Can you hear me?';
821
END;
922
$SQL$
1023
LANGUAGE plpgsql;
1124

1225

13-
-- stored procedures support error handling and subtransactions via BEGIN/EXCEPTION/END block
26+
-- note that above functions we could (and should) write in pure SQL as this has some performance benefits
27+
CREATE OR REPLACE FUNCTION say_hello_2() RETURNS text as
28+
$SQL$
29+
SELECT 'Hello! Can you hear me?'::text;
30+
$SQL$
31+
LANGUAGE sql;
32+
1433

15-
CREATE OR REPLACE FUNCTION merge_db(key INT, data TEXT) RETURNS VOID AS
16-
$$
34+
-- a stored procedure to performa a "bank transfer" for our banking schema
35+
CREATE OR REPLACE FUNCTION perform_transaction(
36+
account_from account.account_id%TYPE,
37+
account_to account.account_id%TYPE,
38+
amount_to_transfer account.balance%TYPE)
39+
RETURNS VOID AS
40+
$SQL$
41+
DECLARE
42+
acccount_balance account.balance%TYPE;
1743
BEGIN
18-
LOOP
19-
-- first try to update the key
20-
UPDATE db SET b = data WHERE a = key;
21-
IF found THEN
22-
RETURN;
23-
END IF;
24-
-- not there, so try to insert the key
25-
-- if someone else inserts the same key concurrently,
26-
-- we could get a unique-key failure
27-
BEGIN
28-
INSERT INTO db(a,b) VALUES (key, data);
29-
RETURN;
30-
EXCEPTION WHEN unique_violation THEN
31-
-- Do nothing, and loop to try the UPDATE again.
32-
END;
33-
END LOOP;
44+
45+
SELECT balance INTO acccount_balance FROM account WHERE account_id = account_from;
46+
47+
IF NOT FOUND THEN
48+
RAISE EXCEPTION 'Account % not found', account_from;
49+
END IF;
50+
51+
IF acccount_balance < amount_to_transfer THEN
52+
RAISE EXCEPTION 'Not enough funds';
53+
END IF;
54+
55+
UPDATE account SET balance = balance + amount_to_transfer WHERE account_id = account_to; -- NB! Very simplistic approch, lot of problems possible here
56+
UPDATE account SET balance = balance - amount_to_transfer WHERE account_id = account_from;
57+
3458
END;
35-
$$
36-
LANGUAGE plpgsql;
59+
$SQL$
60+
LANGUAGE plpgsql;
61+
62+
63+
-- TODO error handling and subtransactions via BEGIN/EXCEPTION/END block

25_table_using_sproc.sql

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
-- setting a function as a default value
2-
-- dropping the function with "cascade" will also remove the "default" declaration. code updates with "replace" are non-blocking
3-
CREATE TABLE t_func_as_def_param(
1+
-- setting a function as default value
2+
-- dropping the function with "cascade" will also remove the "default" declaration
3+
CREATE TABLE func_as_def_param(
44
id serial PRIMARY KEY,
5-
data text NOT NULL DEFAULT f1_returns_text()
5+
data text NOT NULL DEFAULT say_hello()
66
);
77

8-
INSERT INTO t_func_as_def_param (data) VALUES (default);
8+
INSERT INTO func_as_def_param (data) VALUES (default);

0 commit comments

Comments
 (0)