SQL - Count rows based on matching columns and value range - sql

Please see below query using T-SQL with SSMS. There are three tables: B, G and L.
B has a column Bname
G has 2 columns Gname, Gross
L has 2 columns Bname, Gname
Gross column is an INT ranging values between 80 and 100.
Table L's columns: Bname and Gname will feature names from the B and G tables on the same row. Where both names feature on the same row, I would like to COUNT this as one item; only if the Gross on Table G ranges between 80 and 100 to the corresponding Gname row.
My current query reads:
SELECT l.bname, (SELECT COUNT(*) FROM g WHERE g.gross BETWEEN 80 AND 90) AS Good
FROM l
INNER JOIN b
ON b.bname=l.bname
INNER JOIN g
ON g.gname=l.gname
GROUP BY l.bname;
The result is nearly there, but it counts all Table G:Gname rows betweeen 80 and 100. Emitting the instances on Table L where the Bname and Gname are on the same row.
Thanks in advance for looking.

I suspect that you want:
SELECT l.bname,
(SELECT COUNT(*)
FROM b INNER JOIN
g
ON g.gname = l.gname
WHERE b.bname = l.bname AND g.gross BETWEEN 80 AND 90
) AS Good
FROM l ;
The outer aggregation is not needed of l.bname is unique.
This would more commonly be calculating using conditional aggregation:
SELECT l.bname,
SUM(CASE WHEN g.gross BETWEEN 80 AND 90 THEN 1 ELSE 0 END) AS Good
FROM l INNER JOIN
b
ON b.bname = l.bname INNER JOIN
g
ON g.gname = l.gname
GROUP BY l.bname;
No subquery is needed.

Related

SELECT JOIN Table Operations

In my query below, I joined tables b, c, d to a by the base_id column.
However, I need to do some operations in my SELECT statement.
Since b, c, and d are not joined to each other,
is my (a.qty - (b.qty - (c.qty + d.qty))) formula computing only those tables that have the same base_id column?
SELECT (a.qty - (b.qty - (c.qty + d.qty))) AS qc_in
FROM receiving a
LEFT JOIN (
SELECT SUM(qty) AS qty, base_id
FROM quality_control bb
WHERE location_to = 6
AND is_canceled = 0
GROUP BY base_id
) b
ON b.base_id = a.base_id
LEFT JOIN (
SELECT SUM(qty) AS qty, base_id
FROM quality_control ba
WHERE location_from = 6
AND is_canceled = 0
GROUP BY base_id
) c
ON c.base_id = a.base_id
LEFT JOIN (
SELECT SUM(qty) AS qty, base_id
FROM issuance
WHERE location_from = 6
AND is_canceled = 0
GROUP BY base_id
) d
ON d.base_id = a.base_id
WHERE a.is_canceled = 0
I think you're confused by how joining works (if I'm reading the question correctly). If you have:
select *
from table1 a
join table2 b
on a.Id = b.Id
join table3c
on a.Id = c.Id
Then yes a is joined to b, and b is joined to c, but that also means that a is joined to c. One way to think of it is as one giant in-memory table that has all of the a columns then all of the b columns and then all of the c columns in the one result.
If a.Id is 1, and you select the row from b where Id is the same (1) and then you join to c where the Id is the same as a (1), then a, b and c all have the same id.
So yes, (a.qty - (b.qty - (c.qty + d.qty))) will only be doing that calculation for rows where a, b, c and d all have the same base_id .
Surely. In the nested statements you join all the tables on base_id. That means, as the result of those joins you will get a huge table containing columns from all of the joined tables with a common column you join on (base_id in your case).

Having trouble understand which table is count(*) actually counting when joining two tables together.

If I have the following codes, which table is count(*) counting the number or rows in it?
SELECT COUNT(*)
FROM region r
JOIN sales_rep s
ON r.id = s.region_id
Thanks a lot!!!
It is counting the rows from your join query as a whole. It is the same as:
SELECT COUNT(*)
FROM
(SELECT * FROM region r
JOIN sales_rep s
ON r.id = s.region_id)
It is counting all records of the "resulting" table from the join. The number will depend on the type of relationship between region and sales_rep. Assuming your domain is that a region has zero or many sales_rep and that all sales_rep have one and only one region then the count will always equals to the count of sales_rep. For example:
regions: (id) sales_rep: (id, reg_id)
A 1 A
B 2 A
C 3 B
4 C
count(*) = 4
On the other hand if a sales_rep can have no region then the ones with no region are excluded:
regions: (id) sales_rep: (id, reg_id)
A 1 A
B 2 null
C 3 B
4 C
count(*) = 3
Or if a sales_rep can have more than one region then the result is the same as 1st case but you will have a higher count:
regions: (id) sales_rep: (id, reg_id)
A 1 A
B 1 B
C 2 A
3 B
4 C
count(*) = 5
I'd definitely recommend you to do a select * so you can see by yourself whats happening and then change it to count. Additionally please refer to this article to better understand joints: https://www.w3schools.com/sql/sql_join.asp

How can I show all "article" which have more than 3 "bids"?

I wanna show the "ArticleName" of all "offers" that have more than 3 "bids". The number of the "Bids" should be output.
I don't know how I can write it down. But I think I know the Logic. It should count the same number of the Table "bid" and the column "OID" and in the end it should paste the number which is more than 3.
Picture:
Well that's easy enough:
Select ArticleName
, count(*) NumberOfBids
from Offer o
join Bid b
on b.oid = o.oid
group by ARticleName
having count(*) >= 3
SELECT * FROM (
SELECT o.ArticleName, count(b.BID) as numberOfBids
FROM Offer as o INNER JOIN bid as b ON o.oid = b.oid
GROUP BY o.ArticleName
) as c
WHERE c.numberOfBids > 3

SQL populating table with calculation from other tables

I have two tables:
grades_table = has 100 rows of students with grades i.e.
Student Mark
Joe 64
Mark 50
percentage_table = a table which has a percentage to a grade i.e.
Grade Mark
A 70+
B 60+
C 50+
Is it possible in SQL to populate a new table called Overall, with the grade calculation? For example,
Student Grade
Joe B
Mark C
I've looked at IN and BETWEEN. But I can't quite understand how to calculate the ranges. I'm new to SQL and any help or point to the right direction would be great.
Split percentage table's mark column into two columns, markmin and markmax. Then JOIN the tables as:
select g.student, p.grade
from grades g
join percentage p ON g.mark between p.markmin and p.markmax
Or, according to later suggested If I changed the scenario slightly, and only had one mark column in the percentage_table, so only 60+, 70+:
Here I assume (A, 70) means 70 and above is grade A. Do a GROUP BY with MIN to find "lowest" grade alphabetically:
select g.student, min(p.grade)
from grades g
join percentage p ON g.mark <= p.mark
group by g.student
Alternatively, use a correlated sub-select:
select g.student, (select min(p.grade) from percentage p
where g.mark <= p.mark)
from grades g
I would suggest you to have two different columns in your table percentage_table:
Grade | MarkMin | MarkMax
A | 70 | Null
B | 60 | 69
C | 50 | 59
Then you can just use a JOIN:
SELECT g.student, p.Mark
FROM
grades_table AS g LEFT JOIN percentage_table AS p
ON (g.Mark >= p.MarkMin OR p.MarkMin IS NULL)
AND (g.Mark<=p.MarkMax OR p.MarkMax IS NULL)
70+ which means >= 70 can then be inserted as (70, Null).
If you want to have a <=49 then you can add a mark as (Null, 49).

I need a SQL query for comparing column values against rows in the same table

I have a table called BB_BOATBKG which holds passengers travel details with columns Z_ID, BK_KEY and PAXSUM where:
Z_ID = BookingNumber* LegNumber
BK_KEY = BookingNumber
PAXSUM = Total number passengers travelled in each leg for a particular booking
For Example:
Z_ID BK_KEY PAXSUM
001234*01 001234 2
001234*02 001234 3
001287*01 001287 5
001287*02 001287 5
002323*01 002323 7
002323*02 002323 6
I would like to get a list of all Booking Numbers BK_KEY from BB_BOATBKG where the total number of passengers PAXSUM is different in each leg for the same booking
Example, For Booking number A, A*Leg01 might have 2 Passengers, A* Leg02 might have 3 passengers
Dependent of your RDBMs there might be several options availible. A solution that should work for most is:
SELECT A.Z_ID, A.BK_KEY, A.PAXSUM
FROM BB_BOATBKG A
JOIN (
SELECT BK_KEY
FBB_BOATBKGROM BB_BBK_KEY
GROUP BY BK_KEY
HAVING COUNT( DISTINCT PAXSUM ) > 1
) B
ON A.BK_KEY = B.BK_KEY
If your DBMS support OLAP functions, have a look at RANK() OVER (...)
It's a little counterintuitive, but you could join the table to itself on {BK_KEY, PAXSUM} and pull out only the records whose joined result is null.
I think this does it:
SELECT
a.BK_KEY
FROM
BB_BOATBKG a
LEFT OUTER JOIN BB_BOATBKG b ON a.BK_KEY = b.BK_KEY AND a.PAXSUM = b.PAXSUM
WHERE
b.Z_ID IS NULL
GROUP BY
a.BK_KEY
Edit: I think I missed anything beyond the trivial case. I think you can do it with some really nasty subselecting though, a la:
SELECT
b.BK_KEY
FROM
(
SELECT
a.BK_KEY,
Count = COUNT(*)
FROM
(
SELECT
a.BK_KEY,
a.PAXSUM
FROM
BB_BOATBKG a
GROUP BY
a.BK_KEY,
a.PAXSUM
HAVING
COUNT(*) = 1
) a
GROUP BY
a.BK_KEY
) b
INNER JOIN
(
SELECT
c.BK_KEY,
Count = COUNT(*)
FROM
BB_BOATBKG c
GROUP BY
c.BK_KEY
) c ON b.BK_KEY = c.BK_KEY AND b.Count = c.Count