SQL split totals - sql

Currently I have the following result in SQL:
qty
description
volume
weight
4
Flowers
3,4
4
This is in an XML format. So what I want to do, and I think I need to user "FOR XML PATH" in a certain way, but I am not sure how to achieve the following result:
qty
description
volume
weight
1
Flowers
0,85
1
1
Flowers
0,85
1
1
Flowers
0,85
1
1
Flowers
0,85
1
So I need to divide the XML path based on the total qty (4). For each (4) products, I need to create a new row. Then divide the volume and the weight (/qty).
Can anyone help me to push me into the right direction?
Edit:
The first result, qty of 4, is a result in a temp table.
I extract the data from the temp table into XML format. Here is a snippet
(SELECT "qty" = value('(#col24)[1]', 'varchar(50)'), "weight" = value('(#col28)[1]', 'varchar(50)'), "volume" = value('(#col26)[1]', 'decimal(16,2)') FOR XML PATH('product'), ROOT('products'), TYPE)
The qty, weight and volume represents the totals.
This is what I want to devide to create a "product" for each "qty".

You can use a recursive CTE to split the rows (you might need to up the recursion limit if your quantity can be higher than 100).
declare #Test table (qty int, [description] varchar(64), volume decimal(9,2), [weight] decimal(9,2))
insert into #Test (qty, [description], volume, [weight]) values (4, 'Flowers', 3.4, 4);
with cte as (
select qty, [description], volume, [weight], 1 as rn
from #Test
union all
select qty, [description], volume, [weight], rn + 1
from cte
where rn < qty
)
select 1 qty, [description], cast(volume / qty as decimal(9,2)) volume, cast([weight] / qty as decimal(9,2)) [weight]
from cte
for xml path('product'), root('products'), type;
-- option (maxrecursion 200); -- If you need to increase it above the default of 100
Note: If you setup the DDL+DML, as I shown, in your questions you make it much easier for people to reply.

One way is by recursive CTE:
WITH cte AS (
SELECT *, qty AS cc, 1 org FROM #sale
UNION ALL
SELECT cte.qty /qty
, cte.description
, cte.volume /qty
, cte.weight /qty
, cte.cc - 1 AS cc
, 0 org
FROM cte
WHERE cc > 1
)
SELECT * FROM cte
where org = 0
FOR XML path
However if you have a tally table it will faster and simpler:
SELECT qty /qty
, description
, volume /qty
, weight /qty
FROM table
JOIN numbertables < -- number tables from 1 to max
on numbertables.values < qty
FOR XML path

Related

Use SSRS to move child rows to repeating columns for exporting?

I'm trying to take an ugly SQL output and use SSRS to make it suitable for export to a mail house.
What would be the right approach to group data from this:
order_no
type
name
item
price
1
header
sally
NULL
NULL
1
data
NULL
book
12.50
1
data
NULL
dvd
39.00
2
header
bob
NULL
NULL
2
data
NULL
shirt
50.00
2
data
NULL
shorts
65.00
Into this?
order_no
type
name
item_1
price_1
item_2
price_2
1
header
sally
book
12.50
dvd
39.00
2
header
bob
shirt
50.00
shorts
65.00
Should this be a Matrix? I'm having trouble getting making progress.
There may be a much cleaner way of doing this but this is the approach I took...
First I replicated your sample data
DECLARE #t TABLE(order_no int, [type] varchar(20), [name] varchar(20), [item] varchar(20), price decimal (10,2))
INSERT INTO #t VALUES
(1,'header', 'sally' , NULL, NULL ),
(1,'data', NULL , 'book', 12.50),
(1,'data', NULL , 'dvd', 39.00),
(2,'header', 'bob' , NULL, NULL ),
(2,'data', NULL , 'shirt', 50.00),
(2,'data', NULL , 'shorts', 65.00)
;
WITH o (order_no, [type], [name], [item], [price], [ItemNumber]) AS
(
SELECT *, ROW_NUMBER() OVER(PARTITION BY order_no ORDER BY item) AS ItemNumber FROM #t WHERE [type] != 'header'
)
SELECT
h.order_no, h.[type], h.name
, d.ItemNumber, d.ItemCaption, d.ItemValue
FROM (SELECT DISTINCT order_no, [Type], [name] FROM #t WHERE [type] = 'header') h
JOIN
(
SELECT order_no, ItemNumber, 'Item_' + CAST(ItemNumber as varchar(10)) as ItemCaption, Item as ItemValue from o
UNION
SELECT order_no, ItemNumber, 'Price_' + CAST(ItemNumber as varchar(10)) as ItemCaption, CAST(Price as varchar(20)) as ItemValue from o
) d ON h.order_no = d.order_no
I created a CTE just to clean up the query a little and included an row_number for each item, we'll use this to created column captions which we can use in the matrix.
This gives us the following output
We now have everything in place for a simple matrix.
Note: As we had to convert everything to strings, the prices are no longer numbers so bear this in mind if you plan on doing anything else with the data later - they would have to be converted back
So, create a new report, add a new dataset and use the above query as the dataset query.
Add a matrix control, drag order_no to the row placeholder, ItemCaption to the column placeholder and ItemValue to the data placeholder.
Next, right-click the order_no column and choose "insert column - inside group right", the set new column value to your type field. Repeat for the header field.
Your design will look like this.
Finally In the column group sort properties, sort by ItemNumber then ItemCaption
The final report looks like this...

SQL Select Record Using Range of Row Value

I have the following database records
ID Weight Cost
1 3 1.00
2 10 2.00
3 14 3.00
What I want is to grab the cost for a given weight. So if my Weight is 5, the cost would be 2.00.
The clarify, the ranges would be:
Cost 1.00, weight 0-3
Cost 2.00, weight 4-10
Cost 3.00, weight 11-14
I'm not sure what SQL should I use to get the row range, but using columns, I can use SELECT Cost FROM table1 WHERE column1 BETWEEN x AND y.
Any suggestions / comments are welcome.
Is this what you're after?
http://sqlfiddle.com/#!6/b5307/20
declare #Weight int = 5
select top 1 cost
from weightings
where weight >= #Weight
order by weight
There are several ways to do this, perhaps the easiest is to generate a sequence of weights/costs given the intervals present in the table. To do this we can use a numbers/tally-table (in this case I use the master..spt_values table which has a suitable range of numbers).
So given a table like:
declare #t table (ID int, Weight int, Cost decimal(10,2))
insert #t values (1, 3, 1.00),(2, 10, 2.00),(3, 14, 3.00)
We can define two versions of a query (wrapped in a common table expression for convenience). The first version uses the lag()window function and requires a version of SQL Server newer than 2012:
;with costs1 (weight, cost) as (
select number, cost
from master..spt_values
inner join (
select isnull(LAG(weight) over (order by id)+1,0) low, weight high, cost from #t
) t on number <= t.high and number >= t.low
where type= 'P'
)
select cost from costs1 where weight = 5;
The second version doesn't rely on lag() but uses a self-join instead:
;with costs2 (weight, cost) as (
select number, cost
from master..spt_values
inner join (
select
isnull(t2.weight + 1,0) as low,
t1.Weight as high,
t1.Cost
from #t t1
left join #t t2 on t1.ID - 1 = t2.ID
) t on number <= t.high and number >= t.low
where type= 'P'
)
select cost from costs2 where weight = 5
Another option would be to just compute the low/high points for each range and do a query like this:
select cost from (
select isnull(LAG(weight) over (order by id)+1,0) low, weight high, cost from #t
) t
where 5 between low and high
Sample SQL Fiddle with the queries above.

not just another 'column invalid in select list' error

i say this because i tried all the usual solutions and they just aren't working. here's what i have..
Table 1
CREATE TABLE dbo.Temp
(
PrintData nvarchar(250) NOT NULL,
Acronym nvarchar(3) NOT NULL,
Total int not null
)
this is successfully populated using 3 SELECT's with a Group By unioned together
Table 2
CREATE TABLE dbo.Result
(
PrintData nvarchar(250) NOT NULL,
Acronym nvarchar(3) NOT NULL,
Total int not null,
[Percent] decimal(7,5) not null
)
all i want to do is populate this table from Table 1 while adding the Percent column which i calculate using the following stmt..
INSERT INTO dbo.Result
(PrintData, Acronym, Total, [Percent])
select *, ((t.Total / SUM(t.Total)) * 100)
from Temp t
group by PrintData, Acronym, Total
but the Percent col comes out as 0.00000 on every row
i thought it might have something to do with the group by but if i remove it, i get that stupid error i quoted.
some sample data from table 1..
OSHIKANGO OSH 1
WINDHOEK 1 WHA 18
WINDHOEK 2 WHB 8
WINDHOEK 3 WHC 2
WINDHOEK 4 WHD 4
with this sample data, SUM(Total) is 33. what i want in table 2 is this..
OSHIKANGO OSH 1 3.03030
WINDHOEK 1 WHA 18 54.5454
WINDHOEK 2 WHB 8 24.2424
WINDHOEK 3 WHC 2 etc
WINDHOEK 4 WHD 4
seems it should be simpler than this and hope i don't have to go as far as using a Transaction/cursor loop..
Try modifying your query a bit like below, by getting the percent calculation separately and do a JOIN with it later
INSERT INTO dbo.Result (PrintData, Acronym, Total, [Percent])
select t1.PrintData,
t1.Acronym,
t1.Total,
tab.computed
from Temp t1
join
(
select PrintData,
cast(t.Total as decimal(7,5)) / SUM(t.Total) * 100 as computed
from Temp t
group by PrintData, Total
) tab on t1.PrintData = tab.PrintData;
There is casting problem, try this query :
INSERT INTO dbo.Result
SELECT PrintData,
Acronym,
Sum(Total) [total],
Round(Sum(Total) / Cast((SELECT Sum(Total)
FROM temp) AS DECIMAL(10, 4)) * 100, 4) [Percent]
FROM temp
GROUP BY PrintData,Acronym
Also I see you are group by Total too. in that case you can use this :
INSERT INTO dbo.Result
SELECT *,Round((Sum(Total)OVER(partition BY PrintData, Acronym)) / Cast(Sum(Total) OVER() AS DECIMAL(10, 4)) * 100, 4) AS [percent]
FROM temp
convert both to decimal (7,5)
INSERT INTO dbo.Result
(PrintData, Acronym, Total, [Percent])
select *, (convert(decimal(7,5),Total) /
(select SUM(convert(decimal(7,5),Total)) * 100 AS [percent] FROM temp))
from Temp
group by PrintData, Acronym, Total

how to get query value from 1 row to use to another row?

This is example query:
payment_Type payment_value cost_type cost value
Cost I 100 Registration 40
Cost I 100 books 40
Cost I 100 Lab 40
The COST I has 3 elements Cost_Type that have their own Cost_value.
I want to manipulate like this:
payment_Type payment_value cost_type cost value Payment_by_cost_type
Cost I 100 Registration 40 40
Cost I 100 books 40 40
Cost I 100 Lab 40 20
The point is the I want to divided the payment_value into each cost_value. In the example the payment_by_cost becomes 40, 40, 20 = 100.
The lab cost_value is 40 but it can assign value is 20 because remains from the divided 2 cost type above.
Is it possible that I can use the value from Payment_by_cost_type in the next row record? I have been trying to insert the value Payment_by_cost_type to a temporary table but select cannot have insert statement.
Does anyone have any ideas on how to solve this? I've been consulting to DWH he said it must using Store procedure it cannot done by query.
I guess your table contains not only "Cost I" but other values so here is a query to output results for all groups (by Payment_type) in the table:
;with table1 as
(select
t.*,
row_number()
OVER
(PARTITION BY payment_Type order by cost_type) rn
from t
)
,table2 as
( select t4.*,isnull((select sum(cost_value) from table1
where table1.payment_type=t4.payment_type and rn<t4.rn),0) CumSum
from table1 t4
)
select payment_type,payment_value,cost_type,cost_value,
case when cost_value+CumSum<=payment_value then cost_value
else
payment_value-Cumsum
end
from table2
order by Payment_type,rn;
SQLFIDDLE demo
You need to define some kind of order for your records to define in which order the payments should be applied
Once you have done that (i'm using ID in this example)...
select *
, case
when payment_value-(select isnull(SUM(cost_value),0) from yourtable t2 where t2.id<t1.id)<cost_value
then payment_value-(select isnull(SUM(cost_value),0) from yourtable t2 where t2.id<t1.id)
else cost_value
end
from yourtable t1
Doing it step by step using common table expressions.
declare #t table (
payment_type varchar(20),
payment_value int,
cost_type varchar(20),
cost_value int,
cost_id int --for the ordering
)
insert #t values
('Cost I',100,'Registration',40,1),
('Cost I',100,'books',40,2),
('Cost I',100,'Lab',40,3),
('Cost 2',100,'Registration',40,4),
('Cost 2',100,'books',50,5),
('Cost 2',100,'Lab',40,6)
--get count for each payment_type to determine last row
;with payment_value_cte(payment_type,payment_value,count) as
(
select payment_type,payment_value,COUNT(*) from #t group by payment_type,payment_value
),
--use sequential index for each row in payment type
payment_value_index_cte(
payment_type ,
payment_value,
cost_type,
cost_value,
cost_id,
row) as
(
select *,ROW_NUMBER() OVER(PARTITION BY payment_type ORDER BY cost_id) from #t --assumes order is by an id
),
--get sum of each row for payment type except last row
payment_value_sum_except_last_cte(
payment_type,
payment_value,
current_sum) as
(
select pi.payment_type,pi.payment_value,SUM(cost_value)
from payment_value_index_cte pi
inner join payment_value_cte pt on pt.payment_type = pi.payment_type
where pi.row < pt.count
group by pi.payment_type,pi.payment_value
)
select
pi.payment_type,pi.payment_value,pi.cost_type,pi.cost_value,
--if last row calculate difference, else use the cost_value
case when pi.row = pt.count then pt.payment_value - pe.current_sum else pi.cost_value end [Payment_by_cost_type]
from payment_value_index_cte pi
inner join payment_value_cte pt on pt.payment_type = pi.payment_type
inner join payment_value_sum_except_last_cte pe on pe.payment_type = pi.payment_type
SELECT payment_Type, payment_value, cost_type, cost_value,
CASE WHEN ROW_NUMBER() OVER (ORDER BY(SELECT 1)) = COUNT(*) OVER (PARTITION BY payment_Type)
THEN SUM(cost_value) OVER (PARTITION BY payment_Type) - payment_value
ELSE cost_value END AS Payment_by_cost_type
FROM dbo.your_table
Demo on SQLFiddle

Database Join Query

I am having a problem with a database join query and I am looking for someone to help me out.
Basically I've got two tables, Invoices and Receipts.
Invoices
Invoice ID
Amount
Date_Added
Receipts
ReceiptID
InvoiceID
Amount
Date_Added
The thing is that I need to produce a table like below, but I have multiple records in Receipts and I am pretty sure that the data is stored in a good way, just not exactly sure what the query would be.
InvoiceID RecieptID Amount Balance Date_Added
1 0 100.00 100.00 01.05.2012
1 1 100.00 0.00 02.05.2012
2 0 250.00 250.00 03.05.2012
3 0 100.00 350.00 04.05.2012
2 2 100.00 250.00 05.05.2012
Does this make sense? So it should be in date order. So effectively I can see line by line what is going on each date.
Setup:
USE tempdb;
GO
CREATE TABLE dbo.Invoices
(
InvoiceID INT,
Amount DECIMAL(12,2),
DateAdded SMALLDATETIME
);
CREATE TABLE dbo.Receipts
(
ReceiptID INT,
InvoiceID INT,
Amount DECIMAL(12,2),
DateAdded SMALLDATETIME
);
SET NOCOUNT ON;
INSERT dbo.Invoices SELECT 1, 100, '20120501'
UNION ALL SELECT 2, 250, '20120503'
UNION ALL SELECT 3, 100, '20120504';
INSERT dbo.Receipts SELECT 1, 1, 100, '20120502'
UNION ALL SELECT 2, 2, 100, '20120505';
Query:
;WITH x AS
(
SELECT InvoiceID, ReceiptID, Amount, DateAdded,
rn = ROW_NUMBER() OVER (ORDER BY DateAdded)
FROM
(
SELECT InvoiceID, ReceiptID = 0, Amount, DateAdded
FROM dbo.Invoices -- where clause?
UNION ALL
SELECT InvoiceID, ReceiptID, Amount, DateAdded
FROM dbo.Receipts -- where clause?
) AS y
),
z AS
(
SELECT xrn = x.rn, x.InvoiceID, x.ReceiptID, x.Amount, x.DateAdded,
PlusMinus = CASE WHEN x.ReceiptID > 0 THEN -x.Amount ELSE x.Amount END
FROM x LEFT OUTER JOIN x AS x2
ON x.rn = x2.rn + 1
)
SELECT InvoiceID, ReceiptID, Balance = (
SELECT SUM(COALESCE(PlusMinus, Amount))
FROM z AS z2
WHERE z2.xrn <= z.xrn
), Amount, DateAdded
FROM z
ORDER BY DateAdded;
Cleanup:
DROP TABLE dbo.Invoices, dbo.Receipts;
you can make a view that select data from the 2 tables like if the DB is oracle :
CREATE VIEW INVOICE_RECPT_VIEW as
select a.InvoiceID, b.RecieptID, b.Amount, b.Date_Added
from invoices a, receipts b
where a.InvoiceID = b.InvoiceID
order by Date_Added