Using MS SQL Server 2005, how can I consolidate detail records into a single comma separated list - sql

BACKGROUND:**I am running **MS2005. I have a MASTER table (ID, MDESC) and a DETAIL table (MID, DID, DDESC) with data as follows
1 MASTER_1
2 MASTER_2
1 L1 DETAIL_M1_L1
1 L2 DETAIL_M1_L2
1 L3 DETAIL_M1_L3
2 L1 DETAIL_M2_L1
2 L2 DETAIL_M2_L2
If I join the tables with
SELECT M.*, D.DID FROM MASTER M INNER JOIN DETAIL D on M.ID = D.MID
I get a list like the following:
1 MASTER_1 L1
1 MASTER_1 L2
1 MASTER_1 L3
2 MASTER_2 L1
2 MASTER_2 L2
QUESTION: Is there any way to use a MS SQL select statement to get the detail records into a comma separated list like this:
1 MASTER_1 "L1, L2, L3"
2 MASTER_2 "L1, L2"

You need a function:-
CREATE FUNCTION [dbo].[FN_DETAIL_LIST]
(
#masterid int
)
RETURNS varchar(8000)
AS
BEGIN
DECLARE #dids varchar(8000)
SELECT #dids = COALESCE(#dids + ', ', '') + DID
FROM DETAIL
WHERE MID = #masterid
RETURN #dids
END
Usage:-
SELECT MASTERID, [dbo].[FN_DETAIL_LIST](MASTERID) [DIDS]
FROM MASTER

Thanks to the concept in the link from Bill Karwin, it's the CROSS APPLY that makes it work
SELECT ID, DES, LEFT(DIDS, LEN(DIDS)-1) AS DIDS
FROM MASTER M1 INNER JOIN DETAIL D on M1.ID = D.MID
CROSS APPLY (
SELECT DID + ', '
FROM MASTER M2 INNER JOIN DETAIL D on M2.ID = D.MID
WHERE M1.ID = M2.ID
FOR XML PATH('')
) pre_trimmed (DIDS)
GROUP BY ID, DES, DIDS
RESULTS:
ID DES DIDS
--- ---------- ---------------
1 MASTER_1 L1, L2, L3
2 MASTER_2 L1, L2

coalesce is your friend.
declare #CSL vachar(max)
set #CSL = NULL
select #CSL = coalesce(#CSL + ', ', '') + cast(DID as varchar(8))
from MASTER M INNER JOIN DETAIL D on M.ID = D.MID
select #CSL
This will not work well for a generalized query (i.e. works great for a single master record).
You could drop this into a function... but that may not give you the performance you need/want.

This is the purpose of MySQL's GROUP_CONCAT() aggregate function. Unfortunately, it's not very easy to duplicate this function in other RDBMS brands that don't support it.
See Simulating group_concat MySQL function in Microsoft SQL Server 2005?

I think you need a function for this to work properly in recent version of SQL Server:
http://sqljunkies.com/WebLog/amachanic/archive/2004/11/10/5065.aspx?Pending=true

Related

Use Data of 1 table into another one dynamically

I have one table category_code having data like
SELECT Item, Code, Prefix from category_codes
Item Code Prefix
Bangles BL BL
Chains CH CH
Ear rings ER ER
Sets Set ST
Rings RING RG
Yellow GOld YG YG........
I have another table item_categories having data like
select code,name from item_categories
code name
AQ.TM.PN AQ.TM.PN
BL.YG.CH.ME.PN BL.YG.CH.ME.PN
BS.CZ.ST.YG.PN BS.CZ.ST.YG.PN
CR.YG CR.YG.......
i want to update item_categories.name column corresponding to category_code.item column like
code name
BL.YG.CH.ME.PN Bangles.Yellow Gold.Chains.. . . .
Please suggest good solution for that. Thanks in advance.
First, split the code into several rows, join with the category code and then, concat the result to update the table.
Here an example, based on the data you gave
create table #category_code (item varchar(max), code varchar(max), prefix varchar(max));
create table #item_categories (code varchar(max), name varchar(max));
insert into #category_code (item, code, prefix) values ('Bangles','BL','BL'),('Chains','CH','CH'),('Ear rings','ER','ER'), ('Sets','Set','ST'),('Rings','RING','RG'), ('Yellow gold','YG','YG');
insert into #item_categories (code, name) values ('AQ.TM,PN','AQ.TM.PN'),('BL.YG.CH.ME.PN','BL.YG.CH.ME.PN'),('BS.CZ.ST.YG.PN','BS.CZ.ST.YG.PN')
;with splitted as ( -- split the codes into individual code
select row_number() over (partition by ic.code order by ic.code) as id, ic.code, x.value, cc.item
from #item_categories ic
outer apply string_split(ic.code, '.') x -- SQL Server 2016+, otherwise, use another method to split the data
left join #category_code cc on cc.code = x.value -- some values are missing in you example, but can use an inner join
)
, joined as ( -- then joined them to concat the name
select id, convert(varchar(max),code) as code, convert(varchar(max),coalesce(item + ',','')) as Item
from splitted
where id = 1
union all
select s.id, convert(varchar(max), s.code), convert(varchar(max), j.item + coalesce(s.item + ',',''))
from splitted s
inner join joined j on j.id = s.id - 1 and j.code = s.code
)
update #item_categories
set name = substring (j.item ,1,case when len(j.item) > 1 then len(j.item)-1 else 0 end)
output deleted.name, inserted.name
from #item_categories i
inner join joined j on j.code = i.code
inner join (select code, max(id)maxid from joined group by code) mj on mj.code = j.code and mj.maxid = j.id

Horizontal To Vertical Sql Server

I'm stuck with a SQL query (SQL Server) that involves converting horizontal rows to vertical rows
Below is my Query that I am trying
SELECT P AS Amount_Rs
FROM (
Select (F1.D32-F1.D20) As Profit_For_The_Period ,F3.D2 as Current_Libilities,F5.D20 As Capital_Acount,
--M1.Name As Name,
F2.D20 AS Loan_Liabilities,F4.d1 As Opening_Diff --F2.D68 As Loan,
from Folio1 As F1
--inner Join Master1 As m1 on m1.Code like '101' or m1.Code Like '102' or m1.Code Like '106' or m1.Code Like '109' or m1.Code lIke '103'
--And m1.Code=102 And m1.Code=101)
inner Join Folio1 As F2 On (F2.MasterCode=F2.MasterCode)
inner Join Folio1 As F3 On (F3.MasterCode=F3.MasterCode)
inner Join Folio1 As F4 On (F4.MasterCode=F4.MasterCode)
inner Join Folio1 As F5 On (F5.MasterCode=F5.MasterCode)
Where F1.MasterCode=109
and F2.MasterCode =106
and F3.MasterCode=103
and F4.MasterCode=102
And F5.MasterCode=101
) p UNPIVOT
( p FOR value IN
( Profit_For_The_Period,Capital_Acount, Current_Libilities, Loan_Liabilities, Opening_Diff )
) AS unvpt
Current Output:
1 12392
2 0
3 0
4 4000
5 -200
Desired Output:
1 Capital Account 12392
2 Current Assets 0
3 Current Liabilities 0
4 Loans (Liability) 4000
5 Revenue Accounts -200
Thanks !!!
I think you are looking for a pivot. Use the CASE statement with a SUM or any aggregate function in the SELECT part and a group by in the where clause, that's how I use to put rows into columns in a query when I have to in MySQL. I don't know SQL Server but I think you can do quite the same.
your conditions below
F1.MasterCode=109
and F2.MasterCode =106
and F3.MasterCode=103
and F4.MasterCode=102
And F5.MasterCode=101
shouldn't be in the the where clause but with the case in the select part
example :
select whatever,
case when F2.MasterCode =106 then sum(column_name)
end case as column_alias, (other columns) from ...
hope this could help

Split function in T-SQL

I have table with field that contains coma separated string. I whant create query thats return follow result:
I want to create query that return split text. For exapmle:
Data in table:
| ID | Names |
| ------------------------------------ | -------------------------------- |
| 63F5D993-3AC9-4EEA-8007-B669542BAD9A | John Smith,Kerry King,Tom Arraya |
Required result:
ID | Names
------------------------------------ | -----------
63F5D993-3AC9-4EEA-8007-B669542BAD9A | John Smith
------------------------------------- | -----------
63F5D993-3AC9-4EEA-8007-B669542BAD9A | Kerry King
------------------------------------- | -----------
63F5D993-3AC9-4EEA-8007-B669542BAD9A | Tom Arraya
I found "split" function for T-SQL but its works not quite right for my case. I can't execute it like this:
SELECT dbo.Split(dbo.name, ',') FROM dbo.Mytable
It can execute only follows:
SELECT * FROM dbo.Split('John Smith,Kerry King,Tom Arraya', ',')
But it does not suit me.
Also i attempted write cursor:
DECLARE #bkb varchar(256)
DECLARE #Bkb_Cursor CURSOR
SET #Bkb_Cursor = CURSOR SCROLL FOR
SELECT bkb.best_know_by
FROM [sugarcrm_cmsru_dev].[dbo].[contacts] c
left JOIN [dbo].[email_addr_bean_rel] eb
ON eb.[bean_id] = c.[id]
JOIN [dbo].[email_addresses] ea
ON ea.[id] = eb.[email_address_id]
JOIN [dbo].[contacts_cstm] ccs
ON eb.bean_id = ccs.id_c
left JOIN [dbo].[BestKnowBy$] bkb
ON c.[campaign_id] =bkb.Con_id
where c.deleted = 0;
OPEN #Bkb_Cursor;
FETCH NEXT FROM #Bkb_Cursor
INTO #bkb;
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT dbo.Splitfn(#bkb);
FETCH NEXT FROM #Bkb_Cursor INTO #bkb;
END
CLOSE #Bkb_Cursor;
DEALLOCATE #Bkb_Cursor;
GO
But it is not worked. I get error "Column "dbo" is not allowed in this context, and the user-defined function or aggregate "dbo.Splitfn" could not be found."
How can I solve this problem?
My query looks like follow:
SELECT c.[id],
bkb.best_know_by
FROM [sugarcrm_cmsru_dev].[dbo].[contacts] c
left JOIN [dbo].[email_addr_bean_rel] eb
ON eb.[bean_id] = c.[id]
JOIN [dbo].[email_addresses] ea
ON ea.[id] = eb.[email_address_id]
JOIN [dbo].[contacts_cstm] ccs
ON eb.bean_id = ccs.id_c
left JOIN [dbo].[BestKnowBy$] bkb
ON c.[campaign_id] =bkb.Con_id
where c.deleted = 0;
The bkb.best_know_by field contains comma separated string. How can i use "Cross Apply" in this case?
You need to use CROSS APPLY in combination with your table's result set which will execute the function for every row:
SELECT st.ID, spl.value
FROM SplitTest st
CROSS APPLY string_split(st.Names, ',') spl
Edit:
With regard to the addition of your query to your question, you could do the following:
;WITH CTE_Query AS (
SELECT c.[id],
bkb.best_know_by
FROM [sugarcrm_cmsru_dev].[dbo].[contacts] c
left JOIN [dbo].[email_addr_bean_rel] eb
ON eb.[bean_id] = c.[id]
JOIN [dbo].[email_addresses] ea
ON ea.[id] = eb.[email_address_id]
JOIN [dbo].[contacts_cstm] ccs
ON eb.bean_id = ccs.id_c
left JOIN [dbo].[BestKnowBy$] bkb
ON c.[campaign_id] =bkb.Con_id
where c.deleted = 0
)
SELECT cte.id, spl.value
FROM CTE_Query AS cte
CROSS APPLY string_split(cte.best_know_by, ',') spl
Cross Apply will do the trick
Select A.ID
,Names = B.Item -- << Return Field from your Split Function
From YourTable A
Cross Apply (Select * from dbo.Split(A.Names, ',') ) B
With your query
SELECT c.[id]
,S.* --<< Removed bkb.best_know_by and Replaced with S.* (don't know your Split() Return Field)
FROM [sugarcrm_cmsru_dev].[dbo].[contacts] c
LEFT JOIN [dbo].[email_addr_bean_rel] eb ON eb.[bean_id] = c.[id]
JOIN [dbo].[email_addresses] ea ON ea.[id] = eb.[email_address_id]
JOIN [dbo].[contacts_cstm] ccs ON eb.bean_id = ccs.id_c
LEFT JOIN [dbo].[BestKnowBy$] bkb ON c.[campaign_id] =bkb.Con_id
Cross Apply dbo.Split(bkb.best_know_by,',') S
where c.deleted = 0;

return column name of the maximum value in sql server 2012

My table looks like this (Totally different names)
ID Column1--Column2---Column3--------------Column30
X 0 2 6 0101 31
I want to find the second maximum value of Column1 to Column30 and Put the column_Name in a seperate column.
First row would look like :
ID Column1--Column2---Column3--------------Column30------SecondMax
X 0 2 6 0101 31 Column3
Query :
Update Table
Set SecondMax= (select Column_Name from table where ...)
with unpvt as (
select id, c, m
from T
unpivot (c for m in (c1, c2, c3, ..., c30)) as u /* <-- your list of columns */
)
update T
set SecondMax = (
select top 1 m
from unpvt as u1
where
u1.id = T.id
and u1.c < (
select max(c) from unpvt as u2 where u2.id = u1.id
)
order by c desc, m
)
I really don't like relying on top but this isn't a standard sql question anyway. And it doesn't do anything about ties other than returning the first column name by order of alphabetical sort.
You could use a modification via the condition below to get the "third maximum". (Obviously the constant 2 comes from 3 - 1.) Your version of SQL Server lets you use a variable there as well. I think SQL 2012 also supports the limit syntax if that's preferable to top. And since it should work for top 0 and top 1 as well, you might just be able to run this query in a loop to populate all of your "maximums" from first to thirty.
Once you start having ties you'll eventually get a "thirtieth maximum" that's null. Make sure you cover those cases though.
and u1.c < all (
select top 2 distinct c from unpvt as u2 where u2.id = u1.id
)
And after I think about it. If you're going to rank and update so many columns it would probably make even more sense to use a proper ranking function and do the update all at once. You'll also handle the ties a lot better even if the alphabetic sorting is still arbitrary.
with unpvt as (
select id, c, m, row_number() over (partition by id order by c desc, m) as nthmax
from T
unpivot (c for m in (c1, c2, c3, ..., c30)) as u /* <-- your list of columns */
)
update T set
FirstMax = (select c from unpvt as u where u.id = T.id and nth_max = 1),
SecondMax = (select c from unpvt as u where u.id = T.id and nth_max = 2),
...
NthMax = (select c from unpvt as u where u.id = T.id and nth_max = N)

Remove Spaces in SQL Server table data

We have the following data in table1.column1
A V John
B V S Shultz
S Hanks
K L C Gove, P S Murphy
We need to remove space between one letter words. The data after conversion should like
AV John
BVS Shultz
S Hanks
KLC Gove, PS Murphy
Is it possible to write sql query regex to clean up the whole column data once. Please guide. Thanks.
You might want to take a look at Using regular expression within a stored procedure
Is describes how to use regex in a SQL Server client.
You could also write a function using REPLACE
It is possible with T-SQL but no one would call it beautiful ...
with cNames as (
select id=cast(id as tinyint), name=cast(name as varchar(50))
from (values (1, 'A V John'), (2, 'B V S Shultz'),
(3, 'S Hanks'), (4, 'K L C Gove, P S Murphy')
) n (id, name)
),
cNumbers as (
select n=row_number() over (order by (select 1))
from (values (1),(1),(1),(1)) a (n)
cross join (values (1),(1),(1),(1)) b (n)
cross join (values (1),(1),(1),(1)) c (n)
),
cNameparts as (
select c.id, n.n, c.name,
namepart=substring(c.name,n.n,charindex(' ',c.name+' ',n.n)-n.n)
from cNames c
inner join cNumbers n
on substring(' '+c.name,n.n,1) = ' '
and n.n < len(c.name)
)
select name=
(select case when len(namepart)>1 then ' ' else '' end +
namepart +
case when right(namepart,1)=',' then ' ' else '' end
from cNameparts np
where np.id = c.id
for xml path(''))
from cNames c
order by c.id;
I was looking for an answer to a similar problem I had and this seem like it solved my issue where I had
Tom Smith
Tom Smith
Tom Smith
replace(replace(replace(NAME,' ','<>'),'><',''),'<>',' ')
will all be Tom Smith based on this link
Here's the regex you need to match the space between 2 adjacent single letters, then you'd replace that with "" (a blank):
"(?<(^| )[a-zA-Z]) (?=[a-zA-Z]( |$))"
Note that this pattern doesn't include either of the letters - it's just the space between.