I have two MySQL database tables that are meant to hold data for eshop orders. They're built as such (extremely simplified version):
CREATE TABLE `orders` (
`id` int(11) NOT NULL auto_increment
PRIMARY KEY (`id`)
);
CREATE TABLE `order_items` (
`id` int(11) NOT NULL auto_increment,
`orderID` int(11) NOT NULL,
PRIMARY KEY (`id`)
)
The relationship between the two is that orders.id corresponds to order_items.orderID.
I'm using a transaction to place a new order, however have a problem preserving the above relationship. In order to get the new order id. I have to commit the orders INSERT query, get the autoincremented id and then start another transaction for the order items. Which pretty much defeats the point of using transactions.
I could insert the new order in the orders table and then try something like
INSERT INTO order_items(orderID) VALUES(LAST_INSERT_ID())
which I assume would work. However after the first order item is inserted LAST_INSERT_ID() would stop returning the order id and instead return the order item id making it impossible to use this query to insert another order item.
Is there a way to make this whole thing work within a single transaction or should I give up and use a procedure instead?
WOuld this work?:
INSER QUERY;
SET #insertid = LAST_INSERT_ID();
INSERT INTO `order_items` SET `OrderID` = #insertid;
All in one statement. You will have to double check the syntax
You can't count on LAST_INSERT_ID() because it also changes when you insert values to order_items because it inserts its id which is also auto_imcrement.
Maybe you can try this.
INSERT INTO order_items(orderID) VALUES((SELECT id FROM orders ORDER BY id desc LIMIT 1))
Related
I have a table where I log members.
There are 1,486,044 records here.
SELECT * FROM `user_log` WHERE user = '1554143' order by id desc
However, this query takes 5 seconds. What do you recommend ?
Table construction below;
CREATE TABLE IF NOT EXISTS `user_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user` int(11) NOT NULL,
`operation_detail` varchar(100) NOT NULL,
`ip_adress` varchar(50) NOT NULL,
`l_date` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
COMMIT;
For this query:
SELECT * FROM `user_log` WHERE user = 1554143 order by id desc
You want an index on (user, id desc).
Note that I removed the single quotes around the filtering value for user, since this column is a number. This does not necessarily speeds things up, but is cleaner.
Also: select * is not a good practice, and not good for performance. You should enumerate the columns you want in the resultset (if you don't need them all, do not select them all). If you want all columns, since your table has not a lot of columns, you might want to try a covering index on all 5 columns, like: (user, id desc, operation_detail, ip_adress, l_date).
In addition to the option of creating an index on (user, id), which has already been mentioned, a likely better option is to convert the table to InnoDB as create an index only on (user).
I have already asked this but did not get what I am looking for, I have many tables with column id set to auto-increment and primary key.
Table1
id, title, condition...
Table2
id, title, condition...
And I have a search box where I search for products and my query is like
$stmt = $mysqli->prepare("select * from table1 where title Like ? or id = ? ");
$stmt->bind_param('ss', $searchterm, $searchterm");
$stmt->execute();
$stmt = $mysqli->prepare("select * from table2 where title Like ? or id = ? ");
$stmt->bind_param('ss', $searchterm, $searchterm");
$stmt->execute();
So if I search for "1" then I will get all the products with id or title 1 but what I want is to have a unique identfier for each row. How can I accomplish this? As I am working with products, there should be a unique id for each product.
Or could it be possible to have a alphabet in front of the id, something like T1, T2, T3?
If you were using the Oracle table server, you'd use a database entity called a SEQUENCE to get a data-base-wide unique id value for your tables.
It's easy enough to emulate SEQUENCE entities in MySQL, though, even if it's a little clunky and not quite as space-efficient.
First, create yourself a table like this.
CREATE TABLE sequence (
sequence_id BIGINT NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`sequence_id`)
)
Then, create your other tables, the ones which will use these unique sequence_id values. Here's an example:
CREATE TABLE gadget (
sequence_id BIGINT NOT NULL,
gname VARCHAR(20) DEFAULT NULL,
gvalue VARCHAR(20) DEFAULT NULL,
PRIMARY KEY (sequence_id)
)
Here's another:
CREATE TABLE widget (
sequence_id BIGINT NOT NULL,
wname VARCHAR(20) DEFAULT NULL,
wvalue VARCHAR(20) DEFAULT NULL,
PRIMARY KEY (sequence_id)
)
Then, whenever you insert a row into your other tables, do it like this:
INSERT INTO sequence () VALUES() ;
INSERT INTO widget (sequence_id, wname, wvalue)
VALUES (LAST_INSERT_ID(), 'whatever', 'you_want');
or, for the other example table,
INSERT INTO sequence () VALUES() ;
INSERT INTO gadget (sequence_id, gname, gvalue)
VALUES (LAST_INSERT_ID(), 'another', 'gadget');
The trick is this: when you insert each (seemingly empty) row into the sequence table it updates the auto-increment sequence_id field. Then, when you use the LAST_INSERT_ID() function it retrieves the most-recently inserted value of that sequence_id.
Be very careful to keep your two INSERT INTO statements consecutive, or this will stop working properly.
This works even if many different clients of the database server are doing the inserts, for two reasons;
auto-increment is thread-safe. No two sequence numbers can be the same.
LAST_INSERT_ID() values are maintained one for each distinct connection to the MySQL database. Different programs (or different threads in a multi-threaded web application) each have their own connections, so they each have their own values of LAST_INSERT_ID().
By the way, I used bigint data for the sequence, but int will work just as well.
I have this SQL code which inserts into a database named daily. I regularly update the daily database like this:
INSERT INTO daily
(SELECT * FROM dailytemp
WHERE trim(`Point`) <>''
AND trim(`Point`) IN (Select trim(`Point`) FROM `data`));
I believe I am getting the error because both tables have id primary columns.
I want the data inserted, but a new id generated for any duplicate found.
I tried to list the columns names, but it's about 20 columns, which might make the process cumbersome.
What is my best option?
Edit
The table defination of daily and daily_temp are the same, all the folumns are varchar(100) with and id of (bigint)
id bigint
col1 varchar(100)
col2 varchar(100)
col3 varchar(100)
etc..
A possible solution might be to use a BEFORE INSERT trigger and a separate table for sequencing (if you don't mind).
Sequence table
CREATE TABLE daily_seq(id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY);
Now the trigger
DELIMITER $$
CREATE TRIGGER tg_daily_insert
BEFORE INSERT ON daily
FOR EACH ROW
BEGIN
INSERT INTO daily_seq VALUES(NULL);
SET NEW.id = LAST_INSERT_ID();
END$$
DELIMITER ;
Now you can insert whatever you want to daily, id value will replaced with unique auto incremented one.
Here is SQLFiddle demo
Indeed, you are certainly trying to insert a row with an id that already exists in daily.
If this does not pose any referential integrity issue, you could regenerate the id's in dailytemp so that they do not overlap with any one in daily. This might do the trick:
SET #offset = (SELECT MAX(id) FROM daily) - (SELECT MIN(id) FROM dalytemp) +1;
UPDATE dailytemp SET id = id + #offset;
Then try your INSERT.
If rows can be inserted into daily by another process than the one you described, or if for any reason a id is skipped in this table (eg. if the column is AUTO_INCREMENT), then the problem will reoccur. You may want to include this preliminary UPDATE in your daily procedure.
i decided to use a stored proceedure to perform the action, listing all colums name instead of this headache
thanks to all those who helped. Another reason i did that was that i needed to insert a date for the daily table for each set of data inserted, so i created a date column for the dail table only
INSERT INTO daily (col1, col2, etc..., created)
SELECT col1, col2, etc..., 'mydate' from daily_temp WHERE
trim(`Point`) <>'' AND trim(`Point`) IN
(Select trim(`Point`) FROM `data`));
thanks all
Is it possible to make a script without else/if that deletes the oldest row for a user if the new row is the 4 row for that specific user?
I have a table called points_history. Fields are:
date(datetime),
fk_player_id(int),
points(int)
Here is my insert:
mysqli_query($mysqli,"INSERT INTO points_history (date,fk_player_id,points) VALUES (NOW(),$player,$points)");
The reason for this taht I want to be able to go back in the players history and check points, but only the last 3 points and don't want a table with a million of rows.
Can it be done in one sql query?
Hoping for help and thanks in advance :-)
This is quite easy to do if you add a primary key to your table points_history.
Part 1:
Use the following script to add a primary key called points_history_id to your table:
ALTER TABLE points_history RENAME TO points_history_old;
CREATE TABLE points_history
(
`points_history_id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`date` datetime NOT NULL,
`fk_player_id` int(11) NOT NULL,
`points` int(11) NOT NULL,
PRIMARY KEY (`points_history_id`)
);
INSERT INTO points_history (date, fk_player_id, points)
SELECT date, fk_player_id, points
FROM points_history_old;
-- Drop table if migration succeeded (up to you)
-- DROP TABLE points_history_old;
This needs to be run only once!
Part 2:
Now you can use the following SQL script to add a new record and delete the outdated:
-- First insert the new record
INSERT INTO points_history (date,fk_player_id,points)
VALUES (NOW(),:player,:points);
-- Create temporary table with records to keep
CREATE TEMPORARY TABLE to_keep AS
(
SELECT points_history_id
FROM points_history
WHERE fk_player_id = :player
ORDER BY date DESC
LIMIT 3
);
SET SQL_SAFE_UPDATES = 0;
-- Delete all records not in table to_keep
DELETE FROM points_history
WHERE points_history_id NOT IN (SELECT points_history_id FROM to_keep);
SET SQL_SAFE_UPDATES = 1;
-- Drop temporary table
DROP TEMPORARY TABLE to_keep;
If you use a database supporting transactions, I strongly recommend to wrap this script in a transaction. I tested it on MySQL 5.5.29 and it runs fine.
I'm tracking costs to clients by session and by items specific to each session. I'm trying to get the total session costs and session item costs (cost * count from tbl_sessionitem). But when I check the results, the code outputs the error:
Warning: mysql_fetch_array(): supplied argument is not a valid MySQL result resource
Here are my tables:
CREATE TABLE tbl_session (
`clientid` INT UNSIGNED NOT NULL,
`sessioncost` DECIMAL(6,2) NOT NULL,
`datetoday` DATETIME NOT NULL,
);
CREATE TABLE tbl_sessionitem (
`clientid` INT UNSIGNED NOT NULL,
`cost` DECIMAL(6,2) NOT NULL,
`count` INT UNSIGNED NOT NULL,
`datetoday` DATETIME NOT NULL
);
Here is my php code:
<?php
$date=$_POST['date'];
mysql_connect("localhost","root","");
mysql_select_db("database");
$sql=mysql_query("
SELECT id
, SUM(tbl_session.sessioncost) AS 'totalcost'
, SUM(tbl_sessionitem.count) * SUM(tbl_sessionitem.cost) AS 'totalquantitycost'
FROM (
SELECT clientid
, sessioncost
FROM tbl_session
WHERE datetoday = ('$date')
UNION ALL
SELECT clientid
, cost
, count
FROM tbl_sessionitem
WHERE datetoday = ('$date')
)
GROUP BY id");
while($row = mysql_fetch_array($sql))
{
echo $row['totalcost'];
echo $row['totalquantitycost'];
}
mysql_close();
?>
The warning means what it said: the value passed to mysql_fetch _array isn't a result. mysql_query returns a mixed value; when the query fails, it returns false. You need to perform error checking. mysql_error will give you an error message from MySQL, though be careful never to output database error messages to non-admins.
If you had done that, you would have seen a number of problems:
the subselect result must be given an alias.
the selects being UNIONed have a different number of columns
there's no column named "id" in the subselect results.
the aggregate functions reference the tables from the subselect, but the outer select can only access the result table (the one missing an alias).
Even if you fix those SQL errors, the query itself won't give the results you're looking for, due to the way grouping and aggregate functions work.
There's a much better approach. Session items are associated with sessions, but in the schema this association is loose, via the datetoday column. As a result, you have the odd use of unions. Instead, create surrogate keys for the tables and give the session items table a column that refers to the session table. While you're at it, drop the redundant "tbl_" prefix.
CREATE TABLE sessions (
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
client INT UNSIGNED NOT NULL,
cost DECIMAL(5,2),
`date` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
FOREIN KEY (client) REFERENCES clients (id)
) Engine=InnoDB;
CREATE TABLE session_items (
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
session INT UNSIGNED NOT NULL,
cost DECIMAL(5,2),
`count` INT UNSIGNED,
FOREIN KEY (session) REFERENCES sessions (id)
) Engine=InnoDB;
To get the total session cost and quantity cost for a given day, you can use a subquery to get the quantity cost for a session (necessary to prevent including session costs multiple time in the totalcost sum), then sum the session and quantity costs in an outer query for each client's total costs for a given day.
SELECT client,
SUM(cost) AS totalcost,
SUM(quantitycost) AS totalquantitycost
FROM (
SELECT client,
sessions.cost,
SUM(session_items.`count`) * SUM(session_items.cost) AS quantitycost
FROM sessions
JOIN session_items ON sessions.id=session_items.session
WHERE sessions.`date` = NOW()
GROUP BY sessions.id
) AS session_invoices
GROUP BY client
;
COUNT is not to be used as a Column name, it's a function, it's used like this:
Select COUNT(id) as countOfId FROM table
Also, I would recommend doing all those calculations in PHP, much easier to maintain and probably better performance, MySql isn't meant as a calculator.
If you want to use reserved keywords as column names, you need to add backticks and don't write them in capitals because that decreases readability in this case:
Select `count` from table
And what is COST?