PostgreSQL - join tables using pattern matching - sql

I have two tables and need to join them using two columns that are similar.
The first table is called articles has a column called 'slug' with slug lines for articles, ex: 'trump-fails-yet-again.'
The second table is called log and has a column called path with the url path for the articles, ex: '/articles/trump-fails-yet-again/'
Here is my search query:
"SELECT articles.title, count(*) as num FROM articles, log WHERE articles.slug LIKE CONCAT('%',log.path) GROUP BY articles.title;"
This returns nothing but brackets, []
I have also tried:
"SELECT articles.title, count(*) as num FROM articles JOIN log ON articles.slug SIMILAR TO CONCAT('%',log.path) GROUP BY articles.title;"
That returns a DataError: invalid regular expression: quantifier operand invalid
Any help is greatly appreciated!

try this:
` select articles.title, count(*) as views from articles
join log on articles.slug ~~ ('%' || articles.slug || '%')
group by articles.title;`

You have a slash at the end of the path. How about this?
SELECT a.title, count(*) as num
FROM articles a JOIN
log l
ON a.path LIKE '%' || l.slug || '%'
GROUP BY a.title;
You should also learn to use proper, explicit JOIN syntax. Never use commas in the FROM clause.

Because there is a 1:1 function with this you can do this
SELECT articles.title, count(*) as num
FROM articles
JOIN log ON articles.slug = '/articles/' || articles.slug || '/'
GROUP BY articles.title;
Or even better
CREATE FUNCTION slug_to_article_path( slug text )
RETURNS text AS
$$
SELECT '/articles/' || slug || '/';
$$ LANGUAGE sql
IMMUTABLE;
SELECT articles.title, count(*) as num
FROM articles
JOIN log ON articles.slug = slug_to_article_path(articles.slug)
GROUP BY articles.title;

Related

How to take multiple rows and make a comma separated list in SQL

I have a query I ran which is
SELECT * FROM rpg.class_primary_abilities AS cpab
INNER JOIN rpg.abilities AS ab ON cpab.ability_id = ab.ability_id
INNER JOIN rpg.classes AS cl ON cpab.class_id = cl.class_id;
It gives me the below output.
What I would like to know is how can I change this query in order to display everything except the first class_id column in a comma separated list. Essentially I would like to get an output of class_idand value,value,value,value,value,value,value,value as my two columns in the output.
I am a college student just learning SQL and have not been exposed to any possible solutions to this problem in class. Any help is appreciated.
Most databases support a function such as string_agg() or listagg() that does what you want:
SELECT cl.class_id,
STRING_AGG(ability_id, ',') as ability_ids,
. . .
FROM rpg.class_primary_abilities cpab JOIN
rpg.abilities ab
ON cpab.ability_id = ab.ability_id JOIN
rpg.classes cl
ON cpab.class_id = cl.class_id
GROUP BY cl.class_id
Please tag your database for more info.
This is one way for Oracle where Ihave used a concatenate sign which is || to concatenate(put two strings together in one) and between them I have also concatenated a comma ,. You can also see that I have used double quotes for the column named desc. I did it because it is not a good practice to call your columns with keywords and word desc is used for example when you order by some column(at the end of the query) you can order by that column ascending then you use asc or descending when you can use desc. Also in both examples I used keyword as to give a name to this concatenated column.
SELECT class_id, cpab.ability_id || ',' ||
ab.ability_id || ',' ||
ab.name || ',' ||
class_id || ',' ||
cpab.name || ',' ||
hit_die || ',' ||
"desc" || ',' ||
isPlayable as values
FROM rpg.class_primary_abilities AS cpab
INNER JOIN rpg.abilities AS ab ON cpab.ability_id = ab.ability_id
INNER JOIN rpg.classes AS cl ON cpab.class_id = cl.class_id;
This is another for MYSQL where I have used concat to concatenate column values and I have used different single quotes for desc column.:
SELECT class_id, concat(cpab.ability_id, ',' ,
ab.ability_id, ',' ,
ab.name, ',' ,
class_id, ',' ,
cpab.name, ',' ,
hit_die, ',' ,
`desc`, ',' ,
isPlayable) as values
FROM rpg.class_primary_abilities AS cpab
INNER JOIN rpg.abilities AS ab ON cpab.ability_id = ab.ability_id
INNER JOIN rpg.classes AS cl ON cpab.class_id = cl.class_id;
In both examples you have columns with same name from different tables and theer you will have to use aliases when calling them in your select clause like I have did in my example: cpab.ability_id and ab.ability_id but please note that I do not know if they are from cpab and ab tables.

SQL query to find matches

I have written a query to provide matches with the same DB and it's giving me expected results except that I don't get few part of it. Below is the query :
select f.name, f.id, f.industry, d.name, d.id, d.industry
from product_table f, product_table d
where (f.name like '%' || d.name || '%') and
(f.industrylike '%' || d.industry|| '%') and
I know by providing this it's actually looking for matches between the 2 columns :
(..... like '%' || ..... || '%')
But what does each part of it do exactly and what does it mean?
This query is executing a self-join (here, a cross self-join) in which we query two instances of the same table for some purpose. In this case it looks like some form of data quality exercise, where we suspect we might have almost duplicate records. That is, we think we have records for the same combination of (product name and industry). The use of wild cards will identify records where the value of one column is wholly embedded in another column: for instance '%STACK%' matches 'META STACKOVERFLOW'.
The posted version has a potential flaw, in that if there are two records with an exact match you will get two hits (one for F:D, one for D:F). You can finagle that by adding a filter on id
select f.name, f.id, f.industry,
d.name, d.id, d.industry
from product_table f, product_table d
where (f.name like '%' || d.name || '%')
and (f.industrylike '%' || d.industry|| '%')
and ( ( f.name = d.name
and f.industry = d.industry
and f.id < d.id )
or f.name != d.name
or f.industry != d.industry
)
The double vertical bar (more commonly known as a pipe) is the concatenation operator. It is used for joining strings together. (Many programming languages use + but Oracle reserves that strictly for arithmetic on numbers.)
not so much clear on why we put it before and after only the second column : f.name like '%' || d.name || '%'
In this case, the query is concatenating a wild card. Given this value for f.name = 'XYZ' , we would get matches for '%' || d.name || '%' on:
'1XYZ1'
'11XYZ11'
'11XYZ'
'XYZ1'
'XYZ' <---- matching same record
We don't need to wrap f.name in wildcard operators because the query is a self-join so all the values of name will appear on the left hand side of the filter. When f.name = '1XYZ1' it match for '%' || d.name || '%' on:
'1XYZ1' <---- matching same record
'XYZ1'
'XYZ'
So you're going to get multiple hits already. Embedding both sides of the filter in wildcards will only generate more noisy duplicates.

Postgres concatenate regex and table column

I would like to run the query
SELECT * FROM table_a JOIN table_b ON table_a.title LIKE ('^[a-b]' || table_b.title)
where if table_a.title = "the foo bar", and table_b.title has a row named "oo", the query will not return any results, but if table_b.title has a row named "foo", it will return results.
Basically I want to match the title on table_b only if it is an entire word (surrounded by spaces, or at the beginning and end of string) but not if another word has a part of table_b.title in it.
Any ideas?
If you want to use regular expressions in Postgres, use ~, similar to, or regexp_matches, not like:
SELECT *
FROM table_a JOIN
table_b
ON table_a.title ~ ('^[a-b]' || table_b.title);
This is what ended up working for me:
SELECT 1 FROM table_a JOIN table_b
ON (' ' || table_b.title || ' ') ~* ('.*?[^a-zA-Z0-9]+' || table_b.name || '[^a-zA-Z0-9].*?')
where table_b.id IS NOT NULL

SQL select is query is not working properly

I am unable to apply a filter(where condition) on the query
SELECT A.Form_Id,
B.CONTAINER_ID,
A.FORM_DESC,
A.FORM_TITLE,
A.LAYOUT,
A.TOTAL_COLUMNS,
COUNT (*) Over () AS Total_Rows
ROW_NUMBER () OVER ( ORDER BY CONTAINER_ID ASC ) ROWNM
FROM FORM_DEFINITION A
LEFT JOIN
(SELECT CONTAINER_ID,FORM_ID FROM FORM_CONTAINER_DEFINITION
) B
ON A.FORM_ID = B.FORM_ID
AND ( ( UPPER(TRIM(A.FORM_ID)) LIKE '%'
|| UPPER(TRIM('FORM2'))
||'%' ) )
In the above code I applied filter like this
( ( UPPER(TRIM(A.FORM_ID)) LIKE '%'
|| UPPER(TRIM('FORM2'))
||'%' ) )
Except this part the query is giving all the info. This filter should show only 'FORM2' row.
But it is showing all the rows as normaly.
.
.
Could you resolve my issue....
.
.
Thanks in advance. :)
Conditions on the first table in a LEFT JOIN need to go in the WHERE clause. On the second table, in the ON clause. Also, the subquery is not necessary. So:
SELECT . ..
FROM FORM_DEFINITION A LEFT JOIN
FORM_CONTAINER_DEFINITION B
ON A.FORM_ID = B.FORM_ID
WHERE UPPER(TRIM(A.FORM_ID)) LIKE '%' || UPPER(TRIM('FORM2')) || '%';
The logic is actually simpler than the above rule. A LEFT JOIN keeps all rows in the first table, regardless of the condition in the ON clause. Matching rows get the values from the second table. Non-matching rows get NULL values.
This is true even when the condition is on the first table.
Also, I would encourage you to use sensible aliases for tables rather than A and B. I would suggest FD and FCD for these two tables.

Sql query to select records only when count(column)=1

I am trying to retrieve records from oracle 10g express.
I want to execute :
select name|| '=' || id from literals where name='vge_1'
only when count(vge_1) is equal to 1.
else I want to display an error.
I tried following query, but its giving
ORA-00905: missing keyword
THe query I tried is as follows:
select case(name)
when count('vge_1') then (select name|| '=' || id from literals where name='vge_1';)
else Errror
end
from Literals where name='vge_1';
Thanks for your help in advance.
Instead of the case add HAVING count(name)=1 in the end of th query
Try this:
select b.id,a.* from
(select name from Literals
where name='vge_1'
group by name
having count(name)=1)a,Literals b
where b.name=a.name
Try this SQL Fiddle:
select distinct case
when l2.c = 1 then l1.name || '=' || l1.id
else 'Error'
end as name_id
from literals l1,
(
select name, count(name) as c
from literals
where name = 'vge_1'
group by name
having count(name) = 1
) l2
where l1.name = l2.name(+)
and l1.name = 'vge_1'
;
The inner query is roughly same as the other answers. The outer query uses a left outer join (+) to determine if the inner query contains a match for your count() restriction.
Note that you must update the name in two places when running this query.