I am trying to update my db table by checking if my product_number column already exists. I know you can use the INSERT OR UPDATE ON DUPLICATE KEY but the column I am trying to use is not a primary key.
I found the following query on a stack post but I keep getting the error Unrecognized statement type. (near Duplicate) when I try run it using phpMyAdmin.
DECLARE #numrecords INT
SELECT #numrecords = count(*)
FROM users_remark
WHERE product_number = '444'
IF #numrecords > 0 THEN
UPDATE products
SET product_name = 'abc',
product_description = def,
product_detail = '',
product_price = '100',
product_price_canada = '200'
WHERE product_number = '444'
ELSE
INSERT INTO products (product_number, product_name, product_description, product_price)
VALUES (444, abc, def, 100)
END IF
Why is this query not working?
Is there a better way to achieve what I am trying to accomplish?
I created a procedure for you:
DELIMITER $$
DROP PROCEDURE IF EXISTS insert_or_update $$
CREATE PROCEDURE insert_or_update(pProduct_number INT, pProduct_name VARCHAR(200), pProduct_description VARCHAR(200), pProduct_detail VARCHAR(200), pProduct_price FLOAT(15,2), pProduct_price_canada FLOAT(15,2))
BEGIN
DECLARE numrecords INT;
SELECT count(*) INTO numrecords FROM products WHERE product_number = pProduct_number;
IF numrecords > 0 THEN
UPDATE products SET
product_name = pProduct_name,
product_description = pProduct_description,
product_detail = pProduct_detail,
product_price = pProduct_price,
product_price_canada = pProduct_price_canada
WHERE product_number = pProduct_number;
ELSE
INSERT INTO products (product_number, product_name, product_description, product_price)
VALUES (pProduct_number, pProduct_name, pProduct_description, pProduct_price);
END IF;
END $$
DELIMITER ;
To call just:
CALL insert_or_update(444, , 'abc', 'def', '', '100', '444');
Related
I have a simple HTML form and some php variables as follows to submit book details to a MySQL table named Book using submit method. clcode is a auto increment field.
$isbn =$_POST["isbn"];
$bno =$_POST["b_no"];
$bname =$_POST["b_name"];
$qty =$_POST["qty"];
$price =$_POST["price"];
And need to insert records depending on the qty. eg:- If a qty=2,
It needs to execute the insert query twice (Should be inserted 2 same records with auto increment clcodes). If a qty=5,
It needs to execute the insert query 5 times (Should be inserted 5 same records with auto increment clcodes) and so on... I used a stored procedure and the following SQL query to do that.
$query ="CREATE PROCEDURE myproc()
BEGIN
DECLARE i int DEFAULT 0;
DO
INSERT INTO book (isbn, b_no, b_name, qty, price) VALUES ('$isbn', '$bno', '$bname', '$qty', '$price');
SET i = i + 1;
WHILE i < $qty;
END WHILE;
END";
$result = mysql_query($query) or die ( mysql_error());
But it is return a syntax error. I can not uderstand what I am going wrong. Pls. anyone can help me...? Tnx.
Create table
create table book (
id int not null auto_increment,
isbn VARCHAR(20) not null,
b_no VARCHAR(20) not null,
b_name VARCHAR(20) not null,
qty INT not null,
price decimal(8, 2) not null,
primary key(id)
);
Create SP
DELIMITER //
CREATE PROCEDURE proc_book (
IN isbn VARCHAR(20),
IN no VARCHAR(20),
IN name VARCHAR(20),
IN qty INT,
IN price DECIMAL(8,2)
)
BEGIN
DECLARE i INT DEFAULT 0;
WHILE i < qty DO
INSERT INTO book (isbn, b_no, b_name, qty, price) VALUES (isbn, no, name, qty, price);
SET i = i + 1;
END WHILE;
END;
DELIMITER ;
Test SP
call proc_book('978-3-16-148410-0', '1', 'name1', 2, 10.00);
Data in table
+----+-------------------+------+--------+-----+-------+
| id | isbn | b_no | b_name | qty | price |
+----+-------------------+------+--------+-----+-------+
| 1 | 978-3-16-148410-0 | 1 | name1 | 2 | 10.00 |
| 2 | 978-3-16-148410-0 | 1 | name1 | 2 | 10.00 |
+----+-------------------+------+--------+-----+-------+
Call SP from PHP
Use mysqli instead of mysql
<?php
// input data should be filterd to prevent SQL injection etc.
$isbn = $_POST["isbn"];
$bno = $_POST["b_no"];
$bname = $_POST["b_name"];
$qty = $_POST["qty"];
$price = $_POST["price"];
//connect to database
$connection = mysqli_connect("hostname", "user", "password", "db", "port");
//run the store proc
$sql = "CALL proc_book('" . $isbn . "', '" . $bno . "', '" . $bname . "', " . $qty . ", " . $price . ")";
$result = mysqli_query($connection, $sql) or die("Query fail: " . mysqli_error());
?>
Reference
CREATE PROCEDURE
Stored Procedures
MySQL WHILE
How to call a MySQL stored procedure from within PHP code?
It doesn't make sense to create a procedure to meet your requirement.
Suppose it worked for first run, but create procedure will show error in next run because procedure is already there.
You just need to run insert query a number of times, or build insert query with multiple values and run once.
Here is what I think you should do.
1.for($i=0;$i< $qty;$i++){
$sql = "INSERT INTO table_name (field1,field2,fieldn) VALUES ('field-val1','field-val2','field-valn')";
$result = mysql_query($sql) or die ( mysql_error());
}
Or approach 2
$sql = "INSERT INTO table_name (field1,field2,fieldn) VALUES";
for($i=0;$i< $qty;$i++){
$sql .= "('val1','val2','valn')";
if($i < ($qty -1 )){
$sql .=",";
}
}
$result = mysql_query($sql) or die ( mysql_error());
You don't want to CREATE PROCEDURE in your query. You just want to INSERT. You should really be just looping in php to execute a prepared statement to perform the insert.
Read the PHP docs on using a mysqli prepared statement.
$count = 0
while($qty >= $count){
//insertion code goes here...
$count++
}
[begin_label:] WHILE search_condition DO
statement_list
END WHILE [end_label]
this is what the syntax of while loop
For e.g.:-
CREATE PROCEDURE dowhile()
BEGIN
DECLARE i INT DEFAULT 0;
WHILE i < $qty DO
INSERT INTO book (isbn, b_no, b_name, qty, price) VALUES ('$isbn', '$bno', '$bname', '$qty', '$price');
SET i = i + 1;
END WHILE;
END;
Try this one.
Modify your sql statement :
$query = "DELIMITER $$
CREATE PROCEDURE myproc()
BEGIN DECLARE i integer DEFAULT 0;
while (i< ".$qty.") do
begin
INSERT INTO book (isbn, b_no, b_name, qty, price) VALUES (".$isbn.", ".$bno.", ".$bname.", ".$qty.", ".$price."); SET i = i + 1; end; end while;
END $$
DELIMITER" ;
For more details visit: http://www.mysqltutorial.org/stored-procedures-loop.aspx
Performance benchmark with and without transaction in SP.
1.) Without transactions.
DELIMITER //
CREATE PROCEDURE proc_book (
IN isbn VARCHAR(20),
IN no VARCHAR(20),
IN name VARCHAR(20),
IN qty INT,
IN price DECIMAL(8,2)
)
BEGIN
DECLARE i INT DEFAULT 0;
WHILE i < qty DO
INSERT INTO book (isbn, b_no, b_name, qty, price) VALUES (isbn, no, name, qty, price);
SET i = i + 1;
END WHILE;
END;
DELIMITER ;
Try to add 1000 qty (without transactions)
mysql> call proc_book('978-3-16-148410-0', '1', 'name1', 10000, 10.00);
Query OK, 1 row affected (8 min 43.01 sec)
2.) With transactions.
DELIMITER //
CREATE PROCEDURE proc_book (
IN isbn VARCHAR(20),
IN no VARCHAR(20),
IN name VARCHAR(20),
IN qty INT,
IN price DECIMAL(8,2)
)
BEGIN
DECLARE i INT DEFAULT 0;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
END;
DECLARE EXIT HANDLER FOR SQLWARNING
BEGIN
ROLLBACK;
END;
START TRANSACTION;
WHILE i < qty DO
INSERT INTO book (isbn, b_no, b_name, qty, price) VALUES (isbn, no, name, qty, price);
SET i = i + 1;
END WHILE;
COMMIT;
END;
DELIMITER ;
Try to add 1000 qty (with transactions)
call proc_book('978-3-16-148410-0', '1', 'name1', 10000, 10.00);
Query OK, 0 rows affected (0.21 sec)
I am trying to track users sales based on the new transactions that come through. The transactions are assigned through our site and not their desktop application. It is giving me a syntax error, but I can't seem to figure out why.
This is my code:
DROP TRIGGER IF EXISTS AssignTrans;
DELIMITER $$
CREATE TRIGGER `testing_cake3`.`AssignTrans` AFTER INSERT ON `main_1` FOR EACH ROW
BEGIN
SET #t1 = (SELECT team_id FROM team_assignments WHERE team_assignments.misc_id = NEW.CUSTOMER_ID AND team_assignments.type = 1);
SET #t2 = (SELECT team_id FROM team_assignments WHERE team_assignments.misc_id = NEW.GROUP_ID AND team_assignments.type = 0);
IF (#t1) THEN
INSERT INTO team_trans (team_id, trans_id, type, misc_id) VALUES (#t1, NEW.id, 0, 1 );
ELSE IF (#t2) THEN
INSERT INTO team_trans (team_id, trans_id, type, misc_id) VALUES (#t2, NEW.id, 0, 1 );
END IF;
END $$
DELIMITER ;
THe answer is CHANGING THE ELSE IF TO ElSEIF
I have a procedure called SELECT_DESCRIPTION that receives an id and returns a Description field I need to show in my page.
Now I want to create a new procedure that having a number of ids coming from a select clause like this:
SELECT id FROM MYTABLE
Can pass it to the SELECT_DESCRIPTION procedure so I can have the same number of descriptions
If I was using php I would be doing something like this:
$sql=”SELECT id FROM MYTABLE”;
$result = mysql_query($sql) //using mysql to make the example faster but TSQL is what I use
or die(mysql_error());
while($row = mysql_fetch_array( $result )) {
$newSql = "EXEC SELECT_DESCRIPTION #id = '$row[‘id’]'";
//do whatever with $newSql
}
But I need to use a procedure. Is is possible to do it? How can I do it?
Thanks a ton!
So you're wanting to do all of this in TSQL? Something like this would do it:
DECLARE #tmp TABLE (tmpID INT IDENTITY, tblID INT)
DECLARE #RecordCount INT,
#LoopCount INT,
#ID INT
INSERT INTO #tmp SELECT id FROM MYTABLE
SELECT #RecordCount = COUNT(*) FROM #tmp
SET #LoopCount = 1
WHILE #LoopCount <= #RecordCount
BEGIN
SELECT #ID = tblID FROM #tmp WHERE tmpID = #LoopCount
EXEC SELECT_DESCRIPTION #ID
SELECT #LoopCount = #LoopCount + 1
END
The #tmp table has an identity column that insures whatever data you're running the loop on has consecutive row numbers (1, 2, 3, 4, etc).
I have the following stored procedure:
proc_main:begin
declare done tinyint unsigned default 0;
declare dpth smallint unsigned default 0;
create temporary table hier(
AGTREFERRER int unsigned,
AGTNO int unsigned,
depth smallint unsigned default 0
)engine = memory;
insert into hier values (p_agent_id, p_agent_id, dpth);
/* http://dev.mysql.com/doc/refman/5.0/en/temporary-table-problems.html */
create temporary table tmp engine=memory select * from hier;
while done <> 1 do
if exists( select 1 from agents a inner join hier on a.AGTREFERRER = hier.AGTNO and hier.depth = dpth) then
insert into hier
select a.AGTREFERRER, a.AGTNO, dpth + 1 from agents a
inner join tmp on a.AGTREFERRER = tmp.AGTNO and tmp.depth = dpth;
set dpth = dpth + 1;
truncate table tmp;
insert into tmp select * from hier where depth = dpth;
else
set done = 1;
end if;
end while;
select
a.AGTNO,
a.AGTLNAME as agent_name,
if(a.AGTNO = b.AGTNO, null, b.AGTNO) as AGTREFERRER,
if(a.AGTNO = b.AGTNO, null, b.AGTLNAME) as parent_agent_name,
hier.depth,
a.AGTCOMMLVL
from
hier
inner join agents a on hier.AGTNO = a.AGTNO
inner join agents b on hier.AGTREFERRER = b.AGTNO
order by
-- dont want to sort by depth but by commission instead - i think ??
-- hier.depth, hier.agent_id;
a.AGTCOMMLVL desc;
drop temporary table if exists hier;
drop temporary table if exists tmp;
end proc_main
While the function does its job well - it only currently allows sorting via AGTCOMMLVL descending order. The stored procedure's purpose is to match a memberID with their parentID and associated COMMLVL. Once paired appropriately,I use the memberID in a second query to return information about that particular member.
I would like to be able to sort by any number of filters but have the following problems:
I can't seem to find a way to pass a variable into the stored procedure altering its sorting by field.
Even if I could - the sort may actually only contain data from the second query (such as first name, last name, etc)
Running a sort in the second query does nothing even though syntax is correct - it always falls back to the stored procedure's sort.
any ideas?
EDIT
My php uses mysqli with code:
$sql = sprintf("call agent_hier2(%d)", $agtid);
$resulta = $mysqli->query($sql, MYSQLI_STORE_RESULT) or exit(mysqli_error($mysqli));
If you want to sort by input parameter of the stored procedure, you need to use Prepared staments
For example,
DELIMITER //
CREATE PROCEDURE `test1`(IN field_name VARCHAR(40) )
BEGIN
SET #qr = CONCAT ("SELECT * FROM table_name ORDER BY ", field_name);
PREPARE stmt FROM #qr;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END //
$stmt = $dbh->prepare("CALL sp_takes_string_returns_string(?)");
$value = 'hello';
$stmt->bindParam(1, $value, PDO::PARAM_STR|PDO::PARAM_INPUT_OUTPUT, 4000);
// call the stored procedure
$stmt->execute();
print "procedure returned $value\n";
This also works in Mysql 5.6
DELIMITER //
CREATE PROCEDURE `test1`(IN field_name VARCHAR(40) )
BEGIN
"SELECT * FROM table_name ORDER BY ", field_name);
END //
I am looking to create a function that gets me a random item from a mySQL table, but let's me keep the returned as the "item of the day". In other words, the item that was "the item of the day" yesterday should not be shown again until all other items have been shown as item of the day.
Any suggestions on how to do this in an elegant way?
Thanks
Add a bool column "UsedAsItemOfTheDay" set to false (0). Update to true when item is picked. Exclude already used items from the picking process.
SELECT * FROM `table`
WHERE UsedAsItemOfTheDay = 0
ORDER BY RAND() LIMIT 1;
(Note: this is not the fastest way to return a random row in MySql; it will be slow on huge tables)
See also: quick selection of a random row from a large table in mysql
SELECT <fields> FROM <table> WHERE <some logic to exclude already used> ORDER BY RAND() LIMIT 1 will get you a random row from the table.
Add a column to store whether the item has been used:
ALTER TABLE your_table ADD COLUMN isused BOOL DEFAULT 0;
Get a random item of the day:
SELECT t.*
FROM your_table t
WHERE t.isused = 0
ORDER BY RAND()
LIMIT 1
Now update that record so it can't be used in the future:
UPDATE your_table
SET isused = 1
WHERE id = id_from_select_random_statement
People who "know" SQL will look for declarative solutions and will shun procedural code. Flagging rows is a "smell" for procedural code.
Is the set of Items static (never changes) or stable (rarely changes)? If yes, it would be easier to do a one-off exercise of generating a lookup table of values from now until the end of time, rather than scheduling a proc to running daily to look for unused flags and update the flag for today and clear all flags if all have been used etc.
Create a table of sequential dates between today and a far future date representing the lifetime of your application (you could consider omitting non-business days, of course). Add a column(s) referencing the key in you Items table (ensure you opt for ON DELETE NO ACTION referential action just in case those Items prove not to be static!) Then randomly assign the whole set of Items one per day until each has been used once. Repeat again for the whole set of Items until the table is full. You could easily generate this data using a spreadsheet and import it (or pure SQL if you are hardcore ;)
Quick example using Standard SQL:
Say there are only five Items in the set:
CREATE TABLE Items
(
item_ID INTEGER NOT NULL UNIQUE
);
INSERT INTO Items (item_ID)
VALUES (1),
(2),
(3),
(4),
(5);
You lookup table would be as simple as this:
CREATE TABLE ItemsOfTheDay
(
cal_date DATE NOT NULL UNIQUE,
item_ID INTEGER NOT NULL
REFERENCES Items (item_ID)
ON DELETE NO ACTION
ON UPDATE CASCADE
);
Starting with today, add the whole set of Items in random order:
INSERT INTO Items (item_ID)
VALUES ('2010-07-13', 2),
('2010-07-14', 4),
('2010-07-15', 5),
('2010-07-16', 1),
('2010-07-17', 3);
Then, starting with the most recent unfilled date, add the whole set of Items in (hopefully a different) random order:
INSERT INTO Items (item_ID)
VALUES ('2010-07-18', 1),
('2010-07-19', 3),
('2010-07-20', 4),
('2010-07-21', 5),
('2010-07-22', 2);
...and again...
INSERT INTO Items (item_ID)
VALUES ('2010-07-23', 2),
('2010-07-24', 3),
('2010-07-25', 5),
('2010-07-26', 1),
('2010-07-27', 4);
..and so on until the table is full.
Then it would then simply be a case of looking up today's date in the lookup table as and when required.
If the set of Items changes then the lookup table would obviously need to be regenerated, so you need to balance out the simplicity of design against the need for manual maintenance.
If you have fixed items you can add column
ALTER TABLE your_table ADD COLUMN item_day INT DEFAULT 0;
then selecting item use
WHERE item_day = DATE_FORMAT('%j')
If you get empty result then you can format new list of day items:
<?php
$qry = " UPDATE your_table SET item_day = 0";
$db->execute($qry);
// You only need 355 item to set as item of the day
for($i = 0; $i < 355; $i++) {
$qry = "UPDATE your_table SET item_day = ".($i+1)." WHERE item_day = 0 ORDER BY RAND() LIMIT 1";
$rs = $db->execute($qry);
// If no items left stop update
if (!$rs) { break; }
}
?>
Here's a stored procedure which selects a random row without using ORDER BY RAND(), and which resets the used flag once all items have been used:
DELIMITER //
DROP PROCEDURE IF EXISTS random_iotd//
CREATE PROCEDURE random_iotd()
BEGIN
# Reset used flag if all the rows have been used.
SELECT COUNT(*) INTO #used FROM iotd WHERE used = 1;
SELECT COUNT(*) INTO #rows FROM iotd;
IF (#used = #rows) THEN
UPDATE iotd SET used = 0;
END IF;
# Select a random number between 1 and the number of unused rows.
SELECT FLOOR(RAND() * (#rows - #used)) INTO #rand;
# Select the id of the row at position #rand.
PREPARE stmt FROM 'SELECT id INTO #id FROM iotd WHERE used = 0 LIMIT ?,1';
EXECUTE stmt USING #rand;
# Select the row where id = #id.
PREPARE stmt FROM 'SELECT id, item FROM iotd WHERE id = ?';
EXECUTE stmt USING #id;
# Update the row where id = #id.
PREPARE stmt FROM 'UPDATE iotd SET used = 1 WHERE id = ?';
EXECUTE stmt USING #id;
DEALLOCATE PREPARE stmt;
END;
//
DELIMITER ;
To use:
CALL random_iotd();
The procedure assumes a table structure like this:
CREATE TABLE `iotd` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`item` varchar(255) NOT NULL,
`used` BOOLEAN NOT NULL DEFAULT 0,
INDEX `used` (`used`),
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
Here's one way to get the result from PHP (to keep things simple, error checking has been removed):
$mysqli = new mysqli('localhost', 'root', 'password', 'database');
$stmt = $mysqli->prepare('CALL random_iotd()');
$stmt->execute();
$stmt->bind_result($id, $item);
$stmt->fetch();
echo "$id, $item\n";
// 4, Item 4
UPADATE
This version should return the same result repeatedly on a given date. I've not really had time to test this, so be sure to do some testing of your own...
DELIMITER //
DROP PROCEDURE IF EXISTS random_iotd//
CREATE PROCEDURE random_iotd()
BEGIN
# Get today's item.
SET #id := NULL;
SELECT id INTO #id FROM iotd WHERE ts = CURRENT_DATE();
IF ISNULL(#id) THEN
# Reset used flag if all the rows have been used.
SELECT COUNT(*) INTO #used FROM iotd WHERE used = 1;
SELECT COUNT(*) INTO #rows FROM iotd;
IF (#used = #rows) THEN
UPDATE iotd SET used = 0;
END IF;
# Select a random number between 1 and the number of unused rows.
SELECT FLOOR(RAND() * (#rows - #used)) INTO #rand;
# Select the id of the row at position #rand.
PREPARE stmt FROM 'SELECT id INTO #id FROM iotd WHERE used = 0 LIMIT ?,1';
EXECUTE stmt USING #rand;
# Update the row where id = #id.
PREPARE stmt FROM 'UPDATE iotd SET used = 1, ts = CURRENT_DATE() WHERE id = ?';
EXECUTE stmt USING #id;
END IF;
# Select the row where id = #id.
PREPARE stmt FROM 'SELECT id, item FROM iotd WHERE id = ?';
EXECUTE stmt USING #id;
DEALLOCATE PREPARE stmt;
END;
//
DELIMITER ;
And the table structure:
CREATE TABLE `iotd` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`item` varchar(255) NOT NULL,
`used` BOOLEAN NOT NULL DEFAULT 0,
`ts` DATE DEFAULT 0,
INDEX `used` (`used`),
INDEX `ts` (`ts`),
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
Why don't you use sequence?
Sequence serves your purpose easily...