( CASE WHEN [date] IS NULL THEN 0 ELSE 1 END) is not working in my pivot - sql

I have written the follwoing code expecting to get 0's and 1's returned in the CASES but I get 1's and NULL's.
any suggestions to get 0's?
DECLARE #Today DATETIME
SET #Today = FLOOR(CAST(GETDATE() AS FLOAT))
SELECT*
FROM
( SELECT e.[Employeenr],
e.[Name],
dc.[Code],
c.[Description],
(CASE WHEN ec.[date] IS NULL THEN 0 ELSE 1 END) as 'Date',
(CASE WHEN dc.[creationdate] IS NULL THEN 0 ELSE 1 END) as 'Aanwezig'
FROM HR_Employee e
left join HR_EmployeeDriverLicense d ON e.[Employeenr] = d.[Employee]
left join DriverLicenseCategory dc ON d.[DriverLicenseCategory] = dc.
[DriverLicenseCategorynr]
left join HR_EmployeeCertificate ec ON e.[Employeenr] = ec.[Employee]
left join HR_Certificate c ON ec.[Certificate] = c.[Certificatenr]
left join HR_Function f ON e.[Function] = f.[Functionnr]
WHERE (e.[Date_out_of_employment] IS NULL
or e.[Date_out_of_employment] >= #today
or e.[Licensenumber] is not null)
and e.[LicenseExpireDate] is not null
and c.[Description] is not null
and ec.[Certificate] <> 5
and f.[Functionnr] = 1
) AS SRC
PIVOT
( MAX(Aanwezig)
FOR [Code] IN ([C], [C1], [C1E], [CE])) AS PivotTable
PIVOT
( MAX ([Date])
FOR [Description] IN ([Kooiaap certificaat], [ADR Certificaat])) AS PivotTable

When you are performing PIVOT you will get a NULL for rows, which do not have a value for the corresponding PIVOT column. Unfortunately, if you want to get 0 instead NULL, you need to add this logic in the final SELECT statement.
Something like this:
SELECT ISNULL([Kooiaap certificaat], 0), ISNULL([ADR Certificaat], 0)

Instead of using MAX use COUNT function and CASE WHEN together.
Try below.
COUNT(CASE WHEN AANWEZIG= 0 THEN NULL WHEN AANWEZIG= 1 THEN 1 END)

Related

Get single row depending of conditional

I have a simple select query with some joins like:
SELECT
[c].[column1]
, [c].[column2]
FROM [Customer] AS [c]
INNER JOIN ...
So I do a left join with my principal table as:
LEFT JOIN [Communication] AS [com] ON [c].[CustomerGuid] = [com].[ComGuid]
this relatioship its 1 to *, one customer can have multiple communications
So in my select I want to get value 1 or 2 depending of condition:
Condition:
if ComTypeKey (from communication) table have a row with value 3 and have another row with vale 4 return 1 then 0
So I try something like:
SELECT
[c].[column1]
, [c].[column2]
, IIF([com].[ComTypeKey] = 3 AND [com].[ComTypeKey] = 4,1,0)
FROM [Customer] AS [c]
INNER JOIN ...
LEFT JOIN [Communication] AS [com] ON [c].[CustomerGuid] = [com].[ComGuid]
But it throws me two rows, beacause there are 2 rows on communication. My desire value is to get only one row with value 1 if my condition is true
If you have multiple rows you need GROUP BY, then count the relevant keys and subtract 1 to get (1, 0)
SELECT
[c].[column1]
, [c].[column2]
, COUNT(CASE WHEN [ComTypeKey] IN (3,4) THEN 1 END) - 1 as FLAG_CONDITION
FROM [Customer] AS [c]
INNER JOIN ...
LEFT JOIN [Communication] AS [com]
ON [c].[CustomerGuid] = [com].[ComGuid]
GROUP BY
[c].[column1]
, [c].[column2]
I'm not really sure I understand.
This will literally find if both values 3 and 4 exist for that CustomerGuid, and only select one of them in that case - not filtering out any record otherwise.
If this is not what you want, providing sample data with the expected result would remove the ambiguity.
SELECT Field1,
Field2,
...
FieldN
FROM (SELECT TMP.*,
CASE WHEN hasBothValues = 1 THEN
ROW_NUMBER() OVER ( PARTITION BY CustomerGuid ORDER BY 1 )
ELSE 1
END AS iterim_rn
FROM (SELECT TD.*,
MAX(CASE WHEN Value1 = '3' THEN 1 ELSE 0 END) OVER
( PARTITION BY CustomerGuid ) *
MAX(CASE WHEN Value1 = '4' THEN 1 ELSE 0 END) OVER
( PARTITION BY CustomerGuid ) AS hasBothValues
FROM TEST_DATA TD
) TMP
) TMP2
WHERE interim_rn = 1

TSQL Select only the highest credentail

I have a query that is returning multiple line for a single service because an individual may have multiple credentials. In the medical field you retain several credentials but for simplicity sake I will use just standard credentials Phd, MA, MS, BA, BS, AS
I need to know the simplest way to ignore rows where Z_ServiceLedger.clientvisit_id has any Credentials.credentials lower in the hierarchy. So if an employee does a service and he has a Phd and a MA only return the lines for Phd and if he has a Phd an Ma and a BA only return the lines for phd. We have around 50 credentials so if I use CASE for each credential you can see how mess that will get an I'm hoping there is a better way to avoid that.
Here is my current query:
SELECT DISTINCT
SUM(CASE WHEN v.non_billable = 0 THEN v.duration ELSE 0 END) / 60 AS billable_hours,
SUM(CASE WHEN (v.non_billable = 0 AND Z_ServiceLedger.payer_id = 63) THEN v.duration ELSE 0 END) / 60 AS billable_mro_hours,
Credentials.credentials
FROM
Z_ServiceLedger
INNER JOIN
ClientVisit v ON Z_ServiceLedger.clientvisit_id = v.clientvisit_id
LEFT JOIN
Employees ON v.emp_id = Employees.emp_id
LEFT JOIN
EmployeeCredential ON Employees.emp_id = EmployeeCredential.emp_id
LEFT JOIN
Credentials ON Credentials.credential_id = EmployeeCredential.credential_id
WHERE
v.rev_timein <= CASE
WHEN EmployeeCredential.end_date IS NOT NULL
THEN EmployeeCredential.end_date
ELSE GETDATE()
END
AND v.rev_timein >= #param1
AND v.rev_timein < DateAdd(d, 1, #param2)
AND Z_ServiceLedger.amount > 0
AND v.splitprimary_clientvisit_id IS NULL
AND v.gcode_primary_clientvisit_id IS NULL
AND v.non_billable = 0
AND v.non_billable = 'FALSE'
AND v.duration / 60 > 0
AND Z_ServiceLedger.action_type NOT IN ('SERVICE RATE CHANGE', 'CLIENT STATEMENT')
AND (EmployeeCredential.is_primary IS NULL OR EmployeeCredential.is_primary != 'False')
AND v.client_id != '331771 '
GROUP BY
Credentials.credentials,
v.non_billable
ORDER BY
Credentials.credentials
Some aliases and formatting really shed some light on some major logical flaws here. You have at least two predicates in your where clause that logically turn a left join into an inner join. This is total shot in the dark since from both of your questions today we don't have anything to actually work with for tables or sample data.
The biggest concern though is your where clause is trying to get rows v.non_billable = 0 and where it equals 'FALSE'. It can't be both.
Select sum(Case When v.non_billable = 0 Then v.duration Else 0 End) / 60 As billable_hours
, sum(Case When (v.non_billable = 0 And sl.payer_id = 63) Then v.duration Else 0 End) / 60 As billable_mro_hours
, c.credentials
From Z_ServiceLedger sl
Inner Join ClientVisit v On sl.clientvisit_id = v.clientvisit_id
Left Join Employees e On v.emp_id = e.emp_id
Left Join EmployeeCredential ec On e.emp_id = ec.emp_id
--if you leave these predicates in the where clause you have turned your left join into an inner join.
AND v.rev_timein <= isnull(ec.end_date, GetDate())
and (ec.is_primary Is Null Or ec.is_primary != 'False')
Left Join Credentials c On c.credential_id = ec.credential_id
Where v.rev_timein >= #param1
And v.rev_timein < DateAdd(day, 1, #param2)
And v.splitprimary_clientvisit_id Is Null
And v.gcode_primary_clientvisit_id Is Null
--you need to pick one value for v.non_billable. It can't be both 0 and 'FALSE' at the same time.
And v.non_billable = 0
And v.non_billable = 'FALSE'
--And v.duration / 60 > 0
and v.duration > 60 --this is the same thing and is SARGable
And sl.amount > 0
And sl.action_type NOT IN ('SERVICE RATE CHANGE', 'CLIENT STATEMENT')
And v.client_id != '331771 '
Group By c.credentials
, v.non_billable
Order By c.credentials
EDIT: Modified query to add a CTE to calculate the credential_rank, using a FROM (VALUES (...)) table-value-constructor syntax. This works in SQL 2008+. (https://learn.microsoft.com/en-us/sql/t-sql/queries/table-value-constructor-transact-sql?view=sql-server-2017)
SQL Fiddle
First, I'll build out a very simple piece of data.
SETUP:
CREATE TABLE Employees ( emp_id int, emp_name varchar(20) ) ;
INSERT INTO Employees (emp_id, emp_name)
VALUES (1,'Jay'),(2,'Bob')
;
CREATE TABLE Credentials ( credential_id int, credentials varchar(20), credential_rank int ) ;
INSERT INTO Credentials (credential_id, credentials, credential_rank)
VALUES (1,'BA',3),(2,'MA',2),(3,'PhD',1)
;
CREATE TABLE EmployeeCredential (emp_id int, credential_id int, is_primary bit, end_date date )
INSERT INTO EmployeeCredential (emp_id, credential_id, is_primary, end_date)
VALUES
( 1,2,null,'20200101' )
, ( 1,3,0,'20200101' ) /* NON-PRIMARY */
, ( 1,1,1,'20100101' ) /* EXPIRED CRED */
, ( 2,3,null,'20200101' )
, ( 2,3,1,'20200101' )
;
CREATE TABLE z_ServiceLedger ( payer_id int, clientvisit_id int, amount int, action_type varchar(50) ) ;
INSERT INTO z_ServiceLedger ( payer_id, clientvisit_id, amount, action_type )
VALUES (63,1,10,'XXXXX'),(63,2,20,'XXXXX'),(63,3,10,'XXXXX'),(63,4,30,'XXXXX')
;
CREATE TABLE ClientVisit ( clientvisit_id int, client_id int, non_billable bit, duration int, emp_id int , rev_timein date, splitprimary_clientvisit_id int, gcode_primary_clientvisit_id int ) ;
INSERT INTO ClientVisit ( clientvisit_id, client_id, non_billable, duration, emp_id, rev_timein, splitprimary_clientvisit_id, gcode_primary_clientvisit_id )
VALUES
(1, 1234, 0, 110, 1, getDate(), null, null )
, (2, 1234, null, 120, 1, getDate(), null, null )
, (3, 1234, 1, 110, 2, getDate(), null, null )
, (4, 1234, 0, 130, 2, getDate(), null, null )
;
MAIN QUERY:
; WITH creds AS (
SELECT c.credential_id, c.credentials, r.credential_rank
FROM Credentials c
LEFT OUTER JOIN (VALUES (1,3),(2,2),(3,1) ) r(credential_id, credential_rank)
ON c.credential_id = r.credential_id
)
SELECT DISTINCT
SUM(CASE WHEN ISNULL(v.non_billable,1) = 0 THEN v.duration ELSE 0 END)*1.0 / 60 AS billable_hours,
SUM(CASE WHEN (ISNULL(v.non_billable,1) = 0 AND zsl.payer_id = 63) THEN v.duration ELSE 0 END)*1.0 / 60 AS billable_mro_hours,
s2.credentials
FROM Z_ServiceLedger zsl
INNER JOIN ClientVisit v ON zsl.clientvisit_id = v.clientvisit_id
AND v.rev_timein >= #param1
AND v.rev_timein < DateAdd(d, 1, #param2)
AND v.splitprimary_clientvisit_id IS NULL
AND v.gcode_primary_clientvisit_id IS NULL
AND ISNULL(v.non_billable,1) = 0
AND v.duration*1.0 / 60 > 0
AND v.client_id <> 331771
INNER JOIN (
SELECT s1.emp_id, s1.emp_name, s1.credential_id, s1.credentials, s1.endDate
FROM (
SELECT e.emp_id, e.emp_name, c.credential_id, c.credentials, ISNULL(ec.end_date,GETDATE()) AS endDate
, ROW_NUMBER() OVER (PARTITION BY e.emp_id ORDER BY c.credential_rank) AS rn
FROM Employees e
LEFT OUTER JOIN EmployeeCredential ec ON e.emp_id = ec.emp_id
AND ISNULL(ec.is_primary,1) <> 0 /* I don't think a NULL is_primary should be TRUE */
LEFT OUTER JOIN creds c ON ec.credential_id = c.credential_id
) s1
WHERE s1.rn = 1
) s2 ON v.emp_id = s2.emp_id
AND v.rev_timein <= s2.endDate /* Credential not expired at rev_timein */
WHERE zsl.amount > 0
AND zsl.action_type NOT IN ('SERVICE RATE CHANGE', 'CLIENT STATEMENT')
GROUP BY s2.credentials
ORDER BY s2.credentials
Results:
| billable_hours | billable_mro_hours | credentials |
|----------------|--------------------|-------------|
| 1.833333 | 1.833333 | MA |
| 2.166666 | 2.166666 | PhD |
A couple of things to watch for:
1) Integer Division : duration/60 will return an integer. So if you had duration=70, then you'd have 70/60 = 1. You'd miss that 10 minutes, because of the result will be converted back to an integer. You lose that extra 10 minutes. Probably not what you inteded. The easiest solution is to just multiply duration by 1.0 so that it is forced into a decimal datatype and won't cause the operation to be treated like integers.
2) EmployeeCredential.is_primary != 'False' : Rather than account for the strings of "True"/"False", you should use an actual boolean value (1/0). And a NULL value should indicate that the value is NOT TRUE or FALSE rather than implying TRUE. Also, in SQL, != will work to indicate NOT EQUAL TO, but you should use <> instead. It means the same thing, but is grammatically more correct for SQL.
3) v.non_billable = 0 AND v.non_billable = 'FALSE' : This can be shortened to ISNULL(v.non_billable,1)=0 to short-circuit both checks, especially since non_billable can be NULL. You also avoid the implicit type converstion when comparing the number 0 and the string 'False'.
4) v.client_id != '331771 ' : Change to v.client_id<>33171. First, the != to <> that I mentioned earlier. Then '331771' is implicitly converted to a number. You should avoid implicit conversions.
5) You originally had v.non_billable in your GROUP BY. Since you aren't including it in your SELECT, you can't use it to GROUP BY. Also, you're already filtering out everything other than non_billable=0, so you'd never have more than one value to GROUP BY anyway. Just exclude it.
6) CASE WHEN EmployeeCredential.end_date IS NOT NULL THEN EmployeeCredential.end_date ELSE GETDATE() END : This is the same as saying ISNULL(EmployeeCredential.end_date,GETDATE()).
7) Unless you actually need to filter out specific records for a specific reason, more your JOIN conditions into the JOIN rather than using them in the WHERE clause. This will help you be more efficient with the data your initial query returns before it is filtered or reduced. Also, when using a WHERE filter with a LEFT JOIN, you may end up with unexpected results.

Count rows for two columns using two different clauses

I'm after a CTE which I want to return two columns, one with the total number of 1's and one with the total number of 0's. Currently I can get it to return one column with the total number of 1's using:
WITH getOnesAndZerosCTE
AS (
SELECT COUNT([message]) AS TotalNo1s
FROM dbo.post
WHERE dbo.checkletters([message]) = 1
--SELECT COUNT([message]) AS TotalNo0s
--FROM dbo.post
--WHERE dbo.checkletters([message]) = 0
)
SELECT * FROM getOnesAndZerosCTE;
How do I have a second column called TotalNo0s in the same CTE which I have commented in there to show what I mean.
Using conditional aggregation:
WITH getOnesAndZerosCTE AS(
SELECT
TotalNo1s = SUM(CASE WHEN dbo.checkletters([message]) = 1 THEN 1 ELSE 0 END),
TotalNo0s = SUM(CASE WHEN dbo.checkletters([message]) = 0 THEN 1 ELSE 0 END)
FROM post
)
SELECT * FROM getOnesAndZerosCTE;
For using COUNT() directly just be aware that it counts any NON-NULL values. You can omit the ELSE condition which implicitly returns NULL if not stated
SELECT
COUNT(CASE WHEN dbo.checkletters([message]) = 1 THEN 1 END) TotalNo1s
, COUNT(CASE WHEN dbo.checkletters([message]) = 0 THEN 1 END) TotalNo0s
FROM post
or, explicitly state NULL
SELECT
COUNT(CASE WHEN dbo.checkletters([message]) = 1 THEN 1 ELSE NULL END) TotalNo1s
, COUNT(CASE WHEN dbo.checkletters([message]) = 0 THEN 1 ELSE NULL END) TotalNo0s
FROM post
You can do it without CTE
select count(message) total,
dbo.checkletters(message) strLength
from post
group by dbo.checkletters(message)
having dbo.checkletters(message) in (1, 2) //All the messages with length 1 or 2

Converting a cursor/while loop into a set based approach

I am very new to SQL and I am trying to update a stored procedure that has a cursor in it. I had never seen a cursor prior to this one. The cursor's select statement has an inner join, but returns only a single column of IDs. The cursor calculates the number of deleted accounts for every ID, on a row by row basis.
At the end of the stored procedure, the number of deletion variables are inserted into a table
I was hoping someone that understands more about cursors/while loops would be able to suggest the best way to convert the code above into an efficient set based approach.
This is a set based way:
;WITH IDS AS
(
SELECT DISTINCT c.p_id
FROM dbo.deletion_h dh
INNER JOIN dbo.Child c
ON dh.C_id = c.c_id
WHERE CONVERT(CHAR(25),dh.delete_date,101) = #ReportDate
AND c.isT = 1
AND c.p_id NOT IN (SELECT p_id FROM dbo.Parent WHERE support = 'Y')
), Data AS
(
SELECT p_id,
COUNT(*) ActiveChild,
SUM(CASE WHEN isT = 1 AND [level] <> 'H' THEN 1 ELSE 0 END) activePk8,
SUM(CASE WHEN isT = 1 AND [level] = 'H' THEN 1 ELSE 0 END) activeHS
FROM dbo.child c
WHERE [login] <> 'f'
AND EXISTS( SELECT 1 FROM IDS
WHERE p_id = c.p_id)
GROUP BY p_id
)
SELECT SUM(CASE WHEN ActiveChild > 0 THEN 1 ELSE 0 END) NumParentDeletions,
SUM(CASE WHEN activechildPk8 > 0 THEN 1 ELSE 0 END) NumDeletionsPk8,
SUM(CASE WHEN activeHS > 0 THEN 1 ELSE 0 END) NumDeletionsHS
FROM Data
You can modify the last SELECT to make it insert those values into your table.

counting records on the same table with different values possibly none sql server 2008

I have a inventory table with a condition i.e. new, used, other, and i am query a small set of this data, and there is a possibility that all the record set contains only 1 or all the conditions. I tried using a case statement, but if one of the conditions isn't found nothing for that condition returned, and I need it to return 0
This is what I've tried so far:
select(
case
when new_used = 'N' then 'new'
when new_used = 'U' then 'used'
when new_used = 'O' then 'other'
end
)as conditions,
count(*) as count
from myDB
where something = something
group by(
case
when New_Used = 'N' then 'new'
when New_Used = 'U' then 'used'
when New_Used = 'O' then 'other'
end
)
This returns the data like:
conditions | count
------------------
new 10
used 45
I am trying to get the data to return like the following:
conditions | count
------------------
new | 10
used | 45
other | 0
Thanks in advance
;WITH constants(letter,word) AS
(
SELECT l,w FROM (VALUES('N','new'),('U','used'),('O','other')) AS x(l,w)
)
SELECT
conditions = c.word,
[count] = COUNT(x.new_used)
FROM constants AS c
LEFT OUTER JOIN dbo.myDB AS x
ON c.letter = x.new_used
AND something = something
GROUP BY c.word;
try this -
DECLARE #t TABLE (new_used CHAR(1))
INSERT INTO #t (new_used)
SELECT t = 'N'
UNION ALL
SELECT 'N'
UNION ALL
SELECT 'U'
SELECT conditions, ISNULL(r.cnt, 0) AS [count]
FROM (
VALUES('U', 'used'), ('N', 'new'), ('O', 'other')
) t(c, conditions)
LEFT JOIN (
SELECT new_used, COUNT(1) AS cnt
FROM #t
--WHERE something = something
GROUP BY new_used
) r ON r.new_used = t.c
in output -
new 2
used 1
other 0
You can do it as a cross-tab:
select
sum(case when new_used = 'N' then 1 else 0 end) as N,
sum(case when new_used = 'U' then 1 else 0 end) as U,
sum(case when new_used = 'O' then 1 else 0 end) as Other
from myDB
where something = something