MYSQL & innoDB alter dynamically AUTO_INCREMENT of a table - mysql

I have a problem, for example in my system I have the next table:
CREATE TABLE `sales` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`amount` FLOAT NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
-- is more complex table
With content:
+-----+-------+
| id | amount|
+-----+-------+
|2023 | 100 |
|2024 | 223 |
|2025 | 203 |
|... |
|2505 | 324 |
+-----+-------+
I don't know the current id(There are sales every day). I'm trying to normalize the table.
UPDATE sales SET id=id - 2022;
Result:
+-----+-------+
| id | amount|
+-----+-------+
| 1 | 100 |
| 2 | 223 |
| 3 | 203 |
|... |
| 482 | 324 |
+-----+-------+
The problem
My problem was trying to change the AUTO_INCREMENT, f.e.:
ALTER TABLE sales AUTO_INCREMENT = 483;
Its correct but I don't know the current id :(, I try the following query:
ALTER TABLE sales AUTO_INCREMENT = (SELECT MAX(id) FROM sales );
This causes me a error(#1064). Reading the documentation tells me:
In MySQL, you cannot modify a table and select from the same table in a subquery.
http://dev.mysql.com/doc/refman/5.7/en/subqueries.html
I try whit variables:
SET #new_index = (SELECT MAX(id) FROM sales );
ALTER TABLE sales AUTO_INCREMENT = #new_index;
But, this causes a error :(.

ALTER TABLE must have literal values in it by the time the statement is parsed (i.e. at prepare time).
You can't put variables or parameters into the statement at parse time, but you can put variables into the statement before parse time. And that means using dynamic SQL:
SET #new_index = (SELECT MAX(id) FROM sales );
SET #sql = CONCAT('ALTER TABLE sales AUTO_INCREMENT = ', #new_index);
PREPARE st FROM #sql;
EXECUTE st;

Thanks to Bill Karwin, my query was:
SET #sales_init = 2022;
DELETE FROM `sales` WHERE `sales`.`id` <= #sales_init;
UPDATE sales SET id=id - #sales_init;
-- set new index for sales
SET #new_init = (SELECT MAX(id) + 1 FROM sales );
SET #query = CONCAT("ALTER TABLE sales AUTO_INCREMENT = ", #new_init);
PREPARE stmt FROM #query;
EXECUTE stmt;

Related

select most recently added record with value in MySQL

I have a table with the following structure
CREATE TABLE `data` (
`type` varchar(64) DEFAULT NULL,
`subscr_id` varchar(64) DEFAULT NULL
)
In this table, there are many records with subscr_id of id100. I would like to select a record with subscr_id of id100, that was added to the table most recently.
How can I do that?
You add an ID - Indentify column. It's best performance in this/your situation.
ALTER TABLE data ADD COLUMN id INT NULL AUTO_INCREMENT FIRST, ADD KEY(id);
Run the below SQL, you will receive the record with subscr_id of id100, that was added to the table most recently most recently.
SELECT * FROM `data` WHERE subscr_id = 'id100' ORDER BY id DESC LIMIT 1;
I think you have to improve your table design and add auto-inctemental primary key or created_at field.
But if you can't do it or you need run query just once, you can try this approach (it's a bit tricky but it works 😉).
In general recent record will be present at the end of the table. For example we have table like this:
+------+-----------+
| type | subscr_id |
+------+-----------+
| a | id100 |
| b | id100 |
| c | id100 |
| a | id200 |
| b | id200 |
| d | id100 |
| c | id200 |
| e | id100 |
+------+-----------+
And here wee need calculate total count of interesting rows and use it for offset, like this:
set #offset = (select count(*) from data where subscr_id = 'id100') - 1;
set #sql = concat(
"select * from data where subscr_id = 'id100' limit 1 offset ",
#offset
);
prepare stmt1 from #sql;
execute stmt1;
The result will look like this:
+------+-----------+
| type | subscr_id |
+------+-----------+
| e | id100 |
+------+-----------+

MySQL Query INNER JOIN with aliases

I have two tables: users and users_info
users looks like this:
+----+----------+-------+
| id | slug | name |
+----+----------+-------+
| 1 | theploki | Kris |
+----+----------+-------+
and users_info looks like this:
+----+--------+----------+---------------+
| id | parent | info_key | info_val |
+----+--------+----------+---------------+
| 1 | 1 | email | kris#kris.com |
+----+--------+----------+---------------+
| 2 | 1 | age | 28 |
+----+--------+----------+---------------+
I want to SELECT a user who has user_info email = 'kris#kris.com'
- and -
return ALL user_info values and users values
Here's the result I'm looking for:
+----+----------+-------+---------------+-----+
| id | slug | name | email | age |
+----+----------+-------+---------------+-----+
| 1 | theploki | Kris | kris#kris.com | 28 |
+----+----------+-------+---------------+-----+
So far the closest I've gotten is with this query:
SELECT users.*, users_info.* FROM users
INNER JOIN users_info on users_info.parent = users.id
where users.id = (SELECT users_info.parent FROM users_info
WHERE users_info.parent = users.id
AND users_info.info_val = 'kris#kris.com')
And it returns this result:
+----+----------+-------+----+--------+----------+---------------+
| id | slug | name | id | parent | info_key | info_val |
+----+----------+-------+----+--------+----------+---------------+
| 1 | theploki | Kris | 1 | 1 | email | kris#kris.com |
+----+----------+-------+----+--------+----------+---------------+
| 1 | theploki | Kris | 2 | 1 | age | 28 |
+----+----------+-------+----+--------+----------+---------------+
Obviously I don't need the id of the users_info result and I want each info_key to be the "alias" (or column name) and each info_val to be the value for that "alias".
For this case, you can do it like this;) Just a simple table pivot.
select
users.id,
users.slug,
users.name,
max(if(users_info.info_key = 'email', users_info.info_val, null)) as email,
max(if(users_info.info_key = 'age', users_info.info_val, null)) as age
from users
inner join users_info
on users.id = users_info.parent
group by users.id
SQLFiddle DEMO HERE
If you have a dynamic info_key, you will need a dynamic sql to do this, here I give you a sample.
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'max(if(users_info.info_key = ''',
users_info.info_key,
''', users_info.info_val, null)) as ',
users_info.info_key
)
) INTO #sql
FROM users
inner join users_info
on users.id = users_info.parent
;
SET #sql = CONCAT('select users.id, users.slug, users.name, ', #sql, ' FROM users
inner join users_info group by users.id having email = \'kris#kris.com\'');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SQLFiddle DEMO HERE
This utilizes a change in the schema to support casting of data coming back. And it hinges on the use of a stored procedure.
The maximum value of group_concat is governed by your setting for the following variable (which is usually defaulted rather low, like 1K):
set session group_concat_max_len = 20000;
Embed that call at the top of your stored proc under BEGIN. The manual page is here. The value can be huge. For instance, at least 4GB.
Schema
drop table if exists users;
create table users
(
id int auto_increment primary key,
slug varchar(100) not null,
name varchar(100) not null
-- other indexes here like uniqueness, etc (btw none added)
);
drop table if exists users_info;
create table users_info
(
id int auto_increment primary key,
parent int not null,
info_key varchar(100) not null,
info_val varchar(100) not null,
datatype varchar(100) not null, -- see http://stackoverflow.com/a/8537070/ (DATA TYPES)
-- other indexes here (btw none added)
-- FK below:
foreign key `ui_2_users_9283` (parent) references users(id) -- I guess
);
Load Test data;
-- delete from users; -- note truncate disallowed on parent with an FK (so delete !)
insert users(slug,name) values
('theploki','Kris'),
('Yoda','Yoda');
-- select * from users;
-- truncate table users_info;
insert users_info(parent,info_key,info_val,datatype) values
(1,'email','kris#kris.com','char(100)'),
(1,'age','28','unsigned'),
(2,'birthdate','1996-02-14','date'),
(2,'email','yoda#starwars.com','char(100)'),
(2,'networth','102504.12','decimal(12,2)'),
(2,'age','910','unsigned');
Stored Procedure:
drop procedure if exists fetchInfoKeysByEmailAddr;
DELIMITER $$
create procedure fetchInfoKeysByEmailAddr(emailAddr varchar(100))
BEGIN
set #parentid=-1;
select parent into #parentid
from users_info
where info_key='email' and info_val=emailAddr;
if #parentid>0 then
-- http://stackoverflow.com/a/8537070/ (DATA TYPES)
SELECT GROUP_CONCAT(concat('cast("',info_val,'" as ',datatype,') as ',info_key)
ORDER BY info_key SEPARATOR ',') into #tail
FROM users_info
where parent=#parentid
GROUP BY parent;
set #final:=concat("select id,slug,name,",#tail,' from users where id=',#parentid);
PREPARE stmt1 FROM #final;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
end if;
END$$
DELIMITER ;
Test:
call fetchInfoKeysByEmailAddr('x');
-- user info does not exist, empty (todo: change accordingly)
call fetchInfoKeysByEmailAddr('kris#kris.com');
+----+----------+------+-----+---------------+
| id | slug | name | age | email |
+----+----------+------+-----+---------------+
| 1 | theploki | Kris | 28 | kris#kris.com |
+----+----------+------+-----+---------------+
call fetchInfoKeysByEmailAddr('yoda#starwars.com');
+----+------+------+-----+------------+-------------------+-----------+
| id | slug | name | age | birthdate | email | networth |
+----+------+------+-----+------------+-------------------+-----------+
| 2 | Yoda | Yoda | 910 | 1996-02-14 | yoda#starwars.com | 102504.12 |
+----+------+------+-----+------------+-------------------+-----------+
Due to the cast call embedded in the select, the data is brought back in its native, anticipated data type. Which means you can work on it directly.

Use already returned SELECT in another SELECT in stored procedures

I have a stored procedure that returns two result sets. But second SELECT is related on first by join.
Is there a way in MySQL to get rid of repeating? How could I address a SELECT that already was done? Would it increase performance?
CREATE PROCEDURE `procedure`()
BEGIN
SELECT * FROM users WHERE age > 20;
SELECT item_id FROM items INNER JOIN (SELECT * FROM users WHERE age > 20) ON user_id;
END
create table items
(
item_id int auto_increment primary key,
user_id int not null
-- FK not shown
);
create table users
(
user_id int auto_increment primary key,
age int not null
);
insert items(user_id) values (1),(1),(2);
insert users (age) values (22),(1);
Data at the moment
select * from items;
+---------+---------+
| item_id | user_id |
+---------+---------+
| 1 | 1 |
| 2 | 1 |
| 3 | 2 |
+---------+---------+
select * from users;
+---------+-----+
| user_id | age |
+---------+-----+
| 1 | 22 |
| 2 | 1 |
+---------+-----+
Query
SELECT item_id FROM items
join
( select user_id
from users
where age>20
) xDerived
on xDerived.user_id=items.user_id;
+---------+
| item_id |
+---------+
| 1 |
| 2 |
+---------+
So there, that is your query. One query, 1 result set.
Depiction of temp tables
This is a view of things of temp tables based on op questions above (not below).
drop procedure if exists proc123;
delimiter $$
create procedure proc123 (iParam int)
begin
insert myTempThing (theI,theWhen) select iParam,now();
end
$$
delimiter ;
-- the following is a temp table NOT created inside a stored proc
-- as in, via whatever programming language you are using
-- will incur time overhead especially if indexes are used, but regardless
create temporary table myTempThing
( id int auto_increment primary key,
theI int not null,
theWhen datetime not null
);
insert myTempThing (theI,theWhen) select 123,now();
-- now wait a bit and run the call stmt below
-- again I am still at a workbench prompt
call proc123(456);
select * from myTempThing;
-- two rows

MySQL: Multiple Primary Keys and Auto Increment

I'm quite new to setting up tables in MySQL and there is something I'd like to do which is a bit more advance than I'm able to do.
I have two columns as part of a composite primary key, one is a Date and an ID I would like to be an auto increment integer. For each date, I would like to reset the auto integer to 0, so something like this:
|-----------------|
|Date | ID |
|-----------------|
|2012-06-18 | 1 |
|2012-06-18 | 2 |
|2012-06-18 | 3 |
|2012-06-19 | 1 |
|2012-06-19 | 2 |
|2012-06-20 | 1 |
|-----------------|
Thanks
Here this should work.
CREATE TABLE `answer`(
`dates` DATE NOT NULL,
`id` mediumint(9) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`dates`,`id`)
) ENGINE=MyISAM;
It is known to cause problems with innoDB. Hope this helps you.
EDIT: RESULTS
2012-06-19 1
2012-06-19 2
2012-06-19 3
2012-07-19 1
2012-07-19 2
2012-08-19 1
On php myadmin.
Well, for me mysql does what you want automatically.
mysql> CREATE TABLE TestData(Date date not null, ID int unsigned not null auto_increment, PRIMARY KEY(Date, ID));
mysql> INSERT INTO TestData SET Date = "2012-06-18";
mysql> INSERT INTO TestData SET Date = "2012-06-18";
mysql> INSERT INTO TestData SET Date = "2012-06-18";
mysql> INSERT INTO TestData SET Date = "2012-06-19";
mysql> INSERT INTO TestData SET Date = "2012-06-19";
mysql> INSERT INTO TestData SET Date = "2012-06-20";
mysql> select * from TestData;
+------------+----+
| Date | ID |
+------------+----+
| 2012-06-18 | 1 |
| 2012-06-18 | 2 |
| 2012-06-18 | 3 |
| 2012-06-19 | 1 |
| 2012-06-19 | 2 |
| 2012-06-20 | 1 |
+------------+----+
No magic involved.
You can create a before insert trigger.
DELIMITER $$
CREATE TRIGGER `composite_auto_increment` BEFORE INSERT ON `your_table`
FOR EACH ROW
BEGIN
DECLARE max_id INT(11); -- add the appropriate column length from your table definition
SELECT ID FROM `your_table` WHERE `Date` = DATE(NOW()) INTO max_id;
SET NEW.ID = IF(ISNULL(max_id), 1, max_id + 1);
END$$
This way, if and ID already existed for the day, it gets incremented. If it didn't, it gets set to 1. Note that in this scenario, ID isn't AUTO_INCREMENT in the table definition. It just gets done by the trigger.
In trigger:
SELECT ID FROM your_table WHERE Date = DATE(NOW()) INTO max_id;
must be:
SELECT max(ID) FROM your_table WHERE Date = NEW.key_field INTO max_id;
but better is lock by key.
this is better for concurrent inserts on innodb.

MySQL query to reorder field values?

I have table like this:
===============
| rank | name |
===============
| 3 | john |
| 6 | bob |
| 10 | alex |
| 11 | brad |
| 12 | matt |
| 34 | luke |
| 145 | ben |
===============
(this table is an example. In reality my table consists of ~5000 rows of data).
Is there a query to reorder the rank values starting from 1 and going up so it ends up like this:
===============
| rank | name |
===============
| 1 | john |
| 2 | bob |
| 3 | alex |
| 4 | brad |
| 5 | matt |
| 6 | luke |
| 7 | ben |
===============
It would be preferable to do this in 1 or 2 queries, not 1 query for each row since my table has 5000+ rows.
EDIT: Sorry I wasn't clear. I am trying to UPDATE the values in the database.
This is a little crude but will work in a pinch.
First order your table correctly just incase
ALTER TABLE tablename ORDER BY rank
Then drop the column
ALTER TABLE tablename DROP rank
Then add it again, with auto increment
ALTER TABLE tablename ADD COLUMN rank INT NOT NULL AUTO_INCREMENT FIRST
The auto increment will take care of numbering them in order, plus you don't have to loop through each row.
Here is the solution I came up with for this problem:
1.Create a temporary table without any keys
CREATE TEMPORARY TABLE tempTable (
id INT(11) NOT NULL
)
COLLATE='latin1_swedish_ci'
ENGINE=MyISAM
ROW_FORMAT=DEFAULT;
2.Populate the temporary table with data from the original table, ordered by rank
INSERT INTO tempTable SELECT id FROM myTable ORDER BY rank;
3.Add auto-incrementing rank column, giving all rows a unique rank, counting up from 1
ALTER TABLE tempTable
ADD COLUMN `rank` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
ADD PRIMARY KEY (`rank`);
4.Update the original table with a join to the temp table, overriding the original ranks
UPDATE myTable
INNER JOIN tempTable
ON myTable.id = tempTable.id
SET myTable.rank = tempTable.rank;
5.Drop the temp table
DROP TABLE tempTable;
An alternative to a strict MySQL solution would be to loop through the rows with a scripting language. Not a great idea if you have a large table, but could be acceptable if this is a one time fix.
In PHP
$db = mysql_connect('localhost', 'user', 'password');
mysql_select_db('database', $db);
$result = mysql_query("SELECT rank
FROM myTable
ORDER BY rank");
$i = 1;
while ($row = mysql_fetch_assoc($result)) {
mysql_query("UPDATE myTable
SET rank = " . $i++ . "
WHERE rank = " . $row['rank']);
}
Note that this will only work if rank is unique and you traverse in an order.
set #a:=(select max(id) from mytable)+1;
update mytable set id=(#a:=#a+1)
order by id;
set #a := 0;
update mytable set id=(#a:=#a+1)
order by id;
simple way, work for me. easy way.