Mysql - JOIN two tables group by 2 dates - mysql

I have two tables that are not linked by a foreign key but that I would like to join.
Here are the two functional SQL queries I would like to join:
SELECT DATE(added) as 'day', COUNT(*) as 'TopicSub'
FROM user_subscription
WHERE topic_id = 39
GROUP BY DATE(added)
SELECT DATE(date) as 'day', COUNT(*) as 'QSub'
FROM user_submitted_q
WHERE question_id IN (SELECT id FROM questions WHERE topic_id = 39)
GROUP BY DATE(date)
These two queries return a result including the total number of entries grouped by days.
First concern the days of the two queries do not necessarily match (There are no entries for all the days of the period), I would like to merge the results and have 0 if the other column does not include an equivalent date.
After a lot of unsuccessful attempts with all possible joints, I found this query that might work on PostgreSQL:
SELECT *
FROM
(SELECT DATE(date) as 'day', COUNT(*) as 'QSub'
FROM user_submitted_q
WHERE question_id IN (SELECT id FROM questions WHERE topic_id = 39)
GROUP BY DATE(date)) AS q1
FULL OUTER JOIN
(SELECT DATE(added) as 'day', COUNT(*) as 'TopicSub'
FROM user_subscription
WHERE topic_id = 39
GROUP BY DATE(added)) AS q2 ON q1.day = q2.day
ORDER BY
day
But unfortunately I use Mysql5.7
Here is the desired result:
Date | QSub | TopicSub
-----------+------+---------
2018-02-09 | 5 | 1
2018-02-19 | 19 | 13
2018-02-21 | 12 | 1
2018-02-22 | 43 | 0
2018-02-25 | 0 | 1
Sample user_submitted_q data:
+----+---------+-------------+--------+---------------------+----------------+
| id | user_id | question_id | result | date | ip |
+----+---------+-------------+--------+---------------------+----------------+
| 1 | 2 | 436 | good | 2018-02-09 00:13:15 | 176.159.30.253 |
| 2 | 2 | 409 | good | 2018-02-09 00:13:15 | 176.159.30.253 |
| 3 | 3 | 651 | wrong | 2018-02-09 00:13:53 | 77.136.14.187 |
| 4 | 3 | 651 | wrong | 2018-02-09 00:13:53 | 77.136.14.187 |
| 5 | 1 | 96 | wrong | 2018-02-09 00:21:51 | 77.69.200.124 |
| 6 | 1 | 24 | good | 2018-02-09 00:21:51 | 77.69.200.124 |
| 7 | 1 | 25 | good | 2018-02-09 00:21:51 | 77.69.200.124 |
| 8 | 1 | 96 | wrong | 2018-02-09 00:26:52 | 77.69.200.124 |
| 9 | 1 | 24 | good | 2018-02-09 00:26:52 | 77.69.200.124 |
| 10 | 1 | 25 | good | 2018-02-09 00:26:52 | 77.69.200.124 |
+----+---------+-------------+--------+---------------------+----------------+
Sample user_subscription data:
+----+---------+----------+---------------------+
| id | user_id | topic_id | added |
+----+---------+----------+---------------------+
| 8 | 1 | 39 | 2018-02-09 00:27:30 |
| 9 | 4 | 47 | 2018-02-09 00:42:34 |
| 10 | 4 | 19 | 2018-02-09 00:42:34 |
| 11 | 5 | 47 | 2018-02-09 00:54:14 |
| 13 | 6 | 47 | 2018-02-09 01:00:23 |
| 14 | 6 | 19 | 2018-02-09 01:00:23 |
| 17 | 8 | 47 | 2018-02-09 01:06:50 |
| 18 | 8 | 19 | 2018-02-09 01:06:50 |
| 19 | 9 | 47 | 2018-02-09 01:08:33 |
| 20 | 9 | 19 | 2018-02-09 01:08:33 |
+----+---------+----------+---------------------+
Thank you in advance for your help

Given
MariaDB [sandbox]> select * from t order by dt;
+------------+
| dt |
+------------+
| 2018-01-01 |
| 2018-01-02 |
| 2018-01-03 |
| 2018-01-03 |
| 2018-01-04 |
| 2018-01-04 |
+------------+
6 rows in set (0.00 sec)
MariaDB [sandbox]> select * from t1 order by dt;
+------------+
| dt |
+------------+
| 2018-01-02 |
| 2018-01-03 |
| 2018-01-04 |
| 2018-01-05 |
| 2018-01-05 |
| 2018-01-06 |
| 2018-01-06 |
| 2018-01-07 |
| 2018-01-07 |
+------------+
9 rows in set (0.00 sec)
select tu.dt,(select count(*) from t where t.dt = tu.dt) tdt,
(Select count(*) from t1 where t1.dt = tu.dt) t1dt
from
(
select dt from t
union
select dt from t1
) tu;
Will establish all dates spanning both tables then the sub queries will count them.
+------------+------+------+
| dt | tdt | t1dt |
+------------+------+------+
| 2018-01-01 | 1 | 0 |
| 2018-01-02 | 1 | 1 |
| 2018-01-03 | 2 | 1 |
| 2018-01-04 | 2 | 1 |
| 2018-01-05 | 0 | 2 |
| 2018-01-06 | 0 | 2 |
| 2018-01-07 | 0 | 2 |
+------------+------+------+
7 rows in set (0.00 sec)

My SQL does not support full outer joins but you can simulate that behavior as follows:
Select Col1, Col2, ...
From Table1
Left Join Table 2 on Table1.col = Table2.col
Union
Select Col1, Col2, ...
From Table 1
Right Join Table 2 on Table1.col = Table2.col
If you care to preserve duplicate rows you would change the Union to a Union All

Related

Compare number of credits users using two different tables in MySql 8

On my MySql version 8.0.17 database I have two different tables.
In the table table_credits_used I stored for each user the number of access in shared area for current date.
mysql> SELECT
COUNT(*) tCount,
tNameUser
FROM
`table_credits_used`
WHERE
tDate = CURDATE()
GROUP BY
tDate,
tNameUser;
+--------+-----------+
| tCount | tNameUser |
+--------+-----------+
| 1 | Chenoa |
| 6 | Kimi |
| 1 | Aponi |
| 1 | Imala |
| 6 | Doba |
| 1 | Elsu |
| 1 | Jair |
| 2 | Nova |
| 1 | Aarav |
| 1 | Aarush |
| 1 | Aaryan |
| 1 | Aayansh |
| 1 | Aayush |
| 5 | Abeer |
| 1 | Adhrit |
| 2 | Adi |
| 1 | Aditya |
| 1 | Advaith |
| 6 | Advay |
| 6 | Advik |
| 6 | Agastya |
+--------+-----------+
21 rows in set (0.04 sec)
In the table table_credit_to_use is stored for each user the maximum number of access in shared area for current date.
mysql> SELECT * FROM `table_credit_to_use`;
+--------+-----------+-----+
| tCount | tNameUser | tID |
+--------+-----------+-----+
| 1 | Chenoa | 1 |
| 6 | Kimi | 2 |
| 1 | Aponi | 3 |
| 1 | Imala | 4 |
| 6 | Doba | 5 |
| 1 | Elsu | 6 |
| 1 | Jair | 7 |
| 2 | Nova | 8 |
| 1 | Aarav | 9 |
| 1 | Aarush | 10 |
| 1 | Aaryan | 11 |
| 1 | Aayansh | 12 |
| 1 | Aayush | 13 |
| 6 | Abeer | 14 |
| 1 | Adhrit | 15 |
| 2 | Adi | 16 |
| 1 | Aditya | 17 |
| 1 | Advaith | 18 |
| 6 | Advay | 19 |
| 6 | Advik | 20 |
| 6 | Agastya | 21 |
+--------+-----------+-----+
21 rows in set (0.04 sec)
I need to show users who have not consumed the maximum number of logins for the current date.
In this example, I need this output:
+--------+-----------+-----+
| tCount | tNameUser | tID |
+--------+-----------+-----+
| 5 | Abeer | 14 |
+--------+-----------+-----+
Because the user Abeer has the right to 6 total accesses, but today he has used only 5.
Any suggestion?
My table structure below on db-fiddle.com, which offers MySQL 8.0
Make sure you have you have ONLY_FULL_GROUP_BY disabled in MySQL.
mysql> SET GLOBAL sql_mode=(SELECT REPLACE(##sql_mode,'ONLY_FULL_GROUP_BY',''));
The following SQL should do the trick:
SELECT cu.tCount, cu.tNameUser, cu.tID FROM table_credits_used cu
INNER JOIN table_credit_to_use ctu ON (ctu.tID = cu.tID)
WHERE cu.tCount < ctu.tCount
AND cu.tDate = CURDATE()
GROUP BY cu.tDate, cu.tNameUser;
Note: The ONLY_FULL_GROUP_BY is enabled on the dbfiddle hence I couldn't try the above full query. The one that worked in db fiddle is the following without the group by clause:
SELECT cu.tCount, cu.tNameUser, cu.tID FROM table_credits_used cu
INNER JOIN table_credit_to_use ctu ON (ctu.tID = cu.tID)
WHERE cu.tCount < ctu.tCount;
Your sample data has no tdate
But you can join the table with the subselect
SELECT
t1.tNameUser,t1.tCount,t2.tCount current_count
FROM `table_credit_to_use` t1
INNER JOIN ( SELECT
COUNT(*) tCount,
tNameUser
FROM
`table_credits_used`
WHERE
tDate = CURDATE()
GROUP BY
tDate,
tNameUser) t2 ON t1.tNameUser = t2.tNameUser

How to select the group of most recent records based on specific conditions in MySQL

I am looking for a way to select group of most recent records. I have two tables:
Table1:
+---------+-------------+--------+--------+------------------+
| user_id | category_id | field1 | field2 | date |
+---------+-------------+--------+--------+------------------+
| 19 | 2 | 3 | 3 | 22/01/2021 15:00 |
| 19 | 1 | 1 | 3 | 22/01/2021 15:00 |
| 19 | 5 | 2 | 2 | 22/01/2021 15:00 |
| 18 | 1 | 2 | 2 | 22/01/2021 15:04 |
| 18 | 31 | 4 | 1 | 22/01/2021 15:04 |
| 18 | 40 | 3 | 1 | 22/01/2021 15:04 |
| 19 | 40 | 2 | 2 | 22/01/2021 15:21 |
| 19 | 166 | 1 | 1 | 22/01/2021 15:21 |
| 19 | 40 | 5 | 5 | 22/01/2021 16:23 |
| 19 | 166 | 6 | 6 | 22/01/2021 16:23 |
+---------+-------------+--------+--------+------------------+
Table2:
+---------+-------------+
| user_id | category_id |
+---------+-------------+
| 18 | 1 |
| 18 | 31 |
| 18 | 40 |
| 19 | 40 |
| 19 | 166 |
+---------+-------------+
I would like to select the most recent rows from Table1 for specific user_id and category_id that exist in Table2. For, example if user_id=19, I would like to get the following group of records:
+---------+-------------+--------+--------+------------------+
| user_id | category_id | field1 | field2 | date |
+---------+-------------+--------+--------+------------------+
| 19 | 40 | 5 | 5 | 22/01/2021 16:23 |
| 19 | 166 | 6 | 6 | 22/01/2021 16:23 |
+---------+-------------+--------+--------+------------------+
My the most closest query is given the working fiddle, but that gives me the last four records of Table1.
This seems to do what you want for all users:
SELECT T1.*
FROM Table1 t1
WHERE T1.DATE = (SELECT MAX(tt1.DATE)
FROM Table1 tt1 JOIN
Table2 tt2
ON TT1.user_id = Tt2.user_id AND
tt1.category_id = tt2.category_id
WHERE tt1.user_id = t1.user_id AND
tt1.category_id = t1.category_id
)
You can add a filter in the outer query for a particular user.
I think the most efficient way is using Ties in sql :
SELECT TOP 1 WITH TIES t1.*
FROM Table1 t1 join Table2 t2 on t1.user_id=t2.user_id and t1.category_id=t2.category_id
ORDER BY ROW_NUMBER() OVER(PARTITION BY t1.[user_id],t1.[category_id] ORDER BY t1.date desc)

sql joins with multiple conditions

i have two tables, (say bill and soldproduct)
select * from bill;
+------+------------+------------+
| id | solddate | customerId |
+------+------------+------------+
| 11 | 2018-07-23 | 1 |
| 12 | 2018-07-21 | 1 |
| 13 | 2018-08-02 | 2 |
| 14 | 2018-08-08 | 2 |
| 15 | 2018-08-08 | 1 |
| 16 | 2018-08-08 | 1 |
+------+------------+------------+
select * from soldproduct;
+--------+-------------+----------+-------+------------+
| billid | productname | quantity | price | totalprice |
+--------+-------------+----------+-------+------------+
| 11 | book | 2 | 100 | 200 |
| 11 | pen | 10 | 10 | 100 |
| 11 | pencil | 5 | 2 | 10 |
| 12 | pencil | 5 | 2 | 10 |
| 13 | pen | 10 | 10 | 100 |
| 13 | book | 2 | 100 | 200 |
| 14 | pen | 1 | 10 | 10 |
| 14 | bottle | 1 | 75 | 75 |
| 15 | phone | 1 | 5000 | 5000 |
| 16 | lock | 15 | 50 | 750 |
+--------+-------------+----------+-------+------------+
I need to find the highest bill id using totalprice.
I tried using
select billid,sum(totalprice)
from soldproduct
where billid in (select id from bill where solddate >= date_sub(curdate(),interval 1 month))
group by billid
order by totalprice desc;
and my output is
+--------+-----------------+
| billid | sum(totalprice) |
+--------+-----------------+
| 15 | 5000 |
| 16 | 750 |
| 11 | 310 |
| 13 | 300 |
| 12 | 10 |
| 14 | 85 |
+--------+-----------------+
How do i get the same output with a single query using joins (without using subquery)?
try the following join
select billid,sum(totalprice)
from soldproduct
join bill on soldproduct.billid = bill.id and solddate >= date_sub(curdate(),interval 1
month)
group by billid
order by totalprice desc;
Can you try the below query:(I do not tested it out)
SELECT billid, SUM(totalprice)
FROM soldproduct SP
JOIN bill B ON (B.id = SP.billid)
WHERE B.solddate BETWEEN (CURRENT_DATE() - INTERVAL 1 MONTH) AND CURRENT_DATE()
GROUP BY SP.billid
ORDER BY SP.totalprice DESC;

Include all the days when using limit?

I want want to get all rows from 1st August to 31 August but limit to 1500 rows, however, it must include all the possible days from 1 to 31. So from each day I expected around 48 rows.
How is that possible with SQL Query?
I have tried this SQL query but it wont include all the days because I am using limit.
SELECT * FROM table1
WHERE `submit` >= '2014-08-01' AND `submit` <= '2014-08-31'
order by `submit`
LIMIT 1500
Update:
It should get all the possible days from 1 to 31, if any days does not have enough rows - then include the remaining rows from any days. If there is no record on the 7th Aug for example, then any other days should include more rows.
Update2:
Let say submit >= '2014-08-01' AND submit <= '2014-08-31' there are 3000 rows but I only want 1000 rows (if possible) from 01 to 31.
Each day should have approx 32 rows.
For example
Aug 01 have 32 rows
Aug 02 have 32 rows
Aug 03 only 0 rows
Aug 04 only 0 rows
Aug 05 have 32 rows
to
Aug 31 have 32 rows
This is a troublesome requirement, because it calls for the unpredictable omission of data under unpredictable circumstances. This is the kind of thing that gives auditors bad dreams (not to mention programmers). It may help you to figure out what exactly you are trying to do with this result set, and tighten up your requirement.
Assuming your table has an autoincrementing unique id column, here's a query that will retrieve the last row from the table for each date.
SELECT 0 AS priority, t.* FROM table1 AS t
WHERE id IN (SELECT MAX(id) AS id FROM table1 GROUP BY DATE(submit))
You can then do this to get the remaining rows:
SELECT 1 AS priority, t.* FROM table1 AS t
WHERE id NOT IN (SELECT MAX(id) AS id FROM table1 GROUP BY DATE(submit))
You can take the union of these, sort them, and limit them.
SELECT *
FROM (
SELECT 0 AS priority, t.* FROM table1 AS t
WHERE id IN (SELECT MAX(id) AS id FROM table1 GROUP BY DATE(submit))
UNION ALL
SELECT 1 AS priority, t.* FROM table1 AS t
WHERE id NOT IN (SELECT MAX(id) AS id FROM table1 GROUP BY DATE(submit))
) AS results
ORDER BY priority, submit
LIMIT 1500
This will give you at least one row from each submit date, and then the rest of the rows, but never more than 1500. It makes no attempt to balance the number of rows from each date, however.
Step 1. The data table...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,dt DATE NOT NULL
);
INSERT INTO my_table (dt) VALUES
('2014-07-28'),
('2014-07-29'),
('2014-08-01'),
('2014-08-01'),
('2014-08-02'),
('2014-08-03'),
('2014-08-05'),
('2014-08-05'),
('2014-08-05'),
('2014-08-05'),
('2014-08-07'),
('2014-08-07'),
('2014-08-09'),
('2014-08-10'),
('2014-08-10'),
('2014-08-10'),
('2014-08-11'),
('2014-08-13'),
('2014-08-13'),
('2014-08-13'),
('2014-08-13'),
('2014-08-13'),
('2014-08-13'),
('2014-08-14'),
('2014-08-14'),
('2014-08-15'),
('2014-08-17'),
('2014-08-17'),
('2014-08-18'),
('2014-08-18'),
('2014-08-18'),
('2014-08-19'),
('2014-08-19'),
('2014-08-21'),
('2014-08-21'),
('2014-08-21'),
('2014-08-21'),
('2014-08-22'),
('2014-08-23'),
('2014-08-25'),
('2014-08-25'),
('2014-08-26'),
('2014-08-26'),
('2014-08-26'),
('2014-08-27'),
('2014-08-28'),
('2014-08-29'),
('2014-08-29'),
('2014-08-29'),
('2014-08-29'),
('2014-08-29'),
('2014-08-30'),
('2014-08-31'),
('2014-08-31');
SELECT * FROM my_table;
+----+------------+
| id | dt |
+----+------------+
| 1 | 2014-07-28 |
| 2 | 2014-07-29 |
| 3 | 2014-08-01 |
| 4 | 2014-08-01 |
| 5 | 2014-08-02 |
| 6 | 2014-08-03 |
| 7 | 2014-08-05 |
| 8 | 2014-08-05 |
| 9 | 2014-08-05 |
| 10 | 2014-08-05 |
| 11 | 2014-08-07 |
| 12 | 2014-08-07 |
| 13 | 2014-08-09 |
| 14 | 2014-08-10 |
| 15 | 2014-08-10 |
| 16 | 2014-08-10 |
...
...
| 44 | 2014-08-26 |
| 45 | 2014-08-27 |
| 46 | 2014-08-28 |
| 47 | 2014-08-29 |
| 48 | 2014-08-29 |
| 49 | 2014-08-29 |
| 50 | 2014-08-29 |
| 51 | 2014-08-29 |
| 52 | 2014-08-30 |
| 53 | 2014-08-31 |
| 54 | 2014-08-31 |
+----+------------+
Step 2. A calendar utility table holding all conceivable dates... (there are other ways of doing this, but - unusually - this problem requires that this is done here, in MySQL, rather than in post-processing)
SELECT *
FROM calendar
WHERE dt BETWEEN '2014-07-28' AND '2014-09-02';
+------------+
| dt |
+------------+
| 2014-07-28 |
| 2014-07-29 |
| 2014-07-30 |
| 2014-07-31 |
| 2014-08-01 |
| 2014-08-02 |
| 2014-08-03 |
| 2014-08-04 |
| 2014-08-05 |
| 2014-08-06 |
| 2014-08-07 |
| 2014-08-08 |
| 2014-08-09 |
| 2014-08-10 |
| 2014-08-11 |
| 2014-08-12 |
| 2014-08-13 |
| 2014-08-14 |
| 2014-08-15 |
| 2014-08-16 |
| 2014-08-17 |
| 2014-08-18 |
| 2014-08-19 |
| 2014-08-20 |
| 2014-08-21 |
| 2014-08-22 |
| 2014-08-23 |
| 2014-08-24 |
| 2014-08-25 |
| 2014-08-26 |
| 2014-08-27 |
| 2014-08-28 |
| 2014-08-29 |
| 2014-08-30 |
| 2014-08-31 |
| 2014-09-01 |
| 2014-09-02 |
+------------+
37 rows in set
Step 3. The query (guaranteeing at least one result per day and no more than 50 results overall)
SELECT dt FROM
(
SELECT a.dt
, COALESCE(rank,0)
FROM calendar a
LEFT
JOIN
( SELECT x.dt,COUNT(*) rank
FROM my_table x
JOIN my_table y
ON y.dt = x.dt
AND y.id <= x.id
GROUP
BY x.id
) b
ON b.dt = a.dt
WHERE a.dt BETWEEN '2014-08-01' AND '2014-08-31'
ORDER
BY rank,dt
LIMIT 50
)n
ORDER BY dt;
+------------+
| dt |
+------------+
| 2014-08-01 |
| 2014-08-01 |
| 2014-08-02 |
| 2014-08-03 |
| 2014-08-04 |
| 2014-08-05 |
| 2014-08-05 |
| 2014-08-05 |
| 2014-08-06 |
| 2014-08-07 |
| 2014-08-07 |
| 2014-08-08 |
| 2014-08-09 |
| 2014-08-10 |
| 2014-08-10 |
| 2014-08-10 |
| 2014-08-11 |
| 2014-08-12 |
| 2014-08-13 |
| 2014-08-13 |
| 2014-08-13 |
| 2014-08-14 |
| 2014-08-14 |
| 2014-08-15 |
| 2014-08-16 |
| 2014-08-17 |
| 2014-08-17 |
| 2014-08-18 |
| 2014-08-18 |
| 2014-08-18 |
| 2014-08-19 |
| 2014-08-19 |
| 2014-08-20 |
| 2014-08-21 |
| 2014-08-21 |
| 2014-08-21 |
| 2014-08-22 |
| 2014-08-23 |
| 2014-08-24 |
| 2014-08-25 |
| 2014-08-25 |
| 2014-08-26 |
| 2014-08-26 |
| 2014-08-27 |
| 2014-08-28 |
| 2014-08-29 |
| 2014-08-29 |
| 2014-08-30 |
| 2014-08-31 |
| 2014-08-31 |
+------------+
50 rows in set (0.00 sec)
Something crazy like this perhaps?
SELECT * FROM
(SELECT * FROM
(SELECT * FROM table1
WHERE `submit` = '2014-08-01'
LIMIT 48
UNION SELECT * FROM table1
WHERE `submit` = '2014-08-02'
LIMIT 48
UNION SELECT * FROM table1
WHERE `submit` = '2014-08-03'
LIMIT 48
UNION SELECT * FROM table1
WHERE `submit` = '2014-08-04'
LIMIT 48
etc...
)
UNION SELECT * FROM Table1
WHERE `submit` >= '2014-08-01' AND `submit` <= '2014-08-31'
)
order by `submit`
LIMIT 1500

join with a group by?

i have a table called rc_language_type_table with:
id language
1 english
2 Xhosa
3 afrikaans
etc
then i have a table rc_language_type_assoc_table with:
profile_id | language_type_id |
+------------+------------------+
| 3 | 1 |
| 13 | 1 |
| 15 | 1 |
| 16 | 1 |
where i have profiles and each profile is connected to a language id in a 1 to many
so then i did:
select *,count(*) from rc_language_type_assoc_table group by language_type_id;
+------------+------------------+----------+
| profile_id | language_type_id | count(*) |
+------------+------------------+----------+
| 3 | 1 | 96 |
| 3 | 2 | 19 |
| 3 | 3 | 18 |
| 64 | 4 | 51 |
| 94 | 5 | 10 |
| 37 | 6 | 26 |
| 3 | 7 | 21 |
| 3 | 8 | 4 |
| 3 | 9 | 6 |
| 88 | 10 | 4 |
| 3 | 11 | 3 |
+------------+------------------+----------+
what i want now is: instead having the language_type_id i want to display the actual language...how would i do this please???
i tried:
select *, count(*)
from rc_language_type_assoc_table, rc_language_type_table
group by language_type_id
where rc_language_type_assoc_table.language_type_id = rc_language_type_table.id;
but i get a syntax error...
please help??
thank you
GROUP BY should be "after" the WHERE statement and not before
select *, count(*)
from rc_language_type_assoc_table, rc_language_type_table
where rc_language_type_assoc_table.language_type_id = rc_language_type_table.id
group by language_type_id ;