Full invoice number + comma separated SQL list (TSQL) - sql-server

Does someone perhaps know how to get a comma separated list from SQL that doesn't duplicate - it's a little hard to explain. Let me give an example.
I have a list of invoices + the shipment it belongs to in a table like below:
InvoiceNumber ShipmentNumber
0180376000 1stShipment
0180376005 1stShipment
0180376003 1stShipment
0180375997 1stShipment
0180375993 1stShipment
This list needs to be divided up into main InvoiceNumbers followed by the right 2 digits of the remaining invoice numbers. Result should look similar to the below.
01803760, 00, 05, 03, 01803759, 97, 93
At this point I can get the comma separated list fairly easily but cannot figure out how to position the 2 digit after each respective invoice that it belongs to.
Any suggestion of how to do this would be great!!

Try it like this
DECLARE #tbl TABLE(InvoiceNumber VARCHAR(100));
INSERT INTO #tbl VALUES
('0180376000')
,('0180376005')
,('0180376003')
,('0180375997')
,('0180375993');
WITH CutInTwo AS
(
SELECT LEFT(InvoiceNumber,8) AS Number
,RIGHT(InvoiceNumber,2) AS SubNumber
FROM #tbl
)
,OnlyMainNumbers AS
(
SELECT DISTINCT Number
FROM CutInTwo
)
SELECT y.Number + ' ' + (STUFF((SELECT ', ' + x.SubNumber FROM CutInTwo AS x WHERE x.Number=y.Number FOR XML PATH('')),1,2,''))
FROM OnlyMainNumbers AS y
If you really need this in one single string, you might wrap the final select like this:
SELECT STUFF(
(
SELECT ', ' + y.Number + ', ' + (STUFF((SELECT ', ' + x.SubNumber FROM CutInTwo AS x WHERE x.Number=y.Number FOR XML PATH('')),1,2,''))
FROM OnlyMainNumbers AS y
FOR XML PATH('')
),1,2,'')

Try this
DECLARE #tbl TABLE (InvoceNumber NVARCHAR(50))
INSERT INTO #tbl VALUES ('0180376000')
INSERT INTO #tbl VALUES ('0180376005')
INSERT INTO #tbl VALUES ('0180376003')
INSERT INTO #tbl VALUES ('0180375997')
INSERT INTO #tbl VALUES ('0180375993')
SELECT
(
SELECT
A.InvoceNumber + ', '
FROM
(
SELECT DISTINCT LEFT(InvoceNumber, LEN(InvoceNumber) - 2) InvoceNumber FROM #tbl
UNION ALL
SELECT RIGHT(InvoceNumber, 2) InvoceNumber FROM #tbl
) A
FOR XML PATH ('')
) Invoce
Output:
01803759, 01803760, 00, 05, 03, 97, 93,
If Order is important.
SELECT
(
SELECT
Result.InvoceNumber + ', ' + Result.Invo
FROM
(
SELECT
A.InvoceNumber,
(
SELECT
IA.Invo + ', ' AS [text()]
FROM
(
SELECT DISTINCT LEFT(InvoceNumber, LEN(InvoceNumber) - 2) InvoceNumber, RIGHT(InvoceNumber, 2) AS Invo FROM #tbl
) IA
WHERE
IA.InvoceNumber = A.InvoceNumber
FOR XML PATH ('')
) Invo
FROM
(
SELECT DISTINCT LEFT(InvoceNumber, LEN(InvoceNumber) - 2) AS InvoceNumber FROM #tbl
) A
GROUP BY
A.InvoceNumber
) Result
FOR XML PATH ('')
) S
Output:
01803759, 93, 97, 01803760, 00, 03, 05,

Related

Remove all Non-Numeric Chars from String

I would like to truncate all characters in a column, no matter where they are.
Example:
"+49123/4567890(testnumber)"
Should be changed to
"491234567890"
Is there a way without doing a replace for each char?
I have tried to replace it with several, but it is very time-consuming.
As you mentioned, if you are expecting only [a-zA-z()/+], you can use the translate function which is available from 2017+
declare #table TABLE (str varchar(max))
insert into #table
select '+49123/4567890(estnumber)'
select replace(translate(str, '/+()abcdefghijklmnopqrstuvwxyz', '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'), '~', '') digits
from #table
For more complex scenarios where the characters are not known, you can try using recursive CTE on a string column to extract only digits like following query.
;with cte
as (
select v.txt originalstring
,v.txt
,convert(varchar(max), '') as digits
,1 as lev
from (
values ('+49123/4567890(testnumber)')
,('+234&*#$%!##')
) v(txt)
union all
select originalstring
,stuff(txt, 1, 1, '')
,(
case
when left(txt, 1) LIKE '[0-9]'
then digits + left(txt, 1)
else digits
end
)
,lev + 1
from cte
where txt > ''
)
select originalstring
,digits
from (
select c.originalstring
,c.digits
,row_number() over (partition by c.originalstring order by lev desc
) rn
from cte c
) t
where rn = 1
Output
originalstring digits
--------------- --------
+234&*#$%!## 234
+49123/4567890(testnumber) 491234567890
A set-based option that exists in SQL Server 2017+ is to utilise translate.
You can hopefully adapt the following to your specific use-case:
select col, Replace(Translate(col, r, Replicate('*', Len(r))), '*', '') Newcol
from t
cross apply(values(' ABCDEFGHIJKLMNOPQRSTUVWXYZ/\+()'))r(r);
Example DB<>Fiddle
Instead of hardcoding the list of "bad" characters you can use a double TRANSLATE to first get the unwanted characters and then plug that back into TRANSLATE.
DECLARE #table TABLE
(
str VARCHAR(max)
)
INSERT INTO #table
SELECT '+49123/4567890(testnumber) '
DECLARE #CharactersToKeep VARCHAR(30) = '0123456789'
SELECT REPLACE(TRANSLATE(str, bad_chars, REPLICATE('X', LEN(bad_chars + 'X') - 1)), 'X', '')
FROM #table
CROSS APPLY (SELECT REPLACE(TRANSLATE(str, #CharactersToKeep, REPLICATE(LEFT(#CharactersToKeep, 1), LEN(#CharactersToKeep))), LEFT(#CharactersToKeep, 1), '')) ca(bad_chars)

How to get "," instead of "and" in the rows in SQL Server

I have a table Test with 1 column
Module_name
Table
Computer
Laptop
Chair
My expected output:
Table,Computer,Laptop and Chair
My Query:
declare #module_name varchar(50)
SELECT #Module_Name = COALESCE(#Module_Name + ' and ', '') + module_name FROM
(SELECT DISTINCT module_name FROM Test) T
select #module_name
I am getting the output as:
Table and Computer and Laptop and Chair
My concern is how to get the "," instead of "and".
Have you tried xml method with stuff() function ?
declare #Module_names varchar(max)
set #Module_names = stuff((select distinct ',' +Module_name
from table t
for xml path('')),1,1, '')
select REVERSE(STUFF(REVERSE(#Module_names),
CHARINDEX(',', REVERSE(#Module_names)), 1,' dna ')) as Module_names
I don't endorse this solution, like I said in the comments, "grammarisation" should be done in your presentation layer.. You can, however, achieve this in SQL like so:
Edit: Slight update to cater for a single value return.
CREATE TABLE #Sample (Module varchar(10));
INSERT INTO #Sample
VALUES ('Table'),
('Computer'),
('Laptop'),
('Chair');
GO
WITH RNs AS (
SELECT Module,
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS RN --SELECT NULL as there is no ID field to work with here, thus the order will be random
FROM #Sample)
SELECT STUFF((SELECT CASE WHEN RN = MAX(RN) OVER () AND RN != 1 THEN ' and ' ELSE ', ' END + Module
FROM RNs
ORDER BY RN
FOR XML PATH('')),1,2,'');
GO
DROP TABLE #Sample;
Use the following. First gather all records together with comma, then replace just the last one with "and". Will have to make sure that your column values don't contain comma or it will be misplaced with an "and" if on last occurence.
DECLARE #result VARCHAR(MAX) = STUFF(
(
SELECT DISTINCT
', ' + T.module_name
FROM
Test AS T
FOR XML
PATH('')
),
1, 2, '')
SET #result =
REVERSE(
STUFF( -- Replace
REVERSE(#result), -- ... in the reversed string
CHARINDEX(',', REVERSE(#result)), -- ... at the first position of the comma (the last one on the original string)
1, -- just 1 character (the comma)
'dna ') -- for the reversed " and"
)
SELECT #result
Used Row_number to capture last row,
CREATE TABLE test
([Module_name] varchar(8))
;
INSERT INTO test
([Module_name])
VALUES
('Table'),
('Computer'),
('Laptop'),
('Chair')
;
SELECT STUFF((SELECT CASE WHEN RN = MAX(RN) OVER () THEN ' and ' ELSE ', ' END + Module_name
from
(
SELECT Module_name,
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS RN
FROM test
) rns
ORDER BY RN
FOR XML PATH('')),1,2,'');

Comparing two tables and displaying the result as a separate output

I have two tables and the values like this, `
CREATE TABLE Location (ID int ,Location Varchar(500))
INSERT INTO Location values (1,'Loc3'),(2,'Loc4'),(3,'Loc5'),(4,'Loc7')
CREATE TABLE InputLocation (ID int ,Location Varchar(500))
Insert into InputLocation values(1,'Loc1,Loc2,Loc3,Loc4,Loc5,Loc6')
I need to get the output by matching each values from table Location with table InputLocation and need to display the output whichever not matched with 2nd table, i.e Loc1,Loc2,Loc6 , I have tried some code like this and it worked But i need even simpler code, Any help would be greatly appreciated
My code :
SELECT STUFF((select ','+ Data.C1
FROM
(select
n.r.value('.', 'varchar(50)') AS C1
from InputLocation as T
cross apply (select cast('<r>'+replace(replace(Location,'&','&'), ',', '</r><r>')+'</r>' as xml)) as S(XMLCol)
cross apply S.XMLCol.nodes('r') as n(r)) DATA
WHERE data.C1 NOT IN (SELECT Location
FROM Location) for xml path('')),1,1,'') As Output
your script is ok.
Another method will be to use SPLIT String as describe here.
http://www.sqlservercentral.com/articles/Tally+Table/72993/
use [dbo].[DelimitedSplit8K]
Suppose my comma seperated string won't be longer than 500 then in my custom UDF i make it 500 varchar instead of varchar(8000) in order to improve performance.
SELECT STUFF((
SELECT ',' + Data.item
FROM (
SELECT il.ID
,fn.item
FROM #InputLocation IL
CROSS APPLY (
SELECT *
FROM dbo.DelimitedSplit2K(il.Location, ',')
) fn
WHERE NOT EXISTS (
SELECT *
FROM #Location L
WHERE l.Location = fn.Item
)
) data
FOR XML path('')
), 1, 1, '') AS
OUTPUT
Use recursion to avoid using slow XML Reader:
;with tmp(DataItem, Location) as (
select cast(LEFT(Location, CHARINDEX(',',Location+',')-1) as nvarchar(50)),
cast(STUFF(Location, 1, CHARINDEX(',',Location+','), '') as nvarchar(50))
from [InputLocation]
union all
select cast(LEFT(Location, CHARINDEX(',',Location+',')-1) as nvarchar(50)),
cast(STUFF(Location, 1, CHARINDEX(',',Location+','), '') as nvarchar(50))
from tmp
where Location > ''
)
select STUFF((SELECT ',' + x.Location
from (
select DataItem as Location from tmp
except Select Location from [Location]) x
FOR XML path('')), 1, 1, '') AS OUTPUT

SQL Pivot table without aggregate

I have a number of text files that are in a format similar to what is shown below.
ENTRY,1,000000,Widget 4000,1,,,2,,
FIELD,Type,A
FIELD,Component,Widget 4000
FIELD,Vendor,Acme
ENTRY,2,000000,PRODUCT XYZ,1,,,3,
FIELD,Type,B
FIELD,ItemAssembly,ABCD
FIELD,Component,Product XYZ - 123
FIELD,Description1,Product
FIELD,Description2,XYZ-123
FIELD,Description3,Alternate Part #440
FIELD,Vendor,Contoso
They have been imported into a table with VARCHAR(MAX) as the only field. Each ENTRY is a "new" item, and all the subsequent FIELD rows are properties of that item. The data next to the FIELD is the column name of the property. The data to the right of the property is the data I want to display.
The desired output would be:
ENTRY Type Component Vendor ItemAssembly Description1
1,000000,Widget 4000 A Widget 4000 Acme
2,000000,Product XYZ B Product XYZ-123 Contoso ABCD Product
I've got the column names using the code below (there are several tables that I have UNIONed together to list all the property names).
select #cols =
STUFF (
(select Distinct ', ' + QUOTENAME(ColName) from
(SELECT
SUBSTRING(ltrim(textFileData),CHARINDEX(',', textFileData, 1)+1,CHARINDEX(',', textFileData, CHARINDEX(',', textFileData, 1)+1)- CHARINDEX(',', textFileData, 1)-1) as ColName
FROM [MyDatabase].[dbo].[MyTextFile]
where
(LEFT(textFileData,7) LIKE #c)
UNION
....
) A
FOR XML PATH(''), TYPE).value('.','NVARCHAR(MAX)'),1,1,'')
Is a Pivot table the best way to do this? No aggregation is needed. Is there a better way to accomplish this? I want to list out data next to the FIELD name in a column format.
Thanks!
Here is the solution in SQL fiddle:
http://sqlfiddle.com/#!3/8f0b0/8
Prepare raw data in format (entry, field, value), use dynamic SQL to make pivot on unknown column count.
MAX() for string is enough to simulate "without aggregate" behavior in this case.
create table t(data varchar(max))
insert into t values('ENTRY,1,000000,Widget 4000,1,,,2,,')
insert into t values('FIELD,Type,A')
insert into t values('FIELD,Component,Widget 4000')
insert into t values('FIELD,Vendor,Acme ')
insert into t values('ENTRY,2,000000,PRODUCT XYZ,1,,,3,')
insert into t values('FIELD,Type,B')
insert into t values('FIELD,ItemAssembly,ABCD')
insert into t values('FIELD,Component,Product XYZ - 123')
insert into t values('FIELD,Description1,Product ')
insert into t values('FIELD,Description2,XYZ-123 ')
insert into t values('FIELD,Description3,Alternate Part #440')
insert into t values('FIELD,Vendor,Contoso');
create type preparedtype as table (entry varchar(max), field varchar(max), value varchar(max))
declare #prepared preparedtype
;with identified as
(
select
row_number ( ) over (order by (select 1)) as id,
substring(data, 1, charindex(',', data) - 1) as type,
substring(data, charindex(',', data) + 1, len(data)) as data
from t
)
, tree as
(
select
id,
(select max(id)
from identified
where type = 'ENTRY'
and id <= i.id) as parentid,
type,
data
from identified as i
)
, pivotsrc as
(
select
p.data as entry,
substring(c.data, 1, charindex(',', c.data) - 1) as field,
substring(c.data, charindex(',', c.data) + 1, len(c.data)) as value
from tree as p
inner join tree as c on c.parentid = p.id
where p.id = p.parentid
and c.parentid <> c.id
)
insert into #prepared
select * from pivotsrc
declare #dynamicPivotQuery as nvarchar(max)
declare #columnName as nvarchar(max)
select #columnName = ISNULL(#ColumnName + ',','')
+ QUOTENAME(field)
from (select distinct field from #prepared) AS fields
set #dynamicPivotQuery = N'select * from #prepared
pivot (max(value) for field in (' + #columnName + ')) as result'
exec sp_executesql #DynamicPivotQuery, N'#prepared preparedtype readonly', #prepared
Here your are, this comes back exactly as you need it. I love tricky SQL :-). This is a real ad-hoc singel-statement call.
DECLARE #tbl TABLE(OneCol VARCHAR(MAX));
INSERT INTO #tbl
VALUES('ENTRY,1,000000,Widget 4000,1,,,2,,')
,('FIELD,Type,A')
,('FIELD,Component,Widget 4000')
,('FIELD,Vendor,Acme ')
,('ENTRY,2,000000,PRODUCT XYZ,1,,,3,')
,('FIELD,Type,B')
,('FIELD,ItemAssembly,ABCD')
,('FIELD,Component,Product XYZ - 123')
,('FIELD,Description1,Product ')
,('FIELD,Description2,XYZ-123 ')
,('FIELD,Description3,Alternate Part #440')
,('FIELD,Vendor,Contoso');
WITH OneColumn AS
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS inx
,CAST('<root><r>' + REPLACE(OneCol,',','</r><r>') + '</r></root>' AS XML) AS Split
FROM #tbl AS tbl
)
,AsParts AS
(
SELECT inx
,Each.part.value('/root[1]/r[1]','varchar(max)') AS Part1
,Each.part.value('/root[1]/r[2]','varchar(max)') AS Part2
,Each.part.value('/root[1]/r[3]','varchar(max)') AS Part3
,Each.part.value('/root[1]/r[4]','varchar(max)') AS Part4
,Each.part.value('/root[1]/r[5]','varchar(max)') AS Part5
FROM OneColumn
CROSS APPLY Split.nodes('/root') AS Each(part)
)
,TheEntries AS
(
SELECT DISTINCT *
FROM AsParts
WHERE Part1='ENTRY'
)
SELECT TheEntries.Part2 + ',' + TheEntries.Part3 + ',' + TheEntries.Part4 AS [ENTRY]
,MyFields.AsXML.value('(fields[1]/field[Part2="Type"])[1]/Part3[1]','varchar(max)') AS [Type]
,MyFields.AsXML.value('(fields[1]/field[Part2="Component"])[1]/Part3[1]','varchar(max)') AS Component
,MyFields.AsXML.value('(fields[1]/field[Part2="Vendor"])[1]/Part3[1]','varchar(max)') AS Vendor
,MyFields.AsXML.value('(fields[1]/field[Part2="ItemAssembly"])[1]/Part3[1]','varchar(max)') AS ItemAssembly
,MyFields.AsXML.value('(fields[1]/field[Part2="Description1"])[1]/Part3[1]','varchar(max)') AS Description1
FROM TheEntries
CROSS APPLY
(
SELECT *
FROM AsParts AS ap
WHERE ap.Part1='FIELD' AND ap.inx>TheEntries.inx
AND ap.inx < ISNULL((SELECT TOP 1 nextEntry.inx FROM TheEntries AS nextEntry WHERE nextEntry.inx>TheEntries.inx ORDER BY nextEntry.inx DESC),10000000)
ORDER BY ap.inx
FOR XML PATH('field'), ROOT('fields'),TYPE
) AS MyFields(AsXML)

SQL Server Concatenate Query Results

I am new to sql server.
I have a query that retrieves the desired data from various tables.
The result looks like this with other columns, that contain things like name, removed for simplicity.
id xvalue
1 x
1 y
1 z
2 x
2 y
2 z
3 x
3 y
3 z
I would like to wrap the query with a select to concatenate the result set into an new result set like this.
id xvalue
1 x,y,z
2 x,y,z
3 x,y,z
I have tried to figure out how use the for xml path option and cannot seem to find the
correct syntax.
We have two tables in SQL one with columns (id,mdf,hist) another table with columns (id,hist). First table called historial, second table is resultado. Then we need to concat hist column when the mfn is the same and copy the result to the column hist in resultado table.
DECLARE #iterator INT
SET #iterator = 1
WHILE (#iterator < 100) /* Number of rows */
BEGIN
DECLARE #ListaIncidencias VARCHAR(MAX) = ''
SELECT #ListaIncidencias = #ListaIncidencias + ';' + p.hist
FROM historial p WHERE mfn = #iterator
SET #ListaIncidencias = SUBSTRING(#ListaIncidencias,2,LEN(#ListaIncidencias))
SELECT #ListaIncidencias as 'Incidencias'
INSERT INTO resultado(hist) VALUES(#ListaIncidencias) /* Insert results in new table */
SET #iterator = #iterator + 1
END
Try this
CREATE TABLE #Tmp
(
id INT ,
xValue VARCHAR(10)
)
INSERT INTO #Tmp VALUES ( 1, 'x' )
INSERT INTO #Tmp VALUES ( 1, 'y' )
INSERT INTO #Tmp VALUES ( 1, 'Z' )
INSERT INTO #Tmp VALUES ( 2, 'A' )
INSERT INTO #Tmp VALUES ( 2, 'B' )
SELECT id ,
( STUFF(( SELECT DISTINCT
',' + CAST(xValue AS VARCHAR(20))
FROM #Tmp
WHERE id = t.id
FOR
XML PATH('')
), 1, 1, '') ) AS Data
FROM #Tmp t
GROUP BY id
Something like this should do the trick for you. SQL Server should really just have an easy syntax for this. But it'll give you a comma and space separated list of the xValues.
SELECT id
,STUFF(
(SELECT DISTINCT
', ' + t.xvalue
FROM tableName t
WHERE t.id = t1.id
ORDER BY ', ' + t.xvalue
FOR XML PATH(''), TYPE
).value('.','varchar(max)')
,1,2, ''
) AS xvalue
FROM tableName t1
GROUP BY id
select id,Ids=Stuff((SELECT ',' + xvalue FROM t t1 WHERE t1.id=t.id
FOR XML PATH (''))
, 1, 1, '' )
from t
GROUP BY id
FIDDLE
There is very new functionality in Azure SQL Database and SQL Server 2016 to handle this exact scenario. Example:
select id, STRING_AGG(xvalue, ',') as xvalue
from some_table
group by id
STRING_AGG - https://msdn.microsoft.com/en-us/library/mt790580.aspx

Resources