Background: I want to compare each column in all tables for two Person records by looping through all possible columns. I then want to run a common table expression which uses each column as it loops, and if the second Person column is NULL, I want it to update with the value of the first Person column, for all duplicate records based on specific criteria.
This will essentially loop through all duplicates and update them to match, prior to deleting one.
I am running a stored procedure which loops through table columns. I would like to use those columns in a query. Here is what I have:
declare table_columns cursor local dynamic for
select BaseTable,
DBColumn
from viewTableInfo
declare #BaseTable nvarchar (100)
declare #DBColumn nvarchar (100)
open table_columns
while (1 = 1)
begin
fetch next from table_columns into #BaseTable, #DBColumn
if (##FETCH_STATUS <> 0 ) break
;with CTE as
(
select firstName,
lastName,
#DBColumn AS Column,
rn = ROW_NUMBER()OVER(PARTITION BY firstName, lastName ORDER BY pID)
from Person p
inner join Company c ON c.pID = p.pID -- joined because variable may be column from Company table.
where p.pID in (1, 2)
)
select destinationProperty = case
when c1.Column = c2.Column then c2.Column
when c2.Column IS NULL THEN c1.Column
ELSE c2.Column END
from cte c1
inner join cte c2 on c1.firstName = c2.firstName and c1.lastName = c2.LastName and c2.rn = c1.rn + 1
When I run this, I get zero records in the result set because the common table expression is selecting the value of the variable #DBColumn, which, for each loop, is the actual Column Name.
What I need is to be able to use the actual Column name in the query and get the value of that column for each pID in the result set.
Is this possible?
Not sure of exact requirement to use cursor and dynamic column names here, but you can get the required functionality using dynamic SQL.
Changed the loop not to use 1=1 , instead to check for cursor status.
declare table_columns cursor local dynamic for
select BaseTable,
DBColumn
from viewTableInfo
declare #BaseTable nvarchar (100)
declare #DBColumn nvarchar (100)
declare #query nvarchar(max)
open table_columns
fetch next from table_columns into #BaseTable, #DBColumn
WHILE ##FETCH_STATUS = 0
begin
set #query =';with CTE as
(
select firstName,
lastName,
' + #DBColumn + ' AS Column,
rn = ROW_NUMBER()OVER(PARTITION BY firstName, lastName ORDER BY pID)
from Person p
inner join Company c ON c.pID = p.pID -- joined because variable may be column from Company table.
where p.pID in (1, 2)
)
select destinationProperty = case
when c1.Column = c2.Column then c2.Column
when c2.Column IS NULL THEN c1.Column
ELSE c2.Column END
from cte c1
inner join cte c2 on c1.firstName = c2.firstName and c1.lastName = c2.LastName and c2.rn = c1.rn + 1'
exec sp_executesql #query
fetch next from table_columns into #BaseTable, #DBColumn
end
Related
My stored procedure is currently using Is Null Or Exist logic combined with an inner query to filter out the records. The stored procedure is converting multiple comma-separated input values to temp tables (in the production scenario, the input record count will be much higher). And the inner query is using these temp tables for filter conditions. Due to the concern over query execution time would like to change the existing inner-query with an alternate (like left join). But need to retain the same Is Null Or Exist logic. Any suggestions?
DECLARE #SelectedOfferes varchar(1000) = 'FLT10,SPL20'
DECLARE #SelectedBrandCode varchar(1000) = '208,406'
DECLARE #CategoryCode varchar(1000) = 'GMOVN2,CELSMR,LCDTV38IN'
CREATE TABLE #SelectedOfferes
(
DiscountCode VARCHAR(20)
)
CREATE TABLE #BrandCode
(
BrandCode VARCHAR(20)
)
CREATE TABLE #CategoryCode
(
CategoryCode VARCHAR(20)
)
IF #SelectedOfferes IS NOT NULL
BEGIN
INSERT INTO #SelectedOfferes
SELECT part
FROM dbo.[FormatTextByDelimiter] (#SelectedOfferes, ',')
END
IF #SelectedBrandCode IS NOT NULL
BEGIN
INSERT INTO #BrandCode
SELECT part
FROM dbo.[FormatTextByDelimiter] (#SelectedBrandCode, ',')
END
IF #CategoryCode IS NOT NULL
BEGIN
INSERT INTO #CategoryCode
SELECT part
FROM dbo.[FormatTextByDelimiter] (#CategoryCode, ',')
END
SELECT *
FROM Products P
INNER JOIN Discount D ON P.DiscountCode = D.DiscountCode
INNER JOIN AvailableBrand AB ON P.BrandCode = AB.BrandCode
INNER JOIN Category C ON P.CategoryCode = C.CategoryCode
WHERE (#SelectedOfferes IS NULL
OR (EXISTS (SELECT 1 FROM #SelectedOfferes OFR
WHERE OFR.DiscountCode = P.DiscountCode)))
AND (#SelectedBrandCode IS NULL
OR (EXISTS (SELECT 1 FROM #BrandCode BC
WHERE BC.BrandCode = P.BrandCode)))
AND (#CategoryCode IS NULL
OR (EXISTS (SELECT 1 FROM #CategoryCode CAT
WHERE CAT.CategoryCode = P.CategoryCode)))
Dynamic SQL version
I have some questions about your string split function, is it set-based or a looping query? If it's not set-based then you should probably replace it with Jeff Moden's DelimitedSplit8K available at http://www.sqlservercentral.com/articles/Tally+Table/72993/ .
The below example should work the same as what you supplied but should be faster since it removes the ORs and the correlated subqueries from the WHERE clause. I'm not a fan of using dynamic SQL but sometimes it is the best way to get the job done. Maybe someone else can come up with a non-dynamic solution that works as well or better.
DECLARE #SelectedOfferes varchar(1000) = 'FLT10,SPL20'
DECLARE #SelectedBrandCode varchar(1000) = '208,406'
DECLARE #CategoryCode varchar(1000) = 'GMOVN2,CELSMR,LCDTV38IN'
CREATE TABLE #SelectedOfferes
(
DiscountCode VARCHAR(20)
)
CREATE TABLE #BrandCode
(
BrandCode VARCHAR(20)
)
CREATE TABLE #CategoryCode
(
CategoryCode VARCHAR(20)
)
IF #SelectedOfferes IS NOT NULL
BEGIN
INSERT INTO #SelectedOfferes
SELECT part
FROM dbo.[FormatTextByDelimiter] (#SelectedOfferes, ',')
END
IF #SelectedBrandCode IS NOT NULL
BEGIN
INSERT INTO #BrandCode
SELECT part
FROM dbo.[FormatTextByDelimiter] (#SelectedBrandCode, ',')
END
IF #CategoryCode IS NOT NULL
BEGIN
INSERT INTO #CategoryCode
SELECT part
FROM dbo.[FormatTextByDelimiter] (#CategoryCode, ',')
END
DECLARE #SQL NVarchar(4000);
SET #SQL = N'SELECT *
FROM Products P
INNER JOIN Discount D ON P.DiscountCode = D.DiscountCode
INNER JOIN AvailableBrand AB ON P.BrandCode = AB.BrandCode
INNER JOIN Category C ON P.CategoryCode = C.CategoryCode'
IF #SelectedOfferes IS NOT NULL
SET #SQL = #SQL + N'
INNER JOIN #SelectedOfferes OFR ON OFR.DiscountCode = P.DiscountCode';
IF #SelectedBrandCode IS NOT NULL
SET #SQL = #SQL + N'
INNER JOIN #BrandCode BC ON BC.BrandCode = P.BrandCode';
IF #CategoryCode IS NOT NULL
SET #SQL = #SQL + N'
INNER JOIN #CategoryCode CAT ON CAT.CategoryCode = P.CategoryCode';
EXEC sys.sp_executesql #stmt = #SQL;
This method doesn't quite do what the OP wanted but is valid in many other cases
I have some questions about your string split function, is it set-based or a looping query? If it's not set-based then you should probably replace it with Jeff Moden's DelimitedSplit8K available at http://www.sqlservercentral.com/articles/Tally+Table/72993/ .
But either way the below change to your last query should help quite a bit. The IS NULL parts aren't needed since it is a LEFT JOIN and the table will be empty if the variable it is built with is NULL, so you get the same result with less work for the engine.
SELECT *
FROM Products P
INNER JOIN Discount D ON P.DiscountCode = D.DiscountCode
INNER JOIN AvailableBrand AB ON P.BrandCode = AB.BrandCode
INNER JOIN Category C ON P.CategoryCode = C.CategoryCode
LEFT JOIN #SelectedOfferes OFR ON OFR.DiscountCode = P.DiscountCode
LEFT JOIN #BrandCode BC ON BC.BrandCode = P.BrandCode
LEFT JOIN #CategoryCode CAT ON CAT.CategoryCode = P.CategoryCode
I created a temp table #test containing 3 fields: ColumnName, TableName, and Id.
I would like to see which rows in the #test table (columns in their respective tables) are not empty? I.e., for every column name that i have in the ColumnName field, and for the corresponding table found in the TableName field, i would like to see whether the column is empty or not. Tried some things (see below) but didn't get anywhere. Help, please.
declare #LoopCounter INT = 1, #maxloopcounter int, #test varchar(100),
#test2 varchar(100), #check int
set #maxloopcounter = (select count(TableName) from #test)
while #LoopCounter <= #maxloopcounter
begin
DECLARE #PropIDs TABLE (tablename varchar(max), id int )
Insert into #PropIDs (tablename, id)
SELECT [tableName], id FROM #test
where id = #LoopCounter
set #test2 = (select columnname from #test where id = #LoopCounter)
declare #sss varchar(max)
set #sss = (select tablename from #PropIDs where id = #LoopCounter)
set #check = (select count(#test2)
from (select tablename
from #PropIDs
where id = #LoopCounter) A
)
print #test2
print #sss
print #check
set #LoopCounter = #LoopCounter + 1
end
In order to use variables as column names and table names in your #Check= query, you will need to use Dynamic SQL.
There is most likely a better way to do this but I cant think of one off hand. Here is what I would do.
Use the select and declare a cursor rather than a while loop as you have it. That way you dont have to count on sequential id's. The cursor would fetch fields columnname, id and tablename
In the loop build a dynamic sql statement
Set #Sql = 'Select Count(*) Cnt Into #Temp2 From ' + TableName + ' Where ' + #columnname + ' Is not null And ' + #columnname <> '''''
Exec(#Sql)
Then check #Temp2 for a value greater than 0 and if this is what you desire you can use the #id that was fetched to update your #Temp table. Putting the result into a scalar variable rather than a temp table would be preferred but cant remember the best way to do that and using a temp table allows you to use an update join so it would well in my opinion.
https://www.mssqltips.com/sqlservertip/1599/sql-server-cursor-example/
http://www.sommarskog.se/dynamic_sql.html
Found a way to extract all non-empty tables from the schema, then just joined with the initial temp table that I had created.
select A.tablename, B.[row_count]
from (select * from #test) A
left join
(SELECT r.table_name, r.row_count, r.[object_id]
FROM sys.tables t
INNER JOIN (
SELECT OBJECT_NAME(s.[object_id]) table_name, SUM(s.row_count) row_count, s.[object_id]
FROM sys.dm_db_partition_stats s
WHERE s.index_id in (0,1)
GROUP BY s.[object_id]
) r on t.[object_id] = r.[object_id]
WHERE r.row_count > 0 ) B
on A.[TableName] = B.[table_name]
WHERE ROW_COUNT > 0
order by b.row_count desc
How about this one - bitmask computed column checks for NULLability. Value in the bitmask tells you if a column is NULL or not. Counting base 2.
CREATE TABLE FindNullComputedMask
(ID int
,val int
,valstr varchar(3)
,NotEmpty as
CASE WHEN ID IS NULL THEN 0 ELSE 1 END
|
CASE WHEN val IS NULL THEN 0 ELSE 2 END
|
CASE WHEN valstr IS NULL THEN 0 ELSE 4 END
)
INSERT FindNullComputedMask
SELECT 1,1,NULL
INSERT FindNullComputedMask
SELECT NULL,2,NULL
INSERT FindNullComputedMask
SELECT 2,NULL, NULL
INSERT FindNullComputedMask
SELECT 3,3,3
SELECT *
FROM FindNullComputedMask
We have a legacy application which created multiple tables with the following naming convention: table_20140618, table_20140623, etc where the date is when the program run. I am trying to clean up the database now, and drop some of these tables.
In each table there are two fields: DateStarted and DateFinished. I want to select the tables (and then drop them) where DateStarted has value and DateFinished is NOT null.
At the moment I am using the following query to select all the tables that start with 'table_'
such as:
Select (TABLE_NAME) FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
AND TABLE_NAME LIKE 'table_%';
I am not sure how to get all the tables together by searching within their fields. I could do it through the code, but that should mean multiple hits on the database. Any ideas?
Made this after my first comment above, but you should be able to alter the code to fit your specs. Basically, this will use dynamic SQL to generate the commands based on your filters and conditions. So you can use whatever conditions you want in the SELECT #SQL = ... part, to check for the dates, and then add the table name when the conditions are met.
The script returns a list with tablenames and the drop command, so you can check what you're doing before you do it. But from there you can just copy the drop command list and execute it if you want.
IF OBJECT_ID('tempdb..#TABLES') IS NOT NULL
DROP TABLE #TABLES
CREATE TABLE #TABLES (ROWNMBER INT IDENTITY(1,1), TABLENAME VARCHAR(256) COLLATE DATABASE_DEFAULT)
/*
-- Old code to fetch ALL tables with specified name
INSERT INTO #TABLES
SELECT name
FROM sys.tables
WHERE name LIKE 'table[_]%'
*/
-- Updated code to fetch only those tables which contain the DateStarted and DateFinished columns
INSERT INTO #TABLES
SELECT TAB.name
FROM sys.tables TAB
LEFT JOIN sys.columns C1 on C1.object_id = TAB.object_id
AND C1.name = 'DateStarted'
LEFT JOIN sys.columns C2 on C2.object_id = TAB.object_id
AND C2.name = 'DateFinished'
WHERE TAB.name LIKE 'table[_]%'
AND C1.name IS NOT NULL AND C2.name IS NOT NULL
IF OBJECT_ID('tempdb..#DROPPABLE_TABLES') IS NOT NULL
DROP TABLE #DROPPABLE_TABLES
CREATE TABLE #DROPPABLE_TABLES (TABLENAME VARCHAR(256) COLLATE DATABASE_DEFAULT)
DECLARE #ROW_NOW INT, #ROW_MAX INT, #SQL VARCHAR(MAX), #TABLENAME VARCHAR(256)
SELECT #ROW_NOW = MIN(ROWNMBER), #ROW_MAX = MAX(ROWNMBER) FROM #TABLES
WHILE #ROW_NOW <= #ROW_MAX
BEGIN
SELECT #TABLENAME = TABLENAME FROM #TABLES WHERE ROWNMBER = #ROW_NOW
SELECT #SQL =
'IF (SELECT COUNT(*) FROM '+#TABLENAME+' WHERE DateStarted IS NOT NULL) > 0
AND (SELECT COUNT(*) FROM '+#TABLENAME+' WHERE DateFinished IS NOT NULL) > 0
SELECT '''+#TABLENAME+''''
INSERT INTO #DROPPABLE_TABLES
EXEC(#SQL)
SET #ROW_NOW = #ROW_NOW+1
END
SELECT *, 'DROP TABLE '+TABLENAME DROPCOMMAND FROM #DROPPABLE_TABLES
EDIT:
As per your comment, it seems not all such tables have those columns. You can use the following script to identify said tables and which column is missing, so you can check into them further. And you can use the same idea to filter the results of the first query to only count in tables which have those columns.
SELECT TAB.name TABLENAME
, CASE WHEN C1.name IS NULL THEN 'Missing' ELSE '' END DateStarted_COL
, CASE WHEN C2.name IS NULL THEN 'Missing' ELSE '' END DateFinished_COL
FROM sys.tables TAB
LEFT JOIN sys.columns C1 on C1.object_id = TAB.object_id
AND C1.name = 'DateStarted'
LEFT JOIN sys.columns C2 on C2.object_id = TAB.object_id
AND C2.name = 'DateFinished'
WHERE TAB.name LIKE 'table[_]%'
AND (C1.name IS NULL
OR C2.name IS NULL)
LN_REF_NO is the reference number of the loan. LN_REF_NO must have one ACNO AND ONE LRAC.
BOTH OF THEM ARE STORED TO ACCOUNTS UNDER ACNO COLUMN.
HOW I WILL SEPARATE THEM ? PLS LOOK THE IMAGE
ALTER PROCEDURE sp_FinalLoanPayment
AS
DECLARE #LN_REF_NO char(13)
DECLARE #TempA
TABLE (
[LN_REF_NO] [char](13),
[Status] [varchar](15),
[ACNO] [varchar](21),
[ACNOBalance] [dbo].[Amount]
)
DECLARE #TempL
TABLE (
[LN_REF_NO] [char](13),
[Status] [varchar](15),
[LRAC] [varchar](21),
[LRACBalance] [dbo].[Amount]
)
DECLARE #TempAll
TABLE (
[LN_REF_NO] [char](13),
[Status] [varchar](15),
[ACNO] [varchar](21),
[ACNOBalance] [dbo].[Amount],
[LRAC] [varchar](21),
[LRACBalance] [dbo].[Amount]
)
INSERT INTO #TempL (LN_REF_NO ,Status,LRAC,LRACBalance)
SELECT Loan.LN_REF_NO, Loan.Status,ACCOUNTS.ACNO ,ACCOUNTS.BALANCE
FROM Loan
INNER JOIN Accounts
ON LOAN.LRAC = ACCOUNTS.ACNO
WHERE Loan.Status ='Paid' AND ACCOUNTS.BALANCE>0
INSERT INTO #TempA (LN_REF_NO ,Status,ACNO,ACNOBalance)
SELECT Loan.LN_REF_NO, Loan.Status,ACCOUNTS.ACNO ,ACCOUNTS.BALANCE
FROM Loan
INNER JOIN Accounts
ON LOAN.ACNO = ACCOUNTS.ACNO
WHERE Loan.Status ='Paid' AND ACCOUNTS.BALANCE<0
-- START CURSOR 1
DECLARE #cursor CURSOR
SET #cursor = CURSOR FOR
select LN_REF_NO from #TempL
OPEN #cursor
FETCH NEXT
FROM #cursor INTO
#LN_REF_NO
WHILE (##FETCH_STATUS = 0)
BEGIN
INSERT INTO #TempAll (LN_REF_NO ,Status,ACNO,ACNOBalance)
select LN_REF_NO,Status,ACNO,ACNOBalance
from #TempA
where LN_REF_NO = #LN_REF_NO
FETCH NEXT
FROM #cursor INTO
#LN_REF_NO
END
CLOSE #cursor
DEALLOCATE #cursor
-- CURSOR 1 END
select * from #TempAll order by LN_REF_NO
Results:
I think you should be able to get this data as you need it with this query:
SELECT
ln.LN_REF_NO, ln.Status, a1.ACNO, a1.BALANCE, a2.ACNO, a2.BALANCE
FROM
dbo.Loan ln
INNER JOIN
dbo.Accounts a1 ON ln.LRAC = a1.ACNO
INNER JOIN
dbo.Accounts a2 ON ln.ACNO = a2.ACNO
WHERE
ln.Status = 'Paid'
AND a1.Balance > 0
AND a2.Balance > 0
You basically just join to the Accounts table twice - once for the link to Loan.LRAC and a second time for the link on Loan.ACNO.
Based on those two joins, you just pick those columns from all joined tables that you need - and that's really all there is! No cursor, no temp tables - nothing like that needed !
Given 2 or more rows that are selected to merge, one of them is identified as being the template row. The other rows should merge their data into any null value columns that the template has.
Example data:
Id Name Address City State Active Email Date
1 Acme1 NULL NULL NULL NULL blah#yada.com 3/1/2011
2 Acme1 1234 Abc Rd Springfield OR 0 blah#gmail.com 1/12/2012
3 Acme2 NULL NULL NULL 1 blah#yahoo.com 4/19/2012
Say that a user has chosen row with Id 1 as the template row, and rows with Ids 2 and 3 are to be merged into row 1 and then deleted. Any null value columns in row Id 1 should be filled with (if one exists) the most recent (see Date column) non-null value, and non-null values already present in row Id 1 are to be left as is. The result of this query on the above data should be exactly this:
Id Name Address City State Active Email Date
1 Acme1 1234 Abc Road Springfield OR 1 blah#yada.com 3/1/2011
Notice that the Active value is 1, and not 0 because row Id 3 had the most recent date.
P.S. Also, is there any way possible to do this without explicitly defining/knowing beforehand what all the column names are? The actual table I'm working with has a ton of columns, with new ones being added all the time. Is there a way to look up all the column names in the table, and then use that subquery or temptable to do the job?
You might do it by ordering rows first by template flag, then by date desc. Template row should always be the last one. Each row is assigned a number in that order. Using max() we are finding fist occupied cell (in descending order of numbers). Then we select columns from rows matching those maximums.
; with rows as (
select test.*,
-- Template row must be last - how do you decide which one is template row?
-- In this case template row is the one with id = 1
row_number() over (order by case when id = 1 then 1 else 0 end,
date) rn
from test
-- Your list of rows to merge goes here
-- where id in ( ... )
),
-- Finding first occupied row per column
positions as (
select
max (case when Name is not null then rn else 0 end) NamePosition,
max (case when Address is not null then rn else 0 end) AddressPosition,
max (case when City is not null then rn else 0 end) CityPosition,
max (case when State is not null then rn else 0 end) StatePosition,
max (case when Active is not null then rn else 0 end) ActivePosition,
max (case when Email is not null then rn else 0 end) EmailPosition,
max (case when Date is not null then rn else 0 end) DatePosition
from rows
)
-- Finally join this columns in one row
select
(select Name from rows cross join Positions where rn = NamePosition) name,
(select Address from rows cross join Positions where rn = AddressPosition) Address,
(select City from rows cross join Positions where rn = CityPosition) City,
(select State from rows cross join Positions where rn = StatePosition) State,
(select Active from rows cross join Positions where rn = ActivePosition) Active,
(select Email from rows cross join Positions where rn = EmailPosition) Email,
(select Date from rows cross join Positions where rn = DatePosition) Date
from test
-- Any id will suffice, or even DISTINCT
where id = 1
You might check it at Sql Fiddle.
EDIT:
Cross joins in last section might actually be inner joins on rows.rn = xxxPosition. It works this way, but change to inner join would be an improvement.
It's not so complicated.
At first..
DECLARE #templateID INT = 1
..so you can remember which row is treated as template..
Now find latest NOT NULL values (exclude template row). The easiest way is to use TOP 1 subqueries for each column:
SELECT
(SELECT TOP 1 Name FROM DataTab WHERE Name IS NOT NULL AND NOT ID = #templateID ORDER BY Date DESC) AS LatestName,
(SELECT TOP 1 Address FROM DataTab WHERE Address IS NOT NULL AND NOT ID = #templateID ORDER BY Date DESC) AS AddressName
-- add more columns here
Wrap above into CTE (Common Table Expression) so you have nice input for your UDPATE..
WITH Latest_CTE (CTE_LatestName, CTE_AddressName) -- add more columns here; I like CTE prefix to distinguish source columns from target columns..
AS
-- Define the CTE query.
(
SELECT
(SELECT TOP 1 Name FROM DataTab WHERE Name IS NOT NULL AND NOT ID = #templateID ORDER BY Date DESC) AS LatestName,
(SELECT TOP 1 Address FROM DataTab WHERE Address IS NOT NULL AND NOT ID = #templateID ORDER BY Date DESC) AS AddressName
-- add more columns here
)
UPDATE
<update statement here (below)>
Now, do smart UPDATE of your template row using ISNULL - it will act as conditional update - update only if target column is null
WITH
<common expression statement here (above)>
UPDATE DataTab
SET
Name = ISNULL(Name, CTE_LatestName), -- if Name is null then set Name to CTE_LatestName else keep Name as Name
Address = ISNULL(Address, CTE_LatestAddress)
-- add more columns here..
WHERE ID = #templateID
And the last task is delete rows other then template row..
DELETE FROM DataTab WHERE NOT ID = #templateID
Clear?
For dynamic columns, you need to write a solution using dynamic SQL.
You can query sys.columns and sys.tables to get the list of columns you need, then you want to loop backwards once for each null column finding the first non-null row for that column and updating your output row for that column. Once you get to 0 in the loop you have a complete row which you can then display to the user.
I should pay attention to posting dates. In any case, here's a solution using dynamic SQL to build out an update statement. It should give you something to build from, anyway.
There's some extra code in there to validate the results along the way, but I tried to comment in a way that made that non-vital code apparent.
CREATE TABLE
dbo.Dummy
(
[ID] int ,
[Name] varchar(30),
[Address] varchar(40) null,
[City] varchar(30) NULL,
[State] varchar(2) NULL,
[Active] tinyint NULL,
[Email] varchar(30) NULL,
[Date] date NULL
);
--
INSERT dbo.Dummy
VALUES
(
1, 'Acme1', NULL, NULL, NULL, NULL, 'blah#yada.com', '3/1/2011'
)
,
(
2, 'Acme1', '1234 Abc Rd', 'Springfield', 'OR', 0, 'blah#gmail.com', '1/12/2012'
)
,
(
3, 'Acme2', NULL, NULL, NULL, 1, 'blah#yahoo.com', '4/19/2012'
);
DECLARE
#TableName nvarchar(128) = 'Dummy',
#TemplateID int = 1,
#SetStmtList nvarchar(max) = '',
#LoopCounter int = 0,
#ColumnCount int = 0,
#SQL nvarchar(max) = ''
;
--
--Create a table to hold the column names
DECLARE
#ColumnList table
(
ColumnID tinyint IDENTITY,
ColumnName nvarchar(128)
);
--
--Get the column names
INSERT #ColumnList
(
ColumnName
)
SELECT
c.name
FROM
sys.columns AS c
JOIN
sys.tables AS t
ON
t.object_id = c.object_id
WHERE
t.name = #TableName;
--
--Create loop boundaries to build out the SQL statement
SELECT
#ColumnCount = MAX( l.ColumnID ),
#LoopCounter = MIN (l.ColumnID )
FROM
#ColumnList AS l;
--
--Loop over the column names
WHILE #LoopCounter <= #ColumnCount
BEGIN
--Dynamically construct SET statements for each column except ID (See the WHERE clause)
SELECT
#SetStmtList = #SetStmtList + ',' + l.ColumnName + ' =COALESCE(' + l.ColumnName + ', (SELECT TOP 1 ' + l.ColumnName + ' FROM ' + #TableName + ' WHERE ' + l.ColumnName + ' IS NOT NULL AND ID <> ' + CAST(#TemplateID AS NVARCHAR(MAX )) + ' ORDER BY Date DESC)) '
FROM
#ColumnList AS l
WHERE
l.ColumnID = #LoopCounter
AND
l.ColumnName <> 'ID';
--
SELECT
#LoopCounter = #LoopCounter + 1;
--
END;
--TESTING - Validate the initial table values
SELECT * FROM dbo.Dummy ;
--
--Get rid of the leading common in the SetStmtList
SET #SetStmtList = SUBSTRING( #SetStmtList, 2, LEN( #SetStmtList ) - 1 );
--Build out the rest of the UPDATE statement
SET #SQL = 'UPDATE ' + #TableName + ' SET ' + #SetStmtList + ' WHERE ID = ' + CAST(#TemplateID AS NVARCHAR(MAX ))
--Then execute the update
EXEC sys.sp_executesql
#SQL;
--
--TESTING - Validate the updated table values
SELECT * FROM dbo.Dummy ;
--
--Build out the DELETE statement
SET #SQL = 'DELETE FROM ' + #TableName + ' WHERE ID <> ' + CAST(#TemplateID AS NVARCHAR(MAX ))
--Execute the DELETE
EXEC sys.sp_executesql
#SQL;
--
--TESTING - Validate the final table values
SELECT * FROM dbo.Dummy;
--
DROP TABLE dbo.Dummy;