MySQL temporary variable assignment - mysql

I have a table like the one below.
CREATE TABLE People(PeopleId INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
Name VARCHAR(255),
Age INT);
INSERT INTO People(Name, Age)
VALUES('Sam', 25),
('John', 24),
('Ria', 14),
('Diya', 23),
('Topel',19),
('Mac', 45);
I have created a procedure where I use temporary variable age for some purpose.
DROP PROCEDURE IF EXISTS Sample;
CREATE PROCEDURE Sample()
BEGIN
SELECT #Age = Age
FROM People
WHERE PeopleId = 4;
SELECT *
FROM People;
END;
Don't ask why I am storing age in temporary variable since the above is not the exact procedure.
When I run the procedure, the temporary variable is getting displayed as one of result set along with the result set I get for select query. How can I avoid displaying the temporary variable as part of the result set after assignment?

try this one,
SET #Age = (SELECT Age FROM People WHERE PeopleId = 16);
or
SELECT Age INTO #Age
FROM People
WHERE PeopleId = 16;

Related

MYSQL TRIGGER doesn't work sql_extras.sql

Why it doesn't work?
I understand that:
line no.: 9, 21
id int NOT NULL IDENTITY(1, 1), should be AUTO_INCREMENT
id int NOT NULL AUTO_INCREMENT
line no.: 24
wrong column name it can be longs instead of long:
longs decimal(9,6) NOT NULL,
line no.:
out of ; on the end of line 36
out of ; on the end of line 44
line 38, 47 longs instead of long:
INSERT INTO city (city_name, lat, longs, country_id)
but I don't know where is problem below line no.: 91
-- Create new database
CREATE DATABASE lesson7;
USE lesson7;
-- 1. Primary and foreign keys
-- Table: country
CREATE TABLE country (
id int NOT NULL IDENTITY(1, 1),
country_name char(128) NOT NULL,
country_name_eng char(128) NOT NULL,
country_code char(8) NOT NULL,
CONSTRAINT country_ak_1 UNIQUE (country_name),
CONSTRAINT country_ak_2 UNIQUE (country_name_eng),
CONSTRAINT country_ak_3 UNIQUE (country_code),
CONSTRAINT country_pk PRIMARY KEY (id)
);
-- Table: city
CREATE TABLE city (
id int NOT NULL IDENTITY(1, 1),
city_name char(128) NOT NULL,
lat decimal(9,6) NOT NULL,
long decimal(9,6) NOT NULL,
country_id int NOT NULL,
CONSTRAINT city_pk PRIMARY KEY (id),
CONSTRAINT city_country FOREIGN KEY (country_id) REFERENCES country (id)
);
-- Fill the tables
INSERT INTO country (country_name, country_name_eng, country_code)
VALUES ('Deutschland', 'Germany', 'DEU'),
('Srbija', 'Serbia', 'SRB'),
('Hrvatska', 'Croatia', 'HRV'),
('United States of America', 'United States of America', 'USA'),
('Polska', 'Poland', 'POL')
INSERT INTO city (city_name, lat, long, country_id)
VALUES ('Berlin', 52.520008, 13.404954, 1),
('Belgrade', 44.787197, 20.457273, 2),
('Zagreb', 45.815399, 15.966568, 3),
('New York', 40.730610, -73.935242, 4),
('Los Angeles', 34.052235, -118.243683, 4),
('Warsaw', 52.237049, 21.017532, 5)
-- Can we do this?
INSERT INTO city (city_name, lat, long, country_id)
VALUES ('Wien', 48.2084885, 16.3720798, 6);
-- Let's try to delete Poland
DELETE FROM country WHERE id = 5;
-- And check the result
SELECT * FROM city;
SELECT * FROM country;
-- We can remove the constraint using its name
ALTER TABLE city DROP CONSTRAINT city_country;
-- And add it once again with different rescrictions
ALTER TABLE city
ADD CONSTRAINT city_country
FOREIGN KEY (country_id)
REFERENCES country (id)
ON UPDATE CASCADE
ON DELETE CASCADE;
-- Let's try to delete Poland once again
DELETE FROM country WHERE id = 5;
-- And check the results
SELECT * FROM country;
SELECT * FROM city;
-- Triggers
/*
DML (data manipulation language) triggers they react to DML commands.
These are INSERT, UPDATE, and DELETE
DDL (data definition language) triggers they react to DDL commands.
These are CREATE, ALTER, and DROP
*/
/*
DROP <object> IF EXISTS before its creation is helpful in two ways:
1) We could get an arror trying to create a duplicate, we prevent it using DROP
2) If we want to DROP someting non-existent, we can get an error so we check that condidtion first
Combining two above should result in smooth execution
*/
-- Trigger to handle inserts with missing 'country_name' or 'country_name_eng' values
DROP TRIGGER IF EXISTS t_country_insert;
GO
CREATE TRIGGER t_country_insert ON country INSTEAD OF INSERT
AS BEGIN
DECLARE #country_name CHAR(128);
DECLARE #country_name_eng CHAR(128);
DECLARE #country_code CHAR(8);
SELECT #country_name = country_name, #country_name_eng = country_name_eng, #country_code = country_code FROM INSERTED;
IF #country_name IS NULL SET #country_name = #country_name_eng;
IF #country_name_eng IS NULL SET #country_name_eng = #country_name;
INSERT INTO country (country_name, country_name_eng, country_code) VALUES (#country_name, #country_name_eng, #country_code);
END;
-- 'country_name' has NOT NULL constraint, but thanks to the trigger, this insert works fine
SELECT * FROM country;
INSERT INTO country (country_name_eng, country_code) VALUES ('United Kingdom', 'UK');
SELECT * FROM country;
-- Trigger to prevent removal of record from parent table if there are records in child table referencing it
DROP TRIGGER IF EXISTS t_country_delete;
GO
CREATE TRIGGER t_country_delete ON country INSTEAD OF DELETE
AS BEGIN
DECLARE #id INT;
DECLARE #count INT;
SELECT #id = id FROM DELETED;
SELECT #count = COUNT(*) FROM city WHERE country_id = #id;
IF #count = 0
DELETE FROM country WHERE id = #id;
ELSE
THROW 51000, 'can not delete - country is referenced in other tables', 1;
END;
-- Example
SELECT * FROM country;
SELECT * FROM city;
DELETE FROM country WHERE id = 4;
/*
Functions - idea behind them is to avoid writing the same code over and over again
*/
-- Example: function returning if point on the map is located in a western or eastern hemisphere
-- Input: single decimal value (longitude)
-- Output: single char value (position)
-- It's an example of a "scalar-valued function" - returns a single value
CREATE FUNCTION east_or_west (
#long DECIMAL(9,6)
)
RETURNS CHAR(4) AS
BEGIN
DECLARE #return_value CHAR(4);
SET #return_value = 'same';
IF (#long > 0.00) SET #return_value = 'east';
IF (#long < 0.00) SET #return_value = 'west';
RETURN #return_value
END;
-- Examples
SELECT * FROM city;
SELECT city_name, dbo.east_or_west(long) AS 'position'
FROM city;
-- Example: function returning cities from table 'city' located to the east of a given point
-- Input: single decimal value (longitude of a point)
-- Output: filtered table 'city'
-- It's an example of a "table-valued function" - returns a table (multiple values)
CREATE FUNCTION east_from_long (
#long DECIMAL(9,6)
)
RETURNS TABLE AS
RETURN
SELECT *
FROM city
WHERE city.long > #long;
-- Example
SELECT *
FROM east_from_long(0.00);
/*
Stored procedures - idea behind them is to put multiple operations
(inserting, updating, deleting, retrieving data) into one "black box" that can be called multiple times
using various parameters
*/
DROP PROCEDURE IF EXISTS p_cities_all;
GO
CREATE PROCEDURE p_cities_all
-- procedure returns all rows from the customer table
AS BEGIN
SELECT *
FROM city;
END;
-- Execute the procedure with EXEC, procedure uses no parameters
EXEC p_cities_all;
-- Another example, procedure returns the entire row for the given id
DROP PROCEDURE IF EXISTS p_city;
GO
CREATE PROCEDURE p_city (#id INT)
AS BEGIN
SELECT *
FROM city
WHERE id = #id;
END;
-- Execute using EXEC and providing the value for a parameter (id of a row)
EXEC p_city 4;
SELECT * FROM country;
SELECT * FROM city;
-- Example: procedure inserting city from germany (no 'country_id' value is needed while inserting records)
DROP PROCEDURE IF EXISTS p_city_in_germany_insert;
GO
CREATE PROCEDURE p_city_in_germany_insert (#city_name CHAR(128), #lat DECIMAL(9, 6), #long DECIMAL(9, 6))
AS BEGIN
INSERT INTO city(city_name, lat, long, country_id)
VALUES (#city_name, #lat, #long, 1);
END;
-- Example, let's insert Munich to our table
SELECT * FROM city;
EXEC p_city_in_germany_insert 'Munich', 48.13743, 11.57549;
SELECT * FROM city;
-- Example: deleting rows based on 'id'
DROP PROCEDURE IF EXISTS p_city_delete;
GO
CREATE PROCEDURE p_city_delete (#id INT)
AS BEGIN
DELETE
FROM city
WHERE id = #id;
END;
-- Example, delete city with id = 1
EXEC p_city_delete 1;
-- Results
SELECT * FROM city;

How to `SELECT FROM` a table that is a part of a query itself using MySQL?

Say, if I have multiple tables that have the same schema:
CREATE TABLE `tbl01`
(
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`name` TINYTEXT,
`data` INT
);
CREATE TABLE `tbl02`
(
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`name` TINYTEXT,
`data` INT
);
CREATE TABLE `tbl03`
(
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`name` TINYTEXT,
`data` INT
);
-- etc. ------------------
INSERT INTO `tbl01` (`name`, `data`) VALUES
('row 1', 1),
('row 2', 1),
('row 3', 3);
INSERT INTO `tbl02` (`name`, `data`) VALUES
('cube', 1),
('circle', 0);
INSERT INTO `tbl03` (`name`, `data`) VALUES
('one', 1);
and then one table that contains names of all other tables in one of its columns:
CREATE TABLE `AllTbls`
(
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`tblnm` VARCHAR(64) NOT NULL UNIQUE,
`desc` TINYTEXT,
`flgs` BIGINT UNSIGNED
);
INSERT INTO `AllTbls` (`tblnm`, `desc`, `flgs`) VALUES
('tbl01', 'Table 1', 0),
('tbl02', 'Table two', 1),
('tbl03', '3rd table', 0);
So if I want to write a query to retrieve contents of AllTbls and also in one column to include count of rows in each of corresponding tables, I thought the following would be the way to do it:
SELECT *, `tblnm` as TblName, (SELECT COUNT(*) FROM TblName) as cntRws
FROM `AllTbls` ORDER BY `id` ASC LIMIT 0,30;
But this returns an error:
#1146 - Table 'database.TblName' doesn't exist
I know that I can do this in multiple queries (using a loop in a programming language), but is it possible to do it in one query?
PS. I'm using MySQL v.5.7.28
The simple answer is: "you can't"
Table names are not supposed to be used like variables, to hold data, in this way. What you're supposed to have is one table:
tblContractCounts
Client, ContractCount
-------------------
IBM, 1
Microsoft, 3
Google, 2
Not three tables:
tblIBMContractCounts
ContractCount
1
tblMicrosoftContractCounts
ContractCount
3
tblGoogleContractCounts
ContractCount
2
If your number of tables is known and fixed you can perhaps remedy things by creating a view that unions them all back together, or embarking on an operation to put them all into one table, with separate views named the old names so things carry in working til you can change them. If new tables are added all the time it's a flaw in the data modelling and need to be corrected. In that case you'd have to use a programming language (front end or stored procedure) to build a single query:
//pseudo code
strSql = ""
for each row in dbquery("Select name from alltbls")
strSql += "select '" + row.name + "' as tbl, count(*) as ct from " + row.name + " union all "
next row
strSql += "select 'dummy', 0"
result = dbquery(strSql)
It doesn't have to be your front end that does this - you could also do this in mysql and leverage the dynamic sql / EXECUTE. See THIS ANSWER how we can concatenate a string using logic like above so that the string contains an sql query and then execute the query. The information schema will give you the info you need to get a list of all current table names
But all you're doing is working around the fact that your data modelling is broken; I recommend to fix that instead
ps: the INFORMATION_SCHEMA has rough counts for tables with their names, which may suffice for your needs in this particular case
select table_name, table_rows from infornation_schema.tables where table_name like ...
I managed to solve the problem using the following stored procedure.
-- DROP PROCEDURE sp_Count_Rows;
Delimiter $$
CREATE PROCEDURE sp_Count_Rows()
BEGIN
DECLARE table_name TEXT DEFAULT "";
DECLARE finished INTEGER DEFAULT 0;
DECLARE table_cursor
CURSOR FOR
SELECT tblnm FROM alltbls;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET finished = 1;
OPEN table_cursor;
DROP TABLE IF EXISTS RowsCount;
CREATE TABLE IF NOT EXISTS RowsCount(Tlbnm text, ctnRws int);
table_loop: LOOP
FETCH table_cursor INTO table_name;
IF finished = 1 THEN
LEAVE table_loop;
END IF;
SET #s = CONCAT("insert into RowsCount select '", table_name ,"', count(*) as cntRws from ", table_name);
PREPARE stmt1 FROM #s;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
END LOOP table_loop;
CLOSE table_cursor;
SELECT * FROM RowsCount;
DROP TABLE RowsCount;
END
$$
And then when you call the procedure
CALL sp_Count_Rows();
You get this result

mysql how to update a column of all rows and for each row a different column value

Here is a sample table:
id name code
----------------
1 n1
2 n2
3 n3
I want to update the code column of every row with different values, so for row of id 1 i want to add this value for code 'zb6DXBfJ', and for row id 2 'NV6Nx4St', and for row id 3 this value for code column 'q23ZMACc'. So my final table should look like this:
id name code
----------------
1 n1 zb6DXBfJ
2 n2 NV6Nx4St
3 n3 q23ZMACc
UPDATE TableName
SET Code = CASE
WHEN id = 1 THEN 'zb6DXBfJ'
WHEN id = 2 THEN 'NV6Nx4St'
WHEN id = 3 THEN 'q23ZMACc'
END;
Try this
UPDATE Table_Name WHERE id = desired_id SET code = desired_code;
Of course, you'll need to substitute Table_Name, desired_id, and desired_code as required.
Depending on where your codes come from, you can try one of the folowing:
If your codes came from another table, you can create a procedure that will "match" each line of the two table in order to update the codes. Here's an example :
First create the tables (the one you already have, and the one with the codes)
CREATE TABLE table1 (
id INT(6) UNSIGNED PRIMARY KEY,
name VARCHAR(300) NOT NULL,
code VARCHAR(300));
CREATE TABLE table2 (
id INT(6) UNSIGNED PRIMARY KEY,
code VARCHAR(300) NOT NULL);
INSERT INTO table1 (id, name) VALUES
(1, 'n1'),
(2, 'n2'),
(3, 'n3');
INSERT INTO table2 (id, code) VALUES
(1, 'zb6DXBfJ'),
(2, 'NV6Nx4St'),
(3, 'q23ZMACc');
Then create the actual procedure
delimiter //
CREATE PROCEDURE assign_strings()
BEGIN
DECLARE _id INT DEFAULT 0;
DECLARE str VARCHAR(300);
DECLARE cur CURSOR FOR SELECT id FROM table1;
open cur;
myloop:LOOP
fetch cur into _id;
SELECT code INTO str FROM table2 WHERE id = _id;
UPDATE table1 SET code = str WHERE id = _id;
end loop myloop;
close cur;
END //
delimiter ;
You can now call the procedure
CALL assign_strings();
Note that I don't know your logic to retrieve these code. Here I just assume table2.id has the code for table1.id. Its a little dumb but your logic may be more complicated.
If your codes are just random strings (non-unique) you can just use a function instead of a procedure like this :
DELIMITER //
CREATE FUNCTION get_random_string()
RETURNS VARCHAR(300)
BEGIN
RETURN 'Your_random_string';
END//
DELIMITER ;
Note that you'll need to implement your own random string strategy. You can use something like a MD5 function with a random number and substrings... whatever you need.
You can now call this function directly in an update statement like so :
UPDATE table1 set code = get_random_string();
Hope it gets you started.

mysql stored procedure checking if record exists

I created the following stored procedure:
CREATE DEFINER=`root`#`localhost` PROCEDURE `add_summit`(IN `assoc_code` CHAR(5), IN `assoc_name` CHAR(50), IN `reg_code` CHAR(2), IN `reg_name` CHAR(100), IN `code` CHAR(20), IN `name` CHAR(100), IN `sota_id` CHAR(5), IN `altitude_m` SMALLINT(5), IN `altitude_ft` SMALLINT(5), IN `longitude` DECIMAL(10,4), IN `latitude` DECIMAL(10,4), IN `points` TINYINT(3), IN `bonus_points` TINYINT(3), IN `valid_from` DATE, IN `valid_to` DATE)
BEGIN
declare assoc_id SMALLINT(5);
declare region_id SMALLINT(5);
declare summit_id MEDIUMINT(8);
-- ASSOCIATION check if an association with the given code and name already exists
SELECT id INTO assoc_id FROM association WHERE code = assoc_code LIMIT 1;
IF (assoc_id IS NULL) THEN
INSERT INTO association(code, name) VALUES (assoc_code, assoc_name);
set assoc_id = (select last_insert_id());
END IF;
-- REGION check if a region with the given code and name already exists
SET region_id = (SELECT id FROM region WHERE code = reg_code AND name = reg_name AND association_id = assoc_id);
IF (region_id IS NULL) THEN
INSERT INTO region(association_id, code, name) VALUES (assoc_id, reg_code, reg_name);
set region_id = (select last_insert_id());
END IF;
-- SUMMIT check if a summit with given parameters already exists
SET summit_id = (SELECT id FROM summit WHERE association_id = assoc_id AND region_id = region_id);
IF (summit_id IS NULL) THEN
INSERT INTO summit(code, name, sota_id, association_id, region_id, altitude_m, altitude_ft, longitude,
latitude, points, bonus_points, valid_from, valid_to)
VALUES (code, name, sota_id, assoc_id, region_id, altitude_m, altitude_ft, longitude, latitude,
points, bonus_points, valid_from, valid_to);
END IF;
END$$
basically, it should check if a record exists in some tables and, if it doesn't, it should insert it and use the inserted id (auto increment).
The problem is that even if the record exists (for instance in the association table), assoc_id keeps returning null and that leads to record duplication.
I'm new to stored procedures so I may be doing some stupid errors. I've been trying to debug this SP for hours but I cannot find the problem.
A newbie mistake.
I forgot to specify the table name in the field comparison and that leads to some conflicts with param names (for example the param name).
A good idea is to specify some kind of prefix for parameters (like p_) and always specify the name of the table in the SP.

Mysql - Determinate number of inserted rows in a stored procedure

So, basically I have this sql stored procedure:
drop procedure if exists registrar_cuenta;
delimiter //
create procedure registrar_cuenta (in p_email varchar(255), in p_passwordHash varchar(40), in p_salt varchar(40))
begin
if (exists(select 1 from usuarios where email = p_email and registrado = 0)) then
update usuarios set passwordHash = p_passwordHash, salt = p_salt, fechaRegistro = now(), registrado = 1 where email = p_email and registrado = 0;
else
insert into usuarios (email, passwordHash, salt, fechaRegistro, registrado) values (p_email, p_passwordHash, p_salt, now(), 1);
end if;
end
//
delimiter ;
Which runs great, BUT I want to change this piece of code:
else
insert into usuarios (email, passwordHash, salt, fechaRegistro, registrado) values (p_email, p_passwordHash, p_salt, now(), 1);
end if;
For something like this:
insert into usuarios (email, passwordHash, salt, fechaRegistro, registrado) values (p_email, p_passwordHash, p_salt, now(), 1);
if (inserted_rows == 0) then
alter table usuarios auto_increment = auto_increment - 1;
end if;
The thing is that I have an unique field (email) which can produce a duplicate entry error, if so, then the auto_increment value will increase anyways and I want to avoid that.
Is there any way I could archieve this task?
Sounds like you want to use the ROW_COUNT function.
http://dev.mysql.com/doc/refman/5.0/en/information-functions.html#function_row-count
MySQL Solution:
You can use alter table ... as in the example below:
alter table usuarios
auto_increment = ( SELECT ( AUTO_INCREMENT - 1 )
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME='usuarios' and TABLE_SCHEMA=DATABASE()
);
But, this practice is discouraged. If you define a column with int unsigned max value you can store is 4294967295 and if it is bigint unsigned max value is 18446744073709551615. If the database engine can insert 100,000 records per second, calculate your self, how many hours ( days / months / years ) would it take to cross the max value. Hence, you can omit the auto incremented value that is wasted.