SQL - Split string to columns by multiple delimiters - sql

There appear to be numerous solutions to this problem, however my solutions needs to be dynamic as the number of delimiters changes from between 0 and 3 and needs to be relatively efficient as it will be running across >10m rows across 5 loops.
As example:
US
US-AL
US-AL-Talladega
US-AL-Talladega-35160
The solution would need to be able to deposit each item in a Country, State, County, ZIP field with a NULL field if the information is not within the string.
Any comments on the best approach would be appreciated or even point me in the direction of where I may have possible missed a solution would be much appreciated

Another option is with a little XML in concert with a CROSS or OUTER APPLY
Example
Declare #YourTable table (YourCol varchar(100))
Insert Into #YourTable values
('US')
,('US-AL')
,('US-AL-Talladega')
,('US-AL-Talladega-35160')
Select A.*
,B.*
From #YourTable A
Outer Apply (
Select Country = xDim.value('/x[1]','varchar(max)')
,State = xDim.value('/x[2]','varchar(max)')
,County = xDim.value('/x[3]','varchar(max)')
,ZIP = xDim.value('/x[4]','varchar(max)')
From (Select Cast('<x>' + replace(YourCol,'-','</x><x>')+'</x>' as xml) as xDim) as A
) B
Returns
YourCol Country State County ZIP
US US NULL NULL NULL
US-AL US AL NULL NULL
US-AL-Talladega US AL Talladega NULL
US-AL-Talladega-35160 US AL Talladega 35160

you will need a delimited splitter. Like DelimitedSplit8K from http://www.sqlservercentral.com/articles/Tally+Table/72993/
; with tbl as
(
select col = 'US' union all
select col = 'US-AL' union all
select col = 'US-AL-Talladega' union all
select col = 'US-AL-Talladega-35160'
)
select t.col,
max(case when ItemNumber = 1 then Item end) as Country,
max(case when ItemNumber = 2 then Item end) as State,
max(case when ItemNumber = 3 then Item end) as County,
max(case when ItemNumber = 4 then Item end) as Zip
from tbl t
cross apply dbo.[DelimitedSplit8K](t.col, '-')
group by t.col

Related

SQL Comma separated values comparisons

I am having a challenge comparing values in Available column with values in Required column. They are both comma separated.
Available
Required
Match
One, Two, Three
One, Three
1
One, Three
Three, Five
0
One, Two, Three
Two
1
What I want to achieve is, if values in the Required column are all found in the Available column then it gives me a match of 1 and 0 if one or more values that are in the Required column is missing in the Available column
I want to achieve this in SQL.
If I understand the question correctly, an approach based on STRING_SPLIT() and an appropriate JOIN is an option:
Sample data:
SELECT *
INTO Data
FROM (VALUES
('One, Two, Three', 'One, Three'),
('One, Three', 'Three, Five'),
('One, Two, Three', 'Two')
) v (Available, Required)
Statement:
SELECT
Available, Required,
CASE
WHEN EXISTS (
SELECT 1
FROM STRING_SPLIT(Required, ',') s1
LEFT JOIN STRING_SPLIT(Available, ',') s2 ON TRIM(s1.[value]) = TRIM(s2.[value])
WHERE s2.[value] IS NULL
) THEN 0
ELSE 1
END AS Match
FROM Data
Result:
Available
Required
Match
One, Two, Three
One, Three
1
One, Three
Three, Five
0
One, Two, Three
Two
1
A variation of Zhorov's solution.
It is using a set based operator EXCEPT.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (Available VARCHAR(100), Required VARCHAR(100));
INSERT INTO #tbl (Available, Required) VALUES
('One, Two, Three', 'One, Three'),
('One, Three', 'Three, Five'),
('One, Two, Three', 'Two');
-- DDL and sample data population, end
SELECT t.*
, [Match] = CASE
WHEN EXISTS (
SELECT TRIM([value]) FROM STRING_SPLIT(Required, ',')
EXCEPT
SELECT TRIM([value]) FROM STRING_SPLIT(Available, ',')
) THEN 0
ELSE 1
END
FROM #tbl AS t;
Output
+-----------------+-------------+-------+
| Available | Required | Match |
+-----------------+-------------+-------+
| One, Two, Three | One, Three | 1 |
| One, Three | Three, Five | 0 |
| One, Two, Three | Two | 1 |
+-----------------+-------------+-------+
You need to do a cross join to look in all available values, your query would be :
SELECT t.*
,case when SUM(CASE
WHEN t1.Available LIKE '%' + t.Required + '%'
THEN 1
ELSE 0
END) > 0 THEN 1 ELSE 0 END AS [Match_Calculated]
FROM YOUR_TABLE t
CROSS JOIN YOUR_TABLE t1
GROUP BY t.Available
,t.Required
,t.Match
Here's a dbfiddle
You can use "STRING_SPLIT" to achieve your request
;with Source as
(
select 1 id,'One,Two,Three' Available,'One,Three' Required
union all
select 2 id,'One,Three' Available,'Three,Five' Required
union all
select 3 id,'One,Two,Three' Available,'Two' Required
)
,AvailableTmp as
(
SELECT t.id,
x.value
FROM Source t
CROSS APPLY (SELECT trim(value) value
FROM string_split(t.Available, ',')) x
)
,RequiredTmp as
(
SELECT t.id,
x.value
FROM Source t
CROSS APPLY (SELECT trim(value) value
FROM string_split(t.Required, ',')) x
)
,AllMatchTmp as
(
select a.id
,1 Match
From RequiredTmp a
left join AvailableTmp b on a.id=b.id and a.value = b.value
group by a.id
having max(case when b.value is null then 1 else 0 end ) = 0
)
select a.id
,a.Available
,a.Required
,ISNULL(b.Match,0) Match
from Source a
left join AllMatchTmp b on a.id = b.id
Another way using STRING_SPLIT
DECLARE #data TABLE (Available VARCHAR(100), [Required] VARCHAR(100),
INDEX IX_data(Available,[Required]));
INSERT #data
VALUES ('One, Two, Three', 'One, Three'),('One, Three', 'Three, Five'),
('One, Two, Three', 'Two');
SELECT
Available = d.Available,
[Required] = d.[Required],
[Match] = MIN(f.X)
FROM #data AS d
CROSS APPLY STRING_SPLIT(REPLACE(d.[Required],' ',''),',') AS split
CROSS APPLY (VALUES(REPLACE(d.[Available],' ',''))) AS cleaned(String)
CROSS APPLY (VALUES(IIF(split.[value] NOT IN
(SELECT s.[value] FROM STRING_SPLIT(cleaned.String,',') AS s),0,1))) AS f(X)
GROUP BY d.Available, d.[Required];

Separating out key value pairs

In T-SQL how can one separate columns; one with the key and the other with the value for strings that follow the pattern below?
Examples of the strings that need to be processed are:
country_code: "US"province_name: "NY"city_name: "Old Chatham"
postal_code: "11746-8031"country_code: "US"province_name: "NY"street_address: "151 Millet Street North"city_name: "Dix Hills"
street_address: "1036 Main Street, Holbrook, NY 11741"
Desired outcome for example 1 would be:
Key
Value
country_code
US
province_name
NY
city_name
Old Chatham
Nice to see Old Chatham ... a little touch of home
My first thought was to "correct" the JSON string, but that got risky.
Here is an option that will parse and pair the key/values
Example or dbFiddle
Select A.*
,C.*
From YourTable A
Cross Apply ( values ( replace(replace(replace(SomeCol,'"',':'),': :',':'),'::',':') ) ) B(CleanString)
Cross Apply (
Select [Key] =max(case when Seq=1 then Val end)
,[Value]=max(case when Seq=0 then Val end)
From (
Select Seq = row_number() over (order by [Key]) % 2
,Grp = (row_number() over (order by [Key])-1) / 2
,Val = Value
From OpenJSON( '["'+replace(string_escape(CleanString,'json'),':','","')+'"]' )
Where ltrim(Value)<>''
) C1
Group By Grp
) C
Results

How to merge two columns from CASE STATEMENT of DIFFERENT CONDITION

My expected result should be like
----invoiceNo----
T17080003,INV14080011
But right now, I've come up with following query.
SELECT AccountDoc.jobCode,AccountDoc.shipmentSyskey,AccountDoc.docType,
CASE AccountDoc.docType
WHEN 'M' THEN
JobInvoice.invoiceNo
WHEN 'I' THEN
(STUFF((SELECT ', ' + RTRIM(CAST(AccountDoc.docNo AS VARCHAR(20)))
FROM AccountDoc LEFT OUTER JOIN JobInvoice
ON AccountDoc.principalCode = JobInvoice.principalCode AND
AccountDoc.jobCode = JobInvoice.jobCode
WHERE (AccountDoc.isCancelledByCN = 0)
AND (AccountDoc.docType = 'I')
AND (AccountDoc.jobCode = #jobCode)
AND (AccountDoc.shipmentSyskey = #shipmentSyskey)
AND (AccountDoc.principalCode = #principalCode) FOR XML
PATH(''), TYPE).value('.','NVARCHAR(MAX)'),1,2,' '))
END AS invoiceNo
FROM AccountDoc LEFT OUTER JOIN JobInvoice
ON JobInvoice.principalCode = AccountDoc.principalCode AND
JobInvoice.jobCode = AccountDoc.jobCode
WHERE (AccountDoc.jobCode = #jobCode)
AND (AccountDoc.isCancelledByCN = 0)
AND (AccountDoc.shipmentSyskey = #shipmentSyskey)
AND (AccountDoc.principalCode = #principalCode)
OUTPUT:
----invoiceNo----
T17080003
INV14080011
Explanation:
I want to select docNo from table AccountDoc if AccountDoc.docType = I.
Or select invoiceNo from table JobInvoice if AccountDoc.docType = M.
The problem is what if under same jobCode there have 2 docType which are M and I, how I gonna display these 2 invoices?
You can achieve this by using CTE and FOR XML. below is the sample code i created using similar tables you have -
Create table #AccountDoc (
id int ,
docType char(1),
docNo varchar(10)
)
Create table #JobInvoice (
id int ,
invoiceNo varchar(10)
)
insert into #AccountDoc
select 1 , 'M' ,'M1234'
union all select 2 , 'M' ,'M2345'
union all select 3 , 'M' ,'M3456'
union all select 4 , 'I' ,'I1234'
union all select 5 , 'I' ,'I2345'
union all select 6 , 'I' ,'I3456'
insert into #JobInvoice
select 1 , 'INV1234'
union all select 2 , 'INV2345'
union all select 3 , 'INV3456'
select *
from #AccountDoc t1 left join #JobInvoice t2
on t1.id = t2.id
with cte as
(
select isnull( case t1.docType WHEN 'M' THEN t2.invoiceNo WHEN 'I' then
t1.docNo end ,'') invoiceNo
from #AccountDoc t1 left join #JobInvoice t2
on t1.id = t2.id )
select invoiceNo + ',' from cte For XML PATH ('')
You need to pivot your data if you have situations where there are two rows, and you want two columns. Your sql is a bit messy, particularly the bit where you put an entire select statement inside a case when in the select part of another query. These two queries are virtually the same, you should look for a more optimal way of writing them. However, you can wrap your entire sql in the following:
select
Jobcode, shipmentsyskey, [M],[I]
from(
--YOUR ENTIRE SQL GOES HERE BETWEEN THESE BRACKETS. Do not alter anything else, just paste your entire sql here
) yoursql
pivot(
max(invoiceno)
for docType in([M],[I])
)pvt

How to convert rows into column in sql

I have a table,
and need to convert it as
I have tried pivot but can't figure out please help me out .
Pivot does not work with more than one column out-of-the-box There are several approaches to solve this:
Use this table variable for all tests and please state your sample data always copy'n'pasteable. Best was a MCVE (Minimal Complete Verifyable Example) where you set up some code like mine here.
DECLARE #tbl TABLE(ID INT, Code INT,EmployeeName VARCHAR(100),ExamName VARCHAR(100),Board VARCHAR(100),Result VARCHAR(100));
INSERT INTO #tbl VALUES
(11537,12984,'TheName','SSC','b04','1st')
,(11537,12984,'TheName','HSC','b04','2nd')
,(11537,12984,'TheName','BA(H)','u33','2nd');
This is code to first concatenate your data to one single column. This allows PIVOT:
SELECT p.*
FROM
(
SELECT tbl.ID
,tbl.Code
,tbl.EmployeeName
,'Exam_' + CAST(ROW_NUMBER() OVER(PARTITION BY tbl.ID ORDER BY tbl.Code) AS VARCHAR(100)) AS ColName
,ExamName + ' (' + Board + '): ' + Result AS Concatenated
FROM #tbl AS tbl
) AS t
PIVOT
(
MIN(Concatenated) FOR ColName IN(Exam_1,Exam_2,Exam_3 /*add as many as you need*/)
) AS p
The result:
11537 12984 TheName SSC (b04): 1st HSC (b04): 2nd BA(H) (u33): 2nd
The next code does quite the same, but creates XML instead of plain text, which allows to separate your data afterwards:
SELECT p.ID
,p.Code
,p.EmployeeName
,E1
,E1.value('(/exam/#ExamName)[1]','varchar(100)') AS ExamName1
,E1.value('(/exam/#Board)[1]','varchar(100)') AS Board1
,E1.value('(/exam/#Result)[1]','varchar(100)') AS Result1
,E2
,E2.value('(/exam/#ExamName)[1]','varchar(100)') AS ExamName2
,E2.value('(/exam/#Board)[1]','varchar(100)') AS Board2
,E2.value('(/exam/#Result)[1]','varchar(100)') AS Result2
,E3
,E3.value('(/exam/#ExamName)[1]','varchar(100)') AS ExamName3
,E3.value('(/exam/#Board)[1]','varchar(100)') AS Board3
,E3.value('(/exam/#Result)[1]','varchar(100)') AS Result3
FROM
(
SELECT tbl.ID
,tbl.Code
,tbl.EmployeeName
,'Exam_' + CAST(ROW_NUMBER() OVER(PARTITION BY tbl.ID ORDER BY tbl.Code) AS VARCHAR(100)) AS ColName
,(SELECT ExamName AS [#ExamName],Board AS [#Board],Result AS [#Result] FOR XML PATH('exam')) AS AsXML
FROM #tbl AS tbl
) AS t
PIVOT
(
MIN(AsXML) FOR ColName IN(Exam_1,Exam_2,Exam_3 /*add as many as you need*/)
) AS p
OUTER APPLY
(
SELECT CAST(p.Exam_1 AS XML) AS E1
,CAST(p.Exam_2 AS XML) AS E2
,CAST(p.Exam_3 AS XML) AS E3
) AS CastedToXml
The result:
11537 12984 TheName <exam ExamName="SSC" Board="b04" Result="1st" /> SSC b04 1st <exam ExamName="HSC" Board="b04" Result="2nd" /> HSC b04 2nd <exam ExamName="BA(H)" Board="u33" Result="2nd" /> BA(H) u33 2nd
This is old-fashioned-pivot which is quite often better then normal pivot:
;WITH Numberd AS
(
SELECT *
,ROW_NUMBER() OVER(PARTITION BY tbl.ID ORDER BY tbl.Code) AS Number
FROM #tbl AS tbl
)
SELECT ID,Code,EmployeeName
,MAX(CASE WHEN Number=1 THEN ExamName END) AS ExamName1
,MAX(CASE WHEN Number=1 THEN Board END) AS Board1
,MAX(CASE WHEN Number=1 THEN Result END) AS Result1
,MAX(CASE WHEN Number=2 THEN ExamName END) AS ExamName2
,MAX(CASE WHEN Number=2 THEN Board END) AS Board2
,MAX(CASE WHEN Number=2 THEN Result END) AS Result2
,MAX(CASE WHEN Number=3 THEN ExamName END) AS ExamName3
,MAX(CASE WHEN Number=3 THEN Board END) AS Board3
,MAX(CASE WHEN Number=3 THEN Result END) AS Result3
FROM Numberd
GROUP BY ID,Code,EmployeeName
The last option was dynamic SQL...
Thnak you all , But My purpose is solved with this
SELECT
he.Id,he.Code,he.Name EmployeeName,en.Name [ExamName],bu.Name
[Board],[Result] = CASE WHEN
ac.GPA IS NULL THEN ac.Result ELSE CAST(ac.GPA AS VARCHAR) END,
ac.PassingYear
INTO #Temp
FROM H_Employee AS he
INNER JOIN
H_AcademicQualification AS ac ON ac.H_EmployeeId = he.Id
INNER JOIN
ExamName AS en ON en.Id = ac.ExamNameId
INNER JOIN
GroupSubject AS gs ON gs.Id = ac.GroupSubjectId
INNER JOIN
BoardUniversity AS bu ON bu.Id = ac.BoardUniversityId
Then
SELECT
a.id, a.Code, a.EmployeeName, a.ExamName, a.Board, a.Result, a.Passingyear,
b.ExamName, b.Board, b.Result, b.Passingyear,
c.ExamName, c.Board, c.Result, c.Passingyear,
d.ExamName, d.Board, d.Result, d.Passingyear
FROM
(SELECT
Id, Code, EmployeeName ,ExamName = CASE WHEN
ExamName LIKE 'S.S.%' THEN 'S.S.C' END,
Board=Board ,[Result]=CASE WHEN
Result IS NULL THEN Result ELSE CAST(Result AS VARCHAR) END,
Passingyear
FROM #temp
WHERE ExamName LIKE 'S.S.%') AS a
LEFT JOIN
(SELECT Id, Code, EmployeeName ,ExamName = CASE WHEN
ExamName LIKE 'H.S.%' THEN 'H.S.C' END,
Board=Board,[Result] = CASE WHEN
Result IS NULL THEN Result ELSE CAST(Result AS VARCHAR) END,
Passingyear
FROM #temp
WHERE ExamName LIKE 'H.S.%') AS b
ON a.Id=b.Id
LEFT JOIN
(SELECT Id, Code, EmployeeName ,ExamName = CASE WHEN
ExamName LIKE 'B.%' THEN 'Graduate' END,
Board=Board, [Result] = CASE WHEN
Result IS NULL THEN Result ELSE CAST(Result AS VARCHAR) END,
Passingyear
FROM #temp
WHERE ExamName LIKE 'B.%') AS c
ON a.Id=c.Id
LEFT JOIN
(SELECT Id, Code, EmployeeName, ExamName = CASE WHEN
ExamName LIKE 'M.%' THEN 'Post-Graduate' END,
Board=Board,[Result] = CASE WHEN
Result IS NULL THEN Result ELSE CAST(Result AS VARCHAR) END,
Passingyear
FROM #temp
WHERE ExamName LIKE 'M.%') AS d
ON a.Id=d.Id
Above query serve me purposes but will try #Shnugo Sample , Any way thank you all .

SQL: How to update multiple fields so empty field content is moved to the logically last columns - lose blank address lines

I have three address line columns, aline1, aline2, aline3 for a street
address. As staged from inconsistent data, any or all of them can be
blank. I want to move the first non-blank to addrline1, 2nd non-blank
to addrline2, and clear line 3 if there aren't three non blank lines,
else leave it. ("First" means aline1 is first unless it's blank,
aline2 is first if aline1 is blank, aline3 is first if aline1 and 2
are both blank)
The rows in this staging table do not have a key and there could be
duplicate rows. I could add a key.
Not counting a big case statement that enumerates the possible
combination of blank and non blank and moves the fields around, how
can I update the table? (This same problem comes up with a lot more
than 3 lines, so that's why I don't want to use a case statement)
I'm using Microsoft SQL Server 2008
Another alternative. It uses the undocumented %%physloc%% function to work without a key. You would be much better off adding a key to the table.
CREATE TABLE #t
(
aline1 VARCHAR(100),
aline2 VARCHAR(100),
aline3 VARCHAR(100)
)
INSERT INTO #t VALUES(NULL, NULL, 'a1')
INSERT INTO #t VALUES('a2', NULL, 'b2')
;WITH cte
AS (SELECT *,
MAX(CASE WHEN RN=1 THEN value END) OVER (PARTITION BY %%physloc%%) AS new_aline1,
MAX(CASE WHEN RN=2 THEN value END) OVER (PARTITION BY %%physloc%%) AS new_aline2,
MAX(CASE WHEN RN=3 THEN value END) OVER (PARTITION BY %%physloc%%) AS new_aline3
FROM #t
OUTER APPLY (SELECT ROW_NUMBER() OVER (ORDER BY CASE WHEN value IS NULL THEN 1 ELSE 0 END, idx) AS
RN, idx, value
FROM (VALUES(1,aline1),
(2,aline2),
(3,aline3)) t (idx, value)) d)
UPDATE cte
SET aline1 = new_aline1,
aline2 = new_aline2,
aline3 = new_aline3
SELECT *
FROM #t
DROP TABLE #t
Here's an alternative
Sample table for discussion, don't worry about the nonsensical data, they just need to be null or not
create table taddress (id int,a varchar(10),b varchar(10),c varchar(10));
insert taddress
select 1,1,2,3 union all
select 2,1, null, 3 union all
select 3,null, 1, 2 union all
select 4,null,null,2 union all
select 5,1, null, null union all
select 6,null, 4, null
The query, which really just normalizes the data
;with tmp as (
select *, rn=ROW_NUMBER() over (partition by t.id order by sort)
from taddress t
outer apply
(
select 1, t.a where t.a is not null union all
select 2, t.b where t.b is not null union all
select 3, t.c where t.c is not null
--- EXPAND HERE
) u(sort, line)
)
select t0.id, t1.line, t2.line, t3.line
from taddress t0
left join tmp t1 on t1.id = t0.id and t1.rn=1
left join tmp t2 on t2.id = t0.id and t2.rn=2
left join tmp t3 on t3.id = t0.id and t3.rn=3
--- AND HERE
order by t0.id
EDIT - for the update back into table
;with tmp as (
select *, rn=ROW_NUMBER() over (partition by t.id order by sort)
from taddress t
outer apply
(
select 1, t.a where t.a is not null union all
select 2, t.b where t.b is not null union all
select 3, t.c where t.c is not null
--- EXPAND HERE
) u(sort, line)
)
UPDATE taddress
set a = t1.line,
b = t2.line,
c = t3.line
from taddress t0
left join tmp t1 on t1.id = t0.id and t1.rn=1
left join tmp t2 on t2.id = t0.id and t2.rn=2
left join tmp t3 on t3.id = t0.id and t3.rn=3
Update - Changed statement to an Update statement. Removed Case statement solution
With this solution, you will need a unique key in the staging table.
With Inputs As
(
Select PK, 1 As LineNum, aline1 As Value
From StagingTable
Where aline1 Is Not Null
Union All
Select PK, 2, aline2
From StagingTable
Where aline2 Is Not Null
Union All
Select PK, 3, aline3
From StagingTable
Where aline3 Is Not Null
)
, ResequencedInputs As
(
Select PK, Value
, Row_Number() Over( Order By LineNum ) As LineNum
From Inputs
)
, NewValues As
(
Select S.PK
, Min( Case When R.LineNum = 1 Then R.addrline1 End ) As addrline1
, Min( Case When R.LineNum = 2 Then R.addrline1 End ) As addrline2
, Min( Case When R.LineNum = 3 Then R.addrline1 End ) As addrline3
From StagingTable As S
Left Join ResequencedInputs As R
On R.PK = S.PK
Group By S.PK
)
Update OtherTable
Set addrline1 = T2.addrline1
, addrline2 = T2.addrline2
, addrline3 = T2.addrline3
From OtherTable As T
Left Join NewValues As T2
On T2.PK = T.PK
R. A. Cyberkiwi, Thomas, and Martin, thanks very much - these were very generous responses by each of you. All of these answers were the type of spoonfeeding I was looking for. I'd say they all rely on a key-like device and work by dividing addresses into lines, some of which are empty and some of which aren't, excluding the empties. In the case of lines of addresses, in my opinion this is semantically a gimmick to make the problem fit what SQL does well, and it's not a natural way to conceptualize the problem. Address lines are not "really" separate rows in a table that just got denormalized for a report. But that's debatable and whether you agree or not, I (a rank beginner) think each of your alternatives are idiomatic solutions worth elaborating on and studying.
I also get lots of similar cases where there really is normalization to be done - e.g., collatDesc1, collatCode1, collatLastAppraisal1, ... collatLastAppraisal5, with more complex criteria about what in excludeand how to order than with addresses, and I think techniques from your answers will be helpful.
%%phsloc%% is fun - since I'm able to create a key in this case I won't use it (as Martin advises). There was other stuff in Martin's stuff I wasn't familiar with too, and I'm still tossing them all around.
FWIW, here's the trigger I tried out, I don't know that I'll actually use it for the problem at hand. I think this qualifies a "bubble sort", with the swapping expressed in a peculiar way.
create trigger fixit on lines
instead of insert as
declare #maybeblank1 as varchar(max)
declare #maybeblank2 as varchar(max)
declare #maybeblank3 as varchar(max)
set #maybeBlank1 = (select line1 from inserted)
set #maybeBlank2 = (select line2 from inserted)
set #maybeBlank3 = (select line3 from inserted)
declare #counter int
set #counter = 0
while #counter < 3
begin
set #counter = #counter + 1
if #maybeBlank2 = ''
begin
set #maybeBlank2 =#maybeblank3
set #maybeBlank3 = ''
end
if #maybeBlank1 = ''
begin
set #maybeBlank1 = #maybeBlank2
set #maybeBlank2 = ''
end
end
select * into #kludge from inserted
update #kludge
set line1 = #maybeBlank1,
line2 = #maybeBlank2,
line3 = #maybeBlank3
insert into lines
select * from #kludge
You could make an insert and update trigger that check if the fields are empty and then move them.