Separate string into columns - sql-server-2008

Data :
Tree Depth
URL1||URL2 2
URL2||URL3 2
URL3||URL4||URL5 3
URL1||URL2||URL3 3
In the above data the Tree column consists of string separated by "||". I need to convert the above data such that I have 3 columns (since the max depth is 3 in this example) , the result should look like:
COL1 COL2 COL3 DEPTH
URL1 URL2 2
URL2 URL3 2
URL3 URL4 URL5 3
URL1 URL2 URL3 3
In the above example the max depth is 3 however in real world it could be N number.

Good day,
In first glance it is look like we need to use user defined SPLIT function but since number of values that you have in each string is not more then 4, there is a much simpler and probably much better solution. We just need to use the built-in PARSENAME function.
I did not test the code but the solution should be something like this:
SELECT PARSENAME(REPLACE(Tree,'||','.'), 1) as col1, PARSENAME(REPLACE(Tree,'||','.'), 2) as col2, PARSENAME(REPLACE(Tree,'||','.'), 3) as col3, Depth
from TableName
I replace the || with dot, since PARSENAME parse names that split by dot. this is the trick :-)
I actually mentioned example like this in my lecture at the sqlsaturday #360. You can see the presentation. The lecture was about WHY to use SQLCLR, and not less important WHEN to use it over transact-SQL. but I also talked about when NOT to use it, and this was one of the examples there.
In any case! if you are going to use SPLIT function then you should use SQLCLR and not T-SQL, as you can see here.

Try this, you just need to enter your Input Table, Output Table, Delimeter and Column to split. It can handle depth of more than 3, unlike PARSENAME function.
It is tested with 100,000 records and 30 split columns. It takes 10 sec to create the desired output.
Declare #Delimiter nvarchar(10) = '||'
Declare #InputTable nvarchar(2000) = '<<input table name>>'
Declare #OutputTable nvarchar(2000) = '<<output table name>>'
Declare #ColumnToSplit nvarchar(2000) = '<<column to split>>'
Declare #lsql nvarchar(max)
Declare #treeDepth int
If Object_id('dbo.treeDepth') is not null
Drop table dbo.treeDepth
CREATE TABLE dbo.treeDepth (depth INT)
declare #ltext nvarchar(max)= 'Select max(1+(len('+#ColumnToSplit+')- len(Replace('+#ColumnToSplit+','''+#Delimiter+''','''')))/(len('''+#Delimiter+'''))) from '+#InputTable
insert dbo.treeDepth EXEC(#ltext)
Select #lsql = isnull(#lsql+',','') +
'xmlname.value(''/Node[1]/Node['+cast(number+1 as nvarchar)+']'',''varchar(1000)'') AS Col_'+cast(number+1 as nvarchar)+''
from master..spt_values where type = 'P' and number < (Select * from dbo.treeDepth)
set #lsql = '
WITH ForXML
AS
(
SELECT *,
CONVERT(XML,''<Node><Node>''
+ REPLACE('+#ColumnToSplit+','''+#Delimiter+''', ''</Node><Node>'') + ''</Node></Node>'') AS xmlname
FROM '+#InputTable+'
)
Select *, '+#lsql+' Into '+#OutputTable+' From ForXML
Alter table '+#OutputTable+'
Drop column xmlname
'
EXEC(#lsql)

Related

Take substring and use it in multiple where condition in sql

I have a string which has some values concatenated. Like 'Val1val2val3val4'.
I have a query like
Select * from table where var1 = Val1 and var2 = val2 and var3 = var3;
Val1 and val2 are length only 4 but val3 may differ in length. Val4 is of length 8 and no use.
I can form query with functions like var1 = substring (text, 1, 4), var2 = substring (text, 5, 8) and for var3 = substring (text, 9, Len(text) - 8).
But the problem is, i have to manually edit replace the value in 3 places in query each time in workbench. Which is a painful task in my case. Is it possible to just put the string in one place and make the SQL automatically take substrings and use in where clause conditions? I have only read access, cannot modify tables. Local variables like #var = Text, throws error code 1046. Hands tied for me. Need ideas if feasible.
You could put the input string into a derived table of one row, and cross-join that to your table:
SELECT ...
FROM (SELECT 'Val1val2val3val4' AS text) AS v
CROSS JOIN MyTable
WHERE var1 = substring(v.text, 1, 4) AND ...
Or you could use a user-defined variable and use it in a subsequent query:
SET #var = 'Val1val2val3val4';
SELECT ...
FROM MyTable
WHERE var1 = substring(#var, 1, 4) AND ...
You mentioned you got a 1046 error, but it's not clear how you were trying to assign it, so I can't guess what happened.
If you are sure that both #var1 and #var2 have length of 4 chars, then all you need is:
WHERE text LIKE CONCAT(#var1, #var2, #var3, REPEAT('_', 8))
If not then:
WHERE text LIKE CONCAT(#var1, #var2, #var3, REPEAT('_', 8))
AND CHAR_LENGTH(#var1) = 4
AND CHAR_LENGTH(#var2) = 4;
If the comparison should be case sensitive, change to:
WHERE text LIKE BINARY CONCAT(#var1, #var2, #var3, REPEAT('_', 8))

Add 2 SUM CASE statements as a column update in MySQL

I think I have this almost figured out but after 50+ Google searches, I ask this: How can I add a column to a db that is essentially a sumif function? I've seen many related questions as simple Select statements for just looking at the table in a mini table but I was hoping to actually add a column that would show these totals. I'm taking this and then pulling the data into R for further analysis.
In Excel it works like so with [ ] denoting columns of a table. It is split into 2 areas via the Serial #. The first 6 digits of the serial indicate the "parent" and the later half indicate the "child". One parent can have multiple children, as seen with BSA101 below. What I'm trying to do is sum all the costs that went into making the child (parent + child costs). So the total parent costs, get allocated to both children below.
"Packing" is the last step so this is where I'd want the totals to end up so there are no duplicates.
Example
=IF(LEN([serial])>6,IF([process]="Packing",SUMIF([serial],[#serial],[process_cost])+SUMIF([serial],LEFT([#serial],6),[process_cost]),""),"")
serial process process_cost total_child_cost
BSA101A33 Packing 10 160
BSA101A34 Packing 10 195
BSA101 Cast 50 ""
BSA101 Mold 30 ""
BSA101 Mold 30 ""
BSA101A33 Finish 15 ""
BSA101A34 Finish 25 ""
BSA101A33 Polish 25 ""
BSA101A34 Polish 50 ""
^desired table result above
MySQL attempt:This post helped me Adding Case Statements
SQL Fiddle: http://sqlfiddle.com/#!9/b0e58
Here I've added a column in data called total_cost. Right now I'm getting an "Invalid use of group function" error which after researching, talks about a HAVING clause but not sure where to place it.
UPDATE data
SET total__child_cost =
(CASE WHEN length(serial) > 6
AND process = 'Packing'
THEN
IF(serial = serial, sum(process_cost),0) END)
+
(CASE WHEN left(serial,6) = serial
THEN sum(process_cost)
END)
This ended up being the solution.
DELIMITER //
CREATE FUNCTION `getParent1`(inSerialn Varchar(20)) RETURNS int(11)
BEGIN
Declare parent varchar(20);
Declare result int;
set parent = left(inSerialn, 6);
set result = (Select sum(process_cost) From mfng.data where serialn = parent);
return result;
END //
DELIMITER //
CREATE FUNCTION `getChild1`(inSerialn Varchar(20)) RETURNS int(11)
BEGIN
Declare result int;
set result = (Select sum(process_cost) FROM mfng.data where serialn = inSerialn);
return result;
END//
UPDATE mfng.data set total_child_cost =
(case when length(serialn) > 6 AND pdn_process = 'Packing'
THEN
getChild1(serialn) + getParent1(serialn)
ELSE
0 END);
//

How detect the two word of a string like “helpme”?

I have a dictionary table (words) and another table with concatenated 2 words like "helpme", "helloword" "loveme"...
I want to transform this table to "help me", "hello word", "love me"
I run this sequence :
SELECT
table_concatened.twowords,
t1.word as 'word1',
t2.word as 'word2'
FROM
table_concatened
JOIN dictionary_table AS t1 ON SUBSTRING(table_concatened.twowords,1,len(t1.word)) = t1.word
JOIN dictionary_table AS t2 ON SUBSTRING(table_concatened.twowords,len(t1.word)+1,len(table_concatened.twowords)) = t2.word;
It is working, but is took a very long time with my table.
How can I optimise my sql sequence?
---- exemple of table ---
dictionary_table
|hello|
|word |
|love |
|me |
exemple of table_concatened :
|helloword|
|loveyou |
Edit:
1) The use case is for autocorrection. For example on skype, on iPhone, on chrome, when I type "helloword", I have auto correction to "hello word".
2) The database here is not very important. Our issue is about algo logic and performance optimisation.
If you don't mind going dynamic (and if SQL Server)
-- Generate Some Sample Data
Declare #Dictionary_Table table (word varchar(50));Insert Into #Dictionary_Table values ('hello'),('word'),('love'),('me')
Declare #table_concatened table (ID int,twowords varchar(50));Insert Into #table_concatened values (1,'helloword'),(2,'loveyou')
-- Generate SQL and Execute
Declare #SQL varchar(max)=''
Select #SQL = #SQL+concat(',(',ID,',''||',replace(twowords,'''',''''''),'||'')') From #table_concatened --Where ID=2
Select #SQL = Replace(#SQL,MapFrom,MapTo)
From (
Select MapFrom = word
,MapTo = '|'+ltrim(rtrim(word))+'|'
From #Dictionary_Table
Union All
Select '|',' ' -- Remove Any Remaining |
Union All
Select ' ',' ' -- Remove Any Remaining |
) A
Select #SQL = 'Select ID,Value=ltrim(rtrim(Value)) From ('+Stuff(#SQL,1,1,'values')+') N(ID,Value)'
Exec(#SQL)
Returns
ID Value
1 hello word
2 love you

Updating column values as per our format

There are two types of records in my Db such as MS-NW and CS in the same column of table DICIPLINE I want to wrap if its CS (ANY TWO STRING LIKE CS,TE OR THE LIKE) then wrap it to BS(CS) (OR BS(TE) ETC) or if its MS-NW (Or MS-CS, MS-TE and the like) then wrap it to MS(NW) from the column dicipline.
I updated for two strings successfully and following is the query for that kindly let me know how can i do it for values like MS-NW OR MS-CS and convert it to the format like MS(NW) from following query .
UPDATE DEG set DICIPLINE = concat("BS(",DICIPLINE,")") where CHAR_LENGTH(DICIPLINE) = 2
The below query helps you to update your data.
update deg set DISIPLINE = if(length(DISIPLINE)= 2,concat('BC(',DISIPLINE,')')
,concat('MS(',substr(DISIPLINE, 4,4),')'));
See Sqlfiddle demo.
For safety, create a temporary column of same type and perform an update like this:
UPDATE deg
SET dicipline_temp = CASE
WHEN CHAR_LENGTH(dicipline) = 2
THEN CONCAT('BS(', dicipline, ')')
WHEN CHAR_LENGTH(dicipline) = 5 AND SUBSTRING(dicipline, 3, 1) = '-'
THEN CONCAT(REPLACE(dicipline, '-', '('), ')')
END
WHERE CHAR_LENGTH(dicipline) = 2 OR (CHAR_LENGTH(dicipline) = 5 AND SUBSTRING(dicipline, 3, 1) = '-')
If results are acceptable, update the actual column.

Inserting images from file path -- Not getting value on the select statement

DECLARE #imgString varchar(800)
DECLARE #insertString varchar(3000)
DECLARE #imgNumber int
Declare #imgName varchar(100)
SET #imgNumber = 1
WHILE #imgNumber<> 101
BEGIN
SET #imgName = 'SELECT (items) FROM dbo.building_piclink'
SET #imgString = 'C:\Documents and Settings\Administrator\Desktop\photos\' + #imgName
SET #insertString = 'INSERT INTO dbo.building__ATTACH (DATA)
SELECT * FROM OPENROWSET(BULK N''' + #imgString + ''', SINGLE_BLOB) as tempImg'
SET #imgNumber = #imgNumber + 1
END
GO
I am having problems with the #imgName. I can't figure out how to get the value from the select statement not the (items) like below:
C:\Documents and Settings\Administrator\Desktop\photos\SELECT (items) FROM dbo.building_piclink
Thank you!
Your code has several problems:
1) You're selecting a file name from the view - but what if that view contains more than one entry?? Which filename are you selecting?? Your current code first of all doesn't work at all the way it is, and even if it were working - you're still potentially selecting hundreds of filenames into a single variable - which of course won't work....
So you'll need to fix this here first:
SET #imgName = 'SELECT (items) FROM dbo.building_piclink'
First of all - loose the single quotes:
SELECT #imgName = (items) FROM dbo.building_piclink
But now - do you have a unique ID that you can select for? Or do you want to get just the first entry (whatever that is) ??
So either you need:
SELECT #imgName = ImageFileName FROM dbo.building_piclink WHERE ..........
and fill in that WHERE clause with a condition that guarantees to return just a single row, or use TOP 1:
SELECT TOP (1) #imgName = ImageFileName FROM dbo.building_piclink
In that case - you'll just get exactly one filename - if you don't specify an ORDER BY, then there's no guarantee what you'll get - maybe you'll want to add a ORDER BY DueDate or something to prioritize which file names you get first.
2) Your code for loading the image data is non workable, either - what you need to do is build up the SQL statement as a string, and then execute it (called dynamic SQL) - something like this:
SET #imgString = 'C:\Documents and Settings\Administrator\Desktop\photos\' + #imgName
SET #insertString =
'INSERT INTO dbo.building__ATTACH (DATA)
SELECT * FROM OPENROWSET(BULK N''' + #imgString + ''', SINGLE_BLOB) as tempImg'
EXEC(#insertString) -- actually execute your SQL statement!
With these two fixes, you should be on the way to get this thing working