I have the following table:
+---------+------------+----------+-------+
| userId | campaignId | countryId| points|
+---------+------------+----------+-------+
| 10 | 1 | 101 | 72 |
| 3 | 1 | 101 | 30 |
| 6 | 1 | 101 | 72 |
| 4 | 1 | 101 | 49 |
| 1 | 1 | 101 | 53 |
| 8 | 1 | 101 | 67 |
| 5 | 1 | 101 | 6 |
| 7 | 1 | 101 | 87 |
| 2 | 1 | 101 | 41 |
| 11 | 1 | 101 | 76 |
| 9 | 1 | 101 | 50 |
+---------+------------+----------+-------+
I have already created a leaderboard toplist with a query like this:
select
RANK() OVER(order by T.points desc) AS rowRank,
T.UserID, T.points
from table as T
where T.campaignId=#campaignId
OFFSET (#page-1)*#limit ROWS FETCH NEXT #limit ROWS ONLY
Above query returns a regular toplist from top to bottom.
However, next requirement is to create a leaderboard toplist that returns current user Id rank + the above 2 ranked users + the 2 below ranked users, in total 5 users should be listed with current user in the center.
So additional input parameters would be:
set #userId = 8 // current user where leader board should center around
set #maxTopLimit = 2 // include 2 users ranked above current user
set #maxBottomLimit = 2 // include 2 users ranked below current user
The leaderboard returned should look like this with userId 8 in the center
+---------+------------+----------+-------+---------|
| userId | campaignId | countryId| points| rowRank |
+---------+------------+----------+-------+---------+
| 11 | 1 | 101 | 76 | 3 |
| 10 | 1 | 101 | 72 | 4 |
#####|###### 8 | 1 #########|##### 101 |### 67 |## 5 ####|########
| 9 | 1 | 101 | 50 | 6 |
| 2 | 1 | 101 | 49 | 7 |
+---------+------------+----------+-------+------+--+
How do I write a SQL query that behaves like this?
Move the ranking results in a subquery or common table expression.
with cte_rank as (...)
Select the target user.
from cte_rank cr where cr.UserId = #userId
Join the target row with all rows in the defined interval.
join cte_rank cr2 on cr2.RowRank >= cr.RowRank - #before and cr2.RowRank <= cr.RowRank + #after
Select all rows from the interval.
select cr2.*
Sample data
create table CampaignPoints
(
UserId int,
CampaignId int,
CountryId int,
Points int
);
insert into CampaignPoints (UserId, CampaignId, CountryId, Points) values
(10, 1, 101, 72),
( 3, 1, 101, 30),
( 6, 1, 101, 72),
( 4, 1, 101, 49),
( 1, 1, 101, 53),
( 8, 1, 101, 67),
( 5, 1, 101, 6),
( 7, 1, 101, 87),
( 2, 1, 101, 41),
(11, 1, 101, 76),
( 9, 1, 101, 50);
Solution
declare #userId int = 8;
declare #before int = 2;
declare #after int = 2;
with cte_rank as
(
select cp.UserId,
cp.CampaignId,
cp.CountryId,
cp.Points,
rank() over(order by cp.Points desc) as RowRank
from CampaignPoints cp
)
select cr2.*
from cte_rank cr
join cte_rank cr2
on cr2.RowRank >= cr.RowRank - #before
and cr2.RowRank <= cr.RowRank + #after
where cr.UserId = #userId
order by cr2.RowRank;
Result
UserId CampaignId CountryId Points RowRank
------ ---------- --------- ------ -------
10 1 101 72 3
6 1 101 72 3
8 1 101 67 5
1 1 101 53 6
9 1 101 50 7
Fiddle to see things in action.
Related
I have 2 tables:
product_facet_values_facet_value
+-----------+--------------+
| productId | facetValueId |
+-----------+--------------+
| 6 | 1 |
| 6 | 34 |
| 7 | 39 |
| 8 | 34 |
| 8 | 1 |
| 8 | 11 |
| 9 | 1 |
| 9 | 39 |
+-----------+--------------+
facet_value
+--------------+---------+
| facetValueId | facetId |
+--------------+---------+
| 1 | 2 |
| 34 | 6 |
| 39 | 2 |
| 44 | 2 |
| 56 | 11 |
+--------------+---------+
I need to be able to get all productIds with those facetValueIds I ask for but with one extra step - I need an intersection between facetValueId groups based on same facetId.
For example I want to get all product ids with facetValueId 1, 34, 39 and result of this query should be same as I would get with the following query:
select "productId"
from "product_facet_values_facet_value"
where "facetValueId" in (1, 39)
INTERSECT
select "productId"
from "product_facet_values_facet_value"
where "facetValueId" in (34)
I wrote this query based on: facetValueIds 1 or 39 has same "facetId"=2, facetValueId 34 has "facetId"=6.
I need a query that would result in same result without having it to group it manually. If for example next time I ask for all products that have facetValueIds 1, 34, 39, 56 the result of such dynamic query should be same as if I would write 3 INTERSECTIONs between IN (1, 39) & IN(34) & IN(56) like:
select "productId"
from "product_facet_values_facet_value"
where "facetValueId" in (1, 39)
INTERSECT
select "productId"
from "product_facet_values_facet_value"
where "facetValueId" in (34)
INTERSECT
select "productId"
from "product_facet_values_facet_value"
where "facetValueId" in (56)
https://dbfiddle.uk/?rdbms=postgres_13&fiddle=d06344b4a68c7b97fc1fad46c7437894
This is the same method as #a_horse_with_no_name used, but generalised very slightly.
WITH
targets AS
(
SELECT * FROM facet_value WHERE facetId IN (2, 6)
)
SELECT
map.productId
FROM
product_facet_values_facet_value AS map
INNER JOIN
targets AS tgt
ON tgt.facetValueId = map.facetValueId
GROUP BY
map.productId
HAVING
COUNT(DISTINCT tgt.facetId) = (SELECT COUNT(DISTINCT facetId) FROM targets)
I have two tables. In one table(order_produt) have multiple records by date and other table(Transfer_product) also multiple record by date. order_product table have correct record. i want update my transfer_product table with order_product table by date range.
order_product_table
-------------------------
id | date | Product_id | value
-------------------------------------------
1 | 2017-07-01 | 2 | 53
2 | 2017-08-05 | 2 | 67
3 | 2017-10-02 | 2 | 83
4 | 2018-01-20 | 5 | 32
5 | 2018-05-01 | 5 | 53
6 | 2008-08-05 | 6 | 67
Transfer_product_table
----------------------------
id | date | Product_id | value
--------------------------------------------
1 | 2017-08-01 | 2 | 10
2 | 2017-10-06 | 2 | 20
3 | 2017-12-12 | 2 | 31
4 | 2018-06-25 | 5 | 5
Result(Transfer_product_table)
--------------------------------
id | date | Product_id | value
--------------------------------------------
1 | 2017-08-01 | 2 | 53
2 | 2017-10-06 | 2 | 83
3 | 2017-12-12 | 2 | 83
4 | 2018-06-25 | 5 | 53
I want by date value update like you can see Result table.
i use query partion by but this is not what i want.
UPDATE Transfer_product_table imp
SET value = sub.value
FROM (SELECT product_id,value
,ROW_NUMBER() OVER (PARTITION BY product_id ORDER BY orderdate DESC)AS Rno
FROM order_product_table
where orderdate between '2017-07-01' and '2019-10-31') sub
WHERE imp.product_id = sub.product_id
and sub.Rno=1
and imp.date between '2017-07-01' and '2019-10-31'
This is pretty straightforward using postgres' awesome daterange type.
with order_product_table as (
select * from (
VALUES (1, '2017-07-01'::date, 2, 53),
(2, '2017-08-05', 2, 67),
(3, '2017-10-02', 2, 83),
(4, '2018-01-20', 5, 32),
(5, '2018-05-01', 5, 53),
(6, '2008-08-05', 6, 67)
) v(id, date, product_id, value)
), transfer_product_table as (
select * from (
VALUES (1, '2017-08-01'::date, 2, 10),
(2, '2017-10-06', 2, 20),
(3, '2017-12-12', 2, 31),
(4, '2018-06-25', 5, 5)
) v(id, date, product_id, value)
), price_ranges AS (
select product_id,
daterange(date, lead(date) OVER (PARTITION BY product_id order by date), '[)') as pricerange,
value
FROM order_product_table
)
SELECT id,
date,
transfer_product_table.product_id,
price_ranges.value
FROM transfer_product_table
JOIN price_ranges ON price_ranges.product_id = transfer_product_table.product_id
AND date <# pricerange
ORDER BY id
;
id | date | product_id | value
----+------------+------------+-------
1 | 2017-08-01 | 2 | 53
2 | 2017-10-06 | 2 | 83
3 | 2017-12-12 | 2 | 83
4 | 2018-06-25 | 5 | 53
(4 rows)
Basically, we figure out the price at any given date by using the order_product_table. We get the price between the current date (inclusive) and the next date (exclusive) with this:
daterange(date, lead(date) OVER (PARTITION BY product_id order by date), '[)') as pricerange,
Then we simply join to this on the condition that the product_ids match and that date in the transfer_product_table is contained by the pricerange.
We are dealing with large recordset and are currently using NTILE() to get the range of FileIDs and then using FileID column in BETWEEN clause to get specific records set. Using FileID in BETWEEN clause is a mandatory requirement from Developers. So, we cannot have random FileIDs in one batch, it has to be incremental.
As per new requirement, we have to make range based on FileSize column, e.g. 100 GB per batch.
For example:
Batch 1 : 1 has 100 size So ID: 1 record only.
Batch 2 : 2,3,4,5 = 80 but it is < 100 GB, so have to take FileId 6 if 120 GB (Total 300 GB)
Batch 3 : 7 ID has > 100 so 1 record only
And so on…
Below are my sample code, but it is not giving the expected result:
CREATE TABLE zFiles
(
FileId INT
,FileSize INT
)
INSERT INTO dbo.zFiles (
FileId
,FileSize
)
VALUES (1, 100)
,(2, 20)
,(3, 20)
,(4, 30)
,(5, 10)
,(6, 120)
,(7, 400)
,(8, 50)
,(9, 100)
,(10, 60)
,(11, 40)
,(12, 5)
,(13, 20)
,(14, 95)
,(15, 40)
DECLARE #intBatchSize FLOAT = 100;
SELECT y.FileID ,
y.FileSize ,
y.RunningTotal ,
DENSE_RANK() OVER (ORDER BY CEILING(RunningTotal / #intBatchSize)) Batch
FROM ( SELECT i.FileID ,
i.FileSize ,
RunningTotal = SUM(i.FileSize) OVER ( ORDER BY i.FileID ) -- RANGE UNBOUNDED PRECEDING)
FROM dbo.zFiles AS i WITH ( NOLOCK )
) y
ORDER BY y.FileID;
Result:
+--------+----------+--------------+-------+
| FileID | FileSize | RunningTotal | Batch |
+--------+----------+--------------+-------+
| 1 | 100 | 100 | 1 |
| 2 | 20 | 120 | 2 |
| 3 | 20 | 140 | 2 |
| 4 | 30 | 170 | 2 |
| 5 | 10 | 180 | 2 |
| 6 | 120 | 300 | 3 |
| 7 | 400 | 700 | 4 |
| 8 | 50 | 750 | 5 |
| 9 | 100 | 850 | 6 |
| 10 | 60 | 910 | 7 |
| 11 | 40 | 950 | 7 |
| 12 | 5 | 955 | 7 |
| 13 | 20 | 975 | 7 |
| 14 | 95 | 1070 | 8 |
| 15 | 40 | 1110 | 9 |
+--------+----------+--------------+-------+
Expected Result:
+--------+---------------+---------+
| FileID | FileSize (GB) | BatchNo |
+--------+---------------+---------+
| 1 | 100 | 1 |
| 2 | 20 | 2 |
| 3 | 20 | 2 |
| 4 | 30 | 2 |
| 5 | 10 | 2 |
| 6 | 120 | 2 |
| 7 | 400 | 3 |
| 8 | 50 | 4 |
| 9 | 100 | 4 |
| 10 | 60 | 5 |
| 11 | 40 | 5 |
| 12 | 5 | 6 |
| 13 | 20 | 6 |
| 14 | 95 | 6 |
| 15 | 40 | 7 |
+--------+---------------+---------+
We can achieve this if somehow we can reset the running total once it gets over 100. We can write a loop to have this result, but for that we need to go record by record, which is time consuming.
Please somebody help us on this?
You need to do this with a recursive CTE:
with cte as (
select z.fileid, z.filesize, z.filesize as batch_filesize, 1 as batchnum
from zfiles z
where z.fileid = 1
union all
select z.fileid, z.filesize,
(case when cte.batch_filesize + z.filesize > #intBatchSize
then z.filesize
else cte.batch_filesize + z.filesize
end),
(case when cte.batch_filesize + z.filesize > #intBatchSize
then cte.batchnum + 1
else cte.batchnum
end)
from cte join
zfiles z
on z.fileid = cte.fileid + 1
)
select *
from cte;
Note: I realize that fileid probably is not a sequence. You can create a sequence using row_number() in a CTE, to make this work.
There is a technical reason why running sums don't work for this. Essentially, any given fileid needs to know the breaks before it.
Small modification on above answered by Gordon Linoff and got expected result.
DECLARE #intBatchSize INT = 100
;WITH cte as (
select z.fileid, z.filesize, z.filesize as batch_filesize, 1 as batchnum
from zfiles z
where z.fileid = 1
union all
select z.fileid, z.filesize,
(case when cte.batch_filesize >= #intBatchSize
then z.filesize
else cte.batch_filesize + z.filesize
end),
(case when cte.batch_filesize >= #intBatchSize
then cte.batchnum + 1
else cte.batchnum
end)
from cte join
zfiles z
on z.fileid = cte.fileid + 1
)
select *
from cte;
I have a table that consists of a set codes for an item. Each code's group is defined by group_id. The table is defined as follows:
CREATE TABLE item_code (
id int PRIMARY KEY NOT NULL IDENTITY (1,1),
item_id int DEFAULT NULL,
group_id int NOT NULL,
code varchar(50) NOT NULL
);
CREATE TABLE groups (
id int PRIMARY KEY NOT NULL IDENTITY (1,1),
name varchar(50) NOT NULL,
order int NOT NULL
)
For each item_id in the table, I need to select 1 code from each group_id ordered by the group's order. For example:
INSERT INTO groups (id, name, order) VALUES (1, 'one', 10), (2, 'two', 20), (3, 'three', 30);
INSERT INTO item_code (item_id, group_id, [code])
VALUES
(99, 1, 'code1-1'),
(99, 1, 'code1-2'),
(99, 2, 'code2-1'),
(99, 2, 'code2-2'),
(99, 3, 'code3-1'),
(100,1, 'another-code');
would result in the set:
item_id code_combination
99 "code1-1"
99 "code1-2"
99 "code2-1"
99 "code2-2"
99 "code3-1"
99 "code1-1, code2-1"
99 "code1-1, code2-2"
99 "code1-2, code2-1"
99 "code1-2, code2-2"
99 "code1-1, code3-1"
99 "code1-2, code3-1"
99 "code2-1, code3-1"
99 "code2-2, code3-1"
99 "code1-1, code2-1, code3-1"
99 "code1-2, code2-1, code3-1"
99 "code1-1, code2-2, code3-1"
99 "code1-2, code2-2, code3-1"
100 "another-code"
The order of the actual results does not matter. I included a row for item_id == 100 just to show that results for all item_id should be included.
What I've done so far:
I've build a CTE that gets combinations of codes, but it does not respect item_id, groups or order and that's where I'm stuck:
;WITH cte ( combination, curr ) AS (
SELECT CAST(ic.code AS VARCHAR(MAX)), ic.id
FROM items_code ic
UNION ALL
SELECT CAST( c.combination + ',' + CAST(ic.code AS VARCHAR(10) ) AS VARCHAR(MAX) ), ic.id
FROM item_code ic
INNER JOIN
cte c
ON ( c.curr < ic.id )
)
SELECT combination FROM cte
UPDATE: I have a slightly more complicated schema than what I originally posted, and have built the schema in this fiddle. The idea is the same, it's just that "order" is defined on a different table.
Adding a little more to your recursive cte, expanding the final join conditions, as well as some additional columns:
;with cte as (
select
ic.id
, ic.item_id
, ic.group_id
, g.[order]
, level = 0
, combination = cast(ic.code as varchar(max))
from item_code ic
inner join groups g
on ic.group_id = g.id
union all
select
ic.id
, ic.item_id
, ic.group_id
, g.[order]
, level = c.level + 1
, combination = cast( c.combination + ',' + cast(ic.code as varchar(10) ) as varchar(max) )
from item_code ic
inner join groups g
on ic.group_id = g.id
inner join cte c
on c.id < ic.id
and c.[order] < g.[order]
and c.item_id = ic.item_id
)
select *
from cte
order by item_id, level, combination
rextester demo: http://rextester.com/PJC44281
returns:
+----+---------+----------+-------+-------+-------------------------+
| id | item_id | group_id | order | level | combination |
+----+---------+----------+-------+-------+-------------------------+
| 1 | 99 | 1 | 10 | 0 | code1-1 |
| 2 | 99 | 1 | 10 | 0 | code1-2 |
| 3 | 99 | 2 | 20 | 0 | code2-1 |
| 4 | 99 | 2 | 20 | 0 | code2-2 |
| 5 | 99 | 3 | 30 | 0 | code3-1 |
| 3 | 99 | 2 | 20 | 1 | code1-1,code2-1 |
| 4 | 99 | 2 | 20 | 1 | code1-1,code2-2 |
| 5 | 99 | 3 | 30 | 1 | code1-1,code3-1 |
| 3 | 99 | 2 | 20 | 1 | code1-2,code2-1 |
| 4 | 99 | 2 | 20 | 1 | code1-2,code2-2 |
| 5 | 99 | 3 | 30 | 1 | code1-2,code3-1 |
| 5 | 99 | 3 | 30 | 1 | code2-1,code3-1 |
| 5 | 99 | 3 | 30 | 1 | code2-2,code3-1 |
| 5 | 99 | 3 | 30 | 2 | code1-1,code2-1,code3-1 |
| 5 | 99 | 3 | 30 | 2 | code1-1,code2-2,code3-1 |
| 5 | 99 | 3 | 30 | 2 | code1-2,code2-1,code3-1 |
| 5 | 99 | 3 | 30 | 2 | code1-2,code2-2,code3-1 |
| 6 | 100 | 1 | 10 | 0 | another-code |
+----+---------+----------+-------+-------+-------------------------+
I have a table like this which contains a column number Order
FunctionID Name ParentFunctionID OrderNo Enabled ControllerName ActionName
1 Nikhil 0 10 1 nik
2 Sahil 0 20 1 sah
3 With 2 10 1 sah Withsnew
4 User 2 20 1 sah users
5 Pen 2 10 0 sah pend
44 User Summary 2 210 1 sah usersummary
45 Summary 2 230 1 sah summary
46 Hourly 1 231 1 nik hourly
47 Code 1 232 1 nik code
I want the Table like
FunctionID Name ParentFunctionID OrderNo Enabled ControllerName ActionName
1 Nikhil 0 1 1 nik
2 Sahil 0 2 1 sah
3 With 2 1 1 sah Withsnew
4 User 2 2 1 sah users
5 Pen 2 3 0 sah pend
44 User Summary 2 4 1 sah usersummary
45 Summary 2 5 1 sah summary
46 Hourly 1 1 1 nik hourly
47 Code 1 2 1 nik code
USE THE ROWNUM FEATURE:
SELECT
FunctionID ,
Name ,
ParentFunctionID,
ROWNUM AS ORDER_NO,
Enabled,
ControllerName ,
ActionName
FROM TABLE NAME;
If you want rownumbers to reset based on particular conditions, use PARTITION along with ROW_NUMBER AS:
SELECT
FunctionID ,
Name ,
ParentFunctionID,
ROW_NUMBER() OVER (PARTITION BY ParentFunctionID ORDER BY FunctionID ) AS ORDER_NO,
Enabled,
ControllerName ,
ActionName
FROM TABLE NAME;
you can use ROW_NUMBER and partition by ParentFunctionID just like this
SELECT FunctionID, Name, ParentFunctionID
, ROW_NUMBER() OVER(PARTITION BY ParentFunctionID ORDER BY FunctionID) OrderNo
, [Enabled], ControllerName, ActionName
FROM TABLENAME
this will return the resultset if you want to update you can modify the script.
I hope this is helpful.
You can use ROW_NUMBER (Transact-SQL) to enumerate your rows partitioned by ParentFunctionID ordered by FunctionID to get the result you want.
SQL Fiddle
MS SQL Server 2012 Schema Setup:
create table YourTable
(
FunctionID int,
Name varchar(20),
ParentFunctionID int,
OrderNo int,
Enabled bit,
ControllerName varchar(20),
ActionName varchar(20)
)
go
insert into YourTable values
(1 , 'Nikhil ', 0, 10 , 1, 'nik', ' '),
(2 , 'Sahil ', 0, 20 , 1, 'sah', ' '),
(3 , 'With ', 2, 10 , 1, 'sah', 'Withsnew '),
(4 , 'User ', 2, 20 , 1, 'sah', 'users '),
(5 , 'Pen ', 2, 10 , 0, 'sah', 'pend '),
(44, 'User Summary', 2, 210, 1, 'sah', 'usersummary'),
(45, 'Summary ', 2, 230, 1, 'sah', 'summary '),
(46, 'Hourly ', 1, 231, 1, 'nik', 'hourly '),
(47, 'Code ', 1, 232, 1, 'nik', 'code ')
Query 1:
with C as
(
select OrderNo,
row_number() over(partition by ParentFunctionID order by FunctionID) as rn
from YourTable
)
update C
set OrderNo = rn
select *
from YourTable
Results:
| FUNCTIONID | NAME | PARENTFUNCTIONID | ORDERNO | ENABLED | CONTROLLERNAME | ACTIONNAME |
|------------|--------------|------------------|---------|---------|----------------|-------------|
| 1 | Nikhil | 0 | 1 | 1 | nik | |
| 2 | Sahil | 0 | 2 | 1 | sah | |
| 3 | With | 2 | 1 | 1 | sah | Withsnew |
| 4 | User | 2 | 2 | 1 | sah | users |
| 5 | Pen | 2 | 3 | 0 | sah | pend |
| 44 | User Summary | 2 | 4 | 1 | sah | usersummary |
| 45 | Summary | 2 | 5 | 1 | sah | summary |
| 46 | Hourly | 1 | 1 | 1 | nik | hourly |
| 47 | Code | 1 | 2 | 1 | nik | code |