SQL: JOIN two tables with distinct rows from one table - sql

This might be a very simple problem but I can't seem to get my head around this since last night.
I have 3 tables
VirtualLicense
VirtualLicenseId ProductName
-----------------------------------
1 Transaction
2 Query
3 Transaction
Product
ProductId Name
---------------------------
1 Transaction
2 Query
License
LicenseId ExpiryDate ProductId
-----------------------------------------
1 14/07/2013 1
2 13/07/2013 1
3 13/07/2013 2
4 14/07/2013 2
The VirtualLicense and License are joined using ProductName and ProductId mapping using the Product table.
I want to get combination of VirtualLicenseId and LicenseId, where I can basically assign the VirtualLicenseId to a LicenseId. Once a licenseid is assigned to a VirtualLicenseId, it should not be available for the following VirtualLicenseIds. Also, I want that the licenseid for which the expirydate is nearer(smaller) should be assigned first.
So, the result for my example data set should be
VirtualLicenseId LicenseId
---------------------------------
1 2
2 3
3 1
I do not want to loop over any of the tables for this.
I hope my problem is clear from my description and data.

You can do something like this:
In first CTE - assign rankings for VirtualLicenses within the Product groups.
In second CTE - assign rankings for Licensce within the Product groups (order by exp. date)
And at the end just join the two subqueries on productID and ranking.
WITH CTE_VL AS
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY ProductId ORDER BY vl.VirtualLicenseId ASC) RN
FROM dbo.VirtualLicense vl
LEFT JOIN dbo.Product p ON vl.ProductName = p.Name
)
,CTE_License AS
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY ProductId ORDER BY ExpiryDate ASC) RN
FROM dbo.License
)
SELECT VirtualLicenseId, LicenseId
FROM CTE_VL vl
LEFT JOIN CTE_License l ON vl.ProductId = l.ProductID AND vl.RN = l.RN
SQLFiddle DEMO

Related

SQL How Do I Display All Products for an Order In Separate Columns?

I'm working with a table in the following format
Order_ID
Product_Name
1
A
1
B
2
B
2
C
3
A
3
C
3
B
I need to query the data to output like this
Order_ID
Product_1
Product_2
Product_3
Etc.
1
A
B
2
B
C
3
A
C
B
Basically, I need to show all, or at least several, product_names for a given order_id as separate columns. I found a few answers that suggested using self joins to do this, but that doesn't seem practical when I need to show more than two products.
This is in BigQuery.
This general operation is called pivoting.
Depending on your database and its SQL dialect, there will be different optimal approaches for this. Snowflake and BigQuery, for example, have a PIVOT table operation that does specifically this.
On BigQuery:
with numbered as (
select
*,
'Product_' || row_number() over (
partition by OrderID order by ProductName asc
) as n
from my_table
)
select
*
from numbered pivot(
max(ProductName)
for n in ('Product_1', 'Product_2', 'Product_3')
)
group by Order_ID
If your dialect doesn't support this, but does support window functions like row_number, then you can manually do something like this to support a pre-defined number of products:
with numbered as (
select *, row_number() over (partition by OrderID order by ProductName asc) as n
from my_table
)
select
OrderID,
max(case when n = 1 then ProductName end) as Product_1,
max(case when n = 2 then ProductName end) as Product_2,
...
from numbered
group by Order_ID
.. I need to show all, or at least several, product_names for a given order_id as separate columns
Consider below
select * from (
select *, row_number() over(partition by Order_ID) index
from your_table
)
pivot (any_value(Product_Name) Product for index in (1,2,3,4,5))
if applied to sample data in your question - output is

Only run SQL Query if there are multiple rows

I have a table similar to the below - OrderStatusId is the PK on the table. StatusId is a FK to the Reference Table that contains all the possible statuses. CustomerId is a FK to the Customer Details table
OrderStatusId StatusId CustomerId DateModified
1 1 1 05/09/2017
2 1 1 06/09/2017
3 2 1 07/09/2017
4 1 2 07/09/2017
What I want to do is run this query below to select the most recent Modified Date for that Customer for Status ID = 1. However, I only want to run this query if there is a count greater than 1 for the StatusId = 1 for that customer. So in my sample table above I would expect 06/09/2017 returned for CustomerId 1 but nothing to be returned for CustomerId 2. I tried a Having count(*) > 1 clause but could not get it working correctly.
SELECT TOP(1) os.DateModified AS LastModifiedDateForm
FROM [myTable].[OrderStatus] os WHERE os.CustomerId = 1 AND os.StatusID = 1
ORDER BY os.DateModified DESC
can you just use MAX instead of top 1? or are you returning more columns?
SELECT MAX(os.DateModified) AS LastModifiedDateForm
FROM [myTable].[OrderStatus] os
WHERE os.CustomerId = 1
AND os.StatusID = 1
GROUP BY CustomerId,
StatusId
HAVING COUNT(*) > 1
Maybe you are after the maximum date per customer, only if they have more than one date?
select max(os.DateModified) AS LastModifiedDateForm
from [myTable].[OrderStatus] os
group by os.CustomerId
having count(1) > 1
This will only give you data for customer with id 1, in the current situation, or every other customer id that has more than 1 row for some other sample data.
another approach using ROW_NUMBER()
with firstonly as (
select CustomerId, DateModified, ROW_NUMBER() over (partiton by CustomerId order by DateModified desc) rownumDesc, ROW_NUMBER() over (partiton by CustomerId order by DateModified) rownumAsc
FROM [myTable].[OrderStatus] os WHERE os.CustomerId = 1 AND os.StatusID = 1
)
select *
from firstonly
where rownumDesc = 1
and rownumAsc != 1
;
works for all customers at once

SQL select x number of rows from table based on column value

I'm looking for a way to select top 3 rows from 4 vendors from a table of products, following this criteria:
Must select 4 vendors.
Must select top 3 products for each vendor ordered by product rating.
I tried doing something like:
select top 12 * product, vendor
from products
order by productrating
but obvisously that goesn't give me 3 products for each vendor.
The product table has:
productid (int), productname (nvarchar(500)), productrating (float),
vendor (id), price (float).
These are the relevant columns.
You can use the ANSI standard row_number() function to get 3 products for each vendor:
select p.*
from (select p.*,
row_number() over (partition by vendor order by rating desc) as seqnum
from products p
) p
where p.seqnum <= 3
If you want 4 vendors:
select top 12 p.*
from (select p.*,
row_number() over (partition by vendor order by rating desc) as seqnum
from products p
) p
where p.seqnum <= 3
order by vendor;
This will give you top 3 Products per vendor. You didn't specify how you're selecting the 4 vendors. That logic could easily be included using the WHERE clause or using a different ORDER BY depending on how you select the 4 vendors.
SELECT TOP 12 vnd.Vendor, apl.ProductName
FROM Vendors vnd
CROSS APPLY (
SELECT TOP 3 ProductID
FROM Products prd
WHERE vnd.ProductID = prd.ProductID
ORDER BY prd.ProductRating DESC
) apl
ORDER BY vnd.VendorName
If you have fixed list of vendors you would like to query you can use the following approach:
SELECT TOP 3
p.ProductID
FROM Products p
WHERE p.ProductID IN ( SELECT v.ProductID
FROM Vendors v
WHERE v.VendorID IN (Vendor1ID, Vendor2ID, Vendor3ID, Vendor4ID)
ORDER BY p.ProductRating DESC
You will need to look for a work-around if you have vendor names to select them filtering out by its name but keeping the join coindition trhough its IDs.

Select several latest values for different IDs

I have the following table:
serialNumber value masterID
1 red 500
1 blue 501
2 red 502
2 green 503
2 black 504
I need a single query that returns the following
serialNumber value
1 blue
2 black
I mean, I need the latest data that was inserted in the table for each serialNumber. MasterID is an incremental identity (PK).
I only managed to retrieve the last value for a single row (using top 1 and order by masterid desc) but I need to retrieve lot of data and making a query for each serialNumber is not viable.
Thanks!
You can select your Max(MasterID) from a subquery, then join the results back to the table to get the value for that max(masterId):
SELECT t1.serialNumber,
value,
masterID
FROM TABLE t1
INNER JOIN (
SELECT MAX(masterID) AS maxid,
serialNumber
FROM TABLE
GROUP BY serialNumber
) t2
ON t1.masterid = t2.maxid
AND t1.serialNumber = t2.serialNumber
If you are using SQL SERVER then try this.
;WITH cte
AS (SELECT Row_number()
OVER(
partition BY serialNumber
ORDER BY masterID DESC) rn,
serialNumber,
value,
masterID)
SELECT serialNumber,
value
FROM cte
WHERE rn = 1

SQL Group By Question

I have a table which tracks views of products.
TrackId ProductId CreatedOn
1 1 01/01/2011
2 4 01/01/2011
3 4 01/01/2011
4 10 01/01/2011
What I want to do is return a dataset which doesn't have two ProductIds next to each other. I.E from the above data set I would want to return:
TrackId ProductId CreatedOn
1 1 01/01/2011
2 4 01/01/2011
4 10 01/01/2011
I can't use distinct as far as I am aware as this is row based?
Help appreciated.
Generate a row number sequence per ProductID, take the first
;WITH cte AS
(
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY ProductID ORDER BY TrackID) AS rn
FROM
MyProductTable
)
SELECT
TrackId ProductId CreatedOn
FROM
cte
WHERE
rn = 1
Edit:
If you want to use an aggregate, you need a separate subquery first to ensure consistent results. A straight MIN won't work.
This is based on my comment to the question
"not having productid in two adjacent rows. Adjacent is defined by next/previous Trackid"
SELECT
M.*
FROM
myProductTable M
JOIN
( --gets the lowest TrackID for a ProductID
SELECT ProductID, MIN(TrackID) AS MinTrackID
FROM myProductTable
GROUP BY ProductID
) M2 ON M.ProductID= M2.ProductID AND M.TrackID= M2.MinTrackID
select min(TrackId), ProductId, CreatedOn
from YourTable
group by ProductId, CreatedOn;
You can GroupBy on the TrackID and ProductID and do a Min of the CreatedOn if the date is not important.
SELECT TrackID ,ProductID ,MIN(CreatedOn)
FROM [table]
GROUP BY TrackID ,ProductID
If the date is the same you can group by all three
SELECT TrackID ,ProductID ,CreatedOn
FROM [table]
GROUP BY TrackID ,ProductID ,CreatedOn