getting data for individual months using single query - sql

Im having 3 tables as per below picture (im giving just sample data but actual tables having data for all months). My SALES table will contain the data both for future(which were sold in future) and past dates.
select item_id as agg from item_category f JOIN
sales t ON f.item_id=t.item_id where t.selling_date
BETWEEN sysdate AND sysdate+21 and f.item_type='medicine';
and now my query returns ITEMS which were sold for next 21 days. Now i want to get the profit of these ITEMS for past one year
For example i want the profit for last one year as Jan:2000,feb:3000,mar:1000.......

To get the output in rows:
SELECT -- s.item_id, -- Not sure if you want it total or per item
TRUNC( s.sold_date, 'MM' ) AS sold_month,
SUM( r.profit ) AS total_profit
FROM sales s
INNER JOIN
revenue_table r
ON ( s.item_id = r.item_id )
GROUP BY -- s.item_id, -- Not sure if you want it total or per item
TRUNC( s.sold_date, 'MM' )
To PIVOT the rows into columns:
SELECT *
FROM (
SELECT -- s.item_id, -- Not sure if you want it total or per item
TRUNC( s.sold_date, 'MM' ) AS sold_month,
r.profit
FROM sales s
INNER JOIN
revenue_table r
ON ( s.item_id = r.item_id )
)
PIVOT ( SUM( profit ) FOR sold_month IN (
DATE '2017-05-01' AS May2017,
DATE '2017-04-01' AS Apr2017,
DATE '2017-03-01' AS Mar2017,
DATE '2017-02-01' AS Feb2017,
DATE '2017-01-01' AS Jan2017,
DATE '2016-12-01' AS Dec2016,
DATE '2016-11-01' AS Nov2016,
DATE '2016-10-01' AS Oct2016
) )

You could group items by date of sale and aggregate this profit.
SELECT
YEAR(sold_date) AS `Year`,
MONTH(sold_date) AS `Month`,
SUM(profit) AS Profit
FROM sales JOIN Reventue_Table ON sales.item_id=Reventue_Table.item_id
GROUP BY `Year`, `Month`;
If you want to get result for specific year, use HAVING clause. For SQL use DATEPART() function. For oracle use EXTRACT().

Related

In Postgres how do I write a SQL query to select distinct values overall but aggregated over a set time period

What I mean by this is if I have a table called payments with a created_at column and user_id column I want to select the count of purchases aggregated weekly (can be any interval I want) but only selecting first time purchases e.g. if a user purchased for the first time in week 1 it would be counted but if he purchased again in week 2 he would not be counted.
created_at
user_id
timestamp
1
timestamp
1
This is the query I came up with. The issue is if the user purchases multiple times they are all included. How can I improve this?
WITH dates AS
(
SELECT *
FROM generate_series(
'2022-07-22T15:30:06.687Z'::DATE,
'2022-11-21T17:04:59.457Z'::DATE,
'1 week'
) date
)
SELECT
dates.date::DATE AS date,
COALESCE(COUNT(DISTINCT(user_id)), 0) AS registrations
FROM
dates
LEFT JOIN
payment ON created_at::DATE BETWEEN dates.date AND dates.date::date + '1 ${dateUnit}'::INTERVAL
GROUP BY
dates.date
ORDER BY
dates.date DESC;
You want to count only first purchases. So get those first purchases in the first step and work with these.
WITH dates AS
(
SELECT *
FROM generate_series(
'2022-07-22T15:30:06.687Z'::DATE,
'2022-11-21T17:04:59.457Z'::DATE,
'1 week'
) date
)
, first_purchases AS
(
SELECT user_id, MIN(created_at:DATE) AS purchase_date
FROM payment
GROUP BY user_id
)
SELECT
d.date,
COALESCE(COUNT(p.purchase_date), 0) AS registrations
FROM
dates d
LEFT JOIN
first_purchases p ON p.purchase_date >= d.date
AND p.purchase_date < d.date + '1 ${dateUnit}'::INTERVAL
GROUP BY
d.date
ORDER BY
d.date DESC;

Calculate average days between orders The last three records tsql

I trying to take an average per customer, but you're not grouping by customer.
I would like to calculate the average days between several order dates from a table called invoice. For each BusinessPartnerID, what is the average days between orders i want average days last three records orders .
I got the average of all order for each user but need days last three records orders
The sample table is as below
;WITH temp (avg,invoiceid,carname,carid,fullname,mobail)
AS
(
SELECT AvgLag = AVG(Lag) , Lagged.idinvoice,
Lagged.carname ,
Lagged.carid ,Lagged.fullname,Lagged.mobail
FROM
(
SELECT
(car2.Name) as carname ,
(car2.id) as carid ,( busin.Name) as fullname, ( busin.Mobile) as mobail , INV.Id as idinvoice , Lag = CONVERT(int, DATEDIFF(DAY, LAG(Date,1)
OVER (PARTITION BY car2.Id ORDER BY Date ), Date))
FROM [dbo].[Invoice] AS INV
JOIN [dbo].[InvoiceItem] AS INITEM on INV.Id=INITEM.Invoiceid
JOIN [dbo].[BusinessPartner] as busin on busin.Id=INV.BuyerId and Type=5
JOIN [dbo].[Product] as pt on pt.Id=INITEM.ProductId and INITEM.ProductId is not null and pt.ProductTypeId=3
JOIN [dbo].[Car] as car2 on car2.id=INv.BusinessPartnerCarId
) AS Lagged
GROUP BY
Lagged.carname,
Lagged.carid,Lagged.fullname,Lagged.mobail, Lagged.idinvoice
-- order by Lagged.fullname
)
SELECT * FROM temp where avg is not null order by avg
I don't really see how your query relate to your question. Starting from a table called invoice that has columns businesspartnerid, and date, here is how you would take the average of the day difference between the last 3 invoices of each business partner:
select businesspartnerid,
avg(1.0 * datediff(
day,
lag(date) over(partition by businesspartnerid order by date),
date
) avg_diff_day
from (
select i.*,
row_number() over(partiton by businesspartnerid order by date desc) rn
from invoice i
) i
where rn <= 3
group by businesspartnerid
Note that 3 rows gives you 2 intervals only, that will be averaged.

Same output in two different lateral joins

I'm working on a bit of PostgreSQL to grab the first 10 and last 10 invoices of every month between certain dates. I am having unexpected output in the lateral joins. Firstly the limit is not working, and each of the array_agg aggregates is returning hundreds of rows instead of limiting to 10. Secondly, the aggregates appear to be the same, even though one is ordered ASC and the other DESC.
How can I retrieve only the first 10 and last 10 invoices of each month group?
SELECT first.invoice_month,
array_agg(first.id) first_ten,
array_agg(last.id) last_ten
FROM public.invoice i
JOIN LATERAL (
SELECT id, to_char(invoice_date, 'Mon-yy') AS invoice_month
FROM public.invoice
WHERE id = i.id
ORDER BY invoice_date, id ASC
LIMIT 10
) first ON i.id = first.id
JOIN LATERAL (
SELECT id, to_char(invoice_date, 'Mon-yy') AS invoice_month
FROM public.invoice
WHERE id = i.id
ORDER BY invoice_date, id DESC
LIMIT 10
) last on i.id = last.id
WHERE i.invoice_date BETWEEN date '2017-10-01' AND date '2018-09-30'
GROUP BY first.invoice_month, last.invoice_month;
This can be done with a recursive query that will generate the interval of months for who we need to find the first and last 10 invoices.
WITH RECURSIVE all_months AS (
SELECT date_trunc('month','2018-01-01'::TIMESTAMP) as c_date, date_trunc('month', '2018-05-11'::TIMESTAMP) as end_date, to_char('2018-01-01'::timestamp, 'YYYY-MM') as current_month
UNION
SELECT c_date + interval '1 month' as c_date,
end_date,
to_char(c_date + INTERVAL '1 month', 'YYYY-MM') as current_month
FROM all_months
WHERE c_date + INTERVAL '1 month' <= end_date
),
invocies_with_month as (
SELECT *, to_char(invoice_date::TIMESTAMP, 'YYYY-MM') invoice_month FROM invoice
)
SELECT current_month, array_agg(first_10.id), 'FIRST 10' as type FROM all_months
JOIN LATERAL (
SELECT * FROM invocies_with_month
WHERE all_months.current_month = invoice_month AND invoice_date >= '2018-01-01' AND invoice_date <= '2018-05-11'
ORDER BY invoice_date ASC limit 10
) first_10 ON TRUE
GROUP BY current_month
UNION
SELECT current_month, array_agg(last_10.id), 'LAST 10' as type FROM all_months
JOIN LATERAL (
SELECT * FROM invocies_with_month
WHERE all_months.current_month = invoice_month AND invoice_date >= '2018-01-01' AND invoice_date <= '2018-05-11'
ORDER BY invoice_date DESC limit 10
) last_10 ON TRUE
GROUP BY current_month;
In the code above, '2018-01-01' and '2018-05-11' represent the dates between we want to find the invoices. Based on those dates, we generate the months (2018-01, 2018-02, 2018-03, 2018-04, 2018-05) that we need to find the invoices for.
We store this data in all_months.
After we get the months, we do a lateral join in order to join the invoices for every month. We need 2 lateral joins in order to get the first and last 10 invoices.
Finally, the result is represented as:
current_month - the month
array_agg - ids of all selected invoices for that month
type - type of the selected invoices ('first 10' or 'last 10').
So in the current implementation, you will have 2 rows for each month (if there is at least 1 invoice for that month). You can easily join that in one row if you need to.
LIMIT is working fine. It's your query that's broken. JOIN is just 100% the wrong tool here; it doesn't even do anything close to what you need. By joining up to 10 rows with up to another 10 rows, you get up to 100 rows back. There's also no reason to self join just to combine filters.
Consider instead window queries. In particular, we have the dense_rank function, which can number every row in the result set according to groups:
SELECT
invoice_month,
time_of_month,
ARRAY_AGG(id) invoice_ids
FROM (
SELECT
id,
invoice_month,
-- Categorize as end or beginning of month
CASE
WHEN month_rank <= 10 THEN 'beginning'
WHEN month_reverse_rank <= 10 THEN 'end'
ELSE 'bug' -- Should never happen. Just a fall back in case of a bug.
END AS time_of_month
FROM (
SELECT
id,
invoice_month,
dense_rank() OVER (PARTITION BY invoice_month ORDER BY invoice_date) month_rank,
dense_rank() OVER (PARTITION BY invoice_month ORDER BY invoice_date DESC) month_rank_reverse
FROM (
SELECT
id,
invoice_date,
to_char(invoice_date, 'Mon-yy') AS invoice_month
FROM public.invoice
WHERE invoice_date BETWEEN date '2017-10-01' AND date '2018-09-30'
) AS fiscal_year_invoices
) ranked_invoices
-- Get first and last 10
WHERE month_rank <= 10 OR month_reverse_rank <= 10
) first_and_last_by_month
GROUP BY
invoice_month,
time_of_month
Don't be intimidated by the length. This query is actually very straightforward; it just needed a few subqueries.
This is what it does logically:
Fetch the rows for the fiscal year in question
Assign a "rank" to the row within its month, both counting from the beginning and from the end
Filter out everything that doesn't rank in the 10 top for its month (counting from either direction)
Adds an indicator as to whether it was at the beginning or end of the month. (Note that if there's less than 20 rows in a month, it will categorize more of them as "beginning".)
Aggregate the IDs together
This is the tool set designed for the job you're trying to do. If really needed, you can adjust this approach slightly to get them into the same row, but you have to aggregate before joining the results together and then join on the month; you can't join and then aggregate.

Query that returns data for every Friday for 1 year

I have a table that contains the number of orders a company makes per day from a given vendor, along with other information. I want to write a query that returns the number of orders from a vendor along with other info for every Friday for 1 year - i have figured this out as follows:
with dt as
( select next_day(trunc(add_months(sysdate,-12)) + 7*(level-1),'FRI') d
from dual connect by level <= 53 )
select *
from dt, vendor where vendor.dt = dt.d
But, suppose there are 10 vendors in total; and on a given Friday, orders were placed only from 6 vendors. Then for the remaining 4 vendors, i want to re-run the query for a Thursday and so on. Any help is appreciated.
I don't know your tables' structures, so I'll show you an example how I would do it.
Say there is a table orders:
CREATE TABLE orders(
vendor varchar2(100), order_date date
);
then the query for this table could be:
SELECT to_char( order_date, 'IW' ) as Week_nbr,
vendor,
count(*) As number_of_orders
FROM (
SELECT t.*,
max( order_date ) OVER (Partition by vendor, to_char( order_date, 'IW' ))
As lastest_date_within_a_week
FROM orders t
) xx
WHERE order_date = lastest_date_within_a_week
GROUP BY to_char( order_date, 'IW' ),
vendor
ORDER BY 1,2
This line:
max( order_date ) OVER (Partition by vendor, to_char( order_date, 'IW' ))
As lastest_date_within_a_week
is looking for a latest (oldest) orders' date within each week for each vendor. If a vendor has only 2 orders in a given week, one in Monday, and second in Thursday, then this function returns a date of Thurdsay.
And here:
WHERE order_date = lastest_date_within_a_week
we are taking vendor's orders only from last day in a week.
Then the query is doing a simple GROUP BY week_nr, vendor and a COUNT(*)
A working demo: http://sqlfiddle.com/#!4/96df3/1
You can sum data for every single date showing the day of the week with to_char(sysdate,'d'). Then you wrap the query and sum for the max day of the week grouping by vendor, like this sum(imp) keep (dense_rank last order by day_of_week).
Watch out for NLS. Day of week depends on it

Join operation on two tables retrieving dates

My first query, retrieving date and hours worked from work_details of a given employee number in a given date.
SELECT date,
SEC_TO_TIME( SUM( TIME_TO_SEC( `total_hours` ) ) ) AS total
FROM `work_details`
WHERE employee_id='28'
and date between '2012-02-01'
and '2012-02-29'
GROUP BY DATE ORDER BY DATE
and the Second query retrieving date from table holy_date:
SELECT holy_date
from holiday
where holy_date between '2012-02-01' and '2012-02-29'
I need to combine results of the two queries in the correct date order.
I tried union operation,but dint get result.
How can I do it?
There are a few ways to achieve what you want.
This is not the documented way of doing it. But this should work.
SELECT date, total
FROM
(
SELECT date, SEC_TO_TIME( SUM( TIME_TO_SEC( `total_hours` ) ) ) AS total
FROM `work_details`
WHERE employee_id='28' AND date BETWEEN '2012-02-01' AND '2012-02-29'
GROUP BY date
UNION ALL
(
SELECT holy_date AS date, NULL AS total
FROM holiday
WHERE holy_date BETWEEN '2012-02-01' AND '2012-02-29'
)
) AS t
GROUP BY date
ORDER BY date