I'm trying to write an SQL query that will update a 'password' record in my database when the user provides a matching email and login. I've written the query below to try and achieve this:
SET #password = 'password123';
SET #email = 'email';
SET #newPassword = 'pass54321';
IF `customer.Password` = #password WHERE `customer.Email` == #email
BEGIN
set `customer`.`Password` = #newPassword
END
I get an error saying 'Unrecognised statement type (near IF)'. If anyone knows how to solve this, any help would be appreciated! I'm very new to using SQL so this might be completely wrong!!!
I'm not a mysql user but looking at the docs (https://dev.mysql.com/doc/), there are a couple of routes to take. I'm showing you the upsert way (which is pretty cool, I've never seen it before).
First, you should be using primary keys to help with lookup performance and to help qualify the selection criteria.
# sample table with primary key
CREATE TABLE customer (customer_id int not null primary key, name VARCHAR(20), email VARCHAR(20), password VARCHAR(20));
# seed with data
INSERT INTO customer VALUES(1,'John','john#email.com','password1');
INSERT INTO customer VALUES(2,'Jane','jane#email.com','passwordx');
# set test params
SET #password = 'password1';
SET #email = 'john#email.com';
SET #newPassword = 'pass54321';
/*
depending on how the rest of the query is structured or if this
doesn't fit the requirements...you have the option to use IF EXISTS
This is using the 'ON DUPLICATE KEY UPDATE' to check if the record exists then performs an update if it does or an insert if it doesn't (aka upsert)
*/
INSERT INTO customer VALUES (1, 'John', 'john#email.com', 'password1')
ON DUPLICATE KEY UPDATE password = #newPassword;
# test for password update
SELECT * FROM customer WHERE customer_id = 1
If your table doesn't have primary keys, run! It may make more sense to use the If EXISTS statement.
Tag someone in your organization to verify that what you're doing is within the coding standards. Use the MySql docs site - it seems like it's well maintained.
Related
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.
I am currently adding a trigger to all tables in a database using this script:
exec sp_MSForEachTable 'CREATE TRIGGER [?_Update] ON ? FOR UPDATE AS
BEGIN SET NOCOUNT ON update ? set ModifiedOn = GETDATE() END'
I need to change the trigger so that it only updates the changed row so it need to look like this:
exec sp_MSForEachTable 'CREATE TRIGGER [?_Update] ON ? FOR UPDATE AS
BEGIN SET NOCOUNT ON update ? set ModifiedOn = GETDATE()
from ? updatedTable inner join inserted i
on i.[the tables primary key] = updatedTable.[the tables primary key]
END'
however I do not know how to get the [the tables primary key] value. A Trigger created by the first script looks like:
update [dim].[company] set ModifiedOn = GETDATE()
and assuming I could get the second script to work it would create this trigger:
update [dim].[company] set ModifiedOn = GETDATE()
from [Dim].[Company] updatedTable
inner join inserted i on i.CompanyKey = updatedTable.CompanyKey
Does anyone know how to do this, or if its not possible an alternative method of adding the required trigger to all tables in the database?
The ultimate goal is to know when a record was changed, preferably human readable or that can be converted to something human readable. I do not know when or where the updates will come from so using sprocs for update is out.
If all of your primary keys are, specifically, identity columns, then you can cheat and use the IDENTITYCOL "column" name:
create table T (ID int IDENTITY(1,1) not null primary key,Col1 varchar(10) not null,Changed datetime null)
go
create trigger T_T
on T
after insert,update
as
update t set Changed = GETDATE()
from inserted i
inner join
T t on i.IDENTITYCOL = t.IDENTITYCOL
IDENTITYCOL is effectively an automatic alias for whatever column is an identity column in that table.
Actually, $identity might be preferred:
update t set Changed = GETDATE()
from inserted i
inner join
T t on i.$identity = t.$identity
It would appear the IDENTITYCOL is deprecated. Not that I can actually find an actual documentation page for either of these.
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;
//
Suppose I have an attribute called phone number and I would like to enforce certain validity on the entries to this field. Can I use regular expression for this purpose, since Regular Expression is very flexible at defining constraints.
Yes, you can. MySQL supports regex (http://dev.mysql.com/doc/refman/5.6/en/regexp.html) and for data validation you should use a trigger since MySQL doesn't support CHECK constraint (you can always move to PostgreSQL as an alternative:). NB! Be aware that even though MySQL does have CHECK constraint construct, unfortunately MySQL (so far 5.6) does not validate data against check constraints. According to http://dev.mysql.com/doc/refman/5.6/en/create-table.html: "The CHECK clause is parsed but ignored by all storage engines."
You can add a check constraint for a column phone:
CREATE TABLE data (
phone varchar(100)
);
DELIMITER $$
CREATE TRIGGER trig_phone_check BEFORE INSERT ON data
FOR EACH ROW
BEGIN
IF (NEW.phone REGEXP '^(\\+?[0-9]{1,4}-)?[0-9]{3,10}$' ) = 0 THEN
SIGNAL SQLSTATE '12345'
SET MESSAGE_TEXT = 'Wroooong!!!';
END IF;
END$$
DELIMITER ;
INSERT INTO data VALUES ('+64-221221442'); -- should be OK
INSERT INTO data VALUES ('+64-22122 WRONG 1442'); -- will fail with the error: #1644 - Wroooong!!!
However you should not rely merely on MySQL (data layer in your case) for data validation. The data should be validated on all levels of your app.
MySQL 8.0.16 (2019-04-25) and MariaDB 10.2.1 (2016-04-18) now not only parse CHECK constraint but also enforces it.
MySQL: https://dev.mysql.com/doc/refman/8.0/en/create-table-check-constraints.html
MariaDB: https://mariadb.com/kb/en/constraint/
Actually, we can can set regular expression within check constraints in MySQL.
Eg.,:
create table fk
(
empid int not null unique,
age int check(age between 18 and 60),
email varchar(20) default 'N/A',
secondary_email varchar(20) check(secondary_email RLIKE'^[a-zA-Z]#[a-zA-Z0-9]\.[a-z,A-Z]{2,4}'),
deptid int check(deptid in(10,20,30))
)
;
This INSERT query will work:
insert into fk values(1,19,'a#a.com','a#b.com', 30);
This INSERT query will not work:
insert into fk values(2,19,'a#a.com','a#bc.com', 30);
I have one table: drupal.comments, with amongst others, the columns:
cid: primary key
uid: foreign key to users table, optional
name: varchar, optional
email: varchar, optional
The description says: UID is optional, if 0, comment made by anonymous; in that case the name/email is set.
I want to split this out into two tables rails.comments and rails.users, where there is always a user:
id: primary key
users_id: foreign key, always set.
So, for each drupal.comment, I need to create either a new user from the drupal.comments.name/drupal.comments.email and a rails.comment where the rails.comment.users_id is the ID of the just created user.
Or, if username/email already exists for a rails.user, I need to fetch that users_id and use that on the new comment record as foreign key.
Or, if drupal.comment.uid is set, I need to use that as users_id.
Is this possible in SQL? Are queries that fetch from one source, but fill multiple tables possible in SQL? Or is there some (My)SQL trick to achieve this? Or should I simply script this in Ruby, PHP or some other language instead?
You could do this with a TRIGGER.
Here's some pseudo-code to illustrate this technique:
DELIMITER $$
DROP TRIGGER IF EXISTS tr_b_ins_comments $$
CREATE TRIGGER tr_b_ins_comments BEFORE INSERT ON comments FOR EACH ROW BEGIN
DECLARE v_uid INT DEFAULT NULL;
/* BEGIN pseudo-code */
IF (new.uid IS NULL)
THEN
-- check for existing user with matching name and email address
select user_id
into v_uid
from your_user_table
where name = new.name
and email = new.email;
-- if no match, create a new user and get the id
IF (v_uid IS NULL)
THEN
-- insert a new user into the user table
insert into your_user_table ...
-- get the new user's id (assuming it's auto-increment)
set v_uid := LAST_INSERT_ID();
END IF;
-- set the uid column
SET new.uid = v_uid;
END IF;
/* END pseudo-code */
END $$
DELIMITER ;
I searched further and found that, apparently, it is not possible to update/insert more then one table in a single query in MySQL.
The solution would, therefore have to be scripted/programmed outside of SQL.