SQL:关于子类型的报告随时间推移

时间:2018-07-06 15:56:47

标签: sql sql-view subtyping

(标题可能需要重新措辞;请随时进行编辑)

我正在审查一个工作项目,负责将SAS程序转换为View。该程序用于从我们的表中为另一组数据源生成源文件。该视图的目的是产生与先前创建的源文件相同的内容,以便可以退出SAS程序,而另一组可以指向我们的视图。

困难在于我们(公司)具有的子类型化概念。在用户方面,我们从技术上讲有3种类型:

  • 类型1:UG1
  • 类型2:UG2
  • 类型3:UG0
    • 0表示它们当前不属于活动组

根据与日期/时间相关的子类型定义的一个报告项目是用户电话号码。

例如,给定的用户正在类型之间进行转换(UG1到UG2;创建两个条目),并且每种类型的条目的有效日期跨度可能与这两种类型中的TODAY重叠。某些仅与成员相关联的指示符设置为在每个条目处表示它们的类型为UG2(在联接上,包含UG1的条目也将具有指示当前类型的指示器)。

在报告该用户的电话号码时,我们只想查看一个电话号码,但是我们有多个可用的电话号码(家庭,办公,移动和其他)。对于电话号码本身,我们有一个合并的设置,按照家庭,移动,工作和其他的顺序排列(如果家庭没有价值,请检查移动等)。关于子类型化,我们必须检查与UG2相关的电话号码,但是如果没有返回电话号码,那么我们将尝试从UG1中查找电话号码(如果存在)。否则,电话号码将返回为空,并且报告将显示“无电话号码”。如果出现UG1类型的用户,我们还必须朝另一个方向看。

注意:对于包含电话号码的表,还有一个指示符用于指示相同的子类型(UG1条目和UG2条目)。 (基本上是根据创建时或有效日期跨度时的子类型创建的用户信息)

所以简而言之,逻辑类似于这种格式:

  • UG1类型的用户
    • COALESCE(家庭,移动,工作,其他)
      • 如果未选择任何数字,则COALESCE(UG2(家庭,移动,工作,其他)
        • 如果未选择任何数字,则为空
  • UG2类型的用户
    • COALESCE(家庭,移动,工作,其他)
      • 如果未选择任何数字,则COALESCE(UG1(家庭,移动,工作,其他)
        • 如果未选择任何数字,则为空

排除UG0中的用户。它主要被列为一种可能性,但是如果不清楚,我可以删除它。

对于该视图,对于一个子类型中存在的边缘案例用户,我遇到了困难,而对于另一个子类型中的边缘案例用户,我则遇到了困难。鉴于视图方面的一些限制,没有变量或没有创建## table(临时表),我开始使用通用表表达式(cte)。

每种类型都有自己的CTE,UG1Phone和UG2Phone,并且使用CTE来组合它们。我发现使用完全外部联接可以获取所有需要的数据,以及一些额外的空条目。由于null可能发生(某些方面不存在),因此我看不到一种简单地执行检查以忽略null的方法。我做了一个where语句,检查了用户的子类型指示符,并对与电话号码条目关联的子类型进行了空检查(如果用户子类型只是UG1,则完全外部联接将根据UG2 CTE)。

我的主要问题涉及分隔(UG1 CTE和UG2 CTE)以及如何对数据进行分组(带有许多Case语句的Full Outer Join)。

如前所述,我对每个用户子类型都有一个CTE,并且没有明确的路线图来知道是否还会有其他子类型。我可以合并CTE并移动FULL OUTER JOIN,但是感觉嵌套子查询太混乱了。再次,我对这种连接方式的理由是,如果需要并可以继续使用下一个子类型。

还有另一种产生相同结果的方法吗?我的目标是减少看起来重复的查询或看起来可以简化的查询(对于每个用户CTE,唯一的区别是指标值;合并的CTE具有WHERE语句,该语句也基于如上所述的排序逻辑)。

对于case语句,View要求使用case语句来确定首先使用FULL OUTER JOIN的哪一侧(coalesce语句和其他列)。当前,每个定义的列都包含一个case语句,以确定首先要检查哪个表的可用数据。

是否有一种方法可以执行一个语句,或者至少不添加每一列的case语句?我正在寻找一种方法来确定每个列的使用顺序,而不必在每个列上进行相同的检查。例如:

SELECT
CASE 
WHEN Indicator = 1 
    THEN ISNULL(UG1.effectiveDate, UG2.EffectiveDate)
WHEN Indicator = 2
    THEN ISNULL(UG2.effectiveDate, UG2.EffectiveDate)
ELSE
    NULL
END AS [EffectiveDate]
, CASE
WHEN Indicator = 1
    THEN ISNULL(UG1.phoneNumber, UG2.phoneNumber)
WHEN Indicator = 2
    THEN ISNULL(UG2.phoneNumber, UG1.phoneNumber)
ELSE
    NULL
END AS [PhoneNumber]
, -- and so on

随意问我上面的任何问题。对于列出的项目,我找到了一个可行的解决方案,但是我觉得对于那些正在研究的人来说,许多单独的部分可能不清楚。因此,我要解决的问题是定义一个明确的方法,将这些数据分组/合并,以确保删除重复的空数据并以正确的顺序检查数据。


(编辑:添加涉及的表结构)

dbo.UsersCommonTable

列:

  • UserId VARCHAR
  • CurrentUserTypeId INT
  • EffectiveDate DATETIME
  • TerminationDate DATETIME
    • 通过更改CurrentUserTypeId的概念,EffectiveDate用于将用户信息映射到特定的日期范围(在生效日期和终止日期之间)

dbo.UserInformation

列:

  • UserId VARCHAR
  • UserTypeId INT
    • 关于何时创建(例如:对于组中的用户 即使UsersCommonTable的用户作为UserTypeId为2,该表中的UserTypeId也将为1
  • 家庭电话VARCHAR
  • 手机VARCHAR
  • WorkPhone VARCHAR
  • OtherPhone VARCHAR
  • StartDate DATETIME
  • EndDate DATETIME
    • 由于当前时间/没有指定的结束日期而可能为空
    • 开始日期和结束日期仅是指用户何时开始使用此信息以及何时停止使用此信息(如果适用)
  • DateChanged DATETIME
    • DateChanged与首次输入时的创建日期相同,并且更改代表对特定记录的每次更改都会更新(不是很好的设计,但是当前存在)

dbo.UserReportView

列:

  • UserId INT
  • 电话号码VARCHAR
    • 如果为NULL,则替换为“无电话号码”。
  • //更多用户信息

让我知道是否需要进行任何更改,或者对所要求的内容有误解。


(编辑:添加SAS特定代码/逻辑和当前要求)

除了电话号码之间的合并以外,其他要求是相同的。以前,HomePhone是唯一使用的列,并且存在用户类型的定向概念。因此,该逻辑看起来更像这样:

  • UG1类型的用户
    • 检查HomePhone是否为空
      • 如果为null,则检查UG2(HomePhone)是否为null
        • 如果仍然为null,则返回“无电话号码”。

以UG2开头,依此类推。

proc sql noprint;
create table cc_user_list as
select distinct
         UserID
        ,EffectiveDate as effdt2
        ,mdy(input(scanq(EffectiveDate ,1,'/'),2.), 
             input(scanq(EffectiveDate ,2,'/'),2.),
             input(scanq(EffectiveDate ,3,'/'),4.)) format yymmdd10. as effdt
        ,mdy(input(scanq(TerminationDate,1,'/'),2.), 
             input(scanq(TerminationDate,2,'/'),2.),
             input(scanq(TerminationDate,3,'/'),4.)) format yymmdd10. as termdt
from imp.UsersCommonTable 
where UserTypeId = 1 or UserTypeId = 2
order by UserID ;
quit;

data cc_user_list;
  set cc_user_list;
  if effdt <= today() <= termdt;  /* Only keep currently Active cases */
run; 

proc sql noprint;
create table User_phone_number as
select distinct
        cats(UserID, '01') as User_no
       ,homephone as usedPhoneNumber
       ,datechanged
       ,startdate
       ,enddate
from impl=.UserInformation 
where (HomePhone ^= ' ') and
       UserTypeID = 1  and
       (datepart(enddate) >= today() 
             or enddate = . ) 
order by User_no ;
quit;

    proc sql noprint;
create table User_phone_number as
select distinct
        cats(UserID, '01') as User_no
       ,homephone as otherPhoneNumber
       ,datechanged
       ,startdate
       ,enddate
from imp.UserInformation
where (HomePhone ^= ' ') and
       UserTypeID = 2  and
       (datepart(enddate) >= today() 
             or enddate = . ) 
order by User_no ;
quit;

data User_phone_number (drop=usedPhoneNumber);
  set User_phone_number;
  if usedPhoneNumber = ' ' and otherPhoneNumber^= ' ' then usedPhoneNumber= otherPhoneNumber;
run;

data User_phone_number (drop=usedPhoneNumber);
  set User_phone_number;
  if usedPhoneNumber = ' ' then usedPhoneNumber= 'No Phone Number.';
run;

我缩短并改写了很多可用代码。让我知道这是否没有道理(我可以在某种程度上翻译一些SAS,但是我不知道在SAS中制作/更改程序的实际操作)


(编辑:示例数据)

在UsersCommon表中:

UserId CurrentUserTypeId EffectiveDate TerminationDate
  U001                 2    01/01/2018      09/30/2018
  U001                 2    09/01/2018      09/30/2019

在用户信息表中:

UserId UserTypeId  StartDate   EndDate     HomePhone
  U001          1  02/01/2018  09/15/2018  1112223344
  U001          2  09/01/2018  09/30/2019  NULL

报告结果在2018年2月1日至2018年9月15日之间的任意一天运行:

UserId PhoneNumber
  U001 1112223344

报告结果在2018年9月15日之后的任意一天运行:

UserId PhoneNumber
  U001 No Phone Number.

(进行编辑以包括相关查询的示例)

CREATE VIEW [dbo].[UserReportView] AS
    WITH UG1Phone AS (
        SELECT
                UserId
                , UserTypeId
                , HomePhone
                , MobilePhone
                , WorkPhone
                , OtherPhone
                , StartDate
                , EndDate
        FROM
                UserInformation uinfo
        INNER JOIN
                (
                    SELECT
                            UserId
                            , UserTypeId
                            , MAX(DateChanged) LatestChanged
                    FROM
                            UserInformation
                    WHERE
                            UserTypeId = 1
                ) Latest
        ON
                uinfo.UserId = Latest.UserId
                AND uinfo.UserTypeId = Latest.UserTypeId
                AND uinfo.DateChanged = Latest.LatestChanged
        WHERE
                uinfo.EndDate >= GETDATE() OR uinfo.EndDate IS NULL)
    ),
    UG2Phone AS (
        SELECT
                UserId
                , UserTypeId
                , HomePhone
                , MobilePhone
                , WorkPhone
                , OtherPhone
                , StartDate
                , EndDate
        FROM
                UserInformation uinfo
        INNER JOIN
                (
                    SELECT
                            UserId
                            , UserTypeId
                            , MAX(DateChanged) LatestChanged
                    FROM
                            UserInformation
                    WHERE
                            UserTypeId = 2
                ) Latest
        ON
                uinfo.UserId = Latest.UserId
                AND uinfo.UserTypeId = Latest.UserTypeId
                AND uinfo.DateChanged = Latest.LatestChanged
        WHERE
                uinfo.EndDate >= GETDATE() OR uinfo.EndDate IS NULL)
    ),
    OutputPhone (UserId, ProvidedPhone, DateChanged, StartDate, EndDate, UserTypeId) AS (
        SELECT
                CASE
                    WHEN UG1.OrderIndex = 1
                        THEN ISNULL(UG1.UserId, UG2.UserId)
                    WHEN UG2.OrderIndex = 1
                        THEN ISNULL(UG2.UserId, UG1.UserId)
                    ELSE
                        NULL
                END As UserId
                , CASE
                    WHEN UG1.OrderIndex = 1
                        THEN ISNULL(UG1.ProvidedNumber, UG2.ProvidedNumber)
                    WHEN UG2.OrderIndex = 1
                        THEN ISNULL(UG2.ProvidedNumber, UG1.ProvidedNumber)
                    ELSE
                        NULL
                END As ProvidedNumber
                , --and so on for specified columns
        FROM
                (
                    SELECT
                    DISTINCT
                            CASE WHEN uct.CurrentUserTypeId = 1 THEN 1 ELSE 2 END OrderIndex
                            , ugp.UserId
                            , ugp.DateChanged
                            , ugp.StartDate
                            , ugp.EndDate
                            , ugp.UserTypeId
                            , COALESCE(ugp.HomePhone, ugp.MobilePhone, ugp.WorkPhone, ugp.OTherPhone) ProvidedNumber
                    FROM
                            UG1Phone ugp
                    INNER JOIN
                            UsersCommonTable uct
                    ON
                            ugp.UserId = uct.UserId AND GETDATE() BETWEEN uct.EffectiveDate and uct.TerminationDate
                ) UG1
        FULL OUTER JOIN
                (
                    SELECT
                    DISTINCT
                            CASE WHEN uct.CurrentUserTypeId = 2 THEN 1 ELSE 2 END OrderIndex
                            , ugp.UserId
                            , ugp.DateChanged
                            , ugp.StartDate
                            , ugp.EndDate
                            , ugp.UserTypeId
                            , COALESCE(ugp.HomePhone, ugp.MobilePhone, ugp.WorkPhone, ugp.OTherPhone) ProvidedNumber
                    FROM
                            UG2Phone ugp
                    INNER JOIN
                            UsersCommonTable uct
                    ON
                            ugp.UserId = uct.UserId AND GETDATE() BETWEEN uct.EffectiveDate and uct.TerminationDate
                ) UG2
        ON
                UG1.UserId = UG2.UserId
        WHERE
                1 = CASE
                        WHEN UG1.OrderIndex = 1
                            THEN CASE WHEN UG1.UserTypeId IS NOT NULL THEN 1 ELSE 0 END
                        WHEN UG2.OrderIndex = 1
                            THEN CASE WHEN UG2.UserTypeId IS NOT NULL THEN 1 EKSE 0 END
                        ELSE
                            0
                        END
    ), --and so on to complete the rest of the report
GO

0 个答案:

没有答案