SELECT TOP inside INNER JOIN - sql

I created this simple database in SQL Server:
create database product_test
go
use product_test
go
create table product
(
id int identity primary key,
label varchar(255),
description text,
price money,
);
create table picture
(
id int identity primary key,
p_path text,
product int foreign key references product(id)
);
insert into product
values ('flip phone 100', 'back 2 the future stuff.', 950),
('flip phone 200', 's;g material', 1400)
insert into picture
values ('1.jpg', 1), ('2.jpg', 1), ('3.jpg', 2)
What I want is to select all products and only one picture for each product. Any help is greatly appreciated.

I'm a fan of outer apply for this purpose:
select p.*, pi.id, pi.path
from product p outer apply
(select top 1 pi.*
from picture pi
where pi.product = p.id
) pi;
You can include an order by to get one particular picture (say, the one with the lowest or highest id). Or, order by newid() to get a random one.

Have you tried using a correlated sub-query?
SELECT *, (SELECT TOP 1 p_path FROM picture WHERE product = p.id ORDER BY id)
FROM picture p
Hope this helps,

SELECT
*,
(
SELECT TOP 1 p2.p_path
FROM dbo.picture p2
WHERE p.id = p2.product
) AS picture
FROM dbo.product p
Or with join:
SELECT
*
FROM dbo.product p
INNER JOIN
(
SELECT p2.product, MIN(p2.p_path) AS p_path
FROM dbo.picture p2
GROUP BY p2.product
) AS pt
ON p.id = pt.product
But you need to change p_path to varchar type

I would use a windowing function like this:
SELECT *
FROM product
JOIN (
SELECT id, product, p_path,
row_number() OVER (PARTITION BY product ORDER BY id ASC) as RN
FROM picture
) pic ON product.id = pic.product AND pic.RN = 1
As you can see here I am selecting the picture with the lowest id (ORDER BY id ASC) -- you can change this order by to your requirements.

just group by and take min or max
left join in case there is no picture
select pr.ID, pr.label, pr.text, pr.price
, min(pic.p_path)
from product pr
left join picture pic
on pic.product = pr.ID
group by pr.ID, pr.label, pr.text, pr.price

Related

SQL Server: Query for products with matching tags

I have been pondering over this for the past few hours but I cannot find a solution.
I have a products in a table, tags in another table and a product/tag link table.
Now I want to retrieve all products which have the same tags as a certain product.
Here are the tables (simplified):
PRODUCT:
id varchar(36) (primary key)
Name varchar(50)
TAG:
id varchar(36) (primary key)
Name varchar(50)
PRODUCTTAG:
id varchar(36) (primary key)
ProductID varchar(36)
TagID varchar(36)
I find quite a few answers here on Stackoverflow talking about returning full and partial matches. However I am looking for a query which only gives full matches.
Example:
Product A has tags 1, 2, 3
Product B has tags 1, 2
Product C has tags 1, 2, 3
Product D has tags 1, 2, 3, 4
If I query for product A, only product C should be found - as it is the only one having exactly the same tags.
Is this even possible?
Yes, yes, try this way:
with aa as (
select count(*) count
from [PRODUCTTAG]
where ProductID = '19A947C0-6A0F-4A6F-9675-48FBE30A877D'
), bb as
(
select ProductID, count(*) count
from [PRODUCTTAG]
group by ProductID
)
select distinct b.ProductID
from [dbo].[PRODUCTTAG] a join
[dbo].[PRODUCTTAG] b on a.TagID = b.TagID cross join
aa join
bb on aa.count = bb.count and b.ProductID = bb.ProductID
where a.ProductID = '19A947C0-6A0F-4A6F-9675-48FBE30A877D'
declare #PRODUCTTAG table(id int identity(1,1),ProductID int,TagID int)
insert into #PRODUCTTAG VALUES
(1,1),(1,2),(1,3)
,(2,1),(2,2)
,(3,1),(3,2),(3,3)
,(4,1),(4,2),(4,3),(4,4)
;With CTE as
(
select ProductID,count(*)smallCount
FROM #PRODUCTTAG
group by ProductID
)
,CTE1 as
(
select smallCount, count(smallCount)BigCount
from cte
group by smallCount
)
,CTE2 as
(
select * from cTE c
where exists(
select smallCount from cte1 c1
where BigCount>1 and c1.smallCount=c.smallCount
)
)
select * from cte2
--depending upon the output expected join this with #PRODUCTTAG,#Product,#Tag
--like this
--select * from #PRODUCTTAG PT
--where exists(
--select * from cte2 c2 where pt.productid=c2.productid
--)
Or Tell what is final output look like ?
This is a case where I find it simpler to combine all the tags into a single string and compare the strings. But, that is painful in SQL Server until 2016.
So, there is a set based solution:
with pt as (
select pt.*, count(*) over (partition by productid) as cnt
from producttag pt
)
select pt.productid
from pt join
pt pt2
on pt.cnt = pt2.cnt and
pt.productid <> pt2.productid and
pt.tagid = pt2.tagid
where pt2.productid = #x
group by pt.productid, pt.cnt
having count(*) = pt.cnt;
This matches every product to your given product based on the tags. The having clause then ensures that the number of matching tags is the same for the two products. Because the join only considers matching tags, all the tags are the same.

how can select unique row using where/having cluse and compare with another table

i cant understand how can take unique column (remove duplication) from a table
which compare with another table data.
in my case
i have two table
i want to get unique rows from tblproduct after compireing with tblviewer as
[in table viewer first taking viewerid after that taking productid in viewer table afterthat compire with tblproduct.
actualy like that
if i take vieweris=123 two row productid select 12001&11001 after that this tblproduct productid and finaly taking the row from tblproduct which maching.
select *
from tblproduct
where productid =
(
select distinct(productid)
from tblviewer
where viewerid = 123
)
There are a few ways to do this. You can do a standard INNER JOIN to the table to filter the results:
Select Distinct P.*
From tblProduct P
Join tblViewer V On V.ProductId = P.ProductId
Where V.ViewerId = 123
Alternatively, you could use EXISTS as well - this eliminates the need to use a DISTINCT altogether:
Select *
From tblProduct P
Where Exists
(
Select *
From tblViewer V
Where V.ProductId = P.ProductId
And V.ViewerId = 123
)
Or, you could also use an IN, as suggested by the other answers:
Select *
From tblProduct
Where ProductId In
(
Select ProductId
From tblViewer
Where ViewerId = 123
)
I think you just want to use an IN clause, you will not need to use distinct
select *
from tblproduct
where productid in
(
select productid
from tblviewer
where viewerid = 123
)
I'm not sure what you're asking, but I think it is,
select *
from tblproduct
where productid in
(
select distinct(productid)
from tblviewer
)

take each maximum value of a column and get information from another table

i have two tables:
create table saller(
id_saller int IDENTITY PRIMARY KEY,
name varchar(50),
branch varchar(10)
);
create table sale(
id_sale int IDENTITY PRIMARY KEY,
amount float,
id_saller int,
CONSTRAINT fk_saller FOREIGN KEY (id_saller)REFERENCES saller(id_saller)
);
i wanna get the biggest selling value of the amount for each branch
and get the name and id of the saller in charge for the biggest selling
i tried this:
SELECT saller.name, saller.id_saller,maxv.branch, maxv.maxbranch
FROM saller
INNER JOIN sale
ON saller.id_saller = sale.id_saller
INNER JOIN (
SELECT saller.branch,saller.id_saller,MAX(sale.amount) AS maxbranch
FROM saller
INNER JOIN sale
ON saller.id_saller = sale.id_saller
GROUP BY saller.branch,saller.id_saller
) AS maxv ON(sale.id_saller = maxv.id_saller)
One way to do it if you want to return exactly one row per branch even if you have ties
SELECT branch, id_saller, name, amount
FROM
(
SELECT r.branch, s.id_saller, r.name, s.amount,
ROW_NUMBER() OVER (PARTITION BY r.branch ORDER BY s.amount DESC) rnum
FROM sale s JOIN saller r
ON s.id_saller = r.id_saller
) q
WHERE q.rnum = 1
or if you want the highest value with ties
SELECT branch, id_saller, name, amount
FROM
(
SELECT r.branch, s.id_saller, r.name, s.amount,
RANK() OVER (PARTITION BY r.branch ORDER BY s.amount DESC) rank
FROM sale s JOIN saller r
ON s.id_saller = r.id_saller
) q
WHERE q.rank = 1
Here is SQLFiddle demo
According to your question, I don't understand the presence of branch, maybe a table that has not been mentionned. But to retrieve the seller id and name, you can try this;
SELECT saller.name, saller.id_saller
FROM saller
INNER JOIN sale
ON saller.id_saller = sale.id_saller
WHERE sale.Amount = (Select Max(Amount) from sale)

How to display the record with the highest value in Oracle?

I have 4 tables with the following structure:
Table artist:
artistID lastname firstname nationality dateofbirth datedcease
Table work:
workId title copy medium description artist ID
Table Trans:
TransactionID Date Acquired Acquistionprice datesold askingprice salesprice customerID workID
Table Customer:
customerID lastname Firstname street city state zippostalcode country areacode phonenumber email
First question is which artist has the most works of artsold and how many of the artist works have been sold.
My SQL query is this:
SELECT * From dtoohey.artist A1
INNER JOIN
(
SELECT COUNT(W1.ArtistID) AS COUNTER, artistID FROM dtoohey.trans T1
INNER JOIN dtoohey.work W1
ON W1.workid = T1.Workid
GROUP BY W1.artistID
) TEMP1
ON TEMP1.artistID = A1.artistID
WHERE A1.artistID = TEMP1.artistId
ORDER BY COUNTER desc;
I am to get the whole table but I only want show only the first row which is the highest count how do I do that??
I have tried inserting WHERE ROWNUM <=1 but it shows artist ID with 1
qns 2 is sales of which artist's work have resulted in the highest average profit (i.e) the average of the profits made on each sale of worksby an artist), and what is that amount.
My SQL query is:
SELECT A1.artistid, A1.firstname FROM
(
SELECT
(salesPrice - AcquisitionPrice) as profit,
w1.artistid as ArtistID
FROM dtoohey.trans T1
INNER JOIN dtoohey.WORK W1
on W1.workid = T1.workid
) TEMP1
INNER JOIN dtoohey.artist A1
ON A1.artistID = TEMP1.artistID
GROUP BY A1.artistid
HAVING MAX(PROFIT) = AVG(PROFIT);
I'm not able to execute it
I have tried query below but still not able to get it keep getting the error missing right parenthesis
SELECT A1.artistid, A1.firstname, TEMP1.avgProfit
FROM
(
SELECT
AVG(salesPrice - AcquisitionPrice) as avgProfit,
W1.artistid as artistid
FROM dtoohey.trans T1
INNER JOIN dtoohey.WORK W1
ON W1.workid = T1.workid
GROUP BY artistid
ORDER BY avgProfit DESC
LIMIT 1
) TEMP1
INNER JOIN dtoohey.artist A1
ON A1.artisid = TEMP1.artistid
Sometimes ORA-00907: missing right parenthesis means exactly that: we have a left bracket without a matching right one. But it can also be thrown by a syntax error in a part of a statement bounded by parentheses.
It's that second cause here: LIMIT is a Mysql command which Oracle does not recognise. You can use an analytic function here:
SELECT A1.artistid, A1.firstname, TEMP1.avgProfit
FROM
(
select artistid
, avgProfit
, rank() over (order by avgProfit desc) as rnk
from (
SELECT
AVG(salesPrice - AcquisitionPrice) as avgProfit,
W1.artistid as artistid
FROM dtoohey.trans T1
INNER JOIN dtoohey.WORK W1
ON W1.workid = T1.workid
GROUP BY artistid
)
) TEMP1
INNER JOIN dtoohey.artist A1
ON A1.artisid = TEMP1.artistid
where TEMP1.rnk = 1
This uses the RANK() function which will return more than one row if several artists achieve the same average profit. You might want to use ROW_NUMBER() instead. Analytic functions can be very powerful. Find out more.
You can apply ROWN_NUMBER(), RANK() and DENSE_RANK() to any top-n problem. You can use one of them to solve your first problem too.
"however the avg profit is null."
That's probably a data issue. If one of the numbers in (salesPrice - AcquisitionPrice) is null the result will be null, and won't be included in the average. If all the rows for an artist are null the AVG() will be null.
As it happens the sort order will put NULL last. But as the PARTITION BY clause sorts by AvgProfit desc that puts the NULL results at rank 1. The solution is to use the NULLS LAST in the windowing clause:
, rank() over (order by avgProfit desc nulls last) as rnk
This will guarantee you a non-null result at the top (providing at least one of your artists has values in both columns).
1st question - Oracle does not guarantee the order by which rows are retrieved. Hence you must first order and then limit the ordered set.
SELECT * from (
SELECT A1.* From dtoohey.artist A1
INNER JOIN
(
SELECT COUNT(W1.ArtistID) AS COUNTER, artistID FROM dtoohey.trans T1
INNER JOIN dtoohey.work W1
ON W1.workid = T1.Workid
GROUP BY W1.artistID
) TEMP1
ON TEMP1.artistID = A1.artistID
WHERE A1.artistID = TEMP1.artistId
ORDER BY COUNTER desc
) WHERE ROWNUM = 1
2nd question: I believe (haven't tested) that you have that LIMIT 1 wrong. That keyword is for use with Bulk collecting.

Find the top value for each parent

I'm sure this is a common request but I wouldn't know how to ask for it formally.
I encountered this a long time ago when I was in the Army. A soldier has multiple physical fitness tests but the primary test that counts in the most recent. The soldier also has multiple marksmanship qualifications but only the most recent qualification to the weapon assigned is significant.
How do you create a view that itemizes the most significant child of the parent?
Use:
SELECT p.*, x.*
FROM PARENT p
JOIN CHILD x ON x.parent_id = p.id
JOIN (SELECT c.id,
c.parent_id,
MAX(c.date_column) AS max_date
FROM CHILD c
GROUP BY c.id, c.parent_id) y ON y.id = x.id
AND y.parent_id = x.parent_id
AND y.max_date = x.date
Assuming SQL Server 2005+:
WITH summary AS (
SELECT p.*,
c.*,
ROW_NUMBER() OVER (PARTITION BY p.id
ORDER BY c.date DESC) AS rank
FROM PARENT p
JOIN CHILD c ON c.parent_id = p.id)
SELECT s.*
FROM summary s
WHERE s.rank = 1
Although I'm not quite sure what you are implying by "itemizing", you can do something like so:
Select ..
From Soldier
Left Join FitnessTest
On FitnessTest.SoldierId = Soldier.Id
And FitnessTest.TestDate = (
Select Max(FT1.TestDate)
From FitnessTest As FT1
Where FT1.SoldierId = FitnessTest.SoldierId
)
Left Join MarksmanshipTest
On MarksmanshipTest.SoldierId = Soldier.Id
And MarksmanshipTest.TestDate = (
Select Max(MT1.TestDate)
From MarksmanshipTest As MT1
Where MT1.SoldierId = MarksmanshipTest.SoldierId
)
This assumes that a solider cannot have two test datetime values for a fitness test or a marksmanship test.
No significant differnce from previous two answer but a little more detail perhaps:
create table soldier ( soldierId int primary key,
name varchar(100) )
create table fitnessTest ( soldierId int foreign key references soldier,
occurred datetime, result int )
create table marksmanshipTest ( soldierId int foreign key references soldier,
occurred datetime, result int )
;with
mostRecentFitnessTest as
(
select
fitnessTest.soldierId,
fitnessTest.result,
row_number() over (order by occurred desc) as row
from fitnessTest
),
mostRecentMarksmanshipTest as
(
select
marksmanshipTest.soldierId,
marksmanshipTest.result,
row_number() over (order by occurred desc) as row
from marksmanshipTest
)
select
soldier.soldierId,
soldier.name,
mostRecentFitnessTest.result,
mostRecentMarksmanshipTest.result
from soldier
left outer join mostRecentFitnessTest on
mostRecentFitnessTest.soldierId = soldier.soldierId
and mostRecentFitnessTest.row = 1
left outer join mostRecentMarksmanshipTest on
mostRecentMarksmanshipTest.soldierId = soldier.soldierId
and mostRecentMarksmanshipTest.row = 1