Mssql err: Column 'dbo.History.Email' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause - tsql

when executing this MSSQL statement it forces me to add the CASE WHEN field History.Email in the group by field, however I need to group only on a date field.
Any idea how this can be solved? (sql 2008)
SELECT TOP (100)
convert(varchar, VisitDate, 111) as Date,
SUM(AmountLoaded) AS Loaded,
CASE when History.Email is null then
SUM(AmountCash)
else SUM(AmountToPay)
end AS Total,
CASE when History.Email is null then
0
else SUM(AmountToPay)
end AS App,
SUM(AmountToPay) AS Consumed
FROM dbo.History
GROUP BY convert(varchar, VisitDate, 111)
order by 1 desc

When performing conditional aggregation you need to put the entire CASE expression in an aggregate, not just the expression(s) you want to aggregate:
SELECT TOP (100)
CONVERT(varchar(10), H.VisitDate, 111) AS [Date], --Always specify a length/precision/scale for your data types
SUM(H.AmountLoaded) AS Loaded,
SUM(CASE WHEN H.Email IS NULL THEN H.AmountCash ELSE H.AmountToPay END) AS Total,
SUM(CASE WHEN H..Email IS NULL THEN 0 ELSE H.AmountToPay END) AS App,
SUM(H.AmountToPay) AS Consumed
FROM dbo.History H
GROUP BY CONVERT(varchar(10), H.VisitDate, 111) --Always specify a length/precision/scale for your data types
ORDER BY [Date] DESC; --Be specific, don't use ordinal positions
Note, as well, I suggest not converting your column VisitDate to a varchar in your query. If you want to display the data in a specific format then that's something for your presentation layer to do, not the RDBMS. Fortunately, as style 111 is yyyy/MM/dd then the order of the data isn't affected. If, however, you want to simply truncate the time portion of your value, convert it to a date so that the value continues to remain strongly typed.

By definition any nonaggregate fields should be in the group by clause. You can write that as:
SELECT TOP (100)
convert(varchar, VisitDate, 111) as Date,
SUM(AmountLoaded) AS Loaded,
SUM(CASE when History.Email is null
then AmountCash
else AmountToPay end) AS Total,
SUM(CASE when History.Email is null
then 0
else AmountToPay
end) AS App,
SUM(AmountToPay) AS Consumed
FROM dbo.History
GROUP BY convert(varchar, VisitDate, 111)
order by 1 desc

Related

SQL query to put a number in a column and put an incremented number when there is a new text in a column

I have a query SELECT * from TABLE which gives the result as below table:
Expected column is as below:
I want to frame a new column like whenever we get the value as 0 then the number should be incremented by 1. I tried DENSE_RANK() , ROW_NUMBER() but couldn't get the exact result which mentioned. Is that possible in PostgreSQL.
Try This:
select name, value,
sum(case when value=0 then 1 else 0 end) over (order by "sno")
from (
select row_number() over() as "sno",* from example
) tab
DEMO
NOTE: Please note that there is no guaranteed that you will get same output always due no ordering field in your raw data.
So Better approach is to add some field in your view output by which it can be ordered and run the query like below:(assuming you have a ID field)
select
name,
value,
sum(case when value=0 then 1 else 0 end) over (order by id)
from example
DEMO

How to build this query in postgresql

Supposing I have this data. How to query this in such a way that will result to this in an efficient way. It will sum the qty of all OUT less the IN per item.
Try this query:
select
"Desc",
sum(case when Type = 'out' then Qty else 0 end) -
sum(case when Type = 'in' then Qty else 0 end)
from yourTable
group by "Desc"
Desc
Note that DESC is a reserved keyword and you should not be naming your databases, tables, or columns using it. I think you would have to escape it in double quotes to get the query to run.
select desc, sum(case Type when 'out' then Qty else -Qty end) from test group by desc;
this will be faster.
Or
SELECT Description, SUM(Case WHEN rowtype = 'in' then 1 else -1 end * Qty) as rowqty FROM yourtable GROUP BY description
Note that it is not a good idea to use reserved words as column names. DESC and TYPE are asking for trouble!

PostgreSQL crosstab() alternative with CASE and aggregates

I want to create a pivot table view showing month on month sum of bookings for every travel_mode.
Table bookings:
timestamp
, bookings
, provider_id
Table providers:
provider_id
, travel_mode
Pivot table function and crosstab functions are not to be used to do this. So I am trying to use JOIN and CASE. Following is the query:
SELECT b.month,
(CASE WHEN p.travel_mode=train then b.amount end)train,
(CASE WHEN p.travel_mode=bus then b.amount end)bus,
(CASE WHEN p.travel_mode=air then b.amount end)air
FROM
(SELECT to_char(date_,month) as month, travel_mode, sum(bookings) as amount
from bookings as b
join providers as p
on b.provider_id=p.provider_id
group by b.month, p.travel_mode)
group by b.month;
However I am getting an error which says:
subquery in FROM must have an alias LINE 6:
And when I add an alias it throws an error saying:
column p.travel_mode must appear in the GROUP BY clause or be used in an aggregate function
LINE 2:
The final result should be something like this
Month Air Bus Train
01 Amount(air) Amount(Bus) Amount(train)
I have a feeling it is a minor error somewhere but I am unable to figure it out at all.
P.S. I had to remove all quotations in the question as it was not allowing me to post this. But those are being taken care of in the actual query.
Multiple problems. The missing table alias is just one of them. This query should work:
SELECT month
, sum(CASE WHEN travel_mode = 'train' THEN amount END) AS train
, sum(CASE WHEN travel_mode = 'bus' THEN amount END) AS bus
, sum(CASE WHEN travel_mode = 'air' THEN amount END) AS air
FROM (
SELECT to_char(timestamp, 'MM') AS month, travel_mode, sum(bookings) AS amount
FROM bookings b
JOIN providers p USING (provider_id)
GROUP BY month, p.travel_mode
) sub
GROUP BY month;
Missing single quotes for string literals. (You seem to have removed those being under the wrong impression you couldn't post quotations.)
Missing table alias for the subquery - just like the 1st error message says.
In the outer query, table names (or aliases) of underlying tables in the subquery are not visible. Only the table alias of the subquery is. Since there is only one subquery, you don't need table-qualification at all there.
month is an output column name (not in the underlying table), so the table qualification b.month was wrong, too.
You seem to want 2-digit numbers for months. Use the template pattern 'MM' instead of 'month' with to_char().
The aggregation in the outer query does not work like you had it - just like your 2nd error message says. You have to wrap the outer CASE expression in a aggregate function. You might as well use min() or max() in this case, because there are never more than one rows after the subquery.
Still unclear where date_ is coming from? You mean timestamp? (which is not a good identifier).
But you don't need the subquery to begin with and can simplify to:
SELECT to_char(timestamp, 'MM') AS month
, sum(CASE WHEN p.travel_mode = 'train' THEN b.bookings END) AS train
, sum(CASE WHEN p.travel_mode = 'bus' THEN b.bookings END) AS bus
, sum(CASE WHEN p.travel_mode = 'air' THEN b.bookings END) AS air
FROM bookings b
JOIN providers p USING (provider_id)
GROUP BY 1;
For best performance you should still use crosstab(), though:
PostgreSQL Crosstab Query
You have to name the subquery as the error message says:
SELECT b.month,
(CASE WHEN p.travel_mode=train then b.amount end)train,
(CASE WHEN p.travel_mode=bus then b.amount end)bus,
(CASE WHEN p.travel_mode=air then b.amount end)air
FROM
(SELECT to_char(date_,month) as month, travel_mode, sum(bookings) as amount
from bookings as b
join providers as p
on b.provider_id=p.provider_id
group by b.month, p.travel_mode)
**as foo** group by b.month;
Remove the stars to make it work.

Aggregated values depending on an other field

I have a table with a date-time and multiples propertied some on which I group by and some on which I aggregate, the query will be like get me revenue per customer last week.
Now I want to see the change between the requested period and the previous one so I will have 2 columns revenue and previous_revenue.
Right now I'm requesting the rows of the requested period plus the rows of the previous period and for each aggregated field I add a case statement inside which return the value or 0 if not in the period that I want.
That lead to as many CASE as aggregate fields but always with the same conditional statement.
I'm wondering if there is a better design for this use case...
SELECT
customer,
SUM(
CASE TIMESTAMP_CMP('2016-07-01 00:00:00', ft.date) > 0 WHEN true THEN
REVENUE
ELSE 0 END
) AS revenue,
SUM(
CASE TIMESTAMP_CMP('2016-07-01 00:00:00', ft.date) < 0 WHEN true THEN
REVENUE
ELSE 0 END
) AS previous_revenue
WHERE date_hour >= '2016-06-01 00:00:00'
AND date_hour <= '2016-07-31 23:59:59'
GROUP BY customer
(In my real use case I have many columns which make it even more ugly)
First, I'd suggest to refactor out the timestamps and precalculate the current and previous period for later use. This is not strictly necessary to solve your problem, though:
create temporary table _period as
select
'2016-07-01 00:00:00'::timestamp as curr_period_start
, '2016-07-31 23:59:59'::timestamp as curr_period_end
, '2016-06-01 00:00:00'::timestamp as prev_period_start
, '2016-06-30 23:59:59'::timestamp as prev_period_end
;
Now a possible design to avoid repetition of timestamps and CASE statements is to group by the periods first and then doing a FULL OUTER JOIN for that table on itself:
with _aggregate as (
select
case
when date_hour between prev_period_start and prev_period_end then 'previous'
when date_hour between curr_period_start and curr_period_end then 'current'
end::varchar(20) as period
, customer
-- < other columns to group by go here >
, sum(revenue) as revenue
-- < other aggregates go here >
from
_revenue, _period
where
date_hour between prev_period_start and curr_period_end
group by 1, 2
)
select
customer
, current_period.revenue as revenue
, previous_period.revenue as previous_revenue
from
(select * from _aggregate where period = 'previous') previous_period
full outer join (select * from _aggregate where period = 'current') current_period
using(customer) -- All columns which have been group by must go into the using() clause:
-- e.g. using(customer, some_column, another_column)
;

TSQL - Control a number sequence

Im a new in TSQL.
I have a table with a field called ODOMETER of a vehicle. I have to get the quantity of km in a period of time from 1st of the month to the end.
SELECT MAX(Odometer) - MIN(Odometer) as TotalKm FROM Table
This will work in ideal test scenary, but the Odomometer can be reset to 0 in anytime.
Someone can help to solve my problem, thank you.
I'm working with MS SQL 2012
EXAMPLE of records:
Date Odometer value
datetime var, 37210
datetime var, 37340
datetime var, 0
datetime var, 220
Try something like this using the LAG. There are other ways, but this should be easy.
EDIT: Changing the sample data to include records outside of the desired month range. Also simplifying that Reading for easy hand calc. Will shows a second option as siggested by OP.
DECLARE #tbl TABLE (stamp DATETIME, Reading INT)
INSERT INTO #tbl VALUES
('02/28/2014',0)
,('03/01/2014',10)
,('03/10/2014',20)
,('03/22/2014',0)
,('03/30/2014',10)
,('03/31/2014',20)
,('04/01/2014',30)
--Original solution with WHERE on the "outer" SELECT.
--This give a result of 40 as it include the change of 10 between 2/28 and 3/31.
;WITH cte AS (
SELECT Reading
,LAG(Reading,1,Reading) OVER (ORDER BY stamp ASC) LastReading
,Reading - LAG(Reading,1,Reading) OVER (ORDER BY stamp ASC) ChangeSinceLastReading
,CONVERT(date, stamp) stamp
FROM #tbl
)
SELECT SUM(CASE WHEN Reading = 0 THEN 0 ELSE ChangeSinceLastReading END)
FROM cte
WHERE stamp BETWEEN '03/01/2014' AND '03/31/2014'
--Second option with WHERE on the "inner" SELECT (within the CTE)
--This give a result of 30 as it include the change of 10 between 2/28 and 3/31 is by the filtered lag.
;WITH cte AS (
SELECT Reading
,LAG(Reading,1,Reading) OVER (ORDER BY stamp ASC) LastReading
,Reading - LAG(Reading,1,Reading) OVER (ORDER BY stamp ASC) ChangeSinceLastReading
,CONVERT(date, stamp) stamp
FROM #tbl
WHERE stamp BETWEEN '03/01/2014' AND '03/31/2014'
)
SELECT SUM(CASE WHEN Reading = 0 THEN 0 ELSE ChangeSinceLastReading END)
FROM cte
I think Karl solution using LAG is better than mine, but anyway:
;WITH [Rows] AS
(
SELECT o1.[Date], o1.[Value] as CurrentValue,
(SELECT TOP 1 o2.[Value]
FROM #tbl o2 WHERE o1.[Date] < o2.[Date]) as NextValue
FROM #tbl o1
)
SELECT SUM (CASE WHEN [NextValue] IS NULL OR [NextValue] < [CurrentValue] THEN 0 ELSE [NextValue] - [CurrentValue] END )
FROM [Rows]