简体   繁体   中英

JOIN statement on an alias - SQL Server

I am looking for advice on the best way to join using an alias instead of the original data. eg the data is modified before it is joined.

An example:

CREATE TABLE Table1 (
No1 varchar(10)
);
CREATE TABLE Table2 (
No1 varchar(10)
);

INSERT INTO Table1 (No1)
VALUES ('222');
INSERT INTO Table2 (No1)
VALUES ('111');

If i created a join with a case statement but i wanted to join on the alias of the case statement this doesnt work with usual join syntax eg

SELECT
CASE WHEN T1.[No1] = '222' THEN '111' ELSE T1.[No1] END AS [T1 No], 
T2.[No1] AS [T2 No]
FROM Table1 T1
FULL JOIN Table2 T2
ON T1.[No1] = T2.[No1]

This gives result:

|  T1 No |  T2 No |
|--------+--------|
|    111 | (null) |
| (null) |    111 |

http://www.sqlfiddle.com/#!18/203e8/1

However, the approach i have taken to join on the alias is:

SELECT  
   T1.[T1 No],
   T2.[No1] AS [T2 No]
FROM
(
    SELECT
       CASE WHEN T1.[No1] = '222' THEN '111' ELSE T1.[No1] END AS [T1 No]
    FROM Table1 T1
) T1
JOIN Table2 T2
ON T1.[T1 No] = T2.[No1]

This gives result:

| T1 No | T2 No |
|-------+-------|
|   111 |   111 |

http://www.sqlfiddle.com/#!18/5fd7c/14

Which is exactly what i am looking for. However, the real life query i am dealing with is huge and sub-querying it to join on an alias makes it so messy.

Can anyone give me advice on a better approach to this? or is this the only way to do it?

Ok, firstly, it's probably good for you to be aware of the Logical Processing Order of the SELECT statement . Specifically, that order is:

  1. FROM
  2. ON
  3. JOIN
  4. WHERE
  5. GROUP BY
  6. WITH CUBE or WITH ROLLUP
  7. HAVING
  8. SELECT
  9. DISTINCT
  10. ORDER BY
  11. TOP

Notice that that SELECT is the 8th thing to be processed, which is when the alias of a column would be processed. This means you can't reference a columns alias until step 9 ( DISTINCT ), which really means your left with doing so in the ORDER BY and that's it.

Thus, if you want to reference a column that is derived by an expression, you have few ways of doing so, some I have listed below.

1st way:

Use the Expression in the SELECT and the ON clause. Thus:

SELECT CASE WHEN T1.[No1] = '222' THEN '111'
                                  ELSE T1.[No1]
       END AS [T1 No], 
       T2.[No1] AS [T2 No]
FROM Table1 T1
     JOIN Table2 T2 ON CASE WHEN T1.[No1] = '222' THEN '111'
                                                  ELSE T1.[No1]
                       END = T2.[No1];

This can make things a little confusing, as it can make the query "busy".

2nd way:

Use a Subselect:

SELECT [T1 No]
FROM (SELECT CASE WHEN T1.[No1] = '222' THEN '111'
                                        ELSE T1.[No1]
             END AS [T1 No], 
      FROM Table1 T1) AS Tsq1
     JOIN Table2 T2 ON Tsq1.[T1 No] = T2.[No1];

3rd way

This is basically the same as the last option, however, using a CTE

WITH T1 AS (
    SELECT CASE WHEN T1.[No1] = '222' THEN '111'
                                        ELSE T1.[No1]
           END AS [T1 No], 
    FROM Table1 T1)
SELECT [T1 No]
FROM T1
     JOIN Table2 T2 ON T1.[T1 No] = T2.[No1];

4th Way:

You also could create a VIEW , and then JOIN on that:

CREATE VIEW Table1_vw AS

    SELECT *,
           SELECT CASE WHEN T1.[No1] = '222' THEN '111'
                                       ELSE T1.[No1]
                  END AS [T1 No]
    FROM Table1 T1;
GO

SELECT T1.[T1 No]
FROM Table1_vw T1
     JOIN Table2 T2 ON T1.[T1 No] = T2.[No1];

These are just a few options, but hopefully that puts you on the right path for what works for your own needs.

As HoneyBadger said. Use the CASE Both in the Select and on the ON condition

SQL DEMO

SELECT CASE WHEN T1.[No1] = '222' THEN '111' ELSE T1.[No1] END AS [T1 No],
       T2.[No1] AS [T2 No]
FROM  Table1 T1
JOIN  Table2 T2
   ON CASE WHEN T1.[No1] = '222' THEN '111' ELSE T1.[No1] END = T2.[No1];

The problem is you cant use the alias because the execution order of the SELECT

As you can see here Order Of Execution of the SQL query

The JOIN happen before the SELECT create the alias

As variant you can use an auxiliary table

CREATE TABLE Link(
  Table1_No1 varchar(10),
  Table2_No1 varchar(10),
PRIMARY KEY(Table1_No1),
UNIQUE(Table1_No1,Table2_No1)
)

INSERT Link(Table1_No1,Table2_No1)VALUES
('222','111'),
('444','333'),
...

And then a query

SELECT  
  T1.No1 [T1 No],
  T2.No1 [T2 No]
FROM
  (
    SELECT ISNULL(L.Table2_No1,T1.No1) No1
    FROM Table1 T1
    LEFT JOIN Link L ON L.Table1_No1=T1.No1
  ) T1
JOIN Table2 T2 ON T1.No1=T2.No1

This way is useful because you don't need rewrite your query for new conditions.

And if this variant suits you, you can write it more shorter

SELECT  
  ISNULL(L.Table2_No1,T1.No1) [T1 No],
  T2.No1 [T2 No]
FROM Table1 T1
LEFT JOIN Link L ON L.Table1_No1=T1.No1
JOIN Table2 T2 ON T2.No1=ISNULL(L.Table2_No1,T1.No1)

If you want to avoid writing your expressions multiple times, then the only option is joining after asigning the expression to an alias (and the join must be in an outmost scope, so it's treated like a table). If the problem is the tidiness, I always find using CTEs a lot more readable than subquerying in the FROM .

;WITH ComplexQueryCalculations AS
(
    SELECT
        ID = T.ID,
        SomeColumn = T.SomeColumn,
        ExpressionResult = CASE
            WHEN T.PlanetsAlign = 1 AND X.OtherColumn > 100
                THEN (N.OtherColumn * 100) / NULLIF(N.AnotherColumn, 0)
            ELSE
                N.OtherColumn END
    FROM
        Table1 AS T
        INNER JOIN Table2 AS N ON T.SomeColumn = N.SomeColumn
        LEFT JOIN Table3 AS X ON
            T.SomeColumn = CONVERT(INT, X.SomeColumn) AND
            N.SomeColumn = X.OtherColumn
    WHERE
        T.ID <= 15000 AND
        CHARINDEX('_', T.SomeColumn) > 1 AND
        (
            T.SomeColumn <> 'Property' OR
            (T.SomeColumn = 'Property' AND X.SomeColumn BETWEEN 1 AND 100)
        )
),
FilteredExpressionResult AS
(
    SELECT
        C.ID,
        C.SomeColumn,
        C.ExpressionResult
    FROM
        ComplexQueryCalculations AS C -- Reference previous CTE
    WHERE
        C.ExpressionResult >= 50 -- Filter the alias!
)
SELECT
    F.*
FROM
    FilteredExpressionResult AS F
    INNER JOIN YetAnotherTable AS Y ON F.ID = Y.ID
WHERE
    Y.SomeColumn IS NOT NULL

You can keep your subqueries separated, give them proper spacing and table alias, and give the person who is reading a proper query order, without spending resources by creating temporary or variable tables in the middle.

I'm not sure why you are using full join rather than inner join . But another solution is to use apply :

SELECT . . .
FROM Table1 T1 CROSS APPLY
     (VALUES (CASE WHEN T1.[No1] = '222' THEN '111' ELSE T1.[No1] END)
     ) V([T1 No]) JOIN
     Table2 T2
     ON V.[T1 No] = T2.[No1];

APPLY can be handy for adding in computed columns. You don't need subqueries or CTEs. The changes to the query are minimal.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM