XML Table- Parametrized Path - sql

I have a problem with getting positions of the specific node.
Problem is that positions doesn't have reference to the parent node (no attribute), but they are under it in xml hierarchy.
When I try to parametrize it in this way:
SELECT something....
FROM ktr_xml x,
XMLTABLE (
'/Invoices/Invoice[#ID="' || p_invoice_number || '"]' || '/InvoiceLine'
PASSING x.xml
COLUMNS line_number VARCHAR2 (100) PATH 'ID',
Product_quantity VARCHAR2 (100)
PATH 'InvoicedQuantity',
etc.... etc....
WHERE x.id = p_id;
It gives me error, that string is expected, it means I cant build dynamic path based on field of invoice (ID).
If I run this query:
FROM ktr_xml x,
XMLTABLE (
'/Invoices/Invoice/InvoiceLine'
PASSING x.xml
Its getting all the items, not only from the specific invoice.
Any ideas how I could solve it?

You could split the query into several correlated XMLTABLE statements:
Oracle Setup:
CREATE TABLE ktr_xml( id, xml ) AS
SELECT 1, XMLTYPE(
'<Invoices>
<Invoice ID="2">
<InvoiceLine>
<ID>Invoice1</ID>
<InvoicedQuantity>42</InvoicedQuantity>
</InvoiceLine>
</Invoice>
</Invoices>'
) FROM DUAL;
Query:
SELECT x1.*
FROM ktr_xml x
INNER JOIN
XMLTABLE (
'/Invoices/Invoice'
PASSING x.xml
COLUMNS
id NUMBER PATH './#ID',
xml XMLTYPE PATH '.'
) t
ON ( t.id = 2 /*p_invoice_number*/ )
CROSS JOIN
XMLTABLE (
'Invoice/InvoiceLine'
PASSING t.xml
COLUMNS
line_number VARCHAR2 (100) PATH 'ID',
Product_quantity VARCHAR2 (100) PATH 'InvoicedQuantity'
) x1
WHERE x.id = 1 /*p_id*/;
Output:
LINE_NUMBER | PRODUCT_QUANTITY
:---------- | :---------------
Invoice1 | 42
db<>fiddle here

Related

Use replace to update column value from another column

I have database that looks like this
CREATE TABLE code (
id SERIAL,
name VARCHAR(255) NOT NULL
);
INSERT INTO code (name) VALUES ('random_value1_random');
INSERT INTO code (name) VALUES ('random_value123_random');
CREATE TABLE value (
id SERIAL,
name VARCHAR(255) NOT NULL
);
INSERT INTO value (name) VALUES ('value1');
INSERT INTO value (name) VALUES ('value123');
UPDATE code SET name = REPLACE(name, SELECT name from value , '');
I want to update my table code to remove a portion of a code and that code is coming from another table. My goal is to update all values of code and remove the portion of the string that matches another value. My end goal is to make all code.name in the example look like: random_random removing the value from the value table.
When tried using to replace with a query I get an error:
[21000] ERROR: more than one row returned by a subquery used as an expression
What is a cleaner better way to do this?
You can use REGEXP_REPLACE to replace multiple substrings in a string. You can use STRING_AGG to get the search pattern from the single search values.
UPDATE code SET name =
REGEXP_REPLACE( name,
(SELECT '(' || STRING_AGG(name, '|') || ')' from value),
''
);
This will leave you with 'random___random', not 'random_random'. If you only want to look for substrings separated with the underline character, then use
UPDATE code SET name =
TRIM('_' FROM
REGEXP_REPLACE(name,
(SELECT '(' || STRING_AGG('_?' || name || '_?', '|') || ')' from value),
'_'
)
);
Demo: https://dbfiddle.uk/RrOel8Ns
This T-SQL (I don't have Postgres) and isn't elegant, but it works..
;with l as (
-- Match the longest value first
select c.id c_id, v.id v_id, ROW_NUMBER () over (partition by c.id order by len(v.name) desc) r
from code c
join value v on charindex (v.name, c.name) > 0)
, l1 as (
-- Select the longest value first
select c_id, v_id from l where r = 1
)
update c set name = REPLACE(c.name, v.name, '')
from l1
join code c on c.id = l1.c_id
join value v on v.id = l1.v_id

How to get the structure of json element?

does a function exist (or how to create such a function) that take a json element and return the structure.
For instance, I would like a function f that in this case :
SELECT f(json_Array (json_object ('a' VALUE 1,json_Array (b valuejson_object ('a' VALUE 1)))= FROM DUAL;
returns [a integer, b [ a integer]] or somethings equivalent
From Oracle 12.2, You can use JSON_DATAGUIDE:
WITH table_name (value) AS (
SELECT json_Array(
json_object (
'a' VALUE 1,
'b' VALUE json_Array(
json_object ('a' VALUE 1),
'abcd',
123
)
)
)
FROM DUAL
)
SELECT j.path,
j.type
FROM table_name t
CROSS JOIN LATERAL(
SELECT JSON_DATAGUIDE(t.value) AS data
FROM DUAL
) d
CROSS JOIN LATERAL(
SELECT *
FROM JSON_TABLE(
d.data,
'$[*]'
COLUMNS(
path VARCHAR2(200) PATH '$."o:path"',
type VARCHAR2(200) PATH '$.type',
len INTEGER PATH '$."o:length"'
)
)
) j;
Which outputs:
PATH
TYPE
$
array
$.a
number
$.b
array
$.b[*]
string
$.b.a
number
If you want something more detailed then you are probably going to have to write your own JSON parser in PL/SQL (or Java and compile it in the database).
db<>fiddle here

PL/SQL extract XML namespace value

I have the XML file with this ROOT tag
<DEFeatureDataset xsi:type='typens:DEFeatureDataset'
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns:xs='http://www.w3.org/2001/XMLSchema'
xmlns:typens='http://www.esri.com/schemas/ArcGIS/10.1'>
How can I extract the 3rd namespace value "http://www.esri.com/schemas/ArcGIS/10.1" in a select statement using xml query ?
From How to select namespace value via XPath:
SELECT XMLQUERY(
'/DEFeatureDataset/namespace-uri-for-prefix("typens",.)'
PASSING XMLTYPE( xml ) RETURNING CONTENT
) AS typens
FROM table_name;
or
SELECT x.*
FROM table_name t
OUTER APPLY XMLTABLE(
'/DEFeatureDataset'
PASSING XMLTYPE( t.xml )
COLUMNS
typens VARCHAR2(100) PATH './namespace-uri-for-prefix("typens",.)'
) x
Which, for the sample data:
CREATE TABLE table_name ( xml ) AS
SELECT '<DEFeatureDataset
xsi:type="typens:DEFeatureDataset"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:typens="http://www.esri.com/schemas/ArcGIS/10.1"></DEFeatureDataset>' FROM DUAL
Both output:
| TYPENS |
| :-------------------------------------- |
| http://www.esri.com/schemas/ArcGIS/10.1 |
db<>fiddle here

Select rows using in with comma-separated string parameter

I'm converting a stored procedure from MySql to SQL Server. The procedure has one input parameter nvarchar/varchar which is a comma-separated string, e.g.
'1,2,5,456,454,343,3464'
I need to write a query that will retrieve the relevant rows, in MySql I'm using FIND_IN_SET and I wonder what the equivalent is in SQL Server.
I also need to order the ids as in the string.
The original query is:
SELECT *
FROM table_name t
WHERE FIND_IN_SET(id,p_ids)
ORDER BY FIND_IN_SET(id,p_ids);
The equivalent is like for the where and then charindex() for the order by:
select *
from table_name t
where ','+p_ids+',' like '%,'+cast(id as varchar(255))+',%'
order by charindex(',' + cast(id as varchar(255)) + ',', ',' + p_ids + ',');
Well, you could use charindex() for both, but the like will work in most databases.
Note that I've added delimiters to the beginning and end of the string, so 464 will not accidentally match 3464.
You would need to write a FIND_IN_SET function as it does not exist. The closet mechanism I can think of to convert a delimited string into a joinable object would be a to create a table-valued function and use the result in a standard in statement. It would need to be similar to:
DECLARE #MyParam NVARCHAR(3000)
SET #MyParam='1,2,5,456,454,343,3464'
SELECT
*
FROM
MyTable
WHERE
MyTableID IN (SELECT ID FROM dbo.MySplitDelimitedString(#MyParam,','))
And you would need to create a MySplitDelimitedString type table-valued function that would split a string and return a TABLE (ID INT) object.
A set based solution that splits the id's into ints and join with the base table which will make use of index on the base table id. I assumed the id would be an int, otherwise just remove the cast.
declare #ids nvarchar(100) = N'1,2,5,456,454,343,3464';
with nums as ( -- Generate numbers
select top (len(#ids)) row_number() over (order by (select 0)) n
from sys.messages
)
, pos1 as ( -- Get comma positions
select c.ci
from nums n
cross apply (select charindex(',', #ids, n.n) as ci) c
group by c.ci
)
, pos2 as ( -- Distinct posistions plus start and end
select ci
from pos1
union select 0
union select len(#ids) + 1
)
, pos3 as ( -- add row number for join
select ci, row_number() over (order by ci) as r
from pos2
)
, ids as ( -- id's and row id for ordering
select cast(substring(#ids, p1.ci + 1, p2.ci - p1.ci - 1) as int) id, row_number() over (order by p1.ci) r
from pos3 p1
inner join pos3 p2 on p2.r = p1.r + 1
)
select *
from ids i
inner join table_name t on t.id = i.id
order by i.r;
You can also try this by using regex to get the input values from comma separated string :
select * from table_name where id in (
select regexp_substr(p_ids,'[^,]+', 1, level) from dual
connect by regexp_substr(p_ids, '[^,]+', 1, level) is not null );

SQL XML, is there a technique by which I can get some columns in xml format?

I want to store data from multiple tables into one table
table A (
p, q,
PRIMARY KEY (p,q)
)
table B (
p, m, n,
PRIMARY KEY (p,m,n)
)
table output(
p,
xml
)
The output should be like
for table A
-------------------------------
| p | xml |
---------------------------------
|value of p | <q>some value</q> |
-------------------------------
for table B
-----------------------------------------------
| p | xml |
------------------------------------------------
|value of p | <m>some value</m><n>some data</n> |
-----------------------------------------------
This can be achieved by this query
SELECT *
FROM Students s
CROSS APPLY
(
SELECT
(
SELECT *
FROM Students t
WHERE t.DisplayName = s.DisplayName
FOR XML RAW
) x1
)x
But I want to make the SQL generic enough so that given any table name we can get the above output.
The problem with the inner join is that then the query will not be generic
To get the result you want from one table you could use something like this:
select T.Table1ID,
(
select T.*
for xml raw, type
) as x1
from Table1 as T
To apply the same pattern to another table you need to change the table name and the field name of the key column.
select T.Table2ID,
(
select T.*
for xml raw, type
) as x1
from Table2 as T
If you want a even more generic version you can use dynamic SQL and build your query from a table name and a column name.
declare #TableName sysname
declare #KeyColumnName sysname
set #TableName = 'master..spt_values'
set #KeyColumnName = 'Number'
declare #SQL nvarchar(500)
set #SQL = '
select '+#KeyColumnName+',
(
select T.*
for xml raw, type
) as x1
from '+#TableName+' as T'
exec (#SQL)
I'm sorry if I have misunderstood, but is this more or less the principle you are after?
;WITH Base AS
(
SELECT Cat = PC.Name
,SubCat = SC.Name
FROM Production.ProductCategory PC
JOIN Production.ProductSubcategory SC ON SC.ProductCategoryID = PC.ProductCategoryID
)
SELECT Cat, (SELECT SubCat FROM Base T2 WHERE T2.Cat = T1.Cat FOR XML AUTO )DT
FROM Base T1