DENSE_RANK() without duplication - sql

Here's what my data looks like:
| col1 | col2 | denserank | whatiwant |
|------|------|-----------|-----------|
| 1 | 1 | 1 | 1 |
| 2 | 1 | 1 | 1 |
| 3 | 2 | 2 | 2 |
| 4 | 2 | 2 | 2 |
| 5 | 1 | 1 | 3 |
| 6 | 2 | 2 | 4 |
| 7 | 2 | 2 | 4 |
| 8 | 3 | 3 | 5 |
Here's the query I have so far:
SELECT col1, col2, DENSE_RANK() OVER (ORDER BY COL2) AS [denserank]
FROM [table1]
ORDER BY [col1] asc
What I'd like to achieve is for my denserank column to increment every time there is a change in the value of col2 (even if the value itself is reused). I can't actually order by the column I have denserank on, so that won't work). See the whatiwant column for an example.
Is there any way to achieve this with DENSE_RANK()? Or is there an alternative?

I would do it with a recursive cte like this:
declare #Dept table (col1 integer, col2 integer)
insert into #Dept values(1, 1),(2, 1),(3, 2),(4, 2),(5, 1),(6, 2),(7, 2),(8, 3)
;with a as (
select col1, col2,
ROW_NUMBER() over (order by col1) as rn
from #Dept),
s as
(select col1, col2, rn, 1 as dr from a where rn=1
union all
select a.col1, a.col2, a.rn, case when a.col2=s.col2 then s.dr else s.dr+1 end as dr
from a inner join s on a.rn=s.rn+1)
col1, col2, dr from s
result:
col1 col2 dr
----------- ----------- -----------
1 1 1
2 1 1
3 2 2
4 2 2
5 1 3
6 2 4
7 2 4
8 3 5
The ROW_NUMBER is only required in case your col1 values are not sequential. If they are you can use the recursive cte straight away

Try this using window functions:
with t(col1 ,col2) as (
select 1 , 1 union all
select 2 , 1 union all
select 3 , 2 union all
select 4 , 2 union all
select 5 , 1 union all
select 6 , 2 union all
select 7 , 2 union all
select 8 , 3
)
select t.col1,
t.col2,
sum(x) over (
order by col1
) whatyouwant
from (
select t.*,
case
when col2 = lag(col2) over (
order by col1
)
then 0
else 1
end x
from t
) t
order by col1;
Produces:
It does a single table read and forms group of consecutive equal col2 values in increasing order of col1 and then finds dense rank on that.
x: Assign value 0 if previous row's col2 is same as this row's col2 (in order of increasing col1) otherwise 1
whatyouwant: create groups of equal values of col2 in order of increasing col1 by doing an incremental sum of the value x generated in the last step and that's your output.

Here is one way using SUM OVER(Order by) window aggregate function
SELECT col1,Col2,
Sum(CASE WHEN a.prev_val = a.col2 THEN 0 ELSE 1 END) OVER(ORDER BY col1) AS whatiwant
FROM (SELECT col1,
col2,
Lag(col2, 1)OVER(ORDER BY col1) AS prev_val
FROM Yourtable) a
ORDER BY col1;
How it works:
LAG window function is used to find the previous col2 for each row ordered by col1
SUM OVER(Order by) will increment the number only when previous col2 is not equal to current col2

I think this is possible in pure SQL using some gaps and islands tricks, but the path of least resistance might be to use a session variable combined with LAG() to keep track of when your computed dense rank changes value. In the query below, I use #a to keep track of the change in the dense rank, and when it changes this variable is incremented by 1.
DECLARE #a int
SET #a = 1
SELECT t.col1,
t.col2,
t.denserank,
#a = CASE WHEN LAG(t.denserank, 1, 1) OVER (ORDER BY t.col1) = t.denserank
THEN #a
ELSE #a+1 END AS [whatiwant]
FROM
(
SELECT col1, col2, DENSE_RANK() OVER (ORDER BY COL2) AS [denserank]
FROM [table1]
) t
ORDER BY t.col1

Related

sql selecting unique rows based on a specific column

I have an table like this :
Col1 Col2 Col3 Col4
asasa 1 d 44
asasa 2 sd 34
asasa 3 f 3
dssd 4 d 2
sdsdsd 5 sd 11
dssd 1 dd 34
xxxsdsds2 d 3
erewer 3 sd 3
I am trying to filter out something like this based on Col1
Col1 Col2 Col3 Col4
asasa 1 d 44
dssd 4 d 2
sdsdsd 5 sd 11
xxxsdsds2 d 3
erewer 3 sd 3
I am trying to get the all unique rows based on the values in Col1. If I have duplicates in Col1, the first row should be taken.
I tried SELECT Col1 FROM tblname GROUP BY Col1 and got unique Col1 but extending it using * is giving me error.
You should be able to achieve your goal using something like the following:
WITH CTE AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY Col1 ORDER BY Col2) AS rn FROM MyTable
)
SELECT * FROM CTE WHERE rn = 1
What it does is it creates a CTE (Common Table Expression) that adds a ROW_NUMBER on Col1, ordered by the data in row2.
In the outer select, we then only grab the rows from the CTE where the row number generated is 1.
Try this
;WITH CTE(
SELECT *,
ROW_NUMBER() OVER(PARTITIAN BY Col1 ORDER BY(SELECT NULL))RN
FROM tblname
)
SELECT Col1, Col2, Col3, Col4 FROM CTE;
Depending on the flavor of SQL that you have are using, what may help you are window functions.
In SQL Server, this can be accomplished with the FIRST_VALUE window function like so:
DROP TABLE IF EXISTS #vals;
CREATE TABLE #vals (COL1 VARCHAR(10), COL2 INT, COL3 VARCHAR(5), COL4 INT);
INSERT INTO #vals (COL1, COL2, COL3, COL4)
VALUES ('asasa', 1, 'd', 44),
('asasa', 2, 'sd', 34),
('asasa', 3, 'f', 3),
('dssd' , 4, 'd', 2),
('sdsdsd', 5, 'sd', 11),
('dssd', 1, 'dd', 34),
('xxxsdsds', 2, 'd', 3),
('erewer', 3, 'sd', 3);
SELECT *
FROM #vals
SELECT DISTINCT COL1,
FIRST_VALUE(COL2) OVER (PARTITION BY COL1 ORDER BY Col1) AS Col2,
FIRST_VALUE(COL3) OVER (PARTITION BY COL1 ORDER BY Col1) AS Col3,
FIRST_VALUE(COL4) OVER (PARTITION BY COL1 ORDER BY Col1) AS Col4
FROM #vals AS v1
This returns:
|COL1 | Col2 | Col3 | Col4|
|-----------|-----------|-----------|-------|
|asasa | 1 | d | 44 |
|dssd | 4 | d | 2 |
|erewer | 3 | sd | 3 |
|sdsdsd | 5 | sd | 11 |
|xxxsdsds | 2 | d | 3 |
which may then be ORDERed in whatever way is needed.
Select DISTINCT , should do the trick. Here is a good reference https://www.w3schools.com/sql/sql_distinct.asp

T-SQL sequential updating with two columns

I have a table created by:
CREATE TABLE table1
(
id INT,
multiplier INT,
col1 DECIMAL(10,5)
)
INSERT INTO table1
VALUES (1, 2, 1.53), (2, 3, NULL), (3, 2, NULL),
(4, 2, NULL), (5, 3, NULL), (6, 1, NULL)
Which results in:
id multiplier col1
-----------------------
1 2 1.53000
2 3 NULL
3 2 NULL
4 2 NULL
5 3 NULL
6 1 NULL
I want to add a column col2 which is defined as multiplier * col1, however the next value of col1 then updates to take the previous calculated value of col2.
The resulting table should look like:
id multiplier col1 col2
---------------------------------------
1 2 1.53000 3.06000
2 3 3.06000 9.18000
3 2 9.18000 18.36000
4 2 18.36000 36.72000
5 3 36.72000 110.16000
6 1 110.16000 110.16000
Is this possible using T-SQL? I've tried a few different things such as joining id to id - 1 and have played around with a sequential update using UPDATE and setting variables but I can't get it to work.
A recursive CTE might be the best approach. Assuming your ids have no gaps:
with cte as (
select id, multiplier, convert(float, col1) as col1, convert(float, col1 * multiplier) as col2
from table1
where id = 1
union all
select t1.id, t1.multiplier, cte.col2 as col1, cte.col2 * t1.multiplier
from cte join
table1 t1
on t1.id = cte.id + 1
)
select *
from cte;
Here is a db<>fiddle.
Note that I converted the destination type to float, which is convenient for this sort of operation. You can convert back to decimal if you prefer that.
Basically, this would require an aggregate/window function that computes the product of column values. Such set function does not exists in SQL though. We can work around this with arithmetics:
select
id,
multiplier,
coalesce(min(col1) over() * exp(sum(log(multiplier)) over(order by id rows between unbounded preceding and 1 preceding)), col1) col1,
min(col1) over() * exp(sum(log(multiplier)) over(order by id)) col2
from table1
Demo on DB Fiddle:
id | multiplier | col1 | col2
-: | ---------: | -----: | -----:
1 | 2 | 1.53 | 3.06
2 | 3 | 3.06 | 9.18
3 | 2 | 9.18 | 18.36
4 | 2 | 18.36 | 36.72
5 | 3 | 36.72 | 110.16
6 | 1 | 110.16 | 110.16
This will fail if there are negative multipliers.
If you wanted an update statement:
with cte as (
select col1, col2,
coalesce(min(col1) over() * exp(sum(log(multiplier)) over(order by id rows between unbounded preceding and 1 preceding)), col1) col1_new,
min(col1) over() * exp(sum(log(multiplier)) over(order by id)) col2_new
from table1
)
update cte set col1 = col1_new, col2 = col2_new

SQL Split Single Row into Fixed Number of Columns

We need to split a single row into fixed number of multiple columns. Following is an example for the data set:
1
2
3
4
5
6
7
Desired Output:
Column A Column B Column C Column D
1 2 3 4
5 6 7 NULL
Thanks for your help in advance.
SQL Server Solution:
Create Sample Table:
create table mytable (col1 int)
insert into mytable values
(1),
(2),
(3),
(4),
(5),
(6),
(7);
Using Modulo and Row_Number(), you could easily do this:
Modulo Query:
SELECT
R1.col1 as columnA,
R2.col1 as columnB,
R3.col1 as columnC,
R4.col1 as columnD
FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY col1 ASC) AS RowNum, col1
FROM mytable
WHERE
col1 % 4 = 1
) AS R1
FULL OUTER JOIN (
SELECT ROW_NUMBER() OVER (ORDER BY col1 ASC) AS RowNum, col1
FROM mytable
WHERE
col1 % 4 = 2
) AS R2
ON R1.RowNum = R2.RowNum
FULL OUTER JOIN (
SELECT ROW_NUMBER() OVER (ORDER BY col1 ASC) AS RowNum, col1
FROM mytable
WHERE
col1 % 4 = 3
) AS R3
ON R2.RowNum = R3.RowNum
FULL OUTER JOIN (
SELECT ROW_NUMBER() OVER (ORDER BY col1 ASC) AS RowNum, col1
FROM mytable
WHERE
col1 % 4 = 0
) AS R4
ON R4.RowNum = R3.RowNum
Result:
+---------+---------+---------+---------+
| columnA | columnB | columnC | columnD |
+---------+---------+---------+---------+
| 1 | 2 | 3 | 4 |
| 5 | 6 | 7 | (null) |
+---------+---------+---------+---------+
SQL Fiddle Demo

Query to get previous value

I have a scenerio where I need previous column value but it should not be same as current column value.
Table A:
+------+------+-------------+
| Col1 | Col2 | Lead_Col2 |
+------+------+-------------+
| 1 | A | NULL |
| 2 | B | A |
| 3 | B | A |
| 4 | C | B |
| 5 | C | B |
| 6 | C | B |
| 7 | D | C |
+------+------+-------------+
As Given above, I need previuos column(Col2) value. which is not same as current value.
Try:
select *
from (select col1,
col2,
lag(col2, 1) over(order by col1) as prev_col2
from table_a)
where col2 <> prev_col2
The name lead_col2 is misleading, because you really want a lag.
Here is a brute force method that uses a correlated subquery to get the index of the value and then joins the value in:
select aa.col1, aa.col2, aa.col2
from (select col1, col2,
(select max(col1) as maxcol1
from a a2
where a2.id < a.id and a2.col2 <> a.col2
) as prev_col1
from a
) aa left join
a
on aa.maxcol1 = a.col1
EDIT:
You can also use logic with lead() and ignore NULLs. If a value is the last in its sequence, then use that value, otherwise set it to NULL. Then use lag() with ignoreNULL`s:
select col1, col2,
lag(col3) over (order by col1 ignore nulls)
from (select col1, col2,
(case when col2 <> lead(col2) over (order by col1) then col2
end) as col3
from a
) a;
Try this:
select t.col1
,t.col2
,first_value(lag_col2) over (partition by col2 order by ord) lag_col2
from (select t.*
,case when lag_col2 = col2 then 1 else 0 end ord
from (select t.*
,lag (col2) over (order by col1) lag_col2
from table1 t
)t
)t
order by col1
SQL Fiddle

Get ID of a row having maximum value in other column

I have a table like
+------+-------+-------------------------------------+
| id | col2 | col3 |
+------+-------+-------------------------------------+
| 1 | 1 | 10 |
| 2 | 1 | 20 |
| 3 | 1 | 15 |
| 4 | 2 | 10 |
| 5 | 2 | 20 |
| 6 | 2 | 15 |
| 7 | 2 | 30 |
+------+-------+-------------------------------------+
I want to select Id where col3 has maximum value and col2 = 2. (id 7 in this case since it has maximum value 30 where col2=2). I tried with GROUP BY clause
SELECT id, MAX(col3) FROM table_name
WHERE col2 =2
GROUP BY id
But it gives me all the Id's where col2=2. How can I achieve desired output?
Thanks.
You could use ROW_NUMBER:
CREATE TABLE temp(
ID INT,
Col2 INT,
Col3 INT
)
INSERT INTO temp VALUES
(1, 1, 10), (2, 1, 20), (3, 1, 15),
(4, 2, 10), (5, 2, 20), (6, 2, 15),
(7, 2, 30);
SELECT
ID, Col3
FROM(
SELECT *, rn = ROW_NUMBER() OVER(PARTITION BY col2 ORDER BY col3 DESC)
FROM table_name
)t
WHERE
rn = 1
AND col2 = 2
RESULT
ID Col3
----------- -----------
7 30
You have to use this following simple sql query for your requirement.
SELECT TOP 1 ID
FROM table_name
Where col2 = 2
ORDER BY col3 DESC
You can use also ROW_NUMBER() function. It's another way for same result.
A simple sub-query will do the job:
SELECT id
FROM table_name
WHERE col2 = 2
AND col3 = (SELECT MAX(col3) FROM table_name WHERE col2=2)
Demo
This can return multiple ID's if there are multiple rows with the same max-value, if you don't want that you could use DISTINCT id or an aggregate function like MAX(id) or TOP 1 id.
Here's another approach using GROUP BY:
SELECT TOP 1 MAX(id)
FROM table_name
GROUP BY col2, col3
HAVING col2 = 2
ORDER BY col3 DESC