Tips for Creating Summary Count of Value in other Tables - sql

I have multiple tables with a status column in each. I want to display a summary of the counts of each status per table. Something like this:
=============================================
Status | Table A | Table B | Table C |
Status A | 3 | 8 | 2 |
Status B | 5 | 7 | 4 |
==============================================
I need help getting started as I'm not sure how to approach this issue. I can do simple COUNT functions like:
SELECT status, count(status) from TABLE_A group by status
But I'm not sure how to populate the data in the form I want or how to, if possible, use the table names as the column headers. I'd appreciate a point in the right direction. Thanks!

May be try doing left joins after you have calculated counts for each table separately.Something like:
select distinct t1.status,
count(t1.status) as [tableA],
t2.TableB,
t3.TableC from Table A t1
left join (
select distinct status,
count(status) as [TableB] from Table B
group by status
) t2 on t1.status=t2.status
left join (
select distinct status,
count(status) as [TableC] from Table C
group by status
) t3 on t1.status=t3.status
group by t1.Status

I would use union all and aggregation:
select status, sum(a) as a, sum(b) as b, sum(c) as c
from ((select status, count(*) as a, 0 as b, 0 as c
from tablea
group by status
) union all
(select status, 0, count(*), 0
from tableb
group by status
) union all
(select status, 0, 0, count(*)
from tablea
group by status
)
) abc
group by status;
This ensures that all rows appear, even when one or more tables are missing some values of status.

could be using left join
select t.status, a.cnt A, b.cnt B,c.cnt C
from(
select status
from tableA
union
select status
from tableB
select status
from tableC
) t
left join (
select status, count(*) cnt
from tableA
group by status
) a ON on t.status = a.status
left join (
select status, count(*) cnt
from tableB
group by status
) b ON on t.status = b.status
left join (
select status, count(*) cnt
from tableC
group by status
) c ON on t.status = c.status

Related

Is it possible to JOIN a table on the TOP 1 if there is no unique identifier?

For Example
SELECT
a.SomethingInCommon,
tbl1.Status AS Status1,
tbl2.Status AS Status2,
tbl3.Status AS Status3
FROM Maintable a
LEFT OUTER JOIN SecondTable tbl1 ON
tbl1.ID = (SELECT TOP 1 ID
FROM SecondTable SomethingInCommon = a.SomethingInCommon)
LEFT OUTER JOIN SecondTable tbl2 ON
tbl2.ID = (SELECT TOP 1 ID
FROM SecondTable WHERE SomethingInCommon = a.SomethingInCommon
AND ID NOT IN (SELECT TOP 1 ID
FROM SecondTABLE
WHERE SomethingInCommon = a.SomethingInCommon))
LEFT OUTER JOIN SecondTable tbl3 ON
tbl23.ID = (SELECT TOP 1 ID
FROM SecondTable
WHERE SomethingInCommon = a.SomethingInCommon
AND ID NOT IN (SELECT TOP 2 ID
FROM SecondTABLE WHERE SomethingInCommon = a.SomethingInCommon))
This query joins SecondTable three times to show a record like
SomethingInCommon | Status1 | Status2 | Status 3
Is there anyway to accomplish these results if SecondTable does not have the unique identifier column (ID) ?
Perhaps maybe creating a temporary unique ID on the fly?
If you don't have IDs but know the order you want, you could create artificial IDs using ROW_NUMBER() and then do your TOP 1's off of that.
WITH TEMP AS (
SELECT 3 a, 1 b UNION ALL
SELECT 2, 1 UNION ALL
SELECT 1, 1 UNION ALL
SELECT 2, 2 UNION ALL
SELECT 1, 2)
SELECT A, B, ROW_NUMBER() OVER (PARTITION BY B ORDER BY B ASC) as RowNumber FROM TEMP
;WITH TEMP AS (
SELECT 3 a, 1 b UNION ALL
SELECT 2, 1 UNION ALL
SELECT 1, 1 UNION ALL
SELECT 2, 2 UNION ALL
SELECT 1, 2)
SELECT A, B, ROW_NUMBER() OVER (ORDER BY A ASC) as RowNumber FROM TEMP
As Raphael said in the comment, this can be done with CTE like below
with cte
as
(
SELECT M.SomethingInCommon, S.ID, ROW_NUMBER() OVER ( Partition by S.SomethingInCommon ORDER BY S.ID desc) as rn
FROM Maintable M
LEFT JOIN SecondTable S
on M.SomethingInCommon = S.SomethingInCommon
)
SELECT cte.SomethingInCommon
case when rn =1 then cte.ID end as Status1,
case when rn =2 then cte.ID end as Status2,
case when rn =3 then cte.ID end as Status3
where rn <=3
If you want the top three statuses, then you can use conditional aggregation:
select m.somethingincommon,
max(case when seqnum = 1 then status end) as status1,
max(case when seqnum = 2 then status end) as status2,
max(case when seqnum = 3 then status end) as status3
from maintable m left join
(select s.*,
row_number() over (partition by s.somethingincommon order by (select NULL)) as seqnum
from secondtable
) s
on m.somethingincommon = s.somethingincommon
group by m.somethingincommon;
If you prefer, you can do this with multiple joins:
with s as (
select s.*,
row_number() over (partition by s.somethingincommon order by (select NULL)) as seqnum
from secondtable
)
select m.*, s1.status as status, s2.status as status2, s3.status as status3
from maintable m left join
s s1
on m.somethingincommon = s1.somethingincommon and
s1.seqnum = 1 left join
s s2
on m.somethingincommon = s2.somethingincommon and
s2.seqnum = 2 left join
s s3
on m.somethingincommon = s3.somethingincommon and
s3.seqnum = 3;

left join without duplicate values using MIN()

I have a table_1:
id custno
1 1
2 2
3 3
and a table_2:
id custno qty descr
1 1 10 a
2 1 7 b
3 2 4 c
4 3 7 d
5 1 5 e
6 1 5 f
When I run this query to show the minimum order quantities from every customer:
SELECT DISTINCT table_1.custno,table_2.qty,table_2.descr
FROM table_1
LEFT OUTER JOIN table_2
ON table_1.custno = table_2.custno AND qty = (SELECT MIN(qty) FROM table_2
WHERE table_2.custno = table_1.custno )
Then I get this result:
custno qty descr
1 5 e
1 5 f
2 4 c
3 7 d
Customer 1 appears twice each time with the same minimum qty (& a different description) but I only want to see customer 1 appear once. I don't care if that is the record with 'e' as a description or 'f' as a description.
First of all... I'm not sure why you need to include table_1 in the queries to begin with:
select custno, min(qty) as min_qty
from table_2
group by custno;
But just in case there is other information that you need that wasn't included in the question:
select table_1.custno, ifnull(min(qty),0) as min_qty
from table_1
left outer join table_2
on table_1.custno = table_2.custno
group by table_1.custno;
"Generic" SQL way:
SELECT table_1.custno,table_2.qty,table_2.descr
FROM table_1, table_2
WHERE table_2.id = (SELECT TOP 1 id
FROM table_2
WHERE custno = table_1.custno
ORDER BY qty )
SQL 2008 way (probably faster):
SELECT custno, qty, descr
FROM
(SELECT
custno,
qty,
descr,
ROW_NUMBER() OVER (PARTITION BY custno ORDER BY qty) RowNum
FROM table_2
) A
WHERE RowNum = 1
If you use SQL-Server you could use ROW_NUMBER and a CTE:
WITH CTE AS
(
SELECT table_1.custno,table_2.qty,table_2.descr,
RN = ROW_NUMBER() OVER ( PARTITION BY table_1.custno
Order By table_2.qty ASC)
FROM table_1
LEFT OUTER JOIN table_2
ON table_1.custno = table_2.custno
)
SELECT custno, qty,descr
FROM CTE
WHERE RN = 1
Demolink

only using select in sql instead of group by

I have this table:
supplier | product | qty
--------------------------
s1 | p1 | 300
s1 | p2 | 90
s2 | p3 | 89
I want to find suppliers with more than 2 products.
But only with select and where, no group by. Any suggestion?
Why would you want not to use group by is beyond me, but this might work:
SELECT Supplier FROM table outer WHERE
(
select count(Products) from table inner
where inner.Supplier = outer.Supplier
) > 2
Please bear in mind, that group by is made for stuff like that and should be used.
;WITH
sequenced_data AS
(
SELECT
supplier,
ROW_NUMBER() OVER (PARTITION BY supplier ORDER BY product) AS supplier_product_ordinal
FROM
YourTable
)
SELECT
supplier
FROM
sequenced_data
WHERE
supplier_product_ordinal = 3
But I'd expect it to be slower than using GROUP BY.
SELECT DISTINCT
supplier
FROM
yourTable
WHERE
EXISTS (SELECT * FROM yourTable AS lookup WHERE supplier = yourTable.supplier AND product < yourTable.product)
AND EXISTS (SELECT * FROM yourTable AS lookup WHERE supplier = yourTable.supplier AND product > yourTable.product);
In the usual parts and suppliers database, this relvar is named SP:
SELECT DISTINCT T1.SNO
FROM SP AS T1
JOIN SP AS T2
ON T1.SNO = T2.SNO
AND T2.PNO <> T1.PNO
JOIN SP AS T3
ON T1.SNO = T3.SNO
AND T3.PNO <> T1.PNO
AND T3.PNO <> T2.PNO;
Noting that you can use HAVING without GROUP BY:
SELECT DISTINCT T1.SNO
FROM SP AS T1
WHERE EXISTS (
SELECT 1
FROM SP AS T2
WHERE T2.SNO = T1.SNO
HAVING COUNT(*) > 2
);
;WITH T AS
(
SELECT *,
COUNT(*) OVER (PARTITION BY S) AS Cnt
FROM YourTable
)
SELECT DISTINCT S
FROM T
WHERE Cnt > 2
with subquery:
select distinct supplier
from table a
where (select count(*)
from table b
where b.supplier = a.supplier and b.product <> a.product
) > 1

SQL aggregation query, grouping by entries in junction table

I have TableA in a many-to-many relationship with TableC via TableB. That is,
TableA TableB TableC
id | val fkeyA | fkeyC id | data
I wish the do select sum(val) on TableA, grouping by the relationship(s) to TableC. Every entry in TableA has at least one relationship with TableC. For example,
TableA
1 | 25
2 | 30
3 | 50
TableB
1 | 1
1 | 2
2 | 1
2 | 2
2 | 3
3 | 1
3 | 2
should output
75
30
since rows 1 and 3 in Table have the same relationships to TableC, but row 2 in TableA has a different relationship to TableC.
How can I write a SQL query for this?
SELECT
sum(tableA.val) as sumVal,
tableC.data
FROM
tableA
inner join tableB ON tableA.id = tableB.fkeyA
INNER JOIN tableC ON tableB.fkeyC = tableC.id
GROUP by tableC.data
edit
Ah ha - I now see what you're getting at. Let me try again:
SELECT
sum(val) as sumVal,
tableCGroup
FROM
(
SELECT
tableA.val,
(
SELECT cast(tableB.fkeyC as varchar) + ','
FROM tableB WHERE tableB.fKeyA = tableA.id
ORDER BY tableB.fkeyC
FOR XML PATH('')
) as tableCGroup
FROM
tableA
) tmp
GROUP BY
tableCGroup
Hm, in MySQL it could be written like this:
SELECT
SUM(val) AS sumVal
FROM
( SELECT
fkeyA
, GROUP_CONCAT(fkeyC ORDER BY fkeyC) AS grpC
FROM
TableB
GROUP BY
fkeyA
) AS g
JOIN
TableA a
ON a.id = g.fkeyA
GROUP BY
grpC
SELECT sum(a.val)
FROM tablea a
INNER JOIN tableb b ON (b.fKeyA = a.id)
GROUP BY b.fKeyC
It seems that is it needed to create a key_list in orther to allow group by:
75 -> key list = "1 2"
30 -> key list = "1 2 3"
Because GROUP_CONCAT don't exists in T-SQL:
WITH CTE ( Id, key_list )
AS ( SELECT TableA.id, CAST( '' AS VARCHAR(8000) )
FROM TableA
GROUP BY TableA.id
UNION ALL
SELECT TableA.id, CAST( key_list + ' ' + str(TableB.id) AS VARCHAR(8000) )
FROM CTE c
INNER JOIN TableA A
ON c.Id = A.id
INNER join TableB B
ON B.Id = A.id
WHERE A.id > c.id --avoid infinite loop
)
Select
sum( val )
from
TableA inner join
CTE on (tableA.id = CTE.id)
group by
CTE.key_list

Different record count values in one query

My goal is to achieve a result set like the following
CODE | TOTAL1 | TOTAL2
1 | 56 | 34
2 | 12 | 15
3 | 90 | 3
There are 2 tables e.g tableA and tableB
The counts are different by tableB.type
SELECT code, COUNT (*) AS total1
FROM tableA a
WHERE a.ID IN (select ID from tableB
where type = 'XYZ')
GROUP BY code
SELECT code, COUNT (*) AS total2
FROM tableA a
WHERE a.ID IN (select ID from tableB
where type = 'ABC')
GROUP BY code
I'd like to display the count for each code per type in the same query
Thanks in advance
No subquery
SELECT a.code,
sum(decode(b.type,'ABC',1,0)) AS total1,sum(decode(b.type,'XYZ',1,0)) AS total2
FROM tableA a
join tableB b on a.ID = b.ID
GROUP BY a.code
Regards
K
Subqueries :
SELECT code, (select COUNT (*) AS total1
FROM tableA a1
WHERE a.ID IN (select ID from tableB
where type = 'XYZ')
and a1.code = tableA.code) as Total1,
(select COUNT (*) AS total2
FROM tableA a2
WHERE a.ID IN (select ID from tableB
where type = 'ABC')
and a2.code = tableA.code) as Total2)
from tableA
group by Code
Probably one of many ways to skin it is to UNION the two in an in-line view and then select the sum of the counts, like this:
SELECT code, SUM(total1) total1, SUM(total2) total2 FROM
(
SELECT code, COUNT() total1, 0 total2
FROM tableA a
WHERE a.ID IN (select ID from tableB
where type = 'XYZ')
GROUP BY code
UNION
SELECT code, 0, COUNT ()
FROM tableA a
WHERE a.ID IN (select ID from tableB
where type = 'ABC')
GROUP BY code
)
GROUP BY code;