SELECT CASE vs. CASE IN SQL - sql-server

I do not quite understand why those two different codesamples return a different value.
somehow incorrect but working syntax, returns false results, e.g it returns 0 when the comparison is done over two equal values:
(SELECT CASE
WHEN
SUM(V.IsCompatible) OVER
(PARTITION BY ComputerName, UserID) = ApplicationCount
THEN 1 ELSE 0 END
) AS CompatibleUser
The one below returns the correct values, ie. 1 when there are two equal values compared.
(CASE
WHEN
SUM(V.IsCompatible) OVER
(PARTITION BY ComputerName, UserID) = ApplicationCount
THEN 1 ELSE 0 END
) AS CompatibleUser
or even simpler:
(SELECT CASE
WHEN
X = Y
THEN 1 ELSE 0 END
) AS Result
X = 22 AND Y = 22 => Result = 0
(CASE
WHEN
X = Y
THEN 1 ELSE 0 END
) AS Result
X = 22 AND Y = 22 => Result = 1
I understand applying the correct syntax is important, and I am aware of the SELECT CASE syntax in T-SQL, but I do not understand how the first code sample is evaluated and delivering an unexpected result.
update: full query in it's context
select userapplication.username,
computerdetails.computername,
sum(userapplication.iscompatible)
over (partition by computerdetails.computername,
userapplication.userid) as compatiblecount,
userapplication.applicationcount,
( case
when sum(userapplication.iscompatible)
over (partition by
computerdetails.computername,
userapplication.userid) <> userapplication.applicationcount
then 0
else 1
end
) as usercomputeriscompatible
from computerdetails
right outer join usercomputer
on computerdetails.computerid = usercomputer.computerid
right outer join userapplication
on usercomputer.gebruikerid = userapplication.userid
so userComputerIsCompatible is the result in question here

I think the reason for this behavior is the next one: the expressions like (SELECT ...) are considered to be sub-queries even they don't have FROM clause. Is assume the source of data for these (false) "sub-queries" is only the current row. So, (SELECT expression) is interpreted as (SELECT expression FROM current_row) and (SELECT SUM(iscompatible)OVER(...)) is executed as (SELECT SUM(iscompatible)OVER(current_row)).
Argument: analyzing execution plan for (SELECT SUM(IsWeb) OVER(PARTITION BY OrderDate) [FROM current_row]) expression
I see a Constant Scan (Scan an internal table of constants) operator instead of Clustered Index Scan before Segment and Stream Aggregate ([Expr1007] = Scalar Operator(SUM(#OrderHeader.[IsWeb] as [h].[IsWeb]))) operators. This internal table (Constant Scan) is constructed from current row.
Example (tested with SQL2005SP3 and SQL2008):
DECLARE #OrderHeader TABLE
(
OrderHeaderID INT IDENTITY PRIMARY KEY
,OrderDate DATETIME NOT NULL
,IsWeb TINYINT NOT NULL --or BIT
);
INSERT #OrderHeader
SELECT '20110101', 0
UNION ALL
SELECT '20110101', 1
UNION ALL
SELECT '20110101', 1
UNION ALL
SELECT '20110102', 1
UNION ALL
SELECT '20110103', 0
UNION ALL
SELECT '20110103', 0;
SELECT *
,SUM(IsWeb) OVER(PARTITION BY OrderDate) SumExpression_1
FROM #OrderHeader h
ORDER BY h.OrderDate;
SELECT *
,(SELECT SUM(IsWeb) OVER(PARTITION BY OrderDate)) SumWithSubquery_2
FROM #OrderHeader h
ORDER BY h.OrderDate;
Results:
OrderHeaderID OrderDate IsWeb SumExpression_1
------------- ----------------------- ----- ---------------
1 2011-01-01 00:00:00.000 0 2
2 2011-01-01 00:00:00.000 1 2
3 2011-01-01 00:00:00.000 1 2
4 2011-01-02 00:00:00.000 1 1
5 2011-01-03 00:00:00.000 0 0
6 2011-01-03 00:00:00.000 0 0
OrderHeaderID OrderDate IsWeb SumWithSubquery_2
------------- ----------------------- ----- -----------------
1 2011-01-01 00:00:00.000 0 0
2 2011-01-01 00:00:00.000 1 1
3 2011-01-01 00:00:00.000 1 1
4 2011-01-02 00:00:00.000 1 1
5 2011-01-03 00:00:00.000 0 0
6 2011-01-03 00:00:00.000 0 0

I tried your code and I get the same results for both queries. Here's the code I tried:
DECLARE #X INT = 22
DECLARE #Y INT = 22
SELECT (SELECT CASE
WHEN
#X = #Y
THEN 1 ELSE 0 END
) AS Result
SELECT (CASE
WHEN
#X = #Y
THEN 1 ELSE 0 END
) AS Result
Result is 1 and 1
I ran this on SQL Server 2008 R2

Related

How to check values of different rows of a table

I have below sample input table. In real it has lots of records.
Input:
ID
Classification
123
1
123
2
123
3
123
4
657
1
657
3
657
4
For a 'ID', I want it's records should have 'Classification' column contains all the values 1, 2, 3 and 4. If any of these values are not present then that ID's records should be considered as an exception. The output should be as below.
ID
Classification
Flag
123
1
0
123
2
0
123
3
0
123
4
0
657
1
1
657
3
1
657
4
1
Can someone please help me with how can this can be achieved in sql server.
Thanks.
There are a couple of options here, which is more performant is up to you to test, not me (especially when I don't know what indexes you have). One uses conditional aggregation, to check that all the values are there, and the other uses a subquery and counts the DISTINCT values (as I don't know if there could be duplicate classifications):
SELECT *
INTO dbo.YourTable
FROM (VALUES(123,1),
(123,2),
(123,3),
(123,4),
(657,1),
(657,3),
(657,4))V(ID,Classification);
GO
CREATE CLUSTERED INDEX CI_YourIndex ON dbo.YourTable (ID,Classification);
GO
SELECT ID,
Classification,
CASE WHEN COUNT(CASE YT.Classification WHEN 1 THEN 1 END) OVER (PARTITION BY ID) > 0
AND COUNT(CASE YT.Classification WHEN 2 THEN 1 END) OVER (PARTITION BY ID) > 0
AND COUNT(CASE YT.Classification WHEN 3 THEN 1 END) OVER (PARTITION BY ID) > 0
AND COUNT(CASE YT.Classification WHEN 4 THEN 1 END) OVER (PARTITION BY ID) > 0 THEN 1 ELSE 0
END AS Flag
FROM dbo.YourTable YT;
GO
SELECT ID,
Classification,
CASE (SELECT COUNT(DISTINCT sq.Classification)
FROM dbo.YourTable sq
WHERE sq.ID = YT.ID
AND sq.Classification IN (1,2,3,4)) WHEN 4 THEN 1 ELSE 0
END AS Flag
FROM dbo.YourTable YT;
GO
DROP TABLE dbo.YourTable;

How to insert "empty" row extracting a month list?

I've this sp, which return a list of data, for each "month" (i.e. each row is a month). Somethings like that:
SELECT
*,
(CAST(t1.NumActivities AS DECIMAL) / t1.NumVisits) * 100 AS PercAccepted,
(CAST(t1.Accepted AS DECIMAL) / t1.Estimated) * 100 AS PercValue
FROM
(SELECT
MONTH(DateVisit) AS Month,
COUNT(*) AS NumVisits,
SUM(CASE WHEN DateActivity is not null THEN 1 ELSE 0 END) AS NumActivities,
SUM(Estimate) AS Estimated,
SUM(CASE WHEN DateActivity is not null THEN Estimate ELSE 0 END) AS Accepted
FROM [dbo].[Activities]
WHERE
DateVisit IS NOT NULL
AND (#year IS NULL OR YEAR(DateVisit) = #year)
AND (#clinicID IS NULL OR ClinicID = #clinicID)
GROUP BY MONTH(DateVisit)) t1
This is a result:
Month NumVisits NumActivities Estimated Accepted PercAccepted PercValue
1 5 1 13770.00 2520.00 20.00000000000 18.30065359477124
2 2 2 7900.00 7900.00 100.00000000000 100.00000000000000
3 1 0 2730.00 0.00 0.00000000000 0.00000000000000
8 1 1 3000.00 3000.00 100.00000000000 100.00000000000000
But as you can see, I could "miss" some Month (for example, here April "4" is missed).
Is it possible to insert, for the missing month/row, an empty (0) record? Such as:
Month NumVisits NumActivities Estimated Accepted PercAccepted PercValue
1 5 1 13770.00 2520.00 20.00000000000 18.30065359477124
2 2 2 7900.00 7900.00 100.00000000000 100.00000000000000
3 1 0 2730.00 0.00 0.00000000000 0.00000000000000
4 0 0 0 0 0 0
...
Here is a example with sample data:
CREATE TABLE #Report
(
Id INT,
Name nvarchar(max),
Percentage float
)
INSERT INTO #Report VALUES (1,'ONE',2.01)
INSERT INTO #Report VALUES (2,'TWO',3.01)
INSERT INTO #Report VALUES (5,'Five',5.01)
;WITH months(Month) AS
(
SELECT 1
UNION ALL
SELECT Month+1
FROM months
WHERE Month < 12
)
SELECT *
INTO #AllMonthsNumber
from months;
Your select query:
The left join will gives you the NULL for other months so just use ISNULL('ColumnName','String_to_replace')
\/\/\/\/
SELECT Month, ISNULL(Name,0), ISNULL(Percentage,0)
FROM AllMonthsNumber A
LEFT JOIN #Report B
ON A.Month = B.Id
EDIT:
Yes you can do it without creating AllMonthNumber Table:
You can use master..spt_values (found here) system table which contains the numbers so just with some where condition.
SELECT Number as Month, ISNULL(B.Name,0), ISNULL(Percentage,0)
FROM master..spt_values A
LEFT JOIN #Report B ON A.Number = B.Id
WHERE Type = 'P' AND number BETWEEN 1 AND 12

Cannot increment values in a T-SQL CTE

I have a case where I need to write a CTE ( at least this seems like the best approach) . I have almost everything I need in place but one last issue. I am using a CTE to generate many millions of a records and then I will insert them into a table. The data itself is almost irrelevant except for three columns. 2 date time columns and one character column.
The idea behind the CTE is this. I want one datetime field called Start and one int field called DataValue. I will have a variable which is the count of records I want to aim for and then another variable which is the number of times I want to repeat the datetime value. I don't think I need to explain the software this data represents but basically I need to have 16 rows where the Start value is the same and then after the 16th run I want to then add 15 minutes and then repeat. Effectively there will be events in 15 minute intervals and I will need X number of rows per 15 minute interval to represent those events.
This is my code
Declare #tot as int;
Declare #inter as int;
Set #tot = 26
Set #inter = 3;
WITH mycte(DataValue,start) AS
(
SELECT 1 DataValue, cast('01/01/2011 00:00:00' as datetime) as start
UNION all
if DataValue % #inter = 0
SELECT
DataValue + 1,
cast(DateAdd(minute,15,start) as datetime)
else
select
DataValue + ,
start
FROM mycte
WHERE DataValue + 1 <= #tot)
select
m.start,
m.start,
m.Datavalue%#inter
from mycte as m
option (maxrecursion 0);
I'll change the select statement into an insert statement once I get it working but the m.DataValue%#inter will make it repeat integer when inserting so the only thing I need is to figure out how to make the start be the same 16 times in a row and then increment
It seems that I cannot have an IF statement in the CTE but I am not sure how to accomplish that but what I was going to do was basically say if the DataValue%16 was 0 then increase the value of start.
In the end I should hopefully have something like this where in this case I only repeat it 4 times
+-----------+-------------------+
| DateValue | start |
+-----------+-------------------+
| 1 | 01/01/01 00:00:00 |
| 2 | 01/01/01 00:00:00 |
| 3 | 01/01/01 00:00:00 |
| 4 | 01/01/01 00:00:00 |
| 5 | 01/01/01 00:15:00 |
| 6 | 01/01/01 00:15:00 |
| 7 | 01/01/01 00:15:00 |
| 8 | 01/01/01 00:15:00 |
Is there another way to accomplish this without conditional statements?
You can use case when as below:
Declare #tot as int;
Declare #inter as int;
Set #tot = 26
Set #inter = 3;
WITH mycte(DataValue,start) AS
(
SELECT 1 DataValue, cast('01/01/2011 00:00:00' as datetime) as start
UNION all
SELECT DataValue+1 [Datavalue],
case when (DataValue % #inter) = 0 then cast(DateAdd(minute,15,start) as datetime) else [start] end [start]
FROM mycte
WHERE (DataValue + 1) <= #tot)
select
m.DataValue,
m.[start]
from mycte as m
option (maxrecursion 0);
This will give the below result
DataValue Start
========= =============
1 2011-01-01 00:00:00.000
2 2011-01-01 00:00:00.000
3 2011-01-01 00:00:00.000
4 2011-01-01 00:15:00.000
5 2011-01-01 00:15:00.000
6 2011-01-01 00:15:00.000
7 2011-01-01 00:30:00.000
8 2011-01-01 00:30:00.000
9 2011-01-01 00:30:00.000
10 2011-01-01 00:45:00.000
11 2011-01-01 00:45:00.000
12 2011-01-01 00:45:00.000
....
26 2011-01-01 02:00:00.000
And if you dont want to use case when you can use double recursive cte as below:-
WITH mycte(DataValue,start) AS
( --this recursive cte will generate the same record the number of #inter
SELECT 1 DataValue, cast('01/01/2011 00:00:00' as datetime) as start
UNION all
SELECT DataValue+1 [DataValue],[start]
FROM mycte
WHERE (DataValue + 1) <= #inter)
,Increments as (
-- this recursive cte will do the 15 additions
select * from mycte
union all
select DataValue+#inter [DataValue]
,DateAdd(minute,15,[start]) [start]
from Increments
WHERE (DataValue + 1) <= #tot
)
select
m.DataValue,
m.[start]
from Increments as m
order by DataValue
option (maxrecursion 0);
it will give the same results.
You can do this with a tally table and some basic math. I'm not sure if your total rows are #tot or should they be #tot * #inter. If so, you just need to change the TOP clause. If you need more rows, you just need to alter the tally table generation.
Declare #tot as int;
Declare #inter as int;
Set #tot = 26
Set #inter = 3;
WITH
E(n) AS(
SELECT n FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0))E(n)
),
E2(n) AS(
SELECT a.n FROM E a, E b
),
E4(n) AS(
SELECT a.n FROM E2 a, E2 b
),
cteTally(n) AS(
SELECT TOP( #tot) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) n
FROM E4
)
SELECT n, DATEADD( MI, 15* ((n-1)/#inter), '20110101')
FROM cteTally;

Union two list of dates, union with further info

I have two tables of dates, for the questions's sake call them dates1 and dates2. Sometimes a given date is in both, sometimes it is in 1 but not 2, and other times in 2 but not 1.
My original requirement was just a list of all dates from both sets
SELECT Date FROM dates1
UNION
SELECT Date FROM dates2
Easy peasy. New requirement; know which list the dates came from (or both if that is the case). The columns I need are as follows:
Date, IsList1, IsList2
So, some example data:
Dates1
======
Date
====
2017-01-31
2017-02-28
2017-03-31
Dates2
======
Date
====
2017-01-31
2017-04-30
Expected output
Date | IsList1 | IsList2
2017-01-31 | 1 | 1
2017-02-28 | 1 | 0
2017-03-31 | 1 | 0
2017-04-30 | 0 | 1
SQL fiddle with the above data: http://sqlfiddle.com/#!18/9eecb/5425
You'll most likely need to use a FULL OUTER JOIN and some expressions to achieve this.
SELECT ISNULL(D1.[Date], D2.[Date]) AS [Date],
CASE WHEN D1.[Date] IS NULL THEN 0 ELSE 1 END AS IsList1,
CASE WHEN D2.[Date] IS NULL THEN 0 ELSE 1 END AS IsList2
FROM #dates1 D1
FULL OUTER JOIN #dates2 D2 ON D1.[Date] = D2.[Date];
It's also worth nothing, on your SQL fiddle, that you have 2 INSERT statements into #Dates1 and none into #Dates2, thus the result set is 1 for all of IsList1 and 0 for IsList2.
;with cte
as
(
select dt.date as tb1,dt1.date as tbl2
from
#dates1 dt
full join
#dates2 dt1
on dt.date =dt1.date
)
select isnull(tb1 ,tbl2 ) as dt,
case when tb1 is not null then 1 else 0 end as list1,
case when tbl2 is not null then 1 else 0 end as list2
from cte
Perhaps another option with a simple aggregation:
Select Date
,InList1=sum(InList1)
,InList2=sum(InList2)
From (
Select Distinct Date,InList1=1,InList2=0 from #dates1
Union All
Select Distinct Date,InList1=0,InList2=1 from #dates2
) A
Group By Date
Returns
Date InList1 InList2
2017-01-31 1 1
2017-02-28 1 0
2017-03-31 1 0
2017-04-30 0 1

Getting records from a table which have non-null values for a particular column in all months

I have a table:
Project_Id Period Value
123 Jan-15 0
123 Feb-15 34
123 Mar-15 78
123 Apr-15 56
456 Jan-15 0
456 Feb-15 0
456 Mar-15 0
456 Apr-15 0
789 Jan-15 45
789 Feb-15 4
789 Mar-15 18
789 Apr-15 26
I need to retrieve Project data only when i do not have 0 for Value field in all the months like:
Project_Id Period Value
123 Jan-15 0
123 Feb-15 34
123 Mar-15 78
123 Apr-15 56
789 Jan-15 45
789 Feb-15 4
789 Mar-15 18
789 Apr-15 26
Project no 456 should not come in my result because for all the months the value is 0 for that particular project.
Can someone help me with the query?
Use SUM and COUNT to determine the number of 0 Values:
SELECT *
FROM tbl
WHERE project_id IN(
SELECT project_id
FROM tbl
GROUP BY project_id
HAVING SUM(CASE WHEN Value = 0 THEN 1 ELSE 0 END) <> COUNT(*)
)
SQL Fiddle
Another solution is to use EXISTS:
SELECT *
FROM tbl t1
WHERE EXISTS(
SELECT 1 FROM tbl t2 WHERE t2.project_id = t1.project_id AND t2.Value > 0
)
SQL Fiddle
The inner select gets all project_ids that have a least one value that is not 0.
select * from your_table
where project_id in
(
select project_id
from your_table
group by project_id
having sum(case when value <> 0 then 1 else 0 end) > 0
)
Some test data but idea remains the same
create table #test123
(
pid int,
value int
)
insert into #test123
select 1,0
union all
select 1,1
union all
select 2,0
union all
select 2,0
union all
select 3,2
select * from #test123 t2 where exists (select 1 from #test123 t1
where t1.pid=t2.pid
group by pid
having sum(value)>0
)
For performance, I prefer not making a join to check for repeating values:
;WITH CTE as
(
SELECT
Project_Id,
Period,
Value,
max(abs(value)) over (Partition by Period) value
FROM YourTable
)
SELECT
Project_Id,
Period,
Value
FROM CTE
WHERE value > 0
*using abs to check for negative values. If all values are positive, the abs can be omitted.

Resources