-1

我有一个查询为单个服务返回多行,因为一个人可能有多个凭据。在医学领域,您保留了几个证书,但为简单起见,我将仅使用标准证书 Phd、MA、MS、BA、BS、AS

我需要知道最简单的方法来忽略 Z_ServiceLedger.clientvisit_id 在层次结构中具有任何 Credentials.credentials 较低的行。因此,如果员工从事一项服务,并且他拥有 Phd 和 MA,则仅返回 Phd 的行,如果他拥有 Phd、Ma 和 BA,则仅返回 phd 的行。我们有大约 50 个凭据,因此如果我对每个凭据使用 CASE,您可以看到会变得多么混乱,我希望有更好的方法来避免这种情况。

这是我当前的查询:

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
4

2 回答 2

1

一些别名和格式确实揭示了这里的一些主要逻辑缺陷。您的 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
于 2018-08-02T19:27:19.430 回答
0

编辑:修改查询以添加 CTE 来计算credential_rank,使用FROM (VALUES (...))table-value-constructor 语法。这适用于 SQL 2008+。(https://docs.microsoft.com/en-us/sql/t-sql/queries/table-value-constructor-transact-sql?view=sql-server-2017

SQL小提琴

首先,我将构建一个非常简单的数据。

设置

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将返回一个整数。因此,如果您有duration=70,那么您将获得 70/60 = 1。您会错过 10 分钟,因为结果将被转换回整数。你失去了额外的10分钟。可能不是你想要的。最简单的解决方案是仅乘以duration1.0以便将其强制为十进制数据类型,并且不会导致操作被视为整数。

2) EmployeeCredential.is_primary != 'False':您应该使用实际的布尔值 (1/0),而不是考虑字符串“True”/“False”。并且一个NULL值应该表明该值是NOT TRUEFALSE而不是暗示TRUE。此外,在 SQL 中,!=可以指示NOT EQUAL TO,但您应该<>改用。这意味着同样的事情,但在语法上对 SQL 更正确。

3)v.non_billable = 0 AND v.non_billable = 'FALSE':这可以缩短为ISNULL(v.non_billable,1)=0短路两个检查,特别是因为non_billable可以NULL。在比较数字 0 和字符串 'False' 时,您还可以避免隐式类型转换。

4) v.client_id != '331771 ': 改为v.client_id<>33171. 首先,我之前提到!=的那个。<>然后'331771'隐式转换为数字。您应该避免隐式转换。

5) 你最初v.non_billable在你的GROUP BY. 由于您没有将其包含在 中SELECT,因此您无法将其用于GROUP BY. 此外,您已经过滤掉了除 之外的所有内容,因此无论如何non_billable=0您永远不会拥有一个以上的值。GROUP BY只是排除它。

6)CASE WHEN EmployeeCredential.end_date IS NOT NULL THEN EmployeeCredential.end_date ELSE GETDATE() END:这和说的一样ISNULL(EmployeeCredential.end_date,GETDATE())

7)除非您出于特定原因确实需要过滤掉特定记录,否则将您的JOIN条件更多地放入JOIN而不是在WHERE子句中使用它们。这将帮助您更有效地处理初始查询返回的数据,然后再过滤或减少数据。此外,当使用带有 的WHERE过滤器时LEFT JOIN,您可能会得到意想不到的结果。

于 2018-08-02T21:38:32.943 回答