简体   繁体   中英

More efficient way of counting occurrences of different column values?

I've seen some related questions, but they didn't seem to be the same situation, so help would be appreciated.

I have the current query:

SELECT
    COUNT(CASE WHEN training_enhancments.reason_id = 0 THEN 1 END) AS '0Count',
    COUNT(CASE WHEN training_enhancments.reason_id = 1 THEN 1 END) AS '1Count',
    COUNT(CASE WHEN training_enhancments.reason_id = 2 THEN 1 END) AS '2Count',
    COUNT(CASE WHEN training_enhancments.reason_id = 3 THEN 1 END) AS '3Count',
    COUNT(CASE WHEN training_enhancments.reason_id = 8 THEN 1 END) AS '4Count',
    ...
    COUNT(CASE WHEN training_enhancments.reason_id = 40 THEN 1 END) AS '40Count',
FROM 
    claims claims 
    INNER JOIN users users ON claims.surveyor_id = users.user_id
    INNER JOIN insurers insurers on claims.insurer_id = insurers.insurer_id
    LEFT JOIN training_enhancments training_enhancments on claims.claim_id = training_enhancments.claim_id
WHERE
    claims.claim_cancelled_id <= 0 AND
    claims.date_completed BETWEEN '2014-10-01 00:00:00' AND '2014-10-31 23:59:59'
GROUP BY claims.surveyor_id;

How can I rewrite the COUNT() statements to be more efficient / readable? Something like a for / while loop, but I'm not sure that they exist in SQL.

Thanks.

Try to group by training_enhancments.reason_id , then in your application code, you can use for/while loop to display 40 counts.

SELECT
    claims.surveyor_id,
    training_enhancments.reason_id,
    COUNT(*) 
FROM 
    claims claims 
    INNER JOIN users users ON claims.surveyor_id = users.user_id
    INNER JOIN insurers insurers on claims.insurer_id = insurers.insurer_id
    LEFT JOIN training_enhancments training_enhancments on claims.claim_id = training_enhancments.claim_id
WHERE
    claims.claim_cancelled_id <= 0 AND
    claims.date_completed BETWEEN '2014-10-01 00:00:00' AND '2014-10-31 23:59:59'
GROUP BY 
    claims.surveyor_id,
    training_enhancments.reason_id;

You won't get it more efficient, if you need this in separate columns. In MySQL, however, you can make use of boolean expressions resulting in 1 for true and 0 for false and thus get it more readable:

SELECT
    SUM(training_enhancments.reason_id = 0) AS '0Count',
    SUM(training_enhancments.reason_id = 1) AS '1Count',
    ...
FROM ...

If you need one row per surveyor id with fixed columns then your current solution is pretty good.

If you can have multiple rows per surveyor id and can process them in the calling script then the solution by Jaugar Chang would be my choice.

If you must return one row per each surveyor id but can split up a field in that returned row to the various counts then you could do the following:-

SELECT surveyor_id, GROUP_CONCAT(CONCAT_WS(':', reason_id, reason_count))
(
    SELECT claims.surveyor_id, training_enhancments.reason_id, COUNT(*) AS reason_count
    FROM claims claims 
    INNER JOIN users users ON claims.surveyor_id = users.user_id
    INNER JOIN insurers insurers on claims.insurer_id = insurers.insurer_id
    LEFT JOIN training_enhancments training_enhancments on claims.claim_id = training_enhancments.claim_id
    WHERE claims.claim_cancelled_id <= 0 
    AND claims.date_completed BETWEEN '2014-10-01 00:00:00' AND '2014-10-31 23:59:59'
    GROUP BY claims.surveyor_id, training_enhancments.reason_id
) sub0
GROUP BY surveyor_id;

Or to force a value for all the reason ids for a surveyor (even if none used for that surveyor):-

SELECT surveyor_id, GROUP_CONCAT(CONCAT_WS(':', reason_id, reason_count))
(
    SELECT claims.surveyor_id, reasons.reason_id, COUNT(training_enhancments.claim_id) AS reason_count
    FROM claims claims 
    CROSS JOIN (SELECT 0 AS reason_id UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 8 UNION SELECT 40) reasons
    INNER JOIN users users ON claims.surveyor_id = users.user_id
    INNER JOIN insurers insurers on claims.insurer_id = insurers.insurer_id
    LEFT JOIN training_enhancments training_enhancments on claims.claim_id = training_enhancments.claim_id
    WHERE claims.claim_cancelled_id <= 0 
    AND claims.date_completed BETWEEN '2014-10-01 00:00:00' AND '2014-10-31 23:59:59'
    GROUP BY claims.surveyor_id, training_enhancments.reason_id
) sub0
GROUP BY surveyor_id;

In the end I went for a for loop in the surrounding language, PHP.

Is it more readable? Debatable. It's certainly easier to expand upon though.

$numReasons = 38;

$SQL = "SELECT CONCAT(users.user_first_name,IF((users.user_surname<>''),CONCAT(' ',users.user_surname),'')) as 'Surveyor'";
for ($i = 0; $i < $numReasons + 1; $i++) {
    $SQL .= ", COUNT(CASE WHEN training_enhancments.reason_id = $i THEN 1 END) AS '" . $i . "Count' ";
}
$SQL .= "FROM 
    claims claims 
    INNER JOIN users users ON claims.surveyor_id = users.user_id
    INNER JOIN insurers insurers on claims.insurer_id = insurers.insurer_id
    LEFT JOIN training_enhancments training_enhancments on claims.claim_id = training_enhancments.claim_id

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM