SQL subquery result in WHERE - sql

I have a Paths table with columns PathID (unique) and PathStr.
I'd like to get all PathStr records in this table what starts with a string, came from a selected PathID.
In other words I'd like to get all subfolders of a folder if I know the PathID of the root folder.
Example table:
PathID | PathStr
-------+------------------------
1 | D:\Project1
2 | D:\Project1\Sub1
3 | D:\Project1\Sub1\Sub11
4 | D:\Project2
5 | D:\Project2\Sub1
Required result if the PathID = 1:
D:\Project1
D:\Project1\Sub1
D:\Project1\Sub1\Sub11
My query looks like this now, but I'm stuck with the subquery at WHERE:
SELECT P.PathStr As 'Folder'
FROM Paths AS P
WHERE P.PathStr LIKE (SELECT Paths.PathStr FROM Paths WHERE Paths.PathID = 1) + "%"
I guess I can't simply concatenate the result of the sub-query with a string, but I don't know how can I do that, I didn't find solution with my friend google :)
The result of the sub-query is always one record.
Thank you

I would suggest instead using exists:
SELECT p.PathStr as Folder
FROM Paths p
WHERE EXISTS (SELECT 1
FROM Paths p2
WHERE p2.PathId = 1 AND
p.PathStr LIKE CONCAT(p2.PathStr, '%')
);
You have not tagged your database. The standard operator for string concatenation is || and many databases also support a CONCAT() function. The use of + for string concatenation is quite limited.
EDIT:
To fix the problem mentioned in the comment:
SELECT p.PathStr as Folder
FROM Paths p
WHERE EXISTS (SELECT 1
FROM Paths p2
WHERE p2.PathId = 1 AND
CONCAT(p.PathStr, '\') LIKE CONCAT(p2.PathStr, '\%')
);
Note: In some databases the backslash would need to be doubled so it is not an escape character.

Related

PostgreSQL SQL query to find number of occurrences of substring in string

I’m trying to wrap my head around a problem but I’m hitting a blank. I know SQL quite well, but I’m not sure how to approach this.
My problem:
Given a string and a table of possible substrings, I need to find the number of occurrences.
The search table consists of a single colum:
searchtable
| pattern TEXT PRIMARY KEY|
|-------------------------|
| my |
| quick |
| Earth |
Given the string "Earth is my home planet and where my friends live", the expected outcome is 3 (2x "my" and 1x "Earth").
In my function, I have variable bodytext which is the string to examine.
I know I can do IN (SELECT pattern FROM searchtable) to get the list of substrings, and I could possibly use a LIKE ANY clause to get matches, but how can I count occurrences of the substrings in the table within the search string?
This is easily done without a custom function:
select count(*)
from (values ('Earth is my home planet and where my friends live')) v(str) cross join lateral
regexp_split_to_table(v.str, ' ') word join
patterns p
on word = p.pattern
Just break the original string into "words". Then match on the words.
Another method uses regular expression matching:
select (select count(*) from regexp_matches(v.str, p.rpattern, 'g'))
from (values ('Earth is my home planet and where my friends live')) v(str) cross join
(select string_agg(pattern, '|') as rpattern
from patterns
) p;
This stuffs all the patterns into a regular expression. Not that this version does not take word breaks into account.
Here is a db<>fiddle.
I solved the problem with the following code:
CREATE OR REPLACE FUNCTION count_matches(body TEXT, OUT matches INTEGER) AS $$
DECLARE
results INTEGER := 0;
matchlist RECORD;
BEGIN
FOR matchlist IN (SELECT pattern FROM searchtable)
LOOP
results := results + (SELECT LENGTH(body) -
LENGTH(REPLACE(body, matchlist.pattern, ''))) /
LENGTH(matchlist.pattern);
END LOOP;
matches := results;
END;
$$ LANGUAGE plpgsql;

how to skip particular character(s) in SQL LIKE query

I have a table(say users) in which there is a column say name.
you may think table structure a shown below:
-------------
name
--------------
Abdul Khalid
--------------
Abdul, Khalid
--------------
Abdul - Khalid
--------------
other names
My question is can I do some query to find all the 3 rows in which the name column value is "Abdul Khalid"(basically "Abdul Khalid" or "Abdul, Khalid" or "Abdul - Khalid" if I skip the "," and "-" character).
You can use like:
select t.*
from t
where name like 'Abdul%Khalid';
If you want the names anywhere in the string (but in that order), then put wildcards at the beginning:
select t.*
from t
where name like '%Abdul%Khalid%';
If you are passing in the value as a variable:
select t.*
from t
where name like replace('Abdul Khalid', ' ', '%');
For PostgreSQL is better to use '~'
name ~ '^Abdul[ ,-]Khalid$'
OR if you want also in middle of string:
name ~ 'Abdul[ ,-]Khalid'
Or you can use translate (with index on it) for any SQL:
translate(name, ' ,-') = 'AbdulKhalid'
you also can use REGEXP like this:
SELECT * from yourTable where name REGEXP 'Abdul( |, | - )Khalid';

SQL special group by on list of strings ending with *

I would like to perform a "special group by" on strings with SQL language, some ending with "*". I use postgresql.
I can not clearly formulate this problem, even if I have partially solved it, with select, union and nested queries which are not elegant.
For exemple :
1) INPUT : I have a list of strings :
thestrings
varchar(9)
--------------
1000
1000-0001
1000-0002
2000*
2000-0001
2000-0002
3000*
3000-00*
3000-0001
3000-0002
2) OUTPUT : That I would like my "special group by" return :
1000
1000-0001
1000-0002
2000*
3000*
Because 2000-0001 and 2000-0002 are include in 2000*,
and because 3000-00*, 3000-0001 and 3000-0002 are includes in 3000*
3) SQL query I do :
SELECT every strings ending with *
UNION
SELECT every string where the begining NOT IN (SELECT every string ending with *) <-- with multiple inelegant left functions and NOT IN subqueries
4) That what I'm doing return :
1000
1000-0001
1000-0002
2000*
3000*
3000-00* <-- the problem
The problem is : 3000-00* staying in my result.
So my question is :
How can I generalize my problem? to remove all string who have a same begining string in the list (ending with *) ?
I think of regular expressions, but how to pass a list from a select in a regex ?
Thanks for help.
Select only strings for which no master string exists in the table:
select str
from mytable
where not exists
(
select *
from mytable master
where master.str like '%*'
and master.str <> mytable.str
and rtrim(mytable.str, '*') like rtrim(master.str, '*') || '%'
);
Assuming that only one general pattern can match any given string, the following should do what you want:
select coalesce(tpat.thestring, t.thestring) as thestring
from t left join
t tpat
on t.thestring like replace(tpat.thestring, '*', '%') and
t.thestring <> tpat.thestring
group by coalesce(tpat.thestring, t.thestring);
However, that is not your case. However, you can adjust this with distinct on:
select distinct on (t.thestring) coalesce(tpat.thestring, t.thestring)
from t left join
t tpat
on t.thestring like replace(tpat.thestring, '*', '%') and
t.thestring <> tpat.thestring
order by t.thestring, length(tpat.thestring)

Concatenating fields when BREAK ON command is used

I have built a command that uses the BREAK ON command to stop the output of duplicate field names. For example:
f.name | f.value
f.name | f.value
f.name | f.value
becomes:
f.name | f.value
| f.value
| f.value
Is there any way to have this output as:
f.name | f.value,f.value,f.value
In some instances the f.name field with have over 20 f.values associated with it.
The output will eventually be used to import into other places so I am trying to make the output as friendly as possible.
You're not looking for a SQL*Plus command, you're looking for a string aggregation.
Assuming your current query is:
select name, value from my_table
you can change it as follows to get your desired result. The DISTINCT is included to eliminate duplicate results in your list.
select name, listagg(value, ', ') within group (order by value) as value
from ( select distinct name, value from my_table )
group by name
LISTAGG() was only released in 11.2, if you're using an earlier version of Oracle you could use the undocumented function WM_CONCAT() or the user defined function STRAGG() as outlined in this useful page on string aggregation techniques.

sql in clause doesn't work

I have a table with a column ancestry holding a list of ancestors formatted like this "1/12/45". 1 is the root, 12 is children of 1, etc...
I need to find all the records having a specific node/number in their ancestry list. To do so, I wrote this sql statement:
select * from nodes where 1 in (nodes.ancestry)
I get following error statement: operator does not exist: integer = text
I tried this as well:
select * from nodes where '1' in (nodes.ancestry)
but it only returns the records having 1 in their ancestry field. Not the one having for instance 1/12/45
What's wrong?
Thanks!
This sounds like a job for LIKE, not IN.
If we assume you want to search for this value in any position, and then we might try:
select * from nodes where '/' + nodes.ancestry + '/' like '%/1/%'
Note that exact syntax for string concatenation varies between SQL products. Note that I'm prepending and appending to the ancestry column so that we don't have to treat the first/last items in the list differently than middle items. Note also that we surround the 1 with /s, so that we don't get false matches for e.g. with /51/ or /12/.
In MySQL you could write:
SELECT * FROM nodes
WHERE ancestry = '1'
OR LEFT(ancestry, 2) = '1/'
OR RIGHT(ancestry, 2) = '/1'
OR INSTR(ancestry, '/1/') > 0
The in operator expects a comma separated list of values, or a query result, i.e.:
... in (1,2,3,4,5)
or:
... in (select id from SomeOtherTable)
What you need to do is to create a string from the number, so that you can look for it in the other string.
Just looking for the string '1' in the ancestry list would give false positives, as it would find it in the string '2/12/45'. You need to add the separator to the beginning and the end of both strings, so that you look for a string like '/1/' in a string like '/1/12/45/':
select * from nodes
where charindex('/' + convert(varchar(50), 1) + '/', '/' + nodes.ancestry + '/') <> 0