Select multiple counts from Postgres table, convert to Ruby object - ruby-on-rails

Given the following select:
SELECT
COUNT(*) AS total,
SUM(CASE approved WHEN 't' THEN 1 ELSE 0 END) AS num_approved,
SUM(CASE soft_delete WHEN 't' THEN 1 ELSE 0 END) AS num_deleted
FROM model_name;
How might I translate this into something in my class ModelName definition, so that I can retrieve the values of total, num_approved, and num_deleted in my Rails application? I'm happy with any output (array, hash, accessors on a ModelName object) that puts the numbers at the Ruby level.

result = ModelName.select("
COUNT(*) AS total,
SUM(CASE approved WHEN 't' THEN 1 ELSE 0 END) AS num_approved,
SUM(CASE soft_delete WHEN 't' THEN 1 ELSE 0 END) AS num_deleted
").first
irb(main):278:0> result.total
=> 3041
irb(main):279:0> result.num_approved
=> 199464763
irb(main):280:0> result.num_deleted
=> #<BigDecimal:7fa0b7d79fd8,'0.19365329E8',9(18)>

Related

Create multiple columns on sql

Query written :
CREATE TABLE CEREAL_LIST_TAB
CEREAL_DESC VARCHAR(10)NOT NULL
INSERT INTO CEREAL_LIST_TAB
SELECT
NVL(CASE WHEN INDICATOR >= '01' AND INDICATOR <= '05' THEN 'WHEAT'
ELSE CASE WHEN INDICATOR >= '06' AND INDICATOR <= '10' THEN 'RICE'
ELSE CASE WHEN INDICATOR >= '11' AND INDICATOR <= '15' THEN 'BARLEY'
ELSE CASE WHEN INDICATOR >= '16' AND INDICATOR <= '20' THEN 'OATS'
ELSE CASE WHEN INDICATOR = '21' AND INDICATOR = '22' THEN 'OTHER' END END END END END,' ')
COUNT (CASE WHEN REVENUE <'1000000' THEN KEY_FIELD END) as 'Less than $1M',
COUNT (CASE WHEN REVENUE >='1000000' AND REVENUE <='5000000' THEN KEY_FIELD END) as '$1M-$5M',
COUNT (CASE WHEN REVENUE >='5000001' AND REVENUE <='10000000' THEN KEY_FIELD END) as '$5M-$10M',
COUNT (CASE WHEN REVENUE >='10000001' AND REVENUE <='25000000' THEN KEY_FIELD END) as '$10M-$25M';
FROM TABLE_REVENUE
GROUP BY CEREAL_DESC
ORDER BY CEREAL_DESC;
expected result: I need counts of each cereal in each revenue bucket listed in the query but its's throwing error, please help to let me know where i am making error
Multiple things here,
You don't need to repeat case expressions you can produce
CEREAL_DESC with just one and without NVL
Correlation names must be coded between ", not '
you cannot create CEREAL_DESC in the SELECT clause AND use it in the GROUP BY clause, you have to use a subquery or a CTE
a BETWEEN b and c is more readable to me than a >= b and a <= c
if a column is numeric compare it to numeric values, else you could trigger unneeded conversions to char types with performance degradations
try this
with
descs as (
SELECT
CASE
WHEN INDICATOR between '01' and '05' THEN 'WHEAT'
WHEN INDICATOR between '06' and '10' THEN 'RICE'
WHEN INDICATOR between '11' and '15' THEN 'BARLEY'
WHEN INDICATOR between '16' and '20' THEN 'OATS'
WHEN INDICATOR between '21' and '22' THEN 'OTHER'
ELSE '' end as CEREAL_DESC,
REVENUE
FROM TABLE_REVENUE
)
select
CEREAL_DESC,
sum(REVENUE < 1000000) as "Less than $1M",
sum(REVENUE between 1000001 and 5000000) as "$1M-$5M",
sum(REVENUE between 5000001 and 10000000) as "$5M-$10M",
sum(REVENUE between 10000001 and 25000000) as "$10M-$25M"
from descs
GROUP BY CEREAL_DESC
ORDER BY CEREAL_DESC;

Having a difficult time figuring out how to count non nulls in a row combined with joins and calculations

I have a SQL Server database and I am trying to pull specific data. I need a count of all the non-null columns in each row, a subtraction of one column from another, and data from other table columns (joins).
This is where I am, could someone please look at the code and tell me what I am doing wrong (ignore the hard-coded dates, they are there solely for testing)?
SELECT
((CASE WHEN TC.Time0 IS NOT NULL THEN 1 ELSE 0 END)
+ (CASE WHEN TC.Time1 IS NOT NULL THEN 1 ELSE 0 END)
+ (CASE WHEN TC.Time2 IS NOT NULL THEN 1 ELSE 0 END)
+ (CASE WHEN TC.Time3 IS NOT NULL THEN 1 ELSE 0 END)
+ (CASE WHEN TC.Time4 IS NOT NULL THEN 1 ELSE 0 END)
+ (CASE WHEN TC.Time5 IS NOT NULL THEN 1 ELSE 0 END)
+ (CASE WHEN TC.Time6 IS NOT NULL THEN 1 ELSE 0 END)
+ (CASE WHEN TC.Time7 IS NOT NULL THEN 1 ELSE 0 END)
+ (CASE WHEN TC.Time8 IS NOT NULL THEN 1 ELSE 0 END)
+ (CASE WHEN TC.Time9 IS NOT NULL THEN 1 ELSE 0 END)) AS [Time Punches]
,SUM(CASE WHEN TC.Odometer0 IS NOT NULL THEN 1 ELSE 0 END) AS MileageStart
,SUM(CASE WHEN TC.Odometer1 IS NOT NULL THEN 1 ELSE 0 END) AS MileageEnd
,SUM(CASE WHEN MileageEnd >= 0 THEN 1 ELSE 0 END) -
SUM(CASE WHEN MileageStart < 0 THEN 1 ELSE 0 END) AS [Total Miles]
,D.DriverID AS [Driver ID]
,W.FirstName +' '+W.LastName AS [Driver Name]
,TC.PunchDate AS [DATE]
FROM tblTimeClock TC WITH (NOLOCK)
INNER JOIN tblDrivers D WITH (NOLOCK)
ON D.DriverID = TC.PunchID
INNER JOIN tblWorker W WITH (NOLOCK)
ON W.WorkerID = D.DriverID
WHERE TC.PunchID IS NOT NULL
AND TC.PunchDate BETWEEN '2017-05-01' AND '2017-06-01'
ORDER BY TC.PunchDate
With the above I am getting this error:
> Column 'tblTimeClock.Time0' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
But I do not know how to include this in a GROUP BY clause - every time I try it causes other errors to pop up (different, depending on where I place the clause).
The reason I asked if someone can "tell me what I am doing wrong" is so that once I fix what is broken, I don't have to come back and say "help" again and again for each subsequent issue. I know the code is bad, this is why I need help.
I figured it out on my own:
SELECT
TC.PunchDate AS [Date]
,D.DriverID AS [Driver ID]
,W.FirstName +' '+W.LastName AS [Driver Name]
,((CASE WHEN TC.Time0 IS NOT NULL THEN 1 ELSE 0 END)
+ (CASE WHEN TC.Time1 IS NOT NULL THEN 1 ELSE 0 END)
+ (CASE WHEN TC.Time2 IS NOT NULL THEN 1 ELSE 0 END)
+ (CASE WHEN TC.Time3 IS NOT NULL THEN 1 ELSE 0 END)
+ (CASE WHEN TC.Time4 IS NOT NULL THEN 1 ELSE 0 END)
+ (CASE WHEN TC.Time5 IS NOT NULL THEN 1 ELSE 0 END)
+ (CASE WHEN TC.Time6 IS NOT NULL THEN 1 ELSE 0 END)
+ (CASE WHEN TC.Time7 IS NOT NULL THEN 1 ELSE 0 END)
+ (CASE WHEN TC.Time8 IS NOT NULL THEN 1 ELSE 0 END)
+ (CASE WHEN TC.Time9 IS NOT NULL THEN 1 ELSE 0 END)) AS [Time Punches]
,TC.Odometer0 AS [Starting Mileage]
,TC.Odometer1 AS [Ending Mileage]
,SUM(CASE WHEN TC.Odometer1 IS NOT NULL AND TC.Odometer1 >= 0 THEN TC.Odometer1 ELSE 0 END) -
SUM(CASE WHEN TC.Odometer0 IS NOT NULL AND TC.Odometer0 >= 0 THEN TC.Odometer0 ELSE 0 END) AS [Total Miles]
FROM tblTimeClock TC WITH (NOLOCK)
INNER JOIN tblDrivers D WITH (NOLOCK)
ON CAST(D.DriverID AS VARCHAR(50)) = TC.PunchID
INNER JOIN tblWorker W WITH (NOLOCK)
ON W.WorkerID = D.DriverID
WHERE TC.PunchID IS NOT NULL
AND TC.PunchDate BETWEEN #StartDate AND #EndDate
GROUP BY TC.Time0, TC.Time1, TC.Time2, TC.Time3, TC.Time4, TC.Time5, TC.Time6, TC.Time7, TC.Time8, TC.Time9, TC.Odometer0,TC.Odometer1, D.DriverID, W.FirstName, W.LastName, TC.PunchDate
ORDER BY TC.PunchDate

SQLite LEFT JOIN count(*)?

I need to join two tables (well, actually two views) so that for every selected row of the left view, there is a count of rows from the right view. That sounds to me like a LEFT JOIN, but in SQLite (this test database) and a LEFT JOIN query:
SELECT TARGET.session_id session_id, TARGET.labeltype_id labeltype_id, TARGET.label_id label_id, count(SECONDARY.label_id) NOlabels
FROM segment_extended TARGET LEFT JOIN segment_extended SECONDARY
WHERE TARGET.session_id = SECONDARY.session_id AND TARGET.lt_name= "Word" AND SECONDARY.lt_name ="Comments"
AND ((SECONDARY.start <= TARGET.start AND TARGET.END <= SECONDARY.END) OR (TARGET.start <= SECONDARY.start AND SECONDARY.END <= TARGET.END))
AND TARGET.label != '' AND SECONDARY.label != ''
GROUP BY TARGET.session_id,TARGET.labeltype_id, TARGET.label_id;
I get only a small subset of what I would expect:
2 3 3 1
2 3 9 1
A more extended query gives the correct result:
SELECT session_id, labeltype_id, label_id, max(NOlabels) NOlabels
FROM (SELECT TARGET.session_id session_id, TARGET.labeltype_id labeltype_id, TARGET.label_id label_id, count(SECONDARY.label_id) NOlabels
FROM segment_extended TARGET , segment_extended SECONDARY
WHERE TARGET.session_id = SECONDARY.session_id AND TARGET.lt_name= "Word" AND SECONDARY.lt_name ="Comments"
AND ((SECONDARY.start <= TARGET.start AND TARGET.END <= SECONDARY.END) OR (TARGET.start <= SECONDARY.start AND SECONDARY.END <= TARGET.END))
AND TARGET.label != '' AND SECONDARY.label != ''
GROUP BY TARGET.session_id,TARGET.labeltype_id, TARGET.label_id
UNION
SELECT TARGET.session_id session_id, TARGET.labeltype_id labeltype_id, TARGET.label_id label_id, 0 NOlabels
FROM segment_extended TARGET
WHERE TARGET.lt_name= "Word"
AND TARGET.label != ''
GROUP BY TARGET.session_id,TARGET.labeltype_id, TARGET.label_id)
GROUP BY session_id, labeltype_id, label_id
ORDER BY session_id,labeltype_id, label_id
session_id labeltype_id label_id NOlabels
2 3 2 0
2 3 3 1
2 3 4 0
2 3 5 0
2 3 7 0
2 3 8 0
2 3 9 1
2 3 10 0
but it seems unnecessarily complicated. What am I doing wrong with the left join?
When doing a left join you have to count the null values from the left join as 0 records but still include them. You can accomplish this with a CASE construct in the inner query, then using the SUM aggregate function in the outer group-by.
SELECT session_id, labeltype_id, label_id, sum(has_label) NOlabels
FROM (
SELECT TARGET.session_id session_id, TARGET.labeltype_id labeltype_id, TARGET.label_id label_id, CASE WHEN SECONDARY.label_id is NULL then 0 else 1 END has_label
FROM
segment_extended TARGET
LEFT JOIN
segment_extended SECONDARY on
TARGET.session_id = SECONDARY.session_id
AND SECONDARY.lt_name ="Comments"
AND ((
SECONDARY.start <= TARGET.start AND TARGET.END <= SECONDARY.END)
OR (TARGET.start <= SECONDARY.start AND SECONDARY.END <= TARGET.END))
AND SECONDARY.label != ''
WHERE TARGET.lt_name= "Word" AND TARGET.label != '')
GROUP BY session_id, labeltype_id, label_id
Your join is not a left join.
A left join adds NULL values for the right table if there are no rows that match the join condition.
However, you query does not have a join condition, and the WHERE condition is not affect by the LEFT JOIN clause.
Replace WHERE with ON.

How can I group 5 sub-queries counting total based on the value in one field in child table into one update

I've inherited a solution where a Stored Procedure is triggered as a post process to calculate the total number of each type of communique in the child table and update the Parent table.
The query is not optimal resulting in timeouts if the number of Child rows to the parent are more than 2-3000.
It feels as if this could be solved by grouping each of the communication types in one query instead of 5 different ones.
I'm thinking something like this pseudo code:
UPDATE Parent SET Total = query.Total, email = query.email, letter = query.letter, spoken = query.spoken, written = query.written
JOIN ... as query
WHERE Parent.ParentID = #pid
Is my gut feeling correct or are SQL Server able to optimize this internally?
Adding an index might be an option, but each of the sub queries are as far as I can see hitting one.
The stored procedure looks like this:
CREATE PROCEDURE [dbo].[UpdateParentTotal]
#pid int
AS
UPDATE Parent
-- count Total
set Total = (select count(*) from Child
where Child.ParentID = Parent.ParentID),
-- count Type "email" total
email = (select count(*) from Child
where Child.ParentID = Parent.ParentID and Type = 1),
-- count Type "letter" total
letter = (select count(*) from Child
where Child.ParentID = Parent.ParentID and Type = 2),
-- count Total for Spoken word
spoken = (select count(*) from Child
where Child.ParentID = Parent.ParentID and Type > 99),
-- count all types of written word
written = (select count(*) from Child
where Child.ParentID = Parent.ParentID and ((Type > 1 and Type < 100) or Type is null))
where ParentID = #pid
GO
PS. Naming the question was really hard, since I don't know exactly what kind of solution I need.
You're correct in how you are thinking ... you can put all of these calculations into a single query, then connect to that. See below for an example.
UPDATE Parent
SET
Total = CountTotal,
Email = CountEmail
Letter = CountLetter
Spoken = CountSpoken
Written = CountWritten
FROM
Parent
INNER JOIN
(
SELECT
ParentID,
COUNT(*) CountTotal,
COUNT(CASE WHEN [Type] = 1 THEN 1 END) AS CountEmail,
COUNT(CASE WHEN [Type] = 2 THEN 1 END) AS CountLetter,
COUNT(CASE WHEN [Type] >99 THEN 1 END) AS CountSpoken,
COUNT(CASE WHEN (([Type] > 1 and [Type] < 100) or [Type] is null)) THEN 1 END) AS CountWritten,
FROM Child
GROUP BY ParentID
) Child ON
Parent.ParentID = Child.ParentID
WHERE Parent.ParentID = #pid
You can use a conditional count to summarise your child table by parentID, e.g.
SELECT ParentID,
Email = COUNT(CASE WHEN [Type] = 1 THEN 1 END),
Letter = COUNT(CASE WHEN [Type] = 2 THEN 1 END),
Spoken = COUNT(CASE WHEN [Type] > 99 THEN 1 END),
Written = COUNT(CASE WHEN ([Type] = > 1 AND [Type] < 100) OR [Type] IS NULL THEN 1 END),
Total = COUNT(*)
FROM Child
GROUP BY ParentID
Then it is just a case of incorporating this into your update statement:
WITH ChildSummary AS
( SELECT ParentID,
Email = COUNT(CASE WHEN [Type] = 1 THEN 1 END),
Letter = COUNT(CASE WHEN [Type] = 2 THEN 1 END),
Spoken = COUNT(CASE WHEN [Type] > 99 THEN 1 END),
Written = COUNT(CASE WHEN ([Type] = > 1 AND [Type] < 100) OR [Type] IS NULL THEN 1 END),
Total = COUNT(*)
FROM Child
GROUP BY ParentID
)
UPDATE P
SET Total = ISNULL(cs.Total, 0),
Email = ISNULL(cs.Email, 0),
Letter = ISNULL(cs.Letter, 0),
Spoken = ISNULL(cs.Spoken, 0),
Written = ISNULL(cs.Written, 0)
FROM Parent AS p
LEFT JOIN ChildSummary AS cs
ON cs.ParentID = p.ParentID
WHERE p.ParentID = #Pid;

Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery

declare #ProductDetails as table(ProductName nvarchar(200),ProductDescription nvarchar(200),
Brand nvarchar(200),
Categry nvarchar(200),
Grop nvarchar(200),
MRP decimal,SalesRate decimal,CurrentQuantity decimal,AvailableQty decimal)
declare #AvailableQty table(prcode nvarchar(100),Aqty decimal)
declare #CloseStock table(pcode nvarchar(100),
Cqty decimal)
insert into #CloseStock
select PCODE ,
0.0
from producttable
insert into #AvailableQty
select PCODE ,
0.0
from producttable
--Current Qty
--OpenQty
update #CloseStock set Cqty=((OOQTY+QTY+SRRQTY+PYQTY)-(STQTY+PRRQTY))
from
(
select PC.PCODE as PRODUCTCODE,
--Opening
(select case when SUM(PU.Quantity)is null then 0 else SUM(PU.Quantity) end as Q from ProductOpeningYearEnd PU
where PC.PCODE=PU.ProductName) as OOQTY,
--Purchase
(select case when SUM(PU.quantity)is null then 0 else SUM(PU.quantity) end as Q from purchase PU
where PC.PCODE=PU.prdcode ) as QTY,
--Sales
(select case when SUM(ST.QUANTITY)is null then 0 else SUM(ST.QUANTITY)end as Q2 from salestable ST
where PC.PCODE=ST.PRODUCTCODE and ST.status!='cancel' )as STQTY,
--Physical Stock
(select case when SUM(PS.Adjustment)is null then 0 else SUM(PS.Adjustment)end as Q3 from physicalstock PS
where PC.PCODE=PS.PCODE )as PYQTY,
--Sales Return
(select case when SUM(SR.quantity)is null then 0 else SUM(SR.quantity)end as Q3 from salesreturn SR
where PC.PCODE=SR.prdcode )as SRRQTY,
--Purchase Return
(select case when SUM(PR.quantity)is null then 0 else SUM(PR.quantity)end as Q3 from purchasereturn PR
where PC.PCODE=PR.prdcode )as PRRQTY
from producttable PC
group by PC.PCODE
)t
where PCODE=t.PRODUCTCODE
--Available
update #AvailableQty set Aqty=((CCqty-GIQty)+(GOQty))
--((OOQTY+QTY+SRRQTY+PYQTY)-(STQTY+PRRQTY))
from
(
select PC.PCODE as PRODUCTCODE,
--GoodsIn
(select case when SUM(GI.quantity)is null then 0 else SUM(GI.quantity) end as Q from goodsin GI
where PC.PCODE=GI.productcode) as GIQty,
--GoodsOut
(select case when SUM(GUT.quantity)is null then 0 else SUM(GUT.quantity) end as Q from goodsout GUT
where PC.PCODE=GUT.productcode ) as GOQty,
--Current Stock
(select CS.Cqty as Q from #CloseStock CS
where PC.PCODE=CS.pcode ) as CCqty
from producttable PC
group by PC.PCODE
)t
where prcode=t.PRODUCTCODE
insert into #ProductDetails
select PCODE,[DESCRIPTION],BRAND,CATEGORY,DEPARTMENT,MRP,SALERATE,0,0
from producttable
update #ProductDetails set CurrentQuantity=pcqty,AvailableQty=acqty
from
(
select pt.ProductName as pn,cs.Cqty as pcqty,ac.Aqty as acqty from #ProductDetails pt
inner join #CloseStock cs on pt.ProductName=cs.pcode
inner join #AvailableQty ac on pt.ProductName=ac.prcode
)t
where ProductName=t.pn
select * from #ProductDetails
end
This not working when productable in pcode field add ant (-.&) this kind of symbol i want to even allow in pcode field,
please help me how i can allow any symbol in query
(problem with this code)
update #AvailableQty set Aqty=((CCqty-GIQty)+(GOQty))
from
(
select PC.PCODE as PRODUCTCODE,
--GoodsIn
(select case when SUM(GI.quantity)is null then 0 else SUM(GI.quantity) end as Q from goodsin GI
where PC.PCODE=GI.productcode) as GIQty,
--GoodsOut
(select case when SUM(GUT.quantity)is null then 0 else SUM(GUT.quantity) end as Q from goodsout GUT
where PC.PCODE=GUT.productcode ) as GOQty,
--Current Stock
(select CS.Cqty as Q from #CloseStock CS
where PC.PCODE=CS.pcode ) as CCqty
from producttable PC
group by PC.PCODE
)t
where prcode=t.PRODUCTCODE
The problem here isn't with the symbols you're using, it's that you are assigning the value of a subquery to a single column in the result set. For example:
(select case when SUM(PR.quantity)is null then 0 else SUM(PR.quantity)end as Q3 from purchasereturn PR
where PC.PCODE=PR.prdcode )as PRRQTY
Note that this is allowed only if the subquery returns only a single value; otherwise, we don't know which of the values should be assigned to the column.
If you expect your subqueries to return multiple values and you just want an arbitrary one, use TOP 1 in the subquery to only return 1 value. Otherwise, you'll have to debug each subquery to figure out which returns multiple results and is causing the issue.

Resources