large number of inner joins within the same table - sql-server-2012

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

Related

Combining two columns based on match in other colums in two tables

I have two tables, each containing two identical columns and one column unique for that table. What I need to do is combine those tables, with combinations of those unique columns for each matching pair of identical columns as result. Example of what I mean:
ACC ACTION PRIORITY ACC ACTION TARGET
A 1 10 A 1 i
A 2 15 A 1 j
A 3 25 A 3 k
B 3 101 B NULL l
B 4 102 B 4 m
B 5 103 B 1 n
ACC and ACTION are columns in both tables. ORDER is unique for the left one, TARGET for the right one. I need to get combinations of ORDER and TARGET on rows where ACC and ACTION match - for example when ACC is A and ACTION is 1, PRIORITY is 10, and TARGET is I or j, therefore combinations would be "10 I" and "10 j".
Also, when ACTION is null in right table, there should be row with the top PRIORITY on that TARGET.
So, expected result:
PRIORITY TARGET
10 i
10 j
25 k
102 m
103 l
Any attempt to do a correct JOIN or so failed from my side.
What I tried:
INSERT INTO #RESULT(TARGET, PRIORITY)
SELECT R.TARGET, MAX(L.PRIORITY)
FROM LEFT_TABLE L INNER JOIN RIGHT_TABLE R
ON L.ACC=R.ACC AND (L.ACTION = R.ACTION OR R.ACTION IS NULL);
But it gives an error. Grouping by TARGET does not make the right output, though.
I used UNION to solve this
SELECT priority, t.target
FROM prio p
JOIN target t ON p.acc = t.acc AND t.action = p.action
UNION
SELECT priority, t.target
FROM prio p
JOIN target t ON p.acc = t.acc AND t.action is null
AND p.priority = (SELECT MAX(priority) FROM prio)
See if this works. I did not test it.
DECLARE #L TABLE(ACC NVARCHAR(10),Action INT,Priority INT)
INSERT #L (ACC,Action,Priority) VALUES ('A',1,10),('A',2,15),('A',3,25),('B',4,101),('B',5,102),('B',6,103)
DECLARE #R TABLE(ACC NVARCHAR(10),Action INT,Target NVARCHAR(10))
INSERT #R (ACC,Action,Target) VALUES ('A',1,'i'),('A',1,'j'),('A',3,'k'),('B',NULL,'l'),('B',4,'m'),('B',1,'n')
SELECT
Target = MAX(R.Target),
Priority = MAX(L.Priority)
FROM
#L L
INNER JOIN #R R ON R.ACC=L.Acc AND (R.ACTION=L.Action OR R.Action IS NULL)
GROUP BY
L.ACC,
L.Action
ORDER BY
MAX(L.Priority)
You can do it like this:
Sample data
create table a
(
acc nvarchar(50),Action1 int, priority1 int)
create table b (acc nvarchar(50),action1 int, target1 nvarchar(50)
)
insert into a
values
('A',1, 10 ),
('A',2, 15 ),
('A',3, 25 ),
('B',3, 101),
('B',4, 102),
('B',5, 103)
insert into dbo.b
values
('A',1,'i'),
('A',1,'j'),
('A',3,'k'),
('B',null,'l'),
('B',4,'m'),
('B',1,'n')
SQL
with data1 as (
select acc,case when action1 is null then maxaction1 else action1 end as action1,target1 from (
select * from dbo.b a
cross apply (select MAX(action1) as MaxAction1 from dbo.a b where a.acc = b.acc ) x
)z
)
select priority1,target1 from data1 a inner join dbo.a b on a.acc = b.acc and a.action1 = b.Action1
Update
Just saw that you wrote you want it on Priority. Then you can do it like this:
SQL Code Update
with data1 as (
select * from (
select y.acc,y.target1,COALESCE(y.action1,c.action1) as Action1 from (
select * from dbo.b a
cross apply (select MAX(priority1) as MaxP from dbo.a b where a.acc = b.acc ) x
)y inner join dbo.a c on c.priority1 = MaxP
)z
)
select priority1,target1 from data1 a inner join dbo.a b on a.acc = b.acc and a.action1 = b.Action1
Result
You can try this:
WITH
tTable1
AS ( SELECT ACC
, [ACTION]
, [PRIORITY]
FROM ( VALUES ('A', 1, 10 )
, ('A', 2, 15 )
, ('A', 3, 25 )
, ('B', 3, 101)
, ('B', 4, 102)
, ('B', 5, 103)
) tTable1(ACC, [ACTION], [PRIORITY])
)
, tTable2
AS ( SELECT ACC
, [ACTION]
, [TARGET]
FROM ( VALUES ('A', 1 , 'i')
, ('A', 1 , 'j')
, ('A', 3 , 'k')
, ('B', NULL, 'l')
, ('B', 4 , 'm')
, ('B', 1 , 'n')
) tTable1(ACC, [ACTION], [TARGET])
)
, tTable1Max
AS ( SELECT ACC
, [PRIORITY] = MAX([PRIORITY])
FROM tTable1
GROUP BY ACC
)
, tResult
AS ( SELECT [PRIORITY] = CASE WHEN tTable2.[ACTION] IS NOT NULL
THEN tTable1.[PRIORITY]
ELSE tTable1Max.[PRIORITY]
END
, tTable2.[TARGET]
FROM tTable2
LEFT JOIN tTable1 ON tTable1.ACC = tTable2.ACC
AND tTable1.[ACTION] = tTable2.[ACTION]
OUTER APPLY ( SELECT [PRIORITY]
FROM tTable1Max
WHERE tTable1Max.ACC = tTable2.ACC
) tTable1Max
)
SELECT *
FROM tResult
WHERE [PRIORITY] IS NOT NULL
ORDER BY [PRIORITY]

How to add a temp table in SQL server

I am trying to add a temp table to my query so that I can query that temp table, I have searched the internet but I couldn't get a solution.
this is my query
;WITH cte AS (
SELECT ID, g.Name
FROM game.Game g WITH(NOLOCK
WHERE ID IN (SELECT Data FROM system.Split(1, ','))
UNION ALL
SELECT g.ID, g.Name
FROM game.Game g WITH(NOLOCK)
JOIN cte ON g.ParentID = cte.ID
)
SELECT c.ID,
c.Name
FROM cte c
INNER JOIN list.Type gt WITH(NOLOCK) ON c.TypeId = gt.TypeID
WHERE c.ID NOT IN (SELECT Data FROM system.Split(1, ','))
AND c.ID IN (SELECT ID FROM game.code WITH(NOLOCK)
WHERE ID = c.ID
AND StatusCode IN ('OP', 'CL', 'SU')
AND isDisplay = 'True'
AND GETDATE() BETWEEN DisplayStart AND DisplayEnd
AND GETDATE() < ISNULL(ResultDateTime, ResultExpected)
)
which gives me the following when I run it
ID | Name
1111 | BaseBall
2222 |BasketBall
45896 |Relay
now I tried to create a temp table as follows
Create Table #temp(
ID int,
Name varchar
)
;WITH cte AS (
SELECT ID, g.Name
FROM game.Game g WITH(NOLOCK)
WHERE ID IN (SELECT Data FROM system.Split(1, ','))
UNION ALL
SELECT g.ID, g.Name
FROM game.Game g WITH(NOLOCK)
JOIN cte ON g.ParentID = cte.ID
)
insert into #temp // i wanted to set these values in the temp table
SELECT c.ID,
c.Name
FROM cte c
INNER JOIN list.Type gt WITH(NOLOCK) ON c.TypeId = gt.TypeID
WHERE c.ID NOT IN (SELECT Data FROM system.Split(1, ','))
AND c.ID IN (SELECT ID FROM game.code WITH(NOLOCK)
WHERE ID = c.ID
AND StatusCode IN ('OP', 'CL', 'SU')
AND isDisplay = 'True'
AND GETDATE() BETWEEN DisplayStart AND DisplayEnd
AND GETDATE() < ISNULL(ResultDateTime, ResultExpected)
)
every time I try to store this information in the temp table it gives me an error 'Column name or number of supplied values does not match table definition.' But I only have two values in. What am I doing wrong that I cant see?
First, why not just use select into?
IF OBJECT_ID('TempDB..#temp') IS NOT NULL
BEGIN
DROP TABLE #temp
END
select c.ID, c.Name
into #temp
from . . .
Then you don't need to define #temp as a table.
Next, your definition is bad, because Name has only one character. This would be fixed with select into.
However, I don't know why you are getting the particular error you are getting. The numbers of columns appears to match.

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

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

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

SQL for columns A, B, C: group by A, compute share of B equals C for each A

Database is Oracle. I have a single table with three columns, A, B and C.
A is a string column
B is an int column
C is an int column
I need an SQL select that groups on A and returns the share (in percent) where B = C = [particular int value] for each value of A.
Something like
SELECT A, percentBEqC
...
GROUP ON A
SELECT A
, COUNT( CASE WHEN B = C AND C = 256
THEN 1
END
)
/ COUNT(*) AS percentBEqC
FROM YourTable
GROUP BY A
You may want to check if B = 256 AND C = 256 has better performance.
Another way for your problem would be:
SELECT ta.A
, COALESCE(cnt,0) / cntAll AS percentBEqC
FROM
( SELECT A
, COUNT(*) AS cntAll
FROM YourTable
GROUP BY A
) ta
LEFT JOIN
( SELECT A
, COUNT(*) AS cnt
FROM YourTable
WHERE B = 256 AND C = 256
GROUP BY A
) ts
ON ts.A = ta.A
Another way:
..once the syntax is correct ;/
SELECT A, (COUNT(*) / (SELECT COUNT(*) FROM mytbl b WHERE b.A = a.A)) AS percentBEqC
FROM mytbl a
WHERE B = particular_value
AND C = particular_value
GROUP BY A;
Returns only rows where percentBEqC > 0,