Last two row from multiple records assistance - sql

I’m having a little problem that I’m not sure how to get a round and hoping someone here can assist. What I need to do is run a select on multiple records and retrieve the last two records of p.CustID. When enter in one p.CustID the code works fine however I need to remove the where clause and I need it to retrieve the last two records for each p.CustID (about 14,000 records in total) When I remove the where clause it only returns two records in total which are top two records in my from statement [DB_User].[dbo].[P1ASellers]. I tried using this in a CTE but still cannot get this to return
Code I’m using below:
SELECT TOP (2)
cbc.StorePartnerCustConfigID,
p.CustID,
cbc.ConfigID,
cbc.EffectiveDate,
ROW_NUMBER() OVER (ORDER BY cbc.StorePartnerID DESC) AS RowNum
FROM [DB_User].[dbo].[P1ASellers] p
INNER JOIN [ACA].dbo.tblConfig_StorePartnerConfig BP
ON BP.EntityID=CAST(p.CustID AS VARCHAR)
INNER JOIN [ACA].dbo.tblConfig_StorePartner CBP
ON CBP.StorePartnerID=BP.StorePartnerID
INNER JOIN [ACA].dbo.tblConfig_StorePartnerCustConfig CBC
ON CBP.StorePartnerID=CBC.StorePartnerID
AND cbc.ProcessConfigID IN (1,2,3,4)
INNER JOIN [ACA].dbo.tblConfig_StorePartnerCustConfig CBC2
ON CBC.StorePartnerID=CBC2.StorePartnerID
AND cbc2.ConfigID IN (1,2,3,4) where p.CustID=55555 <-need to remove the
where
ORDER BY cbc.StorePartnerID DESC
The results from the query
StorePartnerCustConfigID CustID ConfigID EffectiveDate RowNum
15031 55555 4 2015-06-25 1
15032 55555 1 2015-06-25 2
What I actually get after I remove the where clause:
StorePartnerCustConfigID CustID ConfigID EffectiveDate RowNum
68995 89566 2 2011-03-02 1
68996 89566 1 2011-03-02 2
what I expect after I remove the where clause:
StorePartnerCustConfigID CustID ConfigID EffectiveDate RowNum
15031 55555 4 2015-06-25 1
15032 55555 1 2015-06-25 2
64584 65486 2 2013-04-16 1
64585 65486 1 2013-04-16 2
So on and so on.......
Any input greatly appreciated, thanks!!

I think you are looking for top 2 records for each customer which you can get as below:
SELECT TOP (2) with ties
cbc.StorePartnerCustConfigID,
p.CustID,
cbc.ConfigID,
cbc.EffectiveDate,
ROW_NUMBER() OVER (ORDER BY cbc.StorePartnerID DESC) AS RowNum
FROM [DB_User].[dbo].[P1ASellers] p
INNER JOIN [ACA].dbo.tblConfig_StorePartnerConfig BP
ON BP.EntityID=CAST(p.CustID AS VARCHAR)
INNER JOIN [ACA].dbo.tblConfig_StorePartner CBP
ON CBP.StorePartnerID=BP.StorePartnerID
INNER JOIN [ACA].dbo.tblConfig_StorePartnerCustConfig CBC
ON CBP.StorePartnerID=CBC.StorePartnerID
AND cbc.ProcessConfigID IN (1,2,3,4)
INNER JOIN [ACA].dbo.tblConfig_StorePartnerCustConfig CBC2
ON CBC.StorePartnerID=CBC2.StorePartnerID
AND cbc2.ConfigID IN (1,2,3,4)
ORDER (row_number() over(partition by p.CustId Order BY cbc.StorePartnerID DESC-1)/2 +1 )

Related

Count current job with a partition

The next code makes a join between users and payment, to get the last payment.
The query should work if the payment table did not contain duplicated rows with the same max_date as the following one.
Something to notice, is that the row is not completely duplicated, sometimes contains little changes. But we do not care if we select the 'right' one, we only need it to be one, no matter which one of those is.
user_ID | Payment | date | product | credit_card
1 300 1/1/2020 A No
1 300 1/1/2020 Null | No
1 300 1/1/2021 A Yes
1 300 1/1/2021 Null | Yes
This causes the second inner join to duplicate rows because it makes a match twice with the maxDate which is 1/1/2021
SELECT a.*, c.*
FROM users a
INNER JOIN payments c
ON a.id = c.user_ID
INNER JOIN
(
SELECT user_ID, MAX(date) maxDate
FROM payments
GROUP BY user_ID
) b ON c.user_ID = b.user_ID AND
c.date = b.maxDate
I'm looking for a way to select only the first match of the maxDate. Any clue is welcome, thank in advance for any help.
You should be using window functions for this. That would be:
SELECT u.*, p.*
FROM users u JOIN
(SELECT p.*,
ROW_NUMBER() OVER (PARTITION BY p.user_id ORDER BY p.date DESC) as seqnum
FROM payments p
) p
ON p.user_ID = u.id AND p.seqnum = 1;
This returns one row, but which row is arbitrary.
Note the use of meaningful table aliases in the query -- u for users and p for `payments. Don't use meaningless letters. They just make the query hard to read -- and to maintain.

SQL - Tricky Merging

I'm facing a tricky query to do. I hope your expertise will help me to sort it out.
There are 2 tables:
Table1 : Orders
Index ProductName OrderDate
0 a 03/03/1903
1 a 10/03/2014
2 b 01/01/2017
3 c 01/01/2019
Table2 : Product Specs
--> This table shows every change made in the Color of our products
Index ProductName Color ColorUpdatedOn
0 a Blue 01/01/1900
1 a Red 01/01/2014
2 a Yellow 01/01/2017
3 b Pink 01/01/2017
4 c Black 01/01/2018
5 c Black 31/12/2018
I would like to be able to retrieve all the data from Table1 with the Column Color et UpdatedOn
Index ProductName OrderDate Color ColorUpdatedOn
0 a 03/03/1903 Blue 01/01/1900
1 a 10/03/2014 Red 01/01/2014
2 a 01/01/2019 Yellow 01/01/2017
3 c 01/01/2019 Black 31/12/2018
Do you have any idea how I could do this ?
Thank you in advance for your help
Largo
Get the max() date of Product Specs table based on color,
then join it using year() function, applicable on mysql and mssql, not sure with other db.
select o.Index, o.ProdcutName, o.Date, t1.color, t1.ColorUpdatedOn
from Orders o
inner join
(select color, max(colorupdatedon) as ColorUpdatedOn
from productspecs
group by color) t1 on year(o.OrderDate) = year(t1.createdon)
but I would prefer using right() function since your year dates are at the end.
select o.Index, o.ProdcutName, o.Date, t1.color, t1.ColorUpdatedOn
from Orders o
inner join
(select color, max(colorupdatedon) as ColorUpdatedOn
from productspecs
group by color) t1 on right(o.OrderDate, 4) = right(t1.createdon, 4)
In a database that supports lateral joins (which is quite a few of them now), this is pretty easy:
select o.*, s.* -- select the columns you want
from orders o left join lateral
(select s.*
from specs s
where s.ProductName = o.ProductName and
s.ColorUpdatedOn <= o.OrderDate
order by s.ColorUpdatedOn desc
fetch first 1 row only
) s
on 1=1;
In SQL Server, this would use outer apply rather than left join lateral.
In other databases, I would use lead():
select o.*, s.* -- select the columns you want
from orders o left join
(select s.*,
lead(ColorUpdatedOn) over (partition by ProductName order by ColorUpdatedOn) as next_ColorUpdatedOn
from specs s
) s
on s.ProductName = o.ProductName and
o.OrderDate >= s.ColorUpdatedOn and
(o.OrderDate < s.next_ColorUpdatedOn or s.next_ColorUpdatedOn is null)
Assuming, the datatype for OrderDate and ColorUpdatedOn are both date, we can find the colors which was at the time of order.
For this I have used the anlytical/windowing function. The Hive query would look like this:
SELECT
y.ProductName, y.OrderDate, y.Color, y.ColorUpdatedOn
FROM (
SELECT
x.*,
DENSE_RANK() OVER(PARTITION BY x.ProductName, x.OrderDate ORDER BY x.recency ASC) AS relevance
FROM (
SELECT
a.*, b.color, b.ColorUpdatedOn, DATEDIFF(a.OrderDate, b.ColorUpdatedOn) AS recency
FROM
Order a
INNER JOIN
Product b
ON (
a.ProductName = b.ProductName
AND a.OrderDate >= b.ColorUpdatedOn
)
) x
) y
WHERE
y.relevance = 1;
The query could be made specific if you let me know the database you are using.
Let me know if it helps.

How to find max value in SQL Server 2014?

I have a table named StatementSummary.
SELECT *
FROM StatementSummary
WHERE AccountID = 1234
Results
StatementId StatementDate AccountId AmountDue
-------------------------------------------------
100 2017-10-16 1234 600
99 2017-09-16 1234 500
98 2017-08-16 1234 400
I have another table that has a list of Accounts. I am trying to give results that show the last AmountDue for each account
My code:
SELECT
AccountID,
(SELECT MAX(StatementDate)
FROM StatementSummary
GROUP BY AccountID) LastStatementDate,
AmountDue
FROM
Accounts A
INNER JOIN
StatementSummary S ON A.AccountId = S.AccountId
Basically, I want to show all the details of the last statement for every AccountId.
You can use the SQL Server Windowing functions in cases like this.
SELECT DISTINCT
a.AccountId,
FIRST_VALUE(s.StatementDate) OVER (PARTITION BY s.AccountId ORDER BY s.StatementDate DESC) As LastStatementDate,
FIRST_VALUE(s.AmountDue) OVER (PARTITION BY s.AccountId ORDER BY s.StatementDate DESC) As LastAmountDue
FROM Accounts a
INNER JOIN StatementSummary s
ON a.AccountId = s.AccountId
Basically what happens is the OVER clause creates partitons in your data, in this case, by the account number (these partitions are the windows). We then tell SQL Server to sort the data within each partition by the statement date in descending order, so the last statement will be at the top of the partition, and then the FIRST_VALUE function is used to just grab the first row.
Finally, since you perform this operation for every account/statement combo between the two tables, you need the DISTINCT to say you just want one copy of each row for each account.
There are quite a bit of useful things you can do with the windowing functions in SQL Server. This article gives a good introduction to them: https://www.red-gate.com/simple-talk/sql/learn-sql-server/window-functions-in-sql-server/
Derived Table over row numberand left join - to display all accounts regardless if there is a statement
select *
from
(select row_number() over (partition by accountid order by statementdate desc) rn,
accountid, statementdate,amount
from statementtable
) l
left outer join accountstable a
on l.accountid = a.accountid
And l.rn = 1
That's sounds like a job for me sings lateral join aka cross apply in T-SQL.
SELECT a.*, last_ss.*
FROM Accounts A
cross apply (
select top 1 *
from StatementSummary S ON A.AccountId = S.AccountId
order by StatementDate desc
) last_ss
Alternatively you can use CTE to get last date for account:
; with l as (
select accountid, max(StatementDate)
from StatementSummary
group by accountid
)
select ...
from Accounts a
inner join l on l.accountid = a.accountid
inner join StatementSummary ss on ss.accountid = a.accountid
and l.StatementDate = ss.StatementDate

Inner Join without repeating value, keeping the rows of the first table

I have a table with the discount for each invoice. For example:
Invoice Number|Discount
------------------------
1 | 3
2 | 5
3 | 6
I need to pull these discounts to the invoice lines table (because they only apply to the total of the invoice, not to a particular line). At the same time I cannot lose any line.
Example: If the invoice 1 has 5 lines, I need all lines to show up (the 5 lines of the invoice), but I want the discount only once (for example, the first line would be enough).
Expected:
Invoice Number|Discount
------------------------
1 | 3
1 | null
1 | null
1 | null
1 | null
If I have an Invoice table, and a InvoiceLines table that can be joined by the invoice number in both tables, how can I get the result that I need?
I tried this query without success:
Select
ROW_NUMBER() over(order by v.num_fra)as Rank,
l.*,
v.ctdrap_div as discount
from ffac_vta v --(invoicetable)
join ffac_hla l --(invoice lines table)
ON v.num_fra = l.num_fra
Can you help me?
Here is another way to do this...Basically, your sub query pulls the line item info - and gets the row number (partitioned by order number). Then, you LEFT OUTER JOIN that subset to the table with the discount value ONLY when the row number = 1. This approach doesn't require a CASE statement since the LEFT OUTER JOIN will give you NULL values for all row numbers above 1.
SELECT Sub.*,
V.ctdrap_div AS [Discount]
FROM
(
SELECT *,
ROW_NUMBER() OVER(PARTITION BY v.num_fra ORDER BY v.num_fra) AS [Row Number]
FROM ffac_hla L
) Sub
LEFT OUTER JOIN ffac_vta V
ON v.num_fra = Sub.num_fra
AND Sub.[Row Number] = 1
It seems like you should be able to just change your join to a left join.
Select
ROW_NUMBER() over(order by v.num_fra)as Rank,
l.*,
v.ctdrap_div as discount
from ffac_vta v --(invoicetable)
left join ffac_hla l --(invoice lines table)
ON v.num_fra = l.num_fra
You will need to use the key of your lines table or something to order your rows. Something like this. Also, do we have ROW_NUMBER() in SQL-Server-2008?
SELECT T.num_fra,
CASE WHEN T.rank = 1 THEN T.Discount
ELSE NULL AS Discount
FROM
(
Select
ROW_NUMBER() over(PARTITION BY v.num_fra ORDER BY <<ADD THE KEY OF INVOICE LINES HERE>>)as Rank,
l.*,
v.ctdrap_div as discount
from ffac_vta v --(invoicetable)
join ffac_hla l --(invoice lines table)
ON v.num_fra = l.num_fra
) AS T
Change to
;WITH cte AS (SELECT DENSE_RANK() over(order by v.num_fra)as Rank,
num_fra,
l.*,
v.ctdrap_div as discount
FROM ffac_vta v --(invoicetable)
JOIN ffac_hla l --(invoice lines table)
ON v.num_fra = l.num_fra
)
SELECT num_fra
, CASE WHEN Rank = 1 THEN discount ELSE 0 END
, *
FROM cte;
I think you could use left join...
For more information about join, you can see this web site : https://www.quora.com/SQL-What-is-the-difference-between-various-types-of-joins
difference between join image
I hope this help you
Try this:
select i.InvoiceID,Discount
from invoicedetail i
left join invoicediscount id on i.invoiceID=id.invoiceid and i.linenumber=1

Select all from max date

Good morning,
I am writing a SQL query for the latest metal prices with the latest date they were put into the database. Example table below:
ID Date Created
1 01/01/01 01:01
2 01/01/01 01:02
3 01/01/01 01:03
4 01/01/01 01:04
1 02/01/01 01:01
2 02/01/01 01:02
So from this I want the following result:
ID Date Created
1 02/01/01 01:01
2 02/01/01 01:02
When I run the below query it is just giving me the last one entered into the date base so from the above example it would be ID 2 DateCreated 02/01/01 01:02. The query I am using is below:
SELECT mp.MetalSourceID, ROUND(mp.PriceInPounds,2),
mp.UnitPrice, mp.HighUnitPrice, mp.PreviousUnitPrice,
mp.PreviousHighUnitPrice, ms.MetalSourceName,
ms.UnitBasis, cu.Currency
FROM tblMetalPrice AS mp
INNER JOIN tblMetalSource AS ms
ON tblMetalPrice.MetalSourceID = tblMetalSource.MetalSourceID
INNER JOIN tblCurrency AS cu
ON tblMetalSource.CurrencyID = tblCurrency.CurrencyID
WHERE DateCreated = (SELECT MAX (DateCreated) FROM tblMetalPrice)
GROUP BY mp.MetalSourceID;
Could anyone please help its driving me crazy not knowing and my brain is dead this friday morning.
Thanks
Use a correlated subquery for the where clause:
WHERE DateCreated = (SELECT MAX(DateCreated) FROM tblMetalPrice mp2 WHERE mp2.id = mp.id)
You can join on a subquery, and I don't think you'll need the group by, or indeed the where clause (because that's handled by the join).
SELECT mp.MetalSourceID,
ROUND(mp.PriceInPounds,2),
mp.UnitPrice,
mp.HighUnitPrice,
mp.PreviousUnitPrice,
mp.PreviousHighUnitPrice,
ms.MetalSourceName,
ms.UnitBasis,
cu.Currency
FROM tblMetalPrice AS mp
INNER JOIN tblMetalSource AS ms
ON tblMetalPrice.MetalSourceID = tblMetalSource.MetalSourceID
INNER JOIN tblCurrency AS cu
ON tblMetalSource.CurrencyID = tblCurrency.CurrencyID
INNER JOIN (SELECT ID,MAX(DateCreated) AS maxdate FROM tblMetalPrice GROUP BY ID) AS md
ON md.ID = mp.ID
AND md.maxdate = mp.DateCreated
with maxDates as
(select max(datecreated) maxd, ids grp , count(1) members from s_tableA group by ids having count(1) > 1)
select ids, datecreated from s_tableA,maxDates
where maxd = datecreated and ids = grp;
this query will give your desired result. Correlated sub queries tend to consume lot of processing time, because for each row of the outer query it has to process all the rows in the inner query.