SAP ABAP DCL change the cardinality of the CDS VIEW result? - abap

I have a CDS view ZCDS_XXX :
define view ZCDS_XXX
as select distinct from XXX
association [1..*] to ZCDS_YYY as _YYY ...
{
key a,
_YYY
} ...
I have a DCL ZDCL_XXX :
define role ZDCL_XXX{
grant
select
on
ZCDS_XXX
where
( _YYY.ProfitCenter ) = aspect pfcg_auth( K_PCAR_REP, PRCTR );
}
The goal is to return the entries of the CDS ZCDS_XXX
for which the user has at least one authorization from a list of associated profit centers .
Okay everything is working fine ...
BUT, when the DCL is active, The CDS view ZCDS_XXX view returns me duplicate keys
despite the DISTINCT (removes duplicates from the results list).
As if the DCL does the processing after the selection and
changed the cardinality of my result >(
I would like to know if there is a solution directly in the DCL or the CDS view to avoid having duplicates and avoid doing a SELECT DISTINCT in oSQL or delete duplicates for example if I use the CDS view in a search help.
And also if you have a link that describes its behavior or the SQL syntax in the DCL, I can't find much in the docs...
Thanks you a lot for your help !

Probably you need a left outer to one join in the CDS definition, please give it a try.
At SQL run time of a CDS selection, cardinality plays a important role if using a field from associated entity. Please read this blog for the general explanation https://blogs.sap.com/2018/08/20/cardinality-of-association-in-cds-view/

Related

How do you "unpack" a query that queries views in Google BigQuery?

Suppose that I have a view in BigQuery, e.g. [views.myview] defined as follows:
SELECT
Id AS Id,
MAX(Time) AS MostRecentTime
FROM
[dataset.mytable]
GROUP BY
Id
And then another query that queries that view:
SELECT
*
FROM
[dataset.mytable] tbl
JOIN [views.myview] view ON tbl.Time = mview.MostRecentTime
Is there a way to automatically generate a query where the [views.myview] in the second query is replaced with the query that generates it - basically "unpacking" the views so you have just one query that queries tables directly?
(The underlying problem: I have a query which queries many different views, including several layers of views-querying-other-views, and I want to put this query in my application. I don't want a user to be able to mess with the results of the query by changing the definition of one of the views, so I want to put the whole query in a fixed form in the application.)
This is not possible to do 'automatically'. You could try writing some script or code to do this through the BigQuery apis - https://cloud.google.com/bigquery/docs/managing-views

SQL joins with views

I have a view that is now joining two other views with some extra tables.
It's very slow.
My experience tell me it's because views are not indexed by default. I tried to create an index on each of them, but it's not possible since they have self joins or inner queries.
My question is:
It appears to me that in general the join of views is not recommended. So, in short, there is no way to reuse a code from one view into another? Example: the view A calculates the percentage and the view B calculates something else that uses the percentage from view A plus other information from other tables/views. What would be the best approach? Do you really have to replicate the code from view A to view B so it uses the original table's indexes?
Views (simplified view, to show the issue):
View A (calculates the percentage):
SELECT dbo.tblPopAgeGrp.RevID, dbo.tblPopAgeGrp.VarID, dbo.tblPopAgeGrp.LocID,
dbo.tblPopAgeGrp.TimeID, dbo.tblPopAgeGrp.AgeID, tPAGT.AgeID AS AgeTotal,
100 * dbo.tblPopAgeGrp.PopMale / tPAGT.PopMale AS PopMalePerc,
100 * dbo.tblPopAgeGrp.PopFemale / tPAGT.PopFemale AS PopFemalePerc,
100 * dbo.tblPopAgeGrp.PopTotal / tPAGT.PopTotal AS PopTotalPerc
FROM dbo.tblPopAgeGrp
INNER JOIN dbo.tblPopAgeGrp tPAGT
ON dbo.tblPopAgeGrp.GroupID = tPAGT.GroupID
AND dbo.tblPopAgeGrp.AgeID = 700
View A by itself, since there so many records, takes a long time to execute. However, in view B the records are filtered according to the VersionID.
View B (gets the percentage from view A with additional info from another view):
SELECT vPAGP.VersionID,
vPAGP.LocationID AS LocID,
vPAGP.PopTotalPerc AS pPopTot,
vPAGP.PopMalePerc AS pMale,
vPAGP.PopFemalePerc AS pFemale,
vPAGPSR.PopMaleSexRatio AS SexRatio,
vPAGPSR.PopFemaleSexRatio AS FemRatio
FROM dbo.vwA AS vPAGP
INNER JOIN dbo.vwOther AS vPAGPSR
ON vPAGPSR.GroupID = vPAGP.GroupID
WHERE vPAGP.VersionID=10
Executing View A without filters, takes like 10 minutes. Executing it for VersionID=10 only, it executes in 10 seconds. The view vwOther executes very quickly.
Thanks!
You are not correct when you state "It appears to me that in general the join of views is not recommended."
Views can be combined with other views and will perform well provided that all JOINs are optimizable and have the appropriate index created and any filtering done within the view is optimizable and has appropriate indexes created.
A view based on other views should perform as well as the same query written to factor out the views. If you want further help, please post the definition of all views involved in your problem.

Search through Users with dynamic attributes with SQL

.Hi i'm working with Asp and SQL-Server and i have no problem with writing dynamic query
I'm trying to Write a search page for searching people.
I have 3 related tables:
See my table diagram in : http://tinypic.com/r/21159go/5
What i'm trying to do is to design a search page that a person can search users with a dynamic number of attributes.
Example:
think that a username called "User1" has 3 attributes named "Attr1", "Attr2" and "Attr3" related to him in "UserAttributes" table and "User2" has 3 attributes named "Attr1", "Attr2" and "Attr4".
Attribute names and other bunch of items unrelated to search function saved in "Attributes" Table. This is because i want to relate an attribute between multiple users. and their values are stored in "UserAttributes" table.
Well someone wants to search upon "Attr1" and "Attr2" and wants to return all users that have "Attr1" and "Attr2" with specific value.
I need a query to know how to implement this. I can write a dynamic query with asp.net so if someone please give me a query for this one example i have brought, i would be thankful
P.S. This is not my real database. my real database is much more complex and has more fields and tables but i just cut it and brought only necessary items. and because attributes are very dynamic they can't be embedded in table columns.
Thanks in advance
Based on your DB diagram your code would be something like this
update:
SELECT u.*
FROM users AS u
LEFT OUTER JOIN UserAttributes AS ua1
ON u.USER_ID = ua1.USER_ID
LEFT OUTER JOIN UserAttributes AS ua2
ON u.USER_ID = ua2.USER_ID
WHERE (
ua1.attribute_id = 'att1'
AND ua.attribute_value = 'MyValue' )
AND (
ua2.attribute_id = 'att2'
AND ua.attribute_value = 'MyValue2' )
In where clause you would specify the attirbute_Id and what value you are expecting out of it. Than just decide if you want to restrict users to have all values match or just one of them, in that case modify AND between statements to be OR
if you just want to do this quick and dirty you can create your own class library that can create adhoc sql that will pass to the database.
if you want more organized matter, create SP that will bring back users and accepts any left of id and value. Do a lot of that by passing list separated by comma, colon or semicolon. Than split it up in SP and filter results based on those values
there are many other alternatives like EntityFramework, LINQ-to-SQL and other options, just need to figure out what works best for you, how much time you want to spend on it and how easy will it be to support later.

How to remove row that exists in another table?

I have two tables. Main table is "CompleteEmailListJuly11" and the second table is "CurrentCustomersEmailJuly11". I want to delete rows in CompleteEmailListJuly11 table that CurrentCustomersEmailJuly11 has based off email.
I've tried this following Delete example, but it doesn't do anything close to what I'm trying to do. This only shows me the ones that EXIST in the database, it doesn't show me the the list of emails that AREN'T matching.
DELETE * FROM CompleteEmailListJuly11 AS i
WHERE EXISTS (
SELECT 1 FROM CurrentCustomersEmailJuly11
WHERE CurrentCustomersEmailJuly11.email = i.EmailAddress
)
Help is greatly appreciated.
This is the query I think you need:
DELETE FROM CompleteEmailListJuly11
WHERE EmailAddress IN (SELECT email FROM CurrentCustomersEmailJuly11)
Ps: The DELETE query does not delete individual fields, only entire rows, so the * is not necessary, you will also need to "Execute" this query rather than "Previewing" or "Exporting"
If you're building your DELETE query in Access' query designer, notice there are two different modes of operation which seem similar to "go ahead and do this".
Datasheet View (represented by the grid icon labeled "View" on the "Design" section of the ribbon). That view enables you to preview the affected records, but does not actually delete them.
The "Run" icon (represented by a red exclamation point). "Run" will actually execute the query and delete the affected records.
If you already know this, my description may seem insulting. Sorry. However, it seems that folks new to Access can easily overlook the distinction between them.
You can use something like this adapted to delete
SELECT ... // complete
EXCEPT
SELECT ... // current
I am not sure exactly how it maps to delete but take a look at that.
I fond it in a similar question: How do I 'subtract' sql tables?
We can use Correlated Query to resolve the issue like
DELETE FROM COMPLETE C
WHERE EMAIL = (SELECT EMAIL FROM CURR CU WHERE CU.EMAIL=C.EMAIL);

How do I efficiently create a TRIGGER which, on occasion, generates a VIEW?

On a small pre-registration database, I'm using the following SQL to generate a VIEW whenever a specific user name is given. I'm using this mainly to get a snapshot whenever a sysadmin suspects duplicate names are registering. This will be done rarely (atmost once per hour), so the database schema shouldn't be getting excessively big.
CREATE OR REPLACE TRIGGER upd_on_su_entry
AFTER UPDATE OR INSERT
ON PRE_REG_MEMBER
FOR EACH ROW
BEGIN
IF :new.MEMBER_NAME = 'SysAdmin Dup Tester' THEN
EXECUTE IMMEDIATE 'CREATE OR REPLACE VIEW mem_n AS SELECT :old.MEMBER_NAME, COUNT(:old.MEMBER_NAME) FROM MEMBER GROUP BY MEMBER_NAME';
END IF;
END;
However, this appears to be a bloated, inefficient and erroneous way of working (according to my admin). Is there a fundamental error here ? Can I take an equivalent snapshot some other way?
I'm very new to SQL, so please bear with me.
Also, I want to be using the view as :
public void dups()throws Exception
{
Calendar cal = Calendar.getInstance();
jt.setText("Duplicate List at : "+ cal.getTime());
try{
rs=stat.executeQuery("select * from upd_on_su_entry");
while(rs.next())
{
jt.append(rs.getString("MEMBER_NAME")+"\t");
jt.append(rs.getString(2)+"\t");
}
}
catch(Exception e){System.out.print("\n"+e);}
}
There seem to be some misunderstandings here.
1.) views are basically stored sql statements, not stored sql results, so your view will always display the data as it is at the point of querying the view.
2.) Never ever use DDL (create statements) and similar during normal processing of an application. Its just not the way databases are intended to work.
If you want a snapshot at a point in time, create a secondary table which contains all the columns of the original table plus a snapshot time stamp.
When ever you want to make a snapshot copy all the data you want from the original table into the snapshot table while adding the current time stamp.
Based on your comment, it sounds like you want something like this
SELECT MEMBER_NAME FROM PRE_REG_MEMBER
GROUP BY MEMBER_NAME HAVING COUNT(*) > 1;
This will return all members with more than one row in the table
Again, ask yourself "what am I trying to do"?
Don't focus on the technology. Do that after you have a clear idea of what your goal is.
If you are just trying to avoid duplicate registrations on your database, just search the users table and show an error if that username is already there.
Also, think of your datamodel carefully before going into the implementation details.