Values within primary key occasionally changes over time - mysql

I got some data defined in a table in a MySQL database like this
CREATE TABLE `T_dev` (
id VARCHAR(20) NOT NULL,
date DATETIME NOT NULL,
amount VARCHAR(9) DEFAULT NULL,
PRIMARY KEY (id,date)
);
I then insert a record, for example
INSERT INTO T_dev VALUES
('10000','2009-08-05 23:00:00','35')
However, one month later I get a report that tells me that this exact record should have amount equal to 30, thus
INSERT INTO T_dev VALUES
('10000','2009-08-05 23:00:00','30')
However, that can´t be done because of the primary key I´ve defined. I would like to overwrite the old record with the new one, but not really change my primary key. Any suggestions?
Thanks.
Alexander

Since the record already exists, you don't use the INSERT statement. Instead use an UPDATE statement to change the value to 30 for that specific id and date combination:
UPDATE T_dev SET amount = '30'
WHERE id = '10000' AND date = '2009-08-05 23:00:00'
Just an observation, your table is a little out of the norm. Typically primary keys are of type INT and your amount would probably be better off as a DECIMAL.

use an update statement
UPDATE T_dev
SET amount = 30
WHERE id=10000 AND date = '2009-08-05 23:00:00'

Related

mysql insert and update dates

so, I have a table named companies and I want to add 2 columns for insert and update times.
how do I do this? I dont want to add insert and update dates into my query.
this is my create statement
create table companies(
name varchar(20),
city char(10),
numberofemployees int(10),
averagesalary double
);
alter table companies add inserttime datetime, add updatedtime datetime, add id serial;
I need the insert query to look like this:
insert into companies values ("company","bglr",30,400.00)
and need output as
name,city,numberofemployees,averagesalary,inserttime,updatetime
company blr 30 400.00 23:00:11 23:00:11
First of all, you'll have to change your INSERT statement since the number of values doesn't match the number of columns. Luckily, you can specify the subset of columns that correspond to the given values:
INSERT INTO companies(name, city, numberofemployees, averagesalary)
VALUES ('company', 'blgr', 30, 400.00);
As you don't provide values for inserttime and updatetime, default values will be used for new records. Another way to insert records using default values is to put in the DEFAULT keyword instead of a concrete value (please refer to the documentation of INSERT for more details).
In your case the default values should be NULL because you didn't define anything else. To change the DEFAULT value for your columns, you can modify their definitions:
ALTER TABLE companies MODIFY COLUMN inserttime datetime DEFAULT CURRENT_TIMESTAMP;
Having this, inserttime is set to the current time for newly inserted records. Of course, you can also use the DEFAULT clause in CREATE TABLE statements, or while adding the columns.
Next, let's have a look at the updatetime. Usually, you want this to be updated automatically to the current time. This can be achieved in MySQL by specifying an ON UPDATE clause for the default value (details in Automatic Initialization and Updating for TIMESTAMP and DATETIME:
ALTER TABLE companies MODIFY COLUMN
updatetime datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
From now on, updatetime will be set to the current time automatically when a new record is inserted or an existing record is updated.
More information about default values in MySQL can be found in the documentation.

Error Code: 1136. Column count doesn't match value count at row 1 why?

USE Airline;
CREATE TABLE Responsible_for(
Time_work TIME NOT NULL,
date_work DATE NOT NULL,
Staff_ID INT NOT NULL,
Passenger_ID VARCHAR(45) NOT NULL,
CONSTRAINT FOREIGN KEY(Passenger_ID) REFERENCES Passenger(Passenger_ID),
CONSTRAINT FOREIGN KEY(Staff_ID) REFERENCES Staff(Staff_ID));
SELECT * FROM airline.Responsible_for;
INSERT INTO Responsible_for VALUES(
('04:00:00','2019-04-01',1235,'1102546778'));
why there is error?
thank you
You have an additional pair of parentheses around the list of values that should not be there.
That should be:
INSERT INTO Responsible_for(time_work, date_work, staff_id, passenger_id)
VALUES ('04:00:00','2019-04-01',1235,'1102546778');
Notes:
it is a good practice to always enumerate the columns to insert; this prevents hard-to-debug issues, and can make the code resilient to changes in the structure of the target table
you use airline at the beginning of the script, so there is no need to prefix the table name with the schema name in the insert statement
I would recommend against storing the date and time components in separate columns; this makes things unnecessarily complicated (and less efficient) when you need to compare it against a datetime; MySQL has the datetime datatype, that is meant to store both together

Update/Assign a foreign key to a pre-existing table row and not have it overwritten [mysql]

I have a table called promotion_codes
CREATE TABLE promotion_codes (
id int(10) UNSIGNED NOT NULL auto_increment,
created_at datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
code varchar(255) NOT NULL,
order_id int(10) UNSIGNED NULL DEFAULT NULL,
allocated_at datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
This table is pre-populated with available codes that will be assigned to orders that meet a specific criteria.
What I need to ensure is that after the ORDER is created, that I obtain an available promotion code and update its record to reflect that it has been allocated.
I am not 100% sure how to not grab the same record twice if simultaneous requests come in.
I have tried locking the row during a select and locking the row during a update - both still seem to allow a second (simultaneous) attempt to grab the same record - which is what I want to avoid
UPDATE promotion_code
SET allocated_at = "' . $db_now . '", order_id = ' . $donation->id . '
WHERE order_id IS NULL LIMIT 1
You can add a second table which holds all used codes. So you can use an unique constraint in the assignment table to make sure that one code is not assigned twice.
CREATE TABLE `used_codes` (`usage` INTEGER PRIMARY KEY auto_increment,
`id` INTEGER NOT NULL UNIQ, -- This makes sure, that there are no two assignments of one code
allocated_at datetime NOT NULL);
You add the ID of an used code into the used_codes table, and query which code you used afterwards. When this two operations are in one transaction, the entire transaction will fail when there is a second try to use the same code.
I did not test the following code, you might to adjust it.
Also you need to make sure that you have your server meets the requirements for transactions.
-- There are changes which have to be atomic, so don't use autocommit
SET autocommit = 0;
BEGIN TRANSACTION
INSERT INTO `used_codes` (`id`, `allocated_at`) VALUES
(SELECT `id` FROM `promotion_codes`
WHERE NOT `id` in (SELECT `id` FROM `used_codes`)
LIMIT 1), now());
SELECT `code` FROM `promotion_codes` WHERE `id` =
-- You might need to adjust the extraction of insertion ID, since
-- I don't know if parallel running transactions can see the maximum
-- their maximum IDs. But there should be a way to extract the last assigned
-- ID within this transaction.
(SELECT `id` FROM `used_codes` HAVING `usage` = max(`usage`));
COMMIT
You can use the returned code if the transaction sucseeded. If there where more than one processes running to use the same code, only one of them succed, while the rest fails with insert errors about the duplicated row. In your software you need to distinguish between the duplicated row error and other errors, and reexecute the statement on duplication errors.

Using UNIQUE indices with NULL fields in MySQL?

I can check, periodically, for a list of users that are currently online. I want to turn this into something useful like a list of entries per user with login / logout time. There is no other way to determine this information apart from checking who is currently online.
After some thinking I came up with something like this:
CREATE TABLE onlineActivity (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
name CHAR (32) NOT NULL,
login_time DATETIME NOT NULL,
logout_time DATETIME NOT NULL,
time SMALLINT (3) NOT NULL DEFAULT 0,
online BOOL DEFAULT NULL,
UNIQUE (name, online),
PRIMARY KEY (id)
) ENGINE = MyISAM;
I run this query every few minutes to add/update names in the activity list:
INSERT INTO onlineActivity (name, login_time, logout_time, online)
SELECT name, now(), now(), true FROM onlineList ON DUPLICATE KEY UPDATE logout_time = now()
And this query is run for every user that has logged out:
(the names are determined by comparing two adjacent online lists, the current one and the previous one)
UPDATE onlineActivity SET online = NULL WHERE name = ? AND online = 1
The questions:
I'm worrying that using a NULL field (online) in a UNIQUE index is a bad idea, and will hurt performance. I figure that MySQL might have to do a full scan of all the online's (instead of using an index) for each name to find one that is not NULL. Could someone clarify if that is the case here? I couldn't find any information on how MySQL deals with this sort of situation.
Do other database systems (PostgreSQL, SQLite) behave differently then MySQL in this regard?
should I instead of the first query, run two queries for each name, to see if a specified user is currently online, and act accordingly on that?
I thought of this design because I wanted to minimize the amount of queries used, is this a flawed idea in itself?
This table will be getting around 300~500k new records per day. Is there something else I can do to lessen the performance decrease?
I want to store a full history of user activity, not a single entry.
I am not sure why you have a unique on name and online since what you are trying to do is create a list of online activity. Putting a unique key as you have specified will mean that you can only have a name in there three times, one for each state (null, true, false).
What you are effectively doing is trying to create a history table in which case to use your current method of populating the table you should put a unique key on (name, logout_time) with a null logout_time indicating a currently logged in user (since you would only want one logout time that is null).
Something like this:
CREATE TABLE onlineActivity (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
name CHAR (32) NOT NULL,
login_time DATETIME NOT NULL,
logout_time DATETIME NULL,
time SMALLINT (3) NOT NULL DEFAULT 0,
online BOOL not null DEFAULT false,
UNIQUE (name, logout_time),
PRIMARY KEY (id)
) ENGINE = MyISAM;
Then run this on a schedule to update the table
INSERT IGNORE INTO onlineActivity (name, login_time, logout_time, online)
SELECT name, now(), null, true FROM onlineList
And this on user logout
UPDATE onlineActivity SET online = false, logout_time = now() WHERE name = ? AND logout_time = null

Choosing correct MySQL query

In my project I am using one MySQL table to store actual information for every id (unnecessary fields are omitted):
CREATE TABLE mytable (
`id` varchar(8) NOT NULL,
`DateTime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`id`)
) ENGINE=MyISAM
I need to update rows in this table and insert new DateTime only if this incoming DateTime is newer. But at the same time I do not know if the row with such id already exists in the table. Here is the pseudo-code of what I would like to achieve:
if id does not exist
insert id and DateTime
else if newDateTime > DateTime
update DateTime
I tried to use replace, but as far as I know it is not possible to compare fields from within replace body.
I tried to use update, but if row does not already exist - new information is not being inserted.
Is it possible to do something like this in one query? And if not - what could be the workaround?
PS: I do not think that I have permission to add stored procedures, so this should be a last resort.
I've tested this and it works. And I'm pretty proud of it.
INSERT INTO mytable (`id`,`DateTime`)
VALUES ('your_new_id','your_new_DateTime')
ON DUPLICATE KEY UPDATE `DateTime` = IF(`DateTime` < 'your_new_DateTime', 'your_new_DateTime', `DateTime`)