MYSQL: Ordering by the average of an array - php

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

Related

How can I UPDATE a column in a record when that information is the result of a GROUP CONCAT in the same table?

I cannot determine if this should be something nested or a JOIN.
Each record has three values from the column value across from their name in the column variable.
I have a successful GROUP CONCAT that combines them into a single text string.
BUT, I need to UPDATE/INSERT the concat value into another variable=>value pair.
Such as this. I want each person to have "cityStateZip" in the value for `cust_abc'.
I believe it's "INSERT" and not update since none of the records have anything in cust_abc yet. But, I'm not quite sure if it shouldn't be UPDATE.
id_member
variable
value
1234
cust_abc
"should show citystatezip"
1234
cust_a
city
1234
cust_b
state
1234
cust_c
zip
I can't get past the error of having the target table being the same in the SELECT FROM.
I was attempting things like:
INSERT INTO smgqg_themes.value (my group concat) WHEN `variable` = "cust_abc"
This is the group concat that works fine to make the string:
SELECT GROUP_CONCAT
( `value`
order by case variable
when 'cust_a' then 1
when 'cust_b' then 2 else 3 end
SEPARATOR '')
output
FROM smfqg_themes
WHERE `id_member` IN (1234, 1235, 1236, etc)
AND `variable` IN ('cust_a', 'cust_b', 'cust_c')
You can INSERT into a the same table you SELECT from in the same SQL statement. I do this all the time.
The syntax is:
INSERT INTO <tablename> (<columns...>)
SELECT ...;
The SELECT must return the same columns that you name in the INSERT clause, in the same order.
If you want an existing row to be updated, but still insert a row if one is missing with the 'cust_abc' varible, then you can use INSERT...ON DUPLICATE KEY UPDATE:
INSERT INTO smgqg_themes (id_member, variable, value)
SELECT id_member, 'cust_abc',
GROUP_CONCAT(`value`
ORDER BY CASE variable
WHEN 'cust_a' THEN 1
WHEN 'cust_b' THEN 2 ELSE 3 END
SEPARATOR '') as v
FROM smgqg_themes
WHERE `id_member` IN (1234, 1235, 1236)
AND `variable` IN ('cust_a', 'cust_b', 'cust_c')
GROUP BY `id_member`
ON DUPLICATE KEY UPDATE value = VALUES(value);
Demo: https://dbfiddle.uk/OPJQ1ZXF

Insert rows on one table based on the values of a field of another table in PHP-MySQL

I would like to ask for a solution on how to insert n rows based on the values of a field in another table.
Consider the following tables:
CREATE TABLE Input (
ID INT AUTO_INCREMENT PRIMARY KEY,
Name VARCHAR(128),
Ticket_Piece INT
);
CREATE TABLE Output (
Ticket_ID INT AUTO_INCREMENT PRIMARY KEY,
Transaction_ID INT,
Ticket_Number VARCHAR(23) UNIQUE,
FOREIGN KEY (Transaction_ID)
REFERENCES Input (ID)
ON UPDATE CASCADE
ON DELETE CASCADE
);
If a row from the Input table has n in the Ticket_Number column, then n rows should be inserted into the Output table, with Ticket_Number having values "ID-1" through "ID-n" (e.g. (4, "D", 5) in Input should result in rows with ticket numbers "4-1" through "4-5" being added to Output). How can rows for Output be generated in a range of numbers based on the Ticket_Piece column using PHP and MySQL?
For example, with the input:
INSERT INTO Input (ID, Name, Ticket_Piece)
VALUES
(1, 'A', 2),
(2, 'B', 1),
(3, 'C', 3)
;
the result should be:
Ticket_ID
Transaction_ID
Ticket_Number
1
1
1-1
2
1
1-2
3
2
2-1
4
3
3-1
5
3
3-2
6
3
3-3
For each row you fetch from the input table, use a for loop to insert multiple rows into the output table.
$res = $pdo->query("SELECT id, ticket_piece FROM Input_Table");
$insert_stmt = $pdo->prepare("INSERT INTO Output_Table (transaction_id, ticket_number) VALUES (:id, :ticket)");
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
$pieces = $row['ticket_piece'];
$id = $row['transaction_id'];
for ($i = 1; $i <= $pieces; $i++) {
$insert_stmt->execute([':id' => $id, ':ticket' => "$id-$i"]);
}
}
A solution in PHP or SQL will likely need to use a loop.
If this comes from the data model rather than business rules (and depending on other factors), a trigger might be a fairly simple option. The trigger body could have a WHILE or other loop to iterate over the ticket piece numbers, and CONCAT to combine the ID and piece number into a ticket number, inserting each in turn.
DELIMITER ;;
CREATE TRIGGER create_ticket_pieces
AFTER INSERT ON Input
FOR EACH ROW
BEGIN
DECLARE piece INT DEFAULT 1;
WHILE piece <= NEW.Ticket_Piece DO
INSERT INTO Output (Transaction_ID, Ticket_Number) Values (NEW.ID, Concat(NEW.ID, '-', piece));
SET piece := piece + 1;
END WHILE;
END;;
DELIMITER ;
An alternative some use is to pregenerate a table of numbers, then join with this table to generate rows:
INSERT INTO Output (Transaction_ID, Ticket_Number)
SELECT Input.ID, Concat(Input.ID, '-', Numbers.Number)
FROM Input
JOIN Numbers ON Numbers.Number <= Input.Ticket_Piece
WHERE ... -- select Input rows
It should be noted that by duplicating information (the transaction ID) in two different columns, the Output table isn't normalized. In particular, it violates 3rd normal form, due to a functional dependency of Transaction_ID on Ticket_Number. The way to resolve this is to leave the transaction ID out of the ticket number field (i.e. Output.Ticket_Number holds only the generated integer ≤ Input.Ticket_Piece). (See: "Third Normal Form: Composite PRIMARY KEY vs System-Generated Surrogate (IDENTITY)")

get the maximum number that extracted from a string using mysql

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.

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
;

Using php and Mysql to Order by highest number and confirmation

Well I have this mysql table with numbers in one column and a confirmation boolean of 0 or 1 and I have about 1,000 rows so it's not something I can do manually but anyways...
I want to sort the row by highest value and grab the names of the first 5 people and put those 5 people in another table on a column and then set them to confirmed and continue until there's no one left in the table that isn't confirmed...
ex:
Name:Rank:Confirm
Bob:5000:0
James:34:0
Josh:59:1
Alex:48:0
Romney:500:0
Rolf:24:0
Hat:51:0
so when you run the code it will do the following:
Squad:Name1:Name2:Name3:Name4:Name5
1:Bob:Romney:Hat:Alex:James
(as you can see Josh was excluded and Rolf was too low)
And since Rolf is alone and there are no one else left, he wont be put into a team and will be left unconfirmed...
I'm not really pro at mysql so I was stumped on this and at most was capable of organizing the whole thing by rank and that's it ._.
edit:
The terrible attempt I had at this:
<?php
$parse = mysql_query("SELECT MAX(rank) AS rank FROM users AND confirm='0'");
mysql_query("Insert into squad (nameone)values($parse)");
mysql_query("Update squad set confirm = '1' where name = $parse");
?>
Assuming confirm will have only either 1 or 0.
CREATE TABLE table2 (id INT PRIMARY KEY AUTO_INCREMENT, name varchar(255));
CREATE PROCEDURE rank()
BEGIN
DECLARE count INT DEFAULT 1;
WHILE count > 0 DO
UPDATE table1 SET Confirm=2 WHERE Confirm=0 ORDER BY Rank DESC LIMIT 5;
INSERT INTO table2 (SELECT GROUP_CONCAT(Name) FROM table1 WHERE Confirm=2);
UPDATE table1 SET Confirm=1 WHERE Confirm=2;
SELECT count(*) FROM table1 WHERE Confirm=0;
END WHILE;
END;
Call the procedure rank() when ever you want
CALL rank();

Categories