sql concat within a SELECT statement - sql

This is similiar to this one. How to concatenate all columns in a select with SQL Server
But not quite. This is MS SQL 2008. I am pulling patient demographics, and one of those is race, which is a multi-choice field (you could be asian and chinese for example). The race table 'PatientRace' is linked to the patient table by patientid. So the table structure is thus:
Patient table
PatientID
PatientName
PatientAddress
PatientRace table
PatientRaceID
PatientID (FK)
Description
I only want one row, and I want race to be concatenated. Is it possible to do this within a single SELECT statement or do I need to do a cursor? I am envisioning the cursor to be like this: Initial select for all the other demographics, insert into a temp table. Go through the temp table by patientID, then for each, grab out the race, concat, and add to the temp table.
The desired output is like this: 1 row per patient.
Name: "Joe Blow"
Race: "Asian, Chinese"

You need to use STUFF and FOR XML like this
SELECT p.PatientName,
(STUFF(SELECT ',' + r.Description
FROM PatientRace r
WHERE r.PatientID = p.PatientID
FOR XML('')), 1, 1, '')
FROM Patients p

Concatenating string values in SQL Server is not obvious. It requires using "xml" data processing and a subquery:
select p.*,
stuff((select ', ' + Description
from patientrace pr
where pr.patientid = p.patientid
for xml path ('')
), 1, 2, ''
) as races
from patients p;

As for me, you have write function like fn_GetRaceByID(int PatientID), that returns desired string. So use it in your select. Link in your question has good example, how to do this.

Related

How to execute a select with a WHERE using a not-always-existing column

Simple example: I have some (nearly) identical tables with personal data (age, name, weight, ...)
Now I have a simple, but long SELECT to find missing data:
Select ID
from personal_data_a
where
born is null
or age < 1
or weight > 500
or (name is 'John' and surname is 'Doe')
Now the problem is:
I have some personal_data tables where the column "surname" does not exit, but I want to use the same SQL-statement for all of them. So I have to check (inside the WHERE clause) that the last OR-condition is only used "IF the column surname exists".
Can it be done in a simple way?
You should have all people in the same table.
If you can't do that for some reason, consider creating a view. Something like this:
CREATE OR REPLACE VIEW v_personal_data
AS
SELECT id,
born,
name,
surname,
age,
weight
FROM personal_data_a
UNION ALL
SELECT id,
born,
name,
NULL AS surname, --> this table doesn't contain surname
age,
weight
FROM personal_data_b;
and then
SELECT id
FROM v_personal_data
WHERE born IS NULL
OR age < 1
OR ( name = 'John'
AND ( surname = 'Doe'
OR surname IS NULL))
Can it be done in a simple way?
No, SQL statements work with static columns and the statements will raise an exception if you try to refer to a column that does not exist.
You will either:
need to have a different query for tables with the surname column and those without;
have to check in the data dictionary whether the table has the column or not and then use dynamic SQL to build your query; or
to build a VIEW of the tables which do not have that column and add the column to the view (or add a GENERATED surname column with a NULL value to the tables that are missing it) and use that instead.
While dynamic predicates are usually best handled by the application or by custom PL/SQL objects that use dynamic SQL, you can solve this problem with a single SQL statement using DBMS_XMLGEN, XMLTABLE, and the data dictionary. The following code is not what I would call "simple", but it is simple in the sense that it does not require any schema changes.
--Get the ID column from a PERSONAL table.
--
--#4: Get the IDs from the XMLType.
select id
from
(
--#3: Convert the XML to an XMLType.
select xmltype(personal_xml) personal_xmltype
from
(
--#2: Convert the SQL to XML.
select dbms_xmlgen.getxml(v_sql) personal_xml
from
(
--#1: Use data dictionary to create SQL statement that may or may not include
-- the surname predicate.
select max(replace(replace(
q'[
Select ID
from #TABLE_NAME#
where
born is null
or age < 1
or weight > 500
or (name = 'John' #OPTIONAL_SURNAME_PREDICATE#)
]'
, '#TABLE_NAME#', table_name)
, '#OPTIONAL_SURNAME_PREDICATE#', case when column_name = 'SURNAME' then
'and surname = ''Doe''' else null end)) v_sql
from all_tab_columns
--Change this literal to the desired table.
where table_name = 'PERSONAL_DATA_A'
)
)
where personal_xml is not null
)
cross join xmltable
(
'/ROWSET/ROW'
passing personal_xmltype
columns
id number path 'ID'
);
See this db<>fiddle for a runnable example.

Combine Multiple Rows Into A Single Column

I am working on a stored procedure for reporting purposes and I need to combine rows in a single column but I can't find a way to do this. I have four tables, Case, Debtor, DebtorAddress and CaseNote. Rather than try to do this in a single statement I decided to create a temp table with the columns needed, populate the ones that occupy a single row, then use an update statement to combine the multiple rows of the last column needed into a single row. My temp table has rows for CApKey (the ID of the Case table), OwnerName (from Debtor), Address (from DebtorAddress, and Note. For each Case there may be multiple Notes (stored in the CaseNote table). So I may have Case #1, with a CApKey value of 1, OwerName of John Jones, Address of 1234 Main St. There may be one Note the says 'Called and left message', another that says 'Sent letter', and another that says 'Left a second voicemail', etc. I'd like to combine the three notes into a single row with Note values of Called and left a message, Sent Letter, and Left a second voicemail. I can use space, period, or comma as a delimiter. I found a way to do the update in theory but I'm getting an error that the sub-query returned more than 1 value. Below is the "heart" of the procedure. I've been wracking my brain on this for two days now. Any assistance is greatly appreciated in advance. Here is the statement I'm trying:
CREATE TABLE #temp
(
CaseKey int,
OwnerName varchar(500),
Address varchar(500),
Note varchar(MAX)
)
DECLARE #val Varchar(MAX);
INSERT INTO #temp
(CaseKey, OwnerName, Address)
SELECT ca.CApKey, DEFirstName + ' ' + DELastName, da.DAAddress1
FROM [Case] ca INNER JOIN Debtor de ON ca.CApKey = de.CApKey INNER JOIN DebtorAddress da ON ca.CApKey = da.CApKey
WHERE ca.LFpKey = #LFpKey AND de.DEIsPrimary = 1
UPDATE #temp SET Note =
(SELECT COALESCE(#val + ', ' + CANNote, CANNote)
FROM CaseNote WHERE CApKey = 51)
--SELECT #val;)
SELECT * FROM #temp
Thanks!
If i understood you correctly, you need to combine all the notes.
Get your data, queried from the 4 tables into a table (ex. #t)
Then you can use XML PATH and Stuff to achieve your goal (later you can use that for whatever your purpose as inserting to a table or display in a report etc)
SELECT CaseKey, OwnerName, Address, (STUFF((
SELECT ', ' + Note
FROM #t tx
where tx.CaseKey = t.CaseKey and tx.OwnerName = t.OwnerName and tx.Address = t.Address
FOR XML PATH('')
), 1, 2, '')
) AS StringValue
From #t t
Group by CaseKey, OwnerName, Address
Here is the fiddle

TSQL: Find part of string based on a column in temp table

I need to extract the school name from a file name string. I put all of the schools in a temp table and I need to use the temp table column of SchoolName to search the string. This filename is inherited and should be a one time event to try to extract data from.
The SchoolCode and SchoolName are in the temp table. If the SchoolName matches what's in the temp table, I would like the SchoolCode as well.
I tried doing a subquery in the WHERE clause but I get an error stating it returned more than one result.
Current data:
Desired Result:
Test Code:
DROP TABLE #SchoolName
GO
CREATE TABLE #SchoolName (
FILENAME VARCHAR(MAX)
)
INSERT INTO #SchoolName
( FILENAME )
VALUES
('New Student added 10-02-16\High School\2015\North Side HS\JONES, JIMMY E_07-29-1993_2314687.PDF')
,('2006\South Side HIGH SCHOOL\GRADUATE\Johnson Jack B,03-19-1989,7123444.PDF')
,('2010\Riverwood\GRADUATES\Harmon, Kardon C_07-21-1991_370143.PDF')
,('2009\RockCreek\GRADUATES\Vandolay, Art E_09-23-1974_7210122.PDF')
DROP TABLE #SchoolsList
CREATE TABLE #SchoolsList (
SchoolCode VARCHAR(4)
,School varchar(500)
)
INSERT INTO #SchoolsList
(
SchoolCode
, School
)
VALUES
(1111 ,'North Side '),(1112 ,'South Side '),(1113 ,'Riverwood '),(1114 ,'RockCreek')
You can join to your SchoolList table using a like (assuming your school list is actually a distinct list)
SELECT l.SchoolCode, n.*
FROM #SchoolName n
LEFT OUTER JOIN #SchoolsList l
ON n.FILENAME LIKE '%' + l.School + '%'
Riverwood doesn't return a code since you have a space in the school list (SS and NS work since there is actually a space after them in both the filename and the school list)
I think you're looking for
SELECT SL.*
FROM SchoolsList SL JOIN SchoolName SN
ON SN.FileName LIKE CONCAT('%\', RTRIM(LTRIM(SL.School)), '%');
-- You can remove LTRIM() if there is no spaces in the begining
Demo

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

Return multiple values in one column within a main query

I am trying to find Relative information from a table and return those results (along with other unrelated results) in one row as part of a larger query.
I already tried using this example, modified for my data.
How to return multiple values in one column (T-SQL)?
But I cannot get it to work. It will not pull any data (I am sure it is is user[me] error).
If I query the table directly using a TempTable, I can get the results correctly.
DECLARE #res NVARCHAR(100)
SET #res = ''
CREATE TABLE #tempResult ( item nvarchar(100) )
INSERT INTO #tempResult
SELECT Relation AS item
FROM tblNextOfKin
WHERE ID ='xxx' AND Address ='yyy'
ORDER BY Relation
SELECT #res = #res + item + ', ' from #tempResult
SELECT substring(#res,1,len(#res)-1) as Result
DROP TABLE #tempResult
Note the WHERE line above, xxx and yyy would vary based on the input criteria for the function. but since you cannot use TempTables in a function... I am stuck.
The relevant fields in the table I am trying to query are as follows.
tblNextOfKin
ID - varchar(12)
Name - varchar(60)
Relation - varchar(30)
Address - varchar(100)
I hope this makes enough sense... I saw on another post an expression that fits.
My SQL-fu is not so good.
Once I get a working function, I will place it into the main query for the SSIS package I am working on which is pulling data from many other tables.
I can provide more details if needed, but the site said to keep it simple, and I tried to do so.
Thanks !!!
Follow-up (because when I added a comment to the reponse below, I could not edit formatting)
I need to be able to get results from different columns.
ID Name Relation Address
1, Mike, SON, 100 Main St.
1, Sara, DAU, 100 Main St.
2, Tim , SON, 123 South St.
Both the first two people live at the same address, so if I query for ID='1' and Address='100 Main St.' I need the results to look something like...
"DAU, SON"
Mysql has GROUP_CONCAT
SELECT GROUP_CONCAT(Relation ORDER BY Relation SEPARATOR ', ') AS item
FROM tblNextOfKin
WHERE ID ='xxx' AND Address ='yyy'
You can do it for the whole table with
SELECT ID, Address, GROUP_CONCAT(Relation ORDER BY Relation SEPARATOR ', ') AS item
FROM tblNextOfKin
GROUP BY ID, Address
(assuming ID is not unique)
note: this is usually bad practice as an intermediate step, this is acceptable only as final formatting for presentation (otherwise you will end up ungrouping it which will be pain)
I think you need something like this (SQL Server):
SELECT stuff((select ',' +Relation
FROM tblNextOfKin a
WHERE ID ='xxx' AND Address ='yyy'
ORDER BY Relation
FOR XML path('')),1,1,'') AS res;