PROGRESS DB extract string between semi colons - INSTR Function error - tsql

I'm querying a Progress DB via an OpenQuery in MS SQL Server.
I have a field (addr) that contains a string value in the below format:
text123; text 456; text 789; text 1011
I need to extract each value before the semi colon ; for separate columns
In T SQL I would do this using the below to extract the first part:
SELECT
SUBSTRING(addr,1,CHARINDEX(';', addr,1) - 1)
FROM MyTable
However, in Progress there is no CHARINDEX, but INSTR
This is my Openquery:
SELECT * FROM OPENQUERY (MyServer, 'SELECT addr, SUBSTRING("addr",1,INSTR('';'', "addr",1) - 1) as test FROM MyTable')
But I receive the following error:
OLE DB provider "MSDASQL" for linked server "MyServer" returned message "[DataDirect][ODBC Progress OpenEdge Wire Protocol driver]Error in row.".
What am I missing?

Here's how I would handle this...
PostgreSQL:
WITH -- YUOUR TEXT HERE:
someString AS (SELECT 'text123; text 456; text 789; text 1011' AS txt), -- Can come in as a variable
a AS (SELECT N FROM (VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS x(N)),
iTally(N) AS
(
SELECT 0 UNION ALL
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM a, a AS b, a AS c, a AS d
),
ngrams(N,Token) AS
(
SELECT N, SUBSTRING(txt from N::integer for 1)
FROM iTally
CROSS JOIN someString
WHERE N <= CHAR_LENGTH(txt)
)
SELECT
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS ItemNumber,
(N+1) AS ItemIndex,
len AS ItemLength,
SUBSTRING(ntxt from 1 for len) AS Item
FROM ngrams
CROSS JOIN someString
CROSS JOIN LATERAL (VALUES(SUBSTRING(txt from (N+1)::integer for CHAR_LENGTH(txt)))) AS eg(ntxt)
CROSS JOIN LATERAL (VALUES(COALESCE(NULLIF(strpos(ntxt,';'),0),CHAR_LENGTH(ntxt))-1)) AS item(len)
WHERE N = 0 OR token LIKE ';%';
Results:
ItemNumber ItemIndex ItemLength Item
----------- ---------- ----------- -----------
1 1 7 text123
2 9 9 text 456
3 19 9 text 789
4 29 9 text 101
A little trimming may be needed for the return item

Extent / array fields in Progress OpenEdge are also shown as separated by a semi-colon when viewed from SQL92, if this is an extent / array field, then you can simply use:
addr[1]
If it is not, then you can use instr, but you will need to use the correct syntax. The first parameter is the string, the second is what you want to find in it. Note the extra + ';' below to ensure the string always ends with a semi-colon:
substr( addr, 1, instr( addr + ';', ';', 1 ) - 1 )
If you not only want the first, but the second or the third, then it gits a bit trickier:
select
addr,
substring( addr, 1, instr( addr + ';', ';', 1, 1 ) ) as '#1' ,
case
when instr( addr, ';', 1, 1 ) > 0 then
substring( addr, instr( addr, ';', 1, 1 ), instr( addr + ';', ';', 1, 2 ) - instr( addr, ';', 1, 1 ) )
else
''
end as '#2' ,
case
when instr( addr, ';', 1, 2 ) > 0 then
substring( addr, instr( addr, ';', 1, 2 ), instr( addr + ';', ';', 1, 3 ) - instr( addr, ';', 1, 2 ) )
else
''
end as '#3'
from ...
All of the above has been tested from PDSOE (Eclipse) using the JDBC driver. Assuming OPENQUERY does not corrupt anything along the way, you should be fine.

Related

(Postgres) Query in a tree table in ascending and descending mode

I'm having some issues with two queries to search in a "tree" table.
So, my table is represented by the following code, and it has one only direction. However, I need to get data in both directions, ascending and descending mode.
create table graph_examle (input int null, output int );
insert into graph_examle (input, output) values
(null, 1),
(1, 2),
(2, 3 ),
(3, 4 ),
(null, 7 ),
(7,8),
(8, 4 ),
(null, 10 ),
(10, 11 ),
(11, 4),
(3, 15),
(25, 15),
(26, 15),
(15, 4 );
The ascending query has some issues. If I search by id 1, I'm expecting to see the relations:
1, 1->2, 1->2->3, 1->2->3->4, but the results are:
WITH recursive cte (initial_id, level, path, loop, input, output) AS
(
SELECT input, 1, ':' ||input || ':' , 0, input, output
FROM graph_examle WHERE input = 1
UNION ALL
SELECT
c.initial_id,
c.level + 1,
c.path ||ur.input|| ':' ,
CASE WHEN c.path LIKE '%:' ||ur.input || ':%' THEN 1 ELSE 0 END,
ur.*
FROM graph_examle ur
INNER JOIN cte c ON c.output = ur.input AND c.loop = 0
)
SELECT *
FROM cte
ORDER BY initial_id, level;
The descending query does not work as expected. If I search by id 4, I'm expecting to see the relations:
4, 4->3, 4->3->2, 4->3->2->1
4->8, 4->8->7
4->11,4->11>10
4->15, (...)
But I'm only getting:
WITH RECURSIVE cte (input, output, level, real_parent_id, path) AS
(
SELECT
ur.input, ur.input, 1, output, ( ur.input|| ' -> ' || ur.output)
FROM graph_examle ur
WHERE ur.output = 4
UNION ALL
SELECT
ur_cte.input, ur.input, level + 1, ur.output, (ur_cte.path || '->' || ur.output)
FROM cte ur_cte
INNER JOIN graph_examle ur on ur.input = ur_cte.real_parent_id
)
SELECT *
FROM cte
ORDER BY path
Note that in my queries I'm trying to solve circular dependencies
The ascending query sounds good ... maybe you can concatenate the path and output columns.
For the descending query, you can try this :
WITH RECURSIVE cte (input, output, level, path, loop) AS
(
SELECT
ur.input, ur.output, 1, ( ur.output|| ' -> ' || ur.input), 0
FROM graph_examle ur
WHERE ur.output = 4
UNION ALL
SELECT
ur.input, ur_cte.output, level + 1, (ur_cte.path || '->' || ur.input),
CASE WHEN ur_cte.path LIKE '%->' || ur.input THEN 1 ELSE 0 END
FROM cte ur_cte
INNER JOIN graph_examle ur on ur.output = ur_cte.input
WHERE ur_cte.loop = 0
AND ur.input IS NOT NULL
)
SELECT *
FROM cte
ORDER BY path
see dbfiddle

TSQL - in a string, replace a character with a fixed one every 2 characters

I can't replace every 2 characters of a string with a '.'
select STUFF('abcdefghi', 3, 1, '.') c3,STUFF('abcdefghi', 5, 1,
'.') c5,STUFF('abcdefghi', 7, 1, '.') c7,STUFF('abcdefghi', 9, 1, '.')
c9
if I use STUFF I should subsequently overlap the strings c3, c5, c7 and c9. but I can't find a method
can you help me?
initial string:
abcdefghi
the result I would like is
ab.de.gh.
the string can be up to 50 characters
Create a numbers / tally / digits table, if you don't have one already, then you can use this to target each character position:
with digits as ( /* This would be a real table, here it's just to test */
select n from (values(1),(2),(3),(4),(5),(6),(7),(8),(9),(10))x(n)
), t as (
select 'abcdefghi' as s
)
select String_Agg( case when d.n%3 = 0 then '.' else Substring(t.s, d.n, 1) end, '')
from t
cross apply digits d
where d.n <Len(t.s)
Using for xml with existing table
with digits as (
select n from (values(1),(2),(3),(4),(5),(6),(7),(8),(9),(10))x(n)
),
r as (
select t.id, case when d.n%3=0 then '.' else Substring(t.s, d.n, 1) end ch
from t
cross apply digits d
where d.n <Len(t.s)
)
select result=(select '' + ch
from r r2
where r2.id=r.id
for xml path('')
)
from r
group by r.id
You can try it like this:
Easiest might be a quirky update ike here:
DECLARE #string VARCHAR(100)='abcdefghijklmnopqrstuvwxyz';
SELECT #string = STUFF(#string,3*A.pos,1,'.')
FROM (SELECT TOP(LEN(#string)/3) ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM master..spt_values) A(pos);
SELECT #string;
Better/Cleaner/Prettier was a recursive CTE:
We use a declared table to have some tabular sample data
DECLARE #tbl TABLE(ID INT IDENTITY, SomeString VARCHAR(200));
INSERT INTO #tbl VALUES('')
,('a')
,('ab')
,('abc')
,('abcd')
,('abcde')
,('abcdefghijklmnopqrstuvwxyz');
--the query
WITH recCTE AS
(
SELECT ID
,SomeString
,(LEN(SomeString)+1)/3 AS CountDots
,1 AS OccuranceOfDot
,SUBSTRING(SomeString,4,LEN(SomeString)) AS RestString
,CAST(LEFT(SomeString,2) AS VARCHAR(MAX)) AS Growing
FROM #tbl
UNION ALL
SELECT t.ID
,r.SomeString
,r.CountDots
,r.OccuranceOfDot+2
,SUBSTRING(RestString,4,LEN(RestString))
,CONCAT(Growing,'.',LEFT(r.RestString,2))
FROM #tbl t
INNER JOIN recCTE r ON t.ID=r.ID
WHERE r.OccuranceOfDot/2<r.CountDots-1
)
SELECT TOP 1 WITH TIES ID,Growing
FROM recCTE
ORDER BY ROW_NUMBER() OVER(PARTITION BY ID ORDER BY OccuranceOfDot DESC);
--the result
1
2 a
3 ab
4 ab
5 ab
6 ab.de
7 ab.de.gh.jk.mn.pq.st.vw.yz
The idea in short
We use a recursive CTE to walk along the string
we add the needed portion together with a dot
We stop, when the remaining length is to short to continue
a little magic is the ORDER BY ROW_NUMBER() OVER() together with TOP 1 WITH TIES. This will allow all first rows (frist per ID) to appear.

How to display Text Unit only one time if it repeated for same Feature when do stuff?

I work with SQL Server 2012 and face an issue: I can't display Text Unit only one time where it repeated for feature using Stuff.
What I need is when Text Unit is repeated for same feature, then no need to repeat it - only display it once.
In my case, I face issue that I can't prevent repeat Text Unit when It be same Text Unit for same Feature.
Voltage | Voltage | Voltage ONLY one Voltage display .
CREATE TABLE #FinalTable
(
PartID INT,
DKFeatureName NVARCHAR(100),
TextUnit NVARCHAR(100),
StatusId INT
)
INSERT INTO #FinalTable (PartID, DKFeatureName, TextUnit, StatusId)
VALUES
(1211, 'PowerSupply', 'Voltage', 3),
(1211, 'PowerSupply', 'Voltage', 3),
(1211, 'PowerSupply', 'Voltage', 3)
SELECT
PartID, DKFeatureName,
COUNT(PartID) AS CountParts,
TextUnit = STUFF ((SELECT ' | ' + TextUnit
FROM #FinalTable b
WHERE b.PartID = a.PartID
AND a.DKFeatureName = b.DKFeatureName
AND StatusId = 3
FOR XML PATH('')), 1, 2, ' ')
INTO
#getUnitsSticky
FROM
#FinalTable a
GROUP BY
PartID, DKFeatureName
HAVING
(COUNT(PartID) > 1)
SELECT *
FROM #getUnitsSticky
Expected result is :
Voltage
Incorrect result or result I don't need is as below :
Voltage|Voltage|Voltage
TomC's answer is basically correct. However, when using this method with SQL Server, it is usually more efficient to get the rows in a subquery and then use stuff() in the outer query. That way, the values in each row are processed only once.
So:
SELECT PartID, DKFeatureName, CountParts,
STUFF( (SELECT ' | ' + TextUnit
FROM #FinalTable b
WHERE b.PartID = a.PartID AND
b.DKFeatureName = a.DKFeatureName AND
StatusId = 3
FOR XML PATH('')
), 1, 3, ' ') as TextUnit
INTO #getUnitsSticky
FROM (SELECT PartID, DKFeatureName, COUNT(*) as CountParts
FROM #FinalTable a
GROUP BY PartID, DKFeatureName
HAVING COUNT(*) > 1
) a;
This also removes the leading space from the concatenated result.
To put this into a complete answer - this should be your SQL (shortened slightly and removed the last temp table):
SELECT
PartID, DKFeatureName,
COUNT(PartID) AS CountParts,
TextUnit = STUFF ((SELECT distinct ' | ' + TextUnit
FROM #FinalTable b
WHERE b.PartID = a.PartID
AND a.DKFeatureName = b.DKFeatureName
AND StatusId = 3
FOR XML PATH('')), 1, 2, ' ')
FROM #FinalTable a
GROUP BY PartID, DKFeatureName
HAVING (COUNT(PartID) > 1)

Get the text between two special characters in oracle

I have a requirement wherein I have to take in the data between two special characters only. The first one and the second character.
Ex: KVN REG#HENDRI#AEP: 6256765058812#KERG00101258#875303069817#THT914000
Here I need the data between first # and the second one. I gathered data from different sources and wrote a query. I just want to know a simpler form of the query, wherein I need not to have to put the rownum function.
Query:
select b.name as v_custname
from ( select a.*, rownum rnum
from (SELECT regexp_substr(token, '[^:]+', 1, 1) name
FROM (
SELECT regexp_substr(s, '[^\#]+', 1, lvl) token, lvl
FROM (
SELECT s, LEVEL lvl FROM (select 'KVN REG#HENDRI#AEP: 6256765058812#KERG00101258#875303069817#THT914000' s from dual)
CONNECT BY LEVEL < LENGTH(s) - LENGTH(REPLACE(s, '#'))
)
) ) a
where rownum <= 2 ) b
where rnum >= 2;
You can do this with just instr and substr():
with sample_data as (
select 'KVN REG#HENDRI#AEP: 6256765058812#KERG00101258#875303069817#THT914000' as token
from dual
)
select substr(token,
instr(token, '#') + 1,
instr(token, '#', 1, 2) - instr(token, '#') - 1
)
from sample_data
instr(token, '#') + 1 finds the first occurrence of #
instr(token, '#', 1, 2) finds the second occurrence of #
substr() takes the first position plus a length to be extracted. The length you need is the second position minus the first position.

use cursor to display rows and group them by one column using variable

I have two tables:
Sales.SalesOrderHeader(SalesOrderID(PK), SalesOrderNumber, Status,..)
Sales.SalesOrderDetail(SalesOrderID(PK,FK), ..)
I've declared a variable #numdetail and a cursor SaleReportCursor.
How can I Print something like:
SalesOrderNumber1 (3 items) was shipped.
SalesOrderNumber2 (4 items) was shipped.
SalesOrderNumber3 (2 items) was shipped.
So that products with the same SalesOrderID can be grouped and counted?
This is what I got for the Cursor:
DECLARE
#salesOrderID INT,
#salesOrderNum NVARCHAR(25),
#dueDate DATETIME,
#status tinyint,
#message varchar(80),
#numDetail INT,
#count INT = 0,
#astatus varchar(10);
DECLARE salesReportCursor CURSOR
FOR
select s.SalesOrderID, SalesOrderNumber,DueDate, Status
FROM [Sales].[SalesOrderDetail] s
join Sales.SalesOrderHeader h
on s.SalesOrderID=h.SalesOrderID
where
h.DueDate between '2008-08-01' and '2008-08-31'
group by SalesOrderNumber,s.SalesOrderID,DueDate, Status
Order by SalesOrderNumber desc
FOR READ ONLY
OPEN salesReportCursor
FETCH NEXT from salesReportCursor
INTO #salesOrderID, #salesOrderNum , #dueDate , #status;
WHILE ##FETCH_STATUS = 0
BEGIN
set #numDetail= 1
if #salesOrderNum =#salesOrderNum
set #numDetail=#numDetail+1
Set #astatus=
case when #status=1 then 'In process'
when #status=2 then 'Approved'
when #status=3 then 'Backordered'
when #status=4 then 'Rejected'
when #status=5 then 'Shipped'
when #status=6 then 'Cancelled'
end
Select #message=cast(#salesOrderNum as varchar)
+' ('+ cast(#numDetail as varchar)+' items) due '+
cast(#dueDate as varchar) +' is '+ #astatus
set #count=#count+1
Print #message
FETCH NEXT from salesReportCursor
INTO #salesOrderID, #salesOrderNum , #status;
END
CLOSE salesReportCursor
DEALLOCATE salesReportCursor
What my outcome looks like:
SalesOrderNumber1 (2 items) was shipped.
SalesOrderNumber2 (2 items) was shipped.
SalesOrderNumber3 (2 items) was shipped.
I think this was because of after the BEGIN where I set #numdetail initially as 1 for each row then add it by 1. I wonder how to group and count products with the same SalesOrderID?
You shouldn't be needing a cursor at all for that...
Try this instead (use COUNT to get number of items per order):
select
SalesOrderNumber + ' (' + CONVERT(varchar, COUNT(*)) + ' items) due ' + CONVERT(varchar, h.DueDate, 20) + ' is ' + Statuses.Name
FROM
[Sales].[SalesOrderDetail] s
join Sales.SalesOrderHeader h
on s.SalesOrderID=h.SalesOrderID
join (
select 1 as Status, 'In process' as Name
union all select 2, 'Approved'
union all select 3, 'Backordered'
union all select 4, 'Rejected'
union all select 5, 'Shipped'
union all select 6, 'Cancelled'
) Statuses on
Statuses.Status = h.Status
where
h.DueDate between '2008-08-01' and '2008-08-31'
group by
h.SalesOrderNumber,
s.SalesOrderID,
h.DueDate,
h.Status
Order by
h.SalesOrderNumber desc
(I haven't tried the code myself, so there could be some syntax errors, but I hope you get the point)
You should also put the statuses in its own table... Preferably a table without an identity column, and use it as a lookup table.