Sort columns in a row after numeric value - sql

I have a task where I need to order the results of query in a way, that the highest of 3 values gets displayed in the first column after the ID, the second highest in the middle one and the smallest in the last column.
The tables look like this:
ID
Col1
Col2
Col3
1234
30
50
40
2345
40
30
60
3456
60
50
40
And the result should look like this:
ID
Col1
Col2
Col3
1234
50
40
30
2345
60
40
30
3456
60
50
40
The values are package dimensions which need to be in order to be processed.
Thank you in advance :)

This is pretty simple using apply:
select t.*, v.*
from t outer apply
(select max(case when seqnum = 1 then col end) as col1,
max(case when seqnum = 2 then col end) as col2,
max(case when seqnum = 3 then col end) as col3
from (select v.col,
row_number() over (order by v.col desc) as seqnum
from (values (t.col1), (t.col2), (t.col3)
) v(col)
) v
) v;
SQL Server is quite efficient when using APPLY within a single row. I would expect the performance to be comparable to a bunch of complex case expressions. In addition, this gives more flexibility if any of the values are NULL.
And, it is much easier to expand to more columns!

Unfortunately, there is no simple and short way of achieving this.
Try below query:
select
case when col1 > col2 and col1 > col3 then col1
else case when col2 > col1 and col2 > col3 then col2
else col3 end end,
case when (col1 > col2 and col1 < col3) or (col1 < col2 and col1 > col3) then col1
else case when (col2 > col1 and col2 < col3) or (col2 < col1 and col2 > col3) then col2
else col3 end end,
case when col1 < col2 and col1 < col3 then col1
else case when col2 < col1 and col2 < col3 then col2
else col3 end end
from tbl
SQL fiddle

You may also unpivot it, sort the value and then pivot it back
SELECT *
FROM
(
SELECT t.id, v.col,
col_no = row_number() over (partition by t.id order by v.col desc)
FROM yourtable t
CROSS APPLY
(
VALUES (col1), (col2), (col3)
) v (col)
) d
PIVOT
(
MAX(col)
for col_no in ([1], [2], [3])
) p

the question is not clear but you can use a temp table. First, read the top 3 of data with ordering highest, secondly do it same again with offset value 3, 6 and third times order by lowest.
push the results after each step
read the temp table

Related

Specify row number start point

I have some sample data below and I want to use row_number but make it start when the value is 0 for col3
I have tried the below but it doesn't work
row_number() over (partition by col1,col2, case when col3 = 0 then 1 end order by col4 desc) as row2
Col1
col2
col3
col4
row_number (output wanted)
abc
def
7
500
abc
def
0
300
1
abc
def
1
200
abc
def
0
2
2
abc
def
4
30
Have NULL for others
case when col3 = 0
then row_number() over (partition by col1,col2, col3 order by col4 desc) end as row2
A row number is just a running count of rows. So you should be able to do this:
select
col1, col2, col3, col4,
count(case when col3 = 0 then 1 end)
over (partition by col1, col2 order by col4 desc, ctid) as row2
from ...
order by col1, col2, col4 desc, ctid;
I have added Postgre's internal CTID in order to get a deterministic order for the case of duplicate col4 values. If such duplicates are not possible, you can remove CTID from the ORDER BY clause.
Thanks guys but I just figured it out.
I did
case when col3 =0 then row_number() over (partition by col1,col2,col3=0 order by col4 desc) else null end as Row
so what this did was a running count for both sets of 0 and non0 and I'm just hiding the non 0 ones with the case.

Oracle Group by based on next row value

We are trying to get a group by result by checking the next rows value.
Sample Data:
Table A
COL1 COL2 COL3
---- ---- ----
B BUY 1
B SELL 1.2
B SELL 2
C BUY 3
C SELL 4
C BUY 5
Result:
COL1 COL2 COUNT(1)
---- ---- --------
B BUY 1
B SELL 2
C BUY 1
C SELL 1
C BUY 1
You appear to have ordered by COL3; if this is the case then:
SELECT col1,
col2,
change - COALESCE( LAG( change ) OVER ( PARTITION BY col1 ORDER BY change ), 0 )
AS cnt
FROM (
SELECT col1,
col2,
CASE LEAD( col2 ) OVER ( PARTITION BY col1 ORDER BY col3 )
WHEN col2
THEN NULL
ELSE ROW_NUMBER() OVER ( PARTITION BY col1 ORDER BY col3 )
END AS change
FROM a
)
WHERE change IS NOT NULL;
If I understand correctly, you can do this with a difference of row numbers approach:
select col1, col2, count(*)
from (select t.*,
row_number() over (partition by col1 order by col3) as seqnum,
row_number() over (partition by col1, col2 order by col3) as seqnum_2,
from t
) t
group by col1, col2, (seqnum - seqnum_2);
This identifies groups of adjacent col2 values based on the ordering in col3.

SQL Server : get max of the column2 and column3 value must be 1

I have an output of some part of my stored proedure like this:
col1 col2 col3 col4
--------------------------
2016-05-05 1 2 2
2016-05-05 1 3 32
2016-05-12 2 1 11
2016-05-12 3 1 31
Now I need to get result based on this condition
col2 = 1 and col3 = max or col3 = 1
and col2 = max
The final result should be
col1 col2 col3 col4
-------------------------
2016-05-05 1 3 32
2016-05-12 3 1 31
Not sure if thats the most efficient way , but you can use ROW_NUMBER() :
SELECT * FROM (
SELECT t.*,
ROW_NUMBER() OVER(PARTITION BY t.col1 ORDER BY t.col3 DESC) as rnk,
WHERE t.col2 = 1
UNION ALL
SELECT t.*,
ROW_NUMBER() OVER(PARTITION BY t.col1 ORDER BY t.col2 DESC) as rnk,
WHERE t.col3 = 1) tt
WHERE rnk = 1
This will give you all the records with
(col2=1 and col3=max) or (col3=1 and col2=max)
This is a bit tricky. Your data has no ambiguities, such as duplicate maximuma in col4 or "1" values in both col2 and col3.
The following is a direct translation of the logic in your question:
select t.*
from t
where t.col4 = (select max(t2.col4)
from t t2
where t2.col1 = t.col1 and (t2.col2 = 1 or t2.col3 = 1)
);
Try this. Note if there are more than 1 same max value, then you need all of those in output. And it will work for all scenarios, even when col1 is not in sync with col2 and col3.
I am first finding highest values of col2 and col3 and assigning them value as 1. Then in outer query, I am using your join condition. Demo created for Postgres DB as SQLServer wasn't available.
SQLFiddle Demo
select col1,col2,col3,col4
from
(
select t.*,
RANK() OVER(ORDER BY col3 DESC) as col3_max,
RANK() OVER(ORDER BY col2 DESC) as col2_max
from your_table t
) t1
where
(col2=1 and col3_max=1)
OR
(col3=1 and col2_max=1)
Alternative way:
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY col1 ORDER BY iif(col2 = 1, col3, col2) DESC) as r
FROM tbl) t
WHERE r = 1

How to get the minimum value in a set of columns while excluding a certain value?

I have been trying a query to select the minimum value in a row but also exclude a certain value (-998).
The table looks like this:
col1 col2 col3
----------------------------------
1 1 -998
2 -998 2
3 2 1
-998 1 3
So in the first row, the minimum value would be 1; in the second row, it would be 2; and in the third row, it would be 1 again.
I tried using a case statement and excluding -998 in each condition, but it keeps grabbing -998 for some reason.
SELECT
CASE
WHERE (col1 <= col2 and col1 <= col3) and col1 != -998 THEN col1
WHERE (col2 <= col1 and col2 <= col3) and col2 != -998 THEN col2
WHERE (col3 <= col1 and col3 <= col2) and col3 != -998 THEN col3
END AS [MIN_VAL]
FROM myTable
If anyone can point me in the right direction that would ge awesome.
Use the table value constructor to unpivot your column values and exclude values from there.
SQL Fiddle
MS SQL Server 2012 Schema Setup:
create table YourTable
(
col1 int,
col2 int,
col3 int
);
insert into YourTable values
(1 , 1 , -998),
(2 , -998 , 2 ),
(3 , 2 , 1 ),
(-998 , 1 , 3 );
Query 1:
select (
select min(R.Value)
from (values(T.col1),
(T.col2),
(T.col3)) as R(Value)
where R.Value <> -998
) as min_val
from YourTable as T;
Results:
| MIN_VAL |
|---------|
| 1 |
| 2 |
| 1 |
| 1 |
How about this:
use tempdb
create table myTable(
col1 int,
col2 int,
col3 int
)
insert into myTable values
(1, 1, -998),
(2, -998, 2),
(3, 2, 1),
(-998, 1, 3)
;with cte as(
select
rn = row_number() over(order by (select null)),
col = col1
from myTable
union all
select
rn = row_number() over(order by (select null)),
col = col2
from myTable
union all
select
rn = row_number() over(order by (select null)),
col = col3
from myTable
)
select
minimum = min(col)
from cte
where col <> - 998
group by rn
drop table mytable
SELECT
CASE
WHEN (col1 <= col2 or col2 = -998)
and (col1 <= col3 or col3 = -998)
and col1 != -998
THEN col1
WHEN (col2 <= col1 or col1 = -998)
and (col2 <= col3 or col3 = -998)
and col2 != -998
THEN col2
WHEN (col3 <= col1 or col1 = -998)
and (col3 <= col2 or col2 = -998)
and col3 != -998
THEN col3
END AS [MIN_VAL]
FROM myTable;

Element-wise quotient of two columns in SQL

How can I combine the columns returned by two SELECT statements to give their element-wise quotient?
Query 1:
SELECT COUNT(*) AS count
FROM table1
WHERE col2 = 1 AND col3 > 5
GROUP BY col4
ORDER BY col4
Query 2:
SELECT COUNT(*) AS count
FROM table1
WHERE col2 = 1
GROUP BY col4
ORDER BY col4
So if they return something like:
Query 1 Query 2
count count
-----------------------
1 5
2 4
I will get:
quotient
-------
0.2
0.5
With the 4-column version of the question, we can assume that the quotient is between groups with the same value in col4. So, the answer becomes:
SELECT col4, SUM(CASE WHEN col3 > 5 THEN 1 ELSE 0 END) / COUNT(*) AS quotient
FROM table1
WHERE col2 = 1
GROUP BY col4;
I've retained col4 in the output because I don't think the ratios (quotients) will be useful without something to identify which quotient is associated with which values, though theoretically, the answer doesn't want that column in the output.
In this case, you don't need two separate queries at all:
SELECT SUM(col3 > 5) / COUNT(*)
FROM table1
WHERE col2 = 1
GROUP BY col4
ORDER BY col4
In case your actual queries cannot be simplified as per the other answers, you can join the subqueries, like this:
select j1.count / j2.count as quotient
from (
SELECT col4, COUNT(*) AS count
FROM table1
WHERE col2 = 1 AND col3 > 5
GROUP BY col4
) j1
join (
SELECT col4, COUNT(*) AS count
FROM table1
WHERE col2 = 1
GROUP BY col4
) j2 on j1.col4=j2.col4