sql join problem with negatives - c#

I need to select data from two table using a join. This is fairly simple and have no problems here. The problem occurs when the field I am joining is used as two separate foreign keys (I didn't design this). So the ID field that I join on is either a positive or negative number.
If it's a positive number it relates to ID_1 on the table_2 table, if it's a negative, the number relates to ID_2 on the table_2 table. However the ID_2 will be a positive number (even though it's stored as a negative in the foreign key). Obviously there are no constraints to enforce these - so in essence not real foreign keys :/
The SQL I'm using goes something like this and is fine for the positive numbers:
select t1.Stuff, t2.MoreStuff from table_1 t1
join table_2 t2 on t1.ID_1 = t2.ID_1
where ...
How to incorporate the negative aspect of this into the join. Is this even possible? Ideally I'd like to alter the table to my needs but apparently this is not a valid option. I'm well and truly stuck.
The only other idea I've had is a separate sql statement to handle these odd ones. This is all being run by clr sql from C#. Adding a separate SqlCommand to the code will most likely slow things down hence why I'd prefer to keep it all in one command.
Your input is welcome, thanks :)

Let's say the tables look like this:
Table1 (id INT, foo INT, fk INT)
Table2 (id1 INT, id2 INT, bar VARCHAR(100))
...where fk can be used to look up a row in Table2 using id1 if positive and id2 if negative.
Then you can do the join as follows:
SELECT T1.id, T1.foo, T2.bar
FROM Table1 T1 INNER JOIN Table2 T2
ON (T1.fk > 0 AND T2.id1 = T1.fk)
OR (T1.fk < 0 AND T2.id2 = - T1.fk)

Simpliest way - join these tables using UNION ALL:
select t1.Stuff, t2.MoreStuff from table_1 t1
join table_2 t2 on t1.ID_1 = t2.ID_1
where t1._ID_1>0
UNION ALL
select t1.Stuff, t2.MoreStuff from table_1 t1
join table_2 t2 on abs(t1.ID_1) = t2.ID_2
where t1._ID_1<0

This won't be very performant...but then, nothing will. You need to transform your negative key into a positive one, and conditional logic for the join. Like this:
select t1.Stuff, t2.MoreStuff
from table_1 t1
join table_2 t2 on (t1.ID_1 > 0 AND t1.ID_1 = t2.ID_1)
OR (t1.ID_1 <0 AND ABS(t1.ID_1) = t2.ID_2)
where ...
No chance of using an index, because you're transforming t1.ID_1 (with the ABS function), but it's the best that you can do given the circumstances.

You can do something like this, but only after introducing the schema designer to a LART:
SELECT
t1.stuff, COALESCE(t2a.morestuff, t2b.morestuff)
FROM
table_1 t1
LEFT JOIN table_2 t2a ON (t1.id_1 > 0 AND t1.id_1 = t2a.id_1)
LEFT JOIN table_2 t2b ON (t1.id_1 < 0 AND t1.id_1 = -1 * t2b.id_2)
// etc
Alternatively,
SELECT
t1.stuff, t2.morestuff
FROM
table_1 t1
LEFT JOIN table_2 t2 ON (
(t1.id_1 > 0 AND t1.id_1 = t2.id_1)
OR (t1.id_1 < 0 AND t1.id_1 = -1 * t2.id_2)
)
// etc
Remember the LART, that's the most important part!

try this
DECLARE #Table TABLE(
ID INT,
ForeignKeyID INT
)
INSERT INTO #Table (ID,ForeignKeyID) SELECT 1, 1
INSERT INTO #Table (ID,ForeignKeyID) SELECT 2, 2
INSERT INTO #Table (ID,ForeignKeyID) SELECT 3, -1
INSERT INTO #Table (ID,ForeignKeyID) SELECT 4, -2
DECLARE #ForeignTable TABLE(
ID_1 INT,
ID_2 INT,
Val VARCHAR(MAX)
)
INSERT INTO #ForeignTable (ID_1,ID_2,Val) SELECT 1, 11, '1'
INSERT INTO #ForeignTable (ID_1,ID_2,Val) SELECT 2, 22, '2'
INSERT INTO #ForeignTable (ID_1,ID_2,Val) SELECT 3, 1, '3'
INSERT INTO #ForeignTable (ID_1,ID_2,Val) SELECT 3, 2, '4'
SELECT *
FROM #Table t INNER JOIN
#ForeignTable ft ON ABS(t.ForeignKeyID) =
CASE
WHEN t.ForeignKeyID > 0
THEN ft.ID_1
ELSE
ft.ID_2
END

It will have to be something like
select t1.Stuff, t2.MoreStuff from table_1 t1, table_2 t2 where (t1.ID_1 = t2.ID_1 OR t1.ID_1 = CONCAT("-",t2.ID_1)) where ...
Not sure if I have misunderstood your question.

By applying left joins across table two and using the absolute value function, you should be able to accomplish what you're looking for:
SELECT t1.Stuff, isnull(t2.MoreStuff, t2_2.MoreStuff)
FROM table_1 t1
LEFT JOIN table_2 t2 ON t1.ID_1 = t2.ID_1
AND t1.ID_1 > 0
LEFT JOIN table_2 t2_2 ON abs(t1.ID_2) = t2_2.ID_2
AND t1.ID_2 < 0
WHERE
...
The caveat here is that if ID_1 and ID_2 are not mutually exclusive you will get 2 query results.

select t1.Stuff, t2.MoreStuff from table_1 t1
join table_2 t2 on t1.ID_1 = t2.ID_1 or -t1.ID_1 = t2.ID_2
where ...

Related

Replace the following query in sqlite

I have a sql server query as follows in a store procedure with parameters #firstId and #secondId
IF #firstId = ''
begin
SELECT DISTINCT x, y =
FROM t1
LEFT OUTER JOIN t2 ON t1.id = t2.id AND t2.id = #secondId
ORDER BY t1.somecolumn
END
ELSE
BEGIN
SELECT DISTINCT x, y =
FROM t1
LEFT OUTER JOIN t2 ON t1.id = t2.id AND t2.id = #secondId
AND t1.id = #firstId
ORDER BY t1.somecolumn
END
I am very new to sqlite and trying to write the above query in sqlite with case statements as suggested in the sqlite documentation but so far not able to achieve the same. I want achieve something like below or a better query.
CASE WHEN #firstId = '' THEN (CHOOSE FIRST SELECT STATEMENT) ELSE (CHOOSE SECOND SELECT STATEMENT) END
I thought of splitting the original sql query into two select statements in sqlite and call respective query based on the condition handled in c# code base file as shown below but not sure if this is the good practice.
if(firstId == string.empty)
{
//call the first select statement
}
else
{
//call the second select statement
}
I don't want to split the store procedure into two separate queries. Any help is appreciated.
You can do in one go. Above 2 statements are equivalent to this:
SELECT DISTINCT x, y
FROM t1
LEFT OUTER JOIN t2 ON t1.id = t2.id AND t2.id = #secondId
AND (t1.id = #firstId OR #firstId = '')
ORDER BY t1.somecolumn

Compare two dataset and add coulm in 1st dataset if exists in 2nd dataset

I have two datatable :
table1:
id value label
1 val1 lbl1
2 val2 lbl2
table2:
id value label
1 val1 lbl1
I want to compare two datatable and add a column to 1st table say 'assigned'= 'true' if it exists in table2 else false.
How do I do this? Either in c# or TSQL would help.
So the final result what I expect is:
id value label assigned
1 val1 lbl1 true
2 val2 lbl2 false
here since id=1 exists in both tables it is marked as assigned and id=2 which is only present in table1 and not in table2 then it is marked as false in assigned column.
EDIT
I have two separate select queries to get table1 and table2 data:
Here are my queries:
query1:
select Id,label,Values from
t1 inner join t2
on ID=t2.UDF1ValueID inner join t3
on t2.UDFID = t3.UDF_ID
where ID=15
query2
select Id,label,Values from t4 w
inner join t2 on(w.Id=t2.Id)
inner join t3 on (t3.ID=t2.ID)
inner join t4 on (t3.ID=t4.ID)
where w_Id=5 and t2.ID=15
You can do this using LEFT JOIN with a combination of CASE statement:
-- Sample Data
WITH table1(id, value, label) AS(
SELECT 1, 'val1', 'lbl1' UNION ALL
SELECT 2, 'val2', 'lbl2'
),
table2(id, value, label) AS(
SELECT 1, 'val1', 'lbl1'
)
-- Solution
SELECT
t1.*,
assigned = CASE WHEN t2.id IS NOT NULL THEN 1 ELSE 0 END
FROM table1 t1
LEFT JOIN table2 t2
ON t2.id = t1.id
declare #t table (Id int,val varchar(10),label varchar(10))
insert into #t (id,val,label)values(1,'val1','lbl1'),(2,'val2','lbl2')
declare #tt table (Id int,val varchar(10),label varchar(10))
insert into #tt (id,val,label)values(1,'val1','lbl1')
select tt.id,tt.val,tt.label,CASE WHEN ttt.Id IS NOT NULL THEN 'TRUE' ELSE 'FALSE' END 'Assigned'
from #t tt INNER JOIN #tt ttt ON tt.Id = ttt.Id
OR
select tt.id,tt.val,tt.label,CASE WHEN tt.Id IS NOT NULL THEN 'TRUE' ELSE 'FALSE' END 'Assigned'
from #t tt where exists (select 1 from #tt t where t.Id = tt.Id)
C# way of doing.
Please note this code here just handles schema but not data.
DataTable t1;
DataTable t2;
foreach( DataColumn c in t1.Columns)
{
if(!t2.Columns.Contains(c.ColumnName))
{
t2.Columns.Add(new DataColumn(c.ColumnName,c.DataType));
}
}
Working sample can be found here

Query in Ms Access, how to make a chain of joins (with its own criteria) that only show up if the first part of the join exists

I'm trying to make a query that makes a new table from 7 different tables, and then I need to use this query in a C# program, so I wouldn't be able to make 2 queries to do this.
How would I make a join that only shows up if the two parts of the join are equal, and if it does show up, it gets more info from a third table and only really shows up if one of its columns in the third table equals 2.
For example, I have a lot of different joins, and at one part, Table 1, I join it to Table 2. I want everything from table 1 to show up, and only the matching ones from table 2. and if table 1 and 2 match, the info in table 2 only shows up if it fits a certain criteria from table 3, which is join to table 2.
I hope that makes sense?
Thanks!
EDIT
okay the main problem is am I able to set criteria that only happens for a certain join and not the whole query?
From the info you have provided it sounds like you want to left join table 2 to table 1, and inner join table 3 to table 2, using your criteria.
for example, i have alot of different joints, and at one part, Table 1, i join it to Table 2.
Ok, you want Table 1 to join to Table 2.
I want everything from table 1 to show up, and only the matching ones from table 2.
This is called a Left Join from Table1 to Table2. A Left Join returns all records from the left hand table, even if there is no matching record in the right hand table.
So, so far we have
SELECT *
FROM Table1 T1
LEFT JOIN Table2 T2 ON T1.ID = T2.ID
and if table 1 and 2 match, the info in table 2 only shows up if it fits a certain criteria from table 3, which is joint to table 2.
This means you want an inner join between Table2 and Table3. An Inner Join returns all records that match both the left and right hand tables. Let's combine this with our first query:
SELECT *
FROM Table1 T1
LEFT JOIN Table2 T2 ON T1.ID = T2.ID
INNER JOIN Table3 T3 ON T3.ID = T2.ID
However, this won't work. This only returns records that match all 3 tables because the Inner Join is applied to the result from the first Left Join.
What you need in this circumstance is a derived table. You can get one by using the following query:
SELECT *
FROM #Table1 T1
LEFT JOIN (
SELECT T2.ID as T2ID, T2.HairColor as HairColor, T3.ID as T3ID, T3.Age as Age FROM #Table2 T2
INNER JOIN #Table3 T3 ON T3.ID = T2.ID
WHERE T3.Age = 3
) as T2SubSet ON t1.ID = T2SubSet.T2ID
This is evaluated by creating the subset of table 2/3 that you are looking for and then taking the result of that query and joining it to table 1 with a left join. For a full complete example, you can use the following query:
DECLARE #Table1 TABLE(
ID int,
Name varchar(10))
DECLARE #Table2 TABLE(
ID int,
HairColor varchar(10))
DECLARE #Table3 TABLE(
ID int,
Age int)
INSERT INTO #Table1 values (1, 'John'), (2, 'Mary'), (3,'Sue')
INSERT INTO #Table2 values (1, 'Red'), (2, 'Brown'), (3, 'Black')
INSERT INTO #Table3 values (1, 3), (2, 5), (4,8)
SELECT *
FROM #Table1 T1
LEFT JOIN #Table2 T2 ON T1.ID = T2.ID
INNER JOIN #Table3 T3 ON T3.ID = T2.ID
SELECT *
FROM Table1 T1
LEFT JOIN (
SELECT T2.ID as T2ID, T2.HairColor as HairColor, T3.ID as T3ID, T3.Age as Age FROM Table2 T2
INNER JOIN Table3 T3 ON T3.ID = T2.ID
) as T2SubSet ON t1.ID = T2SubSet.T2ID
I also just noticed you said:
columns in the third table equals 2.
This can be done by adding another 'On' statement like 'T3.Column = 2' Using my last query above, it would be
SELECT *
FROM #Table1 T1
LEFT JOIN (
SELECT T2.ID as T2ID, T2.HairColor as HairColor, T3.ID as T3ID, T3.Age as Age FROM #Table2 T2
INNER JOIN #Table3 T3 ON T3.ID = T2.ID AND T3.Age = 2
) as T2SubSet ON t1.ID = T2SubSet.T2ID

How to set an integer value to one if a record exist in database C# Sql Query

getName_as_Rows is an array which contains some names.
I want to set an int value to 1 if record found in data base.
for(int i = 0; i<100; i++)
{
using (var command = new SqlCommand("select some column from some table where column = #Value", con1))
{
command.Parameters.AddWithValue("#Value", getName_as_Rows[i]);
con1.Open();
command.ExecuteNonQuery();
}
}
I am looking for:
bool recordexist;
if the above record exist then bool = 1 else 0 with in the loop.
If have to do some other stuff if the record exist.
To avoid making N queries to the database, something that could be very expensive in terms of processing, network and so worth, I suggest you to Join only once using a trick I learned. First you need a function in your database that splits a string into a table.
CREATE FUNCTION [DelimitedSplit8K]
--===== Define I/O parameters
(#pString VARCHAR(8000), #pDelimiter CHAR(1))
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 0 up to 10,000...
-- enough to cover VARCHAR(8000)
WITH E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), --10E+1 or 10 rows
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS (--==== This provides the "zero base" and limits the number of rows right up front
-- for both a performance gain and prevention of accidental "overruns"
SELECT 0 UNION ALL
SELECT TOP (DATALENGTH(ISNULL(#pString,1))) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
SELECT t.N+1
FROM cteTally t
WHERE (SUBSTRING(#pString,t.N,1) = #pDelimiter OR t.N = 0)
)
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY s.N1),
Item = SUBSTRING(#pString,s.N1,ISNULL(NULLIF(CHARINDEX(#pDelimiter,#pString,s.N1),0)-s.N1,8000))
FROM cteStart s
GO
Second, concatenate your 100 variables into 1 string:
"Value1", "Value 2", "Value 3"....
In Sql Server you can just join the values with your table
SELECT somecolumn FROM sometable t
INNER JOIN [DelimitedSplit8K](#DelimitedString, ',') v ON v.Item = t.somecolumn
So you find 100 strings at a time with only 1 query.
Use var result = command.ExecuteScalar() and check if result != null
But a better option than to loop would be to say use a select statement like
SELECT COUNT(*) FROM TABLE WHERE COLUMNVAL >= 0 AND COLUMNVAL < 100,
and run ExecuteScalar on that, and if the value is > 0, then set your variable to 1.

Selecting multiple row from one row in SQL

I have the following output with me from multiple tables
id b c b e b g
abc 2 123 3 321 7 876
abd 2 456 3 452 7 234
abe 2 0 3 123 7 121
abf 2 NULL 3 535 7 1212
Now I want to insert these values into another table and the insert query for a single command is as follows:
insert into resulttable values (id,b,c), (id,b,e) etc.
For that I need to do a select such that it gives me
id,b,c
id,b,e etc
I dont mind getting rid of b too as it can be selected using c# query.
How can I achieve the same using a single query in sql. Again please note its not a table its an output from different tables
My query should look as follows: from the above I need to do something like
select b.a, b.c
union all
select b.d,b.e from (select a,c,d,e from <set of join>) b
But unfortunately that does not work
INSERT resulttable
SELECT id, b, c
FROM original
UNION
SELECT id, b, e
FROM original
Your example has several columns named 'b' which isn't allowed...
Here, #tmporigin refers to your original query that produces the data in the question. Just replace the table name with a subquery.
insert into resulttable
select
o.id,
case a.n when 1 then b1 when 2 then b2 else b3 end,
case a.n when 1 then c when 2 then e else g end
from #tmporigin o
cross join (select 1n union all select 2 union all select 3) a
The original answer below, using CTE and union all requiring CTE evaluation 3 times
I have the following output with me from multiple tables
So set that query up as a Common Table Expression
;WITH CTE AS (
-- the query that produces that output
)
select id,b1,c from CTE
union all
select id,b2,e from CTE
union all
select id,b3,g from CTE
NOTE - Contrary to popular belief, your CTE while conveniently written once, is run thrice in the above query, once for each of the union all parts.
NOTE ALSO that if you actually name 3 columns "b" (literally), there is no way to identify which b you are referring to in anything that tries to reference the results - in fact SQL Server will not let you use the query in a CTE or subquery.
The following example shows how to perform the above, as well as (if you show the execution plan) revealing that the CTE is run 3 times! (the lines between --- BELOW HERE and --- ABOVE HERE is a mock of the original query that produces the output in the question.
if object_id('tempdb..#eav') is not null drop table #eav
;
create table #eav (id char(3), b int, v int)
insert #eav select 'abc', 2, 123
insert #eav select 'abc', 3, 321
insert #eav select 'abc', 7, 876
insert #eav select 'abd', 2, 456
insert #eav select 'abd', 3, 452
insert #eav select 'abd', 7, 234
insert #eav select 'abe', 2, 0
insert #eav select 'abe', 3, 123
insert #eav select 'abe', 7, 121
insert #eav select 'abf', 3, 535
insert #eav select 'abf', 7, 1212
;with cte as (
---- BELOW HERE
select id.id, b1, b1.v c, b2, b2.v e, b3, b3.v g
from
(select distinct id, 2 as b1, 3 as b2, 7 as b3 from #eav) id
left join #eav b1 on b1.b=id.b1 and b1.id=id.id
left join #eav b2 on b2.b=id.b2 and b2.id=id.id
left join #eav b3 on b3.b=id.b3 and b3.id=id.id
---- ABOVE HERE
)
select b1, c from cte
union all
select b2, e from cte
union all
select b3, g from cte
order by b1
You would be better off storing the data into a temp table before doing the union all select.
Instead of this which does not work as you know
select b.a, b.c
union all
select b.d,b.e from (select a,c,d,e from <set of join>) b
You can do this. Union with repeated sub-select
select b.a, b.c from (select a,c,d,e from <set of join>) b
union all
select b.d, b.e from (select a,c,d,e from <set of join>) b
Or this. Repeated use of cte.
with cte as
(select a,c,d,e from <set of join>)
select b.a, b.c from cte b
union all
select b.d, b.e from cte b
Or use a temporary table variable.
declare #T table (a int, c int, d int, e int)
insert into #T values
select a,c,d,e from <set of join>
select b.a, b.c from #T b
union all
select b.d, b.e from #T b
This code is not tested so there might be any number of typos in there.
I'm not sure if I understood Your problem correctly, but i have been using something like this for some time:
let's say we have a table
ID Val1 Val2
1 A B
2 C D
to obtain a reslut like
ID Val
1 A
1 B
2 C
2 D
You can use a query :
select ID, case when i=1 then Val1 when i=2 then Val2 end as Val
from table
left join ( select 1 as i union all select 2 as i ) table_i on i=i
which will simply join the table with a subquery containing two values and create a cartesian product. In effect, all rows will be doubled (or multiplied by how many values the subquery will have). You can vary the number of values depending on how many varsions of row You'll need. Depending on the value of i, Val will be Val1 or Val2 from original table. If you'll see the execution plan, there will be a warning that the join has no join predicates (because of i=i), but it is ok - we want it.
This makes queries a bit large (in terms of text) because of all the case when, but are quite easy to read if formatted right. I needed it for stupid tables like "BigID, smallID1, smallID2...smallID11" that was spread across many columns I don't know why.
Hope it helps.
Oh, I use a static table with 10000 numbers, so i just use
join tab10k on i<=10
for 10x row.
I apologize for stupid formatting, I'm new here.

Categories

Resources