Get top n row of each group of two columns - sql

This question is different from Get top 1 row of each group. In my question, each group is consists of two columns (col1, col2), while in his question each group is consists of only one column (col1). I also tried to modify the answer in his question but failed.
Example:
Suppose n = 1
Input:
col1 col2 x Amt
A B x1 100
A B x2 200
C D x3 400
C D x4 500
...more data ...
Output:
col1 col2 x Amt
A B x2 200
C D x4 500
...more data ...
What I tried ...select *, row_numne() over ( partition by (col1, col2) order by ...

You can still use row_number within a CTE. The idea is to get all the rows, per your grouping, that is <= the number you pass in. This is similar to getting the top n rows for your pairing based on the order of AMT
declare #count int = 1
with cte as(
select
col1,
col2,
x,
Amt,
row_number() over (partition by col1, col2 order by Amt desc) as rn
from yourTable)
select
col1,
col2,
x,
Amt
from cte
where rn <= #count

why not simple max works for you?
select col1, col2, max(x), Max(Amt) from yourtable
group by col1, col2

Declare #Top int = 1
Select col1,col2,x,Amt
From (
Select *
,RN=Row_Number() over (Partition By Col1,Col2 Order By Amt Desc)
From YourTable ) A
Where RN<=#Top
Returns
col1 col2 x Amt
A B x2 200
C D x4 500

And here is the CROSS APPLY option, with test tables to confirm its functionality:
DECLARE #MyTable TABLE (Col1 varchar(4) not null, Col2 varchar(4) not null, x varchar(8) not null, amt int not null)
INSERT INTO #myTable VAlues ('A', 'B', 'x1', 100)
INSERT INTO #myTable VAlues ('A', 'B', 'x2', 200)
INSERT INTO #myTable VAlues ('C', 'D', 'x4', 400)
INSERT INTO #myTable VAlues ('C', 'D', 'x3', 500)
DECLARE #n int
SET #n = 1
SELECT DISTINCT
m.Col1,
m.Col2,
m2.x,
m2.Amt
FROM #MyTable m
CROSS APPLY (
SELECT TOP(#n) Amt, x
FROM #MyTable
WHERE col1 = m.Col1
AND col2 = m.col2
ORDER BY Amt Desc, x Desc
) m2

Related

Not sure about the below scenario. Need a bit push. How can I solve below sql scenario

Input:
COL1 COL2
---------------
10 a
20 b
30 c
40 NULL
50 d
Desired output:
COL1 COL2
-----------------
10 a
20 a,b
30 a,b,c
40 a,b,c
50 a,b,c,d
Below is the solution I have tried so far. But this is not returning the desired output.
WITH CTE AS
(
SELECT
COL1,
LAG(COL2) OVER (ORDER BY COL1) AS prev_word,
COL2
FROM
dbo.Scenario
), CTE_A AS
(
SELECT
COL1, COL2, prev_word,
CONCAT(ISNULL(Prev_word, ''), ' ', ISNULL(COL2, '')) AS Con_Word
FROM
CTE
)
SELECT *
FROM CTE_A
One possible solution is the following statement. I assume, that the values in the COL1 column define the order, that is needed for the aggregation.
Table:
CREATE TABLE Data (
COL1 int,
COL2 varchar(1)
)
INSERT INTO Data (COL1, COL2)
VALUES
(10, 'a'),
(20, 'b'),
(30, 'c'),
(40, NULL),
(50, 'd')
Statement for SQL Server 2012:
SELECT d.COL1, STUFF(a.COL2, 1, 1, '') AS COL2
FROM Data d
CROSS APPLY (
SELECT CONCAT(',', COL2)
FROM Data
WHERE COL1 <= d.COL1 AND COL2 IS NOT NULL
ORDER BY COL2
FOR XML PATH('')
) a (COL2)
ORDER BY d.COL1
Statement for SQL Server 2017+ (using STRING_AGG() for string aggregation):
SELECT d1.COL1, STRING_AGG(d2.COL2, ',') WITHIN GROUP (ORDER BY d2.COL1) AS COL2
FROM Data d1
JOIN Data d2 ON d1.COL1 >= d2.COL1
WHERE d2.COL2 IS NOT NULL
GROUP BY d1.COL1
ORDER BY d1.COL1
Result:
COL1 COL2
10 a
20 a,b
30 a,b,c
40 a,b,c
50 a,b,c,d
try the following:
declare #t table (COL1 int, COL2 varchar(max))
insert into #t select 10, 'a'
insert into #t select 20, 'b'
insert into #t select 30, 'c'
insert into #t select 40, NULL
insert into #t select 50, 'd'
select COL1, STUFF(
(
SELECT DISTINCT ',' + COL2 FROM #t t2
WHERE t.COL1 >= t2.COL1 for xml path('')
),1,1,''
) AS COL2
from #t t
SELECT ID,STUFF((SELECT DISTINCT ',' + [Values] FROM Table_ t2
WHERE t.ID>= t2.ID for xml path('')),1,1,'') AS [Values]
FROM Table_ t

How to convert many rows into Columns in SQL Server?

How would you convert a field that is stored as multiple rows into columns?
I listed the code below as well. Below is an example of what is needed but it can really go up to 20 columns. Thanks!
COL1 COL2 COL3
----------------
TEST 30 NY
TEST 30 CA
TEST2 10 TN
TEST2 10 TX
I would like the output to be :
COL1 COL2 COL3 COL4
------------------------
TEST 30 NY CA
TEST2 10 TN TX
select * from (
select
ID,
Name,
STORE,
Group,
Type,
Date,
State,
row_number() over(partition by ID, state order by Date desc) as rn
from
#test
) t
where t.rn = 1
declare #Table AS TABLE
(
Col1 VARCHAR(100) ,
Col2 INT ,
Col3 VARCHAR(100)
)
INSERT #Table
( Col1, Col2, Col3 )
VALUES
( 'TEST', 30 ,'NY' ),
( 'TEST', 30 ,'CA' ),
( 'TEST2', 10 ,'TN' ),
( 'TEST2', 10 ,'TX' )
SELECT
xQ.Col1,
xQ.Col2,
MAX(CASE WHEN xQ.RowNumber = 1 THEN xQ.Col3 ELSE NULL END) AS Col3,
MAX(CASE WHEN xQ.RowNumber = 2 THEN xQ.Col3 ELSE NULL END) AS Col4
FROM
(
SELECT * , RANK() OVER(PARTITION BY T.Col1,T.Col2 ORDER BY T.Col1,T.Col2,T.Col3) AS RowNumber
FROM #Table AS T
)AS xQ
GROUP BY
xQ.Col1,
xQ.Col2
There are multiple options to convert data from rows into columns. In SQL, you can use PIVOT to transform data from rows into columns.
CREATE table #tablename
(Id int, Value varchar(10), ColumnName varchar(15);
INSERT INTO #tablename
(ID, Value, ColumnName)
VALUES
(1, ‘Lucy’, 'FirstName'),
(2, ‘James’, ‘LastName’),
(3, ‘ABCDXX’, ‘Adress’),
(4, ’New York’, ‘City’),
(5, '8572685', ‘PhoneNo’);
select FirstName, LastName, Address, City, PhoneNo
from
(
select Value, ColumnName
from #tablename
) d
pivot
(
max(Value)
for ColumnName in (FirstName, LastName, Address, City, PhoneNo)
) piv;
Refer the below link for other options of transforming data from rows to columns:
https://www.sqlshack.com/multiple-options-to-transposing-rows-into-columns/

find max value in a row and update new column with the max column name

I have a table like this
number col1 col2 col3 col4 max
---------------------------------------
0 200 150 300 80
16 68 250 null 55
I want to find max value between col1,col2,col3,col4 in every row and update the last column "max" with the max value column name!
for example in first row max value is 300 the "max" column value will be "col3"
result like this:
number col1 col2 col3 col4 max
------------------------------------------
0 200 150 300 80 col3
16 68 250 null 55 col2
How can I do this?
QUERY
SELECT *,(
SELECT MAX(n)
FROM
(
VALUES(col1),(col2),(col3),(col4)
) AS t(n)
) AS maximum_value
FROM #tmp
Update statement
with MaxValues
as (select [number], [max] = (
select (
select max ([n])
from (values ([col1]) , ([col2]) , ([col3]) , ([col4])) as [t] ([n])
) as [maximum_value])
from [#tmpTable])
update [#tmpTable]
set [max] = [mv].[max]
from [MaxValues] [mv]
join [#tmpTable] on [mv].[number] = [#tmpTable].[number];
assuming number is a key column
SQL Fiddle
Check in SQL Fiddle
Schema
DECLARE #temp table ([number] int NOT NULL, [col1] int, [col2] int, [col3] int, [col4] int, [colmax] int);
INSERT #temp VALUES (0, 200, 150, 300, 80, null), (16, 68, 250, null, 55, null);
Query
SELECT number
,(
SELECT MAX(col) maxCol
FROM (
SELECT t.col1 AS col
UNION
SELECT t.col2
UNION
SELECT t.col3
UNION
SELECT t.col4
) a
) col
FROM #temp t
and the update statement is -
UPDATE tempCol
SET colmax = a.col
FROM (
SELECT (
SELECT MAX(col) maxCol
FROM (
SELECT t.col1 AS col
UNION
SELECT t.col2
UNION
SELECT t.col3
UNION
SELECT t.col4
) a
) col
FROM tempCol t
) a

Select only distinct values from two columns from a table

If I have a table such as
1 A
1 B
1 A
1 B
2 C
2 C
And I want to select distinct from the two columns so that I would get
1
2
A
B
C
How can I word my query? Is the only way to concatenate the columns and wrap them around a distinct function operator?
You could use a union to create a table of all values from both columns:
select col1 as BothColumns
from YourTable
union
select col2
from YourTable
Unlike union all, union removes duplicates, even if they come from the same side of the union.
SQL Fiddle
Why even distinct in Union, try this :
select cast(id as char(1)) from test
union
select val from test
Please try:
Select Col1 from YourTable
union
Select Col2 from YourTable
UNION removes duplicate records (where all columns in the results are the same), UNION ALL does not.
Please check What is the difference between UNION and UNION ALL
For multiple columns, you can go for UNPIVOT.
SELECT distinct DistValues
FROM
(SELECT Col1, Col2, Col3
FROM YourTable) p
UNPIVOT
(DistValues FOR Dist IN
(Col1, Col2, Col3)
)AS unpvt;
Try this one -
DECLARE #temp TABLE
(
Col1 INT
, Col2 NVARCHAR(50)
)
INSERT INTO #temp (Col1, Col2)
VALUES (1, 'ab5defg'), (2, 'ae4eii')
SELECT disword = (
SELECT DISTINCT dt.ch
FROM (
SELECT ch = SUBSTRING(t.mtxt, n.number + 1, 1)
FROM [master].dbo.spt_values n
CROSS JOIN (
SELECT mtxt = (
SELECT CAST(Col1 AS VARCHAR(10)) + Col2
FROM #temp
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)'
)
) t
WHERE [type] = N'p'
AND number <= LEN(mtxt) - 1
) dt
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)'
)
Or try this -
DECLARE #temp TABLE
(
a CHAR(1), b CHAR(1)
)
INSERT INTO #temp (a, b)
VALUES
('1', 'A'), ('1', 'B'), ('1', 'A'),
('1', 'B'), ('2', 'C'), ('2', 'C')
SELECT a
FROM #temp
UNION
SELECT b
FROM #temp
Because what you want select is in different columns, you can use union like below:
select distinct tarCol from
(select distinct column1 as tarCol from table
union
select distinct column2 from table) as tarTab
You can use like this to get multiple distinct column values
(SELECT DISTINCT `enodeb` as res,
"enodeb" as columnname
FROM `raw_metrics`)
UNION
(SELECT DISTINCT `interval` as res,
"interval" as columnname
FROM `raw_metrics`)

How to combine multiple rows into one with nulled values where row values differ

How can I do with SQL Server to get a single row where the only non-null values are the ones that are consistent and non-null through all the selected rows.
A B C D
10 NULL text NULL
4 abc text NULL
4 def text NULL
Should give the following row:
A B C D
NULL NULL text NULL
create table #t (col1 int, col2 char(3), col3 char(4), col4 int)
go
insert into #t select 10, null, 'text', null
insert into #t select 4, 'abc', 'text', null
insert into #t select 4, 'def', 'text', null
go
select
case when count(distinct isnull(col1, 0)) > 1 then null else max(col1) end as 'col1',
case when count(distinct isnull(col2, '')) > 1 then null else max(col2) end as 'col2',
case when count(distinct isnull(col3, '')) > 1 then null else max(col3) end as 'col3',
case when count(distinct isnull(col4, 0)) > 1 then null else max(col4) end as 'col4'
from
#t
go
drop table #t
go
EDIT: I added ISNULL to handle the issue identified by t-clausen.dk but this will only work if the 'default' values (i.e. zero and empty string) do not appear in the real data.
Daniel's comment about data types is also correct, but since we don't know the data types involved it's not easy to suggest an alternative. Providing a self-contained test script that uses the real data types is the best way to ask questions like this.
declare #t table(A int, b varchar(10), c varchar(max), d int)
insert #t values(10, null, 'text', null)
insert #t values(4, 'abc', 'text', null)
insert #t values(10, 'def', 'text', null)
select case when max(rna) > 1 then null else min(a) end,
case when max(rnb) > 1 then null else min(b) end,
case when max(rnc) > 1 then null else min(c) end,
case when max(rnd) > 1 then null else min(d) end
from
(
select rna = rank() over(order by a),
rnb = rank() over(order by b),
rnc = rank() over(order by c),
rnd = rank() over(order by d),
a, b,c,d
from #t
) e
If you have text columns replace the column type with varchar(max). Text columns are outdated.
Using count(distinct col1) was by first thought, but it doesn't count null values.
select count(distinct a) from (select cast(null as int) a) b
returns 0 rows
SELECT
CASE WHEN COUNT(DISTINCT col1) = 1
AND COUNT(col1) = COUNT(*)
THEN MIN(col1)
END AS col1
, CASE WHEN COUNT(DISTINCT col2) = 1
AND COUNT(col2) = COUNT(*)
THEN MIN(col2)
END AS col2
, CASE WHEN COUNT(DISTINCT col3) = 1
AND COUNT(col3) = COUNT(*)
THEN MIN(col3)
END AS col3
, CASE WHEN COUNT(DISTINCT col4) = 1
AND COUNT(col4) = COUNT(*)
THEN MIN(col4)
END AS col4
FROM
tableX