Use SQL to Evaluate XML string broken into several rows - tsql

I have an application that stores a single XML record broken up into 3 separate rows, I'm assuming due to length limits. The first two rows each max out the storage at 4000 characters and unfortunately doesn't break at the same place for each record.
I'm trying to find a way to combine the three rows into a complete XML record that I can then extract data from.
I've tried concatenating the rows but can't find a data type or anything else that will let me pull the three rows into a single readable XML record.
I have several limitations I'm up against as we have select only access to the DB and I'm stuck using just SQL as I don't have enough access to implement any kind of external program to pull the data that is there an manipulate it using something else.
Any ideas would be very appreciated.

Without sample data, and desired results, we can only offer a possible approach.
Since you are on 2017, you have access to string_agg()
Here I am using ID as the proper sequence.
I should add that try_convert() will return a NULL if the conversion to XML fails.
Example
Declare #YourTable table (ID int,SomeCol varchar(4000))
Insert Into #YourTable values
(1,'<root><name>XYZ Co')
,(2,'mpany</')
,(3,'name></root>')
Select try_convert(xml,string_agg(SomeCol,'') within group (order by ID) )
From #YourTable
Returns
<root>
<name>XYZ Company</name>
</root>
EDIT 2014 Option
Select try_convert(xml,(Select '' + SomeCol
From #YourTable
Order By ID
For XML Path(''), TYPE).value('.', 'varchar(max)')
)
Or Even
Declare #S varchar(max) = ''
Select #S=#S+SomeCol
From #YourTable
Order By ID
Select try_convert(xml,#S)

Related

splitting the source data to have the specific data oracle

I have a source field from oracle db table data type VARCHAR2(512 CHAR) which is like this
%custId{str:hGl0EWJsRTwweerRkaaKsdKDsqKm0123}
%prod{str:BalanceAmount}%logistic{str:Logistic}%hiringdate{str:1999-02-28T11:10:11p}%custId{str:FpseikiD0Jt1L0Mskdww8oZBjU4La123}
but when i consider for my extract i must only consider only data with %cusId pull data and only this alphanumeric data has to be captured and populated for the extract , the problem is this is just one example from source there can be any number of combinations but i have to only consider %custId with
%custId{str:hGl0EWJsRTwweerRkaaKsdKDsqKm0123}
i need to use which function substr,lpad ?
after using the below query
SELECT
field,
REGEXP_SUBSTR(field, '%custId\{.*?\}') AS custId
FROM yourTable
where col_source='%prod{str:BalanceAmount}%logistic{str:Logistic}%hiringdate{str:1999-02-28T11:10:11p}%custId{str:FpseikiD0Jt1L0Mskdww8oZBjU4La123}'
Result
%custId{str:FpseikiD0Jt1L0Mskdww8oZBjU4La123}
but expected result
FpseikiD0Jt1L0Mskdww8oZBjU4La123
You may use REGEXP_SUBSTR here:
SELECT
field,
REGEXP_SUBSTR(field, '%custId\{(.*?)\}', 1, 1, NULL, 1) AS custId
FROM yourTable;

Using a list as replacement for singular patterns in regexp_replace

I have a table that I need to delete random words/characters out of. To do this, I have been using a regexp_replace function with the addition of multiple patterns. An example is below:
select regexp_replace(combined,'\y(NAME|001|CONTAINERS:|MT|COUNT|PCE|KG|PACKAGE)\y','', 'g')
as description, id from export_final;
However, in the full list, there are around 70 different patterns that I replace out of the description. As you can imagine, the code if very cluttered: This leads me to my question. Is there a way to put these patterns into another table then use that table to check the descriptions?
Of course. Populate your desired 'other' table with what patterns you need. Then create a CTE that uses string_agg function to build the regex. Example:
create table exclude_list( pattern_word text);
insert into exclude_list(pattern_word)
values('NAME'),('001'),('CONTAINERS:'),('MT'),('COUNT'),('PCE'),('KG'),('PACKAGE');
with exclude as
( select '\y(' || string_agg(pattern_word,'|') || ')\y' regex from exclude_list )
-- CTE simulates actual table to provide test data
, export_final (id,combined) as (values (0,'This row 001 NAME Main PACKAGE has COUNT 3 units'),(1,'But single package can hold 6 KG'))
select regexp_replace(combined,regex,'', 'g')
as description, id
from export_final cross join exclude;

How to return a comma separated string using Crystal SQL Expression

I want to display a string on each row (Details section) in my Crystal Report. The contents of this string will be retrieved with the help of a SQL Expression.
The SQL I have is follows: However if multiple rows are returned, I am not sure how to convert that into a Comma Separated String. I have an Oracle 11g database.
(select distinct NAME from TEST
where SAMPLE_NUMBER = "TEST"."SAMPLE_NUMBER"
and X_BENCH <> '"TEST"."X_BENCH"')
The TEST Table looks like this:
My report will be filtered for all samples with a specific test (e.g. Calcium). For those samples on the report, My SQL Expression should retrieve all "Other" Tests on the sample. See output example.
You can accomplish this with a wm_concat. WM_CONCAT takes a bunch of rows in a group and outputs a comma separated varchar.
Using the substr function you can separate the first result with the last.
Please note that I am dirty coding this (without a compiler to check my syntax) so things may not be 100% correct.
select sample_number
, substr(wm_concat(name),1,instr(wm_concat(name),",")-1) as NAME
, substr(wm_concat(name),instr(wm_concat(name),","),length(wm_concat(name)-instr(wm_concat(name),",")+1) as OTHER_TEST_NAMES
from TEST
where SAMPLE_NUMBER = "TEST"."SAMPLE_NUMBER"
and X_BENCH <> '"TEST"."X_BENCH"'
and rownum < 2
group by sample_number
However, if it is not necessary to separate the name and the other test names, it actually is much simpler.
select sample_number
, wm_concat(name) as NAMES
from TEST
where SAMPLE_NUMBER = "TEST"."SAMPLE_NUMBER"
and X_BENCH <> '"TEST"."X_BENCH"'
and rownum < 2
group by sample_number
Also please try to organize your lines to make it easier to read.
You can use LISTAGG for Converting Rows to Comma-Separated String in Oracle.
Example:
SELECT user_id
, LISTAGG(expertise, ',')
WITHIN GROUP (ORDER BY expertise)
AS expertise
FROM TEMP_TABLE
GROUP BY user_id;

Performance issue in retriving data from XMLAttribute using Xquery

I have one history table which has information of transaction record of master table, in this table I have used XML column to store that transaction information. Table structure with data looks as follows,
In Content XML data are stored as XML like below.
<Answers>
<AnswerSet>
<Answer questionId="ProductCode">S3404</Answer>
<Answer questionId="ProductName">Parabolic Triple</Answer>
<Answer questionId="LegacyOptionID" selectedvalue="1389">1389</Answer>
<Answer questionId="LegacyContentID" selectedvalue="624">624</Answer>
<Answer questionId="LegacyPageID" selectedvalue="355">355</Answer>
<Answer questionId="LegacyParentID" selectedvalue="760">760</Answer>
</AnswerSet>
</Answers>
In all rows structure is same but data is different in answer node, I want to get data which has ProductCode="S3404" and CreatedDate is New.
I have created query like
select n2.* from nodehistory n2 CROSS APPLY
n2.content.nodes('Answers/AnswerSet') T(c) WHERE
c.value('./Answer[#questionId="ProductCode"][1]','varchar(100)') ='J154'
ProductCode has unique data for every nodeid, but this is returning more than one row for same nodeid because this is transaction table so same XML can be store multiple time, for this require condition like order by Createddate desc, but execution of this query is taking more time due to XML processing I think.
Can we do like first get
Select Top 1 nodeid from NodeHistory order by CreatedDate desc
then search for XML part.
Any ideas on the more suitable views for better performance?
If you're not doing much else with the XML data then .exist should be more efficient than .value. I think there's a note in BOL regarding this. You could also use sql:variable to make this more generic, eg something like:
declare #produceCode varchar(20) = 'S3404'
select n2.*
from nodehistory n2
inner join ( select max(id) id from #nodehistory group by nodeId ) maxId ON n2.id = maxId.id
where n2.content.exist('Answers/AnswerSet/Answer[#questionId="ProductCode"][.=sql:variable("#produceCode")]') = 1
I've used a subquery to limit the resultset to the max(id) per nodeId. Your requirement might be slightly different but you get the idea.
In terms of performance, XML indexes can transform SQL / XML queries but at a cost. For storage you will need between 2-5 times the size of the original table so you'll have to weigh it up with your data. If you do decide to go with XML indexes, then a PROPERTY index should help this type of query, eg
-- create the primary XML index
CREATE PRIMARY XML INDEX xmlidx_nodehistory ON nodehistory(content)
GO
CREATE XML INDEX xmlprpidx_nodehistory ON nodehistory(content)
USING XML INDEX xmlidx_nodehistory FOR PROPERTY
go
declare #produceCode varchar(20) = 'S3404'
select n2.*
from nodehistory n2
inner join ( select max(id) id from nodehistory group by nodeId ) maxId ON n2.id = maxId.id
where n2.content.exist('Answers/AnswerSet/Answer[#questionId="ProductCode"][.=sql:variable("#produceCode")]') = 1
See these great articles for more SQL XML performance tuning ideas:
Performance Optimizations for the XML Data Type in SQL Server 2005
http://msdn.microsoft.com/en-us/library/ms345118.aspx
XML Indexes in SQL Server 2005
http://msdn.microsoft.com/en-us/library/ms345121(SQL.90).aspx

Dynamic number of fields in table

I have a problem with TSQL. I have a number of tables, each table contain different number of fielsds with different names.
I need dynamically take all this tables, read all records and manage each record into string list, where each value separated by commas. And do smth. with this string.
I think that I need to use CURSORS, but I can't FETCH em without knowing A concrete amount of fields with names and types. Maybe I can create a table variable with dynamic number of fields?
Thanks a lot!
Makarov Artem.
I would repurpose one of the many T-SQL scripts written to generate INSERT statements. They do exactly what you require. Namely
Reverse engineer a given table to determine columns names and types
Generate a delimited string of values
The most complete example I've found is here
But just a simple Google search for "INSERT STATEMENT GENERATOR" will yield several examples that you can repurpose to fit your needs.
Best of luck!
SELECT
ORDINAL_POSITION
,COLUMN_NAME
,DATA_TYPE
,CHARACTER_MAXIMUM_LENGTH
,IS_NULLABLE
,COLUMN_DEFAULT
FROM
INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_NAME = 'MYTABLE'
ORDER BY
ORDINAL_POSITION ASC;
from http://weblogs.sqlteam.com/joew/archive/2008/04/27/60574.aspx
Perhaps you can do something with this.
select T2.X.query('for $i in *
return concat(data($i), ",")'
).value('.', 'nvarchar(max)') as C
from (
select *
from YourTable
for xml path('Row'),elements xsinil, type
) as T1(X)
cross apply T1.X.nodes('/Row') T2(X)
It will give you one row for each row in YourTable with each value in YourTable separated by a comma in the column C.
This builds an XML for the entire table and then parses that XML. Might get you into trouble if you have tables with a lot of rows.
BTW: I saw from a comment that you can "use only pure SQL". I really don't think this qualifies as "pure SQL" :).