Get TOP 8 PERCENT then insert into another table - sql-server

I have a scenario where I need to calculate the top 8 percent of records based on scores attributed to each artist/record as determined by 5 different judges over 3 categories.
To do this I use the following in a stored procedure
SELECT TOP 8 PERCENT
*
FROM
(SELECT
MEM.Id,
EN.artistName, EN.dateAdded, EN.voteStatus,
ES.enterNextRound, ES.notified, ES.voted,
GR.genre,
ES.entrantId AS bandID,
ES.rnd2Feedback AS feedback, ES.compositionVote,
ES.vocalsVote, ES.originalityVote,
(SELECT COUNT(Voted)
FROM recEntrantStatus
WHERE voted = 1
AND roundId = 2
AND entrantId = ES.entrantId) CountVoted,
(SELECT (COUNT(Voted)/*-1*/)
FROM recEntrantStatus
WHERE roundId = 2
AND entrantId = ES.entrantId) CountTotalVotes,
(SELECT COUNT(Id)
FROM recMembers) TotalJudges,
(SELECT coalesce(SUM(compositionVote),0)
FROM recEntrantStatus
WHERE roundId = 2
AND entrantId = ES.entrantId
AND voted = 1) SumTotalComposition,
(SELECT coalesce(SUM(vocalsVote),0)
FROM recEntrantStatus
WHERE roundId = 2
AND entrantId = ES.entrantId
AND voted = 1) SumTotalVocals,
(SELECT coalesce(SUM(originalityVote),0)
FROM recEntrantStatus
WHERE roundId = 2
AND entrantId = ES.entrantId
AND voted = 1) SumTotalOrig,
(SELECT SUM(compositionVote + vocalsVote + originalityVote)
FROM recEntrantStatus
WHERE roundId = 2
AND entrantId = ES.entrantId) TotalVoteScore
FROM
recMembers AS MEM
LEFT JOIN
recEntrantStatus AS ES ON MEM.Id = ES.judgeId
LEFT JOIN
recEntrants AS EN ON ES.entrantId = EN.Id
LEFT JOIN
recGenre AS GR ON EN.genreId = GR.Id
WHERE
MEM.Id = 4
AND ES.roundId = #input) q
ORDER BY
TotalVoteScore DESC, compositionVote DESC,
originalityVote DESC, vocalsVote DESC
Now, to prepare the competition for the next round I need to take this result set and create a new record for each row, once for each of the full set of judges(currently approximately 20 in total). So that would be 20 records for each of this top 8% recordset (approximately 12 entrants / records). This therefore should result in approximately 12*20 records being inserted.
In previous rounds I simply selected 'ALL' entrants who had a bit field used as a marker to indicate that they were to progress to the next round. I did this with the following code:
INSERT INTO recEntrantStatus (entrantId, roundId, judgeId, notified, voted, enterNextRound)
SELECT
r.entrantId, (#input + 1), j.judgeId /*Now getting tblJudges Id*/, 0, 0, 0
FROM
recEntrantStatus r
-- Get all of the judges
CROSS JOIN
(SELECT DISTINCT Id AS judgeId
FROM recMembers
WHERE Privilege >= 2) AS j
WHERE
r.roundId = #input
AND r.voted = 1
AND r.enterNextround = 1
So, my problem is essentially how do I combine these two queries so as to take the top 8% from the current round, then add a new record for each of 20 judges for each of this top 8% of entrants?
Unfortunately it's not a simple case of replacing the CROSS JOIN SELECT with the one that retrieves the TOP 8%.
Any suggestions??

Compose your query using CTEs that identify your TOP 8 PERCENT members and all of your judges, then cross join the two so you have the multiple records to insert.
Here is the skeleton of the query, which you can fill in with all of your properties and necessary conditions.
WITH Members AS
(
--note, for a CTE, all columns in the select must have a name (or you must specify the column names above next to the CTE name)
SELECT TOP 8 PERCENT mem.id, entrantId, ... FROM recMembers [mem] ... ORDER BY ...
),
Judges AS
(
SELECT Id [judgeId] FROM recMembers WHERE Privilege >= 2
)
INSERT INTO recEntrantStatus (entrantId, roundId, judgeId, notified, voted, enterNextRound)
SELECT m.entrantId, ...
FROM Members m, Judges j --cross join of your top 8% members and all of your judges
WHERE ...
You can do this in other ways such as with a derived table expression in your example, but I think the CTE approach is more readable.

Related

Join in SQL Server when some query doesn't have results

I have a table with stock product movements (MOVSTOCKS) in two warehouses (CodAlm). For simplify the question, I will focus on a single product (with CodArt = C5):
CodArt
DescArt
CodAlm
UnidadesStock
EntranStock
SalenStock
FecDoc
TipDoc
C5
Palet
1
16
16
0
2021-12-31
IN
C5
Palet
2
0
0
0
2021-12-31
IN
C5
Palet
1
3
0
3
2022-01-11
SL
C5
Palet
1
4
0
4
2022-01-20
SL
C5
Palet
1
7
7
0
2022-02-01
EN
C5
Palet
1
6
0
6
2022-02-14
SL
C5
Palet
1
9
9
10
2022-05-01
IN
C5
Palet
2
1
1
0
2022-05-01
IN
C5
Palet
1
2
0
2
2022-06-10
SL
I need to get the stock on a certain day. For this, is necessary obtain stock quantity of the last inventory (TipDoc = IN) and add it purchases quantities (TipDoc = EN) and subtract the sales (TipDoc = SL).
I tried this query:
SELECT MV.CODART, MV.DESCART, MV.CODALM, SC.UNIDADESSTOCK + SUM(MV.ENTRANSTOCK) - SUM(MV.SALENSTOCK) as STOCK
FROM MOVSTOCKS MV
JOIN ( SELECT MV1.CODART, MV1.CODALM, MV2.FECDOC, MV1.UNIDADESSTOCK
FROM MOVSTOCKS MV1
JOIN ( SELECT CODART, CODALM, MAX(FECDOC) FECDOC
FROM MOVSTOCKS
WHERE TIPDOC = 'IN'
GROUP BY CODART, CODALM) MV2
ON MV1.CODART = MV2.CODART AND MV1.CODALM = MV2.CODALM AND MV1.FECDOC = MV2.FECDOC
WHERE MV1.TIPDOC = 'IN' ) SC
ON MV.CODART = SC.CODART AND MV.CODALM = SC.CODALM AND MV.FECDOC > SC.FECDOC
WHERE MV.CODART = 'C5' and MV.FECDOC <= '2022-06-01'
GROUP BY MV.CODART, MV.DESCART, MV.CODALM, SC.UNIDADESSTOCK
ORDER BY MV.CODART, MV.CODALM
With above data example and the query I expected to get following results:
CodArt
DescArt
CodAlm
Stock
C5
Palet
1
9
C5
Palet
2
1
The problem is that after the last inventory (2022-05-01) there have been no movements and then the join query get 0 rows because the filter MV.FECDOC <= '2022-06-01' in the WHERE doesn't get rows. I could modify the 'ON' condition in the join to MV.FECDOC >= SC.FECDOC and then get at least the inventory row, but I shouldn't do that because on inventory day there might be other previous movements that I shouldn't get for stock calculation.
Moreover, I will have the same problem if I want to get stock in a date for a product without inventory movements, because subquery 'SC' won't get rows.
Any help, please?
If you know that the inventory records exist, but the other transaction records might or might not exist afterwards, you will need to select your inventory records first and then LEFT JOIN the other transaction records.
If we only had to select one inventory record, we could start with a SELECT TOP 1 * ... ORDER BY FECDOC DESC with appropriate additional conditions. However, to get the latest inventory record for each of multiple warehouses, it get a bit more complicated. Two approaches that come to mind.
One would be to first select distinct warehouse codes (or perhaps distinct warehouse and product codes) and then CROSS APPLY a subselect to retrieve the latest inventory record for each.
The other approach would be to use the ROW_NUMBER() window function to number the inventory records by descending date (partitioned by warehouse codes and product codes) and then exclude all but row number = 1.
In either case, the next step would be to LEFT JOIN the transaction records, apply a GROUP BY, and SUM() up the results. Since SUM() returns NULL if there are no elements to sum, we need to use the ISNULL() function to assign a default value of zero.
The following two similar queries demonstrate the above approaches.
DECLARE #CODART VARCHAR(10) = 'C5'
DECLARE #AsOfDate DATETIME2 = '2022-06-01'
-- Method 1: SELECT DISTINCT followed by a cross apply
SELECT
SC.CODART, SC.DESCART, SC.CODALM,
SC.UNIDADESSTOCK + ISNULL(SUM(MV2.ENTRANSTOCK), 0) - ISNULL(SUM(MV2.SALENSTOCK), 0) as STOCK
FROM (
SELECT DISTINCT MV.CODART, MV.CODALM
FROM #MOVSTOCKS MV
WHERE MV.CODART = #CODART
) A
CROSS APPLY (
-- Most recent inventory for each selected CODART, CODALM
SELECT TOP 1 MV1.*
FROM #MOVSTOCKS MV1
WHERE MV1.CODART = A.CODART
AND MV1.CODALM = A.CODALM
AND MV1.TIPDOC = 'IN'
AND MV1.FECDOC <= #AsOfDate
ORDER BY MV1.FECDOC DESC
) SC
LEFT JOIN #MOVSTOCKS MV2
ON MV2.CODART = SC.CODART
AND MV2.CODALM = SC.CODALM
AND MV2.TIPDOC IN ('EN', 'SL')
AND MV2.FECDOC > SC.FECDOC
AND MV2.FECDOC <= #AsOfDate
GROUP BY SC.CODART, SC.DESCART, SC.CODALM, SC.UNIDADESSTOCK
ORDER BY SC.CODART, SC.CODALM
-- Method 2: Using the ROW_NUMBER() window function
SELECT
SC.CODART, SC.DESCART, SC.CODALM,
SC.UNIDADESSTOCK + ISNULL(SUM(MV2.ENTRANSTOCK), 0) - ISNULL(SUM(MV2.SALENSTOCK), 0) as STOCK
FROM (
-- Most recent inventory at or before #AsOfDate will have Recency = 1
SELECT MV1.*,
ROW_NUMBER() OVER(PARTITION BY CODART, CODALM ORDER BY FECDOC DESC) AS Recency
FROM #MOVSTOCKS MV1
WHERE MV1.FECDOC <= #AsOfDate
AND MV1.TIPDOC = 'IN'
) SC
LEFT JOIN #MOVSTOCKS MV2
ON MV2.CODART = SC.CODART
AND MV2.CODALM = SC.CODALM
AND MV2.FECDOC > SC.FECDOC
AND MV2.FECDOC <= #AsOfDate
AND MV2.TIPDOC IN ('EN', 'SL')
WHERE SC.Recency = 1
AND SC.CODART = #CODART
GROUP BY SC.CODART, SC.DESCART, SC.CODALM, SC.UNIDADESSTOCK
ORDER BY SC.CODART, SC.CODALM
Both queries above produce the desired result. See this db<>fiddle for a working demo.
*** UPDATE *** To handle cases where we might have transactions (added stock and/or sales) but no prior reference inventory, we will need to assume an initial inventory of zero and include all transactions since the beginning-of-time (start or data). This also requires making the initial SELECT DISTINCT the primary source for product and warehouse information. The CROSS APPLY becomes an OUTER APPLY (similar to a LEFT JOIN) and we need to make other adjustments to handle a possible null inventory record. That includes adjusting the date range for the transaction join.
The updated query would be something like:
SELECT
A.CODART, A.DESCART, A.CODALM,
ISNULL(SC.UNIDADESSTOCK, 0) + ISNULL(SUM(MV2.ENTRANSTOCK), 0) - ISNULL(SUM(MV2.SALENSTOCK), 0) as STOCK
, SC.UNIDADESSTOCK AS PriorInventory
, SUM(MV2.ENTRANSTOCK) AS NewStock
, SUM(MV2.SALENSTOCK) as Sales
FROM (
SELECT DISTINCT MV.CODART, MV.DESCART, MV.CODALM
FROM #MOVSTOCKS MV
WHERE MV.CODART = #CODART
) A
OUTER APPLY (
-- Most recent inventory for each selected CODART, CODALM combination
SELECT TOP 1 MV1.*
FROM #MOVSTOCKS MV1
WHERE MV1.CODART = A.CODART
AND MV1.CODALM = A.CODALM
AND MV1.TIPDOC = 'IN'
AND MV1.FECDOC <= #AsOfDate
ORDER BY MV1.FECDOC DESC
) SC
LEFT JOIN #MOVSTOCKS MV2
-- Transactions since inventory was last recorded
ON MV2.CODART = A.CODART
AND MV2.CODALM = A.CODALM
AND MV2.TIPDOC IN ('EN', 'SL') -- Alternately <> 'IN' or omitted entirely.
AND MV2.FECDOC > ISNULL(SC.FECDOC, '1900-01-01')
AND MV2.FECDOC <= #AsOfDate
GROUP BY A.CODART, A.DESCART, A.CODALM, SC.UNIDADESSTOCK
ORDER BY A.CODART, A.CODALM
See this db<>fiddle for an updated demo. For this demo, I added a new stock record for warehouse 3 and set the #AsOfDate to '2022-12-31'. I also added details to the result showing the separate initial inventory, new stock, and sales that feed the final STOCK calculation.
CODART
DESCART
CODALM
STOCK
PriorInventory
NewStock
Sales
C5
Palet
1
7
9
0
2
C5
Palet
2
1
1
null
null
C5
Palet
3
6
null
6
0
The above includes cases having inventory with transactions, inventory but no transactions, and transactions with no initial inventory.
I abandoned the ROW_NUMBER() alternate approach.

Optimise query with count and order by function

I have a problem with the optimization of this query, I have 3 tables (Products = Catalogo.GTIN, Sales Header = TEDEF.Factura and Sales Detail = TEDEF.Farmacia).
The query tries to find the Mode of the column VPRODEXENIGV_FAR. This query without the ORDER BY executes in less than 3 seconds (the table of details has about 30 million rows).
But when I add the ORDER BY clause, the query now takes more than 30 minutes to run.
I want to know how can I optimize this query or the indexes that I need to optimize this.
SELECT *
FROM Catalogo.GTIN G
CROSS APPLY
(SELECT TOP 1
COUNT(FAR.VPRODEXENIGV_FAR) [ROW],
YEAR(FAC2.VFECEMI_FAC) [AÑO],
MONTH(FAC2.VFECEMI_FAC) [MES],
FAR.VCODPROD_FAR_003,
CASE WHEN FAR.VPRODEXENIGV_FAR = 'A' THEN 1 ELSE 0 END AfectoIGV
FROM
TEDEF.Factura FAC2
INNER JOIN
TEDEF.Farmacia FAR ON FAC2.VTDOCPAGO_FAC = FAR.VTDOCPAGO_FAC
AND FAC2.VNDOCPAGO_FAC = FAR.VNDOCPAGO_FAC
WHERE
G.CODIGO = FAR.VCODPROD_FAR_003
GROUP BY
YEAR(FAC2.VFECEMI_FAC),
MONTH(FAC2.VFECEMI_FAC),
FAR.VCODPROD_FAR_003,
FAR.VPRODEXENIGV_FAR
ORDER BY
1 DESC --- <----- THE PROBLEM IS HERE
) GG
Ouch! You have a hugely expensive dependent subquery. It's expensive because SELECT TOP(n) ... ORDER BY col DESC does a whole lot of work to create a result set only to discard all but one row. And, it's a dependent subquery so it's run for every row of Catalogo.GTIN .
It looks like you want to count the resultset rows in the most recent month and year for each Catalogo.GTIN row. So, let's try to refactor your query to do that.
We'll start with a subquery to grab the month-start date of the latest Factura row for each catalog entry.
SELECT CODIGO,
DATEFROMPARTS(YEAR(maxd), MONTH(maxd),1) maxmes
FROM (
SELECT MAX(FAC2.VFECEMI_FAC) maxd,
G.CODIGO
FROM Catalogo.GTIN G
JOIN TDEF.Farmacia FAR
ON G.CODIGO = FAR.VCODPROD_FAR_003
JOIN TEDEF.Factura FAC2
ON FAC2.VTDOCPAGO_FAC = FAR.VTDOCPAGO_FAC
AND FAC2.VNDOCPAGO_FAC = FAR.VNDOCPAGO_FAC
GROUP BY G.CODIGO
) maxd
It's wise to test this and make sure it works correctly and performs tolerably well. If you test it in SSMS, you can use "Show Actual Execution Plan" and see if it recommends an extra index. This subquery need only be run once, rather than once per G.CODIGO row.
Then we'll use it in your larger query.
SELECT G.*,
COUNT(FAR.VPRODEXENIGV_FAR) [ROW],
YEAR(FAC2.VFECEMI_FAC) [AÑO],
MONTH(FAC2.VFECEMI_FAC) [MES],
FAR.VCODPROD_FAR_003,
CASE WHEN FAR.VPRODEXENIGV_FAR = 'A' THEN 1 ELSE 0 END AfectoIGV
FROM Catalogo.GTIN G
JOIN (
SELECT CODIGO,
DATEFROMPARTS(YEAR(maxd), MONTH(maxd),1) maxmes
FROM (
SELECT MAX(FAC2.VFECEMI_FAC) maxd,
G.CODIGO
FROM Catalogo.GTIN G
JOIN TDEF.Farmacia FAR
ON G.CODIGO = FAR.VCODPROD_FAR_003
JOIN TEDEF.Factura FAC2
ON FAC2.VTDOCPAGO_FAC = FAR.VTDOCPAGO_FAC
AND FAC2.VNDOCPAGO_FAC = FAR.VNDOCPAGO_FAC
GROUP BY G.CODIGO
) maxd
) maxmes ON G.CODIGO = maxmes.CODIGO
JOIN TEDEF.Farmacia FAR
ON G.CODIGO = FAR.VCODPROD_FAR_003
JOIN TEDEF.Factura FAC2
ON FAC2.VTDOCPAGO_FAC = FAR.VTDOCPAGO_FAC
AND FAC2.VNDOCPAGO_FAC = FAR.VNDOCPAGO_FAC
AND FAC2.VFECEMI_FAC >= maxmes.maxmes
GROUP BY maxmes.maxmes,
G.CODIGO,
FAR.VCODPROD_FAR_003,
FAR.VPRODEXENIGV_FAR
Here is the tricky bit:
DATEFROMPARTS(YEAR(maxd), MONTH(maxd),1) maxmes turns any date maxd into the first day of that month.
And, FAC2.VFECEMI_FAC >= maxmes.maxmes filters out rows before the first day of that month (for that CODIGO). It does so in a sargable way: a way that can exploit an index on FAC2.VFECEMI_FAC.
That is an alternative way to do TOP(1) ORDER BY d DESC. And faster.
It's all about sets of rows. Especially when using GROUP BY, it's performance-helpful to limit the number of rows in each set.
Obviously I cannot debug this.
Is me again, Finally i resolve the problem of the optimization, now the query delay is about 20 sec (with the sort instruction and with the count in a table over 30 million rows) i hope this way can help others or could be optimice more by the community.
I resolve the problem applying the sort but with the Row_Number instruction, in that way the server take my index for the sort instruction and make the magic:
WITH x
AS
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY GG.COD, GG.[AÑO], GG.[MES] ORDER BY GG.[ROW] DESC) [ID]
FROM Catalogo.GTIN G
CROSS APPLY
(
SELECT COUNT(FAR.VPRODEXENIGV_FAR) [ROW]
, YEAR(FAC2.VFECEMI_FAC) [AÑO]
, MONTH(FAC2.VFECEMI_FAC) [MES]
, FAR.VCODPROD_FAR_003 [COD]
, CASE WHEN FAR.VPRODEXENIGV_FAR = 'A' THEN 1 ELSE 0 END AfectoIGV
FROM TEDEF.Factura FAC2
INNER JOIN TEDEF.Farmacia FAR
ON FAC2.VTDOCPAGO_FAC = FAR.VTDOCPAGO_FAC
AND FAC2.VNDOCPAGO_FAC = FAR.VNDOCPAGO_FAC
WHERE G.CODIGO = FAR.VCODPROD_FAR_003
GROUP BY YEAR(FAC2.VFECEMI_FAC)
, MONTH(FAC2.VFECEMI_FAC)
, FAR.VCODPROD_FAR_003
, FAR.VPRODEXENIGV_FAR
-- ORDER BY 1 DESC --- <---- this is the bad guy, please, don't do that xD
) GG
) SELECT *
FROM x WHERE ID = 1
In that way i can sort the Count instruction and calculate the Mode for the Column FAR.VPRODEXENIGV_FAR

TSQL Subquery effeciency

I am trying to write a subquery in a view to be returned as a column but I am not exactly sure what would give me the most efficient call.
I have View A that gathers a bunch of fields from different tables (one table being Listings that has has a 1 to many relationship with OpenHours) then one of the fields I want it to be from another table (OpenHours) that will only be Today's Open hours field.
The OpenHours table has ListingID, Day (0 based for the day of the week), Hours (text of the open hours such as "8:00am-5:00pm"). Here is what I need to do:
Check if OpenTable has a record for that particular listing that day = 7, if its 7 (which is not a day of the week) then return "Open 24 hours".
If does not exist then return the next record, since SQL Servers datepart(dw.. is 1 based, following will be used select datepart(dw,getdate())-1 to get a 0 day based day of week starting on Sunday (Sunday being 0)
Return nothing if no records exist that match the criteria.
I would appreciate some help on this. I attempted to write this but could not get far. I am not sure how to declare variables for day of the week in the view.
UPDATE
here is my function, anyone see any glaring inefficiencies?
ALTER FUNCTION [dbo].[GetTodaysOpenHours](#ListingID int)
RETURNS VARCHAR(50)
AS
BEGIN
DECLARE #DayOfWeek int
--SQL Day of week starts on sunday but it is 1 based, listing open hours are 0 based
SET #DayOfWeek = DATEPART(DW, GETDATE()) - 1
DECLARE #OpenHours VARCHAR(50)
IF EXISTS(SELECT * FROM OpenHours WHERE Day = 7 AND ListingID = #ListingID)
SET #OpenHours = 'Open 24 Hours'
ELSE
SELECT #OpenHours = Hours FROM OpenHours WHERE ListingID = #ListingID AND Day = #DayOfWeek
RETURN #OpenHours
END
UPDATED VIEW
ALTER view [dbo].[vListings]
as
SELECT l.ListingID, l.ExpiryDate, l.IsApproved, l.IsActive, l.Position,MoneyField1, DateField1,
IntField1, IntField2, IntField3, IntField4,
BoolField1, BoolField2, BoolField3,
OptionField1, OptionField2, OptionField3, OptionField4,
IsTop, TopStartDate, TopExpireDate, Address, Address + ' ' + c.Name + ' ' + p.Name AS FullAddress,
o1.Description as Options1Description,
o2.Description as Options2Description,
o3.Description as Options3Description,
o4.Description as Options4Description,
COALESCE(
(SELECT TOP 1 ThumbnailPath
FROM Attachments
WHERE ListingID = l.listingID), '/content/images/noImageThumbnail2.jpg') AS MainThumbnail,
COALESCE(
(SELECT TOP 1 ThumbnailPath2
FROM Attachments
WHERE ListingID = l.listingID), '/content/images/noImageThumbnail.jpg') AS MainThumbnail2,
l.UserID,
c.SubDomainName as CitySubDomainName, l.Name,
CASE
WHEN l.IsAutoGenerated = 1 THEN l.ImportedPhoneNumber
ELSE dbo.FormatPhoneNumber(u.PhoneNumber)
END as PhoneNumber,
CASE
WHEN l.IsAutoGenerated = 1 THEN l.ImportedContactInfo
ELSE u.FirstName + ' ' + u.LastName
END as ContractInfo,
p.Abbv as StateAbbv,
cn.Code as CountryCode,
l.Comments, l.UniqueID, l.Rating, l.Website,
(select L.ListingID,
isnull(H1.Hours, H2.Hours) as Hours
from Listings L
outer apply (
select Hours FROM OpenHours H WHERE H.Day = 7
AND H.ListingID = L.ListingID
) H1
outer apply (
select Hours FROM OpenHours H WHERE Day = DATEPART(DW, GETDATE()) - 1
AND H.ListingID = L.ListingID
) H2
--dbo.GetTodaysOpenHours(l.ListingID) as TodaysOpenHours
FROM Listings l
INNER JOIN Cities c ON c.CityID = l.CITYID
INNER JOIN Provinces p ON p.ProvinceID = c.ProvinceID
INNER JOIN Countries cn ON cn.CountryID = p.CountryID
INNER JOIN AspNetUsers u ON u.Id = l.UserID
LEFT OUTER JOIN Options1 o1 ON o1.OptionID = l.OptionField1
LEFT OUTER JOIN Options2 o2 ON o2.OptionID = l.OptionField2
LEFT OUTER JOIN Options3 o3 ON o3.OptionID = l.OptionField3
LEFT OUTER JOIN Options4 o4 ON o4.OptionID = l.OptionField4
GO
I get an error that says "incorrect syntax near the keyword FROM
(FROM Listings l)
I upadted the view (added FROM) to the select statements as well
I prefer not to use t he function because I had to add an index to my openhours table on listingid and day in order to make it a little faster but if adding sql into view itself would be better that would be awesome
User defined function isn't usually the best performing solution. You could try just to add that SQL into the view / query by doing something like this (sorry, can't test this, hopefully there's no syntax errors):
select
L.ListingID,
isnull(H1.Hours, H2.Hours) as Hours
from Listing L
outer apply (
select Hours OpenHours H WHERE H.Day = 7
AND H.ListingID = L.ListingID
) H1
outer apply (
select Hours OpenHours H WHERE Day = DATEPART(DW, GETDATE()) - 1
AND H.ListingID = L.ListingID
) H2

Counting duplicate items in different order

Goal:
To know if we have purchased duplicate StockCodes or Stock Description more than once on difference purchase orders
So, if we purchase Part ABC on Purchase Order 1 and Purchase Order 2, it should return the result of
PurchaseOrders, Part#, Qty
Purchase Order1, Purchase Order2, ABC, 2
I just don't know how to pull the whole code together, more to the point, how do I know if it's occurred on more than 1 Purchase Order without scrolling through all the results , may also have to do with Multiple (Having Count) Statements as I only seem to be doing by StockCode
SELECT t1.PurchaseOrder,
t1.MStockCode,
Count(t1.MStockCode) AS SCCount,
t1.MStockDes,
Count(t1.MStockDes) AS DescCount
FROM PorMasterDetail t1
INNER JOIN PorMasterHdr t2
ON t1.PurchaseOrder = t2.PurchaseOrder
WHERE Year(t2.OrderEntryDate) = Year(Getdate())
AND Month(t2.OrderEntryDate) = Month(Getdate())
GROUP BY t1.PurchaseOrder,
t1.MStockCode,
t1.MStockDes
HAVING Count(t1.MStockCode) > 1
Using responses I came up with the following
select * from
(
SELECT COUNT(dbo.InvMaster.StockCode) AS Count, dbo.InvMaster.StockCode AS StockCodes,
dbo.PorMasterDetail.PurchaseOrder, dbo.PorMasterHdr.OrderEntryDate
FROM dbo.InvMaster INNER JOIN dbo.PorMasterDetail ON
dbo.InvMaster.StockCode = dbo.PorMasterDetail.MStockCode
INNER JOIN dbo.PorMasterHdr ON dbo.PorMasterDetail.PurchaseOrder = dbo.PorMasterHdr.PurchaseOrder
WHERE YEAR(dbo.PorMasterHdr.OrderEntryDate) = YEAR(GETDATE())
GROUP BY dbo.InvMaster.StockCode, dbo.InvMaster.StockCode,
dbo.PorMasterDetail.PurchaseOrder, dbo.PorMasterHdr.OrderEntryDate
) Count
Where Count.Count > 1
This returns the below , which is starting to be a bit more helpful
In result line 2,3,4 we can see the same stock code (*30044) ordered 3 times on different
purchase orders.
I guess the question is, is it possible to look at If something was ordered more than once within say a 30 day period.
Is this possible?
Count StockCodes PurchaseOrder OrderEntryDate
2 *12.0301.0021 322959 2014-09-08
2 *30044 320559 2014-01-21
8 *30044 321216 2014-03-26
4 *30044 321648 2014-05-08
5 *32317 321216 2014-03-26
4 *4F-130049/TEST 323353 2014-10-22
5 *650-1157/E 322112 2014-06-24
2 *650-1757 321226 2014-03-27
SELECT *
FROM
(
SELECT h.OrderEntryDate, d.*,
COUNT(*) OVER (PARTITION BY d.MStockCode) DupeCount
FROM
PorMasterHdr h
INNER JOIN PorMasterDetail d ON
d.PurchaseOrder = h.PurchaseOrder
WHERE
-- first day of current month
-- http://blog.sqlauthority.com/2007/05/13/sql-server-query-to-find-first-and-last-day-of-current-month/
h.OrderEntryDate >= CONVERT(VARCHAR(25), DATEADD(dd,-(DAY(GETDATE())-1),GETDATE()),101)
) dupes
WHERE
dupes.DupeCount > 1;
This should work if you're only deduping on stock code. I was a little unclear if you wanted to dedupe on both stock code and stock desc, or either stock code or stock desc.
Also I was unclear on your return columns because it almost looks like you're wanting to pivot the columns so that both purchase order numbers appear on the same line.

Sql query to create teams

I need a query to assign teams to a series of users. Data looks like this:
UserId Category Team
1 A null
2 A null
3 B null
4 B null
5 A null
6 B null
8 A null
9 B null
11 B null
Teams should be created by sorting by userid and the first userid becomes the team number and the consecutive A's are part of that team as are the B's that follow. The first A after the Bs starts a new team. There will always be at least one A and one B. So after the update, that data should look like this:
UserId Category Team
1 A 1
2 A 1
3 B 1
4 B 1
5 A 5
6 B 5
8 A 8
9 B 8
11 B 8
EDIT:
Need to add that the user id's will not always increment by 1. I edited the example data to show what I mean. Also, the team ID doesn't strictly have to be the id of the first user, as long as they end up grouped properly. For example, users 1 - 4 could all be on team '1', users 5 and 6 on team '2' and users 8,9 and 11 on team '3'
First you could label each row with an increasing number. Then you can use a left join to find the previous user. If the previous user has category 'B', and the current one category 'A', that means the start of a new team. The team number is then the last UserId that started a new team before the current UserId.
Using SQL Server 2008 syntax:
; with numbered as
(
select row_number() over (order by UserId) rn
, *
from Table1
)
, changes as
(
select cur.UserId
, case
when prev.Category = 'B' and cur.Category = 'A' then cur.UserId
when prev.Category is null then cur.UserId
end as Team
from numbered cur
left join
numbered prev
on cur.rn = prev.rn + 1
)
update t1
set Team = team.Team
from Table1 t1
outer apply
(
select top 1 c.Team
from changes c
where c.UserId <= t1.UserId
and c.Team is not null
order by
c.UserId desc
) as team;
Example at SQL Fiddle.
You can do this with a recursive CTE:
with userCTE as
(
select UserId
, Category
, Team = UserId
from users where UserId = 1
union all
select users.UserId
, users.Category
, Team = case when users.Category = 'A' and userCTE.Category = 'B' then users.UserId else userCTE.Team end
from userCTE
inner join users on users.UserId = userCTE.UserId + 1
)
update users
set Team = userCTE.Team
from users
inner join userCTE on users.UserId = userCTE.UserId
option (maxrecursion 0)
SQL Fiddle demo.
Edit:
You can update the CTE to get this to go:
with userOrder as
(
select *
, userRank = row_number() over (order by userId)
from users
)
, userCTE as
(
select UserId
, Category
, Team = UserId
, userRank
from userOrder where UserId = (select min(UserId) from users)
union all
select users.UserId
, users.Category
, Team = case when users.Category = 'A' and userCTE.Category = 'B' then users.UserId else userCTE.Team end
, users.userRank
from userCTE
inner join userOrder users on users.userRank = userCTE.userRank + 1
)
update users
set Team = userCTE.Team
from users
inner join userCTE on users.UserId = userCTE.UserId
option (maxrecursion 0)
SQL Fiddle demo.
Edit:
For larger datasets you'll need to add the maxrecursion query hint; I've edited the previous queries to show this. From Books Online:
Specifies the maximum number of recursions allowed for this query.
number is a nonnegative integer between 0 and 32767. When 0 is
specified, no limit is applied.
In this case I've set it to 0, i.e. not limit on recursion.
Query Hints.
I actually ended up going with the following. It finished on all 3 million+ rows in a half an hour.
declare #userid int
declare #team int
declare #category char(1)
declare #lastcategory char(1)
set #userid = 1
set #lastcategory='B'
set #team=0
while #userid is not null
begin
select #category = category from users where userid = #userid
if #category = 'A' and #lastcategory = 'B'
begin
set #team = #userid
end
update users set team = #team where userid = #userid
set #lastcategory = #category
select #userid = MIN(userid) from users where userid > #userid
End

Resources