Domanda

Click image to see the table structure and problm http://i.stack.imgur.com/odWKL.png

enter image description here

SELECT B.ENTITYID, 
       B.BALANCEDATE,
       B.BALANCE, 
       MIN(DATEDIFF(DAY,B.BALANCEDATE,C.STATUSDATE)) RECENT
FROM   BALANCES B JOIN STATUS C ON B.ENTITYID = C.ENTITYID
GROUP  BY B.ENTITYID, B.BALANCEDATE,B.BALANCE
HAVING B.ENTITYID =1

I have tried the following but can't go further than that as more nested selects have problems with accessing similar attributes:

È stato utile?

Soluzione 3

Also in SQLServer2005+ you can use option with APPLY() operator.

The APPLY operator allows you to join two table expressions. Right table expression is processed every time for each row from the left table expression.The final result-set contains all the selected columns from the left table expression followed by all the columns of right table expression. OUTER APLLY for those rows for which there are no corresponding matches in right table expression, it contains NULL values in columns of right table expression.

SELECT e.EntityName, b.BalanceDate AS Date, b.Balance, o.Status
FROM Entity e JOIN Balances b ON e.EntityID = b.EntityID
              OUTER APPLY (
                           SELECT TOP 1 s.Status AS Status                           
                           FROM Status s
                           WHERE b.EntityID = s.EntityID 
                             AND s.StatusDate < b.BalanceDate
                           ORDER BY s.StatusDate DESC
                           ) o
WHERE e.EntityName = 'ABCD' 

For improving performance(force INDEX SEEK operation) use this indexes with INCLUDE clause.The INCLUDE clause adds the data at the lowest/leaf level, rather than in the index tree. This makes the index smaller because it's not part of the tree

CREATE INDEX x ON Status(StatusDate) INCLUDE(EntityID, Status)
CREATE INDEX x ON Entity(EntityName) INCLUDE(EntityID)
CREATE INDEX x ON Balances(EntityID, BalanceDate, Balance)

Demo on SQLFiddle

enter image description here

Altri suggerimenti

See @ljh's Answer if you are working on SQL Server. The solution below works on MySQL. It's a little bit messy since MySQL doesn't support CTE and Window Function.

MySQL

SET @entity_name = 'ABCD';

SELECT  b.*, d.Status
FROM    Entity a
        INNER JOIN Balances b
            ON a.EntityID = b.EntityID
        LEFT JOIN
        (
            SELECT  a.EntityID, 
                    a.StatusDate StartDate, 
                    b.StatusDate + Interval -1 DAY EndDate,
                    a.Status
            FROM
                    (
                        SELECT  b.*, @r1 := @r1 + 1 AS Row_number
                        FROM    `Entity` a
                                INNER JOIN Status b
                                    ON a.EntityID = b.EntityID
                                CROSS JOIN (SELECT @r1 := 0) rowCount
                        WHERE   a.EntityName = @entity_name
                        ORDER   BY b.Status ASC
                    ) a
                    LEFT JOIN
                    (
                        SELECT  b.*, @r2 := @r2 + 1 AS Row_number
                        FROM    `Entity` a
                                INNER JOIN Status b
                                    ON a.EntityID = b.EntityID
                                CROSS JOIN (SELECT @r2 := 1) rowCount
                        WHERE   a.EntityName = @entity_name
                        ORDER   BY b.Status ASC
                    ) b ON  a.Row_number = b.Row_number
        ) d 
        ON b.BalanceDate BETWEEN d.StartDate AND d.EndDate
WHERE   a.EntityName = @entity_name

The Brief Break Down

Since, MySQL do not support Windowing Function such as ROW_NUMBER(), the query below uses User Variable to provide row number similar to ROW_NUMBER() for every record which will then be used to join on the other subquery.

SELECT  b.*, @r1 := @r1 + 1 AS Row_number
FROM    `Entity` a
        INNER JOIN Status b
            ON a.EntityID = b.EntityID
        CROSS JOIN (SELECT @r1 := 0) rowCount
WHERE   a.EntityName = @entity_name
ORDER   BY b.Status ASC

OUTPUT

╔══════════╦═════════════════════════════════╦════════╦════════════╗
║ ENTITYID ║           STATUSDATE            ║ STATUS ║ ROW_NUMBER ║
╠══════════╬═════════════════════════════════╬════════╬════════════╣
║        1 ║ May, 29 2010 00:00:00+0000      ║ A      ║          1 ║
║        1 ║ April, 16 2010 00:00:00+0000    ║ B      ║          2 ║
║        1 ║ April, 02 2010 00:00:00+0000    ║ C      ║          3 ║
║        1 ║ February, 26 2010 00:00:00+0000 ║ D      ║          4 ║
╚══════════╩═════════════════════════════════╩════════╩════════════╝

The main purpose of the provision of row number for the records is it will be used to join on another subquery so we can get the StartDate and EndDate for every Status. This is easy on SQL Server 2012 because it has a Windowing Function called LAG()

╔══════════╦═════════════════════════════════╦══════════════════════════════╦════════╗
║ ENTITYID ║            STARTDATE            ║           ENDDATE            ║ STATUS ║
╠══════════╬═════════════════════════════════╬══════════════════════════════╬════════╣
║        1 ║ May, 29 2010 00:00:00+0000      ║ (null)                       ║ A      ║
║        1 ║ April, 16 2010 00:00:00+0000    ║ May, 28 2010 00:00:00+0000   ║ B      ║
║        1 ║ April, 02 2010 00:00:00+0000    ║ April, 15 2010 00:00:00+0000 ║ C      ║
║        1 ║ February, 26 2010 00:00:00+0000 ║ April, 01 2010 00:00:00+0000 ║ D      ║
╚══════════╩═════════════════════════════════╩══════════════════════════════╩════════╝

Once the Status Range has been organized. It is now the basis as the LookUp status for every Balances.

The final Result

╔══════════╦═════════════════════════════════╦═════════╦════════╗
║ ENTITYID ║           BALANCEDATE           ║ BALANCE ║ STATUS ║
╠══════════╬═════════════════════════════════╬═════════╬════════╣
║        1 ║ May, 01 2010 00:00:00+0000      ║     100 ║ B      ║
║        1 ║ April, 01 2010 00:00:00+0000    ║      50 ║ D      ║
║        1 ║ March, 01 2010 00:00:00+0000    ║      75 ║ D      ║
║        1 ║ February, 01 2010 00:00:00+0000 ║      85 ║ (null) ║
╚══════════╩═════════════════════════════════╩═════════╩════════╝

SQL Server 2012

The query above demonstrated in MySQL can be easily converted in TSQL by using Common Table Expression and a Window Function which uses LAG() (introduce in SQL Server 2012 only)

WITH lookupTable
AS
(
    SELECT  EntityID, 
            StatusDate StartDate, 
            DATEADD(DAY, -1, LAG(StatusDate) OVER(PARTITION BY EntityID ORDER BY Status)) EndDate,
            Status
    FROM    Status
)
SELECT  b.*, d.Status
FROM    Entity a
        INNER JOIN Balances b
            ON a.EntityID = b.EntityID
        LEFT JOIN lookupTable d
            ON b.BalanceDate BETWEEN d.StartDate AND d.EndDate AND
               d.EntityID = a.EntityID
WHERE   a.EntityName = 'ABCD'

OUTPUT

╔══════════╦═════════════════════════════════╦═════════╦════════╗
║ ENTITYID ║           BALANCEDATE           ║ BALANCE ║ STATUS ║
╠══════════╬═════════════════════════════════╬═════════╬════════╣
║        1 ║ May, 01 2010 00:00:00+0000      ║     100 ║ B      ║
║        1 ║ April, 01 2010 00:00:00+0000    ║      50 ║ D      ║
║        1 ║ March, 01 2010 00:00:00+0000    ║      75 ║ D      ║
║        1 ║ February, 01 2010 00:00:00+0000 ║      85 ║ (null) ║
╚══════════╩═════════════════════════════════╩═════════╩════════╝

Using SQL Server 2012 as RDBMS as example. This is the 1st query you need.
This answer utilized SQL CTE(Common Table Expression), which may not apply to other RDBMS system.
SQL FIDDLE DEMO
Explanantion of query:
1. First join [Balances] and [Status] table, use BalanceDate > StatusDate to filter out result, return all columns from both table, this is because all is needed later on.
2. Join output from Step .1 with [Entity] table, use EntityName to filter out result, still leave all columns from 3 tables, certainly duplicate EntityID is not needed.
3. Use CTE to save the join
4. Use CTE to save the rank applied on the join output
5. Use Rank number to filter out result and order by BalanceDate


;with CTE_AfterJoin
as 
(
    select E.EntityID, E.EnityName, C.BalanceDate, C.Balance, C.StatusDate, C.status
    from Entity E
    left join (
            select B.EntityID, B.BalanceDate, B.Balance,S.StatusDate, S.[Status]
            from Balances B 
            left join  [Status] S
            on B.EntityID = S.EntityID and B.BalanceDate > S.StatusDate
            ) C
    on E.EntityID = C.EntityID
    where E.EnityName = 'ABCD'
),
CTE_afterRank
as 
(
    select EnityName, BalanceDate, Balance, 
           rank() over (partition by BalanceDate order by StatusDate desc) as Rn, Status 
    from CTE_AfterJoin
)
select EnityName, BalanceDate, Balance, Status
from CTE_afterRank
where Rn = 1
order by BalanceDate desc
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top