Using Boolean to determine 5-way Where clause - sql

I'm looking at 5 different columns (db made badly unfortunately). If of the five columns two have one "1" value and one "2" value I want this record to be excluded from the results. However, if it only has one of the two values I want it to be included.
I have this so far, but I'm certain it will not include the record if it has even one of the two values.
NOT ((Ew.DocRecvd1 = 10 OR Ew.DocRecvd1 = 11) OR
(Ew.DocRecvd2 = 10 OR Ew.DocRecvd2 = 11) OR
(Ew.DocRecvd3 = 10 OR Ew.DocRecvd3 = 11) OR
(Ew.DocRecvd4 = 10 OR Ew.DocRecvd4 = 11) OR
(Ew.DocRecvd5 = 10 OR Ew.DocRecvd5 = 11))
Thanks.

I would suggest that you count the number of values in each group that you want. And, I would do it in a subquery, just because that makes the code more readable and maintainable.
Here is an example:
from (select t.*,
((case when Ew.DocRecvd1 in (10, 11) then 1 else 0) +
(case when Ew.DocRecvd2 in (10, 11) then 1 else 0) +
(case when Ew.DocRecvd3 in (10, 11) then 1 else 0) +
(case when Ew.DocRecvd4 in (10, 11) then 1 else 0) +
(case when Ew.DocRecvd5 in (10, 11) then 1 else 0) +
) as Num1s,
<something similar> as Num2s
from table t
) t
where Num1s = 2 and Num2s = 1;

You state the filter conditions simply in the where clause. Given a table
create table foobar
(
id int not null primary key ,
c1 int not null ,
c2 int not null ,
c3 int not null ,
c4 int not null ,
c5 int not null ,
)
go
You can say
select *
from foobar
where not ( 2 = case c1 when 1 then 1 else 0 end
+ case c2 when 1 then 1 else 0 end
+ case c3 when 1 then 1 else 0 end
+ case c4 when 1 then 1 else 0 end
+ case c5 when 1 then 1 else 0 end
and 1 = case c1 when 2 then 1 else 0 end
+ case c2 when 2 then 1 else 0 end
+ case c3 when 2 then 1 else 0 end
+ case c4 when 2 then 1 else 0 end
+ case c5 when 2 then 1 else 0 end
)
The other approach which might run faster is to use as mask table, containing the conditions you want to exclude. Something like this one:
create table mask
(
c1 tinyint null ,
c2 tinyint null ,
c3 tinyint null ,
c4 tinyint null ,
c5 tinyint null ,
unique clustered ( c1,c2,c3,c4,c5) ,
)
In your case, there are only 30 conditions to be excluded:
c1 c2 c3 c4 c5
---- ---- ---- ---- ----
NULL NULL 1 1 2
NULL NULL 1 2 1
NULL NULL 2 1 1
NULL 1 NULL 1 2
NULL 1 NULL 2 1
NULL 1 1 NULL 2
NULL 1 1 2 NULL
NULL 1 2 NULL 1
NULL 1 2 1 NULL
NULL 2 NULL 1 1
NULL 2 1 NULL 1
NULL 2 1 1 NULL
1 NULL NULL 1 2
1 NULL NULL 2 1
1 NULL 1 NULL 2
1 NULL 1 2 NULL
1 NULL 2 NULL 1
1 NULL 2 1 NULL
1 1 NULL NULL 2
1 1 NULL 2 NULL
1 1 2 NULL NULL
1 2 NULL NULL 1
1 2 NULL 1 NULL
1 2 1 NULL NULL
2 NULL NULL 1 1
2 NULL 1 NULL 1
2 NULL 1 1 NULL
2 1 NULL NULL 1
2 1 NULL 1 NULL
2 1 1 NULL NULL
(30 row(s) affected)
The actual query is trivial then (and if you have a covering index on the columns to be tested, the test is done with index seeks and so should perform extremely well:
select *
from dbo.foobar t
where not exists ( select *
from mask m
where t.c1 = m.c1
and t.c2 = m.c2
and t.c3 = m.c3
and t.c4 = m.c4
and t.c5 = m.c6
)
The advantage of this approach is that the ruleset is table-driven, meaning future changes to the rules are just data modifications to your mask table.
You could also use a positive set of rules, but in your case, the set is bigger (>200 positive cases as opposed to the 30 negative cases).

OK, I think I've found the result I wanted.
I used the following in the WHERE clause of my query:
NOT
(2 =
(CASE WHEN Ew.DocRecvd1 = 10 THEN 1 ELSE 0 END
+
CASE WHEN Ew.DocRecvd2 = 10 THEN 1 ELSE 0 END
+
CASE WHEN Ew.DocRecvd3 = 10 THEN 1 ELSE 0 END
+
CASE WHEN Ew.DocRecvd4 = 10 THEN 1 ELSE 0 END
+
CASE WHEN Ew.DocRecvd5 = 10 THEN 1 ELSE 0 END
+
CASE WHEN Ew.DocRecvd1 = 11 THEN 1 ELSE 0 END
+
CASE WHEN Ew.DocRecvd2 = 11 THEN 1 ELSE 0 END
+
CASE WHEN Ew.DocRecvd3 = 11 THEN 1 ELSE 0 END
+
CASE WHEN Ew.DocRecvd4 = 11 THEN 1 ELSE 0 END
+
CASE WHEN Ew.DocRecvd5 = 11 THEN 1 ELSE 0 END))
It is only possible in my DB to get these two documents in one of five places within one record, so the count could not go over 2 with the two documents i'm looking for.
Kudos to Nicholas Carey and Gordon Linoff for keying me into what I could do and look for!

Related

T-sql: calculate sum of factors when quantity of factors can differ from time to time

Would you please, help me, to develop the algorythm of counting rating of the clients.
Initial dataset and desirable result is in the code below. Thank you.
The logic:
We have clients and 6 factors (with values 1 or 0 (present or not present)).
We should calculate a rating of the client:
1 (max rate) - client has all the factors
2 - client has factors 1-5 and doesn't have 6th
3 - client has factors 1-4 and doesn't have 5th (factor 6 doesn't matter)
4 - client has factors 1-3 and doesn't have 4th (factors 5-6 don't matter)
5 - client has factors 1-2 and doesn't have 3rd (factors 4-6 don't matter)
6 - client has factor 1 and doesn't have 2nd (factors 3-6 don't matter)
7 - client doesnt have factor 1 (factors 2-6 don't matter)
The key is that number of factors can differ from time to time.
drop table if exists #tmp;
create TABLE #tmp (
[client] [nvarchar] null,
[factor1] [int] NULL,
[factor2] [int] NULL,
[factor3] [int] NULL,
[factor4] [int] NULL,
[factor5] [int] NULL,
[factor6] [int] null,
[desirable_result] [int] NULL
)
insert into #tmp (
[client]
,[factor1]
,[factor2]
,[factor3]
,[factor4]
,[factor5]
,[factor6]
,[desirable_result]
)
select '1', 1,1,1,1,1,1,1 union all
select '2', 1,1,0,1,1,1,5 union all
select '3', 1,0,1,1,0,1,6 union all
select '4', 1,1,1,1,1,0,2 union all
select '5', 1,1,1,0,0,1,4
This solution works, but only if the num of factors is always equal.
The key is that number of factors can differ from time to time.
select *
, "factor1" + "factor2" + "factor3" + "factor4" + "factor5" + "factor6" sum_6
, "factor1" + "factor2" + "factor3" + "factor4" + "factor5" sum_5
, "factor1" + "factor2" + "factor3" + "factor4" sum_4
, "factor1" + "factor2" + "factor3" sum_3
, "factor1" + "factor2" sum_2
, "factor1" sum_1
into #tmp2
from #tmp
select *
, case when sum_6 = 6 then 1 else
(case when sum_5 = 5 and sum_6 < 6 then 2 else
(case when sum_4 = 4 and sum_5 < 5 then 3 else
(case when sum_3 = 3 and sum_4 < 4 then 4 else
(case when sum_2 = 2 and sum_3 < 3 then 5 else
(case when sum_1 = 1 and sum_2 < 2 then 6 else
7
end)
end)
end)
end)
end)
end rate
from
#tmp2
you can use CASE WHEN ... as what scaisEdge has demonstrated.
What i have here is to UNPIVOT the table using CROSS APPLY and then using SUM() with CASE to workout the necessary logic
select t.client,
t.[desirable_result],
case when sum(f.fval) = 6 then 1
when sum(f.fval) = 5
and sum(case when f.fno = 6 then f.fval end) = 0 then 2
when sum(case when f.fno <= 4 then f.fval end) = 4
and sum(case when f.fno = 5 then f.fval end) = 0 then 3
when sum(case when f.fno <= 3 then f.fval end) = 3
and sum(case when f.fno = 4 then f.fval end) = 0 then 4
when sum(case when f.fno <= 2 then f.fval end) = 2
and sum(case when f.fno = 3 then f.fval end) = 0 then 5
when sum(case when f.fno = 1 then f.fval end) = 1
and sum(case when f.fno = 2 then f.fval end) = 0 then 6
when sum(case when f.fno = 1 then f.fval end) = 0 then 7
end
from #tmp t
cross apply
(
values
(1, factor1),
(2, factor2),
(3, factor3),
(4, factor4),
(5, factor5),
(6, factor6)
) f (fno, fval)
group by t.client, t.[desirable_result]
order by t.client
You could try using CASE WHEN
select case
when [factor1] = 1
AND [factor2] = 1
AND [factor3] = 1
AND [factor4] = 1
AND [factor5] = 1
AND [factor6] = 1
then 'ALL6'
when [factor1] = 1
AND [factor2] = 1
AND [factor3] = 1
AND [factor4] = 1
AND [factor5] = 1
then 'FIRST5'
.....
....
when [factor1] = 1
AND [factor2] = 1
AND [factor3] = 1
AND [factor4] = 1
then 'FIRST4'
when [factor1] = 1
then 'ONLy1' END client_rate
from my_table
chenge the dot.... with the missing conditions

Select only the "most complete" record

I need to solve the following problem.
Let's suppose I have a table with 4 fields called a, b, c, d.
I have the following records:
-------------------------------------
a | b | c | d
-------------------------------------
1 | 2 | | row 1
1 | 2 | 3 | 4 row 2
1 | 2 | | 4 row 3
1 | 2 | 3 | row 4
As it's possible to observe, rows 1,3,4 are "sub-records" of row 2.
What I would like to do is, to extract only 2nd row.
Could you help me please?
Thanks in advance for the answer
EDIT: I need to be more specific.
I could have also the cases:
-------------------------------------
a | b | c | d
-------------------------------------
1 | 2 | | row 1
1 | 2 | | 4 row 2
1 | | | 4 row 3
where I need to extract the 2nd row,
-------------------------------------
a | b | c | d
-------------------------------------
1 | 2 | | row 1
1 | 2 | 3 | row 2
1 | | 3 | row 3
and again I need to extract the 2nd row.
Same for couples,
a | b | c | d
-------------------------------------
1 | | | row 1
1 | | 3 | row 2
| | 3 | row 3
and so on for the other examples.
(Of course, it's now always 2nd row)
Using a NOT EXISTS the records that have a better duplicate can be filtered out.
create table abcd (
a int,
b int,
c int,
d int
);
insert into abcd (a, b, c, d) values
(1, 2, null, null)
,(1, 2, 3, 4)
,(1, 2, null, 4)
,(1, 2, 3, null)
,(2, 3, null,null)
,(2, 3, null, 5)
,(2, null, null, 5)
,(3, null, null, null)
,(3, null, 5, null)
,(null, null, 5, null)
SELECT *
FROM abcd AS t
WHERE NOT EXISTS
(
select 1
from abcd as d
where (t.a is null or d.a = t.a)
and (t.b is null or d.b = t.b)
and (t.c is null or d.c = t.c)
and (t.d is null or d.d = t.d)
and (case when t.a is null then 0 else 1 end +
case when t.b is null then 0 else 1 end +
case when t.c is null then 0 else 1 end +
case when t.d is null then 0 else 1 end) <
(case when d.a is null then 0 else 1 end +
case when d.b is null then 0 else 1 end +
case when d.c is null then 0 else 1 end +
case when d.d is null then 0 else 1 end)
);
a | b | c | d
-: | ---: | ---: | ---:
1 | 2 | 3 | 4
2 | 3 | null | 5
3 | null | 5 | null
db<>fiddle here
You will need to compute a "completion index" for each row. In the example you provided, you might use something along the lines of:
(CASE WHEN a IS NULL THEN 0 ELSE 1) +
(CASE WHEN b IS NULL THEN 0 ELSE 1) +
(CASE WHEN c IS NULL THEN 0 ELSE 1) +
(CASE WHEN d IS NULL THEN 0 ELSE 1) AS CompletionIndex
Then SELECT the top 1 ordered by CompletionIndex in descending order.
This is obviously not very scalable across a large number of columns. But if you have a large number of sparsely populated columns you might consider a row-based rather than column-based structure for your data. That design would make it much easier to count the number of non-NULL values for each entity.
Most complete rows, by your definition, are the ones with the least null columns:
SELECT * FROM tablename
WHERE (
(CASE WHEN a IS NULL THEN 0 ELSE 1 END) +
(CASE WHEN b IS NULL THEN 0 ELSE 1 END) +
(CASE WHEN c IS NULL THEN 0 ELSE 1 END) +
(CASE WHEN d IS NULL THEN 0 ELSE 1 END)
) =
(SELECT MAX(
(CASE WHEN a IS NULL THEN 0 ELSE 1 END) +
(CASE WHEN b IS NULL THEN 0 ELSE 1 END) +
(CASE WHEN c IS NULL THEN 0 ELSE 1 END) +
(CASE WHEN d IS NULL THEN 0 ELSE 1 END))
FROM tablename)
Hmmm . . . I think you can use not exists:
with t as (
select t.*, row_number() over (order by a) as id
from t
)
select t.*
from t
where not exists (select 1
from t t2
where ((t2.a is not distinct from t.a or t2.a is not null and t.a is null) and
(t2.b is not distinct from t.b or t2.b is not null and t.b is null) and
(t2.c is not distinct from t.c or t2.c is not null and t.c is null) and
(t2.d is not distinct from t.d or t2.d is not null and t.d is null)
) and
t2.id <> t.id
);
The logic is that no more specific row exists, where the values match
Here is a db<>fiddle.
As mentioned by Gordon Linoff, we do have to use something like not exists too,
Edit Using EXCEPT helps
This might work...
SELECT * from table1
EXCEPT
(
SELECT t1.*
FROM table1 t1
JOIN table1 t2
ON COALESCE(t1.a, t2.a, -1) = COALESCE(t2.a, -1)
AND COALESCE(t1.b, t2.b, -1) = COALESCE(t2.b, -1)
AND COALESCE(t1.c, t2.c, -1) = COALESCE(t2.c, -1)
AND COALESCE(t1.d, t2.d, -1) = COALESCE(t2.d, -1)
)
Here, t1 is every subset row.
Note: We are assuming value -1 as sentinel value and it does not occur in any column.

SQL Server: Using COUNT with IN and NOT IN

I have a data table as follows :
file_id | action code
1 | 10
1 | 20
2 | 10
2 | 12
3 | 10
3 | 20
4 | 10
4 | 10
4 | 20
The output is:
file_id | Warning
1 | 0
2 | 0 <- this should be 1 instead
3 | 0
4 | 1
The first count works as expected, and sets warning as 1, if there are any action_code duplicates, but i can't get it to work and display a warning if action_code is not perfectly divisible with 10
#exported [int] = NULL,
#bin_id [int] = NULL,
#date_start [DateTime],
#date_stop [DateTime],
#action_code [int] = NULL,
#action_description [varchar](43) = NULL
SELECT
dbo.Tf.file_id AS 'ID',
dbo.Tf.file_name AS 'NAME',
MAX(dbo.TFD.action_date) AS 'DATE',
MAX(dbo.TFD.file_length) AS 'SIZE',
dbo.Bins.name AS 'BIN',
dbo.TFD.action_description,
CASE
WHEN (COUNT(DISTINCT dbo.TFD.action_code) <> COUNT(dbo.TFD.action_code) )
AND
((SELECT COUNT ( dbo.TFD.action_code ) FROM TFD WHERE action_code IN (10,20,30,40,50)) > 0
AND
(SELECT COUNT ( dbo.TFD.action_code ) FROM TFD WHERE action_code NOT IN (10,20,30,40,50)) > 0 ) THEN 1
ELSE 0
END AS 'Warning'
FROM
( SELECT
dbo.Tf.file_id,
MAX(dbo.TFD.action_code) AS 'action_code'
FROM Tf
INNER JOIN TFD
ON Tf.file_id = TFD.file_id INNER JOIN Bins ON Tf.bin_id = Bins.bin_id
WHERE
(#bin_id IS NULL OR Tf.bin_id = #bin_id)
AND Tf.file_id IN
(
SELECT H.file_id
FROM Tf AS H INNER JOIN TFD AS D ON H.file_id = D.file_id
WHERE ((D.action_date >= #date_start AND D.action_date <= #date_stop) OR (H.file_date >= #date_start AND H.file_date <= #date_stop))
AND (H.bin_id = #bin_id OR #bin_id IS NULL)
AND H.file_type = #exported
AND ((#action_description IS NULL) OR (D.action_description LIKE #action_description + '%'))
)
AND (#exported IS NULL OR Tf.file_type = #exported)
GROUP BY dbo.Tf.file_id) AS TempSelect
INNER JOIN Tf
ON Tf.file_id = TempSelect.file_id
INNER JOIN TFD
ON (TFD.file_id = TempSelect.file_id
AND TFD.action_code = TempSelect.action_code)
INNER JOIN Bins ON Tf.bin_id = Bins.bin_id
WHERE
(
(#action_code IS NULL ) OR (#action_code <> -1 AND TempSelect.action_code = #action_code)
OR (#action_code = -1 AND TempSelect.action_code NOT IN (10,20,30,40) )
)
GROUP BY
dbo.Tf.file_id,
dbo.Tf.file_name,
dbo.Bins.name,
dbo.Tf.bin_id,
dbo.TFD.action_description
EDIT: I added the whole procedure. My main goal,among others, is to set the field warning as 1 if the following conditions are met:
if there are any action_code duplicates (as it's the case for file 4)
if there is an action_code not divisible by 10 among the other action_codes for each file (as it's the case with file 2)
If your logic is: Set a flag to 1 if there are duplicates or if a code is not divisible by 10, then I would suggest:
select (case when count(distinct d.action_code) <> count(*) then 1
else max(case when d.action_code % 10 <> 0 then 1 else 0 end)
end)
Notice that I replaced dbo.Detail with the table alias d. Table aliases make a query easier to write, read, and understand.
Hope this helps you:
SELECT FILE_ID,
MAX(CASE WHEN action_code % 10 != 0 THEN 1 END) not_divisible,
CASE WHEN COUNT(*)!=COUNT(DISTINCT action_Code) THEN 1 END not_unique
FROM #test
GROUP BY FILE_ID
Putting it all together you can use:
SELECT file_id,
CASE WHEN COUNT(*)!=COUNT(DISTINCT action_Code) THEN 1
ELSE MAX(CASE WHEN action_code % 10 != 0 THEN 1 ELSE 0 END) END Warning
FROM #test
GROUP BY file_id
Try with the below query..
CREATE TABLE #t (FileID INT,ActionCode INT)
INSERT INTO #t
VALUES (1,10),(1,20),(2,10),(2,12),(3,10),(3,20),(4,10),(4,10),(4,20)
WITH cte_1
as (
SELECT *,COUNT(1) OVER(PARTITION BY FileID,ActionCode ORDER BY fileID,ActionCode) CNT
FROM #T)
SELECT FileID,case WHEN SUM(ActionCode) %10 <>0 THEN 1 WHEN MAX(CNT)<>1 THEN 1 ELSE 0 END
FROM CTE_1
GROUP BY FileID
Result :
Thanks all for your answers, they were helpful, i modified the following section as such, and now it works:
...
dbo.TFD.action_description,
CASE
WHEN (COUNT(DISTINCT dbo.TFD.action_code) <> COUNT(dbo.TFD.action_code)) OR err_ac > 0
THEN 1 ELSE 0 END AS 'Warning'
FROM
(
SELECT
dbo.Tf.file_id,
MAX(dbo.TFD.action_code) AS 'action_code',
CASE
WHEN SUM(dbo.TFD.action_code) %10 <> 0 THEN 1 ELSE 0 END AS 'err_ac'
...

T-SQL Query a matrix table used as LIFO

Following my [question]: T-SQL Query a matrix table for free position
I've now trying to handle my matrix table as a LIFO. Each couple of (X,Z) represent a channel in which I can store an element. When I generate a location I'm now using the query provided in the above question and here below.
SELECT x, z, MAX(CASE WHEN disabled = 0 AND occupiedId IS NULL THEN Y ELSE 0 END) firstFreeY
FROM matrix
GROUP BY x, z
ORDER BY x, z;
This is working but it doesn't handle "holes". In fact It's possible that a Disabled flag is removed from the table or an element is manually deleted.
In case my Matrix table will look like this:
X Z Y Disabled OccupiedId
--------------------------------------------------
1 1 1 0 591
1 1 2 0 NULL
1 1 3 1 NULL
1 1 4 0 524
1 1 5 0 523
1 1 6 0 522
1 1 7 0 484
1 2 1 0 NULL
1 2 2 0 NULL
1 2 3 0 NULL
1 2 4 0 NULL
1 2 5 0 NULL
1 2 6 0 589
1 2 7 0 592
the result of the above query is:
X Z firstFreeY
------------------------
1 1 2
1 2 5
instead of:
X Y firstFreeY
------------------------
1 1 0
1 2 5
Any suggestions on how to achieve this?
This query looks for the largest Y that is smaller than all other occupied Y's:
select m1.X
, m1.Z
, max(
case
when m2.MinOccupiedY is null or m1.Y < m2.MinOccupiedY then m1.Y
else 0
end
) as FirstFreeY
from matrix m1
join (
select X
, Z
, min(
case
when disabled <> 0 or occupiedId is not null then Y
end
) as MinOccupiedY
from matrix
group by
X
, Z
) m2
on m1.X = m2.X
and m1.Z = m2.Z
group by
m1.X
, m1.Z
Live example at SQL Fiddle.
just to know if i understood what you were asking, is this working too?
select distinct
m1.x,m1.z, o.y
from
matrix m1
cross apply
(
select top 1 (case when m2.Disabled = 0 then m2.y else 0 end)
from matrix m2
where
m1.x = m2.x
and m1.z = m2.z
and m2.OccupiedId is null
order by m2.y desc
) o (y);

how to get field name what are the field have value?

i have one table sql server like below , from that table i want to get quesno, field name[what are the field have value]
QuesNo A B C D
1 1 0 1 0
2 0 0 0 1
Output :
QuesNo Result
1 A,C
2 D
Is there any possible ways to get outpu?
This ought to do it...
SELECT QuesNo, SUBSTRING(Answers, 1, LENGTH(Answers) - 1) AS Answers
FROM (
SELECT QuesNo,
CASE
WHEN A <> 0 THEN 'A,'
ELSE ''
END +
CASE
WHEN B <> 0 THEN 'B,'
ELSE ''
END +
CASE
WHEN C <> 0 THEN 'C,'
ELSE ''
END +
CASE
WHEN D <> 0 THEN 'D,'
ELSE ''
END AS Answers
FROM yourtable
) Foo