i have the next sub-query:
SELECT SUBSTRING(
(SELECT GROUP_CONCAT(DISTINCT PT.Factura SEPARATOR '|')
FROM darwin.vt_partidas PT
WHERE PT.Pedimento = P.ID)
,1,30) AS 'Resultado'
FROM darwin.vt_pedimentos P WHERE P.ID=130
I need to concat all results separated with | until i reach 130 characters, but my problem is that if at the end a result doesn't fit example:
i get the first 30 characters but the last result doesn't fit, i get:
result1|result2|result3|result
and i want this:
result1|result2|result3
(if the result doesn't fit, remove all characters from that result)
Thank you guys
Try this
Updated your GROUP_CONCAT and added another step to remove irrelevant data exceeding the 30 max length
SELECT
#str:= left(GROUP_CONCAT( DISTINCT PT.Factura SEPARATOR '|'), 30)
FROM
vt_pedimentos P
INNER JOIN
vt_partidas PT
ON PT.Pedimento = P.ID
WHERE
P.ID = 130;
-- to check whether the last or truncated text exists in the table otherwise remove
select
#str:= left(#str,
(
length(#str) - length(reverse(left(reverse(#str), locate('|', reverse(#str)) - 1)))
)
- 1)
FROM
vt_pedimentos P
where
NOT EXISTS
(
select
1
from
vt_partidas PT
where
PT.Factura = reverse(left(reverse(#str), locate('|', reverse(#str)) - 1))
)
and P.ID = 130;
further enhancement - have it to one sql statement
String manipulation is not a forte of SQL expressions.
But something like this should do it:
SELECT
IF(CHAR_LENGTH( GROUP_CONCAT(DISTINCT PT.Factura SEPARATOR '|') ) < 130
, GROUP_CONCAT(DISTINCT PT.Factura SEPARATOR '|')
, SUBSTRING_INDEX(
SUBSTR( GROUP_CONCAT(DISTINCT PT.Factura SEPARATOR '|') ,1,130)
, '|'
, CHAR_LENGTH( SUBSTR( GROUP_CONCAT(DISTINCT PT.Factura SEPARATOR '|') ,1,130) )
-CHAR_LENGTH(REPLACE(SUBSTR( GROUP_CONCAT(DISTINCT PT.Factura SEPARATOR '|') ,1,130),'|',''))
)
)
That's fairly complicated. It will be easier to decipher if we replace the GROUP_CONCAT expression with a placeholder. Let's have res represent GROUP_CONCAT(DISTINCT PT.Factura SEPARATOR '|') expression.
SELECT
IF(CHAR_LENGTH( res ) < 130
, res
, SUBSTRING_INDEX(
SUBSTR( res ,1,130)
, '|'
, CHAR_LENGTH( SUBSTR( res ,1,130) )
-CHAR_LENGTH(REPLACE(SUBSTR( res ,1,130),'|',''))
)
)
Still ugly, but better. Let's break that down.
If the number of characters in res is less than 130, we're done. Just return res.
Otherwise, we need to trim res to 130 characters, we can use SUBSTRING function to do that.
Now, we want to trim the last | and the following characters. To do that, we can get a count of the number | separator characters. Then we know which one the last one is.
(We can get a count of the separator characters by replacing all separator characters with an empty string, then getting the length of that string, and subtracting that from the length of the original string. The difference is the total length of the removed separator characters.
Then we can use that difference in a SUBSTRING_INDEX function to return all of the the characters before the last separator.
It's not a pretty solution. But it does implement an algorithm that satisfies the specification.
Related
in mySQL query is an
GROUP_CONCAT(fieldname SEPARATOR ', ')
But field with should not be too long.
Therefore I want a different Separator after x datasets (i.e. each 3rd separator should be '\n')
I would be happy to get help for this.
Thanks!
This is a real pain. One method uses lead() to bring three values together and then filter the values to every third one:
select x,
group_concat(col_3 separator '; ')
from (select t.x,
concat_ws(', ',
col,
lead(col, 1) over (order by ?),
lead(col, 2) over (order by ?)
) as col_3
row_number() over (partition by x order by ?) as seqnum
from t
) t
where mod(seqnum, 3) = 1
group by x;
If you want other aggregations, you can filter in the group_concat() instead:
select x,
group_concat(case when mod(seqnum, 3) = 1 then col_3 end separator '; ')
is it possible to have numbering in GROUP_CONCAT
like
If, from GROUP_CONCAT(empnam SEPARATOR ', ')
I get a set,
< JohnM, DannyP, TiffnyK, KarlM >
I need to have
< 1.JohnM, 2.DannyP, 3.TiffnyK, 4.KarlM >
I tried following, but didnt get desired results.
SET #x:=0;
SELECT
GROUP_CONCAT(#x:=#x+1,' ', s.empnam SEPARATOR ', ') AS emps, #x:=0
< tables >
< filters >
is it possible at Query-Level, or I have to do it at Application Side ?
Years later, we should abandon mutating variables inside a select statement, as since MySQL 8 we can use the standard way, with window functions:
with base as (
select dep,
empnam,
count(*) over (partition by dep order by empnam) num
from t)
select dep,
group_concat(concat(num, '.', empnam) separator ', ') emps
from base
group by dep
See db-fiddle
Original answer (2016)
You can do this on the application side, but in MySQL 5.7 it is possible. In the following query, I assume you group the names by something, for example their department (I called it dep). This in order to illustrate that the counter starts from 1 for every new group.
select dep,
group_concat(
concat(#i := if (#grp = dep, #i + 1, if(#grp := dep,1,1)), '.', empnam)
separator ', ') emps
from t,
(select #i := 0, #grp := '') init
group by dep;
See SQL fiddle
or db-fiddle.
Make sure to put your table name in the from clause, and to use the actual field you want to group by. If you have multiple fields to group by, the expression assigned to #i will need to change. You could for instance concatenate the values that define a group.
By using a separator of two characters you ensure to have a space between each name.
Try this:
SET #x:=0;
SELECT
GROUP_CONCAT(CONCAT(#x:=#x+1, '.', s.empnam) SEPARATOR ', ') AS emps, #x:=0
< tables >
< filters >
I would like to have a mysql query like this:
select <second word in text> word, count(*) from table group by word;
All the regex examples in mysql are used to query if the text matches the expression, but not to extract text out of an expression. Is there such a syntax?
The following is a proposed solution for the OP's specific problem (extracting the 2nd word of a string), but it should be noted that, as mc0e's answer states, actually extracting regex matches is not supported out-of-the-box in MySQL. If you really need this, then your choices are basically to 1) do it in post-processing on the client, or 2) install a MySQL extension to support it.
BenWells has it very almost correct. Working from his code, here's a slightly adjusted version:
SUBSTRING(
sentence,
LOCATE(' ', sentence) + CHAR_LENGTH(' '),
LOCATE(' ', sentence,
( LOCATE(' ', sentence) + 1 ) - ( LOCATE(' ', sentence) + CHAR_LENGTH(' ') )
)
As a working example, I used:
SELECT SUBSTRING(
sentence,
LOCATE(' ', sentence) + CHAR_LENGTH(' '),
LOCATE(' ', sentence,
( LOCATE(' ', sentence) + 1 ) - ( LOCATE(' ', sentence) + CHAR_LENGTH(' ') )
) as string
FROM (SELECT 'THIS IS A TEST' AS sentence) temp
This successfully extracts the word IS
Shorter option to extract the second word in a sentence:
SELECT SUBSTRING_INDEX(SUBSTRING_INDEX('THIS IS A TEST', ' ', 2), ' ', -1) as FoundText
MySQL docs for SUBSTRING_INDEX
According to http://dev.mysql.com/ the SUBSTRING function uses start position then the length so surely the function for the second word would be:
SUBSTRING(sentence,LOCATE(' ',sentence),(LOCATE(' ',LOCATE(' ',sentence))-LOCATE(' ',sentence)))
No, there isn't a syntax for extracting text using regular expressions. You have to use the ordinary string manipulation functions.
Alternatively select the entire value from the database (or the first n characters if you are worried about too much data transfer) and then use a regular expression on the client.
As others have said, mysql does not provide regex tools for extracting sub-strings. That's not to say you can't have them though if you're prepared to extend mysql with user-defined functions:
https://github.com/mysqludf/lib_mysqludf_preg
That may not be much help if you want to distribute your software, being an impediment to installing your software, but for an in-house solution it may be appropriate.
I used Brendan Bullen's answer as a starting point for a similar issue I had which was to retrive the value of a specific field in a JSON string. However, like I commented on his answer, it is not entirely accurate. If your left boundary isn't just a space like in the original question, then the discrepancy increases.
Corrected solution:
SUBSTRING(
sentence,
LOCATE(' ', sentence) + 1,
LOCATE(' ', sentence, (LOCATE(' ', sentence) + 1)) - LOCATE(' ', sentence) - 1
)
The two differences are the +1 in the SUBSTRING index parameter and the -1 in the length parameter.
For a more general solution to "find the first occurence of a string between two provided boundaries":
SUBSTRING(
haystack,
LOCATE('<leftBoundary>', haystack) + CHAR_LENGTH('<leftBoundary>'),
LOCATE(
'<rightBoundary>',
haystack,
LOCATE('<leftBoundary>', haystack) + CHAR_LENGTH('<leftBoundary>')
)
- (LOCATE('<leftBoundary>', haystack) + CHAR_LENGTH('<leftBoundary>'))
)
I don't think such a thing is possible. You can use SUBSTRING function to extract the part you want.
My home-grown regular expression replace function can be used for this.
Demo
See this DB-Fiddle demo, which returns the second word ("I") from a famous sonnet and the number of occurrences of it (1).
SQL
Assuming MySQL 8 or later is being used (to allow use of a Common Table Expression), the following will return the second word and the number of occurrences of it:
WITH cte AS (
SELECT digits.idx,
SUBSTRING_INDEX(SUBSTRING_INDEX(words, '~', digits.idx + 1), '~', -1) word
FROM
(SELECT reg_replace(UPPER(txt),
'[^''’a-zA-Z-]+',
'~',
TRUE,
1,
0) AS words
FROM tbl) delimited
INNER JOIN
(SELECT #row := #row + 1 as idx FROM
(SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) t1,
(SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) t2,
(SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) t3,
(SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) t4,
(SELECT #row := -1) t5) digits
ON LENGTH(REPLACE(words, '~' , '')) <= LENGTH(words) - digits.idx)
SELECT c.word,
subq.occurrences
FROM cte c
LEFT JOIN (
SELECT word,
COUNT(*) AS occurrences
FROM cte
GROUP BY word
) subq
ON c.word = subq.word
WHERE idx = 1; /* idx is zero-based so 1 here gets the second word */
Explanation
A few tricks are used in the SQL above and some accreditation is needed. Firstly the regular expression replacer is used to replace all continuous blocks of non-word characters - each being replaced by a single tilda (~) character. Note: A different character could be chosen instead if there is any possibility of a tilda appearing in the text.
The technique from this answer is then used for transforming a string with delimited values into separate row values. It's combined with the clever technique from this answer for generating a table consisting of a sequence of incrementing numbers: 0 - 10,000 in this case.
The field's value is:
"- DE-HEB 20% - DTopTen 1.2%"
SELECT ....
SUBSTRING_INDEX(SUBSTRING_INDEX(DesctosAplicados, 'DE-HEB ', -1), '-', 1) DE-HEB ,
SUBSTRING_INDEX(SUBSTRING_INDEX(DesctosAplicados, 'DTopTen ', -1), '-', 1) DTopTen ,
FROM TABLA
Result is:
DE-HEB DTopTEn
20% 1.2%
I'm trying to do something that sounds really simple but I have been going round in circles a little with it..
I have a stored procedure that currently works as required missing only one bit of functionality, to return a name for a corrosponding max calculation...
So I return
average calculation &
max calculation but want to return 'the name from another column' for the max value.
Here is an example of my SP, apologies that it may not seem very natural as I have had to rename and omit non relevant bits so may seem a little contrived::
SELECT
IFNULL(ROUND(AVG(TABLE1.TotalCapacityPercentageUsage / TABLE1.TotalSnapshotsForTimeSegment), 2), 0.0) AS TotalAvgCapacityPercentageUsage,
IFNULL(ROUND(MAX(TABLE1.MaxCapacityPercentageUsage), 2), 0.0) AS TotalMaxCapacityPercentageUsage,
-- TODO return the QueuesTmp.QueueName for max calculation (This could be more than one row, so I was going to use something like the following:
-- (SELECT GROUP_CONCAT(QueuesTmp.QueueName SEPARATOR ' ') to ensure only one field is returned..
FROM TABLE1
INNER JOIN QueuesTmp ON QueuesTmp.QueueID = TABLE1.QueueID
RIGHT JOIN TimesTmp ON TABLE1.TimeSegment = TimesTmp.QuarterHour AND
TABLE1.Date = DATE(TimesTmp.StartOfRangeUTC)
GROUP BY TimesTmp.QuarterHour;
I started by doing a Sub select but it seems I would then have to repeat all of the Joins, WHERE and Group By (Seems this is not even possible because that's what having is for)..
Can anybody guide me in the right direction as to how this can be achieved?
Thanks in advance.
WORKING SOLUTION
GROUP_CONCAT(DISTINCT QueuesTmp.QueueName ORDER BY MYCOLUMN DESC
SEPARATOR ':') AS MaxColumnQueueName,
I'm not sure that I'm on the right way. You need the QueueName of that row with the max - calculation. So use the group_concat with an ORDER BY of this calculation and get with SUBSTRING_INDEX the first element of this list.
substring_index(
GROUP_CONCAT(DISTINCT QueuesTmp.QueueName ORDER BY `maxCalculation` DESC) SEPARATOR ':',
':',
1
)
Additional question.
Sorry unfortunately the max comment space has reached. Here a query.
I used your example - query for sub and select the queueId as comma-separated list and the max(maxColumn) as additional.
After that I join to queue-table again with queueId and maxColumn. I can't guarantee if that works.
SELECT
sub.TotalAvgCapacityPercentageUsage,
sub.TotalMaxCapacityPercentageUsage,
GROUP_CONCAT(DISTINCT QueuesTmp.QueueName ORDER BY MYCOLUMN DESC SEPARATOR ':') AS MaxColumnQueueName
FROM(
SELECT
TimesTmp.QuarterHour,
IFNULL(
ROUND(
AVG(
TABLE1.TotalCapacityPercentageUsage /
TABLE1.TotalSnapshotsForTimeSegment
),
2
),
0.0
) AS TotalAvgCapacityPercentageUsage,
IFNULL(
ROUND(
MAX(TABLE1.MaxCapacityPercentageUsage),
2
),
0.0
) AS TotalMaxCapacityPercentageUsage,
max(QueuesTmp.maxColumn) AS maxColumn,
group_concat(DISTINCT QueueID) AS QueueID
FROM TABLE1
INNER JOIN QueuesTmp
ON QueuesTmp.QueueID = TABLE1.QueueID
RIGHT JOIN TimesTmp
ON TABLE1.TimeSegment = TimesTmp.QuarterHour
AND TABLE1.Date = DATE(TimesTmp.StartOfRangeUTC)
GROUP BY TimesTmp.QuarterHour
) AS sub
LEFT JOIN QueuesTmp
ON QueuesTmp.QueueID IN(sub.QueueID)
AND QueuesTmp.maxColumn = sub.maxColumn
I need a better way to replace a non-numeric characters in a string.
I have phone numbers like so
(888) 488-6655
888-555-8888
blah blah blah
So I am able to return a clean string by using a simple replace function but I am looking for a better way may be using expression function to replace any non-numeric value. like space slash, backslash, quote..... any none numeric value
this is my current query
SELECT
a.account_id,
REPLACE(REPLACE(REPLACE(REPLACE(t.phone_number, '-', ''), ' ', ''), ')', ''),'(','') AS contact_number,
IFNULL(t.ext, '') AS extention,
CASE WHEN EXISTS (SELECT number_id FROM contact_numbers WHERE main_number = 1 AND account_id = a.account_id) THEN 0 ELSE 1 END AS main_number,
'2' AS created_by
FROM cvsnumbers t
INNER JOIN accounts a ON a.company_code = t.company_code
WHERE REPLACE(REPLACE(REPLACE(REPLACE(t.phone_number, '-', ''), ' ', ''), ')', ''),'(','') NOT IN(SELECT contact_number FROM contact_numbers WHERE account_id = a.account_id)
AND LENGTH(REPLACE(REPLACE(REPLACE(REPLACE(t.phone_number, '-', ''), ' ', ''), ')', ''),'(','') ) = 10
How can I change my query to use an REGEX to replace non-numeric values.
Thanks
This is a brute force approach.
The idea is to create a numbers table, which will index each digit in the phone number. Keep the digit if it is a number and then group them together. Here is how it would work:
select t.phone_number,
group_concat(SUBSTRING(t.phone_number, n.n, 1) separator '' order by n
) as NumbersOnly
from cvsnumbers t cross join
(select 1 as n union all select 2 union all select 3
) n
where SUBSTRING(t.phone_number, n.n, 1) between '0' and '9'
group by t.phone_number;
This example only looks at the first 3 digits in the number. You would expand the subquery for n to the maximum length of a phone number.
I don't know the mySql regex flavour but I would give this a go:
REPLACE(t.phone_number, '[^\d]+', '')
[^\d]+ means: 'Match everything that is not a digit, once or more times'
You might need to escape the backslash ([^\\d]+).