select half of partition by for a sum() - sql

I have a query that is calculating the sum of a partition by and giving me a running total by a category.
this part works well, now, I would like the sum of only the top 50% of the partition by.
maybe a table example will show:
╔═══════╦══════════════════════════╦════════════════════════════╗
║ col_1 ║ sum of partition by ║ sum of 50% of partition by ║
╠═══════╬══════════════════════════╬════════════════════════════╣
║ 1 ║ 36 (this is 1+2+3+...8) ║ 10 (1+2+3+4) ║
╠═══════╬══════════════════════════╬════════════════════════════╣
║ 2 ║ 35 (this is 2+3+4+....8) ║ 9 (2+3+4) ║
╠═══════╬══════════════════════════╬════════════════════════════╣
║ 3 ║ 34 ║ 7 (3+4) ║
╠═══════╬══════════════════════════╬════════════════════════════╣
║ 4 ║ 33 ║ 4 ║
╠═══════╬══════════════════════════╬════════════════════════════╣
║ 5 ║ 32 ║ null ║
╠═══════╬══════════════════════════╬════════════════════════════╣
║ 6 ║ 31 ║ null ║
╠═══════╬══════════════════════════╬════════════════════════════╣
║ 7 ║ 30 ║ null ║
╠═══════╬══════════════════════════╬════════════════════════════╣
║ 8 ║ 29 ║ null ║
╚═══════╩══════════════════════════╩════════════════════════════╝
right now I'm doing
sum(col_) over(partition by <another col> order by <a third col>) as [sum of partition by ]
then I later need to add another column for this calculation over the 25% so you get the idea.

You can use conditional logic by enumerating the rows and filtering. The following uses standard SQL syntax:
select x,
sum(x) over (order by x desc),
sum(x) filter (where seqnum <= 0.5 * cnt) over (order by x desc),
sum(x) filter (where seqnum <= 0.25 * cnt) over (order by x desc)
from (select x, count(*) over () as cnt,
row_number() over (order by x) as seqnum
from generate_series(1, 8, 1) gs(x)
) x
order by x;
Here is a db<>fiddle.
Although standard, Postgres is the only database that supports filter. The logic can easily be replaced with sum(case . . .).
Here is a db<>fiddle using SQL Server instead. The corresponding code is:
with gs as (
select 1 as x union all
select x + 1 from gs where x < 8
)
select x,
sum(x) over (order by x desc),
sum(case when seqnum <= 0.5 * cnt then x end) over (order by x desc),
sum(case when seqnum <= 0.25 * cnt then x end) over (order by x desc)
from (select x, count(*) over () as cnt,
row_number() over (order by x) as seqnum
from gs
) x
order by x;

Related

select query to get result by merging two columns

I have a table like below :
Id Price1 Price2
3 30 20
3 40 20
3 50 20
I want to write a query to get a below result :
Desired Ouput :
RowNo Id Price
1 3 20
2 3 30
3 3 40
4 3 50
Please help!!
Use Cross apply to unpivot the data and generate row number
SELECT Row_number()OVER(ORDER BY price) AS Rowno,*
FROM (SELECT DISTINCT Id,
Price
FROM (VALUES (3,30,20),
(3,40,20),
(3,50,20) ) tc ( Id, Price1, Price2)
CROSS apply (VALUES (Price1),
(Price2)) Cs (Price)) A
Result :
╔═══════╦════╦═══════╗
║ Rowno ║ Id ║ Price ║
╠═══════╬════╬═══════╣
║ 1 ║ 3 ║ 20 ║
║ 2 ║ 3 ║ 30 ║
║ 3 ║ 3 ║ 40 ║
║ 4 ║ 3 ║ 50 ║
╚═══════╩════╩═══════╝
You can use union to combine the rows (so duplicates are removed). And then row_number() to calculate rownum:
select row_number() over (order by price) as rownum, id, price
from ((select id, price1 as price from t) union
(select id, price2 from t
) t
order by price;

sql sorting by subgroup sum data

How sort this
a 1 15
a 2 3
a 3 34
b 1 55
b 2 44
b 3 8
to (by third column sum):
b 1 55
b 2 44
b 3 8
a 1 15
a 2 3
a 3 34
since (55+44+8) > (15+3+34)
If you are using SQL Server/Oracle/Postgresql you could use windowed SUM:
SELECT *
FROM tab
ORDER BY SUM(col3) OVER(PARTITION BY col) DESC, col2
LiveDemo
Output:
╔═════╦══════╦══════╗
║ col ║ col2 ║ col3 ║
╠═════╬══════╬══════╣
║ b ║ 1 ║ 55 ║
║ b ║ 2 ║ 44 ║
║ b ║ 3 ║ 8 ║
║ a ║ 1 ║ 15 ║
║ a ║ 2 ║ 3 ║
║ a ║ 3 ║ 34 ║
╚═════╩══════╩══════╝
You can do this using ANSI standard window functions. I prefer to use a subquery although this is not strictly necessary:
select col1, col2, col3
from (select t.*, sum(col3) over (partition by col1) as sumcol3
from t
) t
order by sumcol3 desc, col3 desc;
...and an example how to do it without windowing functions, in for example MySQL (but also in just about any other standard SQL version)
SELECT m.col1, m.col2, m.col3
FROM myTable m
JOIN (
SELECT col1, SUM(col3) groupsum FROM myTable GROUP BY col1
) z ON m.col1 = z.col1
ORDER BY z.groupsum DESC, col2;
Basically, calculate the group sum in a subquery and join/order the results by the group's sum descending.
An SQLfiddle to test with.

How to get New, Existing and Inactive users from table

For example, Below is input table which has Month & User
Output Required:
NewUsers are new in that month. ExistingUsers are users in that month which have some data in previous month as well. Inactive users are users active in previous month but not in current month
Is it possible?
You can use windowed function to achieve that:
New User is very easy COUNT rows that have rn = 1
Existing Users: easy too, COUNT rows that have rn > 1
Inactive Users: bit complicated (get sum of new + existing and substract (new + existing) from row before.
Code:
WITH cte AS
(
SELECT *
,rn = ROW_NUMBER() OVER (PARTITION BY UserKey ORDER BY MonthId)
FROM #tab t1
), cte2 AS(
SELECT
MonthId,
[New_User] = COUNT(CASE WHEN rn = 1 THEN 1 END),
[Existing_User] = COUNT(CASE WHEN rn > 1 THEN 1 END),
[s] = COUNT(rn)
FROM cte
GROUP BY MonthId
)
SELECT
MonthId,
[New_User],
[Existing_User],
[Inactive_User] = CASE WHEN [s] - LAG(s, 1) OVER(ORDER BY MonthId) < 0
THEN ABS([s] - LAG(s, 1) OVER(ORDER BY MonthId))
ELSE 0
END
FROM cte2
ORDER BY MonthId;
LiveDemo
Output:
╔═════════╦═══════════╦════════════════╦════════════════╗
║ MonthID ║ New_Users ║ Existing_Users ║ Inactive_Users ║
╠═════════╬═══════════╬════════════════╬════════════════╣
║ 201411 ║ 1 ║ 0 ║ 0 ║
║ 201412 ║ 1 ║ 1 ║ 0 ║
║ 201501 ║ 1 ║ 2 ║ 0 ║
║ 201502 ║ 0 ║ 2 ║ 1 ║
╚═════════╩═══════════╩════════════════╩════════════════╝
Warning:
I've assumed that data per each MonthId is UNIQUE if not add one more CTE step to remove duplicates first.

SQL Grouping Integers by Range

I have integer values: (199903, 199908, 201203, 201408, 201410, 201501, 201503)
and I would like to group these integers by integers falling within a range of 3.
In this example the grouping would be the following:
199903 (group 1)
199908 (group 2)
201203 (group 3)
201408 (group 4)
201410 (group 4)
201501 (group 5)
201503 (group 5)
You can use windowed function DENSE_RANK:
LiveDemo
CREATE TABLE #mytable(val INTEGER);
INSERT INTO #mytable(val)
VALUES(199903),(199908),(201203),(201408),(201410),(201501),(201503);
SELECT
val,
[group] = DENSE_RANK() OVER (ORDER BY val/3)
FROM #mytable;
Output:
╔════════╦═══════╗
║ val ║ group ║
╠════════╬═══════╣
║ 199903 ║ 1 ║
║ 199908 ║ 2 ║
║ 201203 ║ 3 ║
║ 201408 ║ 4 ║
║ 201410 ║ 4 ║
║ 201501 ║ 5 ║
║ 201503 ║ 5 ║
╚════════╩═══════╝
I suspect you mean sequences that differ by three or less. So, a new period starts when the difference is greater than 3. In SQL Server 2012+, you can use lag() for this. In SQL Server 2008, here is one way:
with t as (
select t.*,
(case when t.val - tprev.val < 3 then 0 else 1 end) as IsGroupStart
from table t outer apply
(select top 1 t2.val
from table t2
where t2.val < t.val
order by t2.val desc
) tprev
) t
select t.val, t2.grp
from t outer apply
(select sum(IsGroupStart) as grp
from t t2
where t2.val <= t.val
) t2;

How to sort a column based on length of data in it in SQL server

As we all know general sorting is using order by. The sort I want to perform is different. I want the smallest length value in middle of table n the largest ones in top and bottom of it. One half should be descending and another half should be ascending. Can you guys help. It was an interview question.
This is one way:
;WITH CTE AS
(
SELECT *,
RN = ROW_NUMBER() OVER(ORDER BY LEN(YourColumn))
FROM dbo.YourTable
)
SELECT *
FROM CTE
ORDER BY RN%2, (CASE WHEN RN%2 = 0 THEN 1 ELSE -1 END)*RN DESC
Test Data
DECLARE #Table TABLE
(ID INT, Value VARCHAR(10))
INSERT INTO #Table VALUES
(1 , 'A'),
(2 , 'AB'),
(3 , 'ABC'),
(4 , 'ABCD'),
(5 , 'ABCDE'),
(6 , 'ABCDEF'),
(7 , 'ABCDEFG'),
(8 , 'ABCDEFGI'),
(9 , 'ABCDEFGIJ'),
(10 ,'ABCDEFGIJK')
Query
;WITH CTE AS (
SELECT *
,NTILE(2) OVER (ORDER BY LEN(Value) DESC) rn
FROM #Table )
SELECT *
FROM CTE
ORDER BY CASE WHEN rn = 1 THEN LEN(Value) END DESC
,CASE WHEN rn = 2 THEN LEN(Value) END ASC
Result
╔════╦════════════╦════╗
║ ID ║ Value ║ rn ║
╠════╬════════════╬════╣
║ 10 ║ ABCDEFGIJK ║ 1 ║
║ 9 ║ ABCDEFGIJ ║ 1 ║
║ 8 ║ ABCDEFGI ║ 1 ║
║ 7 ║ ABCDEFG ║ 1 ║
║ 6 ║ ABCDEF ║ 1 ║
║ 1 ║ A ║ 2 ║
║ 2 ║ AB ║ 2 ║
║ 3 ║ ABC ║ 2 ║
║ 4 ║ ABCD ║ 2 ║
║ 5 ║ ABCDE ║ 2 ║
╚════╩════════════╩════╝
Here's a short approach that would ge t you started:
WITH cte AS
(
SELECT TOP 1000 number
FROM master..spt_values
WHERE type = 'P' and number >0
)
SELECT number, row_number() OVER(ORDER BY CASE WHEN number %2 = 1 THEN number ELSE -(number) END) pos
FROM cte