I don't understand why this function won't update the first app and change its lock state to 0 and rest to 1. It should update all apps with pending status.
public function updateLockState( $stu_id )
{
$apps = Application::where('stu_id', $stu_id)->get();
$acceptedApps = $apps->whereIn('status', [ 'ACCEPTED_W_SCHOLARSHIP', 'ACCEPTED_WO_SCHOLARSHIP', 'ACCEPTED_BYSTUDENT', 'ACCEPTED_CONDITIONALLY', 'UNDER_REVIEW', 'REGISTERED' ]);
if ( $acceptedApps->count() == 0 )
{
// This has two apps showing in descending order by rank.
$pending_apps = $apps->where('status', 'PENDING')->sortByDesc('rank');
foreach ($pending_apps as $key => $value)
{
if ( $key == 0 )
{
$value->update(['locked' => 0]);
}
else
{
$value->update(['locked' => 1]);
}
}
}
dd();
}
I have even tried to use DB:table() function for updating apps with id in where clause but it gives same result. This function is executed on the first line of the index function i.e its the first function to run on the page that interacts with apps. It ends with a dd() or exit() which stops anything else to run.
EDIT:
When I manually update rank values in db, I run this script and it should update locks but it is not. It should make lock = 0 for first app and lock = 1 for all other app and since apps are ordered by ranks desc it, app with 18 rank should be on top hence its lock = 0 after update:
sortByDesc() sorts in descending order BUT it keeps the keys in the same order in the new collection that it returns.
So even if its desc order the first item in the collection $key may not always be 0.
Use:
$pending_apps = $apps->where('status', 'PENDING')->sortByDesc('rank')->values()->all();
Which will give you keys starting with 0.
Related
I'm pulling data from a third party api. The api runs multiple times in a day. So, if the same data is present in the table it should ignore that record, else if there are any changes it should update that record or insert a new record if anything new shows up in the json received.
I'm using the below code for inserting any new data.
var input = JsonConvert.DeserializeObject<List<DeserializeLookup>>(resultJson).ToList();
var entryset = input.Select(y => new Lookup
{
lookupType = "JOBCODE",
code = y.Code,
description = y.Description,
isNew = true,
lastUpdatedDate = DateTime.UtcNow
}).ToList();
await _context.Lookup.AddRangeAsync(entryset);
await _context.SaveChangesAsync();
But, after the first run, when the api runs again it's again inserting the same data in the table. As a result, duplicate entries are getting into table. To handle the same, I used a foreach loop as below before inserting data to the table.
foreach (var item in input)
{
if (!_context.Lookup.Any(r =>
r.code== item.Code))
{
//above insert code
}
}
But, the same doesn't work as expected. Also, the api takes a lot of time to run when I put a foreach loop. Is there a solution to this in .net core 3.1
List<DeserializeLookup> newList=new();
foreach (var item in input)
{
if (!_context.Lookup.Any(r =>
r.code== item.Code))
{
newList.add(item);
//above insert code
}
}
await _context.Lookup.AddRangeAsync(newList);
await _context.SaveChangesAsync();
It will be better if you try this way
I’m on my phone so forgive me for not being able to format the code in my response. The solution to your problem is something I actually just encountered myself while syncing data from an azure function and third party app and into a sql database.
Depending on your table schema, you would need one column with a unique identifier. Make this column a primary key (first step to preventing duplicates). Here’s a resource for that: https://www.w3schools.com/sql/sql_primarykey.ASP
The next step you want to take care of is your stored procedure. You’ll need to perform what’s commonly referred to as an UPSERT. To do this you’ll need to merge a table with the incoming data...on a specified column (whichever is your primary key).
That would look something like this:
MERGE
Table_1 AS T1
USING
Incoming_Data AS source
ON
T1.column1 = source.column1
/// you can use an AND / OR operator in here for matching on additional values or combinations
WHEN MATCHED THEN
UPDATE SET T1.column2= source.column2
//// etc for more columns
WHEN NOT MATCHED THEN
INSERT (column1, column2, column3) VALUES (source.column1, source.column2, source.column3);
First of all, you should decouple the format in which you get your data from your actual data handling. In your case: get rid of the JSon before you actually interpret the data.
Alas, I haven't got a clue what your data represents, so Let's assume your data is a sequence of Customer Orders. When you get new data, you want to Add all new orders, and you want to update changed orders.
So somewhere you have a method with input your json data, and as output a sequence of Orders:
IEnumerable<Order> InterpretJsonData(string jsonData)
{
...
}
You know Json better than I do, besides this conversion is a bit beside your question.
You wrote:
So, if the same data is present in the table it should ignore that record, else if there are any changes it should update that record or insert a new record
You need an Equality Comparer
To detect whether there are Added or Changed Customer Orders, you need something to detect whether Order A equals Order B. There must be at least one unique field by which you can identify an Order, even if all other values are of the Order are changed.
This unique value is usually called the primary key, or the Id. I assume your Orders have an Id.
So if your new Order data contains an Id that was not available before, then you are certain that the Order was Added.
If your new Order data has an Id that was already in previously processed Orders, then you have to check the other values to detect whether it was changed.
For this you need Equality comparers: one that says that two Orders are equal if they have the same Id, and one that says checks all values for equality.
A standard pattern is to derive your comparer from class EqualityComparer<Order>
class OrderComparer : EqualityComparer<Order>
{
public static IEqualityComparer<Order> ByValue = new OrderComparer();
... // TODO implement
}
Fist I'll show you how to use this to detect additions and changes, then I'll show you how to implement it.
Somewhere you have access to the already processed Orders:
IEnumerable<Order> GetProcessedOrders() {...}
var jsondata = FetchNewJsonOrderData();
// convert the jsonData into a sequence of Orders
IEnumerable<Order> orders = this.InterpretJsonData(jsondata);
To detect which Orders are added or changed, you could make a Dictonary of the already Processed orders and check the orders one-by-one if they are changed:
IEqualityComparer<Order> comparer = OrderComparer.ByValue;
Dictionary<int, Order> processedOrders = this.GetProcessedOrders()
.ToDictionary(order => order.Id);
foreach (Order order in Orders)
{
if(processedOrders.TryGetValue(order.Id, out Order originalOrder)
{
// order already existed. Is it changed?
if(!comparer.Equals(order, originalOrder))
{
// unequal!
this.ProcessChangedOrder(order);
// remember the changed values of this Order
processedOrder[order.Id] = Order;
}
// else: no changes, nothing to do
}
else
{
// Added!
this.ProcessAddedOrder(order);
processedOrder.Add(order.Id, order);
}
}
Immediately after Processing the changed / added order, I remember the new value, because the same Order might be changed again.
If you want this in a LINQ fashion, you have to GroupJoin the Orders with the ProcessedOrders, to get "Orders with their zero or more Previously processed Orders" (there will probably be zero or one Previously processed order).
var ordersWithTPreviouslyProcessedOrder = orders.GroupJoin(this.GetProcessedOrders(),
order => order.Id, // from every Order take the Id
processedOrder => processedOrder.Id, // from every previously processed Order take the Id
// parameter resultSelector: from every Order, with its zero or more previously
// processed Orders make one new:
(order, previouslyProcessedOrders) => new
{
Order = order,
ProcessedOrder = previouslyProcessedOrders.FirstOrDefault(),
})
.ToList();
I use GroupJoin instead of Join, because this way I also get the "Orders that have no previously processed orders" (= new orders). If you would use a simple Join, you would not get them.
I do a ToList, so that in the next statements the group join is not done twice:
var addedOrders = ordersWithTPreviouslyProcessedOrder
.Where(orderCombi => orderCombi.ProcessedOrder == null);
var changedOrders = ordersWithTPreviouslyProcessedOrder
.Where(orderCombi => !comparer.Equals(orderCombi.Order, orderCombi.PreviousOrder);
Implementation of "Compare by Value"
// equal if all values equal
protected override bool Equals(bool x, bool y)
{
if (x == null) return y == null; // true if both null, false if x null but y not null
if (y == null) return false; // because x not null
if (Object.ReferenceEquals(x, y) return true;
if (x.GetType() != y.GetType()) return false;
// compare all properties one by one:
return x.Id == y.Id
&& x.Date == y.Date
&& ...
}
For GetHashCode is one rule: if X equals Y then they must have the same hash code. If not equal, then there is no rule, but it is more efficient for lookups if they have different hash codes. Make a tradeoff between calculation speed and hash code uniqueness.
In this case: If two Orders are equal, then I am certain that they have the same Id. For speed I don't check the other properties.
protected override int GetHashCode(Order x)
{
if (x == null)
return 34339d98; // just a hash code for all null Orders
else
return x.Id.GetHashCode();
}
I am struggling to get Laravel Eloquent to retrieve and group results in the way that i'd like.
Basically I am creating a 'My Agenda' page which shows all task in order of their due date, but grouped together if 2 or more Tasks (in order) belong to the same Stage, and likewise if 2 or more stages belong to the same project.
My data is Projects -> (has many) Stages -> (has many) Tasks
I would like to output my data as follows:
Project B
Stage 2
Task 1 (due 1st Sep)
Task 3 (due 2nd Sep)
Stage 1
Task 2 (due 3rd Sep)
Project A
Stage 1
Task 2 (due 4th Sep)
Project B <---- repeated as Project A has a stage->task due before these tasks due
Stage 3
Task 2 (due 5th Sep)
Project A <---- repeated as Project B has a stage->task due before
Stage 1 <---- repeated
Task 1 (due 6th Sep)
Any ideas how I can achieve this? I am open to doing this on the front end with JS/Vue/Lodash.
Thanks in advance!
M
I think you can do it this way:
First, let's combine all the tables with JOIN. If you want to see all projects and stages that don't have any relational data, you can use LEFT JOIN, or maybe RIGHT JOIN, I don't know which one will work.
$tasks = Task::orderBy("due")
->join("stages", "stages.id", "=", task.stage_id)
->join("projects", "projects.id", "=", stages.project_id)
->select("pick the columns you want to have")
->get();
I think you should aim for this type of array as your output, so you won't have any issues because of the repeated key names.
/*
$output = [
[
'project'=> A,
'stages'=> [
stage_name => [task 1, task 2],
stage_name => [task 4, task 8],
],
],
[
'project'=> B,
'stages'=> [
stage_name => [task 5],
],
],
[...]
];
*/
To create that type of array, the function down below should work.
$output = [];
foreach($tasks => $task) {
$project = $task['project_name'];
$lastEntry = $output[count($output) - 1];
if ( count($output) > 0 && $lastEntry['project'] == $project) {
// this means $task should be inserted in the last array.
// You should check for stages.
if (array_key_exists($task['stage_name'], $lastEntry['stages'])) {
$lastEntry['stages'][$task['stage_name']][] = $task;
} else {
$lastEntry['stages'][$task['stage_name']] = [$task];
}
// I think $lastEntry['stages'][$task['stage_name']][] = $task; will work without checking stage names, but I can't be sure, you need to try it.
} else {
// This means you should create a new item in $output.
$output[] = [
'project' => name,
'stages' => [
'stage_name' => [$task];
]
]
}
}
I created those codes here directly. There can be typos and everything, but the logic should work.
I have the table products with the following structure.
id | name | promote
Where the column promote is of boolean type.
I want to set the value of the boolean column to 1 with the selected rows and set 0 to non-selected rows. I have the following code in the controller to handle this query.
$yes = Tour::whereIn('id', $request->promote)->get();
$no = Tour::whereNotIn('id', $request->promote)->get();
foreach ($yes as $item) {
$item->promote = 1;
$item->save();
}
foreach ($no as $item) {
$item->promote = 0;
$item->save();
}
I get following from the form request.
The above code does work but it isn't very efficient I assume. I'm looking for optional ways to achieve the result in a more efficient way.
Instead retrieving result, looping through, you can update directly,
$yes = Tour::whereIn('id', $request->promote)->update(['promote' => 1]);
$no = Tour::whereNotIn('id', $request->promote)->update(['promote' => 0]);
If you don't care about going through the Model to do the updating you can call update on the builder to update all the matched records. As this will use the builder and not the Model there will not be any model events fired:
// set them all to promote = 0
Tour::update(['promote' => 0]);
// or just set the ones that need to be 0
Tour::whereNotIn('id', $request->promote)->update(['promote' => 0]);
// set the ones you want to promote = 1
Tour::whereIn('id', $request->promote)->update(['promote' => 1]);
Just one way to give it a go.
In first function of my controller, I am fetching random records from mysql table using CI active record
$query = $this->db->query("SELECT DISTINCT * FROM questions WHERE `level` = '1' ORDER BY RAND() limit 0,5");
$result = $query->result_array();
and saving result in session as
// saving questions id in session
for($i = 0; $i < count($result); $i++)
{
$session['questionsId'][] = $result[$i]['qId'];
}
$this->session->set_userdata($session);
and if print session variable it shows output like:
$qIds_o = $this->session->userdata('questionsId');
var_debug($qIds_o);
Array
(
[0] => 5
[1] => 9
[2] => 3
[3] => 6
[4] => 11
)
but if I retrieve same session in another function of same controller it shows different result
$qIds = $this->session->userdata('questionsId');
var_debug($qIds);
Array
(
[0] => 2
[1] => 8
[2] => 6
[3] => 3
[4] => 5
)
and if I remove ORDER BY RAND() from mysql query like:
$this->db->query("SELECT DISTINCT * FROM questions WHERE `level` = '1' limit 0,5");
it shows same session array in both functions. Very strange.
Please guide what is going wrong....
Here is my controller script:
public function set_value(){
$query = $this->db->query("SELECT DISTINCT * FROM questions WHERE `level` = '1' ORDER BY RAND() limit 0,5");
$result = $query->result_array();
// saving questions id in session
for($i = 0; $i < count($result); $i++)
{
$session['questionsId'][] = $result[$i]['qId'];
}
$this->session->set_userdata($session);
$qIds_o = $this->session->userdata('questionsId');
var_debug($qIds_o);
}
public function get_value(){
$qIds = $this->session->userdata('questionsId');
var_debug($qIds);
}
I called set_value() on page load while once the page loaded I call get_value() using AJAX post which simply hits my_controller/get_value/ and response back to browser.
Without looking at your controller (let's call it my_controller in detail, I think what might be happening is:
(1) You call the first function, my_controller/set_value where set_value sets the session variable and you echo the result.
(2) You then call the second function, show_value that simply echos out the session variable.
What you might be doing in set_value is:
1) echo out the current session variable
2) call the query and re-set the session variable
If this is the case, then when you go to show_value (2nd function) you are looking at the recently re-set value instead of the prior value that you echoed out in the first function.
I have two questions about this sentence:
but if I retrieve same session in another function of same controller it shows different result
Is this on a new page load?
If so, is the query run again?
I'm going to assume the answer to both of these questions is yes, since you said removing RAND() gives you the same results.
You are using a combination of RAND() and LIMIT in your query, meaning you want only five rows in a random order. That means that each time the query is run (and your session data is set), it is very likely that the results will be different.
I don't know exactly what you're doing with these IDs and what sort of data set you need, so this may not be 100% perfect for your solution, but if you only need to set this session data once, you should check if it exists before running the query.
if ($this->session->userdata('questionsId') === FALSE)
{
// Run your query and set your session data here.
// Note that in CI 3.0, Session::userdata()
// will return NULL if empty, not a boolean.
}
I want a list of counts for some of my data (count the number of open.closed tasks etc), I want to get all counts inside 1 query, so I am not sure what I do with my linq statement below...
_user is an object that returns info about the current loggedon user
_repo is am object that returns an IQueryable of whichever table I want to select
var counters = (from task in _repo.All<InstructionTask>()
where task.AssignedToCompanyID == _user.CompanyID || task.CompanyID == _user.CompanyID
join instructions in _repo.GetAllMyInstructions(_user) on task.InstructionID equals
instructions.InstructionID
group new {task, instructions}
by new
{
task
}
into g
select new
{
TotalEveryone = g.Count(),
TotalMine = g.Count(),
TotalOpen = g.Count(x => x.task.IsOpen),
TotalClosed = g.Count(c => !c.task.IsOpen)
}).SingleOrDefault();
Do I convert my object to single or default? The exception I am getting is, this sequence contains more than one element
Note: I want overall stats, not for each task, but for all tasks - not sure how to get that?
You need to dump everything into a single group, and use a regular Single. I am not sure if LINQ-to-SQL would be able to translate it correctly, but it's definitely worth a try.
var counters = (from task in _repo.All<InstructionTask>()
where task.AssignedToCompanyID == _user.CompanyID || task.CompanyID == _user.CompanyID
join instructions in _repo.GetAllMyInstructions(_user) on task.InstructionID == instructions.InstructionID
group task by 1 /* <<=== All tasks go into one group */ into g select new {
TotalEveryone = task.Count(),
TotalMine = task.Count(), // <<=== You probably need a condition here
TotalOpen = task.Count(x => x.task.IsOpen),
TotalClosed = task.Count(c => !c.task.IsOpen)
}).Single();
From MSDN
Returns the only element of a sequence, or a default value if the
sequence is empty; this method throws an exception if there is more
than one element in the sequence.
You need to use FirstOrDefault. SingleOrDefault is designed for collections that contains exactly 1 element (or none).