How to join concatenated values from same table in SQL - sql

I have two tables:
Table entries-
ID Entry Tags
1 ABC 0001,0002
2 DEF 0002,0003
table tags-
ID Tag
0001 Tag1
0002 Tag2
0003 Tag3
Is there a way to write a query that returns something like
ID Entry Tags
1 ABC Tag1,Tag2
2 DEF Tag2,Tag3
I been searching the web for a while now but without success. I'm not sure what to look for.

Do the follwoing steps to generate expected output:-
STEP 1-- Create Temp Table
CREATE TABLE #TEMP (ID int, entry varchar(50),tags varchar(50))
STEP 2-- Insert into temp table
INSERT INTO #TEMP
select entries.id,entries.entry,tags.tags
from entries inner join tags
on ',' + entries.tags + ',' like '%,' + tags.id + ',%'
Step 3 -
Select distinct T2.id, T2.entry,
substring(
(
Select ','+T1.tags AS [text()]
From dbo.#TEMP T1
Where T1.id =T2.id
ORDER BY T1.id
For XML PATH ('')
), 2, 1000) tags
From dbo.#TEMP T2
Suggestion:- Your database design violates the first normal form of database design, and it must be changed. One column should not contains the ids as comma separated values. There will be severe performance problems and querying will always be difficult.
The first normal form (or 1NF) requires that the values in each column of a table are atomic. By atomic we mean that there are no sets of values within a column.
Refer Here

Seems like you're looking for some sort of a cross apply. However it's not really a good idea to have comma separated values within a single column in a table. You'd be better off having an entry table, a tag table and an entry_tag table that would look like
Entry_ID Tag_ID
1 0001
1 0002
2 0002
2 0003
And then you join through that table to get the names of the tags. This keeps you from breaking the first normal form and allows you to write queries much cleaner.
Hope that helps.

You have a fundamental flaw in your schema design and, if it's not too late, it should be corrected. You have a many-to-many relationship between Entrys and Tags that should be resolved with a junction table.

First you need to fix your relational model, as it is incorrect.
You need 3 entities to represent the relationship many to many, as follows: Entry, Tag, Entry_Tag.
In Oracle database exists a grouping function calls "LISTAGG" to concatenate many results in a single row, so we can group the result of tags for each Entry record.
If you do not use Oracle, need to consult the documentation for your database to find out what is the function that can concatenate the results into a single row.
Finally, a final query should look like:
SELECT E.ID, E.NAME, (LISTAGG(T.NAME, ', ') WITHIN GROUP (ORDER BY T.NAME)) Tags
FROM ENTRY E
INNER JOIN ENTRY_TAG ET ON ET.ID_ENTRY = E.ID
INNER JOIN TAG T ON T.ID = ET.ID_TAG
GROUP BY E.ID, E.NAME

Related

Search using LIKE operator with multiple dynamic values accepting both full and partial text match

Is there any way to do multiple term search in a column using like operator dynamically in SQL Server? Like below
SELECT ID
FROM table
WHERE
Company LIKE '%goog%' OR
Company LIKE '%micros%' OR
Company LIKE '%amazon%'
For example: input values "goog; micro; amazon;" (input value should auto split by delimiter ';' and check the text exist in the table) means that Search term 'goog' or 'micros' or 'amazon' from company column, if exists return.
Table - sample data:
ID Company
------------------------------------------
1 Google; Microsoft;
2 oracle; microsoft; apple; walmart; tesla
3 amazon; apple;
4 google;
5 tesla;
6 amazon;
Basically, The above query should return the results as like below,
Desired results:
ID
-----
1
2
4
6
Is it possible to achieve in SQL Server by splitting, then search in query? I look forward to an experts answer.
If you pass in a table valued parameter, you can join on that.
So for example
CREATE TYPE StringList AS TABLE (str varchar(100));
DECLARE #tmp StringList;
INSERT #tmp (str)
VALUES
('%goog%'),
('%micros%'),
('%amazon%');
SELECT t.ID
FROM table t
WHERE EXISTS (SELECT 1
FROM #tmp tmp
WHERE t.Company LIKE tmp.str);
The one issue with this is that someone could write le; Mic and still get a result.
Strictly speaking, your table design is flawed, because you are storing multiple different items in the same column. You really should have this normalized into rows, so every Company is a separate row. Then your code would look like this:
SELECT t.ID
FROM table t
JOIN #tmp tmp ON t.Company LIKE tmp.str
GROUP BY t.ID
You can simulate it by splitting your string
SELECT t.ID
FROM table t
WHERE EXISTS (SELECT 1
FROM STRING_SPLIT(t.Company) s
JOIN #tmp tmp ON s.value LIKE tmp.str);

Get data from other table based on column with concatenated values

I have two tables:
category with columns:
id name
1 business
2 sports
...
article with columns:
id title categories
1 abc 1|2|3
2 xyz 1|2
I know there should be a separate table for article categories but I was given this.
Is it possible to write a query that returns:
id title category_names
1 xyz business,sports
I thought of splitting the string in article -> categories column, then use in query to extract name from category table but couldn't figure it out.
You should fix your data model. But, you can do this in SQL Server:
select a.*, s.names
from article a cross apply
(select string_agg(c.name, ',') as names
from string_split(a.categories, '|') ss join
category c
on try_convert(int, ss.value) = c.id
) s;
Here is a db<>fiddle.
Presumably, you already know the shortcomings of this data model:
SQL Server has poor string handling functionality.
Numbers should be stored as numbers not strings.
Foreign key references should be properly declared.
Such queries cannot make use of indexes and partitions.
If you really want to store multiple values in a field, SQL Server offers both JSON and XML. Strings are not the right approach.

SQL Server where condition on column with separated values

I have a table with a column that can have values separated by ",".
Example column group:
id column group:
1 10,20,30
2 280
3 20
I want to create a SELECT with where condition on column group where I can search for example 20 ad It should return 1 and 3 rows or search by 20,280 and it should return 1 and 2 rows.
Can you help me please?
As pointed out in comments,storing mutiple values in a single row is not a good idea..
coming to your question,you can use one of the split string functions from here to split comma separated values into a table and then query them..
create table #temp
(
id int,
columnss varchar(100)
)
insert into #temp
values
(1,'10,20,30'),
(2, '280'),
(3, '20')
select *
from #temp
cross apply
(
select * from dbo.SplitStrings_Numbers(columnss,',')
)b
where item in (20)
id columnss Item
1 10,20,30 20
3 20 20
The short answer is: don't do it.
Instead normalize your tables to at least 3NF. If you don't know what database normalization is, you need to do some reading.
If you absolutely have to do it (e.g. this is a legacy system and you cannot change the table structure), there are several articles on string splitting with TSQL and at least a couple that have done extensive benchmarks on various methods available (e.g. see: http://sqlperformance.com/2012/07/t-sql-queries/split-strings)
Since you only want to search, you don't really need to split the strings, so you can write something like:
SELECT id, list
FROM t
WHERE ','+list+',' LIKE '%,'+#searchValue+',%'
Where t(id int, list varchar(max)) is the table to search and #searchValue is the value you are looking for. If you need to search for more than one value you have to add those in a table and use a join or subquery.
E.g. if s(searchValue varchar(max)) is the table of values to search then:
SELECT distinct t.id, t.list
FROM t INNER JOIN s
ON ','+t.list+',' LIKE '%,'+s.searchValue+',%'
If you need to pass those search values from ADO.Net consider table parameters.

How to show one to many relationship between two tables in SQL?

I have two tables A and B.
A table contain
postid,postname,CategoryURl
and
B table contain
postid,CategoryImageURL
For one postid there are multiple CategoryImageURL assigned.I want to display that CategoryImageURL in Table A but for one postid there should be CategoryImageURL1,CategoryImageURL2 should be like that one.
I want to achieve one to many relationship for one postid then what logic should be return in sql function??
In my eyes it seems that you want to display all related CategoryImageURLs of the second table in one line with a separator in this case the comma?
Then you will need a recursive operation there. Maybe a CTE (Common Table Expression) does the trick. See below. I have added another key to the second table, to be able to check, if all rows of the second table have been processed for the corresponding row in the first table.
Maybe this helps:
with a_cte (post_id, url_id, name, list, rrank) as
(
select
a.post_id
, b.url_id
, a.name
, cast(b.urln + ', ' as nvarchar(100)) as list
, 0 as rrank
from
dbo.a
join dbo.b
on a.post_id = b.post_id
union all
select
c.post_id
, a1.url_id
, c.name
, cast(c.list + case when rrank = 0 then '' else ', ' end + a1.urln as nvarchar(100))
, c.rrank + 1
from a_cte c
join ( select
b.post_id
, b.url_id
, a.name
, b.urln
from dbo.a
join dbo.b
on a.post_id = b.post_id
) a1
on c.post_id = a1.post_id
and c.url_id < a1.url_id -- ==> take care, that there is no endless loop
)
select d.name, d.list
from
(
select name, list, rank() over (partition by post_id order by rrank desc)
from a_cte
) d (name, list, rank)
where rank = 1
You are asking the wrong sort of question. This is about normalization.
As it stands, you have a redundancy? Where each postname and categoryURL is represented by an ID field.
For whatever reason, the tables separated CategoryImageUrl into its own table and linked this to each set of postname and categoryURL.
If the relation is actually one id to each postname, then you can denormalize the table by adding the column CategoryImageUrl to your first table.
Postid, postname, CategoryURL, CategoryImageUrl
Or if you wish to keep the normalization, combine like fields into their own table like so:
--TableA:
Postid, postname, <any other field dependent on postname >
--TableA
Postid, CategoryURL, CategoryImageUrl
Now this groups CategoryURL together but uses a redundancy of having multiple CategoryURL to exist. However, Postid has only one CategoryUrl.
To remove this redundancy in our table, we could use a Star Schema strategy like this:
-- Post Table
Postid, postname
-- Category table
CategoryID, CategoryURL, <any other info dependent only on CategoryURL>
-- Fact Table
Postid, CategoryID, CategoryImageURL
DISCLAIMER: Naturally I assumed aspects of your data and might be off. However, the strategy of normalization is still the same.
Also, remember that SQL is relational and deals with sets of data. Inheritance is incompatible to the relational set theory. Every table can be queried forwards and backwards much the way every page and chapter in a book is treated as part of the book. At no point would we see a chapter independent of a book.

Selecting distinct rows based on values from left table

Using Postgres. Here's my scenario:
I have three different tables. One is a title table. The second is a genre table. The third table is used to join the two. When I designed the database, I expected that each title would have one top level genre. After filling it with data, I discovered that there were titles that had two, sometimes, three top level genres.
I wrote a query that retrieves titles and their top level genres. This obviously requires that I join the two tables. For those that only have one top level genre, there is one record. For those that have more, there are multiple records.
I realize I'll probably have to write a custom function of some kind that will handle this for me, but I thought I'd ask if it's possible to do this without doing so just to make sure I'm not missing anything.
Is it possible to write a query that will allow me to select all of the distinct titles regardless of the number of genres that it has, but also include the genre? Or even better, a query that would give me a comma delimited string of genres when there are multiples?
Thanks in advance!
Sounds like a job for array_agg to me. With tables like this:
create table t (id int not null, title varchar not null);
create table g (id int not null, name varchar not null);
create table tg (t int not null, g int not null);
You could do something like this:
SELECT t.title, array_agg(g.name)
FROM t, tg, g
WHERE t.id = tg.t
AND tg.g = g.id
GROUP BY t.title, t.id
to get:
title | array_agg
-------+-----------------------
one | {g-one,g-two,g-three}
three | {g-three}
two | {g-two}
Then just unpack the arrays as needed. If for some reason you really want a comma delimited string instead of an array, then string_agg is your friend:
SELECT t.title, string_agg(g.name, ',')
FROM t, tg, g
WHERE t.id = tg.t
AND tg.g = g.id
GROUP BY t.title, t.id
and you'll get something like this:
title | string_agg
-------+---------------------
one | g-one,g-two,g-three
three | g-three
two | g-two
I'd go with the array approach so that you wouldn't have to worry about reserving a character for the delimiter or having to escape (and then unescape) the delimiter while aggregating.
Have a look at this thread which might answer your question.