Count similar records - sql

How do I write an SQL showing the number of films member 1 and 2 have in common?
MID=Member ID
FID=Film ID
Explanation of table below: Member 1 has films 2,3,5,17,21 on his list. Member 2 has films 5,14,18 on his list.
1. MID ------ | FID
2. M000001 | F000002
3. M000001 | F000003
4. M000001 | F000005
5. M000001 | F000021
6. M000002 | F000005
7. M000002 | F000014
8. M000002 | F000018
9. M000003 | F000001
10. M000003 | F000004
11. M000003 |F000024
*sorry for the poor table.

If you want the number for all pairs, just do a self join:
select t1.mid, t2.mid, count(*)
from table as t1 inner join
table as t2
on t1.fid = t2.fid and t1.mid < t2.mid
group by t1.mid, t2.mid;
If you want to limit this just to two members, you can do:
select count(*)
from table as t1 inner join
table as t2
on t1.fid = t2.fid and
t1.mid = 1 and
t2.mid = 2;

With a subquery is one way.
SELECT COUNT(*) FROM t1 t
WHERE EXISTS (SELECT 1 FROM t1 WHERE Mid = 2 AND Fid = t.Fid)
AND Mid = 1

Related

SQL COUNT conditional on the results another table

I am new to SQL and I'm having hard time with this scenario.
Table 1 contains information when a player was on a team.
Table1
playerID | yearID
----------------------
Player1 | 2000
Player1 | 2001
Player2 | 2000
Table 2 contains ballot information with the Hall of Fame. A player can be on multiple times.
playerID | BallotYear | Inducted
---------------------------------
Player1 | 2010 | N
Player1 | 2011 | N
Player2 | 2010 | Y
I am trying to count how many times a player shows up on Table1 conditional on them never being inducted based on Table2.
Desired Result
playerID | Count on Table 1
---------------------------
Player1 | 2
The issue I have come across is that Player1 returns 4, instead of 2. I have looked into a number of different functions, but I keep getting the same results. This is what I have come up with.
select Table1.playerID, count(Table1.playerID)
from Table1
join Table2 on Table1.playerID = Table2.playerID
where Table2.inducted = 'N'
group by Table1.playerID;
If I understand correctly, you want to filter out players that have ever had a 'Y' in the inducted field. If so:
select t1.playerId, count(*)
from table1 t1
where not exists (select 1
from table2 t2
where t2.playerID = t1.playerID and t2.inducted = 'Y'
)
group by t1.playerId;
You can achieve this using Left join which will also add to performance of query
select t1.playerId, count(*)
from table1 t1 left join
(select distinct playerID
from table2
where inducted = 'Y'
) t2 on t1.playerID = t2.playerID
where t2.playerId is null
group by t1.playerId

Postgresql COALESCE does not set default value

I have two tables:
tcars
id | name | car_price
----|---------------------|------------
1 |First_car_name | 1000
2 |Second_car_name | 1200
tcar_optionals
id | id_car | spec | opt_included |price
----|----------|------|-------------------------
1 | 2 |Spec1 | true | 500
2 | 2 |Spec2 | true | 100
3 | 2 |Spec3 | false | 500
4 | 2 |Spec4 | true | 0
5 | 1 |Spec5 | false | 500
6 | 1 |Spec6 | true | 0
And the following query:
select t1.id, coalesce(t1.car_price, 0)+ coalesce(sum(t2.price), 0) as total_price
from tcars t1
left join tcar_optionals t2 on t2.id_car = t1.id
where t2.opt_included and t2.price>0 and t1.id=?
group by t1.id, t1.car_price
It returns the id from tcars and the total_price(car_price+price of included optionals that have price>0).
Example:
for t1.id=2 returns:
id | total_price
----|------------
2 | 1800
The problem appears when I have no included optionals with price>0, for example t1.id = 1.
What it returns:
id | total_price
----|------------
What I need is return only t1.car_price as total_price if there are no included optionals with price>0:
id | total_price
----|------------
1 | 1000
Can someone help me with this problem, please?
You should firstly join the tables with all conditions on the second table and aggregate values from this (joined) result, e.g:
select id, coalesce(car_price, 0)+ coalesce(sum(price), 0) total_price
from tcars
left join tcar_optionals on id = id_car and spec_included
-- where id = 1
group by id, car_price
The condition q1.id_car=1 in the where clause effectively turns your outer join into an inner join because for rows not matching the join condition q1.id_car will be null and the comparison =1 will remove those rows again.
You would need to put that into the JOIN condition - but as you already have a condition on the id_car in the derived table ("q1"), you don't need it anyway.
The other possibility would be to filter on the corresponding value from the tcars table: where t1.id = 1
Edit
By moving the conditions on the t2 table to the join condition you do get what you want:
select t1.id, coalesce(t1.car_price, 0) + coalesce(sum(t2.price), 0) as total_price
from tcars t1
left join tcar_optionals t2
on t2.id_car = t1.id
and t2.opt_included and t2.price > 0 --<< conditions for tcar_optionals go here
where t1.id = 1 --<< limit the car you want to see
group by t1.id;
If id is defined as the primary key in tcars, then group by t1.id is enough.
See the example here: http://rextester.com/YOYF30261
select (t1.car_price + coalesce(extra_price, 0)) as start_price
from tcars t1
left join (select id_car,sum(price) as extra_price from tcar_optionals
where opt_included and price > 0 group by 1) q1 on q1.id_car = t1.id
where t1.id=$1

Join that will return the same amount of records found on the left table

I am having some trouble trying to figure out how to write a sql statement that will return the same amount of records found in the left table.
For instance we have two tables, Transactions and Partners. Due to how the tables were originally designed there does not exist a way to retrieve an exact matching pair. IE. A transaction could have many partners that it relates to.
What I am looking to do is display all the transactions with a Partner ID. If a transaction has more than one matching Partner ID then I need to take the first occurrence of the match and throw away the rest. If a transaction does not have a matching Partner ID, I still need to display it, but with an empty or null value for the Partner ID.
Transaction Table
Transaction ID | ID 1 | ID 2
-------------- +---------+----------
T1 | A | 1
T2 | C | 3
T3 | B | 1
T4 | D | 4
T5 | A | 2
Partner Table
Transaction ID | ID 1 | ID 2
---------------+---------+----------
P1 | A | 1
P2 | B | 2
P3 | C | 3
P4 | C | 3
P5 | D | 4
Desired Results
Transaction ID| ID 1 | ID 2 | Partner ID
--------------+---------+----------+-----------
T1 | A | 1 | P1
T2 | C | 3 | P3
T3 | B | 1 | Null
T4 | D | 4 | P5
T5 | A | 2 | Null
I feel like I need some form of outer join to make sure no transactions are not queried, but I cant decipher how to make sure no duplicate transactions are displayed.
Thanks
SQL tables have no concept of "first" record, without some column to specify the ordering. But, the basic idea of what you want uses left join and row_number():
select t.*, p.partnerid
from transaction t left join
(select p.*,
row_number() over (partition by id1, id2 order by partnerid) as seqnum
from partner p
) p
on t.id1 = p.id1 and t.id2 = p.id2 and p.seqnum = 1;
This version takes "first" to mean "lowest value of partner id".
You can use an outer join with row_number:
select *
from (
select t1.transactionid, t1.id1, t1.id2, t2.transactionid as partnerid,
row_number() over (partition by t1.transactionid order by t2.transactionid) rn
from Transaction t1
left join Partner t2 on t1.id1 = t2.id1 and t1.id2 = t2.id2
) t
where rn = 1
This selects all records from transaction table and then only 1 from the partner table if it exists.
I'm not sure how you know you don't want P4 partnered with T2 as well as P3, but I found this worked for me, assuming it's the lowest ID:
select t1.transactionid, t1.id1, t1.id2, min(t2.transactionid) as partnerid
from Transaction t1, Partner t2 where t1.id1 = t2.id1(+) and t1.id2 = t2.id2(+)
group by t1.transactionid, t1.id1, t1.id2
order by t1.transactionid, t1.id1, t1.id2

SQL Query - Get count of two columns from two tables

Table 1:
TicketNumber | Rules
---------------------------
PR123 | rule_123
PR123 | rule_234
PR123 | rule_456
PR999 | rule_abc
PR999 | rule_xyz
Table2:
TicketNumber | Rules
---------------------------
PR123 | rule_123
PR123 | rule_234
PR999 | rule_abc
NOTE: Both tables have the same structure: same column names but different count.
NOTE: Both tables have same set of TicketNumber values
CASE 1:
If I need ticket and rules count of each ticket from table1, the query is:
Select [TicketNo], COUNT([TicketNo]) AS Rules_Count from [Table1] group by TicketNo
This will give me output in format :
ticketNumber | Rules_Count
---------------------------
PR123 | 3
PR999 | 9
CASE 2: (NEED HELP WITH THIS)
Now, the previous query gets the ticket and the count of the ticket of only 1 table. I need the count of the same ticket (since both have same set of tkt nos) in table2 also.
I need result in this way:
ticketNumber | Count(ticketNumber) of table1 | Count(ticketNumber) of table2
---------------------------------------------------------------------------------
PR123 | 3 | 2
PR999 | 2 | 1
Both Table1 and table2 have the same set of ticket nos but different counts
How do i get the result as shown above?
A simpler solution from a "statement point of view" (without COALESCE that maybe it's not so easy to understand).
Pay attention to the performances:
Select T1.TicketNumber,T1.Rules_Count_1,T2.Rules_Count_2
FROM
(
Select [TicketNumber], COUNT([TicketNumber]) AS Rules_Count_1
from [Table1] T1
group by TicketNumber) T1
INNER JOIN
(
Select [TicketNumber], COUNT([TicketNumber]) AS Rules_Count_2
from [Table2] T2
group by TicketNumber
) T2
on T1.TicketNumber = T2.TicketNumber
SQL Fiddle Demo
You can do this with a full outer join after aggregation (or an inner join if you really know that both tables have the same tickets:
select coalesce(t1.TicketNo, t2.TicketNo) as TicketNo,
coalesce(t1.Rules_Count, 0) as t1_Rules_Count,
coalesce(t2.Rules_Count, 0) as t2_Rules_Count
from (Select [TicketNo], COUNT([TicketNo]) AS Rules_Count
from [Table1]
group by TicketNo
) t1 full outer join
(Select [TicketNo], COUNT([TicketNo]) AS Rules_Count
from [Table2]
group by TicketNo
) t2
on t1.TicketNo = t2.TicketNo;
SELECT A.center,
A.total_1st,
B.total_2nd
FROM (SELECT a.center,
Count (a.dose1) AS Total_1st
FROM table_1 a
GROUP BY a.center) A
INNER JOIN (SELECT b.center,
Count (b.dose2) AS Total_2nd
FROM table_2 b
GROUP BY b.center) B
ON a.center = b.center
ORDER BY A.center

PostgreSQL LEFT OUTER JOIN query syntax

Lets say I have a table1:
id name
-------------
1 "one"
2 "two"
3 "three"
And a table2 with a foreign key to the first:
id tbl1_fk option value
-------------------------------
1 1 1 1
2 2 1 1
3 1 2 1
4 3 2 1
Now I want to have as a query result:
table1.id | table1.name | option | value
-------------------------------------
1 "one" 1 1
2 "two" 1 1
3 "three"
1 "one" 2 1
2 "two"
3 "three" 2 1
How do I achieve that?
I already tried:
SELECT
table1.id,
table1.name,
table2.option,
table2.value
FROM table1 AS table1
LEFT outer JOIN table2 AS table2 ON table1.id = table2.tbl1fk
but the result seems to omit the null vales:
1 "one" 1 1
2 "two" 1 1
1 "one" 2 1
3 "three" 2 1
SOLVED: thanks to Mahmoud Gamal: (plus the GROUP BY)
Solved with this query
SELECT
t1.id,
t1.name,
t2.option,
t2.value
FROM
(
SELECT t1.id, t1.name, t2.option
FROM table1 AS t1
CROSS JOIN table2 AS t2
) AS t1
LEFT JOIN table2 AS t2 ON t1.id = t2.tbl1fk
AND t1.option = t2.option
group by t1.id, t1.name, t2.option, t2.value
ORDER BY t1.id, t1.name
You have to use CROSS JOIN to get every possible combination of name from the first table with the option from the second table. Then LEFT JOIN these combination with the second table. Something like:
SELECT
t1.id,
t1.name,
t2.option,
t2.value
FROM
(
SELECT t1.id, t1.name, t2.option
FROM table1 AS t1
CROSS JOIN table2 AS t2
) AS t1
LEFT JOIN table2 AS t2 ON t1.id = t2.tbl1_fk
AND t1.option = t2.option
SQL Fiddle Demo
Simple version: option = group
It's not specified in the Q, but it seems like option is supposed to define a group somehow. In this case, the query can simply be:
SELECT t1.id, t1.name, t2.option, t2.value
FROM (SELECT generate_series(1, max(option)) AS option FROM table2) o
CROSS JOIN table1 t1
LEFT JOIN table2 t2 ON t2.option = o.option AND t2.tbl1_fk = t1.id
ORDER BY o.option, t1.id;
Or, if options are not numbered in sequence, starting with 1:
...
FROM (SELECT DISTINCT option FROM table2) o
...
Returns:
id | name | option | value
----+-------+--------+-------
1 | one | 1 | 1
2 | two | 1 | 1
3 | three | |
1 | one | 2 | 1
2 | two | |
3 | three | 2 | 1
Faster and cleaner, avoiding the big CROSS JOIN and the big GROUP BY.
You get distinct rows with a group number (grp) per set.
Requires Postgres 8.4+.
More complex: group indicated by sequence of rows
WITH t2 AS (
SELECT *, count(step OR NULL) OVER (ORDER BY id) AS grp
FROM (
SELECT *, lag(tbl1_fk, 1, 2147483647) OVER (ORDER BY id) >= tbl1_fk AS step
FROM table2
) x
)
SELECT g.grp, t1.id, t1.name, t2.option, t2.value
FROM (SELECT generate_series(1, max(grp)) AS grp FROM t2) g
CROSS JOIN table1 t1
LEFT JOIN t2 ON t2.grp = g.grp AND t2.tbl1_fk = t1.id
ORDER BY g.grp, t1.id;
Result:
grp | id | name | option | value
-----+----+-------+--------+-------
1 | 1 | one | 1 | 1
1 | 2 | two | 1 | 1
1 | 3 | three | |
2 | 1 | one | 2 | 1
2 | 2 | two | |
2 | 3 | three | 2 | 1
-> SQLfiddle for both.
How?
Explaining the complex version ...
Every set is started with a tbl1_fk <= the last one. I check for this with the window function lag(). To cover the corner case of the first row (no preceding row) I provide the biggest possible integer 2147483647 the default for lag().
With count() as aggregate window function I add the running count to each row, effectively forming the group number grp.
I could get a single instance for every group with:
(SELECT DISTINCT grp FROM t2) g
But it's faster to just get the maximum and employ the nifty generate_series() for the reduced CROSS JOIN.
This CROSS JOIN produces exactly the rows we need without any surplus. Avoids the need for a later GROUP BY.
LEFT JOIN t2 to that, using grp in addition to tbl1_fk to make it distinct.
Sort any way you like - which is possible now with a group number.
try this
SELECT
table1.id, table1.name, table2.option, table2.value FROM table1 AS table11
JOIN table2 AS table2 ON table1.id = table2.tbl1_fk
This is enough:
select * from table1 left join table2 on table1.id=table2.tbl1_fk ;