Group_Concat with multiple joined tables - mysql

I have two main tables that comprise bookings for events.
A Registrants table (Bookings) R and an Events table E.
There are also two connected tables, Field_Values V and Event_Categories C
This diagram shows the relationship
What I am trying to do is create an Invoice query that mirrors the user's shopping cart. Often a user will book multiple events in one transaction, so my invoice should have columns for the common items e.g. User Name, User Email, Booking Date, Transaction ID and aggregated columns for the invoice line item values e.g. Quantity "1,2" Description "Desc1, Desc2" Price "10.00, 20.00" where there are two line items in the shopping cart.
The Transaction ID (dcea4_eb_registrant.transaction_id) is unique per Invoice and repeated per line item in that sale.
I have the following query which produces rows for each line item
SELECT
R.id as ID,
E.event_date as ServiceDate,
E.event_date - INTERVAL 1 DAY as DueDate,
Concat('Ad-Hoc Booking:',E.title) as ItemProductService,
Concat(R.first_name, ' ',R.last_name) as Customer,
R.first_name as FirstName,
R.last_name as LastName,
R.email,
R.register_date as InvoiceDate,
R.amount as ItemAmount,
R.comment,
R.number_registrants as ItemQuantity,
R.transaction_id as InvoiceNo,
R.published as Status,
E.event_date AS SERVICEDATE,
Concat('Ad-Hoc Booking:',E.title) AS DESCRIPTION,
R.number_registrants AS QUANTITY,
FORMAT(R.amount / R.number_registrants,2) AS RATE,
R.amount AS AMOUNT,
C.category_id as CLASS,
Concat(Group_Concat(V.field_value SEPARATOR ', '),'. ',R.comment) as Memo
FROM dcea4_eb_events E
LEFT JOIN dcea4_eb_registrants R ON R.event_id = E.id
LEFT JOIN dcea4_eb_field_values V ON V.registrant_id = R.id
LEFT JOIN dcea4_eb_event_categories C ON C.event_id = R.event_id
WHERE 1=1
AND V.field_id IN(14,26,27,15)
AND R.published <> 2 /*Including this line omits Cancelled Invoices */
AND R.published IS NOT NULL
AND (R.published = 1 OR R.payment_method = "os_offline")
AND (R.register_date >= CURDATE() - INTERVAL 14 DAY)
GROUP BY E.event_date, E.title, R.id, R.first_name, R.last_name, R.email,R.register_date, R.amount, R.comment
ORDER BY R.register_date DESC, R.transaction_id
This produces output like this
I'm using the following query to try to group together the rows with a common transaction_ID (rows two and three in the last picture) - I add group_concat on the columns I want to aggregate and change the Group By to be the transaction_id
SELECT
R.id as ID,
E.event_date as ServiceDate,
E.event_date - INTERVAL 1 DAY as DueDate,
Concat('Ad-Hoc Booking:',E.title) as ItemProductService,
Concat(R.first_name, ' ',R.last_name) as Customer,
R.first_name as FirstName,
R.last_name as LastName,
R.email,
R.register_date as InvoiceDate,
R.amount as ItemAmount,
R.comment,
R.number_registrants as ItemQuantity,
R.transaction_id as InvoiceNo,
R.published as Status,
Group_ConCat( E.event_date) AS SERVICEDATE,
Group_ConCat( Concat('Ad-Hoc Booking:',E.title)) AS DESCRIPTION,
Group_ConCat( R.number_registrants) AS QUANTITY,
Group_ConCat( FORMAT(R.amount / R.number_registrants,2)) AS RATE2,
Group_ConCat( R.amount) AS AMOUNT,
Group_ConCat( C.category_id) as CLASS,
Concat(Group_Concat(V.field_value SEPARATOR ', '),'. ',R.comment) as Memo
FROM dcea4_eb_events E
LEFT JOIN dcea4_eb_registrants R ON R.event_id = E.id
LEFT JOIN dcea4_eb_field_values V ON V.registrant_id = R.id
LEFT JOIN dcea4_eb_event_categories C ON C.event_id = R.event_id
WHERE 1=1
AND V.field_id IN(14,26,27,15)
AND R.published <> 2 /*Including this line omits Cancelled Invoices */
AND R.published IS NOT NULL
AND (R.published = 1 OR R.payment_method = "os_offline")
AND (R.register_date >= CURDATE() - INTERVAL 14 DAY)
GROUP BY R.transaction_id
ORDER BY R.register_date DESC, R.transaction_id
But this produces this output
It seems to be multiplying the rows. The Quantity column in the first row should just be 1 and in the second row it should be 2,1 .
I've tried using Group_Concat with DISTINCT but this doesn't work because often the values being concatenated are the same (e.g. the price for two events being booked are both the same) and the query only returns one value e.g. 10 and not 10, 10. The latter being what I need.
I'm guessing the issue is around the way the tables are joined but I'm struggling to work out how to get what I need.
Pointers in the right direction most appreciated.

You seem determined to go in what seems to me to be the wrong direction, so here's a gentle nudge down that hill...
Consider the following...
CREATE TABLE users
(user_id SERIAL PRIMARY KEY
,username VARCHAR(12) UNIQUE
);
INSERT INTO users VALUES
(101,'John'),(102,'Paul'),(103,'George'),(104,'Ringo');
DROP TABLE IF EXISTS sales;
CREATE TABLE sales
(sale_id SERIAL PRIMARY KEY
,purchaser_id INT NOT NULL
,item_code CHAR(1) NOT NULL
,quantity INT NOT NULL
);
INSERT INTO sales VALUES
( 1,101,'A',1),
( 2,103,'A',2),
( 3,103,'A',3),
( 4,104,'A',1),
( 5,104,'A',2),
( 6,104,'A',3),
( 7,103,'B',2),
( 8,103,'B',2),
( 9,104,'B',3),
(10,103,'B',2),
(11,104,'B',2),
(12,104,'B',1);
SELECT u.*
, x.sale_ids
, x.item_codes
, x.quantities
FROM users u
LEFT
JOIN
( SELECT purchaser_id
, GROUP_CONCAT(sale_id ORDER BY sale_id) sale_ids
, GROUP_CONCAT(item_code ORDER BY sale_id) item_codes
, GROUP_CONCAT(quantity ORDER BY sale_id) quantities
FROM sales
GROUP
BY purchaser_id
) x
ON x.purchaser_id = u.user_id;
+---------+----------+---------------+-------------+-------------+
| user_id | username | sale_ids | item_codes | quantities |
+---------+----------+---------------+-------------+-------------+
| 101 | John | 1 | A | 1 |
| 102 | Paul | NULL | NULL | NULL |
| 103 | George | 2,3,7,8,10 | A,A,B,B,B | 2,3,2,2,2 |
| 104 | Ringo | 4,5,6,9,11,12 | A,A,A,B,B,B | 1,2,3,3,2,1 |
+---------+----------+---------------+-------------+-------------+

Related

MySQL merge multiple queries for report

See SQLFiddle here: http://sqlfiddle.com/#!9/9bb273
I need to create a report out of 3 queries. It needs to be a single query without subqueries (due to ORM limitations).
The main query is:
SELECT SQL_CALC_FOUND_ROWS
Organization.name as organization_name,
Program.unique_id as program_uuid,
Program.name as program_name,
Program.start_date,
Program.end_date,
Program.grace_period,
'Placeholder A' as 'Participant Count',
'Placeholder B' as 'Total Participant Points',
count(distinct Transaction.id) as 'Transaction Count',
sum(TransactionItem.quantity) as 'Total Redemptions',
sum(((TransactionProduct.retail + IFNULL(TransactionProduct.shipping,0) + IFNULL(TransactionProduct.handling,0)) * TransactionItem.quantity)) as 'Total'
FROM `TransactionItem`
JOIN `Transaction` ON `Transaction`.id = `TransactionItem`.transaction_id
JOIN `TransactionProduct` ON `TransactionItem`.reference_id = `TransactionProduct`.reference_id
JOIN `Participant` ON `Transaction`.participant_id = `Participant`.id
JOIN `Program` ON `Program`.id = `Participant`.program_id
JOIN `Organization` ON `Organization`.id = `Participant`.organization_id
WHERE 1=1
AND `Organization`.`unique_id` = 'demo2'
AND `Program`.`unique_id` = 'demo2'
AND `Transaction`.`created_at` >= '2018-10-01 00:00:00'
AND `Transaction`.`created_at` <= '2018-12-18 00:00:00';
As you can see, this report is for the date range between 10/1 and 12/18. The result set that makes up the report is...
organization_name | program_uuid | program_name | start_date | end_date | grace_period | Participant Count | Total Participant Points | Transaction Count | Total Redemptions | Total
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Demo2 Org demo2 Demo2 2018-10-01 2018-12-27 5 Placeholder A Placeholder B 11 92 2853.13
As you can see, there are 2 data points that I'm unable to get from this query.
(1) The total number of participants belonging to the "demo2" Program. This query gets that data point.
/* Placeholder A */
select program_id, count(*) as 'Participant Count' from participant
where active = 1
group by program_id;
Returns:
program_id | Participant Count
----------------------------------
2 102
(2) The sum of Adjustments.amount for all rows between dates 10/1 and 12/18. This query fulfills that.
/* Placeholder B */
select sum(amount) as 'Total Particpant Points' from adjustment
where participant_id in (select id from participant where program_id =2)
and type = 1
and created_at >= '2018-10-01 00:00:00' and created_at <= '2018-12-18 00:00:00';
Returns:
Total Participant Points
------------------------
10000.50000
Is there a way to gather all of this data in a single query without subqueries?

Retrieving top 2 bids for each item in an auction

I am having trouble combining data from multiple tables. I have tried joins and subqueries but to no avail. I basically need to combine 2 queries into one. My tables (simplified):
Stock:
id int(9) PrimaryIndex
lot_number int(4)
description text
reserve int(9)
current_bid int(9)
current_bidder int(6)
Members:
member_id int(11) PrimaryIndex
name varchar(255)
Bids:
id int(9)
lot_id int(9)
bidder_id int(5)
max_bid int(9)
time_of_bid datetime
I'm currently using 2 separate queries which with 1000's of lots, makes it very inefficient. 1st query:
SELECT S.id, S.lot_number, S.description, S.reserve FROM stock S ORDER BY
S.lot_number ASC
The 2nd query within a while loop then gets the bidding info:
SELECT DISTINCT B.bidder_id, B.lot_id, B.max_bid, B.time_of_bid,
M.fname, M.lname FROM bids B, members M WHERE B.lot_id=? AND
B.bidder_id=M.member_id ORDER BY B.max_bid DESC LIMIT 2
Below is what i would like as output from a single query, if possible:
Lot No. | Reserve | Current Bid | 1st Max Bid | 1st Bidder | 2nd Max Bid | 2nd Max Bidder
1 | $100 | $120 | $150 | Steve | $110 | John
2 | $500 | $650 | $900 | Tom | $600 | Paul
I have had partial success with just getting the MAX(B.bid) and then its related details (WHERE S.id=B.id), but i cant get the top 2 bids for each lot.
First assign a row number rn to rows within each group of lot_id in table bids (highest bid gets 1, 2nd highest bid gets 2 and so on). The highest bid and second highest bid will be on two different rows after the LEFT JOIN. Use GROUP BY to merge the two rows into one.
select s.lot_number, s.reserve, s.current_bid,
max( case when rn = 1 then b.max_bid end) as first_max_bid,
max( case when rn = 1 then m.name end) as first_bidder,
max( case when rn = 2 then b.max_bid end) as second_max_bid,
max( case when rn = 2 then m.name end ) as second_bidder
from
stock s
left join
(select * from
(select *,
(#rn := if(#lot_id = lot_id, #rn+1,
if( #lot_id := lot_id, 1, 1))) as rn
from bids cross join
(select #rn := 0, #lot_id := -1) param
order by lot_id, max_bid desc
) t
where rn <= 2) b
on s.lot_number = b.lot_id
left join members m
on b.bidder_id = m.member_id
group by s.lot_number, s.reserve, s.current_bid
order by s.lot_number

I need help regarding JOIN query in mysql

I have started learning MySQL and I'm having a problem with JOIN.
I have two tables: purchase and sales
purchase
--------------
p_id date p_cost p_quantity
---------------------------------------
1 2014-03-21 100 5
2 2014-03-21 20 2
sales
--------------
s_id date s_cost s_quantity
---------------------------------------
1 2014-03-21 90 9
2 2014-03-22 20 2
I want these two tables to be joined where purchase.date=sales.date to get one of the following results:
Option 1:
p_id date p_cost p_quantity s_id date s_cost s_quantity
------------------------------------------------------------------------------
1 2014-03-21 100 5 1 2014-03-21 90 9
2 2014-03-21 20 2 NULL NULL NULL NULL
NULL NULL NULL NULL 2 2014-03-22 20 2
Option 2:
p_id date p_cost p_quantity s_id date s_cost s_quantity
------------------------------------------------------------------------------
1 2014-03-21 100 5 NULL NULL NULL NULL
2 2014-03-21 20 2 1 2014-03-21 90 9
NULL NULL NULL NULL 2 2014-03-22 20 2
the main problem lies in the 2nd row of the first result. I don't want the values
2014-03-21, 90, 9 again in row 2... I want NULL instead.
I don't know whether it is possible to do this. It would be kind enough if anyone helps me out.
I tried using left join
SELECT *
FROM sales
LEFT JOIN purchase ON sales.date = purchase.date
output:
s_id date s_cost s_quantity p_id date p_cost p_quantity
1 2014-03-21 90 9 1 2014-03-21 100 5
1 2014-03-21 90 9 2 2014-03-21 20 2
2 2014-03-22 20 2 NULL NULL NULL NULL
but I want 1st 4 values of 2nd row to be NULL
Since there are no common table expressions or full outer joins to work with, the query will have some duplication and instead need to use a left join unioned with a right join;
SELECT p_id, p.date p_date, p_cost, p_quantity,
s_id, s.date s_date, s_cost, s_quantity
FROM (
SELECT *,(SELECT COUNT(*) FROM purchase p1
WHERE p1.date=p.date AND p1.p_id<p.p_id) rn FROM purchase p
) p LEFT JOIN (
SELECT *,(SELECT COUNT(*) FROM sales s1
WHERE s1.date=s.date AND s1.s_id<s.s_id) rn FROM sales s
) s
ON s.date=p.date AND s.rn=p.rn
UNION
SELECT p_id, p.date p_date, p_cost, p_quantity,
s_id, s.date s_date, s_cost, s_quantity
FROM (
SELECT *,(SELECT COUNT(*) FROM purchase p1
WHERE p1.date=p.date AND p1.p_id<p.p_id) rn FROM purchase p
) p RIGHT JOIN (
SELECT *,(SELECT COUNT(*) FROM sales s1
WHERE s1.date=s.date AND s1.s_id<s.s_id) rn FROM sales s
) s
ON s.date=p.date AND s.rn=p.rn
An SQLfiddle to test with.
In a general sense, what you're looking for is called a FULL OUTER JOIN, which is not directly available in MySQL. Instead you only get LEFT JOIN and RIGHT JOIN, which you can UNION together to get essentially the same result. For a very thorough discussion on this subject, see Full Outer Join in MySQL.
If you need help understanding the different ways to JOIN a table, I recommend A Visual Explanation of SQL Joins.
The way this is different from a regular FULL OUTER JOIN is that you're only including any particular row from either table at most once in the JOIN result. The problem being, if you have one purchase record and two sales records on a particular day, which sales record is the purchase record associated with? What is the relationship you're trying to represent between these two tables?
It doesn't sound like there's any particular relationship between purchase and sales records, except that some of them happened to take place on the same day. In which case, you're using the wrong tool for the job. If all you want to do is display these tables side by side and line the rows up by date, you don't need a JOIN at all. Instead, you should SELECT each table separately and do your formatting with some other tool (or manually).
Here's another way to get the same result, but the EXPLAIN for this is horrendous; and performance with large sets is going to be atrocious.
This is essentially two queries UNIONed together. The first query is essentially "purchase LEFT JOIN sales", the second query is essentially "sales ANTI JOIN purchase".
Because there is no foreign key relationship between the two tables, other than rows matching on date, we have to "invent" a key we can join on; we use user variables to assign ascending integer values to each row within a given date, so we can match row 1 from purchase to row 1 from sales, etc.
I wouldn't normally generate this type of result using SQL; it's not a typical JOIN operation, in the sense of how we traditionally join tables.
But, if I had to produce the specified resultset using MySQL, I would do it like this:
SELECT p.p_id
, p.p_date
, p.p_cost
, p.p_quantity
, s.s_id
, s.s_date
, s.s_cost
, s.s_quantity
FROM ( SELECT #pl_i := IF(pl.date = #pl_prev_date,#pl_i+1,1) AS i
, #pl_prev_date := pl.date AS p_date
, pl.p_id
, pl.p_cost
, pl.p_quantity
FROM purchase pl
JOIN ( SELECT #pl_i := 0, #pl_prev_date := NULL ) pld
ORDER BY pl.date, pl.p_id
) p
LEFT
JOIN ( SELECT #sr_i := IF(sr.date = #sr_prev_date,#sr_i+1,1) AS i
, #sr_prev_date := sr.date AS s_date
, sr.s_id
, sr.s_cost
, sr.s_quantity
FROM sales sr
JOIN ( SELECT #sr_i := 0, #sr_prev_date := NULL ) srd
ORDER BY sr.date, sr.s_id
) s
ON s.s_date = p.p_date
AND s.i = p.i
UNION ALL
SELECT p.p_id
, p.p_date
, p.p_cost
, p.p_quantity
, s.s_id
, s.s_date
, s.s_cost
, s.s_quantity
FROM ( SELECT #sl_i := IF(sl.date = #sl_prev_date,#sl_i+1,1) AS i
, #sl_prev_date := sl.date AS s_date
, sl.s_id
, sl.s_cost
, sl.s_quantity
FROM sales sl
JOIN ( SELECT #sl_i := 0, #sl_prev_date := NULL ) sld
ORDER BY sl.date, sl.s_id
) s
LEFT
JOIN ( SELECT #pr_i := IF(pr.date = #pr_prev_date,#pr_i+1,1) AS i
, #pr_prev_date := pr.date AS p_date
, pr.p_id
, pr.p_cost
, pr.p_quantity
FROM purchase pr
JOIN ( SELECT #pr_i := 0, #pr_prev_date := NULL ) prd
ORDER BY pr.date, pr.p_id
) p
ON p.p_date = s.s_date
AND p.i = s.i
WHERE p.p_date IS NULL
ORDER BY COALESCE(p_date,s_date),COALESCE(p_id,s_id)

Sum of columns from tables in join

I have 3 tables, Orders, Orders_products and Orders_total. Currently i have a query that gets the SUM of products for each months, but now we would like to also add the freight cost that is in a different table.
I tried with the following that returned the correct total_value, but the total_shipping is 5 times as big. This i think has to due with that orders, can have multiplie products, but i cant figure out what else to do.
SELECT Count(DISTINCT O.orders_id) AS Orders,
Sum(OP.final_price * OP.products_quantity) AS total_value,
Date_format(O.last_modified, '%m-%Y') AS date_interval,
Sum(OT.value) AS total_shipping
FROM
orders AS O
LEFT JOIN
orders_total AS OT
ON ( OT.orders_id = O.orders_id
AND OT.class = 'ot_shipping' ),
orders_products AS OP
WHERE
( O.orders_id = OP.orders_id )
AND ( O.orders_status = 3 )
GROUP BY date_interval
ORDER BY O.last_modified DESC
The returned value is:
+----+------------+---------------+----------------+
| ID | total_value| date_interval | total_shipping |
+----+------------+---------------+----------------+
| 17 | 55912.2160 | 01-2014 | 24954 |
Expected:
+----+------------+---------------+----------------+
| ID | total_value| date_interval | total_shipping |
+----+------------+---------------+----------------+
| 17 | 55912.2160 | 01-2014 | 4938 |
Here is the sqlfiddle http://sqlfiddle.com/#!2/dfe10/1/0
It contains one order, with 3 products in it. the expected total_value is 500 and the expected total_shipping is also 500, but returns 1500 (3 x products). Sadly i had to remove a lot of fields from my table due to a limit in sqlfiddle of max 8000 chars.
Try putting the shipping value into an inline view:
select count(*) as Orders,
sum(ord.order_total_value) as total_value,
ord.date_interval as date_interval,
sum(ship.order_shipping_value) as total_shipping
from
(
SELECT O.orders_id,
O.last_modified AS modified_date,
Sum(OP.final_price * OP.products_quantity) as order_total_value,
Date_format(O.last_modified, '%m-%Y') as date_interval
FROM orders AS O
INNER JOIN orders_products AS OP on O.orders_id = OP.orders_id
WHERE O.orders_status = 3
GROUP BY date_interval,O.orders_id
) ord LEFT OUTER JOIN
(
SELECT orders_id,sum(value) as order_shipping_value
FROM orders_total
WHERE class='ot_shipping'
GROUP BY orders_id
) ship ON ord.orders_id = ship.orders_id
GROUP BY ord.date_interval
ORDER BY modified_date DESC;

MySQL SELECT combining 3 SELECTs INTO 1

Consider following tables in MySQL database:
entries:
creator_id INT
entry TEXT
is_expired BOOL
other:
creator_id INT
entry TEXT
userdata:
creator_id INT
name VARCHAR
etc...
In entries and other, there can be multiple entries by 1 creator. userdata table is read only for me (placed in other database).
I'd like to achieve a following SELECT result:
+------------+---------+---------+-------+
| creator_id | entries | expired | other |
+------------+---------+---------+-------+
| 10951 | 59 | 55 | 39 |
| 70887 | 41 | 34 | 108 |
| 88309 | 38 | 20 | 102 |
| 94732 | 0 | 0 | 86 |
... where entries is equal to SELECT COUNT(entry) FROM entries GROUP BY creator_id,
expired is equal to SELECT COUNT(entry) FROM entries WHERE is_expired = 0 GROUP BY creator_id and
other is equal to SELECT COUNT(entry) FROM other GROUP BY creator_id.
I need this structure because after doing this SELECT, I need to look for user data in the "userdata" table, which I planned to do with INNER JOIN and select desired columns.
I solved this problem with selecting "NULL" into column which does not apply for given SELECT:
SELECT
creator_id,
COUNT(any_entry) as entries,
COUNT(expired_entry) as expired,
COUNT(other_entry) as other
FROM (
SELECT
creator_id,
entry AS any_entry,
NULL AS expired_entry,
NULL AS other_enry
FROM entries
UNION
SELECT
creator_id,
NULL AS any_entry,
entry AS expired_entry,
NULL AS other_enry
FROM entries
WHERE is_expired = 1
UNION
SELECT
creator_id,
NULL AS any_entry,
NULL AS expired_entry,
entry AS other_enry
FROM other
) AS tTemp
GROUP BY creator_id
ORDER BY
entries DESC,
expired DESC,
other DESC
;
I've left out the INNER JOIN and selecting other columns from userdata table on purpose (my question being about combining 3 SELECTs into 1).
Is my idea valid? = Am I trying to use the right "construction" for this?
Are these kind of SELECTs possible without creating an "empty" column? (some kind of JOIN)
Should I do it "outside the DB": make 3 SELECTs, make some order in it (let's say python lists/dicts) and then do the additional SELECTs for userdata?
Solution for a similar question does not return rows where entries and expired are 0.
Thank you for your time.
This should work (assuming all creator_ids appear in the userdata table.
SELECT userdata.creator_id, COALESCE(entries_count_,0) AS entries_count, COALESCE(expired_count_,0) AS expired_count, COALESCE(other_count_,0) AS other_count
FROM userdata
LEFT OUTER JOIN
(SELECT creator_id, COUNT(entry) AS entries_count_
FROM entries
GROUP BY creator_id) AS entries_q
ON userdata.creator_id=entries_q.creator_id
LEFT OUTER JOIN
(SELECT creator_id, COUNT(entry) AS expired_count_
FROM entries
WHERE is_expired=0
GROUP BY creator_id) AS expired_q
ON userdata.creator_id=expired_q.creator_id
LEFT OUTER JOIN
(SELECT creator_id, COUNT(entry) AS other_count_
FROM other
GROUP BY creator_id) AS other_q
ON userdata.creator_id=other_q.creator_id;
Basicly, what you are doing looks correct to me.
I would rewrite it as follows though
SELECT entries.creator_id
, any_entry
, expired_entry
, other_entry
FROM (
SELECT creator_id, COUNT(entry) AS any_entry,
FROM entries
GROUP BY creator_id
) entries
LEFT OUTER JOIN (
SELECT creator_id, COUNT(entry) AS expired_entry,
FROM entries
WHERE is_expired = 1
GROUP BY creator_id
) expired ON expired.creator_id = entries.creator_id
LEFT OUTER JOIN (
SELECT creator_id, COUNT(entry) AS other_entry
FROM other
GROUP BY creator_id
) other ON other.creator_id = entries.creator_id
How about
SELECT creator_id,
(SELECT COUNT(*)
FROM entries e
WHERE e.creator_id = main.creator_id AND
e.is_expired = 0) AS entries,
(SELECT COUNT(*)
FROM entries e
WHERE e.creator_id = main.creator_id AND
e.is_expired = 1) as expired,
(SELECT COUNT(*)
FROM other
WHERE other.creator_id = main.creator_id) AS other,
FROM entries main
GROUP BY main.creator_id;