How to automatically fill a column with spaces to a pre-determined length in update SQL sentence in SQL Server 2012?
I have a table with several columns like
Col1 NVARCHAR(10)
Col2 NVARCHAR(100)
Col3 NVARCHAR(200)
Col4 NVARCHAR(50)
and more.
If value of column is NULL or '', I update the column with spaces to a pre-determined length (the lenth of the column).
For Col3, if value is NULL or '', spaces to 200 blank space (' ')
if value has any characters, 'abcd', fill (pad right) to 200 blank spaces. Then, finally 4 not spaces characters and 196 spaces characteres.
For example, for Col1 has length 10.
1) Value = NULL , Col1 value = ' ' (10 spaces)
2) Value = '' , Col1 value = ' ' (10 spaces)
2) Value = 'abc' , Col1 value = 'abc ' (abc and 7 spaces)
How can I do that in the UPDATE SQL?
Maybe using
select column_name, data_type, character_maximum_length
from information_schema.columns
where table_name = 'myTable'
or
SELECT COL_LENGTH('Table', 'Column')
More in How to get the size of a varchar[n] field in one SQL statement?
Try the following, the LEFT is used to keep the length down to the column length, while the space ensures the field is filled with spaces:
create table test (col1 varchar(10), col2 varchar(15))
GO
insert into test (col1, col2)
values ('', '')
,(NULL, NULL)
,('abc', 'abc')
UPDATE test
SET col1 = LEFT(COALESCE(col1, '') + SPACE(COL_LENGTH('test', 'col1')), COL_LENGTH('test', 'col1'))
,col2 = LEFT(COALESCE(col2, '') + SPACE(COL_LENGTH('test', 'col2')), COL_LENGTH('test', 'col2'))
FROM test
SELECT *
FROM test
I don't understand what you want exactly, but here is what I understand:
CREATE TABLE MyTable (
Col1 NVARCHAR(200),
Col2 NVARCHAR(100),
Col3 NVARCHAR(200),
Col4 NVARCHAR(50)
);
INSERT INTO MyTable VALUES (NULL, NULL, NULL, NULL), ('ABC', NULL, NULL, NULL);
-- You can do the same for the other cols
UPDATE MyTABLE
SET Col1 = REPLICATE(' ', COL_LENGTH('MyTable', 'Col1')/2)
WHERE Col1 IS NULL;
SELECT *
FROM MyTable;
Demo
Update:
Here is how to do it in one statement:
UPDATE MyTABLE
SET Col1 = (SELECT CASE WHEN (Col1 IS NULL) OR (Col1 = '') THEN
REPLICATE(' ', COL_LENGTH('MyTable', 'Col1')/2)
ELSE Col1 + REPLICATE(' ', (COL_LENGTH('MyTable', 'Col1')/2)- LEN(Col1)) END),
Col2 = (SELECT CASE WHEN (Col2 IS NULL) OR (Col2 = '') THEN
REPLICATE(' ', COL_LENGTH('MyTable', 'Col2')/2)
ELSE Col2 + REPLICATE(' ', (COL_LENGTH('MyTable', 'Col2')/2)- LEN(Col2)) END),
Col3 = (SELECT CASE WHEN (Col3 IS NULL) OR (Col3 = '') THEN
REPLICATE(' ', COL_LENGTH('MyTable', 'Col1')/2)
ELSE Col3 + REPLICATE(' ', (COL_LENGTH('MyTable', 'Col3')/2)- LEN(Col3)) END),
Col4 = (SELECT CASE WHEN (Col4 IS NULL) OR (Col4 = '') THEN
REPLICATE(' ', COL_LENGTH('MyTable', 'Col4')/2)
ELSE Col4 + REPLICATE(' ', (COL_LENGTH('MyTable', 'Col4')/2)- LEN(Col4)) END);
Demo
Related
I have a requirement to transform the result of a query into an xml fragment using t-SQL.
My table/query looks like this:
[Col1] [Col2] [Col3] qty
A group1 mod1 5
A group1 mod2 7
A group1 NULL 12
A group2 mod3 8
A group2 mod4 5
A group2 NULL 13
A NULL NULL 25
and the desired XML output should look like this
<A qty="25">
<group1 qty="12">
<mod1 qty="5"/>
<mod2 qty="7"/>
</group1>
<group2 qty="13">
<mod3 qty="8"/>
<mod4 qty="5"/>
</group2>
</A>
Is there a way to achieve this without pivoting the table and using dynamic sql which would be very messy
Good day, Please confirm that this solve your need. We can next work on improving performance. It is important to have the right indexes
DDL+DML
DROP TABLE IF EXISTS T
GO
CREATE TABLE T(
[Col1] VARCHAR(10),
[Col2] VARCHAR(10),
[Col3] VARCHAR(10),
qty VARCHAR(10)
)
GO
INSERT T([Col1],[Col2],[Col3],qty) VALUES
('A', 'group1', 'mod1', 5 ),
('A', 'group1', 'mod2', 7 ),
('A', 'group1', NULL , 12),
('A', 'group2', 'mod3', 8 ),
('A', 'group2', 'mod4', 5 ),
('A', 'group2', NULL , 13),
('A', NULL , NULL , 25)
GO
SELECT [Col1],[Col2],[Col3],qty
FROM T
GO
Solution
;with l1 as (
SELECT COL1, qty
FROM T
where col2 is null and col3 is null
)
, l2 as (
SELECT Col1, COL2, qty
FROM T
where col2 is not null and col3 is null
)
, l3 as (
SELECT Col1, COL2, COL3, qty
FROM T
where col2 is not null and col3 is not null
)
, a1 as (
SELECT COL2, X = STRING_AGG('<' + COL3 + ' qty="' + qty + '"/>' ,'')
FROM l3
GROUP BY Col2
)
, a2 as (
select X = '<' + a1.Col2 + ' qty="' + l2.qty + '">' + a1.X + '</' + a1.Col2 + '>', l2.Col1
from a1
LEFT JOIN l2 on a1.Col2 = l2.Col2
)
select CONVERT(XML,
'<' + l1.Col1 + ' qty="' + l1.qty + '">' + STRING_AGG(a2.X,'') + '</' + l1.Col1 + '>'
)
from a2
left join l1 on l1.Col1 = a2.Col1
group by l1.Col1, l1.qty
GO
Note! at the end I convert the value to XML in order to check that the result fit. In production there is no reason for this step, which cost resources.
#RonenAriely your solution inspired me. I've modified it a little. Because I could not run it.
My solution looks a little dirty and complicated and not elegant but the result is correct.
DECLARE #Tbl TABLE (Col1 CHAR(1), Col2 CHAR(6), Col3 CHAR(4), qty INT);
INSERT INTO #Tbl
VALUES ('A', 'group1', 'mod1', 5),
('A', 'group1', 'mod2', 7),
('A', 'group1', '', 12),
('A', 'group2', 'mod3', 8),
('A', 'group2', 'mod4', 5),
('A', 'group2', '', 13),
('A', '', '', 25);
WITH l1
AS (SELECT Col1,
Col2,
SUM(qty) AS qty FROM #Tbl WHERE Col2 <> ''
AND Col3 <> ''
AND Col2 <> ''
GROUP BY Col1,
Col2),
l2
AS (SELECT Col2,
SUM(qty) AS qty FROM #Tbl WHERE Col3 <> ''
GROUP BY Col2),
l3
AS (SELECT Col2,
Col3,
SUM(qty) AS qty FROM #Tbl
GROUP BY Col2,
Col3),
a1
AS (SELECT Col2,
'<' + Col3 + ' qty="' + CAST(qty AS VARCHAR(10)) + '"/>' AS c FROM l3 WHERE Col3 <> ''),
a2
AS (SELECT l1.Col1,
'<' + aa.Col2 + ' qty="' + CAST(l2.qty AS VARCHAR(10)) + '">' +
STUFF((SELECT DISTINCT '' + c FROM a1 WHERE Col2 = aa.Col2
FOR XML PATH ('')), 1, 0, '')
+ '</' + aa.Col2 + '>' AS c FROM a1 aa
LEFT JOIN l1
ON l1.Col2 = aa.Col2
LEFT JOIN l2
ON aa.Col2 = l2.Col2
GROUP BY l1.Col1,
aa.Col2,
l2.qty)
SELECT REPLACE(REPLACE(REPLACE('<' + aa.Col1 + ' qty="' + CAST(l1.qty AS VARCHAR(10)) + '">'
+ STUFF((SELECT DISTINCT '' + c FROM a2 WHERE Col1 = aa.Col1
FOR XML PATH ('')), 1, 0, '')
+ '</' + aa.Col1 + '>', '&', '&'), '<', '<'), '>', '>') AS c
FROM a2 aa
LEFT OUTER JOIN (SELECT Col1, SUM(qty) AS qty FROM l1 GROUP BY Col1) l1
ON l1.Col1 = aa.Col1
GROUP BY aa.Col1,
l1.qty;
Replying to my own post.
Thank you all for your help. In the end, I have implemented a solution using dynamic SQL as I have many blocks to process and I need a generic solution. My final code looks like this:
DECLARE #SQL nvarchar(4000)
SELECT #SQL = 'SELECT ' +
STUFF((SELECT ',' + qty + ' as '''
+ Col1
+ ISNULL('/'+ Col2,'')
+ ISNULL('/'+ Col3,'')
+ '/#qty''' FROM T ORDER BY Col1, Col2, Col3 FOR XML PATH(''))
,1,1,'') + ' FOR XML PATH('''')'
EXEC(#SQL)
Unfortunately, it cannot be implemented as a function (which was my initial goal) because it requires a physical table and because of the string size limitation. This would also not work for a large dataset with many sublevels. I will have to live with it!
I have two tables with following records:
Table 1: 100 rows
Table 2: 50 Millions rows
Example:
Table 1: tb100
create table tb100
(
name varchar(50)
);
insert into tb100 values('Mak John'),('Will Smith'),('Luke W')......100 rows.
Table 2: tb50mil
create table tb50mil
(
name varchar(50)
);
insert into tb10mil values('John A Mak'),('K Smith Will'),('James Henry')......50 millions rows.
create nonclustered index nci_tb10mil_name on tb10mil(name);
Requirement: I want to match the name between two tables, if any WORD(John,Smith,Will) present in another table. For example John present in John A Mark.
My try: Used XML to split column name of the table tb100 and also adding collation with CHARINDEX.
;WITH splitdata AS
(
SELECT splitname
FROM
(
SELECT *,Cast('<X>' + Replace(t.name, ' ', '</X><X>') + '</X>' AS XML) AS xmlfilter
FROM tb100 t
)F1
CROSS apply
(
SELECT fdata.d.value('.', 'varchar(50)') AS splitName
FROM f1.xmlfilter.nodes('X') AS fdata(d)
) O
)
SELECT t2.name AS [Aadhar Names]
FROM tb50mil t2
INNER JOIN splitdata S
ON CHARINDEX(S.splitname collate Latin1_General_BIN,T2.name collate Latin1_General_BIN)>0
GROUP BY t2.name
Time taken to execution: 00:01:34
Rows affected: (2251429 row(s) affected)
Execution Plan:
If you need the separate words within your name then maybe having a table where your name is just one string isn't optimal conceptionally. Also, separating the names now is painful since you have no reoccurring pattern to your middle names. Plus string modification is really not a SQL strength. I would instead extend your table into something like this:
alter table tb100
add
nameID int IDENTITY(1,1) NOT NULL,
first_name varchar(50) null,
middle_name varchar(50) null,
last_name varchar(50) null
insert into tb100 values('Mak John'),('Will Smith'),('Luke W')......100 rows.
if (SELECT LEN(col) - LEN(REPLACE(col, ' ', '')) > 1)
update tb100
set
first_name = (Select Substring(name, 0, (Charindex(' ', name)))),
middle_name = (Select Substring( Right(name, (LEN(name) - (Charindex(' ', name) +1)), 0, (Charindex(' ', name)))),
last_name = (select Substring( Right(middle_name, (LEN(middle_name) - (Charindex(' ', middle_name) + 1)), LEN(LEN(middle_name) - (Charindex(' ', middle_name) + 1))
else
update tb100
set
first_name = (Select Substring(name, 0, (Charindex(' ', name)))),
middle_name = '',
last_name = (Select Substring( Right(name, (LEN(name) - (Charindex(' ', name) +1)), 0, (Charindex(' ', name))))
I hope it works I didn't have a chance to test it because I'm on the road.
If you have the chance of inserting the data into those columns without this entire modification then please do so.
You then do the same to your other table...
alter table tb50mil
add
nameID int IDENTITY(1,1) NOT NULL,
first_name varchar(50) null,
middle_name varchar(50) null,
last_name varchar(50) null
insert into tb10mil values('John A Mak'),('K Smith Will'),('James Henry')......50 million rows.
if (SELECT LEN(col) - LEN(REPLACE(col, ' ', '')) > 1)
update tb50mil
set
first_name = (Select Substring(name, 0, (Charindex(' ', name)))),
middle_name = (Select Substring( Right(name, (LEN(name) - (Charindex(' ', name) +1)), 0, (Charindex(' ', name)))),
last_name = (select Substring( Right(middle_name, (LEN(middle_name) - (Charindex(' ', middle_name) + 1)), LEN(LEN(middle_name) - (Charindex(' ', middle_name) + 1))
else
update tb50mil
set
first_name = (Select Substring(name, 0, (Charindex(' ', name)))),
middle_name = '',
last_name = (Select Substring( Right(name, (LEN(name) - (Charindex(' ', name) +1)), 0, (Charindex(' ', name))))
and from here on it's a simple join really:
select * from tb100 hun
inner join
tb50mil mil on hun.first_name = mil.first_name OR hun.middle_name = mil.middle_name OR hun.last_name OR mil.last_name
Hope this helps!
I want to concatenate column values with a separator and assign it to variable.
If column value is null, there's no need to add separator.
For example: A|B|C|D
If B is null A|C|D.
I tried with CONCAT function, but if B is null, it results in A||C|D
DECLARE #OldValue VARCHAR(8000);
SELECT #OldValue = CONCAT([FloorCode],'|',
[FloorName],'|',
[BuildingID],'|',
[HCMLocationCode],'|',
[IsActive])
FROM tblFloor_Master
WHERE FloorID = #FloorID;
#FloorID is an input parameter of SP
SELECT #OldValue = CONCAT('',
CASE WHEN [FloorCode] IS NULL THEN '' ELSE CONCAT([FloorCode],'|') END,
CASE WHEN [FloorName] IS NULL THEN '' ELSE CONCAT([FloorName],'|') END,
CASE WHEN [BuildingID] IS NULL THEN '' ELSE CONCAT([BuildingID],'|') END,
CASE WHEN [HCMLocationCode] IS NULL THEN '' ELSE CONCAT([HCMLocationCode],'|') END,
[IsActive])
FROM tblFloor_Master
WHERE FloorID = #FloorID;
You can try the following query.
create table tempTable (id int identity(1, 1),col1 char(1), col2 char(1), col3 char(1), col4 char(1))
insert into tempTable values ('A', NULL, 'C', 'D')
select * into #NewTable from(
select id, col1 as Value from tempTable where col1 is not null
union
select id, col2 as Value from tempTable where col2 is not null
union
select id, col3 as Value from tempTable where col3 is not null
union
select id, col4 as Value from tempTable where col4 is not null
)a
SELECT ID
,STUFF((SELECT '| ' + CAST(Value AS VARCHAR(10)) [text()]
FROM #NewTable
WHERE ID = t.ID
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,1,' ') List_Output
FROM #NewTable t
GROUP BY ID
The output is as shown below
ID List_Output
---------------
1 A| C| D
If you do not want to put space between values then you can try this
SELECT ID
,STUFF((SELECT '|' + CAST(Value AS VARCHAR(10)) [text()]
FROM #NewTable
WHERE ID = t.ID
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,1,'') List_Output
FROM #NewTable t
GROUP BY ID
In this case the output will be
ID List_Output
---------------
1 A|C|D
You can also try the below actual query using stored procedure
create table tblFloor_Master (FloorID int identity(1, 1),
FloorCode char(1),
FloorName char(1),
BuildingID char(1),
HCMLocationCode char(1))
insert into tblFloor_Master values ('A', NULL, 'C', 'D')
GO
--To create a procedure
create proc uspGetConcateValue
#FloorId int
as
BEGIN
select * into #tblFloor_Master from(
select FloorId, FloorCode as Value from tblFloor_Master where FloorCode is not null
union
select FloorId, FloorName as Value from tblFloor_Master where FloorName is not null
union
select FloorId, BuildingID as Value from tblFloor_Master where BuildingID is not null
union
select FloorId, HCMLocationCode as Value from tblFloor_Master where HCMLocationCode is not null
)a
SELECT FloorId
,STUFF((SELECT '|' + CAST(Value AS VARCHAR(10)) [text()]
FROM #tblFloor_Master
WHERE FloorId = t.FloorId
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,1,'') List_Output
FROM #tblFloor_Master t
where FloorID = #FloorId
GROUP BY FloorId
END
Live demo here - Live Demo
Should be simple as doing replace, replace || to | then replace ||| to | since the maximum number of | is 3.
DECLARE #OldValue VARCHAR(8000);
SELECT #OldValue = replace(replace(CONCAT([FloorCode],'|',
[FloorName],'|',
[BuildingID],'|',
[HCMLocationCode],'|',
[IsActive]),'||','|'),'|||','|')
FROM tblFloor_Master
WHERE FloorID = #FloorID;
SELECT
TRIM ('|' FROM
regexp_replace(
CONCAT('','|','COLB','|','','|','COLD','|','COLE')
,'[\|]+'
,'|'
,'g'
)
)
We have a requirement of appending around three varchar columns of table with comma operator. Also we need to ignore the NULL or Empty Values during append. Suppose the table structure is as below
declare #t table (col1 varchar(10),col2 varchar(10),col3 varchar(10))
insert into #t
select 'test1','test2','test3' union all
select 'test4',null,'test5' union all
select null,null,'test6' union all
select '','test7',''
Expected result must be as below
Result
--------------------------------
test1,test2,test3
test4,test5
test6
test7
I have tried using case statements, but the query is becoming very complex. BTW we are using SQL Server 2005. Looking for any easy and simple solution. Thanks in advance.
Not very elegant;
;with T(string) as (
select
isnull(case col1 when '' then null else col1 + ',' end, '')
+ isnull(case col2 when '' then null else col2 + ',' end, '')
+ isnull(case col3 when '' then null else col3 + ',' end, '')
from #t
)
select left(string, abs(len(string) - 1)) from T
Here is a very alternative way
select reverse(stuff(reverse(coalesce(replace(col1, col1, col1 + ','), '')+
coalesce(replace(col2, col2, col2 + ','), '')+
coalesce(replace(col3, col3, col3 + ','), '')), 1,1,'')) Result from #t
Basically, just append a comma to each column if that column is not null / empty.Then, strip the last comma off of the result.
SELECT
CASE
WHEN RIGHT(T.outColumn, 1) = ',' THEN SUBSTRING(T.outColumn, 1, LEN(T.outColumn) - 1)
ELSE T.outColumn
END
FROM
(
SELECT
ISNULL(col1, '') + CASE WHEN ISNULL(col1, '') <> '' THEN ',' ELSE '' END +
ISNULL(col2, '') + CASE WHEN ISNULL(col2, '') <> '' THEN ',' ELSE '' END +
ISNULL(col3, '') AS outColumn
FROM
#t
) AS T
I would like to transform an Address (Line1, Line2, Line3, City, State, ZIP) into a Mailing Address (Addr1, Addr2, Addr3, Addr4) that has no blank lines and the City, State and ZIP are concatenated together on one line. Having a function do this would be very nice.
i.e.
Line1=
Line2=123 Somewhere
Line3=
City=Detroit
State=MI
Zip=48000
Here is the table stucture for the incoming address:
IF OBJECT_ID('tempdb..#Employee') IS NOT NULL DROP TABLE #Employee
CREATE TABLE #Employee (Line1 VARCHAR(30), Line2 VARCHAR(30), Line3 VARCHAR(30),
City VARCHAR(17), State VARCHAR(2), ZIP VARCHAR(10))
GO
INSERT #Employee VALUES ('', '123 Somewhere', '', 'Detroit', 'MI', '48000')
SELECT * FROM #Employee
The resulting Mailing Address
Addr1=123 Somewhere
Addr2=Detroit MI 48000
Addr3=
Addr4=
or one field with cr character
Addr=
123 Somewhere cr
Detroit MI 48000 cr
cr
cr
A function would be nice to return Addr1, Addr2, Addr3 and Addr4 or just Addr with .
SqueezeAddress(Line1, Line2, Line3, City, State, ZIP)
Then SqueezeAddress would return Addr1, Addr2, Addr3, Addr4
or
Addr with cr
All the Addr1-4 lines would be VARCHAR (40) or if one field is used Addr VARCHAR (200)
Per Phil's request in the comments below, here is the current logic that is being used (Many fields were removed to make it easier to read):
SELECT Line1, Line2, Line3,
ISNULL(LTRIM(RTRIM(ADDR.City)) + ', ','') + ISNULL(ADDR.RegionCode,'')
+ ' ' + ISNULL(ADDR.PostalCode,'') AS Line4,
UPDATE #tmpBilling
SET Line1 = Line2, Line2 = NULL
WHERE ISNULL(Line1, '') = ''
AND ISNULL(Line2, '') <> ''
UPDATE #tmpBilling
SET Line2 = Line3, Line3 = NULL
WHERE ISNULL(Line2, '') = ''
AND ISNULL(Line3, '') <> ''
UPDATE #tmpBilling
SET Line2 = Line4, Line4 = NULL
WHERE ISNULL(Line2, '') = ''
AND ISNULL(Line4, '') <> ''
UPDATE #tmpBilling
SET Line3 = Line4, Line4 = NULL
WHERE ISNULL(Line3, '') = ''
AND ISNULL(Line2, '') <> ''
I may be missing something here, but if this is just simple string concatenation, then this would work...
Set up testing data (I added a few more samples)
IF OBJECT_ID('tempdb..#Employee') IS NOT NULL DROP TABLE #Employee
CREATE TABLE #Employee (Line1 VARCHAR(30), Line2 VARCHAR(30), Line3 VARCHAR(30),
City VARCHAR(17), State VARCHAR(2), ZIP VARCHAR(10))
GO
INSERT #Employee VALUES ('', '123 Somewhere', '', 'Detroit', 'MI', '48001')
INSERT #Employee VALUES ('123 Somewhere', 'Suite 500', '', 'Detroit', 'MI', '48002')
INSERT #Employee VALUES ('123 Somewhere', 'Suite 500', 'attn: JP', 'Detroit', 'MI', '48003')
SELECT * FROM #Employee
From here, all you have to do is stitch the strings together. This version presumes that you have both nulls and empty strings to factor out.
SELECT
isnull(nullif(Line1, '') + char(13) + char(10), '')
+ isnull(nullif(Line2, '') + char(13) + char(10), '')
+ isnull(nullif(Line3, '') + char(13) + char(10), '')
+ City + ' ' + State + ' ' + ZIP
+ char(13) + char(10) + '------------------------------'
from #Employee
Wrap that into a function:
CREATE FUNCTION dbo.SqueezeAddress
(
#Line1 varchar(30)
,#Line2 varchar(30)
,#Line3 varchar(30)
,#City varchar(17)
,#State varchar(2)
,#Zip varchar(10)
)
RETURNS varchar(200)
AS
BEGIN
RETURN isnull(nullif(#Line1, '') + char(13) + char(10), '')
+ isnull(nullif(#Line2, '') + char(13) + char(10), '')
+ isnull(nullif(#Line3, '') + char(13) + char(10), '')
+ #City + ' ' + #State + ' ' + #ZIP
+ char(13) + char(10) + '------------------------------'
END
GO
Lastly, put the function in the query:
SELECT dbo.SqueezeAddress(Line1, Line2, Line3, City, State, Zip)
from #Employee
More straightforward (and easier to debug, IMHO):
-------------------------------------------------------------
-- assumptions:
--
-- * nullable fields never contain an nil (empty) string.
-- every nullable column will contain either a proper value
-- or NULL.
--
-- * zipcode is a 5- or 9-digit USPS zip code, without a dash.
-- Addresses lacking a zipcode will be NULL.
--------------------------------------------------------------
drop table dbo.address
go
create table dbo.address
(
id int not null identity(1,1) primary key clustered ,
line1 varchar(100) null ,
line2 varchar(100) null ,
line3 varchar(100) null ,
city varchar(100) null ,
state varchar(2) null ,
zipcode varchar(9) null ,
)
go
-----------------------------------------------------------------------
-- create a work table and rotate the source table such that
-- the work table contains 1 row for each non-null row for each address
-----------------------------------------------------------------------
drop table #addr
go
create table #addr
(
id int not null , -- pk.1
line_no int not null , -- pk.2
value varchar(100) not null ,
primary key clustered ( id , line_no ) ,
)
go
insert #addr ( id , line_no , value )
select addr.id , addr.line_no , addr.value
from ( select id = t.id ,
line_no = row_number() over ( partition by t.id order by t.seq ) ,
value = t.value
from ( select id = id ,
seq = 1 ,
value = line1
from dbo.address where line1 is not null
UNION
select id = id ,
seq = 2 ,
value = line2
from dbo.address where line2 is not null
UNION
select id = id ,
seq = 3 ,
value = line3
from dbo.address where line3 is not null
UNION
select id = id ,
seq = 4 ,
value = ltrim(rtrim(
coalesce( city , '' )
+ case when city is not null and state is not null then ', ' else '' end
+ coalesce( state , '' )
+ case when ( city is not null or state is not null ) and zipcode is not null then ' ' else '' end
+ coalesce( left(zipcode,5) , '' )
+ case when len(zipcode) = 9 then '-' + right(zipcode,4) else '' end
))
from dbo.address
where city is not null
OR state is not null
OR zipcode is not null
) t
) addr
---------------------------------------------------------------------
-- finally, do another table rotation to build the desired result set
---------------------------------------------------------------------
select id = addr.id ,
line1 = line1.value ,
line2 = line2.value ,
line3 = line3.value ,
line4 = line4.value
from #addr addr
left join #addr line1 on line1.id = addr.id and line1.line_no = 1
left join #addr line2 on line2.id = addr.id and line2.line_no = 2
left join #addr line3 on line3.id = addr.id and line3.line_no = 3
left join #addr line4 on line4.id = addr.id and line4.line_no = 4
order by addr.id
Assuming that empty values are actually NULL and not empty strings and that City, State and Zip are requried:
;With AddressValues As
(
Select PK, Line1 As LineValue, 1 As LinePos
From AddressTable
Union All
Select PK, Line2, 2
From AddressTable
Union All
Select PK, Line3, 3
From AddressTable
Union All
Select PK, [City] + ', ' + [State] + ' ' + [Zip], 4
From AddressTable
)
, OrderedValues As
(
Select PK, LineValue
, Row_Number() Over( Partition By PK Order By LinePos ) As Num
From AddressValues
Where LineValue Is Not Null
)
, ValuesAsColumns As
(
Select PK
, Case When Num = 1 Then LineValue End As Line1
, Case When Num = 2 Then LineValue End As Line2
, Case When Num = 3 Then LineValue End As Line3
, Case When Num = 4 Then LineValue End As Line4
From OrderedValues
Group By PK
)
Update #tmpBilling
Set Line1 = VC.Line1
, Line2 = VC.Line2
, Line3 = VC.Line3
, Line4 = VC.Line4
From #tmpBilling As B
Join ValuesAsColumns As VC
On VC.PK = B.PK
EDIT
Here is the same result in the form of a function:
CREATE FUNCTION dbo.SqueezeAddress
(
#Line1 varchar(50)
, #Line2 varchar(50)
, #Line3 varchar(50)
, #City varchar(50)
, #State varchar(50)
, #Zip varchar(50)
, #LineNumToReturn int
)
RETURNS varchar(50)
AS
BEGIN
Declare #Result varchar(50);
With AddressValues As
(
Select #Line1 As LineValue, 1 As LinePos
Union All
Select #Line2, 2
Union All
Select #Line3, 3
Union All
Select #City + ', ' + #State + ' ' + #Zip, 4
)
, OrderedValues As
(
Select LineValue
, Row_Number() Over( Order By LinePos ) As Num
From AddressValues
Where LineValue Is Not Null
)
Select #Result = LineValue
From OrderedValues
Where Num = #LineNumToReturn
Return #Result
END
GO
Select dbo.SqueezeAddress(null, '123 Main St', null, 'Detroit', 'MI', '12345', 1)
, dbo.SqueezeAddress(null, '123 Main St', null, 'Detroit', 'MI', '12345', 2)
, dbo.SqueezeAddress(null, '123 Main St', null, 'Detroit', 'MI', '12345', 3)
, dbo.SqueezeAddress(null, '123 Main St', null, 'Detroit', 'MI', '12345', 4)
)