get the maximum number that extracted from a string using mysql - php

I have a table called Elements
id reference
101 AES/JN/2001
102 AMES/JN/2001
103 AES/JN/2002
104 AES/JN/2003
105 AMES/JN/2002
I want to get the maximum number from the string. If my search key word is AMES/JN I should get 2002. And If my key word is AES/JN then output should be 2003
I have tried the following code:
select max(convert(substring_index(reference,'/', -1), unsigned)) as max
FROM Elements WHERE reference like 'AES/JN/'

I almost agree with Shyam except for that horribly convoluted function.
I recommend this query:
SELECT SUBSTRING_INDEX(reference,'/',-1) as `max`
FROM `Elements`
WHERE reference LIKE 'AES/JN/%'
ORDER BY reference DESC
LIMIT 1
This will output a single row with with 2003 as the value in the max column.
The reason I like this method is because CONVERT() is omitted/unnecessary.
I've compared my query against Xenofexs' on my server and mine is only .0001 seconds faster -- but this is only running on the 5 rows that the OP posted. As the database volume increases, I am confident that my query's performance lead will increase.
Even if you don't care about the micro-optimization, I think this query is easier to read/comprehend because it doesn't have a function inside a function inside a function.
In fact, I believe this next query may outperform my above query:
SELECT SUBSTRING_INDEX(reference,'/',-1) as `max`
FROM `Elements`
WHERE LOCATE('AES/JN/',reference)
ORDER BY reference DESC
LIMIT 1
Because LOCATE() will be checking the leading characters from the reference column, and the targeted substring will not occur later in the string, LOCATE() has been benchmarked to outperform LIKE.
Additional reading:
MySQL LIKE vs LOCATE
For the record, here is the table that I used:
CREATE TABLE `Elements` (
`id` int(10) NOT NULL,
`reference` varchar(100) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
INSERT INTO `Elements` (`id`, `reference`) VALUES
(101, 'AES/JN/2001'),
(102, 'AMES/JN/2001'),
(103, 'AES/JN/2002'),
(104, 'AES/JN/2003'),
(105, 'AMES/JN/2002');
ALTER TABLE `Elements`
ADD PRIMARY KEY (`id`);
ALTER TABLE `Elements`
MODIFY `id` int(10) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=106;

Please check how "LIKE" work's.
You can use % as joker
Just change your query and add % character. And it's work
SELECT
max(
CONVERT (
substring_index(reference, '/', - 1),
UNSIGNED
)
) AS max
FROM
reference
WHERE
reference LIKE 'AES/JN/%'
Please note : LIKE 'AES/JN/%'

Please find below solution.
Query:
select id,reference,digits(reference) as num_values from asasas where reference like 'AMES/JN%' order by num_values DESC limit 1
You need to create one function in mysql
DELIMITER $$
DROP FUNCTION IF EXISTS `test`.`digits`$$
CREATE FUNCTION `digits`( str CHAR(32) ) RETURNS char(32) CHARSET latin1
BEGIN
DECLARE i, len SMALLINT DEFAULT 1;
DECLARE ret CHAR(32) DEFAULT '';
DECLARE c CHAR(1);
IF str IS NULL
THEN
RETURN "";
END IF;
SET len = CHAR_LENGTH( str );
REPEAT
BEGIN
SET c = MID( str, i, 1 );
IF c BETWEEN '0' AND '9' THEN
SET ret=CONCAT(ret,c);
END IF;
SET i = i + 1;
END;
UNTIL i > len END REPEAT;
RETURN ret;
END$$
DELIMITER ;
Let me know if it not works for you

SELECT MAX(Z.COUNT),reference FROM
(
SELECT reference,CAST(SUBSTRING_INDEX(reference, '/', -1) AS DECIMAL) count
FROM Elements where reference like 'AES/JN/%'
)Z
Try above code.
Hope this helps.

Related

MySQL, how to get the string with highest suffix

My table is:
After Query:
SELECT * FROM `table` WHERE `identifier` LIKE '000123_%'
I get:
How to get the row with highest suffix, i.e: 000123_5 with query.
Why dont create the function SPLIT_STRING at your DB like this:
CREATE FUNCTION `SPLIT_STRING`(
str VARCHAR(255) ,
delim VARCHAR(12) ,
pos INT
) RETURNS VARCHAR(255) CHARSET utf8 RETURN REPLACE(
SUBSTRING(
SUBSTRING_INDEX(str , delim , pos) ,
CHAR_LENGTH(
SUBSTRING_INDEX(str , delim , pos - 1)
) + 1
) ,
delim ,
''
);
And later you can call her from your query to get the last characters:
-- Example table
CREATE TABLE `test`(
`keywords` VARCHAR(255) DEFAULT NULL
) ENGINE = INNODB DEFAULT CHARSET = utf8;
INSERT INTO `test`(`keywords`)
VALUES
(
'keyword 1,keyword 2, keyword 3, keyword 4'
);
-- Example query
SELECT
-- keyword 1
SPLIT_STRING(`keywords`,',',1) AS keyword_1,
-- keyword 4, NOT trimmed
SPLIT_STRING(`keywords`,',',4) AS keyword_4,
-- keyword 4, trimmed
trim(SPLIT_STRING(`keywords`,',',4)) AS keyword_4_trimmed
FROM `test`;
Using it at your example:
SELECT SPLIT_STRING(`identifier`,'_',2) as identifier2,* FROM `table`
WHERE `identifier` LIKE '000123_%'
ORDER BY SPLIT_STRING(`identifier`,'_',2) DESC
In this particular answer, you could use RIGHT(identifier,1) to take the rightmost character. Then you would take the MAX() of the right to find the highest. However, if the suffix would end up being 1 or more characters then you would need to find the right regex to capture the characters that come after the last underscore and then take the MAX() of that group.
You can try this query, where you pick the highest value by comparation from all the right side values of the varchar 'identifier'. My code may not work exactly as I don't have a pc here. Good luck!
SELECT *, select RIGHT(identifier,1) as rvalue FROM `table` WHERE `identifier` LIKE '000123_%' and rvalue >= ALL (SELECT RIGHT(identifier,1) as rvalue FROM `table`) t
If it's just for a single group of rows you can apply:
order by char_length(identifier) desc, identifier desc
limit 1
Longer string means larger numbers...

MySql sum cell with delimited values [duplicate]

I'm writing a query that selects data from one table into another, one of the columns that needs to be moved is a DECIMAL column. For reasons beyond my control, the source column can sometimes be a comma separated list of numbers. Is there an elegant sql only way to do this?
For example:
source column
10.2
5,2.1
4
Should produce a destination column
10.2
7.1
4
I'm using MySQL 4, btw.
To do this kind of non trivial string manipulations, you need to use stored procedures, which, for MySQL, only appeared 6 years ago, in version 5.0.
MySQL 4 is now very old, the latest version from branch 4.1 was 4.1.25, in 2008. It is not supported anymore. Most Linux distributions don't provide it anymore. It's really time to upgrade.
Here is a solution that works for MySQL 5.0+:
DELIMITER //
CREATE FUNCTION SUM_OF_LIST(s TEXT)
RETURNS DOUBLE
DETERMINISTIC
NO SQL
BEGIN
DECLARE res DOUBLE DEFAULT 0;
WHILE INSTR(s, ",") > 0 DO
SET res = res + SUBSTRING_INDEX(s, ",", 1);
SET s = MID(s, INSTR(s, ",") + 1);
END WHILE;
RETURN res + s;
END //
DELIMITER ;
Example:
mysql> SELECT SUM_OF_LIST("5,2.1") AS Result;
+--------+
| Result |
+--------+
| 7.1 |
+--------+
Here is a mysql function to split a string:
CREATE FUNCTION SPLIT_STR(
x VARCHAR(255),
delim VARCHAR(12),
pos INT
)
RETURNS VARCHAR(255)
RETURN REPLACE(SUBSTRING(SUBSTRING_INDEX(x, delim, pos),
LENGTH(SUBSTRING_INDEX(x, delim, pos -1)) + 1),
delim, '');
And u have to use it this way:
SELECT SPLIT_STR(FIELD, ',', 1) + SPLIT_STR(FIELD, ',', 2) FROM TABLE
Unfortunately mysql does not include string split functions or aggregates, so you will need to do this either in a stored procedure or on the client side.
A number table-based parse approach can be found at this SQLFiddle link. Esentially, once you have the substrings, the sum function will auto-cast the numbers. For convenience:
create table scores (id int primary key auto_increment, valueset varchar(30));
insert into scores (valueset) values ('7,6,8');
insert into scores (valueset) values ('3,2');
create table numbers (n int primary key auto_increment, stuffer varchar(3));
insert into numbers (stuffer) values (NULL);
insert into numbers (stuffer) values (NULL);
insert into numbers (stuffer) values (NULL);
insert into numbers (stuffer) values (NULL);
insert into numbers (stuffer) values (NULL);
SELECT ID, SUM(SCORE) AS SCORE
FROM (
SELECT
S.id
,SUBSTRING_INDEX(SUBSTRING_INDEX(S.valueset, ',', numbers.n),',',-1) score
, Numbers.n
FROM
numbers
JOIN scores S ON CHAR_LENGTH(S.valueset)
-CHAR_LENGTH(REPLACE(S.valueset, ',', ''))>=numbers.n-1
) Z
GROUP BY ID
;

Find first available identifier from database table

The requirement is to find the first available identifier where an identifier is an alphanumeric string, such as:
ABC10000
ABC10345
ABC88942
ABC90123
The database table has a structure such as:
id, user, identifier
Note that the alpha component ABC is consistent throughout and won't change. The numeric component should be between 10000 and 99999.
How best to tackle this? It does not seem like an overly complex problem - looking for simplest solution using either just MySQL or a combination of SQL and PHP. The current solution pulls each record from the database into an array and then loops from 10000 onwards, prepending ABC and checking availability, which seems like it could be improved significantly.
Edit: Original question was not clear enough in that a certain amount of identifiers have been assigned already, and I am looking to fill in the gaps. From the short list I provided, the next available would be ABC10001. Eventually, however, it would be ABC10346 and then ABC88943 and so on
Edit: Sorry for a poorly structured question. To avoid any further confusion, here is the actual table structure:
CREATE TABLE IF NOT EXISTS `User_Loc` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user` int(11) DEFAULT NULL,
`value` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UNIQ_64FB41DA17323CBC` (`user`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=4028 ;
You have to self join the table and look for the first NULL value in the joined table
SELECT CONCAT('ABC', SUBSTRING(t1.value, 4)+1) AS next_value
FROM test t1
LEFT JOIN test t2 on SUBSTRING(t1.value, 4)+1 = SUBSTRING(t2.value, 4)
WHERE ISNULL(t2.value)
ORDER BY t1.value ASC
LIMIT 1
http://sqlfiddle.com/#!2/d69105/22
edit
With the comment about some 'specialities' at ncatnow. There are slight adjusments to make with the help of subselects for ridding the 'ABC' and UNION for having a default value
SELECT
CONCAT('ABC', t1.value+1) AS next_value
FROM
((SELECT '09999' AS value) UNION (SELECT SUBSTRING(value, 4) AS value FROM test)) t1
LEFT JOIN
((SELECT '09999' AS value) UNION (SELECT SUBSTRING(value, 4) AS value FROM test)) t2
ON t1.value+1 = t2.value
WHERE
ISNULL(t2.value)
AND t1.value >= '09999'
ORDER BY t1.value ASC
LIMIT 1
http://sqlfiddle.com/#!2/28acf6/50
Similar to the above reply by #HerrSerker, but this will cope with existing identifiers which have the numeric part starting with a zero.
SELECT CONCAT('ABC',SUBSTRING(CONCAT('00000', CAST((CAST(SUBSTRING(a.identifier, 4) AS SIGNED) + 1) AS CHAR)), -5)) AS NextVal
FROM SomeTable a
LEFT OUTER JOIN SomeTable b
ON b.identifier = CONCAT('ABC',SUBSTRING(CONCAT('00000', CAST((CAST(SUBSTRING(a.identifier, 4) AS SIGNED) + 1) AS CHAR)), -5))
WHERE b.identifier IS NULL
ORDER BY NextVal
LIMIT 1
what comes to my mind is one table with all indentifiers and use this sql
SELECT identifier FROM allIdentifiersTable WHERE identifier NOT IN (SELECT identifier FROM yourTable) LIMIT 1
Reconsidering this from your edit, you should go the PHP route and add another table or other means to store the last filled id:
$identifier = 0;
$i = mysql_query("SELECT identifier FROM last_identifier");
if ($row = mysql_fetch_assoc($i)) $identifier = $row["identifier"];
if ($identifier < 10000) $identifier = 10000;
do {
$identifier += 1;
$result = mysql_query("
INSERT IGNORE INTO table (id, user, identifier)
VALUES ('[...]', '[...]',
'ABC" . str_pad($identifier, 5, "0", STR_PAD_LEFT) . "'
)");
if (mysql_affected_rows($result) < 1) continue;
} while (false);
mysql_query("UPDATE last_identifier SET identifier = '$identifier'");
Of course, you need to add a UNIQUE index on the identifier field.

MYSQL: Ordering by the average of an array

So I have an imploded array in a mysql table that is basically just a sequence of numbers (ex. 1,5,3,1,4,5) and I was wondering if it was possible to order them by the average (or even the sum if that were possible) of the sets of numbers in the table
Instead of storing your numbers in a delimited string, take advantage of MySQL's relational capabilities and normalise your data: store your numbers as (key, value) pairs in a separate table that relates a foreign key (i.e. that of your existing table) to a single number in the list. If order is important, include an additional column to indicate the number's position within the list.
CREATE TABLE `newtable` (
`key` INT,
`value` INT,
FOREIGN KEY (`key`) REFERENCES `existingtable` (`key`)
)
Then you need only join the tables together, GROUP BY the key in your existing table and ORDER BY the AVG() or SUM() of the values in the new table; you can even reclaim the comma-separated list if so desired using MySQL's GROUP_CONCAT() function:
SELECT `existingtable`.*, GROUP_CONCAT(`newtable`.`value`) AS `values`
FROM `existingtable` LEFT JOIN `newtable` USING (`key`)
GROUP BY `key`
ORDER BY AVG(`newtable`.`value`) -- or use SUM() if preferred
As others have mentioned, it's not clear what your table looks like, or the data in you table looks like. It's not at all clear what an "imploded array" looks like. This is where an EXAMPLE would really help you get the answer you are looking for.
But let's go with the "worst case" here, and assume you've got a string containing those values you want to average. Like this:
CREATE TABLE foo (str VARCHAR(1000));
INSERT INTO foo VALUES ('1,5,3,1,4,5');
INSERT INTO foo VALUES ('2.8,4.2');
INSERT INTO foo VALUES ('bar');
INSERT INTO foo VALUES (' 0, -2, 3');
It's possible to create a function that returns an average of the values in your "imploded array" string. As an example:
DELIMITER $$
CREATE FUNCTION `do_average`(p_str VARCHAR(2000))
RETURNS DOUBLE
BEGIN
DECLARE c INT;
DECLARE i INT;
DECLARE s VARCHAR(2000);
DECLARE v DOUBLE;
SET c = 0;
SET v = 0;
SET s = CONCAT(p_str,',');
SET i = INSTR(s,',');
WHILE i > 0 DO
SET v = v + SUBSTR(s,1,i);
SET c = c + 1;
SET s = SUBSTR(s,i+1,2000);
SET i = INSTR(s,',');
END WHILE;
RETURN v / NULLIF(c,0);
END$$
DELIMITER ;
We can demonstrate this working:
SELECT f.str
, do_average(f.str) AS davg
FROM foo f
ORDER BY do_average(f.str)
str davg
----------- -------------------
bar 0
0, -2, 3 0.333333333333333
1,5,3,1,4,5 3.16666666666667
2.8,4.2 3.5
Note that with this function, we're using MySQL's implicit conversion of strings to numbers, so empty strings, or invalid numbers are going to be converted to zero, and that zero is going to get added and counted in computing the average.
A do_sum function would be nearly identical, just return the total rather than the total divided by the count.
sqlfiddle example here http://sqlfiddle.com/#!2/4391b/2/0

MySQL "ERROR 1046 (3D000): No database selected" on update query

I have an UPDATE query where I explicitely reference the database, but MySQL still complains with the message: ERROR 1046 (3D000): No database selected.
Other queries that are similar of structure, but use an INSERT work fine. Other queries that only perform SELECTs also run fine.
To repeat the problem in a test case, try running these queries:
create table test.object1 (
id_object1 int unsigned not null auto_increment,
total int,
weight int,
dt datetime,
primary key (id_object1)
) engine=InnoDB;
create table test.object2 (
id_object2 int unsigned not null auto_increment,
primary key (id_object2)
) engine=InnoDB;
create table test.score (
id_object1 int unsigned not null,
id_object2 int unsigned not null,
dt datetime,
score float,
primary key (id_object1, id_object2),
constraint fk_object1 foreign key (id_object1) references object1 (id_object1),
constraint fk_object2 foreign key (id_object2) references object2 (id_object2)
) engine=InnoDB;
insert into test.object1 (id_object1, total, weight, dt) values (1, 0, 0, '2012-01-01 00:00:00');
insert into test.object1 (id_object1, total, weight, dt) values (2, 0, 0, '2012-01-02 00:00:00');
insert into test.object2 (id_object2) values (1);
insert into test.score (id_object1, id_object2, dt, score) values (1, 1, '2012-01-03 00:00:00', 10);
insert into test.score (id_object1, id_object2, dt, score) values (2, 1, '2012-01-04 00:00:00', 8);
update test.object1 p
join (
select ur.id_object1, sum(ur.score * ur.weight) as total, count(*) as weight
from (
select lur.*
from (
select s.id_object1, s.id_object2, s.dt, s.score, 1 as weight
from test.score as s
join test.object1 as o1 using(id_object1)
where s.dt > o1.dt
order by s.id_object1, s.id_object2, s.dt desc
) as lur
group by lur.id_object2, lur.id_object1, date(lur.dt)
order by lur.id_object1, lur.id_object2
) as ur
group by ur.id_object1
) as r using(id_object1)
set
p.total = p.total + r.total,
p.weight = p.weight + r.weight,
p.dt = now();
Note: I'm running these queries from a PHP environment and I have NOT explicitely used mysql_select_db('test'), because I prefer not to and none of the other (many!) queries require it. I'm sure that using mysql_select_db would solve my issue, but I would like to know why exactly this particular query does not work.
For comparison sake: if you'd run this simpler query, also without using mysql_select_db, everything works fine:
update test.object1 set total=1, weight=1, dt=now() where id_object1=1;
I've searched to no avail. The only thing I found that came close, was this bug report: http://bugs.mysql.com/bug.php?id=28551 and especially that last (unanswered) message...
You have fields named incorrectly, but even if you correct them, this is a bug in MySQL that won't let you do it if you don't have default database.
update test.object1 p
join (
select ur.id_object1, sum(ur.score * ur.weight) as total, count(*) as weight
from (
select lur.*
from (
select s.id_object1, s.id_object2, s.dt, s.score, 1 as weight
from test.score as s
join test.object1 as o1
using (id_object1)
where s.dt > o1.dt
order by
s.id_object1, s.id_object2, s.dt desc
) as lur
group by
lur.id_object1, lur.id_object1, date(lur.dt)
order by
lur.id_object1, lur.id_object1
) as ur
group by ur.id_object1
) as r
USING (id_object1)
SET p.total = p.total + r.total,
p.weight = p.weight + r.weight,
p.dt = now();
The problem is specific to UPDATE with double-nested queries and no default database (SELECT or single-nested queries or default database work fine)
You have some wrong field names in the UPDATE statement -
What is s.object? Shouldn't it be s.id_object2?
What is lur.object1? Shouldn't it be lur.id_object1?
What is lur.object2? Shouldn't it be lur.id_object2?
What is ur.id_object at the end?
Fix all these issues and try to update again;-)
First time I ran this script I got that error. My output:
1 row inserted [0,184s]
1 row inserted [0,068s]
1 row inserted [0,066s]
1 row inserted [0,147s]
1 row inserted [0,060s]
Error (32,1): No database selected
When I set default database name the problem disappeared.
Remember that you cannot use foreign keys when the Engine is set to MyISAM. Not only does the table that you are creating a foreign key in need to be InnoDB, but the table you are getting the key from also needs to be InnoDB.
I was getting the same error as you and pulling my hair out for days before I thought of this. I went into each of my tables and made sure the Engines were set to InnoDB for each one, and now I have no issues setting up foreign keys.

Categories