Sql Select from another table (loop?) - sql-server

My SQL skills aren't great hence the post.
I'm trying to get all the contact names based on a company out.
For example I have two statements:
Select Id, CompanyName, Address From Clients
Select ClientId, ContactName From Contacts
You may have many contacts to a single client
Result: (I need all the contact names in a single column)
ContactName Company Address
----------------------------------------
Johh, Steve 123 Comp 12345 Address
David,Mike, Sarah 44 Comp 111 Address
A working example would be very much appreciated.

SELECT DISTINCT (
SELECT ISNULL(ct.ContactName, '') + ', '
FROM dbo.Clients cl JOIN dbo.Contacts ct ON cl.Id = ct.ClientId
WHERE cl.ID = cl2.Id
FOR XML PATH('')) AS ContactName, CAST(cl2.Id AS nvarchar(7)) + ' ' + cl2.CompanyName AS Company, Address
FROM dbo.Clients cl2
ORDER BY 2
Demo on SQLFiddle

Firstly build all the Contact Names for a Company into a Single Column. Assuming the database to be SQL Server, I'm using a Common Table Expression to store the single column contact list. Once the CTE is built, join it with the Clients table to get the ContactNames. FOR XML is used to concatenate rows.
WITH CTEContactList(ClientID,ContactNames)
AS
(
SELECT c1.ClientID,
Names = SUBSTRING(( SELECT ', ' + c2.ContactName
FROM Contacts c2
WHERE c1.ClientID = c2.ClientID
FOR XML PATH ('')),3,8000 ))
FROM Contacts c1
GROUP BY c1.ClientID
)
SELECT
cl.ID,
cl.CompanyName,
cl.Address,
ctelist.ContactNames
FROM Clients cl
INNER JOIN CTEContactList ctelist
ON cl.ID = cteList.ClientID

Sounds like you need to do a table join.
Example: two tables here
1. Person
2. Orders
Query:
SELECT
Persons.LastName, Persons.FirstName, Orders.OrderNo
FROM Persons
INNER JOIN Orders ON Persons.P_Id = Orders.P_Id
ORDER BY Persons.LastName

You didn't specify your DBMS, so I'm assuming PostgreSQL:
select string_agg(ct.contactName, ', '), cl.companyname, cl.address
from contacts ct
join clients cl on cl.id = ct.clientId
group by cl.companyname, cl.address

Related

SQL Concatenate rows from multiple tables into single text field separated by comma

I have 3 tables
Projects, Consultant, Contact.
The Consultant table is a linking table containing only 3 fields, Projectid, Consultantid, and Contactid, which links a contact to a project as a consultant. There is no direct link from the contact to the project. The full name of the consultant is in the contact table. The Project Number is in the project table.
Project :
ProjectNumber
Contact:
Contactid, Consultant_Full_name
Consultant: Consultantid, Projectid, Contactid
I have built a table in SSRS showing project fields and want to pull in a column on the end showing all the consultants linked to each project
Example -
+---------------+--------------------------------------+
| ProjectNumber | Consultants |
+---------------+--------------------------------------+
| 12356 | Mary White, Fred Bloggs, Peter Jones |
| 12445 | Fred Bloggs, Paul White |
+---------------+--------------------------------------+
The code I have written is:
SELECT t2.ProjectNumber
,consultant = STUFF((
SELECT ',' + fullname
FROM FilteredContact t1
WHERE t1.contactid = t2.ConsultantID
FOR XML PATH('')
), 1, 1, '')
FROM (
SELECT proj.ccx_projectnumber AS ProjectNumber
,link.contactid AS ConsultantID
,con.fullname AS ConsultantName
FROM FilteredContact con
INNER JOIN Filteredccx_ccx_project_contact_consultant AS link ON con.contactid = link.contactid
INNER JOIN Filteredccx_project proj ON link.ccx_projectid = proj.ccx_projectid
) t2
GROUP BY t2.ProjectNumber
I know that this is not grouped correctly as the Contactid needs to be in an aggregate or group by statement but I cannot work out how to do it correctly. I am thinking that maybe I have not linked the tables correctly. If I use
group by ts.ProjectNumber, Contactid
the results are a row per each consultant and not per each project which is what I want.
Any help gratefully accepted.
I am using Sql server 2008
This is what you want
;WITH CTE
AS (
SELECT proj.ccx_projectnumber AS ProjectNumber
,con.fullname AS ConsultantName
FROM Filteredccx_ccx_project_contact_consultant AS link
INNER JOIN Filteredccx_project proj ON link.ccx_projectid = proj.ccx_projectid
INNER JOIN FilteredContact con ON link.contactid = con.contactid
)
SELECT DISTINCT ProjectNumber
,STUFF((
SELECT ',' + ConsultantName
FROM CTE C1
WHERE C.ProjectNumber = C1.ProjectNumber
FOR XML PATH('')
), 1, 1, '')
FROM CTE C

Dynamic SQL in SQL Server - how to?

My question is about dynamic SQL. I have two tables
customers (custid, fn_name, Ln_name)
vendors (vendor_id, Custid, ordernum, orderdate)
And also I have other tables
table (tableid(pk), name)
table_col (colid(pk), tableid(fk), colname)
table_keys (key_id, tableid, key_col_name)
Now if user selects any column from the table_col tables, dynamically I need to get column names and dynamically identify the join from the table_keys based on the name match.
Could be somethings like this
select
t.name
, c.colname
, case k.key_col_name when not null then 'X' else '' end as iskey
from table_col as c
left join table_keys as k on k.key_col_name = c.colname
inner join table as t on t.tableid = c.tableid
where t.name in ('customers', 'vendors');

Convert groups of multiple key-value rows to XML

I have a table called userInfo that has data similar to the following:
Id, Field, Value
---------------------
1, FirstName, John
1, LastName, Smith
1, Age, 25
1, Gender, Male
2, FirstName, Jane
2, LastName, Smythe
2, Age, 24
2, Gender, Female
What I need is some T-SQL that will produce a single row for each Id with the following structure:
Row:1
<FieldValues>
<FirstName>John</FirstName>
<LastName>Smith</LastName>
<Age>25</Age>
<Gender>Male</Gender>
</FieldValues>
Row:2
<FieldValues>
<FirstName>Jane</FirstName>
<LastName>Smythe</LastName>
<Age>24</Age>
<Gender>Female</Gender>
</FieldValues>
I have tried a couple of things to get this but can't get figure this out.
Edit:
The list of Fields I provided here (i.e. FirstName, LastName, etc) is not a static list of fields. I will be adding and taking away from this list all the time so the query would be able to handle this automatically). Ideally I could use something like FOR XML PATH('FieldValues')
You can build your XML as a string using for xml path('') and then cast to XML.
select T.Id,
cast('<FieldValues>' + (
select '<'+T2.Field+'>'+
(select T2.Value as '*' for xml path(''))+
'</'+T2.Field+'>'
from dbo.YourTable as T2
where T.Id = T2.Id
for xml path(''), type
).value('text()[1]', 'varchar(max)') +
'</FieldValues>' as xml) as FieldValues
from dbo.YourTable as T
group by T.Id;
SQL Fiddle
This part (select T2.Value as '*' for xml path('')) is there to take care of characters that needs to be entities in the value like &.
Here is one way:
SELECT '<FieldValues>'+
'<FirstName>'+fn.Value +'</FirstName>' +
'<LastName>'+ln.Value +'</LastName>' +
'<Age>'+age.Value +'</Age>' +
'<Gender>'+gender.Value +'</Gender>' +
'</FieldValues>
FROM (SELECT DISTINCT ID FROM userInfo) t
JOIN userInfo fn ON t.ID = fn.ID and fn.Field = 'FirstName'
JOIN userInfo ln ON t.ID = ln.ID and ln.Field = 'LastName'
JOIN userInfo age ON t.ID = age.ID and age.Field = 'Age'
JOIN userInfo gender ON t.ID = gender.ID and gender.Field = 'Gender'
How this works:
First I create table of just the unique ID numbers.
SELECT DISTINCT ID FROM table
Then I use this table to join back to the main table for each field. (Each of these joins will have only one row per ID.)
JOIN table fn ON t.ID = fn.ID and fn.Field = 'FirstName'
JOIN table ln ON t.ID = ln.ID and ln.Field = 'LastName'
JOIN table age ON t.ID = age.ID and age.Field = 'Age'
JOIN table gender ON t.ID = gender.ID and gender.Field = 'Gender'
Finally I create a string formatted as you need.
'<FieldValues>'+
'<FirstName>'+fn.Value +'</FirstName>' +
'<LastName>'+ln.Value +'</LastName>' +
'<Age>'+age.Value +'</Age>' +
'<Gender>'+gender.Value +'</Gender>' +
'</FieldValues>
An additional note: It is recommended to not use Camel Case on your xml since xml is case sensitive any use of case is a pain -- most just use all lower case.

How to work with data in dynamically created column in SQL Server?

Sometimes you want to append a string or int to the data in a given column, such as SELECT 1005 + ID FROM Users (where you'd be adding 1005 to the ID column data). How can this be done for columns that are created dynamically?
The following works:
SELECT ID,
Name,
(SELECT Email FROM Emails WHERE Emails.ID = d.ID) AS Email,
Address
FROM data d
But adding the following new line creates the error Invalid column name "Email":
SELECT ID,
Name,
(SELECT Email FROM Emails WHERE Emails.ID = d.ID) AS Email,
Email + ' testing ' AS Test, /* New line that causes error */
Address
FROM data d
How to fix this error?
You can't reference an alias. You can repeat the expression (usually unwise performance-wise and/or undesirable syntax-wise) or use a subquery / CTE. And why are you using a correlated subquery instead of a join?
SELECT ID, Name, Email, Email + ' testing' AS Test, Address
FROM
(
SELECT d.ID, d.Name, e.Email, d.Address
FROM dbo.data AS d
INNER JOIN dbo.Emails AS e
ON e.ID = d.ID
) AS x;
...or a CTE...
;WITH x AS
(
SELECT d.ID, d.Name, e.Email, d.Address
FROM dbo.data AS d
INNER JOIN dbo.Emails AS e
ON e.ID = d.ID
)
SELECT ID, Name, Email, Email + ' testing' AS Test, Address
FROM x;
This is something that should not be done in a subquery, use a join instead. Correlated subqueries in genral are a poor technique as they can be performance killers and they are only rarely needed. They should be a technique of last resort not a technique of first resort.
SELECT ID,
Name,
Email,
Email + ' testing ' AS Test,
Address
FROM data d
JOIN Emails e ON e.ID = d.ID
If everyone won't have an email use a left join. If there are multipel email addresses then you may need some addtional criteria to filter on or you may need to use aggregae functions.
It looks like the problem your having is that you are trying to reference something that is 'Out of scope'. When you use a subquery in the select statement the subqueries tables are only accessible for that item. That access does not carry over to other items in the select statement. So, this should work for you (if it's ugly and it works, it's not ugly).
SELECT ID,
Name,
(SELECT Email FROM Emails WHERE Emails.ID = d.ID) AS Email,
(SELECT Email FROM Emails WHERE Emails.ID = d.ID)
+ ' testing ' AS Test,
Address
FROM data d

Using Inner Join on Comma-Separated Values in Stored Procedure

I have a table UserMaster as follow...(only required columns are shown)
UserID UserName EmailID
---------------------------------
1000 amol amol#gmail.com
1001 mahesh mahesh#gmail.com
1002 saurabh saurabh#gmail.com
1003 nitesh nitesh#gmail.com
Another table MessageHistory (Only required columns are shown)
MsgCode From To
-----------------------------
MSG001 1000 1001,1002,1003
MSG002 1001 1000,1002,1003
I am storing UserIds in From and To columns...
I am trying to create a stored procedure to display the Email History of particular message code
Create Procedure proc_GetMessageHistory
#MsgCode varchar(50)
as
Begin
Select * From MessageHistory Where MsgCode=#MsgCode
End
The result is coming as shown above in MessageHistory table...but I want to show respective UserEmailIDs instead of UserID (e.g. 'amol#gmail.com' instead of 1000)...
How could I do this in a stored procedure? How could I use inner join in this case specially with comma-separated values? Please help...thanks
As everyone has already noted, this should never be any sort of permanent solution, as there no way it will ever perform in an efficient manner. Also, that sort of denormalised structure is likely to have any number of issues. That said...
List of email addresses per messages, i.e. one recipient per row:
select m.MsgCode
, sender = s.EmailID
, recipient = u.EmailID
from MessageHistory m
inner join UserMaster s on m.[From] = s.UserID
inner join UserMaster u on charindex(cast(u.UserID as varchar), m.[To]) > 0
SQL Fiddle with demo.
List of messages, comma separated list of email addresses, one message per row:
with emails as
(
select m.MsgCode
, recipient = u.EmailID
from MessageHistory m
inner join UserMaster u on charindex(cast(u.UserID as varchar), m.[To]) > 0
)
select m.MsgCode
, [From] = u.EmailID
, [To] = stuff
(
(
select ',' + recipient
from emails e
where m.MsgCode = e.MsgCode
for xml path('')
)
, 1
, 1
, ''
)
from MessageHistory m
inner join UserMaster u on m.[From] = u.UserID
SQL Fiddle with demo.

Resources