List characters (and their count) which meet a regex - sql

This example lists the result of column col, which does not contain solely alphanumeric characters:
select col
from foo
where col ~ '[^a-zA-Z0-9]';
My aim is to list all the different characters which meet the regex with the count of their occurrence.

Try this :
SELECT c.char[1], count(*) AS count
FROM foo
CROSS JOIN LATERAL regexp_matches(col, '[^a-zA-Z0-9]', 'g') AS c(char)
GROUP BY c.char
see the test result in dbfiddle.

Related

How to know which regular expression pattern is matched in SQL Query

I am writing a SQL query and I have n regular expressions to match in the query.
SELECT * FROM
Sample_Table
WHERE
(REGEXP_CONTAINS(content, r"[a-z0-9A-Z]{40}") OR -- expression 1
REGEXP_CONTAINS(content, r"[0-9a-z]{32}") OR -- expression 2
..................
REGEXP_CONTAINS(content, r"[a-z0-9]{80}") OR -- expression n-1
REGEXP_CONTAINS(content, r"[a-z0-9A-Z\%]{35}")) -- expression n
Now, when I get back the result, I want to know the particular returned row is matched by expression 1 or expression 2. One option would have been to run each regular expression once and tag the result. But, I have to run all the regular expression in one query because of resource limitation in Google BigQuery.
Is there any way to tag each returned result row with the matched regular expression?
Consider below approach
with patterns as (
select 1 pattern_id, r"[a-z0-9A-Z]{40}" pattern union all
select 2, r"[0-9a-z]{32}" union all
select 3, r"[a-z0-9]{80}" union all
select 4, r"[a-z0-9A-Z\%]{35}"
)
select any_value(t).*, string_agg('' || pattern_id) matches
from Sample_Table t, patterns p
where regexp_contains(content, pattern)
group by format('%t', t)
if you have better column to use for group by you should use it - for example
select any_value(t.sample_repo_name), string_agg('' || pattern_id) as matches
from `bigquery-public-data.github_repos.sample_contents` t, patterns p
where regexp_contains(content, pattern)
group by id

Select distinct values of comma-separated values column excluding subsets in PostgreSQL

Assume having a table foo with column bar that carries comma-separated values,
('a,b',
'a,b,c',
'a,b,c,d',
'd,e')
How can I select the largest combination and exclude all the subsets included in that combination (the largest one)?
Example on the above data-set. The result should be:
('a,b,c,d', 'd,e') and the first two entities ('a,b', 'a,b,c') are excluded as they are subset of ('a,b,c,d').
Taking in consideration that all the values in the comma-separated string are sorted alphabetically.
I tried the below query, but the results seem a little far away from what I need:
select distinct a.bar from foo a inner join foo b
on a.bar like '%'|| b.bar||'%'
and a.bar != b.bar
You can use string_to_array() to split the strings into an array. With the contains operator, #>, you can check whether an array contains another. (See "9.18. Array Functions and Operators".)
Use that in a NOT EXISTS clause. fi.ctid <> fo.ctid is there to make sure the physical addresses of the compared pair of rows is not equal, as of course an array of one row would contain the array compared to the same row.
SELECT fo.bar
FROM foo fo
WHERE NOT EXISTS (SELECT *
FROM foo fi
WHERE fi.ctid <> fo.ctid
AND string_to_array(fi.bar, ',') #> string_to_array(fo.bar, ','));
SQL Fiddle
But: Don't use comma-separated strings in a relational database. You've got something way better. It's called "table".
First process the string into sets of characters, and then cross join the character-sets with itself, excluding rows where the character-sets on both sides are the same.
Next, aggregate and use BOOL_OR in a HAVING clause to filter out any character-set that is a subset of any other character-set.
With a sample table declared in the CTE, the query becomes:
WITH foo(bar) AS (SELECT '("a,b" , "a,b,c" , "a,b,c,d" , "d,e")'::TEXT)
SELECT bar, string_to_array(elems[1], ',') not_subset
FROM foo
CROSS JOIN regexp_matches(bar, '[\w|,]+', 'g') elems
CROSS JOIN regexp_matches(bar, '[\w|,]+', 'g') elems2
WHERE elems2[1] != elems[1]
-- my regex also matches the ',' between sets which need to be ignored
-- alternatively, i have to refine the regex
AND elems2[1] != ','
AND elems[1] != ','
GROUP BY 1, 2
HAVING NOT BOOL_OR(string_to_array(elems[1], ',') <# string_to_array(elems2[1], ','))
produces the output
bar not_subset
'("a,b" , "a,b,c" , "a,b,c,d" , "d,e")' {'d','e'}
'("a,b" , "a,b,c" , "a,b,c,d" , "d,e")' {'a','b','c','d'}
Example in SQL Fiddle

Get group maxima from combined strings

I have a table with a column code containing multiple pieces of data like this:
001/2017/TT/000001
001/2017/TT/000002
001/2017/TN/000003
001/2017/TN/000001
001/2017/TN/000002
001/2016/TT/000001
001/2016/TT/000002
001/2016/TT/000001
002/2016/TT/000002
There are 4 items in 001/2016/TT/000001: 001, 2016, TT and 000001.
How can I extract the max for every group formed by the first 3 items? The result I want is this:
001/2017/TT/000003
001/2017/TN/000002
001/2016/TT/000002
002/2016/TT/000002
Edit
The subfield separator is /, and the length of subfields can vary.
I use PostgreSQL 9.3.
Obviously, you should normalize the table and split the combined string into 4 columns with proper data type. The function split_part() is the tool of choice if the separator '/' is constant in your string and the length of can vary.
CREATE TABLE tbl_better AS
SELECT split_part(code, '/', 1)::int AS col_1 -- better names?
, split_part(code, '/', 2)::int AS col_2
, split_part(code, '/', 3) AS col_3 -- text?
, split_part(code, '/', 4)::int AS col_4
FROM tbl_bad
ORDER BY 1,2,3,4 -- optionally cluster data.
Then the task is trivial:
SELECT col_1, col_2, col_3, max(col_4) AS max_nr
FROM tbl_better
GROUP BY 1, 2, 3;
Related:
Split comma separated column data into additional columns
Of course, you can do it on the fly, too. For varying subfield length you could use substring() with a regular expression like this:
SELECT max(substring(code, '([^/]*)$')) AS max_nr
FROM tbl_bad
GROUP BY substring(code, '^(.*)/');
Related (with basic explanation for regexp pattern):
Filter strings with regex before casting to numeric
Or to get only the complete string as result:
SELECT DISTINCT ON (substring(code, '^(.*)/'))
code
FROM tbl_bad
ORDER BY substring(code, '^(.*)/'), code DESC;
About DISTINCT ON:
Select first row in each GROUP BY group?
Be aware that data items cast to a suitable type may behave differently from their string representation. The max of 900001 and 1000001 is 900001 for text and 1000001 for integer ...
Use the LEFT and RIGHT functions.
SELECT MAX(RIGHT(code,6)) AS MAX_CODE
FROM yourtable
GROUP BY LEFT(code,12)
check this out, possible helpfull
select
distinct on (tab[4],tab[2]) tab[4],tab[3],tab[2],tab[1]
from
(
select
string_to_array(exe.x,'/') as tab,
exe.x
from
(
select
unnest
(
array
['001/2017/TT/000001',
'001/2017/TT/000002',
'001/2017/TN/000003',
'001/2017/TN/000001',
'001/2017/TN/000002',
'001/2016/TT/000001',
'001/2016/TT/000002',
'001/2016/TT/000001',
'002/2016/TT/000002']
) as x
) exe
) exe2
order by tab[4] desc,tab[2] desc,tab[3] desc;

pgsql parse string to get a string after certain position

I have a table column that has data like
NA_PTR_51000_LAT_CO-BOGOTA_S_A
NA_PTR_51000_LAT_COL_M_A
NA_PTR_51000_LAT_COL_S_A
NA_PTR_51000_LAT_COL_S_B
NA_PTR_51000_LAT_MX-MC_L_A
NA_PTR_51000_LAT_MX-MTY_M_A
I want to parse each column value so that I get the values in column_B. Thank you.
COLUMN_A COLUMN_B
NA_PTR_51000_LAT_CO-BOGOTA_S_A CO-BOGOTA
NA_PTR_51000_LAT_COL_M_A COL
NA_PTR_51000_LAT_COL_S_A COL
NA_PTR_51000_LAT_COL_S_B COL
NA_PTR_51000_LAT_MX-MC_L_A MX-MC
NA_PTR_51000_LAT_MX-MTY_M_A MX-MTY
I'm not sure of the Postgresql and I can't get SQL fiddle to accept the schema build...
substring and length may vary...
Select Column_A, substr(columN_A,18,length(columN_A)-17-4) from tableName
Ok how about this then:
http://sqlfiddle.com/#!15/ad0dd/56/0
Select column_A, b
from (
Select Column_A, b, row_number() OVER (ORDER BY column_A) AS k
FROM (
SELECT Column_A
, regexp_split_to_table(Column_A, '_') b
FROM test
) I
) X
Where k%7=5
Inside out:
Inner most select simply splits the data into multiple rows on _
middle select adds a row number so that we can use the use the mod operator to find all occurances of a 5th remainder.
This ASSUMES that the section of data you're after is always the 5th segment AND that there are always 7 segments...
Use regexp_matches() with a search pattern like 'NA_PTR_51000_LAT_(.+)_'
This should return everything after NA_PTR_51000_LAT_ before the next underscore, which would match the pattern you are looking for.

Counting word lengths in a string

I am using an Oracle regular expression to extract the first letter of each word in a string. The results are returned in a single cell, with spaces representing hard breaks. Here is an example...
input:
'I hope that some kind person
browsing stack overflow
can help me'
output:
ihtskp bso chm
What I am trying to do next is count the length of each "word" in my output, like this:
6 3 3
Alternatively, a count of the words in each line of the original string would be acceptable, as it would yield the same result.
Thanks!
Count the number of spaces and add one:
select (length(your_col) - length(replace(your_col, ' '))+1) from your_table;
It will give you the number of words per line. From there you can get all counts on one line by using listagg function:
select LISTAGG(cnt,' ') within group (order by null) from (
select (length(a)-length(replace(a,' '))+1) cnt from (
select 'apa bpa bv' a from dual
union all
select 'n bb gg' a from dual
union all
select 'ff ff rr gg' a from dual))
group by null;
Perhaps you also need to split the strings if they contain newlines or are they split already?
I tried to edit my original post but it hasn't appeared, but I figured out a way to solve my issue. I just decided to break the words into rows, since I know how to character count rows, and then reassembled the character counts into a single cell using listagg:
with my_string as (
select regexp_substr (words,'[0-9]+|[a-z]+|[A-Z]+',1,lvl) parsed
from (
select words, level lvl
from letters connect by level <= length(words) - length(replace(words,' ')) + 1)
)
select listagg(length(parsed),' ') within group (order by parsed) word_count
from my_string