how to add a conditional statement after calculating two fields in SQL - sql

I need to output data based on a condition to limit output to usable data. Need help with understanding and optimizing query and removing redundancies for my SQL query
I tried conditions in the where statement, but that is giving me an error. Also tried adding a Having statement, which did not work either.
select
o2.car_move_id as Carrier_Code,
o1.early_shpdte,
o1.prtnum,
shpsts,
(o1.host_ordqty / o3.untqty) as Order_pallets,
(
select
count(i3.untqty)
from
INVENTORY_PCKWRK_VIEW i3
inner join prtftp_dtl i4 on i3.prtnum = i4.prtnum
where
i3.invsts like 'U'
and i3.wrkref is null
and i3.prtnum = o1.prtnum
and i3.untqty = i4.untqty
and i4.uomcod like 'PL'
and i4.wh_id like 'RX'
) as full_pallets,
(
select
count(i5.untqty)
from
INVENTORY_PCKWRK_VIEW i5
inner join prtftp_dtl i6 on i5.prtnum = i6.prtnum
where
i5.invsts like 'U'
and i5.wrkref is null
and i5.prtnum = o1.prtnum
and i5.untqty < i6.untqty
and i5.prtnum = i6.prtnum
and i6.uomcod like 'PL'
and i6.wh_id like 'RX'
) as Partial_pallets
from
ord_line o1
inner join SHIP_STRUCT_VIEW o2 on o1.ordnum = o2.ship_id
inner join prtftp_dtl o3 on o1.prtnum = o3.prtnum
where
o2.ship_id like '0%'
and shpsts in ('R', 'I')
and o1.non_alc_flg = 0
and o3.wh_id like 'RX'
and o3.uomcod like 'PL'
order by
full_pallets asc,
o1.early_shpdte asc
I want to only output the query where order_pallets > Full_Pallets. not sure where I can add this condition in my query.

The items on the SELECT list of an SQL query are logically processed after the WHERE clause (as explained in this answer), that's why you cannot reference column aliases in the WHERE clause. You will need to use a subselect to accomplish what you want:
select * from (
select
o2.car_move_id as Carrier_Code,
o1.early_shpdte,
o1.prtnum,
shpsts,
-- the rest of your current query
) t
where t.order_pallets > t.Full_Pallets

You can enclose your entire query in
with x as ()
Then select from it:
select * from x
where x.order_pallets > x.full_pallets
This will save you from having to maintain multiple subqueries for the same information.

Related

Optimizing SQL Query speed

I am trying to optimize my SQL query below as I am using a very old RDMS called firebird. I tried rearranging the items in my where clause and removing the order by statement but the query still seems to take forever to run. Unfortunately firebird doesn't support Explain Execution Plan Functionalities and therefore I cannot identify the code that is holding up the query.
select T.veh_reg_no,T.CON_NO, sum(T.pos_gpsunlock) as SUM_GPS_UNLOCK,
count(T.pos_gpsunlock) as SUM_REPORTS, contract.con_name
from
(
select veh_reg_no,CON_NO,
case when pos_gpsunlock = upper('T') then 1 else 0 end as pos_gpsunlock
from vehpos
where veh_reg_no in
( select regno
from fleetvehicle
where fleetno in (97)
) --DS5
and pos_timestamp > '2022-07-01'
and pos_timestamp < '2022-08-01'
) T
join contract on T.con_no = contract.con_no
group by T.veh_reg_no, T.con_no,contract.con_name
order by SUM_GPS_UNLOCK desc;
If anyone can help it would be greatly appreciated.
I'd either comment out some of the sub-queries or remove a join or aggregation and see if that improves it. Once you find the offending code maybe you can move it or re-write it. I know nothing of Firebird but I'd approach that query with the below code, wrapping the aggregation outside of the joins and removing the "Where in" clause.
If nothing works can you create an aggregation table or pre-filtered table and use that?
select
x.*
,sum(case when x.pos_gpsunlock = upper('T') then 1 else 0 end) as SUM_GPS_UNLOCK
,count(*) as SUM_REPORTS
FROM (
select
a.veh_reg_no
,a.pos_gpsunlock
,a.CON_NO
,c.con_name
FROM vehpos a
JOIN fleetvehicle b on a.veg_reg_no = b.reg_no and b.fleetno = 97 and b.pos_timestamp between '222-07-01' and '2022-08-01'
JOIN contract c on a.con_no = contract.con_no
) x
Group By....
This might help by converting subqueries to joins and reducing nesting. Also an = instead of IN() operation.
select vp.veh_reg_no,vp.con_no,c.con_name,
count(*) as SUM_REPORTS,
sum(case when pos_gpsunlock = upper('T') then 1 else 0 end) as SUM_GPS_UNLOCK
from vehpos vp
inner join fleetvehicle fv on fv.fleetno = 97 and fv.regno = vp.veh_reg_no
inner join contract c on vp.con_no = c.con_no
where vp.pos_timestamp >= '2022-07-01'
and vp.pos_timestamp < '2022-08-01'
group by vp.veh_reg_no, vp.con_no, c.con_name

Single Query To Select Based On Parameters If Or not supplied

I have used SqlDataSource and have a select query based on District & Zone as below
SELECT a.[committee_id] memberid, a.[membername], a.[memberemail], a.[memberdesignation], a.[membercreatedby],b.districtname AS district,b.districtid,c.zone_name AS zone,c.zoneid
FROM [committee_details] a
LEFT JOIN district_master b on b.districtid=a.districtid
LEFT JOIN zone_master c on c.districtid=a.districtid and c.zoneid = a.zoneid
WHERE (a.[membercreatedby] = 'director') AND ((convert(varchar,a.districtid) LIKE '%2%') AND (convert(varchar,a.zoneid) LIKE '%25%')) ORDER BY a.[committee_id] DESC
It's an inline query. I have tried above query but not able to figure out how to Select condition based.
I want if district supplied then Select according to District, if both District & Zone supplied then Select according to both & If nothing supplied then Select All. But should be in single query. How should I do this?
First, fix your query so you are not using meaningless table aliases. Use table abbreviations! I would also drop all the square braces; they just make the query harder to write and to read.
Basically, you want comparisons with NULL in the WHERE clause. I have no idea why your sample code uses LIKE, particularly columns that appear to be numbers. Nothing in the question explains why LIKE is used for the comparison, so the idea is:
SELECT cd.committee_id as memberid, cd.membername,
cd.memberemail, cd.memberdesignation, cd.membercreatedby,
dm.districtname AS district, dm.districtid,
zm.zone_name AS zone, zm.zoneid
FROM committee_details cd LEFT JOIN
district_master dm
ON cd.districtid = dm.districtid LEFT JOIN
zone_master zm
ON zm.districtid = cd.districtid AND
zm.zoneid = cd.zoneid
WHERE cd.membercreatedby = 'director') AND
(cd.districtid = #district or #district is null) AND
(cd.zoneid = #zone or #zone is null)
ORDER BY cd.[committee_id] DESC;
If you were using LIKE, then I would phrase the logic like:
WHERE cd.membercreatedby = 'director') AND
(cast(cd.districtid as varchar(255)) like #district) AND
(cast(cd.zoneid as varchar(255)) like #zone)
And pass in the patterns as '%' when you want all values to match. This assumes that the columns in cd are not NULL. If they can be NULL, then you want an explicit comparison, as in the first example.
If I got the question right then you can use parameters and compare to the column itself if the values are not supplied or not present.
try the following:
SELECT a.[committee_id] memberid, a.[membername], a.[memberemail], a.[memberdesignation], a.[membercreatedby],b.districtname AS district,b.districtid,c.zone_name AS zone,c.zoneid
FROM [committee_details] a
LEFT JOIN district_master b on b.districtid=a.districtid
LEFT JOIN zone_master c on c.districtid=a.districtid and c.zoneid = a.zoneid
WHERE (a.[membercreatedby] = 'director')
AND b.districtname = isnull(nullif(#districtname, ''), b.districtname)
AND c.zone_name = isnull(nullif(#zone_name, ''), c.zone_name)
ORDER BY a.[committee_id] DESC

Display only result > 2

I face a problem with the result on my script.
My formula for MARGIN is ((plnamt-(ibhexc/ibhand))/plnamt)*100.
I want to display only result > 2. How to do this? Please help.
This my script:
select
a.plnitm,a.plnstr,max(a.plncdt),max(a.plnndt),max(a.plnamt),max(a.plnevt),b.idept,c.ibhand,c.ibhexc,
decimal((c.ibhexc/c.ibhand),12,4) as AVG_COST,
decimal(((((max(a.plnamt)-(c.ibhexc/c.ibhand))/(max(a.plnamt))))*100),12,4) as MARGIN
from prcpln a
inner join invmst b on a.plnitm = b.inumbr
inner join invbal c on a.plnitm = c.inumbr and a.plnstr = c.istore and c.ibhand <> 0
where a.plnstr = ''14006''
group by a.plnitm,a.plnstr,b.idept,c.ibhand,c.ibhexc
order by a.plnitm
Simplistically, for situations like this, you can take your query and put it inside a cte:
WITH q AS (
select
a.plnitm,a.plnstr,max(a.plncdt),max(a.plnndt),max(a.plnamt),max(a.plnevt),b.idept,c.ibhand,c.ibhexc,
decimal((c.ibhexc/c.ibhand),12,4) as AVG_COST,
decimal(((((max(a.plnamt)-(c.ibhexc/c.ibhand))/(max(a.plnamt))))*100),12,4) as MARGIN
from prcpln a
inner join invmst b on a.plnitm = b.inumbr
inner join invbal c on a.plnitm = c.inumbr and a.plnstr = c.istore and c.ibhand <> 0
where a.plnstr = ''14006''
group by a.plnitm,a.plnstr,b.idept,c.ibhand,c.ibhexc
)
SELECT * FROM q WHERE margin > 2
ORDER BY q.plnitm
This is similar to the advice to use HAVING - a HAVING is a "where clause that is done after a GROUP BY"
A cte is a way of taking some calculated block of data and giving it an alias that can be used just like a table. I wanted to answer this way to point out to you that queries don't have to be formed purely from tables; tables are just blocks of data (with a name), and the output from a select is also "just a block of data" that can be given a name (by use of a cte or subquery) and then used just like a table is

Is it possible to have multiple CASE in a GROUP BY that can make it so it doesn't group at all?

I'm trying to query come datas and on a spcific case I might have to group datas by multiple values. But most of the time it needs not to be grouped at all. So I'm using multiple CASE WHEN {...} inside the GROUP BY, and all the WHEN basically have the same condition. The problem is that if the condition is met, everything works fine. But if it's false, then the GROUP BY section is empty and the query returns only the first row.
I basically tried to reorganize the quesry in every way that came to my mind, nothing seemed to work, and I didn't find anything conclusive on internet.
I'm using MySql 5.7.
SELECT
{element I want to select}
FROM
{tables}
WHERE
{conditions}
GROUP BY
CASE WHEN (condition) THEN [table].[column] END,
CASE WHEN (condition) THEN [table].[column] END,
CASE WHEN (condition) THEN [table].[column] END
ORDER BY
{...}
Full query :
SELECT
tx.code,
IFNULL(hr.label,'') AS rh_label,
IFNULL(cli.label,'') AS client_label,
DATE(FROM_UNIXTIME(created.value / 1000)) AS Created,
IFNULL(item_enfant.label,'') As Parasite,
IFNULL(item_parent.label,'') As Zone,
CASE
WHEN :perWeek = 'week' THEN SUM(qte.value)
ELSE qte.value
END AS Quantite,
CEILING(DATEDIFF(DATE(FROM_UNIXTIME(created.value / 1000)), DATE(FROM_UNIXTIME(:from / 1000))) / 7) AS Weeks
FROM tx
LEFT JOIN tx_type AS tt ON tt.id = tx.tx_type_id
LEFT JOIN human_resource AS hr ON hr.id = tx.human_resource_id
LEFT JOIN client AS cli ON cli.id = tx.client_id
LEFT JOIN tx_state AS ts ON ts.id = tx.current_tx_state_id
LEFT JOIN workflow_step AS ws ON ws.id = ts.workflow_step_id
LEFT JOIN item AS item_enfant ON item_enfant.item_list_id = tx.item_list_id
JOIN item_type AS ite ON ite.id = item_enfant.item_type_id
LEFT JOIN item_meta AS qte ON qte.item_id = item_enfant.id AND qte.name = 'qtePourRapport'
LEFT JOIN item_prop AS created ON created.item_id = item_enfant.id AND created.name = 'visite.timestamp'
JOIN item AS item_parent ON item_parent.id = item_enfant.parent_item_id
JOIN item_type AS itp ON itp.id = item_parent.item_type_id
WHERE
ite.name = 'parasite' AND
item_enfant.product_id IN (:parasiteIds) AND
itp.name = 'zone' AND
item_parent.product_id IN (:zoneIds) AND
cli.id = (:clientId) AND
ws.logic_id = 600 AND
created.value BETWEEN :from AND :to AND
created.value IS NOT NULL AND qte.value IS NOT NULL
GROUP BY
CASE WHEN :perWeek = 'week' THEN item_enfant.label END, #Parasite
CASE WHEN :perWeek = 'week' THEN item_parent.label END, #Zone
CASE WHEN :perWeek = 'week' THEN CEILING(DATEDIFF(DATE(FROM_UNIXTIME(created.value / 1000)), DATE(FROM_UNIXTIME(:from / 1000))) / 7) END #Weeks
ORDER BY
Created;
I'm getting the datas of the first row alone. And I actually have no idea how to get it just not to group if the condition is not met.
You need a unique value for the aggregation or two separate queries. The simplest method might be union all:
select . . .
from t
where <conditions not to group by>
union all
select . . .
from t
where <conditions to group by>
group by . . .;
You need to be sure that each subquery returns compatible columns.
SELECT
{element I want to select}
FROM
{tables}
WHERE
{conditions}
GROUP BY
CASE WHEN (condition) THEN [table].[column] ELSE [some unique value of same data-type as column] END,
CASE WHEN (condition) THEN [table].[column] ELSE [some unique value of same data-type as column] END,
CASE WHEN (condition) THEN [table].[column] ELSE [some unique value of same data-type as column] END
ORDER BY
{...}
I guess the missing ELSE clause will evaluate to NULL. This is constant, thus all rows will be in the same group, thus there will be only one row returned for this group. To avoid grouping you need unique values over all returned rows in the combination of the grouping-elements (not in every single grouping-element as stated erlier).
EDIT
Thus the soultion from the comment might be easier: Just add another grouping-element CASE WHEN !(condition) THEN CONCAT([different elements making it unique]) END

Incorrect syntax near the keyword 'FROM' in SQL Server

Please help me in the code, I get an error
Incorrect syntax near the keyword 'FROM'
SELECT
produkt.Twr_Kod as kod,
(SELECT
ISNULL(SUM(zasoby.TwZ_Ilosc), 0) + ISNULL(SUM(trs_ilosc), 0)
FROM
cdn.TraSElem
WHERE
trs_typ = 3 AND TrS_TrEIdWydania = 0
AND TrS_DataOpe > GETDATE() AND zasoby.TwZ_TwrId = TrS_TwrId
AND zasoby.TwZ_MagId = 1
FROM
CDN.TwrZasoby as zasoby
WHERE
zasoby.Twz_TwrId = product.Twr_twrid) AS zasoby,
CONVERT(NUMERIC(10, 0), produkt.Twr_IloscMin) AS ilosc_minimalna
FROM
CDN.Towary AS product
LEFT JOIN
CDN.Towary AS produkt ON product.Twr_TwrId = produkt.Twr_TwrId
GROUP BY
product.Twr_TwrId, produkt.Twr_Kod, produkt.Twr_IloscMin
ORDER BY
kod
SQLs kinda need to look like this:
SELECT columns FROM tables_or_queries WHERE predicates GROUP BY grouping_keys ORDER BY columns
This pattern can be nested both inside the SELECT region and the FROM region:
SELECT
column1,
(SELECT columnA FROM table WHERE where_predicates) as column2,
column3
FROM
table
INNER JOIN
(SELECT columnA, columnB FROM table WHERE where_predicates) as nested_query
ON join_predicates
WHERE
where_predicates
But you can't have multiple FROM regions etc.. To get more targeted advice youre going to have to tell us what you're hoping your query does.. Right now, we can only say to sort out the basic syntax errors the compilation process is complaining about
You seem to have multiple FROM statements in a single SELECT statement. Every SELECT can have only one FROM clause. Glancing through your query, I think it's going to give you performance nightmares. Use joins instead of using inline queries. Will improve performance.
SELECT
produkt.Twr_Kod as kod,
(SELECT
ISNULL(SUM(zasoby.TwZ_Ilosc), 0) + ISNULL(SUM(trs_ilosc), 0)
**FROM
cdn.TraSElem
WHERE
trs_typ = 3 AND TrS_TrEIdWydania = 0
AND TrS_DataOpe > GETDATE() AND zasoby.TwZ_TwrId = TrS_TwrId
AND zasoby.TwZ_MagId = 1
FROM
CDN.TwrZasoby as zasoby**
WHERE
zasoby.Twz_TwrId = product.Twr_twrid) AS zasoby,
CONVERT(NUMERIC(10, 0), produkt.Twr_IloscMin) AS ilosc_minimalna
FROM
CDN.Towary AS product
LEFT JOIN
CDN.Towary AS produkt ON product.Twr_TwrId = produkt.Twr_TwrId
GROUP BY
product.Twr_TwrId, produkt.Twr_Kod, produkt.Twr_IloscMin
ORDER BY
kod