unable to join on a sub query - sql

apologies for the question name I was unsure how to describe this problem :
take the following CTE
WITH cte_form_answers AS (
SELECT fa.id
, MAX(CASE WHEN fa.question = 'contact' THEN fa.answer END) AS ContactMethod
FROM formanswers fa
GROUP BY fa.id)
SELECT * FROM cte_form_answers
id | ContactMethod
0 | Mobile
1 | Landline
and the following table
SELECT id, ContactMethod, Contact from contacts
id | ContactMethod | Contact
0 | Mobile | xxxx-xxx-xxx
0 | Email | xxx#email.com
1 | Landline | xxxx-xxxx-xxx
1 | Mobile | xxx-xxx-xxxx
I'm attempting to join using the contatMethod from my within my CTE onto the contact table
My own attempt has been :
WITH cte_form_answers AS (SELECT fa.id
, MAX(CASE WHEN fa.question = 'contact' THEN fa.answer END) AS ContactMethod
FROM formanswers fa
LEFT JOIN contacts c
ON c.id = fa.id
AND c.ContactMethod = ( SELECT fa1.id, MAX(CASE WHEN fa1.question = 'contact' THEN fa1.answer END)
FROM formanswers fa1 GROUP BY fa1.ID
GROUP BY fa.id)
which results in an error SQL Error [42601]: ERROR: subquery must return only one column Position: 722
Can someone guide me how to perform this correctly?
just to note the contact table is a slowly changing dimension so it has an end_date column which I also filter in the join but I feel that is of no consquence for this question.

You need to join in another scope that where you are aggregating. For example:
WITH cte_form_answers AS (
SELECT fa.id,
MAX(fa.answer) FILTER(WHERE fa.question = 'contact') AS ContactMethod
FROM formanswers fa
GROUP BY fa.id
)
SELECT *
FROM cte_form_answers a
LEFT JOIN contacts c ON c.id = fa.id AND c.ContactMethod = a.ContactMethod
Or using another CTE if you prefer:
WITH
cte_form_answers AS (
SELECT fa.id,
MAX(fa.answer) FILTER(WHERE fa.question = 'contact') AS ContactMethod
FROM formanswers fa
GROUP BY fa.id
),
cte_form_contact AS (
SELECT *
FROM cte_form_answers a
LEFT JOIN contacts c ON c.id = fa.id AND c.ContactMethod = a.ContactMethod
)
SELECT * FROM cte_form_contact

Related

How to use PIVOT in SQL for this sample

How do I pivot a results query?
Currently it looks like this
| Date | Count | BankName |
+---------+----------+----------+
| 970401 | 87 | Saderat |
| 970401 | 25 | Melli |
| 970401 | 11 | Sina |
into this
|Date | Saderat | Melli | Sina |
+---------+----------+----------+----------+
|970401 | 87 | 25 | 11 |
I tried the following but it's not working
SELECT
PayDate AS [Date],
COUNT(*) AS [Count], b.BankName
FROM
Payments p
INNER JOIN
dbo.Accounts a ON a.AccountId = p.CashAccountId
INNER JOIN
dbo.Banks b ON b.BankId = a.BankId
WHERE
PayTypeId = 21.101
AND PayDate BETWEEN '970401' AND '970412'
GROUP BY
PayDate, b.BankName
ORDER BY
paydate
or
SELECT
x.PayDate AS 'Date',
b.BankName
FROM
(SELECT
p.PayDate, p.PaymentId, p.CashAccountId
FROM
Payments p
WHERE
PayTypeId = 21.101
AND PayDate BETWEEN '970401' AND '970412') AS x
INNER JOIN
dbo.Accounts a ON a.AccountId = x.CashAccountId
INNER JOIN
dbo.Banks b ON b.BankId = a.BankId
PIVOT
(COUNT(PaymentId) FOR PayDate IN (bankid)) AS Pivotable
You can try following SQL for required results:
SELECT PayDate, Saderat, Melli, Sina
FROM
(SELECT PayDate , COUNT(*) AS [Count] , b.BankName
FROM Payments p INNER JOIN dbo.Accounts a ON a.AccountId = p.CashAccountId
INNER JOIN dbo.Banks b ON b.BankId = a.BankId
WHERE PayTypeId = 21.101 AND PayDate BETWEEN '970401' AND '970412'
GROUP BY PayDate , b.BankName
ORDER BY paydate) AS SourceTable
PIVOT
(
SUM([Count])
FOR BankName IN (Saderat, Melli, Sina)
) AS PivotTable;
You can do aggregation :
SELECT PayDate AS [Date],
SUM(CASE WHEN b.BankName = 'Saderat' THEN 1 ELSE 0 END) AS Saderat,
. . .
FROM Payments p INNER JOIN
dbo.Accounts a
ON a.AccountId = p.CashAccountId INNER JOIN
dbo.Banks b
ON b.BankId = a.BankId
WHERE PayTypeId = 21.101 AND PayDate BETWEEN '970401' AND '970412'
GROUP BY PayDate
ORDER BY paydate;
You can use PIVOT function in SQL Server, try following query:
SELECT date, [Saderat],[Melli],[Sina]
FROM YourTableName
PIVOT( MAX(count)
FOR BankName IN ([Saderat],[Melli],[Sina])) AS p
You could PIVOT
SELECT *
FROM
(
SELECT
p.PayDate AS [Date],
b.BankName
FROM dbo.Payments p
JOIN dbo.Accounts a ON a.AccountId = p.CashAccountId
JOIN dbo.Banks b ON b.BankId = a.BankId
WHERE p.PayTypeId = 21.101
AND p.PayDate BETWEEN CAST('1997-04-01' AS DATE) AND CAST('1997-04-12' AS DATE)
) src
PIVOT
(
COUNT(*)
FOR BankName IN (...) -- put quoted list of bank names here
) pvt
ORDER BY [Date]

Nested Oracle SQL - Multiple Values

I have a table structure like:
Table = contact
Name Emailaddress ID
Bill bill#abc.com 1
James james#abc.com 2
Gill gill#abc.com 3
Table = contactrole
ContactID Role
1 11
1 12
1 13
2 11
2 12
3 12
I want to select the Name and Email address from the first table where the person has Role 12 but not 11 or 13. In this example it should return only Gill.
I believe I need a nested SELECT but having difficulty in doing this. I did the below but obviously it isn't working and returning everything.
SELECT c.Name, c.Emailaddress FROM contact c
WHERE (SELECT count(*) FROM contactrole cr
c.ID = cr.ContactID
AND cr.Role NOT IN (11, 13)
AND cr.Role IN (12)) > 0
You can use a combination of EXISTS and NOT EXISTS
SELECT *
FROM contact c
WHERE
EXISTS(SELECT 1 FROM contactrole cr WHERE cr.ContactID = c.ID AND cr.Role = 12)
AND NOT EXISTS(SELECT 1 FROM contactrole cr WHERE cr.ContactID = c.ID AND cr.Role IN(11, 13))
Another option is to use GROUP BY and HAVING:
SELECT c.*
FROM contact c
INNER JOIN contactrole cr
ON cr.ContactID = c.ID
GROUP BY
c.ID, c.Name, c.Emailaddress
HAVING
SUM(CASE WHEN cr.Role = 12 THEN 1 ELSE 0 END) > 0
AND SUM(CASE WHEN cr.Role IN(11, 13) THEN 1 ELSE 0 END) = 0
Use conditional aggregation in Having clause to filter the records
Try this
SELECT c.NAME,
c.emailaddress
FROM contact c
WHERE id IN (SELECT contactid
FROM contactrole
GROUP BY contactid
HAVING Count(CASE WHEN role = 12 THEN 1 END) > 1
AND Count(CASE WHEN role in (11,13) THEN 1 END) = 0)
If you have only 11,12,13 in role then use can use this
SELECT c.NAME,
c.emailaddress
FROM contact c
WHERE id IN (SELECT contactid
FROM contactrole
GROUP BY contactid
HAVING Count(CASE WHEN role = 12 THEN 1 END) = count(*)
You can do this using JOINs:
SELECT c.*
FROM CONTACT c
INNER JOIN CONTACTROLE cr12
ON cr12.CONTACTID = c.ID AND
cr12.ROLE = 12
LEFT OUTER JOIN CONTACTROLE cr11
ON cr11.CONTACTID = c.ID AND
cr11.ROLE = 11
LEFT OUTER JOIN CONTRACTROLE cr13
ON cr13.CONTACTID = c.ID AND
cr13.ROLE = 13
WHERE cr11.ROLE IS NULL AND
cr13.ROLE IS NULL
The INNER JOIN CONTACTROLE cr12 requires that role 12 exist for the given contact ID; the LEFT OUTER JOIN CONTACTROLE cr11 and LEFT OUTER JOIN CONTRACTROLE cr13 check to see if roles 11 and 13 might exist for the given contact ID; and the WHERE clause verifies that neither roles 11 or 13 exist.
Best of luck.

Count Customers based on item master

I need you to help me on writing two queries in SQL Server 2008 that shows the following information based on item master:
Brand wise count on customer master plus customer who purchased the brand
Item Wise count of customer master plus customer who purchased the item
Here the link that shows the table information and the query which I tried.
Click here to view the table in SQL Fiddle
SELECT
brandname,
division,
route,
DivisionTotalCustomersCount = MAX(DivisionTotalCustomersCount),
RouteTotalCustomersCount = MAX(RouteTotalCustomersCount),
PurchasedCustomersCount = SUM(PurchasedCustomersCount)
FROM
(SELECT
i.brandname,
c.division,
c.route,
DivisionTotalCustomersCount =
(SELECT COUNT(distinct x.CustomerID)
FROM CustomerMaster x
WHERE x.division = c.division),
RouteTotalCustomersCount =
(SELECT COUNT(distinct x.CustomerID)
FROM CustomerMaster x
WHERE x.Route = c.route),
PurchasedCustomersCount = count(distinct C.CustomerID)
FROM CustomerMaster c
LEFT OUTER JOIN SalesData s on c.CustomerID = s.CustomerID
right outer join ItemMaster i on s.item = i.itemcode
GROUP BY i.brandname, c.division, c.route) A
GROUP BY
brandname, division, route
ORDER BY 1
Result Should as below
Excelsheet
I think you need to go reconsider the report and maybe splitting it out into multiple reports.
It does not make sense to have a route count as well as a divisional count if they are counting things at different levels of aggregation. So have a route count and division count report.
Either way, division and route is going to be null for 100PLUS because there are no customers for that brand which means there is no route or division info available.
--Division Count
SELECT BrandName, Division, COUNT(CustomerMaster.CustomerID) [Customer Count]
FROM ItemMaster LEFT OUTER JOIN
SalesData ON ItemMaster.BrandName = SalesData.Brand LEFT OUTER JOIN
CustomerMaster ON SalesData.CustomerID = CustomerMaster.CustomerID
GROUP BY BrandName, Division
--Route Count
SELECT BrandName, Route, Division, COUNT(CustomerMaster.CustomerID) [Customer Count]
FROM ItemMaster LEFT OUTER JOIN
SalesData ON ItemMaster.BrandName = SalesData.Brand LEFT OUTER JOIN
CustomerMaster ON SalesData.CustomerID = CustomerMaster.CustomerID
GROUP BY BrandName, Route, Division
Using your sqlfiddle data there are 25 sales records & 18 distinct brand/ division/ route/ customer records and there are no sales invloving 100PLUS
select
B.BrandName
, V.Division
, coalesce(Brand_count,0) as Brand_count
, coalesce(Division_count,0) as Division_count
from (select distinct BrandName from ItemMaster) as B
cross join (select distinct Division from CustomerMaster) as V
left join (
select
Brand
, Division
, sum(cust_count) over (partition by Brand) as Brand_count
, sum(cust_count) over (partition by Division) as Division_count
from (
select
S.Brand
, C.Division
, count(distinct S.CustomerID) cust_count
from salesdata as S
inner join CustomerMaster as C
on S.CustomerID = C.CustomerID
inner join ItemMaster as I
on S.item = I.ItemCode
group by
S.Brand
, C.Division
) as S
) as D
on B.BrandName = D.Brand
and V.Division = D.Division
order by
B.BrandName
, V.Division
;
| BRANDNAME | DIVISION | BRAND_COUNT | DIVISION_COUNT |
|-----------|----------|-------------|----------------|
| 100PLUS | Dubai | 0 | 0 |
| 100PLUS | RAK | 0 | 0 |
| KITCO | Dubai | 9 | 11 |
| KITCO | RAK | 9 | 7 |
| Red Bull | Dubai | 9 | 11 |
| Red Bull | RAK | 9 | 7 |
http://sqlfiddle.com/#!3/fecb0/27
All Credit to #kevriley
select
A.BrandName,
B.Division,
B.Route,
B.DivisionTotalCustomers,
B.RouteTotalCustomers,
isnull(C.PurchasedCustomersCount,0) as PurchasedCustomersCount
from
(
select distinct
BrandName, Route, Division
from dbo.ItemMaster
cross join dbo.CustomerMaster
) A
join
(
select distinct
Division,
Route,
DENSE_RANK() over (partition by Division order by c.CustomerID asc) + DENSE_RANK() over (partition by Division order by c.CustomerID desc) - 1 as DivisionTotalCustomers ,
DENSE_RANK() over (partition by ROUTE order by c.CustomerID asc) + DENSE_RANK() over (partition by ROUTE order by c.CustomerID desc) - 1 as RouteTotalCustomers
from CustomerMaster c
left join SalesData s on c.CustomerID = s.CustomerID
) B on B.Division = A.Division and B.Route = A.Route
left join
(
select
s.brand,
c.division,
c.route,
PurchasedCustomersCount = count(distinct C.CustomerID)
FROM CustomerMaster c
JOIN SalesData s on c.CustomerID = s.CustomerID
--join ItemMaster i on s.item = i.itemcode
GROUP by s.brand, c.division, c.route
) C on A.Brandname = C.Brand and C.Division = A.Division and C.Route = A.Route
See the same on SQL Fiddle
Select B.Brandname,B.Division,C AS DivisionTotalCustomerCount,
ISNULL(T.Count,0) AS PURCHASEDCUSTOMERSCOUNT
from
(
Select CM.Division,M.BrandName,COUNT(distinct CM.CustomerID) AS C
from dbo.CustomerMaster CM
CROSS JOIN ItemMaster M
GROUP BY CM.Division,M.BrandName
)B
LEFT JOIN
(Select Division,Brand,COUNT(Distinct C.CustomerID) As Count from CustomerMaster C
JOIN salesdata D
On C.CustomerID=D.CustomerID
where D.Brand='Red Bull'
GROUP BY Division,Brand
)T
ON B.Brandname=T.Brand
and B.Division=T.Division
Order by 1,2

Join multiple tables, select counts from different tables and group by one column in one query

I need to join multiple tables, select counts from different tables and group by one column in one query. This is how I would do this separately:
select c.CommunityName, SUM(case when m.ListKey = c.ListKey then 1 else 0 end) as Posts
from Community c with(NOLOCK)
join messages_ m with(NOLOCK)
on c.ListKey = m.ListKey
group by c.CommunityName
select c.CommunityName, SUM(case when b.CommunityKey = c.CommunityKey then 1 else 0 end) as Blogs
from Community c with(NOLOCK)
join Blog b with(NOLOCK)
on c.CommunityKey = b.CommunityKey
group by c.CommunityName
select c.CommunityName, SUM(case when ce.CommunityKey = c.CommunityKey then 1 else 0 end) as Events
from Community c with(NOLOCK)
join CalendarEvent ce with(NOLOCK)
on c.CommunityKey = ce.CommunityKey
where ce.StartDateTime >= GETDATE()
group by c.CommunityName
or simply
select c.CommunityName, COUNT(*)
from Community c with(NOLOCK)
join messages_ m with(NOLOCK)
on c.ListKey = m.ListKey
group by c.CommunityName
select c.CommunityName, COUNT(*)
from Community c with(NOLOCK)
join Blog b with(NOLOCK)
on c.CommunityKey = b.CommunityKey
group by c.CommunityName
select c.CommunityName, COUNT(*)
from Community c with(NOLOCK)
join CalendarEvent ce with(NOLOCK)
on c.CommunityKey = ce.CommunityKey
where ce.StartDateTime >= GETDATE()
group by c.CommunityName
There are more tables, some that require additional joins... Can someone please help?
If I understand your question correctly, you are looking for community name along with the counts such as posts, blogs, event etc..
As your queries count individually, add dummy columns in the SELECT for the other counts and then in the end UNION them and get the SUM.
SELECT CommunityName , SUM(MessageCount), SUM(BlogCount), SUM(EventCount)
FROM (
SELECT c.CommunityName CommunityName , COUNT(*) MessageCount, 0 BlogCount, 0 EventCount
FROM Community c with(NOLOCK)
JOIN messages_ m with(NOLOCK)
ON c.ListKey = m.ListKey
GROUP BY c.CommunityName
UNION
SELECT c.CommunityName, 0, COUNT(*), 0
FROM Community c with(NOLOCK)
JOIN Blog b with(NOLOCK)
ON c.CommunityKey = b.CommunityKey
GROUP BY c.CommunityName
UNION
SELECT c.CommunityName, 0, 0, COUNT(*)
FROM Community c with(NOLOCK)
JOIN CalendarEvent ce with(NOLOCK)
ON c.CommunityKey = ce.CommunityKey
WHERE ce.StartDateTime >= GETDATE()
GROUP BY c.CommunityName
) CountsTable
GROUP BY CountsTable.CommunityName
CountsTable will look like
| COMMUNITYNAME | MESSAGECOUNT | BLOGCOUNT | EVENTCOUNT |
|---------------|--------------|-----------|------------|
| Name | 10 | 0 | 0 |
| Name | 0 | 20 | 0 |
| Name | 0 | 0 | 30 |
So, you can GROUP BY name and sum up the counts to get your result
| COMMUNITYNAME | MESSAGECOUNT | BLOGCOUNT | EVENTCOUNT |
|---------------|--------------|-----------|------------|
| Name | 10 | 20 | 30 |
Have you thought about using LEFT JOIN to connect your tables? Then you can check for NULLs and sum up the non-NULL values.
SELECT
c.CommunityName,
SUM(case when m.ListKey IS NOT NULL then 1 else 0 end) as Posts,
SUM(case when b.CommunityKey IS NOT NULL then 1 else 0 end) as Blogs,
SUM(case when ce.CommunityKey IS NOT NULL then 1 else 0 end) as Events
FROM
Community c WITH(NOLOCK)
LEFT JOIN
messages_ m WITH(NOLOCK)
ON c.ListKey = m.ListKey
LEFT JOIN
Blog b WITH(NOLOCK)
ON c.CommunityKey = b.CommunityKey
LEFT JOIN
CalendarEvent ce WITH(NOLOCK)
ON c.CommunityKey = ce.CommunityKey
WHERE
ce.StartDateTime >= GETDATE()
GROUP BY
c.CommunityName

SQL condition sum from two joined tables

I have two tables as below:
Invoice
InvId | Amount | Name
-----------------------
1 | 50 | John
2 | 30 | Mike
3 | 20 | John
Detail
MetalType| Weight | InvId
-------------------------
Gold | 2 | 2
Silver | 4 | 3
Silver | 3 | 3
Gold | 5 | 1
I would like to have the following output, but my query will only provide the total for silver and gold for John. How can I build a query that will also include the total invoice amount for John.
Total Invoice Amount For John = 70
Total Silver Weight = 7
total Gold Weith = 5
SELECT
SUM(IFF(D.MetalType=”Gold”, D.Weight, 0)) AS TotGold,
SUM((IFF(D.MetalType=”Silver”, D.Weight, 0)) AS TotSilver
FROM Invoice I INNER JOIN Detail D ON I.InvId = D.InvId WHERE I.Name = “John”
Try this:
For Sql-Server:
SELECT
SUM(TotalAmount) AS TotalAmount,
SUM(TotGold) AS TotGold,
SUM(TotSilver) AS TotSilver
FROM(
SELECT
SUM (I.Amount) OVER (Partition by D.Invid) AS TotalAmount,
SUM(CASE WHEN D.MetalType='Gold' THEN D.Weight ELSE 0 END) AS TotGold,
SUM(CASE WHEN D.MetalType='Silver' THEN D.Weight ELSE 0 END) AS TotSilver
FROM Invoice I INNER JOIN Detail D ON I.InvId = D.InvId
WHERE I.Name = 'John'
GROUP BY D.InvId, I.Amount) n
Here is an SQL Fiddle - now it kills the duplicate detail and counts it only once.
EDITED for Access:
SELECT
n.Name,
MAX(TotalAmount),
SUM(TotGold) AS TotGold,
SUM(TotSilver) AS TotSilver
FROM(
SELECT
I.Name,
SUM(CASE WHEN D.MetalType='Gold' THEN D.Weight ELSE 0 END) AS TotGold,
SUM(CASE WHEN D.MetalType='Silver' THEN D.Weight ELSE 0 END) AS TotSilver
FROM Invoice I
INNER JOIN Detail D ON I.InvId = D.InvId
GROUP BY I.Name, D.InvId, I.Amount) n
INNER JOIN (
SELECT
I.Name, SUM (I.Amount) AS TotalAmount
FROM Invoice I
GROUP BY I.Name) m ON m.Name = n.Name
GROUP BY n.Name
Try with this:
With tbl3 (Amt,Gold,Silver)
as
(
SELECT
SUM (I.Amount) OVER (Partition by D.Invid) AS TotalAmount,
SUM(CASE WHEN D.MetalType='Gold' THEN D.Weight ELSE 0 END) AS TotGold,
SUM(CASE WHEN D.MetalType='Silver' THEN D.Weight ELSE 0 END) AS TotSilver
FROM Invoice I Right JOIN Detail D ON I.InvId = D.InvId
WHERE I.Name = 'John' Group by D.InvId, I.Amount
)
Select SUM(Amt) as Total_Invoice_Amount_For_John,
SUM(Gold) as Total_Silver_Weight,
SUM(Silver) as Total_Gold_Width from tbl3
SQL Fiddle
I havent tried out the other queries already posted and they might already be suitable for what you want but here's my take on it :-
SELECT X.NAME, X.METALTYPE, X.WEIGHT, Y.TOTAL
FROM
(SELECT NAME, METALTYPE, SUM(Weight) AS WEIGHT
FROM INVOICE i
INNER JOIN DETAIL d ON i.InvId = d.InvId
GROUP BY NAME, METALTYPE) X
INNER JOIN
(SELECT SUM(AMOUNT) AS Total, NAME
FROM INVOICE
GROUP BY NAME)Y
ON X.NAME = Y.NAME
ORDER BY NAME, TOTAL, METALTYPE
select name, sum(Amount) as 'total invoice',sum(Gold) as 'Gold',sum(Silver) as Silver from(
select aa.Name,aa.Amount,
sum(case when bb.MetalType='Gold' then bb.Weight else 0 end) as 'Gold',
sum(case when bb.MetalType='Silver' then bb.Weight else 0 end) as 'Silver'
from a aa left outer join b bb on aa.InvID=bb.InvID group by aa.InvID) as c group by c.name