Fetch timestamped counts from multiple tables - mysql

I have a table of nodes:
nid type created status
2 blog 134292319 1
3 forum 134292536 1
4 blog 135921392 0
To graph the number of published (status=1) nodes over time, I execute this query:
SELECT created, type
FROM node WHERE status = 1
ORDER BY created
I then go through this data set in PHP, splitting it into timestamped groups with a node count associated with each group. The results are cached, so slow execution isn't a problem.
I also have a table of comments:
nid timestamp status
2 134292363 1
3 134293234 1
I want to incorporate forum comment counts into the graph of node counts.
To get the comment counts, I would run this query:
SELECT timestamp
FROM comments
INNER JOIN node ON comments.nid = node.nid
WHERE
node.type = 'forum'
AND comments.status = 1
ORDER BY timestamp
I need to somehow combine these two queries, to end up with (for the examples given):
created type
134292319 blog
134292536 forum
134293234 forum_comment
Any ideas?
Thanks.

This will get you your example output but I am not sure it's exactly what you are looking for based on your description of the question.
SELECT created, type FROM
(
SELECT created, type
FROM node WHERE status = 1
UNION ALL
SELECT timestamp as created, 'forum_comment' as type
FROM comments
INNER JOIN node ON comments.nid = node.nid
WHERE node.type = 'forum'
AND comments.status = 1
) AS U
ORDER BY U.created

Related

I need help to figure out syntax for a view in an existing PostgreSQL database that extracts (unnests) content in a JSON array field

I am working on a JSON array field named session_durations in existing PostgreSQL 11.8 database view. Each field describes the sessionID and the duration (amount of time a program user visits that session). There are 12 possible sessions, an "session" refers here to online lesson in an eHealth treatment program.
This JSON field (session_durations) is populated as the user accesses the session. If user never accesses a session then no data appears in the JSON field for that session (see my examples) -- hence some sessions can be skipped over entirely.
I'd like to use SQL code to unpack this field in order to separate its components. Here are 2 example records:
Record 1: [{"sessionId":"7","duration":1886400},{"sessionId":"8","duration":1710000},{"sessionId":"9","duration":706800}]
Record 2: [{"sessionId":"1","duration":879600},{"sessionId":"2","duration":975600},{"sessionId":"3","duration":9600}]
I'd like to use my View to save duration data (e.g., "duration":879600) from each possible session into 12 new columns for each user session (e.g., "sessionId":"1") named the following:
• S1_duration
• S2_duration
• S3_duration
• S4_duration
• S5_duration
• S6_duration...
• S12_duration
All help would be greatly appreciated!!
Table:
CREATE TABLE users (
id int4 PRIMARY KEY,
session_durations json
);
----some rows of data:
13 [{"sessionId":"1","duration":12699},{"sessionId":"7","duration":1423041},{"sessionId":"8","duration":7598502},{"sessionId":"10","duration":1531229}]
14 [{"sessionId":"1","duration":55812},{"sessionId":"7","duration":2905}]
161 [{"sessionId":"7","duration":1125600},{"sessionId":"8","duration":460800}]
12 [{"sessionId":"1","duration":1520988},{"sessionId":"2","duration":94565},{"sessionId":"6","duration":35468}]
Your solutions worked perfectly! I chose to use the second solution (grouping syntax) for my project. Thanks for your patience -- and the online demo examples!
This should do:
WITH usd AS (
SELECT
us.id AS user_id,
(sd->>'sessionId')::int AS session_id,
(sd->>'duration')::int AS duration
FROM users us,
LATERAL json_array_elements(us.session_durations) AS sd
)
SELECT
users.id AS user_id,
(SELECT duration FROM usd WHERE user_id = users.id AND session_id = 1) AS "S1_duration",
(SELECT duration FROM usd WHERE user_id = users.id AND session_id = 2) AS "S2_duration",
(SELECT duration FROM usd WHERE user_id = users.id AND session_id = 3) AS "S3_duration",
…
(SELECT duration FROM usd WHERE user_id = users.id AND session_id = 12) AS "S12_duration"
FROM users;
(online demo)
Alternatively, using grouping and some filtered aggregate (dealing better with potential duplicates):
SELECT
user_id,
MAX(duration) FILTER (WHERE session_id = 1) AS "S1_duration",
MAX(duration) FILTER (WHERE session_id = 2) AS "S2_duration",
MAX(duration) FILTER (WHERE session_id = 3) AS "S3_duration",
…
MAX(duration) FILTER (WHERE session_id = 12) AS "S12_duration"
FROM (
SELECT
us.id AS user_id,
(sd->>'sessionId')::int AS session_id,
(sd->>'duration')::int AS duration
FROM users us,
LATERAL json_array_elements(us.session_durations) AS sd
) AS usd
GROUP BY user_id;
(online demo)

Getting X results from SQL with multiple tables

(Sorry for my bad english, I'll try to be the clearest)
I want to select 5 conversations (over an undetermined number, there could be 5 or 300 conversations) of one user in a MySQL table, and for each of those, I want to select all the users who talk in it.
In a wonderfull world, I'd like to do it with one query.
My query looks like (tables are in french, plz don't hurt me) :
SELECT mc.mc_id, mc.mc_sujet, mc.mc_statut,
miu.mi_ustatut as uself_statut, miu.mi_datelecture as uself_datelecture,
mi.mi_uid, mi.mi_ustatut, mi.mi_datelecture,
u.u_pseudonyme
FROM msg_individus as miu
LEFT JOIN msg_conversations as mc ON mc.mc_id = miu.mi_mcid
LEFT JOIN msg_individus as mi ON mi.mi_mcid = mc.mc_id
LEFT JOIN u_individus as u ON u.u_id = mi.mi_uid
WHERE miu.mi_uid = :u_id
Where msg_individus is the table with participants of a conversation,
msg_conversations is the table of the conversation (id, subject, status),
u_individus is the table with users' informations.
To select only 5 of those conversations, I added something like
GROUP BY mc.mc_id,
LIMIT 0,5
But of course, only one user per conversation is given is this way.
I also tried to write GROUP BY mc.mc_id, mi.mi_uid but this, like no writting a GROUP BY condition, returns 5 iterations like :
(Conversation 1 has two users, conversation 2 has one, conversation 3 has four)
Iteration 1 : conversation 1, user 1
Iteration 2 : conversation 1, user 2
Iteration 3 : conversation 2, user 1
Iteration 4 : conversation 3, user 1
Iteration 5 : conversation 3, user 2
What I want is to get five CONVERSATIONS with all their datas (whatever the number of users in it, etc)
I guess I'll have to use two queries (after getting the 5 conversations, I'll get the users per conversations), but maybe you guys can light me with your knowledges.
Thx.
Use a subquery to get five conversations. I also suggest that you replace the outer joins with inner joins. I think the table keys should all have matches:
SELECT mc.mc_id, mc.mc_sujet, mc.mc_statut,
miu.mi_ustatut as uself_statut, miu.mi_datelecture as uself_datelecture,
mi.mi_uid, mi.mi_ustatut, mi.mi_datelecture,
u.u_pseudonyme
FROM (SELECT miu.*, mc.*
FROM msg_individus miu JOIN
msg_conversations mc
ON mc.mc_id = miu.mi_mcid
WHERE miu.mi_uid = :u_id
ORDER BY rand() -- not necessary, but why not?
LIMIT 5
) ic
msg_individus mi
ON mi.mi_mcid = ic.mc_id JOIN
u_individus u
ON u.u_id = ic.mi_uid;

SELECT last row grouped by account and assigned number

I have a table "Log"
My game server inserts a record into this table when someone login the server, then inserts a second record when they logout.
What I want to do is create a query to count the number of people logged in.
the main data that gets inserted to the table "Log"
When they Login:
[Type] = 0
[Player1] = Their account ID
[Value2] = a random number which matches the logout row when they logout
[Value3] = 0
When they Logout:
[Type] = 1
[Player1] = Their account ID
[Value2] = a random number which matches the login row when they logout
[Value3] = some random number
Is there a way I can count the last "Player1" of each account and check if "Type" = 0 which means that account is logged in then echo the result.
The result I'm looking for would pull the last record of every account an count them.
Note: everytime an account logs in and out it inserts them 2 records so if 1 account logs in 20 times there would be 40 records in "Log"
One way to do it is to count all rows with type 0 for which there doesn't exist any type 1 row with the same player and a later date:
select count(*) as number_of_logged_in
from log l
where Type = 0 -- 0 meaning log on event
-- and [Value3] = 0 -- maybe this should be included
and not exists (
select 1 from log
where Player1 = l.Player1
and type = 1 -- 1 meaning log out event
and date > l.date
-- and [Value2] = l.[Value2] -- maybe this should be included
);
I found your problem statement a bit confusing as you say you want to count the number of people that are logged in, but then you say I want to count the last of each [Player!] where [Type] is 1 which seems to be the opposite. It's also not clear to me why the random number would be important - if the last recorded type for a user is 0 then they should be considered as logged in, or?
Sample SQL Fiddle with some demo data
I am assuming you want list of the all the logged in players names,so you can try using the ROW_NUMBER() to get what you want,
;WITH CTE AS(
SELECT
Player1 AS LoggedInPlayer,
ROW_NUMBER() OVER (PARTITION BY Player1 ORDER BY datecolumn Asc) As LoggedValue
FROM
yourtable
)
SELECT
*
FROM
CTE
WHERE
LoggedValue = 1
If you know, that all logins and logouts are stored in Log without gaps, you can simply count them and if there's a difference you know, that the player is currently logged in.
SELECT logins.player1, logouts.cnt - logins.cnt
FROM
(select player1, count(*) as cnt from Log where type = 0 group by player1) as logins
LEFT OUTER JOIN
(select player1, count(*) as cnt from Log where type = 1 group by player1) as logouts
ON (logins.player1 = logouts.player1)
WHERE logins.cnt > logouts.cnt or logouts.player1 is null
You need the left outer join, if the player logged in one time and never logged out. Sorry, if you encounter syntax issues. I just wrote this without testing and usually work on a Teradata System and the SQL Dialect there. But as the SQL given here is plain Ansi, it should work on any database.

MYSQL - How to check if a user is following another user, potentially through a subselect?

I'm fetching a list of activities (activities) and using a left join to grab the user data (users) who created the activity. Within my application users have the ability to follow one another.
This is my current query, which grabs all activities not posted by yourself ($user_id)
SELECT
activities.id, activities.user_id, users.id, users.name
FROM
activities
LEFT JOIN
users on activities.user_id = users.id
WHERE
users.id != $user_id
Aside from the activities + users tables, I have a another table in my application called followers:
followers
id | user_id_1 | user_id_2 | followed_back
1 1 3 1
2 2 3 0
3 3 1 1
I need to check whether you ($user_id) have followed a particular user joined to each activity and perhaps call this new field "user_followed" which represents a true/false/null value?
For example, I'm user_id = 1. Based on the above table, this means I have followed user_id 3. When an activity is fetched and user_id 3 is joined / responsible, the new field "user_followed" would be true.
Therefore, I think I'd need to incorporate another SELECT query, checking if the user is being followed:
(SELECT
*
FROM
followers
WHERE
user_id_1 = $user_id AND user_id_2 = users.id
)
I'm just largely unsure of how to incorporate this into my initial query and create a new field representing yes or no. Any help would be much appreciated!

mysql update with a self referencing query

I have a table of surveys which contains (amongst others) the following columns
survey_id - unique id
user_id - the id of the person the survey relates to
created - datetime
ip_address - of the submission
ip_count - the number of duplicates
Due to a large record set, its impractical to run this query on the fly, so trying to create an update statement which will periodically store a "cached" result in ip_count.
The purpose of the ip_count is to show the number of duplicate ip_address survey submissions have been recieved for the same user_id with a 12 month period (+/- 6months of created date).
Using the following dataset, this is the expected result.
survey_id user_id created ip_address ip_count #counted duplicates survey_id
1 1 01-Jan-12 123.132.123 1 # 2
2 1 01-Apr-12 123.132.123 2 # 1, 3
3 2 01-Jul-12 123.132.123 0 #
4 1 01-Aug-12 123.132.123 3 # 2, 6
6 1 01-Dec-12 123.132.123 1 # 4
This is the closest solution I have come up with so far but this query is failing to take into account the date restriction and struggling to come up with an alternative method.
UPDATE surveys
JOIN(
SELECT ip_address, created, user_id, COUNT(*) AS total
FROM surveys
WHERE surveys.state IN (1, 3) # survey is marked as completed and confirmed
GROUP BY ip_address, user_id
) AS ipCount
ON (
ipCount.ip_address = surveys.ip_address
AND ipCount.user_id = surveys.user_id
AND ipCount.created BETWEEN (surveys.created - INTERVAL 6 MONTH) AND (surveys.created + INTERVAL 6 MONTH)
)
SET surveys.ip_count = ipCount.total - 1 # minus 1 as this query will match on its own id.
WHERE surveys.ip_address IS NOT NULL # ignore surveys where we have no ip_address
Thank you for you help in advance :)
A few (very) minor tweaks to what is shown above. Thank you again!
UPDATE surveys AS s
INNER JOIN (
SELECT x, count(*) c
FROM (
SELECT s1.id AS x, s2.id AS y
FROM surveys AS s1, surveys AS s2
WHERE s1.state IN (1, 3) # completed and verified
AND s1.id != s2.id # dont self join
AND s1.ip_address != "" AND s1.ip_address IS NOT NULL # not interested in blank entries
AND s1.ip_address = s2.ip_address
AND (s2.created BETWEEN (s1.created - INTERVAL 6 MONTH) AND (s1.created + INTERVAL 6 MONTH))
AND s1.user_id = s2.user_id # where completed for the same user
) AS ipCount
GROUP BY x
) n on s.id = n.x
SET s.ip_count = n.c
I don't have your table with me, so its hard for me to form correct sql that definitely works, but I can take a shot at this, and hopefully be able to help you..
First I would need to take the cartesian product of surveys against itself and filter out the rows I don't want
select s1.survey_id x, s2.survey_id y from surveys s1, surveys s2 where s1.survey_id != s2.survey_id and s1.ip_address = s2.ip_address and (s1.created and s2.created fall 6 months within each other)
The output of this should contain every pair of surveys that match (according to your rules) TWICE (once for each id in the 1st position and once for it to be in the 2nd position)
Then we can do a GROUP BY on the output of this to get a table that basically gives me the correct ip_count for each survey_id
(select x, count(*) c from (select s1.survey_id x, s2.survey_id y from surveys s1, surveys s2 where s1.survey_id != s2.survey_id and s1.ip_address = s2.ip_address and (s1.created and s2.created fall 6 months within each other)) group by x)
So now we have a table mapping each survey_id to its correct ip_count. To update the original table, we need to join that against this and copy the values over
So that should look something like
UPDATE surveys SET s.ip_count = n.c from surveys s inner join (ABOVE QUERY) n on s.survey_id = n.x
There is some pseudo code in there, but I think the general idea should work
I have never had to update a table based on the output of another query myself before.. Tried to guess the right syntax for doing this from this question - How do I UPDATE from a SELECT in SQL Server?
Also if I needed to do something like this for my own work, I wouldn't attempt to do it in a single query.. This would be a pain to maintain and might have memory/performance issues. It would be best have a script traverse the table row by row, update on a single row in a transaction before moving on to the next row. Much slower, but simpler to understand and possibly lighter on your database.