Case Insensitive REPLACE for MySQL - mysql

Is there a case insensitive Replace for MySQL?
I'm trying to replace a user's old username with their new one within a paragraph text.
$targetuserold = "#".$mynewusername;
$targetusernew = "#".$newusername;
$sql = "
UPDATE timeline
SET message = Replace(message,'".$targetuserold."', '".$targetusernew."')
";
$result = mysql_query($sql);
This is missing the instances where the old username is a different case. Example: replacing "Hank" with "Jack" in all the rows in my database will leave behind instances of "hank".

An easier way that works without any stored function:
SELECT message,
substring(comments,position(lower('".$targetuserold."') in message) ) AS oldval
FROM timeline
WHERE message LIKE '%".$targetuserold."%'
gives you the exact, case sensitive spellings of the username in all messages. As you seem to run that from a PHP script, you could use that to collect the spellings together with the corresponding IDs, and then run a simple REPLACE(message,'".$oldval.",'".$targetusernew."') on that. Or use the above as sub-select:
UPDATE timeline
SET message = REPLACE(
message,
(SELECT substring(comments,position(lower('".$targetuserold."') in message))),
'".$targetusernew."'
)
Works like a charm here.
Credits given to this article, where I got the idea from.

Here it is:
DELIMITER $$
DROP FUNCTION IF EXISTS `replace_ci`$$
CREATE FUNCTION `replace_ci` ( str TEXT,needle CHAR(255),str_rep CHAR(255))
RETURNS TEXT
DETERMINISTIC
BEGIN
DECLARE return_str TEXT DEFAULT '';
DECLARE lower_str TEXT;
DECLARE lower_needle TEXT;
DECLARE pos INT DEFAULT 1;
DECLARE old_pos INT DEFAULT 1;
SELECT lower(str) INTO lower_str;
SELECT lower(needle) INTO lower_needle;
SELECT locate(lower_needle, lower_str, pos) INTO pos;
WHILE pos > 0 DO
SELECT concat(return_str, substr(str, old_pos, pos-old_pos), str_rep) INTO return_str;
SELECT pos + char_length(needle) INTO pos;
SELECT pos INTO old_pos;
SELECT locate(lower_needle, lower_str, pos) INTO pos;
END WHILE;
SELECT concat(return_str, substr(str, old_pos, char_length(str))) INTO return_str;
RETURN return_str;
END$$
DELIMITER ;
Usage:
$sql = "
UPDATE timeline
SET message = replace_ci(message,'".$targetuserold."', '".$targetusernew."')
";

My solution ultimately was that I cannot do a case insensitive Replace.
However, I did find a workaround.
I was trying to have a feature where a user can change their username. The system would then need to update wherever #oldusername was found in all the messages in the database.
The problem was... people wouldn't type other people's usernames in the correct case that it is found in the members table. So when the user would change their username, it wouldn't catch those instances of #oldSeRNAmE because of it not matching the case of the real format of the oldusername.
I don't have permission with my GoDaddy shared server to do this with a customized SQL function, so I had to find a different way.
My solution: Upon inserting new messages into the database, whenever a username is found in the new message, I have an UPDATE statement at that point to replace the username they typed with the correct formatted case that is found in the members table. That way, if that person ever wants to change their username in the future, all the instances of that username in the database will all be the same exact formatted case. Problem solved.

Related

Duplicate problem in MySQL with autoincrement id

I have an issue with my database.
Table structure is:
Table name: sales
sale_id (autoincrement)
date (datetime)
total (decimal)
etc.
I have 2 computers, one is "the server" and the other is "the client", when I Insert in "sales" sometimes the database saves more than 1 record, it's an issue kind of random because one day could be normal just save 1 record as is but other day could save 2 or more duplicates.
My code is:
qry1.SQL.Text := 'SELECT * FROM sales '
+ 'WHERE sale_id = 1';
qry1.Open;
qry1.Insert;
qry1.FieldByName('date').AsDateTime := Date;
qry1.FieldByName('total').AsFloat := total;
qry1.Post;
saleId := qry1.FieldByName('sale_id').AsInteger;
qry1.Close;
// Code to save sale details using saleId.
I'm using Delphi 10.3 + ZeosLib 7.2.6-stable + MySQL 8.0
I opened the ports in the server so I have a direct connection to MySQL, I don't know what could be happening
Hope you can help me
Update----
Thanks for your kind answers,
#nbk Yes, I did it already.
#A Lombardo I used "where" to get just 1 record and then I use the query to insert the new one similar to use TTable but instead of load the hole table I just get one record and I can insert (qry.Insert),
#TheSatinKnight not only I get two records, sometimes I get 3 or more, but makes sense probably the keayboard is not working well and could send "enter" key more than once.
#fpiette, I will do ti right now.
I will keep you posted.
There are better ways to accomplish an insert than to open a TZTable and inserting on that open table.
As another approach, drop 2 TZQuery (NOT TZTable) on your form (which I'll assume is TForm1 - change as appropriate).
Assuming the name is ZQuery1 and ZQuery2.
Set its connection property the same as your TZTable, so it uses the same connector.
Set ZQuery1.SQL property to 'Insert into sales (date, total) values (:pdate, :ptotal)' //(w/o quotes)
Set ZQuery2.SQL property to 'select last_insert_id() as iddb'
now add the Function below to your form's Private delcaration
TForm1 = class(TForm)
ZQuery1: TZQuery; //added when dropped on form
ZQuery2: TZQuery;
private
{ Private declarations }
function AddNewSale(SaleDate: TDateTime; Total: Double): Integer; //add this line
public
{ Public declarations }
end;
and then add the following code to your form's methods
var
Form1: TForm1;
implementation
{$R *.dfm}
function TForm1.AddNewSale(SaleDate: TDateTime; Total: Double): Integer;
begin
ZQuery1.ParamByName('pdate').AsDateTime := SaleDate;
ZQuery1.ParamByName('ptotal').AsFloat := Total;
ZQuery1.ExecSQL; //*Execute* the Insert - Only "open" SQL that returns a result set
//now the record has been added to your DB
if ZQuery1.RowsAffected = 1 then //check to ensure it was inserted
begin
ZQuery2.Open;
try
Result := ZQuery2.FieldByName('iddb').AsInteger;
finally
ZQuery2.Close;
end;
end
else
result := -1;//indicate error by returning negative value
end;
now in the place you want to insert the record, simply call this function:
var
ReturnValue: Integer;
begin
ReturnValue := AddNewSale(Date, total);
if ReturnValue < 0 then
//error happened
else
begin
//Everything worked
end;
end;
Thanks again for all your kind answers.
At the end the problem was keyboard, It had a problem with "Enter" key, so when you pressed it, it send more than one pulsation so #TheSatinKnigh your approach was correct
#fpiette I created the log file and I figured out as you said the request had been executed twice or more.
I know maybe it is a silly thing for a programmer because I was disabling the button to late, sorry for that
#A Lombardo thanks for you code I like it better than mine I will use it

MySQL after update trigger using CONCAT_WS: Why is it inserting a newline where I DON'T want it?

The intended result is to store the notes of edits to a field, in another field.
I want the new notes to APPEND to the storage field, and since the is not function that does this I am attmpting to find a way to work this out without adding more layers of code like functions and stored procedures.
/* Before Update Trigger */
DECLARE v_description VARCHAR(255);
DECLARE v_permnotes MEDIUMTEXT;
DECLARE v_oldnote VARCHAR(500);
DECLARE v_now VARCHAR(25);
SET v_now = TRIM(DATE_FORMAT(NOW(), '%Y-%m-%d %k:%i:%s'));
SET v_oldnote = OLD.notes;
IF (NEW.permanent_notes IS NULL) THEN
SET v_permnotes = '';
ELSE
SET v_permnotes = OLD.permanent_notes;
END IF;
SET NEW.permanent_notes = CONCAT_WS(CHAR(10), v_permnotes, v_now,": ", v_description);
I'm aiming to have the results in the permanent field look like this
<datetime value>: Some annotation from the notes field.
<a different datetime>: A new annotation
etc....
What I get from my current trigger:
2018-12-30 17:15:50
:
Test 17: Start from scratch.
2018-12-30 17:35:51
:
Test 18: Used DATE_FORMAT to sxet the time
2018-12-30 17:45:52
:
Test 19. Still doing a carriage return after date and after ':'
I can't figure out why there is a newline after the date, and then again after the ':'.
If I leave out CHAR(10), I get:
Test 17: Start from scratch.
2018-12-30 17:35:51
:
Test 18: Used DATE_FORMAT to sxet the time
2018-12-30 17:45:52
:
Test 19. Still doing a carriage return after date and after ':'Test 20. Still doing a carriage return after date and after ':'
Some fresh/more experienced eyes would be really helpful in debugging this.
Thanks.
I think you should just be using plain CONCAT here:
DECLARE separator VARCHAR(1);
IF (NEW.permanent_notes IS NULL) THEN
SET separator = '';
ELSE
SET separator = CHAR(10)
END IF;
-- the rest of your code as is
SET
NEW.permanent_notes = CONCAT(v_permnotes, separator, v_now, ": ", v_description);
The logic here is that we conditionally print a newline (CHAR(10)) before each new log line, so long as that line is not the very first. You don't really want CONCAT_WS here, which is mainly for adding a separator in between multiple terms. You only want a single newline in between each logging statement.

MySQL Error pls-00103

I am making a MySQLand getting an error I dont know how to fix it. What the function should be doing is asking the user for input, this input should be stored in the corresponding variables and return a variable. But when i try run the function I am getting errors on the lines that store the variables. I am quite new to mySQL so i might be doing something stupid. Any help will be great thank you
This is is the mysql function
create or replace FUNCTION AD_AGENCY_INFO(agency_id in agency_id%type)
RETURN agency_id
DECLARE
v_no_of_runs AD_AGENCY.NO_OF_AD_RUNS%TYPE = '&enter_number_of_Runs';
v_credit_worthy AD_AGENCY.CREDIT_WORTHY%TYPE = '&enter_credit_worthy';
v_available_slots AD_AGENCY.AVAILABLE_SLOTS%TYPE = '&enter_available_slots';
v_status AD_AGENCY.STATUS%TYPE = '&enter_status';
BEGIN
insert into ad_agency values (agency_id, v_no_of_runs, v_credit_worthy,v_available_slots, v_status);
insert into ad (agency_id) values (agency_id);
commit;
RETURN (agency_id));
END AD_AGENCY_INFO;
The error that i am getting is the following and is the same for lines 7,8,9
Error(6,45): PLS-00103: Encountered the symbol "=" when expecting one of the following: := ( ; not null range default character The symbol ":= was inserted before "=" to continue.
Try changing your declare statement(s) to be
v_no_of_runs AD_AGENCY.NO_OF_AD_RUNS%TYPE := '&enter_number_of_Runs'

capture values from a simple select query TADOQuery Delphi

I am having issues returning the value gotten from a simple SELECT query using TADOQuery
here is my code below:
dbWizconQuery.SQL.Clear;
dbWizconQuery.SQL.Add('SELECT * FROM test');
tb_wizconValues.Items.Add('' + dbWizconQuery.SQL.GetText);
dbWizconQuery.ExecSQL;
processed := IntToStr(dbWizconQuery.FieldByName ('input' ).Value);
tb_wizconValues.Items.Add('' + processed);
I get the first print out in my textbox ok, with the SQL String
but then i dont get the value coming out.
Can you see why this might be?
Processed is a String and input is an INT(5) which comes out AsString
Kind Regards,
Jordan
in delphi ExecSQL is used when you want to do an
UPDATE,INSERT,DELETE
when you want to do a select call open or just set the query to
active := true;
you can get multiple results back by using this code:
dbWizconQuery.SQL.Clear;
dbWizconQuery.SQL.Text := 'SELECT * FROM test';
dbWizconQuery.Open; // or dbWizconQuery.Active := True;
while not dbWizconQuery.eof do
begin
ShowMessage(dbWizconQuery.FieldByName('FieldName').AsString); // this shows the fields value
dbWizconQuery.Next; //use this line or you will get an infinite loop
end;
dbWizconQuery.Close; //closes the dataset
for executing statements like UPDATE,INSERT,DELETE use it like this:
dbWizconQuery.SQL.Text := 'DELETE FROM test WHERE ID = 1';
dbWizconQuery.ExecSQL;
ExecSQL is used for statements that don't return a rowset, such as INSERT, DELETE and UPDATE. For a SELECT, you need to use Open instead:
dbWizconQuery.SQL.Clear;
dbWizconQuery.SQL.Add('SELECT * FROM test');
tb_wizconValues.Items.Add('' + dbWizconQuery.SQL.GetText);
dbWizconQuery.Open;
processed := IntToStr(dbWizconQuery.FieldByName ('input' ).Value);
tb_wizconValues.Items.Add('' + processed);
For more info, see the documentation for TDataSet

mysql procedure how to execute a query that retrieves data within a loop then return a custom value

I don't understand how procedures work even though I search for several topic/tutorial but only found pointless examples.
In PHP my code would look like this:
function getFullPathFromID($ID)
{
$path = '';
while($result = $c->query("SELECT IDParentFolder,path FROM folders WHERE ID=$ID")->fetch())
{
$ID = $result->IDParentFolder;
$path = $result->path.'/'.$path;
}
return $path;
}
I couldn't get it working with procedures, I can't even make a query to the db:
DELIMITER #
CREATE PROCEDURE getFullPathFromID(ID INT)
BEGIN
SELECT path FROM folders WHERE ID = ID;
END
#
When I execute it with a valid value I still get "MySQL returned an empty result set (i.e. zero rows)."
So how can I execute a query that retrieves data within a loop and then return a custom value (concatenation of each step's value)?
Thanks
You have a problem in your stored procedure because the scalar id has the same name as a column in the table. So, the expression:
where id = id
does not do what you expect.
You should write this as:
DELIMITER #
CREATE PROCEDURE getFullPathFromID(v_ID INT)
BEGIN
SELECT path FROM folders f WHERE f.ID = v_ID;
END
#
However, I'm not sure this will fix the problem of no rows being selected.
I finally got it working.
BEGIN
DECLARE v_IDParentFolder INT;
DECLARE v_Name VARCHAR(255);
SET path='';
REPEAT
SELECT IDParentFolder,Name INTO v_IDParentFolder, v_Name FROM folders WHERE ID = IDFolder;
SET IDFolder = v_IDParentFolder;
SET path = CONCAT(v_Name,'/',path);
UNTIL v_IDParentFolder IS null END REPEAT;
SET path = CONCAT('/',path);
END
For the "SELECT path FROM folders" I mistook 'path' for 'name' so I couldn't get any result indeed...
For the others beginners in SQL and especially procedures, a SELECT query will not save the result but return it, in order to only save the result "INTO" has to be used as in my code and these variables have to be declared at the beginning of the procedure.
Thanks to the guy who downvoted me, he has been really helpful.
Your code is wrong, using mysqli extension you should prepare to avoid sql injection.
You should not put the query into a loop, you would loop through a recordset, but in your case you are getting a single row so it makes no sense. here 's what you should really do:
$query = 'SELECT path FROM folders WHERE ID = ?';
$stmt = $mysqli->prepare($query);
$stmt->bind_param('i', $id);
if ($stmt->execute()){
$row = $stmt->fetch_assoc();
$path = $row['path'];
}
return $path;