SQL Query to display % Change - sql

I have a database with sample data represented by Table 1 below. How do I write an SQL query to display them in either Table 2 or Table 3 format?
Table 1 Table 2
Date | Value Year | Week | Total Value | % Change
------------+------- ------+-----+--|---------------|----------
19/12/2011 | 60 2012 | 1 | 295 | 656.41%
20/12/2011 | 49 2012 | 0 | 39 | -80.98%
21/12/2011 | 42 2012 | 52 | 205 | -41.76%
22/12/2011 | 57 2011 | 51 | 352 |
23/12/2011 | 88
24/12/2011 | 18 Table 3
25/12/2011 | 38 Year | Week | SUM1 | Year | Week | SUM2 | % Change
26/12/2011 | 16 ------+--------+--------+--------+--------+--------+-----------
27/12/2011 | 66 2012 | 1 | 295 | 2012 | 0 | 39 | 656.41%
28/12/2011 | 21 2012 | 0 | 39 | 2011 | 52 | 205 | -80.98%
29/12/2011 | 79 2011 | 52 | 205 | 2011 | 51 | 352 | -41.76%
30/12/2011 | 7 2011 | 51 | 352 |
31/12/2011 | 16
01/01/2012 | 39
02/01/2012 | 17
03/01/2012 | 86
04/01/2012 | 55
05/01/2012 | 82
06/01/2012 | 0
07/01/2012 | 9
08/01/2012 | 46

My preference would be to run 1 query to aggregate Table 1 to the year/week level and then do the "% change" in another language, depending on your environment. However, if you truly needed a SQL-only solution, you could do something like this.
create table t1 as
select year(Date) as year, week(Date) as week, sum(Value) as totalvalue
from table1
group by year(Date) as year, week(Date) as week
order by Date desc
;
select a.year, a.month, a.totalvalue,
(a.totalvalue-b.totalvalue)/b.totalvalue as pct_change
from (
select year, month, totalvalue,
case when week>1 then week-1 else 52 end as prevweek,
case when week>1 then year else year-1 end as prevyear
from t1
) a
left outer join t1 b
on a.prevweek=b.week and a.prevyear =b.year
;

Related

SQL - Total Row Values Meeting Criteria

I have a table and I want to sum rows that meet a certain criteria.
Table looks like this:
| Product | Sales_Num | Week | Cost | Retail |
|:------- |:---------:|:----:|:----:| ------:|
| PLA | 45281 | 38 | 53 | 88 |
| PLA2 | 45281 | 38 | 3 | 4 |
| CR25 | 45281 | 38 | 99 | 250 |
| BA34 | 45281 | 38 | 74 | 99 |
| PLA | 40251 | 38 | 53 | 75 |
| PLA2 | 40251 | 38 | 2 | 5 |
| CR25 | 40251 | 38 | 99 | 200 |
| BA34 | 40251 | 38 | 74 | 88 |
I want to Calculate the RETAIL column WHERE Product IN ('PLA','PLA2') AND Week = 38 and Sales_Num = 45281
Essentially, I want to Add 88 + 4 (first 2 rows above meet criteria). I want to eventually turn this into a function where I pass in Product, Week, and Sales_Num and I write the Calculation, Sales_Num, and Week to another table.
I was able to sum the rows I want, but I want the output to be [Sales_Num],[Week],[Total_Retail]
SELECT (
(SUM(CASE WHEN [Product]='PLA' AND [Sales_Num]=45281 AND [WEEK]=38 THEN [Retail] END) +
SUM(CASE WHEN [Product]='PLA2' AND [Sales_Num]=45281 AND [WEEK]=38 THEN [Retail] END)
)
) AS Total_Retail
select Sales_Num
,week
,sum(Retail) as Total_Retail
from t
where Week = 38
and Sales_Num = 45281
and Product in('PLA', 'PLA2')
group by Sales_Num, week
Sales_Num
week
Total_Retail
45281
38
92
Fiddle
SELECT Sales_Num, [Week], SUM(Retail) AS Total_Retail
FROM #yourtable
WHERE [Week] = 38
AND Sales_Num = 45281
AND Product IN ('PLA', 'PLA2')
GROUP BY Sales_Num, [Week]

Replace a missing value SQL Server?

There is a table (SQL Server 2017) on sales of goods in stores, some records have no price.
+---------+-------------+---------+----------+-------+
| year_id | week_number | good_id | store_id | price |
+---------+-------------+---------+----------+-------+
| 2019 | 6 | 140629 | 2 | 199 |
+---------+-------------+---------+----------+-------+
| 2019 | 8 | 140629 | 2 | NULL |
+---------+-------------+---------+----------+-------+
| 2017 | 40 | 137233 | 9 | 278 |
+---------+-------------+---------+----------+-------+
| 2017 | 35 | 137233 | 9 | NULL |
+---------+-------------+---------+----------+-------+
| 2017 | 37 | 137233 | 9 | NULL |
+---------+-------------+---------+----------+-------+
We would like to replace the missing values according to the following scheme: set the price value to the same as the good with this number (good_id) from the same store (store_id), but sold as far as possible in the nearest to the missing value date, for example:
+---------+-------------+---------+----------+-------+
| year_id | week_number | good_id | store_id | price |
+---------+-------------+---------+----------+-------+
| 2019 | 6 | 140629 | 2 | 199 |
+---------+-------------+---------+----------+-------+
| 2019 | 8 | 140629 | 2 | 199 |
+---------+-------------+---------+----------+-------+
| 2017 | 40 | 137233 | 9 | 278 |
+---------+-------------+---------+----------+-------+
| 2017 | 35 | 137233 | 9 | 278 |
+---------+-------------+---------+----------+-------+
| 2017 | 37 | 137233 | 9 | 278 |
+---------+-------------+---------+----------+-------+
So far made something like this, but this query contains mutually exclusive conditions, so it does not affect the rows:
UPDATE dataset
SET price = p.price
FROM dataset AS p
WHERE good_id = p.good_id
AND store_id = p.store_id
AND price IS NULL
AND p.price IS NOT NULL;
GO
You can use apply. This works if all years have 52 weeks:
update d
set price = d2.price
from dataset d cross apply
(select top (1) d2.*
from dataset d2
where d2.good_id = d.good_id and
d2.store_id = d.store_id and
d2.price is not null
order by abs( (d2.year_id * 52 + d2.week_id) - (d.year_id * 52 + d.week_id) )
) d2
where d.price is null;
The only issue is when the comparisons pass the year boundary and the previous year has 53 weeks. Depending on how you define years, you can convert the year/week combos in to dates and use direct date comparisons for the difference.

get records where one colum has values within range across records with same column names

with a table like below
+------+-----+------+----------+-----------+
| city | day | hour | car_name | car_count |
+------+-----+------+----------+-----------+
| 1 | 12 | 00 | corolla | 8 |
| 1 | 12 | 00 | city | 9 |
| 1 | 12 | 00 | amaze | 3 |
| 1 | 13 | 00 | corolla | 17 |
| 1 | 13 | 00 | city | 2 |
| 1 | 13 | 00 | amaze | 8 |
| 1 | 14 | 00 | corolla | 3 |
| 1 | 14 | 00 | amaze | 1 |
+------+-----+------+----------+-----------+
need to find out the city, day, hour where the car_count for all car_names is >= 3 and <= 10
expected result
| city | day | hour |
+------+-----+------+
| 1 | 12 | 00 |
Use group by and having.
select city,day,hour
from tablename
group by city,day,hour
having sum(case when car_count>=3 and car_count<=10 then 1 else 0 end) = count(*)
select city, day, hour
from t
group by 1, 2, 3
having bool_and(car_count >= 3)
You can group by on city, day and hour with the having condition sum(your condition) = count(your condition)
So basically we are creating a flag for each row which satisfies the condition "10 >= car_count >= 3" . Now we are summing all the flags and counting them simultaneously, if both the count and sum are equal that means your condition "10 >= car_count >= 3" was true for all the cars against city,day and hour
create table want as
select city,day,hour from have
group by city,day,hour
having sum(car_count>=3 and car_count<=10)=count(car_count>=3 and car_count<=10);
Please let me know in case of any queries.

Checking for Consecutive 12 Weeks of 0 Sales

I have a table with customer_number, week, and sales. I need to check if there were 12 consecutive weeks of no sales for each customer and create a flag of 0/1.
I can check the last 12 weeks or a certain time frame, but what's the best way to check for consecutive runs? Here is the code I have so far:
select * from weekly_sales
where customer_nbr in (123, 234)
and week < '2015-11-01'
and week > '2014-11-01'
order by customer_nbr, week
;
Sql Fiddle Demo
Here is a simplify version only need a week_id and sales
SELECT S1.weekid start_week, MAX(S2.weekid) end_week, SUM (S2.sales)
FROM Sales S1
JOIN Sales S2
ON S2.weekid BETWEEN S1.weekid and S1.weekid + 11
WHERE S1.weekid BETWEEN 1 and 25 -- your search range
GROUP BY S1.weekid
Let me know if that work for you
OUTPUT
| start_week | end_week | |
|------------|----------|----|
| 1 | 12 | 12 |
| 2 | 13 | 8 |
| 3 | 14 | 3 |
| 4 | 15 | 2 |
| 5 | 16 | 0 | <-
| 6 | 17 | 0 | <- no sales for 12 week
| 7 | 18 | 0 | <-
| 8 | 19 | 4 |
| 9 | 20 | 9 |
| 10 | 21 | 11 |
| 11 | 22 | 15 |
| 12 | 23 | 71 |
| 13 | 24 | 78 |
| 14 | 25 | 86 |
| 15 | 25 | 86 | < - less than 12 week range
| 16 | 25 | 86 | < - below this line
| 17 | 25 | 86 |
| 18 | 25 | 86 |
| 19 | 25 | 86 |
| 20 | 25 | 82 |
| 21 | 25 | 77 |
| 22 | 25 | 75 |
| 23 | 25 | 71 |
| 24 | 25 | 15 |
| 25 | 25 | 8 |
Your final query should have
HAVING SUM (S2.sales) = 0
AND COUNT(*) = 12
Ummmmm...You could use between 'week' and 'week', and you can use too the "count(column)" in order to improve performance.
So you only have to compare if result is bigger than 0

Oracle query latest row with average for specific column?

Having the following dataset. I need some help with a sql statement that would give me the latest row based on PING_DATE with unique PING_DESTINATION and PING_SOURCE with added column with the AVG of PING_AVG for all rows within the last 10 minutes.
PING_DATE | PACKET_LOSS | PING_MIN | PING_AVG | PING_MAX | PING_SOURCE | PING_DESTINATION
-------------------------------------------------------------------------------------------------------
5/5/2015 12:58:18 PM | 0 | 68 | 68 | 72 | site1 | orange15
5/5/2015 12:58:43 PM | 0 | 68 | 71 | 76 | site1 | orange15
5/5/2015 12:59:11 PM | 0 | 68 | 68 | 72 | site1 | pear11
5/5/2015 1:09:47 PM | 0 | 68 | 70 | 76 | site1 | pear11
5/5/2015 1:43:59 PM | 0 | 68 | 69 | 72 | site1 | pear11
5/5/2015 1:45:41 PM | 0 | 68 | 69 | 72 | site1 | pear11
5/5/2015 2:03:43 PM | 0 | 68 | 68 | 72 | site1 | pear11
5/5/2015 3:01:53 PM | 0 | 68 | 68 | 72 | site1 | pear11
5/5/2015 3:02:05 PM | 0 | 68 | 69 | 72 | site1 | pear11
5/5/2015 3:00:59 PM | 20 | 68 | 68 | 68 | site1 | pear11
5/5/2015 3:01:07 PM | 0 | 68 | 68 | 72 | site1 | pear11
5/5/2015 3:01:14 PM | 0 | 68 | 70 | 72 | site1 | pear11
5/5/2015 12:46:55 PM | 3 | 3 | 3 | 3 | site1 | lemon1
Query Result:
PING_DATE | PACKET_LOSS | PING_MIN | PING_AVG | PING_MAX | PING_SOURCE | PING_DESTINATION | 10minavg
------------------------------------------------------------------------------------------------------------------
5/5/2015 12:58:43 PM | 0 | 68 | 71 | 76 | site1 | orange15 | 71
5/5/2015 3:01:14 PM | 0 | 68 | 70 | 72 | site1 | pear11 | 65
5/5/2015 12:46:55 PM | 3 | 3 | 3 | 3 | site1 | lemon1 | 3
For "last 10 minutes average" being "last 10 minutes in each group" this is the query you are looking for:
with xyz as (
select X.*,
row_number() over (
partition by ping_destination, ping_source
order by ping_date desc
) as latest_row#,
avg(ping_avg) over (
partition by ping_destination, ping_source
order by ping_date asc
range between
interval '10' minute preceding
and current row
) as the_10_min_avg
from ping_table X
)
select *
from xyz
where latest_row# = 1
;
For "last 10 minutes average" being "from 10 minutes ago until now" this is the query you are looking for:
with xyz as (
select X.*,
row_number() over (
partition by ping_destination, ping_source
order by ping_date desc
) as latest_row#,
avg(ping_avg) over (
partition by ping_destination, ping_source
) as the_10_min_avg
from ping_table X
where X.ping_date >= systimestamp - interval '10' minute
)
select *
from xyz
where latest_row# = 1
;
Something like this:
SELECT DISTINCT ping_source,
first_value(ping_date) over (partition by ping_source order by ping_date desc),
first_value(packet_loss) over (partition by ping_source order by ping_date desc)
-- ...
FROM data
CROSS JOIN
select avg(ping_avg) from data
where (sysdate - ping_date) * 24 * 60 < 10;
Here is a straight forward query based on the question.
Edited based on the sample output. For last 10 minutes from now, use systemtimestamp instead of i.latest_ping in the snippet "(i.latest_ping - interval '10' minute)". Use i.latest_ping for last 10 minutes from max_ping_time for that source-dest pair.
select
o.*,
(select avg(ping_avg) from ping_info a
where a.ping_source = i.ping_source
and a.ping_dest = i.ping_dest
and a.ping_date >= (systemtimestamp - interval '10' minute)
) last_10min_avg
from ping_info o,
(select ping_source, ping_dest, max(ping_date) latest_ping
from ping_info
group by ping_source, ping_dest) i
where o.ping_source = i.ping_source
and o.ping_dest = i.ping_dest
and o.ping_date = i.latest_ping;