I'm fairly new to SQL, roughly a week of using it.
I'm trying to figure out a way to create an array of column names.
Through research, I've found a way to select column names and a way to select an nth row. However, I need some way to combine these two.
Here is the following code for each:
Selecting columns:
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = N'tablename'
Selecting nth row:
SELECT * FROM
(
SELECT ROW_NUMBER() OVER(ORDER BY Starting) NUM,
* FROM tablename
) A
WHERE NUM = 1
Is there a way to combine the two so that I can get a particular value for the nth row for the first select command (column names)?
SELECT COLUMN_NAME FROM
(
SELECT ROW_NUMBER() OVER(ORDER BY ORDINAL_POSITION) Num, COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = N'tablename'
) A
WHERE NUM = 1
ORDINAL_POSITION is nothing but "column identification number".
Related
On the newbie struggle bus and using Snowflake. I queried to find all of the tables in a database that have a column name containing a certain phrase. Is there a simple-ish way to select 10 (or any number, just something) rows from each of these tables in a single query?
What I’m working with so far:
Use database ‘mydatabase’;
Select table_name, column_name
From informationschema.columns
Where column_name like ‘%phrase%’
Let’s say I get 15 tables in the result. I’d like to see a small sample of records from each of those tables so I can confirm what actual values are in those ‘%phrase%’ columns. I’d LOVE to do it without individually querying each table in the list.
Any suggestions for someone who has given up on Googling how to do it?
This will take some steps.
First lets construct a query that will sample 10 rows from a list of tables:
with list_cols as (
select table_name, column_name
from information_schema.columns
where column_name like '%WEEK%'
), tables as (
select distinct table_name
from list_cols
)
select 'select * from ' || listagg(x, ' union all \n')
from (
select '(select \''|| table_name || '\' table_name, object_construct(x.*) sample_row from ' || table_name || ' x limit 10)' x
from tables
)
The above query will return a value like:
select * from (select 'CLAIRE_DECAYING' table_name, object_construct(x.*) sample_row from CLAIRE_DECAYING x limit 10) union all
(select 'CLAIRE_DECAYING_HUGE' table_name, object_construct(x.*) sample_row from CLAIRE_DECAYING_HUGE x limit 10) union all
(select 'DECAY_PUZZLE' table_name, object_construct(x.*) sample_row from DECAY_PUZZLE x limit 10) union all
(select 'DECAY_PUZZLE_100M' table_name, object_construct(x.*) sample_row from DECAY_PUZZLE_100M x limit 10) union all
(select 'DECAY_PUZZLE_10M' table_name, object_construct(x.*) sample_row from DECAY_PUZZLE_10M x limit 10) union all
(select 'QL2_CARS_WEEKLY_VIZ' table_name, object_construct(x.*) sample_row from QL2_CARS_WEEKLY_VIZ x limit 10) union all
(select 'SAMPLE_TABLE' table_name, object_construct(x.*) sample_row from SAMPLE_TABLE x limit 10) union all
(select 'QL2_CARS_WEEKLY' table_name, object_construct(x.*) sample_row from QL2_CARS_WEEKLY x limit 10)
Which is a query that you can run to get 10 sample rows of each input able.
Note that we use object_construct(*) to get around the need for each select to have the same # of columns: The whole row gets represented in one column.
Then to have that query run and get its output, you can either copy paste it and run it, or capture that value and execute immediately:
https://docs.snowflake.com/en/sql-reference/sql/execute-immediate.html
I have a schema in my SQL table out of which some table has a time value stamp (same column name 'timestamp' in all the tables in the schema) and I need to create a new table which will give the latest time stamp for each such table. I have achieved a part which will give me a table with 2 columns, one the table name column and another column which gives the query for each table which if runs will give me the latest timeStamp for each table in table name Column. The script I used is as follows and I show 3 rows as an example:
WITH CTE AS
(
SELECT
CONCAT(schema_name(t.schema_id), '.',t.name) AS table_name,
c.name AS 'time_stamp'
FROM
sys.tables t
INNER JOIN
sys.columns c ON c.object_id = t.object_id
WHERE
schema_name(t.schema_id) = 'PROD'
AND c.name = 'timestamp'
)
SELECT table_name, time_stamp
INTO #TEMP_TABLE
FROM CTE
DECLARE #i int = 1, #c int = (SELECT COUNT(*) FROM #TEMP_TABLE)
DECLARE #Result TABLE
(
tName varchar(500),
tStamp varchar(500)
)
WHILE (#i <= #c)
BEGIN
INSERT INTO #Result
SELECT
table_name,
'SELECT MAX('+ time_stamp +') FROM ' + table_name
FROM #TEMP_TABLE;
SET #i = #i + 1
END
DROP TABLE #TEMP_TABLE
SELECT * FROM #RESULT
When I run this script I get the following table (3 rows shown as an illustration)
My output (O)
tName tStamp
-----------------------------------------------------------
PROD.table_A SELECT MAX(time_stamp) FROM PROD.table_A
PROD.table_B SELECT MAX(time_stamp) FROM PROD.table_B
PROD.table_C SELECT MAX(time_stamp) FROM PROD.table_C
However what I want is the value of the query in the tStamp column and not the query string. So actually the output table should look like (say assuming the query in each of the above rows in column tStamp. I put in some max values as an example when we run each query in tStamp column)
My final expected output (F)
tName tStamp
------------------------------------------
PROD.table_A 2021-10-12 14:20:56.000
PROD.table_B 2021-11-01 19:04:35.000
PROD.table_C 2021-10-23 08:07:12.000
I am in a limbo at this stage not sure, how to get the table F from table O. So I will really appreciate any help. If it can be possible to tweak something which I am doing to get directly the output table F or if we can work on the table O to get to table F anything can help.
Thanks in advance.
If this is a one shot thing, I would consider just using a macro (vim, excel) to generate the query text for each table using your CTE results and then paste it back in and run.
If not, you could consider some of the suggestions for dynamic sql in this article: [https://www.mssqltips.com/sqlservertip/1160/execute-dynamic-sql-commands-in-sql-server/][1]
I am exploring provided code. I have various CTEs, and I want to count the columns in each CTE? Or select the differences in columns per CTE?
The below code will grab the count from a table in the DB, but I'm not sure how to apply this to CTEs?
select count(*)
from INFORMATION_SCHEMA.columns
where TABLE_CATALOG = 'db_name'
and TABLE_NAME = 'table_name'
First Create CTE and then insert just one record into temp table , then query the sys.columns table to get the list of the columns in CTE
IF OBJECT_ID('tempdb.dbo.#temp', 'U') IS NOT NULL
DROP TABLE #temp;
go
With CTE1 as (select * from table_name)
select top 1 * into #temp from CTE1
SELECT count(*) FROM tempdb.sys.columns where object_id = object_id('tempdb..#temp')
I'm trying to create a query that returns the paging of the rows from a generic table because the call that invokes this query will work on more tables and databases; the program where I'm working built the query in different parts of the code, and the only variable parameters that the database receive are #myTable, #offset and #row.
Initially, I tried a query for a single table like this
SELECT * FROM myTable ORDER BY myPKColumn
OFFSET #offset ROWS
FETCH NEXT #row ROWS ONLY;
and, with #offset = 0 and #row = 10, return me the first 10 rows ASC.
So far it works all without problem, but if I need to use this query for every table, I have problems to specify the column in the ORDER BY clause; to avoid this problem I've tried this query
SELECT * FROM #myTable ORDER BY (SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_NAME=#myTable and CONSTRAINT_NAME like 'PK_%')
OFFSET #offset ROWS
FETCH NEXT #row ROWS ONLY;
this because I know from the project structure that all the tables that I could invoke have a single primary key.
The query works, but the results are a bit strange: if I pass the same parameter of the precedent query, the result is the rows from 11 to 2 DESC.
Also, changing the offset value, I figured this strange relation:
If [ #offset <= ( 100 - #row ) ]
>
the results are the first (#row+1) DESC without the first row
( in this case all the rows between #row and #offset will never be retrieved)
Else
>
the result are the first #row starting from the (#offset + 1)
examples:
#offset = 90 and #row = 10 return rows from 13 to 2
#offset = 91 and #row = 10 return rows from 92 to 101
#offset = 101 and #row = 10 return rows from 102 to 111
I've tried some little variations like specify OBJECT_ID(...) or OBJECT_NAME(OBJECT_ID(...)) outside the second select, but without changes; also, I've tried to use the debug option, but I haven't the privileges to use it.
Any idea of what could cause this strange results?
P.S: sorry for my bad English; also it's my first post so, if I missed something I'll edit without problems
EDIT:
i found a way that logically should work, but it doesn't work:
i discovered that writing a query like
SELECT * FROM myTable ORDER BY 5 asc;
order my rows ascendent considering the values of the 5th column.
so i made the query:
select ORDINAL_POSITION from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME = 'myTable' and COLUMN_NAME = (SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_NAME='myTable' and CONSTRAINT_NAME like 'PK_%')
that return me the position of the column with the primary key (unluckily for me, there are a lot of table where it isn't in the 1st position); the value that return is correct (i've verified a lot of table and it works always)
So, the next step, if i run the query
select * from mytable ORDER BY (select ORDINAL_POSITION from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME = 'myTable' and COLUMN_NAME = (SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_NAME='myTable' and CONSTRAINT_NAME like 'PK_%' )) asc;
works with a little problem: i can declare asc or desc, but the result is always asc. (it's not a real problem, because like said before the order is not important im my case).
Now the real problem.
The last step is the query
select * from mytable ORDER BY (select ORDINAL_POSITION from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME = 'myTable' and COLUMN_NAME = (SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_NAME='myTable' and CONSTRAINT_NAME like 'PK_%' )) asc
OFFSET 0 ROWS
FETCH NEXT 10 ROWS ONLY;
and here nothing has changed from the problem before the edit: the result are the rows from 11 to 2 desc.
to avoid the possibility that the error could be a string interpretation of the number returned, i've add a cast to int
SELECT * FROM myTable
ORDER BY CAST((select ORDINAL_POSITION from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME = 'myTable' and COLUMN_NAME = (SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_NAME='myTable' and CONSTRAINT_NAME like 'PK_%' ))AS int) asc
OFFSET 0 ROWS
FETCH NEXT 10 ROWS ONLY;
but nothing changed.
It seems that the new query works fine until the offset/fetch is added.
The Situation
We have an application where we store machine settings in a SQL table. When the user changes a parameter of the machine, we create a "revision", that means we insert a row into a table. This table has about 200 columns.
In our application, the user can take a look on each revision.
The Problem
We want to highlight the parameters that have changed since the last revision.
The Question
Is there an SQL-only way to get the column names of the differences between two rows?
An Example
ID | p_x | p_y | p_z
--------------------
11 | xxx | yyy | zzz
12 | xxy | yyy | zzy
The query should return p_x and p_z.
EDIT
The table has 200 columns, not rows...
MY WAY OUT
My intention was to find a "one-line-SQL-statement" for this problem.
I see in the answers below, it's kind a bigger thing in SQL.
As there is no short, SQL-included solution for this problem, solving it in the backend of our software (c#) is of course much easier!
But as this is not a real "answer" to my question, I don't mark it as answered.
Thanks for the help.
You say:
We want to highlight the parameters that have changed since the last revision.
This implies that you want the display (or report) to make the parameters that changed stand out.
If you're going to show all the parameters anyway, it would be a lot easier to do this programmatically in the front end. It would be a much simpler problem in a programming language. Unfortunately, not knowing what your front end is, I can't give you particular recommendations.
If you really can't do it in the front end but have to receive this information in a query from the database (you did say "SQL-only"), you need to specify the format you'd like the data in. A single-column list of the columns that changed between the two records? A list of columns with a flag indicating which columns did or didn't change?
But here's one way that would work, though in the process it converts all your fields to nvarchars before it does its comparison:
Use the technique described here (disclaimer: that's my blog) to transform your records into ID-name-value pairs.
Join the resulting data set to itself on ID, so that you can compare the values and print those that have changed:
with A as (
-- We're going to return the product ID, plus an XML version of the
-- entire record.
select ID
, (
Select *
from myTable
where ID = pp.ID
for xml auto, type) as X
from myTable pp )
, B as (
-- We're going to run an Xml query against the XML field, and transform it
-- into a series of name-value pairs. But X2 will still be a single XML
-- field, associated with this ID.
select Id
, X.query(
'for $f in myTable/#*
return
<data name="{ local-name($f) }" value="{ data($f) }" />
')
as X2 from A
)
, C as (
-- We're going to run the Nodes function against the X2 field, splitting
-- our list of "data" elements into individual nodes. We will then use
-- the Value function to extract the name and value.
select B.ID as ID
, norm.data.value('#name', 'nvarchar(max)') as Name
, norm.data.value('#value', 'nvarchar(max)') as Value
from B cross apply B.X2.nodes('/myTable') as norm(data))
-- Select our results.
select *
from ( select * from C where ID = 123) C1
full outer join ( select * from C where ID = 345) C2
on C1.Name = c2.Name
where c1.Value <> c2.Value
or not (c1.Value is null and c2.Value is null)
You can use unpivot and pivot. The key is to transpose data so that you can use where [11] != [12].
WITH CTE AS (
SELECT *
FROM
(
SELECT ID, colName, val
FROM tblName
UNPIVOT
(
val
FOR colName IN ([p_x],[p_y],[p_z])
) unpiv
) src
PIVOT
(
MAX(val)
FOR ID IN ([11], [12])
) piv
)
SELECT colName
--SELECT *
FROM CTE WHERE [11] != [12]
If there are only a few columns in the table, it's easy to simply put [p_x],[p_y],[p_z], but obviously it's not convenient to type 50 or more columns. Even though you may use this trick to drag and drop, or copy/paste, the column names from the table, it's still bulky. And for that, you may use the SELECT * EXCEPT strategy with dynamic sql.
DECLARE #TSQL NVARCHAR(MAX), #colNames NVARCHAR(MAX)
SELECT #colNames = COALESCE(#colNames + ',' ,'') + [name]
FROM syscolumns WHERE name <> 'ID' and id = (SELECT id FROM sysobjects WHERE name = 'tablelName')
SET #TSQL = '
WITH CTE AS (
SELECT *
FROM
(
SELECT ID, colName, val
FROM tablelName
UNPIVOT
(
val
FOR colName IN (' + #colNames + ')
) unpiv
) src
PIVOT
(
MAX(val)
FOR ID IN ([11], [12])
) piv
)
--SELECT colName
SELECT *
FROM CTE WHERE [11] != [12]
'
EXEC sp_executesql #TSQL
Here's one way using UNPIVOT:
;WITH
cte AS
(
SELECT CASE WHEN t1.p_x <> t2.p_x THEN 1 ELSE 0 END As p_x,
CASE WHEN t1.p_y <> t2.p_y THEN 1 ELSE 0 END As p_y,
CASE WHEN t1.p_z <> t2.p_z THEN 1 ELSE 0 END As p_z
FROM MyTable t1, MyTable t2
WHERE t1.ID = 11 AND t2.ID = 12 -- enter the two revisions to compare here
)
SELECT *
FROM cte
UNPIVOT (
Changed FOR ColumnName IN (p_x, p_y, p_z)
) upvt
WHERE upvt.Changed = 1
You have to add code to handle NULLs during the comparisons. You can also build the query dynamically if there are lots of columns in your table.
for sql server 2012 you can do something like that (duplicate it for
each column):
SELECT iif((p_x != lead(p_x) over(ORDER BY p_x)),
(SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'tbl'
AND
TABLE_SCHEMA='schema'
AND
ORDINAL_POSITION='1')
,NULL)
FROM tbl
for sql server 2008 try
DECLARE #x int =11 -- first id
WHILE #x!=(SELECT count(1) FROM tbl)
BEGIN --comparison of two adjacent rows
if (SELECT p_x FROM tbl WHERE id=#x)!=(SELECT p_x FROM tbl WHERE id=#x+1)
BEGIN
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'tbl' --insert your table
AND
TABLE_SCHEMA='schema' --insert your schema
AND
ORDINAL_POSITION='1' --first column 'p_x'
END
set #x=#x+1
END