Concatenate several fields into one with SQL - sql

I have three tables tag, page, pagetag
With the data below
page
ID NAME
1 page 1
2 page 2
3 page 3
4 page 4
tag
ID NAME
1 tag 1
2 tag 2
3 tag 3
4 tag 4
pagetag
ID PAGEID TAGID
1 2 1
2 2 3
3 3 4
4 1 1
5 1 2
6 1 3
I would like to get a string containing the correspondent tag names for each page with SQL in a single query. This is my desired output.
ID NAME TAGS
1 page 1 tag 1, tag 2, tag 3
2 page 2 tag 1, tag 3
3 page 3 tag 4
4 page 4
Is this possible with SQL?
I am using MySQL. Nonetheless, I would like a database vendor independent solution if possible.

Sergio del Amo:
However, I am not getting the pages without tags. I guess i need to write my query with left outer joins.
SELECT pagetag.id, page.name, group_concat(tag.name)
FROM
(
page LEFT JOIN pagetag ON page.id = pagetag.pageid
)
LEFT JOIN tag ON pagetag.tagid = tag.id
GROUP BY page.id;
Not a very pretty query, but should give you what you want - pagetag.id and group_concat(tag.name) will be null for page 4 in the example you've posted above, but the page shall appear in the results.

Yep, you can do it across the 3 something like the below:
SELECT page_tag.id, page.name, group_concat(tags.name)
FROM tag, page, page_tag
WHERE page_tag.page_id = page.page_id AND page_tag.tag_id = tag.id;
Has not been tested, and could be probably be written a tad more efficiently, but should get you started!
Also, MySQL is assumed, so may not play so nice with MSSQL! And MySQL isn't wild about hyphens in field names, so changed to underscores in the above examples.

As far as I'm aware SQL92 doesn't define how string concatenation should be done. This means that most engines have their own method.
If you want a database independent method, you'll have to do it outside of the database.
(untested in all but Oracle)
Oracle
SELECT field1 | ', ' | field2
FROM table;
MS SQL
SELECT field1 + ', ' + field2
FROM table;
MySQL
SELECT concat(field1,', ',field2)
FROM table;
PostgeSQL
SELECT field1 || ', ' || field2
FROM table;

I got a solution playing with joins. The query is:
SELECT
page.id AS id,
page.name AS name,
tagstable.tags AS tags
FROM page
LEFT OUTER JOIN
(
SELECT pagetag.pageid, GROUP_CONCAT(distinct tag.name) AS tags
FROM tag INNER JOIN pagetag ON tagid = tag.id
GROUP BY pagetag.pageid
)
AS tagstable ON tagstable.pageid = page.id
GROUP BY page.id
And this will be the output:
id name tags
---------------------------
1 page 1 tag2,tag3,tag1
2 page 2 tag1,tag3
3 page 3 tag4
4 page 4 NULL
Is it possible to boost the query speed writing it another way?

pagetag.id and group_concat(tag.name) will be null for page 4 in the example you've posted above, but the page shall appear in the results.
You can use the COALESCE function to remove the Nulls if you need to:
select COALESCE(pagetag.id, '') AS id ...
It will return the first non-null value from it's list of parameters.

I think you may need to use multiple updates.
Something like (not tested):
select ID as 'PageId', Name as 'PageName', null as 'Tags'
into #temp
from [PageTable]
declare #lastOp int
set #lastOp = 1
while #lastOp > 0
begin
update p
set p.tags = isnull(tags + ', ', '' ) + t.[Tagid]
from #temp p
inner join [TagTable] t
on p.[PageId] = t.[PageId]
where p.tags not like '%' + t.[Tagid] + '%'
set #lastOp == ##rowcount
end
select * from #temp
Ugly though.
That example's T-SQL, but I think MySql has equivalents to everything used.

Related

Return count id's value from multiple rows in one column Postgres

I'm having two tables (relation between themTest_case.id = Test_tag.test_id) like this:
Test_case table
id
name
1
Test name 1
2
Test name 2
3
Test name 3
4
Test name 4
Test_tag table
test_id
tag
1
feature:example1
1
package:Reports
1
QA
2
feature:example1
2
package:Reports
2
QA
3
feature:example1
3
package:Reports
3
QA
4
feature:newexample1
4
package:Charts
4
QA
The database tables and structure were already defined as I'm using a oublic library to push the results.
So, I need to return in the result the count of the id's and the value feature:example1
knowing that is a test that contains the tag package:Reports
So, it should return something like
Results
count(id)
tag
3
feature:example1
I already tried some different approaches without success.
How can I do that?
I think I'm as confused as everyone else, but this is a shot in the dark based on the various comments. There are much easier ways to arrive at this dataset, but I'm trying to read between the lines on your comments:
select
count (t.test_id), t.tag
from
test_case c
join test_tag t on c.id = t.test_id
where
t.tag like 'feature%' and
exists (
select null
from test_tag t2
where t2.test_id = t.test_id and t2.tag = 'package:Reports'
)
group by
t.tag

Combining row values from multiple tables into one result cell

I'm looking to create a report of sorts and am having a hard time wrapping my head around how this portion could be done with a single select in SQL (my experience is limited to a database course and some working knowledge - more of a front end dev).
I should mention that joining the question table and question tag bindings/tags table isn't an issue for me - what I can't wrap my head around is how multiple values could be added to the same result cell without multiple nasty T-SQL loops.
Any tips on how to get started would be a huge help.
Table 1: Question Table
ID Content CategoryName
---------------------------
1 ABC Q1
2 DEF Q3
3 GEH Q3
Table 2: Tag Table
Tag Id Tag Name
---------------------------------
4 Dream
5 Light
6 Recover
Table 3: Question Tag Bindings
BoundQuestion ID BoundTagId
---------------------------------
1 4
2 5
3 6
3 4
Desired Result Table (Question table with added Tags column)
ID Content CategoryName Tags
----------------------------------------
1 ABC Q1 Dream
2 DEF Q3 Light
3 GEH Q3 Recover, Light
Thanks to anybody who looks at this, hope you're all staying safe.
You could join the three tables and aggregate to generate tag list. I guess that a lateral join should also be an efficient option here, since it avoids outer aggregation:
select q.*, t.*
from questions q
outer apply(
select string_agg(tag_name, ', ') tags
from questionTags qt
inner join tags t on t.TagID = qt.BoundTagID
where qt.BoundQuestionID = q.ID
) t
Note that string_agg() was added in SQL Server 2017.
In earlier versions, we can resort the for xml path solution:
select
q.*,
stuff(
(
', ' + tag_name tags
from questionTags qt
inner join tags t on t.TagID = qt.BoundTagID
where qt.BoundQuestionID = q.ID
order by tag_name
for xml path('')
),
1, 2, ''
) tags
from questions q

SQL find similar content

I have table ITEMS and column URL. All I need is in items.url to find similar rows:
Example of two similar rows:
ITEM_ID | URL
1 | www.google.com/test1/test2/test3.php
2 | www.yahoo.com/test1/test2/test3.php
3 | www.google.com/test5.php
4 | www.facebook.com/test5.php
As you can see the URL is similar JUST with different domains.
My query should be something like:
SELECT * FROM ITEMS
WHERE URL LIKE `%google.com%`...
AND `here code probably` ???
My query should return me ITEM_ID 2 and 4
You could group by the substring starting from the '/' character, and take the max ID in the group. Using postgresql syntax, it should look like this:
SELECT *
FROM ITEMS t
WHERE t.item_id IN (SELECT MAX(s.item_d)
FROM ITEMS s
GROUP BY SUBSTRING(s.url FROM POSITION('/' IN s.url)))
ORDER BY t.item_id;
Update: if you want only google domains, which have similar rows on different domains, you could use a filter EXISTS:
SELECT *
FROM ITEMS t
WHERE t.url LIKE 'www.google.com%'
AND EXISTS (SELECT 1
FROM ITEMS s
WHERE s.url NOT LIKE 'www.google.com%'
AND SUBSTRING(t.url FROM POSITION('/' IN t.url)) =
SUBSTRING(s.url FROM POSITION('/' IN s.url)));

Searching for a number in a database column where column contains series of numbers seperated by a delimeter '"&" in SQLite

My table structure is as follows :
id category
1 1&2&3
2 18&2&1
3 11
4 1&11
5 3&1
6 1
My Question: I need a sql query which generates the result set as follows when the user searched category is 1
id category
1 1&2&3
2 18&2&1
4 1&11
5 3&1
6 1
but i am getting all the results not the expected one
I have tried regexp and like operators but no success.
select * from mytable where category like '%1%'
select * from mytable where category regexp '([.]*)(1)(.*)'
I really dont know about regexp I just found it.
so please help me out.
For matching a list item separated by &, use:
SELECT * FROM mytable WHERE '&'||category||'&' LIKE '%&1&%';
this will match entire item (ie, only 1, not 11, ...), whether it is at list beginning, middle or end.

How can I get the number of occurrences in a SQL IN clause?

Let's say I have four tables: PAGE, USER, TAG, and PAGE-TAG:
Table | Fields
------------------------------------------
PAGE | ID, CONTENT
TAG | ID, NAME
USER | ID, NAME
PAGE-TAG | ID, PAGE-ID, TAG-ID, USER-ID
And let's say I have four pages:
PAGE#1 'Content page 1' tagged with tag#1 by user1, tagged with tag#1 by user2
PAGE#2 'Content page 2' tagged with tag#3 by user2, tagged by tag#1 by user2, tagged by tag#8 by user1
PAGE#3 'Content page 3' tagged with tag#7 by user#1
PAGE#4 'Content page 4' tagged with tag#1 by user1, tagged with tag#8 by user1
I expect my query to look something like this:
select page.content ?
from page, page-tag
where
page.id = page-tag.pag-id
and page-tag.tag-id in (1, 3, 8)
order by ? desc
I would like to get output like this:
Content page 2, 3
Content page 4, 2
Content page 1, 1
Quoting Neall
Your question is a bit confusing. Do you want to get the number of times each page has been tagged?
No
The number of times each page has gotten each tag?
No
The number of unique users that have tagged a page?
No
The number of unique users that have tagged each page with each tag?
No
I want to know how many of the passed tags appear in a particular page, not just if any of the tags appear.
SQL IN works like an boolean operator OR. If a page was tagged with any value within the IN Clause then it returns true. I would like to know how many of the values inside of the IN clause return true.
Below i show, the output i expect:
page 1 | in (1,2) -> 1
page 1 | in (1,2,3) -> 1
page 1 | in (1) -> 1
page 1 | in (1,3,8) -> 1
page 2 | in (1,2) -> 1
page 2 | in (1,2,3) -> 2
page 2 | in (1) -> 1
page 2 | in (1,3,8) -> 3
page 4 | in (1,2,3) -> 1
page 4 | in (1,2,3) -> 1
page 4 | in (1) -> 1
page 4 | in (1,3,8) -> 2
This will be the content of the page-tag table i mentioned before:
id page-id tag-id user-id
1 1 1 1
2 1 1 2
3 2 3 2
4 2 1 2
5 2 8 1
6 3 7 1
7 4 1 1
8 4 8 1
#Kristof does not exactly what i am searching for but thanks anyway.
#Daren If i execute you code i get the next error:
#1054 - Unknown column 'page-tag.tag-id' in 'having clause'
#Eduardo Molteni Your answer does not give the output in the question but:
Content page 2 8
Content page 4 8
content page 2 3
content page 1 1
content page 1 1
content page 2 1
cotnent page 4 1
#Keith I am using plain SQL not T-SQL and i am not familiar with T-SQL, so i do not know how your query translate to plain SQL.
Any more ideas?
This might work:
select page.content, count(page-tag.tag-id) as tagcount
from page inner join page-tag on page-tag.page-id = page.id
group by page.content
having page-tag.tag-id in (1, 3, 8)
OK, so the key difference between this and kristof's answer is that you only want a count of 1 to show against page 1, because it has been tagged only with one tag from the set (even though two separate users both tagged it).
I would suggest this:
SELECT page.ID, page.content, count(*) AS uniquetags
FROM
( SELECT DISTINCT page.content, page.ID, page-tag.tag-id
FROM page INNER JOIN page-tag ON page.ID=page-tag.page-ID
WHERE page-tag.tag-id IN (1, 3, 8)
)
GROUP BY page.ID
I don't have a SQL Server installation to check this, so apologies if there's a syntax mistake. But semantically I think this is what you need.
This may not give the output in descending order of number of tags, but try adding:
ORDER BY uniquetags DESC
at the end. My uncertainty is whether you can use ORDER BY outside of grouping in SQL Server. If not, then you may need to nest the whole thing in another SELECT.
In T-Sql:
select count(distinct name)
from page-tag
where tag-id in (1, 3, 8)
This will give you a count of the number of different tag names for your list of ids
Agree with Neall, bit confusing the question.
If you want the output listed in the question, the sql is as simple as:
select page.content, page-tag.tag-id
from page, page-tag
where page.id = page-tag.pag-id
and page-tag.tag-id in (1, 3, 8)
order by page-tag.tag-id desc
But if you want the tagcount, Daren answered your question
select
page.content,
count(pageTag.tagID) as tagCount
from
page
inner join pageTag on page.ID = pageTag.pageID
where
pageTag.tagID in (1, 3, 8)
group by
page.content
order by
tagCount desc
That gives you the number of tags per each page; ordered by the higher number of tags
I hope I understood your question correctly
Leigh Caldwell answer is correct, thanks man, but need to add an alias at least in MySQL. So the query will look like:
SELECT page.ID, page.content, count(*) AS uniquetags FROM
( SELECT DISTINCT page.content, page.ID, page-tag.tag-id FROM page INNER JOIN page-tag ON page.ID=page-tag.page-ID WHERE page-tag.tag-id IN (1, 3, 8) ) as page
GROUP BY page.ID
order by uniquetags desc