Retrieving all items that are instances or sub-instances or sub-sub-instances in a chain - query-optimization

I need a logic OR of all valid statements about an item or its parents (instance-parent or subclass-parent).
Example: Q957653 is a instance of Q3184121, and the last have ?item P17 Q155. So safisfies a chain Q957653 P31 Q3184121 P17 Q155... So I need something as
?item P17 Q155
| ?item P31 ?x P17 Q155
| ?item P31 ?x P31 ?y P17 Q155
| ?item P279 ?x P17 Q155
| ?item P279 ?x P31 ?y P17 Q155
| ?item P279 ?x P279 ?y P17 Q155
A big logic "or" for all possible chains of P31 or P279 dependences.
Real example
I need a list of items that have some property (eg. ?item wdt:P402 _:b0.) and are instances or subclasses of items with other property, eg. wdt:P17 wd:Q155.
The "first level" of ?item wdt:P17 wd:Q155 is working fine,
SELECT DISTINCT ?item ?osm_relid ?itemLabel
WHERE {
?item wdt:P402 _:b0.
?item wdt:P17 wd:Q155.
OPTIONAL { ?item wdt:P1448 ?name. }
OPTIONAL { ?item wdt:P402 ?osm_relid .}
SERVICE wikibase:label {
bd:serviceParam wikibase:language "en,[AUTO_LANGUAGE]".
}
}
But how to express the union (or logic "or") of all other possible dependences?
Edit/Notes
Supposing that ?item wdt:P31*/wdt:P279* wd:Qxx . will be all "chain dependences of something Qxx", as I need... But Qxx is also a query, ?item wdt:P31*/wdt:P279* (?xx wdt:P17 wd:Q155) ..
... A solution (!) seems
SELECT (COUNT(DISTINCT ?item) AS ?count)
WHERE {
?item wdt:P402 _:b0.
?item (wdt:P31*|wdt:P279*)/wdt:P17 wd:Q155 .
}
but I can't check because is time-consuming.
... Something perhaps near the "feasible solution" is ?item wdt:P31*/wdt:P279*/wdt:P31*/wdt:P17 wd:Q155 .
... after testing feasibles, seems wdt:P31*/wdt:P279*/wdt:P17 the only "optimal" with no time-out problem.

In order to improve performance, you can use Blazegraph query hints. Query hints allow to modify auto-generated query execution plan.
SELECT DISTINCT ?item ?itemLabel ?osm_relid ?name {
?itemi wdt:P17 wd:Q155 .
hint:Prior hint:runFirst true .
?item (wdt:P31|wdt:P279)* ?itemi .
?item wdt:P625 [].
OPTIONAL { ?item wdt:P1448 ?name. }
OPTIONAL { ?item wdt:P402 ?osm_relid .}
SERVICE wikibase:label {
bd:serviceParam wikibase:language "en,[AUTO_LANGUAGE]".
}
}
Try it!
This is how query execution plan looks like (just add &explain to the query URL and scroll down).
Please note that you can't use hint:Prior hint:runLast true from the original comment when the label service is used: there can be only one such hint in any graph pattern group.

Related

Count and remove duplicates from an array

In Powershell I have the following array
foreach ($Record in $Records)
{ write-host $Record
}
#{car=OPEL; count=3}
#{car=BMW; count=2}
#{car=OPEL; count=8}
#{car=AUDI; count=3}
#{car=FORD; count=5}
#{car=FORD; count=4}
#{car=OPEL; count=4}
#{car=AUDI; count=5}
#{car=BMW; count=3}
I want to remove the duplicate elements by property "car", but to count and sum the property "count", so the above array becomes
#{car=OPEL; count=15}
#{car=BMW; count=5}
#{car=AUDI; count=8}
#{car=FORD; count=9}
Any suggestions how to accomplish this?
Use the Group-Object cmdlet to group the objects by their car property value, then use Select-Object to create new objects with the total count:
$records |Group-Object -Property car |Select-Object Name,#{Name='Count';Expression={($_.Group |Measure-Object Count -Sum).Sum}}
Combine Group-Object and Measure-Object as follows (of course, there could be a more elegant way):
$Records | Group-Object -Property car |
ForEach-Object {
[pscustomobject]#{
car = $_.Name;
count = ($_.Group | Measure-Object -Property count -Sum).Sum
}
}
foreach ($Record in $RecordsSum) {
Write-Host $Record
}
Result:
#{car=OPEL; count=15}
#{car=BMW; count=5}
#{car=AUDI; count=8}
#{car=FORD; count=9}

Editing the content of an object within a json array causes the objects in the array to be replaced with incorrectly formatted entries

For context, I am attempting to create a cmdlet that would allow for single value substitutions on arbitrary Json files, for use in some pipelines. I've managed to get this working for non-array-containing Json.
A representative bit of Json:
{"test": {
"env": "dev",
"concept": "abstraction",
"array": [
{"id":1, "name":"first"},
{"id":2, "name":"second"}
]
}
}
I want to be able to replace values by providing a function with a path like test.array[1].name and a value.
After using ConvertFrom-Json on the Json above, I attempt to use the following function (based on this answer) to replace second with third:
function SetValue($object, $key, $value) {
$p1, $p2 = $key.Split(".")
$a = $p1 | Select-String -Pattern '\[(\d{1,3})\]'
if ($a.Matches.Success) {
$index = $a.Matches.Groups[1].Value
$p1 = ($p1 | Select-String -Pattern '(\w*)\[').Matches.Groups[1].Value
if ($p2.length -gt 0) { SetValue -object $object.$p1[$index] -key $p2 -value $value }
else { $object.$p1[$index] = $value }
}
else {
if ($p2.length -gt 0) { SetValue -object $object.$p1 -key $p2 -value $value }
else {
Write-Host $object.$p1
$object.$p1 = $value
}
}
}
$content = SetValue -object $content -key "test.array[1].name" -rep "third"
Unfortunately this results in the following:
{ "test": {
"env": "dev",
"concept": "abstraction",
"array": [
"#{id=1; name=first}",
"#{id=2; name=third}"
]
}
}
If the values in the array aren't objects the code works fine as presented, it's only when we get to objects within arrays that this output happens.
What would be a way to ensure that the returned Json contains an array that is more in line with the input?
Edit: please note that the actual cause of the issue lay in not setting the -Depth property of ConvertTo-Json to 3 or greater. Doing so restored the resulting Json to the expected format. The accepted answer was still helpful in investigating the cause.
While Invoke-Expression (iex) should generally be avoided, there are exceptional cases where it offers the simplest solution.
$fromJson = #'
{
"test": {
"env": "dev",
"concept": "abstraction",
"array": [
{"id":1, "name":"first"},
{"id":2, "name":"second"}
]
}
}
'# | ConvertFrom-Json
$nestedPropertyAccessor = 'test.array[1].name'
$newValue = 'third'
Invoke-Expression "`$fromJson.$nestedPropertyAccessor = `"$newValue`""
Important:
Be sure that you either fully control or implicitly trust the content of the $nestedPropertyAccessor and $newValue variables, to prevent inadvertent or malicious execution of injected commands.
On re-conversion to JSON, be sure to pass a high-enough -Depth argument to ConvertTo-Json; with the sample JSON, at least -Depth 3 is required - see this post.

Powershell: How to dynamically update a nested JSON array?

What I'm trying to do is take any well-formed JSON file/object and search for a path inside it. If the path isn't found, move on. If it is found, update the value. Once updated, save the updated JSON to the original file.
The catch to this, is the well-formed JSON structure is not known ahead of time. It's possible I might be searching hundreds of .json files on a disk, so the files that don't match any of my search terms can just be ignored.
I'm struggling to wrap my head around how to solve this problem. Most of the examples out there don't have a JSON object with an array for one of the key values, or they don't access the properties dynamically when an array is involved.
This link: Powershell: How to Update/Replace data and values in Json and XML Object shows a (sort of)"real" JSON structure, but the accepted answer relies on knowing what the JSON structure is (the OP didn't ask for help with dynamic pathing).
This link: Set Value of Nested Object Property by Name in PowerShell has something very close, although when an array is in the mix, it doesn't work properly when setting.
Here's some example JSON to use with this problem, though again, the structure is not known before the script runs. I'm looping over a list of files on disk, and executing for each file.
$JSON = ConvertFrom-Json '{
"key1":"key 1 value",
"options":{
"outDir":"./app-dir",
"lib":[
"someLibrary",
"anotherLibrary"
],
"someObjects":[
{
"first":"I am first"
},
{
"second":"I am second"
}
]
}
}'
The string to search this json might look like the following:
$SearchString = 'options.someObjects.first'
Or perhaps, something non-existent like:
$SearchString = 'options.someObjects.foo'
Using the recursive function GetValue from the 2nd article works beautifully for getting (and much more elegant than what I was doing):
function GetValue($object, $key)
{
$p1,$p2 = $key.Split(".")
if($p2) { return GetValue -object $object.$p1 -key $p2 }
else { return $object.$p1 }
}
However, the function SetValue does not work with an array. It returns an error stating "The property 'first' can not be found on this object."
function SetValue($object, $key, $Value)
{
$p1,$p2 = $key.Split(".")
if($p2) { SetValue -object $object.$p1 -key $p2 -Value $Value }
else { $object.$p1 = $Value }
}
I am aware this is because $JSON.options.someObjects is an array, therefore to access the object with the "first" key, the path would be:
$JSON.options.someObjects[0].first
That's the problem I'm having. How do I dynamically iterate over all objects once it reaches a part of the path that needs iterating? That part of the path could be anywhere, or more levels down, etc...
It's strange that powershell will allow you to dynamically iterate through an array when getting the value, but not when trying to set it.
Here's a complete example which demonstrates the entire issue:
#Create JSON:
$JSON = ConvertFrom-Json '{
"key1":"key 1 value",
"options":{
"outDir":"./app-dir",
"lib":[
"someLibrary",
"anotherLibrary"
],
"someObjects":[
{
"first":"I am first"
},
{
"second":"I am second"
}
]
}
}'
$SearchPath = 'options.someObjects.first'
$NewValue = 'I am a new value'
function GetValue($object, $key)
{
$p1,$p2 = $key.Split(".")
if($p2) { GetValue -object $object.$p1 -key $p2 }
else { return $object.$p1 }
}
function SetValue($object, $key, $Value)
{
$p1,$p2 = $key.Split(".")
if($p2) { SetValue -object $object.$p1 -key $p2 -Value $Value }
else { return $object.$p1 = $Value }
}
GetValue -object $JSON -key $SearchPath
SetValue -object $JSON -key $SearchPath -Value $NewValue
I've been searching all kinds of different terms trying to arrive at a good solution for this problem, but so far, no luck. I'm fairly certain I'm not the 1st person to want to do this sort of thing, apologies if I missed the answer somewhere.
There are two issues with your SetValue script:
Returning the object
An Object ([Object]) vs an object array
([Object[]])
Return
You can't return an assignment like return $object.$p1 = $Value. The assignment itself returns nothing with will result in returning a $Null to caller.
Besides, if you return the $Object for each recursive call, you will need to void ($Null = SetValue -object...) it by each parent caller so that it is only returned by the top caller. but keep in mind that you are actually poking the $NewValue in the original ($JSON) object!. If you don't want that, you will need to figure out the top caller and only copy the $Object at the top level before the recursive call.
Object array
You not just dealing with properties containing single objects but each property might potentially contain a collection objects. In fact, the leaf property SomeObject is an example of this. Meaning that each object in the collection has its own unique set of properties (which could have the same property name as the sibling object):
$JSON = ConvertFrom-Json '{
"key1":"key 1 value",
"options":{
"outDir":"./app-dir",
"lib":[
"someLibrary",
"anotherLibrary"
],
"someObjects":[
{
"first":"I am first"
},
{
"first":"I am first too"
},
{
"second":"I am second"
}
]
}
}'
Note that you might actually encounter a object collection at every level of the Json object.
Since PSv3 you have a feature called Member Enumeration which lets you list these properties in ones, like: ([Object[]]$SomeObject).First but you can't just set (all) the concerned properties like this: ([Object[]]$SomeObject).First = $Value. (That is why your SetValue function doesn't work and your GetValue function does. Note that it
actually returns two items for the above "I am first too" Json example).
Answer
In other words, you will need to iterate through all the object collections on each level to set the concerned property:
function SetValue($object, $key, $Value)
{
$p1,$p2 = $key.Split(".",2)
if($p2) { $object | ?{$Null -ne $_.$p1} | %{SetValue -object $_.$p1 -key $p2 -Value $Value} }
else { $object | ?{$Null -ne $_.$p1} | %{$_.$p1 = $Value} }
}
SetValue -object $JSON -key $SearchPath -Value $NewValue
$Json | ConvertTo-Json -Depth 5
{
"key1": "key 1 value",
"options": {
"outDir": "./app-dir",
"lib": [
"someLibrary",
"anotherLibrary"
],
"someObjects": [
{
"first": "I am a new value"
},
{
"second": "I am second"
}
]
}
}

convert 1 dimension strings array into json file

Perhaps it's easy question and will be banned but I spend hours today and not make it work as I expected.
so I have such object:
data_1 data_2 ... abra_12 ...
I want convert it into json like this:
[
{"data_1" : "data_1"},
{"data_2" : "data_2"},
...
]
I tried this:
$result = Get-ChildItem $Path2Search -recurse | Select-String -Pattern '(?<=locI(.*)\(\").+?(?=\")' -casesensitive | foreach {$_.matches} | select value | ConvertTo-Json | Out-File $save2file
But I'm getting this:
{
"Value": "data_1"
},
{
"Value": "data_2"
},
While I want it like this:
{
"data_1": "data_1"
},
{
"data_2": "data_2"
},
Any advice how? :)
You can first shape the result in a list of key-values then convert it to json.
For example if your input is a comma separated string, you can use:
$input = "data_1, data_2, data_3"
$input.Split(',').Trim() |
ForEach-Object {#{$_=$_}} |
ConvertTo-Json |
Out-File "C:\test.txt"
Or if the input is a string array:
$input = #("data_1", "data_2", "data_3")
$input | ForEach-Object {#{$_=$_}} |
ConvertTo-Json |
Out-File "C:\test.txt"
And the result would be:
[
{
"data_1": "data_1"
},
{
"data_2": "data_2"
},
{
"data_3": "data_3"
}
]
While I prefer to use above solution for creating json result, but you also can rely on string manipulation as well:
$input = "data_1, data_2, data_3"
#"
[
`t$(($input -split '\s*,\s*' |%{"{ `"$_`" : `"$_`" }"}) -join ",`n`t")
]
"#

Laravel Many to Many Relationship - Linking 3 items

I have a users table
|------------|
| USERS |
|------------|
|id |
|f_name |
|l_name |
|------------|
Now these users can be related to each other - Dad, Brother, Sister, etc.
For this i have a relations table
|------------|
| RELATIONS |
|------------|
|id |
|type |
|------------|
Now how do i associate the users? What i have tried is:
I created a relation_user table
|---------------|
| RELATION_USER |
|---------------|
|id |
|user_id |
|user2_id |
|relation_id |
|---------------|
Models:
Relation.php
public function user()
{
return $this->belongsToMany('User');
}
User.php
public function relations()
{
return $this->belongsToMany('Relation');
}
I try to grab the relation like so:
$u = User::find('11');
var_dump($u->relations->toArray());
Here is what i get:
array(1) {
[0]=>
array(5) {
["id"]=>
string(1) "1"
["relation"]=>
string(12) "Grand Father"
["pivot"]=>
array(2) {
["user_id"]=>
string(2) "11"
["relation_id"]=>
string(1) "1"
}
}
}
Notice that its missing the user2_id from the pivot table (relation_user). How can i get the 3rd field as well. Also is this a right database design? Thank you.
I know i can do something like the code below. Just want to know the best practise/better way. Probably something that can leverage eager loading and return a Laravel collection?
$a = DB::table('relation_user')->where('user2_id', '=', '1')->get(array('user_id', 'user2_id', 'relation_id'));
Okay, i got a good solution to my problem. Here is what i did:
Models:
Relation.php
public function user(){
return $this->belongsToMany('User', 'relation_user', 'user_id', 'relation_id')->withPivot('user2_id')->withTimestamps();
}
User.php
public function relations() {
return $this->belongsToMany('Relation', 'relation_user', 'user_id', 'relation_id')->withPivot('user2_id')->withTimestamps();
}
Grab the relation like so:
$user = User::find('11');
$user->relations->toArray();
Give me the following output:
array(1) {
[0]=>
array(5) {
["id"]=>
string(1) "1"
["relation"]=>
string(12) "Grand Father"
["created_at"]=>
string(19) "2014-03-17 01:18:54"
["updated_at"]=>
string(19) "2014-03-17 01:18:54"
["pivot"]=>
array(5) {
["user_id"]=>
string(2) "11"
["relation_id"]=>
string(1) "1"
["user2_id"]=>
string(1) "1"
["created_at"]=>
string(19) "2014-03-17 02:02:43"
["updated_at"]=>
string(19) "2014-03-17 02:02:43"
}
}
}
And insert like so:
$user->relations()->attach(1, array('user2_id' => 2));
You need to correct your model class like so:
public function user()
{
return $this->belongsTo('User'); //one relation entry correspond to one user
}
public function relations()
{
return $this->hasMany('Relation'); //one user has multiple relations
}
//query the user and the relations
$u = User::find(11)->relations; // this will returns entries in relation table with user_id=11

Resources