Converting SQL to Joomla Syntax (set and update) - sql

Ok so basically I need to convert this regular sql statement to the syntax joomla uses via
https://api.joomla.org/11.4/Joomla-Platform/Database/JDatabaseQuery.html
here is my statement
SET #myunsubid = (
SELECT subid
FROM aqbi8_acymailing_subscriber s
WHERE s.email = 'email#email.co.nz'
);
SELECT #myunsubid;
UPDATE aqbi8_acymailing_listsub a
SET a.`status` = 1
WHERE a.subid = #myunsubid AND a.listid = 232
So id like it to be like
$db->set(#myunsubid = ( $db->select($db->quoteName('subid') )
$db->from($db->quoteName('aqbi8_acymailing_subscriber s') )
$db->where($db->quoteName('s.email') = 'email#email.co.nz')
)
$db->update($db->quoteName('aqbi8_acymailing_listsub a'))
$db->set($db->quoteName('a.status') = 1)
$db->where ($db->quoteName('a.subid') = #myunsubid AND $db->quoteName('a.listid') = 232 )
But this isnt quite right. please help!

I actually figured it out got it to work like this.
$db = &JDatabase::getInstance($option);
$query = $db->getQuery(true);
// make a variable for subID
$query->select($db->quoteName(array('subid')));
$query->from($db->quoteName('aqbi8_acymailing_subscriber'));
$query->where($db->quoteName('email') . " = '" . $email ."'");
$db->setQuery($query);
$db->execute();
$test = $db->loadObjectList();
print_r( $test );
$myid = $test[0]->subid;
$query->clear();
// // Create Database query
$fields = $db->quoteName('status') . ' = 1';
$conditions = array(
$db->quoteName('subid') . ' = ' . $myid,
$db->quoteName('listid') . ' = ' . $listid
);
// // update query
$query->update($db->quoteName('aqbi8_acymailing_listsub'))->set($fields)->where($conditions);
$db->setQuery($query);
$db->execute();

You don't need to make two trips to the database, you can write a subquery into your UPDATE's WHERE condition (no mysql variables or table aliases are necessary).
Raw Query:
UPDATE aqbi8_acymailing_listsub
SET status = 1
WHERE listid = 232
AND subid = (
SELECT subid
FROM aqbi8_acymailing_subscriber
WHERE `email` = 'email#email.co.nz'
)
Tested Code:
$db = JFactory::getDBO();
try {
$subquery = $db->getQuery(true)
->select('subid')
->from('#__acymailing_subscriber')
->where("email = 'email#email.co.nz'");
$query = $db->getQuery(true)
->update("#__acymailing_listsub")
->set("status = 1")
->where(["listid = 232", "personid = ($subquery)"]); // or make 2 where() calls
echo $query->dump(); // if you want to see; *during development ONLY
$db->setQuery($query);
$db->execute();
if ($affrows = $db->getAffectedRows()) {
JFactory::getApplication()->enqueueMessage("Updated. Rows affected: $affrows", 'success');
} else {
JFactory::getApplication()->enqueueMessage("Logic Error", 'error');
}
} catch (Exception $e) {
JFactory::getApplication()->enqueueMessage("Query Syntax Error: " . $e->getMessage(), 'error'); // never show getMessage() to public
}
It is not clear if any of your values are coming from users/untrusted sources, so be sure to follow good practices when writing variables into your queries -- like casting integers with (int) and calling $db->quote() on string values.
If you want to see a complex/convoluted UPDATE query with several other tables and techniques blended in, here is a comprehensive post: https://joomla.stackexchange.com/a/22916/12352

Please DON'T USE JDatabase Object to update Joomla tables, when there's an API available for the extension.
Whilst I appreciate the OP's question is pertaining to how to update the joomla database using the joomla database object (JDatabase), I propose a safer and more robust method, the "ACYMailing API".
"BUT WHY?", I hear you ask...
Good question!!!
There are 2 pitfalls in updating the joomla database directly - be it on the command-line, in a GUI such as MySQL Workbench or PHPMyAdmin, or even with the Joomla Database Object. Simply put, they both concern compatibility - 1. regarding third party integrations, and 2. concerning the future compatibility of your code. In a nutshell, whenever there's a an API for interacting with a component, I'd use it, over JDatabase every time to future proof your code, and ensure that all pre and post save, update, delete... ...move, and publish plugin events take care of your integrations, just as if you'd performed the action authentically.
To elaborate on these points a bit...
Most Joomla extensions (core and 3rd-party) make use of Joomla's powerful plugin architecture. By doing so, extensions can perform actions at key points in the application's life cycle. For example, after deleting a record from a table belonging to component1, delete related records from a table relating to compnent2. Therefore, one run's the risk of breaking the behaviour/functionality of the component in question - i.e. ACY Mailing, as in your case. Potentially, other core/3rd-party extensions that rely on ACY's data, that would otherwise, get updated through onAfterSave() or onAfterDelete() plugin events, as they will not get called.
There's a big risk that your code to break with future Joomla/ACY Mailing updates, if/when the table structure changes.
OK, so how do we use the API?
The following example code displays everything that you should need to update a subscription record. Each step explains the code, which for reference, is summarised in doc and inline comments in the code itself. To begin, navigate to the file where you are entering your code, then...
STEP BY STEP
STEP 1: Check the existence of ACY Mailing by attempting to include it's helper class, as follows. N.B. If the include_once() fails, you should see the echo statement, indicating that ACY Mailing IS NOT installed.
// load the ACY Mailing helper - bail out if not
if(!include_once(rtrim(JPATH_ADMINISTRATOR, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'components' . DIRECTORY_SEPARATOR . 'com_acymailing' . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'helper.php')){
echo 'This code can not work without the AcyMailing Component';
return false;
}
STEP 2: Set-up your parameters by inputting values into the following 3 variables. See examples in code comments.
// array $lists An array of integer IDs (primary keys) of the lists you want the user to be subscribed to (can be empty).
// e.g. array(2,4,6)
$lists = array();
// array $unsubs An array of integer IDs (primary keys) of the lists you want the user to be un-subscribed from (can be empty).
// e.g. array(2,4,6)
$unsubs = array();
// string $userID Numeric Joomla User or user e-mail. For example: '42' or 'name#domain.com'
$userID = '';
STEP 3: Add the following code to find the ACY Mailing user, from the Joomla User ID/Email address passed in to the ->subid() method, and bail out if not found.
// instantiate the ACY Mailing Subscriber (user) Class
$user = acymailing_get('class.subscriber');
// find the ACY Mailing user id (subid) from the joomla ID or email address set in $userID
$subID = $user->subid($userID);
// No ACY Mailing user/subscriber?
if(empty($subID))
return; // bail out
STEP 4: Add the following code to check, and setup the data for any of the subscriptions/unsubscriptions you've configured to update ($lists and $unsubs arrays). If any found, they will be updated. If not found, return.
// create an array to store data in
$data = array();
// Set up new newsletter subscriptions from the $lists array()
if(!empty($lists)) foreach($lists as $listId)
$data[$listId] = array("status" => 1);
// Set up un-subscriptions from the $unsubs array()
if(!empty($unsubs)) foreach($unsubs as $listId)
$data[$listId] = array('status' => 0);
// no data, bail out...
if(empty($data))
return; //there is nothing to do...
// update the user's subscription records, creating/removing subscriptions/unsubsriptions accordingly
$user->saveSubscription($subID, $data);

Related

CDbMigration::update does not work inside foreach loop

Following this question. There is something wrong, when using CDbMigration::update() inside foreach loop.
This code does not work correctly:
//This is executed inside Yii migration, so $this is CDbMigration.
foreach($idMap as $menuId=>$pageId)
{
$this->update
(
'menus_items',
array('link'=>'/content/show?id='.$pageId),
array('id = '.$menuId)
);
}
For each item in $idMap value of $pageId is always the same and equals value of last item in $idMap array. Therefore, every menu item points to the same URL.
This code works like a charm:
foreach($idMap as $menuId=>$pageId)
{
$sql = "UPDATE `menus_items` SET link = '/content/show?id=".$pageId."' WHERE id = ".$menuId."; ";
Yii::app()->db->createCommand($sql)->execute();
}
For each item in $idMap value of $pageId is always different and equals value of current item in $idMap array. Therefore, every menu item points to correct URL.
The same goes, when executing all statements in one SQL query:
$sql = '';
foreach($idMap as $menuId=>$pageId)
{
$sql .= "UPDATE `menus_items` SET link = '/content/show?id=".$pageId."' WHERE id = ".$menuId."; ";
}
Yii::app()->db->createCommand($sql)->execute();
Again, everything is OK.
Why using CDbMigration::update() fails, while direct SQL execution works like a charm?
I don't think you are providing the criteria parameter properly # array('id = '.$menuId)
. You should use a string if you want to send it like that, putting it in an array presumes you are mapping out the conditions in a key => value pair. Also you should be wrapping the value constraint in quotes id = "$menuId".

PDO INSERT & UPDATE query to different tables

I have a PDO SQL script which enables a user to complete a form which captures band information. It then posts this information to my database table called 'bands'. This works fine.
Simultaneously, I would like the script to update a different table called 'users' which has a column called 'num_bands' which needs to increase by a value of +1 if the user creates more than one band.
I have tried a number of methods, but none of them work. The script seems to be able to INSERT to the 'bands' table perfectly, but I cannot UPDATE the 'users' table. Here is the 'register_band' script:
<?php
// First we execute our common code to connection to the database and start the session
require("common.php");
// At the top of the page we check to see whether the user is logged in or not
if(empty($_SESSION['user']))
{
// If they are not, we redirect them to the login page.
header("Location: ../index.php");
// Remember that this die statement is absolutely critical. Without it,
// people can view your members-only content without logging in.
die("Redirecting to ../index.php");
}
// This if statement checks to determine whether the registration form has been submitted
// If it has, then the registration code is run, otherwise the form is displayed
if(!empty($_POST))
{
// Ensure that the user has entered a non-empty username
if(empty($_POST['username']))
{
// Note that die() is generally a terrible way of handling user errors
// like this. It is much better to display the error with the form
// and allow the user to correct their mistake. However, that is an
// exercise for you to implement yourself.
die("Please enter a username.");
}
// An INSERT query is used to add new rows to a database table.
// Again, we are using special tokens (technically called parameters) to
// protect against SQL injection attacks.
$query = "
INSERT INTO bands (
member_id,
username,
bandname,
bandhometown,
bandtype
) VALUES (
:member_id,
:username,
:bandname,
:bandhometown,
:bandtype
)
";
// Here we prepare our tokens for insertion into the SQL query. We do not
// store the original password; only the hashed version of it. We do store
// the salt (in its plaintext form; this is not a security risk).
$query_params = array(
':member_id' => $_POST['member_id'],
':username' => $_POST['username'],
':bandname' => $_POST['bandname'],
':bandhometown' => $_POST['bandhometown'],
':bandtype' => $_POST['bandtype']
);
try
{
// Execute the query to create the user
$stmt = $db->prepare($query);
$result = $stmt->execute($query_params);
}
catch(PDOException $ex)
{
// Note: On a production website, you should not output $ex->getMessage().
// It may provide an attacker with helpful information about your code.
die("Failed to run query: " . $ex->getMessage());
}
$query2 = "UPDATE users
SET num_bands = num_bands + 1
WHERE id = :member_id";
$stmt2 = $db->prepare($query2);
// This redirects the user to the private page after they register
header("Location: ../gig_view.php");
// Calling die or exit after performing a redirect using the header function
// is critical. The rest of your PHP script will continue to execute and
// will be sent to the user if you do not die or exit.
die("Redirecting to ../gig_view.php");
}
?>
I'm running this in non-production mode at the moment, so the code is not 100%. How do I get the script to UPDATE the 'users' table?
$stmt->closeCursor();
$query2 = "UPDATE users
SET num_bands = num_bands + 1
WHERE id = :member_id";
$stmt2 = $db->prepare($query2);
$params = array(':member_id' => $_POST['member_id']);
$result = $stmt2->execute($params);
The code you have here is well documented, and explains how to use PDO statements, prepared queries and how to execute them with parameters.
Just follow the same pattern as you did with your SELECT, only the string of the query is meant to change here.

Two separate database queries to relate separate tables with Drupal 7 module?

I am developing a custom module for a site I'm working on and have created the following code. This is my first module, so any ideas of what I could be doing better would be appreciate.
As it is, this module works perfectly for me. But, I want to optimize it and be sure that I fix shoddy code.
Thanks!
The function in question is as follows:
// Declared variables for future incrementation
$total=0;
$countOne=0;
$countTwo=0;
$countThree=0;
$countOld=0;
// Call the native global user object from Drupal
global $user;
$userID = $user->uid;
// Check for nodes of given type owned by current user
$sql = db_query("SELECT nid FROM {node} WHERE type = 'content_type' AND uid = " . $userID);
// Iteratively checks each node id against a custom Drupal field on a separate table
foreach ($sql as $record) {
// SQL query for all custom fields attached to the node id given above
$query = db_query("SELECT * FROM {field_birth} WHERE entity_id = " . $record->nid);
$result = $query->fetchObject();
// The unmodified birth format (Y-m-d 00:00:00)
$originalBirth = $result->field_date_of_birth_value;
// The sanitized birth format for comparison (Y-m-d)
$birth = date('Y-m-d', strtotime($originalBirth));
// The current date/time (Y-m-d)
$now = date('Y-m-d');
//Future dates (Y-m-d)
$one_year = date('Y-m-d', strtotime('+1 year', strtotime($birth)));
$two_years = date('Y-m-d', strtotime('+2 years', strtotime($birth)));
$three_years = date('Y-m-d', strtotime('+3 years', strtotime($birth)));
// A count of all records returned before logical statements
$total++;
// Logic to determine the age of the records
if($now < $one_year) {
$countOne++;
}
else if($now >= $one_year && $now < $two_years) {
$countTwo++;
}
else if($now >= $two_years && $now < $three_years) {
$countThree++;
}
else {
$countOld++;
}
My question is, can I avoid having two separate database queries to hit both tables? I am not really sure how to go about that. Also, am I doing things in a manner which will be resource intensive and highly inefficient? As I am not a programmer by trade, I am not certain when code is 'good'. I do want to try my best to make this good code though since it is a module for a website I hope will last a long time.
Thank you stackoverflow community!
EDIT: The code I got working thanks to Mike is as follows. If anyone has a similar question / problem hopefully this will help!
// Join field_birth_table to nodes of given type owned by current user
$sql = db_select('node', 'n');
$sql->join('field_birth_table', 'b', 'n.nid = b.entity_id');
$sql
->fields('b', array('field_birth_field_value', 'entity_id'))
->condition('n.type', 'content_type')
->condition('n.status', '1')
->condition('n.uid', $user->uid)
->addTag('node_access');
$results = $sql->execute();
You can use a left join between the node and field_birth table:
$query = db_select('node', 'n');
$query->leftJoin('field_birth', 'b', '(b.entity_id = n.nid AND b.entity_type = :node)', array(':node' => 'node'));
$query
->fields('b', array())
->condition('n.type', 'content_type')
->condition('n.uid', $user->uid)
$results = $query->execute();

Redbean: How does one get last inserted id of owned Bean?

I use RedBeanPHP 3.5.1 for ORM in my MVP project (powered by Nette FW).
I need to get ID of the last inserted element, that is owned by element from another table. Below you can find method representing functionality which I just described:
public function createSite($userId, $siteName, $feedUrl, $reloadTime, $reloadRate){
$site = R::dispense('site');
$site->user_id = $userId;
$site->name = $siteName;
$site->feed = $feedUrl;
$site->reload_time = $reloadTime;
$site->reload_rate = $reloadRate;
$user = R::load('user', $userId);
$user->ownSite[] = $site;
$id = R::store($user);
return $id;
}
Now I would assume that line
$id = R::store($user);
would store site ID into $id variable since it is owned by already existing user. Instead of that it fills variable with user ID that I have no further use for.
So my question is: How do I get last inserted ID of owned bean that was just created by calling R::store() method on parent (just loaded) bean? Is there an implementation on this in RedBean or do I have to do this manually?
I browsed every corner of RedBeanPHP project web but so far no luck.
Thanks for possible suggestions, guys.
Using common sense I finally figured out how to solve this elegantly and since no one answered my question so far let my just do that myself.
Since R::store($user) is capable of storing both $user and $site, there is misleadingly no need to store $site object manually.
But if you need to get last inserted id of owned bean, there is really no harm in doing so. By storing $site object framework will do the exact same thing and on top of that it returns resired id.
So the correct method implementation looks like this:
public function createSite($userId, $siteName, $feedUrl, $reloadTime, $reloadRate){
$site = R::dispense('site');
$site->user_id = $userId;
$site->name = $siteName;
$site->feed = $feedUrl;
$site->reload_time = $reloadTime;
$site->reload_rate = $reloadRate;
$user = R::load('user', $userId);
$user->ownSite[] = $site;
$id = R::store($site);
R::store($user);
return $id;
}
So in conclusion, hats off to RedBeanPHP ORM FW and I sincerely hope this helps people with similar problem in the future.
There is a function called R::findLast('...')
$last_record = R::findLast('...');
Not sure if this would have been a correct answer 7 years ago but at least now there is no need to do any kind of extra work:
$shop = R::dispense( 'shop' );
$shop->name = 'Antiques';
$vase = R::dispense( 'product' );
$vase->price = 25;
$shop->ownProductList[] = $vase
R::store( $shop );
echo $vase->$id; // <-- yes, id which was created by database is present here

SQL to Magento model understanding

Understanding Magento Models by reference of SQL:
select * from user_devices where user_id = 1
select * from user_devices where device_id = 3
How could I perform the same using my magento models? getModel("module/userdevice")
Also, how can I find the number of rows for each query
Following questions have been answered in this thread.
How to perform a where clause ?
How to retrieve the size of the result set ?
How to retrieve the first item in the result set ?
How to paginate the result set ? (limit)
How to name the model ?
You are referring to Collections
Some references for you:
http://www.magentocommerce.com/knowledge-base/entry/magento-for-dev-part-5-magento-models-and-orm-basics
http://alanstorm.com/magento_collections
http://www.magentocommerce.com/wiki/1_-_installation_and_configuration/using_collections_in_magento
lib/varien/data/collection/db.php and lib/varien/data/collection.php
So, assuming your module is set up correctly, you would use a collection to retrieve multiple objects of your model type.
Syntax for this is:
$yourCollection = Mage::getModel('module/userdevice')->getCollection()
Magento has provided some great features for developers to use with collections. So your example above is very simple to achieve:
$yourCollection = Mage::getModel('module/userdevice')->getCollection()
->addFieldToFilter('user_id', 1)
->addFieldToFilter('device_id', 3);
You can get the number of objects returned:
$yourCollection->count() or simply count($yourCollection)
EDIT
To answer the question posed in the comment: "what If I do not require a collection but rather just a particular object"
This depends if you still require both conditions in the original question to be satisfied or if you know the id of the object you wish to load.
If you know the id of the object then simply:
Mage::getModel('module/userdevice')->load($objectId);
but if you wish to still load based on the two attributes:
user_id = 1
device_id = 3
then you would still use a collection but simply return the first object (assuming that only one object could only ever satisfy both conditions).
For reuse, wrap this logic in a method and place in your model:
public function loadByUserDevice($userId, $deviceId)
{
$collection = $this->getResourceCollection()
->addFieldToFilter('user_id', $userId)
->addFieldToFilter('device_id', $deviceId)
->setCurPage(1)
->setPageSize(1)
;
foreach ($collection as $obj) {
return $obj;
}
return false;
}
You would call this as follows:
$userId = 1;
$deviceId = 3;
Mage::getModel('module/userdevice')->loadByUserDevice($userId, $deviceId);
NOTE:
You could shorten the loadByUserDevice to the following, though you would not get the benefit of the false return value should no object be found:
public function loadByUserDevice($userId, $deviceId)
{
$collection = $this->getResourceCollection()
->addFieldToFilter('user_id', $userId)
->addFieldToFilter('device_id', $deviceId)
;
return $collection->getFirstItem();
}