select most recently added record with value in MySQL - 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 |
+------+-----------+

Related

What is wrong in this Update query which tried to update table using concat() fun

I want to update the field with appending data into it, but it is giving an error, please correct me (Query and Table desc are below)
I tried to fire UPDATE command with CONCAT () FUNCTION in SQL.
update products a
set a.des = (select concat((select b.des from products b limit 1) ,' one okay') from a)
where a.p_id = 1;
I have used MySQL,
Table Description:
mysql> desc products;
+---------+-------------+------+-----+--------------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+--------------+-------+
| p_id | int(3) | NO | PRI | 0 | |
| p_name | varchar(10) | YES | | NULL | |
| p_price | int(10) | YES | | NULL | |
| cat_id | int(3) | YES | MUL | NULL | |
| des | varchar(30) | YES | | Good | |
+---------+-------------+------+-----+--------------+-------+
Expected Output :
mysql> select * from products;
+------+--------+---------+--------+---------------+
| p_id | p_name | p_price | cat_id | des |
+------+--------+---------+--------+---------------+
| 1 | Mouse | 150 | 3 | Good one okay |
| 2 | LAN | 50 | 4 | Good |
+------+--------+---------+--------+---------------+
2 rows in set (0.00 sec)
Output Came :
Error -
update products a set a.des =
(select concat((select b.des from products b limit 1) ,' one okay')
from a) where a.p_id = 1 Error Code: 1146. Table 'test.a' doesn't exist 0.437 sec
MySQL does not allow you to reference the table being updated in the rest of the update statement, as a general rule.
The normal work-around is to phrase this as a JOIN:
update products p cross join
(select * from products limit 1) arbitrary
set p.des = concat(arbitrary.des, ' one okay')
where p.p_id = 1;
Note the use of the alias arbitrary. You are using limit with no order by so you are getting an arbitrary description.
If you just want to append a string to the existing description, then you want the simpler:
update products p
set p.des = concat(p.des, ' one okay')
where p.p_id = 1;

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.

MySQL database relationship without an ID

Hi StackOverflow community,
I have these two tables:
tbl_users
ID_user (PRIMARY KEY)
Username (UNIQUE)
Password
...
tbl_posts
ID_post (PRIMARY KEY)
Owner (UNIQUE)
Description
...
Why always everybody make database relationships with foreign keys? What about if I want to relate Username with Owner instead of doing ID_user with ID_user in both tables?
Username is UNIQUE and the Owner is the username of the creator of the post.
Can it be done like that? There is something to correct or make better? Maybe I have a misconception.
I would appreciate detailed and understandable answers.
Thank you in advance.
The reason is primarily for data integrity. The argument concerning performance is a little misleading. While neither exhaustive, nor definitive, I hope this little example will shed some light on that fact:
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(i INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,s CHAR(12) NOT NULL UNIQUE
);
STEP1:
INSERT IGNORE INTO my_table (s)
SELECT CONCAT(CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97)
,CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97)
);
STEP2:
INSERT IGNORE INTO my_table (s)
SELECT CONCAT(CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97)
,CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97),CHAR((RAND()*26)+97)
)
FROM my_table;
[REPEAT STEP 2 SEVERAL TIMES]
SELECT COUNT(*) FROM my_table;
+----------+
| COUNT(*) |
+----------+
| 16384 |
+----------+
1 row in set (0.01 sec)
SELECT * FROM my_table ORDER BY i LIMIT 12;;
+----+------------+
| i | s |
+----+------------+
| 1 | kkxeehxsvy |
| 2 | iuyhrk{vaq |
| 3 | ngpedelooc |
| 4 | irkbyqgkhc |
| 6 | yqkcifcxdz |
| 7 | sgezlgvjjq |
| 8 | blavbvxbnl |
| 9 | wdbtqvgvgt |
| 13 | pakzpbnhxr |
| 14 | vpoy{gdwyd |
| 15 | ezlhz{drwg |
| 16 | ncwcwbpudh |
+----+------------+
SELECT * FROM my_table x JOIN my_table y ON y.i < x.i ORDER BY x.i,y.i LIMIT 1;
+---+------------+---+------------+
| i | s | i | s |
+---+------------+---+------------+
| 2 | iuyhrk{vaq | 1 | kkxeehxsvy |
+---+------------+---+------------+
1 row in set (1 min 22.60 sec)
SELECT * FROM my_table x JOIN my_table y ON y.s < x.s ORDER BY x.s,y.s LIMIT 1;
+-------+------------+------+------------+
| i | s | i | s |
+-------+------------+------+------------+
| 21452 | aabetdlvum | 6072 | aabdnegtav |
+-------+------------+------+------------+
1 row in set (1 min 13.59 sec)
So, we have two queries doing essentially the same thing (a comparison of 270 million values). The first joins the table to itself on an integer value. The second joins the table to itself on a string value. Both columns are indexed. As you can see, in this example, the string join actually performs better than the integer join - even though the hit on the CPU may actually be greater!

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.

MySQL Alter table, add column with unique random value

I have a table that I added a column called phone - the table also has an id set as a primary key that auto_increments. How can I insert a random value into the phone column, that won't be duplicated. The following UPDATE statement did insert random values, but not all of them unique. Also, I'm not sold I cast the phone field correctly either, but ran into issues when trying to set it as a int(11) w/ the ALTER TABLE command (mainly, it ran correctly, but when adding a row with a new phone number, the inserted value was translated into a different number).
UPDATE Ballot SET phone = FLOOR(50000000 * RAND()) + 1;
Table spec's
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| phone | varchar(11) | NO | | NULL | |
| age | tinyint(3) | NO | | NULL | |
| test | tinyint(4) | NO | | 0 | |
| note | varchar(100) | YES | | NULL | |
+------------+--------------+------+-----+---------+----------------+
-- tbl_name: Table
-- column_name: Column
-- chars_str: String containing acceptable characters
-- n: Length of the random string
-- dummy_tbl: Not a parameter, leave as is!
UPDATE tbl_name SET column_name = (
SELECT GROUP_CONCAT(SUBSTRING(chars_str , 1+ FLOOR(RAND()*LENGTH(chars_str)) ,1) SEPARATOR '')
FROM (SELECT 1 /* UNION SELECT 2 ... UNION SELECT n */) AS dummy_tbl
);
-- Example
UPDATE tickets SET code = (
SELECT GROUP_CONCAT(SUBSTRING('123abcABC-_$#' , 1+ FLOOR(RAND()*LENGTH('123abcABC-_$#')) ,1) SEPARATOR '')
FROM (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) AS dummy_tbl
);
Try this
UPDATE Ballot SET phone = FLOOR(50000000 * RAND()) * id;
I'd tackle this by generating a (temporary) table containing the numbers in the range you need, then looping through each record in the table you wish to supply with random numbers. Pick a random element from the temp table, update the table with that, and remove it from the temp table. Not beautiful, nor fast.. but easy to develop and easy to test.