Home » Php » php – Laravel Query Builder Subtract COUNT by Subquery in query grouped by ID

php – Laravel Query Builder Subtract COUNT by Subquery in query grouped by ID

Posted by: admin February 25, 2020 Leave a comment

Questions:

I got stuck in this problem I encountered in Laravel query.

Firstly I have this query that i use in order to get transactions

$transactions = Transaction::select(
    'customers.member_id as member_id',
    DB::raw('
        CASE
            WHEN transactions.customer_id IS NOT NULL
                THEN
                    customers.name 
            ELSE 
                "Tanpa pelanggan" 
        END AS customer_name
    '),
    'customers.phone as customer_phone',
    DB::raw('COUNT(transactions.id) as sum_qty'),
    DB::raw('SUM(final_amount) as sum_amount')
)
->join('outlets', 'outlets.id', '=', 'transactions.outlet_id')
->leftJoin('customers', 'customers.id', '=', 'transactions.customer_id')
->where('outlets.company_id', '=', $companyID)
->where(function ($q) use ($permittedOutlets) {
    if (!empty($permittedOutlets)) {
        $q->whereIn('transactions.outlet_id', $permittedOutlets);
    }
})
->where(function ($query) use ($companyID, $outletID, $startDate, $endDate, $filterData) {
    if ($outletID != 0) {
        $query->where('transactions.outlet_id', '=', $outletID);
    }
    if (!empty($startDate) && !empty($endDate)) {
        if ($startDate == $endDate) {
            $query->whereDate(deviceTimestamp(), '=', $startDate);
        } else {
            $query->whereDate(deviceTimestamp(), '>=', $startDate)
                ->whereDate(deviceTimestamp(), '<=', $endDate);
        }
    }
    if ($filterData != "") {
        $query->where(function ($q) use ($filterData) {
            $q->where('customers.name', 'like', '%' . $filterData . '%')
                ->orWhere('customers.phone', 'like', '%' . $filterData . '%');
        });
    }
})
->isSuccessAndVoidedInDifferentDate()
->orderBy($sortField, $sort)
->groupBy('transactions.customer_id');

and for isSuccessAndVoidedInDifferentDate Transactions Model scope

$query->whereRaw('
    CASE 
        WHEN transactions.void_id IS NOT NULL
            THEN 
                EXISTS (
                    SELECT *
                    FROM transactions as transactions2
                    WHERE transactions.void_id = transactions2.id
                    AND transactions.created_at <> transactions2.created_at
                    LIMIT 1
                )                
        WHEN transactions.status = \''.self::STATUS_VOID.'\'
            THEN 
                EXISTS (
                    SELECT *
                    FROM transactions as transactions2
                    WHERE transactions.id = transactions2.void_id
                    AND transactions.created_at <> transactions2.created_at
                    LIMIT 1
                )   
        ELSE 
                TRUE
    END
');

For Raw MySql Query

SELECT 
    `customers`.`member_id` AS `member_id`,
    CASE
        WHEN transactions.customer_id IS NOT NULL THEN customers.name
        ELSE 'Tanpa pelanggan'
    END AS customer_name,
    `customers`.`phone` AS `customer_phone`,
    COUNT(transactions.id) AS sum_qty,
    SUM(final_amount) AS sum_amount
FROM
    `transactions`
        INNER JOIN
    `outlets` ON `outlets`.`id` = `transactions`.`outlet_id`
        LEFT JOIN
    `customers` ON `customers`.`id` = `transactions`.`customer_id`
WHERE
    `outlets`.`company_id` = 153113
        AND (`transactions`.`outlet_id` IN (9164))
        AND (DATE(ADDDATE(transactions.device_timestamp,
            INTERVAL transactions.timezone HOUR)) >= '2020-02-01'
        AND DATE(ADDDATE(transactions.device_timestamp,
            INTERVAL transactions.timezone HOUR)) <= '2020-02-29')
        AND CASE
        WHEN
            transactions.void_id IS NOT NULL
        THEN
            EXISTS( SELECT 
                    *
                FROM
                    transactions AS transactions2
                WHERE
                    transactions.void_id = transactions2.id
                        AND transactions.created_at <> transactions2.created_at
                LIMIT 1)
        WHEN
            transactions.status = 'void'
        THEN
            EXISTS( SELECT 
                    *
                FROM
                    transactions AS transactions2
                WHERE
                    transactions.id = transactions2.void_id
                        AND transactions.created_at <> transactions2.created_at
                LIMIT 1)
        ELSE TRUE
    END
        AND `transactions`.`deleted_at` IS NULL
GROUP BY `transactions.customer_id`
ORDER BY `transactions.customer_id` ASC

All transactions have two different status. The status is either success or voided. Transactions types are divided by two categories. First category is transaction with customer and second transaction is without customer. Transaction without customer will be selected as “Tanpa pelanggan” in this case. All transactions here are being grouped by transactions.customer_id.

This question is focusing on

   DB::raw('COUNT(transactions.id) as sum_qty'),

at the select part. For simpler understanding the where clauses of company, outlets and date here can be ignored. I am trying to make a condition where the count in the query will return count of success transactions subtracted by count of voided transactions grouped by transactions.customer_id. I am kinda confused about whether I should make subquery with all same where conditions as the main query which is kinda repetitive or any better solution I haven’t found.

Example case :
With data consists of :

1 voided transaction
rest transactions are success

Expected result :

member_id | customer_name | customer_phone| sum_qty | sum_ammount

NULL      | Sam           | 123xxxx       | 1       | 20720

NULL      | Tanpa Pelanggan| -            | 2       | 28490

Actual result :

member_id | customer_name | customer_phone| sum_qty | sum_ammount

NULL      | Sam           | 123xxxx       | 1       | 20720

NULL      | Tanpa Pelanggan| -            | 3       | 51800

My question is what’s the best way to solve this problem?

Thank you in Advance!

How to&Answers:

Found the solution !

DB::raw('SUM(CASE WHEN transactions.status != \'void\' THEN 1 ELSE 0 END) - SUM(CASE WHEN transactions.status = \'void\' THEN 1 ELSE 0 END) as sum_qty')

It’s to actually use SUM and add CASE condition inside SUM function.