ZI2RJOZ2HXBHX7L54BTSDKESY6NG5KBJLVHCNY7K7ZLGIUOIHQUQC
TBTYGTE3JUKOX6GDBR7AMMMV4RYCUFAK5N3ATSVCLCGESBSMJUHQC
UCWCMBNLNMCMNJ2QRKZSFABEHKSOO7BWVLLEJCOPJPZ7QZCECQUAC
UCY5PVFCT2QIOGR47KFS4KBF2ZPHRBOAIOH6PJEVWCHLMBRBLOMQC
EGNRBQUMOZHAF5B7KCMTAPMB4H3USK4MK33Q6T5V26D6HOJNW6LQC
VO5G3FF3NDOKCIF2OU7G257R3XIPTFDCAMDTOVKHKOB5Z3I55KDQC
E4HFJ4L4PAGV7R7EXVG2B2CWDGGW7XWU4D37VURZ66HZ3ILHCFUAC
4WREYORZT3SWUXADWHSS6B4PQSP3QSLH77CGNKRH6IRKUMX4TARAC
4ADMC3SGJNATDR6D7WXHNZ6RD5F5BUPLPRMCEUERWRWKVZQVAO3AC
WYTMZJFYVKHR4QH7AV5JUNWXT6NAC5NNQNPZCQSDI6LGI7DVXFYAC
54TMOAIBD5UBRSWPGW45YZO4VMVKSP7YYKIQ7H4LWSVCN2E3QA4QC
CREATE EXTENSION IF NOT EXISTS citext;
CREATE EXTENSION IF NOT EXISTS first_last_agg;
-- Create a function that always returns the first non-NULL item
CREATE FUNCTION public.first_agg ( anyelement, anyelement )
RETURNS anyelement LANGUAGE SQL IMMUTABLE STRICT AS $$
SELECT $1;
$$;
-- And then wrap an aggregate around it
CREATE AGGREGATE public.FIRST (
sfunc = public.first_agg,
basetype = anyelement,
stype = anyelement
create extension if not exists citext;
create extension if not exists periods version '1.1' cascade;
create table store (
ID serial primary key,
name text not null,
URL text not null unique,
address text,
address2 text,
city text,
region text not null,
country text not null,
postal_code text,
coordinate point not null,
timezone text
);
SELECT periods.add_system_time_period('store');
SELECT periods.add_system_versioning('store');
create table store_tax (
ID serial primary key,
store integer not null references store on delete cascade,
name text not null,
rate decimal(4,4) not null
-- Create a function that always returns the last non-NULL item
CREATE FUNCTION public.last_agg ( anyelement, anyelement )
RETURNS anyelement LANGUAGE SQL IMMUTABLE STRICT AS $$
SELECT $2;
$$;
-- And then wrap an aggregate around it
CREATE AGGREGATE public.LAST (
sfunc = public.last_agg,
basetype = anyelement,
stype = anyelement
SELECT periods.add_system_time_period('store_tax');
SELECT periods.add_system_versioning('store_tax');
create table store_time (
ID serial primary key,
store integer not null references store on delete cascade,
day integer constraint valid_day check (day > 0 and day < 8) not null,
start time not null,
end_ time not null,
constraint end_greater_than_start check(end_ > start)
--need an exclude constraint with day, start, and end_
--is it better to use timestamptz so we can use range / period functions, and query extract just time?
create type product_type as enum ('flower', 'preroll', 'oil', 'spray', 'capsule');
create type concentration_unit as enum ('mg', 'proportion');
create type measurement_unit as enum('ml', 'mg', 'g', 'capsule');
SELECT periods.add_system_time_period('store_time');
SELECT periods.add_system_versioning('store_time');
create table store (
ID serial primary key,
name text not null,
URL text not null unique,
address text,
address2 text,
city text,
region text not null,
country text not null,
postal_code text,
coordinate point not null,
timezone text
);
create table store_tax (
ID serial primary key,
store integer not null references store on delete cascade,
name text not null,
tax decimal(4,4) not null--should be rate
);
create table store_hours (--should be times
ID serial primary key,
store integer not null references store on delete cascade,
day integer constraint valid_day check (day > 0 and day < 8),--does this imply not null?
open_time time not null,--should be start
close_time time not null--should be end
);
--store_with_history view
--store_history table via trigger
--https://stackoverflow.com/questions/4464898/best-way-to-store-working-hours-and-query-it-efficiently
--do we care about store hours history?
create type product_type as enum ('flower', 'preroll', 'oil', 'spray', 'capsule');
create type strain as enum('indica', 'sativa', 'hybrid');
create type concentration_unit as enum ('mg', 'proportion');
create type measurement_unit as enum('ml', 'mg', 'g', 'capsule');
show boolean default false,
created_at timestamptz not null default now(),--useful for "new arrivals". do we need this at store level too? todo: terpene text array, image_paths text array
daily boolean default false not null,
weekly boolean default false not null,
monthly boolean default false not null,
rarely boolean default false not null,
happy boolean default false not null,
relaxed boolean default false not null,
balanced boolean default false not null,
concentration_unit concentration_unit not null,
measurement_unit measurement_unit not null,
show boolean default false,--todo: terpene text array, image_paths text array
variant integer not null references variant on delete cascade,--perhaps restrict makes more sense. in other areas too... but with temporal_table is it safe to cascade since it'll still be present in history table?
variant integer not null references variant on delete cascade,
featured boolean default false not null,
featured boolean default false not null,--rank someday: https://stackoverflow.com/a/49956113/1201238
use_for_compute boolean default true not null,--online stores have stupid ass numbers so initially, use for compute, but once we have stores turn it off.
create type "CYP2C19*2" as enum ('inconclusive', '*1/*1', '*1/*2', '*2/*2');
create type "CYP2C19*3" as enum ('inconclusive', '*1/*1', '*1/*3', '*3/*3');
create type "CYP2C19*17" as enum ('inconclusive', '*1/*1', '*1/*17', '*17/*17');
create type CYP2C19_2 as enum ('inconclusive', '*1/*1', '*1/*2', '*2/*2');
create type CYP2C19_3 as enum ('inconclusive', '*1/*1', '*1/*3', '*3/*3');
create type CYP2C19_17 as enum ('inconclusive', '*1/*1', '*1/*17', '*17/*17');
--todo: store reviews, product reviews, notifications, watched products, mixology, bidding for product placement on home page
SELECT periods.add_system_time_period('user_account', excluded_column_names => ARRAY['token_hash', 'token_expiry']);
SELECT periods.add_system_versioning('user_account');
create type status as enum ('placed', 'ready', 'paid', 'user_cancelled', 'store_cancelled', 'no_show');--'created' is null
user_account integer references user_account on delete cascade, --nullable, for no user
store integer not null, --references store on delete cascade, since temporal_table relevation lol
created_at timestamptz not null default now(),
abandoned_at timestamptz,--when cart is cleared or they start adding stuff from other store
placed_at timestamptz,--before this, they are editing their cart
want_at timestamptz, --user could want it later ie not immidiately. null means now. kill?
ready_at timestamptz,
payed_at timestamptz,
delivered_at timestamptz,
user_cancelled_at timestamptz,
store_cancelled_at timestamptz,
no_show_at timestamptz,
user_account integer not null references user_account on delete cascade,
store integer not null references store on delete cascade,
status status,
--todo: discounts and loyalty,
);--compute total in application layer?
create unique index unique_cart_3 on cart (user_account, store, placed_at) where placed_at is not null;
create unique index unique_cart_2 on cart (user_account, store) where placed_at is null;
--history table enough for cart?
);
create unique index unique_cart on cart (user_account, store) where status is null;
SELECT periods.add_system_time_period('cart');
SELECT periods.add_system_versioning('cart');
product_with_history integer not null,--can I fk this? https://stackoverflow.com/a/10183157/1201238
variant_with_history integer not null,--ditto
menu_item_with_history integer not null,--yo
menu_item integer not null references menu_item on delete cascade,
const [strain_families, strains, product_types, concentration_units, measurement_units] = await Promise.all([
pool.query('select id, name from strain_family').then(x => x.rows),
pool.query('select array_to_json(enum_range(null::strain)) as wow').then(x => x.rows[0].wow),
pool.query('select array_to_json(enum_range(null::product_type)) as wow').then(x => x.rows[0].wow),
pool.query('select array_to_json(enum_range(null::concentration_unit)) as wow').then(x => x.rows[0].wow),
pool.query('select array_to_json(enum_range(null::measurement_unit)) as wow').then(x => x.rows[0].wow),
]);
let product = (await pool.query('insert into product values (default, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) returning id', [
let product = (await pool.query('insert into product values (default, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, true) returning id', [
response_ID: request_ID,
data: (await pool.query(` select first(product.id) as id, first(type) as type, first(display_brand) as brand, first(producer) as producer, first(display_name) as name, first(strain) as strain, first(strain_family_effect.name) as strain_family, COALESCE(json_agg(json_build_object('id', variant.id, 'portions', portions, 'quantity', quantity)) filter (where quantity is not null)) as variants, first(concentration_unit) as concentration_unit, first(measurement_unit) as measurement_unit from product left outer join variant on (product.id = variant.product) left outer join strain_family_effect on (product.strain_family = strain_family_effect.id) where show = true group by product.id`)).rows
response_ID: request_ID,//https://dba.stackexchange.com/a/159899
data: (await pool.query(`
select distinct on(product.id)
product.id as id,
type,
display_brand as brand,
producer,
display_name as name,
strain,
strain_family_effect.name as strain_family,
COALESCE(json_agg(json_build_object('id', variant.id, 'portions', portions, 'quantity', quantity)) filter (where quantity is not null)) as variants,
concentration_unit,
measurement_unit
from
product
left outer join strain_family_effect on (product.strain_family = strain_family_effect.id)
left outer join variant on (product.id = variant.product)
where
show = true
group by
product.id,
strain_family_effect.id
`)).rows//I guess it's necessary to group by strain_family_effect.id as well because there's no way for postgres to know that there'll only be one row in the result per strain_family_effect. ask dba