TSQL仅选择最高的credentail
我有一个查询返回单个服务的多行,因为一个人可能有多个凭据。在医学领域,你会保留一些证书,但为了简单起见,我只会使用标准证书,如博士、硕士、硕士、学士、学士 我需要知道忽略Z_ServiceLedger.clientvisit_id在层次结构中具有任何凭据的行的最简单方法。因此,如果一名员工提供服务,他拥有博士学位和硕士学位,则只返回博士学位的行,如果他拥有博士学位,则只返回博士学位的行。我们有大约50个凭证,所以如果我使用每个凭证的用例,您可以看到这会变得多么混乱,我希望有更好的方法来避免这种情况 以下是我当前的查询:TSQL仅选择最高的credentail,sql,sql-server,tsql,Sql,Sql Server,Tsql,我有一个查询返回单个服务的多行,因为一个人可能有多个凭据。在医学领域,你会保留一些证书,但为了简单起见,我只会使用标准证书,如博士、硕士、硕士、学士、学士 我需要知道忽略Z_ServiceLedger.clientvisit_id在层次结构中具有任何凭据的行的最简单方法。因此,如果一名员工提供服务,他拥有博士学位和硕士学位,则只返回博士学位的行,如果他拥有博士学位,则只返回博士学位的行。我们有大约50个凭证,所以如果我使用每个凭证的用例,您可以看到这会变得多么混乱,我希望有更好的方法来避免这种情
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
选择DISTINCT
总和(如果v.non_bilable=0,则v.duration否则0 END)/60作为计费小时,
总和(当(v.non_bilable=0和Z_serviceloger.payer_id=63)然后v.duration ELSE 0 END)/60作为可计费的mro_小时,
凭证
从…起
Z_服务分类账
内连接
Z_ServiceLedger上的ClientVisit v.ClientVisit_id=v.ClientVisit_id
左连接
v.emp_id上的员工=Employees.emp_id
左连接
EmployeeCredential ON Employees.emp\u id=EmployeeCredential.emp\u id
左连接
凭据上的凭据。凭据\u id=员工凭据。凭据\u id
哪里
v、 rev_timein=@param1
和v.rev_timein0
并且v.splitprimary\u clientvisit\u id为空
并且v.gcode_primary_clientvisit_id为空
和v.non_bilable=0
和v.non_bilable='FALSE'
和v.duration/60>0
和Z_ServiceLedger.action_类型不在('SERVICE RATE CHANGE','CLIENT STATEMENT')
和(EmployeeCredential.is_primary为NULL或EmployeeCredential.is_primary!=“False”)
和v.client_id!='331771 '
分组
凭证,凭证,
v、 不计费
订购人
凭证
一些别名和格式确实揭示了一些主要的逻辑缺陷。在where子句中至少有两个谓词在逻辑上将左连接转换为内部连接。这完全是瞎猜,因为从你们今天的两个问题来看,我们没有任何实际的表格或样本数据
不过,最大的问题是where子句试图获取行v.non_billable=0,其中它等于'FALSE'。不可能两者都有
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
选择sum(如果v.non_bilable=0,则v.duration Else 0 End)/60作为计费小时数
,总和(当(v.non_bilable=0和sl.payer_id=63)时,则v.duration Else 0 End)/60作为可计费的mro_小时数
,c
来自Z_ServiceLedger sl
sl.ClientVisit\u id=v.ClientVisit\u id上的内部联接客户端访问v
左键在v.emp_id=e.emp_id上加入员工e
左加入e.emp_id=ec.emp_id上的EmployeeCredential ec
--如果将这些谓词保留在where子句中,则已将左连接变成内部连接。
和v.rev_timein=@param1
和v.rev_timein0
v.duration>60——这是同样的事情,是可以接受的
和sl.amount>0
和sl.action_类型不在('服务费率变化','客户声明')
和v.client_id!='331771 '
按c.凭据分组
,v.不收费
按c.证书订购
编辑:修改查询以添加CTE,使用FROM(VALUES(…)
表值构造函数语法计算凭证等级。这适用于SQL 2008+。()
首先,我将构建一段非常简单的数据
设置:
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 )
;
; 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
| billable_hours | billable_mro_hours | credentials |
|----------------|--------------------|-------------|
| 1.833333 | 1.833333 | MA |
| 2.166666 | 2.166666 | PhD |
主查询:
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 )
;
; 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
| billable_hours | billable_mro_hours | credentials |
|----------------|--------------------|-------------|
| 1.833333 | 1.833333 | MA |
| 2.166666 | 2.166666 | PhD |
有几件事需要注意:
1) 整数除法:duration/60
将返回一个整数。因此,如果您的持续时间为70,那么您的持续时间为70/60=1。您将错过10分钟,因为结果将转换回整数。你失去了额外的10分钟。可能不是你所说的。最简单的解决方案是将duration
乘以1.0
,这样它就被强制为十进制数据类型,而不会导致操作被视为整数
2) EmployeeCredential.is_primary!='False'
:不应考虑“True”/“False”字符串,而应使用实际的布尔值(1/0)。NULL
值应表示该值为非真
或假
,而不是表示真
。另外,在SQL中,=
将用于指示不等于
,但您应该改用
。它的意思是相同的,但在语法上更适合SQL
3) v.non\u billable=0和v.non\u billable='FALSE'
:这可以缩短为ISNULL(v.non\u billable,1)=0
,以使两个检查短路,特别是因为non\u billable
可以是NULL
。在比较数字0和字符串“False”时,还可以避免隐式类型转换
4) v.client\u id!='331771'
:更改为v.client_id33171
。首先是=代码>到我前面提到的
。然后'331771'
被隐式转换为一个数字。您应该避免隐式转换
5) 您的组中最初有v.non_bilable
。由于未将其包括在选择中,因此不能使用它来按分组。此外,您已经过滤掉了除非计费=0
之外的所有内容,因此您永远不会有多个值