NEWID () en tabla virtual unida provoca comportamiento involuntario de aplicación cruzada

9

Mi consulta de trabajo real era una unión interna, pero este simple ejemplo con unión cruzada casi siempre reproduce el problema.

SELECT *
FROM (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )
CROSS JOIN (
    SELECT NEWID() TEST_ID
) BB ( B )

Con mi unión interna, tuve muchas filas para las cuales agregué a cada uno un GUID usando la función NEWID (), y para aproximadamente 9 de cada 10 filas, la multiplicación con la tabla virtual de 2 filas produjo los resultados esperados, solo 2 copias de el mismo GUID, mientras que 1 de cada 10 produciría resultados diferentes. Esto fue inesperado, por decir lo menos, y me costó mucho tratar de encontrar este error en mi script de generación de datos de prueba.

Si echa un vistazo a las siguientes consultas utilizando también funciones getdate y sysdatetime no deterministas, no verá esto, no lo veo de todos modos: siempre veo el mismo valor de fecha y hora en ambas filas de resultados finales.

SELECT *
FROM (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )
CROSS JOIN (
    SELECT GETDATE() TEST_ID
) BB ( B )

SELECT *
FROM (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )
CROSS JOIN (
    SELECT SYSDATETIME() TEST_ID
) BB ( B )

Actualmente estoy usando SQL Server 2008 y mi trabajo por ahora es cargar mis filas con GUID en una variable de tabla antes de terminar mi script de generación de datos aleatorio. Una vez que los tengo como valores en una tabla en lugar de una tabla virtual, el problema desaparece.

Tengo una solución, pero estoy buscando formas de solucionarlo sin tablas o variables de tabla reales.

Mientras escribía esto, probé sin éxito estas posibilidades: 1) colocar el newid () en una tabla virtual anidada:

SELECT *
FROM (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )
CROSS JOIN (
    SELECT TEST_ID
    FROM (
        SELECT NEWID() TEST_ID
    ) TT
) BB ( B )

2) envolviendo el newid () dentro de una expresión de conversión como:

SELECT CAST(NEWID() AS VARCHAR(100)) TEST_ID

3) invertir el orden de aparición de las tablas virtuales dentro de la expresión de unión

SELECT *
FROM (
    SELECT NEWID() TEST_ID
) BB ( B )
CROSS JOIN (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )

4) utilizando una aplicación cruzada no correlacionada

SELECT *
FROM (
    SELECT NEWID() TEST_ID
) BB ( B )
CROSS APPLY (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )

Justo antes de finalmente publicar esta pregunta, ahora lo intenté con éxito, parece que se aplica una cruz correlacionada:

SELECT *
FROM (
    SELECT NEWID() TEST_ID
) BB ( B )
CROSS APPLY (
    SELECT A
    FROM (
        SELECT 1 UNION ALL
        SELECT 2
    ) TT ( A )
    WHERE BB.B IS NOT NULL
) AA ( A )

¿Alguien tiene alguna otra solución más elegante y simple? Realmente no quiero usar la aplicación cruzada o la correlación para una simple multiplicación de filas si no es necesario.

JM Hicks
fuente

Respuestas:

20

Este comportamiento es por diseño, como se explica en detalle en este informe de error de Connect . La respuesta de Microsoft más pertinente se reproduce a continuación por conveniencia (y en caso de que el enlace falle en algún momento):

Publicado por Microsoft el 7/7/2008 a las 9:27 a.m.

Cerrando el ciclo . . . He discutido esta pregunta con el equipo de desarrollo. Y finalmente hemos decidido no cambiar el comportamiento actual, por las siguientes razones:

  1. El optimizador no garantiza el tiempo ni el número de ejecuciones de funciones escalares. Este es un principio largamente establecido. Es el 'margen de maniobra' fundamental que le permite al optimizador suficiente libertad para obtener mejoras significativas en la ejecución del plan de consulta.

  2. Este "comportamiento de una vez por fila" no es un problema nuevo, aunque no se discute ampliamente. Comenzamos a modificar su comportamiento en el lanzamiento de Yukon. ¡Pero es bastante difícil precisar con precisión, en todos los casos, exactamente lo que significa! Por ejemplo, ¿se aplica a las filas intermedias calculadas 'en el camino' al resultado final? - en cuyo caso, depende claramente del plan elegido. ¿O se aplica solo a las filas que finalmente aparecerán en el resultado completado? - Hay una desagradable recursión aquí, ¡estoy seguro de que estarás de acuerdo!

  3. Como mencioné anteriormente, por defecto "optimizamos el rendimiento", lo cual es bueno para el 99% de los casos. El 1% de los casos en los que podría cambiar los resultados es bastante fácil de detectar ("funciones" de efectos secundarios como NEWID) y fácil de "arreglar" (rendimiento comercial, como consecuencia). Este valor predeterminado para "optimizar el rendimiento" nuevamente, está establecido desde hace mucho tiempo y es aceptado. (Sí, no es la postura elegida por los compiladores para los lenguajes de programación convencionales, pero que así sea).

Entonces, nuestras recomendaciones son:

  1. Evite la confianza en el tiempo no garantizado y la semántica del número de ejecuciones.
  2. Evite usar NEWID () en las expresiones de tabla.
  3. Use OPTION para forzar un comportamiento particular (rendimiento comercial)

Espero que esta explicación ayude a aclarar nuestras razones para cerrar este error, ya que "no se solucionará".

Las funciones GETDATEy SYSDATETIMEson de hecho no deterministas, pero se tratan como constantes de tiempo de ejecución para una consulta particular. En términos generales, esto significa que el valor de la función se almacena en caché cuando comienza la ejecución de la consulta, y el resultado se reutiliza para todas las referencias dentro de la consulta.

Ninguna de las 'soluciones' en la pregunta es segura; no hay garantía de que el comportamiento no cambie la próxima vez que se compile el plan, la próxima vez que aplique un paquete de servicio o una actualización acumulativa ... o por otros motivos.

La única solución segura es usar un objeto temporal de algún tipo, por ejemplo, una variable, tabla o función de múltiples instrucciones. El uso de una solución alternativa que parece funcionar hoy en día en base a la observación es una excelente manera de experimentar comportamientos inesperados en el futuro, generalmente en forma de alerta de aviso a las 3 am del domingo por la mañana.

Paul White 9
fuente
"Ninguna de las 'soluciones' en la pregunta son seguras". lo mismo. Cuando intenté aplicar uno de ellos a mi consulta de trabajo real, no me ayudó en absoluto.
JM Hicks