Searching one term in multiple fields with Postgres - postgresql

I'm trying to search the same text across to fields in my database for a livesearch box.
SELECT DISTINCT u.id, u.username FROM
users AS u, user_invoice AS ui, user_roles AS ur, roles AS r WHERE
u.id = ur.user_id AND
ur.role_id = r.id AND
r.name = 'teacher' AND
(
ui.user_id = u.id AND
CAST(ui.invoice AS TEXT) = 'searchterm'
)
This query searches the invoice table and returns results properly and extremely quickly.
SELECT DISTINCT u.id, u.username FROM
users AS u, user_invoice AS ui, user_roles AS ur, roles AS r WHERE
u.id = ur.user_id AND
ur.role_id = r.id AND
r.name = 'teacher' AND
(u.username like '%searchterm%')
This query searches for a matching username and returns extremely quickly as well.
But when I combine the two like this:
SELECT DISTINCT u.id, u.username FROM
users AS u, user_invoice AS ui, user_roles AS ur, roles AS r WHERE
u.id = ur.user_id AND
ur.role_id = r.id AND
r.name = 'teacher' AND
(
u.username like '%searchterm%' OR
(
ui.user_id = u.id AND
CAST(ui.invoice AS TEXT) = 'searchterm'
)
)
It returns the proper results but take almost a minute to do so. What am I doing wrong?
EDIT: EXPLAINs of my queries:
First:
http://explain.depesz.com/s/PvS
Second:
http://explain.depesz.com/s/D5c
Combined:
http://explain.depesz.com/s/Dhf
Edited for mistake in copying the cast lines.

Here's how I solve this problem in my main app.
I have a main entity that I want users to be able to search for. Call it customer. This entity has associated detail records in a 1:n contact (for phone, email, etc) table.
I define a view, customer_quicksearch, that calculates a quicksearch key - a text field containing the concatenation of contact records for a customer along with some of the customer fields directly.
I've added triggers to customer and contact customer_summary table. The customer trigger adds a record to customer_summary when a row is inserted into customer and delete the row when the customer record is deleted. They update customer_summary by SELECTing an updated quicksearch key from `customer_quicksearch. I could use a SQL function for this instead of a view, but found the view both more useful and faster. With a view it's quicker to calculate the quicksearch keys for all customers, say, after a bulk insert or update.
CREATE VIEW customer_quicksearch AS
SELECT
customer.id AS customer_id, array_to_string(ARRAY[
customer.code,
customer.name,
string_agg(array_to_string(ARRAY[
contact.email::text,contact.altemail::text, contact.mobile_phone, contact.work_phone, contact.home_phone, contact.fax
],'|'),'|')
], '|') AS quicksearch_key
FROM customer
LEFT OUTER JOIN contact ON (customer.id = contact.customer_id)
GROUP BY customer.id;
and one of the triggers:
CREATE OR REPLACE FUNCTION customer_summary_update_for_contact() RETURNS trigger AS $$
DECLARE
_customer_id integer;
BEGIN
-- When a contact is added/removed/changed we have to regenerate the customer search key
IF tg_op = 'INSERT' OR tg_op = 'UPDATE' THEN
_customer_id = NEW.customer_id;
ELSE
_customer_id = OLD.customer_id;
END IF;
UPDATE customer_summary
SET quicksearch_key = (SELECT quicksearch_key FROM customer_quicksearch WHERE customer_id = _customer_id)
WHERE customer_id = _customer_id;
RETURN NULL;
END;
$$
LANGUAGE 'plpgsql'
SET search_path = 'public';
CREATE TRIGGER customer_summary_update_for_contact_trg AFTER INSERT OR UPDATE OR DELETE ON contact
FOR EACH ROW EXECUTE PROCEDURE customer_summary_update_for_contact();
You also need a trigger on customer to handle insert, update and delete of customer, maintaining the customer_summary record for that customer appropriately.
The customer_summary table contains records that include a quicksearch_key that's a pipe-concatenation of fields, like:
'1800MA|1800 MAKE IT BUILDERS|info#1800makeit.example.com|1234 5678|0499 999 999'
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^
[from customer record] [from 1st contact record] [from another contact record]
This is searched with a simple LIKE pattern. I could add a text_pattern_ops index on it for improved performance if I was doing prefix searches, but since I'm mostly doing searches with no left or right anchor - LIKE '%search%' - there's no benefit.

No-op (transformed into JOIN syntax) (not an anwer!) :
SELECT DISTINCT u.id, u.username
FROM
users AS u
JOIN user_invoice AS ui ON u.username like '%searchterm%'
OR ( ui.user_id = u.id AND ui.invoice = CAST('searchterm' AS INTEGER))
JOIN user_roles AS ur ON u.id = ur.user_id
JOIN roles AS r ON ur.role_id = r.id
WHERE r.name = 'teacher'
;
The CAST('searchterm' AS INTEGER)) makes no sense to me. Double quotes? parameter?

Related

How To Prevent Trigger Function from Creating Duplicative Rows

Goal: Create a trigger function to update a table, payment_detail. The table payment_detail comprises of an inner join of two other tables (customer & payment). The trigger function is focused only on updating payment_detail when an UPDATE operation occurs on the payment table.
Step 1: Create detail table
CREATE TABLE IF NOT EXISTS payment_detail AS
SELECT customer.customer_id,
customer.first_name,
customer.last_name,
payment.payment_id,
payment.amount,
to_char(payment.payment_date, 'Mon YYYY') AS month_yr
FROM payment
INNER JOIN customer ON payment.customer_id = customer.customer_id;
The table above works great. Next, I create a trigger function to automatically update the above table (I know, VIEWS would be better than updating a table, but that's not the problem):
Step 2: Create trigger function
CREATE OR REPLACE FUNCTION update_payment_detail()
RETURNS TRIGGER LANGUAGE PLPGSQL AS
$$
BEGIN
UPDATE payment_detail
SET
customer_id = NEW.customer_id,
first_name = customer.first_name,
last_name = customer.last_name,
payment_id = NEW.payment_id,
amount = NEW.amount,
month_yr = to_char(NEW.payment_date, 'Mon YYYY')
FROM
customer
WHERE customer.customer_id = NEW.customer_id;
RETURN NULL;
END;
$$
CREATE TRIGGER update_payment_detail
AFTER UPDATE
ON payment
FOR EACH ROW
EXECUTE PROCEDURE update_payment_detail();
Step 3: Test the trigger function
Now, in order to test the trigger function, I update the payment table as follows:
UPDATE payment
SET amount = 4.35
WHERE payment_id = 32126 AND customer_id = 1;
View the payment_detail table to verify updated record:
SELECT * FROM payment_detail WHERE customer_id = 1;
The result is a single record (the same exact record I updated above) being repeated throughout the entire payment_detail table and that's the problem. Why does it do that? There should only be one such record, among many other unique records. And if I DROP the payment_detail at this point and then re-create it and then just run the SELECT * FROM payment_detail statement above, the payment_detail table comes out perfectly fine with just the one record updated. So it's not clear what is happening here. How could I resolve this?
Take a look at your update script:
UPDATE payment_detail
SET
customer_id = NEW.customer_id,
first_name = customer.first_name,
last_name = customer.last_name,
payment_id = NEW.payment_id,
amount = NEW.amount,
month_yr = to_char(NEW.payment_date, 'Mon YYYY')
FROM
customer
WHERE customer.customer_id = NEW.customer_id;
It says you update all payment_detail records with the data from customer of currently updated record. So on every update you update all the rows in payment_detail while you should update only the rows matching current payment (add AND payment_detail.payment_id = OLD.payment_id to your WHERE part).
EDIT 1
So the result UPDATE statement would look like:
UPDATE payment_detail SET
customer_id = NEW.customer_id,
first_name = customer.first_name,
last_name = customer.last_name,
payment_id = NEW.payment_id,
amount = NEW.amount,
month_yr = to_char(NEW.payment_date, 'Mon YYYY')
FROM
customer
WHERE
customer.customer_id = NEW.customer_id
AND payment_detail.payment_id = OLD.payment_id;
You have to use OLD.payment_id(not NEW) in WHERE clause to handle cases where payment ID changes.

SELECT WHERE statement in a JOIN

I'm trying to make a query with a SELECT statement in a JOIN but couldn't get it to work.
The tables I have are below :
CREATE TABLE check_result
(id int,
check_result_id int,
id_relation int);
INSERT INTO check_result
values(1, 12, 1), (2,9, 1),(3,13, 3);
CREATE TABLE relation
(id int,
name VARCHAR(20),
id_group int);
INSERT INTO relation
values(1, 'pietje', 1), (2,'klaasje', 1),(3,'Harry', 3);
CREATE TABLE groups
(id int,
name VARCHAR(20),
id_sub int);
INSERT INTO groups
values(1, 'support_worker 1',2),(2, 'support_worker 2',2),(3, 'support_worker 2',3);
The query I have thus far is something like :
SELECT R.name , G.name
FROM check_result CR
LEFT JOIN relation R ON R.id = CR.id_relation
LEFT JOIN groups G ON R.id_group = (SELECT id_sub
FROM groups
WHERE name = 'support_worker 2'
AND id_sub = R.id_group )
In the end I was hoping for 3 records in the results but instead there are 6, with the correct results from groups.
Is there somebody who can show me what I'm doing wrong?
With that dataset and without your expected results it is hard to give you a solid answer.
SELECT R.name , G.name
FROM check_result CR
LEFT JOIN relation R ON R.id = CR.id_relation
LEFT JOIN groups G ON G.id_sub = R.id_group and G.name = 'support_worker 2'
You mentioned wanting all 3 results, but your sub select was causing duplicate records to appear.
Is it not a case as the above of not needing to rely on the sub select and simply adding more conditions onto your left join?
One additional thing worth mentioning - as I have little knowledge on what you database structure is but if Groups has an Id that is being references in R.id_group then you should join that and not Id_sub which would change your code to be:
SELECT R.name , G.name
FROM check_result CR
LEFT JOIN relation R ON R.id = CR.id_relation
LEFT JOIN groups G ON G.id = R.id_group and G.name = 'support_worker 2'
Giving the same result in the limited data.
SQL Fiddle

Delete a record if the associated Join entry does not exist

I'm trying to delete a record from a table if the parent table record does not exist.
The table in question are merchants and merchant_configurations
merchant_configurations has a foreign key(merchant_id) reference to merchant table primary key (id)
Here how the 2 tables looks
== merchant_configurations
id integer
merchant_id integer
config_options hstore
Merchant table
== merchants
id integer
name string
Now, select query to retrieve all those merchant_configurations record for whose merchant record is delete look like this
select merchant_configurations.id from merchant_configurations LEFT JOIN merchants ON merchant_configurations.merchant_id = merchants.id where merchants.id IS NULL
Now, I essentially want is to delete all those record but for some reason
DELETE merchants_configurations from select merchant_configurations.id from merchant_configurations LEFT JOIN merchants ON merchant_configurations.merchant_id = merchants.id where merchants.id IS NULL
Does not seem to work.
The only way I manage to get it done using WITH clause.
WITH zombie_configurations AS (
select merchant_configurations.id from merchant_configurations LEFT JOIN
merchants ON merchant_configurations.merchant_id = merchants.id where
merchants.id IS NULL
)
DELETE from merchant_configurations where id IN (select id from zombie_configurations);
Now my question is:
Is it possible to delete the Record using normal way without having to do the WITH clause and stuff
Use NOT EXISTS, it is simple and efficient:
SELECT FROM merchant_configurations mc
WHERE NOT EXISTS (SELECT 1
FROM merchants m
WHERE mc.merchant_id = m.id);
You can also use USING:
DELETE FROM merchant_configurations AS mc
USING merchant_configurations AS mc2
LEFT JOIN merchants ON mc2.merchant_id = merchants.id
WHERE mc2.id = mc.id AND merchants.id IS NULL

Help needed with Update trigger t-sql

How to create trigger on Update in transact sql, to set another field in updated row?
For example:
UPDATE table SET true_false = 1 WHERE ID = #ID
will run command:
UPDATE table SET date = GETDATE() WHERE ID = #ID
.
Please help. I can't figure it out ;)
Keep in mind that you must always allow for the possibility of multi-row updates in any trigger you write.
create trigger tr_U_YourTable
on YourTable
for Update
as
begin
if update(true_false)
update yt
set date = getdate()
from Inserted i
inner join Deleted d
on i.ID = d.ID
inner join YourTable yt
on i.ID = yt.ID
where coalesce(i.true_false,0) <> coalesce(d.true_false,0)
end

T-SQL: Selecting rows to delete via joins

Scenario:
Let's say I have two tables, TableA and TableB. TableB's primary key is a single column (BId), and is a foreign key column in TableA.
In my situation, I want to remove all rows in TableA that are linked with specific rows in TableB: Can I do that through joins? Delete all rows that are pulled in from the joins?
DELETE FROM TableA
FROM
TableA a
INNER JOIN TableB b
ON b.BId = a.BId
AND [my filter condition]
Or am I forced to do this:
DELETE FROM TableA
WHERE
BId IN (SELECT BId FROM TableB WHERE [my filter condition])
The reason I ask is it seems to me that the first option would be much more effecient when dealing with larger tables.
Thanks!
DELETE TableA
FROM TableA a
INNER JOIN TableB b
ON b.Bid = a.Bid
AND [my filter condition]
should work
I would use this syntax
Delete a
from TableA a
Inner Join TableB b
on a.BId = b.BId
WHERE [filter condition]
Yes you can. Example :
DELETE TableA
FROM TableA AS a
INNER JOIN TableB AS b
ON a.BId = b.BId
WHERE [filter condition]
Was trying to do this with an access database and found I needed to use a.* right after the delete.
DELETE a.*
FROM TableA AS a
INNER JOIN TableB AS b
ON a.BId = b.BId
WHERE [filter condition]
It's almost the same in MySQL, but you have to use the table alias right after the word "DELETE":
DELETE a
FROM TableA AS a
INNER JOIN TableB AS b
ON a.BId = b.BId
WHERE [filter condition]
The syntax above doesn't work in Interbase 2007. Instead, I had to use something like:
DELETE FROM TableA a WHERE [filter condition on TableA]
AND (a.BId IN (SELECT a.BId FROM TableB b JOIN TableA a
ON a.BId = b.BId
WHERE [filter condition on TableB]))
(Note Interbase doesn't support the AS keyword for aliases)
I'm using this
DELETE TableA
FROM TableA a
INNER JOIN
TableB b on b.Bid = a.Bid
AND [condition]
and #TheTXI way is good as enough but I read answers and comments and I found one things must be answered is using condition in WHERE clause or as join condition. So I decided to test it and write an snippet but didn't find a meaningful difference between them. You can see sql script here and important point is that I preferred to write it as commnet because of this is not exact answer but it is large and can't be put in comments, please pardon me.
Declare #TableA Table
(
aId INT,
aName VARCHAR(50),
bId INT
)
Declare #TableB Table
(
bId INT,
bName VARCHAR(50)
)
Declare #TableC Table
(
cId INT,
cName VARCHAR(50),
dId INT
)
Declare #TableD Table
(
dId INT,
dName VARCHAR(50)
)
DECLARE #StartTime DATETIME;
SELECT #startTime = GETDATE();
DECLARE #i INT;
SET #i = 1;
WHILE #i < 1000000
BEGIN
INSERT INTO #TableB VALUES(#i, 'nameB:' + CONVERT(VARCHAR, #i))
INSERT INTO #TableA VALUES(#i+5, 'nameA:' + CONVERT(VARCHAR, #i+5), #i)
SET #i = #i + 1;
END
SELECT #startTime = GETDATE()
DELETE a
--SELECT *
FROM #TableA a
Inner Join #TableB b
ON a.BId = b.BId
WHERE a.aName LIKE '%5'
SELECT Duration = DATEDIFF(ms,#StartTime,GETDATE())
SET #i = 1;
WHILE #i < 1000000
BEGIN
INSERT INTO #TableD VALUES(#i, 'nameB:' + CONVERT(VARCHAR, #i))
INSERT INTO #TableC VALUES(#i+5, 'nameA:' + CONVERT(VARCHAR, #i+5), #i)
SET #i = #i + 1;
END
SELECT #startTime = GETDATE()
DELETE c
--SELECT *
FROM #TableC c
Inner Join #TableD d
ON c.DId = d.DId
AND c.cName LIKE '%5'
SELECT Duration = DATEDIFF(ms,#StartTime,GETDATE())
If you could get good reason from this script or write another useful, please share. Thanks and hope this help.
Let's say you have 2 tables, one with a Master set (eg. Employees) and one with a child set (eg. Dependents) and you're wanting to get rid of all the rows of data in the Dependents table that cannot key up with any rows in the Master table.
delete from Dependents where EmpID in (
select d.EmpID from Employees e
right join Dependents d on e.EmpID = d.EmpID
where e.EmpID is null)
The point to notice here is that you're just collecting an 'array' of EmpIDs from the join first, the using that set of EmpIDs to do a Deletion operation on the Dependents table.
In SQLite, the only thing that work is something similar to beauXjames' answer.
It seems to come down to this
DELETE FROM table1 WHERE table1.col1 IN (SOME TEMPORARY TABLE);
and that some temporary table can be crated by SELECT and JOIN your two table which you can filter this temporary table based on the condition that you want to delete the records in Table1.
The simpler way is:
DELETE TableA
FROM TableB
WHERE TableA.ID = TableB.ID
DELETE FROM table1
where id IN
(SELECT id FROM table2..INNER JOIN..INNER JOIN WHERE etc)
Minimize use of DML queries with Joins. You should be able to do most of all DML queries with subqueries like above.
In general, joins should only be used when you need to SELECT or GROUP by columns in 2 or more tables. If you're only touching multiple tables to define a population, use subqueries. For DELETE queries, use correlated subquery.
You can run this query:
DELETE FROM TableA
FROM
TableA a, TableB b
WHERE
a.Bid=b.Bid
AND
[my filter condition]