The question… Is it possible to add MySQL permissions to only allow to select fields based on permissions?
Example:
User user1 can only select/insert/delete from the users table where the column instance is equal to 1 (1 being passed via PHP).
User user2 can only select/insert/delete from the users table where the column instance is equal to 2 (1 being passed via PHP).
Here's the background info:
I'm creating an application with the same code base being used for multiple sites. Conditions within the app load different layouts. The multiple sites are using the same database because most information can be shared between sites. A user that registers on one site must also register on another site (this is how we want it because the sites are "by invitation only")
What I'm thinking of doing is to have users table: id, email, password, instance. The instance column would have the id of the site.
On the application layer every time I need to select/insert/delete from this table I append instance = [site_id] to the query... example: SELECT * FROM users WHERE email = '' AND instance = [site_id];
no, the permission is per table,db,server,etc but not for rows, however there is a solution, you can use view tables and set permission to user, for example
mysql> CREATE VIEW test.v AS SELECT * FROM t where email = '' AND instance = [site_id];
just create 2 view tables and grant access to those users
here is the Mysql documentation
It is not possible from what I know, MySQL doesn't allow conditional users.
Use one user for both sites and modify your all queries accordingly to your 'instance'. So every time you query something site-specific you add WHERE instance = $site_id.
MySQL does not facilitate using permissions to lock down certain rows based on the MySQL user that is connected to the database.
If you have users that you want to limit in such a way, it is probably best to not give them direct database access, but have them connecting through another layer.
Using a view is not a good idea - every user would have to use different queries (referencing their own personal view) to accomplish the same things. If you were just limiting the columns that a user could see (instead of the rows), a view would be a good solution.
It is possible to use stored procedures to accomplish something like what you're looking for:
# This table already exists in your schema
CREATE TABLE `user` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`email` VARCHAR(50) NOT NULL,
`instance` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB;
INSERT INTO `user`(`id`,`email`,`instance`)
VALUES ( NULL,'wat#lol.com','1');
INSERT INTO `user`(`id`,`email`,`instance`)
VALUES ( NULL,'huh#bwu.com','2');
INSERT INTO `user`(`id`,`email`,`instance`)
VALUES ( NULL,'no#yes.lol','1');
# This would be a new table you would have to create
CREATE TABLE `user_instance` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`user` VARCHAR(20) NOT NULL,
`instance` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `user_instance` (`user`,`instance`)
) ENGINE=INNODB;
INSERT INTO `user_instance`(`user`,`instance`) VALUES ('user1','1');
INSERT INTO `user_instance`(`user`,`instance`) VALUES ('user2','2');
# This is a stored procedure that might accomplish what you want
DELIMITER $$
CREATE PROCEDURE `p_get_users`()
BEGIN
SELECT `user`.`id`, `user`.`email`
FROM `user`
WHERE `user`.`instance` IN(
SELECT `instance`
FROM `user_instance`
WHERE `user` = USER());
END$$
DELIMITER ;
Related
I have a table where I log members.
There are 1,486,044 records here.
SELECT * FROM `user_log` WHERE user = '1554143' order by id desc
However, this query takes 5 seconds. What do you recommend ?
Table construction below;
CREATE TABLE IF NOT EXISTS `user_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user` int(11) NOT NULL,
`operation_detail` varchar(100) NOT NULL,
`ip_adress` varchar(50) NOT NULL,
`l_date` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
COMMIT;
For this query:
SELECT * FROM `user_log` WHERE user = 1554143 order by id desc
You want an index on (user, id desc).
Note that I removed the single quotes around the filtering value for user, since this column is a number. This does not necessarily speeds things up, but is cleaner.
Also: select * is not a good practice, and not good for performance. You should enumerate the columns you want in the resultset (if you don't need them all, do not select them all). If you want all columns, since your table has not a lot of columns, you might want to try a covering index on all 5 columns, like: (user, id desc, operation_detail, ip_adress, l_date).
In addition to the option of creating an index on (user, id), which has already been mentioned, a likely better option is to convert the table to InnoDB as create an index only on (user).
I made a "follow system". The DB design looks like this
CREATE TABLE IF NOT EXISTS `users_followers` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT 'auto incrementing USER_FOLLOWER_ID for each row unique index',
`user_id` int(11) NOT NULL COMMENT 'foriegn key to UserId column in users table',
`follower_id` int(11) NOT NULL COMMENT 'foriegn key to UserId column in users table',
`follower_since_timestamp` bigint(20) DEFAULT NULL COMMENT 'timestamp of the follow'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1;
Before the follow system, all inputs/post/entries were visible to all. Going on a big feed/wall. Now with a couple 100 users it is hard to navigate.
Should I do it the way it was before: 1)check if I follow a user and fetch all information or should I future proof it and make an activity table?: 2)Check if i follow a user, look at the activity table and fetch the appropriate data from post table.
With the activity table it seems I can have a much bigger overview of what's going on. I could for example have a column deleted, but the data is still there in post.
Is an activity table necessary?
Just sharing my two cents, I built a system that enable researchers (well users of the system) to follow each other based on the study of interest, I did it exactly the way you proposed, it really made it easy to pull out data from the data, like for example if User A has a study interest of JavaScript, I can easily use the user_id of User A in table Activity, to pull areas of interest of User A, I hope you get what I am trying to say tho?
Hello Im in the midst of creating a social networking site and I would like to know how I would go about creating the relationships between users. Many sites say that I should create a relationship/friend table, but Im looking into the future and believe that this would be ineffective. This idea could be as popular as facebook and I want to be ready for that many users. Facebook has 400 million users so a friends table would be at least 150 times that. Doing a query for ones friends would be very slow I would think. So would the solution be a seperate table for each user containing their friends ID's. or an associated CSV file containing the ID's. Any help would be greatly appreciated to the design of my site. Thanks
Build the schema you need today, not the one you think you'll need 5 years from now.
Do you think facebook designed their schema to support 400 million users on day one? Of course not. Building for that kind of scale is complicated, expensive, and honestly, if you try it now, you'll probably get it wrong and have to redo it later anyway.
And let's be honest: you have a better chance of winning the lottery than hitting 400 million users any time soon. Even if you do, your project will have hundreds of engineers by then -- plenty of bandwidth for redesigning your schema.
Now's the time to build simple.
Edit to add some solid examples:
Youtube:
They went through a common evolution:
single server, went to a single master
with multiple read slaves, then
partitioned the database, and then
settled on a sharding approach.
Keep it simple! Simplicity allows you
to rearchitect more quickly so you can
respond to problems. It's true that
nobody really knows what simplicity
is, but if you aren't afraid to make
changes then that's a good sign
simplicity is happening.
Livejournal also grew from a single database on a single server to multiple sharded replicated databases
I'm sure you could find a dozen more examples on the highscalability blog
While you think of eventually supporting millions of users, you're only ever seeing a particular persons friends list - that limits the actual amount of data substantially...
In order to maintain normalized friendship relationships in the database, you'd need two tables:
USERS
user_id (primary key)
username
FRIENDS
user_id (primary key, foreign key to USERS(user_id))
friend_id (primary key, foreign key to USERS(user_id))
This will stop duplicates (IE: 1, 2) from happening, but won't stop reversals because (2, 1) is valid. You'd need a trigger to enforce that there's only one instance of the relationship...
In your code, when inserting relationships into table, follow a convention.
issueSQLQuery("INSERT INTO relationships (friend1, friend2)
VALUES (?, ?)", min(friend_1_ID, friend_2_ID), max(friend_1_ID, friend_2_ID))
Do similarly for retrievals, as well. Of course, this could be done in a stored procedure.
Both of the alternatives you've suggested would no doubt result in grief - imagine 400 million tables, or managing 400 million files.
Definitely best to maintain a properly indexed relationships table.
If you expect the levels of success attained by Facebook (I like your confidence), you will soon realize what they realized. Relational databases begin to fall short and you'll want to look into NoSQL solutions.
That being said, why pre-optimize for 400 millions users? Build a system that will work now for, say, 500, 000 users. If you need to redesign after that, then you must be very successful and will have the resources to do so.
something like this should do you initially: http://pastie.org/1127206
drop table if exists user_friends;
drop table if exists users;
create table users
(
user_id int unsigned not null auto_increment primary key,
username varchar(32) unique not null,
created_date datetime not null
)
engine=innodb;
delimiter #
create trigger users_before_ins_trig before insert on users
for each row
begin
set new.created_date = now();
end#
delimiter ;
create table user_friends
(
user_id int unsigned not null,
friend_user_id int unsigned not null,
created_date datetime not null,
primary key (user_id, friend_user_id), -- note clustered composite PK
foreign key (user_id) references users(user_id),
foreign key (friend_user_id) references users(user_id)
)
engine=innodb;
delimiter #
create trigger user_friends_before_ins_trig before insert on user_friends
for each row
begin
set new.created_date = now();
end#
delimiter ;
drop procedure if exists insert_user;
delimiter #
create procedure insert_user
(
in p_username varchar(32)
)
proc_main:begin
insert into users (username) values (p_username);
end proc_main #
delimiter ;
drop procedure if exists insert_user_friend;
delimiter #
create procedure insert_user_friend
(
in p_user_id int unsigned,
in p_friend_user_id int unsigned
)
proc_main:begin
if p_user_id = p_friend_user_id then
leave proc_main;
end if;
insert into user_friends (user_id, friend_user_id) values (p_user_id, p_friend_user_id);
end proc_main #
delimiter ;
drop procedure if exists list_user_friends;
delimiter #
create procedure list_user_friends
(
in p_user_id int unsigned
)
proc_main:begin
select
u.*
from
user_friends uf
inner join users u on uf.friend_user_id = u.user_id
where
uf.user_id = p_user_id
order by
u.username;
end proc_main #
delimiter ;
call insert_user('f00');
call insert_user('bar');
call insert_user('bish');
call insert_user('bash');
call insert_user('bosh');
select * from users;
call insert_user_friend(1,2);
call insert_user_friend(1,3);
call insert_user_friend(1,4);
call insert_user_friend(1,1); -- oops
call insert_user_friend(2,1);
call insert_user_friend(2,5);
select * from user_friends;
call list_user_friends(1);
call list_user_friends(2);
-- call these stored procs from your php !!
You could accomplish this using a table to represent the "Relationship" that one user has with another user. This is essentially a JOIN table between two different rows in the same table. An example join table might include the following columns:
USER_1_ID
USER_2_ID
To get a list of friends write a query that performs an INNER JOIN from the USER in question to the RELATIONSHIP table back to a second instance on the USER table.
Question on preventing duplicated entry in my simple web form.
My table record user input from a web form, and distinguished by date e.g. DATE(). How to prevent user with the same name to enter information twice in a single date, e.g. same username cannot be entered twice in the same date, but can be entered at other date?
Your table should have these:
create table tablename (
...
user_id bigint, -- or whatever
date_created date,
unique key(user_id, date_created)
...
);
You can simple create a composite primary key. For your case this means that your primary key must consists of a date field as well as the username field.
In several ways.
First, you can create index on your table. (i'm using simple table as an example).
CREATE TABLE `test` (
`id` INT NOT NULL ,
`name` VARCHAR( 255 ) NOT NULL ,
`date` DATE NOT NULL ,
PRIMARY KEY ( `id` )
) ENGINE = MYISAM;
ALTER TABLE `test` ADD UNIQUE (
`name` ,
`date`
);
This is MySQL way.
You also should make checks in PHP ,although you can do it when inserting (MySQL will return error and you can check it). But you can make additional SELECT before inserting (SELECT * from test WHERE name=USER AND date=DATE) and check record count. If it's more than 0, you show error.
When saving, you seldom should worry about one additional SQL. If you should, just check MySQL statement for errors (MySQL way :)).
Create a unique key on the user and date column
http://dev.mysql.com/doc/refman/5.1/en/create-table.html
I'm working on a real estate site and need to make notification mailer: when new property is inserted on a site, people who subscribed for notification in that particular country and/or area and/or city and/or particular property operation (rental, selling) will receive a notification on email. One person could subscribe for different areas, cities, etc, not only one. One person will receive only one notification a week let say if there are new properties for him, though. And I'm thinking on how better to create a mysql table for subscribers in order to easy retrieve them. Table like:
create table subscribers(
user_email varchar(255),
area_id int(4));
is a bad idea, because if there will be let say 100,000 (looking to the future) subscribers and each will subscribe for 10 areas there will be 1,000,000 rows in a table. So, I'm looking for efficient solution to do such task.
If you have additional recommendations, I will like to hear them.
Thanks in advance!
You should use a cross-reference (many-to-many) table. This will make data more normalized:
CREATE TABLE `areas` (
`id` int(10) unsigned NOT NULL auto_increment,
`name` varchar(255) NOT NULL
PRIMARY KEY (`id`)
)
CREATE TABLE `subscribers` (
`id` int(10) unsigned NOT NULL auto_increment,
`email` varchar(255) NOT NULL
PRIMARY KEY (`id`)
)
-- cross ref table
CREATE TABLE `areas_subscribers` (
`area_id` int(10) unsigned NOT NULL,
`subscriber_id` int(10) unsigned NOT NULL,
UNIQUE KEY (`area_id`,`subscriber_id`)
)
And a million rows is not a problem. Especially with a cross ref table.
there will be 1,000,000 rows in a table
So what? mySQL can handle it.
As far as I can see, the way you are doing it is perfectly fine. It's nicely normalized, I can't think of a better method.
Your table looks correct, assuming that user_email is the primary key identifying your users. If so, add to your subscribers table a PRIMARY KEY (user_email, area_id) to indicate that both fields together make up your primary key.
Your concern about duplicating e-mails has little to do with the schema design and more to do with the query you intend to run. That, of course, will depend largely on how your other data are stored, but might look something like:
SELECT DISTINCT user_email WHERE area_id IN (...)
(For a list of area_id values that have seen listings in the past week.)
That's a simple query that could be optimized and improved given the rest of your schema, but it illustrates how easy it is to avoid generating multiple e-mails despite the same person being listed multiple times.
You can make an extra table of the email addresses.
So you only store an ID in the subscriber table and not the same email address over and over again (whereas there might be some optimizations in the database anyway).