CASE Statement - An expression services limit has been reached - sql

I'm getting the following error:
An expression services limit has been reached. Please look for potentially complex expressions in your query, and try to simplify them.
I'm attempting to run the below query, however it appears there is one line too many in my case statement (when i remove the "London" Line, it works perfectly) or "Scotland" for example.
I can't think of the best way to split this statement.
If i split it into 2 queries and union all, it does work. however the ELSE 'No Region' becomes a problem. Everything which is included in the first part of the query shows as "No Region" for the second part of the query, and vice versa.
(My end goal is essentially to create a list of customers per region) I can then use this as the foundation of a regional sales report.
Many Thanks
Andy
SELECT T0.CardCode, T0.CardName, T0.PostCode,
CASE
WHEN T0.PostCodeABR IN ('DG','KW','IV','PH','AB','DD','PA','FK','KY','G','EH','ML','KA','TD') THEN 'Scotland'
WHEN T0.PostCodeABR IN ('BT') THEN 'Ireland'
WHEN T0.PostCodeABR IN ('CA','NE','DH','SR','TS','DL','LA','BD','HG','YO','HX','LS','FY','PR','BB','L','WN','BL','OL') THEN 'North M62'
WHEN T0.PostCodeABR IN ('CH','WA','CW','SK','M','HD','WF','DN','HU','DE','NG','LN','S') THEN 'South M62'
WHEN T0.PostCodeABR IN ('LL','SY','LD','SA','CF','NP') THEN 'Wales'
WHEN T0.PostCodeABR IN ('NR','IP','CB') THEN 'East Anglia'
WHEN T0.PostCodeABR IN ('SN','BS','BA','SP','BH','DT','TA','EX','TQ','PL','TR') THEN 'South West'
WHEN T0.PostCodeABR IN ('LU','AL','HP','SG','SL','RG','SO','GU','PO','BN','RH','TN','ME','CT','SS','CM','CO') THEN 'South East'
WHEN T0.PostCodeABR IN ('ST','TF','WV','WS','DY','B','WR','HR','GL','OX','CV','NN','MK','PE','LE') THEN 'Midlands'
WHEN T0.PostCodeABR IN ('WD','EN','HA','N','NW','UB','W','WC','EC','E','IG','RM','DA','BR','CR','SM','KT','TW','SW') THEN 'London'
ELSE 'No Region'
END AS 'Region'
FROM [dbo].[REPS-PostcodeABBR] T0

As I mentioned in the comment, I would suggest you create a "lookup" table for the post codes, then all you need to do is JOIN to the table, and not have a "messy" and large CASE expression (T-SQL doesn't support Case (Switch) statements).
So your lookup table would look a little like this:
CREATE TABLE dbo.PostcodeRegion (Postcode varchar(2),
Region varchar(20));
GO
--Sample data
INSERT INTO dbo.PostcodeRegion (Postcode,Region)
VALUES('DG','Scotland'),
('BT','Ireland'),
('LL','Wales');
And then your query would just do a LEFT JOIN:
SELECT RPA.CardCode,
RPA.CardName,
RPA.PostCode,
COALESCE(PR.Region,'No Region') AS Region
FROM [dbo].[REPS-PostcodeABBR] RPA --T0 is a poor choice of an alias, there is no T not 0 in "REPS-PostcodeABBR"
LEFT JOIN dbo.PostcodeRegion PR ON RPA.PostCodeABR = PR.Region;
Note you would likely want to INDEX the table as well, and/or apply a UNIQUE CONSTRAINT or PRIMARY KEY to the PostCode column.

Thanks for the help... I tried multiple ways mentioned above, and they all did work, however the most efficient seemed to be this way.
Created a lookup table within SAP; This table included PostCodeFrom, PostCodeTo, PostCodeABR, Region
This would look like; TS00, TS99, TS, North M62
I then done;
SELECT OCRD.ZipCode PCLOOKUP.Region, PCLOOKUP.PostCodeABR FROM OCRD T0 LEFT OUTER JOIN PCLOOKUP ON OCRD.ZipCode >= PCLOOKUP.PostCodeFROM AND OCRD.ZipCode <= PCLOOKUP.PostCodeFrom
Basically, if the postcode is between
FROM AND To Display the abbreviation and region.

Related

the below select statement takes a long in running

This select statement takes a long time running, after my investigation I found that the problem un subquery, stored procedure, please I appreciate your help.
SELECT DISTINCT
COKE_CHQ_NUMBER,
COKE_PAY_SUPPLIER
FROM
apps.Q_COKE_AP_CHECKS_SIGN_STATUS_V
WHERE
plan_id = 40192
AND COKE_SIGNATURE__A = 'YES'
AND COKE_SIGNATURE__B = 'YES'
AND COKE_AUDIT = 'YES'
AND COKE_CHQ_NUMBER NOT IN (SELECT DISTINCT COKE_CHQ_NUMBER_DELIVER
FROM apps.Q_COKE_AP_CHECKS_DELIVERY_ST_V
WHERE UPPER(COKE_CHQ_NUMBER_DELIVER_STATUS) <> 'DELIVERED')
AND COKE_CHQ_NUMBER NOT IN (SELECT COKE_CHQ_NUMBER_DELIVER
FROM apps.Q_COKE_AP_CHECKS_DELIVERY_ST_V)
Well there are a few issues with your SELECT statement that you should address:
First let's look at this condition:
COKE_CHQ_NUMBER NOT IN (SELECT DISTINCT COKE_CHQ_NUMBER_DELIVER
FROM apps.Q_COKE_AP_CHECKS_DELIVERY_ST_V
WHERE UPPER(COKE_CHQ_NUMBER_DELIVER_STATUS) <> 'DELIVERED')
First you select DISTINCT cheque numbers with a not delivered status then you say you don't want this. Rather than saying I don't want non delivered it is much more readable to say I want delivered ones. However this is not really an issue but rather it would make your SELECT easier to read and understand.
Second let's look at your second cheque condition:
COKE_CHQ_NUMBER NOT IN (SELECT COKE_CHQ_NUMBER_DELIVER
FROM apps.Q_COKE_AP_CHECKS_DELIVERY_ST_V)
Here you want to exclude all cheques that have an entry in Q_COKE_AP_CHECKS_DELIVERY_ST_V. This makes your first DISTINCT condition redundant as whatever cheques numbers will bring back would be rejected by this second condition of yours. I do't know if Oracle SQL engine is clever enough to work out this redundancy but this could cause your slowness as SELECT distinct can take longer to run
In addition to this if you don't have them already I would recommend adding the following indexes:
CREATE INDEX index_1 ON q_coke_ap_checks_sign_status_v(coke_chq_number, coke_pay_supplier);
CREATE INDEX index_2 ON q_coke_ap_checks_sign_status_v(plan_id, coke_signature__a, coke_signature__b, coke_audit);
CREATE INDEX index_3 ON q_coke_ap_checks_delivery_st_v(coke_chq_number_deliver);
I called the index_1,2,3 for easy to read obviously not a good naming convention.
With this in place your select should be optimized to retrieve you your data in an acceptable performance. But of course it all depends on the size and the distribution of your data which is hard to control without performing specific data analysis.
looking to you code .. seems you have redundant where condition the second NOT IN implies the firts so you could avoid
you could also transform you NOT IN clause in a MINUS clause .. join the same query with INNER join of you not in subquery
and last be careful you have proper composite index on table
Q_COKE_AP_CHECKS_SIGN_STATUS_V
cols (plan_id,COKE_SIGNATURE__A , COKE_SIGNATURE__B, COKE_AUDIT, COKE_CHQ_NUMBER, COKE_PAY_SUPPLIER)
SELECT DISTINCT
COKE_CHQ_NUMBER,
COKE_PAY_SUPPLIER
FROM
apps.Q_COKE_AP_CHECKS_SIGN_STATUS_V
WHERE
plan_id = 40192
AND COKE_SIGNATURE__A = 'YES'
AND COKE_SIGNATURE__B = 'YES'
AND COKE_AUDIT = 'YES'
MINUS
SELECT DISTINCT
COKE_CHQ_NUMBER,
COKE_PAY_SUPPLIER
FROM apps.Q_COKE_AP_CHECKS_SIGN_STATUS_V
INNER JOIN (
SELECT COKE_CHQ_NUMBER_DELIVER
FROM apps.Q_COKE_AP_CHECKS_DELIVERY_ST_V
) T ON T.COKE_CHQ_NUMBER_DELIVER = apps.Q_COKE_AP_CHECKS_SIGN_STATUS_V
WHERE
plan_id = 40192
AND COKE_SIGNATURE__A = 'YES'
AND COKE_SIGNATURE__B = 'YES'
AND COKE_AUDIT = 'YES'

Select query with inner join with some pblm

SELECT DISTINCT
response_user, patient_name, ipno, department
FROM
response
INNER JOIN
patient_details ON response.response_user = patient_details.id
INNER JOIN
key_points ON response.response_key = key_points.kp_id
WHERE
department = 'CVTS'
AND MONTH(response_on) BETWEEN '12' AND '12'
AND response_key BETWEEN 146 AND 149
AND response_val = '5' OR response_val = '4'
AND kp_belongs_to = '1'
ORDER BY
patient_name ASC
I want the name of the patients in specific department
I'd suggest you to use table aliases and precede column names with those aliases, so that it is always clear which column belongs to which table.
Although it is a simple SELECT statement (nothing fancy in it), it would be a good idea to specify which database you use.
Saying that there's "some pblm" (is your keyboard broken? Why wouldn't you write "problem" instead?) is difficult to debug. Is there any error when you run that statement? If so, which one?
Furthermore, it would be nice if you formatted code you wrote; when it looks prettier, it is easier to spot the culprit. I did that, and - if everything else is OK, tables correctly joined etc., OR might be the one, i.e.
SELECT DISTINCT response_user,
patient_name,
ipno,
department
FROM response
INNER JOIN patient_details ON response.response_user = patient_details.id
INNER JOIN key_points ON response.response_key = key_points.kp_id
WHERE department = 'CVTS'
AND month(response_on)BETWEEN '12' AND '12'
AND response_key BETWEEN 146 AND 149
AND ( response_val = '5' --> OR condition should be enclosed
OR response_val = '4' --> into parenthesis
)
AND kp_belongs_to = '1'
ORDER BY patient_name ASC
See if it helps. If not, consider following guidelines I wrote to help us help you.

how to select a row if multiple values are in a related table

I am trying to make a filter to find all the stuffs made of various substances.
In the database, there is:
a stuffs table
a substances table
a stuffs_substances join table.
Now, I want to find only all the stuffs that are made of gold AND silver (not all the stuffs that contain gold and all stuffs that contain silver).
One last thing: the end user can type only a part of the substance name in the filter form field. For example he will type silv and it will show up all the stuffs made of silver.
So I made this query (not working):
select "stuffs".*
from "stuffs"
inner join "stuffs_substances" as "substances_join"
on "substances_join"."stuff_id" = "stuffs"."id"
inner join "substances"
on "substances_join"."substance_id" = "substances"."id"
where ("substances"."name" like '%silv%')
and ("substances"."name" like '%gold%')
It returns an empty array. What am I doing wrong here?
Basically, you just want aggregation:
select st.*
from "stuffs" st join
"stuffs_substances" ss join
on ss."stuff_id" = st."id" join
"substances" s
on ss."substance_id" = s."id"
where s."name" like '%silv%' or
s."name" like '%gold%'
group by st.id
having count(*) filter (where s."name" like '%silv%') > 0 and
count(*) filter (where s."name" like '%gold%') > 0;
Note that this works, assuming that stuff.id is the primary key in stuffs.
I don't understand your fascination with double quotes and long table aliases. To me, those things just make the query harder to write and to read.
if you want to do search by part of word then do action to re-run query each time user write a letter of word , and the filter part in query in case of oracle sql way
in case to search with start part only
where name like :what_user_write || '%'
or in case any part of word
where name like '%' || :what_user_write || '%'
you can also use CAB, to be sure user can search by capital or small whatever
ok, you ask about join, I test this in mysql , it work find to get stuff made from gold and silver or silver and so on, hope this help
select sf.id, ss.code, sf.stuff_name from stuffs sf, stuffs_substances ss , substances s
where sf.id = ss.id
and s.code = ss.code
and s.sub_name like 'gol%'

Left join on 2 varchar fields not working

Im having trouble joining two varchar(31) fields on sql server 2008. below is my query and it works fine
select A.CustId,A.Country,B.Country from [ACC].[dbo].[Customer] as A
left join
[Task Centre].[dbo].[CountryCodes] as B on A.Country=B.Country]
the results are as follows
CustomerA United Kingdom Null
CustomerB Ireland Ireland
CustomerC Spain Spain
CustomerD South Africa Null
South Africa and United Kingdom don't match even though they are in both dbs
I have tried to replace the space but its very slow and doesnt work. I think its something to do with the whitespace but I cant find the right command to achieve what I want.
Bear with me if I have omitted anything as Im a novice, I have also searched everywhere for an answer but cant find one that works for me.
Any help is greatly appreciated
Mike
Try to execute the following query on both tables. This will tell you if there's any "hidden" difference between the tables (for example, blank characters, line breaks, etc.):
select Country, CAST(Country AS VARBINARY) AS BinaryCountry
from [ACC].[dbo].[Customer]
where Country = 'United Kingdom'
select Country, CAST(Country AS VARBINARY) AS BinaryCountry
from [Task Centre].[dbo].[CountryCodes]
where Country = 'United Kingdom'
The column BinaryCountry should show a different value, if the content of the Country-columns are not exactly the same. If that is the case, consider correcting the error in either table. Once you've made sure that the value is the same in both tables, your join should work just fine.
Edit: The problem turns out to be a non-breaking space character in the Task Centre-table. To workaround this, use the following in your join criteria:
ON A.Country = Replace(B.Country, CHAR(0xA0), ' ')
Try this:
If any space is in value you need to trim and check
SELECT A.CustId, A.Country, B.Country
FROM [ACC].[dbo].[Customer] AS A LEFT JOIN
[Task Centre].[dbo].[CountryCodes] AS B
ON LTRIM(RTRIM(A.Country)) = LTRIM(RTRIM(B.Country))
If the difference is whitespace, then I guess this might work for you. It will be slow though, since it won't be able to use any indexes:
select A.CustId,
A.Country,
B.Country
from
(
SELECT A.CustId,
A.Country,
LOWER(REPLACE(A.Country, ' ', '')) AS CleanedCountry
FROM [ACC].[dbo].[Customer] as A
) A
left
join
(
SELECT B.Country,
LOWER(REPLACE(B.Country, ' ', '')) AS CleanedCountry
FROM [Task Centre].[dbo].[CountryCodes] as B
) B
on A.CleanedCountry=B.CleanedCountry
You would only need the lower if your collation is case sensitive...

SQL query to select multiple values

I'm creating a traffic report for my company but I'm really stuck on this piece of code..
I have a report number, and type of accidents denoted by number i.e; 1-slight, 2-serious, 3-fatal and 4-not injured.
usually an accident report contains more than one number, for instance:
a report number 2014123 has a driver with serious injury '2', passenger Not Injured '4'.
so when i fire the select query where report number=2014123 , i get two records, one with '2' injury and the other with '4'.
in the above scenario, the accident is treated as 'Serious' since it contains '2'. '4' is not considered since the passenger is not injured. injury codes with (slight, serious, fatal) are treated higher than (not injured).
How can I generate the report with serious injury (i.e; code '2') and the injury count as '2'(since two records)?
Methods I have tried:
I tried with the SQL Case statement:
(Case WHEN InjuryCode IN ('1','2','4') THEN 'Serious'
WHEN InjuryCode IN ('1','2','3','4') THEN 'FATAL'
WHEN InjuryCode IN ('1','4') THEN 'SLIGHT'
ELSE 'UNKNOWN'
END) AS ACCIDENT_STATUS
but all i got was duplicating and incorrect data.
the injury code is given preference in the following manner:
3>2>1>4
eg: an accident with contains injurycode as:
1,4- slight
2,4- serious
3,4- fatal
1,2,3,4-fatal (because 3 is the highest injury code) etc etc..
I hope this doesn't get you confused, but kindly bear with me, ,i was totally confused at the beginning, but i am actually getting the picture now, although without a solution, please help!
EDIT (the full query from the comment):
SELECT REPORTNUMBER, INJURYCODE,
(CASE WHEN InjuryCode IN ('1','2','4') THEN 'Serious'
WHEN INJURYCODE IN ('1','2','3','4') THEN 'FATAL'
WHEN INJURYCODE IN ('1','4') THEN 'SLIGHT' ELSE 'UNKNOWN'
END) AS ACCIDENT_STATUS
FROM ACCIDENTS
WHERE REPORTNUMBER=20140302
The use of injuryCode suggests that you need an Injury table (if you don't have one already). Ideally, this includes some sort of severity column that you could order by - something like this:
CREATE TABLE Injury (injuryCode CHAR(1),
severity INTEGER,
description VARCHAR(20));
INSERT INTO Injury VALUES ('1', 1, 'Slight'),
('2', 2, 'Serious'),
('3', 3, 'Fatal'),
('4', 0, 'Not Injured');
Strictly speaking, what you were attempting before was sorting based on an apparent id of the injury - the only thing ids should be used for is joins, and should otherwise be considered random/undefined values (that is, the actual value is unimportant - it's whether there's anything connected to it that's important). The fact that these happen to be numerical codes (apparently stored as character data - this is perfectly acceptable) is immaterial.
Regardless, with a sorting table defined, we can now safely query the data:
SELECT AggregateAccident.reportNumber, Injury.injuryCode, Injury.description,
AggregateAccident.victimCount
FROM (SELECT Accidents.reportNumber, MAX(Injury.severity) as severity,
COUNT(*) as victimCount
FROM Accidents
JOIN Injury
ON Injury.injuryCode = Accidents.injuryCode
GROUP BY Accidents.reportNumber) AggregateAccident
JOIN Injury
ON Injury.severity = AggregateAccident.severity
ORDER BY AggregateAccident.reportNumber
(And SQL Fiddle example. Thanks to Turophile for the skeleton. Using SQL Server, but this should work on any RDBMS).
EDIT:
If you can't create a permanent table, you can create a temporary one:
WITH Injury AS (SELECT a AS injuryCode, b AS severity, c AS description
FROM (VALUES ('1', 1, 'Slight'),
('2', 2, 'Serious'),
('3', 3, 'Fatal'),
('4', 0, 'Not Injured')) I(a, b, c))
SELECT AggregateAccident.reportNumber, Injury.injuryCode, Injury.description,
AggregateAccident.victimCount
FROM (SELECT Accidents.reportNumber, MAX(Injury.severity) as severity,
COUNT(*) as victimCount
FROM Accidents
JOIN Injury
ON Injury.injuryCode = Accidents.injuryCode
GROUP BY Accidents.reportNumber) AggregateAccident
JOIN Injury
ON Injury.severity = AggregateAccident.severity
ORDER BY AggregateAccident.reportNumber
(And updated SQL Fiddle)
The WITH clause constructs what's known as a Common Table Expression (CTE), and is basically an inline view or temporary table definition. This could also be done with a subquery, but as I reference Injury twice, using a CTE means I only have to write the contained information once (in cases where the CTE is the result of some other query, this may help performance, too). Most recent/current RDBMSs support this functionality (notably, MySQL does not), including DB2.
Something like this? (Not tested).
SELECT REPORTNUMBER,
CASE INJURYCODE
WHEN 1 THEN 'SLIGHT'
WHEN 2 THEN 'Serious'
WHEN 3 THEN 'FATAL'
ELSE 'UNKNOWN'
END ACCIDENT_STATUS,
INJURYCOUNT
FROM (
SELECT REPORTNUMBER,
MAX(CASE INJURYCODE WHEN 4 THEN 0 ELSE INJURYCODE END) INJURYCODE,
COUNT(1) INJURYCOUNT,
FROM ACCIDENTS
GROUP BY REPORTNUMBER
)
This is not an answer, but an attempt to improve your skills.
If you stored the injury code as a number like this:
0 = Not injured
1 = Slight
2 = Serious
3 = Fatal
Then you could use this clear and simple SQL:
select reportnumber, max(injurycode) as injurycode, count(*) as involved
from accidents
group by reportnumber
Here is a fiddle to illustrate it: http://sqlfiddle.com/#!2/87faf/1