Mysql - Improving consultation query in 'group by' - sql

I have a query with 'group by':
SELECT date_audience,
Sum( If( quality_apuration = '1', 1, 0 ) ) AS very_good,
Sum( If( quality_apuration = '2', 1, 0 ) ) AS good,
Sum( If( quality_apuration = '3', 1, 0 ) ) AS bad,
Sum( If( quality_apuration = '4', 1, 0 ) ) AS no_apuration,
Count(quality_apuration) AS total
FROM pp_base
WHERE date_audience >= '2011-01-01' AND date_audience <= '2011-02-28'
GROUP BY date_audience ORDER BY date_audience ASC
Where to return the following result (or see http://jsbin.com/imuru5/):
As the table X has foreign key to another table Y, eventually someone will ask to include one more item in the table Y, for example: 'exccelent', 'regular', etc. And I will also have to adjust the schedule as php $query[0]['very_good'], $query[0]['good'], $query[0]['bad'], etc, adding among other items, spending more time .
Does anyone have any idea how I can improve this query, in order to automate the results?
Thanks, Vinicius.

I can see two options.
1) Dealing with it in the application. The following query will perform neccesary aggregation in the database and return 4 rows for each date_audience (one for each value of quality_apuration).
select date_audience
,quality_apuration
,count(*)
from pp_base
where date_audience >= date '2011-01-01'
and date_audience <= date '2011-02-28'
group
by date_audience
,quality_apuration
order
by date_audience
,quality_apuration;
This is preferred when you expect changes to the quality_apuration values.
2) Dealing with it in the database. You could define a view like the following:
create or replace view pp_view as
select date_audience
,sum(case when quality_apuration = '1' then 1 else 0 end) as very_good
,sum(case when quality_apuration = '2' then 1 else 0 end) as good
,sum(case when quality_apuration = '3' then 1 else 0 end) as bad
,sum(case when quality_apuration = '4' then 1 else 0 end) as no_apuration
,count(quality_apuration) as total
from pp_base
group
by date_audience;
...from the application you would then select as follows:
select ...
from pp_view
where date_audience >= date '2011-01-01'
and date_audience <= date '2011-02-28'
order
by date_audience;
Of course, whenever you add another value for quality_apuration, you would have to modify the view definition. Still, it's better than modifying all queries.

Related

How to select count of 0s, 1s, and both 0s and 1s in a postgres table column?

Say there's a table that has columns named binary_value, name, and created_at along with the id column.
Here's the SQL Fiddle for this question: http://sqlfiddle.com/#!15/d15d1/36
What would be an efficient query to get a result like the following?
ones_count | zeros_count | total
3 | 1 | 4
So far, I've got:
with cte2(count_type, counted) as (
with cte as (
select binary_value,
sum(case when binary_value = 1 then 1 else 0 end) as ones_count,
sum(case when binary_value = 0 then 1 else 0 end) as zeros_count
from infos
where name = 'me'
and created_at >= '2020-03-10 21:13:01.319677'
and created_at <= '2020-03-10 21:13:01.619677'
group by binary_value
)
select 'ones_count', ones_count from cte where binary_value = 1
union
select 'ones_count', zeros_count from cte where binary_value = 0
union
select 'total', sum(ones_count + zeros_count) as total from cte
)
select * from cte2;
Which gives it in column form:
count_type | counted
ones_count | 1
total | 4
ones_count | 3
How can we get the result in a row? Perhaps there's a different approach altogether than Common Table Expression? I'm starting to look at crosstab, which is postgres-specific, and so wondering if all this is overkill.
Including DDL and data here, too:
create table infos (
id serial primary key,
name character varying not null,
binary_value integer not null,
created_at timestamp without time zone not null
)
insert into infos ("binary_value", "name", "created_at") values
(1, 'me', '2020-03-10 21:13:01.319677'),
(1, 'me', '2020-03-10 21:13:01.419677'),
(0, 'me', '2020-03-10 21:13:01.519677'),
(1, 'me', '2020-03-10 21:13:01.619677');
I think you just want conditional aggregation:
select count(*) filter (where binary_value = 0) as num_0s,
count(*) filter (where binary_value = 1) as num_1s,
count(*)
from infos
where name = 'me' and
created_at >= '2020-03-10 21:13:01.319677' and
created_at <= '2020-03-10 21:13:01.619677';
The date comparison looks rather, uh, specific. I assume that you really intend a range there.
Here is a SQL Fiddle.
Note: If you are really using Postgres 9.3, then you can't use the filter clause (alas). Instead:
select sum( (binary_value = 0)::int ) as num_0s,
sum( (binary_value = 1)::int ) as num_1s,
count(*)
from infos
where name = 'me' and
created_at >= '2020-03-10 21:13:01.319677' and
created_at <= '2020-03-10 21:13:01.619677';
Also, if you wanted the results in three separate rows, a simpler query is:
select binary_value, count(*)
from infos
where name = 'me' and
created_at >= '2020-03-10 21:13:01.319677' and
created_at <= '2020-03-10 21:13:01.619677'
group by grouping sets ( (binary_value), () );
Much simpler:
select
sum(case when binary_value = 1 then 1 else 0 end) as ones_count,
sum(case when binary_value = 0 then 1 else 0 end) as zeroes_count,
count(*) as total
from infos

Sql out put which come into multiple row converted to single row

This query give multiple row which needs to be shown in single row. Please help.
SELECT blng_serv_code, (COUNT (blng_serv_code)) AS total ,
DECODE (package_trx_yn, 'Y', 'PKG', 'N', 'NPKG') pkg_status FROM bl_patient_charges_folio
WHERE operating_facility_id = 'MC'
AND trx_date >= TO_DATE ('10/10/2019 00:00:00', 'MM/DD/YYYY HH24:MI:SS')AND blng_serv_code = 'LBSB000015'
GROUP BY blng_serv_code, package_trx_yn
If you want the value in a single row, leave out the package status:
SELECT blng_serv_code, COUNT(*) AS total
FROM bl_patient_charges_folio
WHERE operating_facility_id = 'MC' AND
trx_date >= DATE '2019-10-10' AND
blng_serv_code = 'LBSB000015'
GROUP BY blng_serv_code;
If you do want the package status, then you need to explain the logic for including it "on a single row".
EDIT:
It sounds like you want the values in separate columns:
SELECT blng_serv_code, COUNT(*) AS total,
SUM(CASE WHEN package_trx_yn = 'Y' THEN 1 ELSE 0 END) as pkg_cnt,
SUM(CASE WHEN package_trx_yn = 'N' THEN 1 ELSE 0 END) as npkg_cnt
FROM bl_patient_charges_folio
WHERE operating_facility_id = 'MC' AND
trx_date >= DATE '2019-10-10' AND
blng_serv_code = 'LBSB000015'
GROUP BY blng_serv_code;

How to improve slow sql query with aggregate functions

I want to show top ten customers,sales,margin where customers is registred during this accounting year. The query takes about 65seconds to run and it is not accepted :-(
As you may see i am not good at sql and will be very happy for help to improve the query.
SELECT Top 10
AcTr.R3, Actor.Nm,
SUM(CASE WHEN AcTr.AcNo<='3999' THEN AcAm*-1 ELSE 0 END) AS Sales ,
SUM(AcAm*-1) AS TB
FROM AcTr, Actor
WHERE (Actor.CustNo = AcTr.R3) AND
(Actor.CustNo <> '0') AND
(Actor.CreDt >= '20180901') AND
(Actor.CreDt <= '20190430') AND
AcTr.AcYr = '2018' AND
AcTr.AcPr <= '8' AND
AcTr.AcNo>='3000' AND
AcTr.AcNo <= '4999'
GROUP BY AcTr.R3, Actor.Nm
ORDER BY Sales DESC
Welcome to the community. You have a good start, but future, it is more helpful if you can provide (as commented), the CREATE table declarations so users know the actual data types. Not always required, but helps.
As for your query layout, it is more common to show the JOIN syntax instead of WHERE showing relations between tables, but that comes in time and practice.
Indexes help and should be based on a combination of both WHERE/JOIN criteria AND Grouping fields. Also, if fields are numeric, then do not 'quote' them, just leave as numbers. For example, your AcYr, AcPr, AcNo. I would think that an account number really would be a string value vs number for accounting purposes.
I would suggest the following indexes on your tables
Table Index
Actr ( AcYr, AcPr, AcNo, R3 )
Actor ( CustNo, CreDt )
The Actr table I have the filtering criteria first and the R3 last to help optimize the GROUP BY. The Actor table by the customer number, then the CreDt (Create date??), and is it really a string, or is it a date field? If so, the date criteria would be something like '2018-09-01' and '2019-04-30'
select TOP 10
Actor.Nm,
PreSum.Sales,
PreSm.TB
from
( select
R3,
SUM(CASE WHEN AcTr.AcNo <= '3999'
THEN AcAm * -1 ELSE 0 END) AS Sales,
SUM( AcAm * -1) AS TB
from
Actr
where
AcTr.AcYr = 2018
AND AcTr.AcPr <= 8
AND AcTr.AcNo >= '3000'
AND AcTr.AcNo <= '4999'
GROUP BY
AcTr.R3 ) PreSum
JOIN Actor
on PreSum.R3 = Actor.CustNo
AND Actor.CustNo <> 0
AND Actor.CreDt >= '20180901'
AND Actor.CreDt <= '20190430'
order by
Sales DESC
Per latest inquiry / comment, wanting by year comparison and getting rid of the top 10 performers per a given time period.
select
Actor.Nm,
PreSum.Sales2018,
PreSum.Sales2019,
PreSum.TB2018,
PreSum.TB2019
from
( select
AcTr.R3,
SUM(CASE WHEN AcTr.AcYr = 2018
AND AcTr.AcNo <= '3999'
THEN AcAm * -1 ELSE 0 END) AS Sales2018,
SUM(CASE WHEN AcTr.AcYr = 2019 AND AcTr.AcNo <= '3999'
THEN AcAm * -1 ELSE 0 END) AS Sales2019,
SUM( CASE WHEN AcTr.AcYr = 2018
THEN AcAm * -1 else 0 end ) AS TB2018
SUM( CASE WHEN AcTr.AcYr = 2019
THEN AcAm * -1 else 0 end ) AS TB2019
from
Actr
where
AcTr.AcYr IN ( 2018, 2019 )
AND AcTr.AcPr <= 8
AND AcTr.AcNo >= '3000'
AND AcTr.AcNo <= '4999'
GROUP BY
AcTr.R3 ) PreSum
JOIN Actor
on PreSum.R3 = Actor.CustNo
AND Actor.CustNo <> 0
AND Actor.CreDt >= '20180901'
AND Actor.CreDt <= '20190430'
order by
Sales DESC

How do I properly group by case using a CTE? I am getting an incomplete result

below is my SQL query, where I am trying to create a stacked bar chart where different attribute values are grouped by date ranges based off the creation date. Despite my dataset having many values for each combination of attribute and date values, my query only returns a single row, with the range '46-90' broken out by attribute number.
I have looked at some related articles to this query type and I can't find what I am missing (I'm guessing this is one of those times where I've been looking at this too long and a minor little detail is escaping me). Any insight would be greatly appreciated :)
With CTE As(
SELECT obj_createDate, obj_att,
CASE when DATEDIFF(dd, obj_createdate, getDate()) <= 45 then '<45'
when DATEDIFF(dd, obj_createdate, getDate()) > 45
AND DATEDIFF(dd, obj_createdate, getDate()) <= 90 then '46-90'
when DATEDIFF(dd, obj_createdate, getDate()) > 90 then '>90'
end AS DateRange
FROM DEMO_OBJECT
WHERE DEMO_OBJECT.obj_ot_id = 24
AND DEMO_OBJECT.obj_resolveddate IS NULL
GROUP BY obj_att, obj_createDate
)
SELECT
DateRange,
COUNT(*) Total,
sum(case when obj_att = '1' then 1 else 0 end) '1',
sum(case when obj_att = '2' then 1 else 0 end) '2',
sum(case when obj_att = '3' then 1 else 0 end) '3',
sum(case when obj_att = '4' then 1 else 0 end) '4',
sum(case when obj_att = '5' then 1 else 0 end) '5'
FROM CTE
GROUP BY DateRange;

Convert 2 rows with multiple columns into 2 columns with multiple rows

I often run ad-hoc queries in SQL Server 2005/2008 where I would like to convert two rows in multiple columns into multiple rows having only two columns.
Given a query like this:
SELECT
SUM(CASE WHEN created_at IS NOT NULL THEN 1 END) AS 'TOTAL'
, SUM(CASE WHEN created_at > '2013-07-15' THEN 1 END) AS 'CREATED W/I LAST YEAR'
, SUM(CASE WHEN updated_at > '2013-07-15' THEN 1 END) AS 'MODIFIED W/I LAST YEAR'
, SUM(CASE WHEN updated_at < '2011-07-15' THEN 1 END) AS 'UNTOUCHED OVER 3 YEARS'
, SUM(CASE WHEN updated_at < '2009-07-15' THEN 1 END) AS 'UNTOUCHED OVER 5 YEARS'
-- , often there are more columns
FROM
mytable
WHERE
< filtering >
I would like it to display something like this:
TOTAL: 5000
CREATED W/I LAST YEAR: 500
MODIFIED W/I LAST YEAR: 1500
UNTOUCHED OVER 3 YEARS: 2000
UNTOUCHED OVER 5 YEARS: 1000
I want to keep DRY and not string together a bunch of SELECTs with UNIONs. I have never used PIVOT, UNPIVOT or CROSS APPLY. Most of the examples I have seen for UNPIVOT don't seem to apply to queries like the one above - or am I must missing something? It seems simple enough but "I'm just not getting it."
;WITH t AS (
SELECT
SUM(CASE WHEN created_at IS NOT NULL THEN 1 END) AS 'TOTAL'
, SUM(CASE WHEN created_at > '2013-07-15' THEN 1 END) AS 'CREATED W/I LAST YEAR'
, SUM(CASE WHEN updated_at > '2013-07-15' THEN 1 END) AS 'MODIFIED W/I LAST YEAR'
, SUM(CASE WHEN updated_at < '2011-07-15' THEN 1 END) AS 'UNTOUCHED OVER 3 YEARS'
, SUM(CASE WHEN updated_at < '2009-07-15' THEN 1 END) AS 'UNTOUCHED OVER 5 YEARS'
-- , often there are more columns
FROM
mytable
WHERE
< filtering >
)
SELECT name, value
FROM t
UNPIVOT(value FOR name IN (
[TOTAL]
, [CREATED W/I LAST YEAR]
, [MODIFIED W/I LAST YEAR]
, [UNTOUCHED OVER 3 YEARS]
, [UNTOUCHED OVER 5 YEARS]
)) p