I have a hierarchy of Article, and each Article has a property IsCommentable. This can take a value of true, false, or NULL. If it is NULL, it means that it inherits the value based on it's parents. Articles can be nested recursively, and there is no limit to the 'depth'.
Now, I need to make a query where I get all the articles from a database which are commentable. Is there any way these can be loaded via an SQL query?
Assuming you are using SQL Server, You can do it with a recursive CTE.
WITH cte (id, iscommentable) AS (
SELECT id, iscommentable FROM Article WHERE iscommentable IS NOT NULL
UNION ALL
SELECT a1.id, a2.iscommentable FROM Article a1
INNER JOIN cte a2 ON a1.parent=a2.id
WHERE a1.iscommentable IS NULL
)
SELECT * FROM cte
SQL fiddle example.
Related
I am programming an Excel add-in in C# where I process data contained in different DataTable objects. I would like to provide a function to perform SQL queries on the data, with the ability to reference data from other tables in where and sort by clauses (for example, using a join).
An example of such a query would be
SELECT name
FROM Table1
WHERE id = Table2.id AND Table2.age > 18
The problem with this is that a DataTable doesn't know of the existance of the other DataTables, so (for so far I know) there are no such methods in the class. Also, I cannot use something like LINQ, since the query will be written by the users of the add-in in excel.
Would it be a good solution to copy the data to an in-memory database, where each DataTable is mapped to a table? How would this work performance-wise? Is there a simpler solution?
In terms of SQL query you are missing a table reference in selecting the tables, corrected query will look like
SELECT name
FROM Table1, Table2
WHERE Table1.id = Table2.id AND Table2.age > 18
Use Table1.name if there is same named attribute in Table2.
However using only WHERE condition in Joins without specifying the joining attribute is not recommended read this question. Use JOIN.
SELECT Table1.name
FROM Table1 INNER JOIN Table2 ON Table1.id = Table2.id WHERE Table2.age > 18
Is there a way in NHibernate / QueryOver API to pass a parameter already in the mapping (so it uses the parameter as a fixed value for all queries on this particular instance).
I need this (or a workaround) because I have a view like this in the Database:
CREATE VIEW ProblematicView
AS
SELECT
v.*,
-- lots of data
FROM someView v
LEFT JOIN someTable t ON v.ForeignKey = t.ForeignKey
Now additionally to the ForeignKey match I need an additional check for a property like this:
AND t.SomeOtherValue = #myParameter
which is not possible as there is no way to pass parameters to a view directly. With a table valued function this would be possible but then I don´t know how to map it to NHibernate / QueryOver.
Also the function approach would be hard to realize as a huge QueryOver statement is used to filter all the remaining properties (as the view is used for searching business entities)
Currently I am applying the SomeOtherValue / #myParameter filter to the the entire view as a part of my QueryOver.
This is my main problem:
Using for example:
SELECT
v.*,
-- lots of data
FROM someView v
LEFT JOIN someTable t ON v.ForeignKey = t.ForeignKey AND t.SomeOtherValue = 123
(followed by alot of other property checks...)
will return a different result (NULL entries for t.SomeOtherValue included on intent due to left join)
than using:
SELECT * FROM ProblematicView where SomeOtherValue = 123
(followed by alot of other property checks)
As now the left join happens inside the view without checking for SomeOtherValue and as the SomeOthervalue check is applied independent on the left join, all the NULL values will be excluded (which is wrong business logic).
Also using:
SELECT * FROM ProblematicView where SomeOtherValue = 123 OR SomeOtherValue = NULL
(followed by alot of other property checks)
does not seem to help, as NULL values are still ignored...
So the only way I can imagine to solve this problem would be to find a way to pass my SomeOtherValue property somehow to the view so that it can use it as a parameter in the view itself (instead of in the where clause) or maybe somehow use an sql table based function with parameter for the model...
EDIT:
After some more research I managed to hopefully simplify the problem:
I am trying to convert this SQL:
Select v.*, ... from (someView v LEFT JOIN someTable t ON v.ForeignKey = t.ForeignKey)
WHERE SomeOtherValue = 123
(where SomeOtherValue comes from someOtherTable)
to this:
Select v.*, ... from someView v LEFT JOIN someTable t on v.ForeignKey = t.ForeignKey
AND t.SomeOtherValue = 123
using NHibernate / QueryOver. Note that in the second version the property SomeOthervalue is checked against directly within the left join, where in the first version it is incorrectly applied only after the left join.
I need to find a way to write latter SQL statement in a way that I can put it inside a view while still being able to pass 123 as parameter for SomeOtherValue.
You can add a condition to a join clause using the overload of JoinQueryOver or JoinAlias that takes a withClause parameter. For example:
SomeTable stAlias = null;
session.QueryOver<ProblematicView>()
.Left.JoinAlias(
pv => pv.SomeTable, // Join path
() => stAlias, // alias assignment
st => st.SomeOtherValue == 123) // "with" clause
/* etc. */
The "with" portion of the QueryOver join is what will add a condition to the left outer join in SQL. The above should generate this:
SELECT /* select list */
FROM [ProblematicView] this_
left outer join [SomeTable] sometable1_
on this_.Id = sometable1_.ProblematicViewId
and (sometable1.SomeOtherValue = 123 /* #p0 */)
If I understand correctly, this should help solve your problem.
A few things to note about adding a "with" clause:
Interestingly, all of the overloads of JoinQueryOver and JoinAlias that allow you to specify a "with" clause require that you assign an alias when you do the join.
You cannot (as far as I know) generate SQL with an or in the join condition. the "with" clause is always anded with the mapped join condition (i.e., FK → PK)
This question already has an answer here:
Stored Proc slower from application than Management Studio
(1 answer)
Closed 9 years ago.
This is my dynamic query used on search form which runs in milliseconds in SSMS roughly between 300 to 400 ms:
exec sp_executesql N'set arithabort off;
set transaction isolation level read uncommitted;
With cte as
(Select ROW_NUMBER() OVER
(Order By Case When d.OldInstrumentID IS NULL
THEN d.LastStatusChangedDateTime Else d.RecordingDateTime End
desc) peta_rn,
d.DocumentID
From Documents d
Inner Join Users u on d.UserID = u.UserID
Inner Join IGroupes ig on ig.IGroupID = d.IGroupID
Inner Join ITypes it on it.ITypeID = d.ITypeID
Where 1=1
And (CreatedByAccountID = #0 Or DocumentStatusID = #1 Or DocumentStatusID = #2 )
And (d.JurisdictionID = #3 Or DocumentStatusID = #4 Or DocumentStatusID = #5)
AND ( d.DocumentStatusID = 9 )
)
Select d.DocumentID, d.IsReEfiled, d.IGroupID, d.ITypeID, d.RecordingDateTime,
d.CreatedByAccountID, d.JurisdictionID,
Case When d.OldInstrumentID IS NULL THEN d.LastStatusChangedDateTime
Else d.RecordingDateTime End as LastStatusChangedDateTime,
dbo.FnCanChangeDocumentStatus(d.DocumentStatusID,d.DocumentID) as CanChangeStatus,
d.IDate, d.InstrumentID, d.DocumentStatusID,ig.Abbreviation as IGroupAbbreviation,
u.Username, j.JDAbbreviation, inf.DocumentName,
it.Abbreviation as ITypeAbbreviation, d.DocumentDate,
ds.Abbreviation as DocumentStatusAbbreviation,
Upper(dbo.GetFlatDocumentName(d.DocumentID)) as FlatDocumentName
From Documents d
Left Join IGroupes ig On d.IGroupID = ig.IGroupID
Left Join ITypes it On d.ITypeID = it.ITypeID
Left Join Users u On u.UserID = d.UserID
Left Join DocumentStatuses ds On d.DocumentStatusID = ds.DocumentStatusID
Left Join InstrumentFiles inf On d.DocumentID = inf.DocumentID
Left Join Jurisdictions j on j.JurisdictionID = d.JurisdictionID
Inner Join cte on cte.DocumentID = d.DocumentID
Where 1=1
And peta_rn>=#6 AND peta_rn<=#7
Order by peta_rn',
N'#0 int,#1 int,#2 int,#3 int,#4 int,#5 int,#6 bigint,#7 bigint',
#0=44,#1=5,#2=9,#3=1,#4=5,#5=9,#6=94200,#7=94250
This sql is formed in C# code and the where clauses are added dynamically based on the value the user has searched in search form. It takes roughly 3 seconds to move from one page to 2nd. I already have necessary indexes on most of the columns where I search.
Any idea why would my Ado.Net code be slow?
Update: Not sure if execution plans would help but here they are:
It is possible that SQL server has created inappropriate query plan for ADO.NET connections. We have seen similar issues with ADO, usual solution is to clear any query plans and run slow query again - this may create better plan.
To clear query plans most general solution is to update statistics for involved tables. Like next for you:
update statistics documents with fullscan
Do same for other tables involved and then run your slow query from ADO.NET (do not run SSMS before).
Note that such timing inconsistencies may hint of bad query or database design - at least for us that is usually so :)
If you run a query repeatedly in SSMS, the database may re-use a previously created execution plan, and the required data may already be cached in memory.
There are a couple of things I notice in your query:
the CTE joins Users, IGroupes and ITypes, but the joined records are not used in the SELECT
the CTE performs an ORDER BY on a calculated expression (notice the 85% cost in (unindexed) Sort)
probably replacing the CASE expression with a computed persisted column which can be indexed speeds up execution.
note that the ORDER BY is executed on data resulting from joining 4 tables
the WHERE condition of the CTE states AND d.DocumentStatusID = 9, but AND's other DocumentStatusIDs
paging is performed on the result of 8 JOINed tables.
most likely creating an intermediate CTE which filters the first CTE based on peta_rn improves performance
.net by default uses UTF strings, which equates to NVARCHAR as opposed to VARCHAR.
When you are doing a WHERE ID = #foo in dot net, you are likely to be implicitly doing
WHERE CONVERT(ID, NVARCHAR) = #foo
The result is that this where clause can't be indexed, and must be table scanned. The solution is to actually pass each parameter into the SqlCommand as a DbParameter with the DbType set to VARCHAR (in the case of string).
A similar situation could of course occur with Int types if the .net parameter is "wider" than the SQL column equivalent.
PS The easiest way to "prove" this issue is to run your query in SSMS with the following above
DECLARE #p0 INT = 123
DECLARE #p1 NVARCHAR = "foobar" //etc etc
and compare with
DECLARE #p0 INT = 123
DECLARE #p1 VARCHAR = "foobar" //etc etc
I am trying to write a CTE Recursive Query to build a tree relationship of a flat table with a 'marketGroupID' (that element's ID) and a 'parentGroupID' (the element's parent ID). Where each 'marketGroup' can have any number of children 'marketGroups' and so on.
Here is my working query (tested in Sql Server Management):
With cte As
(SELECT [marketGroupID]
,[parentGroupID]
,[marketGroupName]
,[description]
,[iconID]
,[hasTypes]
, 0 As Level
FROM [Eve_Retribution_1.0.7].[dbo].[invMarketGroups]
WHERE [parentGroupID] IS NULL
UNION All
Select mg.marketGroupID
,mg.parentGroupID
,mg.marketGroupName
,mg.description
,mg.iconID
,mg.hasTypes
,c.Level + 1 As Level
FROM [Eve_Retribution_1.0.7].dbo.invMarketGroups mg
Inner Join cte c On mg.parentGroupID = c.marketGroupID
WHERE mg.marketGroupID <> mg.parentGroupID
)
SELECT marketGroupID
,parentGroupID
,marketGroupName
,description
,iconID
,hasTypes
, Level
FROM cte
This Query correctly lists the Elements in the correct order and the Level parameter is meant to be used to build the tree from the elements.
Translating this into C# is where I have a problem. I have integrated this database and all the corresponding tables have been built from my database into my code automatically. I try to call this query with C# as follows:
EveOnlineClassesDataContext context = new EveOnlineClassesDataContext();
IEnumerable<invMarketGroup> results = context.ExecuteQuery<invMarketGroup>
(#"**ABOVE QUERY**");
Where the 'invMarketGroup' class is the automatically created class built by the O/R Designer. My problem is that I lose access to the Level parameter for each 'marketGroup' as it was not part of the table itself and has no element in the provided class.
I want to retrieve from the query the actual 'invMarketGroup' class objects and the level corresponding to each so I can build a tree from this in memory representing this structure. How would I go about doing this?
Thanks
Might be easier to create a View vwInvMarketGroup inside your database using that query:
CREATE VIEW vwInvMarketGroup AS
With cte As
(SELECT [marketGroupID]
,[parentGroupID]
,[marketGroupName]
,[description]
,[iconID]
,[hasTypes]
, 0 As Level
FROM [Eve_Retribution_1.0.7].[dbo].[invMarketGroups]
WHERE [parentGroupID] IS NULL
UNION All
Select mg.marketGroupID
,mg.parentGroupID
,mg.marketGroupName
,mg.description
,mg.iconID
,mg.hasTypes
,c.Level + 1 As Level
FROM [Eve_Retribution_1.0.7].dbo.invMarketGroups mg
Inner Join cte c On mg.parentGroupID = c.marketGroupID
WHERE mg.marketGroupID <> mg.parentGroupID
)
SELECT marketGroupID
,parentGroupID
,marketGroupName
,description
,iconID
,hasTypes
, Level
FROM cte
GO
Then you can use this:
IEnumerable<invMarketGroup> results = context.ExecuteQuery<invMarketGroup>(#"SELECT * FROM vwInvMarketGroup");
How do I turn this table:
+------------+-----------------+
| Category + Subcategory |
+------------+-----------------+
|Cat..........+ Persian.........|
|Cat..........+ Siamese........|
|Cat..........+ Tabby...........|
|Dog.........+ Poodle..........|
|Dog.........+ Boxer............|
+------------+----------------+
on it's side to get the following:
+------------+-----------------+
| Cat......... + Dog............. |
+------------+-----------------+
+ Persian..+ Poodle.........+
+ Siamese + Boxer...........+
+ Burmese + ...................+
+------------+-----------------+
The initial table is from the following MySQL query:
select c.CATEGORYNAME, sc.NAME from subcategorydefinition sc
join categorydefinition c on sc.CATEGORYID = c.CATEGORYID
where c.ISDELETED = 0
order by CATEGORYNAME, NAME ASC
And I want to display it in (probably) a Gridview.
Cheers!
Pivot is static in SQL. You need to know in advance the columns you want in output, so if the list of categories is not fixed, you can't use pivot directly.
If you were using Microsoft SQL Server (which I know you're not, but it's for the sake of example), you could use a dynamic query in a stored procedure, as described here:
http://www.simple-talk.com/community/blogs/andras/archive/2007/09/14/37265.aspx
Now, in MySql, there is no way to execute dynamic SQL on the sql side (no equivalent of EXECUTE or sp_executeqsl), so your best choice would be to generate a similar SQL query server-side (aspnet server-side).
Another simpler idea IMHO would be to forget about doing it in SQL, but to do the aggregation in your C# code.
You should use pivot
To do this in SQL, you'd need to dynamically generate your query based on the available set of values in the "Category" column. This is usually fairly painful and error prone, regardless of whether you do it in pure SQL (in a sproc) or in code (dynamic SQL).
I'd recommend reading your values from the database in the way that they are stored, then dynamically creating a DataTable or similar structure to use as the datasource for your UI.
I don't have a working version of MySql handy but this will work as long as there is always more cats than dogs because of the left join at the end of the script. I forgot that there isn't a full outer join in MySql but you could use this logic to try it out.
But the point of this is that if you have two tables with arbitrary keys you can join on the keys to get the results lined up like you want.
-- drop tables
DROP TABLE dbo.cat
DROP TABLE dbo.dog
--create dog table
create table dog (
dog_id int IDENTITY(1,1) NOT NULL
,dog varchar(50)
)
--add dogs only
insert into dog (dog)
select subcategory
FROM play.dbo.test
where category = 'Dog'
--create cat table
create table cat (
cat_id int IDENTITY(1,1) NOT NULL
,cat varchar(50)
)
--add cats only
insert into cat (cat)
select subcategory
FROM play.dbo.test
where category = 'cat'
-- disply everything
SELECT cat
, dog
from dog d
--full outer join cat c
left join dog d
on d.dog_id = c.cat_id