Paginated conversations fetching with latest message - sql

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?

Related

Incorrect Syntax on a Table in SQL

I'm building a database for a site and I keep getting an incorrect syntax error for this portion of it:
CREATE TABLE posts (
pid SERIAL PRIMARY KEY,
title VARCHAR(255),
body VARCHAR,
user_id INT REFERENCES users(uid),
author VARCHAR REFERENCES users(username),
date_created TIMESTAMP
like_user_id INT[] DEFAULT ARRAY[]::INT,
likes INT DEFAULT
);
When I put a comma after TIMESTAMP, it then switches the error message to after the INT [] on the like_user_id line.
Assuming you are using Postgres (which the syntax is closest to), you can use something like this:
CREATE TABLE posts (
pid SERIAL PRIMARY KEY,
title VARCHAR(255),
body VARCHAR(255),
author_uid INT REFERENCES users(uid),
date_created TIMESTAMP,
like_user_id INT[] DEFAULT '{}'::INT[],
likes INT DEFAULT 0
);
Notes:
There is no need for a separate foreign key reference for the user and the name. Use a join to get the name.
You need a comma after the timestamp.
You need an appropriate expression for an empty array for like_user_id.
You need a default value for likes.
I also recommend putting in lengths for the varchar(). I mean, you can skip all the lengths if you like (Postgres allows that), but I don't see a reason to have lengths in some columns and no lengths in others.
Cast ARRAY[]::INT => ARRAY[]::INT[]:
CREATE TABLE posts (
pid SERIAL PRIMARY KEY,
title VARCHAR(255),
body VARCHAR,
user_id INT REFERENCES users(uid),
author VARCHAR REFERENCES users(username),
date_created TIMESTAMP,
like_user_id INT[] DEFAULT array[]::int[],
likes INT DEFAULT 0
);
db<>fiddle demo

Query By Set in Postgres

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};

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.

Create views for user profile management

I have the below schema for user profile management,
CREATE TABLE IF NOT EXISTS users
(
userid TEXT NOT NULL,
name TEXT NULL,
lmessage INTEGER NULL,
statusid INTEGER NULL,
/* statusid should refer to last status of the user in status table*/
locationid INTEGER NULL,
/* locationid should refer to last status of the user in locations table */
registered INTEGER NOT NULL,
tinypic INTEGER NULL
/* this refers to media id in media table */,
largepic INTEGER NULL
/* this also refers to media id in media table */,
groupid INTEGER NULL
/* this refers to id in groups table */ ,
PRIMARY KEY (userid)
);
CREATE TABLE IF NOT EXISTS locations
(
serial INTEGER,
locationid TEXT NOT NULL,
userid TEXT NOT NULL,
time INTEGER NULL,
PRIMARY KEY (serial)
);
CREATE TABLE IF NOT EXISTS status
(
serial INTEGER,
userid TEXT NULL,
message TEXT NOT NULL,
time INTEGER NULL,
PRIMARY KEY (serial)
);
CREATE TABLE IF NOT EXISTS messages
(
sno INTEGER,
messageid INTEGER NOT NULL,
sender TEXT NOT NULL,
receiver TEXT NOT NULL,
time INTEGER NULL,
message TEXT NULL,
image INTEGER NULL,
video INTEGER NULL,
audio INTEGER NULL,
PRIMARY KEY (sno)
);
CREATE TABLE IF NOT EXISTS media
(
mediaid TEXT NOT NULL UNIQUE,
url TEXT NULL,
downloaded INTEGER NULL,
thumbnail TEXT NULL,
PRIMARY KEY (mediaid)
);
CREATE TABLE IF NOT EXISTS groups
(
serial INTEGER,
name TEXT NOT NULL,
id INTEGER NOT NULL
PRIMARY KEY(serial)
);
CREATE UNIQUE INDEX IF NOT EXISTS id_unique ON users (userid ASC);
CREATE UNIQUE INDEX IF NOT EXISTS serial_unique ON status (serial ASC);
CREATE UNIQUE INDEX IF NOT EXISTS id_unique ON messages (sno ASC);
CREATE UNIQUE INDEX IF NOT EXISTS serial_unique ON patterns (serial DESC);
CREATE UNIQUE INDEX IF NOT EXISTS mediaid_unique ON media (mediaid ASC);
How can create views on user table to get list of users based on filter conditions. Please suggest me schema design is not good.
A view example that I would like to add on this schema :
Select all users who belong to group and all groups created after that group.
Select all users with last message, status, location and media urls included.
Thanks.
Please Note that I am SQL nube, please forgive me if you feel like this is immature question. All I need is, I want to learn from reviews of other people.
for 1 - you need to have timestamp (INTEGER id you go by UTC ticks) in group table - it is missing
for 2 - you can do it with join
bot case i don't think you need to have a view unless you have some specific reasons. You can go by select queries with join to get required data.
I recommend you to use http://www.sqliteexpert.com/download.html - try out creating the schema there and try out all your queries before you get in to implementing it in android

Lookup tables localization

On an project where I use localization I have the following tables:
create table dbo.Posts
(
Id int identity not null primary key clustered (Id),
Created datetime not null,
);
create table dbo.PostsLocalized
(
Id int identity not null primary key clustered (Id),
PostId int not null,
LanguageId int not null,
PostTypeId int not null,
[Text] nvarchar (max) not null,
Title nvarchar (120) not null,
constraint UQ_PostsLocalized_PostId_LanguageId unique (PostId, LanguageId)
);
create table dbo.PostTypes
(
Id int identity not null primary key clustered (Id),
Name nvarchar (max) not null
);
So I am localizing the Posts with a PostsLocalized table but not the PostTypes table.
The PostTypes table is basically a lookup table as others I have in my database.
Do you think I should localize the lookup tables, for example, PostTypes?
I would add a new table named PostTypesLocalized with the localized names.
The same for other lookup tables like Genders, Countries, ...
Or should I localize the lookup tables only in the application?
UPDATE
To clarify:
All localized versions of one post has the same PostType.
I need to display the PostTypes in the UI that is why I need to translate them.
So I tried a new approach following the answer of #dasblinkenlight:
create table dbo.Posts
(
Id int identity not null primary key clustered (Id), -- The id of the localized post
Created datetime not null,
PostId int not null, -- The id of the post
PostTypeId int not null
LanguageId int not null,
[Text] nvarchar (max) not null,
Title nvarchar (120) not null,
constraint UQ_PostsLocalized_PostId_LanguageId unique (PostId, LanguageId)
);
create table dbo.PostTypes
(
Id int identity not null primary key clustered (Id), -- PostType localized id
PostTypeId int not null, -- The id of the post type
Name nvarchar (max) not null
);
Considering (1) then Posts > PostTypeId should be related to PostTypes > PostTypeId.
But how can I do this?
The answer depends on the usage of the Name field of the PostTypes table:
If all uses of that field come from code and/or non-localizable scripts that you may have, localization is not necessary
If the Name makes it to the end-user's view, you should localize the table.
If you need to localize PostTypes, a separate PostTypesLocalized table, in addition to the PostTypes table with locale-independent name, sounds like an appropriate solution.
You should consider the placement of the PostTypeId field, too. Would all localizations with the same PostId refer to the same PostTypeId, or would some of them be different? In case that all localizations of the same Post refer to the same PostType, the field should belong to the Posts table, instead of PostLocalized.
should I localize the lookup tables only in the application?
Adding localization to your database counts as localization of your application. It is a good solution when you contemplate multiple applications using the same database structure.