MySQL get records beetween tables with conditions - mysql

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!

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

Request with two different dates ranges

I've a little issue with MySQL for making a request with two different dates ranges.
I need to have nb_sales and last_sales until 2014 but frequence only for the past year.
The result I want :
customer_id | nb_sales | last_sales | frequence
---------------------------------------------------------------
Customer ID | Sales make by | How many days| How many sales
| the customer | since the | has been made
| | last sales? | this year?
Column 1-3 are in the first date range : today to 2014
Column 4 is in a seconde date range : today to y-1
So I tried to :
Create a temporary table and insert frequence
SELECT customer_id, nb_sales, last_sales and frequence with LEFT OUTER JOIN
The first step is ok but for the second one I don't have any result or error message... And this happened when I wanted to LEFT OUTER JOIN my temporary table:
LEFT OUTER JOIN tmp_frequence
ON tmp_frequence.client_id = sales_flat_order.customer_id
Maybe you have a better idea?
CREATE TEMPORARY TABLE tmp_frequence (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
client_id INT,
frequence INT,
PRIMARY KEY (id)
);
INSERT INTO tmp_frequence (client_id, frequence)
SELECT sales_flat_order.customer_id, COUNT(sales_flat_order.entity_id)
FROM sales_flat_order
WHERE sales_flat_order.created_at BETWEEN '2014-05-22 00:00:00' and '2017-07-31 23:59:59'
GROUP BY sales_flat_order.customer_id;
/* ------------------------------ */
SELECT
-- * ,
sales_flat_order.customer_id customer_id,
COUNT(sales_flat_order.entity_id) nb_sales,
DATEDIFF("2017-07-31",DATE_FORMAT(MAX(sales_flat_order_item.created_at),"%Y-%m-%d")) last_sales,
tmp_frequence.frequence frequence
FROM adl_ec.sales_flat_order_item
LEFT OUTER JOIN sales_flat_order
ON sales_flat_order.entity_id = sales_flat_order_item.order_id
LEFT OUTER JOIN tmp_frequence
ON tmp_frequence.client_id=sales_flat_order.customer_id
WHERE sales_flat_order_item.created_at BETWEEN '2014-05-22 00:00:00' and '2017-07-31 23:59:59'
GROUP BY customer_id;
DROP TABLE tmp_frequence ;
I've Finally found a solution.
Thank you really for your help :)
DROP TABLE IF EXISTS tmp_frequence;
CREATE TEMPORARY TABLE tmp_frequence (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
client_id INT,
recence INT,
frequence INT,
montant INT,
PRIMARY KEY (id)
);
INSERT INTO tmp_frequence (client_id, frequence)
SELECT sales_flat_order.customer_id, COUNT(sales_flat_order.entity_id)
FROM sales_flat_order
WHERE sales_flat_order.created_at BETWEEN '2016-07-31 00:00:00' and '2017-07-31 23:59:59'
GROUP BY sales_flat_order.customer_id;
INSERT INTO tmp_frequence (client_id, recence, montant)
SELECT
sales_flat_order.customer_id,
DATEDIFF("2017-07-31",DATE_FORMAT(MAX(sales_flat_order_item.created_at),"%Y-%m-%d")) recence,
COUNT(sales_flat_order.grand_total) montant
FROM adl_ec.sales_flat_order_item
LEFT OUTER JOIN sales_flat_order ON sales_flat_order.entity_id = sales_flat_order_item.order_id
AND sales_flat_order_item.created_at BETWEEN '2014-05-22 00:00:00' and '2017-07-31 23:59:59'
AND qty_invoiced >0
AND sales_flat_order_item.sku NOT LIKE '%abo%'
AND sales_flat_order.total_qty_ordered < 5
GROUP BY customer_id;
SELECT tmp_frequence.client_id,MAX(tmp_frequence.recence) Recence,MAX(tmp_frequence.frequence) Frequence,MAX(tmp_frequence.montant) Montant
FROM tmp_frequence
GROUP BY tmp_frequence.client_id;

MySQL individual counts, from multiple tables, grouped by date

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

Fill missing dates in mysql query range

I have the following query:
select date(updated_at) as data, COUNT(id) as numar
from `coupons`
where `user_id` = 5 and `won_by` != 0 and `updated_at` >= '2016-04-01'
group by DAY(updated_at), month(updated_at), year(updated_at)
and the result is this:
2016-04-01- 229
2016-04-03- 30
2016-04-04- 6
2016-04-07- 1
2016-04-08- 1
2016-04-10- 1
What can I do to receive something like this:
2016-04-01- 229
2016-04-02- 0
2016-04-03- 30
2016-04-04- 6
2016-04-05- 0
2016-04-06- 0
2016-04-07- 1
2016-04-08- 1
2016-04-10- 1
The best way that I've found to do this is to simply create (and maintain) a secondary table with a single column, containing all of the dates that you care about. Something like:
CREATE TABLE date_join (
date date not null primary key
);
Then insert records for each date in whatever way is convenient (by hand, if it's a one-off, as part of your daily process, via stored procedure, etc).
At that point, it's simply a left join of date_join and your initial query, with a CASE statement to translate NULLs to 0s:
SELECT dj.date, q.numar
FROM date_join dj
LEFT JOIN (select date(updated_at) as date, COUNT(id) as numar
from `coupons`
where `user_id` = 5 and `won_by` != 0 and `updated_at` >= '2016-04-01'
group by DATE(updated_at)
) q
ON dj.date = q.date
ORDER BY dj.date;

Getting the missing dates from a sql table

I have a mysql table with the rows: ID, name, startDate, endDate.
As a rule, the dates should be consecutive and i want to alert the user if an interval is missing.
Saying i have this dates inserted:
2012-03-25 -> 2012-03-29
2012-04-02 -> 2012-04-05
I wanna show a message like
"No dates found from 2012-03-29 to 2012-04-02. Please insert data for this interval"
Can this be done without surfing with php the entire table entries?
Thanks!
SELECT t1.endDate AS gapStart, (SELECT MIN(t3.startDate) FROM `table` t3 WHERE t3.startDate > t1.endDate) AS gapEnd
FROM `table` t1
LEFT JOIN `table` t2
ON t1.endDate = t2.startDate
WHERE t2.startDate IS NULL
This works. I include code for creating table and inserting data for testing purposes.
create table dates(
id int(11) not null auto_increment,
name varchar(16) not null,
startDate date,
endDate date,
primary key(id)
);
insert into dates (name,startDate,endDate)
values('personA', '2012-03-25', '2012-03-29'),
('PersonB','2012-04-02', '2012-04-05');
So here is the query:
select d1.endDate,d2.startDate
from dates d1, dates d2
where (d1.id+1) =d2.id and d1.endDate < d2.startDate;
Yes, it can be done without surfing the whole table using PHP, instead you have to surf the whole table using mysql. A crude solution would be:
SELECT a.enddate AS start_of_gap,
(SELECT MIN(c.startdate)
FROM yourtable c
WHERE c.startdate>a.enddate) AS end_of_gap
FROM yourtable a
WHERE NOT EXISTS (
SELECT 1
FROM yourtable b
WHERE b.startdate=a.enddate + INTERVAL 1 DAY
);
I expect if I thought about it some more, there will be a more efficient (but likely less obvious) method.