SQL Select Query - problem pivoting rows into columns - sql

I have three tables in an SQL 2005 database, that I need to query and display on one row. The tables are:
MasterStock
StockID, Description
1, Plate
2, Bowl
ShopStock
ShopID, StockID, StockLevel
1,1,6
2,1,0
3,1,0
4,1,10
Sales
StockId, ShopId, SoldQuantity, transDate
1, 1, 1, 5/1/2011
1,2,1, 5/1/2011
I need to get them to show one row:
StockID, Description, 1 Sales, 1 Stock, 2 Sales, 2 Stock, 3 Sales,…
I have managed to get what somewhere with the query below:
SELECT MasterStock.StockID, MasterStock.Description,
SUM(CASE WHEN sales.shopid = 1 THEN sales.Soldquantity ELSE 0 END) AS [1 Sold],
MAX(CASE WHEN shopstock.shopid = 1 THEN shopstock.stockLevel ELSE 0 END) AS [1 Stock],
SUM(CASE WHEN sales.shopid = 2 THEN sales.Soldquantity ELSE 0 END) AS [2 Sold],
MAX(CASE WHEN shopstock.shopid = 2 THEN shopstock.stockLevel ELSE 0 END) AS [2 Stock],
SUM(CASE WHEN sales.shopid = 3 THEN sales.Soldquantity ELSE 0 END) AS [3 Sold],
MAX(CASE WHEN shopstock.shopid = 3 THEN shopstock.stockLevel ELSE 0 END) AS [3 Stock],
SUM(CASE WHEN sales.shopid = 4 THEN sales.Soldquantity ELSE 0 END) AS [4 Sold],
MAX(CASE WHEN shopstock.shopid = 4 THEN shopstock.stockLevel ELSE 0 END) AS [4 Stock]
FROM ShopStock INNER JOIN
Sales ON ShopStock.StockID = Sales.StockID AND ShopStock.shopID = Sales.ShopID
INNER JOIN MasterStock ON ShopStock.StockID = MasterStock.StockID
WHERE (sales.transdate > 1/1/2010)
GROUP BY MasterStock.StockID, MasterStock.Description
However, if there are no sales for the product it doesn’t show any stock levels. If I remove the shopID join on shopstock and sales it shows the stock levels, but reports inaccurate sales - multiplies by four (one for each shopstock record?).
I know I’m missing something here, but I’m not getting anywhere! Any help would be greatly received.

Two problems:
1) You need a LEFT OUTER JOIN between ShopStock and Sales, which will ensure that the query returns records from ShopStock even if there are no related entries in Sales. By definition, an INNER JOIN will not return records from either side of the join, if one of the sides is missing records.
2) You need to move your sales.transdate > 1/1/2010 condition to the inner join, rather than the WHERE clause. Conditions in the WHERE clause will be logically applied after any logic in the table joins. So even if you get your joins right, the where clause will filter out stock without sales because sales.transdate will appear null.
Something like this:
FROM ShopStock LEFT OUTER JOIN Sales
ON ShopStock.StockID = Sales.StockID
AND Sales.transdate > 1/1/2010
INNER JOIN // the rest of your joins here
I'm guessing you also want >= on your transdate filter as well, but that's just a hunch.
Good luck!

Related

How to use count for a case statement

I'm using the below code to get the count of every case statements which has sum inside the case statements but I'm getting the error message.
SELECT
count(case when SUM(Orders.Sales)>10000 then 1 end) as High,
count(case when SUM(Orders.Sales)>5000 and SUM(Orders.Sales)<9999 then
SUM(Orders.Sales) end) as Medium,
count(case when SUM(Orders.Sales)<5000 then SUM(Orders.Sales) end) as Low
FROM Orders
INNER JOIN Returns ON Orders.[Order ID] = Returns.[Order ID]
OUTPUT
This is the required output which I'm supposed to be expected.
I feel that you should be doing the outer count rollups over a subquery which aggregates by order:
SELECT
COUNT(CASE WHEN sales < 5000 THEN 1 END) AS Low,
COUNT(CASE WHEN sales < 9999 THEN 1 END) AS Medium,
COUNT(CASE WHEN sales >= 9999 THEN 1 END) AS High
FROM
(
SELECT o.[Order ID], SUM(o.Sales) AS sales
FROM Orders o
INNER JOIN Returns r ON o.[Order ID] = r.[Order ID]
GROUP BY o.[Order ID]
) t;
That being said, I don't actually know what the purpose of joining Orders to the Returns table is. If you intend to only find sales amounts from orders which have been returned, then maybe this makes sense. Otherwise, maybe it doesn't make sense.

Transact SQL - Table with different Record types requiring calculation

I have a table of invoices and Record_Types that I need to reconcile to open invoice report. I have the process down and know what I need to do. Just dont know how to properly structure the query and would prefer to not create 3 tables.
Record Types.
Invoice = 1 Credit = 5 Payment = 7
Invoice_Number, Record_Type, Dollar figure
Outstanding_Balance = Invoice(1) -(Payment(7)-(Credit))
Invoice_number Record_type Gen_Numeric_3
Basically I need to take the record_Type 1 and subtract the total of record type 7's from the below.
Invoice_Num Rec_Type Dollar_Amt
00820437 1 536.7700000000
00820437 7 469.6200000000
00820437 7 67.1500000000
Any advice would be great. messer
You can do this with aggregation and case statements:
SELECT invoice_num,
SUM(CASE WHEN rec_type = 1 THEN dollar_amt ELSE 0 END) - (SUM(CASE WHEN rec_type=7 THEN dollar_amt ELSE 0 END) - SUM(CASE WHEN rec_type=5 THEN dollar_amt ELSE 0 END)) as outstanding_balance
FROM yourtable
GROUP BY invoice_num

SELECTING with multiple WHERE conditions on same table

I am currently working on my sql query that looks like this
SELECT reference.agent_ID, reference.policy_ID, Count(reference.status_ID) AS [Quoted Motor Breakdown] , Sum(IIf([status_ID]=1,1,0)) AS sold
FROM reference
GROUP BY reference.agent_ID, reference.policy_ID
HAVING (((reference.policy_ID)=1));
So results show as below
Agent| Number of policies quoted for Motor breakdown|number of policies Motor breakdown sold
I would like to add more columns to the same table showing results for remaining type of policies :
reference.policy_ID=2 (Travel),
reference.policy_ID=3 (Home Insurance) next to one agent
example:
Mr Smith|Quoted Motor Breakdown| Sold| Quoted Motor Travel| Sold
I would also like to my query to show result=0 if there wos no quote for specific type of policy
Any advise would be much appreciated
Thank you
I think you want conditional aggregation:
SELECT r.agent_ID,
SUM(CASE WHEN r.policy_ID = 1 THEN 1 ELSE 0 END) AS [Quoted Motor Breakdown],
SUM(CASE WHEN r.policy_ID = 1 AND status_ID = 1 THEN 1 ELSE 0 END) AS sold,
SUM(CASE WHEN r.policy_ID = 2 THEN 1 ELSE 0 END) AS NumTravel,
SUM(CASE WHEN r.policy_ID = 3 THEN 1 ELSE 0 END) AS NumHomeInsurance
FROM reference r
GROUP BY r.agent_ID;
If you are using MS Access, then you'll need to use the non-standard iif() instead:
SELECT r.agent_ID,
SUM(iif(r.policy_ID = 1, 1, 0)) AS [Quoted Motor Breakdown],
SUM(iif(r.policy_ID = 1 AND status_ID = 1, 1, 0)) AS sold,
SUM(iif(r.policy_ID = 2, 1, 0)) AS NumTravel,
SUM(iif(r.policy_ID = 3, 1, 0)) AS NumHomeInsurance
FROM reference as r
GROUP BY r.agent_ID;

SQL Beginner : Multiple where clause from same table

I'm very new to SQL and am not sure how to accomplish the following.
I have a list of meter IDs that have a years worth of Target and a years worth of Budget data assigned, however these are all held in the same table with an identifier (TargetType) to distinguish between Target(0) & Budget(1)
ID TARGETTYPE VALUE01 VALUE02 ...(etc up to VALUE12)
123 0 1001 1100
123 1 9000 9100
456 0 5000 5100
456 1 8000 8100
Desired result would be also include information from a couple of other tables want to add that in as well
My query so far can bring through one set of data :
PARAMETERS
[Utility] Text;
SELECT
Contacts.Group,
Contacts.Site,
Points.ID,
Points.Utility,
Points.MPAN1,
Target.Value_01 AS [Target JAN],
Target.Value_02 AS [Target FEB],
Target.Value_03 AS [Target MAR],
Target.Value_04 AS [Target APR],
Target.Value_05 AS [Target MAY],
Target.Value_06 AS [Target JUN],
Target.Value_07 AS [Target JUL],
Target.Value_08 AS [Target AUG],
Target.Value_09 AS [Target SEP],
Target.Value_10 AS [Target OCT],
Target.Value_11 AS [Target NOV],
Target.Value_12 AS [Target DEC]
FROM
((Contacts INNER JOIN Points ON Contacts.[Id] = Points.[Contacts_Id])
INNER JOIN Contracts ON Points.[Id] = Contracts.[Point_Id])
INNER JOIN Target ON Points.Id = Target.DataSetId
WHERE
Points.UtilityType =[Utility]
ORDER BY
Contacts.ClientGroup;
Desired Output
(the values would go TargetJan through to TargetDec and then BudgetJan through to BudgetDec but I've not shown for brevity):
Group Site ID Utility MPAN1 TargetJan TargetFeb etc... BudgetJan BudgetFeb etc...
ABC London 123 Gas 123456 1,000 1,100 9,000 9,100
ABC NewYork 456 Gas ABC123 5,000 5,100 8,000 8,100
How can I add in the same fields but depending on the value of Target.TargetType, I'm guessing that it is a Union query, but I have no idea.
Any pointers of where to start would be gratefully received :)
UPDATE #1
Thank you for your help. I think I understand the query, however there is still a bit of odd behaviour I can't figure out.
QUERY USED
SELECT
Points.ID,
SUM(CASE WHEN Target.TargetType = '0' THEN Target.Value_01 else 0 end) AS [TGT JAN],
SUM(CASE WHEN Target.TargetType = '0' THEN Target.Value_02 else 0 end) AS [TGT FEB],
FROM
((Contacts INNER JOIN Points ON Contacts.[Id] = Points.[Contacts_Id])
INNER JOIN Contracts ON Points.[Id] = Contracts.[Point_Id] )
INNER JOIN Target ON Points.Id = Target.DataSetId
GROUP BY
Points.ID
ORDER BY
Points.ID;
Where my data row only has one Target.Type then the query returns as desired:
RAW DATA
ID TARGETTYPE VALUE_01 VALUE_02
10079 0 7642 5735
RESULT
ID TGTJAN TGTFEB
10079 7642 5735
However ... If I have an ID with 2 or more TargetTypes then the Value output is being multiplied by 6.
RAW DATA
ID TARGETTYPE VALUE_01 VALUE_02
7423 0 58339 57441
7423 1 1663 1637
RESULT
ID TGTJAN TGTFEB
7423 350034 344646
I have experimented and removed the INNER JOIN to the contacts table (although I will need that join) and then it all works as expected ???? So why is the Contacts JOIN causing this issue ?
Please put me out of my misery as I can not work out what is going on !
What about using a select case syntax?
select
...
sum(case when Target.TargetType = 'Target' then Target.Value_01 else 0 end) AS [Target JAN],
sum(case when Target.TargetType = 'Budget' then Target.Value_01 else 0 end) AS [Budget JAN],
...
group by
--all fields in the select list which are not aggregates
For future reference I have solved this problem by rewriting the joins and changing the way the data is held so I don't need to reference the Contracts table, which is redudant and probably contributed to my bizarre x by 6 problem.
SELECT
Points.ID,
SUM(CASE WHEN Target.TargetType = '0' THEN Target.Value_01 else 0 end) AS [TGT kWh JAN],
SUM(CASE WHEN Target.TargetType = '0' THEN Target.Value_02 else 0 end) AS [TGT kWh FEB],
SUM(CASE WHEN Target.TargetType = '1' THEN Target.Value_01 else 0 end) AS [BUD kWh JAN],
SUM(CASE WHEN Target.TargetType = '1' THEN Target.Value_02 else 0 end) AS [BUD kWh FEB]
FROM (Contacts INNER JOIN Points ON Contacts.Id = Points.Contacts_Id)
INNER JOIN Target ON Points.Id = Target.DataSetId
GROUP BY
Points.ID
ORDER BY
Points.ID;

SQL Query to compare 2 weeks

I've got to design a query in visual studio where I have 2 data sets.
basically it goes like this.
I want to compare this weeks call total to last week per country calling.
the only thing is last weeks calls may have come from 20 diff countries while this weeks might only have come from 15.
How can I make the query such that the 20 countries will show up for both while having "0" value in for countries that do not appear this week.
below is my query:
Select country,
Sum(Case When actstatus in (5,105) Then 1 Else 0 End) As TotalCalls,
Sum(Case When actstatus = 105 Then 1 Else 0 End) As FailedCalls
From termactivity(nolock)
INNER JOIN termconfig(NOLOCK) ON cfgterminalID = actterminalID
INNER JOIN Country (nolock) on country = cycode
Where actstatus in (5,105)
and (actTerminalDateTime BETWEEN #StartDate-7 AND #EndDate-7)
Group By country
order By country asc
When Act status = 105 it means the call was not completed and when it = 5 it means the call was successful. I am doing this to get a successful call % rate per week.
Thanks in Advance!
Apply the same logic as you did to total calls and failed calls as you did to the this week and last week.
SELECT country,
COUNT(CASE WHEN actTerminalDateTime < #StartDate THEN 1 END) [LastWeekTotalCalls],
COUNT(CASE WHEN ActStatus = 105 AND actTerminalDateTime < #StartDate THEN 1 END) [LastWeekFailedCalls],
COUNT(CASE WHEN actTerminalDateTime >= #StartDate THEN 1 END) [ThisWeekTotalCalls],
COUNT(CASE WHEN ActStatus = 105 AND actTerminalDateTime >= #StartDate THEN 1 END) [ThisWeekFailedCalls]
FROM termactivity (NOLOCK)
INNER JOIN termconfig (NOLOCK)
ON cfgterminalID = actterminalID
INNER JOIN Country (NOLOCK)
ON country = cycode
WHERE actstatus in (5,105)
AND actTerminalDateTime BETWEEN DATEADD(DAY, -7, #StartDate) AND #EndDate
GROUP BY country
ORDER BY country ASC
I've also tidied up your query slightly, for example there is no point in specifying
WHEN ActStatus IN (5, 105) ...
When your WHERE clause already limits all results to 5, 105, therefore this is a redundant predicate in your case expression
From what I understand, you want to perform separate queries for two weeks, and you want both queries to produce rows for all countries, regardless of whether all countries had any calls. To achieve this, you need to use LEFT OUTER JOINS. The below code should guarantee that every country found in the Country table has a row, even if both sums are 0.
SELECT country,
SUM(CASE WHEN actstatus IN (5,105) THEN 1 ELSE 0 END) AS TotalCalls,
SUM(CASE WHEN actstatus = 105 THEN 1 ELSE 0 END) AS FailedCalls
FROM Country (NOLOCK)
LEFT OUTER JOIN termconfig (NOLOCK) ON country = cycode
LEFT OUTER JOIN termactivity (NOLOCK) ON cfgterminalID = actterminalID
WHERE (actTerminalDateTime BETWEEN #StartDate-7 AND #EndDate-7)
GROUP BY country
ORDER BY country ASC
If this was not what you wanted, perhaps you need to clarify your question. Many others have assumed that you want to combine the results into a single query.