I am trying to create a function that will return a table of all cows that produced on average more than 20 liters of milk per day.
This is the code I came up with:
CREATE FUNCTION SuperCows (#year int)
RETURNS #supercows TABLE (
Name nvarchar(50),
AvgMilk decimal(4,2)
)
BEGIN
INSERT #supercows
SELECT c.Name, AVG(CAST(p.MilkQuantity AS decimal(4,2))) FROM MilkProduction AS p
INNER JOIN Cows AS c ON c.IDCow = p.CowID
WHERE YEAR(p.Date) = #year
GROUP BY p.CowID
HAVING AVG(CAST(p.MilkQuantity AS decimal(4,2))) > 20
RETURN
END
GO
The error that I get when trying to create the function is this:
Column 'Cows.Name' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
My knowledge of SQL is fairly limited and I was hoping someone cold help me with solving this.
You need to add Cows.name to the group by list:
SELECT c.Name, AVG(CAST(p.MilkQuantity AS decimal(4,2))) FROM MilkProduction AS p
INNER JOIN Cows AS c ON c.IDCow = p.CowID
WHERE YEAR(p.Date) = #year
GROUP BY p.CowID, c.Name
HAVING AVG(CAST(p.MilkQuantity AS decimal(4,2))) > 20
If you are using group by every field you select needs to either be in the list being grouped by or have an aggregate function applied to the column (AVG, MIN, MAX, SUM, etc) as there can be multiple values returned for each of the non-grouped-by columns.
Change
GROUP BY p.CowID
to
GROUP BY c.Name
This won't work if you have multiple cows with the same name - in that case their total MilkQuantity will be combined into a single record.
Related
I´m having issues with the following query. I have two tables; Table Orderheader and table Bought. The first query I execute gives me, for example, two dates. Based on these two dates, I need to find Production data AND, based on the production data, I need to find the Bought data, and combine those data together. Lets say I do the following:
Select Lotdate From Orderheader where orhsysid = 1
This results in two rows: '2019-02-05' and '2019-02-04'. Now I need to do two things: I need two run two queries using this result set. The first one is easy; use the dates returned and get a sum of column A like this:
Select date, SUM(Amount) from Orderheader where date = Sales.date() [use the two dates here]
The second one is slighty more complicated, I need to find the last day where something has been bought based on the two dates. Production is everyday so Productiondate=Sales.date()-1. But Bought is derived from Productionday and is not everyday so for every Productionday it needs to find the last Boughtday. So I can't say where date = Orderheader.date. I need to do something like:
Select date, SUM(Amount)
FROM Bought
WHERE date = (
SELECT top 1 date
FROM Bought
WHERE date < Orderheader.date)
But twice, for both the dates I got.
This needs to result in 1 table giving me:
Bought.date, Bought.SUM(AMOUNT), Orderheader.date, Orderheader.SUM(AMOUNT)
All based on the, possible multiple, Lotdate(s) I got from the first query from Sales table.
I've been struggling with this for a moment now, using joins and nested queries but I can't seem to figure it out!
Example sample:
SELECT CONVERT(date,ORF.orfDate) as Productiedatum, SUM(orlQuantityRegistered) as 'Aantal'
FROM OrderHeader ORH
LEFT JOIN OrderFrame ORF ON ORH.orhFrameSysID = ORF.orfSysID
LEFT JOIN OrderLine ORL ON ORL.orhSysID = ORH.orhSysID
LEFT JOIN Item ON Item.itmSysID = ORL.orlitmSysID
where CONVERT(date,ORF.orfDate) IN
(
SELECT DISTINCT(CONVERT(date, Lot.lotproductiondate)) as Productiedatum
FROM OrderHeader ORH
LEFT JOIN Registration reg ON reg.regorhSysID = ORH.orhSysID
LEFT JOIN StockRegistration stcreg ON stcreg.stcregRegistrationSysID = reg.regSysID
LEFT JOIN Lot ON Lot.lotSysID = stcregSrclotSysID
WHERE ORH.orhSysID = 514955
AND regRevokeRegSysID IS NULL
AND stcregSrcitmSysID = 5103
)
AND ORL.orlitmSysID = 5103
AND orldirSysID = 2
AND NOT orlQuantityRegistered IS NULL
GROUP BY Orf.orfDate
Sample output:
Productiedatum Aantal
2019-02-05 20
2019-02-06 20
Here I used a nested subquery to get the results from 'Production' (orderheader) because I just can use date = date. I'm struggling with the Sales part where I need to find the last date(s) and use those dates in the Sales table to get the sum of that date.
Expected output:
Productiedatum Aantal Boughtdate Aantal
2019-02-04 20 2019-02-01 55
2019-02-05 20 2019-02-04 60
Try this.
IF OBJECT_ID('tempdb..#Production') IS NOT NULL DROP TABLE #Production
IF OBJECT_ID('tempdb..#Bought') IS NOT NULL DROP TABLE #Bought
CREATE table #Production(R_NO int,ProductionDate datetime,ProductionAmount float)
CREATE table #Bought(R_NO int,Boughtdate datetime,Boughtamount float)
insert into #Production(ProductionDate,ProductionAmount,R_NO)
select p.date ProductionDate,sum(Amount) ProductionAmount,row_number()over (order by p.date) R_NO
from Production P
join Sales s on p.date=S.date-1
where orhsysid=1
group by p.date
declare #loop int,#ProdDate datetime
select #loop =max(R_NO) from #Production
while (1<=#loop)
begin
select #ProdDate=ProductionDate from #Production where r_no=#loop
insert into #Bought(Boughtdate,Boughtamount,R_NO)
select Date,Sum(Amount),#loop R_NO from Bought where date=(
select max(date) from bought B
where B.Date<#ProdDate)
group by Date
set #loop=#loop-1
end
select ProductionDate,ProductionAmount,Boughtdate,Boughtamount from #Bought B
join #Production p on B.R_NO=P.R_NO
I am new to Postgres and databases so I am sorry. I ran a query to get a count of students per school from one table. Now I have the table below:
school_probs:
school_code(PK bigint) schoolName(text) probs(numeric)
1 CAA {0.05,0.08,0.18,0.3,0.11,0.28}
2 CAS {0.06,0.1,0.295,0.36,0.12,0.065}
3 CBA {0.05,0.11,0.35,0.32,0.12,0.05}
4 CL {0.07,0.09,0.24,0.4,0.06,0.09}
How would I go about multiplying the count from each school with each number in the probs column. Ex: We have total number of students at school "CAA" If it is 198, then the probability distribution will be
(0.05*198, 0.08*198, 0.18*198, 0.3*198, 0.11*198, 0.28*198). With the results I can then assign grades to students.
My query to get the count is as follows:
SELECT simulated_records.school, COUNT(simulated_records.school) as studentCount INTO CountSchool
FROM simulated_records, school_probs
WHERE simulated_records.school = school_probs.school
GROUP BY simulated_records.school;
To multiply elements of an array with a constant, you need to unnest, multiply, and aggregate again. Some caveats lie in wait. Consider:
PostgreSQL unnest() with element number
And best use an ARRAY constructor.
That said, and making some assumptions about your undisclosed table design, I would also simplify the count:
Aggregate a single column in query with many columns
Arriving at:
SELECT *, ARRAY(SELECT unnest(p.probs) * r.student_ct) AS scaled_probs
FROM school_probs p
LEFT JOIN (
SELECT school, COUNT(*)::int AS student_ct
FROM simulated_records
GROUP BY 1
) r USING (school);
Or, to represent NULL arrays as NULL arrays:
SELECT *
FROM school_probs p
LEFT JOIN (
SELECT school, COUNT(*)::int AS student_ct
FROM simulated_records
GROUP BY 1
) r USING (school)
LEFT JOIN LATERAL (
SELECT ARRAY(SELECT unnest(p.probs) * r.student_ct) AS scaled_probs
) p1 ON p.probs IS NOT NULL;
db<>fiddle here
I suggest this simply form with a set-returning function in the SELECT list only for Postgres 10 or later, because of:
What is the expected behaviour for multiple set-returning functions in select clause?
I have the following table structure for the table "products":
id amount number
1 10 M6545
2 32 M6424
3 32 M6545
4 49 M6412
... ... ...
I want to select the sum of amounts of all rows with the same number. The rows with the same number should be summed up to one sum. That means:
M6545 -> Sum 42
M6424 -> Sum 32
M6421 -> Sum 49
My query looks like the following and still does not work:
SELECT SUM(amount) as SumAm FROM products WHERE number IN ('M6412', 'M6545')
I want to find a way where I can only select the sum ordered by the numbers in the "IN" statement. That means, the result table should look like:
SumAm
49
42
The sums should not be ordered in some way. It should match the order of numbers in the IN clause.
use group by number
SELECT number, SUM(amount) as SumAm FROM products
--WHERE number IN ('M6412', 'M6545') i think you dont need where clause
group by number
But if you want just for 'M6412', 'M6545' then you need where clause that you showed in your 2nd output sample
Use group by and aggregation
SELECT SUM(amount) as SumAm FROM products
WHERE number IN ('M6412', 'M6545')
group by number
You can't order by results based directly on the order of the IN clause.
What you can do is something like this:
SELECT SUM(amount) as SumAm
FROM products
WHERE number IN ('M6412', 'M6545')
GROUP BY number -- You must group by to get a row for each number
ORDER BY CASE number
WHEN 'M6412' THEN 1
WHEN 'M6545' THEN 2
END
Of course, the more items you have in your IN clause the more cumbersome this query will get. Therefor another solution might be more practical - joining to a table variable instead of using IN:
DECLARE #Numbers AS TABLE
(
sort int identity(1,1), -- this will hold the order of the inserted values
number varchar(10) PRIMARY KEY -- enforce unique values
);
INSERT INTO #Numbers (number) VALUES
('M6412'),
('M6545')
SELECT SUM(amount) as SumAm
FROM products As p
JOIN numbers As n ON p.Number = n.Number
-- number and sort have a 1 - 1 relationship,
-- so it's safe to group by it instead of by number
GROUP BY n.sort
ORDER BY n.sort
Your requirement is non-sense... this is not how IN is designed to work. Having said that, the following will give you the result in the desired order:
SELECT SUM(amount)
FROM (VALUES
('M6545', 1),
('M6412', 2)
) AS va(number, sortorder)
INNER JOIN sumam ON va.number = sumam.number
GROUP BY va.number, va.sortorder
ORDER BY va.sortorder
It is somewhat better than writing a CASE statement when you would need to add a WHEN condition for each number.
Hi how can I get the percentage of each record over the total?
Lets imagine I have one table with the following
ID code Points
1 101 2
2 201 3
3 233 4
4 123 1
The percentage for ID 1 is 20% for 2 is 30% and so one
how do I get it?
There's a couple approaches to getting that result.
You essentially need the "total" points from the whole table (or whatever subset), and get that repeated on each row. Getting the percentage is a simple matter of arithmetic, the expression you use for that depends on the datatypes, and how you want that formatted.
Here's one way (out a couple possible ways) to get the specified result:
SELECT t.id
, t.code
, t.points
-- , s.tot_points
, ROUND(t.points * 100.0 / s.tot_points,1) AS percentage
FROM onetable t
CROSS
JOIN ( SELECT SUM(r.points) AS tot_points
FROM onetable r
) s
ORDER BY t.id
The view query s is run first, that gives a single row. The join operation matches that row with every row from t. And that gives us the values we need to calculate a percentage.
Another way to get this result, without using a join operation, is to use a subquery in the SELECT list to return the total.
Note that the join approach can be extended to get percentage for each "group" of records.
id type points %type
-- ---- ------ -----
1 sold 11 22%
2 sold 4 8%
3 sold 25 50%
4 bought 1 50%
5 bought 1 50%
6 sold 10 20%
To get that result, we can use the same query, but a a view query for s that returns total GROUP BY r.type, and then the join operation isn't a CROSS join, but a match based on type:
SELECT t.id
, t.type
, t.points
-- , s.tot_points_by_type
, ROUND(t.points * 100.0 / s.tot_points_by_type,1) AS `%type`
FROM onetable t
JOIN ( SELECT r.type
, SUM(r.points) AS tot_points
FROM onetable r
GROUP BY r.type
) s
ON s.type = t.type
ORDER BY t.id
To do that same result with the subquery, that's going to be a correlated subquery, and that subquery is likely to get executed for every row in t.
This is why it's more natural for me to use a join operation, rather than a subquery in the SELECT list... even when a subquery works the same. (The patterns we use for more complex queries, like assigning aliases to tables, qualifying all column references, and formatting the SQL... those patterns just work their way back into simple queries. The rationale for these patterns is kind of lost in simple queries.)
try like this
select id,code,points,(points * 100)/(select sum(points) from tabel1) from table1
To add to a good list of responses, this should be fast performance-wise, and rather easy to understand:
DECLARE #T TABLE (ID INT, code VARCHAR(256), Points INT)
INSERT INTO #T VALUES (1,'101',2), (2,'201',3),(3,'233',4), (4,'123',1)
;WITH CTE AS
(SELECT * FROM #T)
SELECT C.*, CAST(ROUND((C.Points/B.TOTAL)*100, 2) AS DEC(32,2)) [%_of_TOTAL]
FROM CTE C
JOIN (SELECT CAST(SUM(Points) AS DEC(32,2)) TOTAL FROM CTE) B ON 1=1
Just replace the table variable with your actual table inside the CTE.
I am trying to update a column called Number_Of_Marks in our Results table using the results we get from our SELECT statement. Our select statement is used to count the numbers of marks per module in our results table. The SELECT statement works and the output is correct, which is
ResultID ModuleID cnt
-------------------------
111 ART3452 2
114 ART3452 2
115 CSC3039 3
112 CSC3039 3
113 CSC3039 3
The table in use is:
Results: ResultID, ModuleID, Number_Of_Marks
We need the results of cnt to be updated into our Number_Of_Marks column. This is our code below...
DECLARE #cnt INT
SELECT #cnt
SELECT C.cnt
FROM Results S
INNER JOIN (SELECT ModuleID, count(ModuleID) as cnt
FROM Results
GROUP BY ModuleID) C ON S.ModuleID = C.ModuleID
UPDATE Results
SET [Number_Of_Marks] = (#cnt)
You can do this in SQL Server using the update/join syntax:
UPDATE s
SET [Number_Of_Marks] = c.cnt
FROM Results S INNER JOIN
(SELECT ModuleID, count(ModuleID) as cnt
FROM Results
GROUP BY ModuleID
) C
ON S.ModuleID = C.ModuleID;
I assume that you want the count from the subquery, not from the uninitialized variable.
EDIT:
In general, when you change the question it is better to ask another question. Sometimes, though, the changes are really small. The revised query looks something like:
UPDATE s
SET [Number_Of_Marks] = c.cnt,
Marks = avgmarks
FROM Results S INNER JOIN
(SELECT ModuleID, count(ModuleID) as cnt, avg(marks * 1.0) as avgmarks
FROM Results
GROUP BY ModuleID
) C
ON S.ModuleID = C.ModuleID;
Note that I multiplied the marks by 1.0. This is a quick-and-dirty way to convert an integer to a numeric value. SQL Server takes averages on integers and produces an integer. Usually you want some sort of decimal or floating value.