How do I convert a number enum column to tinyint? - mysql

Developing in Laravel 5.7, using a MySQL database. On a couple of my database columns I have the type of enum - didn't do my research and made the enum full of numbers (0-2, or 0-3). After reading the pros and cons, I want to move away from enums and convert them to tinyints.
What's the best way to change the type of the column in my table to tinyint and convert the strings '0','1','2','3' to tinyint?
I don't really want to lose my data in the process.
https://laravel.com/docs/5.7/migrations#modifying-columns has information about modifying columns, however it does not support enums:
Only the following column types can be "changed": bigInteger, binary, boolean, date, dateTime, dateTimeTz, decimal, integer, json, longText, mediumText, smallInteger, string, text, time, unsignedBigInteger, unsignedInteger and unsignedSmallInteger.

To be on a safe side I'd do this using temporary column;
ALTER TABLE tbl ADD COLUMN _temp_col CHAR(1) COLLATE 'latin1_general_ci'; -- CHAR(1) is OK if you only have numeric ENUMs
UPDATE tbl SET _temp_col = col; -- ENUM values would be copied as is
ALTER TABLE tbl MODIFY COLUMN col TINYINT(1) UNSIGNED;
UPDATE tbl SET col = _temp_col; -- Values would be auto-converted to ints
ALTER TABLE tbl DROP COLUMN _temp_col;

Experimenting with MySQL-8.0 generated the following conversion.
The ALTER TABLE seems to convert 'x' -> x+1. So I guess that be altered per the subsequent UPDATE below
select version();
| version() |
| :-------- |
| 8.0.13 |
create table x (y enum ('0','1','2','3') );
✓
insert into x values ('1'),('0'),('2'),('3')
✓
select * from x
| y |
| :- |
| 1 |
| 0 |
| 2 |
| 3 |
alter table x modify y tinyint unsigned;
✓
select * from x;
| y |
| -: |
| 2 |
| 1 |
| 3 |
| 4 |
update x set y=y-1
✓
select * from x
| y |
| -: |
| 1 |
| 0 |
| 2 |
| 3 |
db<>fiddle here

Related

After union (create view) table tinyint(1) changed to be without display width just tinyint

I have one table. For example, we can call it mytable. This table for example has one column and this column is a boolean type, let's call this column mybool. When i want to create view like this: CREATE VIEW union_mytable AS SELECT * FROM mytable UNION ALL SELECT * FROM mytable; then type has changed tinyint where previously was
tinyint(1)
mytable:
mysql> DESCRIBE mytable;
+--------+------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+------------+------+-----+---------+-------+
| mybool | tinyint(1) | NO | | 0 | |
+--------+------------+------+-----+---------+-------+
union_mytable:
mysql> DESCRIBE union_mytable;
+--------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+---------+------+-----+---------+-------+
| mybool | tinyint | NO | | 0 | |
+--------+---------+------+-----+---------+-------+
steps to recreate DB:
create table mytable (
mybool boolean not null default 0
);
insert into mytable () values ();
insert into mytable () values ();
describe mytable;
create view union_mytable select * from mytable union all select * from mytable;
describe union_mytable;
Database is MYSQL
It is possible to set this somehow to not cast this type manually to boolean?
Why this type is changing when union tables columns are the same?

JSON merge arrays and UNIQUE or DISTINCT

In my MySQL database I have a table features with this structure:
+-------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| val | json | NO | | NULL | |
+-------+---------+------+-----+---------+----------------+
When I select everything from it, like so, I get the following:
mysql> select * from features;
+----+----------------------------------+
| id | val |
+----+----------------------------------+
| 1 | ["apple", "banana", "orange"] |
| 2 | ["apple", "orange", "pineapple"] |
| 3 | ["orange", "banana"] |
| 4 | [] |
+----+----------------------------------+
The value in the val column should always be an array of strings. This array can have any length (>= 0).
The question is:
How can I select all those array values in a single result set, not repeated? So that I get this result and use it in PHP:
+------------+
| arr_values |
+------------+
| apple |
| banana |
| orange |
| pineapple |
+------------+
The only constraint to solve this is that it should be compatible with MySQL v5.7.
If maximal amount of elements per JSON value is limited then (an example for not more than 10 elements)
SELECT DISTINCT JSON_EXTRACT(features.val, CONCAT('$[', numbers.num, ']')) arr_values
FROM features, ( SELECT 0 num 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 ) numbers
HAVING arr_values IS NOT NULL;
If really the max array size is limited nevertheless (for example, 1000000) then it is possible to generate the dynamic table with proper amount of number. But stored procedure with iterational parsing and temporary table is more safe solution.
UPDATE.
Non-limited solution (stored procedure).
CREATE PROCEDURE get_unique ()
BEGIN
CREATE TEMPORARY TABLE temp (val JSON);
INSERT INTO temp
SELECT val
FROM features;
CREATE TEMPORARY TABLE tmp (val JSON);
cycle: LOOP
INSERT IGNORE INTO tmp
SELECT JSON_EXTRACT(val, '$[0]')
FROM temp;
DELETE
FROM temp
WHERE JSON_EXTRACT(val, '$[1]') IS NULL;
UPDATE temp
SET val = JSON_REMOVE(val, '$[0]');
IF 0 = (SELECT COUNT(*)
FROM temp) THEN
LEAVE cycle;
END IF;
END LOOP;
DROP TEMPORARY TABLE temp;
SELECT DISTINCT *
FROM tmp
WHERE val IS NOT NULL;
DROP TEMPORARY TABLE tmp;
END
fiddle

Unexpected behaviour INT NOT NULL DEFAULT -1

I rarely work with negative numbers (except personal finances) so perhaps that's why there's a gap in my knowledge here...
Consider the following, prompted by a response to a question asked by another user in SO (How to achieve default value if column value is NULL?):
-- Mysql Version 5.5.16
-- sql_mode = ''
DROP TABLE prices;
CREATE TABLE prices (price_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,price INT SIGNED NOT NULL DEFAULT -1);
INSERT INTO prices (price) VALUES (' '),(''),(NULL);
INSERT INTO prices (price_id) VALUES (NULL);
SELECT * FROM prices;
Expected output:
+----------+-------+
| price_id | price |
+----------+-------+
| 1 | -1 |
| 2 | -1 |
| 3 | -1 |
| 4 | -1 |
+----------+-------+
Actual output:
+----------+-------+
| price_id | price |
+----------+-------+
| 1 | 0 |
| 2 | 0 |
| 3 | 0 |
| 4 | -1 |
+----------+-------+
Why?
Intermediate answer: In a nutshell, it seems that if you want to be sure of inserting the default value (when sql_mode is not set), either omit the column from the INSERT or explicitly INSERT a DEFAULT value, i.e.: INSERT INTO prices (price) VALUES(DEFAULT); To me, this goes against the spirit of a DEFAULT value !?!?
Seems like:
a.) If you provide a NULL value to a not null numeric field (not autoincrementing), the default is zero.
b.) If you dont provide a value (as in the last row), you use the given default value (-1)
http://dev.mysql.com/doc/refman/5.0/en/data-type-defaults.html
If you use Mysql STRICT MODE then the result could be different.
Currently you are providing a value NULL, the server tries to map this value to the closest INT value. The server is not using the default value of -1 because it is taking NULL as a valid value.

MySQL Math - calculate value from another column

I need to create a view for with resulting math function multiply. Is this possible?
Table:
mysql> show create table devices \G
Create Table: CREATE TABLE `devices` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`d_type` varchar(255) NOT NULL,
`multiplier` char(255) NOT NULL DEFAULT '',
`value` decimal(10,3) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
Sample rows:
mysql> SELECT devices.id, devices.d_type, devices.multiplier, devices.`value` FROM devices;
+----+--------------------------+------------+--------+
| id | d_type | multiplier | value |
+----+--------------------------+------------+--------+
| 1 | Cisco Call Manager | / | 4.000 |
| 2 | Generic Router/Switch | * | 0.350 |
I need to somehow calculate using the multiplier to provide a result for various math.
Example (not working of course):
SELECT devices.id, devices.d_type, devices.multiplier, devices.`value`, (SELECT 1 $multiplier devices.`value`) as EPS FROM devices;
The result in this case should be:
+----+--------------------------+------------+--------+--------+
| id | d_type | multiplier | value | EPS |
+----+--------------------------+------------+--------+--------+
| 1 | Cisco Call Manager | / | 4.000 | 0.25 |
| 2 | Generic Router/Switch | * | 0.350 | 0.35 |
we need to derive the EPS column by dividing or multiplying 1 by the value column.
Is there a way to use that multiplier as a determination of whether to divide or multiply?
(note that I used 1 as an example, this could be any incoming integer from user input)
You can use this query
SELECT devices.id, devices.d_type, devices.multiplier, devices.`value`, IF(multiplier = '/', 1/value, 1*value) as EPS FROM devices;
it works for you.
IF(multiplier = '/', 1/value, value) AS EPS

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.