How to produce a circular shift with postgreSQL - sql

Let's suppose that I have this simple table:
ID
1
2
3
4
If I do:
WITH example AS
(
SELECT UNNEST(ARRAY[1,2,3,4]) as ID
)
SELECT ID, lAG(ID,1) over() as LAG_ID
FROM example
-- The shift is 1 in this case, but could be any integer in practice.
I got:
ID
LAG_ID
1
2
1
3
2
4
3
But I need a circular shift:
ID
LAG_ID
1
4
2
1
3
2
4
3
Is there an elegant way to produce this result ?

If ID is not nullable
WITH example AS
(
SELECT UNNEST(ARRAY[1,2,3,4]) as ID
)
SELECT ID, coalesce(lAG(ID,1) over(order by ID asc), (select max(ID) from example)) as LAG_ID
FROM example

Related

How to find the most frequently repeated column?

ID UserID LevelID
1 1 1
2 1 2
3 1 2
4 1 2
5 2 1
6 2 3
7 3 2
8 4 1
9 4 1
The query should return: LevelID: 1 (3 times) - the LevelID column that is most frequently repeated by different Users (UserID).
I have the following query:
SELECT LevelID, COUNT(LevelID) AS 'Occurrence'
FROM
(
SELECT DISTINCT * FROM
(
SELECT UserID, LevelID
FROM SampleTable
) cv
) levels
GROUP BY LevelID
ORDER BY 'Occurrence' DESC
Which returns:
LevelID Occurence
1 3
2 2
3 1
But it doesn't let me to add LIMIT 1; at the bottom to retrieve the first top row of the selection. What's wrong with the query?
There is no need for these several levels of nesting. Consider using aggregation, count(distinct ...), ordering the results and using a row-limiting clause to keep the top record only:
select top(1) levelID, count(distinct userID) cnt
from mytable
group by levelID
order by cnt desc
If you want to allow possible top ties, then use top (1) with ties instead of just top (1).

SQL group by multiple fields get first occurrence

I have this table (sales_lines):
id sale_id sale_seq_id other_fields
----------------------------------------
1 1 1
2 1 2
3 2 1
4 3 1
5 3 2
But this table can have a duplicated sale_seq_id (yes, it's an error). Like this:
id sale_id sale_seq_id other_fields
----------------------------------------
1 1 1
2 1 2
3 1 2
4 2 1
5 3 1
6 3 1
7 3 2
Lines 3 and 6 are errors, so I should discard them.
How can I do it?
To delete the wrong records do
delete from sales_lines
where id not in
(
select min(id)
from sales_lines
group by sale_id, sale_seq_id
)
To just delete the correct data do
select min(id), sale_id, sale_seq_id
from sales_lines
group by sale_id, sale_seq_id
I would use correlated sub-query :
select sl.*
from sales_line sl
where sl.id = (select min(sl1.id)
from sales_line sl1
where sl1.sale_id = sl.sale_id and
sl1.sale_seq_id = sl.sale_seq_id
);
If your DBMS supports window function then you can do :
select sl.*
from (select sl.*,
row_number() over (partition by sl.sale_id, sl.sale_seq_id order by sl.id) as seq
from sales_line sl
) sl
where seq = 1;
By this way, you will get full row with other fields too.

sql - select single ID for each group with the lowest value

Consider the following table:
ID GroupId Rank
1 1 1
2 1 2
3 1 1
4 2 10
5 2 1
6 3 1
7 4 5
I need an sql (for MS-SQL) select query selecting a single Id for each group with the lowest rank. Each group needs to only return a single ID, even if there are two with the same rank (as 1 and 2 do in the above table). I've tried to select the min value, but the requirement that only one be returned, and the value to be returned is the ID column, is throwing me.
Does anyone know how to do this?
Use row_number():
select t.*
from (select t.*,
row_number() over (partition by groupid order by rank) as seqnum
from t
) t
where seqnum = 1;

Hive: window function - how to exclude the CURRENT ROW

I wish to calculate the minimum of a value over a partition, but the current row should not be taken into account.
SELECT *,
MIN(val) OVER(PARTITION BY col1)
FROM table
outputs the minimum over all rows in the partition.
The documentation shows ways to use CURRENT ROW, but not how to exclude it while performing the windowing operation.
I am looking for something like this:
SELECT *,
MIN(val) OVER(PARTITION BY col1 ROWS NOT CURRENT ROW)
FROM table
but this does not work.
I can think of a way to do this. The min over a window excluding the current row will always be the min over the window except when the row you are at is the min; then then min will be the 2nd min over the window. Example:
Data:
-----------
key | val
-----------
1 8
1 2
1 4
1 6
1 11
2 3
2 5
2 7
2 9
Query:
select key, val, act_min, val_arr
, case when act_min=val then val_arr[1] else act_min
end as min_except_for_c_row
from (
select key, val, act_min, sort_array(val_arr) val_arr
from (
select key, val
, min(val) over (partition by key) act_min
, collect_set(val) over (partition by key) val_arr
from db.table ) A
) B
I left all the columns in for illustration. You can modify the query as needed.
Output:
key val act_min val_arr min_except_for_c_row
1 8 2 [2,4,6,8,11] 2
1 2 2 [2,4,6,8,11] 4
1 4 2 [2,4,6,8,11] 2
1 6 2 [2,4,6,8,11] 2
1 11 2 [2,4,6,8,11] 2
2 3 3 [3,5,7,9] 5
2 5 3 [3,5,7,9] 3
2 7 3 [3,5,7,9] 3
2 9 3 [3,5,7,9] 3

Is there a way to update groups of rows with separate incrementing values in one query

Lets say you have the following table:
Id Index
1 3
1 1
2 1
3 3
1 5
what I would like to have is the following:
Id Index
1 0
1 1
2 0
3 0
1 2
As you might notice, the goal is for every row where Id is the same, to incrementally update the Index column, starting from zero.
Now, I know this is fairly simple with using cursors, but out of curiosity is there a way to do this with single UPDATE query, somehow combining with temp tables, common table expressions or something similar?
Yes, assuming that the you don't really care about the order of the values for the new index values. SQL Server offers updatable CTEs and window functions that do exactly what you want:
with toupdate as (
select t.*, row_number() over (partition by id order by (select NULL)) as newindex
from table t
)
update toupdate
set index = newindex;
If you want them in a specific order, then you need another column to specify the ordering. The existing index column doesn't work.
With Row_number() -1 and CTE you can write as:
CREATE TABLE #temp1(
Id int,
[Index] int)
INSERT INTO #temp1 VALUES (1,3),(1,1),(2,1),(3,3),(1,5);
--select * from #temp1;
With CTE as
(
select t.*, row_number() over (partition by id order by (select null))-1 as newindex
from #temp1 t
)
Update CTE
set [Index] = newindex;
select * from #temp1;
Demo
I'm not sure why you would want to do this really, but I had fun figuring it out!
This solution relies on your table having a primary key for the self join... but you could always create an auto inc index if none exists and this is a one off job... This will also have the added benefit of getting you to think about the precise ordering of this you want... as currently there is no way of saying which order [ID] will get [Index] in.
UPDATE dbo.Example
SET [Index] = b.newIndex
FROM dbo.Example a
INNER JOIN (
select
z.ID,
z.[Index],
(row_number() over (partition by ID order by (select NULL))) as newIndex
from Example z
) b ON a.ID = b.ID AND a.[Index]=b.[Index] --Is this a unique self join for your table?.. no PK provided. You might need to make an index first.
Probably, this is what you want
SELECT *,RANK() OVER(PARTITION BY Id ORDER BY [Index])-1 AS NewIndex FROM
(
SELECT 1 AS Id,3 [Index]
UNION
SELECT 1,1
UNION
SELECT 2,1
UNION
SELECT 3,3
UNION
SELECT 1,5
) AS T
& the result will come as
Now if you want to update the table then execute this script
UPDATE tblname SET Index=RANK() OVER(PARTITION BY t.Id ORDER BY t.[Index])-1
FROM tblname AS t
In case I am missing something or any further assistance is required please let me know.
CREATE TABLE #temp1(
Id int,
Value int)
INSERT INTO #temp1 VALUES (1,2),(1,3),(2,3),(4,5)
SELECT
Id
,Value
,ROW_NUMBER() OVER (PARTITION BY Id ORDER BY Id) Id
FROM #temp1
Start with this :)
Gave me results like
Id Value Count
1 2 1
1 3 2
1 2 3
1 3 4
1 2 5
1 3 6
1 2 7
1 3 8
2 3 1
2 4 2
2 5 3
2 3 4
2 4 5
2 5 6
2 4 7
2 5 8
2 3 9
2 3 10
3 4 1
4 5 1
4 5 2
4 5 3
4 5 4