MySQL individual counts, from multiple tables, grouped by date - mysql

I have multiple MySQL tables, within one database, which each record transactions performed. Each table has a DATETIME field to record when each transaction occurred.
How do I construct a MySQL query, or procedure, to produce the count of transactions from each table, grouped by each date?
I want to see each days total from the start of the current month up to, and including, the current day.
In each table a row represents one transaction. So the columns are just the count of rows in each table, for each date.
E.g running such query, Today, would yield a table like;
26/04/2016 Total A Transactions, Total B Transactions, ...
25/04/2016 Total A Transactions, Total B Transactions, ...
....
01/04/2016 Total A Transactions, Total B Transactions, ...
Sample schema;
CREATE TABLE tableA (
uuid BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
creationTime DATETIME NOT NULL, ..
CREATE TABLE tableB (
uuid BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
creationTime DATETIME NOT NULL, ..
...

select date(my_date), count(*), 'tableA' fromc tableA
where month(my_date) = month(curdate())
AND year(my_date) = Year(curdate())
group by date(my_date)
union
select date(my_date), count(*), 'tableB' fromc tableB
where month(my_date) = month(curdate())
AND year(my_date) = Year(curdate())
group by date(my_date)

I assume that tableA has at least one transaction a day.
select create_time, total_a_transaction, total_b_transaction, ...
from
(select date(creationTime) create_time, count(*) total_a_transaction
from tableA
where year(creationTime) = year(curdate())
and month(creationTime) = month(curdate())
group by date(creationTime)) transaction_a
left join
(select date(creationTime) create_time, count(*) total_b_transaction
from tableB
where year(creationTime) = year(curdate())
and month(creationTime) = month(curdate())
group by date(creationTime)) transaction_b
using (create_time)
left join ...

Related

Adding null values for records not existing in joined table

I have a table, called top_trends which has the following schema:
id int(11) AI PK
criteria_id int(11)
value varchar(255)
created_at timestamp
updated_at timestamp
Then I have another table, called search_criterias which has the following schema:
id int(11) AI PK
title varchar(255)
created_at timestamp
updated_at timestamp
Here is a query which provides the value based on maximum number of records by a value. So, if theres 2 records in top_trends with both having criteria_id as 1 and both have values of 3 in the top_trends.value column, and then a separate SINGLE record with the same criteria_id 1 but a value of 2, the query will produce a result of the selected criteria (being 1) having a value of 3 since the value 3 occurred more times than any other rows of values with criteria_id 1. So, in simple terms, the query chose the value 3 for criteria id 1 because that occurred the most amount of times based on the records in the top_trends having criteria_id 1
select
x.value as `values`
, sc.id as id
, sc.title
, sc.created_at
, sc.updated_at
, x.criteria_id as search_category_id
from
(
select
criteria_id
, `value`
from
top_trends
group by
`criteria_id`
order by
`value`
) x
left join search_criterias sc
on sc.id = x.criteria_id
group by
criteria_id
My issue is that unfortunately right now we dont have data for all possible search_criterias so some of the records in the search_criteria table are not being aggregated in my query.
For example, we have a search_criteria record of city, with an id of 5, but no records in the top_trends table with having a criteria_id of 5... so the query above is not including that search_criteria.
What I'd like to do is include those records in the search_criteria table that are not in the top_trends table but have the values attribute as null
LEFT JOIN is used when there are rows in the left table that have no match in the right table. Since the missing rows in your case are in top_trends, that should be the right table of the LEFT JOIN. So switch the order of the join.
select
x.value as `values`
, sc.id as id
, sc.title
, sc.created_at
, sc.updated_at
, x.criteria_id as search_category_id
from search_criterias sc
left join
(
select
criteria_id
, `value`
from
top_trends
group by
`criteria_id`
order by
`value`
) x
on sc.id = x.criteria_id
group bysc.id

SQL Find date range gaps in Table

Good day.
I seem to be struggling with what seems like a simple problem.
I have a table that has a value connected to a date (Monthly) for a finite number of ID's
ie. Table1
ID | Date ---| Value
01 | 2015-01 | val1
01 | 2015-02 | val2
02 | 2015-01 | val1
02 | 2015-03 | val2
So ID: 02 does not have a value for date 2015-02.
I would like to return all ID's and Dates that do not have a value.
Date range is: select distinct date from Table1
I can't seem to think outside the realms of selecting and joining on the same table.
I need to include the ID in my select to I can somehow select the ID and Date range that exists for that ID and compare to the entire date range, to get all the dates for each ID that isn't in the "entire" date range.
Please advise.
Thank you
Not very clear about your last two sentences. But you can play with the following query with different #max_days and #min_date:
-- DROP TABLE table1;
CREATE TABLE table1(ID int not null, `date` date not null, value varchar(64) not null);
INSERT table1(ID,`date`,value)
VALUES (1,'2015-01-01','v1'),(1,'2015-01-02','v2'),(2,'2015-01-01','v1'),(2,'2015-01-03','v2'),(4,'2015-01-01','v1'),(4,'2015-01-04','v2');
SELECT * FROM table1;
SET #day=0;
SET #max_days=5;
SET #min_date='2015-01-01';
SELECT i.ID,d.`date`
FROM (SELECT DISTINCT ID FROM table1) i
CROSS JOIN (
SELECT TIMESTAMPADD(DAY,#day,#min_date) AS `date`,#day:=#day+1 AS day_num
FROM table1 WHERE #day<#max_days) d
LEFT JOIN table1 t
ON t.ID=i.ID
AND t.`date`=d.`date`
WHERE t.`date` IS NULL
ORDER BY i.ID,d.`date`;
I now understand your requirement of dates being taken from the table; you want to find any gaps in the date ranges for each id.
This does what you need, but can probably be improved. Explanation below and you can view a working example.
DROP TABLE IF EXISTS Table1;
DROP TABLE IF EXISTS Year_Month_Calendar;
CREATE TABLE Table1 (
id INTEGER
,date CHAR(7)
,value CHAR(4)
);
INSERT INTO Table1
VALUES
(1,'2015-01','val1')
,(1,'2015-02','val2')
,(2,'2015-01','val1')
,(2,'2015-03','val1');
CREATE TABLE Year_Month_Calendar (
date CHAR(10)
);
INSERT INTO Year_Month_Calendar
VALUES
('2015-01')
,('2015-02')
,('2015-03');
SELECT ID_Year_Month.id, ID_Year_Month.date, Table1.id, Table1.date
FROM (
SELECT Distinct_ID.id, Year_Month_Calendar.date
FROM Year_Month_Calendar
CROSS JOIN
( SELECT DISTINCT id FROM Table1 ) AS Distinct_ID
WHERE Year_Month_Calendar.date >= (SELECT MIN(date) FROM Table1 WHERE id=Distinct_ID.ID)
AND Year_Month_Calendar.date <= (SELECT MAX(date) FROM Table1 WHERE id=Distinct_ID.ID)
) AS ID_Year_Month
LEFT JOIN Table1
ON ID_Year_Month.id = Table1.id AND ID_Year_Month.date = Table1.date
-- WHERE Table1.id IS NULL
ORDER BY ID_Year_Month.id, ID_Year_Month.date
Explanation
You need a calendar table which contains all dates (year/months) to cover the data you are querying.
CREATE TABLE Year_Month_Calendar (
date CHAR(10)
);
INSERT INTO Year_Month_Calendar
VALUES
('2015-01')
,('2015-02')
,('2015-03');
The inner select creates a table with all dates between the min and max date for each id.
SELECT Distinct_ID.id, Year_Month_Calendar.date
FROM Year_Month_Calendar
CROSS JOIN
( SELECT DISTINCT id FROM Table1 ) AS Distinct_ID
WHERE Year_Month_Calendar.date >= (SELECT MIN(date) FROM Table1 WHERE id=Distinct_ID.ID)
AND Year_Month_Calendar.date <= (SELECT MAX(date) FROM Table1 WHERE id=Distinct_ID.ID)
This is then LEFT JOINED to the original table to find the missing rows.
If you only want to return the missing row (my query displays the whole table to show how it works), add a WHERE clause to restrict the output to those rows where an id and date is not returned from Table1
Original answer before comments
You can do this without a tally table, since you say
Date range is: select distinct date from Table1
I've slightly changed the field names to avoid reserved words in SQL.
SELECT id_table.ID, date_table.`year_month`, table1.val
FROM (SELECT DISTINCT ID FROM table1) AS id_table
CROSS JOIN
(SELECT DISTINCT `year_month` FROM table1) AS date_table
LEFT JOIN table1
ON table1.ID=id_table.ID AND table1.`year_month` = date_table.`year_month`
ORDER BY id_table.ID
I've not filtered the results, in order to show how the query is working. To return the rows where only where a date is missing, add WHERE table1.year_month IS NULL to the outer query.
SQL Fiddle
You will need a tally table(s) or month/year tables. So you can then generate all of the potential combinations you want to test with. As far as exactly how to use it your example could use some expanding on such as last 12 months, last3 months, etc. but here is an example that might help you understand what you are looking for:
http://rextester.com/ZDQS5259
CREATE TABLE IF NOT EXISTS Tbl (
ID INTEGER
,Date VARCHAR(10)
,Value VARCHAR(10)
);
INSERT INTO Tbl VALUES
(1,'2015-01','val1')
,(1,'2015-02','val2')
,(2,'2015-01','val1')
,(2,'2015-03','val1');
SELECT yr.YearNumber, mn.MonthNumber, i.Id
FROM
(
SELECT 2016 as YearNumber
UNION SELECT 2015
) yr
CROSS JOIN (
SELECT 1 MonthNumber
UNION SELECT 2
UNION SELECT 3
UNION SELECT 4
UNION SELECT 5
UNION SELECT 6
UNION SELECT 7
UNION SELECT 8
UNION SELECT 9
UNION SELECT 10
UNION SELECT 11
UNION SELECT 12
) mn
CROSS JOIN (
SELECT DISTINCT ID
FROM
Tbl
) i
LEFT JOIN Tbl t
ON yr.YearNumber = CAST(LEFT(t.Date,4) as UNSIGNED)
AND mn.MonthNumber = CAST(RIGHT(t.Date,2) AS UNSIGNED)
AND i.ID = t.ID
WHERE
t.ID IS NULL
The basic idea to determine what you don't know is to generate all possible combinations of something could be. E.g. Year X Month X DISTINCT Id and then join back to figure out what is missing.
Probably not the prettiest but this should work.
select distinct c.ID, c.Date, d.Value
from (select a.ID, b.Date
from (select distinct ID from Table1) as a, (select distinct Date from Table1) as b) as c
left outer join Table1 d on (c.ID = d.ID and c.Date = d.Date)
where d.Value is NULL

MySQL get records beetween tables with conditions

I've got a big problem in my hands, I have the following SQL structure, where the contracts tables are dinamically generated, with random names, like _xyz, _xxx, etc:
CREATE TABLE contract_xyz(
id INT(11) PRIMARY KEY NOT NULL AUTO_INCREMENT,
created_at DATETIME NOT NULL
);
CREATE TABLE contract_events(
id INT(11) PRIMARY KEY NOT NULL AUTO_INCREMENT,
id_contract INT(11) NOT NULL,
table_contract VARCHAR(255) NOT NULL,
created_at DATETIME NOT NULL
);
INSERT INTO contract_xyz (id,created_at) VALUES (1,'2016-11-01');
INSERT INTO contract_xyz (id,created_at) VALUES (2,'2016-10-21');
INSERT INTO contract_xyz (id,created_at) VALUES (3,'2016-11-04');
INSERT INTO contract_events(id,id_contract,table_contract,created_at) VALUES (1,1,'contract_xyz','2016-11-03');
INSERT INTO contract_events(id,id_contract,table_contract,created_at) VALUES (2,3,'contract_xyz','2016-11-04');
Each contract can have his own events. I need to solve the following issue:
Get all contracts that don't have new events in 2 days, or don't have any event at all, and was created over 2 days ago.
I've tried with LET JOIN but it wasn't the correct result. The nearest I get was the following query:
SELECT `contract_xyz`.*
FROM `contract_xyz`
WHERE EXISTS(SELECT 1
FROM `contract_events`
WHERE
`contract_events`.id_contract = `contract_xyz`.id AND `contract_events`.table_contract = 'contract_xyz'
AND DATEDIFF(CURDATE(), `contract_events`.created_at) >= 2
ORDER BY `contract_events`.created_at DESC
LIMIT 1)
OR (NOT EXISTS(SELECT 1
FROM `contract_events`
WHERE `contract_events`.id_contract = `contract_xyz`.id AND
`contract_events`.table_contract = 'contract_xyz') AND
DATEDIFF(CURDATE(), `contract_xyz`.created_at) >= 2);
But I still can't find the contracts that doesn't have any events, and was created over 2 days ago.
I would create a subquery with the max event date for each contract. I would left join the contracts table on this subquery. You can filter based on the max event date and the created date fields to achieve the expected outcome:
select c.*
from contract_xyz c
left join
(select id_contract,
max(created_at) max_event_date
from contract_events
group by id_contract) t on c.id-t.id_contract
where
DATEDIFF(CURDATE(), t.max_event_date) >= 2
or (t.max_event_date is null and DATEDIFF(CURDATE(), c.created_at) >= 2)
Alternatively, you do not use a subquery, but join the 2 tables directly with group by and do the filtering in the having clause.
LEFT OUTER JOIN with an ON condition could help here:
select c.id, c.created_at,count(e.id) as contract_events_less_than_2_days_old
from contract_xyz c
left outer join contract_events e on e.id_contract = c.id
and e.table_contract = 'contract_xyz'
and e.created_at > now() - interval 2 day
where c.created_at < now() - interval 2 day
and e.id is null
group by c.id, c.created_at;
If you have any control over it I would advise against dynamically-generated table names!

Join Tables Based on Correlated Subquery, SQL

I need to join two tables that are described below:
Table1:
ID Date Info1
1 1/29/2011 i10
1 1/30/2011 i11
Table2:
ID Date Info2
1 1/31/2011 i2
I would like to left join the records in Table 2 identified by ID, Month, Year to that in Table 1 identified by the same ID, Month, Year but use the last available record date as the joining record. So for example, in the data above I would join the record in Table 2 to the second record in Table 1 because they match in ID, Month, Year and record 2 of Table 1 has the greatest available day for that (ID, Month, Year) combination. The correct result is:
ID Date Info1 Info2
1 1/30/2011 i11 i2
The SQL code I am coming up with so far is pretty convoluted. Please suggest something. I am using MySQL.
[I want to] ...use the last available record date as the joining record
Solve that first, with a derived table. Assuming that ID, Date is unique, then you can easily group by ID and take the MAX date.
SELECT
T1.*,
T2.*
FROM Table1 as T1
JOIN (
SELECT
ID, MAX(Date) as Date
FROM Table1
GROUP BY
ID
) as Last ON
T1.ID = Last.ID
AND T1.Date = Last.Date
LEFT OUTER JOIN Table2 as T2 ON
T1.ID = Last.ID
AND MONTH(T1.Date) = MONTH(T2.Date)
AND YEAR(T1.Date) = YEAR(T2.Date)

SELECT newest record of any GROUP of records (ignoring records with one record)

Having trouble with a query to return the newest order of any grouped set of orders having more than 1 order. CREATE & INSERTs for the test data are below.
This query returns the unique customer id's I want to work with, along with the grouped order_id's. Of these records, I only need the most recent order (based on date_added).
SELECT COUNT(customer_id), customer_id, GROUP_CONCAT(order_id) FROM orderTable GROUP BY customer_id HAVING COUNT(customer_id)>1 LIMIT 10;
mysql> SELECT COUNT(customer_id), customer_id, GROUP_CONCAT(order_id) FROM orderTable GROUP BY customer_id HAVING COUNT(customer_id)>1 LIMIT 10;
+--------------------+-------------+------------------------+
| COUNT(customer_id) | customer_id | GROUP_CONCAT(order_id) |
+--------------------+-------------+------------------------+
| 2 | 0487 | F9,Z33 |
| 3 | 1234 | 3A,5A,88B |
+--------------------+-------------+------------------------+
2 rows in set (0.00 sec)
I'm looking for order Z33 (customer_id 0487) and 3A (customer_id 1234).
For clarification, I do not want orders for customers that have only ordered once.
Any help or tips to get me pointed in the right direction appreciated.
Sample table data:
--
-- Table structure for table orderTable
CREATE TABLE IF NOT EXISTS orderTable (
customer_id varchar(10) NOT NULL,
order_id varchar(4) NOT NULL,
date_added date NOT NULL,
PRIMARY KEY (customer_id,order_id)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Dumping data for table orderTable
INSERT INTO orderTable (customer_id, order_id, date_added) VALUES
('1234', '5A', '1997-01-22'),
('1234', '88B', '1992-05-09'),
('0487', 'F9', '2002-01-23'),
('5799', 'A12F', '2007-01-23'),
('1234', '3A', '2009-01-22'),
('3333', '7FHS', '2009-01-22'),
('0487', 'Z33', '2004-06-23');
==========================================================
Clarification of the query.
The question was to only include those customers that had more... hence my query has it INSIDE with the GROUP BY... This way it ONLY GIVES the customer in question that HAD multiple orders, but at the same time, only gives the most recent date OF the last order for the person... Then the PreQuery is re-joined to the orders table by the common customer ID, but only for the order that matches the last date as detected in the prequery. If a customer only had a single order, its inner PreQuery count would have only been 1 and thus excluded from the final PreQuery result set.
select ot.*
from
( select
customer_id,
max( date_added ) as LastOrderDate,
from
orderTable
having
count(*) > 1
group by
customer_id ) PreQuery
join orderTable ot
on PreQuery.Customer_ID = ot.Customer_ID
and PreQuery.LastOrderDate = ot.date_added