Combining multiple queries - sql

I want a table with all customers and their last charge transaction date and their last invoice date. I have the first two, but don't know how to add the last invoice date to the table. Here's what I have so far:
WITH
--Last customer transaction
cust_trans AS (
SELECT customer_id, created
FROM charges a
WHERE created = (
SELECT MAX(created) AS last_trans
FROM charges b
WHERE a.customer_id = b.customer_id)),
--All customers
all_cust AS (
SELECT customers.id AS customer, customers.email, CAST(customers.created AS DATE) AS join_date, ((1.0 * customers.account_balance)/100) AS balance
FROM customers),
--Last customer invoice
cust_inv AS (
SELECT customer_id, date
FROM invoices a
WHERE date = (
SELECT MAX(date) AS last_inv
FROM invoices b
WHERE a.customer_id = b.customer_id))
SELECT * FROM cust_trans
RIGHT JOIN all_cust ON all_cust.customer = cust_trans.customer_id
ORDER BY join_date;

This should get what you need. Notice each individual subquery is left-joined to the customer table, so you always START with the customer, and IF there is a corresponding record in each subquery for max charge date or max invoice date, it will be pulled in. Now, you may want to apply a COALESCE() for the max dates to prevent showing nulls, such as
COALESCE(maxCharges.LastChargeDate, '') AS LastChargeDate
but your call.
SELECT
c.id AS customer,
c.email,
CAST(c.created AS DATE) AS join_date,
((1.0 * c.account_balance) / 100) AS balance,
maxCharges.LastChargeDate,
maxInvoices.LastInvoiceDate
FROM
customers c
LEFT JOIN
(SELECT
customer_id,
MAX(created) LastChargeDate
FROM
charges
GROUP BY
customer_id) maxCharges ON c.id = maxCharges.customer_id
LEFT JOIN
(SELECT
customer_id,
MAX(date) LastInvoiceDate
FROM
invoices
GROUP BY
customer_id) maxInvoices ON c.id = maxInvoices.customer_id
ORDER BY
c.created

Related

Create a column with daily count in impala

I want to create a count column which will has the count per day. I have managed to do it like this:
select book, orders, s.common_id,s.order_date,d.customer_region,t.cnt
from books_tbt as s
inner join customer_tbt as d
on s.common_id = d.common_id
inner join (select count(*) as cnt,order_date from customer_tbt where customer !='null'
group by order_date) as t
on t.order_date = d.order_date
where d.customer !='null'
and s.order_date = 20220122
group by book, orders, s.common_id,s.order_date,d.customer_region,t.cnt;
I want to ask if there is a more efficient way to do it?
You can simply use COUNT(*) OVER( Partitioned by ORDER_DATE Order by ORDER_DATE) window function to calculate count for an order date.
select book, orders, s.common_id,s.order_date,d.customer_region,d.cnt
from books_tbt as s
inner join
( select d.*, COUNT(*) OVER( Partition by ORDER_DATE Order by ORDER_DATE) as cnt from customer_tbt d) as d on s.common_id = d.common_id -- count(*) over can not be calculated together with group by so we are using a sub qry
where d.customer !='null'
and s.order_date = 20220122
group by book, orders, s.common_id,s.order_date,d.customer_region,d.cnt;

How ovoid duplicate code in sql query subQuery

I'm new in SQL. Need some help to improve my query to ovoid duplicate code.
SELECT customers.name, orders.price
FROM customers
JOIN orders ON orders.id = customers.order_id
WHERE customers.order_id IN (
SELECT orders.id
FROM orders
WHERE orders.price = (
SELECT orders.price
FROM orders
WHERE orders.order_date BETWEEN
(SELECT MIN(orders.order_date) FROM orders)
AND
(SELECT DATE_ADD(MIN(orders.order_date), INTERVAL 10 year)FROM orders)
ORDER BY orders.price DESC LIMIT 1
)
AND orders.order_date BETWEEN
(SELECT MIN(orders.order_date) FROM orders)
AND
(SELECT DATE_ADD(MIN(orders.order_date), INTERVAL 10 year)FROM orders)
)
I would like ovoid duplicate code here
SELECT MIN(orders.order_date) FROM orders
and
SELECT DATE_ADD(MIN(orders.order_date), INTERVAL 10 year)FROM orders
You can use WITH to get first 10 years orders. By defitinion there exists no orders with the date < min(date), so you needn't between, just <= .
firstOrders as (
SELECT *
FROM orders
WHERE order_date <=
(SELECT DATE_ADD(MIN(o.order_date), INTERVAL 10 year)
FROM orders o)
)
SELECT customers.name, orders.price
FROM customers
JOIN FirsrOrders orders ON orders.id = customers.order_id
AND orders.price = (
select price
from firstOrders
order py price desc
limit 1
)
You want orders from the first ten years where the price was equal to the maximum price among those orders. So rank by price and grab those holding the #1 spot.
with data as (
select *,
date_add(min(order_date) over (), interval 10 year) as max_date,
rank() over (order by price desc) as price_rank
from orders
)
select *
from data
where order_date <= max_date and price_rank = 1;

how to filter data in sql based on percentile

I have 2 tables, the first one is contain customer information such as id,age, and name . the second table is contain their id, information of product they purchase, and the purchase_date (the date is from 2016 to 2018)
Table 1
-------
customer_id
customer_age
customer_name
Table2
------
customer_id
product
purchase_date
my desired result is to generate the table that contain customer_name and product who made purchase in 2017 and older than 75% of customer that make purchase in 2016.
Depending on your flavor of SQL, you can get quartiles using the more general ntile analytical function. This basically adds a new column to your query.
SELECT MIN(customer_age) as min_age FROM (
SELECT customer_id, customer_age, ntile(4) OVER(ORDER BY customer_age) AS q4 FROM table1
WHERE customer_id IN (
SELECT customer_id FROM table2 WHERE purchase_date = 2016)
) q
WHERE q4=4
This returns the lowest age of the 4th-quartile customers, which can be used in a subquery against the customers who made purchases in 2017.
The argument to ntile is how many buckets you want to divide into. In this case 75%+ equals 4th quartile, so 4 buckets is OK. The OVER() clause specifies what you want to sort by (customer_age in our case), and also lets us partition (group) the data if we want to, say, create multiple rankings for different years or countries.
Age is a horrible field to include in a database. Every day it changes. You should have date-of-birth or something similar.
To get the 75% oldest value in 2016, there are several possibilities. I usually go for row_number() and count(*):
select min(customer_age)
from (select c.*,
row_number() over (order by customer_age) as seqnum,
count(*) over () as cnt
from customers c join
where exists (select 1
from customer_products cp
where cp.customer_id = c.customer_id and
cp.purchase_date >= '2016-01-01' and
cp.purchase_date < '2017-01-01'
)
)
where seqnum >= 0.75 * cnt;
Then, to use this for a query for 2017:
with a2016 as (
select min(customer_age) as customer_age
from (select c.*,
row_number() over (order by customer_age) as seqnum,
count(*) over () as cnt
from customers c
where exists (select 1
from customer_products cp
where cp.customer_id = c.customer_id and
cp.purchase_date >= '2016-01-01' and
cp.purchase_date < '2017-01-01'
)
) c
where seqnum >= 0.75 * cnt
)
select c.*, cp.product_id
from customers c join
customer_products cp
on cp.customer_id = c.customer_id and
cp.purchase_date >= '2017-01-01' and
cp.purchase_date < '2018-01-01' join
a2016 a
on c.customer_age >= a.customer_age;

How to sum over selected dates from table

Consider the following query which generates customerid and days on which they bought a particular product, clearly each customer will have different dates on which he/she bought an item. What I want to do is get total purchase made on those days that the customer bought that product.
I have the ff query.
Select customerid, eventdate
into #days
from table1
where product='chocolate'
now i want to sum all purchases made on just those days customer bought 'chocolate'.
so i have
select customerid, sum(purchases) purchases
into #pur
from table1 a
where eventdate in (select eventdate from #days where customerid=a.customerid)
group by customerid
but the above is taking to long to run so i cancelled it.
please assist with a better query.
Not very sure if this will work but try it out. If it doesnt then can you please provide sample data and expected output so that we can try it out in a better fashion
select customerid, sum(purchases) purchases
into #pur
from table1 a
inner join #days d
ON d.customerid = a.customerid
AND d.eventdate = a.eventdate
group by customerid
OR try this query
select customerid, sum(purchases) purchases
into #pur
from table1 a
inner join #days d
ON d.customerid = a.customerid
WHERE d.eventdate = a.eventdate
group by customerid
Hope this helps
The sum of all purchases by customer, counting only the dates on which he bought chocolate.
SELECT customerid, sum(purchases) purchases
FROM table1 a
WHERE eventdate IN
(SELECT eventdate
FROM table1
WHERE product = 'chocolate'
AND customerid = a.customerid)
GROUP BY customerid;
This gives sum of purchases for each customer AND for each of that day on which chocolate was purchased.
select customerid, eventdate ,sum(purchases) purchases
into #pur
from table1
where product='chocolate'
group by customerid,eventdate
If you want total purchase when chocolate was brought then do this
select customerid,sum(purchases) purchases
into #pur
from table1
where product='chocolate'
group by customerid
As per your clarification
select customerid, sum(purchases) purchases
into #pur
from table1 a
where eventdate in (select eventdate from table1 where product='chocolate')
group by customerid
I suggest you to apply indexing on eventdate column to improve query performance.
After thinking through carefully the following worked for me, and is faster.
--drop table #days
select customerid, eventdate
into #days
from table1
with(nolock, index(ix_eventdate))
WHERE EVENTDATE between 20140401 and 20140430
and product='chocolate'
--drop table #pur
select customerid, eventdate, purchases
into #pur
from table1
with(nolock, index(ix_eventdate))
where eventdate between 20140401 and 20140430
--drop table #first
select a.*, b.purchases
into #first from #days a
left join #pur b
on a.customerid=b.customerid
and a.EventDate =b.EventDate
--select * from #first
--drop table #purdays
select customerid, sum(purchases) revenue into #purdays from #first
group by customerid
order by customerid
select * from #purdays

MySQL: Returning multiple columns from an in-line subquery

I'm creating an SQL statement that will return a month by month summary on sales.
The summary will list some simple columns for the date, total number of sales and the total value of sales.
However, in addition to these columns, i'd like to include 3 more that will list the months best customer by amount spent. For these columns, I need some kind of inline subquery that can return their ID, Name and the Amount they spent.
My current effort uses an inline SELECT statement, however, from my knowledge on how to implement these, you can only return one column and row per in-line statement.
To get around this with my scenario, I can of course create 3 separate in-line statements, however, besides this seeming impractical, it increases the query time more that necessary.
SELECT
DATE_FORMAT(OrderDate,'%M %Y') AS OrderMonth,
COUNT(OrderID) AS TotalOrders,
SUM(OrderTotal) AS TotalAmount,
(SELECT SUM(OrderTotal) FROM Orders WHERE DATE_FORMAT(OrderDate,'%M %Y') = OrderMonth GROUP BY OrderCustomerFK ORDER BY SUM(OrderTotal) DESC LIMIT 1) AS TotalCustomerAmount,
(SELECT OrderCustomerFK FROM Orders WHERE DATE_FORMAT(OrderDate,'%M %Y') = OrderMonth GROUP BY OrderCustomerFK ORDER BY SUM(OrderTotal) DESC LIMIT 1) AS CustomerID,
(SELECT CustomerName FROM Orders INNER JOIN Customers ON OrderCustomerFK = CustomerID WHERE DATE_FORMAT(OrderDate,'%M %Y') = OrderMonth GROUP BY OrderCustomerFK ORDER BY SUM(OrderTotal) DESC LIMIT 1) AS CustomerName
FROM Orders
GROUP BY DATE_FORMAT(OrderDate,'%m%y')
ORDER BY DATE_FORMAT(OrderDate,'%y%m') DESC
How can i better structure this query?
FULL ANSWER
After some tweaking of Dave Barkers solution, I have a final version for anyone in the future looking for help.
The solution by Dave Barker worked perfectly with the customer details, however, it made the simpler Total Sales and Total Sale Amount columns get some crazy figures.
SELECT
Y.OrderMonth, Y.TotalOrders, Y.TotalAmount,
Z.OrdCustFK, Z.CustCompany, Z.CustOrdTotal, Z.CustSalesTotal
FROM
(SELECT
OrdDate,
DATE_FORMAT(OrdDate,'%M %Y') AS OrderMonth,
COUNT(OrderID) AS TotalOrders,
SUM(OrdGrandTotal) AS TotalAmount
FROM Orders
WHERE OrdConfirmed = 1
GROUP BY DATE_FORMAT(OrdDate,'%m%y')
ORDER BY DATE_FORMAT(OrdDate,'%Y%m') DESC)
Y INNER JOIN
(SELECT
DATE_FORMAT(OrdDate,'%M %Y') AS CustMonth,
OrdCustFK,
CustCompany,
COUNT(OrderID) AS CustOrdTotal,
SUM(OrdGrandTotal) AS CustSalesTotal
FROM Orders INNER JOIN CustomerDetails ON OrdCustFK = CustomerID
WHERE OrdConfirmed = 1
GROUP BY DATE_FORMAT(OrdDate,'%m%y'), OrdCustFK
ORDER BY SUM(OrdGrandTotal) DESC)
Z ON Z.CustMonth = Y.OrderMonth
GROUP BY DATE_FORMAT(OrdDate,'%Y%m')
ORDER BY DATE_FORMAT(OrdDate,'%Y%m') DESC
Move the inline SQL to be a inner join query. So you'd have something like...
SELECT DATE_FORMAT(OrderDate,'%M %Y') AS OrderMonth, COUNT(OrderID) AS TotalOrders, SUM(OrderTotal) AS TotalAmount, Z.OrderCustomerFK, Z.CustomerName, z.OrderTotal as CustomerTotal
FROM Orders
INNER JOIN (SELECT DATE_FORMAT(OrderDate,'%M %Y') as Mon, OrderCustomerFK, CustomerName, SUM(OrderTotal) as OrderTotal
FROM Orders
GROUP BY DATE_FORMAT(OrderDate,'%M %Y'), OrderCustomerFK, CustomerName ORDER BY SUM(OrderTotal) DESC LIMIT 1) Z
ON Z.Mon = DATE_FORMAT(OrderDate,'%M %Y')
GROUP BY DATE_FORMAT(OrderDate,'%m%y'), Z.OrderCustomerFK, Z.CustomerName
ORDER BY DATE_FORMAT(OrderDate,'%y%m') DESC
You can also do something like:
SELECT
a.`y`,
( SELECT #c:=NULL ) AS `temp`,
( SELECT #d:=NULL ) AS `temp`,
( SELECT
CONCAT(#c:=b.`c`, #d:=b.`d`)
FROM `b`
ORDER BY b.`uid`
LIMIT 1 ) AS `temp`,
#c as c,
#d as d
FROM `a`
Give this a shot:
SELECT CONCAT(o.order_month, ' ', o.order_year),
o.total_orders,
o.total_amount,
x.sum_order_total,
x.ordercustomerfk,
x.customername
FROM (SELECT MONTH(t.orderdate) AS order_month,
YEAR(t.orderdate) AS order_year
COUNT(t.orderid) AS total_orders,
SUM(t.ordertotal) AS total_amount
FROM ORDERS t
GROUP BY MONTH(t.orderdate), YEAR(t.orderdate)) o
JOIN (SELECT MONTH(t.orderdate) AS ordermonth,
YEAR(t.orderdate) AS orderyear
SUM(t.ordertotal) 'sum_order_total',
t.ordercustomerfk,
c.customername
FROM ORDERS t
JOIN CUSTOMERS c ON c.customerid = o.ordercustomerfk
GROUP BY t.ordercustomerfk, MONTH(t.orderdate), YEAR(t.orderdate)) x ON x.order_month = o.order_month
AND x.order_year = o.order_year
ORDER BY o.order_year DESC, o.order_month DESC