Analysing the edges between two vertices gremlin - graph-databases

Here is my graph
g.addV('user').property('id',1).as('1').
addV('user').property('id',2).as('2').
addV('user').property('id',3).as('3').
addE('follow').from('1').to('2').
addE('follow').from('1').to('3').iterate()
The below is my approach when a user wants to follow another user suppose 2 wants to follow 3
I'm checking first whether follow edge exist between 2 and 3
if(g.V().has(id, 2).outE(follow).inV().has(id, 3).hasNext())
{
//if exists that means he already following him so i'm dropping the follow edge and adding unfollow edge to 2,3.
}
else if(g.V().has(id, 2).outE(unfollow).inV().has(id, 3).hasNext())
{
//if exists he already unfollowed him and he wants to follow him again i'm dropping the unfollow edge and adding the follow edge to 2,3.
}
else
{
// there is no edges between 2,3 so he is following him first so i'm adding follow edge 2,3.
}
but the drawback of this approach is every time it needs to query 2 times which impacts performance . Can you suggest me a better approach ?

You can build if-then-else semantics with choose(). A direct translation of your logic there would probably look like this:
gremlin> g.addV('user').property(id,1).as('1').
......1> addV('user').property(id,2).as('2').
......2> addV('user').property(id,3).as('3').
......3> addE('follow').from('1').to('2').
......4> addE('follow').from('1').to('3').iterate()
gremlin> g.V(3).as('target').
......1> V(2).as('source').
......2> choose(outE('follow').aggregate('d1').inV().hasId(3),
......3> sideEffect(addE('unfollow').from('source').to('target').
......4> select('d1').unfold().drop()).constant('unfollowed'),
......5> choose(outE('unfollow').aggregate('d2').inV().hasId(3),
......6> sideEffect(addE('follow').from('source').to('target').
......7> select('d2').unfold().drop()).constant('followed'),
......8> addE('follow').from('source').to('target').constant('followed-first')))
==>followed-first
gremlin> g.E()
==>e[0][1-follow->2]
==>e[1][1-follow->3]
==>e[2][2-follow->3]
gremlin> g.V(3).as('target').
......1> V(2).as('source').
......2> choose(outE('follow').aggregate('d1').inV().hasId(3),
......3> sideEffect(addE('unfollow').from('source').to('target').
......4> select('d1').unfold().drop()).constant('unfollowed'),
......5> choose(outE('unfollow').aggregate('d2').inV().hasId(3),
......6> sideEffect(addE('follow').from('source').to('target').
......7> select('d2').unfold().drop()).constant('followed'),
......8> addE('follow').from('source').to('target').constant('followed-first')))
==>unfollowed
gremlin> g.E()
==>e[0][1-follow->2]
==>e[1][1-follow->3]
==>e[3][2-unfollow->3]
gremlin> g.V(3).as('target').
......1> V(2).as('source').
......2> choose(outE('follow').aggregate('d1').inV().hasId(3),
......3> sideEffect(addE('unfollow').from('source').to('target').
......4> select('d1').unfold().drop()).constant('unfollowed'),
......5> choose(outE('unfollow').aggregate('d2').inV().hasId(3),
......6> sideEffect(addE('follow').from('source').to('target').
......7> select('d2').unfold().drop()).constant('followed'),
......8> addE('follow').from('source').to('target').constant('followed-first')))
==>followed
gremlin> g.E()
==>e[0][1-follow->2]
==>e[1][1-follow->3]
==>e[4][2-follow->3]

Related

Conditionally set a property value on edge when adding a vertex [GREMLIN API]

Im trying to add a vertex that will be linked to another vertex with a conditional property value in between their edges.
So far this is what i came up with:
- this runs with no errors but im not able to get any results.
g.V().has('label', 'product')
.has('id', 'product1')
.outE('has_image')
.has('primary', true)
.inV()
.choose(fold().coalesce(unfold().values('public_url'), constant('x')).is(neq('x')))
.option(true,
addV('image')
.property('description', '')
.property('created_at', '2019-10-31 09:08:15')
.property('updated_at', '2019-10-31 09:08:15')
.property('pk', 'f920a210-fbbd-11e9-bed6-b9a9c92913ef')
.property('path', 'product_images/87wfMABXBodgXL1O4aIf6BcMMG47ueUztjNCkGxP.png')
.V()
.hasLabel('product')
.has('id', 'product1')
.addE('has_image')
.property('primary', false))
.option(false,
addV('image')
.property('description', '')
.property('created_at', '2019-10-31 09:08:15')
.property('updated_at', '2019-10-31 09:08:15')
.property('pk', 'f920a930-fbbd-11e9-b444-8bccc55453b9')
.property('path', 'product_images/87wfMABXBodgXL1O4aIf6BcMMG47ueUztjNCkGxP.png')
.V()
.hasLabel('product')
.has('id', 'product1')
.addE('has_image')
.property('primary', true))
What im doing here is im trying to set the primary property of newly added edge in between image vertex and product vertex, depending on whether a product is already connected to an image where the edge already has a primary set to true.
if a product already has an image with an edge property: primary:true then the newly added image that will be linked to the product should have an edge with property primary:false
Seed azure graphdb:
//add product vertex
g.addV('product').property(id, 'product1').property('pk', 'product1')
//add image vertex
g.addV('image').property(id, 'image1').property('public_url', 'url_1').property('pk', 'image1');
//link products to images
g.V('product1').addE('has_image').to(V('image1')).property('primary', true)
I'm surprised that your traversal runs without errors as I hit several syntax problems around your use of option() and some other issues with your mixing of T.id and the property key of "id" (the latter of which might be part of your issue in why this didn't work as-is, but I'm not completely sure). Of course, I didn't test on CosmosDB, so perhaps they took such liberties with the Gremlin language.
Anyway, assuming I have followed your explanation correctly, I think there is a way to vastly simplify your Gremlin. I think you just need this:
g.V('product1').as('p').
addV('image').
property('description', '').
property('created_at', '2019-10-31 09:08:15').
property('updated_at', '2019-10-31 09:08:15').
property('pk', 'f920a210-fbbd-11e9-bed6-b9a9c92913ef').
property('path', 'product_images/87wfMABXBodgXL1O4aIf6BcMMG47ueUztjNCkGxP.png').
addE('has_image').
from('p').
property('primary', choose(select('p').outE('has_image').values('primary').is(true),
constant(false), constant(true)))
Now, I'd say that this is the most idiomatic approach for Gremlin and as I've not tested on CosmosDB I can't say if this approach will work for you but perhaps looking at my console session below you can see that it does satisfy your expectations:
gremlin> g.V('product1').as('p').
......1> addV('image').
......2> property('description', '').
......3> property('created_at', '2019-10-31 09:08:15').
......4> property('updated_at', '2019-10-31 09:08:15').
......5> property('pk', 'f920a210-fbbd-11e9-bed6-b9a9c92913ef').
......6> property('path', 'product_images/87wfMABXBodgXL1O4aIf6BcMMG47ueUztjNCkGxP.png').
......7> addE('has_image').
......8> from('p').
......9> property('primary', choose(select('p').outE('has_image').values('primary').is(true), constant(false), constant(true)))
==>e[31][product1-has_image->25]
gremlin> g.E().elementMap()
==>[id:31,label:has_image,IN:[id:25,label:image],OUT:[id:product1,label:product],primary:true]
gremlin> g.V('product1').as('p').
......1> addV('image').
......2> property('description', '').
......3> property('created_at', '2019-10-31 09:08:15').
......4> property('updated_at', '2019-10-31 09:08:15').
......5> property('pk', 'f920a210-fbbd-11e9-bed6-b9a9c92913ef').
......6> property('path', 'product_images/87wfMABXBodgXL1O4aIf6BcMMG47ueUztjNCkGxP.png').
......7> addE('has_image').
......8> from('p').
......9> property('primary', choose(select('p').outE('has_image').values('primary').is(true), constant(false), constant(true)))
==>e[38][product1-has_image->32]
gremlin> g.E().elementMap()
==>[id:38,label:has_image,IN:[id:32,label:image],OUT:[id:product1,label:product],primary:false]
==>[id:31,label:has_image,IN:[id:25,label:image],OUT:[id:product1,label:product],primary:true]
gremlin> g.V('product1').as('p').
......1> addV('image').
......2> property('description', '').
......3> property('created_at', '2019-10-31 09:08:15').
......4> property('updated_at', '2019-10-31 09:08:15').
......5> property('pk', 'f920a210-fbbd-11e9-bed6-b9a9c92913ef').
......6> property('path', 'product_images/87wfMABXBodgXL1O4aIf6BcMMG47ueUztjNCkGxP.png').
......7> addE('has_image').
......8> from('p').
......9> property('primary', choose(select('p').outE('has_image').values('primary').is(true), constant(false), constant(true)))
==>e[45][product1-has_image->39]
gremlin> g.E().elementMap()
==>[id:38,label:has_image,IN:[id:32,label:image],OUT:[id:product1,label:product],primary:false]
==>[id:45,label:has_image,IN:[id:39,label:image],OUT:[id:product1,label:product],primary:false]
==>[id:31,label:has_image,IN:[id:25,label:image],OUT:[id:product1,label:product],primary:true]
If that looks right and this doesn't work properly in CosmosDB, it is because of line 9 which utilizes a Traversal as an argument to property() which isn't yet supported in CosmosDB. The remedy is to simply invert that line a bit:
g.V('product1').as('p').
addV('image').
property('description', '').
property('created_at', '2019-10-31 09:08:15').
property('updated_at', '2019-10-31 09:08:15').
property('pk', 'f920a210-fbbd-11e9-bed6-b9a9c92913ef').
property('path', 'product_images/87wfMABXBodgXL1O4aIf6BcMMG47ueUztjNCkGxP.png').
addE('has_image').
from('p').
choose(select('p').outE('has_image').values('primary').is(true),
property('primary', false),
property('primary', true))
I find this approach only slightly less readable as the property() doesn't align with the addE() but, it's not a terrible alternative.

Gremlin: division after groupCount

I am using Gremlin to query Neptune.
I have 2 counts
g.V().hasLabel(*).outE.inV().groupCount().by('name')
result is like : 'a':2, 'b':4
g.V().hasLabel(*).count()
4
How can I write a single query to get the numbers that result 1 divided by result 2? i.e. 'a': 0.5, 'b': 1
I can think of a few ways, but I guess using match() is the easiest:
g.V().hasLabel(*).
union(count(),
out().groupCount().by('name')).fold().
match(__.as('values').limit(local, 1).as('c'),
__.as('values').tail(local, 1).unfold().as('kv'),
__.as('kv').select(values).math('_/c').as('v')).
group().
by(select('kv').by(keys)).
by(select('v'))
A similar query on the modern graph:
gremlin> g = TinkerFactory.createModern().traversal()
==>graphtraversalsource[tinkergraph[vertices:6 edges:6], standard]
gremlin> g.V().union(count(),
......1> out().groupCount().by(label)).fold().
......2> match(__.as('values').limit(local, 1).as('c'),
......3> __.as('values').tail(local, 1).unfold().as('kv'),
......4> __.as('kv').select(values).math('_/c').as('v')).
......5> group().
......6> by(select('kv').by(keys)).
......7> by(select('v'))
==>[software:0.6666666666666666,person:0.3333333333333333]
The next one is probably harder to understand, but would be my personal favorite (because a) I don't like match() and b) it doesn't rely on the order of the results returned by union()):
gremlin> g.V().
......1> groupCount('a').
......2> by(constant('c')).
......3> out().
......4> groupCount('b').
......5> by(label).
......6> cap('a','b').as('x').
......7> select('a').select('c').as('c').
......8> select('x').select('b').unfold().
......9> group().
.....10> by(keys).
.....11> by(select(values).math('_/c'))
==>[software:0.6666666666666666,person:0.3333333333333333]

GroupBy query with list of vertices

Suppose I want to query the Neptune graph with "group-by" on one property (or more), and I want to get back the list of vertices too.
Let's say, I want to group-by on ("city", "age") and want to get the list of vertices too:
[
{"city": "SFO", "age": 29, "persons": [v[1], ...]},
{"city": "SFO", "age": 30, "persons": [v[10], v[13], ...]},
...
]
Or, get back the vertex with its properties (as valueMap):
[
{"city": "SFO", "age": 29, "persons": [[id:1,label:person,name:[marko],age:[29],city:[SFO]], ...]},
...
]
AFAIK, Neptune doesn't support lambda nor variable assignments. is there a way to do this with one traversal and no lambdas?
Update: I'm able to get the vertices, but without their properties (with valueMap).
Query:
g.V().hasLabel("person").group().
by(values("city", "age").fold()).
by(fold().
match(__.as("p").unfold().values("city").as("city"),
__.as("p").unfold().values("age").as("age"),
__.as("p").fold().unfold().as("persons")).
select("city", "age", "persons")).
select(values).
next()
Output:
==>[city:SFO,age:29,persons:[v[1]]]
==>[city:SFO,age:27,persons:[v[2],v[23]]]
...
If I understand it correctly, then ...
g.V().hasLabel("person").
group().
by(values("city", "age").fold())
... or ...
g.V().hasLabel("person").
group().
by(valueMap("city", "age").by(unfold()))
... already gives you what you need, it's just about reformating the result. To merge the maps in keys and values together, you can do something like this:
g.V().hasLabel("person").
group().
by(valueMap("city", "age").by(unfold())).
unfold().
map(union(select(keys),
project("persons").
by(values)).
unfold().
group().
by(keys).
by(select(values)))
Executing this on the modern toy graph (city replaced with name) will yield the following result:
gremlin> g = TinkerFactory.createModern().traversal()
==>graphtraversalsource[tinkergraph[vertices:6 edges:6], standard]
gremlin> g.V().hasLabel("person").
......1> group().
......2> by(valueMap("name", "age").by(unfold())).
......3> unfold().
......4> map(union(select(keys),
......5> project("persons").
......6> by(values)).
......7> unfold().
......8> group().
......9> by(keys).
.....10> by(select(values)))
==>[persons:[v[2]],name:vadas,age:27]
==>[persons:[v[4]],name:josh,age:32]
==>[persons:[v[1]],name:marko,age:29]
==>[persons:[v[6]],name:peter,age:35]

How to get a list of Coinbase CryptoCurrency Coins

I've been trying to figure out a way to get a list of all the Coins that Coinbase has listed (not necessarily for trade) but can't figure it out, in the early days it was easy as you could just login and see the list of 4 basic coins that were supported (and could hard code those values in a program and/or script).
But now they have a list of many coins listed, some as I understand, which are not available to actually trade but are listed for educational purposes (as stated on their site when looking at such coins).
I was wondering if anyone has figured out a way to get a list those coins (all supported and simply listed) perhaps with a tag of which are actually supported for trade.
I looked at the API and the REST API (using a simple GET request over HTTPS or using cURL for testing) has the following endpoints:
curl https://api.coinbase.com/v2/currencies - This lists all the Fiat currencies.
and:
curl https://api.pro.coinbase.com/products - This lists all the supported trading pairs (which is not what I'm looking for....)
Any ideas, short of logging in and parsing the html? (which could break since the site can be reformatted etc at any time).
Any help would be greatly appreciated!
perhaps not really what you asked, but you could also use https://api.pro.coinbase.com/currencies
import requests
import json
uri = 'https://api.pro.coinbase.com/currencies'
response = requests.get(uri).json()
for i in range(len(response)):
if response[i]['details']['type'] == 'crypto':
print(response[i]['id])
This will return the coins available for trading.
I'm not sure if I this is the response that you want or not. I first used the first URL that you have listed... The response from that looked like it didn't have the available coins. I then tried the below URL instead and the response does have a lot of curriencies listed on it. You can parse it by loading with JSON and looking for the fields that you want.
Also I didn't see a language posted with your question. I'm using python3 below. If you're a Linux person you can also just use curl GET from the command line. It doesn't matter the language... you just need to make a GET request to that URL and parse the response however you see fit.
To get 1 particular field you can use a line like response['data']['rates']['BTC'] to extract '0.00029200' out of the response/JSON string.
>>> r = requests.get("https://api.coinbase.com/v2/exchange-rates")
>>> response = json.loads(r.text)
>>> pprint.pprint(response)
{'data': {'currency': 'USD',
'rates': {'AED': '3.67',
'AFN': '75.22',
'ALL': '108.84',
'AMD': '487.59',
'ANG': '1.79',
'AOA': '311.37',
'ARS': '37.32',
'AUD': '1.38',
'AWG': '1.80',
'AZN': '1.70',
'BAM': '1.71',
'BAT': '9.00418244',
'BBD': '2.00',
'BCH': '0.00879160',
'BDT': '83.80',
'BGN': '1.71',
'BHD': '0.377',
'BIF': '1824',
'BMD': '1.00',
'BND': '1.58',
'BOB': '6.90',
'BRL': '3.65',
'BSD': '1.00',
'BTC': '0.00029200',
'BTN': '71.11',
'BWP': '10.41',
'BYN': '2.15',
'BYR': '21495',
'BZD': '2.02',
'CAD': '1.31',
'CDF': '1631.00',
'CHF': '0.99',
'CLF': '0.0242',
'CLP': '656',
'CNH': '6.71',
'CNY': '6.70',
'COP': '3174.95',
'CRC': '608.98',
'CUC': '1.00',
'CVE': '96.90',
'CZK': '22.50',
'DJF': '178',
'DKK': '6.52',
'DOP': '50.44',
'DZD': '118.30',
'EEK': '14.61',
'EGP': '17.68',
'ERN': '15.00',
'ETB': '28.52',
'ETC': '0.25542784',
'ETH': '0.00944599',
'EUR': '0.87',
'FJD': '2.10',
'FKP': '0.76',
'GBP': '0.76',
'GEL': '2.66',
'GGP': '0.76',
'GHS': '4.98',
'GIP': '0.76',
'GMD': '49.52',
'GNF': '9210',
'GTQ': '7.74',
'GYD': '208.55',
'HKD': '7.85',
'HNL': '24.49',
'HRK': '6.49',
'HTG': '78.37',
'HUF': '276',
'IDR': '13940.00',
'ILS': '3.63',
'IMP': '0.76',
'INR': '70.93',
'IQD': '1190.000',
'ISK': '120',
'JEP': '0.76',
'JMD': '132.72',
'JOD': '0.710',
'JPY': '109',
'KES': '100.60',
'KGS': '68.70',
'KHR': '4015.00',
'KMF': '429',
'KRW': '1114',
'KWD': '0.303',
'KYD': '0.83',
'KZT': '380.63',
'LAK': '8559.50',
'LBP': '1511.15',
'LKR': '178.40',
'LRD': '160.75',
'LSL': '13.53',
'LTC': '0.03208728',
'LTL': '3.22',
'LVL': '0.66',
'LYD': '1.385',
'MAD': '9.53',
'MDL': '17.05',
'MGA': '3465.0',
'MKD': '53.78',
'MMK': '1519.04',
'MNT': '2453.75',
'MOP': '8.08',
'MRO': '357.0',
'MTL': '0.68',
'MUR': '34.23',
'MVR': '15.49',
'MWK': '728.47',
'MXN': '19.14',
'MYR': '4.10',
'MZN': '61.87',
'NAD': '13.53',
'NGN': '361.50',
'NIO': '32.60',
'NOK': '8.43',
'NPR': '113.78',
'NZD': '1.45',
'OMR': '0.385',
'PAB': '1.00',
'PEN': '3.33',
'PGK': '3.36',
'PHP': '52.13',
'PKR': '139.30',
'PLN': '3.73',
'PYG': '6084',
'QAR': '3.64',
'RON': '4.14',
'RSD': '103.53',
'RUB': '65.47',
'RWF': '886',
'SAR': '3.75',
'SBD': '8.06',
'SCR': '13.67',
'SEK': '9.05',
'SGD': '1.35',
'SHP': '0.76',
'SLL': '8390.00',
'SOS': '582.00',
'SRD': '7.46',
'SSP': '130.26',
'STD': '21050.60',
'SVC': '8.75',
'SZL': '13.52',
'THB': '31.23',
'TJS': '9.43',
'TMT': '3.50',
'TND': '2.968',
'TOP': '2.26',
'TRY': '5.18',
'TTD': '6.77',
'TWD': '30.72',
'TZS': '2317.00',
'UAH': '27.70',
'UGX': '3670',
'USD': '1.00',
'USDC': '1.000000',
'UYU': '32.58',
'UZS': '8380.00',
'VEF': '248487.64',
'VND': '23287',
'VUV': '111',
'WST': '2.60',
'XAF': '573',
'XAG': '0',
'XAU': '0',
'XCD': '2.70',
'XDR': '1',
'XOF': '573',
'XPD': '0',
'XPF': '104',
'XPT': '0',
'YER': '250.30',
'ZAR': '13.27',
'ZEC': '0.02056344',
'ZMK': '5253.08',
'ZMW': '11.94',
'ZRX': '4.04721481',
'ZWL': '322.36'}}}
The following code:
import requests
uri = 'https://api.pro.coinbase.com/currencies'
response = requests.get(uri).json()
for i in range(len(response)):
if response[i]['details']['type'] == 'crypto':
print(response[i]['id'])
Will provide this output:
COTI
BTC
ETH
LTC
BCH
ZEC
XTZ
XRP
XLM
EOS
ALGO
DASH
ATOM
CGLD
FIL
ADA
ICP
SOL
DOT
DOGE
OXT
KNC
MIR
REP
COMP
NMR
ACH
BAND
ZRX
BAT
LOOM
UNI
YFI
LRC
CVC
DNT
MANA
GNT
REN
LINK
BAL
ETC
USDC
RLC
DAI
WBTC
NU
AAVE
SNX
BNT
GRT
SUSHI
MLN
ANKR
CRV
STORJ
SKL
AMP
1INCH
ENJ
NKN
OGN
FORTH
GTC
TRB
CTSI
MKR
UMA
USDT
CHZ
SHIB
BOND
LPT
QNT
KEEP
CLV
MASK
MATIC
OMG
POLY
FARM
FET
PAX
RLY
PLA
RAI
IOTX
ORN
AXS
QUICK
TRIBE
UST
REQ
TRU
WLUNA
you can use
curl -X GET https://api.exchange.coinbase.com/products
refer to
https://docs.cloud.coinbase.com/exchange/reference/exchangerestapi_getproducts

Parsing complicated XML that has recursive nodes

I have a really complicated XML issue that I've spent days experimenting with code and searching the internet for a solution to with no joy.
There is a service that I'm calling which returns vehicle data in a really complicated and confusing XML format. In several places, there are an unknown number of same-named-elements within the same named elements. I need some attribute information from the lowest level, and also some information from outside the nested elements as well.
I need help with the following: (1) There can be zero, one, or over a dozen 'category' nodes on the way down to the price information. (2) There can be multiple 'price' elements... the first almost always has zero values, then there are additional 'ambiguousoption' elements, each with their own 'price' elements below it. (3) I need the list of 'category #id' values that go into the path to get down to the price level (if there are any). (4) I need the stack of 'category #id' values to be sensitive to the 'ambiguousoption'... any 'category #id' values in the first 'ambiguousoption' list, then any that are in the second list.
What is happening in my code below is that it is grabbing the first (and only the first) 'price' information within each 'factoryoption' node, without regard to the fact that there can be multiple 'price' nodes in the 'factoryoption' node, one outside of the 'ambiguousoption' element, then an additional 'price' node inside each 'ambiguousoption' element.
What a nightmare. It's got me totally confused. Help!
Thanks,
Patrick
declare #xml xml = '<vehicledescription bestmodelname="Sierra 1500" beststylename="4WD Crew Cab 143.5" SLE" besttrimname="SLE" bestmakename="GMC" country="US" language="en" modelyear="2014"><factoryoption optionkindid="5" utf="M" fleetonly="false" standard="false" chromecode="FE9" oemcode="FE9"><header id="1156">EMISSIONS</header><description>EMISSIONS, FEDERAL REQUIREMENTS</description><price unknown="false" invoicemin="0" invoicemax="0" msrpmin="0" msrpmax="0"><styleid>358200</styleid><installed cause="OptionCodeBuild" /></price></factoryoption><factoryoption optionkindid="6" utf="E" fleetonly="false" standard="false" chromecode="L83" oemcode="L83"><header id="1160">ENGINE</header><description>ENGINE, 5.3L ECOTEC3 V8 WITH ACTIVE FUEL MANAGEMENT</description><category id="1052"><category id="1213"><price unknown="false" invoicemin="963.6" invoicemax="963.6" msrpmin="1095" msrpmax="1095"><styleid>358200</styleid><installed cause="Engine" /></price></category></category></factoryoption><factoryoption optionkindid="7" utf="T" fleetonly="false" standard="true" chromecode="MYC" oemcode="MYC"><header id="1379">TRANSMISSION</header><description>TRANSMISSION, 6-SPEED AUTOMATIC, ELECTRONICALLY CONTROLLED</description><category id="1104"><category id="1130"><category id="1131" removed="true"><price unknown="false" invoicemin="0" invoicemax="0" msrpmin="0" msrpmax="0"><styleid>358200</styleid><installed cause="OptionCodeBuild" /></price></category></category></category></factoryoption><factoryoption optionkindid="28" utf="F" standard="false" oemcode="H1Y"><header id="1347">SEAT TRIM</header><description>JET BLACK, LEATHER-APPOINTED FRONT SEAT TRIM</description><category id="1077" removed="true"><category id="1078"><category id="1079" removed="true"><category id="1309" removed="true"><price unknown="false" invoicemin="0" invoicemax="0" msrpmin="0" msrpmax="0"><styleid>358200</styleid><installed cause="OptionCodeBuild"><ambiguousoption optionkindid="28" utf="F" fleetonly="false" standard="false" chromecode="H1Y-R" oemcode="H1Y"><header id="1347">SEAT TRIM</header><description>JET BLACK, LEATHER-APPOINTED FRONT SEAT TRIM</description><category id="1074"><category id="1077" removed="true"><category id="1078"><category id="1079" removed="true"><category id="1309" removed="true"><price unknown="false" invoicemin="0" invoicemax="0" msrpmin="0" msrpmax="0"><styleid>358200</styleid></price></category></category></category></category></category></ambiguousoption><ambiguousoption optionkindid="28" utf="F" fleetonly="true" standard="false" chromecode="H1Y-F" oemcode="H1Y"><header id="1347">SEAT TRIM</header><description>JET BLACK, LEATHER-APPOINTED FRONT SEAT TRIM</description><category id="1077" removed="true"><category id="1078"><category id="1079" removed="true"><category id="1156"><category id="1309" removed="true"><price unknown="false" invoicemin="0" invoicemax="0" msrpmin="0" msrpmax="0"><styleid>358200</styleid></price></category></category></category></category></category></ambiguousoption></installed></price></category></category></category></category></factoryoption><factoryoption optionkindid="33" utf="A" fleetonly="false" standard="true" chromecode="IO5" oemcode="IO5"><header id="1301">RADIO</header><description>AUDIO SYSTEM, 8" DIAGONAL COLOR TOUCH SCREEN WITH INTELLILINK, AM/FM/SIRIUSXM/HD</description><category id="1014"><category id="1017" removed="true"><category id="1149"><category id="1150"><category id="1211"><category id="1230"><category id="1299"><price unknown="false" invoicemin="0" invoicemax="0" msrpmin="0" msrpmax="0"><styleid>358200</styleid><installed cause="OptionCodeBuild" /></price></category></category></category></category></category></category></category></factoryoption><factoryoption utf="0" standard="false" oemcode="PDU"><header id="10750">ADDITIONAL EQUIPMENT</header><description>SLE VALUE PACKAGE</description><category id="1009"><category id="1010"><category id="1011"><category id="1034"><category id="1074"><category id="1151"><category id="1204"><category id="1221"><price unknown="false" invoicemin="1425.6" invoicemax="1425.6" msrpmin="1620" msrpmax="1620"><styleid>358200</styleid><installed cause="OptionCodeBuild"><ambiguousoption utf="0" fleetonly="false" standard="false" chromecode="PDU-R" oemcode="PDU"><header id="10750">ADDITIONAL EQUIPMENT</header><description>SLE VALUE PACKAGE</description><category id="1009"><category id="1010"><category id="1011"><category id="1034"><category id="1074"><category id="1151"><category id="1204"><category id="1221"><price unknown="false" invoicemin="1425.6" invoicemax="1425.6" msrpmin="1620" msrpmax="1620"><styleid>358200</styleid></price></category></category></category></category></category></category></category></category></ambiguousoption><ambiguousoption utf="0" fleetonly="true" standard="false" chromecode="PDU-F" oemcode="PDU"><header id="10750">ADDITIONAL EQUIPMENT</header><description>SLE VALUE PACKAGE</description><category id="1009"><category id="1010"><category id="1011"><category id="1034"><category id="1074"><category id="1151"><category id="1204"><category id="1221"><price unknown="false" invoicemin="1425.6" invoicemax="1425.6" msrpmin="1620" msrpmax="1620"><styleid>358200</styleid></price></category></category></category></category></category></category></category></category></ambiguousoption></installed></price></category></category></category></category></category></category></category></category></factoryoption></vehicledescription>'
select #xml
SELECT
-- Nodes.node.value('.', 'varchar(max)') AS category,
Nodes.node.value('(header/#id)[1]', 'varchar(100)') AS headerid,
Nodes.node.value('(header/text())[1]', 'varchar(100)') AS headertext,
Nodes.node.value('description[1]', 'varchar(100)') AS description,
Nodes.node.value('(.//price/#invoicemin)[1]', 'money') AS invoicemin,
Nodes.node.value('(.//price/#invoicemax)[1]', 'money') AS invoicemax,
Nodes.node.value('(.//price/#msrpmin)[1]', 'money') AS msrpmin,
Nodes.node.value('(.//price/#msrpmax)[1]', 'money') AS msrpmax,
Nodes.node.value('(.//price/styleid/text())[1]', 'varchar(100)') AS styleid,
Nodes.node.value('(.//price/installed/#cause)[1]', 'varchar(50)') AS installcause
FROM
#xml.nodes('//factoryoption') AS Nodes(node);
Take it step-by-step
FROM
#xml.nodes('//factoryoption') AS Nodes(node);
This will only yield one row for every <factoryoption> element, regardless of depth. You need to fan that out.
FROM
#xml.nodes('//factoryoption') AS Nodes1(factoryoption)
CROSS APPLY
#xml.nodes('category') AS Nodes2(category)
This will yield one row for every <category> element that is a child of a <factoryoption>
FROM
#xml.nodes('//factoryoption') AS Nodes1(factoryoption)
CROSS APPLY
factoryoption.nodes('category') AS Nodes2(category)
CROSS APPLY
category.nodes('price') AS Nodes3(price)
This will yield one row for every <price> element that is a child of a <category> element that is a child of a <factoryoption>
You need to first fan out your xml. When you don't do that and instead use something like (.//price/#invoicemin)[1], you are asking "indiscriminately search the whole subtree for a <price> element with a #invoicemin attribute and return the [1] (i.e. first) one you find.

Resources