Multiple queries resulting in minimum number of occurrences - sql

I need to find the smallest number of documents retrieved by any subject based on exp_condition. Exp_condition from the subjects table contains a '1' and a '2' column.
Here are the tables:
subjects table:
+-----------------+--------------+
| Field | Type |
+-----------------+--------------+
| username | varchar(255) |
| user_type | varchar(10) |
| years | int |
| low_grade | int |
| high_grade | int |
| on_line | varchar(10) |
| on_line_sources | varchar(255) |
| location | varchar(5) |
| exp_condition | int |
+-----------------+--------------+
tasks table:
+------------+--------------+
| Field | Type |
+------------+--------------+
| username | varchar(255) |
| task | varchar(5) |
| confidence | int |
| sim_helpd | int |
+------------+--------------+
docs table:
+--------------+--------------+
| Field | Type |
+--------------+--------------+
| username | varchar(255) |
| task | varchar(5) |
| doc_type | varchar(10) |
| used_tool | int |
| relevant | int |
| motivational | int |
| concepts | int |
| background | int |
| grade_level | int |
| hands_on | int |
| attachments | int |
+--------------+--------------+
I'm able to generate the number of subjects and number of documents for both exp_condition values. I'm allowed to use multiple queries, but I'm not sure how.
Code for generating number of subjects for exp_condition 1 and 2:
select count(distinct(t2.username))
from tasks as t1
inner join subjects as t2
on t1.username = t2.username group by exp_condition;
Code for generating number of documents for exp_condition 1 and 2:
select count(*), exp_condition
from docs as t1
left join subjects as t2
on t1.username = t2.username
group by exp_condition;
Expected output: two separate numbers for smallest number of documents retrieved by any subject based on exp_condition.
Thanks in advance.

You can use a subquery or a CTE
SubQuery
SELECT exp_condition, MIN(A) as Tasks, MIN(B) as Docs FROM (
SELECT exp_condition, COUNT(DISTINCT t2.username) A, COUNT(DISTINCT (t3.username) B
FROM subjects s
LEFT JOIN tasks T2 ON s.username = t2.username
LEFT JOIN docs T3 ON s.username = t3.username
GROUP BY exp_condition
) A
GROUP BY ex_condition
CTE
;WITH CTE AS (
SELECT exp_condition, COUNT(DISTINCT t2.username) A, COUNT(DISTINCT (t3.username) B
FROM subjects s
LEFT JOIN tasks T2 ON s.username = t2.username
LEFT JOIN docs T3 ON s.username = t3.username
GROUP BY exp_condition
)
SELECT exp_condition, MIN(A) as Tasks, MIN(B) as Docs
FROM CTE
GROUP BY ex_condition

Related

How to join a polymorphic table with its child tables?

I am sorry about not being able to articulate the title of the question or the description of this question better. However, I will give the schema, sample data and expected result. Please help me write a query for such a use case.
Schema of restaurants
id
name
item_type
item_id
Schema of foods
id
name
Schema of food_items
id
name
food_id
Sample data in restaurants
|---------------------|------------------|---------------------|------------------|
| id | name | item_type | item_id |
|---------------------|------------------|---------------------|------------------|
| 1 | Apple Crushers | food_items | 1 |
|---------------------|------------------|---------------------|------------------|
| 2 | Retro Cafe | foods | 2 |
|---------------------|------------------|---------------------|------------------|
| 3 | Fruit Mania | foods | 1 |
|---------------------|------------------|---------------------|------------------|
| 4 | Meat and Eat | NULL | NULL |
|---------------------|------------------|---------------------|------------------|
Sample data in foods:
|---------------------|------------------|
| id | Name |
|---------------------|------------------|
| 1 | Fruits |
|---------------------|------------------|
| 2 | Chocolates |
|---------------------|------------------|
Sample data in food_items
|---------------------|------------------|---------------------|
| id | Name | food_id |
|---------------------|------------------|---------------------|
| 1 | Apple | 1 |
|---------------------|------------------|---------------------|
| 2 | Mango | 1 |
|---------------------|------------------|---------------------|
I need to write a query such that I get this as my result.
|---------------------|------------------|---------------------|------------------|---------------------|------------------|
| r_id | r_name | food_id | food_name | food_item_id | food_item_name |
|---------------------|------------------|---------------------|------------------|---------------------|------------------|
| 1 | Apple Crushers | 1 | Fruit | 1 | Apple |
|---------------------|------------------|---------------------|------------------|---------------------|------------------|
| 2 | Retro Cafe | 2 | Chocolates | NULL | NULL |
|---------------------|------------------|---------------------|------------------|---------------------|------------------|
| 3 | Fruit Mania | 1 | Fruit | NULL | NULL |
|---------------------|------------------|---------------------|------------------|---------------------|------------------|
| 4 | Meat and Eat | NULL | NULL | NULL | NULL |
|---------------------|------------------|---------------------|------------------|---------------------|------------------|
p.s: It will also be very helpful if someone could come up with an appropriate title and description for this problem. I am lost for words to describe this.
You must join the food table twice and use COALESCE:
select
r.id,
r.name,
coalesce(f.id, fif.id) as food_id,
coalesce(f.name, fif.name) as food_name,
fi.id as food_item_id,
fi.name as food_item_name
from restaurants r
left join foods f on f.id = r.item_id and r.item_type = 'foods'
left join food_items fi on fi.id = r.item_id and r.item_type = 'food_items'
left join foods fif on fif.id = fi.food_id
order by r.id;
select r.id, r.name, foods.id, foods.name, food_items.id, food_items.name from restaurents as r
left join food_items on r.item_type = 'food_items' and r.item_id = food_items.id
left join foods on (r.item_type = 'foods' and r.item_id = foods.id) or (r.item_type = 'foods' and r.item_id = food_items.food_id)
This might be having some syntax issues related to table names, but it should work.
For your database, I would suggest another data model based on compound keys. This would guarantee data consistency and makes queries a tad simpler:
food_group
(
food_group_no integer not null,
name varchar(100) not null,
primary key (food_group_no)
);
food
(
food_group_no integer not null,
food_no integer not null,
name varchar(100) not null,
primary key (food_group_no, food_no)
);
restaurant
(
restaurant_no integer not null,
name varchar(100) not null,
food_group_no integer not null,
food_no integer null,
primary key (restaurant_id),
foreign key (food_group_no) references food_group(food_group_no),
foreign key (food_group_no, food_no) references food(food_group_no, food_no)
);
The query:
select
r.restaurant_no,
r.name as restaurant_name,
fg.food_group_no,
fg.name as food_group_name,
f.food_id,
f.name as food_name
from restaurants r
join food_group fg on fg.id = r.food_group_no
left join food f on f.food_group_no = r.food_group_no and f.food_no = r.food_no
order by r.id;

Getting wrong data from relation

I have 2 tables:
DesignGroup table:
+--------------------------------------+--------+
| DesignGroupId | Name |
+--------------------------------------+--------+
| 9D32C543-24EA-497E-918E-387C8A66BF1A | Group1 |
| 532C543E-24EA-497E-918E-387C8A66BF1A | Group2 |
+--------------------------------------+--------+
Design table:
+-----------+---------------+--------------------------------------+
| DesignKey | Name | DesignGroupId |
+-----------+---------------+--------------------------------------+
| 1 | Design | 9D32C543-24EA-497E-918E-387C8A66BF1A |
| 2 | Design | 9D32C543-24EA-497E-918E-387C8A66BF1A |
| 3 | AnotherDesign | 532C543E-24EA-497E-918E-387C8A66BF1A |
+-----------+---------------+--------------------------------------+
As you can see multiple designs can have same DesignGroupId, So I make a query like:
DECLARE #DesignName VARCHAR(255) = 'Design'
SELECT
[D].[Name] AS [Display],
[D].[DesignKey] AS [Value]
FROM
[Design] AS [D]
JOIN
[DesignGroup] AS [DG] ON [D].[DesignGroupId] = [DG].[DesignGroupId]
GROUP BY
[D].[Name], [D].[DesignKey];
My desire result of this, is get only one value one [Display] and one [Value] of each DesignGroupId, so my desire result there was:
+---------------+-------+
| Display | Value |
+---------------+-------+
| Design | 1 |
| AnotherDesign | 3 |
+---------------+-------+
But I'm getting result foreach DesignKey like:
+---------------+-------+
| Display | Value |
+---------------+-------+
| Design | 1 |
| Design | 2 |
| AnotherDesign | 3 |
+---------------+-------+
How can I solve this? Regards
I don't see why you need to join the tables.
The results you want depend only on the data from the table [Design].
So GROUP BY [Name] and get the minimum value of [DesignKey]:
SELECT
[Name] AS [Display],
MIN([DesignKey]) AS [Value]
FROM [Design]
GROUP BY [Name];
But this will be fine if the table has only one value for DesignGroupId.
What do you need as a result for the other DesignGroupIds?
Maybe GROUP BY [DesignGroupId], [Name]:
SELECT
[DesignGroupId],
[Name] AS [Display],
MIN([DesignKey]) AS [Value]
FROM [Design]
GROUP BY [DesignGroupId], [Name];
Is your objective to get the minimum DesignKey value for each Name (Display) and Group? You can wrap your result into a minimum value outer query:
SELECT DISPLAY, MIN(VALUE) from (
SELECT
[D].[Name] AS [Display],
[D].[DesignKey] AS [Value]
FROM
[Design] AS [D]
JOIN
[DesignGroup] AS [DG] ON [D].[DesignGroupId] = [DG].[DesignGroupId]
GROUP BY
[D].[Name], [D].[DesignKey])
GROUP BY Display;

SQL Server : Using recursive CTE to resolve group membership

I have one table (users_groups) :
+-----------+------------+---------+
| groupGUID | memberGUID | isGroup |
+-----------+------------+---------+
| 32AB160C | 5B277276 | 0 |
| 32AB160C | 0A023D1D | 0 |
| 5C952B2E | 32AB160C | 1 |
| 4444FTG5 | 5C952B2E | 1 |
+-----------+------------+---------+
isGroup column indicates whether memberGUID is a group or not.
I want to obtain a new table (new_users_groups) with all group memberships resolved :
+-----------+------------+
| groupGUID | memberGUID |
+-----------+------------+
| 32AB160C | 5B277276 |
| 32AB160C | 0A023D1D |
| 5C952B2E | 5B277276 |
| 5C952B2E | 0A023D1D |
| 4444FTG5 | 5B277276 |
| 4444FTG5 | 0A023D1D |
+-----------+------------+
For now, I'm doing everything manually :
Looking for all group's memberGUID
SELECT * FROM users_groups WHERE isGroup = 1;
For all groups returned by previous step, find its members
SELECT * FROM users_groups WHERE groupGUID = '5C952B2E'
If members are not groups, insert them into a new table
INSERT INTO new_users_groups (groupGUID, memberGUID) VALUES ('5C952B2E', '5B277276');
INSERT INTO new_users_groups (groupGUID, memberGUID) VALUES ('5C952B2E', '0A023D1D');
If members are groups, go to step 2.
How can I automate this? Maybe with a Recursive CTE ?
You can do this with a recursive CTE:
with cte as (
select ug.groupGUID, ug.groupGUID as grp, ug.memberGUID
from user_groups ug
where isGroup = 0
union all
select ug.groupGUID, ug.groupGUID as grp, cte.memberGUID
from user_groups ug join
cte
on cte.grp = ug.memberGUID
)
select groupGUID, memberGUID
from cte;
Here is a Rextester.
Try CROSS JOIN
SELECT t1.groupGUID, t2.memberGUID
FROM temp t1, temp t2 WHERE t2.isGroup = 0
GROUP BY t1.groupGUID, t2.memberGUID

Get nth level on self-referencing table

I have a self-referencing table which has at max 5 levels
groupid | parentid | detail
--------- | --------- | ---------
Group A | Highest | nope
Group B | Group A | i need this
Highest | NULL | nope
Group C | Group B | nope
Group D | Group C | nope
I have a transaction table which lookups to the groupid on the table above to retrieve the detail value where groupid = Group B. The values of the groupid on the transaction table is only between Group B to D and will never go any higher.
txnid | groupid | desired | desired
--------- | --------- | --------- | ---------
1 | Group D | Group B | i need this
2 | Group B | Group B | i need this
3 | Group C | Group B | i need this
4 | Group B | Group B | i need this
How should my T-SQL script be like to attain the desired column? I can left join to the self referencing table multiple times to get until group B it's not consistent on how many time I need to join back.
Greatly appreciate any thoughts!
Still not clear to me how do you know which is the GROUP B, I suppose it's the record where the parent of it parent is null.
create table org(groupid char(1), parentid char(1), details varchar(20));
insert into org values
('a', null, 'nope'),('b', 'a', 'I need this'),('c', 'b', 'nope'),('d', 'c', 'nope'),('e', 'd', 'nope');
create table trans(id int, groupid char(1));
insert into trans values
(1, 'b'),(2, 'c'),(3, 'c'),(4, 'd'),(5, 'e');
GO
10 rows affected
with all_levels as
(
select ob.groupid groupid_b, oc.groupid groupid_c,
od.groupid groupid_d, oe.groupid groupid_e,
ob.details
from org ob
inner join org oc
on oc.parentid = ob.groupid
inner join org od
on od.parentid = oc.groupid
inner join org oe
on oe.parentid = od.groupid
where ob.parentid is not null
) select * from all_levels;
GO
groupid_b | groupid_c | groupid_d | groupid_e | details
:-------- | :-------- | :-------- | :-------- | :----------
b | c | d | e | I need this
--= build a 4 levels row
with all_levels as
(
select ob.groupid groupid_b, oc.groupid groupid_c,
od.groupid groupid_d, oe.groupid groupid_e,
ob.details
from org ob
inner join org oc
on oc.parentid = ob.groupid
inner join org od
on od.parentid = oc.groupid
inner join org oe
on oe.parentid = od.groupid
where ob.parentid is not null
)
--= no matter what groupid returns b group details
, only_b as
(
select groupid_b as groupid, groupid_b, details from all_levels
union all
select groupid_c as groupid, groupid_b, details from all_levels
union all
select groupid_d as groupid, groupid_b, details from all_levels
union all
select groupid_e as groupid, groupid_b, details from all_levels
)
--= join with transactions table
select id, t.groupid, groupid_b, ob.details
from trans t
inner join only_b ob
on ob.groupid = t.groupid;
GO
id | groupid | groupid_b | details
-: | :------ | :-------- | :----------
1 | b | b | I need this
2 | c | b | I need this
3 | c | b | I need this
4 | d | b | I need this
5 | e | b | I need this
dbfiddle here
You can deal with a recursive function too, but I don't believe it can be better on terms of performance.
create function findDetails(#groupid char(1))
returns varchar(100)
as
begin
declare #parentid char(1) = '1';
declare #next_parentid char(1) = '1';
declare #details varchar(100) = '';
while #next_parentid is not null
begin
select #details = org.details, #parentid = org.parentid, #next_parentid = op.parentid
from org
inner join org op
on op.groupid = org.parentid
where org.groupid = #groupid
set #groupid = #parentid;
end
return #details;
end
GO
✓
select id, groupid, dbo.findDetails(groupid) as details_b
from trans;
GO
id | groupid | details_b
-: | :------ | :----------
1 | b | I need this
2 | c | I need this
3 | c | I need this
4 | d | I need this
5 | e | I need this
dbfiddle here

How to make select with extra columns that depends on existing ones

There goes example:
select * from table
and got
ID | NAME | MAIL | BOSS_ID |
1 | Mike | mike#mike.com | 2 |
2 | Josh | josh#hotmail.com | null |
What I actually want to do is make SELECT statement (somehow?) to show two more columns like:
ID | NAME | MAIL | BOSS_ID | BOSS_NAME | BOSS_MAIL
1 | Mike | mike#dfsfs.com | 2 | Josh | josh#dsa.com
2 | Josh | josh#dfsa.com | null | null | null
I know it looks silly, but that what I exactly have been asked to do...
All hints are much appreciatted!
You do an outer join to the table itself on ID and BOSS_ID.
SQL Fiddle
MS SQL Server 2012 Schema Setup:
create table YourTable
(
ID int,
Name varchar(10),
Mail varchar(20),
BOSS_ID int
)
insert into YourTable values
(1 , 'Mike' , 'mike#mike.com' , 2),
(2 , 'Josh' , 'josh#hotmail.com' , null)
Query 1:
select T1.ID,
T1.Name,
T1.Mail,
T2.Name as BossName,
T2.Mail as BossMail
from YourTable as T1
left outer join YourTable as T2
on T1.BOSS_ID = T2.ID
Results:
| ID | NAME | MAIL | BOSSNAME | BOSSMAIL |
|----|------|------------------|----------|------------------|
| 1 | Mike | mike#mike.com | Josh | josh#hotmail.com |
| 2 | Josh | josh#hotmail.com | (null) | (null) |
You cannot SELECT columns that do not exist. You will need to add them. You would want an ALTER TABLE statement like this:
ALTER TABLE yourtable ADD BOSS_NAME VARCHAR( 255 )