What schema for an activity / news / timeline feed ? (without friend sytem) - mysql

i was wondering what would be the best schema to show two different type of contents in a feed. I came up with 2 ways, one used by WP and the other one is what i think should be best but i'm still not sure.
First schema and relations.
feed
| id | type | type_id |
shots
| id | user_id | title | desc | image | date creation | date update |
snippet
| id | user_id | title |desc | url | date creation | date update |
In this case, i would do something like
SELECT *
FROM feed
INNER JOIN shots
ON feed.type_id = shots.id
INNER JOIN snippets
ON feed.type_id = snippets.id
And the other schema would be WP type schema
feed
| id | user_id | title | desc | image | url | date creation | date update
And only one query without join would be done, BUT all records would be in the same table and potentially if i had other fields i would add columns here, which is not scalable at all.
I could change to the following
extra
| id | owner_id | name | value |
| 1 | 123 | url | ..... |
| 2 | 456 | image| ..... |
feed
| id | user_id | title | desc | date creation | date update
And eventually i would have the following
SELECT *
FROM feed
LEFT JOIN extra
ON feed.id = extra.owner_id
Which one is better ? Keep in mind that in future i would have more and more different kind of content (questions, videos, comments, tutorials, posts...)

Related

MySQL - Recursive - getting email addresses from 2 different tables and columns

I have a first table called emails with a list of all the emails of my colleagues
| email |
| ----------------------- |
| saramaia#email.com |
| miguelferreira#email.com |
| joaosilva#email.com |
| joanamaia#email.com |
I have a second table called aliases, with a list of all the secondary emails/aliases my colleagues are using
| alias1 | alias2 |
| ------------------------ | ------------------- |
| joanamaia#email.com | maiajoana#email.com |
| maiajoana#email.com | maia#email.com |
| miguelferreira#email.com | miguel#email.com |
| maia#email.com | joana#email.com |
| joanamaia#email.com | jomaia#email.com |
| joana#email.com | jmaia#email.com |
I can see that the users joanamaia#email.com and miguelferreira#email.com are using aliases. But let's focus on the user joanamaia#email.com.
I need to get a list of all the email addresses the user joanamaia#email.com is using. The difficult part is that I need to get a list with the main email address plus all the intersections where the first email and consecutive ones are being used by this user. The end result should look like this
| emails |
| ------------------- |
| joanamaia#email.com |
| jomaia#email.com |
| maiajoana#email.com |
| maia#email.com |
| joana#email.com |
| jmaia#email.com |
If I do WHERE email='joanamaia#email.com' it should look like this, but I also need the same result if I do
WHERE email='jmaia#email.com'
I've been through some days of testing queries and I don't seem to have a solution for this (I've been using right joins, full outer joins and unions, but no luck so far). Is there a good way to do this?
You can use a recursive CTE to walk the graph and get the full list of interconnected aliases. Care needs to be taken to handle cycles; that requires the query to use UNION instead of the traditional UNION ALL to separate the anchor and recursive member of the CTE.
The query can take the form:
with recursive
n as (
select 'joanamaia#email.com' as email
union
select case when a.alias1 = n.email then a.alias2 else a.alias1 end
from n
join aliases a on (a.alias1 = n.email or a.alias2 = n.email)
and a.alias1 <> a.alias2
)
select * from n;
Result:
email
-------------------
joanamaia#email.com
maiajoana#email.com
jomaia#email.com
maia#email.com
joana#email.com
jmaia#email.com
See running example at DB Fiddle.

MySQL: Grouping Duplicate ID with IF Condition

I have a table tblRater (which conatins all the users who can rate a topic) whose sample data is as follows:
RaterID | Name |
=================
rater_1 |Katty |
rater_2 |Batty |
rater_3 |Ratty |
rater_4 |Shatty |
rater_5 |Patty |
there is another table tblAuthorizedRater (which contains the data about which rater is assigned to rate which topic) whose sample data is as follows:
TopicID | RaterID |
===================
topic_1 | rater_1 |
topic_1 | rater_2 |
topic_1 | rater_3 |
topic_2 | rater_2 |
topic_2 | rater_3 |
topic_3 | rater_1 |
topic_3 | rater_2 |
topic_6 | rater_1 |
So when I look up for the list of raters who are allowed to rate topic_1, I expect the following data:
RaterID | Authorized|
=====================
rater_1 | 1 |
rater_2 | 1 |
rater_3 | 1 |
rater_4 | 0 |
rater_5 | 0 |
The above table is basically list of all the raters from tblRater and the list of authorized raters for topic_1 from tblAuthorizedRater.
I tried doing the left join with tblRater on left and tblAuthorizedRater on right and an if condition. However the results I am getting repeats the RaterID and if I do a group by on it, I am not getting the expected result.
Just for reference, this is the query I am trying so far:
select
tr.RaterID,
IF(tar.TopicID = 'topic_1', 1, 0) as Authorized
from tblRater as tr
left join tblAuthorizedRater as tar
on tr.RaterID = tar.RaterID;
I also tried looking up at subselect, union, but none of it helped.
Put topicID = 'topic_1' in the ON clause, so you only select those rows from tblAuthorizedRater. Then check whether the join found a matching row by testing for NULL.
SELECT
tr.RaterID,
tar.topicID IS NOT NULL AS Authorized
FROM tblRater AS tr
LEFT JOIN tblAuthorizedRater AS tar
ON tr.RaterID = tar.RaterID AND tar.TopicID = 'topic_1'
DEMO

Select value from table sorted by a certain order from another table

I want to select value from table sorted by a certain order.
I have a table called test that looks like this:
| date | code | value |
+----------+-----------+----------+
| 20050104 | 000005.SZ | -6359.19 |
| 20050104 | 600601.SH | -7876.34 |
| 20050104 | 600602.SH | -25693.3 |
| 20050104 | 600651.SH | NULL |
| 20050104 | 600652.SH | -15309.9 |
...
| 20050105 | 000005.SZ | -4276.28 |
| 20050105 | 600601.SH | -3214.56 |
...
| 20170405 | 000005.SZ | 23978.13 |
| 20170405 | 600601.SH | 32212.54 |
Right now I want to select only one date, say date = 20050104, and then sort the data by a certain order (the order that each stock was listed in the stock market).
I have another table called stock_code which stores the correct order:
+---------+-----------+
| code_id | code |
+---------+-----------+
| 1 | 000002.SZ |
| 2 | 000004.SZ |
| 3 | 600656.SH |
| 4 | 600651.SH |
| 5 | 600652.SH |
| 6 | 600653.SH |
| 7 | 600654.SH |
| 8 | 600602.SH |
| 9 | 600601.SH |
| 10 | 000005.SZ |
...
I want to sorted the selected data by stock_code(code_id), but I don't want to use join because it takes too much time. Any thoughts?
I tried to use field but it gives me an error, please tell me how to correct it or give me an even better idea.
select * from test
where date = 20050104 and code in (select code from stock_code order by code)
order by field(code, (select code from stock_code order by code));
Error Code: 1242. Subquery returns more than 1 row
You told us that you don't want to join because it takes too much time, but the following join query is probably the best option here:
SELECT t.*
FROM test t
INNER JOIN stock_code sc
ON t.code = sc.code
WHERE t.date = '20050104'
ORDER BY sc.code_id
If this really runs slowly, then you should check to make sure you have indices setup on the appropriate columns. In this case, indices on the code columns from both tables as well as an index on test.date should be very helpful.
ALTER TABLE test ADD INDEX code_idx (code)
ALTER TABLE test ADD INDEX date_idx (date)
ALTER TABLE code ADD INDEX code_idx (code)

Join multiple tables with same column name

I have these tables in my MySQL database:
General table:
+----generalTable-----+
+---------------------+
| id | scenario | ... |
+----+----------+-----+
| 1 | facebook | ... |
| 2 | chief | ... |
| 3 | facebook | ... |
| 4 | chief | ... |
Facebook Table:
+----facebookTable-----+
+----------------------+
| id | expiresAt | ... |
+----+-----------+-----+
| 1 | 12345678 | ... |
| 3 | 45832458 | ... |
Chief Table:
+------chiefTable------+
+----------------------+
| id | expiresAt | ... |
+----+-----------+-----+
| 2 | 43547343 | ... |
| 4 | 23443355 | ... |
Basically, the general table holds some (obviously) general data. Based on the generalTable.scenario you can look up more details in the other two tables, which are in some columns familiar (expiresAt for example) but in others not.
My question is, how to get the joined data of generalTable and the right detailed table in just one query.
So, I would like a query like this:
SELECT id, scenario, expiresAt
FROM generalTable
JOIN facebookTable
ON generalTable.id = facebookTable.id
JOIN chiefTable
ON generalTable.id = chiefTable.id
And an output like this:
| id | scenario | expiresAt |
+----+----------+-----------+
| 1 | facebook | 12345678 |
| 2 | chief | 43547343 |
| 3 | facebook | 45832458 |
| 4 | chief | 23443355 |
However, this doesn't work, because both facebookTable and chiefTable have ambiguous column name "expiresAt". For the ease of use I want to keep it that way. The result table should also only have one column "expiresAt" that is automatically filled with the right values from either facebookTable or chiefTable.
You might want to consider adding expiredAt to your general table, and removing it from the others, to remove duplication in the schema, and to make this particular query simpler.
If you need to stick with your current schema, you can use table aliases to resolve the name ambiguity, and use two joins and a union to create the result you are looking for:
SELECT g.id, g.scenario, f.expiresAt
FROM generalTable g
JOIN facebookTable f
ON g.id = f.id
UNION ALL
SELECT g.id, g.scenario, c.expiresAt
FROM generalTable g
JOIN chiefTable c
ON g.id = c.id;
The outer join approach mentioned in another answer would also solve the problem.
One way you could accomplish it is with LEFT JOIN. In the result fields you can do something like this for common fields IF(fTbl.id IS NULL, cTbl.expiresAt, fTbl.expiresAt) AS expiresAt.

MySQL - Select everything from one table, but only first matching value in second table

I'm feeling a little rusty with creating queries in MySQL. I thought I could solve this, but I'm having no luck and searching around doesn't result in anything similar...
Basically, I have two tables. I want to select everything from one table and the matching row from the second table. However, I only want to have the first result from the second table. I hope that makes sense.
The rows in the daily_entries table are unique. There will be one row for each day, but maybe not everyday. The second table notes contains many rows, each of which are associated with ONE row from daily_entries.
Below are examples of my tables;
Table One
mysql> desc daily_entries;
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| eid | int(11) | NO | PRI | NULL | auto_increment |
| date | date | NO | | NULL | |
| location | varchar(100) | NO | | NULL | |
+----------+--------------+------+-----+---------+----------------+
Table Two
mysql> desc notes;
+---------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+---------+------+-----+---------+----------------+
| task_id | int(11) | NO | PRI | NULL | auto_increment |
| eid | int(11) | NO | MUL | NULL | |
| notes | text | YES | | NULL | |
+---------+---------+------+-----+---------+----------------+
What I need to do, is select all entries from notes, with only one result from daily_entries.
Below is an example of how I want it to look:
+----------------------------------------------+---------+------------+----------+-----+
| notes | task_id | date | location | eid |
+----------------------------------------------+---------+------------+----------+-----+
| Another note | 3 | 2014-01-02 | Home | 2 |
| Enter a note. | 1 | 2014-01-01 | Away | 1 |
| This is a test note. To see what happens. | 2 | | Away | 1 |
| Testing another note | 4 | | Away | 1 |
+----------------------------------------------+---------+------------+----------+-----+
4 rows in set (0.00 sec)
Below is the query that I currently have:
SELECT notes.notes, notes.task_id, daily_entries.date, daily_entries.location, daily_entries.eid
FROM daily_entries
LEFT JOIN notes ON daily_entries.eid=notes.eid
ORDER BY daily_entries.date DESC
Below is an example of how it looks with my query:
+----------------------------------------------+---------+------------+----------+-----+
| notes | task_id | date | location | eid |
+----------------------------------------------+---------+------------+----------+-----+
| Another note | 3 | 2014-01-02 | Home | 2 |
| Enter a note. | 1 | 2014-01-01 | Away | 1 |
| This is a test note. To see what happens. | 2 | 2014-01-01 | Away | 1 |
| Testing another note | 4 | 2014-01-01 | Away | 1 |
+----------------------------------------------+---------+------------+----------+-----+
4 rows in set (0.00 sec)
At first I thought I could simply GROUP BY daily_entries.date, however that returned only the first row of each matching set. Can this even be done? I would greatly appreciate any help someone can offer. Using Limit at the end of my query obviously limited it to the value that I specified, but applied it to everything which was to be expected.
Basically, there's nothing wrong with your query. I believe it is exactly what you need because it is returning the data you want. You can not look at as if it is duplicating your daily_entries you should be looking at it as if it is return all notes with its associated daily_entry.
Of course, you can achieve what you described in your question (there's an answer already that solve this issue) but think twice before you do it because such nested queries will only add a lot of noticeable performance overhead to your database server.
I'd recommend to keep your query as simple as possible with one single LEFT JOIN (which is all you need) and then let consuming applications manipulate the data and present it the way they need to.
Use mysql's non-standard group by functionality:
SELECT n.notes, n.task_id, de.date, de.location, de.eid
FROM notes n
LEFT JOIN (select * from
(select * from daily_entries ORDER BY date DESC) x
group by eid) de ON de.eid = n.eid
You need to do these queries with explicit filtering for the last row. This example uses a join to do this:
SELECT n.notes, n.task_id, de.date, de.location, de.eid
FROM daily_entries de LEFT JOIN
notes n
ON de.eid = n.eid LEFT JOIN
(select n.eid, min(task_id) as min_task_id
from notes n
group by n.eid
) nmin
on n.task_id = nmin.min_task_id
ORDER BY de.date DESC;