mysql combine 2 table with a common group by column - mysql

i've 2 tables with date, in timestamp format,
i have to count how many record there are every week in total...
...and have the result in just one query...
so i'll explain it better with examples:
2 table, the first one:
table name: visitor
ID | time | ref (now i put in fake timestamp in time column sorry)
--------------------
1 | 3455 | john
2 | 3566 | ted (week 40)
3 | 8353 | ted (week 38)
4 | 6673 | katy
5 | 6365 | ted (week 40)
6 | 4444 | john
7 | 3555 | ted (week 40)
and the second one (very similar):
table name: users
ID | time | ref (now i put in fake timestamp in time column sorry)
--------------------
1 | 3455 | ted (week 41)
2 | 3566 | ted (week 42)
3 | 8353 | ted (week 40)
4 | 6673 | katy
5 | 6365 | ted (week 41)
6 | 4444 | john
7 | 3555 | ted (week 38)
8 | 6789 | ted (week 43)
i do this query and i obtain this result:
SELECT WEEK(FROM_UNIXTIME(time)) AS week, COUNT( * ) AS tot
FROM visitor WHERE ref='ted' GROUP BY week
table result #1
week | tot
----------
38 | 1
40 | 3
43 | 1
the i do the some for the second table:
SELECT WEEK(FROM_UNIXTIME(time)) AS week, COUNT( * ) AS totuser
FROM users WHERE ref='ted' GROUP BY week
table result #2
week | totuser
----------
38 | 1
40 | 1
41 | 2
42 | 1
but i want with just one query do this result:
week | tot | totusers
---------------------- (when both are 0 doent appear -like 39 for example-)
38 | 1 | 1
40 | 3 | 1
41 | 0 | 2
42 | 0 | 1
43 | 1 | 0
i know that i've to use LEFT JOIN, GROUP BY and IFNULL but i'm doing always something wrong and i cant figure it out.
order by WEEK desc
thank u for any help.

Technically, what you want is a full outer join, but MySQL does not support that. I approach this using union all and group by:
SELECT weeek, SUM(visitors) as visitors, SUM(users) as users
FROM ((SELECT WEEK(FROM_UNIXTIME(time)) AS week, COUNT( * ) AS visitors, 0 as users
FROM visitor
WHERE ref='ted'
GROUP BY week
) UNION ALL
(SELECT WEEK(FROM_UNIXTIME(time)) AS week, 0, COUNT( * )
FROM users
WHERE ref ='ted'
GROUP BY week
)
) w
GROUP BY week
ORDER BY week;
Note: As in your data, this will only include weeks that have either visitors or users. If you want weeks with neither, it is best to start with a table containing all the weeks you want (some sort of calendar table).

Related

Mysql - Group by month with unique subscriber user. User should not be repeated

I have the following table with all subscriber users. I want unique subscriber user group by month but if user has already subscribed then should not be repeated in the count.
ID | USER | CREATED DATE
1 | 1 | 2019-10-16
2 | 2 | 2019-10-18
3 | 3 | 2019-12-06
4 | 2 | 2020-01-01 (* This is repeated so should not be count in 01)
5 | 4 | 2020-01-05
6 | 5 | 2020-01-11
7 | 1 | 2020-02-14 (* This is repeated so should not be count in 02)
8 | 2 | 2020-03-06 (* This is repeated so should not be count in 03)
9 | 5 | 2020-03-15 (* This is repeated so should not be count in 03)
My expected output should be the following. Total users are 5 so the sum of total must be 5.
TOTAL | MONTH
2 | 10
1 | 12
2 | 01
0 | 02
0 | 03
Use group by to get the first month and then aggregate again:
select year(min_cd), month(min_cd), count(*)
from (select user, min(created_date) as min_cd
from t
group by user
) u
group by year(min_cd), month(min_cd);
This does not include the 0 values, but they seem somewhat arbitrary.
If you do want them with your data, then one method is window functions and conditional aggregation:
select year(min_cd), month(min_cd),
sum( min_cd = created_date )
from (select user, min(created_date) over (partition by user) as min_cd
from t
) u
group by year(min_cd), month(min_cd);

how to do Group By between three or more tables in SQL

i have First Table called Header
id | user_id | created_on
------------------------
15 | 42 | 1 day ago
16 | 43 | 1 day ago
17 | 44 | 1 day ago
18 | 45 | 2 day ago
Second Table called Line
line_id | ph_name | quantity
----------------------------
15 | nokia | 3
16 | sumsung | 5
17 | nokia | 1
Third table called User
id | name
--------------
42 | hi
43 | bye
44 | tata
so how relate and group this to give out put like below based on the users count and by considering today date in SQL query or django aggregate ORM
{
{
"ph_name":"nokia",
"num_of_user":"2", //hi and tata are the users
"quantity":"4"
},
{
"ph_name":"samsung",
"num_of_user":"1", //bye is the only user
"quantity":"5"
}
}
Try this:
select ph_name, count(distinct first_table.user_id), sum(quantity)
from second_table
inner join (select * from first_table where created_on = '1 day ago') as first_table on second_table.line_id = first_table.id
inner join third_table on third_table.id = first_table.user_id
group by ph_name
Pay attention, I don't know if your tables are in the same database/schema. You could have to specify the database/schema by prefixing table name by schema/database_name.table_name.

MySql: get MIN record in INNER JOIN If a field is null, or change criteria

I have a query between two tables.
First table is a list of users
+----+-------+-----------+
| id | name | expire_on |
+----+-------+-----------+
| 22 | JOHN | (null) |
| 44 | SMITH | (null) |
| 55 | DOE | 5 |
+----+-------+-----------+
Where "expire_on" can be NULL, but if compiled it is the expire of his subscription, in days.
And I have a list of transactions:
+----+----------------+-----------------+--------------+-------------+----------------------+
| id | id_member_card | amount_original | amount_final | description | utc_date_t |
+----+----------------+-----------------+--------------+-------------+----------------------+
| 1 | 22 | 12 | 12 | (null) | 2017-05-01T10:11:12Z |
| 2 | 22 | 50 | 50 | (null) | 2018-02-01T10:20:30Z |
| 3 | 44 | 7 | 7 | (null) | 2018-02-02T07:50:40Z |
| 4 | 22 | 9 | 9 | (null) | 2018-03-01T10:00:14Z |
| 5 | 44 | 5 | 5 | (null) | 2018-03-03T08:09:10Z |
| 6 | 22 | 0 | 0 | RENEW | 2018-05-02T11:22:33Z |
| 7 | 55 | 12 | 12 | (null) | 2018-05-03T10:20:30Z |
+----+----------------+-----------------+--------------+-------------+----------------------+
I have this starting points:
1) The user "expires" after 365 days of his very first transaction. The id 44 will expire on the 02-02-2019... > BUT >
2) If the user has a field "expire_on", he expires after the X days and not anymore the 365. In my example, id 55 is expired on the 07-05-2018.
3) If in the transaction list there is a RENEW, the user expires 365 days after this transaction renew and not anymore from the first one. Id 22 will expire only on the 02-05-2019 (pratically, we could consider a RENEW transaction as his first_transaction, if this can help to write a smarter query ) > BUT
If the user has the expire_on set, he expires X days after this renew (if the id 22 had expire_on set on, for example, let's say, 10 days, he would be expired on 12-05-2018 and not anymore 02-05-2019).
I hope that I'm clear.
Now MySql query, that I cannot complete considering the RENEW or not.
First of all, this is the link to the fiddle: http://sqlfiddle.com/#!9/16a3a/1
And this is the query:
SELECT member_card.id AS id,
member_card.name,
member_card.expire_on,
ts1.* FROM member_card
INNER JOIN (
SELECT member_card.id,
MIN(transaction.utc_date_t) AS first_transaction,
MAX(transaction.utc_date_t) AS last_transaction,
IFNULL (
DATE(DATE_ADD(MAX(transaction.utc_date_t), INTERVAL expire_on DAY)) ,
DATE(DATE_ADD(MAX(transaction.utc_date_t), INTERVAL 365 DAY))
)
AS final_expire ,
SUM(transaction.amount_final) AS balance
FROM transaction
INNER JOIN member_card ON transaction.id_member_card = member_card.id
GROUP BY member_card.id ) AS ts1 ON member_card.id = ts1.id
WHERE ( final_expire BETWEEN '2019-02-01' AND '2019-02-28' )
GROUP BY member_card.id
With my query, I would expect to find id 44, because his first transaction is made on 2018-02-01, so he will expire on the february 2019. But my query considers only LAST transaction (see MAX aggregate).
So, I need to search and looking for:
If exists a RENEW:
If yes, take this date and sum 365 (OR the custom expire date)
If no, take MIN transaction.
Thank you very much for your support.
Tryng to solve
I could also get the last renew transactions, with another query:
SELECT id_member_card , MAX(utc_date_t) AS last_transaction_renew
FROM transaction
WHERE description IS NOT NULL
GROUP BY id_member_card
and substitute these found id_member_card to the others, using this last_transaction_renew inside that IFNULL, but, how?

An interesting SQL query CHALLENGE - list the AVERAGE value of the top 3 scores of each athlete

An interesting SQL query CHALLENGE:
A table named athelets consisting of id, ath_id, name, score, date.
+----+--------+-----------------+--------+------------+
| id | ath_id | name | record | r_date |
+----+--------+-----------------+--------+------------+
| 1 | 2 | John Wayne | 79 | 2010-07-08 |
| 2 | 7 | Ronald Regan | 51 | 2000-03-22 |
| 3 | 1 | Ford Harrison | 85 | 2009-11-13 |
| 4 | 2 | John Wayne | 69 | 2017-01-01 |
Please write a sql query to list the average value of the top three scores of each athlete, something like:
ath_id: 1, the arithmetic mean of his/her top 3 records: 77
ath_id: 2, the arithmetic mean of his/her top 3 records: 73
ath_id: 3, the arithmetic mean of his/her top 3 records: 47
select ath_id, avg(record)
from
(select ath_id, record
from atheletes as t1
where
(select count(*) from atheletes where t1.ath_id=ath_id and record > t1.record) < 3) as d
group by ath_id;
The above query should works as expected.
Assuming combinations of athletes and records are unique...
SELECT ath_id
, ROUND(AVG(record),2) top3_avg
FROM
( SELECT x.*
FROM athletes x
JOIN athletes y
ON y.ath_id = x.ath_id
AND y.record >= x.record
GROUP
BY x.id
HAVING COUNT(*) <=3
) a
GROUP
BY ath_id;

SQL Group By Date Conflicts

I have a table with columns start_date and end_date. What we need to do is Select everything and group them by date conflicts for each Object_ID.
A date conflict is when a row's start date and/or end date pass through another rows'. For instance, here are some examples of conflicts:
Row 1 has dates 1st through the 5th, Row 2 has dates 2nd through the 3rd.
Row 1 has dates 2nd through the 5th, Row 2 has dates 1st through the 3rd.
Row 1 has dates 2nd through the 5th, Row 2 has dates 3rd through the 6th.
Row 1 has dates 2nd through the 5th, Row 2 has dates 1st through the 7th.
So for example, if we have some sample data (assume the numbers are just days of the month for simplicity):
id | object_id | start_date | end_date
1 | 1 | 1 | 5
2 | 1 | 2 | 4
3 | 1 | 6 | 8
4 | 2 | 2 | 3
What i would expect to see is this:
object_id | start_date | end_date | numconflicts
1 | <na> | <na> | 2
1 | 6 | 8 | 0 or null
2 | 2 | 3 | 0 or null
And for a Second Test Case, Here is some sample data:
id | object_id | start_date | end_date
1 | 1 | 1 | 5
2 | 1 | 2 | 4
3 | 1 | 6 | 8
4 | 2 | 2 | 3
5 | 2 | 4 | 5
6 | 1 | 2 | 3
7 | 1 | 10 | 12
8 | 1 | 11 | 13
And for the second Test Case, what I would expect to see as output:
object_id | start_date | end_date | numconflicts
1 | <na> | <na> | 3
1 | 6 | 8 | 0 or null
2 | 2 | 3 | 0 or null
2 | 4 | 5 | 0 or null
1 | <na> | <na> | 2
Yes, I will need some way of differentiating the first and the second grouping (the first and last rows) but I haven't quite figured that out. The goal is to view this list, and then when you click on a group of conflicts you can view all of the conflicts in that group.
My first thought was to attempt some GROUP BY CASE ... clause but I just wrapped by head around itself.
The language I am using to call mysql is php. So if someone knows of a php-loop solution rather than a large mysql query i am all ears.
Thanks in advance.
Edit: Added in primary Keys to provide a little less confusion.
Edit: Added in a Test case 2 to provide some more reasoning.
This query finds the number of duplicates:
select od1.object_id, od1.start_date, od1.end_date, sum(od2.id is not null) as dups
from object_date od1
left join object_date od2
on od2.object_id = od1.object_id
and od2.end_date >= od1.start_date
and od2.start_date <= od1.end_date
and od2.id != od1.id
group by 1,2,3;
You can use this query as the basis of a query that gives you exactly what you asked for (see below for output).
select
object_id,
case dups when 0 then start_date else '<na>' end as start_date,
case dups when 0 then end_date else '<na>' end as end_date,
sum(dups) as dups
from (
select od1.object_id, od1.start_date, od1.end_date, sum(od2.id is not null) as dups
from object_date od1
left join object_date od2
on od2.object_id = od1.object_id
and od2.end_date >= od1.start_date
and od2.start_date <= od1.end_date
and od2.id != od1.id
group by 1,2,3) x
group by 1,2,3;
Note that I have used an id column to distinguish the rows. However, you could replace the test of id's not matching with comparisons on every column, ie replace od2.id != od1.id with tests that every other column is not equal, but that would require a unique index on all the other columns to make sense, and having an id column is a good idea anyway.
Here's a test using your data:
create table object_date (
id int primary key auto_increment,
object_id int,
start_date int,
end_date int
);
insert into object_date (object_id, start_date, end_date)
values (1,1,5),(1,2,4),(1,6,8),(2,2,3);
Output of first query when run against this sample data:
+-----------+------------+----------+------+
| object_id | start_date | end_date | dups |
+-----------+------------+----------+------+
| 1 | 1 | 5 | 1 |
| 1 | 2 | 4 | 1 |
| 1 | 6 | 8 | 0 |
| 2 | 2 | 3 | 0 |
+-----------+------------+----------+------+
Output of second query when run against this sample data:
+-----------+------------+----------+------+
| object_id | start_date | end_date | dups |
+-----------+------------+----------+------+
| 1 | 6 | 8 | 0 |
| 1 | <na> | <na> | 2 |
| 2 | 2 | 3 | 0 |
+-----------+------------+----------+------+
Oracle : This could be done with a subquery in a group by CASE statement.
https://forums.oracle.com/forums/thread.jspa?threadID=2131172
Mysql : You could have a view which had all the conflicts .
select distinct a1.appt, a2.appt from appointment a1, appointment a2 where a1.start < a2.end and a1.end > a2.start.
and then simply do a count(*) on that table.
Something like the following should work:
select T1.object_id, T1.start_date, T1.end_date, count(T1.object_id) as numconflicts
from T1
inner join T2 on T1.start_date between T2.start_date and T2.end_date
inner join T3 on T1.end_date between T2.start_date and T2.end_date
group by T1.object_id
I might be off a little bit, but it should help you get started.
Edit: Indented it properly