SQL Server Parsing - sql-server

I have data in one column as below semi colon delimited. I want to parse this in separate rows.
9000389003; 9000389000; 9000389093; 9000383895; 9000490984; 9000389000
9000490980; 9000593580; 9000593599; 9000498085; 9000389003; 9000593580; 9000490990
9000489405; 9000435334; 9000535090; 9000995990

In SQL Server 2016+ you can use string_split().
In SQL Server pre-2016, using a CSV Splitter table valued function by Jeff Moden:
create table t (id int not null identity(1,1), str varchar(8000));
insert into t values
('9000389003; 9000389000; 9000389093; 9000383895; 9000490984; 9000389000')
,('9000490980; 9000593580; 9000593599; 9000498085; 9000389003; 9000593580; 9000490990')
,('9000489405; 9000435334; 9000535090; 9000995990');
select
t.id
, s.ItemNumber
, Item = ltrim(s.Item)
from t
cross apply [dbo].[delimitedsplit8K](t.str,';') as s
rextester demo: http://rextester.com/AVQL94047
returns:
+----+------------+------------+
| id | ItemNumber | Item |
+----+------------+------------+
| 1 | 1 | 9000389003 |
| 1 | 2 | 9000389000 |
| 1 | 3 | 9000389093 |
| 1 | 4 | 9000383895 |
| 1 | 5 | 9000490984 |
| 1 | 6 | 9000389000 |
| 2 | 1 | 9000490980 |
| 2 | 2 | 9000593580 |
| 2 | 3 | 9000593599 |
| 2 | 4 | 9000498085 |
| 2 | 5 | 9000389003 |
| 2 | 6 | 9000593580 |
| 2 | 7 | 9000490990 |
| 3 | 1 | 9000489405 |
| 3 | 2 | 9000435334 |
| 3 | 3 | 9000535090 |
| 3 | 4 | 9000995990 |
+----+------------+------------+
splitting strings reference:
Tally OH! An Improved SQL 8K “CSV Splitter” Function - Jeff Moden
Splitting Strings : A Follow-Up - Aaron Bertrand
Split strings the right way – or the next best way - Aaron Bertrand
string_split() in SQL Server 2016 : Follow-Up #1 - Aaron Bertrand

If you cannot use SQL-Server 2016 for string_split() or if you are not allowed to create a new function, or if you just want a simpe ad-hoc approach you might do it this way:
DECLARE #tbl TABLE(id int not null identity, YourString varchar(1000));
INSERT INTO #tbl VALUES
('9000389003; 9000389000; 9000389093; 9000383895; 9000490984; 9000389000')
,('9000490980; 9000593580; 9000593599; 9000498085; 9000389003; 9000593580; 9000490990')
,('9000489405; 9000435334; 9000535090; 9000995990');
WITH Casted AS
(
SELECT t.id
,CAST('<x>' + REPLACE(YourString,';','</x><x>') + '</x>' AS XML) AsXml
FROM #tbl AS t
)
SELECT id
,nodes.value('(./text())[1]','bigint') AS TheNumber
FROM Casted
CROSS APPLY Casted.AsXml.nodes('/x') AS The(nodes)
One advantage might be - other than usual string splitter approaches - that you get the values typed (as bigint in this case).

Related

Use column values as filenames when COPYing into a stage

Is there an out-of-the-box method for Snowflake to use values from a column as a filename when using COPY INTO #mystage? The goal is to copy X number of files into an s3 stage (essentially PARTITION BY column1), but straight to the stage, not creating subfolders. X would be the number of distinct values in a column.
This can obviously be done manually:
copy into #mystage/mycustomfilename
However, the better option would be something like this:
copy into #mystage/$column1
Is there a version of this that Snowflake supports?
As mentioned above, the PARTITION BY setting parses data into subfolders and the subfolders are named using the values in the specified column, but Snowflake still uses a generic filename within each subfolder.
Created structure -
create temporary table temp_tab_split_members(seq_id number, member_id number, name varchar2(30));
+----------------------------------------------------+
| status |
|----------------------------------------------------|
| Table TEMP_TAB_SPLIT_MEMBERS successfully created. |
+----------------------------------------------------+
Fake data -
insert into temp_tab_split_members
with cte as
(select seq4(),(trim(mod(seq4(),4))+1)::integer,'my name-'||seq4() from table(generator(rowcount=>12)))
select * from cte;
+-------------------------+
| number of rows inserted |
|-------------------------|
| 12 |
+-------------------------+
Checking data format -
select * from TEMP_TAB_SPLIT_MEMBERS order by member_id;
+--------+-----------+------------+
| SEQ_ID | MEMBER_ID | NAME |
|--------+-----------+------------|
| 0 | 1 | my name-0 |
| 4 | 1 | my name-4 |
| 8 | 1 | my name-8 |
| 1 | 2 | my name-1 |
| 5 | 2 | my name-5 |
| 9 | 2 | my name-9 |
| 2 | 3 | my name-2 |
| 6 | 3 | my name-6 |
| 10 | 3 | my name-10 |
| 3 | 4 | my name-3 |
| 7 | 4 | my name-7 |
| 11 | 4 | my name-11 |
+--------+-----------+------------+
Checked stage is empty
list #test_row_stage;
+------+------+-----+---------------+
| name | size | md5 | last_modified |
|------+------+-----+---------------|
+------+------+-----+---------------+
Main procedure to generate files
EXECUTE IMMEDIATE $$
DECLARE
company varchar2(30);
BU varchar2(30);
eval_desc varchar2(30);
member_id varchar2(30);
file_name varchar2(30);
c1 CURSOR FOR SELECT distinct member_id FROM temp_tab_split_members;
BEGIN
for record in c1 do
member_id:=record.member_id;
file_name:='load'||'_'||member_id||'.csv';
execute immediate 'copy into #test_row_stage/'||:file_name||' from
(select * from temp_tab_split_members where member_id='||:member_id||') overwrite=false';
end for;
RETURN 0;
END;
$$
;
+-----------------+
| anonymous block |
|-----------------|
| 0 |
+-----------------+
Check stage contents after procedure execution
list #test_row_stage; -- output truncated columnwise
+----------------------------------------+------+
| name | size |
|----------------------------------------+------+
| test_row_stage/load_1.csv_0_0_0.csv.gz | 48 |
| test_row_stage/load_2.csv_0_0_0.csv.gz | 48 |
| test_row_stage/load_3.csv_0_0_0.csv.gz | 48 |
| test_row_stage/load_4.csv_0_0_0.csv.gz | 48 |
File contents cross-check
select $1,$2,$3 from #test_row_stage/load_1.csv_0_0_0.csv.gz union
select $1,$2,$3 from #test_row_stage/load_2.csv_0_0_0.csv.gz union
select $1,$2,$3 from #test_row_stage/load_3.csv_0_0_0.csv.gz union
select $1,$2,$3 from #test_row_stage/load_4.csv_0_0_0.csv.gz;
+----+----+------------+
| $1 | $2 | $3 |
|----+----+------------|
| 0 | 1 | my name-0 |
| 4 | 1 | my name-4 |
| 8 | 1 | my name-8 |
| 1 | 2 | my name-1 |
| 5 | 2 | my name-5 |
| 9 | 2 | my name-9 |
| 2 | 3 | my name-2 |
| 6 | 3 | my name-6 |
| 10 | 3 | my name-10 |
| 3 | 4 | my name-3 |
| 7 | 4 | my name-7 |
| 11 | 4 | my name-11 |
+----+----+------------+
There is no OOB for this as I understand, but you can write custom code and fetch values and use them to name files and copy them to stage/s3.
Please refer below for something similar -
EXECUTE IMMEDIATE $$
DECLARE
company varchar2(30);
BU varchar2(30);
eval_desc varchar2(30);
member_id varchar2(30);
file_name varchar2(30);
c1 CURSOR FOR SELECT * FROM test_pivot;
BEGIN
for record in c1 do
company:=record.company;
BU:=record.BU;
eval_desc:=record.eval_desc;
member_id:=record.member_id;
file_name:='load'||'_'||member_id||'.csv';
create or replace temporary table temp_test_pvt(company varchar2(30),BU varchar2(30),eval_desc varchar2(30),member_id varchar2(30));
insert into temp_test_pvt values (:company,:bu,:eval_desc,:member_id);
execute immediate 'copy into #test_row_stage/'||:file_name||' from (select * from temp_test_pvt) overwrite=false';
end for;
RETURN 0;
END;
$$
;
Also, refer a similar post here -
Copy JSON data from Snowflake into S3

All caps word with lagging hyphen or plus doesn't match with CONTAINS in SQL Server 2008

When using a simple_termed CONTAINS statement on a fulltext column, I was very surprised to run across this behavioral discrepancy in SQL Server 2008 between all caps words and mixed/lowercase words with trailing hyphens - [or +, see end]. Consider this case:
CREATE TABLE has_hyphens (
id INT IDENTITY,
t VARCHAR(20)
);
CREATE FULLTEXT CATALOG ft_catalog AS DEFAULT;
ALTER TABLE has_hyphens ADD CONSTRAINT u_id UNIQUE NONCLUSTERED (id);
CREATE FULLTEXT INDEX ON has_hyphens(t) KEY INDEX u_id WITH STOPLIST=SYSTEM;
INSERT INTO has_hyphens(t) VALUES ('foo- bar'), ('FoO- bar'), ('FOO- bar');
The confusing part is with the following SELECT:
SELECT t FROM has_hyphens WHERE CONTAINS(t, 'foo');
/*
+----------+
| t |
+----------+
| foo- bar |
| FoO- bar |
+----------+
*/
which returns the rows with mixed- or lowercase foo, but not FOO- bar. Looking at the fulltext keywords:
SELECT * FROM sys.dm_fts_index_keywords_by_document(
DB_ID('sandbox'),
OBJECT_ID('has_hyphens')
);
/*
+--------------------+--------------+-----------+-------------+-----------------+
| keyword | display_term | column_id | document_id | occurence_count |
+--------------------+--------------+-----------+-------------+-----------------+
| 0x006200610072 | bar | 2 | 1 | 1 |
| 0x006200610072 | bar | 2 | 2 | 1 |
| 0x006200610072 | bar | 2 | 3 | 1 |
| 0x0066006F006F | foo | 2 | 1 | 1 |
| 0x0066006F006F | foo | 2 | 2 | 1 |
| 0x0066006F006F002D | foo- | 2 | 1 | 1 |
| 0x0066006F006F002D | foo- | 2 | 2 | 1 |
| 0x0066006F006F002D | foo- | 2 | 3 | 1 |
| 0xFF | END OF FILE | 2 | 1 | 1 |
| 0xFF | END OF FILE | 2 | 2 | 1 |
| 0xFF | END OF FILE | 2 | 3 | 1 |
+--------------------+--------------+-----------+-------------+-----------------+
*/
We see row 3 with FOO- bar doesn't have a foo term alone, only a foo- term with a trailing hyphen, which is why the exact match doesn't hit. Why is this?
I've tested all other ASCII word breakers for English, and + also inhibits matching. It is alone with hyphen though, none of the other 90 characters do.
This behavior appeared for me in SQL Server 2008 Express with Advanced Services, but I cannot reproduce this in SQL Server 2014 Enterprise, perhaps suggesting it's version dependent?
I tested with the query:
-- Helper table for generating ASCII characters
CREATE TABLE incr( id INT IDENTITY );
INSERT INTO incr VALUES (1), (2), (3), (4), (5), (6), (7), -- snip, to 255
-- reusing table despite now-inaccurate name
TRUNCATE TABLE has_hyphens;
INSERT INTO has_hyphens
SELECT 'FOO'+CHAR(AVG(id))+' bar' FROM incr
CROSS APPLY sys.dm_fts_parser('"word1'+CHAR(id)+'word2"', 1033, 0, 0) fts
WHERE id<>34 AND id<256
GROUP BY id
HAVING COUNT(fts.keyword)>1; -- generate all 'FOO'+[breaker character]+' bar'
-- combinations
SELECT * FROM has_hyphens WHERE NOT CONTAINS(t, 'FOO');

How to create a cross tab (in crystal) from multiple columns (in sql)

I have 5 columns in SQL that I need to turn into a cross tab in Crystal.
This is what I have:
Key | RELATIONSHIP | DISABLED | LIMITED | RURAL | IMMIGRANT
-----------------------------------------------------------------
1 | Other Dependent | Yes | No | No | No
2 | Victim/Survivor | No | No | No | No
3 | Victim/Survivor | Yes | No | No | No
4 | Child | No | No | No | No
5 | Victim/Survivor | No | No | No | No
6 | Victim/Survivor | No | No | No | No
7 | Child | No | No | No | No
8 | Victim/Survivor | No | Yes | Yes | Yes
9 | Child | No | Yes | Yes | Yes
10 | Child | No | Yes | Yes | Yes
This is what I want the cross tab to look like (Distinct count on Key):
| Victim/Survivor | Child | Other Dependent | Total |
--------------------------------------------------------------
| DISABLED | 1 | 0 | 1 | 2 |
--------------------------------------------------------------
| LIMITED | 1 | 2 | 0 | 3 |
--------------------------------------------------------------
| RURAL | 1 | 2 | 0 | 3 |
--------------------------------------------------------------
| IMMIGRANT | 1 | 2 | 0 | 3 |
--------------------------------------------------------------
| TOTAL | 4 | 6 | 1 | 11 |
--------------------------------------------------------------
I used this formula in Crystal in an effort to combine 4 columns (Field name = {#OTHERDEMO})...
IF {TABLE.DISABLED} = "YES" THEN "DISABLED" ELSE
IF {TABLE.LIMITED} = "YES" THEN "LIMITED" ELSE
IF {TABLE.IMMIGRANT} = "YES" THEN "IMMIGRANT" ELSE
IF {TABLE.RURAL} = "YES" THEN "RURAL"
...then made the cross-tab with #OTHERDEMO as the rows, RELATIONSHIP as the Columns with a distinct count on KEY:
Problem is, once crystal hits the first "Yes" it stops counting thus not categorizing correctly in the cross-tab. So I get a table that counts the DISABILITY first and gives the correct display, then counts the Limited and gives some info, but then dumps everything else.
In the past, I have done mutiple conditional formulas...
IF {TABLE.DISABLED} = "YES" AND {TABLE.RELATIONSHIP} = "Victim/Survivor" THEN {TABLE.KEY} ELSE {#NULL}
(the #null formula is because Crystal, notoriously, gets confused with nulls.)
...then did a distinct count on Key, and finally summed it in the footer.
I am convinced there is another way to do this. Any help/ideas would be greatly appreciated.
If you unpivot those "DEMO" columns into rows it will make the crosstab super easy...
select
u.[Key],
u.[RELATIONSHIP],
u.[DEMO]
from
Table1
unpivot (
[b] for [DEMO] in ([DISABLED], [LIMITED], [RURAL], [IMMIGRANT])
) u
where
u.[b] = 'Yes'
SqlFiddle
or if you were stuck on SQL2000 compatibility level you could manually unpivot the Yes values...
select [Key], [REALTIONSHIP], [DEMO] = cast('DISABLED' as varchar(20))
from Table1
where [DISABLED] = 'Yes'
union
select [Key], [REALTIONSHIP], [DEMO] = cast('LIMITED' as varchar(20))
from Table1
where [LIMITED] = 'Yes'
union
select [Key], [REALTIONSHIP], [DEMO] = cast('RURAL' as varchar(20))
from Table1
where [RURAL] = 'Yes'
union
select [Key], [REALTIONSHIP], [DEMO] = cast('IMMIGRANT' as varchar(20))
from Table1
where [IMMIGRANT] = 'Yes'
For the crosstab, use a count on the Key column (aka row count), [DEMO] on rows, and [RELATIONSHIP] on columns.

SQL Server. Join two tables n a view, take rows from one and turn into columns [duplicate]

This question already has answers here:
Efficiently convert rows to columns in sql server
(5 answers)
Closed 8 years ago.
I'm pretty new to SQL Server so don't really know what I'm doing with this. I have two tables, which might look like this:
table 1
| ID | customer | Date |
| 1 | company1 | 01/08/2014 |
| 2 | company2 | 10/08/2014 |
| 3 | company3 | 25/08/2014 |
table 2
| ID | Status | Days |
| 1 | New | 6 |
| 1 | In Work | 25 |
| 2 | New | 17 |
| 3 | New | 14 |
| 3 | In Work | 72 |
| 3 | Complete | 25 |
What I need to do is join based on the ID, and create new columns to show how long each ID has been in each status. Every time an order goes to a new status, a new line is added and the number of days is counted as in the 2nd table above. What I need to create from this, should look like this:
| ID | customer | Date | New | In Work | Complete |
| 1 | company1 | 01/08/2014 | 6 | 25 | |
| 2 | company2 | 10/08/2014 | 17 | | |
| 3 | company3 | 25/08/2014 | 14 | 72 | 25 |
So what do I need to to to create this?
Thanks for any help, as I say I'm pretty new to this.
I would suggest that AHiggins' link is a better candidate to mark this as a dupe rather than the one that's actually been selected because his link involves a join.
WITH [TimeTable] AS (
SELECT
T1.ID,
T1.[Date],
T2.[Status] AS [Status],
T2.[Days]
FROM
dbo.Table1 T1
INNER JOIN dbo.Table2 T2
ON T2.ID = T1.ID
)
SELECT *
FROM
[TimeTable]
PIVOT (MAX([Days]) FOR [Status] IN ([New], [Complete], [In Work])) TT
;

Query a query's column name in SQL Server

I want to query in SQL Server a column's name. I know it is possible to get a table's columns from the system table, but unfortunately that's not enough for me.
Example:
I have a table that contains an ID column and a string column. The table's name is test, and it has a testID and a test column.
This query:
select column_name
from information_schema.columns
where table_name = 'teszt'
return the names of the columns of my table. So it returns testID and Test.
What I want is when I use a query like this:
select count(*) as Amount from test
I want a query that can return the column names of my query. So in this specific case it returns the string 'Amount'. I don't know if that is possible.
Not sure if there is an easier way of getting the name of columns with aliases, but one way of doing it is via XML. This query will return one row per column in the inner query:
select T1.res.value('local-name(.)', 'varchar(50)')
from (select cast(
(
select count(*) as Amount from test
for xml raw) as xml
)) q(res)
CROSS APPLY q.res.nodes('/row/#*') as T1(res)
In SQL Server 2012 you have a stored procedure that you can use for exactly this purpose.
sp_describe_first_result_set (Transact-SQL)
SQL Fiddle
MS SQL Server 2012 Schema Setup:
create table test(id int);
Query 1:
exec sp_describe_first_result_set N'select count(*) as Amount from test'
Results:
| IS_HIDDEN | COLUMN_ORDINAL | NAME | IS_NULLABLE | SYSTEM_TYPE_ID | SYSTEM_TYPE_NAME | MAX_LENGTH | PRECISION | SCALE | COLLATION_NAME | USER_TYPE_ID | USER_TYPE_DATABASE | USER_TYPE_SCHEMA | USER_TYPE_NAME | ASSEMBLY_QUALIFIED_TYPE_NAME | XML_COLLECTION_ID | XML_COLLECTION_DATABASE | XML_COLLECTION_SCHEMA | XML_COLLECTION_NAME | IS_XML_DOCUMENT | IS_CASE_SENSITIVE | IS_FIXED_LENGTH_CLR_TYPE | SOURCE_SERVER | SOURCE_DATABASE | SOURCE_SCHEMA | SOURCE_TABLE | SOURCE_COLUMN | IS_IDENTITY_COLUMN | IS_PART_OF_UNIQUE_KEY | IS_UPDATEABLE | IS_COMPUTED_COLUMN | IS_SPARSE_COLUMN_SET | ORDINAL_IN_ORDER_BY_LIST | ORDER_BY_IS_DESCENDING | ORDER_BY_LIST_LENGTH | TDS_TYPE_ID | TDS_LENGTH | TDS_COLLATION_ID | TDS_COLLATION_SORT_ID |
|-----------|----------------|--------|-------------|----------------|------------------|------------|-----------|-------|----------------|--------------|--------------------|------------------|----------------|------------------------------|-------------------|-------------------------|-----------------------|---------------------|-----------------|-------------------|--------------------------|---------------|-----------------|---------------|--------------|---------------|--------------------|-----------------------|---------------|--------------------|----------------------|--------------------------|------------------------|----------------------|-------------|------------|------------------|-----------------------|
| 0 | 1 | Amount | 1 | 56 | int | 4 | 10 | 0 | (null) | (null) | (null) | (null) | (null) | (null) | (null) | (null) | (null) | (null) | 0 | 0 | 0 | (null) | (null) | (null) | (null) | (null) | 0 | (null) | 0 | 0 | 0 | (null) | (null) | (null) | 38 | 4 | (null) | (null) |
Maybe you want something like this? :-)
SELECT AMOUNT
FROM
(
SELECT COUNT(*) AS AMOUNT
FROM TEST
)X

Resources