Get data from other table based on column with concatenated values - sql

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.

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);

How to join concatenated values from same table in 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

Joining tables on multiple conditions

I have a little problem - since im not very experienced in SQL - about joining the same table on multiple values. Imagine there is table 1 (called Strings):
id value
1 value1
2 value2
and then there is table 2 (called Maps):
id name description
1 1 2
so name is reference into the Strings table, as is description. Without the second field referencing the Strings table it would be no problem, id just do an inner join on Strings.id = Maps.name. But now id like to obtain the actual string also for description. What would be the best approach for a SELECT that returns me both? Right now it looks like this:
SELECT Maps.id, Strings.value AS mapName FROM Maps INNER JOIN Strings ON Strings.id = Maps.name;
But that obviously only covers one of the localized names. Thank you in advance.
You can do this with two joins to the same table:
SELECT m.id, sname.value AS mapName, sdesc.value as description
FROM Maps m INNER JOIN
Strings sname
ON sname.id = m.name INNER JOIN
Strings desc
ON sdesc.id = m.description;
Note the use of table aliases to distinguish between the two tables.
As long as you want to get a single value from another table, you can use subqueries to do these lookups:
SELECT id,
(SELECT value FROM Strings WHERE id = Maps.name) AS name,
(SELECT value FROM Strings WHERE id = Maps.description) AS description
FROM Maps

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.

What the simplest way to sub-query a variable number of rows into fields of the parent query?

What the simplest way to sub-query a variable number of rows into fields of the parent query?
PeopleTBL
NameID int - unique
Name varchar
Data: 1,joe
2,frank
3,sam
HobbyTBL
HobbyID int - unique
HobbyName varchar
Data: 1,skiing
2,swimming
HobbiesTBL
NameID int
HobbyID int
Data: 1,1
2,1
2,2
The app defines 0-2 Hobbies per NameID.
What the simplest way to query the Hobbies into fields retrieved with "Select * from PeopleTBL"
Result desired based on above data:
NameID Name Hobby1 Hobby2
1 joe skiing
2 frank skiing swimming
3 sam
I'm not sure if I understand correctly, but if you want to fetch all the hobbies for a person in one row, the following query might be useful (MySQL):
SELECT NameID, Name, GROUP_CONCAT(HobbyName) AS Hobbies
FROM PeopleTBL
JOIN HobbiesTBL USING NameID
JOIN HobbyTBL USING HobbyID
Hobbies column will contain all hobbies of a person separated by ,.
See documentation for GROUP_CONCAT for details.
I don't know what engine are you using, so I've provided an example with MySQL (I don't know what other sql engines support this).
Select P.NameId, P.Name
, Min( Case When H2.HobbyId = 1 Then H.HobbyName End ) As Hobby1
, Min( Case When H2.HobbyId = 2 Then H.HobbyName End ) As Hobby2
From HobbyTbl As H
Join HobbiesTbl As H2
On H2.HobbyId = H.HobbyId
Join PeopleTbl As P
On P.NameId = H2.NameId
Group By P.NameId, P.Name
What you are seeking is called a crosstab query. As long as the columns are static, you can use the above solution. However, if you want to dynamic build the columns, you need to build the SQL statement in middle-tier code or use a reporting tool.