SQL: One 2 many rows with many rows combined into one column - sql

I have four tables, namely tblProject (one record), tblTeamMembers (many records), tblProjectStatus (look-up table) and tblProjectScoresComments (many records). I'm using SQL Server 2017.
Below are the table definitions:
tblProject:
[ProjectID] [INT] IDENTITY(1,1) NOT NULL,
[ProjectName] [NVARCHAR](150) NOT NULL,
[CommunityProblem] [NTEXT] NOT NULL,
[IctSolveCommunityProblem] [NTEXT] NOT NULL,
[TeamMemberRoles] [NTEXT] NOT NULL,
[ProjectImpact] [NTEXT] NOT NULL,
[HelpRaiseFunds] [NTEXT] NOT NULL,
[ProjectStatus] [INT] NOT NULL,
[CaptureDate] [DATE] NOT NULL
tblTeamMembers:
[MemberID] [INT] IDENTITY(1,1) NOT NULL,
[Person] [NVARCHAR](150) NOT NULL,
[SalRef] [NVARCHAR](50) NOT NULL,
[Email] [NVARCHAR](150) NOT NULL,
[UserName] [NVARCHAR](150) NOT NULL,
[TeamLeader] [INT] NOT NULL,
[ProjectLeader] [INT] NOT NULL,
[ProjectLeaderContactNo] [NVARCHAR](150) NULL,
[ProjectID] [INT] NOT NULL
tblProjectScoresComments
[RecID] [INT] IDENTITY(1,1) NOT NULL,
[ProjectID] [INT] NOT NULL,
[Score] [FLOAT] NOT NULL,
[Comments] [NVARCHAR](MAX) NULL,
[UserID] [NVARCHAR](150) NOT NULL,
[DateCaptured] [DATETIME] NOT NULL
tblProjectStatus:
[ProjectStatusID] [INT] IDENTITY(1,1) NOT NULL,
[ProjectStatus] [NVARCHAR](100) NOT NULL
I would like the results to return columns from all three tables, but the 3rd table (tblProjectScoresComments) has many records and for the column [Score] an average should be returned, and the [Comments] column should have all comments returned as 1 column and every comment should be separated by a comma (,).
I would like to use a query similar to the below:
SELECT
p.ProjectID, p.ProjectName AS Project,
ps.ProjectStatus,
tm.Person AS ProjectLeader,
p.CaptureDate, [AVERAGE_SCORE_FOR_ALL] AS Score,
[ALL_COMMENTS_MERGED_TO_ONE_COLUMN] AS Comments
FROM
dbo.tblProject AS p
INNER JOIN
dbo.tblProjectStatus AS ps ON p.ProjectStatus = ps.ProjectStatusID
INNER JOIN
dbo.tblTeamMembers AS tm ON p.ProjectID = tm.ProjectID
INNER JOIN
dbo.tblProjectScoresComments AS psc ON p.ProjectID = psc.ProjectID
WHERE
(tm.ProjectLeader = 1)
Results should look something like this:
ProjectID | Project | ProjectStatus | ProjectLeader | CaptureDate | Score |Comments
---------------------------------------------------------------------------------------------------------------------------------------------------------------
1 | Access to ICT-Makatane High School and Community Project | Not yet decided | Mary Ndlovu | 2019-10-04 | 1.67 |Comment 1,Comment 2,Comment 3
2 | Asample project | Rejected | Joe Soap | 2019-11-07 | 3 |Comment 1,Comment 2
Would really appreciate assistance!

This sounds like aggregation:
SELECT p.ProjectID, p.ProjectName AS Project, ps.ProjectStatus, tm.Person AS ProjectLeader,
p.CaptureDate,
avg(psc.score) AS avg_score,
string_agg(psc.comments, ' ') as Comments
FROM dbo.tblProject p JOIN
dbo.tblProjectStatus ps
ON p.ProjectStatus = ps.ProjectStatusID JOIN
dbo.tblTeamMembers tm
ON p.ProjectID = tm.ProjectID JOIN
dbo.tblProjectScoresComments
psc
ON p.ProjectID = psc.ProjectID
WHERE tm.ProjectLeader = 1
GROUP BY p.ProjectID, p.ProjectName AS Project, ps.ProjectStatus, tm.Person AS ProjectLeader,
p.CaptureDate;
EDIT:
The alternative to string_agg() is rather messy:
SELECT . . .,
psc.avg_score, c.comments,
FROM dbo.tblProject p JOIN
dbo.tblProjectStatus ps
ON p.ProjectStatus = ps.ProjectStatusID JOIN
dbo.tblTeamMembers tm
ON p.ProjectID = tm.ProjectID OUTER APPLY
(SELECT STUFF( (SELECT ', ' + psc.comment
FROM dbo.tblProjectScoresComments psc
WHERE p.ProjectID = psc.ProjectID
FOR XML PATH (''), TYPE
).value(N'.[1]', N'nvarchar(max)'
), 1, 2, ''
) as comments
) c OUTER APPLY
(SELECT AVG(psc.score) as avg_score
FROM dbo.tblProjectScoresComments psc
WHERE p.ProjectID = psc.ProjectID
) psc
An outer GROUP BY should not be necessary.

Related

COALESCE vs OR condition for JOIN (SQL)

I have Event table
TABLE Event(
EventId [int] IDENTITY(1,1) NOT NULL,
EventSource1Id [int] NULL,
EventSource2Id [int] NULL
)
that contains info about events from different sources
where one of the event sources can be null
TABLE EventSource1(
Id [int] IDENTITY(1,1) NOT NULL,
Name [nvarchar](50) NULL,
VenueId [int] NOT NULL
)
and
TABLE EventSource2(
Id [int] IDENTITY(1,1) NOT NULL,
Name [nvarchar](50) NULL,
VenueId [int] NOT NULL
)
TABLE Venue(
Id [int] IDENTITY(1,1) NOT NULL,
TimeZone [nvarchar](100) NOT NULL
)
I'd like to create view, but I'm not sure what is the best way to use: coalesce vs OR condition for JOIN
First option:
SELECT
ev.[Id] AS 'Id',
ven.[Id] AS 'VenueId'
FROM Event ev
LEFT JOIN EventSource1 source1 ON source1.[Id] = ev.EventSource1Id
LEFT JOIN EventSource2 source1 ON source2.[Id] = ev.EventSource2Id
LEFT JOIN Venue AS ven ON ven.[Id] = source1.[VenueId] OR v.[Id] = source2.[VenueId]
Second option:
SELECT
ev.[Id] AS 'Id',
ven.[Id] AS 'VenueId'
FROM Event ev
LEFT JOIN EventSource1 source1 ON source1.[Id] = ev.EventSource1Id
LEFT JOIN EventSource2 source1 ON source2.[Id] = ev.EventSource2Id
LEFT JOIN Venue AS ven ON ven.[Id] = COALESCE(source1.[Id], source2.[Id])
Could you help me please?
The COALESCE will typically yield a better query plan. You should test with your data.

getting wrong results in count function while using group by clause in sql server

im calculating how many links are submitted in each categories. i have assigned four categories -Classifieds,Events,Articles,Socialbookmarking for monday . user submitted 15 links in classified category . but it showing 15*4 = 60 links submitted.
output getting like this
Categoryname DayTarget LnkSubmsnDate LnkSubmtdBy submittedLinks performance
Classifieds 15 10/12/2015 swapna 60 Reached
but i want like this
Categoryname DayTarget LnkSubmsnDate LnkSubmtdBy submittedLinks performance
Classifieds 15 10/12/2015 swapna 15 Reached
Events 2 10/12/2015 swapna 0 not Reached
Articles 2 10/12/2015 swapna 0 not Reached
Socialbookmarking 10 10/12/2015 swapna 0 not Reached
sql query
select c.Categoryname,DayTarget,l.LnkSubmsnDate,l.LnkSubmtdBy,COUNT(LinkId) as submittedLinks,
(CASE
WHEN DayTarget=COUNT(LinkId) THEN 'Reached'
WHEN DayTarget-COUNT(LinkId) < 1 THEN 'Reached'
WHEN DayTarget-COUNT(LinkId) >= 1 THEN 'Not Reached'
END ) as performance
from tbl_Link as l
join Tbl_DaySubmission ds on l.ProjectId=ds.projectid and l.CategoryId=ds.CatId
join tbl_Category c on l.CategoryId=c.CategoryId
where LnkSubmsnDate='2015-10-12'and l.ProjectId='109'
group by c.Categoryname,ds.DayTarget,LnkSubmsnDate,l.LnkSubmtdBy
table definations
TABLE [dbo].[Tbl_DaySubmission](
[DayId] [bigint] IDENTITY(100,1) NOT NULL,
[DayName] [varchar](50) NOT NULL,
[DayNumber] [int] NOT NULL,
[CatId] [int] NULL,
[DayTarget] [int] NOT NULL,
[ProjectID] [int] NULL,
[status] [bit] NULL
tbl_link
TABLE [dbo].[tbl_Link](
[LinkId] [bigint] IDENTITY(1,1) NOT NULL,
[LinkName] [nvarchar](255) NULL,
[ReportLinks] [nvarchar](255) NULL,
[CreatedDate] [nvarchar](255) NULL,
[CategoryId] [int] NULL,
[KeywordID] [int] NULL,
[ProjectId] [int] NULL,
[LnkSubmsnDate] [date] NULL,
[LnkSubmtdBy] [nvarchar](255) NULL,
tbl_category
TABLE [dbo].[tbl_Category](
[CategoryId] [int] IDENTITY(1,1) NOT NULL,
[Categoryname] [varchar](50) NOT NULL
if you are getting answers in multiples of the expected value, it means that your join is not working properly (Data is not as you expected in one of the table).
We cannot guide you without seeing the actual data in all of the tables as you are not using the primary keys to join tables.
I would suggest you to break your query into multiple steps and evaluate the result at each step before joining with another table and that would help you in fixing it.
You can't retrieve LnkSubmsnDate and LnkSubmtdBy from unsubmitted tbl_Links. You have to set default values to the 2 columns if they are unsubmitted, otherwise they show null.
SELECT
c.Categoryname
, ds.DayTarget
, CASE WHEN (COUNT(LinkId) > 0) THEN l.LnkSubmsnDate
ELSE NULL END AS [LnkSubmsnDate]
, CASE WHEN (COUNT(LinkId) > 0) THEN l.LnkSubmtdBy
ELSE NULL END AS [LnkSubmtdBy]
, COUNT(LinkId) AS submittedLinks
, (CASE
WHEN DayTarget=COUNT(LinkId) THEN 'Reached'
WHEN DayTarget-COUNT(LinkId) < 1 THEN 'Reached'
WHEN DayTarget-COUNT(LinkId) >= 1 THEN 'Not Reached'
END ) AS performance
FROM tbl_Category AS c
INNER JOIN Tbl_DaySubmission AS ds ON ds.CatId = c.CategoryId
LEFT JOIN (SELECT * FROM tbl_Link WHERE LnkSubmsnDate='2015-10-12') AS l ON l.ProjectId = ds.ProjectID AND l.CategoryId = ds.CatId
WHERE ds.ProjectID = 109
GROUP BY c.Categoryname, ds.DayTarget, l.LnkSubmsnDate, l.LnkSubmtdBy
ORDER BY performance DESC
http://sqlfiddle.com/#!6/548a26/7

How do I aggregate 3 columns that are different with MIN(DATE)?

I'm facing a simple problem here that I can't solve, I have this query:
SELECT
MIN(TEA_InicioTarefa),
PFJ_Id_Analista,
ATC_Id,
SRV_Id
FROM
dbo.TarefaEtapaAreaTecnica
INNER JOIN Tarefa t ON t.TRF_Id = TarefaEtapaAreaTecnica.TRF_Id
WHERE SRV_Id = 88
GROUP BY SRV_Id, ATC_Id, PFJ_Id_Analista
ORDER BY ATC_Id ASC
It returns me this:
I was able to group it a little with GROUP BY SRV_Id, ATC_Id, PFJ_Id_Analista that gave me these 8 records, but as you can see some PFJ_Id_Analista are different.
What I want is to select only the early date of each SRV_Id and ATC_Id, the PFJ_Id_Analista don't need to grup, if I remove PFJ_Id_Analista from the grouping the query works, but I need the column.
For eg.: between row number 2 and 3 I want only the early date, so it will be row 2. The same goes for rows 5 to 8, I want only row 6.
DDL for TarefaEtapaAreaTecnica (important key: TRF_Id)
CREATE TABLE [dbo].[TarefaEtapaAreaTecnica](
[TEA_Id] [int] IDENTITY(1,1) NOT NULL,
**[TRF_Id] [int] NOT NULL,**
[ETS_Id] [int] NOT NULL,
[ATC_Id] [int] NOT NULL,
[TEA_Revisao] [int] NOT NULL,
[PFJ_Id_Projetista] [int] NULL,
[TEA_DoctosQtd] [int] NULL,
[TEA_InicioTarefa] [datetime2](7) NULL,
[PFJ_Id_Analista] [int] NULL,
[TEA_FimTarefa] [datetime2](7) NULL,
[TEA_HorasQtd] [numeric](18, 1) NULL,
[TEA_NcfQtd] [int] NULL,
[PAT_Id] [int] NULL
DDL for Tarefa (important keys TRF_Id and SRV_Id (which I need it)):
CREATE TABLE [dbo].[Tarefa](
**[TRF_Id] [int] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL,**
**[SRV_Id] [int] NOT NULL,**
[TRT_Id] [int] NOT NULL,
[TRF_Descr] [varchar](255) NULL,
[TRF_Entrada] [datetime] NOT NULL,
[TRF_DoctosQtd] [int] NOT NULL,
[TRF_Devolucao] [datetime] NULL,
[TRF_NcfQtd] [int] NULL,
[TRF_EhDocInsuf] [bit] NULL,
[TRF_Observ] [varchar](255) NULL,
[TRF_AreasTrfQtd] [int] NULL,
[TRF_AreasTrfLiqQtd] [int] NULL
Thanks a lot.
EDIT:
CORRECT QUERY
Based on #Gordon Linoff post:
select t.TEA_InicioTarefa, t.PFJ_Id_Analista, t.ATC_Id, t.SRV_Id
from (select t.*,
row_number() over (partition by ATC_Id, SRV_Id
order by TEA_InicioTarefa) as seqnum, ta.SRV_Id
from dbo.TarefaEtapaAreaTecnica t
inner join dbo.Tarefa ta on t.TRF_Id = ta.TRF_Id
) t
where seqnum = 1 AND t.SRV_Id = 88
Just use window functions:
select t.*
from (select t.*,
row_number() over (partition by ATC_Id, SRV_Id
order by ini) as seqnum
from dbo.TarefaEtapaAreaTecnica t
) t
where seqnum = 1;
This is really an example of filtering, not aggregation. The problem is getting the right value to filter on.
Then get the grouping first and then do a JOIN with it like
SELECT
x.Min_TEA_InicioTarefa,
t.PFJ_Id_Analista,
t.ATC_Id,
t.SRV_Id
FROM
dbo.TarefaEtapaAreaTecnica t
INNER JOIN Tarefa ta ON ta.TRF_Id = t.TRF_Id
INNER JOIN (
select SRV_Id, MIN(TEA_InicioTarefa) as Min_TEA_InicioTarefa
from dbo.TarefaEtapaAreaTecnica
GROUP BY SRV_Id
) x ON t.SRV_Id = x.SRV_Id
WHERE t.SRV_Id = 88
ORDER BY t.ATC_Id ASC;

Add SQL SELECT COUNT result to an existing column as text

I have two tables:
1. TABLE [dbo].[ItemCategories](
[Id] [int] IDENTITY(1,1) NOT NULL,
[CategoryId] [int] NULL,
[StockId] [int] NULL,
2. TABLE [dbo].[Categories](
[Id] [int] IDENTITY(1,1) NOT NULL,
[ParentCategoryId] [int] NULL,
[CategoryName] [nvarchar](100) NULL,
[Slug] [nvarchar](150) NULL
And this query in SQL Server 2012
SELECT [CategoryName], [Slug], [ParentCategoryId], [Id]
FROM [Categories]
ORDER BY [ParentCategoryId] DESC
Which returns these rows
[CategoryName] [Slug] [ParentCategoryId] [Id]
Exercise exercise 42 46
Fashion fashion 42 47
And I have a second query:
SELECT COUNT(*)
FROM [ItemCategories]
WHERE CategoryId = '46' <--- This Id is the same as [Id] from the first query
How can I a modify the first query to add total count from the second query to the returned CategoryName column (as a single string) ?
Like this:
[CategoryName] [Slug] [ParentCategoryId] [Id]
Exercise (31) exercise 42 46
Fashion (56) fashion 42 47
I have created this join, but I don't know how to add the COUNT(*) as text
SELECT [CategoryName], [Slug], [ParentCategoryId], [Categories].[Id]
FROM [Categories]
INNER JOIN [ItemCategories] ON [Categories].[Id]=[ItemCategories].[CategoryId]
ORDER BY [ParentCategoryId] DESC
You can use the count(*) window function. I would put it in a separate column, but you can do:
SELECT [CategoryName] + ' (' + cast(count(*) over (partition by Id) as varchar(255)) + ')',
[Slug], [ParentCategoryId], [Id]
FROM [Categories]
ORDER BY [ParentCategoryId] DESC;
EDIT:
For two tables, use a JOIN and GROUP BY:
SELECT c.CategoryName + ' (' + cast(count(ic.Id) as varchar(255)) + ')',
c.Slug, c.ParentCategoryId, c.Id
FROM Categories c LEFT JOIN
ItemCategories ic
on ic.CategoryId = c.Id
GROUP BY c.CategoryName, c.slug, c.ParentCategoryId, c.id
ORDER BY ParentCategoryId DESC;

Joining SQL tables

I have a customer table that has primary information about the customer like, name, lastname,password,… I have a Address, Email, Phone table that has for example 3 kinds of address , 2 phone number, 2 email address for each customer. I have a Type table that TypeID and Type_Group,Type_Value. For example:
TypeID Type_Group Type_Value
1 Address Work
2 Address Home
3 Address mailing
4 Email Primary
5 Email secondary
I know how to join customer table with address, Email and phone table. I don't know how to join the Address, phone,Email with type Table.
This is my Query:
SELECT
cc.[Customer_ID]
,[Account_Number]
,[First_Name]
,[Middle_Name]
,[Last_Name]
,[Password]
,ce.[Email]
,cph.Phone_Number
,ca.Address_1
,ca.Address_2
,ca.City
,ca.State
,ca.Zip
,tp.Type_Desc
FROM [CustomerPortal].[dbo].[Customer] cc WITH (NOLOCK)
left join [CustomerPortal].[dbo].Customer_Email ce WITH (NOLOCK) on cc.Customer_ID = ce.Customer_ID
left join [CustomerPortal].[dbo].Customer_Address ca WITH (NOLOCK) on cc.Customer_ID =cp.Customer_ID
left join [CustomerPortal].[dbo].Customer_Phone cph WITH (NOLOCK) on cc.Customer_ID =cph.Customer_ID
WHERE cc.Customer_ID=#Customer_ID
This is Tables:
this is customer Table:
(PRIMARY KEY)[Customer_ID] [int] IDENTITY(1,1) NOT NULL,
[Account_Number] [int] NULL,
[First_Name] [varchar](50) NULL,
[Middle_Name] [varchar](50) NULL,
Customer Address Table:
[dbo].[Customer_Address](
primary key[Customer_Address_ID] [int] IDENTITY(1,1) NOT NULL,
Fkey [Customer_ID] [int] NOT NULL,
[Address_1] [varchar](100) NULL,
[Address_2] [varchar](100) NULL,
[City] [varchar](100) NULL,
[State] [varchar](10) NULL,
[Zip] [varchar](10) NULL,
Fkey [Address_Type] [int] NULL,
CustomerEmail Table:
[dbo].[Customer_Email](
PKey [Customer_Email_ID] [int] IDENTITY(1,1) NOT NULL,
Fkey [Customer_ID] [int] NOT NULL,
[Email] [varchar](50) NULL,
Fkey [Email_Type] [int] NULL,
Customer Phone Table:
PK [dbo].[Customer_Phone](
FK [Customer_Phone_ID] [int] IDENTITY(1,1) NOT NULL,
[Customer_ID] [int] NOT NULL,
[Phone_Number] [bigint] NULL,
FK [Phone_Type] [int] NULL,
Type Table:
PK [dbo].[Type_XREF](
[Type_ID] [int] IDENTITY(1,1) NOT NULL,
[Type_Group] [varchar](25) NULL,
[Type_Value] [varchar](50) NULL,
[Type_Desc] [varchar](100) NULL,
I am not sure how to add join to Type Table, any thing I try produce me several Rows, as I want to have 1 row for customer with name, All related Home address,All related mailing address, primary email,secondary email,.... So all customer info in 1 line of record.
You can join each of the tables to the type table seperately, since their Type ID will correspond to one of the IDs in the type table. Even though the types are mixed in the table, each element in their respective table would reasonable have a logical type assigned to it. I'll just make an assumption on the format that you want to display the Type in and the name of the table:
SELECT
cc.[Customer_ID]
,[Account_Number]
,[First_Name]
,[Middle_Name]
,[Last_Name]
,[Password]
,ce.[Email]
,cph.Phone_Number
,ca.Address_1
,ca.Address_2
,ca.City
,ca.State
,ca.Zip
,tp.Type_Desc
,te.Type_Group + '-' + te.Type_Value as [EmailType]
,ta.Type_Group + '-' + ta.Type_Value as [AddressType]
,tph.Type_Group + '-' + tph.Type_Value as [PhoneType]
FROM [CustomerPortal].[dbo].[Customer] cc WITH (NOLOCK)
left join [CustomerPortal].[dbo].Customer_Email ce WITH (NOLOCK) on cc.Customer_ID = ce.Customer_ID
left join [CustomerPortal].[dbo].Customer_Address ca WITH (NOLOCK) on cc.Customer_ID =cp.Customer_ID
left join [CustomerPortal].[dbo].Customer_Phone cph WITH (NOLOCK) on cc.Customer_ID =cph.Customer_ID
left join [dbo].Type_Table te on te.ID = ce.Type
left join [dbo].Type_Table ta on ta.ID = ca.Type
left join [dbo].Type_Table tph on tph.ID = cph.Type