MySQL - Increment Column Value or Insert Data if not Exists - mysql

I have users visiting the site. The user can do a bunch of different actions. I'd like a counter to count the amount of times the user does that action. The problem is, it's per day, it starts over each day.
So the model has, Id, User, Action, Times, Date
I'd like to use this, but I can't because Action is not a key, and cannot be. None of the other fields can be a key either.
insert into useractions (user, action, times) values (2, 3, 1)
on duplicate key update times = times + 1;

You left the data column out of your insert example, but you mentioned it several times so I'll assume that exists. Also, I'm assuming it's an actual date (not a timestamp or datetime).
If you add a unique index on (user,action,date) then your query will work.
Here's the DDL:
alter table useractions
add unique index unique_idx (user,action,date);
And your DML (adding the date column):
insert into useractions (user, action, times, date) values (2, 3, 1, current_date())
on duplicate key update times = times + 1;

Do you absolutely want to calculate the counter value at the time of inserting the action? It may be simpler to simply keep track of the users and actions with timestamps, like so:
+--------+----------+---------------------+
| UserID | ActionID | Time |
+--------+----------+---------------------+
| 1 | 1 | 2012-01-19 14:47:03 |
| 1 | 2 | 2012-01-19 14:48:12 |
| 1 | 3 | 2012-01-19 14:48:15 |
| 2 | 1 | 2012-01-19 14:49:33 |
| 2 | 1 | 2012-01-18 14:49:42 |
And then calculate the daily tallies with a query:
SELECT UserID,
ActionID,
DATE(Time) AS Date,
COUNT(*) AS n
FROM actions
GROUP BY UserID,ActionID,Date
ORDER BY Date,UserID,ActionID;
+--------+----------+------------+---+
| UserID | ActionID | Date | n |
+--------+----------+------------+---+
| 1 | 2 | 2012-01-17 | 2 |
| 1 | 3 | 2012-01-17 | 2 |
| 3 | 2 | 2012-01-17 | 6 |
| 1 | 1 | 2012-01-18 | 1 |
| 1 | 2 | 2012-01-18 | 1 |
| 1 | 3 | 2012-01-18 | 4 |

You can use a unique key on a combination of columns. That way you can make that combination (user, action, date) unique and your query should then work.
That really is the easiest solution. You do need rights to alter the table though.

Related

MySQL - How to order duplicate rows in a key value pair table based on multiple columns?

So I have the following key/value pair table, where users submit data through a form and each question on the form is added to the table here as an individual row. Submission_id identifies each form submission.
+----+---------------+--------------+--------+
| id | submission_id | key | value |
+----+---------------+--------------+--------+
| 1 | 10 | manufacturer | Apple |
| 2 | 10 | model | 5s |
| 3 | 10 | firstname | Paul |
| 4 | 15 | manufacturer | Apple |
| 5 | 15 | model | 5s |
| 6 | 15 | firstname | Paul |
| 7 | 20 | manufacturer | Apple |
| 8 | 20 | model | 5s |
| 9 | 20 | firstname | Andrew |
+----+---------------+--------------+--------+
From the data above you can see that the submissions with id of 10 and 15 both have the same values (just different submission id). This is basically because a user has submitted the same form twice and so is a duplicate.
Im trying to find a way to order these table where the any duplicate submissions appear together in order. Given the above table I am trying to build a query that gives me the result as below:
+---------------+
| submission_id |
+---------------+
| 10 |
| 15 |
| 20 |
+---------------+
So I want to check to see if a submission where the manufacturer, model and firstname keys have the same value. If it does then these get the submission id and place them adjacently in the result. In the actual table there are other keys, but I only want to match duplicates based on these 3 keys (manufacturer, model, firstname).
I’ve been going back and forth to the drawing board quite some time now and have tried looking for some possible solutions but cannot get something reliable.
That's not a key value table. It's usually called an Entity-Attribute-Value table/relation/pattern.
Looking at the problem, it would be trivial if the table were laid out in conventional 1st + 2nd Normal form - you just do a join on the values, group by those and take a count....
SELECT manufacturer, model, firstname, COUNT(DISTINCT submission_id)
FROM atable
GROUP BY manufacturer, model, firstname
HAVING COUNT(DISTINCT submission_id)>1;
Or a join....
SELECT a.manufacturer, a.model, a.firstname
, a.submission_id, b.submission_id
FROM atable a
JOIN atable b
ON a.manufacturer=b.manufacturer
AND a.model=b.model
AND a.firstname=b.firstname
WHERE a.submission_id<b.submission_id
;
Or using sorting and comparing adjacent rows....
SELECT *
FROM
(
SELECT #prev.submission_id AS prev_submission_id
, #prev.manufacturer AS prev_manufacturer
, #prev.model AS prev_model
, #prev.firstname AS pref_firstname
, a.submission_id
, a.manufacturer
, a.model
, set #prev.submission_id:=a.submission_id as currsid
, set #prev.manufacturer:=a.manufacturer as currman
, set #prev.model:=a.model as currmodel
, set #prev.firstname=a.forstname as currname
FROM atable
ORDER BY manufacturer, model, firstname, submission_id
)
WHERE prev_manufacturer=manufacturer
AND prev_model=model
AND prev_firstname=firstname
AND prev_submission_id<>submission_id;
So the solution is to simply make your data look like a normal relation....
SELECT ilv.values
, COUNT(ilv.submission_id)
, GROUP_CONCAT(ilv.submission_id)
FROM
(SELECT a.submission_id
, GROUP_CONCAT(CONCAT(a.key, '=',a.value)) AS values
FROM atable a
GROUP BY a.submission_id
) ilv
GROUP BY ilv.values
HAVING COUNT(ilv.submission_id)>1;
Hopefully the join and sequence based solutions should now be obvious.

How to create an update trigger where an insert operation is performed?

Consider below is my table,
register_device
id | user_id | device_id | status |
1 | 12 | 1234 | 1 |
2 | 1 | 5678 | 1 |
3 | 11 | 1456 | 1 |
Logic for trigger:
Before inserting value in register_device table, i want to check whether the new device_id value already exist in register_device table. If in case the value is already present its status need to be changed to 0, before inserting a new value, like below.
id | user_id | device_id | status |
1 | 12 | 1234 | 0 |
2 | 1 | 5678 | 1 |
3 | 11 | 1456 | 1 |
4 | 14 | 1234 | 1 |
So when i run insert operation through coding, trigger need to perform the above logic by its own in database.
Your current approach is going to make it very difficult to implement your insert logic for several reasons:
You can't use a trigger because a trigger cannot modify the table which caused it to fire
Using ON DUPLICATE KEY UPDATE also won't fly because you actually want to insert the record even if an update also happens
I propose a slightly different table design:
id | user_id | device_id | status_timestamp
The only change here is that status has become status_timestamp, which is a timestamp column. You can insert records and use NOW() for the current timestamp value.
Logically speaking, any record which does not have the max value of status_timestamp for a given device_id would correspond to a zero status in your original table. And records having the max status_timestamp value for a given device_id would correspond to a status of 1.
For example, to get the status=1 record for device_id 1234 you could use the following query:
SELECT *
FROM register_device
WHERE device_id = 1234
ORDER BY status_timestamp DESC
LIMIT 1
And to get all the status=0 records for device_id 1234 you could use:
SELECT *
FROM register_device
WHERE device_id = 1234 AND
status_timestamp < (SELECT MAX(status_timestamp) FROM register_device
WHERE device_id = 1234)

SQL ON DUPLICATE KEY with 2 unique keys?

I have a MySQL table that goes like so:
+----+--------+-------+-------------+
| id | userID | month | lastUpdated |
+----+--------+-------+-------------+
| 1 | 5 | 1 | 2017-03-27 |
| 2 | 5 | 2 | 2017-03-22 |
| 3 | 5 | 3 | 2017-03-21 |
| 4 | 9 | 1 | 2017-03-27 |
| 5 | 9 | 2 | 2017-03-22 |
| 6 | 9 | 3 | 2017-03-21 |
+----+--------+-------+-------------+
I want to make an INSERT statement to this table but use ON DUPLICATE KEY UPDATE like this:
INSERT INTO users_last_updated
(userID, month, lastUpdated)
VALUES
(:userID, :month, :lastUpdated)
ON DUPLICATE KEY UPDATE lastUpdated = :lastUpdated
The thing is, a userID can show up multiple times and a month value can show up multiple times BUT, the uniqueness of each row is a combination of userID & month (e.g: userID = 1 & month = 3 can only appear once).
Is there a way to make a combination of 2 columns to be unique?
Thanks :)
If the unique key is userid/month, then use both of those for a unique index or constraint:
create index unq_users_last_updated_2 on users_last_updated(userId, month);
I would add that it seems strange to have month without a year.
The on duplicate key uses any and all available unique indexes, including primary keys (but not limited only to primary keys).

Only return an ordered subset of the rows from a joined table

Given a structure like this in a MySQL database
#data_table
(id) | user_id | time | (...)
#relations_table
(id) | user_id | user_coach_id | (...)
we can select all data_table rows belonging to a certain user_coach_id (let's say 1) with
SELECT rel.`user_coach_id`, dat.*
FROM `relations_table` rel
LEFT JOIN `data_table` dat ON rel.`uid` = dat.`uid`
WHERE rel.`user_coach_id` = 1
ORDER BY val.`time` DESC
returning something like
| user_coach_id | id | user_id | time | data1 | data2 | ...
| 1 | 9 | 4 | 15 | foo | bar | ...
| 1 | 7 | 3 | 12 | oof | rab | ...
| 1 | 6 | 4 | 11 | ofo | abr | ...
| 1 | 4 | 4 | 5 | foo | bra | ...
(And so on. Of course time are not integers in reality but to keep it simple.)
But now I would like to query (ideally) only up to an arbitrary number of rows from data_table per distinct user_id but still have those ordered (i.e. newest first). Is that even possible?
I know I can use GROUP BY user_id to only return 1 row per user, but then the ordering doesn't work and it seems kind of unpredictable which row will be in the result. I guess it's doable with a subquery, but I haven't figured it out yet.
To limit the number of rows in each GROUP is complicated. It is probably best done with an #variable to count, plus an outer query to throw out the rows beyond the limit.
My blog on Groupwise Max gives some hints of how to do such.

Sort table records in special order

I have table:
+----+--------+----------+
| id | doc_id | next_req |
+----+--------+----------+
| 1 | 1 | 4 |
| 2 | 1 | 3 |
| 3 | 1 | 0 |
| 4 | 1 | 2 |
+----+--------+----------+
id - auto incerement primary key.
nex_req - represent an order of records. (next_req = id of record)
How can I build a SQL query get records in this order:
+----+--------+----------+
| id | doc_id | next_req |
+----+--------+----------+
| 1 | 1 | 4 |
| 4 | 1 | 2 |
| 2 | 1 | 3 |
| 3 | 1 | 0 |
+----+--------+----------+
Explains:
record1 with id=1 and next_req=4 means: next must be record4 with id=4 and next_req=2
record4 with id=5 and next_req=2 means: next must be record2 with id=2 and next_req=3
record2 with id=2 and next_req=3 means: next must be record3 with id=1 and next_req=0
record3 with id=3 and next_req=0: means that this is a last record
I need to store an order of records in table. It's important fo me.
If you can, change your table format. Rather than naming the next record, mark the records in order so you can use a natural SQL sort:
+----+--------+------+
| id | doc_id | sort |
+----+--------+------+
| 1 | 1 | 1 |
| 4 | 1 | 2 |
| 2 | 1 | 3 |
| 3 | 1 | 4 |
+----+--------+------+
Then you can even cluster-index on doc_id,sort for if you need to for performance issues. And honestly, if you need to re-order rows, it is not any more work than a linked-list like you were working with.
Am able to give you a solution in Oracle,
select id,doc_id,next_req from table2
start with id =
(select id from table2 where rowid=(select min(rowid) from table2))
connect by prior next_req=id
fiddle_demo
I'd suggest to modify your table and add another column OrderNumber, so eventually it would be easy to order by this column.
Though there may be problems with this approach:
1) You have existing table and need to set OrderNumber column values. I guess this part is easy. You can simply set initial zero values and add a CURSOR for example moving through your records and incrementing your order number value.
2) When new row appears in your table, you have to modify your OrderNumber, but here it depends on your particular situation. If you only need to add items to the end of the list then you can set your new value as MAX + 1. In another situation you may try writing TRIGGER on inserting new items and calling similar steps to point 1). This may cause very bad hit on performance, so you have to carefully investigate your architecture and maybe modify this unusual construction.