Operand should contain 1 column(s) - mysql

Getting a Operand should contain 1 column(s) mysql error whenever I try to insert into the table sets.
I googled and found a hoard of similar questions but they are always pin point specific to solving their immediate problem. I have mysql 5.6 by the way. I am allowed multiple TIMESTAMPS.
Here is my code:
INSERT INTO `sets` (`tabler_name`) VALUES ("leads_auto");
Here is my table:
CREATE TABLE IF NOT EXISTS `lms`.`sets` (
`set_id` BIGINT NOT NULL AUTO_INCREMENT,
`on_off` SMALLINT NOT NULL DEFAULT 0,
`tabler_name` VARCHAR(45) NULL,
`origin_date` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`last_modified_date` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`original_count` INT NULL,
`current_count` INT NULL,
`source_type` VARCHAR(45) NULL,
`source` VARCHAR(45) NULL,
`method` VARCHAR(45) NULL,
`agent` VARCHAR(45) NULL,
`dupes` INT NULL,
`bads` INT NULL,
`aged` INT NULL COMMENT 'This table keeps track of the record sets that enter the system. Example: a set of leads imported into the database.',
PRIMARY KEY (`set_id`)
) ENGINE = InnoDB;
Stored Procedure:
DELIMITER //
CREATE PROCEDURE `lms`.`leads_to_bak` ()
BEGIN
SET #table1 = (SELECT `tabler_name` FROM sets WHERE on_off=0 LIMIT 1);
SET #table2 = CONCAT(#table1, '_bak');
SET #SQL1 = CONCAT('INSERT INTO ',#table2, '(', (SELECT
REPLACE(GROUP_CONCAT(COLUMN_NAME), 'lead_id,', '') FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #table2), ')', ' SELECT ', (SELECT REPLACE(GROUP_CONCAT(COLUMN_NAME), 'lead_id,', '') FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = #table1), ' FROM ', #table1);
PREPARE stmt FROM #sql1;
EXECUTE stmt;
END//
DELIMITER ;
USE `lms`;
Trigger
DELIMITER $$
USE `lms`$$
CREATE TRIGGER `lms`.`after_insert_into_leads`
AFTER INSERT ON `sets` FOR EACH ROW
BEGIN
IF (SELECT * FROM sets WHERE on_off=0 LIMIT 1) THEN
CALL lms.leads_to_bak();
END IF;
END$$
DELIMITER ;
USE `lms`;
I don't see anything wrong with my routines. Removing the routines and trigger seems to make the problem go away.

In your trigger, did you mean to put EXISTS after IF? Like this:
CREATE TRIGGER `lms`.`after_insert_into_leads`
AFTER INSERT ON `sets` FOR EACH ROW
BEGIN
IF EXISTS (SELECT * FROM sets WHERE on_off=0 LIMIT 1) THEN
CALL lms.leads_to_bak();
END IF;
END$$

Escape your table name, it seems to be a reserved function. I'm not sure if you've defined one locally.
INSERT INTO `sets` (tabler_name) VALUES ("leads_auto");
Also, you can't have two timestamp fields in a single database afaik. Change one of the two timestamps to a DATETIME field if it caused you issues as well

Besides escaping the field name in your INSERT-statement, it cannot be improved very much. But it doesn't generate any error in my test enviroment. Is this really the exact statement throwing you an error?
However, there's a slight problem in your table definition, it will throw you an
Incorrect table definition; there can be only one TIMESTAMP column
with CURRENT_TIMESTAMP in DEFAULT or ON UPDATE clause.
As the error message indicates, you can only use one timestamp column with CURRENT_TIMESTAMP, if you need more than one, you can do this using a trigger.

Related

Idempotent table and index creation with value insertion in MySQL

I have some SQL Server schema changes that I'm trying to convert to MySQL. I know about CREATE TABLE IF NOT EXISTS in MySQL. I don't think I can use that here.
What I want to do is create a table in MySQL, with an index, and then insert some values all as part of the "if not exists" predicate. This was what I came up with, though it doesn't seem to be working:
SET #actionRowCount = 0;
SELECT COUNT(*) INTO #actionRowCount
FROM information_schema.tables
WHERE table_name = 'Action'
LIMIT 1;
IF #actionRowCount = 0 THEN
CREATE TABLE Action
(
ActionNbr INT AUTO_INCREMENT,
Description NVARCHAR(256) NOT NULL,
CONSTRAINT PK_Action PRIMARY KEY(ActionNbr)
);
CREATE INDEX IX_Action_Description
ON Action(Description);
INSERT INTO Action
(Description)
VALUES
('Activate'),
('Deactivate'),
('Specified');
END IF
I can run it once, and it'll create the table, index, and values. If I run it a second time, I get an error: Table Action already exists. I would have thought that it wouldn't run at all if the table already exists.
I use this pattern a lot when bootstrapping a schema. How can I do this in MySQL?
In mysql compound statements can only be used within stored programs, which includes the if statement as well.
Therefore, one solution is to include your code within a stored procedure.
The other solution is to use the create table if not exists ... with the separate index creation included within the table definition and using insert ignore or insert ... select ... to avoidd inserting duplicate values.
Examples of options:
Option 1:
CREATE TABLE IF NOT EXISTS `Action` (
`ActionNbr` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`Description` VARCHAR(255) NOT NULL,
INDEX `IX_Action_Description` (`Description`)
) SELECT 'Activate' `Description`
UNION
SELECT 'Deactivate'
UNION
SELECT 'Specified';
Option 2:
DROP PROCEDURE IF EXISTS `sp_create_table_Action`;
DELIMITER //
CREATE PROCEDURE `sp_create_table_Action`()
BEGIN
IF NOT EXISTS(SELECT NULL
FROM `information_schema`.`TABLES` `ist`
WHERE `ist`.`table_schema` = DATABASE() AND
`ist`.`table_name` = 'Action') THEN
CREATE TABLE `Action` (
`ActionNbr` INT AUTO_INCREMENT,
`Description` NVARCHAR(255) NOT NULL,
CONSTRAINT `PK_Action` PRIMARY KEY (`ActionNbr`)
);
CREATE INDEX `IX_Action_Description`
ON `Action` (`Description`);
INSERT INTO `Action`
(`Description`)
VALUES
('Activate'),
('Deactivate'),
('Specified');
END IF;
END//
DELIMITER ;
CALL `sp_create_table_Action`;

MYSQL stored procedure create dynamic table from date

I'm facing a little problem to create the table name dynamically.
For example ,
This query (SELECT DATE_FORMAT(NOW(),'%Y%m')) returns 201702
My motive is to check whether the table exist.
If it doesn't exist , it will create it automatically.
Now here's my problem.
messages_`,datereturn
It seems I'm getting error for even compiling. How do we pass parameter to create the table?
Really appreciate your help.
Full Code:
BEGIN
SET datereturn = (SELECT DATE_FORMAT(NOW(),'%Y%m'));
CREATE TABLE IF NOT EXISTS `messages_`,datereturn (
`mid` bigint(17) NOT NULL,
`uid` int(11) NOT NULL,
`csid` int(11) NOT NULL,
`content` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`content_type` tinyint(4) NOT NULL,
`datecreated` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`datecreated1` bigint(13) DEFAULT NULL,
PRIMARY KEY (`mid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
END
EDIT: it's different than the answer proposed.
I'm making the table dynamic with MySQL 5.6.
The above suggested thread doesn't work for my version.
So, I don't know why it's a possible of duplicate.
Stored procedures don't allow you to combine variables into SQL identifiers. In other words, things like table names must be set before the query is parsed.
So you need to create a dynamic SQL statement and make the stored procedure prepare the CREATE TABLE statement at runtime:
BEGIN
SET #sql = CONCAT('CREATE TABLE IF NOT EXISTS messages_',datereturn,' (',
'mid bigint NOT NULL, ',
'uid int NOT NULL, ',
'csid int NOT NULL, ',
'content varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, ',
'content_type tinyint NOT NULL, ',
'datecreated timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, ',
'datecreated1 bigint DEFAULT NULL, ',
'PRIMARY KEY (mid)',
') ENGINE=InnoDB DEFAULT CHARSET=latin1');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END
(Note: I have not tested this code, so if I missed a quote, I expect you know how to fix that.)
In this example, I also removed your back-ticks because they're not necessary, and I removed the length options for int types because they have no effect.
All that said, I have to question why you need a table per date. Seems like a code smell to me. Have you considered creating a table partitioned by date instead?
SET #CrtTbl = CONCAT('CREATE TABLE', CONCAT('`messages_', datererun), '(mid bigint(17) NOT NULL, .... and so on
...
PREPARE CrtTblStmt FROM #CrtTbl;
EXECUTE CrtTblStmt;
DEALLOCATE PREPARE CrtTblStmt;
something like that

How do I modify a MySQL column to allow NULL without specifying its data type?

I have a table in mysql and I want to alter a table to allow a column to be null.
When I do describe on my mysql table, I see these things in mysql workbench:
Field Type Null Key Default Extra
I want to set Null field as YES for a particular column. Here is how I am trying which works fine but do I need to provide its data type while setting DEFAULT to NULL?
ALTER TABLE abc.xyz CHANGE hello hello int(10) unsigned DEFAULT NULL;
ALTER TABLE abc.xyz CHANGE world world int(10) unsigned DEFAULT NULL;
Is there any other way by which I can just pick column name and set default to NULL instead of using its data type as well? I don't want to provide int(10) while setting its default to NULL.
You're kind of on the wrong track. Changing the default to NULL does not "allow" the column to be null: what you need to do is drop the "NOT NULL" constraint on the column.
But to redefine the nullability of a column through script, you will have to continue to reference the datatype. You'll have to enter something like
ALTER TABLE MyTable MODIFY COLUMN this_column Int NULL;
Not sure if this applies if you're looking for a straight DDL statement, but in MySQL Workbench you can right-click on a table name, select "Alter Table..." and it will pull up a table definition GUI. From there, you can select null/not null (among all the other options) without explicitly listing the column's datatype. Just a "for what it's worth"...
This can be done with the help of dynamic statements.
Usage:
CALL sp_make_nullable('schema_name', 'table_name', 'column_name', TRUE);
Implementation:
DELIMITER $$
create procedure eval(IN var_dynamic_statement text)
BEGIN
SET #dynamic_statement := var_dynamic_statement;
PREPARE prepared_statement FROM #dynamic_statement;
EXECUTE prepared_statement;
DEALLOCATE PREPARE prepared_statement;
END;
DELIMITER ;
DELIMITER $$
create procedure sp_make_nullable(IN var_schemaname varchar(64), IN var_tablename varchar(64),
IN var_columnname VARCHAR(64), IN var_nullable BOOLEAN)
BEGIN
DECLARE var_column_type LONGTEXT DEFAULT (SELECT COLUMN_TYPE
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = var_schemaname
AND TABLE_NAME = var_tablename
AND COLUMN_NAME = var_columnname);
DECLARE var_nullable_prefix VARCHAR(64) DEFAULT '';
IF NOT var_nullable THEN
SET var_nullable_prefix := 'NOT';
end if;
CALL eval(CONCAT('
ALTER TABLE ', var_schemaname, '.', var_tablename,
' MODIFY ', var_columnname, ' ', var_nullable_prefix, ' NULL
'));
end$$
DELIMITER ;

Why postgresql's single insert statement is faster than mysql?

I wonder why postgresql's single "insert" statement is completely faster than MySQL's when autocommit is turned on? The following is the same code that I did on them.
Version:
MySQL: 5.6.10
PostgreSQL: PostgreSQL 9.3.2 on x86_64
Table definition:
MySQL:
CREATE TABLE `user` (
`username` char(36) NOT NULL,
`password` char(32) NOT NULL,
`register_time` datetime NOT NULL,
`mobile_phone` char(11) NOT NULL,
`is_admin` enum('yes','no') NOT NULL,
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
PostgreSQL:
CREATE TYPE ytt_enum AS ENUM ('yes','no');
CREATE TABLE ytt."user" (
"username" char(36) NOT NULL,
"password" char(32) NOT NULL,
"register_time" timestamp NOT NULL,
"mobile_phone" char(11) NOT NULL,
"is_admin" ytt_enum NOT NULL,
PRIMARY KEY ("username")
) ;
Store functions:
MySQL:
DELIMITER $$
USE `t_girl`$$
DROP PROCEDURE IF EXISTS `sp_insert_user_simple`$$
CREATE DEFINER=`root`#`localhost` PROCEDURE `sp_insert_user_simple`(
IN f_input INT
)
BEGIN
DECLARE i INT DEFAULT 0;
WHILE i <= f_input
DO
INSERT INTO t_girl.user (`username`, `password`, register_time,mobile_phone,is_admin)
VALUES (UUID(),MD5(REPLACE(UUID(),'-','')),DATE_SUB(NOW(),INTERVAL CEIL(RAND()*40) DAY),CEIL(RAND()*10000)+13800000000,IF(TRUNCATE(RAND()*2,0)=1,'yes','no'));
SET i = i + 1;
END WHILE;
END$$
DELIMITER ;
PostgreSQL:
CREATE or replace function sp_insert_user_simple(
IN f_input INT
) returns void as
$ytt$
declare i int := 0;
v_username char(36);
v_password char(32);
v_register_time timestamp;
v_mobile_phone char(11);
v_is_admin ytt_enum;
BEGIN
WHILE i < f_input
loop
v_username := uuid_generate_v1();
v_password :=MD5(REPLACE(uuid_generate_v1()::text,'-',''));
v_register_time := to_timestamp((now() - '1 day'::interval*ceil(random()*40))::text,'yyyy-mm-dd HH24:MI:SS');
v_mobile_phone :=CEIL(RANDOM()*10000)+13800000000;
v_is_admin := (case TRUNC(RANDOM()*2) when 1 then 'yes' else'no' end)::ytt_enum;
INSERT INTO ytt.user (username, password, register_time,mobile_phone,is_admin)
VALUES (v_username,v_password,v_register_time,v_mobile_phone,v_is_admin);
i := i + 1;
END loop;
END;
$ytt$language plpgsql;
Parameters:
MySQL:
innodb_buffer_pool_size=32M
bulk_insert_buffer_size=20M
autocommit=on
PostgreSQL:
shared_memory=32M
effective_cache_size=20M
autocommit=on
Test result:
MySQL:
mysql> call sp_insert_user_simple(10000);
Query OK, 1 row affected (1 min 9.93 sec)
PostgreSQL:
ytt=# select sp_insert_user_simple(10000);
sp_insert_user_simple
-----------------------
(1 row)
Time: 1177.043 ms
The above test shows that MySQL's running time is 69.93 second but PostgreSQL's is only 1.17 second.
Any answer is appreciated. Thanks.
I think what's happening here is that MySQL's procedures may be doing a commit for each individual INSERT. In PostgreSQL the whole procedure commits at the end; procedures cannot run individual transactions. (I'm not totally sure if that's how MySQL's procedures behave with autocommit=off, but it seems to be from a quick look at the docs).
You should really be doing this INSERT as a single statement anyway, using INSERT ... SELECT:
CREATE or replace function sp_insert_user_simple(
IN f_input integer
) returns void AS $$
INSERT INTO ytt.user (username, password, register_time,mobile_phone,is_admin)
SELECT
uuid_generate_v1(),
MD5(REPLACE(uuid_generate_v1()::text,'-','')),
to_timestamp((now() - '1 day'::interval*ceil(random()*40))::text,'yyyy-mm-dd HH24:MI:SS'),
CEIL(RANDOM()*10000)+13800000000,
case TRUNC(RANDOM()*2) when 1 then 'yes' else'no' end
FROM generate_series(1,$1);
$$ LANGUAGE sql;
(I assume this is dummy user-data generation?).
Also, use char, not varchar. char is an awful data type and should be avoided. Also, consider using boolean for the is_admin column.
Try testing simple insert queries:
INSERT INTO ytt.user (username, password) VALUES ('a', 'b');
and loop it in a procedure, thus making time measurement more accurate. Avoid using other built-in functions (like rng and timestamp), since their perfomance can differ singnificantly on a large sample, unless, of course, you tested those first.

MySQL variable table naming creation

I am creating a database for a collection of servers for minecraft, and the way I have it set up, I want a table to be created for each server as it is added. At the moment, I have everything working except for the fact that i cannot get the tables that are being created to contain the IP address. I want the table to be something like [IP]_Players where the [IP] is replaced by the actual IP address, which will be send through the function that it is being created through. Here is what I have so far:
DELIMITER $$
CREATE PROCEDURE `minecraft`.`AddServer` (ip Text)
BEGIN
DECLARE play TEXT;
DECLARE tran TEXT;
SET play = ip + '_Players';
SET tran = ip + '_Transactions';
INSERT INTO `minecraft`.`Server_Data` (`Server_IP`) VALUES (ip);
CREATE TABLE `minecraft`.play (
`Player` TEXT NOT NULL ,
`Balance` DOUBLE NOT NULL DEFAULT 100 ,
`Warnings` INT NOT NULL DEFAULT 0 ,
`Offences` INT NOT NULL DEFAULT 0 ,
UNIQUE INDEX `Player_UNIQUE` (`Player` ASC) );
CREATE TABLE `minecraft`.tran (
`Time` TIMESTAMP NOT NULL ,
`Player` TEXT NOT NULL ,
`Destination` TEXT NOT NULL ,
`Amount` DOUBLE NOT NULL ,
`Description` TEXT NOT NULL ,
PRIMARY KEY (`Time`) );
END
Instead of creating it as 192.168.001.107_Players when
CALL minecraft.AddServer('192.168.001.107');
is preformed, it creates a table called play.
What am I doing wrong?
I was playing around and got this to work. Note that you cannot have periods in a table name. So you may want to use the REPLACE function to replace the periods with underscores for example.
DELIMITER $$
CREATE PROCEDURE `minecraft`.`AddServer` (ip Text)
BEGIN
DECLARE play varchar(500);
DECLARE STMT varchar(500);
SET play = CONCAT(ip, '_Players');
SET #sql_stmt = CONCAT('CREATE TABLE minecraft.', play, ' (
`Player` VARCHAR(50) NOT NULL ,
`Balance` DOUBLE NOT NULL DEFAULT 100 ,
`Warnings` INT NOT NULL DEFAULT 0 ,
`Offences` INT NOT NULL DEFAULT 0 ,
UNIQUE INDEX `Player_UNIQUE` (`Player` ASC) );');
PREPARE STMT FROM #sql_stmt;
EXECUTE STMT;
END$$
Delimiter ;
You have to use prepared statements I guess
SQL Syntax Prepared Statements.