Querying a column that shows [count] of [max count] - sql

I have 3 tables:
CustomerTypes table
Customers table (has a foreign key CustomerType). A customer can only have one customer type.
CustomersCollection table (contains many customerIds)
The primary SELECT of the query will be on CustomerTypes. I will be selecting two columns: CustomerTypeName and CountInCollection
The CustomerCount column in my query needs to show something like the following:
[Total # of Customers in CustomerType that are in Customer Collection] Of [Total # of Customers in CustomerType]
How can I get the proper customer count of the CustomerType that is part of the collection?
Example:
Customer1, Customer2, and Customer3 are all of CustomerTypeA.
CustomerCollection1 has customers Customer1 and Customer2 in it. The CountInCollection column for the CustomerTypeA record should show '2 of 3'.
Here is how I am able to get each count in separate queries:
-- Total customers in customer collection of customer type
SELECT COUNT(c.Id)
FROM Customer c
INNER JOIN CustomerCollection cc ON c.Id = cc.CustomerId
WHERE cc.CollectionId = 1019 AND c.CustomerTypeId=1000
-- Total customers in customer type
SELECT COUNT(Id) FROM
Customer WHERE CustomerTypeId=1000

Since you are using SQL 2008, I would take advantage of Common Table Expressions, aka CTEs, to assemble the data.
First, we'll need some test data. NOTE: I've thrown in some 'outliers' so that you can see where this kind of logic can bite you later.
DECLARE #CustomerTypes TABLE
(
CustomerTypeID INT,
[Customer Type] VARCHAR(100)
)
INSERT INTO #CustomerTypes
SELECT 1, 'TypeA'
UNION SELECT 2, 'TypeB'
UNION SELECT 3, 'TypeC' --NOTE: An outlier (not in customers-collection)
UNION SELECT 4, 'TypeD' --NOTE: An outlier (not in customers)
DECLARE #Customers TABLE
(
CustomerID INT,
CustomerTypeID INT
)
INSERT INTO #Customers
SELECT 1, 1
UNION SELECT 2, 1
UNION SELECT 3, 1
UNION SELECT 4, 2
UNION SELECT 5, 2
UNION SELECT 6, 2
UNION SELECT 7, 3
DECLARE #CustomersCollection TABLE
(
CollectionID INT IDENTITY(1,1),
CustomerID INT
)
INSERT INTO #CustomersCollection
(CustomerID)
SELECT TOP 2 --INSERT 2 of 3
CustomerID FROM #Customers WHERE CustomerTypeID = 1 --TypeA
INSERT INTO #CustomersCollection
(CustomerID)
SELECT TOP 1 --INSERT 1 of 3
CustomerID FROM #Customers WHERE CustomerTypeID = 2 --TypeB
Second, assemble the CTE data, and generate your output
;WITH CTE_COUNT_TYPE(CustomerTypeID, TypeCount)
AS
(
SELECT CustomerTypeID, COUNT(1)
FROM #Customers
GROUP BY CustomerTypeID
)
--SELECT * FROM CTE_COUNT_TYPE --DEBUG
,
CTE_COUNT_COLLECTION(CustomerTypeID, CollectionCount)
AS
(
SELECT CustomerTypeID, COUNT(1)
FROM #CustomersCollection CC
INNER JOIN #Customers C
ON CC.CustomerID = C.CustomerID
GROUP BY CustomerTypeID
)
--SELECT * FROM CTE_COUNT_COLLECTION --DEBUG
SELECT [Customer Type],
--CONVERT is necessary to combine INT data type (i.e. Count) and VARCHAR data type (i.e. 'as')
CONVERT(VARCHAR(100), COALESCE(CCC.CollectionCount, 0)) +
' of ' +
CONVERT(VARCHAR(100), COALESCE(CCT.TypeCount, 0)) As [Count in Collection]
FROM #CustomerTypes CT
LEFT OUTER JOIN #Customers C --Left outer join assists in outliers
ON CT.CustomerTypeID = C.CustomerTypeID
LEFT OUTER JOIN CTE_COUNT_TYPE CCT --Left outer join assists in outliers
ON CCT.CustomerTypeID = CT.CustomerTypeID
LEFT OUTER JOIN CTE_COUNT_COLLECTION CCC --Left outer join assists in outliers
ON CCC.CustomerTypeID = CT.CustomerTypeID
GROUP BY CT.[Customer Type]
, CCC.CollectionCount
, CCT.TypeCount

Hope so i get the question-
select
ct.CustomerTypeName as [Customer Type],
convert(varchar(30),count(cc.CollectionId)) + ' of ' + convert(varchar(30), count(c.CustomerId)) as [Count in Collection]
from
#Customer c
inner join #CustomerType ct on ct.CustomerTypeId = c.CustomerTypeId
left join #CustomerCollection cc on cc.CustomerId = c.CustomerId
group by
CustomerTypeName
Data script-
declare #customerType table (CustomerTypeId int, CustomerTypeName varchar(100))
insert into #customerType (CustomerTypeId, CustomerTypeName)
select 30, 'TypeA'
union
select 40, 'TypeB'
declare #customer table (CustomerId int, CustomerTypeId int)
insert into #customer (CustomerId, CustomerTypeId)
select 1, 30
union
select 2, 30
union
select 3, 30
union
select 4, 40
union
select 5, 40
union
select 6, 40
declare #customercollection table (CollectionId int, CustomerId int)
insert into #customercollection (CollectionId, CustomerId)
select 100, 1
union
select 200, 2
union
select 300, 5

Related

How to sum columns when table is joined?

Trying to sum from a table that is joined to another (resulting in multiple rows per row of the first table) it is counting the amount for each of the rows in second table.
Tables:
create table t_orders (
oid int,
cartlink nvarchar(3),
ordertotal float,
ordertax float
);
create table t_cart (
cartlink nvarchar(3),
productid int
);
insert into t_orders (oid,cartlink,ordertotal,ordertax) values
(1,'abc',10, 2),
(2,'cdf',9, 1),
(3,'zxc',11, 3)
;
insert into t_cart (cartlink,productid) values
('abc', 123),('abc', 321),('abc', 987),
('cdf', 123),('cdf', 321),('cdf', 987),
('zxc', 123),('zxc', 321),('zxc', 987)
;
Using following values for t_orders table is more accurate to the problem. Using distinct only counts order 2 and 3 once because both of their totals are 9.
insert into t_orders (oid,cartlink,ordertotal,ordertax) values
(1,'abc',10, 2),
(2,'cdf',9, 1),
(3,'zxc',9, 3)
;
Query and result:
SELECT
SUM(t_orders.ordertotal) AS SumOfTotal,
SUM(t_orders.ordertax) AS SumOfTax
FROM
t_orders
JOIN t_cart ON t_orders.cartlink = t_cart.cartlink
;
SumOfTotal
SumOfTax
90
18
What I want :
SumOfTotal
SumOfTax
30
6
I have to join t_orders -> t_cart -> t_products -> t_manufacturer because I'm trying to sum from t_orders WHERE t_manufacturer.type = 'some value'.
Try this
SELECT SUM(DISTINCT t_orders.ordertotal) AS SumOfTotal
, SUM(DISTINCT t_orders.ordertax) AS SumOfTax
FROM t_orders
JOIN t_cart ON t_orders.cartlink = t_cart.cartlink
"… I'm trying to sum from t_orders WHERE t_manufacturer.type = 'some value'."
Using sub-query:
SELECT
SUM(t1.Total) AS SumOfTotal,
SUM(t1.Tax) AS SumOfTax
FROM
(
SELECT
MAX(t_orders.ordertotal) AS Total,
MAX(t_orders.ordertax) AS Tax
FROM t_orders
JOIN t_cart ON t_orders.cartlink = t_cart.cartlink
JOIN t_products ON t_cart.productid = t_products.pid
JOIN t_manufacturer ON t_products.manufacturerid = t_manufacturer.mid
WHERE t_manufacturer.type = 'some value'
GROUP BY t_orders.oid
) AS t1
Or:
SELECT
SUM(t1.Total) AS SumOfTotal,
SUM(t1.Tax) AS SumOfTax
FROM
(
SELECT
MAX(t_orders.ordertotal) AS Total,
MAX(t_orders.ordertax) AS Tax
FROM t_orders
JOIN t_cart ON t_orders.cartlink = t_cart.cartlink
JOIN t_products ON t_cart.productid = t_products.pid
WHERE t_products.manufacturerid IN (
SELECT mid FROM t_manufacturer WHERE type = 'some value'
) AS t2
GROUP BY t_orders.oid
) AS t1

Select second max as max based on condition where max is used for condition

Trying to create a report to see how long Storage Units are vacant between 1 lease and the other. The information is stored in the customer table with columns:
PK (Customer Code) | Storage_Number (FK) | Status | startdate | enddate
I got the basic code to look for the MAX (last) customer per unit and for 1 customer before. My problem is with Storage Unit that’s currently Vacant. It will show for New Customer Start Date the Past customer, while I want to see this field blank, and the Prior Customer End Date should be from the most recent customer.
Select s.PK [Storage Number],
ca.enddate [Prior Customer End Date],
cb.startdate [New Customer Start Date],
Datediff(DAY, ca.enddate, cb.startdate)-1 [Days Vacant]
From Storage s
Left Outer Join (
Select Storage_Number,
MAX(PK) Customer_Code
FROM Customer
WHERE Status in (‘Future’, ‘Active’, ’Past’)
Group By Storage_Number
)maxa On maxa.Storage_Number = s.PK
Left Outer Join(
Select Storage_Number,
MAX(PK) Customer_Code
FROM Customer b
Full Outer Join (
Select Storage_Number sn,
MAX(PK) cc
FROM Customer
WHERE Status in (‘Future’, ‘Active’, ’Past’)
Group By Storage_Number
)a on a.sn = b.Storage_Number
WHERE Status in (‘Future’, ‘Active’, ’Past’)
And b.PK != a.cc
Group by Storage_Number
)maxb
ON maxb.Storage_Number = s.PK
Left Outer Join Customer ca on ca.Storage_Number = s.PK and ca.PK = maxb.PK
Left Outer Join Customer cb on cb.Storage_Number = s.PK and cb.PK = maxa.PK
WHERE ca.enddate !> cb.startdate
Order By ca.enddate
I can add multiple CASE statements
Select s.PK [Storage Number],
CASE
WHEN cb.status = ‘Past’
THEN cb.enddate
Else ca.enddate
END [Prior Customer End Date],
CASE
WHEN cb.status = ‘Past’
THEN null
ELSE cb.startdate
END [New Customer Start Date],
CASE
WHEN cb.status = ‘Past’
THEN null
ELSE Datediff(DAY, ca.enddate, cb.startdate)-1
END [Days Vacant]
FROM ………..
However I would like to see if there is a better way to do this.
Update
SQL 2000 Version (uses setup code from below / I haven't tested as don't have an SQL 2000 instance; hopefully everything's compatible with that version as I've tried not to use anything too modern).
declare #firstAvailableDate datetime = '2014-12-20' --allows us to see how many days the units were available before their first booking
, #lastAvailableDate datetime = getutcdate() --allows us to say up to which date we're interested for the last booking
declare #BookingSequence table
(
OrderByStorageId bigint identity(1,1)
, StorageId bigint
, StartDate datetime
, EndDate datetime
, CustomerId bigint
)
insert #BookingSequence
select StorageId, StartDate, EndDate, CustomerId
from #CustomerBookings
order by StorageId, StartDate
declare #BookingPrevAndCurrent table
(
StorageId bigint
, StartDate datetime
, EndDate datetime
, PreviousBookingEndDate datetime
, CustomerId bigint
, OrderByStorageId bigint
)
insert #BookingPrevAndCurrent
select c.StorageId, c.StartDate, c.EndDate, coalesce(p.EndDate,#firstAvailableDate), c.CustomerId, c.OrderByStorageId
from #BookingSequence c
left outer join #BookingSequence p
on p.StorageId = c.StorageId
and p.OrderByStorageId = c.OrderByStorageId - 1
select s.Name StorageUnit
, c.Name Customer
, x.PreviousBookingEndDate
, coalesce(x.StartDate, #lastAvailableDate) CurrentBookingStartDate
, DateDiff(Day,x.PreviousBookingEndDate, coalesce(x.StartDate, #lastAvailableDate)) DaysBetweenBookings
from #Storage s
left outer join #BookingPrevAndCurrent x
on x.StorageId = s.id
left outer join #Customer c
on c.id = x.CustomerId
order by StorageUnit, PreviousBookingEndDate
SQL 2008 Version
Try something like this:
SQL Fiddle
--setup code / sample data
declare #CustomerBookings table
(
Id bigint identity(1,1)
, CustomerId bigint
, StorageId bigint
, StatusId int
, StartDate datetime
, EndDate datetime
)
declare #Storage table
(
id bigint identity(1,1)
, Name nvarchar(32) not null
)
declare #Customer table
(
id bigint identity(1,1)
, Name nvarchar(32) not null
)
insert #Storage values('A'),('B'),('C')
insert #Customer values('Jane'),('Paul'),('Keith')
insert #CustomerBookings
select c.id, s.id, 1, x.b, x.e
from
(
select 'Jane' c, 'A' s, '2015-01-01' b, '2015-01-03' e
union all
select 'Jane' c, 'A' s, '2015-01-05' b, '2015-01-06' e
union all
select 'Jane' c, 'B' s, '2015-01-05' b, '2015-01-06' e
union all
select 'Paul' c, 'C' s, '2015-01-02' b, '2015-01-06' e
union all
select 'Paul' c, 'A' s, '2015-01-07' b, '2015-01-10' e
union all
select 'Paul' c, 'A' s, '2015-01-012' b, '2015-01-18' e
union all
select 'Keith' c, 'B' s, '2015-01-01' b, '2015-01-04' e
union all
select 'Keith' c, 'A' s, '2015-01-20' b, '2015-01-23' e
union all
select 'Keith' c, 'B' s, '2015-01-08' b, '2015-01-10' e
union all
select 'Keith' c, 'B' s, '2015-01-20' b, '2015-01-23' e
) x
inner join #Storage s on s.Name = x.s
inner join #Customer c on c.Name = x.c
--the actual solution
declare #firstAvailableDate datetime = '2014-12-20' --allows us to see how many days the units were available before their first booking
, #lastAvailableDate datetime = getutcdate() --allows us to say up to which date we're interested for the last booking
;with BookingSequence as
(
select StorageId, StartDate, EndDate, CustomerId, ROW_NUMBER() over (partition by StorageId order by StartDate) OrderByStorageId
from #CustomerBookings
)
, BookingPrevAndCurrent as
(
select StorageId, StartDate, EndDate, #firstAvailableDate PreviousBookingEndDate, CustomerId, OrderByStorageId
from BookingSequence c
where c.OrderByStorageId = 1
union all
select c.StorageId, c.StartDate, c.EndDate, p.EndDate, c.CustomerId, c.OrderByStorageId
from BookingPrevAndCurrent p
inner join BookingSequence c
on c.StorageId = p.StorageId
and c.OrderByStorageId = p.OrderByStorageId + 1
)
select s.Name StorageUnit
, c.Name Customer
, x.PreviousBookingEndDate
, coalesce(x.StartDate, #lastAvailableDate) CurrentBookingStartDate
, DateDiff(Day,x.PreviousBookingEndDate, coalesce(x.StartDate, #lastAvailableDate)) DaysBetweenBookings
from #Storage s
left outer join BookingPrevAndCurrent x
on x.StorageId = s.id
left outer join #Customer c
on c.id = x.CustomerId
order by StorageUnit, PreviousBookingEndDate

large number of inner joins within the same table

I have a scenario where all the items are in one table named "table". I am doing this for two items:
SELECT I.A + J.A AS A, I.B + J.B AS B, I.C + J.C AS C FROM table AS I
INNER JOIN Table AS J
ON I.StartDate = J.StartDate
AND I.EndDate = J.EndDate
WHERE I.ItemId = 602028
AND J.ItemId = 602029
and I doing this for three items in the table:
SELECT I.A + J.A + K.A AS A, I.B + J.B + K.B AS B, I.C + J.C + K.C AS C FROM table AS I
INNER JOIN Table AS J
ON I.StartDate = J.StartDate
AND I.EndDate = J.EndDate
INNER JOIN Table AS K
ON I.StartDate = K.StartDate
AND I.EndDate = K.EndDate
WHERE I.ItemId = 602028
AND J.ItemId = 602029
AND K.ItemId = 602030
So now you have an idea of what I am trying to do; straightforward. Here is the issue. The number of items to join is supplied at run time, and this number can be large, up to 200. My question is: what is the most efficient way to do this? Currently it's starting to get ugly at more than 5 joins.
Not sure why you want to alias and pivot as I don't think you need it.
Example to try ..
declare #table table (ID int, A int, B int, C int, startdate date, enddate date)
insert #table (ID, A, B, C, startdate, enddate)
select 123, 1, 1, 1, '2014-10-10', '2014-10-10'
union all
select 456,2, 2, 2, '2014-10-10', '2014-10-10'
union all
select 789,3, 3, 3, '2014-10-10', '2014-10-10'
union all
select 111,4, 4, 4, '2014-10-11', '2014-10-11'
union all
select 222,4, 4, 4, '2014-10-11', '2014-10-11'
select sum(A) as A, sum(B) as B, sum(C) as C, startdate, enddate
from #table
where ID in (123,456,789, 111, 222)
group by startdate, enddate
You could try a union on the tables in a derived table and use sum() on the columns for the selected Dates and Item
I.E
SELECT *
FROM I,
(SELECT SUM(A), SUM(B), SUM(C)
FROM (SELECT * FROM I UNION
SELECT * FROM J UNION
SELECT * FROM K) UnT
WHERE UnT.STARTDATE = I.STARTDATE
AND UnT.ENDDATE = I.ENDDATE
AND UnT.ItemId = I.ItemId
AND I.ItemId = 602028
Now you just have to Union the new table in the derived table.
Please try this !
Select sum(a)as A, sum(b) as B,sum(c) as C from
(select itemid,a,b,c from table t
where itemid in (602028,602029,...)
and exists (select 'x' from table t1 where itemid in (602028,602029,...) and t.startdate=t1.startdate and t.enddate=t1.enddate))a

I have two SELECTS in the same query - i need to make inner join between them

This is how my query looks:
DECLARE #month date
DECLARE #CustomerId int
DECLARE #InterfacedSystemId int
SET #month = '2013-05-01'
SET #CustomerId = 24
SET #InterfacedSystemId = 1
SELECT * FROM
(
SELECT CONVERT(BIGINT,MisparHeshbonit) AS PreProcInvoiceNumber ,CONVERT(DATE,TaarichErech,103) AS PreProcDate , ROUND(sum(convert(float,SchumBruto)),2) AS PreProcSum
FROM [VisaCalCredit] VCC
WHERE CONVERT(DATE,TaarichErech,103) BETWEEN #month AND DATEADD(DAY,-1,DATEADD(MONTH,1,#month))
AND VCC.CustomerID = #CustomerID
GROUP BY MisparHeshbonit , CONVERT(DATE,TaarichErech,103)
) AS PreTable
ORDER BY PreProcInvoiceNumber, PreProcDate
SELECT * FROM
(
SELECT InvoiceNumber AS PostProcInvoiceNumber,ActualPaymentTime AS PostProcDate ,ROUND(sum(GrossAmount),2) AS PostProcSum
FROM [CreditAndDebit] C INNER JOIN [Transaction] T ON C.TransactionID = T.ID
WHERE ActualPaymentTime BETWEEN #month AND DATEADD(DAY,-1,DATEADD(MONTH,1,#month))
AND T.CustomerID = #CustomerId
AND T.InterfacedSystemID = 1
GROUP BY InvoiceNumber , ActualPaymentTime
) AS PostTable
ORDER BY PostProcInvoiceNumber ,PostProcDate
I need to find the differences between the PreProcSum and the PostProcSum in those tables - and I cannot make inner join between the inner tables themselves (trigger other problems).
How can I make this inner join between those two tables i've defined in this query?
If your key is (InvoiceNumber, Date), I believe you can do this:
;WITH PreTable AS (
SELECT
CONVERT(BIGINT,MisparHeshbonit) AS PreProcInvoiceNumber,
CONVERT(DATE,TaarichErech,103) AS PreProcDate,
ROUND(sum(convert(float,SchumBruto)),2) AS PreProcSum
FROM [VisaCalCredit] VCC
WHERE
CONVERT(DATE,TaarichErech,103) BETWEEN #month AND DATEADD(DAY,-1,DATEADD(MONTH,1,#month))
AND VCC.CustomerID = #CustomerID
GROUP BY MisparHeshbonit, CONVERT(DATE,TaarichErech,103)
),
PostTable AS (
SELECT
InvoiceNumber AS PostProcInvoiceNumber,
ActualPaymentTime AS PostProcDate,
ROUND(sum(GrossAmount),2) AS PostProcSum
FROM [CreditAndDebit] C INNER JOIN [Transaction] T ON C.TransactionID = T.ID
WHERE ActualPaymentTime BETWEEN #month AND DATEADD(DAY,-1,DATEADD(MONTH,1,#month))
AND T.CustomerID = #CustomerId
AND T.InterfacedSystemID = 1
GROUP BY InvoiceNumber, ActualPaymentTime
),
MergedKeys AS (
SELECT
PreProcInvoiceNumber AS InvoiceNumber,
PreProcDate AS TheDate
FROM PreTable
UNION ALL
SELECT
PostProcInvoiceNumber,
PostProcDate
FROM PostTable
)
SELECT *
FROM
MergedKeys mk
LEFT JOIN PreTable prt
ON prt.PreProcInvoiceNumber = mk.InvoiceNumber
AND prt.PreProcDate= mk.TheDate
LEFT JOIN PostTable pot
ON pot.PostProcInvoiceNumber = mk.InvoiceNumber
AND pot.PostProcDate= mk.TheDate
ORDER BY
mk.InvoiceNumber,
mk.TheDate
Replace LEFT JOIN with JOIN if you are sure you will get all the invoice data in both of your selects.

Get distinct result

I have the following tables
Product --stored for productid
ProductRelation -- storing linked product id's
DECLARE #Product table(ProductID int)
DECLARE #ProductRelation TABLE (FirstProductID int,SecondProductID int)
INSERT INTO #Product
SELECT 1
UNION ALL
SELECT 2
UNION ALL
SELECT 3
UNION ALL
SELECT 4
UNION ALL
SELECT 5
UNION ALL
SELECT 6
UNION ALL
SELECT 7
UNION ALL
SELECT 8
UNION ALL
SELECT 9
UNION ALL
SELECT 10
--SELECT * FROM #Product
INSERT INTO #ProductRelation
SELECT 1,2
UNION ALL
SELECT 3,5
UNION ALL
SELECT 2,6
UNION ALL
SELECT 1,4
UNION ALL
SELECT 1,4
--SELECT * FROM #ProductRelation
SELECT ProductID,'Not Linked' AS 'Relation' FROM #Product
UNION
SELECT FirstProductID,'Linked' from #ProductRelation
UNION
SELECT SecondProductID ,'Linked' FROM #ProductRelation
Above query results repeating ProductID
I wanted to select distinct ProductID...if there is relation between product id then it should display the ProductID with 'Linked'
If no relation then ProductID with 'Not Linked'
I want the expected result like this
ProductID Relation
1 Linked
2 Linked
3 Linked
4 Linked
5 Linked
6 Linked
7 Not Linked
8 Not Linked
9 Not Linked
10 Not Linked
Try this:
SELECT
P.ProductID,
CASE WHEN COUNT(R.FirstProductID) > 0
THEN 'Linked'
ELSE 'Not Linked'
END Relation
FROM Product P
LEFT JOIN ProductRelation R
ON P.ProductID = R.FirstProductID
OR P.ProductID = R.SecondProductID
GROUP BY P.ProductID
well... I love this opportunity to use some unpivot tricks!
select P.ProductID
,isnull(L.relation,'Not Linked') as relation
from #Product P
left outer join (select U.ProductID, cast('Linked' as varchar(max)) as relation
from #ProductRelation
unpivot (ProductID for prod in (FirstProductID,SecondProductID))U
group by U.ProductID
)L
on L.ProductID = P.ProductID
You can use a left join combined with a CASE WHEN in SQL Server
SELECT DISTINCT
ProductID,
CASE WHEN #ProductRelation.FirstProductId IS NULL THEN 'Not Linked' ELSE 'Linked' END [Status]
FROM #Product
LEFT JOIN #ProductRelation ON #Product.ProductID = #ProductRelation.FirstProductId
RIGHT JOIN #ProductRelation ON #Product.ProductID = #ProductRelation.SecondProductId