I want to do something that could be done in SQL:
select * from table
How can this be achieved using SOQL?
I won't be able to use Apex Code, because I run SOQL queries from a PHP page.
By default SOQL has no support for selecting all fields in this way:
SELECT * From sObject
Check below for a generic class that accepts the object name, and based on that gives a SOQL query for selecting all fields as a String.
Apex Class:
public inherited sharing class GenerateDynamicSOQLQuery {
public static string getSOQLQuery(String strObjectName) {
if(!String.isBlank(strObjectName)) {
String strQuery = 'SELECT ';
list<Schema.DescribeSObjectResult> objDiscribe = Schema.describeSObjects(new List<String>{strObjectName});
map<String, Schema.SObjectField> objFileds = objDiscribe[0].fields.getMap();
list<String> lstOfFieldNames = new list<String>(objFileds.keySet());
strQuery = strQuery + String.join(lstOfFieldNames, ', ') + ' FROM ' +strObjectName;
return strQuery;
}
else {
return null;
}
}
}
Demo: To obtain the output of the generated query, open an "Execute Anonymous" window and then execute below code:
String strQuery = GenerateDynamicSOQLQuery.getSOQLQuery('Account');
//strQuery += ' WHERE Industry = \'Banking\' LIMIT 1';
strQuery += ' LIMIT 1';
System.debug('Account Records ====> '+Database.query(strQuery));
The query results will be in the debug logs.
You can use the meta data available from the API to determine which fields for the object are available to the current user.
See the PHP Toolkit 20.0 DescribeSObject Sample for the Enterprise or Partner WSDL. You want the Name from the fields array. Append them together with comma separators to make the complete list of accessible fields.
You can use FIELDS() as the complete field list. For example:
SELECT FIELDS(ALL) FROM Account LIMIT 200
SELECT FIELDS(CUSTOM) FROM Account LIMIT 200
SELECT FIELDS(STANDARD) FROM Account
You can also mix FIELDS() with other field names in the field list. For example:
SELECT Name, Id, FIELDS(CUSTOM) FROM Account LIMIT 200
SELECT someCustomField__c, FIELDS(STANDARD) FROM Account
Note: Max Limit is 200 - see documentation here
Related
I have a datase table with a list of books. Below is my sql statement:
SELECT `Book`.`id` , `Book`.`name` , `Book`.`isbn` , `Book`.`quantity_in_stock` , `Book`.`price` , (`Book`.`quantity_in_stock` * `Book`.`price`) AS `sales`, concat(`Author`.`name`, ' ', `Author`.`surname`) AS `author`
FROM `books` AS `Book`
LEFT JOIN authors AS `Author`
ON ( `Book`.`author_id` = `Author`.`id` )
WHERE (`Book`.`quantity_in_stock` * `Book`.`price`) > 5000.00
The query works fine and the workflow works fine too. However, I am wanting to access this through an API and make the 5000.00 value configurable through a variable bar.
Question is how do I make this possible such that when I call my API with my endpoint below it works?
https://domain.flowgear.io/5000booklist/{sales_value}
What I want is to be able to re-use my workflow via an API and just pass a sales value I want to query the table against. Sales value can be 2000 or 5000 depending on what I want to achieve.
Add a variable bar and add a property to it called "salesValue"
In the workflow detail pane, provide this url: "/booklist/{salesValue}" - the value in braces must match the name of the property in the variable bar
Add a Formatter, put your SQL template including "WHERE (Book.quantity_in_stock * Book.price) > {salesValue}" in the Expression property then add a custom field called salesValue and pin that from the variable bar salesValue property. Set Escaping to SQL.
Take the output of the Formatter and plug that into the SQL Query property of a SQL Query Connector.
Add another variable bar, and add the special properties FgResponseBody and FgResponseContentType
Pin the SQL result to FgResponseBody and set FgResponseContentType to 'text/xml'
If you want to return JSON, convert the result from the SQL Query to JSON using JSON Convert and then pin that to FgResponseBody and set FgResponseContentType to 'application/json'
#sanjay I will try to give you an overview of what I did back then when I was experimenting with Flowgear through PHP following instructions from here.
I am not sure if you are also invoking the Flowgear REST API through PHP or any other language but regardless I presume logic should remain the same.
What I did was to wrap the PHP CURL sample code in a class so that I can be able to reuse it. Below is a code I wrote for a simple select query:
<?php
//Require the FlowgearConnect class
require_once '/path/to/flowgear_class_with_api_call.php';
try{
$workflow = new FlowgearConnect(return include 'endpoints.php');
$serial = $_POST['serial'];
$clientId = $_POST['client_id'];
//Get the results
$sql = '';
if(empty($serial)){
$conditions = sprintf(' `a`.`client_id` = %s AND `a`.`serial` > -1 ORDER BY `a`.`serial` ASC', $clientId);
}else{
$conditions = ' `a`.`serial` = ' . $serial;
}
/**
In your workflow you will most probably have a VARIABLE BAR that holds your request parameters which is what $conditions speaks to.
*/
$conditions = array('conditions' => $conditions);
$results = $workflow->getResults('orders', 'orders', $conditions);
}catch(catch any exceptions thrown by the API here){
//Log the exceptions here or do whatever
}
The listing above should be self explanatory. Below I will show you the functions I have made use of from my FlowgearConnect class. This is not a standard way as you may configure your code differently to suite your needs.
//FlowgearConnect constructor
class FlowgearConnect
{
protetced $endpoints = [];
protected $domain = "https://your-domain.flowgear.io";
public function __construct(array $endpoints)
{
$this->endpoints = $endpoints;
}
public function getResults($model, $workflow, $options= array())
{
$endpoint = $this->getEndpoint($model, $workflow);
$results = array();
if(!empty($endpoint)){
$results = FlowgearInvoke::run($authOpts, $endpoint, $options, array('timeout' => 30));
}
return $results;
}
....
}
The enpoints.php file, as mentioned before, just returns an array of configured endpoints and/or worflow names from within flowgear console. Below is a excerpt of how mine looked like:
return array(
'orders' => array(
'shipped_orders' => '/shipped_orders',
//etc
),
'items' => array(
'your_model' => '/workflow_name_from_flowgear_console',
),
);
This is just a basic select query with Flowgear's REST API using PHP. If you are lucky you should get your records the way you have configured your response body for your workflow.
Below is a typical testing of a workflow and what you should get back in your API.
I advice you to first create your workflows on your flowgear console and make sure that the produce the desired output and the extract the parts that you want changed no your query, move them to a variable bar for your request and have them injected at run-time based on what you looking to achieve. This explanation can be substituted for other operations such as update and/or delete. Best thing is to understand flowgear first and make sure that you can have everything working there before attempting to create a restful interactive application.
Caution: It's over a year that I have since worked with this platform so you might find errors in this but I am hoping that it will lead you to finding a solution for your problem. If not then perhaps you can create a repo and have me check it out to see how you are configuring everything.
I have an itemid and a category id that are both conditional. If none is given then all items are shown newest fist. If itemid is given then only items with an id lower than given id are shown (for paging). If category id is given than only items in a certain category are shown and if both are given than only items from a certain category where item id smaller than itemid are shown (items by category next page).
Because the parameters are conditional you get a lot of if statements depending on the params when building a SQL string (pseudo code I'm wearing out my dollar sign with php stuff):
if itemid ' where i.iid < :itemid '
if catid
if itemid
' and c.id = :catid'
else
' where c.id = :catid'
end if
end if
If more optional parameters are given this will turn very messy so I thought I'd give the createQueryBuilder a try. Was hoping for something like this:
if($itemId!==false){
$qb->where("i.id < :id");
}
if($categoryId!==false){
$qb->where("c.id = :catID");
}
This is sadly not so and the last where will overwrite the first one
What I came up with is this (in Symfony2):
private function getItems($itemId,$categoryId){
$qb=$this->getDoctrine()->getRepository('mrBundle:Item')
->createQueryBuilder('i');
$arr=array();
$qb->innerJoin('i.categories', 'c', null, null);
$itemIdWhere=null;
$categoryIdWhere=null;
if($itemId!==false){
$itemIdWhere=("i.id < :id");
}
if($categoryId!==false){
$categoryIdWhere=("c.id = :catID");
}
if($itemId!==false||$categoryId!==false){
$qb->where($itemIdWhere,$categoryIdWhere);
}
if($itemId!==false){
$qb->setParameter(':id', $itemId);
}
if($categoryId!==false){
$arr[]=("c.id = :catID");
$qb->setParameter(':catID', $categoryId);
}
$qb->add("orderBy", "i.id DESC")
->setFirstResult( 0 )
->setMaxResults( 31 );
I'm not fully trusting the $qb->where(null,null) although it is currently not creating errors or unexpected results. It looks like these parameters are ignored. Could not find anything in the documentation but an empty string would generate an error $qb->where('','').
It also looks a bit clunky to me still, if I could use multiple $qb->where(condition) then only one if statement per optional would be needed $qb->where(condition)->setParameter(':name', $val);
So the question is: Is there a better way?
I guess if doctrine had a function to escape strings I can get rid of the second if statement round (not sure if malicious user could POST something in a different character set that would allow sql injection):
private function getItems($itemId,$categoryId){
$qb=$this->getDoctrine()->getRepository('mrBundle:Item')
->createQueryBuilder('i');
$arr=array();
$qb->innerJoin('i.categories', 'c', null, null);
$itemIdWhere=null;
$categoryIdWhere=null;
if($itemId!==false){
$itemIdWhere=("i.id < ".
someDoctrineEscapeFunction($id));
}
Thank you for reading this far and hope you can enlighten me.
[UPDATE]
I am currently using a dummy where statement so any additional conditional statements can be added with andWhere:
$qb->where('1=1');// adding a dummy where
if($itemId!==false){
$qb->andWhere("i.id < :id")
->setParameter(':id',$itemId);
}
if($categoryId!==false){
$qb->andWhere("c.id = :catID")
->setParameter(':catID',$categoryId);
}
You can create filters if you want to use more generic approach of handling this.
Doctrine 2.2 features a filter system that allows the developer to add SQL to the conditional clauses of queries
Read more about filters however, I'm handling this in similar manner as you showed
In Joomla 2.5.14, I have a script that gets the current user group id, like:
$groups = $user->get('groups');
foreach($groups as $group) {
echo "<p>Your group ID is:" . $group . "</p>";
};
Now, I need to count the number of articles this user is able to read, given his access level.
For that, I need to select the access level from the viewlevels table, that looks like this:
id title rules JSON encoded access control.
1 Public [1]
2 AccessA [13,8,16,17]
3 AccesssF [8]
4 AccessD [14,8]
6 AccessB [8,16,17]
7 AccessC [8,17]
So, for example, Group 17 may read the articles in AccessA, AccessB and Access C.
I tried the following query, but it isn't selecting any rows:
$query="SELECT * FROM xmb9d_viewlevels WHERE rules LIKE '%.$group.%'";
How can I select all the acess levels for the current user group and then count the number of articles he's able to read?
Thanks for helping!
First
https://github.com/joomla/joomla-cms/blob/master/libraries/joomla/access/access.php#L285
JAccess::getGroupsByUser($userId, $recursive = true)
Will get you the groups a user is matched to including via inheritance (unless you make $recursive false)
https://github.com/joomla/joomla-cms/blob/master/libraries/joomla/access/access.php#L402
JAccess::getAuthorisedViewLevels($userId) will give you all the view levels a user is allowed to see as an array.
If you do
$allowedViewLevels = JAccess::getAuthorisedViewLevels($userId);
if nothing else you could do
$implodedViewLevels = implode(',', $allowedViewLevels);
....
$query->select($db->quoteName('id'))
->from('#__content')
->where($db->quoteName('access') . 'IN (' . $implodedViewLevels . ')';
....
(not tested but you get the general idea).
In joomla always try to let the api do the work for you rather than fight with it.
I think you just need to remove the periods from within the LIKE clause. Such as:
$query="SELECT * FROM xmb9d_viewlevels WHERE rules LIKE '%$group%'";
Also, to make it not specific to the database you should substitute #__ (Note: it's two underscores) for the table prefix:
$query="SELECT * FROM #__viewlevels WHERE rules LIKE '%$group%'";
I've probably murdered the whole concept of MVC somewhere along the line, but my current situation is thus:
I have participants in events and a HABTM relationship between them (with an associated field money_raised). I have a controller that successfully creates new HABTM relationships between pre-existing events and participants which works exactly as I want it to.
When a new relationship is created I wish to set the flash to include the name of the participant that has just been added. The actually addition is done using ids, so I've used the following code:
public function addParticipantToEvent($id = null) {
$this->set('eventId', $id);
if ($this->request->is('post')) {
if ($this->EventsParticipant->save($this->request->data)) {
$participant_id = $this->request->data['EventsParticipant']['participant_id'];
$money_raised = $this->request->data['EventsParticipant']['money_raised'];
$participant_array = $this->EventsParticipant->Participant->findById($participant_id);
$participant_name = $participant_array['Participant']['name'];
$this->Session->setFlash('New participant successfully added: ' . $participant_name . ' (' . $participant_id . ') ' . '— £' . $money_raised);
} else {
$this->Session->setFlash('Unable to create your event-participant link.');
}
}
}
This works, but generates the following SQL queries:
INSERT INTO `cakephptest`.`cakephptest_events_participants` (`event_id`, `participant_id`, `money_raised`) VALUES (78, 'crsid01', 1024) 1 1 0
SELECT `Participant`.`id`, `Participant`.`name`, `Participant`.`college` FROM `cakephptest`.`cakephptest_participants` AS `Participant` WHERE `Participant`.`id` = 'crsid01' LIMIT 1 1 1 0
SELECT `Event`.`id`, `Event`.`title`, `Event`.`date`, `EventsParticipant`.`id`, `EventsParticipant`.`event_id`, `EventsParticipant`.`participant_id`, `EventsParticipant`.`money_raised` FROM `cakephptest`.`cakephptest_events` AS `Event` JOIN `cakephptest`.`cakephptest_events_participants` AS `EventsParticipant` ON (`EventsParticipant`.`participant_id` = 'crsid01' AND `EventsParticipant`.`event_id` = `Event`.`id`)
This final one seems superfluous (and rather costly) as the second should give me all that I need, but removing $this->EventsParticipant->Participant->findById($participant_id) takes out both the second and third queries (which sort of makes sense to me, but not fully).
What can I do to remedy this redundancy (if indeed I'm not wrong that it is a redundancy)? Please tell me if I've made a complete hash of how these sorts of things should work – I'm very new to this.
This is probably due to the default recursive setting pulling the relationship. You can remedy this by setting public $recursive = -1; on your model (beware this will affect all find calls). Or, disable it temporarily for this find:
$this->EventsParticipant->Participant->recursive = -1;
$this->EventsParticipant->Participant->findById($participant_id);
I always suggest setting public $recursive = -1; on your AppModel and using Containable to bring in the related data where you need it.
I'm using readbeanphp as ORM for my php project. I'm trying to load a bean with an additional where clasue. But i'm not sure how to do this.
Normally i'd get a 'bean' like this:
$book = R::load('book', $id);
Which is basically like saying:
SELECT * FROM book WHERE id = '$id'
But i need to add another condition in the where clause:
SELECT * FROM book WHERE id = '$id' AND active = 1
How can i do this with redbeanphp?
R::find() and R::findOne() is what you are looking for:
$beans=R::find("books","active=? AND id=?",array(1,1));
Or if you want a single bean only:
$bean=R::findOne("books","active=? AND id=?",array(1,1));
R::find() will return multiple beans, while R::findOne() returns just a single bean.
You technically could use R::load() but would have to use PHP to check the active field after the bean is loaded to test if it is valid:
$bean=R::load("book",$id);
if($bean->active==1){//valid
//do stuff
}else{//not valid
//return error
}
Hope this helps!
RedBean_Facade::load is using for primary key only.
if you want get beans by a complex query
use
R::find like,
$needles = R::find('needle',' haystack = :haystack
ORDER BY :sortorder',
array( ':sortorder'=>$sortorder, ':haystack'=>$haystack ));
Read more about R::find
http://www.redbeanphp.com/manual/finding_beans
try to use getAll to write your query directly with parameters
R::getAll( 'select * from book where
id= :id AND active = :act',
array(':id'=>$id,':act' => 1) );
Read more about queries,
http://www.redbeanphp.com/manual/queries
these answers are fine but can be simplified. What you should use is:
$book = R::find('books', id =:id AND active =:active, array('id' => $id, 'active' => 1));
And then you can do your standard foreach to loop through the returned array of data.