I have a recursive select problem which I can't get to work, it feels like almost there but not necessarily.
I have two tables where:
Table 1 - Product List
List ProductCode SortNumber
------------------------------------
1 A 1
1 B 2
1 F 3
1 G 4
1 K 5
2 C 1
2 A 2
3 B 1
3 K 2
3 G 3
Table 2 - Product Price
ProductType Client ProductCode Price
---------------------------------------------------
Type 1 1 A 100
Type 1 1 A 150
Type 1 1 B 200
Type 1 1 B 120
Type 1 1 F 150
Type 2 1 A 200
Type 2 1 A 300
Type 2 1 B 300
Type 2 1 F 400
Type 2 1 G 125
Type 2 1 G 75
Type 1 2 A 190
Type 1 2 A 130
Type 1 2 A 200
Type 1 2 B 270
Type 1 2 B 180
Type 1 2 F 130
Type 2 2 A 210
Type 2 2 A 100
Type 2 2 B 350
Type 2 2 F 200
Type 2 2 G 175
Type 2 2 G 95
Type 2 2 K 65
What I am trying to achieve is when I request for Product List of 1, it will get all the max prices associated with the product code. If there's no price for Product Type 1, it will check for the max prices for Product Type 2. If there's no corresponding Type 2 price, then value is 0.
So, the desired output is that when I request for price list 1 for all clients (say here for Clients 1 & 2), the desired output should be
Client ProductCode SortNumber PriceToDisplay
---------------------------------------------------
1 A 1 150
1 B 2 200
1 F 3 150
1 G 4 125 (No Type 1, so we get type 2 price)
1 K 5 0 (No Type 1, 2 Price for Product K)
2 A 1 200
2 B 2 270
2 F 3 130
2 G 4 175
2 K 5 65
Tried Solution: CTE Approach
Since I was having the impression that this might be a recursive select, I have thought of using CTE. I have this query -
(note the commented code - -- And pp1.ProductCode = pp2.ProductCode)
Declare #List int = 1
;With ClientCTE As (
Select Distinct Client From ClientsTable
),
RecursiveSelect As (
Select p1.Client
, l.ProductCode
, l.SortOrder
, p1.Price As P1Price
, p2.Price As P2Price
, Case when p1.Price Is Null Then Case When p2.Price Is Null Then 0 Else p2.Price End
Else p1.Price End As PriceToDisplay
From ProductList l
Left Join (
Select Distinct pp.Client, pp.ProductCode, Max(pp.Price) As ItemPrice From ProductPrice pp
Left Join ClientCTE c On c.Client = pp.Client
Where pp.ProductType = 1
Group By pp.Client, pp.ProductCode) p1 On p1.ProductCode = l.ProductCode
Left Join (
Select Distinct pp.Client, pp.ProductCode, Max(pp.Price) As ItemPrice From ProductPrice pp
Left Join ClientCTE c On c.Client = pp.Client
Where pp.ProductType = 2
Group By pp.Client, pp.ProductCode) p2 On p2.Client = p1.Client
Where pp1.Client = pp2.Client
-- And pp1.ProductCode = pp2.ProductCode **this commented code**
And l.List = 1
)
Select Distinct Client, ProductCode, SortOrder, Max(P1Price), Max(P2Price)
From RecursiveSelect
Group By Client, ProductCode, SortOrder
Findings - CTE
If the code is commented, it will:
Get the prices correctly
However, since there is no price for Product Code G and K for Type 1 in Product Price table, it will not display correctly
Results
Client ProductCode SortNumber PriceToDisplay
---------------------------------------------------
1 A 1 150
1 B 2 200
1 F 3 150
(missing G and K product codes)
2 A 1 200
2 B 2 270
2 F 3 130
2 G 4 175
2 K 5 65
If the code is not commented, it will:
All product codes will appear but get the prices wrong
The price that's being displayed is the maximum price in Price1 table, regardless of client hence
Results
Client ProductCode SortNumber PriceToDisplay
---------------------------------------------------
1 A 1 WRONG PRICE
1 B 2 WRONG PRICE
1 F 3 WRONG PRICE
1 G 4 WRONG PRICE
1 K 5 WRONG PRICE
2 A 1 WRONG PRICE
2 B 2 WRONG PRICE
2 F 3 WRONG PRICE
2 G 4 WRONG PRICE
2 K 5 WRONG PRICE
I guess there's just one tweak that I have to do on my query but I can't pinpoint exactly where. The uncommented code returns the right values but why does it not return the missing left table values considering it is a left join?
based on requirements , here's what I would do.
subquery or view e.g. vw_product_max_price:
select producttype, client, productcode, max(price) as maxprice group by producttype, client, productcode
query to request product list
select MP.client, MP.productcode, PL.sortnumber,
case MP.maxprice
when NULL
then 0
else MP.maxprice
end as PriceToDisplay
from productlist PL left outer join vw_product_max_price MP on PL.productcode=MP.productcode
where MP.client = ? and MP.productcode = ?
I havent tested this.. but the idea is to have a list of maximum prices for each product code first.
You can use OUTER APPLY to get the max price, you don't really need a recursive CTE
select c.Client, l.ProductCode, l.SortNumber, Price = isnull(p.Price, 0)
from ProductList l
cross join ClientCTE c
outer apply
(
-- Top 1 to return only one row per client + Pproduct Code
select top 1 p.Price
from ProductPrice p
where p.Client = c.Client
and p.ProductCode = l.ProductCode
-- Type 1 will comes before Type 2, and higher price first
-- If there isn't any rows for Type 1, Type 2 will be selected
-- no rows return if no rows are found
order by p.ProductType, p.Price desc
) p
where l.List = 1
order by Client, ProductCode
You can do this with join and group by
declare #PL table (client int, ProductCode char(1), Sort int);
insert into #pl values
(1, 'A', 1),
(1, 'B', 2),
(1, 'F', 3),
(1, 'G', 4),
(1, 'K', 5),
(2, 'C', 1),
(2, 'A', 2),
(3, 'B', 1),
(3, 'K', 2),
(3, 'G', 3);
--select * from #PL;
declare #PP table (ProductType varchar(10), Client int, ProductCode char(1), Price int);
insert into #PP values
('Type 1', 1, 'A', 100),
('Type 1', 1, 'A', 150),
('Type 1', 1, 'B', 200),
('Type 1', 1, 'B', 120),
('Type 1', 1, 'F', 150),
('Type 2', 1, 'A', 200),
('Type 2', 1, 'A', 300),
('Type 2', 1, 'B', 300),
('Type 2', 1, 'F', 400),
('Type 2', 1, 'G', 125),
('Type 2', 1, 'G', 75),
('Type 1', 2, 'A', 190),
('Type 1', 2, 'A', 130),
('Type 1', 2, 'A', 200),
('Type 1', 2, 'B', 270),
('Type 1', 2, 'B', 180),
('Type 1', 2, 'F', 130),
('Type 2', 2, 'A', 210),
('Type 2', 2, 'A', 100),
('Type 2', 2, 'B', 350),
('Type 2', 2, 'F', 200),
('Type 2', 2, 'G', 175),
('Type 2', 2, 'G', 95),
('Type 2', 2, 'K', 65);
--select * from #pp;
select pl.client, pl.ProductCode
--, pp1.Price as Price1, pp2.Price as Price2
, max(isnull(pp1.Price, isnull(pp2.Price, 0))) as Price12
from #PL pl
left join #PP pp1
on pp1.ProductCode = pl.ProductCode
and pp1.Client = pl.client
and pp1.ProductType = 'Type 1'
left join #PP pp2
on pp2.ProductCode = pl.ProductCode
and pp2.Client = pl.client
and pp2.ProductType = 'Type 2'
where pl.client in (1, 2)
group by pl.client, pl.ProductCode
order by pl.client, pl.ProductCode;
So I sit with a friend and worked on this. The solution that worked (tweaked from original db table name and columns) is as follows:
Declare #List int = 1
;With ClientCTE (BuyGroup, CustomerPriceGroup, ProductListHeaderId) As (
Select Distinct Client From ClientsTable
),
RecursiveSelect As (
Select x.Client, x.ProductCode
, Case When Max(Type1Price) Is Null Then
Case When Max(Type2Price) Is Null Then 0
Else Max(Type2Price) End
Else Max(Type1Price) End
As PriceToDisplay
From
(Select h.Client, h.ProductCode,
Case When h.ProductType = 'Type1'
Then
Max(h.UnitPrice)
Else
Null
End As Type1Price
, Case When h.ProductType = 'Type2'
Then
Max(h.UnitPrice)
Else
Null
End As Type2Price
, h.BuyGroup, h.ConditionScale
From ProductPrice h
Inner Join ClientCTE c On c.Client = h.Client
Where
h.ProductType In ('Type1', 'Type2')
And h.ProductCode In (Select ProductCode From ProductList where List = #List)
Group by h.Client, h.ProductCode
) x
Group by Client, ProductCode
)
select * from RecursiveSelect
We both figured out that on the original query, the two left joins differ only by a single criteria (which is ProductType). Once we got the raw data from the query, then we grouped and selected the price that will be displayed.
The reason for the CTE is that the original intention of the query is to get the prices for N customers. That's why the anchor query gets the list of clients and then passed on the second one. Moreover, if you would be working on X number of lists as well, you can include that in the anchor query and replace "#List" with the column on the first one.
Related
It's not clear the exact statement for me to use here. I want to know how many times certain occurrences happen in the table when the value is A. So for some sample data:
user | value
1 | A
1 | A
1 | B
4 | A
4 | A
4 | B
5 | A
5 | A
5 | A
Would result in:
Occurrence Frequency
1 0
2 2
3 1
Which reads as: there are 0 users that have 1 value A. There are 2 users that have two value A etc.
I feel like I should use a group by and a count(*) by not clear to me how to construct it.
Since you want the occurrences even for 0 frequencies, you need a recursive cte which return all occurrences from 1 to the max number of occurrences.
Then you join this cte with a LEFT join to a query that aggregates on the table and aggregate once more to get the frequencies:
with
cte as (
select count(*) counter
from tablename
where value = 'A'
group by [user]
),
top_counter as (select max(counter) counter from cte),
occurrences as (
select 1 occurrence
union all
select occurrence + 1
from occurrences
where occurrence < (select counter from top_counter)
)
select o.occurrence, count(c.counter) frequency
from occurrences o left join cte c
on c.counter = o.occurrence
group by o.occurrence
See the demo.
Results:
> occurrence | frequency
> ---------: | --------:
> 1 | 0
> 2 | 2
> 3 | 1
You do use COUNT, just 2 of them:
WITH Counts AS(
SELECT V.[User],
COUNT([Value]) AS Frequency
FROM (VALUES(1,'A'),
(1,'A'),
(1,'B'),
(4,'A'),
(4,'A'),
(4,'B'),
(5,'A'),
(5,'A'),
(5,'A'))V([User],[Value]) --USER is a reserved keyword and should not be used for object names
WHERE V.[Value] = 'A'
GROUP BY V.[user])
SELECT V.I,
COUNT(C.Frequency) AS Frequecy
FROM (VALUES(1),(2),(3))V(I)
LEFT JOIN Counts C ON V.I = C.Frequency
GROUP BY V.I;
Here's my take:
with cte as (
select * from (values
(1, 'A'),
(1, 'A'),
(1, 'B'),
(4, 'A'),
(4, 'A'),
(4, 'B'),
(5, 'A'),
(5, 'A'),
(5, 'A')
) as x([User], [Value])
)
select c, count(*)
from (
select [User], count(*) as c
from cte
where [Value] = 'A'
group by [User]
) as s
group by c;
The common table expression isn't important here - it's just setting up your test data.
What you're after is an aggregation of aggretations. That is, the first level aggregate is a "count of value by user". But then you're going to get a "count of (count of value by user) by (that count)". Note, my set doesn't produce the "0 users that have 1 value A". Nor does it produce "0 users that have 17 value A". If it's important that it produce certain negative results, you'll need a list of which ones you care about and join that list with this set of results with an outer join.
I have a log file I need to either rank (but treating sequential and equal rows as ties), or merge sequential equal rows (based on specific column). My table looks like below, The Start and Stop are all being sequential (within the same ID window)
ID Start Stop Value
1 0 1 A
1 1 2 A
1 2 3 A
1 3 4 B
1 4 5 B
1 5 6 A
2 3 4 A
I have two approches to get what I need.
Approach 1: Rank (treating sequential rows with equal values in "Value" as ties) and using ID as partition.
This should give the output below. But how do I do the special rank: Treating sequential rows with equal values in "Value" as ties.
Select *,
rank() OVER (partition by id order by start, stop) as Rank,
XXX as SpecialRank
from Table
ID Start Stop Value Rank SpecialRank
1 0 1 A 1 1
1 1 2 A 2 1
1 2 3 A 3 1
1 3 4 B 4 2
1 4 5 B 5 2
1 5 6 A 6 3
2 3 4 A 1 1
Approach 2: Merge sequential rows with equal values in "Value".
This will shall create a table like below.
ID Start Stop Value
1 0 3 A
1 3 5 B
1 5 6 A
2 3 4 A
I don't know if this helps, but I have also a nextValue column that might help in this
ID Start Stop Value NextValue
1 0 1 A A
1 1 2 A A
1 2 3 A B
1 3 4 B B
1 4 5 B A
1 5 6 A A
2 3 4 A ...
Example-table:
CREATE TABLE #Table ( id int, start int, stop int, Value char(1), NextValue char(1));
INSERT INTO #Table values (1,0, 1, 'A', 'A');
INSERT INTO #Table values (1,1, 2, 'A', 'A');
INSERT INTO #Table values (1,2, 3, 'A', 'B');
INSERT INTO #Table values (1,3, 4, 'B', 'B');
INSERT INTO #Table values (1,4, 5, 'B', 'A');
INSERT INTO #Table values (1,5, 6, 'A', 'A');
INSERT INTO #Table values (2,3, 4, 'A', null);
Use a self join to an aggregate subquery from the full set, e.g.
with rankTable (id, value) as
( select 1, 'A' union all select 1, 'A' union all select 1, 'B' union all select 2, 'A')
select t2.* from rankTable t1 join (
select id, value, rank() over (partition by id order by value) as specialRank from
(
select distinct id, value
from rankTable
) t) t2 on t2.id =t1.id and t2.value = t1.value
id value specialRank
1 A 1
1 A 1
1 B 2
2 A 1
Let's say I have the following table tbl_Rules:
RuleID NameOperator NameValues TypeOperator TypeValue
1 NotIn John In 2
1 NotIn Alex In NULL
1 NotIn Mike In NULL
2 In Mike NotIn 2
And my source table looks like this tbl_Source:
ID Name Type Cost
1 Mike 2 100
2 Cole 2 200
3 Ken 1 300
4 Tara 1 400
5 Mike 1 500
6 Sonya 1 600
7 Ann 2 700
8 Mike 1 800
I want to be able to join these two tables and get the following result tbl_Result:
RuleID Name Type Cost
1 Cole 2 200
1 Ann 2 700
2 Mike 1 500
2 Mike 1 800
If I was writing this query manually my query would look like this:
select 1, Name, Type, Cost
from tbl_Source
Where Name not in ('John', 'Alex', 'Mike') and Type in (2)
union all
select 2, Name, Type, Cost
from tbl_Source
where Name in ('Mike') and Type not in (2)
In my current setup tbl_Rule has 500 records and tbl_Source has 500k records.
Any advice on this can be achieved is greatly appreciated. Limitations:
No CLR functions, No 2017 features (e.g. String_agg)
Update: DDL statements for the above sample can be found here: http://sqlfiddle.com/#!18/9a29f/2/0
Here's one way. I have used cross join to check the rules. There may be better ways with dynamic SQL where rules are implemented in join
declare #tbl_Rules table(
RuleID int
, NameOperator varchar(20)
, NameValues varchar(20)
, TypeOperator varchar(20)
, TypeValue int
)
insert into #tbl_Rules
values
(1, 'NotIn', 'John', 'In', 2)
, (1, 'NotIn', 'Alex', 'In', NULL)
, (1, 'NotIn', 'Mike', 'In', NULL)
, (2, 'In', 'Mike', 'NotIn', 2)
declare #tbl_Source table (
ID int
, Name varchar(20)
, Type int
, Cost int
)
insert into #tbl_Source
values
(1, 'Mike', 2, 100)
, (2, 'Cole', 2, 200)
, (3, 'Ken', 1, 300)
, (4, 'Tara', 1, 400)
, (5, 'Mike', 1, 500)
, (6, 'Sonya', 1, 600)
, (7, 'Ann', 2, 700)
, (8, 'Mike', 1, 800)
;with cte as (
select
distinct Ruleid, a.NameOperator, a.TypeOperator
, NameValues = (
select
'!' + b.NameValues
from
#tbl_Rules b
where
a.RuleID = b.RuleID
and b.NameValues is not null
for xml path('')
) + '!'
, TypeValue = (
select
concat('!', b.TypeValue)
from
#tbl_Rules b
where
a.RuleID = b.RuleID
and b.TypeValue is not null
for xml path('')
) + '!'
from
#tbl_Rules a
)
select
b.RuleID, a.Name, a.Type, a.Cost
from
#tbl_Source a
cross join cte b
where
1 = case
when b.NameOperator = 'In' and charindex('!' + a.Name + '!', b.NameValues) > 0 and b.TypeOperator = 'In' and charindex(concat('!', a.Type, '!'), b.TypeValue) > 0 then 1
when b.NameOperator = 'In' and charindex('!' + a.Name + '!', b.NameValues) > 0 and b.TypeOperator = 'Notin' and charindex(concat('!', a.Type, '!'), b.TypeValue) = 0 then 1
when b.NameOperator = 'NotIn' and charindex('!' + a.Name + '!', b.NameValues) = 0 and b.TypeOperator = 'In' and charindex(concat('!', a.Type, '!'), b.TypeValue) > 0 then 1
when b.NameOperator = 'NotIn' and charindex('!' + a.Name + '!', b.NameValues) = 0 and b.TypeOperator = 'NotIn' and charindex(concat('!', a.Type, '!'), b.TypeValue) = 0 then 1
else 0
end
Output:
RuleID Name Type Cost
---------------------------
1 Cole 2 200
1 Ann 2 700
2 Mike 1 500
2 Mike 1 800
You can try this.
SELECT MAX(R_N.RuleID) RuleID, S.Name, S.Type, S.Cost
FROM tbl_Source S
INNER JOIN tbl_Rules R_N ON
(R_N.NameValues <> S.Name and R_N.NameOperator = 'NotIn' )
OR (R_N.NameValues = S.Name and R_N.NameOperator = 'In' )
INNER JOIN tbl_Rules R_S ON
R_S.RuleID = R_N.RuleID AND
(R_S.TypeValue <> S.Type and R_S.TypeOperator = 'NotIn' )
OR (R_S.TypeValue = S.Type and R_S.TypeOperator = 'In' )
GROUP BY
S.Name, S.Type, S.Cost
HAVING
MAX(R_N.NameOperator) = MIN(R_N.NameOperator)
AND MAX(R_S.TypeOperator) = MIN(R_S.TypeOperator)
Result:
RuleID Name Type Cost
----------- -------------------- ----------- -----------
1 Ann 2 700
1 Cole 2 200
2 Mike 1 500
2 Mike 1 800
I have one table table1 that might look like this, where there could be duplicates on 2 fields Cnumber and Dob and then unique pkSID:
pkSID Cnumber Dob
1 12345 01/02/2002
2 12345 01/02/2002
3 12345 01/02/2002
4 12345 01/02/2002
5 12345 01/02/2002
There can be multiple occurrences of this in table1. I then have another table that references the pkSID, and I want to consolidate those rows so they all only reference one of the pkSID in table1, so table2 will look like this initially:
pkSTID fkSID OtherVal1 OtherVal2
1 1 s x
2 2 t f
3 3 a d
4 4 v g
5 5 b z
And then after the consolidation:
pkSTID fkSID OtherVal1 OtherVal2
1 1 s x
2 1 t f
3 1 a d
4 1 v g
5 1 b z
How can I find those rows in table1 and then consolidate in table2?
Try this:
Note: I'm considering pkSID is in continuation for relative Cnumber and Dob
CREATE TABLE #TABLE1(pkSID INT,Cnumber INT,Dob DATE)
INSERT INTO #TABLE1
SELECT 1, 12345, '01/02/2002' UNION ALL
SELECT 2, 12345, '01/02/2002' UNION ALL
SELECT 3, 12345, '01/02/2002' UNION ALL
SELECT 4, 12345, '01/02/2002' UNION ALL
SELECT 5, 12345, '01/02/2002'
CREATE TABLE #TABLE2(pkSTID INT,fkSID INT,OtherVal1 VARCHAR(10),OtherVal2 VARCHAR(10))
INSERT INTO #TABLE2
SELECT 1, 1, 's', 'x' UNION ALL
SELECT 2, 2, 't', 'f' UNION ALL
SELECT 3, 3, 'a', 'd' UNION ALL
SELECT 4, 4, 'v', 'g' UNION ALL
SELECT 5, 5, 'b', 'z'
SELECT T2.pkSTID, T3.min_pkSID fkSID, T2.OtherVal1 , T2.OtherVal2
FROM #TABLE2 T2
INNER JOIN
(
SELECT MIN(T1.pkSID) min_pkSID, MAX(T1.pkSID) max_pkSID FROM #TABLE1 T1 GROUP BY T1.Cnumber, T1.Dob --T1 ON T2.fkSID = T1.pkSID
)T3
ON T2.fkSID BETWEEN T3.min_pkSID AND T3.max_pkSID
I Use SQL Server 2012 and have a table like below:
DECLARE #T TABLE(Id INT, [Type] CHAR(1), Quantity INT, Price MONEY, UnitPrice AS (Price/Quantity))
INSERT INTO #T VALUES
(1, 'I', 30, 1500),
(2, 'O', 5, NULL),
(3, 'O', 20, NULL),
(4, 'O', 2, NULL),
(5, 'I', 10, 2500),
(6, 'I', 8, 1000),
(7, 'O', 3, NULL),
(8, 'O', 10, NULL),
(9, 'I', 12, 3600)
In my table I have a Type Column With Values ('I' and 'O') I have unit price for 'I' Type Record and 'O' Type Record used last 'I' Type Record Value I want to calculate RunningTotalPrice (Sum of Quantity*UnitPrice of each rows).
Following code calculate RunningTotalQuantity:
SELECT *,
SUM(CASE WHEN [Type] = 'I' Then Quantity ELSE -Quantity END)OVER (ORDER BY Id) AS QuantityRunningTotal
FROM #T
and Results of this query is:
Id Type Quantity Price UnitPrice QuantityRunningTotal
1 I 30 1500/00 50/00 30
2 O 5 NULL NULL 25
3 O 20 NULL NULL 5
4 O 2 NULL NULL 3
5 I 10 2500/00 250/00 13
6 I 8 1000/00 125/00 21
7 O 3 NULL NULL 18
8 O 10 NULL NULL 8
9 I 12 3600/00 300/00 20
I want to have following Result
Id Type Quantity Price UnitPrice QuantityRunningTotal Price RunningTotalPrice
1 I 30 1500/00 50/00 30 1500/00 1500/00
2 O 5 NULL 50/00 25 250/00 1250/00
3 O 20 NULL 50/00 5 1000/00 250/00
4 O 2 NULL 50/00 3 100/00 150/00
5 I 10 2500/00 250/00 13 2500/00 2650/00
6 I 8 1000/00 125/00 21 1000/00 3650/00
7 O 3 NULL 125/00 18 375/00 3275/00
8 O 10 NULL 125/00 8 1250/00 2025/00
9 I 12 3600/00 300/00 20 3600/00 5625/00
In this result Null Unitprice Column valued with last exists unitprice in before records.
and Calculate Price ( Quantity * UnitPrice) and The Calculate Running Total Of Price.
Unfortunately LEAD and LAG functions can't be used to the last not NULL value, so you would need to use OUTER APPLY to get the previous UnitPrice to use in rows where the type is 'O':
SELECT t.ID,
t.[Type],
t.Quantity,
t.Price,
t.UnitPrice,
SUM(CASE WHEN t.[Type] = 'I' THEN t.Quantity ELSE -t.Quantity END) OVER (ORDER BY t.Id) AS QuantityRunningTotal,
CASE WHEN t.[Type] = 'I' THEN t.Price ELSE t.Quantity * p.UnitPrice END AS Price2,
SUM(CASE WHEN t.[Type] = 'I' THEN t.Price ELSE -t.Quantity * p.UnitPrice END)OVER (ORDER BY t.Id) AS QuantityRunningTotal
FROM #T AS t
OUTER APPLY
( SELECT TOP 1 t2.UnitPrice
FROM #T AS t2
WHERE t2.ID < t.ID
AND t2.UnitPrice IS NOT NULL
ORDER BY t2.ID DESC
) AS p;