Need two columns in one row in SQL - sql

Given the values below I need records with the same ID to be on the same line ONLY if either the Paid or Interest values are 0 and the other is not. In this case ID= 1 would be on one line, but the others would remain the same.
declare #t table(id int, paid varchar(20), interest varchar (20))
insert into #t values(1, '0.00', '3.51'),
(1, '1000', '0.00'),
(3, '2.50', '0.00'),
(4, '50.00', '2.20'),
(4, '75.00', '0.10')
select * from #t
I need a result like this:
ID Paid Interest
1 1000 3.51
3 2.50 0.00
4 50.00 2.20
4 75.00 0.00
I tried creating something using a windows function but couldn't come close. Anyone have any ideas?

Hmmm . . . this approach splits the columns apart and then re-combines them.
Try this:
select coalesce(ti.id, tp.id), tp.paid, ti.interest
from (select t.*,
row_number() over (partition by paid) as seqnum
from t
where paid <> 0
) tp full join
(select t.*,
row_number() over (partition by interest) as seqnum
from t
where interest <> 0
) ti
on tp.id = ti.id and tp.seqnum = ti.seqnum;

Related

Can I SELECT the AVG of 'x' values from two tables but only select the values where the 'y' values match? (INNER JOIN)

I'm trying to get the average of two values which are in two different tables. I only want to get the average of the values where the in the same column the 'Week' Values of both tables are the same.
So e.g.:
Table1 Name= BicepsTable
Week | Biceps
1 | 33
2 | 33.2
3 | 34.1
.
Table2 Name=ThighTable
Week | Thigh
1 | 42.1
3 | 42.8
4 | 43
.
From these tables I want to have the values {(1, 37.55), (3, 38.45)}.
( . (33+42)/2=37.55 . . . . (34.1+42.8)/2=38.45 . )
I tried to get this with the following code but the following code gives me {(1, 37.55), (3, 37.55)} where the second value is wrong, the second average value should be the one of the next column.
sql = 'SELECT BicepsTable.Week,
((SELECT BicepsTable.Biceps FROM BicepsTable INNER JOIN ThighTable ON BicepsTable.Week = ThighTable.Week)
+
(SELECT ThighTable.Thigh FROM ThighTable INNER JOIN BicepsTable ON ThighTable.Week = BicepsTable.Week)) /2
FROM BicepsTable INNER JOIN ThighTable ON BicepsTable.Week = ThighTable.Week'
Please help, if you don't understand my problem, or got questions, feel free to ask:)
I suggest taking a union of the two tables, and then taking the average of each week:
SELECT Week, AVG(rating) AS avg_rating
FROM
(
SELECT Week, Biceps AS rating FROM BicepsTable
UNION ALL
SELECT Week, Thigh FROM ThighTable
) t
GROUP BY Week
HAVING COUNT(*) = 2
ORDER BY Week;
Aggregation, as used above, is a good option here, because the AVG will only operate on the values which are present. So, if only one or the other table has a value, then the average will reflect that.
declare #test1 as table
(id int,t1value float)
declare #test2 as table
(id int,t2value float)
insert into #test1
values(1,100),(2,150),(3,200)
insert into #test2
values(1,100),(3,150),(5,200)
select *,(a.t1value+b.t2value)/2 taverage from #test1 a
inner join #test2 b on a.[id]=b.[id]
group by a.id,a.t1value,b.id,b.t2value

SQL window function to remove multiple values with different criteria

I have a data set where I'm trying to remove records with the following conditions:
If a practid has multiple records with the same date and at least one record has a reason of "L&B" then I want all the practid's for that date to be removed.
DECLARE t table(practid int, statusdate date, reason varchar(100)
INSERT INTO t VALUES (1, '2018-03-01', 'L&B'),
(1, '2018-03-01', 'NULL'),
(1, '2018-04-01, 'R&D'),
(2, '2018-05-01, 'R&D'),
(2, '2018-05-01, 'R&D'),
(2, '2018-03-15', NULL),
(2, '2018-03-15', 'R&D),
(3, '2018-07-01, 'L&B)
With this data set I would want the following result:
PractId StatusDate Reason
1 2018-04-01 R&D
2 2018-05-01 R&D
2 2018-05-01 R&D
2 2018-03-15 NULL
2 2018-03-15 R&D
I tried solving this with a window function but am getting stuck:
SELECT *, ROW_NUMBER() OVER
(PARTITION BY practid, statusdate, CASE WHEN reason = 'L&B' THEN 0 ELSE 1 END) AS rn
FROM table
From my query I can't figure out how to keep Practid = 2 since I would want to keep all the records.
To continue along your current approach, we can use COUNT as an analytic function. We can count the occurrences of the L&B reason over each practid/statusdate window, and then retain only groups where this reason never occurs.
SELECT practid, statusdate, reason
FROM
(
SELECT *,
COUNT(CASE WHEN reason = 'L&B' THEN 1 END) OVER
(PARTITION BY practid, statusdate) cnt
FROM yourTable
) t
WHERE cnt = 0;
Demo
You can try to use not exists with a subquery.
Select *
from t t1
where not exists (
select 1
from t tt
where tt.reason = 'L&B' and t1.statusdate = tt.statusdate
)
sqlfiddle

Product Final Price after Many Discount given

I have two tables.
One table of Ids and their prices, and second table of discounts per Id.
In the table of discounts an Id can has many Discounts, and I need to know the final price of an Id.
What is the Best way to query it (in one query) ?
The query should be generic for many discounts per id (not only 2 as mentioned below in the example)
For example
Table one
id price
1 2.00
2 2.00
3 2.00
Table two
id Discount
1 0.20
1 0.30
2 0.40
3 0.50
3 0.60
Final result:
id OrigPrice PriceAfterDiscount
1 2.00 1.12
2 2.00 1.20
3 2.00 0.40
Here's another way to do it:
SELECT T1.ID, T1.Price, T1.Price * EXP(SUM(LOG(1 - T2.Discount)))
FROM T1 INNER JOIN T2 ON T1.ID = T2.ID
GROUP BY T1.ID, T1.Price
The EXP/LOG trick is just another way to do multiplication.
If you have entries in T1 without discounts in T2, you could change the INNER JOIN to a LEFT JOIN. You would end up with the following:
ID Price Discount
4 2.00 NULL
Your logic can either account for the null in the discounted price column and take the original price instead, or just add a 0 discount record for those.
Generally it can be done with a trick with LOG/EXP functions but it is complex.
Here is a basic example:
declare #p table(id int, price money)
declare #d table(id int, discount money)
insert into #p values
(1, 2),
(2, 2),
(3, 2)
insert into #d values
(1, 0.2),
(1, 0.3),
(2, 0.4),
(3, 0.5),
(3, 0.6)
select p.id,
p.price,
p.price * ca.discount as PriceAfterDiscount
from #p p
cross apply (select EXP(SUM(LOG(1 - discount))) as discount FROM #d where id = p.id) ca
For simpler(cursor based approach) you will need a recursive CTE, but in this case you need some unique ordering column in Discounts table to run it correctly. This is shown in #Tanner`s answer.
And finally you can approach this with a regular cursor
I believe this produces the desired results using a CTE to iterate through the discounts. The solution below is re-runnable in isolation.
Edited: to include data that might not have any discounts applied in the output with a left join in the first part of the CTE.
CREATE TABLE #price
(
id INT,
price DECIMAL(5, 2)
);
CREATE TABLE #discount
(
id INT,
discount DECIMAL(5, 2)
);
INSERT INTO #price
(
id,
price
)
VALUES
(1, 2.00),
(2, 2.00),
(3, 2.00),
(4, 3.50); -- no discount on this item
INSERT INTO #discount
(
id,
discount
)
VALUES
(1, 0.20),
(1, 0.30),
(2, 0.40),
(3, 0.50),
(3, 0.60);
-- new temporary table to add a row number to discounts so we can iterate through them
SELECT d.id,
d.discount,
ROW_NUMBER() OVER (PARTITION BY id ORDER BY d.discount) rn
INTO #GroupedDiscount
FROM #discount AS d;
-- note left join in first part of cte to get prices that aren't discounted included
WITH cte
AS (SELECT p.id,
p.price,
CASE
WHEN gd.discount IS NULL THEN
p.price
ELSE
CAST(p.price * (1.0 - gd.discount) AS DECIMAL(5, 2))
END AS discountedPrice,
gd.rn
FROM #price AS p
LEFT JOIN #GroupedDiscount AS gd
ON gd.id = p.id
AND gd.rn = 1
UNION ALL
SELECT cte.id,
cte.price,
CAST(cte.discountedPrice * (1.0 - gd.discount) AS DECIMAL(5, 2)) AS discountedPrice,
cte.rn + 1 AS rn
FROM cte
INNER JOIN #GroupedDiscount AS gd
ON gd.id = cte.id
AND gd.rn = cte.rn + 1
)
SELECT cte.id,
cte.price,
MIN(cte.discountedPrice) AS discountedPrice
FROM cte
GROUP BY id,
cte.price;
DROP TABLE #price;
DROP TABLE #discount;
DROP TABLE #GroupedDiscount;
Results:
id price discountedPrice
1 2.00 1.12
2 2.00 1.20
3 2.00 0.40
4 3.50 3.50 -- no discount
As others have said, EXP(SUM(LOG())) is the way to do the calculation. Here is basically same approach from yet another angle:
WITH CTE_Discount AS
(
SELECT Id, EXP(SUM(LOG(1-Discount))) as TotalDiscount
FROM TableTwo
GROUP BY id
)
SELECT t1.id, CAST(Price * COALESCE(TotalDiscount,1) AS Decimal(18,2)) as FinalPRice
FROM TableOne t1
LEFT JOIN CTE_Discount d ON t1.id = d.id
SQLFIddle Demo

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 increment in a select query

I've got a query I'm working on and I want to increment one of the fields and restart the counter when a key value is different.
I know this code doesn't work. Programmatically this is what I want...
declare #counter int, #id
set #counter = 0
set #id = 0
select distinct
id,
counter = when id = #id
then #counter += 1
else #id = id
#counter = 1
...with the end result looking something like this:
ID Counter
3 1
3 2
3 3
3 4
6 1
6 2
6 3
7 1
And yes, I am stuck with SQL2k. Otherwise that row_number() would work.
Assuming a table:
CREATE TABLE [SomeTable] (
[id] INTEGER,
[order] INTEGER,
PRIMARY KEY ([id], [order])
);
One way to get this in Microsoft SQL Server 2000 is to use a subquery to count the rows with the same id and a lower ordering.
SELECT *, (SELECT COUNT(*) FROM [SomeTable] counter
WHERE t.id = counter.id AND t.order < counter.order) AS row_num
FROM [SomeTable] t
Tip: It's 2010. Soon your SQL Server will be old enough to drive.
If you use SQL Server 2005 or later, you get wonderful new functions like ROW_NUMBER() OVER (PARTITION...).
Yes you want ROW_NUMBER().
I would try:
SELECT id, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY ID) AS Counter
One way to do this is to throw the data into a temp table with an identity column that is used as a row number. Then make the counter column a count of the other rows with the same Id and a lower row number + 1.
CREATE TABLE #MyData(
Id INT
);
INSERT INTO #MyData VALUES(3);
INSERT INTO #MyData VALUES(3);
INSERT INTO #MyData VALUES(3);
INSERT INTO #MyData VALUES(3);
INSERT INTO #MyData VALUES(6);
INSERT INTO #MyData VALUES(6);
INSERT INTO #MyData VALUES(6);
INSERT INTO #MyData VALUES(7);
CREATE TABLE #MyTempTable(
RowNum INT IDENTITY(1,1),
Id INT,
Counter INT
);
INSERT INTO #MyTempTable
SELECT Id, 0
FROM #MyData
ORDER BY Id;
SELECT Id, (SELECT COUNT(*) + 1 FROM #MyTempTable WHERE Id = t1.Id AND RowNum < t1.RowNum) AS 'Counter'
FROM #MyTempTable t1;
You should get the following output based on your example:
Id Counter
3 1
3 2
3 3
3 4
6 1
6 2
6 3
7 1
Having row_number() means you have to deal with far, far fewer correlated subqueries. #Bill Karwin's solution works (+1); here's another version that does the same thing but that might be a bit easier to follow. (I used datetimes to determine ordering.)
-- Test table
CREATE TABLE Test
( Id int not null
,Loaded datetime not null
)
-- Load dummy data with made-up distinct datetimes
INSERT Test values (3, 'Jan 1, 2010')
INSERT Test values (3, 'Jan 2, 2010')
INSERT Test values (3, 'Jan 5, 2010')
INSERT Test values (3, 'Jan 7, 2010')
INSERT Test values (6, 'Feb 1, 2010')
INSERT Test values (6, 'Feb 11, 2010')
INSERT Test values (7, 'Mar 31, 2010')
-- The query
SELECT t1.Id, count(*) Counter
from Test t1
inner join Test t2
on t2.Id = t1.Id
and t2.Loaded <= t1.Loaded
group by t1.Id, t1.Loaded
-- Clean up when done
DROP TABLE Test
It is important to note that, without good indexes (and perhaps even with them), these kinds of queries can perform very poorly, particularly on large tables. Check and optimize carefully!
For MySql, I was able to make it with this query.
SELECT (SELECT COUNT(id) +1 FROM sku s WHERE t.item_id = s.item AND s.id < t.sku_id) AS rowNumber, t.*
FROM
(select item.Name as itemName ,item.id as item_id , sku.Name as skuName ,sku.id as sku_id from item
INNER JOIN sku ON item.id = sku.item
WHERE item.active = 'Y'
) t
1 Roasted Pistachios (Salted, In Shell) 84 1 Pound Bags 84
3 Roasted Pistachios (Salted, In Shell) 84 25 Pound Cases 1174
5 Roasted Pistachios (Salted, In Shell) 84 12 x 2.6 Ounce Bags 5807
2 Roasted Pistachios (Salted, In Shell) 84 5 Pound Bags 814
4 Roasted Pistachios (Salted, In Shell) 84 Samples 4724
6 Roasted Pistachios (Salted, In Shell) 84 12 x 3.2 Ounce Bags 18145
4 Star Fruit 981 5 Pound Bags 17462
1 Star Fruit 981 1 Pound Bags 2125
3 Star Fruit 981 11 Pound Bags 2226
2 Star Fruit 981 44 Pound Cases 2156