SQL: Select the minimum value from multiple columns with null values - sql

I have a table like this one
ID Col1 Col2 Col3
-- ---- ---- ----
1 7 NULL 12
2 2 46 NULL
3 NULL NULL NULL
4 245 1 792
I wanted a query that yields the following result
ID Col1 Col2 Col3 MIN
-- ---- ---- ---- ---
1 7 NULL 12 7
2 2 46 NULL 2
3 NULL NULL NULL NULL
4 245 1 792 1
I mean, I wanted a column containing the minimum values out of Col1, Col2, and Col 3 for each row ignoring NULL values. In a previous question (What's the best way to select the minimum value from multiple columns?) there is an answer for non NULL values. I need a query as efficient as possible for a huge table.
Select Id,
Case When Col1 < Col2 And Col1 < Col3 Then Col1
When Col2 < Col1 And Col2 < Col3 Then Col2
Else Col3
End As MIN
From YourTableNameHere

Assuming you can define some "max" value (I'll use 9999 here) that your real values will never exceed:
Select Id,
Case When Col1 < COALESCE(Col2, 9999)
And Col1 < COALESCE(Col3, 9999) Then Col1
When Col2 < COALESCE(Col1, 9999)
And Col2 < COALESCE(Col3, 9999) Then Col2
Else Col3
End As MIN
From YourTableNameHere;

You didn't specify which version of Teradata you're using. If you're using version 14+ then you can use least.
Unfortunately least will return null if any of its arguments are null. From the docs:
LEAST supports 1-10 numeric values.
If numeric_value is the data type of the first argument, the return
data type is numeric. The remaining arguments in the input list must
be the same or compatible types. If either input parameter is NULL,
NULL is returned.
But you can get around that by using coalesce as Joe did in his answer.
select id,
least(coalesce(col1,9999),coalesce(col2,9999),coalesce(col3,9999))
from mytable

This might work:
Select id, Col1, Col2, Col3, least(Col1, Col2, Col3) as MIN From YourTableNameHere

in this way you don't need to check for nulls, just use min and a subquery
select tbl.id,tbl.col1,tbl.col2,tbl.col3,
(select min(t.col)
from (
select col1 as col from tbl_name t where t.id=tbl.id
union all
select col2 as col from tbl_name t where t.id=tbl.id
union all
select col3 as col from tbl_name t where t.id=tbl.id
)t)
from tbl_name tbl
Output:
1 7 NULL 12 7
2 2 46 NULL 2
3 NULL NULL NULL NULL
4 245 1 792 1

Just modify your query with coalesce():
Select Id,
(Case When Col1 <= coalesce(Col2, col3, col1) And
Col1 <= coalesce(Col3, col2, col1)
Then Col1
When Col2 <= coalesce(Col1, col3, col2) And
Col2 <= coalesce(Col3, col1, col2)
Then Col2
Else Col3
End) As MIN
From YourTableNameHere;
This doesn't require inventing a "magic" number or over-complicating the logic.

I found this solution to be more efficient than using multiple case statement clauses, which can get extremely lengthy when evaluating data from several columns across one row.
Also, I can't take credit for this solution as I found it on some website a year or so ago. Today I needed a refresh on this logic, and I couldn't find it anywhere. I found my old code and decided to share it in this forum now.
Creating your test table:
create table #testTable(ID int, Col1 int, Col2 int, Col3 int)
Insert into #testTable values(1,7,null,12)
Insert into #testTable values(2,2,46,null)
Insert into #testTable values(3,null,null,null)
Insert into #testTable values(4,245,1,792)
Finding min value in row data:
Select ID, Col1, Col2, Col3 ,(SELECT Min(v) FROM ( VALUES (Col1), (Col2), (Col3) ) AS value(v)) [MIN] from #testTable order by ID

Related

Compute trace of matrix in database

I have a table in a Postgres that stores 10x10 matrices, where each row has it's own entry, defined as:
id, matrix_id, row_id, col1, col2, col3...
I'd like to compute the trace (sum of main diagonal) for every matrix identified by its matrix_id, that is, for every matrix_id, I would like to get (col1 where row_id=1) + (col2 where row_id=2) + (col3 where row_id=3)...
I've tried grouping it by matrix_id but then I cannot use subqueries, something like:
select matrix_id, (select col1 where row_id=1) + (col2 where row_id=2) +
(col3 where row_id=3) ... from matrix group by matrix_id;
but it doesn't work this way.
How could I do that?
So long as they are all 10x10 matrices, use a case statement like so:
select matrix_id,
sum(
case row_id
when 1 then col1
when 2 then col2
when 3 then col3
when 4 then col4
when 5 then col5
when 6 then col6
when 7 then col7
when 8 then col8
when 9 then col9
when 10 then col10
end
) as trace
from matrix
group by matrix_id;
Had variable-sized matrices been allowed, you could transpose columns to rows via to_jsonb() and then sum where row_id = <column suffix>.
EDIT TO ADD
Based on your comment, you really should update your version of PostgreSQL. That said, try a CTE to filter on the new trace column:
with traces as (
select matrix_id,
sum(
case row_id
when 1 then col1
when 2 then col2
when 3 then col3
when 4 then col4
when 5 then col5
when 6 then col6
when 7 then col7
when 8 then col8
when 9 then col9
when 10 then col10
end
) as trace
from matrix
group by matrix_id
)
select *
from traces
where trace > 100;

How can I return a list of row names and column names where the value is greater than 0 in SQL?

I've put together a reconciliation tool in SQL Server which identifies the number of record breaks by field (col 2 - col 4) between two identical (data types/structure) sources. The output returned is in the format below, grouped on col 1.
Col1 Col2 Col3 Col4
X 0 0 1
Y 0 1 1
Z 1 0 1
I am trying to manipulate the output so that it provides a list of the Col 1 identifier and the name of any column names (col 2 - col 4) which have breaks (value > 0).
The expected output based on the above data would look like this.
Col1 FieldBreak
X Col2
Y Col3
Y Col4
Z Col2
Z Col4
I'm newer to SQL (6 months of professional experience) and am stuck. Any help would be much appreciated!
In any database, you can use:
select col1, 'col2' as col
from t
where col2 = 1
union all
select col1, 'col3' as col
from t
where col3 = 1
union all
select col1, 'col4' as col
from t
where col4 = 1;
There are probably more efficient methods, but those depend on the database. And for a small table efficiency may not be a concern.
In SQL Server, you would unpivot using apply:
select t.col1, v.*
from t cross apply
(values ('col2', t.col2), ('col3', t.col3) . . .
) v(col, val)
where v.val is not null;
If you have a lot of columns, you can construct the expression using a SQL statement (from INFORMATION_SCHEMA.COLUMNS) and/or using a spreadsheet.

Update one column value with another column value using SQL Query

I have a table which has three columns with integer values. In first two columns I have some values. Now I need to update the third column with the value which is minimum of other two columns.
I have tried using
update table set col3 = (SELECT CASE WHEN (col2 is null OR col1 < col2 )
THEN col1
ELSE col2
END AS col3
FROM table
WHERE col1 is not null or col2 is not null)`.
But I am getting an error like below:
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
You can use LEAST in order to update:
UPDATE mytable
SET col3 = LEAST(COALESCE(col1, col2), COALESCE(col2, col1))
WHERE col1 IS NOT NULL OR col2 IS NOT NULL
You don't have to query your table table to get col2, col1 values. You can use them directly in the SET part of your UPDATE query.
Note: COALESCE is used to handle NULL values. If both col1, col2 are NULL then col3 is also set to NULL.
In SQL Server you can use:
UPDATE mytable
SET col3 = CASE
WHEN col2 IS NULL OR col1 < col2 THEN col1
ELSE col2
END
WHERE col1 IS NOT NULL OR col2 IS NOT NULL
In SQL Server 2012, or later, you can use IIF:
UPDATE mytable
SET col3 = IIF(col2 IS NULL OR col1 < col2, col1, col2)
WHERE col1 IS NOT NULL OR col2 IS NOT NULL
Demo here
You don't need to join to the table it self, you can use the data from the columns in the same query
update table
set col3 = CASE WHEN (col2 is null OR col1 < col2 )
THEN col1 ELSE col2 END
WHERE col1 is not null or col2 is not null
The problem with your query is that you are using a correlated query, that selects col1 and col2(which is basically fine but unnecessary) and you are not filtering the results(which col1,col2 to take?) so for each col3 you want to update, you have a 'bunch' of other values, when you can only have 1. If you want your query to work all you have to do is add a relation condition.

SQL Selecting MIN value from row data with null values

My table in Oracle is like this
Col Col1 Col2 Col3 ColR
-- ---- ---- ---- ----
30 73 40 null -10
60 32 null 20 40
90 80 null null 10
80 45 81 30 50
I can also set 0 instead of null in the above column.
I need to find the min value from Col1,Col2,Col3 ignoring null or 0 and populate the ColR by subtracting from Col.
EDIT:
i wrote a CASE statement which doesn't work due to the null values inside my table.
SELECT col,col1,col2,col3,
CASE
WHEN Col1 < Col2 AND Col1 < Col3
THEN Col - Col1
WHEN Col2 < Col1 AND Col2 < Col3
THEN Col - Col2
ELSE Col - Col3
END ColR
FROM
(SELECT col,col1,
CASE
WHEN col22 IS NULL
THEN NULL( i can also SET TO 0 but it will mess WITH my other calculation TO find MIN)
ELSE ROUND( 100* ( (col22) / (col44)))
END col2 ,
CASE
WHEN col33 IS NULL
THEN NULL
ELSE ROUND( 100* ( (col33) / (col44)))
END col3
FROM TABLE
)
I have just included the case statement inside my select query. all the the column values all populated from another query.
It sounds like you want something like
SELECT least( (case when col1 is null or col1 = 0 then 999999999 else col1 end),
(case when col2 is null or col2 = 0 then 999999999 else col2 end),
(case when col3 is null or col3 = 0 then 999999999 else col3 end) )
FROM <<table name>>
where 999999999 is some numeric value that is large enough that it will always be larger than any other valid value. If it is possible that all three columns will have NULL or 0 values, then you'd probably want to add an additional check that if the result of that least function is 999999999 that you return 0 or NULL or whatever else makes sense.
#X-Zero was kind enough to put together a working SQL Fiddle example of this construct. Note that his example is filtering out the rows where all three columns have either NULL or 0 values.
// IF YOU NEED MINIMAL FROM COL1 or COL (ANY COLUMN)
SELECT MIN (COL1) FROM (SELECT * FROM TABLE WHERE COL1 IS NOT NULL)
Can you please elaborate I am not able to help you with this small set of info actually.
Oracle NVL Usage:
nvl(check_expression, replacement_value)
So
nvl(col2,0) ought to take of nulls that mess with your math.
So try:
CASE
WHEN nvl(col1,0) < nvl(col2,0) AND nvl(col1,0) < nvl(col3,0)
THEN Col - nvl(col1,0)
WHEN nvl(col2,0) < nvl(col1,0) AND nvl(col2,0) < nvl(col3,0)
THEN Col - nvl(col2,0)
ELSE Col - nvl(col3,0)
END ColR
EDIT: Taking X-Zero's point which I missed. I think if you replace the NULLS with 9999999 instead of 0, the logic will work, although that may be too specific to this sample data and not a real world solution.
If you want to ignore nulls in a column, you could wrap them with the NVL function. This replaces null values in a column with the value specified, for which you could use some large number. For example:
NVL(col1,99999)
Oracle Database SQL Reference - NVL: http://docs.oracle.com/cd/B19306_01/server.102/b14200/functions105.htm

SQL query grouped parameter maximum

Let's say I had two columns in a database, col1 and col2. Column 2 is the time, Column 1 something. In my query, I want to do the following:
I want to SELECT * from my table and group the results by col1. However, I only want those entries where for the grouped col1 there is no value of col2 higher than a certain value. Meaning that, I only want those col1-s for which col2 does not exceed a certain value.
If, for instance, I had three rows, as follows:
ROW1: col1 = val1, col2 = 3
ROW2: col1 = val1, col2 = 5
ROW3: col1 = val2, col2 = 3
ROW4: col1 = val2, col2 = 4
And I do not want the time for any of them to exceed 4, then, as a result, I would only want ROW3 or ROW4, which, does not matter, for col1 is the same and is grouped. But in rows 1 and 2, that are grouped by col1's value "val1", in one of them col2 DOES exceed 4, therefore, I do not want any of them.
SELECT col1 FROM table GROUP BY col1 HAVING MAX(col2) <= 4
Because you want only the common value (col1) from the group, you can use GROUP BY. When you do a GROUP BY (aggregate) query, you can use the HAVING clause to apply a filter to the aggregated data set.
I am not use I got the point (my english is not good).
I think sub-query is the best choice.
Note: this example should work with mySql ...
SELECT *
FROM table
WHERE col1 IN
(SELECT col1 FROM table WHERE col2 < 5 GROUP BY col1)
ORDER BY col1
CREATE TABLE x (
t TIME NOT NULL,
v INT NOT NULL );
INSERT INTO x VALUES
('13:14:00', 24),
('13:14:00', 27),
('13:14:00', 29),
('17:12:00', 14),
('17:12:00', 20),
('17:12:00', 24);
SELECT t, MAX(v) AS mv FROM x
GROUP BY t
HAVING mv <= 25;
Or do I misunderstand the question?