Select customers with no invoices after date - mysql

I have Client and Invoice tables. They have one-to-many relationship, where Client.id = Invoice.client_id.
Client columns:
id
Invoice columns:
id,
client_id,
invoice_date
Of course the example is simplified to relevant data.
I am trying to select customers who did NOT have invoices after '2010-01-01'.
I can't figure out any working way to do this. Some routes I took look like this (there many other variations, but no point displaying the here):
SELECT c.id, COUNT(i.invoice_date > "2010-01-01") AS cnt
FROM Client AS c LEFT JOIN Invoice i ON i.client_id = c.id
GROUP BY c.id HAVING cnt = 0
and
SELECT client_id, COUNT(invoice_date > '2010-01-01') as cnt
FROM Invoice
GROUP BY client_id HAVING cnt = 0

You can use a sub-query with NOT EXISTS like this:
SELECT *
FROM Client
WHERE NOT EXISTS (
SELECT 1
FROM Invoice
WHERE Invoice.invoice_date > '2010-01-01' AND Invoice.client_id = Client.id
)
You can also use SUM with CASE or IF:
-- CASE
SELECT c.id, SUM(CASE WHEN i.invoice_date > '2010-01-01' THEN 1 ELSE 0 END) AS cnt
FROM Client AS c LEFT JOIN Invoice i ON i.client_id = c.id
GROUP BY c.id
HAVING cnt = 0
-- IF
SELECT c.id, SUM(IF(i.invoice_date > '2010-01-01', 1, 0)) AS cnt
FROM Client AS c LEFT JOIN Invoice i ON i.client_id = c.id
GROUP BY c.id
HAVING cnt = 0
You can also use COUNT, but with CASE or IF:
-- CASE
SELECT client_id, COUNT(CASE WHEN invoice_date > '2010-01-01' THEN 1 ELSE NULL END) as cnt
FROM Invoice
GROUP BY client_id HAVING cnt = 0
-- IF
SELECT client_id, COUNT(IF(invoice_date > '2010-01-01', 1, NULL)) as cnt
FROM Invoice
GROUP BY client_id HAVING cnt = 0
demo on dbfiddle.uk

You can also use NOT IN with a sub-query
SELECT * FROM client
WHERE id NOT IN (
SELECT client_id
FROM invoice
WHERE invoice_date>'2020-01-01'
);

Try this :-
Select Client.* from Client Client left join Invoice Invoice on
client.id = Invoice.client_id and Invoice.invoice_date > "2010-01-01"
where Invoice.client_id is null;
Basically you include only the subset of the data after "2010-01-01" from the invoice table.

Related

I need to get specific ids from db if these are in current and last quarter using SQL

[DB Table]
SELECT b.first_name, b.last_name, a.pod_name, a.category, c.user_id,
SUM(IF(QUARTER(CURDATE())-1 OR (QUARTER(CURDATE())-2) AND a.user_id, 1, 0)) AS flag FROM kudos a
INNER JOIN users b ON a.user_id = b.id INNER JOIN users_groups c ON a.user_id = c.user_id
INNER JOIN groups d ON c.group_id = d.id WHERE a.group_name = 'G2' AND d.id IN (7,8,9,11,12,13,14,15,16,17,21,22,23,24,25,26,27,28)
AND QUARTER(CURDATE())-1 = a.quarter ORDER BY a.final_score+0 DESC
I need to get the user_ids of those users which are both in quarter 1 and 2 from table.
Tried above query but failed to get expected results.
Can someone please guide me on this?
if you only need user_id then you can do this :
select user_id
from tablename
where quarter in (1,2)
group by user_id
having count(distinct quarter) = 2
another way is to use window function, assuming you have one user id in each quarter:
select * from (
select * , count(*) over (partition by user_id) cn
from tablename
where quarter in (1,2)
) t where cn = 2

Use main query condition on sub query

I have a table "products" and a table "links". every product can have multiple links, each link can have many sales, clicks and impressions, but doesn't necessarily have all of them. I want to get a list of links of a certain product matching some criteria for them. I want to get this data grouped per day and campaign and link banner size.
The following query works correctly, but it's much slower than it could be. The problem is that the subqueries get the data for all link ids and it's just filtered in the end. The overall query would become much faster if the sub queries included something like
where product_id IN (...) but I only know the link_ids from the main query, not before
if I try to add
where link_id = l.id
it's obviously an unknown column, because the sub query doesn't have access to the main queries results.
how can I let the sub queries only look up data for the link_Ids that the main query found? I could split it up to 2 complete separate queries, but is this possible in one query?
select p.id, p.name, l.id, l.banner_size,
coalesce(sum(case when t1.col = 'sales' then ct else 0 end), 0) as total_sales,
coalesce(sum(case when t1.col = 'clicks' then ct else 0 end), 0) as total_clicks,
coalesce(sum(case when t1.col = 'impressions' then ct else 0 end), 0) as total_impressions,
t1.dt
from links l
inner join products p
on p.id = l.product_id
left join
(
select count(1) as ct, link_id, date(clicked) dt, 'sales' as col
from sales
where clicked >= '2020-01-01 00:00:00' and clicked <= '2020-01-31 00:00:00'
group by date(clicked), link_id
union all
select count(1) as ct, link_id, date(created) dt, 'clicks'
from clicks_source1
where created >= '2020-01-01 00:00:00' and created <= '2020-01-31 00:00:00'
group by date(created), link_id
union all
select count(1) as ct, link_id, date(time) dt, 'clicks'
from clicks_source2
where time >= '2020-01-01 00:00:00' and time <= '2020-01-31 00:00:00'
group by date(time), link_id
union all
select count(1) as ct, link_id, date(created) dt, 'impressions'
from impression_source1
where created > '2020-01-01 00:00:00' and created <= '2020-01-31 00:00:00'
group by date(created), link_id
union all
select count(1) as ct, link_id, date(time) dt, 'impressions'
from impression_source2
where time > '2020-01-01 00:00:00' and time <= '2020-01-31 00:00:00'
group by date(time), link_id
) t1 on t1.link_id = l.id
where l.agent_id = 300
and p.id = 3454
and l.banner_size = 48
and p.company NOT IN (13592, 28189)
group by c.id, l.banner_size, t1.dt
having (total_sales + total_clicks + total_impressions) > 0
order by dt DESC, p.id ASC, l.banner_size ASC
What you'd like to use is called lateral joins, but MySQL doesn't feature them.
One solution is to move the subqueries for the counts to the select clause:
select
id,
name,
coalesce((select count(*) from views v where v.product_id = p.product_id), 0)
as total_views,
coalesce((select count(*) from clicks c where c.product_id = p.product_id), 0)
as total_clicks
from products
where status = 1;
It is unnecessary that you combined views and clicks in one subquery. Maybe it's just this, that kept the optimizer from getting a better execution plan. You can try the following and check whether it's already much faster than your original query.
select
p.id,
p.name,
coalesce(v.total, 0) as total_views
coalesce(c.total, 0) as total_clicks
from products
left join (select product_id, count(*) as total from views group by product_id) v
on v.product_id = p.product_id
left join (select product_id, count(*) as total from clicks group by product_id) c
on c.product_id = p.product_id
where p.status = 1;
You say that your example is much simplified. Maybe you can just apply the condition early by repeating it. E.g.:
select p.id, p.name,
coalesce(sum(case when t1.col = 'views' then ct else 0 end), 0) as total_views,
coalesce(sum(case when t1.col = 'clicks' then ct else 0 end), 0) as total_clicks
from products p
left join
(
select count(1) as ct, product_id, 'views' as col
from views
where product_id in (select product_id from products where status = 1)
group by product_id
union all
select count(1) as ct, product_id, 'clicks' as col
from clicks
where product_id in (select product_id from products where status = 1)
group by product id
) t1 on t1.product_id = p.product_id
where p.status = 1;
Or with a WITH clause:
with p as (select * from products where status = 1)
select p.id, p.name,
coalesce(sum(case when t1.col = 'views' then ct else 0 end), 0) as total_views,
coalesce(sum(case when t1.col = 'clicks' then ct else 0 end), 0) as total_clicks
from p
left join
(
select count(1) as ct, product_id, 'views' as col
from views
where product_id in (select product_id from p)
group by product_id
union all
select count(1) as ct, product_id, 'clicks' as col
from clicks
where product_id in (select product_id from p)
group by product id
) t1 on t1.product_id = p.product_id
;

MySQL - Arithmetic operations with two relations

How do I get the total amount of money paid by each customer minus the amount collected (em_paid_to)?
table customer
cust_id INT
f_name VARCHAR
l_name VARCHAR
email VARCHAR
c_limit INT
table transaction
id INT
em_paid_by VARCHAR
em_paid_to VARCHAR
amount INT
trans_date DATE
I already tried this to get the total paid by each customer but it did not work:
SELECT C.F_NAME, C.L_NAME, COUNT(T.EM_PAID_BY), SUM(T.AMOUNT)
FROM CUSTOMER C
JOIN TRANSACTION T ON C.EMAIL = T.EM_PAID_BY;
...and this to get the total collected by each customer, still the same error and I need to get the difference between the two results.
SELECT C.F_NAME, C.L_NAME, COUNT(T.EM_PAID_TO), SUM(T.AMOUNT)
FROM CUSTOMER C
JOIN TRANSACTION T ON C.EMAIL = T.EM_PAID_TO;
What I am hoping to get is like this Old McDonald oldmcdonald#gmail.com 2000
i.e (2000 + 4000 + 1000) - (2000 + 3000) = 2000
Your existing queries generate errors because they use aggregate functions (SUM(), COUNT()) without a GROUP BY clause to list all non-aggregated columns.
To solve your requirement, one solution would be to use conditional aggregation:
recover all transactions where the customer's email appears in the em_paid_to or in the em_paid_by clause
group by customer (a sensible option is to add the customer id to the GROUP BY clause, eventhough it is not part of the results)
do conditional counts and sums, depending on whether the records was matched on em_paid_to or em_paid_by
The following query gives you detailed information (count of payments by and to, amounts paid by and to, and balance), you can pick what's relevant for you:
SELECT
c.f_name,
c.l_name,
SUM(c.email = t.em_paid_by) count_paid_by,
SUM(c.email = t.em_paid_to) count_paid_to,
SUM(CASE WHEN c.email = t.em_paid_by THEN t.amound ELSE 0 END) total_paid_by,
SUM(CASE WHEN c.email = t.em_paid_to THEN t.amound ELSE 0 END) total_paid_to,
SUM(CASE WHEN c.email = t.em_paid_by THEN t.amound ELSE -1 * t.amount END) balance
FROM
customer c
INNER JOIN transaction t
ON c.email IN (t.em_paid_by, t.em_paid_to)
GROUP BY
c.cust_id,
c.f_name,
c.l_name
;
I would unpivot the data and aggregate:
select t.email, c.fname, c.lname, sum(t.amount)
from ((select em_paid_by as email, -amount as amount
from transaction t
) union all
(select em_paid_to, amount
from transaction t
)
) t
group by email;
You can join to the customer table to get the additional customer information:
select email, sum(amount)
from cusomer c join
((select em_paid_by as email, -amount as amount
from transaction t
) union all
(select em_paid_to, amount
from transaction t
)
) t
on c.email = t.email
group by t.email, c.fname, c.lname;
I would use correlated subqueries in the SELECT clause:
select c.*,
coalesce((
select sum(amount)
from transaction t
where t.em_paid_by = c.email
), 0)
-
coalesce((
select sum(amount)
from transaction t
where t.em_paid_to = c.email
), 0) as paid_balance
from customer c
If you want more information like the count of transactions, I would use subqueries in the FROM clause:
select c.*,
p.cnt_paid,
r.cnt_received
coalesce(p.sum_paid, 0) as sum_paid,
coalesce(r.sum_received, 0) as sum_received,
coalesce(p.sum_paid, 0) - coalesce(r.sum_received, 0) as paid_balance,
p.cnt_paid + r.cnt_received as total_transactions
from customer c
left join (
select em_paid_by as email, sum(amount) as sum_paid, count(*) as cnt_paid
from transaction
group by em_paid_by
) p on p.email = c.email
left join (
select em_paid_to as email, sum(amount) as sum_received, count(*) as cnt_received
from transaction
group by em_paid_to
) r on r.email = c.email

how to select from table with those condition?

the table has 4 columns follows :
id int(11) not null primary key,
name varchar not null ,
status tinyint(1) not null,
created datetime not null
now, I want to find out the result rows with conditions :
the status is 0;
foreach the result rows as A, if the table can select a row name B that has the same name and created is the same day and status is 1, except the A.
could someone select the result with a sql without nested select, thanks a lot!
Sounds like an anti-join pattern fits the bill. This pattern requires two references to the table, but only a single SELECT keyword. As an example:
SELECT t.id
, t.name
, t.status
, t.created
FROM the_table t
-- anti-join exclude matching rows
LEFT
JOIN the_table d
ON d.name = t.name
AND d.status = 1
AND d.created >= DATE(t.created)
AND d.created < DATE(t.created) + INTERVAL 1 DAY
WHERE d.name IS NOT NULL
AND t.status = 0
The trick is the outer join, to return all rows from t, along with matching rows from d, with the condition in the WHERE clause that excludes all rows that had a match. Leaving only rows from t that didn't have a matching row from d.
name not in rows that has second condition:
SELECT * FROM tablename as o
WHERE o.status = 0
AND o.name NOT IN
(
SELECT `name` FROM tablename as i
WHERE i.status = 1
AND i.created
BETWEEN DATE(t.created)
AND DATE(t.created) + INTERVAL 1 DAY
)
Try this:
select a.id,a.name,a.created,a.status from
(select b.id,a.name,b.created,b.status from
(select count(name) as cnt, name from tbl_sample as a GROUP BY name) as a
LEFT JOIN
(select * from tbl_sample) as b
on a.`name` = b.`name` where cnt>1 and STATUS = 0) as a
LEFT JOIN
(select b.id,b.name,b.created,b.status from
(select count(created) as cnt, created from tbl_sample as a GROUP BY created) as a
LEFT JOIN
(select * from tbl_sample) as b
on a.`created` = b.`created` where cnt>1 and STATUS = 0) as b
on a.id = b.id
SELECT a.id, a.name, a.status, a.created
FROM userTable as a
LEFT JOIN userTable as b
ON a.name = b.name
AND b.status = 1
AND b.created BETWEEN DATE(a.created) AND DATE(DATE_ADD(a.created, INTERVAL 1 day))
WHERE a.status = 0
AND a.name IS NULL;
thanks the answer from #spencer7597 , I find out the result.

Making my mysql query more efficient

What I am trying to do is pulling the id,phone_type,os_version columns from Enswitch_Mobile_Users table.
And with the id i've just got to get the enswitch_id from Enswitch_Users table.
And after that to COUNT all the entires from Enswitch_Android_Purchases or Enswitch_Iphone_Purchases which the user colum match the id from enswitch_mobile_users. and getting first entry date and the last entry date.
And I managed to made it work with this query:
SELECT p.user AS `Mobile_User_ID`,
e.os_version `Os_Version`,
e.phone_type `Phone_Type`,
eu.enswitch_id `Enswitch_ID`,
Count(1) AS `Buy_Count`,
(SELECT pc.date
FROM
(
SELECT date, user, status
FROM enswitch_android_purchases
UNION
SELECT date, user, status
FROM enswitch_iphone_purchases
) AS pc
WHERE pc.status = 1
AND pc.user = p.user
ORDER BY pc.date ASC
LIMIT 1) AS `First_Purchase`,
(SELECT pc.date
FROM
(
SELECT date, user, status
FROM enswitch_android_purchases
UNION
SELECT date, user, status
FROM enswitch_iphone_purchases
) AS pc
WHERE pc.status = 1
AND pc.user = p.user
ORDER BY pc.date DESC LIMIT 1) AS `Last_Purchase`
FROM
(
SELECT item, date, user, status
FROM enswitch_android_purchases
UNION
SELECT item, date, user, status
FROM enswitch_iphone_purchases
) AS p
LEFT JOIN enswitch_mobile_users e
ON p.user = e.id
LEFT JOIN enswitch_users eu
ON e.user_id = eu.id
WHERE p.`date` >= :from_date
AND p.`date` <= :to_date
AND p.user is not null
AND p.status = 1
GROUP BY `Mobile_User_ID`
But because of the selects it will be really slow so how can I make it more efficient?
You might be able to use the following which replaces the two selects in the SELECT list with min(p.date) and max(p.date):
SELECT p.user AS `Mobile_User_ID`,
e.os_version `Os_Version`,
e.phone_type `Phone_Type`,
eu.enswitch_id `Enswitch_ID`,
Count(1) AS `Buy_Count`,
min(p.date) AS `First_Purchase`,
max(p.date) AS `Last_Purchase`
FROM
(
SELECT item, date, user, status
FROM enswitch_android_purchases
UNION
SELECT item, date, user, status
FROM enswitch_iphone_purchases
) AS p
LEFT JOIN enswitch_mobile_users e
ON p.user = e.id
LEFT JOIN enswitch_users eu
ON e.user_id = eu.id
WHERE p.`date` >= :from_date
AND p.`date` <= :to_date
AND p.user is not null
AND p.status = 1
GROUP BY p.user