Laravel 7 query relationships tables - sql

I need a bit of help with getting data from tables that are in relationship.
I have 2 tables: cards and statuses:
I have following data in cards table:
id_card | card_type
--------|----------
123 | tag
281 | card
455 | card
721 | tag
In statuses table I have:
id_status | id_card | status_date | status_type | status_company | status_user
----------|---------|-------------|-------------|----------------|------------
1 | 123 | 2018-12-04 | available | |
2 | 281 | 2018-12-04 | available | |
3 | 455 | 2018-12-04 | available | |
4 | 721 | 2019-03-26 | available | |
5 | 281 | 2020-01-25 | issued | Company A | User One
6 | 123 | 2020-01-10 | issued | Company B | User Two
7 | 281 | 2020-01-25 | available | |
8 | 123 | 2020-02-02 | lost | |
9 | 455 | 2020-02-14 | issued | Company C | Third User
For table cards I have following in Card model:
public function Status() {
return $this->hasMany('App\Status', 'id_card', 'id_card')
->orderBy('status_date', 'desc');
}
And for statuses table I have in Status model:
public function Card() {
return $this->hasOne('App\Card', 'id_card', 'id_card')
}
Now, how should I write a query, that would return me list of cards together with values from statuses table, but only with latest status for each card, like this:
id_card | card_type | status_type | status_date | status_company | status_user
--------|-----------|-------------|-------------|----------------|------------
123 | tag | lost | 2020-02-02 | |
281 | card | available | 2020-01-25 | |
455 | card | issued | 2020-02-14 | Company C | Third User
721 | tag | available | 2019-03-26 |
And is there a way, to also filter such a query i.e. to get only available cards:
id_card | card_type | status_type | status_date | status_company | status_user
--------|-----------|-------------|-------------|----------------|------------
281 | card | available | 2020-01-25 | |
721 | tag | available | 2019-03-26 | |
Thank you very much in advance :)

For getting all the cards having status_type 'available'
Card::join('statuses', 'cards.id_card', '=', 'statuses.id_card')
->where('statuses.status_type', 'available')
->select('cards.id_card', 'cards.card_type', 'statuses.status_type', 'statuses.status_date', 'statuses.status_company', 'statuses.status_user')
->get();
For getting latest status you can follow these steps,
Add following method in Card model
public function latestStatus()
{
return $this->hasOne('\App\Status')->latest();
}
Method latest() orders all rows by created_at desc, and takes the first one. If you don't have created_at column you can add it in statuses table.
$cards = Card::with('latestStatus')->get();
foreach ($cards as $card) {
echo $card->type . ' has latest status of ' . $card->latestStatus->status_type;
}
last but not the least, I think if you follow laravel conventions then you'll already have half of your problem solved.

Laravel is a framework that works better for you if you follow some conventions. The database models standards in Laravel in your case for Card and Status would be:
Then, you could simply have:
Status.php
use App\Card;
class Status extends Model
{
public function card()
{
return $this->hasOne(Card::class);
}
}
Card.php
use App\Status;
class Card extends Model
{
public function statuses()
{
return $this->hasMany(Status::class);
}
}
Finally, you could do:
$cards = App\Card::with(['statuses' => function($q){
$q->latest()->take(1);
}])->get();
Laravel will give out of the box a collection of Cards, with the last status attached.
To filter results based in related Status you could do:
$filter = 'available';
$cards = App\Card::with(['statuses' => function($q) use($filter) {
$q->where('status_type', $filter)->latest()->take(1);
}])->get();
Hope this helps to guide you in the right direction!

Firstly since you have a hasMany relationship, the naming should be plural.
public function Statuses() {
Defining an Accessor can help you on your status problem. In your Card.php model define it.
public function getStatusAttribute()
{
return $this->statuses->first();
}
Using the appends property on your Card.php model, to tell which properties should be serialized.
protected $appends = ['status'];
This will result in your model looking like so, when it is transformed to JSON.
{
"card_type": "tag",
"status": {
"id_card": 1,
"status_date": "2020-02-02",
"status_company": "Company C",
"status_user": "Third User"
}
}
If you are using it, in a Blade or PHP -context you can access like so.
$card->status; //your first status
For your query needs a simply where() clause combined with joining the table. For performance it would be smart to eager load statuses as you will access the first status, with() can be used for this.
Card::leftJoin('statuses', 'cards.id_card', 'statuses.id_card')
->where('statuses.status_type', 'available')
->select('cards.*')
->with('statuses')
->get();
Your naming of the columns is inconsistent with the Laravel naming convention and your data structure seems a little bit off, but this solves your problem, i would consider to rethink it.

Related

PowerBI / SQL Query to verify records

I am working on a PowerBI report that is grabbing information from SQL and I cannot find a way to solve my problem using PowerBI or how to write the required code. My first table, Certifications, includes a list of certifications and required trainings that must be obtained in order to have an active certification.
My second table, UserCertifications, includes a list of UserIDs, certifications, and the trainings associated with a certification.
How can I write a SQL code or PowerBI measure to tell if a user has all required trainings for a certification? ie, if UserID 1 has the A certification, how can I verify that they have the TrainingIDs of 1, 10, and 150 associated with it?
Certifications:
CertificationsTable
UserCertifications:
UserCertificationsTable
This is a DAX pattern to test if contains at least some values.
| Certifications |
|----------------|------------|
| Certification | TrainingID |
|----------------|------------|
| A | 1 |
| A | 10 |
| A | 150 |
| B | 7 |
| B | 9 |
| UserCertifications |
|--------------------|---------------|----------|
| UserID | Certification | Training |
|--------------------|---------------|----------|
| 1 | A | 1 |
| 1 | A | 10 |
| 1 | A | 300 |
| 2 | A | 150 |
| 2 | B | 9 |
| 2 | B | 90 |
| 3 | A | 7 |
| 4 | A | 1 |
| 4 | A | 10 |
| 4 | A | 150 |
| 4 | A | 1000 |
In the above scenario, DAX needs to find out if the mandatory trainings (Certifications[TrainingID]) by Certifications[Certification] is completed by
UserCertifications[UserID ]&&UserCertifications[Certifications] partition.
In the above scenario, DAX should only return true for UserCertifications[UserID ]=4 as it is the only User that completed at least all the mandatory trainings.
The way to achieve this is through the following measure
areAllMandatoryTrainingCompleted =
VAR _alreadyCompleted =
CONCATENATEX (
UserCertifications,
UserCertifications[Training],
"-",
UserCertifications[Training]
) // what is completed in the fact Table; the fourth argument is very important as it decides the sort order
VAR _0 =
MAX ( UserCertifications[Certification] )
VAR _supposedToComplete =
CONCATENATEX (
FILTER ( Certifications, Certifications[Certification] = _0 ),
Certifications[TrainingID],
"-",
Certifications[TrainingID]
) // what is comeleted in the training Table; the fourth argument is very important as it decides the sort order
VAR _isMandatoryTrainingCompleted =
CONTAINSSTRING ( _alreadyCompleted, _supposedToComplete ) // CONTAINSSTRING (<Within Text>,<Search Text>); return true false
RETURN
_isMandatoryTrainingCompleted

Using Snowflake SQL how do you find two records and then change another one based on those records to a predefined record using a local variable?

Using SQL how do you use two records to find a place, hold onto that place and use that record to replace 'Nonsense' value with that held onto place? I am going to show what I have been able to write so far, but then write out what I am still trying to figure out:
SELECT * FROM "TABLES". "ACCTS_OF_SUPERHEROS".;
DECLARE #count_rows INT = 0;
DECLARE #row_total INT = 0;
DECLARE #refAcctNum INT = 0;
DECLARE #selectedPlaceName TINYTEXT;
SET #row_total = SELECT COUNT (*)
WHILE countRows < row_total
for each acct_num store value in refAcctNum.
Using refAcctNum find place: "Gotham City", "Central City", "Metropolis", "Smallville", "Star City", "Fawcett City" store that in selectedPlaceName.
If refAccountNumber has Nonsense then replace with selectedPlaceName record
otherwise add + 1 to countRows and repeat.
END
Current table data; "ACCTS_OF_SUPERHEROS" table:
| row | acct_num | exact_address | place
| --- | -------- |------------------|--------
| 1 | 049403 | 344 Clinton Str | Metropolis
| 2 | 049403 | 344 Clinton Str | Nonsense
| 3 | 049206 | 1007 Mountain Dr | Gotham City
| 4 | 049206 | 1007 Mountain Dr | Gotham City
| 5 | 049206 | 1096 Show Dr. | Fawcett City
| 6 | 049206 | 1096 Show Dr. | Nonsense
| 7 | 049206 | NULL | Nonsense
| 8 | 049291 | 1938 Sullivan Pl | Smallville
| 9 | 049293 | 700 Hamilton Str | Central City
| 10 | 049396 | 800 Nonsense Way | Nonsense
| 11 | 049396 | NULL | Nonsense
Desired output:
| row | acct_num | exact_address | place
| --- | -------- |------------------|--------
| 1 | 049403 | 344 Clinton Str | Metropolis
| 2 | 049403 | 344 Clinton Str | Metropolis
| 3 | 049206 | 1007 Mountain Dr | Gotham City
| 4 | 049206 | 1007 Mountain Dr | Gotham City
| 5 | 049206 | 1096 Show Dr. | Fawcett City
| 6 | 049206 | 1096 Show Dr. | Fawcett City
| 7 | 049206 | NULL | Fawcett City
| 8 | 049291 | 1938 Sullivan Pl | Smallville
| 9 | 049293 | 700 Hamilton Str | Central City
| 10 | 049396 | 800 Tidal Way | Star City
| 11 | 049396 | NULL | Star City
You can use window functions:
select t.*,
max(case when place <> 'Nonsense' then place end) over (partition by acct_num) as imputed_place
from t;
This returns NULL if all the rows are 'Nonsense' for a given acct_num. You can use COALESCE() to replace the value with something else.
I was reading through the available list of window functions in Snowflake and think you're going to need a new window function for this. Perhaps someone can find a more built-in way, but anyway here's a user defined table function REPLACE_WITH_LKG implemented as a window function that will replace a bad value with the last known good value. As long as I was going to write it, I thought it may as well be general purpose, so it matches "bad" values using a regular expression and JavaScript RegExp options.
create or replace function REPLACE_WITH_LKG("VALUE" string, "REGEXP" string, "REGEXP_OPTIONS" string)
returns table(LKG_VALUE string)
language javascript
strict immutable
as
$$
{
initialize: function (argumentInfo, context) {
this.lkg = "";
},
processRow: function (row, rowWriter, context) {
const rx = new RegExp(row.REGEXP, row.REGEXP_OPTIONS);
if (!rx.test(row.VALUE)) {
this.lkg = row.VALUE;
}
rowWriter.writeRow({LKG_VALUE: this.lkg});
},
finalize: function (rowWriter, context) {},
}
$$;
select S.*, LKG.LKG_VALUE as PLACE
from superhero S, table(REPLACE_WITH_LKG(PLACE, 'Nonsense', 'ig')
over(partition by null order by "ROW")) LKG;
;
A note on performance; the way the data shows this the're no partition other than the entire table. That's because the one obvious place to partition, by account, won't work. Row 10 is getting its value from what would be a different window if using account, so the way the sample data appears it needs to be a window that spans the entire table. This will not parallelize well and should be avoided for very large tables.

NoSQL to SQL adapter design pattern

I have 2 existing applications that I'd like to bridge somehow. Both have similar domains (Product Catalogs), but the first application uses a NoSQL document store for records, and the 2nd application uses SQL tables.
An example record from the first table looks something like:
{
"productId": 123,
"sku": "abc",
"packageSizes": {
"container": 20,
"pallet": 50
}
}
Whereas the same item in the 2nd domain would be 1 row in the ProductItem table:
| id | sku |
| 123 | abc |
Then 2 rows in the ProductPackageSizes table:
| productId | type | size |
| 123 | container | 20 |
| 123 | pallet | 50 |
The systems currently are completely independent, but I'd like it so that whenever a record is created in the NoSQL application to have the same item created in the SQL application.
I can write a one off script for this, that just creates it procedurally based on what the data looks like currently. However, I would be interested to know if there are any established design patterns to describe such transformations? Particularly if there are new packageSizes or other relations added in the future.
You do not have any foreign keys in the sql database?
you can use a foreign key in ProductPackageSizes table that related with ProductItem and next in the every time that you want create a new record in the Nosql database you can assign
"productId": 123,"sku": "abc"
in the ProductItem , like :
| id | sku |
| 123 | abc |
and assign
"packageSizes": {
"container": 20,
"pallet": 50 }
to ProductPackageSizes with a foreign key (like P_id) , like :
| productId | type | size |P_id
| 123 | container | 20 |123
| 123 | pallet | 50 |123
Hope this helps :)

Get last price by user role?

How do I get the last price claimed by the retailer b#gmail.com (the 5th row in the price table) with the condition that the role = 'retailer' ?
So here is my simple table:
table users (using Entrust Role package with the default relationship in the model)
__________________________
| id | email | password |
|-------------------------|
| 1 | a#g.com | 123 |
| 2 | b#g.com | 123 |
| 3 c#g.com | 123 |
| 4 d#g.com | 123 |
--------------------------
table roles (using Entrust Role package with the default relationship in the model)
______________
|id | name |
|--------------|
|1 | customer |
|2 | retailer |
----------------
table role_user (using Entrust Role package with the default relationship in the model)
__________________
|id_user | id_role|
|------------------|
| 1 | 1 | -> a#gmail.com is a customer
| 2 | 2 | -> b#gmail.com is a retailer
| 3 | 1 | -> c#gmail.com is a customer
| 4 | 1 | -> d#gmail.com is a customer
------------------
Here is the tricky part how to query the price:
I have the following prices table (Users can post 1 or more prices. Look at the relationship below):
____________________
|id| user_id| price |
|--------------------|
|1 | 1 | 10.00 | -> price claimed by a customer a#gmail.com
|2 | 2 | 5.00 | -> price claimed by a retailer b#gmail.com
|3 | 1 | 6.00 | -> price claimed by a previous customer a#gmail.com
|4 | 3 | 5.00 | -> price claimed by a customer c#gmail.com
|5 | 2 | 7.00 | -> price claimed by a previous retailer b#gmail.com //How to get this one? This is the last price claimed by the retailer.
|6 | 3 | 8.00 | -> price claim by a customer c#gmail.com
---------------------
The relationship in my Price model:
class Price extends Model{
public function product()
{
return $this->belongsTo('App\Product');
}
public function user()
{
return $this->belongsTo('App\User');
}
How do I get the last price claimed by the retailer b#gmail.com (the 5th row in the price table) with the condition that the role = 'retailer' ?
The purpose is to get the last price that the retailer has claimed.
Update my question:
I want to access the price claimed by the last retailer from my Product model using the $products variable.
A sample table product that I have:
_______________________________
|id | user_id| name |
|------------------------------
| 1 | 1 | Milk |
| 2 | 2 | Phone |
| 3 | 1 | computer |
| 4 | 1 | Banana |
------------------------------
My Product.php model relationship:
class Product extends Model{
public function prices()
{
return $this->hasMany('App\Price');
}
}
So, in my ProductController.php I send the $product variable to the view like this:
class ProductController extends Controller
{
public function show($id)
{
$product = Product::where('id', '=', $id)->
return view('products.show')->with('product', $product);
}
}
and in my view show.blade.php, I loop through the $product variables and I can display the prices claimed for the product.
#foreach($product->prices as $price)
<li>Price claimed: {{$price->price. " " }} </li>
#endforeach
I want to get like something like
$price_last = $product->prices()->where(role, 'retailer')->last().
dd($price_last);
The last() function there is the last price that the retailer claimed but this code is just example. How do I achieve this?
Please let me know if you need more information.
You're looking for the whereHas method:
$query = Price::latest('id')->whereHas('user', function ($query) {
$query->whereHas('role', function ($query) {
$query->where('name', 'retailer');
});
});
$price = $query->value('price');
This assumes you've set up the relationships between your User, Role and Price models.

Relative incremental ID by reference field

I have a table to store reservations for certain events; relevant part of it is:
class Reservation(models.Model):
# django creates an auto-increment field "id" by default
event = models.ForeignKey(Event)
# Some other reservation-specific fields..
first_name = models.CharField(max_length=255)
Now, I wish to retrieve the sequential ID of a given reservation relative to reservations for the same event.
Disclaimer: Of course, we assume reservations are never deleted, or their relative position might change.
Example:
+----+-------+------------+--------+
| ID | Event | First name | Rel.ID |
+----+-------+------------+--------+
| 1 | 1 | AAA | 1 |
| 2 | 1 | BBB | 2 |
| 3 | 2 | CCC | 1 |
| 4 | 2 | DDD | 2 |
| 5 | 1 | EEE | 3 |
| 6 | 3 | FFF | 1 |
| 7 | 1 | GGG | 4 |
| 8 | 1 | HHH | 5 |
+----+-------+------------+--------+
The last column is the "Relative ID", that is, a sequential number, with no gaps, for all reservations of the same event.
Now, what's the best way to accomplish this, without having to manually calculate relative id for each import (I don't like that)? I'm using postgresql as underlying database, but I'd prefer to stick with django abstraction layer in order to keep this portable (i.e. no database-specific solutions, such as triggers etc.).
Filtering using Reservation.objects.filter(event_id = some_event_id) should suffice. This will give you a QuerySet that should have the same ordering each time. Or am I missing something in your question?
I hate always being the one that responds its own questions, but I solved using this:
class Reservation(models.Model):
# ...
def relative_id(self):
return self.id - Reservation.objects.filter(id__lt=self.id).filter(~Q(event=self.event)).all().count()
Assuming records from reservations are never deleted, we can safely assume the "relative id" is the incremental id - (count of reservations before this one not belonging to same event).
I'm thinking of any drawbacks, but I didn't find any.