SQL Server Function for Maximum Date in Different Fields - sql

I have multiple related tables in a database and each can be updated separately and have their own LastUpdated Date field. In one of these tables there is more than one LastUpdated field each indicating from which source that record was updated. We commonly query these multiple tables as a single item with joins and thus I would like to know for each record what the most recent LastUpdated record is across all joins. I know this can be achieved with many sub-queries but I was wondering whether there was something along the lines of the Coalesce function into which you can pass many fields and it returns the first non-null value.
So it would read something like:
SELECT a.a_id,
a.name,
b.b_id,
b.detail,
c.c_id,
c.otherfield,
Maxdate(a.lastupdated, a.lastupdatedfromweb, b.lastupdated,
c.lastupdated) AS
LastUpdatedDate
FROM a
INNER JOIN b
ON a.a_id = b.a_id
INNER JOIN c
ON b.b_id = c.b_id
Any ideas? Could this be written as a custom function or does it exist in the box? I am working on SQL Server 2005 and 2008 if that helps.

In 2008+ you can use CROSS APPLY to create a row for each of your dates, then select the Max:
SELECT
a.a_Id,
a.Name,
b.b_Id,
b.Detail,
c.c_Id,
c.OtherField,
ld.LastUpdatedDate
FROM
a INNER JOIN b ON a.a_Id = b.a_Id
INNER JOIN c ON b.b_Id = c.b_Id
CROSS APPLY
( SELECT LastUpdatedDate = MAX(LastUpdatedDate)
FROM (VALUES
(a.LastUpdated),
(a.LastUpdatedFromWeb),
(b.LastUpdated),
(c.LastUpdated)
) d (LastUpdatedDate)
) ld
You could also do this as a correlated subquery, but during optimisation SQL Server will rewrite correlated subqueries as OUTER APPLY so I prefer to just cut a step out and write the APPLY myself, as I have found some unusual behaviour when SQL deconstructs the correlated subquery and rewrites it as an APPLY. More details of this are described in this answer
For 2005, I think you will need to use a slightly different method as it doesn't support table valued constructors:
SELECT
a.a_Id,
a.Name,
b.b_Id,
b.Detail,
c.c_Id,
c.OtherField,
ld.LastUpdatedDate,
( SELECT MAX(CASE Number
WHEN 1 THEN a.LastUpdated
WHEN 2 THEN a.LastUpdatedFromWeb
WHEN 3 THEN b.LastUpdated
WHEN 4 THEN c.LastUpdated
END)
FROM (SELECT TOP 4 Number = ROW_NUMBER() OVER(ORDER BY object_id)
FROM sys.all_objects) n
) AS LastUpdated
FROM
a INNER JOIN b ON a.a_Id = b.a_Id
INNER JOIN c ON b.b_Id = c.b_Id;
Or:
SELECT
a.a_Id,
a.Name,
b.b_Id,
b.Detail,
c.c_Id,
c.OtherField,
ld.LastUpdatedDate,
( SELECT MAX(LastUpdated)
FROM ( SELECT a.LastUpdated UNION ALL
SELECT a.LastUpdatedFromWeb UNION ALL
SELECT b.LastUpdated UNION ALL
SELECT c.LastUpdated
) d
) AS LastUpdated
FROM
a INNER JOIN b ON a.a_Id = b.a_Id
INNER JOIN c ON b.b_Id = c.b_Id;
Unfortunately I no longer have any 2005 instances installed so I can't test this.
EDIT
Just realised that you wanted the source of the field too, and also remembered that 2005 does support the use of APPLY, so for 2008+:
SELECT
a.a_Id,
a.Name,
b.b_Id,
b.Detail,
c.c_Id,
c.OtherField,
ld.LastUpdatedDate,
ld.FieldName
FROM
a INNER JOIN b ON a.a_Id = b.a_Id
INNER JOIN c ON b.b_Id = c.b_Id
CROSS APPLY
( SELECT TOP 1 LastUpdatedDate, FieldName
FROM (VALUES
(a.LastUpdated, 'a.LastUpdated'),
(a.LastUpdatedFromWeb, 'a.LastUpdatedFromWeb'),
(b.LastUpdated, 'b.LastUpdated'),
(c.LastUpdated, 'c.LastUpdated')
) d (LastUpdatedDate, FieldName)
ORDER BY LastUpdated DESC
) ld
For 2005:
SELECT
a.a_Id,
a.Name,
b.b_Id,
b.Detail,
c.c_Id,
c.OtherField,
ld.LastUpdatedDate,
ld.FieldName
FROM
a INNER JOIN b ON a.a_Id = b.a_Id
INNER JOIN c ON b.b_Id = c.b_Id
CROSS APPLY
( SELECT TOP 1 LastUpdatedDate = LastUpdated, FieldName
FROM (
SELECT a.LastUpdated, FieldName = 'a.LastUpdated' UNION ALL
SELECT a.LastUpdatedFromWeb, 'a.LastUpdatedFromWeb' UNION ALL
SELECT b.LastUpdated, 'b.LastUpdated' UNION ALL
SELECT c.LastUpdated, 'c.LastUpdated'
) d
ORDER BY LastUpdated DESC
) ld

SELECT
a.a_Id,
a.Name,
b.b_Id,
b.Detail,
c.c_Id,
c.OtherField,
MAX(SELECT a.LastUpdated, a.LastUpdatedFromWeb, b.LastUpdated, c.LastUpdated FROM a INNER JOIN b ON a.a_Id = b.a_Id INNER JOIN c ON b.b_Id = c.b_Id) AS LastUpdatedDate
FROM
a INNER JOIN b ON a.a_Id = b.a_Id
INNER JOIN c ON b.b_Id = c.b_Id

Related

How to subtract values from different tables?

I need to get the result of subtract values from these 3 different tables in SQL Server.
This is my SQL:
SELECT COUNT(A.Id)
FROM Table_A AS A WITH (NOLOCK)
WHERE A.City = 'NewYork'
SELECT COUNT(B.Id)
FROM Table_B AS B WITH (NOLOCK)
WHERE B.City = 'England'
SELECT COUNT(C.Id)
FROM Table_C AS C WITH (NOLOCK)
WHERE C.City = 'Berlin'
Let's say the result of the first query is 9, and the second one is 1, and the third one is 3.
I need to get (9-1-3 = 5). How can I do this?
You can do this with a CTE or with subqueries.
Using a CTE:
WITH tbla AS (
SELECT COUNT(A.Id) A
FROM Table_A AS A
WHERE A.City = 'NewYork'
),
tblb AS (
SELECT COUNT(B.Id) B
FROM Table_B AS B
WHERE B.City = 'England'
),
tblc AS (
SELECT COUNT(C.Id) C
FROM Table_C AS C
WHERE C.City = 'Berlin'
)
SELECT a.A - b.B - c.C
FROM tbla a
CROSS JOIN tblb b
CROSS JOIN tblc c;
Same thing only with subqueries
SELECT a.A - b.B - c.C
FROM (
SELECT COUNT(A.Id) A
FROM Table_A AS A
WHERE A.City = 'NewYork'
) a
CROSS JOIN (
SELECT COUNT(B.Id) B
FROM Table_B AS B
WHERE B.City = 'England'
) b
CROSS JOIN (
SELECT COUNT(C.Id) C
FROM Table_C AS C
WHERE C.City = 'Berlin'
) c;
Note: I removed the WITH (NOLOCK) hints because in all likelihood they are not needed in this case.
As all queries return a scalar value 8one row one column you can simply add or subtract them
SELECT
(SELECT
COUNT(A.Id)
FROM
Table_A AS A
WHERE
A.City = 'NewYork') - (SELECT
COUNT(B.Id)
FROM
Table_B AS B
WHERE
B.City = 'England') - (SELECT
COUNT(C.Id)
FROM
Table_C AS C
WHERE
C.City = 'Berlin') as Whatever

Triple Join In SQL

How can I get the red section of the following Venn diagram in SQL
Thanks for your help!
I would think first of except:
select c.*
from c
except
select a.*
from a
except
select b.*
from b;
My next thought would be not exists:
select c.*
from t
where not exists (select 1 from a where a.id = c.id) and
not exists (select 1 from b where b.id = c.id);
Using Joins you can get any portion of Ven diagram, Somethink like below for your scenario.
select c.*
from TableC c
left join TableA a on a.id = c.id
left join TableB b on b.id = c.id
Where a.id is null and b.id is null -- Records which does not match in both tables
Well there is gonna be a bit of handwaving considering you are not giving actual table structures, but something like this:
Select c.name
From c
Where not exists (select 1 from b where b.id = c.id)
And not exists (select 1 from a where a.id = c.id);
You can use NOT IN (assuming id is the common attribute):
SELECT c.id FROM c
WHERE c.id NOT IN (SELECT a.id FROM a)
AND c.id NOT IN (SELECT b.id FROM b);

Multiply two columns and sum them on inner join

I have two tables and I need to get the sum of a.TOTAL * b.QUANTITY.
a (A_ID, TOTAL)
b (B_ID, QUANTITY)
So far I wrote:
SELECT a.A_ID, a.TOTAL * b.QUANTITY as calculation
FROM a INNER JOIN b ON
a.A_ID = b.B_ID
I tried ...SUM(a.TOTAL * b.QUANTITY) as calculation but it doesn't work. I would be grateful for any help!
Try this :
SELECT A_ID, SUM(calculation) as mySum
FROM (
SELECT a.A_ID, a.TOTAL * b.QUANTITY as calculation
FROM a INNER JOIN b ON
a.A_ID = b.B_ID
) q
GROUP BY A_ID
Did you intend to do a grouping on A's id. If so, then this might give your desired result:
SELECT a.A_ID,
SUM(a.TOTAL * b.QUANTITY) AS calculation
FROM a
INNER JOIN b
ON a.A_ID = b.B_ID
GROUP BY a.A_ID

SQL summations with multiple outer joins

I have tables a, b, c, and d whereby:
There are 0 or more b rows for each a row
There are 0 or more c rows for each a row
There are 0 or more d rows for each a row
If I try a query like the following:
SELECT a.id, SUM(b.debit), SUM(c.credit), SUM(d.other)
FROM a
LEFT JOIN b on a.id = b.a_id
LEFT JOIN c on a.id = c.a_id
LEFT JOIN d on a.id = d.a_id
GROUP BY a.id
I notice that I have created a cartesian product and therefore my sums are incorrect (much too large).
I see that there are other SO questions and answers, however I'm still not grasping how I can accomplish what I want to do in a single query. Is it possible in SQL to write a query which aggregates all of the following data:
SELECT a.id, SUM(b.debit)
FROM a
LEFT JOIN b on a.id = b.a_id
GROUP BY a.id
SELECT a.id, SUM(c.credit)
FROM a
LEFT JOIN c on a.id = c.a_id
GROUP BY a.id
SELECT a.id, SUM(d.other)
FROM a
LEFT JOIN d on a.id = d.a_id
GROUP BY a.id
in a single query?
Your analysis is correct. Unrelated JOIN create cartesian products.
You have to do the sums separately and then do a final addition. This is doable in one query and you have several options for that:
Sub-requests in your SELECT: SELECT a.id, (SELECT SUM(b.debit) FROM b WHERE b.a_id = a.id) + ...
CROSS APPLY with a similar query as the first bullet then SELECT a.id, b_sum + c_sum + d_sum
UNION ALL as you suggested with an outer SUM and GROUP BY on top of that.
LEFT JOIN to similar subqueries as above.
And probably more... The performance of the various solutions might be slightly different depending on how many rows in A you want to select.
SELECT a.ID, debit, credit, other
FROM a
LEFT JOIN (SELECT a_id, SUM(b.debit) as debit
FROM b
GROUP BY a_id) b ON a.ID = b.a_id
LEFT JOIN (SELECT a_id, SUM(b.credit) as credit
FROM c
GROUP BY a_id) c ON a.ID = c.a_id
LEFT JOIN (SELECT a_id, SUM(b.other) as other
FROM d
GROUP BY a_id) d ON a.ID = d.a_id
Can also be done with correlated subqueries:
SELECT a.id
, (SELECT SUM(debit) FROM b WHERE a.id = b.a_id)
, (SELECT SUM(credit) FROM c WHERE a.id = c.a_id)
, (SELECT SUM(other) FROM d WHERE a.id = d.a_id)
FROM a

Aliasing derived table which is a union of two selects

I can't get the syntax right for aliasing the derived table correctly:
SELECT * FROM
(SELECT a.*, b.*
FROM a INNER JOIN b ON a.B_id = b.B_id
WHERE a.flag IS NULL AND b.date < NOW()
UNION
SELECT a.*, b.*
FROM a INNER JOIN b ON a.B_id = b.B_id
INNER JOIN c ON a.C_id = c.C_id
WHERE a.flag IS NOT NULL AND c.date < NOW())
AS t1
ORDER BY RAND() LIMIT 1
I'm getting a Duplicate column name of B_id. Any suggestions?
The problem isn't the union, it's the select a.*, b.* in each of the inner select statements - since a and b both have B_id columns, that means you have two B_id cols in the result.
You can fix that by changing the selects to something like:
select a.*, b.col_1, b.col_2 -- repeat for columns of b you need
In general, I'd avoid using select table1.* in queries you're using from code (rather than just interactive queries). If someone adds a column to the table, various queries can suddenly stop working.
In your derived table, you are retrieving the column id that exists in table a and table b, so you need to choose one of them or give an alias to them:
SELECT * FROM
(SELECT a.*, b.[all columns except id]
FROM a INNER JOIN b ON a.B_id = b.B_id
WHERE a.flag IS NULL AND b.date < NOW()
UNION
SELECT a.*, b.[all columns except id]
FROM a INNER JOIN b ON a.B_id = b.B_id
INNER JOIN c ON a.C_id = c.C_id
WHERE a.flag IS NOT NULL AND c.date < NOW())
AS t1
ORDER BY RAND() LIMIT 1
First, you could use UNION ALL instead of UNION. The two subqueries will have no common rows because of the excluding condtion on a.flag.
Another way you could write it, is:
SELECT a.*, b.*
FROM a
INNER JOIN b
ON a.B_id = b.B_id
WHERE ( a.flag IS NULL
AND b.date < NOW()
)
OR
( a.flag IS NOT NULL
AND EXISTS
( SELECT *
FROM c
WHERE a.C_id = c.C_id
AND c.date < NOW()
)
)
ORDER BY RAND()
LIMIT 1