How to count number of unique values in N columns - sql

How to verify the number of unique values ​​in the columns? For example I have a table:
Shop_1
Shop_2
Shop_3
Shop_4
Adidas
Nike
Adidas
Reebok
Nike
Adidas
Asics
Ascics
Asics
Asics
Asics
Nike
Nike
Nike
Adidas
For this table, I would like to have an additional column with information on how many unique stores appeared in a given record. The results should be as follows:
First row: 2 (because there was Nike and Adidas)
Second row: 4
Third row: 1 (there were 4 shops but all Asics)
Fourth row: 2
CREATE TABLE shops
(ID INTEGER PRIMARY KEY,
shop1 CHAR(20),
shop2 CHAR(20),
shop3 CHAR(20),
shop4 CHAR(20),
expected_result INT )
INSERT INTO shops VALUES (1, 'Adidas', 'Nike', 'Adidas', null, 2);
INSERT INTO shops VALUES (2, 'Reebok', 'Nike', 'Adidas', 'Asics', 4);
INSERT INTO shops VALUES (3, 'Asics', 'Asics', 'Asics', 'Asics', 1);
INSERT INTO shops VALUES (4, 'Nike', 'Nike', 'Nike', 'Adidas', 2);

PLease try the following method.
SQL #1
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, Shop_1 VARCHAR(20), Shop_2 VARCHAR(20), Shop_3 VARCHAR(20), Shop_4 VARCHAR(20));
INSERT INTO #tbl (Shop_1, Shop_2, Shop_3, Shop_4) VALUES
('Adidas', 'Nike', 'Adidas', NULL),
('Reebok', 'Nike', 'Adidas', 'Asics'),
('Asics', 'Asics', 'Asics', 'Asics'),
('Nike', 'Nike', 'Nike', 'Adidas');
-- DDL and sample data population, end
SELECT *
, (
SELECT Shop_1, Shop_2, Shop_3, Shop_4
FOR XML PATH(''), TYPE, ROOT('root')
)
.value('count(distinct-values(/root/*/text()))','INT') AS [Counter]
FROM #tbl AS p;
SQL #2
It allows to handle a scenario where column list is vary:
Shop_1, Shop_2, ..., ShopN.
SELECT *
, (
SELECT *
FROM #tbl AS c
WHERE c.ID = p.ID
FOR XML PATH(''), TYPE, ROOT('root')
)
.value('count(distinct-values(/root/*[local-name()!="ID"]/text()))','INT') AS [UniqueCounter]
FROM #tbl AS p;
Output
+----+--------+--------+--------+--------+---------+
| ID | Shop_1 | Shop_2 | Shop_3 | Shop_4 | Counter |
+----+--------+--------+--------+--------+---------+
| 1 | Adidas | Nike | Adidas | NULL | 2 |
| 2 | Reebok | Nike | Adidas | Asics | 4 |
| 3 | Asics | Asics | Asics | Asics | 1 |
| 4 | Nike | Nike | Nike | Adidas | 2 |
+----+--------+--------+--------+--------+---------+

One way you can do this would be to use cross apply to pivot and then count
select *
from #t
cross apply (
select Count (distinct shops) UniqueCount
from (
values (shop_1),(shop_2),(shop_3),(shop_4)
)x(shops)
)a
DB Fiddle

A dynamical solution might be by using a catalog table(information_schema.columns) such as
DECLARE #cols1 AS NVARCHAR(MAX), #cols2 AS NVARCHAR(MAX), #query AS NVARCHAR(MAX)
SET #cols1 = ( SELECT STRING_AGG(QUOTENAME([column_name]),',')
FROM information_schema.columns
WHERE [column_name] != 'ID'
AND [table_name]='shops');
SET #cols2 = ( SELECT STRING_AGG('COUNT('+QUOTENAME([column_name])+')
OVER (PARTITION BY ID ORDER BY ID)','+') AS total
FROM information_schema.columns
WHERE [column_name] != 'ID');
SET #query = N'SELECT [ID], '+ #cols1 +',' + #cols2 +
N' FROM [shops]';
--SELECT #query;
EXEC sp_executesql #query;
Demo

Just count the distinct values for the columns.
SELECT
COUNT(DISTINCT shop_1) count1,
COUNT(DISTINCT shop_2) count2,
COUNT(DISTINCT shop_3) count3,
COUNT(DISTINCT shop_4) count4
FROM
your_table

Related

How do I select all employees and display them in rows per department?

Below is an example of the data I want to manipulate in my select statement.
Employee ID | Department ID | FirstName | LastName | Role
----------------------------------------------------------------
1 | 1 | Jack | Skeleton | Cashier
2 | 2 | Rachel | Sparrow | Cashier
3 | 1 | Samuel | Kite | Bagger
4 | 2 | Arnold | Herrera | Bagger
5 | 1 | Edwin | Molina | Bagger
Below is how I want to display my data.
Dept ID| Emp ID | Role | Emp ID | Role | EmpID | Role |
-----------------------------------------------------------------
1 | 1 |Cashier | 3 |Bagger | 5 | Bagger|
2 | 2 |Cashier | 4 |Bagger |
I have tried a Pivot, but selecting the same columns for each employee in the same row has been an issue for me.
Also I wanted to note that each department has a different number of employees. So, some rows would show 6 employees for that department and others would display 4 or 9
This should get you what you're looking for.
SET NOCOUNT ON;
GO
--================================================================
-- create some test data...
IF OBJECT_ID('tempdb..#Department', 'U') IS NOT NULL
BEGIN DROP TABLE #Department; END;
CREATE TABLE #Department (
DepartmentID INT NOT NULL PRIMARY KEY,
DepartmentName VARCHAR(50) NOT NULL
);
INSERT #Department (DepartmentID, DepartmentName) VALUES
(1, 'Department 1'), (2, 'Department 2');
IF OBJECT_ID('tempdb..#Employee', 'U') IS NOT NULL
BEGIN DROP TABLE #Employee; END;
CREATE TABLE #Employee (
EmployeeID INT NOT NULL PRIMARY KEY,
DepartmentID INT NOT NULL,
FirstName VARCHAR(20) NOT NULL,
LastName VARCHAR(20) NOT NULL,
[Role] VARCHAR(20) NOT NULL
);
INSERT #Employee (EmployeeID, DepartmentID, FirstName, LastName, [Role]) VALUES
(1, 1, 'Jack', 'Skeleton', 'Cashier'),
(2, 2, 'Rachel', 'Sparrow', 'Cashier'),
(3, 1, 'Samuel', 'Kite', 'Bagger'),
(4, 2, 'Arnold', 'Herrera', 'Bagger'),
(5, 1, 'Edwin', 'Molina', 'Bagger');
--================================================================
-- begin the actual solution...
DECLARE
#sql NVARCHAR(MAX) = N'',
#debug BIT = 0; -- choose 0 the execute the dynamic sql & 1 to print it.
WITH
cte_columns_needed AS (
SELECT DISTINCT
rn = ROW_NUMBER() OVER (PARTITION BY e.DepartmentID ORDER BY e.EmployeeID)
FROM
#Employee e
)
SELECT TOP (10000)
#sql = CONCAT(#sql, N',
', QUOTENAME(N'EmpID_' + x.rn), N' = MAX(CASE WHEN ern.rn = ', cn.rn, N' THEN ern.EmployeeID END),
', QUOTENAME(N'Role_' + x.rn), N' = MAX(CASE WHEN ern.rn = ', cn.rn, N' THEN ern.Role END)'
)
FROM
cte_columns_needed cn
CROSS APPLY ( VALUES (CONVERT(NVARCHAR(10), cn.rn)) ) x (rn)
ORDER BY
cn.rn;
SET #sql = CONCAT(N'
SELECT
[DeptID] = ern.DepartmentID',
#sql, N'
FROM (
SELECT
e.EmployeeID,
e.DepartmentID,
e.Role,
rn = ROW_NUMBER() OVER (PARTITION BY e.DepartmentID ORDER BY e.EmployeeID)
FROM
#Employee e
) ern
GROUP BY
ern.DepartmentID
ORDER BY
ern.DepartmentID;'
);
IF #debug = 1
BEGIN
PRINT(#sql);
END;
ELSE
BEGIN
EXEC sys.sp_executesql #sql;
END;
Note... This type of dynamic code isn't exactly intuitive for most people. So, please feel free to ask whatever questions come up.

Splitting dynamically SQL columns into multiple columns based on a different column value

I am trying to convert dynamically a table like this:
+----+---------+-------+
| ID | Subject | Users |
+----+---------+-------+
| 1 | Hi! | Anna |
| 2 | Hi! | Peter |
| 3 | Try | Jan |
| 4 | Try | Peter |
| 5 | Try | Jan |
| 6 | Problem | Anna |
| 7 | Problem | José |
| 8 | Test | John |
| 9 | Test | John |
| 10 | Hi! | Anna |
| 11 | Hi! | José |
| 12 | Hi! | Anna |
| 13 | Hi! | Joe |
+----+---------+-------+
Into something like that:
+----+---------+-------+-------+-------+-------+
| ID | Subject | User1 | User2 | User3 | User4 |
+----+---------+-------+-------+-------+-------+
| 1 | Hi! | Anna | Peter | José | NULL |
| 2 | Try | Jan | Peter | NULL | NULL |
| 3 | Problem | Anna | José | NULL | NULL |
| 4 | Test | John | NULL | NULL | NULL |
+----+---------+-------+-------+-------+-------+
I have been reading the following links, but they are thought for splitting a column into a predefined number of columns:
Splitting SQL Columns into Multiple Columns Based on Specific Column Value
Split column into two columns based on type code in third column
I would need to split it dinamically depending on the content of the table.
SQL:
--【Build Test Data】
create table #Tem_Table ([ID] int,[Subject] nvarchar(20),[Users] nvarchar(20));
insert into #Tem_Table ([ID],[Subject] ,[Users]) values
('1','Hi!','Anna')
,('2','Hi!','Peter')
,('3','Try','Jan')
,('4','Try','Peter')
,('5','Try','Jan')
,('6','Problem','Anna')
,('7','Problem','José')
,('7','Test','John')
,('9','Test','John')
,('10','Hi! ','Anna')
,('11','Hi! ','José')
,('12','Hi! ','Anna')
,('13','Hi! ','Joe')
;
--STEP 1 distinct and ROW_NUMBER
with distinct_table as (
select [Subject],[Users]
,ROW_NUMBER() OVER (PARTITION BY [Subject] order by [Users]) [rank]
from (
select distinct [Subject],[Users] from #Tem_Table
) T00
)
--STEP 2 Group by row_count
,group_table as (
select [Subject]
from distinct_table T
group by [Subject]
)
--STEP 3 Use Left Join and Rank
select
T.[Subject],T1.[Users] as User1, T2.[Users] as User2 , T3.[Users] as User3, T4.[Users] as User4
from group_table T
left join distinct_table T1 on T.[Subject] = T1.[Subject] and T1.[rank] = 1
left join distinct_table T2 on T.[Subject] = T2.[Subject] and T2.[rank] = 2
left join distinct_table T3 on T.[Subject] = T3.[Subject] and T3.[rank] = 3
left join distinct_table T4 on T.[Subject] = T4.[Subject] and T4.[rank] = 4
order by [Subject];
result:
-------------------- -------------------- -------------------- -------------------- --------------------
Hi! Anna Joe José Peter
Problem Anna José NULL NULL
Test John NULL NULL NULL
Try Jan Peter NULL NULL
Update the Dynamic version :
--STEP 1 distinct and ROW_NUMBER
SELECT * into #distinct_table from (
select [Subject],[Users]
,ROW_NUMBER() OVER (PARTITION BY [Subject] order by [Users]) [rank]
from (
select distinct [Subject],[Users] from #Tem_Table
) T00
)T;
--STEP 2 Group by row_count
SELECT * into #group_table from (
select [Subject] ,count(1) [count]
from #distinct_table T
group by [Subject]
)T;
--Use Exec
DECLARE #select_sql AS NVARCHAR(MAX) = ' select T.[Subject] ',
#join_sql AS NVARCHAR(MAX) = ' from #group_table T ',
#max_count INT = (SELECT max([count]) FROM #group_table),
#temp_string NVARCHAR(5),
#temp_string_addone NVARCHAR(5)
;
DECLARE #index int = 0 ;
WHILE #index < #max_count
BEGIN
sELECT #temp_string = Convert(nvarchar(10),#index);
sELECT #temp_string_addone = Convert(nvarchar(10),#index+1);
select #select_sql = #select_sql + ' , T'+#temp_string_addone+'.[Users] as User'+#temp_string_addone+' '
select #join_sql = #join_sql + 'left join #distinct_table T'+#temp_string_addone+' on T.[Subject] = T'+#temp_string_addone+'.[Subject] and T'+#temp_string_addone+'.[rank] = '+#temp_string_addone+' ';
SET #index = #index + 1;
END;
EXEC (#select_sql
+ #join_sql
+' order by [Subject]; ')
;
CREATE TABLE mytable
([ID] int, [Subject] varchar(7), [Users] varchar(5))
;
INSERT INTO mytable
([ID], [Subject], [Users])
VALUES
(1, 'Hi!', 'Anna'),
(2, 'Hi!', 'Peter'),
(3, 'Try', 'Jan'),
(4, 'Try', 'Peter'),
(5, 'Try', 'Jan'),
(6, 'Problem', 'Anna'),
(7, 'Problem', 'José'),
(8, 'Test', 'John'),
(9, 'Test', 'John'),
(10, 'Hi!', 'Anna'),
(11, 'Hi!', 'José'),
(12, 'Hi!', 'Anna'),
(13, 'Hi!', 'Joe')
;
select distinct subject,
(select users from (
select distinct users from mytable where subject=m.subject) a order by users offset 0 rows fetch next 1 row only) user1,
(select users from (
select distinct users from mytable where subject=m.subject) a order by users offset 1 rows fetch next 1 row only) user2,
(select users from (
select distinct users from mytable where subject=m.subject) a order by users offset 2 rows fetch next 1 row only) user3,
(select users from (
select distinct users from mytable where subject=m.subject) a order by users offset 3 rows fetch next 1 row only) user4
from mytable m
you can use below dynamic query to get the result-
create table test_Raw(ID int ,Subject varchar(100), Users varchar(100))
insert into test_Raw
values (1,' Hi!','Anna'),
(2,' Hi!','Peter'),
(3,'Try','Jan'),
(4,'Try','Peter'),
(5,'Try','Jan'),
(6,'Problem','Anna'),
(7,'Problem','José'),
(8,'Test','John'),
(9,'Test','John'),
(10,' Hi!','Anna'),
(11,' Hi!','José'),
(12,' Hi!','Anna'),
(13,' Hi!','Joe')
--select * from test_Raw
select dense_RANK() over( order by Subject) Ranking1, dense_RANK() over(partition by Subject order by users) Ranking2 , Subject , Users
into test
from test_Raw
group by Subject , Users
order by 3
declare #min int , #mx int , #Select nvarchar(max) , #from nvarchar(max) , #vmin varchar(3)
select #min= 1 , #mx = MAX(Ranking2) , #Select= 'select ' , #from = ' from test t1 ' , #vmin = '' from test
while (#min<=#mx)
begin
select #vmin = CAST(#min as varchar(3))
select #Select = #Select + CASE WHEN #min = 1 THEN 't1.Ranking1 as ID , t1.Subject , t1.Users AS User1 ' ELSE ',t' +#vmin+'.Users as User'+#vmin END
select #from = #from + CASE WHEN #min = 1 THEN '' ELSE ' left join test t'+#vmin + ' on t1.Ranking1 = t' + #vmin + '.Ranking1 and t1.Ranking2 + ' + cast (#min-1 as varchar(10)) + ' = t'+#vmin+'.Ranking2' END
set #min = #min + 1
end
select #Select = #Select + #from + ' where t1.Ranking2 = 1'
exec sp_executesql #Select

MS SQL Group rows based on start and end

I have a table that looks like this with repeating rows of 3 and 3...
Column1 | Column2
CustomerID | 22
CustomerName | ”ABC”
Responsible | ”Allan”
CustomerID | 23
CustomerName | ”DEF”
Responsible | ”Jessica”
CustomerID | 24
CustomerName | ”GHI”
Responsible | ”Paul”
The following script can be used to create the table and populate it with sample data...
CREATE TABLE Responsible
( [ RowType ] VARCHAR(12),
[ Value ] VARCHAR(9) )
;
INSERT INTO Responsible
( [RowType],
[ Value ] )
VALUES
( 'CustomerID',
'22' ),
( 'CustomerName',
'ABC'),
( 'Responsible',
'Allan' ),
( 'CustomerID',
'23' ),
( 'CustomerName',
'DEF' ),
( 'Responsible',
'Jessica' ),
( 'CustomerID',
'24' ),
( 'CustomerName',
'GHI' ),
( 'Responsible',
'Paul' );
And I would like to get it like a table that looks like this:
CustomerID | CustomerName | Responsible
22 | ABC | Allan
23 | DEF | Jessica
24 | GHI | Paul
What is the best way forward?
I got it to work like this in SQL Server. I don't see any other option, but to use a cursor to go down one row at a time. The script below works only in your unique situation.
Create the new table
USE [YOURDATABASE NAME GOES HERE]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Table_2](
[CustomerID] [int] NULL,
[CustomerName] [varchar](50) NULL,
[Responsible] [varchar](50) NULL
) ON [PRIMARY]
GO
Insert Data Into Table
DECLARE #CustomerId INT
DECLARE #CustomerName VARCHAR(50)
DECLARE #Responsible VARCHAR(50)
DECLARE myCursor CURSOR
FOR SELECT Column2 FROM Table_1;
OPEN myCursor;
FETCH NEXT FROM myCursor
INTO #CustomerId;
FETCH NEXT FROM myCursor
INTO #CustomerName;
FETCH NEXT FROM myCursor
INTO #Responsible;
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO [dbo].[Table_2](CustomerID,CustomerName,Responsible)
VALUES (#CustomerId,#CustomerName,#Responsible)
FETCH NEXT FROM myCursor
INTO #CustomerId;
FETCH NEXT FROM myCursor
INTO #CustomerName;
FETCH NEXT FROM myCursor
INTO #Responsible;
END
CLOSE myCursor;
DEALLOCATE myCursor;
GO
edit, SQL server version :
Query 10:
select CustomerID, CustomerName, Responsible
from (
select row_number() over(order by k) as id , v as CustomerID
from t1
where k ='CustomerID') tt1
inner join (
select row_number() over(order by k) as id , v as CustomerName
from t1
where k ='CustomerName') tt2
on tt1.id = tt2.id
inner join (
select row_number() over(order by k) as id , v as Responsible
from t1
where k ='Responsible') tt3
on tt1.id = tt3.id
Results:
| CustomerID | CustomerName | Responsible |
|------------|--------------|-------------|
| 22 | ABC | Allan |
| 23 | DEF | Jessica |
| 24 | GHI | Paul |
This is what you want I think ?
SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE t1
(`k` varchar(12), `v` varchar(9))
;
INSERT INTO t1
(`k`, `v`)
VALUES
('CustomerID', '22'),
('CustomerName', 'ABC'),
('Responsible', 'Allan'),
('CustomerID', '23'),
('CustomerName', 'DEF'),
('Responsible', 'Jessica'),
('CustomerID', '24'),
('CustomerName', 'GHI'),
('Responsible', 'Paul')
;
Query 1:
set #v1 = 0, #v2 = 0, #v3 = 0
Query 2:
select CustomerID, CustomerName, Responsible
from (
select #v1:= #v1+1 as id , v as CustomerID
from t1
where k ='CustomerID'
) tt1
inner join (
select #v2:= #v2+1 as id , v as CustomerName
from t1
where k ='CustomerName'
) tt2
on tt1.id = tt2.id
inner join (
select #v3:= #v3+1 as id , v as Responsible
from t1
where k ='Responsible'
) tt3
on tt1.id = tt3.id;
Results:
| CustomerID | CustomerName | Responsible |
|------------|--------------|-------------|
| 22 | ABC | Allan |
| 23 | DEF | Jessica |
| 24 | GHI | Paul |

How do I group pivot results in SQL?

I have been able to combine results using the pivot function. Been trying various examples on my solution to group these results together. Am I doing this wrong or is it a simple fix?
create table DBE_LOCATION
(
REF int,
STATUS varchar(1)
);
insert into DBE_LOCATION values
(1, 'A'),
(2, 'A');
create table SYS_SCREEN_FIELD
(
REF int,
FIELD_DISPLAY varchar(20),
ORDER_BY int
);
insert into SYS_SCREEN_FIELD values
(1, 'Location Name', 0),
(2, 'Address', 1),
(3, 'Suburb', 2),
(4, 'Postcode', 3),
(5, 'State', 4),
(6, 'Country', 5);
create table DBE_LOCATION_DATA
(
REF int,
FIELD_REF int,
LOCATION_REF int,
VALUE_TEXT_FIELD varchar(MAX)
);
insert into DBE_LOCATION_DATA values
(1, 1, 1, 'New York'),
(2, 1, 2, 'Japan'),
(3, 2, 1, '123 Address St'),
(4, 2, 2, '456 Address St');
Now the final thing would be to show a result set of each Location with the field display as the column name. Something like this if using the above example:
Ref Location Name Address Status
1 New York 123 Address St A
2 Japan 456 Address Ave A
Have got the following working in gathering the data and creating the dynamic columns:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(FIELD_DISPLAY)
from SYS_SCREEN_FIELD
group by FIELD_DISPLAY, ORDER_BY
order by ORDER_BY
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT REF, ' + #cols + ', STATUS from
(
select l.REF, l.STATUS,
f.FIELD_DISPLAY,
d.FIELD_REF, d.VALUE_TEXT_FIELD
from DBE_LOCATION l
right join DBE_LOCATION_DATA d
on l.REF = d.LOCATION_REF
inner join SYS_SCREEN_FIELD f
on d.FIELD_REF = f.REF
) x
pivot
(
max(VALUE_TEXT_FIELD)
for FIELD_DISPLAY in (' + #cols + ')
) p'
execute(#query)
Results are not grouped by REF. How is this done?
SQL Fiddle Link
The problem is with the addition of the column FIELD_REF in your subquery. Even though you are not including this column in your final select list, since it is in your subquery the column is used during the grouping of the PIVOT.
You can see the issue if you include it in your final select, you get a result:
| REF | FIELD_REF | LOCATION NAME | ADDRESS | SUBURB | POSTCODE | STATE | COUNTRY | STATUS |
|-----|-----------|---------------|----------------|--------|----------|--------|---------|--------|
| 1 | 1 | Adelaide | (null) | (null) | (null) | (null) | (null) | S |
| 1 | 2 | (null) | 1 Adelaide St | (null) | (null) | (null) | (null) | S |
The FIELD_REF has a value of 1 and 2 for REF=1, when the aggregate and group by are applied, you will return multiple rows.
If you remove this column from your subquery you will get the result that you want:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(FIELD_DISPLAY)
from SYS_SCREEN_FIELD
group by FIELD_DISPLAY, ORDER_BY
order by ORDER_BY
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT REF, ' + #cols + ', STATUS from
(
select l.REF, l.STATUS,
f.FIELD_DISPLAY,
d.VALUE_TEXT_FIELD
from DBE_LOCATION l
right join DBE_LOCATION_DATA d
on l.REF = d.LOCATION_REF
inner join SYS_SCREEN_FIELD f
on d.FIELD_REF = f.REF
) x
pivot
(
max(VALUE_TEXT_FIELD)
for FIELD_DISPLAY in (' + #cols + ')
) p'
execute sp_executesql #query;
See SQL Fiddle with Demo. Now your final result is:
| REF | LOCATION NAME | ADDRESS | SUBURB | POSTCODE | STATE | COUNTRY | STATUS |
|-----|---------------|----------------|--------|----------|--------|---------|--------|
| 1 | Adelaide | 1 Adelaide St | (null) | (null) | (null) | (null) | S |
| 2 | Melbourne | 2 Melbourne St | (null) | (null) | (null) | (null) | S |

Use Dyamic Pivot query for this?

i have the below table. (no primary key in this table)
ID | IC | Name | UGCOS | MCOS
---------------------------------------------------------
1AA | A123456B | Edmund | Australia | Denmark
1AA | A123456B | Edmund | Australia | France
2CS | C435664C | Grace | Norway | NULL
3TG | G885595H | Rae | NULL | Japan
I need to get the result like this.
ID | IC | Name | UGCOS | MCOS | MCOS1
--------------------------------------------------------------------
1AA | A123456B | Edmund | Australia | Denmark | France
2CS | C435664C | Grace | Norway | NULL | NULL
3TG | G885595H | Rae | NULL | Japan | NULL
Did googled around and seems like PIVOT is what i need to do that. However i am not sure how can that be implemented to my tables. It would be great help if somebody can help me with it. Thanks!
I'll create a second answer, as this approach is something completely different from my first:
This dynamic query will first find the max count of a distinct ID and then build a dynamic pivot
CREATE TABLE #tmpTbl (ID VARCHAR(100),IC VARCHAR(100),Name VARCHAR(100),UGCOS VARCHAR(100),MCOS VARCHAR(100))
INSERT INTO #tmpTbl VALUES
('1AA','A123456B','Edmund','Australia','Denmark')
,('1AA','A123456B','Edmund','Australia','France')
,('1AA','A123456B','Edmund','Australia','OneMore')
,('2CS','C435664C','Grace','Norway',NULL)
,('3TG','G885595H','Rae',NULL,'Japan');
GO
DECLARE #maxCount INT=(SELECT TOP 1 COUNT(*) FROM #tmpTbl GROUP BY ID ORDER BY COUNT(ID) DESC);
DECLARE #colNames VARCHAR(MAX)=
(
STUFF
(
(
SELECT TOP(#maxCount)
',MCOS' + CAST(ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS VARCHAR(10))
FROM sys.objects --take any large table or - better! - an numbers table or a tally CTE
FOR XML PATH('')
),1,1,''
)
);
DECLARE #cmd VARCHAR(MAX)=
'SELECT p.*
FROM
(
SELECT *
,''MCOS'' + CAST(ROW_NUMBER() OVER(PARTITION BY ID ORDER BY (SELECT NULL)) AS VARCHAR(10)) AS colName
FROM #tmpTbl
) AS tbl
PIVOT
(
MIN(MCOS) FOR colName IN(' + #colNames + ')
) AS p';
EXEC(#cmd);
GO
DROP TABLE #tmpTbl;
The result
1AA A123456B Edmund Australia Denmark France OneMore
2CS C435664C Grace Norway NULL NULL NULL
3TG G885595H Rae NULL Japan NULL NULL
This is a suggestion with a concatenated result:
CREATE TABLE #tmpTbl (ID VARCHAR(100),IC VARCHAR(100),Name VARCHAR(100),UGCOS VARCHAR(100),MCOS VARCHAR(100))
INSERT INTO #tmpTbl VALUES
('1AA','A123456B','Edmund','Australia','Denmark')
,('1AA','A123456B','Edmund','Australia','France')
,('2CS','C435664C','Grace','Norway',NULL)
,('3TG','G885595H','Rae',NULL,'Japan');
SELECT ID,IC,Name,UGCOS,
(
STUFF(
(
SELECT ' ,' + x.MCOS
FROM #tmpTbl AS x
WHERE x.ID=outerTbl.ID
FOR XML PATH('')
),1,2,''
)
) AS MCOS
FROM #tmpTbl AS outerTbl
GROUP BY ID,IC,Name,UGCOS;
GO
DROP TABLE #tmpTbl;
The result
1AA A123456B Edmund Australia Denmark ,France
2CS C435664C Grace Norway NULL
3TG G885595H Rae NULL Japan
Using Cross Apply and Pivot we can achieve this
DECLARE #Table1 TABLE
( ID varchar(3), IC varchar(8), Name varchar(6), UGCOS varchar(9), MCOS varchar(7))
;
INSERT INTO #Table1
( ID , IC , Name , UGCOS , MCOS )
VALUES
('1AA', 'A123456B', 'Edmund', 'Australia', 'Denmark'),
('1AA', 'A123456B', 'Edmund', 'Australia', 'France'),
('2CS', 'C435664C', 'Grace', 'Norway', NULL),
('3TG', 'G885595H', 'Rae', NULL, 'Japan')
;
Select ID , IC , Name , UGCOS,MAX([MCOS1])[MCOS1],MAX([MCOS2])[MCOS2] from (
select ID , IC , Name , UGCOS , MCOS,col,val,col +''+CAST(ROW_NUMBER()OVER(PARTITION BY ID ORDER BY col) AS VARCHAR)RN from #Table1
CROSS APPLY (values('MCOS',MCOS))CS(col,val))T
PIVOT (MAX(val) FOR RN IN ([MCOS1],[MCOS2]))PVT
GROUP BY ID , IC , Name , UGCOS
Do you always have a maximum of 2 rows of data that you'll want to turn into columns? If so, this would do you;
CREATE TABLE #TableName (ID varchar(3), IC varchar(8), Name varchar(6), UCGOS varchar(9), MCOS varchar(7))
INSERT INTO #TableName
VALUES
('1AA','A123456B','Edmund','Australia','Denmark')
,('1AA','A123456B','Edmund','Australia','France')
,('2CS','C435664C','Grace','Norway',NULL)
,('3TG','G885595H','Rae',NULL,'Japan')
SELECT DISTINCT a.ID
,a.IC
,a.NAME
,a.UCGOS
,b.Mcos1 MCOS
,c.Mcos2 MCOS1
FROM #TableName a
LEFT JOIN (
SELECT ID
,MAX(MCOS) Mcos1
FROM #TableName
GROUP BY ID
) b ON a.ID = b.ID
LEFT JOIN (
SELECT ID
,MIN(MCOS) Mcos2
FROM #TableName
GROUP BY ID
) c ON a.ID = c.ID
AND (
b.ID = c.ID
AND b.Mcos1 <> c.Mcos2
)
DROP TABLE #TableName
Gives you the result you're after.