Convert Excel formula (PRODUCT) to MySQL query - mysql

wondering if anyone can help me convert the following Excel formula to MySQL query:
Excel: =PRODUCT(1+A1:A7)-1
MySQL data:
id | data
---------------
1 | -1.64
2 | 1.38
3 | 0
4 | 0
5 | -1.52
6 | 0
7 | -1.78
Result should equal -0.207936

This is how you can do it. I sure hope someone can improve on it cause it is awful.
with data(id, val) as(
select 1,-1.64 from dual union all
select 2,1.38 from dual union all
select 3,0.00 from dual union all
select 4,0.00 from dual union all
select 5,-1.52 from dual union all
select 6,0.00 from dual union all
select 7,-1.78 from dual
),
products as(
select Replace(LISTAGG(val+1, '*') within group (order by val),',','.') chain
from data)
SELECT o.val-1 AS product_value
FROM products p
OUTER APPLY(
SELECT *
FROM XMLTABLE('/ROWSET/ROW/*'
passing dbms_xmlgen.getXMLType('SELECT ' || p.chain || ' FROM dual')
COLUMNS val NUMBER PATH '.')
WHERE p.chain IS NOT NULL
) o;
https://dbfiddle.uk/?rdbms=oracle_18&fiddle=c12284b5165da61de8ed418cdf1d6cd7

a prettier solution
with data(id, val) as(
select 1,-1.64 from dual union all
select 2,1.38 from dual union all
select 3,0.00 from dual union all
select 4,0.00 from dual union all
select 5,-1.52 from dual union all
select 6,0.00 from dual union all
select 7,-1.78 from dual
),
neg(val , modifier) as(
select exp(sum(ln(abs(val+1)))), case when mod(count(*),2) = 0 then 1 Else -1 end
from data
where val+1 <0
)
,
pos(val) as (
select exp(sum(ln(val+1)))
from data
where val+1 >=0
)
select (select val*modifier from neg)*(select val from pos)-1 from dual

Related

Insert unique random number with recursive MySQL

I want to populate a table with unique random numbers without using a procedure.
I've tried using this reply to do it but not success.
What I'm trying to do is something like this but validating that numbers are not repeated:
INSERT into table (row1,row2)
WITH RECURSIVE
cte AS ( SELECT 0 num, LPAD(FLOOR(RAND() * 99999999),8,0) random_num
UNION ALL
SELECT num+1, LPAD(FLOOR(RAND() * 99999999),8,0) random_num
FROM cte WHERE num < 100000-1)
SELECT random_num, null
FROM cte;
In the example above, I am able to generate random values and insert them but without validating that the numbers are not repeated.
I have tried to do this:
INSERT into table (row1,row2)
WITH RECURSIVE
cte AS ( SELECT 0 num, LPAD(FLOOR(RAND() * 99999999),8,0) random_num
UNION ALL
SELECT num+1, LPAD(FLOOR(RAND() * 99999999),8,0) random_num
FROM cte WHERE num < 100000-1 AND random_num NOT IN (SELECT random_num FROM cte WHERE random_num IS NOT NULL))
SELECT random_num, null
FROM cte;
but the condition AND random_num NOT IN (SELECT random_num FROM cte WHERE random_num IS NOT NULL) in the where case, causes an SQL Error [4008] [HY000]: Restrictions imposed on recursive definitions are violated for table 'cte'
Any suggestions of how to do it? thank you!.
This could be an option. Generate all possible values, sort randomly and take desired number of entries.
CREATE TABLE random_data (
row1 INT PRIMARY KEY AUTO_INCREMENT,
row2 VARCHAR(10) NOT NULL,
UNIQUE KEY _Idx1 ( row2 )
);
INSERT INTO random_data (row2)
SELECT LPAD(num, 8, 0)
FROM (
SELECT h * 10000000 + g * 1000000 + f * 100000 + e * 10000 + d * 1000 + c * 100 + b * 10 + a AS num
FROM (SELECT 0 a UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) ta
JOIN (SELECT 0 b UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) tb
JOIN (SELECT 0 c UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) tc
JOIN (SELECT 0 d UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) td
JOIN (SELECT 0 e UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) te
JOIN (SELECT 0 f UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) tf
JOIN (SELECT 0 g UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) tg
JOIN (SELECT 0 h UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) th
) n
ORDER BY RAND()
LIMIT 100000;
If you have a table - any table - with e.g. 100 rows then you can generate million distinct random numbers between 0 and 99999999 as follows:
select distinct floor(rand() * 100000000)
from t as t0, t as t1, t as t2
limit 1000000
Note that because of distinct you will need to generate a bigger number of rows so that you get desired number of rows after distinct.

Laravel Query Builder raw query with two FROM statements

my apologies if this question sounds a little boring for someone I really turn here because I have already tried to solve my problem in different ways and I have not been able to obtain good results.
I am trying to convert a MySQL query to Laravel Query Builder, I show you the query:
SELECT DAY(AAA.fecha_hora_entrada) as DAY, IFNULL(BBB.SALES, 0) SALES, IFNULL(BBB.NET, 0) NET
FROM (
SELECT fecha_hora_entrada
FROM (
SELECT MAKEDATE(YEAR(NOW()), 1) +
INTERVAL (MONTH(NOW()) - 1) MONTH +
INTERVAL daynum DAY fecha_hora_entrada
FROM (
SELECT t * 10 + u daynum
FROM (SELECT 0 t UNION SELECT 1 UNION SELECT 2 UNION SELECT 3) A,
(SELECT 0 u UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) B
ORDER BY daynum
) AS AA
) AS AA
WHERE MONTH(fecha_hora_entrada) = MONTH(NOW())
) AS AAA
LEFT JOIN (SELECT (DATE(fecha_hora_entrada)) AS fecha_hora_entrada, (SUM(neto)) AS NET, (COUNT(neto)) AS SALES
FROM t_derivados
GROUP BY DATE(fecha_hora_entrada)) BBB
ON AAA.fecha_hora_entrada = BBB.fecha_hora_entrada;
What I have tried so far is the following. So far I have managed to pass part of the MySQL query to Query Builder
public function salesYear()
{
return DB::table(DB::table(DB::table(DB::table(DB::table(null, null)
->selectRaw('0 t UNION SELECT 2 UNION SELECT 3'),'A')
->orderBy('daynum')
->selectRaw('t * 10 + u daynum'), 'AA')
->selectRaw('MAKEDATE(YEAR(NOW()), 1) + INTERVAL (MONTH(NOW()) - 1) MONTH + INTERVAL daynum DAY fecha_hora_entrada'), 'AA')
->addSelect('fecha_hora_entrada')
->whereRaw('MONTH(fecha_hora_entrada) = MONTH(NOW())'), 'AAA')
->selectSub('DAY(AAA.fecha_hora_entrada)', 'DAY')
->selectSub('IFNULL(BBB.SALES, 0)', 'SALES')
->selectSub('IFNULL(BBB.NET, 0)', 'NET')
->leftJoinSub(DB::table('t_derivados')
->selectSub('DATE(fecha_hora_entrada)', 'fecha_hora_entrada')
->selectSub('SUM(neto)', 'NET')
->selectSub('COUNT(neto)', 'SALES')
->groupBy(DB::raw('DATE(fecha_hora_entrada)')), 'BBB', 'AAA.fecha_hora_entrada', 'BBB.fecha_hora_entrada')
->toSql();
}
I know it's a bit boring to read everything .. but let's focus on the next sub-sentence:
SELECT t * 10 + u daynum
FROM (SELECT 0 t UNION SELECT 1 UNION SELECT 2 UNION SELECT 3) A,
(SELECT 0 u UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) B
ORDER BY daynum
...
The truth is I have not found a way to add the second FROM to Query Builder
SELECT 0 u UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) B
...
Someone expert on the subject could help me please. Thank you very much in advance

MYSQL select count of distinct values from comma separated

How can I select the distinct values and relative count from data that is stored as comma separated values in MySql?
This is how the data in the table looks like:
"Category" "Values"
"red" "D,A,D,D"
"yellow" "A,A,D,D"
Desire Output:
"red" "D" "3"
"red" "A" "1"
"yellow" "D" "2"
"yellow" "A" "2"
Can you think of a good way in mysql to get output like the way I've done?
Thank's in advance.
Using a derived table you can split the string into rows
SELECT `Category`, SUBSTRING_INDEX(SUBSTRING_INDEX(t.`Values`, ',', n.n), ',', -1) `value`
FROM table1 t CROSS JOIN
(
SELECT a.N + b.N * 10 + 1 n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
ORDER BY n
) n
WHERE n.n <= 1 + (LENGTH(t.`Values`) - LENGTH(REPLACE(t.`Values`, ',', '')))
Then just GROUP BY and COUNT
DEMO
SELECT `Category`, `value`, COUNT(*) as cnt
FROM (
<previous query>
) T
GROUP BY `Category`, `value`

I tried this code in mysql but it was row operation,how to insert column wise data

MySQL stored procedures can be used to split the string, the following details for your usage of MySQL stored procedures, for your reference learning purposes.
Existing string, such as Apple, banana, orange, pears, grape, it should follow the comma (,) is divided into:
apple
banana
orange
pears
grape
Where in () method can then query.
1, the specific function:
Function: func_split_TotalLength the
DELIMITER $ $
DROP function IF EXISTS `func_split_TotalLength` $ $
CREATE DEFINER = `root` # `%` FUNCTION `func_split_TotalLength`
(F_string varchar (1000), f_delimiter varchar (5)) RETURNS int (11)
BEGIN
return 1 + (length (f_string) - length (replace (f_string, f_delimiter,'')));
END $ $
DELIMITER;
Function: func_split
DELIMITER $ $
DROP function IF EXISTS `func_split` $ $
CREATE DEFINER = `root` # `%` FUNCTION `func_split`
(F_string varchar (1000), f_delimiter varchar (5), f_order int) RETURNS varchar (255) CHARSET utf8
BEGIN
declare result varchar (255) default '';
set result = reverse (substring_index (reverse (substring_index (f_string, f_delimiter, f_order)), f_delimiter, 1));
return result;
END $ $
DELIMITER;
Stored procedure: SplitString
DELIMITER $ $
DROP PROCEDURE IF EXISTS `splitString` $ $
CREATE Procedure the `SplitString`
(IN f_string varchar (1000), IN f_delimiter varchar (5))
BEGIN
declare cnt int default 0;
declare i int default 0;
set cnt = func_split_TotalLength (f_string, f_delimiter);
DROP TABLE IF EXISTS `tmp_split`;
create temporary table `tmp_split` (`status` varchar (128) not null) DEFAULT CHARSET = utf8;
while i <cnt
do
set i = i + 1;
insert into tmp_split (`status`) values ??(func_split (f_string, f_delimiter, i));
end while;
END $ $
DELIMITER;
2, the test will be successful segmentation
call splitString ("apple, banana, orange, pears, grape", ",");
select * from tmp_split;
The results are splitting success:
mysql> call splitString ("apple, banana, orange, pears, grape", ",");
select * from tmp_split;
Query OK, 1 row affected but i need to insert by column wise data...,please help me
Well I would use an other approach to this problem, to get the result in table form:
Simpler approach without a stored procedure
Assuming you have a table example with two columns: id and col1 like so:
CREATE TABLE example (
id INT NOT NULL PRIMARY KEY,
col1 VARCHAR(1000)
);
and those values
id | col
-----------------------------
1 | abcd,efgh,ijkl,ghjy,sdfg
2 | some other text
and you want to get the comma separated values from col into a column with one value at the time for the id = 1. Then I would use following sql:
SELECT
id,
SUBSTRING_INDEX(SUBSTRING_INDEX(e.col, ',', t.n), ',', -1) value
FROM example e CROSS JOIN (
SELECT
1 + a.N + b.N * 10 + c.N * 100 AS n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) c
ORDER BY n
) t
WHERE
e.id = 1
AND
t.n <= (1 + LENGTH(e.col) - LENGTH(REPLACE(e.col, ',', '')));
It's the same procedure as in the stored procedure. You can use this result easily in joins or other operations.
See this Demo
First part assuming, a stored procedure was needed.
stored procedure
no other function needed
no explicit temp table
works up to 1000 items, easily exptensible
Code:
DROP procedure splitString;
DELIMITER $ $
CREATE procedure splitString(IN f_string VARCHAR(1000), IN f_delimiter VARCHAR(5))
BEGIN
SELECT SUBSTRING_INDEX(
SUBSTRING_INDEX(h.haystack, f_delimiter, t.n
), f_delimiter, -1) as value
FROM (SELECT f_string as haystack) h
CROSS JOIN (
SELECT
1 + a.N + b.N * 10 + c.N * 100 AS n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) c
ORDER BY n
) t
WHERE t.n <=
(
1
+ (
CHAR_LENGTH(f_string)
-
CHAR_LENGTH(REPLACE(f_string, f_delimiter, ''))
)
/ CHAR_LENGTH(f_delimiter)
);
END;
$ $
DELIMITER ;
Explanation
1. Generating a list of numbers from 1 to 1000
The most inner SELECT
SELECT
1 + a.N + b.N * 10 + c.N * 100 AS n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) c
ORDER BY n
does not more than creating a list with the numbers from 1 to 1000. If you need more then you should see, how it's done. Add a line with those union all and call this d, add + d.N * 1000 to the sum.
2. Performing the split and getting just one element per row
We use this list to do the split of your func_split in the form of (a bit more readable statements)
SELECT SUBSTRING_INDEX(
SUBSTRING_INDEX(h.haystack, f_delimiter, 1
), f_delimiter, -1) as value
FROM (SELECT f_string as haystack) h
SELECT SUBSTRING_INDEX(
SUBSTRING_INDEX(h.haystack, f_delimiter, 2
), f_delimiter, -1) as value
FROM (SELECT f_string as haystack) h
SELECT SUBSTRING_INDEX(
SUBSTRING_INDEX(h.haystack, f_delimiter, 3
), f_delimiter, -1) as value
FROM (SELECT f_string as haystack) h
that extract the first, second, third, ... substring, separated with the f_delimiter.
Getting the number of elements
Our WHERE clause is all but self-explanatory:
-- because we begin by 1 we've got to include the upper limit
WHERE t.n <=
(
1 -- we have one comma less than parts
+ (
-- count how many characters (not bytes) we lose by the replace operation
CHAR_LENGTH(f_string)
-
CHAR_LENGTH(REPLACE(f_string, f_delimiter, ''))
)
-- and divide this by the count of characters in the delimiter string
/ CHAR_LENGTH(f_delimiter)
);
Note
LENGTH() instead of CHAR_LENGTH() would do too, because the result of the division would be the same, but I like to do it right :-)
** Check of function**
You can check the result with
CALL splitString('apple, bananas, grape, ananas, pears', ', ');
it returns
value
-----
apple
bananas
grape
ananas
pears

Mysql Query to find distinct characters in a string

Is there a way to write recursive query in mysql. Equivalent of connect by (level or Prior) in oracle. I searched google as well as stackoverflow and there is no direct eqivalent. But is there any work around to get it.
I have a string, i have to iterate through individual characters in the string and print only the distinct characters of the string.
Input:
recursive
Output:
recusiv
I came up with this "simple" solution.
Functions used:
GROUP_CONCAT: http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_group-concat
SUBSTRING http://dev.mysql.com/doc/refman/5.0/en/string-functions.html#function_substring
Live demo: http://www.sqlfiddle.com/#!2/d41d8/19186
SELECT
GROUP_CONCAT( chars.c SEPARATOR '') AS allchars
FROM (
SELECT DISTINCT
SUBSTRING( str.str, pos.pos, 1 ) AS c
FROM
( SELECT x1.x + x2.x*10 AS pos
FROM
( SELECT 0 AS x UNION ALL
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4 UNION ALL
SELECT 5 UNION ALL
SELECT 6 UNION ALL
SELECT 7 UNION ALL
SELECT 8 UNION ALL
SELECT 9
) AS x1
INNER JOIN
( SELECT 0 AS x UNION ALL
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4 UNION ALL
SELECT 5 UNION ALL
SELECT 6 UNION ALL
SELECT 7 UNION ALL
SELECT 8 UNION ALL
SELECT 9
) AS x2
) AS pos
INNER JOIN
( SELECT 'recursive' AS str UNION ALL
SELECT 'XYZ'
) AS str
) AS chars