MySQL conditional insert with insert .. select - mysql

I have an ecommerce site wherby customers login and see prices for various products. Each customer has their own pricelist and the the prices can be set a number of ways (sources). The table is below:
CREATE TABLE customer_price_list (
customer_price_id int(11) NOT NULL AUTO_INCREMENT,
customer_account_id int(11) NOT NULL,
product_id int(11) DEFAULT NULL,
currency_id int(11) DEFAULT NULL,
customer_price decimal(7,2) DEFAULT NULL,
source enum('TRADE_DEFAULT','TEMPLATE','SAGE','MANUAL') DEFAULT NULL,
PRIMARY KEY (customer_price_id),
KEY customer_account_id (customer_account_id),
KEY product_id (product_id),
KEY currency_id (currency_id),
KEY source (source)
)
Taking just the product_id, customer_price and source, sample data for one customer may look like (using a string for product_id just for illustration) :
Prod_1, 10.00, TRADE_DEFAULT
Prod_2, 20.00, TRADE_DEFAULT
Prod_3, 25.00, MANUAL
And a different customer:
Prod_1, 7.50, SAGE
Prod_2, 20.00, TRADE_DEFAULT
Prod_3, 30.00, TRADE_DEFAULT
The basic price for something, without a discount, is when the source is TRADE_DEFAULT - the above shows these two customers had the TRADE_DEFAULT price for two items each but got a discount on one item.
The TRADE_DEFAULT prices are set by importing a CSV file which has product_id, currency_id and customer_price in it. Within PHP I loop through all the rows in the CSV and bind the values to this query:
insert into customer_price_list
(customer_price_id, customer_account_id, product_id, currency_id, customer_price, source)
select 0, customer_account_id, :product_id, :currency_id, :price, 'TRADE_DEFAULT' from customer_account
This works fine when customer_price_list is empty (for all product_id/currency_id combinations within the CSV). But, if some customers already have product_id/currency_id entries this will result in extra rows (i.e. there will be two or more prices for a product for that customer)
So, when processing the CSV I want to:
A) update any existing TRADE_DEFAULT to the new value from the CSV (again run from a loop of the CSV contents)
update customer_price_list set customer_price = :price
where currency_id=:currency_id and product_id=:product_id and source ='TRADE_DEFAULT'
B) insert a TRADE_DEFAULT price if that customer has no price for that product/currency
insert (ONLY IF NO PRICE OF ANY SOURCE IS THERE) into customer_price_list
(customer_price_id, customer_account_id, product_id, currency_id, customer_price, source)
select 0, customer_account_id, :product_id, :currency_id, :price, 'TRADE_DEFAULT' from customer_account
This is where I need help. I have searched for conditional insert queries but I can only find where they are inserting one recoord like this:
insert into table1 (user,rating,last_modified)
select 'user1', 1999, NOW() from table1
where not exists (
select * from table1
where last_modified > '2007-04-13 08:52:41'
and user='user1'
) limit 1
But I am wanting to do insert .. select and do an inseret, if needed, for all customers.
Thanks.

Try using merge:
https://learn.microsoft.com/en-us/sql/t-sql/statements/merge-transact-sql
It may be helpful with your case as it allows to insert values based on condition and update if they already exist.

Using #Stavr00 suggestion I came up with this:
First add an index across three columns to make product/currency unique for each customer:
alter table customer_price_list add UNIQUE INDEX unq_cust_prod (customer_account_id ASC, product_id ASC, currency_id ASC)
Then change the query:
insert into customer_price_list
(customer_price_id, customer_account_id, product_id, currency_id, customer_price, source)
select 0, customer_account_id, :product_id, :currency_id, :price, 'TRADE_DEFAULT' from customer_account
ON DUPLICATE KEY UPDATE customer_price=customer_price;
By making the update set the price to the current value, the query becomes just the conditional insert. When combined with the update query in step A in my question, it does what I want.
I guess I could have used the ON DUPLICATE KEY UPDATE to do the work of the other query and do it all on one. But, I would only want to update existing records of source = TRADE_DEFAULT so not sure how to do that.
Also, it seems I could have used insert ignore, but as this would not flag some real errors like type mismatch, I thought my solution was safer.
Comments welcome!

Related

insert a new record into a mysql table with one of the values incremented by 1

I've got the following table:
productId price
1 price_value1
2 price_value2
3 price_value3
I would like to insert a new product into the table and assign it a new productId. In this case its value equals to 4.
So I want my new table to look like so:
productId price
1 price_value1
2 price_value2
3 price_value3
4 price_value4
So as far as I understand, in order to do that I have to somehow retrieve the max value of productId and insert it using INSERT INTO mytable VALUES (productId + 1, price_value4).
But how do I find out the maximum value of productId?
I tried INSERT INTO mytable VALUES (SELECT MAX(productId) + 1 FROM mytable, price_value4) but it didn't work.
This should Work:
Select the max(productID) and price_value4 as a columns from mytable and insert the result.
INSERT INTO mytable (SELECT MAX(productId) + 1, 'price_value4' FROM mytable);
However, if you are not going to jump some number you can just add an auto increment id key to product_id and then you will have only to insert the price, the product ID will be incremented automatically..
This will do so :
ALTER TABLE mytable
MODIFY COLUMN `productId` INT(10) UNSIGNED PRIMARY KEY AUTO_INCREMENT;
you can change INT(10) with the INT(5) for example depanding on the size you want to give to your productId column
EDIT :
In return to the OP question in comments why his solution wouldn't work
Some suggetions says you have to make the SELECT statment in insert always between parenthesis
INSERT INTO mytable VALUES ( (SELECT MAX(ID)+1 FROM mytable) , price_value4)
.. In my Case it Return
(1093): You can't specify target table
'mytable' for update in FROM clause
AND HERE IS WHY (Quoting From the documentation)
When selecting from and inserting into the same table, MySQL creates
an internal temporary table to hold the rows from the SELECT and then
inserts those rows into the target table. However, you cannot use
INSERT INTO t ... SELECT ... FROM t when t is a TEMPORARY table,
because TEMPORARY tables cannot be referred to twice in the same
statement
BUT there is away to overcome by using a query instead of the table itself in the FROM, which has the effect of copying the requested table values instead of referencing the one that you are updating..
INSERT INTO mytable VALUES (
(SELECT MAX(ID)+1 FROM (SELECT * FROM mytable ) as mytmp ),
'price_value4');
OR (Quoting From the documentation)
To avoid ambiguous column reference problems when the SELECT and the
INSERT refer to the same table, provide a unique alias for each table
used in the SELECT part, and qualify column names in that part with
the appropriate alias.
INSERT INTO mytable Values ( (SELECT MAX(ID)+1 FROM mytable as mytmp) , 'price_value4')
This is a duplicate question. In order to take advantage of the auto-incrementing capability of the column, do not supply a value for that column when inserting rows.
A simple syntax to create table
CREATE TABLE Product (
productId MEDIUMINT NOT NULL AUTO_INCREMENT,
price INT NOT NULL,
PRIMARY KEY (productid)
);
While inserting supplied default or leave column as blank or supplied value as NULL. Take a look at below code snippet.
INSERT INTO Product (price) VALUES
('10'),('20'),('4'),
('30');
refer this link

MYSQL insert only if values different

I'm trying to insert a new row with the same ID into a simple table but only want to insert if the values are different.
This table is to track price history of an item. The table has the following columns:
id, timestamp, productID, price
I only want to insert a new record if either the product doesn't exist or the product does exist but the price has changed.
Unfortunately I'm having a brain block due to my limited knowledge and would appreciate help in where to turn so I don't have any trials at the code to do this.
Thanks!
you can try something like this:
SET #PRODUCT = 1; # product id
SET #PRICE = 1; # new product price
insert into `t`(`product`, `timestamp`, `price`)
select v.product, now(), v.price
from
(select #PRODUCT as `product`, #PRICE as `price`) as v
left outer join
(select `product`, `price` from `t` where `product`=#PRODUCT order by `id` desc limit 1) as p
on (v.product=p.product)
where
(p.price is null) or
(p.price <> v.price);
so, this statement either insert new row (for new product or new price) or does nothing
u need composite primary key
ALTER TABLE products ADD PRIMARY KEY(product,price);
after this query if you insert if the product and price is same in your table returns error with duplicate entry
or it will insert the query even one field value changes

SQL Insert into table only if record doesn't exist [duplicate]

This question already has answers here:
Check if a row exists, otherwise insert
(12 answers)
MySQL Conditional Insert
(13 answers)
Closed 9 years ago.
I want to run a set of queries to insert some data into an SQL table but only if the record satisfying certain criteria are met. The table has 4 fields: id (primary), fund_id, date and price
I have 3 fields in the query: fund_id, date and price.
So my query would go something like this:
INSERT INTO funds (fund_id, date, price)
VALUES (23, '2013-02-12', 22.43)
WHERE NOT EXISTS (
SELECT *
FROM funds
WHERE fund_id = 23
AND date = '2013-02-12'
);
So I only want to insert the data if a record matching the fund_id and date does not already exist. If the above is correct it strikes me as quite an inefficient way of achieving this as an additional select statement must be run each time.
Is there a better way of achieving the above?
Edit: For clarification neither fund_id nor date are unique fields; records sharing the same fund_id or date will exist but no record should have both the same fund_id and date as another.
This might be a simple solution to achieve this:
INSERT INTO funds (ID, date, price)
SELECT 23, DATE('2013-02-12'), 22.5
FROM dual
WHERE NOT EXISTS (SELECT 1
FROM funds
WHERE ID = 23
AND date = DATE('2013-02-12'));
p.s. alternatively (if ID a primary key):
INSERT INTO funds (ID, date, price)
VALUES (23, DATE('2013-02-12'), 22.5)
ON DUPLICATE KEY UPDATE ID = 23; -- or whatever you need
see this Fiddle.
Although the answer I originally marked as chosen is correct and achieves what I asked there is a better way of doing this (which others acknowledged but didn't go into). A composite unique index should be created on the table consisting of fund_id and date.
ALTER TABLE funds ADD UNIQUE KEY `fund_date` (`fund_id`, `date`);
Then when inserting a record add the condition when a conflict is encountered:
INSERT INTO funds (`fund_id`, `date`, `price`)
VALUES (23, DATE('2013-02-12'), 22.5)
ON DUPLICATE KEY UPDATE `price` = `price`; --this keeps the price what it was (no change to the table) or:
INSERT INTO funds (`fund_id`, `date`, `price`)
VALUES (23, DATE('2013-02-12'), 22.5)
ON DUPLICATE KEY UPDATE `price` = 22.5; --this updates the price to the new value
This will provide much better performance to a sub-query and the structure of the table is superior. It comes with the caveat that you can't have NULL values in your unique key columns as they are still treated as values by MySQL.
Assuming you cannot modify DDL (to create a unique constraint) or are limited to only being able to write DML then check for a null on filtered result of your values against the whole table
FIDDLE
insert into funds (ID, date, price)
select
T.*
from
(select 23 ID, '2013-02-12' date, 22.43 price) T
left join
funds on funds.ID = T.ID and funds.date = T.date
where
funds.ID is null

Using If or Case (or any other method) to increment a value based on country code

I have this table built like this, which assigns a unique articleId starting with zero for each user.
CREATE TABLE `articles` (
`artcId` INT(10) NOT NULL AUTO_INCREMENT,
`artcUserId` INT(10) NOT NULL DEFAULT '0',
`artcStackId` INT(10) NOT NULL DEFAULT '0',
`artcTitle` VARCHAR(200) NULL DEFAULT NULL,
PRIMARY KEY (`artcUserId`, `artcId`)
)
COLLATE='utf8_general_ci'
ENGINE=MyISAM;
There also is a part which assigns a running serial number (artcStackId) to each article. You'll see in the code.
Thing is, is it somehow possible to assign a artcStackId based on the country of the user? That country code will come from php.
Eg: If it's US then start from 10001+1, if its UK start from 20001+1, if its CA then start from 30001+1 and on & on.
Will it be possible to do this?
My current SQL query goes as follows:
insert into articles (artcUserId, artcStackId, artcCountry, artcTitle)
select 4,IFNULL((MAX(artcStackId)+1) ,0),'US','Hello World'
FROM articles;
But the way I want it it'll have to go like (this is just an example sql):
insert into articles (artcUserId, artcStackId, artcCountry, artcTitle)
select 4, IF artcCountry = 'US' then (selct MAX(artcStackId)+1
where artcCountry = 'US'),
'US','Hello World'
FROM articles;
I hope you get the idea.
I would suggest you to use a TRIGGER:
CREATE TRIGGER ins_articles
BEFORE INSERT ON articles
FOR EACH ROW
SET new.artcStackId=coalesce(
(select max(artcStackId)
from articles
where artcCountry = new.artcCountry)+1,1)
;
or something like this:
CREATE TRIGGER ins_articles
BEFORE INSERT ON articles
FOR EACH ROW
SET new.artcStackId=coalesce(
(select max(artcStackId)
from articles
where artcCountry = new.artcCountry)+1,
case when new.artcCountry = 'US' then 10001
when new.artcCountry = 'UK' then 20001
when new.artcCountry = 'CA' then 30001
end)
;
see it working here. Then you can insert without specifying the column artcStackId, since it will automatically be calculated.
Technically this will work, but you might run into problems with duplicate values for artcStackId during heavy load. It's also not likely very efficient.
INSERT INTO articles (artcUserId,artcStackId,artcCountry,artcTitle)
SELECT 4,MAX(artcStackId)+1,'US','Hello World'
FROM articles
WHERE artcCountry = 'US' LIMIT 1
Also, this assumes that the articles table already has at least one row for that country.
A better option might be to have a country table with a nextStackId value on it. In a transaction, update the next stack id in that table, select the updated value, use that for your insert, then commit the transaction. That should be much safer.
What happen if you create a new table named Cities, with the idCity and numberBeg as fields, and with a relation you will know from which number your begin to count

using mysql to auto populate a sort order

I have a table called t_home_feature with the following columns: id, type, sort_order. I then executed the following MySQL statement:
INSERT INTO t_home_feature (SELECT news_id, 'news', ( SELECT max(sort_order) + 1 FROM t_home_feature ) FROM t_news )
I then did a SELECT * FROM t_home_feature but the sort_order for all rows has a value that is equal to the number of rows in t_home_feature prior to the insert statement, instead of a value like previous row + 1.
How can I modify my insert query to achieve a previous row + 1 output?
You could turn the sort_order into an auto_increment field, which means the database will automatically increment it and you need not refer to it in your insert. This has to be a key, but not a primary key. For example, here's an example from:
http://forums.mysql.com/read.php?22,264498,264967#msg-264967
There the link has an example of a workaround:
create table ai (
id int auto_increment not null,
xx varchar(9),
key(id),
primary key (xx));
You may have to do something fancy with local variables. Perhaps something like this:
SELECT COUNT(1) INTO #maxval FROM t_home_feature;
INSERT INTO t_home_feature
SELECT news_id, 'news', #maxval:=#maxval+1 FROM t_news ;
No need to any auto increment values from tables.
I have done things like this when answering questions before. Here are four(4) examples of questions I answered in the DBA StackExchange using local variables:
https://dba.stackexchange.com/questions/29007/update-ranking-on-table/29009#29009
https://dba.stackexchange.com/questions/29016/selecting-without-repititions/29018#29018
https://dba.stackexchange.com/questions/18987/update-rank-on-a-large-table/18990#18990
https://dba.stackexchange.com/questions/10251/whats-wrong-with-this-update-rank-query10320#10320