Mysql insert with value from target table - mysql

So I need to select a value from my table and modify it slightly to use as a value in a new row in the same table when inserting. See below:
INSERT INTO reservationbody(
reservationid,
driverid,
tripnumber,
fromlocation,
tolocation
)
VALUES(
1,
2,
(
SELECT
MAX(tripnumber) + 1
FROM reservationbody
WHERE reservationid = 1
),
'here',
'there'
)
I'm getting the following error:
You can't specify target table 'reservationbody' for update in FROM
clause
I've had a look at other questions and have found plenty of questions on this error but they all seem to be about updates and deletes and all require PK values that already exist. None deal with inserting.
Basically what I'm expecting here is that if SELECT MAX(tripnumber) FROM reservationbody where reservationid = 1 returns a value of 4 then the insert should add a new row with a tripnumber value of 5
Can anyone suggest how I can accomplish this?

Have you tried (I know you can do this in MS SQL):
INSERT INTO reservationbody(reservationid,driverid,tripnumber,
fromlocation,tolocation)
SELECT 1, 2, MAX(tripnumber) + 1, 'here', 'there'
FROM reservationbody
WHERE reservationid = 1

Related

MySQL Stop Insert based on field value

I want to do an insert ignore if a column has a specific value.
Say I have a simple table that records changes to a PO over time. Each time the PO is updated, I need to insert a record on this table:
PONum | POLine | submittedBy | ... other columns
100 1 initial-value
100 1 TB
100 2 initial-value
On a PHP page I check for the existence of the PO number in this table. If there isn't one, I insert all the PO lines I find from another table. I set the submittedBy name to "initial-value".
Now later on, say PO Line 3 is added. I want to run through my check again and only insert the new record for line 3 if there isn't already one that has line 3 with a submittedBy of initial-value
INSERT IGNORE INTO PODetailCL (PONum, POLine, submittedBy, ... )
VALUES (100, 1, 'TB', ... ),
(100, 2, 'TB', ...),
(100, 3, 'initial-value', ...)
WHERE submittedBy <> 'initial-value' -- <- something like this but not sure of the syntax
Try using conditional insert multiple rows:
INSERT INTO PODetailCL(PONum, POLine, submittedBy, ... )
SELECT (100, 1, 'TB', ... ), (100, 2, 'TB', ...),(100, 3, 'initial-value', ...)
FROM PODetailCL
WHERE EXISTS (SELECT * FROM PODetailCL WHERE submittedBy <> 'initial-value')
I was able to use a virtual column and then add a unique constraint on that column. After that I can use INSERT IGNORE. (Virtual columns are only available in MySQL version 5.7 or later.)
ALTER TABLE porelcl
ADD COLUMN initialCheck BIT GENERATED ALWAYS
AS (CASE WHEN submittedBy = 'initial-value' THEN b'1' ELSE NULL END)
then after that:
ALTER TABLE porelcl ADD UNIQUE initial_check_index (PONum, POLine, PORelNum, initialCheck)
Now when my code runs through the check again, if tries to insert a duplicate record: SomePO, SomePOLine, SomePORelNum & 'initial-value' the insert won't take place, which is what I need.

Adapt result of one row to other rows

How can I can write the query this way, that the result of the CASE WHEN - statement is adapted to every row. So that in every row the result will be 5. Thank you very much!
CREATE TABLE DATA
(`Person` CHAR(4),
`Apples` INT(1),
`Tomatoes` INT(1),
`Result` INT(1)
);
INSERT INTO DATA
(Person, Apples, Tomatoes)
VALUES ('Mark' , 1, 2),
('Sepp', 2, 3),
('Carl', 3, 1);
UPDATE DATA
SET `Result` = CASE WHEN (`Person` = 'Sepp') THEN (`Apples` + `Tomatoes`) END;
Table of result as it should be
SQL fiddle demonstration
If you want all rows to get the value from Sepps row you can do it using a subquery.
The "normal" way would be to do this:
UPDATE DATA
SET Result = (SELECT Apples + Tomatoes FROM DATA WHERE Person = 'Sepp')
But this will most likely give you an error with MySQL (can't specify target table for update) and a workaround is to introduce another level in the query which forces a temporary table to be used, like this:
UPDATE DATA
SET Result = (
SELECT Value FROM (
SELECT Apples + Tomatoes AS Value
FROM DATA WHERE Person = 'Sepp'
) t
);

MySql Query - INSERT INTO command but only without duplicates

I have this workable query which is inserting proper data into 'selections' table according to some conditions:
INSERT INTO selections (auctionid, selections.order)
VALUES
((SELECT id FROM auctions, game WHERE auctions.BetfairMark = game.BetfairMarketID), 1,
((SELECT id FROM auctions, game WHERE auctions.BetfairMark = game.BetfairMarketID), 2,
((SELECT id FROM auctions, game WHERE auctions.BetfairMark = game.BetfairMarketID), 3
but my problem is how to improve this query to prevent getting same duplicates in table when running this query?
Selections table have 3 columns: id, auctionid, order where id is autoincrement number generated for each new record.
So auctionid and order shouldn't be the same values in record.
Add a UNIQUE INDEX to the (auctionid, order) pair.
ALTER TABLE selections ADD UNIQUE index_name (`auctionid`, `order`)
And when you insert you can use INSERT IGNORE INTO ... so that it ignores duplicates instead of throwing an error. (Useful when you batch insert and the duplicates are expected)
SOLVED!
Created 3 separated queries for each row, working for now!
So with added UNIQUE INDEX to the (auctionid, order) pair have this workable code:
INSERT IGNORE INTO
selections
(
selections.auctionid,
selections.order,
selections.title,
startamount
)
SELECT
auctions.id,
1,
PlayerA,
0.01
FROM
auctions, game
WHERE
auctions.BetfairMark = game.BetfairMarketID
;
INSERT IGNORE INTO
elections
(
selections.auctionid,
selections.order,
selections.title,
startamount
)
SELECT
auctions.id,
2,
PlayerB,
0.01
FROM
auctions, game
WHERE
auctions.BetfairMark = game.BetfairMarketID
;
INSERT IGNORE INTO
selections
(
selections.auctionid,
selections.order,
selections.title,
startamount
)
SELECT
auctions.id,
3,
'third text',
0.01
FROM
auctions, game
WHERE
auctions.BetfairMark = game.BetfairMarketID
;

MySQL INSERT IF (custom if statements)

First, here's the concise summary of the question:
Is it possible to run an INSERT statement conditionally?
Something akin to this:
IF(expression) INSERT...
Now, I know I can do this with a stored procedure.
My question is: can I do this in my query?
Now, why would I want to do that?
Let's assume we have the following 2 tables:
products: id, qty_on_hand
orders: id, product_id, qty
Now, let's say an order for 20 Voodoo Dolls (product id 2) comes in.
We first check if there's enough Quantity On Hand:
SELECT IF(
( SELECT SUM(qty) FROM orders WHERE product_id = 2 ) + 20
<=
( SELECT qty_on_hand FROM products WHERE id = 2)
, 'true', 'false');
Then, if it evaluates to true, we run an INSERT query.
So far so good.
However, there's a problem with concurrency.
If 2 orders come in at the exact same time, they might both read the quantity-on-hand before any one of them has entered the order.
They'll then both place the order, thus exceeding the qty_on_hand.
So, back to the root of the question:
Is it possible to run an INSERT statement conditionally, so that we can combine both these queries into one?
I searched around a lot, and the only type of conditional INSERT statement that I could find was ON DUPLICATE KEY, which obviously does not apply here.
INSERT INTO TABLE
SELECT value_for_column1, value_for_column2, ...
FROM wherever
WHERE your_special_condition
If no rows are returned from the select (because your special condition is false) no insert happens.
Using your schema from question (assuming your id column is auto_increment):
insert into orders (product_id, qty)
select 2, 20
where (SELECT qty_on_hand FROM products WHERE id = 2) > 20;
This will insert no rows if there's not enough stock on hand, otherwise it will create the order row.
Nice idea btw!
Try:
INSERT INTO orders(product_id, qty)
SELECT 2, 20 FROM products WHERE id = 2 AND qty_on_hand >= 20
If a product with id equal to 2 exists and the qty_on_hand is greater or equal to 20 for this product, then an insert will occur with the values product_id = 2, and qty = 20. Otherwise, no insert will occur.
Note: If your product ids are note unique, you might want to add a LIMIT clause at the end of the SELECT statement.
Not sure about concurrency, you'll need to read up on locking in mysql, but this will let you be sure that you only take 20 items if 20 items are available:
update products
set qty_on_hand = qty_on_hand - 20
where qty_on_hand >= 20
and id=2
You can then check how many rows were affected. If none were affected, you did not have enough stock. If 1 row was affected, you have effectively consumed the stock.
You're probably solving the problem the wrong way.
If you're afraid two read-operations will occur at the same time and thus one will work with stale data, the solution is to use locks or transactions.
Have the query do this:
lock table for read
read table
update table
release lock
I wanted to insert into a table using values so I found this solution to insert the values using the IF condition
DELIMITER $$
CREATE PROCEDURE insertIssue()
BEGIN
IF (1 NOT IN (select I.issue_number from issue as I where I.series_id = 1)) THEN
INSERT IGNORE INTO issue ( issue_number, month_published, year_published, series_id, mcs_issue_id) VALUES (1, 1, 1990, 1, 1);
END IF;
END$$
DELIMITER ;
If you later on want to call the procedure it's as simple as
CALL insertIssue()
You can find more information about PROCEDURES and if conditions in this site

A multitude of the same id in an WHERE id IN () statement

I have a simple query that increases the value of a field by 1.
Now I used to loop over all id's and fire a query for each of them, but now that things are getting a bit resource heavy I wanted to optimize this. Normally I would just do
UPDATE table SET field = field + 1 WHERE id IN (all the ids here)
but now I have the problem that there are id's that occur twice (or more, I can't know that on forehand).
Is there a way to have the query run twice for id 4 if the query looks like this:
UPDATE table SET field = field + 1 WHERE id IN (1, 2, 3, 4, 4, 5)
Thanks,
lordstyx
Edit: sorry for not being clear enough.
The id here is an auto inc field, so it are all unique ID's. the id's that have to be updated are indirectly comming from users, so I can't predict which id is going to occur how often.
If there are the ID's (1, 2, 3, 4, 4, 5) I need the field of row with id 4 to be incremented with 2, and all the rest with 1.
If (1, 2, 3, 4, 4, 5) comes from a SELECT id ... query, then you can do something like this:
UPDATE yourTable
JOIN
( SELECT id
, COUNT(id) AS counter
....
GROUP BY id
) AS data
ON yourTable.id = data.id
SET yourTable.field = yourTable.field + data.counter
;
Since the input comes from users, perhaps you can manipulate it a bit. Change (1, 2, 3, 4, 4, 5) to (1), (2), (3), (4), (4), (5).
Then (having created a temporary table):
CREATE TABLE tempUpdate
( id INT )
;
Do the following procedure:
add the values in the temporary table,
run the update and
delete the values.
Code:
INSERT INTO TempUpdate
VALUES (1), (2), (3), (4), (4), (5)
;
UPDATE yourTable
JOIN
( SELECT id
, COUNT(id) AS counter
FROM TempUpdate
GROUP BY id
) AS data
ON yourTable.id = data.id
SET yourTable.field = yourTable.field + data.counter
;
DELETE FROM TempUpdate
;
No. But you could perform something like
UPDATE table
SET field = field + (LENGTH(',1,2,3,4,4,5,') - LENGTH(REPLACE(',1,2,3,4,4,5,', CONCAT(',', id, ','), ''))) / LENGTH(CONCAT(',', id, ','))
WHERE id IN (1, 2, 3, 4, 4, 5)
if you need row with id = 4 specifically to be incremented twice
Here is solution you wanted, but I'm not sure this is what you need.
Let's say that your talbe is called test. You want to increase id. I've added a field idwas to easily show what was the id before the query:
CREATE TABLE `test` (
`id` int(10) unsigned NOT NULL auto_increment,
`idwas` int(8) unsigned default NULL,
PRIMARY KEY (`id`)
) ;
Let's fill it with data:
truncate table test;
insert into test(id) VALUES(1),(3),(15);
update test set idwas = id;
Now let's say that you have user input 1,3,5,3, so:
id 1 should be increased by 1
id 3 should be increased by 2
id 5 is missing, nothing to increase.
row with id 15 should not be changed because not in user input
We'll put the user input in a variable to be easier to use it:
SET #userInput = '1,3,5,3';
then do the magic:
SET #helperTable = CONCAT(
'SELECT us.id, count(us.id) as i FROM ',
'(SELECT ',REPLACE(#userInput, ',',' AS `id` UNION ALL SELECT '),
') AS us GROUP BY us.id');
SET #stmtText = CONCAT(
' UPDATE ',
'(',#helperTable,') AS h INNER JOIN test as t ON t.id = h.id',
' SET t.id = t.id + h.i');
PREPARE stmt FROM #stmtText;
EXECUTE stmt;
And this is the result:
mysql> SELECT * FROM test;
+----+-------+
| id | idwas |
+----+-------+
| 2 | 1 |
| 5 | 3 |
| 15 | 15 |
+----+-------+
3 rows in set (0.00 sec)
If it's reasonable, you could try doing a combination of what you had before and what you have now.
In whatever is creating this list, separate it into (depending on the language's constructs) some type of array. Follow this by sorting it,finding how many multiples of each there are, and doing whatever else you need to to get the following: an array with (increment-number => list of ids), so you do one query for each increment amount. Thus, your example becomes
UPDATE table SET field = field + 1 WHERE id IN (1, 2, 3, 5)
UPDATE table SET field = field + 2 WHERE id IN (4)
In php, for example, I would take the array, sort the array, use the content of the array as the keys for another array of the form (id => count), and then fold that over into the (count => list of ids) array.
It's not that efficient, but is definitely better than one query per id. It's also probably better than using iteration and string manipulation in SQL. Unless you're forced to use SQL to do everything (which it sounds like you're not), I wouldn't use it to do everything, when it's overly awkward to do so.
You could use the following:
create temporary table temp1 (id integer);
insert into temp1 (id) values (1),(2),(3),(4),(4),(5);
update your_table set your_field = your_field + (select count(*) from temp1 where id = your_table.id)
This solution requires you to format the id list like (1),(2),(3),(4),(4),(5) but I don't think that is a problem, right?
This worked on my test database, hope it works for you too!
Regards,
Arthur