Change 'descendants' relationship to 'siblings' relationship - php

I have variants table as following:
+-------------------+------------------+------+-----+---------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+------------------+------+-----+---------------------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| parent_product_id | int(10) unsigned | NO | MUL | NULL | |
| child_product_id | int(10) unsigned | NO | MUL | NULL | |
+-------------------+------------------+------+-----+---------------------+----------------+
with constraints:
CONSTRAINT `variant_products_child_product_id_foreign` FOREIGN KEY (`child_product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE
CONSTRAINT `variant_products_parent_product_id_foreign` FOREIGN KEY (`parent_product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE
Let's say that it is filled with:
| id | parent_product_id | child_product_id |
|----+-------------------+------------------|
| 28 | 9 | 11 |
| 29 | 17 | 30 |
| 30 | 9 | 59 |
| 31 | 9 | 60 |
| 32 | 17 | 25 |
At first, business requirements was that one (parent) product can have multiple children. In my Product model I have
public function variants()
{
return $this->hasMany(\App\Variant::class, 'parent_product_id', 'id');
}
And in Variant model:
public function child()
{
return $this->belongsTo(\App\Product::class, 'child_product_id');
}
When I am querying Product (id:9) using:
$query->with([
'variants.child' => function ($query) {
$query->select(['products.id', 'products.name'])
},
]);
I am getting nice response:
{
"id": 9,
"name": "Foo",
"description": "Ipsam minus provident cum accusantium id asperiores.",
"variants": [
{
"id": 11,
"name": "Bar"
},
{
"id": 59,
"name": "Fizz"
},
{
"id": 60,
"name": "Buzz"
}
]
}
When asking about product 59, there are not any variants.
Now, I need to redefine my relationships so that products and variants will become rather siblings than descendants.
For example, after asking about product 59, desired response is:
{
"id": 59,
"name": "Fizz",
"description": "Lorem ipsum dolor sit amet, consectetur adipisicing elit.",
"variants": [
{
"id": 9,
"name": "Foo"
},
{
"id": 11,
"name": "Bar"
},
{
"id": 60,
"name": "Buzz"
}
]
}
How can I achieve it without changing database structure. Any help and tips are much appreciated.
Edit: Two notes:
Every child can have only one parent. (There is a unique key on
child_product_id column in database.)
There can only be one level of "nesting". Which means, that if one product already is a child - it cannot be a parent to another.

As I said in the comment, I think it is still better to keep some type of parent to better manage the sibling relationships. For example if you want 59, 9, 11, 60 to be siblings, you can keep a common parent id for them all, like 999, which will keep them as siblings.
The other thing is that, if you have only one parent_product_id for each item, you don't need to keep it in a separate table. You can keep the parent_product_id in the same table products and set variants in \App\Product like this:
public function variants()
{
return $this->hasMany(\App\Product::class, 'parent_product_id', 'parent_product_id');
}
Now, you can get the list of siblings with this little modification to your query part :
$query->with([
'variants' => function ($query) {
$query->select(['products.id', 'products.name'])
},
]);

Related

Laravel how to addSelect all total counts on a single table each row

i am trying to build a single query but something is wrong. i want to write a code that each row have a all total count on a one table. i will describe
first i will query the total counts :
$count = Rating::whereIN('book_id',Books::select('id'))->count();
//the all total counts of this ratings table is 12
second is querying the books count each rows in ratings with authors :
return $books = Books::withCount('rating')
->with(['author:id,user_id','author.user:id,name,email'])
->get();
the output of this :
[
{
"id": 1,
"created_at": "2022-06-15T09:59:10.000000Z",
"updated_at": "2022-06-15T09:59:10.000000Z",
"author_id": 2,
"title": "vel",
"name": "Qui odit eum ea recusandae rem officiis.",
"rating_count": 5,
"author": {
"id": 2,
"user_id": 1,
"user": {
"id": 1,
"name": "Joshua Weber",
"email": "bhessel#example.com"
}
}
},
{
"id": 2,
"created_at": "2022-06-15T09:59:10.000000Z",
"updated_at": "2022-06-15T09:59:10.000000Z",
"author_id": 1,
"title": "atque",
"name": "Beatae tenetur modi rerum dolore facilis eos incidunt.",
"rating_count": 7,
"author": {
"id": 1,
"user_id": 5,
"user": {
"id": 5,
"name": "Miss Destinee Nitzsche III",
"email": "jamir.powlowski#example.net"
}
}
}
]
you can see in this code each row has own their rating_count in id:1 has rating_count 5 and in id:2 has rating count 7 when summing them total of 12.
now the point of my problem is i want to add addSelect() in the Book::withCount i want to add the first query i wrote. so each row has a total books of 12
i tried this code but it gives a error:
return $books = Books::withCount('rating')
->with(['author:id,user_id','author.user:id,name,email'])
->addSelect(['total_books'=>Rating::whereIN('book_id',Books::select('id'))->count()])
->get();
the error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column '105' in 'field list' (SQL: select `books`.*, (select count(*) from `ratings` where `books`.`id` = `ratings`.`book_id`) as `rating_count`, `105` from `books`)
here is my tables: ( i did not add row created_at and updated_ad in authors and ratings )
my table authors
id | user_id
1 | 1
2 | 5
my table books
id | created_at | updated_at | author_id | title | name
1 | | | 1 | vel | Qui odit eum ea recusandae rem officiis
2 | | | 2 | atque | Beatae tenetur modi rerum dolore facilis eos incidunt.
my table ratings
id | rating | book_id
1 | 5 | 1
2 | 4 | 1
3 | 4 | 1
4 | 3 | 1
5 | 2 | 1
6 | 1 | 1
7 | 1 | 1
8 | 5 | 2
9 | 4 | 2
10 | 3 | 2
11 | 3 | 2
12 | 1 | 2
here is my models
model Authors
class Author extends Model
{
use HasFactory;
public function books(){
return $this->hasMany(Books::class);
}
public function User(){
return $this->belongsTo(User::class);
}
}
model Books
class Books extends Model
{
use HasFactory;
protected $casts = [
'created_at' => 'datetime',
];
public function rating(){
return $this->hasMany(Rating::class,'book_id');
}
public function author(){
return $this->belongsTo(Author::class);
}
}
I don't quite understand the query for count of total_books
$count = Rating::whereIN('book_id',Books::select('id'))->count();
The above query is essentially the count of records in the ratings table. Since the records in ratings table will have a valid value for book_id (assuming integrity constraints are defined) which means that for any row/record in the ratings table the value contained in book_id will be an id of an existing record in books table.
So the whereIn('book_id', Book::select('id')) is unnecessary. You can do just
$count = Rating::count();
//Which will output the same result as
//$count = Rating::whereIN('book_id',Books::select('id'))->count();
Then you can have your composite query with addSelect as
$books = Books::query()
->withCount('rating')
->with(['author:id,user_id','author.user:id,name,email'])
->addSelect([
'total_books' => Rating::selectRaw('count(*)')
])
->get();
Or using selectRaw
$books = Books::query()
->withCount('rating')
->with(['author:id,user_id','author.user:id,name,email'])
->selectRaw('? as total_ratings', [Rating::count()])
->get();
The total_books should probably be named as total_ratings
If you still want to have your whereIn constraint (which isn't necessary) you can
$books = Books::query()
->withCount('rating')
->with(['author:id,user_id','author.user:id,name,email'])
->addSelect([
'total_books' => Rating::selectRaw('count(id)')
->whereIn('book_id', Book::select('id'))
])
->get();
The above will generate an sql
select `books`.*,
(select count(*) from `ratings` where `books`.`id` = `ratings`.`book_id`) as `rating_count`,
(select count(id) from `ratings` where `book_id` in (select `id` from `books`)) as `total_books`
from `books`
OR with selectRaw
$books = Books::query()
->withCount('rating')
->with(['author:id,user_id','author.user:id,name,email'])
->selectRaw('? as total_ratings',[Rating::whereIn('book_id', Book::select('id'))->count()])
->get();
As I commented, you can count total ratings first, then reassign it to $book instance. Don't let SQL query calculates for every fetched rows if unnecessary.
$books = Books::query()
->with(['author:id,user_id','author.user:id,name,email'])
->get();
$count = Rating::query()
->whereIn('book_id', $books->pluck('id')->toArray())
->count();
foreach ($books as $book) {
$book->rating_count = $count;
}

concat two mysql tables and convert it to json with php

I'm using PHP with PDO for a project
I have the following two tables in my database.
Category
CategoryID | Name | CategoryCode
1 | Fixed | FA
2 | Consumable | CA
3 | Intangible | IA
Type
TypeID | CategoryID | Name | TypeCode
1 | 1 | Furniture | FU
2 | 1 | Computers & Computer Peripherals | CP
3 | 1 | Electrical Appliances | EA
4 | 1 | Machinery | MA
5 | 2 | Computer Peripherals | PE
6 | 3 | Software | SW
I need to get a output like below from the select operation. I have tried it with group concat and then fecthAll(), but it doesn't give me the result in correct JSON
Output
[
{
"CategoryID": 1,
"Name": "Fixed",
"CategoryCode": "FA",
"Types": [
{
"TypeID": 1,
"Name": "Furniture",
"TypeCode": "FU"
},
{
"TypeID": 2,
"Name": "Computers & Computer Peripherals",
"TypeCode": "CP"
},
{
"TypeID": 3,
"Name": "Electrical Appliances",
"TypeCode": "EA"
}
]
},
{
"CategoryID": 2,
"Name": "Consumable",
"CategoryCode": "CA",
"Types": [
{
"TypeID": 5,
"Name": "Computer Peripherals",
"TypeCode": "PE"
}
]
}
]
Figured it out this way to get my intended output
protected function getCategories(){
$dbConnection = $this->connect();
$catSql = 'SELECT * FROM `category`';
$stmt = $dbConnection->prepare($catSql);
$stmt->execute();
$result = $stmt->fetchAll();
$output = array();
foreach ($result as $row) {
$line = array("CategoryID" => $row['CategoryID'], "CategoryName" => $row['Name'], "CategoryCode" => $row['CategoryCode']);
$typeSQL = 'SELECT
TypeID,
Name,
TypeCode
FROM type
WHERE
CategoryID = ' . $row['CategoryID'];
$stmt2 = $dbConnection->prepare($typeSQL);
$stmt2->execute();
$types = $stmt2->fetchAll();
$typeArray = array();
foreach ($types as $type) {
$typeLine = array("TypeID" => $type['TypeID'], "TypeName" => $type['Name'], "TypeCode" => $type['TypeCode']);
array_push($typeArray, $typeLine);
}
$line['Types'] = $typeArray;
array_push($output, $line);
}
return json_encode($output);
}

(Laravel/PHP) SQL Query that selects all orders from table that matches product_id and variation_id in array

Im trying to build a SQL Query that will select all orders from a table that matches options that i defined.
Databse i use: Mysql
Language: PHP
Basicly i have a array that looks like this.
[
[
"user_id" => 1,
"product_id" => 5548,
"variation_id" => 14
],
[
"user_id" => 1,
"product_id" => 5548,
"variation_id" => 15
],
[
"user_id" => 1,
"product_id" => 4422,
"variation_id" => 4
]
]
This means that the user(id: 1) has one product with the "id" of 5548, and then he also has 2 variations of that product that are "id" 14 and 15. You can also see that the same user owns the product(id:4422) that has variation(id:4).
I then have a "order_lines" table that looks like this
order_lines
+----+-----+---------+-----------------------------+
| id | uid | user_id | product_id | variation_id |
+----+-----+---------+-----------------------------+
| 1 | 1 | 1 | 5548 | 14 |
+----+-----+---------+-----------------------------+
| 2 | 2 | 1 | 5548 | 15 |
+----+-----+---------+-----------------------------+
| 3 | 3 | 1 | 4422 | 4 |
+----+-----+---------+-----------------------------+
| . | . | . | .... | .. |
+----+-----+---------+-----------------------------+
I now need a SQL Query that selects all the rows where there is a match between the user_id, product_id and variation_id that are defined in the array.
The output should contain all rows that meet these conditions.
I hope someone can pin me in the right direction.
I'm building in Laravel if you got the query builder just at your hand. Else i very much appreciate an SQL Query.
if I am getting you right, below code will help you, using just Core PHP
foreach($array as $arr){
$user_id = $arr['user_id'];
$prodct_id = $arr['prodct_id'];
$variation_id = $arr['variation_id'];
$query = "SELECT * FROM order_lines WHERE user_id = $userId AND product_id = $productId AND variation_id = $variationId";
$queryResult = mysql_fetch_assoc($query);
$yourCollection[] = $queryResult;
}
print_r($yourCollection);
Try below code to use Laravel Query Builder, below code will help you to get results for multiple users based on product and variation.
$qb_order_lines = DB::table('order_lines');
$where_condition = [
['user_id' => '', 'product_id' => '', 'variation_id' => ''],
];
foreach ($where_condition as $condition) {
$qb_order_lines->orWhere(function($query) use ($condition) {
$query->where('user_id', $condition['user_id'])
->where('product_id', $condition['product_id'])
->where('variation_id', $condition['variation_id']);
});
}
$obj_result = $qb_order_lines->get();
If you want to get it for only one user, use below code
$obj_result = DB::table('order_lines')
->where('user_id', $condition['user_id'])
->where('product_id', $condition['product_id'])
->where('variation_id', $condition['variation_id'])
->get();
You can modify the above query builders based on your requirements like select fields or group by.
Let me know if you need any help.
For anyone interesting.
My problem was that i needed to count of many matches that were between my array and my database.
Instead of selecting and outputting. I eneded up using sql count() function in a query, that did the job.

create array using two foreach

I hope you guys can help me here, because I guess my code is not made correctly.
I have 2 mysql tables:
table: checks
+-----------+-------------+------------+
| id | name | host |
+-----------+-------------+------------+
| 1 | demo 1 | 1.1.1.1 |
+-----------+-------------+------------+
| 2 | demo 2 | 1.1.1.2 |
+-----------+-------------+------------+
| 3 | demo 3 | 1.1.1.3 |
+-----------+-------------+------------+
table: checks_history
+-----------+-------------+------------+------------+
| id | check_id | status | timestamp |
+-----------+-------------+------------+------------+
| 1 | 1 | 0 | 3451245 |
+-----------+-------------+------------+------------+
| 2 | 1 | 0 | 3451245 |
+-----------+-------------+------------+------------+
| 3 | 2 | 0 | 3451245 |
+-----------+-------------+------------+------------+
| 4 | 1 | 1 | 3451245 |
+-----------+-------------+------------+------------+
| 5 | 2 | 0 | 3451245 |
+-----------+-------------+------------+------------+
I want create a json file per id (table: checks) with this structure:
{
"info": { // Associated to table "checks"
"id": "1",
"name": "Demo 1",
"host": "1.1.1.1"
},
"data": { // associated to table check_history according with the id on table check
"1": { // associated to Column "id" on table checks_history
"status": "0",
"timestamp": "3451245"
},
"2": {
"status": "0",
"timestamp": "3451245"
},
"4": {
"status": "1",
"timestamp": "3451245"
}
}
}
There is my code PHP:
$info = array();
$history = array();
$incidents = $database->select("app_checks","*", false);
foreach ($incidents as $key => $value) {
$id = $value['id'];
$name = $value['name'];
$host = $value['host'];
$check_history = $database->select("app_checks_history", "*", [ "checkid" => $id, "ORDER" => ['id' => 'DESC'], "LIMIT" => 30 ]);
foreach ($check_history as $k => $v) {
$history = array(
$v['id'] => array(
'timestamp' => $v['timestamp'],
'status' => $v['status']
)
);
}
$info = array(
'info'=> array(
'id'=> $id,
'name'=> $name,
'host'=> $host
),
'data' => $history
);
$json_data = json_encode($info, JSON_PRETTY_PRINT);
$fileName = 'json/server_'.$id.'.json';
file_put_contents($fileName, $json_data);
}
When I try run the code, im getting the first value on "data" instead all loop:
{
"info": {
"id": "1",
"name": "Demo 1",
"host": "1.1.1.1"
},
"data": {
"1": {
"status": "0",
"timestamp": "3451245"
}
}
}
I searched in the forum and I did not found any similar issue related to my code.
I appreciate any help here.
Thanks in advance.
br
Well, I don't know PHP; however, I believe if you get your data model right, it should get you there. I would have my data model something like below in C#, an instance of Check class represent a check as per your table, serialize and save each instance in its own JSON file.
namespace Stackoverflow
{
using System;
using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
public partial class Check
{
[JsonProperty("info")]
public Info Info { get; set; }
[JsonProperty("data")]
public Dictionary<string, History> Data { get; set; }
}
public partial class History
{
[JsonProperty("status")]
[JsonConverter(typeof(ParseStringConverter))]
public long Status { get; set; }
[JsonProperty("timestamp")]
[JsonConverter(typeof(ParseStringConverter))]
public long Timestamp { get; set; }
}
public partial class Info
{
[JsonProperty("id")]
[JsonConverter(typeof(ParseStringConverter))]
public long Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("host")]
public string Host { get; set; }
}
}

Complicated nested array issue

I want to query data from a database into a json array that ultimalty produces a force directed graph in javascript. Here is what the Json array should be like. However nodes can have mulitiple adjacencies or none, how can I query a json array where the adjacencies section varies from node to node and is able to adjust according to the number of adjacencies a node has?
Var JSON =[
{
"adjacencies": [
{
"nodeTo": "graphnode9",
"nodeFrom": "graphnode5",
"data": {}
}
],
"data": {
"$color": "#416D9C",
"$type": "star"
},
"id": "graphnode5",
"name": "graphnode5"
},
];
or they can have
Var JSON =[
{
"adjacencies": [
{
"nodeTo": "graphnode9",
"nodeFrom": "graphnode5",
"data": {}
},
{
"nodeTo": "graphnode9",
"nodeFrom": "graphnode5",
"data": {}
},
{
"nodeTo": "graphnode9",
"nodeFrom": "graphnode5",
"data": {}
}
],
"data": {
"$color": "#416D9C",
"$type": "star"
},
"id": "graphnode5",
"name": "graphnode5"
},
];
or they can not have any
Var JSON =[
{
"adjacencies": [],
"data": {
"$color": "#416D9C",
"$type": "star"
},
"id": "graphnode5",
"name": "graphnode5"
},
];
Here is my attempt so far, however this only produces a json that only allows one adjacencies, How can I setup a Json query that will adjust the the number of adjacencies a node have? while just loading the data and id section once but allowing the adjacenies to be varied?
Here is my Database structure
nodes Relationships
----- -------------
id int(11), id int(11),
name varchar(35), goingto int(11), //this is the destination node from the id relation
color varchar(7), data varchar(0) null
type varchar (12), Foreign key (id) references nodes(id)
Primary key (id)
engine = innodb
And here is my attempt that
function getjson(){
$db = adodbConnect();
$query = "SELECT nodes.*, relationships.* FROM nodes inner JOIN relationships ON nodes.id = relationships.id";
$result = $db -> Execute($query);
while($row=$result->FetchRow())
{
$id= (float)$row['id'];
$name = $row['name'];
$color1 = $row['color'];
$type1 = $row['type'];
$to= (float)$row['goingto'];
$thumb =$row['thumb']; //image path
$array[] = array(
"adjacencies" => array( array(
"nodeTo" => "$to",
"nodeFrom" => "$id",
"data" => array() )),
"data" => array(
"$"."color" => $color1,
"$"."type" => $type1 ),
"id" => $id,
"name" => "<img src='".$thumb."' height='25' width='25' alt='root'/><label>".$name."</label>");
}
$json = json_encode($array);
print "$json";
//return $json;
}
If you want to return the result in a single query, then you will end up with duplicated data for the node, in each separate row where there's a distinct adjacency from that node... Which is fine, that's how it works.
But as it sits, you won't get nodes returned if there's no adjacency on that node (because you're using an INNER join. You should use a LEFT join to include nodes that have no results from the related adjacency table).
By sorting by node id, we explicitly ensure that all nodes and their adjacencies appear grouped together. This is probably happening already because id is your pk and hence the sort is happening this way "automatically". But an ORDER BY nodes.id ensures this happens, and makes your intention clear to anyone looking at the code.
Also, because you're returning everything * from both tables, you're going to have column name conflicts, on node.id and relationship.id. Ideally you'd explicitly name your columns to avoid this so that you have predictable results back in PHP.
So your SQL could look more like:
SELECT
n.id as n_id,
n.name,
n.color,
n.type,
r.id as r_id,
r.goingto,
r.data
FROM
nodes n
LEFT JOIN relationships r
ON n.id = r.id
ORDER BY
n.id
This returns a result set that looks something like:
n_id | name | color | type | r_id | goingto | data
------+-------+--------+-------+------+---------+-----------
1 | node1 | red | type1 | 1 | 5 | stuff
1 | node1 | red | type1 | 2 | 6 | morestuff
2 | node2 | blue | type2 | 3 | 10 | whatever
3 | node3 | green | type3 | null | null | null
4 | node4 | orange | type4 | 4 | 20 | xxx1
4 | node4 | orange | type4 | 5 | 21 | xxx2
4 | node4 | orange | type4 | 6 | 22 | xxx3
etc...
(ie this assumes node 1 has two relationships, node 2 has 1 relationship, node 3 has no relationships, and node 4 has 3).
And then, your code that builds the array just needs to iterate the results, building a new node only when the current record's node is not the same as the previous one (ie we're relying on the ORDER BY node.id to "gather" all the info for a particular node, sequentially).
This code hasn't been tested, but I think the intent is clear, you should be able to bend this as required - but it basically just implements the above.
Replace your while loop with all of this.
$previd = -1;
while($row=$result->FetchRow())
{
$id= (float)$row['n_id']; // <--- note change from 'id' to 'n_id'
$name = $row['name'];
$color1 = $row['color'];
$type1 = $row['type'];
$to= (float)$row['goingto'];
$thumb =$row['thumb']; //image path
// Is this row the start of a new node?
if ($previd != $id) {
// Yes, new node. Record our new node id, for future new node checks.
$previd = $id;
// Store the previous node we've already built, now that it's complete (but only if there was a previous node!)
if ($previd != -1) {
$array.push($node);
}
// Start our new node off, ready to accept adjacencies
$node = array(
"adjacencies" => array(),
"data" => array(
"$"."color" => $color1,
"$"."type" => $type1
),
"id" => $id,
"name" => "<img src='".$thumb."' height='25' width='25' alt='root'/><label>".$name."</label>");
}
// Any adjacency for this node, on this row?
if ($to != null) { // <-- Not sure about this line!
// Yes there is, so create the new adjacency record and add it to the current node's adjacency array.
$node["adjacencies"].push(
array(
"nodeTo" => "$to",
"nodeFrom" => "$id",
"data" => array()
)
);
}
}
I'm not sure how "no adjacency" will be represented in $to - ie if this will be "null" or what. I'll leave that to you to test, but suffice to say you'll need to reflect this in the line if ($to != null) { // <-- Not sure about this line!

Categories