Building a temp table /map - sql

I'm working on a stored procedure in SQL Server 2000 with a temp table defined like this:
CREATE TABLE #MapTable (Category varchar(40), Code char(5))
After creating the table I want to insert some standard records (which will then be supplemented dynamically in the procedure). Each category (about 10) will have several codes (typically 3-5), and I'd like to express the insert operation for each category in one statement.
Any idea how to do that?
The best idea I've had so far is to keep a real table in the db as a template, but I'd really like to avoid that if possible. The database where this will live is a snapshot of a mainframe system, such that the entire database is blown away every night and re-created in a batch process- stored procedures are re-loaded from source control at the end of the process.
The issue I'm trying to solve isn't so much keeping it to one statement as it is trying to avoid re-typing the category name over and over.

DJ's is a fine solution but could be simplified (see below).
Why does it need to be a single statement?
What's wrong with:
insert into #MapTable (category,code) values ('Foo','AAAAA')
insert into #MapTable (category,code) values ('Foo','BBBBB')
insert into #MapTable (category,code) values ('Foo','CCCCC')
insert into #MapTable (category,code) values ('Bar','AAAAA')
For me this is much easier to read and maintain.
Simplified DJ solution:
CREATE TABLE #MapTable (Category varchar(40), Code char(5))
INSERT INTO #MapTable (Category, Code)
SELECT 'Foo', 'AAAAA'
UNION
SELECT 'Foo', 'BBBBB'
UNION
SELECT 'Foo', 'CCCCC'
SELECT * FROM #MapTable
There's nothing really wrong with DJ's, it just felt overly complex to me.
From the OP:
The issue I'm trying to solve isn't so much keeping it to one statement as it
is trying to avoid re-typing the category name over and over.
I feel your pain -- I try to find shortcuts like this too and realize that by the time I solve the problem, I could have typed it long hand.
If I have a lot of repetitive data to input, I'll sometimes use Excel to generate the insert codes for me. Put the Category in one column and the Code in another; use all of the helpful copying techniques to do the hard work
then
="insert into #MapTable (category,code) values ('"&A1&"','"&B1&"')"
in a third row and I've generated my inserts
Of course, all of this is assuming that the Categories and Codes can't be pulled from a system table.

insert into #maptable (category, code)
select 'foo1', b.bar
from
( select 'bar11' as bar
union select 'bar12'
union select 'bar13'
) b
union
select 'foo2', b.bar
from
( select 'bar21' as bar
union select 'bar22'
union select 'bar23'
) b

This might work for you:
CREATE TABLE #MapTable (Category varchar(40), Code char(5))
INSERT INTO #MapTable
SELECT X.Category, X.Code FROM
(SELECT 'Foo' as Category, 'AAAAA' as Code
UNION
SELECT 'Foo' as Category, 'BBBBB' as Code
UNION
SELECT 'Foo' as Category, 'CCCCC' as Code) AS X
SELECT * FROM #MapTable

Here's the notation I ended up using. It's based on Arvo's answer, but a little shorter and uses cAse to help make things clearer:
SELECT 'foo1', b.code
FROM ( select 'bar11' as code
union select 'bar12'
union select 'bar13' ) b
UNION SELECT 'foo2', b.code
FROM ( select 'bar21' as code
union select 'bar22'
union select 'bar32' ) b
This way highlights the category names a little, lines up codes vertically, and uses less vertical space.

Related

Abbreviate a list in PostgreSQL

How can I abbreviate a list so that
WHERE id IN ('8893171511',
'8891227609',
'8884577292',
'886790275X',
.
.
.)
becomes
WHERE id IN (name of a group/list)
The list really would have to appear somewhere. From the point of view of your code being maintainable and reusable, you could represent the list in a CTE:
WITH id_list AS (
SELECT '8893171511' AS id UNION ALL
SELECT '8891227609' UNION ALL
SELECT '8884577292' UNION ALL
SELECT '886790275X'
)
SELECT *
FROM yourTable
WHERE id IN (SELECT id FROM cte);
If you have a persistent need to do this, then maybe the CTE should become a bona fide table somewhere in your database.
Edit: Using the Horse's suggestion, we can tidy up the CTE to the following:
WITH id_list (id) AS (
VALUES
('8893171511'),
('8891227609'),
('8884577292'),
('886790275X')
)
If the list is large, I would create a temporary table and store the list there.
That way you can ANALYZE the temporary table and get accurate estimates.
The temp table and CTE answers suggested will do.
Just wanted to bring another approach, that will work if you use PGAdmin for querying (not sure about workbench) and represent your data in a "stringy" way.
set setting.my_ids = '8893171511,8891227609';
select current_setting('setting.my_ids');
drop table if exists t;
create table t ( x text);
insert into t select 'some value';
insert into t select '8891227609';
select *
from t
where x = any( string_to_array(current_setting('setting.my_ids'), ',')::text[]);

MERGE INTO Performance

I have a table contains tat contains {service_id, service_name,region_name}
As input my procedure gets service_id , i_svc_region list of key,value pairs, which has {service_name, region}.
Have to insert into the table if the record does not exists already. I know it is a very simple query.. But does the below queries make any difference in performance?
which one is better and why?
MERGE INTO SERVICE_REGION_MAP table1
USING
(SELECT i_svc_region(i).key as service_name,i_enabled_regions(i).value as region
FROM dual) table2
ON (table1.service_id =i_service_id and table1.region=table2.region)
WHEN NOT MATCHED THEN
INSERT (service_id,service_name ,region) VALUES (i_service_id ,table2.service_name,table2.region);
i_service_id - is passed as it is.
MERGE INTO SERVICE_REGION_MAP table1
USING
(SELECT i_service_id as service_id, i_svc_region(i).key as service_name,i_enabled_regions(i).value as region
FROM dual) table2
ON (table1.service_id =table2.service_id and table1.region=table2.region)
WHEN NOT MATCHED THEN
INSERT (service_id,service_name ,region) VALUES (table2.service_id,table2.service_name,table2.region);
i_service_id is considered as column in table.
Does this really make any difference?
You should be using the FORALL statement. It will result in much faster performance than any looping we could write. Check out the documenation, starting with https://docs.oracle.com/database/121/LNPLS/forall_statement.htm#LNPLS01321
As #Brian Leach suggests the FORALL will give you a single round trip to SQL engine for all of the elements (i's) in your table. This can give between 10 and 100 times improvement depending on table size and many other things beyond me.
Also you are only using the INSERT capability of MERGE so a time honoured INSERT statement should make life easier/faster for the database. MERGE has more bells and whistles which can slow it down.
So try something like:
FORALL i IN 1..i_svc_region(i).COUNT
INSERT INTO SERVICE_REGION_MAP table1
(service_id, service_name, region)
SELECT
i_service_id AS service_id,
i_svc_region(i).KEY AS service_name,
i_enabled_regions(i).VALUE AS region
FROM DUAL table2
WHERE NOT EXISTS
( SELECT *
FROM SERVICE_REGION_MAP table1
WHERE table1.service_id=table2.service_id AND table1.region=table2.region
);

How to combine three tables into a new table

all with the same column headings and I would like to create one singular table from all three.
I'd also, if it is at all possible, like to create a trigger so that when one of these three source tables is edited, the change is copied into the new combined table.
I would normally do this as a view, however due to constraints on the STSrid, I need to create a table, not a view.
Edit* Right, this is a bit ridiculous but anyhow.
I HAVE THREE TABLES
THERE ARE NO DUPLICATES IN ANY OF THE THREE TABLES
I WANT TO COMBINE THE THREE TABLES INTO ONE TABLE
CAN SOMEONE HELP PROVIDE THE SAMPLE SQL CODE TO DO THIS
ALSO IS IT POSSIBLE TO CREATE TRIGGERS SO THAT WHEN ONE OF THE THREE TABLES IS EDITED THE CHANGE IS PASSED TO THE COMBINED TABLE
I CAN NOT CREATE A VIEW DUE TO THE FACT THAT THE COMBINED TABLE NEEDS TO HAVE A DIFFERENT STSrid TO THE SOURCE TABLES, CREATING A VIEW DOES NOT ALLOW ME TO DO THIS, NOR DOES AN INDEXED VIEW.
Edit* I Have Table A,Table B and Table C all with columns ORN, Geometry and APP_NUMBER. All the information is different so
Table A (I'm not going to give an example geometry column)
ORN ID
123 14/0045/F
124 12/0002/X
Table B (I'm not going to give an example geometry column)
ORN ID
256 05/0005/D
989 12/0012/X
Table C (I'm not going to give an example geometry column)
ORN ID
043 13/0045/D
222 11/0002/A
I want one complete table of all info
Table D
ORN ID
123 14/0045/F
124 12/0002/X
256 05/0005/D
989 12/0012/X
043 13/0045/D
222 11/0002/A
Any help would be greatly appreciated.
Thanks
If the creation of the table is a one time thing you can use a select into combined with a union like this:
select * into TableD from
(
select * from TableA
union all
select * from TableB
union all
select * from TableC
) UnionedTables
As for the trigger, it should be easy to set up a after insert trigger like this:
CREATE TRIGGER insert_trigger
ON TableA
AFTER INSERT AS
insert TableD (columns...) select (columns...) from inserted
Obviously you will have to change the columns... to match your structure.
I haven't checked the syntax though so it might not be prefect and it could need some adjustment, but it should give you an idea I hope.
If IDs are not duplicated it ill be easy to achieve it, in another case you can must add a OriginatedFrom column. You also can create a lot of instead off triggers (not only for insert but for delete and update) but that a lazy excuse for not refactoring the app.
Also you must pay attention for any reference for the data, since its a RELATIONAL model is likely to other tables are related to the table you are about to drop.
This is the code for create the table D
drop table D;
Select * into D from (select * from A Union all select* from B Union all select * from C);
Its rather simple Just Create Table_D First
CREATE TABLE_D
(
ORN INT,
ID VARCHAR(20),
Column3 Datatype
)
GO
Use INSERT statement to insert records into this table SELECTing and using UNION ALL operator from other three table.
INSERT INTO TABLE_D (ORN , ID, Column3)
SELECT ORN , ID, Column3
FROM Table_A
UNION ALL
SELECT ORN , ID, Column3
FROM Table_B
UNION ALL
SELECT ORN , ID, Column3
FROM Table_C
Trigger
You will need to create this trigger on all of the tables.
CREATE TRIGGER tr_Insert_Table_A
ON TABLE_A
FOR INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO TABLE_D (ORN , ID, Column3)
SELECT i.ORN , i.ID, i.Column3
FROM Inserted i LEFT JOIN TABLE_D D
ON i.ORN = D.ORN
WHERE D.ORN IS NULL
END
Read here to learn more about SQL Server Triggers

Select distinct values from multiple columns in same table

I am trying to construct a single SQL statement that returns unique, non-null values from multiple columns all located in the same table.
SELECT distinct tbl_data.code_1 FROM tbl_data
WHERE tbl_data.code_1 is not null
UNION
SELECT tbl_data.code_2 FROM tbl_data
WHERE tbl_data.code_2 is not null;
For example, tbl_data is as follows:
id code_1 code_2
--- -------- ----------
1 AB BC
2 BC
3 DE EF
4 BC
For the above table, the SQL query should return all unique non-null values from the two columns, namely: AB, BC, DE, EF.
I'm fairly new to SQL. My statement above works, but is there a cleaner way to write this SQL statement, since the columns are from the same table?
It's better to include code in your question, rather than ambiguous text data, so that we are all working with the same data. Here is the sample schema and data I have assumed:
CREATE TABLE tbl_data (
id INT NOT NULL,
code_1 CHAR(2),
code_2 CHAR(2)
);
INSERT INTO tbl_data (
id,
code_1,
code_2
)
VALUES
(1, 'AB', 'BC'),
(2, 'BC', NULL),
(3, 'DE', 'EF'),
(4, NULL, 'BC');
As Blorgbeard commented, the DISTINCT clause in your solution is unnecessary because the UNION operator eliminates duplicate rows. There is a UNION ALL operator that does not elimiate duplicates, but it is not appropriate here.
Rewriting your query without the DISTINCT clause is a fine solution to this problem:
SELECT code_1
FROM tbl_data
WHERE code_1 IS NOT NULL
UNION
SELECT code_2
FROM tbl_data
WHERE code_2 IS NOT NULL;
It doesn't matter that the two columns are in the same table. The solution would be the same even if the columns were in different tables.
If you don't like the redundancy of specifying the same filter clause twice, you can encapsulate the union query in a virtual table before filtering that:
SELECT code
FROM (
SELECT code_1
FROM tbl_data
UNION
SELECT code_2
FROM tbl_data
) AS DistinctCodes (code)
WHERE code IS NOT NULL;
I find the syntax of the second more ugly, but it is logically neater. But which one performs better?
I created a sqlfiddle that demonstrates that the query optimizer of SQL Server 2005 produces the same execution plan for the two different queries:
If SQL Server generates the same execution plan for two queries, then they are practically as well as logically equivalent.
Compare the above to the execution plan for the query in your question:
The DISTINCT clause makes SQL Server 2005 perform a redundant sort operation, because the query optimizer does not know that any duplicates filtered out by the DISTINCT in the first query would be filtered out by the UNION later anyway.
This query is logically equivalent to the other two, but the redundant operation makes it less efficient. On a large data set, I would expect your query to take longer to return a result set than the two here. Don't take my word for it; experiment in your own environment to be sure!
try something like SubQuery:
SELECT derivedtable.NewColumn
FROM
(
SELECT code_1 as NewColumn FROM tbl_data
UNION
SELECT code_2 as NewColumn FROM tbl_data
) derivedtable
WHERE derivedtable.NewColumn IS NOT NULL
The UNION already returns DISTINCT values from the combined query.
Union is applied wherever the row data required is similar in terms of type, values etc. It doesnt matter you have column in the same table or the other to retrieve from as the results would remain the same ( in one of the above answers already mentioned though).
As you didn't wanted duplicates theres no point using UNION ALL and use of distinct is simply unnecessary as union gives distinct data
Can create a view would be best choice as view is a virtual representation of the table. Modifications could be then done neatly on that view created
Create VIEW getData AS
(
SELECT distinct tbl_data.code_1
FROM tbl_data
WHERE tbl_data.code_1 is not null
UNION
SELECT tbl_data.code_2
FROM tbl_data
WHERE tbl_data.code_2 is not null
);
Try this if you have more than two Columns:
CREATE TABLE #temptable (Name1 VARCHAR(25),Name2 VARCHAR(25))
INSERT INTO #temptable(Name1, Name2)
VALUES('JON', 'Harry'), ('JON', 'JON'), ('Sam','harry')
SELECT t.Name1+','+t.Name2 Names INTO #t FROM #temptable AS tSELECT DISTINCT ss.value FROM #t AS t
CROSS APPLY STRING_SPLIT(T.Names,',') AS ss

Write INSERT statements with values next to column names?

When writing an INSERT statement with a lot of columns, it would be nice to have the value next to the column name like in an UPDATE statement. Something like:
insert into myTable
set
[col1] = 'xxx',
[col2] = 'yyy',
[col3] = 42
etc...
Are there any tricks to mimic this?
I thought I was onto something with this:
insert into myTable
select
[col1] = 'xxx',
[col2] = 'yyy',
[col3] = 42
etc...
But the aliases aren't actually being associated with the insert table's columns and if someone added a new column to the table it could really screw things up. Anyone have any other ideas of how one could do this?
The closest you'll get would be to specify the columns on the insert (this will protect you from your concern about a new column being added) and alias the values on the select (which gives you some degree of self-documenting code).
insert into myTable
([col1], [col2], [col3])
select 'xxx' as [col1], 'yyy' as [col2], 42 as [col3]
For big inserts, I've done fancy (and perhaps overfly fussy) layouts. Some examples:
INSERT MyTable ( MyTableId, Name, Description, SomeStringData1
,SomeStringData2, SomeStringData3, SomeStringData4, MoreStringData1
,MoreStringData2, MoreStringData3, MoreStringData4, SomeNumericData1
,SomeNumericData2, SomeNumericData3, SomeNumericData4, MoreNumericData1
,MoreNumericData2, MoreNumericData3, MoreNumericData4, BigBlobAA
,BigBlobBB, EnteredAtDate, UpdatedAtDate, RevisedAtDate
,NeedAnotherDate )
values
( #MyTableId, #Name, #Description, #SomeStringData1
,#SomeStringData2, #SomeStringData3, #SomeStringData4, #MoreStringData1
,#MoreStringData2, #MoreStringData3, #MoreStringData4, #SomeNumericData1
,#SomeNumericData2, #SomeNumericData3, #SomeNumericData4, #MoreNumericData1
,#MoreNumericData2, #MoreNumericData3, #MoreNumericData4, #BigBlobAA
,#BigBlobBB, #EnteredAtDate, #UpdatedAtDate, #RevisedAtDate
,#NeedAnotherDate )
This works if you're pretty darn certain that you wont ever be inserting columns or otherwise modifying what is being inserted. It gets gets everything on one screen, and makes it fairly simple to pick out what value goes into which column.
If inserted values are likely to change or are complex (such as case statements), I do the following (outdent all but every fifth item):
INSERT MyTable
(
MyTableId
,Name
,Description
,SomeStringData1
,SomeStringData2
,SomeStringData3
,SomeStringData4
,MoreStringData1
,MoreStringData2
,MoreStringData3
,MoreStringData4
,SomeNumericData1
,SomeNumericData2
,SomeNumericData3
,SomeNumericData4
,MoreNumericData1
,MoreNumericData2
,MoreNumericData3
,MoreNumericData4
,BigBlobAA
,BigBlobBB
,EnteredAtDate
,UpdatedAtDate
,RevisedAtDate
,NeedAnotherDate
)
values
(
MyTableId
,Name
,Description
,SomeStringData1
,SomeStringData2
,SomeStringData3
,SomeStringData4
,MoreStringData1
,MoreStringData2
,MoreStringData3
,MoreStringData4
,case
when something then 'A'
when orOther then 'B'
else 'Z'
end
,SomeNumericData2
,SomeNumericData3
,SomeNumericData4
,MoreNumericData1
,MoreNumericData2
,MoreNumericData3
,MoreNumericData4
,BigBlobAA
,BigBlobBB
,EnteredAtDate
,UpdatedAtDate
,RevisedAtDate
,NeedAnotherDate
)
(After adding that CASE statement, I "counted indents" to make sure I had everything lined up properly.)
It takes a bit of effort to get things layed out properly, but it can make maintenance, support, and subsequent modification simpler.
Basically, no. The syntax for SQL INSERT is to the list all the columns, then all the values. Even if you could find a trick to express the syntax the way you want it would be non-portable and confusing to the next person to have to maintain your code.
If you want a visual mapping of columns to values use something along the lines of Philip's answer, above. But please don't spend any of your valuable time to make your code avoid the standard syntax.
Once you want to insert multiple rows simultaneously, using either 2008 style row constructors, or using the older union all syntax, you tend to be thankful that you don't have to specify the column names for every row.
2008 row constructors:
insert into myTable (col1,col2,col3)
values ('xxx','yyy',42),
('abc','def',19),
('HJK','www',-4)
UNION all:
insert into myTable (col1,col2,col3)
select 'xxx','yyy',42 union all
select 'abc','def',19 union all
select 'HJK','www',-4
The quick solution I came up with was the following:
insert into MyTable
(ColumnName1, ColumnName2, ColumnName3)
values
(/*ColumnName1*/'value1', /*ColumnName2*/'value2', /*ColumnName3*/'value3')
INSERT INTO myTable (col1, col2, col3)
SELECT 'xxx' [col1], 'yyy' [col2], '42' [col3]