SQL query to select alternate value if a value is not found - sql

I am trying to write a sql query such as that in where condition of my query if I search for a key that does not exist, it should take value of key null in below example.
i.e. if I use below query
select value
from myTable
where Id=1 and Key = 't'
As key 't' does not exist, it should take value on key null. So I want to write my query something like this.
select value
from myTable
where Id=1
and (if key = 't' exist then give its value else give value of key = null)
Id
Key
value
1
null
val1
1
a
val2
2
x
val3
2
y
val4
2
null
val5
3
p
val6
4
q
val7
It is not possible for there to be two rows for a given Id to have a key that is NULL, and there will always be at least one row for any Id that is either t or NULL.

One way would be to apply row numbers in order of preference:
DECLARE #id int = 1;
;WITH x AS
(
SELECT Id, [Key], [value], en = ROW_NUMBER() OVER
(ORDER BY CASE WHEN [Key] = 't' THEN 1
WHEN [Key] IS NULL THEN 2 END)
FROM dbo.table
WHERE Id = #id
AND ([Key] = 't' OR [Key] IS NULL)
)
SELECT Id, [Key], [value] FROM x WHERE rn = 1;
That allows you to apply additional rules, like what to do if there are multiple rows with NULL or no rows with either.
A simpler route if those aren't possibilities:
SELECT TOP (1) Id, [Key], [value]
FROM dbo.table
WHERE Id = #id
AND ([Key] = 't' OR [Key] IS NULL)
ORDER BY [Key] DESC; -- puts NULLs last

Related

How to check all the columns are 0 in SQL Server without specifying all columns with OR condition?

I want to return an error flag when all columns are 0s in a temp table.
Is there any good method other than specifying all columns checking to 0 with OR condition?
An OUTER APPLY can be used to calculate the flag.
For example, from this sample data:
create table #test (
id int identity(1,1) primary key,
col1 int not null,
col2 float,
col3 decimal(10,2)
);
insert into #test (col1, col2, col3) values
(1,0,0),(0,0.2,0),(0,null,0.3),(0,0,null),(0,0,0);
This query:
-- sum of nums check
select *
from #test t
outer apply (
select cast(iif(sum(abs(v.num))>0,0,1) as bit) as AllZeroOrNullFlag
from (values (col1),(col2),(col3)) v(num)
) ca
where ca.AllZeroOrNullFlag = 1;
Returns:
id col1 col2 col3 AllZeroOrNullFlag
4 0 0 NULL True
5 0 0 0,00 True
Without more information at the moment, all I can suggest you is the following :
SELECT id, 'ERROR'
FROM temp
WHERE value1+value2+value3 = 0;
Assuming you can provide all numeric columns that you would have checked in OR condition. (EDIT: In fact it would have been AND condition instead of OR...)
SEE A DEMO HERE

Get the latest value for each column

Suppose I have the following "values" table in my SQL Server (2012) DB:
Table1:
Id Col1 Col2 Col3 Col4
And I want to create a second "override" table that will store values to override the original values in case a user needs to do so. So, given the table above, the override table would look as follows:
Overrides:
FK_Id Col1 Col2 Col3 Col4 When_Inserted
Where Overrides.FK_Id references Table1.Id as a foreign key.
So, for example, suppose my Overrides table had the following rows within it with overrides for a row in Table1 with Id=1:
FK_Id: Col1: Col2: Col3: Col4: When_Inserted:
1 Val1_1 Val2_1 Expected_Val3 NULL 1-Jan
1 NULL Val2_2 NULL NULL 2-Jan
1 NULL Expected_Val2 NULL NULL 3-Jan
1 Expected_Val1 NULL NULL NULL 4-Jan
Then, based upon the When_Inserted column - Wanting the latest inserts to take precedence, I'd want the overrides to be as follows:
FK_Id: Col1: Col2: Col3: Col4:
1 Expected_Val1 Expected_Val2 Expected_Val3 NULL
I'm trying to think of a smart way to create this SQL and am coming up with a fairly ugly solution along the lines of:
SELECT
FK_Id
,(
SELECT TOP 1
Col1
FROM
Overrides O1
WHERE
Col1 IS NOT NULL
AND O1.FK_Id = O.FK_Id
ORDER BY
O1.When_Inserted DESC
) Col1
.... <same for each of the other columns> ....
FROM
Overrides O
GROUP BY
FK_Id
I'm sure there has to be a better way that is cleaner and substantially more efficient.
using a common table expression with row_number() (latest first), cross apply() to unpivot your columns, filter for the latest of each column (rn = 1), and finally pivot() back to the same form:
;with cte as (
select o.fk_id, v.Col, v.Value, o.When_Inserted
, rn = row_number() over (partition by o.fk_id, v.col order by o.when_inserted desc)
from overrides o
cross apply (values('Col1',Col1),('Col2',Col2),('Col3',Col3),('Col4',Col4)
) v (Col,Value)
where v.value is not null
)
select fk_id, col1, col2, col3, col4
from (
select fk_id, col, value
from cte
where rn = 1
) s
pivot (max(Value) for Col in (col1,col2,col3,col4)) p
rextester demo: http://rextester.com/KGM96394
returns:
+-------+---------------+---------------+---------------+------+
| fk_id | col1 | col2 | col3 | col4 |
+-------+---------------+---------------+---------------+------+
| 1 | Expected_Val1 | Expected_Val2 | Expected_Val3 | NULL |
+-------+---------------+---------------+---------------+------+
dbfiddle.uk demo comparison of 3 methods
Looking at the io stats for the sample:
unpivot/pivot version:
Table 'Worktable'. Scan count 0, logical reads 0
Table 'overrides'. Scan count 1, logical reads 1
first_value over() version:
Table 'Worktable'. Scan count 20, logical reads 100
Table 'overrides'. Scan count 1, logical reads 1
select top 1 subquery version:
Table 'overrides'. Scan count 5, logical reads 5
Table 'Worktable'. Scan count 0, logical reads 0
You can use first_value():
select distinct fkid,
first_value(col1) over (partition by fkid
order by (case when col1 is not null then 1 else 2 end),
when_inserted desc
) as col1,
first_value(col2) over (partition by fkid
order by (case when col2 is not null then 1 else 2 end),
when_inserted desc
) as col2,
. . .
from t;
The select distinct is because SQL Server does not have the equivalent functionality as an aggregation function.
See my solution is quite different.
IMHO, my script performance will be better provided it give correct output across all sample data.
I have use auto generated id in my script,but in case if you don't have identity id then you can use ROW_NUMBER . and my script is very easy to understand .
declare #t table(id int identity(1,1),FK_Id int,Col1 varchar(50),Col2 varchar(50)
,Col3 varchar(50),Col4 varchar(50),When_Inserted date)
insert into #t VALUES
(1 ,'Val1_1' ,'Val2_1' ,'Expected_Val3', NULL , '2017-01-1')
,(1 ,NULL ,'Val2_2' , NULL , NULL, '2017-01-2')
,(1 ,NULL ,'Expected_Val2', NULL , NULL, '2017-01-3')
,(1 ,'Expected_Val1' , NULL , NULL , NULL, '2017-01-4')
;
WITH CTE
AS (
SELECT *
,CASE
WHEN col1 IS NULL
THEN NULL
ELSE CONCAT (
cast(id AS VARCHAR(10))
,'_'
,col1
)
END col1Code
,CASE
WHEN col2 IS NULL
THEN NULL
ELSE CONCAT (
cast(id AS VARCHAR(10))
,'_'
,col2
)
END col2Code
,CASE
WHEN col3 IS NULL
THEN NULL
ELSE CONCAT (
cast(id AS VARCHAR(10))
,'_'
,col3
)
END col3Code
,CASE
WHEN col4 IS NULL
THEN NULL
ELSE CONCAT (
cast(id AS VARCHAR(10))
,'_'
,col4
)
END col4Code
FROM #t
)
,CTE1
AS (
SELECT FK_Id
,max(col1Code) col1Code
,max(col2Code) col2Code
,max(col3Code) col3Code
,max(col4Code) col4Code
FROM cte
GROUP BY FK_Id
)
SELECT FK_Id
,SUBSTRING(col1Code, charindex('_', col1Code) + 1, len(col1Code)) col1Code
,SUBSTRING(col2Code, charindex('_', col2Code) + 1, len(col2Code)) col2Code
,SUBSTRING(col3Code, charindex('_', col3Code) + 1, len(col2Code)) col3Code
,SUBSTRING(col4Code, charindex('_', col4Code) + 1, len(col4Code)) col4Code
FROM cte1 c1

with clause execution order

In a simplified scenario I have table T that looks somthing like:
Key Value
1 NULL
1 NULL
1 NULL
2 NULL
2 NULL
3 NULL
3 NULL
I also have a very time-consuming function Foo(Key) which must be considered as a black box (I must use it, I can't change it).
I want to update table T but in a more efficient way than
UPDATE T SET Value = dbo.Foo(Key)
Basically I would execute Foo only one time for each Key.
I tried something like
WITH Tmp1 AS
(
SELECT DISTINCT Key FROM T
)
, Tmp2 AS
(
SELECT Key, Foo(Key) Value FROM Tmp1
)
UPDATE T
SET T.Value = Tmp2.Value
FROM T JOIN Tmp2 ON T.Key = Tmp2.Key
but unexpectedly computing time doesn't change at all, because Sql Server seems to run Foo again on every row.
Any idea to solve this without other temporary tables?
One method is to use a temporary table. You don't have much control over how SQL Server decides to optimize its queries.
If you don't want a temporary table, you could do two updates:
with toupdate as (
select t.*, row_number() over (partition by id order by id) as seqnum
from t
)
update toupdate
set value = db.foo(key)
where seqnum = 1;
Then you can run a similar update again:
with toupdate as (
select t.*, max(value) over (partition by id) as as keyvalue
from t
)
update toupdate
set value = keyvalue
where value is null;
You might try it like this:
CREATE FUNCTION dbo.Foo(#TheKey INT)
RETURNS INT
AS
BEGIN
RETURN (SELECT #TheKey*2);
END
GO
CREATE TABLE #tbl(MyKey INT,MyValue INT);
INSERT INTO #tbl(MyKey) VALUES(1),(1),(1),(2),(2),(3),(3),(3);
SELECT * FROM #tbl;
DECLARE #tbl2 TABLE(MyKey INT,TheFooValue INT);
WITH DistinctKeys AS
(
SELECT DISTINCT MyKey FROM #tbl
)
INSERT INTO #tbl2
SELECT MyKey,dbo.Foo(MyKey) TheFooValue
FROM DistinctKeys;
UPDATE #tbl SET MyValue=TheFooValue
FROM #tbl
INNER JOIN #tbl2 AS tbl2 ON #tbl.MyKey=tbl2.MyKey;
SELECT * FROM #tbl2;
SELECT * FROM #tbl;
GO
DROP TABLE #tbl;
DROP FUNCTION dbo.Foo;

How to return only numbers from query where column is nvarchar

I have a simple query that is returning records where "column2" > 0
Here is the data in the database
Column1 Column2
1 123456789
2 123456781
3 13-151-1513
4 alsdjf
5
6 000000000
Her is the query
select column1, replace(a.Payroll_id,'-','')
from table1
where isnumeric(column2) = 1
I'd like to return the following:
Column1 Column2
1 123456789
2 123456781
3 131511513
This mean, I won't select any records when the column is blank (or null), will not return a row if it's not an integer, and will drop out the '-', and would not show row 6 since it's all 0.
How can I do this?
I think you can use something like this :
USE tempdb
GO
CREATE TABLE #Temp
(
ID INT IDENTITY
,VALUE VARCHAR(30)
)
INSERT INTO #Temp (VALUE) VALUES ('1213213'), ('1213213'), ('121-32-13'), ('ASDFASF2123')
GO
WITH CteData
AS
(
SELECT REPLACE(VALUE,'-','') as Valor FROM #Temp
)
SELECT * FROM CteData WHERE (ISNUMERIC(Valor) = 1 AND valor not like '%[0-0]%')
DROP TABLE #Temp
then you can apply validations for empty, NULL,0 etc
If you are using SQL2012 or above you can also use TRY_PARSE that is more selective in its parsing. This function will return NULL if a record can't be converted. You could use it like this:
CREATE TABLE #temp
(
ID INT IDENTITY ,
VALUE VARCHAR(30)
)
INSERT INTO #temp
( VALUE )
VALUES ( '1213213' ),
( '1213213' ),
( '121-32-13' ),
( 'ASDFASF2123' ),
( '0000000' )
SELECT ParsedValue
FROM #temp
CROSS APPLY ( SELECT TRY_PARSE(
Value AS INT ) AS ParsedValue
) details
WHERE ParsedValue IS NOT NULL
AND ParsedValue>0

SQL - Get a subset based on Datetime using OVER clause

I am trying to write a query against the following dataset that will separate the last updated records based on a unique set of fields..
Assume for the following example the the unique set of fields is val1 + val2 + val3.
I tried using window functions to achieve this, but there seems to be another level of complexity for this example.
--DROP TABLE #demo;
-- assume there is a unique key on val1 + val2 + val3
CREATE TABLE #demo
(
id int NOT NULL,
val1 varchar(100) NOT NULL,
val2 varchar(100) NOT NULL,
val3 varchar(100) NOT NULL,
last_updated DATETIME NOT NULL,
active bit not null
);
INSERT INTO #demo VALUES
(0,'a','b','c', '20150817', 0),
(1,'a','b','c', '20150817', 0),
(2,'a','b','c', '20150817', 0),
(3,'a','b','c', '20150815', 0),
(4,'a','b','c', '20150815', 0),
(5,'d','e','f', '20150701', 0),
(6,'d','e','f', '20150630', 0),
(7,'d','e','f', '20150630', 0),
(8,'d','e','f', '20150630', 0)
-- using unique key columns, trying to get only those with the most recent date
SELECT *
, ROW_NUMBER() OVER(PARTITION BY val1, val2, val3 ORDER BY last_updated DESC) AS RowNum
FROM #demo
I am hoping to return only the highlighted records below (id: 0,1,2,5) with the query.
Is this possible? Thanks in advance.
SELECT *
FROM (
SELECT *
, DENSE_RANK() OVER(PARTITION BY val1, val2, val3
ORDER BY last_updated DESC) AS RowNum
FROM #demo
)t
WHERE RowNum = 1
select d.* from
demo d join
(
SELECT val1,val2,val3 ,
max(last_updated) as mx
FROM demo
group by val1,val2,val3) x
on x.val1 = d.val1 and x.val2 = d.val2 and x.val3 = d.val3
and x.mx = d.last_updated
This is one more approach.
Fiddle