I have three queries below (identical except for the first line of the WHERE clauses) that all work perfectly in my script. The first one queries orders for a customer, the 2nd for all orders assigned to a representative and the third are all orders period, across the whole company.
Again, they all work given their respective variables (all variables come from the same page) but I'm trying to fill columns on a table for all 3 cases.
Is there a way I can combine these and create one query that gives me the same values for each respective clause?
So, I would expect all 6 columns returned for one query. This is running on db2 so I don't know the best way to proceed but could I create a larger CASE based query?
//query on orders for this customer
SELECT
count(*) as sales_180Cust,
180/count(*) as velocityCust
FROM orders g
inner join dates i
on g.date1 = i.acyyyymmdd
WHERE g.cust = $customer
AND g.frm = $frm
AND g.cvr = $cvr
AND g.clr = $clr
AND i.aciso between current_Date - 180 DAY AND current_Date;
//orders belonging to representative
SELECT
count(*) as sales_180Rep,
180/count(*) as velocityRep
FROM orders g
inner join dates i
on g.date1 = i.acyyyymmdd
WHERE g.rep = $rep
AND g.frm = $frm
AND g.cvr = $cvr
AND g.clr = $clr
AND i.aciso between current_Date - 180 DAY AND current_Date;
//query across ALL orders
SELECT
count(*) as sales_180Company,
180/count(*) as velocityCompany
FROM orders g
inner join dates i
on g.date1 = i.acyyyymmdd
WHERE g.frm = $frm
AND g.cvr = $cvr
AND g.clr = $clr
AND i.aciso between current_Date - 180 DAY AND current_Date;
This would be another way to do this
WITH CTE AS (
SELECT g.cust
, g.rep
FROM orders g
inner join dates i
on g.date1 = i.acyyyymmdd
WHERE
g.frm = $frm
AND g.cvr = $cvr
AND g.clr = $clr
)
SELECT
count(*) as sales_180Company
, 180/count(*) as velocityCompany
, 'cust' as query
FROM CTE
WHERE cust = $customer
UNION ALL
SELECT
count(*) as sales_180Company
, 180/count(*) as velocityCompany
, 'rep' as query
FROM CTE
WHERE rep = $rep
UNION ALL
SELECT
count(*) as sales_180Company
, 180/count(*) as velocityCompany
, 'all' as query
FROM CTE
which returns e.g.
SALES_180COMPANY VELOCITYCOMPANY QUERY
---------------- --------------- -----
3 60 cust
2 90 rep
5 36 all
select q1.*, q2.*, q3.*
from
(select count(*) as sales_180Cust, 180/count(*) as velocityCust from table(values 1) t(i)) q1
, (select count(*) as sales_180Rep, 180/count(*) as velocityRep from table(values 1, 2) t(i)) q2
, (select count(*) as sales_180Company, 180/count(*) as velocityCompany from table(values 1, 2, 3) t(i)) q3
I amended your FROM and WERE clauses to show the idea.
Lots of ways to do this. UNION would be an obvious way. GROUPING SETs is a bit more clever.
create table orders(date1 int, rep int, cust int,frm int, cvr int, clr int, aci int);
create table dates(acyyyymmdd int, aciso date);
create variable $frm int default 1;
create variable $cvr int default 1;
create variable $clr int default 1;
create variable $customer int default 1;
create variable $rep int default 1;
insert into orders values (1,0,0,1,1,1,1), (1,1,1,1,1,1,1), (1,2,1,1,1,1,1), (1,3,1,1,1,1,1), (1,1,2,1,1,1,1);
insert into dates values (1, current date);
then this
SELECT
count(*) as sales_180Company
, 180/count(*) as velocityCompany
, g.cust
, g.rep
FROM orders g
inner join dates i
on g.date1 = i.acyyyymmdd
WHERE
g.frm = $frm
AND g.cvr = $cvr
AND g.clr = $clr
AND i.aciso between current_Date - 180 DAY AND current_Date
GROUP BY GROUPING SETS ( (), (cust), (rep) )
HAVING (cust = $customer AND rep is null)
OR (cust is null AND rep = $rep)
OR (cust is null AND rep is null)
gives this
SALES_180COMPANY VELOCITYCOMPANY CUST REP
---------------- --------------- ---- ----
5 36 NULL NULL
3 60 1 NULL
2 90 NULL 1
Or this... there are often a multitude of ways to do things in SQL
WITH CTE AS (
SELECT g.cust
, g.rep
FROM orders g
inner join dates i
on g.date1 = i.acyyyymmdd
WHERE
g.frm = $frm
AND g.cvr = $cvr
AND g.clr = $clr
) , CUST AS (
SELECT
count(*) as sales_180Company
, 180/count(*) as velocityCompany
FROM CTE
WHERE cust = $customer
) , REP AS (
SELECT
count(*) as sales_180Company
, 180/count(*) as velocityCompany
FROM CTE
WHERE rep = $rep
) , ALL AS (
SELECT
count(*) as sales_180Company
, 180/count(*) as velocityCompany
FROM CTE
)
SELECT * FROM CUST, REP, ALL
retuning
SALES_180COMPANY VELOCITYCOMPANY SALES_180COMPANY VELOCITYCOMPANY SALES_180COMPANY VELOCITYCOMPANY
---------------- --------------- ---------------- --------------- ---------------- ---------------
3 60 2 90 5 36
Related
Using Sql Server 2008. We have a table of jobs done. Normally for any customer, job#1 is followed by job#2 which is followed by job#3.
CustNum, JobDate, TypeJob
100, 4/10/2019, 2
100, 4/11/2019, 1
100, 4/12/2019, 2
100, 4/13/2019, 3
100, 4/13/2019, 3
222, 4/10/2019, 2
222, 4/11/2019, 1
333, 4/11/2019, 2
444, 3/1/2019, 3
444, 4/10/2019, 1
444, 4/11/2019, 2
I'm looking for all customers which have job#2 with date > job#1 (both existing) but job#3 was not entered afterward.
Customer 100 is fine with jobs 1,2,3 all completed in sequential date order. I don't want Customer 100.
Customer 222 doesn't have job#2 after job#1 so I don't want that one either.
Customer 333 doesn't have any job#1 so I don't want that one.
Customer 444 is the one I'm looking for.
Here's what I did and it works but it seems clumsy.
Select
L.CustNum,
L.JobDate1,
L.JobDate2,
R.JobDate3
From
(
--A<B has JobDate1 followed by JobDate2
Select
First.CustNum as [CustNum],
First.JobDate as JobDate1,
Second.JobDate as JobDate2
From
(
Select [CustNum], Max([JobDate]) as JobDate From tbJobs
Where [TypeJob] = 1
Group by CustNum
) First
Join
(
Select [CustNum], Max([JobDate]) as JobDate From tbJobs
Where [TypeJob] = 2
Group by CustNum
) Second
On First.CustNum = Second.CustNum
Where Second.JobDate > First.JobDate
) L
Left Outer Join
(
--A<B #and# C>A has JobDate1 followed by JobDate2 and JobDate3
Select
First.CustNum as [CustNum],
--First.JobDate as JobDate1,
--Second.JobDate as JobDate2,
Third.JobDate as JobDate3
From
(
Select [CustNum], Max([JobDate]) as JobDate From tbJobs
Where [TypeJob] = 1
Group by CustNum
) First
Join
(
Select [CustNum], Max([JobDate]) as JobDate From tbJobs
Where [TypeJob] = 2
Group by CustNum
) Second
On First.CustNum = Second.CustNum
Join
(
Select [CustNum], Max([JobDate]) as JobDate From tbJobs
Where [TypeJob] = 3
Group by CustNum
) Third
On Second.CustNum = Third.CustNum
Where Third.JobDate > First.JobDate
And Second.JobDate > First.JobDate
) R
On First.CustNum = Third.CustNum
Where JobDate3 is null
Order by CustNum
What I really would like to do is something like this:
Select ... From
(Select ...) First
Join
(Select ...) Second
Left Outer Join
(Select ...) Third
On ...
Where Second.JobDate > First.JobDate
And (Third.JobDate > First.JobDate) is null
How would I (is it possible) formulate a Where statement so it ignores any Third.JobDate <= First.JobDate and only finds rows where Third.JobDate (greater than First.JobDate) is null?
Using Sql Server 2008.
A simple aggregation query should work here:
WITH cte AS (
SELECT
CustNum,
MAX(CASE WHEN TypeJob = 1 THEN JobDate END) AS date1,
MAX(CASE WHEN TypeJob = 2 THEN JobDate END) AS date2,
MAX(CASE WHEN TypeJob = 3 THEN JobDate END) AS date3
FROM tbJobs
GROUP BY CustNum
)
SELECT CustNum
FROM cte
WHERE
COALESCE(date2, date1) > COALESCE(date1, date2) AND
(date3 < date2 OR date3 IS NULL);
The use of COALESCE in the HAVING clause ensures that a customer only passes if he has both the first and second dates present.
Use common table expressions:
;WITH First AS (
SELECT CustNum, MAX(JobDate) JobDate1
FROM tbJobs
WHERE TypeJob = 1
GROUP BY CustNum
), Second AS (
SELECT CustNum, MAX(JobDate) JobDate2
FROM tbJobs
WHERE TypeJob = 2
GROUP BY CustNum
), Third AS (
SELECT CustNum, MAX(JobDate) JobDate3
FROM tbJobs
WHERE TypeJob = 3
GROUP BY CustNum
)
SELECT f.CustNum, JobDate1, JobDate2, JobDate3
FROM First f
INNER JOIN Second s ON f.CustNum = s.CustNum AND f.JobDate1 < s.JobDate2
LEFT JOIN Third t ON s.CustNum = t.CustNum AND s.JobDate2 < t.JobDate3
WHERE JobDate3 IS NULL
I have the follow set of data
enter image description here
how can I write the sql to gives the result on right side?
that is the counting of unique id that did appeared previously for each month.
After long time of reading and reading his question, Ssiu wanted to ask the following:
So here is the test data in MS SQL: at that time he didn't clarify on postgresql
create table tmp1 (
ddate datetime
, iid int
)
insert into tmp1 values
('2017-11-01',1)
,('2017-11-02',2)
,('2017-11-03',3)
,('2017-11-04',4)
,('2017-11-05',5)
,('2017-11-06',5)
,('2017-11-07',5)
,('2017-12-01',1)
,('2017-12-02',2)
,('2017-12-03',3)
,('2017-12-04',6)
,('2017-12-05',7)
,('2018-01-01',1)
,('2018-01-02',2)
,('2018-01-03',3)
,('2018-01-04',4)
,('2018-01-05',8)
Disclaimer: The following is not the best approach for this problem. It is not applicable for more months, however it can give Ssiu a clue.
with cte(mmonth, iid) as (
select distinct convert(varchar(7), ddate, 120) mmonth
, iid
from tmp1
)
, cte_201711 as (
select * from cte where mmonth = '2017-11'
)
, cte_201712 as (
select * from cte where mmonth = '2017-12'
)
, cte_201801 as (
select * from cte where mmonth = '2018-01'
)
, cte_cnt201712 as(
select cte_201711.mmonth as mm201711
, cte_201711.iid as id201711
, cte_201712.mmonth as mm201712
, cte_201712.iid as id201712
from cte_201711
full outer join cte_201712
on cte_201712.iid = cte_201711.iid
)
, cte_cnt201801 as (
select cte_201711.mmonth as mm201711
, cte_201711.iid as id201711
, cte_201712.mmonth as mm201712
, cte_201712.iid as id201712
, cte_201801.mmonth as mm201801
, cte_201801.iid as id201801
from cte_201711
full outer join cte_201712
on cte_201712.iid = cte_201711.iid
full outer join cte_201801
on cte_201801.iid = cte_201712.iid
or cte_201801.iid = cte_201711.iid
)
--select * from cte_cnt201801 order by isnull(mm201711,'z'), isnull(mm201712,'z')
select '2017-12' mmonth, count(*) Ssiu
from cte_cnt201712
where mm201711 is null
union all
select '2018-01' mmonth, count(*) Ssiu
from cte_cnt201801
where mm201711 is null
and mm201712 is null
Note the data for the cte_cnt201801 CTE:
select * from cte_cnt201801 order by isnull(mm201711,'z'), isnull(mm201712,'z')
So the result for the above query is:
I have a database with following structure.
CREATE TABLE Party
(
PartyID INT IDENTITY
PRIMARY KEY ,
StatusID INT ,
Weigth INT ,
OldWeigth INT
);
GO
CREATE TABLE PartyLocation
(
PartyLocationID INT IDENTITY
PRIMARY KEY ,
PartyID INT FOREIGN KEY REFERENCES dbo.Party ( PartyID ) ,
LocationID INT ,
Distance INT
);
GO
CREATE TABLE PartyRole
(
PartyRoleID INT IDENTITY
PRIMARY KEY ,
PartyID INT FOREIGN KEY REFERENCES dbo.Party ( PartyID ) ,
RoleID INT
);
with some simple data.
INSERT INTO dbo.Party
( StatusID, Weigth, OldWeigth )
VALUES ( 1, -- StatusID - int
10, -- Age - int
20 -- OldAge - int
),
( 1, 15, 25 ),
( 2, 20, 30 );
INSERT INTO dbo.PartyLocation
( PartyID, LocationID, Distance )
VALUES ( 1, -- PartyID - int
1, -- LocationID - int
100 -- Distance - int
),
( 1, 2, 200 ),
( 1, 3, 300 ),
( 2, 1, 1000 ),
( 2, 2, 2000 ),
( 3, 1, 10000 );
INSERT INTO dbo.PartyRole
( PartyID, RoleID )
VALUES ( 1, -- PartyID - int
1 -- RoleID - int
),
( 1, 2 ),
( 1, 3 ),
( 2, 1 ),
( 2, 2 ),
( 3, 1 );
I want to query the following information
Return sum of Weigth of all parties that has roleID = 1 in PartyRole table
Return sum of OldWeigth of all parties that has statusID = 2
Return sum of distances of all parties that has locationID = 3
Return sum of distances of all parties that has roleID = 2
So the expected results are
FilteredWeigth FilteredOldWeigth FilteredDistance AnotherFilteredDistance
-------------- ----------------- ---------------- -----------------------
45 30 600 3600
Can we write a query that will query each table just once? If no what will be the most optimal way to query the data?
You can try this.
SELECT
FilteredWeigth = SUM(CASE WHEN RoleID = 1 AND RN_P = 1 THEN Weigth END) ,
FilteredOldWeigth = SUM(CASE WHEN StatusID = 2 AND RN_P = 1 THEN OldWeigth END),
FilteredDistance = SUM(CASE WHEN LocationID = 3 AND RN_L = 1 THEN Distance END),
AnotherFilteredDistance = SUM(CASE WHEN RoleID = 2 THEN Distance END)
FROM (
SELECT P.Weigth, P.StatusID, P.OldWeigth, PL.LocationID, PL.Distance, PR.RoleID,
RN_P = ROW_NUMBER() OVER (PARTITION BY P.PartyID ORDER BY PL.PartyLocationID),
RN_L = ROW_NUMBER() OVER (PARTITION BY PL.LocationID ORDER BY PR.PartyRoleID)
FROM Party P
INNER JOIN PartyLocation PL ON P.PartyID = PL.PartyID
INNER JOIN PartyRole PR ON P.PartyID = PR.PartyID
) AS T
the below gives
45 20 300 3600
the third column gives 300 which does not correspond to your expected result.
with q1
as
(
select sum(weigth) FilteredWeigth
from party join partyrole on party.partyid = partyrole.partyid
where partyrole.RoleID = '1'
),
q2 as
(
select sum(weigth) OldWeigth from party where StatusID = '2'
),
q3 as (
select sum(Distance) FilteredDistance
from party join PartyLocation on party.partyid = PartyLocation.partyid
where PartyLocation.locationID = '3'
),
q4 as
(
select sum(Distance) AnotherFilteredDistance
from party join partyrole on party.partyid = partyrole.partyid
join PartyLocation on party.partyid = PartyLocation.partyid
where partyrole.RoleID = '2'
)
select FilteredWeigth,OldWeigth,FilteredDistance,AnotherFilteredDistance
from q1,q2,q3,q4
When Using Individual Queries, you can achieve this using the following
Return sum of Weight of all parties that has roleID = 1 in PartyRole table
SELECT
SUM(Weight) FilteredWeigth
FROM dbo.Party P
WHERE EXISTS
(
SELECT
1
FROM dbo.PartyRole PR
WHERE PR. PartyID = P.PartyID
AND PR.RoleId = 1
)
Return sum of OldWeigth of all parties that has statusID = 2
SELECT
SUM(OldWeigth) FilteredOldWeigth
FROM dbo.Party P
WHERE EXISTS
(
SELECT
1
FROM dbo.PartyRole PR
WHERE PR. PartyID = P.PartyID
AND PR.RoleId = 2
)
Return sum of distances of all parties that has locationID = 3
SELECT
SUM(Distance) FilteredDistance
FROM dbo.PartyLocation
WHERE LocationID = 3
Return sum of distances of all parties that has roleID = 2
SELECT SUM(Distance) FROM PartyLocation PL
WHERE EXISTS
(
SELECT 1 FROM PartyRole PR
WHERE PR.PartyID = PL.PartyID
AND PR.Roleid = 2
)
If you want to get the result of all these in a single result set. then maybe you can try a pivot query. Like this
WITH CTE
AS
(
SELECT
'FilteredWeigth' ColNm,
SUM(Weigth) Val
FROM dbo.Party P
WHERE EXISTS
(
SELECT
1
FROM dbo.PartyRole PR
WHERE PR. PartyID = P.PartyID
AND PR.RoleId = 1
)
UNION
SELECT
'FilteredOldWeigth' ColNm,
SUM(OldWeigth) Val
FROM dbo.Party P
WHERE EXISTS
(
SELECT
1
FROM dbo.PartyRole PR
WHERE PR. PartyID = P.PartyID
AND PR.RoleId = 2
)
UNION
SELECT
'FilteredDistance' ColNm,
SUM(Distance) Val
FROM dbo.PartyLocation
WHERE LocationID = 3
UNION
SELECT
'AnotherFilteredDistance' ColNm,
SUM(Distance) Val FROM PartyLocation PL
WHERE EXISTS
(
SELECT 1 FROM PartyRole PR
WHERE PR.PartyID = PL.PartyID
AND PR.Roleid = 2
)
)
SELECT
*
FROM CTE
PIVOT
(
SUM(Val)
FOR ColNm IN
(
[FilteredWeigth],[FilteredOldWeigth],[FilteredDistance],[AnotherFilteredDistance]
)
)Pvt
The Result Will be
I could think of only three possible options:
Union query with four different select statements as answered by #ab-bennett
Join all tables then use select statements as answered by sarslan
Mix of 1 and 2, based on experiments
Coming to the question you asked:
Can we write a query that will query each table just once?
Assuming best performance is the goal, following could happen in each of the above cases:
All select statements would have their own where clause. This would perform best when where produces few rows compared to the count(*). Note that Joins are terrible for very large tables.
A join is made once, and the desired output is obtained from the same Joined table. This would perform optimal when where produces significant number of rows and the table is not too big to join.
You can mix JOIN / IN / EXISTS / WHERE to optimize your queries based on number of rows you are having in table. This approach could be used when your dataset cardinality might not vary a lot.
I have a view which is defined by the following code
CREATE VIEW [dbo].V_SOME_VIEW AS
WITH all_dates AS (SELECT DISTINCT(read_dtime) AS date FROM t_periodic_value),
theObjects AS (SELECT * FROM t_object)
SELECT
ad.date,
objs.id,
pv1.value as theValue
FROM all_dates ad
LEFT JOIN theObjects objs ON
objs.start_date <= ad.date AND (objs.end_date IS NULL OR (objs.end_date IS NOT NULL AND objs.end_date >= ad.date))
LEFT JOIN t_periodic_value pv1 ON pv1.data_point_id = (SELECT id FROM t_data_point WHERE object_id = objs.id AND measurement_id = 'MonthlyValue')
AND pv1.read_dtime = ad.date AND pv1.latest_ind = 1
GO
Which if I run a select for any given month gives me output along the lines of :
Date | ID | theValue
01/01/1990 | someFacility | 1000
02/01/1990 | someFacility | NULL
03/01/1990 | someFacility | NULL
...
and so on for the rest of the month. Nulls are returned for every date except the first as the value is calculated on a monthly basis. Is there a way I can define the view so that for every other day in the month, the value from the 1st is used?
Use a window function:
CREATE VIEW [dbo].V_SOME_VIEW AS
WITH all_dates AS (
SELECT DISTINCT(read_dtime) AS date
FROM t_periodic_value
),
theObjects AS ( -- no idea why you are doing this
SELECT *
FROM t_object
)
SELECT ad.date, objs.id,
SUM(pv1.value) OVER (PARTITION BY YEAR(ad.date), MONTH(ad.date)) as theValue
FROM all_dates ad LEFT JOIN
theObjects objs ON
ON objs.start_date <= ad.date AND (objs.end_date IS NULL OR (objs.end_date IS NOT NULL AND objs.end_date >= ad.date)) LEFT JOIN
t_periodic_value pv1
ON pv1.data_point_id = (SELECT id FROM t_data_point WHERE object_id = objs.id AND measurement_id = 'MonthlyValue')
AND pv1.read_dtime = ad.date AND pv1.latest_ind = 1
GO
select * from non_bidders_report_view
where applicant_category_id =1314
and applicant_status_id not in(10,11)
and partner_id = 4
and applicant_status_id <> 6
and applicant_id not in (
Select apb.applicant_id
from applicant_property_bids apb
inner join applicants a on
a.applicant_id=apb.applicant_id
where to_date(apb.bid_Date) >= to_date('30/4/2012','dd/mm/yyyy')
and to_date(apb.bid_Date) <= to_date('30/4/2015','dd/mm/yyyy')
and a.partner_id = 4 group by apb.applicant_Id
union
select aba.applicant_Id from Archive_Bid_Applicants aba
inner join applicants a on a.applicant_id=aba.applicant_id
where to_date(aba.bid_Date) >= to_date('30/4/2012','dd/mm/yyyy')
and to_date(aba.bid_Date) <= to_date('30/4/2015','dd/mm/yyyy')
and a.partner_id = 4 group by aba.applicant_Id
);
You can try this query:
select * from non_bidders_report_view nb
where applicant_category_id = 1314 and partner_id = 4
and applicant_status_id not in (6, 10, 11)
and not exists (
select 1 from applicant_property_bids abp
join applicants a on a.applicant_id=abp.applicant_id and a.partner_id=4
and abp.bid_Date between date '2012-04-30' and date '2015-04-30'
where abp.applicant_id = nb.applicant_id )
and not exists (
select 1 from archive_bid_applicants aba
join applicants a on a.applicant_id=aba.applicant_id and a.partner_id=4
and aba.bid_Date between date '2012-04-30' and date '2015-04-30'
where aba.applicant_id = nb.applicant_id )
The idea is to get rid of group by and union which seems to be unnecesary here and change not in to not exists.
Alternative solution:
select * from non_bidders_report_view nb
where applicant_category_id = 1314 and partner_id = 4
and applicant_status_id not in (6, 10, 11)
and not exists (
select 1 from (
select applicant_id, bid_date from applicant_property_bids
union all
select applicant_id, bid_date from archive_bid_applicants
) ab
join applicants a on a.applicant_id=ab.applicant_id and a.partner_id=4
and ab.bid_Date between date '2012-04-30' and date '2015-04-30'
where ab.applicant_id = nb.applicant_id )
If you have millions of data then create index on primary key. It will increase your performance. Indexes helps to speed up data retrieval. Create index on all 3 tables.