How can I add a constraint to this condition in MySQL? - mysql

My Table looks like this:
CREATE TABLE IF NOT EXISTS `entry_title` (
`entry_title_id` int(11) NOT NULL AUTO_INCREMENT,
`entry_id` int(11) NOT NULL,
`accepted` tinyint(1) DEFAULT NULL,
`entry_title_lang` char(2) CHARACTER SET ascii NOT NULL,
`entry_title_value` varchar(255) NOT NULL,
`entry_title_created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`entry_title_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
A row in the table represents a title for a content on a website.
The idea is that anyone can submit a new (hopefully improved) title.
Then the community accepts or discards the change.
If the accepted flag equals NULL this represents that the change is pending review by the community. 0 is interpreted as discarded and 1 as accepted.
The website displays the title with the most recent timestamp where the accepted flag equals 1.
When a change is pending review I no other change can be submitted until the pending one has either been accepted or declined.
Therefore i want a constraint in my database that makes sure that there is only row per entry_id where the value of accepted is NULL.
I thought about using a seperate field pending_review which is either 1 or NULL and put a UNIQUE constraint on it in combination with entry_id.
The problem with that is that I would somehow need to unset that field when the change gets accepted or declined and consistency on that level would call for another constraint that kind of leads to the same problem as the simpler solution above.

[updated]
In the standard-driven ideal world:
CHECK(NOT EXISTS(SELECT 1 FROM entry_title WHERE accepted IS NULL GROUP BY entry_id HAVING COUNT(*) > 1))
Alas, we live in imperfect world. See this question
So use trigger with the same logic instead.
[update - trigger]
Something like
CREATE TRIGGER triggerName BEFORE INSERT ON entry_title FOR EACH ROW
BEGIN
IF EXISTS(SELECT 1 FROM entry_title
WHERE accepted IS NULL AND entry_id = NEW.entry_id
GROUP BY entry_id
HAVING COUNT(*) > 1) THEN
SIGNAL SQLSTATE '45000'
END IF;
END
and also do same thing for BEFORE UPDATE.
disclaimer, I did not check this.

Related

update column dedicated to specific order

hello I have a table as follows:
DROP TABLE IF EXISTS `Master_Product`;
CREATE TABLE IF NOT EXISTS `Master_Product` (
`KeyId_Product` bigint(21) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'Id of table',
`ID_OrderInform` bigint(21) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'Order desire by user for ouput Inform Printed',
`ID_OrderReport` bigint(21) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'Order desire by user for ouput Report View Datatable',
`Name_Product` varchar(200) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'PDF Name on disk',
PRIMARY KEY (`KeyId`),
UNIQUE KEY `KeyId` (`KeyId`),
KEY `xID_OrderInform` (`ID_OrderInform`),
KEY `xID_OrderReport` (`ID_OrderReport`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=COMPACT;
Each time i insert a new product i need fill this table with the order and name, but i cant use the KeyId_Product to sort in printed Inform or View Datatables, Becouse someone users need use a desirable order.
to get this scalability i need use 2 column aditional to store the desirable order, The problem occurs when a new product must be inserted between 2 existing products, and all products that with a higher index of ordering must be pushed +1 to give space to the new one.
The only solution i find is use 2 query additional to update:
UPDATE
Master_Product
SET
ID_OrderInform = ID_OrderInform + 1
WHERE
ID_OrderInform>$NewitemOrderInform
this other
UPDATE
Master_Product
SET
ID_OrderReport = ID_OrderReport + 1
WHERE
ID_OrderReport>$NewitemOrderReport
how can I do all this in a single query, and if when updating the other products there is an error, apply the rolback that inpides even add the new record.
This does both "at the same time":
UPDATE Master_Product
SET ID_OrderInform = ID_OrderInform + (ID_OrderInform > $NewitemOrderInform),
ID_OrderReport = ID_OrderReport + (ID_OrderReport > $NewitemOrderReport);
To explain,... The (id>$x) is a boolean expression that evaluates to either false or true. false is represented as 0; true as 1. So this adds the 'correct' value (0 or 1) to each of the columns.
Meanwhile, a PRIMARY KEY is a UNIQUE KEY, so get rid of the redundant UNIQUE KEY KeyId (KeyId).
What other queries hit this table? It would probably be better to remove the indexes on Inform and Report. It takes a significant amount of effort to update many of the rows in each for each master UPDATE. And you probably only fetch all the rows when you need the ordered list.
A nitpick: BIGINT is overkill.

MYSQL: Partitioning Table keeping id unique

We are using a table which has schema like following:-
CREATE TABLE `user_subscription` (
`ID` varchar(40) NOT NULL,
`COL1` varchar(40) NOT NULL,
`COL2` varchar(30) NOT NULL,
`COL3` datetime NOT NULL,
`COL4` datetime NOT NULL,
`ARCHIVE` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`ID`)
)
Now we wanted to do partition on column ARCHIVE. ARCHIVE can have only 2 values 0 or 1 and so 2 partitions.
Actually in our case, we are using partitioning as a Archival process. To do partition, we need to make ARCHIVE column as a part of primary key. But the problem here is that 2 rows can have same ID with different ARCHIVE column value. Actually thats not the main problem for us as 2 rows will be in different partitions. Problem is when we will update the archive column value of one of them to other to move one of the row to archive partition, then it will not allow us to update the entry giving "Duplicate Error".
Can somebody help in this regard?
Unfortunately,
A UNIQUE INDEX (or a PRIMARY KEY) must include all columns in the table's partitioning function
and since MySQL does not support check constraints either, the only ugly workaround I can think of is enforcing the uniqueness manually though triggers:
CREATE TABLE t (
id INT NOT NULL,
archived TINYINT(1) NOT NULL DEFAULT 0,
PRIMARY KEY (id, archived), -- required by MySQL limitation on partitioning
)
PARTITION BY LIST(archived) (
PARTITION pActive VALUES IN (0),
PARTITION pArchived VALUES IN (1)
);
CREATE TRIGGER tInsert
BEFORE INSERT ON t FOR EACH ROW
CALL checkUnique(NEW.id);
CREATE TRIGGER tUpdate
BEFORE UPDATE ON t FOR EACH ROW
CALL checkUnique(NEW.id);
DELIMITER //
CREATE PROCEDURE checkUnique(pId INT)
BEGIN
DECLARE flag INT;
DECLARE message VARCHAR(50);
SELECT id INTO flag FROM t WHERE id = pId;
IF flag IS NOT NULL THEN
-- the below tries to mimic the error raised
-- by a regular UNIQUE constraint violation
SET message = CONCAT("Duplicate entry '", pId, "'");
SIGNAL SQLSTATE "23000" SET
MYSQL_ERRNO = 1062,
MESSAGE_TEXT = message,
COLUMN_NAME = "id";
END IF;
END //
(fiddle)
MySQL's limitations on partitioning being such a downer (in particular its lack of support for foreign keys), I would advise against using it altogether until the table grows so large that it becomes an actual concern.

Subquery result returning comma separated values using IN clause MySQL

Friends,
two tables one table is
CREATE TABLE `vbw_push_notifications` (
`push_notification_id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Primary key ,Auto increment field',
`push_notification_customer_ids` text NOT NULL COMMENT 'comma separated customer id list which was used for messaging/related customers/broadcasting',
`push_notification_message` varchar(500) NOT NULL COMMENT 'The notification message.(A new message from Veebow/A new message from <Merchant Name>/A new public deal <Deal Name> from <Merchant Name>/A new game deal <Deal Name> from <Merchant Name>',
`push_notification_time` datetime NOT NULL,
`push_notification_is_processed` tinyint(4) NOT NULL,
PRIMARY KEY (`push_notification_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1 COMMENT='The comma separated customer ids of the customers who needs ';
-- ----------------------------
-- Records of vbw_push_notifications
-- ----------------------------
INSERT INTO `vbw_push_notifications` VALUES ('1', '165836,65802,65829,65837,65838', 'test test test', '2013-11-07 12:36:42', '0');
And I have another table with the following details.
CREATE TABLE `vbw_mobile_sessions` (
`mobile_session_id` int(10) NOT NULL AUTO_INCREMENT COMMENT 'The unique identifier for a mobile session',
`mobile_session_start_time` datetime DEFAULT NULL COMMENT 'The starting time # server of a mobile session',
`mobile_session_end_time` datetime DEFAULT NULL COMMENT 'The ending time # server of a mobile session',
`mobile_session_token` varchar(255) DEFAULT NULL COMMENT 'The mobile session token generated for this session',
`mobile_session_device_id` varchar(50) DEFAULT NULL COMMENT 'The device id of the device used for making the session',
`mobile_session_customer_id` int(10) DEFAULT NULL COMMENT 'The customer ID of the customer who made this session',
`mobile_session_device_type` tinyint(4) DEFAULT NULL COMMENT 'The type of device that customer uses for this session. 0 - iOS, 1 - Android',
PRIMARY KEY (`mobile_session_id`),
KEY `fk_mobile_session_customer_id` (`mobile_session_customer_id`)
) ENGINE=InnoDB AUTO_INCREMENT=677 DEFAULT CHARSET=latin1 COMMENT='This table holds the merchant account activation links creat';
I want to use a subquery like this.
SELECT DISTINCT(mobile_session_customer_id)
FROM vbw_mobile_sessions
WHERE mobile_session_end_time IS null
AND mobile_session_customer_id IN (SELECT push_notification_customer_ids FROM vbw_push_notifications WHERE push_notification_id=6) .
This query not returns zero rows. But i am getting result when i have used like this.
SELECT DISTINCT(mobile_session_customer_id)
FROM vbw_mobile_sessions
WHERE mobile_session_end_time IS null
AND mobile_session_customer_id IN ( SELECT DISTINCT(mobile_session_customer_id)
FROM vbw_mobile_sessions
WHERE mobile_session_end_time IS null
AND mobile_session_customer_id IN (65836,65802,65829,65837,65838)
I think its in a different format the subquery is returning the result . Can you please point out the mistake i have made. Many Thanks.
Your answer is obvious. String 'a,b,c,d' has nothing to do with set of values (a,b,c,d). This is not how it will work.
The correct solution is not to use delimiter-separated values in one field. You should normalize your DB structure and create linking table. Then place your values into it and build your query with using subquery, selecting from it.
Another, possible solution is to select your data (string data) from your field in application, split it by delimiter and substitute to another query then.
The subquery is returning you a varchar with a value '1,2,3' and you need a set of integers which is 1,2,3...
The engine is treating your result of the subquery as a varchar() not a set of integers.
You can go through this question, asking exactly what you need

MySQL after insert trigger get auto incremed value, update field value after insert gives "Unknown column" error

I am trying to figure out make a trigger to assign the value of the auto incremented 'ID' primary key field that is auto generated upon insert to another field 'Sort_Placement' so they are the same after insert.
If you are wondering why I am doing this, 'Sort_Placement' is used as a sort value in a table that can be changed but by default the record is added to the bottom of the table
Table Data
`ID` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`Account_Num` mediumint(8) unsigned NOT NULL,
`Product_Num` mediumint(8) unsigned NOT NULL,
`Sort_Placement` mediumint(8) unsigned DEFAULT NULL,
`Order_Qty_C` smallint(6) NOT NULL DEFAULT '0',
`Order_Qty_B` smallint(6) NOT NULL DEFAULT '0',
`Discount` decimal(6,2) NOT NULL DEFAULT '0.00',
PRIMARY KEY (`ID`),
UNIQUE KEY `ID_UNIQUE` (`ID`)
After Insert Trigger
CREATE
TRIGGER `order_guide_insert_trigger`
AFTER INSERT ON `order_guide`
FOR EACH ROW
BEGIN
IF Sort_Placement IS NULL THEN
SET Sort_Placement = NEW.ID;
END IF;
END;
I have tried a bunch of combinations of using the "NEW" prefix with no luck. For example putting the NEW prefix before each field name.
Trying it out
INSERT INTO `order_guide` (`Account_Num`, `Product_Num`) VALUES ('5966', '3');
Insert Error
ERROR 1054: Unknown column 'Sort_Placement' in 'field list'
This seems like a bit of a hack job but I was able to get it working using the LAST_INSERT_ID() function built into MySQL.
CREATE TRIGGER `order_guide_insert_trigger`
BEFORE INSERT ON `order_guide`
FOR EACH ROW
BEGIN
IF NEW.Sort_Placement IS NULL THEN
SET NEW.Sort_Placement = LAST_INSERT_ID() + 1;
END IF;
END;
This also works and seems to work
CREATE TRIGGER `order_guide_insert_trigger`
BEFORE INSERT ON `order_guide`
FOR EACH ROW
BEGIN
IF NEW.Sort_Placement IS NULL THEN
SET NEW.Sort_Placement = (SELECT ID FROM order_Guide ORDER BY id DESC LIMIT 1) + 1;
END IF;
END;
I ran into a similar (yet different) requirement, where a field value in the table needed to be based on the new record's Auto Increment ID. I found two solutions that worked for me.
The first option was to use an event timer that runs every 60 seconds. The event updated the records where my field was set to the default of null. Not a bad solution if you don't mind the up to 60 second delay (you could run it every 1 second if the field that is being update is indexed). Basically the event does this:
CREATE EVENT `evt_fixerupper`
ON SCHEDULE EVERY 1 MINUTE
ENABLE
COMMENT '' DO
BEGIN
UPDATE table_a SET table_a.other_field=CONCAT(table_a.id,'-kittens')
WHERE ISNULL(table_a.other_field);
END;
The other option was to generate my own unique primary IDs (rather than relying upon AUTOINCREMENT. In this case I used a function (in my application) modeled after the perl module https://metacpan.org/pod/Data::Uniqid. the generated ID's are huge in length, but they work well, and I know the value before I insert, so I can use it to generate values for additional fields.

Set a datetime column with a SQL trigger

I using a MySQL server (5.5.27 - Community Server). I have a table with this definition:
CREATE TABLE IF NOT EXISTS tbl_messages (
`msg_id` VARCHAR(50) NOT NULL ,
`msg_text` VARCHAR(50) NULL ,
PRIMARY KEY (`msg_id`);
I write a trigger that, when I do an insert, the server sets the msg_id column with the current time including microseconds with this format "yyyymmddhhnnssuuuuuu". "u" is for microseconds.
I created a trigger:
create trigger tbl_messages_trigger
before insert on tbl_messages
for each row
BEGIN
SET NEW.msg_id = DATE_FORMAT(NOW(),'%Y%m%d%H%i%s%f');
END;$$
But the msg_id column only gets values like this: 20130302144818*000000*, with microseconds in zero. ¿Is it possible capture the microseconds?
TIA,
From the code provided I guess that you are trying to use microsecond to minimize probability of getting same msg_id for different rows.
Also, msg_id is the primary key, which should not present any object-specific data but only be unique. There is a good link about: http://en.wikipedia.org/wiki/Surrogate_key
The best way to deal with primary keys in MySql is AUTO_INCREMENT column attribute. If you need insert time for messages, you may provide column for it:
CREATE TABLE tbl_messages
(
`msg_id` bigint(20) NOT NULL AUTO_INCREMENT,
`msg_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`msg_text` VARCHAR(50) NULL,
PRIMARY KEY (`msg_id`)
);