Get unique comma separated values using group by in SQL - sql

My table is as below
ID | Name | City
------ | ------ | ------
1 | ABC | London, Paris
1 | ABC | Paris
1 | ABC | Japan
2 | XYZ | Delhi
2 | XYZ | Delhi, New York
My output needs to be like this:
ID | Name | City
------ | ------ | ------
1 | ABC | London, Paris, Japan
2 | XYZ | Delhi, New York
I see it as a 2 step process:
Concatenate all the unique cities for each ID and Name. Example: For ID 1 and Name ABC Cities would be London, Paris, Japan
Update the concatenated string by grouping the ID and Name.
I am able to do this for just one group, but how do I do this for all the different groups in the table?
Also, would cursors come into picture here when I want to update the string to all the rows matching the ID and Name.
Any help or idea on this would be appreciated.

You should consider normalizing your table first.
Here, you first want to convert all the comma separated values into separate rows and then, group them together using STUFF and FOR XML PATH.
with your_table (ID, name, City)
as (
select 1, 'ABC', 'London, Paris'
union all
select 1, 'ABC', 'Paris'
union all
select 1, 'ABC', 'Japan'
union all
select 2, 'XYZ', 'Delhi'
union all
select 2, 'XYZ', 'Delhi, New York'
), your_table_modified
as (
select distinct id, name, Split.a.value('.', 'varchar(100)') City
from (
select id, name, cast('<x>' + replace(City, ', ', '</x><x>') + '</x>' as xml) as x
from your_table
) t
cross apply x.nodes('/x') as Split(a)
)
select id, name, stuff((
select ', ' + city
from your_table_modified t2
where t.id = t2.id
for xml path(''), type
).value('(./text())[1]', 'varchar(max)')
, 1, 2, '')
from your_table_modified t
group by id, name;
Produces:

Try this:
select y.id,y.name,y.city from (
select id,name, case when Id=1 and name='ABC' then 'London,Paris,Japan'
when Id=2 and name='XYZ' then 'Delhi,Newyork'
end as CIty
,row_number () over (partition by id order by name asc) as rnk
from Yourtable
)y
where y.rnk=1

Related

Convert multiple rows into a single row in SQL Server

I want to change multiple rows into a single row based on Number and Type:
+------+---------+----------+-------+---------+-------+
| Name | Address | City | State | Number | Type |
+------+---------+----------+-------+---------+-------+
| XYZ | 123 Rd | New York | NY | 123D | Code1 |
| XYZ | 123 Rd | New York | NY | 56A45 | Code2 |
| XYZ | 123 Rd | New York | NY | D45256 | Code3 |
| XYZ | 123 Rd | New York | NY | 345TT | Code2 |
| XYZ | 123 Rd | New York | NY | 34561NN | Code2 |
| XYZ | 123 Rd | New York | NY | 84567YY | Code2 |
+------+---------+----------+-------+---------+-------+
Result
+------+---------+----------+-------+-------+-------+----------+-----------+----------+--------+
| Name | Address | City | State | code1 | Code2 | code2_II | Code2_III | Code2_IV | Code3 |
+------+---------+----------+-------+-------+-------+----------+-----------+----------+--------+
| XYZ | 123 Rd | New York | NY | 123D | 56A45 | 345TT | 34561NN | 84567YY | D45256 |
+------+---------+----------+-------+-------+-------+----------+-----------+----------+--------+
Query I used only work for Code2 and Code2_II but I want to bring Code2_III and Code2_IV
select
Name, Addresss, City, State,
min(Number) as Code1,
(case when min(Number) <> max(Number) then max(Number) end) as Code2
from
t
group by
Name, Addresss, City, State;
As Tim mentions in the comments to your question, this will be very difficult to do without using dynamic SQL.
This code is not exactly what you are looking for but should get you most of the way there.
CREATE TABLE #Addresses (
[Name] varchar(255),
[Address] varchar(255),
[City] varchar(255),
[State] varchar(255),
[Number] varchar(255),
[Type] varchar(255)
)
INSERT INTO #Addresses
VALUES
('XYZ','123 Rd','New York','NY','123D','Code1'),
('XYZ','123 Rd','New York','NY','56A45','Code2'),
('XYZ','123 Rd','New York','NY','D45256','Code3'),
('XYZ','123 Rd','New York','NY','345TT','Code2'),
('XYZ','123 Rd','New York','NY','34561NN','Code2'),
('XYZ','123 Rd','New York','NY','84567YY','Code2');
SELECT *
FROM (
SELECT
[Name], [Address], [City], [State], [Number]
, [Type] + '_' + LTRIM(STR(rn)) AS NewType
FROM (
SELECT *
,ROW_NUMBER() OVER(PARTITION BY [Name], [Type] ORDER BY [Number]) rn
FROM #Addresses
) a
) p
PIVOT (
MAX([Number])
FOR [NewType] IN ([Code1_1], [Code2_1], [Code2_2], [Code2_3], [Code2_4], [Code3_1])
) AS pvt
In short it is using ROW_NUMBER to figure out how many Numbers exist for a particular Type. It then uses a select statement to build a new name field called NewType.
From there it uses a PIVOT statement to break out the data into separate columns.
A few things to note.
As mentioned it is somewhat dynamic but you will have to use Dynamic SQL for the PIVOT part if you are going to run this in a larger query or have varying amount of values in the Type and Number fields. However if you know you will never have more than 4 of any code you can build the statement like this
SELECT *
FROM (
SELECT
[Name], [Address], [City], [State], [Number]
, [Type] + '_' + LTRIM(STR(rn)) AS NewType
FROM (
SELECT *
,ROW_NUMBER() OVER(PARTITION BY [Name], [Type] ORDER BY [Number]) rn
FROM #Addresses
) a
) p
PIVOT (
MAX([Number])
FOR [NewType] IN ([Code1_1], [Code1_2], [Code1_3], [Code1_4], [Code2_1], [Code2_2], [Code2_3], [Code2_4], [Code3_1], [Code3_2], [Code3_3], [Code3_4])
) AS pvt
Doing it this way will produce null fields for any code that doesn't have a value.
This will likely be slow with larger datasets.
Hope this gets you point in the right direction.
You can use conditional aggregation:
select Name, Addresss, City, State,
min(case when type = 'Code1' then number) as code1,
min(case when type = 'Code2' and seqnum = 1 then number) as code2,
min(case when type = 'Code2' and seqnum = 2 then number) as code2_II,
min(case when type = 'Code2' and seqnum = 3 then number) as code2_III,
min(case when type = 'Code2' and seqnum = 4 then number) as code2_IV,
min(case when type = 'Code3' and seqnum = 1 then number) as code3
from (select t.*,
row_number() over (partition by Name, Addresss, City, State, type order by type) as seqnum
from t
) t
group by Name, Addresss, City, State;
Note: This returns exactly six code columns. That seems to be what you expect. If you want a dynamic number of columns, you need to use dynamic SQL.
SELECT DISTINCT Name,
Address,
City,
STATE,
STUFF((
SELECT ',' + [tCode1].Number
FROM #TestTable [tCode1]
WHERE [tCode1].Type = 'Code1'
FOR XML PATH('')
), 1, 1, N'') [Code1],
STUFF((
SELECT ',' + [tCode2].Number
FROM #TestTable [tCode2]
WHERE [tCode2].Type = 'Code2'
FOR XML PATH('')
), 1, 1, N'') [Code2],
STUFF((
SELECT ',' + [tCode3].Number
FROM #TestTable [tCode3]
WHERE [tCode3].Type = 'Code3'
FOR XML PATH('')
), 1, 1, N'') [Code3]
FROM #TestTable;
I referenced this https://www.mssqltips.com/sqlservertip/2914/rolling-up-multiple-rows-into-a-single-row-and-column-for-sql-server-data/
To Rolling multiple rows into one
Result:
+------+---------+----------+-------+-------+-----------------------------+---------+
| Name | Address | City | State | Code1 | Code2 | Code3 |
+------+---------+----------+-------+-------+-----------------------------+---------+
| XYZ | 123 Rd | New York | NY | 123D | 56A45,345TT,34561NN,84567YY | D45256 |
+------+---------+----------+-------+-------+-----------------------------+---------+

SQL Server - How to PIVOT multi value of same key

How to PIVOT a column which have multi values with same Key.
For instance, we have table
ID FieldName FieldValue
1 Name Jack
1 Country Auz
1 PostCode 1234
1 Name Jhon
2 Name Leo
2 Country USA
2 PostCode 2345
I want to get the result table after pivot:
ID Name Country PostCode
1 Jack Auz 1234
1 Jhon Auz 1234
2 Leo USA 2345
I have used code with pivot function:
SELECT *
FROM (
SELECT
ID, FieldName, FieldValue
FROM Table
) AS p
PIVOT
(
MAX(FieldValue)
FOR FieldName IN ([Name], [Country], [PostCode])
) AS pvt
From the above code, The result table will only get one record for ID '1', I think this may due to the function of 'MAX' used. Is there any way to show two records by using any other function to solve it?
You have an unusual situation in your data because ID 1 has 2 names associated with it, and you want both to share the other attributes such as postcode etc. For this you can use an apply operator to return 1 or more names for each ID, and then group by the combination of id and name.
SELECT
id
, Name
, MAX(CASE WHEN fieldname = 'Country' THEN fieldvalue END) AS Country
, MAX(CASE WHEN fieldname = 'Postcode' THEN fieldvalue END) AS Postcode
FROM (
SELECT
t.*, ca.Name
FROM mytable AS t
CROSS APPLY (
SELECT
FieldValue AS Name
FROM mytable AS t2
WHERE t.id = t2.id
AND t2.FieldName = 'Name'
) AS ca
WHERE FieldName IN ('Country','PostCode')
) t
GROUP BY
id
, name
;
also see: https://rextester.com/GKOK78960
result:
+----+----+------+---------+----------+
| | id | Name | Country | Postcode |
+----+----+------+---------+----------+
| 1 | 1 | Jack | Auz | 1234 |
| 2 | 1 | Jhon | Auz | 1234 |
| 3 | 2 | Leo | USA | 2345 |
+----+----+------+---------+----------+
note that using cross apply is a bit like an inner join; if there is no name associated to an id, that id will not be returned by the query above. If there is a need for an id without name use outer apply instead (as this is similar to left join).
As an alternative, you could use pivot like so:
SELECT id, name, [Country], [PostCode]
FROM (
SELECT
t.*, ca.Name
FROM mytable AS t
CROSS APPLY (
SELECT
FieldValue AS Name
FROM mytable AS t2
WHERE t.id = t2.id
AND t2.FieldName = 'Name'
) AS ca
WHERE FieldName IN ('Country','PostCode')
) AS p
PIVOT
(
MAX(FieldValue)
FOR FieldName IN ([Country], [PostCode])
) AS pvt

Create a column where parent items are listed with their respective children under them

I have created 2 tables as an example.
Table1:
Gender Table
ID | Gender
---------------------
0000-0000 | MALE
0000-0001 | FEMALE
Table2:
Table
ID | PARENTID | NAME
----------------------
1 | 0000-0001 | Apple
2 | 0000-0000 | Bob
3 | 0000-0000 | Chris
4 | 0000-0000 | Dan
5 | 0000-0001 | Elsa
The PARENTID of table2 refers to the IDs of Table1.
From that we can see that Apple is Female and Bob is Male.
However, I need the output to look like this.
List
-----
MALE
Bob
Chris
Dan
FEMALE
Apple
Elsa
Is this possible at all?
Try this way:
DECLARE #sql nvarchar(MAX)='';
SELECT #sql += 'SELECT '+QUOTENAME(Gender, '''')
+' List UNION ALL SELECT NAME FROM Table2 WHERE ParentId='+QUOTENAME(ID, '''')
+char(13)+char(10)+'UNION ALL'+char(13)+char(10)
FROM Table1
SET #sql = SUBSTRING(#sql, 1, LEN(#sql)-13)
EXEC(#sql)
Do a UNION ALL, add an extra column t to tell which table is selected.
Order the result:
select list
from
(
select ID, 1 as t, Gender as list
from table1
union all
select PARENTID, 2 as t, NAME
from table2
)
order by id, t, list
Use UNION ALL to combine the results:
select name
from
(1 as SortOrder, 'Male' as name
+
2, male names (apply WHERE clause)
+
3, 'Female'
+
4, female names(apply WHERE clause)
) Q
order by SortOrder
UPDATE (universal solution):
Assuming:
*you have ID in Gender table, and that ID is in desired order (otherwise you need to add some int column to specify the order)
*assuming Min ID in Name table is not negative
select Q.Name
from
(select g.GenderID as GenderSortOrder, -1 as NameSortOrder, GenderName as Name
from #gender g
union all
select g.GenderID, t.ID, t.Name
from #gender g
inner join #name t on t.GenderID = g.GenderID) Q
order by Q.GenderSortOrder, Q.NameSortOrder

How to get the column name of a value in PostgreSQL?

Suppose we have 3 tables:
Table1:
ID FrenchCity
1 Paris
2 Lille
3 Lyon
Table2:
ID IntlCity
1 Lille
2 Geneva
3 Toulouse
Table3:
ID BritishCity
1 London
2 Leeds
I would like to get the column name correspondent with a value.
For instance, I give a value Lille and SQL should return Table1.FrenchCity Table2.IntlCity.
As I said, I would like to get the column name of a value. So Lille exists in 2 tables, I would like SQL to return the {{table name}}.{{column name}}
How to write a query to do that?
This work for you ?
SELECT 'Table1.FrenchCity' as fieldName
FROM Table1
WHERE FrenchCity = 'Lille'
UNION ALL
SELECT 'Table2.IntlCity' as fieldName
FROM Table2
WHERE IntlCity = 'Lille'
UNION ALL
SELECT 'Table3.BritishCity' as fieldName
FROM Table3
WHERE BritishCity = 'Lille'
Then you can use array_agg
SELECT array_agg(fieldName)
FROM (
previous union query
) t
you better create one table with 3 columns:
ID COUNTRY fieldName CITY
1 France FrenchCity Paris
2 France FrenchCity Lille
3 France FrenchCity Lyon
4 Intl IntlCity Lille
5 Intl IntlCity Geneva
6 Intl IntlCity Toulouse
ect.
then use query:
SELECT country || '.' || fieldName
FROM three_col_table
WHERE CITY = 'Lille'
If you don't wont to use DB metadata then you can to convert table data into the series of (column_name, column_value) pairs using row_to_json and json_each_text functions:
with
-- Demo data start
Table1(ID, FrenchCity) as (values
(1, 'Paris'),
(2, 'Lille'),
(3, 'Lyon')),
Table2(ID, IntlCity) as (values
(1, 'Lille'),
(2, 'Geneva'),
(3, 'Toulouse')),
-- Demo data end
my_data as (
select 'Table1' as tbl, j.*
from Table1 as t, json_each_text(row_to_json(t.*)) as j(fld,val)
union all
select 'Table2' as tbl, j.*
from Table2 as t, json_each_text(row_to_json(t.*)) as j(fld,val)
-- ... and so on ...
)
select *, format('%s.%s', tbl, fld) as desirede_value from my_data
where val ilike 'lille';
tbl | fld | val | desirede_value
--------+------------+-------+-------------------
Table1 | frenchcity | Lille | Table1.frenchcity
Table2 | intlcity | Lille | Table2.intlcity
(2 rows)

Data Matching with SQL and assigning Identity ID's

How to write a query that will match data and produce and identity for it.
For Example:
RecordID | Name
1 | John
2 | John
3 | Smith
4 | Smith
5 | Smith
6 | Carl
I want a query which will assign an identity after matching exactly on Name.
Expected Output:
RecordID | Name | ID
1 | John | 1X
2 | John | 1X
3 | Smith | 1Y
4 | Smith | 1Y
5 | Smith | 1Y
6 | Carl | 1Z
Note: The ID should be unique for every match. Also, it can be numbers or varchar.
Can somebody help me with this? The main thing is to assign the ID's.
Thanks.
How about this:
with temp as
(
select 1 as id,'John' as name
union
select 2,'John'
union
select 3,'Smith'
union
select 4,'Smith'
union
select 5,'Smith'
union
select 6,'Carl'
)
SELECT *, DENSE_RANK() OVER
(ORDER BY Name) as NewId
FROM TEMP
Order by id
The first part is for testing purposes only.
Please try:
SELECT *,
Rank() over (order by Name ASC)
FROM table
This structure seems to work:
CREATE TABLE #Table
(
Department VARCHAR(100),
Name VARCHAR(100)
);
INSERT INTO #Table VALUES
('Sales','michaeljackson'),
('Sales','michaeljackson'),
('Sales','jim'),
('Sales','jim'),
('Sales','jill'),
('Sales','jill'),
('Sales','jill'),
('Sales','j');
WITH Cte_Rank AS
(
SELECT [Name],
rw = ROW_NUMBER() OVER (ORDER BY [Name])
FROM #Table
GROUP BY [Name]
)
SELECT a.Department,
a.Name,
b.rw
FROM #Table a
INNER JOIN Cte_Rank b
ON a.Name = b.Name;