Referencing multiple aliases in a new field [duplicate] - sql

This question already has answers here:
How to reuse calculated columns avoiding duplicating the sql statement
(6 answers)
Possible to store value of one select column and use it for the next one?
(4 answers)
Closed 4 months ago.
I'm calculating 3 new fields where calculation #2 is dependent on calculation #1 and calculation #2 is dependent on calculation #3.
I'd like to alias these calculations to create a cleaner solution, but I'm not sure how to reference more than one alias. If I just had 2 calculations, I know I could create a subquery and reference my alias in the upper level. However, I'm not sure how to do this with 3 calculations. Would I join subqueries?
Reprex below (current code will encounter an error when attempting to reference an alias within an alias.)
DECLARE #myTable AS TABLE([state] VARCHAR(20), [season] VARCHAR(20), [rain] int, [snow] int, [ice] int)
INSERT INTO #myTable VALUES ('AL', 'summer', 1, 1, 1)
INSERT INTO #myTable VALUES ('AK', 'summer', 3, 3, 1)
INSERT INTO #myTable VALUES ('AZ', 'summer', 0, 1, 1)
INSERT INTO #myTable VALUES ('AL', 'winter', 5, 4, 2)
INSERT INTO #myTable VALUES ('AK', 'winter', 2, 2, 2)
INSERT INTO #myTable VALUES ('AZ', 'winter', 1, 1, 2)
INSERT INTO #myTable VALUES ('AL', 'summer', 6, 4, 3)
INSERT INTO #myTable VALUES ('AK', 'summer', 3, 0, 3)
INSERT INTO #myTable VALUES ('AZ', 'summer', 5, 1, 3)
select *,
ice + snow as cold_precipitation,
rain as warm_precipitation,
cold_precipitation + warm_precipitation as overall_precipitation,
cold_precipitation / sum(overall_precipitation) as cold_pct_of_total,
warm_precipitation / sum(overall_precipitation) as warm_pct_of_total
from #myTable

You can use CROSS APPLY(s) to stack expressions and reference aliases. However, you have an aggregate sum() without a GROUP BY, so your desired results is not clear.
I did change your sum() to a window function sum() over(partition by state,season)
Example
select A.*,
cold_precipitation,
warm_precipitation,
overall_precipitation,
cold_precipitation / sum(overall_precipitation+0.0) over(partition by state,season) as cold_pct_of_total,
warm_precipitation / sum(overall_precipitation+0.0) over(partition by state,season) as warm_pct_of_total
from #myTable A
Cross Apply ( values ( ice + snow , rain ) ) B(cold_precipitation,warm_precipitation)
Cross Apply ( values ( cold_precipitation+warm_precipitation )) C(overall_precipitation)
Results

Related

SQL Pivot Columns with prefixes

New to SQL, struggling to fully understand the pivot clause. I have four fields (state, season, rain, snow) and am trying to pivot so that I have 5 fields (state, summer_rain, summer_snow, winter_rain, winter_snow). I'm not sure how to pivot two fields so that they are prefixed with another if that makes sense. Reprex below.
What I have now
What I'm after
My code (receiving an error when aggregating snow & rain within pivot clause):
DECLARE #myTable AS TABLE([state] VARCHAR(20), [season] VARCHAR(20), [rain] int, [snow] int)
INSERT INTO #myTable VALUES ('AL', 'summer', 1, 1)
INSERT INTO #myTable VALUES ('AK', 'summer', 3, 3)
INSERT INTO #myTable VALUES ('AZ', 'summer', 0, 1)
INSERT INTO #myTable VALUES ('AL', 'winter', 5, 4)
INSERT INTO #myTable VALUES ('AK', 'winter', 2, 2)
INSERT INTO #myTable VALUES ('AZ', 'winter', 1, 1)
INSERT INTO #myTable VALUES ('AL', 'summer', 6, 4)
INSERT INTO #myTable VALUES ('AK', 'summer', 3, 0)
INSERT INTO #myTable VALUES ('AZ', 'summer', 5, 1)
SELECT [state], [year], [month], [day]
FROM
(
SELECT * FROM #myTable
) t
PIVOT
(
sum([rain]), sum([snow]) FOR [season] IN ([summer], [winter])
) AS pvt
PIVOTS are great, but Conditional Aggregations offer a bit more flexibility and often more performant.
PIVOT
Select *
From (
SELECT State
,B.*
FROM #myTable
Cross Apply (values (concat(season,'_rain'),rain)
,(concat(season,'_snow'),snow)
) B(Item,Value)
) src
Pivot ( sum(value) for Item in ([summer_rain],[summer_snow],[winter_rain],[winter_snow]) ) pvt
Conditional Aggregation
Select State
,[summer_rain] = sum(case when season='summer' then rain end)
,[summer_snow] = sum(case when season='summer' then snow end)
,[winter_rain] = sum(case when season='winter' then rain end)
,[winter_snow] = sum(case when season='winter' then snow end)
From #myTable
Group By State

SQL Filter rows based on multiple distinct values of a column

Given the following table
DECLARE #YourTable TABLE (id int, PLU int, Siteid int, description varchar(50))
INSERT #YourTable VALUES (1, 8972, 2, 'Beer')
INSERT #YourTable VALUES (2, 8972, 3, 'cider')
INSERT #YourTable VALUES (3, 8972, 4, 'Beer')
INSERT #YourTable VALUES (4, 8973, 2, 'Vodka')
INSERT #YourTable VALUES (5, 8973, 3, 'Vodka')
INSERT #YourTable VALUES (6, 8973, 4, 'Vodka')
I trying to write a query that would give me all rows that have multiple distinct values for a given description value against a plu.
So in the example above I would want to return rows 1,2,3 as they have both a 'cider' value and a 'beer' value for a plu of '8972'.
I thought 'GROUP BY' and 'HAVING' was the way to go but I can't seem to get it to work correctly.
SELECT P.PLU, P.Description
FROM #YourTable P
GROUP BY P.PLU, P.Description
HAVING COUNT(DISTINCT(P.DESCRIPTION)) > 1
Any help appreciated.
You shouldn't GROUP BY the description if you are doing a DISTINCT COUNT on it (then it will always be just 1). Try something like this:
SELECT P2.PLU, P2.Description
FROM #YourTable P2
WHERE P2.PLU in (
SELECT P.PLU
FROM #YourTable P
GROUP BY P.PLU
HAVING COUNT(DISTINCT(P.DESCRIPTION)) > 1
)

GROUP BY with HAVING clause does not return a row?

Strange problem with a GROUP BY and HAVING clause on SQL Server 2014.
It's distinctly possible I've done something wrong, but I cannot make sense of this.
Here's the setup data:
create table #accounts(accountid int)
create table #data(accountid int, categoryid int, asofdate date, datavalue numeric(20, 10))
insert into #accounts
values
(1),(2),(3)
insert into #data
values
(1, 10, '1/31/2015', 0),
(1, 10, '2/28/2015', 10),
(1, 10, '3/31/2015', 20),
(2, 10, '1/31/2015', 0),
(2, 10, '2/28/2015', 15),
(2, 10, '3/31/2015', 25),
(3, 10, '1/31/2015', 0),
(3, 10, '2/28/2015', 7),
(3, 10, '3/31/2015', 12)
This returns a single row... for 1/31/2015. The only date with a total of zero
select categoryid, asofdate, sum(datavalue) as totalvalue
from #accounts a
inner join #data d
on d.accountid = a.accountid
group by d.categoryid, d.asofdate
having sum(datavalue) = 0
Result is 10, '1/31/2015', 0
Yet, somehow the following does not return any rows... I'm basically asking it to give me the Max date from the first query. Why does this balk?
select categoryid, max(asofdate) as MaxAsOfDate, sum(datavalue) as totalvalue
from #accounts a
inner join #data d
on d.accountid = a.accountid
group by d.categoryid
having sum(datavalue) = 0
drop table #accounts
drop table #data
Yet it returns no rows... baffled.
SQL Fiddle here:
http://sqlfiddle.com/#!6/5e44cb/1
Simply, because you are Grouping by d.categoryid - And there is no grouping by this column, which would return 0 for sum(datavalue):
http://sqlfiddle.com/#!6/5e44cb/4 (it's 89)
If you could add your expected output to your post, we might be able to help you with the query you need.
By removing the GROUP BY asOfDate, you've removed any groups that have SUM()=0.
You should make your first query a derived table or CTE, and then do the MAX outside of it to get your desired results.

SQL Query by using with

I have the following query...
------ create table
create table test222
(
sid bigint,
scode nvarchar(50),
parentid bigint,
sname nvarchar(50)
)
insert into test222 values (1, '11', 0, 'iam a boy')
insert into test222 values (2, '111', 1, 'boy')
insert into test222 values (3, '1111', 1, 'bo')
insert into test222 values (4, '11111', 3, 'girl')
insert into test222 values (5, '111111', 0, 'boyy')
insert into test222 values (6, '1111111', 5, 'gril')
insert into test222 values (7, '22', 0, 'body')
insert into test222 values (8, '222', 7, 'girll')
following is my code,,,
;WITH SInfo AS
(
SELECT
t.sId,
t.scode,
t.ParentId,
t.sName,
CONVERT(nvarchar(800), t.scode) AS Hierarchy,
t.ParentId as HParentId
FROM test222 as t
WHERE
t.sname like '%bo%'
UNION ALL
SELECT
si.sId,
si.scode,
si.ParentId,
si.sName,
CONVERT(nvarchar(800), TH.scode + '\' + si.Hierarchy),
th.parentid
FROM SInfo as si
INNER JOIN test222 TH
ON TH.sId = si.HParentId
)
Select t.sId, t.scode, t.ParentId, t.sName, t.Hierarchy
from SInfo as t
where
HParentId = 0 and
not exists (select 1 from SInfo as s
where
s.sid <> t.sid and
s.Hierarchy like t.Hierarchy + '%')
the op generated is shown below
5 111111 0 boyy 111111
7 22 0 body 22
3 1111 1 bo 11\1111
the third row is not correct
It should be
3 111111 1 bo 11\111\1111.
How can i do that???
All you need to do is change the parent id of the record - sid=3 to its chronological parent instead of its grand parent :-) . Check below.
Change
insert into #test222 values (3, '1111', 1, 'bo')
to
insert into #test222 values (3, '1111', 2, 'bo')
The reason you are not seeing the middle portion (record - sid:2) is because record - sid=2 and record - sid=3 essentially share the same "FROM" criteria. sname= '%bo%' (or rather LIKE '%bo%') and parentid=1. The record - sid=3 is the last record in the set with this shared "FROM" criteria and hence is the record being returned.
Order of SQL query execution (FROM, WHERE, GROUP BY, HAVING, SELECT, ORDER BY)
Here is a link to recursive CTE queries
Hope this helps.

SQL recursive logic

I have a situation where I need to configure existing client data to address a problem where our application was not correctly updating IDs in a table when it should have been.
Here's the scenario. We have a parent table, where rows can be inserted that effectively replace existing rows; the replacement can be recursive. We also have a child table, which has a field that points to the parent table. In existing data, the child table could be pointing at rows that have been replaced, and I need to correct that. I can't simply update each row to the replacing row, however, because that row could have been replaced as well, and I need the latest row to be reflected.
I was trying to find a way to write a CTE that would accomplish this for me, but I'm struggling to find a query that finds what I'm actually looking for. Here's a sample of the tables that I'm working with; the 'ShouldBe' column is what I'd like my update query to end up with, taking into account the recursive replacement of some of the rows.
DECLARE #parent TABLE (SampleID int,
SampleIDReplace int,
GroupID char(1))
INSERT INTO #parent (SampleID, SampleIDReplace, GroupID)
VALUES (1, -1, 'A'), (2, 1, 'A'), (3, -1, 'A'),
(4, -1, 'A'), (5, 4, 'A'), (6, 5, 'A'),
(7, -1, 'B'), (8, 7, 'B'), (9, 8, 'B')
DECLARE #child TABLE (ChildID int, ParentID int)
INSERT INTO #child (ChildID, ParentID)
VALUES (1, 4), (2, 7), (3, 1), (4, 3)
Desired results in child table, after the update script has been applied:
ChildID ParentID ParentID_ShouldBe
1 4 6 (4 replaced by 5, 5 replaced by 6)
2 7 9 (7 replaced by 8, 8 replaced by 9)
3 1 2 (1 replaced by 2)
4 3 3 (unchanged, never replaced)
The following returns what you are looking for:
with cte as (
select sampleid, sampleidreplace, 1 as num
from #parent
where sampleidreplace <> -1
union all
select p.sampleid, cte.sampleidreplace, cte.num+1
from #parent p join
cte
on p.sampleidreplace = cte.sampleId
)
select c.*, coalesce(p.sampleid, c.parentid)
from #child c left outer join
(select ROW_NUMBER() over (partition by sampleidreplace order by num desc) as seqnum, *
from cte
) p
on c.ParentID = p.SampleIDReplace and p.seqnum = 1
The recursive part keeps track of every correspondence (4-->5, 4-->6). The addition number is a "generation" count. We actually want the last generation. This is identified by using the row_number() function, ordering by the num in decreasing order -- hence the p.seqnum = 1.
Ok, so it took me a while and there are probably better ways to do it, but here is one option.
DECLARE #parent TABLE (SampleID int,
SampleIDReplace int,
GroupID char(1))
INSERT INTO #parent (SampleID, SampleIDReplace, GroupID)
VALUES (1, -1, 'A'), (2, 1, 'A'), (3, -1, 'A'),
(4, -1, 'A'), (5, 4, 'A'), (6, 5, 'A'),
(7, -1, 'B'), (8, 7, 'B'), (9, 8, 'B')
DECLARE #child TABLE (ChildID int, ParentID int)
INSERT INTO #child (ChildID, ParentID)
VALUES (1, 4), (2, 7), (3, 1), (4, 3)
;WITH RecursiveParent1 AS
(
SELECT SampleIDReplace, SampleID, 1 RecursionLevel
FROM #parent
WHERE SampleIDReplace != -1
UNION ALL
SELECT A.SampleIDReplace, B.SampleID, RecursionLevel + 1
FROM RecursiveParent1 A
INNER JOIN #parent B
ON A.SampleId = B.SampleIDReplace
),RecursiveParent2 AS
(
SELECT *,
ROW_NUMBER() OVER(PARTITION BY SampleIdReplace ORDER BY RecursionLevel DESC) RN
FROM RecursiveParent1
)
SELECT A.ChildID, ISNULL(B.ParentID,A.ParentID) ParentID
FROM #child A
LEFT JOIN ( SELECT SampleIDReplace, SampleID ParentID
FROM RecursiveParent2
WHERE RN = 1) B
ON A.ParentID = B.SampleIDReplace
OPTION(MAXRECURSION 500)
I've got a iterative SQL loop that I think sorts this out as follows:
WHILE EXISTS (SELECT * FROM #child C INNER JOIN #parent P ON C.ParentID = P.SampleIDReplace WHERE P.SampleIDReplace > -1)
BEGIN
UPDATE #child
SET ParentID = SampleID
FROM #parent
WHERE #child.ParentID = SampleIDReplace
END
Basically, the while condition compares the contents of the parent ID column in the child table and sees if there is a matching value in the SampleIDReplace column of the parent table. If there is, it goes and gets the SampleID of that record. It only stops when the join results in every SampleIDReplace being -1, meaning we have nothing else to do.
On your sample data, the above results in the expected output.
Note that I had to use temp tables rather than table variables here in order for the table to be accessible within the loop. If you have to use table variables then there would need to be a bit more surgery done.
Clearly if you have deep replacement hierarchies then you'll do quite a few updates, which may be a consideration when looking to perform the query against a production database.