I have made a table where the material number, material text and so on have been added (selected_materials). Now I wanted to create a loop with this table so that it counts in the EKPO table how often the material number was mentioned in order to use it afterwards.
For the below code, the compiler says that WHERE <selected_material>-matnr is invalid, even though WRITE: ... <selected_material>-matnr = ... is valid.
LOOP AT selected_materials ASSIGNING <selected_material>.
DATA: lv_count TYPE i.
SELECT SINGLE COUNT(*)
From Ekpo
INTO lv_count
WHERE <selected_material>-matnr = Ekpo-matnr.
WRITE: <selected_material>-maktx, <selected_material>-matnr, <selected_material>-meins, <selected_material>-msehl, <selected_material>-ekpo_count.
NEW-LINE.
ENDLOOP.
Your WHERE condition is not correct, it has to be like this:
SELECT COUNT( * )
FROM ekpo
INTO lv_count
WHERE matnr EQ <selected_material>-matnr.
Please note SINGLE is also removed, as (I guess) the question is, how many entries are in EKPO for the material number (SELECT COUNT( * )...) and not, if there is at least one at all (SELECT SINGLE COUNT( * )...)
One more thing: In the WRITE, you display the field: -ekpo_count, however in the SELECT COUNT( * ) lv_count is used.
Before ABAP 7.40 SP 05, the operand on the left of the operator had to be a column name (cf József Szikszai answer).
From ABAP 7.40 SP 05, there is the "strict mode" which allows more complex SQL features. The strict mode is mainly activated by using the # character before the names of ABAP variables, like this:
SELECT COUNT( * )
FROM ekpo
WHERE #lv_matnr = ekpo~matnr
INTO #lv_count .
NB: if a column name is used on the right side the "table alias" is required (ekpo~matnr) and in strict mode the INTO clause is to be positioned at the end (tested with ABAP 7.52)
Related
I am new to SQL and it seems not to be the same as tradition coding. Anyways, I am trying to figure out why my results end up empty but only with the first AND statement. If I remove this statement, the code works. The syntax seems correct. What I am trying to do is match channel names with 'P' and 'HDP' at the end of the columns and not match channel numbers. Maybe I am wrong on the syntax. Any help on this matter would be appreciated. Also, I am using Microsoft SQL Server Management Studio 2012.
How the results should look:
SELECT a.ChannelNumber AS "Standard Channel",
a.DisplayName AS "Standard Name",
b.ChannelNumber AS "HD Channel",
b.DisplayName AS "HD Name"
FROM CHANNEL a CROSS JOIN CHANNEL b
WHERE b.ChannelNumber <> a.ChannelNumber
AND b.DisplayName = a.DisplayName /*this is what is giving me problems*/
AND RIGHT(b.DisplayName, 3) LIKE '%HDP'
AND RIGHT(a.DisplayName, 1) LIKE '%P';
Ultimately you want things like AETVP and AETVHDP to be "equal". This doesn't seem like a use case for a Cross Join. You can break this down with a CTE.
First you'll define your HD channels, then your Standard Channels. In each of those blocks you can get the core part of the channel's name (the part without the P or HDP). Then join those together on the CoreName. This will enable us to join AETV to AETV
WITH HdChannels
AS (
SELECT *
,CoreName = left(DisplayName, len(DisplayName) - len('HDP'))
FROM Channel
WHERE displayName LIKE '%HDP'
)
,StdChannels
AS (
SELECT *
,CoreName = left(DisplayName, len(DisplayName) - len('P'))
FROM Channel
WHERE displayName LIKE '%P'
AND displayName NOT LIKE '%HDP'
)
SELECT std.ChannelNumber AS [Standard Channel]
,std.DisplayName AS [Standard Name]
,hd.ChannelNumber AS [HD Channel]
,hd.DisplayName AS [HD Name]
FROM HdChannels hd
INNER JOIN StdChannels std ON std.CoreName = hd.CoreName
To answer your question,
"Give reasons as to why my AND clause returns an empty string in SQL"
it's because you've declared a.DisplayName = b.DisplayName in the WHERE. And that can't be the case according to the picture of the output you've linked to because the Display names are spelled differently.
The only difference between standard and HD tables is HD tables end with "HDP". Standard tables never end with "HDP", though they do end with a "P".
In the absence of sample data, I've included the most basic example I could think of using a temp table.
DECLARE #CHANNEL TABLE(ChannelNumber int, DisplayName varchar(100))
INSERT INTO #CHANNEL VALUES
(3, 'ABCP'), (25, 'ABCHDP')
SELECT a.ChannelNumber AS "Standard Channel",
a.DisplayName AS "Standard Name",
b.ChannelNumber AS "HD Channel",
b.DisplayName AS "HD Name"
FROM #CHANNEL a CROSS JOIN #CHANNEL b
WHERE LEFT(a.DisplayName, LEN(a.DisplayName) - 1) + 'HDP' = b.DisplayName
AND a.DisplayName NOT LIKE '%HDP'
AND b.DisplayName LIKE '%HDP'
AND a.ChannelNumber <> b.ChannelNumber
Produces output:
Standard Channel Standard Name HD Channel HD Name
3 ABCP 25 ABCHDP
The algorithm identifies standard channels (NOT LIKE '%HDP') and HD channels (LIKE '%HDP') on the left and right sides of the CROSS JOIN.
Notice in your code you put: AND RIGHT(b.DisplayName, 3) LIKE '%HDP'... it is unnecessary to specify the RIGHT function with a length of chars- when you indicate the end of the string using LIKE '%HDP'.
LEFT(a.DisplayName, LEN(a.DisplayName) - 1) + 'HDP' cuts off the last char of the Standard Channel's DisplayName (which is always a 'P' by it's naming convention) and concatenates 'HDP' at the end of the result. This is compared to the format for HD channels which always end with 'HDP'.
When the conditions match you get a row of data.
Looking at the filtering conditions- you can see that a.DisplayName can never equal b.DisplayName
We use a housing software based on ColdFusion and uses SQL to create database functions in our reports. I am working on a custom report to try to subtract one column from another: more specifically subtract COUNT_OF_INSPECTION_TYPE from OCCUPANCY. The OCCUPANCY column is based on the following code:
CONVERT(INT
, CASE
WHEN tblHalls.HALLNAME = 'Hall1' THEN 198
WHEN tblHalls.HALLNAME = 'Hall2' THEN 430
WHEN tblHalls.HALLNAME = 'Hall3' THEN 333
END
)
When I try a new function OCCUPANCY - COUNT_OF_INSPECTION_TYPE, I get an error:
Unable to invoke CFC - Error Executing Database Query. [Macromedia][SQLServer JDBC Driver][SQLServer]Invalid column name
'OCCUPANCY'.
I'm not sure if I am explaining this right. I'd appreciate any help you can offer.
You cannot create an alias and use it in another calculation, at the same level, because the alias is not defined yet. Either repeat the whole CASE ... END statement in the calculation (less desirable) OR use another option, such as a CTE, derived table, APPLY operator, etcetera.
Wrapping your existing SELECT in a CTE is probably one of the simplest options:
;WITH cte
AS
(
-- Your current SELECT statement goes here
SELECT
CONVERT(INT
, CASE
WHEN tblHalls.HALLNAME = 'Hall1' THEN 198
WHEN tblHalls.HALLNAME = 'Hall2' THEN 430
WHEN tblHalls.HALLNAME = 'Hall3' THEN 333
END
)
AS OCCUPANCY
, tblHalls.COUNT_OF_INSPECTION_TYPE
, ... (Other Columns)
FROM tblHalls
)
-- Now use the alias in a calculation
SELECT cte.*
, cte.OCCUPANCY - cte.COUNT_OF_INSPECTION_TYPE AS SomeAliasForThisCol
FROM cte
Side note, since the CASE statement does not define an ELSE condition, the result of the calculation will be NULL if none of HallName's match. If that is not desirable, consider setting a default.
I have a very peculiar case. My ASP.NET page calls a stored procedure of ours that performs a Full-Text Search query on our database. Some of the commonly searched strings include an ampersand because a few brands of our products (well-known brands, too) have an & in their name.
It turns out that in a certain case I get no results unless I escape the ampersand (\&), and in a certain other case I get no results only if I escape the ampersand.
I don't know if this is relevant, but (without giving out the brand names) one ends in &b and the other one in &c.
Is it possible that these strings (&b or &c) have some special meaning of their own? And that by escaping them I'm actually passing a special string to T-SQL?
EDIT
Additional info: after further testing, I proved that the error is in the stored procedure itself. Calling it with & or \& yields different results.
I'll try to post selected parts of the stored procedures. I won't post it all, because most of it isn't really relevant.
The vParamBuca parameter is the one that causes the troubles. Values could be 'word&letter' or word\&letter.
SET #ricercaA = '''FORMSOF(INFLECTIONAL,"' +
REPLACE(LTRIM(RTRIM(#vParamBuca)),' ', '") AND FORMSOF(INFLECTIONAL,"') + '")'''
The variable #ricercaA is then used to create the query string:
[...]
FROM Products AS FT_TBL
LEFT OUTER JOIN CONTAINSTABLE (Products, Sign1, '+ #ricercaA + ') AS ColSign1_0 ON FT_TBL.ID = ColSign1_0.[KEY]
LEFT OUTER JOIN CONTAINSTABLE (Products, ManufacturerAdditionalText, '+ #ricercaA + ') AS ColManufacturerAdditionalText_0 ON FT_TBL.ID = ColManufacturerAdditionalText_0.[KEY]
LEFT OUTER JOIN CONTAINSTABLE (Products, ManufacturerForSearch, '+ #ricercaA + ') AS ColManufacturer_0 ON FT_TBL.ID = ColManufacturer_0.[KEY]
LEFT OUTER JOIN CONTAINSTABLE (Products, TuttaLaRiga, '+ #ricercaA + ') AS ColTuttaLaRiga_0 ON FT_TBL.ID = ColTuttaLaRiga_0.[KEY]
[...]
EDIT 2
Many thanks to #srutzky for pointing me in the right direction! In the meanwhile, I also found a data inconsistency where one of the brands with the & in its name was modified not to have the &, and the other one wasn't modified (bottom line, my current problem is caused by that: a partial fix that was made by someone in the past).
Anyway, back on track. Now I understand that the & character in the CONTAINSTABLE function is treated as a logical AND (non bitwise).
I still need a solution for that. This answer gives a solution that doesn't work for me (the conditions are not the same as mine). How could I perform a CONTAINSTABLE search for a string with an ampersand in it? Preferably without having to transform the ampersand to another safe character?
The odd behavior you are seeing is most likely due to the CONTAINS and CONTAINSTABLE functions (both used with SQL Server's Full Text Search feature) using the ampersand ( & ) character as equivalent to the AND operator. The following statement is taken from the documentation for CONTAINS:
The ampersand symbol (&) may be used instead of the AND keyword to represent the AND operator.
There is no mention of there being any escape character for it (and a back-slash isn't typically an escape character in SQL anyway).
UPDATE
Based on the information now provided in "Edit 2" of the Question, and additional research, I would say that you do not need to escape anything. It seems that putting the search phrases in double-quotes (as a result of using FORMSOF) treats the & as either a literal or a word-breaker, depending on the values on both sides of the &. Try the following examples:
DECLARE #Term NVARCHAR(100);
SET #Term = N'bob&sally'; -- 48 rows
--SET #Term = N'bob\&sally'; -- 48 rows
--SET #Term = N'r&f'; -- 4 rows
--SET #Term = N'r\&f'; -- 24 rows
SET #Term = N'FORMSOF(INFLECTIONAL,"' + #Term + '")';
SELECT * FROM sys.dm_fts_parser(#Term, 1033, 0, 0);
SELECT * FROM sys.dm_fts_parser(#Term, 1033, 0, 1);
SELECT * FROM sys.dm_fts_parser(#Term, 1033, NULL, 0);
SELECT * FROM sys.dm_fts_parser(#Term, 1033, NULL, 1);
The results for bob&sally and bob\&sally are the same, and in both cases bob and sally are separated and never combined into a single exact-match string.
The results between r&f and r\&f, however, are not the same. r&f is only ever treated as a single, exact-match string because r and f alone are not known words. On the other hand, adding in the back-slash separates the two letter since \ is a word-breaker, in which case you get both r and f.
Given that you stated in the Update that you have "data inconsistency, where one of the brands with the "&" in its name was modified not to have the "&", and the other one wasn't", I suspect that when you do not add in the \ character you get the brand that was not modified (since it is an exact match for the full term). But when you do add in the \ character, then you get the brand that was modified to have the & removed, since you are now searching on both pieces, each one matching part of that brand name.
I would fix the data to be consistent: update the brand names that had the & removed to put the ampersands back in. Then when people search using & without the extra \ added, it will be an exact match. This behavior will be consisted across the data, and will not require you adding code to circumvent the natural operation of FTS, which seems to be an error-prone approach.
I have string data in the following format:
MODELNUMBER=Z12345&HELLOWORLD=WY554&GADTYPE=PLA&ID=Z-12345
/DTYPE=PLA&ID=S-10758&UN_JTT_REDIRECT=UN_JTT_IOSV
and need to extract IDs based on two conditions
Starting after a pattern &ID=
Ending till the last character or
if it hits a & stop right there.
So in the above example I'm using the following code:
SUBSTRING(MyCol,(PATINDEX('%&id=%',[MyCol])+4),(LEN(MyCol) - PATINDEX('%&id%',[MyCol])))
Essentially looking the pattern &id=% and extract string after that till end of the line. Would anyone advise on how to handle the later part of the logic ..
My current results are
Z-12345
Z-12345&UN_JTT_REDIRECT=UN_JTT_IOSV
What I need is
Z-12345
Z-12345
Try this
SUBSTRING(MyCol, (PATINDEX('%[A-Z]-[0-9][0-9][0-9][0-9][0-9]%',[MyCol])),7)
if you run into performance issues add the where clause below
-- from Mytable
WHERE [MyCol] like '%[A-Z]-[0-9][0-9][0-9][0-9][0-9]%'
maybe not the most elegant solution but it works for me.
Correct syntax of PATINDEX
Here's one example how to do it:
select
substring(d.data, s.s, isnull(nullif(e.e,0),2000)-s.s) as ID,
d.data
from data d
cross apply (
select charindex('&ID=', d.data)+4 as s
) s
cross apply (
select charindex('&', d.data, s) as e
) e
where s.s > 4
This assumes there data column is varchar(2000) and the where clause leaves out any rows that don't have &ID=
The first cross apply searches for the start position, the second one for the end. The isnull+nulliff in the actual select handles the case where & is not found and replaces it with 2000 to make sure the whole string is returned.
I have an xml column called OrderXML in an Orders table...
there is an XML XPath like this in the table...
/Order/InternalInformation/InternalOrderBreakout/InternalOrderHeader/InternalOrderDetails/InternalOrderDetail
There InternalOrderDetails contains many InternalOrderDetail nodes like this...
<InternalOrderDetails>
<InternalOrderDetail>
<Item_Number>FBL11REFBK</Item_Number>
<CountOfNumber>10</CountOfNumber>
<PriceLevel>FREE</PriceLevel>
</InternalOrderDetail>
<InternalOrderDetail>
<Item_Number>FCL13COTRGUID</Item_Number>
<CountOfNumber>2</CountOfNumber>
<PriceLevel>NONFREE</PriceLevel>
</InternalOrderDetail>
</InternalOrderDetails>
My end goal is to modify the XML in the OrderXML column IF the Item_Number of the node contains COTRGUID (like '%COTRGUID') AND the PriceLevel=NONFREE. If that condition is met I want to change the PriceLevel column to equal FREE.
I am having trouble with both creating the xpath expression that finds the correct nodes (using OrderXML.value or OrderXML.exist functions) and updating the XML using the OrderXML.modify function).
I have tried the following for the where clause:
WHERE OrderXML.value('(/Order/InternalInformation/InternalOrderBreakout/InternalOrderHeader/InternalOrderDetails/InternalOrderDetail/Item_Number/node())[1]','nvarchar(64)') like '%13COTRGUID'
That does work, but it seems to me that I need to ALSO include my second condition (PriceLevel=NONFREE) in the same where clause and I cannot figure out how to do it. Perhaps I can put in an AND for the second condition like this...
AND OrderXML.value('(/Order/InternalInformation/InternalOrderBreakout/InternalOrderHeader/InternalOrderDetails/InternalOrderDetail/PriceLevel/node())[1]','nvarchar(64)') = 'NONFREE'
but I am afraid it will end up operating like an OR since it is an XML query.
Once I get the WHERE clause right I will update the column using a SET like this:
UPDATE Orders SET orderXml.modify('replace value of (/Order/InternalInformation/InternalOrderBreakout/InternalOrderHeader/InternalOrderDetails/InternalOrderDetail/PriceLevel[1]/text())[1] with "NONFREE"')
However, I ran this statement on some test data and none of the XML columns where updated (even though it said zz rows effected).
I have been at this for several hours to no avail. Help is appreciated. Thanks.
if you don't have more than one node with your condition in each row of Orders table, you can use this:
update orders set
data.modify('
replace value of
(
/Order/InternalInformation/InternalOrderBreakout/
InternalOrderHeader/InternalOrderDetails/
InternalOrderDetail[
Item_Number[contains(., "COTRGUID")] and
PriceLevel="NONFREE"
]/PriceLevel/text()
)[1]
with "FREE"
');
sql fiddle demo
If you could have more than one node in one row, there're a several possible solutions, none of each is really elegant, sadly.
You can reconstruct all xmls in table - sql fiddle demo
or you can do your updates in the loop - sql fiddle demo
This may get you off the hump.
Replace #HolderTable with the name of your table.
SELECT T2.myAlias.query('./../PriceLevel[1]').value('.' , 'varchar(64)') as MyXmlFragmentValue
FROM #HolderTable
CROSS APPLY OrderXML.nodes('/InternalOrderDetails/InternalOrderDetail/Item_Number') as T2(myAlias)
SELECT T2.myAlias.query('.') as MyXmlFragment
FROM #HolderTable
CROSS APPLY OrderXML.nodes('/InternalOrderDetails/InternalOrderDetail/Item_Number') as T2(myAlias)
EDIT:
UPDATE
#HolderTable
SET
OrderXML.modify('replace value of (/InternalOrderDetails/InternalOrderDetail/PriceLevel/text())[1] with "MyNewValue"')
WHERE
OrderXML.value('(/InternalOrderDetails/InternalOrderDetail/PriceLevel)[1]', 'varchar(64)') = 'FREE'
print ##ROWCOUNT
Your issue is the [1] in the above.
Why did I put it there?
Here is a sentence from the URL listed below.
Note that the target being updated must be, at most, one node that is explicitly specified in the path expression by adding a "[1]" at the end of the expression.
http://msdn.microsoft.com/en-us/library/ms190675.aspx
EDIT.
I think I've discovered the the root of your frustration. (No fix, just the problem).
Note below, the second query works.
So I think the [1] is some cases is saying "only ~~search~~ the first node".....and not (as you and I were hoping)...... "use the first node..after you find a match".
UPDATE
#HolderTable
SET
OrderXML.modify('replace value of (/InternalOrderDetails/InternalOrderDetail/PriceLevel/text())[1] with "MyNewValue001"')
WHERE
OrderXML.value('(/InternalOrderDetails/InternalOrderDetail/PriceLevel[text() = "NONFREE"])[1]', 'varchar(64)') = 'NONFREE'
/* and OrderXML.value('(/InternalOrderDetails/InternalOrderDetail/Item_Number)[1]', 'varchar(64)') like '%COTRGUID' */
UPDATE
#HolderTable
SET
OrderXML.modify('replace value of (/InternalOrderDetails/InternalOrderDetail/PriceLevel/text())[1] with "MyNewValue002"')
WHERE
OrderXML.value('(/InternalOrderDetails/InternalOrderDetail/PriceLevel[text() = "FREE"])[1]', 'varchar(64)') = 'FREE'
Try this :
;with InternalOrderDetail as (SELECT id,
Tbl.Col.value('Item_Number[1]', 'varchar(40)') Item_Number,
Tbl.Col.value('CountOfNumber[1]', 'int') CountOfNumber,
case
when Tbl.Col.value('Item_Number[1]', 'varchar(40)') like '%COTRGUID'
and Tbl.Col.value('PriceLevel[1]', 'varchar(40)')='NONFREE'
then 'FREE'
else
Tbl.Col.value('PriceLevel[1]', 'varchar(40)')
end
PriceLevel
FROM (select id ,orderxml from demo)
as a cross apply orderxml.nodes('//InternalOrderDetail')
as
tbl(col) ) ,
cte_data as(SELECT
ID,
'<InternalOrderDetails>'+(SELECT ITEM_NUMBER,COUNTOFNUMBER,PRICELEVEL
FROM InternalOrderDetail
where ID=Results.ID
FOR XML AUTO, ELEMENTS)+'</InternalOrderDetails>' as XML_data
FROM InternalOrderDetail Results
GROUP BY ID)
update demo set orderxml=cast(xml_data as xml)
from demo
inner join cte_data on demo.id=cte_data.id
where cast(orderxml as varchar(2000))!=xml_data;
select * from demo;
SQL Fiddle
I have handled following cases :
1. As required both where clause in question.
2. It will update all <Item_Number> like '%COTRGUID' and <PriceLevel>= NONFREE in one
node, not just the first one.
It may require minor changes for your data and tables.