Possible to convert result of Drupal db_query to PHP array? - drupal

In Drupal, I can execute a SQL as follows:
$query_object = db_query("SELECT * FROM {nodes}");
If I know the query returns only a single result (so only 1 row and 1 column), I can directly fetch it with:
$result = db_result($query_object);
If I got multiple results, I need to loop through them with something like:
$rows[] = array();
while (($row = db_fetch_object($query_object) != FALSE) {
$rows[] = $row;
}
I'm wondering if there is an easier way to do that? Is there a way that I can transfer all results into an array with a single statement? Or isn't that working, because db_result returns a cursor-like object, where you can only fetch a single row each time?

Not in Drupal 6.
In Drupal 7, there are fetch methods that can help to avoid loops like that. From http://drupal.org/node/310072:
<?php
// Retrieve all records into an indexed array of stdClass objects.
$result->fetchAll();
// Retrieve all records into an associative array keyed by the field in the result specified.
$result->fetchAllAssoc($field);
// Retrieve a 2-column result set as an associative array of field 1 => field 2.
$result->fetchAllKeyed();
// You can also specify which two fields to use by specifying the column numbers for each field
$result->fetchAllKeyed(0,2); // would be field 0 => field 2
$result->fetchAllKeyed(1,0); // would be field 1 => field 0
// Retrieve a 1-column result set as one single array.
$result->fetchCol();
// Column number can be specified otherwise defaults to first column
$result->fetchCol($column_index);
?>

In Drupal 7, you can also use:
db_query('QUERY')->fetchAll(PDO::FETCH_ASSOC);

I do always something like this ( just a simple exemple) :
$query = db_query("SELECT nid
FROM {from}
WHERE blallala
",
$tab_arg
);
if ($query->rowCount() == 0) {
$output=t('no result')
} else
{
foreach($query as $result)
{
$tab_res[]=$result;
}
foreach($tab_res as $res)
{
$output.=$res->nid;
}
}

One can also use db_fetch_array($result), where $result =db_query($queryString). As explained from the Drupal documentation:
[It returns] ...an associative array representing the next row of the result, or
FALSE. The keys of this object are the names of the table fields
selected by the query, and the values are the field values for this
result row.

Related

Wordpress/ACF merging multiple fields value to one

Is there any way to get values from multiple fields (for example: “value_1”, “value_2”, etc.) and merge all of them to a new field, let`s say “merged_values” after publishing/editing the post?
Something like this:
If:
value_1 = red blue; value_2 = gold silver;
Then:
merged_values = red blue gold silver;
I need to achieve this, because of a search plugin, that doesn`t show the results if the search query is “red silver” and there is no option in it to combine the search within multiple ACF fields.
(to be exact, I need the “merged_values” field to be written in the db as the example above – not just echo the values together in the front-end).
The output from the default fields is pure text with spaces and the "merged_values" should also be just pure text (no arrays, etc.)
I think you want something like this
// This function runs after your post is saved
function my_acf_save_post( $post_id ) {
// Get new value of field 1
$value1 = get_field( 'field1', $post_id );
// Get new value of field 2
$value2 = get_field( 'field2', $post_id );
// Merged values with ; on the end
$merge = implode(" ",$value1).' '.implode(" ",$value2);
// Update field 3 with the new value which should be
// value1 value2;
update_field( 'field3', $merge, $post_id );
}
add_action('acf/save_post', 'my_acf_save_post', 20);
I'm not sure if you wanted to put the merged value in another ACF field. If not you can use $wpdb to insert it in de database manually.

Drupal 7 | update multiple rows using db_update

I have an array like
Array (
[1] => 85590762,22412382,97998072
[3] => 22412382
)
Where key is the item_id and value is the value of a column which I need to update against an item. I can use db_update in a loop but i want to avoid this strategy due to performance. I want to update all the rows in a single db call. Also using db_query I think will not be a good idea. So is there any way using db_update to update these rows?
According to above data, standard mysql queries will be like
update items set sold= 1, users = '85590762,22412382,97998072' Where item_id = 1;
update items set sold = 1, users = '22412382' Where item_id = 3;
Unfortunately you can't do that. Currently and probably in the feature, there won't be support for updating (multiple) values on different rows with different values with one db_update.
If your data is this:
$for_update = array(
1 => "85590762,22412382,97998072",
3 => "22412382",
);
You can do these:
CASE 1: Create a loop and call every time the update function:
foreach ($for_update as $key => $value) {
$fields = array('sold' => 1, 'users' => $value);
db_update('items')->fields($fields)->condition('item_id', $key, '=')->execute();
}
Pros: other modules can hook inside your query, can support multiple DB drivers
Cons: Object initialization is slow, lots of DB connection/traffic
CASE 2: It's faster if you use db_query()...
... because in that case there's no object instantiation/operation, which is a bit costly and other conversion. (i.e.: https://drupal.stackexchange.com/questions/129669/whats-faster-db-query-db-select-or-entityfieldquery)
foreach ($for_update as $key => $value) {
db_query("UPDATE items SET sold = :sold, users = :users WHERE item_id = :item_id",
array(':sold' => 1, ':users' => $value, ':item_id' => $key)
);
}
Pros: no object initialization and operation < so it's faster
Cons: other modules can't hook inside your query, your code can break under different DB drivers, lots of DB connection/traffic
CASE 3: Also you can use transaction
This can boost a bit your performance at some case.
$transaction = db_transaction();
try {
foreach ($for_update as $key => $value) {
$fields = array('sold' => 1, 'users' => $value);
db_update('items')->fields($fields)->condition('item_id', $key, '=')->execute();
}
}
catch (Exception $e) {
$transaction->rollback();
watchdog_exception('my_type', $e);
}
Note: transaction mostly used, when you want everything or nothing. But with some DB drivers, you can optimize out the COMMIT commands.
In case 1 you get something like this:
UPDATE items SET sold = 1, users = '85590762,22412382,97998072' WHERE item_id = 1;
COMMIT;
UPDATE items SET sold = 1, users = '22412382' WHERE item_id = 3;
COMMIT;
With transaction you do something like this:
UPDATE items SET sold = 1, users = '85590762,22412382,97998072' WHERE item_id = 1;
UPDATE items SET sold = 1, users = '22412382' WHERE item_id = 3;
COMMIT;
With COMMIT you write out the data to the final place (into long-term storage, until this, it's only somewhere in the memory or a temporary place). When you use rollback, you drop these changes. At least this is how I understand this. (i.e. I get performance improvement with this when I used sqlite.)
Same as case 1 or 2 +
Pros: you can rollback your data if you need to be consistent, you write out data only once to the drive < so sql only "once" do IO, lots of DB connection/traffic
CASE 4: Or you can build one sql statement for this:
$ids = array();
$when_then = "";
foreach ($for_update as $key => $value) {
$ids[] = $key;
$when_then .= "WHEN $key THEN '$value' ";
}
db_query("UPDATE items
SET sold = 1, users = CASE item_id
$when_then
ELSE users
END
WHERE item_id IN(:item_ids)", array(':item_ids' => $ids));
Probably this is the fastest way from all from here.
NOTE: $when_then variable doesn't contain the best solution, it's better if you can use the params or you escape unsafe data.
Pros: between your server and sql there will be only one request and less redundant data, one bigger sql queries faster then a lot of small ones
Cons: other modules can't hook inside your query, your code can break under different DB drivers
Also please note, after (I think) PHP 5.3 you can't run more than one statement in db_query or in PDO by default, because of it can be an SQL Injection, so it blocks. However you can set this, but not recommended. (PDO support for multiple queries (PDO_MYSQL, PDO_MYSQLND))
13-05-2019 edit:
Just a side note, I did a few month ago some performance measurement on medium dataset to query down some data via db_select and db_query. The magnitude of the performance of these are: under you run one db_select, you can run two db_query. This also applies to db_update and db_query. The test queried down some columns with a simple SELECT, no JOINS, no fancy dandy stuff, just two condition. Of course, the measurement also contained/included the time which was required to build up these queries (at both case). However please note, the Drupal Coding Standard requires to use db_update, db_delete, db_merge on modification normally, only 'allows' db_query instead of db_select operation.
Using BULK UPDATE you can update multiple values on diferent rows or in the same row with the dinamic query db_update .
Note: By bulk updating only one query can be sent to the server instead of one query for each row to update.
Syntax:
UPDATE yourTableName
SET yourUpdateColumnName =
(CASE yourConditionColumnName WHEN Value1 THEN 'UpdatedValue'
WHEN Value2 THEN 'UpdatedValue'
.
.
N
END)
WHERE yourConditionColumnName IN(Value1,Value2,.....N);
More info about expressions on UpdateQuery on:
https://api.drupal.org/api/drupal/includes%21database%21query.inc/function/UpdateQuery%3A%3Aexpression/7.x
With your data will be:
//Create array
$for_update = [
1 => "85590762,22412382,97998072",
3 => "22412382",
];
$item_ids_to_filter = [];
//Setup when-then conditions
$when_then = "CASE item_id ";
foreach ($for_update as $item_id => $users_id) {
$when_then .= "WHEN {$item_id} THEN '{$users_id}' ";
$item_ids_to_filter[] = $item_id;
}
$when_then .= "END";
//Execute query and update the data with db_update
db_update('items')
->expression("users",$when_then)
->condition('item_id', $item_ids_to_filter)
->execute();
As result the next query is generated:
UPDATE items
SET users =
(CASE item_id = WHEN 1 THEN '85590762,22412382,97998072'
WHEN 3 THEN '22412382'
END)
WHERE (item_id IN ('1', '3'))
I think you could do something like
$query = db_udpate('table')->fields(array('field_1','field_2','field_3'));
foreach ($fields as $record) {
$query->values($record);
}
return $query->execute();
Where $fields = array ('field_1' => VALUE, 'field_2' => VALUE)

select single or multiple records using a single linq query

I want to pass a valid id or -1("ALL") from a drop down to the function.
Can I select 'All' rows or one row based on the selected option from ddl from a table using a single linq query?
You can add an OR to the statement to get the result. Basically, (#value = -1) OR (id = #value). Whenever value is -1, it will return all value, otherwise that we be false and only return the matching record.
Having an OR in the LINQ can sometimes result in full-table scans and non-optimal retrievals.
Instead of having a condition inside your LINQ statement, I'd recommend building your LINQ query conditionally based on whether you want a single result or "all":
// Initialize your select
var query = db.Select(r => r);
// Conditionally add the "where"
if (selectedValue != -1)
query = query.Where(n => n.id == selectedValue);
// Collect your results
foreach (var record in query)
{
// ...
}
If you want all records, then your query won't include a where clause at all, otherwise, the where clause will include only the filter by id.

How can I order by NULL in DQL?

I'm building an app using Symfony2 framework and using Doctrine ORM. I have a table with airlines for which some IATA codes are missing. I'm outputting a list, ordered by this IATA code, but I'm getting the undesirable result that the records with null IATA codes are sorted at the top.
In MySQL this is simple enough to do, with ORDER BY ISNULL(code_iata), code_iata but I'm clueless as to what the equivalent would be for DQL. I tried
$er->createQueryBuilder('airline')->orderBy('ISNULL(airline.codeIata), airline.codeIata', 'ASC')
but this gives me a syntax error.
The Doctrine docs give me no answer either. Is there a way?
You can use the following trick in DQL to order NULL values last
$em->createQuery("SELECT c, -c.weight AS HIDDEN inverseWeight FROM Entity\Car c ORDER BY inverseWeight DESC");
The HIDDEN keyword (available since Doctrine 2.2) will result in omitting the inverseWeight field from the result set and thus preventing undesirable mixed results.
(The sort fields value is inverted therefore the order has to be inverted too, that's why the query uses DESC order, not ASC.)
Credits belong to this answer.
The most unobtrusive generic solution would be to use the CASE expression in combination with the HIDDEN keyword.
SELECT e,
CASE WHEN e.field IS NULL THEN 1 ELSE 0 END HIDDEN _isFieldNull
FROM FooBundle:Entity e
ORDER BY _isFieldNull ASC
Works with both numeric as well as other field types and doesn't require extending Doctrine.
If you want to do something similar to "NULLS LAST" in SQL (with PostgreSQL in my case):
ORDER BY freq DESC NULLS LAST
You can use the COALESCE function with the Doctrine Query Builder
(HIDDEN will hide the field "freq" on your query result set).
$qb = $this->createQueryBuilder('d')
->addSelect('COALESCE(d.freq, 0) AS HIDDEN freq')
->orderBy('freq', 'DESC')
->setMaxResults(20);
Here it is an example for a custom walker to get exactly what you want. I have taken it from Doctrine in its github issues:
https://github.com/doctrine/doctrine2/pull/100
But the code as it is there didn't work for me in MySQL. I have modified it to work in MySQL, but I haven't test at all for other engines.
Put following walker class for example in YourNS\Doctrine\Waler\ directory;
<?php
namespace YourNS\Doctrine\Walker;
use Doctrine\ORM\Query\SqlWalker;
class SortableNullsWalker extends SqlWalker
{
const NULLS_FIRST = 'NULLS FIRST';
const NULLS_LAST = 'NULLS LAST';
public function walkOrderByClause($orderByClause)
{
$sql = parent::walkOrderByClause($orderByClause);
if ($nullFields = $this->getQuery()->getHint('SortableNullsWalker.fields'))
{
if (is_array($nullFields))
{
$platform = $this->getConnection()->getDatabasePlatform()->getName();
switch ($platform)
{
case 'mysql':
// for mysql the nulls last is represented with - before the field name
foreach ($nullFields as $field => $sorting)
{
/**
* NULLs are considered lower than any non-NULL value,
* except if a – (minus) character is added before
* the column name and ASC is changed to DESC, or DESC to ASC;
* this minus-before-column-name feature seems undocumented.
*/
if ('NULLS LAST' === $sorting)
{
$sql = preg_replace_callback('/ORDER BY (.+)'.'('.$field.') (ASC|DESC)/i', function($matches) {
if ($matches[3] === 'ASC') {
$order = 'DESC';
} elseif ($matches[3] === 'DESC') {
$order = 'ASC';
}
return ('ORDER BY -'.$matches[1].$matches[2].' '.$order);
}, $sql);
}
}
break;
case 'oracle':
case 'postgresql':
foreach ($nullFields as $field => $sorting)
{
$sql = preg_replace('/(\.' . $field . ') (ASC|DESC)?\s*/i', "$1 $2 " . $sorting, $sql);
}
break;
default:
// I don't know for other supported platforms.
break;
}
}
}
return $sql;
}
}
Then:
use YourNS\Doctrine\Walker\SortableNullsWalker;
use Doctrine\ORM\Query;
[...]
$qb = $em->getRepository('YourNS:YourEntity')->createQueryBuilder('e');
$qb
->orderBy('e.orderField')
;
$entities = $qb->getQuery()
->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, '\YourNS\Doctrine\Walker\SortableNullsWalker')
->setHint('SortableNullsWalker.fields', array(
'sortOrder' => SortableNullsWalker::NULLS_LAST
))
->getResult();
DQL does not contain every function of plain SQL. Fortunately you can define your custom DQL method to accomplish this.
Some resources:
http://punkave.com/window/2012/07/24/for-the-php-crowd-adding-custom-functions-to-doctrine-2-dql
http://docs.doctrine-project.org/en/2.1/cookbook/dql-user-defined-functions.html
http://symfony.com/doc/2.0/cookbook/doctrine/custom_dql_functions.html
By default, MySQL will still sort a NULL value; it will just place it at the beginning of the result set if it was sorted ASC, and at the end if it was sorted DESC. Here, you're looking to sort ASC, but you want the NULL values to be at the bottom.
Unfortunately, as powerful as it is, Doctrine isn't going to offer much support here, since function support is limited, and most of it is limited to SELECT, WHERE, and HAVING clauses. You actually wouldn't have a problem at all if any of the following were true about the QueryBuilder:
select() accepted ISNULL()
orderBy() or addOrderBy() supported ISNULL()
the class supported the concept of UNIONs (with this, you could run two queries: one where the codeIata was NULL, and one where it wasn't, and you could sort each independently)
So that said, you can go with the user-defined functions that ArtWorkAD mentioned already, or you could replicate that last point with two different Doctrine queries:
$airlinesWithCode = $er->createQueryBuilder("airline")
->where("airline.iataCode IS NULL")
->getQuery()
->getResult();
$airlinesWithoutCode = $er->createQueryBuilder("airline")
->where("airline.iataCode IS NOT NULL")
->getQuery()
->getResult();
Then you can combine these into a single array, or treat them independently in your templates.
Another idea is to have DQL return everything in one data set, and let PHP do the heavy lifting. Something like:
$airlines = $er->findAll();
$sortedAirlines = array();
// Add non-NULL values to the end if the sorted array
foreach ($airlines as $airline)
if ($airline->getCodeIata())
$sortedAirlines[] = $airline;
// Add NULL values to the end of the sorted array
foreach ($airlines as $airline)
if (!$airline->getCodeIata())
$sortedAirlines[] = $airline;
The downside to both of these is that you won't be able to do LIMITs in MySQL, so it might only work well for relatively small data sets.
Anyway, hope this gets you on your way!

Drupal CCK field select option names, where are they?

I have a custom content type which has a field called "location" which is a long select list (100 or so items). I want to get an array of all locations which have a piece of content associated with them. The numeric value of this field is stored in content_type_[my_content_type], but I can't find anywhere in the database where the name of the values are stored. I hope that's not too confusing - just to be clear, I want to do something like this:
SELECT DISTINCT(field_location_value) FROM content_type_[my_content_type]
and then
SELECT field_location_name_or_something FROM where_on_earth_are_the_names_stored
and then do something with the two arrays to find the names I want.
Can anyone help?
Thanks a lot. Drupal 6, by the way.
If you mean select list field of CCK:
Get all location associated with current node (piece of content?):
$node = node_load('YOUR content ID');
print_r($node->field_location); // $node->field_location - this will array of values.
Get all values of that field (defined in "Allowed values"):
$content_field = content_fields('field_location');
$allowed_values = content_allowed_values($content_field); // array of values
I found this, after much trial and tribulation, in the database table content_node_field_instance, under the field's widget settings field.
This Drupal 6 code snipped will retrieve your cck field value options and put them in the $allowed_values variable as an array. Replace 'YOUR_CCK_FIELD_NAME' with your cck field name (name, not label)
$cck_field_name = 'YOUR_CCK_FIELD_NAME';
$cck_field = db_fetch_object(db_query("SELECT global_settings FROM {content_node_field} WHERE field_name = '%s'", $cck_field_name));
$cck_field_global_settings = unserialize($cck_field->global_settings);
$allowed_values = explode("\r\n", trim($cck_field_global_settings['allowed_values'], "\r\n"));
In Drupal 7 if you have a field of type List (text) then I have written the following function to return an array of options:
function get_drupal_select_options($field_name) {
$options = unserialize(db_query("SELECT c.data
FROM field_config_instance i
INNER JOIN field_config c ON c.id = i.field_id
WHERE i.field_name = :fieldname", array(':fieldname'=>$field_name))->fetchField());
return $options['settings']['allowed_values'];
}

Resources