SQL Server : stored procedure many-to-many table query - sql

I have 3 tables Campaign, Mall, MallCampaign (many-to-many) and want to order nearest campaigns which are available in one or more malls.
In the first query; I am eligible for reaching the campaigns' distance which are available in different malls. But need to do it for all campaigns not only one (Id=79). I couldn't manage to do it with JOIN.
SELECT
m.MallId,
ROUND(#geo1.STDistance(geography::Point(m.MallLatitude, m.MallLongitude, 4326))/1000,1) AS Distance
FROM
MallCampaign mc
INNER JOIN
Mall m ON m.MallId = mc.MallId
WHERE
m.IsActive != 0 AND mc.CampaignId = 79
ORDER BY
Distance
SELECT
ca.CampaignId, ca.CampaignTitle
FROM
Campaign ca

Does this do what you want? It sorts by campaign id first and then by distance:
SELECT mc.CampaignId, m.MallId,
ROUND(#geo1.STDistance(geography::Point(m.MallLatitude, m.MallLongitude, 4326))/1000,1) AS Distance
FROM MallCampaign mc join
Mall m
ON m.MallId = mc.MallId
WHERE m.IsActive <> 0
ORDER BY mc.CampaignId, Distance;
EDIT:
Your original question didn't have a top in it, which I sort of expected. To handle this, you want to use row_number(). In this case, it is easiest to do two subqueries (or CTEs):
select CampaignId, MallId, Distance
from (select mc.*,
row_number() over (partition by CampaignId order by distance) as seqnum
from (SELECT mc.CampaignId, m.MallId,
ROUND(#geo1.STDistance(geography::Point(m.MallLatitude, m.MallLongitude, 4326))/1000,1) AS Distance
FROM MallCampaign mc join
Mall m
ON m.MallId = mc.MallId
WHERE m.IsActive <> 0
) mc
) mc
where seqnum = 1;

Related

Multiple WHERE functions in SQL

I'm trying to write an SQL query to return information including product code, yearly sales revenues, costs, sales people information from two different tables. I need to return the product ID information for the product with the lowest'onboarding cost' for the 'north' region.
I have used WHERE Region = 'North' to just get the product info for the North region, and ORDER BY onboarding cost; to sort this low to high and find the product with the lowest cost. Is there a way of just returning the product with the lowest onboarding cost for the north region?
In the WHERE clause you could add:
... AND onboarding cost=(SELECT MAX(onboarding cost) FROM ... WHERE region='North' ...)
SELECT
ProductRevenueAndCosts.ProductID,
ProductRevenueAndCosts.SalesRevenueYear1,
ProductRevenueAndCosts.SalesRevenueYear2,
ProductRevenueAndCosts.OperationalCostsYear1,
ProductRevenueAndCosts.OperationalCostsYear2,
ProductRevenueAndCosts.OnboardingCost,
SalesPeople.FirstName,
SalesPeople.LastName,
SalesPeople.Region
FROM SalesPeople INNER JOIN ProductRevenueAndCosts ON
SalesPeople.SalesPersonID = ProductRevenueAndCosts.SalesPersonID
WHERE SalesPeople.Region = "North"
AND ProductRevenueAndCosts.OnboardingCost=(
SELECT MAX(ProductRevenueAndCosts.OnboardingCost)
FROM SalesPeople INNER JOIN ProductRevenueAndCosts ON
SalesPeople.SalesPersonID = ProductRevenueAndCosts.SalesPersonID
WHERE SalesPeople.Region = "North"
)
ORDER BY ProductRevenueAndCosts.Onboardingcost DESC -- this is obsolete
;
But this will eventually return more than one result - in case that more than one product has the same highest onboaring cost.
Yes, you need to limit the number of records, like this:
MySQL, PostgreSQL
SELECT
ProductRevenueAndCosts.ProductID,
ProductRevenueAndCosts.SalesRevenueYear1,
ProductRevenueAndCosts.SalesRevenueYear2,
ProductRevenueAndCosts.OperationalCostsYear1,
ProductRevenueAndCosts.OperationalCostsYear2,
ProductRevenueAndCosts.OnboardingCost,
SalesPeople.FirstName,
SalesPeople.LastName,
SalesPeople.Region
FROM SalesPeople INNER JOIN ProductRevenueAndCosts ON
SalesPeople.SalesPersonID = ProductRevenueAndCosts.SalesPersonID
WHERE SalesPeople.Region = "North"
ORDER BY ProductRevenueAndCosts.Onboardingcost DESC
LIMIT 0, 1;
Where 0 is the starting index (first row) and 1 is the number of records you want to get.
SQL Server
SELECT TOP 1
ProductRevenueAndCosts.ProductID,
ProductRevenueAndCosts.SalesRevenueYear1,
ProductRevenueAndCosts.SalesRevenueYear2,
ProductRevenueAndCosts.OperationalCostsYear1,
ProductRevenueAndCosts.OperationalCostsYear2,
ProductRevenueAndCosts.OnboardingCost,
SalesPeople.FirstName,
SalesPeople.LastName,
SalesPeople.Region
FROM SalesPeople INNER JOIN ProductRevenueAndCosts ON
SalesPeople.SalesPersonID = ProductRevenueAndCosts.SalesPersonID
WHERE SalesPeople.Region = "North"
ORDER BY ProductRevenueAndCosts.Onboardingcost DESC;
Where TOP 1 tells the RDBMS that you are interested only in the very first row.
Oracle
SELECT
ProductRevenueAndCosts.ProductID,
ProductRevenueAndCosts.SalesRevenueYear1,
ProductRevenueAndCosts.SalesRevenueYear2,
ProductRevenueAndCosts.OperationalCostsYear1,
ProductRevenueAndCosts.OperationalCostsYear2,
ProductRevenueAndCosts.OnboardingCost,
SalesPeople.FirstName,
SalesPeople.LastName,
SalesPeople.Region
FROM SalesPeople INNER JOIN ProductRevenueAndCosts ON
SalesPeople.SalesPersonID = ProductRevenueAndCosts.SalesPersonID
WHERE SalesPeople.Region = "North"
ORDER BY ProductRevenueAndCosts.Onboardingcost DESC
OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY;

By store (number), list the maximum number of store visits made by a customer

The three tables that I'm linking are item_scan_fact, member_dimension and store_dimension. So far this is what I have:
SELECT
store_dimension.store_number,
member_dimension.member_number
COUNT (item_scan_fact.visit_number) AS NumVisits
FROM
member_dimension,
item_scan_fact
INNER JOIN store_dimension
ON item_scan_fact.member_key = member_dimension.member_key
AND item_scan_fact.store_key = store_dimension.store_key
GROUP BY
store_dimension.store_number,
member_dimension.member_number, NumVisits;
On the surface it appears solvable with a couple Common Table Expressions
Does this help point you in the right direction?
WITH s1 -- JJAUSSI: Find the visit_number_count by member_key and store_key
AS
(SELECT isf.member_key
,isf.store_key
-- JJAUSSI: DISTINCT resolves a potential 1:N (one to many) relationship here
,COUNT( DISTINCT isf.visit_number) AS visit_number_count
FROM item_scan_fact isf
GROUP BY isf.member_key
,isf.store_key),
s2 -- JJAUSSI: Find the visit_number_count_max by member_key
AS
(SELECT s1.member_key
,MAX(s1.visit_number_count) AS visit_number_count_max
FROM s1
GROUP BY s1.member_key)
-- JJAUSSI: Use this version to see the list of store_key values
-- that have the visit_number_count_max value. This has the potential
-- to be a 1:N relationship.
SELECT s1.member_key
,md.member_number
,s1.store_key
,sd.store_number
,s1.visit_number_count
FROM s2 INNER JOIN s1
ON s2.member_key = s1.member_key
AND s2.visit_number_count_max = s1.visit_number_count
INNER JOIN store_dimension sd
ON sd.store_key = s1.store_key
INNER JOIN member_dimension md
ON md.member_key = s1.member_key;
If this is what you were going for...congratulations! On to the next query!
If you ultimately are after a single store_key response for each member_key (basically you want to determine the member_key's "primary" store_key) then an additional step is probably needed (depending on your data).
Here are some ideas:
Evaluate the member_key based on some other summable facet of
item_scan_fact (like total price paid?)
If you consider all store_key values of equal merit that have the same visit_number_count_max value for a given member_key, just choose a store_key with MAX or MIN
You would seem to want:
SELECT member_number, MAX(NumVisits)
FROM (SELECT sd.store_number, md.member_number
COUNT(*) AS NumVisits
FROM member_dimension md JOIN
item_scan_fact isf
ON md.member_key = isf.member_key JOIN
store_dimension sd
ON isf.store_key = sd. store_key
GROUP BY sd.store_number, md.member_number
) sm
GROUP BY member_number;
If you want to return both the max and the matching customer number you can apply a Teradata SQL extension, qualify:
SELECT sd.store_number, md.member_number
COUNT(*) AS NumVisits
FROM member_dimension md JOIN
item_scan_fact isf
ON md.member_key = isf.member_key JOIN
store_dimension sd
ON isf.store_key = sd. store_key
GROUP BY sd.store_number, md.member_number
QUALIFY
rank() -- might return multiple rows with the same max, ROW_NUMBER a single row
over (partition by sd.store_number
order by NumVisits desc) = 1

How to find highest count of result set using multiple tables in SQL (Oracle)

I have four tables. Here are the skeletons...
ACADEMIC_TBL
academic_id
academic_name
AFFILIATION_TBL
academic_id*
institution_id*
joined_date
leave_date
INSTITUTION_TBL
institution_id
institution_name
REVIEW_TBL
academic_id*
institution_id*
date_posted
review_score
Using these tables I need to find the academic (displaying their name, not ID) with the highest number of reviews and the institution name (not ID) they are currently affiliated with. I imagine this will need to be done using multiple sub-select scripts but I'm having trouble figuring out how to structure it.
this will work:
SELECT at.academic_name,
it.institution_name,
Max(rt.review_score),
from academic_tbl at,
affiliation_tbl afft,
institution_tbl it,
review_tbl rt
WHERE AT.academic_id=afft.academic_id
AND afft.institution_id=it.institution_id
AND afft.academic_id=rt.academic_id
GROUP BY at.academia_name,it.instituton_id
You need an aggregated query that JOINs all 4 tables to count how many reviews were performed by each academic.
Query :
SELECT
inst.institution_name,
aca.academic_name,
COUNT(*)
FROM
academic_tbl aca
INNER JOIN affiliation_tbl aff ON aff.academic_id = aca.academic_id
INNER JOIN institution_tbl inst ON inst.institution_id = aff.institution_id
INNER JOIN review_tbl rev ON rev.academic_id = aca.academic_id AND rev.institution_id = aff.institution_id
GROUP BY
inst.institution_name,
aca.academic_name,
inst.institution_id,
aca.academic_id
NB :
added the academic and institution id to the GROUP BY clause to prevent potential academics or institutions having the same name from being (wrongly) grouped together
if the same academic performed reviews for different institutions, then you will find one row for each academic / institution couple, which, if I understood you right, is what you want
Try this one:
select
inst.institution_name
, aca.academic_name
from
academic_tbl aca
, institution_tbl inst
, affiliation_tbl aff
, review_tbl rev
, (
select
max(rt.review_score) max_score
from
review_tbl rt
, affiliation_tbl aff_inn
where
rt.date_posted >= aff_inn.join_date
and rt.date_posted <= aff_inn.leave_date
and rt.academic_id = aff_inn.academic_id
and rt.institution_id = aff_inn.institution_id
)
agg
where
aca.academic_id = inst.academic_id
and inst.institution_id = aff.institution_id
and aff.institution_id = rev.institution_id
and aff.academic_id = rev.academic_id
and rev.date_posted >= aff.join_date
and rev.date_posted <= aff.leave_date
and rev.review_score = agg.max_score
;
It might return more than one academic, if there are more with the same score (maximum one).

How get specific rows in grouped result

i need to group some data but because there are 4 store images , sql query return 4 result for every store. How can i get only one for a store by using sql query ?
select s.name,si.SHOP_IMG_PATH,count(*) amount from stab t
inner join shop s on (s.shop_id = t.shop_id)
inner join SHOP_IMG si on (s.shop_id= si.SHOP_ID)
where t.acct_id = 111 and t.CR_DATE >= sysDate - 1
group by s.name,si.SHOP_IMG_PATH
order by 3 desc,1 asc
As you see below image there a re 4 images so query can give random image
You are grouping by s.name, si.SHOP_IMG_PATH it will consider all possible combination of s.name, si.SHOP_IMG_PATH as separate you need to keep group by only s.name
Try this
SELECT a.NAME, a.PATH, a.AMOUNT
FROM (select
s.name AS 'NAME', si.SHOP_IMG_PATH AS 'PATH', count(*) AS 'AMOUNT',
ROW_NUMBER() OVER(PARTITION BY s.name
ORDER BY type si.SHOP_IMG_PATH) AS rk
from
stab t
inner join shop s on (s.shop_id = t.shop_id)
inner join SHOP_IMG si on (s.shop_id= si.SHOP_ID)
where t.acct_id = 111 and t.CR_DATE >= sysDate - 1
group by s.name
order by 3 desc,1 asc) a
WHERE a.rk = 1;
Alternative
You will get result but this is just a workaround and easy alternative to your problem but not a good one.
select s.name AS 'NAME', min(si.SHOP_IMG_PATH) AS 'PATH', count(*) AS 'AMOUNT',
from
stab t
inner join shop s on (s.shop_id = t.shop_id)
inner join SHOP_IMG si on (s.shop_id= si.SHOP_ID)
where t.acct_id = 111 and t.CR_DATE >= sysDate - 1
group by s.name
order by 3 desc,1 asc
This second query will return result as per your need
group by s.name, si.SHOP_IMG_PATH
You're telling it to differentiate them according to SHOP_IMG_PATH. Hence, it shows 4 results, one for each of those.
You'll have to drop SHOP_IMG_PATH from the select clause, if you won't let it use it.
Edit
Got your comment. What you're looking for is random aggregation. This is achieved diferently on different SQL engines. Google around for the one you're using.
If it's Oracle, as indicated by the question tag, here
I solved my problem by using below query,
select s.name,t.shop_id,(select min(SHOP_IMG_PATH) from SHOP_IMG si where shop_id =t.shop_id),count(*) amount from stab t
inner join shop s on (s.shop_id = t.shop_id)
where t.acct_id = 111 and t.CR_DATE >= sysDate - 1
group by s.name,t.shop_id
order by 4 desc,1 asc

Get percentages of larger group

The query below is kind of an ugly one so I hope I've got it spaced well enough to make it readable. The query finds the percentage of people that visit a given hospital if they are from a certain area. For instance, if 100 people live in county X and 20 go to hospital A and 80 go to hospital B the query outputs. How the heck is this sort of thing done? Let me know if I need to document the query or whatever I can do to make it clearer.
hospital A 20
hospital B 80
The query below works exactly like I want it to, but it give me thinking: how could this be done for every county in my table?
select hospitalname, round(cast(counts as float)/cast(fayettestrokepop as float)*100,2)as percentSeen
from
(
SELECT tblHospitals.hospitalname, COUNT(tblHospitals.hospitalname) AS counts, tblStateCounties_1.countyName,
(SELECT COUNT(*) AS Expr1
FROM Patient INNER JOIN
tblStateCounties ON Patient.stateCode = tblStateCounties.stateCode AND Patient.countyCode = tblStateCounties.countyCode
WHERE (tblStateCounties.stateCode = '21') AND (tblStateCounties.countyName = 'fayette')) AS fayetteStrokePop
FROM Patient AS Patient_1 INNER JOIN
tblHospitals ON Patient_1.hospitalnpi = tblHospitals.hospitalnpi INNER JOIN
tblStateCounties AS tblStateCounties_1 ON Patient_1.stateCode = tblStateCounties_1.stateCode AND Patient_1.countyCode = tblStateCounties_1.countyCode
WHERE (tblStateCounties_1.stateCode = '21') AND (tblStateCounties_1.countyName = 'fayette')
GROUP BY tblHospitals.hospitalname, tblStateCounties_1.countyName
) as t
order by percentSeen desc
EDIT: sample data
The sample data below is without the outermost query (the as t order by part).
The countsInTheCounty column is the (select count(*)..) part after 'tblStateCounties_1.countyName'
hospitalName hospitalCounts countyName countsInTheCounty
st. james 23 X 300
st. jude 40 X 300
Now with the outer query we would get
st james 0.076 (23/300)
st. jude 0.1333 (40/300)
Here is my guess. You'll have to test against your data or provide proper DDL + sample data.
;WITH totalCounts AS
(
SELECT StateCode, countyCode, COUNT(*) AS totalcount
FROM dbo.Patient GROUP BY StateCode, countyCode
)
SELECT
h.hospitalName,
hospitalCounts = COUNT(p.hospitalnpi),
c.countyName,
countsInTheCounty = tc.totalCount,
percentseen = CONVERT(DECIMAL(5,2), COUNT(p.hospitalnpi)*100.0/tc.totalCount)
FROM
dbo.Patient AS p
INNER JOIN
dbo.tblHospitals AS h
ON p.hospitalnpi = h.hospitalnpi
INNER JOIN
totalCounts AS tc
ON p.StateCode = tc.StateCode
AND p.countyCode = tc.countyCode
INNER JOIN
dbo.tblStateCounties AS c
ON tc.StateCode = c.stateCode
AND tc.countyCode = c.countyCode
GROUP BY
h.hospitalname,
c.countyName,
tc.totalcount
ORDER BY
c.countyName,
percentseen DESC;