Read notifications table - mysql

I am making a notifications table. That is structured like this:
notifications:
+----+------+----------------------+-----------------------------------+-----------+
| ID | user | notification | message | timestamp |
+----+------+----------------------+-----------------------------------+-----------+
| 1 | 6 | Denied acceptance | You have been denied for... | |
| 2 | 6 | Apreooved acceptance | You have been accepted... | |
| 3 | 0 | Open slots | there are still open slots for... | |
+----+------+----------------------+-----------------------------------+-----------+
And a table that indicates which notification was read by each user
notifications_read
+----+------+--------------+-----------+
| ID | user | notification | timestamp |
+----+------+--------------+-----------+
| 1 | 6 | 2 | |
+----+------+--------------+-----------+
If notification is meant only for one user, there is a user id under "user" in "notifications"- If the notification is meant for everybody I insert '0' under user.
I am trying to make a query that selects rows from "notifications", that is not read.
My current query is not ok:
SELECT *
FROM notifications
WHERE id NOT IN
(SELECT notification
FROM notification_read
WHERE user = $id) AND user = $id OR user = 0
So for above table it needs to select the rows with id 1 & 3
Thank you for the help!

If you want unread notifications for a particular user, then you are on the right track:
SELECT n.*
FROM notifications n
WHERE n.id NOT IN (SELECT nr.notification
FROM notification_read nr
WHERE nr.user = $id) AND
(n.user = $id OR n.user = 0);
I think the only issue is the parentheses for the logic around OR.
You can also write this using IN:
SELECT n.*
FROM notifications n
WHERE n.id NOT IN (SELECT nr.notification
FROM notification_read nr
WHERE nr.user = $id) AND
n.user IN (0, $id);

SELECT n.*
FROM notifications n
LEFT JOIN notifications_read nr ON nr.notification = n.id
WHERE
nr.id IS NULL
This will give you notifications that don't exist n the notifications_read table.
Left join will give you all rows in notifications and join rows from notifications_read that have a notification id. So when you filter by nr.id IS NULL you will return rows that exist in notifications but not in notifications_read.
Additionally, the LEFT JOIN will perform better.
A LEFT [OUTER] JOIN can be faster than an equivalent subquery because the server might be able to optimize it better—a fact that is not specific to MySQL Server alone.
https://dev.mysql.com/doc/refman/5.7/en/rewriting-subqueries.html

Related

Remove duplicate form select result

#edited
Ok. I will delete all role permissions and add them again.
Is it possible to combine all 4 queries into one?
I am creating a permission system.
Assumptions:
Each user can have more than one role
Each role can have more than one permission
Permissions can also be assigned directly to the user (they have a higher priority than permissions for roles)
The priority of permissions is:
role permission
denial of role
user permission
denied to user
Denying the user has the highest priority
The matter in PHP is quite simple:
I create an array with all permissions
I am getting permissions for a role (order by access)
I assign access, if it's denied, I overwrite access with denied
I do the same for user permissions
I assign access, if it's denied, I overwrite access with denied
This way I have the whole array with permissions for a specific user, e.g. $ user['permission']['delete_post'] // output: false || true
I need to do permission inspection now. This means which user has access to e.g. 'delete_post'
I have this database scructure:
Here fiddle with database: DB fiddle
I have problem with first query:
**Query #1**
=============================================
List of all roles related to permission with id 3 */
SELECT DISTINCT role_id, access, permission_id FROM role_permissions WHERE permission_id=3 ORDER BY role_id, access DESC;
| role_id | access | permission_id |
| ------- | ------ | ------------- |
| 5 | 0 | 3 |
| 8 | 1 | 3 |
| 10 | 1 | 3 |
| 10 | 0 | 3 |
As expected I should get
| role_id | access | permission_id |
| ------- | ------ | ------------- |
| 8 | 1 | 3 |
I cant add WHERE permission_id=3 AND access=1, because i getting result: role_id=8 and role_id=10, but role_id=10 doesn't really have access.
One way is to do all appropriate joins between various tables based on their relationships, and then do aggregation based filtering using GROUP BY with HAVING clause.
Following query will give you all the users who has access allowed for a given permission id (more explanation in comments inside the query below - there may be requirement of more fiddling with the logic; check and comment if needed):
Query - View on DB Fiddle
SELECT
u.user_id,
u.name
FROM users AS u
-- Left Join to get access (if defined for input permission id)
LEFT JOIN user_permissions AS up
ON up.user_id = u.user_id
AND up.permission_id = 3
-- Join to Roles; assuming every user has atleast one role
-- Change this to LEFT JOIN if it is possible that user can have NO role
JOIN user_roles AS ur
ON ur.user_id = u.user_id
-- Left Join to get access defined for input permission id for roles
LEFT JOIN role_permissions AS rp
ON rp.role_id = ur.role_id
AND rp.permission_id = 3
GROUP BY u.user_id, u.name
HAVING
-- first priority if user specific access allowed
-- if data is sane then up.access will have same value
-- across all rows in a group for user
MAX(up.access) = 1
OR
-- second priority is not have a single role with
-- denied permission AND
-- atleast one role exists with allowed permission
( NOT SUM(rp.access = 0)
AND
SUM(rp.access = 1)
);
Result
| user_id | name |
| ------- | ---------------- |
| 4 | Cyrus Gomez |
| 7 | MacKensie Morton |
| 13 | Nadine Taylor |
| 15 | Ezekiel Bonner |
| 17 | Ciaran Turner |
| 35 | Olga Dominguez |
| 38 | Lucas Pierce |
You can add a WHERE condition as below-
SELECT DISTINCT role_id, access, permission_id
FROM role_permissions
WHERE permission_id=3
AND role_id NOT IN (
SELECT DISTINCT role_id
FROM role_permissions
WHERE access = 0
AND permission_id=3 -- New condition added
)
ORDER BY role_id, access DESC;

Retrieve data from tables via JOINS

I have three tables: sessions, urls and visitors.
I need to join these three tables in such a way that I should be able to get data from each table and the maximum number of rows returned should be equal to sessions.
Following is the basic schema of my tables.
Table sessions
session_id | url_id | referrer_id | country
-------------------------------------------
1234 | a1b1 | bb11 | US
4567 | x1y1 | ll33 | IN
6789 | a1b1 | ff99 | UK
Table urls
id | url |
-----------------------------------------
a1b1 | https://url-1.com |
x1y1 | https://url-2.com |
bb11 | https://referrer-url-1.com |
ll33 | https://referrer-url-2.com |
ff99 | https://referrer-url-3.com |
Table visitors
id | session_id | visiting_time |
-----------------------------------------
1 | 1234 | 447383930 |
2 | 4567 | 547383930 |
3 | 6789 | 647383930 |
What I want as the final output should look like:
session_id | visiting_time | url | referrer_url | country
------------------------------------------------------------------------------------------
1234 | 447383930 | https://url-1.com | https://referrer-url-1.com | US |
4567 | 547383930 | https://url-2.com | https://referrer-url-2.com | IN |
6789 | 647383930 | https://url-1.com | https://referrer-url-3.com | UK |
I want to map url_id in sessions table with id in urls table and get the corresponding url from urls table and have the value in the new column named url. Similarly, map referrer_id in sessions table with id in urls table and get the corresponding url from urls table and have the value in the new column named referring_url.
As you can see: JOINS with sessions and visitors is simple and can be simply done via:
select session_id, visiting_time, country
from sessions,
visitors
where sessions.session_id = visitors.session_id;
But joining with urls table and getting the url and referring_url is somewhat tricky. I have tried LEFT JOIN and INNER JOIN but couldn't make it work.
Any help with query or references would be helpful.
Thanks!
You should avoid using comma based Implicit joins and use Explicit Join based syntax
You will need two joins with urls table; one to fetch the url and another for referrer_url.
Try the following:
SELECT s.session_id,
v.visiting_time,
u1.url,
u2.url AS referrer_url,
s.country
FROM sessions AS s
JOIN visitors AS v ON v.session_id = s.session_id
JOIN urls AS u1 ON u1.id = s.url_id
JOIN urls AS u2 ON u2.id = s.referrer_id
select sessions.session_id, visitors.visiting_time, urls.url, urlsReferrer.url referrer_url, sessions.country
from sessions
inner join visitors on sessions.session_id = visitors.session_id
inner join urls on sessions.url_id = url.id
left join urls urlsReferrer on sessions.referrer_id = urlsReferrer.id
You should use a join on urls twice one of url_id and one for referrer_id
select session_id
, visiting_time
, u1.url
, u2.url
, country
from sessions
INNER JOIN visitors ON sessions.session_id = visitors.session_id
INNER JOIN urls u1 on u1.id= sessions.url_id
INNER JOIN urls u2 on u2.id= sessions.referrer_id
In this way you can join the sessions for retrive both then values you need
Joins are defined in the from statement - please read up on https://www.w3schools.com/sql/default.asp to better get a sense of join usage.
Modify the query as needed based on which table "referrer_url" actually comes from
Warning: You must include a where statement which limits your result. I strongly suggest defining a date field and range to prevent you from initiating a long running query and affecting database performance.
see below for query
select
s.session_id,
v.visiting_time,
s.country,
u.url,
u.referrer_url
from
sessions s
join visitors v on session_id
join urls on u.id=s.url_id
;

MySQL: Grouping and counting multiple occurences, then only display the highest amount per individual combination

I'm currently trying to determine which user owns which PC in my company.
We're logging every login activity in a MySQL table.
Now we only have information about which User is using which client, but given that users may also use other machines, I need to check which user has the highest amount of logins on that specific machine.
Table: log
+----+-----------+------+----------------+
| ID | Client | user | timestamp |
+----+-----------+------+----------------+
| 1 | hostnamea | ab | xx.xx.xx xx:xx |
| 2 | hostnameb | cd | xx.xx.xx xx:xx |
| 3 | hostnameb | ab | xx.xx.xx xx:xx |
| 4 | hostnameb | ab | xx.xx.xx xx:xx |
| 5 | hostnamec | ab | xx.xx.xx xx:xx |
| 6 | hostnamec | ef | xx.xx.xx xx:xx |
| 7 | hostnamec | ef | xx.xx.xx xx:xx |
+----+-----------+------+----------------+
(timestamp is actually unimportant,
but maybe someone has an awesome optimization idea which includes the timestamp,
so I leave it here)
In this case, hostname user "ef" becomes owner of "hostnamec"
I already got so far that I can display a "top logins" list:
SELECT `user`, `client`, COUNT(*) logins
FROM `log`
WHERE `client` LIKE '_%' AND timestamp > "2016-09-00 00:00:00" # (filter out garbage and old machines)
GROUP BY `client, `user`
HAVING COUNT(*) > 10 (If there are less than 10 logins, the machine isn't being used anyways)
ORDER BY `user`;
This returns user, client and a number of logins, which, I hope, specify how often a specific user logged into a machine.
I don't really know how to access and use the extra "logins" row generated by the count.
My goal is something like this:
+-----------+------+
| Client | user |
+-----------+------+
| hostnamea | ab | (1xab, nothing else: ab wins)
| hostnameb | ab | (2x ab,1x cd: ab wins)
| hostnamec | ef | (2x ef, 1x ab: ef wins)
+-----------+------+
My next step would be an Update on another existing table that has hostnames, but it's missing users
UPDATE hosts
INNER JOIN query(output of first query) USING (client)
SET hosts.user = query.user
(If that makes sense)
Can anyone assist me in my first query?
I already found some solutions including Oracle functions, or tables that are slightly diffently designed.
It's been long time since I had to do more than simple Selects, updates and inserts with SQL; to be honest, I got kinda confused by what I found, so I'm asking my own question
You can do this by filtering in the where clause. Here is one way:
select l.*
from log l
where l.timestamp = (select max(l2.timestamp)
from log l2
where l2.user = l.user
);
This is easily generalizable to get the owner as of any given date:
select l.*
from log l
where l.timestamp = (select max(l2.timestamp)
from log l2
where l2.timestamp <= $AsOfDate and l2.user = l.user
);
Try this query
SELECT
Client
, SUBSTRING_INDEX(GROUP_CONCAT(user ORDER BY Client DESC), ',', 1) user
FROM
log
GROUP BY
Client
ORDER BY
Client ASC

MySQL Select using one table's data as column for query

I have a database table which each row represents a column of data I'd like pulled for a query. My table is:
tracked_pages
page_id | page_url | page_name
-------------------------------------
1 | users.php | Users
2 | auctions.php | Auctions
3 | receipts.php | Receipts
So essentially, I have another table that tracks each time a page is accessed by a logged-in user. But these 3 pages are the only ones I'm really interested in right now.
This is not my actual query, but just to show how I'd write it if I just hand-wrote it, I'd do something like:
Select user.id, count(clicks1.id) As user_clicks, count(clicks2.id) As auction_clicks, count(clicks3.id) As receipt_clicks
From users
Left Join clicks As clicks1 On (users.id = clicks1.id And clicks1.page = 'users.php')
Left Join clicks As clicks2 On (users.id = clicks2.id And clicks2.page = 'auctions.php')
Left Join clicks As clicks3 On (users.id = clicks3.id And clicks.page = 'receipts.php')
Group By users.id
So I'd expect output similar to:
Output
user_id | user_clicks | auction_clicks | receipt_clicks
-------------------------------------------------------
1 | 54 | 16 | 16
2 | 27 | 22 | 12
3 | 32 | 24 | 38
However, I'd like the query to pull each column from the database instead of having to join the same table manually. The reason is because the tracked_pages table can be updated through a management area and I don't want to have to go update the code each time they want to track a different page.
I know I could just perform a query to get the users, then as I loop through that I could perform another query to get the page counts, but I'm really wanting to make it just one query.
Any help would be appreciated.
Thanks,
James

MySQL Query JOIN returning 1 null field

I have two simple tables:
user
+-------------------------------------------------+
| uid | firstname | lastname |
|-------------------------------------------------|
| 4000 | Zak | Me |
+-------------------------------------------------+
user_role
+----------------------------------------------+
| rid | uid | oid |
|----------------------------------------------|
| 5 | 4000 | 7000 |
+----------------------------------------------+
I am using this query
SELECT us.firstname, us.lastname, ur.oid
FROM user us
LEFT JOIN user_role ur
ON us.uid = ur.uid
WHERE us.firstname = 'Zak';
My result is
+-------------------------------------------------+
| firstname | lastname | oid |
|-------------------------------------------------|
| Zak | Me | (null) |
+-------------------------------------------------+
What am I missing? It has to be something simple!
UPDATE
Has to do something with the WHERE clause .. Because if left out, it returns all rows with oid included
Follow a process like this:
run this query
SELECT *
FROM user us
WHERE us.firstname = 'Zak';
If you get a result the Where clause is fine. So now you run:
SELECT *
FROM user us
LEFT JOIN user_role ur
ON us.uid = ur.uid
WHERE us.firstname = 'Zak';
If yuo get no records then there is something wrong with the join. Could be that they have some unprintable characters and 4000 <>4000 as a result. So let's check that.
select * FROM user us where us.uid = 4000
select * FROM user_role us where us.uid = 4000
If one of then does not return a result then there is a data problem where one of the fields contains unprintable characters.
If the select * works, then try the original query again only add a few other fields from the user_role table such as the uid. Then you can see if the join is working but the field is empty or if the join is wrong or possibly you are looking at the wrong field.
SELECT us.firstname, us.lastname, ur.oid, ur.uid
FROM user us
LEFT JOIN user_role ur
ON us.uid = ur.uid
WHERE us.firstname = 'Zak';
It is also possible the join fields are different datatypes and some type of implicit conversion is messing them up. In that case you probably want to explicitly convert or preferably design your table so that they both use the same data type as they appear to be in a PK/FK relationship.