I've table with following structure :
id | storeid | code
Where id is primary key
I want to insert data in this table with incremental order like this :
id | storeid | code
1 | 2 | 1
2 | 2 | 2
3 | 2 | 3
4 | 2 | 4
I've two solution to do this task.
1) Fire a query to get last record (code) from table and increment value of code with 1 using PHP and after that second query to insert that incremented value in database.
2) This single query : "INSERT INTO qrcodesforstore (storeid,code) VALUES (2,IFNULL((SELECT t.code+1 FROM (select code from qrcodesforstore order by id desc limit 1) t),1))"
I just want suggestion which approach is best and why for performance.
I'm currently using second method but confuse about performance as I'm using three level sub query.
You simply can use INSERT with SELECT and MAX():
INSERT INTO qrcodesforstore
(storeid, code)
(SELECT 2, IFNULL((MAX(code)+1),1) FROM qrcodesforstore)
SQLFiddle
Wrapping it up in a trigger:-
DELIMITER $$
DROP TRIGGER IF EXISTS bi_qrcodesforstore$$
CREATE TRIGGER bi_qrcodesforstore BEFORE INSERT ON qrcodesforstore
FOR EACH ROW
BEGIN
DECLARE max_code INT;
SELECT MAX(code) INTO max_code FROM qrcodesforstore;
IF max_code IS NULL THEN
SET max_code := 1;
ELSE
SET max_code := max_code + 1;
END IF
SET NEW.code := max_code;
END
you declare the field as primary key and unique and auto increment key they automatically increment the values
You can set the code column as AUTO_INCREMENT, you don't need to set it as primary key, see this.
Anyway, the second solution would be better, only one query is better than two.
Related
I've a table for storing products as per the following structure...
id shop_id product_id product_title
Every shop selects a plan, and accordingly it can stored N products in this tables. N is different for every shop.
Problem Statement: While doing insert operation in the table, total number of entries per shop_id can't be more than N.
I can count the #products before every insert operation, and then decide whether new entry should go in the table or be ignored. The operations are triggered by events received, and it may be in millions. So it doesn't seem to efficient. Performance is the key.
Is there a better way?
I Think you should use a stored procedure so you can delegate the validations to MySql and not to PHP, here is an example of what you might need, just be sure to replace the table names and columns names properly.
If you are worried about the performance you should check the indexes of the tables for better performance.
Procedure
DELIMITER //
DROP PROCEDURE IF EXISTS storeProduct//
CREATE PROCEDURE storeProduct(IN shopId INT, IN productId INT, IN productTitle VARCHAR(255))
LANGUAGE SQL MODIFIES SQL DATA SQL SECURITY INVOKER
BEGIN
/*Here we get the plan for the shop*/
SET #N = (SELECT plan FROM planTable WHERE shop_id = shopId);
/*NOW WE COUNT THE PRODUCTS THAT ARE STORED WITH THE shop_id*/
SET #COUNT = (SELECT COUNT(id) FROM storing_products WHERE shop_id = shopId);
/*NOW WE CHECK IF WE CAN STORE THE PRODUCTS OR NOT*/
IF #COUNT < #N THEN
/*YES WE CAN INSERT*/
INSERT INTO storing_products(shop_id, product_id, product_title)
VALUES (shopId, productId, productTitle);
/*1 means that the insert acording to the plan is ok*/
SELECT 1 AS 'RESULT';
ELSE
/*NO WE CAN NOT INSERT*/
/*0 means that the insert acording to the plan is not ok*/
SELECT 0 AS 'RESULT';
END IF;
END//
DELIMITER ;
Now you can call it from PHP just like
<?php
//....
$result = $link->query("CALL storeProduct($shop_id, $product_id, $product_title);");
//....
?>
or whatever you do
the answer is like
+--------+
| RESULT |
+--------+
| 1 |
+--------+
if its ok or
+--------+
| RESULT |
+--------+
| 0 |
+--------+
if not
I hope it will help
Greetings
If you don't want to count at insertion time, you can maintain count in another table than can be referred while insertion
shop_id max_product product_count insertion_allowed
------------------------------------------------------------
1 1000 50 1
2 2000 101 1
3 100 100 0
Two approaches:
compare product_count and max_product and insert when product_count is smaller than max_product. After successful insertion increment product_count of corresponding shop_id.
Alternatively, you may use insertion_allowed flag to check condition and after each successful insertion increment the product_count by 1 of corresponding shop_id.
Hope this will help you.
Please share performance improvement statistics of the approach(if you can). It may help others in choosing better approach.
Another approach without store procedure.
CREATE TABLE `test` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`prod_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8;
You can do an insert with select. It's a little bit more efficient of two separate queries. The trick is if the select returns empty rows, then no insert happens:
$prod_id = 12;
$N = 3;
$qry = "insert into test (prod_id)
(select $prod_id FROM test WHERE prod_id=$prod_id HAVING count(id) <= $N )";
I have a table in this format:
id | ....... | other_id
-------------------------
1 | ....... | 5
2 | ....... | 2
Basically, when the form is submitted, sometimes there will be a value for other_id in the form, and the insertion of the new row goes as normal. However, if there is no value given for other_id, I want its value to come from id. The issue is that id is the auto incrementing id, so the actual value of id is unknown until it's actually inserted into the table.
Is there a way to dynamically do this with SQL itself, without having to run additional queries afterward?
You can use a insertion trigger:
CREATE TRIGGER foo AFTER INSERT ON TABLENAME FOR EACH ROW
IF NEW.other_id IS NULL THEN
SET NEW.other_id := NEW.id;
END IF;;
#jh314 is almost right. Just change AFTER INSERT to BEFORE INSERT
I have a basic SQL problem that's been driving me mad. If I have a mySQL table e.g below.
How would I add another 80+ values to Column 2 starting from the first empty row (in this example row 3).
I've been trying a number of queries using INSERT or UPDATE but the closest I've got is to add the values to column 2 starting from the last defined ID value (e.g. row 80ish).
ID | Column 2 |
--------------------------------
1 | value |
2 | value |
3 | |
4 | |
5 | |
etc
The real table has around 10 columns, all with data in but I just need to add content (a list of around 80 different strings in CSV format to one of the columns)
I'd appreciate it if anyone could point me in the right direction.
I'd load the data into a separate table with the same structure and then update the target table using join or subquery to determine which columns are currently empty.
i.e. load interim table and then:
update target_table set column2 = (select column2 from interim_table where ...
where column2 is null
(slow but intuitive)
update target table, interim_table
set target table.column2 = interim_table.column2
where target table... = interim_table...
and target_table.column2 is null
(better performance)
Why don't you first run a query to find out the first empty row ID number? you can use SELECT COUNT(*)
FROM TABLE_NAME for that.
then you create a for loop and inside run a INSERT query, starting with the value returned by the previous query. just a scratch:
for(var id = last; id < totalOfQueries; id++)
{
var query = new MysqlCommand("INSERT INTO table VALUES ('" + id + "',....);
}
I have a script to find duplicate rows in my MySql table, the table contains 40,000,000 rows. but it is very slow going, is there an easier way to find the duplicate records without going in and out of php?
This is the script i currently use
$find = mysql_query("SELECT * FROM pst_nw ID < '1000'");
while ($row = mysql_fetch_assoc($find))
{
$find_1 = mysql_query("SELECT * FROM pst_nw add1 = '$row[add1]' AND add2 = '$row[add2]' AND add3 = '$row[add3]' AND add4 = '$row[add4]'");
if (mysql_num_rows($find_1) > 0) {
mysql_query("DELETE FROM pst_nw WHERE ID ='$row[ID]'}
}
You have a number of options.
Let the DB do the work
Create a copy of your table with a unique index - and then insert the data into it from your source table:
CREATE TABLE clean LIKE pst_nw;
ALTER IGNORE TABLE clean ADD UNIQUE INDEX (add1, add2, add3, add4);
INSERT IGNORE INTO clean SELECT * FROM pst_nw;
DROP TABLE pst_nw;
RENAME TABLE clean pst_nw;
The advantage of doing things this way is you can verify that your new table is correct before dropping your source table. The disadvantage is it takes up twice as much space and is (relatively) slow to execute.
Let the DB do the work #2
You can also achieve the result you want by doing:
set session old_alter_table=1;
ALTER IGNORE TABLE pst_nw ADD UNIQUE INDEX (add1, add2, add3, add4);
The first command is required as a workaround for the ignore flag being .. ignored
The advantage here is there's no messing about with a temporary table - the disadvantage is you don't get to check that your update does exactly what you expect before you run it.
Example:
CREATE TABLE `foo` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`one` int(10) DEFAULT NULL,
`two` int(10) DEFAULT NULL,
PRIMARY KEY (`id`)
)
insert into foo values (null, 1, 1);
insert into foo values (null, 1, 1);
insert into foo values (null, 1, 1);
select * from foo;
+----+------+------+
| id | one | two |
+----+------+------+
| 1 | 1 | 1 |
| 2 | 1 | 1 |
| 3 | 1 | 1 |
+----+------+------+
3 row in set (0.00 sec)
set session old_alter_table=1;
ALTER IGNORE TABLE foo ADD UNIQUE INDEX (one, two);
select * from foo;
+----+------+------+
| id | one | two |
+----+------+------+
| 1 | 1 | 1 |
+----+------+------+
1 row in set (0.00 sec)
Don't do this kind of thing outside the DB
Especially with 40 million rows doing something like this outside the db is likely to take a huge amount of time, and may not complete at all. Any solution that stays in the db will be faster, and more robust.
Usually in questions like this the problem is "I have duplicate rows, want to keep only one row, any one".
But judging from the code, what you want is: "if a set of add1, add2, add3, add4 is duplicated, DELETE ALL COPIES WITH ID < 1000". In this case, copying from the table to another with INSERT IGNORE won't do what you want - might even keep rows with lower IDs and discard subsequent ones.
I believe you need to run something like this to gather all the "bad IDs" (IDs with a duplicate, the duplicate above 1000; in this code I used "AND bad.ID < good.ID", so if you have ID 777 which duplicates to ID 888, ID 777 will still get deleted. If this is not what you want, you can modify that in "AND bad.ID < 1000 AND good.ID > 1000" or something like that).
CREATE TABLE bad_ids AS
SELECT bad.ID FROM pst_nw AS bad JOIN pst_nw AS good
ON ( bad.ID < 1000 AND bad.ID < good.ID
AND bad.add1 = good.add1
AND bad.add2 = good.add2
AND bad.add3 = good.add3
AND bad.add4 = good.add4 );
Then once you have all bad IDs into a table,
DELETE pst_nw.* FROM pst_nw JOIN bad_ids ON (pst_nw.ID = bad_ids.ID);
Performances will greatly benefit from a (non_unique, possibly only temporary) index on add1, add2, add3, add4 and ID in this order.
Get the duplicate rows using "Group by" operator. Here is a sample that you can try :
select id
from table
group by matching_field1,matching_field2....
having count(id) > 1
So, you are getting all the duplicate ids. Now delete them using a delete query.
Instead of using "IN", use "OR" operator as "IN" is slow compared to "OR".
Sure there is. Note however that with 40 million records You most probably will exceed max php execution time. Try following
Create table temp_pst_nw like pst_nw;
Insert into temp_pst_nw select * from pst_nw group by add1,add2,add3,add4;
Confirm that everything is ok first!!
Drop table pat_nw;
Rename table temp_pst_nw to pst_nw;
Try creating a new table that has the same definitions. i.e. "my_table_two", then do:
SELECT DISTINCT unique_col1, col2, col3 [...] FROM my_table INTO
my_table_two;
Maybe that'll sort it out.
Your code will be better if you don't use select *, only select columns (4 address) you want to compare. It should have limit clause in my sql. It can avoid state not response when you have too large nums rows like that.
I have a typical LAMP based site + Zend Framework where I have a base table and a summary table. Summary table is used to display data in reports.
Base table -
ID | Status
1 | 1
2 | 1
3 | 2
4 | 2
5 | 1
6 | 1
Summary table -
Status | Count
1 | 4
2 | 2
The base table will be changed(insert,update,delete) at an average of 20 times per day.
Currently, I am using triggers to call a stored procedure which will update the summary table based on the base table.
This is the stored procedure.
CREATE PROCEDURE UpdateSummary()
BEGIN
UPDATE summary a
INNER JOIN
(SELECT status, count(*) c from base group by status) b
ON a.status = b.status
SET a.count = b.c;
END
And I have 3 triggers (one for each - Insert, Delete and Update). I have shown the insert sample alone below. Other are similar to this.
CREATE TRIGGER S_T_TRIGGER_I
AFTER INSERT ON base
FOR EACH ROW
CALL UpdateSummary();
I want the summary table to be updated to the latest values always.
Using triggers and stored procedure like this is the best way or is there a elegant way to do this?
Well you are re-querying the DB over and over for data that you already know.
Why not just update the summary with only the changes.
DELIMITER $$
CREATE TRIGGER ai_base_each AFTER INSERT ON base FOR EACH ROW
BEGIN
INSERT INTO summary (status, count) VALUES (NEW.status,1)
ON DUPLICATE KEY UPDATE
SET count = count + 1;
END $$
CREATE TRIGGER ad_base_each AFTER DELETE ON base FOR EACH ROW
BEGIN
UPDATE summary s
SET s.count = s.count - 1
WHERE s.status = OLD.status;
END $$
CREATE TRIGGER au_base_each AFTER UPDATE ON base FOR EACH ROW
BEGIN
UPDATE summary s
SET s.count = s.count - 1
WHERE s.status = OLD.status;
INSERT INTO summary (status, count) VALUES (NEW.status,1)
ON DUPLICATE KEY UPDATE
SET count = count + 1;
END $$
DELIMITER ;
This will be much much faster and more to the point much more elegant.
Why don't you use a view like :
CREATE VIEW Summary AS
SELECT status, count(*)
FROM Base
GROUP BY status;
Each time you need, just do :
SELECT *
FROM Summary
And you'll get your result in real time (each call re-computed).
Views can be used the same way like table is used in Zend Framework. Just that you need to specify a primary key explicitly as explained here