How to only include full months - Postgres - sql

I have the following SQL query:
SELECT
SUM(amount)
FROM
(SELECT
l.human_readable_id,
DATE_TRUNC('day', c.created_date)::TIMESTAMP AS Date,
(ROUND(c.amount/100.00, 2))::DOUBLE PRECISION AS amount,
(ROUND(c.amount/100.00, 2)*0.04)::DOUBLE PRECISION AS Repayment,
c.currency,
c.payment_type,
c.status,
c.payment_id
FROM
loan_applications AS l
LEFT JOIN
merchants AS m ON l.merchant_id = m.id
LEFT JOIN
codat_companies AS cc ON m.id = cc.merchant_id
LEFT JOIN
codat_commerce_payments AS c ON cc.id = c.codat_company_id
WHERE
amount IS NOT NULL) AS subquery
GROUP BY
date
And get the sum of every month. Based on this, I can calculate the average. Is it possible to only include full months? For instance this is data from 1st of May 2021 until yesterday. But including this month would have a negative impact on the overall monthly average.
Thanks in advance

You can add a condition in the WHERE clause :
WHERE amount IS NOT NULL
AND c.created_date < date_trunc('month', Now())

Related

Retrieve records from multiple Records returned by Sub-Query

I have this database Diagram :
the diagram is represent a database for Insurance Company.
the final_cost table represent the cost that the company should paid to repair a car
the table car has the field car_type which take one of the following values (1,2,3) where 1 refers to small cars, 2 refers to trucks , 3 refers to buses
I want to retrieve the name of kind (1 or 2 or 3 ) which has the maximum repaired cost during the 2013 year
I wrote The following Query :
select innerr.car_type from (
select car_type ,sum(fina_cost.cost) from car_acc inner join cars on cars.car_id = car_acc.car_id
inner join final_cost on FINAL_COST.CAR_ACC_ID = car_acc.CAR_ACC_ID
where (extract(year from final_cost.fittest_date)=2013)
group by(car_type)) innerr;
but I don't know how to get the car_type with maximum repaired Cost from the inner Sub-Query !
You can have access to anything and everything from a subquery if you use it right. The best way to build a complicated query is to start simply, seeing what data you have and usually the answer, or the next step, will be obvious.
So let's start by displaying all the accidents for 2013. We aren't interest in the individual cars, just the most expensive accidents by type. So...
select c.car_type, f.cost
from car_acc a
join cars c
on c.car_id = a.car_id
join final_cost f
on f.car_acc_id = a.car_acc_id
where f.fittest_date >= date '2013-01-01'
and f.fittest_date < date '2014-01-01';
I've changed the filtering criteria to a sargable form for efficiency. I don't usually worry about performance early in the design of a query, but when it's this obvious, why not?
Anyway, we now have a list of all 2013 accidents, by car type and the cost of each one. So now we only have to group by the type and take the Max of the cost of each group.
select c.car_type, Max( f.cost ) MaxCost
from car_acc a
join cars c
on c.car_id = a.car_id
join final_cost f
on f.car_acc_id = a.car_acc_id
where f.fittest_date >= date '2013-01-01'
and f.fittest_date < date '2014-01-01'
group by c.car_type;
Now we have a list of car types and the most expensive accidents for that type for 2013. With only three rows in the result set, it's easy to see which is the car type we're looking for. Now we just have to isolate that one row. The easiest step from here is to use this query in a CTE.
with MaxPerType( car_type, MaxCost )as(
select c.car_type, Max( f.cost ) MaxCost
from car_acc a
join cars c
on c.car_id = a.car_id
join final_cost f
on f.car_acc_id = a.car_acc_id
where f.fittest_date >= date '2013-01-01'
and f.fittest_date < date '2014-01-01'
group by c.car_type
)
select m.car_type, m.MaxCost
from MaxPerType m
where m.MaxCost =(
select Max( MaxCost )
from MaxPerType );
So the CTE gives us the largest cost per type and the subquery in the main query gives us the largest cost overall. So the result is the type(s) that match the largest cost overall.
You could try either orderby or better yet, use the Max Function http://docs.oracle.com/cd/B19306_01/server.102/b14200/functions085.htm
Try this:
SELECT A.car_type
FROM (SELECT c.car_type, SUM(fc.cost) totalCost
FROM car_accident ca
INNER JOIN cars c ON c.car_id = ca.car_id
INNER JOIN final_cost fc ON fc.CAR_ACC_ID = ca.CAR_ACC_ID
WHERE EXTRACT(YEAR FROM fc.fittest_date) = 2013
GROUP BY c.car_type
ORDER BY totalCost DESC
) AS A
WHERE ROWNUM = 1;

SQL - average distance per day

I have a problem here: "Show average distance per day driven by cars from Paris"
I have also 2 tables referred to this problem
table_cars: id, brand, type, license
table_distances: id_car, date, distance
I have managed to select "the average distance for the cars from Paris"
select avg(table_distances.distance)
from table_distances
INNER JOIN table_cars ON table_distances.id_car = table_cars.id
where table_cars.license = 'Paris';'
Though, I have still a problem with average distance per day. I looked over related questions on the stackoverflow/google but I got more confused.
Can somebody explain how I can improve my query to show average distance per day?
This should get you the distances per car per date.
SELECT id_car, date, AVG(table_distances.distance)
FROM table_distances
INNER JOIN table_cars
ON table_distances.id_car = table_cars.id
WHERE table_cars.license = 'Paris'
GROUP BY id_car, date
ORDER BY id_car, date
Simply add the date to what you select and group by it so it's averaged per row:
SELECT table_distances.date, avg(table_distances.distance)
FROM table_distances
INNER JOIN table_cars ON table_distances.id_car = table_cars.id
WHERE table_cars.license = 'Paris'
GROUP BY table_distances.date
Simply add GROUP BY clause to your DATE column
Reference for SQL GROUP BY clause
SELECT AVG(td.distance)
FROM table_distances td
INNER JOIN table_cars tc ON td.id_car = tc.id
WHERE tc.license = 'Paris'
GROUP BY td.date;
Then you have to get average distance of a car per day

Complex SQL Query Help

I am trying to do rather complex SQL query to produce a report. This is a database used by an inventory and accounting system.
Essentially I need to produce a report with the following columns
Month / Year (group results by month / year)
Reseller (order results by reseller with in the month / year group)
Total sales - Sales - Hardware
Total sales - Sales - Consumables
The following tables will need to be used in the report:
Invoice
Reseller
Job
JobStockItem
Stock
Essentially the query would need to start as:
1. Select all invoices from Invoice
2. Get the reseller name from Reseller.Name (join on Reseller.ID with Invoice.CustomerID)
3. Get the associated job ID from Job table (join on Job.InvoiceID with Invoice.ID)
4. Get each component of the invoice from JobStockItems (join JobStockItem.JobID on Job.ID)
5. Get the stock item in in the job from Stock (join on JobStockItems.StockId on Stock.ID) and see if the category (Stock.Category1) is either Hardware or Consumables
6. If the stock item is hardware or consumables, use the sale price in the JobStockItem (JobStockItem.PriceExTax) and add it towards the total for the month of the resellers purchases
The month and year come from the invoice date (Invoice.InvoiceDate).
Now I could produce this result myself by executing a bunch of queries and processing myself, one each for the above steps, but it's going to end up slow and I'm sure there'd have to be a query out there that could wrap all those requirements up and do it in one?
I have not attempted to do the query yet as to be honest, I don't know where to start - it's a lot more complex than anything I've done in the past.
I am just using Management Studio, not using Reporting Services, Crystal Reports or anything. My aim is to dump the output to HTML when I have it working.
Thanks heaps in advance.
It think if you Left Join into the JobStockItems table twice (once for hardware, and once for consumables), you can manage all that in one query. The final query will look something like this (Don't have my editor up right now, so apologies for any typos)
SELECT DATEPART(m, Invoice.InvoiceDate) month,
DATEPART(yy, Invoice.InvoiceDate) year,
Reseller.Name,
SUM(jobstockitems_hardware.Price) sales_hardware,
SUM(jobstockitems_consumables.Price) sales_consumables,
FROM Invoice
INNER JOIN Reseller
ON Invoice.CustomerID = Reseller.ID
INNER JOIN Job
ON Invoice.ID = Job.InvoiceID
LEFT JOIN (SELECT JobID, SUM(PriceExTax) Price
FROM JobStockItems
INNER JOIN Stock
ON JobStockItems.StockID = Stock.StockID
AND Stock.Category1 = 'Hardware'
GROUP BY JobID) jobstockitems_hardware
ON Job.ID = jobstockitems_hardware.JobID
LEFT JOIN (SELECT JobID, SUM(PriceExTax) Price
FROM JobStockItems
INNER JOIN Stock
ON JobStockItems.StockID = Stock.StockID
AND Stock.Category1 = 'Consumables'
GROUP BY JobID) jobstockitems_consumables
ON Job.ID = jobstockitems_consumables.JobID
GROUP BY DATEPART(m, Invoice.Date),
DATEPART(yy, Invoice.Date),
Reseller.Name
ORDER BY DATEPART(yy, Invoice.Date) ASC,
DATEPART(m, Invoice.Date) ASC,
Reseller.Name ASC
I'm assuming that Retailer has a column called Name that you want to return as well, feel free to change that to ID or whatever else you'd rather return.
Edit: Fixed query to remove duplicates
Partly you want to PIVOT your results. So you can simply join your data together and let the PIVOT do the selecting and summarizing of categories:
select *
from (
select Year = datepart(year, i.InvoiceDate),
Month = datepart(month, i.InvoiceDate),
ResellerName = r.name,
StockCategory = s.Category1,
jsi.PriceExTax
from Invoice i
inner join Reseller r
on r.ID = i.CustomerID
inner join Job j
on j.InvoiceID = i.ID
inner join JobStockItem jsi
on jsi.JobID = j.ID
inner join Stock s
on s.ID = jsi.StockID
) d
pivot (sum(PriceExTax) for StockCategory in (Hardware, Consumables)) p
order by Year, Month, ResellerName;
In the end you'll need some conditions in the inner query (e.g. where i.InvoiceDate between ...).
Perhaps you have to multiply the PriceExTax with the amount in JobStockItem...
DPMattingley has produced a good query, but with one limitation - if a time period has no results, it won't show a row at all. This may well be an unlikely case in the specific example here but where it does happen it's annoying to find the report hides the zero results month!
My standard solution to this involves taking the month from a numbers table, which forces all time periods to appear. Using DPMattingley's query as a starting point -
select
mths.month,
mths.year,
d.Name,
d.sales_hardware,
d.sales_consumables
from
/*All the in-range months on which to report*/
(select
datepart(m,dateadd(m,number,minDate)) month,
datepart(yy,dateadd(m,number,minDate)) year
from
numbers n
inner join (select min(invoice.invoicedate) as minDate from invoice) m
on n.number between 0 and datediff(m,minDate,getdate())) mths
left join
/*Data for each month*/
(SELECT DATEPART(m, Invoice.InvoiceDate) month,
DATEPART(yy, Invoice.InvoiceDate) year,
Reseller.Name,
SUM(jobstockitems_hardware.Price) sales_hardware,
SUM(jobstockitems_consumables.Price) sales_consumables
FROM
Invoice
INNER JOIN Reseller
ON Invoice.CustomerID = Reseller.ID
INNER JOIN Job
ON Invoice.ID = Job.InvoiceID
LEFT JOIN (SELECT JobID, SUM(PriceExTax) Price
FROM JobStockItems
INNER JOIN Stock
ON JobStockItems.StockID = Stock.StockID
AND Stock.Category1 = 'Hardware'
GROUP BY JobID) jobstockitems_hardware
ON Job.ID = jobstockitems_hardware.JobID
LEFT JOIN (SELECT JobID, SUM(PriceExTax) Price
FROM JobStockItems
INNER JOIN Stock
ON JobStockItems.StockID = Stock.StockID
AND Stock.Category1 = 'Consumables'
GROUP BY JobID) jobstockitems_consumables
ON Job.ID = jobstockitems_consumables.JobID
GROUP BY DATEPART(m, Invoice.Date),
DATEPART(yy, Invoice.Date),
Reseller.Name) d
on mths.month=d.month
and mths.year=d.year
ORDER BY mths.month ASC,
mths.year ASC,
Reseller.Name ASC

MySql get rows between date values

I'm doing the following query and getting all rows returned, regardless of date:
SELECT DISTINCT p.name, p.category, u.f_name, t.name
FROM (
prizes p, tags t, results r
)
LEFT JOIN
users u
ON (r.user_id = u.id)
LEFT JOIN
f_tag_lookup tl
ON (tl.tag_id = t.id)
WHERE r.tag_id = t.id
AND r.date BETWEEN concat(date_format(LAST_DAY(now() - interval 1 month), '%Y-%m-'),'01') AND last_day(NOW() - INTERVAL 1 MONTH)
r.date is a datetime field. there are three rows with dates from last month and three rows with dates from this month but I'm getting all rows back?
Would appreciate any pointers. I want to return results between the first and last day of last month i.e. all results for July.
thanks,
Could you not just use the month() date function ?
e.g. use month(date) = month(now()) as the filter.
You would need to also match on year(date) = year(now()), if you had data from more than one year.
I'm not sure of the performance, but it seems more readable to me to express it that way.
SELECT DISTINCT p.name, p.category, u.f_name, t.name
FROM (prizes p, tags t, results r)
LEFT JOIN users u ON (r.user_id = u.id)
LEFT JOIN f_tag_lookup tl ON (tl.tag_id = t.id)
WHERE r.tag_id = t.id AND
month(r.date) = month(now()) AND
year(r.date) = year(now())
figured it out. The SQL regarding dates was fine but the JOINS were wrong. Thanks for the replies.

Left and right joining in a query

A friend asked me for help on building a query that would show how many pieces of each model were sold on each day of the month, showing zeros when no pieces were sold for a particular model on a particular day, even if no items of any model are sold on that day. I came up with the query below, but it isn't working as expected. I'm only getting records for the models that have been sold, and I don't know why.
select days_of_months.`Date`,
m.NAME as "Model",
count(t.ID) as "Count"
from MODEL m
left join APPLIANCE_UNIT a on (m.ID = a.MODEL_FK and a.NUMBER_OF_UNITS > 0)
left join NEW_TICKET t on (a.NEW_TICKET_FK = t.ID and t.TYPE = 'SALES'
and t.SALES_ORDER_FK is not null)
right join (select date(concat(2009,'-',temp_months.id,'-',temp_days.id)) as "Date"
from temp_months
inner join temp_days on temp_days.id <= temp_months.last_day
where temp_months.id = 3 -- March
) days_of_months on date(t.CREATION_DATE_TIME) =
date(days_of_months.`Date`)
group by days_of_months.`Date`,
m.ID, m.NAME
I had created the temporary tables temp_months and temp_days in order to get all the days for any month. I am using MySQL 5.1, but I am trying to make the query ANSI-compliant.
You should CROSS JOIN your dates and models so that you have exactly one record for each day-model pair no matter what, and then LEFT JOIN other tables:
SELECT date, name, COUNT(t.id)
FROM (
SELECT ...
) AS days_of_months
CROSS JOIN
model m
LEFT JOIN
APPLIANCE_UNIT a
ON a.MODEL_FK = m.id
AND a.NUMBER_OF_UNITS > 0
LEFT JOIN
NEW_TICKET t
ON t.id = a.NEW_TICKET_FK
AND t.TYPE = 'SALES'
AND t.SALES_ORDER_FK IS NOT NULL
AND t.CREATION_DATE_TIME >= days_of_months.`Date`
AND t.CREATION_DATE_TIME < days_of_months.`Date` + INTERVAL 1 DAY
GROUP BY
date, name
The way you do it now you get NULL's in model_id for the days you have no sales, and they are grouped together.
Note the JOIN condition:
AND t.CREATION_DATE_TIME >= days_of_months.`Date`
AND t.CREATION_DATE_TIME < days_of_months.`Date` + INTERVAL 1 DAY
instead of
DATE(t.CREATION_DATE_TIME) = DATE(days_of_months.`Date`)
This will help make your query sargable (optimized by indexes)
You need to use outer joins, as they do not require each record in the two joined tables to have a matching record.
http://dev.mysql.com/doc/refman/5.1/en/join.html
You're looking for an OUTER join. A left outer join creates a result set with a record from the left side of the join even if the right side does not have a record to be joined with. A right outer join does the same on the opposite direction, creates a record for the right side table even if the left side does not have a corresponding record. Any column projected from the table that does not have a record will have a NULL value in the join result.