How can I write a query that prints in response to letters by giving row ID and size number?
Example:
ID = 4000
NAME = 38/39/40/41/42/43/44
My Input :
ID = 4000
NAME = 40
Result = A/B/C/D/E/F/G
40 = C
Please try the following solution.
SQL
-- DDL and sample data population, start
DECLARE #tbl AS TABLE (ID INT PRIMARY KEY, tokens VARCHAR(MAX));
INSERT INTO #tbl (ID, tokens) VALUES
(2000, '44/46/48/50/52/54'),
(4000, '38/39/40/41/42/43/44');
-- DDL and sample data population, end
DECLARE #separator CHAR(1) = '/'
, #alphabet VARCHAR(50) = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
, #ID INT = 4000
, #token VARCHAR(20) = '40';
SELECT *
, Result = IIF(pos > 0, SUBSTRING(#alphabet,pos,1),NULL)
FROM #tbl AS t
CROSS APPLY (SELECT TRY_CAST('<root><r>' +
REPLACE(tokens, #separator, '</r><r>') +
'</r></root>' AS XML)
.query('
for $x in /root/r
let $pos := count(/root/r[. << $x[1]]) + 1
return if ($x/text()=sql:variable("#token")) then $pos
else ()
').value('.', 'INT')) AS t1(pos)
WHERE ID = #ID;
Output
+------+----------------------+-----+--------+
| ID | tokens | pos | Result |
+------+----------------------+-----+--------+
| 4000 | 38/39/40/41/42/43/44 | 3 | C |
+------+----------------------+-----+--------+
Related
I need to find a way to get the numbers between the dashes. This is the only way I know to do it, but I know that not all of our accounts are the same length. So I am just looking for a way to get everything between before, after and between the dashes. This is an example of the types of accounts we have. '2-0-200-325-0' and '1-0-1105-1500-1520' The non-digit characters are only dashes and nothing else.
declare #Department Int
declare #Account Int
declare #Company Int
declare #Location Int
declare #SubAccount Int
declare #AccountNo varchar(24) = '2-0-200-325-0'
declare #CommaPos Int
select #CommaPos = charindex('-',#accountno)
set #Company = substring(#accountno,1,#CommaPos-1)
select #Company as Company
set #Location = Substring(#AccountNo, #CommaPos+1, 1)
select #Location as Location
set #Department = Substring(#AccountNo, #CommaPos+3, 4)
select #Department as Department
set #Account = Substring(#AccountNo, #CommaPos+8, 4)
select #Account as Account
set #SubAccount = Substring(#AccountNo, #CommaPos+13, 4)
select #SubAccount as SubAccount
One option uses a recursive query for parsing. This properly handles the variable lenght of each part - and can easily be extended to handle more parts if needed.
-- declare the variables
declare #AccountNo varchar(24) = '2-0-200-325-0';
declare #Department Int;
declare #Account Int;
declare #Company Int;
declare #Location Int;
declare #SubAccount Int;
-- parse and assign values to variables
with cte as (
select
substring(#AccountNo + '-', 1, charindex('-', #AccountNo + '-') - 1) val,
substring(#AccountNo + '-', charindex('-', #AccountNo + '-') + 1, len(#AccountNo)) rest,
1 lvl
union all
select
substring(rest, 1, charindex('-', rest) - 1),
substring(rest, charindex('-', rest) + 1, len(rest)),
lvl + 1
from cte
where charindex('-', rest) > 0
)
select
#Company = max(case when lvl = 1 then val end),
#Location = max(case when lvl = 2 then val end),
#Department = max(case when lvl = 3 then val end),
#Account = max(case when lvl = 4 then val end),
#SubAccount = max(case when lvl = 5 then val end)
from cte;
-- check the results
select
#AccountNo AccountNo,
#Company Company,
#Location Location,
#Department Department,
#Account Account,
#SubAccount SubAccount
;
Demo on DB Fiddle:
AccountNo | Company | Location | Department | Account | SubAccount
:------------ | ------: | -------: | ---------: | ------: | ---------:
2-0-200-325-0 | 2 | 0 | 200 | 325 | 0
This was my approach:
--first I use a declared table variable to simulate your issue:
DECLARE #tbl TABLE(ID INT IDENTITY,ACCOUNT_NO VARCHAR(24))
INSERT INTO #tbl VALUES('2-0-200-325-0');
--The query
SELECT t.ID
,t.ACCOUNT_NO
,casted.value('x[1]','int') AS Company
,casted.value('x[2]','int') AS Location
,casted.value('x[3]','int') AS Department
,casted.value('x[4]','int') AS Account
,casted.value('x[5]','int') AS SubAccount
FROM #tbl t
CROSS APPLY(VALUES(CAST('<x>' + REPLACE(t.ACCOUNT_NO,'-','</x><x>') + '</x>' AS XML))) A(casted);
The idea in short:
We use simple string operations to transform your dash-separated list of numbers into XML.
Now we can use XQuery to retrieve each element by its position (typesafe!).
Find details here. In this link there is also a faster approach using JSON support (needs v2016+):
SELECT t.ID
,t.ACCOUNT_NO
,A.*
FROM #tbl t
CROSS APPLY OPENJSON(CONCAT('[[',REPLACE(t.ACCOUNT_NO,'-',','),']]'))
WITH(Company INT '$[0]'
,Location INT '$[1]'
,Department INT '$[2]'
,Account INT '$[3]'
,SubAccount INT '$[4]') A;
The idea of this JSON approach:
Agains we use some simple string operations to transform your string into a JSON array.
Using two array brackets ([[) allows to use OPENJSON() with a WITH clause.
The WITH clause allows to grab each fragment by its (zero-based) position (typesafe).
The WITH clause is some kind of implicit pivoting.
/*
-- First Create this function. This is what you need.
-- It will split a sentence into words, given a defined separator
CREATE FUNCTION [dbo].[udf_SplitString] (#Sentence varchar(max), #Separator char(1))
RETURNS #WordList TABLE (Word varchar(50))
AS
BEGIN
SET #Separator = ISNULL(#Separator, ' ')
DECLARE #Word varchar(50)
SET #Sentence = LTRIM(#Sentence) + #Separator
WHILE (CHARINDEX(#Separator, #Sentence) > 0)
BEGIN
SET #Word = SUBSTRING(#Sentence, 1, CHARINDEX(#Separator, #Sentence) - 1)
INSERT INTO #WordList SELECT LTRIM(#Word)
-- Remove word added to the List from the sentence.
SET #Sentence = SUBSTRING(#Sentence, CHARINDEX(#Separator, #Sentence) + 1, LEN(#Sentence))
SET #Sentence = LTRIM(#Sentence)
END
RETURN
END
GO
*/
DECLARE #AccountList TABLE (AccountNo varchar(20), Variable varchar(20))
INSERT INTO #AccountList VALUES
('1-0-1105-1200-1290','')
, ('1-0-1105-1500-1520','')
, ('1-0-1105-1500-1620','')
, ('1-0-1106-1200-1250','')
, ('1-0-1106-1200-1290','')
, ('1-0-1106-1500-1520','')
;
DECLARE #VariableList TABLE (OrderNo int, VariableName varchar(20))
INSERT INTO #VariableList VALUES
(1, 'Company ')
, (2, 'Location ')
, (3, 'Department ')
, (4, 'Account ')
, (5, 'SubAccount ')
;
SELECT
AccountNo
, Variable = (SELECT VariableName FROM #VariableList WHERE RowNo = OrderNo)
, Value = Word
FROM (
SELECT
RowNo = ROW_NUMBER() OVER(PARTITION BY AccountNo ORDER BY AccountNo)
, AccountNo = L.AccountNo
, Variable = ''
, Word = W.Word
FROM #AccountList L
CROSS APPLY dbo.udf_SplitString(L.AccountNo, '-') W -- Here how to use the function
) R
I have a string : Brand=b1-b2&Vendor=v1-v2-v3&CustomField=cf1-cf2-cf3
I want to make a table..
**Name** | **Value**
------------- | ------
Brand | b1
Brand | b2
Vendor | v1
Vendor | v2
Vendor | v3
CustomField | cf1
CustomField | cf2
CustomField | cf3
Thanks for your help
Edit Note : I tried, but I do not think it's performance
CREATE PROCEDURE [dbo].[SplitTableInStr]
(
#StrParameter NVARCHAR(MAX)
)
AS
BEGIN
DECLARE #Delimiter CHAR = '&',
#Index SMALLINT,
#Start SMALLINT,
#DelSize SMALLINT;
SET #DelSize = LEN(#Delimiter);
CREATE TABLE #FilteredItems
(
[Key] NVARCHAR(400),
[Values] NVARCHAR(MAX)
)
WHILE LEN(#StrParameter) > 0
BEGIN
SET #Index = CHARINDEX(#Delimiter, #StrParameter)
IF #Index = 0
BEGIN
DECLARE #Key NVARCHAR(400) = SUBSTRING(#StrParameter, 1, CHARINDEX('=', #StrParameter) - 1);
DECLARE #Values NVARCHAR(MAX) = SUBSTRING(#StrParameter, CHARINDEX('=', #StrParameter) + 1, LEN(#StrParameter));
INSERT INTO #FilteredItems ([Key],[Values])
VALUES (#Key,#Values)
BREAK
END
ELSE
BEGIN
DECLARE #IndexItem NVARCHAR(MAX) = SUBSTRING(#StrParameter, 1, #Index - 1);
DECLARE #IndexKey NVARCHAR(400) = SUBSTRING(#IndexItem, 1, CHARINDEX('=', #IndexItem) - 1);
DECLARE #IndexValues NVARCHAR(MAX) = SUBSTRING(#IndexItem, CHARINDEX('=', #IndexItem) + 1, LEN(#IndexItem));
INSERT INTO #FilteredItems ([Key],[Values])
VALUES (#IndexKey,#IndexValues)
SET #Start = #Index + #DelSize
SET #StrParameter = SUBSTRING(#StrParameter, #Start , LEN(#StrParameter) - #Start + 1)
END
END
DECLARE #KeyBase NVARCHAR(400),
#ValueBase NVARCHAR(MAX);
CREATE TABLE #DisplayOrderTmp
(
[EndName] NVARCHAR(400),
[EndValue] NVARCHAR(400)
)
WHILE EXISTS(SELECT * From #FilteredItems)
BEGIN
SELECT TOP 1 #KeyBase = [Key], #ValueBase = [Values] FROM #FilteredItems
DECLARE #OptionDelimiter CHAR = '-',
#OptionIndex SMALLINT,
#OptionStart SMALLINT,
#OptionDelSize SMALLINT;
SET #OptionDelSize = LEN(#OptionDelimiter)
WHILE LEN(#ValueBase) > 0
BEGIN
SET #OptionIndex = CHARINDEX(#OptionDelimiter, #ValueBase)
IF #OptionIndex = 0
BEGIN
INSERT INTO #DisplayOrderTmp VALUES (#KeyBase, #ValueBase)
BREAK;
END
ELSE
BEGIN
INSERT INTO #DisplayOrderTmp VALUES (#KeyBase, SUBSTRING(#ValueBase, 1, CHARINDEX(#OptionDelimiter, #ValueBase) - 1))
SET #OptionStart = #OptionIndex + #OptionDelSize
SET #ValueBase = SUBSTRING(#ValueBase, #OptionStart , LEN(#ValueBase) - #OptionStart + 1)
END
END
DELETE #FilteredItems WHERE [Key] = #KeyBase
END
SELECT * FROM #DisplayOrderTmp;
END
EXEC dbo.[SplitTableInStr] 'Brand=b1-b2&Vendor=v1-v2-v3&CustomField=cf1-cf2-cf3'
using a CSV Splitter table valued function by Jeff Moden with cross apply()
declare #s nvarchar(4000) = 'Brand=b1-b2&Vendor=v1-v2-v3&CustomField=cf1-cf2-cf3';
select
Name = left(s.Item,charindex('=',s.Item)-1)
, Value = i.Item
from dbo.DelimitedSplitN4k(#s,'&') s
cross apply dbo.DelimitedSplitN4k(replace(s.Item,'=','-'),'-') i
where i.ItemNumber > 1
rextester demo: http://rextester.com/RTAT28301
returns:
+-------------+------+
| Name | Item |
+-------------+------+
| Brand | b1 |
| Brand | b2 |
| Vendor | v1 |
| Vendor | v2 |
| Vendor | v3 |
| CustomField | cf1 |
| CustomField | cf2 |
| CustomField | cf3 |
+-------------+------+
splitting strings reference:
Tally OH! An Improved SQL 8K “CSV Splitter” Function - Jeff Moden
Splitting Strings : A Follow-Up - Aaron Bertrand
Split strings the right way – or the next best way - Aaron Bertrand
string_split() in SQL Server 2016 : Follow-Up #1 - Aaron Bertrand
I think you should get the best performance with CLR function (in C#) for this purpose:
https://msdn.microsoft.com/en-us/library/ms131103.aspx
You might manually split the input or create compiled Regex with named groups to parse input values.
I have a varchar field like:
195500
122222200
I need to change these values to:
1955.00
1222222.00
try this
Declare #s varchar(50) = '1234567812333445'
Select Stuff(#s, Len(#s)-1, 0, '.')
--> 12345678123334.45
fiddle demo
Query:
SELECT col,
LEFT(col,len(col)-2) + '.' + RIGHT(col,2) as newcol
FROM Table1
Result:
| COL | NEWCOL |
|-----------|------------|
| 195500 | 1955.00 |
| 122222200 | 1222222.00 |
If you want to add a '.' before the last two digits of your values you can do:
SELECT substring(code,0,len(code)-1)+'.'+substring(code,len(code)-1,len(code))
FROM table1;
sqlfiddle demo
Please try:
select
Col,
REVERSE(STUFF(REVERSE(Col), 1, 2, LEFT(REVERSE(Col), 2)+'.'))
from YourTable
SQL Fiddle Demo
CREATE TABLE #T ( Value VARCHAR(20) )
INSERT INTO #T ( Value ) VALUES ( 195500 ), ( 122222200)
SELECT
Value
, NewValue = CONVERT(DECIMAL(17,2),CONVERT(DECIMAL,Value) / 100)
FROM #T
| Value | NewValue |
|-----------|------------|
| 195500 | 1955.00 |
| 122222200 | 1222222.00 |
Please Try :
select reverse(stuff(reverse(columnName),3,0,'.') ) from yourTable
Ran into something similar and came up with this, could probably change this to a function/SP to make it reusable. Scenario faced was inserting a specified character at different positions within the string, for a certain number of times.
/*
--- passed in string or column, N'C4CB6B22250B'
--- desired output, C4:CB:6B:22:25:0D
--- Test values
--- declare #strToChange varchar(50) = N'C4:CB:6B:22:25:0B'
--- declare #strToChange varchar(50) = N'C4CB6B22250B'
*/
declare #strToChange varchar(50) = N'C4CB6B22250B'
IF(SELECT LEN(#strToChange) - LEN(REPLACE(#strToChange, ':', ''))) > 0
BEGIN
---returns count of specified char
SELECT LEN(#strToChange) - LEN(REPLACE(#strToChange, ':', ''))
END
ELSE
BEGIN
declare #charCount int = 5; --# of times to insert the char
declare #shiftPosition int = 2; -- inital insertion shift
While(#charCount > 0)
begin
SET #strToChange = LEFT(#strToChange,len(#strToChange)- #shiftPosition) + ':' + RIGHT(#strToChange,#shiftPosition)
SET #charCount = #charCount - 1 --decrement charCount for each added char
SET #shiftPosition = #shiftPosition + 3 ---increment shift position by 3 for the new char and the chars that were already there
end
SELECT #strToChange
END
Please see the following code. You can choose the symbols and index in variable.
declare #index int,#sym varchar(10)
set #sym='#'
set #index=2
select left(195500,#index) +''+#sym+''+right(195500,len(195500)-#index)
declare #a varchar(10) = 'aaa'
select concat(#a,'.00')
I can't find a good way to do this and because of some restrictions it has to be a coded without the use of variables but I can call a function. Anyway, I need to return a result set from a Select query and one of the rows is a pipe delimited string.
So, it will return something like:
id| Name | Message |
---------------------
1 | Some Name | 'Here is my | delimited | message'
And I need it to be
id| Name | Message1 | Message2 | Message3
------------------------------------------------------
1 | Some Name | 'Here is my' | 'delimited' | 'message'
I was thinking of something like Parsename('','|',1), where you can pass in the delimiter instead of it always being a period but I don't know the best way to accomplish that.
EDIT: I've tried variations of this but of course it is very confusing. There could be 4 or more |
SELECT Field1,
Field2 AS Originalvalue,
--1
SUBSTRING(Field2,0,CHARINDEX('|',Field2)) AS Msg1,
--2
LEFT(SUBSTRING(Field2,CHARINDEX('|',Field2)+1 ,LEN(Field2)),CHARINDEX('|',SUBSTRING(Field2,CHARINDEX('|',Field2)+1 ,LEN(Field2)))-1)
AS ExtractedValue
FROM Table1 T1 JOIN Table2 T2
ON T1.Id = T2.Id
WHERE T1.Id = 12
You can write a sql function as follows.
create Function dbo.fn_Parsename(#Message Varchar(1000), #delimiter char(1), #index int )
Returns Varchar(1000)
As
Begin
Declare
#curIndex int = 0,
#pos int = 1,
#prevPos int = 0,
#result varchar(1000)
while #pos > 0
Begin
set #pos = CHARINDEX(#delimiter, #Message, #prevPos);
if(#pos > 0)
begin-- get the chars between the prev position to next delimiter pos
set #result = SUBSTRING(#message, #prevPos, #pos-#prevPos)
end
else
begin--get last delim message
set #result = SUBSTRING(#message, #prevPos, LEN(#message))
end
if(#index = #curIndex)
begin
return #result
end
set #prevPos = #pos + 1
set #curIndex = #curIndex + 1;
end
return ''--not found
End
And you can call it as below:
select dbo.fn_Parsename('Here is my | delimited | message','|', 0)
select dbo.fn_Parsename('Here is my | delimited | message','|', 1)
select dbo.fn_Parsename('Here is my | delimited | message','|', 2)
I have a query that returns the people in a certain household, however the individuals show up in to separate rows, what i want to do is merge the two rows into one.
SELECT dbo.households.id, dbo.individuals.firstname, dbo.individuals.lastname
FROM dbo.households INNER JOIN
dbo.individuals ON dbo.households.id = dbo.individuals.householdID
WHERE (dbo.households.id = 10017)
Current results:
ID | First Name | Last Name |
1 | Test | Test1 |
1 | ABC | ABC1 |
Desired results:
ID | First Name | Last Name |ID1| First Name1| Last Name1|
1 | Test | Test1 |1 | ABC | ABC1 |
However if theres 3 people then it would need to merge all 3 and so on
Depending on the response to the question I asked above, below is a simple script that would compile the names into a string and then output the string (I don't have access to the syntax validator now so forgive any errors):
DECLARE
#CNT INT,
#R_MAX INT,
#H_ID INT,
#R_FIRST VARCHAR(250),
#R_LAST VARCHAR(250),
#R_NAMES VARCHAR(MAX)
SET #CNT = 0; --Counter
SET #R_NAMES = 'Names: ';
SELECT #R_MAX = COUNT(*) FROM dbo.individuals a WHERE a.householdID = #H_ID; --Get total number of individuals
PRINT(#R_MAX); --Output # of matching rows
--Loop through table to get individuals
WHILE #CNT < #R_MAX
BEGIN
--Select statement
SELECT * FROM (SELECT
#R_FIRST = b.firstname,
#R_LAST = b.lastname,
ROW_NUMBER() OVER (ORDER BY b.lastname, b.firstname) AS Row
FROM
dbo.households a INNER JOIN
dbo.individuals b ON a.id = b.householdID
WHERE
(a.id = #H_ID)) AS RN WHERE (Row = #CNT);
SET #R_NAMES = #R_NAMES + #R_FIRST + #R_LAST + '; '; --Add individual's name to name string
PRINT(CAST(#CNT AS VARCHAR) + ':' + #R_NAMES);
SET #CNT = #CNT +1; --Increase counter
END
PRINT(#R_NAMES); --Output the individuals
Provided you're using SQL Server 2005 or up, you might be able to use FOR XML PATH('') to concatenate the strings.
This should do what you want without having to do manual loops:
edit: fixed up SQL to actually work (now I have access to SQL)
SELECT households.id,
STUFF(
(
SELECT '; ' + [firstname] + '|' + lastname AS [text()]
FROM individuals
WHERE individuals.householdID = households.id
FOR XML PATH('')
)
, 1, 2, '' ) -- remove the first '; ' from the string
AS [name]
FROM dbo.households
WHERE (households.id = 10017)
This is pretty close to the format of data that you wanted.
it converts the data to XML (without any actual XML markup due to the PATH('')) and then joins it back to the header row.