split a column into three separate columns with a delimiter - mysql

I have values in a column in the table
For Eg: Abc-a,d,f,g,h;Acd-b,h,i,j;Asx-i,k,l,m
Now what i want is i need the values a,d,f,g,h to be copied into a separate column named 'Abc' similarly b,h,i,j to another column "Acd"(Adding clarity to the above point,i want all the comma separated values to come under a separate column and the column name will be the string which is prefixed before the '-').i should be splited using the delimiter ';'

First of all never store data as comma separated values, its a bad practice and you should always normalize the data
Now as far as your current situation is concern and if the pattern is same you can extract data as below using the substring_index() function
mysql> select substring_index(substring_index('Abc-a,d,f,g,h;Acd-b,h,i,j;Asx-i,k,l,m',';',1),'Abc-',-1) as Abc ;
+-----------+
| Abc |
+-----------+
| a,d,f,g,h |
+-----------+
mysql> select substring_index(substring_index('Abc-a,d,f,g,h;Acd-b,h,i,j;Asx-i,k,l,m',';',2),'Acd-',-1) as Acd;
+---------+
| Acd |
+---------+
| b,h,i,j |
+---------+
mysql> select substring_index(substring_index('Abc-a,d,f,g,h;Acd-b,h,i,j;Asx-i,k,l,m',';',-1),'Asx-',-1) as Asx;
+---------+
| Asx |
+---------+
| i,k,l,m |
+---------+
Finally putting all together you can have the update command as
update your_table
set
Abc = substring_index(substring_index('Abc-a,d,f,g,h;Acd-b,h,i,j;Asx-i,k,l,m',';',1),'Abc-',-1),
Acd = substring_index(substring_index('Abc-a,d,f,g,h;Acd-b,h,i,j;Asx-i,k,l,m',';',2),'Acd-',-1),
Asx = substring_index(substring_index('Abc-a,d,f,g,h;Acd-b,h,i,j;Asx-i,k,l,m',';',-1),'Asx-',-1) ;
Note that I have added the complete string in the above example, you may just add the column name where the values are stored

DECLARE #DATA NVARCHAR(MAX), #COLUMNROWCOUNT INT, #COLUMNVALUE NVARCHAR(MAX)
DECLARE #VALUEROWCOUNT INT, #VALUE NVARCHAR(MAX), #SQLQUERY NVARCHAR(MAX)
SET #DATA='Abc-a,d,f,g,h;Acd-b,h,i,j;Asx-i,k,l,m'
CREATE TABLE #TEMPCOLUMNS(
ID int IDENTITY(1,1) NOT NULL,
VALUE NVARCHAR(MAX)
)
CREATE TABLE #TEMPVALUES(
ID int IDENTITY(1,1) NOT NULL,
VALUE NVARCHAR(MAX)
)
CREATE TABLE #TEMPCOLUMNVALUE(
ID int IDENTITY(1,1) NOT NULL,
COLUMNNAME NVARCHAR(MAX),
COLUMNVALUE NVARCHAR(MAX)
)
INSERT INTO #TEMPCOLUMNS
SELECT value AS VALUE
FROM dbo.Split(#DATA, ';')
SET #COLUMNROWCOUNT=1
SET #COLUMNVALUE=''
WHILE #COLUMNROWCOUNT <=(SELECT COUNT(*) FROM #TEMPCOLUMNS)
BEGIN
SET #COLUMNVALUE=(SELECT VALUE FROM #TEMPCOLUMNS WHERE ID=#COLUMNROWCOUNT)
TRUNCATE TABLE #TEMPVALUES
INSERT INTO #TEMPVALUES
SELECT value AS VALUE
FROM dbo.Split(#COLUMNVALUE, '-')
INSERT INTO #TEMPCOLUMNVALUE SELECT
(SELECT VALUE FROM #TEMPVALUES WHERE ID=1) COLUMNNAME,
(SELECT VALUE FROM #TEMPVALUES WHERE ID=2) COLUMNVALUE
SET #COLUMNROWCOUNT=#COLUMNROWCOUNT+1
END
SET #VALUEROWCOUNT=1
SET #VALUE=''
SET #SQLQUERY='CREATE TABLE #TEMP( '
WHILE #VALUEROWCOUNT <=(SELECT COUNT(*) FROM #TEMPCOLUMNVALUE)
BEGIN
SET #VALUE=(SELECT COLUMNNAME FROM #TEMPCOLUMNVALUE WHERE ID=#VALUEROWCOUNT)
SET #SQLQUERY=#SQLQUERY+#VALUE+' NVARCHAR(MAX)'
IF (#VALUEROWCOUNT <>(SELECT COUNT(*) FROM #TEMPCOLUMNVALUE))
BEGIN
SET #SQLQUERY=#SQLQUERY+','
END
SET #VALUEROWCOUNT=#VALUEROWCOUNT+1
END
SET #SQLQUERY=#SQLQUERY+')'
SET #VALUEROWCOUNT=1
SET #VALUE=''
SET #SQLQUERY=#SQLQUERY+'INSERT INTO #TEMP SELECT '
WHILE #VALUEROWCOUNT <=(SELECT COUNT(*) FROM #TEMPCOLUMNVALUE)
BEGIN
SET #VALUE=(SELECT COLUMNVALUE FROM #TEMPCOLUMNVALUE WHERE ID=#VALUEROWCOUNT)
SET #SQLQUERY=#SQLQUERY+''''+#VALUE+''''
IF (#VALUEROWCOUNT <>(SELECT COUNT(*) FROM #TEMPCOLUMNVALUE))
BEGIN
SET #SQLQUERY=#SQLQUERY+','
END
SET #VALUEROWCOUNT=#VALUEROWCOUNT+1
END
SET #SQLQUERY=#SQLQUERY+'SELECT * FROM #TEMP DROP TABLE #TEMP'
PRINT(#SQLQUERY)
EXEC(#SQLQUERY)
DROP TABLE #TEMPCOLUMNS
DROP TABLE #TEMPVALUES
DROP TABLE #TEMPCOLUMNVALUE
SQL FUNCTION:
CREATE FUNCTION [dbo].[Split]
(
#List nvarchar(2000),
#SplitOn nvarchar(5)
)
RETURNS #RtnValue table
(
Id int identity(1,1),
Value nvarchar(100)
)
AS
BEGIN
While (Charindex(#SplitOn,#List)>0)
Begin
Insert Into #RtnValue (value)
Select
Value = ltrim(rtrim(Substring(#List,1,Charindex(#SplitOn,#List)-1)))
Set #List = Substring(#List,Charindex(#SplitOn,#List)+len(#SplitOn),len(#List))
End
Insert Into #RtnValue (Value)
Select Value = ltrim(rtrim(#List))
Return
END
Please try it.

Related

How to SELECT multiple values in MYSQL trigger?

how can I SELECT multiple values INTO variables in MYSQL trigger?
I have tried SELECT values into variables in this way but it didn't work. I was inspired by this thread how to use trigger to set value based on query result.
When I examined value in variables, it is NULL. When I put this SELECT into mysql workbench it will select right values. I check column types and they are same type as variables in trigger.
With debug I discovered that there is problem with SELECTing values.
Thank you in advance.
Here is my trigger:
CREATE TRIGGER fin_den_zam_insert
AFTER INSERT
ON table1 FOR EACH ROW
BEGIN
DECLARE koef1 DECIMAL(6,3);
DECLARE koef2 DECIMAL(6,3);
DECLARE koef3 DECIMAL(6,3);
DECLARE sum DECIMAL(6,3);
SELECT DISTINCT
koef_salary, koef_sunday, koef_holiday
INTO
koef1 , koef2, koef3
FROM
employee E
WHERE
E.personal_number = NEW.personal_number_id;
SET sum := NEW.salary * (koef1 + koef2 + koef3);
INSERT INTO export_table
(
id_export,
personal_number,
final_sum
)
VALUES
(
NULL,
NEW.personal_number_id,
sum
);
END;
//
DELIMITER ;
You must be careful with reserved words like sum, that can cause very much trouble-
most people write before every own variable _ like _sum, so that also a stranger can identofy such variables.
That is as you can see not absoltely necessary, but helps also when you tale a look in 5 years
create table employee
(personal_number int,koef_salary DECIMAL(6,3), koef_sunday DECIMAL(6,3), koef_holiday DECIMAL(6,3));
create table export_table(
id_export int auto_increment primary key,
personal_number int,
final_sum DECIMAL(6,3)
);
CREATE table table1
(id_export int auto_increment primary key
, personal_number_id int
, salary DECIMAL(6,3));
insert into employee values(1,1.1,1.3,1.4);
CREATE TRIGGER fin_den_zam_insert
AFTER INSERT
ON table1 FOR EACH ROW
BEGIN
DECLARE koef1 DECIMAL(6,3);
DECLARE koef2 DECIMAL(6,3);
DECLARE koef3 DECIMAL(6,3);
DECLARE final_sum DECIMAL(6,3);
SELECT DISTINCT
koef_salary, koef_sunday, koef_holiday
INTO
koef1 , koef2, koef3
FROM
employee E
WHERE
E.personal_number = NEW.personal_number_id;
SET final_sum := NEW.salary * (koef1 + koef2 + koef3);
INSERT INTO export_table
(
id_export,
personal_number,
final_sum
)
VALUES
(
NULL,
NEW.personal_number_id,
final_sum
);
END
INSERT INTO table1 VALUES (NULL,1,100)
SELECT * FROM export_table
id_export | personal_number | final_sum
--------: | --------------: | --------:
1 | 1 | 380.000
db<>fiddle here

Use string from select to select a specific column in mysql

I'm trying to select a column based on a string I get from another select:
SELECT id, (
SELECT COLUMN_NAME
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'database_name'
AND TABLE_NAME = 'a'
AND DATA_TYPE = 'varchar'
ORDER BY `COLUMNS`.`ORDINAL_POSITION` ASC LIMIT 1) AS `name`
FROM `a`
The problem is like this I'm getting only the string from the sub select not the column content of the main query.
(what I want to do in this case, is to return the value of the first column that is varchar)
You can do this with a dynamic query constructed in a stored procedure which you then call with the appropriate parameters.
I'd put the example in a sqlfiddle if I could find a way of querying the information_schema but you can just copy/paste this into your normal client and it should work.
I've gone a little further than your example by allowing you to select the id and first varchar column from any table but you can easily adjust the procedure and hardcode the database and table names if that's all you require.
CREATE DATABASE IF NOT EXISTS `mydb`;
USE `mydb`;
CREATE TABLE IF NOT EXISTS `mydb`.`tableA` (
id INT auto_increment primary key,
char_col char(3),
varchar_col varchar(25)
);
-- clear out any existing records
TRUNCATE `mydb`.`tableA`;
INSERT INTO `mydb`.`tableA` VALUES (null,'abc','varchar value abc');
INSERT INTO `mydb`.`tableA` VALUES (null,'def','varchar value def');
INSERT INTO `mydb`.`tableA` VALUES (null,'ghi','varchar value ghi');
INSERT INTO `mydb`.`tableA` VALUES (null,'klm','varchar value klm');
DELIMITER //
DROP PROCEDURE IF EXISTS `mydb`.`dyntest` //
CREATE PROCEDURE `mydb`.`dyntest` (IN dbname VARCHAR(64), IN tname VARCHAR(64))
BEGIN
DECLARE colname VARCHAR(64);
-- get the column name (as your example)
SELECT `COLUMN_NAME` INTO colname
FROM `information_schema`.`COLUMNS`
WHERE `TABLE_SCHEMA` = dbname
AND `TABLE_NAME` = tname
AND `DATA_TYPE` = 'VARCHAR'
ORDER BY `COLUMNS`.`ORDINAL_POSITION` ASC LIMIT 1;
-- construct the query
SET #sqlquery = CONCAT('SELECT `id`,`', colname , '` FROM `' , dbname, '`.`', tname, '`');
-- Prepare and execute
PREPARE stmt FROM #sqlquery;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END //
DELIMITER ;
This returns
mysql> CALL `mydb`.`dyntest`('mydb','tableA');
+----+-------------------+
| id | varchar_col |
+----+-------------------+
| 1 | varchar value abc |
| 2 | varchar value def |
| 3 | varchar value ghi |
| 4 | varchar value klm |
+----+-------------------+
4 rows in set (0.06 sec)

GROUP CONCAT REPLACE sep strings

I am using the below statement to pull a unique value off a table from a field that is separated with "|". It looks like this: "40|180|408|360|40|1s66|80|59" My problem is that I can't seem to get this statement to allow me to pull a primary key field, id, and assign it to each unique item from the field string so I can actually use it later. Ideally, ID # 27 should be assigned to each of the values on this temp table from "40|180|408|360|40|1s66|80|59". Can anyone help with the below statement to allow me to insert and assign field id to this from table BlogImageBundle?
CREATE TEMPORARY TABLE test (postId INT(11), val CHAR(255));
SET #S1 = CONCAT("INSERT INTO test (val) VALUES ('",REPLACE((SELECT GROUP_CONCAT( DISTINCT `images`)
AS data FROM `BlogImageBundle`), "|", "'),('"),"');");
PREPARE stmt1 FROM #s1;
EXECUTE stmt1;
SELECT DISTINCT(val) FROM test;
This works but ONLY for one record. You will need to add another while loop that keeps track of the rowcount to deal with multiple records : http://sqlfiddle.com/#!6/d62f7/1
CREATE TABLE #Test
(
ID INT,
Initial VARCHAR(MAX)
)
CREATE TABLE #Test2
(
ID INT,
Final VARCHAR(MAX)
)
INSERT INTO #Test VALUES(27,'40|180|408|360|40|1s66|80|59')
DECLARE #String VARCHAR(MAX)
SET #string = (SELECT Initial FROM #Test)
DECLARE #StringInput VARCHAR(MAX)
SET #stringInput = (SELECT Initial FROM #Test)
WHILE LEN(#StringInput) > 0
BEGIN
SET #String = LEFT(#StringInput,ISNULL(NULLIF(CHARINDEX('|', #StringInput) - 1, -1),LEN(#StringInput)))
SET #StringInput = SUBSTRING(#StringInput,ISNULL(NULLIF(CHARINDEX('|', #StringInput), 0),LEN(#StringInput)) + 1, LEN(#StringInput))
INSERT INTO #Test2 (ID, Final)
SELECT ID,#string FROM #Test
END

How to update/Edit table through CSVs?

I stored 2 contact numbers in a table corresponding to one companyID in companycontactno. table. I did that using extracting values from CSV in Stored procedure.
If I want to edit those contact numbers Corresponding to company ID how am I going to do that?
This is the storedProcedure I will use for editing contact numbers .. I am having difficulty in updating it .. please help
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
CREATE PROCEDURE dbo.EditCompanyDetails
(
#OldCompanyName varchar(max),
#NewCompanyName varchar(max),
#newAddress varchar(max),
#newMailID varchar(max),
#Temp varchar(8000)
)
AS
BEGIN
SET NOCOUNT ON;
declare #compID int
DECLARE #c int, #a varchar(max), #id int, #variable varchar(8000), #max int
DECLARE #Temp_Table table (serial_no int Identity(1,1), value varchar(max))
--PROCEDURE--
--Editing Company Table--
set #compID=(Select Company.CompanyID from Company where Company.CompanyName=#OldCompanyName)
update Company
set CompanyName=#NewCompanyName, [Address]=#newAddress, Email=#newMailID
where Company.CompanyID=#compID
--For CONTACT NUMBERS
--Using Table to store CSV seperately in each row--
select #c = CHARINDEX(',', #Temp)
while #c > 0
BEGIN
insert into #Temp_Table
select LEFT(#Temp, #c - 1)
select #Temp = right(#Temp, LEN(#Temp) - #c)
select #c = CHARINDEX(',', #Temp)
END
--Update Table CompanyContactNo
--CompanyContactNo have following Columns:
--CNoID (PK)
--CompanyID (references PK in Company table
--ContactNumber
set #max= (select MAX(serial_no) from #Temp_Table)
while #max > 0
BEGIN
set #variable = (select value from #Temp_Table where serial_no=#max)
update CompanyContactNo
set ContactNumber=#variable
where CompanyID=#compID
set #max = #max-1
END
End
GO
Assuming you will only have 2 rows, and you know that there are 2 contact rows in the CompanyContactNo table, you could execute 2 Update statements that would each affect a different row:
UPDATE CompanyContactNo
Set ContactNumber=
(SELECT value FROM #Temp_Table WHERE serial_no = (SELECT MAX(serial_no) FROM #Temp_Table))
WHERE
CompanyID=#compID
AND (CNoId = select MAX(CNoId) FROM CompanyContactNo WHERE CompanyID = #compID)
and then for second contact number:
UPDATE CompanyContactNo
SET ContactNumber=
(SELECT value FROM #Temp_Table WHERE serial_no = (SELECT MIN(serial_no) FROM #Temp_Table))
WHERE
CompanyID=#compID
AND (CNoId = select MIN(CNoId) FROM CompanyContactNo WHERE CompanyID = #compID)

How can I simulate an array variable in MySQL?

It appears that MySQL doesn't have array variables. What should I use instead?
There seem to be two alternatives suggested: A set-type scalar and temporary tables. The question I linked to suggests the former. But is it good practice to use these instead of array variables? Alternatively, if I go with sets, what would be the set-based idiom equivalent to foreach?
Well, I've been using temporary tables instead of array variables. Not the greatest solution, but it works.
Note that you don't need to formally define their fields, just create them using a SELECT:
DROP TEMPORARY TABLE IF EXISTS my_temp_table;
CREATE TEMPORARY TABLE my_temp_table
SELECT first_name FROM people WHERE last_name = 'Smith';
(See also Create temporary table from select statement without using Create Table.)
You can achieve this in MySQL using WHILE loop:
SET #myArrayOfValue = '2,5,2,23,6,';
WHILE (LOCATE(',', #myArrayOfValue) > 0)
DO
SET #value = ELT(1, #myArrayOfValue);
SET #myArrayOfValue= SUBSTRING(#myArrayOfValue, LOCATE(',',#myArrayOfValue) + 1);
INSERT INTO `EXEMPLE` VALUES(#value, 'hello');
END WHILE;
EDIT:
Alternatively you can do it using UNION ALL:
INSERT INTO `EXEMPLE`
(
`value`, `message`
)
(
SELECT 2 AS `value`, 'hello' AS `message`
UNION ALL
SELECT 5 AS `value`, 'hello' AS `message`
UNION ALL
SELECT 2 AS `value`, 'hello' AS `message`
UNION ALL
...
);
Try using FIND_IN_SET() function of MySql
e.g.
SET #c = 'xxx,yyy,zzz';
SELECT * from countries
WHERE FIND_IN_SET(countryname,#c);
Note: You don't have to SET variable in StoredProcedure if you are passing parameter with CSV values.
Nowadays using a JSON array would be an obvious answer.
Since this is an old but still relevant question I produced a short example.
JSON functions are available since mySQL 5.7.x / MariaDB 10.2.3
I prefer this solution over ELT() because it's really more like an array and this 'array' can be reused in the code.
But be careful: It (JSON) is certainly much slower than using a temporary table. Its just more handy. imo.
Here is how to use a JSON array:
SET #myjson = '["gmail.com","mail.ru","arcor.de","gmx.de","t-online.de",
"web.de","googlemail.com","freenet.de","yahoo.de","gmx.net",
"me.com","bluewin.ch","hotmail.com","hotmail.de","live.de",
"icloud.com","hotmail.co.uk","yahoo.co.jp","yandex.ru"]';
SELECT JSON_LENGTH(#myjson);
-- result: 19
SELECT JSON_VALUE(#myjson, '$[0]');
-- result: gmail.com
And here a little example to show how it works in a function/procedure:
DELIMITER //
CREATE OR REPLACE FUNCTION example() RETURNS varchar(1000) DETERMINISTIC
BEGIN
DECLARE _result varchar(1000) DEFAULT '';
DECLARE _counter INT DEFAULT 0;
DECLARE _value varchar(50);
SET #myjson = '["gmail.com","mail.ru","arcor.de","gmx.de","t-online.de",
"web.de","googlemail.com","freenet.de","yahoo.de","gmx.net",
"me.com","bluewin.ch","hotmail.com","hotmail.de","live.de",
"icloud.com","hotmail.co.uk","yahoo.co.jp","yandex.ru"]';
WHILE _counter < JSON_LENGTH(#myjson) DO
-- do whatever, e.g. add-up strings...
SET _result = CONCAT(_result, _counter, '-', JSON_VALUE(#myjson, CONCAT('$[',_counter,']')), '#');
SET _counter = _counter + 1;
END WHILE;
RETURN _result;
END //
DELIMITER ;
SELECT example();
Dont know about the arrays, but there is a way to store comma-separated lists in normal VARCHAR column.
And when you need to find something in that list you can use the FIND_IN_SET() function.
I know that this is a bit of a late response, but I recently had to solve a similar problem and thought that this may be useful to others.
Background
Consider the table below called 'mytable':
The problem was to keep only latest 3 records and delete any older records whose systemid=1 (there could be many other records in the table with other systemid values)
It would be good if you could do this simply using the statement
DELETE FROM mytable WHERE id IN (SELECT id FROM `mytable` WHERE systemid=1 ORDER BY id DESC LIMIT 3)
However this is not yet supported in MySQL and if you try this then you will get an error like
...doesn't yet support 'LIMIT & IN/ALL/SOME subquery'
So a workaround is needed whereby an array of values is passed to the IN selector using variable. However, as variables need to be single values, I would need to simulate an array. The trick is to create the array as a comma separated list of values (string) and assign this to the variable as follows
SET #myvar = (SELECT GROUP_CONCAT(id SEPARATOR ',') AS myval FROM (SELECT * FROM `mytable` WHERE systemid=1 ORDER BY id DESC LIMIT 3 ) A GROUP BY A.systemid);
The result stored in #myvar is
5,6,7
Next, the FIND_IN_SET selector is used to select from the simulated array
SELECT * FROM mytable WHERE FIND_IN_SET(id,#myvar);
The combined final result is as follows:
SET #myvar = (SELECT GROUP_CONCAT(id SEPARATOR ',') AS myval FROM (SELECT * FROM `mytable` WHERE systemid=1 ORDER BY id DESC LIMIT 3 ) A GROUP BY A.systemid);
DELETE FROM mytable WHERE FIND_IN_SET(id,#myvar);
I am aware that this is a very specific case. However it can be modified to suit just about any other case where a variable needs to store an array of values.
I hope that this helps.
DELIMITER $$
CREATE DEFINER=`mysqldb`#`%` PROCEDURE `abc`()
BEGIN
BEGIN
set #value :='11,2,3,1,';
WHILE (LOCATE(',', #value) > 0) DO
SET #V_DESIGNATION = SUBSTRING(#value,1, LOCATE(',',#value)-1);
SET #value = SUBSTRING(#value, LOCATE(',',#value) + 1);
select #V_DESIGNATION;
END WHILE;
END;
END$$
DELIMITER ;
Maybe create a temporary memory table with columns (key, value) if you want associative arrays. Having a memory table is the closest thing to having arrays in mysql
Here’s how I did it.
First, I created a function that checks whether a Long/Integer/whatever value is in a list of values separated by commas:
CREATE DEFINER = 'root'#'localhost' FUNCTION `is_id_in_ids`(
`strIDs` VARCHAR(255),
`_id` BIGINT
)
RETURNS BIT(1)
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
DECLARE strLen INT DEFAULT 0;
DECLARE subStrLen INT DEFAULT 0;
DECLARE subs VARCHAR(255);
IF strIDs IS NULL THEN
SET strIDs = '';
END IF;
do_this:
LOOP
SET strLen = LENGTH(strIDs);
SET subs = SUBSTRING_INDEX(strIDs, ',', 1);
if ( CAST(subs AS UNSIGNED) = _id ) THEN
-- founded
return(1);
END IF;
SET subStrLen = LENGTH(SUBSTRING_INDEX(strIDs, ',', 1));
SET strIDs = MID(strIDs, subStrLen+2, strLen);
IF strIDs = NULL or trim(strIds) = '' THEN
LEAVE do_this;
END IF;
END LOOP do_this;
-- not founded
return(0);
END;
So now you can search for an ID in a comma-separated list of IDs, like this:
select `is_id_in_ids`('1001,1002,1003',1002);
And you can use this function inside a WHERE clause, like this:
SELECT * FROM table1 WHERE `is_id_in_ids`('1001,1002,1003',table1_id);
This was the only way I found to pass an "array" parameter to a PROCEDURE.
I'm surprised none of the answers mention ELT/FIELD.
ELT/FIELD works very similar to an array especially if you have static data.
FIND_IN_SET also works similar but doesn't have a built in complementary
function but it's easy enough to write one.
mysql> select elt(2,'AA','BB','CC');
+-----------------------+
| elt(2,'AA','BB','CC') |
+-----------------------+
| BB |
+-----------------------+
1 row in set (0.00 sec)
mysql> select field('BB','AA','BB','CC');
+----------------------------+
| field('BB','AA','BB','CC') |
+----------------------------+
| 2 |
+----------------------------+
1 row in set (0.00 sec)
mysql> select find_in_set('BB','AA,BB,CC');
+------------------------------+
| find_in_set('BB','AA,BB,CC') |
+------------------------------+
| 2 |
+------------------------------+
1 row in set (0.00 sec)
mysql> SELECT SUBSTRING_INDEX(SUBSTRING_INDEX('AA,BB,CC',',',2),',',-1);
+-----------------------------------------------------------+
| SUBSTRING_INDEX(SUBSTRING_INDEX('AA,BB,CC',',',2),',',-1) |
+-----------------------------------------------------------+
| BB |
+-----------------------------------------------------------+
1 row in set (0.01 sec)
Is an array variable really necessary?
I ask because I originally landed here wanting to add an array as a MySQL table variable. I was relatively new to database design and trying to think of how I'd do it in a typical programming language fashion.
But databases are different. I thought I wanted an array as a variable, but it turns out that's just not a common MySQL database practice.
Standard Practice
The alternative solution to arrays is to add an additional table, and then reference your original table with a foreign key.
As an example, let's imagine an application that keeps track of all the items every person in a household wants to buy at the store.
The commands for creating the table I originally envisioned would have looked something like this:
#doesn't work
CREATE TABLE Person(
name VARCHAR(50) PRIMARY KEY
buy_list ARRAY
);
I think I envisioned buy_list to be a comma-separated string of items or something like that.
But MySQL doesn't have an array type field, so I really needed something like this:
CREATE TABLE Person(
name VARCHAR(50) PRIMARY KEY
);
CREATE TABLE BuyList(
person VARCHAR(50),
item VARCHAR(50),
PRIMARY KEY (person, item),
CONSTRAINT fk_person FOREIGN KEY (person) REFERENCES Person(name)
);
Here we define a constraint named fk_person. It says that the 'person' field in BuyList is a foreign key. In other words, it's a primary key in another table, specifically the 'name' field in the Person table, which is what REFERENCES denotes.
We also defined the combination of person and item to be the primary key, but technically that's not necessary.
Finally, if you want to get all the items on a person's list, you can run this query:
SELECT item FROM BuyList WHERE person='John';
This gives you all the items on John's list. No arrays necessary!
This is my solution to use a variable containing a list of elements.
You can use it in simple queries (no need to use store procedures or create tables).
I found somewhere else on the site the trick to use the JSON_TABLE function (it works in mysql 8, I dunno of it works in other versions).
set #x = '1,2,3,4' ;
select c.NAME
from colors c
where
c.COD in (
select *
from json_table(
concat('[',#x,']'),
'$[*]' columns (id int path '$') ) t ) ;
Also, you may need to manage the case of one or more variables set to empty_string.
In this case I added another trick (the query does not return error even if x, y, or both x and y are empty strings):
set #x = '' ;
set #y = 'yellow' ;
select c.NAME
from colors
where
if(#y = '', 1 = 1, c.NAME = #y)
and if(#x = '', 1, c.COD) in (
select *
from json_table(
concat('[',if(#x = '', 1, #x),']'),
'$[*]' columns (id int path '$') ) t) ;
This works fine for list of values:
SET #myArrayOfValue = '2,5,2,23,6,';
WHILE (LOCATE(',', #myArrayOfValue) > 0)
DO
SET #value = ELT(1, #myArrayOfValue);
SET #STR = SUBSTRING(#myArrayOfValue, 1, LOCATE(',',#myArrayOfValue)-1);
SET #myArrayOfValue = SUBSTRING(#myArrayOfValue, LOCATE(',', #myArrayOfValue) + 1);
INSERT INTO `Demo` VALUES(#STR, 'hello');
END WHILE;
Both versions using sets didn't work for me (tested with MySQL 5.5). The function ELT() returns the whole set. Considering the WHILE statement is only avaible in PROCEDURE context i added it to my solution:
DROP PROCEDURE IF EXISTS __main__;
DELIMITER $
CREATE PROCEDURE __main__()
BEGIN
SET #myArrayOfValue = '2,5,2,23,6,';
WHILE (LOCATE(',', #myArrayOfValue) > 0)
DO
SET #value = LEFT(#myArrayOfValue, LOCATE(',',#myArrayOfValue) - 1);
SET #myArrayOfValue = SUBSTRING(#myArrayOfValue, LOCATE(',',#myArrayOfValue) + 1);
END WHILE;
END;
$
DELIMITER ;
CALL __main__;
To be honest, i don't think this is a good practice. Even if its realy necessary, this is barely readable and quite slow.
Isn't the point of arrays to be efficient? If you're just iterating through values, I think a cursor on a temporary (or permanent) table makes more sense than seeking commas, no? Also cleaner. Lookup "mysql DECLARE CURSOR".
For random access a temporary table with numerically indexed primary key. Unfortunately the fastest access you'll get is a hash table, not true random access.
Another way to see the same problem.
Hope helpfull
DELIMITER $$
CREATE PROCEDURE ARR(v_value VARCHAR(100))
BEGIN
DECLARE v_tam VARCHAR(100);
DECLARE v_pos VARCHAR(100);
CREATE TEMPORARY TABLE IF NOT EXISTS split (split VARCHAR(50));
SET v_tam = (SELECT (LENGTH(v_value) - LENGTH(REPLACE(v_value,',',''))));
SET v_pos = 1;
WHILE (v_tam >= v_pos)
DO
INSERT INTO split
SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(v_value,',',v_pos),',', -1);
SET v_pos = v_pos + 1;
END WHILE;
SELECT * FROM split;
DROP TEMPORARY TABLE split;
END$$
CALL ARR('1006212,1006404,1003404,1006505,444,');
If we have one table like that
mysql> select * from user_mail;
+------------+-------+
| email | user |
+------------+-------+-
| email1#gmail | 1 |
| email2#gmail | 2 |
+------------+-------+--------+------------+
and the array table:
mysql> select * from user_mail_array;
+------------+-------+-------------+
| email | user | preferences |
+------------+-------+-------------+
| email1#gmail | 1 | 1 |
| email1#gmail | 1 | 2 |
| email1#gmail | 1 | 3 |
| email1#gmail | 1 | 4 |
| email2#gmail | 2 | 5 |
| email2#gmail | 2 | 6 |
We can select the rows of the second table as one array with CONCAT function:
mysql> SELECT t1.*, GROUP_CONCAT(t2.preferences) AS preferences
FROM user_mail t1,user_mail_array t2
where t1.email=t2.email and t1.user=t2.user
GROUP BY t1.email,t1.user;
+------------+-------+--------+------------+-------------+
| email | user | preferences |
+------------+-------+--------+------------+-------------+
|email1#gmail | 1 | 1,3,2,4 |
|email2#gmail | 2 | 5,6 |
+------------+-------+--------+------------+-------------+
In MYSQL version after 5.7.x, you can use JSON type to store an array. You can get value of an array by a key via MYSQL.
Inspired by the function ELT(index number, string1, string2, string3,…),I think the following example works as an array example:
set #i := 1;
while #i <= 3
do
insert into table(val) values (ELT(#i ,'val1','val2','val3'...));
set #i = #i + 1;
end while;
Hope it help.
Here is an example for MySQL for looping through a comma delimited string.
DECLARE v_delimited_string_access_index INT;
DECLARE v_delimited_string_access_value VARCHAR(255);
DECLARE v_can_still_find_values_in_delimited_string BOOLEAN;
SET v_can_still_find_values_in_delimited_string = true;
SET v_delimited_string_access_index = 0;
WHILE (v_can_still_find_values_in_delimited_string) DO
SET v_delimited_string_access_value = get_from_delimiter_split_string(in_array, ',', v_delimited_string_access_index); -- get value from string
SET v_delimited_string_access_index = v_delimited_string_access_index + 1;
IF (v_delimited_string_access_value = '') THEN
SET v_can_still_find_values_in_delimited_string = false; -- no value at this index, stop looping
ELSE
-- DO WHAT YOU WANT WITH v_delimited_string_access_value HERE
END IF;
END WHILE;
this uses the get_from_delimiter_split_string function defined here: https://stackoverflow.com/a/59666211/3068233
I Think I can improve on this answer. Try this:
The parameter 'Pranks' is a CSV. ie. '1,2,3,4.....etc'
CREATE PROCEDURE AddRanks(
IN Pranks TEXT
)
BEGIN
DECLARE VCounter INTEGER;
DECLARE VStringToAdd VARCHAR(50);
SET VCounter = 0;
START TRANSACTION;
REPEAT
SET VStringToAdd = (SELECT TRIM(SUBSTRING_INDEX(Pranks, ',', 1)));
SET Pranks = (SELECT RIGHT(Pranks, TRIM(LENGTH(Pranks) - LENGTH(SUBSTRING_INDEX(Pranks, ',', 1))-1)));
INSERT INTO tbl_rank_names(rank)
VALUES(VStringToAdd);
SET VCounter = VCounter + 1;
UNTIL (Pranks = '')
END REPEAT;
SELECT VCounter AS 'Records added';
COMMIT;
END;
This method makes the searched string of CSV values progressively shorter with each iteration of the loop, which I believe would be better for optimization.
I would try something like this for multiple collections. I'm a MySQL beginner. Sorry about the function names, couldn't decide on what names would be best.
delimiter //
drop procedure init_
//
create procedure init_()
begin
CREATE TEMPORARY TABLE if not exists
val_store(
realm varchar(30)
, id varchar(30)
, val varchar(255)
, primary key ( realm , id )
);
end;
//
drop function if exists get_
//
create function get_( p_realm varchar(30) , p_id varchar(30) )
returns varchar(255)
reads sql data
begin
declare ret_val varchar(255);
declare continue handler for 1146 set ret_val = null;
select val into ret_val from val_store where id = p_id;
return ret_val;
end;
//
drop procedure if exists set_
//
create procedure set_( p_realm varchar(30) , p_id varchar(30) , p_val varchar(255) )
begin
call init_();
insert into val_store (realm,id,val) values (p_realm , p_id , p_val) on duplicate key update val = p_val;
end;
//
drop procedure if exists remove_
//
create procedure remove_( p_realm varchar(30) , p_id varchar(30) )
begin
call init_();
delete from val_store where realm = p_realm and id = p_id;
end;
//
drop procedure if exists erase_
//
create procedure erase_( p_realm varchar(30) )
begin
call init_();
delete from val_store where realm = p_realm;
end;
//
call set_('my_array_table_name','my_key','my_value');
select get_('my_array_table_name','my_key');
Rather than Saving data as a array or in one row only you should be making diffrent rows for every value received. This will make it much simpler to understand rather than putting all together.
Have you tried using PHP's serialize()?
That allows you to store the contents of a variable's array in a string PHP understands and is safe for the database (assuming you've escaped it first).
$array = array(
1 => 'some data',
2 => 'some more'
);
//Assuming you're already connected to the database
$sql = sprintf("INSERT INTO `yourTable` (`rowID`, `rowContent`) VALUES (NULL, '%s')"
, serialize(mysql_real_escape_string($array, $dbConnection)));
mysql_query($sql, $dbConnection) or die(mysql_error());
You can also do the exact same without a numbered array
$array2 = array(
'something' => 'something else'
);
or
$array3 = array(
'somethingNew'
);