I have two tables:
Account ID | A | B
-------------------
1 | x | y
2 | c | f
3 |...|...
the first table is a general account list. The second table is a list of documents on hand for each acct:
Account ID | Doctype
---------------------
1 | chrgoff
2 | dtpmnt
2 | chrgoff
3 | lstpmt
3 | suit
For the report I'm creating, I need to create a column in the first table which stores the value of a flag, where 'Y' indicates that the second table contains the docType 'chrgoff' for a given account number.
I tried doing this with the following case statement, but the query won't execute at all:
'chgoff' =
CASE
WHEN EXISTS(SELECT docType FROM table2 WHERE docType='chrgoff' and AccountID=table1.accountID)
THEN 'Y'
ELSE 'N'
END
I'm very new to T-SQL programming, so I would appreciate any help I could get! Let me know if I need to clarify anything. Thanks!
You code looks okay, but I would suggest:
(CASE WHEN EXISTS (SELECT docType FROM table2 t2 WHERE t2.docType = 'chrgoff' and t2.AccountID = table1.accountID)
THEN 'Y'
ELSE 'N'
END) as chgoff
The main differences are:
No single quotes on the column name. Only use single quotes for string and date constants.
Qualify the column references in the subquery. Don't depend on SQL's scoping rules. Be explicit.
As for as versus =. I prefer the former because it is standard SQL; = only assigns column aliases in SQL Server and related databases.
Alternate way - You can flag a record by joining both the tables with left outer join. I believe it would be faster approach than EXIST with subquery.
SQL -
select t1.*,
case when t2.account_id is not null then 'Y' else 'N' end as chgoff
from table1 t1
left join table2 t2 on t1.account_id = t2.account_id and t2.doctype = 'chrgoff'
Related
I have a multi-table join (only two shown in example) where I need to retain all rows from the base table. Obviously I use a LEFT JOIN to include all rows on the base table. Without the WHERE clause it works great – When a row doesn’t exist in the Right table the row from the Left table still shows, just with a 0 from the column in the Right table. The first two rows in the dataset are Labels from the Left table and Count of rows from the Right table, grouped by Label. All I want is a count of 0 when a label does not have a value from Table2 assigned.
Table1
Label | FK
----------
Blue | 1
Red | 2
Green | 3
Table2
Values | pk | Date
---------------------------
Dog | 1 | 02/02/2010
Cat | 2 | 02/02/2010
Dog | 1 | 02/02/2010
Cat | 2 | 02/02/2010
Query:
SELECT 1.Label, COUNT(2.values)
FROM Table1 1
LEFT JOIN Table2 2 ON 1.fk = 2.pk
GROUP BY 1.Label
Good Result Set - No filters
Blue | 2
Red | 2
Green | 0
Great!
My issue is that when I add filtering criteria to remove rows from the Right table the row is removed for my Left join rows (zeroing them out), the Left rows are dropped. I need the Left rows to remain even if their count is filtered down to zero.
SELECT 1.Label, COUNT(2.values)
FROM Table1 1
LEFT JOIN Table2 2 ON 1.fk = 1.pk
WHERE 2.Date BETWEEN '1/1/2010' AND '12/31/2010'
GROUP BY 1.Label
Bummer Result Set - After Filters
Blue | 2
Red | 2
Dukes!
So, what the hell? Do I need to get a temp table with the filtered dataset THEN join it to the Left table? What am I missing?
Thanks!
Do a second join or recursive join. Get my “good” join table, get a second “filtered” table, then LEFT JOIN them
You are filtering on the second table in the where. The values could be NULL and NULL fails the comparisons.
Move the where condition to the on clause:
SELECT 1.Label, COUNT(2.values)
FROM Table1 1 LEFT JOIN
Table2 2
ON 1.fk = 1.pk AND
2.Date BETWEEN 1/1/2010 AND 12/31/2010
GROUP BY 1.Label
Note:
The date formats retain the dates from the question. However, I don't advocate using BETWEEN for dates and the conditions should use standard date formats:
SELECT 1.Label, COUNT(2.values)
FROM Table1 1 LEFT JOIN
Table2 2
ON 1.fk = 1.pk AND
2.Date >= '2010-01-01' AND
2.Date < '2011-01-01'
GROUP BY 1.Label;
Some databases support the SQL Standard keyword DATE to identify date constants.
simply move the condition in WHERE clause to the ON clause.
LEFT JOIN Table2 2 ON 1.fk = 1.pk AND 2.Date BETWEEN '1/1/2010' AND '12/31/2010'
First of all i don't think it is a good idea to have number as an alias. Use a character or a word instead.
I would put the criteria in the join.
SELECT T1.Label, COUNT(T2.values)
FROM Table1 T1
LEFT JOIN Table2 T2 ON T1.fk = T1.pk
AND T2.Date BETWEEN '20100101' AND '20101231'
GROUP BY T1.Label
Other comments:
I would use ANSI(yyyyMMdd) format for the dates, so there is no missunderstanding.
I would be aware of the BETWEEN as it is not clear what you want to do if the date is '20101231 01:00' Would you like to include that date or not? >= and <= or >= and < is clearer.
If you are using SQL Server, put single quotes around the dates. If not, then it will evaluate the expression instead of the date.
BETWEEN '1/1/2010' AND '12/31/2010'
So, I have been trying to write a query in SQL but facing an issue. I am trying to write a 'belongs to' kind of condition. What I want to do is if the values being fetched belongs to a column in another table then populate one thing otherwise populate null.
for ex.
NAME table
ID NAMES
1 A
2 B
3 C
4 D
5 E
XYZ table
ID
2
4
5
I wrote the query something like this
(CASE WHEN NAME.ID IN (SELECT ID FROM XYZ) THEN NAME.NAMES ELSE NULL END ) AS 'ABC'
This query does run but it has been running for 14 hours (OBVIOUSLY FOR A VERY HUGE AMOUNT OF DATA) and still there is no result. Is there some flaw in this logic or is there some better way it could be done?
I expect a result like this:
ABC
NULL
B
NULL
D
E
You just need a plain left join here:
SELECT
CASE WHEN t2.ID IS NOT NULL THEN t1.NAMES END AS ABC
FROM NAME t1
LEFT JOIN XYZ t2
ON t1.ID = t2.ID;
Demo
Note that a CASE expressions else condition, if not explicitly specified, defaults to NULL. This behavior works here because you want to render NULL if a given record in the NAME table does not match to any record in the XYZ table.
The problem isn't your logic. It is simply how the code is optimized. The subquery is probably being run for each row in the outer query.
I would recommend switching to exists:
(case when exists (select 1 from xyz where xyz.id = name.id) then name.names
end) as abc
This maintains the semantics of your original query. In particular, there is no danger that duplicates in xyz would return multiple rows (as would happen with a left join).
For performance for this -- or for the left join -- you want an index on xyz(id).
I've created an SQL query (Oracle) that is a join of 2 tables, and this table has entries related to users joining or leaving a particular instance.
So, let's say that the table looks something like:
+------------+--------+
| User_ID | State |
+------------+--------+
| 101 | Joined |
+------------+--------+
| 102 | Joined |
+------------+--------+
| 101 | Left |
+------------+--------+
As you can see, user 101 Joined, but then left. Is it possible to somehow specify that in case user left, then all entries related to this user are removed from the table (user ID is unique), but in case there is only entry saying that user Joined, then user will stay in the table?
I am new to SQL, my apologies if the question lacks some details, please feel free to ask for clarification.
To remove the records pertaining to users that have left, you might try the following:
DELETE FROM mytable m1
WHERE EXISTS ( SELECT 1 FROM mytable m2
WHERE m2.user_id = m1.user_id
AND m2.state = 'Left' );
Alternately (this might make things a bit more clear):
DELETE FROM mytable m1
WHERE m1.user_id IN ( SELECT m2.user_id
FROM mytable m2
WHERE m2.state = 'Left' );
Hope this helps.
You can get the list of ID that has the State "Left" and then delete those IDs
DELETE FROM tablename
WHERE user_id IN (SELECT user_id
FROM tablename
WHERE state = "left")
You can do something like this:
select t.*
from t
where not exists (select 1 from t t2 where t2.id = t.id and t.state = 'Left');
You can easily turn this to a delete:
delete t
where exists (select 1 from t t2 where t2.id = t.id and t.state = 'Left');
I have two tables both with a column called Name. Sometimes the names begin with an uppercase letter whereas other times not. I would like to join the two tables on the names so that bob's match the Bob's. I assume that this could be possible with regular expressions, so how can one construct an SQL JOIN query that does do this match and what is the correct regex?
For example: Say I have table1 as :
Name| col1
----------
Bob | a
Jon | b
and table 2 as:
Name| col2
----------
bob| c
Jon| d
I would join them as follows (with xx being the missing regex and yy being the correct selection)
SELECT * , yy as NameWithCapAtFront
FROM table1 as t1
LEFT JOIN table2 as t2
ON xx(t1.Name)=xx(t2.Name)
but this misses the bob match with Bob.
Further, how would one always select capitalised version of the Name.
How about just using the lower() function?
SELECT *, upper(substr(t1.name, 1, 1) || lower(t1.name, 2) as NameWithCapAtFront
FROM table1 as t1 LEFT JOIN
table2 as t2
ON lower(t1.Name) = lower(t2.Name);
Admittedly, this lower cases the whole name, but that seems reasonable in this case.
Is there a way to query different databases based on the value of a column in the query?
Say for example you have the following columns:
id
part_id
attr_id
attr_value_ext
attr_value_int
You then run a query and if the attr_id is '1' is returns the attr_value_int column but if attr_id is greater than '1' it joins data from another table based on the attr_value_ext.
something like this?
select case when a.id = 1 then a.attr_value_int
when a.id > 1 then b.some_other_col
else null end as whatever
from first_table a
left outer join other_table b
on ( a.attr_val_ext = b.id )
/
You could use a conditional in the on clause, like:
select case when attr_id = 1 then attr_value_int
when attr_id = 2 then t1.value_int
when attr_id = 3 then t2.value_int
end
from YourTable yt
left join Table1 t1
on t1.attr_id = 2 and yt.part_id = t1.part_id
left join Table2 t2
on t1.attr_id = 3 and yt.part_id = t2.part_id
This will work best if the number of tables is relatively small, and known in advance. Otherwise you'll have to resort to dynamic SQL, or building a follow up query from the client.
hard to give an exact answer based on that description...
you should be able to do that with a UNION
select stuff from one table where attr_id = 1
UNION
select stuff from another table where attr_id > 1
Nothing in the SQL spec, but there are often DB specific functions that will do this for you. For example, decode on oracle would work for you.
SELECT ID,PART_ID,ATTR_ID,CASE
WHEN attr_id =1 THEN attr_value_int
WHEN attr_id >1 THEN <SELECT QUERY>
END <My Column>
from TABLE;
Maybe this will work