Query based on multiple rows and multiple columns - sql

Database : Azure SQL server 2019, .net core 3.0
I'm using stored procedure for querying data.
--Table Structure
create table yourtable
(
id int,
class int,
islab bit,
isschool bit
);
insert into yourtable
values (1, 1, 1, 1),
(1, 2, 1, 1),
(1, 3, 1, 1),
(2, 1, 1, 0),
(2, 2, 1, 1),
(2, 3, 1, 1)
Now if I want a query to return all unique Id's where class = 1 and 2 and islab = 1 and isschool = 1, it should return only Id =1 because
a) Id=1 has both classes i,e (1,2) and in both classes islab = 1 and isschool = 1
b) Id=2 is not true for this condition because in classes 1 value for isschool = 0
Can you help me write this query? Currently I'm getting all row for input classes than in c# using list check all conditions. It's working but I want do all in SQL
Also I think using cursor in stored procedure I can have same result as in C# but in C# it's easy as we have Collection and various methods like intersect between lists and so on.

Assuming that islab and isschool are actually a bit (as bool doesn't exist in SQL Server), one method would be to use a HAVING with conditional aggregation. So, for the first one, you would do the following:
SELECT id
FROM dbo.yourtable
WHERE islab = 1
AND isschool = 1
GROUP BY id
HAVING COUNT(CASE class WHEN 1 THEN 1 END) = 1
AND COUNT(CASE class WHEN 2 THEN 1 END) = 1;

The question is not clear, but based on your expected output, I think you want a query that, for a given set of classes, finds the ids where there are no records with isschool = 0 or islab = 0. You can do this with a NOT EXISTS condition:
WITH mytab AS
(
SELECT *
FROM yourtable
WHERE class IN (1,2,3) -- Change this line to get your 3 different outputs
)
SELECT DISTINCT id
FROM mytab t1
WHERE NOT EXISTS
(
SELECT *
FROM mytab t2
WHERE t1.id = t2.id
AND (t2.islab = 0 OR t2.isschool = 0)
)
For class IN (1,2) this returns id 1.
For class IN (2,3) this returns ids 1 and 2.
For class IN (1,2,3) this returns id 1.
The CTE limits to the classes we want to consider. The subquery in the NOT EXISTS finds ids that should be eliminated because either isschool = 0 or islab = 0.
An alternative way of doing this, using a LEFT JOIN instead of the NOT EXISTS condition is:
WITH mytab AS
(
SELECT *
FROM yourtable
WHERE class IN (1,2,3) -- Change this line to get your 3 different version
)
SELECT DISTINCT t1.id
FROM mytab t1 LEFT OUTER JOIN mytab t2
on t1.id = t2.id AND (t2.islab = 0 OR t2.isschool = 0)
WHERE t2.id is null

Related

select subquery using data from the select statement?

I have two tables, headers and lines. I need to grab the batch_submission_date from the header table, but sometimes a query for batch_id will return a null for batch_submission_date, but will also return a parent_batch_id, and if we query THAT parent_batch_id as a batch_id, it will then return the correct batch_submission_date.
e.g.
SELECT t1.batch_id,
t1.parent_batch_id,
t2.batch_submission_date
FROM db.headers t1, db.lines t2
WHERE t1.batch_id = '12345';
output = 12345, 99999, null
Then we use that parent batch_id as a batch_id :
SELECT t1.batch_id,
t1.parent_batch_id,
t2.batch_submission_date
FROM db.headers t1, db.lines t2
WHERE t1.batch_id = '99999';
and we get output = 99999,99999,'2018-01-01'
So I'm trying to write a query that will do this for me - anytime a batch_id's batch_submission_date is null, we find that batch_id's parent batch_id and query that instead.
This was my idea - but I just get back null both for bp_batch_submission_date and for new_submission_date.
SELECT
t1.parent_id as parent_id,
t1.BATCH_ID as bp_batch_id,
t2.BATCH_LINE_NUMBER as bp_batch_li,
t1.BATCH_SUBMISSION_DATE as bp_batch_submission_date,
CASE
WHEN t1.BATCH_SUBMISSION_DATE is null
THEN
(SELECT a.BATCH_SUBMISSION_DATE
FROM
db.headers a,
db.lines b
WHERE
a.SD_BATCH_HEADERS_SKEY = b.SD_BATCH_HEADERS_SKEY
and a.parent_batch_id = bp_batch_id
and b.batch_line_number = bp_batch_li
) END as new_submission_date
FROM
db.headers t1,
db.lines t2
WHERE
t1.SD_BATCH_HEADERS_SKEY = t2.SD_BATCH_HEADERS_SKEY
and (t1.BATCH_ID = '12345' or t1.PARENT_BATCH_ID = '12345')
and t2.BATCH_LINE_NUMBER = '1'
GROUP BY
t2.BATCH_CLAIM_LINE_STATUS_DESC,
t1.PARENT_BATCH_ID,
t1.BATCH_ID,
t2.BATCH_LINE_NUMBER,
t1.BATCH_SUBMISSION_DATE;
is what I'm trying to do possible? using the bp_batch_id and bp_batch_li variables
Use CTE (common table expression) to avoid redundant code, then use coalesce() to find parent date in case of null. In your first queries you didn't attach joining condition between two tables, I assumed it's based on sd_batch_headers_skey like in last query.
dbfiddle demo
with t as (
select h.batch_id, h.parent_batch_id, l.batch_submission_date bs_date
from headers h
join lines l on l.sd_batch_headers_skey = h.sd_batch_headers_skey
and l.batch_line_number = '1' )
select batch_id, parent_batch_id,
coalesce(bs_date, (select bs_date from t x where x.batch_id = t.parent_batch_id)) bs_date
from t
where batch_id = 12345;
You could use simpler syntax with connect by and level <= 2 but if in your data there are really rows containing same ids (99999, 99999) then we get cycle error.

SQL Server - How to check if a value does not exist in other rows of the same table for same column values?

Following are the two tables in SQL Server: TABLE_A and TABLE_B
I need to get the output as follows:
Get IDs from TABLE_A where Exist = 0
We would get 100, 101 & 102
Now, among 100, 101 & 102, no other rows (in the same table) with the same ID value should have Exist = 1
Hence, 100 can't be selected as it has Exist = 1 in the 2nd row.
So, only 101 & 102 remain
With the remaining ID values (101 & 102), check against the ID column in TABLE_B where 'Exist' column value should not be equal to '1' in any of the rows
In TABLE_B, 4th row has Exist = 1 for 102. So, that can't be selected
We have only 101 now. This is required output and that should be selected.
Could you let me know how to write the simplest query to achieve this please? Let me know if the question needs to be improved.
You can use exists & not exists :
with t as (
select t1.*
from t1
where exists (select 1 from t1 t11 where t11.id = t1.id and t11.exists = 0) and
not exists (select 1 from t1 t11 where t11.id = t1.id and t11.exists = 1)
)
select t.*
from t
where not exists (select 1 from t2 where t.id = t2.id and t2.exists = 1);
Try:
SELECT
ID,
SUM(CAST(Exist AS int)) AS [Exists]
FROM
TABLE_A
GROUP BY ID
HAVING SUM(CAST(Exist AS bit)) = 0
will give you the answer to the first part. You can then JOIN this to a similar query for TABLE_B. That is a "simple" way to show how this works. You can write more complex queries as that from #Yogest Sharma
Like #Peter Smith mentioned, you can use the aggregate function SUM. Note that you would need a cast since you cannot use the aggregate function on a field that has a BIT datatype
;WITH CTE AS
(
SELECT ID, SUM(CAST(Exist AS INT)) AS AggExist FROM TABLE_A GROUP BY ID
UNION
SELECT ID, SUM(CAST(Exist AS INT)) As AggExist FROM TABLE_B GROUP BY ID
)
SELECT ID, SUM(AggExist) FROM CTE GROUP BY ID
HAVING SUM(AggExist) = 0
Here is the demo

how to scan each row of a table, and update current row based on previous row?

I need to update the current row using the following logic:
if current row is null, then set it as previous row
if current row is not null, then no action
the 1st row is not null, then NULL appears randomly
Those NULLs need to be updated using the logic previously mentioned
e.g.
1. 1
2. null
3. null
4. 2
5. null
6. null
needs to be updated as
1. 1
2. 1
3. 1
4. 2
5. 2
6. 2
How to do it in SQL?
Thanks
r
In case of two Null values in a row, you need to define the least non-null value of the table, so I think Outer Apply will handle your problem:
CREATE TABLE #TB(ID Int Identity(1, 1), Value Int)
INSERT INTO #TB([Value]) VALUES(1),(Null),(Null),(2),(Null),(Null)
UPDATE G SET G.Value = GG.Value
FROM
#TB AS G
OUTER APPLY
(SELECT
TOP 1 *
FROM
#TB AS GG
WHERE
GG.Value IS NOT NULL
AND
GG.ID < G.ID
ORDER BY
GG.ID DESC
) AS GG
WHERE
G.Value IS NULL
SELECT * FROM #TB AS T
but note, that if the first value is Null it will not give you the results, as you have not defined the logic for this scenario.
This might help:
SELECT
t1.col1,
t1.col2 AS previous,
(SELECT
t2.col2
FROM table_1 t2
WHERE t2.col1 = (SELECT
MAX(t3.col1)
FROM table_1 t3
WHERE t3.col1 <= t1.col1
AND col2 IS NOT NULL))
AS new
FROM table_1 t1;
result
Where are you using this SQL code? If you are using Hive SQL for example, there is a function which allows you to directly get last non null value:
LAST_VALUE(col, true) over (PARTITION BY id ORDER BY date)
Oracle 10g has also a function to do this, as adressed in this thread:
Fill null values with last non-null amount - Oracle SQL
Are you familiar with window functions?
while (select count(*) FROM Table_1 where c1_derived = '') > 0
begin
update top(1) Table_1
set c1_derived = (select c1_derived from Table_1 t2 where (t2.id = [Table_1].id-1))
where c1_derived = ''
end
Try the below script. (sql 2008 +)
CREATE TABLE #table(id Int Identity(1, 1), value Int)
INSERT INTO #table([Value]) VALUES(1),(Null),(Null),(2),(Null),(Null)
;WITH cte AS
(
SELECT ID,Value,ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS row
FROM #table
)
SELECT a.ID,max(b.Value)
FROM cte a
INNER JOIN cte b ON a.row >=b.row
GROUP BY a.ID
drop table #table
Edit2 this also another script using "UNBOUNDED PRECEDING "
CREATE TABLE #table(id Int Identity(1, 1), value Int)
INSERT INTO #table([Value]) VALUES(1),(Null),(Null),(2),(Null),(Null)
select * ,max(t.value) over(order by Id Rows UNBOUNDED PRECEDING) maxValue
from #table t
drop table #table
check this link about "OVER Clause"
https://learn.microsoft.com/en-us/sql/t-sql/queries/select-over-clause-transact-sql

SQL: I want a row to be return with NULL even if there is no match to my IN clause

I would like my SQL query to return a row even if there is no row matching in my IN clause.
For exemple this query:
SELECT id, foo
FROM table
WHERE id IN (0, 1, 2, 3)
would return:
id|foo
0|bar
1|bar
2|bar
3|null
But instead I have (because no row with id 3):
id|foo
0|bar
1|bar
2|bar
I have been able to find this trick:
SELECT tmpTable.id, table.bar
FROM (
SELECT 0 as id
UNION SELECT 1
UNION SELECT 2
UNION SELECT 3
) tmpTable
LEFT JOIN
(
SELECT table.foo, table.id
FROM table
WHERE table.id IN (0, 1, 2, 3)
) table
on table.id = tmpTable.id
Is there a better way?
Bonus: How to make it work with myBatis's list variable?
overslacked is right. Most SQL developers use an auxiliary table that stores integers (and one that stores dates). This is outlined in an entire chapter of Joe Celko's "SQL for Smarties".
Example:
CREATE TABLE numeri ( numero INTEGER PRIMARY KEY )
DECLARE #x INTEGER
SET #x = 0
WHILE #x < 1000
BEGIN
INSERT INTO numeri ( numero ) VALUES ( #x )
SET #x = #x + 1
END
SELECT
numero AS id,
foo
FROM
numeri
LEFT OUTER JOIN my_table
ON my_table.id = numero
WHERE
numero BETWEEN 0 AND 3
Main Goal of Programming minimal code high performance no need this things just remove id 3 from in clause
What about just saying:
SELECT id, foo
FROM table
WHERE id >= 0 AND <= 3

Select column value that matches a combination of other columns values on the same table

I have a table called Ads and another Table called AdDetails to store the details of each Ad in a Property / Value style, Here is a simplified example with dummy code:
[AdDetailID], [AdID], [PropertyName], [PropertyValue]
2 28 Color Red
3 28 Speed 100
4 27 Color Red
5 28 Fuel Petrol
6 27 Speed 70
How to select Ads that matches many combinations of PropertyName and PropertyValue, for example :
where PropertyName='Color' and PropertyValue='Red'
And
where PropertyName='Speed' and CAST(PropertyValue AS INT) > 60
You are probably going to do stuff like this a lot so I would start out by making a view that collapses all of the properties to a single row.
create view vDetail
as
select AdID,
max(case PropertyName
when 'Color' then PropertyValue end) as Color,
cast(max(case PropertyName
when 'Speed' then PropertyValue end) as Int) as Speed,
max(case PropertyName
when 'Fuel' then PropertyValue end) as Fuel
from AdDetails
group by AdID
This approach also solves the problem with casting Speed to an int.
Then if I select * from vDetails
This makes it easy to deal with when joined to the parent table. You said you needed a variable number of "matches" - note the where clause below. #MatchesNeeded would be the count of the number of variables that were not null.
select *
from Ads a
inner join vDetails v
on a.AdID = v.AdID
where case when v.Color = #Color then 1 else 0 end +
case when v.Spead > #Speed then 1 else 0 end +
case when v.Fuel = #Fuel then 1 else 0 end = #MatchesNeeded
I think you have two main problems to solve here.
1) You need to be able to CAST varchar values to integers where some values won't be integers.
If you were using SQL 2012, you could use TRY_CAST() ( sql server - check to see if cast is possible ). Since you are using SQL 2008, you will need a combination of CASE and ISNUMERIC().
2) You need an efficient way to check for the existence of multiple properties.
I often see a combination of joins and where clauses for this, but I think this can quickly get messy as the number of properties that you check gets over... say one. Instead, using an EXISTS clause tends to be neater and I think it provides better clues to the SQL Optimizer instead.
SELECT AdID
FROM Ads
WHERE 1 = 1
AND EXISTS (
SELECT 1
FROM AdDetails
WHERE AdID = Ads.AdID
AND ( PropertyName='Color' and PropertyValue='Red' )
)
AND EXISTS (
SELECT 1
FROM AdDetails
WHERE AdID = Ads.AdID
AND PropertyName='Speed'
AND
(
CASE
WHEN ISNUMERIC(PropertyValue) = 1
THEN CAST(PropertyValue AS INT)
ELSE 0
END
)
> 60
)
You can add as many EXISTS clauses as you need without the query getting particularly difficult to read.
Something like this might work for 2 conditions, you would have to adapt depending on the number of conditions
select a.*
from ads as a
join addetails as d1 on d1.adid = a.id
join addetails as d2 on d2.adid = a.id
where (d1.PropertyName='Color' and d1.PropertyValue='Red')
and (d2.PropertyName='Speed' and d2.CAST(PropertyValue AS INT) > 60)
DECLARE #AdDetails TABLE
(
AdDetailID INT,
AdID INT,
PropertyName VARCHAR(20),
PropertyValue VARCHAR(20)
)
INSERT INTO #AdDetails
( AdDetailID, AdID, PropertyName, PropertyValue )
VALUES
(2, 28, 'Color', 'Red'),
(3, 28, 'Speed', '100'),
(4, 27, 'Color', 'Red'),
(5, 28, 'Fuel', 'Petrol'),
(6, 27, 'Speed', '70');
--Col1
DECLARE #ColorValue VARCHAR(20) = 'Red'
--Col2
DECLARE #SpeedValue INT = 90
DECLARE #SpeedType VARCHAR(2) = '>'
--Col3
DECLARE #FuelValue VARCHAR(20) = null
SELECT DISTINCT a.AdID FROM #AdDetails a
INNER JOIN
(
SELECT *
FROM #AdDetails
WHERE #ColorValue IS NULL
OR #ColorValue = PropertyValue
) Color
ON Color.AdID = a.AdID
INNER JOIN
(
SELECT *
FROM #AdDetails
WHERE #SpeedType IS NULL
UNION
SELECT *
FROM #AdDetails
WHERE PropertyName = 'Speed'
AND ((#SpeedType = '>' AND CONVERT(INT, PropertyValue) > #SpeedValue)
OR (#SpeedType = '<' AND CONVERT(INT, PropertyValue) < #SpeedValue)
OR (#SpeedType = '=' AND CONVERT(INT, PropertyValue) = #SpeedValue))
) AS Speed
ON Speed.AdID = a.AdID
INNER JOIN
(
SELECT *
FROM #AdDetails
WHERE #FuelValue IS NULL
OR (#FuelValue = PropertyValue)
) AS Fuel
ON Fuel.AdID = a.AdID
I add one inner join clause per property type (with some overrides), your sql query would pass all of the possible property type info in one go nulling out whatever they don't want. very ugly code though as it grows.