Assigning a Row Number in SQL Server, but grouped on a value - sql

I want to select 2 columns from a table, and assign a int value to each value. However, I want the 1st column ID to be the same for all values that are the same.
For the 2nd column, I want each value to numbered as well, but partitioned by the first column. I have figured this piece out, but I can't get the first part to work.
Here is the test scenario I'm using.
DECLARE #TestTable as Table (Column1 char(1), Column2 char(1))
INSERT INTO #TestTable SELECT 'A','A'
INSERT INTO #TestTable SELECT 'A','B'
INSERT INTO #TestTable SELECT 'A','C'
INSERT INTO #TestTable SELECT 'B','D'
INSERT INTO #TestTable SELECT 'B','E'
INSERT INTO #TestTable SELECT 'B','F'
INSERT INTO #TestTable SELECT 'B','G'
INSERT INTO #TestTable SELECT 'B','H'
INSERT INTO #TestTable SELECT 'C','A'
INSERT INTO #TestTable SELECT 'C','B'
INSERT INTO #TestTable SELECT 'C','C'
SELECT
Row_Number() OVER (Partition BY Column1 ORDER BY Column1) as Column1_ID,
Column1,
Row_Number() OVER (Partition BY Column1 ORDER BY Column1, Column2) as Column2_ID,
Column2
FROM #TestTable
When I run this, the values in Column2_ID are correct, but I would like the values for Column1_ID to be as follows.
Column1_ID Column1 Column2_ID Column2
1 A 1 A
1 A 2 B
1 A 3 C
2 B 1 D
2 B 2 E
2 B 3 F
2 B 4 G
2 B 5 H
3 C 1 A
3 C 2 B
3 C 3 C

You just need to use a different ranking function,
dense_rank() OVER (ORDER BY Column1) as Column1_ID
http://msdn.microsoft.com/en-us/library/ms173825.aspx
SQL Fiddle : http://www.sqlfiddle.com/#!6/d41d8/1832

Related

postgresql count rows with special columns

my table looks like this:
table1:
ident
A
B
C
D
1
2
1
2
3
3
1
2
1
5
4
4
5
4
1
3
6
3
2
7
3
8
1
9
1
Now i need something like a analysis from that table.
It should look like:
table2:
name
just_name
A
3
B
1
C
1
D
0
the column just_name count the columns from table1 where there are no other entry in the other columns exept the ident column.
in the real table there are more than 4 columns so i better not work with a where for every other column. :)
thx
If you are ok with just putting the column names in column list then below query can get you your desired result. Though it's possible to make those part dynamic but if you know your column names and it's not changing dynamically this will be better approach. Please let me know if you wanna have hat part dynamic olso.
Schema:
create table mytable1(ident int, A int, B int, C int, D int);
insert into mytable1 values(1,null,2,1,null);
insert into mytable1 values(2,3,null,null,null);
insert into mytable1 values(3,1,2,1,5);
insert into mytable1 values(4,null,4,null,null);
insert into mytable1 values(5,4,1,null,3);
insert into mytable1 values(6,null,3,2,null);
insert into mytable1 values(7,null,null,3,null);
insert into mytable1 values(8,1,null,null,null);
insert into mytable1 values(9,1,null,null,null);
Query:
with cte as (SELECT
unnest(array['A', 'B', 'C','D']) AS Columns,
unnest(array[A, B, C,D]) AS Values,
row_number()over(order by 1)rn
FROM mytable1),
cte2 as (
select rn,max(cte.columns)col,count(*) from cte
where values is not null
group by rn
having count(*)=1)
select distinct columns as name,coalesce(just_name,0) from cte left join (select col,count(rn) just_name from cte2
group by col)t on cte.columns=t.col
Output:
name
coalesce
A
3
C
1
D
0
B
1
db<>fiddle here
I would do this as columns:
select count(*) filter (where A is not null and B is null and C is null and d is null),
count(*) filter (where A is null and B is not null and C is null and d is null),
count(*) filter (where A is null and B is null and C is not null and d is null),
count(*) filter (where A is null and B is null and C is null and d is not null)
from t;
You could also express this as:
select c.colname, count(*) filter (where c.num_vals = 1)
from t cross join lateral
(select colname, count(colval) over () as num_vals
from (values ('a', t.a), ('b', t.b), ('c', t.c), ('d', t.d)) v(colname, colval)
group by colname
) c
group by c.colname;
This returns the values in separate rows. And it is a bit easier to generalize.

Case when duplicate add one more letter

For example: I have a table with these records below
1 A
2 A
3 B
4 C
...
and I need to migrate these record in to another table
1 AA
2 AB
3 B
4 C
...
Meaning if the record is duplicate, it will automatically add one more letter alphabetically.
Just a slightly different approach
Example
Declare #YourTable Table (ID int,[SomeCol] varchar(50))
Insert Into #YourTable Values
(1,'A')
,(2,'A')
,(3,'B')
,(4,'C')
Select *
,NewVal = concat(SomeCol,IIF(sum(1) over (partition by SomeCol)=1,'',char(64+row_number() over ( partition by SomeCol order by ID ))) )
From #YourTable
Returns
ID SomeCol NewVal
1 A AA
2 A AB
3 B B
4 C C
EDIT - Requested UPDATE
Declare #YourTable Table (ID int,[SomeCol] varchar(50))
Insert Into #YourTable Values
(1,'A')
,(2,'A')
,(3,'B')
,(4,'C')
Select *
,NewVal = concat(SomeCol,IIF(sum(1) over (partition by SomeCol)=1,'',replace(char(63+row_number() over ( partition by SomeCol order by ID )),'#','')) )
From #YourTable
Returns
ID SomeCol NewVal
1 A A
2 A AA
3 B B
4 C C
We might be able to handle this requirement with the help of a calendar table mapping secondary letters to duplicate sequence counts:
WITH letters AS (
SELECT 1 AS seq, 'A' AS let UNION ALL
SELECT 2, 'B' UNION ALL
SELECT 3, 'C' UNION ALL
...
SELECT 26, 'Z' UNION ALL
...
),
cte AS (
SELECT id, let, ROW_NUMBER() OVER (PARTITION BY let ORDER BY id) rn,
COUNT(*) OVER (PARTITION BY let) cnt
FROM yourTable
)
SELECT t1.id, t1.let + CASE WHEN t1.cnt > 1 THEN t2.let ELSE '' END AS let
FROM cte t1
LEFT JOIN letters t2
ON t1.id = t2.seq
ORDER BY t1.id;
Demo

SQL count number of records where value remains constant

I need to find the count of tracker_id where position remains 1 through out the table.
tracker_id | position
---------------------
5 | 1
11 | 1
4 | 1
4 | 2
5 | 2
4 | 1
4 | 1
11 | 1
14 | 1
9 | 2
Here, the output should be 2 since, position of tracker_id:11 and 14 remains 1 through out the table.
You can use not exists
select count(*) from tbl a
where not exists(select 1
from tbl b
where a.tracker_id = b.tracker_id
and a.position <> b.position )
and a.position = 1
Output: 2
declare #table1 as table (tracker_id int,postion int)
insert into #table1 values (5,1)
insert into #table1 values (11,1)
insert into #table1 values (4,1)
insert into #table1 values (4,2)
insert into #table1 values (5,2)
insert into #table1 values (4,1)
insert into #table1 values (4,1)
insert into #table1 values (11,1)
insert into #table1 values (14,1)
insert into #table1 values (9,2)
select count(tracker_id),tracker_id,postion from #table1 group by tracker_id,postion
You can also do:
select ( count(distinct tracker_id) -
count(distinct tracker_id) filter (where position <> 1)
) as num_all_1s
from t;
Using uncorrelated subquery
select count(distinct tracker_id)
from t
where position=1
and tracker_id not in (select tracker_id from t where position<>1);
Using window function
select count(distinct tracker_id)
from (select *, avg(position) over (partition by tracker_id) as avg_pos from t) a
where avg_pos=1;
This one is just for giggles
select distinct count(*) over ()
from t
group by tracker_id
having count(*) = sum(position);
And if you really want to have fun
select count(distinct tracker_id)-count(distinct case when position<>1 then tracker_id end)
from t;
If position can only be 1, then you can use this, which gets all the tracker_ids with only a single position value, and then limits that to those records where position = 1:
WITH agg AS
(
SELECT
tracker_id
, p = MAX(position)
FROM table1
GROUP BY tracker_id
HAVING COUNT(DISTINCT position) = 1
)
SELECT COUNT(tracker_id)
FROM agg
WHERE p = 1

SQL Delete duplicate records and leave the rest

I have 2 tables a and b. A have 5 records and B have same records as A but 7 rows. Thats is same values in 7 rows. I wants to delete only the first 5 records in B since the row number is matches with A. How to do this. please help me.
table :A
col1 col2 col3 DuplicateCount
1 2 n 1
1 2 n 2
1 2 n 3
1 2 n 4
2 2 m 1
2 2 m 2
table b:
col1 col2 col3 DuplicateCount
1 2 n 1
1 2 n 2
1 2 n 3
1 2 n 4
1 2 n 5
1 2 n 6
desired data should reside in table b is
col1 col2 col3 DuplicateCount
1 2 n 5
1 2 n 6
which is nothing but the last 2 rows in the table b.
Try this :
delete from TableB
WHERE Id IN
(
select b.id
from TableB b, TableA a
WHERE b.Id = a.ID
)
I added id column to identify rows in table B, I am not sure how to delete only some of duplicate rows without id column:
declare #a table
(
id int primary key,
col1 int,
col2 int,
col3 varchar
)
declare #b table
(
id int primary key,
col1 int,
col2 int,
col3 varchar
)
insert into #a values (1,1,2,'n')
insert into #a values (2,1,2,'n')
insert into #a values (3,1,2,'n')
insert into #a values (4,1,2,'n')
insert into #a values (5,2,2,'n')
insert into #a values (6,2,2,'n')
insert into #b values (10,1,2,'n')
insert into #b values (20,1,2,'n')
insert into #b values (30,1,2,'n')
insert into #b values (40,1,2,'n')
insert into #b values (50,1,2,'n')
insert into #b values (60,1,2,'n')
delete from #b
where id in
(
(
select t1.id from
(
select
id,
cnt = count(*) over(partition by col1, col2, col3),
rn = row_number() over(partition by col1, col2, col3 order by id)
from #b
) t1
join
(
select
*,
cnt = count(*) over(partition by col1, col2, col3)
from #a
) t2 on
t1.cnt > 1 and t1.rn <= t2.cnt
)
)
select * from #b
You can use TOP key word for deleting first five records
DELETE TOP (select * from TableA a,TableB b where a.col1=b.col1 AND a.col2=b.col2 AND
a.col3=b.col3) FROM TableA
or
Note: The below is an example for deleting one or more records based on their IDs
DELETE From yourTable where ID in (2,3,4,5,6)

Order by specific values in a column without using case statement

I would like to get the records in the below format:
if i have a record like
A, B, C, D
and I would like get record in this order -
B, A, C, D, E, F, G, H, so on,
But I need the value B should be at the first row...
try this:
SELECT
*, 1 AS SortBy
FROM YourTable
WHERE YourCol='B'
UNION ALL
SELECT
*, 2 AS SortBy
FROM YourTable
WHERE YourCol!='B'
ORDER BY SortBy, YourCol
You don't give any reason to not want to use CASE. I'd still give it a try and see which is faster, the UNION ALL or the CASE method:
SELECT
*
FROM YourTable
ORDER BY CASE WHEN YourCol='B' then 1 ELSE 2 END, YourCol
EDIT Working example:
DECLARE #YourTable table (YourCol char(1), RowValue varchar(5))
INSERT #YourTable VALUES ('A','aaa')
INSERT #YourTable VALUES ('A','aa')
INSERT #YourTable VALUES ('B','bbb')
INSERT #YourTable VALUES ('B','bb')
INSERT #YourTable VALUES ('C','ccc')
INSERT #YourTable VALUES ('D','ddd')
INSERT #YourTable VALUES ('E','eee')
INSERT #YourTable VALUES ('F','fff')
SELECT
*, 1 AS SortBy
FROM #YourTable
WHERE YourCol='B'
UNION ALL
SELECT
*, 2 AS SortBy
FROM #YourTable
WHERE YourCol!='B'
ORDER BY SortBy, YourCol
OUTPUT:
YourCol RowValue SortBy
------- -------- -----------
B bbb 1
B bb 1
A aaa 2
A aa 2
C ccc 2
D ddd 2
E eee 2
F fff 2
(8 row(s) affected)
SELECT * from mytable where mycolumn = "B";
followed by
SELECT * from mytable where mycolumn != "B" order by mycolumn asc;
Declare and populate table:
DECLARE #t TABLE (col1 CHAR)
INSERT #t
SELECT char(number+ 65)
FROM master..spt_values
WHERE type = 'P' AND number < 6
Query1:
SELECT *, cast(ascii(col1)-66 as bit) * 2 + ascii(col1) [orderby]
FROM #t
ORDER BY cast(ascii(col1)-66 as bit) * 2 + ascii(col1)
Query2:
SELECT *
FROM #t
ORDER BY replace(col1, 'B', ' ')
Result for Query1: (the [orderby] column is included for documentation only)
col1 orderby
---- --------
B 66
A 67
C 69
D 70
E 71
F 72