Sum the digits of a number in mysql - 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.

Related

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 |
+----+------------+

How to add a character to specific index in string

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

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] |
+---+-----------+

replace() concat() and substr() add character at specific location in string cannot get the position right

End result: all 3 fields should be merged (solved OK), and the character "T" should be added as the 5th character in the merged string (no other characters should be removed or altered in sequence). (see all specifics below).
What am I doing wrong?
Data is in the following format:
data1: AL
data2: 33 0230S 0440E
data3: SW
Here is my current sql:
replace(concat(b.data1,
substr(b.data2, 4, 1),
'T',
substr(b.data2, 1),
b.data3), ' ', '')
AS MergedData
The final output should look like:
AL33T0230S0440ESW
I've been able to get the "T" placed at random locations, but cannot get it consistently added as the 5th character from the start of the string.
Use:
replace only on data2 (because that's the only field that needs it), then
concat() to join it all up, and finally
the insert() function to insert the T
(Don't use substr at all)
insert(concat(data1, replace(data2, ' ', ''), data3), 5, 0, 'T')
Here's a test:
set #data1 := 'AL', #data2 := '33 0230S 0440E', #data3 := 'SW';
select
insert(concat(#data1, replace(#data2, ' ', ''), #data3), 5, 0, 'T')
as MergedData;
Output:
+-------------------+
| MergedData |
+-------------------+
| AL33T0230S0440ESW |
+-------------------+
Random locations seems odd, this seems to work though;
replace(concat(b.data1,
substr(b.data2, 1, 2),
'T',
substr(b.data2, 4),
b.data3), ' ', '')
Demo here.
Find the position of the first space in data2, replace it with T, remove the rest of the spaces in the resulting string, then concatenate it with the two other values:
CONCAT(
b.data1,
REPLACE(INSERT(b.data2, LOCATE(' ', b.data2), 1, 'T'), ' ', ''),
b.data3
) AS MergedData