Limit result from inner join query to 2 rows - sql

My query is giving me result from grouped data but now I want only two rows
I have tried HAVING COUNT(*) <= 2 but issue is is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
my query is
select f.CompanyName, f.EmployeeCity, f.PrioritySL ,f.EmployeeSeniorityLevel ,f.EmployeeID
from (
select ConcatKey, min(PrioritySL) as PSL
from dbo.WalkerItContacts group by ConcatKey
) as x inner join dbo.WalkerItContacts as f on f.ConcatKey = x.ConcatKey and f.PrioritySL = x.PSL
where f.PrioritySL != '10'
Company apple have 9 records I want only 2 records
my data
company name priority
a 10
a 1
a 3
b 2
b 4
b 3
b 5
c 1
c 10
c 2
my expected data
company name priority
a 1
a 3
b 2
b 3
c 1
c 2

Add a 'top 2' clause to the outer query:
select top 2 f.CompanyName, f.EmployeeCity, f.PrioritySL ,f.EmployeeSeniorityLevel ,f.EmployeeID
from (
select ConcatKey, min(PrioritySL) as PSL
from dbo.WalkerItContacts group by ConcatKey
) as x inner join dbo.WalkerItContacts as f on f.ConcatKey = x.ConcatKey and f.PrioritySL = x.PSL
where f.PrioritySL != '10'
and f.CompanyName= 'Apple'
will give you two rows. Add a order clause by in the outer query so you can control which two rows are returned.

You can phrase this more succinctly and with better performance as:
select top (2) wic.*
from (select wic,
rank() over (partition by CompanyName, ConcatKey order by PrioritySL) as seqnum
from dbo.WalkerItContacts wic
) wic
where seqnum = 1 and
wic.PrioritySL <> 10 and
wic.CompanyName = 'Apple';

I think you could solve your problem using the ROW_NUMBER() function to count the rows and filter it in the WHERE clause to only show 2 rows per group.
I think something like this might work for you:
SELECT rownum, f.CompanyName, f.EmployeeCity, f.PrioritySL,
f.EmployeeSeniorityLevel, f.EmployeeID
FROM ( SELECT ConcatKey, MIN(PrioritySL) AS PSL, ROW_NUMBER() OVER(PARTITION BY
f.CompanyName) AS rownum
FROM dbo.WalkerItContacts
GROUP BY ConcatKey) AS x
INNER JOIN dbo.WalkerItContacts AS f ON f.ConcatKey = x.ConcatKey
AND f.PrioritySL = x.PSL
WHERE f.PrioritySL != '10' AND rownum <= 2
ORDER BY f.CompanyName ASC;
Hope this helps some.

Related

I have problem in getting the min of column name TRANS the required output should be TOWN_HALL and 2 [duplicate]

This question already has answers here:
How do I limit the number of rows returned by an Oracle query after ordering?
(14 answers)
Closed 3 years ago.
SELECT
BRANCH_ADD,
COUNT(TRANS_AMOUNT) AS TRANS
FROM
(SELECT
A.BRANCH_ADD, C.TRANS_AMOUNT
FROM
BRANCHES A, ACCTS B, TRANSACTION C
WHERE
a.branch_code = b.branch_code
AND b.acct_no = c.acct_no
ORDER BY
BRANCH_ADD)
GROUP BY
BRANCH_ADD;
Output of the above query :
as text :
BRANCH_ADD TRANS
------------ ------
TOWN_HALL 2
CHAMRAJPET 4
CITY_MARKET 4
You need some subquery and filter for the count = to max
SELECT BRANCH_ADD, my_count AS max_value
FROM (
SELECT
A.BRANCH_ADD
, count(C.TRANS_AMOUNT ) my_count
FROM BRANCHES A
INNER JOIN ACCTS B ON a.branch_code=b.branch_code
INNER JOIN TRANSACTION C ON b.acct_no=c.acct_no
GROUP BY A.BRANCH_ADD;
)
WHERE my_count = (
select max(my_count)
from (
SELECT
A.BRANCH_ADD
, count(C.TRANS_AMOUNT ) my_count
FROM BRANCHES A
INNER JOIN ACCTS B ON a.branch_code=b.branch_code
INNER JOIN TRANSACTION C ON b.acct_no=c.acct_no
GROUP BY A.BRANCH_ADD;
)
)
Use ROW_NUMBER() to return the 1st row only:
SELECT t.BRANCH_ADD, t.TRANS FROM (
SELECT A.BRANCH_ADD, COUNT(C.TRANS_AMOUNT) TRANS,
ROW_NUMBER() OVER (ORDER BY COUNT(C.TRANS_AMOUNT)) rn
FROM BRANCHES A
INNER JOIN ACCTS B ON A.BRANCH_CODE = B.BRANCH_CODE
INNER JOIN TRANSACTION C ON B.ACCT_NO = C.ACCT_NO
GROUP BY BRANCH_ADD
) t
WHERE t.rn = 1
If you are using Oracle version 12+ you can also do it with FETCH:
SELECT A.BRANCH_ADD, COUNT(C.TRANS_AMOUNT) TRANS
FROM BRANCHES A
INNER JOIN ACCTS B ON A.BRANCH_CODE = B.BRANCH_CODE
INNER JOIN TRANSACTION C ON B.ACCT_NO = C.ACCT_NO
GROUP BY BRANCH_ADD
ORDER BY TRANS
FETCH FIRST 1 ROWS ONLY
You can use analytical functions
such as MIN(..) KEEP (DENSE_RANK FIRST ORDER BY ..) OVER (PARTITION BY ..):
SELECT * FROM
(
SELECT A.BRANCH_ADD, C.TRANS_AMOUNT,
MIN(C.TRANS_AMOUNT)
KEEP (DENSE_RANK FIRST ORDER BY C.TRANS_AMOUNT)
OVER (PARTITION BY 0) AS lowest
FROM BRANCHES A
JOIN ACCTS B ON B.branch_code = A.branch_code
JOIN TRANSACTION C ON C.acct_no = B.acct_no
)
WHERE TRANS_AMOUNT = lowest

Counting if name appears under a given date

I have a dataframe with a few columns, I will focus on the ones relevant to my issue.
I would like to return the value 1 for cases where a user's name appears in a row associated with a given date.
So here is an example:
user_name date
a 20/1/2019
a 20/1/2019
c 21/1/2019
c 21/1/2019
a 21/1/2019
b 20/1/2019
Using this as an example, this would be my desired output
user_name date val
a 20/1/2019 1
b 20/1/2019 1
c 20/1/2019 0
a 21/1/2019 1
b 21/1/2019 0
c 21/1/2019 1
I had though about use a case statement of sorts with the pseudo logic if name doesn't appear under a specific date then return 0 else return 1
case when user_name not in date then 0 else 1 end
but this doesn't seem to make sense
I think you want a cross join to generate rows and then logic to set the flag:
select u.user_name, d.date,
(case when exists (select 1 from t where t.user_name = u.user_name and t.date = d.date)
then 1 else 0
end) as flag
from (select distinct user_name from t) u cross join
(select distinct date from t) d ;
You may use a calendar table approach here. In the absence of any formal tables containing all dates and users, we can use subqueries in their place:
SELECT DISTINCT
u.user_name,
d.date,
CASE WHEN t.user_name IS NULL THEN 0 ELSE 1 END AS val
FROM (SELECT DISTINCT user_name FROM yourTable) u
CROSS JOIN (SELECT DISTINCT date FROM yourTable) d
LEFT JOIN yourTable t
ON t.user_name = u.user_name AND
t.date = d.date
ORDER BY
d.date,
u.user_name;
Demo

Join table on conditions, count on conditions

SELECT *, null AS score,
'0' AS SortOrder
FROM products
WHERE datelive = -1
AND hidden = 0
UNION
SELECT e.*, (SUM(r.a)/(COUNT(*)*1.0)+
SUM(r.b)/(COUNT(*)*1.0)+
SUM(r.c)/(COUNT(*)*1.0)+
SUM(r.d)/(COUNT(*)*1.0))/4 AS score,
'1' AS SortOrder
FROM products e
LEFT JOIN reviews r
ON r.productID = e.productID
WHERE e.hidden = 0
AND e.datelive != -1
GROUP BY e.productID
HAVING COUNT(*) >= 5
UNION
SELECT e.*, (SUM(r.a)/(COUNT(*)*1.0)+
SUM(r.b)/(COUNT(*)*1.0)+
SUM(r.c)/(COUNT(*)*1.0)+
SUM(r.d)/(COUNT(*)*1.0))/4 AS score,
'2' AS SortOrder
FROM products e
LEFT JOIN reviews r
ON r.productID = e.productID
WHERE e.hidden = 0
AND e.datelive != -1
GROUP BY e.productID
HAVING COUNT(*) < 5
ORDER BY SortOrder ASC, score DESC
This creates an SQL object for displaying products on a page. The first request grabs items of type datelive = -1, the second of type datelive != -1 but r.count(*) >= 5, and the third of type datelive != -1 and r.count(*) < 5. The reviews table is structured similar to the below:
reviewID | productID | a | b | c | d | approved
-------------------------------------------------
1 1 5 4 5 5 1
2 5 3 2 5 5 0
3 2 5 5 4 3 1
... ... ... ... ... ... ...
I'm trying to work it such that r.count(*) only cares for rows of type approved = 1, since tallying data based on unapproved reviews isn't ideal. How can I join these tables such that the summations of scores and the number of rows is dependent only on approved = 1?
I've tried adding in AND r.approved = 1 in the WHERE conditional for the joins and it doesn't do what I'd like. It does sort it properly, but then it no longer includes items with zero reviews.
You seem to be nearly there.
In your question you talked about adding the AND r.approved = 1 to the join criteria but by the sounds of it you are actually adding it to the WHERE clause.
If you instead properly add it to the join criteria like below then it should work fine:
SELECT *, null AS score,
'0' AS SortOrder
FROM products
WHERE datelive = -1
AND hidden = 0
UNION
SELECT e.*, (SUM(r.a)/(COUNT(*)*1.0)+
SUM(r.b)/(COUNT(*)*1.0)+
SUM(r.c)/(COUNT(*)*1.0)+
SUM(r.d)/(COUNT(*)*1.0))/4 AS score,
'1' AS SortOrder
FROM products e
LEFT JOIN reviews r ON r.productID = e.productID
WHERE e.hidden = 0
AND e.datelive != -1
GROUP BY e.productID
HAVING COUNT(*) >= 5
UNION
SELECT e.*, (SUM(r.a)/(COUNT(*)*1.0)+
SUM(r.b)/(COUNT(*)*1.0)+
SUM(r.c)/(COUNT(*)*1.0)+
SUM(r.d)/(COUNT(*)*1.0))/4 AS score,
'2' AS SortOrder
FROM products e
LEFT JOIN reviews r ON r.productID = e.productID AND r.approved = 1
WHERE e.hidden = 0
AND e.datelive != -1
GROUP BY e.productID
HAVING COUNT(*) < 5
ORDER BY SortOrder ASC, score DESC
SQL Fiddle here.
Notice again how I have simply put the AND r.approved = 1 directly after LEFT JOIN reviews r ON r.productID = e.productID which adds an extra criteria to the join.
As I mentioned in my comment, the WHERE clause will filter rows out of the combined record set after the join has been made. In some cases the RDBMS may optimise it out and put it into the join criteria but only where that would make no difference to the result set.
Calculating the non-zero sums and joining it to your result may solve it;
fiddle
SELECT a.productID,
NULL AS score,
'0' AS SortOrder
FROM products a
WHERE datelive = -1
AND hidden = 0
UNION
SELECT e.productID,
(min(x.a)/(min(x.cnt)*1.0)+ min(x.b)/(min(x.cnt)*1.0)+ min(x.c)/(min(x.cnt)*1.0)+ min(x.d)/(min(x.cnt)*1.0))/4 AS score,
'1' AS SortOrder
FROM products e
JOIN reviews r ON r.productID = e.productID
LEFT JOIN
(SELECT ee.productID,
sum(rr.a) AS a,
sum(rr.b) AS b,
sum(rr.c) AS c,
sum(rr.d) AS d,
count(*) AS cnt
FROM products ee
LEFT JOIN reviews rr ON ee.productID = rr.productID
GROUP BY ee.productID) x ON e.productID = x.productID
WHERE e.hidden = 0
AND e.datelive != -1
GROUP BY e.productID HAVING COUNT(*) >= 5
UNION
SELECT e.productID,
(min(x.a)/(min(x.cnt)*1.0)+ min(x.b)/(min(x.cnt)*1.0)+ min(x.c)/(min(x.cnt)*1.0)+ min(x.d)/(min(x.cnt)*1.0))/4 AS score,
'2' AS SortOrder
FROM products e
LEFT JOIN reviews r ON r.productID = e.productID
LEFT JOIN
(SELECT ee.productID,
sum(rr.a) AS a,
sum(rr.b) AS b,
sum(rr.c) AS c,
sum(rr.d) AS d,
count(*) AS cnt
FROM products ee
LEFT JOIN reviews rr ON ee.productID = rr.productID
GROUP BY ee.productID) x ON e.productID = x.productID
WHERE e.hidden = 0
AND e.datelive != -1
GROUP BY e.productID HAVING COUNT(*) < 5
ORDER BY SortOrder ASC,
score DESC
You could create a temp table that only contains rows where approved = 1, and then join on the temp table instead of reviews.
create table tt_reviews like reviews;
insert into tt_reviews
select * from reviews
where approved = 1;
alter table tt_reviews add index(productID);
Then replace reviews with tt_reviews in your above query.

SQL Dynamic join?

Please see http://sqlfiddle.com/#!3/2506f/2/0
I have two tables. One is a general record, and the other is a table containing related documents that link to that record.
In my example I've created a straightforward query which shows all records and their associated documents. This is fine, but I want a more complex situation.
In the 'mainrecord' table there is a 'multiple' field. If this is 0, then I only want the most recent document from the documents table (that is, with the highest ID). If it is 1, I want to join all linked documents.
So, rather than the result of the query being this:-
ID NAME MULTIPLE DOCUMENTNAME IDLINK
1 One 1 first document 1
1 One 1 second document 1
2 Two 0 third document 2
2 Two 0 fourth document 2
3 Three 1 fifth document 3
3 Three 1 sixth document 3
It should look like this:-
ID NAME MULTIPLE DOCUMENTNAME IDLINK
1 One 1 first document 1
1 One 1 second document 1
2 Two 0 fourth document 2
3 Three 1 fifth document 3
3 Three 1 sixth document 3
Is there a way of including this condition into my query to get the results I'm after. I'm happy to explain further if needed.
Thanks in advance.
WITH myData
AS
(SELECT mainrecord.*, documentlinks.documentName, documentlinks.idlink,
Row_number()
OVER (
partition BY mainrecord.ID
ORDER BY mainrecord.ID ASC) AS ROWNUM
FROM mainrecord INNER JOIN documentlinks
ON mainrecord.id = documentlinks.idlink)
SELECT *
FROM mydata o
WHERE multiple = 0 AND rownum =
(SELECT max(rownum) FROM mydata i WHERE i.id = o.id)
UNION
SELECT *
FROM myData
WHERE multiple = 1
http://sqlfiddle.com/#!3/2506f/57
Another solution (tested at SQL-Fiddle):
SELECT m.*,
d.id as did, d.documentName, d.IDLink
FROM mainrecord AS m
JOIN documentlinks AS d
ON d.IDLink = m.id
AND m.multiple = 1
UNION ALL
SELECT m.*,
d.id as did, d.documentName, d.IDLink
FROM mainrecord AS m
JOIN
( SELECT d.IDLink
, MAX(d.id) AS did
FROM mainrecord AS m
JOIN documentlinks AS d
ON d.IDLink = m.id
AND m.multiple = 0
GROUP BY d.IDLink
) AS g
ON g.IDLink = m.id
JOIN documentlinks AS d
ON d.id = g.did
ORDER BY id, did ;
This will probably do:
SELECT mainrecord.name, documentlinks.documentname
FROM documentlinks
INNER JOIN mainrecord ON mainrecord.id = documentlinks.IDLink AND multiple = 1
UNION
SELECT mainrecord.name, documentlinks.documentname
FROM (SELECT max(id) id, IDLink FROM documentlinks group by IDLink) maxdocuments
INNER JOIN documentlinks ON documentlinks.id = maxdocuments.id
INNER JOIN mainrecord ON mainrecord.id = documentlinks.IDLink AND multiple = 0
How about this:
select * from mainrecord a inner join documentlinks b on a.Id=b.IDLink
where b.id=(case
when a.multiple=1 then b.id
else (select max(id) from documentlinks c where c.IDLink=b.IDLink) end)
SQL FIDDLE
select
m.ID, m.name, m.multiple, dl.idlink,
dl.documentName
from mainrecord as m
left outer join documentlinks as dl on dl.IDlink = m.id
where
m.multiple = 1 or
not exists (select * from documentlinks as t where t.idlink = m.id and t.id < dl.id)

Select all data not in Top 'n' as 'Other'

I hope somebody may be able to point out where i'm going wrong here but i've been looking at this for the last 30 minutes and not gotten anywhere with it.
I have a temporary table that is populated with data, the front end application cannot do any logic for me so please excuse the ugly case statement logic in the table.
The user is happy with the resultset brought back as I get the top 10 records. They have now decided they want to see a group of the remaining countries (all rows not in the top 10) as 'Other'.
I have tried to create a grouping of countries not in the top 10 but it's not working, I was planning on UNION'ing this result to the top 10 results.
SELECT c.Country, count(*) AS 'Total_Number_of_customers', COALESCE(ili.new_customers,0) AS 'New_Customers', COALESCE(ilb.existing_first,0) AS 'Existing_First_Trans', COALESCE(ilc.existing_old,0) AS 'Existing_Prev_Trans'
FROM #customer_tmp c
LEFT JOIN (SELECT z.country, count(*) AS 'new_customers' FROM #customer_tmp z where z.customer_type='New_Customer' group by z.country)ili ON ili.country = c.country
LEFT JOIN (SELECT zy.country, count(*) AS 'existing_first' FROM #customer_tmp zy where zy.customer_type='Existing_Customer' AND zy.first_transaction=1 group by zy.country)ilb ON ilb.country = c.country
LEFT JOIN (SELECT zx.country, count(*) AS 'existing_old' FROM #customer_tmp zx where zx.customer_type='Existing_Customer' AND zx.first_transaction=0 group by zx.country)ilc ON ilc.country = c.country
GROUP BY c.country, ili.new_customers, ilb.existing_first, ilc.existing_old
ORDER BY 2 DESC
Here is the SQL that I use to get results from my table.
For reference, each row in my temporary table contains a customer ID, the date they were created and their customer type, which is specific to what i'm trying to achieve.
Hopefully this is a simple problem and i'm just being a bit slow..
Many thanks in Adv.
Use the EXCEPT operator in SQL Server:
SELECT <fields>
FROM <table>
WHERE <conditons>
EXCEPT
<Query you want excluded>
Yup; EXCEPT, or maybe add a row number to your query and then select by that:
SELECT * FROM (
SELECT c.Country, count(*) AS 'Total_Number_of_customers',
row_number() OVER (ORDER BY COUNT(*) DESC) AS 'r',
COALESCE(ili.new_customers,0) AS 'New_Customers', COALESCE(ilb.existing_first,0) AS 'Existing_First_Trans', COALESCE(ilc.existing_old,0) AS 'Existing_Prev_Trans'
FROM #customer_tmp c
LEFT JOIN (SELECT z.country, count(*) AS 'new_customers' FROM #customer_tmp z where z.customer_type='New_Customer' group by z.country)ili ON ili.country = c.country
LEFT JOIN (SELECT zy.country, count(*) AS 'existing_first' FROM #customer_tmp zy where zy.customer_type='Existing_Customer' AND zy.first_transaction=1 group by zy.country)ilb ON ilb.country = c.country
LEFT JOIN (SELECT zx.country, count(*) AS 'existing_old' FROM #customer_tmp zx where zx.customer_type='Existing_Customer' AND zx.first_transaction=0 group by zx.country)ilc ON ilc.country = c.country
GROUP BY c.country, ili.new_customers, ilb.existing_first, ilc.existing_old
ORDER BY 2 DESC
) sub_query WHERE sub_query.r >= 10
This may be more flexible, as you can run one query and then divide the results up into "top ten" and "the rest" quite easily.
(This is equivalent to bobs' answer; I guess we were working on this at exactly the same time!)
Here's an approach using common table expressions (CTE)
WITH CTE AS
(
SELECT c.Country, count(*) AS 'Total_Number_of_customers', COALESCE(ili.new_customers,0) AS 'New_Customers', COALESCE(ilb.existing_first,0) AS 'Existing_First_Trans', COALESCE(ilc.existing_old,0) AS 'Existing_Prev_Trans'
, ROW_NUMBER() OVER (ORDER BY count(*) DESC) AS sequence
FROM #customer_tmp c
LEFT JOIN (SELECT z.country, count(*) AS 'new_customers' FROM #customer_tmp z where z.customer_type='New_Customer' group by z.country)ili ON ili.country = c.country
LEFT JOIN (SELECT zy.country, count(*) AS 'existing_first' FROM #customer_tmp zy where zy.customer_type='Existing_Customer' AND zy.first_transaction=1 group by zy.country)ilb ON ilb.country = c.country
LEFT JOIN (SELECT zx.country, count(*) AS 'existing_old' FROM #customer_tmp zx where zx.customer_type='Existing_Customer' AND zx.first_transaction=0 group by zx.country)ilc ON ilc.country = c.country
GROUP BY c.country, ili.new_customers, ilb.existing_first, ilc.existing_old
)
SELECT *
FROM CTE
WHERE sequence > 10
ORDER BY sequence
SELECT country, COUNT(*) cnt, SUM(new_customer), SUM(existing_first_trans), SUM(existing_prev_trans)
FROM (
SELECT CASE
WHEN country IN
(
SELECT TOP 10 country
FROM #customer_tmp
ORDER BY
COUNT(*) DESC
) THEN
country
ELSE 'Others'
END AS country,
CASE WHEN customer_type = 'New_Customer' THEN 1 END AS new_customer,
CASE WHEN customer_type = 'Existing_Customer' AND first_transaction = 1 THEN 1 AS existing_first_trans,
CASE WHEN customer_type = 'Existing_Customer' AND first_transaction = 0 THEN 1 AS existing_prev_trans,
FROM #customer_tmp
)
GROUP BY
country
ORDER BY
CASE country WHEN 'Others' THEN 2 ELSE 1 END, cnt DESC