using a user variable in where clause without subquery - mysql

I have the following scenario:
+-----------+
| my_column |
+-----------+
| A |
| B |
| C |
| D |
| E |
+-----------+
I have simplified my_function bellow for this example;
DROP FUNCTION IF EXISTS my_function;
CREATE FUNCTION my_function(
phrase VARCHAR(255),
column_value VARCHAR(255)
)
RETURNS FLOAT(20,10)
READS SQL DATA
SQL SECURITY INVOKER
BEGIN
IF(column_value = 'A') THEN RETURN 1.0000000000;
ELSEIF(column_value = 'B') THEN RETURN 0.7500000000;
ELSEIF(column_value = 'C') THEN RETURN 0.7500000000;
ELSEIF(column_value = 'D') THEN RETURN 0.5000000000;
ELSEIF(column_value = 'E') THEN RETURN 0.0000000000;
END IF;
END;
Here is my main stored procedure:
DROP PROCEDURE IF EXISTS my_procedure;
CREATE PROCEDURE my_procedure(
IN phrase VARCHAR(255)
)
READS SQL DATA
SQL SECURITY INVOKER
BEGIN
SET #phrase = phrase;
SET #query = "
SELECT
my_column,
#score_var := my_function(?,my_column) as score,
#score_var
FROM my_table
ORDER BY score DESC;
";
PREPARE stmt FROM #query;
EXECUTE stmt USING #phrase;
DEALLOCATE PREPARE stmt;
END;
Now if I call my_procedure
call my_procedure('anything');
The result is:
+-----------+--------------+------------+
| my_column | score | #score_var |
+-----------+--------------+------------+
| A | 1.0000000000 | 1 |
| B | 0.7500000000 | 0.75 |
| C | 0.7500000000 | 0.75 |
| D | 0.5000000000 | 0.5 |
| E | 0.0000000000 | 0 |
+-----------+--------------+------------+
But if I add WHERE #score_var > 0.5 inside of the query in my_procedure, the result is:
+-----------+--------------+------------+
| my_column | score | #score_var |
+-----------+--------------+------------+
| A | 1.0000000000 | 1 |
| C | 0.7500000000 | 0.75 |
| E | 0.0000000000 | 0 |
+-----------+--------------+------------+
Expected result ´> 0.5´:
+-----------+--------------+------------+
| my_column | score | #score_var |
+-----------+--------------+------------+
| A | 1.0000000000 | 1 |
| B | 0.7500000000 | 0.75 |
| C | 0.7500000000 | 0.75 |
+-----------+--------------+------------+
I have seen some answers that use a subquery, but my question is: can (in this case) I not use a subquery?
Alternative approaches are also welcome.

When you read and write a user variable in the same statement, the behavior is documented as "undocumented". In other words the result is unpredictable unlesss you read and understand the source code of your MySQL version.
However - I think you are complicating things here unnecessarily. I don't see a reason to use a prepared statement, neither to use u user variable. Your procedure body could be just:
SELECT
my_column,
my_function(phrase, my_column) as score,
FROM my_table
HAVING score > 0.5
ORDER BY score DESC
Also your function could be written with less code duplicatin:
RETURN
CASE column_value
WHEN 'A' THEN 1.0000000000
WHEN 'B' THEN 0.7500000000
WHEN 'C' THEN 0.7500000000
WHEN 'D' THEN 0.5000000000
WHEN 'E' THEN 0.0000000000
END

Related

Using code to increment values in a column

I am trying to use a simple code to increment only the values in the “chat_id” column of a table.
For the table lz_chat_archive_dup1, the column “chat_id” is has empty strings (no values). This is the partial excerpt of the table :
mysql> select chat_id, fullname from lz_chat_archive_dup1 LIMIT 5;
+---------+--------------+
| chat_id | fullname |
+---------+--------------+
| | Yw |
| | Shah |
| | Sunny Duhel |
| | Leong Zi Yin |
| | Mohd Nasir |
+---------+--------------+
5 rows in set (0.00 sec)
I tried to insert a value for the name “Yw” like this and it worked :
mysql> UPDATE lz_chat_archive_dup1 SET chat_id = '383933' where fullname = 'Yw';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
So now the table is like this :
mysql> select chat_id, fullname from lz_chat_archive_dup1 LIMIT 5;
+---------+--------------+
| chat_id | fullname |
+---------+--------------+
| 383933 | Yw |
| | Shah |
| | Sunny Duhel |
| | Leong Zi Yin |
| | Mohd Nasir |
+---------+--------------+
5 rows in set (0.00 sec)
However, the number of rows in this table is 2589, and for me to do it one by one is tedious and time consuming :
mysql> select count(*) from lz_chat_archive_dup1;
+----------+
| count(*) |
+----------+
| 2589 |
+----------+
1 row in set (0.00 sec)
I thought I could use a code something like this to update/increment only that one column, but I don’t think this is the correct syntax for MySQL. Can you please help to correct the code to customize it to work in my situation :
DECLARE #counter int
SET #counter = 383933
UPDATE #lz_chat_archive_dup1
SET #counter = counter = #counter + 1
So with this code, what I am trying to achieve is increment the chat_id column so that the next value is always 1 integer higher than the previous one. So the first row is 383933, the next one should be 383934, 383935, 383936,…etc etc.
The table has > 2000 rows, so this is an excerpt of it :
mysql> select time, endtime, chat_id from lz_chat_archive_dup1 LIMIT 20;
+------------+------------+---------+
| time | endtime | chat_id |
+------------+------------+---------+
| 1594948770 | 1594948928 | 383933 |
| 1594950285 | 1594950542 | |
| 1594950708 | 1594951085 | |
| 1594953554 | 1594955581 | |
| 1594955956 | 1594956551 | |
| 1595215646 | 1595218410 | |
| 1595215648 | 1595216044 | |
| 1595216110 | 1595216138 | |
| 1595220816 | 1595221144 | |
| 1595221046 | 1595221584 | |
| 1595221448 | 1595221505 | |
| 1595222302 | 1595222653 | |
| 1595236468 | 1595236848 | |
| 1595236954 | 1595237033 | |
| 1595293418 | 1595293589 | |
| 1595303280 | 1595304388 | |
| 1595303410 | 1595303822 | |
| 1595303675 | 1595303986 | |
| 1595304153 | 1595306613 | |
| 1595304878 | 1595304995 | |
+------------+------------+---------+
20 rows in set (0.00 sec)
mysql>
Here is an approach using a user variable:
set #rn = 383933;
update #lz_chat_archive_dup1
set chat_id = (select #rn := #rn + 1)
order by name;
This will assign an incrementingn number to each row, following the alphabetical order of name. If there are ties, it is undefined which name will get which number (a reason why you should have a primary key column in your table).
Assuming the names are unique, you could use a join:
update lz_chat_archive_dup1 cad join
(select cad2.*, row_number() over () as seqnum
from lz_chat_archive_dup1 cad2
) cad2
on cad2.name = cad.name
set count = seqnum + 383933;
I think this might be the recommended approach in MySQL 8+. (The statement on the deprecation of variables is a little vague on whether it would apply to UPDATE.)
You can also use variables. The problem with your statement is:
SET #counter = counter = #counter + 1
This is not even setting the column in the table! It is setting a variable. Use := to set parameters. And I strongly recommend parentheses. So, you can do:
DECLARE #counter int;
SET #counter = 383933;
UPDATE #lz_chat_archive_dup1
SET counter = (#counter := #counter + 1);
Or, in a single statement:
UPDATE #lz_chat_archive_dup1 cad CROSS JOIN
(SELECT #counter := 383933) params
SET cad.counter = (#counter := #counter + 1);
If you can live with numbers starting from 1, following the alphabetic order of your fullname column, you can try with a helper table to run the update:
CREATE TABLE updtab
AS
SELECT
ROW_NUMBER() OVER(ORDER BY fullname) AS chat_id
, fullname
FROM lz_chat_archive_dup1;
Then , run the update:
UPDATE lz_chat_archive_dup1
SET chat_id = (
SELECT chat_id
FROM updtab
WHERE updtab.fullname=lz_chat_archive_dup1.fullname
)
;

How to inner join result of stored function?

I am searching for all day with no success so I decided to ask.
I will very simplify structure as much as possible to ask for essence.
I have function:
mysql> SELECT set_of_ids_to_names('1:2:3:4:5', ':') AS `res`;
+-------------------------------+
| res |
+-------------------------------+
| NameA:NameB:NameC:NameD:NameE |
+-------------------------------+
I have table:
mysql> SELECT * FROM `tbl_tool`;
+----+-----------------+---------+
| ID | Tool | ID_name |
+----+-----------------+---------+
| 1 | Tool_1 | 1:2:3:4 |
| 2 | Tool_2 | 2:4:5 |
| 3 | Tool_3 | 4:5 |
| 4 | Tool_4 | 3 |
+----+-----------------+---------+
The result I would like to achieve is to have view called 'v_tool' so once I selet it I get:
mysql> SELECT * FROM `v_tool`;
+----+-----------------+-------------------------+
| ID | Tool | Name |
+----+-----------------+-------------------------+
| 1 | Tool_1 | NameA:NameB:NameC:NameD |
| 2 | Tool_2 | NameB:NameD:NameE |
| 3 | Tool_3 | NameD:NameE |
| 4 | Tool_4 | NameC |
+----+-----------------+-------------------------+
This what I tried is:
SELECT `tbl_tool`.`ID`, `tbl_tool`.`Tool`, `Name` FROM `tbl_tool`
INNER JOIN (SELECT set_of_ids_to_names((SELECT `ID` FROM `tbl_tool` WHERE `ID` = `tbl_tool`.`ID`), ':') AS `Name`) AS `aaa`
I know that it is wrong, but I just could not find idea how to pass proper value to function 'set_of_ids_to_names'.
Big thank you in advance.
Looking at the original function call you made:
SELECT set_of_ids_to_names('1:2:3:4:5', ':') AS `res`
It is important to note the function call appears in the SELECT clause, not in the FROM clause.
This suggests set_of_ids_to_names is a scalar function, not a table-valued function.
When querying table tbl_tool, you can do the exact same thing: call set_of_ids_to_names in the SELECT clause.
SELECT Tool, set_of_ids_to_names(ID_name, ':') AS Name
FROM tbl_tool
For table-valued functions, the situation is different of course. SQL Server has CROSS APPLY for that, in MySQL you'd probably have to join the table with a subquery encapsulating the function call.

How to delete rows inserted unintentionally with a given column value

I am a beginner with MySQL. I made a stored procedure to insert 1,000 random names from a table. It has 3 fields with num, course_name and grade. num is foreign key--as this was for a test purpose, I just kept incremented the num only. So I didn't mark it as a PRIMARY KEY/AUTO_INCREMENT. I called the procedure, and it inserted 1,000 random names in the table. Unknowingly, I called the procedure again, and stopped it after some time. Then the table got 500 more entries after that previous 1000 entries. I wanted to delete the rows that created after the second procedure call.
Below is my statements in a stored procedure: (course_name and grade_details are additional tables with course names and grades.)
DELIMITER //
CREATE PROCEDURE course_grade(IN name_entries int)
BEGIN
DECLARE i int DEFAULT 0;
DECLARE course varchar(20);
DECLARE crs_grade char(1);
gradeloop : LOOP
SELECT name INTO course FROM course_name ORDER BY rand() LIMIT 1;
SELECT grade INTO crs_grade FROM grade_details ORDER BY rand() LIMIT 1;
INSERT INTO tbl_grade(fk_int_roll_no,vchr_course,vchr_grade)
VALUES (i+1,course,crs_grade);
SET i = i + 1;
IF (i=name_entries)
THEN LEAVE gradeloop;
END IF;
END LOOP gradeloop;
SELECT COUNT(*) FROM tbl_grade;
END //
DELIMITER ;
And my table is like :
+----------------+-------------+------------+
| fk_int_roll_no | vchr_course | vchr_grade |
+----------------+-------------+------------+
| 1 | AE | A |
| 2 | MECH | B |
| 3 | EC | A |
| . | .... | . |
| . | .... | . |
| 1000 | IT | E |
| 1 | MARINE | F |
| 2 | BIOTECH | F |
| . | .... | . |
| . | .... | . |
| . | .... | . |
| . | .... | . |
| 500 | RM | A |
+----------------+-------------+------------+
Wanted to delete the last 1 to 500 rows made by mistake!

Group and make row become header

I have this data in my MySQL table fact
+------------+-------+--------+
| timestamp | code | unique |
+------------+-------+--------+
| 1416157200 | 7E001 | 100 |
| 1416157200 | 7E002 | 200 |
| 1416243600 | 7E001 | 100 |
| 1416243600 | 7E002 | 200 |
+------------+-------+--------+
I want to get this result
+-------+------------+------------+
| code | 2014-11-18 | 2014-11-17 |
+-------+------------+------------+
| 7E001 | 100 | 100 |
| 7E002 | 200 | 200 |
+-------+------------+------------+
I use this query select code, from_unixtime(timestamp, '%Y-%m-%d') as date, unique from fact; to produce this result and have no idea to aggregate this result became above desirable result.
+-------+------------+--------+
| code | date | unique |
+-------+------------+--------+
| 7E001 | 2014-11-17 | 100 |
| 7E002 | 2014-11-17 | 200 |
| 7E001 | 2014-11-18 | 100 |
| 7E002 | 2014-11-18 | 200 |
+-------+------------+--------+
Is it possible? And how to achieve that?
PS: Please help me editing the title to be more descriptive since I can't explain this problem in such short
Case based aggregation can be used
As the dates can be many, you need to use dynamic SQL
select code,
Max( case when date=1416157200 then unique end) as '2014-11-17' ,
Max( case when date=1416243600 t hen unique end) as '2014-11-18'
from fact
Group by date
Yes it is possible creating a dynamic query using that values as columns.
I use some code from article describing a way to get a pivot table in MySQL:
mysql> call get_fact_pivot_table();
+-------+------------+------------+
| code | 2014-11-16 | 2014-11-17 |
+-------+------------+------------+
| 7E001 | 100 | 100 |
| 7E002 | 200 | 200 |
+-------+------------+------------+
2 rows in set (0,06 sec)
The procedure get_fact_pivot_table contains a cursor to make a dynamic query, you can change it as you need:
DELIMITER $$
DROP PROCEDURE if exists get_fact_pivot_table$$
CREATE PROCEDURE `get_fact_pivot_table`()
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE p_sql text;
DECLARE p_col_date VARCHAR(20);
DECLARE p_col_value int;
DECLARE c_columns cursor FOR
select distinct from_unixtime(timestamp, '%Y-%m-%d') as `col_date` ,
`timestamp` as col_value
from fact;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
SET p_sql = 'select f.code ';
OPEN c_columns;
read_loop: LOOP
FETCH c_columns INTO p_col_date, p_col_value;
IF done THEN
LEAVE read_loop;
END IF;
SET p_sql = concat(p_sql,
', (select c.`unique` from fact as c where
c.`timestamp` = ', p_col_value ,'
and c.code = f.code limit 1) as `',p_col_date,'` ');
END LOOP;
SET #SQL = concat(p_sql,' from fact as f group by f.code');
close c_columns;
PREPARE stmt1 FROM #SQL;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
END$$
delimiter ;

MYSQL Function with a calcuation based on data in a db column

I Have a table that is a lookup for scoring points based on Place (P) and Number of Racers(R)
and scoring formats indicated by points_id. Two cases are shown in the table.
Sometime the points are determined directly by the values of P and N as in points_id =3
other times they are most easily determined by a simple calculation shown in the pts_calc column.
|points_id| P | N |points|pts_calc|
| 1 | 0 | 0 | NULL | pin |
| 1 |DNS| 0 | NULL | nin+1 |
| 3 | 1 | 0 |102.00| NULL |
| 3 | 2 | 0 | 98.00| NULL |
| 3 | 3 | 0 | 96.00| NULL |
| 3 | 4 | 0 | 93.00| NULL |
| 3 | 5 | 0 | 91.00| NULL |
| 3 | 6 | 0 | 89.00| NULL |
| 3 |DNF| 0 | 85.00| NULL |
I was hoping to create a function that returned the points from the three input variables.
points_id, P, N.
Below is what I tried.
CREATE FUNCTION POINTS(pid INT,pin VARCHAR(3),nin INT)
RETURNS DEC(6,2)
DETERMINISTIC
BEGIN
DECLARE pts DECIMAL(6,2);
DECLARE pcalc VARCHAR(20);
SELECT points,pts_calc INTO pts,pcalc FROM scoring_points WHERE points_id=pid AND (P=pin OR P='0') AND (N=nin or N=0);
IF(pts IS NULL) THEN
SET #s= CONCAT('SET pts = ',pcalc);
PREPARE stmt FROM #s;
EXECUTE stmt;
END IF;
RETURN pts;
END
But i got this error.
1336 - Dynamic SQL is not allowed in stored function or trigger
Further research show the Prepare statement is not allowed in functions only but procedures.
I was hoping to do something like;
SELECT SUM(Points(pid,place,numb)) FROM t1 GROUP BY racer.id
But onto plan B (tbd) unless someone has great idea.
I think you might fare better having three numeric columns instead of your pts_calc column:
cPIN - coefficient of pin term
cNIN - coefficient of nin term
cnst - constant term
Your function could then perform:
SELECT IFNULL(points, cPIN*pin + cNIN*nin + cnst) INTO pts
FROM scoring_points
WHERE ...
Depending on your needs, you might even be able to get rid of the points column by just using cnst and leaving the other two equal to 0.