Related
I have following tables and trying to PIVOT both parent and child as column headers. In my case "author and books". I am able to PIVOT either author or books at a time but not able to getting both at a time as comma separated with condition ( condition explained below). I have given sample data and output.author1 and author 2 column shows "AVG" of reviews along with color.
r=red &
y=yellow
In the sample output, we can see color as r & y. I have applied a condition here. if an author gets "r" at any time, Then output is always "r" otherwise "y". In my first case author1 gets y & r. so the output gives r. Other case "r" is not getting so "y" is displayed. If no color assigned it should be "NA"
user
Aid userName
1 author1
2 author2
books
bid NAME Aid
1 x 1
2 y 1
3 z 2
Location
loc_id Loc_name
1 UK
2 USA
3 Europe
UserAssign
uid Aid bid loc_d color reviews
1 1 1 1 y 12
2 1 2 1 r 14
3 2 3 1 y 11
4 1 1 2 y 10
5 2 3 2 y 112
Expected o/p
--------------------------------------------
Location author1 x y author2 z
Uk r,13 12 14 y,11 11
USA y,10 10 y,112 112
A useful query in the format you are requesting is not possible. When using PIVOT, the expected number of columns in the result should always be the same. In the format that you are displaying the data, additional columns would get added if there was a new book or a new author.
The query below will only work with the specified set of books and authors:
WITH
users (aid, username)
AS
(SELECT 1, 'author1' FROM DUAL
UNION ALL
SELECT 2, 'author2' FROM DUAL),
books (bid, name, aid)
AS
(SELECT 1, 'x', 1 FROM DUAL
UNION ALL
SELECT 2, 'y', 1 FROM DUAL
UNION ALL
SELECT 3, 'z', 2 FROM DUAL),
location (loc_id, loc_name)
AS
(SELECT 1, 'UK' FROM DUAL
UNION ALL
SELECT 2, 'USA' FROM DUAL
UNION ALL
SELECT 3, 'Europe' FROM DUAL),
UserAssign (u_UID,
Aid,
bid,
loc_id,
color,
reviews)
AS
(SELECT 1, 1, 1, 1, 'y', 12 FROM DUAL
UNION ALL
SELECT 2, 1, 2, 1, 'r', 14 FROM DUAL
UNION ALL
SELECT 3, 2, 3, 1, 'y', 11 FROM DUAL
UNION ALL
SELECT 4, 1, 1, 2, 'y', 10 FROM DUAL
UNION ALL
SELECT 5, 2, 3, 2, 'y', 112 FROM DUAL)
SELECT l.loc_name, p.*
FROM ( --authors
SELECT loc_id,
u.username AS colheader,
MIN (color) || ',' || AVG (ua1.reviews) AS avg_reviews
FROM userassign ua1 JOIN users u ON (ua1.aid = u.aid)
GROUP BY loc_id, u.username
UNION ALL
--books
SELECT loc_id, b.name, TO_CHAR (ua2.reviews) AS avg_reviews
FROM userassign ua2 JOIN books b ON (ua2.bid = b.bid))
PIVOT (MIN (avg_reviews)
FOR colheader
IN ('author1' AS author1,
'x' AS x,
'y' AS y,
'author2' AS author2,
'z' AS z)) p
JOIN location l ON (p.loc_id = l.loc_id)
ORDER BY l.loc_name;
LOC_NAME LOC_ID AUTHOR1 X Y AUTHOR2 Z
___________ _________ __________ _____ _____ __________ ______
UK 1 r,13 12 14 y,11 11
USA 2 y,10 10 y,112 112
Let’s say I have a list of baskets that can contain fruits with a certain weight:
Table baskets
(id, name)
----------------
1, 'apples, oranges and more'
2, 'apples and small oranges'
3, 'apples and bananas'
4, 'only oranges'
5, 'empty'
Table basket_fruits
(id, basket, fruit, weight)
----------------
1, 1, 'apple', 2
2, 1, 'apple', 3
3, 1, 'orange', 2
4, 1, 'banana', 2
5, 2, 'apple', 2
6, 2, 'orange', 1
7, 3, 'apple', 2
8, 3, 'banana', 2
9, 4, 'orange', 2
SQL Fiddle with this data
I’m struggling to come up with reasonably efficient queries for these two scenarios:
I want to fetch all baskets that contain at least one apple AND at least one orange, each above a given weight. So the expected result for weight >= 2 is
1, 'apples, oranges and more'
and for weight >= 1 it’s
1, 'apples, oranges and more'
2, 'apples and small oranges'
I want to fetch all baskets that contain no fruit above a given weight. So for weight >= 2 I would expect
5, 'empty'
and for weight >= 3 it should return
2, 'apples and small oranges'
3, 'apples and bananas'
4, 'only oranges'
5, 'empty'
The weight constraint is just a placeholder for "each sub-relation must meet certain constraints". In practice, we need to restrict the sub-relation by date range, status, etc. but I didn’t want to complicate the example any further.
(I’m using postgresql, in case the solution needs to be database-specific.)
I strongly recommend using group by and having for this purpose.
For your first question, this query should work:
SELECT b.name
FROM baskets b INNER JOIN
basket_fruits bf
ON b.id = bf.basket
GROUP BY b.name
HAVING SUM( (bf.fruit = 'apple' AND bf.weight >= 2)::int ) > 0 AND
SUM( (bf.fruit = 'orange' AND bf.weight >= 2)::int ) > 0 ;
The second is a little more complicated, because there are no rows. But a left join and coalesce() suffice so you can express it in the same format:
SELECT b.name
FROM baskets b LEFT JOIN
basket_fruits bf
ON b.id = bf.basket
GROUP BY b.name
HAVING SUM( (COALESCE(bf.weight, 0) >= 2)::int ) = 0
Here are my solutions so far:
All baskets containing both fruits with weight >= 2 (thanks to Gordon Linoff’s suggestions):
SELECT b.* FROM baskets b
INNER JOIN (
SELECT basket FROM basket_fruits
WHERE weight >= 2
GROUP BY basket
HAVING SUM((fruit = 'apple')::int) > 0 AND SUM((fruit = 'orange')::int) > 0
) bf ON b.id = bf.basket
SQL-Fiddle
All baskets without fruits with weight >= 2:
SELECT b.* FROM baskets b
LEFT JOIN (
SELECT basket, fruit FROM basket_fruits
WHERE weight >= 2
) bf ON b.id = bf.basket
WHERE fruit IS NULL
SQL-Fiddle
If anyone has more efficient ideas, I’d love to hear them.
SELECT b.name
FROM baskets b
INNER JOIN basket_fruits f
ON b.id = f.basket
GROUP BY b.name
HAVING SUM(f.wage) >= 3
OR b.id = 5
Is it what you expected?
Tables:
tblStudents:
ID, Student Name
1, John
2, Mark
3, Fred
tblEnrolledSubjects:
ID, Student ID, Subject ID, Score
1, 1, 1, 75
2, 1, 2, 75
3, 1, 3, 75
4, 1, 4, NULL
5, 2, 1, 75
6, 3, 1, 75
7, 3, 2, 80
8, 3, 3, 85
tblSubject:
ID, Subject Name
1, Maths
2, English
3, Science
4, History
I want to return all distinct students that have a score of over (say 70) for all subjects. SELECT [Student Name]
Students can be enrolled in 1 or more subjects.
If a student has even one subject not of required score then they should not be listed.
From he above data I would expect to see
Mark
Fred
What would the SQL query be for this?
If a NULL value doesn't count against you, then
SELECT tblStudents.[Student Name]
FROM tblEnrolledSubjects RIGHT JOIN tblStudents
ON tblEnrolledSubjects.[Subject ID] = tblStudents.ID
GROUP BY tblStudents.[Student Name]
HAVING (Min(tblEnrolledSubjects.[Score])>=70);
SELECT t.Studentname
FROM tblStudents t JOIN
(select count(*) cnt, min(nz(score,0)) minscore, StudentID
FROM tblEnrolledSubjects
GROUP BY StudentID) s
ON t.Studentid = s.studentid
WHERE minscore>=70 --if mark is greater than or equal to 70
I have the next table tree:
id, name, boss, group
1, Boss 1, 9, false
2, Boss 2, 9, false
3, Group 1, 1, true
4, Group 2, 2, true
5, Employee 1, 3, false
6, Employee 2, 3, false
7, Employee 3, 3, false
8, Employee 4, 4, false
9, Boss 0, null, false
Which must be represented in the next way:
Boss 0
|___ Boss 1
| |-- Group 1
| |________ Employee 1
| |________ Employee 2
| |________ Employee 3
|___ Boss 2
|-- Group 2
|________ Employee 4
I can get this result:
id, name, level
9, Boss 0, 1
1, Boss 1, 2
2, Boss 2, 2
3, Group 1, 3
4, Group 2, 3
5, Employee 1, 4
6, Employee 2, 4
7, Employee 3, 4
8, Employee 4, 4
using the next query:
WITH RECURSIVE t(id, name, boss, level, group) AS
(
SELECT
p1.id,
p1.name,
p1.boss,
1 as level,
p1.group
FROM tree as p1
WHERE p1.boss is null
UNION ALL
SELECT p2.id,
p2.name,
p2.boss,
CASE WHEN p2.group = true THEN level + 1
WHEN p2.group is null THEN level
END,
p2.group
FROM tree as p2
INNER JOIN t on p2.boss = t.id
)
SELECT * FROM t WHERE t.group is null
However, what I need to get is the next information: how many people are directly and indirectly below under each employee? For example:
Boss 0:
2 Direct
4 Indirect
That is, what I am looking for is something like this:
id, name, level
9, Boss 0, 1
1, Boss 1, 2
2, Boss 2, 2
3, Group 1, 3
4, Group 2, 3
5, Employee 1, 3
6, Employee 2, 3
7, Employee 3, 3
8, Employee 4, 3
What can I do in this case? Do you think is better idea to use the nested set model for this kind of problem?
You don't specify RDBMS so I use SQL Server:
SqlFiddleDemo
WITH t(id, name, boss, [level], [group]) AS
(
SELECT
p1.id,
p1.name,
p1.boss,
1 as [level],
p1.[group]
FROM tree as p1
WHERE p1.boss IS NULL
UNION ALL
SELECT
p2.id,
p2.name,
p2.boss,
CASE WHEN t.[group] = 0 THEN [level] + 1
ELSE [level]
END,
p2.[group]
FROM tree as p2
JOIN t
ON p2.boss = t.id
)
SELECT *
FROM t
I have a set of data as below, showing the history of who has done what with a record. The unique identifier for each record is shown in 'ID' and 'Rec No' is the sequential number assigned to each interaction with the record.
ID Rec No Who Type
1 1 Bob New
1 2 Bob Open
1 3 Bob Assign
1 4 Sarah Add
1 5 Bob Add
1 6 Bob Close
2 1 John New
2 2 John Open
2 3 John Assign
2 4 Bob Assign
2 5 Sarah Add
2 6 Sarah Close
3 1 Sarah New
3 2 Sarah Open
3 3 Sarah Assign
3 4 Sarah Close
I need to find all of the 'Assign' operations. However where multiple 'Assign' are in a certain ID, I want to find the first one. I then also want to find the name of the person who did that.
So ultimately from the above date I would like the output to be-
Who Count (assign)
Bob 1
John 1
Sarah 1
The code I have at the moment is-
SELECT IH.WHO, Count(IH.ID)
FROM Table.INCIDENTS_H IH
WHERE (IH.TYPE = Assign)
GROUP BY IH.WHO
But this gives the output as-
Who Count (assign)
Bob 2
John 1
Sarah 1
As it is finding that Bob did an assign on ID 2, Rec No 4.
Any help would be appreciated. I am using MS SQL.
I think something like this is what you are after:
select
who, count(id)
from (
select ID, Who, row_number() over (partition by ID order by Rec) [rownum]
from Table.INCIDENTS_H IH
WHERE (IH.TYPE = Assign)
) a
where rownum = 1
group by who
This should count only the first Assign (ordered by Rec) within each ID group.
This ought to do it:
SELECT IH.WHO, COUNT(IH.ID)
FROM INCIDENTS_H IH
JOIN (
SELECT ID, MIN([Rec No]) [Rec No]
FROM INCIDENTS_H
WHERE ([Type] = 'Assign')
GROUP BY ID
) IH2
ON IH2.ID = IH.ID AND IH2.[Rec No] = IH.[Rec No]
GROUP BY IH.WHO
You can use row_number to accomplish this
WITH INCIDENTS_H as (
SELECT
1 as ID, 1 as RecNo, 'Bob' as Who, 'New' as type
UNION ALL SELECT 1, 2, 'Bob','Open'
UNION ALL SELECT 1, 3, 'Bob','Assign'
UNION ALL SELECT 1, 4, 'Sarah','Add'
UNION ALL SELECT 1, 5, 'Bob','Add'
UNION ALL SELECT 1, 6, 'Bob','Close'
UNION ALL SELECT 2, 1, 'John','New'
UNION ALL SELECT 2, 2, 'John','Open'
UNION ALL SELECT 2, 3, 'John','Assign'
UNION ALL SELECT 2, 4, 'Bob','Assign'
UNION ALL SELECT 2, 5, 'Sarah','Add'
UNION ALL SELECT 2, 6, 'Sarah','Close'
UNION ALL SELECT 3, 1, 'Sarah','New'
UNION ALL SELECT 3, 2, 'Sarah','Open'
UNION ALL SELECT 3, 3, 'Sarah','Assign'
UNION ALL SELECT 3, 4, 'Sarah','Close')
, GetTheMin AS (
SELECT
ROW_NUMBER() over (partition by id order by recno) row,
ID,
RecNo,
Who,
type
FROM
INCIDENTS_H
WHERE
type = 'Assign'
)
SELECT Who,
COUNT(ID)
FROM GetTheMin
WHERE
row = 1
GROUP BY
who
OR you can use CROSS Apply
SELECT
who,
COUNT(id) id
FROM
(SELECT DISTINCT
MinValues.*
FROM
INCIDENTS_H h
CROSS APPLY ( SELECT TOP 1 *
FROM INCIDENTS_H h2
WHERE h.id = h2.id
ORDER BY ID, RecNo asc) MinValues) getTheMin
GROUP BY WHO
Or you can use Min which uses standard SQL John Fisher's answer demonstrates
Here's a view of everything in the table which should match your "first assign" requirement:
select a.*
from Table.INCIDENTS_H a
inner join
(select ID, min([Rec No]) [Rec No] from Table.INCIDENTS_H where Type = 'Assign' group by ID) b
on a.ID = b.ID and a.[Rec No] = b.[Rec No]
Result:
ID Rec No Who Type
1 3 Bob Assign
2 3 John Assign
3 3 Sarah Assign
select * from
(select
id, rec_no, who
from
operation_history
where
type = 'Assign'
order by rec_no asc) table_alias
group by
id
order by id asc
Tested and here are the results:
id rec_no who
1 3 Bob
2 3 John
3 3 Sarah
(Code not specific to SQL Server)
Here is the query with virtual test data that were mentioned in the original post:
with T (ID, RecNo, Who, Type) as
(
select 1, 1, 'Bob', 'New' union all
select 1, 2, 'Bob', 'open' union all
select 1, 3, 'Bob', 'Assign' union all
select 1, 4, 'Sarah', 'Add' union all
select 1, 5, 'Bob', 'Add' union all
select 1, 6, 'Bob', 'Close' union all
select 2, 1, 'John', 'New' union all
select 2, 2, 'John', 'Open' union all
select 2, 3, 'John', 'Assign' union all
select 2, 4, 'Bob', 'Assign' union all
select 2, 5, 'Sarah', 'Add' union all
select 2, 6, 'Sarah', 'Close' union all
select 3, 1, 'Sarah', 'New' union all
select 3, 2, 'Sarah', 'Open' union all
select 3, 3, 'Sarah', 'Assign' union all
select 3, 4, 'Sarah', 'Close'
)
select top 1 with ties *
from T
where Type = 'Assign'
order by row_number() over(partition by ID order by RecNo)
The "select" statement that can be applied to the real situation from the question might look like:
SELECT TOP 1 WITH TIES
IH.ID, IH.[Rec No], IH.WHO, IH.TYPE
FROM Table.INCIDENTS_H IH
WHERE IH.TYPE = 'Assign'
ORDER BY ROW_NUMBER() OVER(PARTITION BY IH.ID ORDER BY IH.[Rec No]);