Is there a way to find percentage difference between 2 different days for a specific group? - sql

I need to calculate the percentage difference between 2 days for a specific group in my table.
I need percentage change of column [Group] with the field 'FUND' between days 20190731 and 20190628 by using the column Value. In my example should be -75%.

I hope this works for you:
select prd, grp, 100.0 * (sum(end_value) - sum(start_value)) / sum(start_value)
from
(
select prd, grp,
(case when date = "20190731" then val else 0 end) as end_value,
(case when date = "20190628" then val else 0 end) as start_value
from table1
) as t
group by prd, grp

Related

CASE WHEN condition with MAX() function

There are a lot questions on CASE WHEN topic, but the closest my question is related to this How to use CASE WHEN condition with MAX() function query which has not been resolved.
Here is some of my sample data:
date
debet
2022-07-15
57190.33
2022-07-14
815616516.00
2022-07-15
40866.67
2022-07-14
1221510.00
So, I want to all records for the last two dates and three additional columns: sum(sales) for the previous day, sum for the current day and the difference between them:
SELECT
[debet],
[date] ,
SUM( CASE WHEN [date] = MAX(date) THEN [debet] ELSE 0 END ) AS sum_act,
SUM( CASE WHEN [date] = MAX(date) - 1 THEN [debet] ELSE 0 END ) AS sum_prev ,
(
SUM( CASE WHEN [date] = MAX(date) THEN [debet] ELSE 0 END )
-
SUM( CASE WHEN [date] = MAX(date) - 1 THEN [debet] ELSE 0 END )
) AS diff
FROM
Table
WHERE
[date] = ( SELECT MAX(date) FROM Table WHERE date < ( SELECT MAX(date) FROM Table) )
OR
[date] = ( SELECT MAX(date) FROM Table WHERE date = ( SELECT MAX(date) FROM Table ) )
GROUP BY
[date],
[debet]
Further, of course, it informs that I can't use the aggregate function inside CASE WHEN. Now I use this combination: sum(CASE WHEN [date] = dateadd(dd,-3,cast(getdate() as date)) THEN [debet] ELSE 0 END). But here every time I need to make an adjustment for weekends and holidays. The question is, is there any other way than using 'getdate' in 'case when' Statement to get max date?
Expected result:
date
sum_act
sum_prev
diff
2022-07-15
97190.33
0.00
97190.33
2022-07-14
0.00
508769.96
-508769.96
You can use dense_rank() to filter the last 2 dates in your table. After that you can use either conditional case expression with sum() to calculate the required value
select [date],
sum_act = sum(case when rn = 1 then [debet] else 0 end),
sum_prev = sum(case when rn = 2 then [debet] else 0 end),
diff = sum(case when rn = 1 then [debet] else 0 end)
- sum(case when rn = 2 then [debet] else 0 end)
from
(
select *, rn = dense_rank() over (order by [date] desc)
from tbl
) t
where rn <= 2
group by [date]
db<>fiddle demo
Two steps:
Get the sums for the last three dates
Show the results for the last two dates.
Well, we could also get all daily sums in step 1, but we just need the last three in order to calculate the sums for the last two days, so why aggregate more data than necessary?
Here is the query. You may have to put the date column name in brackets in SQL Server, as date is a keyword in SQL.
select top(2)
date,
sum_debit_current,
sum_debit_previous,
sum_debit_current - sum_debit_previous as diff
(
select
date,
sum(debet) as sum_debit_current,
lag(sum(debet)) over (order by date) as sum_debit_previous
from table
where date in (select distinct top(3) date from table order by date desc)
group by date
)
order by date desc;
(SQL Server uses TOP(n) instead of standard SQL FETCH FIRST 3 ROWS and while SELECT DISTINCT TOP(3) date looks like "get the top 3 rows, then apply distinct on their date", it is really "apply distinct on the dates, then get the top 3" like in standard SQL.)

SQL for begin and end of data rows

I've got the following table:
and I was wondering if there is an SQL query, which would give me the begin and end Calender week (CW), where the value is greater than 0.
So in the case of the table above, a result like below:
Thanks in advance!
You can assign a group by counting the number of zeros and then aggregating:
select article_nr, min(year), max(year)
from (select t.*,
sum(case when amount = 0 then 1 else 0 end) over (partition by article_nr order by year) as grp
from t
) t
where amount > 0
group by article_nr, grp;
select Atricle_Nr, min(Year&CW) as 'Begin(Year&CW)',max(Year&CW) as 'End(Year&CW)'
from table where Amount>0 group by Atricle_Nr;

Conditional CASE WHEN select snowflake SQL

I am stuck on a conditional snowflake select sql. I am trying to count the IDs when they have the corresponding categorial value. I would appreciate some help.
Thanks
SELECT
YEAR(DATETIME) AS YEAR,
WEEKOVERYEAR(DATETIME) AS WEEK,
COUNT(CASE WHEN ID THEN CATEGORY = 'A')
from table
group by week, year;
Here is one method:
SELECT YEAR(DATETIME) AS YEAR,
WEEKOVERYEAR(DATETIME) AS WEEK,
SUM(CASE WHEN CATEGORY = 'A' THEN 1 ELSE 0 END) as num_a
FROM table
GROUP BY week, year;
Snowflake supports COUNT_IF:
Returns the number of records that satisfy a condition.
Aggregate function
COUNT_IF( <condition> )
SELECT YEAR(DATETIME) AS YEAR,
WEEKOVERYEAR(DATETIME) AS WEEK,
COUNT_IF(CATEGORY = 'A') AS num_a
FROM tab
GROUP BY week, year;
You should / can use IFF() since case when is more suitable when there are multiple conditions.
SELECT
YEAR(DATETIME) AS YEAR,
WEEKOVERYEAR(DATETIME) AS WEEK,
COUNT(IFF(CATEGORY = 'A',ID,NULL)) as count
from table
group by week, year;
COUNT() counts the number of rows that are not null.
If you are want when ID is not null AND CATEGORY = 'A' then
COUNT(CASE WHEN ID IS NOT NULL AND CATEGORY = 'A' THEN TRUE ELSE NULL END)
will give you that, or you can use a SUM like in Gordon's answer
SUM(CASE WHEN ID IS NOT NULL AND CATEGORY = 'A' THEN 1 ELSE 0 END)
or you can use the snowflake IFF as a shorter form for the same thing, which is how I do it
SUM( IFF( ID IS NOT NULL AND CATEGORY = 'A', 1, 0))

MSSQL Group by and Select rows from grouping

I'm trying to figure out if what I'm trying to do is possible. Instead of resorting to multiple queries on a table, I wanted to group the records by business date and id then group by the id and select one date for a field and another date for the other field.
SELECT
*
{AMOUNT FROM DATE}
{AMOUNT FROM OTHER DATE}
FROM (
SELECT
date,
id,
SUM(amount) AS amount
FROM
table
GROUP BY id, date
AS subquery
GROUP BY id
It seems that you're looking to do a pivot query. I usually use cross tabs for this. Based on the query you posted, it could look like:
SELECT
id,
SUM(CASE WHEN date = '20190901' THEN amount ELSE 0 END) AmountFromSept01,
SUM(CASE WHEN date = '20191001' THEN amount ELSE 0 END) AmountFromOct01
FROM (
SELECT
date,
id,
SUM(amount) AS amount
FROM
table
GROUP BY id, date
)AS subquery
GROUP BY id;
You could also use a CTE.
WITH CTE AS(
SELECT
date,
id,
SUM(amount) AS amount
FROM
table
GROUP BY id, date
)
SELECT
id,
SUM(CASE WHEN date = '20190901' THEN amount ELSE 0 END) AmountFromSept01,
SUM(CASE WHEN date = '20191001' THEN amount ELSE 0 END) AmountFromOct01
FROM CTE
GROUP BY id;
Or even be a rebel and do the operation directly.
SELECT
id,
SUM(CASE WHEN date = '20190901' THEN amount ELSE 0 END) AmountFromSept01,
SUM(CASE WHEN date = '20191001' THEN amount ELSE 0 END) AmountFromOct01
FROM CTE
GROUP BY id;
However, some people have tested for performance and found that pre-aggregating can improve performance.
If I understand you correctly, then you're just trying to pivot, but only with two particular dates:
select id,
date1 = sum(iif(date = '2000-01-01', amount, null)),
date2 = sum(iif(date = '2000-01-02', amount, null))
from [table]
group by id

SQL Server 2008 Combine two rows into one

I have written pretty straightforward queries so far, so I am now looking a help to write a SQL statement so that it will combine two separate period end rows from a table into one row. The rows are basically can be matched by their PId, Region, Market, Code, Source. For example-
if 1st row is:
Id Region Market CODE Source Period_End Amt Pct
100 CAN CABLE V1 SA 20120930 100.00 0.2
and 2nd row is:
Id Region Market CODE Source Period_End Amt Pct
100 CAN CABLE V1 SA 20121231 200.00 0.5
Then the SQL should return this result:
Id Region Market CODE Source Period_End_1 Amt_1 Pct_1 Period_End_2 Amt_2 Pct_2
100 CAN CABLE V1 SA 20120930 100.00 0.2 20121231 200.00 0.5
Your help is really appreciated.
Ana.
Thanks for your responses. This is what I started with but I am not sure if I am on right direction or not. I also noticed as I would add more and more information to the row based on Period End then the below query would be too long with redundant "case condition" in each select.
select
A.id , A.region, A.market, A.code, A.source ,
case when period_end = #day_id1 then period_end else '' end as Period_End_1,
case when period_end = #day_id2 then period_end else '' end as Period_End_2,
case when period_end = #day_id1 then Amt else 0.0 end as Amt_1,
case when period_end = #day_id2 then Amt else 0.0 end as Amt_2,
case when period_end = #day_id1 then Pct else 0.0 end as Pct_1,
case when period_end = #day_id2 then pct else 0.0 end as Pct_2,
from
products A with (nolock)
where
A.product_id in (select product_id from #products) -- temp table holding multiple Ids
If I'm understanding your question correctly, you're trying to pivot multiple rows into multiple columns.
Assuming it's always 2 rows you're trying to combine, using the period_end field to order the first from the second, then something like this should work using max with case to pivot your results:
WITH CTE AS (
SELECT *,
Row_Number() Over (Partition By Id, Region, Market, Code, Source
Order By Period_End) rn
FROM YourTable
)
SELECT Id,
Region,
Market,
Code,
Source,
max(case when rn = 1 then Period_End end) Period_End_1,
max(case when rn = 1 then Amt end) Amt_1,
max(case when rn = 1 then Pct end) Pct_1,
max(case when rn = 2 then Period_End end) Period_End_2,
max(case when rn = 2 then Amt end) Amt_2,
max(case when rn = 2 then Pct end) Pct_2
FROM CTE
GROUP BY Id, Region, Market, Code, Source
If you have more potential period_end dates, then you might need to use dynamic sql to achieve your results.
SELECT t1.Id
,t1.Region
,t1.Market
,t1.CODE
,t1.Source
,t1.Period_End AS Period_End_1
,t1.Amt AS Amt_1
,t1.Pct AS Pct_1
,t2.Period_End AS Period_End_2
,t2.Amt AS Amt_2
,t2.Pct AS Pct_2
FROM Table_Name t1
INNER JOIN TABLE_Name t2 ON t1.ID = t2.ID
WHERE t1.ID = 100 AND t1.Period_End <> t2.Period_End