Blocking the entry of null values into a database - mysql

I have a Database which i am accessing it through an application .the application takes all kind of values including Blank(My mistake :( should have constrained this in my application only). I want to run a script such that it blocks the entry of null values into my database .
for example : Books is my database with columns author , price , title
I want to block null author name entry ,
I have used a trigger as shown below;
DELIMITER |
CREATE TRIGGER trigger1 AFTER INSERT ON books
FOR EACH ROW BEGIN
IF NEW.author = '' THEN
delete from books where author = '';
END IF;
END;
|books
DELIMITER ;
This script meets the objective of blocking null values , But has some inconsistency (after certain point i'm not able to access the database at all , i can neither enter a value nor access one ).
Can any one tell me what might be the problem.

There are multiple ways you could achieve this-
1)Change the trigger to BEFORE INSERT
and if the author name is null, return false
This would stop the application from inserting blank author names.
2)Alter the table schema, and add a constraint not null to the author name.
Refer Blocking null value entries into database

Related

INSERT field=LAST_INSERT_ID() [duplicate]

I'm designing a comments MySQL db, and my comments table has fields:
id primary key, auto-incr
thread int, not null
content
All replies to the same comment, in addition to that root comment must share the same thread. This is simple when a user is replying to a comment but what about when a new root comment is posted? I figured I would set thread=id for root comments.
Problem is, I don't know how to write a query that will reuse the just created id value within the same query when filling thread. Is this even possible?
I've tried
INSERT INTO `comments`
VALUES (NULL, LAST_INSERT_ID(), 'hi there')
This gives me the id from the previous insert, not the current one. Do I have to use 2 queries?
Do I have to use 2 queries?
Yes. As you discovered, the id value hasn't been generated yet in a BEFORE INSERT trigger. But you can't change your NEW.thread value in an AFTER INSERT trigger.
You can't rely on reading the INFORMATION_SCHEMA, because you can cause a race condition.
You'll just have to do the INSERT, and then immediately execute:
UPDATE comments SET thread=id WHERE id=LAST_INSERT_ID() AND thread IS NULL;
If it's a root comment.
See also my past answers on the similar topic:
Concatenating a string and primary key Id while inserting
Two autoincrements columns or autoincrement and same value in other column
Thanks to Michael's answer I started looking into triggers; basically event-handlers that run some code when something happens. Michael's trigger didn't work for me, but the following does:
USE `my_db_name`;
DELIMITER $$
CREATE TRIGGER comments_bi
BEFORE INSERT ON `comments`
FOR EACH ROW
BEGIN
DECLARE nextID INT DEFAULT 0;
SELECT AUTO_INCREMENT INTO nextID FROM information_schema.tables
WHERE table_name = 'comments' AND table_schema = DATABASE();
IF NEW.`thread` IS NULL OR NEW.`thread` = 0 THEN
SET NEW.`thread` = nextID;
END IF;
END $$
DELIMITER ;
One big caveat: because this trigger requires access to the information_schema, only the root account could define it.
Thanks to this answer for inspiration
You can simply set thread to NULL to signify that the comment is a root comment rather than attached to a thread.

OUTPUT Inserted.row in mysqli

I have the following sql table:
id|email|fbid
When I perform the query
INSERT INTO users(email,fbid) VALUES('randomvalue','otherrandomvalue')
I want to get the id of the inserted row. To do so, I've tried to edit the query like this:
INSERT INTO users(email,fbid) VALUES('randomvalue','otherrandomvalue') OUTPUT Inserted.id
But I'm getting:
1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use
near 'OUTPUT Inserted.id' at line 1
What could be the problem?
Unfortunately (as far as I can tell) mysql does not support output as sql-server does.
You do have an option for what you're trying to accomplish in a single row insert (assuming auto_increment primary key):
SELECT LAST_INSERT_ID();
This unfortunately would not work in the case of a batch insert - though in your case you are not (at least not in your example), so this should be fine.
I'm going to use the process i describe below to handle the same situation with a private at home (non-enterprise) application that i wrote for personal use. I know this question is a year old right now but there doesn't seem to be an adequate answer for batch processing. I can't find an adequate answer. MySQL doesn't seem to have the facilities built into it to handle this type of thing.
I had concerns about the reliability of this solution, when put into a production environment where multiple different users/jobs could access the same procedure at the same time to do the insert. I believe I have resolved these concerns by adding the connection id to the #by variable assignment. Doing this makes it so that the by has a: the connection id for the session and b: the name of the program/job/procedure doing the insert. Combined with the date AND time of the insert, I believe these three values provide a very secure key to retrieve the correct set of inserted rows. If absolute certainty is required for this, you could possibly add a third column of a GUID type (or varchar) generate a GUID variable to insert into that, then use the GUID variable along with #by and #now as your key. I feel it's unnecessary for my purpose because the process I'm going to use it in is an event (job) script that runs on the server rather than in PHP. So I am not going to exemplify it unless someone asks for that.
WARNING
If you are doing this in PHP, consider using a GUID column in your process rather than the CreatedBy. It's important that you do that in PHP because your connection can be lost in between inserting the records and trying to retrive the IDS and your CreatedBy with the connection ID will be rendered useless. If you have a GUID that you create in PHP, however, you can loop until your connection succeeds or recover using the GUID that you saved off somewhere in a file. The need for this level of connection security is not necessary for my purposes so I will not be doing this.
The key to this solution is that CreatedBy is the connection id combined with the name of the job or procedure that is doing the insert and CreatedDate is a CURRENT_TIMESTAMP that is held inside a variable that is used through the below code. Let's say you have a table named "TestTable". It has the following structure:
Test "Insert Into" table
CREATE TABLE TestTable (
TestTableID INT NOT NULL AUTO_INCREMENT
, Name VARCHAR(100) NOT NULL
, CreatedBy VARCHAR(50) NOT NULL
, CreatedDate DATETIME NOT NULL
, PRIMARY KEY (TestTableID)
);
Temp table to store inserted ids
This temporary table will hold the primary key ids of the rows inserted into TestTable. It has a simple structure of just one field that is both the primary key of the temp table and the primary key of the inserted table (TestTable)
DROP TEMPORARY TABLE IF EXISTS tTestTablesInserted;
CREATE TEMPORARY TABLE tTestTablesInserted(
TestTableID INT NOT NULL
, PRIMARY KEY (TestTableID)
);
Variables
This is important. You need to store the CreatedBy and CreatedDate in a variable. CreatedBy is stored for consistency/coding practices, CreatedDate is very important because you are going to use this as a key to retrieve the inserted rows.
An example of what #by will look like: CONID(576) BuildTestTableData
Note that it's important to encapsulate the connection id with something that indicates what it is since it's being used as a "composite" with other information in one field
An example of what #now will look like: '2016-03-11 09:51:10'
Note that it's important to encapsulate #by with a LEFT(50) to avoid tripping a truncation error upon insert into the CreatedBy VARCHAR(50) column. I know this happens in sql server, not so sure about mysql. If mysql does not throw an exception when truncating data, a silent error could persist where you insert a truncated value into the field and then matches for the retrieval fail because you're trying to match a non-truncated version of the string to a truncated version of the string. If mysql doesn't truncate upon insert (i.e. it does not enforce type value restrictions) then this is not a real concern. I do it out of standard practice from my sql server experience.
SET #by = LEFT(CONCAT('CONID(', CONNECTION_ID(), ') BuildTestTableData'), 50);
SET #now = CURRENT_TIMESTAMP;
Insert into TestTable
Do your insert into test table, specifying a CreatedBy and CreatedDate of #by and #now
INSERT INTO TestTable (
Name
, CreatedBy
, CreatedDate
)
SELECT Name
, #by
, #now
FROM SomeDataSource
WHERE BusinessRulesMatch = 1
;
Retrieve inserted ids
Now, use #by and #now to retrieve the ids of the inserted rows in test table
INSERT INTO tTestTablesInserted (TestTableID)
SELECT TestTableID
FROM TestTable
WHERE CreatedBy = #by
AND CreatedDate = #now
;
Do whatever with retreived information
/*DO SOME STUFF HERE*/
SELECT *
FROM tTestTablesInserted tti
JOIN TestTable tt
ON tt.TestTableID = tti.TestTableID
;
if You are using php then it is better to use following code :
if ($conn->query($sql) === TRUE) {
$last_id = $conn->insert_id;
echo "New record created successfully. Last inserted ID is: " . $last_id;
} else {
echo "Error: " . $sql . "<br>" . $conn->error;
}
where $conn is connection variable.

How to display result and or any message in trigger body?

Hi I want to create trigger if it's condition satisfy then it's body should be executed and I want to display some message or any data that should be displayed if trigger body executed.
I want that if quantity of product went less then 50 then it should display message or some data.
Is it possible to display message ?
Here testdata is table name.
Code :
delimiter //
create trigger trigger2 before update on test.testdata
for each row
begin
if new.qty < 50 then
**display here some message that quantity is less**
end if;
end;
//
delimiter ;
You cannot do it, there is no place to output them in MySQL. As a work around you can add your message to the table, and then read this table.
Short example -
CREATE TABLE table1 (
column1 VARCHAR(255) DEFAULT NULL
);
CREATE TABLE messages (
id INT(11) NOT NULL AUTO_INCREMENT,
message VARCHAR(255) DEFAULT NULL,
time TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id)
);
DELIMITER $$
CREATE TRIGGER trigger1
AFTER INSERT
ON table1
FOR EACH ROW
BEGIN
INSERT INTO messages(message) VALUES ('new action');
END
$$
DELIMITER ;
Also, you could use UDF function write your logic.
More information - CREATE FUNCTION Syntax for User-Defined Functions.
For Quick and plain answering: You cannot Display Messages From Triggers. You may Only Throw errors.
You are propably not knowing the reason u use triggers in databases assuming from your question. We all have passed that level so dont worry. U have understood the syntax when we use triggers but not how and what they can do and not.
A trigger will do (for your case BEFORE an UPDATE) something concerning the database and only that.
That means the trigger cannot display any message on your screen. You can only handle database staff and not all of the actions are allowed for that too or some actions arent even recommended!.
That is for the theory part.
To give you a solution to your problem now.
The only thing you can do to know when the trigger has worked (that means when the new.qua<50) or basically check anything with any other trigger is the following. (Just a small fast solution):
You need to create a Table that will handle all logging of the
triggers.
Add in it an ID field, a descr field that will hold the action of
the triggerex. BefUpdate, BefInsert etc. another field for the
propably the condition that triggered the logging and antyhing else
u want displayed later in the application.
Then inside the if condition u are using write and insert
statemement to fill the info in the new (logging) table.
in your app later select that logging table to see the messages.
That is a useful and fast way to log, not only triggers but also functions (stored procs).
Judt for reference i give you s sample code with the CREATE, and the INSERT statement for your trigger.
CREATE TABLE LOGGER (
ID BIGINT PRIMARY KEY AUTO_INCREMENT,
DESCR_ACTIVITY VARCHAR(10),
ACTIVITY VARCHAR(50),
DT TIMESTAMP,
CONDITIONVALUE VARCHAR(50)
)
In the IF of your code now make it as :
if new.qty < 50 then
INSERT INTO LOGGER VALUES ('BEFINS','CHECKING QUA',NULL,'QUANTITY IS LOWER THAN 50')
end if;
And even from the workbench or from your application u can just :
SELECT * FROM LOGGER
to see the loggings.
But if i am confused from the reading and you want just to throw an error u can read the Mysql Documentation concerning throwing errors:
enter link description here
What u can do is in your if condition write something like:
if new.qty < 50 then
SIGNAL SQLSTATE '01000' SET MESSAGE_TEXT = 'Lower than 50', MYSQL_ERRNO = 1000;
endif;
What u should always NOT DO is alter the same table that a trigger is assigned and use only small portion of not so complex code in the trigger.
Hope i helped a bit.
Also, you can display any message using the select command.
IF (NEW.qty < 50) THEN
SELECT "message that quantity is less" AS Output;
END IF
Place above code inside the trigger. It will print the output

Trigger issue with syntax

Hi to all im trying to create a trigger for this table:
create table Episode(
title varchar(25) not null,
number int not null,
length time not null,
aired date not null,
serie_name varchar(25),
PRIMARY KEY(title,number),
FOREIGN KEY (serie_name)REFERENCES Serie(name)
) ENGINE=InnoDB;
this is a table of a db that saves some tv series...so the trigger have to check if i m trying to insert a new episode that had benn aired before the precedent one....But i have some problem any solutions?
i tried this:
create trigger ControlDataEp
before insert on Episode
for each row
begin
if new.aired<(select aired from Episode where title=new.title and number=new.number-1)
then raise.application_error(-21,'error');
end if;
end;
If you don't that much care about returning meaningful error message you can simplify your trigger to one statement
CREATE TRIGGER ControlDataEp
BEFORE INSERT ON Episode
FOR EACH ROW
SET NEW.aired =
(
SELECT IF(IFNULL(MAX(aired), 0) < NEW.aired, NEW.aired, NULL)
FROM Episode
WHERE title = NEW.title
AND number = NEW.number - 1
);
What it does it violates NOT NULL constraint on aired column.
Here is SQLFiddle demo. Uncomment last insert statement
Now if you do need to return a custom error message:
You can use SIGNAL but only if you on MySql 5.5 and higher
Use one the hackish ways (read e.g. TRIGGERs that cause INSERTs to fail? Possible?, Throw an error in a MySQL trigger)
If I were you, I wouldn't use a trigger when there's no need to.
You can use
INSERT IGNORE ...
INSERT ... ON DUPLICATE KEY UPDATE ...
REPLACE ...
Please have a look in the respective manual pages I linked for additional information.
To explain, why your solution doesn't work:
if new.aired<(select aired
Your subquery might return multiple rows. Use SELECT MAX(aired) ... instead.
... and number=new.number-1)
It's a bad idea to rely on code outside the database to make sure, that data in the database is okay.
then raise.application_error(-21,'error');
raise.application_error() is not a MySQL built-in function. You can't call functions from your code in MySQL like this. If you really want to raise an error in MySQL use SIGNALs

MySQL Trigger - Update relation table with extra values

What I'm trying to achieve is, I want to automate the values of the table between the users and folders table. Since it's a many-to-many relationship I created the user_folders table. Currently the server (nodejs) gets the request with userid, clientfolderid and some an array of bookmarks (which are not important now). It checks if the user already has this folder, by selecting from the user_folders table and if it's not existing it inserts a new row into the folder table. Then it has to send another statement to insert into the user_folders table.
So I have to "manually" keep the users_folder table updated.I guess this is a common problem and wanted to know if there is a pattern or a proven solution? The odd thing is that MySQL automatically handles the deletion of rows with an AFTER DELETE trigger but there is no (at least that I know of) automation with an AFTER INSERT trigger.
As I already said an AFTER INSERT trigger could possibly solve it, but I think it's not possible to pass some extra parameters to the AFTER INSERT trigger. This would be the user_id and the folder_client_id in my case.
I was thinking of a solution that I could create another table called tmp_folder which would look like:
tmp_folder
-- id
-- title
-- changed
-- user_id
-- folder_client_id
Then create an AFTER INSERT trigger on this table which inserts into folders and user_folders and then removes the row from tmp_folder again. Would this be the right way or is there a better one?
I would basically do the same with the bookmarks and user_bookmarks table. The best thing would be if it's even possible to insert a folder then the owner into the user_folders table with user_id and folder_client_id and then multiple other users into user_folders with the user_id and an default folder_client_id of -1 or something which will be updated later.
Meanwhile thanks for reading and I hope you can help me :)
PS: Is there a name for the table between 2 other tables in an m-2-m relationship?
I don't see an easy way to do this via triggers, but a stored procedure may suit you:
DELIMITER //
CREATE PROCEDURE
add_user_folder(
IN u_user_id BIGINT UNSIGNED,
IN u_folder_client_id BIGINT UNSIGNED,
IN v_title VARCHAR(255)
)
BEGIN
DECLARE u_found INT UNSIGNED DEFAULT 0;
SELECT
1 INTO u_found
FROM
user_folders
WHERE
user_id = u_user_id AND
folder_client_id = u_folder_client_id;
IF IFNULL(u_found, 0) = 0 THEN
START TRANSACTION;
INSERT INTO
folders
SET
title = v_title,
changed = UNIX_TIMESTAMP();
INSERT INTO
user_folders
SET
user_id = u_user_id,
folder_id = LAST_INSERT_ID(),
folder_client_id = u_folder_client_id;
COMMIT;
END IF;
END;
//