Adding a feature to an already complex query - sql

I have the following table which logs chat messages
CREATE TABLE public.message_log
(
id integer NOT NULL DEFAULT nextval('message_log_id_seq'::regclass),
message text,
from_id character varying(500),
to_id character varying(500),
match_id character varying(500),
unix_timestamp bigint,
own_account boolean,
reply_batch boolean DEFAULT false,
insert_time timestamp with time zone DEFAULT now(),
CONSTRAINT message_log_pkey PRIMARY KEY (id),
CONSTRAINT message_log_message_from_id_to_id_match_id_unix_timestamp_key UNIQUE (message, from_id, to_id, match_id, unix_timestamp)
)
A chat conversation has the same match_id
I have the following query which returns a list of match_ids which the last message related to the match_id (the last message of the chat conversation) is from the non account holder (own_account = false) the query is working fine
select m.* from message_log m where m.from_id <> ? and m.to_id = ? and m.unix_timestamp =
(
select max(unix_timestamp) from message_log where match_id = m.match_id group by match_id
)
I want to modify the query above that it would count the chat conversations that have had been replied to twice or more (I think we would need to use the reply_batch column). I can not get my mind around it. Any help would be appreciated.

SELECT match_id, replies_count FROM (SELECT match_id, COUNT(*) AS replies_count FROM message_log
WHERE from_id <> ? and to_id = ? GROUP BY match_id) AS replies_counter WHERE replies_count > 1

Related

SQLite: Workaround for SQLite-TRIGGER with WITH

I'm working on a project to monitor downtimes of production lines with an embedded device. I want to automate acknowledging of these downtimes by generic rules the user can configure.
I want to use a TRIGGER but get a syntax error near UPDATE even though the documentation says it should be fine to use the WITH statement.
CREATE TRIGGER autoAcknowledge
AFTER UPDATE OF dtEnd ON ackGroups
FOR EACH ROW
WHEN old.dtEnd IS NULL AND new.dtEnd IS NOT NULL
BEGIN
WITH sub1(id, stationId, groupDur) AS (
SELECT MIN(d.id), d.station,
strftime('%s', ag.dtEnd) - strftime('%s', ag.dtStart)
FROM ackGroups AS ag
LEFT JOIN downtimes AS d on d.acknowledge = ag.id
WHERE ag.id = old.id
GROUP BY ag.id ),
sub2( originId, groupDur, reasonId, above, ruleDur) AS (
SELECT sub1.stationId, sub1.groupDur, aar.reasonId, aar.above, aar.duration
FROM sub1
LEFT JOIN autoAckStations AS aas ON aas.stationId = sub1.stationId
LEFT JOIN autoAckRules AS aar ON aas.autoAckRuleId = aar.id
ORDER BY duration DESC )
UPDATE ackGroups SET (reason, dtAck, origin)=(
SELECT reasonId, datetime('now'), originId
FROM sub2 as s
WHERE ( s.ruleDur < s.groupDur AND above = 1 ) OR (s.ruleDur > s.groupDur AND above = 0)
LIMIT 1
)
WHERE id = old.id;
END
Background: First we have the downtimes table. Each production line consists of multiple parts called stations. Each station can start the line downtime and they can overlap with other stations downtimes.
CREATE TABLE "downtimes" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"station" integer NOT NULL,
"acknowledge" integer,
"dtStart" datetime NOT NULL,
"dtEnd" datetime,
"dtLastModified" datetime)
Overlaping downtimes are grouped to acknowledge groups using TRIGGER AFTER INSERT on downtimes to set acknowledge id right or create a new group.
CREATE TABLE "ackGroups" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"reason" integer,
"dtAck" datetime,
"dtStart" datetime NOT NULL,
"dtEnd" datetime,
"line" integer NOT NULL,
"origin" integer)
The autoAckRules table represents the configuration. The user decides whether the rule should apply to durations higher or lower a certain value and which rasonId should be used to acknowledge.
CREATE TABLE "autoAckRules" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"description" text NOT NULL,
"reasonId" integer NOT NULL,
"above" bool NOT NULL,
"duration" real NOT NULL)
The autoAckStations table is used to manage M:N relationship. Each rule allow multiple stations which started the ackGroup.
CREATE TABLE autoAckStations (
autoAckRuleId INTEGER NOT NULL,
stationId INTEGER NOT NULL,
PRIMARY KEY ( autoAckRuleId, stationId )
)
When the last downtime ends dtEnd of ackGroups is set to datetime('now') and the trigger is fired to check if there is a autoAckRule that fits.
If I substitute the sub selects with a SELECT .. FROM( SELECT .. FROM(SELECT .. FROM ))) cascade
is there a nice way to avoid the need to write and evaluate it twice?
Or am I missing something stupid?
Common table expression are not supported for statements inside of triggers. You need to convert CTE to sub-query such as
CREATE TRIGGER autoAcknowledge
AFTER UPDATE OF dtEnd ON ackGroups
FOR EACH ROW
WHEN old.dtEnd IS NULL AND new.dtEnd IS NOT NULL
BEGIN
UPDATE ackGroups
SET (reason, dtAck, origin)= (
SELECT reasonId, datetime('now'), originId
FROM (SELECT sub1.stationId AS originId,
sub1.groupDur AS groupDur,
aar.reasonId AS reasonId,
aar.above AS above,
aar.duration AS ruleDur
FROM (SELECT MIN(d.id) AS id,
d.station AS stationId,
strftime('%s', ag.dtEnd) - strftime('%s', ag.dtStart) AS groupDur
FROM ackGroups AS ag
LEFT
JOIN downtimes AS d
ON d.acknowledge = ag.id
WHERE ag.id = old.id
GROUP BY ag.id ) AS sub1
LEFT
JOIN autoAckStations AS aas
ON aas.stationId = sub1.stationId
LEFT
JOIN autoAckRules AS aar
ON aas.autoAckRuleId = aar.id
ORDER BY duration DESC) as s
WHERE ( s.ruleDur < s.groupDur AND above = 1 ) OR (s.ruleDur > s.groupDur AND above = 0)
LIMIT 1
);
END;

SQL Inbox list all messages by sender_id OR receiver_id

I have a table messages like this :
CREATE TABLE messages (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
created_at timestamp with time zone NOT NULL DEFAULT now(),
sender_id uuid NOT NULL REFERENCES users(id),
receiver_id uuid NOT NULL REFERENCES users(id),
message text NOT NULL,
deleted boolean DEFAULT false,
is_read timestamp with time zone
);
I want to list all not deleted last messages receive or send for inbox view, I have try this query :
SELECT m.*,
sender.username AS sender_username,
sender.avatar AS sender_avatar,
receiver.username AS receiver_username,
receiver.avatar AS receiver_avatar
FROM messages AS m
INNER JOIN
(
SELECT
LEAST(sender_id, receiver_id) AS sender_id,
GREATEST(sender_id, receiver_id) AS receiver_id,
MAX(created_at) AS created_at
FROM messages
GROUP BY
LEAST(sender_id, receiver_id),
GREATEST(sender_id, receiver_id)
) AS q
ON LEAST(m.sender_id, m.receiver_id) = q.sender_id AND
GREATEST(m.sender_id, m.receiver_id) = q.receiver_id AND
m.created_at = q.created_at
INNER JOIN users sender ON m.sender_id = sender.id
INNER JOIN users receiver ON m.receiver_id = receiver.id
WHERE m.sender_id = 'bfa46dbd-b260-4b3c-ba16-06e030b1c0c2'
OR m.receiver_id = 'bfa46dbd-b260-4b3c-ba16-06e030b1c0c2'
AND m.deleted = FALSE
LIMIT 10 OFFSET 0;
I feel like this query expects IDs based on numbers and not uuids..
there isn't an easier way to make this query ?
Finally I will write a request which must list the last 10 messages between two users.
SQL Fiddle
For those who do not wish to open the link here is the raw data :
CREATE TABLE users (
id uuid PRIMARY KEY,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now(),
deleted_at timestamp with time zone,
username text NOT NULL UNIQUE,
email text NOT NULL UNIQUE,
password text NOT NULL,
avatar text
);
CREATE TABLE messages (
id uuid PRIMARY KEY,
created_at timestamp with time zone NOT NULL DEFAULT now(),
sender_id uuid NOT NULL REFERENCES users(id),
receiver_id uuid NOT NULL REFERENCES users(id),
message text NOT NULL,
deleted boolean DEFAULT false,
is_read timestamp with time zone
);
INSERT INTO "users"("id","created_at","updated_at","deleted_at","username","email","password","avatar")
VALUES
(E'17c10b11-d895-4d6e-b332-ba29494540ee',E'2021-05-11 15:17:42.233379+04',E'2021-05-11 16:18:03.06961+04',NULL,E'user1',E'user1#yopmail.com',E'xxxx',E'/img/user1.png'),
(E'bf1de12e-1845-4923-b978-9ede5bae6560',E'2021-05-11 15:17:42.233379+04',E'2021-05-11 16:18:03.06961+04',NULL,E'user2',E'user2#yopmail.com',E'xxxx',E'/img/user2.png'),
(E'bfa46dbd-b260-4b3c-ba16-06e030b1c0c2',E'2021-05-11 15:17:42.233379+04',E'2021-05-13 07:39:16.510222+04',NULL,E'user',E'user#yopmail.com',E'xxxx',E'/img/user.png');
INSERT INTO "messages"("id","created_at","sender_id","receiver_id","message","deleted","is_read")
VALUES
(E'1e0316d4-4392-44b0-9928-719a339e3e2e',E'2021-05-13 12:18:16.630658+04',E'bfa46dbd-b260-4b3c-ba16-06e030b1c0c2',E'17c10b11-d895-4d6e-b332-ba29494540ee',E'ping',FALSE,NULL),
(E'f77f7623-e1df-4257-a1b9-89ebb0c38d23',E'2021-05-13 12:18:37.702524+04',E'bfa46dbd-b260-4b3c-ba16-06e030b1c0c2',E'bf1de12e-1845-4923-b978-9ede5bae6560',E'ping',FALSE,NULL),
(E'd196b2ea-84ed-4d56-8b15-e0b9e1ba8212',E'2021-05-13 12:19:03.549715+04',E'bf1de12e-1845-4923-b978-9ede5bae6560',E'bfa46dbd-b260-4b3c-ba16-06e030b1c0c2',E'pong',FALSE,NULL),
(E'3138aa5a-3d48-4123-a078-c43caf692484',E'2021-05-13 12:19:15.016226+04',E'17c10b11-d895-4d6e-b332-ba29494540ee',E'bfa46dbd-b260-4b3c-ba16-06e030b1c0c2',E'pong',TRUE,NULL),
(E'7ab80cd7-821d-4854-81ed-98392edc4ada',E'2021-05-13 19:53:37.59779+04',E'17c10b11-d895-4d6e-b332-ba29494540ee',E'bfa46dbd-b260-4b3c-ba16-06e030b1c0c2',E'pong 2',TRUE,NULL),
(E'88fdf968-ebae-4268-a04d-06a7811f9db7',E'2021-05-13 19:58:12.304655+04',E'bfa46dbd-b260-4b3c-ba16-06e030b1c0c2',E'17c10b11-d895-4d6e-b332-ba29494540ee',E'pin 2',FALSE,NULL);

How to query two link tables in the same query?

I have two tables. device that can have on blacklisted_device. I would like to get the number of device that include specific user_ids and in the same request number of blacklisted_devices linked.
Here the full sql to try it :
CREATE TABLE device (
device_id serial PRIMARY KEY,
user_id integer,
updated_at timestamp default current_timestamp
);
CREATE TABLE blacklisted_device (
blacklisted_id serial PRIMARY KEY,
device_id integer,
updated_at timestamp default current_timestamp,
CONSTRAINT blacklisted_device_device_id_fkey FOREIGN KEY (device_id)
REFERENCES device (device_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
);
INSERT INTO device (user_id)
VALUES (1),(2),(2),(7),(88),(99),(102),(106);
INSERT INTO blacklisted_device (device_id)
VALUES (1),(2),(3),(4);
SELECT COUNT(*) AS total_device
FROM device
WHERE user_id IN (7,88,99);
SELECT COUNT(*) AS blacklisted
FROM blacklisted_device
WHERE device_id IN (SELECT device_id FROM device WHERE user_id IN (7,88,99));
As you can see at the end I get the result I want but in two requests. How to get it in one request?
total_device: 3, blacklisted: 1
Feel free to make any comment on all the SQL, I probably made few mistakes.
Thanks
You need a LEFT JOIN:
SELECT COUNT(*) AS total_device,
COUNT(DISTINCT bd.device_id) AS blacklisted
FROM device d
LEFT JOIN blacklisted_device bd
ON d.device_id = bd.device_id
WHERE d.user_id IN (7,88,99);

SQL Query giving inconsistant results

I have the following table which logs chat messages
CREATE TABLE message_log
(
id serial NOT NULL,
message text,
from_id character varying(500),
to_id character varying(500),
match_id character varying(500),
unix_timestamp bigint,
own_account boolean,
reply_batch boolean DEFAULT false,
CONSTRAINT message_log_pkey PRIMARY KEY (id)
)
A chat conversation will have the same match_id
I want a query that would return a list of match_ids which the last message related to the match_id (the last message of the chat conversation) is from the non account holder (own_account = false)
I came up with the following query, but it is giving inconsistent results which I don't understand.
select * from message_log
where from_id <> ?
and to_id = ?
and unix_timestamp in ( select distinct max(unix_timestamp)
from message_log group by match_id )
The question mark in the SQL query represents the account holder's user id
It would seem you need to bind the message_id back to the base query as well, otherwise you could be getting a unix_timestamp from a different message:
select m.*
from message_log m
where m.from_id <> ?
and m.to_id = ?
and m.unix_timestamp = ( select max(unix_timestamp)
from message_log
where match_id = m.match_id
group by match_id )
I would suggest using distinct on. This is specific to Postgres, but designed for this situation:
select distinct on (match_id) ml.*
from message_log ml
where from_id <> ? and to_id = ?
order by match_id, unix_timestamp desc;

Can't aggregate on max function

I have a table
CREATE TABLE `messages` ( `uid` BIGINT NOT NULL ,
`mid` BIGINT , `date` BIGINT NOT NULL , PRIMARY KEY (`mid`));
I want to select max(date) grouped by uid, i.e. for every uid(read user) I want to find the latest message (with tha maximum date)
tried this
select messages.mid, max(messages.date), messages.uid, messages.body
from messages
where messages.chat_id is NULL
group by messages.uid
but the query works wrong.
A subquery can give you the date you need in order to retrieve the newest message for each user:
SELECT messages.uid, messages.mid, messages.date, messages.body
FROM messages
WHERE messages.chat_id IS NULL
AND messages.date IN
( SELECT MAX(m2.date) FROM messages m2
WHERE m2.uid = messages.uid AND m2.chat_id IS NULL
)
;
u need to group by all the fields while using aggregate functions :) using a subquery would sort out the problem.
SELECT messages.date,messages.uid, messages.mid, messages.body
FROM messages
WHERE messages.chat_id IS NULL AND messages.date IN (SELECT MAX(msg.date) FROM messages msg WHERE messages.chat_id IS NULL And msg.uid = messages.uid )
alternatively it can also be done using the 'having' clause
done :)