How to use group by without using an aggregation function - sql

I am trying to find the row number of the price while grouping them with respect to their ProductId. For ProductId = 1, rank of price (where price = 1000) should be smt. For productId=3, rank of price (= 1000) should be sth.
How can I find row number of price for different productId in the same table?
How can I achieve this using group by without aggregation for Row_number.
ProductId Price
-----------------
1 2000
1 1600
1 1000
2 2200
2 1000
2 3250
3 1000
3 2500
3 1750
So result should be
ProductId Price PriceRank
------------------------------
1 1000 3
2 1000 2
3 1000 1
This is my code:
SELECT
ProductId,
ROW_NUMBER() OVER (ORDER BY price ASC) AS PriceRank
FROM
product
WHERE
price = 1000
GROUP BY
ProductId

This will give you correct result:
select * from
(SELECT ProductId,price,ROW_NUMBER()
OVER (partition by productid order by productid ) AS PriceRank
FROM products) a
WHERE price =1000

Not sure if this is the best way to do it.. but it certainly is ONE way to do it
;WITH mycte AS (
SELECT
1 as ProductId , 2000 as Price
UNION ALL SELECT
1 , 1600
UNION ALL SELECT
1 , 1000
UNION ALL SELECT
2 , 2200
UNION ALL SELECT
2 , 1000
UNION ALL SELECT
2 , 3250
UNION ALL SELECT
3 , 1000
UNION ALL SELECT
3 , 2500
UNION ALL SELECT
3 , 1750
)
,my_rank as (
Select
ProductId
, Price
,ROW_NUMBER() OVER (ORDER BY (SELECT 1)) rownumber
from mycte
)
,ranking AS (
SELECT
ProductId
, Price
, rownumber
, ROW_NUMBER() OVER (PARTITION BY ProductId ORDER BY rownumber) pricerank
FROM my_rank
)
SELECT
ProductId
, Price
,pricerank
FROM ranking
WHERE Price = 1000

Try this:
declare #result table(ProductID smallint, Price int, PriceRank smallint)
declare #product table(ProductID smallint, Price int) --replicate your table data
declare #id smallint
--feed table with your data
insert into #product
select ProductId, Price from product
while(exists(select * from #product))
begin
select top 1 #id = ProductId from #product
insert into #result
SELECT
ProductId, price,
ROW_NUMBER() OVER (ORDER BY ProductID) as CountPerGroup
FROM #product
WHERE ProductID = #id
delete from #product where ProductID = #id
end
select * from #result where Price = 1000

Related

How to join tables, concatenate some data

I have two tables:
1. indexes and quantity of indexes
2. indexes and quantity of indexes with specified boxcodes. Boxcode is a number of box, which box contains indexes.
1. input table 1
item_id quantity
1 10
2 15
3 5
1 5
1 5
2 5
3 5
sum:
1 - 20
2 - 20
3 - 10
2. input table 2
item_id quantity boxcode
1 3 abc
2 2 abc
1 8 def
3 10 ghi
1 9 ghi
2 9 def
2 8 ghi !!!!!!!
1 item_id once on 1 boxcode
I want to get result:
3. result
item_id quantity boxcodes
1 10 abc/3, def/7
2 15 abc/2, def/9, ghi/4
3 5 ghi/5
1 5 def/1, ghi/4
1 5 ghi/5
2 5 ghi/4 !!!!!!!!
3 5 ghi/5
Records from table 1 must be in the same order.
I have no idea how it can be done.
Any suggestion?
CREATE TABLE #input1
(
rownum int,
item_id int,
quantity int
)
CREATE TABLE #input2
(
item_id int,
quantity int,
boxcode varchar(10)
)
INSERT INTO #input1 VALUES (1,1,10)
INSERT INTO #input1 VALUES (2,2,15)
INSERT INTO #input1 VALUES (3,3,5)
INSERT INTO #input1 VALUES (4,1,5)
INSERT INTO #input1 VALUES (5,1,5)
INSERT INTO #input1 VALUES (6,2,5)
INSERT INTO #input1 VALUES (7,3,5)
INSERT INTO #input2 VALUES (1,3, 'abc')
INSERT INTO #input2 VALUES (2,2, 'abc')
INSERT INTO #input2 VALUES (1,8, 'def')
INSERT INTO #input2 VALUES (3,10, 'ghi')
INSERT INTO #input2 VALUES (1,9, 'ghi')
INSERT INTO #input2 VALUES (2,9, 'def')
INSERT INTO #input2 VALUES (2,8, 'ghi')
select * from #input1
select * from #input2
drop table #input1
drop table #input2
result
Thanks,
Weird, but it works:
;WITH rec1 AS (
SELECT rownum,
item_id,
1 as q,
1 as [Level],
quantity
from #input1
UNION ALL
SELECT r.rownum,
r.item_id,
1,
[Level] + 1,
i.quantity
FROM rec1 r
INNER JOIN #input1 i
ON r.rownum = i.rownum AND r.item_id = i.item_id
WHERE [Level] < i.quantity
), rec2 AS (
SELECT boxcode,
item_id,
1 as q,
1 as [Level],
quantity
from #input2
UNION ALL
SELECT r.boxcode,
r.item_id,
1,
[Level] + 1,
i.quantity
FROM rec2 r
INNER JOIN #input2 i
ON r.boxcode = i.boxcode AND r.item_id = i.item_id
WHERE [Level] < i.quantity
), cte1 AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY item_id ORDER BY item_id, rownum) as rn
FROM rec1
), cte2 AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY item_id ORDER BY item_id, boxcode) as rn
FROM rec2
), final AS (
SELECT c1.rownum,
c1.item_id,
c1.quantity,
c2.boxcode+'/'+CAST(SUM(c2.q) as nvarchar(10)) as boxcodes
FROM cte1 c1
INNER JOIN cte2 c2
ON c1.item_id = c2.item_id and c1.rn = c2.rn
GROUP BY c1.rownum, c1.item_id, c1.quantity, c2.boxcode
)
SELECT DISTINCT
f.rownum,
f.item_id,
f.quantity,
STUFF((
SELECT ', '+f1.boxcodes
FROM final f1
WHERE f1.rownum = f.rownum
AND f1.item_id = f.item_id
AND f1.quantity = f.quantity
FOR XML PATH('')
),1,2,'') boxcodes
FROM final f
Output for dataset you have provided:
rownum item_id quantity boxcodes
1 1 10 abc/3, def/7
2 2 15 abc/2, def/9, ghi/4
3 3 5 ghi/5
4 1 5 def/1, ghi/4
5 1 5 ghi/5
6 2 5 ghi/4
7 3 5 ghi/5
The main idea is to spread quantity in both tables for a small parts 1. Than add row number, then join and get result.
A solution (but it's totally based on gofr1's answer, to be honest !), to simplify a bit, would be to create a Numbers table, with as many numbers as you want.
CREATE TABLE Numbers(Number INT PRIMARY KEY);
INSERT Numbers
SELECT TOP 1000 ROW_NUMBER() OVER (ORDER BY name)
FROM sys.all_columns;
That would just avoid the 2 recursive CTEs.
You could then use the same logic as gofr1 :
with rec1 AS (
SELECT
ROW_NUMBER() OVER (PARTITION BY item_id ORDER BY item_id, rownum) as rn,
rownum,
item_id,
case when quantity = 0 then 0 else 1 end as q,
quantity
from #input1
join Numbers n on n.Number <= quantity
)
, rec2 AS (
SELECT
ROW_NUMBER() OVER (PARTITION BY item_id ORDER BY item_id, boxcode) as rn,
boxcode,
item_id,
case when quantity = 0 then 0 else 1 end as q,
quantity
from #input2
join Numbers n on n.Number <= quantity
),
final AS (
SELECT c1.rownum,
c1.item_id,
c1.quantity,
c2.boxcode+'/'+CAST(SUM(c2.q) as nvarchar(10)) as boxcodes
FROM rec1 c1
INNER JOIN rec2 c2
ON c1.item_id = c2.item_id and c1.rn = c2.rn
GROUP BY c1.rownum, c1.item_id, c1.quantity, c2.boxcode
),
stuffed as (
SELECT
distinct rownum,
f.item_id,
f.quantity,
STUFF((
SELECT ', '+f1.boxcodes
FROM final f1
WHERE f1.rownum = f.rownum
AND f1.item_id = f.item_id
AND f1.quantity = f.quantity
FOR XML PATH('')
),1,2,'') boxcodes
FROM final f
group by item_id, quantity, boxcodes, rownum)
select *
from stuffed
order by rownum

How to select data rows into columns w/out knowing all values

Below is a table I have with some order details.
OrderID TypeID Amount
11148 1 900
11148 7 30
11148 6 75
12506 3 100
12506 4 60
16845 1 30
Is it possible to return something like this:
OrderID TypeID1 Amount1 TypeID2 Amount2 TypeID3 Amount3
11148 1 900 7 30 6 75
12506 3 100 4 60 null null
16845 1 30 null null null null
I want to get results like this so I can join into another result set with other order information but I can only have one row per ID. I've been trying with a Pivot but it seems I need to know all the possible results for TYPEID and have that many columns instead of just the 3 for TYPEID and 3 for AMOUNT that I'm looking for.
Below is the Pivot table I know how to get to work but this is not desired because then I have columns for each ID type but I would rather have those in the rows too.
SELECT OrderID
,[1] as Amount1
,[2] as Amount2
,[3] as Amount3
,[4] as Amount4
,[5] as Amount5
,[6] as Amount6
,[7] as Amount7
FROM (
SELECT ORDERID, TYPEID, AMOUNT
FROM Order_Details
)x
PIVOT
(
MAX(AMOUNT)
FOR TYPEID in ([1],[2],[3],[4],[5],[6],[7])
)P
OrderID Amount1 Amount2 Amount3 Amount4 Amount5 Amount6 Amount7
11148 900 null null null null 75 30
12506 null null 100 60 null null null
16845 30 null null null null null null
the solution is to make a self outer join combined with row_number over partition like:
select t1.OrderId as OrderID1, t1.TypeID as TypeID1, t1.Amount as Amount1,
t2.TypeID as TypeID2, t2.Amount as Amount2,
t3.TypeID as TypeID3, t3.Amount as Amount3 from
(
SELECT OrderID, TypeID, Amount
FROM (
select OrderID , TypeID, Amount ,ROW_NUMBER() OVER (PARTITION BY OrderID order by OrderID) AS RN
from tt
)Sub
WHERE rn = 1) t1 left join
(
SELECT OrderID, TypeID, Amount
FROM (
select OrderID , TypeID, Amount ,ROW_NUMBER() OVER (PARTITION BY OrderID order by OrderID) AS RN
from tt
)Sub
WHERE rn = 2) t2 on t1.OrderID=t2.OrderID left join
(
SELECT OrderID, TypeID, Amount
FROM (
select OrderID , TypeID, Amount ,ROW_NUMBER() OVER (PARTITION BY OrderID order by OrderID) AS RN
from tt
)Sub
WHERE rn = 3) t3 on t1.OrderID=t3.OrderID;
live example
by using cross apply and Row_number we can achieve same result
declare #Table1 TABLE
(OrderID int, TypeID int, Amount int)
;
INSERT INTO #Table1
(OrderID, TypeID, Amount)
VALUES
(11148, 1, 900),
(11148, 7, 30),
(11148, 6, 75),
(12506, 3, 100),
(12506, 4, 60),
(16845, 1, 30)
;
;with CTE As (
select OrderID,[TypeID1],[TypeID2],[TypeID3] from (
Select OrderID,
val,
COL + CAST(ROW_NUMBER()OVER(PARTITION BY OrderID ORDER BY OrderID) AS VARCHAR(1))RN
FROM #Table1
CROSS APPLY (VALUES ('TypeID',TypeID))CS(Col,val))T
PIVOT (MAX(VAL) FOR RN IN ([TypeID1],[TypeID2],[TypeID3]))P )
,CTE2 AS (select OrderID,[Amount1],[Amount2],[Amount3] from (
Select OrderID,
val,
COL + CAST(ROW_NUMBER()OVER(PARTITION BY OrderID ORDER BY OrderID) AS VARCHAR(1))RN
FROM #Table1
CROSS APPLY (VALUES ('Amount',Amount))CS(Col,val))T
PIVOT (MAX(VAL) FOR RN IN ([Amount1],[Amount2],[Amount3]))P)
select c.OrderID,c.TypeID1,cc.Amount1,c.TypeID2,cc.Amount2,c.TypeID3,cc.Amount3 from CTE C
LEFT JOIN CTE2 CC
ON c.OrderID = cc.OrderID

Get specific row from a subquery using aggregate function

I am trying to get a specific row from a subquery, but I cannot use an aggregate function in a WHERE clause and I have read that I should be using a HAVING clause but I have no idea where to start.
This is my current sql statement:
SELECT *
FROM
(
select ID, SUM(BALANCE) AS Balance FROM bankacc GROUP BY ID
)A
I will get :
ID | Balance
1 | 30
2 | 40
3 | 50
4 | 50
I need the rows with the MAX(Balance), but I have no idea where to start, please help.
With window function:
DECLARE #t TABLE ( ID INT, Amount MONEY )
INSERT INTO #t
VALUES ( 1, 10 ),
( 1, 10 ),
( 1, 10 ),
( 2, 5 ),
( 2, 20 ),
( 3, 50 )
SELECT ID ,
Amount
FROM ( SELECT ID ,
SUM(Amount) AS Amount ,
RANK() OVER ( ORDER BY SUM(Amount) DESC ) AS rn
FROM #t
GROUP BY ID
) t
WHERE rn = 1
With TOP and TIES:
SELECT TOP 1 WITH TIES
ID ,
SUM(Amount) AS Amount
FROM #t
GROUP BY ID
ORDER BY Amount desc
These versions will return rows where sum will be max, not just top 1 row.
Output:
ID Amount
3 50.00
you can wrap it in a subquery:
SELECT q.id, max(q.b)
FROM
(
select ID, SUM(BALANCE) b FROM bankacc GROUP BY ID
) q
group by q.id
or order them in dessending order and get first record:
select top 1 ID, SUM(BALANCE) b FROM bankacc GROUP BY ID order by b desc
in MySQL you need to use limit 1 instead of top 1
I think this should be simple.
-- This will return only 1 record, even if there are 2 records for MAX same amount
SELECT top 1 ID ,
Amount
FROM ( SELECT ID ,
SUM(Amount) AS Amount
FROM Table
GROUP BY ID
) t
Order by Amount desc,ID asc
Using Window function : This will return what you want.
SELECT ID ,
Amount
FROM ( SELECT ID ,
SUM(Amount) AS Amount ,
RANK() OVER ( ORDER BY SUM(Amount) DESC ) AS rnk
FROM Table
GROUP BY ID
) t
WHERE rnk = 1

Group data by the change of grouping column value in order

With the following data
create table #ph (product int, [date] date, price int)
insert into #ph select 1, '20120101', 1
insert into #ph select 1, '20120102', 1
insert into #ph select 1, '20120103', 1
insert into #ph select 1, '20120104', 1
insert into #ph select 1, '20120105', 2
insert into #ph select 1, '20120106', 2
insert into #ph select 1, '20120107', 2
insert into #ph select 1, '20120108', 2
insert into #ph select 1, '20120109', 1
insert into #ph select 1, '20120110', 1
insert into #ph select 1, '20120111', 1
insert into #ph select 1, '20120112', 1
I would like to produce the following output:
product | date_from | date_to | price
1 | 20120101 | 20120105 | 1
1 | 20120105 | 20120109 | 2
1 | 20120109 | 20120112 | 1
If I group by price and show the max and min date then I will get the following which is not what I want (see the over lapping of dates).
product | date_from | date_to | price
1 | 20120101 | 20120112 | 1
1 | 20120105 | 20120108 | 2
So essentially what I'm looking to do is group by the step change in data based on group columns product and price.
What is the cleanest way to achieve this?
There's a (more or less) known technique of solving this kind of problem, involving two ROW_NUMBER() calls, like this:
WITH marked AS (
SELECT
*,
grp = ROW_NUMBER() OVER (PARTITION BY product ORDER BY date)
- ROW_NUMBER() OVER (PARTITION BY product, price ORDER BY date)
FROM #ph
)
SELECT
product,
date_from = MIN(date),
date_to = MAX(date),
price
FROM marked
GROUP BY
product,
price,
grp
ORDER BY
product,
MIN(date)
Output:
product date_from date_to price
------- ---------- ------------- -----
1 2012-01-01 2012-01-04 1
1 2012-01-05 2012-01-08 2
1 2012-01-09 2012-01-12 1
I'm new to this forum so hope my contribution is helpful.
If you really don't want to use a CTE (although I think thats probably the best approach) you can get a solution using set based code. You will need to test the performance of this code!.
I have added in an extra temp table so that I can use a unique identifier for each record but I suspect you will already have this column in you source table. So heres the temp table.
If Exists (SELECT Name FROM tempdb.sys.tables WHERE name LIKE '#phwithId%')
DROP TABLE #phwithId
CREATE TABLE #phwithId
(
SaleId INT
, ProductID INT
, Price Money
, SaleDate Date
)
INSERT INTO #phwithId SELECT row_number() over(partition by product order by [date] asc) as SalesId, Product, Price, Date FROM ph
Now the main body of the Select statement
SELECT
productId
, date_from
, date_to
, Price
FROM
(
SELECT
dfr.ProductId
, ROW_NUMBER() OVER (PARTITION BY ProductId ORDER BY ChangeDate) AS rowno1
, ChangeDate AS date_from
, dfr.Price
FROM
(
SELECT
sl1.ProductId AS ProductId
, sl1.SaleDate AS ChangeDate
, sl1.price
FROM
#phwithId sl1
LEFT JOIN
#phwithId sl2
ON sl1.SaleId = sl2.SaleId + 1
WHERE
sl1.Price <> sl2.Price OR sl2.Price IS NULL
) dfr
) da1
LEFT JOIN
(
SELECT
ROW_NUMBER() OVER (PARTITION BY ProductId ORDER BY ChangeDate) AS rowno2
, ChangeDate AS date_to
FROM
(
SELECT
sl1.ProductId
, sl1.SaleDate AS ChangeDate
FROM
#phwithId sl1
LEFT JOIN
#phwithId sl3
ON sl1.SaleId = sl3.SaleId - 1
WHERE
sl1.Price <> sl3.Price OR sl3.Price IS NULL
) dto
) da2
ON da1.rowno1 = da2.rowno2
By binding the data source offset by 1 record (+or-) we can identify when the price buckets change and then its just a matter of getting the start and end dates for the buckets back into a single record.
All a bit fiddly and I'm not sure its going to give better performance but I enjoyed the challenge.
WITH marked AS (
SELECT
*,
case
when (lag(price,1,'') over (partition by product order by date_from)) = price
then 0 else 1
end is_price_change
FROM #ph
),
marked_as_group AS
( SELECT m.*,
SUM(is_price_change) over (PARTITION BY product order by date_from ROWS
BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS price_change_group
FROM marked m
),
SELECT
product,
date_from = MIN(date_from),
date_to = MAX(date_to),
price = MIN(price)
FROM marked_as_group
GROUP BY
product,
price_change_group
ORDER BY
product,
date_to
One solution I have come up with which is relatively "clean" is:
;with cte_sort (product, [date], price, [row])
as
(select product, [date], price, row_number() over(partition by product order by [date] asc) as row
from #ph)
select a.product, a.[date] as date_from, c.[date] as date_to, a.price
from cte_sort a
left outer join cte_sort b on a.product = b.product and (a.row+1) = b.row and a.price = b.price
outer apply (select top 1 [date] from cte_sort z where z.product = a.product and z.row > a.row order by z.row) c
where b.row is null
order by a.[date]
I used a CTE with row_number because you then don't need to worry about whether any dates are missing if you use functions like dateadd. You obviously only need the outer apply if you want to have the date_to column (which I do).
This solution does solve my problem, I am however having a slight issue getting it to perform as quickly as I'd like on my table of 5 million rows.
Create function [dbo].[AF_TableColumns](#table_name nvarchar(55))
returns nvarchar(4000) as
begin
declare #str nvarchar(4000)
select #str = cast(rtrim(ltrim(column_name)) as nvarchar(500)) + coalesce(' ' + #str , ' ')
from information_schema.columns
where table_name = #table_name
group by table_name, column_name, ordinal_position
order by ordinal_position DESC
return #str
end
--select dbo.AF_TableColumns('YourTable') Select * from YourTable

Group By by hiding a column - TSQL

I have a table structure
Table1
ID Hours Qty ProductID
1 2 1 100
1 3 5 200
2 6 6 100
2 2 2 200
If productid is (1,2,3) then i need sum ( Qty * Hours),If productid in (200,300,400,500) then i need sum(qty).
I have written a code like this
select ID,case when productid in (1,2,3) then
SUM( qty * hrs)
when productid in (100,200,300) then SUM( qty ) end result1
from Prod group by id ,productid
but i don't want to group by productid,i would like to pass it in "IN clause".How to achieve it.
Move the SUM() outside of the CASE WHEN statement.
SELECT
ID,
SUM(case when productid in (1,2,3) then qty * hrs
when productid in (100,200,300) then qty
end) result1
FROM
Prod
GROUP BY
ID
Assuming you want all columns plus the result of your query, you can do this:
select p.*, aux.result
from Prod p
inner join (select ID,case when productid in (1,2,3) then SUM( qty * hrs)
when productid in (100,200,300) then SUM( qty )
end as result
from Prod group by id ,productid) aux on aux.id = p.id