Selecting first 4 numbers from a string MySQL - mysql

If I have strings like this:
CC123484556
CC492014512
BUXT122256690
How can I manipulate code like this in MySQL to pull the first 4 values that are numbers ? There are various # of letters before numbers in other rows but the most important thing are the first 4 numbers that show up.
SELECT LEFT(alloy , 4) FROM tbl
So the desired result would be:
1234
4920
1222

Slow and ugly:
SELECT col,
SUBSTRING(tab.col, MIN(LOCATE(four_digits, tab.col,1)), 4) + 0 AS result
FROM (SELECT 'CC123484556' AS col UNION ALL
SELECT 'CC492014512' UNION ALL
SELECT 'BUXT122256690' UNION ALL
SELECT 'abced') tab
CROSS JOIN (
SELECT CONCAT(d1.z, d2.z, d3.z, d4.z) AS four_digits
FROM (SELECT '1' AS z UNION SELECT '2' UNION SELECT '3' UNION
SELECT '4' UNION SELECT '5' UNION SELECT '6' UNION
SELECT '7' UNION SELECT '8' UNION SELECT '9' UNION SELECT '0') d1
CROSS JOIN (SELECT '1' AS z UNION SELECT '2' UNION SELECT '3' UNION
SELECT '4' UNION SELECT '5' UNION SELECT '6' UNION
SELECT '7' UNION SELECT '8' UNION SELECT '9' UNION SELECT '0') d2
CROSS JOIN (SELECT '1' AS z UNION SELECT '2' UNION SELECT '3' UNION
SELECT '4' UNION SELECT '5' UNION SELECT '6' UNION
SELECT '7' UNION SELECT '8' UNION SELECT '9' UNION SELECT '0') d3
CROSS JOIN (SELECT '1' AS z UNION SELECT '2' UNION SELECT '3' UNION
SELECT '4' UNION SELECT '5' UNION SELECT '6' UNION
SELECT '7' UNION SELECT '8' UNION SELECT '9' UNION SELECT '0') d4
) sub
WHERE LOCATE(four_digits, tab.col,1) > 0
GROUP BY col;
Rextester Demo
Generate all 4 digit combinations, locate them in string and get substring with lowest index.
EDIT:
A bit faster approach:
SELECT col, SUBSTRING(col, MIN(i), 4) + 0 AS r
FROM (
SELECT col, SUBSTRING(tab.col, i , 4) + 0 AS result, i
FROM tab
CROSS JOIN (
SELECT CONCAT(d1.z, d2.z)+1 AS i
FROM (SELECT '1' AS z UNION SELECT '2' UNION SELECT '3' UNION
SELECT '4' UNION SELECT '5' UNION SELECT '6' UNION
SELECT '7' UNION SELECT '8' UNION SELECT '9' UNION SELECT '0') d1
CROSS JOIN (SELECT '1' AS z UNION SELECT '2' UNION SELECT '3' UNION
SELECT '4' UNION SELECT '5' UNION SELECT '6' UNION
SELECT '7' UNION SELECT '8' UNION SELECT '9' UNION SELECT '0') d2
) sub
WHERE i <= LENGTH(tab.col)-1
) sub
WHERE result <> 0
GROUP BY col;
Rextester Demo2
Get 4 character substring from beginning, convert implicitly to number, get number with lowest i.

Using locate(), least(), and substr()
SQLFiddle Demo
select col,SUBSTR(col,LEAST(
if (Locate(0,col) >0,Locate(0,col),999),
if (Locate(1,col) >0,Locate(1,col),999),
if (Locate(2,col) >0,Locate(2,col),999),
if (Locate(3,col) >0,Locate(3,col),999),
if (Locate(4,col) >0,Locate(4,col),999),
if (Locate(5,col) >0,Locate(5,col),999),
if (Locate(6,col) >0,Locate(6,col),999),
if (Locate(7,col) >0,Locate(7,col),999),
if (Locate(8,col) >0,Locate(8,col),999),
if (Locate(9,col) >0,Locate(9,col),999)
),4) as result from test;
Test Results:
mysql> create table test ( col varchar(15));
Query OK, 0 rows affected (0.70 sec)
mysql> insert into test (col) values
-> ('CC123484556'),
-> ('CC492014512'),
-> ('BUXT122256690');
Query OK, 3 rows affected (0.13 sec)
Records: 3 Duplicates: 0 Warnings: 0
Output:
mysql> select * from test;
+---------------+
| col |
+---------------+
| CC123484556 |
| CC492014512 |
| BUXT122256690 |
+---------------+
3 rows in set (0.00 sec)
mysql> select col,SUBSTR(col,LEAST(
-> if (Locate(0,col) >0,Locate(0,col),999),
-> if (Locate(1,col) >0,Locate(1,col),999),
-> if (Locate(2,col) >0,Locate(2,col),999),
-> if (Locate(3,col) >0,Locate(3,col),999),
-> if (Locate(4,col) >0,Locate(4,col),999),
-> if (Locate(5,col) >0,Locate(5,col),999),
-> if (Locate(6,col) >0,Locate(6,col),999),
-> if (Locate(7,col) >0,Locate(7,col),999),
-> if (Locate(8,col) >0,Locate(8,col),999),
-> if (Locate(9,col) >0,Locate(9,col),999)
-> ),4) as result from test;
+---------------+--------+
| col | result |
+---------------+--------+
| CC123484556 | 1234 |
| CC492014512 | 4920 |
| BUXT122256690 | 1222 |
+---------------+--------+
3 rows in set (0.00 sec)

Related

Convert Excel formula (PRODUCT) to MySQL query

wondering if anyone can help me convert the following Excel formula to MySQL query:
Excel: =PRODUCT(1+A1:A7)-1
MySQL data:
id | data
---------------
1 | -1.64
2 | 1.38
3 | 0
4 | 0
5 | -1.52
6 | 0
7 | -1.78
Result should equal -0.207936
This is how you can do it. I sure hope someone can improve on it cause it is awful.
with data(id, val) as(
select 1,-1.64 from dual union all
select 2,1.38 from dual union all
select 3,0.00 from dual union all
select 4,0.00 from dual union all
select 5,-1.52 from dual union all
select 6,0.00 from dual union all
select 7,-1.78 from dual
),
products as(
select Replace(LISTAGG(val+1, '*') within group (order by val),',','.') chain
from data)
SELECT o.val-1 AS product_value
FROM products p
OUTER APPLY(
SELECT *
FROM XMLTABLE('/ROWSET/ROW/*'
passing dbms_xmlgen.getXMLType('SELECT ' || p.chain || ' FROM dual')
COLUMNS val NUMBER PATH '.')
WHERE p.chain IS NOT NULL
) o;
https://dbfiddle.uk/?rdbms=oracle_18&fiddle=c12284b5165da61de8ed418cdf1d6cd7
a prettier solution
with data(id, val) as(
select 1,-1.64 from dual union all
select 2,1.38 from dual union all
select 3,0.00 from dual union all
select 4,0.00 from dual union all
select 5,-1.52 from dual union all
select 6,0.00 from dual union all
select 7,-1.78 from dual
),
neg(val , modifier) as(
select exp(sum(ln(abs(val+1)))), case when mod(count(*),2) = 0 then 1 Else -1 end
from data
where val+1 <0
)
,
pos(val) as (
select exp(sum(ln(val+1)))
from data
where val+1 >=0
)
select (select val*modifier from neg)*(select val from pos)-1 from dual

How to get missing non numeric IDs in MySQL table

I have a table with ID field format mm_xxxx where xxxx is an hexadecimal value.
So mm_1AF8 is a valid ID.
Now I need to get all the singles IDs that are not in use.
I need to search only from mm_0000 to mm_FFFF.
Combine Barmars response with the answer from Robert McKee
SELECT *
FROM yourtable
WHERE id NOT IN (SELECT z1.*
FROM
(SELECT CONCAT(#prefix, d1.f,d2.f,d3.f,d4.f) as h
FROM (
SELECT '0' AS f UNION ALL
SELECT '1' AS f UNION ALL
SELECT '2' AS f UNION ALL
SELECT '3' AS f UNION ALL
SELECT '4' AS f UNION ALL
SELECT '5' AS f UNION ALL
SELECT '6' AS f UNION ALL
SELECT '7' AS f UNION ALL
SELECT '8' AS f UNION ALL
SELECT '9' AS f UNION ALL
SELECT 'A' AS f UNION ALL
SELECT 'B' AS f UNION ALL
SELECT 'C' AS f UNION ALL
SELECT 'D' AS f UNION ALL
SELECT 'E' AS f UNION ALL
SELECT 'F' AS f) d1
CROSS JOIN (
SELECT '0' AS f UNION ALL
SELECT '1' AS f UNION ALL
SELECT '2' AS f UNION ALL
SELECT '3' AS f UNION ALL
SELECT '4' AS f UNION ALL
SELECT '5' AS f UNION ALL
SELECT '6' AS f UNION ALL
SELECT '7' AS f UNION ALL
SELECT '8' AS f UNION ALL
SELECT '9' AS f UNION ALL
SELECT 'A' AS f UNION ALL
SELECT 'B' AS f UNION ALL
SELECT 'C' AS f UNION ALL
SELECT 'D' AS f UNION ALL
SELECT 'E' AS f UNION ALL
SELECT 'F' AS f) d2
CROSS JOIN (
SELECT '0' AS f UNION ALL
SELECT '1' AS f UNION ALL
SELECT '2' AS f UNION ALL
SELECT '3' AS f UNION ALL
SELECT '4' AS f UNION ALL
SELECT '5' AS f UNION ALL
SELECT '6' AS f UNION ALL
SELECT '7' AS f UNION ALL
SELECT '8' AS f UNION ALL
SELECT '9' AS f UNION ALL
SELECT 'A' AS f UNION ALL
SELECT 'B' AS f UNION ALL
SELECT 'C' AS f UNION ALL
SELECT 'D' AS f UNION ALL
SELECT 'E' AS f UNION ALL
SELECT 'F' AS f) d3
CROSS JOIN (
SELECT '0' AS f UNION ALL
SELECT '1' AS f UNION ALL
SELECT '2' AS f UNION ALL
SELECT '3' AS f UNION ALL
SELECT '4' AS f UNION ALL
SELECT '5' AS f UNION ALL
SELECT '6' AS f UNION ALL
SELECT '7' AS f UNION ALL
SELECT '8' AS f UNION ALL
SELECT '9' AS f UNION ALL
SELECT 'A' AS f UNION ALL
SELECT 'B' AS f UNION ALL
SELECT 'C' AS f UNION ALL
SELECT 'D' AS f UNION ALL
SELECT 'E' AS f UNION ALL
SELECT 'F' AS f) d4
) z1,(SELECT #prefix := 'mm_') pre);

Get number of monday in a rangedate mysql

Get number of monday in a rangedate MySQL, I run this code but it give me result 0:
select count(*) from tarif where weekday(`end_tarif`<= '2019-02-21'AND `start_tarif`>='2019-02-05') = 0;
my table:
CREATE TABLE `tarif` (
`tarif_id` int(11) NOT NULL AUTO_INCREMENT,
`start_tarif` date NOT NULL,
`end_tarif` date NOT NULL,
`day_tarif` varchar(50) NOT NULL,
PRIMARY KEY (`tarif_id`)
);
INSERT INTO `tarif` VALUES (1, '2019-02-01', '2019-02-10', '10'),
(2, '2019-02-11', '2019-02-20', '20'),
(3, '2019-02-21', '2019-02-28', '10'),
(4, '2019-03-01', '2019-02-10', '15');
You can use a solution using a calendar table. So you can use a solution like the following:
1. create a table with calendar data
-- create the table "calendar"
CREATE TABLE `calendar` (
`dateValue` DATE
);
-- insert the days to the table "calendar"
INSERT INTO calendar
SELECT adddate('1970-01-01',t4*10000 + t3*1000 + t2*100 + t1*10 + t0) gen_date from
(select 0 t0 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t0,
(select 0 t1 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t1,
(select 0 t2 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t2,
(select 0 t3 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t3,
(select 0 t4 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t4
HAVING gen_date BETWEEN '2019-01-01' AND '2019-12-31'
You can find the script to generate the calendar data on StackOverflow:
How to populate a table with a range of dates?
2. create the table with your data (with monday tarif)
-- create the table "tarif"
CREATE TABLE tarif (
tarif_id INT(11) NOT NULL AUTO_INCREMENT,
start_tarif DATE NOT NULL,
end_tarif DATE NOT NULL,
day_tarif VARCHAR(50) NOT NULL,
monday_tarif VARCHAR(50) NOT NULL,
PRIMARY KEY (tarif_id)
);
-- insert the tarif information
INSERT INTO tarif VALUES
(1, '2019-02-01', '2019-02-10', '10', '5'),
(2, '2019-02-11', '2019-02-20', '20', '5'),
(3, '2019-02-21', '2019-02-28', '10', '5'),
(4, '2019-03-01', '2019-02-10', '15', '5');
Note: To create a useful example I added the column monday_tarif and insert the value 5 on every date range.
3. get the result
Now you can get all days of your needed range (between 2019-02-05 and 2019-02-21) from the calendar table. With a LEFT JOIN you add your tarif table to all days of date range.
With a CASE WHEN and the condition DAYOFWEEK = 2 or DAYNAME = 'Monday' you can check if the current date is a Monday or not, to get the correct tarif value of the day.
SELECT SUM(CASE WHEN DAYOFWEEK(cal.dateValue) = 2 THEN tarif.monday_tarif ELSE tarif.day_tarif END) AS sumWithMondayTarif
FROM calendar cal
LEFT JOIN tarif ON cal.dateValue BETWEEN start_tarif AND end_tarif
WHERE cal.dateValue BETWEEN '2019-02-05' AND '2019-02-21';
You can also use a SELECT with a sub select of the calendar:
SELECT SUM(CASE WHEN DAYOFWEEK(cal.dateValue) = 2 THEN tarif.monday_tarif ELSE tarif.day_tarif END) AS sumWithMondayTarif FROM (
SELECT adddate('1970-01-01',t4*10000 + t3*1000 + t2*100 + t1*10 + t0) dateValue FROM
(select 0 t0 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t0,
(select 0 t1 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t1,
(select 0 t2 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t2,
(select 0 t3 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t3,
(select 0 t4 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t4
HAVING dateValue BETWEEN '2019-02-05' AND '2019-02-21'
) cal LEFT JOIN tarif ON cal.dateValue BETWEEN start_tarif AND end_tarif
demo on dbfiddle.uk
Below mentioned query is for sundays count. you can modify it as per your requirement
select ROUND((
(unix_timestamp(`end_tarif`) - unix_timestamp(`start_tarif`) )/(24*60*60)
-7+WEEKDAY(`start_tarif`)-WEEKDAY(`end_tarif`)
)/7)
+ if(WEEKDAY(`start_tarif`) <= 6, 1, 0)
+ if(WEEKDAY(`end_tarif`) >= 6, 1, 0) as Sunday
from tarif
where `end_tarif`<= '2019-02-21' AND `start_tarif`>='2019-02-05' ;

How to reduce SQL queries in only one in this case

I want to save the hassle of doing many querys for the following:
I have a table like this:
name, age
{
Mike, 7
Peter, 2
Mario, 1
Tony, 4
Mary, 2
Tom, 7
Jerry, 3
Nick, 2
Albert, 22
Steven, 7
}
And I want the following result:
Results(custom_text, num)
{
1 Year, 1
2 Year, 3
3 Year, 1
4 Year, 1
5 Year, 0
6 Year, 0
7 Year, 3
8 Year, 0
9 Year, 0
10 Year, 0
More than 10 Year, 1
}
I know how to do this but in 11 queries :( But how to simplify it?
EDIT:
Doing the following, I can obtain the non zero values, but I need the zeroes in the right places.
SELECT COUNT(*) AS AgeCount
FROM mytable
GROUP BY Age
How can I achieve this?
Thanks for reading.
you can use below query but it will not show the gaps if you want gaps then the use Linoff's answer:
select t.txt, count(t.age) from
(select
case
when age<11 then concat(age ,' year')
else 'more than 10'
end txt, age
from your_table)t
group by t.txt
order by 1
SQL FIDDLE DEMO
You can use left join and a subquery to get what you want:
select coalesce(concat(ages.n, ' year'), 'More than 10 year') as custom_text,
count(*)
from (select 1 as n union all select 2 union all select 3 union all select 4 union all
select 5 union all select 6 union all select 7 union all select 8 union all
select 9 union all select 10 union all select null
) ages left join
tabla t
on (t.age = ages.n or ages.n is null and t.age > 10)
group by ages.n;
EDIT:
I think the following is a better way to do this query:
select (case when least(age, 11) = 11 then 'More than 10 year'
else concat(age, ' year')
end) as agegroup, count(name)
from (select 1 as age, NULL as name union all
select 2, NULL union all
select 3, NULL union all
select 4, NULL union all
select 5, NULL union all
select 6, NULL union all
select 7, NULL union all
select 8, NULL union all
select 9, NULL union all
select 10, NULL union all
select 11, NULL
union all
select age, name
from tabla t
) t
group by least(age, 11);
Basically, the query need a full outer join and MySQL does not provide one. However, we can get the same result by adding in extra values for each age, so we know something is there. Then because name is NULL, the count(name) will return 0 for those rows.
Please try using this query for required output.
SQL FIDDLE link http://www.sqlfiddle.com/#!9/4e52a/6
select coalesce(concat(ages.n, ' year'), 'More than 10 year') as custom_text,
count(t.age) from (select 1 as n union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9 union all select 10 union all select null ) ages left join tabla t
on (case when ages.n<11 then t.age = ages.n else t.age > 10 end)
group by ages.n;

Order By Issue with MySQL vs SQL Server

I have one SQL query in SQL Server like this
SELECT
CASE WHEN ROW_NUMBER() OVER(PARTITION BY _no ORDER BY _no asc) = 1 THEN
_no ELSE '' END
as row_no,
_no,
_name,
r._names
FROM
(
SELECT '1' as _no, 'vikas' as _name UNION ALL
SELECT '1', 'kratika' UNION ALL
SELECT '2', 'vikas' UNION ALL
SELECT '1', 'kratika kastwar'
) t
INNER JOIN
(
SELECT '1' as _nos, 'One' as _names UNION ALL
SELECT '2', 'two'
) r
ON r._nos = t._no
ORDER BY _no
Output:
row_no _no _name _names
------ ---- --------------- ------
1 1 kratika One
1 kratika kastwar One
1 vikas One
2 2 vikas two
And the same I am doing in MySQL like this
SELECT
CASE WHEN _no = #i THEN '' ELSE #i := _no END
as row_no,
_no,
_name,
r._names
FROM
(
SELECT '1' as _no, 'vikas' as _name UNION ALL
SELECT '1', 'kratika' UNION ALL
SELECT '2', 'vikas' UNION ALL
SELECT '1', 'kratika kastwar'
) t
INNER JOIN
(
SELECT '1' as _nos, 'One' as _names UNION ALL
SELECT '2', 'two'
) r
ON r._nos = t._no
,
(SELECT #i := '') temp
ORDER BY _no
Output :
1 1 vikas One
1 kratika One
1 1 kratika kastwar One
2 2 vikas two
But I am expecting output in MySQL like this
1 1 kratika One
1 kratika kastwar One
1 vikas One
2 2 vikas two
I don't want to use query like this in MySQL as desc here MYSQL Order By W/Count
SELECT
CASE WHEN _no = #i THEN '' ELSE #i := _no END
as row_no,
_no,
_name,
_names
FROM
(SELECT
*
FROM
(
SELECT '1' as _no, 'vikas' as _name UNION ALL
SELECT '1', 'kratika' UNION ALL
SELECT '2', 'vikas' UNION ALL
SELECT '1', 'kratika kastwar'
) t
INNER JOIN
(
SELECT '1' as _nos, 'One' as _names UNION ALL
SELECT '2', 'two'
) r
ON r._nos = t._no
,
(SELECT #i := '') temp
ORDER BY _no) t
How I can achieve the same in MySQL, query performance is major parameter
I think what's happening here is that MySQL is constructing the resultset and then going through an extra step to order it according to the ORDER BY clause. Since the 'kratika kastwar' row comes after the row where _no is 2, you get the unexpected output.
The solution, I guess, would be to put the basic SELECT (without the special user-variable shenanigans) in a subquery in the FROM clause, applying the ORDER BY clause to the subquery. Then do the user-variable work in the outer query. That way the ordering has already happened.
Edit: I see that you said you don't want to do this. I don't think you have a choice, unless you can find a way to get MySQL to not do the ORDER BY by performing a filesort step on the computed results (very unlikely).
SELECT
CASE WHEN _no = #i THEN '' ELSE #i := _no END
as row_no,
_no,
_name,
_names
FROM
(SELECT
*
FROM
(
SELECT '1' as _no, 'vikas' as _name UNION ALL
SELECT '1', 'kratika' UNION ALL
SELECT '2', 'vikas' UNION ALL
SELECT '1', 'kratika kastwar'
) t
INNER JOIN
(
SELECT '1' as _nos, 'One' as _names UNION ALL
SELECT '2', 'two'
) r
ON r._nos = t._no
,
(SELECT #i := '')