select column name from max query - sql

I have a query that goes something like this :
;WITH t as
(
select 1 as RowNumber, 1 as ObjectID, 10 as [Col1], 20 as [Col2], 20 as [Col3], 20 as [Col4] UNION ALL
select 2 as RowNumber, 2 as ObjectID, 20 as [Col1], 30 as [Col2], 40 as [Col3], 50 as [Col4]
)
SELECT RowNumber, ObjectID,
(
SELECT MAX(Amount)
FROM (
SELECT [Col1] AS Amount
UNION ALL
SELECT [Col2]
UNION ALL
SELECT [Col3]
UNION ALL
SELECT [Col4]
) d
WHERE Amount > 0
)
FROM t
The query works fine, but I want to know is where the Max(Amount) comes from.
So in my result set, on top of having (RowNumber, ObjectId, Amount) I want the name of the column (Col1, Col2, Col3, Col4) as a String.
Is there any way to do that?
EDIT
Question from the comments : If two columns have the same max, it could be either one? Yes, it could be either one. Any column name will do as long as I know where it could be coming from.
Using SQL Server 2008

Don't MAX: use TOP which avoids the aggregate/GROUP BY.
It can also deal with duplicates using WITH TIES
I'm not sure if what you had was psuedo-code or a sub-query, but this should do what you want
SELECT TOP 1 -- WITH TIES if needed
*
FROM
(
SELECT RowNumber, ObjectID, [Col1] AS Amount, 'Col1' AS ColName
FROM table
UNION ALL
SELECT RowNumber, ObjectID, [Col2], 'Col2' AS ColName
FROM table
UNION ALL
SELECT RowNumber, ObjectID, [Col3], 'Col3' AS ColName
FROM table
UNION ALL
SELECT RowNumber, ObjectID, [Col4], 'Col4' AS ColName
FROM table
) foo
WHERE Amount > 0
ORDER BY Amount DESC
Your main problem is that you'll have to touch the table 4 times no matter how you do it because a subquery only returns one value. I can't see a ROW_NUMBER solution either (but there probably is one though... :-)

This is untested: however to see whats going on with your data, this might help. Not really production code quality:
SELECT RowNumber, ObjectID,
(
SELECT MAX(Amount)
FROM (
SELECT str([Col1]) + ", col1, " AS Amount
UNION ALL
SELECT str([Col2]) + ", col2"
UNION ALL
SELECT str([Col3]) + ", col3"
UNION ALL
SELECT str([Col4]) + ", col4"
)
WHERE Amount > 0
)
FROM table
str() is the "toString()" function of your DBMS.
Your SQL seems pretty weird, what DBMS are you using?

Adding a step to user202553's answer
;WITH t1 as(
select 1 as RowNumber, 1 as ObjectID, 10 as [Col1], 20 as [Col2], 20 as [Col3], 20 as [Col4] UNION ALL
select 2 as RowNumber, 2 as ObjectID, 20 as [Col1], 30 as [Col2], 40 as [Col3], 50000045 as [Col4]
),
t2 as(
SELECT RowNumber, ObjectID,
(
SELECT TOP 1 CAST(C AS BINARY(4)) + CAST(Amount as BINARY(4))
FROM (
SELECT 'Col1' AS C, [Col1] AS Amount
UNION ALL
SELECT 'Col2' AS C, [Col2]
UNION ALL
SELECT 'Col3' AS C, [Col3]
UNION ALL
SELECT 'Col4' AS C, [Col4]
) d
WHERE Amount > 0
ORDER BY Amount desc
) AS Top1
FROM t1
)
SELECT RowNumber,
ObjectID,
CAST(Left(Top1, 4) AS CHAR(4)) AS Col,
CAST(SUBSTRING(Top1,5,4) AS INT) AS Amount
FROM t2

You can use a combination of UNPIVOT and OUTER APPLY:
;WITH t as (
select 1 as RowNumber, 1 as ObjectID, 10 as [Col1], 20 as [Col2],
20 as [Col3], 20 as [Col4] UNION ALL
select 2 as RowNumber, 2 as ObjectID, 20 as [Col1], 30 as [Col2],
40 as [Col3], 50 as [Col4] )
SELECT
RowNumber,
ObjectID,
ColName,
ColAmount
FROM t
OUTER APPLY (
SELECT TOP 1
ColName,
ColAmount
FROM
(
SELECT
Col1,
Col2,
Col3,
Col4
) x
UNPIVOT (
ColAmount FOR ColName IN (Col1, Col2, Col3, Col4)
) y
WHERE ColAmount > 0
ORDER BY ColAmount DESC
) z
Results:
RowNumber ObjectID ColName ColAmount
----------- ----------- --------- -----------
1 1 Col2 20
2 2 Col4 50

Related

SQL - Two Columns into One Distinct Ordered Column

If I have a table like this:
Col 1 | Col 2
-------------
A | 1
A | 2
B | 1
C | 1
C | 2
C | 3
How can I write a query to pull one column that looks like this --
Col 1
------
A
1
2
B
1
C
1
2
3
SELECT col1
FROM Some_Table_You_Did_Not_Name
UNION ALL
SELECT col2
FROM Some_Table_You_Did_Not_Name
If the order matters in your example then you want this:
WITH data AS
(
SELECT col1, col2, ROW_NUMBER() OVER (ORDER BY col1, col2) as RN
FROM Some_Table_You_Did_Not_Name
)
SELECT col
FROM (
SELECT DISTINCT col1 as col, RN, 1 as O
FROM data
UNION ALL
SELECT DISTINCT col2 as col, RN, 2 as O
FROM data
) JC_IS_THAT_GUY
ORDER BY RN ASC, O ASC, col ASC
You can use a query like the following:
SELECT Col1
FROM (
SELECT DISTINCT Col1, Col1 AS Col2, 0 AS grp
FROM mytable
UNION ALL
SELECT Col2 AS Col1, Col1 AS Col2, 1 AS grp
FROM mytable) AS t
ORDER BY Col2, grp, Col1
Demo here
There is absolutely no need to do a UNION, UNION ALL or reference the table more than once to unpivot data...
-- if Col2 is always a well ordered sequense like the test data...
SELECT
Col1 = x.Value
FROM
#TestData td
CROSS APPLY ( VALUES (IIF(td.Col2 = 1, td.Col1, NULL)), (CAST(td.Col2 AS CHAR(1))) ) x (Value)
WHERE
x.Value IS NOT NULL;
-- if it isn't...
WITH
cre_Add_RN AS (
SELECT
td.Col1,
td.Col2,
RN = ROW_NUMBER() OVER (PARTITION BY td.Col1 ORDER BY td.Col2)
FROM
#TestData td
)
SELECT
x.Value
FROM
cre_Add_RN arn
CROSS APPLY ( VALUES (IIF(arn.RN = 1, arn.Col1, NULL)), (CAST(arn.Col2 AS CHAR(1))) ) x (Value)
WHERE
x.Value IS NOT NULL;
HTH,
Jason

Pivoting row's to columns

How to achieve the below??
Anyone help me out
col_1 col_2
A 1
B 1
C 1
B 2
C 4
A 2
A 6
Output:
A B C
1 1 1
2 2 4
6
This will do the job, but it seems like quite an odd thing to want to do, so I am probably missing something?
CREATE TABLE #table (col1 CHAR(1), col2 INT);
INSERT INTO #table SELECT 'A', 1;
INSERT INTO #table SELECT 'B', 1;
INSERT INTO #table SELECT 'C', 1;
INSERT INTO #table SELECT 'B', 2;
INSERT INTO #table SELECT 'C', 4;
INSERT INTO #table SELECT 'A', 2;
INSERT INTO #table SELECT 'A', 6;
WITH Ranked AS (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY col1 ORDER BY col2) AS rank_id
FROM
#table),
Numbers AS (
SELECT 1 AS number
UNION ALL
SELECT number + 1 FROM Numbers WHERE number < 50)
SELECT
MAX(CASE WHEN col1 = 'A' THEN col2 END) AS [A],
MAX(CASE WHEN col1 = 'B' THEN col2 END) AS [B],
MAX(CASE WHEN col1 = 'C' THEN col2 END) AS [C]
FROM
Numbers n
INNER JOIN Ranked r ON r.rank_id = n.number
GROUP BY
n.number;
Results are:
A B C
1 1 1
2 2 4
6 NULL NULL
Looks like you are trying to pivot without aggregation? Here is another option:
select A, B, C from
( select col1, col2, dense_rank() over (partition by col1 order by col2) dr from #table) t
pivot
( max(t.col2) for t.col1 in (A, B, C)) pvt;
Also check this out for more examples/discussion: TSQL Pivot without aggregate function

Cumulative string concatenation

I have a requirement where I have to show data in cumulative concatenation style, just like running total by group.
Sample data
Col1 Col2
1 a
1 b
2 c
2 d
2 e
Expected output:
Col1 Col2
1 a
1 b,a
2 c
2 d,c
2 e,d,c
The concatenation needs to be broken down by Col1. Any help regarding how to get this result by Oracle SQL will be appreciated.
Assuming something on the way you need to order, this can be a solution, based on Hierarchical Queries:
with test as
(
select 1 as col1, 'a' as col2 from dual union all
select 1 as col1, 'b' as col2 from dual union all
select 2 as col1, 'c' as col2 from dual union all
select 2 as col1, 'd' as col2 from dual union all
select 2 as col1, 'e' as col2 from dual
)
select col1, col2
from (
select col1 AS col1, sys_connect_by_path(col2, ',') AS col2, connect_by_isleaf leaf
from (
select row_number() over (order by col1 asc, col2 desc) as num, col1, col2
from test
)
connect by nocycle prior col1 = col1 and prior num = num -1
)
where leaf = 1
order by col1, col2
Try:
WITH d AS (
select col1, col2,
row_number() over (partition by col1 order by col2) as x
from tab_le
),
d1( col1, col2, x, col22) as (
SELECT col1, col2, x, col2 col22 FROM d WHERE x = 1
UNION ALL
SELECT d.col1, d.col2, d.x, d.col2 || ',' || d1.col22
FROM d
JOIN d1 ON (d.col1 = d1.col1 AND d.x = d1.x + 1)
)
SELECT * FROM d1
order by 1,2;
I'm not sure you can do this with listagg as it doesn't seem to support windowing clauses. If you're on 11g or higher you can use recursive subquery factoring to achieve your result.
with your_table (col1, col2) as (
select 1, 'a' from dual
union all select 1, 'b' from dual
union all select 2, 'c' from dual
union all select 2, 'd' from dual
union all select 2, 'e' from dual
), t as (
select col1, col2, row_number() over (partition by col1 order by col2) as rn
from your_table
), r (col1, col2, rn) as (
select col1, col2, rn
from t
where rn = 1
union all
select r.col1, t.col2 ||','|| r.col2, t.rn
from r
join t on t.col1 = r.col1 and t.rn = r.rn + 1
)
select col1, col2
from r
order by col1, rn;
COL1 COL2
---------- --------------------
1 a
1 b,a
2 c
2 d,c
2 e,d,c
The your_table CTE is just to mimic your base data. The t CTE adds a row_number() analytic column to provide a sequence for the next part. The interesting part is the r recursive CTE. The anchor member starts with the first row (according to rn from the previous CTE). The recursive member then finds the next row (against according to rn) for that col1, and for that it concatenates the current col2 with the previous one, which may itself already be a concatenation.

Apply the distinct on 2 fields and also fetch the unique data for each columns

According to some weird requirement, i need to select the record where all the output values in both the columns should be unique.
Input looks like this:
col1 col2
1 x
1 y
2 x
2 y
3 x
3 y
3 z
Expected Output is:
col1 col2
1 x
2 y
3 z
or
col1 col2
1 y
2 x
3 z
I tried applying the distinct on 2 fields but that returns all the records as overall they are distinct on both the fields. What we want to do is that if any value is present in the col1, then it cannot be repeated in the col2.
Please let me know if this is even possible and if yes, how to go about it.
Great problem! Armunin has picked up on the deeper structural issue here, this is a recursive enumerable problem description and can only be resolved with a recursive solution - base relational operators (join/union/etc) are not going to get you there. As Armunin cited, one approach is to bring out the PL/SQL, and though I haven't checked it in detail, I'd assume the PL/SQL code will work just fine. However, Oracle is kind enough to support recursive SQL, through which we can build the solution in just SQL:
-- Note - this SQL will generate every solution - you will need to filter for SOLUTION_NUMBER=1 at the end
with t as (
select 1 col1, 'x' col2 from dual union all
select 1 col1, 'y' col2 from dual union all
select 2 col1, 'x' col2 from dual union all
select 2 col1, 'y' col2 from dual union all
select 3 col1, 'x' col2 from dual union all
select 3 col1, 'y' col2 from dual union all
select 3 col1, 'z' col2 from dual
),
t0 as
(select t.*,
row_number() over (order by col1) id,
dense_rank() over (order by col2) c2_rnk
from t),
-- recursive step...
t1 (c2_rnk,ids, str) as
(-- base row
select c2_rnk, '('||id||')' ids, '('||col1||')' str
from t0
where c2_rnk=1
union all
-- induction
select t0.c2_rnk, ids||'('||t0.id||')' ids, str||','||'('||t0.col1||')'
from t1, t0
where t0.c2_rnk = t1.c2_rnk+1
and instr(t1.str,'('||t0.col1||')') =0
),
t2 as
(select t1.*,
rownum solution_number
from t1
where c2_rnk = (select max(c2_rnk) from t1)
)
select solution_number, col1, col2
from t0, t2
where instr(t2.ids,'('||t0.id||')') <> 0
order by 1,2,3
SOLUTION_NUMBER COL1 COL2
1 1 x
1 2 y
1 3 z
2 1 y
2 2 x
2 3 z
You can use a full outer join to merge two numbered lists together:
SELECT col1, col2
FROM ( SELECT col1, ROW_NUMBER() OVER ( ORDER BY col1 ) col1_num
FROM your_table
GROUP BY col1 )
FULL JOIN
( SELECT col2, ROW_NUMBER() OVER ( ORDER BY col2 ) col2_num
FROM your_table
GROUP BY col2 )
ON col1_num = col2_num
Change ORDER BY if you require a different order and use ORDER BY NULL if you're happy to let Oracle decide.
What would be the result if another row of
col1 value as 1 and col2 value as xx ?
A single row is better in this case:
SELECT DISTINCT TO_CHAR(col1) FROM your_table
UNION ALL
SELECT DISTINCT col2 FROM your_table;
My suggestion is something like this:
begin
EXECUTE IMMEDIATE 'CREATE global TEMPORARY TABLE tmp(col1 NUMBER, col2 VARCHAR2(50))';
end;
/
DECLARE
cur_print sys_refcursor;
col1 NUMBER;
col2 VARCHAR(50);
CURSOR cur_dist
IS
SELECT DISTINCT
col1
FROM
ttable;
filtered sys_refcursor;
BEGIN
FOR rec IN cur_dist
LOOP
INSERT INTO tmp
SELECT
col1,
col2
FROM
ttable t1
WHERE
t1.col1 = rec.col1
AND t1.col2 NOT IN
(
SELECT
tmp.col2
FROM
tmp
)
AND t1.col1 NOT IN
(
SELECT
tmp.col1
FROM
tmp
)
AND ROWNUM = 1;
END LOOP;
FOR rec in (select col1, col2 from tmp) LOOP
DBMS_OUTPUT.PUT_LINE('col1: ' || rec.col1 || '|| col2: ' || rec.col2);
END LOOP;
EXECUTE IMMEDIATE 'DROP TABLE tmp';
END;
/
May still need some refining, I am especially not happy with the ROWNUM = 1 part.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE tbl ( col1, col2 ) AS
SELECT 1, 'x' FROM DUAL
UNION ALL SELECT 1, 'y' FROM DUAL
UNION ALL SELECT 2, 'x' FROM DUAL
UNION ALL SELECT 2, 'y' FROM DUAL
UNION ALL SELECT 3, 'x' FROM DUAL
UNION ALL SELECT 3, 'y' FROM DUAL
UNION ALL SELECT 4, 'z' FROM DUAL;
Query 1:
WITH c1 AS (
SELECT DISTINCT
col1,
DENSE_RANK() OVER (ORDER BY col1) AS rank
FROM tbl
),
c2 AS (
SELECT DISTINCT
col2,
DENSE_RANK() OVER (ORDER BY col2) AS rank
FROM tbl
)
SELECT c1.col1,
c2.col2
FROM c1
FULL OUTER JOIN c2
ON ( c1.rank = c2.rank)
ORDER BY COALESCE( c1.rank, c2.rank)
Results:
| COL1 | COL2 |
|------|--------|
| 1 | x |
| 2 | y |
| 3 | z |
| 4 | (null) |
And to address the additional requirement:
What we want to do is that if any value is present in the col1, then it cannot be repeated in the col2.
Query 2:
WITH c1 AS (
SELECT DISTINCT
col1,
DENSE_RANK() OVER (ORDER BY col1) AS rank
FROM tbl
),
c2 AS (
SELECT DISTINCT
col2,
DENSE_RANK() OVER (ORDER BY col2) AS rank
FROM tbl
WHERE col2 NOT IN ( SELECT TO_CHAR( col1 ) FROM c1 )
)
SELECT c1.col1,
c2.col2
FROM c1
FULL OUTER JOIN c2
ON ( c1.rank = c2.rank)
ORDER BY COALESCE( c1.rank, c2.rank)

Max values on distinct groups

Column1 Column2 Column3
------- ------- -------
jim 1788 5F
jim 2000 9F
jim 500 9F
ben 190 4H
matt 400 46
matt 20 3G
I need to run a query that outputs:
Column1 MaxValue PL
------- ------- -------
jim 2000 9F
jim 2000 NULL
ben 190 4H
matt 400 46
matt 400 NULL
For each value in Column1 (e.g. jim, ben, matt): we group the data by Column1 and for each group we display the row that has the maximum value on column2.
Then, for each row found in this manner, it displays it again but with NULL in column3, if the groupping by Column1 returns more than 1 row and there are smaller values in Column2 than the maximum found number in the previous step.
ben 190 NULL is not displayed because we have ben only once on Column1.
Thank you in advance for any tips or suggestions.
This is what I tried so far but I receive an error prompting me to include Column2 and Column3 in the GROUP By clause, but if I do so I don't reach the desired output as shown above.
CREATE VIEW VIEWB AS
SELECT DISTINCT t1.Column1,
/* MAX_Value */
(MAX(t1.[Column2])) AS [MAX Value],
/* PL */
(CASE
WHEN t1.[Column2] = MAX(t1.[Column2]) THEN t1.[Column3]
ELSE NULL
END) AS PL
FROM TABLEA AS t1
GROUP BY t1.Column1;
Try this code:
DECLARE #t TABLE (Column1 VARCHAR(50), Column2 INT, Column3 VARCHAR(50))
INSERT #t
VALUES
('jim' ,1788 ,'5F'),
('jim' ,2000 ,'9F'),
('jim' ,500 ,'9F'),
('ben' ,190 ,'4H'),
('matt' ,400 ,'46'),
('matt' ,20 ,'3G')
;WITH a AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY Column1 ORDER BY Column2 DESC) RowNum,
MAX(Column2) OVER (PARTITION BY Column1) Maximum
FROM #t
)
SELECT Column1,
Maximum,
CASE WHEN RowNum = 1 THEN Column3 END
FROM a
WHERE RowNum IN (1, 2)
ORDER BY Column3 DESC
If you need to, you can put this in a view.
try this
;with cte as (select *,ROW_NUMBER() over(partition by Column1
order by Column2 desc) as row_num from table_A),
a as (
select Column1,MAX(Column2) [Column2],null [row_num]
from table_A
group by Column1
having COUNT(*)>1),
b as (select Column1,Column2,Column3 from cte where row_num=1
union all
select * from a)
select Column1,Column2 [MaxValue],Column3 [PL] from b
order by Column2 desc,Column1,ISNULL(Column3,'') desc
with data as (
select * from
(values
( 'jim' , 1788 , '5F' ),
( 'jim' , 2000 , '9F' ),
( 'jim' , 500 , '9F' ),
( 'ben' , 190 , '4H' ),
( 'matt' , 400 , '46' ),
( 'matt' , 20 , '3G' )
) foo (col1, col2, col3)
),
maxes as (
select d.col1, d.col2, d.col3
from
data d
inner join (select col1, max(col2) as col2 from data group by col1) m on d.col1 = m.col1 and d.col2 = m.col2
)
select col1, col2, col3
from maxes
union all
select col1, col2, null
from maxes
where exists (select 0 from data where data.col1 = maxes.col1 and data.col2 < maxes.col2)
order by col1, col3 desc
declare #t table(column1 varchar(10),column2 int, column3 varchar(10))
insert into #t
select 'jim', 1788, '5F' union all
select 'jim', 2000, '9F' union all
select 'jim', 500, '9F' union all
select 'ben', 190, '4H' union all
select 'matt', 400, '46' union all
select 'matt', 20, '3G'
select column1,column2,column3 from
(
select *, row_number() over (partition by column1 order by column2 desc) as sno from #t
) as t
where sno=1
union
select t1.column1,t2.column2,NULL
from #t as t1 inner join
(
select Column1, max(Column2) as column2,count(*) as counting from #t
group by column1 having count(*)>1
) as t2
on t1.column1=t2.column1
Might not be the most efficient way of doing this, so if you have a really large table this may not be an ideal solution, but an option:
create table #temp (column1 varchar(10), column2 float, column3 varchar(10))
insert #temp select 'jim', 1788, '5F'
insert #temp select 'jim', 2000, '9F'
insert #temp select 'jim', 500, '9F'
insert #temp select 'ben', 190, '4H'
insert #temp select 'matt', 400, '46'
insert #temp select 'matt', 20, '3G'
SELECT column1, column2 as MaxValue, column3 as PL FROM #temp
WHERE column2=(SELECT Max(column2) FROM #temp t2 WHERE t2.column1=#temp.column1)
union
SELECT column1, column2, NULL FROM #temp
WHERE column2=(SELECT Max(column2) FROM #temp t3 WHERE t3.column1=#temp.column1)
AND EXISTS(SELECT 1 FROM #temp t4 WHERE t4.column2<>#temp.column2 and t4.column1=#temp.column1)
DROP TABLE #temp