MySQL update field on insert if other field (not key) changed - mysql

I'm relatively confused about this...
I've got a table like:
+----------------+--------------------------------------------------+------+-----+-------------------+-----------------------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+--------------------------------------------------+------+-----+-------------------+-----------------------------+
| dhcp | int(10) unsigned | NO | PRI | 0 | |
| ip | int(10) unsigned | NO | PRI | 0 | |
| mac | varchar(17) | YES | MUL | NULL | |
| lease | enum('fr','a','fi','a','u') | NO | MUL | free) | | |
| date | timestamp | NO | MUL | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
| uid | varchar(255) | YES | | NULL | |
| starts_date | datetime | YES | | NULL | |
| starts_weekday | tinyint(1) | YES | | NULL | |
| ends_date | datetime | YES | | NULL | |
| ends_weekday | tinyint(1) | YES | | NULL | |
| first_seen | datetime | YES | | NULL | |
+----------------+--------------------------------------------------+------+-----+-------------------+-----------------------------+
I just added the first_seen column. The idea is that I will use INSERT... ON DUPLICATE KEY UPDATE. dhcp and ip are rightly primary keys, as I want to only have one record for them at a time. So if mac changes, it should update the existing row if one exists for that dhcp, ip combination.
however, I want to have first_seen updated every time a (ip, dhcp, mac) combination is seen... i.e. if the value of 'mac' changes, I want to update first_seen. If the value of 'mac' stays the same, I want to leave first_seen the same.
Is there any simple way to do this in SQL... i.e. an IF() or something? or do I have to handle this with another SELECT in the PHP script (keeping in mind that we're parsing a file to get this data, and inserting abut 10-16k rows, so time is a factor).
Thanks,
Jason

Have you considered using triggers? That's a MySQL server-side event happening when some other event, such as update of the mac column, happens.

You could use a MySQL trigger for this.

I think you might want to use the TIMESTAMP column in MySQL - this will change when the record is updated or inserted. It will change when any value in the row changes however. If you want something else to happen you might consider a trigger that fires after update and validates the other business logic you referenced.

Thanks for all the suggestions, guys. It turns out that I don't need a trigger for this, just an IF at the beginning of the update statement.
in the INSERT INTO... I have first_seen=CURRENT_TIMESTAMP
Then at the beginning of the UPDATE I have
first_seen = IF( mac != VAULES(MAC), CURRENT_TIMESTAMP, first_seen)
This only updates first_seen if the mac changes, else it sets first_seen to itself (its current value).

Related

MySQL: Proper way to implement a "conditional primary key"

Here is my submissions table. Users make Submissions on Challenges. They can make as many submissions as they want, until there is a correct submission. Once a correct submission is recorded there should be no more submissions given a challenge_id, user_id combo. I was initially enforcing this constraint from within my app but would like to move this constraint to the DB.
+--------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| user_id | int(11) | YES | MUL | NULL | |
| challenge_id | int(11) | YES | MUL | NULL | |
| correct | tinyint(1) | YES | | NULL | |
| timestamp | datetime | YES | | NULL | |
| flag | varchar(512) | YES | | NULL | |
+--------------+--------------+------+-----+---------+----------------+
What I've Tried
I've tried making the primary key of the table be KEY(user_id, challenge_id, correct). The problem with this is that there could be multiple submissions as long as correct is false.
What is one way to solve this issue?
If you don't need to record the incorrect submissions, don't.
If you do need the full history, it cannot be done by a UNIQUE key, as you observed.
Plan A: Add a TRIGGER that checks for inserting a second correct answer. Meanwhile, something else would need to be the PK.
Plan B: Have a table of correct submissions and a table of incorrect (or possibly all) submissions. Neither would necessarily need the correct column. And perhaps some other columns don't have to be in both tables. The PKs would be different.

MySQL - 'Updated By' column to automatically update with user who modified the row?

Here's my table.
+-------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+--------------+------+-----+---------+----------------+
| ID | int(11) | NO | PRI | NULL | auto_increment |
| Postcode | varchar(255) | YES | | NULL | |
| Town | varchar(255) | YES | | NULL | |
| Region | varchar(255) | YES | | NULL | |
| Company Name | varchar(255) | YES | | NULL | |
| Fee | double | YES | | NULL | |
| Company Benefits | varchar(255) | YES | | NULL | |
| Date Updated | date | YES | | NULL | |
| Website | mediumtext | YES | | NULL | |
| Updated By | varchar(255) | YES | | NULL | |
| Notes | varchar(255) | YES | | NULL | |
| LNG | varchar(255) | YES | | NULL | |
| LAT | varchar(255) | YES | | NULL | |
+-------------------+--------------+------+-----+---------+----------------+
You can see we have an "Updated by" column.
How can I make it so that, when a user updates the row, the "Updated By" column automatically updates (or inserts if it's a new row they're adding) with the currently logged-in users name?
Many Thanks
You will have to explicitly make sure about that and whenever an UPDATE is happening then you need to update that column as well saying below. Best way to assure it, have your application logic fill in the column whenever an UPDATE to the record is happening from current logged-in user principle or claim
update tbl1
set ...,
Updated By = <logged in user name>
where Id = <some val>
You can use USER() or CURRENT_USER() in Update or Insert statements to achieve needed effect.
From my side - the only one secure way is to create stored procedures, providing inserts or updates to desired table.
Indeed, this problem was discussed here:
mysql Set default value of a column as the current logged in user
Something like this !
CREATE TRIGGER `updater`.`tableName_BEFORE_INSERT` BEFORE INSERT ON `tableName`
FOR EACH ROW
BEGIN
Set New.Updated_By = current_user();
END

disabling entries (for a particular set of days) in a database

I'm working on an application using JSP and MYSQL.
The application includes feature for user to input 'Date' and 'Hours', which are stored in a table 'epinfo' in MYSQL.
+-------+-------------+------+-----+------------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+------------+-------+
| uid | varchar(10) | NO | PRI | | |
| dt | date | NO | PRI | 0000-00-00 | |
| hrs | int(2) | YES | | NULL | |
+-------+-------------+------+-----+------------+-------+
Another feature is applying for leave, stored in a different table 'leave'
+--------+-------------+------+-----+------------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+-------------+------+-----+------------+-------+
| uid | varchar(10) | NO | PRI | | |
| stdt | date | NO | PRI | 0000-00-00 | |
| enddt | date | NO | PRI | 0000-00-00 | |
+--------+-------------+------+-----+------------+-------+
I need to disable and delete any entries for this time period, in the table epinfo.
Eg. If a leave application entry is 2014-06-14 to 2014-06-16, I need to make sure the user cannot enter any data for 14,15,16th of June in the table epinfo.
I know a BEFORE INSERT Trigger can help here, which goes something like this:
create trigger cons_check
before insert on epinfo
for each row
begin
if exists(select * from leave where stdt<=entered_date and enddt>=entered_date)
then
Disable entry in database
else
insert into table.
end if
end
Can someone help me create this trigger? Or help with a different solution?
Also, the entered date is the value I have in my insert query. How can I use this value in the trigger?

mysql time field conditions for expiration

I have table with auction lots:
mysql> desc lots;
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | int(10) | NO | PRI | NULL | auto_increment |
| account_id | int(10) | YES | | NULL | |
| item_id | int(10) | YES | | NULL | |
| bid | int(10) | YES | | NULL | |
| buyout | int(10) | YES | | NULL | |
| leader | int(13) | YES | | NULL | |
| left | int(10) | YES | | NULL | |
+------------+--------------+------+-----+---------+----------------+
left - field in unix timestamp format like '1391143424'. This shows the timestamp after which the lot should be expired.
I need to develop efficient algorithm for:
-when customer click on SEARCH button, the result will show only non-expired lots
-once lot expired it should be automatically put into completed_lots table
I have some ideas, but want to verify if there is any better approaches:
1: add additional search criteria like:
WHERE '$currtime' <= left;
2: setup some asynchronous tasks using crontab that will check lots table for expired lots every 5 minutes and move it to completed_lots table.
Are there any better ways? maybe using another mysql filed types for unix time, or create some mysql functions that will do that job automatically?

Audit logging for products data?

When the staff change the information of product name, option name or prices. It should insert the data into history log and who done it.
items table:
item_id (PK)
item_name
item_description
Note: item prices are in the item_options table
item_options table:
option_id (PK)
item_id (FK)
option_name
option_price
A item can have 1 or more options.
If I want to change the name items.item_name, It should copy the current record to the history table, delete current record from items table and then insert a new record with the new information in the items table?
What about the item_options, how would that work? If there are multiple options from specific item_id, do that mean I need to duplicate options to history table?
What Audit logging/history tables should look like for items and item_options?
Thanks
Your audit data should be stored per-table, rather than all in one place. What you'd do is create an audit table for each of the tables you want to track, and create triggers to create a record in the audit table for any data-manipulation operation on the audited table.
It's definitely advisable to disallow DELETE operations on the items and item_options tables - add flags like item_active and item_option_active so that you can softdelete them instead. This is normal practice in situations where you're doing things like storing invoices that reference products ordered in the past, and need the data for historical reporting purposes, but not for day-to-day use.
Your audit tables aren't something you should use for referencing old data, your normal data model should support simply "hiding" old data where it's likely that it's still going to be used, and storing multiple versions of data that will change over time.
For auditing, it's also useful to store the username of the last user to modify a given record - when used from a web application, you can't use MySQL's USER() function to get any useful information about who's logged on. Adding a column and populating it means you can use that information in your audit triggers.
NB: I'll assume that you won't allow item IDs to be changed under normal conditions - that would make your auditing system more complex.
If you add active flags, and last-modified-by data to your tables, they'll look something like:
Items table:
mysql> desc items;
+------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------------+--------------+------+-----+---------+----------------+
| item_id | int(11) | NO | PRI | NULL | auto_increment |
| item_name | varchar(100) | YES | | NULL | |
| item_description | text | YES | | NULL | |
| item_active | tinyint(4) | YES | | NULL | |
| modified_by | varchar(50) | YES | | NULL | |
+------------------+--------------+------+-----+---------+----------------+
Item options table:
mysql> desc item_options;
+---------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+--------------+------+-----+---------+----------------+
| option_id | int(11) | NO | PRI | NULL | auto_increment |
| item_id | int(11) | YES | MUL | NULL | |
| option_name | varchar(100) | YES | | NULL | |
| option_price | int(11) | YES | | NULL | |
| option_active | tinyint(4) | YES | | NULL | |
| modified_by | varchar(50) | YES | | NULL | |
+---------------+--------------+------+-----+---------+----------------+
Your audit tables need to store four extra pieces of information:
Audit ID - this ID is only unique for the history of this table, it's not a global value
Change made by - the database user who made the change
Change date/time
Action type - INSERT or UPDATE (or DELETE if you were allowing it)
Your audit tables should look something like:
Items audit table:
mysql> desc items_audit;
+------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------------+--------------+------+-----+---------+----------------+
| audit_id | int(11) | NO | PRI | NULL | auto_increment |
| item_id | int(11) | YES | | NULL | |
| item_name | varchar(100) | YES | | NULL | |
| item_description | text | YES | | NULL | |
| item_active | tinyint(4) | YES | | NULL | |
| modified_by | varchar(50) | YES | | NULL | |
| change_by | varchar(50) | YES | | NULL | |
| change_date | datetime | YES | | NULL | |
| action | varchar(10) | YES | | NULL | |
+------------------+--------------+------+-----+---------+----------------+
Item options audit table:
mysql> desc item_options_audit;
+---------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+--------------+------+-----+---------+----------------+
| audit_id | int(11) | NO | PRI | NULL | auto_increment |
| option_id | int(11) | YES | | NULL | |
| item_id | int(11) | YES | | NULL | |
| option_name | varchar(100) | YES | | NULL | |
| option_price | int(11) | YES | | NULL | |
| option_active | tinyint(4) | YES | | NULL | |
| modified_by | varchar(50) | YES | | NULL | |
| change_by | varchar(50) | YES | | NULL | |
| change_date | datetime | YES | | NULL | |
| action | varchar(10) | YES | | NULL | |
+---------------+--------------+------+-----+---------+----------------+
Don't use foreign keys on your audit tables; the rows in the audit tables aren't child rows of the records they're auditing, so foreign keys aren't of any use.
Triggers
NB: MySQL doesn't support multi-statement-type triggers, so you need one for each of INSERT, UPDATE and DELETE (if applicable).
Your triggers simply need to INSERT all the NEW values into the audit table. The trigger definitions for the items table might be:
/* Trigger for INSERT statements on the items table */
CREATE DEFINER=`root`#`localhost` TRIGGER trigger_items_insert_audit
AFTER INSERT ON items
FOR EACH ROW BEGIN
INSERT INTO items_audit (
item_id, item_name, item_description,
item_active, modified_by, change_by,
change_date, action
) VALUES (
NEW.item_id, NEW.item_name, NEW.item_description,
NEW.item_active, NEW.modified_by, USER(),
NOW(), 'INSERT'
);
END;
/* Trigger for UPDATE statements on the items table */
CREATE DEFINER=`root`#`localhost` TRIGGER trigger_items_update_audit
AFTER UPDATE ON items
FOR EACH ROW BEGIN
INSERT INTO items_audit (
item_id, item_name, item_description,
item_active, modified_by, change_by,
change_date, action
) VALUES (
NEW.item_id, NEW.item_name, NEW.item_description,
NEW.item_active, NEW.modified_by, USER(),
NOW(), 'UPDATE'
);
END;
Create similar triggers for the item_options table.
Update: Data History In E-commerce
The auditing we did above will allow you to keep a history of any given database table, but creates a data store that isn't suitable for use for data that needs to be accessed regularly.
In an e-commerce system, keeping usable historical data is important, so that you can change attributes while still presenting old values in certain situations.
This should be completely separate from your auditing solution
The best way to store history is to create a history table for each attribute that needs to be stored historically. This Stackoverflow question has some good information about keeping a history of a given attribute.
In your situation, if you're only concerned about price and title, you'd create a prices table, and an item_titles table. Each one would have a foreign key to either the item_options table or the items table (the master tables would still store the current price, or title), and would have the price or title, with its effective dates. These tables should have fine-grained (possibly column-based) permissions to avoid updating the effective_from dates, and the actual values once the record is inserted.
You should use the auditing solution above on these tables also.
if you do not have a bunch of constraints - then your data will get messed up in a hurry when you orphan the item entries by removing option entries and visaversa.
what you are asking for can be done in triggers, but this is not probably what you want.
imaging if you have an item with 2 options.
now you change the item name, that item gets deelted (and moved to history) - you have unlinkable options... is that what you intend?
what about order or other things that reference the items? same issues.
instead, create trigger logic to only allow 'reasonable' edits to the item. if desired, put a copy of the record into a parallel history table, but DO NOT delete the original.
you may also consider adding a status column to the item or some date ranges in order to account for the idea that this item is currently available or whatever other status you may need.