How count same columns from few tables in one SQL query - sql

I have five tables, TDetective, TMonths, TProduct1, TProduct2 and TProduct3. I want to have a result set with four columns:
first is Detective name
second is count - how many TProduct1 are assigned to this Detective in special months
third is count - how many TProduct2 are assigned to this Detective in special months
and fourth is count - how many TProduct3 are assigned to this Detective in special months.
Please help me.
TDetective
|id |
|Detective|
TMonths
|id |
|Months |
TProduct1
|id |
|RequestDay|
|Mobile|
|Operator|
|Subjects|
|OccurrenceMode|
|Detective|
|Months|
TProduct2
|id |
|RequestDay|
|Mobile|
|Operator|
|Subjects|
|OccurrenceMode|
|Detective|
|Months|
TProduct3
|id |
|RequestDay|
|Mobile|
|Operator|
|Subjects|
|OccurrenceMode|
|Detective|
|Months|
My SQL query looks like this:
select
TDetective.Detective,
count(TProduct1.id) as countOfDetectiveP1
count(TProduct2.id) as countOfDetectiveP2
count(TProduct3.id) as countOfDetectiveP3
from
TDetective
left outer join
TProduct1 on TDetective.Detective = TProduct1.Detective
where
TProduct1.Months in (select months from TMonths)
left outer join
TProduct2 on TDetective.Detective = TProduct2.Detective
where
TProduct2.Months in (select months from TMonths)
left outer join
TProduct3 on TDetective.Detective = TProduct3.Detective
where
TProduct3.Months in (select months from TMonths)
group by
Detective.Detective
order by
Detective
Many thanks

I would cross join the first two tables to get a row for every detective and month. Then use correlated subqueries to fill in the rest of the information:
select d.id, d.month,
(select count(*)
from product1 p1
where p1.detective = d.id and
p1.month = m.month
),
(select count(*)
from product2 p2
where p2.detective = d.id and
p2.month = m.month
),
(select count(*)
from product3 p3
where p3.detective = d.id and
p3.month = m.month
)
from detective d cross join
months m;
You can add a where clause to the outer query to filter for particular detectives or months.

select d.Detective,
(select count(*)
from P118 p1
where p1.detective = d.Detective and
p1.Months in (select months from TempMonths)
),
(select count(*)
from P119 p2
where p2.detective = d.Detective and
p2.Months in (select months from TempMonths)
),
(select count(*)
from P120 p3
where p3.detective = d.Detective and
p3.Months in (select months from TempMonths)
)
from Detective d
order by Detective

Related

How to force postgres to return 0 even if there are no rows matching query, using coalesce, group by and join

I've been trying hopelessly to get the following SQL statement to return the query results and default to 0 if there are no rows matching the query.
This is the intended result:
vol | year
-------+------
0 | 2018
Instead I get:
vol | year
-----+------
(0 rows)
Here is the sql statement:
select coalesce(vol,0) as vol, year
from (select sum(vol) as vol, year
from schema.fact_data
join schema.period_data
on schema.fact_data.period_tag = schema.period_data.tag
join schema.product_data
on schema.fact_data.product_tag =
schema.product_data.tag
join schema.market_data
on schema.fact_data.market_tag = schema.market_data.tag
where "retailer"='MadeUpRetailer'
and "product_tag"='FakeProductTag'
and "year"='2018' group by year
) as DerivedTable;
I know the query works because it returns data when there is data. Just doesn't default to 0 as intended...
Any help in finding why this is the case would be much appreciated!
Using your subquery DerivedTable, you could write:
SELECT coalesce(DerivedTable.vol, 0) AS vol,
y.year
FROM (VALUES ('2018'::text)) AS y(year)
LEFT JOIN (SELECT ...) AS DerivedTable
ON DerivedTable.year = y.year;
Remove the GROUP BY (and the outer query):
select 2018 as year, coalesce(sum(vol), 0) as vol
from schema.fact_data f join
schema.period_data p
on f.period_tag = p.tag join
schema.product_data pr
on f.product_tag = pr.tag join
schema.market_data m
on fd.market_tag = m.tag
where "retailer" = 'MadeUpRetailer' and
"product_tag" = 'FakeProductTag' and
"year" = '2018';
An aggregation query with no GROUP BY always returns exactly one row, so this should do what you want.
EDIT:
The query would look something like this:
select v.yyyy as year, coalesce(sum(vol), 0) as vol
from (values (2018), (2019)) v(yyyy) left join
schema.fact_data f
on f.year = v.yyyy left join -- this is just an example. I have no idea where year is coming from
schema.period_data p
on f.period_tag = p.tag left join
schema.product_data pr
on f.product_tag = pr.tag left join
schema.market_data m
on fd.market_tag = m.tag
group by v.yyyy
However, you have to move the where conditions to the appropriate on clauses. I have no idea where the columns are coming from.
From the code you posted it is not clear in which table you have the year column.
You can use UNION to fetch just 1 row in case there are no rows in that table for the year 2018 like this:
select sum(vol) as vol, year
from schema.fact_data innrt join schema.period_data
on schema.fact_data.period_tag = schema.period_data.tag
inner join schema.product_data
on schema.fact_data.product_tag = schema.product_data.tag
inner join schema.market_data
on schema.fact_data.market_tag = schema.market_data.tag
where
"retailer"='MadeUpRetailer' and
"product_tag"='FakeProductTag' and
"year"='2018'
group by "year"
union
select 0 as vol, '2018' as year
where not exists (
select 1 from tablename where "year" = '2018'
)
In case there are rows for the year 2018, then nothing will be fetched by the 2nd query,

altering query in db2 to fix count from a join

I'm getting an aggregated count of records for orders and I'm getting the expected count on this basic query:
SELECT
count(*) as sales_180,
180/count(*) as velocity
FROM custgroup g
WHERE g.cstnoc = 10617
AND g.framec = 4847
AND g.covr1c = 1763
AND g.colr1c = 29
AND date(substr(g.extd1d,1,4)||'-'||substr(g.EXTD1d,5,2)||'-'||substr(g.EXTD1d,7,2) ) between current_Date - 180 DAY AND current_Date
But as soon as I add back in my joins and joined values then my count goes from 1 (which it should be) to over 200. All I need from these joins is the customer ID and the manager number. so even if my count is high, I'm basically just trying to say "for this cstnoc, give me the slsupr and xlsno"
How can I perform this below query without affecting the count? I only want my count (sales_180 and velocity) coming from the custgroup table based on my where clause, but I then just want one value of the xcstno and xslsno based on the cstnoc.
SELECT
count(*) as sales_180,
180/count(*) as velocity,
c.xslsno as CustID,
cr.slsupr as Manager
FROM custgroup g
inner join customers c
on g.cstnoc = c.xcstno
inner join managers cr
on c.xslsno = cr.xslsno
WHERE g.cstnoc = 10617
AND g.framec = 4847
AND g.covr1c = 1763
AND g.colr1c = 29
AND date(substr(g.extd1d,1,4)||'-'||substr(g.EXTD1d,5,2)||'-'||substr(g.EXTD1d,7,2) ) between current_Date - 180 DAY AND current_Date
GROUP BY c.xslsno, cr.slsupr
You are producing multiple rows when joining, so your count is now counting all the resulting rows with all that [unintended] multiplicity.
The solution? Use a table expression to pre-compute your count, and then you can join it to the other tables, as in:
select
g2.sales_180,
g2.velocity,
c.xslsno as CustID,
cr.slsupr as Manager
from customers c
join managers cr on c.xslsno = cr.xslsno
join ( -- here the Table Expression starts
SELECT
count(*) as sales_180,
180/count(*) as velocity
FROM custgroup g
WHERE g.cstnoc = 10617
AND g.framec = 4847
AND g.covr1c = 1763
AND g.colr1c = 29
AND date(substr(g.extd1d,1,4)||'-'||substr(g.EXTD1d,5,2)
||'-'||substr(g.EXTD1d,7,2) )
between current_Date - 180 DAY AND current_Date
) g2 on g2.cstnoc = c.xcstno
You can also use a Common Table Expression (CTE) that will produce the same result:
with g2 as (
SELECT
count(*) as sales_180,
180/count(*) as velocity
FROM custgroup g
WHERE g.cstnoc = 10617
AND g.framec = 4847
AND g.covr1c = 1763
AND g.colr1c = 29
AND date(substr(g.extd1d,1,4)||'-'||substr(g.EXTD1d,5,2)
||'-'||substr(g.EXTD1d,7,2) )
between current_Date - 180 DAY AND current_Date
)
select
g2.sales_180,
g2.velocity,
c.xslsno as CustID,
cr.slsupr as Manager
from customers c
join managers cr on c.xslsno = cr.xslsno
join g2 on g2.cstnoc = c.xcstno

Extract only the topline of data from a specific table SQL

I'm having trouble extracting the topline of data from a table and joining it with other extracted fields from other tables.
I have 3 tables:
Person
Folder
Earnings
Person:
PERSONID |FORENAMES|SURNAME|DOB |GENDER|NINO
1000000 |JOHNSTON |ALI |10/10/80 |M |JK548754A
Folder:
FOLDERID|FOLDERREF
1000000 |104567LK
Earnings:
FOLDERID|DATESTARTED|DATEENDED |GROSSEARNINGS
1000000 |01-04-2014 |31-03-2015 |31846.00
1000000 |01-04-2013 |31-03-2014 |31160.04
1000000 |01-04-2012 |31-03-2013 |30011.04
1000000 |01-04-2011 |31-03-2012 |29123.94
I need my data to look like:
JOHNSTON |ALI| 10-10-1980 | 31-03-2015 | 31846.00 | 31649.60
I've tried:
SELECT A.PERSONID, A.SURNAME, A.FORENAMES, A.DOB, B.FOLDERREF, C.DATEENDED, C.GROSSEARNINGS, C.BASICEARNINGS, C.FLUCTUATINGEARNINGS
FROM PERSON A, FOLDER B, EARNINGS C
WHERE A.PERSONID = B.FOLDERID AND B.FOLDERID = C.FOLDERID
Which extracts all of the data from the EARNINGS table, but I only wish to extract the top line.
Any advice is greatly received.
If you want just the data from the latest date then you could do something like the query below. Bear in mind, you're using fields like c.BasicEarnings and c.FluctuatingEarnings that you don't have in table 'Earnings'
SELECT a.PersonID
,a.Suranme
,a.Forenames
,a.DOB
,b.FolderRef
,c.DateEnded
,c.GrossEarnings
FROM Person a
JOIN Folder b ON a.FolderID = b.FolderID
JOIN (
SELECT e.FolderID
,e.DateEnded
,e.GrossEarnings
FROM Earnings e
JOIN (
SELECT FolderID
,MAX(DateEnded) DateEnded
FROM Earnings
GROUP BY FolderID
) m ON e.FolderID = m.FolderID
AND e.DateEnded = m.DateEnded
) c ON a.FolderID = c.FolderID
Assuming the final field in your expected output is GROSSEARNINGS and by "I only wish to extract the top line" you mean latest (by date) then use GROUP BY with a MAX function.
SELECT p.FORENAMES, p.SURNAME, p.DOB, MAX(e.DATEENDED), e.GROSSEARNINGS, e.BASICEARNINGS
FROM Person p
INNER JOIN Earnings e ON p.PERSONID = e.FOLDERID
GROUP BY p.FORENAMES, p.SURNAME, p.DOB, e.GROSSEARNINGS, e.BASICEARNINGS

Avoid third table multiplying results

I have three tables.
Defect: Main table used to store defects found.
FollowUp: Table that stores followups to a specific Defect.
Defect_Attach: Related table used all photo attachments for Defects and FollowUps.
How can I get dates for all photo attachments?
Some of these photos were taken for a defect, then at a later date, more photos were related to the defect during a followup.
The results I'm trying to get would look something like this:
or
So far my query looks like this:
SELECT d.GUID
,p.ATTACHMENTID
,p.REL_OBJECTID
,p.CONTENT_TYPE
,p.ATT_NAME
,p.DATA_SIZE
,d.DateObserved as 'Defect Date'
--,f.DateObserved as 'FollowUp Date'
FROM [ECIMUSR].[DEFECT__ATTACH] p
LEFT OUTER JOIN ECIMUSR.DEFECT d on d.ObjectID = p.REL_OBJECTID
--LEFT JOIN ECIMUSR.FOLLOWUP f on f.DefectGUID = d.GUID
WHERE
d.GUID = '{E511EA70-F5E5-11E4-8189-6C3BE50ED71F}'
ORDER BY [Defect Date]
But as soon as I try joining my third table (FOLLOWUP), my results multiply.
UPDATE:
Results:
SELECT p.ATT_NAME
,d.DateObserved as 'Defect Date'
--,f.DateObserved as 'FollowUp Date'
FROM [ECIMUSR].[DEFECT__ATTACH] p
LEFT OUTER JOIN ECIMUSR.DEFECT d on d.ObjectID = p.REL_OBJECTID
--LEFT JOIN ECIMUSR.FOLLOWUP f on f.DefectGUID = d.GUID
WHERE
d.GUID = '{E511EA70-F5E5-11E4-8189-6C3BE50ED71F}'
ORDER BY [Defect Date]
Joining THIRD Table:
SELECT p.ATT_NAME
,d.DateObserved as 'Defect Date'
,f.DateObserved as 'FollowUp Date'
FROM [ECIMUSR].[DEFECT__ATTACH] p
LEFT OUTER JOIN ECIMUSR.DEFECT d on d.ObjectID = p.REL_OBJECTID
LEFT JOIN ECIMUSR.FOLLOWUP f on f.DefectGUID = d.GUID
WHERE
d.GUID = '{E511EA70-F5E5-11E4-8189-6C3BE50ED71F}'
ORDER BY [Defect Date]
SELECT
d.DateObserved AS defect_date,
p.ATT_NAME AS photo_name,
f.DateObserved AS follow_up_date
FROM
(
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY REL_OBJECTID
ORDER BY ATT_NAME) AS ordinal
FROM
ECIMUSR.DEFECT__ATTACH
)
p
FULL OUTER JOIN
(
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY DefectGUID
ORDER BY DateObserved) AS ordinal
FROM
ECIMUSR.FOLLOWUP
)
f
ON f.DefectGUID = p.REL_OBJECTID
AND f.ordinal = p.ordinal
RIGHT JOIN
ECIMUSR.DEFECT d
ON d.ObjectID = COALESCE(f.DefectGUID, p.REL_OBJECTID)
Would give something like...
defect_date | photo_name | follow_up_date
-------------+-----------------------------+----------------
2014-12-19 | photo1.jpg | 2015-01-16
2014-12-19 | PhotoFollowUp1_20150117.jpg | 2015-03-19
2014-12-19 | PhotoFollowUp1_20150324.jpg | 2015-04-17
2014-12-19 | PhotoFollowUp1_20150417.jpg | NULL
2014-12-19 | PhotoFollowUp2_20150324.jpg | NULL
The photo names and the follow up dates have nothing to do with each others. they're just in alphabetical order with gaps if one list is longer than the other.

Rollup / recursive addition SQL Server 2008

I have a query with rollup that outputs data like (the query is a little busy, but I can post if necessary)
range subCounts Counts percent
1-9 3 100 3.0
10-19 13 100 13.0
20-29 30 100 33.0
30-39 74 100 74.0
NULL 100 100 100.0
How is it possible to keep a running summation total of percent? Say I need to find the bottom 15 percentile, in this case 3+13=16 so I would like for the last row to be returned read
range subCounts counts percent
10-19 13 100 13.0
EDIT1: here the query
select '$'+cast(+bin*10000 + ' ' as varchar(10)) + '-' + cast(bin*10000+9999 as varchar(10)) as bins,
count(*) as numbers,
(select count(distinct patient.patientid) from patient
inner join tblclaims on patient.patientid = tblclaims.patientid
and patient.admissiondate = tblclaims.admissiondate
and patient.dischargedate = tblclaims.dischargedate
inner join tblhospitals on tblhospitals.hospitalnpi = patient.hospitalnpi
where (tblhospitals.hospitalname = 'X')
) as Totals
, round(100*count(*)/cast((select count(distinct patient.patientid) from patient
inner join tblclaims on patient.patientid = tblclaims.patientid
and patient.admissiondate = tblclaims.admissiondate
and patient.dischargedate = tblclaims.dischargedate
inner join tblhospitals on tblhospitals.hospitalnpi = patient.hospitalnpi
where (tblhospitals.hospitalname = 'X')) as float),2) as binsPercent
from
(
select tblclaims.patientid, sum(claimsmedicarepaid) as TotalCosts,
cast(sum(claimsmedicarePaid)/10000 as int) as bin
from tblclaims inner join patient on patient.patientid = tblclaims.patientid
and patient.admissiondate = tblclaims.admissiondate
and patient.dischargedate = tblclaims.dischargedate
inner join tblhospitals on patient.hospitalnpi = tblhospitals.hospitalnpi
where tblhospitals.hospitalname = 'X'
group by tblclaims.patientid
) as t
group by bin with rollup
OK, so for whomever might use this for reference I figured out what I needed to do.
I added row_number() over(bin) as rownum to the query and saved all of this as a view.
Then I used
SELECT *,
SUM(t2.binspercent) AS SUM
FROM t t1
INNER JOIN t t2 ON t1.rownum >= t2.rownum
GROUP BY t1.rownum,
t1.bins, t1.numbers, t1.uktotal, t1.binspercent
ORDER BY t1.rownum
by joining t1.rownum >=t2.rownum you can get the rolling count sort of thing.
This isn't exactly what i was looking for, but it's on the same track:
http://blog.tallan.com/2011/12/08/sql-server-2012-windowing-functions-part-1-of-2-running-and-sliding-aggregates/ and http://blog.tallan.com/2011/12/19/sql-server-2012-windowing-functions-part-2-of-2-new-analytic-functions/ - check out PERCENT_RANK
CUME_DIST
PERCENTILE_CONT
PERCENTILE_DISC
Sorry for the lame answer