SQL - Find Highest Single Day of Sales From Date Range - mysql

I am using MySQL..
I have a simple sales table as follow:
o----o----------o-----------o
| id | store_id | logDate |
o----o----------o-----------o
| 1 | 1 | 2015-1-13 |
| 2 | 1 | 2015-1-14 |
| 3 | 2 | 2015-1-11 |
| 4 | 2 | 2015-1-18 |
o----o----------o-----------o
And sale product table
o----o----------o---------o------------o
| id | sale_id | qty | price |
o----o----------o---------o------------o
| 1 | 1 | 1 | 10 |
| 2 | 2 | 1 | 10 |
| 3 | 2 | 1 | 10 |
| 4 | 3 | 1 | 10 |
| 5 | 3 | 1 | 10 |
| 6 | 3 | 1 | 10 |
| 7 | 4 | 1 | 10 |
| 8 | 4 | 1 | 10 |
o----o----------o---------o------------o
Expected Result
o-- --------o----------------o---------------------o
| store_id | SUM(price*qty) | Highest Date On |
o-----------o----------------o---------------------o
| 1 | 20 | 2015-1-14 |
| 2 | 30 | 2015-1-11 |
O-----------o----------------o---------------------o
How to achieve my expected result?
I have tried as follow but it didn't work as expected:
SELECT store_id, MAX(total), highestSingleDateOn
FROM (
SELECT SUM(price * qty) AS total,
DATE(s.logDate) AS highestSingleDateOn, s.store_id AS store_id
FROM sale_product sp JOIN sales s ON s.id = sp.sales_id
GROUP BY DATE(s.logDate), s.store_id
ORDER BY DATE(s.logDate) ASC
) AS result_for_highest_single_day
GROUP BY highestSingleDateOn, store_id

SELECT store_id, MAX(total), highestSingleDateOn
FROM (
SELECT SUM(price * qty) AS total,
DATE(s.logDate) AS highestSingleDateOn, s.store_id AS store_id
FROM sale_product sp JOIN sales s ON s.id = sp.sales_id
GROUP BY DATE(s.logDate), s.store_id
ORDER BY total DESC
) AS result_for_highest_single_day
GROUP BY store_id
I just have modified the script ORDER BY DATE(s.logDate) ASC >> ORDER BY total DESC
and GROUP BY highestSingleDateOn, store_id >> GROUP BY store_id.
*Above sql script,it uses the unstable features about group by of MYSQL.
*Then according to Mysql standard,I write a other version sql script.
select table1.*
from
( SELECT SUM(price * qty) AS total,
DATE(s.logDate) AS highestSingleDateOn, s.store_id AS store_id
FROM sale_product sp JOIN sales s ON s.id = sp.sale_id
GROUP BY DATE(s.logDate), s.store_id) as table1
,
(select tmp.store_id,MAX(tmp.total) as max_total from
(SELECT SUM(price * qty) AS total,
DATE(s.logDate) AS highestSingleDateOn, s.store_id AS store_id
FROM sale_product sp JOIN sales s ON s.id = sp.sale_id
GROUP BY DATE(s.logDate), s.store_id ) as tmp group by tmp.store_id) as table2
where table1.store_id = table2.store_id and table1.total=table2.max_total

One way to do this in MySQL is with multiple an aggregations and then a join. Perhaps an easier way is to use variables:
SELECT sd.*
FROM (SELECT sd.*,
(#rn := if(#s = store_id, #rn + 1,
if(#s := store_id, 1, 1)
)
) as rn
FROM (SELECT DATE(s.logDate) AS date, s.store_id, SUM(price * qty) AS total
FROM sale_product sp JOIN sales s ON s.id = sp.sales_id
GROUP BY DATE(s.logDate), s.store_id
ORDER BY s.store_id, total desc
) sd cross join
(SELECT #rn := 0, #s := -1) params
) sd
WHERE rn = 1;

Related

How to sum and count Id's of different tables from a Union

I have 3 tables councils, station_levy, market_levy, and the station table is joined to get councils in station_levy.
I need to get data by council sum the number of station_levy + market_levy and get the total amount tendered.
Tables are as follows
councils
---------------+----------+
| council_id | Name |
+--------------+----------+
| 1 | LSK |
---------------+----------+
| 2 | KBW |
---------------+----------+
station_levy
------------------+-------------+-----------------+
| station_levy_id | station_id | amount_tendered |
+-----------------+-------------+-----------------+
| 1 | 3 | 10.00 |
+-----------------+-------------+-----------------+
| 2 | 3 | 10.00 |
+-----------------+-------------+-----------------+
| 3 | 1 | 5.00 |
+-----------------+-------------+-----------------+
(station_id = 1 is found in the LSK council_id=1 And station_id = 3 is found in the KBW council_id=2)
market_levy
------------------+-------------+-----------------+
| market_levy_id | council_id | amount_tendered |
+-----------------+-------------+-----------------+
| 1 | 1 | 5.00 |
+-----------------+-------------+-----------------+
| 2 | 2 | 5.00 |
+-----------------+-------------+-----------------+
| 3 | 1 | 5.00 |
+-----------------+-------------+-----------------+
mysql
SELECT c.council_name, (COUNT(market_levy.market_levy_id)+ COUNT(st.station_levy_id )) count, SUM(amount_tendered) revenue
FROM councils c
JOIN (
(SELECT council_id, amount_tendered,market_levy_id FROM market_levy WHERE transaction_date >= CURDATE() )
UNION ALL
(SELECT station_levy_id , councils.council_id, amount_tendered
FROM station_levy st
JOIN stations ON stations.station_id = st.station_id
JOIN councils ON councils .council_id= stations .council_id
WHERE transaction_datetime >= CURDATE()
)) totalCouncilRevenue USING (council_id)
group by council_id, c.council_name ORDER BY SUM(amount_tendered) DESC
Expected result
------------------+-------------+-----------------+
| council_name | count | revenue |
+-----------------+-------------+-----------------+
| LSK | 3 | 15.00 |
+-----------------+-------------+-----------------+
| KBW | 3 | 25.00 |
+-----------------+-------------+-----------------+
You are confusing columns in your UNION ALL matching council_id with station_levy_id, amount_tendered with council_id, and market_levy_id with amount_tendered.
Then, in your main query you try to access market_levy.market_levy_id and st.station_levy_id, but these columns are not accessible, as you select from a subquery called totalCouncilRevenue, not from tables labelled market_levy and st there.
Your query fixed:
SELECT
c.council_name,
COUNT(*) AS transaction_count,
SUM(amount_tendered) AS revenue
FROM councils c
JOIN
(
SELECT council_id, amount_tendered
FROM market_levy
WHERE transaction_date >= CURDATE()
UNION ALL
SELECT s.council_id, st.amount_tendered
FROM station_levy st
JOIN stations s ON s.station_id = st.station_id
WHERE st.transaction_datetime >= CURDATE()
) totalCouncilRevenue USING (council_id)
GROUP BY council_id, c.council_name
ORDER BY SUM(amount_tendered) DESC;
I prefer aggregating before joining, though:
SELECT
c.council_name,
COALESCE(t1.cnt, 0) + COALESCE(t2.cnt, 0) AS transaction_count,
COALESCE(t1.total, 0) + COALESCE(t2.total, 0) AS revenue
FROM councils c
LEFT JOIN
(
SELECT council_id, SUM(amount_tendered) as total, COUNT(*) as cnt
FROM market_levy
WHERE transaction_date >= CURDATE()
GROUP BY council_id
) t1 USING (council_id)
LEFT JOIN
(
SELECT s.council_id, SUM(st.amount_tendered) as total, COUNT(*) as cnt
FROM station_levy st
JOIN stations s ON s.station_id = st.station_id
WHERE st.transaction_datetime >= CURDATE()
GROUP BY s.council_id
) t2 USING (council_id)
ORDER BY revenue DESC;
Such queries are usually less prone to errors and are sometimes faster, because they may be able to use indexes more efficiently.

Mysql query the most visited url per user

I need to count the urls with the most visits per user.
Table name:visit_actions: mysql version:5.7
+----+--------+---------+---------------------+
| id | url_id | user_id | server_time |
+----+--------+---------+---------------------+
| 1 | 265338 | 4 | 2019-11-07 08:54:47 |
| 2 | 265405 | 1 | 2019-11-07 08:55:21 |
| 3 | 265391 | 4 | 2019-11-07 08:56:03 |
| 4 | 265338 | 1 | 2019-11-07 08:57:36 |
| 5 | 265338 | 1 | 2019-11-07 10:02:46 |
| 21 | 265207 | 5 | 2019-11-08 02:17:30 |
| 22 | 265207 | 5 | 2019-11-08 02:17:30 |
+----+--------+---------+---------------------+
I have tried this sql:
SELECT
url_id,
user_id,
count( * ) AS visit_times
FROM
visit_actions
GROUP BY
user_id,
url_id
ORDER BY
visit_times DESC
I expect the output :
+--------+---------+-------------+
| url_id | user_id | visit_times |
+--------+---------+-------------+
| 265338 | 4 | 1 |
| 265207 | 5 | 2 |
| 265338 | 1 | 2 |
+--------+---------+-------------+
Each user only finds the one with the most url_id.
Click Here Online Demo . Thanks folks!
On MySQL 8+ a fairly clean solution uses ROW_NUMBER with aggregation:
WITH cte AS (
SELECT url_id, user_id, COUNT(*) AS cnt,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY COUNT(*) DESC) rn
FROM visit_actions
GROUP BY url_id, user_id
)
SELECT
url_id,
user_id,
cnt AS visit_times
FROM cte
WHERE rn = 1;
If you had to do this on MySQL 5.7, here is one way:
SELECT
t1.url_id,
t1.user_id,
t1.cnt AS visit_times
FROM
(
SELECT url_id, user_id, COUNT(*) AS cnt
FROM visit_actions
GROUP BY url_id, user_id
) t1
INNER JOIN
(
SELECT user_id, MAX(cnt) AS max_cnt
FROM
(
SELECT url_id, user_id, COUNT(*) AS cnt
FROM visit_actions
GROUP BY url_id, user_id
) t
GROUP BY user_id
) t2
ON t1.user_id = t2.user_id AND
t1.cnt = t2.max_cnt;
Here's a MySQL 5.7 solution. Basically you have to find the maximum number of visits per user and then join the count of visits per user to that table to give the user and urls they have visited most. Note in your sample that yields 6 rows for user 1 as they have visited 6 sites twice.
SELECT c.url_id, c.user_id, c.visit_times
FROM (SELECT url_id, user_id, count( * ) AS visit_times
FROM visit_actions
GROUP BY user_id, url_id) c
JOIN (SELECT user_id, MAX(visit_times) AS max_visits
FROM (SELECT url_id, user_id, count( * ) AS visit_times
FROM visit_actions
GROUP BY user_id, url_id ) c
GROUP BY user_id) m ON m.user_id = c.user_id AND m.max_visits = c.visit_times
Output:
url_id user_id visit_times
265207 0 2
265338 1 2
265391 1 2
265394 1 2
265396 1 2
265410 1 2
265431 1 2
265338 4 1
Demo on SQLFiddle

mysql - selecting limited rows of duplicate columns

What I have
So I'm running this statement:
SELECT
i.id,
i.item_id,
v.item_to_map_id,
i.date,
COALESCE( SUM(CAST(CAST(v.score AS char) AS SIGNED)), 0 ) AS score
FROM item_to_map i
LEFT JOIN
vote_item v
ON i.id = v.item_to_map_id
GROUP BY
i.id, i.item_id, i.date, v.item_to_map_id
ORDER BY
item_id asc, score desc;
And I'm getting the following table:
+----+---------+----------------+---------------------+-------+
| id | item_id | item_to_map_id | date | score |
+----+---------+----------------+---------------------+-------+
| 1 | 1 | 1 | 2017-07-05 09:38:23 | 3 |
| 3 | 1 | 3 | 2017-07-05 09:38:23 | 0 |
| 2 | 1 | 2 | 2017-07-05 09:38:23 | -1 |
| 4 | 2 | NULL | 2017-07-05 09:38:23 | 0 |
| 5 | 2 | NULL | 2017-07-05 09:38:23 | 0 |
| 6 | 2 | NULL | 2017-07-05 09:38:24 | 0 |
+----+---------+----------------+---------------------+-------+
What I'm trying to do is select the first X of the repeated item_ids based on some ordering, for example, score or date.
What I've tried
I looked at this answer https://stackoverflow.com/a/1902167/6554121 and tried a modified version:
SELECT
i.id,
i.item_id,
v.item_to_map_id,
i.date,
COALESCE( SUM(CAST(CAST(v.score AS char) AS SIGNED)), 0 ) AS score
FROM item_to_map i
LEFT JOIN
vote_item v
ON i.id = v.item_to_map_id
WHERE
(
SELECT
COUNT(*)
FROM
item_to_map i2
WHERE
i2.item_id = i.item_id
) < 3
GROUP BY
i.id, i.item_id, i.date, v.item_to_map_id
ORDER BY item_id asc, score desc;
However this returns me no results
What I expected
If ordered by score:
+----+---------+----------------+---------------------+-------+
| id | item_id | item_to_map_id | date | score |
+----+---------+----------------+---------------------+-------+
| 1 | 1 | 1 | 2017-07-05 09:38:23 | 3 |
| 3 | 1 | 3 | 2017-07-05 09:38:23 | 0 |
| 4 | 2 | NULL | 2017-07-05 09:38:23 | 0 |
| 5 | 2 | NULL | 2017-07-05 09:38:23 | 0 |
+----+---------+----------------+---------------------+-------+
You can achieve this using session variables which simulate row number functionality:
SET #row_number = 0;
SET #item_id = 1;
SELECT t.id, t.item_id, t.item_to_map_id, t.date, t.score
FROM
(
SELECT
#row_number:=CASE WHEN #item_id = t.item_id
THEN #row_number + 1 ELSE 1 END AS rn,
#item_id:=t.item_id AS item_id,
t.id, t.item_to_map_id, t.date, t.score
FROM
(
SELECT
i.id,
i.item_id,
v.item_to_map_id,
i.date,
COALESCE( SUM(CAST(CAST(v.score AS char) AS SIGNED)), 0 ) AS score
FROM item_to_map i
LEFT JOIN vote_item v
ON i.id = v.item_to_map_id
GROUP BY
i.id, i.item_id, i.date, v.item_to_map_id
) t
ORDER BY
t.item_id, t.score DESC
) t
WHERE t.rn <= 2 -- this restricts to the first two rows per item_id group
-- as ordered by the logic in your ORDER BY clause
As far as I know, there is no nice way to get the first X records of a group in MySQL, unless your schema coincidentally happens to have row numbers already for each group. Using session variables as above is one way to handle this, and the performance might even be good as well.
Demo here:
Rextester

Group exactly four rows sql

I have a table with Transactions, amongst whose columns are id, created_at, and company_id. I'd like to group the four first transactions of every company and return the created_at values of each transaction on each row.
In other words, I want each row of my output to correspond to the four first transactions of each company (so grouping by company_id) with columns showing me the company_id and the created_at of each of those four transactions.
How do I do that?
Sample data:
id | company_id | created_at
---------------------------------
1123 | abcd | 10/12/2015
8291 | abcd | 10/14/2015
9012 | abcd | 10/15/2015
9540 | abcd | 10/16/2015
10342 | abcd | 10/21/2015
10456 | abcd | 10/22/2015
2301 | efgh | 10/13/2015
4000 | efgh | 11/01/2015
4023 | efgh | 11/03/2015
6239 | efgh | 11/08/2015
7500 | efgh | 11/14/2015
Sample output:
company_id | created_at_1 | created_at_2 | created_at_3 | created_at_4
--------------------------------------------------------------------------
abcd | 10/12/2015 | 10/14/2015 | 10/15/2015 | 10/16/2015
efgh | 10/13/2015 | 11/01/2015 | 11/03/2015 | 11/08/2015
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,company_id VARCHAR(12) NOT NULL
,created_at DATE NOT NULL
);
INSERT INTO my_table VALUES
( 1123,'abcd','2015/10/12'),
( 8291,'abcd','2015/10/14'),
( 9012,'abcd','2015/10/15'),
( 9540,'abcd','2015/10/16'),
(10342,'abcd','2015/10/21'),
(10456,'abcd','2015/10/22'),
( 2301,'efgh','2015/10/13'),
( 4000,'efgh','2015/11/01'),
( 4023,'efgh','2015/11/03'),
( 6239,'efgh','2015/11/08'),
( 7500,'efgh','2015/11/14');
SELECT x.*
FROM my_table x
JOIN my_table y
ON y.company_id = x.company_id
AND y.created_at <= x.created_at
GROUP
BY x.id
HAVING COUNT(*) <= 4
ORDER
BY company_id
, created_at;
+------+------------+------------+
| id | company_id | created_at |
+------+------------+------------+
| 1123 | abcd | 2015-10-12 |
| 8291 | abcd | 2015-10-14 |
| 9012 | abcd | 2015-10-15 |
| 9540 | abcd | 2015-10-16 |
| 2301 | efgh | 2015-10-13 |
| 4000 | efgh | 2015-11-01 |
| 4023 | efgh | 2015-11-03 |
| 6239 | efgh | 2015-11-08 |
+------+------------+------------+
A solution with variables will be orders of magnitude faster, e.g...
SELECT a.id
, a.company_id
, a.created_at
FROM
( SELECT x.*
, CASE WHEN #prev = x.company_id THEN #i:=#i+1 ELSE #i:=1 END i, #prev:=x.company_id prev
FROM my_table x
, (SELECT #i:=1,#prev:=null) vars
ORDER
BY x.company_id
, x.created_at
) a
WHERE i <= 4;
One possible way is the following:
select company_id,
min(created_at) as created_at_1,
(select created_at from t where company_id=t1.company_id order by created_at limit 1 offset 1) as created_at_2,
(select created_at from t where company_id=t1.company_id order by created_at limit 1 offset 2) as created_at_3,
(select created_at from t where company_id=t1.company_id order by created_at limit 1 offset 3) as created_at_4
from t as t1
group by company_id
EDIT:
Another possibility (inspired by this answer) is:
select company_id,
min(created_at) as created_at_1,
min(case r when 2 then created_at else null end) as created_at_2,
min(case r when 3 then created_at else null end) as created_at_3,
min(case r when 4 then created_at else null end) as created_at_4
from (
select company_id, created_at,
(case company_id when #curType
then #curRank := #curRank + 1
else #curRank := 1 and #curType := company_id end)+1 as r
from t, (select #curRank := 0, #curType := '') f
order by company_id, created_at
) as o
where r <= 4
group by company_id
Could by like this maybe?
SELECT S.company_id,
A.created_at created_at_1,
B.created_at created_at_2,
C.created_at created_at_3,
D.created_at created_at_4
FROM sample S
LEFT JOIN sample A on S.company_id = A.company_id AND A.id NOT IN(S.id)
LEFT JOIN sample B on S.company_id = B.company_id AND B.id NOT IN(S.id, A.id)
LEFT JOIN sample C on S.company_id = C.company_id AND C.id NOT IN(S.id, A.id, B.id)
LEFT JOIN sample D on S.company_id = D.company_id AND D.id NOT IN(S.id, A.id, B.id, C.id)
GROUP BY S.company_id
http://sqlfiddle.com/#!9/c577e/3
It might not be very efficient, though.
And they are not in order, because your American date format is not good to be sorted. Better switch to TIMESTAMP format.

Selecting max(date) twice from the same table, for different conditions

This is a small snippet of my table, which currently contains ~10,000,000 rows
+---------+---------------------+-----------+----------------+
| card_id | date | avg_price | foil_avg_price |
+---------+---------------------+-----------+----------------+
| 10000 | 2014-06-28 09:05:56 | 5.02 | 10.22 |
| 20000 | 2014-06-28 09:05:54 | 14.58 | 25.10 |
| 10000 | 2014-06-29 09:05:56 | 0.00 | 19.62 |
| 20000 | 2014-06-29 09:05:54 | 14.58 | 0.00 |
| 10000 | 2014-07-01 09:05:56 | 0.00 | 19.62 |
| 20000 | 2014-07-01 09:05:54 | 0.00 | 25.10 |
+---------+---------------------+-----------+----------------+
It is a price history for cards, including what the avg_price and what the foil_avg_price was for each day or so.
I'd like to select, for a group of card id's the most recent date when the foil_avg_price was > 0, what that price was, and the most recent date that the avg_price was > 0, and what that price was. My resulting data set for the above would look something like this:
+---------+---------------------+-----------+---------------------+----------------+
| card_id | avg_date | avg_price | foil_date | foil_avg_price |
+---------+---------------------+-----------+---------------------+----------------+
| 10000 | 2014-06-28 09:05:56 | 5.02 | 2014-07-01 09:05:54 | 19.62 |
| 20000 | 2014-06-29 09:05:54 | 14.58 | 2014-07-01 09:05:54 | 25.10 |
+---------+---------------------+-----------+---------------------+----------------+
I'm sure that this involves an INNER JOIN on the same table but I can't quite get my head around it. Any help would be much appreciated.
Three steps:
Find last price date
Find last foil price date
resolve prices on these dates
So,
SELECT dates.*, price.avg_price, foilprice.foil_avg_price
FROM (
SELECT
card_id,
MAX(IF(avg_price>0, `date`, '0001-01-01')) AS avg_date,
MAX(IF(foil_avg_price>0, `date`, '0001-01-01')) AS foil_avg_date
FROM card_price
GROUP BY card_id
) AS dates
INNER JOIN card_price AS price
ON dates.card_id=price.`date`
INNER JOIN card_price AS foilprice
ON dates.card_id=foilprice.`date`
Try this query
SELECT A.card_id,max(date),MAX(avg_price), (SELECT MAX(date) FROM test WHERE card_id = A.card_id AND foil_avg_price = MAX(A.foil_avg_price)) AS date,MAX(foil_avg_price) FROM test A
GROUP BY A.card_id
How about if you had 20,000,000 rows...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(card_id INT NOT NULL
,date DATETIME NOT NULL
,price_type VARCHAR(20) NOT NULL
,price_value DECIMAL(5,2) NOT NULL
,PRIMARY KEY(card_id,date,price_type)
);
INSERT INTO my_table VALUES
(10000,'2014-06-28 09:05:56','avg_price',5.02),
(20000,'2014-06-28 09:05:54','avg_price',14.58),
(10000,'2014-06-29 09:05:56','avg_price',0.00),
(20000,'2014-06-29 09:05:54','avg_price',14.58),
(10000,'2014-07-01 09:05:56','avg_price',0.00),
(20000,'2014-07-01 09:05:54','avg_price',0.00),
(10000,'2014-06-28 09:05:56','foil_avg_price',10.22),
(20000,'2014-06-28 09:05:54','foil_avg_price',25.10),
(10000,'2014-06-29 09:05:56','foil_avg_price',19.62),
(20000,'2014-06-29 09:05:54','foil_avg_price',0.00),
(10000,'2014-07-01 09:05:56','foil_avg_price',19.62),
(20000,'2014-07-01 09:05:54','foil_avg_price',25.10);
SELECT x.*
FROM my_table x
JOIN
( SELECT card_id,price_type,MAX(date) max_date FROM my_table WHERE price_value > 0 GROUP BY card_id,price_type) y
ON y.card_id = x.card_id
AND y.price_type = x.price_type
AND y.max_date = x.date;
+---------+---------------------+----------------+-------------+
| card_id | date | price_type | price_value |
+---------+---------------------+----------------+-------------+
| 10000 | 2014-06-28 09:05:56 | avg_price | 5.02 |
| 10000 | 2014-07-01 09:05:56 | foil_avg_price | 19.62 |
| 20000 | 2014-06-29 09:05:54 | avg_price | 14.58 |
| 20000 | 2014-07-01 09:05:54 | foil_avg_price | 25.10 |
+---------+---------------------+----------------+-------------+
Try this:
SELECT a.card_id, a.avg_date, a.avg_price, b.foil_date, b.foil_avg_price
FROM (SELECT c.card_id, c.date AS avg_date, c.avg_price
FROM cards c
INNER JOIN (SELECT c.card_id, MAX(IF(c.avg_price > 0, c.date, NULL)) avg_date
FROM cards c GROUP BY c.card_id
) a ON c.card_id = a.card_id AND c.date = a.avg_date
) AS a
LEFT JOIN (SELECT c.card_id, c.date AS foil_date, c.foil_avg_price
FROM cards c
INNER JOIN (SELECT c.card_id, MAX(IF(c.foil_avg_price > 0, c.date, NULL)) foil_date
FROM cards c GROUP BY c.card_id
) a ON c.card_id = a.card_id AND c.date = a.foil_date
) AS b ON a.card_id = b.card_id ;
OR
SELECT a.card_id, a.avg_date, a.avg_price, b.foil_date, b.foil_avg_price
FROM (SELECT *
FROM (SELECT c.card_id, c.date, c.avg_price
FROM cards c WHERE c.avg_price > 0
ORDER BY c.date DESC
) AS A
GROUP BY A.date
) AS a
LEFT JOIN ( SELECT *
FROM (SELECT c.card_id, c.date, c.foil_avg_price
FROM cards c WHERE c.foil_avg_price > 0
ORDER BY c.date DESC
) AS B
GROUP BY B.date
) AS b ON a.card_id = b.card_id;