SQL: select max(value) when change columns in table - sql

Sorry if the title is confusing. I have a problem when Select from 2 table. I have 2 table like that.
Table 1: contains the column names of Table 2
+ Pkey | name1 | name2 +
+----------------------+
| 1 | a | b |
+----------------------+
| 2 | c | b |
Table 2: contains values
+ Pkey | a | b | c +
+----------------------+------+
| 1 | 10 | 2 | 7 |
+----------------------+------+
| 2 | 12 | 4 | 8 |
+----------------------+------+
| 3 | 8 | 2 | 4 |
+----------------------+------+
| 4 | 7 | 1 | 3 |
I want to get the max(value) from the table 2 and add when select table 1
Example: With first row of table 1 contains 2 values : a and b. From these two values, we refer to table 2 to calculated column a - column b is [8,8,6,6]. After getting the max value of this column is 8 and add when query table 1. Keep going with the next rows
Desired table:
+ Pkey | name1 | name2 | Desired column |
+----------------------+-------------------+
| 1 | a | b | 8 |
+----------------------+-------------------+
| 2 | c | b | 5 |
I have more than 10000 rows in table 1. I used function and It can not use dynamic in Function

One possible approach is to generate dynamic SQL:
-- Tables
CREATE TABLE #Table1 (
Pkey int,
name1 varchar(1),
name2 varchar(1)
)
INSERT INTO #Table1 (Pkey, name1, name2)
VALUES
(1, 'a', 'b'),
(2, 'c', 'b')
CREATE TABLE #Table2 (
Pkey int,
a int,
b int,
c int
)
INSERT INTO #Table2 (Pkey, a,b, c)
VALUES
(1, 10, 2, 7),
(2, 12, 4, 8),
(3, 8, 2, 4),
(4, 7, 1, 3)
-- Statement
DECLARE #stm nvarchar(max)
SET #stm = N''
SELECT #stm = #stm +
N'UNION ALL
SELECT
' + STR(Pkey) + ' AS Pkey,
''' + name1 + ''' AS name1,
''' + name2 + ''' AS name2, ' +
'PkeyMax = (SELECT MAX(' + name1 + ' - ' + name2 + ') FROM #Table2) '
FROM #Table1
SELECT #stm = STUFF(#stm, 1, 10, '')
-- Execution
EXEC (#stm)
Output:
Pkey name1 name2 PkeyMax
1 a b 8
2 c b 5

This gets the results you want, since there are few fields, makes sense to use CASE to get the ones you want (in order to avoid building dynamic SQL)
SELECT pkey,name1,name2,max(dif) FROM
(SELECT t1.pkey, t1.name1, t1.name2,
case when t1.name1 ='a' then t2.a
when t1.name1 ='b' then t2.b
when t1.name1 ='c' then t2.c
end
-
case when t1.name2 ='a' then t2.a
when t1.name2 ='b' then t2.b
when t1.name2 ='c' then t2.c
end dif
FROM Table1 t1 , Table2 t2) IQ
GROUP BY IQ.pkey, IQ.name1, IQ.name2

Related

SQL Server recursive query to show path of parents

I am working with SQL Server statements and have one table like:
| item | value | parentItem |
+------+-------+------------+
| 1 | 2test | 2 |
| 2 | 3test | 3 |
| 3 | 4test | 4 |
| 5 | 1test | 1 |
| 6 | 3test | 3 |
| 7 | 2test | 2 |
And I would like to get the below result using a SQL Server statement:
| item1 | value1 |
+-------+--------------------------+
| 1 | /4test/3test/2test |
| 2 | /4test/3test |
| 3 | /4test |
| 5 | /4test/3test/2test/1test |
| 6 | /4test/3test |
| 7 | /4test/3test/2test |
I didn't figure out the correct SQL to get all the values for all the ids according to parentItem.
I have tried this SQL :
with all_path as
(
select item, value, parentItem
from table
union all
select a.item, a.value, a.parentItem
from table a, all_path b
where a.item = b.parentItem
)
select
item as item1,
stuff(select '/' + value
from all_path
order by item asc
for xml path ('')), 1, 0, '') as value1
from
all_path
But got the "value1" column in result like
/4test/4test/4test/3test/3test/3test/3test/2test/2test/2test/2test
Could you please help me with that? Thanks a lot.
based on the expected output you gave, use the recursive part to concatenate the value
;with yourTable as (
select item, value, parentItem
from (values
(1,'2test',2)
,(2,'3test',3)
,(3,'4test',4)
,(5,'1test',1)
,(6,'3test',3)
,(7,'2test',2)
)x (item,value,parentItem)
)
, DoRecursivePart as (
select 1 as Pos, item, convert(varchar(max),value) value, parentItem
from yourTable
union all
select drp.pos +1, drp.item, convert(varchar(max), yt.value + '/' + drp.value), yt.parentItem
from yourTable yt
inner join DoRecursivePart drp on drp.parentItem = yt.item
)
select drp.item, '/' + drp.value
from DoRecursivePart drp
inner join (select item, max(pos) mpos
from DoRecursivePart
group by item) [filter] on [filter].item = drp.item and [filter].mpos = drp.Pos
order by item
gives
item value
----------- ------------------
1 /4test/3test/2test
2 /4test/3test
3 /4test
5 /4test/3test/2test/1test
6 /4test/3test
7 /4test/3test/2test
Here's the sample data
drop table if exists dbo.test_table;
go
create table dbo.test_table(
item int not null,
[value] varchar(100) not null,
parentItem int not null);
insert dbo.test_table values
(1,'test1',2),
(2,'test2',3),
(3,'test3',4),
(5,'test4',1),
(6,'test5',3),
(7,'test6',2);
Here's the query
;with recur_cte(item, [value], parentItem, h_level) as (
select item, [value], parentItem, 1
from dbo.test_table tt
union all
select rc.item, tt.[value], tt.parentItem, rc.h_level+1
from dbo.test_table tt join recur_cte rc on tt.item=rc.parentItem)
select rc.item,
stuff((select '/' + cast(parentItem as varchar)
from recur_cte c2
where rc.item = c2.item
order by h_level desc FOR XML PATH('')), 1, 1, '') [value1]
from recur_cte rc
group by item;
Here's the results
item value1
1 4/3/2
2 4/3
3 4
5 4/3/2/1
6 4/3
7 4/3/2

SQL: Pick highest and lowest value (int) from one row

I am looking for a way to pick the highest and lowest value (integer) from a single row in table. There are 4 columns that i need to compare together and get highest and lowest number there is.
The table looks something like this...
id | name | col_to_compare1 | col_to_compare2 | col_to_compare3 | col_to_compare4
1 | John | 5 | 5 | 2 | 1
2 | Peter | 3 | 2 | 4 | 1
3 | Josh | 3 | 5 | 1 | 3
Can you help me, please? Thanks!
You can do this using CROSS APPLY and the VALUES clause. Use VALUES to group all your compared columns and then select the max.
SELECT
MAX(d.data1) as MaxOfColumns
,MIN(d.data1) as MinOfColumns
,a.id
,a.name
FROM YOURTABLE as a
CROSS APPLY (
VALUES(a.col_to_compare1)
,(a.col_to_compare2)
,(a. col_to_compare3)
,(a.col_to_compare4)
,(a. col_to_compare5)
) as d(data1) --Name the Column
GROUP BY a.id
,a.name
Assuming you are looking for min/max per row
Declare #YourTable table (id int,name varchar(50),col_to_compare1 int,col_to_compare2 int,col_to_compare3 int,col_to_compare4 int)
Insert Into #YourTable values
(1,'John',5,5,2,1),
(2,'Peter',3,2,4,1),
(3,'Josh',3,5,1,3)
Select A.ID
,A.Name
,MinVal = min(B.N)
,MaxVal = max(B.N)
From #YourTable A
Cross Apply (Select N From (values(a.col_to_compare1),(a.col_to_compare2),(a.col_to_compare3),(a.col_to_compare4)) N(N) ) B
Group By A.ID,A.Name
Returns
ID Name MinVal MaxVal
1 John 1 5
3 Josh 1 5
2 Peter 1 4
These solutions keep the current rows and add additional columns of min/max.
select *
from t cross apply
(select min(col) as min_col
,max(col) as max_col
from (
values
(t.col_to_compare1)
,(t.col_to_compare2)
,(t.col_to_compare3)
,(t.col_to_compare4)
) c(col)
) c
OR
select *
,cast ('' as xml).value ('min ((sql:column("t.col_to_compare1"),sql:column("t.col_to_compare2"),sql:column("t.col_to_compare3"),sql:column("t.col_to_compare4")))','int') as min_col
,cast ('' as xml).value ('max ((sql:column("t.col_to_compare1"),sql:column("t.col_to_compare2"),sql:column("t.col_to_compare3"),sql:column("t.col_to_compare4")))','int') as max_col
from t
+----+-------+-----------------+-----------------+-----------------+-----------------+---------+---------+
| id | name | col_to_compare1 | col_to_compare2 | col_to_compare3 | col_to_compare4 | min_col | max_col |
+----+-------+-----------------+-----------------+-----------------+-----------------+---------+---------+
| 1 | John | 5 | 5 | 2 | 1 | 1 | 5 |
+----+-------+-----------------+-----------------+-----------------+-----------------+---------+---------+
| 2 | Peter | 3 | 2 | 4 | 1 | 1 | 4 |
+----+-------+-----------------+-----------------+-----------------+-----------------+---------+---------+
| 3 | Josh | 3 | 5 | 1 | 3 | 1 | 5 |
+----+-------+-----------------+-----------------+-----------------+-----------------+---------+---------+
A way to do this is to "break" apart the data
declare #table table (id int, name varchar(10), col1 int, col2 int, col3 int, col4 int)
insert into #table values (1 , 'John' , 5 , 5 , 2 , 1)
insert into #table values (2 , 'Peter' , 3 , 2 , 4 , 1)
insert into #table values (3 , 'Josh' , 3 , 5 , 1 , 3)
;with stretch as
(
select id, col1 as col from #table
union all
select id, col2 as col from #table
union all
select id, col3 as col from #table
union all
select id, col4 as col from #table
)
select
t.id,
t.name,
agg.MinCol,
agg.MaxCol
from #table t
inner join
(
select
id, min(col) as MinCol, max(col) as MaxCol
from stretch
group by id
) agg
on t.id = agg.id
Seems simple enough
SELECT min(col1), max(col1), min(col2), max(col2), min(col3), max(col3), min(col4), max(col4) FROM table
Gives you the Min and Max for each column.
Following OP's comment, I believe he may be looking for a min/max grouped by the person being queried against.
So that would be:
SELECT name, min(col1), max(col1), min(col2), max(col2), min(col3), max(col3), min(col4), max(col4) FROM table GROUP BY name

SQL SELECT: concatenated column with line breaks and heading per group

I have the following SQL result from a SELECT query:
ID | category| value | desc
1 | A | 10 | text1
2 | A | 11 | text11
3 | B | 20 | text20
4 | B | 21 | text21
5 | C | 30 | text30
This result is stored in a temporary table named #temptab. This temporary table is then used in another SELECT to build up a new colum via string concatenation (don't ask me about the detailed rationale behind this. This is code I took from a colleague). Via FOR XML PATH() the output of this column is a list of the results and is then used to send mails to customers.
The second SELECT looks as follows:
SELECT t1.column,
t2.column,
(SELECT t.category + ' | ' + t.value + ' | ' + t.desc + CHAR(9) + CHAR(13) + CHAR(10)
FROM #temptab t
WHERE t.ID = ttab.ID
FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)') AS colname
FROM table1 t1
...
INNER JOIN #temptab ttab on ttab.ID = someOtherTable.ID
...
Without wanting to go into too much detail, the column colname becomes populated with several entries (due to multiple matches) and hence, a longer string is stored in this column (CHAR(9) + CHAR(13) + CHAR(10) is essentially a line break). The result/content of colname looks like this (it is used to send mails to customers):
A | 10 | text1
A | 11 | text11
B | 20 | text20
B | 21 | text21
C | 30 | text30
Now I would like to know, if there is a way to more nicely format this output string. The best case would be to group the same categories together and add a heading and empty line between different categories:
*A*
A | 10 | text1
A | 11 | text11
*B*
B | 20 | text20
B | 21 | text21
*C*
C | 30 | text30
My question is: How do I have to modify the above query (especially the string-concatenation-part) to achieve above formatting? I was thinking about using a GROUP BY statement, but this obviously does not yield the desired result.
Edit: I use Microsoft SQL Server 2008 R2 (SP2) - 10.50.4270.0 (X64)
Declare #YourTable table (ID int,category varchar(50),value int, [desc] varchar(50))
Insert Into #YourTable values
(1,'A',10,'text1'),
(2,'A',11,'text11'),
(3,'B',20,'text20'),
(4,'B',21,'text21'),
(5,'C',30,'text30')
Declare #String varchar(max) = ''
Select #String = #String + Case when RowNr=1 Then Replicate(char(13)+char(10),2) +'*'+Category+'*' Else '' end
+ char(13)+char(10) + category + ' | ' + cast(value as varchar(25)) + ' | ' + [desc]
From (
Select *
,RowNr=Row_Number() over (Partition By Category Order By Value)
From #YourTable
) A Order By Category, Value
Select Substring(#String,5,Len(#String))
Returns
*A*
A | 10 | text1
A | 11 | text11
*B*
B | 20 | text20
B | 21 | text21
*C*
C | 30 | text30
This should return what you want
Declare #YourTable table (ID int,category varchar(50),value int, [desc] varchar(50))
Insert Into #YourTable values
(1,'A',10,'text1'),
(2,'A',11,'text11'),
(3,'B',20,'text20'),
(4,'B',21,'text21'),
(5,'C',30,'text30');
WITH Categories AS
(
SELECT category
,'**' + category + '**' AS CatCaption
,ROW_NUMBER() OVER(ORDER BY category) AS CatRank
FROM #YourTable
GROUP BY category
)
,Grouped AS
(
SELECT c.CatRank
,0 AS ValRank
,c.CatCaption AS category
,-1 AS ID
,'' AS Value
,'' AS [desc]
FROM Categories AS c
UNION ALL
SELECT c.CatRank
,ROW_NUMBER() OVER(PARTITION BY t.category ORDER BY t.Value)
,t.category
,t.ID
,CAST(t.value AS VARCHAR(100))
,t.[desc]
FROM #YourTable AS t
INNER JOIN Categories AS c ON t.category=c.category
)
SELECT category,Value,[desc]
FROM Grouped
ORDER BY CatRank,ValRank
The result
category Value desc
**A**
A 10 text1
A 11 text11
**B**
B 20 text20
B 21 text21
**C**
C 30 text30

SQL Pivoting or Transposing or ... column to row?

I have a question and this looks way better in SQLfiddle:
http://www.sqlfiddle.com/#!3/dffa1/2
I have a table with multirows for each user with datestamp and test results and i would like to transpose or pivot it into one line result as follows where each user has listed all time and value results:
USERID PSA1_time PSA1_result PSA2_time PSA2_result PSA3_time PSA3_result ...
1 1999-.... 2 1998... 4 1999... 6
3 1992... 4 1994 6
4 2006 ... 8
Table below:
CREATE TABLE yourtable
([userid] int, [Ranking] int,[test] varchar(3), [Date] datetime, [result] int)
;
INSERT INTO yourtable
([userid], [Ranking],[test], [Date], [result])
VALUES
('1', '1', 'PSA', 1997-05-20, 2),
('1', '2','PSA', 1998-05-07, 4),
('1', '3','PSA', 1999-06-08, 6),
('1', '4','PSA', 2001-06-08, 8),
('1', '5','PSA', 2004-06-08, 0),
('3', '1','PSA', 1992-05-07, 4),
('3', '2','PSA', 1994-06-08, 6),
('4', '1','PSA', 2006-06-08, 8)
;
Since you want to PIVOT two columns my suggestion would be to unpivot the date and result columns first, then apply the PIVOT function.
The unpivot process will convert the two columns date and result into multiple rows:
select userid,
col = test +'_'+cast(ranking as varchar(10))+'_'+ col,
value
from yourtable t1
cross apply
(
select 'time', convert(varchar(10), date, 120) union all
select 'result', cast(result as varchar(10))
) c (col, value)
See Demo. This will give you a result:
| USERID | COL | VALUE |
--------------------------------------
| 1 | PSA_1_time | 1997-05-20 |
| 1 | PSA_1_result | 2 |
| 1 | PSA_2_time | 1998-05-07 |
| 1 | PSA_2_result | 4 |
| 1 | PSA_3_time | 1999-06-08 |
Now that you have the data in this format, then you can apply pivot to get the max/min value for each item in col:
If you have a limited number of columns, then you can hard-code the query:
select *
from
(
select userid,
col = test +'_'+cast(ranking as varchar(10))+'_'+ col,
value
from yourtable t1
cross apply
(
select 'time', convert(varchar(10), date, 120) union all
select 'result', cast(result as varchar(10))
) c (col, value)
) d
pivot
(
max(value)
for col in (PSA_1_time, PSA_1_result,
PSA_2_time, PSA_2_result,
PSA_3_time, PSA_3_result,
PSA_4_time, PSA_4_result,
PSA_5_time, PSA_5_result)
) piv;
See SQL Fiddle with Demo
If you have unknown columns, then you will need to use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(test +'_'+cast(ranking as varchar(10))+'_'+ col)
from yourtable
cross apply
(
select 'time', 1 union all
select 'result', 2
) c (col, so)
group by test, ranking, col, so
order by Ranking, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT userid,' + #cols + '
from
(
select userid,
col = test +''_''+cast(ranking as varchar(10))+''_''+ col,
value
from yourtable t1
cross apply
(
select ''time'', convert(varchar(10), date, 120) union all
select ''result'', cast(result as varchar(10))
) c (col, value)
) x
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo. Both versions will give a result:
| USERID | PSA_1_TIME | PSA_1_RESULT | PSA_2_TIME | PSA_2_RESULT | PSA_3_TIME | PSA_3_RESULT | PSA_4_TIME | PSA_4_RESULT | PSA_5_TIME | PSA_5_RESULT |
------------------------------------------------------------------------------------------------------------------------------------------------------
| 1 | 1997-05-20 | 2 | 1998-05-07 | 4 | 1999-06-08 | 6 | 2001-06-08 | 8 | 2004-06-08 | 0 |
| 3 | 1992-05-07 | 4 | 1994-06-08 | 6 | (null) | (null) | (null) | (null) | (null) | (null) |
| 4 | 2006-06-08 | 8 | (null) | (null) | (null) | (null) | (null) | (null) | (null) | (null) |

Insert when NOT matched - T/SQL

I have 3 tables with same 5 columns (+ 1 flag column in T2 which we will use later...let's focus on 5 columns for this example).
T1, T2, and T3.
1st column in all three tables are Key Columns.
Let's say there are 5 records in T1 and T2.
4 records in T1 and T2 match for all columns.
5th record has 4 matching columns (1 key + 3 non-key). This means, T1 and T2 has 1 non-matching non-key column for the 5th record.
I want to do nothing with the first 4 columns
I want to insert the 5th column from T2 to T3 and update the 6th column of T2 to TRUE.
How does one go about doing this? Merge query won't work as it works with 2 tables only as far as I know...correct me if I am wrong.
Just a note: these tables have actually over 100 columns (same columns) sooo...hehe
ThanX
UPDATE: I wish to still pass the values for the first 4 columns from T2 to T3...not just the column that changed.
-- Not tuned to any particular syntax, may need to be tweaked.
T1([a],b,c,d,e);
T2([a],b,c,d,e,f);
T3([a],b,c,d,e);
---------
-- SQL --
---------
BT;
INSERT INTO T3
SELECT
T2.a,
CASE WHEN (T1.b <> T2.b) THEN T2.b ELSE null,
CASE WHEN (T1.c <> T2.c) THEN T2.c ELSE null,
CASE WHEN (T1.d <> T2.d) THEN T2.d ELSE null,
CASE WHEN (T1.e <> T2.e) THEN T2.e ELSE null
FROM
T1,
T2
WHERE T1.a = T2.a
AND (
T1.b <> T2.b
OR T1.c <> T2.c
OR T1.d <> T2.d
OR T1.e <> T2.e
);
UPDATE T2
SET T2.f = true
T1.a IN (
SEL T1.a
FROM T1, T2
WHERE T1.a = T2.a
AND (
T1.b <> T2.b
OR T1.c <> T2.c
OR T1.d <> T2.d
OR T1.e <> T2.e
)
);
ET; -- OR COMMIT depending on sytax
EXAMPLE DATA
-- BEFORE -- -- AFTER --
T1 T1
|[a]| b | c | d | e | |[a]| b | c | d | e |
|---+---+---+---+---| |---+---+---+---+---|
| 0 | 1 | 2 | 3 | 4 | | 0 | 1 | 2 | 3 | 4 |
| 1 | 2 | 3 | 4 | 5 | | 1 | 2 | 3 | 4 | 5 |
| 1 | 2 | 3 | 4 | 5 | | 1 | 2 | 3 | 4 | 5 |
| 3 | 4 | 5 | 6 | 7 | | 3 | 4 | 5 | 6 | 7 |
| 4 | 5 | 6 | 7 | 8 | | 4 | 5 | 6 | 7 | 8 |
T2 T2
|[a]| b | c | d | e | f | |[a]| b | c | d | e | f |
|---+---+---+---+---+---| |---+---+---+---+---+---|
| 0 | 1 | 2 | 3 | 4 | f | | 0 | 1 | 2 | 3 | 4 | f |
| 1 | 2 | 3 | 4 |-3 | f | | 1 | 2 | 3 | 4 |-3 | t |
| 2 | 3 | 4 | 5 | 6 | f | | 2 | 3 | 4 | 5 | 6 | f |
| 3 | 4 | 5 |-5 | 7 | f | | 3 | 4 | 5 |-5 | 7 | t |
| 4 | 5 | 6 | 7 | 8 | f | | 4 | 5 | 6 | 7 | 8 | f |
T3 T3
|[a]| b | c | d | e | |[a]| b | c | d | e |
|---+---+---+---+---| |---+---+---+---+---|
| 1 |nul|nul|nul|-3 |
| 3 |nul|nul|-5 |nul|
that solution is fine for five columns. How about 100 columns. You need dynamic T-SQL.
The below code is quite long and divided into 4 sections.
Section 1 - Create the database, test tables, and test data.
Section 2 - My user defined function for a delimited column list and Jeff Moden's split function.
Section 3 - Dynamic T-SQL, assumes first column is key and last column is flag. Uses EXCEPT command to find row differences.
Section 4 - Dynamic T-SQL, compares column 2 in T1 to column 2 in T2 with case statement. Continued for all columns. Insert into T3 for only rows that have a difference.
-- THIS CODE WILL WORK FOR A DYNAMIC LIST OF COLUMNS, NOT JUST 4! --
-- 1 - Create test tables w/data
-- the master db
use master;
go
-- create test database
create database test;
go
-- use test
use test;
go
-- create table 1
if (OBJECT_ID('t1') <> 0) drop table t1;
go
create table t1
( key1 int, col1 int, col2 int, col3 varchar(16), col4 varchar(16) );
go
-- create table 2
if (OBJECT_ID('t2') <> 0) drop table t2;
go
create table t2
( key1 int, col1 int, col2 int, col3 varchar(16), col4 varchar(16), flag1 int default 0);
go
-- create table 3
if (OBJECT_ID('t3') <> 0) drop table t3;
go
create table t3
( key1 int, col1 int, col2 int, col3 varchar(16), col4 varchar(16) );
go
-- Add 5 rows to t1
insert into t1 values (1, 2, 4, 'A', 'B');
insert into t1 values (2, 4, 8, 'C', 'D');
insert into t1 values (3, 6, 12, 'E', 'F');
insert into t1 values (4, 8, 16, 'G', 'H');
insert into t1 values (5, 10, 20, 'I', 'J');
select * from t1;
-- Add 5 rows to t2
insert into t2 (key1, col1, col2, col3, col4) values (1, 2, 4, 'A', 'B');
insert into t2 (key1, col1, col2, col3, col4) values (2, 4, 8, 'C', 'D');
insert into t2 (key1, col1, col2, col3, col4) values (3, 6, 12, 'E', 'F');
insert into t2 (key1, col1, col2, col3, col4) values (4, 8, 16, 'G', 'H');
insert into t2 (key1, col1, col2, col3, col4) values (5, 10, 20, 'I', 'K');
select * from t2;
--
-- 2A - Declare helper function for column name list
--
-- use test
use test;
go
-- remove function if it exists
if (OBJECT_ID('dbo.get_column_list') <> 0)
drop function get_column_list;
go
-- create new function
create function get_column_list (#schema_name sysname, #table_name sysname, #del_value varchar(10) = ',') returns varchar(max)
as
begin
-- nothing to do
if (#table_name is null) return null;
-- misc variables
declare #list varchar(max) = '';
-- select the changed items
select
#list += c.name + #del_value
from
sys.schemas s join sys.objects o on s.schema_id = o.schema_id
join sys.columns c on o.object_id = c.object_id
where
o.type = 'u' and
s.name = #schema_name and
o.name = #table_name
order by c.column_id;
-- remove last delimiter
select #list = substring(#list, 1, len(#list) - len(#del_value));
-- return a list
return #list
end;
go
--
-- 2B - spliter function from jeff moden
--
-- http://www.sqlservercentral.com/articles/Tally+Table/72993/
-- You download and install as TVF in [TEST] database
--
-- 3 - Find row differences using except
--
-- declare/initialize variables
declare #stmt1 varchar(max) = '';
-- column lists
declare #list1 varchar(max) = dbo.get_column_list ('dbo', 't1', ', ');
declare #list2 varchar(max) = dbo.get_column_list ('dbo', 't2', ', ');
-- key (first), flag (last)
declare #key1 sysname = left(#list1, charindex(',', #list1, 1) - 1);
declare #key2 sysname = reverse(left(reverse(#list2), charindex(',', reverse(#list2), 1) - 2));
-- make dynamic sql
select #stmt1 = '(select ' + #list1 + ' from t2 except select ' + #list1 + ' from t1) as d1';
select #stmt1 = 'update t2 set ' + #key2 + ' = 1 where ' + #key1 + ' in (select d1.' + #key1 + ' from ' + #stmt1 + ');';
-- debug line
--print #stmt1;
-- execute the sql
exec (#stmt1);
go
--
-- 4 - Find the columns differences using case
--
-- declare/initialize variables
declare #stmt varchar(max) = '';
-- column list
declare #list varchar(max) = dbo.get_column_list ('dbo', 't2', ',');
-- key (first), flag (last)
declare #key1 sysname = left(#list, charindex(',', #list, 1) - 1);
declare #key2 sysname = reverse(left(reverse(#list), charindex(',', reverse(#list), 1) - 1));
-- select the changed items (skip key & flag)
select
#stmt += 'case when s.' + Item + ' = t.' + Item + ' then null else t.' + Item + ' end as val_' + Item + ', '
from
DelimitedSplit8K (#list, ',')
where
ItemNumber not in
(
select min(ItemNumber) as skip_vals from DelimitedSplit8K (#list, ',')
union
select max(ItemNumber) as skip_vals from DelimitedSplit8K (#list, ',')
);
-- complete the statement
select #stmt = 'insert into t3 select t.' + #key1 + ',' + substring(#stmt, 1, len(#stmt) - 1) + ' from t1 as s join t2 as t on s.' + #key1 + ' = t.' + #key1 + ' where t.' + #key2 + ' = 1 ';
-- debug line
--print #stmt;
-- execute the sql
exec (#stmt);
go