I currently have a database which many users can access and make changes to. The is also a log database that stores all changes to tables within the database using triggers.
I would like to add the ability to approve edits before they are changed in the database.
What would be the best way to go about this?
We have something similar on one of our sites, we've added a bunch of tables:
users sites ... etc
Then we have a bunch of shadow tables:
users-shadow sites-shadow ... etc
The shadow tables have identical structures to the real tables except for an added line for the user who made the change. So first we use this query when a change is submitted by a user who needs to have his/her database actions approved:
REPLACE INTO users-shadow (user_mod,id,username,password,salt...) VALUES (16,50,'bob','stuff','salt'...);
Obviously, make sure this isn't open to injection, use prepared statements etc.
When approved, a row in the shadow table is simply removed from the shadow table, the user_mod value dropped and changes (non-null values) inserted into the real table (or updated if an id is specified, using REPLACE syntax). We do this logic in perl so sadly don't have any SQL on hand for it.
Remember that SQL REPLACE does a DELETE and an INSERT rather than an UPDATE. You will need to amend any triggers to allow for this behaviour.
Note: The reason we didn't use an 'approve' flag was that we needed the ability to amend existing records, of course we couldn't have multiple records with the same primary key.
well i have made this system once and here is my solution for DB structure and over all algorithm:
there should be a sub system of admin panel which different users can manage their products but every change should be approved by administrator before going affecting the main Product table. there is three main table:
1.Product : store products that have final approved and are used in entire system
2.Changes_versions : a table with One To Many Relation with Product Table that indicates each change version is committed by who , when ,and is approved/rejected by admin or still is in Pending state .table structure is as following :
CREATE TABLE changes_versions(
xid int(11) unsigned NOT NULL AUTO_INCREMENT,
xcreated_date datetime DEFAULT NULL,
xupdated_date timestamp NULL DEFAULT NULL,
xversion int(11) DEFAULT NULL,
xobject_id int(11) DEFAULT NULL,
xobject_type varchar(255) DEFAULT NULL,
xstate enum('PENDING','ACCEPTED','REJECTED') DEFAULT 'PENDING',
PRIMARY KEY (xid)
) ENGINE=InnoDB AUTO_INCREMENT=165 DEFAULT CHARSET=utf8
3.Changes : a table that have One To Many relation with Changes_versions table that keep every column change record of the main Table (here i mean product table) and by approving a change_version record by admin its related changes records will be placed in main table column. table structure is as following :
CREATE TABLE changes(
xid int(11) unsigned NOT NULL AUTO_INCREMENT,
xcreated_date datetime DEFAULT NULL,
xcreated_by varchar(255) DEFAULT NULL,
xupdated_date timestamp NULL DEFAULT NULL,
xupdated_by varchar(255) DEFAULT NULL,
xversion_id int(11) DEFAULT NULL,
xcolumn_name varchar(255) DEFAULT NULL,
xcolumn_value varchar(255) DEFAULT NULL,
xstate enum('PENDING','ACCEPTED','REJECTED') DEFAULT 'PENDING',
xadmin_review text,
PRIMARY KEY (xid)
) ENGINE=InnoDB AUTO_INCREMENT=764 DEFAULT CHARSET=utf8
with this system and table schema i handled to work with record changes, user fetch list of records ,if user have any Pending state change_version, system will pull its related changes records and place them in the right column in the fetched product row(temporary just for displaying) , so even if user has any pending state changes he/she can see its changes in his/her panel(not main system, only his/her panel).
at the end if system administrator accept a user changes_version version and its related changes records ,system should place each changes table record in the right column of product table(for example i used product table, with this system you can versioning and admin approving any table).and change version record state to approved and its changes related records to approved to. so with this structure you can save and versioning different tables and keep log of each version changes.
Related
Mysql and SQL users. This question related to both of you. Its about indexing. I have this table structure for a classified website. I have a one common table to store title, description, user who post etc.. Also I have this table structure to store detail attributes about a particular ad category.
CREATE TABLE `ad_detail` (
`ad_detail_id_pk` int(10) NOT NULL AUTO_INCREMENT,
`header_id_fk` int(10) NOT NULL,
`brand_id_fk` smallint(5) NULL,
`brand_name` varchar(200) NULL,
`is_brand_new` bool,
.......
`transmission_type_id_fk` tinyint(3) NULL,
`transmission_type_name` varchar(200) NULL,
`body_type_id_fk` tinyint(3) unsigned NULL,
`body_type_name` varchar(200) NULL,
`mileage` double NULL,
`fuel_type_id_fk` tinyint(3) NULL,
......
PRIMARY KEY (`ad_detail_id_pk`)
)
SO as you can see first part of the attributes will belong to mobile ads and second part belongs to vehicle ads like so on I have other attributes for other categories. header_id_fk will hold the relationship to header table which have common information. So all of these foreign keys are some what involves in filtering ads. Some may wants to find all the mobile phones which made by Nokia. SO then the brand_id_fk will be use. Some may wants to filter vehicle by fuel type. So as you can see I need to index every filtering attributes in this table. So now this is my question.
So when user post a mobile ad insert statement will contain certain no of fields to store data. But as we all know index will gain the performance when data retrieval but it will make additional cost to insert and update queries. So if I insert mobile ad, will that insert query suffer from other attributes which are relevant to vehicles ads' index fields?
Yes, normal indexes contain one row for every row in the table (unless you use oracle http://use-the-index-luke.com/sql/where-clause/null). So therefore every index will have a new row inserted every time you insert a row into the table, and the associated index maintenance issues (page splits etc.)
You could create a filtered/partial index which excludes nulls which would solve the particular issue of INSERT performance being slowed down by indexes on fields into which you're inserting NULL but you would need to test the solution thoroughly to make sure that the indexes are still being used by the queries that you expect them to be used. Note that mysql does not support partial indexes, AFAIK, the following is for sql-server.
Create Index ix_myFilteredIndex On ad_detail (brand_id_fk) Where brand_id_fk Is Not Null;
I've been doing a lot of new learning about MySQL and triggers. I think I understand the concept and I realise there are a LOT of possible dangers in using them. However I believe the limited use of them is correct for the function I want to perform.
I have 9 tables which correspond to 9 different web based Ajax engined forms. I've worked hard on these, being my first time using Ajax, and I'm reasonably happy with them. Each time a user makes a change to whichever form they are filling out, the change is Ajaxed back to the DB and they get a confirmation or error response. Fairly straight forward. Each forms respective table has a "status" field, a "lastModified" field and a field I call "agRef" which is sort of like status but is null until the form reaches a certain stage, further along the process.
I have an additional table called "records" which is where all entries in any of the other tables, is listed so we can easily see what forms have been started, when their last changes were made and what status's they have. So here is where I believe the trigger part should work, so that I don't have to make updates to the "records" table in my php on every single transaction.
The "records" table is set out like this:
`uaID` int(11) NOT NULL AUTO_INCREMENT,
`uID` int(11) NOT NULL,
`appNo` int(11) NOT NULL,
`applicationKey` varchar(8) NOT NULL,
`appID` int(11) DEFAULT NULL,
`applicationName` varchar(64) NOT NULL,
`agRef` varchar(32) DEFAULT NULL,
`status` varchar(32) NOT NULL,
`dateStarted` int(11) NOT NULL,
`lastModified` int(11) NOT NULL,
Now all of these fields are populated at the same time the matching entry is inserted into which ever one of the other 9 tables the form connects to. A small example of one of the other 9 tables would look like this:
`appID` int(11) NOT NULL AUTO_INCREMENT,
`uID` int(11) NOT NULL,
`uaID` int(11) NOT NULL,
`status` varchar(32) NOT NULL DEFAULT 'Data Acquisition',
`agRef` varchar(32) DEFAULT NULL,
`groupName` varchar(64) DEFAULT NULL,
`shortTitle` varchar(64) DEFAULT NULL,
`recipient` varchar(64) DEFAULT NULL,
`partOfValCh` varchar(64) DEFAULT NULL,
`sector` varchar(64) DEFAULT NULL,
`subSector` varchar(64) DEFAULT NULL,
`topic` varchar(64) DEFAULT NULL,
<snip because this can go on for a lot of lines>
`dateStarted` int(11) NOT NULL,
`lastModified` int(11) NOT NULL,
agRef on both tables remain null for now, appID is null on the records table initially at the point of creation but is updated immediately as soon as the corresponding entry is made into the second table, where it is generated by auto increment and then a call is made back to my records table to insert the appID there.
The three things that will change from any of the data tables are the three fields "status", "agRef", "lastModified".
So I'm trying to create a trigger that will do this after each alteration/update to the data table, so that the data in my records table is consistent and accurate.
This is my first ever trigger set up attempt:
DELIMITER $$
CREATE TRIGGER `dataTableOne_to_records_sync` AFTER UPDATE ON `dataTableOne`
FOR EACH ROW BEGIN
UPDATE records (agRef, status, lastModified) VALUES (NEW.agRef, NEW.status, NEW.lastModified) WHERE appID = OLD.appID;
END$$
DELIMITER ;
I am trying to set this up through phpmyadmin, but it is returning an error telling me I have a syntax problem within my UPDATE line. I feel that it is an issue with the WHERE part - the appID is the one common element that ties the row in "records" to the row being updated/changed in "dataTableOne". How do I set this up correctly? Is my error something more serious, and am I running the risk of creating a huge mess, like a never ending loop? I'm a bit paranoid about doing this for the first time. Thanks in advance for help and advice.
UPDATE I have now tried a few other trigger attempts but although MySQL will accept them as being valid trigger syntax, they always seem to break the entire DB functionality. Can anyone help me with my trigger syntax to get it to work correctly? In the demo tables above, if the SECOND table gets updated at all, I want the three fields copied over into the FIRST table by the trigger. The three values I want copied across are "status", "agRef", and "lastModified".
My most recent failed attempt was this:
CREATE TRIGGER AIGltInq_sync AFTER INSERT ON app_AIGltInq
FOR EACH ROW
UPDATE records r
SET r.agRef = NEW.agRef
, r.status = NEW.status
, r.lastModified = NEW.lastModified
WHERE uaID = NEW.uaID;
I'm not at all familiar with that form of the UPDATE statement.
To change values of columns in rows, we'd typically write an UPDATE statement like this:
UPDATE records r
SET r.agRef = NEW.agRef
, r.status = NEW.status
, r.lastModified = NEW.lastModified
WHERE r.appId = OLD.appID
Reference: https://dev.mysql.com/doc/refman/5.5/en/update.html
Question withdrawn. Triggers are seriously best to avoid, as they tend to cause more breakages than they fix! Most recommendations tend towards handling the function with whatever scripting language you are using to talk with the DB. In my case this is PHP and PHP is now performing all of the functionality I was hoping to short-cut by using triggers. Lesson? Don't take short-cuts when wanting to do the job right. :)
I have a service in which users may "like" content posted by other users. Currently, the system doesn't filter out content that the user has already liked, which is undesirable behavior. I have a table called LikeRecords which stores a userID, a contentID, and a timePlaced timestamp. The idea is to use this table to filter content that a user has already liked when choosing what to display.
The thing is, I'm a MySQL amateur, and don't understand scaling and maintenance well. Even though I only have about 1,500 users, this table already has 45,000 records. I'm worried that as my service grows to tens or hundreds of thousands of users, this table will explode into millions and become slow since the filter operation would be called very frequently.
Is there a better design pattern I could use here, or a maintenance technique I should use?
EDIT: Here is the query for building the table in question:
CREATE TABLE `likerecords` (
`likeID` int(11) NOT NULL AUTO_INCREMENT,
`userID` int(10) unsigned NOT NULL,
`orderID` int(11) NOT NULL,
`timePlaced` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`special` tinyint(1) NOT NULL,
PRIMARY KEY (`likeID`)
) ENGINE=InnoDB AUTO_INCREMENT=44775 DEFAULT CHARSET=latin1
I would be using it to filter results in other tables, such as an "orders" table.
I have a system where users posts data and they can upgrade their post by optionioally paying to upgrade. This is the information I want to store from stripe on their payment response:
CREATE TABLE IF NOT EXISTS `db`.`pay` (
`payments_id` int(11) NOT NULL AUTO_INCREMENT
payment, unique index',
`stripe_id`
`card_id`
`brand`
`amount`
`created`
`currency`
`paid`
`refunded`
`exp_month`
`exp_year`
`last4`
`country`
`fingerprint`
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='payments';
Should this be in the same table as the one containing the main post data, or should it be a separate and linked table. What logic is used to make this decision?
On one hand it seems nice to separate it but then you also have the overhead of linking the tables. Only one payment will ever be associated with one post.
Card data should be in a different table with a userId that links to your user table.
With limited knowledge of what you're trying to achieve I would say you need at least 3 tables.
User Table
Post Table with userId linking back to user table
Payment Card Table with userId linking back to user table
I'm trying to implement a way to track changes to a table named user and another named report_to Below are their definitions:
CREATE TABLE `user`
(
`agent_eid` int(11) NOT NULL,
`agent_id` int(11) DEFAULT NULL,
`agent_pipkin_id` int(11) DEFAULT NULL,
`first_name` varchar(45) NOT NULL,
`last_name` varchar(45) NOT NULL,
`team_id` int(11) NOT NULL,
`hire_date` date NOT NULL,
`active` bit(1) NOT NULL,
`agent_id_req` bit(1) NOT NULL,
`agent_eid_req` bit(1) NOT NULL,
`agent_pipkin_req` bit(1) NOT NULL,
PRIMARY KEY (`agent_eid`),
UNIQUE KEY `agent_eid_UNIQUE` (`agent_eid`),
UNIQUE KEY `agent_id_UNIQUE` (`agent_id`),
UNIQUE KEY `agent_pipkin_id_UNIQUE` (`agent_pipkin_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `report_to`
(
`agent_eid` int(11) NOT NULL,
`report_to_eid` int(11) NOT NULL,
PRIMARY KEY (`agent_eid`),
UNIQUE KEY `agent_eid_UNIQUE` (`agent_eid`),
KEY `report_to_report_fk_idx` (`report_to_eid`),
CONSTRAINT `report_to_agent_fk` FOREIGN KEY (`agent_eid`) REFERENCES `user` (`agent_eid`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `report_to_report_fk` FOREIGN KEY (`report_to_eid`) REFERENCES `user` (`agent_eid`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8
What can change that needs to be tracked is user.team_id, user.active and report_to.report_to_eid. What i currently have implemented is a table that is populated via an update trigger on user that tracks team changes. That table is defined as:
CREATE TABLE `user_team_changes`
(
`agent_id` int(11) NOT NULL,
`date_changed` date NOT NULL,
`old_team_id` int(11) NOT NULL,
`begin_date` date NOT NULL,
PRIMARY KEY (`agent_id`,`date_changed`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
This works fine for just tracking team changes. I'm able to use joins and a union to populate a history view that tracks that change over time for the individual users. The issue of complexity rises when I try to implement tracking for the other two change types.
I have thought about creating additional tables similar to the one tracking changes for teams, but I worry about performance hits due to the joins that will be required.
Another way I have considered is creating a table similar to a view that I have that details the current user state (it joins all necessary user data together from 4 tables), then insert a record on update with a valid until date field added. My concern with that is the amount of space this could take.
We will be using the user change history quite a bit as we will be running YTD, MTD, PMTD and time interval reports with it on an almost daily basis.
Out of the two options I am considering, which would be the best for my given situation?
The options you've presented:
using triggers to populate transaction-log tables.
including a new table with an effective-date columns in the schema and tracking change by inserting new rows.
Either one of these will work. You can add logging triggers to other tables without causing any trouble.
What distinguishes these two choices? The first one is straightforward, once you get your triggers debugged.
The second choice seems to me that it will create denormalized redundant data. That is never good. I would opt not to do that. It is possible with judicious combinations of views and effective-date columns to create history tables that are viewable as the present state of the system. To learn about this look at Prof. RT Snodgrass's excellent book on Developing Time Oriented applications. http://www.cs.arizona.edu/~rts/publications.html If you have time to do an excellent engineering (over-engineering?) job on this project you might consider this approach.
The data volume you've mentioned will not cause intractable performance problems on any modern server hardware platform. If you do get slowdowns on JOIN operations, it's almost certain that the addition of appropriate indexes will completely fix them, as long as you declare all your DATE, DATETIME, and TIMESTAMP fields NOT NULL. (NULL values can mess up indexing and searching).
Hope this helps.