Drupal db_select - How to combine UNION with LIMIT properly? - database

I'm trying to get content from database divided by category. I want strictly max 4 entries of type "people" and three other entries of type "organization".
I tried to do it like that:
$query = db_select('node', 'n')
->fields('n', array('title','type'))
->fields('i', array('field_image_fid'))
->fields('f', array('uri'))
->condition('n.title', '%'. db_like($keys) . '%', 'LIKE')
->condition('type', array('people'))
->range(0,4);
$query->leftJoin('field_data_field_image', 'i', 'i.entity_id = n.nid');
$query->leftJoin('file_managed', 'f', 'f.fid = i.field_image_fid');
$query2 = db_select('node', 'n')
->fields('n', array('title','type'))
->fields('i', array('field_image_fid'))
->fields('f', array('uri'))
->condition('n.title', '%'. db_like($keys) . '%', 'LIKE')
->condition('type', array('organization'))
->range(0,4);
$query2->leftJoin('field_data_field_image', 'i', 'i.entity_id = n.nid');
$query2->leftJoin('file_managed', 'f', 'f.fid = i.field_image_fid');
$query->union($query2, 'UNION');
$result = $query
->execute();
The problem is that this query is returning only the first three occurrences of people or organization combined. So if there are three people returned by the query, I will not be able to see any organization.
I also tried something like this:
$query = db_query('
SELECT p.title,p.type
FROM node as p
WHERE p.type = :type
LIMIT 4'
, array(':type' => 'people',':type1' => 'organization'))->fetchAll();
$query2 = db_query('
SELECT o.title,o.type
FROM node as o
WHERE o.type = :type1
LIMIT 4'
, array(':type' => 'people',':type1' => 'organization'))->fetchAll();
$query->union($query2, 'UNION');
or like this:
$result = db_query('
SELECT title,type
FROM {node}
WHERE type = :type
LIMIT 4
UNION ALL
SELECT title,type
FROM {node}
WHERE type = :type1
LIMIT 4'
, array(':type' => 'people',':type1' => 'organization'))->fetchAll();
But these two approaches are only returning the 4 people and no organizations, I mean never..
Thank you if you can help!

Union statement in query its actually get record from first query and merge result record of first query to second query result record.
For example first case don't used limit in query of union its giving all record.
$query1 = db_select('node', 'n')
->fields('n', array('nid','title','type'))
->condition('type', array('people'));
$query2 = db_select('node', 'n')
->fields('n', array('nid','title','type'))
->condition('type', array('organization'));
$query = Database::getConnection()
->select($query1->union($query2))
->fields(NULL, array('nid','title', 'type'))
->orderBy('nid');
$result = $query->execute()->fetchAll();
From above query it get records from first query append before records of second query
As per the you example If you wanted to get 4 records from people content type and 3 record from organizations content type
used following query to solve your issue
$query1 = db_select('node', 'n')
->fields('n', array('nid','title','type'))
->condition('type', array('people'))
->range(0,4);
$query2 = db_select('node', 'n')
->fields('n', array('nid','title','type'))
->condition('type', array('organization'));
->range(0,7);
$query = Database::getConnection()
->select($query1->union($query2))
->fields(NULL, array('nid','title', 'type'))
->orderBy('nid');
$result = $query->execute()->fetchAll();

Related

SQL Joining tables; can you repeat columns?

I'm trying to compose a view that we can use to export our inventory.
I have two tables:
Inventory, which contains the columns Description, Year, Make, Model, and Serial.
Pictures, which contains the columns DocumentBody, MimeType, Serial, and Last Modified.
I'd like to make a view that has all columns from Inventory, and also adds columns for x amount of Pictures related to Serial number.
So if there were two pictures with the same serial number, the resultant table would include these fields:
Description, Year, Make, Model, Serial, DocumentBody1, MimeType1, Last Modified1, DocumentBody2, MimeType2, Last Modified2.
For those Inventory items that only have one picture, the second picture columns would all be null.
Is this something I can even do? From what I'm reading about joins, it doesn't seem possible.
As others have said, you should probably evaluate whether you actually need the view you think you need. But if you really want it, you could use PIVOT in MSSQL:
WITH BaseData AS
(
SELECT Serial
,DocumentBody
,MimeType
,LastModified
,ROWNUMBER() OVER(PARTITION BY Serial ORDER BY LastModified) AS RowNum
FROM Pictures) AS t
),
DocumentPivot AS (
SELECT
Serial
,DocumentBody
,'DocumentBody' + RowNum AS ColumnName
FROM BaseData
),
MimePivot AS (
SELECT
Serial
,MimeType
,'MimeType' + RowNum AS ColumnName
FROM BaseData
),
ModifiedPivot AS (
SELECT
Serial
,LastModified
,'LastModified' + RowNum AS ColumnName
FROM BaseData
)
SELECT Description
,Year
,Make
,Model
,Inventory.Serial
,DocumentBody1
,MimeType1
,LastModified1
,DocumentBody2
,MimeType2
,LastModified2
,...
,LastModified10
FROM Inventory
LEFT OUTER JOIN (
SELECT Serial
,DocumentBody1
,DocumentBody2
,...
,DocumentBody10
FROM DocumentPivot
PIVOT (MAX(DocumentBody) FOR ColumnName IN (DocumentBody1, DocumentBody2, ..., DocumentBody10)) AS P1
) AS Documents
ON Documents.Serial=Inventory.Serial
LEFT OUTER JOIN (
SELECT Serial
,MimeType1
,MimeType2
,...
,MimeType10
FROM MimePivot
PIVOT (MAX(MimeType) FOR ColumnName IN (MimeType1, MimeType2, ..., MimeType10)) AS P2
) AS Mimes
ON Mimes.Serial=Inventory.Serial
LEFT OUTER JOIN (
SELECT Serial
,LastModified1
,LastModified2
,...
,LastModified10
FROM ModifiedPivot
PIVOT (MAX(LastModified) FOR ColumnName IN (LastModified1, LastModified2, ..., LastModified10)) AS P3
) AS Modifieds
ON Modifieds.Serial=Inventory.Serial
Select inventory.*, count(pictures.serial) as picture_count From inventory Left Join pictures On inventory.serial = pictures.serial Where [your where statement]
Use Left Join in case there are no pictures at all. This way you still get back a result.
Update
Actually, after reading your question again, it seems you just want to extend your search results with each additional picture in the system. That's not the best way to do this. The best you can do is just get a row returned for each pic that's in the system.
Select inventory.*, pictures.DocumentBody, pictures.MimeType, pictures.Serial, pictures.Last_Modified From inventory Left Join pictures On inventory.serial = pictures.serial Where [your where statement]
Since there is no "Group By" clause, this will give you 1 row for each picture. Then you can just loop through the results.
Also
There are ways to do this by making temp tables, looping through results within a stored procedure, creating new columns (DocumentBody1, DocumentBody2, etc) for each picture result and adding the data to the new fields, then querying the temp table. But that's a lot to go through I would think.
thanks for the help everyone. In the end, I ended up using PHP to accomplish this with the following code:
<?php
date_default_timezone_set('America/Edmonton');
$serverName = "database";
$connectionInfo = array( "Database"=>"CRM_MSCRM");
$conn = sqlsrv_connect( $serverName, $connectionInfo);
if( $conn === false )
{
echo "Unable to connect.\n\n";
die( print_r( sqlsrv_errors(), true));
}
else
{
echo "Connected. Selecting trucks...\n\n";
}
$tsql = "SELECT * FROM CRM_MSCRM.dbo.Trader_Export_Simple";
$stmt = sqlsrv_query( $conn, $tsql);
if( $stmt === false )
{
echo "Error executing query.\n\n";
die( print_r( sqlsrv_errors(), true));
}
$csvData = array();
while ($row = sqlsrv_fetch_array($stmt))
{
$count = 1;
$mainpicsql = "SELECT * FROM CRM_MSCRM.dbo.TruckImages WHERE Serial = '".$row[0]."' AND MainPic = 1";
$mainpicstmt = sqlsrv_query( $conn, $mainpicsql);
while ($mainpicrow = sqlsrv_fetch_array($mainpicstmt))
{
$truck = $mainpicrow[1];
$mainfilename = $truck ."-". $count . ".png";
file_put_contents($mainfilename, base64_decode($mainpicrow[0]));
$mainpicdate = $mainpicrow[3]->format("d/m/Y h:m:s");
$mainfilename = "http://images.website/images/".$mainfilename;
echo $mainpicdate."\n";
}
$picsql = "SELECT * FROM CRM_MSCRM.dbo.TruckImages WHERE Serial = '".$row[0]."' AND MainPic = 0";
$picstmt = sqlsrv_query( $conn, $picsql);
$extrapicsdate = "";
$filenames = "";
while ($picrow = sqlsrv_fetch_array($picstmt))
{
$count++;
$filename = $picrow[1] ."-". $count . ".png";
file_put_contents($filename, base64_decode($picrow[0]));
$picdate = $picrow[3]->format("d/m/Y h:m:s");
$filenames .= "http://images.website/images/".$filename.";";
$extrapicsdate .= $picdate.";";
}
$filenames = rtrim($filenames, ";");
$extrapicsdate = rtrim($extrapicsdate, ";");
echo $filenames."\n";
echo $extrapicsdate."\n";
if ($truck != "") {
$csvData[] = array($truck, $mainfilename, $mainpicdate, $filenames, $extrapicsdate);
}
if ($filenames != "")
{
$filenames = "";
}
if ($extrapicsdate != "")
{
$extrapicsdate = "";
}
echo "Next truck...\n\n";
$truck = "";
$mainfilename = "";
$mainpicdate = "";
}
$fp = fopen('file.csv', 'w');
foreach ($csvData as $fields) {
fputcsv($fp, $fields);
}
//print_r($csvData);
sqlsrv_free_stmt( $stmt);
sqlsrv_free_stmt( $picstmt);
sqlsrv_close( $conn);
?>
this gets the files out but I still have to merge the resultant CSV with the main "information" CSV.

drupal 7 , multiple FROM tables, need a very sample example

Someone can give me an example where there is a multiple 'FROM' table ?
I don't understand corectly the documentation (thanks my bad english lvl -_-).
I would like use a query like :
SELECT a.one a.two b.one FROM {table1} a, {table2} b WHERE a.one = b.one
thx =)
You can use join:
http://drupal.org/node/1848348
<?php
$query = db_select('table1', 'a');
$query->join('table2', 'b', 'a.one = b.one');
$query->fields('a', array('one', 'two'));
$query->fields('b', array('one'));
$result = $query->execute();
?>
Here is asked and answered:
https://drupal.stackexchange.com/questions/27723/drupal-7-join-two-tables
More about dynamic queries
http://drupal.org/node/310075

Drupal 7, query with WHERE and AND

I would use this query :
SELECT g.id_site, g.commune, g.latitude, g.longitude, g.altitude, g.date, g.id_fiche, a.essence, e.nom_espece, e.effectif
FROM general g, arbre a, espece e
WHERE g.nom = a.nom AND g.id_fiche = e.id_fiche AND g.id_fiche = a.id_fiche
with the drupal 7 API. I try this :
$query = db_select('general' ,'g');
$query -> join ('arbre','a','a.nom = g.nom');
$query -> join ('espece','e','e.nom = a.nom');
$query -> fields ('g', array('commune','latitude','longitude','altitude','date','id_fiche'));
$query -> fields ('a', array('essence'));
$query -> condition ('g.id_fiche','e.id_fiche','=');
$query -> condition ('g.id_fiche','a.id_fiche','=');
But I get no result :/ the problem is these two lines :
$query -> condition ('g.id_fiche','e.id_fiche','=');
$query -> condition ('g.id_fiche','a.id_fiche','=');
If I comment this two lines, I have a result (but without the WHERE clause). What should I do for properly use the WHERE clause ?
thx =)
Ignoring the query issues and focusing on just the answer:
Try This:
I think you simply have to define all the fields you are actually going to use.
If that still does not work try use the Where Clause
$query = db_select('general' ,'g');
$query -> join ('arbre','a','a.nom = g.nom');
$query -> join ('espece','e','e.nom = a.nom');
$query -> fields ('e', array('id_fiche','nom'));
$query -> fields ('g', array('commune','latitude','longitude','altitude','date','id_fiche','nom'));
$query -> fields ('a', array('essence','nom'));
$query -> condition ('g.id_fiche','e.id_fiche','=');
$query -> condition ('g.id_fiche','a.id_fiche','=');
You can use db_query(), i found this method more usable.

How to add IN statement with JOIN ON clause in tablegateway ZF2

Getting all result so far until I added a 'IN' statement with join ON clause
My code is something like this:
// $this->table = 'category';
$sql = $this->getSql();
$select = $sql->select();
$select->join('user_category_subscriptions', 'user_category_subscriptions.category_id = category.id AND user_category_subscriptions.status IN (1,2,3,4)', array(), 'left');
$select->where(array('category.user_id = 2 OR (user_category_subscriptions.status IN (2,4))'));
Error
ZEND\DB\ADAPTER\EXCEPTION\INVALIDQUERYEXCEPTION
File:E:\htdocs\myproj\vendor\zendframework\zendframework\library\Zend\Db\Adapter\Driver\Pdo\Statement.php:245
Message:Statement could not be executed
Used 'echo $select->getSqlString();' to print the query:
SELECT "categories".* FROM "categories"
LEFT JOIN "user_category_subscriptions"
ON "user_category_subscriptions"."category_id" = "categories"."category_id" AND "user_category_subscriptions"."status" IN ("1""," "2""," "3""," "4")
WHERE category.user_id = 2 OR (user_category_subscriptions.status IN (2,4))
So the problem is zend's auto converting (1,2,3,4) into ("1""," "2""," "3""," "4")
Any idea to solve this? Thanks
I think you can pass an expression instead of the join string:
// $this->table = 'category';
$sql = $this->getSql();
$select = $sql->select();
$join = new Expression('user_category_subscriptions.category_id = category.id AND user_category_subscriptions.status IN (1,2,3,4)');
$select->join('user_category_subscriptions', $join, array(), 'left');
$select->where(array('category.user_id = 2 OR (user_category_subscriptions.status IN (2,4))'));
try this
$select->where
->AND->NEST->equalTo('category.user_id', 2)
->addPredicate(new Sql\Predicate\In('user_category_subscriptions.status', array(2,4)));

CakePHP : Search date from form helper

I use form helpers to show drop down select date. My problem is, i can't compare date from ms sql with form data.
My View :
<?php echo $this->Form->input('collect_date', array('label' => 'Collect Date','type'=>'date','dateFormat'=> 'DMY','minYear' => date('Y'),'maxYear' => date('Y')+1));?>
My Controller
$status =array(0,2,4);
$find_date = $this->data['Transaction']['collect_date'];
$field = array('Transaction.status','Catalogue.title', 'Catalogue.author', 'Catalogue.isbn', 'Location.rack');
$condition = array('OR' => array('AND' =>array('Transaction.return_date <'=> $find_date,'Transaction.status '=> '3'),array('Transaction.status'=> $status)));
$data = $this->Catalogue->Transaction->find('count',array('fields' => $field,'conditions'=>$condition,'order'=>array('Transaction.id desc') ));
$this->set('Found', $data);
And the sql dump
SELECT COUNT(*) AS [count] FROM [transactions] AS [Transaction]
LEFT JOIN [catalogues] AS [Catalogue] ON ([Transaction].[catalogue_id] = [Catalogue].[id])
LEFT JOIN [users] AS [User] ON ([Transaction].[user_id] = [User].[id])
WHERE (((([Transaction].[return_date] < ('01', '09', 2013))
AND ([Transaction].[status] = 3))) OR ([Transaction].[status] IN (0, 2, 4)))
As you can see the date format ('01', '09', 2013). But when try to convert use this
'Transaction.return_date <'=> date('Y-m-d', strtotime($find_date))
it show error:
Warning (2): strtotime() expects parameter 1 to be string, array given [APP\Controller\CataloguesController.php, line 66]
and the sql show:
[Transaction].[return_date] < '1970-01-01'
You could use:
$date = DateTime::createFromFormat('d/m/y', implode('/', $find_date));
'Transaction.return_date <'=> $date->format('Y-m-d');
Edit:
I think the way its meant to be 'flattened' is with
$this->Model->deconstruct('find_date', $find_date);
However, last time i tried to use this, i couldn't get it to work properly, so i went with the above.
http://api21.cakephp.org/class/model#method-Modeldeconstruct
Try converting return date using UNIX_TIMESTAMP in sql query and then comparing it with
strtotime($find_date).

Resources