data model and app logic question? - mysql

basically i have this problem i have a table of posts(in my mysql database)
Post{user_id, post, vote_up(int), vote_down(int)}
because users log in, then they get to vote, but i wanted to check if the user has already voted, so then i can not allow them to vote again, and obviously im going to be using user session to control this oppose to tracking ips etc.
so i was thinking do i have to create another table or is thier a better approach.

You will need another table e.g. called "votes":
Vote{user_id, post_id}
I assume, that your "Post" table has a primary key (e.g. "id") that you have not shown in your question above? Then "post_id" should be a foreign key to Post#id.
/Carsten

You'll definately need another table, and some primary and foreign keys would help too:
Post{post_id(int), user_id(varchar), post(clob)}
Votes{vote_id(int), post_id, user_id, up_down(char)}
Your vote_up and vote_down column values are removed and are now calculated with queries:
-- vote_up
select count(*) from votes where post_id = n and up_down = '+';
-- vote_down
select count(*) from votes where post_id = n and up_down = '-';
There should be a unique index on votes(post_id, user_id) to prevent multiple votes by the same user on the same post.
The primary key vote_id does not have to be defined, but each table should have a primary key and if you don't want to use a surrogate key, then you can define the PK using the same columns as above and this will serve as the unique index too, so ot does not have to be defined.

Related

How to properly index

I'm creating a table on a database that has different poll options. There is another table with polls.
The idea is that given a poll_id I want to get as fast as possible all its options.
This are the table columns: opt_id, poll_id, opt_text, opt_votes.
I would like the opt_id not to be an auto_increment but just the id (1 to N options) within the poll, so to me the primary key is given by both the poll_id and the option_id, right?
What I want is to have a proper index so that a query such SELECT * FROM options WHERE poll_id=X takes as less as possible, but I don't know if just by setting the primary key to these two fields is enough or I have to set an index somewhere.
For SELECT * FROM options WHERE poll_id=X, INDEX(poll_id) is optimal. If you already have PRIMARY KEY(poll_id), then that is sufficient. (A PRIMARY KEY is a UNIQUE KEY which is an INDEX.)
Index Cookbook .
Please provide SHOW CREATE TABLE; there is too much hand-waving in your description of the tables.
And show us any other SELECTs; they may need other indexes.

Insert if not exist and Update if exist

I have three columns on my database table
user_id, post_id, and vote
I want to insert a new data if user_id and post_id don't exist. But if both columns user_id and post_id exist i will be able to update 'vote' column value. I set user_id to be unique but it proves to be not working since i want user to insert votes on different post.
The query below only updated the value of vote since user_id already exist. I want to have it updated if and only if user_id and post_id existed
I used this sql query
INSERT INTO polls (user_id,post_id,vote) VALUES (1,2,5)
ON DUPLICATE KEY UPDATE vote= ?;
Here's my problem
You must create unique key combination
Create unique index your_index_name on yourtable (field_one,field_two),
then do the insert into , on duplicate key logic
It is absolutely logical that your code does not work as intended, because your only key is user_id, thus if you want to check the uniqueness of user_id AND post_id, then you should set it as so.
Don't think you can do it purely in MySQL :*( post_id would have to be unique and you said that does not fit your business logic. Furthermore, if multiple keys are detected, they are joined by an OR in the resulting query, which will further complicate things for you. Here's an excerpt from the manual, where a and b are keys involved in the ON DUPLICATE KEY query:
If a=1 OR b=2 matches several rows, only one row is updated. In general, you should try to avoid using an ON DUPLICATE KEY UPDATE clause on tables with multiple unique indexes.

See which posts user has viewed

I have a table user it has:
id INT PRIMARY AUTO_INCREMENT
name VARCHAR(30) NOT NULL
Also I have table post
id INT PRIMARY AUTO_INCREMENT
post TEXT NOT NULL
If user visits a certain post I would like to store somewhere in the database that he has already viewed the said post. How would I go about modifying the database to accommodate for that and how would I query for the posts that user has not seen. Should I create a new table to store that info? Or is there some other nifty trick?
The idea is that I simply want a way where if user views the post once he can not view it ever again.
Sorry I am just trying to learn databases and this is one challenge I find quite interesting and difficult.
You will need to implement a many-to-many relationship: a User visits multiple Posts, and a Post is visited by multiple users.
This will take the form of a table with only two columns, each of them being a foreign key to one of your existing tables:
CREATE TABLE user_post_visit (
user_id INT NOT NULL,
post_id INT NOT NULL,
PRIMARY KEY (user_id, post_id),
FOREIGN KEY (user_id) REFERENCES user(id),
FOREIGN KEY (post_id) REFERENCES post(id)
);
Add a record into this table every time a user views a post. In order to find posts that a given user has not viewed yet:
SELECT post.*
FROM post
LEFT JOIN user_post_visit AS upv
ON (upv.post_id = post.id AND upv.user_id = <your user ID here>)
WHERE upv.post_id IS NULL;
Use what RandomSeed has suggested for creating a bridge-table between user and post and then use the following to query out the posts viewed by a user:
SELECT post_id, post FROM user_post_visit, post WHERE user_post_visit.user_id=<user id>
It seems I follow behind #RandomSeed. :D
It seems to me that you want many-to-many relation so you need a new table. How you do it is not strict but for example
Table "read_post" or "unread_post":
user_id, post_id
If the table is unread_post (but you have to create new row to every user when new post created) you can show unread post:
SELECT text FROM post INNER JOIN unread_post ON post.id =
unread_post.post_id WHERE user_id = ?
If table is read_post get unread see RandomSeeds answer.

creating a new mysql table with a primary auto incrementing id that is linked to another table?

I want two tables to share a primary auto incrementing id, is this possible? how do i do this? is their anything i need to consider?
the reasons i am doing this, is because it is a better solution than adding groups column to the users table, and also better than creating a completly seperate groups table, because if they share a primary key, i can use the existing posts table for both groups and users. instead of having to create a two distinct posts tables, (group_posts table for group posts. and a user_posts table for user posts.)
existing users table is
id(primary, ai)
username
password
email
my groups table that i want to link to my users table with a shared ai primary key
id(primary, ai, linked to users table id)
group_name
created_by
creation_date
etc.
You should make you schema clearer by doing the following:
Create a table (e.g. people)
id, primary key, auto-increment
type, tells you if it's a user or a group
Make users and groups primary keys foreign keys on people
Insert records in people
Obtain the ID that was assigned using LAST_INSERT_ID()
Insert in users or groups appropriately, using the ID obtained above
Then you'd reference "people", and not "users" or "groups" in your posts table and so on.
Conceptually, thinking of it in an OO way, it means users and groups both extend people.

How to set a database integrity check on foreign keys referenced fields

I have four Database Tables like these:
Book
ID_Book |ID_Company|Description
BookExtension
ID_BookExtension | ID_Book| ID_Discount
Discount
ID_Discount | Description | ID_Company
Company
ID_Company | Description
Any BookExtension record via foreign keys points indirectly to two different ID_Company fields:
BookExtension.ID_Book references a Book record that contains a Book.ID_Company
BookExtension.ID_Discount references a Discount record that contains a Discount.ID_Company
Is it possible to enforce in Sql Server that any new record in BookExtension must have Book.ID_Company = Discount.ID_Company ?
In a nutshell I want that the following Query must return 0 record!
SELECT count(*) from BookExtension
INNER JOIN Book ON BookExstension.ID_Book = Book.ID_Book
INNER JOIN Discount ON BookExstension.ID_Discount = Discount.ID_Discount
WHERE Book.ID_Company <> Discount.ID_Company
or, in plain English:
I don't want that a BookExtension record references a Book record of a Company and a Discount record of another different Company!
Unless I've misunderstood your intent, the general form of the SQL statement you'd use is
ALTER TABLE FooExtension
ADD CONSTRAINT your-constraint-name
CHECK (ID_Foo = ID_Bar);
That assumes existing data already conforms to the new constraint. If existing data doesn't conform, you can either fix the data (assuming it needs fixing), or you can limit the scope (probably) of the new constraint by also checking the value of ID_FooExtension. (Assuming you can identify "new" rows by the value of ID_FooExtension.)
Later . . .
Thanks, I did indeed misunderstand your situation.
As far as I know, you can't enforce that constraint the way you want to in SQL Server, because it doesn't allow SELECT queries within a CHECK constraint. (I might be wrong about that in SQL Server 2008.) A common workaround is to wrap a SELECT query in a function, and call the function, but that's not reliable according to what I've learned.
You can do this, though.
Create a UNIQUE constraint on Book
(ID_Book, ID_Company). Part of it will look like UNIQUE (ID_Book, ID_Company).
Create a UNIQUE constraint on Discount (ID_Discount, ID_Company).
Add two columns to
BookExtension--Book_ID_Company and
Discount_ID_Company.
Populate those new columns.
Change the foreign key constraints
in BookExtension. You want
BookExtension (ID_Book,
Book_ID_Company) to reference
Book (ID_Book, ID_Company). Similar change for the foreign key
referencing Discount.
Now you can add a check constraint to guarantee that BookExtension.Book_ID_Company is the same as BookExtension.Discount_ID_Company.
I'm not sure how [in]efficient this would be but you could also use an indexed view to achieve this. It needs a helper table with 2 rows as CTEs and UNION are not allowed in indexed views.
CREATE TABLE dbo.TwoNums
(
Num int primary key
)
INSERT INTO TwoNums SELECT 1 UNION ALL SELECT 2
Then the view definition
CREATE VIEW dbo.ConstraintView
WITH SCHEMABINDING
AS
SELECT 1 AS Col FROM dbo.BookExtension
INNER JOIN dbo.Book ON dbo.BookExtension.ID_Book = Book.ID_Book
INNER JOIN dbo.Discount ON dbo.BookExtension.ID_Discount = Discount.ID_Discount
INNER JOIN dbo.TwoNums ON Num = Num
WHERE dbo.Book.ID_Company <> dbo.Discount.ID_Company
And a unique index on the View
CREATE UNIQUE CLUSTERED INDEX [uix] ON [dbo].[ConstraintView]([Col] ASC)