MySQL - Update view column every time the row is SELECTed - mysql

I have a table that has a "view_count" column.
I would like to increment said column every time I select the row (even with a generic SELECT, such as SELECT * FROM table WHERE 1.. This would increment by 1 the view_count of all rows).
Is there a way to do it automatically "server-side" (where mysql is the server and my application is the client), ie without an UPDATE every time?
EDIT: Since a couple of people asked me why I wanted to do this, and a few misunderstood my request, imagine that's a forum software, and the table is the thread table. Each thread has a view count, and you want to update it every time you display the thread in the main page (I know usually the threads' view count are only updated when you actually view it, but it's the best example i could come up with, my particular case is kinda long and complicated T_T)

Can't be done. You basically want a trigger on SELECT, and triggers are only supported for INSERT, UPDATE, and DELETE. The closest you could get would be to run all your interactions with the table through a stored procedure, and it sounds like you want this behavior to be enforced under all conditions.

Why not just issue one SQL statement like so:
UPDATE tableName SET viewCount = viewCount + 1; SELECT tableName.col1, tableName.col2;
That could even be dynamically created with ease in any language.

You can do it in the session scope:
SET #view_count := 0;
SELECT v.*,
#view_count := #view_count + 1 AS view_count
FROM view
You will have an incrementing value for each row viewed:
SELECT q.*,
#view_count := #view_count + 1 AS view_count
FROM (
SELECT 1 AS col
UNION ALL
SELECT 2 AS col
) q;
-- --
1 1
2 2
SELECT q.*,
#view_count := #view_count + 1 AS view_count
FROM (
SELECT 1 AS col
UNION ALL
SELECT 2 AS col
) q;
-- --
1 3
2 4
This, however, will only count the views within your session, not the total views.

I don't believe there's a simple way to do this server-side; triggers only execute on UPDATEs, INSERTs, and DELETEs.

Stored procedures, but it's a pain. I would implement security elsewhere.

Related

MySQL - Updating a selection with iterative integers

Hello again internet nerds!
Technically I have solved this problem, but want to know if there is a more optimal route I should take...
I have a large table (~4m rows) that is a collection of data, segmented using int "chip." There are 6 segments of data, so Chip IDs 1 through 6.
Of these 6 segments, I need to assign an order integer, which needs to be iterative, as it represents the exact location of the data on said segment.
My solution is (was) this:
# set iterative
set #i:=0;
# init
update table set `order` = #i:=(#i+1) where chip = 1;
This works. But it is so slow it sometimes triggers a timeout error. I need to run it 6 times and it may be triggered on occasion by our application whenever necessary. Maybe it's just that I need to alot more time to MySQL settings to account for the slow query, or is there an optimal, simpler solution for this?
Thanks for the advice.
Edit:
I've found a solution that works accurately and takes ~50 seconds to complete.
I'm now using an ordered select statement paired in the update, using a generated join table that's iterating within a column.
See:
set #count:= 0;
update
table as target,
(select
(#count := #count+1) as row_num,
t.*
from table as t
where chip = 1
order by t.id asc) as table_with_iterative
set target.`order` = table_with_iterative.row_num
where target.id = table_with_iterative.id;
I think that if possible you should assign the sequence numbers as you are inserting the rows into the table for the very first time. Strictly speaking, an SQL database does not promise that rows will initially appear inside the table in the order of of the INSERT statements.
One possibility that occurs to me is to use an auto-increment field in this table, because this will express the order in which the rows were added. But the field-values for any given chip, although ascending, might not be consecutive and undoubtedly would not start at 1.
If you did need such a field, I think I'd define a separate field (default value: NULL) to hold it. Then, a very simple stored procedure could query the rows for a given chip (ORDER BY auto-increment number) and assign a 1-based consecutive sequential number to that separate field. (A slightly more elaborate query could identify all the lists that don't have those numbers yet, and number all of them at once.)
I've found a solution that works accurately and takes ~50 seconds to complete.
This handles the out-of-order updates I was experiencing.
I'm now using an ordered select statement paired in the update statement, using a generated join table that's iterating within a column row_num.
See:
set #count:= 0;
update
table as target,
(select
(#count := #count+1) as row_num,
t.*
from table as t
where chip = 1
order by t.id asc) as table_with_iterative
set target.`order` = table_with_iterative.row_num
where target.id = table_with_iterative.id;

How to update a counter field from a different table in mysql

I have 3 tables in MySQL ParkIn{ parkin_id,InTime} ,ParkOut{parkout_id,OutTime} and Counter{ Counter_ID, Counter}.
Everytime there is an Insert in the Parkin table I need the Counter column to add 1 to its previous value and whenever there is a Insert in the ParkOut table I need the counter column to subtract 1. The counter table doesnt necessarily have to insert new lines everytime but rather just keep updating the counter value.
You're doing this the hard way.
You can do
SELECT (parkin.rowcount - parkout.rowcount) Counter
FROM (SELECT COUNT(*) rowcount FROM ParkIn) parkin
JOIN (SELECT COUNT(*) rowcount FROM ParkOut) parkout
when you need the value you described. You can trust that databases are made to do this kind of thing.
If you really want the illusion that you have a Counter table, you can create a view.
CREATE OR REPLACE VIEW Counter AS
SELECT (parkin.rowcount - parkout.rowcount) Counter
FROM (SELECT COUNT(*) rowcount FROM ParkIn) parkin
JOIN (SELECT COUNT(*) rowcount FROM ParkOut) parkout
Your Counter table, as you described it in your question, is known as a "materialized view" in the DBMS trade. It's "materialized" because it's an actual table that you actually maintain in real time. I am suggesting using an ordinary view, not a materialized view, to solve your problem.

update statement after the condition met in select statement (mysql)

im having trouble with update if selected table is true...
IF this select statement is true
(SELECT COUNT(*) FROM Add_bonus WHERE value > 0) > 0)
THEN do
UPDATE bonus SET amount = amount + 10 WHERE 1
The if statement is only allowed in stored procedures, functions, and triggers in MySQL. You can do this with a single update:
UPDATE bonus
SET amount = amount + 10
WHERE exists (select 1 from Add_Bonus where value > 0);
Note that this query will update all rows in the bonus table when any appropriate row exists in the add_bonus table. That appears to be the intention of your original query. More commonly, there might be an employee id linking the two tables.
Also note the use of exists. This is typically more efficient than using count(*), because it can stop processing on the first row that matches.

an efficient way to test if a table row exists

I'm trying to find the most efficient way to determine if a table row exists.
I have in mind 3 options:
SELECT EXISTS(SELECT 1 FROM table1 WHERE some_condition);
SELECT 1 FROM table1 WHERE some_condition LIMIT 0,1;
SELECT COUNT(1) FROM table1 WHERE some_condition;
It seems that for MySQL the first approach is more efficient:
Best way to test if a row exists in a MySQL table
Is it true in general for any database?
UPDATE:
I've added a third option.
UPDATE2:
Let's assume the database products are mysql, oracle and sql-server.
I would do
SELECT COUNT(1) FROM table 1 WHERE some_condition.
But I don't think it makes a significant difference unless you call it a lot (in which case, I'd probably use a different strategy).
If you mean to use as a test if AT LEAST ONE row exists with some condition (1 or 0, true or false), then:
select count(1) from my_table where ... and rownum < 2;
Oracle can stop counting after it gets a hit.
Exists is faster because it will return the number of results that match the subquery and not the whole result.
The different methods have different pros and cons:
SELECT EXISTS(SELECT 1 FROM table1 WHERE some_condition);
might be the fastest on MySQL, but
SELECT COUNT(1) FROM table 1 WHERE some_condition
as in #Luis answer gives you the count.
More to the point I recommend you take a look at your business logic: Very seldom is it necessary to just see if a row exists, more often you will want to
either use these rows, so just do the select and handle the 0-rows case
or you will want to change these rows, in which case just do your update and check mysql_affected_rows()
If you want to INSERT a row if it doesn't already exist, take a look at INSERT .. ON DUPLICATE KEY or REPLACE INTO
The exists function is defined generally in SQL, it isn't only as a MySQL function : http://www.techonthenet.com/sql/exists.php
and I usually use this function to test if a particular row exists.
However in Oracle I've seen many times the other approach suggested before:
SELECT COUNT(1) FROM table 1 WHERE some_condition.

ranking entries in mysql table

I have a MySQL table with many rows. The table has a popularity column. If I sort by popularity, I can get the rank of each item. Is it possible to retrieve the rank of a particular item without sorting the entire table? I don't think so. Is that correct?
An alternative would be to create a new column for storing rank, sort the entire table, and then loop through all the rows and update the rank. That is extremely inefficient. Is there perhaps a way to do this in a single query?
There is no way to calculate the order (what you call rank) of something without first sorting the table or storing the rank.
If your table is properly indexed however (index on popularity) it is trivial for the database to sort this so you can get your rank. I'd suggest something like the following:
Select all, including rank
SET #rank := 0;
SELECT t.*, #rank := #rank + 1
FROM table t
ORDER BY t.popularity;
To fetch an item with a specific "id" then you can simply use a subquery as follows:
Select one, including rank
SET #rank := 0;
SELECT * FROM (
SELECT t.*, #rank := #rank + 1
FROM table t
ORDER BY t.popularity
) t2
WHERE t2.id = 1;
You are right that the second approach is inefficent, if the rank column is updated on every table read. However, depending on how many updates there are to the database, you could calculate the rank on every update, and store that - it is a form of caching. You are then turning a calculated field into a fixed value field.
This video covers caching in mysql, and although it is rails specific, and is a slightly different form of caching, is a very similar caching strategy.
If you are using an InnoDb table then you may consider building a clustered index on the popularity column. (only if the order by on popularity is a frequent query). The decision also depends on how varied the popularity column is (0 - 3 not so good).
You can look at this info on clustered index to see if this works for your case: http://msdn.microsoft.com/en-us/library/ms190639.aspx
This refers to SQL server but the concept is the same, also look up mysql documentation on this.
If you're doing this using PDO then you need to modify the query to all be within the single statement in order to get it to work properly. See PHP/PDO/MySQL: Convert Multiple Queries Into Single Query
So hobodave's answer becomes something like:
SELECT t.*, (#count := #count + 1) as rank
FROM table t
CROSS JOIN (SELECT #count := 0) CONST
ORDER BY t.popularity;
hobodave's solution is very good. Alternatively, you could add a separate rank column and then, whenever a row's popularity is UPDATEd, query to determine whether that popularity update changed its ranking relative to the row above and below it, then UPDATE the 3 rows affected. You'd have to profile to see which method is more efficient.