Create additional rows in select statement result in SQL Server - sql

I am creating a fairly complex SELECT query with joins across multiple tables and views. The primary table is joined with several secondary tables. The result set of rows needs to be further split up based on approximately 25 CASE statement conditions for two scenarios:
Where one column is checked for a particular value based on which a new row has to be created with some unique values for a few columns;
Where two columns using an AND condition have to be checked in order to create a new row
Here is an example:
SELECT col1, col2, col3, col25, col26, col27, col48
FROM T1
INNER JOIN Table2 on Table2.col1 = T1.col1
INNER JOIN Table3 on Table3.col2 = T1.col1
Col25 is being compared with a column (Table2.ColA) from an INNER JOIN and this is where my CASE conditions are required, approximately 25 of them:
CASE WHEN Table2.ColA = 'W-%' THEN Col25 = 'WATER'
and I also need to insert one or more additional rows where
Col25 = 'REL',
Col26 = 5000,
Col27 = 'M'
Basically what the CASE expression is supposed to do is replace a value for the resulting row from my SELECT statement, and also insert one or more rows with different values for Col25, Col26, Col27 while keeping other column values the same as those returned by the SELECT statement.
I understand that this might involve more than just my select statements and could require traversing a cursor containing the result of my primary query in order to create the news rows. Is this the only route available to me? Any help or pointers would be much appreciated.

Option 1
with q as ( -- your query
select ...
)
select * from q
union all
select ... from q
where <conditions requiring a copy>
Option 2
inner join (values ('org'), ('new')) copy(flag) on flag = 'org' or
flag = 'new' and 1 = case
when col25 = 'A' then 1
when col25 = 'B' and col26 = 'X' then 1
...
end
For output columns you'd need similar cases to distinguish between original row and new row.

Related

How to correctly sum the values for a unique ID with the condition

How to sum the values in Number field in table2 when the ID field is unique and field Type = 3 (range 1-5)?
SELECT TOP (100)
TB1.CarID
FROM table1 as TB1
LEFT JOIN table2 as TB2 on TB1.CarID = TB2.CarID
WHERE
GROUP BY
Table1
Table2
I have tried several solutions:
,SUM (CASE WHEN TB2.Type = 3 THEN TB2.Number END)
Return result is incorrect x2, possibly due to a large number of table joins. If you have any comment why the values are displayed x2, please give me a hint. I would like to add an additional condition, i.e. sum only when the value in the id column is unique. I believe this can solve the problem.
,SUM (CASE WHEN TB2.Type = 3 AND TB2.ID is UNIQUE THEN TB2.Number END) [incorrect]
I will be grateful for your help!
Part of the problem is likely that you join on carid, but that is not unique in Either table.
At present you have 5 copies of 'aaaad' in Table1 and 5 copies in Table2. When you join them, you get 25 rows back (each row in T1 matching against 5 rows in T2).
So, I'd start with aggregating the second table before joining on it...
SELECT
*
FROM
table1 AS t1
LEFT JOIN
(
SELECT
carid,
SUM(number) AS number
FROM
table2
WHERE
type = 3
GROUP BY
carid
)
AS t2
ON t1.CarID = t2.CarID
That way you avoid multiplying the number of rows.

SQL Server: add values of small tables to the values of big table without losing the dimensions of the big table?

I have 3 tables. I want to add corresponding values from second table and third table to the first table in the picture below. Each table has an ID by which they can be matched, ... field in the pictures. The first table has 1531 rows with an ID column and 8 other columns. This table, the top table in the pictures, is almost full of zeroes.
I have tried to join the tables in different ways but the problem is that each table has different number of rows and hence different number unique IDs. The top table has all IDs.
Is there some convenient way to add the second table to the first table and then the third table to that result?
Result of Left Join as suggested Suzena: why do the numbers not get summed up together?
Method1: Joins
select a.id,(a.col1 + b.col1+c.col1) as col1, (a.col2 + b.col2 + c.col2) as col2, (a.col3 + b.col3 + c.col3) as col3
from
table1 a
left join
table2 b
on a.id = b.id
left join
table3 c
on a.id = c.id;
Method2: Unions
select id,sum(col1) col1, sum(col2) col2, sum(col3) col3
from
(
select id,col1,col2,col3
from table1
union all
select id,col1,col2,col3
from table2
union all
select id,col1,col2,col3
from table3
) t
group by id
Let me know if you have any different criteria.
Method 3: having different number of fields so use NULL or 0
SELECT
[MID],
SUM([KEVAT 201501-04]) AS 'KEVAT 201501-04',
SUM([KESA 201504-06]) AS 'KESA 201504-06',
SUM([SYKSY 201507-09]) AS 'SYKSY 201507-09',
SUM([TALVI 201510-12]) AS 'TALVI 201510-12',
SUM([KEVAT 201601-04]) AS 'KEVAT 201601-04',
SUM([KESA 201604-06]) AS 'KESA 201604-06',
SUM([SYKSY 201607-09]) AS 'SYKSY 201607-09',
SUM([TALVI 201610-12]) AS 'TALVI 201610-12'
FROM
(
SELECT * FROM TABLE1
UNION ALL
SELECT [MID]
,0 AS 'KEVAT 201501-04'
,0 AS 'KESA 201504-06'
,0 AS 'SYKSY 201507-09'
,0 AS 'TALVI 201510-12'
,[KEVAT 201601-04]
,[KESA 201604-06]
,[SYKSY 201607-09]
,[TALVI 201610-12]
FROM TABLE2
UNION ALL
SELECT [MID]
,[KEVAT 201501-04]
,[KESA 201504-06]
,[SYKSY 201507-09]
,[TALVI 201510-12]
,0 AS 'KEVAT 201601-04'
,0 AS 'KESA 201604-06'
,0 AS 'SYKSY 201607-09'
,0 AS 'TALVI 201610-12'
FROM TABLE3
) a
GROUP BY [MID]
If i understand your question, you could use an union. Something like:
insert into table1(col1,col2,col3,col4)
(select col1,col2,col3,col4 from table2 union
select col1,col2,col3,col4 from table3)
The names of the columns of table2 and table3 must match. Use alias for that.
Try using MERGE
--Get data from table 2 and merge into table 1
MERGE Table_1 AS TARGET
USING (SELECT [ID]
,[KEVAT 201501-04]
,[KESA 201504-06]
,[SYKSY 201507-09]
,[TALVI 201510-12] FROM Table_2) AS SOURCE
ON (TARGET.ID = SOURCE.ID)
WHEN MATCHED
THEN UPDATE SET
TARGET.[KEVAT 201501-04] = SOURCE.[KEVAT 201501-04],
TARGET.[KESA 201504-06] = SOURCE.[KESA 201504-06],
TARGET.[SYKSY 201507-09] = SOURCE.[SYKSY 201507-09],
TARGET.[TALVI 201510-12] = SOURCE.[TALVI 201510-12];
GO
--Get data from table 3 and merge into table 1
MERGE Table_1 AS TARGET
USING (SELECT [ID]
,[KEVAT 201601-01]
,[KESA 201604-06]
,[SYKSY 201607-09]
,[TALVI 201610-12] FROM Table_3) AS SOURCE
ON (TARGET.ID = SOURCE.ID)
WHEN MATCHED
THEN UPDATE SET
TARGET.[KEVAT 201601-01] = SOURCE.[KEVAT 201601-01],
TARGET.[KESA 201604-06] = SOURCE.[KESA 201604-06],
TARGET.[SYKSY 201607-09] = SOURCE.[SYKSY 201607-09],
TARGET.[TALVI 201610-12] = SOURCE.[TALVI 201610-12];
GO

SQLite table aliases effecting the performance of queries

How does SQLite internally treats the alias?
Does creating a table name alias internally creates a copy of the same table or does it just refers to the same table without creating a copy?
When I create multiple aliases of the same table in my code, performance of the query is severely hit!
In my case, I have one table, call it MainTable with namely 2 columns, name and value.
I want to select multiple values in one row as different columns. for example
Name: a,b,c,d,e,f
Value: p,q,r,s,t,u
such that a corresponds to p and so on.
I want to select values for names a,b,c and d in one row => p,q,r,s
So I write a query
SELECT t1.name, t2.name, t3.name, t4.name
FROM MainTable t1, MainTable t2, MainTable t3, MainTable t4
WHERE t1.name = 'a' and t2.name = 'b' and t3.name = 'c' and t4.name = 'd';
This way f writing the query kills the performance when size of the table increases as rightly pointed above by Larry.
Is there any efficient way to retrieve this result. I am bad at SQL queries :(
If you list the same table more than once in your SQL statement and do not supply conditions on which to JOIN the tables, you are creating a cartesian JOIN in your result set and it will be enormous:
SELECT * FROM MyTable A, MyTable B;
if MyTable has 1000 records, will create a result set with one million records. Any other selection criteria you include will then have to be evaluated across all one million records.
I'm not sure that's what you're doing (your question is very unclear), but it may be a start on solving your problem.
Updated answer now that the poster has added the query that is being executed.
You're going to have to get a little tricky to get the results you want. You need to use CASE and MAX and, unfortunately, the syntax for CASE is a little verbose:
SELECT MAX(CASE WHEN name='a' THEN value ELSE NULL END),
MAX(CASE WHEN name='b' THEN value ELSE NULL END),
MAX(CASE WHEN name='c' THEN value ELSE NULL END),
MAX(CASE WHEN name='d' THEN value ELSE NULL END)
FROM MainTable WHERE name IN ('a','b','c','d');
Please give that a try against your actual database and see what you get (of course, you want to make sure the column name is indexed).
Assuming you have table dbo.Customers with a million rows
SELECT * from dbo.Customers A
does not result in a copy of the table being created.
As Larry pointed out, the query as it stands is doing a cartesian product across your table four times which, as you has observed, kills your performance.
The updated ticket states the desire is to have 4 values from different queries in a single row. That's fairly simple, assuming this syntax is valid for sqllite
You can see that the following four queries when run in serial produce the desired value but in 4 rows.
SELECT t1.name
FROM MainTable t1
WHERE t1.name='a';
SELECT t2.name
FROM MainTable t2
WHERE t2.name='b';
SELECT t3.name
FROM MainTable t3
WHERE t3.name='c';
SELECT t4.name
FROM MainTable t4
WHERE t4.name='d';
The trick is to simply run them as sub queries like so there are 5 queries: 1 driver query, 4 sub's doing all the work. This pattern will only work if there is one row returned.
SELECT
(
SELECT t1.name
FROM MainTable t1
WHERE t1.name='a'
) AS t1_name
,
(
SELECT t2.name
FROM MainTable t2
WHERE t2.name='b'
) AS t2_name
,
(
SELECT t3.name
FROM MainTable t3
WHERE t3.name='c'
) AS t3_name
,
(
SELECT t4.name
FROM MainTable t4
WHERE t4.name='d'
) AS t4_name
Aliasing a table will result a reference to the original table that exists for the duration of the SQL statement.

SQL Statement Performance Issue on Informix

I have this Informix SQL statement which takes ages to run. Does anybody see any way to optimize it so it wouldn't take so long?
SELECT * FROM OriginalTable WHERE type = 'S' AND flag <> 'S' INTO TEMP TempTableA;
SELECT * FROM OriginalTable WHERE type = 'Z' AND flag <> 'S' INTO TEMP TempTableB;
UPDATE OriginalTable SET flag = 'D' WHERE Serialnumber in
(
select Serialnumber from TempTableA
WHERE NOT EXISTS(SELECT * FROM TempTableB
WHERE TempTableB.Col1 = TempTableA.Col1
AND TempTableB.Col2 = TempTableA.Col2)
)
I have in my OriginalTable around 300 million rows, TempTableA 93K rows, and TempTableB 58K rows.
Update OriginalTable
Set flag = 'D'
Where Type = 'S'
And Flag <> 'S'
And Not Exists (
Select 1
From OriginalTable As T1
Where T1.Type = 'Z'
And T1.flag <> 'S'
And T1.Col1 = OriginalTable.Col1
And T1.Col2 = OriginalTable.Col2
)
In a similar approach as #tombom stated. Pre-query only the columns you care about to keep the temp table smaller. If you are dealing with a table of 60 columns, you are filling a whole lot more than just 3-4 columns where your primary consideration are valid serial numbers. Pre-test the query to make sure it gives you the correct set you are expecting, then apply that to your SQL-update.
So here, the inner query are the ones you DO NOT WANT... Since you were comparing against only column 1 and column 2 from this table, that's all I'm pre-querying. I'm then doing a LEFT JOIN to this inner result set on COL1 and COL2. I know, you want to EXCLUDE THOSE FOUND IN THIS result set... That's why, in the OUTER WHERE clause, I've added "AND ExcludeThese.Col1 IS NULL". So, any instances from OT1 that never existed in the subquery are good to go (via left join), and those that WERE FOUND, WILL have a match on col1 and col2, but THOSE will be excluded via the "and" clause I've described.
SELECT OT1.SerialNumber
FROM OriginalTable OT1
LEFT JOIN ( select OT2.Col1,
OT2.Col2
FROM OriginalTable OT2
where OT2.type = 'Z'
AND OT2.flag <> 'S' ) ExcludeThese
ON OT1.Col1 = ExcludeThese.Col1
AND OT1.Col2 = ExcludeThese.Col2
WHERE OT1.type = 'S'
AND OT1.flag <> 'S'
AND ExcludeThese.Col1 IS NULL
ORDER BY
OT1.SerialNumber
INTO
TEMP TempTableA;
Again, test this query by itself to make sure you ARE getting the records you expect. To help clarify the records returned, change the above select to include more columns for a mental / sanity check, such as
SELECT OT1.SerialNumber,
OT1.Col1,
OT1.Col2,
ExcludeThese.Col1 JoinedCol1,
ExcludeThese.Col2 JoinedCol2
from <keep rest of query intact>
Now, you'll be able to see the serial number and instances of those columns that would or not be joined to the "excludeThese" resultset... Try again, but remove only the
"AND ExcludeThese.Col1 IS NULL" clause, and you'll see the other lines and WHY they are being excluded -- that is if you DID have any questions to the content.
Once you are satisfied with the pre-query... which will only return the single column of SerialNumber, that can be index/optimized since you are pulling into a temp table, build an index, then apply your update.
UPDATE OriginalTable
SET flag = 'D'
WHERE Serialnumber in ( select Serialnumber from TempTableA );
I was too lazy to test with test data, but maybe this can do?
SELECT col1, col2,
CASE WHEN type = 'S' THEN 1
ELSE WHEN type = 'Z' THEN 2 END AS filteredType
FROM OriginalTable WHERE (type = 'S' OR type = 'Z') AND flag <> 'S' INTO TempTable;
UPDATE OriginalTable SET flag = 'D' WHERE Serialnumber IN
(
SELECT t1.Serialnumber FROM TempTable t1
LEFT JOIN TempTable t2 ON (t1.col1 = t2.col2 AND t1.col2 = t2.col2)
WHERE t1.filteredType = 1
AND t2.filteredType = 2
AND t2.Serialnumber IS NULL
)
That way you can omit one loading into temp table. On the other hand there will be no index on the new column filteredType.
Also I have no idea of informix. Hope it helps anyway.

SELECT clause: Optional Columns?

I want to run a single query on my MYSQL database. The table I am searching has a file_type field that will be one of three values (1,2,3). Depending what these values are I want to look for different information from the database.
So for example if the 'file_type' field is 1 or 2 then I want to SELECT fields a,b,c,d
However if I notice that file_type = 3 then I want to SELECT fields a,b,c,d,e,f
Can this be done in a single SELECT statement? like this - ish
my_simple_query = SELECT file_type,a,b,c,d FROM table1
my_new_query = SELECT file_type,a,b,c,d (AND e,f IF file_type = 3) FROM table1
thanks all
---------------------------------- ADDITION -----------------------------------
And how would I do this if e,f were stored in another table?
my_multitable_query = SELECT file_type,id,a,b,c,d (AND e,f FROM table2 WHERE id=id) FROM table1
get my drift?
No, SQL SELECT statements do not support optional columns.
But you can specify logic to only return values based on criteria, otherwise they would be null:
SELECT a,b,c,d,
CASE WHEN file_type = 3 THEN t2.e ELSE NULL END AS e,
CASE WHEN file_type = 3 THEN t2.f ELSE NULL END AS f
FROM TABLE_1 t1
JOIN TABLE_2 t2 ON t2.id = t1.id
You may be happier using a non-relational database such as CouchDB or MongoDB if you need non-relational data.
In SQL, you have to declare all the columns at the time you write the query. All these columns must be present on every row of the result set; the set of columns cannot vary based on the content in each row. This is practically the definition of being a relational database (along with the requirement that each column has the same type on every row).
Regarding your additional question, this may be getting somewhere:
SELECT t1.file_type, t1.a, t1.b, t1.c, t1.d, t2.e, t2.f
FROM table1 t1
LEFT OUTER JOIN table2 ON t1.id=t2.id;
You'll still get columns e and f on every row of the result set, even if they're NULL because there is no match in table2 for a given row in table1.
This leads to you a pattern called Class Table Inheritance.
Not familiar with mysql, but why not using a UNION?
> SELECT file_type,a,b,c,d FROM table1 WHERE file_type in (1,2)
> UNION
> SELECT file_type,a,b,c,d,e,f FROM table1 WHERE file_type = 3