How to add a character to specific index in string - mysql

I have a db with email-addresses which are 15 characters in total.
Some are 14 characters long and they have the same in common, they miss a 0 in their name at the 3th index.
I had a what similar problem with the id numbers but that was easy to fix because i had to push 0 to the first index.
My expected results should be changing 'aa-001#test.me' to 'aa-0001#test.me'.

To add a 0 as 4th character in emails having 14 characters, you may simply use SUBSTR :
SELECT CONCAT(SUBSTR(email, 1, 3), '0', SUBSTR(email, 4))
FROM mytable
WHERE LENGTH(email) = 14
Another solution is to use REGEXP_REPLACE, with a regular expression that matches only on emails with 14 characters ; this avoids the need for a WHERE clause, as emails that do not match the pattern will be returned untouched :
SELECT REGEXP_REPLACE(email, '^(.{3})(.{11})$', CONCAT('$1', '0', '$2'))
FROM t
db<>fiddle here :
WITH t AS (SELECT 'aa-001#test.me' email UNION SELECT 'bb-0002#test.me')
SELECT CONCAT(SUBSTR(email, 1, 3), '0', SUBSTR(email, 4))
FROM t
WHERE LENGTH(email) = 14
| CONCAT(SUBSTR(email, 1, 3), '0', SUBSTR(email, 4)) |
| :----------------------------------------------------- |
| aa-0001#test.me |
WITH t AS (SELECT 'aa-001#test.me' email UNION SELECT 'bb-0002#test.me')
SELECT REGEXP_REPLACE(email, '^(.{3})(.{11})$', CONCAT('$1', '0', '$2'))
FROM t
| REGEXP_REPLACE(email, '^(.{3})(.{11})$', CONCAT('$1', '0', '$2')) |
| :---------------------------------------------------------------- |
| aa-0001#test.me |
| bb-0002#test.me

Related

How to convert tinyint back to boolean when reading from SQL?

`mysql> select * from movies;
+----------+-------+---------+
| movie_id | title | watched |
+----------+-------+---------+
| 1 | bo | 0 |
| 2 | NEW | 0 |
| 3 | NEW 2 | 0 |
+----------+-------+---------+
CREATE TABLE MOVIES (
movie_id INTEGER NOT NULL AUTO_INCREMENT,
title VARCHAR(50) NOT NULL,
watched BOOLEAN NOT NULL,
PRIMARY KEY (movie_id)
);
`
I am having to store the "watched" field as a tiny int instead of typical boolean, I am trying to find a way of converting it back to boolean when reading from table, so I dont have to loop through all responses and convert manually.
ie. {movie_id: 1, title: 'bo', watched: 0} ---> {movie_id: 1, title: 'bo', watched: false}
I have tried select cast but am unfamiliar with the syntax
MySQL saves Boolean as 0 and 1 as it handles all Boolean that way.
It is very practical, then you can add true or false from a comparison in a SUM without CASE WHEN or a FILTER
You need still to make a condition to give bak True or False, but they only text of course
SELECT
movie_id , title ,
CASE WHEN watched = 0 THEN 'False' ELSE 'True' END IF
This is similar to 'IF' in 'SELECT' statement - choose output value based on column values
Borrowing from the answer there,
SELECT movie_id, IF (watched > 0, true, false) as bwatched, ...
Note that this assumes your schema still includes "NOT NULL" for watched. Without that (or some extra code) NULL would become false.
The way "IF()" works is IF(expression , value / expression if true, v /e if false)

ORDER by multiple queries in MySql

I am trying to build a chat list page where latest sent/received contact is shown at the top from one table. For this, I have a table smshistory where i store sent/received sms with numbers where one is company phone and other is client phone number
CREATE TABLE IF NOT EXISTS `smshistory` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`fromnumber` varchar(20) NOT NULL,
`tonumber` varchar(20) NOT NULL,
`sms` varchar(20) NOT NULL,
`added` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=11 ;
--
-- Dumping data for table `smshistory`
--
INSERT INTO `smshistory` (`id`, `fromnumber`, `tonumber`, `sms`, `added`) VALUES
(1, 'companynum', 'client1num', 'Hello', '2021-07-16 12:28:23'),
(2, 'companynum', 'client2num', 'Hello', '2021-07-16 12:28:23'),
(3, 'companynum', 'client3num', 'Hello', '2021-07-16 12:28:23'),
(4, 'client1num', 'companynum', 'Hi', '2021-07-16 12:28:23'),
(5, 'companynum', 'client1num', 'Hello', '2021-07-16 12:28:23'),
(6, 'client1num', 'companynum', 'Hi', '2021-07-16 12:28:23'),
(7, 'client2num', 'companynum', 'Hi', '2021-07-16 12:28:23'),
(8, 'companynum', 'client2num', 'Hello', '2021-07-16 12:28:23'),
(9, 'client3num', 'companynum', 'Hi', '2021-07-16 12:28:23');
As first message will always be from company number, so I am showing DISTINCT list with:
SELECT DISTINCT (`tonumber`) FROM `smshistory` WHERE `fromnumber` = $companynum
Which gives me list like:
client1num
client2num
client3num
Requirement:
What I require is to show the DISTINCT with order of added DESC column in a way that if a client's number is in fromnumber or tonumber, it should show at top. So, according to my table, results should be:
client3num
client2num
client1num
Fiddle is at http://sqlfiddle.com/#!9/4256d1d/1
Any idea on how to achieve that?
In answer to your question you can use the following query:
SELECT distinct client_num from (
SELECT CASE WHEN fromnumber = 'companynum' THEN tonumber
ELSE fromnumber END client_num
FROM smshistory ORDER BY id DESC ) as a
For each of the rows that contain $companynum either in fromnumber or in tonumber you must extract the client's number with a CASE expression and use GROUP BY to remove duplicates.
Finally, sort the results by the max value of added:
SELECT CASE WHEN fromnumber = $companynum THEN tonumber ELSE fromnumber END client_num
FROM smshistory
WHERE $companynum IN (fromnumber, tonumber)
GROUP BY client_num
ORDER BY MAX(added) DESC
In answer to the question 'How do I sort a de-normalised collection of strings (that a follow a very constrained pattern) according to the numerals contained within those strings?', here's one method...
DROP TABLE IF EXISTS x;
CREATE TABLE x
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,bad_string VARCHAR(20) NOT NULL
);
INSERT INTO x VALUES (11,'client2num'),(17,'client3num'),(21,'client1num');
SELECT REPLACE(REPLACE(bad_string,'client',''),'num','') p FROM x;
+---+
| p |
+---+
| 2 |
| 3 |
| 1 |
+---+
...which can be rewritten as follows:
SELECT * FROM x ORDER BY REPLACE(REPLACE(bad_string,'client',''),'num','') DESC;
+----+------------+
| id | bad_string |
+----+------------+
| 17 | client3num |
| 11 | client2num |
| 21 | client1num |
+----+------------+

Sum the digits of a number in mysql

I have a query that returns an integer number from a mathematical calculation. I need to sum all the digits in that integer number.
Something like this:
select sumdigits(number) from dual
-- if number =123, output: 1+2+3 = 6
-- if number =100, output: 1+0+0 = 1
I wanted to test this using Fiddle or Rextester, but neither is working right now. So, your upvotes/downvotes will serve as the test:
SELECT CAST(SUBSTRING(number, 1, 1) AS UNSIGNED) + -- first digit
CAST(SUBSTRING(number, 2, 1) AS UNSIGNED) + -- second digit
CAST(SUBSTRING(number, 3, 1) AS UNSIGNED) AS the_sum -- third digit
FROM yourTable
This assumes a number with a max width of 3 digits, which is also zero padded (as you mentioned we can assume).
If you really need to do this in production, you should probably create a user defined function to handle such manipulations, edge cases, etc.
Update:
Going with the comment by #ThorstenKettner we could generalize this answer to a number field of any length by just adding more terms for each digit position. For example, if we wanted to cover numbers which could be up to four digits wide we could just add this term:
+ CAST(SUBSTRING(number, 4, 1) AS UNSIGNED)
which would either add a number if present, or would add zero if not present.
I'm ashamed to even suggest this but...
SELECT
foo,
CHAR_LENGTH(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(foo, '-', ''),
'0', ''),
'1', '1'),
'2', '22'),
'3', '333'),
'4', '4444'),
'5', '55555'),
'6', '666666'),
'7', '7777777'),
'8', '88888888'),
'9', '999999999')
) AS digit_sum
FROM (
SELECT 123 AS foo
UNION ALL SELECT 100
UNION ALL SELECT 413432143
UNION ALL SELECT -6301
UNION ALL SELECT 1234567890
) x
+------------+-----------+
| foo | digit_sum |
+------------+-----------+
| 123 | 6 |
| 100 | 1 |
| 413432143 | 25 |
| -6301 | 10 |
| 1234567890 | 45 |
+------------+-----------+
5 rows in set (0.00 sec)
It probably makes more sense rewritten as function, together with some error checking to return NULL on floats or something similar.

Creating a Daily Report from non-Daily time-serie without the use of a calendar table

I have a "time_serie" table with date gaps (not existing dates) that looks like:
+-----+-------+-------------+------+
| id | TS_ID | Date_publi | Val |
+-----+-------+-------------+------+
| 4 | 3 | 1996-11-01 | 50.5 |
| 5 | 3 | 1996-12-02 | 53 |
| 6 | 3 | 1997-01-02 | 55.2 |
... ... .......... ...
I would like to create an output which replaces missing values with either zeros or #N/A or previous value so it looks like:
1996-10-30 : #N/A
1996-10-31 : #N/A
1996-11-01 : 50.5
1996-11-02 : #N/A
1996-11-03 : #N/A
.... ...
To do so, I thought about creating a "calendar" table with every single date in it and then call the right-join query:
SELECT calendar.dates AS DATE, IFNULL(time_serie.val, "#N/A") AS val
FROM time_serie RIGHT JOIN calendar ON (DATE(time_serie.date_publi) = calendar.dates)
WHERE (calendar.datefield BETWEEN start_date AND end_date)
But, I would rather not have to create and manage a calendar table.
Does someone has an idea how to do such a report without using a calendar table?
You can use the following to get it done without using Calendar table. It isn't in MySQL but would do the trick:
DECLARE #temp TABLE (Date DATETIME, Users NVARCHAR(40), Score INT) ---- Temporary table strucrure
INSERT INTO #temp (Date, Users, Score)
VALUES ---- Default values
('20120101', 'User1', 17),('20120201', 'User1', 19),
('20120401', 'User1', 15),('20120501', 'User1', 16),
('20120701', 'User1', 14),('20120801', 'User1', 15),
('20120901', 'User1', 15),('20121001', 'User1', 13),
('20121201', 'User1', 11),('20130101', 'User1', 10),
('20130201', 'User1', 15),('20130301', 'User1', 13),
('20130501', 'User1', 18),('20130601', 'User1', 14),
('20130801', 'User1', 15),('20130901', 'User1', 14),
('20161001', 'User1', 10),('20120601', 'User1', 10)
;WITH cte AS ---- Created a common table expression. You can say another query executed here
(
SELECT Users, StartDate = MIN(Date), EndDate = MAX(Date) ---- Retrieved the max and min date from the table
FROM #temp
GROUP BY Users
UNION ALL ---- Used 'UNION ALL' to combine all the dates missing and existing one
SELECT Users, DATEADD(MONTH, 1, StartDate), EndDate ---- Checks the months that are missing
FROM cte
WHERE StartDate <= EndDate
)
SELECT e.StartDate, t.Users, Score = ISNULL(t.Score, 0) ---- Finally checks the user scores for the existing and non-existing months using 'CTE'
FROM cte e
LEFT JOIN #temp t ON e.StartDate = t.Date AND e.Users = t.Users
ORDER BY e.StartDate, t.Users
The above returns 0 or null if there is no entry for a specific month.
Please check this out for more: Find Missing Dates - MySQL
More specifically, this would do just fine: Find Missing Dates - MySQL 2

How to search JSON array in MySQL?

Let's say I have a JSON column named data in some MySQL table, and this column is a single array. So, for example, data may contain:
[1,2,3,4,5]
Now I want to select all rows which have a data column where one of its array elements is greater than 2. Is this possible?
I tried the following, but seems it is always true regardless of the values in the array:
SELECT * from my_table
WHERE JSON_EXTRACT(data, '$[*]') > 2;
You may search an array of integers as follows:
JSON_CONTAINS('[1,2,3,4,5]','7','$') Returns: 0
JSON_CONTAINS('[1,2,3,4,5]','1','$') Returns: 1
You may search an array of strings as follows:
JSON_CONTAINS('["a","2","c","4","x"]','"x"','$') Returns: 1
JSON_CONTAINS('["1","2","3","4","5"]','"7"','$') Returns: 0
Note: JSON_CONTAINS returns either 1 or 0
In your case you may search using a query like so:
SELECT * from my_table
WHERE JSON_CONTAINS(data, '2', '$');
SELECT JSON_SEARCH('["1","2","3","4","5"]', 'one', "2") is not null
is true
SELECT JSON_SEARCH('["1","2","3","4","5"]', 'one', "6") is not null
is false
Since MySQL 8 there is a new function called JSON_TABLE.
CREATE TABLE my_table (id INT, data JSON);
INSERT INTO my_table VALUES
(1, "[1,2,3,4,5]"),
(2, "[0,1,2]"),
(3, "[3,4,-10]"),
(4, "[-1,-2,0]");
SELECT DISTINCT my_table.*
FROM my_table, JSON_TABLE(data, "$[*]" COLUMNS(nr INT PATH '$')) as ids
WHERE ids.nr > 2;
+------+-----------------+
| id | data |
+------+-----------------+
| 1 | [1, 2, 3, 4, 5] |
| 3 | [3, 4, -10] |
+------+-----------------+
2 rows in set (0.00 sec)
I use a combination of JSON_EXTRACT and JSON_CONTAINS (MariaDB):
SELECT * FROM table WHERE JSON_CONTAINS(JSON_EXTRACT(json_field, '$[*].id'), 11, '$');
I don't know if we found the solution.
I found with MariaDB a way, to search path in a array. For example, in array [{"id":1}, {"id":2}], I want find path with id equal to 2.
SELECT JSON_SEARCH('name_field', 'one', 2, null, '$[*].id')
FROM name_table
The result is:
"$[1].id"
The asterisk indicate searching the entire array
This example works for me with mysql 5.7 above
SET #j = '{"a": [ "8428341ffffffff", "8428343ffffffff", "8428345ffffffff", "8428347ffffffff","8428349ffffffff", "842834bffffffff", "842834dffffffff"], "b": 2, "c": {"d": 4}}';
select JSON_CONTAINS(JSON_EXTRACT(#j , '$.a'),'"8428341ffffffff"','$') => returns 1
notice about " around search keyword, '"8428341ffffffff"'
A possible way is to deal with the problem as string matching. Convert the JSON to string and match.
Or you can use JSON_CONTAINS.
You can use JSON extract to search and select data
SELECT data, data->"$.id" as selectdata
FROM table
WHERE JSON_EXTRACT(data, "$.id") = '123'
#ORDER BY c->"$.name";
limit 10 ;
SET #doc = '[{"SongLabels": [{"SongLabelId": "111", "SongLabelName": "Funk"}, {"SongLabelId": "222", "SongLabelName": "RnB"}], "SongLabelCategoryId": "test11", "SongLabelCategoryName": "曲风"}]';
SELECT *, JSON_SEARCH(#doc, 'one', '%un%', null, '$[*].SongLabels[*].SongLabelName')FROM t_music_song_label_relation;
result: "$[0].SongLabels[0].SongLabelName"
SELECT song_label_content->'$[*].SongLabels[*].SongLabelName' FROM t_music_song_label_relation;
result: ["Funk", "RnB"]
I have similar problem, search via function
create function searchGT(threshold int, d JSON)
returns int
begin
set #i = 0;
while #i < json_length(d) do
if json_extract(d, CONCAT('$[', #i, ']')) > threshold then
return json_extract(d, CONCAT('$[', #i, ']'));
end if;
set #i = #i + 1;
end while;
return null;
end;
select searchGT(3, CAST('[1,10,20]' AS JSON));
This seems to be possible with to JSON_TABLE function. It's available in mysql version 8.0 or mariadb version 10.6.
With this test setup
CREATE TEMPORARY TABLE mytable
WITH data(a,json) AS (VALUES ('a','[1]'),
('b','[1,2]'),
('c','[1,2,3]'),
('d','[1,2,3,4]'))
SELECT * from data;
we get the following table
+---+-----------+
| a | json |
+---+-----------+
| a | [1] |
| b | [1,2] |
| c | [1,2,3] |
| d | [1,2,3,4] |
+---+-----------+
It's possible to select every row from mytable wich has a value greater than 2 in the json array with this query.
SELECT * FROM mytable
WHERE TRUE IN (SELECT val > 2
FROM JSON_TABLE(json,'$[*]'
columns (val INT(1) path '$')
) as json
)
Returns:
+---+-----------+
| a | json |
+---+-----------+
| c | [1,2,3] |
| d | [1,2,3,4] |
+---+-----------+