I got this php script that should only show one user per p.account_id, but isn't working like that.
If the user have two others users in the same account, it shows as well. I couldn't think anything to fix that.
$groupConfig = [
'Administrator' => ['groupId' => 5, 'accountType' => [5]],
'Community Manager' => ['groupId' => 4, 'accountType' => [4]],
'Gamemaster' => ['groupId' => 3, 'accountType' => [4]],
'Tutor' => ['groupId' => 1, 'accountType' => [2, 3]],
];
foreach ($groupConfig as $groupTitle => $group) {
$k = $SQL->query('SELECT
`p`.`name`,
`p`.`lastlogin`,
`p`.`id`,
`p`.`group_id`,
`a`.`type`
FROM
`players` AS `p`
LEFT JOIN `accounts` AS `a`
ON `a`.`id` = `p`.`account_id`
WHERE `a`.`type`
IN (' . implode(',', $group['accountType']) . ')
AND group_id = ' . $group['groupId'] . '
ORDER BY
`group_id`
DESC')->fetchAll();
}
You can try using GROUP BY clause to group by the identical value (p.account_id).
Why does your data allow multiple users per same account?
$groupConfig = [
'Administrator' => ['groupId' => 5, 'accountType' => [5]],
'Community Manager' => ['groupId' => 4, 'accountType' => [4]],
'Gamemaster' => ['groupId' => 3, 'accountType' => [4]],
'Tutor' => ['groupId' => 1, 'accountType' => [2, 3]],
];
foreach ($groupConfig as $groupTitle => $group) {
$k = $SQL->query('SELECT
`p`.`name`,
`p`.`lastlogin`,
`p`.`id`,
`p`.`group_id`,
`a`.`type`
FROM
`players` AS `p`
LEFT JOIN `accounts` AS `a`
ON `a`.`id` = `p`.`account_id`
WHERE `a`.`type`
IN (' . implode(',', $group['accountType']) . ')
AND group_id = ' . $group['groupId'] . '
GROUP BY `p`.`account_id`
ORDER BY
`group_id`
DESC')->fetchAll();
}
Change the fetchAll to fetch, or add a "LIMIT" to the SQL query. It's absolutely normal that you get multiple results. You aren't querying from table accounts, you are querying the table players. So your result will contain as many rows as the account had players. The best way would be to query the right table, but you could also solve it with the fixes mentioned before.
Related
I have a fairly complex query, which includes a SQL count in a subquery join.
The problem is Cake's query builder returns a count of 23, but I know this is incorrect. So I debugged the SQL query it built, and ran it directly in MySQL. The strange thing is the raw SQL being generated by Cake is correct and it returns a count of 1 when I run it, which is also correct.
I am hesitant to call this a bug with the query builder, but it seems like the way Cake is interpreting the SQL result is wrong somehow. I can't see what is wrong here, or how this is possible.
I have stripped down the query here to the very basics which are causing the issue.
Query object
This is what is being generated by the query builder. The SQL is correct.
object(Cake\ORM\Query) {
'(help)' => 'This is a Query object, to get the results execute or iterate it.',
'sql' => 'SELECT Orders.order_id AS `Orders__order_id`, all_orders.total AS `Customers__orders_placed`, Customers.id AS `Customers__id`, Customers.email AS `Customers__email`, FROM orders Orders LEFT JOIN (SELECT T.customer_id AS `T__customer_id`, COUNT(*) AS `total` FROM orders T GROUP BY customer_id ) all_orders ON all_orders.T__customer_id = :c0 LEFT JOIN customers Customers ON Customers.id = (Orders.customer_id) WHERE Orders.order_id = :c1',
'params' => [
':c0' => [
'value' => 'Orders.customer_id',
'type' => null,
'placeholder' => 'c0'
],
':c1' => [
'value' => '12345',
'type' => 'string',
'placeholder' => 'c1'
]
],
// ...
Cake's generated SQL (formatted for readability)
SELECT
Orders.order_id AS `Orders__order_id`,
all_orders.total AS `Customers__orders_placed`,
Customers.id AS `Customers__id`,
Customers.email AS `Customers__email`,
FROM orders Orders
LEFT JOIN (
SELECT T.customer_id AS `T__customer_id`,
COUNT(*) AS `total`
FROM orders T
GROUP BY customer_id
) all_orders ON all_orders.T__customer_id = :c0
LEFT JOIN customers Customers ON Customers.id = (Orders.customer_id)
WHERE Orders.order_id = :c1
Query builder result
Note that customer->orders_placed = 23
object(App\Model\Entity\Order) {
'order_id' => '12345',
'customer' => object(App\Model\Entity\Customer) {
'orders_placed' => '23',
'id' => (int) 34727,
'email' => 'xxxxx#example.com',
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Customers'
},
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Orders'
}
If I run the query myself in MySQL
The only thing I change from Cake's SQL is I fill in the parameters for :c0 and :c1.
SELECT
Orders.order_id AS `Orders__order_id`,
all_orders.total AS `Customers__orders_placed`,
Customers.id AS `Customers__id`,
Customers.email AS `Customers__email`
FROM orders Orders
LEFT JOIN (
SELECT T.customer_id AS `T__customer_id`,
COUNT(*) AS `total`
FROM orders T
GROUP BY customer_id
) all_orders ON all_orders.T__customer_id = Orders.customer_id
LEFT JOIN customers Customers ON Customers.id = (Orders.customer_id)
WHERE Orders.order_id = 12345
Then when I run it in MySQL, this is the result:
Orders__order_id | Customers__orders_placed | Customers__id | Customers__email
-------------------------------------------------------------------------------
12345 | 1 | 34727 | xxxxx#example.com
How can the count be different when I'm literally copy/pasting the SQL generated by Cake?
Cake version is 3.3.16
So I need to write SQL statement using CakePhp ORM, but I have problem how to write in Cakephp GROUP BY IF NULL(condition).
Here is SQL Statement:
SELECT COUNT(*) FROM
(
SELECT i.id
FROM items i
INNER JOIN orders o ON i.order_id = o.id
WHERE (
(i.type = 1 AND i.status = 50) OR ((i.type = 2 OR i.type = 4) AND i.status = 60))
AND i.date_of_completion IS NOT NULL
GROUP BY IFNULL(i.vessel_change_identifier, i.id)
) AS temptbl;
This is my CakePhp Query
$query = TableRegistry::get('Items')
->find('all')
->innerJoinWith('Orders', function ($q) {
return $q->where(['Orders.id' => 'Items.order_id']);
})
->Where([
'Items.type' => 1,
'Items.status' => 50,
])
->orWhere([
'Items.type IN ' => [2, 4],
'Items.status' => 60,
])
->andWhere([
'Items.date_of_completion IS NOT' => NULL
]);
$query->select([
'count' => $query->func()->count('*')
]);
Thank you!
Try to use ->ifnull()
$query = TableRegistry::get('Items')
->find()
->innerJoinWith('Orders')
->where([
'Items.type' => 1,
'Items.status' => 50,
])
->orWhere([
'Items.type IN ' => [2, 4],
'Items.status' => 60,
])
->andWhere([
'Items.date_of_completion IS NOT' => NULL
]);
$query
->group(
$query
->func()
->ifnull([
'Items.vessel_change_identifier',
'Items.id'
])
);
$query->select([
'count' => $query->func()->count('*')
]);
Use it and enjoy it :D
I am building an application using CakePHP and I am stuck on a problem retrieving data using a series of joins. In the simplified example below the join with the alias Delivery could have more than record and I want to bring back the record with a max value in a particular field in that table.
$inv_conditions = array( 'Invoice.invoice_date >=' => $DateFormatter->dateForDB($dateFrom),
'Invoice.invoice_date <=' => $DateFormatter->dateForDB($dateTo),
'Invoice.id >' => 385380 );
$join = array(array(
'table' => 'jobs',
'alias' => 'Jobs',
'type' => 'LEFT',
'conditions' => array('Invoice.job_id = Jobs.JOB_ID' )
),
array(
'table' => 'functional',
'alias' => 'Delivery',
'type' => 'LEFT'
'conditions'=> array('AND ' => array('Invoice.job_id = Delivery.JOB',
'Delivery.TYPE_STAGE = 1')
)
)
);
$invoices = $this->Invoice->find("all", array(
"fields" => array(
'Invoice.id',
'Invoice.job_id',
'Invoice.invoice_no',
'Invoice.consolidated_type',
'Invoice.customer_id_tbc',
'Invoice.invoice_date',
'Invoice.invoice_reference',
'Invoice.invoice_req',
'Jobs.PAYMENT_TYPE',
'Jobs.CUSTOMER',
'Jobs.MOST_RELEVANT_LINE',
'Delivery.DEPARTURE_DATE',
'Delivery.CNOR_CNEE_NAME',
'Delivery.TOWN_NAME',
),
"conditions" => $inv_conditions,
"joins" => $join
)
);
}
I can do this with SQL no problem as follows:
SELECT
jobs.JOB_ID,
jobs.CUSTOMER,
functional.JOB_LINE_ORDER,
functional.CNOR_CNEE_NAME,
functional.TOWN_NAME
FROM jobs JOIN functional ON
jobs.JOB_ID = 'M201409180267'
AND
functional.JOB = jobs.JOB_ID
AND
functional.TYPE_STAGE = 0
AND
functional.JOB_LINE_ORDER =
(SELECT MAX(JOB_LINE_ORDER) FROM functional
WHERE functional.JOB = 'M201409180267' AND functional.TYPE_STAGE = 0)
I have tried using the following to the conditions array:
'conditions' => array('AND ' =>
array( 'Invoice.job_id = Delivery.JOB',
'Delivery.TYPE_STAGE = 1'
'Delivery.JOB_LINE_ORDER = MAXIMUM(Delivery.JOB_LINE_ORDER)' )
)
This does bring back results but not the correct ones and the resulting SQL generated by Cake does have a select in the where clause. Is there a way of doing this when retrieving data in cake where the sql statement created will have a select in the where clause.
Any suggestions would be greatly appreciated.
Thanks
Bas
You need to use subquery to generate the select in the where clause. You can create a method in your model that does this and call it from your controller.
$subQuery = $db->buildStatement(
array(
'fields' => array(''),
'table' => $db->fullTableName($this),
'alias' => 'aliasName',
'limit' => null,
'offset' => null,
'joins' => array(),
'conditions' => $conditionsArray,
'order' => null,
'group' => null
),
$this
);
$subQuery = ' <<YOUR MAIN QUERY CONDITION (' . $subQuery . ') >>';
$subQueryExpression = $db->expression($subQuery);
$conditions[] = $subQueryExpression;
return $this->Model->find('list', compact('conditions'));
Here is the query string.
$query = "SELECT t.id, t.assignee, t.owner,
d.code, d.status, d.target_completion_date,
d.target_extension_date, d.submission_date, d.approval_date,
d.revision_start_date, d.revision_completion_date, d.message,
ty.name, f.orig_name, f.new_name,
b.payment_date, b.discount, b.total_cost, b.amount_payed, b.edit_level,
b.billing_type, b.pages, b.words
FROM tasks t
INNER JOIN details d ON t.detail_id = d.id
INNER JOIN billing b ON t.billing_id = b.id
INNER JOIN TYPE ty ON d.document_type_id = ty.id
INNER JOIN files f ON t.file_id = f.id
WHERE t.assignee = 'argie1234'";
And this is the array i would like the query result to turn into.
$user = array('allTask'=>array(array('taskid' => 1,
'assignee'=>'argie1234',
'owner'=>'austral1000',
'details' => array( 'code' => 'E',
'status'=>'TC',
'targetCompletionDateUTC'=>'1379401200',
'targetExtentionDateUTC'=>'1379401200',
'submissionDateUTC'=>'1379401200',
'approvalDateUTC'=>'1379401200',
'revisionStartDateUTC'=>'1379401200',
'revisionCompletionDateUTC'=>'1379401200',
'messageToEditor'=>'Please work on it asap.',
'documentType' => 'Thesis'),
'file' => array('orig_name' =>'originalname.docx',
'new_name' => 'newname.docx'),
'billing'=>array('paymentDate'=>'July 26,2013 12:40',
'discount' => '0',
'totalRevisionCharge' => '$20.00',
'totalAmountPayed' => '$20.00',
'revisionLevel' => '1',
'chargeType'=> '1',
'numPages' => '60',
'numWords' => '120,000' ) ),
array('taskid' => 12,
'assignee'=>'argie1234',
'owner'=>'usaroberto',
'details' => array( 'code' => 'E',
'status'=>'TC',
'targetCompletionDateUTC'=>'1379401200',
'targetExtentionDateUTC'=>'1379401200',
'submissionDateUTC'=>'1379401200',
'approvalDateUTC'=>'1379401200',
'revisionStartDateUTC'=>'1379401200',
'revisionCompletionDateUTC'=>'1379401200',
'messageToEditor'=>'Please work on it asap.',
'documentType' => 'Thesis'),
'file' => array('orig_name' => 'originalname.docx',
'new_name' => 'newname.docx'),
'billing'=>array('paymentDate'=>'July 26,2013 12:40',
'discount' => '0',
'totalRevisionCharge' => '$20.00',
'totalAmountPayed' => '$20.00',
'revisionLevel' => '1',
'chargeType'=> '1',
'numPages' => '60',
'numWords' => '120,000' ) ),
'account' => array( 'username' => 'marooon55',
'emailadd' => 'marooon#yahoo.com',
'firstname' => 'Maroon',
'initial' => 'E',
'lastname' => 'Young',
'country' => 'Australia',
'gender' => 'M',
'password' =>'360e2801190744a2af74ef6cbfdb963078b59709',
'activationDate' => '2013-09-13 14:30:34') );
How can i create the above array? I sure know how to define multi dimensional array, regretfully though i am having difficulty creating this complex array dynamically. As a beginner i don't even know where to begin.
Here is an example that might help you out. Try starting with simple multi dimensional arrays, once you get a hold of it, you can move onto building complex ones. You will then find that the array you want to build is not really difficult than you initially thought it to be.
$mycomplexarray = array('key1' => array('val1', 'val2'),
'key2' => array('val3', 'val4' => array('val5', 'val6')
)
);
You could create the array just as you have here. I'm not gonna write the whole thing out, but something like this...
$result = $mysqli->query($query); // however you query the db is up to you.
$row = $result->fetch_assoc(); //same as query use your prefered method to fetch
$user = array('allTask'=>array(array('taskid' => $row['id'],
'assignee'=>$row['assignee'],
'owner'=>$row['owner'],
'details' => array( 'code' => $row['code'],
'status'=>$row['status'],
...etc, Hope this makes sense for you.
Set up a structure array first that defines which columns will be stored in a sub array like
$struc=array('Id'->0, 'assignee'->0, 'owner'->0,
'code'->'detail', 'status'->'detail', 'target_completion_date'->'detail',
'target_extension_date'->'detail', 'submission_date'->'detail', 'approval_date'->'detail',
'revision_start_date'->'detail', 'revision_completion_date'->'detail', 'message'->'detail',
'name'->'file', 'orig_name'->'file', 'new_name'->'file',
'payment_date'->'billing', 'discount'->'billing', 'total_cost'->'billing', 'amount_payed'->'billing', 'edit_level'->'billing', 'billing_type'->'billing', 'words');
In your while ($a=mysqli_fetch_assoc($res)) loop you can now use this structure to decide whether you want to store an element directly in your target array or whether you want to place it in the subarray named in this structure array. Like
$res=mysqli_query($con,$sql);
$arr=array();
while($a=mysqli_fetch_assoc($res)) {
// within result loop: $a is result from mysqli_fetch_assoc()
$ta=array(); // temp array ...
foreach ($a as $k => $v){
if ($struc[$k]) $ta[struc[$k]][$k]=$v;
else $ta[$k]=$v;
}
$arr[]=$ta; // add to target array
}
This is the complete code, no more is needed. It was typed up on my iPod, so it is NOT tested yet.
The generated array should be equivalent to your $user['allTask'] array.
I'm trying to join a Users table to my curent hasMany Through table which has Interest and User model id's.
Below is the find query with options:
$params = array(
'fields' => array('*', 'COUNT(DISTINCT(InterestsUser.interest_id)) as interest_count'),
'limit' => 15,
'recursive' => -1,
'offset' => $offset,
'conditions' => array('InterestsUser.interest_id' => $conditions),
'group' => array('InterestsUser.user_id'),
'order' => array('interest_count DESC', 'InterestsUser.user_id ASC', 'InterestsUser.interest_id ASC'),
'joins' => array(
array('table' => 'users',
'alias' => 'User',
'type' => 'LEFT',
'conditions' => array(
'User.id' => 'InterestsUser.user_id',
)
)
)
);
$results = $this->InterestsUser->find('all', $params);
This returns InterestsUser table fine but does not return any values for Users table. It only returns field names.
What could be wrong?
UPDATE:
OK, above is generating below SQL which I got from Cake's datasources sql log:
SELECT *, COUNT(DISTINCT(InterestsUser.interest_id)) as interest_count
FROM `interests_users` AS `InterestsUser`
LEFT JOIN users AS `User` ON (`User`.`id` = 'InterestsUser.user_id')
WHERE `InterestsUser`.`interest_id` IN (3, 2, 1)
GROUP BY `InterestsUser`.`user_id`
ORDER BY `interest_count` DESC, `InterestsUser`.`user_id` ASC, `InterestsUser`.`interest_id` ASC
LIMIT 15
Why is users table values returning NULL only for all fields?
UPDATE:
OK I tried below but this is working fine...What am I missing here!!??
SELECT * , COUNT( DISTINCT (
interests_users.interest_id
) ) AS interest_count
FROM interests_users
LEFT JOIN users ON ( users.id = interests_users.user_id )
WHERE interests_users.interest_id
IN ( 1, 2, 3 )
GROUP BY interests_users.user_id
ORDER BY interest_count DESC
LIMIT 15
The array syntax for join conditions should be like the following
array('User.id = InterestsUser.user_id')
as opposed to array('User.id' => 'InterestsUser.user_id'). For more, see http://book.cakephp.org/view/1047/Joining-tables.