Love Your Database. An interesting example of how to exploit Postgres' advanced types and of running code in the database. Take a step beyond stored procedures!
28. CREATE OR REPLACE VIEW public.all_join AS
SELECT c.customerid,
c.firstname,
c.lastname,
c.address1,
c.address2,
c.city,
c.state,
c.zip,
c.country,
c.email,
c.phone,
c.creditcardtype,
c.creditcard,
c.creditcardexpiration,
c.username,
c.password,
c.age,
c.income,
c.gender,
o.orderid,
o.orderdate,
o.netamount,
o.tax,
o.totalamount,
ol.quantity AS qty,
p.prod_id,
p.title,
p.price,
inv.quan_in_stock,
inv.sales,
cat.categoryname AS category
FROM customers c
FULL JOIN orders o USING (customerid)
FULL JOIN orderlines ol USING (orderid)
FULL JOIN products p USING (prod_id)
FULL JOIN categories cat ON p.category_id = cat.category
FULL JOIN inventory inv USING (prod_id)
30. Create a Schema
class AddReportingSchema < ActiveRecord::Migration[5.0]
def up
execute 'CREATE SCHEMA reporting'
end
def down
execute 'DROP SCHEMA reporting'
end
end
31.
32. Create a Reports View 1
CREATE VIEW
all_views AS
SELECT
c.relname AS name,
obj_description(c.oid) AS description,
ns.nspname AS namespace
FROM
pg_class c JOIN
pg_namespace ns ON c.relnamespace = ns.oid
WHERE
c.relkind = 'v'::"char"
33. Create a Reports View 2
CREATE VIEW
public.reports AS
SELECT
name,
description
FROM
all_views
WHERE
namespace = 'reporting'::name
34. Create reporting.customers ViewCREATE VIEW
reporting.customers
AS
SELECT
customerid,
lastname,
firstname,
zip,
ARRAY_AGG(prod_id ORDER BY orderdate, prod_id) AS prod_ids,
ARRAY_AGG(title ORDER BY orderdate, prod_id) AS titles,
ARRAY_AGG(category ORDER BY orderdate, prod_id) AS categories,
ARRAY_AGG(price ORDER BY orderdate, prod_id) AS prices,
SUM(totalamount) AS spent
FROM
all_join
GROUP BY
customerid, lastname, firstname, zip
44. Create another reportWITH
scifi_viewers AS (
SELECT customerid
FROM all_join
WHERE ((category)::text = 'Sci-Fi'::text)
),
californians AS (
SELECT customerid
FROM all_join
WHERE ((state)::text = 'CA'::text)
),
targets AS (
SELECT customerid,
firstname,
lastname,
address1,
address2,
city,
state,
zip
FROM
all_join
WHERE
customerid IN (
SELECT customerid FROM scifi_viewers
)
OR customerid IN
(SELECT customerid FROM californians)
)
…
These are
Common Table Expressions
45. Create another reportSELECT prod_id,
title,
price,
quan_in_stock,
sales,
sum(totalamount) AS total_amt,
count(DISTINCT orderid) AS order_ct,
array_agg(lastname ORDER BY lastname, firstname, customerid) AS lastnames,
array_agg(firstname ORDER BY lastname, firstname, customerid) AS firstnames,
array_agg(state ORDER BY lastname, firstname, customerid) AS states,
array_agg(zip ORDER BY lastname, firstname, customerid) AS zips
FROM
targets
GROUP BY
prod_id,
title,
price,
quan_in_stock,
sales
48. Create reporting.customers_by_state View
…
SELECT
state,
CASE
WHEN LAG(state) OVER (ORDER BY state, lastname) IS NULL
OR
LAG(state) OVER (ORDER BY state, lastname)::text <> state::text
THEN
(('{"break": [1, "'::text || state::text) || '"]}'::text)::jsonb
ELSE
NULL::jsonb
END AS _meta),
…
Copy customers and add this
49. WHERE (length((state)::text) > 0)
GROUP BY
customerid,
lastname,
firstname,
state,
zip
ORDER BY
state,
lastname
… also add this
51. From Postgres Docs
• A window function performs a calculation across a set of table
rows that are somehow related to the current row…
• …unlike regular aggregate functions, use of a window function does
not cause rows to become grouped
52. Three parts
• The function
• Which rows (optional)
• Which order (optional)
55. Order required
Function Use
rank Rank within partition
row_number Number of row within partition
lag Value from preceding row
lead Value from later row
…
66. Create a view
CREATE VIEW
reporting_helpers.customers_by_state_ranked
AS
SELECT
*,
rank() OVER (
PARTITION BY
c.state
ORDER BY
c.spent) AS rank
FROM
reporting.customers_by_state c
68. Create a Javascript FunctionCREATE FUNCTION
reporting.formatted_cust_by_state_bold(
c reporting_helpers.customers_by_state_ranked
)
RETURNS
reporting_helpers.customers_by_state_ranked
LANGUAGE
plv8
STRICT COST 1
AS $function$
if (c.rank == 1) {
c['_meta'] = c['_meta'] || {}
c['_meta']['raw'] = c['_meta']['raw'] || {}
c['_meta']['raw']['spent'] = c['_meta']['raw']['spent'] || {}
c['_meta']['raw']['spent'] = "<td><b>" + c.spent + "</b></td>"
}
return c
$function$
A table/view defines a type
STRICT means “functional on a given db state”
69. Create a Javascript Function
CREATE FUNCTION
reporting.formatted_cust_by_state_bold(
c reporting_helpers.customers_by_state_ranked
)
RETURNS
reporting_helpers.customers_by_state_ranked
LANGUAGE
plv8
STRICT COST 1
AS $function$
70. LANGUAGE
plv8
STRICT COST 1
AS $function$
if (c.rank == 1) {
c['_meta'] = c['meta'] || {}
c['_meta']['raw'] = c['_meta']['raw'] || {}
c['_meta']['raw']['spent'] = c['_meta']['raw']['spent'] || {}
c['_meta']['raw']['spent'] = "<td><b>" + c.spent + "</b></td>"
}
return c
$function$
Create a Javascript Function
80. Novel Postgres Features
• Proper type system
• Custom types
• Functions
• Automatic casts
• Tables are types
• Arrays
• JSON
• XML
81. Novel Postgres Features
• Foreign Data Wrappers
• SQL Databases
• LDAP
• Elastic Search
• Redis
• MongoDB
• Cassandra, CouchDB, …
• CSV/JSON/XML/…
• git/file system/imap/twitter
• google spreadsheet
• Roll your own (Python)
• …
82.
83. In Console (on a Mac)
ActiveRecord::Base.connection_pool.with_connection do |connection|
c = connection.instance_variable_get(:@connection)
c.async_exec "LISTEN speak"
c.wait_for_notify do |channel, pid, payload|
p "got #{channel} #{pid} #{payload}"
`say #{payload}`
end
c.async_exec "UNLISTEN *"
end
84. In psql
SELECT pg_notify('speak', 'Hello from Postgres');
or another console:
ActiveRecord::Base.connection.execute("
SELECT pg_notify('speak', 'Hello from Postgres');")