Finding a specific row in a PropelCollection - php

I have two tables. One for orders, and for their prefs. The tables look like this:
Order:
+----------+---------------+------------+
| orderID | orderNumber | clientID |
+----------+---------------+------------+
| 1 | abc123 | 2 |
| 2 | orderX | 7 |
| 3 | Joe9 | 2 |
| 4 | Order4 | 2 |
+----------+---------------+------------+
OrderPref
+----------+----------+-------------+
| orderID | prefID | prefValue |
+----------+----------+-------------+
| 1 | 1 | $100 |
| 1 | 2 | 123 |
| 1 | 3 | $35 |
| 2 | 1 | $600 |
| 2 | 2 | 876 |
| 2 | 3 | $44 |
+----------+----------+-------------+
What I want to is for each order, get the prefValue for a specific prefID. Currently, this is what I am doing:
$orders = OrdersQuery::create()->filterByClientID(2)->find();
foreach($orders as $o){
$prefs = $o->getOrderPrefs();
foreach($prefs as $p){
if($p->getPrefID() === 2){
echo $p->getPrefValue();
break;
}
}
}
This works, but there needs to be a better way to get the one row I want for each order without looping through all the prefs.
I know this doesn't work, but is there something like this?
$orders = OrdersQuery::create()->filterByClientID(2)->find();
foreach($orders as $o){
// This obviously doesn't work, so is there a short way to do this?
echo $o->getOrderPrefs()->filterByPrefID(2)->getPrefValue();
}
I was reading the docs and found a ->search() method, but I don't know how to use it.
$orders = OrdersQuery::create()->filterByClientID(2)->find();
foreach($orders as $o){
// How can I search for the row with the prefID I want?
echo $o->getOrderPrefs()->search()->getPrefValue();
}

Looking at some old Propel stuff I've written, I'm guessing something like:
$prefValue = OrdersQuery::create()->
joinOrderPref()->
where('OrderPref.prefID = ?', $prefId)->
filterByClientID($clientId)->
select('OrderPref.prefValue')->
find()
;
The issue is that you need to get a specific column (reference).
For these chainable queries, my view is that an auto-completing editor is pretty much mandatory - remembering the syntax is nigh-on impossible without it.

$prefValues = OrdersQuery::create()
->filterByClientId(2)
->joinOrderPref()
->withColumn('OrderPref.PrefValue', 'theValue')
// row above fetches ONLY the needed column from joined table and
// gives 'theValue' alias to it
->select('theValue')
->find();
With this you don't need to do any foreaches in PHP or do it in two steps.
About the ->select() part - it's there only to not get the fully bloated/hydrated object, but actually only an array (PropelArrayCollection to be precise) with the prefValue data you need.
I didn't test this live though, some syntax issues may arise.

Related

SQL query returns one row too few

SOLUTION: Make sure you don't 'use up' any $responses->fetch_assoc()s before the while loop.
I performed mysqli_fetch_array($responses);.
In php I have this sql query (simplified for your convenience, but the problem remains)
$sql = "SELECT id, content FROM responses ORDER BY RAND()";
$responses = $conn->query($sql);
where the responses table looks like this:
+----+----------+--------+------+
| id | content | userId | part |
+----+----------+--------+------+
| 4 | peewee | 31 | 1 |
| 5 | tallinn | 31 | 1 |
| 6 | dewey | 31 | 1 |
| 7 | stanford | 31 | 1 |
+----+----------+--------+------+
That doesn't format properly so all you need to know is that the id and content rows are different for each entry while the rest is the same for each.
The problem is, when I do a while loop on $responses like so:
while ($row = $responses->fetch_assoc()) {
$responseId = $row["id"];
$content = $row["content"];
echo " id: ".$responseId;
echo " content: ".$content;
}
I always get 1 record fewer than there are. In this case, since there are 4 rows, I would only see 3 echoed. However, it is not always the same 3, nor are they in the same order. If I remove the ORDER BY RAND() clause, then it is always the first record which is left out.
Thanks in advance
Cheers

Shortening PHP code when comparing multiple MYSQL columns

I use lengths of code similar to this repeatedly in my scripting because I cannot find a shorter way to to compare the MYSQL columns
if ($them['srel1']=="Y" AND $me['Religion']=='Adventist'){$seek11pts=5;}
if ($them['srel2']=="Y" AND $me['Religion']=='Agnostic'){$seek11pts=5;}
if ($them['srel3']=="Y" AND $me['Religion']=='Atheist'){$seek11pts=5;}
if ($them['srel4']=="Y" AND $me['Religion']=='Baptist'){$seek11pts=5;}
if ($them['srel5']=="Y" AND $me['Religion']=='Buddhist'){$seek11pts=5;}
if ($them['srel6']=="Y" AND $me['Religion']=='Caodaism'){$seek11pts=5;}
if ($them['srel7']=="Y" AND $me['Religion']=='Catholic'){$seek11pts=5;}
if ($them['srel8']=="Y" AND $me['Religion']=='Christian'){$seek11pts=5;}
if ($them['srel9']=="Y" AND $me['Religion']=='Hindu'){$seek11pts=5;}
if ($them['srel10']=="Y" AND $me['Religion']=='Iskcon'){$seek11pts=5;}
if ($them['srel11']=="Y" AND $me['Religion']=='Jainism'){$seek11pts=5;}
if ($them['srel12']=="Y" AND $me['Religion']=='Jewish'){$seek11pts=5;}
if ($them['srel13']=="Y" AND $me['Religion']=='Methodist'){$seek11pts=5;}
if ($them['srel14']=="Y" AND $me['Religion']=='Mormon'){$seek11pts=5;}
if ($them['srel15']=="Y" AND $me['Religion']=='Moslem'){$seek11pts=5;}
if ($them['srel16']=="Y" AND $me['Religion']=='Orthodox'){$seek11pts=5;}
if ($them['srel17']=="Y" AND $me['Religion']=='Pentecostal'){$seek11pts=5;}
if ($them['srel18']=="Y" AND $me['Religion']=='Protestant'){$seek11pts=5;}
if ($them['srel19']=="Y" AND $me['Religion']=='Quaker'){$seek11pts=5;}
if ($them['srel20']=="Y" AND $me['Religion']=='Scientology'){$seek11pts=5;}
if ($them['srel21']=="Y" AND $me['Religion']=='Shinto'){$seek11pts=5;}
if ($them['srel22']=="Y" AND $me['Religion']=='Sikhism'){$seek11pts=5;}
if ($them['srel23']=="Y" AND $me['Religion']=='Spiritual'){$seek11pts=5;}
if ($them['srel24']=="Y" AND $me['Religion']=='Taoism'){$seek11pts=5;}
if ($them['srel25']=="Y" AND $me['Religion']=='Wiccan'){$seek11pts=5;}
if ($them['srel26']=="Y" AND $me['Religion']=='Other'){$seek11pts=5;}
EG: if ($them['srel1']=="Y" AND $me['Religion']=='Adventist'){$seek11pts=5;}
I check to seek if the MYSQL column srel1 has a value of Y. if it does then I check to see if the column Religion equals Adventist. If both are true then $seek11pts=5, if they are not both true then nothing happens.
There are 26 srel type columns with either a Y value or null. There are also 26 different values for Religion as you may see. This is but one section of my code. I have multiple HUGE code groupings like this and I'd love to be able to reduce it down to a few lines. I was thinking some kind of array for the religions and another for the numerical endings of the srel columns but I cant get it.
For this current code you can use this:
<?php
$religions = array(1 => 'Adventist','Agnostic','Atheist','Baptist','Buddhist','Caodaism','Catholic','Christian','Hindu','Iskcon','Jainism','Jewish','Methodist','Mormon','Moslem','Orthodox','Pentecostal','Protestant','Quaker','Scientology','Shinto','Sikhism','Spiritual','Taoism','Wiccan','Other');
$count = count($religions) + 1;
for ($i = 1; $i < $count; $i++) {
if ($them["srel$i"]=="Y" && $me['Religion']==$religions[$i]) {
$seek11pts=5;
break;
}
}
While there are ways to accomplish what you ask, you should instead seriously consider restructuring your data.
Better data structure
If your data had a structure more similar to the following:
db.person
+----+------+
| id | name |
+----+------+
| 1 | Nick |
| 2 | Bob |
| 3 | Tony |
+----+------+
PrimaryKey: id
db.religion
+----+---------+
| id | name |
+----+---------+
| 1 | Atheist |
| 2 | Jainism |
| 3 | FSM |
+----+---------+
PrimaryKey: id
db.person_religion
+--------+----------+
| person | religion |
+--------+----------+
| 1 | 2 |
| 2 | 2 |
| 2 | 3 |
| 3 | 1 |
| 3 | 2 |
| 3 | 3 |
+--------+----------+
UniqueIndex: (person,religion)
...everything you're trying to do could be done with simple queries.
SELECT me.id, me.name, meR.name as religion, count(them.id) as matches
FROM person me
LEFT INNER JOIN person_religion meRlookup
ON me.id = meRlookup.person
LEFT INNER JOIN religion meR
ON meRlookup.religion = meR.id
LEFT INNER JOIN person_religion themRlookup
ON meRlookup.religion = themRlookup.religion
LEFT INNER JOIN person them
ON themRlookup.person = them.id
GROUP BY meR.id
I would recommend using laravel or lumen, since these include a "queries generator" that just write a little code (NOTHING SQL) to make queries and that ..

Reorder mysql table ROWS on front end, and update backend [duplicate]

I have a table of food items. They have a "Position" field that represents the order they should appear in on a list (listID is the list they are on, we don't want to re-order items on another list).
+--id--+--listID--+---name---+--position--+
| 1 | 1 | cheese | 0 |
| 2 | 1 | chips | 1 |
| 3 | 1 | bacon | 2 |
| 4 | 1 | apples | 3 |
| 5 | 1 | pears | 4 |
| 6 | 1 | pie | 5 |
| 7 | 2 | carrots | 0 |
| 8,9+ | 3,4+ | ... | ... |
+------+----------+----------+------------+
I want to be able to say "Move Pears to before Chips" which involves setting the position of Pears to position 1, and then incrementing all the positions inbetween by 1. so that my resulting Table look like this...
+--id--+--listID--+---name---+--position--+
| 1 | 1 | cheese | 0 |
| 2 | 1 | chips | 2 |
| 3 | 1 | bacon | 3 |
| 4 | 1 | apples | 4 |
| 5 | 1 | pears | 1 |
| 6 | 1 | pie | 5 |
| 7 | 2 | carrots | 0 |
| 8,9+ | 3,4+ | ... | ... |
+------+----------+----------+------------+
So that all I need to do is SELECT name FROM mytable WHERE listID = 1 ORDER BY position and I'll get all my food in the right order.
Is it possible to do this with a single query? Keep in mind that a record might be moving up or down in the list, and that the table contains records for multiple lists, so we need to isolate the listID.
My knowledge of SQL is pretty limited so right now the only way I know of to do this is to SELECT id, position FROM mytable WHERE listID = 1 AND position BETWEEN 1 AND 5 then I can use Javascript (node.js) to change position 5 to 1, and increment all others +1. Then UPDATE all the records I just changed.
It's just that anytime I try to read up on SQL stuff everyone keeps saying to avoid multiple queries and avoid doing syncronous coding and stuff like that.
Thanks
This calls for a complex query that updates many records. But a small change to your data can change things so that it can be achieved with a simple query that modifies just one record.
UPDATE my_table set position = position*10;
In the old days, the BASIC programming language on many systems had line numbers, it encouraged spagetti code. Instead of functions many people wrote GOTO line_number. Real trouble arose if you numbered the lines sequentially and had to add or delete a few lines. How did people get around it? By increment lines by 10! That's what we are doing here.
So you want pears to be the second item?
UPDATE my_table set position = 15 WHERE listId=1 AND name = 'Pears'
Worried that eventually gaps between the items will disappear after multiple reordering? No fear just do
UPDATE my_table set position = position*10;
From time to time.
I do not think this can be conveniently done in less than two queries, which is OK, there should be as few queries as possible, but not at any cost. The two queries would be like (based on what you write yourself)
UPDATE mytable SET position = 1 WHERE listID = 1 AND name = 'pears';
UPDATE mytable SET position = position + 1 WHERE listID = 1 AND position BETWEEN 2 AND 4;
I've mostly figured out my problem. So I've decided to put an answer here incase anyone finds it helpful.
I can make use of a CASE statement in SQL. Also by using Javascript beforehand to build my SQL query I can change multiple records.
This builds my SQL query:
var sql;
var incrementDirection = (startPos > endPos)? 1 : -1;
sql = "UPDATE mytable SET position = CASE WHEN position = "+startPos+" THEN "+endPos;
for(var i=endPos; i!=startPos; i+=incrementDirection){
sql += " WHEN position = "+i+" THEN "+(i+incrementDirection);
}
sql += " ELSE position END WHERE listID = "+listID;
If I want to move Pears to before Chips. I can set:
startPos = 4;
endPos = 1;
listID = 1;
My code will produce an SQL statement that looks like:
UPDATE mytable
SET position = CASE
WHEN position = 4 THEN 1
WHEN position = 1 THEN 2
WHEN position = 2 THEN 3
WHEN position = 3 THEN 4
ELSE position
END
WHERE listID = 1
I run that code and my final table will look like:
+--id--+--listID--+---name---+--position--+
| 1 | 1 | cheese | 0 |
| 2 | 1 | chips | 2 |
| 3 | 1 | bacon | 3 |
| 4 | 1 | apples | 4 |
| 5 | 1 | pears | 1 |
| 6 | 1 | pie | 5 |
| 7 | 2 | carrots | 0 |
| 8,9+ | 3,4+ | ... | ... |
+------+----------+----------+------------+
After that, all I have to do is run SELECT name FROM mytable WHERE listID = 1 ORDER BY position and the output will be as follows::
cheese
pears
chips
bacon
apples
pie

Yii criteria selecting all rows

I am useing Yii framework to do a criteria selection for all rows that fit the criteria.
I am trying to take the ID of one table and search another tables codes that contain the prefix of the ID. (exp ID-code or 1-sdfa). Currently the code below is returning all of the rows as a result. Below are the details, any insight would help. Thank you.
[table 1]
tbl_School
---------------------------
| ID | Name |
---------------------------
| 1 | forist hills |
| 2 | Dhs |
---------------------------
[table 2]
tbl_ticket
------------------
| ID | code |
------------------
| 1 | 1-fd23s |
| 2 | 2-fdet2 |
| 3 | 1-4wscd |
| 4 | 2-oifjd |
| 5 | 1-zzds6 |
------------------
After runing the function on ID=1 I would like to see
------------------
| ID | code |
------------------
| 1 | 1-fd23s |
| 3 | 1-4wscd |
| 5 | 1-zzds6 |
------------------
Here is my code:
public static function get_tickets($ticket_ID){
$match = '';
$match = addcslashes($match, "$ticket_ID".'_%');
$q = new CDbCriteria( array(
'condition' => "code LIKE :match",
'params' => array(':match' => "$match%")
) );
$rows = Ticket::model()->findAll( $q );
return $rows;
}
PDO does escaping for you so you don't need to do the addcslashes yourself (I didn't even know that existed, always used addslashes :) )
Secondly, you end up selecting on [NUMBER]_%%, those are 3 wildcards.
As Ryan already changed in its comment, you might want to select on -% instead:
public static function get_tickets($ticket_ID){
$ticket_ID = intval($ticket_ID);
if (!$ticket_ID)
return null;
$q = new CDbCriteria( array(
'condition' => "code LIKE :match",
'params' => array(':match' => $ticket_ID . "-%")
) );
$rows = Ticket::model()->findAll( $q );
return $rows;
}
As you can see, I did do a numeric sanity check for the ticket number, just like being cautious.
Lastly: I hope you don't mind the suggestion, but isn't it simply possible adding the ticket number as a separate column? You are going to end up with perfectly avoidable performance problems if there are a lot of rows in this table. With a separate column that is an index you'd use a lot less cpu for the same result.

Optimising SQL Queries

I'm developing a content management system at the moment, and I wanted to hear your thoughts on the following:
I have one table, page. Let's assume it looks like this
ID | Title | Content
1 | Test | This is a test
As well as this, I have a page_option table (so I can store options relating to the page, but I don't want to have a finite list of options - modules could add their own options to a page if required.)
The page_option table could look like this:
page_id | option_key | option_value
1 | background | red
1 | module1_key | chicken
Now to retrieve a page object, I do the following using the Active Record class (this was pseudo coded for this question):
function get_by_id($page_id) {
$this->db->where('id', $page_id);
$page_object = $this->db->get('page');
if($page_object->num_rows() > 0) {
$page = $page_object->row();
$this->db->where('page_id', $page_id);
$options_object = $this->db->get('option');
if($options_object->num_rows() > 0) {
$page->options = $options_object->result();
}
return $page;
}
return $page_object->row();
}
What I want to know, is there a way to do this in one query, so that the option keys become virtual columns in my select, so I'd get:
ID | Title | Content | background | module1_key
1 | Test | This is a test | red | chicken
In my results, rather than doing a seperate query for every row. What if there were 10,000? Etc.
Many thanks in advance!
Using the EAV (Entity-Attribute-Value) model you will always have to cope with these kind of issues. They're also not ver efficient due to the complexity of the queries (pivoting is required in most of them).
SELECT page_id,
MAX(CASE WHEN option_key = 'background' THEN option_value END) background,
MAX(CASE WHEN option_key = 'module1_key' THEN option_value END) module1_key,
MAX(CASE WHEN option_key = 'module2_key' THEN option_value END) module2_key
FROM page_option
GROUP BY page_id
For example, given this table:
| PAGE_ID | OPTION_KEY | OPTION_VALUE |
|---------|-------------|--------------|
| 1 | background | red |
| 1 | module1_key | chicken |
| 2 | module1_key | duck |
| 3 | module1_key | cow |
| 4 | background | blue |
| 4 | module2_key | alien |
| 4 | module1_key | chicken |
You will the following output:
| PAGE_ID | BACKGROUND | MODULE1_KEY | MODULE2_KEY |
|---------|------------|-------------|-------------|
| 1 | red | chicken | (null) |
| 2 | (null) | duck | (null) |
| 3 | (null) | cow | (null) |
| 4 | blue | chicken | alien |
Fiddle here.
Then just join with the page table and that's it :) I've omitted that part in order to focus the query in the grouping itself.
If you can add virtual fields with the activerecord class you can do something similar:
$this->db->add_field("(select group_concat(concat(option_key,':',option_value) SEPARATOR ' ') from page_option where page_id=$page_id group by page_id)");
It wont be optimal...
If option_key is uniqe per page_id (you don't have two or more background with page_id==1) you can do:
SELECT page.page_id, page.title, page.content,
GROUP_CONCAT(option_key SEPARATOR '#') AS option_keys,
GROUP_CONCAT(option_value SEPARATOR '#') as option_values,
FROM page
LEFT JOIN page_option ON page_option.page_id=page.page_id
WHERE page.page_id=USER_SPECIFIED_ID
You can execute this SQL-query and put its result into $result. After you should do every item of $result:
$result[$i]["options"] = array_combine(
explode("#",$result[$i]["option_keys"]),
explode("#",$result[$i]["option_values"])
);
You can do it with a foreach or you can use array_walk too.
After these you've an associative array with options in $result[$i]["options"]:
{
"background" => "red",
"module_key1"=> "chicken"
}
I hope it's what do you want.

Categories