Codeigniter 4 Model Relationship Entity - codeigniter-4

I have database tables that look like this : Table 1 : transaction
id|buyer_id|transaction_date
----------------------------
1 | 1 |2020-01-01
2 | 4 |2020-03-04
3 | 6 |2020-11-12
----------------------------
Table 2 : transaction_detail
id|transaction_id|item_id|qty
--------------------------------
1| 1 | 1 | 3 |
2| 1 | 4 | 1 |
3| 1 | 6 | 2 |
--------------------------------
transaction_detai.transaction_id is a foreign key to transaction.id
How can I select data in transaction table but also get all the transaction_detail as a child ? If I use join, it will generate all data in one row. I need something just like this :
Array(
[0] => Master\Entities\Transaction Object
(
[id:protected] =>
[buyer_id:protected] =>
[transaction_date:protected] =>
[transaction_detail:protected]=>
Array(
[0] => Master\Entities\TransactionDetail Object
(
[id:protected] =>
[transaction_id:protected] =>
[item_id:protected] =>
[qty:protected] =>
)
)
)
)

Your Context not clear whether you need it be done with model or with query builders. Using builders you can create a multidimensional array and place details accordingly and sample code for that is as:
$db = \Config\Database::connect();
// Fetch all details from main table say `transaction`
$dataMain = $db->table('transaction')
->where('transaction.deleted_at',NULL)
->select('transaction.id,
transaction.buyer_id,transaction.transaction_date')
->get()
->getResult();
$result = [];
foreach($dataMain as $dataRow){
// For each master table row generate two sub array one to store main table row and other to hold details table data respective to main table row
if(!isset($result[$dataRow->id])){
$result[$dataRow->id]['transaction'] = [];
$result[$dataRow->id]['transaction_details'] = [];
array_push($result[$dataRow->id]['transaction'],$dataRow);
}
$dataSecondary = $db->table('transaction_detail')
->where('transaction_detail.deleted_at',NULL)
->select('transaction_detail.id,
transaction_detail.transaction_id,
transaction_detail.item_id,transaction_detail.qty')
->where('transaction_detail.offer_id',$dataRow->id)
->get()
->getResult();
array_push($result[$dataRow->id]['transaction_details'],$dataSecondary);
}
// $result[$id]['transaction'] - contains master table data
// $result[$id]['transaction_details'] - contains sub table datas associated with respective master table data
print '<pre>';
var_dump($result);
exit;

Related

get data from intermediate table in laravel

i have 2 model(SaleInvoice and Product) with many to many relation
in SaleInvoice model :
public function products()
{
return $this->belongsToMany(Product::class, 'sale_invoice_product', 'saleInvoice_id', 'product_id')->withPivot('count');
}
in Product Model:
public function saleInvoices()
{
return $this->belongsToMany(SaleInvoice::class, 'sale_invoice_product', 'product_id', 'saleInvoice_id');
}
this is the example of data that recorded in sale_invoice_product table(intermediate table)
id | saleInvoiceId | product_id | count
1 | 1500 | 1 | 3
2 | 1500 | 3 | 2
3 | 1500 | 4 | 4
4 | 1501 | 1 | 1
5 | 1501 | 4 | 1
how can i access to data of product and sale invoice from this table like below(in json mode for api request)
product_id | product_name | count | saleInvoice | date
1 LG 3 1500 2020-05-12
3 SONY 2 1500 2020-05-13
4 OT 4 1500 2020-05-17
1 LG 1 1501 2020-05-19
4 OT 1 1501 2020-05-22
i want to return a json file in SaleInvoiceController with top format
Your work was good, Just enough make a API resource for this model and send attributes as you want, For accessing to pivot table you could use $product->pivot->count.
You can try one of these methods
Building a model for sale_invoice_product table with relations to SaleInvoice and Product. Then manually construct the JSON in your controller
Build an SQL View and a Model for it
Solution 1: Building a model to the intermediate table and manually constructing the JSON
Let's say you built a model called SaleInvoiceProduct that has product() relation to the Products table and saleInvoice() relation to the SaleInvoices table. In your controller you can do this
$resultInvoiceProducts = [];
$allSaleInvoiceProducts = SaleInvoiceProduct::all();
foreach ($allSaleInvoiceProducts as oneSaleInvoiceProduct) {
$tempSaleInvoiceProduct = new stdClass();
$tempSaleInvoiceProduct->product_id = oneSaleInvoiceProduct->product_id;
$tempSaleInvoiceProduct->product_name = oneSaleInvoiceProduct->product->name;
$tempSaleInvoiceProduct->saleInvoiceId = oneSaleInvoiceProduct->saleInvoiceId;
$tempSaleInvoiceProduct->data = oneSaleInvoiceProduct->saleInvoice->date;
array_push($resultInvoiceProducts, $tempSaleInvoiceProduct);
}
Solution 2: Using SQL Views
You can create an SQL View that uses Joins to construct the data you need
DROP VIEW IF EXISTS vSaleInvoiceProduct;
CREATE VIEW vSaleInvoiceProduct AS
SELECT sp.product_id,
sp.saleInvoiceId,
sp.`count`,
p.product_name,
s.`date`
FROM SaleInvoiceProduct sp
LEFT JOIN SaleInvoices s on sp.saleInvoiceId = s.saleInvoiceId
LEFT JOIN Products p on sp.product_id = p.product_id
Then you can create a Laravel model for this View just like you would do for any table, call the ::all() method on it and directly return the results with json()

SSAS OLAP Cube Dynamic Security. Many dimensions in one role

After setting a cube I was asked to add dynamic security with use of table of users and data they can see.
The problem is that i have to take into account 3 different dimensions.
I've decided to use the fact table with noneEmpty function on count.
NonEmpty([Dimension].[Hierarchy].members,
([Measures].[Allowed Count],
[Users].[User].&[UserName]
)
)
After setting role I've got result like:
Dim1 | Dim2 | Dim3
1 | A | 300
1 | A | 320
1 | A | 340
1 | B | 300
1 | B | 320
1 | B | 340
Where it should be:
Dim1 | Dim2 | Dim3
1 | A | 300
1 | A | 320
1 | B | 340
Data for allowed user access are stored in table like
UserName | Dim1Key | Dim2Key | Dim3Key
Hierarchy is like
Each Dim1 contains each type of Dim2 that contains each type of Dim3.
And user can only access given member of Dim3 in Dim2 in Dim1.
Is there a way to connect this dimensions in MDX so each Dim in the end has only its respective values
UPDATE:
After some research I've got this query:
SELECT [Measures].[CC Count] ON 0,
NonEmpty(
(
NonEmpty((Dim1.children),
([Measures].[CC Count],
[Users].[User].&[userName]
))
,
NonEmpty((Dim2.children),
([Measures].[CC Count],
[Users].[User].&[userName]
)),
NonEmpty((Dim3.children),
([Measures].[CC Count],
[Users].[User].&[userName]
))
)
,([Measures].[CC Count],
[Users].[User].&[userName]
))
ON 1
FROM [Cost Center]
That gives me wanted results, but I can't place it into Dimensiom Data in Role. Is there a way to change it?
Please try creating a new hidden dimension where the key attribute has a composite key of key1, key2 and key3. You will have to pick some NameColumn but it doesn't matter. So pick key1 as the name. You don't need anything on the dimension except the dimension key.
In the Dimension Usage of your cube designer make sure this new dimension is joined to all fact tables and to the security measure group which provided the CC Count measure.
Then create role based security just on that dimension. The users will be able to see all members of all dimensions but this new composite key dimension will ensure they can't see fact rows they are not supposed to. And this should perform much better than the alternative which is cell security.

Cassandra how to add values in a single row on every hit

In this table application will feed us with the below data and it will be incremental as and when we will receive updates on the status . So initially table will look like the below as shown:-
+---------------+---------------+---------------+---------------+
| ID | Total count | Failed count | Success count |
+---------------+---------------+---------------+---------------+
| 1 | 30 | 10 | 20 |
+---------------+---------------+---------------+---------------+
Now let’s assume total 30 messages are pushed now out of which 10 Failed and 20 Success as shown above.Now again application is run and values changed . Now total 20 new records came in out of which all are success. This should be updated in the same row .
+---------------+---------------+---------------+---------------+
| ID | Total count | Failed count | Success count |
+---------------+---------------+---------------+---------------+
| 1 | 50 | 10 | 40 |
+---------------+---------------+---------------+---------------+
Is it feasible in Cassandra DB using Counter data type?
Of course you can use counter tables in your case.
Let's assume table structure like :
CREATE KEYSPACE Test WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 3 };
CREATE TABLE data (
id int,
data string,
PRIMARY KEY (id)
);
CREATE TABLE counters (
id int,
total_count counter,
failed_count counter,
success_coutn counter,
PRIMARY KEY (id)
);
You can increment counters by running queries like :
UPDATE counters
SET total_count = total_count + 1,
success_count = success_count + 1
WHERE id= 1;
Hope this can help you.

How to count the duplicate records in a table in cassandra

Let say my data is like below:
Acct_id | amount
--------|-------
10001 |6.00
20000 |5.00
32356 |1.00
10001 |2.00
45000 |1.50
45000 |10.00
My expected result should be like this:
acct_id| count
-------|-----
10001 | 2
45000 | 2
How do i get it in cassandra?
How do i get it in cassandra?
If you're using Cassandra 2.2.x or 3.x you can create an user defined aggregate
CREATE FUNCTION counByAccId(state map<int, int>, acctid int)
RETURNS NULL ON NULL INPUT
RETURNS map<int, int>
LANGUAGE java
AS '
if(state.containsKey(acctid)) {
Integer currentCount = (Integer)state.get(acctid);
state.put(acctid, currentCount + 1);
} else {
state.put(acctid, 1);
}
return state;
';
CREATE AGGREGATE groupByAcctIdAndCount(int)
SFUNC counByAccId
STYPE map<int, int>
INITCOND {};
SELECT groupByAcctIdAndCount(acct_id) FROM myTable WHERE partition_key = xxx;
Example data set:
select * from agg;
partition_key | acct_id | val
---------------+---------+-----
5 | 45000 | 1.5
1 | 10001 | 6
2 | 20000 | 5
4 | 10001 | 2
6 | 45000 | 10
3 | 32356 | 1
select groupByAcctIdAndCount(acctid) FROM agg;
music.groupbyacctidandcount(acct_id)
------------------------------------------
{10001: 2, 20000: 1, 32356: 1, 45000: 2}
WARNING: be sure to read my blog about UDA and the implication in term of performance when scanning a full table: http://www.doanduyhai.com/blog/?p=2015

Spark: How to join RDDs by time range

I have a delicate Spark problem, where i just can't wrap my head around.
We have two RDDs ( coming from Cassandra ). RDD1 contains Actions and RDD2 contains Historic data. Both have an id on which they can be matched/joined. But the problem is the two tables have an N:N relation ship. Actions contains multiple rows with the same id and so does Historic. Here are some example date from both tables.
Actions time is actually a timestamp
id | time | valueX
1 | 12:05 | 500
1 | 12:30 | 500
2 | 12:30 | 125
Historic set_at is actually a timestamp
id | set_at| valueY
1 | 11:00 | 400
1 | 12:15 | 450
2 | 12:20 | 50
2 | 12:25 | 75
How can we join these two tables in a way, that we get a result like this
1 | 100 # 500 - 400 for Actions#1 with time 12:05 because Historic was in that time at 400
1 | 50 # 500 - 450 for Actions#2 with time 12:30 because H. was in that time at 450
2 | 50 # 125 - 75 for Actions#3 with time 12:30 because H. was in that time at 75
I can't come up with a good solution that feels right, without making a lot of iterations over huge datasets. I always have to think about making a range from the Historic set and then somehow check if the Actions fits in the range e.g (11:00 - 12:15) to make the calculation. But that seems to pretty slow to me. Is there any more efficient way to do that? Seems to me, that this kind of problem could be popular, but i couldn't find any hints on this yet. How would you solve this problem in spark?
My current attempts so far ( in half way done code )
case class Historic(id: String, set_at: Long, valueY: Int)
val historicRDD = sc.cassandraTable[Historic](...)
historicRDD
.map( row => ( row.id, row ) )
.reduceByKey(...)
// transforming to another case which results in something like this; code not finished yet
// (List((Range(0, 12:25), 400), (Range(12:25, NOW), 450)))
// From here we could join with Actions
// And then some .filter maybe to select the right Lists tuple
It's an interesting problem. I also spent some time figuring out an approach. This is what I came up with:
Given case classes for Action(id, time, x) and Historic(id, time, y)
Join the actions with the history (this might be heavy)
filter all historic data not relevant for a given action
key the results by (id,time) - differentiate same key at different times
reduce the history by action to the max value, leaving us with relevant historical record for the given action
In Spark:
val actionById = actions.keyBy(_.id)
val historyById = historic.keyBy(_.id)
val actionByHistory = actionById.join(historyById)
val filteredActionByidTime = actionByHistory.collect{ case (k,(action,historic)) if (action.time>historic.t) => ((action.id, action.time),(action,historic))}
val topHistoricByAction = filteredActionByidTime.reduceByKey{ case ((a1:Action,h1:Historic),(a2:Action, h2:Historic)) => (a1, if (h1.t>h2.t) h1 else h2)}
// we are done, let's produce a report now
val report = topHistoricByAction.map{case ((id,time),(action,historic)) => (id,time,action.X -historic.y)}
Using the data provided above, the report looks like:
report.collect
Array[(Int, Long, Int)] = Array((1,43500,100), (1,45000,50), (2,45000,50))
(I transformed the time to seconds to have a simplistic timestamp)
After a few hours of thinking, trying and failing I came up with this solution. I am not sure if it is any good, but due the lack of other options, this is my solution.
First we expand our case class Historic
case class Historic(id: String, set_at: Long, valueY: Int) {
val set_at_map = new java.util.TreeMap[Long, Int]() // as it seems Scala doesn't provides something like this with similar operations we'll need a few lines later
set_at_map.put(0, valueY) // Means from the beginning of Epoch ...
set_at_map.put(set_at, valueY) // .. to the set_at date
// This is the fun part. With .getHistoricValue we can pass any timestamp and we will get the a value of the key back that contains the passed date. For more information look at this answer: http://stackoverflow.com/a/13400317/1209327
def getHistoricValue(date: Long) : Option[Int] = {
var e = set_at_map.floorEntry(date)
if (e != null && e.getValue == null) {
e = set_at_map.lowerEntry(date)
}
if ( e == null ) None else e.getValue()
}
}
The case class is ready and now we bring it into action
val historicRDD = sc.cassandraTable[Historic](...)
.map( row => ( row.id, row ) )
.reduceByKey( (row1, row2) => {
row1.set_at_map.put(row2.set_at, row2.valueY) // we add the historic Events up to each id
row1
})
// Now we load the Actions and map it by id as we did with Historic
val actionsRDD = sc.cassandraTable[Actions](...)
.map( row => ( row.id, row ) )
// Now both RDDs have the same key and we can join them
val fin = actionsRDD.join(historicRDD)
.map( row => {
( row._1.id,
(
row._2._1.id,
row._2._1.valueX - row._2._2.getHistoricValue(row._2._1.time).get // returns valueY for that timestamp
)
)
})
I am totally new to Scala, so please let me know if we could improve this code on some place.
I know that this question has been answered but I want to add another solution that worked for me -
your data -
Actions
id | time | valueX
1 | 12:05 | 500
1 | 12:30 | 500
2 | 12:30 | 125
Historic
id | set_at| valueY
1 | 11:00 | 400
1 | 12:15 | 450
2 | 12:20 | 50
2 | 12:25 | 75
Union Actions and Historic
Combined
id | time | valueX | record-type
1 | 12:05 | 500 | Action
1 | 12:30 | 500 | Action
2 | 12:30 | 125 | Action
1 | 11:00 | 400 | Historic
1 | 12:15 | 450 | Historic
2 | 12:20 | 50 | Historic
2 | 12:25 | 75 | Historic
Write a custom partitioner and use repartitionAndSortWithinPartitions to partition by id, but sort by time.
Partition-1
1 | 11:00 | 400 | Historic
1 | 12:05 | 500 | Action
1 | 12:15 | 450 | Historic
1 | 12:30 | 500 | Action
Partition-2
2 | 12:20 | 50 | Historic
2 | 12:25 | 75 | Historic
2 | 12:30 | 125 | Action
Traverse through the records per partition.
If it is a Historical record, add it to a map, or update the map if it already has that id - keep track of the latest valueY per id using a map per partition.
If it is a Action record, get the valueY value from the map and subtract it from valueX
A map M
Partition-1 traversal in order
M={ 1 -> 400} // A new entry in map M
1 | 100 // M(1) = 400; 500-400
M={1 -> 450} // update M, because key already exists
1 | 50 // M(1)
Partition-2 traversal in order
M={ 2 -> 50} // A new entry in M
M={ 2 -> 75} // update M, because key already exists
2 | 50 // M(2) = 75; 125-75
You could try to partition and sort by time, but you need to merge the partitions later. And that could add to some complexity.
This, I found it preferable to the many-to-many join that we usually get when using time ranges to join.

Resources