Compare 2 columns value in a table and show the matching values in a new column . column values are comma seperated - sql

Here I want to compare the comma-separated values from the column roles and userroles, then show the matching values in another column.
I found an example compare comma separated values in sql and used the cursor to iterate rows one by one. it's working for me. but I feel there should be a better way to do it.
Any help is much appreciated.
Create table #temp4
(
user_id int,
permission_id int,
roles varchar(max),
userroles varchar(max),
matchingrolesinthisrow varchar(max))
Insert Into #temp4 values
( 1, -12010, '2341,8760,3546', '1000,1001,1002,1003', null),
( 1, -334, '1002,1001,3467', '2341,1002,3467', null),
( 2, -12349, '9876,9982,6548', '1001,1002,2341', null)
below is the result table I am looking for.
user_id
permission_id
roles
userroles
matchingrolesinthisrow
1
-12010
2341,8760,3546
1000,1001,1002,1003
1
-334
1002,1001,3467
2341,1002,3467
1002,3467
2
-12349
9876,9982,6548
1001,1002,2341
My attempt so far and it's working. please guide me to do this in a better way.
DECLARE #user_id INT
DECLARE #permission_id INT
DECLARE #roles VARCHAR(MAX)
DECLARE #userroles VARCHAR(MAX)
DECLARE #matchingrolesinthisrow VARCHAR(MAX)
declare cur CURSOR LOCAL for
select user_id, permission_id, roles, userroles, matchingrolesinthisrow from #temp4 order by 1
open cur
fetch next from cur into #user_id, #permission_id, #roles, #userroles, #matchingrolesinthisrow
while ##FETCH_STATUS = 0 BEGIN
print (#roles)
print(#userroles)
--execute on each row
UPDATE #temp4
SET matchingrolesinthisrow = T1.[Item]
FROM [developers].[Split](#roles, ',') AS T1
INNER JOIN [developers].[Split](#userroles, ',') AS T2 on T1.[Item] = T2.[Item]
Where roles = #roles and userroles = #userroles and permission_id = #permission_id and user_id = #user_id
fetch next from cur into #user_id, #permission_id, #roles, #userroles, #matchingrolesinthisrow
END
close cur
deallocate cur
--Split function
CREATE FUNCTION [developers].[Split]
(
#s VARCHAR(max),
#split CHAR(1)
)
RETURNS #temptable TABLE ([Item] VARCHAR(MAX))
AS
BEGIN
DECLARE #x XML
SELECT #x = CONVERT(xml,'<root><s>' + REPLACE(#s,#split,'</s><s>') + '</s></root>');
INSERT INTO #temptable
SELECT [Value] = T.c.value('.','varchar(20)')
FROM #X.nodes('/root/s') T(c);
RETURN
END;

Ideally, you should store each inidividual piece of information in separate row, so you should have two separate tables roles and userroles which are foreign-keyed on this one.
Be that as it may, this does not need cursors. You can use STRING_SPLIT and STRING_AGG to get the result you want very easily:
SELECT
t4.user_id,
t4.permission_id,
t4.roles,
t4.userroles,
matchingrolesinthisrow = (
SELECT STRING_AGG(r.value, ',')
FROM STRING_SPLIT(t4.roles, ',') r
JOIN STRING_SPLIT(t4.userroles, ',') ur ON ur.value = r.value
)
FROM #temp4 t4;
SQL Fiddle
If you are on an early version of SQL Server, you will have to use custom Table Valued Functions to do this.

Modified the shared answer to work in the lower version of SQL Server.
SELECT
t4.user_id,
t4.permission_id,
t4.roles,
t4.userroles,
STUFF((SELECT N',' + CONVERT(nvarchar(2000),r.value)
FROM STRING_SPLIT(t4.roles, ',') r
INNER JOIN STRING_SPLIT(t4.userroles, ',') ur ON ur.value = r.value
FOR XML PATH(N''), TYPE).value(N'.[1]', N'nvarchar(max)'), 1, 1, N'') as matchingrolesinthisrow
FROM #temp4 t4;

Related

How to reverse string in column a word by word?

I have a table with a varchar(50) column 'Relation' having more than 1000 rows of data like:
P1_P10_P45_P20
P1_P14_P5_P22
P1_P3
P3_P4_P5_P2_P100_P2_P1
I want the output to have reverse order:
P20_P45_P10_P1
P22_P5_P14_P1
P3_P1
P1_P2_P100_P2_P5_P4_P3
Could you please help me achieve this in single query?
Aditi you can use a Tally table to find all _ and then join them back using STUFF + FOR XML PATH combination like below.
I recommend that you read about Tally tables at earliest possible time here
Also the demo link is here
--create table yourtable(Relation nvarchar(50));
--insert into yourtable values
-- ('P1_P14_P5_P22'),
-- ('P1_P3'),
-- ('P3_P4_P5_P2_P100_P2_P1'), ('P1_P3'),
-- ('P3_P4_P5_P2_P100_P2_P1');
;WITH Tally AS (
SELECT 1 as Num
UNION ALL
SELECT Num + 1 FROM Tally WHERE Num < 51
)
,
InputSet AS
(
select *, RN=row_number() over (order by (select 1)) from yourtable
)
,TempSet AS
(
SELECT
Relation,
Num,
RN,
partBetweenUnderscore = SUBSTRING(Relation, Num, ISNULL(LEAD(Num) OVER (Partition by RN ORDER BY Num ASC),LEN('_'+Relation)+1)-Num-1)
FROM
(
SELECT *
FROM InputSet CROSS JOIN Tally
WHERE CHARINDEX('_','_'+Relation,Num)=Num
)T
)
SELECT
Relation,
NewRelation = STUFF(
(SELECT '_' + T1.partBetweenUnderscore FROM TempSet T1 WHERE T1.RN=T2.RN ORDER BY T1.Num DESC FOR XML PATH ('')
),1,1,'')
FROM TempSet T2
GROUP BY RN, Relation
You need to split the stored strings using a splitter, that returns the substrings and the position of each substring. After that you can easily build the desired output.
If you use SQL Server 2017+, you may try a JSON-based approach. You need to transform each string into a valid JSON array (for example P1_P10_P45_P20 into ["'P1","P10","P45","P20"]), parse this array as a table with OPENJSON() and join the rows with STRING_AGG() to generate the expected output:
Table:
CREATE TABLE Data (Relation varchar(50))
INSERT INTO Data (Relation)
VALUES
('P1_P10_P45_P20'),
('P1_P14_P5_P22'),
('P1_P3'),
('P3_P4_P5_P2_P100_P2_P1')
Statement:
SELECT c.Relation
FROM Data d
OUTER APPLY (
SELECT STRING_AGG([value], '_') WITHIN GROUP (ORDER BY CONVERT(int, [key]) DESC)
FROM OPENJSON(CONCAT('["', REPLACE(d.Relation, '_', '","'), '"]'))
) c (Relation)
Result:
Relation
----------------------
P20_P45_P10_P1
P22_P5_P14_P1
P3_P1
P1_P2_P100_P2_P5_P4_P3
First all of the prievious comments are correct, especially that this is a data model problem. Here is a very kludgy solution. I offer it because you only have 1000 records. This is not efficient, nor will it scale up. The following works on MS SQL Server 2017.
Drop table if exists Relation
create table Relation (Relation varchar(50))
INSERT INTO Relation (Relation)
VALUES
('P1_P10_P45_P20'),
('P1_P14_P5_P22'),
('P1_P3'),
('P3_P4_P5_P2_P100_P2_P1');
DROP TABLE IF EXISTS Rev
create table Rev (Relation varchar(50), Rev varchar(50))
DROP TABLE IF EXISTS Z
create table Z (token varchar(50))
declare #Reverse varchar(50)
set #Reverse = ''
declare #token varchar(50)
declare #Relation varchar(50)
declare cur cursor for select * from Relation
open cur
fetch next from cur into #Relation
while ##FETCH_STATUS = 0
begin
with T(Relation, starts, pos) as (
select #Relation, 1, charindex('_', #Relation)
union all
select #Relation, pos + 1, charindex('_', #Relation, pos + 1)
from T
where pos > 0)
insert into Z select substring(#Relation, starts, case when pos > 0 then pos - starts else len(#Relation) end) token
from T
declare cur2 cursor for select token from Z
open cur2
fetch next from cur2 into #token
while ##FETCH_STATUS = 0
begin
set #Reverse = #token + '_' + #Reverse
fetch next from cur2 into #token
end
close cur2
deallocate cur2
set #Reverse = (select left(#Reverse,len(#Reverse)-1))
insert into Rev select #Relation, #Reverse
set #Reverse = ''
delete Z
fetch next from cur into #Relation
end;
close cur
deallocate cur
select * from Rev
SELECT ##version

Updating a json array IN SQL Server table

I have an array of json in a SQL Server column, I am trying to update all names to 'Joe'.
I tried the below code , but it is updating only first element of the json array
CREATE TABLE #t (I INT, JsonColumn NVARCHAR(MAX) CHECK (ISJSON(JsonColumn) > 0))
INSERT INTO #t
VALUES (1, '[{"id":"101","name":"John"}, {"id":"102","name":"peter"}]')
INSERT INTO #t VALUES (2,'[{"id":"103","name":"dave"}, {"id":"104","name":"mark"}]')
SELECT * FROM #t
SELECT * FROM #t
CROSS APPLY OPENJSON(JsonColumn) s
WITH cte AS
(
SELECT *
FROM #t
CROSS APPLY OPENJSON(JsonColumn) s
)
UPDATE cte
SET JsonColumn = JSON_MODIFY(JsonColumn, '$[' + cte.[key] + '].name', 'Joe')
SELECT * FROM #t
-- DROP TABLE #t
It is only updating the first element of array to joe
Current result:
[{"id":"101","name":"Joe"}, {"id":"102","name":"cd"}]
[{"id":"103","name":"Joe"}, {"id":"104","name":"mark"}]
Expected
[{"id":"101","name":"Joe"}, {"id":"102","name":"Joe"}]
[{"id":"103","name":"Joe"}, {"id":"104","name":"Joe"}]
Since you want to do in one transaction, I could not think of any other ways than to create another table and store the values into new table and use for XML path with the value. Problem is you are trying to update JSON array and I am not sure how would you update the same row twice with different value. With cross apply as you have shown it creates two rows and then only you can update it to JOE.
Your query will update name = Joe for ID = 101 for first row, and Name = Joe for ID = 102 based on value column. Since these are on two different rows you are seeing only one change in your temp table.
I created one more #temp2 table to store those values and use XML path to concatenate. The final table will be #t2 table for your expected results.
SELECT *
into #t2
FROM #t
CROSS APPLY OPENJSON(JsonColumn) s
select *, json_value (value, '$.name') from #t2
UPDATE #t2
SET value = JSON_MODIFY(value, '$.name', 'Joe')
select t.I ,
JSONValue = concat('[',stuff((select ',' + value from #t2 t1
where t1.i = t.i
for XML path('')),1,1,''),']')
from #t2 t
group by t.I
Output:
I JSONValue
1 [{"id":"101","name":"Joe"},{"id":"102","name":"Joe"}]
Updating original table:
update t
set t.JsonColumn =t2.JSONValue
from #t t
join (select t.I ,
JSONValue = concat('[',stuff((select ',' + value from #t2 t1
where t1.i = t.i
for XML path('')),1,1,''),']')
from #t2 t
group by t.I ) t2 on t.I = t2.i
I think that it is impossible to apply more updates to one record with one command. So you need to explode JSON array to records.
You can do this with a Temporary or Variable Table and a Cursor.
-- Declare the Variable Table
DECLARE #JsonTable TABLE (
RecordKey UNIQUEIDENTIFIER,
ArrayIndex INT,
ObjKey NVARCHAR(100),
ObjValue NVARCHAR(1000)
);
-- Fill the Variable Table
INSERT INTO #JsonTable
SELECT TB1.pk as RecordKey,
TB1data.[key] AS ArrayIndex,
TB1dataItem.[key] as ObjKey,
TB1dataItem.[value] as ObjValue
FROM MyTable TB1
CROSS APPLY OPENJSON(JSON_QUERY(TB1.data, '$.list')) TB1data
CROSS APPLY OPENJSON(JSON_QUERY(TB1data.value, '$')) TB1dataItem
WHERE TB1dataItem.[key] = 'name'
-- Declare Cursor and relative variables
DECLARE #recordKey UNIQUEIDENTIFIER,
#recordData NVARCHAR(MAX),
#arrayIndex INT,
#objKey NVARCHAR(100),
#objValue NVARCHAR(1000);
DECLARE JsonCursor CURSOR FAST_FORWARD READ_ONLY FOR
SELECT * FROM #JsonTable;
-- Use Cursor to read any json array item
OPEN JsonCursor;
FETCH NEXT
FROM JsonCursor
INTO #recordKey, #arrayIndex, #objKey, #objValue;
WHILE ##FETCH_STATUS = 0 BEGIN
UPDATE TB1
SET data = JSON_MODIFY(
data,
'$.list[' + CAST(#arrayIndex as VARCHAR(20)) + '].name',
'Joe'
)
FROM MyTable TB1
WHERE TB1.pk = #recordKey;
FETCH NEXT
FROM JsonCursor
INTO #recordKey, #arrayIndex, #objKey, #objValue;
END;
CLOSE JsonCursor;
DEALLOCATE JsonCursor;
Do you need this?
CREATE TABLE #t (
I INT,
JsonColumn NVARCHAR(MAX) CHECK (ISJSON(JsonColumn) > 0)
);
INSERT INTO #t
VALUES (1, '[{"id":"101","name":"John"}, {"id":"102","name":"peter"}]');
INSERT INTO #t
VALUES (2, '[{"id":"103","name":"dave"}, {"id":"104","name":"mark"}]');
SELECT CONCAT('[', STRING_AGG(JSON_MODIFY(JSON_MODIFY('{}', '$.id', j.id), '$.name', 'John'), ','), ']')
FROM #t t
CROSS APPLY OPENJSON(JsonColumn) WITH (id INT, name sysname) j
GROUP BY t.I

Dynamically Create tables and Insert into it from another table with CSV values

Have a Table with the CSV Values in the columns as below
ID Name text
1 SID,DOB 123,12/01/1990
2 City,State,Zip NewYork,NewYork,01234
3 SID,DOB 456,12/21/1990
What is need to get is 2 tables in this scenario as out put with the corresponding values
ID SID DOB
1 123 12/01/1990
3 456 12/21/1990
ID City State Zip
2 NewYork NewYork 01234
Is there any way of achieving it using a Cursor or any other method in SQL server?
There are several ways that this can be done. One way that I would suggest would be to split the data from the comma separated list into multiple rows.
Since you are using SQL Server, you could implement a recursive CTE to split the data, then apply a PIVOT function to create the columns that you want.
;with cte (id, NameItem, Name, textItem, text) as
(
select id,
cast(left(Name, charindex(',',Name+',')-1) as varchar(50)) NameItem,
stuff(Name, 1, charindex(',',Name+','), '') Name,
cast(left(text, charindex(',',text+',')-1) as varchar(50)) textItem,
stuff(text, 1, charindex(',',text+','), '') text
from yt
union all
select id,
cast(left(Name, charindex(',',Name+',')-1) as varchar(50)) NameItem,
stuff(Name, 1, charindex(',',Name+','), '') Name,
cast(left(text, charindex(',',text+',')-1) as varchar(50)) textItem,
stuff(text, 1, charindex(',',text+','), '') text
from cte
where Name > ''
and text > ''
)
select id, SID, DOB
into table1
from
(
select id, nameitem, textitem
from cte
where nameitem in ('SID', 'DOB')
) d
pivot
(
max(textitem)
for nameitem in (SID, DOB)
) piv;
See SQL Fiddle with Demo. The recursive version will work great but if you have a large dataset, you could have some performance issues so you could also use a user defined function to split the data:
create FUNCTION [dbo].[Split](#String1 varchar(MAX), #String2 varchar(MAX), #Delimiter char(1))
returns #temptable TABLE (colName varchar(MAX), colValue varchar(max))
as
begin
declare #idx1 int
declare #slice1 varchar(8000)
declare #idx2 int
declare #slice2 varchar(8000)
select #idx1 = 1
if len(#String1)<1 or #String1 is null return
while #idx1 != 0
begin
set #idx1 = charindex(#Delimiter,#String1)
set #idx2 = charindex(#Delimiter,#String2)
if #idx1 !=0
begin
set #slice1 = left(#String1,#idx1 - 1)
set #slice2 = left(#String2,#idx2 - 1)
end
else
begin
set #slice1 = #String1
set #slice2 = #String2
end
if(len(#slice1)>0)
insert into #temptable(colName, colValue) values(#slice1, #slice2)
set #String1 = right(#String1,len(#String1) - #idx1)
set #String2 = right(#String2,len(#String2) - #idx2)
if len(#String1) = 0 break
end
return
end;
Then you can use a CROSS APPLY to get the result for each row:
select id, SID, DOB
into table1
from
(
select t.id,
c.colname,
c.colvalue
from yt t
cross apply dbo.split(t.name, t.text, ',') c
where c.colname in ('SID', 'DOB')
) src
pivot
(
max(colvalue)
for colname in (SID, DOB)
) piv;
See SQL Fiddle with Demo
You'd need to approach this as a multi-step ETL project. I'd probably start with exporting the two types of rows into a couple staging tables. So, for example:
select * from yourtable /* rows that start with a number */
where substring(text,1,1) in
('0','1','2','3','4','5','6','7','8','9')
select * from yourtable /* rows that don't start with a number */
where substring(text,1,1)
not in ('0','1','2','3','4','5','6','7','8','9')
/* or simply this to follow your example explicitly */
select * from yourtable where name like 'sid%'
select * from yourtable where name like 'city%'
Once you get the two types separated then you can split them out with one of the already written split functions found readily out on the interweb.
Aaron Bertrand (who is on here often) has written up a great post on the variety of ways to split comma delimted strings using SQL. Each of the methods are compared and contrasted here.
http://www.sqlperformance.com/2012/07/t-sql-queries/split-strings
If your row count is minimal (under 50k let's say) and it's going to be a one time operation than pick the easiest way and don't worry too much about all the performance numbers.
If you have a ton of rows or this is an ETL process that will run all the time then you'll really want to pay attention to that stuff.
A simple solution using cursors to build temporary tables. This has the limitation of making all columns VARCHAR and would be slow for large amounts of data.
--** Set up example data
DECLARE #Source TABLE (ID INT, Name VARCHAR(50), [text] VARCHAR(200));
INSERT INTO #Source
(ID, Name, [text])
VALUES (1, 'SID,DOB', '123,12/01/1990')
, (2, 'City,State,Zip', 'NewYork,NewYork,01234')
, (3, 'SID,DOB', '456,12/21/1990');
--** Declare variables
DECLARE #Name VARCHAR(200) = '';
DECLARE #Text VARCHAR(1000) = '';
DECLARE #SQL VARCHAR(MAX);
--** Set up cursor for the tables
DECLARE cursor_table CURSOR FAST_FORWARD READ_ONLY FOR
SELECT s.Name
FROM #Source AS s
GROUP BY Name;
OPEN cursor_table
FETCH NEXT FROM cursor_table INTO #Name;
WHILE ##FETCH_STATUS = 0
BEGIN
--** Dynamically create a temp table with the specified columns
SET #SQL = 'CREATE TABLE ##Table (' + REPLACE(#Name, ',', ' VARCHAR(50),') + ' VARCHAR(50));';
EXEC(#SQL);
--** Set up cursor to insert the rows
DECLARE row_cursor CURSOR FAST_FORWARD READ_ONLY FOR
SELECT s.Text
FROM #Source AS s
WHERE Name = #Name;
OPEN row_cursor;
FETCH NEXT FROM row_cursor INTO #Text;
WHILE ##FETCH_STATUS = 0
BEGIN
--** Dynamically insert the row
SELECT #SQL = 'INSERT INTO ##Table VALUES (''' + REPLACE(#Text, ',', ''',''') + ''');';
EXEC(#SQL);
FETCH NEXT FROM row_cursor INTO #Text;
END
--** Display the table
SELECT *
FROM ##Table;
--** Housekeeping
CLOSE row_cursor;
DEALLOCATE row_cursor;
DROP TABLE ##Table;
FETCH NEXT FROM cursor_table INTO #Name;
END
CLOSE cursor_table;
DEALLOCATE cursor_table;

Delete duplicate records in SQL Server?

Consider a column named EmployeeName table Employee. The goal is to delete repeated records, based on the EmployeeName field.
EmployeeName
------------
Anand
Anand
Anil
Dipak
Anil
Dipak
Dipak
Anil
Using one query, I want to delete the records which are repeated.
How can this be done with TSQL in SQL Server?
You can do this with window functions. It will order the dupes by empId, and delete all but the first one.
delete x from (
select *, rn=row_number() over (partition by EmployeeName order by empId)
from Employee
) x
where rn > 1;
Run it as a select to see what would be deleted:
select *
from (
select *, rn=row_number() over (partition by EmployeeName order by empId)
from Employee
) x
where rn > 1;
Assuming that your Employee table also has a unique column (ID in the example below), the following will work:
delete from Employee
where ID not in
(
select min(ID)
from Employee
group by EmployeeName
);
This will leave the version with the lowest ID in the table.
Edit
Re McGyver's comment - as of SQL 2012
MIN can be used with numeric, char, varchar, uniqueidentifier, or datetime columns, but not with bit columns
For 2008 R2 and earlier,
MIN can be used with numeric, char, varchar, or datetime columns, but not with bit columns (and it also doesn't work with GUID's)
For 2008R2 you'll need to cast the GUID to a type supported by MIN, e.g.
delete from GuidEmployees
where CAST(ID AS binary(16)) not in
(
select min(CAST(ID AS binary(16)))
from GuidEmployees
group by EmployeeName
);
SqlFiddle for various types in Sql 2008
SqlFiddle for various types in Sql 2012
You could try something like the following:
delete T1
from MyTable T1, MyTable T2
where T1.dupField = T2.dupField
and T1.uniqueField > T2.uniqueField
(this assumes that you have an integer based unique field)
Personally though I'd say you were better off trying to correct the fact that duplicate entries are being added to the database before it occurs rather than as a post fix-it operation.
DELETE
FROM MyTable
WHERE ID NOT IN (
SELECT MAX(ID)
FROM MyTable
GROUP BY DuplicateColumn1, DuplicateColumn2, DuplicateColumn3)
WITH TempUsers (FirstName, LastName, duplicateRecordCount)
AS
(
SELECT FirstName, LastName,
ROW_NUMBER() OVER (PARTITIONBY FirstName, LastName ORDERBY FirstName) AS duplicateRecordCount
FROM dbo.Users
)
DELETE
FROM TempUsers
WHERE duplicateRecordCount > 1
WITH CTE AS
(
SELECT EmployeeName,
ROW_NUMBER() OVER(PARTITION BY EmployeeName ORDER BY EmployeeName) AS R
FROM employee_table
)
DELETE CTE WHERE R > 1;
The magic of common table expressions.
Try
DELETE
FROM employee
WHERE rowid NOT IN (SELECT MAX(rowid) FROM employee
GROUP BY EmployeeName);
If you're looking for a way to remove duplicates, yet you have a foreign key pointing to the table with duplicates, you could take the following approach using a slow yet effective cursor.
It will relocate the duplicate keys on the foreign key table.
create table #properOlvChangeCodes(
id int not null,
name nvarchar(max) not null
)
DECLARE #name VARCHAR(MAX);
DECLARE #id INT;
DECLARE #newid INT;
DECLARE #oldid INT;
DECLARE OLVTRCCursor CURSOR FOR SELECT id, name FROM Sales_OrderLineVersionChangeReasonCode;
OPEN OLVTRCCursor;
FETCH NEXT FROM OLVTRCCursor INTO #id, #name;
WHILE ##FETCH_STATUS = 0
BEGIN
-- determine if it should be replaced (is already in temptable with name)
if(exists(select * from #properOlvChangeCodes where Name=#name)) begin
-- if it is, finds its id
Select top 1 #newid = id
from Sales_OrderLineVersionChangeReasonCode
where Name = #name
-- replace terminationreasoncodeid in olv for the new terminationreasoncodeid
update Sales_OrderLineVersion set ChangeReasonCodeId = #newid where ChangeReasonCodeId = #id
-- delete the record from the terminationreasoncode
delete from Sales_OrderLineVersionChangeReasonCode where Id = #id
end else begin
-- insert into temp table if new
insert into #properOlvChangeCodes(Id, name)
values(#id, #name)
end
FETCH NEXT FROM OLVTRCCursor INTO #id, #name;
END;
CLOSE OLVTRCCursor;
DEALLOCATE OLVTRCCursor;
drop table #properOlvChangeCodes
delete from person
where ID not in
(
select t.id from
(select min(ID) as id from person
group by email
) as t
);
Please see the below way of deletion too.
Declare #Employee table (EmployeeName varchar(10))
Insert into #Employee values
('Anand'),('Anand'),('Anil'),('Dipak'),
('Anil'),('Dipak'),('Dipak'),('Anil')
Select * from #Employee
Created a sample table named #Employee and loaded it with given data.
Delete aliasName from (
Select *,
ROW_NUMBER() over (Partition by EmployeeName order by EmployeeName) as rowNumber
From #Employee) aliasName
Where rowNumber > 1
Select * from #Employee
Result:
I know, this is asked six years ago, posting just incase it is helpful for anyone.
Here's a nice way of deduplicating records in a table that has an identity column based on a desired primary key that you can define at runtime. Before I start I'll populate a sample data set to work with using the following code:
if exists (select 1 from sys.all_objects where type='u' and name='_original')
drop table _original
declare #startyear int = 2017
declare #endyear int = 2018
declare #iterator int = 1
declare #income money = cast((SELECT round(RAND()*(5000-4990)+4990 , 2)) as money)
declare #salesrepid int = cast(floor(rand()*(9100-9000)+9000) as varchar(4))
create table #original (rowid int identity, monthyear varchar(max), salesrepid int, sale money)
while #iterator<=50000 begin
insert #original
select (Select cast(floor(rand()*(#endyear-#startyear)+#startyear) as varchar(4))+'-'+ cast(floor(rand()*(13-1)+1) as varchar(2)) ), #salesrepid , #income
set #salesrepid = cast(floor(rand()*(9100-9000)+9000) as varchar(4))
set #income = cast((SELECT round(RAND()*(5000-4990)+4990 , 2)) as money)
set #iterator=#iterator+1
end
update #original
set monthyear=replace(monthyear, '-', '-0') where len(monthyear)=6
select * into _original from #original
Next I'll create a Type called ColumnNames:
create type ColumnNames AS table
(Columnnames varchar(max))
Finally I will create a stored proc with the following 3 caveats:
1. The proc will take a required parameter #tablename that defines the name of the table you are deleting from in your database.
2. The proc has an optional parameter #columns that you can use to define the fields that make up the desired primary key that you are deleting against. If this field is left blank, it is assumed that all the fields besides the identity column make up the desired primary key.
3. When duplicate records are deleted, the record with the lowest value in it's identity column will be maintained.
Here is my delete_dupes stored proc:
create proc delete_dupes (#tablename varchar(max), #columns columnnames readonly)
as
begin
declare #table table (iterator int, name varchar(max), is_identity int)
declare #tablepartition table (idx int identity, type varchar(max), value varchar(max))
declare #partitionby varchar(max)
declare #iterator int= 1
if exists (select 1 from #columns) begin
declare #columns1 table (iterator int, columnnames varchar(max))
insert #columns1
select 1, columnnames from #columns
set #partitionby = (select distinct
substring((Select ', '+t1.columnnames
From #columns1 t1
Where T1.iterator = T2.iterator
ORDER BY T1.iterator
For XML PATH ('')),2, 1000) partition
From #columns1 T2 )
end
insert #table
select 1, a.name, is_identity from sys.all_columns a join sys.all_objects b on a.object_id=b.object_id
where b.name = #tablename
declare #identity varchar(max)= (select name from #table where is_identity=1)
while #iterator>=0 begin
insert #tablepartition
Select distinct case when #iterator=1 then 'order by' else 'over (partition by' end ,
substring((Select ', '+t1.name
From #table t1
Where T1.iterator = T2.iterator and is_identity=#iterator
ORDER BY T1.iterator
For XML PATH ('')),2, 5000) partition
From #table T2
set #iterator=#iterator-1
end
declare #originalpartition varchar(max)
if #partitionby is null begin
select #originalpartition = replace(b.value+','+a.type+a.value ,'over (partition by','') from #tablepartition a cross join #tablepartition b where a.idx=2 and b.idx=1
select #partitionby = a.type+a.value+' '+b.type+a.value+','+b.value+') rownum' from #tablepartition a cross join #tablepartition b where a.idx=2 and b.idx=1
end
else
begin
select #originalpartition=b.value +','+ #partitionby from #tablepartition a cross join #tablepartition b where a.idx=2 and b.idx=1
set #partitionby = (select 'OVER (partition by'+ #partitionby + ' ORDER BY'+ #partitionby + ','+b.value +') rownum'
from #tablepartition a cross join #tablepartition b where a.idx=2 and b.idx=1)
end
exec('select row_number() ' + #partitionby +', '+#originalpartition+' into ##temp from '+ #tablename+'')
exec(
'delete a from _original a
left join ##temp b on a.'+#identity+'=b.'+#identity+' and rownum=1
where b.rownum is null')
drop table ##temp
end
Once this is complied, you can delete all your duplicate records by running the proc. To delete dupes without defining a desired primary key use this call:
exec delete_dupes '_original'
To delete dupes based on a defined desired primary key use this call:
declare #table1 as columnnames
insert #table1
values ('salesrepid'),('sale')
exec delete_dupes '_original' , #table1

Is there a way to return multiple results with a subquery?

I have need to return multiple results from a subquery and have been unable to figure it out. The end result will produce the persons name across the vertical axis, various actions based on an action category across the horizontal axis. So the end result looking like:
----------
**NAME CATEGORY 1 CATEGORY 2**
Smith, John Action 1, Action 2 Action 1, Action 2, Action 3
----------
Is there a way to do this in a single query?
select
name,
(select action from actionitemtable where actioncategory = category1 and contact = contactid)
from
contact c
inner join actionitemtable a
on c.contactid = a.contactid
If more than one result is returned in that subquery I would like to be able to display it as a single comma separated string, or list of actions, etc.
Thank you.
Microsoft Sql Server 2005 is being used.
I use a User Defined Function for this task. The udf creates a delimited string with all elements matching the parameters, then you call the udf from your select statement such that you pull a delimited list for each record in the recordset.
CREATE FUNCTION dbo.ud_Concat(#actioncategory int, #contactid int)
RETURNS VARCHAR(8000)
AS
BEGIN
DECLARE #sOutput VARCHAR(8000)
SET #sOutput = ''
SELECT #sOutput = COALESCE(#sOutput, '') + action + ', '
FROM dbo.actionitemtable
WHERE actioncategory=#actioncategory AND contact=#contact
ORDER BY action
RETURN #sOutput
END
SELECT
name,
dbo.ud_Concat(category1, contactid) as contactList
FROM contact c
INNER JOIN actionitemtable a ON c.contactid = a.contactid
you need to give more info about your table structure and how they join to each other.
here is a generic example about combining multiple rows into a single column:
declare #table table (name varchar(30)
,ID int
,TaskID char(3)
,HoursAssigned int
)
insert into #table values ('John Smith' ,4592 ,'A01' ,40)
insert into #table values ('Matthew Jones',2863 ,'A01' ,20)
insert into #table values ('Jake Adams' ,1182 ,'A01' ,100)
insert into #table values ('Matthew Jones',2863 ,'A02' ,50)
insert into #table values ('Jake Adams' ,2863 ,'A02' ,10)
SELECT DISTINCT
t1.TaskID
,SUBSTRING(
replace(
replace(
(SELECT
t2.Name
FROM #Table AS t2
WHERE t1.TaskID=t2.TaskID
ORDER BY t2.Name
FOR XML PATH(''))
,'</NAME>','')
,'<NAME>',', ')
,3,2000) AS PeopleAssigned
FROM #table AS t1
OUTPUT:
TaskID PeopleAssigned
------ --------------------------------------
A01 Jake Adams, John Smith, Matthew Jones
A02 Jake Adams, Matthew Jones
(2 row(s) affected)
This is pretty abstract and complex. My initial reaction was "pivot query", but the more I looked at it (and at the earlier responses) the more I thought: Can you pass this one off to the application team? You return the "base", and they write and apply the procedural code that makes this kind of problem a snap. Sure, you can squeeze it in to SQL, but that doesn't make it the right place to do the work.
According to your query try this:
SELECT [Name],
STUFF(
(
SELECT ' ,' + [Action]
FROM [AactionItemTable]
WHERE [ActionCategory] = category1
AND [Contact] = contactid
FOR XML PATH('')
), 1, 2, ''
) AS [AdditionalData]
FROM [Contact] C
INNER JOIN [ActionItemTable] A
ON C.[ContactId] = A.[ContactId]
Guess this is the simplest way to do what you want.
EDIT: if there is no action in the subquery found, the [AdditionalData] result will be NULL.
You will probably have to create a custom aggregate function. Microsoft has a knowledge base article with sample code here.
If you don't mind using cursors, you can write your own function to do this. Here's an example that will work on the Adventureworks sample DB:
CREATE FUNCTION CommaFunctionSample
(
#SalesOrderID int
)
RETURNS varchar(max)
AS
BEGIN
DECLARE OrderDetailCursor CURSOR LOCAL FAST_FORWARD
FOR
SELECT SalesOrderDetailID
FROM Sales.SalesOrderDetail
WHERE SalesOrderID = #SalesOrderID
DECLARE #SalesOrderDetailID INT
OPEN OrderDetailCursor
FETCH NEXT FROM OrderDetailCursor INTO #SalesOrderDetailID
DECLARE #Buffer varchar(max)
WHILE ##FETCH_STATUS = 0
BEGIN
IF #Buffer IS NOT NULL SET #Buffer = #Buffer + ','
ELSE SET #Buffer = ''
SET #Buffer = #Buffer + CAST(#SalesOrderDetailID AS varchar(12))
FETCH NEXT FROM OrderDetailCursor INTO #SalesOrderDetailID
END
CLOSE OrderDetailCursor
DEALLOCATE OrderDetailCursor
RETURN #Buffer
END
This is how such a function would appear in a select query:
SELECT AccountNumber, dbo.CommaFunctionSample(SalesOrderID)
FROM Sales.SalesOrderHeader