Ensuring uniqueness of additions to MySQL table using PHP - php

I'm trying to create a page that tracks some basic user statistics in a database. I'm starting small, by trying to keep track of how many people come using what User Agent, but I've come across a stumbling block. How can I check the User Agent being added to the table to see if it is already there or not?

You can make the column that stores the User Agent string unique, and do INSERT ... ON DUPLICATE KEY UPDATE for your stats insertions
For the table:
CREATE TABLE IF NOT EXISTS `user_agent_stats` (
`user_agent` varchar(255) collate utf8_bin NOT NULL,
`hits` int(21) NOT NULL default '1',
UNIQUE KEY `user_agent` (`user_agent`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
+------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+-------+
| user_agent | varchar(255) | NO | PRI | NULL | |
| hits | int(21) | NO | | NULL | |
+------------+--------------+------+-----+---------+-------+
You could use the following query to insert user agents:
INSERT INTO user_agent_stats( user_agent ) VALUES('user agent string') ON DUPLICATE KEY UPDATE hits = hits+1;
Executing the above query multiple times gives:
+-------------------+------+
| user_agent | hits |
+-------------------+------+
| user agent string | 6 |
+-------------------+------+

Before adding it to the database, SELECT from the table where you're inserting the User Agent string. If mysql_num_rows is greater than 0, the User Agent you're trying to add already exists. If mysql_num_rows is less than or equal to 0, the User Agent you're adding is new.

Related

Store user comments in database

I'm facing some performance issues with MySql. The query to select the comments related to the specific url id took about 1.5 ~ 2 seconds to complete.
Comments Table
CREATE TABLE `comments` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`url_id` INT UNSIGNED NOT NULL,
`user_id` INT UNSIGNED NOT NULL,
`published` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`votes_up` SMALLINT UNSIGNED NOT NULL DEFAULT 0,
`votes_down` SMALLINT UNSIGNED NULL DEFAULT 0,
`text` TEXT,
PRIMARY KEY (id),
INDEX (url_id),
INDEX (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
I have inserted 100.000 comments, and executed this query: SELECT * FROM comments WHERE url_id = 33 ORDER BY published ASC LIMIT 0,5.
Is this normal? A simple query taking almost 2 seconds to complete? Should I create a separate table just for the comment's text?
Youtube, Facebook and so on has millions (or billions) of comments, how they get the comments for that object (video, post, etc) so fast?
To resume my question:
I stop worrying about performance and stick with this and when the website reaches certain amount of user activity, I start worrying about this.
If I need to worry about this, what's wrong to my table structure? What I need to change to reduce the completion time of that query?
Update
The explain output:
+----+-------------+----------+------+---------------+----------+---------+-------+------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------+------+---------------+----------+---------+-------+------+-----------------------------+
| 1 | SIMPLE | comments | ref | url_id | url_id | 4 | const | 549 | Using where; Using filesort |
+----+-------------+----------+------+---------------+----------+---------+-------+------+-----------------------------+
The problem here is that mysql uses only one index per table. That's why your index on published wasn't used. Your explain shows that it's using the index to identify what rows to return, that leaves the RDBMS unable to use an index for the sorting.
What you should do is to create a composite index on (user_id,published)

Know the last modified row or id's row in a mysql table

I'm using Mysql 5.5 and by example I have a table like this
+------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+----------------+
| idgroups | int(11) | NO | PRI | NULL | auto_increment |
| group_id | int(11) | YES | | NULL | |
| group_name | varchar(45) | YES | | NULL |
Where some people are allowed to do inserts,update and delete but I want to know which is the last modified row or row's id in a given time
Any ideas?
Thanks in advance
My suggestion would be to create a second table. something like edit_history for recording modifications. You can put triggers on your groups table above that says "Any time a record is inserted, deleted, or updated, create a record in my edit_history table".
A trigger can be created as follows:
CREATE TRIGGER trigger_name
AFTER INSERT
ON table_name FOR EACH ROW
BEGIN
-- For each row inserted
-- do something...
END;
Since your field is auto_increment, you can just select the maximum value of idgroups to get the most recently inserted value:
select max(idgroups) from tbl
to get last modified in general will require additional structure to your table. In particular, if you are deleting, you will need to store what you have most recently deleted somewhere.

Do the fields (structure) of MySQL tables get unique ids?

I'm not talking about unique keys or auto_increments, suppose I have this structure:
mysql> describe email_notifications;
+---------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+------------------+------+-----+---------+----------------+
| email_id | int(11) unsigned | NO | PRI | NULL | auto_increment |
| email_address | varchar(100) | NO | | | |
| course_id | int(11) unsigned | NO | MUL | NULL | |
+---------------+------------------+------+-----+---------+----------------+
I'm building (for fun, practice, and hopefully some practical use) a tool in PHP that will analyze the structure of each table in a database and then compare it to a newer one (to assist in Dev -> Live updates), and then spit out some MySQL queries (Such as ALTER TABLE...) that I can run on the live database in order to bring it up to speed.
The question - does each field get a unique id of some sort?
If I change email_address from varchar(100) to text (for example) or the name course_id to cr_id, is there any way for me to tell that it's still technically the same dataset? I don't want to run a Delete and Add, but instead rename it give it a new type.
Or if there's a better way to do it without some sort of MySQL ID, that would be great :)
Thanks!
I think you can use information_schema.columns. The following are both unique keys in this table (even if they are not so defined):
TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME
TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, ORDINAL_POSITION
When you change the name or type of a column, I do not believe that ORDINAL_POSITION is affected. So, the second version may be what you are looking for.
This may then lead to the question "what if I change the name of a table?" The information_schema tables can't help there, unfortunately.

PHP custom chat, coding a block function

I've tried to build a Block function without any luck, my SQL skills are not as good as I hoped they would be in this case.
I have one table called "messages" and one table called "blocks"
Now, there's 1 file syncing everything to the Chat, what I'm trying to do is IF user 1 has blocked user 2 than user 1's messages should never reach user 2 and user 2's message should not reach user 1. In short term, if you block someone you can't speak to him/her and him/her can't speak to you!
"blocks" table:
id bigint(20)
user_id tinyint(20)
block_id tinyint(20)
"messages" table:
id bigint(20)
timestamp datetime
dest_type varchar(255)
dest_id bigint(20)
source_type varchar(255)
source_id bigint(20)
message_type varchar(255)
message text
in "blocks" user_id is the owners id of the block row.
and block_id is the id that the owner wants to block.
IF "messages.source_id = blocks.block_id OR messages.block_id = blocks.user_id"
THAN dont let the message get trough. I understand that asking someone to code this for me is quite rude but I'm asking, can anyone give this a shot?
here's the sync.php file:
http://pastebin.com/8iiSCXGS
Big thanks!
I haven't delved very deeply into your code, but perhaps this will help.
Let's start with a reduced database structure as follows:
CREATE TABLE `blocks` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`user_id` INT UNSIGNED NOT NULL,
`block_id` INT UNSIGNED NOT NULL );
INSERT INTO `blocks` (`user_id`,`block_id`) VALUES
(1,2),(3,4),(2,1);
CREATE TABLE `messages` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`author_id` BIGINT NOT NULL,
`message` TEXT NOT NULL );
INSERT INTO `messages` (`author_id`,`message`) VALUES
(1,"Message from user #1, who has a mutual block in place with user #2"),
(2,"Message from user #2, who has a mutual block in place with user #1"),
(3,"Message from user #3, who has blocked user #4"),
(4,"Message from user #4, who has been blocked by user #3"),
(5,"Message from user #5, who takes no part in all this blocking business");
Now let's suppose user $n visits the website (where 1≤$n≤5). To figure out which messages can be displayed, we need to perform a LEFT JOIN of the messages and blocks tables — i.e., we want to consider every row of messages together with any row of blocks that contains relevant information (specifically, where the author of the message has blocked user $n, or has been blocked by user $n). If $n=1, we have the following:
SELECT * FROM `messages`
LEFT JOIN `blocks`
ON (`author_id`=`block_id` AND `user_id`=1)
OR (`author_id`=`user_id` AND `block_id`=1);
Here's the result of that query:
+----+-----------+-----------------------------------------------------------------------+------+---------+----------+
| id | author_id | message | id | user_id | block_id |
+----+-----------+-----------------------------------------------------------------------+------+---------+----------+
| 1 | 1 | Message from user #1, who has a mutual block in place with user #2 | NULL | NULL | NULL |
| 2 | 2 | Message from user #2, who has a mutual block in place with user #1 | 1 | 1 | 2 |
| 2 | 2 | Message from user #2, who has a mutual block in place with user #1 | 3 | 2 | 1 |
| 3 | 3 | Message from user #3, who has blocked user #4 | NULL | NULL | NULL |
| 4 | 4 | Message from user #4, who has been blocked by user #3 | NULL | NULL | NULL |
| 5 | 5 | Message from user #5, who takes no part in all this blocking business | NULL | NULL | NULL |
+----+-----------+-----------------------------------------------------------------------+------+---------+----------+
6 rows in set (0.00 sec)
As you can see, the rows we want are the ones where the last three columns are NULL, which means there is no blocking rule affecting the display of this particular message to this particular user. So to extract these messages, we simply add WHERE block_id IS NULL to the end of the query:
SELECT * FROM `messages`
LEFT JOIN `blocks`
ON (`author_id`=`block_id` AND `user_id`=1)
OR (`author_id`=`user_id` AND `block_id`=1)
WHERE `block_id` IS NULL;
+----+-----------+-----------------------------------------------------------------------+------+---------+----------+
| id | author_id | message | id | user_id | block_id |
+----+-----------+-----------------------------------------------------------------------+------+---------+----------+
| 1 | 1 | Message from user #1, who has a mutual block in place with user #2 | NULL | NULL | NULL |
| 3 | 3 | Message from user #3, who has blocked user #4 | NULL | NULL | NULL |
| 4 | 4 | Message from user #4, who has been blocked by user #3 | NULL | NULL | NULL |
| 5 | 5 | Message from user #5, who takes no part in all this blocking business | NULL | NULL | NULL |
+----+-----------+-----------------------------------------------------------------------+------+---------+----------+
4 rows in set (0.01 sec)
If you substitute different user IDs into this query, you should get the results you're after.

Prestashop category-product association table allowing duplicates?

The table ps_category_product in PrestaShop has the following structure
# Obtained using SHOW CREATE TABLE `ps_category_product`
CREATE TABLE `ps_category_product` (
`id_category` int(10) unsigned NOT NULL,
`id_product` int(10) unsigned NOT NULL,
`position` int(10) unsigned NOT NULL DEFAULT '0',
KEY `category_product_index` (`id_category`,`id_product`),
KEY `id_product` (`id_product`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
For me is not very clear, but it seems that the fields id_category and id_product should be unique among the table, but for some reason MySQL allows me to insert duplicates:
mysql> select * from ps_category_product limit 10;
+-------------+------------+----------+
| id_category | id_product | position |
+-------------+------------+----------+
| 11 | 1 | 1 |
| 11 | 2 | 1 |
| 11 | 3 | 1 |
| 11 | 4 | 1 |
| 11 | 5 | 1 |
| 11 | 6 | 1 |
| 11 | 7 | 1 |
| 11 | 8 | 1 |
| 11 | 9 | 1 |
| 11 | 10 | 1 |
+-------------+------------+----------+
10 rows in set (0.00 sec)
mysql> INSERT INTO `ps_category_product` VALUES(11, 1, 1);
Query OK, 1 row affected (0.05 sec)
How can I prevent this from happening?
Later edit
It was a bug in prestashop. Take a look at http://forge.prestashop.com/browse/PSCFI-4397
Specifying KEY will not enforce a unique constraint unless you specify UNIQUE KEY or PRIMARY KEY.
Try recreating the table using the following DDL:
CREATE TABLE `ps_category_product` (
`id_category` int(10) unsigned NOT NULL,
`id_product` int(10) unsigned NOT NULL,
`position` int(10) unsigned NOT NULL DEFAULT '0',
UNIQUE KEY `category_product_index` (`id_category`,`id_product`),
KEY `id_product` (`id_product`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
That should do the trick.
Have a look at the MySQL CREATE TABLE syntax for more info.
The constraint should be imposed via the admin interface and underlying object code, so you shouldn't ever have a situation where there are duplicates, although it would be easy enough to write a cron job to remove any that did occur.
You could force this unique, but that doesn't solve the fundamental problem as to why this might happen.... I honestly don't see what the issue is that you're trying to solve? If you're importing products yourself, then you should use the object interface rather than writing to these tables directly, otherwise, yes - weird things might happen.

Categories