How to use a nested inner join statement? - sqlite

I have created three tables such that:
CREATE TABLE guest(
name varchar(100),
ranking int,
PRIMARY KEY (name)
);
CREATE TABLE room(
roomname varchar(100),
wallcolor varchar(100),
rating int,
PRIMARY KEY(roomnane)
);
CREATE TABLE reservation(
name varchar(100),
roomname varchar(100),
day varchar(100),
moveinday int,
moveoutday int,
PRIMARY KEY(roomname, day, start, finish),
FOREIGN KEY(roomname) REFERENCES room(roomname),
FOREIGN KEY(name) REFERENCES guest(name)
);
I am trying to write a DELETE query to delete all the guests that are not qualified to rent any room. Qualified to rent means the guests ranking is greater than or equal to room ranking. Pretty much delete the reservation entry, and the guest too if they don't qualify
I tried
DELETE
FROM
reservation, guest
INNER JOIN (
SELECT
reservation.roomname,
reservation.day,
reservation.start,
reservation.finish
FROM
guest
INNER JOIN reservation ON reservation.name = guest.name
INNER JOIN room ON reservation.roomname = room.roomname
WHERE
room.rating > guest.ranking
) invalidReservationTable
ON reservation.roomname = invalidReservationTable.roomname
AND reservation.day = invalidReservationTable.day
AND reservation.start = invalidReservationTable.start
AND reservation.finish = invalidReservationTable.finish;
I am getting an error when I run this on sqlite on the Inner Join statement following the FROM reservation line. What am I doing wrong? Am I on the right track to delete just the reservation?

You cannot delete from multiple tables in same statement. Consider separating into two action queries: first on reservations table due to foreign key constraints, then on guests table, adding a WHERE clause subquery that conditions to your needs.
First, reservations delete:
DELETE FROM reservation
WHERE name IN
(SELECT reservation.name
FROM guest
INNER JOIN reservation ON reservation.name = guest.name
INNER JOIN room ON reservation.roomname = room.roomname
WHERE room.rating > guest.ranking);
Then guests table delete. However, the guest's corresponding reservation would no longer exist per query before. Hence, you should now remove guests without any reservations which you can handle with LEFT JOIN...NULL.
DELETE FROM guest
WHERE name IN
(SELECT guest.name
FROM guest
LEFT JOIN reservation ON reservation.name = guest.name
WHERE reservation.name IS NULL)
By the way, reconsider design here. In one-to-many relationships, the many-to-many table should be the only delete interaction. Guests (who may make future reservations) should not be regularly cleaned out. And have your app/script validate ranking/rating prior to making reservation.

Related

SQLite Order By foreign table field

I have two tables like following: (They have much more fields in reality)
Records (
recordId INTEGER PRIMARY KEY,
dateTime TEXT
);
Alarms (
alarmId INTEGER PRIMARY KEY,
recordId INTEGER,
FOREIGN KEY (recordId) REFERENCES Records(recordId)
ON UPDATE CASCADE
ON DELETE CASCADE
);
I want to get all Alarms sorted by dateTime of Record they are referring. I think it should be something like this:
SELECT * FROM Alarms ORDER BY Records.dateTime DESC;
But I couldn't find any relatable example.
Thank you for your time.
What you need is a join of the 2 tables:
SELECT a.*
FROM Alarms a INNER JOIN Records r
ON r.recordId = a.recordId
ORDER BY r.dateTime DESC;
Or a correlated subquery in the ORDER BY clause:
SELECT a.*
FROM Alarms a
ORDER BY (SELECT r.dateTime FROM Records r WHERE r.recordId = a.recordId) DESC;

SQLite outer join column filtering

As a training exercise I'm working on a fictional SQLite database resembling League of Legends, and I need to perform a left outer join to get a table of all players and if they have skins that are not called 'Classic', return those too.
I currently have this query:
SELECT * FROM players
LEFT OUTER JOIN (SELECT * FROM playerchampions WHERE NOT championskin = 'Classic')
ON name = playername
Which returns what I am looking for, but also a lot of columns I don't want (player experience, player IP, player RP, playername in the playerchampions table. The code for the two tables is as following:
CREATE TABLE players (
name TEXT PRIMARY KEY,
experience INTEGER,
currencyip INTEGER,
currencyrp INTEGER
);
CREATE TABLE playerchampions (
playername TEXT REFERENCES players ( name ) ON UPDATE CASCADE,
championname TEXT REFERENCES champions ( name ) ON UPDATE CASCADE,
championskin TEXT REFERENCES skins ( skinname ) ON UPDATE CASCADE,
PRIMARY KEY ( playername, championname, championskin )
);
As I said, the query executes, but I can't use SELECT players.name, playerchampions.championname, playerchampions.championskin as the playerchampions columns are not given their proper table name when returned.
How do I fix this?
Try using aliases:
SELECT p.name, c.championskin FROM players p LEFT OUTER JOIN (SELECT pc.playername playername, pc.championskin championskin FROM playerchampions pc WHERE NOT pc.championskin = 'Classic') c ON p.name = c.playername;
Not sure if its exactly what you need, but it will get you closer...
SELECT * FROM players p LEFT OUTER JOIN playerchampions pc ON (p.name = pc.playername) WHERE NOT pc.championskin = 'Classic'

SQLite Multi-column Insert/Replace with Multiple Join

Sorry for the poor title. I have a query (below) that executes properly and creates an insertion just as I would desire. However, I want to make it smarter by only inserting when the exact combination of three columns. Essentially, the three column tuple is a primary key, but I'm working with the limitation of sqlite's single primary key.
Basic Context
I have 4 tables: Permissions, Roles, Users, Actions
Permissions connects Roles and Users to Actions. The Actions table has a list of available tasks that a User or a user with a Role can perform. So for example, if user_id = 1 can perform a list_folder action (action_id = 1), then the permissions table would have an entry: (id=1, action_id=1, user_id=1, role_id=NULL). Likewise, suppose an owner_role (role_id=1) might have permissions to perform a list_folder action (action_id=1), then the permissions entry might be: (id=2, action_id=1, user_id=NULL, role_id=1).
When I do an insert, I want to make sure that I do not already have that exact combination (e.g. action_id=1, user_id=NULL, role_id=1). And I'm not entirely sure how to write the sql so that I have this setup properly.
Here's my basic insert statement. I need to come up with an insert and a replace statement:
INSERT INTO permissions (
action_id
,role_id
)
SELECT DISTINCT
a.id as "action_id"
,r.id as "role_id"
FROM tmp_permissions tmp
LEFT OUTER JOIN actions a
ON tmp.action_name = a.name
LEFT OUTER JOIN roles r
ON tmp.roles_name = r.name
LEFT OUTER JOIN permissions p
ON p.role_id
Here are some creation sql statements for the tables:
CREATE TABLE permissions (
id INTEGER NOT NULL,
enabled INTEGER,
action_id INTEGER,
user_id INTEGER,
role_id INTEGER,
PRIMARY KEY (id),
FOREIGN KEY(user_id) REFERENCES users (id),
FOREIGN KEY(action_id) REFERENCES actions (id),
FOREIGN KEY(role_id) REFERENCES roles (id)
);
CREATE TABLE actions (
id INTEGER NOT NULL,
enabled INTEGER,
name VARCHAR(50),
permission_ids INTEGER,
PRIMARY KEY (id),
FOREIGN KEY(permission_ids) REFERENCES permissions (id)
);
CREATE TABLE roles (
id INTEGER NOT NULL,
enabled INTEGER,
name VARCHAR(50),
permission_ids INTEGER,
PRIMARY KEY (id),
FOREIGN KEY(permission_ids) REFERENCES permissions (id)
);
CREATE TABLE users (
id INTEGER NOT NULL,
enabled INTEGER,
name VARCHAR(50),
permission_ids INTEGER,
PRIMARY KEY (id),
FOREIGN KEY(permission_ids) REFERENCES permissions (id)
);
Here's a temp table I'm using to store the data in the table while I work with it:
CREATE TABLE tmp_permissions(
roles_name VARCHAR(50),
action_name VARCHAR(50)
);
Here's some data:
#role|action
admin|setup
admin|debug
admin|login
admin|view_user
manager|view_employee
manager|enroll_employee
manager|login
employee|schedule
employee|login
customer|guest_login
customer|change_credentials
guest|guest_login
Thanks in advance!
Add a UNIQUE constraint to the table:
CREATE TABLE permissions(
... ,
UNIQUE (action_id, user_id, role_id)
)
You can then use any of the conflict resolution algorithms to handle duplicates.

Refactor SQLite Table by splitting it in two and link with foreign keys

I'm working on a SQLite Database. The database is already filled, but I want to refactor it. Here is a sample of what I need to do:
I currently have one table:
CREATE TABLE Cars (ID INTEGER PRIMARY KEY,
Name VARCHAR(32),
TopSpeed FLOAT,
EngineCap FLOAT);
I want to split this into two tables:
CREATE TABLE Vehicles (ID INTEGER PRIMARY KEY,
Name VARCHAR(32),
TopSpeed FLOAT);
CREATE TABLE Cars (ID INTEGER PRIMARY KEY,
VehicleID INTEGER CONSTRAINT FK_Cars REFERENCES [Vehicles](ID),
EngineCap FLOAT);
I have figured out to create a temporary table with the Cars table contents, and I can fill up the Vehicles table with the contents of the Cars table:
CREATE TEMPORARY TABLE Cars_temp AS SELECT * FROM Cars;
INSERT INTO Vehicles (Name, TopSpeed)
SELECT Name, TopSpeed FROM Cars_temp;
But I am still looking for a way to go over that same selection, while putting the EngineCap field into the new Cars table and somehow extracting the corresponding ID value from the Vehicles table to put into the VehicleID foreign key field on the Cars table.
I'm open for workaround or alternative approaches.
Thanks.
Since #mateusza did not provide an example, I've made one:
Suppose you have this table:
CREATE TABLE [Customer] (
[name] TEXT,
[street] TEXT,
[city] TEXT);
Now you want to move street and city into a separate table Address, so you'll end up with two tables:
CREATE TABLE [Customer2] (
[name] TEXT,
[addr] INTEGER);
CREATE TABLE [Address] (
[rowid] INTEGER NOT NULL,
[street] TEXT,
[city] TEXT,
PRIMARY KEY ([rowid])
);
(For this example, I'm doing the conversion in the same database. You'd probably use two DBs, converting one into the other, with an SQL ATTACH command.)
Now we create a view (which imitates our original table using the new tables) and the trigger:
CREATE VIEW Customer1 (name, street, city) AS
SELECT C.name, A.street, A.city FROM Customer2 AS C
JOIN Address as A ON (C.addr == A.rowid);
CREATE TEMP TRIGGER TempTrig INSTEAD OF INSERT ON Customer1 FOR EACH ROW BEGIN
INSERT INTO Address (street, city) SELECT NEW.street, NEW.city;
INSERT INTO Customer2 (addr, name) SELECT last_insert_rowid(), NEW.name;
END;
Now you can copy the table rows:
INSERT INTO Customer1 (name, street, city) SELECT name, street, city FROM Customer;
The above is a simplified case where you'd only move some data into a single new table.
A more complex (and more general) case is where you want to...
Separate your original table's columns into several foreign tables, and
Have unique entries in the foreign tables (that's usually the reason why you'd refactor your table).
This adds some additional challenges:
You'll end up inserting into multiple tables before you can insert their rowids into the table with the referencing rowids. This requires storing the results of each INSERT's last_insert_rowid() into a temporary table.
If the value already exists in the foreign table, its rowid must be stored instead of the one from the (non-executed) insertion operation.
Here's a complete solution for this. It manages a database of music records, constisting of a song's name, album title and artist name.
-- Original table
CREATE TABLE [Song] (
[title] TEXT,
[album] TEXT,
[artist] TEXT
);
-- Refactored tables
CREATE TABLE [Song2] (
[title] TEXT,
[album_rowid] INTEGER,
[artist_rowid] INTEGER
);
CREATE TABLE [Album] (
[rowid] INTEGER PRIMARY KEY AUTOINCREMENT,
[title] TEXT UNIQUE
);
CREATE TABLE [Artist] (
[rowid] INTEGER PRIMARY KEY AUTOINCREMENT,
[name] TEXT UNIQUE
);
-- Fill with sample data
INSERT INTO Song VALUES ("Hunting Girl", "Songs From The Wood", "Jethro Tull");
INSERT INTO Song VALUES ("Acres Wild", "Heavy Horses", "Jethro Tull");
INSERT INTO Song VALUES ("Broadford Bazar", "Heavy Horses", "Jethro Tull");
INSERT INTO Song VALUES ("Statue of Liberty", "White Music", "XTC");
INSERT INTO Song VALUES ("Standing In For Joe", "Wasp Star", "XTC");
INSERT INTO Song VALUES ("Velvet Green", "Songs From The Wood", "Jethro Tull");
-- Conversion starts here
CREATE TEMP TABLE [TempRowIDs] (
[album_id] INTEGER,
[artist_id] INTEGER
);
CREATE VIEW Song1 (title, album, artist) AS
SELECT Song2.title, Album.title, Artist.name
FROM Song2
JOIN Album ON (Song2.album_rowid == Album.rowid)
JOIN Artist ON (Song2.artist_rowid == Artist.rowid);
CREATE TEMP TRIGGER TempTrig INSTEAD OF INSERT ON Song1 FOR EACH ROW BEGIN
INSERT OR IGNORE INTO Album (title) SELECT NEW.album;
UPDATE TempRowIDs SET album_id = (SELECT COALESCE (
(SELECT rowid FROM Album WHERE changes()==0 AND title==NEW.album), last_insert_rowid()
) ) WHERE rowid==1;
INSERT OR IGNORE INTO Artist (name) SELECT NEW.artist;
UPDATE TempRowIDs SET artist_id = (SELECT COALESCE (
(SELECT rowid FROM Artist WHERE changes()==0 AND name==NEW.artist), last_insert_rowid()
) ) WHERE rowid==1;
INSERT INTO Song2 (title, album_rowid, artist_rowid) SELECT
NEW.title, (SELECT album_id FROM TempRowIDs), (SELECT artist_id FROM TempRowIDs);
END;
INSERT INTO TempRowIDs DEFAULT VALUES;
INSERT INTO Song1 (title, album, artist) SELECT title, album, artist FROM Song;
DROP TRIGGER TempTrig;
DROP TABLE TempRowIDs;
-- Conversion ends here
-- Print results
SELECT * FROM Song;
SELECT * FROM Song1;
-- Check if original and copy are identical (https://stackoverflow.com/a/13865679/43615)
SELECT CASE WHEN (SELECT COUNT(*) FROM (SELECT * FROM Song UNION SELECT * FROM Song1)) == (SELECT COUNT() FROM Song) THEN 'Success' ELSE 'Failure' END;
Note that this example has one potential issue: If the constraints on the foreign table are more complex, the SELECT rowid FROM search for the existing entry needs to be updated accordingly. Ideally, SQLite should provide a way to determine the conflicting rowid somehow, but it doesn't, unfortunately (see this related question).
Simple solution without triggers:
create VEHICLES_TEMP table including the CAR_ID
create your new CARS table without the VEHICLES columns you don't want
update CARS with VEHICLE_ID taken from VEHICLES_TEMP (identified by the CAR_ID)
create final VEHICLES table without the CAR_ID
Create a table New_Cars and a INSTEAD OF INSERT trigger, which will insert data to both tables Vehicles and Cars. When inserting to Cars, you can use last_insert_rowid() function to refer to inserted row in Vehicles table.
This can be temporary solution, or you can leave it in your database for further modifications.

SQLite query runs 10 times slower than MSAccess query

I have a 800MB MS Access database that I migrated to SQLite. The structure of the database is as follows (the SQLite database, after migration, is around 330MB):
The table Occurrence has 1,600,000 records. The table looks like:
CREATE TABLE Occurrence
(
SimulationID INTEGER, SimRunID INTEGER, OccurrenceID INTEGER,
OccurrenceTypeID INTEGER, Period INTEGER, HasSucceeded BOOL,
PRIMARY KEY (SimulationID, SimRunID, OccurrenceID)
)
It has the following indexes:
CREATE INDEX "Occurrence_HasSucceeded_idx" ON "Occurrence" ("HasSucceeded" ASC)
CREATE INDEX "Occurrence_OccurrenceID_idx" ON "Occurrence" ("OccurrenceID" ASC)
CREATE INDEX "Occurrence_SimRunID_idx" ON "Occurrence" ("SimRunID" ASC)
CREATE INDEX "Occurrence_SimulationID_idx" ON "Occurrence" ("SimulationID" ASC)
The table OccurrenceParticipant has 3,400,000 records. The table looks like:
CREATE TABLE OccurrenceParticipant
(
SimulationID INTEGER, SimRunID INTEGER, OccurrenceID INTEGER,
RoleTypeID INTEGER, ParticipantID INTEGER
)
It has the following indexes:
CREATE INDEX "OccurrenceParticipant_OccurrenceID_idx" ON "OccurrenceParticipant" ("OccurrenceID" ASC)
CREATE INDEX "OccurrenceParticipant_ParticipantID_idx" ON "OccurrenceParticipant" ("ParticipantID" ASC)
CREATE INDEX "OccurrenceParticipant_RoleType_idx" ON "OccurrenceParticipant" ("RoleTypeID" ASC)
CREATE INDEX "OccurrenceParticipant_SimRunID_idx" ON "OccurrenceParticipant" ("SimRunID" ASC)
CREATE INDEX "OccurrenceParticipant_SimulationID_idx" ON "OccurrenceParticipant" ("SimulationID" ASC)
The table InitialParticipant has 130 records. The structure of the table is
CREATE TABLE InitialParticipant
(
ParticipantID INTEGER PRIMARY KEY, ParticipantTypeID INTEGER,
ParticipantGroupID INTEGER
)
The table has the following indexes:
CREATE INDEX "initialpart_participantTypeID_idx" ON "InitialParticipant" ("ParticipantGroupID" ASC)
CREATE INDEX "initialpart_ParticipantID_idx" ON "InitialParticipant" ("ParticipantID" ASC)
The table ParticipantGroup has 22 records. It looks like
CREATE TABLE ParticipantGroup (
ParticipantGroupID INTEGER, ParticipantGroupTypeID INTEGER,
Description varchar (50), PRIMARY KEY( ParticipantGroupID )
)
The table has the following index:
CREATE INDEX "ParticipantGroup_ParticipantGroupID_idx" ON "ParticipantGroup" ("ParticipantGroupID" ASC)
The table tmpSimArgs has 18 records. It has the following structure:
CREATE TABLE tmpSimArgs (SimulationID varchar, SimRunID int(10))
And the following indexes:
CREATE INDEX tmpSimArgs_SimRunID_idx ON tmpSimArgs(SimRunID ASC)
CREATE INDEX tmpSimArgs_SimulationID_idx ON tmpSimArgs(SimulationID ASC)
The table ‘tmpPartArgs’ has 80 records. It has the below structure:
CREATE TABLE tmpPartArgs(participantID INT)
And the below index:
CREATE INDEX tmpPartArgs_participantID_idx ON tmpPartArgs(participantID ASC)
I have a query that involves multiple INNER JOINs and the problem I am facing is the Access version of the query takes about a second whereas the SQLite version of the same query takes 10 seconds (about 10 times slow!) It is impossible for me to migrate back to Access and SQLite is my only option.
I am new to writing database queries hence these queries might look stupid, so please advise on anything you see faulty or kid-dish.
The query in Access is (the entire query takes 1 second to execute):
SELECT ParticipantGroup.Description, Occurrence.SimulationID, Occurrence.SimRunID, Occurrence.Period, Count(OccurrenceParticipant.ParticipantID) AS CountOfParticipantID FROM
(
ParticipantGroup INNER JOIN InitialParticipant ON ParticipantGroup.ParticipantGroupID = InitialParticipant.ParticipantGroupID
) INNER JOIN
(
tmpPartArgs INNER JOIN
(
(
tmpSimArgs INNER JOIN Occurrence ON (tmpSimArgs.SimRunID = Occurrence.SimRunID) AND (tmpSimArgs.SimulationID = Occurrence.SimulationID)
) INNER JOIN OccurrenceParticipant ON (Occurrence.OccurrenceID = OccurrenceParticipant.OccurrenceID) AND (Occurrence.SimRunID = OccurrenceParticipant.SimRunID) AND (Occurrence.SimulationID = OccurrenceParticipant.SimulationID)
) ON tmpPartArgs.participantID = OccurrenceParticipant.ParticipantID
) ON InitialParticipant.ParticipantID = OccurrenceParticipant.ParticipantID WHERE (((OccurrenceParticipant.RoleTypeID)=52 Or (OccurrenceParticipant.RoleTypeID)=49)) AND Occurrence.HasSucceeded = True GROUP BY ParticipantGroup.Description, Occurrence.SimulationID, Occurrence.SimRunID, Occurrence.Period;
The SQLite query is as follows (this query takes around 10 seconds):
SELECT ij1.Description, ij2.occSimulationID, ij2.occSimRunID, ij2.Period, Count(ij2.occpParticipantID) AS CountOfParticipantID FROM
(
SELECT ip.ParticipantGroupID AS ipParticipantGroupID, ip.ParticipantID AS ipParticipantID, ip.ParticipantTypeID, pg.ParticipantGroupID AS pgParticipantGroupID, pg.ParticipantGroupTypeID, pg.Description FROM ParticipantGroup as pg INNER JOIN InitialParticipant AS ip ON pg.ParticipantGroupID = ip.ParticipantGroupID
) AS ij1 INNER JOIN
(
SELECT tpa.participantID AS tpaParticipantID, ij3.* FROM tmpPartArgs AS tpa INNER JOIN
(
SELECT ij4.*, occp.SimulationID as occpSimulationID, occp.SimRunID AS occpSimRunID, occp.OccurrenceID AS occpOccurrenceID, occp.ParticipantID AS occpParticipantID, occp.RoleTypeID FROM
(
SELECT tsa.SimulationID AS tsaSimulationID, tsa.SimRunID AS tsaSimRunID, occ.SimulationID AS occSimulationID, occ.SimRunID AS occSimRunID, occ.OccurrenceID AS occOccurrenceID, occ.OccurrenceTypeID, occ.Period, occ.HasSucceeded FROM tmpSimArgs AS tsa INNER JOIN Occurrence AS occ ON (tsa.SimRunID = occ.SimRunID) AND (tsa.SimulationID = occ.SimulationID)
) AS ij4 INNER JOIN OccurrenceParticipant AS occp ON (occOccurrenceID = occpOccurrenceID) AND (occSimRunID = occpSimRunID) AND (occSimulationID = occpSimulationID)
) AS ij3 ON tpa.participantID = ij3.occpParticipantID
) AS ij2 ON ij1.ipParticipantID = ij2.occpParticipantID WHERE (((ij2.RoleTypeID)=52 Or (ij2.RoleTypeID)=49)) AND ij2.HasSucceeded = 1 GROUP BY ij1.Description, ij2.occSimulationID, ij2.occSimRunID, ij2.Period;
I don’t know what I am doing wrong here. I have all the indexes but I thinking I am missing declaring some key index that will do the trick for me. The interesting thing is before migration my ‘research’ on SQLite showed that SQLite is faster, smaller and better in all aspects than Access. But I cant seem to get SQLite work faster than Access in terms of querying. I reiterate that I am new to SQLite and obviously do not have much idea as well as experience so if any learned soul could help me out with this, it will be much appreciated.
I have reformatting your code (using my home-brew sql formatter) to hopefully make it easier for others to read..
Reformatted Query:
SELECT
ij1.Description,
ij2.occSimulationID,
ij2.occSimRunID,
ij2.Period,
Count(ij2.occpParticipantID) AS CountOfParticipantID
FROM (
SELECT
ip.ParticipantGroupID AS ipParticipantGroupID,
ip.ParticipantID AS ipParticipantID,
ip.ParticipantTypeID,
pg.ParticipantGroupID AS pgParticipantGroupID,
pg.ParticipantGroupTypeID,
pg.Description
FROM ParticipantGroup AS pg
INNER JOIN InitialParticipant AS ip
ON pg.ParticipantGroupID = ip.ParticipantGroupID
) AS ij1
INNER JOIN (
SELECT
tpa.participantID AS tpaParticipantID,
ij3.*
FROM tmpPartArgs AS tpa
INNER JOIN (
SELECT
ij4.*,
occp.SimulationID AS occpSimulationID,
occp.SimRunID AS occpSimRunID,
occp.OccurrenceID AS occpOccurrenceID,
occp.ParticipantID AS occpParticipantID,
occp.RoleTypeID
FROM (
SELECT
tsa.SimulationID AS tsaSimulationID,
tsa.SimRunID AS tsaSimRunID,
occ.SimulationID AS occSimulationID,
occ.SimRunID AS occSimRunID,
occ.OccurrenceID AS occOccurrenceID,
occ.OccurrenceTypeID,
occ.Period,
occ.HasSucceeded
FROM tmpSimArgs AS tsa
INNER JOIN Occurrence AS occ
ON (tsa.SimRunID = occ.SimRunID)
AND (tsa.SimulationID = occ.SimulationID)
) AS ij4
INNER JOIN OccurrenceParticipant AS occp
ON (occOccurrenceID = occpOccurrenceID)
AND (occSimRunID = occpSimRunID)
AND (occSimulationID = occpSimulationID)
) AS ij3
ON tpa.participantID = ij3.occpParticipantID
) AS ij2
ON ij1.ipParticipantID = ij2.occpParticipantID
WHERE (
(
(ij2.RoleTypeID) = 52
OR
(ij2.RoleTypeID) = 49
)
)
AND ij2.HasSucceeded = 1
GROUP BY
ij1.Description,
ij2.occSimulationID,
ij2.occSimRunID,
ij2.Period;
As per JohnFx (above), I was confused by the derived views. I think there is actually no need for it, especially since they are all inner joins. So, below I have attempted to reduce the complexity. Please review and test for performance. I have had to do a cross join with tmpSimArgs since it is only joined to Occurence - I assume this is desired behaviour.
SELECT
pg.Description,
occ.SimulationID,
occ.SimRunID,
occ.Period,
COUNT(occp.ParticipantID) AS CountOfParticipantID
FROM ParticipantGroup AS pg
INNER JOIN InitialParticipant AS ip
ON pg.ParticipantGroupID = ip.ParticipantGroupID
CROSS JOIN tmpSimArgs AS tsa
INNER JOIN Occurrence AS occ
ON tsa.SimRunID = occ.SimRunID
AND tsa.SimulationID = occ.SimulationID
INNER JOIN OccurrenceParticipant AS occp
ON occ.OccurrenceID = occp.OccurrenceID
AND occ.SimRunID = occp.SimRunID
AND occ.SimulationID = occp.SimulationID
INNER JOIN tmpPartArgs AS tpa
ON tpa.participantID = occp.ParticipantID
WHERE occ.HasSucceeded = 1
AND (occp.RoleTypeID = 52 OR occp.RoleTypeID = 49 )
GROUP BY
pg.Description,
occ.SimulationID,
occ.SimRunID,
occ.Period;
I have presented a smaller scaled down version of my query. Hope this is more clear and legible than my earlier one.
SELECT5 * FROM
(
SELECT4 FROM ParticipantGroup as pg INNER JOIN InitialParticipant AS ip ON pg.ParticipantGroupID = ip.ParticipantGroupID
) AS ij1 INNER JOIN
(
SELECT3 * FROM tmpPartArgs AS tpa INNER JOIN
(
SELECT2 * FROM
(
SELECT1 * FROM tmpSimArgs AS tsa INNER JOIN Occurrence AS occ ON (tsa.SimRunID = occ.SimRunID) AND (tsa.SimulationID = occ.SimulationID)
) AS ij4 INNER JOIN OccurrenceParticipant AS occp ON (occOccurrenceID = occpOccurrenceID) AND (occSimRunID = occpSimRunID) AND (occSimulationID = occpSimulationID)
) AS ij3 ON tpa.participantID = ij3.occpParticipantID
) AS ij2 ON ij1.ipParticipantID = ij2.occpParticipantID WHERE (((ij2.RoleTypeID)=52 Or (ij2.RoleTypeID)=49)) AND ij2.HasSucceeded = 1
The application that I am working on is a Simulation application and in order to understand the context of the above query I thought it necessary to give a brief explanation of the application. Let us assume there is a planet with some initial resources and living agents. The planet is allowed to exist for 1000 years and the actions performed by the agents are monitored and stored in the database. After 1000 years the planet is destroyed and again re-created with the same set of initial resources and living agents as the first time. This (the creation and destruction) is repeated 18 times and all the actions of the agents performed during those 1000 years are stored in the database. Thus our entire experiment consists of 18 re-creations which is termed as the ‘Simulation’. Each of the 18 times the planet is recreated is termed as a run and each of the 1000 years of a run is called a period. So a ‘Simulation’ consists of 18 runs and each run consists of 1000 periods. At the start of each run, we assign the ‘Simulation’ an initial set of knowledge items and dynamic agents that interact with each other and the items. A knowledge item is stored by an agent inside a knowledge store. The knowledge store is also considered to be a participating entity in our Simulation. But this concept (regarding knowledge stores) is not important. I have tried to be detailed about every SELECT statement and the tables involved.
SELECT1: I think this query could be replaced by just the table ‘Occurrence’, since it does nothing much. The table Occurrence stores the different actions taken by the agents, in each period of every simulation run of a particular ‘Simulation’. Normally each ‘Simulation’ consists of 18 runs. And each run consists of a 1000 periods. An agent is allowed to take an action in every period of every run in the ‘Simulation’. But the Occurrence table does not store any details about the agents that perform the actions. The Occurrence table might store data related to multiple ‘Simulations’.
SELECT2: This query simply returns the details of actions performed in every period of every run of a ‘Simulation’ along with the details of all participants of the ‘Simulation’ like their respective ParticipantIDs. The OccurrenceParticipant table stores records for every participating entity of the Simulation and that includes agents, knowledge stores, knowledge items, etc.
SELECT3: This query returns only those records from the pseudo table ij3 that are due to agents and knowledge items. All records in ij3 concerning knowledge items will be filtered out.
SELECT4: This query attaches the ‘Description’ field to every record of ‘InitialParticipant’. Please note that the column ‘Description’ is an Output column of the entire query. The table InitialParticipant contains a record for every agent and every knowledge item that is initially assigned to the ‘Simulation’
SELECT5: This final query returns all records from the pseudo table ij2 for which the RoleType of the participating entity (which may either be an agent or a knowledge item) is 49 or 52.
I would suggest moving the ij2.RoleTypeID filtering from the outermost query to ij3, use IN instead of OR, and move the HasSucceeded query to ij4.

Resources