MS SQL Server where exist same rows - sql

I have table like this
TABLE1
DATE NAME PRODUCT VERSION Colour SIZE ID
2017-08-03 01:30:20.000 Bob Mouse 12 Pink 3 461
2017-08-03 01:30:20.000 Bob Mouse 12 Pink 3 446
2017-08-03 01:30:20.000 Bob Mouse 13 Pink 3 487
2017-08-03 01:30:20.000 Bob Honey 6 Red 5 476
2017-08-03 01:30:20.000 Bob Honey 6 Blue 5 774
2017-08-03 01:30:20.000 Bob Shoe 6 Black 5 487
2017-08-03 01:30:20.000 Bob Dog 5 Black 7 1874
2017-08-03 01:30:20.000 Bob Dog 5 Black 7 999
2017-08-03 01:30:20.000 Bob Pet 689 Red 9 855
2017-08-02 01:30:20.000 Eva Mouse 12 Pink 3 461
2017-08-02 01:30:20.000 Eva Mouse 12 Pink 3 446
And the result should look like this:
TABLE1
DATE NAME PRODUCT VERSION Colour SIZE ID
2017-08-03 01:30:20.000 Bob Mouse 12 Ping 3 446
2017-08-03 01:30:20.000 Bob Mouse 13 Ping 3 487
2017-08-03 01:30:20.000 Bob Honey 6 Red 5 476
2017-08-03 01:30:20.000 Bob Shoe 6 Black 5 487
2017-08-03 01:30:20.000 Bob Dog 5 Black 7 1874
2017-08-03 01:30:20.000 Bob Pet 689 Red 9 855
2017-08-02 01:30:20.000 Eva Mouse 12 Pink 3 446
Main thing i am interest for is : Product AND Version
If is different something else should by deleted rows which contain Same PRODUCT AND VERSION
Problem is with column ID where I have always different ID numbers, but with Different NAME I have Same ID

There are examples if you need just to select such data or if you need to delete it:
create table #test
(
[DATE] DateTime2(7),
[NAME] VARCHAR(200),
[PRODUCT] VARCHAR(200),
[VERSION] INT,
[Colour] VARCHAR(200),
[SIZE] INT,
[ID] INT
);
INSERT INTO #test VALUES ('2017-08-03 01:30:20.000', 'Bob', 'Mouse', 12, 'Pink', 3, 461);
INSERT INTO #test VALUES ('2017-08-03 01:30:20.000', 'Bob', 'Mouse', 12, 'Pink', 3, 446);
INSERT INTO #test VALUES ('2017-08-03 01:30:20.000', 'Bob', 'Mouse', 13, 'Pink', 3, 487);
INSERT INTO #test VALUES ('2017-08-03 01:30:20.000', 'Bob', 'Honey', 6, 'Red', 5, 476);
INSERT INTO #test VALUES ('2017-08-03 01:30:20.000', 'Bob', 'Honey', 6, 'Blue', 5, 774);
INSERT INTO #test VALUES ('2017-08-03 01:30:20.000', 'Bob', 'Shoe', 6, 'Black', 5, 487);
INSERT INTO #test VALUES ('2017-08-03 01:30:20.000', 'Bob', 'Dog', 5, 'Black', 7, 1874);
INSERT INTO #test VALUES ('2017-08-03 01:30:20.000', 'Bob', 'Dog', 5, 'Black', 7, 999);
INSERT INTO #test VALUES ('2017-08-03 01:30:20.000', 'Bob', 'Pet', 689, 'Red', 9, 855);
INSERT INTO #test VALUES ('2017-08-02 01:30:20.000', 'Eva', 'Mouse', 12, 'Pink', 3, 461);
INSERT INTO #test VALUES ('2017-08-02 01:30:20.000', 'Eva', 'Mouse', 12, 'Pink', 3, 446);
SELECT
[DATE] ,
[NAME],
[PRODUCT],
[VERSION],
[Colour],
[SIZE],
[ID]
FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY [PRODUCT], [VERSION] ORDER BY [ID]) rn
FROM #test
) a
WHERE rn = 1;
DELETE FROM #test WHERE ID IN (SELECT
[ID]
FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY [PRODUCT], [VERSION] ORDER BY [ID]) rn
FROM #test
) a
WHERE rn > 1);
SELECT * FROM #test;
DROP TABLE #test;

Related

count real total customer by product groupby

first of all, i'm not really sure if this possible or not.
let say I have this dataset example
CREATE TABLE TRANSACTION(
user_id numeric,
account_id varchar,
product varchar,
colour varchar,
price numeric);
insert into transaction (user_id, account_id, product, colour, price)
values
(1, 'a1', 'biycle', 'black', 500),
(1, 'a2', 'motorbike', 'red', 1000),
(1, 'a2', 'motorbike', 'blue', 1200),
(2, 'b3', 'car', 'grey', 10000),
(2, 'b2', 'motorbike', 'black', 1250),
(3, 'c1', 'biycle', 'black', 500),
(3, 'c2', 'biycle', 'black', 525),
(3, 'c4', 'skateboard', 'white', 250),
(3, 'c5', 'scooter', 'blue', 260)
from that table we know that
the total real customer is 3 (1,2,3) and
the total real account is 8 (a1, a2, b3, b2, c1, c2, c4, c5)
and then with this code
SELECT
product,
colour,
sum(price)total_price,
count(DISTINCT user_id)customer_total,
count(DISTINCT account_id)account_total
from transaction
group by
product, colour
and the return is like this
product
colour
total_price
customer_total
account_total
biycle
black
1525
2
3
car
grey
10000
1
1
motorbike
black
1250
1
1
motorbike
blue
1200
1
1
motorbike
red
1000
1
1
scooter
blue
260
1
1
skateboard
white
250
1
1
from the output above,
if we total the customer_total, it will be 8 and
if we total the account_total, it will be 9
is there any alternative way so that the customer_total will be 3 and account_total will be 8
You can calculate the accounts and customer total using an inline query that computes the customer and account totals within the same query.
SELECT
product,
colour,
sum(price)total_price,
(select count(DISTINCT user_id) from transaction) as customer_total,
(select count(DISTINCT account_id) from transaction) as account_total
from transaction
group by
product, colour
Result:
product
colour
total_price
customer_total
account_total
biycle
black
1525
3
8
car
grey
10000
3
8
motorbike
black
1250
3
8
motorbike
blue
1200
3
8
motorbike
red
1000
3
8
scooter
blue
260
3
8
skateboard
white
250
3
8

Is there a way to update the value of a cell in a record based on whether it is in a corresponding column of another table in Oracle SQL?

I have three worksheets: players, teams, and weights (how highly a particular attribute is weighted when determining player-team match).
Players
Name
Age
Height
Free_Throw_Perc
...
Bod
23
74
62
...
Teams
| Team_Name | Age | Height | Free_Throw_Perc | ... |
|-----------|-----|--------|-----------------|-----|
|Team1|23|78|62|...|
Weights
| Team_Name | Age | Height | Free_Throw_Perc | ... |
|:---------:|:---:|:------:|:---------------:|:---:|
| Team1 | 5 | 10 | 10 | ... |
CREATE TABLE players (name, age, height, free_throw_perc) AS
SELECT 'Alice', 20, 160, 90 FROM DUAL UNION ALL
SELECT 'Betty', 21, 165, 80 FROM DUAL UNION ALL
SELECT 'Carol', 22, 170, 70 FROM DUAL UNION ALL
SELECT 'Debra', 23, 175, 60 FROM DUAL UNION ALL
SELECT 'Emily', 24, 180, 50 FROM DUAL UNION ALL
SELECT 'Fiona', 25, 185, 40 FROM DUAL UNION ALL
SELECT 'Gerri', 26, 190, 30 FROM DUAL UNION ALL
SELECT 'Heidi', 27, 195, 20 FROM DUAL UNION ALL
SELECT 'Irene', 28, 200, 10 FROM DUAL;
CREATE TABLE teams (team_name, age, height, free_throw_perc) AS
SELECT 'ALPHA', 20,175,90 FROM DUAL;
CREATE TABLE weights team_name, age, height, free_throw_perc) AS
SELECT 'ALPHA', 5,10,10 FROM DUAL;
The teams table corresponds to the players table but contains a record for each team detailing their ideal player based on the current composition of the team. The weights table contains a record for each team with an integer value weight stating how much they care about each player attribute. I am trying to compute a total match score for each player-team combination. I was able to do this quite easily with python but am struggling to accomplish the same in SQL.
In Python this would be a simple for loop with logical operators comparing each cell of one dataframe to each cell of another, but the lack of positional referencing in SQL makes this a lot trickier to do and generalize (be able to use the same queries for other pairs of tables with different attributes).
So far I have
BEGIN
FOR c in (SELECT column_name FROM all_tab_columns WHERE table_name = 'teams')
LOOP
INSERT INTO match_table (players.Name, candidates.c)
SELECT players.Name, players.c WHERE players.c = teams.c
END LOOP;
BEGIN
FOR c IN (SELECT column_name FROM all_tab_columns WHERE table_name = 'weights')
LOOP
UPDATE match_table
SET match_table.c = (SELECT weights.c FROM weights WHERE match_table.c = weights.c)
END LOOP;
From what I can tell that will generate a table of player names with a single column corresponding to a match to a team attribute populated by the corresponding weight and all other columns full of null values. If that is the case, I can group by name to create a singular record with all matches and corresponding weights.
The script should loop through each player and team and compare the attributes of the player with those desired by the team. Where there is a match a new row should be added to the match_table with the player name and nulls values except for the column that matched. That should be done for each player-team attribute match. Then those matches should be replaced by the corresponding weight from the weight table. I would then like to sum those to get a total match score. I can't use the '+' operator because the column nammes will vary. They will always match between the three tables, but there will be varied attributes of interest.
The expected output would look something like:
players.name
Age
Height
Free_Throw_Perc
...
'Alice'
5
NULL
NULL
...
'Alice'
NULL
10
NULL
...
How would I then sum across each record to find the total match score of each candidate for a team?
If you have the sample data:
CREATE TABLE teams ( id, name ) AS
SELECT 1, 'Alpha' FROM DUAL UNION ALL
SELECT 2, 'Beta' FROM DUAL UNION ALL
SELECT 3, 'Gamma' FROM DUAL;
CREATE TABLE players (name, team, age, height, free_throw_perc) AS
SELECT 'Alice', 1, 20, 160, 90 FROM DUAL UNION ALL
SELECT 'Betty', 1, 21, 165, 80 FROM DUAL UNION ALL
SELECT 'Carol', 1, 22, 170, 70 FROM DUAL UNION ALL
SELECT 'Debra', 2, 23, 175, 60 FROM DUAL UNION ALL
SELECT 'Emily', 2, 24, 180, 50 FROM DUAL UNION ALL
SELECT 'Fiona', 2, 25, 185, 40 FROM DUAL UNION ALL
SELECT 'Gerri', 3, 26, 190, 30 FROM DUAL UNION ALL
SELECT 'Heidi', 3, 27, 195, 20 FROM DUAL UNION ALL
SELECT 'Irene', 3, 28, 200, 10 FROM DUAL;
CREATE TABLE weights(team, key, weight) AS
SELECT 1, 'AGE', 1.0 FROM DUAL UNION ALL
SELECT 1, 'HEIGHT', 0.5 FROM DUAL UNION ALL
SELECT 1, 'FREE_THROW_PERC', 0.2 FROM DUAL UNION ALL
SELECT 2, 'AGE', 0.0 FROM DUAL UNION ALL
SELECT 2, 'HEIGHT', 1.0 FROM DUAL UNION ALL
SELECT 2, 'FREE_THROW_PERC', 0.8 FROM DUAL UNION ALL
SELECT 3, 'AGE', 0.5 FROM DUAL UNION ALL
SELECT 3, 'HEIGHT', 0.5 FROM DUAL UNION ALL
SELECT 3, 'FREE_THROW_PERC', 1.0 FROM DUAL;
And you want to insert the sum of the weight column from the weights table multiplied by the respective value in the players table into the following table:
CREATE TABLE match_table(
team INT,
value NUMBER
);
Then you can use the following INSERT query:
INSERT INTO match_table (team, value)
SELECT p.team,
SUM(p.value * w.weight)
FROM ( SELECT name, team, key, value
FROM players
UNPIVOT ( value FOR key IN (age, height, free_throw_perc) )
) p
INNER JOIN weights w
ON ( p.team = w.team AND p.key = w.key )
GROUP BY p.team
Then the table will contain the weighted totals:
TEAM
VALUE
2
660
3
393
1
358.5
fiddle
And if your match_table is:
CREATE TABLE match_table(
player VARCHAR2(20),
team INT,
age NUMBER,
height NUMBER,
free_throw_perc NUMBER,
total NUMBER
);
Then you can use the query (and calculate the total with the + operator):
INSERT INTO match_table (player, team, age, height, free_throw_perc, total)
SELECT p.name,
p.team,
p.age * w.age_weight,
p.height * w.height_weight,
p.free_throw_perc * w.free_throw_perc_weight,
p.age * w.age_weight
+ p.height * w.height_weight
+ p.free_throw_perc * w.free_throw_perc_weight
FROM players p
INNER JOIN (
SELECT *
FROM weights
PIVOT (
MAX(weight)
FOR key IN (
'AGE' AS age_weight,
'HEIGHT' AS height_weight,
'FREE_THROW_PERC' AS free_throw_perc_weight
)
)
) w
ON (p.team = w.team)
Which gives the values:
PLAYER
TEAM
AGE
HEIGHT
FREE_THROW_PERC
TOTAL
Alice
1
20
80
18
118
Betty
1
21
82.5
16
119.5
Carol
1
22
85
14
121
Debra
2
0
175
48
223
Emily
2
0
180
40
220
Fiona
2
0
185
32
217
Gerri
3
13
95
30
138
Heidi
3
13.5
97.5
20
131
Irene
3
14
100
10
124
fiddle
Or, if the players are uncorrelated to a team then:
INSERT INTO match_table (player, team, age, height, free_throw_perc, total)
SELECT p.name,
w.team,
p.age * w.age_weight,
p.height * w.height_weight,
p.free_throw_perc * w.free_throw_perc_weight,
p.age * w.age_weight
+ p.height * w.height_weight
+ p.free_throw_perc * w.free_throw_perc_weight
FROM players p
CROSS JOIN (
SELECT *
FROM weights
PIVOT (
MAX(weight)
FOR key IN (
'AGE' AS age_weight,
'HEIGHT' AS height_weight,
'FREE_THROW_PERC' AS free_throw_perc_weight
)
)
) w
Which, for the sample data, outputs:
PLAYER
TEAM
AGE
HEIGHT
FREE_THROW_PERC
TOTAL
Alice
1
20
80
18
118
Alice
2
0
160
72
232
Alice
3
10
80
90
180
Betty
1
21
82.5
16
119.5
Betty
2
0
165
64
229
Betty
3
10.5
82.5
80
173
Carol
1
22
85
14
121
Carol
2
0
170
56
226
Carol
3
11
85
70
166
Debra
1
23
87.5
12
122.5
Debra
2
0
175
48
223
Debra
3
11.5
87.5
60
159
Emily
1
24
90
10
124
Emily
2
0
180
40
220
Emily
3
12
90
50
152
Fiona
1
25
92.5
8
125.5
Fiona
2
0
185
32
217
Fiona
3
12.5
92.5
40
145
Gerri
1
26
95
6
127
Gerri
2
0
190
24
214
Gerri
3
13
95
30
138
Heidi
1
27
97.5
4
128.5
Heidi
2
0
195
16
211
Heidi
3
13.5
97.5
20
131
Irene
1
28
100
2
130
Irene
2
0
200
8
208
Irene
3
14
100
10
124
fiddle

Using aggregate on another aggregate function - MAX() on an aggregate

I've a tournament bracket consisting of 2 Groups (Group A and Group B).
I already have a query where I retreive some information such as the average rating, amount of teams etc.
To the problem: I don't want to allow 2 teams of the same group having the same color. I want a column representing the MAX() of ColorInterference without having to make a subselect.
Is this possible or am I forced to make a SELECT MAX(ColorInterference) over the query result?
Group
Team
Color
Rating
TeamsCount
AverageRating
AverageRatingGroup
ColorInterference
A
Helos
Green
1452
8
1518
1544
0
A
Pelicans
Purple
1687
8
1518
1544
0
A
Epic Square Dance
Red
1498
8
1518
1544
0
A
Narnia Ninjas
Yellow
1542
8
1518
1544
0
B
O.T.
Blue
1502
8
1518
1492
0
B
Helos
Green
1452
8
1518
1492
1
B
Treasure Goggles
Green
1485
8
1518
1492
1
B
Red Off
Yellow
1530
8
1518
1492
0
DECLARE #Bracket_Groups Table ([Group] nvarchar(10), Team nvarchar(50), Color nvarchar(50), Rating int)
INSERT INTO #Bracket_Groups(Team, [Group], Color, Rating)
SELECT 'Narnia Ninjas', 'A', 'Yellow' , 1542
UNION SELECT 'Helos', 'A', 'Green', 1452
UNION SELECT 'Pelicans', 'A', 'Purple', 1687
UNION SELECT 'Epic Square Dance', 'A', 'Red', 1498
UNION SELECT 'O.T.', 'B', 'Blue', 1502
UNION SELECT 'Red Off', 'B', 'Yellow', 1530
UNION SELECT 'Helos', 'B', 'Green', 1452
UNION SELECT 'Treasure Goggles', 'B', 'Green', 1485
SELECT
[Group]
, Team
, Color
, Rating
, COUNT(*) OVER() as TeamsCount
, AVG(Rating) OVER () as AverageRating
, AVG(Rating) OVER (Partition By [Group]) as Group_AverageRating
, SIGN(COUNT(Color) OVER (partition By [Group], Color) - 1) as ColorInterference
FROM #Bracket_Groups
Order by [Group]

How to pass SQL values from one to multiple columns in SSRS Report?

So I have stored procedure which returns data in following:
GroupId FieldName Value
1 Foo 28
1 Bar 2
1 FooBar 11
1 Bizz 22
1 UserName John Smith
2 Foo 4
2 Bar 13
2 FooBar 27
2 Bizz 23
2 UserName Peter Jones
3 Foo 5
3 Bar 4
3 FooBar 12
3 Bizz 18
3 UserName Lisa Johnson
...........
As you see in sample data above, there is 3 groups (1, 2 & 3 (for real there are 10 groups)). Each group have the same field names in FieldName column (It's like header) and in Value column are stored values.
I need to create SSRS Report in following:
Foo 28 4 5
Bar 2 13 4
FooBar 11 27 12
Bizz 22 23 18
UserName John Smith Peter Jones Lisa Johnson
As you see above, each group should be in different column and headers stored in left side (1st column).
In 1st column I've passed FieldName;
In 2nd column I've passed expression: =IIF(Fields!GroupId.Value = 1, Fields!Value.Value, "")
In 3rd column I've passed expression: =IIF(Fields!GroupId.Value = 2, Fields!Value.Value, "")
In 4th column I've passed expression: =IIF(Fields!GroupId.Value = 3, Fields!Value.Value, "")
But I achieve output like this:
Foo 28
Bar 2
FooBar 11
Bizz 22
UserName John Smith
Foo 4
Bar 13
FooBar 27
Bizz 23
UserName Peter Jones
Foo 5
Bar 4
FooBar 12
Bizz 18
UserName Lisa Johnson
Have you any ideas, what's wrong? Should I try to do something with groups? I've tried but also unsuccessfully. If something unclear - ask me, I'll try to provide more details.
If the order if the field name is not important to you then you can do this simply in SSRS using a matrix with a rowgroup of fieldname and a column group of groupid. If you don't want the headers then hide them by changing the text box visibility option.
You can achieve this output via pivoting in SQL Server.
At first I create temptable with your input data:
CREATE TABLE #temptable (
GroupId int,
FieldName nvarchar(max),
[Value] nvarchar(max)
)
INSERT INTO #temptable VALUES
(1, 'Foo', '28'),
(1, 'Bar', '2'),
(1, 'FooBar', '11'),
(1, 'Bizz', '22'),
(1, 'UserName', 'John Smith'),
(2, 'Foo', '4'),
(2, 'Bar', '13'),
(2, 'FooBar', '27'),
(2, 'Bizz', '23'),
(2, 'UserName', 'Peter Jones'),
(3, 'Foo', '5'),
(3, 'Bar', '4'),
(3, 'FooBar', '12'),
(3, 'Bizz', '18'),
(3, 'UserName', 'Lisa Johnson')
Then I use dynamic SQL because we dont know how many GroupID's there are:
DECLARE #columns nvarchar(max), #sql nvarchar(max)
--Here we create a string like '[1],[2],[3]' named by GroupID's
--because if there are many groupid's - manually assign columns for pivot
--will be a long process
SELECT #columns = STUFF((SELECT DISTINCT ','+QUOTENAME(GroupId) FROM #temptable FOR XML PATH('')),1,1,'')
--Create sql statement to execute
SELECT #sql = '
SELECT *
FROM (
SELECT *
FROM #temptable
) as p
PIVOT(
MAX([Value]) FOR GroupId IN ('+#columns+')
) as pvt'
--And execute!
EXEC(#sql)
Output:
FieldName 1 2 3
Bar 2 13 4
Bizz 22 23 18
Foo 28 4 5
FooBar 11 27 12
UserName John Smith Peter Jones Lisa Johnson
Use the below SQL logic & pivot the data at report level ...
;WITH CTE AS (
SELECT 1 GroupId ,'Foo' FieldName,'28' Value
UNION ALL
SELECT 1 ,'Bar' ,'2'
UNION ALL
SELECT 1 ,'FooBar' ,'11'
UNION ALL
SELECT 1 ,'Bizz' ,'22'
UNION ALL
SELECT 1 ,'UserName' ,'John Smith'
UNION ALL
SELECT 2 ,'Foo' ,'4'
UNION ALL
SELECT 2 ,'Bar' ,'13'
UNION ALL
SELECT 2 ,'FooBar' ,'27'
UNION ALL
SELECT 2 ,'Bizz' ,'23'
UNION ALL
SELECT 2 ,'UserName' ,'Peter Jones'
UNION ALL
SELECT 3 ,'Foo' ,'5'
UNION ALL
SELECT 3 ,'Bar' ,'4'
UNION ALL
SELECT 3 ,'FooBar' ,'12'
UNION ALL
SELECT 3 ,'Bizz' ,'18'
UNION ALL
SELECT 3 ,'UserName' ,'Lisa Johnson'
)
SELECT FieldName, CTE.Value
FROM CTE
CROSS APPLY ( SELECT CTE.Value )A
ORDER BY FieldName

Showing History of changes from a History table

I have a History table, which is created by Insert and Update triggers. The History row contains the row as it was on the Insert/Update.
What I am being asked to do is show the changes for each user through time. So, below is what I have in the form of my History table, and then, I created a dummy expected results.
DECLARE #MyTable TABLE
(
id INT NOT NULL IDENTITY(1,1),
userId INT NOT NULL,
locationId INT NOT NULL,
roleId INT NOT NULL,
lastUpdateUserId INT NOT NULL,
lastUpdateDate DATETIME NOT NULL
)
INSERT INTO #MyTable
(userId, locationId, roleId, lastUpdateUserId, lastUpdateDate)
SELECT 1, 1000, 1, 7, GETDATE()+1 UNION
SELECT 2, 1100, 5, 9, GETDATE()+2 UNION
SELECT 2, 1110, 5, 6, GETDATE()+3 UNION
SELECT 1, 1100, 3, 6, GETDATE()+4 UNION
SELECT 4, 1500, 5, 8, GETDATE()+5 UNION
SELECT 7, 1000, 8, 9, GETDATE()+6 UNION
SELECT 7, 1100, 9, 9, GETDATE()+7 UNION
SELECT 1, 1000, 3, 7, GETDATE()+8 UNION
SELECT 9, 1100, 5, 2, GETDATE()+9 UNION
SELECT 9, 1100, 6, 5, GETDATE()+10
SELECT * FROM #MyTable ORDER BY Id
DECLARE #ExpectedResult TABLE
(
ChangeType CHAR(1), -- I=Insert, U=Update
UserId INT,
ChangeDate DATETIME,
ChangedByUser INT,
FieldName VARCHAR(20),
OldValue INT,
NewValue INT
)
INSERT INTO #ExpectedResult
(ChangeType, UserId, ChangeDate, ChangedByUser, FieldName, OldValue, NewValue)
SELECT 'I', 1, '2015-APR-30 09:56:28', 7, 'locationId', NULL, 1000 UNION -- Row1
SELECT 'I', 1, '2015-APR-30 09:56:28', 7, 'roleId', NULL, 1 UNION -- Row1
SELECT 'U', 1, '2015-APR-07 10:27:42', 7, 'roleId', 1, 3 UNION -- Row 2
SELECT 'U', 1, '2015-MAY-03 10:27:42', 6, 'locationId', 1000, 1100 UNION -- Row 3
SELECT 'I', 2, '2015-MAY-01 10:27:42', 9, 'roleId', NULL, 5 UNION -- Row5
SELECT 'I', 2, '2015-MAY-01 10:27:42', 9, 'locationId', NULL, 1100 -- Row5
SELECT * FROM #ExpectedResult
#MyTable has the data as it is at the moment. I am trying to transform that into #ExpectedResults. We're reporting on changes to roleId and locationId. On each change, it needs to have a separate line for each column. So, on insert, we have two lines (As we monitor changes to both fields). When one column is updated, it needs to be represented as one 'U' line. If both fields are updated in the same UPDATE statement, then that would result in two update rows in #Expected.
I started with a Cursor, but was hoping there would be a more efficient way to achieve this.
To get the roleID and locationID on separate rows you can use a simple UNION ALL.
And to combine the old and new values use the ROW_NUMBER() window function, like this:
;with t as(
select *,
ROW_NUMBER() OVER(partition by userid Order BY lastUpdateDate) rn
from #MyTable
),
a as (
select userId, 'locationId' as fieldname,
locationId as value, lastUpdateUserId, lastUpdateDate, rn
from t
UNION ALL
select userId, 'roleId' as fieldname,
roleId as value, lastUpdateUserId, lastUpdateDate, rn
from t
)
select CASE WHEN a2.userId IS NULL THEN 'I' ELSE 'U' END as ChangeType,
a1.userId, a1.lastUpdateDate, a1.lastUpdateUserId, a1.fieldname, a1.value as newValue, a2.value as oldvalue
FROM a a1 LEFT JOIN a a2
ON a1.userId = a2.userId and a1.fieldname = a2.fieldname
AND a1.rn = a2.rn+1
order by 2,3,5
The a1 alias in the query above contains the "new values", the a2 contains the "old values". When you use the real data you will also need to partition by the fieldname (and perhaps table name) and also to join by them
The result:
ChangeType userId lastUpdateDate lastUpdateUserId fieldname newValue oldvalue
---------- ----------- ----------------------- ---------------- ---------- ----------- -----------
I 1 2015-04-30 12:20:59.183 7 locationId 1000 NULL
I 1 2015-04-30 12:20:59.183 7 roleId 1 NULL
U 1 2015-05-03 12:20:59.183 6 locationId 1100 1000
U 1 2015-05-03 12:20:59.183 6 roleId 3 1
U 1 2015-05-07 12:20:59.183 7 locationId 1000 1100
U 1 2015-05-07 12:20:59.183 7 roleId 3 3
I 2 2015-05-01 12:20:59.183 9 locationId 1100 NULL
I 2 2015-05-01 12:20:59.183 9 roleId 5 NULL
U 2 2015-05-02 12:20:59.183 6 locationId 1110 1100
U 2 2015-05-02 12:20:59.183 6 roleId 5 5
I 4 2015-05-04 12:20:59.183 8 locationId 1500 NULL
I 4 2015-05-04 12:20:59.183 8 roleId 5 NULL
I 7 2015-05-05 12:20:59.183 9 locationId 1000 NULL
I 7 2015-05-05 12:20:59.183 9 roleId 8 NULL
U 7 2015-05-06 12:20:59.183 9 locationId 1100 1000
U 7 2015-05-06 12:20:59.183 9 roleId 9 8
I 9 2015-05-08 12:20:59.183 2 locationId 1100 NULL
I 9 2015-05-08 12:20:59.183 2 roleId 5 NULL
U 9 2015-05-09 12:20:59.183 5 locationId 1100 1100
U 9 2015-05-09 12:20:59.183 5 roleId 6 5
(20 row(s) affected)