Let's say I have a table named tableA having a data of
| col1 | col2 |
| 1 | 5 |
| 1 | 6 |
| 1 | 7 |
| 2 | 1 |
| 2 | 2 |
| 2 | 3 |
| 2 | 4 |
| 3 | 3 |
| 3 | 2 |
| 3 | 1 |
Then what I would like to get is the last 2 occurrences of each unique value of col1. The result would be
| col1 | col2 |
| 1 | 6 |
| 1 | 7 |
| 2 | 3 |
| 2 | 4 |
| 3 | 2 |
| 3 | 1 |
Is there a single query to get this result?
You can use ROW_NUMBER
WITH CTE AS(
SELECT *, rn = ROW_NUMBER() OVER(PARTITION BY col1 ORDER BY col2 DESC)
FROM tbl
)
SELECT
col1, col2
FROM CTE
WHERE rn <= 2
Just expanding on Felix's answer, assuming there's an ID column: http://www.sqlfiddle.com/#!3/04a5e/3/0
WITH CTE AS(
SELECT *, rn = ROW_NUMBER() OVER(PARTITION BY cola ORDER BY id DESC)
FROM ta
)
SELECT
cola, colb
FROM CTE
WHERE rn <= 2
order by id
Need to reorder by ID to keep the correct order, plus ordering in the row_number() by the ID because col2 isn't always incremental.
Related
I am trying to assign what amounts to a 'combinationid' to rows of my table, based on the values in the two columns below. Each product has a number of customers linked to it. For every combination of customers, I need to create a combination ID.
For example, the combination of customers for product 'a' is the same combination of customers for product 'c' (they both have customers 1, 2 and 3), so products a and c should have the same combination identifier ('customergroup'). However, products should not share the same customergroup if they only share some of the same customers - e.g. product b only has customers 1 and 2 (not 3), so should have a different customergroup to products 'a' and 'c'.
Input:
| productid | customerid |
|-----------|------------|
| a | 1 |
| a | 2 |
| a | 3 |
| b | 1 |
| b | 2 |
| c | 3 |
| c | 2 |
| c | 1 |
| d | 1 |
| d | 3 |
| e | 1 |
| e | 2 |
| f | 1 |
| g | 2 |
| h | 3 |
Desired output:
| productid | customerid | customergroup |
|-----------|------------|---------------|
| a | 1 | 1 |
| a | 2 | 1 |
| a | 3 | 1 |
| b | 1 | 2 |
| b | 2 | 2 |
| c | 3 | 1 |
| c | 2 | 1 |
| c | 1 | 1 |
| d | 1 | 3 |
| d | 3 | 3 |
| e | 1 | 2 |
| e | 2 | 2 |
| f | 1 | 4 |
| g | 2 | 5 |
| h | 3 | 6 |
or just
| productid | customergroupid |
|-----------|-----------------|
| a | 1 |
| b | 2 |
| c | 1 |
| d | 3 |
| e | 2 |
| f | 4 |
| g | 5 |
| h | 6 |
Edit: first version of this did include a description of my attempts. I currently have nested queries that basically give me a column for customer 1, 2, 3 etc and then uses dense rank to get the grouping. The problem is that is not dynamic for different numbers of customers and I did not know where to start for getting a dynamic result as above. Thanks for the replies.
Considering you haven't shown your efforts, or confirmed the version you're using, I've assumed you have the latest ("and greatest") version of SQL Server, which means you have access to STRING_AGG.
This doesn't give the groupings in the same order, but I'm going to also also that doesn't matter, and the grouping is just arbitrary. This gives you the following:
WITH VTE AS(
SELECT *
FROM (VALUES('a',1),
('a',2),
('a',3),
('b',1),
('b',2),
('c',3),
('c',2),
('c',1),
('d',1),
('d',3),
('e',1),
('e',2),
('f',1),
('g',2),
('h',3)) V(productid,customerid)),
Groups AS(
SELECT productid,
STRING_AGG(customerid,',') WITHIN GROUP (ORDER BY customerid) AS CustomerIDs
FROM VTE
GROUP BY productid),
Rankings AS(
SELECT productid,
CustomerIDs,
DENSE_RANK() OVER (ORDER BY CustomerIDs ASC) AS Grouping
FROM Groups)
SELECT V.productid,
V.customerid,
R.Grouping AS customergroupid
FROM VTE V
JOIN Rankings R ON V.productid = R.productid
ORDER BY V.productid,
V.customerid;
db<>fiddle.
If you aren't using SQL Server 2017, I suggest looking up the FOR XML PATH method for string aggregation.
Using Larnu's answer this is how I got the result for 2008:
WITH VTE AS(
SELECT *
FROM (VALUES('a','1'),
('a','2'),
('a','3'),
('b','1'),
('b','2'),
('c','3'),
('c','2'),
('c','1'),
('d','1'),
('d','3'),
('e','1'),
('e','2'),
('f','1'),
('g','2'),
('h','3')) V(productid,customerid)),
Groups AS(
SELECT productid, CustomerIDs = STUFF((SELECT N', ' + customerid
FROM VTE AS p2
WHERE p2.productid = p.productid
ORDER BY customerid
FOR XML PATH(N'')), 1, 2, N'')
FROM VTE AS p
GROUP BY productid),
Rankings AS(
SELECT productid,
CustomerIDs,
DENSE_RANK() OVER (ORDER BY CustomerIDs ASC) AS Grouping
FROM Groups)
SELECT V.productid,
V.customerid,
R.Grouping AS customergroupid
FROM VTE V
JOIN Rankings R ON V.productid = R.productid
ORDER BY V.productid,
V.customerid;
Thanks again for your assistance.
I have got two tables 'Customer'.
The first one:
ID | UserID | Date
1. | 1 | 2018-05-01
2. | 1 | 2018-05-02
The second one:
ID | UserID | Date
1. | 1 | 2018-05-01
2. | 1 | 2018-05-02
3. | 1 | 2018-05-03
So, as you can see in the second table, there is one row more.
I have written so far this code:
;with cte_table1 as (
select UserID, count(id) cnt from db1.Customer group by UserID
),
cte_table2 as (
select UserID, count(id) cnt from db2.Customer group by UserID
)
select * from cte_table1 t1
join cte_table2 t2 on t2.UserID = t1.UserID
where t1.cnt <> t2.cnt
and this gives me expected result:
UserID | cnt | UserID | cnt
1 | 2 | 1 | 3
And so far, everything is fine. The thing is, these two tables have many rows and I'd like to have result with dates, where cnt does not match.
In other words, I'd like to have something like this:
UserID | cnt | Date | UserID | cnt | Date
1 | 2 | 2018-05-01 | 1 | 3 | 2018-05-01
1 | 2 | 2018-05-02 | 1 | 3 | 2018-05-01
1 | 2 | NULL | 1 | 3 | 2018-05-03
The best soulution would be resultset where both cte's are joined to give this:
UserID | cnt | Date | UserID | cnt | Date
1 | 2 | 2018-05-01 | 1 | 3 | 2018-05-01
1 | 2 | 2018-05-02 | 1 | 3 | 2018-05-01
1 | 2 | NULL | 1 | 3 | 2018-05-03
1 | 2 | 2018-05-30 | 1 | 3 | NULL
You should do a FULL OUTER JOIN query like below
Select
C1.UserID,
C1.cnt,
C1.Date,
C2.UserID,
C2.cnt,
C2.Date
from
db1.Customer C1
FULL OUTER JOIN
db2.Customer C2
on C1.UserId=C2.UserId and C1.date=C2.Date
Based on the product and product key, update the column ord_by. There should be only one min and max for a product and product_key .
E.g: Table
+-------------+---------+-------+--------+
| Product_key | product | price | ord_by |
+-------------+---------+-------+--------+
| 1 | ABC | 10 | |
| 1 | ABC | 10 | |
| 1 | ABC | 20 | |
| 1 | ABC | 100 | |
| 1 | ABC | 100 | |
| 2 | EFG | 20 | |
| 2 | EFG | 40 | |
| 3 | ABC | 100 | |
+-------------+---------+-------+--------+
Expected output:
+-------------+---------+-------+--------+
| Product_key | product | price | ord_by |
+-------------+---------+-------+--------+
| 1 | ABC | 10 | Min |
| 1 | ABC | 10 | Mid |
| 1 | ABC | 20 | Mid |
| 1 | ABC | 100 | Mid |
| 1 | ABC | 100 | Max |
| 2 | EFG | 20 | Min |
| 2 | EFG | 40 | Max |
| 3 | ABC | 100 | None |
+-------------+---------+-------+--------+
My try :
;WITH ord_cte
AS (
SELECT product
,product_key
,max(price) as max_price
,min(price) as min_price
FROM t_prod_ord
group by product,product_key
)
UPDATE t1
SET ord_by = case
when t2.max_price =t2.min_price then 'none'
when t2.max_price=t1.price then 'max'
when t2.min_price=t1.price then 'min'
else 'mid' end
FROM t_prod_ord t1
INNER JOIN ord_cte t2 ON t1.product_key = t2.product_key and t1.product=t2.product
using this query it is updating more than one max and min value for column ord_by.
Generate row number for each Product_key order by Price in both ASC and DESC order. Then use the row number in CASE statement to find the Min/Max values
Count() Over() aggregate window function will help you find the total count of each Product_key which we can use it for finding None
Here is one way
;WITH cte
AS (SELECT *,
Row_number()OVER(PARTITION BY Product_key ORDER BY price) AS Min_KEY,
Row_number()OVER(PARTITION BY Product_key ORDER BY price DESC) AS Max_KEY,
Count(1)OVER(partition BY Product_key) AS cnt
FROM Yourtable)
SELECT Product_key,
product,
price,
CASE
WHEN cnt = 1 THEN 'None'
WHEN Min_KEY = 1 THEN 'Min'
WHEN Max_Key = 1 THEN 'Max'
ELSE 'Mid'
END
FROM cte
Another way to do with out cte...
SELECT [Product_key],
[product],
[price],
CASE
WHEN Max(RN)
OVER(
PARTITION BY PRODUCT_KEY, PRODUCT
)=1 AND RN=1 THEN 'NONE'
WHEN Min(RN)
OVER(
PARTITION BY PRODUCT_KEY, PRODUCT
) = RN THEN 'MIN'
WHEN Max(RN)
OVER(
PARTITION BY PRODUCT_KEY, PRODUCT
) = RN THEN 'MAX'
ELSE 'MID'
END ORDER_BY
FROM (SELECT *,
Row_number()
OVER(
PARTITION BY PRODUCT_KEY, PRODUCT
ORDER BY PRICE) RN
FROM TABLE1)Z
This is a bit of a tricky question/situation and my search fu failed me.
Lets say i have the following data
| UID | SharedID | Type | Date |
|-----|----------|------|-----------|
| 1 | 1 | foo | 2/4/2016 |
| 2 | 1 | foo | 2/5/2016 |
| 3 | 1 | foo | 2/8/2016 |
| 4 | 1 | foo | 2/11/2016 |
| 5 | 2 | bar | 1/11/2016 |
| 6 | 2 | bar | 2/11/2016 |
| 7 | 3 | baz | 2/1/2016 |
| 8 | 3 | baz | 2/3/2016 |
| 9 | 3 | baz | 2/11/2016 |
And I would like to ommit a variable number of leading rows (most recent date in this case) and lets say that number is 2 in this example. The resulting table would be something like this:
| UID | SharedID | Type | Date |
|-----|----------|------|-----------|
| 1 | 1 | foo | 2/4/2016 |
| 2 | 1 | foo | 2/5/2016 |
| 7 | 3 | baz | 2/1/2016 |
Is this possible in SQL? Essentially I want to filter on an unknown number of rows which uses the date column as the order by. The goal is to get the oldest types and get a list of UID's in the process.
Sure, it's possible. Use a ROW_NUMBER function to assign a value to each row, partitioning by the SharedID column so that the count restarts every time that ID changes, and select those rows with a value greater than your limit.
WITH cteNumberedRows AS (
SELECT UID, SharedID, Type, Date,
ROW_NUMBER() OVER(PARTITION BY SharedID ORDER BY Date DESC) AS RowNum
FROM YourTable
)
SELECT UID, SharedID, Type, Date
FROM cteNumberedRows
WHERE RowNum > 2;
Not sure if I understand what you mean but something like this?
SELECT * FROM MyTable t1 JOIN MyTable T2 ON t2.id NOT IN (
SELECT TOP 2 UID FROM myTable
WHERE SharedID = t1.sharedID
ORDER BY [Date] DESC
)
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions asking for code must demonstrate a minimal understanding of the problem being solved. Include attempted solutions, why they didn't work, and the expected results. See also: Stack Overflow question checklist
Closed 9 years ago.
Improve this question
I am trying to convert rows into columns but include a sort of the data.
Sample Data
For instance:
+-----+------+------+
| CId | Cat1 | cat2 |
+-----+------+------+
| 1 | 10 | 6 |
| 1 | 230 | 100 |
| 2 | 1222 | 30 |
| 3 | 2 | 50 |
| 4 | 33 | 21 |
| 1 | 33 | 13 |
+-----+------+------+
Expected output
+-----+------+-----+-----+-----+-----+-----+
| CId | Rw1 | Rw2 | Rw3 | Rw4 | Rw5 | Rw6 |
+-----+------+-----+-----+-----+-----+-----+
| 1 | 10 | 33 | 230 | 6 | 13 | 100 |
| 2 | 1222 | 30 | 0 | 0 | 0 | 0 |
| 3 | 2 | 50 | 0 | 0 | 0 | 0 |
| 4 | 33 | 21 | 0 | 0 | 0 | 0 |
+-----+------+-----+-----+-----+-----+-----+
See how CID: 1 sorted all values for Cat1 after that's done, need to sort cat2 and everything should be in one row.
Please let me know how to do it.
You can get the result by unpivoting and pivoting the data, but you will also want to use row_number() to keep the data in the sequence that you want.
First step would be to query your current data and apply a row_number() to get a value for each row, partitioned by the cid and ordered by cat1 and cat2:
select cid, cat1, cat2,
row_number() over(partition by cid order by cat1, cat2) seq
from yourtable
See Demo. Once you have the data, then you will unpivot the multiple columns cat1 and cat2 into a single column with multiple rows. You can use the UNPIVOT function or you can use CROSS APPLY to convert the data:
select cid, value
, 'rw'+cast(row_number() over(partition by cid order by col, seq) as varchar(10)) rw
from
(
select cid, cat1, cat2,
row_number() over(partition by cid order by cat1, cat2) seq
from yourtable
) d
cross apply
(
select 1, cat1 union all
select 2, cat2
) c (col, value)
See Demo. When you unpivot the data, you will apply the row_number() a second time, this will be used to create your new columns names. When applied this time, you will partition the data by the cid and order it by your columns cat1/cat2 (I used 1/2) as well as the sequence you original created. This new row number will create all of the new column headers and it will keep the data in the order that you want to display it in.
Finally you will apply the PIVOT function:
select cid,
coalesce(rw1, 0) rw1,
coalesce(rw2, 0) rw2,
coalesce(rw3, 0) rw3,
coalesce(rw4, 0) rw4,
coalesce(rw5, 0) rw5,
coalesce(rw6, 0) rw6
from
(
select cid, value
, 'rw'+cast(row_number() over(partition by cid order by col, seq) as varchar(10)) rw
from
(
select cid, cat1, cat2,
row_number() over(partition by cid order by cat1, cat2) seq
from yourtable
) d
cross apply
(
select 1, cat1 union all
select 2, cat2
) c (col, value)
) src
pivot
(
max(value)
for rw in (rw1, rw2, rw3, rw4, rw5, rw6)
) piv;
See SQL Fiddle with Demo. This gives a final result:
| CID | RW1 | RW2 | RW3 | RW4 | RW5 | RW6 |
|-----|------|-----|-----|-----|-----|-----|
| 1 | 10 | 33 | 230 | 6 | 13 | 100 |
| 2 | 1222 | 30 | 0 | 0 | 0 | 0 |
| 3 | 2 | 50 | 0 | 0 | 0 | 0 |
| 4 | 33 | 21 | 0 | 0 | 0 | 0 |