Many to many relationship without duplicates - sql-server

I'm trying to solve a problem with many-to-many relation.
I have 3 tables:
Article
-------
ArticleID
FilePath
Title
Description
Author
--------
AuthorID
AuthorFName /* Father last name */
AuthorMName /* Mother last name */
AuthorName
ArticleAuthor
-------------
AuthorID
ArticleID
Foreign KEY (AuthorID) REFERENCES Author(AuthorID),
FOREIGN KEY (ArticleID) REFERENCES Article(ArticleID),
PRIMARY KEY (AuthorID, ArticleID)
The Author table has 2 authors:
ID FName MName Name
-- ----- ----- ----
1 XXX YYY AAA
2 MMM NNN BBB
The Article:
ID FilePath Title Description (optional)
-- -------- ----- ----------------------
1 /path/file '....' ''
And the ArticleAuthor
ArticleID AuthorID
--------- --------
1 1
1 2
The query I'm using now is:
SELECT DISTINCT ArticleAuthor.ArticleID,
STUFF(
(
SELECT ',' + CONCAT(AuthorFName, ' ', AuthorMName, ' ', AuthorName)
FROM FinderSchema.Author
WHERE Author.AuthorID = ArticleAuthor.AuthorID
FOR XML PATH(''), TYPE
).value('.', 'varchar(max)'), 1, 1, ''
) AS Authors
FROM FinderSchema.ArticleAuthor
LEFT JOIN FinderSchema.Author ON ArticleAuthor.AuthorID = Author.AuthorID
GROUP BY ArticleAuthor.ArticleID, ArticleAuthor.AuthorID
But it returns duplicates:
ArticleID Authors
--------- -------
1 XXX YYY AAA
1 MMM NNN BBB
So, is there a way to remove the duplicates? I have been reading about simulating GROUP_CONCAT (I'm targeting sql server 2008 RC 2), and that's the answer I've found (in stackoverflow, actually), but does not work for me (or many to many relationship, maybe).
Well, if someone can help me, I'll be grateful.
Thanks in advance :)

Try
SELECT ArticleID,
STUFF((SELECT DISTINCT ',' + a.AuthorFName + ' ' + a.AuthorMName + ' ' + a.AuthorName
FROM ArticleAuthor aa JOIN Author a
ON aa.AuthorID = a.AuthorID
WHERE aa.ArticleID = aa2.ArticleID
FOR XML PATH('')) , 1 , 1 , '' ) Authors
FROM ArticleAuthor aa2
GROUP BY ArticleID
Output:
| ARTICLEID | AUTHORS |
---------------------------------------
| 1 | MMM NNN BBB,XXX YYY AAA |
Here is SQLFiddle demo

Related

Concatenate Two Separate Columns of Text Per ID

I know it's possible to concatenate one row using the following code. For example making one row of StudentName text per subject ID.
Example Data
Patient_FIN StudentName
---------- -------------
1 Mary
1 John
2 Alaina
2 Edward
Expected Results
Patient_FIN StudentName
---------- -------------
1 Mary, John
2 Alaina, Edward
Code used to get the above output
SELECT DISTINCT ST2.pt_fin,
SUBSTRING(
(
SELECT ST1.StudentName + ',' AS [text()]
FROM ED_ORDERS_IMPORT_MASTER ST1
WHERE ST1.Patient_FIN = ST2.Patient_FIN
ORDER BY ST1.Patient_FIN
FOR XML PATH ('')
) , 2, 1000) [Patient]
FROM ED_ORDERS_IMPORT_MASTER ST2
However, let's say I have the same data and an additional row that I'd also like to concatenate into a separate column based on Patient_FIN:
Patient_FIN StudentName Color
---------- ------------- ----------
1 Mary Blue
1 John Red
2 Alaina Red
2 Edward White
Desired Results
Patient_FIN StudentName Color
---------- ------------- ----------
1 Mary, John Blue, Red
2 Alaina, Edward Red, White
How can I edit the above code to produce the desired results? Thanks!
You can use STUFF to get your desired output as below-
SELECT Patient_FIN,
STUFF
(
(
SELECT ',' + StudentName
FROM your_table
WHERE Patient_FIN = A.Patient_FIN
FOR XML PATH ('')), 1, 1, ''
) StudentName,
STUFF
(
(
SELECT ',' + Color
FROM your_table
WHERE Patient_FIN = A.Patient_FIN
FOR XML PATH ('')), 1, 1, ''
) Color
FROM your_table A
GROUP BY Patient_FIN
Output is-
Patient_FIN StudentName Color
1 Mary,John Blue,Red
2 Alaina,Edward Red,White

Select all rows in table separated by commas with condition

A customer enters a ticket and selects a department, or in some cases, multiple departments from the list. My application returns the departments separated by comma's for each Ticket ID. It works great. However, the user may have selected "ALL" departments from the selection (the DEPARTMENT_ID for the selection ALL is '1'). I would then need to return every department in the Departments table separated by commas.
Ticket Table
TICKET_ID | ISSUE
-------------------------
100 | Power Outage
101 | Internet is not working
Deartments Table:
DEPARTMENT_ID | DEPARTMENT
--------------------------
1 | ALL
2 | Accounting
3 | Human Resources
4 | Receiving
DepartmentTickets table
DEPARTMENT_TICKETS_ID | TICKET_ID | DEPARTMENT_ID
----------------------------------------------------------
1 | 100 | 2
2 | 100 | 3
3 | 101 | 1
Using my query, ticket 100 shows the following results:
Power Outage: Accounting, Human Resources
How can I make ticket 101 show the following:
Internet is not working: All, Accounting, Human Resources, Receiving
Select ISSUE,
stuff((
SELECT ', ' + cast(Departments.DEPARTMENT as varchar(max))
FROM Departments
left join DepartmentTickets
ON DepartmentTicket.TICKET_ID = TICKETS.TICKET_ID
WHERE DepartmentTickets.TICKET_ID = TICKETS.TICKET_ID and DepartmentTickets.DEPARTMENT_ID = Departments.DEPARTMENT_ID
FOR XML PATH('')
), 1, 2, '') AS DEPARTMENTS
FROM TICKETS
WHERE TICKET_ID = '100'
ORDER BY ISSUE
First make your inner join in disguise a proper inner join. In the ON clause then add with an OR a check for the department ID being 1.
And by the way, if the IDs are integers not strings, which they appear to be, you shouldn't enclose the literals in single quotes.
SELECT t.issue,
stuff((SELECT ', ' + cast(d.department AS varchar(max))
FROM departments d
INNER JOIN departmenttickets dt
ON dt.department_id = d.department_id
OR dt.department_id = 1
WHERE dt.ticket_id = t.ticket_id
FOR XML PATH('')),
1,
2,
'') departments
FROM tickets t
WHERE t.ticket_id = 101
ORDER BY t.issue;
db<>fiddle

SQL server - concat few column values in to one column on join

After joining multiple tables I have some results that differ only in one column. Is there a "easy" way to compact those differences to one row ?
For example, lets assume that after join I have something like this:
id | project | date | Oranges | Apples | Name
1 xaxa 1.1.2000 yes yes Tom
1 xaxa 1.1.2000 yes yes Bob
1 xaxa 1.1.2000 yes yes Jan
And I would like to have something like this:
id | project | date | Oranges | Apples | Name
1 xaxa 1.1.2000 yes yes Tom, Bob, Jan
Still begginer here, please be gentle :)
select id, project, date, Oranges, Apples
, Names = stuff((
select distinct ', '+i.Name
from t as i
where i.id = t.id
and i.project = t.project
and i.date = t.date
and i.Oranges = t.Oranges
and i.Apples = t.Apples
order by i.Name
for xml path (''), type).value('.','varchar(max)')
,1,2,'')
from t
group by id, project, date, Oranges, Apples

How to transform SQL Server row data to columns?

I queried data like this
member_no cover_version product_id product_name product_type
--------- ------------- ---------- -------------------- ------------
11421 7 4 Excellent More E
11421 7 15 Comprehensive Data D
But I want to shape this data like this:
member_no cover_version product_e_id product_e_name product_d_id product_d_name
--------- ------------- ------------ -------------------- ------------ --------------------
11421 7 4 Excellent More 15 Comprehensive Data
I am using SQL Server 2008. What should be the best approach to shape the data as I want?
Assuming you've only got product types D and E as stated, a simple self-join will get you what you're after.
If you want something more generic, please expand your question.
DROP TABLE IF EXISTS #Demo
SELECT
*
INTO
#Demo
FROM
(VALUES
(11421, 7, 4, 'Excellent More', 'E')
,(11421, 7, 15, 'Comprehensive Data', 'D')) A
(member_no, cover_version, product_id, product_name, product_type)
SELECT
D.member_no
,D.cover_version
,E.product_id product_e_id
,E.product_name product_e_name
,D.product_id product_d_id
,D.product_name product_d_name
FROM
#Demo D
JOIN #Demo E ON D.member_no = E.member_no
AND D.product_type = 'D'
AND E.product_type = 'E';
member_no cover_version product_e_id product_e_name product_d_id product_d_name
----------- ------------- ------------ ------------------ ------------ ------------------
11421 7 4 Excellent More 15 Comprehensive Data
If you want to do this dynamically, based on unknown product types, you can use this.
DECLARE #SQL NVARCHAR(MAX),
#Columns NVARCHAR(MAX),
#CaseExpressions NVARCHAR(MAX) = 'MAX(CASE WHEN product_type = ''<<productType>>'' THEN product_id END) AS [product_<<productType>>_id],
MAX(CASE WHEN product_type = ''<<productType>>'' THEN product_name END) AS [product_<<productType>>_name]'
-- build concatenated string with 2 columns for each product_type in your table.
-- group by product_type to get distinct results.
-- replaces <<productType>> with the actual product_type value
SELECT #Columns = COALESCE(#Columns + ',', '') + REPLACE(#CaseExpressions, '<<productType>>', product_type)
FROM myTable
GROUP BY product_type
-- build select query
SET #SQL = 'SELECT member_no, cover_version,' + #Columns + ' FROM myTable GROUP BY member_no, cover_version'
-- to see what the dynamic sql looks like
PRINT #SQL
-- to execute the dynamic sql and see result
EXEC (#SQL)

How to retrieve multiple detail records plus the master record as a single row?

I have two tables with a structure similar to this:
Person:
ID Name Age
1 Jack 25
2 Jill 23
Tests:
ID PersonID TestID Result
1 1 1 125
2 1 2 120
3 1 3 75
4 2 1 90
5 2 2 95
6 2 3 7.2
Is there a way to retrieve that data with a single statement in a way that each record in the master table is presented in a single row? Something like this:
PersonID Name Age Test1 Test2 Test3
1 Jack 25 125 120 75
2 Jill 23 90 95 7.2
So far, the only way I have come up with has been to create a function which iterates through the detail records and fills a temporary table. Not very elegant.
Thanks in advance
In order to get this result, you will need to use the PIVOT function. This transforms the data from multiple rows into columns.
If you know the values ahead of time or you will have a limited number of TestId values, then you can hard-code the query making the query static.
SELECT Name,
Age,
[1] AS Test1,
[2] AS Test2,
[3] AS Test3
FROM
(
SELECT P.Name, P.Age, t.TestID, t.Result
FROM tests t
INNER JOIN person P
ON p.ID = t.PersonID
) T
PIVOT
(
sum(Result)
FOR TestID IN ([1], [2], [3])
) piv;
See SQL Fiddle with Demo.
But if you have an unknown number of TestId values, then you will want to use dynamic SQL to generate the list of columns at run-time. You Code will be:
DECLARE #cols AS NVARCHAR(MAX),
#colNames AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(testId)
from tests
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #colNames = STUFF((SELECT distinct ',' + QUOTENAME(testId) +' as Test'+cast(testId as varchar(10))
from tests
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT Name, age, ' + #colnames + ' from
(
select P.Name, P.Age, t.TestID, t.Result
from tests t
inner join person P
on p.ID = t.PersonID
) x
pivot
(
sum(Result)
for TestID in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo.
They both will generate the same result, difference being that the dynamic one will increase/decrease the columns if the number of test ids changes:
| NAME | AGE | TEST1 | TEST2 | TEST3 |
--------------------------------------
| Jack | 25 | 125 | 120 | 75 |
| Jill | 23 | 90 | 95 | 7.2 |
You can do a pivot on the TestID
Here you go... its kinda messy but you can improve on it :)
SELECT Name,Age,SUM([1]) AS Test1,SUM([2]) AS Test2,SUM([3]) AS Test3
FROM(
SELECT P.Name,P.Age,Te.ID, TestID,Result
FROM Test Te
INNER JOIN dbo.Person P ON P.ID=Te.PersonID) T
PIVOT(MAX(T.Result) FOR TestID IN([1],[2],[3])) AS pvt
GROUP BY Name,Age
heres some links
http://msdn.microsoft.com/en-us/library/ms177410(v=sql.105).aspx
http://www.codeproject.com/Questions/393632/How-to-use-Pivot-in-SQL
http://blog.sqlauthority.com/2008/06/07/sql-server-pivot-and-unpivot-table-examples/

Resources