SQL Server CTE left outer join - sql-server

I have 2 tables in SQL Server 2008, customertest with columns customer id (cid) and it's boss id (upid), and conftest with cid, confname, confvalue
customertest schema and data:
conftest schema and data:
I want to know how to design a CTE that if cid in conftest doesn't have that confname's confvalue, it will keep searching upid and till find a upper line which have confname and confvalue.
For example , I want to get value of 100 if I search for cid=4 (this is normal case). And I want to get value of 200 if I search for cid=7 or 8.
And if cid7 and cid8 have child node , it will all return 200 (of cid5) if I search using this CTE.
I don't have a clue how to do this , I think maybe can use CTE and some left outer join, please give me some example ?? Thanks a lot.

If it's unknown how many levels there are in the hierarchy?
Then such challenge is often done via a Recursive CTE.
Example Snippet:
--
-- Using table variables for testing reasons
--
declare #customertest table (cid int primary key, upid int);
declare #conftest table (cid int, confname varchar(6) default 'budget', confvalue int);
--
-- Sample data
--
insert into #customertest (cid, upid) values
(1,0), (2,1), (3,1), (4,2), (5,2), (6,3),
(7,5), (8,5), (9,8), (10,9);
insert into #conftest (cid, confvalue) values
(1,1000), (2,700), (3,300), (4,100), (5,200), (6,300);
-- The customer that has his own budget, or not.
declare #customerID int = 10;
;with RCTE AS
(
--
-- the recursive CTE starts from here. The seed records, as one could call it.
--
select cup.cid as orig_cid, 0 as lvl, cup.cid, cup.upid, budget.confvalue
from #customertest as cup
left join #conftest budget on (budget.cid = cup.cid and budget.confname = 'budget')
where cup.cid = #customerID -- This is where we limit on the customer
union all
--
-- This is where the Recursive CTE loops till it finds nothing new
--
select RCTE.orig_cid, RCTE.lvl+1, cup.cid, cup.upid, budget.confvalue
from RCTE
join #customertest as cup on (cup.cid = RCTE.upid)
outer apply (select b.confvalue from #conftest b where b.cid = cup.cid and b.confname = 'budget') as budget
where RCTE.confvalue is null -- Loop till a budget is found
)
select
orig_cid as cid,
confvalue
from RCTE
where confvalue is not null;
Result :
cid confvalue
--- ---------
10 200
Btw, the Recursive CTE uses the OUTER APPLY because MS SQL Server doesn't allow a LEFT OUTER JOIN to be used there.
And if it's certain that there's maximum 1 level depth for the upid with a budget?
Then just simple left joins and a coalesce would do.
For example:
select cup.cid, coalesce(cBudget.confvalue, upBudget.confvalue) as confvalue
from #customertest as cup
left join #conftest cBudget on (cBudget.cid = cup.cid and cBudget.confname = 'budget')
left join #conftest upBudget on (upBudget.cid = cup.upid and upBudget.confname = 'budget')
where cup.cid = 8;

I don't think you are looking for a CTE to do that, from what I understand:
CREATE TABLE CustomerTest(
CID INT,
UPID INT
);
CREATE TABLE ConfTest(
CID INT,
ConfName VARCHAR(45),
ConfValue INT
);
INSERT INTO CustomerTest VALUES
(1, 0),
(2, 1),
(3, 1),
(4, 2),
(5, 2),
(6, 3),
(7, 5),
(8, 5);
INSERT INTO ConfTest VALUES
(1, 'Budget', 1000),
(2, 'Budget', 700),
(3, 'Budget', 300),
(4, 'Budget', 100),
(5, 'Budget', 200),
(6, 'Budget', 300);
SELECT MAX(CNT.CID) AS CID,
CNT.ConfName,
MIN(CNT.ConfValue) AS ConfValue
FROM ConfTest CNT INNER JOIN CustomerTest CMT ON CMT.CID = CNT.CID
OR CMT.UPID = CNT.CID
WHERE CMT.CID = 7 -- You can test for values (8, 4) or any value you want :)
GROUP BY
CNT.ConfName;

Related

More efficient way to write this query - several M:M relationships

I am using SQL Server 2014 SP3.
I have the following hypothetical database structure.
There are accounts, which can belong to multiple customers, represented by the following tables:
Account <- Account_Customer -> Customer
The customers, in turn, can own multiple cars:
Customer <- Customer_Car -> Car
In addition, the customers can own many pets:
Customer <- Customer_Pet -> Pet
Now I am trying to come up with the most efficient query to answer the following question:
Get a list of accounts where none of the account owners have a "Cat" and none of the account owners drive a "Dodge".
The script below sets up the tables and some sample data. Please note that in real life, these tables will have 10's of millions of records, so I am trying to come up with the most efficient way to answer this question. So far I was only able to do it by accessing the same tables multiple times.
Setup script:
USE tempdb;
-- Create tables
IF OBJECT_ID('Account') IS NOT NULL DROP TABLE Account;
CREATE TABLE Account (AccountId INT, AccountName VARCHAR(24))
IF OBJECT_ID('Customer') IS NOT NULL DROP TABLE Customer;
CREATE TABLE Customer (CustomerId INT, CustomerName VARCHAR(24))
IF OBJECT_ID('Pet') IS NOT NULL DROP TABLE Pet;
CREATE TABLE Pet (PetId INT, PetName VARCHAR(24))
IF OBJECT_ID('Car') IS NOT NULL DROP TABLE Car;
CREATE TABLE Car (CarId INT, CarName VARCHAR(24))
IF OBJECT_ID('Account_Customer') IS NOT NULL DROP TABLE Account_Customer;
CREATE TABLE Account_Customer (AccountId INT, CustomerId INT)
IF OBJECT_ID('Customer_Pet') IS NOT NULL DROP TABLE Customer_Pet;
CREATE TABLE Customer_Pet (CustomerId INT, PetId INT)
IF OBJECT_ID('Customer_Car') IS NOT NULL DROP TABLE Customer_Car;
CREATE TABLE Customer_Car (CustomerId INT, CarId INT)
-- Populate data
INSERT [dbo].[Account]([AccountId], [AccountName])
VALUES (1, 'Account1'), (2, 'Account2')
INSERT [dbo].[Customer]([CustomerId], [CustomerName])
VALUES (1, 'Customer1'), (2, 'Customer2'), (3, 'Customer3'), (4, 'Customer4')
INSERT [dbo].[Pet]([PetId], [PetName])
VALUES (1, 'Cat1'), (2, 'Cat2'), (3, 'Dog3'), (4, 'Dog4')
INSERT [dbo].[Car]([CarId], [CarName])
VALUES (1, 'Ford1'), (2, 'Ford2'), (3, 'Kia3'), (4, 'Dodge4')
INSERT [dbo].[Account_Customer] ([AccountId], [CustomerId])
VALUES (1,1), (1,2), (2, 2), (2,3), (2,4)
INSERT [dbo].[Customer_Pet] ([CustomerId], [PetId])
VALUES (2,3), (3,1), (3, 2), (4,3), (4,4)
INSERT [dbo].[Customer_Car] ([CustomerId], [CarId])
VALUES (1,2), (2,2), (3,1), (3, 2), (3, 4)
--SELECT * FROM [dbo].[Account] AS [A]
--SELECT * FROM [dbo].[Customer] AS [C]
--SELECT * FROM [dbo].[Pet] AS [P]
--SELECT * FROM [dbo].[Car] AS [C]
--SELECT * FROM [dbo].[Account_Customer] AS [AC]
--SELECT * FROM [dbo].[Customer_Pet] AS [CP]
--SELECT * FROM [dbo].[Customer_Car] AS [CC]
-- Bring all the data together to see what we have (denormalized)
SELECT [A].[AccountId], [A].[AccountName],
[C].[CustomerId], [C].[CustomerName],
[CP].[PetId], [P].[PetName],
[C2].[CarId], [C2].[CarName]
FROM [dbo].[Customer] AS [C]
JOIN [dbo].[Account_Customer] AS [AC] ON [AC].[CustomerId] = [C].[CustomerId]
JOIN [dbo].[Account] AS [A] ON [A].[AccountId] = [AC].[AccountId]
LEFT JOIN [dbo].[Customer_Pet] AS [CP] ON [CP].[CustomerId] = [C].[CustomerId]
LEFT JOIN [dbo].[Pet] AS [P] ON [P].[PetId] = [CP].[PetId]
LEFT JOIN [dbo].[Customer_Car] AS [CC] ON [CC].[CustomerId] = [C].[CustomerId]
LEFT JOIN [dbo].[Car] AS [C2] ON [C2].[CarId] = [CC].[CarId]
ORDER BY [A].[AccountId], [AC].[CustomerId]
And here is the query, which answers my question, but I suspect it's inefficient on a large number of records. Is there a better way?
-- This should only return Account1
SELECT DISTINCT
[A].[AccountId],
[A].[AccountName]
FROM [dbo].[Customer] AS [C]
JOIN [dbo].[Account_Customer] AS [AC] ON [AC].[CustomerId] = [C].[CustomerId]
JOIN [dbo].[Account] AS [A] ON [A].[AccountId] = [AC].[AccountId]
EXCEPT
SELECT -- get Accounts where owner has a "Cat" or drives a "Dodge"
[A].[AccountId],
[A].[AccountName]
FROM [dbo].[Customer] AS [C]
JOIN [dbo].[Account_Customer] AS [AC] ON [AC].[CustomerId] = [C].[CustomerId]
JOIN [dbo].[Account] AS [A] ON [A].[AccountId] = [AC].[AccountId]
WHERE
(
EXISTS (SELECT TOP (1) 1
FROM [dbo].[Customer] AS [C2]
JOIN [dbo].[Customer_Pet] AS [CP2] ON [CP2].[CustomerId] = [C2].[CustomerId]
JOIN [dbo].[Pet] AS [P2] ON [P2].[PetId] = [CP2].[PetId]
WHERE [C2].[CustomerId] = [C].[CustomerId] -- correlation
AND [P2].[PetName] LIKE 'Cat%'
)
OR
EXISTS (SELECT TOP (1) 1
FROM [dbo].[Customer] AS [C2]
JOIN [dbo].[Customer_Car] AS [CP2] ON [CP2].[CustomerId] = [C2].[CustomerId]
JOIN [dbo].[Car] AS [P2] ON [P2].[CarId] = [CP2].[CarId]
WHERE [C2].[CustomerId] = [C].[CustomerId] -- correlation
AND [P2].[CarName] LIKE 'Dodge%'
)
)
Sorry if this is obvious, but please observe that the query below will not work (because it answers slightly different question - return accounts where AT LEAST ONE OWNER does not have a "Cat" and does not drive a "Dodge":
-- Does not work:
SELECT DISTINCT
[A].[AccountId],
[A].[AccountName]
FROM [dbo].[Customer] AS [C]
JOIN [dbo].[Account_Customer] AS [AC] ON [AC].[CustomerId] = [C].[CustomerId]
JOIN [dbo].[Account] AS [A] ON [A].[AccountId] = [AC].[AccountId]
WHERE
(
NOT EXISTS (SELECT TOP (1) 1
FROM [dbo].[Customer] AS [C2]
JOIN [dbo].[Customer_Pet] AS [CP2] ON [CP2].[CustomerId] = [C2].[CustomerId]
JOIN [dbo].[Pet] AS [P2] ON [P2].[PetId] = [CP2].[PetId]
WHERE [C2].[CustomerId] = [C].[CustomerId] -- correlation
AND [P2].[PetName] LIKE 'Cat%'
)
AND
NOT EXISTS (SELECT TOP (1) 1
FROM [dbo].[Customer] AS [C2]
JOIN [dbo].[Customer_Car] AS [CP2] ON [CP2].[CustomerId] = [C2].[CustomerId]
JOIN [dbo].[Car] AS [P2] ON [P2].[CarId] = [CP2].[CarId]
WHERE [C2].[CustomerId] = [C].[CustomerId] -- correlation
AND [P2].[CarName] LIKE 'Dodge%'
)
)
I must say, in a real database I would be very suspicious of all these Many:Many relationships. Can an Account be owned by multiple Customers, each of whom can own multiple Accounts? Equally can a Cat or a Pet have multiple owners?
Be that as it may: you can express your query like this:
You want all Accounts...
for which there do not exist Account_Customers...
Where those Customers are in the set of Customers who own a Cat...
... or a Dodge
SELECT *
FROM Account a
WHERE NOT EXISTS (
SELECT ac.CustomerId
FROM Account_Customer ac
WHERE ac.AccountId = a.AccountId
INTERSECT
(
SELECT cp.CustomerId
FROM Customer_Pet cp
JOIN Pet p ON p.PetId = cp.PetId
WHERE p.PetName LIKE 'Cat%'
UNION ALL
SELECT cc.CustomerId
FROM Customer_Car cc
JOIN Car c ON c.CarId = cc.CarId
WHERE c.CarName LIKE 'Dodge%'
)
)
db<>fiddle
It's too late for a more in-depth answer, so here's a quick and dirty one with a temp table.
Mind you it's not as bad as it looks, many times I've had simple queries on temp tables massively outperform large, interesting (from a mathematic point of view) queries.
Also, a question about performance is never simple to answer. Of special interest is the fact that you mention millions of rows and need for performance while your query uses a like operator on some text column. At least the % is in the end, so it's still SARGable. Will this column have an index? That will probably make a difference.
Here (done blind, hopefully no errors):
create table #forbidden
(
CustomerId int primary key
)
insert #forbidden(CustomerId)
select CustomerId from Customer C
where
exists(select 1 from Customer_Pet CP where CP.CustomerId=C.CustomerId and CP.[PetName] LIKE 'Cat%')
or exists(select 1 from Customer_Car CC where CC.CustomerId=C.CustomerId and CC.[CarName] LIKE 'Dodge%')
select * from Account A
where not exists
(
select 1
from Account_Customer AC
where
AC.CustomerId=A.CustomerId
and AC.CustomerId in (select CustomerId from #forbidden)
)

SQL Server Hierarchical Sum of column

I have my database design as per the diagram.
Category table is self referencing parent child relationship
Budget will have all the categories and amount define for each category
Expense table will have entries for categories for which the amount has been spend (consider Total column from this table).
I want to write select statement that will retrieve dataset with columns given below :
ID
CategoryID
CategoryName
TotalAmount (Sum of Amount Column of all children hierarchy From BudgetTable )
SumOfExpense (Sum of Total Column of Expense all children hierarchy from expense table)
I tried to use a CTE but was unable to produce anything useful. Thanks for your help in advance. :)
Update
I just to combine and simplify data I have created one view with the query below.
SELECT
dbo.Budget.Id, dbo.Budget.ProjectId, dbo.Budget.CategoryId,
dbo.Budget.Amount,
dbo.Category.ParentID, dbo.Category.Name,
ISNULL(dbo.Expense.Total, 0) AS CostToDate
FROM
dbo.Budget
INNER JOIN
dbo.Category ON dbo.Budget.CategoryId = dbo.Category.Id
LEFT OUTER JOIN
dbo.Expense ON dbo.Category.Id = dbo.Expense.CategoryId
Basically that should produce results like this.
This is an interesting problem. And I'm going to solve it with a hierarchyid. First, the setup:
USE tempdb;
IF OBJECT_ID('dbo.Hierarchy') IS NOT NULL
DROP TABLE dbo.[Hierarchy];
CREATE TABLE dbo.Hierarchy
(
ID INT NOT NULL PRIMARY KEY,
ParentID INT NULL,
CONSTRAINT [FK_parent] FOREIGN KEY ([ParentID]) REFERENCES dbo.Hierarchy([ID]),
hid HIERARCHYID,
Amount INT NOT null
);
INSERT INTO [dbo].[Hierarchy]
( [ID], [ParentID], [Amount] )
VALUES
(1, NULL, 100 ),
(2, 1, 50),
(3, 1, 50),
(4, 2, 58),
(5, 2, 7),
(6, 3, 10),
(7, 3, 20)
SELECT * FROM dbo.[Hierarchy] AS [h];
Next, to update the hid column with a proper value for the hiearchyid. I'll use a bog standard recursive cte for that
WITH cte AS (
SELECT [h].[ID] ,
[h].[ParentID] ,
CAST('/' + CAST(h.[ID] AS VARCHAR(10)) + '/' AS VARCHAR(MAX)) AS [h],
[h].[hid]
FROM [dbo].[Hierarchy] AS [h]
WHERE [h].[ParentID] IS NULL
UNION ALL
SELECT [h].[ID] ,
[h].[ParentID] ,
CAST([c].[h] + CAST(h.[ID] AS VARCHAR(10)) + '/' AS VARCHAR(MAX)) AS [h],
[h].[hid]
FROM [dbo].[Hierarchy] AS [h]
JOIN [cte] AS [c]
ON [h].[ParentID] = [c].[ID]
)
UPDATE [h]
SET hid = [cte].[h]
FROM cte
JOIN dbo.[Hierarchy] AS [h]
ON [h].[ID] = [cte].[ID];
Now that the heavy lifting is done, the results you want are almost trivially obtained:
SELECT p.id, SUM([c].[Amount])
FROM dbo.[Hierarchy] AS [p]
JOIN [dbo].[Hierarchy] AS [c]
ON c.[hid].IsDescendantOf(p.[hid]) = 1
GROUP BY [p].[ID];
After much research and using test data, I was able to get the running totals starting from bottom of hierarchy.
The solution is made up of two steps.
Create a scalar-valued function that will decide whether a categoryId is a direct or indirect child of another categoryId. This is given in first code-snippet. Note that a recursive query is used for this since that is the best approach when dealing with hierarchy in SQL Server.
Write the running total query that will give totals according to your requirements for all categories. You can filter by category if you wanted to on this query. The second code snippet provides this query.
Scalar-valued function that tells if a child category is a direct or indirect child of another category
CREATE FUNCTION dbo.IsADirectOrIndirectChild(
#childId int, #parentId int)
RETURNS int
AS
BEGIN
DECLARE #isAChild int;
WITH h(ParentId, ChildId)
-- CTE name and columns
AS (
SELECT TOP 1 #parentId, #parentId
FROM dbo.Category AS b
UNION ALL
SELECT b.ParentId, b.Id AS ChildId
FROM h AS cte
INNER JOIN
Category AS b
ON b.ParentId = cte.ChildId AND
cte.ChildId IS NOT NULL)
SELECT #isAChild = ISNULL(ChildId, 0)
FROM h
WHERE ChildId = #childId AND
ParentId <> ChildId
OPTION(MAXRECURSION 32000);
IF #isAChild > 0
BEGIN
SET #isAChild = 1;
END;
ELSE
BEGIN
SET #isAChild = 0;
END;
RETURN #isAChild;
END;
GO
Query for running total starting from bottom of hierarchy
SELECT c.Id AS CategoryId, c.Name AS CategoryName,
(
SELECT SUM(ISNULL(b.amount, 0))
FROM dbo.Budget AS b
WHERE dbo.IsADirectOrIndirectChild( b.CategoryId, c.Id ) = 1 OR
b.CategoryId = c.Id
) AS totalAmount,
(
SELECT SUM(ISNULL(e.total, 0))
FROM dbo.Expense AS e
WHERE dbo.IsADirectOrIndirectChild( e.CategoryId, c.Id ) = 1 OR
e.CategoryId = c.Id
) AS totalCost
FROM dbo.Category AS c;

CTE to pull entire tree from arbitrary entry

I'm trying to build a CTE which will pull back all records which are related to a given, arbitrary record in the database.
Create table Requests (
Id bigint,
OriginalId bigint NULL,
FollowupId bigint NULL
)
insert into Requests VALUES (1, null, 3)
insert into Requests VALUES (2, 1, 8)
insert into Requests VALUES (3, 1, 4)
insert into Requests VALUES (4, 3, null)
insert into Requests VALUES (5, null, null)
insert into Requests VALUES (6, null, 7)
insert into Requests VALUES (7, 6, null)
insert into Requests VALUES (8, 2, null)
OriginalId is always the Id of a previous record (or null). FollowupId points to the most recent followup record (which, in turn, points back via OriginalId) and can probably be ignored, but it's there if it's helpful.
I can easily pull back either all ancestors or all descendants of a given record using the following CTE
;With TransactionList (Id, originalId, followupId, Steps)
AS
(
Select Id, originalId, followupId, 0 as Steps from requests where Id = #startId
union all
select reqs.Id, reqs.originalId, reqs.followupId, Steps + 1 from requests reqs
inner join TransactionList tl on tl.Id = reqs.originalId --or tl.originalId = reqs.Id
)
SELECT Id from TransactionList
However, if I use both where clauses, I run into recursion, hit the recursion limit, and it bombs out. Even combining both sets, I don't get the entire tree - just one branch from it.
I don't care about anything other than the list of Ids. They don't need to be sorted, or to display their relationship or anything. Doesn't hurt, but not necessary. But I need every Id in a given tree to pull back the same list when it's passed as #startId.
As an example of what I'd like to see, this is what the output should be when #startId is set to any value 1-4 or 8:
1
2
3
4
8
And for either 6 or 7, I get back both 6 and 7.
You can just create 2 CTE's.
The first CTE will get the Root of the hierarchy, and the second will use the Root ID to get the descendants of the Root.
;WITH cteRoot AS (
SELECT *, 0 [Level]
FROM Requests
WHERE Id = #startId
UNION ALL
SELECT r.*, [Level] + 1
FROM Requests r
JOIN cteRoot cte ON r.Id = cte.OriginalID
),
cteDesc AS (
SELECT *
FROM cteRoot
WHERE OriginalId IS NULL
UNION ALL
SELECT r.*, [Level] + 1
FROM Requests r
JOIN cteDesc cte ON r.OriginalId = cte.Id
)
SELECT * FROM cteDesc
SQL Fiddle

SQL- Change column value based on other column values in same group & different row

I have a table in SQL Server:
Unique ITEM_ID are part of a group (GROUP_NUMBER). IS_ACTIVE and IS_LAST are either 1 - true , or 0 - false.
What I want to do:
I want to go through this table and for every active ITEM_ID (IS_ACTIVE = 1) that is the ONLY active ITEM_ID in it's group (GROUP_NUMBER) I want to make that row's IS_LAST is set to 1.
So for example, in the table above, the row for ITEM_ID = 6, I want IS_LAST to be 1
I am not sure how to do this as I am not that versed in SQL. I am trying to use a partition by command to maybe split each group up but doing the check to see if an ITEM_ID is the only active in its group seems challenging.
Any help or guidance here is appreciated.
It should be noted that I do not want to do an update or change the actual table in any way, just design a query that can do the changing and spit out an altered version of that table.
Here is solution:
DECLARE #t TABLE(ITEM_ID INT, GROUP_NUMBER INT, IS_ACTIVE BIT, IS_LAST bit)
INSERT INTO #t VALUES
(1, 1, 1, 0),
(2, 1, 1, 0),
(3, 2, 0, 0),
(4, 2, 0, 0),
(5, 2, 0, 0),
(6, 3, 1, 0),
(7, 3, 0, 0)
SELECT t1.ITEM_ID,
t1.GROUP_NUMBER,
t1.IS_ACTIVE,
CASE WHEN t1.IS_ACTIVE = 1 AND
NOT EXISTS(SELECT * FROM #t t2
WHERE t2.IS_ACTIVE = 1 AND t1.GROUP_NUMBER = t2.GROUP_NUMBER AND
t1.ITEM_ID <> t2.ITEM_ID)
THEN 1 ELSE 0 END AS IS_LAST
FROM #t t1
The following query returns the ITEM_ID's where only one is active in the group:
select ITEM_ID from MyTable M
where IS_ACTIVE = 1 and
not exists (select null
from MyTable N where n.IS_ACTIVE = 1 and
M.GROUP_NUMBER = N.GROUP_NUMBER and M.ITEM_ID <> N.ITEM_ID)
You can then left join this query with MyTable. Something like:
select *
from MyTable A left join (<query above>) B on A.ITEM_ID = B.ITEM_ID
If B.ITEM_ID is not Null then IS_LAST = 1.
I think you're on the right tracks with Partition By. The best way I can think to do this is with some code like:
SELECT Item_ID, Group_Number, Is_Active,
COUNT(*) OVER (PARTITION BY Group_Number, Is_Active ORDER BY Group_Number) [Members_In_Set],
CASE WHEN Is_Active = 1 AND COUNT(*) OVER (PARTITION BY Group_Number, Is_Active ORDER BY Group_Number) = 1 THEN 1 ELSE 0 END [Is_Last]
FROM My_Table
The Members_In_Set column is to demonstrate what the count returns when partitioning and then the CASE shows how to use this value along with Is_Active to get the result you're after

Either Group by or Subquery help not sure will fix the issue

Below is the code I am trying to use however it wants me to add S.Gamingdate to the Group By. When I do that I get a row of results for each player for each day they played. I need just a row for each player summing up all the days. I tried adding a sum around the case statements and moving the sum from the cast to outside the case statements to see if that would help but it did not. I am thinking I will have to subquery but as I am really new to SQL I have not had to subquery before and was hoping for some help.
Using SQL Server 2000
Select P.Player_ID
,P.FirstName
,P.LastName
,P.HostUser_ID
,A.HomePhone1
,P.EMail
,A.City1
,M.Miles
,Case when S.GamingDate between '10/1/2014' and '12/31/2014' then Cast(Sum(S.TWin)/Nullif(Count(Distinct S.GamingDate),0) as Decimal (18,2)) Else 0 End) as "3 Month ADT"
,Case when S.GamingDate between '7/1/2014' and '12/31/2014' then Cast(Sum(S.TWin)/Nullif(Count(Distinct S.GamingDate),0) as Decimal (18,2)) Else 0 End) as "6 Month ADT"
From dbo.CDS_ACCOUNT as A
Join dbo.CDS_Player as P
on A.Account_ID = P.Player_ID
Join dbo.CDS_STATDAY as S
on A.Account_ID = Meta_ID
Join dbo.Player_Miles as M
on A.Account_ID = M.Player_ID
Group by P.Player_ID
,P.FirstName
,P.LastName
,P.HostUser_ID
,A.HomePhone1
,P.EMail
,A.City1
,M.Miles
Rewrite your expressions for [3 Month ADT] and [6 Month ADT] to have S.GamingDate inside the SUM. Without a schema I can't test this but something like this should do the trick;
Cast(Sum(Case when S.GamingDate between '10/1/2014' and '12/31/2014' then S.TWin else null end)/Nullif(Count(Distinct S.GamingDate),0) as Decimal (18,2))) as "3 Month ADT"
Also, you might need to remove the NULLIF - if it ever returns 0 you would get a division-by-zero error.
Edit to provide more info and a complete working script;
I've created a complete script that anyone can run in an empty database. Firstly, after un-picking the expressions for [3 Month ADT] and [6 Month ADT] I think what you are trying to do is get the average TWin over dates played? In which case just use the AVG function - it will ignore NULLs (dates not played). Note the 1.0 * in front of S.TWin - if your TWin is an integer this will convert it to a decimal so that the result of the AVG is decimal too. Also note the LEFT JOIN to dbo.CDS_STATDAY - this prevents players without an entry in dbo.CDS_STATDAY from disappearing from the results.
If I've not quite grasped what you're trying to do let me know.
/*
-- Schema script to create some dummy tables and data
create table dbo.CDS_ACCOUNT (
Account_ID int,
HomePhone1 varchar(20),
City1 varchar(20)
)
create table dbo.CDS_Player (
Player_ID int,
FirstName varchar(20),
LastName varchar(20),
HostUser_ID int,
EMail varchar(20)
)
create table dbo.CDS_STATDAY (
Meta_ID int,
GamingDate date,
TWin int
)
create table dbo.Player_Miles (
Player_ID int,
Miles int
)
go
set nocount on
insert dbo.CDS_ACCOUNT values (1,'Phone1','City1')
insert dbo.CDS_ACCOUNT values (2,'Phone2','City2')
insert dbo.CDS_ACCOUNT values (3,'Phone3','City3')
insert dbo.CDS_ACCOUNT values (4,'Phone4','City4')
insert dbo.CDS_Player values (1, 'Tom','Alas',101,'Email1')
insert dbo.CDS_Player values (2, 'Dick','Smith',102,'Email2')
insert dbo.CDS_Player values (3, 'Harry','Jones',103,'Email3')
insert dbo.CDS_Player values (4, 'Han','Solo',104,'Email4')
insert dbo.CDS_STATDAY values (1,'2014-01-01',10)
insert dbo.CDS_STATDAY values (1,'2014-11-01',10)
insert dbo.CDS_STATDAY values (2,'2014-01-01',9)
insert dbo.CDS_STATDAY values (2,'2014-09-01',9)
insert dbo.CDS_STATDAY values (2,'2014-11-01',10)
insert dbo.CDS_STATDAY values (3,'2014-01-01',9)
insert dbo.CDS_STATDAY values (3,'2014-07-01',9)
insert dbo.CDS_STATDAY values (3,'2014-09-01',10)
insert dbo.CDS_STATDAY values (3,'2014-11-01',11)
insert dbo.Player_Miles values (1, 50)
insert dbo.Player_Miles values (2, 60)
insert dbo.Player_Miles values (3, 70)
insert dbo.Player_Miles values (4, 80)
go
*/
Select P.Player_ID
,P.FirstName
,P.LastName
,P.HostUser_ID
,A.HomePhone1
,P.EMail
,A.City1
,M.Miles
,avg(Case when S.GamingDate between '10/1/2014' and '12/31/2014' then 1.0 * S.TWin else null end) as "3 Month ADT"
,avg(Case when S.GamingDate between '7/1/2014' and '12/31/2014' then 1.0 * S.TWin else null end) as "6 Month ADT"
From dbo.CDS_ACCOUNT as A
Join dbo.CDS_Player as P
on A.Account_ID = P.Player_ID
Left Join dbo.CDS_STATDAY as S
on A.Account_ID = Meta_ID
Join dbo.Player_Miles as M
on A.Account_ID = M.Player_ID
Group by P.Player_ID
,P.FirstName
,P.LastName
,P.HostUser_ID
,A.HomePhone1
,P.EMail
,A.City1
,M.Miles
Hope this helps.
Rhys

Resources