SQL Querying inside XML column - mysql

Am trying to Query inside an SQL table which has XML Column .
Table name: 'Purchase'
Column name: 'XML_COL'
Please find below xml data for column name 'XML_COL' under purchase table:
<ns1:Request xmlns:ns1="http://www.sample.com/hic/event/request"
xmlns:ns2="http://www.sample.com/hic/eventpayload/request">
<ns1:createeventRequest>
<ns1:eventPayLoad>
<ns2:eventPayLoad>
<Id>123456</Id>
</ns2:eventPayLoad>
</ns1:eventPayLoad>
</ns1:createeventRequest>
</ns1:Request>
I have written below query :
`select * from purchase,
XMLTABLE ('$d/Request/createeventRequest/eventPayLoad/eventPayLoad' PASSING XML_COL as "d"
COLUMNS
Id varchar(20) PATH 'Id') as a where(a.Id like '1234%');`
But this is returning me an empty column with no data.
But my requirement is it should fetch all the data for this particular Id.
Please help if any one faced this kind of issue.
Do we need to include namespaces as well while querying?? or am I missing any thing?

I think the expression PATH 'Id' is bit to simple...
I'm not familiar with MySQL's abilities to query XML... The Path Id would try to find an element "Id" from the current node (which is the root node in the first action). But there is no "Id"... You must either specify the full path, starting with a single / to start at the root node, or let the engine try a deep search, starting with two //
These paths should work:
SELECT ExtractValue(
'<ns1:Request xmlns:ns1="http://www.sample.com/hic/event/request" xmlns:ns2="http://www.sample.com/hic/eventpayload/request">
<ns1:createeventRequest>
<ns1:eventPayLoad>
<ns2:eventPayLoad>
<Id>123456</Id>
</ns2:eventPayLoad>
</ns1:eventPayLoad>
</ns1:createeventRequest>
</ns1:Request>',
'/ns1:Request[1]/ns1:createeventRequest[1]/ns1:eventPayLoad[1]/ns2:eventPayLoad[1]/Id[1]' ) AS result;
If there is only one element with a value (in your case "Id") you might use the simple deep search like this:
SELECT ExtractValue(
'<ns1:Request xmlns:ns1="http://www.sample.com/hic/event/request" xmlns:ns2="http://www.sample.com/hic/eventpayload/request">
<ns1:createeventRequest>
<ns1:eventPayLoad>
<ns2:eventPayLoad>
<Id>123456</Id>
</ns2:eventPayLoad>
</ns1:eventPayLoad>
</ns1:createeventRequest>
</ns1:Request>',
'//Id[1]' ) AS result;
But - in general - it is good advise to be as specific as possible...

Just cracked the query...When name spaces are being used in an XML, instead of the entire path, I found it's better to use '/*//' which traverses through the required element tag through XML.
Final Query:
select * from purchase,
XMLTABLE('$d' PASSING XML_COL as "d"
COLUMNS
Id varchar(20) PATH '/*//Id') as a where(a.Id like '1234%') with ur
Using 'with ur' helps to read the data that has not been committed in the database.
Please post comments if it is helpful.

Related

MySQL Stored Procedure with Parameters for Recursive CTE

I'm working on a MySQL Way of printing an "affiliate tree" and got the thing working with Common Table Expression. I'm using the following code right now:
WITH RECURSIVE recUsers AS
(
SELECT ID, username, sponsorID, 1 AS depth, username AS path
FROM users
WHERE id = 1
UNION ALL
SELECT c.ID, c.username, c.sponsorID, sc.depth + 1, CONCAT(sc.path, ' > ', c.username)
FROM recUsers AS sc
JOIN users AS c ON sc.ID = c.sponsorID
)
SELECT * FROM recUsers;
This selects the tree underneath the user with the id 1.
Now what I'd need to get is a way to pass that id as a parameter, so I don't need to define everything from the beginning every time I want to get the result.. So my idea is to put everything in a stored prodecure and pass the id in as a parameter.. However, so far I didn't get it working and always getting various errors that are very self speaking...
Basically what I've tried was
DELIMITER //
CREATE PROCEDURE getAffiliateTree(IN userid INT())
BEGIN
---my code here, the userid 1 replaced with userid
END//
DELIMITER;
However, this doesn't seem to work.. How can I get this done?
Two things I would suggest:
Use INT, not INT(). The optional length argument to integer types is deprecated in MySQL 8.0 (which I know you're using, because you're using CTE syntax). Even if you did use the length argument, using an empty argument is not legal syntax.
Make sure that the userid input parameter name is distinct from all of the columns in the tables you reference. That is, if the table has a column named userid (any capitalization), then change the name of your input parameter. Otherwise you may make ambiguous expressions like:
... WHERE userid = userid
Even though you intend one of these to be the column and the other to be the parameter, the SQL parser has no way of knowing that. It ends up treating both as the column name, so it's trivially true on all rows of the table.
Actually, a third thing I would suggest: when you ask questions, "it doesn't seem to work" isn't clear enough. Did it produce an error? If so, what was the full error message? Did it produce no error, but didn't give you the result you wanted? If so, show a mocked-up example of what you expected, and what the query produced that didn't match. It helps to be as clear as you can when you post questions, so readers don't have to guess what trouble you need help with.

How to JOIN multiple MySQL JSON tables to produce JSON output for collapsing/expanding nav-tree?

I've successfully completed a number of MySQL JSON Document Store-esque type queries, but now I am at a point where I have a very complex query that is beyond my scope of expertise. Any help is greatly appreciated.
Goal: To create a json feed from MySQL that will allow this navigation-tree to be rendered:
Steps to produce:
Install this SQL script: https://implerus.com/st_overflow/profile.sql
And then install this SQL script: https://implerus.com/st_overflow/oscal_rev5.sql
This will create 2 tables: "profiles", and "oscal_rev5" with json data being placed in the "json_data" column of each table.
Now if you want a quick look at what json data is being pulled in to those json_data columns, here's the data:
https://www.implerus.com/st_overflow/profile_data.json
https://www.implerus.com/st_overflow/OSCAL_Rev5_Latest.json
So, here's the goal:
Write a mySQL JSON query that pulls in the "control-id"'s from the profile (json_data) table.
It should return "ac-1", "ac-2", "ac-3", "ac-6.7", "ac-6.9", "ac-7", etc.
I can show you a query that I'd think should work, but doesn't:
SELECT control.controlid FROM profiles, JSON_TABLE(json_data, '$.profile.imports.include."id-selectors[*]"' COLUMNS (controlid VARCHAR(10) PATH '$."control-id"')) control
I get no results back from that and I don't know why. I suspect it has something to do with the pathing and the double-quotes with the hyphens, but from what I've read, that is how you are supposed to support queries with hyphens.
Then, somehow left-join those entries with the oscal_rev5 json_data to retrieve all the titles for the "ac-1", "ac-2", "ac-3", "ac-6.7" etc values and return data as json so that (in this case ReactJS) will be able to draw that nav-tree above as a Bootstrap Accordion. So the query should return the level 1 parts of the nav ("Access Control", "Awareness and Training", "Audit and Accountibility", etc, along with their "child" nodes that originate from step 1 above.
I do have a working query that retrieves the "parent node" list:
SELECT grouplist.id, grouplist.title FROM oscal_rev5, JSON_TABLE(json_data, '$.catalog.groups[*]' COLUMNS (id VARCHAR(140) PATH '$.id',class VARCHAR(20) PATH '$.class',title VARCHAR(80) PATH '$.title')) grouplist
And I have a failed attempt at returning the sub-parent controls list:
SELECT grouplist.id, grouplist.title FROM oscal_rev5, JSON_TABLE(json_data, '$.catalog.groups.controls[*]' COLUMNS (id VARCHAR(140) PATH '$.id',class VARCHAR(20) PATH '$.class',title VARCHAR(80) PATH '$.title')) grouplist
Returns nothing, and I don't know why. You'd think that just bumping down a node in the path would work to get id, class, title data with "$.catalog.groups.controls[*]" but it fails with no error.
Notice in the OSCAL data that "groups"->"controls"->"id": "ac-1" has a title of "Policy and Procedures". And then notice that "groups"->"controls"->"id": "ac-2" has a title of "Account Management". And notice that "groups"->"controls"->"id": "ac-6.7" has a title of "Review of User Privilages". That's all good data to return for level 2 of the nav-tree.
And since all those "ac-1", "ac-2", "ac-6.7" etc entries all start with "ac", then they all need to wrap under the "parent" "ac" node - - - and that node, according to the same oscal json data has a title called "Access Control". Likewise, all the returned values from step one that begin with "at" would need to fall under the "Awareness and Training" parent in the results.
Then, somehow count() all the subnav elements in the nav-tree that fall under "Access Control", "Awareness and Training", "Audit and Accountibility", etc and provide that data within the mySQL results.
So, the big question is - - - is there a way to combine all 3 steps into one mySQL JSON query and get 1 set of results back that distinguishes between the parent-nav-nodes and their children so that it can later be output as JSON and used to drive the nav-tree UI.

how to include hard-coded value to output from mysql query?

I've created a MySQL sproc which returns 3 separate result sets. I'm implementing the npm mysql package downstream to exec the sproc and get a result structured in json with the 3 result sets. I need the ability to filter the json result sets that are returned based on some type of indicator in each result set. For example, if I wanted to get the result set from the json response which deals specifically with Suppliers then I could use some type of js filter similar to this:
var supplierResultSet = mySqlJsonResults.filter(x => x.ResultType === 'SupplierResults');
I think SQL Server provides the ability to include a hard-coded column value in a SQL result set like this:
select
'SupplierResults',
*
from
supplier
However, this approach appears to be invalid in MySQL b/c MySQL Workbench is telling me that the sproc syntax is invalid and won't let me save the changes. Do you know if something like what I'm trying to achieve is possible in MySQL and if not then can you recommend alternative approaches that would help me achieve my ultimate goal of including some type of fixed indicator in each result set to provide a handle for downstream filtering of the json response?
If I followed you correctly, you just need to prefix * with the table name or alias:
select 'SupplierResults' hardcoded, s.* from supplier s
As far as I know, this is the SQL Standard. select * is valid only when no other expression is added in the selec clause; SQL Server is lax about this, but most other databases follow the standard.
It is also a good idea to assign a name to the column that contains the hardcoded value (I named it hardcoded in the above query).
In MySQL you can simply put the * first:
SELECT *, 'SupplierResults'
FROM supplier
Demo on dbfiddle
To be more specific, in your case, in your query you would need to do this
select
'SupplierResults',
supplier.* -- <-- this
from
supplier
Try this
create table a (f1 int);
insert into a values (1);
select 'xxx', f1, a.* from a;
Basically, if there are other fields in select, prefix '*' with table name or alias

Spring data Couchbase #n1ql.fields query

I'm trying to make a N1QL based query on Spring Data Couchbase. The documentation says
#n1ql.fields will be replaced by the list of fields (eg. for a SELECT clause) necessary to reconstruct the entity.
My repository implementation is this one:
#Query("#{#n1ql.fields} WHERE #{#n1ql.filter}")
List<User> findAllByFields(String fields);
And I'm calling this query as follows:
this.userRepository.findAllByFields("SELECT firstName FROM default");
I'm getting this error:
Caused by: org.springframework.data.couchbase.core.CouchbaseQueryExecutionException: Unable to execute query due to the following n1ql errors:
{"msg":"syntax error - at AS","code":3000}
After a little bit of researching, I also tryed:
#Query("SELECT #{#n1ql.fields} FROM #{#n1ql.bucket} WHERE #{#n1ql.filter}")
With this query, I don't get an error, I get all the documents stored but only the ID the other fields are set to null, when my query tries to get the firstName field.
this.userRepository.findAllByFields("firstName");
Anyone knows how to do such a query?
Thank you in advance.
You're misunderstanding the concept, I encourage you to give the documentation more time and see more examples. I'm not sure what exactly you're trying to achieve but I'll throw some examples.
Find all users (with all of their stored data)
#Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter}")
List<User> findAllUsers();
This will basically generate SELECT meta().id,_cas,* FROM bucket WHERE type='com.example.User'
Notice findAllUsers() does not take any parameters because there are no param placeholders defined in the #Query above.
Find all users where firstName like
#Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND firstName like $1")
List<User> findByFirstNameLike(String keyword);
This will generate something like the above query but with an extra where condition firstName like
Notice this method takes a keyword because there is a param placeholder defined $1.
Notice in the documentation it says
#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND test = $1
is equivalent to
SELECT #{#n1ql.fields} FROM #{#n1ql.bucket} WHERE
#{#n1ql.filter} AND test = $1
Now if you don't want to fetch all the data for user(s), you'll need to specify the fields being selected, read following links for more info
How to fetch a field from document using n1ql with spring-data-couchbase
https://docs.spring.io/spring-data/couchbase/docs/2.2.4.RELEASE/reference/html/#_dto_projections
I think you should try below query, that should resolve the issue to get fields based parameter you have sent as arguments.
Please refer blow query.
#Query("SELECT $1 FROM #{#n1q1.bucket} WHERE #{#n1ql.filter}")
List findByFirstName(String fieldName);
Here, bucket name resolve to the User entity and and n1ql.filter would be a default filter.

Query on custom metadata field?

This is a request from my client to tweak an existing Perl script. However, it is the actual database structure on their end that confuses me.
The requirement looks pretty simple:
only pull records where _X begins with 1, 2, or 9.
However, the underlying database is not that simple, here is the guideline from their DBA:
"_X is a custom metadata field. The database stores this data in rows, not columns, within the customData table. In order to query the custom data table in an efficient manner you need to know the Field_ID for the custom field you get that from the fielddef table:
SELECT Field_ID FROM FieldDef WHERE Name = "_X";
This returns:
10012
"Now you can query CustomData. For example:
SELECT Record_ID FROM CustomData where Field_ID="10012" AND StringValue="2012-04";
He also suggests that in my case, probably it would be:
"SELECT Record_ID FROM CustomData where Field_ID="10012" AND (StringValue LIKE '1%' || StringValue LIKE '2%' || StringValue LIKE '9%')
The weird thing is that the existing Perl script doesn't contain anything like "Select Record_ID FROM" but all like "SELECT StringValue FROM".
So that is why I am very confused here: What is "store in rows, not in columns"? Why first query the Field_ID table then CustomData? I would not be able to communicate with any of them during this weekend but really wish to get some idea on the whole thing, hope experts can help me a little on sorting out the whole structure.
More info(Table schema):
http://pastebin.com/ZiDTCCC0
The existing perl script:(focus on lines 72-136)
http://pastebin.com/JHpikTeZ
Thanks in advance.
What they seem to be using is some kind of Entity-Attribute-Value model, with the entities stored as ints and explained in another table (FieldDef).
You explained pretty well how you queried it (although you can do it in one query, with a join or a subquery), and your problem seems to be that you don't know how the Perl script does it. Unfortunately, without us seeing the Perl script, we can't either :]