Message and reply massage functionality - mysql

We've a table called message.
CREATE TABLE IF NOT EXISTS `message` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`from_user_id` int(11) NOT NULL,
`to_user_id` int(11) NOT NULL,
`content` text NOT NULL,
`club_id` int(11) NOT NULL,
`read_flag` int(11) NOT NULL DEFAULT '0',
`parent_id` int(11) NOT NULL,
`status` tinyint(1) DEFAULT NULL,
`create_user_id` int(11) NOT NULL,
`update_user_id` int(11) NOT NULL,
`create_dt_tm` datetime NOT NULL,
`update_dt_tm` datetime NOT NULL,
`delete_flag` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
)
Need to display the messages and message reply to the user.
Entries in the table will like this,
id | from_user_id | to_user_id | content | parent_id
1 | 2 | 3 | hai | 0
2 | 3 | 2 | hi | 1
3 | 3 | 2 | hwru | 1
4 | 3 | 4 | hwru | 1
5 | 4 | 5 | u added | 1
6 | 4 | 5 | new msg | 0
Here is the flow,
lets assume 2=>A, 3 =>B, 4 =>C, 5=> D,
A send a message to B
B reply to that message
B send again one more reply by adding new recipient C
C reply to that thread again by adding new recipient D
All users part of this thread, should able to read full message thread.
A,B,C and D can see the all (1,2,3,4,5) messages if they login except 6th
6th message only C and D can see and it is a different thread
Two queries I'm using now,
One for to list all messages.
Second is for to see the details for that message(when user click on that will show all thread related to that message).
By using single query I need to show the all threads to the login user.
Please help some one to select query for this.

Make the default for parent_id NULL. Gets threads user is allowed to view, replace <thisuserid> with user id
SELECT DISTINCT(COALESCE(parent_id, id)) thread_id FROM message m WHERE from_user_id = <thisuserid> OR to_user_id = <thisuserid>
Get whole thread, including duplicates when sending to many recipients since i cant think of a fool proof way to filter them out as they are stored as separate messages. replace <thisuserid> with user id
SELECT * from message m WHERE id = <threadid> OR parent_id = <threadid>
Although, i would totally separate the recipient from the message itself, not only to make querying the whole chain easier, but also to save space. They way you do it now, every new recipient of a message increases the storage required by an amount equal to the size of the message, which can get out of hand very quickly.
CREATE TABLE IF NOT EXISTS `message` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`from_user_id` int(11) NOT NULL,
`content` text NOT NULL,
`parent_id` int(11),
PRIMARY KEY (`id`)
);
CREATE TABLE IF NOT EXISTS `message_to` (
`message_id` int(11) NOT NULL,
`recipient_id` int(11) NOT NULL,
`read_flag` int(11) NOT NULL DEFAULT '0',
`status` tinyint(1) DEFAULT NULL,
`delete_flag` tinyint(1) NOT NULL DEFAULT '0',
UNIQUE KEY (`message_id`, `recipient_id`)
);
INSERT INTO message VALUES (1, 2, 'hai', null), (2, 3, 'hi', 1), (3, 3, 'hwru', 1), (4, 4, 'u added', 1), (5, 4, 'new msg', null);
INSERT INTO message_to (`message_id`, `recipient_id`) VALUES (1,3), (2,2), (3,2), (3,4), (4,5), (5,5);
Get threads user is allowed to view
SET #user := 2;
SELECT DISTINCT(COALESCE(parent_id, id)) thread_id FROM message m WHERE id IN (
SELECT message_id as id FROM message_to WHERE recipient_id = #user
union
SELECT id from message where from_user_id = #user
);
Get whole thread
SELECT * FROM message m WHERE m.id = 1 OR m.parent_id = 1

Your storage type is called Adjacency list, i.e. just store immediate parent id in parent_id column.
To query node's children:
mysql> SELECT * FROM message m1 INNER JOIN message m2 ON m2.parent_id = m1.id WHERE m1.id = 1;
+----+--------------+------------+---------+-----------+----+--------------+------------+---------+-----------+
| id | from_user_id | to_user_id | content | parent_id | id | from_user_id | to_user_id | content | parent_id |
+----+--------------+------------+---------+-----------+----+--------------+------------+---------+-----------+
| 1 | 2 | 3 | hai | 0 | 2 | 3 | 2 | hi | 1 |
| 1 | 2 | 3 | hai | 0 | 3 | 3 | 2 | hwru | 1 |
| 1 | 2 | 3 | hai | 0 | 4 | 3 | 4 | hwru | 1 |
| 1 | 2 | 3 | hai | 0 | 5 | 4 | 5 | u added | 1 |
+----+--------------+------------+---------+-----------+----+--------------+------------+---------+-----------+
4 rows in set (0.00 sec)
If you would like a flat structure, you can do the following query:
mysql> select * from message m WHERE id = 1 OR parent_id = 1;
+----+--------------+------------+---------+-----------+
| id | from_user_id | to_user_id | content | parent_id |
+----+--------------+------------+---------+-----------+
| 1 | 2 | 3 | hai | 0 |
| 2 | 3 | 2 | hi | 1 |
| 3 | 3 | 2 | hwru | 1 |
| 4 | 3 | 4 | hwru | 1 |
| 5 | 4 | 5 | u added | 1 |
+----+--------------+------------+---------+-----------+
5 rows in set (0.00 sec)
Adjacency list has serious drawbacks: it's hard to query deeply nested trees (we're querying only immediate children of message #1 here).
Please, take a look at linked question and also this excellent presentation by Bill Karwin for other options.

Related

Combine two tables with same Primary key

This seems like a simple question but I can't seem to find an answer. I have two tables. Table 1:
+---------+-----------+--------+-------+----------+
| userid | username | date | time | footsize |
+---------+-----------+--------+-------+----------+
| 1 | user1 | 103999 | 1010 | 9 |
| 2 | user2 | 484883 | 984 | 6 |
+---------+-----------+--------+-------+----------+
and Table 2:
+---------+-----------+----------+
| userid | natural | synthetic|
+---------+-----------+----------+
| 1 | y | n |
| 2 | n | y |
+---------+-----------+----------+
What I'd like to do is delete table 2.
But I need to move the columns and data natural and synthetic from table 2 and insert them into table 1, using userid as a primary key to make sure the data goes to the right customer.
I tried using the join statements but I can't seem to move them from joining to inserting without an error.
The general (loose) idea I want is
select userid from table1, select * from table2.
Insert into table1, table2.natural, table2.synthetic where table1.userid = table2.userid;
So that table 1 looks like this:
+---------+-----------+--------+-------+----------+-----------+----------+
| userid | username | date | time | footsize | natural | synthetic|
+---------+-----------+--------+-------+----------+-----------+----------+
| 1 | user1 | 103999 | 1010 | 9 | y | n |
| 2 | user2 | 484883 | 984 | 6 | n | y |
+---------+-----------+--------+-------+----------+-----------+----------+
I'm aware that's not a real query, but it should clarify what I'm trying to do. Thanks!
first you create a table that has all the columns
CREATE TABLE `table3` (
`userid` INT NOT NULL AUTO_INCREMENT,
`username` VARCHAR(45) NULL,
`date` INT NULL,
`time` INT NULL,
`footsize` INT NULL,
`natural` VARCHAR(45) NULL,
`synthetic` VARCHAR(45) NULL,
PRIMARY KEY (`userid`));
insert into table3
select t1.userid, t1.username, t1.`date`, t1.`time`, t1.footsize,
t2.natural, t2.synthetic
from table1 as t1
join table2 as t2
on t1.userid = t2.userid

Mysql - Select, store in var, select in same query

I am trying to understand how to do this. Not much help found.
I have two tables.
First table:
CREATE TABLE `first` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`parent_id` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
)
Second table:
CREATE TABLE `second` (
`id` int(11) NOT NULL,
`foo` varchar(3) NOT NULL ,
`bar` varchar(255) NOT NULL )
Id in both tables is same value, they are related that way.
I have to fetch some entries, for example with parent_id = '0' and to check if those entries have children. If they has I should get their ids too.
It could be done easily with little php and two queries but I would like to do it better. I tried something like:
SELECT `first`.*, `second`.*, children.*
FROM `first`
JOIN `second` ON `second`.id = `first`.id
INNER JOIN( SELECT
// Something
)AS children ON children.id = `second`.id
WHERE `first`.parent_id = '0'
GROUP BY `first`.id, `second`.la
But I did not make it.
How could I Select those result and their children in same query? Thanks!
EDIT
First:
-------------------------------------------------
| id | parent_id |
--------------------------------------------------
| 1 | 0 |
--------------------------------------------------
| 2 | 1 |
--------------------------------------------------
| 3 | 2 |
--------------------------------------------------
| 4 | 1 |
--------------------------------------------------
Second table:
-------------------------------------------------
| id | foo | name
--------------------------------------------------
| 1 | asd | apple
--------------------------------------------------
| 2 | asd | banana
--------------------------------------------------
| 3 | gsf | orange
--------------------------------------------------
| 4 | gre | potato
--------------------------------------------------
Desired Result would be to get row where id is 1 (joined both tables) and all ids of rows witch have parent_id 1.
Thanks!

For each option in a matchmaker, make sure that at least one (but no more than one) matches per option

This will take a little explaining (moreso because I can't use the word "question" in the title of a question):
I have a matchmaker quiz with the following tables (simplified):
CREATE TABLE `Quiz` (
`quiz_id` int(10) unsigned NOT NULL,
`code` varchar(20) DEFAULT NULL,
`title` varchar(50) DEFAULT NULL,
PRIMARY KEY (`quiz_id`),
UNIQUE KEY `Quiz_1` (`code`)
);
CREATE TABLE `Quiz_Question` (
`quiz_id` int(10) unsigned NOT NULL,
`question_id` int(10) unsigned NOT NULL,
`question` varchar(250) DEFAULT NULL,
`type` int(10) unsigned NOT NULL, -- Lookup table of type of question: booean, radio, select, multiselect
PRIMARY KEY (`question_id`)
);
CREATE TABLE `Quiz_Answer` (
`question_id` int(10) unsigned NOT NULL,
`answer_id` int(10) unsigned NOT NULL,
`answer` varchar(250) DEFAULT NULL,
PRIMARY KEY (`answer_id`)
);
CREATE TABLE `Quiz_Response` (
`user_id` int(10) unsigned NOT NULL,
`quiz_id` int(10) unsigned NOT NULL,
`question_id` int(10) unsigned NOT NULL,
`answer_id` int(10) unsigned DEFAULT NULL,
UNIQUE KEY `Response_1` (`user_id`,`question_id`,`answer_id`),
KEY `Response_2` (`question_id`,`answer_id`)
);
All pretty straightforward so far.
Previously, the query went like this (simplified):
SELECT u.login, COUNT( u.user_id ) AS matches, ...
FROM User u
INNER JOIN Quiz_Response rep ON u.user_id = rep.user_id
WHERE u.active = 1
AND (
(rep.question_id = 3 AND rep.answer_id IN (20, 24)) OR
(rep.question_id = 10 AND rep.answer_id IN (83,84,85))
)
GROUP BY u.user_id
HAVING matches >= 2
ORDER BY u.login
Note: I've removed things like whether something is active or not, display order, blocked users, date ranges, etc from the CREATE TABLE and query to focus on the core problem.
So if a user answered question3 with either 20 or 24, they show up in the results once, and if they answer question10 with either 83, 84, or 85 they show up a second time. The query then counts the number of times any given user shows up and if it is equal or greater than the number of questions tried to match, it is considered a match (in this case the matchmaker checked two possible questions so their should be at least 2 entries (matches).
My issue is that I'm introducing a multiple choice matches. This has the end result of a single question can have multiple matches which throws off the counting.
So, if a searcher says that they are looking for people that answered question 5 with either A, B, or C, and a user says that they like A, B, and C, that becomes three matches essentially nullifying two other questions (searched for three things, and got back three matches just all from the same question).
So the question I'm asking is how do I check that for every given question, it only scores 1 match, even if multiple answers for a single question match multiple times.
Hope that all makes sense.
Instead of counting on u.user_id, count on distinct rep.question_id:
SELECT u.login, u.user_id, COUNT(distinct rep.question_id) AS matches
FROM User u
INNER JOIN Quiz_Response rep ON u.user_id = rep.user_id
WHERE u.active = 1
AND (
(rep.question_id = 3 AND rep.answer_id IN (20, 24)) OR
(rep.question_id = 10 AND rep.answer_id IN (83,84,85))
)
GROUP BY u.user_id
HAVING matches >= 2
ORDER BY u.login;
So if my Quiz_Response table looks like this:
+-------------+---------+-------------+-----------+---------+
| response_id | quiz_id | question_id | answer_id | user_id |
+-------------+---------+-------------+-----------+---------+
| 1 | 1 | 1 | 4 | 3 |
| 2 | 2 | 3 | 20 | 2 |
| 3 | 2 | 3 | 24 | 2 |
| 4 | 4 | 10 | 83 | 1 |
| 5 | 4 | 10 | 84 | 1 |
| 6 | 4 | 10 | 85 | 1 |
| 7 | 2 | 3 | 20 | 4 |
| 8 | 1 | 1 | 1 | 4 |
| 9 | 2 | 3 | 24 | 4 |
| 10 | 4 | 10 | 83 | 4 |
+-------------+---------+-------------+-----------+---------+
Output of the above query will be:
+---------------------+---------+---------+
| login | user_id | matches |
+---------------------+---------+---------+
| 2018-01-01 00:00:00 | 4 | 2 |
+---------------------+---------+---------+

Mysql: Update using select and local variable

I am looking for an update statement that will group terms by language in the following table
CREATE TABLE _tempTerms(
ID int(8) unsigned NOT NULL AUTO_INCREMENT,
TTC_ART_ID mediumint(8) unsigned,
TTC_TYP_ID mediumint(8) unsigned,
Name varchar(200),
Value varchar(200),
ID_Lang tinyint(3) unsigned,
Sequence smallint unsigned,
Group_ID int(8) unsigned DEFAULT 0,
PRIMARY KEY(TTC_ART_ID, TTC_TYP_ID, Name, Value),
UNIQUE KEY(ID)
);
All data except Group_ID is inserted into the table. I need to update the table so that I auto-generate new Group_IDs and the Group_ID for all records with same combination of TTC_ART_ID, TTC_TYP_ID and Sequence will get the same Group_ID. I guess I need a variable to store the current value for Group_ID and so far I experimented with
SET #group_id:=1;
UPDATE _tempTerms
SET Group_ID = (#group_id := #group_id + 1);
which just gives a new group_id to every new record. I believe I need a SELECT Statement somewhere to check if there is a group_id already given, but I am confused on how I go about it.
Thank you
Schema:
create database xGrpId; -- create a test db
use xGrpId; -- use it
CREATE TABLE _tempTerms(
ID int(8) unsigned NOT NULL AUTO_INCREMENT,
TTC_ART_ID mediumint(8) unsigned,
TTC_TYP_ID mediumint(8) unsigned,
Name varchar(200),
Value varchar(200),
ID_Lang tinyint(3) unsigned,
Sequence smallint unsigned,
Group_ID int(8) unsigned DEFAULT 0,
PRIMARY KEY(TTC_ART_ID, TTC_TYP_ID, Name, Value),
UNIQUE KEY(ID)
);
-- truncate table _tempTerms;
insert _tempTerms(TTC_ART_ID,TTC_TYP_ID,Name,Value,ID_Lang,Sequence) values
(1,2,'n','v1',66,4),
(1,1,'n','v2',66,4),
(1,1,'n','v3',66,3),
(1,1,'n','v4',66,4),
(1,1,'n','v5',66,4),
(1,1,'n','v6',66,3),
(2,1,'n','v7',66,4),
(1,2,'n','v8',66,4);
View them:
select * from _tempTerms order by id;
select distinct TTC_ART_ID,TTC_TYP_ID,Sequence from _tempTerms;
-- 4 rows
-- update _tempTerms set Group_ID=0; -- clear before testing
The query:
update _tempTerms t
join
( select TTC_ART_ID,TTC_TYP_ID,Sequence,#rn:=#rn+1 as rownum
from
( select distinct TTC_ART_ID,TTC_TYP_ID,Sequence
from _tempTerms
-- put your `order by` here if needed
order by TTC_ART_ID,TTC_TYP_ID,Sequence
) d1
cross join (select #rn:=0) as xParams
) d2
on d2.TTC_ART_ID=t.TTC_ART_ID and d2.TTC_TYP_ID=t.TTC_TYP_ID and d2.Sequence=t.Sequence
set t.Group_ID=d2.rownum;
Results:
select * from _tempTerms order by TTC_ART_ID,TTC_TYP_ID,Sequence;
+----+------------+------------+------+-------+---------+----------+----------+
| ID | TTC_ART_ID | TTC_TYP_ID | Name | Value | ID_Lang | Sequence | Group_ID |
+----+------------+------------+------+-------+---------+----------+----------+
| 3 | 1 | 1 | n | v3 | 66 | 3 | 1 |
| 6 | 1 | 1 | n | v6 | 66 | 3 | 1 |
| 2 | 1 | 1 | n | v2 | 66 | 4 | 2 |
| 4 | 1 | 1 | n | v4 | 66 | 4 | 2 |
| 5 | 1 | 1 | n | v5 | 66 | 4 | 2 |
| 1 | 1 | 2 | n | v1 | 66 | 4 | 3 |
| 8 | 1 | 2 | n | v8 | 66 | 4 | 3 |
| 7 | 2 | 1 | n | v7 | 66 | 4 | 4 |
+----+------------+------------+------+-------+---------+----------+----------+
Cleanup:
drop database xGrpId;
d1, d2, and xParams are derived tables. Every derived table needs a name. The purpose of xParams and the cross join is merely to bring in a variable to initialize the row number. This is because mysql lacks CTE functionality found in other RDBMS's. So, don't overthink the cross join. It is like saying LET i=0.

Deleting equal rows in mySQL 5.7.9?

I have this table in mysql called ts1
+----------+-------------+---------------+
| position | email | date_of_birth |
+----------+-------------+---------------+
| 3 | NULL | 1987-09-03 |
| 1 | NULL | 1982-03-26 |
| 2 | Sam#gmail | 1976-10-03 |
| 2 | Sam#gmail | 1976-10-03 |
+----------+-------------+---------------+
I want to drop the equal rows using ALTER IGNORE.
I have tried
ALTER IGNORE TABLE ts1 ADD UNIQUE INDEX inx (position, email, date_of_birth);
and
ALTER IGNORE TABLE ts1 ADD UNIQUE(position, email, date_of_birth);
In both cases I get
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'IGNORE TABLE ts1 ADD UNIQUE(position, email, date_of_birth)' at line 1
I'm using mySQL 5.7.9. Any suggestions?
To do it inline against the table, given just the columns you show consider the below. To do it in a new table as suggested by Strawberry, see my pastie link under comments.
create table thing
( position int not null,
email varchar(100) null,
dob date not null
);
insert thing(position,email,dob) values
(3,null,'1987-09-03'),(1,null,'1982-03-26'),
(2,'SamIAm#gmail.com','1976-10-03'),(2,'SamIAm#gmail.com','1976-10-03');
select * from thing;
+----------+------------------+------------+
| position | email | dob |
+----------+------------------+------------+
| 3 | NULL | 1987-09-03 |
| 1 | NULL | 1982-03-26 |
| 2 | SamIAm#gmail.com | 1976-10-03 |
| 2 | SamIAm#gmail.com | 1976-10-03 |
+----------+------------------+------------+
alter table thing add id int auto_increment primary key;
Delete with a join pattern, deleting subsequent dupes (that have a larger id number)
delete thing
from thing
join
( select position,email,dob,min(id) as theMin,count(*) as theCount
from thing
group by position,email,dob
having theCount>1
) xxx -- alias
on thing.position=xxx.position and thing.email=xxx.email and thing.dob=xxx.dob and thing.id>xxx.theMin
-- 1 row affected
select * from thing;
+----------+------------------+------------+----+
| position | email | dob | id |
+----------+------------------+------------+----+
| 3 | NULL | 1987-09-03 | 1 |
| 1 | NULL | 1982-03-26 | 2 |
| 2 | SamIAm#gmail.com | 1976-10-03 | 3 |
+----------+------------------+------------+----+
Add the unique index
CREATE UNIQUE INDEX `thing_my_composite` ON thing (position,email,dob); -- forbid dupes hereafter
View current table schema
show create table thing;
CREATE TABLE `thing` (
`position` int(11) NOT NULL,
`email` varchar(100) DEFAULT NULL,
`dob` date NOT NULL,
`id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`),
UNIQUE KEY `thing_my_composite` (`position`,`email`,`dob`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;