MySQL merge multiple queries for report - mysql

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?

Related

Group_Concat with multiple joined tables

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 |
+---------+----------+---------------+-------------+-------------+

MySQL Unique Exemption Case with Group By

I have a set of data that lists when a User changes a Product, we look at who changed it, when, the old cost and new cost, and the percentage price difference.
I want to use a group by, where statement, or case to group by and exclude products that filters out changes were the change occurred in the same day and resulted in the original price staying.
So the situation I want to exclude would look like this:
| product | Changed By | Old Price | New Price | % diff | Day Changed |
|----------|------------|-----------|-----------|--------|-------------|
| blue hat | me | 94.00 | 95.00 | 1.05 | 2016-11-28 |
| blue hat | me | 95.00 | 94.00 | 1.05 | 2016-11-28 |
Any ideas how to do this with MySql?
Here is a working version for anyone who wants to see this done using subqueries, where's, and group by's.
This query looks at the changes to an Item's cost by a User for the span of 1 day, where it pulls in all the results from "yesterday". It lists all the changes for that day one asc and one desc and compares the price changes that way. If they are the same from the oldest change to the newest change of that say then it is exempted.
SELECT
us.Name,
it.Name,
pal.CS_PA_Line_Supplier__c as supplier_name,
newest.NewValue as new_value,
oldest.OldValue as old_value,
((newest.NewValue - oldest.OldValue) / oldest.OldValue) * 100 as Percentage
FROM
(
SELECT Id, Name, KNDY4__Item__c, CS_PA_Line_Supplier__c
FROM KNDY4__Contract_Line__c
) pal
LEFT JOIN
(
SELECT *
FROM
(
SELECT
ParentId,
CreatedById,
CreatedDate,
Field,
NewValue
FROM KNDY4__Contract_Line__History
WHERE CreatedDate >= CURDATE() - INTERVAL 1 DAY
AND CreatedDate < CURDATE()
AND Field='KNDY4__Negotiated_Price__c'
ORDER BY CreatedDate DESC
) cd
GROUP BY ParentId
) newest
ON newest.ParentId=pal.Id
LEFT JOIN
(
SELECT * FROM (
SELECT
ParentId,
OldValue
FROM KNDY4__Contract_Line__History
WHERE CreatedDate >= CURDATE() - INTERVAL 1 DAY
AND CreatedDate < CURDATE()
AND Field='KNDY4__Negotiated_Price__c'
ORDER BY CreatedDate ASC
) cd
GROUP BY ParentId
) oldest
ON oldest.ParentId=pal.Id
LEFT JOIN
(
SELECT Id, Name
FROM User
) us
ON us.Id = newest.CreatedById
LEFT JOIN
(
SELECT Id,Name
FROM KNDY4__Item__c
) it
ON it.Id=pal.KNDY4__Item__c
WHERE newest.ParentId IS NOT NULL
AND oldest.OldValue IS NOT NULL
AND newest.NewValue != oldest.OldValue
GROUP BY pal.KNDY4__Item__c, pal.CS_PA_Line_Supplier__c
ORDER BY it.Name ASC

How to return results that relate across 3 tables efficiently MySQL

Howdie do,
I have the following 3 tables: order, manifest and tracking_updates. Now, each order has foreign key called manifest_id that references the manifest table. Several orders can be in a manifest. The tracking_updates table has a foreign key called order_id that references the order table.
Now, the manifest table contains a column named upload_date. That column, upload_date is the column I need to use in order to determine if an order was uploaded in the last 30 days.
The tracking_update table can contain many updates for each order and so, I must return the most recent tracking update status for each order that matches the criteria below:
1. orders < 30 days, any delivery status
2. orders > 30 days, not delivered
Please see tables below
**Order**
ID | manifest_id
1 | 123
2 | 123
3 | 456
**Manifest**:
ID | upload_date
123 | 2015-12-15 09:31:12
456 | 2015-10-13 09:31:12
**Tracking Update**:
order_id | status_type | last_updated
1 | M | 2015-12-15 00:00:00
1 | I | 2015-12-16 07:20:00
1 | D | 2015-12-17 15:20:00
2 | M | 2015-12-15 00:00:00
2 | D | 2015-12-16 15:20:00
3 | M | 2015-10-13 00:00:00
3 | I | 2015-10-14 12:00:00
3 | E | 2015-10-15 13:50:00
This is what the result set would look like for the orders above
**Result Set**
order_id | manifest_id | latest_tracking_update_status
1 | 123 | D
2 | 123 | D
3 | 456 | E
As you can see, order 1, 2 are assigned to manifest 123 and the manifest was uploaded within the last 30 days and their latest tracking update shows a 'D' for delivered. So those two orders should be included in the result set.
The order 3 is older then 30 days, but hasn't been delivered based off the latest tracking_update status_type, so it should show up in the result set.
Now, the tracking_update table as well over 1 million updates across all orders. So I'm really going for efficiency here
Currently, I have the following queries.
Query #1 returns orders that have been uploaded within the last 30 days and their corresponding latest tracking update
SELECT
fgw247.order.id as order_id,
(SELECT
status_type
FROM
tracking_update as tu
WHERE
tu.order_id = order_id
ORDER BY
tu.ship_update_date DESC
LIMIT
1
) as latestTrackingUpdate
FROM
fgw247.order, manifest
WHERE
fgw247.order.manifest_id = manifest.id
AND
upload_date >= '2015-12-12 00:00:00'
Query #2 returns the order_id and latest tracking update for every order in the tracking_update table:
SELECT tracking_update.order_id,
substring_index(group_concat(tracking_update.status_type order by tracking_update.last_updated), ',', -1)
FROM
tracking_update
WHERE
tracking_update.order_id is not NULL
GROUP BY tracking_update.order_id
I'm just not sure how to combine these queries to get my orders that match the criteria:
orders < 30 days, any delivery status
orders > 30 days, not delivered
Any ideas would be GREATLY appreciated.
* UPDATE *
This is the current query thanks to answer selected:
select
o.id, t.maxudate, tu.status_type, m.upload_date
from
(select order_id, max(last_updated) as maxudate from tracking_update group by order_id) t
inner join
tracking_update tu on t.order_id=tu.order_id and t.maxudate=tu.last_updated
right join
fgw247.order o on t.order_id=o.id
left join
manifest m on o.manifest_id=m.id
where
(tu.status_type != 'D' and tu.status_type != 'XD' and m.upload_date <='2015-12-12 00:00:00') or m.upload_date >= '2015-12-12 00:00:00'
LIMIT 10
UPDATE
This is the current query that joins the three tables rather efficiently
SELECT
o.*, tu.*
FROM
fgw247.`order` o
JOIN
manifest m
ON
o.`manifest_id` = m.`id`
JOIN
`tracking_update` tu
ON
tu.`order_id` = o.`id` and tu.`ship_update_date` = (select max(last_updated) as last_updated from tracking_update where order_id = o.`id` group by order_id)
WHERE
m.`upload_date` >= '2015-12-14 11:50:12'
OR
(o.`delivery_date` IS NULL AND m.`upload_date` < '2015-12-14 11:50:12')
LIMIT 100
Have a subquery that returns the latest update date from the tracking table for each order. Join this subquery on the tracking, orders, and manifests tables to get the details and filter based on the upload date in the where clause:
select o.order_id, t.maxudate, tu.status_type, m.upload_date
from (select order_id, max(update_date) as maxudate from tracking_update group by order_id) t
inner join tracking_update tu on t.order_id=tu.order_id and t.maxudate=tu.update_date
right join orders o on t.order_id=o.order_id
left join manifests m on o.manifest_id=m.manifest_id
where (tu.status_type<>'D' and curdate()-m.upload_date>30) or curdate()-m.upload_date<=30
It may be more efficient to use a union query instead of the or criteria in the where clause.
You can perform a JOIN with the 2nd query result like
SELECT
fgw247.order.id as order_id,
xx.some_column,
(SELECT
status_type
FROM
tracking_update as tu
WHERE tu.order_id = order_id
ORDER BY
tu.ship_update_date DESC
LIMIT
1
) as latestTrackingUpdate
FROM
fgw247.order JOIN manifest
ON fgw247.order.manifest_id = manifest.id
JOIN (
SELECT tracking_update.order_id,
substring_index(group_concat(tracking_update.status_type order by tracking_update.last_updated), ',', -1) AS some_column
FROM
tracking_update
WHERE
tracking_update.order_id is not NULL
GROUP BY tracking_update.order_id ) xx ON xx.order_id = fgw247.order.id
WHERE upload_date >= '2015-12-12 00:00:00'

Count all items on which a user is the high bidder

I am currently working on an auction system, and the functionality is all complete. I now need to add a count to the user's profile which shows how many items the user is currently bidding on.
The system comprises of two key tables (extra tables feature in the system of course, but these are the only tables related to this issue):
item_sales:
+-----+------------------+------------+-------------+---------+
| id | selling_format | duration | list_date | buyer |
+-----+------------------+------------+-------------+---------+
item_sales_bids:
+-----+-------------+-----------+---------------+-----------+--------+
| id | sale_item | user_id | current_bid | max_bid | date |
+-----+-------------+-----------+---------------+-----------+--------+
item_sales_bids.date is a Unix timestamp of the bid time.
I can easily get a count of all bids a given user has made with the following query:
SELECT COUNT(DISTINCT(`item_sales_bids`.`user_id`)) AS `total`,
SUM((`sale`.`list_date` + (`sale`.`duration` * 86400)) - UNIX_TIMESTAMP()) AS `endTime`
FROM `item_sales_bids`
INNER JOIN `item_sales` `sale` ON `item_sales_bids`.`sale_item` = `sale`.`id`
WHERE `user_id` = 1
GROUP BY `sale_item`
HAVING `endTime` > 0
What I would like to do, is run a query similar to the above, but only include records where the specified user is the current highest bidder (i.e. the max ID entry for a given item's bid set has a user_id value = to our user).
Unfortunately, I'm at a loss on how I might achieve this.
I have set up an SQLFiddle to assist > http://sqlfiddle.com/#!2/b98e4/3
Do a subs query to get the latest bid for all items and then join that to item_sales_bids to only process the latest items.
Something like this:-
SELECT COUNT(DISTINCT(item_sales_bids.user_id)) AS total,
SUM((sale.list_date + (sale.duration * 86400)) - UNIX_TIMESTAMP()) AS endTime
FROM item_sales_bids
INNER JOIN item_sales sale ON item_sales_bids.sale_item = sale.id
INNER JOIN
(
SELECT sale_item, MAX(id) AS LatestBid
FROM item_sales_bids
GROUP BY sale_item
) Sub1
ON item_sales_bids.sale_item = Sub1.sale_item AND item_sales_bids.id = Sub1.LatestBid
WHERE user_id = 1
GROUP BY item_sales_bids.sale_item
HAVING endTime > 0
This should do the trick. Toggling between user 1 & 2 will show the desired behavior. In the sample data, user 1 returns no data (they are not the high bidder) and user 2 returns a single row, with a current high bid of 50.
SELECT COUNT(DISTINCT(`bids`.`user_id`)) AS `total`,
`highbids`.`sale_item`,
`highbids`.`maxBid`,
SUM((`sale`.`list_date` + (`sale`.`duration` * 86400)) - UNIX_TIMESTAMP()) AS `endTime`
FROM `item_sales_bids` `bids`
INNER JOIN `item_sales` `sale` ON `bids`.`sale_item` = `sale`.`id`
INNER JOIN (SELECT MAX(`current_bid`) AS `maxBid`, `sale_item`, `user_id`
FROM `item_sales_bids`
GROUP BY `sale_item`, `user_id`
ORDER BY `current_bid` DESC LIMIT 1,1)
AS `highbids` ON `bids`.`user_id` = `highbids`.`user_id`
WHERE `bids`.`user_id` = 2
GROUP BY `bids`.`sale_item`
HAVING `endTime` > 0

MySQL - Complex COUNT Query

I have a table called user_scores as below:
id | af_id | uid | level | record_date
----------------------------------------
1 | 1.1 | 1 | 3 | 2012-01-01
2 | 1.1 | 1 | 4 | 2012-02-01
3 | 1.2 | 1 | 3 | 2012-01-01
4 | 1.2 | 1 | 5 | 2012-03-01
...
I have another table call user_info as below:
uid | forename | surname | gender
-----------------------------------
1 | Homer | Simpson | M
2 | Marge | Simpson | F
3 | Bart | Simpson | M
4 | Lisa | Simpson | F
...
In user scores uid is the user id of a registered user on the system, af_id identifies a particular test a user submits. A user scores a level between 1 - 5 for each test, which can be submitted every month.
My problem is I need to produce an analysis at the end of the year to COUNT the number of users that have achieved each level for a particular test. The analysis is to show a gender split for male and female.
So for example an administrator would select test 1.1 and the system would generate stats based that would COUNT of the total MAX level achieved by each user in the year, with a gender split.
Any help is much appreciated. Thank you in advance.
-
I think I need to clarify myself a bit. Because a user can complete the test multiple times throughout the year, there will be multiple scores for the same test. The query should take the highest level achieved and include this in the count. An example result would be:
Male Results:
level1 | level2 | level3 | level4 | level5
------------------------------------------
2 | 5 | 10 | 8 | 1
I am not certain I get exactly what you mean, but as always I'll have a go. As I understand it you want to know how many people from each gender reached each level in a certain year.
SELECT MaxLevel,
COUNT(CASE WHEN ui.Gender = 'M' THEN 1 END) AS Males,
COUNT(CASE WHEN ui.Gender = 'F' THEN 1 END) AS Females
FROM User_Info ui
INNER JOIN
( SELECT MAX(Level) AS MaxLevel,
UID
FROM User_Scores us
WHERE af_ID = '1.1'
AND YEAR(Record_Date) = 2012
GROUP BY UID
) AS MaxUs
ON MaxUs.uid = ui.UID
GROUP BY MaxLevel
I've put some sample data on SQL Fiddle so you see if it is what you were after.
EDIT
To transpose the data so levels are along the top and Gender in the rows the following will work:
SELECT Gender,
COUNT(CASE WHEN MaxLevel = 1 THEN 1 END) AS Level1,
COUNT(CASE WHEN MaxLevel = 2 THEN 1 END) AS Level2,
COUNT(CASE WHEN MaxLevel = 3 THEN 1 END) AS Level3,
COUNT(CASE WHEN MaxLevel = 4 THEN 1 END) AS Level4,
COUNT(CASE WHEN MaxLevel = 5 THEN 1 END) AS Level5
FROM User_Info ui
INNER JOIN
( SELECT MAX(Level) AS MaxLevel,
UID
FROM User_Scores us
WHERE af_ID = '1.1'
AND YEAR(Record_Date) = 2012
GROUP BY UID
) AS MaxUs
ON MaxUs.uid = ui.UID
GROUP BY Gender
Note, that if there are ever more than 5 levels you will need to add more to the select statement, or start building dynamic SQL.
Assuming record_date holds only dates (without time parts):
SELECT
s.maxlevel,
COUNT(NULLIF(gender, 'F')) AS M,
COUNT(NULLIF(gender, 'M')) AS F
FROM user_info u
INNER JOIN (
SELECT
uid,
MAX(level) AS maxlevel
FROM user_scores
WHERE record_date > DATE_SUB(CURDATE(), INTERVAL DAYOFYEAR(CURDATE()) DAY)
AND af_id = '1.1'
GROUP BY
uid
) s ON s.uid = u.uid
GROUP BY
s.maxlevel
That will show you only the maximum levels found in the user_scores table. If you have a Levels table where all possible levels (1 to 5) are listed, you could use that table to get a complete list of levels. If some levels are not present in the requested subset of data, the corresponding rows will show 0s in both columns.
Here's the above script with minor changes to show the complete chart of levels:
SELECT
l.level AS maxlevel,
COUNT(NULLIF(gender, 'F')) AS M,
COUNT(NULLIF(gender, 'M')) AS F
FROM user_info u
INNER JOIN (
SELECT
uid, MAX(level) AS maxlevel
FROM user_scores
WHERE record_date > DATE_SUB(CURDATE(), INTERVAL DAYOFYEAR(CURDATE()) DAY)
AND af_id = '1.1'
GROUP BY
uid
) s ON s.uid = u.uid
RIGHT JOIN Levels l ON s.maxlevel = l.level
GROUP BY
l.level
Hope this is what your looking for!
Show number of records group by userid and gender of the max score for af_id '1.1'.
select count(*), info.uid, info.gender, max(score.level)
from user_info as info
join user_scores as score
on info.uid = score.uid
where score.af_id = '1.1'
group by info.uid, info.gender;
EDITED based on your edit.
select sum(if(a.gender="M",1,0)) Male_users, sum(if(a.gender="F",1,0)) Female_users
from myTable a where
a.level = (select max(b.level) from myTable b where a.uid=b.uid)
group by af_id.
I typed this in a rush. But it should work or at least get you where you need to go. E.G. if you need to specify time frame, add that.
You need something like
SELECT
uid,
MAX(level)
WHERE
record_date BETWEEN '2012-01-01' AND '2012-12-31'
AND af_id='1.1'
GROUP BY uid
If you need the gender splits then depending on what stat you need per gender you can either add a JOIN on the user_info table into this query (to get the MAX per gender) to wrap this as a sub-query and JOIN on the whole thing.