How to use model CRUD properly? - php

I have one database table images and second one objects. images table is having object_id primary key related to objects.id field.
Now after a POST I am selecting images by posted ID's, loop through results and select appropriate objects by primary key:
// 1 query
$images = Model_Image::find(array('where' => array('approved' => 0, array('object_id', 'in', Input::post('objects'))));
foreach($images as $image)
{
// now get it's object
// 2 queries
$obj = Model_Object::find_by_pk($image->object_id);
$obj->image = 1;
// 3 queries
$obj->save();
}
The thing is, I'm wondering, how should I work this out properly - I think using CRUD is not always a good idea and might affect performance even though it looks pretty and feels good to use it. But is it really worth to sacrifice performance? Lets say, we have 10 images to edit, so that would generate 11 queries to perform a simple action.
I mean, I could simply use the following query to do it all...
UPDATE `objects` SET `image` = 1
LEFT JOIN `images` ON(`image`.`object_id` = `object.id`)
WHERE `images`.`approved` = 0 AND `images`.`id` IN ([post ids]);
So is it worth to still use CRUD in this case?

Related

How to limit the number of page results based on one table in INNER JOIN SQL query or in php?

I am trying to get 5 questions per page with answers (one to many relational for questions and answers table) but, i am getting the number of records per page for this join table, is there anyway to limit the results based on questions table for pagination.
<?php
$topic_id = $_GET['topic_id'];
$answers_data = [];
$questions_data = [];
if (isset($_GET["page"])) { $page = $_GET["page"]; } else { $page=1; };
$num_rec_from_page = 5;
$start_from = ($page-1) * $num_rec_per_page;
$sql = "SELECT questions.q_id,questions.question,answers.answers,answers.answer_id FROM questions INNER JOIN answers ON questions.q_id = answers.q_id WHERE topic_id='$topic_id' LIMIT $start_from, $num_rec_from_page";
$result = $connection->query($sql);
while($row = mysqli_fetch_assoc($result)) {
$data[] = $row;
}//While loop
foreach($data as $key => $item) {
$answers_data[$item['q_id']][$item['answer_id']] = $item['answers'];
}
foreach($data as $key => $item) {
$questions_data[$item['q_id']] = $item['question'];
}
?>
I am get results for above query data using 2 for-each loops as below.
<?php
$question_count= 0;
foreach ($answers_data as $question_id => $answers_array) {
$question_count++;
$q_above_class = "<div class='uk-card-default uk-margin-bottom'><div class='uk-padding-small'><span class='uk-article-meta'>Question :".$question_count."</span><p>";
$q_below_class = "</p></span><div class='uk-padding-small'>";
echo $q_above_class.$questions_data[$question_id].$q_below_class;
$answer_count = 0;
foreach($answers_array as $key => $answer_options) {
$answer_count++;
$answer_options = strip_tags($answer_options, '<img>');
$ans_above_class="<a class='ansck'><p class='bdr_lite uk-padding-small'><span class='circle'>".$answer_count."</span>";
$ans_below_class = "</p></a>";
echo $ans_above_class.$answer_options.$ans_below_class;
}
echo "</div></div></div>";
}
?>
Is there any idea, how can i limit the results per page, based on questions table.
something like this
SELECT
q.q_id,
q.question,
a.answers,
a.answer_id
FROM
(
SELECT
q_id, question
FROM
questions
WHERE
topic_id=:topic_id
LIMIT
$start_from, $num_rec_from_page
) AS q
JOIN
answers AS a ON q.q_id = a.question_id
A few questions/thoughts/notes.
you had question.q_id and question.question_id which seems like an error. So I just went with q_id the other one is more typing (which I don't like) I had a 50-50 chance I figured... so
you had just topic_id so I can't be sure what table it's from, I'm assuming it's from table "question"? It makes a big difference as we really need the where condition on the sub-query where the limit is.
Inner Join, is the same thing as a Join, so I just put Join because I'm lazy. I found this previous post (click here) on SO that talks about it
:topic_id I parameterized your query, I don't do variable concatenation and SQLInjection vulnerability stuff. (aka. please use prepared statements) Named placeholders are for PDO, that's what I like using, you can pretty much just replace it with a ? for mysqli
as I said with INNER JOIN, I'm lazy so I like aliasing my tables with just 1 character, so that was what I did. ( I think you don't even need the AS part, but I'm not "that" lazy). Sometimes I have to use 2, which really irritates me, but whatever
With a sub-query, you can just limit the rows from that table, then you join the results of that query back to the main query like normal. This way you pull 5 or what have you from question table, and then {n} rows from answer based only on the join to the results of the inner query.
Cant really test it, but in theory it should work. You'll have to go though the results, and group them by question. Because you will get {n} rows that have the same 5 questions joined in them. With PDO, you could do PDO::FETCH_GROUP I don't think Mysqli has an equivalent method so you'll have to do it manually, but it's pretty trivial.
UPDATE
Here is a DB fiddle I put to gather you can see it does exactly what you need it to
https://www.db-fiddle.com/f/393uFotgJVPYxVgdF2Gy2V/3
Also I put a non-subquery below it to show the difference.
As for things like small syntax errors and table/column names, well I don't have access to your DB, you're going to have to put some effort in to adapt it to your setup. The only information I have is what you put in the question and so your question had the wrong table/column name. I already pointed several of these issues out before. I'm not saying that to be mean or condescending, it's just a blunt fact.
UPDATE1
Based on you comment. in 1st query the question is redundant
This is just the way the database works, To explain it is very simple, in my example I have 5 questions that match with 8 answers. In any database (not including NoSQL like mongoDB) you can't realistically nest data. In other words you cant pull it like this.
question1
answer1
answer2
answer3
You have to pull it flat, and the way that happens is like this
question1 answer1
question1 answer2
question1 answer3
This is just a natural consequence of how the Database works when joining data. Now that that is out of the way, what do we do about it. Because we want the data nested like in the first example.
We can pull the question, iterate (loop) over the result and do a query for each question and add the data to a sub-element.
Advantage It's easy to do
Disadvantage While this works it's undesirable because we will be making 6 connections to the database (1 to get 5 questions, 1 to get answers for each of the 5 questions), it requires 2 while loops to process the results, and actually more code.
Psudo code instructions (i don't feel like coding this)
init data variable
query for our 5 questions
while each questions as question
- add question to data
- query for answers that belong to question
- while each answers as answer
-- add answer to nested array in data[question]
return data
We can process the results and build the structure we want.
Advantage We can pull the data in one request
Disadvantage we have to write some code, in #1. we still have to write code, and in fact we have to write more code, because we have to process the DB results 6x (2 while loop) here we need 1 while loop.
Psudo code instructions (for comparison)
init data variable
query for our 5 questions and their answers
while each questions&answers as row
- check if question is in data
-- if no, add question with a key we can match to it
- remove data specific to question (redundant data)
- add answers to data[question]
return data
As you can see the basic instructions for the second one are no more complex then the first (same number of instruction). This is just assuming each line has the same complexity. Obviously a SQL query or a while loop is more complex then an if condition. You'll see below how I convert this psudo code to real code. I actually often write psudo code when planing a project.
Anyway, this is what we need to do. (using the previous SQL or the first one in the fiddle). Here is your normal "standard" loop to pull data from the DB
$data = [];
while($row = mysqli_fetch_assoc($result)) {
$data[] = $row;
}//While loop
We will modify this just a bit (it's very easy)
//query for our 5 questions and their answers(using SQL explained above)
//init data variable
$data = [];
//while each questions&answers as row
while($row = mysqli_fetch_assoc($result)) {
// - create a key based of the question id, for convenience
$key = 'question_'.$row['q_id'];
// - check if question is in data
if(!isset( $data[$key] ) ){
//--if no, add question with a key we can match to it
$data[$key] = [
'q_id' => $row['q_id'],
'question' => $row['question'],
'children' => [] //you can call this whatever you want, i choose "children" because there is a field named "answers"
];
}
//- remove data specific to question (redundant data) [optional]
unset($data['q_id'], $data['question']);
//- add answers to data[question]
$data[$key]['answers'][] = $row;
}
//return data
So what does this look like: For the first while, the standard one, we get this with as you called it redundant data.
[
["q_id" => "4", "question" => "four", "answers"=>"4", "answer_id"=>"4"],
["q_id" => "5", "question" => "five", "answers"=>"5", "answer_id"=>"5"],
["q_id" => "5", "question" => "five", "answers"=>"5", "answer_id"=>"6"],
]
For the second one, with our harder code (that's not really hard) we get this:
[
["q_id" => "4","question" => "four","children" = ["answers"=>"4","answer_id"=>"4"]],
[
"q_id" => "5",
"question" => "five",
"children" = [
"answers"=>"5",
"answer_id"=>"5"
],[
"answers"=>"5",
"answer_id"=>"6"
]
],
]
I expanded the second question so you can see the nesting. This is also a good example of why it had redundant data, and what is happening in general. As you can see there is no way to represent 8 rows with 5 shared question without have some redundant data (without nesting them).
The last thing I would like to mention is my choice of $key. We could have used just q_id with no question_ bit added on. I do this for 2 reasons.
It's easier to read when printing it out.
There are several array_* (and other) functions in PHP that will reset numeric keys. Because we are storing important data here, we don't want to lose that information. The way to do this is to use strings. You can just cast it to a string (int)$row['q_id'], but in some cases the keys can still get removed. An example is when JSON encoding, there is a flag JSON_FORCE_OBJECT that forces numeric keys to be an object {"0":"value} but it acts global. In any case it can happen where you lose the keys if they are just numbers, and I don't much care for that happening. So I prefix them to prevent that.
It's not hard to do something like preg_match('/question_([0-9]+)/', $key, $match) or $id = substr($key, 9); to pull it back off of there,We have the q_id in the array,It's no harder to check isset($data['question_1']) then isset($data['1']), and it looks better.
So for minimum difficulty we can be sure we won't lose our ID's to some code over site (unless we use usort instead of uasort) but I digress..

Laravel - multi-insert rows and retrieve ids

I'm using Laravel 4, and I need to insert some rows into a MySQL table, and I need to get their inserted IDs back.
For a single row, I can use ->insertGetId(), however it has no support for multiple rows. If I could at least retrieve the ID of the first row, as plain MySQL does, would be enough to figure out the other ones.
It's mysql behavior of
last-insert-id
Important
If you insert multiple rows using a single INSERT statement, LAST_INSERT_ID() returns the value generated for the first inserted row only. The reason for this is to make it possible to reproduce easily the same INSERT statement against some other server.
u can try use many insert and take it ids or after save, try use $data->id should be the last id inserted.
If you are using INNODB, which supports transaction, then you can easily solve this problem.
There are multiple ways that you can solve this problem.
Let's say that there's a table called Users which have 2 columns id, name and table references to User model.
Solution 1
Your data looks like
$data = [['name' => 'John'], ['name' => 'Sam'], ['name' => 'Robert']]; // this will insert 3 rows
Let's say that the last id on the table was 600. You can insert multiple rows into the table like this
DB::begintransaction();
User::insert($data); // remember: $data is array of associative array. Not just a single assoc array.
$startID = DB::select('select last_insert_id() as id'); // returns an array that has only one item in it
$startID = $startID[0]->id; // This will return 601
$lastID = $startID + count($data) - 1; // this will return 603
DB::commit();
Now, you know the rows are between the range of 601 and 603
Make sure to import the DB facade at the top using this
use Illuminate\Support\Facades\DB;
Solution 2
This solution requires that you've a varchar or some sort of text field
$randomstring = Str::random(8);
$data = [['name' => "John$randomstring"], ['name' => "Sam$randomstring"]];
You get the idea here. You add that random string to a varchar or text field.
Now insert the rows like this
DB::beginTransaction();
User::insert($data);
// this will return the last inserted ids
$lastInsertedIds = User::where('name', 'like', '%' . $randomstring)
->select('id')
->get()
->pluck('id')
->toArray();
// now you can update that row to the original value that you actually wanted
User::whereIn('id', $lastInsertedIds)
->update(['name' => DB::raw("replace(name, '$randomstring', '')")]);
DB::commit();
Now you know what are the rows that were inserted.
As user Xrymz suggested, DB::raw('LAST_INSERT_ID();') returns the first.
According to Schema api insertGetId() accepts array
public int insertGetId(array $values, string $sequence = null)
So you have to be able to do
DB::table('table')->insertGetId($arrayValues);
Thats speaking, if using MySQL, you could retrive the first id by this and calculate the rest. There is also a DB::getPdo()->lastInsertId(); function, that could help.
Or if it returened the last id with some of this methods, you can calculate it back to the first inserted too.
EDIT
According to comments, my suggestions may be wrong.
Regarding the question of 'what if row is inserted by another user inbetween', it depends on the store engine. If engine with table level locking (MyISAM, MEMORY, and MERGE) is used, then the question is irrevelant, since thete cannot be two simultaneous writes to the table.
If row-level locking engine is used (InnoDB), then, another possibility might be to just insert the data, and then retrieve all the rows by some known field with whereIn() method, or figure out the table level locking.
$result = Invoice::create($data);
if ($result) {
$id = $result->id;
it worked for me
Note: Laravel version 9

mysql | PHP | Join within own table

i dont know if i am doing right or wrong, please dont judge me...
what i am trying to do is that if a record belongs to parent then it will have parent id assosiated with it.. let me show you my table schema below.
i have two columns
ItemCategoryID &
ItemParentCategoryID
Let Suppose a record on ItemCategoryID =4 belongs to ItemCategoryID =2 then the column ItemParentCategoryID on ID 4 will have the ID of ItemCategoryID.
I mean a loop with in its own table..
but problem is how to run the select query :P
I mean show all the parents and childs respective to their parents..
This is often a lazy design choise. Ideally you want a table for these relations or/and a set number of depths. If a parent_id's parent can have it's own parent_id, this means a potential infinite depth.
MySQL isn't a big fan of infinite nesting depths. But php don't mind. Either run multiple queryies in a loop such as Nil'z's1, or consider fetching all rows and sorting them out in arrays in php. Last solution is nice if you pretty much always get all rows, thus making MySQL filtering obsolete.
Lastly, consider if you could have a more ideal approach to this in your database structure. Don't be afraid to use more than one table for this.
This can be a strong performance thief in the future. An uncontrollable amount of mysql queries each time the page loads can easily get out of hands.
Try this:
function all_categories(){
$data = array();
$first = $this->db->select('itemParentCategoryId')->group_by('itemParentCategoryId')->get('table')->result_array();
if( isset( $first ) && is_array( $first ) && count( $first ) > 0 ){
foreach( $first as $key => $each ){
$second = $this->db->select('itemCategoryId, categoryName')->where_in('itemParentCategoryId', $each['itemParentCategoryId'])->get('table')->result_array();
$data[$key]['itemParentCategoryId'] = $each['itemParentCategoryId'];
$data[$key]['subs'] = $second;
}
}
print_r( $data );
}
I don't think you want/can to do this in your query since you can nest a long way.
You should make a getChilds function that calls itself when you retrieve a category. This way you can nest more than 2 levels.
function getCategory()
{
// Retrieve the category
// Get childs
$childs = $this->getCategoryByParent($categoryId);
}
function getCategorysByParent($parentId)
{
// Get category
// Get childs again.
}
MySQL does not support recursive queries. It is possible to emulate recursive queries through recursive calls to a stored procedure, but this is hackish and sub-optimal.
There are other ways to organise your data, these structures allow very efficient querying.
This question comes up so often I can't even be bothered to complain about your inability to use Google or SO search, or to offer a wordy explanation.
Here - use this library I made: http://codebyjeff.com/blog/2012/10/nested-data-with-mahana-hierarchy-library so you don't bring down your database

php code, better way of grabbing sql data

I need to grab data from two tables, but I know theres a better, more tidier way to do this. Is it some kind of JOIN i need?
I'll show you my code and you'll see what I mean:
if ($rs[firearm] != "") {
$sql_result2 = mysql_query("SELECT * FROM db_firearms WHERE name='$rs[firearm]'", $db);
$rs2 = mysql_fetch_array($sql_result2);
$sql_result3 = mysql_query("SELECT * FROM items_firearms WHERE player='$id'", $db);
$rs3 = mysql_fetch_array($sql_result3);
if ($rs3[$rs2[shortname]] < 1) {
mysql_query("UPDATE mobsters SET firearm = '' WHERE id ='$id'");
}
}
This question is clear, but your code example has alot of formatting issues and I cannot give you direct answer, based on your example code.
The reason, why your example is unclear, is because.. with what are you going to join the tables? From one table you are selecting by name='$rs[firearm]' and from another by player='$id'. You have to provide the hidden data, like $rs and also $id.
You should definitely read these about mysql join and mysql left join. But I will try to give you an example based on your code, with fixed syntax. (Keep in mind, that I'm no mysql join expert, I did not test this code and also I do not know the joining conditions.) And also, the system structure is unclear.
As I understood, this what your tables do, correct?
mobsters - Users table
items_firearms - Links from users table to items table
db_firearms - Items table
So basically, my example does this: It will have preloaded $rs value, from the users table. It will check, if there is a entry inside the links table and hook the result with them items table. However, if the links table or even the items table can return multiple entries, then this doesn't work and you need to loop your results in much more smarter way.
// I can only assume, that $id is the ID of the player
$id = 2;
// Since I dont know the $rs value, then Im going to make some up
$rs = array(
'id' => 33,
'firearm' => 'famas'
);
if ($rs['firearm']) {
$result = mysql_fetch_array(mysql_query("SELECT ifa.*, dbfa.* FROM `items_firearms` AS `ifa` LEFT JOIN `db_firearms` AS `dbfa` ON `ifa.shortname` = `dbfa.shortname` WHERE `ifa.player` = '$id'"));
if ($result['id']) {
mysql_query("UPDATE `mobsters` SET `firearm` = '' WHERE `id` = '$id'", $db);
}
}
It is pretty clear, that you are new to PHP and mysql.. So I think you should probably edit your question and talk about your higher goal. Briefly mention, what your application are you building..? What are you trying to do with the mysql queries..? Maybe provide the table structure of your mysql tables..? I'm sure, that you will get your questions votes back to normal and also we can help you much better.
NOTES
You have to quote these types of variables: $rs[firearm] -> $rs['firearm']
If you want to check if your $rs['firearm'] equals something, then there is a better way then $rs[firearm] != "". The most simple is if ($rs['firearm']) {echo 'foo';}, but will produce a notice message, when all errors reporting mode. You can use isset() and empty(), but keep in mind, that isset() checks whether the variable has been set.. Meaning, even if its false, then it has been set. empty() reacts to undefined and empty variable the same, without any messages.
Also, "" means NULL, so if you even need to use "", then use NULL instead...much neater way..
I strongly recommend to use mysql class. You can understand the basics behind that idea from this answer. This is gonna make things much more easier for you. Also, mysql class is a must-have when dealing with dynamic applications.
if ($rs3[$rs2[shortname]] < 1) { .. makes no sense.. Do you want to check if the value is empty? Then (simple): if (!$rs3[$rs2[shortname]]) { .. and a very strict standard: if (empty($rs3[$rs2[shortname]])) { ..
Also you have to quote your sql queries, see my examples above.
Is the last mysql query missing $db?

Joining Customer on Attribute

I'm trying to filter my orders which are returned back by the magento API by a customer attribute. I tried several approaches but nothing seem to work.
I'm using Magento 1.4.1.1 atm and the api does this at the moment:
$billingAliasName = 'billing_o_a';
$shippingAliasName = 'shipping_o_a';
$collection = Mage::getModel("sales/order")->getCollection()
->addAttributeToSelect('*')
->addAddressFields()
->addExpressionFieldToSelect(
'billing_firstname', "{{billing_firstname}}", array('billing_firstname'=>"$billingAliasName.firstname")
)
->addExpressionFieldToSelect(
'billing_lastname', "{{billing_lastname}}", array('billing_lastname'=>"$billingAliasName.lastname")
)
->addExpressionFieldToSelect(
'shipping_firstname', "{{shipping_firstname}}", array('shipping_firstname'=>"$shippingAliasName.firstname")
)
->addExpressionFieldToSelect(
'shipping_lastname', "{{shipping_lastname}}", array('shipping_lastname'=>"$shippingAliasName.lastname")
)
->addExpressionFieldToSelect(
'billing_name',
"CONCAT({{billing_firstname}}, ' ', {{billing_lastname}})",
array('billing_firstname'=>"$billingAliasName.firstname", 'billing_lastname'=>"$billingAliasName.lastname")
)
->addExpressionFieldToSelect(
'shipping_name',
'CONCAT({{shipping_firstname}}, " ", {{shipping_lastname}})',
array('shipping_firstname'=>"$shippingAliasName.firstname", 'shipping_lastname'=>"$shippingAliasName.lastname")
);
Which is the default API call I guess. Now I just want to join a customer attribute called update - how do I achieve this simple task?
Or is this not possible on a flat table like sales_flat_order?
Whenever I need to do this I use something like:
Joining An EAV Table (With Attributes) To A Flat Table
It's not well optimised but you should be able to pick out the parts you need.
PS.
I think I'll explain what I mean by optimised since it's important. In the heart of the method is this bit:
->joinLeft(array($alias => $table),
'main_table.'.$mainTableForeignKey.' = '.$alias.'.entity_id and '.$alias.'.attribute_id = '.$attribute->getAttributeId(),
array($attribute->getAttributeCode() => $field)
);
If you know MySQL then you'll know it will only pick one index when joining a table, the more specific the better. In this case only the entity_id and attribute_id fields are being used so MySQL is restricted to those. Both columns are indexed but the cardinality is low.
If the condition also included the entity type then MySQL would have the choice of using IDX_BASE which indexes the columns entity_type_id,entity_id,attribute_id,store_id in that order (it needs to process them left to right). So something like this results in a much improved EAV performance - depending on how many rows on the 'left' table it could be several hundred- or thousand-fold better.
$alias.'.entity_type_id='.$entityType->getId().' AND main_table.'.$mainTableForeignKey.' = '.$alias.'.entity_id AND '.$alias.'.attribute_id = '.$attribute->getAttributeId().' AND '.$alias.'.store_id=0'

Categories