Using Aliases in Where Clause or an Alternative Option? - tsql

How do I get this to work, it works without the Where Clause, otherwise with the Where clause, i get the obvious error, but that's basically what needs to be done, anyone know how to approach this?
select ID,
Name,
case T.N
when 1 then City1
when 2 then City2
when 3 then City3
end as City,
case T.N
when 1 then State1
when 2 then State2
when 3 then State3
end as State
from YourTable
cross join (values(1),(2),(3)) as T(N)
Where City is NOT Null

You can't use the alias in the WHERE clause. Either repeat the expression (messy) or else put your SELECT in a subquery and then put the WHERE clause in the outer query:
SELECT Id, Name, City, State
FROM
(
SELECT
ID,
Name,
CASE T.N
WHEN 1 THEN City1
WHEN 2 THEN City2
WHEN 3 THEN City3
END AS City,
CASE T.N
WHEN 1 THEN State1
WHEN 2 THEN State2
WHEN 3 THEN State3
END AS State
FROM YourTable
CROSS JOIN (VALUES(1),(2),(3)) AS T(N)
) T1
WHERE City IS NOT NULL

You can't use an alias (from SELECT clause) in WHERE clause because the logical processing order(section: Logical Processing Order of the SELECT statement) is WHERE and then SELECT:
FROM
ON
JOIN
WHERE <--
GROUP BY
WITH CUBE or WITH ROLLUP
HAVING
SELECT <--
DISTINCT
ORDER BY <--
TOP
But, you can use an alias in ORDER BY:
SELECT h.SalesOrderID, YEAR(h.OrderDate) OrderYear
FROM Sales.SalesOrderHeader h
ORDER BY OrderYear;
Solutions: see the solutions presented by Mark Byers.
Tibor Karaszi: Why can't we have column alias in ORDER BY?

Related

Postgresql multiple queries or case

Suppose I have a two tables like
Id date1. date2. status. code
1. .... ........ 1. AB110
2. ....... ..... 2. AB001
3. ....... ....... 1. AB120
4. ...... ........ 1. AB111
And table2
Code. Name. Display
AB110 Abc. Y
AB001 Xyx. Y
I want something like this:
withdate. type1. type2. code. name
2 1. 1 AB110. Abc
1. 2. 3 AB001. Xyz
3. . 1. 2 AB120. Lol
1. 1 5 AB111. Zzz
Select code,
table2.name,
count(case when date1 is not null then id) as withdate,
count(case when status=1 then id) as type1,
count(case when status=2 then id) as type2
from table, table2
where table.code=table2.code
group by code, name
Is it rigth to write a query like this ?
Yes it is right to write a query like that. The logic is good, but there is a synthax error in the case. I will prefer you count using the sum aggregate function as below.
select code,
table2.name,
sum(date1 is not null) as withdate,
sum(status=1) as type1,
sum(status=2) as type2
from table, table2
where table.code=table2.code
group by code, name;
This way when date1 is not null 1 is added to the cummated value of the sum else zero is added which is like counting.
Your query looks correct to me, except for the fact you forgot to fully qualify you column names and forgot the end keywords in the case expressions. There are, however, some improvements you could perform.
First, you shouldn't use implicit joins (having more than one table in the from clause) - they have been considered deprecated for quite a few years, and you should use a join clause.
Second, you didn't mention what version you were using, but Postgres 9.4 introduced a filter clause that can save you some of the boiler-plate of those case expressions.
SELECT table1.code,
table2.name,
COUNT(*) FILTER (WHERE date1 IS NOT NULL) AS withdate,
COUNT(*) FILTER (WHERE status = 1) AS type1,
COUNT(*) FILTER (WHERE status = 2) AS type2
FROM table1
JOIN table2 ON table1.code = table2.code
GROUP BY table1.code, table2.name

T-SQL select all IDs that have value A and B

I'm trying to find all IDs in TableA that are mentioned by a set of records in TableB and that set if defined in Table C. I've come so far to the point where a set of INNER JOIN provide me with the following result:
TableA.ID | TableB.Code
-----------------------
1 | A
1 | B
2 | A
3 | B
I want to select only the ID where in this case there is an entry for both A and B, but where the values A and B are based on another Query.
I figured this should be possible with a GROUP BY TableA.ID and HAVING = ALL(Subquery on table C).
But that is returning no values.
Since you did not post your original query, I will assume it is inside a CTE. Assuming this, the query you want is something along these lines:
SELECT ID
FROM cte
WHERE Code IN ('A', 'B')
GROUP BY ID
HAVING COUNT(DISTINCT Code) = 2;
It's an extremely poor question, but you you probably need to compare distinct counts against table C
SELECT a.ID
FROM TableA a
GROUP BY a.ID
HAVING COUNT(DISTINCT a.Code) = (SELECT COUNT(*) FROM TableC)
We're guessing though.

Cascading sum hierarchy using recursive cte

I'm trying to perform recursive cte with postgres but I can't wrap my head around it. In terms of performance issue there are only 50 items in TABLE 1 so this shouldn't be an issue.
TABLE 1 (expense):
id | parent_id | name
------------------------------
1 | null | A
2 | null | B
3 | 1 | C
4 | 1 | D
TABLE 2 (expense_amount):
ref_id | amount
-------------------------------
3 | 500
4 | 200
Expected Result:
id, name, amount
-------------------------------
1 | A | 700
2 | B | 0
3 | C | 500
4 | D | 200
Query
WITH RECURSIVE cte AS (
SELECT
expenses.id,
name,
parent_id,
expense_amount.total
FROM expenses
WHERE expenses.parent_id IS NULL
LEFT JOIN expense_amount ON expense_amount.expense_id = expenses.id
UNION ALL
SELECT
expenses.id,
expenses.name,
expenses.parent_id,
expense_amount.total
FROM cte
JOIN expenses ON expenses.parent_id = cte.id
LEFT JOIN expense_amount ON expense_amount.expense_id = expenses.id
)
SELECT
id,
SUM(amount)
FROM cte
GROUP BY 1
ORDER BY 1
Results
id | sum
--------------------
1 | null
2 | null
3 | 500
4 | 200
You can do a conditional sum() for only the root row:
with recursive tree as (
select id, parent_id, name, id as root_id
from expense
where parent_id is null
union all
select c.id, c.parent_id, c.name, p.root_id
from expense c
join tree p on c.parent_id = p.id
)
select e.id,
e.name,
e.root_id,
case
when e.id = e.root_id then sum(ea.amount) over (partition by root_id)
else amount
end as amount
from tree e
left join expense_amount ea on e.id = ea.ref_id
order by id;
I prefer doing the recursive part first, then join the related tables to the result of the recursive query, but you could do the join to the expense_amount also inside the CTE.
Online example: http://rextester.com/TGQUX53703
However, the above only aggregates on the top-level parent, not for any intermediate non-leaf rows.
If you want to see intermediate aggregates as well, this gets a bit more complicated (and is probably not very scalable for large results, but you said your tables aren't that big)
with recursive tree as (
select id, parent_id, name, 1 as level, concat('/', id) as path, null::numeric as amount
from expense
where parent_id is null
union all
select c.id, c.parent_id, c.name, p.level + 1, concat(p.path, '/', c.id), ea.amount
from expense c
join tree p on c.parent_id = p.id
left join expense_amount ea on ea.ref_id = c.id
)
select e.id,
lpad(' ', (e.level - 1) * 2, ' ')||e.name as name,
e.amount as element_amount,
(select sum(amount)
from tree t
where t.path like e.path||'%') as sub_tree_amount,
e.path
from tree e
order by path;
Online example: http://rextester.com/MCE96740
The query builds up a path of all IDs belonging to a (sub)tree and then uses a scalar sub-select to get all child rows belonging to a node. That sub-select is what will make this quite slow as soon as the result of the recursive query can't be kept in memory.
I used the level column to create a "visual" display of the tree structure - this helps me debugging the statement and understanding the result better. If you need the real name of an element in your program you would obviously only use e.name instead of pre-pending it with blanks.
I could not get your query to work for some reason. Here's my attempt that works for the particular table you provided (parent-child, no grandchild) without recursion. SQL Fiddle
--- step 1: get parent-child data together
with parent_child as(
select t.*, amount
from
(select e.id, f.name as name,
coalesce(f.name, e.name) as pname
from expense e
left join expense f
on e.parent_id = f.id) t
left join expense_amount ea
on ea.ref_id = t.id
)
--- final step is to group by id, name
select id, pname, sum(amount)
from
(-- step 2: group by parent name and find corresponding amount
-- returns A, B
select e.id, t.pname, t.amount
from expense e
join (select pname, sum(amount) as amount
from parent_child
group by 1) t
on t.pname = e.name
-- step 3: to get C, D we union and get corresponding columns
-- results in all rows and corresponding value
union
select id, name, amount
from expense e
left join expense_amount ea
on e.id = ea.ref_id
) t
group by 1, 2
order by 1;

Postgress by a CASE with DISTINCT in select

I have a query like below getting the error - 'SELECT DISTINCT, ORDER BY expressions must appear in select list'
select distinct name
from fruits
order by case
when name = 'mango' then 1
else 2
end
This results 4 records, say
apple, mango, pear and grape
How can I make sure I get Mango as the first record always and the rest follow. I tried using the case statement, but not able to get the desired results. Any ideas will be appreciated.
I believe this should accomplish what you describe as needing.
select distinct
name,
case name when 'Mango' then 1 else 2 end as fruitOrder
from fruits
order by
fruitOrder
If you need to always have 'mango' in first position, no matter the other rows, this could be a way:
with fruits(name) as (
select 'apple' from dual union all
select 'mango' from dual union all
select 'pear' from dual union all
select 'grape' from dual
)
select name
from fruits
order by case
when name = 'mango' then 1
else 2
end
If you need to add a DISTINCT, this should work:
select distinct name,
case
when name = 'mango' then 1
else 2
end orderCol
from fruits
order by orderCol
This will give you 'Mango' followed by the others in order;
WITH get_rows AS
(SELECT DISTINCT item_type
FROM the_item)
SELECT item_type
FROM
(SELECT 1 as seq, item_type
FROM get_rows
WHERE item_type = 'Mango'
UNION ALL
SELECT 2 as seq, item_type
FROM get_rows
WHERE item_type <> 'Mango')
ORDER BY seq, item_type

Using Derived Tables and CTEs to Display Details?

I am teaching myself T-SQL and am struggling to comprehend the following example..
Suppose you want to display several nonaggregated columns along with
some aggregate expressions that apply to the entire result set or to a
larger grouping level. For example, you may need to display several
columns from the Sales.SalesOrderHeader table and calculate the
percent of the TotalDue for each sale compared to the TotalDue for all
the customer’s sales. If you group by CustomerID, you can’t include
other nonaggregated columns from Sales.SalesOrderHeader unless you
group by those columns. To get around this, you can use a derived
table or a CTE.
Here are two examples given...
SELECT c.CustomerID, SalesOrderID, TotalDue, AvgOfTotalDue,
TotalDue/SumOfTotalDue * 100 AS SalePercent
FROM Sales.SalesOrderHeader AS soh
INNER JOIN
(SELECT CustomerID, SUM(TotalDue) AS SumOfTotalDue,
AVG(TotalDue) AS AvgOfTotalDue
FROM Sales.SalesOrderHeader
GROUP BY CustomerID) AS c ON soh.CustomerID = c.CustomerID
ORDER BY c.CustomerID;
WITH c AS
(SELECT CustomerID, SUM(TotalDue) AS SumOfTotalDue,
AVG(TotalDue) AS AvgOfTotalDue
FROM Sales.SalesOrderHeader
GROUP BY CustomerID)
SELECT c.CustomerID, SalesOrderID, TotalDue,AvgOfTotalDue,
TotalDue/SumOfTotalDue * 100 AS SalePercent
FROM Sales.SalesOrderHeader AS soh
INNER JOIN c ON soh.CustomerID = c.CustomerID
ORDER BY c.CustomerID;
Why doesn't this query produce the same result..
SELECT CustomerID, SalesOrderID, TotalDue, AVG(TotalDue) AS AvgOfTotalDue,
TotalDue/SUM(TotalDue) * 100 AS SalePercent
FROM Sales.SalesOrderHeader
GROUP BY CustomerID, SalesOrderID, TotalDue
ORDER BY CustomerID
I'm looking for someone to explain the above examples in another way or step through it logically so I can understand how they work?
The aggregates in this statement (i.e. SUM and AVG) don't do anything:
SELECT CustomerID, SalesOrderID, TotalDue, AVG(TotalDue) AS AvgOfTotalDue,
TotalDue/SUM(TotalDue) * 100 AS SalePercent
FROM Sales.SalesOrderHeader
GROUP BY CustomerID, SalesOrderID, TotalDue
ORDER BY CustomerID
The reason for this is you're grouping by TotalDue, so all records in the same group have the same value for this field. In the case of AVG this means you're guarenteed for AvgOfTotalDue to always equal TotalDue. For SUM it's possible you'd get a different result, but as you're also grouping by SalesOrderID (which I'd imagine is unique in the SalesOrderHeader table) you will only have one record per group, so again this will always equal the TotalDue value.
With the CTE example you're only grouping by CustomerId; as a customer may have many sales orders associated with it, these aggregate values will be different to the TotalDue.
EDIT
Explanation of the aggregate of field included in group by:
When you group by a value, all rows with that same value are collected together and aggregate functions are performed over them. Say you had 5 rows with a total due of 1 and 3 with a total due of 2 you'd get two result lines; one with the 1s and one with the 2s. Now if you perform a sum on these you have 3*1 and 2*2. Now divide by the number of rows in that result line (to get the average) and you have 3*1/3 and 2*2/2; so things cancel out leaving you with 1 and 2.
select totalDue, avg(totalDue)
from (
select 1 totalDue
union all select 1 totalDue
union all select 1 totalDue
union all select 2 totalDue
union all select 2 totalDue
) x
group by totalDue
select uniqueId, totalDue, avg(totalDue), sum(totalDue)
from (
select 1 uniqueId, 1 totalDue
union all select 2 uniqueId, 1 totalDue
union all select 3 uniqueId, 1 totalDue
union all select 4 uniqueId, 2 totalDue
union all select 5 uniqueId, 2 totalDue
) x
group by uniqueId
Runnable Example: http://sqlfiddle.com/#!2/d41d8/21263