I have the following table, Persons_Companies, that shows a relation between persons and companies knowns by these persons:
PersonID | CompanyID
1 1
2 1
2 2
3 2
4 2
Imagining that company 1 = "Google" and company 2 is = "Microsoft", I would like to know the query to have the following result:
PersonID | Microsoft | Google
1 0 1
2 1 1
3 1 0
4 1 0
Until this moment I have something similar:
select PersonID,
case when CompanyID=1 then 1 else 0
end as Google,
case when EmpresaID=2 then 1 else 0
end as Microsoft
from Persons_Companies
My problem is with the persons that knows both companies, I can't imagine how could this query be.
What is the SQL query?
select PersonID,
case when EXISTS (
SELECT 1
FROM Persons_Companies pc1
WHERE pc.PersonID = pc1.PersonID and pc1.CompanyID = 1 ) then 1 else 0
end as Google,
case when EXISTS (
SELECT 1
FROM Persons_Companies pc2
WHERE pc.PersonID = pc2.PersonID and pc2.CompanyID = 2 ) then 1 else 0
end as Microsoft
from Persons_Companies pc
SELECT personId, sum(case companyId when 1 then 1 else 0 end) google,
sum(case companyId when 2 then 1 else 0 end) microsoft
from Persons_Companies
group by personId
order by personId;
I think this is what you want: http://pastie.org/881092
select
p.person_id,
if(ms.company_id is null,0,1) as 'microsoft',
if(ora.company_id is null,0,1) as 'oracle',
if(mysql.company_id is null,0,1) as 'mysql'
from
person p
left outer join person_company ms on p.person_id = ms.person_id and ms.company_id = 1
left outer join person_company ora on p.person_id = ora.person_id and ora.company_id = 2
left outer join person_company mysql on p.person_id = mysql.person_id and mysql.company_id = 3
order by
p.person_id;
There is a problem with both answers, because there is an assumption that Google and Microsoft will always be the only companies on the table. I believe the query should be generic.
I am not too sure but I think a combination of a cross tab and CTE will work well.
Related
I'm trying to do a simple, at first sight, SQL query, but I can't wrap my head around what I'm doing wrong.
Problem
Imagine these are my tables:
TableUsers
UserID FirstName LastName
1 Bill Johnson
2 Alex Agnew
3 Mike Owen
4 Kate Ryan
TableArticles
ArticleID Description
1 Bananas
2 Eggs
3 Milk
TableOrders
OrderID UserID ArticleID
1 1 1
2 1 2
3 2 3
4 2 2
5 3 3
I want to list all users who have at least one order linked to their name, count how many orders and count how many of a specific article they ordered:
Desired result
UserID FirstName # Orders # Banana orders # Egg orders
1 Bill 2 1 1
2 Alex 2 0 1
3 Mike 1 0 0
I have tried:
SELECT
UserID,
FirstName,
COUNT(*) AS '# Orders',
(SELECT COUNT(*) FROM TableOrders O WHERE O.UserID = TableOrders.UserID AND O.ArticleID = 1) AS '# Banana orders',
(SELECT COUNT(*) FROM TableOrders O WHERE O.UserID = TableOrders.UserID AND O.ArticleID = 2) AS '# Egg orders',
FROM TableUsers
LEFT JOIN TableOrders ON TableOrders.UserID = TableUsers.UserID
GROUP BY UserID, FirstName
HAVING Count(*) > 0;
But I'm getting an error saying the ArticleID needs to be in an aggregate function or group by clause. If I add the ArticleID to the group by clause, users with multiple orders are shown more than once...
Any help is appreciated!
Thank you!
Just use conditional aggregation:
SELECT o.UserID, o.FirstName, COUNT(*) AS num_orders,
SUM(CASE WHEN o.ArticleID = 1 THEN 1 ELSE 0 END) as num_bananas,
SUM(CASE WHEN o.ArticleID = 2 THEN 1 ELSE 0 END) as num_eggs
FROM TableUsers u JOIN
TableOrders o
ON o.UserID = u.UserID
GROUP BY UserID, FirstName;
Notes:
You want users with orders, so just use an inner join, not an outer join.
The HAVING clause is then not necessary.
Table aliases make the query easier to read and write.
Don't use single quotes for column aliases. It is better to name the columns so they don't need to be escaped.
I have a case expression in a stored procedure summing an account field, and then inserting into a user id. The logic works... until joining to another table.
I tried adding distinct counts, and additional tables to the query, but still when I join to another table it applies the 1 value when I want it to be 0 to the account.
This is the calculation in the stored proc;
INSERT INTO #SUMMARY_TEMP (USER_ID,FSN_CNT )
(SELECT USER_ID,
SUM(CASE WHEN A_GROUP_CD = 'RED' AND A_TYPE_CD = 'FSN' THEN REC_COUNT ELSE 0 END)
) AS 'FSN_CNT',
FROM (SELECT A_ACCOUNT_NBR,
A_USER_ID,
A_GROUP_CD,
A_TYPE_CD,
COUNT(*) AS REC_COUNT
FROM EXCEPTION_DETAIL
INNER JOIN #STAFF ON A_REPORT_DT = #REPORT_DT
AND (A_USER = B_USER_ID)
GROUP BY A_ACCOUNT_NBR,
A_USER_ID_ID,
A_GROUP_CD,
A_TYPE_CD) EXCEPTIONS
GROUP BY A_USER_ID,
A_ACCOUNT_NBR)
This is the result which is what I expect for 2 USER Ids
A_ACCOUNT_NBR USER_ID FSN_CNT
123456 HENRY 0
123498 HENRY 1
374933 JOE 1
474930 JOE 0
but when I join to another table the data looks like
A_ACCOUNT_NBR USER_ID FSN_CNT
123456 HENRY 1
123498 HENRY 1
374933 JOE 1
474930 JOE 1
Its applying the 1 value to account 123456 & 474930 when it should be 0.
I think its because the other table does not have the ACCOUNT_NBR column - I am joining on USER_ID and so it applies the 1 to all ACCOUNT_NBR from table A.
Thanks for all the suggestions, I tried using a CTE, and the counts now look good, but its created duplicate rows as shown below. Any suggestions on how to remove the duplication, below is the join I am using for the CTE;
select cte.*, jt.USER_ID
from cte
join EXCEPTION_DETAIL jt on cte.USER_ID=jt.USER_ID
USER ACCOUNT_NBR FSN_CNT
HENRY 123456 0
HENRY 123456 0
HENRY 123498 1
HENRY 123498 1
JOE 374933 1
JOE 374933 1
JOE 474930 0
JOE 474930 0
you can separate the 1st query by using cte and join with it next level like below
with cte as
(
(SELECT USER_ID,
SUM(CASE WHEN A_GROUP_CD = 'RED' AND A_TYPE_CD = 'FSN' THEN REC_COUNT ELSE 0 END)
) AS 'FSN_CNT',
FROM (SELECT A_ACCOUNT_NBR,
A_USER_ID,
A_GROUP_CD,
A_TYPE_CD,
COUNT(*) AS REC_COUNT
FROM EXCEPTION_DETAIL
INNER JOIN #STAFF ON A_REPORT_DT = #REPORT_DT
AND (A_USER = B_USER_ID)
GROUP BY A_ACCOUNT_NBR,
A_USER_ID_ID,
A_GROUP_CD,
A_TYPE_CD) EXCEPTIONS
GROUP BY A_USER_ID,
A_ACCOUNT_NBR)
) select cte.*,jt.USER_ID from cte join jointable_name jt on cte.USER_ID=jt.USER_ID
I have the following 3 tables;
Table_Names:
user_id Name
------------------
1 Mark
2 Tom
3 Ana
Table_Language:
language_id Language
-----------------------
1 English
2 German
Table_Name_Lang
id user_id language_id
---------------------------
1 1 1
2 1 2
3 2 1
4 3 2
How can I create a query to show the expected results like those below?
Name Expr_1_Eng Expr_1_Ger
---------------------------------
Mark English German
Tom English
Ana German
Thanks Tok
I would do:
select n.name,
max(case when l.language = 'English' then l.language end) as has_English,
max(case when l.language = 'German' then l.language end) as has_German
from names n join
name_lang nl
on nl.user_id = n.user_id join
lang l
on nl.language_id = l.language_id
group by n.name
You can try to use JOIN with condition aggregate function
SELECT Name,
MAX(CASE WHEN tnl.language_id = 1 then tl.Language end),
MAX(CASE WHEN tnl.language_id = 2 then tl.Language end)
FROM
Table_Name_Lang tnl
JOIN Table_Names tn on tnl.language_id = tn.language_id
JOIN Table_Language tl on tl.user_id = tnl.user_id
GROUP BY Name
I think you should use pivot:
select n.name,[0] as language_one,[1] as language_two,[2],[3] from
(select n.name,tl.language
tablename n
left join Table_Name_Lang tnl on n.userid=tnl.userid
left join table_language tl on tl.id=tnl.id
group by n.name,tl.language
)T
pivot
(
language
for name in [0],[1],[2],[3]
)AS PivotTable;
I have three tables PATH, ELEMENTS and ELEMENT_DETAILS with the following structure:
PATH:
ID
1
2
3
ELEMENTS:
ID | PATH_ID | DIRECTION | ELEMENT_DETAILS_ID
1 1 'left' 1
2 1 'right' 2
3 2 'left' 3
4 2 'right' 2
ELEMENT_DETAILS:
ID | NAME
1 'Henry'
2 'Mark'
3 'John'
I would like the result to be like so:
ID | left | right
1 'Henry' 'Mark'
2 'John' 'Mark'
This is the SQL I have come up with so far:
SELECT path.id,
CASE WHEN elements.direction='left' THEN element_details.name
ELSE NULL END
as left,
CASE WHEN elements.direction='right' THEN element_details.name
ELSE NULL END
as right,
FROM elements
INNER JOIN path on elements.path_id = path.id
LEFT JOIN element_details on elements.element_details_id = element_details.id
GROUP BY path.id
ORDER BY path.id
However, this does not work since postgres gives me an error saying elements.direction should be in group by. And including elements.direction in group_by does not give me aggregation at path.id level.
Stuck on this. Can someone please help.
I am using Postgres version 9.5
You need aggregation. Here is one method:
SELECT p.id,
MAX(CASE WHEN e.direction = 'left' THEN ed.name
END) as left,
MAX(CASE WHEN e.direction = 'right' THEN ed.name
END) as right
FROM elements e INNER JOIN
path p
ON e.path_id = p.id LEFT JOIN
element_details ed
ON e.element_details_id = ed.id
GROUP BY p.id
ORDER BY p.id
I have two tables with the following sample records in oracle database
1. staffs
inst_name name sid
ABC John 1
PQR Sam 2
ABC Tom 3
ABC Amit 4
PQR Jack 5
2. staffaccounts
sid account_no
1 4587
1 4588
2 4589
3 4581
3 4582
5 4583
5 4585
4 4586
Where I want the result like
inst_name account_type total
PQR SINGLE 1
ABC SINGLE 1
PQR DOUBLE 1
ABC DOUBLE 2
This can be achieved by a outer query, but I want to write a query where there is no outer query. Want to accomplish it in one single query.
SELECT
A .inst_name,
(
CASE COUNT (b.ac_no)
WHEN 1 THEN
'Single'
WHEN 2 THEN
'Double'
END
) account_type,
COUNT (A . NAME)
FROM
staffs A,
staffaccounts b
WHERE
A . s_id = b.s_id
GROUP BY
A .inst_name
The above query gives error ORA-00907: missing right parenthesis. Can it be done in single query or is outer query the only way out.
Oracle Version is 10g
May be something like this would work.
SELECT
A.inst_name,
CASE COUNT (b.account_no)
WHEN 1 THEN
'Single'
WHEN 2 THEN
'Double'
END account_type,
COUNT (A.name)
FROM
staffs A JOIN
staffaccounts b
ON
A.SID = b.sid
GROUP BY
A.inst_name , a.sid
ORDER BY 3;
You are grouping by inst_name, but this is not what you actually want, because you don't want a result row per inst_name, but per inst_name and account_type.
select
s.inst_name,
sa.account_type,
count(*) as total
from staffs s
join
(
select
sid,
case when count(*) = 1 then 'SINGLE' else 'DOUBLE' end as account_type
from staffaccounts
group by sid
having count(*) <= 2
) sa on sa.sid = s.sid
group by sa.account_type, s.inst_name
order by sa.account_type, s.inst_name;
You should learn how to properly use JOIN syntax. I prefer the explicit comparison syntax for CASE.
This may be what you want:
SELECT s.inst_name,
(CASE WHEN COUNT(sa.ac_no) = 1 THEN 'Single'
WHEN COUNT(sa.ac_no) = 2 THEN 'Double'
END) as account_type,
COUNT(*)
FROM staffs s JOIN
staffaccounts sa
ON s.SID = sa.sid
GROUP BY s.inst_name;
EDIT:
Now I see what you want:
SELECT s.inst_name,
(CASE WHEN cnt = 1 THEN 'Single'
WHEN cnt = 2 THEN 'Double'
END) as account_type,
COUNT(*)
FROM (SELECT s.*, COUNT(*) as cnt
FROM staffs s JOIN
staffaccounts sa
ON s.SID = sa.sid
GROUP BY s.id
) s
GROUP BY s.inst_name,
(CASE WHEN cnt = 1 THEN 'Single'
WHEN cnt = 2 THEN 'Double'
END);
I got only the way by using subquery but is in the easy way (more easier and readable) to achieve your requirement
SELECT inst_name, account_type, count(total) as total
FROM (
SELECT
a.inst_name,
CASE
WHEN COUNT (b.account_no) = 1 THEN 'Single'
WHEN COUNT (b.account_no) = 2 THEN 'Double'
END AS account_type,
COUNT (a.name) AS total
FROM staffs a
INNER JOIN staffaccounts b ON A . SID = b.sid
GROUP BY a.inst_name, a.sid) t GROUP BY inst_name, account_type
OUTPUT:
inst_name account_type total
ABC Double 2
PQR Double 1
ABC Single 1
PQR Single 1