Query By Set in Postgres - sql

I have a table of conversations, table of conversations_owned, and a table of player. I want to find one and only one conversation_owned for any set (meaning distinct group, without respect to ordering) of player.
converation_owned columns conversation_id and player_id are foreign keys
CREATE TABLE conversation
(
id INTEGER PRIMARY KEY NOT NULL,
created_at TIMESTAMP WITH TIME ZONE,
modified_at TIMESTAMP WITH TIME ZONE,
count INTEGER
);
CREATE UNIQUE INDEX conversation_id_uindex ON conversation (id);
CREATE TABLE conversation_owned
(
id INTEGER PRIMARY KEY NOT NULL,
conversation_id BIGINT DEFAULT nextval('conversation_owned_conversation_id_seq'::regclass) NOT NULL,
player_id INTEGER,
created_at TIMESTAMP WITH TIME ZONE,
modified_at TIMESTAMP WITH TIME ZONE,
CONSTRAINT conversation_owned_conversation_id_fk FOREIGN KEY (conversation_id) REFERENCES conversation (id),
CONSTRAINT conversation_owned_player_id_fk FOREIGN KEY (player_id) REFERENCES
);
CREATE UNIQUE INDEX conversation_owned_id_uindex ON conversation_owned (id);
CREATE TABLE player
(
id INTEGER PRIMARY KEY NOT NULL,
player_name TEXT NOT NULL
);
For example: if I type several contacts (Billy, Joe, and Sue) into iMessage or Facebook Messenger compose window the app will retrieve any existing conversation I have with myself, Billy, Joe, or Sue, regardless of the order I entered them. But I may have a different conversation that pops up if I enter just Billy and Joe (and not Sue). I'm looking for a query that can give me this kind of behavior in a relatively efficient way.
Note that if I add a "count" column to the conversation table that has the number of participants in the conversation so that it looks like:
CREATE TABLE conversation
(
id INTEGER PRIMARY KEY NOT NULL
);
I can use this query to get what I want, however it seems to me that I should be able to do this without adding and maintaining a "count" column in conversations!
SELECT
*
FROM
conversation_owned
INNER JOIN conversation ON conversation_owned.conversation_id=conversation.id
WHERE
conversation_id
NOT IN (
SELECT
conversation_owned.conversation_id
FROM
conversation_owned
WHERE
conversation_owned.player_id
NOT IN (${playerIDs})
)
AND
conversation.count = ${participantCount}
AND
conversation_owned.player_id = ${requestingPlayerID};

Related

Design sql tables with list of foreign keys

I want to create an application for rotating pairs in a team every day. I need to store this in the database. Requirments are:
A team should be assigned to one ore more members.
Each team can have multiple tabs and different members allocate in them.(If team consist of 4 members for the particular tab only 3 should be part of it)
Each tab will have a pair of members or list of pairs per day stored.
I have ended up designing something like the example below:
create table if not exists team (
id serial not null primary key,
name text not null
);
create table if not exists member (
id serial not null primary key,
team_id integer references team(id),
nickname text
);
create table if not exists team_tab (
id bigserial not null primary key,
team_id integer references team(id) on delete cascade,
name text not null,
member_ids integer[],
);
create table if not exists team_tab_pairs (
id bigserial not null primary key,
team_tab_id integer not null references team_tab(id) on delete cascade,
tab_date date not null,
pair_ids integer[][],
);
I need an advice and suggestions how could I achieve this without having a list of references ids stored in the 2 tables below.
You need an extra table to design an M:N relationship. This is the case, for example, between "team tab" and "member". In addition to both main entities:
create table member (
id serial not null primary key,
team_id integer references team(id),
nickname text
);
create table team_tab (
id bigserial not null primary key,
team_id integer references team(id) on delete cascade,
name text not null
);
...you'll need to create a table to represent the M:N relationship, as in:
create table team_tab_member (
team_tab_id bigint not null,
member_id int not null,
primary key (team_tab_id, member_id) -- optional depending on the model
);

Paginated conversations fetching with latest message

I am trying to create a simple chat application database schema, and query the conversations. My current table setup is the following:
CREATE TABLE chat_user (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
display_name VARCHAR(140),
... other user stuff ...
);
CREATE TABLE conversation (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
title VARCHAR(140),
created timestamp with time zone NOT NULL
);
CREATE TABLE conversation_message (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
conversation_id bigint NOT NULL,
sender_id bigint NOT NULL,
body TEXT NOT NULL,
created timestamp with time zone NOT NULL
);
CREATE TABLE conversation_participant (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
conversation_id bigint NOT NULL,
user_id bigint NOT NULL
);
So basically each conversation has its own title, and multiple participants. What I would like to do is fetch the conversations paginated, with N conversations on each page (where a certain user is a participant) sorted by the conversation creation date (or if possible by the date of the latest message). The result set should contain the id, title of the conversation and list of participants (id and display name) + the id, sender_id and body of the latest message in the conversation.
What would be the most efficient query to achieve this? Is there maybe some better way to model the schema with the described end goal in mind?

ERROR: there is no unique constraint matching given keys for referenced table "users"

I have read through some of the similar questions and answers and I don't quit understand the problem. One of my current guess is that Potgres might not allow foreign key-ing of non-primary key.
user:
user registration, 1 user per row, 1 id per row(serial), unique email(primary key)
user_logins:
information about user logins, references user_id from user as foreign key, the rest of the columns together makes a composite primary key
CREATE TABLE users
(
unique_email TEXT NOT NULL,
password TEXT NOT NULL,
user_id SERIAL,
created_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT LOCALTIMESTAMP,
CONSTRAINT emailPK
PRIMARY KEY(unique_email)
);
CREATE TABLE user_logins
(
user_id SERIAL,
login_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT LOCALTIMESTAMP,
login_ip INET,
CONSTRAINT user_idFK
FOREIGN KEY (user_id)
REFERENCES users(user_id)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT composit_PK_logins
PRIMARY KEY(user_id, login_date, login_ip)
);
user_id must have unique constraints in users table. Otherwise, it will have ambiguities.
In this query, I added UNIQUE constraint in user_id
CREATE TABLE users
(
unique_email TEXT NOT NULL,
password TEXT NOT NULL,
user_id SERIAL UNIQUE,
created_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT LOCALTIMESTAMP,
CONSTRAINT emailPK
PRIMARY KEY(unique_email)
);

How do I make a serial field auto increment and be a foreign key - Postgres

I have 2 tables and I am trying to create a Foreign Key between the two. Here is the structure of my tables:
create table users (
id serial,
user_name varchar(50)
);
create table playlists (
id serial,
user_id integer references users(id)
);
I keep getting this error:
ERROR: there is no unique constraint matching given keys for referenced table "users"
Why is there not a unique constraint? If I create the id in the users table as integer PRIMARY KEY, then everything works fine. How do I fix this where the users id auto increments and can be the FK in the playlists table?
Creating a column of type serial doesn't make it the primary key or constraint it in any way. serial just creates an integer column, creates a sequence, and attaches the sequence to the column to provide default values. From the fine manual:
In the current implementation, specifying:
CREATE TABLE tablename (
colname SERIAL
);
is equivalent to specifying:
CREATE SEQUENCE tablename_colname_seq;
CREATE TABLE tablename (
colname integer NOT NULL DEFAULT nextval('tablename_colname_seq')
);
ALTER SEQUENCE tablename_colname_seq OWNED BY tablename.colname;
If you want your id serial columns to be primary keys (which you almost certainly do), then say so:
create table users (
id serial not null primary key,
user_name varchar(50)
);
create table playlists (
id serial not null primary key,
user_id integer references users(id)
);

Postgresql multiple tables with same foreign key unique constraint

I have following tables on PostgreSQL 9.4
CREATE TABLE "user" (
id SERIAL PRIMARY KEY,
email CHARACTER VARYING NOT NULL,
password CHARACTER VARYING NOT NULL
);
CREATE TABLE "dealer" (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES "user" (id) ON DELETE RESTRICT
);
CREATE TABLE "affiliate" (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES "user" (id) ON DELETE RESTRICT
);
Is it possible to force user_id value to be unique across tables dealer and affiliate?
There are different setups to use for inheritance in SQL and for this you could just use an integer column type in the table user that marks the type of the user and would reference to table user_type (id,name) that would have the values 1,dealer and 2,affiliate:
CREATE TABLE user_type (
id INTEGER PRIMARY KEY, --could be SERIAL
name text
);
INSERT INTO user_type VALUES (1,'dealer'), (2, 'affiliate');
CREATE TABLE "user" (
id SERIAL PRIMARY KEY,
email CHARACTER VARYING NOT NULL,
password CHARACTER VARYING NOT NULL,
user_type INTEGER REFERENCES user_type NOT NULL,
UNIQUE(id,user_type)
);
This in itself wouldn't force uniqueness across tables so after implementing this you would have the following options:
Drop the tables dealer and affiliate - you won't need them if you rely on the type field to see which one the user is.
If you have to keep those inherited tables you can:
Use triggers - these triggers check the uniqueness and would be actived on INSERT or UPDATE
Another (a bit clumsy) solution: add user_type field to both subtables like this:
CREATE TABLE "dealer" (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
user_type INTEGER NOT NULL DEFAULT 1 check (user_type = 1),
FOREIGN KEY (user_id,user_type) REFERENCES "user"(id,user_type) ON DELETE RESTRICT
);
CREATE TABLE "affiliate" (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
user_type INTEGER NOT NULL DEFAULT 2 check (user_type = 2),
FOREIGN KEY (user_id,user_type) REFERENCES "user"(id,user_type) ON DELETE RESTRICT
);
The checks and foreign keys together make sure you cannot have both types of user in the main table. Note that user_id might be used as the PRIMARY KEY in the subtables too. Currently a row in user might have several dealer rows linked to it so at least you might want to set user_id foreign keys in subtables as UNIQUE.