MySQL - search for formatted conbinations of column values - mysql

I have a database where a table has, amongst other, two columns: group and article. Both are int but is always formatted before shown to the user:
The group is always shown as 2 digits, with leading zeros if
needed.
The article is always shown as 4 digits, with leading zeros
if needed.
The group and article are separated with a dash -
Example:
An item with group = 1 and article = 23 will be shown to the user as 01-0023.
Moving on.
I have a php-script in which as user can search for an article. The user will of course write the article in the formatted way. The script I have today, uses regular expressions to separate a search for an article from free-text and then isolates the group and article from each other before searching the database.
My question is, is it possible to pass the formatted string (e.g. 01-0023) in the query instead and if so, how would I manipulate the SQL?

If you want a comparison to a value shown to the user, you can do:
where `group` = substring_index(#Group_Article, '-', 1) + 0 AND
`article` = substring_index(#Group_Article, '-', -1) + 0

SqlFiddleDemo
SELECT CAST(LEFT('01-0023', 2) AS UNSIGNED) AS `Group`
,CAST(RIGHT('01-0023',4) AS UNSIGNED) AS `Article`

Alter your table like that
ALTER TABLE `my_table`
CHANGE COLUMN `group` `group` INT(2) ZEROFILL,
CHANGE COLUMN `article` `article` INT(4) ZEROFILL;
Create a view
CREATE VIEW `v_my_table` AS
SELECT id,article,`group`,CONCAT(`group`,"-",`article`) AS conbined FROM my_table;
Use your view for select statements
mysql> select * from v_my_table;
+----+---------+-------+----------+
| id | article | group | conbined |
+----+---------+-------+----------+
| 1 | 0005 | 03 | 03-0005 |
| 2 | 0005 | 03 | 03-0005 |
| 3 | 0021 | 12 | 12-0021 |
| 4 | 0212 | 55 | 55-0212 |
| 5 | 2113 | 04 | 04-2113 |
+----+---------+-------+----------+

Related

How to loop through an array in SQL where clause?

I have a MySQL table which has the following columns and records:
| Name | Total | GivenBy |
| ---- | -------- | ------------ |
| Z | 200 |['A','B','C'] |
| X | 240 |['A','D','C'] |
I would like to extract Record No. 1 on the basis of 3rd column where the SQL query would be like:
SELECT * FROM mytable WHERE GivenBy='B';
Is there a way I can loop through the list in third column and take out the respective string as required in the SQL WHERE clause in a single query?
Please note that I cannot add more columns in the table.
If you can please provide the query as MySQL compatible, I would really appreciate it.
The "array" you show isn't quite valid JSON, but if you use double-quotes instead of single-quotes, you can use JSON_TABLE() to do this:
CREATE TABLE MyTable
(
Name CHAR(1) PRIMARY KEY,
Total INT NOT NULL,
GivenBy JSON NOT NULL
);
INSERT INTO MyTable VALUES
('Z', 200, '["A","B","C"]'),
('X', 240, '["A","D","C"]');
SELECT Name, Total, g.Value
FROM MyTable
CROSS JOIN JSON_TABLE(GivenBy, '$[*]' COLUMNS(Value CHAR(1) PATH '$')) AS g;
+------+-------+-------+
| name | total | value |
+------+-------+-------+
| X | 240 | A |
| X | 240 | D |
| X | 240 | C |
| Z | 200 | A |
| Z | 200 | B |
| Z | 200 | C |
+------+-------+-------+
But the best choice is not to store "arrays" in MySQL. Store the values one per row in a second table.
You can use the "like" keyword with regex to match your requirements in the third column.
select * from table where givenBy like "%B%";
Something similar would work.
You need to run a script:
Retrieve the list of unique values in the GivenBy column using the following query:
SELECT DISTINCT JSON_EXTRACT(GivenBy, '$[*]') AS GivenByValues
FROM mytable;
Loop through the list of unique values, and for each value, run a query that uses that value in the WHERE clause:
SELECT *
FROM mytable
WHERE JSON_SEARCH(GivenBy, 'one', [current_value_from_loop]) IS NOT NULL;

MySQL - How to order duplicate rows in a key value pair table based on multiple columns?

So I have the following key/value pair table, where users submit data through a form and each question on the form is added to the table here as an individual row. Submission_id identifies each form submission.
+----+---------------+--------------+--------+
| id | submission_id | key | value |
+----+---------------+--------------+--------+
| 1 | 10 | manufacturer | Apple |
| 2 | 10 | model | 5s |
| 3 | 10 | firstname | Paul |
| 4 | 15 | manufacturer | Apple |
| 5 | 15 | model | 5s |
| 6 | 15 | firstname | Paul |
| 7 | 20 | manufacturer | Apple |
| 8 | 20 | model | 5s |
| 9 | 20 | firstname | Andrew |
+----+---------------+--------------+--------+
From the data above you can see that the submissions with id of 10 and 15 both have the same values (just different submission id). This is basically because a user has submitted the same form twice and so is a duplicate.
Im trying to find a way to order these table where the any duplicate submissions appear together in order. Given the above table I am trying to build a query that gives me the result as below:
+---------------+
| submission_id |
+---------------+
| 10 |
| 15 |
| 20 |
+---------------+
So I want to check to see if a submission where the manufacturer, model and firstname keys have the same value. If it does then these get the submission id and place them adjacently in the result. In the actual table there are other keys, but I only want to match duplicates based on these 3 keys (manufacturer, model, firstname).
I’ve been going back and forth to the drawing board quite some time now and have tried looking for some possible solutions but cannot get something reliable.
That's not a key value table. It's usually called an Entity-Attribute-Value table/relation/pattern.
Looking at the problem, it would be trivial if the table were laid out in conventional 1st + 2nd Normal form - you just do a join on the values, group by those and take a count....
SELECT manufacturer, model, firstname, COUNT(DISTINCT submission_id)
FROM atable
GROUP BY manufacturer, model, firstname
HAVING COUNT(DISTINCT submission_id)>1;
Or a join....
SELECT a.manufacturer, a.model, a.firstname
, a.submission_id, b.submission_id
FROM atable a
JOIN atable b
ON a.manufacturer=b.manufacturer
AND a.model=b.model
AND a.firstname=b.firstname
WHERE a.submission_id<b.submission_id
;
Or using sorting and comparing adjacent rows....
SELECT *
FROM
(
SELECT #prev.submission_id AS prev_submission_id
, #prev.manufacturer AS prev_manufacturer
, #prev.model AS prev_model
, #prev.firstname AS pref_firstname
, a.submission_id
, a.manufacturer
, a.model
, set #prev.submission_id:=a.submission_id as currsid
, set #prev.manufacturer:=a.manufacturer as currman
, set #prev.model:=a.model as currmodel
, set #prev.firstname=a.forstname as currname
FROM atable
ORDER BY manufacturer, model, firstname, submission_id
)
WHERE prev_manufacturer=manufacturer
AND prev_model=model
AND prev_firstname=firstname
AND prev_submission_id<>submission_id;
So the solution is to simply make your data look like a normal relation....
SELECT ilv.values
, COUNT(ilv.submission_id)
, GROUP_CONCAT(ilv.submission_id)
FROM
(SELECT a.submission_id
, GROUP_CONCAT(CONCAT(a.key, '=',a.value)) AS values
FROM atable a
GROUP BY a.submission_id
) ilv
GROUP BY ilv.values
HAVING COUNT(ilv.submission_id)>1;
Hopefully the join and sequence based solutions should now be obvious.

How to find data based on comma separated parameter in comma separated data in my SQL query

We have below data,
plant table
----------------------------
| name | classification |
| A | 1,4,7 |
| B | 2,3,7 |
| C | 3,4,9,8 |
| D | 1,5,6,9 |
Now from front end side, they will send multiple parameter like "4,9",
and the objective output should be like this
plant table
---------------------------
| name | classification |
| A | 1,4,7 |
| C | 3,4,9,8 |
| D | 1,5,6,9 |
Already tried with FIND_IN_SET code, but only able to fetch only with 1 parameter
select * from plant o where find_in_set('4',classification ) <> 0
Another solution is by doing multiple queries, for example if the parameter is "4,9" then we do loop the query two times with parameter 4 and 9, but actually that solution will consume so much resources since the data is around 10000+ rows and the parameter itself actually can be more than 5 params
If the table design is in bad practice then OK but we are unable to change it since the table is in third party
Any solution or any insight will be appreciated,
Thank you
Schema (MySQL v8.0)
CREATE TABLE broken_table (name CHAR(12) PRIMARY KEY,classification VARCHAR(12));
INSERT INTO broken_table VALUES
('A','1,4,7'),
('B','2,3,7'),
('C','3,4,9,8'),
('D','1,5,6,9');
Query #1
WITH RECURSIVE cte (n) AS
(
SELECT 1
UNION ALL
SELECT n + 1 FROM cte WHERE n < 5
)
SELECT DISTINCT x.name, x.classification FROM broken_table x JOIN cte
WHERE SUBSTRING_INDEX(SUBSTRING_INDEX(classification,',',n),',',-1) IN (4,9);
name
classification
A
1,4,7
C
3,4,9,8
D
1,5,6,9
View on DB Fiddle
EDIT:
or, for older versions...
SELECT DISTINCT x.name, x.classification FROM broken_table x JOIN
(
SELECT 1 n UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5
) cte
WHERE SUBSTRING_INDEX(SUBSTRING_INDEX(classification,',',n),',',-1) IN (4,9)
Let's just avoid the CSV altogether and fix your table design:
plant table
----------------------------
| name | classification |
| A | 1 |
| A | 4 |
| A | 7 |
| B | 2 |
| B | 3 |
| B | 7 |
| ... | ... |
Now with this design, you may use the following statement:
SELECT *
FROM plant
WHERE classification IN (?);
To the ? placeholder, you may bind your collection of values to match (e.g. (4,9)).
You want or so you can use regular expressions. If everything were one digit:
where classification regexp replace('4,9', ',', '|')
However, this would match 42 and 19, which I'm guessing you do not want. So, make this a little more complicated so you have comma delimiters:
where classification regexp concat('(,|^)', replace('4,9', ',', ',|,'), '(,|$)')

Splitting a cell in mySQL into multiple rows while keeping the same "ID"

In my table I have two columns "sku" and "fitment". The sku represents a part and the fitment represents all the vehicles this part will fit on. The problem is, in the fitment cells, there could be up to 20 vehicles in there, separated by ^^. For example
**sku -- fitment**
part1 -- Vehichle 1 information ^^ vehichle 2 information ^^ vehichle 3 etc
I am looking to split the cells in the fitment column, so it would look like this:
**sku -- fitment**
part1 -- Vehicle 1 information
part1 -- Vehicle 2 information
part1 -- Vehicle 3 information
Is this possible to do? And if so, would a mySQL db be able to handle hundreds of thousands of items "splitting" like this? I imagine it would turn my db of around 250k lines to about 20million lines. Any help is appreciated!
Also a little more background, this is going to be used for a drill down search function so I would be able to match up parts to vehicles (year, make, model, etc) so if you have a better solution, I am all ears.
Thanks
Possible duplicate of this: Split value from one field to two
Unfortunately, MySQL does not feature a split string function. As in the link above indicates there are User-defined Split function's.
A more verbose version to fetch the data can be the following:
SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(fitment, '^^', 1), '^^', -1) as fitmentvehicle1,
SUBSTRING_INDEX(SUBSTRING_INDEX(fitment, '^^', 2), '^^', -1) as fitmentvehicle2
....
SUBSTRING_INDEX(SUBSTRING_INDEX(fitment, '^^', n), '^^', -1) as fitmentvehiclen
FROM table_name;
Since your requirement asks for a normalized format (i.e. not separated by ^^) to be retrieved, it is always better to store it in that way in the first place. And w.r.t the DB size bloat up, you might want to look into possibilities of archiving older data and deleting the same from the table.
Also, you should partition your table using an efficient partitioning strategy based on your requirement. It would be more easier to archive and truncate a partition of the table itself, instead of row by row.
E.g.
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table (user_id INT NOT NULL PRIMARY KEY,stuff VARCHAR(50) NOT NULL);
INSERT INTO my_table VALUES (101,'1,2,3'),(102,'3,4'),(103,'4,5,6');
SELECT *
FROM my_table;
+---------+-------+
| user_id | stuff |
+---------+-------+
| 101 | 1,2,3 |
| 102 | 3,4 |
| 103 | 4,5,6 |
+---------+-------+
SELECT * FROM ints;
+---+
| i |
+---+
| 0 |
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
+---+
SELECT DISTINCT user_id
, SUBSTRING_INDEX(SUBSTRING_INDEX(stuff,',',i2.i*10+i1.i+1),',',-1) x
FROM my_table
, ints i1
, ints i2
ORDER
BY user_id,x;
+---------+---+
| user_id | x |
+---------+---+
| 101 | 1 |
| 101 | 2 |
| 101 | 3 |
| 102 | 3 |
| 102 | 4 |
| 103 | 4 |
| 103 | 5 |
| 103 | 6 |
+---------+---+

Clean duplicate rows on custom conditions

I have this problem managing notes. I started with the strategy to always INSERT new notes and SELECT the last one. Please don't laugh, I must have thought it was a good idea, but right now, the system is not even in all-out production and there's been 300k rows inserted in about a month. In two years, my system will fail. I need to merge duplicate lines. Here is the structure of my notes table:
CREATE TABLE IF NOT EXISTS `ps_notes` (
`CodeNTE` int(11) NOT NULL AUTO_INCREMENT,
`CodePRS` int(11) NOT NULL,
`CodeXYZ` int(11) NOT NULL,
`Type` char(3) NOT NULL,
`Focus` char(3) NOT NULL,
`Texte` tinytext NOT NULL,
`Date` datetime NOT NULL,
PRIMARY KEY (`CodeNTE`),
KEY `CodeXYZ` (`CodeXYZ`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=335068 ;
Notes can be related to a person CodePRS, are necessarily related to a Type, Focus and CodeXYZ. They have a Texte entry and sometime I want to know the Date.
CodeXYZ is a unique identifier for the entity to which the note is attached. This identifier can come from any table and therefore is not absolutely unique, hence comes the Type field. This field specifies from which table the parent row comes. The focus field distincts notes that refer to a same CodeXYZ and Type.
Here some sample lines:
+---------+------+-------+-------------+------------+
| CodeXYZ | Type | Focus | Texte | Date |
+---------+------+-------+-------------+------------+
| 30008 | ctr | adm | Whatever | 2013-05-09 |
| 30008 | ctr | adm | Whatever | 2013-06-10 |
| 30008 | ctr | adm | Lorem ipsum | 2013-06-11 |
| 30008 | ctr | clt | He's cool | 0000-00-00 |
| 2546 | ctr | sup | Another | 2013-02-11 |
| 2546 | ctr | sup | Another | 2013-02-11 |
| 2546 | ctr | sup | Another | 2013-02-19 |
+---------+------+-------+-------------+------------+
this is the output I'd like to have:
+---------+------+-------+-------------+-----------------------------------------+
| CodeXYZ | Type | Focus | Texte | Date |
+---------+------+-------+-------------+-----------------------------------------+
| 30008 | ctr | adm | Lorem ipsum | 2013-06-11 (I want the most recent one) |
| 30008 | ctr | clt | He's cool | 0000-00-00 |
| 2546 | ctr | sup | Another | 2013-02-11 |
| 2546 | ctr | sup | Another | 2013-02-19 |
+---------+------+-------+-------------+-----------------------------------------+
Conditions for merging
I want to merge lines that have the same CodeXYZ,Type and Focus when Focus is not 'sup'.
When Focus is 'sup' I want to merge the lines that have the same CodeXYZ,Type,Focus and Date
Always I want to keep the most recent one
So I ran this query to merge rows in a temporary table:
INSERT INTO notes_tmp (CodePRS,CodeXYZ,Type,Focus,Texte,Date)
SELECT CodePRS,CodeXYZ,Type,Focus,Texte,Date
FROM notes
GROUP BY CodeXYZ,Type,Focus
But that way, all lines will be merged even the last ones.
So I thought of this:
INSERT INTO notes_tmp (CodePRS,CodeXYZ,Type,Focus,Texte,Date)
SELECT CodePRS,CodeXYZ,Type,Focus,Texte,Date
FROM notes
WHERE Focus<>'sup'
GROUP BY CodeXYZ,Type,Focus
ORDER BY Date DESC
UNION
SELECT CodePRS,CodeXYZ,Type,Focus,Texte,Date
FROM notes
WHERE Focus='sup'
GROUP BY CodeXYZ,Type,Focus,Date
ORDER BY Date DESC
but UNION is not at the right place, I don't think I can use it in INSERT INTO ... SELECT sql syntax
Is there a way to manage copying those lines over in a single mysql call with multiple sub queries all ending up in the same table acording to separate conditions
you can use group_concat to merge text field and make other columns unique with group by. try this:
INSERT INTO notes_temp
SELECT CodeXYZ,Type, Focus,GROUP_CONCAT(Texte),Date
FROM notes WHERE Focus = 'sup'
GROUP BY CodeXYZ,Type, Focus,Date;
INSERT INTO notes_temp
SELECT CodeXYZ,Type, Focus,GROUP_CONCAT(Texte),MAX(Date)
FROM notes WHERE Focus <> 'sup'
GROUP BY CodeXYZ,Type, Focus;
check sqlfiddle
So with part of #Volkan answer, I could come up with this somehow strangely working sql to get the correct note out of my GROUP_CONCAT()
The case will get the last entry of the group concat. I used another separator (,,,) because commas do happen often in text. three in a row a little bit less.
INSERT INTO notes_temp
SELECT CodeXYZ,Type, Focus,Texte,Date
FROM notes WHERE Focus = 'sup'
GROUP BY CodeXYZ,Type, Focus,Date;
INSERT INTO notes_temp
SELECT
CodeXYZ,
Type,
Focus,
CASE
WHEN COUNT(Texte) > 1
THEN SUBSTR(GROUP_CONCAT(Texte SEPARATOR ",,,"),((LENGTH(GROUP_CONCAT(Texte SEPARATOR ",,,"))+2) - INSTR(REVERSE(GROUP_CONCAT(Texte SEPARATOR ",,,")),",,,")))
ELSE
Texte
END
AS Texte,
MAX(Date)
FROM notes WHERE Focus <> 'sup'
GROUP BY CodeXYZ,Type, Focus;