SQL referrals view - sql

I have a table with users:
id referred_by_user_id
-----------------------------------
1 null
2 1
3 1
4 2
I need to write request to get number of people referred by user in two levels.
First - direct referral (example: user 1 referred users 2 3. count for level 1 = 2)
Second - user 1 referred to users 2 and 3, user 2 referred to user 4. So count for level 2 should be 1
Result of query should be:
id referred_user_tier_one_total referred_user_tier_two_total
------------------------------------------------------------------------------
1 2 1
2 1 null
3 null null
4 null null
I figured out how to count referred_user_tier_one_total:
select
"id", referred_user_tier_one_total
from
"user"
inner join
(select
count(*) as referred_user_tier_one_total, referred_by_user_id
from
"user"
where
"user".referred_by_user_id is not null
group by
"user".referred_by_user_id) ur on "user".id = ur.referred_by_user_id
But I don't understand how to calculate referred_user_tier_two_total. Please, help
UPD:
Thanks #Stoff for SQL Server solution.
Here is the script rewritten for Postgres
WITH RECURSIVE agg AS
(
SELECT
a.ID, a.referred_by_user_id,
COUNT(b.referred_by_user_id) AS "count"
FROM
"user" a
LEFT JOIN
"user" b ON a.ID = b.referred_by_user_id
GROUP BY
a.ID, a.referred_by_user_id
)
SELECT
a.ID,
a.Count AS referred_user_tier_one_total,
CASE
WHEN SUM(b.count) IS NULL
THEN 0
ELSE SUM(b.count)
END AS referred_user_tier_two_total
FROM
agg a
LEFT JOIN
agg b ON a.ID = b.referred_by_user_id
GROUP BY
a.ID, a.Count
ORDER BY
a.ID

Here is a solution which works in Postgres.
We could carry on writing more levels in the same way.
create table referals(
id int,
referred_by_user_id int);
insert into referals values
(1 , null),
(2 , 1),
(3 , 1),
(4 , 2 );
select
t0.id,
count(t1.id) tier1,
count(t2.id) tier2
from
referals t0
left join referals t1
on t0.id = t1.referred_by_user_id
left join referals t2
on t1.id = t2.referred_by_user_id
group by t0.id
id | tier1 | tier2
-: | ----: | ----:
1 | 2 | 1
2 | 1 | 0
3 | 0 | 0
4 | 0 | 0
db<>fiddle here

Related

How to do group by in postgresql with multiple joins?

I need to group by dest_spot_id but couldn't able to get the desired result.
SELECT
ddsd.dest_spot_id
, ddsd.status
, ddsd.data_type
, ddsd.destination_id
, ddsd.created_on
, ddsd.data_id
, gi.tp_destination_name AS dest_or_spot_name
, mt.taluk_name
, md.district_name
, tgi.tp_destination_name AS destination_name
FROM dm_dest_spot_data ddsd
LEFT JOIN m_district md ON md.id_district = ddsd.district_id
LEFT JOIN m_taluk mt ON mt.id_taluk = ddsd.taluk
JOIN tp_general_info gi ON gi.id_tp_general_info = ddsd.dest_spot_id
LEFT JOIN tp_general_info tgi ON tgi.id_tp_general_info = ddsd.destination_id
GROUP BY
1
, 2
, 3
, 4
, 5
, 6
, 7
, 8
, 9
, 10
ORDER BY
ddsd.data_id
dest_spot_id have common_ids but i want get one from it.
lets say this is my table
dest_spot_id | status| so on
302 | 1 | 5
303 | 2 | 3
302 | 1 | 2
303 | 2 | 1
401 | 1 | 3
308 | 2 | 2
i want the outputs as :
dest_spot_id | status| so on
302 | 1 | 5
303 | 2 | 3
401 | 1 | 3
308 | 2 | 2
please give my some advice to achieve this. i am new to postgresql
You didn't specify what you want to do with the other columns, but let's say you want the maximum value of everything except the dest_spot_id:
SELECT
dest_spot_id
, MAX(other_colum1)
, MAX(other_colum2)
, …
FROM my_table
GROUP BY
dest_spot_id
So you only group by the column(s) that should be unique in the output.
For the other columns you can apply some aggregation function like MIN, MAX, SUM, AVG, etc.
It does not matter whether there are multiple table with JOINS or just one table.
The one thing that is different with multiple tables is that you have to qualify the column in the GROUP BY clause or use the fields position.
SELECT
t1.dest_spot_id
, MAX(t2.other_colum)
, MAX(t3.other_colum)
, …
FROM my_table1 t1
JOIN my_table2 t2 ON …
JOIN my_table3 t3 ON …
GROUP BY
t1.dest_spot_id
or
SELECT
t1.dest_spot_id
, MAX(t2.other_colum)
, MAX(t3.other_colum)
, …
FROM my_table1 t1
JOIN my_table2 t2 ON …
JOIN my_table3 t3 ON …
GROUP BY
1
here is my updated code after jens explanation. may it help someone..
SELECT
ddsd.dest_spot_id
,max(ddsd.status) as dest_spot_status
,max(gi.tp_destination_name)as dest_or_spot_name
,max(ddsd.created_on) as createdon
,max(mt.taluk_name) as taluk_name
,max(md.district_name) as district_name
,max(ddsd.data_type) as dataType
,max(ddsd.destination_id) as destination_idd
,max(tgi.tp_destination_name) as destination_name
,max(ddsd.data_id) as dataid
FROM dm_dest_spot_data ddsd
LEFT JOIN m_district md ON md.id_district = ddsd.district_id
LEFT JOIN m_taluk mt ON mt.id_taluk = ddsd.taluk
JOIN tp_general_info gi ON gi.id_tp_general_info = ddsd.dest_spot_id
LEFT JOIN tp_general_info as tgi ON tgi.id_tp_general_info = ddsd.destination_id
GROUP BY ddsd.dest_spot_id
ORDER BY max(ddsd.data_id) DESC

Need T-SQL query to get multiple choice answer if matches

Example:
Table Question_Answers:
+------+--------+
| q_id | ans_id |
+------+--------+
| 1 | 2 |
| 1 | 4 |
| 2 | 1 |
| 3 | 1 |
| 3 | 2 |
| 3 | 3 |
+------+--------+
User_Submited_Answers:
| q_id | sub_ans_id |
+------+------------+
| 1 | 2 |
| 1 | 4 |
| 2 | 1 |
| 3 | 1 |
| 3 | 2 |
| 3 | 4 |
+------+------------+
I need a T-SQL query if this rows matches count 1 else 0
SELECT
t1.q_id,
CASE WHEN COUNT(t2.sub_ans_id) = COUNT(*)
THEN 1
ELSE 0 END AS is_correct
FROM Question_Answers t1
LEFT JOIN User_Submited_Answers t2
ON t1.q_id = t2.q_id AND
t1.ans_id = t2.sub_ans_id
GROUP BY t1.q_id
Try the following code:
select qa.q_id,case when qa.ans_id=sqa.ans_id then 1 else 0 end as result from questionans qa
left join subquestionans sqa
on qa.q_id=sqa.q_id and qa.ans_id=sqa.ans_id
This should give you expected result for every question.
select q_id, min(Is_Correct)Is_Correct from (
select Q.q_id,case when count(A.sub_ans_id)=count(*) then 1 else 0 end as Is_Correct
from #Q Q left join #A A on Q.q_id=A.q_id and Q.ans_id=A.sub_ans_id
group by Q.q_id
UNION ALL
select A.q_id,case when count(Q.ans_id)=count(*) then 1 else 0 end as Is_Correct
from #Q Q right join #A A on Q.q_id=A.q_id and Q.ans_id=A.sub_ans_id
group by A.q_id ) I group by q_id
MySQL solution (sql fiddle):
SELECT tmp.q_id, MIN(c) as correct
FROM (
SELECT qa.q_id, IF(qa.q_id = usa.q_id, 1, 0) as c
FROM question_answers qa
LEFT JOIN user_submited_answers usa
ON qa.q_id = usa.q_id AND qa.ans_id = usa.sub_ans_id
UNION
SELECT usa.q_id, IF(qa.q_id = usa.q_id, 1, 0) as c
FROM question_answers qa
RIGHT JOIN user_submited_answers usa
ON qa.q_id = usa.q_id AND qa.ans_id = usa.sub_ans_id
) tmp
GROUP BY tmp.q_id;
Now, step by step explanation:
In order to get the right output we will need to:
extract from question_answers table the answers which were not filled in by the user (in your example: q_id = 3 with ans_id = 3)
extract from user_submited_answers table the wrong answers which were filled in by the user (in your example: q_id = 3 with sub_ans_id = 4)
To do that we can use a full outer join (for mysql left join + right join):
SELECT *
FROM question_answers qa
LEFT JOIN user_submited_answers usa
ON qa.q_id = usa.q_id AND qa.ans_id = usa.sub_ans_id
UNION
SELECT *
FROM question_answers qa
RIGHT JOIN user_submited_answers usa
ON qa.q_id = usa.q_id AND qa.ans_id = usa.sub_ans_id;
From the previous query results, the rows which we are looking for (wrong answers) contains NULL values (based on the case, in question_answers table or user_submited_answers table).
The next step is to mark those rows with 0 (wrong answer) using an IF or CASE statement: IF(qa.q_id = usa.q_id, 1, 0).
To get the final output we need to group by q_id and look for 0 values in the grouped rows. If there is at least one 0, the answer for that question is wrong and it should be marked as that.
Check sql fiddle: SQL Fiddle

Get count of related records in two joined tables

Firstly, I apologize for my English. I want get auctions with count of bids and buys. It should look like this:
id | name | bids | buys
-----------------------
1 | Foo | 4 | 1
2 | Bar | 0 | 0
I have tables like following:
auction:
id | name
---------
1 | Foo
2 | Bar
auction_bid:
id | auction_id
---------------
1 | 1
2 | 1
3 | 1
4 | 1
auction_buy:
id | auction_id
---------------
1 | 1
I can get numbers in two queries:
SELECT *, COUNT(abid.id) AS `bids` FROM `auction` `t` LEFT JOIN auction_bid abid ON (t.id = abid.auction) GROUP BY t.id
SELECT *, COUNT(abuy.id) AS `buys` FROM `auction` `t` LEFT JOIN auction_buy abuy ON (t.id = abuy.auction) GROUP BY t.id
But when i combined it into one:
SELECT *, COUNT(abid.id) AS `bids`, COUNT(abuy.id) AS `buys` FROM `auction` `t` LEFT JOIN auction_bid abid ON (t.id = abid.auction) LEFT JOIN auction_buy abuy ON (t.id = abuy.auction) GROUP BY t.id
It was returning wrong amount (bids as much as buys).
How to fix this and get counts in one query?
You'll need to count DISTINCT abuy and abid IDs to eliminate the duplicates;
SELECT t.id, t.name,
COUNT(DISTINCT abid.id) `bids`,
COUNT(DISTINCT abuy.id) `buys`
FROM `auction` `t`
LEFT JOIN auction_bid abid ON t.id = abid.auction_id
LEFT JOIN auction_buy abuy ON t.id = abuy.auction_id
GROUP BY t.id, t.name;
An SQLfiddle to test with.
Try this:
SELECT t.*,COUNT(abid.id) as bids,buys
FROM auction t LEFT JOIN
auction_bid abid ON t.id = abid.auction_id LEFT JOIN
(SELECT t.id, Count(abuy.id) as buys
FROM auction t LEFT JOIN
auction_buy abuy ON t.id = abuy.auction_id
GROUP BY t.id) Temp ON t.id=Temp.id
GROUP BY t.id
Result:
ID NAME BIDS BUYS
1 Foo 2 0
2 Bar 1 1
Result in SQL Fiddle.

selecting multiple counts when tables not directly co-relate

users table:
user_id (distinct for each user)
source_id (users may have the same source)
rule tables:
white_rules
black_rules
general_rules
these tables all look the same, and have:
victim_id (co-relates to user_id from users table).
rule_id (co-relates to a different table which is not important here)
What i need is to extract the amount of total rules per-type (white,black,general) per-source_id.
example:
source_id: 5 ---> total 70 white rules, total 32 black rules, total 21 general rules
source_id: 7 ---> total 2 white rules, total 0 black rules, total 4 general rules
and so on... for all distinct sources that are listed on users table.
what i tried is:
SELECT source_id,
count(w.victim_id) as total_white,
count(b.victim_id) as total_black,
count(g.victim_id) as total_general
from users
LEFT JOIN white_rules as w ON (user_id=w.victim_id)
LEFT JOIN black_rules as b ON (user_id=b.victim_id)
LEFT JOIN general_rules as g ON (user_id=g.victim_id)
where deleted='f' and source is not null
group by source;
but the result table I get has wrong (higher) numbers than what I expect to get,
so I must be doing something wrong :)
would appreciate any hinge in the right direction.
You need to do your counts in subqueries, or count distinct, as your multiple 1 to many relationships are causing cross joining. I don't know your data but imagine this scenario:
Users:
User_ID | Source_ID
--------+--------------
1 | 1
White_Rules
Victim_ID | Rule_ID
----------+-------------
1 | 1
1 | 2
Black_Rules
Victim_ID | Rule_ID
----------+-------------
1 | 3
1 | 4
If you run
SELECT Users.User_ID,
Users.Source_ID,
White_Rules.Rule_ID AS WhiteRuleID,
Black_Rules.Rule_ID AS BlackRuleID
FROM Users
LEFT JOIN White_Rules
ON White_Rules.Victim_ID = Users.User_ID
LEFT JOIN Black_Rules
ON Black_Rules.Victim_ID = Users.User_ID
You will get all combinations of White_Rules.Rule_ID and Black_Rules.Rule_ID:
User_ID | Source_ID | WhiteRuleID | BlackRuleID
--------+-----------+-------------+-------------
1 | 1 | 1 | 3
1 | 1 | 2 | 4
1 | 1 | 1 | 3
1 | 1 | 2 | 4
So counting the results will return 4 white rules and 4 black rules, even though there are only 2 of each.
You should get the required results if you change your query to this:
SELECT Users.Source_ID,
SUM(COALESCE(w.TotalWhite, 0)) AS TotalWhite,
SUM(COALESCE(b.TotalBlack, 0)) AS TotalBlack,
SUM(COALESCE(g.TotalGeneral, 0)) AS TotalGeneral
FROM Users
LEFT JOIN
( SELECT Victim_ID, COUNT(*) AS TotalWhite
FROM White_Rules
GROUP BY Victim_ID
) w
ON w.Victim_ID = Users.User_ID
LEFT JOIN
( SELECT Victim_ID, COUNT(*) AS TotalBlack
FROM Black_Rules
GROUP BY Victim_ID
) b
ON b.Victim_ID = Users.User_ID
LEFT JOIN
( SELECT Victim_ID, COUNT(*) AS TotalGeneral
FROM General_Rules
GROUP BY Victim_ID
) g
ON g.Victim_ID = Users.User_ID
WHERE Deleted = 'f'
AND Source IS NOT NULL
GROUP BY Users.Source_ID
Example on SQL Fiddle
An alternative would be:
SELECT Users.Source_ID,
COUNT(Rules.TotalWhite) AS TotalWhite,
COUNT(Rules.TotalBlack) AS TotalBlack,
COUNT(Rules.TotalGeneral) AS TotalGeneral
FROM Users
LEFT JOIN
( SELECT Victim_ID, 1 AS TotalWhite, NULL AS TotalBlack, NULL AS TotalGeneral
FROM White_Rules
UNION ALL
SELECT Victim_ID, NULL AS TotalWhite, 1 AS TotalBlack, NULL AS TotalGeneral
FROM Black_Rules
UNION ALL
SELECT Victim_ID, NULL AS TotalWhite, NULL AS TotalBlack, 1 AS TotalGeneral
FROM General_Rules
) Rules
ON Rules.Victim_ID = Users.User_ID
WHERE Deleted = 'f'
AND Source IS NOT NULL
GROUP BY Users.Source_ID
Example on SQL Fiddle

Sequence grouping in TSQL

I'm trying to group data in sequence order. Say I have the following table:
| 1 | A |
| 1 | A |
| 1 | B |
| 1 | B |
| 1 | C |
| 1 | B |
I need the SQL query to output the following:
| 1 | A | 1 |
| 1 | A | 1 |
| 1 | B | 2 |
| 1 | B | 2 |
| 1 | C | 3 |
| 1 | B | 4 |
The last column is a group number that is incremented in each group. The important thing to note is that rows 3, 4 and 5 contain the same data which should be grouped into 2 groups not 1.
For MSSQL2008:
Suppose you have a SampleStatuses table:
Status Date
A 2014-06-11
A 2014-06-14
B 2014-06-25
B 2014-07-01
A 2014-07-06
A 2014-07-19
B 2014-07-21
B 2014-08-13
C 2014-08-19
you write the following:
;with
cte as (
select top 1 RowNumber, 1 as GroupNumber, [Status], [Date] from SampleStatuses order by RowNumber
union all
select c1.RowNumber,
case when c2.Status <> c1.Status then c2.GroupNumber + 1 else c2.GroupNumber end as GroupNumber, c1.[Status], c1.[Date]
from cte c2 join SampleStatuses c1 on c1.RowNumber = c2.RowNumber + 1
)
select * from cte;
you get this result:
RowNumber GroupNumber Status Date
1 1 A 2014-06-11
2 1 A 2014-06-14
3 2 B 2014-06-25
4 2 B 2014-07-01
5 3 A 2014-07-06
6 3 A 2014-07-19
7 4 B 2014-07-21
8 4 B 2014-08-13
9 5 C 2014-08-19
The normal way you would do what you want is the dense_rank function:
select key, val,
dense_rank() over (order by key, val)
from t
However, this does not address the problem of separating the last groups.
To handle this, I have to assume there is an "id" column. Tables, in SQL, do not have an ordering, so I need the ordering. If you are using SQL Server 2012, then you can use the lag() function to get what you need. Use the lag to see if the key, val pair is the same on consecutive rows:
with t1 as (
select id, key, val,
(case when key = lead(key, 1) over (order by id) and
val = lead(val, 1) over (order by id)
then 1
else 0
end) as SameAsNext
from t
)
select id, key, val,
sum(SameAsNext) over (order by id) as GroupNum
from t
Without SQL Server 2012 (which has cumulative sums), you have to do a self-join to identify the beginning of each group:
select t.*,
from t left outer join
t tprev
on t.id = t2.id + 1 and t.key = t2.key and t.val = t2.val
where t2.id is null
With this, assign the group as the minimum id using a join:
select t.id, t.key, t.val,
min(tgrp.id) as GroupId
from t left outer join
(select t.*,
from t left outer join
t tprev
on t.id = t2.id + 1 and t.key = t2.key and t.val = t2.val
where t2.id is null
) tgrp
on t.id >= tgrp.id
If you want these to be consecutive numbers, then put them in a subquery and use dense_rank().
This will give you rankings on your columns.
It will not give you 1,2,3 however.
It will give you 1,3,6 etc based on how many in each grouping
select
a,
b,
rank() over (order by a,b)
from
table1
See this SQLFiddle for a clearer idea of what I mean: http://sqlfiddle.com/#!3/0f201/2/0